summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /netwerk/protocol
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/protocol')
-rw-r--r--netwerk/protocol/about/moz.build32
-rw-r--r--netwerk/protocol/about/nsAboutBlank.cpp52
-rw-r--r--netwerk/protocol/about/nsAboutBlank.h32
-rw-r--r--netwerk/protocol/about/nsAboutCache.cpp533
-rw-r--r--netwerk/protocol/about/nsAboutCache.h199
-rw-r--r--netwerk/protocol/about/nsAboutCacheEntry.cpp559
-rw-r--r--netwerk/protocol/about/nsAboutCacheEntry.h86
-rw-r--r--netwerk/protocol/about/nsAboutProtocolHandler.cpp428
-rw-r--r--netwerk/protocol/about/nsAboutProtocolHandler.h139
-rw-r--r--netwerk/protocol/about/nsAboutProtocolUtils.h65
-rw-r--r--netwerk/protocol/about/nsIAboutModule.idl113
-rw-r--r--netwerk/protocol/data/DataChannelChild.cpp58
-rw-r--r--netwerk/protocol/data/DataChannelChild.h40
-rw-r--r--netwerk/protocol/data/DataChannelParent.cpp105
-rw-r--r--netwerk/protocol/data/DataChannelParent.h39
-rw-r--r--netwerk/protocol/data/moz.build29
-rw-r--r--netwerk/protocol/data/nsDataChannel.cpp146
-rw-r--r--netwerk/protocol/data/nsDataChannel.h31
-rw-r--r--netwerk/protocol/data/nsDataHandler.cpp272
-rw-r--r--netwerk/protocol/data/nsDataHandler.h56
-rw-r--r--netwerk/protocol/data/nsDataModule.cpp15
-rw-r--r--netwerk/protocol/file/FileChannelChild.cpp52
-rw-r--r--netwerk/protocol/file/FileChannelChild.h35
-rw-r--r--netwerk/protocol/file/FileChannelParent.cpp105
-rw-r--r--netwerk/protocol/file/FileChannelParent.h39
-rw-r--r--netwerk/protocol/file/moz.build40
-rw-r--r--netwerk/protocol/file/nsFileChannel.cpp569
-rw-r--r--netwerk/protocol/file/nsFileChannel.h59
-rw-r--r--netwerk/protocol/file/nsFileProtocolHandler.cpp266
-rw-r--r--netwerk/protocol/file/nsFileProtocolHandler.h28
-rw-r--r--netwerk/protocol/file/nsIFileChannel.idl17
-rw-r--r--netwerk/protocol/file/nsIFileProtocolHandler.idl102
-rw-r--r--netwerk/protocol/gio/GIOChannelChild.cpp458
-rw-r--r--netwerk/protocol/gio/GIOChannelChild.h111
-rw-r--r--netwerk/protocol/gio/GIOChannelParent.cpp324
-rw-r--r--netwerk/protocol/gio/GIOChannelParent.h80
-rw-r--r--netwerk/protocol/gio/PGIOChannel.ipdl51
-rw-r--r--netwerk/protocol/gio/components.conf24
-rw-r--r--netwerk/protocol/gio/moz.build42
-rw-r--r--netwerk/protocol/gio/nsGIOProtocolHandler.cpp1027
-rw-r--r--netwerk/protocol/gio/nsGIOProtocolHandler.h38
-rw-r--r--netwerk/protocol/http/ASpdySession.cpp94
-rw-r--r--netwerk/protocol/http/ASpdySession.h115
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamChild.cpp204
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamChild.h51
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamParent.cpp87
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamParent.h52
-rw-r--r--netwerk/protocol/http/AltServiceChild.cpp111
-rw-r--r--netwerk/protocol/http/AltServiceChild.h41
-rw-r--r--netwerk/protocol/http/AltServiceParent.cpp49
-rw-r--r--netwerk/protocol/http/AltServiceParent.h39
-rw-r--r--netwerk/protocol/http/AltSvcTransactionChild.cpp75
-rw-r--r--netwerk/protocol/http/AltSvcTransactionChild.h39
-rw-r--r--netwerk/protocol/http/AltSvcTransactionParent.cpp64
-rw-r--r--netwerk/protocol/http/AltSvcTransactionParent.h52
-rw-r--r--netwerk/protocol/http/AlternateServices.cpp1354
-rw-r--r--netwerk/protocol/http/AlternateServices.h271
-rw-r--r--netwerk/protocol/http/BackgroundChannelRegistrar.cpp99
-rw-r--r--netwerk/protocol/http/BackgroundChannelRegistrar.h55
-rw-r--r--netwerk/protocol/http/BackgroundDataBridgeChild.cpp60
-rw-r--r--netwerk/protocol/http/BackgroundDataBridgeChild.h41
-rw-r--r--netwerk/protocol/http/BackgroundDataBridgeParent.cpp63
-rw-r--r--netwerk/protocol/http/BackgroundDataBridgeParent.h36
-rw-r--r--netwerk/protocol/http/BinaryHttpRequest.cpp51
-rw-r--r--netwerk/protocol/http/BinaryHttpRequest.h48
-rw-r--r--netwerk/protocol/http/CacheControlParser.cpp134
-rw-r--r--netwerk/protocol/http/CacheControlParser.h52
-rw-r--r--netwerk/protocol/http/CachePushChecker.cpp248
-rw-r--r--netwerk/protocol/http/CachePushChecker.h45
-rw-r--r--netwerk/protocol/http/ClassOfService.h76
-rw-r--r--netwerk/protocol/http/ConnectionDiagnostics.cpp239
-rw-r--r--netwerk/protocol/http/ConnectionEntry.cpp1102
-rw-r--r--netwerk/protocol/http/ConnectionEntry.h231
-rw-r--r--netwerk/protocol/http/ConnectionHandle.cpp98
-rw-r--r--netwerk/protocol/http/ConnectionHandle.h41
-rw-r--r--netwerk/protocol/http/DnsAndConnectSocket.cpp1391
-rw-r--r--netwerk/protocol/http/DnsAndConnectSocket.h274
-rw-r--r--netwerk/protocol/http/EarlyHintPreconnect.cpp106
-rw-r--r--netwerk/protocol/http/EarlyHintPreconnect.h24
-rw-r--r--netwerk/protocol/http/EarlyHintPreloader.cpp846
-rw-r--r--netwerk/protocol/http/EarlyHintPreloader.h199
-rw-r--r--netwerk/protocol/http/EarlyHintRegistrar.cpp130
-rw-r--r--netwerk/protocol/http/EarlyHintRegistrar.h82
-rw-r--r--netwerk/protocol/http/EarlyHintsService.cpp191
-rw-r--r--netwerk/protocol/http/EarlyHintsService.h59
-rw-r--r--netwerk/protocol/http/HPKEConfigManager.sys.mjs42
-rw-r--r--netwerk/protocol/http/HTTPSRecordResolver.cpp117
-rw-r--r--netwerk/protocol/http/HTTPSRecordResolver.h44
-rw-r--r--netwerk/protocol/http/Http2Compression.cpp1458
-rw-r--r--netwerk/protocol/http/Http2Compression.h207
-rw-r--r--netwerk/protocol/http/Http2HuffmanIncoming.h709
-rw-r--r--netwerk/protocol/http/Http2HuffmanOutgoing.h85
-rw-r--r--netwerk/protocol/http/Http2Push.cpp557
-rw-r--r--netwerk/protocol/http/Http2Push.h182
-rw-r--r--netwerk/protocol/http/Http2Session.cpp4513
-rw-r--r--netwerk/protocol/http/Http2Session.h626
-rw-r--r--netwerk/protocol/http/Http2Stream.cpp298
-rw-r--r--netwerk/protocol/http/Http2Stream.h59
-rw-r--r--netwerk/protocol/http/Http2StreamBase.cpp1324
-rw-r--r--netwerk/protocol/http/Http2StreamBase.h377
-rw-r--r--netwerk/protocol/http/Http2StreamTunnel.cpp764
-rw-r--r--netwerk/protocol/http/Http2StreamTunnel.h151
-rw-r--r--netwerk/protocol/http/Http3Session.cpp2522
-rw-r--r--netwerk/protocol/http/Http3Session.h388
-rw-r--r--netwerk/protocol/http/Http3Stream.cpp533
-rw-r--r--netwerk/protocol/http/Http3Stream.h165
-rw-r--r--netwerk/protocol/http/Http3StreamBase.h70
-rw-r--r--netwerk/protocol/http/Http3WebTransportSession.cpp518
-rw-r--r--netwerk/protocol/http/Http3WebTransportSession.h123
-rw-r--r--netwerk/protocol/http/Http3WebTransportStream.cpp667
-rw-r--r--netwerk/protocol/http/Http3WebTransportStream.h136
-rw-r--r--netwerk/protocol/http/HttpAuthUtils.cpp171
-rw-r--r--netwerk/protocol/http/HttpAuthUtils.h33
-rw-r--r--netwerk/protocol/http/HttpBackgroundChannelChild.cpp499
-rw-r--r--netwerk/protocol/http/HttpBackgroundChannelChild.h156
-rw-r--r--netwerk/protocol/http/HttpBackgroundChannelParent.cpp526
-rw-r--r--netwerk/protocol/http/HttpBackgroundChannelParent.h124
-rw-r--r--netwerk/protocol/http/HttpBaseChannel.cpp6479
-rw-r--r--netwerk/protocol/http/HttpBaseChannel.h1194
-rw-r--r--netwerk/protocol/http/HttpChannelChild.cpp3391
-rw-r--r--netwerk/protocol/http/HttpChannelChild.h481
-rw-r--r--netwerk/protocol/http/HttpChannelParams.ipdlh71
-rw-r--r--netwerk/protocol/http/HttpChannelParent.cpp2202
-rw-r--r--netwerk/protocol/http/HttpChannelParent.h333
-rw-r--r--netwerk/protocol/http/HttpConnectionBase.cpp101
-rw-r--r--netwerk/protocol/http/HttpConnectionBase.h246
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrChild.cpp191
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrChild.h52
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrParent.cpp319
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrParent.h43
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrShell.h229
-rw-r--r--netwerk/protocol/http/HttpConnectionUDP.cpp706
-rw-r--r--netwerk/protocol/http/HttpConnectionUDP.h138
-rw-r--r--netwerk/protocol/http/HttpInfo.cpp16
-rw-r--r--netwerk/protocol/http/HttpInfo.h24
-rw-r--r--netwerk/protocol/http/HttpLog.h73
-rw-r--r--netwerk/protocol/http/HttpTrafficAnalyzer.cpp269
-rw-r--r--netwerk/protocol/http/HttpTrafficAnalyzer.h51
-rw-r--r--netwerk/protocol/http/HttpTrafficAnalyzer.inc106
-rw-r--r--netwerk/protocol/http/HttpTransactionChild.cpp665
-rw-r--r--netwerk/protocol/http/HttpTransactionChild.h127
-rw-r--r--netwerk/protocol/http/HttpTransactionParent.cpp924
-rw-r--r--netwerk/protocol/http/HttpTransactionParent.h197
-rw-r--r--netwerk/protocol/http/HttpTransactionShell.h242
-rw-r--r--netwerk/protocol/http/HttpWinUtils.cpp111
-rw-r--r--netwerk/protocol/http/HttpWinUtils.h18
-rw-r--r--netwerk/protocol/http/InterceptedHttpChannel.cpp1714
-rw-r--r--netwerk/protocol/http/InterceptedHttpChannel.h316
-rw-r--r--netwerk/protocol/http/MockHttpAuth.cpp46
-rw-r--r--netwerk/protocol/http/MockHttpAuth.h31
-rw-r--r--netwerk/protocol/http/NetworkMarker.cpp188
-rw-r--r--netwerk/protocol/http/NetworkMarker.h40
-rw-r--r--netwerk/protocol/http/NullHttpChannel.cpp865
-rw-r--r--netwerk/protocol/http/NullHttpChannel.h64
-rw-r--r--netwerk/protocol/http/NullHttpTransaction.cpp212
-rw-r--r--netwerk/protocol/http/NullHttpTransaction.h94
-rw-r--r--netwerk/protocol/http/ObliviousHttpChannel.cpp860
-rw-r--r--netwerk/protocol/http/ObliviousHttpChannel.h72
-rw-r--r--netwerk/protocol/http/ObliviousHttpService.cpp235
-rw-r--r--netwerk/protocol/http/ObliviousHttpService.h46
-rw-r--r--netwerk/protocol/http/OpaqueResponseUtils.cpp679
-rw-r--r--netwerk/protocol/http/OpaqueResponseUtils.h216
-rw-r--r--netwerk/protocol/http/PAltDataOutputStream.ipdl42
-rw-r--r--netwerk/protocol/http/PAltService.ipdl38
-rw-r--r--netwerk/protocol/http/PAltSvcTransaction.ipdl23
-rw-r--r--netwerk/protocol/http/PBackgroundDataBridge.ipdl32
-rw-r--r--netwerk/protocol/http/PHttpBackgroundChannel.ipdl80
-rw-r--r--netwerk/protocol/http/PHttpChannel.ipdl147
-rw-r--r--netwerk/protocol/http/PHttpChannelParams.h290
-rw-r--r--netwerk/protocol/http/PHttpConnectionMgr.ipdl46
-rw-r--r--netwerk/protocol/http/PHttpTransaction.ipdl119
-rw-r--r--netwerk/protocol/http/PSpdyPush.h56
-rw-r--r--netwerk/protocol/http/ParentChannelListener.cpp292
-rw-r--r--netwerk/protocol/http/ParentChannelListener.h86
-rw-r--r--netwerk/protocol/http/PendingTransactionInfo.cpp136
-rw-r--r--netwerk/protocol/http/PendingTransactionInfo.h63
-rw-r--r--netwerk/protocol/http/PendingTransactionQueue.cpp287
-rw-r--r--netwerk/protocol/http/PendingTransactionQueue.h92
-rw-r--r--netwerk/protocol/http/QuicSocketControl.cpp128
-rw-r--r--netwerk/protocol/http/QuicSocketControl.h67
-rw-r--r--netwerk/protocol/http/README119
-rw-r--r--netwerk/protocol/http/SpeculativeTransaction.cpp89
-rw-r--r--netwerk/protocol/http/SpeculativeTransaction.h70
-rw-r--r--netwerk/protocol/http/TLSTransportLayer.cpp866
-rw-r--r--netwerk/protocol/http/TLSTransportLayer.h170
-rw-r--r--netwerk/protocol/http/TRRServiceChannel.cpp1581
-rw-r--r--netwerk/protocol/http/TRRServiceChannel.h177
-rw-r--r--netwerk/protocol/http/TimingStruct.h44
-rw-r--r--netwerk/protocol/http/TlsHandshaker.cpp336
-rw-r--r--netwerk/protocol/http/TlsHandshaker.h95
-rw-r--r--netwerk/protocol/http/WellKnownOpportunisticUtils.sys.mjs26
-rw-r--r--netwerk/protocol/http/binary_http/Cargo.toml11
-rw-r--r--netwerk/protocol/http/binary_http/src/binary_http.h24
-rw-r--r--netwerk/protocol/http/binary_http/src/lib.rs264
-rw-r--r--netwerk/protocol/http/components.conf33
-rw-r--r--netwerk/protocol/http/http2_huffman_table.txt257
-rw-r--r--netwerk/protocol/http/make_incoming_tables.py202
-rw-r--r--netwerk/protocol/http/make_outgoing_tables.py58
-rw-r--r--netwerk/protocol/http/metrics.yaml275
-rw-r--r--netwerk/protocol/http/moz.build219
-rw-r--r--netwerk/protocol/http/nsAHttpConnection.h292
-rw-r--r--netwerk/protocol/http/nsAHttpTransaction.h307
-rw-r--r--netwerk/protocol/http/nsCORSListenerProxy.cpp1753
-rw-r--r--netwerk/protocol/http/nsCORSListenerProxy.h130
-rw-r--r--netwerk/protocol/http/nsHttp.cpp1160
-rw-r--r--netwerk/protocol/http/nsHttp.h527
-rw-r--r--netwerk/protocol/http/nsHttpActivityDistributor.cpp298
-rw-r--r--netwerk/protocol/http/nsHttpActivityDistributor.h40
-rw-r--r--netwerk/protocol/http/nsHttpAtomList.h111
-rw-r--r--netwerk/protocol/http/nsHttpAuthCache.cpp349
-rw-r--r--netwerk/protocol/http/nsHttpAuthCache.h219
-rw-r--r--netwerk/protocol/http/nsHttpAuthManager.cpp105
-rw-r--r--netwerk/protocol/http/nsHttpAuthManager.h34
-rw-r--r--netwerk/protocol/http/nsHttpBasicAuth.cpp101
-rw-r--r--netwerk/protocol/http/nsHttpBasicAuth.h39
-rw-r--r--netwerk/protocol/http/nsHttpChannel.cpp10430
-rw-r--r--netwerk/protocol/http/nsHttpChannel.h869
-rw-r--r--netwerk/protocol/http/nsHttpChannelAuthProvider.cpp1913
-rw-r--r--netwerk/protocol/http/nsHttpChannelAuthProvider.h185
-rw-r--r--netwerk/protocol/http/nsHttpChunkedDecoder.cpp170
-rw-r--r--netwerk/protocol/http/nsHttpChunkedDecoder.h50
-rw-r--r--netwerk/protocol/http/nsHttpConnection.cpp2609
-rw-r--r--netwerk/protocol/http/nsHttpConnection.h388
-rw-r--r--netwerk/protocol/http/nsHttpConnectionInfo.cpp570
-rw-r--r--netwerk/protocol/http/nsHttpConnectionInfo.h317
-rw-r--r--netwerk/protocol/http/nsHttpConnectionMgr.cpp3863
-rw-r--r--netwerk/protocol/http/nsHttpConnectionMgr.h469
-rw-r--r--netwerk/protocol/http/nsHttpDigestAuth.cpp743
-rw-r--r--netwerk/protocol/http/nsHttpDigestAuth.h98
-rw-r--r--netwerk/protocol/http/nsHttpHandler.cpp2812
-rw-r--r--netwerk/protocol/http/nsHttpHandler.h918
-rw-r--r--netwerk/protocol/http/nsHttpHeaderArray.cpp475
-rw-r--r--netwerk/protocol/http/nsHttpHeaderArray.h334
-rw-r--r--netwerk/protocol/http/nsHttpNTLMAuth.cpp404
-rw-r--r--netwerk/protocol/http/nsHttpNTLMAuth.h36
-rw-r--r--netwerk/protocol/http/nsHttpRequestHead.cpp367
-rw-r--r--netwerk/protocol/http/nsHttpRequestHead.h155
-rw-r--r--netwerk/protocol/http/nsHttpResponseHead.cpp1220
-rw-r--r--netwerk/protocol/http/nsHttpResponseHead.h239
-rw-r--r--netwerk/protocol/http/nsHttpTransaction.cpp3628
-rw-r--r--netwerk/protocol/http/nsHttpTransaction.h599
-rw-r--r--netwerk/protocol/http/nsIBackgroundChannelRegistrar.idl63
-rw-r--r--netwerk/protocol/http/nsIBinaryHttp.idl40
-rw-r--r--netwerk/protocol/http/nsICorsPreflightCallback.h31
-rw-r--r--netwerk/protocol/http/nsIEarlyHintObserver.idl16
-rw-r--r--netwerk/protocol/http/nsIHttpActivityObserver.idl214
-rw-r--r--netwerk/protocol/http/nsIHttpAuthManager.idl115
-rw-r--r--netwerk/protocol/http/nsIHttpAuthenticableChannel.idl122
-rw-r--r--netwerk/protocol/http/nsIHttpAuthenticator.idl221
-rw-r--r--netwerk/protocol/http/nsIHttpChannel.idl497
-rw-r--r--netwerk/protocol/http/nsIHttpChannelAuthProvider.idl86
-rw-r--r--netwerk/protocol/http/nsIHttpChannelChild.idl38
-rw-r--r--netwerk/protocol/http/nsIHttpChannelInternal.idl522
-rw-r--r--netwerk/protocol/http/nsIHttpHeaderVisitor.idl26
-rw-r--r--netwerk/protocol/http/nsIHttpProtocolHandler.idl215
-rw-r--r--netwerk/protocol/http/nsIObliviousHttp.idl78
-rw-r--r--netwerk/protocol/http/nsIObliviousHttpChannel.idl26
-rw-r--r--netwerk/protocol/http/nsIRaceCacheWithNetwork.idl57
-rw-r--r--netwerk/protocol/http/nsITlsHandshakeListener.idl14
-rw-r--r--netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl23
-rw-r--r--netwerk/protocol/http/nsServerTiming.cpp110
-rw-r--r--netwerk/protocol/http/nsServerTiming.h54
-rw-r--r--netwerk/protocol/http/oblivious_http/Cargo.toml11
-rw-r--r--netwerk/protocol/http/oblivious_http/src/lib.rs188
-rw-r--r--netwerk/protocol/http/oblivious_http/src/oblivious_http.h24
-rw-r--r--netwerk/protocol/moz.build10
-rw-r--r--netwerk/protocol/res/ExtensionProtocolHandler.cpp1039
-rw-r--r--netwerk/protocol/res/ExtensionProtocolHandler.h236
-rw-r--r--netwerk/protocol/res/PageThumbProtocolHandler.cpp361
-rw-r--r--netwerk/protocol/res/PageThumbProtocolHandler.h120
-rw-r--r--netwerk/protocol/res/RemoteStreamGetter.cpp138
-rw-r--r--netwerk/protocol/res/RemoteStreamGetter.h68
-rw-r--r--netwerk/protocol/res/SubstitutingJARURI.h229
-rw-r--r--netwerk/protocol/res/SubstitutingProtocolHandler.cpp723
-rw-r--r--netwerk/protocol/res/SubstitutingProtocolHandler.h129
-rw-r--r--netwerk/protocol/res/SubstitutingURL.h66
-rw-r--r--netwerk/protocol/res/moz.build42
-rw-r--r--netwerk/protocol/res/nsIResProtocolHandler.idl15
-rw-r--r--netwerk/protocol/res/nsISubstitutingProtocolHandler.idl62
-rw-r--r--netwerk/protocol/res/nsResProtocolHandler.cpp195
-rw-r--r--netwerk/protocol/res/nsResProtocolHandler.h78
-rw-r--r--netwerk/protocol/viewsource/moz.build29
-rw-r--r--netwerk/protocol/viewsource/nsIViewSourceChannel.idl41
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceChannel.cpp1220
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceChannel.h98
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceHandler.cpp141
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceHandler.h48
-rw-r--r--netwerk/protocol/websocket/BaseWebSocketChannel.cpp378
-rw-r--r--netwerk/protocol/websocket/BaseWebSocketChannel.h137
-rw-r--r--netwerk/protocol/websocket/IPCTransportProvider.cpp89
-rw-r--r--netwerk/protocol/websocket/IPCTransportProvider.h89
-rw-r--r--netwerk/protocol/websocket/PTransportProvider.ipdl30
-rw-r--r--netwerk/protocol/websocket/PWebSocket.ipdl67
-rw-r--r--netwerk/protocol/websocket/PWebSocketConnection.ipdl33
-rw-r--r--netwerk/protocol/websocket/PWebSocketEventListener.ipdl53
-rw-r--r--netwerk/protocol/websocket/WebSocketChannel.cpp4336
-rw-r--r--netwerk/protocol/websocket/WebSocketChannel.h377
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelChild.cpp732
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelChild.h116
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelParent.cpp360
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelParent.h70
-rw-r--r--netwerk/protocol/websocket/WebSocketConnection.cpp261
-rw-r--r--netwerk/protocol/websocket/WebSocketConnection.h79
-rw-r--r--netwerk/protocol/websocket/WebSocketConnectionBase.h86
-rw-r--r--netwerk/protocol/websocket/WebSocketConnectionChild.cpp221
-rw-r--r--netwerk/protocol/websocket/WebSocketConnectionChild.h54
-rw-r--r--netwerk/protocol/websocket/WebSocketConnectionListener.h23
-rw-r--r--netwerk/protocol/websocket/WebSocketConnectionParent.cpp210
-rw-r--r--netwerk/protocol/websocket/WebSocketConnectionParent.h68
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerChild.cpp109
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerChild.h63
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerParent.cpp117
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerParent.h44
-rw-r--r--netwerk/protocol/websocket/WebSocketEventService.cpp566
-rw-r--r--netwerk/protocol/websocket/WebSocketEventService.h125
-rw-r--r--netwerk/protocol/websocket/WebSocketFrame.cpp133
-rw-r--r--netwerk/protocol/websocket/WebSocketFrame.h104
-rw-r--r--netwerk/protocol/websocket/WebSocketLog.h24
-rw-r--r--netwerk/protocol/websocket/moz.build67
-rw-r--r--netwerk/protocol/websocket/nsITransportProvider.idl36
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketChannel.idl268
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketEventService.idl87
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketImpl.idl18
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketListener.idl94
-rw-r--r--netwerk/protocol/webtransport/WebTransportHash.cpp24
-rw-r--r--netwerk/protocol/webtransport/WebTransportHash.h30
-rw-r--r--netwerk/protocol/webtransport/WebTransportLog.h21
-rw-r--r--netwerk/protocol/webtransport/WebTransportSessionProxy.cpp1269
-rw-r--r--netwerk/protocol/webtransport/WebTransportSessionProxy.h195
-rw-r--r--netwerk/protocol/webtransport/WebTransportStreamProxy.cpp386
-rw-r--r--netwerk/protocol/webtransport/WebTransportStreamProxy.h84
-rw-r--r--netwerk/protocol/webtransport/moz.build36
-rw-r--r--netwerk/protocol/webtransport/nsIWebTransport.idl132
-rw-r--r--netwerk/protocol/webtransport/nsIWebTransportStream.idl89
334 files changed, 124171 insertions, 0 deletions
diff --git a/netwerk/protocol/about/moz.build b/netwerk/protocol/about/moz.build
new file mode 100644
index 0000000000..f130d9b07b
--- /dev/null
+++ b/netwerk/protocol/about/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ "nsIAboutModule.idl",
+]
+
+XPIDL_MODULE = "necko_about"
+
+EXPORTS += [
+ "nsAboutProtocolHandler.h",
+ "nsAboutProtocolUtils.h",
+]
+
+UNIFIED_SOURCES += [
+ "nsAboutBlank.cpp",
+ "nsAboutCache.cpp",
+ "nsAboutCacheEntry.cpp",
+ "nsAboutProtocolHandler.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/netwerk/cache2",
+]
diff --git a/netwerk/protocol/about/nsAboutBlank.cpp b/netwerk/protocol/about/nsAboutBlank.cpp
new file mode 100644
index 0000000000..88db0d2de3
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutBlank.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAboutBlank.h"
+#include "nsStringStream.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+
+NS_IMPL_ISUPPORTS(nsAboutBlank, nsIAboutModule)
+
+NS_IMETHODIMP
+nsAboutBlank::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIInputStream> in;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(in), ""_ns);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aURI,
+ in.forget(), "text/html"_ns, "utf-8"_ns,
+ aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ channel.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAboutBlank::GetURIFlags(nsIURI* aURI, uint32_t* result) {
+ *result = nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
+ nsIAboutModule::MAKE_LINKABLE |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutBlank::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) {
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+nsresult nsAboutBlank::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsAboutBlank> about = new nsAboutBlank();
+ return about->QueryInterface(aIID, aResult);
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/protocol/about/nsAboutBlank.h b/netwerk/protocol/about/nsAboutBlank.h
new file mode 100644
index 0000000000..6cb43507f5
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutBlank.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAboutBlank_h__
+#define nsAboutBlank_h__
+
+#include "nsIAboutModule.h"
+
+class nsAboutBlank : public nsIAboutModule {
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIABOUTMODULE
+
+ nsAboutBlank() = default;
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ private:
+ virtual ~nsAboutBlank() = default;
+};
+
+#define NS_ABOUT_BLANK_MODULE_CID \
+ { /* 3decd6c8-30ef-11d3-8cd0-0060b0fc14a3 */ \
+ 0x3decd6c8, 0x30ef, 0x11d3, { \
+ 0x8c, 0xd0, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3 \
+ } \
+ }
+
+#endif // nsAboutBlank_h__
diff --git a/netwerk/protocol/about/nsAboutCache.cpp b/netwerk/protocol/about/nsAboutCache.cpp
new file mode 100644
index 0000000000..1873277a7a
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCache.cpp
@@ -0,0 +1,533 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAboutCache.h"
+#include "nsIInputStream.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsNetUtil.h"
+#include "nsIPipe.h"
+#include "nsContentUtils.h"
+#include "nsEscape.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsPrintfCString.h"
+
+#include "nsICacheStorageService.h"
+#include "nsICacheStorage.h"
+#include "CacheFileUtils.h"
+#include "CacheObserver.h"
+
+#include "nsThreadUtils.h"
+
+using namespace mozilla::net;
+
+NS_IMPL_ISUPPORTS(nsAboutCache, nsIAboutModule)
+NS_IMPL_ISUPPORTS(nsAboutCache::Channel, nsIChannel, nsIRequest,
+ nsICacheStorageVisitor)
+
+NS_IMETHODIMP
+nsAboutCache::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ RefPtr<Channel> channel = new Channel();
+ rv = channel->Init(aURI, aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ channel.forget(result);
+
+ return NS_OK;
+}
+
+nsresult nsAboutCache::Channel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo) {
+ nsresult rv;
+
+ mCancel = false;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(mStream), 16384,
+ (uint32_t)-1,
+ true, // non-blocking input
+ false // blocking output
+ );
+
+ nsAutoCString storageName;
+ rv = ParseURI(aURI, storageName);
+ if (NS_FAILED(rv)) return rv;
+
+ mOverview = storageName.IsEmpty();
+ if (mOverview) {
+ // ...and visit all we can
+ mStorageList.AppendElement("memory"_ns);
+ mStorageList.AppendElement("disk"_ns);
+ } else {
+ // ...and visit just the specified storage, entries will output too
+ mStorageList.AppendElement(storageName);
+ }
+
+ // The entries header is added on encounter of the first entry
+ mEntriesHeaderAdded = false;
+
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel), aURI,
+ inputStream.forget(), "text/html"_ns,
+ "utf-8"_ns, aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ mBuffer.AssignLiteral(
+ "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ " <title>Network Cache Storage Information</title>\n"
+ " <meta charset=\"utf-8\">\n"
+ " <meta name=\"color-scheme\" content=\"light dark\">\n"
+ " <meta http-equiv=\"Content-Security-Policy\" content=\"default-src "
+ "chrome:; object-src 'none'\"/>\n"
+ " <link rel=\"stylesheet\" "
+ "href=\"chrome://global/skin/in-content/info-pages.css\"/>\n"
+ " <link rel=\"stylesheet\" "
+ "href=\"chrome://global/skin/aboutCache.css\"/>\n"
+ "</head>\n"
+ "<body class=\"aboutPageWideContainer\">\n"
+ "<h1>Information about the Network Cache Storage Service</h1>\n");
+
+ if (!mOverview) {
+ mBuffer.AppendLiteral(
+ "<a href=\"about:cache?storage=\">Back to overview</a>");
+ }
+
+ rv = FlushBuffer();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to flush buffer");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAboutCache::Channel::AsyncOpen(nsIStreamListener* aListener) {
+ nsresult rv;
+
+ if (!mChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Kick the walk loop.
+ rv = VisitNextStorage();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mChannel->AsyncOpen(aListener);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAboutCache::Channel::Open(nsIInputStream** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsAboutCache::Channel::ParseURI(nsIURI* uri, nsACString& storage) {
+ //
+ // about:cache[?storage=<storage-name>[&context=<context-key>]]
+ //
+ nsresult rv;
+
+ nsAutoCString path;
+ rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+
+ storage.Truncate();
+
+ nsACString::const_iterator start, valueStart, end;
+ path.BeginReading(start);
+ path.EndReading(end);
+
+ valueStart = end;
+ if (!FindInReadable("?storage="_ns, start, valueStart)) {
+ return NS_OK;
+ }
+
+ storage.Assign(Substring(valueStart, end));
+
+ return NS_OK;
+}
+
+nsresult nsAboutCache::Channel::VisitNextStorage() {
+ if (!mStorageList.Length()) return NS_ERROR_NOT_AVAILABLE;
+
+ mStorageName = mStorageList[0];
+ mStorageList.RemoveElementAt(0);
+
+ // Must re-dispatch since we cannot start another visit cycle
+ // from visitor callback. The cache v1 service doesn't like it.
+ // TODO - mayhemer, bug 913828, remove this dispatch and call
+ // directly.
+ return NS_DispatchToMainThread(mozilla::NewRunnableMethod(
+ "nsAboutCache::Channel::FireVisitStorage", this,
+ &nsAboutCache::Channel::FireVisitStorage));
+}
+
+void nsAboutCache::Channel::FireVisitStorage() {
+ nsresult rv;
+
+ rv = VisitStorage(mStorageName);
+ if (NS_FAILED(rv)) {
+ nsAutoCString escaped;
+ nsAppendEscapedHTML(mStorageName, escaped);
+ mBuffer.Append(nsPrintfCString(
+ "<p>Unrecognized storage name '%s' in about:cache URL</p>",
+ escaped.get()));
+
+ rv = FlushBuffer();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to flush buffer");
+ }
+
+ // Simulate finish of a visit cycle, this tries the next storage
+ // or closes the output stream (i.e. the UI loader will stop spinning)
+ OnCacheEntryVisitCompleted();
+ }
+}
+
+nsresult nsAboutCache::Channel::VisitStorage(nsACString const& storageName) {
+ nsresult rv;
+
+ rv = GetStorage(storageName, nullptr, getter_AddRefs(mStorage));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mStorage->AsyncVisitStorage(this, !mOverview);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+// static
+nsresult nsAboutCache::GetStorage(nsACString const& storageName,
+ nsILoadContextInfo* loadInfo,
+ nsICacheStorage** storage) {
+ nsresult rv;
+
+ nsCOMPtr<nsICacheStorageService> cacheService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ if (storageName == "disk") {
+ rv = cacheService->DiskCacheStorage(loadInfo, getter_AddRefs(cacheStorage));
+ } else if (storageName == "memory") {
+ rv = cacheService->MemoryCacheStorage(loadInfo,
+ getter_AddRefs(cacheStorage));
+ } else {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ cacheStorage.forget(storage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCache::Channel::OnCacheStorageInfo(uint32_t aEntryCount,
+ uint64_t aConsumption,
+ uint64_t aCapacity,
+ nsIFile* aDirectory) {
+ // We need mStream for this
+ if (!mStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mBuffer.AssignLiteral("<h2>");
+ nsAppendEscapedHTML(mStorageName, mBuffer);
+ mBuffer.AppendLiteral(
+ "</h2>\n"
+ "<table id=\"");
+ mBuffer.AppendLiteral("\">\n");
+
+ // Write out cache info
+ // Number of entries
+ mBuffer.AppendLiteral(
+ " <tr>\n"
+ " <th>Number of entries:</th>\n"
+ " <td>");
+ mBuffer.AppendInt(aEntryCount);
+ mBuffer.AppendLiteral(
+ "</td>\n"
+ " </tr>\n");
+
+ // Maximum storage size
+ mBuffer.AppendLiteral(
+ " <tr>\n"
+ " <th>Maximum storage size:</th>\n"
+ " <td>");
+ mBuffer.AppendInt(aCapacity / 1024);
+ mBuffer.AppendLiteral(
+ " KiB</td>\n"
+ " </tr>\n");
+
+ // Storage in use
+ mBuffer.AppendLiteral(
+ " <tr>\n"
+ " <th>Storage in use:</th>\n"
+ " <td>");
+ mBuffer.AppendInt(aConsumption / 1024);
+ mBuffer.AppendLiteral(
+ " KiB</td>\n"
+ " </tr>\n");
+
+ // Storage disk location
+ mBuffer.AppendLiteral(
+ " <tr>\n"
+ " <th>Storage disk location:</th>\n"
+ " <td>");
+ if (aDirectory) {
+ nsAutoString path;
+ aDirectory->GetPath(path);
+ mBuffer.Append(NS_ConvertUTF16toUTF8(path));
+ } else {
+ mBuffer.AppendLiteral("none, only stored in memory");
+ }
+ mBuffer.AppendLiteral(
+ " </td>\n"
+ " </tr>\n");
+
+ if (mOverview) { // The about:cache case
+ if (aEntryCount != 0) { // Add the "List Cache Entries" link
+ mBuffer.AppendLiteral(
+ " <tr>\n"
+ " <td colspan=\"2\"><a href=\"about:cache?storage=");
+ nsAppendEscapedHTML(mStorageName, mBuffer);
+ mBuffer.AppendLiteral(
+ "\">List Cache Entries</a></td>\n"
+ " </tr>\n");
+ }
+ }
+
+ mBuffer.AppendLiteral("</table>\n");
+
+ // The entries header is added on encounter of the first entry
+ mEntriesHeaderAdded = false;
+
+ nsresult rv = FlushBuffer();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to flush buffer");
+ }
+
+ if (mOverview) {
+ // OnCacheEntryVisitCompleted() is not called when we do not iterate
+ // cache entries. Since this moves forward to the next storage in
+ // the list we want to visit, artificially call it here.
+ OnCacheEntryVisitCompleted();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCache::Channel::OnCacheEntryInfo(
+ nsIURI* aURI, const nsACString& aIdEnhance, int64_t aDataSize,
+ int64_t aAltDataSize, uint32_t aFetchCount, uint32_t aLastModified,
+ uint32_t aExpirationTime, bool aPinned, nsILoadContextInfo* aInfo) {
+ // We need mStream for this
+ if (!mStream || mCancel) {
+ // Returning a failure from this callback stops the iteration
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mEntriesHeaderAdded) {
+ mBuffer.AppendLiteral(
+ "<hr/>\n"
+ "<table id=\"entries\">\n"
+ " <colgroup>\n"
+ " <col id=\"col-key\">\n"
+ " <col id=\"col-dataSize\">\n"
+ " <col id=\"col-altDataSize\">\n"
+ " <col id=\"col-fetchCount\">\n"
+ " <col id=\"col-lastModified\">\n"
+ " <col id=\"col-expires\">\n"
+ " <col id=\"col-pinned\">\n"
+ " </colgroup>\n"
+ " <thead>\n"
+ " <tr>\n"
+ " <th>Key</th>\n"
+ " <th>Data size</th>\n"
+ " <th>Alternative Data size</th>\n"
+ " <th>Fetch count</th>\n"
+ " <th>Last Modifed</th>\n"
+ " <th>Expires</th>\n"
+ " <th>Pinning</th>\n"
+ " </tr>\n"
+ " </thead>\n");
+ mEntriesHeaderAdded = true;
+ }
+
+ // Generate a about:cache-entry URL for this entry...
+
+ nsAutoCString url;
+ url.AssignLiteral("about:cache-entry?storage=");
+ nsAppendEscapedHTML(mStorageName, url);
+
+ nsAutoCString context;
+ CacheFileUtils::AppendKeyPrefix(aInfo, context);
+ url.AppendLiteral("&amp;context=");
+ nsAppendEscapedHTML(context, url);
+
+ url.AppendLiteral("&amp;eid=");
+ nsAppendEscapedHTML(aIdEnhance, url);
+
+ nsAutoCString cacheUriSpec;
+ aURI->GetAsciiSpec(cacheUriSpec);
+ nsAutoCString escapedCacheURI;
+ nsAppendEscapedHTML(cacheUriSpec, escapedCacheURI);
+ url.AppendLiteral("&amp;uri=");
+ url += escapedCacheURI;
+
+ // Entry start...
+ mBuffer.AppendLiteral(" <tr>\n");
+
+ // URI
+ mBuffer.AppendLiteral(" <td><a href=\"");
+ mBuffer.Append(url);
+ mBuffer.AppendLiteral("\">");
+ if (!aIdEnhance.IsEmpty()) {
+ nsAppendEscapedHTML(aIdEnhance, mBuffer);
+ mBuffer.Append(':');
+ }
+ mBuffer.Append(escapedCacheURI);
+ mBuffer.AppendLiteral("</a>");
+
+ if (!context.IsEmpty()) {
+ mBuffer.AppendLiteral("<br><span title=\"Context separation key\">");
+ nsAutoCString escapedContext;
+ nsAppendEscapedHTML(context, escapedContext);
+ mBuffer.Append(escapedContext);
+ mBuffer.AppendLiteral("</span>");
+ }
+
+ mBuffer.AppendLiteral("</td>\n");
+
+ // Content length
+ mBuffer.AppendLiteral(" <td>");
+ mBuffer.AppendInt(aDataSize);
+ mBuffer.AppendLiteral(" bytes</td>\n");
+
+ // Length of alternative content
+ mBuffer.AppendLiteral(" <td>");
+ mBuffer.AppendInt(aAltDataSize);
+ mBuffer.AppendLiteral(" bytes</td>\n");
+
+ // Number of accesses
+ mBuffer.AppendLiteral(" <td>");
+ mBuffer.AppendInt(aFetchCount);
+ mBuffer.AppendLiteral("</td>\n");
+
+ // vars for reporting time
+ char buf[255];
+
+ // Last modified time
+ mBuffer.AppendLiteral(" <td>");
+ if (aLastModified) {
+ PrintTimeString(buf, sizeof(buf), aLastModified);
+ mBuffer.Append(buf);
+ } else {
+ mBuffer.AppendLiteral("No last modified time");
+ }
+ mBuffer.AppendLiteral("</td>\n");
+
+ // Expires time
+ mBuffer.AppendLiteral(" <td>");
+
+ // Bug - 633747.
+ // When expiration time is 0, we show 1970-01-01 01:00:00 which is confusing.
+ // So we check if time is 0, then we show a message, "Expired Immediately"
+ if (aExpirationTime == 0) {
+ mBuffer.AppendLiteral("Expired Immediately");
+ } else if (aExpirationTime < 0xFFFFFFFF) {
+ PrintTimeString(buf, sizeof(buf), aExpirationTime);
+ mBuffer.Append(buf);
+ } else {
+ mBuffer.AppendLiteral("No expiration time");
+ }
+ mBuffer.AppendLiteral("</td>\n");
+
+ // Pinning
+ mBuffer.AppendLiteral(" <td>");
+ if (aPinned) {
+ mBuffer.AppendLiteral("Pinned");
+ } else {
+ mBuffer.AppendLiteral("&nbsp;");
+ }
+ mBuffer.AppendLiteral("</td>\n");
+
+ // Entry is done...
+ mBuffer.AppendLiteral(" </tr>\n");
+
+ return FlushBuffer();
+}
+
+NS_IMETHODIMP
+nsAboutCache::Channel::OnCacheEntryVisitCompleted() {
+ if (!mStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mEntriesHeaderAdded) {
+ mBuffer.AppendLiteral("</table>\n");
+ }
+
+ // Kick another storage visiting (from a storage that allows us.)
+ while (mStorageList.Length()) {
+ nsresult rv = VisitNextStorage();
+ if (NS_SUCCEEDED(rv)) {
+ // Expecting new round of OnCache* calls.
+ return NS_OK;
+ }
+ }
+
+ // We are done!
+ mBuffer.AppendLiteral(
+ "</body>\n"
+ "</html>\n");
+ nsresult rv = FlushBuffer();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to flush buffer");
+ }
+ mStream->Close();
+
+ return NS_OK;
+}
+
+nsresult nsAboutCache::Channel::FlushBuffer() {
+ nsresult rv;
+
+ uint32_t bytesWritten;
+ rv = mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
+ mBuffer.Truncate();
+
+ if (NS_FAILED(rv)) {
+ mCancel = true;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAboutCache::GetURIFlags(nsIURI* aURI, uint32_t* result) {
+ *result = nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::IS_SECURE_CHROME_UI;
+ return NS_OK;
+}
+
+// static
+nsresult nsAboutCache::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsAboutCache> about = new nsAboutCache();
+ return about->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+nsAboutCache::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) {
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/protocol/about/nsAboutCache.h b/netwerk/protocol/about/nsAboutCache.h
new file mode 100644
index 0000000000..65292b9aae
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCache.h
@@ -0,0 +1,199 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAboutCache_h__
+#define nsAboutCache_h__
+
+#include "nsIAboutModule.h"
+#include "nsICacheStorageVisitor.h"
+#include "nsICacheStorage.h"
+
+#include "nsString.h"
+#include "nsIChannel.h"
+#include "nsIOutputStream.h"
+#include "nsILoadContextInfo.h"
+
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+#define NS_FORWARD_SAFE_NSICHANNEL_SUBSET(_to) \
+ NS_IMETHOD GetOriginalURI(nsIURI** aOriginalURI) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetOriginalURI(aOriginalURI); \
+ } \
+ NS_IMETHOD SetOriginalURI(nsIURI* aOriginalURI) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetOriginalURI(aOriginalURI); \
+ } \
+ NS_IMETHOD GetURI(nsIURI** aURI) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetURI(aURI); \
+ } \
+ NS_IMETHOD GetOwner(nsISupports** aOwner) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetOwner(aOwner); \
+ } \
+ NS_IMETHOD SetOwner(nsISupports* aOwner) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->SetOwner(aOwner); \
+ } \
+ NS_IMETHOD GetNotificationCallbacks( \
+ nsIInterfaceRequestor** aNotificationCallbacks) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetNotificationCallbacks(aNotificationCallbacks); \
+ } \
+ NS_IMETHOD SetNotificationCallbacks( \
+ nsIInterfaceRequestor* aNotificationCallbacks) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetNotificationCallbacks(aNotificationCallbacks); \
+ } \
+ NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) \
+ override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetSecurityInfo(aSecurityInfo); \
+ } \
+ NS_IMETHOD GetContentType(nsACString& aContentType) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetContentType(aContentType); \
+ } \
+ NS_IMETHOD SetContentType(const nsACString& aContentType) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetContentType(aContentType); \
+ } \
+ NS_IMETHOD GetContentCharset(nsACString& aContentCharset) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetContentCharset(aContentCharset); \
+ } \
+ NS_IMETHOD SetContentCharset(const nsACString& aContentCharset) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetContentCharset(aContentCharset); \
+ } \
+ NS_IMETHOD GetContentLength(int64_t* aContentLength) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetContentLength(aContentLength); \
+ } \
+ NS_IMETHOD SetContentLength(int64_t aContentLength) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetContentLength(aContentLength); \
+ } \
+ NS_IMETHOD GetContentDisposition(uint32_t* aContentDisposition) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetContentDisposition(aContentDisposition); \
+ } \
+ NS_IMETHOD SetContentDisposition(uint32_t aContentDisposition) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetContentDisposition(aContentDisposition); \
+ } \
+ NS_IMETHOD GetContentDispositionFilename( \
+ nsAString& aContentDispositionFilename) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetContentDispositionFilename( \
+ aContentDispositionFilename); \
+ } \
+ NS_IMETHOD SetContentDispositionFilename( \
+ const nsAString& aContentDispositionFilename) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->SetContentDispositionFilename( \
+ aContentDispositionFilename); \
+ } \
+ NS_IMETHOD GetContentDispositionHeader( \
+ nsACString& aContentDispositionHeader) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER \
+ : (_to)->GetContentDispositionHeader( \
+ aContentDispositionHeader); \
+ } \
+ NS_IMETHOD GetLoadInfo(nsILoadInfo** aLoadInfo) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetLoadInfo(aLoadInfo); \
+ } \
+ NS_IMETHOD SetLoadInfo(nsILoadInfo* aLoadInfo) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->SetLoadInfo(aLoadInfo); \
+ } \
+ NS_IMETHOD GetIsDocument(bool* aIsDocument) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetIsDocument(aIsDocument); \
+ } \
+ NS_IMETHOD GetCanceled(bool* aCanceled) override { \
+ return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetCanceled(aCanceled); \
+ };
+
+class nsAboutCache final : public nsIAboutModule {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABOUTMODULE
+
+ nsAboutCache() = default;
+
+ [[nodiscard]] static nsresult Create(REFNSIID aIID, void** aResult);
+
+ [[nodiscard]] static nsresult GetStorage(nsACString const& storageName,
+ nsILoadContextInfo* loadInfo,
+ nsICacheStorage** storage);
+
+ protected:
+ virtual ~nsAboutCache() = default;
+
+ class Channel final : public nsIChannel, public nsICacheStorageVisitor {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHESTORAGEVISITOR
+ NS_FORWARD_SAFE_NSIREQUEST(mChannel)
+ NS_FORWARD_SAFE_NSICHANNEL_SUBSET(mChannel)
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+ NS_IMETHOD Open(nsIInputStream** _retval) override;
+
+ private:
+ virtual ~Channel() = default;
+
+ public:
+ [[nodiscard]] nsresult Init(nsIURI* aURI, nsILoadInfo* aLoadInfo);
+ [[nodiscard]] nsresult ParseURI(nsIURI* uri, nsACString& storage);
+
+ // Finds a next storage we wish to visit (we use this method
+ // even there is a specified storage name, which is the only
+ // one in the list then.) Posts FireVisitStorage() when found.
+ [[nodiscard]] nsresult VisitNextStorage();
+ // Helper method that calls VisitStorage() for the current storage.
+ // When it fails, OnCacheEntryVisitCompleted is simulated to close
+ // the output stream and thus the about:cache channel.
+ void FireVisitStorage();
+ // Kiks the visit cycle for the given storage, names can be:
+ // "disk", "memory", "appcache"
+ // Note: any newly added storage type has to be manually handled here.
+ [[nodiscard]] nsresult VisitStorage(nsACString const& storageName);
+
+ // Writes content of mBuffer to mStream and truncates
+ // the buffer. It may fail when the input stream is closed by canceling
+ // the input stream channel. It can be used to stop the cache iteration
+ // process.
+ [[nodiscard]] nsresult FlushBuffer();
+
+ // Whether we are showing overview status of all available
+ // storages.
+ bool mOverview = false;
+
+ // Flag initially false, that indicates the entries header has
+ // been added to the output HTML.
+ bool mEntriesHeaderAdded = false;
+
+ // Cancelation flag
+ bool mCancel = false;
+
+ // The list of all storage names we want to visit
+ nsTArray<nsCString> mStorageList;
+ nsCString mStorageName;
+ nsCOMPtr<nsICacheStorage> mStorage;
+
+ // Output data buffering and streaming output
+ nsCString mBuffer;
+ nsCOMPtr<nsIOutputStream> mStream;
+
+ // The input stream channel, the one that actually does the job
+ nsCOMPtr<nsIChannel> mChannel;
+ };
+};
+
+#define NS_ABOUT_CACHE_MODULE_CID \
+ { /* 9158c470-86e4-11d4-9be2-00e09872a416 */ \
+ 0x9158c470, 0x86e4, 0x11d4, { \
+ 0x9b, 0xe2, 0x00, 0xe0, 0x98, 0x72, 0xa4, 0x16 \
+ } \
+ }
+
+#endif // nsAboutCache_h__
diff --git a/netwerk/protocol/about/nsAboutCacheEntry.cpp b/netwerk/protocol/about/nsAboutCacheEntry.cpp
new file mode 100644
index 0000000000..08dfcf6ca0
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCacheEntry.cpp
@@ -0,0 +1,559 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+
+#include "nsAboutCacheEntry.h"
+
+#include "CacheFileUtils.h"
+#include "CacheObserver.h"
+#include "mozilla/Sprintf.h"
+#include "nsAboutCache.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsContentUtils.h"
+#include "nsEscape.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsICacheStorage.h"
+#include "nsIPipe.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsInputStreamPump.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla::net;
+
+#define HEXDUMP_MAX_ROWS 16
+
+static void HexDump(uint32_t* state, const char* buf, int32_t n,
+ nsCString& result) {
+ char temp[16];
+
+ const unsigned char* p;
+ while (n) {
+ SprintfLiteral(temp, "%08x: ", *state);
+ result.Append(temp);
+ *state += HEXDUMP_MAX_ROWS;
+
+ p = (const unsigned char*)buf;
+
+ int32_t i, row_max = std::min(HEXDUMP_MAX_ROWS, n);
+
+ // print hex codes:
+ for (i = 0; i < row_max; ++i) {
+ SprintfLiteral(temp, "%02x ", *p++);
+ result.Append(temp);
+ }
+ for (i = row_max; i < HEXDUMP_MAX_ROWS; ++i) {
+ result.AppendLiteral(" ");
+ }
+
+ // print ASCII glyphs if possible:
+ p = (const unsigned char*)buf;
+ for (i = 0; i < row_max; ++i, ++p) {
+ switch (*p) {
+ case '<':
+ result.AppendLiteral("&lt;");
+ break;
+ case '>':
+ result.AppendLiteral("&gt;");
+ break;
+ case '&':
+ result.AppendLiteral("&amp;");
+ break;
+ default:
+ if (*p < 0x7F && *p > 0x1F) {
+ result.Append(*p);
+ } else {
+ result.Append('.');
+ }
+ }
+ }
+
+ result.Append('\n');
+
+ buf += row_max;
+ n -= row_max;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsAboutCacheEntry::nsISupports
+
+NS_IMPL_ISUPPORTS(nsAboutCacheEntry, nsIAboutModule)
+NS_IMPL_ISUPPORTS(nsAboutCacheEntry::Channel, nsICacheEntryOpenCallback,
+ nsICacheEntryMetaDataVisitor, nsIStreamListener, nsIRequest,
+ nsIChannel)
+
+//-----------------------------------------------------------------------------
+// nsAboutCacheEntry::nsIAboutModule
+
+NS_IMETHODIMP
+nsAboutCacheEntry::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ NS_ENSURE_ARG_POINTER(uri);
+ nsresult rv;
+
+ RefPtr<Channel> channel = new Channel();
+ rv = channel->Init(uri, aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ channel.forget(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::GetURIFlags(nsIURI* aURI, uint32_t* result) {
+ *result = nsIAboutModule::HIDE_FROM_ABOUTABOUT |
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) {
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+//-----------------------------------------------------------------------------
+// nsAboutCacheEntry::Channel
+
+nsresult nsAboutCacheEntry::Channel::Init(nsIURI* uri, nsILoadInfo* aLoadInfo) {
+ nsresult rv;
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = GetContentStream(uri, getter_AddRefs(stream));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel), uri,
+ stream.forget(), "text/html"_ns,
+ "utf-8"_ns, aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+nsresult nsAboutCacheEntry::Channel::GetContentStream(nsIURI* uri,
+ nsIInputStream** result) {
+ nsresult rv;
+
+ // Init: (block size, maximum length)
+ nsCOMPtr<nsIAsyncInputStream> inputStream;
+ NS_NewPipe2(getter_AddRefs(inputStream), getter_AddRefs(mOutputStream), true,
+ false, 256, UINT32_MAX);
+
+ constexpr auto buffer =
+ "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ " <meta http-equiv=\"Content-Security-Policy\" content=\"default-src "
+ "chrome:; object-src 'none'\" />\n"
+ " <meta name=\"color-scheme\" content=\"light dark\" />\n"
+ " <title>Cache entry information</title>\n"
+ " <link rel=\"stylesheet\" "
+ "href=\"chrome://global/skin/in-content/info-pages.css\" "
+ "type=\"text/css\"/>\n"
+ " <link rel=\"stylesheet\" "
+ "href=\"chrome://global/skin/aboutCacheEntry.css\" type=\"text/css\"/>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>Cache entry information</h1>\n"_ns;
+ uint32_t n;
+ rv = mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+ if (NS_FAILED(rv)) return rv;
+ if (n != buffer.Length()) return NS_ERROR_UNEXPECTED;
+
+ rv = OpenCacheEntry(uri);
+ if (NS_FAILED(rv)) return rv;
+
+ inputStream.forget(result);
+ return NS_OK;
+}
+
+nsresult nsAboutCacheEntry::Channel::OpenCacheEntry(nsIURI* uri) {
+ nsresult rv;
+
+ rv = ParseURI(uri, mStorageName, getter_AddRefs(mLoadInfo), mEnhanceId,
+ getter_AddRefs(mCacheURI));
+ if (NS_FAILED(rv)) return rv;
+
+ return OpenCacheEntry();
+}
+
+nsresult nsAboutCacheEntry::Channel::OpenCacheEntry() {
+ nsresult rv;
+
+ nsCOMPtr<nsICacheStorage> storage;
+ rv = nsAboutCache::GetStorage(mStorageName, mLoadInfo,
+ getter_AddRefs(storage));
+ if (NS_FAILED(rv)) return rv;
+
+ // Invokes OnCacheEntryAvailable()
+ rv = storage->AsyncOpenURI(
+ mCacheURI, mEnhanceId,
+ nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY, this);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+nsresult nsAboutCacheEntry::Channel::ParseURI(nsIURI* uri,
+ nsACString& storageName,
+ nsILoadContextInfo** loadInfo,
+ nsCString& enahnceID,
+ nsIURI** cacheUri) {
+ //
+ // about:cache-entry?storage=[string]&contenxt=[string]&eid=[string]&uri=[string]
+ //
+ nsresult rv;
+
+ nsAutoCString path;
+ rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+
+ nsACString::const_iterator keyBegin, keyEnd, valBegin, begin, end;
+ path.BeginReading(begin);
+ path.EndReading(end);
+
+ keyBegin = begin;
+ keyEnd = end;
+ if (!FindInReadable("?storage="_ns, keyBegin, keyEnd)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ valBegin = keyEnd; // the value of the storage key starts after the key
+
+ keyBegin = keyEnd;
+ keyEnd = end;
+ if (!FindInReadable("&context="_ns, keyBegin, keyEnd)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ storageName.Assign(Substring(valBegin, keyBegin));
+ valBegin = keyEnd; // the value of the context key starts after the key
+
+ keyBegin = keyEnd;
+ keyEnd = end;
+ if (!FindInReadable("&eid="_ns, keyBegin, keyEnd)) return NS_ERROR_FAILURE;
+
+ nsAutoCString contextKey(Substring(valBegin, keyBegin));
+ valBegin = keyEnd; // the value of the eid key starts after the key
+
+ keyBegin = keyEnd;
+ keyEnd = end;
+ if (!FindInReadable("&uri="_ns, keyBegin, keyEnd)) return NS_ERROR_FAILURE;
+
+ enahnceID.Assign(Substring(valBegin, keyBegin));
+
+ valBegin = keyEnd; // the value of the uri key starts after the key
+ nsAutoCString uriSpec(Substring(valBegin, end)); // uri is the last one
+
+ // Uf... parsing done, now get some objects from it...
+
+ nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(contextKey);
+ if (!info) return NS_ERROR_FAILURE;
+ info.forget(loadInfo);
+
+ rv = NS_NewURI(cacheUri, uriSpec);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsICacheEntryOpenCallback implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnCacheEntryCheck(nsICacheEntry* aEntry,
+ uint32_t* result) {
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnCacheEntryAvailable(nsICacheEntry* entry,
+ bool isNew, nsresult status) {
+ nsresult rv;
+
+ mWaitingForData = false;
+ if (entry) {
+ rv = WriteCacheEntryDescription(entry);
+ } else {
+ rv = WriteCacheEntryUnavailable();
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mWaitingForData) {
+ // Data is not expected, close the output of content now.
+ CloseContent();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Print-out helper methods
+//-----------------------------------------------------------------------------
+
+#define APPEND_ROW(label, value) \
+ PR_BEGIN_MACRO \
+ buffer.AppendLiteral( \
+ " <tr>\n" \
+ " <th>"); \
+ buffer.AppendLiteral(label); \
+ buffer.AppendLiteral( \
+ ":</th>\n" \
+ " <td>"); \
+ buffer.Append(value); \
+ buffer.AppendLiteral( \
+ "</td>\n" \
+ " </tr>\n"); \
+ PR_END_MACRO
+
+nsresult nsAboutCacheEntry::Channel::WriteCacheEntryDescription(
+ nsICacheEntry* entry) {
+ nsresult rv;
+ // This method appears to run in a situation where the run-time stack
+ // should have plenty of space, so allocating a large string on the
+ // stack is OK.
+ nsAutoCStringN<4097> buffer;
+ uint32_t n;
+
+ nsAutoCString str;
+
+ rv = entry->GetKey(str);
+ if (NS_FAILED(rv)) return rv;
+
+ buffer.AssignLiteral(
+ "<table>\n"
+ " <tr>\n"
+ " <th>key:</th>\n"
+ " <td id=\"td-key\">");
+
+ // Test if the key is actually a URI
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), str);
+
+ nsAutoCString escapedStr;
+ nsAppendEscapedHTML(str, escapedStr);
+
+ // javascript: and data: URLs should not be linkified
+ // since clicking them can cause scripts to run - bug 162584
+ if (NS_SUCCEEDED(rv) &&
+ !(uri->SchemeIs("javascript") || uri->SchemeIs("data"))) {
+ buffer.AppendLiteral("<a href=\"");
+ buffer.Append(escapedStr);
+ buffer.AppendLiteral("\">");
+ buffer.Append(escapedStr);
+ buffer.AppendLiteral("</a>");
+ uri = nullptr;
+ } else {
+ buffer.Append(escapedStr);
+ }
+ buffer.AppendLiteral(
+ "</td>\n"
+ " </tr>\n");
+
+ // temp vars for reporting
+ char timeBuf[255];
+ uint32_t u = 0;
+ nsAutoCString s;
+
+ // Fetch Count
+ s.Truncate();
+ entry->GetFetchCount(&u);
+ s.AppendInt(u);
+ APPEND_ROW("fetch count", s);
+
+ // Last Fetched
+ entry->GetLastFetched(&u);
+ if (u) {
+ PrintTimeString(timeBuf, sizeof(timeBuf), u);
+ APPEND_ROW("last fetched", timeBuf);
+ } else {
+ APPEND_ROW("last fetched", "No last fetch time (bug 1000338)");
+ }
+
+ // Last Modified
+ entry->GetLastModified(&u);
+ if (u) {
+ PrintTimeString(timeBuf, sizeof(timeBuf), u);
+ APPEND_ROW("last modified", timeBuf);
+ } else {
+ APPEND_ROW("last modified", "No last modified time (bug 1000338)");
+ }
+
+ // Expiration Time
+ entry->GetExpirationTime(&u);
+
+ // Bug - 633747.
+ // When expiration time is 0, we show 1970-01-01 01:00:00 which is confusing.
+ // So we check if time is 0, then we show a message, "Expired Immediately"
+ if (u == 0) {
+ APPEND_ROW("expires", "Expired Immediately");
+ } else if (u < 0xFFFFFFFF) {
+ PrintTimeString(timeBuf, sizeof(timeBuf), u);
+ APPEND_ROW("expires", timeBuf);
+ } else {
+ APPEND_ROW("expires", "No expiration time");
+ }
+
+ // Data Size
+ s.Truncate();
+ uint32_t dataSize;
+ if (NS_FAILED(entry->GetStorageDataSize(&dataSize))) dataSize = 0;
+ s.AppendInt(
+ (int32_t)dataSize); // XXX nsICacheEntryInfo interfaces should be fixed.
+ s.AppendLiteral(" B");
+ APPEND_ROW("Data size", s);
+
+ // TODO - mayhemer
+ // Here used to be a link to the disk file (in the old cache for entries that
+ // did not fit any of the block files, in the new cache every time).
+ // I'd rather have a small set of buttons here to action on the entry:
+ // 1. save the content
+ // 2. save as a complete HTTP response (response head, headers, content)
+ // 3. doom the entry
+ // A new bug(s) should be filed here.
+
+ // Security Info
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ entry->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (securityInfo) {
+ APPEND_ROW("Security", "This is a secure document.");
+ } else {
+ APPEND_ROW(
+ "Security",
+ "This document does not have any security info associated with it.");
+ }
+
+ buffer.AppendLiteral(
+ "</table>\n"
+ "<hr/>\n"
+ "<table>\n");
+
+ mBuffer = &buffer; // make it available for OnMetaDataElement().
+ entry->VisitMetaData(this);
+ mBuffer = nullptr;
+
+ buffer.AppendLiteral("</table>\n");
+ mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+ buffer.Truncate();
+
+ // Provide a hexdump of the data
+ if (!dataSize) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ entry->OpenInputStream(0, getter_AddRefs(stream));
+ if (!stream) {
+ return NS_OK;
+ }
+
+ RefPtr<nsInputStreamPump> pump;
+ rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream);
+ if (NS_FAILED(rv)) {
+ return NS_OK; // just ignore
+ }
+
+ rv = pump->AsyncRead(this);
+ if (NS_FAILED(rv)) {
+ return NS_OK; // just ignore
+ }
+
+ mWaitingForData = true;
+ return NS_OK;
+}
+
+nsresult nsAboutCacheEntry::Channel::WriteCacheEntryUnavailable() {
+ uint32_t n;
+ constexpr auto buffer = "The cache entry you selected is not available."_ns;
+ mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsICacheEntryMetaDataVisitor implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnMetaDataElement(char const* key,
+ char const* value) {
+ mBuffer->AppendLiteral(
+ " <tr>\n"
+ " <th>");
+ mBuffer->Append(key);
+ mBuffer->AppendLiteral(
+ ":</th>\n"
+ " <td>");
+ nsAppendEscapedHTML(nsDependentCString(value), *mBuffer);
+ mBuffer->AppendLiteral(
+ "</td>\n"
+ " </tr>\n");
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIStreamListener implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnStartRequest(nsIRequest* request) {
+ mHexDumpState = 0;
+
+ constexpr auto buffer = "<hr/>\n<pre>"_ns;
+ uint32_t n;
+ return mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ uint32_t n;
+ return aInputStream->ReadSegments(&nsAboutCacheEntry::Channel::PrintCacheData,
+ this, aCount, &n);
+}
+
+/* static */
+nsresult nsAboutCacheEntry::Channel::PrintCacheData(
+ nsIInputStream* aInStream, void* aClosure, const char* aFromSegment,
+ uint32_t aToOffset, uint32_t aCount, uint32_t* aWriteCount) {
+ nsAboutCacheEntry::Channel* a =
+ static_cast<nsAboutCacheEntry::Channel*>(aClosure);
+
+ nsCString buffer;
+ HexDump(&a->mHexDumpState, aFromSegment, aCount, buffer);
+
+ uint32_t n;
+ a->mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+
+ *aWriteCount = aCount;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnStopRequest(nsIRequest* request,
+ nsresult result) {
+ constexpr auto buffer = "</pre>\n"_ns;
+ uint32_t n;
+ mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+
+ CloseContent();
+
+ return NS_OK;
+}
+
+void nsAboutCacheEntry::Channel::CloseContent() {
+ constexpr auto buffer = "</body>\n</html>\n"_ns;
+ uint32_t n;
+ mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+
+ mOutputStream->Close();
+ mOutputStream = nullptr;
+}
diff --git a/netwerk/protocol/about/nsAboutCacheEntry.h b/netwerk/protocol/about/nsAboutCacheEntry.h
new file mode 100644
index 0000000000..76f8649061
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCacheEntry.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAboutCacheEntry_h__
+#define nsAboutCacheEntry_h__
+
+#include "nsIAboutModule.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheEntry.h"
+#include "nsIStreamListener.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+
+class nsIAsyncOutputStream;
+class nsIInputStream;
+class nsILoadContextInfo;
+class nsIURI;
+
+class nsAboutCacheEntry final : public nsIAboutModule {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABOUTMODULE
+
+ private:
+ virtual ~nsAboutCacheEntry() = default;
+
+ class Channel final : public nsICacheEntryOpenCallback,
+ public nsICacheEntryMetaDataVisitor,
+ public nsIStreamListener,
+ public nsIChannel {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_FORWARD_SAFE_NSICHANNEL(mChannel)
+ NS_FORWARD_SAFE_NSIREQUEST(mChannel)
+
+ Channel() = default;
+
+ private:
+ virtual ~Channel() = default;
+
+ public:
+ [[nodiscard]] nsresult Init(nsIURI* uri, nsILoadInfo* aLoadInfo);
+
+ [[nodiscard]] nsresult GetContentStream(nsIURI*, nsIInputStream**);
+ [[nodiscard]] nsresult OpenCacheEntry(nsIURI*);
+ [[nodiscard]] nsresult OpenCacheEntry();
+ [[nodiscard]] nsresult WriteCacheEntryDescription(nsICacheEntry*);
+ [[nodiscard]] nsresult WriteCacheEntryUnavailable();
+ [[nodiscard]] nsresult ParseURI(nsIURI* uri, nsACString& storageName,
+ nsILoadContextInfo** loadInfo,
+ nsCString& enahnceID, nsIURI** cacheUri);
+ void CloseContent();
+
+ [[nodiscard]] static nsresult PrintCacheData(
+ nsIInputStream* aInStream, void* aClosure, const char* aFromSegment,
+ uint32_t aToOffset, uint32_t aCount, uint32_t* aWriteCount);
+
+ private:
+ nsCString mStorageName, mEnhanceId;
+ nsCOMPtr<nsILoadContextInfo> mLoadInfo;
+ nsCOMPtr<nsIURI> mCacheURI;
+
+ nsCString* mBuffer{nullptr};
+ nsCOMPtr<nsIAsyncOutputStream> mOutputStream;
+ bool mWaitingForData{false};
+ uint32_t mHexDumpState{0};
+
+ nsCOMPtr<nsIChannel> mChannel;
+ };
+};
+
+#define NS_ABOUT_CACHE_ENTRY_MODULE_CID \
+ { /* 7fa5237d-b0eb-438f-9e50-ca0166e63788 */ \
+ 0x7fa5237d, 0xb0eb, 0x438f, { \
+ 0x9e, 0x50, 0xca, 0x01, 0x66, 0xe6, 0x37, 0x88 \
+ } \
+ }
+
+#endif // nsAboutCacheEntry_h__
diff --git a/netwerk/protocol/about/nsAboutProtocolHandler.cpp b/netwerk/protocol/about/nsAboutProtocolHandler.cpp
new file mode 100644
index 0000000000..5bbaa9010e
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolHandler.cpp
@@ -0,0 +1,428 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+#include "mozilla/ArrayUtils.h"
+
+#include "nsAboutProtocolHandler.h"
+#include "nsIURI.h"
+#include "nsIAboutModule.h"
+#include "nsContentUtils.h"
+#include "nsString.h"
+#include "nsNetCID.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsError.h"
+#include "nsNetUtil.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsIChannel.h"
+#include "nsIScriptError.h"
+#include "nsIClassInfoImpl.h"
+
+#include "mozilla/ipc/URIUtils.h"
+
+namespace mozilla {
+namespace net {
+
+static NS_DEFINE_CID(kNestedAboutURICID, NS_NESTEDABOUTURI_CID);
+
+static bool IsSafeToLinkForUntrustedContent(nsIURI* aURI) {
+ nsAutoCString path;
+ aURI->GetPathQueryRef(path);
+
+ int32_t f = path.FindChar('#');
+ if (f >= 0) {
+ path.SetLength(f);
+ }
+
+ f = path.FindChar('?');
+ if (f >= 0) {
+ path.SetLength(f);
+ }
+
+ ToLowerCase(path);
+
+ // The about modules for these URL types have the
+ // URI_SAFE_FOR_UNTRUSTED_CONTENT and MAKE_LINKABLE flags set.
+ return path.EqualsLiteral("blank") || path.EqualsLiteral("logo") ||
+ path.EqualsLiteral("srcdoc");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsAboutProtocolHandler, nsIProtocolHandler,
+ nsIProtocolHandlerWithDynamicFlags, nsISupportsWeakReference)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral("about");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* aFlags) {
+ // First use the default (which is "unsafe for content"):
+ *aFlags = URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD |
+ URI_SCHEME_NOT_SELF_LINKABLE;
+
+ // Now try to see if this URI overrides the default:
+ nsCOMPtr<nsIAboutModule> aboutMod;
+ nsresult rv = NS_GetAboutModule(aURI, getter_AddRefs(aboutMod));
+ if (NS_FAILED(rv)) {
+ // Swallow this and just tell the consumer the default:
+ return NS_OK;
+ }
+ uint32_t aboutModuleFlags = 0;
+ rv = aboutMod->GetURIFlags(aURI, &aboutModuleFlags);
+ // This should never happen, so pass back the error:
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Secure (https) pages can load safe about pages without becoming
+ // mixed content.
+ if (aboutModuleFlags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT) {
+ *aFlags |= URI_IS_POTENTIALLY_TRUSTWORTHY;
+ // about: pages can only be loaded by unprivileged principals
+ // if they are marked as LINKABLE
+ if (aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) {
+ // Replace URI_DANGEROUS_TO_LOAD with URI_LOADABLE_BY_ANYONE.
+ *aFlags &= ~URI_DANGEROUS_TO_LOAD;
+ *aFlags |= URI_LOADABLE_BY_ANYONE;
+ }
+ }
+ return NS_OK;
+}
+
+// static
+nsresult nsAboutProtocolHandler::CreateNewURI(const nsACString& aSpec,
+ const char* aCharset,
+ nsIURI* aBaseURI,
+ nsIURI** aResult) {
+ *aResult = nullptr;
+ nsresult rv;
+
+ // Use a simple URI to parse out some stuff first
+ nsCOMPtr<nsIURI> url;
+ rv = NS_MutateURI(new nsSimpleURI::Mutator()).SetSpec(aSpec).Finalize(url);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (IsSafeToLinkForUntrustedContent(url)) {
+ // We need to indicate that this baby is safe. Use an inner URI that
+ // no one but the security manager will see. Make sure to preserve our
+ // path, in case someone decides to hardcode checks for particular
+ // about: URIs somewhere.
+ nsAutoCString spec;
+ rv = url->GetPathQueryRef(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ spec.InsertLiteral("moz-safe-about:", 0);
+
+ nsCOMPtr<nsIURI> inner;
+ rv = NS_NewURI(getter_AddRefs(inner), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_MutateURI(new nsNestedAboutURI::Mutator())
+ .Apply(&nsINestedAboutURIMutator::InitWithBase, inner, aBaseURI)
+ .SetSpec(aSpec)
+ .Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ url.swap(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ NS_ENSURE_ARG_POINTER(uri);
+
+ // about:what you ask?
+ nsCOMPtr<nsIAboutModule> aboutMod;
+ nsresult rv = NS_GetAboutModule(uri, getter_AddRefs(aboutMod));
+
+ nsAutoCString path;
+ if (NS_SUCCEEDED(NS_GetAboutModuleName(uri, path)) &&
+ path.EqualsLiteral("srcdoc")) {
+ // about:srcdoc is meant to be unresolvable, yet is included in the
+ // about lookup tables so that it can pass security checks when used in
+ // a srcdoc iframe. To ensure that it stays unresolvable, we pretend
+ // that it doesn't exist.
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_FACTORY_NOT_REGISTERED) {
+ // This looks like an about: we don't know about. Convert
+ // this to an invalid URI error.
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return rv;
+ }
+
+ uint32_t flags = 0;
+ if (NS_FAILED(aboutMod->GetURIFlags(uri, &flags))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool safeForUntrustedContent =
+ (flags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT) != 0;
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ safeForUntrustedContent ||
+ (flags & (nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD)) == 0,
+ "Only unprivileged content should be loaded in child processes. (Did "
+ "you forget to add URI_SAFE_FOR_UNTRUSTED_CONTENT to your about: "
+ "page?)");
+
+ // The standard return case:
+ rv = aboutMod->NewChannel(uri, aLoadInfo, result);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_FACTORY_NOT_REGISTERED) {
+ // This looks like an about: we don't know about. Convert
+ // this to an invalid URI error.
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return rv;
+ }
+
+ // Not all implementations of nsIAboutModule::NewChannel()
+ // set the LoadInfo on the newly created channel yet, as
+ // an interim solution we set the LoadInfo here if not
+ // available on the channel. Bug 1087720
+ nsCOMPtr<nsILoadInfo> loadInfo = (*result)->LoadInfo();
+ if (aLoadInfo != loadInfo) {
+ NS_ASSERTION(false,
+ "nsIAboutModule->newChannel(aURI, aLoadInfo) needs to "
+ "set LoadInfo");
+ AutoTArray<nsString, 2> params = {
+ u"nsIAboutModule->newChannel(aURI)"_ns,
+ u"nsIAboutModule->newChannel(aURI, aLoadInfo)"_ns};
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "Security by Default"_ns,
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES, "APIDeprecationWarning", params);
+ (*result)->SetLoadInfo(aLoadInfo);
+ }
+
+ // If this URI is safe for untrusted content, enforce that its
+ // principal be based on the channel's originalURI by setting the
+ // owner to null.
+ // Note: this relies on aboutMod's newChannel implementation
+ // having set the proper originalURI, which probably isn't ideal.
+ if (safeForUntrustedContent) {
+ (*result)->SetOwner(nullptr);
+ }
+
+ RefPtr<nsNestedAboutURI> aboutURI;
+ if (NS_SUCCEEDED(
+ uri->QueryInterface(kNestedAboutURICID, getter_AddRefs(aboutURI))) &&
+ aboutURI->GetBaseURI()) {
+ nsCOMPtr<nsIWritablePropertyBag2> writableBag = do_QueryInterface(*result);
+ if (writableBag) {
+ writableBag->SetPropertyAsInterface(u"baseURI"_ns,
+ aboutURI->GetBaseURI());
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::AllowPort(int32_t port, const char* scheme,
+ bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Safe about protocol handler impl
+
+NS_IMPL_ISUPPORTS(nsSafeAboutProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference)
+
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral("moz-safe-about");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ *result = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::AllowPort(int32_t port, const char* scheme,
+ bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////
+// nsNestedAboutURI implementation
+
+NS_IMPL_CLASSINFO(nsNestedAboutURI, nullptr, nsIClassInfo::THREADSAFE,
+ NS_NESTEDABOUTURI_CID);
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(nsNestedAboutURI)
+
+NS_INTERFACE_MAP_BEGIN(nsNestedAboutURI)
+ if (aIID.Equals(kNestedAboutURICID)) {
+ foundInterface = static_cast<nsIURI*>(this);
+ } else
+ NS_IMPL_QUERY_CLASSINFO(nsNestedAboutURI)
+NS_INTERFACE_MAP_END_INHERITING(nsSimpleNestedURI)
+
+// nsISerializable
+
+NS_IMETHODIMP
+nsNestedAboutURI::Read(nsIObjectInputStream* aStream) {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsNestedAboutURI::ReadPrivate(nsIObjectInputStream* aStream) {
+ nsresult rv = nsSimpleNestedURI::ReadPrivate(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ bool haveBase;
+ rv = aStream->ReadBoolean(&haveBase);
+ if (NS_FAILED(rv)) return rv;
+
+ if (haveBase) {
+ nsCOMPtr<nsISupports> supports;
+ rv = aStream->ReadObject(true, getter_AddRefs(supports));
+ if (NS_FAILED(rv)) return rv;
+
+ mBaseURI = do_QueryInterface(supports, &rv);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNestedAboutURI::Write(nsIObjectOutputStream* aStream) {
+ nsresult rv = nsSimpleNestedURI::Write(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteBoolean(mBaseURI != nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mBaseURI) {
+ // A previous iteration of this code wrote out mBaseURI as nsISupports
+ // and then read it in as nsIURI, which is non-kosher when mBaseURI
+ // implements more than just a single line of interfaces and the
+ // canonical nsISupports* isn't the one a static_cast<> of mBaseURI
+ // would produce. For backwards compatibility with existing
+ // serializations we continue to write mBaseURI as nsISupports but
+ // switch to reading it as nsISupports, with a post-read QI to get to
+ // nsIURI.
+ rv = aStream->WriteCompoundObject(mBaseURI, NS_GET_IID(nsISupports), true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsNestedAboutURI::Serialize(mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ NestedAboutURIParams params;
+ URIParams nestedParams;
+
+ nsSimpleNestedURI::Serialize(nestedParams);
+ params.nestedParams() = nestedParams;
+
+ if (mBaseURI) {
+ SerializeURI(mBaseURI, params.baseURI());
+ }
+
+ aParams = params;
+}
+
+bool nsNestedAboutURI::Deserialize(const mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ if (aParams.type() != URIParams::TNestedAboutURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const NestedAboutURIParams& params = aParams.get_NestedAboutURIParams();
+ if (!nsSimpleNestedURI::Deserialize(params.nestedParams())) {
+ return false;
+ }
+
+ mBaseURI = nullptr;
+ if (params.baseURI()) {
+ mBaseURI = DeserializeURI(*params.baseURI());
+ }
+ return true;
+}
+
+// nsSimpleURI
+/* virtual */ nsSimpleURI* nsNestedAboutURI::StartClone(
+ nsSimpleURI::RefHandlingEnum aRefHandlingMode, const nsACString& aNewRef) {
+ // Sadly, we can't make use of nsSimpleNestedURI::StartClone here.
+ // However, this function is expected to exactly match that function,
+ // aside from the "new ns***URI()" call.
+ NS_ENSURE_TRUE(mInnerURI, nullptr);
+
+ nsCOMPtr<nsIURI> innerClone;
+ nsresult rv = NS_OK;
+ if (aRefHandlingMode == eHonorRef) {
+ innerClone = mInnerURI;
+ } else if (aRefHandlingMode == eReplaceRef) {
+ rv = NS_GetURIWithNewRef(mInnerURI, aNewRef, getter_AddRefs(innerClone));
+ } else {
+ rv = NS_GetURIWithoutRef(mInnerURI, getter_AddRefs(innerClone));
+ }
+
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsNestedAboutURI* url = new nsNestedAboutURI(innerClone, mBaseURI);
+ SetRefOnClone(url, aRefHandlingMode, aNewRef);
+
+ return url;
+}
+
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsNestedAboutURI::Mutator, nsIURISetters,
+ nsIURIMutator, nsISerializable,
+ nsINestedAboutURIMutator)
+
+NS_IMETHODIMP
+nsNestedAboutURI::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<nsNestedAboutURI::Mutator> mutator = new nsNestedAboutURI::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/about/nsAboutProtocolHandler.h b/netwerk/protocol/about/nsAboutProtocolHandler.h
new file mode 100644
index 0000000000..e861f065b3
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolHandler.h
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAboutProtocolHandler_h___
+#define nsAboutProtocolHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsSimpleNestedURI.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+#include "nsIURIMutator.h"
+
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class nsAboutProtocolHandler : public nsIProtocolHandlerWithDynamicFlags,
+ public nsIProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIPROTOCOLHANDLERWITHDYNAMICFLAGS
+
+ // nsAboutProtocolHandler methods:
+ nsAboutProtocolHandler() = default;
+
+ static nsresult CreateNewURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** result);
+
+ private:
+ virtual ~nsAboutProtocolHandler() = default;
+};
+
+class nsSafeAboutProtocolHandler final : public nsIProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ // nsSafeAboutProtocolHandler methods:
+ nsSafeAboutProtocolHandler() = default;
+
+ private:
+ ~nsSafeAboutProtocolHandler() = default;
+};
+
+// Class to allow us to propagate the base URI to about:blank correctly
+class nsNestedAboutURI final : public nsSimpleNestedURI {
+ private:
+ nsNestedAboutURI(nsIURI* aInnerURI, nsIURI* aBaseURI)
+ : nsSimpleNestedURI(aInnerURI), mBaseURI(aBaseURI) {}
+ nsNestedAboutURI() {}
+ virtual ~nsNestedAboutURI() = default;
+
+ public:
+ // Override QI so we can QI to our CID as needed
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+
+ // Override StartClone(), the nsISerializable methods, and
+ virtual nsSimpleURI* StartClone(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef) override;
+ NS_IMETHOD Mutate(nsIURIMutator** _retval) override;
+ NS_IMETHOD_(void) Serialize(ipc::URIParams& aParams) override;
+
+ // nsISerializable
+ NS_IMETHOD Read(nsIObjectInputStream* aStream) override;
+ NS_IMETHOD Write(nsIObjectOutputStream* aStream) override;
+
+ nsIURI* GetBaseURI() const { return mBaseURI; }
+
+ protected:
+ nsCOMPtr<nsIURI> mBaseURI;
+ bool Deserialize(const mozilla::ipc::URIParams&);
+ nsresult ReadPrivate(nsIObjectInputStream* stream);
+
+ public:
+ class Mutator final : public nsIURIMutator,
+ public BaseURIMutator<nsNestedAboutURI>,
+ public nsISerializable,
+ public nsINestedAboutURIMutator {
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI)
+
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+
+ [[nodiscard]] NS_IMETHOD Deserialize(
+ const mozilla::ipc::URIParams& aParams) override {
+ return InitFromIPCParams(aParams);
+ }
+
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override {
+ return InitFromInputStream(aStream);
+ }
+
+ [[nodiscard]] NS_IMETHOD Finalize(nsIURI** aURI) override {
+ mURI.forget(aURI);
+ return NS_OK;
+ }
+
+ [[nodiscard]] NS_IMETHOD SetSpec(const nsACString& aSpec,
+ nsIURIMutator** aMutator) override {
+ if (aMutator) {
+ NS_ADDREF(*aMutator = this);
+ }
+ return InitFromSpec(aSpec);
+ }
+
+ [[nodiscard]] NS_IMETHOD InitWithBase(nsIURI* aInnerURI,
+ nsIURI* aBaseURI) override {
+ mURI = new nsNestedAboutURI(aInnerURI, aBaseURI);
+ return NS_OK;
+ }
+
+ friend class nsNestedAboutURI;
+ };
+
+ friend BaseURIMutator<nsNestedAboutURI>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* nsAboutProtocolHandler_h___ */
diff --git a/netwerk/protocol/about/nsAboutProtocolUtils.h b/netwerk/protocol/about/nsAboutProtocolUtils.h
new file mode 100644
index 0000000000..fdc92620f3
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolUtils.h
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAboutProtocolUtils_h
+#define nsAboutProtocolUtils_h
+
+#include "mozilla/Try.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIAboutModule.h"
+#include "nsServiceManagerUtils.h"
+#include "prtime.h"
+
+[[nodiscard]] inline nsresult NS_GetAboutModuleName(nsIURI* aAboutURI,
+ nsCString& aModule) {
+ NS_ASSERTION(aAboutURI->SchemeIs("about"),
+ "should be used only on about: URIs");
+
+ MOZ_TRY(aAboutURI->GetPathQueryRef(aModule));
+
+ int32_t f = aModule.FindCharInSet("#?"_ns);
+ if (f != kNotFound) {
+ aModule.Truncate(f);
+ }
+
+ // convert to lowercase, as all about: modules are lowercase
+ ToLowerCase(aModule);
+ return NS_OK;
+}
+
+[[nodiscard]] inline bool NS_IsContentAccessibleAboutURI(nsIURI* aURI) {
+ MOZ_ASSERT(aURI->SchemeIs("about"), "Should be used only on about: URIs");
+ nsAutoCString name;
+ if (NS_WARN_IF(NS_FAILED(NS_GetAboutModuleName(aURI, name)))) {
+ return true;
+ }
+ return name.EqualsLiteral("blank") || name.EqualsLiteral("srcdoc");
+}
+
+inline nsresult NS_GetAboutModule(nsIURI* aAboutURI, nsIAboutModule** aModule) {
+ MOZ_ASSERT(aAboutURI, "Must have URI");
+
+ nsAutoCString contractID;
+ MOZ_TRY(NS_GetAboutModuleName(aAboutURI, contractID));
+
+ // look up a handler to deal with "what"
+ contractID.InsertLiteral(NS_ABOUT_MODULE_CONTRACTID_PREFIX, 0);
+
+ return CallGetService(contractID.get(), aModule);
+}
+
+inline PRTime SecondsToPRTime(uint32_t t_sec) {
+ return (PRTime)t_sec * PR_USEC_PER_SEC;
+}
+
+inline void PrintTimeString(char* buf, uint32_t bufsize, uint32_t t_sec) {
+ PRExplodedTime et;
+ PRTime t_usec = SecondsToPRTime(t_sec);
+ PR_ExplodeTime(t_usec, PR_LocalTimeParameters, &et);
+ PR_FormatTime(buf, bufsize, "%Y-%m-%d %H:%M:%S", &et);
+}
+
+#endif
diff --git a/netwerk/protocol/about/nsIAboutModule.idl b/netwerk/protocol/about/nsIAboutModule.idl
new file mode 100644
index 0000000000..7750dd2d4b
--- /dev/null
+++ b/netwerk/protocol/about/nsIAboutModule.idl
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIChannel;
+interface nsILoadInfo;
+
+[scriptable, uuid(c0c19db9-1b5a-4ac5-b656-ed6f8149fa48)]
+interface nsIAboutModule : nsISupports
+{
+
+ /**
+ * Constructs a new channel for the about protocol module.
+ *
+ * @param aURI the uri of the new channel
+ * @param aLoadInfo the loadinfo of the new channel
+ */
+ nsIChannel newChannel(in nsIURI aURI,
+ in nsILoadInfo aLoadInfo);
+
+ /**
+ * A flag that indicates whether a URI should be run with content
+ * privileges. If it is, the about: protocol handler will enforce that
+ * the principal of channels created for it be based on their
+ * originalURI or URI (depending on the channel flags), by setting
+ * their "owner" to null.
+ * If content needs to be able to link to this URI, specify
+ * URI_CONTENT_LINKABLE as well.
+ */
+ const unsigned long URI_SAFE_FOR_UNTRUSTED_CONTENT = (1 << 0);
+
+ /**
+ * A flag that indicates whether script should be enabled for the
+ * given about: URI even if it's disabled in general.
+ */
+ const unsigned long ALLOW_SCRIPT = (1 << 1);
+
+ /**
+ * A flag that indicates whether this about: URI doesn't want to be listed
+ * in about:about, especially if it's not useful without a query string.
+ */
+ const unsigned long HIDE_FROM_ABOUTABOUT = (1 << 2);
+
+ /**
+ * A flag that indicates whether this about: URI wants Indexed DB enabled.
+ */
+ const unsigned long ENABLE_INDEXED_DB = (1 << 3);
+
+ /**
+ * A flag that indicates that this URI can be loaded in a child process
+ */
+ const unsigned long URI_CAN_LOAD_IN_CHILD = (1 << 4);
+
+ /**
+ * A flag that indicates that this URI must be loaded in a child process
+ */
+ const unsigned long URI_MUST_LOAD_IN_CHILD = (1 << 5);
+
+ /**
+ * Obsolete. This flag no longer has any effect and will be removed in future.
+ */
+ const unsigned long MAKE_UNLINKABLE = (1 << 6);
+
+ /**
+ * A flag that indicates that this URI should be linkable from content.
+ * Ignored unless URI_SAFE_FOR_UNTRUSTED_CONTENT is also specified.
+ *
+ * When adding a new about module with this flag make sure to also update
+ * IsSafeToLinkForUntrustedContent() in nsAboutProtocolHandler.cpp
+ */
+ const unsigned long MAKE_LINKABLE = (1 << 7);
+
+ /**
+ * A flag that indicates that this URI can be loaded in the privileged
+ * activity stream content process if said process is enabled. Ignored unless
+ * URI_MUST_LOAD_IN_CHILD is also specified.
+ */
+ const unsigned long URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS = (1 << 8);
+
+ /**
+ * A flag that indicates that this URI must be loaded in an extension process (if available).
+ */
+ const unsigned long URI_MUST_LOAD_IN_EXTENSION_PROCESS = (1 << 9);
+
+ /**
+ * A flag that indicates that this about: URI is a secure chrome UI
+ */
+ const unsigned long IS_SECURE_CHROME_UI = (1 << 10);
+
+ /**
+ * A method to get the flags that apply to a given about: URI. The URI
+ * passed in is guaranteed to be one of the URIs that this module
+ * registered to deal with.
+ */
+ unsigned long getURIFlags(in nsIURI aURI);
+
+ /**
+ * A method to get the chrome URI that corresponds to a given about URI.
+ */
+ nsIURI getChromeURI(in nsIURI aURI);
+};
+
+%{C++
+
+#define NS_ABOUT_MODULE_CONTRACTID "@mozilla.org/network/protocol/about;1"
+#define NS_ABOUT_MODULE_CONTRACTID_PREFIX NS_ABOUT_MODULE_CONTRACTID "?what="
+#define NS_ABOUT_MODULE_CONTRACTID_LENGTH 49 // strlen(NS_ABOUT_MODULE_CONTRACTID_PREFIX)
+
+%}
diff --git a/netwerk/protocol/data/DataChannelChild.cpp b/netwerk/protocol/data/DataChannelChild.cpp
new file mode 100644
index 0000000000..3ce6f02dd5
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelChild.cpp
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DataChannelChild.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED(DataChannelChild, nsDataChannel, nsIChildChannel)
+
+DataChannelChild::DataChannelChild(nsIURI* aURI)
+ : nsDataChannel(aURI), mIPCOpen(false) {}
+
+NS_IMETHODIMP
+DataChannelChild::ConnectParent(uint32_t aId) {
+ mozilla::dom::ContentChild* cc =
+ static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!gNeckoChild->SendPDataChannelConstructor(this, aId)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // IPC now has a ref to us.
+ mIPCOpen = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) {
+ nsresult rv;
+ rv = AsyncOpen(aListener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mIPCOpen) {
+ Unused << Send__delete__(this);
+ }
+ return NS_OK;
+}
+
+void DataChannelChild::ActorDestroy(ActorDestroyReason why) {
+ MOZ_ASSERT(mIPCOpen);
+ mIPCOpen = false;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/data/DataChannelChild.h b/netwerk/protocol/data/DataChannelChild.h
new file mode 100644
index 0000000000..8e9f91ea60
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelChild.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_DATACHANNELCHILD_H
+#define NS_DATACHANNELCHILD_H
+
+#include "nsDataChannel.h"
+#include "nsIChildChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PDataChannelChild.h"
+
+namespace mozilla {
+namespace net {
+
+class DataChannelChild : public nsDataChannel,
+ public nsIChildChannel,
+ public PDataChannelChild {
+ public:
+ explicit DataChannelChild(nsIURI* uri);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICHILDCHANNEL
+
+ protected:
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ private:
+ ~DataChannelChild() = default;
+
+ bool mIPCOpen;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NS_DATACHANNELCHILD_H */
diff --git a/netwerk/protocol/data/DataChannelParent.cpp b/netwerk/protocol/data/DataChannelParent.cpp
new file mode 100644
index 0000000000..e427120a55
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelParent.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DataChannelParent.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+
+#ifdef FUZZING_SNAPSHOT
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) (void)__VA_ARGS__
+#else
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) MOZ_ALWAYS_SUCCEEDS(__VA_ARGS__)
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(DataChannelParent, nsIParentChannel, nsIStreamListener)
+
+bool DataChannelParent::Init(const uint64_t& aChannelId) {
+ nsCOMPtr<nsIChannel> channel;
+
+ MOZ_ALWAYS_SUCCEEDS_FUZZING(
+ NS_LinkRedirectChannels(aChannelId, this, getter_AddRefs(channel)));
+
+ return true;
+}
+
+NS_IMETHODIMP
+DataChannelParent::SetParentListener(ParentChannelListener* aListener) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::Delete() {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::GetRemoteType(nsACString& aRemoteType) {
+ if (!CanSend()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ dom::PContentParent* pcp = Manager()->Manager();
+ aRemoteType = static_cast<dom::ContentParent*>(pcp)->GetRemoteType();
+ return NS_OK;
+}
+
+void DataChannelParent::ActorDestroy(ActorDestroyReason why) {}
+
+NS_IMETHODIMP
+DataChannelParent::OnStartRequest(nsIRequest* aRequest) {
+ // We don't have a way to prevent nsBaseChannel from calling AsyncOpen on
+ // the created nsDataChannel. We don't have anywhere to send the data in the
+ // parent, so abort the binding.
+ return NS_BINDING_ABORTED;
+}
+
+NS_IMETHODIMP
+DataChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ // See above.
+ MOZ_ASSERT(NS_FAILED(aStatusCode));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ // See above.
+ MOZ_CRASH("Should never be called");
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/data/DataChannelParent.h b/netwerk/protocol/data/DataChannelParent.h
new file mode 100644
index 0000000000..122cdda182
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelParent.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_DATACHANNELPARENT_H
+#define NS_DATACHANNELPARENT_H
+
+#include "nsIParentChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PDataChannelParent.h"
+
+namespace mozilla {
+namespace net {
+
+// In order to support HTTP redirects to data:, we need to implement the HTTP
+// redirection API, which requires a class that implements nsIParentChannel
+// and which calls NS_LinkRedirectChannels.
+class DataChannelParent : public nsIParentChannel, public PDataChannelParent {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ [[nodiscard]] bool Init(const uint64_t& aChannelId);
+
+ private:
+ ~DataChannelParent() = default;
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NS_DATACHANNELPARENT_H */
diff --git a/netwerk/protocol/data/moz.build b/netwerk/protocol/data/moz.build
new file mode 100644
index 0000000000..482c0b8a2f
--- /dev/null
+++ b/netwerk/protocol/data/moz.build
@@ -0,0 +1,29 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.net += [
+ "DataChannelChild.h",
+ "DataChannelParent.h",
+]
+
+EXPORTS += [
+ "nsDataChannel.h",
+ "nsDataHandler.h",
+]
+
+UNIFIED_SOURCES += [
+ "DataChannelChild.cpp",
+ "DataChannelParent.cpp",
+ "nsDataChannel.cpp",
+ "nsDataHandler.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
diff --git a/netwerk/protocol/data/nsDataChannel.cpp b/netwerk/protocol/data/nsDataChannel.cpp
new file mode 100644
index 0000000000..87b00adea3
--- /dev/null
+++ b/netwerk/protocol/data/nsDataChannel.cpp
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// data implementation
+
+#include "nsDataChannel.h"
+
+#include "mozilla/Base64.h"
+#include "nsDataHandler.h"
+#include "nsIInputStream.h"
+#include "nsEscape.h"
+#include "nsStringStream.h"
+#include "nsIObserverService.h"
+#include "mozilla/dom/ContentParent.h"
+
+using namespace mozilla;
+
+/**
+ * Helper for performing a fallible unescape.
+ *
+ * @param aStr The string to unescape.
+ * @param aBuffer Buffer to unescape into if necessary.
+ * @param rv Out: nsresult indicating success or failure of unescaping.
+ * @return Reference to the string containing the unescaped data.
+ */
+const nsACString& Unescape(const nsACString& aStr, nsACString& aBuffer,
+ nsresult* rv) {
+ MOZ_ASSERT(rv);
+
+ bool appended = false;
+ *rv = NS_UnescapeURL(aStr.Data(), aStr.Length(), /* aFlags = */ 0, aBuffer,
+ appended, mozilla::fallible);
+ if (NS_FAILED(*rv) || !appended) {
+ return aStr;
+ }
+
+ return aBuffer;
+}
+
+nsresult nsDataChannel::OpenContentStream(bool async, nsIInputStream** result,
+ nsIChannel** channel) {
+ NS_ENSURE_TRUE(URI(), NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv;
+
+ // In order to avoid potentially building up a new path including the
+ // ref portion of the URI, which we don't care about, we clone a version
+ // of the URI that does not have a ref and in most cases should share
+ // string buffers with the original URI.
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_GetURIWithoutRef(URI(), getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString path;
+ rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString contentType, contentCharset;
+ nsDependentCSubstring dataRange;
+ bool lBase64;
+ rv = nsDataHandler::ParsePathWithoutRef(path, contentType, &contentCharset,
+ lBase64, &dataRange, &mMimeType);
+ if (NS_FAILED(rv)) return rv;
+
+ // This will avoid a copy if nothing needs to be unescaped.
+ nsAutoCString unescapedBuffer;
+ const nsACString& data = Unescape(dataRange, unescapedBuffer, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (lBase64 && &data == &unescapedBuffer) {
+ // Don't allow spaces in base64-encoded content. This is only
+ // relevant for escaped spaces; other spaces are stripped in
+ // NewURI. We know there were no escaped spaces if the data buffer
+ // wasn't used in |Unescape|.
+ unescapedBuffer.StripWhitespace();
+ }
+
+ nsCOMPtr<nsIInputStream> bufInStream;
+ uint32_t contentLen;
+ if (lBase64) {
+ nsAutoCString decodedData;
+ rv = Base64Decode(data, decodedData);
+ if (NS_FAILED(rv)) {
+ // Returning this error code instead of what Base64Decode returns
+ // (NS_ERROR_ILLEGAL_VALUE) will prevent rendering of redirect response
+ // content by HTTP channels. It's also more logical error to return.
+ // Here we know the URL is actually corrupted.
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ contentLen = decodedData.Length();
+ rv = NS_NewCStringInputStream(getter_AddRefs(bufInStream), decodedData);
+ } else {
+ contentLen = data.Length();
+ rv = NS_NewCStringInputStream(getter_AddRefs(bufInStream), data);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+
+ SetContentType(contentType);
+ SetContentCharset(contentCharset);
+ mContentLength = contentLen;
+
+ // notify "data-channel-opened" observers
+ MaybeSendDataChannelOpenNotification();
+
+ bufInStream.forget(result);
+
+ return NS_OK;
+}
+
+nsresult nsDataChannel::MaybeSendDataChannelOpenNotification() {
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (!obsService) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsresult rv = GetLoadInfo(getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool isTopLevel;
+ rv = loadInfo->GetIsTopLevelLoad(&isTopLevel);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint64_t browsingContextID;
+ rv = loadInfo->GetBrowsingContextID(&browsingContextID);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if ((browsingContextID != 0 && isTopLevel) ||
+ !loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
+ obsService->NotifyObservers(static_cast<nsIChannel*>(this),
+ "data-channel-opened", nullptr);
+ }
+ return NS_OK;
+}
diff --git a/netwerk/protocol/data/nsDataChannel.h b/netwerk/protocol/data/nsDataChannel.h
new file mode 100644
index 0000000000..d7313d66a0
--- /dev/null
+++ b/netwerk/protocol/data/nsDataChannel.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// data implementation header
+
+#ifndef nsDataChannel_h___
+#define nsDataChannel_h___
+
+#include "nsBaseChannel.h"
+
+class nsIInputStream;
+
+class nsDataChannel : public nsBaseChannel {
+ public:
+ explicit nsDataChannel(nsIURI* uri) { SetURI(uri); }
+
+ const nsACString& MimeType() const { return mMimeType; }
+
+ protected:
+ [[nodiscard]] virtual nsresult OpenContentStream(
+ bool async, nsIInputStream** result, nsIChannel** channel) override;
+
+ nsCString mMimeType;
+
+ private:
+ nsresult MaybeSendDataChannelOpenNotification();
+};
+
+#endif /* nsDataChannel_h___ */
diff --git a/netwerk/protocol/data/nsDataHandler.cpp b/netwerk/protocol/data/nsDataHandler.cpp
new file mode 100644
index 0000000000..d3a5743097
--- /dev/null
+++ b/netwerk/protocol/data/nsDataHandler.cpp
@@ -0,0 +1,272 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDataChannel.h"
+#include "nsDataHandler.h"
+#include "nsNetCID.h"
+#include "nsError.h"
+#include "nsIOService.h"
+#include "DataChannelChild.h"
+#include "nsNetUtil.h"
+#include "nsSimpleURI.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/dom/MimeType.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Try.h"
+#include "DefaultURI.h"
+
+using namespace mozilla;
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsDataHandler, nsIProtocolHandler, nsISupportsWeakReference)
+
+nsresult nsDataHandler::Create(const nsIID& aIID, void** aResult) {
+ RefPtr<nsDataHandler> ph = new nsDataHandler();
+ return ph->QueryInterface(aIID, aResult);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsDataHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral("data");
+ return NS_OK;
+}
+
+/* static */ nsresult nsDataHandler::CreateNewURI(const nsACString& aSpec,
+ const char* aCharset,
+ nsIURI* aBaseURI,
+ nsIURI** result) {
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString contentType;
+ bool base64;
+ MOZ_TRY(ParseURI(aSpec, contentType, /* contentCharset = */ nullptr, base64,
+ /* dataBuffer = */ nullptr));
+
+ // Strip whitespace unless this is text, where whitespace is important
+ // Don't strip escaped whitespace though (bug 391951)
+ nsresult rv;
+ if (base64 || (StaticPrefs::network_url_strip_data_url_whitespace() &&
+ strncmp(contentType.get(), "text/", 5) != 0 &&
+ contentType.Find("xml") == kNotFound)) {
+ // it's ascii encoded binary, don't let any spaces in
+ rv = NS_MutateURI(new mozilla::net::nsSimpleURI::Mutator())
+ .Apply(&nsISimpleURIMutator::SetSpecAndFilterWhitespace, aSpec,
+ nullptr)
+ .Finalize(uri);
+ } else {
+ rv = NS_MutateURI(new mozilla::net::nsSimpleURI::Mutator())
+ .SetSpec(aSpec)
+ .Finalize(uri);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+
+ // use DefaultURI to check for validity when we have possible hostnames
+ // since nsSimpleURI doesn't know about hostnames
+ auto pos = aSpec.Find("data:");
+ if (pos != kNotFound) {
+ nsDependentCSubstring rest(aSpec, pos + sizeof("data:") - 1, -1);
+ if (StringBeginsWith(rest, "//"_ns)) {
+ nsCOMPtr<nsIURI> uriWithHost;
+ rv = NS_MutateURI(new mozilla::net::DefaultURI::Mutator())
+ .SetSpec(aSpec)
+ .Finalize(uriWithHost);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ uri.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDataHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ NS_ENSURE_ARG_POINTER(uri);
+ RefPtr<nsDataChannel> channel;
+ if (XRE_IsParentProcess()) {
+ channel = new nsDataChannel(uri);
+ } else {
+ channel = new mozilla::net::DataChannelChild(uri);
+ }
+
+ // set the loadInfo on the new channel
+ nsresult rv = channel->SetLoadInfo(aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDataHandler::AllowPort(int32_t port, const char* scheme, bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+namespace {
+
+bool TrimSpacesAndBase64(nsACString& aMimeType) {
+ const char* beg = aMimeType.BeginReading();
+ const char* end = aMimeType.EndReading();
+
+ // trim leading and trailing spaces
+ while (beg < end && NS_IsHTTPWhitespace(*beg)) {
+ ++beg;
+ }
+ if (beg == end) {
+ aMimeType.Truncate();
+ return false;
+ }
+ while (end > beg && NS_IsHTTPWhitespace(*(end - 1))) {
+ --end;
+ }
+ if (beg == end) {
+ aMimeType.Truncate();
+ return false;
+ }
+
+ // trim trailing `; base64` (if any) and remember it
+ const char* pos = end - 1;
+ bool foundBase64 = false;
+ if (pos > beg && *pos == '4' && --pos > beg && *pos == '6' && --pos > beg &&
+ ToLowerCaseASCII(*pos) == 'e' && --pos > beg &&
+ ToLowerCaseASCII(*pos) == 's' && --pos > beg &&
+ ToLowerCaseASCII(*pos) == 'a' && --pos > beg &&
+ ToLowerCaseASCII(*pos) == 'b') {
+ while (--pos > beg && NS_IsHTTPWhitespace(*pos)) {
+ }
+ if (pos >= beg && *pos == ';') {
+ end = pos;
+ foundBase64 = true;
+ }
+ }
+
+ // actually trim off the spaces and trailing base64, returning if we found it.
+ const char* s = aMimeType.BeginReading();
+ aMimeType.Assign(Substring(aMimeType, beg - s, end - s));
+ return foundBase64;
+}
+
+} // namespace
+
+nsresult nsDataHandler::ParsePathWithoutRef(const nsACString& aPath,
+ nsCString& aContentType,
+ nsCString* aContentCharset,
+ bool& aIsBase64,
+ nsDependentCSubstring* aDataBuffer,
+ nsCString* aMimeType) {
+ static constexpr auto kCharset = "charset"_ns;
+
+ // This implements https://fetch.spec.whatwg.org/#data-url-processor
+ // It also returns the full mimeType in aMimeType so fetch/XHR may access it
+ // for content-length headers. The contentType and charset parameters retain
+ // our legacy behavior, as much Gecko code generally expects GetContentType
+ // to yield only the MimeType's essence, not its full value with parameters.
+
+ aIsBase64 = false;
+
+ int32_t commaIdx = aPath.FindChar(',');
+
+ // This is a hack! When creating a URL using the DOM API we want to ignore
+ // if a comma is missing. But if we're actually loading a data: URI, in which
+ // case aContentCharset is not null, then we want to return an error if a
+ // comma is missing.
+ if (aContentCharset && commaIdx == kNotFound) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // "Let mimeType be the result of collecting a sequence of code points that
+ // are not equal to U+002C (,), given position."
+ nsCString mimeType(Substring(aPath, 0, commaIdx));
+
+ // "Strip leading and trailing ASCII whitespace from mimeType."
+ // "If mimeType ends with U+003B (;), followed by zero or more U+0020 SPACE,
+ // followed by an ASCII case-insensitive match for "base64", then ..."
+ aIsBase64 = TrimSpacesAndBase64(mimeType);
+
+ // "If mimeType starts with ";", then prepend "text/plain" to mimeType."
+ if (mimeType.Length() > 0 && mimeType.CharAt(0) == ';') {
+ mimeType = "text/plain"_ns + mimeType;
+ }
+
+ // "Let mimeTypeRecord be the result of parsing mimeType."
+ // This also checks for instances of ;base64 in the middle of the MimeType.
+ // This is against the current spec, but we're doing it because we have
+ // historically seen webcompat issues relying on this (see bug 781693).
+ if (mozilla::UniquePtr<CMimeType> parsed = CMimeType::Parse(mimeType)) {
+ parsed->GetEssence(aContentType);
+ if (aContentCharset) {
+ parsed->GetParameterValue(kCharset, *aContentCharset);
+ }
+ if (aMimeType) {
+ parsed->Serialize(*aMimeType);
+ }
+ if (parsed->IsBase64() &&
+ !StaticPrefs::network_url_strict_data_url_base64_placement()) {
+ aIsBase64 = true;
+ }
+ } else {
+ // "If mimeTypeRecord is failure, then set mimeTypeRecord to
+ // text/plain;charset=US-ASCII."
+ aContentType.AssignLiteral("text/plain");
+ if (aContentCharset) {
+ aContentCharset->AssignLiteral("US-ASCII");
+ }
+ if (aMimeType) {
+ aMimeType->AssignLiteral("text/plain;charset=US-ASCII");
+ }
+ }
+
+ if (aDataBuffer) {
+ aDataBuffer->Rebind(aPath, commaIdx + 1);
+ }
+
+ return NS_OK;
+}
+
+static inline char ToLower(const char c) {
+ if (c >= 'A' && c <= 'Z') {
+ return char(c + ('a' - 'A'));
+ }
+ return c;
+}
+
+nsresult nsDataHandler::ParseURI(const nsACString& spec, nsCString& contentType,
+ nsCString* contentCharset, bool& isBase64,
+ nsCString* dataBuffer) {
+ static constexpr auto kDataScheme = "data:"_ns;
+
+ // move past "data:"
+ const char* pos = std::search(
+ spec.BeginReading(), spec.EndReading(), kDataScheme.BeginReading(),
+ kDataScheme.EndReading(),
+ [](const char a, const char b) { return ToLower(a) == ToLower(b); });
+ if (pos == spec.EndReading()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ uint32_t scheme = pos - spec.BeginReading();
+ scheme += kDataScheme.Length();
+
+ // Find the start of the hash ref if present.
+ int32_t hash = spec.FindChar('#', scheme);
+
+ auto pathWithoutRef = Substring(spec, scheme, hash != kNotFound ? hash : -1);
+ nsDependentCSubstring dataRange;
+ nsresult rv = ParsePathWithoutRef(pathWithoutRef, contentType, contentCharset,
+ isBase64, &dataRange);
+ if (NS_SUCCEEDED(rv) && dataBuffer) {
+ if (!dataBuffer->Assign(dataRange, mozilla::fallible)) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ return rv;
+}
diff --git a/netwerk/protocol/data/nsDataHandler.h b/netwerk/protocol/data/nsDataHandler.h
new file mode 100644
index 0000000000..4796f0f453
--- /dev/null
+++ b/netwerk/protocol/data/nsDataHandler.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDataHandler_h___
+#define nsDataHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsWeakReference.h"
+
+class nsDataHandler : public nsIProtocolHandler,
+ public nsSupportsWeakReference {
+ virtual ~nsDataHandler() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ // nsDataHandler methods:
+ nsDataHandler() = default;
+
+ static nsresult CreateNewURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** result);
+
+ // Define a Create method to be used with a factory:
+ [[nodiscard]] static nsresult Create(const nsIID& aIID, void** aResult);
+
+ // Parse a data: URI and return the individual parts
+ // (the given spec will temporarily be modified but will be returned
+ // to the original before returning)
+ // contentCharset and dataBuffer can be nullptr if they are not needed.
+ [[nodiscard]] static nsresult ParseURI(const nsACString& spec,
+ nsCString& contentType,
+ nsCString* contentCharset,
+ bool& isBase64, nsCString* dataBuffer);
+
+ // Parse the path portion of a data: URI and return the individual parts.
+ //
+ // Note: The path is assumed *not* to have a ref portion.
+ //
+ // @arg aPath The path portion of the spec. Must not have ref portion.
+ // @arg aContentType Out param, will hold the parsed content type.
+ // @arg aContentCharset Optional, will hold the charset if specified.
+ // @arg aIsBase64 Out param, indicates if the data is base64 encoded.
+ // @arg aDataBuffer Optional, will reference the substring in |aPath| that
+ // contains the data portion of the path. No copy is made.
+ [[nodiscard]] static nsresult ParsePathWithoutRef(
+ const nsACString& aPath, nsCString& aContentType,
+ nsCString* aContentCharset, bool& aIsBase64,
+ nsDependentCSubstring* aDataBuffer, nsCString* aMimeType = nullptr);
+};
+
+#endif /* nsDataHandler_h___ */
diff --git a/netwerk/protocol/data/nsDataModule.cpp b/netwerk/protocol/data/nsDataModule.cpp
new file mode 100644
index 0000000000..8bcda94362
--- /dev/null
+++ b/netwerk/protocol/data/nsDataModule.cpp
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIGenericFactory.h"
+#include "nsDataHandler.h"
+
+// The list of components we register
+static const nsModuleComponentInfo components[] = {
+ {"Data Protocol Handler", NS_DATAHANDLER_CID,
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "data", nsDataHandler::Create},
+};
+
+NS_IMPL_NSGETMODULE(nsDataProtocolModule, components)
diff --git a/netwerk/protocol/file/FileChannelChild.cpp b/netwerk/protocol/file/FileChannelChild.cpp
new file mode 100644
index 0000000000..fa9cc00584
--- /dev/null
+++ b/netwerk/protocol/file/FileChannelChild.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FileChannelChild.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED(FileChannelChild, nsFileChannel, nsIChildChannel)
+
+FileChannelChild::FileChannelChild(nsIURI* uri) : nsFileChannel(uri) {}
+
+NS_IMETHODIMP
+FileChannelChild::ConnectParent(uint32_t id) {
+ mozilla::dom::ContentChild* cc =
+ static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!gNeckoChild->SendPFileChannelConstructor(this, id)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelChild::CompleteRedirectSetup(nsIStreamListener* listener) {
+ nsresult rv;
+
+ rv = AsyncOpen(listener);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (CanSend()) {
+ Unused << Send__delete__(this);
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/file/FileChannelChild.h b/netwerk/protocol/file/FileChannelChild.h
new file mode 100644
index 0000000000..e6d67b7d1c
--- /dev/null
+++ b/netwerk/protocol/file/FileChannelChild.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla__net__FileChannelChild_h
+#define mozilla__net__FileChannelChild_h
+
+#include "nsFileChannel.h"
+#include "nsIChildChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PFileChannelChild.h"
+
+namespace mozilla {
+namespace net {
+
+class FileChannelChild : public nsFileChannel,
+ public nsIChildChannel,
+ public PFileChannelChild {
+ public:
+ explicit FileChannelChild(nsIURI* uri);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICHILDCHANNEL
+
+ private:
+ ~FileChannelChild() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* mozilla__net__FileChannelChild_h */
diff --git a/netwerk/protocol/file/FileChannelParent.cpp b/netwerk/protocol/file/FileChannelParent.cpp
new file mode 100644
index 0000000000..e54fb11260
--- /dev/null
+++ b/netwerk/protocol/file/FileChannelParent.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FileChannelParent.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+
+#ifdef FUZZING_SNAPSHOT
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) (void)__VA_ARGS__
+#else
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) MOZ_ALWAYS_SUCCEEDS(__VA_ARGS__)
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(FileChannelParent, nsIParentChannel, nsIStreamListener)
+
+bool FileChannelParent::Init(const uint64_t& aChannelId) {
+ nsCOMPtr<nsIChannel> channel;
+
+ MOZ_ALWAYS_SUCCEEDS_FUZZING(
+ NS_LinkRedirectChannels(aChannelId, this, getter_AddRefs(channel)));
+
+ return true;
+}
+
+NS_IMETHODIMP
+FileChannelParent::SetParentListener(ParentChannelListener* aListener) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::Delete() {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::GetRemoteType(nsACString& aRemoteType) {
+ if (!CanSend()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ dom::PContentParent* pcp = Manager()->Manager();
+ aRemoteType = static_cast<dom::ContentParent*>(pcp)->GetRemoteType();
+ return NS_OK;
+}
+
+void FileChannelParent::ActorDestroy(ActorDestroyReason why) {}
+
+NS_IMETHODIMP
+FileChannelParent::OnStartRequest(nsIRequest* aRequest) {
+ // We don't have a way to prevent nsBaseChannel from calling AsyncOpen on
+ // the created nsDataChannel. We don't have anywhere to send the data in the
+ // parent, so abort the binding.
+ return NS_BINDING_ABORTED;
+}
+
+NS_IMETHODIMP
+FileChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ // See above.
+ MOZ_ASSERT(NS_FAILED(aStatusCode));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ // See above.
+ MOZ_CRASH("Should never be called");
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/file/FileChannelParent.h b/netwerk/protocol/file/FileChannelParent.h
new file mode 100644
index 0000000000..f2a10d8f87
--- /dev/null
+++ b/netwerk/protocol/file/FileChannelParent.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla__net__FileChannelParent_h
+#define mozilla__net__FileChannelParent_h
+
+#include "nsIParentChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PFileChannelParent.h"
+
+namespace mozilla {
+namespace net {
+
+// In order to support HTTP redirects to file:, we need to implement the HTTP
+// redirection API, which requires a class that implements nsIParentChannel
+// and which calls NS_LinkRedirectChannels.
+class FileChannelParent : public nsIParentChannel, public PFileChannelParent {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ [[nodiscard]] bool Init(const uint64_t& aChannelId);
+
+ private:
+ ~FileChannelParent() = default;
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* mozilla__net__FileChannelParent_h */
diff --git a/netwerk/protocol/file/moz.build b/netwerk/protocol/file/moz.build
new file mode 100644
index 0000000000..821e20e1c5
--- /dev/null
+++ b/netwerk/protocol/file/moz.build
@@ -0,0 +1,40 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: File")
+
+EXPORTS.mozilla.net += [
+ "FileChannelChild.h",
+ "FileChannelParent.h",
+ "nsFileProtocolHandler.h",
+]
+
+EXPORTS += [
+ "nsFileChannel.h",
+]
+
+XPIDL_SOURCES += [
+ "nsIFileChannel.idl",
+ "nsIFileProtocolHandler.idl",
+]
+
+XPIDL_MODULE = "necko_file"
+
+UNIFIED_SOURCES += [
+ "FileChannelChild.cpp",
+ "FileChannelParent.cpp",
+ "nsFileChannel.cpp",
+ "nsFileProtocolHandler.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
diff --git a/netwerk/protocol/file/nsFileChannel.cpp b/netwerk/protocol/file/nsFileChannel.cpp
new file mode 100644
index 0000000000..096f8807ba
--- /dev/null
+++ b/netwerk/protocol/file/nsFileChannel.cpp
@@ -0,0 +1,569 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOService.h"
+#include "nsFileChannel.h"
+#include "nsBaseContentStream.h"
+#include "nsDirectoryIndexStream.h"
+#include "nsThreadUtils.h"
+#include "nsTransportUtils.h"
+#include "nsStreamUtils.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsIOutputStream.h"
+#include "nsIFileStreams.h"
+#include "nsFileProtocolHandler.h"
+#include "nsProxyRelease.h"
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "../protocol/http/nsHttpHandler.h"
+
+#include "nsIFileURL.h"
+#include "nsIURIMutator.h"
+#include "nsIFile.h"
+#include "nsIMIMEService.h"
+#include "prio.h"
+#include <algorithm>
+
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Unused.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+//-----------------------------------------------------------------------------
+
+class nsFileCopyEvent : public Runnable {
+ public:
+ nsFileCopyEvent(nsIOutputStream* dest, nsIInputStream* source, int64_t len)
+ : mozilla::Runnable("nsFileCopyEvent"),
+ mDest(dest),
+ mSource(source),
+ mLen(len),
+ mStatus(NS_OK),
+ mInterruptStatus(NS_OK) {}
+
+ // Read the current status of the file copy operation.
+ nsresult Status() { return mStatus; }
+
+ // Call this method to perform the file copy synchronously.
+ void DoCopy();
+
+ // Call this method to perform the file copy on a background thread. The
+ // callback is dispatched when the file copy completes.
+ nsresult Dispatch(nsIRunnable* callback, nsITransportEventSink* sink,
+ nsIEventTarget* target);
+
+ // Call this method to interrupt a file copy operation that is occuring on
+ // a background thread. The status parameter passed to this function must
+ // be a failure code and is set as the status of this file copy operation.
+ void Interrupt(nsresult status) {
+ NS_ASSERTION(NS_FAILED(status), "must be a failure code");
+ mInterruptStatus = status;
+ }
+
+ NS_IMETHOD Run() override {
+ DoCopy();
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+ nsCOMPtr<nsIRunnable> mCallback;
+ nsCOMPtr<nsITransportEventSink> mSink;
+ nsCOMPtr<nsIOutputStream> mDest;
+ nsCOMPtr<nsIInputStream> mSource;
+ int64_t mLen;
+ nsresult mStatus; // modified on i/o thread only
+ nsresult mInterruptStatus; // modified on main thread only
+};
+
+void nsFileCopyEvent::DoCopy() {
+ // We'll copy in chunks this large by default. This size affects how
+ // frequently we'll check for interrupts.
+ const int32_t chunk =
+ nsIOService::gDefaultSegmentSize * nsIOService::gDefaultSegmentCount;
+
+ nsresult rv = NS_OK;
+
+ int64_t len = mLen, progress = 0;
+ while (len) {
+ // If we've been interrupted, then stop copying.
+ rv = mInterruptStatus;
+ if (NS_FAILED(rv)) break;
+
+ int32_t num = std::min((int32_t)len, chunk);
+
+ uint32_t result;
+ rv = mSource->ReadSegments(NS_CopySegmentToStream, mDest, num, &result);
+ if (NS_FAILED(rv)) break;
+ if (result != (uint32_t)num) {
+ // stopped prematurely (out of disk space)
+ rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
+ break;
+ }
+
+ // Dispatch progress notification
+ if (mSink) {
+ progress += num;
+ mSink->OnTransportStatus(nullptr, NS_NET_STATUS_WRITING, progress, mLen);
+ }
+
+ len -= num;
+ }
+
+ if (NS_FAILED(rv)) mStatus = rv;
+
+ // Close the output stream before notifying our callback so that others may
+ // freely "play" with the file.
+ mDest->Close();
+
+ // Notify completion
+ if (mCallback) {
+ mCallbackTarget->Dispatch(mCallback, NS_DISPATCH_NORMAL);
+
+ // Release the callback on the target thread to avoid destroying stuff on
+ // the wrong thread.
+ NS_ProxyRelease("nsFileCopyEvent::mCallback", mCallbackTarget,
+ mCallback.forget());
+ }
+}
+
+nsresult nsFileCopyEvent::Dispatch(nsIRunnable* callback,
+ nsITransportEventSink* sink,
+ nsIEventTarget* target) {
+ // Use the supplied event target for all asynchronous operations.
+
+ mCallback = callback;
+ mCallbackTarget = target;
+
+ // Build a coalescing proxy for progress events
+ nsresult rv =
+ net_NewTransportEventSinkProxy(getter_AddRefs(mSink), sink, target);
+
+ if (NS_FAILED(rv)) return rv;
+
+ // Dispatch ourselves to I/O thread pool...
+ nsCOMPtr<nsIEventTarget> pool =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ return pool->Dispatch(this, NS_DISPATCH_NORMAL);
+}
+
+//-----------------------------------------------------------------------------
+
+// This is a dummy input stream that when read, performs the file copy. The
+// copy happens on a background thread via mCopyEvent.
+
+class nsFileUploadContentStream : public nsBaseContentStream {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsFileUploadContentStream,
+ nsBaseContentStream)
+
+ nsFileUploadContentStream(bool nonBlocking, nsIOutputStream* dest,
+ nsIInputStream* source, int64_t len,
+ nsITransportEventSink* sink)
+ : nsBaseContentStream(nonBlocking),
+ mCopyEvent(new nsFileCopyEvent(dest, source, len)),
+ mSink(sink) {}
+
+ bool IsInitialized() { return mCopyEvent != nullptr; }
+
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun fun, void* closure, uint32_t count,
+ uint32_t* result) override;
+ NS_IMETHOD AsyncWait(nsIInputStreamCallback* callback, uint32_t flags,
+ uint32_t count, nsIEventTarget* target) override;
+
+ private:
+ virtual ~nsFileUploadContentStream() = default;
+
+ void OnCopyComplete();
+
+ RefPtr<nsFileCopyEvent> mCopyEvent;
+ nsCOMPtr<nsITransportEventSink> mSink;
+};
+
+NS_IMETHODIMP
+nsFileUploadContentStream::ReadSegments(nsWriteSegmentFun fun, void* closure,
+ uint32_t count, uint32_t* result) {
+ *result = 0; // nothing is ever actually read from this stream
+
+ if (IsClosed()) return NS_OK;
+
+ if (IsNonBlocking()) {
+ // Inform the caller that they will have to wait for the copy operation to
+ // complete asynchronously. We'll kick of the copy operation once they
+ // call AsyncWait.
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // Perform copy synchronously, and then close out the stream.
+ mCopyEvent->DoCopy();
+ nsresult status = mCopyEvent->Status();
+ CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED);
+ return status;
+}
+
+NS_IMETHODIMP
+nsFileUploadContentStream::AsyncWait(nsIInputStreamCallback* callback,
+ uint32_t flags, uint32_t count,
+ nsIEventTarget* target) {
+ nsresult rv = nsBaseContentStream::AsyncWait(callback, flags, count, target);
+ if (NS_FAILED(rv) || IsClosed()) return rv;
+
+ if (IsNonBlocking()) {
+ nsCOMPtr<nsIRunnable> callback =
+ NewRunnableMethod("nsFileUploadContentStream::OnCopyComplete", this,
+ &nsFileUploadContentStream::OnCopyComplete);
+ mCopyEvent->Dispatch(callback, mSink, target);
+ }
+
+ return NS_OK;
+}
+
+void nsFileUploadContentStream::OnCopyComplete() {
+ // This method is being called to indicate that we are done copying.
+ nsresult status = mCopyEvent->Status();
+
+ CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED);
+}
+
+//-----------------------------------------------------------------------------
+
+nsFileChannel::nsFileChannel(nsIURI* uri) : mUploadLength(0), mFileURI(uri) {}
+
+nsresult nsFileChannel::Init() {
+ NS_ENSURE_STATE(mLoadInfo);
+
+ RefPtr<nsHttpHandler> handler = nsHttpHandler::GetInstance();
+ MOZ_ALWAYS_SUCCEEDS(handler->NewChannelId(mChannelId));
+
+ // If we have a link file, we should resolve its target right away.
+ // This is to protect against a same origin attack where the same link file
+ // can point to different resources right after the first resource is loaded.
+ nsCOMPtr<nsIFile> file;
+ nsCOMPtr<nsIURI> targetURI;
+#ifdef XP_WIN
+ nsAutoString fileTarget;
+#else
+ nsAutoCString fileTarget;
+#endif
+ nsCOMPtr<nsIFile> resolvedFile;
+ bool symLink;
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mFileURI);
+ if (fileURL && NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) &&
+ NS_SUCCEEDED(file->IsSymlink(&symLink)) && symLink &&
+#ifdef XP_WIN
+ NS_SUCCEEDED(file->GetTarget(fileTarget)) &&
+ NS_SUCCEEDED(
+ NS_NewLocalFile(fileTarget, true, getter_AddRefs(resolvedFile))) &&
+#else
+ NS_SUCCEEDED(file->GetNativeTarget(fileTarget)) &&
+ NS_SUCCEEDED(NS_NewNativeLocalFile(fileTarget, true,
+ getter_AddRefs(resolvedFile))) &&
+#endif
+ NS_SUCCEEDED(
+ NS_NewFileURI(getter_AddRefs(targetURI), resolvedFile, nullptr))) {
+ // Make an effort to match up the query strings.
+ nsCOMPtr<nsIURL> origURL = do_QueryInterface(mFileURI);
+ nsCOMPtr<nsIURL> targetURL = do_QueryInterface(targetURI);
+ nsAutoCString queryString;
+ if (origURL && targetURL && NS_SUCCEEDED(origURL->GetQuery(queryString))) {
+ Unused
+ << NS_MutateURI(targetURI).SetQuery(queryString).Finalize(targetURI);
+ }
+
+ SetURI(targetURI);
+ SetOriginalURI(mFileURI);
+ mLoadInfo->SetResultPrincipalURI(targetURI);
+ } else {
+ SetURI(mFileURI);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsFileChannel::MakeFileInputStream(nsIFile* file,
+ nsCOMPtr<nsIInputStream>& stream,
+ nsCString& contentType,
+ bool async) {
+ // we accept that this might result in a disk hit to stat the file
+ bool isDir;
+ nsresult rv = file->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ CheckForBrokenChromeURL(mLoadInfo, OriginalURI());
+ }
+
+ if (async && (NS_ERROR_FILE_NOT_FOUND == rv)) {
+ // We don't return "Not Found" errors here. Since we could not find
+ // the file, it's not a directory anyway.
+ isDir = false;
+ } else {
+ return rv;
+ }
+ }
+
+ if (isDir) {
+ rv = nsDirectoryIndexStream::Create(file, getter_AddRefs(stream));
+ if (NS_SUCCEEDED(rv) && !HasContentTypeHint()) {
+ contentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT);
+ }
+ } else {
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, -1, -1,
+ async ? nsIFileInputStream::DEFER_OPEN : 0);
+ if (NS_SUCCEEDED(rv) && !HasContentTypeHint()) {
+ // Use file extension to infer content type
+ nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mime->GetTypeFromFile(file, contentType);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult nsFileChannel::OpenContentStream(bool async, nsIInputStream** result,
+ nsIChannel** channel) {
+ // NOTE: the resulting file is a clone, so it is safe to pass it to the
+ // file input stream which will be read on a background thread.
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> newURI;
+ if (NS_SUCCEEDED(fileHandler->ReadURLFile(file, getter_AddRefs(newURI))) ||
+ NS_SUCCEEDED(fileHandler->ReadShellLink(file, getter_AddRefs(newURI)))) {
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = NS_NewChannel(getter_AddRefs(newChannel), newURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+
+ if (NS_FAILED(rv)) return rv;
+
+ *result = nullptr;
+ newChannel.forget(channel);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+
+ if (mUploadStream) {
+ // Pass back a nsFileUploadContentStream instance that knows how to perform
+ // the file copy when "read" (the resulting stream in this case does not
+ // actually return any data).
+
+ nsCOMPtr<nsIOutputStream> fileStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileStream), file,
+ PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
+ PR_IRUSR | PR_IWUSR);
+ if (NS_FAILED(rv)) return rv;
+
+ RefPtr<nsFileUploadContentStream> uploadStream =
+ new nsFileUploadContentStream(async, fileStream, mUploadStream,
+ mUploadLength, this);
+ if (!uploadStream || !uploadStream->IsInitialized()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ stream = std::move(uploadStream);
+
+ mContentLength = 0;
+
+ // Since there isn't any content to speak of we just set the content-type
+ // to something other than "unknown" to avoid triggering the content-type
+ // sniffer code in nsBaseChannel.
+ // However, don't override explicitly set types.
+ if (!HasContentTypeHint()) {
+ SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
+ }
+ } else {
+ nsAutoCString contentType;
+ rv = MakeFileInputStream(file, stream, contentType, async);
+ if (NS_FAILED(rv)) return rv;
+
+ EnableSynthesizedProgressEvents(true);
+
+ // fixup content length and type
+
+ // when we are called from asyncOpen, the content length fixup will be
+ // performed on a background thread and block the listener invocation via
+ // ListenerBlockingPromise method
+ if (!async && mContentLength < 0) {
+ rv = FixupContentLength(false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (!contentType.IsEmpty()) {
+ SetContentType(contentType);
+ }
+ }
+
+ // notify "file-channel-opened" observers
+ MaybeSendFileOpenNotification();
+
+ *result = nullptr;
+ stream.swap(*result);
+ return NS_OK;
+}
+
+nsresult nsFileChannel::ListenerBlockingPromise(BlockingPromise** aPromise) {
+ NS_ENSURE_ARG(aPromise);
+ *aPromise = nullptr;
+
+ if (mContentLength >= 0) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIEventTarget> sts(
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID));
+ if (!sts) {
+ return FixupContentLength(true);
+ }
+
+ RefPtr<TaskQueue> taskQueue = TaskQueue::Create(sts.forget(), "FileChannel");
+ RefPtr<nsFileChannel> self = this;
+ RefPtr<BlockingPromise> promise =
+ mozilla::InvokeAsync(taskQueue, __func__, [self{std::move(self)}]() {
+ nsresult rv = self->FixupContentLength(true);
+ if (NS_FAILED(rv)) {
+ return BlockingPromise::CreateAndReject(rv, __func__);
+ }
+ return BlockingPromise::CreateAndResolve(NS_OK, __func__);
+ });
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+nsresult nsFileChannel::FixupContentLength(bool async) {
+ MOZ_ASSERT(mContentLength < 0);
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ int64_t size;
+ rv = file->GetFileSize(&size);
+ if (NS_FAILED(rv)) {
+ if (async && NS_ERROR_FILE_NOT_FOUND == rv) {
+ size = 0;
+ } else {
+ return rv;
+ }
+ }
+ mContentLength = size;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileChannel, nsBaseChannel, nsIUploadChannel,
+ nsIFileChannel, nsIIdentChannel)
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsIFileChannel
+
+NS_IMETHODIMP
+nsFileChannel::GetFile(nsIFile** file) {
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(URI());
+ NS_ENSURE_STATE(fileURL);
+
+ // This returns a cloned nsIFile
+ return fileURL->GetFile(file);
+}
+
+nsresult nsFileChannel::MaybeSendFileOpenNotification() {
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (!obsService) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsresult rv = GetLoadInfo(getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool isTopLevel;
+ rv = loadInfo->GetIsTopLevelLoad(&isTopLevel);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint64_t browsingContextID;
+ rv = loadInfo->GetBrowsingContextID(&browsingContextID);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if ((browsingContextID != 0 && isTopLevel) ||
+ !loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
+ obsService->NotifyObservers(static_cast<nsIIdentChannel*>(this),
+ "file-channel-opened", nullptr);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsIUploadChannel
+
+NS_IMETHODIMP
+nsFileChannel::SetUploadStream(nsIInputStream* stream,
+ const nsACString& contentType,
+ int64_t contentLength) {
+ NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
+
+ if ((mUploadStream = stream)) {
+ mUploadLength = contentLength;
+ if (mUploadLength < 0) {
+ // Make sure we know how much data we are uploading.
+ uint64_t avail;
+ nsresult rv = mUploadStream->Available(&avail);
+ if (NS_FAILED(rv)) return rv;
+ // if this doesn't fit in the javascript MAX_SAFE_INTEGER
+ // pretend we don't know the size
+ mUploadLength = InScriptableRange(avail) ? avail : -1;
+ }
+ } else {
+ mUploadLength = -1;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileChannel::GetUploadStream(nsIInputStream** result) {
+ *result = do_AddRef(mUploadStream).take();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsIIdentChannel
+
+NS_IMETHODIMP
+nsFileChannel::GetChannelId(uint64_t* aChannelId) {
+ *aChannelId = mChannelId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileChannel::SetChannelId(uint64_t aChannelId) {
+ mChannelId = aChannelId;
+ return NS_OK;
+}
diff --git a/netwerk/protocol/file/nsFileChannel.h b/netwerk/protocol/file/nsFileChannel.h
new file mode 100644
index 0000000000..781ce5b7af
--- /dev/null
+++ b/netwerk/protocol/file/nsFileChannel.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFileChannel_h__
+#define nsFileChannel_h__
+
+#include "nsBaseChannel.h"
+#include "nsIFileChannel.h"
+#include "nsIUploadChannel.h"
+
+class nsFileChannel : public nsBaseChannel,
+ public nsIFileChannel,
+ public nsIUploadChannel,
+ public nsIIdentChannel {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILECHANNEL
+ NS_DECL_NSIUPLOADCHANNEL
+ NS_FORWARD_NSIREQUEST(nsBaseChannel::)
+ NS_FORWARD_NSICHANNEL(nsBaseChannel::)
+ NS_DECL_NSIIDENTCHANNEL
+
+ explicit nsFileChannel(nsIURI* uri);
+
+ nsresult Init();
+
+ protected:
+ ~nsFileChannel() = default;
+
+ // Called to construct a blocking file input stream for the given file. This
+ // method also returns a best guess at the content-type for the data stream.
+ // NOTE: If the channel has a type hint set, contentType will be left
+ // untouched. The caller should not use it in that case.
+ [[nodiscard]] nsresult MakeFileInputStream(nsIFile* file,
+ nsCOMPtr<nsIInputStream>& stream,
+ nsCString& contentType,
+ bool async);
+
+ [[nodiscard]] virtual nsresult OpenContentStream(
+ bool async, nsIInputStream** result, nsIChannel** channel) override;
+
+ // Implementing the pump blocking promise to fixup content length on a
+ // background thread prior to calling on mListener
+ virtual nsresult ListenerBlockingPromise(BlockingPromise** promise) override;
+
+ private:
+ nsresult FixupContentLength(bool async);
+ nsresult MaybeSendFileOpenNotification();
+
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ int64_t mUploadLength;
+ nsCOMPtr<nsIURI> mFileURI;
+ uint64_t mChannelId = 0;
+};
+
+#endif // !nsFileChannel_h__
diff --git a/netwerk/protocol/file/nsFileProtocolHandler.cpp b/netwerk/protocol/file/nsFileProtocolHandler.cpp
new file mode 100644
index 0000000000..10a1b9c029
--- /dev/null
+++ b/netwerk/protocol/file/nsFileProtocolHandler.cpp
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:ts=4 sw=2 sts=2 et cin:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIFile.h"
+#include "nsFileProtocolHandler.h"
+#include "nsFileChannel.h"
+#include "nsStandardURL.h"
+#include "nsURLHelper.h"
+#include "nsIURIMutator.h"
+
+#include "nsNetUtil.h"
+
+#include "FileChannelChild.h"
+
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/net/NeckoCommon.h"
+
+// URL file handling, copied and modified from
+// xpfe/components/bookmarks/src/nsBookmarksService.cpp
+#ifdef XP_WIN
+# include <shlobj.h>
+# include <intshcut.h>
+# include "nsIFileURL.h"
+# ifdef CompareString
+# undef CompareString
+# endif
+#endif
+
+// URL file handling for freedesktop.org
+#ifdef XP_UNIX
+# include "nsINIParser.h"
+# define DESKTOP_ENTRY_SECTION "Desktop Entry"
+#endif
+
+//-----------------------------------------------------------------------------
+
+nsresult nsFileProtocolHandler::Init() { return NS_OK; }
+
+NS_IMPL_ISUPPORTS(nsFileProtocolHandler, nsIFileProtocolHandler,
+ nsIProtocolHandler, nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsIProtocolHandler methods:
+
+#if defined(XP_WIN)
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadURLFile(nsIFile* aFile, nsIURI** aURI) {
+ nsAutoString path;
+ nsresult rv = aFile->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ if (path.Length() < 4) return NS_ERROR_NOT_AVAILABLE;
+ if (!StringTail(path, 4).LowerCaseEqualsLiteral(".url"))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ HRESULT result;
+
+ rv = NS_ERROR_NOT_AVAILABLE;
+
+ IUniformResourceLocatorW* urlLink = nullptr;
+ result =
+ ::CoCreateInstance(CLSID_InternetShortcut, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IUniformResourceLocatorW, (void**)&urlLink);
+ if (SUCCEEDED(result) && urlLink) {
+ IPersistFile* urlFile = nullptr;
+ result = urlLink->QueryInterface(IID_IPersistFile, (void**)&urlFile);
+ if (SUCCEEDED(result) && urlFile) {
+ result = urlFile->Load(path.get(), STGM_READ);
+ if (SUCCEEDED(result)) {
+ LPWSTR lpTemp = nullptr;
+
+ // The URL this method will give us back seems to be already
+ // escaped. Hence, do not do escaping of our own.
+ result = urlLink->GetURL(&lpTemp);
+ if (SUCCEEDED(result) && lpTemp) {
+ rv = NS_NewURI(aURI, nsDependentString(lpTemp));
+ // free the string that GetURL alloc'd
+ CoTaskMemFree(lpTemp);
+ }
+ }
+ urlFile->Release();
+ }
+ urlLink->Release();
+ }
+ return rv;
+}
+
+#elif defined(XP_UNIX)
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadURLFile(nsIFile* aFile, nsIURI** aURI) {
+ // We only support desktop files that end in ".desktop" like the spec says:
+ // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s02.html
+ nsAutoCString leafName;
+ nsresult rv = aFile->GetNativeLeafName(leafName);
+ if (NS_FAILED(rv) || !StringEndsWith(leafName, ".desktop"_ns)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ bool isFile = false;
+ rv = aFile->IsFile(&isFile);
+ if (NS_FAILED(rv) || !isFile) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsINIParser parser;
+ rv = parser.Init(aFile);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString type;
+ parser.GetString(DESKTOP_ENTRY_SECTION, "Type", type);
+ if (!type.EqualsLiteral("Link")) return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoCString url;
+ rv = parser.GetString(DESKTOP_ENTRY_SECTION, "URL", url);
+ if (NS_FAILED(rv) || url.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_NewURI(aURI, url);
+}
+
+#else // other platforms
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadURLFile(nsIFile* aFile, nsIURI** aURI) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+#endif // ReadURLFile()
+
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadShellLink(nsIFile* aFile, nsIURI** aURI) {
+#if defined(XP_WIN)
+ nsAutoString path;
+ MOZ_TRY(aFile->GetPath(path));
+
+ if (path.Length() < 4 ||
+ !StringTail(path, 4).LowerCaseEqualsLiteral(".lnk")) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ RefPtr<IPersistFile> persistFile;
+ RefPtr<IShellLinkW> shellLink;
+ WCHAR lpTemp[MAX_PATH];
+ // Get a pointer to the IPersistFile interface.
+ if (FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW, getter_AddRefs(shellLink))) ||
+ FAILED(shellLink->QueryInterface(IID_IPersistFile,
+ getter_AddRefs(persistFile))) ||
+ FAILED(persistFile->Load(path.get(), STGM_READ)) ||
+ FAILED(shellLink->Resolve(nullptr, SLR_NO_UI)) ||
+ FAILED(shellLink->GetPath(lpTemp, MAX_PATH, nullptr, SLGP_UNCPRIORITY))) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIFile> linkedFile;
+ MOZ_TRY(NS_NewLocalFile(nsDependentString(lpTemp), false,
+ getter_AddRefs(linkedFile)));
+ return NS_NewFileURI(aURI, linkedFile);
+#else
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral("file");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ nsresult rv;
+
+ RefPtr<nsFileChannel> chan;
+ if (mozilla::net::IsNeckoChild()) {
+ chan = new mozilla::net::FileChannelChild(uri);
+ } else {
+ chan = new nsFileChannel(uri);
+ }
+
+ // set the loadInfo on the new channel ; must do this
+ // before calling Init() on it, since it needs the load
+ // info be already set.
+ rv = chan->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = chan->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *result = chan.forget().downcast<nsBaseChannel>().take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::AllowPort(int32_t port, const char* scheme,
+ bool* result) {
+ // don't override anything.
+ *result = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIFileProtocolHandler methods:
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewFileURI(nsIFile* aFile, nsIURI** aResult) {
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ RefPtr<nsIFile> file(aFile);
+ // NOTE: the origin charset is assigned the value of the platform
+ // charset by the SetFile method.
+ return NS_MutateURI(new mozilla::net::nsStandardURL::Mutator())
+ .Apply(&nsIFileURLMutator::SetFile, file)
+ .Finalize(aResult);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewFileURIMutator(nsIFile* aFile,
+ nsIURIMutator** aResult) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsresult rv;
+
+ nsCOMPtr<nsIURIMutator> mutator = new mozilla::net::nsStandardURL::Mutator();
+ nsCOMPtr<nsIFileURLMutator> fileMutator = do_QueryInterface(mutator, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // NOTE: the origin charset is assigned the value of the platform
+ // charset by the SetFile method.
+ rv = fileMutator->SetFile(aFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mutator.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetURLSpecFromFile(nsIFile* file, nsACString& result) {
+ NS_ENSURE_ARG_POINTER(file);
+ return net_GetURLSpecFromFile(file, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetURLSpecFromActualFile(nsIFile* file,
+ nsACString& result) {
+ NS_ENSURE_ARG_POINTER(file);
+ return net_GetURLSpecFromActualFile(file, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetURLSpecFromDir(nsIFile* file, nsACString& result) {
+ NS_ENSURE_ARG_POINTER(file);
+ return net_GetURLSpecFromDir(file, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetFileFromURLSpec(const nsACString& spec,
+ nsIFile** result) {
+ return net_GetFileFromURLSpec(spec, result);
+}
diff --git a/netwerk/protocol/file/nsFileProtocolHandler.h b/netwerk/protocol/file/nsFileProtocolHandler.h
new file mode 100644
index 0000000000..ff2b30634a
--- /dev/null
+++ b/netwerk/protocol/file/nsFileProtocolHandler.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFileProtocolHandler_h__
+#define nsFileProtocolHandler_h__
+
+#include "nsIFileProtocolHandler.h"
+#include "nsWeakReference.h"
+
+class nsIURIMutator;
+
+class nsFileProtocolHandler : public nsIFileProtocolHandler,
+ public nsSupportsWeakReference {
+ virtual ~nsFileProtocolHandler() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIFILEPROTOCOLHANDLER
+
+ nsFileProtocolHandler() = default;
+
+ [[nodiscard]] nsresult Init();
+};
+
+#endif // !nsFileProtocolHandler_h__
diff --git a/netwerk/protocol/file/nsIFileChannel.idl b/netwerk/protocol/file/nsIFileChannel.idl
new file mode 100644
index 0000000000..e5fcdecb4c
--- /dev/null
+++ b/netwerk/protocol/file/nsIFileChannel.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+/**
+ * nsIFileChannel
+ */
+[scriptable, uuid(06169120-136d-45a5-b535-498f1f755ab7)]
+interface nsIFileChannel : nsISupports
+{
+ readonly attribute nsIFile file;
+};
diff --git a/netwerk/protocol/file/nsIFileProtocolHandler.idl b/netwerk/protocol/file/nsIFileProtocolHandler.idl
new file mode 100644
index 0000000000..d470dcee2a
--- /dev/null
+++ b/netwerk/protocol/file/nsIFileProtocolHandler.idl
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+interface nsIFile;
+interface nsIURIMutator;
+
+[scriptable, uuid(1fb25bd5-4354-4dcd-8d97-621b7b3ed2e4)]
+interface nsIFileProtocolHandler : nsIProtocolHandler
+{
+ /**
+ * This method constructs a new file URI
+ *
+ * @param aFile nsIFile
+ * @return reference to a new nsIURI object
+ */
+ nsIURI newFileURI(in nsIFile aFile);
+
+ /**
+ * This method constructs a new file URI, and returns a URI mutator
+ * that has not yet been finalized, allowing the URI to be changed without
+ * being cloned.
+ *
+ * @param aFile nsIFile
+ * @return reference to a new nsIURIMutator object
+ */
+ nsIURIMutator newFileURIMutator(in nsIFile file);
+
+ /**
+ * DEPRECATED, AVOID IF AT ALL POSSIBLE.
+ *
+ * Calling this will cause IO on the calling thread, to determine
+ * if the file is a directory or file, and based on that behaves as
+ * if you called getURLSpecFromDir or getURLSpecFromActualFile,
+ * respectively. This IO may take multiple seconds (e.g. for network
+ * paths, slow external drives that need to be woken up, etc.).
+ *
+ * Usually, the caller should *know* that the `file` argument is
+ * either a directory (in which case it should call getURLSpecFromDir)
+ * or a non-directory file (in which case it should call
+ * getURLSpecFromActualFile), and not need to call this method.
+ */
+ [noscript] AUTF8String getURLSpecFromFile(in nsIFile file);
+
+ /**
+ * Converts a non-directory nsIFile to the corresponding URL string.
+ * NOTE: under some platforms this is a lossy conversion (e.g., Mac
+ * Carbon build). If the nsIFile is a local file, then the result
+ * will be a file:// URL string.
+ *
+ * The resulting string may contain URL-escaped characters.
+ *
+ * Should only be called on files which are not directories. If
+ * called on directories, the resulting URL may lack a trailing slash
+ * and cause relative URLs in such a document to misbehave.
+ */
+ AUTF8String getURLSpecFromActualFile(in nsIFile file);
+
+ /**
+ * Converts a directory nsIFile to the corresponding URL string.
+ * NOTE: under some platforms this is a lossy conversion (e.g., Mac
+ * Carbon build). If the nsIFile is a local file, then the result
+ * will be a file:// URL string.
+ *
+ * The resulting string may contain URL-escaped characters.
+ *
+ * Should only be called on files which are directories (will enforce
+ * the URL ends with a slash).
+ */
+ AUTF8String getURLSpecFromDir(in nsIFile file);
+
+ /**
+ * Converts the URL string into the corresponding nsIFile if possible.
+ * A local file will be created if the URL string begins with file://.
+ */
+ nsIFile getFileFromURLSpec(in AUTF8String url);
+
+ /**
+ * Takes a local file and tries to interpret it as an internet shortcut
+ * (e.g. .url files on windows).
+ * @param file The local file to read
+ * @return The URI the file refers to
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if the OS does not support such files.
+ * @throw NS_ERROR_NOT_AVAILABLE if this file is not an internet shortcut.
+ */
+ nsIURI readURLFile(in nsIFile file);
+
+ /**
+ * Takes a local file and tries to interpret it as a shell link file
+ * (.lnk files on Windows)
+ * @param file The local file to read
+ * @return The URI the file refers to
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if the OS does not support such files.
+ * @throw NS_ERROR_NOT_AVAILABLE if this file is not a shell link.
+ */
+ nsIURI readShellLink(in nsIFile file);
+};
diff --git a/netwerk/protocol/gio/GIOChannelChild.cpp b/netwerk/protocol/gio/GIOChannelChild.cpp
new file mode 100644
index 0000000000..2adb611666
--- /dev/null
+++ b/netwerk/protocol/gio/GIOChannelChild.cpp
@@ -0,0 +1,458 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/NeckoChild.h"
+#include "GIOChannelChild.h"
+#include "nsGIOProtocolHandler.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "nsContentUtils.h"
+#include "nsIBrowserChild.h"
+#include "nsStringStream.h"
+#include "nsNetUtil.h"
+#include "base/compiler_specific.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "nsIURIMutator.h"
+#include "nsContentSecurityManager.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/Logging.h"
+
+using mozilla::dom::ContentChild;
+
+namespace mozilla {
+#undef LOG
+#define LOG(args) MOZ_LOG(gGIOLog, mozilla::LogLevel::Debug, args)
+namespace net {
+
+GIOChannelChild::GIOChannelChild(nsIURI* aUri)
+ : mEventQ(new ChannelEventQueue(static_cast<nsIChildChannel*>(this))) {
+ SetURI(aUri);
+ // We could support thread retargeting, but as long as we're being driven by
+ // IPDL on the main thread it doesn't buy us anything.
+ DisallowThreadRetargeting();
+}
+
+void GIOChannelChild::AddIPDLReference() {
+ MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void GIOChannelChild::ReleaseIPDLReference() {
+ MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference");
+ mIPCOpen = false;
+ Release();
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelChild::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS_INHERITED(GIOChannelChild, nsBaseChannel, nsIChildChannel)
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelChild::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("GIOChannelChild::AsyncOpen [this=%p]\n", this));
+
+ NS_ENSURE_TRUE((gNeckoChild), NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(
+ !static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown(),
+ NS_ERROR_FAILURE);
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ // Port checked in parent, but duplicate here so we can return with error
+ // immediately, as we've done since before e10s.
+ rv = NS_CheckPortSafety(nsBaseChannel::URI()); // Need to disambiguate,
+ // because in the child ipdl,
+ // a typedef URI is defined...
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mozilla::dom::BrowserChild* browserChild = nullptr;
+ nsCOMPtr<nsIBrowserChild> iBrowserChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIBrowserChild),
+ getter_AddRefs(iBrowserChild));
+ GetCallback(iBrowserChild);
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get());
+ }
+
+ mListener = listener;
+
+ // add ourselves to the load group.
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ Maybe<mozilla::ipc::IPCStream> ipcStream;
+ mozilla::ipc::SerializeIPCStream(do_AddRef(mUploadStream), ipcStream,
+ /* aAllowLazy */ false);
+
+ uint32_t loadFlags = 0;
+ GetLoadFlags(&loadFlags);
+
+ GIOChannelOpenArgs openArgs;
+ SerializeURI(nsBaseChannel::URI(), openArgs.uri());
+ openArgs.startPos() = mStartPos;
+ openArgs.entityID() = mEntityID;
+ openArgs.uploadStream() = ipcStream;
+ openArgs.loadFlags() = loadFlags;
+
+ nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
+ rv = mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &openArgs.loadInfo());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This must happen before the constructor message is sent.
+ SetupNeckoTarget();
+
+ // The socket transport layer in the chrome process now has a logical ref to
+ // us until OnStopRequest is called.
+ AddIPDLReference();
+
+ if (!gNeckoChild->SendPGIOChannelConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), openArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsPending = true;
+ mWasOpened = true;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+GIOChannelChild::IsPending(bool* aResult) {
+ *aResult = mIsPending;
+ return NS_OK;
+}
+
+nsresult GIOChannelChild::OpenContentStream(bool aAsync,
+ nsIInputStream** aStream,
+ nsIChannel** aChannel) {
+ MOZ_CRASH("GIOChannel*Child* should never have OpenContentStream called!");
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult GIOChannelChild::RecvOnStartRequest(
+ const nsresult& aChannelStatus, const int64_t& aContentLength,
+ const nsACString& aContentType, const nsACString& aEntityID,
+ const URIParams& aURI) {
+ LOG(("GIOChannelChild::RecvOnStartRequest [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<GIOChannelChild>(this), aChannelStatus,
+ aContentLength, aContentType = nsCString(aContentType),
+ aEntityID = nsCString(aEntityID), aURI]() {
+ self->DoOnStartRequest(aChannelStatus, aContentLength, aContentType,
+ aEntityID, aURI);
+ }));
+ return IPC_OK();
+}
+
+void GIOChannelChild::DoOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsACString& aContentType,
+ const nsACString& aEntityID,
+ const URIParams& aURI) {
+ LOG(("GIOChannelChild::DoOnStartRequest [this=%p]\n", this));
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aChannelStatus;
+ }
+
+ mContentLength = aContentLength;
+ SetContentType(aContentType);
+ mEntityID = aEntityID;
+
+ nsCString spec;
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+ nsresult rv = uri->GetSpec(spec);
+ if (NS_SUCCEEDED(rv)) {
+ // Changes nsBaseChannel::URI()
+ rv = NS_MutateURI(mURI).SetSpec(spec).Finalize(mURI);
+ }
+
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ rv = mListener->OnStartRequest(this);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+}
+
+mozilla::ipc::IPCResult GIOChannelChild::RecvOnDataAvailable(
+ const nsresult& aChannelStatus, const nsACString& aData,
+ const uint64_t& aOffset, const uint32_t& aCount) {
+ LOG(("GIOChannelChild::RecvOnDataAvailable [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<GIOChannelChild>(this), aChannelStatus,
+ aData = nsCString(aData), aOffset, aCount]() {
+ self->DoOnDataAvailable(aChannelStatus, aData, aOffset, aCount);
+ }));
+
+ return IPC_OK();
+}
+
+void GIOChannelChild::DoOnDataAvailable(const nsresult& aChannelStatus,
+ const nsACString& aData,
+ const uint64_t& aOffset,
+ const uint32_t& aCount) {
+ LOG(("GIOChannelChild::DoOnDataAvailable [this=%p]\n", this));
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aChannelStatus;
+ }
+
+ if (mCanceled) {
+ return;
+ }
+
+ // NOTE: the OnDataAvailable contract requires the client to read all the data
+ // in the inputstream. This code relies on that ('data' will go away after
+ // this function). Apparently the previous, non-e10s behavior was to actually
+ // support only reading part of the data, allowing later calls to read the
+ // rest.
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv =
+ NS_NewByteInputStream(getter_AddRefs(stringStream),
+ Span(aData).To(aCount), NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ rv = mListener->OnDataAvailable(this, stringStream, aOffset, aCount);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ stringStream->Close();
+}
+
+mozilla::ipc::IPCResult GIOChannelChild::RecvOnStopRequest(
+ const nsresult& aChannelStatus) {
+ LOG(("GIOChannelChild::RecvOnStopRequest [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aChannelStatus)));
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<GIOChannelChild>(this), aChannelStatus]() {
+ self->DoOnStopRequest(aChannelStatus);
+ }));
+ return IPC_OK();
+}
+
+void GIOChannelChild::DoOnStopRequest(const nsresult& aChannelStatus) {
+ LOG(("GIOChannelChild::DoOnStopRequest [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aChannelStatus)));
+
+ if (!mCanceled) {
+ mStatus = aChannelStatus;
+ }
+
+ { // Ensure that all queued ipdl events are dispatched before
+ // we initiate protocol deletion below.
+ mIsPending = false;
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ (void)mListener->OnStopRequest(this, aChannelStatus);
+
+ mListener = nullptr;
+
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aChannelStatus);
+ }
+ }
+
+ // This calls NeckoChild::DeallocPGIOChannelChild(), which deletes |this| if
+ // IPDL holds the last reference. Don't rely on |this| existing after here!
+ Send__delete__(this);
+}
+
+mozilla::ipc::IPCResult GIOChannelChild::RecvFailedAsyncOpen(
+ const nsresult& aStatusCode) {
+ LOG(("GIOChannelChild::RecvFailedAsyncOpen [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aStatusCode)));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<GIOChannelChild>(this), aStatusCode]() {
+ self->DoFailedAsyncOpen(aStatusCode);
+ }));
+ return IPC_OK();
+}
+
+void GIOChannelChild::DoFailedAsyncOpen(const nsresult& aStatusCode) {
+ LOG(("GIOChannelChild::DoFailedAsyncOpen [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aStatusCode)));
+ mStatus = aStatusCode;
+
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aStatusCode);
+ }
+
+ if (mListener) {
+ mListener->OnStartRequest(this);
+ mIsPending = false;
+ mListener->OnStopRequest(this, aStatusCode);
+ } else {
+ mIsPending = false;
+ }
+
+ mListener = nullptr;
+
+ if (mIPCOpen) {
+ Send__delete__(this);
+ }
+}
+
+mozilla::ipc::IPCResult GIOChannelChild::RecvDeleteSelf() {
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<GIOChannelChild>(this)]() { self->DoDeleteSelf(); }));
+ return IPC_OK();
+}
+
+void GIOChannelChild::DoDeleteSelf() {
+ if (mIPCOpen) {
+ Send__delete__(this);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelChild::nsIResumableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelChild::Cancel(nsresult aStatus) {
+ LOG(("GIOChannelChild::Cancel [this=%p]\n", this));
+
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = aStatus;
+ if (mIPCOpen) {
+ SendCancel(aStatus);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelChild::Suspend() {
+ NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
+
+ LOG(("GIOChannelChild::Suspend [this=%p]\n", this));
+
+ // SendSuspend only once, when suspend goes from 0 to 1.
+ if (!mSuspendCount++) {
+ SendSuspend();
+ mSuspendSent = true;
+ }
+ mEventQ->Suspend();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelChild::Resume() {
+ NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
+
+ LOG(("GIOChannelChild::Resume [this=%p]\n", this));
+
+ // SendResume only once, when suspend count drops to 0.
+ if (!--mSuspendCount && mSuspendSent) {
+ SendResume();
+ }
+ mEventQ->Resume();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelChild::nsIChildChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelChild::ConnectParent(uint32_t aId) {
+ NS_ENSURE_TRUE((gNeckoChild), NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(
+ !static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown(),
+ NS_ERROR_FAILURE);
+
+ LOG(("GIOChannelChild::ConnectParent [this=%p]\n", this));
+
+ mozilla::dom::BrowserChild* browserChild = nullptr;
+ nsCOMPtr<nsIBrowserChild> iBrowserChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIBrowserChild),
+ getter_AddRefs(iBrowserChild));
+ GetCallback(iBrowserChild);
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get());
+ }
+
+ // This must happen before the constructor message is sent.
+ SetupNeckoTarget();
+
+ // The socket transport in the chrome process now holds a logical ref to us
+ // until OnStopRequest, or we do a redirect, or we hit an IPDL error.
+ AddIPDLReference();
+
+ GIOChannelConnectArgs connectArgs(aId);
+
+ if (!gNeckoChild->SendPGIOChannelConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), connectArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) {
+ LOG(("GIOChannelChild::CompleteRedirectSetup [this=%p]\n", this));
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ mIsPending = true;
+ mWasOpened = true;
+ mListener = aListener;
+
+ // add ourselves to the load group.
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ // We already have an open IPDL connection to the parent. If on-modify-request
+ // listeners or load group observers canceled us, let the parent handle it
+ // and send it back to us naturally.
+ return NS_OK;
+}
+
+void GIOChannelChild::SetupNeckoTarget() {
+ if (mNeckoTarget) {
+ return;
+ }
+ mNeckoTarget = GetMainThreadSerialEventTarget();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/gio/GIOChannelChild.h b/netwerk/protocol/gio/GIOChannelChild.h
new file mode 100644
index 0000000000..158ab6804f
--- /dev/null
+++ b/netwerk/protocol/gio/GIOChannelChild.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_GIOCHANNELCHILD_H
+#define NS_GIOCHANNELCHILD_H
+
+#include "mozilla/net/PGIOChannelChild.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "nsBaseChannel.h"
+#include "nsIUploadChannel.h"
+#include "nsIProxiedChannel.h"
+#include "nsIResumableChannel.h"
+#include "nsIChildChannel.h"
+#include "nsIEventTarget.h"
+#include "nsIStreamListener.h"
+
+class nsIEventTarget;
+
+namespace mozilla {
+namespace net {
+
+class GIOChannelChild final : public PGIOChannelChild,
+ public nsBaseChannel,
+ public nsIChildChannel {
+ public:
+ using nsIStreamListener = ::nsIStreamListener;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICHILDCHANNEL
+
+ NS_IMETHOD Cancel(nsresult aStatus) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+
+ explicit GIOChannelChild(nsIURI* uri);
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+
+ // Note that we handle this ourselves, overriding the nsBaseChannel
+ // default behavior, in order to be e10s-friendly.
+ NS_IMETHOD IsPending(bool* aResult) override;
+
+ nsresult OpenContentStream(bool aAsync, nsIInputStream** aStream,
+ nsIChannel** aChannel) override;
+
+ bool IsSuspended() const;
+
+ protected:
+ virtual ~GIOChannelChild() = default;
+
+ mozilla::ipc::IPCResult RecvOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsACString& aContentType,
+ const nsACString& aEntityID,
+ const URIParams& aURI) override;
+ mozilla::ipc::IPCResult RecvOnDataAvailable(const nsresult& aChannelStatus,
+ const nsACString& aData,
+ const uint64_t& aOffset,
+ const uint32_t& aCount) override;
+ mozilla::ipc::IPCResult RecvOnStopRequest(
+ const nsresult& aChannelStatus) override;
+ mozilla::ipc::IPCResult RecvFailedAsyncOpen(
+ const nsresult& aStatusCode) override;
+ mozilla::ipc::IPCResult RecvDeleteSelf() override;
+
+ void DoOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsACString& aContentType,
+ const nsACString& aEntityID, const URIParams& aURI);
+ void DoOnDataAvailable(const nsresult& aChannelStatus,
+ const nsACString& aData, const uint64_t& aOffset,
+ const uint32_t& aCount);
+ void DoOnStopRequest(const nsresult& aChannelStatus);
+ void DoFailedAsyncOpen(const nsresult& aStatusCode);
+ void DoDeleteSelf();
+
+ void SetupNeckoTarget() override;
+
+ friend class NeckoTargetChannelFunctionEvent;
+
+ private:
+ nsCOMPtr<nsIInputStream> mUploadStream;
+
+ bool mIPCOpen = false;
+ const RefPtr<ChannelEventQueue> mEventQ;
+
+ bool mCanceled = false;
+ uint32_t mSuspendCount = 0;
+ ;
+ bool mIsPending = false;
+
+ uint64_t mStartPos = 0;
+ nsCString mEntityID;
+
+ // Set if SendSuspend is called. Determines if SendResume is needed when
+ // diverting callbacks to parent.
+ bool mSuspendSent = false;
+};
+
+inline bool GIOChannelChild::IsSuspended() const { return mSuspendCount != 0; }
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NS_GIOCHANNELCHILD_H */
diff --git a/netwerk/protocol/gio/GIOChannelParent.cpp b/netwerk/protocol/gio/GIOChannelParent.cpp
new file mode 100644
index 0000000000..a19829a81d
--- /dev/null
+++ b/netwerk/protocol/gio/GIOChannelParent.cpp
@@ -0,0 +1,324 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GIOChannelParent.h"
+#include "nsGIOProtocolHandler.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsISecureBrowserUI.h"
+#include "nsQueryObject.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "mozilla/ipc/URIUtils.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla {
+#undef LOG
+#define LOG(args) MOZ_LOG(gGIOLog, mozilla::LogLevel::Debug, args)
+namespace net {
+
+GIOChannelParent::GIOChannelParent(dom::BrowserParent* aIframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus)
+ : mLoadContext(aLoadContext),
+ mPBOverride(aOverrideStatus),
+ mBrowserParent(aIframeEmbedding) {
+ mEventQ = new ChannelEventQueue(static_cast<nsIParentChannel*>(this));
+}
+
+void GIOChannelParent::ActorDestroy(ActorDestroyReason why) {
+ // We may still have refcount>0 if the channel hasn't called OnStopRequest
+ // yet, but we must not send any more msgs to child.
+ mIPCClosed = true;
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelParent::nsISupports
+//-----------------------------------------------------------------------------
+NS_IMPL_ISUPPORTS(GIOChannelParent, nsIStreamListener, nsIParentChannel,
+ nsIInterfaceRequestor, nsIRequestObserver)
+
+//-----------------------------------------------------------------------------
+// GIOChannelParent methods
+//-----------------------------------------------------------------------------
+
+bool GIOChannelParent::Init(const GIOChannelCreationArgs& aOpenArgs) {
+ switch (aOpenArgs.type()) {
+ case GIOChannelCreationArgs::TGIOChannelOpenArgs: {
+ const GIOChannelOpenArgs& a = aOpenArgs.get_GIOChannelOpenArgs();
+ return DoAsyncOpen(a.uri(), a.startPos(), a.entityID(), a.uploadStream(),
+ a.loadInfo(), a.loadFlags());
+ }
+ case GIOChannelCreationArgs::TGIOChannelConnectArgs: {
+ const GIOChannelConnectArgs& cArgs =
+ aOpenArgs.get_GIOChannelConnectArgs();
+ return ConnectChannel(cArgs.channelId());
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown open type");
+ return false;
+ }
+}
+
+bool GIOChannelParent::DoAsyncOpen(const URIParams& aURI,
+ const uint64_t& aStartPos,
+ const nsCString& aEntityID,
+ const Maybe<IPCStream>& aUploadStream,
+ const LoadInfoArgs& aLoadInfoArgs,
+ const uint32_t& aLoadFlags) {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+ if (!uri) {
+ return false;
+ }
+
+#ifdef DEBUG
+ LOG(("GIOChannelParent DoAsyncOpen [this=%p uri=%s]\n", this,
+ uri->GetSpecOrDefault().get()));
+#endif
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsAutoCString remoteType;
+ rv = GetRemoteType(remoteType);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs, remoteType,
+ getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ OriginAttributes attrs;
+ rv = loadInfo->GetOriginAttributes(&attrs);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIChannel> chan;
+ rv = NS_NewChannelInternal(getter_AddRefs(chan), uri, loadInfo, nullptr,
+ nullptr, nullptr, aLoadFlags, ios);
+
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ mChannel = chan;
+
+ nsIChannel* gioChan = static_cast<nsIChannel*>(mChannel.get());
+
+ rv = gioChan->AsyncOpen(this);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ return true;
+}
+
+bool GIOChannelParent::ConnectChannel(const uint64_t& channelId) {
+ nsresult rv;
+
+ LOG(("Looking for a registered channel [this=%p, id=%" PRIx64 "]", this,
+ channelId));
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_LinkRedirectChannels(channelId, this, getter_AddRefs(channel));
+ if (NS_SUCCEEDED(rv)) {
+ mChannel = channel;
+ }
+
+ LOG((" found channel %p, rv=%08" PRIx32, mChannel.get(),
+ static_cast<uint32_t>(rv)));
+
+ return true;
+}
+
+mozilla::ipc::IPCResult GIOChannelParent::RecvCancel(const nsresult& status) {
+ if (mChannel) {
+ mChannel->Cancel(status);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GIOChannelParent::RecvSuspend() {
+ if (mChannel) {
+ mChannel->Suspend();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GIOChannelParent::RecvResume() {
+ if (mChannel) {
+ mChannel->Resume();
+ }
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelParent::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("GIOChannelParent::OnStartRequest [this=%p]\n", this));
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(aRequest);
+ MOZ_ASSERT(chan);
+ NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
+
+ int64_t contentLength;
+ chan->GetContentLength(&contentLength);
+ nsCString contentType;
+ chan->GetContentType(contentType);
+ nsresult channelStatus = NS_OK;
+ chan->GetStatus(&channelStatus);
+
+ nsCString entityID;
+ URIParams uriparam;
+ nsCOMPtr<nsIURI> uri;
+ chan->GetURI(getter_AddRefs(uri));
+ SerializeURI(uri, uriparam);
+
+ if (mIPCClosed || !SendOnStartRequest(channelStatus, contentLength,
+ contentType, entityID, uriparam)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ LOG(("GIOChannelParent::OnStopRequest: [this=%p status=%" PRIu32 "]\n", this,
+ static_cast<uint32_t>(aStatusCode)));
+
+ if (mIPCClosed || !SendOnStopRequest(aStatusCode)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelParent::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOG(("GIOChannelParent::OnDataAvailable [this=%p]\n", this));
+
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsresult channelStatus = NS_OK;
+ mChannel->GetStatus(&channelStatus);
+
+ if (mIPCClosed ||
+ !SendOnDataAvailable(channelStatus, data, aOffset, aCount)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelParent::nsIParentChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelParent::SetParentListener(ParentChannelListener* aListener) {
+ // Do not need ptr to ParentChannelListener.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelParent::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelParent::Delete() {
+ if (mIPCClosed || !SendDeleteSelf()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOChannelParent::GetRemoteType(nsACString& aRemoteType) {
+ if (!CanSend()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ dom::PContentParent* pcp = Manager()->Manager();
+ aRemoteType = static_cast<dom::ContentParent*>(pcp)->GetRemoteType();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// GIOChannelParent::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+GIOChannelParent::GetInterface(const nsIID& uuid, void** result) {
+ if (uuid.Equals(NS_GET_IID(nsIAuthPromptProvider)) ||
+ uuid.Equals(NS_GET_IID(nsISecureBrowserUI))) {
+ if (mBrowserParent) {
+ return mBrowserParent->QueryInterface(uuid, result);
+ }
+ }
+
+ // Only support nsILoadContext if child channel's callbacks did too
+ if (uuid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(result);
+ return NS_OK;
+ }
+
+ return QueryInterface(uuid, result);
+}
+
+//---------------------
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/gio/GIOChannelParent.h b/netwerk/protocol/gio/GIOChannelParent.h
new file mode 100644
index 0000000000..32d3bd0555
--- /dev/null
+++ b/netwerk/protocol/gio/GIOChannelParent.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_GIOCHANNELPARENT_H
+#define NS_GIOCHANNELPARENT_H
+
+#include "mozilla/net/PGIOChannelParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsIParentChannel.h"
+#include "nsIInterfaceRequestor.h"
+
+class nsILoadContext;
+
+namespace mozilla {
+
+namespace dom {
+class BrowserParent;
+} // namespace dom
+
+namespace net {
+class ChannelEventQueue;
+
+class GIOChannelParent final : public PGIOChannelParent,
+ public nsIParentChannel,
+ public nsIInterfaceRequestor {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ GIOChannelParent(dom::BrowserParent* aIframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus);
+
+ bool Init(const GIOChannelCreationArgs& aOpenArgs);
+
+ protected:
+ virtual ~GIOChannelParent() = default;
+
+ bool DoAsyncOpen(const URIParams& aURI, const uint64_t& aStartPos,
+ const nsCString& aEntityID,
+ const Maybe<mozilla::ipc::IPCStream>& aUploadStream,
+ const LoadInfoArgs& aLoadInfoArgs,
+ const uint32_t& aLoadFlags);
+
+ // used to connect redirected-to channel in parent with just created
+ // ChildChannel. Used during HTTP->FTP redirects.
+ bool ConnectChannel(const uint64_t& channelId);
+
+ virtual mozilla::ipc::IPCResult RecvCancel(const nsresult& status) override;
+ virtual mozilla::ipc::IPCResult RecvSuspend() override;
+ virtual mozilla::ipc::IPCResult RecvResume() override;
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ nsCOMPtr<nsIChannel> mChannel;
+
+ bool mIPCClosed = false;
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+
+ PBOverrideStatus mPBOverride;
+
+ // Set to the canceled status value if the main channel was canceled.
+ nsresult mStatus = NS_OK;
+
+ RefPtr<mozilla::dom::BrowserParent> mBrowserParent;
+
+ RefPtr<ChannelEventQueue> mEventQ;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NS_GIOCHANNELPARENT_H */
diff --git a/netwerk/protocol/gio/PGIOChannel.ipdl b/netwerk/protocol/gio/PGIOChannel.ipdl
new file mode 100644
index 0000000000..e4f1f6380b
--- /dev/null
+++ b/netwerk/protocol/gio/PGIOChannel.ipdl
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include URIParams;
+
+//FIXME: bug #792908 (NeckoChannelParams already included by PNecko)
+include NeckoChannelParams;
+
+using PRTime from "prtime.h";
+
+namespace mozilla {
+namespace net {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+async protocol PGIOChannel
+{
+ manager PNecko;
+
+parent:
+ // Note: channels are opened during construction, so no open method here:
+ // see PNecko.ipdl
+
+ async __delete__();
+
+ async Cancel(nsresult status);
+ async Suspend();
+ async Resume();
+
+child:
+ async OnStartRequest(nsresult aChannelStatus,
+ int64_t aContentLength,
+ nsCString aContentType,
+ nsCString aEntityID,
+ URIParams aURI);
+ async OnDataAvailable(nsresult channelStatus,
+ nsCString data,
+ uint64_t offset,
+ uint32_t count);
+ async OnStopRequest(nsresult channelStatus);
+ async FailedAsyncOpen(nsresult statusCode);
+
+ async DeleteSelf();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/gio/components.conf b/netwerk/protocol/gio/components.conf
new file mode 100644
index 0000000000..a2bbfd8f19
--- /dev/null
+++ b/netwerk/protocol/gio/components.conf
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{ee706783-3af8-4d19-9e84-e2ebfe213480}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=moz-gio'],
+ 'singleton': True,
+ 'type': 'nsGIOProtocolHandler',
+ 'headers': ['nsGIOProtocolHandler.h'],
+ 'constructor': 'nsGIOProtocolHandler::GetSingleton',
+ 'categories': { 'xpcom-startup': 'nsGIOProtocolHandler' },
+ 'protocol_config': {
+ 'scheme': 'moz-gio',
+ 'flags': [
+ 'URI_STD',
+ 'URI_DANGEROUS_TO_LOAD',
+ ],
+ },
+ },
+]
diff --git a/netwerk/protocol/gio/moz.build b/netwerk/protocol/gio/moz.build
new file mode 100644
index 0000000000..4078e5605c
--- /dev/null
+++ b/netwerk/protocol/gio/moz.build
@@ -0,0 +1,42 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+EXPORTS += [
+ "nsGIOProtocolHandler.h",
+]
+
+EXPORTS.mozilla.net += [
+ "GIOChannelChild.h",
+ "GIOChannelParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "GIOChannelChild.cpp",
+ "GIOChannelParent.cpp",
+ "nsGIOProtocolHandler.cpp",
+]
+
+IPDL_SOURCES = [
+ "PGIOChannel.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget: Gtk")
diff --git a/netwerk/protocol/gio/nsGIOProtocolHandler.cpp b/netwerk/protocol/gio/nsGIOProtocolHandler.cpp
new file mode 100644
index 0000000000..469d7982d4
--- /dev/null
+++ b/netwerk/protocol/gio/nsGIOProtocolHandler.cpp
@@ -0,0 +1,1027 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This code is based on original Mozilla gnome-vfs extension. It implements
+ * input stream provided by GVFS/GIO.
+ */
+#include "nsGIOProtocolHandler.h"
+#include "GIOChannelChild.h"
+#include "mozilla/Components.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/NullPrincipal.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIObserver.h"
+#include "nsCRT.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "nsIStringBundle.h"
+#include "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIURI.h"
+#include "nsIAuthPrompt.h"
+#include "nsIChannel.h"
+#include "nsIInputStream.h"
+#include "nsIProtocolHandler.h"
+#include "mozilla/Monitor.h"
+#include "prtime.h"
+#include <gio/gio.h>
+#include <algorithm>
+
+using namespace mozilla;
+
+#define MOZ_GIO_SCHEME "moz-gio"
+#define MOZ_GIO_SUPPORTED_PROTOCOLS "network.gio.supported-protocols"
+
+//-----------------------------------------------------------------------------
+
+// NSPR_LOG_MODULES=gio:5
+LazyLogModule gGIOLog("gio");
+#undef LOG
+#define LOG(args) MOZ_LOG(gGIOLog, mozilla::LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+static nsresult MapGIOResult(gint code) {
+ switch (code) {
+ case G_IO_ERROR_NOT_FOUND:
+ return NS_ERROR_FILE_NOT_FOUND; // shows error
+ case G_IO_ERROR_INVALID_ARGUMENT:
+ return NS_ERROR_INVALID_ARG;
+ case G_IO_ERROR_NOT_SUPPORTED:
+ return NS_ERROR_NOT_AVAILABLE;
+ case G_IO_ERROR_NO_SPACE:
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ case G_IO_ERROR_READ_ONLY:
+ return NS_ERROR_FILE_READ_ONLY;
+ case G_IO_ERROR_PERMISSION_DENIED:
+ return NS_ERROR_FILE_ACCESS_DENIED; // wrong password/login
+ case G_IO_ERROR_CLOSED:
+ return NS_BASE_STREAM_CLOSED; // was EOF
+ case G_IO_ERROR_NOT_DIRECTORY:
+ return NS_ERROR_FILE_NOT_DIRECTORY;
+ case G_IO_ERROR_PENDING:
+ return NS_ERROR_IN_PROGRESS;
+ case G_IO_ERROR_EXISTS:
+ return NS_ERROR_FILE_ALREADY_EXISTS;
+ case G_IO_ERROR_IS_DIRECTORY:
+ return NS_ERROR_FILE_IS_DIRECTORY;
+ case G_IO_ERROR_NOT_MOUNTED:
+ return NS_ERROR_NOT_CONNECTED; // shows error
+ case G_IO_ERROR_HOST_NOT_FOUND:
+ return NS_ERROR_UNKNOWN_HOST; // shows error
+ case G_IO_ERROR_CANCELLED:
+ return NS_ERROR_ABORT;
+ case G_IO_ERROR_NOT_EMPTY:
+ return NS_ERROR_FILE_DIR_NOT_EMPTY;
+ case G_IO_ERROR_FILENAME_TOO_LONG:
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ case G_IO_ERROR_INVALID_FILENAME:
+ return NS_ERROR_FILE_INVALID_PATH;
+ case G_IO_ERROR_TIMED_OUT:
+ return NS_ERROR_NET_TIMEOUT; // shows error
+ case G_IO_ERROR_WOULD_BLOCK:
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ case G_IO_ERROR_FAILED_HANDLED:
+ return NS_ERROR_ABORT; // Cancel on login dialog
+
+ /* unhandled:
+ G_IO_ERROR_NOT_REGULAR_FILE,
+ G_IO_ERROR_NOT_SYMBOLIC_LINK,
+ G_IO_ERROR_NOT_MOUNTABLE_FILE,
+ G_IO_ERROR_TOO_MANY_LINKS,
+ G_IO_ERROR_ALREADY_MOUNTED,
+ G_IO_ERROR_CANT_CREATE_BACKUP,
+ G_IO_ERROR_WRONG_ETAG,
+ G_IO_ERROR_WOULD_RECURSE,
+ G_IO_ERROR_BUSY,
+ G_IO_ERROR_WOULD_MERGE,
+ G_IO_ERROR_TOO_MANY_OPEN_FILES
+ */
+ // Make GCC happy
+ default:
+ return NS_ERROR_FAILURE;
+ }
+}
+
+static nsresult MapGIOResult(GError* result) {
+ if (!result) {
+ return NS_OK;
+ }
+ return MapGIOResult(result->code);
+}
+
+/** Return values for mount operation.
+ * These enums are used as mount operation return values.
+ */
+enum class MountOperationResult {
+ MOUNT_OPERATION_IN_PROGRESS, /** \enum operation in progress */
+ MOUNT_OPERATION_SUCCESS, /** \enum operation successful */
+ MOUNT_OPERATION_FAILED /** \enum operation not successful */
+};
+
+//-----------------------------------------------------------------------------
+/**
+ * Sort function compares according to file type (directory/file)
+ * and alphabethical order
+ * @param a pointer to GFileInfo object to compare
+ * @param b pointer to GFileInfo object to compare
+ * @return -1 when first object should be before the second, 0 when equal,
+ * +1 when second object should be before the first
+ */
+static gint FileInfoComparator(gconstpointer a, gconstpointer b) {
+ GFileInfo* ia = (GFileInfo*)a;
+ GFileInfo* ib = (GFileInfo*)b;
+ if (g_file_info_get_file_type(ia) == G_FILE_TYPE_DIRECTORY &&
+ g_file_info_get_file_type(ib) != G_FILE_TYPE_DIRECTORY) {
+ return -1;
+ }
+ if (g_file_info_get_file_type(ib) == G_FILE_TYPE_DIRECTORY &&
+ g_file_info_get_file_type(ia) != G_FILE_TYPE_DIRECTORY) {
+ return 1;
+ }
+
+ return nsCRT::strcasecmp(g_file_info_get_name(ia), g_file_info_get_name(ib));
+}
+
+/* Declaration of mount callback functions */
+static void mount_enclosing_volume_finished(GObject* source_object,
+ GAsyncResult* res,
+ gpointer user_data);
+static void mount_operation_ask_password(
+ GMountOperation* mount_op, const char* message, const char* default_user,
+ const char* default_domain, GAskPasswordFlags flags, gpointer user_data);
+//-----------------------------------------------------------------------------
+class nsGIOInputStream final : public nsIInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+ explicit nsGIOInputStream(const nsCString& uriSpec) : mSpec(uriSpec) {}
+
+ void SetChannel(nsIChannel* channel) {
+ // We need to hold an owning reference to our channel. This is done
+ // so we can access the channel's notification callbacks to acquire
+ // a reference to a nsIAuthPrompt if we need to handle an interactive
+ // mount operation.
+ //
+ // However, the channel can only be accessed on the main thread, so
+ // we have to be very careful with ownership. Moreover, it doesn't
+ // support threadsafe addref/release, so proxying is the answer.
+ //
+ // Also, it's important to note that this likely creates a reference
+ // cycle since the channel likely owns this stream. This reference
+ // cycle is broken in our Close method.
+
+ mChannel = do_AddRef(channel).take();
+ }
+ void SetMountResult(MountOperationResult result, gint error_code);
+
+ private:
+ ~nsGIOInputStream() { Close(); }
+ nsresult DoOpen();
+ nsresult DoRead(char* aBuf, uint32_t aCount, uint32_t* aCountRead);
+ nsresult SetContentTypeOfChannel(const char* contentType);
+ nsresult MountVolume();
+ nsresult DoOpenDirectory();
+ nsresult DoOpenFile(GFileInfo* info);
+ nsCString mSpec;
+ nsIChannel* mChannel{nullptr}; // manually refcounted
+ GFile* mHandle{nullptr};
+ GFileInputStream* mStream{nullptr};
+ uint64_t mBytesRemaining{UINT64_MAX};
+ nsresult mStatus{NS_OK};
+ GList* mDirList{nullptr};
+ GList* mDirListPtr{nullptr};
+ nsCString mDirBuf;
+ uint32_t mDirBufCursor{0};
+ bool mDirOpen{false};
+ MountOperationResult mMountRes =
+ MountOperationResult::MOUNT_OPERATION_SUCCESS;
+ mozilla::Monitor mMonitorMountInProgress MOZ_UNANNOTATED{
+ "GIOInputStream::MountFinished"};
+ gint mMountErrorCode{};
+};
+
+/**
+ * Set result of mount operation and notify monitor waiting for results.
+ * This method is called in main thread as long as it is used only
+ * in mount_enclosing_volume_finished function.
+ * @param result Result of mount operation
+ */
+void nsGIOInputStream::SetMountResult(MountOperationResult result,
+ gint error_code) {
+ mozilla::MonitorAutoLock mon(mMonitorMountInProgress);
+ mMountRes = result;
+ mMountErrorCode = error_code;
+ mon.Notify();
+}
+
+/**
+ * Start mount operation and wait in loop until it is finished. This method is
+ * called from thread which is trying to read from location.
+ */
+nsresult nsGIOInputStream::MountVolume() {
+ GMountOperation* mount_op = g_mount_operation_new();
+ g_signal_connect(mount_op, "ask-password",
+ G_CALLBACK(mount_operation_ask_password), mChannel);
+ mMountRes = MountOperationResult::MOUNT_OPERATION_IN_PROGRESS;
+ /* g_file_mount_enclosing_volume uses a dbus request to mount the volume.
+ Callback mount_enclosing_volume_finished is called in main thread
+ (not this thread on which this method is called). */
+ g_file_mount_enclosing_volume(mHandle, G_MOUNT_MOUNT_NONE, mount_op, nullptr,
+ mount_enclosing_volume_finished, this);
+ mozilla::MonitorAutoLock mon(mMonitorMountInProgress);
+ /* Waiting for finish of mount operation thread */
+ while (mMountRes == MountOperationResult::MOUNT_OPERATION_IN_PROGRESS) {
+ mon.Wait();
+ }
+
+ g_object_unref(mount_op);
+
+ if (mMountRes == MountOperationResult::MOUNT_OPERATION_FAILED) {
+ return MapGIOResult(mMountErrorCode);
+ }
+ return NS_OK;
+}
+
+/**
+ * Create list of infos about objects in opened directory
+ * Return: NS_OK when list obtained, otherwise error code according
+ * to failed operation.
+ */
+nsresult nsGIOInputStream::DoOpenDirectory() {
+ GError* error = nullptr;
+
+ GFileEnumerator* f_enum = g_file_enumerate_children(
+ mHandle, "standard::*,time::*", G_FILE_QUERY_INFO_NONE, nullptr, &error);
+ if (!f_enum) {
+ nsresult rv = MapGIOResult(error);
+ g_warning("Cannot read from directory: %s", error->message);
+ g_error_free(error);
+ return rv;
+ }
+ // fill list of file infos
+ GFileInfo* info = g_file_enumerator_next_file(f_enum, nullptr, &error);
+ while (info) {
+ mDirList = g_list_append(mDirList, info);
+ info = g_file_enumerator_next_file(f_enum, nullptr, &error);
+ }
+ g_object_unref(f_enum);
+ if (error) {
+ g_warning("Error reading directory content: %s", error->message);
+ nsresult rv = MapGIOResult(error);
+ g_error_free(error);
+ return rv;
+ }
+ mDirOpen = true;
+
+ // Sort list of file infos by using FileInfoComparator function
+ mDirList = g_list_sort(mDirList, FileInfoComparator);
+ mDirListPtr = mDirList;
+
+ // Write column names
+ mDirBuf.AppendLiteral(
+ "200: filename content-length last-modified file-type\n");
+
+ SetContentTypeOfChannel(APPLICATION_HTTP_INDEX_FORMAT);
+ return NS_OK;
+}
+
+/**
+ * Create file stream and set mime type for channel
+ * @param info file info used to determine mime type
+ * @return NS_OK when file stream created successfuly, error code otherwise
+ */
+nsresult nsGIOInputStream::DoOpenFile(GFileInfo* info) {
+ GError* error = nullptr;
+
+ mStream = g_file_read(mHandle, nullptr, &error);
+ if (!mStream) {
+ nsresult rv = MapGIOResult(error);
+ g_warning("Cannot read from file: %s", error->message);
+ g_error_free(error);
+ return rv;
+ }
+
+ const char* content_type = g_file_info_get_content_type(info);
+ if (content_type) {
+ char* mime_type = g_content_type_get_mime_type(content_type);
+ if (mime_type) {
+ if (strcmp(mime_type, APPLICATION_OCTET_STREAM) != 0) {
+ SetContentTypeOfChannel(mime_type);
+ }
+ g_free(mime_type);
+ }
+ } else {
+ g_warning("Missing content type.");
+ }
+
+ mBytesRemaining = g_file_info_get_size(info);
+ // Update the content length attribute on the channel. We do this
+ // synchronously without proxying. This hack is not as bad as it looks!
+ mChannel->SetContentLength(mBytesRemaining);
+
+ return NS_OK;
+}
+
+/**
+ * Start file open operation, mount volume when needed and according to file
+ * type create file output stream or read directory content.
+ * @return NS_OK when file or directory opened successfully, error code
+ * otherwise
+ */
+nsresult nsGIOInputStream::DoOpen() {
+ nsresult rv;
+ GError* error = nullptr;
+
+ NS_ASSERTION(mHandle == nullptr, "already open");
+
+ mHandle = g_file_new_for_uri(mSpec.get());
+
+ GFileInfo* info = g_file_query_info(mHandle, "standard::*",
+ G_FILE_QUERY_INFO_NONE, nullptr, &error);
+
+ if (error) {
+ if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_MOUNTED) {
+ // location is not yet mounted, try to mount
+ g_error_free(error);
+ if (NS_IsMainThread()) {
+ return NS_ERROR_NOT_CONNECTED;
+ }
+ error = nullptr;
+ rv = MountVolume();
+ if (rv != NS_OK) {
+ return rv;
+ }
+ // get info again
+ info = g_file_query_info(mHandle, "standard::*", G_FILE_QUERY_INFO_NONE,
+ nullptr, &error);
+ // second try to get file info from remote files after media mount
+ if (!info) {
+ g_warning("Unable to get file info: %s", error->message);
+ rv = MapGIOResult(error);
+ g_error_free(error);
+ return rv;
+ }
+ } else {
+ g_warning("Unable to get file info: %s", error->message);
+ rv = MapGIOResult(error);
+ g_error_free(error);
+ return rv;
+ }
+ }
+ // Get file type to handle directories and file differently
+ GFileType f_type = g_file_info_get_file_type(info);
+ if (f_type == G_FILE_TYPE_DIRECTORY) {
+ // directory
+ rv = DoOpenDirectory();
+ } else if (f_type != G_FILE_TYPE_UNKNOWN) {
+ // file
+ rv = DoOpenFile(info);
+ } else {
+ g_warning("Unable to get file type.");
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ }
+ if (info) {
+ g_object_unref(info);
+ }
+ return rv;
+}
+
+/**
+ * Read content of file or create file list from directory
+ * @param aBuf read destination buffer
+ * @param aCount length of destination buffer
+ * @param aCountRead number of read characters
+ * @return NS_OK when read successfully, NS_BASE_STREAM_CLOSED when end of file,
+ * error code otherwise
+ */
+nsresult nsGIOInputStream::DoRead(char* aBuf, uint32_t aCount,
+ uint32_t* aCountRead) {
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ if (mStream) {
+ // file read
+ GError* error = nullptr;
+ uint32_t bytes_read = g_input_stream_read(G_INPUT_STREAM(mStream), aBuf,
+ aCount, nullptr, &error);
+ if (error) {
+ rv = MapGIOResult(error);
+ *aCountRead = 0;
+ g_warning("Cannot read from file: %s", error->message);
+ g_error_free(error);
+ return rv;
+ }
+ *aCountRead = bytes_read;
+ mBytesRemaining -= *aCountRead;
+ return NS_OK;
+ }
+ if (mDirOpen) {
+ // directory read
+ while (aCount && rv != NS_BASE_STREAM_CLOSED) {
+ // Copy data out of our buffer
+ uint32_t bufLen = mDirBuf.Length() - mDirBufCursor;
+ if (bufLen) {
+ uint32_t n = std::min(bufLen, aCount);
+ memcpy(aBuf, mDirBuf.get() + mDirBufCursor, n);
+ *aCountRead += n;
+ aBuf += n;
+ aCount -= n;
+ mDirBufCursor += n;
+ }
+
+ if (!mDirListPtr) // Are we at the end of the directory list?
+ {
+ rv = NS_BASE_STREAM_CLOSED;
+ } else if (aCount) // Do we need more data?
+ {
+ GFileInfo* info = (GFileInfo*)mDirListPtr->data;
+
+ // Prune '.' and '..' from directory listing.
+ const char* fname = g_file_info_get_name(info);
+ if (fname && fname[0] == '.' &&
+ (fname[1] == '\0' || (fname[1] == '.' && fname[2] == '\0'))) {
+ mDirListPtr = mDirListPtr->next;
+ continue;
+ }
+
+ mDirBuf.AssignLiteral("201: ");
+
+ // The "filename" field
+ nsCString escName;
+ nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID);
+ if (nu && fname) {
+ nu->EscapeString(nsDependentCString(fname),
+ nsINetUtil::ESCAPE_URL_PATH, escName);
+
+ mDirBuf.Append(escName);
+ mDirBuf.Append(' ');
+ }
+
+ // The "content-length" field
+ // XXX truncates size from 64-bit to 32-bit
+ mDirBuf.AppendInt(int32_t(g_file_info_get_size(info)));
+ mDirBuf.Append(' ');
+
+ // The "last-modified" field
+ //
+ // NSPR promises: PRTime is compatible with time_t
+ // we just need to convert from seconds to microseconds
+ GTimeVal gtime;
+ g_file_info_get_modification_time(info, &gtime);
+
+ PRExplodedTime tm;
+ PRTime pt = ((PRTime)gtime.tv_sec) * 1000000;
+ PR_ExplodeTime(pt, PR_GMTParameters, &tm);
+ {
+ char buf[64];
+ PR_FormatTimeUSEnglish(buf, sizeof(buf),
+ "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ",
+ &tm);
+ mDirBuf.Append(buf);
+ }
+
+ // The "file-type" field
+ switch (g_file_info_get_file_type(info)) {
+ case G_FILE_TYPE_REGULAR:
+ mDirBuf.AppendLiteral("FILE ");
+ break;
+ case G_FILE_TYPE_DIRECTORY:
+ mDirBuf.AppendLiteral("DIRECTORY ");
+ break;
+ case G_FILE_TYPE_SYMBOLIC_LINK:
+ mDirBuf.AppendLiteral("SYMBOLIC-LINK ");
+ break;
+ default:
+ break;
+ }
+ mDirBuf.Append('\n');
+
+ mDirBufCursor = 0;
+ mDirListPtr = mDirListPtr->next;
+ }
+ }
+ }
+ return rv;
+}
+
+/**
+ * This class is used to implement SetContentTypeOfChannel.
+ */
+class nsGIOSetContentTypeEvent : public mozilla::Runnable {
+ public:
+ nsGIOSetContentTypeEvent(nsIChannel* channel, const char* contentType)
+ : mozilla::Runnable("nsGIOSetContentTypeEvent"),
+ mChannel(channel),
+ mContentType(contentType) {
+ // stash channel reference in mChannel. no AddRef here! see note
+ // in SetContentTypeOfchannel.
+ }
+
+ NS_IMETHOD Run() override {
+ mChannel->SetContentType(mContentType);
+ return NS_OK;
+ }
+
+ private:
+ nsIChannel* mChannel;
+ nsCString mContentType;
+};
+
+nsresult nsGIOInputStream::SetContentTypeOfChannel(const char* contentType) {
+ // We need to proxy this call over to the main thread. We post an
+ // asynchronous event in this case so that we don't delay reading data, and
+ // we know that this is safe to do since the channel's reference will be
+ // released asynchronously as well. We trust the ordering of the main
+ // thread's event queue to protect us against memory corruption.
+
+ nsresult rv;
+ nsCOMPtr<nsIRunnable> ev =
+ new nsGIOSetContentTypeEvent(mChannel, contentType);
+ if (!ev) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ } else {
+ rv = NS_DispatchToMainThread(ev);
+ }
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsGIOInputStream, nsIInputStream)
+
+/**
+ * Free all used memory and close stream.
+ */
+NS_IMETHODIMP
+nsGIOInputStream::Close() {
+ if (mStream) {
+ g_object_unref(mStream);
+ mStream = nullptr;
+ }
+
+ if (mHandle) {
+ g_object_unref(mHandle);
+ mHandle = nullptr;
+ }
+
+ if (mDirList) {
+ // Destroy the list of GIOFileInfo objects...
+ g_list_foreach(mDirList, (GFunc)g_object_unref, nullptr);
+ g_list_free(mDirList);
+ mDirList = nullptr;
+ mDirListPtr = nullptr;
+ }
+
+ if (mChannel) {
+ NS_ReleaseOnMainThread("nsGIOInputStream::mChannel", dont_AddRef(mChannel));
+
+ mChannel = nullptr;
+ }
+
+ mSpec.Truncate(); // free memory
+
+ // Prevent future reads from re-opening the handle.
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = NS_BASE_STREAM_CLOSED;
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Return number of remaining bytes available on input
+ * @param aResult remaining bytes
+ */
+NS_IMETHODIMP
+nsGIOInputStream::Available(uint64_t* aResult) {
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ *aResult = mBytesRemaining;
+
+ return NS_OK;
+}
+
+/**
+ * Return the status of the input stream
+ */
+NS_IMETHODIMP
+nsGIOInputStream::StreamStatus() { return mStatus; }
+
+/**
+ * Trying to read from stream. When location is not available it tries to mount
+ * it.
+ * @param aBuf buffer to put read data
+ * @param aCount length of aBuf
+ * @param aCountRead number of bytes actually read
+ */
+NS_IMETHODIMP
+nsGIOInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aCountRead) {
+ *aCountRead = 0;
+ // Check if file is already opened, otherwise open it
+ if (!mStream && !mDirOpen && mStatus == NS_OK) {
+ mStatus = DoOpen();
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+ }
+
+ mStatus = DoRead(aBuf, aCount, aCountRead);
+ // Check if all data has been read
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ return NS_OK;
+ }
+
+ // Check whenever any error appears while reading
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsGIOInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult) {
+ // There is no way to implement this using GnomeVFS, but fortunately
+ // that doesn't matter. Because we are a blocking input stream, Necko
+ // isn't going to call our ReadSegments method.
+ MOZ_ASSERT_UNREACHABLE("nsGIOInputStream::ReadSegments");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsGIOInputStream::IsNonBlocking(bool* aResult) {
+ *aResult = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+/**
+ * Called when finishing mount operation. Result of operation is set in
+ * nsGIOInputStream. This function is called in main thread as an async request
+ * typically from dbus.
+ * @param source_object GFile object which requested the mount
+ * @param res result object
+ * @param user_data pointer to nsGIOInputStream
+ */
+static void mount_enclosing_volume_finished(GObject* source_object,
+ GAsyncResult* res,
+ gpointer user_data) {
+ GError* error = nullptr;
+
+ nsGIOInputStream* istream = static_cast<nsGIOInputStream*>(user_data);
+
+ g_file_mount_enclosing_volume_finish(G_FILE(source_object), res, &error);
+
+ if (error) {
+ g_warning("Mount failed: %s %d", error->message, error->code);
+ istream->SetMountResult(MountOperationResult::MOUNT_OPERATION_FAILED,
+ error->code);
+ g_error_free(error);
+ } else {
+ istream->SetMountResult(MountOperationResult::MOUNT_OPERATION_SUCCESS, 0);
+ }
+}
+
+/**
+ * This function is called when username or password are requested from user.
+ * This function is called in main thread as async request from dbus.
+ * @param mount_op mount operation
+ * @param message message to show to user
+ * @param default_user preffered user
+ * @param default_domain domain name
+ * @param flags what type of information is required
+ * @param user_data nsIChannel
+ */
+static void mount_operation_ask_password(
+ GMountOperation* mount_op, const char* message, const char* default_user,
+ const char* default_domain, GAskPasswordFlags flags, gpointer user_data) {
+ nsIChannel* channel = (nsIChannel*)user_data;
+ if (!channel) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ // We can't handle request for domain
+ if (flags & G_ASK_PASSWORD_NEED_DOMAIN) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+
+ nsCOMPtr<nsIAuthPrompt> prompt;
+ NS_QueryNotificationCallbacks(channel, prompt);
+
+ // If no auth prompt, then give up. We could failover to using the
+ // WindowWatcher service, but that might defeat a consumer's purposeful
+ // attempt to disable authentication (for whatever reason).
+ if (!prompt) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ // Parse out the host and port...
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (!uri) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+
+ nsAutoCString scheme, hostPort;
+ uri->GetScheme(scheme);
+ uri->GetHostPort(hostPort);
+
+ // It doesn't make sense for either of these strings to be empty. What kind
+ // of funky URI is this?
+ if (scheme.IsEmpty() || hostPort.IsEmpty()) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ // Construct the single signon key. Altering the value of this key will
+ // cause people's remembered passwords to be forgotten. Think carefully
+ // before changing the way this key is constructed.
+ nsAutoString key, realm;
+
+ NS_ConvertUTF8toUTF16 dispHost(scheme);
+ dispHost.AppendLiteral("://");
+ dispHost.Append(NS_ConvertUTF8toUTF16(hostPort));
+
+ key = dispHost;
+ if (*default_domain != '\0') {
+ // We assume the realm string is ASCII. That might be a bogus assumption,
+ // but we have no idea what encoding GnomeVFS is using, so for now we'll
+ // limit ourselves to ISO-Latin-1. XXX What is a better solution?
+ realm.Append('"');
+ realm.Append(NS_ConvertASCIItoUTF16(default_domain));
+ realm.Append('"');
+ key.Append(' ');
+ key.Append(realm);
+ }
+ // Construct the message string...
+ //
+ // We use Necko's string bundle here. This code really should be encapsulated
+ // behind some Necko API, after all this code is based closely on the code in
+ // nsHttpChannel.cpp.
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ if (!bundleSvc) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleSvc->CreateBundle("chrome://global/locale/commonDialogs.properties",
+ getter_AddRefs(bundle));
+ if (!bundle) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ nsAutoString nsmessage;
+
+ if (flags & G_ASK_PASSWORD_NEED_PASSWORD) {
+ if (flags & G_ASK_PASSWORD_NEED_USERNAME) {
+ if (!realm.IsEmpty()) {
+ AutoTArray<nsString, 2> strings = {realm, dispHost};
+ bundle->FormatStringFromName("EnterLoginForRealm3", strings, nsmessage);
+ } else {
+ AutoTArray<nsString, 1> strings = {dispHost};
+ bundle->FormatStringFromName("EnterUserPasswordFor2", strings,
+ nsmessage);
+ }
+ } else {
+ NS_ConvertUTF8toUTF16 userName(default_user);
+ AutoTArray<nsString, 2> strings = {userName, dispHost};
+ bundle->FormatStringFromName("EnterPasswordFor", strings, nsmessage);
+ }
+ } else {
+ g_warning("Unknown mount operation request (flags: %x)", flags);
+ }
+
+ if (nsmessage.IsEmpty()) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ // Prompt the user...
+ nsresult rv;
+ bool retval = false;
+ char16_t *user = nullptr, *pass = nullptr;
+ if (default_user) {
+ // user will be freed by PromptUsernameAndPassword
+ user = ToNewUnicode(NS_ConvertUTF8toUTF16(default_user));
+ }
+ if (flags & G_ASK_PASSWORD_NEED_USERNAME) {
+ rv = prompt->PromptUsernameAndPassword(
+ nullptr, nsmessage.get(), key.get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY, &user, &pass, &retval);
+ } else {
+ rv = prompt->PromptPassword(nullptr, nsmessage.get(), key.get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY, &pass,
+ &retval);
+ }
+ if (NS_FAILED(rv) || !retval) { // was || user == '\0' || pass == '\0'
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ free(user);
+ free(pass);
+ return;
+ }
+ /* GIO should accept UTF8 */
+ g_mount_operation_set_username(mount_op, NS_ConvertUTF16toUTF8(user).get());
+ g_mount_operation_set_password(mount_op, NS_ConvertUTF16toUTF8(pass).get());
+ free(user);
+ free(pass);
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_HANDLED);
+}
+
+//-----------------------------------------------------------------------------
+
+mozilla::StaticRefPtr<nsGIOProtocolHandler> nsGIOProtocolHandler::sSingleton;
+
+already_AddRefed<nsGIOProtocolHandler> nsGIOProtocolHandler::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new nsGIOProtocolHandler();
+ sSingleton->Init();
+ ClearOnShutdown(&sSingleton);
+ }
+ return do_AddRef(sSingleton);
+}
+
+NS_IMPL_ISUPPORTS(nsGIOProtocolHandler, nsIProtocolHandler, nsIObserver)
+
+nsresult nsGIOProtocolHandler::Init() {
+ if (net::IsNeckoChild()) {
+ net::NeckoChild::InitNeckoChild();
+ }
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ InitSupportedProtocolsPref(prefs);
+ prefs->AddObserver(MOZ_GIO_SUPPORTED_PROTOCOLS, this, false);
+ }
+
+ return NS_OK;
+}
+
+void nsGIOProtocolHandler::InitSupportedProtocolsPref(nsIPrefBranch* prefs) {
+ nsCOMPtr<nsIIOService> ioService = components::IO::Service();
+ if (NS_WARN_IF(!ioService)) {
+ LOG(("gio: ioservice not available\n"));
+ return;
+ }
+
+ // Get user preferences to determine which protocol is supported.
+ // Gvfs/GIO has a set of supported protocols like obex, network, archive,
+ // computer, dav, cdda, gphoto2, trash, etc. Some of these seems to be
+ // irrelevant to process by browser. By default accept only sftp protocol so
+ // far.
+ nsAutoCString prefValue;
+ nsresult rv = prefs->GetCharPref(MOZ_GIO_SUPPORTED_PROTOCOLS, prefValue);
+ if (NS_SUCCEEDED(rv)) {
+ prefValue.StripWhitespace();
+ ToLowerCase(prefValue);
+ } else {
+ prefValue.AssignLiteral("" // use none by default
+ );
+ }
+ LOG(("gio: supported protocols \"%s\"\n", prefValue.get()));
+
+ // Unregister any previously registered dynamic protocols.
+ for (const nsCString& scheme : mSupportedProtocols) {
+ LOG(("gio: unregistering handler for \"%s\"", scheme.get()));
+ ioService->UnregisterProtocolHandler(scheme);
+ }
+ mSupportedProtocols.Clear();
+
+ // Register each protocol from the pref branch to reference
+ // nsGIOProtocolHandler.
+ for (const nsDependentCSubstring& protocol : prefValue.Split(',')) {
+ if (NS_WARN_IF(!StringEndsWith(protocol, ":"_ns))) {
+ continue; // each protocol must end with a `:` character to be recognized
+ }
+
+ nsCString scheme(Substring(protocol, 0, protocol.Length() - 1));
+ if (NS_SUCCEEDED(ioService->RegisterProtocolHandler(
+ scheme, this,
+ nsIProtocolHandler::URI_STD |
+ nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
+ /* aDefaultPort */ -1))) {
+ LOG(("gio: successfully registered handler for \"%s\"", scheme.get()));
+ mSupportedProtocols.AppendElement(scheme);
+ } else {
+ LOG(("gio: failed to register handler for \"%s\"", scheme.get()));
+ }
+ }
+}
+
+bool nsGIOProtocolHandler::IsSupportedProtocol(const nsCString& aScheme) {
+ for (const auto& protocol : mSupportedProtocols) {
+ if (aScheme.EqualsIgnoreCase(protocol)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::GetScheme(nsACString& aScheme) {
+ aScheme.AssignLiteral(MOZ_GIO_SCHEME);
+ return NS_OK;
+}
+
+static bool IsValidGIOScheme(const nsACString& aScheme) {
+ // Verify that GIO supports this URI scheme.
+ GVfs* gvfs = g_vfs_get_default();
+
+ if (!gvfs) {
+ g_warning("Cannot get GVfs object.");
+ return false;
+ }
+
+ const gchar* const* uri_schemes = g_vfs_get_supported_uri_schemes(gvfs);
+
+ while (*uri_schemes != nullptr) {
+ // While flatSpec ends with ':' the uri_scheme does not. Therefore do not
+ // compare last character.
+ if (aScheme.Equals(*uri_schemes)) {
+ return true;
+ }
+ uri_schemes++;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** aResult) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ nsresult rv;
+
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString scheme;
+ rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!IsSupportedProtocol(scheme)) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+
+ // g_vfs_get_supported_uri_schemes() returns a very limited list in the
+ // child due to the sandbox, so we only check if its valid for the parent.
+ if (XRE_IsParentProcess() && !IsValidGIOScheme(scheme)) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+
+ RefPtr<nsBaseChannel> channel;
+ if (net::IsNeckoChild()) {
+ channel = new mozilla::net::GIOChannelChild(aURI);
+ // set the loadInfo on the new channel
+ channel->SetLoadInfo(aLoadInfo);
+
+ rv = channel->SetContentType(nsLiteralCString(UNKNOWN_CONTENT_TYPE));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel.forget(aResult);
+ return NS_OK;
+ }
+
+ RefPtr<nsGIOInputStream> stream = new nsGIOInputStream(spec);
+ if (!stream) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ RefPtr<nsGIOInputStream> tmpStream = stream;
+ rv = NS_NewInputStreamChannelInternal(aResult, aURI, tmpStream.forget(),
+ nsLiteralCString(UNKNOWN_CONTENT_TYPE),
+ ""_ns, // aContentCharset
+ aLoadInfo);
+ if (NS_SUCCEEDED(rv)) {
+ stream->SetChannel(*aResult);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::AllowPort(int32_t aPort, const char* aScheme,
+ bool* aResult) {
+ // Don't override anything.
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
+ nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(aSubject);
+ InitSupportedProtocolsPref(prefs);
+ }
+ return NS_OK;
+}
diff --git a/netwerk/protocol/gio/nsGIOProtocolHandler.h b/netwerk/protocol/gio/nsGIOProtocolHandler.h
new file mode 100644
index 0000000000..08e7c01bed
--- /dev/null
+++ b/netwerk/protocol/gio/nsGIOProtocolHandler.h
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGIOProtocolHandler_h___
+#define nsGIOProtocolHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsIObserver.h"
+#include "nsIPrefBranch.h"
+#include "nsStringFwd.h"
+
+#include "mozilla/Logging.h"
+extern mozilla::LazyLogModule gGIOLog;
+
+class nsGIOProtocolHandler final : public nsIProtocolHandler,
+ public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIOBSERVER
+
+ static already_AddRefed<nsGIOProtocolHandler> GetSingleton();
+ bool IsSupportedProtocol(const nsCString& aScheme);
+
+ protected:
+ ~nsGIOProtocolHandler() = default;
+
+ private:
+ nsresult Init();
+
+ void InitSupportedProtocolsPref(nsIPrefBranch* prefs);
+
+ static mozilla::StaticRefPtr<nsGIOProtocolHandler> sSingleton;
+ nsTArray<nsCString> mSupportedProtocols;
+};
+
+#endif // nsGIOProtocolHandler_h___
diff --git a/netwerk/protocol/http/ASpdySession.cpp b/netwerk/protocol/http/ASpdySession.cpp
new file mode 100644
index 0000000000..40df46b739
--- /dev/null
+++ b/netwerk/protocol/http/ASpdySession.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+/*
+ Currently supported is h2
+*/
+
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+
+#include "ASpdySession.h"
+#include "PSpdyPush.h"
+#include "Http2Push.h"
+#include "Http2Session.h"
+
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace net {
+
+ASpdySession* ASpdySession::NewSpdySession(net::SpdyVersion version,
+ nsISocketTransport* aTransport,
+ bool attemptingEarlyData) {
+ // This is a necko only interface, so we can enforce version
+ // requests as a precondition
+ MOZ_ASSERT(version == SpdyVersion::HTTP_2, "Unsupported spdy version");
+
+ // Don't do a runtime check of IsSpdyV?Enabled() here because pref value
+ // may have changed since starting negotiation. The selected protocol comes
+ // from a list provided in the SERVER HELLO filtered by our acceptable
+ // versions, so there is no risk of the server ignoring our prefs.
+
+ return Http2Session::CreateSession(aTransport, version, attemptingEarlyData);
+}
+
+SpdyInformation::SpdyInformation() {
+ // highest index of enabled protocols is the
+ // most preferred for ALPN negotiaton
+ Version = SpdyVersion::HTTP_2;
+ VersionString = "h2"_ns;
+ ALPNCallbacks = Http2Session::ALPNCallback;
+}
+
+//////////////////////////////////////////
+// SpdyPushCache
+//////////////////////////////////////////
+
+SpdyPushCache::~SpdyPushCache() { mHashHttp2.Clear(); }
+
+bool SpdyPushCache::RegisterPushedStreamHttp2(const nsCString& key,
+ Http2PushedStream* stream) {
+ LOG3(("SpdyPushCache::RegisterPushedStreamHttp2 %s 0x%X\n", key.get(),
+ stream->StreamID()));
+ if (mHashHttp2.Get(key)) {
+ LOG3(("SpdyPushCache::RegisterPushedStreamHttp2 %s 0x%X duplicate key\n",
+ key.get(), stream->StreamID()));
+ return false;
+ }
+ mHashHttp2.InsertOrUpdate(key, stream);
+ return true;
+}
+
+Http2PushedStream* SpdyPushCache::RemovePushedStreamHttp2(
+ const nsCString& key) {
+ Http2PushedStream* rv = mHashHttp2.Get(key);
+ LOG3(("SpdyPushCache::RemovePushedStreamHttp2 %s 0x%X\n", key.get(),
+ rv ? rv->StreamID() : 0));
+ if (rv) mHashHttp2.Remove(key);
+ return rv;
+}
+
+Http2PushedStream* SpdyPushCache::RemovePushedStreamHttp2ByID(
+ const nsCString& key, const uint32_t& streamID) {
+ Http2PushedStream* rv = mHashHttp2.Get(key);
+ LOG3(("SpdyPushCache::RemovePushedStreamHttp2ByID %s 0x%X 0x%X", key.get(),
+ rv ? rv->StreamID() : 0, streamID));
+ if (rv && streamID == rv->StreamID()) {
+ mHashHttp2.Remove(key);
+ } else {
+ // Ensure we overwrite our rv with null in case the stream IDs don't match
+ rv = nullptr;
+ }
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ASpdySession.h b/netwerk/protocol/http/ASpdySession.h
new file mode 100644
index 0000000000..45831a140c
--- /dev/null
+++ b/netwerk/protocol/http/ASpdySession.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ASpdySession_h
+#define mozilla_net_ASpdySession_h
+
+#include "nsAHttpTransaction.h"
+#include "prinrval.h"
+#include "nsHttp.h"
+#include "nsString.h"
+
+class nsISocketTransport;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpConnection;
+
+class ASpdySession : public nsAHttpTransaction {
+ public:
+ ASpdySession() = default;
+ virtual ~ASpdySession() = default;
+
+ [[nodiscard]] virtual bool AddStream(nsAHttpTransaction*, int32_t,
+ nsIInterfaceRequestor*) = 0;
+ virtual bool CanReuse() = 0;
+ virtual bool RoomForMoreStreams() = 0;
+ virtual PRIntervalTime IdleTime() = 0;
+ virtual uint32_t ReadTimeoutTick(PRIntervalTime now) = 0;
+ virtual void DontReuse() = 0;
+ virtual enum SpdyVersion SpdyVersion() = 0;
+
+ static ASpdySession* NewSpdySession(net::SpdyVersion version,
+ nsISocketTransport*, bool);
+
+ virtual bool TestJoinConnection(const nsACString& hostname, int32_t port) = 0;
+ virtual bool JoinConnection(const nsACString& hostname, int32_t port) = 0;
+
+ virtual void PrintDiagnostics(nsCString& log) = 0;
+
+ bool ResponseTimeoutEnabled() const final { return true; }
+
+ virtual void SendPing() = 0;
+
+ const static uint32_t kSendingChunkSize = 16000;
+ const static uint32_t kTCPSendBufferSize = 131072;
+ const static uint32_t kInitialPushAllowance = 131072; // match default pref
+
+ // This is roughly the amount of data a suspended channel will have to
+ // buffer before h2 flow control kicks in.
+ const static uint32_t kInitialRwin = 12 * 1024 * 1024; // 12MB
+
+ const static uint32_t kDefaultMaxConcurrent = 100;
+
+ // soft errors are errors that terminate a stream without terminating the
+ // connection. In general non-network errors are stream errors as well
+ // as network specific items like cancels.
+ static bool SoftStreamError(nsresult code) {
+ if (NS_SUCCEEDED(code) || code == NS_BASE_STREAM_WOULD_BLOCK) {
+ return false;
+ }
+
+ // this could go either way, but because there are network instances of
+ // it being a hard error we should consider it hard.
+ if (code == NS_ERROR_FAILURE || code == NS_ERROR_OUT_OF_MEMORY) {
+ return false;
+ }
+
+ if (NS_ERROR_GET_MODULE(code) != NS_ERROR_MODULE_NETWORK) {
+ return true;
+ }
+
+ // these are network specific soft errors
+ return (code == NS_BASE_STREAM_CLOSED || code == NS_BINDING_FAILED ||
+ code == NS_BINDING_ABORTED || code == NS_BINDING_REDIRECTED ||
+ code == NS_ERROR_INVALID_CONTENT_ENCODING ||
+ code == NS_BINDING_RETARGETED ||
+ code == NS_ERROR_CORRUPTED_CONTENT);
+ }
+
+ virtual void SetCleanShutdown(bool) = 0;
+ virtual WebSocketSupport GetWebSocketSupport() = 0;
+
+ virtual already_AddRefed<mozilla::net::nsHttpConnection> CreateTunnelStream(
+ nsAHttpTransaction* aHttpTransaction, nsIInterfaceRequestor* aCallbacks,
+ PRIntervalTime aRtt, bool aIsWebSocket = false) = 0;
+};
+
+using ALPNCallback = bool (*)(nsITLSSocketControl*);
+
+// this is essentially a single instantiation as a member of nsHttpHandler.
+// It could be all static except using static ctors of XPCOM objects is a
+// bad idea.
+class SpdyInformation {
+ public:
+ SpdyInformation();
+ ~SpdyInformation() = default;
+
+ SpdyVersion Version; // telemetry enum e.g. SPDY_VERSION_31
+ nsCString VersionString; // npn string e.g. "spdy/3.1"
+
+ // the ALPNCallback function allows the protocol stack to decide whether or
+ // not to offer a particular protocol based on the known TLS information
+ // that we will offer in the client hello (such as version). There has
+ // not been a Server Hello received yet, so not much else can be considered.
+ ALPNCallback ALPNCallbacks;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ASpdySession_h
diff --git a/netwerk/protocol/http/AltDataOutputStreamChild.cpp b/netwerk/protocol/http/AltDataOutputStreamChild.cpp
new file mode 100644
index 0000000000..89e34a4694
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamChild.cpp
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/AltDataOutputStreamChild.h"
+#include "mozilla/Unused.h"
+#include "nsIInputStream.h"
+#include "nsStreamUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(AltDataOutputStreamChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) AltDataOutputStreamChild::Release() {
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "AltDataOutputStreamChild");
+
+ if (mRefCnt == 1 && mIPCOpen) {
+ // The only reference left is the IPDL one. After the parent replies back
+ // with a DeleteSelf message, the child will call Send__delete__(this),
+ // decrementing the ref count and triggering the destructor.
+ SendDeleteSelf();
+ return 1;
+ }
+
+ if (mRefCnt == 0) {
+ mRefCnt = 1; /* stabilize */
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+}
+
+NS_INTERFACE_MAP_BEGIN(AltDataOutputStreamChild)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+AltDataOutputStreamChild::AltDataOutputStreamChild()
+ : mIPCOpen(false), mError(NS_OK), mCallbackFlags(0) {
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+}
+
+void AltDataOutputStreamChild::AddIPDLReference() {
+ MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void AltDataOutputStreamChild::ReleaseIPDLReference() {
+ MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference");
+ mIPCOpen = false;
+
+ if (mCallback) {
+ NotifyListener();
+ }
+
+ Release();
+}
+
+bool AltDataOutputStreamChild::WriteDataInChunks(
+ const nsDependentCSubstring& data) {
+ const size_t kChunkSize = 128 * 1024;
+ size_t next = std::min(data.Length(), kChunkSize);
+ for (size_t i = 0; i < data.Length();
+ i = next, next = std::min(data.Length(), next + kChunkSize)) {
+ nsCString chunk(Substring(data, i, kChunkSize));
+ if (mIPCOpen && !SendWriteData(chunk)) {
+ mIPCOpen = false;
+ return false;
+ }
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::Close() { return CloseWithStatus(NS_OK); }
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::Flush() {
+ if (!mIPCOpen) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+
+ // This is a no-op
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::StreamStatus() {
+ if (!mIPCOpen) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mError;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::Write(const char* aBuf, uint32_t aCount,
+ uint32_t* _retval) {
+ if (!mIPCOpen) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+ if (WriteDataInChunks(nsDependentCSubstring(aBuf, aCount))) {
+ *_retval = aCount;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::WriteFrom(nsIInputStream* aFromStream,
+ uint32_t aCount, uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::WriteSegments(nsReadSegmentFun aReader,
+ void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::IsNonBlocking(bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult AltDataOutputStreamChild::RecvError(
+ const nsresult& err) {
+ mError = err;
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult AltDataOutputStreamChild::RecvDeleteSelf() {
+ PAltDataOutputStreamChild::Send__delete__(this);
+ return IPC_OK();
+}
+
+// nsIAsyncOutputStream
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::CloseWithStatus(nsresult aStatus) {
+ if (!mIPCOpen) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+ Unused << SendClose(aStatus);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::AsyncWait(nsIOutputStreamCallback* aCallback,
+ uint32_t aFlags, uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ mCallback = aCallback;
+ mCallbackFlags = aFlags;
+ mCallbackTarget = aEventTarget;
+
+ if (!mCallback) {
+ return NS_OK;
+ }
+
+ // The stream is blocking so it is writable at any time
+ if (!mIPCOpen || !(aFlags & WAIT_CLOSURE_ONLY)) {
+ NotifyListener();
+ }
+
+ return NS_OK;
+}
+
+void AltDataOutputStreamChild::NotifyListener() {
+ MOZ_ASSERT(mCallback);
+
+ if (!mCallbackTarget) {
+ mCallbackTarget = GetMainThreadSerialEventTarget();
+ }
+
+ nsCOMPtr<nsIOutputStreamCallback> asyncCallback =
+ NS_NewOutputStreamReadyEvent(mCallback, mCallbackTarget);
+
+ mCallback = nullptr;
+ mCallbackTarget = nullptr;
+
+ asyncCallback->OnOutputStreamReady(this);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltDataOutputStreamChild.h b/netwerk/protocol/http/AltDataOutputStreamChild.h
new file mode 100644
index 0000000000..1a6d5143dd
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamChild.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_AltDataOutputStreamChild_h
+#define mozilla_net_AltDataOutputStreamChild_h
+
+#include "mozilla/net/PAltDataOutputStreamChild.h"
+#include "nsIAsyncOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+class AltDataOutputStreamChild : public PAltDataOutputStreamChild,
+ public nsIAsyncOutputStream {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+ NS_DECL_NSIOUTPUTSTREAM
+ explicit AltDataOutputStreamChild();
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+ // Saves an error code which will be reported to the writer on the next call.
+ virtual mozilla::ipc::IPCResult RecvError(const nsresult& err);
+ virtual mozilla::ipc::IPCResult RecvDeleteSelf();
+
+ private:
+ virtual ~AltDataOutputStreamChild() = default;
+ // Sends data to the parent process in 256k chunks.
+ bool WriteDataInChunks(const nsDependentCSubstring& data);
+ void NotifyListener();
+
+ bool mIPCOpen;
+ // If there was an error opening the output stream or writing to it on the
+ // parent side, this will be set to the error code. We check it before we
+ // write so we can report an error to the consumer.
+ nsresult mError;
+
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ uint32_t mCallbackFlags;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AltDataOutputStreamChild_h
diff --git a/netwerk/protocol/http/AltDataOutputStreamParent.cpp b/netwerk/protocol/http/AltDataOutputStreamParent.cpp
new file mode 100644
index 0000000000..252e313358
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamParent.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/AltDataOutputStreamParent.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/Unused.h"
+#include "nsIAsyncOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS0(AltDataOutputStreamParent)
+
+AltDataOutputStreamParent::AltDataOutputStreamParent(nsIOutputStream* aStream)
+ : mOutputStream(aStream), mStatus(NS_OK), mIPCOpen(true) {
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+ PerfStats::RecordMeasurementStart(PerfStats::Metric::JSBC_IO_Write);
+}
+
+AltDataOutputStreamParent::~AltDataOutputStreamParent() {
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+}
+
+mozilla::ipc::IPCResult AltDataOutputStreamParent::RecvWriteData(
+ const nsCString& data) {
+ if (NS_FAILED(mStatus)) {
+ if (mIPCOpen) {
+ Unused << SendError(mStatus);
+ }
+ return IPC_OK();
+ }
+ nsresult rv;
+ uint32_t n;
+ if (mOutputStream) {
+ rv = mOutputStream->Write(data.BeginReading(), data.Length(), &n);
+ MOZ_ASSERT(n == data.Length() || NS_FAILED(rv));
+ if (NS_FAILED(rv) && mIPCOpen) {
+ Unused << SendError(rv);
+ }
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult AltDataOutputStreamParent::RecvClose(
+ const nsresult& aStatus) {
+ PerfStats::RecordMeasurementEnd(PerfStats::Metric::JSBC_IO_Write);
+
+ if (NS_FAILED(mStatus)) {
+ if (mIPCOpen) {
+ Unused << SendError(mStatus);
+ }
+ return IPC_OK();
+ }
+
+ if (!mOutputStream) {
+ return IPC_OK();
+ }
+
+ nsCOMPtr<nsIAsyncOutputStream> asyncOutputStream =
+ do_QueryInterface(mOutputStream);
+ MOZ_ASSERT(asyncOutputStream);
+
+ nsresult rv = asyncOutputStream->CloseWithStatus(aStatus);
+ if (NS_FAILED(rv) && mIPCOpen) {
+ Unused << SendError(rv);
+ }
+
+ mOutputStream = nullptr;
+ return IPC_OK();
+}
+
+void AltDataOutputStreamParent::ActorDestroy(ActorDestroyReason aWhy) {
+ mIPCOpen = false;
+}
+
+mozilla::ipc::IPCResult AltDataOutputStreamParent::RecvDeleteSelf() {
+ mIPCOpen = false;
+ Unused << SendDeleteSelf();
+ return IPC_OK();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltDataOutputStreamParent.h b/netwerk/protocol/http/AltDataOutputStreamParent.h
new file mode 100644
index 0000000000..a9642b9e45
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamParent.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_AltDataOutputStreamParent_h
+#define mozilla_net_AltDataOutputStreamParent_h
+
+#include "mozilla/net/PAltDataOutputStreamParent.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+// Forwards data received from the content process to an output stream.
+class AltDataOutputStreamParent : public PAltDataOutputStreamParent,
+ public nsISupports {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // Called from NeckoParent::AllocPAltDataOutputStreamParent which also opens
+ // the output stream.
+ // aStream may be null
+ explicit AltDataOutputStreamParent(nsIOutputStream* aStream);
+
+ // Called when data is received from the content process.
+ // We proceed to write that data to the output stream.
+ mozilla::ipc::IPCResult RecvWriteData(const nsCString& data);
+ // Called when AltDataOutputStreamChild::Close() is
+ // Closes and nulls the output stream.
+ mozilla::ipc::IPCResult RecvClose(const nsresult& aStatus);
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ // Sets an error that will be reported to the content process.
+ void SetError(nsresult status) { mStatus = status; }
+ mozilla::ipc::IPCResult RecvDeleteSelf();
+
+ private:
+ virtual ~AltDataOutputStreamParent();
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ // In case any error occurs mStatus will be != NS_OK, and this status code
+ // will be sent to the content process asynchronously.
+ nsresult mStatus;
+ bool mIPCOpen;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AltDataOutputStreamParent_h
diff --git a/netwerk/protocol/http/AltServiceChild.cpp b/netwerk/protocol/http/AltServiceChild.cpp
new file mode 100644
index 0000000000..79959f2161
--- /dev/null
+++ b/netwerk/protocol/http/AltServiceChild.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "AltServiceChild.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsProxyInfo.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+StaticRefPtr<AltServiceChild> sAltServiceChild;
+
+AltServiceChild::AltServiceChild() {
+ LOG(("AltServiceChild ctor [%p]\n", this));
+}
+
+AltServiceChild::~AltServiceChild() {
+ LOG(("AltServiceChild dtor [%p]\n", this));
+}
+
+// static
+bool AltServiceChild::EnsureAltServiceChild() {
+ MOZ_ASSERT(XRE_IsSocketProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sAltServiceChild) {
+ return true;
+ }
+
+ SocketProcessChild* socketChild = SocketProcessChild::GetSingleton();
+ if (!socketChild || socketChild->IsShuttingDown()) {
+ return false;
+ }
+
+ sAltServiceChild = new AltServiceChild();
+ ClearOnShutdown(&sAltServiceChild);
+
+ if (!socketChild->SendPAltServiceConstructor(sAltServiceChild)) {
+ sAltServiceChild = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+// static
+void AltServiceChild::ClearHostMapping(nsHttpConnectionInfo* aCi) {
+ LOG(("AltServiceChild::ClearHostMapping ci=%s", aCi->HashKey().get()));
+ MOZ_ASSERT(aCi);
+
+ RefPtr<nsHttpConnectionInfo> ci = aCi->Clone();
+ auto task = [ci{std::move(ci)}]() {
+ if (!EnsureAltServiceChild()) {
+ return;
+ }
+
+ if (!ci->GetOrigin().IsEmpty() && sAltServiceChild->CanSend()) {
+ Unused << sAltServiceChild->SendClearHostMapping(
+ ci->GetOrigin(), ci->OriginPort(), ci->GetOriginAttributes());
+ }
+ };
+
+ if (!NS_IsMainThread()) {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::AltServiceChild::ClearHostMapping", task)));
+ return;
+ }
+
+ task();
+}
+
+// static
+void AltServiceChild::ProcessHeader(
+ const nsCString& aBuf, const nsCString& aOriginScheme,
+ const nsCString& aOriginHost, int32_t aOriginPort,
+ const nsCString& aUsername, bool aPrivateBrowsing,
+ nsIInterfaceRequestor* aCallbacks, nsProxyInfo* aProxyInfo, uint32_t aCaps,
+ const OriginAttributes& aOriginAttributes) {
+ LOG(("AltServiceChild::ProcessHeader"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!EnsureAltServiceChild()) {
+ return;
+ }
+
+ if (!sAltServiceChild->CanSend()) {
+ return;
+ }
+
+ nsTArray<ProxyInfoCloneArgs> proxyInfoArray;
+ if (aProxyInfo) {
+ nsProxyInfo::SerializeProxyInfo(aProxyInfo, proxyInfoArray);
+ }
+
+ Unused << sAltServiceChild->SendProcessHeader(
+ aBuf, aOriginScheme, aOriginHost, aOriginPort, aUsername,
+ aPrivateBrowsing, proxyInfoArray, aCaps, aOriginAttributes);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltServiceChild.h b/netwerk/protocol/http/AltServiceChild.h
new file mode 100644
index 0000000000..cc70a0e048
--- /dev/null
+++ b/netwerk/protocol/http/AltServiceChild.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AltServiceChild_h__
+#define AltServiceChild_h__
+
+#include "mozilla/net/PAltServiceChild.h"
+
+class nsIInterfaceRequestor;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpConnectionInfo;
+class nsProxyInfo;
+
+class AltServiceChild final : public PAltServiceChild {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(AltServiceChild, override)
+
+ static bool EnsureAltServiceChild();
+ static void ClearHostMapping(nsHttpConnectionInfo* aCi);
+ static void ProcessHeader(const nsCString& aBuf,
+ const nsCString& aOriginScheme,
+ const nsCString& aOriginHost, int32_t aOriginPort,
+ const nsCString& aUsername, bool aPrivateBrowsing,
+ nsIInterfaceRequestor* aCallbacks,
+ nsProxyInfo* aProxyInfo, uint32_t aCaps,
+ const OriginAttributes& aOriginAttributes);
+
+ private:
+ AltServiceChild();
+ virtual ~AltServiceChild();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // AltServiceChild_h__
diff --git a/netwerk/protocol/http/AltServiceParent.cpp b/netwerk/protocol/http/AltServiceParent.cpp
new file mode 100644
index 0000000000..468925c2ad
--- /dev/null
+++ b/netwerk/protocol/http/AltServiceParent.cpp
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "AltServiceParent.h"
+#include "AlternateServices.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+mozilla::ipc::IPCResult AltServiceParent::RecvClearHostMapping(
+ const nsCString& aHost, const int32_t& aPort,
+ const OriginAttributes& aOriginAttributes) {
+ LOG(("AltServiceParent::RecvClearHostMapping [this=%p]\n", this));
+ if (gHttpHandler) {
+ gHttpHandler->AltServiceCache()->ClearHostMapping(aHost, aPort,
+ aOriginAttributes);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult AltServiceParent::RecvProcessHeader(
+ const nsCString& aBuf, const nsCString& aOriginScheme,
+ const nsCString& aOriginHost, const int32_t& aOriginPort,
+ const nsACString& aUsername, const bool& aPrivateBrowsing,
+ nsTArray<ProxyInfoCloneArgs>&& aProxyInfo, const uint32_t& aCaps,
+ const OriginAttributes& aOriginAttributes) {
+ LOG(("AltServiceParent::RecvProcessHeader [this=%p]\n", this));
+ nsProxyInfo* pi = aProxyInfo.IsEmpty()
+ ? nullptr
+ : nsProxyInfo::DeserializeProxyInfo(aProxyInfo);
+ AltSvcMapping::ProcessHeader(aBuf, aOriginScheme, aOriginHost, aOriginPort,
+ aUsername, aPrivateBrowsing, nullptr, pi, aCaps,
+ aOriginAttributes);
+ return IPC_OK();
+}
+
+void AltServiceParent::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("AltServiceParent::ActorDestroy [this=%p]\n", this));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltServiceParent.h b/netwerk/protocol/http/AltServiceParent.h
new file mode 100644
index 0000000000..471b7049b2
--- /dev/null
+++ b/netwerk/protocol/http/AltServiceParent.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AltServiceParent_h__
+#define AltServiceParent_h__
+
+#include "mozilla/net/PAltServiceParent.h"
+
+namespace mozilla {
+namespace net {
+
+class AltServiceParent final : public PAltServiceParent {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(AltServiceParent, override)
+ AltServiceParent() = default;
+
+ mozilla::ipc::IPCResult RecvClearHostMapping(
+ const nsCString& aHost, const int32_t& aPort,
+ const OriginAttributes& aOriginAttributes);
+
+ mozilla::ipc::IPCResult RecvProcessHeader(
+ const nsCString& aBuf, const nsCString& aOriginScheme,
+ const nsCString& aOriginHost, const int32_t& aOriginPort,
+ const nsACString& aUsername, const bool& aPrivateBrowsing,
+ nsTArray<ProxyInfoCloneArgs>&& aProxyInfo, const uint32_t& aCaps,
+ const OriginAttributes& aOriginAttributes);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+ virtual ~AltServiceParent() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // AltServiceParent_h__
diff --git a/netwerk/protocol/http/AltSvcTransactionChild.cpp b/netwerk/protocol/http/AltSvcTransactionChild.cpp
new file mode 100644
index 0000000000..096d584c0e
--- /dev/null
+++ b/netwerk/protocol/http/AltSvcTransactionChild.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "AltSvcTransactionChild.h"
+#include "AlternateServices.h"
+#include "nsHttpConnectionInfo.h"
+
+namespace mozilla {
+namespace net {
+
+AltSvcTransactionChild::AltSvcTransactionChild(nsHttpConnectionInfo* aConnInfo,
+ uint32_t aCaps)
+ : mConnInfo(aConnInfo), mCaps(aCaps) {
+ LOG(("AltSvcTransactionChild %p ctor", this));
+}
+
+AltSvcTransactionChild::~AltSvcTransactionChild() {
+ LOG(("AltSvcTransactionChild %p dtor", this));
+}
+
+void AltSvcTransactionChild::OnTransactionDestroy(bool aValidateResult) {
+ LOG(("AltSvcTransactionChild::OnTransactionDestroy %p aValidateResult=%d",
+ this, aValidateResult));
+ RefPtr<AltSvcTransactionChild> self = this;
+ auto task = [self, aValidateResult]() {
+ if (self->CanSend()) {
+ self->Send__delete__(self, aValidateResult);
+ }
+ };
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("AltSvcTransactionChild::OnTransactionClose",
+ std::move(task)),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ task();
+}
+
+void AltSvcTransactionChild::OnTransactionClose(bool aValidateResult) {
+ LOG(("AltSvcTransactionChild::OnTransactionClose %p aValidateResult=%d", this,
+ aValidateResult));
+ RefPtr<AltSvcTransactionChild> self = this;
+ auto task = [self, aValidateResult]() {
+ if (self->CanSend()) {
+ self->SendOnTransactionClose(aValidateResult);
+ }
+ };
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("AltSvcTransactionChild::OnTransactionClose",
+ std::move(task)),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ task();
+}
+
+already_AddRefed<SpeculativeTransaction>
+AltSvcTransactionChild::CreateTransaction() {
+ RefPtr<SpeculativeTransaction> transaction =
+ new AltSvcTransaction<AltSvcTransactionChild>(mConnInfo, nullptr, mCaps,
+ this, mConnInfo->IsHttp3());
+ return transaction.forget();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltSvcTransactionChild.h b/netwerk/protocol/http/AltSvcTransactionChild.h
new file mode 100644
index 0000000000..eb3b4aa580
--- /dev/null
+++ b/netwerk/protocol/http/AltSvcTransactionChild.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AltSvcTransactionChild_h__
+#define AltSvcTransactionChild_h__
+
+#include "mozilla/net/PAltSvcTransactionChild.h"
+
+namespace mozilla {
+namespace net {
+
+class nsHttpConnectionInfo;
+class SpeculativeTransaction;
+
+class AltSvcTransactionChild final : public PAltSvcTransactionChild {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AltSvcTransactionChild, override)
+
+ explicit AltSvcTransactionChild(nsHttpConnectionInfo* aConnInfo,
+ uint32_t aCaps);
+
+ void OnTransactionDestroy(bool aValidateResult);
+ void OnTransactionClose(bool aValidateResult);
+
+ already_AddRefed<SpeculativeTransaction> CreateTransaction();
+
+ private:
+ virtual ~AltSvcTransactionChild();
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ uint32_t mCaps;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // AltSvcTransactionChild_h__
diff --git a/netwerk/protocol/http/AltSvcTransactionParent.cpp b/netwerk/protocol/http/AltSvcTransactionParent.cpp
new file mode 100644
index 0000000000..494efa7f65
--- /dev/null
+++ b/netwerk/protocol/http/AltSvcTransactionParent.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "AltSvcTransactionParent.h"
+#include "AlternateServices.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "nsHttpConnectionInfo.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF_INHERITED(AltSvcTransactionParent, NullHttpTransaction)
+NS_IMPL_RELEASE_INHERITED(AltSvcTransactionParent, NullHttpTransaction)
+
+NS_INTERFACE_MAP_BEGIN(AltSvcTransactionParent)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(AltSvcTransactionParent)
+NS_INTERFACE_MAP_END_INHERITING(NullHttpTransaction)
+
+AltSvcTransactionParent::AltSvcTransactionParent(
+ nsHttpConnectionInfo* aConnInfo, nsIInterfaceRequestor* aCallbacks,
+ uint32_t aCaps, AltSvcMappingValidator* aValidator)
+ : SpeculativeTransaction(aConnInfo, aCallbacks,
+ aCaps & ~NS_HTTP_ALLOW_KEEPALIVE),
+ mValidator(aValidator) {
+ LOG(("AltSvcTransactionParent %p ctor", this));
+ MOZ_ASSERT(mValidator);
+}
+
+AltSvcTransactionParent::~AltSvcTransactionParent() {
+ LOG(("AltSvcTransactionParent %p dtor", this));
+}
+
+bool AltSvcTransactionParent::Init() {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (!parent) {
+ return false;
+ }
+
+ HttpConnectionInfoCloneArgs connInfo;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(mConnectionInfo, connInfo);
+ return parent->SendPAltSvcTransactionConstructor(this, connInfo, mCaps);
+}
+
+mozilla::ipc::IPCResult AltSvcTransactionParent::Recv__delete__(
+ const bool& aValidateResult) {
+ LOG(("AltSvcTransactionParent::Recv__delete__ this=%p", this));
+ mValidator->OnTransactionDestroy(aValidateResult);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult AltSvcTransactionParent::RecvOnTransactionClose(
+ const bool& aValidateResult) {
+ LOG(("AltSvcTransactionParent::RecvOnTransactionClose this=%p", this));
+ mValidator->OnTransactionClose(aValidateResult);
+ return IPC_OK();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltSvcTransactionParent.h b/netwerk/protocol/http/AltSvcTransactionParent.h
new file mode 100644
index 0000000000..dc910ad301
--- /dev/null
+++ b/netwerk/protocol/http/AltSvcTransactionParent.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AltSvcTransactionParent_h__
+#define AltSvcTransactionParent_h__
+
+#include "mozilla/net/PAltSvcTransactionParent.h"
+#include "mozilla/net/SpeculativeTransaction.h"
+
+namespace mozilla {
+namespace net {
+
+class AltSvcMappingValidator;
+
+// 03d22e57-c364-4871-989a-6593eb909d24
+#define ALTSVCTRANSACTIONPARENT_IID \
+ { \
+ 0x03d22e57, 0xc364, 0x4871, { \
+ 0x98, 0x9a, 0x65, 0x93, 0xeb, 0x90, 0x9d, 0x24 \
+ } \
+ }
+
+class AltSvcTransactionParent final : public PAltSvcTransactionParent,
+ public SpeculativeTransaction {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECLARE_STATIC_IID_ACCESSOR(ALTSVCTRANSACTIONPARENT_IID)
+
+ explicit AltSvcTransactionParent(nsHttpConnectionInfo* aConnInfo,
+ nsIInterfaceRequestor* aCallbacks,
+ uint32_t aCaps,
+ AltSvcMappingValidator* aValidator);
+
+ bool Init();
+ mozilla::ipc::IPCResult Recv__delete__(const bool& aValidateResult);
+ mozilla::ipc::IPCResult RecvOnTransactionClose(const bool& aValidateResult);
+
+ private:
+ virtual ~AltSvcTransactionParent();
+
+ RefPtr<AltSvcMappingValidator> mValidator;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(AltSvcTransactionParent,
+ ALTSVCTRANSACTIONPARENT_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // AltSvcTransactionParent_h__
diff --git a/netwerk/protocol/http/AlternateServices.cpp b/netwerk/protocol/http/AlternateServices.cpp
new file mode 100644
index 0000000000..a386206b6d
--- /dev/null
+++ b/netwerk/protocol/http/AlternateServices.cpp
@@ -0,0 +1,1354 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HttpLog.h"
+
+#include "AlternateServices.h"
+#include "LoadInfo.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/dom/PContent.h"
+#include "mozilla/net/AltSvcTransactionChild.h"
+#include "mozilla/net/AltSvcTransactionParent.h"
+#include "nsComponentManagerUtils.h"
+#include "nsEscape.h"
+#include "nsHttpChannel.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpHandler.h"
+#include "nsHttpTransaction.h"
+#include "nsIOService.h"
+#include "nsITLSSocketControl.h"
+#include "nsIWellKnownOpportunisticUtils.h"
+#include "nsThreadUtils.h"
+
+/* RFC 7838 Alternative Services
+ http://httpwg.org/http-extensions/opsec.html
+ note that connections currently do not do mixed-scheme (the I attribute
+ in the ConnectionInfo prevents it) but could, do not honor tls-commit and
+ should not, and always require authentication
+*/
+
+namespace mozilla {
+namespace net {
+
+// function places true in outIsHTTPS if scheme is https, false if
+// http, and returns an error if neither. originScheme passed into
+// alternate service should already be normalized to those lower case
+// strings by the URI parser (and so there is an assert)- this is an extra
+// check.
+static nsresult SchemeIsHTTPS(const nsACString& originScheme,
+ bool& outIsHTTPS) {
+ outIsHTTPS = originScheme.EqualsLiteral("https");
+
+ if (!outIsHTTPS && !originScheme.EqualsLiteral("http")) {
+ MOZ_ASSERT(!originScheme.LowerCaseEqualsLiteral("https") &&
+ !originScheme.LowerCaseEqualsLiteral("http"),
+ "The scheme should already be lowercase");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+bool AltSvcMapping::AcceptableProxy(nsProxyInfo* proxyInfo) {
+ return !proxyInfo || proxyInfo->IsDirect() || proxyInfo->IsSOCKS();
+}
+
+void AltSvcMapping::ProcessHeader(
+ const nsCString& buf, const nsCString& originScheme,
+ const nsCString& originHost, int32_t originPort, const nsACString& username,
+ bool privateBrowsing, nsIInterfaceRequestor* callbacks,
+ nsProxyInfo* proxyInfo, uint32_t caps,
+ const OriginAttributes& originAttributes,
+ bool aDontValidate /* = false */) { // aDontValidate is only used for
+ // testing
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("AltSvcMapping::ProcessHeader: %s\n", buf.get()));
+
+ if (StaticPrefs::network_http_altsvc_proxy_checks() &&
+ !AcceptableProxy(proxyInfo)) {
+ LOG(("AltSvcMapping::ProcessHeader ignoring due to proxy\n"));
+ return;
+ }
+
+ bool isHTTPS;
+ if (NS_FAILED(SchemeIsHTTPS(originScheme, isHTTPS))) {
+ return;
+ }
+ if (!isHTTPS && !gHttpHandler->AllowAltSvcOE()) {
+ LOG(("Alt-Svc Response Header for http:// origin but OE disabled\n"));
+ return;
+ }
+
+ LOG(("Alt-Svc Response Header %s\n", buf.get()));
+ ParsedHeaderValueListList parsedAltSvc(buf);
+ int32_t numEntriesInHeader = parsedAltSvc.mValues.Length();
+
+ // Only use one http3 version.
+ bool http3Found = false;
+
+ for (uint32_t index = 0; index < parsedAltSvc.mValues.Length(); ++index) {
+ uint32_t maxage = 86400; // default
+ nsAutoCString hostname;
+ nsAutoCString npnToken;
+ int32_t portno = originPort;
+ bool clearEntry = false;
+ bool isHttp3 = false;
+
+ for (uint32_t pairIndex = 0;
+ pairIndex < parsedAltSvc.mValues[index].mValues.Length();
+ ++pairIndex) {
+ nsDependentCSubstring& currentName =
+ parsedAltSvc.mValues[index].mValues[pairIndex].mName;
+ nsDependentCSubstring& currentValue =
+ parsedAltSvc.mValues[index].mValues[pairIndex].mValue;
+
+ if (!pairIndex) {
+ if (currentName.EqualsLiteral("clear")) {
+ clearEntry = true;
+ --numEntriesInHeader; // Only want to keep track of actual alt-svc
+ // maps, not clearing
+ break;
+ }
+
+ // h2=[hostname]:443 or h3-xx=[hostname]:port
+ // XX is current version we support and it is define in nsHttp.h.
+ isHttp3 = gHttpHandler->IsHttp3VersionSupported(currentName);
+ npnToken = currentName;
+
+ int32_t colonIndex = currentValue.FindChar(':');
+ if (colonIndex >= 0) {
+ portno =
+ atoi(PromiseFlatCString(currentValue).get() + colonIndex + 1);
+ } else {
+ colonIndex = 0;
+ }
+ hostname.Assign(currentValue.BeginReading(), colonIndex);
+ } else if (currentName.EqualsLiteral("ma")) {
+ maxage = atoi(PromiseFlatCString(currentValue).get());
+ } else {
+ LOG(("Alt Svc ignoring parameter %s", currentName.BeginReading()));
+ }
+ }
+
+ if (clearEntry) {
+ nsCString suffix;
+ originAttributes.CreateSuffix(suffix);
+ LOG(("Alt Svc clearing mapping for %s:%d:%s", originHost.get(),
+ originPort, suffix.get()));
+ gHttpHandler->AltServiceCache()->ClearHostMapping(originHost, originPort,
+ originAttributes);
+ continue;
+ }
+
+ if (NS_FAILED(NS_CheckPortSafety(portno, originScheme.get()))) {
+ LOG(("Alt Svc doesn't allow port %d, ignoring", portno));
+ continue;
+ }
+
+ // unescape modifies a c string in place, so afterwards
+ // update nsCString length
+ nsUnescape(npnToken.BeginWriting());
+ npnToken.SetLength(strlen(npnToken.BeginReading()));
+
+ if (http3Found && isHttp3) {
+ LOG(("Alt Svc ignore multiple Http3 options (%s)", npnToken.get()));
+ continue;
+ }
+
+ SpdyInformation* spdyInfo = gHttpHandler->SpdyInfo();
+ if (!(npnToken.Equals(spdyInfo->VersionString) &&
+ StaticPrefs::network_http_http2_enabled()) &&
+ !(isHttp3 && nsHttpHandler::IsHttp3Enabled() &&
+ !gHttpHandler->IsHttp3Excluded(hostname.IsEmpty() ? originHost
+ : hostname))) {
+ LOG(("Alt Svc unknown protocol %s, ignoring", npnToken.get()));
+ continue;
+ }
+
+ if (isHttp3) {
+ http3Found = true;
+ }
+
+ RefPtr<AltSvcMapping> mapping =
+ new AltSvcMapping(gHttpHandler->AltServiceCache()->GetStoragePtr(),
+ gHttpHandler->AltServiceCache()->StorageEpoch(),
+ originScheme, originHost, originPort, username,
+ privateBrowsing, NowInSeconds() + maxage, hostname,
+ portno, npnToken, originAttributes, isHttp3);
+ if (mapping->TTL() <= 0) {
+ LOG(("Alt Svc invalid map"));
+ mapping = nullptr;
+ // since this isn't a parse error, let's clear any existing mapping
+ // as that would have happened if we had accepted the parameters.
+ gHttpHandler->AltServiceCache()->ClearHostMapping(originHost, originPort,
+ originAttributes);
+ } else if (!aDontValidate) {
+ gHttpHandler->UpdateAltServiceMapping(mapping, proxyInfo, callbacks, caps,
+ originAttributes);
+ } else {
+ gHttpHandler->UpdateAltServiceMappingWithoutValidation(
+ mapping, proxyInfo, callbacks, caps, originAttributes);
+ }
+ }
+
+ if (numEntriesInHeader) { // Ignore headers that were just "alt-svc: clear"
+ Telemetry::Accumulate(Telemetry::HTTP_ALTSVC_ENTRIES_PER_HEADER,
+ numEntriesInHeader);
+ }
+}
+
+AltSvcMapping::AltSvcMapping(nsIDataStorage* storage, int32_t epoch,
+ const nsACString& originScheme,
+ const nsACString& originHost, int32_t originPort,
+ const nsACString& username, bool privateBrowsing,
+ uint32_t expiresAt,
+ const nsACString& alternateHost,
+ int32_t alternatePort, const nsACString& npnToken,
+ const OriginAttributes& originAttributes,
+ bool aIsHttp3)
+ : mStorage(storage),
+ mStorageEpoch(epoch),
+ mAlternateHost(alternateHost),
+ mAlternatePort(alternatePort),
+ mOriginHost(originHost),
+ mOriginPort(originPort),
+ mUsername(username),
+ mPrivate(privateBrowsing),
+ mExpiresAt(expiresAt),
+ mNPNToken(npnToken),
+ mOriginAttributes(originAttributes),
+ mIsHttp3(aIsHttp3) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(SchemeIsHTTPS(originScheme, mHttps))) {
+ LOG(("AltSvcMapping ctor %p invalid scheme\n", this));
+ mExpiresAt = 0; // invalid
+ }
+
+ if (mAlternatePort == -1) {
+ mAlternatePort = mHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+ }
+ if (mOriginPort == -1) {
+ mOriginPort = mHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+ }
+
+ LOG(("AltSvcMapping ctor %p %s://%s:%d to %s:%d\n", this,
+ nsCString(originScheme).get(), mOriginHost.get(), mOriginPort,
+ mAlternateHost.get(), mAlternatePort));
+
+ if (mAlternateHost.IsEmpty()) {
+ mAlternateHost = mOriginHost;
+ }
+
+ if ((mAlternatePort == mOriginPort) &&
+ mAlternateHost.EqualsIgnoreCase(mOriginHost.get()) && !mIsHttp3) {
+ // Http2 on the same host:port does not make sense because we are
+ // connecting to the same end point over the same protocol (TCP) as with
+ // original host. On the other hand, for Http3 alt-svc can be hosted on
+ // the same host:port because protocol(UDP vs. TCP) is always different and
+ // we are not connecting to the same end point.
+ LOG(("Alt Svc is also origin Svc - ignoring\n"));
+ mExpiresAt = 0; // invalid
+ }
+
+ if (mExpiresAt) {
+ MakeHashKey(mHashKey, originScheme, mOriginHost, mOriginPort, mPrivate,
+ mOriginAttributes, mIsHttp3);
+ }
+}
+
+void AltSvcMapping::MakeHashKey(nsCString& outKey,
+ const nsACString& originScheme,
+ const nsACString& originHost,
+ int32_t originPort, bool privateBrowsing,
+ const OriginAttributes& originAttributes,
+ bool aHttp3) {
+ outKey.Truncate();
+
+ if (originPort == -1) {
+ bool isHttps = originScheme.EqualsLiteral("https");
+ originPort = isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+ }
+
+ outKey.Append(originScheme);
+ outKey.Append(':');
+ outKey.Append(originHost);
+ outKey.Append(':');
+ outKey.AppendInt(originPort);
+ outKey.Append(':');
+ outKey.Append(privateBrowsing ? 'P' : '.');
+ outKey.Append(':');
+ nsAutoCString suffix;
+ originAttributes.CreateSuffix(suffix);
+ outKey.Append(suffix);
+ outKey.Append(':');
+
+ outKey.Append(aHttp3 ? '3' : '.');
+}
+
+int32_t AltSvcMapping::TTL() { return mExpiresAt - NowInSeconds(); }
+
+void AltSvcMapping::SyncString(const nsCString& str) {
+ MOZ_ASSERT(NS_IsMainThread());
+ (void)mStorage->Put(HashKey(), str,
+ mPrivate ? nsIDataStorage::DataType::Private
+ : nsIDataStorage::DataType::Persistent);
+}
+
+void AltSvcMapping::Sync() {
+ if (!mStorage) {
+ return;
+ }
+ if (mSyncOnlyOnSuccess && !mValidated) {
+ return;
+ }
+ nsCString value;
+ Serialize(value);
+
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> r;
+ r = NewRunnableMethod<nsCString>("net::AltSvcMapping::SyncString", this,
+ &AltSvcMapping::SyncString, value);
+ NS_DispatchToMainThread(r, NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ (void)mStorage->Put(HashKey(), value,
+ mPrivate ? nsIDataStorage::DataType::Private
+ : nsIDataStorage::DataType::Persistent);
+}
+
+void AltSvcMapping::SetValidated(bool val) {
+ mValidated = val;
+ Sync();
+}
+
+void AltSvcMapping::SetMixedScheme(bool val) {
+ mMixedScheme = val;
+ Sync();
+}
+
+void AltSvcMapping::SetExpiresAt(int32_t val) {
+ mExpiresAt = val;
+ Sync();
+}
+
+void AltSvcMapping::SetExpired() {
+ LOG(("AltSvcMapping SetExpired %p origin %s alternate %s\n", this,
+ mOriginHost.get(), mAlternateHost.get()));
+ mExpiresAt = NowInSeconds() - 1;
+ Sync();
+}
+
+bool AltSvcMapping::RouteEquals(AltSvcMapping* map) {
+ MOZ_ASSERT(map->mHashKey.Equals(mHashKey));
+ return mAlternateHost.Equals(map->mAlternateHost) &&
+ (mAlternatePort == map->mAlternatePort) &&
+ mNPNToken.Equals(map->mNPNToken);
+}
+
+void AltSvcMapping::GetConnectionInfo(
+ nsHttpConnectionInfo** outCI, nsProxyInfo* pi,
+ const OriginAttributes& originAttributes) {
+ RefPtr<nsHttpConnectionInfo> ci = new nsHttpConnectionInfo(
+ mOriginHost, mOriginPort, mNPNToken, mUsername, pi, originAttributes,
+ mAlternateHost, mAlternatePort, mIsHttp3, false);
+
+ // http:// without the mixed-scheme attribute needs to be segmented in the
+ // connection manager connection information hash with this attribute
+ if (!mHttps && !mMixedScheme) {
+ ci->SetInsecureScheme(true);
+ }
+ ci->SetPrivate(mPrivate);
+ ci.forget(outCI);
+}
+
+void AltSvcMapping::Serialize(nsCString& out) {
+ // Be careful, when serializing new members, add them to the end of this list.
+ out = mHttps ? "https:"_ns : "http:"_ns;
+ out.Append(mOriginHost);
+ out.Append(':');
+ out.AppendInt(mOriginPort);
+ out.Append(':');
+ out.Append(mAlternateHost);
+ out.Append(':');
+ out.AppendInt(mAlternatePort);
+ out.Append(':');
+ out.Append(mUsername);
+ out.Append(':');
+ out.Append(mPrivate ? 'y' : 'n');
+ out.Append(':');
+ out.AppendInt(mExpiresAt);
+ out.Append(':');
+ out.Append(mNPNToken);
+ out.Append(':');
+ out.Append(mValidated ? 'y' : 'n');
+ out.Append(':');
+ out.AppendInt(mStorageEpoch);
+ out.Append(':');
+ out.Append(mMixedScheme ? 'y' : 'n');
+ out.Append(':');
+ nsAutoCString suffix;
+ mOriginAttributes.CreateSuffix(suffix);
+ out.Append(suffix);
+ out.Append(':');
+ out.Append(""_ns); // Formerly topWindowOrigin. Now unused empty string.
+ out.Append('|'); // Be careful, the top window origin may contain colons!
+ out.Append('n'); // Formerly mIsolated. Now always 'n'. Should remove someday
+ out.Append(':');
+ out.Append(mIsHttp3 ? 'y' : 'n');
+ out.Append(':');
+ // Add code to serialize new members here!
+}
+
+AltSvcMapping::AltSvcMapping(nsIDataStorage* storage, int32_t epoch,
+ const nsCString& str)
+ : mStorage(storage), mStorageEpoch(epoch) {
+ mValidated = false;
+ nsresult code;
+ char separator = ':';
+
+ // The the do {} while(0) loop acts like try/catch(e){} with the break in
+ // _NS_NEXT_TOKEN
+ do {
+#ifdef _NS_NEXT_TOKEN
+ COMPILER ERROR
+#endif
+#define _NS_NEXT_TOKEN \
+ start = idx + 1; \
+ idx = str.FindChar(separator, start); \
+ if (idx < 0) break;
+ int32_t start = 0;
+ int32_t idx;
+ idx = str.FindChar(separator, start);
+ if (idx < 0) break;
+ // Be careful, when deserializing new members, add them to the end of this
+ // list.
+ mHttps = Substring(str, start, idx - start).EqualsLiteral("https");
+ _NS_NEXT_TOKEN;
+ mOriginHost = Substring(str, start, idx - start);
+ _NS_NEXT_TOKEN;
+ mOriginPort =
+ nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+ _NS_NEXT_TOKEN;
+ mAlternateHost = Substring(str, start, idx - start);
+ _NS_NEXT_TOKEN;
+ mAlternatePort =
+ nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+ _NS_NEXT_TOKEN;
+ mUsername = Substring(str, start, idx - start);
+ _NS_NEXT_TOKEN;
+ mPrivate = Substring(str, start, idx - start).EqualsLiteral("y");
+ _NS_NEXT_TOKEN;
+ mExpiresAt = nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+ _NS_NEXT_TOKEN;
+ mNPNToken = Substring(str, start, idx - start);
+ _NS_NEXT_TOKEN;
+ mValidated = Substring(str, start, idx - start).EqualsLiteral("y");
+ _NS_NEXT_TOKEN;
+ mStorageEpoch =
+ nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+ _NS_NEXT_TOKEN;
+ mMixedScheme = Substring(str, start, idx - start).EqualsLiteral("y");
+ _NS_NEXT_TOKEN;
+ Unused << mOriginAttributes.PopulateFromSuffix(
+ Substring(str, start, idx - start));
+ // The separator after the top window origin is a pipe character since the
+ // origin string can contain colons.
+ separator = '|';
+ _NS_NEXT_TOKEN;
+ // TopWindowOrigin used to be encoded here. Now it's unused.
+ separator = ':';
+ _NS_NEXT_TOKEN;
+ // mIsolated used to be encoded here. Now it's unused.
+ _NS_NEXT_TOKEN;
+ mIsHttp3 = Substring(str, start, idx - start).EqualsLiteral("y");
+ // Add code to deserialize new members here!
+#undef _NS_NEXT_TOKEN
+
+ MakeHashKey(mHashKey, mHttps ? "https"_ns : "http"_ns, mOriginHost,
+ mOriginPort, mPrivate, mOriginAttributes, mIsHttp3);
+ } while (false);
+}
+
+AltSvcMappingValidator::AltSvcMappingValidator(AltSvcMapping* aMap)
+ : mMapping(aMap) {
+ LOG(("AltSvcMappingValidator ctor %p map %p [%s -> %s]", this, aMap,
+ aMap->OriginHost().get(), aMap->AlternateHost().get()));
+ MOZ_ASSERT(mMapping);
+ MOZ_ASSERT(mMapping->HTTPS()); // http:// uses the .wk path
+}
+
+void AltSvcMappingValidator::OnTransactionDestroy(bool aValidateResult) {
+ mMapping->SetValidated(aValidateResult);
+ if (!mMapping->Validated()) {
+ // try again later
+ mMapping->SetExpiresAt(NowInSeconds() + 2);
+ }
+ LOG(
+ ("AltSvcMappingValidator::OnTransactionDestroy %p map %p validated %d "
+ "[%s]",
+ this, mMapping.get(), mMapping->Validated(), mMapping->HashKey().get()));
+}
+
+void AltSvcMappingValidator::OnTransactionClose(bool aValidateResult) {
+ mMapping->SetValidated(aValidateResult);
+ LOG(
+ ("AltSvcMappingValidator::OnTransactionClose %p map %p validated %d "
+ "[%s]",
+ this, mMapping.get(), mMapping->Validated(), mMapping->HashKey().get()));
+}
+
+template <class Validator>
+AltSvcTransaction<Validator>::AltSvcTransaction(
+ nsHttpConnectionInfo* ci, nsIInterfaceRequestor* callbacks, uint32_t caps,
+ Validator* aValidator, bool aIsHttp3)
+ : SpeculativeTransaction(ci, callbacks, caps),
+ mValidator(aValidator),
+ mIsHttp3(aIsHttp3),
+ mRunning(true),
+ mTriedToValidate(false),
+ mTriedToWrite(false),
+ mValidatedResult(false) {
+ MOZ_ASSERT_IF(nsIOService::UseSocketProcess(), XRE_IsSocketProcess());
+ MOZ_ASSERT_IF(!nsIOService::UseSocketProcess(), XRE_IsParentProcess());
+ // We don't want to let this transaction use consistent connection.
+ mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
+}
+
+template <class Validator>
+AltSvcTransaction<Validator>::~AltSvcTransaction() {
+ LOG(("AltSvcTransaction dtor %p running %d", this, mRunning));
+
+ if (mRunning) {
+ mValidatedResult = MaybeValidate(NS_OK);
+ mValidator->OnTransactionDestroy(mValidatedResult);
+ }
+}
+
+template <class Validator>
+bool AltSvcTransaction<Validator>::MaybeValidate(nsresult reason) {
+ if (mTriedToValidate) {
+ return mValidatedResult;
+ }
+ mTriedToValidate = true;
+
+ LOG(("AltSvcTransaction::MaybeValidate() %p reason=%" PRIx32
+ " running=%d conn=%p write=%d",
+ this, static_cast<uint32_t>(reason), mRunning, mConnection.get(),
+ mTriedToWrite));
+
+ if (mTriedToWrite && reason == NS_BASE_STREAM_CLOSED) {
+ // The normal course of events is to cause the transaction to fail with
+ // CLOSED on a write - so that's a success that means the HTTP/2 session
+ // is setup.
+ reason = NS_OK;
+ }
+
+ if (NS_FAILED(reason) || !mRunning || !mConnection) {
+ LOG(("AltSvcTransaction::MaybeValidate %p Failed due to precondition",
+ this));
+ return false;
+ }
+
+ // insist on >= http/2
+ HttpVersion version = mConnection->Version();
+ LOG(("AltSvcTransaction::MaybeValidate() %p version %d\n", this,
+ static_cast<int32_t>(version)));
+ if ((!mIsHttp3 && (version != HttpVersion::v2_0)) ||
+ (mIsHttp3 && (version != HttpVersion::v3_0))) {
+ LOG(
+ ("AltSvcTransaction::MaybeValidate %p Failed due to protocol version"
+ " expacted %s.",
+ this, mIsHttp3 ? "Http3" : "Http2"));
+ return false;
+ }
+
+ nsCOMPtr<nsITLSSocketControl> socketControl;
+ mConnection->GetTLSSocketControl(getter_AddRefs(socketControl));
+
+ LOG(("AltSvcTransaction::MaybeValidate() %p socketControl=%p\n", this,
+ socketControl.get()));
+
+ if (socketControl->GetFailedVerification()) {
+ LOG(
+ ("AltSvcTransaction::MaybeValidate() %p "
+ "not validated due to auth error",
+ this));
+ return false;
+ }
+
+ LOG(
+ ("AltSvcTransaction::MaybeValidate() %p "
+ "validating alternate service with successful auth check",
+ this));
+
+ return true;
+}
+
+template <class Validator>
+void AltSvcTransaction<Validator>::Close(nsresult reason) {
+ LOG(("AltSvcTransaction::Close() %p reason=%" PRIx32 " running %d", this,
+ static_cast<uint32_t>(reason), mRunning));
+
+ mValidatedResult = MaybeValidate(reason);
+ mValidator->OnTransactionClose(mValidatedResult);
+ if (!mValidatedResult && mConnection) {
+ mConnection->DontReuse();
+ }
+ NullHttpTransaction::Close(reason);
+}
+
+template <class Validator>
+nsresult AltSvcTransaction<Validator>::ReadSegments(
+ nsAHttpSegmentReader* reader, uint32_t count, uint32_t* countRead) {
+ LOG(("AltSvcTransaction::ReadSegements() %p\n", this));
+ mTriedToWrite = true;
+ return NullHttpTransaction::ReadSegments(reader, count, countRead);
+}
+
+class WellKnownChecker {
+ public:
+ WellKnownChecker(nsIURI* uri, const nsCString& origin, uint32_t caps,
+ nsHttpConnectionInfo* ci, AltSvcMapping* mapping)
+ : mWaiting(
+ 2) // waiting for 2 channels (default and alternate) to complete
+ ,
+ mOrigin(origin),
+ mAlternatePort(ci->RoutedPort()),
+ mMapping(mapping),
+ mCI(ci),
+ mURI(uri),
+ mCaps(caps) {
+ LOG(("WellKnownChecker ctor %p\n", this));
+ MOZ_ASSERT(!mMapping->HTTPS());
+ }
+
+ nsresult Start() {
+ LOG(("WellKnownChecker::Start %p\n", this));
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ new LoadInfo(nsContentUtils::GetSystemPrincipal(), nullptr, nullptr,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ loadInfo->SetOriginAttributes(mCI->GetOriginAttributes());
+ // allow deprecated HTTP request from SystemPrincipal
+ loadInfo->SetAllowDeprecatedSystemRequests(true);
+
+ RefPtr<nsHttpChannel> chan = new nsHttpChannel();
+ nsresult rv;
+
+ mTransactionAlternate = new TransactionObserver(chan, this);
+ RefPtr<nsHttpConnectionInfo> newCI = mCI->Clone();
+ rv = MakeChannel(chan, mTransactionAlternate, newCI, mURI, mCaps, loadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ chan = new nsHttpChannel();
+ mTransactionOrigin = new TransactionObserver(chan, this);
+ newCI = nullptr;
+ return MakeChannel(chan, mTransactionOrigin, newCI, mURI, mCaps, loadInfo);
+ }
+
+ void Done(TransactionObserver* finished) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("WellKnownChecker::Done %p waiting for %d\n", this, mWaiting));
+
+ mWaiting--; // another channel is complete
+ if (!mWaiting) { // there are all complete!
+ nsAutoCString mAlternateCT, mOriginCT;
+ mTransactionOrigin->mChannel->GetContentType(mOriginCT);
+ mTransactionAlternate->mChannel->GetContentType(mAlternateCT);
+ nsCOMPtr<nsIWellKnownOpportunisticUtils> uu =
+ do_CreateInstance(NS_WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID);
+ bool accepted = false;
+
+ if (!mTransactionOrigin->mStatusOK) {
+ LOG(("WellKnownChecker::Done %p origin was not 200 response code\n",
+ this));
+ } else if (!mTransactionAlternate->mAuthOK) {
+ LOG(("WellKnownChecker::Done %p alternate was not TLS authenticated\n",
+ this));
+ } else if (!mTransactionAlternate->mStatusOK) {
+ LOG(("WellKnownChecker::Done %p alternate was not 200 response code\n",
+ this));
+ } else if (!mTransactionAlternate->mVersionOK) {
+ LOG(("WellKnownChecker::Done %p alternate was not at least h2 or h3\n",
+ this));
+ } else if (!mTransactionAlternate->mWKResponse.Equals(
+ mTransactionOrigin->mWKResponse)) {
+ LOG(
+ ("WellKnownChecker::Done %p alternate and origin "
+ ".wk representations don't match\norigin: %s\alternate:%s\n",
+ this, mTransactionOrigin->mWKResponse.get(),
+ mTransactionAlternate->mWKResponse.get()));
+ } else if (!mAlternateCT.Equals(mOriginCT)) {
+ LOG(
+ ("WellKnownChecker::Done %p alternate and origin content types "
+ "dont match\n",
+ this));
+ } else if (!mAlternateCT.EqualsLiteral("application/json")) {
+ LOG(("WellKnownChecker::Done %p .wk content type is %s\n", this,
+ mAlternateCT.get()));
+ } else if (!uu) {
+ LOG(("WellKnownChecker::Done %p json parser service unavailable\n",
+ this));
+ } else {
+ accepted = true;
+ }
+
+ if (accepted) {
+ MOZ_ASSERT(!mMapping->HTTPS()); // https:// does not use .wk
+
+ nsresult rv = uu->Verify(mTransactionAlternate->mWKResponse, mOrigin);
+ if (NS_SUCCEEDED(rv)) {
+ bool validWK = false;
+ Unused << uu->GetValid(&validWK);
+ if (!validWK) {
+ LOG(("WellKnownChecker::Done %p json parser declares invalid\n%s\n",
+ this, mTransactionAlternate->mWKResponse.get()));
+ accepted = false;
+ }
+ } else {
+ LOG(("WellKnownChecker::Done %p .wk jason eval failed to run\n",
+ this));
+ accepted = false;
+ }
+ }
+
+ MOZ_ASSERT(!mMapping->Validated());
+ if (accepted) {
+ LOG(("WellKnownChecker::Done %p Alternate for %s ACCEPTED\n", this,
+ mOrigin.get()));
+ mMapping->SetValidated(true);
+ } else {
+ LOG(("WellKnownChecker::Done %p Alternate for %s FAILED\n", this,
+ mOrigin.get()));
+ // try again soon
+ mMapping->SetExpiresAt(NowInSeconds() + 2);
+ }
+
+ delete this;
+ }
+ }
+
+ ~WellKnownChecker() { LOG(("WellKnownChecker dtor %p\n", this)); }
+
+ private:
+ nsresult MakeChannel(nsHttpChannel* chan, TransactionObserver* obs,
+ nsHttpConnectionInfo* ci, nsIURI* uri, uint32_t caps,
+ nsILoadInfo* loadInfo) {
+ uint64_t channelId;
+ nsLoadFlags flags;
+
+ ExtContentPolicyType contentPolicyType =
+ loadInfo->GetExternalContentPolicyType();
+
+ if (NS_FAILED(gHttpHandler->NewChannelId(channelId)) ||
+ NS_FAILED(chan->Init(uri, caps, nullptr, 0, nullptr, channelId,
+ contentPolicyType, loadInfo)) ||
+ NS_FAILED(chan->SetAllowAltSvc(false)) ||
+ NS_FAILED(chan->SetRedirectMode(
+ nsIHttpChannelInternal::REDIRECT_MODE_ERROR)) ||
+ NS_FAILED(chan->GetLoadFlags(&flags))) {
+ return NS_ERROR_FAILURE;
+ }
+ flags |= HttpBaseChannel::LOAD_BYPASS_CACHE;
+ if (NS_FAILED(chan->SetLoadFlags(flags))) {
+ return NS_ERROR_FAILURE;
+ }
+ chan->SetTransactionObserver(obs);
+ chan->SetConnectionInfo(ci);
+ return chan->AsyncOpen(obs);
+ }
+
+ RefPtr<TransactionObserver> mTransactionAlternate;
+ RefPtr<TransactionObserver> mTransactionOrigin;
+ uint32_t mWaiting; // semaphore
+ nsCString mOrigin;
+ int32_t mAlternatePort;
+ RefPtr<AltSvcMapping> mMapping;
+ RefPtr<nsHttpConnectionInfo> mCI;
+ nsCOMPtr<nsIURI> mURI;
+ uint32_t mCaps;
+};
+
+NS_IMPL_ISUPPORTS(TransactionObserver, nsIStreamListener)
+
+TransactionObserver::TransactionObserver(nsHttpChannel* channel,
+ WellKnownChecker* checker)
+ : mChannel(channel),
+ mChecker(checker),
+ mRanOnce(false),
+ mStatusOK(false),
+ mAuthOK(false),
+ mVersionOK(false) {
+ LOG(("TransactionObserver ctor %p channel %p checker %p\n", this, channel,
+ checker));
+ mChannelRef = do_QueryInterface((nsIHttpChannel*)channel);
+}
+
+void TransactionObserver::Complete(bool versionOK, bool authOK,
+ nsresult reason) {
+ if (mRanOnce) {
+ return;
+ }
+ mRanOnce = true;
+
+ mVersionOK = versionOK;
+ mAuthOK = authOK;
+
+ LOG(
+ ("TransactionObserve::Complete %p authOK %d versionOK %d"
+ " reason %" PRIx32,
+ this, authOK, versionOK, static_cast<uint32_t>(reason)));
+}
+
+#define MAX_WK 32768
+
+NS_IMETHODIMP
+TransactionObserver::OnStartRequest(nsIRequest* aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // only consider the first 32KB.. because really.
+ mWKResponse.SetCapacity(MAX_WK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransactionObserver::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream, uint64_t aOffset,
+ uint32_t aCount) {
+ MOZ_ASSERT(NS_IsMainThread());
+ uint64_t oldLen = static_cast<uint64_t>(mWKResponse.Length());
+ uint64_t newLen = static_cast<uint64_t>(aCount) + oldLen;
+ if (newLen < MAX_WK) {
+ auto handleOrErr = mWKResponse.BulkWrite(newLen, oldLen, false);
+ if (handleOrErr.isErr()) {
+ return handleOrErr.unwrapErr();
+ }
+ auto handle = handleOrErr.unwrap();
+ uint32_t amtRead;
+ if (NS_SUCCEEDED(
+ aStream->Read(handle.Elements() + oldLen, aCount, &amtRead))) {
+ MOZ_ASSERT(oldLen + amtRead <= newLen);
+ handle.Finish(oldLen + amtRead, false);
+ LOG(("TransactionObserver onDataAvailable %p read %d of .wk [%zd]\n",
+ this, amtRead, mWKResponse.Length()));
+ } else {
+ LOG(("TransactionObserver onDataAvailable %p read error\n", this));
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransactionObserver::OnStopRequest(nsIRequest* aRequest, nsresult code) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("TransactionObserver onStopRequest %p code %" PRIx32 "\n", this,
+ static_cast<uint32_t>(code)));
+ if (NS_SUCCEEDED(code)) {
+ nsHttpResponseHead* hdrs = mChannel->GetResponseHead();
+ LOG(("TransactionObserver onStopRequest %p http resp %d\n", this,
+ hdrs ? hdrs->Status() : -1));
+ mStatusOK = hdrs && (hdrs->Status() == 200);
+ }
+ if (mChecker) {
+ mChecker->Done(this);
+ }
+ return NS_OK;
+}
+
+void AltSvcCache::EnsureStorageInited() {
+ static Atomic<bool> initialized(false);
+
+ if (initialized) {
+ return;
+ }
+
+ auto initTask = [&]() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // nsIDataStorage gives synchronous access to a memory based hash table
+ // that is backed by disk where those writes are done asynchronously
+ // on another thread
+ nsCOMPtr<nsIDataStorageManager> dataStorageManager(
+ do_GetService("@mozilla.org/security/datastoragemanager;1"));
+ if (!dataStorageManager) {
+ LOG(("AltSvcCache::EnsureStorageInited WARN NO STORAGE MANAGER\n"));
+ return;
+ }
+ nsresult rv = dataStorageManager->Get(
+ nsIDataStorageManager::AlternateServices, getter_AddRefs(mStorage));
+ if (NS_FAILED(rv) || !mStorage) {
+ LOG(("AltSvcCache::EnsureStorageInited WARN NO STORAGE\n"));
+ return;
+ }
+ initialized = true;
+
+ mStorageEpoch = NowInSeconds();
+ };
+
+ if (NS_IsMainThread()) {
+ initTask();
+ return;
+ }
+
+ nsCOMPtr<nsIEventTarget> main = GetMainThreadSerialEventTarget();
+ if (!main) {
+ return;
+ }
+
+ SyncRunnable::DispatchToThread(
+ main,
+ NS_NewRunnableFunction("AltSvcCache::EnsureStorageInited", initTask));
+}
+
+already_AddRefed<AltSvcMapping> AltSvcCache::LookupMapping(
+ const nsCString& key, bool privateBrowsing) {
+ LOG(("AltSvcCache::LookupMapping %p %s\n", this, key.get()));
+ if (!mStorage) {
+ LOG(("AltSvcCache::LookupMapping %p no backing store\n", this));
+ return nullptr;
+ }
+
+ if (NS_IsMainThread()) {
+ bool isReady;
+ nsresult rv = mStorage->IsReady(&isReady);
+ if (NS_FAILED(rv)) {
+ LOG(("AltSvcCache::LookupMapping %p mStorage->IsReady failed\n", this));
+ return nullptr;
+ }
+ if (!isReady) {
+ LOG(("AltSvcCache::LookupMapping %p skip when storage is not ready\n",
+ this));
+ return nullptr;
+ }
+ }
+
+ nsAutoCString val;
+ nsresult rv =
+ mStorage->Get(key,
+ privateBrowsing ? nsIDataStorage::DataType::Private
+ : nsIDataStorage::DataType::Persistent,
+ val);
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
+ LOG(("AltSvcCache::LookupMapping %p mStorage->Get failed \n", this));
+ return nullptr;
+ }
+ if (rv == NS_ERROR_NOT_AVAILABLE || val.IsEmpty()) {
+ LOG(("AltSvcCache::LookupMapping %p MISS\n", this));
+ return nullptr;
+ }
+ RefPtr<AltSvcMapping> mapping =
+ new AltSvcMapping(mStorage, mStorageEpoch, val);
+ if (!mapping->Validated() && (mapping->StorageEpoch() != mStorageEpoch)) {
+ // this was an in progress validation abandoned in a different session
+ // rare edge case will not detect session change - that's ok as only impact
+ // will be loss of alt-svc to this origin for this session.
+ LOG(("AltSvcCache::LookupMapping %p invalid hit - MISS\n", this));
+ (void)mStorage->Remove(key, mapping->Private()
+ ? nsIDataStorage::DataType::Private
+ : nsIDataStorage::DataType::Persistent);
+ return nullptr;
+ }
+
+ if (mapping->IsHttp3() &&
+ (!nsHttpHandler::IsHttp3Enabled() ||
+ !gHttpHandler->IsHttp3VersionSupported(mapping->NPNToken()) ||
+ gHttpHandler->IsHttp3Excluded(mapping->AlternateHost()))) {
+ // If Http3 is disabled or the version not supported anymore, remove the
+ // mapping.
+ (void)mStorage->Remove(key, mapping->Private()
+ ? nsIDataStorage::DataType::Private
+ : nsIDataStorage::DataType::Persistent);
+ return nullptr;
+ }
+
+ if (mapping->TTL() <= 0) {
+ LOG(("AltSvcCache::LookupMapping %p expired hit - MISS\n", this));
+ (void)mStorage->Remove(key, mapping->Private()
+ ? nsIDataStorage::DataType::Private
+ : nsIDataStorage::DataType::Persistent);
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mapping->Private() == privateBrowsing);
+ LOG(("AltSvcCache::LookupMapping %p HIT %p\n", this, mapping.get()));
+ return mapping.forget();
+}
+
+// This is only used for testing!
+void AltSvcCache::UpdateAltServiceMappingWithoutValidation(
+ AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor* aCallbacks,
+ uint32_t caps, const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mStorage) {
+ return;
+ }
+ RefPtr<AltSvcMapping> existing =
+ LookupMapping(map->HashKey(), map->Private());
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMappingWithoutValidation %p map %p "
+ "existing %p %s",
+ this, map, existing.get(), map->AlternateHost().get()));
+ if (!existing) {
+ map->SetValidated(true);
+ }
+}
+
+void AltSvcCache::UpdateAltServiceMapping(
+ AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor* aCallbacks,
+ uint32_t caps, const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mStorage) {
+ return;
+ }
+ RefPtr<AltSvcMapping> existing =
+ LookupMapping(map->HashKey(), map->Private());
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p existing %p %s "
+ "validated=%d",
+ this, map, existing.get(), map->AlternateHost().get(),
+ existing ? existing->Validated() : 0));
+
+ if (existing && existing->Validated()) {
+ if (existing->RouteEquals(map)) {
+ // update expires in storage
+ // if this is http:// then a ttl can only be extended via .wk, so ignore
+ // this header path unless it is making things shorter
+ if (existing->HTTPS()) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p updates ttl of "
+ "%p\n",
+ this, map, existing.get()));
+ existing->SetExpiresAt(map->GetExpiresAt());
+ } else {
+ if (map->GetExpiresAt() < existing->GetExpiresAt()) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p reduces ttl of "
+ "%p\n",
+ this, map, existing.get()));
+ existing->SetExpiresAt(map->GetExpiresAt());
+ } else {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p tries to extend "
+ "%p but"
+ " cannot as without .wk\n",
+ this, map, existing.get()));
+ }
+ }
+ Telemetry::Accumulate(Telemetry::HTTP_ALTSVC_MAPPING_CHANGED_TARGET,
+ false);
+ return;
+ }
+
+ if (map->GetExpiresAt() < existing->GetExpiresAt()) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p ttl shorter than "
+ "%p, ignoring",
+ this, map, existing.get()));
+ return;
+ }
+
+ // new alternate. start new validation
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p may overwrite %p\n",
+ this, map, existing.get()));
+ Telemetry::Accumulate(Telemetry::HTTP_ALTSVC_MAPPING_CHANGED_TARGET, true);
+ }
+
+ if (existing && !existing->Validated()) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p ignored because %p "
+ "still in progress\n",
+ this, map, existing.get()));
+ return;
+ }
+
+ if (map->IsHttp3()) {
+ bool isDirectOrNoProxy = pi ? pi->IsDirect() : true;
+ if (!isDirectOrNoProxy) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p map %p ignored h3 because "
+ "proxy is in use %p\n",
+ this, map, existing.get()));
+ return;
+ }
+ }
+
+ // start new validation, but don't overwrite a valid existing mapping unless
+ // this completes successfully
+ MOZ_ASSERT(!map->Validated());
+ if (!existing) {
+ map->Sync();
+ } else {
+ map->SetSyncOnlyOnSuccess(true);
+ }
+
+ RefPtr<nsHttpConnectionInfo> ci;
+ map->GetConnectionInfo(getter_AddRefs(ci), pi, originAttributes);
+ caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
+ caps |= NS_HTTP_ERROR_SOFTLY;
+
+ if (map->HTTPS()) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p validation via "
+ "speculative connect started\n",
+ this));
+ // for https resources we only establish a connection
+ nsCOMPtr<nsIInterfaceRequestor> callbacks = new AltSvcOverride(aCallbacks);
+ RefPtr<AltSvcMappingValidator> validator = new AltSvcMappingValidator(map);
+ RefPtr<SpeculativeTransaction> transaction;
+ if (nsIOService::UseSocketProcess()) {
+ RefPtr<AltSvcTransactionParent> parent =
+ new AltSvcTransactionParent(ci, aCallbacks, caps, validator);
+ if (!parent->Init()) {
+ return;
+ }
+ transaction = parent;
+ } else {
+ transaction = new AltSvcTransaction<AltSvcMappingValidator>(
+ ci, aCallbacks, caps, validator, map->IsHttp3());
+ }
+
+ nsresult rv =
+ gHttpHandler->SpeculativeConnect(ci, callbacks, caps, transaction);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p "
+ "speculative connect failed with code %08x\n",
+ this, static_cast<uint32_t>(rv)));
+ }
+ } else {
+ // for http:// resources we fetch .well-known too
+ nsAutoCString origin("http://"_ns);
+
+ // Check whether origin is an ipv6 address. In that case we need to add
+ // '[]'.
+ if (map->OriginHost().FindChar(':') != kNotFound) {
+ origin.Append('[');
+ origin.Append(map->OriginHost());
+ origin.Append(']');
+ } else {
+ origin.Append(map->OriginHost());
+ }
+ if (map->OriginPort() != NS_HTTP_DEFAULT_PORT) {
+ origin.Append(':');
+ origin.AppendInt(map->OriginPort());
+ }
+
+ nsCOMPtr<nsIURI> wellKnown;
+ nsAutoCString uri(origin);
+ uri.AppendLiteral("/.well-known/http-opportunistic");
+ NS_NewURI(getter_AddRefs(wellKnown), uri);
+
+ auto* checker = new WellKnownChecker(wellKnown, origin, caps, ci, map);
+ if (NS_FAILED(checker->Start())) {
+ LOG(
+ ("AltSvcCache::UpdateAltServiceMapping %p .wk checker failed to "
+ "start\n",
+ this));
+ map->SetExpired();
+ delete checker;
+ checker = nullptr;
+ } else {
+ // object deletes itself when done if started
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p .wk checker started %p\n",
+ this, checker));
+ }
+ }
+}
+
+already_AddRefed<AltSvcMapping> AltSvcCache::GetAltServiceMapping(
+ const nsACString& scheme, const nsACString& host, int32_t port,
+ bool privateBrowsing, const OriginAttributes& originAttributes,
+ bool aHttp2Allowed, bool aHttp3Allowed) {
+ EnsureStorageInited();
+
+ bool isHTTPS;
+ if (NS_FAILED(SchemeIsHTTPS(scheme, isHTTPS))) {
+ return nullptr;
+ }
+ if (!gHttpHandler->AllowAltSvc()) {
+ return nullptr;
+ }
+ if (!gHttpHandler->AllowAltSvcOE() && !isHTTPS) {
+ return nullptr;
+ }
+
+ // First look for HTTP3
+ if (aHttp3Allowed) {
+ nsAutoCString key;
+ AltSvcMapping::MakeHashKey(key, scheme, host, port, privateBrowsing,
+ originAttributes, true);
+ RefPtr<AltSvcMapping> existing = LookupMapping(key, privateBrowsing);
+ LOG(
+ ("AltSvcCache::GetAltServiceMapping %p key=%s "
+ "existing=%p validated=%d ttl=%d",
+ this, key.get(), existing.get(), existing ? existing->Validated() : 0,
+ existing ? existing->TTL() : 0));
+ if (existing && existing->Validated()) {
+ return existing.forget();
+ }
+ }
+
+ // Now look for HTTP2.
+ if (aHttp2Allowed) {
+ nsAutoCString key;
+ AltSvcMapping::MakeHashKey(key, scheme, host, port, privateBrowsing,
+ originAttributes, false);
+ RefPtr<AltSvcMapping> existing = LookupMapping(key, privateBrowsing);
+ LOG(
+ ("AltSvcCache::GetAltServiceMapping %p key=%s "
+ "existing=%p validated=%d ttl=%d",
+ this, key.get(), existing.get(), existing ? existing->Validated() : 0,
+ existing ? existing->TTL() : 0));
+ if (existing && existing->Validated()) {
+ return existing.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+class ProxyClearHostMapping : public Runnable {
+ public:
+ explicit ProxyClearHostMapping(const nsACString& host, int32_t port,
+ const OriginAttributes& originAttributes)
+ : Runnable("net::ProxyClearHostMapping"),
+ mHost(host),
+ mPort(port),
+ mOriginAttributes(originAttributes) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ gHttpHandler->AltServiceCache()->ClearHostMapping(mHost, mPort,
+ mOriginAttributes);
+ return NS_OK;
+ }
+
+ private:
+ nsCString mHost;
+ int32_t mPort;
+ OriginAttributes mOriginAttributes;
+};
+
+void AltSvcCache::ClearHostMapping(const nsACString& host, int32_t port,
+ const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> event =
+ new ProxyClearHostMapping(host, port, originAttributes);
+ if (event) {
+ NS_DispatchToMainThread(event);
+ }
+ return;
+ }
+ nsAutoCString key;
+ for (int secure = 0; secure < 2; ++secure) {
+ constexpr auto http = "http"_ns;
+ constexpr auto https = "https"_ns;
+ const nsLiteralCString& scheme = secure ? https : http;
+ for (int pb = 1; pb >= 0; --pb) {
+ AltSvcMapping::MakeHashKey(key, scheme, host, port, bool(pb),
+ originAttributes, false);
+ RefPtr<AltSvcMapping> existing = LookupMapping(key, bool(pb));
+ if (existing) {
+ existing->SetExpired();
+ }
+ AltSvcMapping::MakeHashKey(key, scheme, host, port, bool(pb),
+ originAttributes, true);
+ existing = LookupMapping(key, bool(pb));
+ if (existing) {
+ existing->SetExpired();
+ }
+ }
+ }
+}
+
+void AltSvcCache::ClearHostMapping(nsHttpConnectionInfo* ci) {
+ if (!ci->GetOrigin().IsEmpty()) {
+ ClearHostMapping(ci->GetOrigin(), ci->OriginPort(),
+ ci->GetOriginAttributes());
+ }
+}
+
+void AltSvcCache::ClearAltServiceMappings() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mStorage) {
+ (void)mStorage->Clear();
+ }
+}
+
+nsresult AltSvcCache::GetAltSvcCacheKeys(nsTArray<nsCString>& value) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (gHttpHandler->AllowAltSvc() && mStorage) {
+ nsTArray<RefPtr<nsIDataStorageItem>> items;
+ nsresult rv = mStorage->GetAll(items);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ for (const auto& item : items) {
+ nsAutoCString key;
+ rv = item->GetKey(key);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ value.AppendElement(key);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetInterface(const nsIID& iid, void** result) {
+ if (NS_SUCCEEDED(QueryInterface(iid, result)) && *result) {
+ return NS_OK;
+ }
+
+ if (mCallbacks) {
+ return mCallbacks->GetInterface(iid, result);
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetIgnoreIdle(bool* ignoreIdle) {
+ *ignoreIdle = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetParallelSpeculativeConnectLimit(
+ uint32_t* parallelSpeculativeConnectLimit) {
+ *parallelSpeculativeConnectLimit = 32;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetIsFromPredictor(bool* isFromPredictor) {
+ *isFromPredictor = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetAllow1918(bool* allow) {
+ // normally we don't do speculative connects to 1918.. and we use
+ // speculative connects for the mapping validation, so override
+ // that default here for alt-svc
+ *allow = true;
+ return NS_OK;
+}
+
+template class AltSvcTransaction<AltSvcTransactionChild>;
+
+NS_IMPL_ISUPPORTS(AltSvcOverride, nsIInterfaceRequestor,
+ nsISpeculativeConnectionOverrider)
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AlternateServices.h b/netwerk/protocol/http/AlternateServices.h
new file mode 100644
index 0000000000..4de5a281c0
--- /dev/null
+++ b/netwerk/protocol/http/AlternateServices.h
@@ -0,0 +1,271 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+Alt-Svc allows separation of transport routing from the origin host without
+using a proxy. See https://httpwg.github.io/http-extensions/alt-svc.html and
+https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06
+
+ Nice To Have Future Enhancements::
+ * flush on network change event when we have an indicator
+ * use established https channel for http instead separate of conninfo hash
+ * pin via http-tls header
+ * clear based on origin when a random fail happens not just 421
+ * upon establishment of channel, cancel and retry trans that have not yet
+ written anything
+ * persistent storage (including private browsing filter)
+ * memory reporter for cache, but this is rather tiny
+*/
+
+#ifndef mozilla_net_AlternateServices_h
+#define mozilla_net_AlternateServices_h
+
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "nsIDataStorage.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsISpeculativeConnect.h"
+#include "mozilla/BasePrincipal.h"
+#include "SpeculativeTransaction.h"
+
+class nsILoadInfo;
+
+namespace mozilla {
+namespace net {
+
+class nsProxyInfo;
+class nsHttpConnectionInfo;
+class nsHttpTransaction;
+class nsHttpChannel;
+class WellKnownChecker;
+
+class AltSvcMapping {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AltSvcMapping)
+
+ private: // ctor from ProcessHeader
+ AltSvcMapping(nsIDataStorage* storage, int32_t storageEpoch,
+ const nsACString& originScheme, const nsACString& originHost,
+ int32_t originPort, const nsACString& username,
+ bool privateBrowsing, uint32_t expiresAt,
+ const nsACString& alternateHost, int32_t alternatePort,
+ const nsACString& npnToken,
+ const OriginAttributes& originAttributes, bool aIsHttp3);
+
+ public:
+ AltSvcMapping(nsIDataStorage* storage, int32_t storageEpoch,
+ const nsCString& str);
+
+ static void ProcessHeader(
+ const nsCString& buf, const nsCString& originScheme,
+ const nsCString& originHost, int32_t originPort,
+ const nsACString& username, bool privateBrowsing,
+ nsIInterfaceRequestor* callbacks, nsProxyInfo* proxyInfo, uint32_t caps,
+ const OriginAttributes& originAttributes,
+ bool aDontValidate = false); // aDontValidate is only used for testing!
+
+ // AcceptableProxy() decides whether a particular proxy configuration (pi) is
+ // suitable for use with Alt-Svc. No proxy (including a null pi) is suitable.
+ static bool AcceptableProxy(nsProxyInfo* proxyInfo);
+
+ const nsCString& AlternateHost() const { return mAlternateHost; }
+ const nsCString& OriginHost() const { return mOriginHost; }
+ uint32_t OriginPort() const { return mOriginPort; }
+ const nsCString& HashKey() const { return mHashKey; }
+ uint32_t AlternatePort() const { return mAlternatePort; }
+ bool Validated() { return mValidated; }
+ int32_t GetExpiresAt() { return mExpiresAt; }
+ bool RouteEquals(AltSvcMapping* map);
+ bool HTTPS() { return mHttps; }
+
+ void GetConnectionInfo(nsHttpConnectionInfo** outCI, nsProxyInfo* pi,
+ const OriginAttributes& originAttributes);
+
+ int32_t TTL();
+ int32_t StorageEpoch() { return mStorageEpoch; }
+ bool Private() { return mPrivate; }
+
+ void SetValidated(bool val);
+ void SetMixedScheme(bool val);
+ void SetExpiresAt(int32_t val);
+ void SetExpired();
+ void Sync();
+ void SetSyncOnlyOnSuccess(bool aSOOS) { mSyncOnlyOnSuccess = aSOOS; }
+
+ static void MakeHashKey(nsCString& outKey, const nsACString& originScheme,
+ const nsACString& originHost, int32_t originPort,
+ bool privateBrowsing,
+ const OriginAttributes& originAttributes,
+ bool aHttp3);
+
+ bool IsHttp3() { return mIsHttp3; }
+ const nsCString& NPNToken() const { return mNPNToken; }
+
+ private:
+ virtual ~AltSvcMapping() = default;
+ void SyncString(const nsCString& str);
+ nsCOMPtr<nsIDataStorage> mStorage;
+ int32_t mStorageEpoch;
+ void Serialize(nsCString& out);
+
+ nsCString mHashKey;
+
+ // If you change any of these members, update Serialize()
+ nsCString mAlternateHost;
+ int32_t mAlternatePort{-1};
+
+ nsCString mOriginHost;
+ int32_t mOriginPort{-1};
+
+ nsCString mUsername;
+ bool mPrivate{false};
+
+ // alt-svc mappping
+ uint32_t mExpiresAt{0};
+
+ bool mValidated{false};
+ // origin is https://
+ MOZ_INIT_OUTSIDE_CTOR bool mHttps{false};
+ // .wk allows http and https on same con
+ MOZ_INIT_OUTSIDE_CTOR bool mMixedScheme{false};
+
+ nsCString mNPNToken;
+
+ OriginAttributes mOriginAttributes;
+
+ bool mSyncOnlyOnSuccess{false};
+ bool mIsHttp3{false};
+};
+
+class AltSvcOverride : public nsIInterfaceRequestor,
+ public nsISpeculativeConnectionOverrider {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ explicit AltSvcOverride(nsIInterfaceRequestor* aRequestor)
+ : mCallbacks(aRequestor) {}
+
+ private:
+ virtual ~AltSvcOverride() = default;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+};
+
+class TransactionObserver final : public nsIStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ TransactionObserver(nsHttpChannel* channel, WellKnownChecker* checker);
+ void Complete(bool versionOK, bool authOK, nsresult reason);
+
+ private:
+ friend class WellKnownChecker;
+ virtual ~TransactionObserver() = default;
+
+ nsCOMPtr<nsISupports> mChannelRef;
+ nsHttpChannel* mChannel;
+ WellKnownChecker* mChecker;
+ nsCString mWKResponse;
+
+ bool mRanOnce;
+ bool mStatusOK; // HTTP Status 200
+ // These two values could be accessed on sts thread.
+ Atomic<bool> mAuthOK; // confirmed no TLS failure
+ Atomic<bool> mVersionOK; // connection h2
+};
+
+class AltSvcCache {
+ public:
+ AltSvcCache() = default;
+ virtual ~AltSvcCache() = default;
+ void UpdateAltServiceMapping(
+ AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor*,
+ uint32_t caps,
+ const OriginAttributes& originAttributes); // main thread
+ void UpdateAltServiceMappingWithoutValidation(
+ AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor*,
+ uint32_t caps,
+ const OriginAttributes& originAttributes); // main thread
+ already_AddRefed<AltSvcMapping> GetAltServiceMapping(
+ const nsACString& scheme, const nsACString& host, int32_t port,
+ bool privateBrowsing, const OriginAttributes& originAttributes,
+ bool aHttp2Allowed, bool aHttp3Allowed);
+ void ClearAltServiceMappings();
+ void ClearHostMapping(const nsACString& host, int32_t port,
+ const OriginAttributes& originAttributes);
+ void ClearHostMapping(nsHttpConnectionInfo* ci);
+ nsIDataStorage* GetStoragePtr() { return mStorage.get(); }
+ int32_t StorageEpoch() { return mStorageEpoch; }
+ nsresult GetAltSvcCacheKeys(nsTArray<nsCString>& value);
+
+ private:
+ void EnsureStorageInited();
+ already_AddRefed<AltSvcMapping> LookupMapping(const nsCString& key,
+ bool privateBrowsing);
+ nsCOMPtr<nsIDataStorage> mStorage;
+ int32_t mStorageEpoch{0};
+};
+
+// This class is used to write the validated result to AltSvcMapping when the
+// AltSvcTransaction is closed and destroyed.
+class AltSvcMappingValidator final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AltSvcMappingValidator)
+
+ explicit AltSvcMappingValidator(AltSvcMapping* aMap);
+
+ void OnTransactionDestroy(bool aValidateResult);
+
+ void OnTransactionClose(bool aValidateResult);
+
+ protected:
+ virtual ~AltSvcMappingValidator() = default;
+
+ RefPtr<AltSvcMapping> mMapping;
+};
+
+// This is the asynchronous null transaction used to validate
+// an alt-svc advertisement only for https://
+// When http over socket process is enabled, this class should live only in
+// socket process.
+template <class Validator>
+class AltSvcTransaction final : public SpeculativeTransaction {
+ public:
+ AltSvcTransaction(nsHttpConnectionInfo* ci, nsIInterfaceRequestor* callbacks,
+ uint32_t caps, Validator* aValidator, bool aIsHttp3);
+
+ ~AltSvcTransaction() override;
+
+ // AltSvcTransaction is used to validate the alt-svc record, so we don't want
+ // to fetch HTTPS RR for this.
+ virtual nsresult FetchHTTPSRR() override { return NS_ERROR_NOT_IMPLEMENTED; }
+
+ private:
+ // check on alternate route.
+ // also evaluate 'reasonable assurances' for opportunistic security
+ bool MaybeValidate(nsresult reason);
+
+ public:
+ void Close(nsresult reason) override;
+ nsresult ReadSegments(nsAHttpSegmentReader* reader, uint32_t count,
+ uint32_t* countRead) override;
+
+ private:
+ RefPtr<Validator> mValidator;
+ uint32_t mIsHttp3 : 1;
+ uint32_t mRunning : 1;
+ uint32_t mTriedToValidate : 1;
+ uint32_t mTriedToWrite : 1;
+ uint32_t mValidatedResult : 1;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // include guard
diff --git a/netwerk/protocol/http/BackgroundChannelRegistrar.cpp b/netwerk/protocol/http/BackgroundChannelRegistrar.cpp
new file mode 100644
index 0000000000..13f1ca88f0
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundChannelRegistrar.cpp
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BackgroundChannelRegistrar.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "HttpBackgroundChannelParent.h"
+#include "HttpChannelParent.h"
+#include "nsXULAppAPI.h"
+
+namespace {
+mozilla::StaticRefPtr<mozilla::net::BackgroundChannelRegistrar> gSingleton;
+}
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(BackgroundChannelRegistrar, nsIBackgroundChannelRegistrar)
+
+BackgroundChannelRegistrar::BackgroundChannelRegistrar() {
+ // BackgroundChannelRegistrar is a main-thread-only object.
+ // All the operations should be run on main thread.
+ // It should be used on chrome process only.
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+BackgroundChannelRegistrar::~BackgroundChannelRegistrar() {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+// static
+already_AddRefed<nsIBackgroundChannelRegistrar>
+BackgroundChannelRegistrar::GetOrCreate() {
+ if (!gSingleton) {
+ gSingleton = new BackgroundChannelRegistrar();
+ ClearOnShutdown(&gSingleton);
+ }
+ return do_AddRef(gSingleton);
+}
+
+void BackgroundChannelRegistrar::NotifyChannelLinked(
+ HttpChannelParent* aChannelParent, HttpBackgroundChannelParent* aBgParent) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aChannelParent);
+ MOZ_ASSERT(aBgParent);
+
+ aBgParent->LinkToChannel(aChannelParent);
+ aChannelParent->OnBackgroundParentReady(aBgParent);
+}
+
+// nsIBackgroundChannelRegistrar
+void BackgroundChannelRegistrar::DeleteChannel(uint64_t aKey) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mChannels.Remove(aKey);
+ mBgChannels.Remove(aKey);
+}
+
+void BackgroundChannelRegistrar::LinkHttpChannel(uint64_t aKey,
+ HttpChannelParent* aChannel) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aChannel);
+
+ RefPtr<HttpBackgroundChannelParent> bgParent;
+ bool found = mBgChannels.Remove(aKey, getter_AddRefs(bgParent));
+
+ if (!found) {
+ mChannels.InsertOrUpdate(aKey, RefPtr{aChannel});
+ return;
+ }
+
+ MOZ_ASSERT(bgParent);
+ NotifyChannelLinked(aChannel, bgParent);
+}
+
+void BackgroundChannelRegistrar::LinkBackgroundChannel(
+ uint64_t aKey, HttpBackgroundChannelParent* aBgChannel) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aBgChannel);
+
+ RefPtr<HttpChannelParent> parent;
+ bool found = mChannels.Remove(aKey, getter_AddRefs(parent));
+
+ if (!found) {
+ mBgChannels.InsertOrUpdate(aKey, RefPtr{aBgChannel});
+ return;
+ }
+
+ MOZ_ASSERT(parent);
+ NotifyChannelLinked(parent, aBgChannel);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/BackgroundChannelRegistrar.h b/netwerk/protocol/http/BackgroundChannelRegistrar.h
new file mode 100644
index 0000000000..8f926103a2
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundChannelRegistrar.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_BackgroundChannelRegistrar_h__
+#define mozilla_net_BackgroundChannelRegistrar_h__
+
+#include "nsIBackgroundChannelRegistrar.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/AlreadyAddRefed.h"
+
+namespace mozilla {
+namespace net {
+
+class HttpBackgroundChannelParent;
+class HttpChannelParent;
+
+class BackgroundChannelRegistrar final : public nsIBackgroundChannelRegistrar {
+ using ChannelHashtable =
+ nsRefPtrHashtable<nsUint64HashKey, HttpChannelParent>;
+ using BackgroundChannelHashtable =
+ nsRefPtrHashtable<nsUint64HashKey, HttpBackgroundChannelParent>;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIBACKGROUNDCHANNELREGISTRAR
+
+ explicit BackgroundChannelRegistrar();
+
+ // Singleton accessor
+ static already_AddRefed<nsIBackgroundChannelRegistrar> GetOrCreate();
+
+ private:
+ virtual ~BackgroundChannelRegistrar();
+
+ // A helper function for BackgroundChannelRegistrar itself to callback
+ // HttpChannelParent and HttpBackgroundChannelParent when both objects are
+ // ready. aChannelParent and aBgParent is the pair of HttpChannelParent and
+ // HttpBackgroundChannelParent that should be linked together.
+ void NotifyChannelLinked(HttpChannelParent* aChannelParent,
+ HttpBackgroundChannelParent* aBgParent);
+
+ // Store unlinked HttpChannelParent objects.
+ ChannelHashtable mChannels;
+
+ // Store unlinked HttpBackgroundChannelParent objects.
+ BackgroundChannelHashtable mBgChannels;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_BackgroundChannelRegistrar_h__
diff --git a/netwerk/protocol/http/BackgroundDataBridgeChild.cpp b/netwerk/protocol/http/BackgroundDataBridgeChild.cpp
new file mode 100644
index 0000000000..481bec5985
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundDataBridgeChild.cpp
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/BackgroundDataBridgeChild.h"
+#include "mozilla/net/HttpBackgroundChannelChild.h"
+
+namespace mozilla {
+namespace net {
+
+BackgroundDataBridgeChild::BackgroundDataBridgeChild(
+ HttpBackgroundChannelChild* aBgChild)
+ : mBgChild(aBgChild) {
+ MOZ_ASSERT(aBgChild);
+}
+
+BackgroundDataBridgeChild::~BackgroundDataBridgeChild() = default;
+
+void BackgroundDataBridgeChild::ActorDestroy(ActorDestroyReason aWhy) {
+ mBgChild = nullptr;
+}
+
+mozilla::ipc::IPCResult BackgroundDataBridgeChild::RecvOnTransportAndData(
+ const uint64_t& offset, const uint32_t& count, const nsACString& data,
+ const TimeStamp& aOnDataAvailableStartTime) {
+ if (!mBgChild) {
+ return IPC_OK();
+ }
+
+ if (mBgChild->ChannelClosed()) {
+ Close();
+ return IPC_OK();
+ }
+
+ return mBgChild->RecvOnTransportAndData(NS_OK, NS_NET_STATUS_RECEIVING_FROM,
+ offset, count, data, true,
+ aOnDataAvailableStartTime);
+}
+
+mozilla::ipc::IPCResult BackgroundDataBridgeChild::RecvOnStopRequest(
+ nsresult aStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const TimeStamp& aOnStopRequestStartTime) {
+ if (!mBgChild) {
+ return IPC_OK();
+ }
+
+ if (mBgChild->ChannelClosed()) {
+ Close();
+ return IPC_OK();
+ }
+
+ return mBgChild->RecvOnStopRequest(
+ aStatus, aTiming, aLastActiveTabOptHit, aResponseTrailers,
+ nsTArray<ConsoleReportCollected>(), true, aOnStopRequestStartTime);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/BackgroundDataBridgeChild.h b/netwerk/protocol/http/BackgroundDataBridgeChild.h
new file mode 100644
index 0000000000..13c890e005
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundDataBridgeChild.h
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_BackgroundDataBridgeChild_h
+#define mozilla_net_BackgroundDataBridgeChild_h
+
+#include "mozilla/net/PBackgroundDataBridgeChild.h"
+#include "mozilla/ipc/BackgroundChild.h"
+
+namespace mozilla {
+namespace net {
+
+class HttpBackgroundChannelChild;
+
+class BackgroundDataBridgeChild final : public PBackgroundDataBridgeChild {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BackgroundDataBridgeChild, override)
+
+ explicit BackgroundDataBridgeChild(HttpBackgroundChannelChild* aBgChild);
+
+ protected:
+ virtual ~BackgroundDataBridgeChild();
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ RefPtr<HttpBackgroundChannelChild> mBgChild;
+
+ public:
+ mozilla::ipc::IPCResult RecvOnTransportAndData(
+ const uint64_t& offset, const uint32_t& count, const nsACString& data,
+ const TimeStamp& aOnDataAvailableStartTime);
+ mozilla::ipc::IPCResult RecvOnStopRequest(
+ nsresult aStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const TimeStamp& aOnStopRequestStartTime);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_BackgroundDataBridgeChild_h
diff --git a/netwerk/protocol/http/BackgroundDataBridgeParent.cpp b/netwerk/protocol/http/BackgroundDataBridgeParent.cpp
new file mode 100644
index 0000000000..843c64023e
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundDataBridgeParent.cpp
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/BackgroundDataBridgeParent.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+BackgroundDataBridgeParent::BackgroundDataBridgeParent(uint64_t aChannelID)
+ : mChannelID(aChannelID), mBackgroundThread(GetCurrentSerialEventTarget()) {
+ if (SocketProcessChild* child = SocketProcessChild::GetSingleton()) {
+ child->AddDataBridgeToMap(aChannelID, this);
+ }
+}
+
+void BackgroundDataBridgeParent::ActorDestroy(ActorDestroyReason aWhy) {
+ if (SocketProcessChild* child = SocketProcessChild::GetSingleton()) {
+ child->RemoveDataBridgeFromMap(mChannelID);
+ }
+}
+
+already_AddRefed<nsISerialEventTarget>
+BackgroundDataBridgeParent::GetBackgroundThread() {
+ return do_AddRef(mBackgroundThread);
+}
+
+void BackgroundDataBridgeParent::Destroy() {
+ RefPtr<BackgroundDataBridgeParent> self = this;
+ MOZ_ALWAYS_SUCCEEDS(mBackgroundThread->Dispatch(
+ NS_NewRunnableFunction("BackgroundDataBridgeParent::Destroy",
+ [self]() {
+ if (self->CanSend()) {
+ self->Close();
+ }
+ }),
+ NS_DISPATCH_NORMAL));
+}
+
+void BackgroundDataBridgeParent::OnStopRequest(
+ nsresult aStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const TimeStamp& aOnStopRequestStart) {
+ RefPtr<BackgroundDataBridgeParent> self = this;
+ MOZ_ALWAYS_SUCCEEDS(mBackgroundThread->Dispatch(
+ NS_NewRunnableFunction("BackgroundDataBridgeParent::OnStopRequest",
+ [self, aStatus, aTiming, aLastActiveTabOptHit,
+ aResponseTrailers, aOnStopRequestStart]() {
+ if (self->CanSend()) {
+ Unused << self->SendOnStopRequest(
+ aStatus, aTiming, aLastActiveTabOptHit,
+ aResponseTrailers, aOnStopRequestStart);
+ self->Close();
+ }
+ }),
+ NS_DISPATCH_NORMAL));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/BackgroundDataBridgeParent.h b/netwerk/protocol/http/BackgroundDataBridgeParent.h
new file mode 100644
index 0000000000..9bda9f3aa9
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundDataBridgeParent.h
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_BackgroundDataBridgeParent_h
+#define mozilla_net_BackgroundDataBridgeParent_h
+
+#include "mozilla/net/PBackgroundDataBridgeParent.h"
+
+namespace mozilla {
+namespace net {
+
+class BackgroundDataBridgeParent final : public PBackgroundDataBridgeParent {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BackgroundDataBridgeParent, override)
+
+ explicit BackgroundDataBridgeParent(uint64_t aChannelID);
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ already_AddRefed<nsISerialEventTarget> GetBackgroundThread();
+ void Destroy();
+ void OnStopRequest(nsresult aStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const TimeStamp& aOnStopRequestStart);
+
+ private:
+ virtual ~BackgroundDataBridgeParent() = default;
+
+ uint64_t mChannelID;
+ nsCOMPtr<nsISerialEventTarget> mBackgroundThread;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_BackgroundDataBridgeParent_h
diff --git a/netwerk/protocol/http/BinaryHttpRequest.cpp b/netwerk/protocol/http/BinaryHttpRequest.cpp
new file mode 100644
index 0000000000..fa28f4c5c9
--- /dev/null
+++ b/netwerk/protocol/http/BinaryHttpRequest.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BinaryHttpRequest.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(BinaryHttpRequest, nsIBinaryHttpRequest)
+
+NS_IMETHODIMP BinaryHttpRequest::GetMethod(nsACString& aMethod) {
+ aMethod.Assign(mMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BinaryHttpRequest::GetScheme(nsACString& aScheme) {
+ aScheme.Assign(mScheme);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BinaryHttpRequest::GetAuthority(nsACString& aAuthority) {
+ aAuthority.Assign(mAuthority);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BinaryHttpRequest::GetPath(nsACString& aPath) {
+ aPath.Assign(mPath);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BinaryHttpRequest::GetHeaderNames(
+ nsTArray<nsCString>& aHeaderNames) {
+ aHeaderNames.Assign(mHeaderNames);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BinaryHttpRequest::GetHeaderValues(
+ nsTArray<nsCString>& aHeaderValues) {
+ aHeaderValues.Assign(mHeaderValues);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BinaryHttpRequest::GetContent(nsTArray<uint8_t>& aContent) {
+ aContent.Assign(mContent);
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/BinaryHttpRequest.h b/netwerk/protocol/http/BinaryHttpRequest.h
new file mode 100644
index 0000000000..f8803f511c
--- /dev/null
+++ b/netwerk/protocol/http/BinaryHttpRequest.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_BinaryHttpRequest_h
+#define mozilla_net_BinaryHttpRequest_h
+
+#include "nsIBinaryHttp.h"
+#include "nsString.h"
+
+namespace mozilla::net {
+
+class BinaryHttpRequest final : public nsIBinaryHttpRequest {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIBINARYHTTPREQUEST
+
+ BinaryHttpRequest(const nsACString& aMethod, const nsACString& aScheme,
+ const nsACString& aAuthority, const nsACString& aPath,
+ nsTArray<nsCString>&& aHeaderNames,
+ nsTArray<nsCString>&& aHeaderValues,
+ nsTArray<uint8_t>&& aContent)
+ : mMethod(aMethod),
+ mScheme(aScheme),
+ mAuthority(aAuthority),
+ mPath(aPath),
+ mHeaderNames(std::move(aHeaderNames)),
+ mHeaderValues(std::move(aHeaderValues)),
+ mContent(std::move(aContent)) {}
+
+ private:
+ ~BinaryHttpRequest() = default;
+
+ const nsAutoCString mMethod;
+ const nsAutoCString mScheme;
+ const nsAutoCString mAuthority;
+ const nsAutoCString mPath;
+ const nsTArray<nsCString> mHeaderNames;
+ const nsTArray<nsCString> mHeaderValues;
+ const nsTArray<uint8_t> mContent;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_BinaryHttpRequest_h
diff --git a/netwerk/protocol/http/CacheControlParser.cpp b/netwerk/protocol/http/CacheControlParser.cpp
new file mode 100644
index 0000000000..79cdef5439
--- /dev/null
+++ b/netwerk/protocol/http/CacheControlParser.cpp
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheControlParser.h"
+
+namespace mozilla {
+namespace net {
+
+CacheControlParser::CacheControlParser(nsACString const& aHeader)
+ : Tokenizer(aHeader, nullptr, "-_"),
+ mMaxAgeSet(false),
+ mMaxAge(0),
+ mMaxStaleSet(false),
+ mMaxStale(0),
+ mMinFreshSet(false),
+ mMinFresh(0),
+ mStaleWhileRevalidateSet(false),
+ mStaleWhileRevalidate(0),
+ mNoCache(false),
+ mNoStore(false),
+ mPublic(false),
+ mPrivate(false),
+ mImmutable(false) {
+ SkipWhites();
+ if (!CheckEOF()) {
+ Directive();
+ }
+}
+
+void CacheControlParser::Directive() {
+ do {
+ SkipWhites();
+ if (CheckWord("no-cache")) {
+ mNoCache = true;
+ IgnoreDirective(); // ignore any optionally added values
+ } else if (CheckWord("no-store")) {
+ mNoStore = true;
+ } else if (CheckWord("max-age")) {
+ mMaxAgeSet = SecondsValue(&mMaxAge);
+ } else if (CheckWord("max-stale")) {
+ mMaxStaleSet = SecondsValue(&mMaxStale, PR_UINT32_MAX);
+ } else if (CheckWord("min-fresh")) {
+ mMinFreshSet = SecondsValue(&mMinFresh);
+ } else if (CheckWord("stale-while-revalidate")) {
+ mStaleWhileRevalidateSet = SecondsValue(&mStaleWhileRevalidate);
+ } else if (CheckWord("public")) {
+ mPublic = true;
+ } else if (CheckWord("private")) {
+ mPrivate = true;
+ } else if (CheckWord("immutable")) {
+ mImmutable = true;
+ } else {
+ IgnoreDirective();
+ }
+
+ SkipWhites();
+ if (CheckEOF()) {
+ return;
+ }
+
+ } while (CheckChar(','));
+
+ NS_WARNING("Unexpected input in Cache-control header value");
+}
+
+bool CacheControlParser::SecondsValue(uint32_t* seconds, uint32_t defaultVal) {
+ SkipWhites();
+ if (!CheckChar('=')) {
+ *seconds = defaultVal;
+ return !!defaultVal;
+ }
+
+ SkipWhites();
+ if (!ReadInteger(seconds)) {
+ NS_WARNING("Unexpected value in Cache-control header value");
+ return false;
+ }
+
+ return true;
+}
+
+void CacheControlParser::IgnoreDirective() {
+ Token t;
+ while (Next(t)) {
+ if (t.Equals(Token::Char(',')) || t.Equals(Token::EndOfFile())) {
+ Rollback();
+ break;
+ }
+ if (t.Equals(Token::Char('"'))) {
+ SkipUntil(Token::Char('"'));
+ if (!CheckChar('"')) {
+ NS_WARNING(
+ "Missing quoted string expansion in Cache-control header value");
+ break;
+ }
+ }
+ }
+}
+
+bool CacheControlParser::MaxAge(uint32_t* seconds) {
+ *seconds = mMaxAge;
+ return mMaxAgeSet;
+}
+
+bool CacheControlParser::MaxStale(uint32_t* seconds) {
+ *seconds = mMaxStale;
+ return mMaxStaleSet;
+}
+
+bool CacheControlParser::MinFresh(uint32_t* seconds) {
+ *seconds = mMinFresh;
+ return mMinFreshSet;
+}
+
+bool CacheControlParser::StaleWhileRevalidate(uint32_t* seconds) {
+ *seconds = mStaleWhileRevalidate;
+ return mStaleWhileRevalidateSet;
+}
+
+bool CacheControlParser::NoCache() { return mNoCache; }
+
+bool CacheControlParser::NoStore() { return mNoStore; }
+
+bool CacheControlParser::Public() { return mPublic; }
+
+bool CacheControlParser::Private() { return mPrivate; }
+
+bool CacheControlParser::Immutable() { return mImmutable; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/CacheControlParser.h b/netwerk/protocol/http/CacheControlParser.h
new file mode 100644
index 0000000000..6a6588be0c
--- /dev/null
+++ b/netwerk/protocol/http/CacheControlParser.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheControlParser_h__
+#define CacheControlParser_h__
+
+#include "mozilla/Tokenizer.h"
+
+namespace mozilla {
+namespace net {
+
+class CacheControlParser final : Tokenizer {
+ public:
+ explicit CacheControlParser(nsACString const& header);
+
+ [[nodiscard]] bool MaxAge(uint32_t* seconds);
+ [[nodiscard]] bool MaxStale(uint32_t* seconds);
+ [[nodiscard]] bool MinFresh(uint32_t* seconds);
+ [[nodiscard]] bool StaleWhileRevalidate(uint32_t* seconds);
+ bool NoCache();
+ bool NoStore();
+ bool Public();
+ bool Private();
+ bool Immutable();
+
+ private:
+ void Directive();
+ void IgnoreDirective();
+ [[nodiscard]] bool SecondsValue(uint32_t* seconds, uint32_t defaultVal = 0);
+
+ bool mMaxAgeSet;
+ uint32_t mMaxAge;
+ bool mMaxStaleSet;
+ uint32_t mMaxStale;
+ bool mMinFreshSet;
+ uint32_t mMinFresh;
+ bool mStaleWhileRevalidateSet;
+ uint32_t mStaleWhileRevalidate;
+ bool mNoCache;
+ bool mNoStore;
+ bool mPublic;
+ bool mPrivate;
+ bool mImmutable;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/CachePushChecker.cpp b/netwerk/protocol/http/CachePushChecker.cpp
new file mode 100644
index 0000000000..9f48aa7eda
--- /dev/null
+++ b/netwerk/protocol/http/CachePushChecker.cpp
@@ -0,0 +1,248 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CachePushChecker.h"
+
+#include "LoadContextInfo.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "nsICacheEntry.h"
+#include "nsICacheStorageService.h"
+#include "nsICacheStorage.h"
+#include "nsThreadUtils.h"
+#include "CacheControlParser.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(CachePushChecker, nsICacheEntryOpenCallback);
+
+CachePushChecker::CachePushChecker(nsIURI* aPushedURL,
+ const OriginAttributes& aOriginAttributes,
+ const nsACString& aRequestString,
+ std::function<void(bool)>&& aCallback)
+ : mPushedURL(aPushedURL),
+ mOriginAttributes(aOriginAttributes),
+ mRequestString(aRequestString),
+ mCallback(std::move(aCallback)),
+ mCurrentEventTarget(GetCurrentSerialEventTarget()) {}
+
+nsresult CachePushChecker::DoCheck() {
+ if (XRE_IsSocketProcess()) {
+ RefPtr<CachePushChecker> self = this;
+ return NS_DispatchToMainThread(
+ NS_NewRunnableFunction(
+ "CachePushChecker::DoCheck",
+ [self]() {
+ if (SocketProcessChild* child =
+ SocketProcessChild::GetSingleton()) {
+ child
+ ->SendCachePushCheck(self->mPushedURL,
+ self->mOriginAttributes,
+ self->mRequestString)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self](bool aResult) { self->InvokeCallback(aResult); },
+ [](const mozilla::ipc::ResponseRejectReason) {});
+ }
+ }),
+ NS_DISPATCH_NORMAL);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsICacheStorageService> css =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<LoadContextInfo> lci = GetLoadContextInfo(false, mOriginAttributes);
+ nsCOMPtr<nsICacheStorage> ds;
+ rv = css->DiskCacheStorage(lci, getter_AddRefs(ds));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return ds->AsyncOpenURI(
+ mPushedURL, ""_ns,
+ nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY, this);
+}
+
+NS_IMETHODIMP
+CachePushChecker::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // We never care to fully open the entry, since we won't actually use it.
+ // We just want to be able to do all our checks to see if a future channel can
+ // use this entry, or if we need to accept the push.
+ *result = nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
+
+ bool isForcedValid = false;
+ entry->GetIsForcedValid(&isForcedValid);
+
+ nsHttpRequestHead requestHead;
+ requestHead.ParseHeaderSet(mRequestString.BeginReading());
+ nsHttpResponseHead cachedResponseHead;
+ bool acceptPush = true;
+ auto onExitGuard = MakeScopeExit([&] { InvokeCallback(acceptPush); });
+
+ nsresult rv =
+ nsHttp::GetHttpResponseHeadFromCacheEntry(entry, &cachedResponseHead);
+ if (NS_FAILED(rv)) {
+ // Couldn't make sense of what's in the cache entry, go ahead and accept
+ // the push.
+ return NS_OK;
+ }
+
+ if ((cachedResponseHead.Status() / 100) != 2) {
+ // Assume the push is sending us a success, while we don't have one in the
+ // cache, so we'll accept the push.
+ return NS_OK;
+ }
+
+ // Get the method that was used to generate the cached response
+ nsCString buf;
+ rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
+ if (NS_FAILED(rv)) {
+ // Can't check request method, accept the push
+ return NS_OK;
+ }
+ nsAutoCString pushedMethod;
+ requestHead.Method(pushedMethod);
+ if (!buf.Equals(pushedMethod)) {
+ // Methods don't match, accept the push
+ return NS_OK;
+ }
+
+ int64_t size, contentLength;
+ rv = nsHttp::CheckPartial(entry, &size, &contentLength, &cachedResponseHead);
+ if (NS_FAILED(rv)) {
+ // Couldn't figure out if this was partial or not, accept the push.
+ return NS_OK;
+ }
+
+ if (size == int64_t(-1) || contentLength != size) {
+ // This is partial content in the cache, accept the push.
+ return NS_OK;
+ }
+
+ nsAutoCString requestedETag;
+ if (NS_FAILED(requestHead.GetHeader(nsHttp::If_Match, requestedETag))) {
+ // Can't check etag
+ return NS_OK;
+ }
+ if (!requestedETag.IsEmpty()) {
+ nsAutoCString cachedETag;
+ if (NS_FAILED(cachedResponseHead.GetHeader(nsHttp::ETag, cachedETag))) {
+ // Can't check etag
+ return NS_OK;
+ }
+ if (!requestedETag.Equals(cachedETag)) {
+ // ETags don't match, accept the push.
+ return NS_OK;
+ }
+ }
+
+ nsAutoCString imsString;
+ Unused << requestHead.GetHeader(nsHttp::If_Modified_Since, imsString);
+ if (!buf.IsEmpty()) {
+ uint32_t ims = buf.ToInteger(&rv);
+ uint32_t lm;
+ rv = cachedResponseHead.GetLastModifiedValue(&lm);
+ if (NS_SUCCEEDED(rv) && lm && lm < ims) {
+ // The push appears to be newer than what's in our cache, accept it.
+ return NS_OK;
+ }
+ }
+
+ nsAutoCString cacheControlRequestHeader;
+ Unused << requestHead.GetHeader(nsHttp::Cache_Control,
+ cacheControlRequestHeader);
+ CacheControlParser cacheControlRequest(cacheControlRequestHeader);
+ if (cacheControlRequest.NoStore()) {
+ // Don't use a no-store cache entry, accept the push.
+ return NS_OK;
+ }
+
+ nsCString cachedAuth;
+ rv = entry->GetMetaDataElement("auth", getter_Copies(cachedAuth));
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t lastModifiedTime;
+ rv = entry->GetLastModified(&lastModifiedTime);
+ if (NS_SUCCEEDED(rv)) {
+ if ((gHttpHandler->SessionStartTime() > lastModifiedTime) &&
+ !cachedAuth.IsEmpty()) {
+ // Need to revalidate this, as the auth is old. Accept the push.
+ return NS_OK;
+ }
+
+ if (cachedAuth.IsEmpty() &&
+ requestHead.HasHeader(nsHttp::Authorization)) {
+ // They're pushing us something with auth, but we didn't cache anything
+ // with auth. Accept the push.
+ return NS_OK;
+ }
+ }
+ }
+
+ bool weaklyFramed, isImmutable;
+ nsHttp::DetermineFramingAndImmutability(entry, &cachedResponseHead, true,
+ &weaklyFramed, &isImmutable);
+
+ // We'll need this value in later computations...
+ uint32_t lastModifiedTime;
+ rv = entry->GetLastModified(&lastModifiedTime);
+ if (NS_FAILED(rv)) {
+ // Ugh, this really sucks. OK, accept the push.
+ return NS_OK;
+ }
+
+ // Determine if this is the first time that this cache entry
+ // has been accessed during this session.
+ bool fromPreviousSession =
+ (gHttpHandler->SessionStartTime() > lastModifiedTime);
+
+ bool validationRequired = nsHttp::ValidationRequired(
+ isForcedValid, &cachedResponseHead, 0 /*NWGH: ??? - loadFlags*/, false,
+ false /* forceValidateCacheContent */, isImmutable, false, requestHead,
+ entry, cacheControlRequest, fromPreviousSession);
+
+ if (validationRequired) {
+ // A real channel would most likely hit the net at this point, so let's
+ // accept the push.
+ return NS_OK;
+ }
+
+ // If we get here, then we would be able to use this cache entry. Cancel the
+ // push so as not to waste any more bandwidth.
+ acceptPush = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CachePushChecker::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
+ nsresult result) {
+ // Nothing to do here, all the work is in OnCacheEntryCheck.
+ return NS_OK;
+}
+
+void CachePushChecker::InvokeCallback(bool aResult) {
+ RefPtr<CachePushChecker> self = this;
+ auto task = [self, aResult]() { self->mCallback(aResult); };
+ if (!mCurrentEventTarget->IsOnCurrentThread()) {
+ mCurrentEventTarget->Dispatch(
+ NS_NewRunnableFunction("CachePushChecker::InvokeCallback",
+ std::move(task)),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ task();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/CachePushChecker.h b/netwerk/protocol/http/CachePushChecker.h
new file mode 100644
index 0000000000..72855443a0
--- /dev/null
+++ b/netwerk/protocol/http/CachePushChecker.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CachePushChecker_h__
+#define CachePushChecker_h__
+
+#include <functional>
+#include "mozilla/OriginAttributes.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsIEventTarget.h"
+#include "nsString.h"
+#include "nsIURI.h"
+
+namespace mozilla {
+namespace net {
+
+class CachePushChecker final : public nsICacheEntryOpenCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+
+ explicit CachePushChecker(nsIURI* aPushedURL,
+ const OriginAttributes& aOriginAttributes,
+ const nsACString& aRequestString,
+ std::function<void(bool)>&& aCallback);
+ nsresult DoCheck();
+
+ private:
+ ~CachePushChecker() = default;
+ void InvokeCallback(bool aResult);
+
+ nsCOMPtr<nsIURI> mPushedURL;
+ OriginAttributes mOriginAttributes;
+ nsCString mRequestString;
+ std::function<void(bool)> mCallback;
+ nsCOMPtr<nsIEventTarget> mCurrentEventTarget;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/ClassOfService.h b/netwerk/protocol/http/ClassOfService.h
new file mode 100644
index 0000000000..bf971d41df
--- /dev/null
+++ b/netwerk/protocol/http/ClassOfService.h
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __ClassOfService_h__
+#define __ClassOfService_h__
+
+#include "nsIClassOfService.h"
+#include "nsPrintfCString.h"
+#include "ipc/IPCMessageUtils.h"
+
+namespace mozilla::net {
+
+class ClassOfService {
+ public:
+ ClassOfService() : mClassFlags(0), mIncremental(false) {}
+ ClassOfService(unsigned long flags, bool incremental)
+ : mClassFlags(flags), mIncremental(incremental) {}
+
+ // class flags (priority)
+ unsigned long Flags() const { return mClassFlags; }
+ void SetFlags(unsigned long flags) { mClassFlags = flags; }
+
+ // incremental flags
+ bool Incremental() const { return mIncremental; }
+ void SetIncremental(bool incremental) { mIncremental = incremental; }
+
+ static void ToString(const ClassOfService aCos, nsACString& aOut) {
+ return ToString(aCos.Flags(), aOut);
+ }
+
+ static void ToString(unsigned long aFlags, nsACString& aOut) {
+ aOut = nsPrintfCString("%lX", aFlags);
+ }
+
+ private:
+ unsigned long mClassFlags;
+ bool mIncremental;
+ friend IPC::ParamTraits<mozilla::net::ClassOfService>;
+ friend bool operator==(const ClassOfService& lhs, const ClassOfService& rhs);
+ friend bool operator!=(const ClassOfService& lhs, const ClassOfService& rhs);
+};
+
+inline bool operator==(const ClassOfService& lhs, const ClassOfService& rhs) {
+ return lhs.mClassFlags == rhs.mClassFlags &&
+ lhs.mIncremental == rhs.mIncremental;
+}
+
+inline bool operator!=(const ClassOfService& lhs, const ClassOfService& rhs) {
+ return !(lhs == rhs);
+}
+
+} // namespace mozilla::net
+
+namespace IPC {
+template <>
+struct ParamTraits<mozilla::net::ClassOfService> {
+ typedef mozilla::net::ClassOfService paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mClassFlags);
+ WriteParam(aWriter, aParam.mIncremental);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mClassFlags) ||
+ !ReadParam(aReader, &aResult->mIncremental))
+ return false;
+
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#endif
diff --git a/netwerk/protocol/http/ConnectionDiagnostics.cpp b/netwerk/protocol/http/ConnectionDiagnostics.cpp
new file mode 100644
index 0000000000..aa1358538d
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionDiagnostics.cpp
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpConnectionMgr.h"
+#include "nsHttpConnection.h"
+#include "HttpConnectionUDP.h"
+#include "Http2Session.h"
+#include "nsHttpHandler.h"
+#include "nsIConsoleService.h"
+#include "nsHttpRequestHead.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransportService2.h"
+
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/StaticPrefs_network.h"
+
+namespace mozilla {
+namespace net {
+
+void nsHttpConnectionMgr::PrintDiagnostics() {
+ nsresult rv =
+ PostEvent(&nsHttpConnectionMgr::OnMsgPrintDiagnostics, 0, nullptr);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnectionMgr::PrintDiagnostics\n"
+ " failed to post OnMsgPrintDiagnostics event"));
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgPrintDiagnostics(int32_t, ARefBase*) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (!consoleService) return;
+
+ mLogData.AppendPrintf("HTTP Connection Diagnostics\n---------------------\n");
+ mLogData.AppendPrintf("IsSpdyEnabled() = %d\n",
+ StaticPrefs::network_http_http2_enabled());
+ mLogData.AppendPrintf("MaxSocketCount() = %d\n",
+ gHttpHandler->MaxSocketCount());
+ mLogData.AppendPrintf("mNumActiveConns = %d\n", mNumActiveConns);
+ mLogData.AppendPrintf("mNumIdleConns = %d\n", mNumIdleConns);
+
+ for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) {
+ mLogData.AppendPrintf(
+ " AtActiveConnectionLimit = %d\n",
+ AtActiveConnectionLimit(ent, NS_HTTP_ALLOW_KEEPALIVE));
+
+ ent->PrintDiagnostics(mLogData, MaxPersistConnections(ent));
+ }
+
+ consoleService->LogStringMessage(NS_ConvertUTF8toUTF16(mLogData).Data());
+ mLogData.Truncate();
+}
+
+void ConnectionEntry::PrintDiagnostics(nsCString& log,
+ uint32_t aMaxPersistConns) {
+ log.AppendPrintf(" ent host = %s hashkey = %s\n", mConnInfo->Origin(),
+ mConnInfo->HashKey().get());
+
+ log.AppendPrintf(" RestrictConnections = %d\n", RestrictConnections());
+ log.AppendPrintf(" Pending Q Length = %zu\n", PendingQueueLength());
+ log.AppendPrintf(" Active Conns Length = %zu\n", mActiveConns.Length());
+ log.AppendPrintf(" Idle Conns Length = %zu\n", mIdleConns.Length());
+ log.AppendPrintf(" DnsAndSock Length = %zu\n",
+ mDnsAndConnectSockets.Length());
+ log.AppendPrintf(" Coalescing Keys Length = %zu\n",
+ mCoalescingKeys.Length());
+ log.AppendPrintf(" Spdy using = %d\n", mUsingSpdy);
+
+ uint32_t i;
+ for (i = 0; i < mActiveConns.Length(); ++i) {
+ log.AppendPrintf(" :: Active Connection #%u\n", i);
+ mActiveConns[i]->PrintDiagnostics(log);
+ }
+ for (i = 0; i < mIdleConns.Length(); ++i) {
+ log.AppendPrintf(" :: Idle Connection #%u\n", i);
+ mIdleConns[i]->PrintDiagnostics(log);
+ }
+ for (i = 0; i < mDnsAndConnectSockets.Length(); ++i) {
+ log.AppendPrintf(" :: Half Open #%u\n", i);
+ mDnsAndConnectSockets[i]->PrintDiagnostics(log);
+ }
+
+ mPendingQ.PrintDiagnostics(log);
+
+ for (i = 0; i < mCoalescingKeys.Length(); ++i) {
+ log.AppendPrintf(" :: Coalescing Key #%u %s\n", i,
+ mCoalescingKeys[i].get());
+ }
+}
+
+void PendingTransactionQueue::PrintDiagnostics(nsCString& log) {
+ uint32_t i = 0;
+ for (const auto& entry : mPendingTransactionTable) {
+ log.AppendPrintf(" :: Pending Transactions with Window ID = %" PRIu64
+ "\n",
+ entry.GetKey());
+ for (uint32_t j = 0; j < entry.GetData()->Length(); ++j) {
+ log.AppendPrintf(" ::: Pending Transaction #%u\n", i);
+ entry.GetData()->ElementAt(j)->PrintDiagnostics(log);
+ ++i;
+ }
+ }
+}
+
+void DnsAndConnectSocket::PrintDiagnostics(nsCString& log) {
+ log.AppendPrintf(" has connected = %d, isSpeculative = %d\n",
+ HasConnected(), IsSpeculative());
+
+ TimeStamp now = TimeStamp::Now();
+
+ if (mPrimaryTransport.mSynStarted.IsNull()) {
+ log.AppendPrintf(" primary not started\n");
+ } else {
+ log.AppendPrintf(" primary started %.2fms ago\n",
+ (now - mPrimaryTransport.mSynStarted).ToMilliseconds());
+ }
+
+ if (mBackupTransport.mSynStarted.IsNull()) {
+ log.AppendPrintf(" backup not started\n");
+ } else {
+ log.AppendPrintf(" backup started %.2f ago\n",
+ (now - mBackupTransport.mSynStarted).ToMilliseconds());
+ }
+
+ log.AppendPrintf(" primary transport %d, backup transport %d\n",
+ !!mPrimaryTransport.mSocketTransport,
+ !!mBackupTransport.mSocketTransport);
+}
+
+void nsHttpConnection::PrintDiagnostics(nsCString& log) {
+ log.AppendPrintf(" CanDirectlyActivate = %d\n", CanDirectlyActivate());
+
+ log.AppendPrintf(" npncomplete = %d setupSSLCalled = %d\n",
+ mTlsHandshaker->NPNComplete(),
+ mTlsHandshaker->SetupSSLCalled());
+
+ log.AppendPrintf(" spdyVersion = %d reportedSpdy = %d everspdy = %d\n",
+ static_cast<int32_t>(mUsingSpdyVersion), mReportedSpdy,
+ mEverUsedSpdy);
+
+ log.AppendPrintf(" iskeepalive = %d dontReuse = %d isReused = %d\n",
+ IsKeepAlive(), mDontReuse, mIsReused);
+
+ log.AppendPrintf(" mTransaction = %d mSpdySession = %d\n", !!mTransaction,
+ !!mSpdySession);
+
+ PRIntervalTime now = PR_IntervalNow();
+ log.AppendPrintf(" time since last read = %ums\n",
+ PR_IntervalToMilliseconds(now - mLastReadTime));
+
+ log.AppendPrintf(" max-read/read/written %" PRId64 "/%" PRId64 "/%" PRId64
+ "\n",
+ mMaxBytesRead, mTotalBytesRead, mTotalBytesWritten);
+
+ log.AppendPrintf(" rtt = %ums\n", PR_IntervalToMilliseconds(mRtt));
+
+ log.AppendPrintf(" idlemonitoring = %d transactionCount=%d\n",
+ mIdleMonitoring, mHttp1xTransactionCount);
+
+ if (mSpdySession) mSpdySession->PrintDiagnostics(log);
+}
+
+void HttpConnectionUDP::PrintDiagnostics(nsCString& log) {
+ log.AppendPrintf(" CanDirectlyActivate = %d\n", CanDirectlyActivate());
+
+ log.AppendPrintf(" dontReuse = %d isReused = %d\n", mDontReuse, mIsReused);
+
+ log.AppendPrintf(" read/written %" PRId64 "/%" PRId64 "\n",
+ mHttp3Session ? mHttp3Session->BytesRead() : 0,
+ mHttp3Session ? mHttp3Session->GetBytesWritten() : 0);
+
+ log.AppendPrintf(" rtt = %ums\n", PR_IntervalToMilliseconds(mRtt));
+}
+
+void Http2Session::PrintDiagnostics(nsCString& log) {
+ log.AppendPrintf(" ::: HTTP2\n");
+ log.AppendPrintf(
+ " shouldgoaway = %d mClosed = %d CanReuse = %d nextID=0x%X\n",
+ mShouldGoAway, mClosed, CanReuse(), mNextStreamID);
+
+ log.AppendPrintf(" concurrent = %d maxconcurrent = %d\n", mConcurrent,
+ mMaxConcurrent);
+
+ log.AppendPrintf(" roomformorestreams = %d roomformoreconcurrent = %d\n",
+ RoomForMoreStreams(), RoomForMoreConcurrent());
+
+ log.AppendPrintf(" transactionHashCount = %d streamIDHashCount = %d\n",
+ mStreamTransactionHash.Count(), mStreamIDHash.Count());
+
+ log.AppendPrintf(" Queued Stream Size = %zu\n", mQueuedStreams.Length());
+
+ PRIntervalTime now = PR_IntervalNow();
+ log.AppendPrintf(" Ping Threshold = %ums\n",
+ PR_IntervalToMilliseconds(mPingThreshold));
+ log.AppendPrintf(" Ping Timeout = %ums\n",
+ PR_IntervalToMilliseconds(gHttpHandler->SpdyPingTimeout()));
+ log.AppendPrintf(" Idle for Any Activity (ping) = %ums\n",
+ PR_IntervalToMilliseconds(now - mLastReadEpoch));
+ log.AppendPrintf(" Idle for Data Activity = %ums\n",
+ PR_IntervalToMilliseconds(now - mLastDataReadEpoch));
+ if (mPingSentEpoch) {
+ log.AppendPrintf(" Ping Outstanding (ping) = %ums, expired = %d\n",
+ PR_IntervalToMilliseconds(now - mPingSentEpoch),
+ now - mPingSentEpoch >= gHttpHandler->SpdyPingTimeout());
+ } else {
+ log.AppendPrintf(" No Ping Outstanding\n");
+ }
+}
+
+void nsHttpTransaction::PrintDiagnostics(nsCString& log) {
+ if (!mRequestHead) return;
+
+ nsAutoCString requestURI;
+ mRequestHead->RequestURI(requestURI);
+ log.AppendPrintf(" :::: uri = %s\n", requestURI.get());
+ log.AppendPrintf(" caps = 0x%x\n", static_cast<uint32_t>(mCaps));
+ log.AppendPrintf(" priority = %d\n", mPriority);
+ log.AppendPrintf(" restart count = %u\n", mRestartCount);
+}
+
+void PendingTransactionInfo::PrintDiagnostics(nsCString& log) {
+ log.AppendPrintf(" ::: Pending transaction\n");
+ mTransaction->PrintDiagnostics(log);
+ RefPtr<DnsAndConnectSocket> dnsAndSock = do_QueryReferent(mDnsAndSock);
+ log.AppendPrintf(" Waiting for half open sock: %p or connection: %p\n",
+ dnsAndSock.get(), mActiveConn.get());
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ConnectionEntry.cpp b/netwerk/protocol/http/ConnectionEntry.cpp
new file mode 100644
index 0000000000..473f0ab9fc
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionEntry.cpp
@@ -0,0 +1,1102 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "ConnectionEntry.h"
+#include "nsQueryObject.h"
+#include "mozilla/ChaosMode.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+// ConnectionEntry
+ConnectionEntry::~ConnectionEntry() {
+ LOG(("ConnectionEntry::~ConnectionEntry this=%p", this));
+
+ MOZ_ASSERT(!mIdleConns.Length());
+ MOZ_ASSERT(!mActiveConns.Length());
+ MOZ_DIAGNOSTIC_ASSERT(!mDnsAndConnectSockets.Length());
+ MOZ_ASSERT(!PendingQueueLength());
+ MOZ_ASSERT(!UrgentStartQueueLength());
+ MOZ_ASSERT(!mDoNotDestroy);
+}
+
+ConnectionEntry::ConnectionEntry(nsHttpConnectionInfo* ci)
+ : mConnInfo(ci),
+ mUsingSpdy(false),
+ mCanUseSpdy(true),
+ mPreferIPv4(false),
+ mPreferIPv6(false),
+ mUsedForConnection(false),
+ mDoNotDestroy(false) {
+ LOG(("ConnectionEntry::ConnectionEntry this=%p key=%s", this,
+ ci->HashKey().get()));
+}
+
+bool ConnectionEntry::AvailableForDispatchNow() {
+ if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) {
+ return true;
+ }
+
+ return gHttpHandler->ConnMgr()->GetH2orH3ActiveConn(this, false, false) !=
+ nullptr;
+}
+
+uint32_t ConnectionEntry::UnconnectedDnsAndConnectSockets() const {
+ uint32_t unconnectedDnsAndConnectSockets = 0;
+ for (uint32_t i = 0; i < mDnsAndConnectSockets.Length(); ++i) {
+ if (!mDnsAndConnectSockets[i]->HasConnected()) {
+ ++unconnectedDnsAndConnectSockets;
+ }
+ }
+ return unconnectedDnsAndConnectSockets;
+}
+
+void ConnectionEntry::InsertIntoDnsAndConnectSockets(
+ DnsAndConnectSocket* sock) {
+ mDnsAndConnectSockets.AppendElement(sock);
+ gHttpHandler->ConnMgr()->IncreaseNumDnsAndConnectSockets();
+}
+
+void ConnectionEntry::RemoveDnsAndConnectSocket(DnsAndConnectSocket* dnsAndSock,
+ bool abandon) {
+ if (abandon) {
+ dnsAndSock->Abandon();
+ }
+ if (mDnsAndConnectSockets.RemoveElement(dnsAndSock)) {
+ gHttpHandler->ConnMgr()->DecreaseNumDnsAndConnectSockets();
+ }
+
+ if (!UnconnectedDnsAndConnectSockets()) {
+ // perhaps this reverted RestrictConnections()
+ // use the PostEvent version of processpendingq to avoid
+ // altering the pending q vector from an arbitrary stack
+ nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ConnectionEntry::RemoveDnsAndConnectSocket\n"
+ " failed to process pending queue\n"));
+ }
+ }
+}
+
+void ConnectionEntry::CloseAllDnsAndConnectSockets() {
+ for (const auto& dnsAndSock : mDnsAndConnectSockets) {
+ dnsAndSock->Abandon();
+ gHttpHandler->ConnMgr()->DecreaseNumDnsAndConnectSockets();
+ }
+ mDnsAndConnectSockets.Clear();
+ nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ConnectionEntry::CloseAllDnsAndConnectSockets\n"
+ " failed to process pending queue\n"));
+ }
+}
+
+void ConnectionEntry::DisallowHttp2() {
+ mCanUseSpdy = false;
+
+ // If we have any spdy connections, we want to go ahead and close them when
+ // they're done so we can free up some connections.
+ for (uint32_t i = 0; i < mActiveConns.Length(); ++i) {
+ if (mActiveConns[i]->UsingSpdy()) {
+ mActiveConns[i]->DontReuse();
+ }
+ }
+ for (uint32_t i = 0; i < mIdleConns.Length(); ++i) {
+ if (mIdleConns[i]->UsingSpdy()) {
+ mIdleConns[i]->DontReuse();
+ }
+ }
+
+ // Can't coalesce if we're not using spdy
+ mCoalescingKeys.Clear();
+}
+
+void ConnectionEntry::DontReuseHttp3Conn() {
+ MOZ_ASSERT(mConnInfo->IsHttp3());
+
+ // If we have any spdy connections, we want to go ahead and close them when
+ // they're done so we can free up some connections.
+ for (uint32_t i = 0; i < mActiveConns.Length(); ++i) {
+ mActiveConns[i]->DontReuse();
+ }
+
+ // Can't coalesce if we're not using http3
+ mCoalescingKeys.Clear();
+}
+
+void ConnectionEntry::RecordIPFamilyPreference(uint16_t family) {
+ LOG(("ConnectionEntry::RecordIPFamilyPreference %p, af=%u", this, family));
+
+ if (family == PR_AF_INET && !mPreferIPv6) {
+ mPreferIPv4 = true;
+ }
+
+ if (family == PR_AF_INET6 && !mPreferIPv4) {
+ mPreferIPv6 = true;
+ }
+
+ LOG((" %p prefer ipv4=%d, ipv6=%d", this, (bool)mPreferIPv4,
+ (bool)mPreferIPv6));
+}
+
+void ConnectionEntry::ResetIPFamilyPreference() {
+ LOG(("ConnectionEntry::ResetIPFamilyPreference %p", this));
+
+ mPreferIPv4 = false;
+ mPreferIPv6 = false;
+}
+
+bool net::ConnectionEntry::PreferenceKnown() const {
+ return (bool)mPreferIPv4 || (bool)mPreferIPv6;
+}
+
+size_t ConnectionEntry::PendingQueueLength() const {
+ return mPendingQ.PendingQueueLength();
+}
+
+size_t ConnectionEntry::PendingQueueLengthForWindow(uint64_t windowId) const {
+ return mPendingQ.PendingQueueLengthForWindow(windowId);
+}
+
+void ConnectionEntry::AppendPendingUrgentStartQ(
+ nsTArray<RefPtr<PendingTransactionInfo>>& result) {
+ mPendingQ.AppendPendingUrgentStartQ(result);
+}
+
+void ConnectionEntry::AppendPendingQForFocusedWindow(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount) {
+ mPendingQ.AppendPendingQForFocusedWindow(windowId, result, maxCount);
+ LOG(
+ ("ConnectionEntry::AppendPendingQForFocusedWindow [ci=%s], "
+ "pendingQ count=%zu for focused window (id=%" PRIu64 ")\n",
+ mConnInfo->HashKey().get(), result.Length(), windowId));
+}
+
+void ConnectionEntry::AppendPendingQForNonFocusedWindows(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount) {
+ mPendingQ.AppendPendingQForNonFocusedWindows(windowId, result, maxCount);
+ LOG(
+ ("ConnectionEntry::AppendPendingQForNonFocusedWindows [ci=%s], "
+ "pendingQ count=%zu for non focused window\n",
+ mConnInfo->HashKey().get(), result.Length()));
+}
+
+void ConnectionEntry::RemoveEmptyPendingQ() { mPendingQ.RemoveEmptyPendingQ(); }
+
+void ConnectionEntry::InsertTransactionSorted(
+ nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority /*= false*/) {
+ mPendingQ.InsertTransactionSorted(pendingQ, pendingTransInfo,
+ aInsertAsFirstForTheSamePriority);
+}
+
+void ConnectionEntry::ReschedTransaction(nsHttpTransaction* aTrans) {
+ mPendingQ.ReschedTransaction(aTrans);
+}
+
+void ConnectionEntry::InsertTransaction(
+ PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority /* = false */) {
+ mPendingQ.InsertTransaction(pendingTransInfo,
+ aInsertAsFirstForTheSamePriority);
+ pendingTransInfo->Transaction()->OnPendingQueueInserted(mConnInfo->HashKey());
+}
+
+nsTArray<RefPtr<PendingTransactionInfo>>*
+ConnectionEntry::GetTransactionPendingQHelper(nsAHttpTransaction* trans) {
+ return mPendingQ.GetTransactionPendingQHelper(trans);
+}
+
+bool ConnectionEntry::RestrictConnections() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (AvailableForDispatchNow()) {
+ // this might be a h2/spdy connection in this connection entry that
+ // is able to be immediately muxxed, or it might be one that
+ // was found in the same state through a coalescing hash
+ LOG(
+ ("ConnectionEntry::RestrictConnections %p %s restricted due to "
+ "active >=h2\n",
+ this, mConnInfo->HashKey().get()));
+ return true;
+ }
+
+ // If this host is trying to negotiate a SPDY session right now,
+ // don't create any new ssl connections until the result of the
+ // negotiation is known.
+
+ bool doRestrict = mConnInfo->FirstHopSSL() &&
+ StaticPrefs::network_http_http2_enabled() && mUsingSpdy &&
+ (mDnsAndConnectSockets.Length() || mActiveConns.Length());
+
+ // If there are no restrictions, we are done
+ if (!doRestrict) {
+ return false;
+ }
+
+ // If the restriction is based on a tcp handshake in progress
+ // let that connect and then see if it was SPDY or not
+ if (UnconnectedDnsAndConnectSockets()) {
+ return true;
+ }
+
+ // There is a concern that a host is using a mix of HTTP/1 and SPDY.
+ // In that case we don't want to restrict connections just because
+ // there is a single active HTTP/1 session in use.
+ if (mUsingSpdy && mActiveConns.Length()) {
+ bool confirmedRestrict = false;
+ for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
+ HttpConnectionBase* conn = mActiveConns[index];
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ if ((connTCP && !connTCP->ReportedNPN()) || conn->CanDirectlyActivate()) {
+ confirmedRestrict = true;
+ break;
+ }
+ }
+ doRestrict = confirmedRestrict;
+ if (!confirmedRestrict) {
+ LOG(
+ ("nsHttpConnectionMgr spdy connection restriction to "
+ "%s bypassed.\n",
+ mConnInfo->Origin()));
+ }
+ }
+ return doRestrict;
+}
+
+uint32_t ConnectionEntry::TotalActiveConnections() const {
+ // Add in the in-progress tcp connections, we will assume they are
+ // keepalive enabled.
+ // Exclude DnsAndConnectSocket's that has already created a usable connection.
+ // This prevents the limit being stuck on ipv6 connections that
+ // eventually time out after typical 21 seconds of no ACK+SYN reply.
+ return mActiveConns.Length() + UnconnectedDnsAndConnectSockets();
+}
+
+size_t ConnectionEntry::UrgentStartQueueLength() {
+ return mPendingQ.UrgentStartQueueLength();
+}
+
+void ConnectionEntry::PrintPendingQ() { mPendingQ.PrintPendingQ(); }
+
+void ConnectionEntry::Compact() {
+ mIdleConns.Compact();
+ mActiveConns.Compact();
+ mPendingQ.Compact();
+}
+
+void ConnectionEntry::RemoveFromIdleConnectionsIndex(size_t inx) {
+ mIdleConns.RemoveElementAt(inx);
+ gHttpHandler->ConnMgr()->DecrementNumIdleConns();
+}
+
+bool ConnectionEntry::RemoveFromIdleConnections(nsHttpConnection* conn) {
+ if (!mIdleConns.RemoveElement(conn)) {
+ return false;
+ }
+
+ gHttpHandler->ConnMgr()->DecrementNumIdleConns();
+ return true;
+}
+
+void ConnectionEntry::CancelAllTransactions(nsresult reason) {
+ mPendingQ.CancelAllTransactions(reason);
+}
+
+nsresult ConnectionEntry::CloseIdleConnection(nsHttpConnection* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ RefPtr<nsHttpConnection> deleteProtector(conn);
+ if (!RemoveFromIdleConnections(conn)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // The connection is closed immediately no need to call EndIdleMonitoring.
+ conn->Close(NS_ERROR_ABORT);
+ return NS_OK;
+}
+
+void ConnectionEntry::CloseIdleConnections() {
+ while (mIdleConns.Length()) {
+ RefPtr<nsHttpConnection> conn(mIdleConns[0]);
+ RemoveFromIdleConnectionsIndex(0);
+ // The connection is closed immediately no need to call EndIdleMonitoring.
+ conn->Close(NS_ERROR_ABORT);
+ }
+}
+
+void ConnectionEntry::CloseIdleConnections(uint32_t maxToClose) {
+ uint32_t closed = 0;
+ while (mIdleConns.Length() && (closed < maxToClose)) {
+ RefPtr<nsHttpConnection> conn(mIdleConns[0]);
+ RemoveFromIdleConnectionsIndex(0);
+ // The connection is closed immediately no need to call EndIdleMonitoring.
+ conn->Close(NS_ERROR_ABORT);
+ closed++;
+ }
+}
+
+void ConnectionEntry::CloseH2WebsocketConnections() {
+ while (mH2WebsocketConns.Length()) {
+ RefPtr<HttpConnectionBase> conn(mH2WebsocketConns[0]);
+ mH2WebsocketConns.RemoveElementAt(0);
+
+ // safe to close connection since we are on the socket thread
+ // closing via transaction to break connection/transaction bond
+ conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
+ }
+}
+
+nsresult ConnectionEntry::RemoveIdleConnection(nsHttpConnection* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!RemoveFromIdleConnections(conn)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ conn->EndIdleMonitoring();
+ return NS_OK;
+}
+
+bool ConnectionEntry::IsInIdleConnections(HttpConnectionBase* conn) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ return connTCP && mIdleConns.Contains(connTCP);
+}
+
+already_AddRefed<nsHttpConnection> ConnectionEntry::GetIdleConnection(
+ bool respectUrgency, bool urgentTrans, bool* onlyUrgent) {
+ RefPtr<nsHttpConnection> conn;
+ size_t index = 0;
+ while (!conn && (mIdleConns.Length() > index)) {
+ conn = mIdleConns[index];
+
+ if (!conn->CanReuse()) {
+ RemoveFromIdleConnectionsIndex(index);
+ LOG((" dropping stale connection: [conn=%p]\n", conn.get()));
+ conn->Close(NS_ERROR_ABORT);
+ conn = nullptr;
+ continue;
+ }
+
+ // non-urgent transactions can only be dispatched on non-urgent
+ // started or used connections.
+ if (respectUrgency && conn->IsUrgentStartPreferred() && !urgentTrans) {
+ LOG((" skipping urgent: [conn=%p]", conn.get()));
+ conn = nullptr;
+ ++index;
+ continue;
+ }
+
+ *onlyUrgent = false;
+
+ RemoveFromIdleConnectionsIndex(index);
+ conn->EndIdleMonitoring();
+ LOG((" reusing connection: [conn=%p]\n", conn.get()));
+ }
+
+ return conn.forget();
+}
+
+nsresult ConnectionEntry::RemoveActiveConnection(HttpConnectionBase* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!mActiveConns.RemoveElement(conn)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
+
+ return NS_OK;
+}
+
+nsresult ConnectionEntry::RemovePendingConnection(HttpConnectionBase* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!mPendingConns.RemoveElement(conn)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+void ConnectionEntry::ClosePersistentConnections() {
+ LOG(("ConnectionEntry::ClosePersistentConnections [ci=%s]\n",
+ mConnInfo->HashKey().get()));
+ CloseIdleConnections();
+
+ int32_t activeCount = mActiveConns.Length();
+ for (int32_t i = 0; i < activeCount; i++) {
+ mActiveConns[i]->DontReuse();
+ }
+
+ mCoalescingKeys.Clear();
+}
+
+uint32_t ConnectionEntry::PruneDeadConnections() {
+ uint32_t timeToNextExpire = UINT32_MAX;
+
+ for (int32_t len = mIdleConns.Length(); len > 0; --len) {
+ int32_t idx = len - 1;
+ RefPtr<nsHttpConnection> conn(mIdleConns[idx]);
+ if (!conn->CanReuse()) {
+ RemoveFromIdleConnectionsIndex(idx);
+ // The connection is closed immediately no need to call
+ // EndIdleMonitoring.
+ conn->Close(NS_ERROR_ABORT);
+ } else {
+ timeToNextExpire = std::min(timeToNextExpire, conn->TimeToLive());
+ }
+ }
+
+ if (mUsingSpdy) {
+ for (uint32_t i = 0; i < mActiveConns.Length(); ++i) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(mActiveConns[i]);
+ // Http3 has its own timers, it is not using this one.
+ if (connTCP && connTCP->UsingSpdy()) {
+ if (!connTCP->CanReuse()) {
+ // Marking it don't-reuse will create an active
+ // tear down if the spdy session is idle.
+ connTCP->DontReuse();
+ } else {
+ timeToNextExpire = std::min(timeToNextExpire, connTCP->TimeToLive());
+ }
+ }
+ }
+ }
+
+ return timeToNextExpire;
+}
+
+void ConnectionEntry::VerifyTraffic() {
+ if (!mConnInfo->IsHttp3()) {
+ for (uint32_t index = 0; index < mPendingConns.Length(); ++index) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(mPendingConns[index]);
+ if (conn) {
+ conn->CheckForTraffic(true);
+ }
+ }
+
+ uint32_t numConns = mActiveConns.Length();
+ if (numConns) {
+ // Walk the list backwards to allow us to remove entries easily.
+ for (int index = numConns - 1; index >= 0; index--) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
+ if (conn) {
+ conn->CheckForTraffic(true);
+ if (conn->EverUsedSpdy() &&
+ StaticPrefs::
+ network_http_http2_move_to_pending_list_after_network_change()) {
+ mActiveConns.RemoveElementAt(index);
+ gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
+ mPendingConns.AppendElement(conn);
+ LOG(("Move active connection to pending list [conn=%p]\n",
+ conn.get()));
+ }
+ }
+ }
+ }
+
+ // Iterate the idle connections and unmark them for traffic checks.
+ for (uint32_t index = 0; index < mIdleConns.Length(); ++index) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(mIdleConns[index]);
+ if (conn) {
+ conn->CheckForTraffic(false);
+ }
+ }
+ }
+}
+
+void ConnectionEntry::InsertIntoIdleConnections_internal(
+ nsHttpConnection* conn) {
+ uint32_t idx;
+ for (idx = 0; idx < mIdleConns.Length(); idx++) {
+ nsHttpConnection* idleConn = mIdleConns[idx];
+ if (idleConn->MaxBytesRead() < conn->MaxBytesRead()) {
+ break;
+ }
+ }
+
+ mIdleConns.InsertElementAt(idx, conn);
+}
+
+void ConnectionEntry::InsertIntoIdleConnections(nsHttpConnection* conn) {
+ InsertIntoIdleConnections_internal(conn);
+ gHttpHandler->ConnMgr()->NewIdleConnectionAdded(conn->TimeToLive());
+ conn->BeginIdleMonitoring();
+}
+
+bool ConnectionEntry::IsInActiveConns(HttpConnectionBase* conn) {
+ return mActiveConns.Contains(conn);
+}
+
+void ConnectionEntry::InsertIntoActiveConns(HttpConnectionBase* conn) {
+ mActiveConns.AppendElement(conn);
+ gHttpHandler->ConnMgr()->IncrementActiveConnCount();
+}
+
+bool ConnectionEntry::IsInH2WebsocketConns(HttpConnectionBase* conn) {
+ return mH2WebsocketConns.Contains(conn);
+}
+
+void ConnectionEntry::InsertIntoH2WebsocketConns(HttpConnectionBase* conn) {
+ // no incrementing of connection count since it is just a "fake" connection
+ mH2WebsocketConns.AppendElement(conn);
+}
+
+void ConnectionEntry::RemoveH2WebsocketConns(HttpConnectionBase* conn) {
+ mH2WebsocketConns.RemoveElement(conn);
+}
+
+void ConnectionEntry::MakeAllDontReuseExcept(HttpConnectionBase* conn) {
+ for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
+ HttpConnectionBase* otherConn = mActiveConns[index];
+ if (otherConn != conn) {
+ LOG(
+ ("ConnectionEntry::MakeAllDontReuseExcept shutting down old "
+ "connection (%p) "
+ "because new "
+ "spdy connection (%p) takes precedence\n",
+ otherConn, conn));
+ otherConn->SetCloseReason(
+ ConnectionCloseReason::CLOSE_EXISTING_CONN_FOR_COALESCING);
+ otherConn->DontReuse();
+ }
+ }
+
+ // Cancel any other pending connections - their associated transactions
+ // are in the pending queue and will be dispatched onto this new connection
+ CloseAllDnsAndConnectSockets();
+}
+
+bool ConnectionEntry::FindConnToClaim(
+ PendingTransactionInfo* pendingTransInfo) {
+ nsHttpTransaction* trans = pendingTransInfo->Transaction();
+
+ for (const auto& dnsAndSock : mDnsAndConnectSockets) {
+ if (dnsAndSock->AcceptsTransaction(trans) && dnsAndSock->Claim()) {
+ pendingTransInfo->RememberDnsAndConnectSocket(dnsAndSock);
+ // We've found a speculative connection or a connection that
+ // is free to be used in the DnsAndConnectSockets list.
+ // A free to be used connection is a connection that was
+ // open for a concrete transaction, but that trunsaction
+ // ended up using another connection.
+ LOG(
+ ("ConnectionEntry::FindConnToClaim [ci = %s]\n"
+ "Found a speculative or a free-to-use DnsAndConnectSocket\n",
+ mConnInfo->HashKey().get()));
+
+ // return OK because we have essentially opened a new connection
+ // by converting a speculative DnsAndConnectSockets to general use
+ return true;
+ }
+ }
+
+ // consider null transactions that are being used to drive the ssl handshake
+ // if the transaction creating this connection can re-use persistent
+ // connections
+ if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) {
+ uint32_t activeLength = mActiveConns.Length();
+ for (uint32_t i = 0; i < activeLength; i++) {
+ if (pendingTransInfo->TryClaimingActiveConn(mActiveConns[i])) {
+ LOG(
+ ("ConnectionEntry::FindConnectingSocket [ci = %s] "
+ "Claiming a null transaction for later use\n",
+ mConnInfo->HashKey().get()));
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool ConnectionEntry::MakeFirstActiveSpdyConnDontReuse() {
+ if (!mUsingSpdy) {
+ return false;
+ }
+
+ for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
+ HttpConnectionBase* conn = mActiveConns[index];
+ if (conn->UsingSpdy() && conn->CanReuse()) {
+ conn->DontReuse();
+ return true;
+ }
+ }
+ return false;
+}
+
+// Return an active h2 or h3 connection
+// that can be directly activated or null.
+HttpConnectionBase* ConnectionEntry::GetH2orH3ActiveConn() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ HttpConnectionBase* experienced = nullptr;
+ HttpConnectionBase* noExperience = nullptr;
+ uint32_t activeLen = mActiveConns.Length();
+
+ // activeLen should generally be 1.. this is a setup race being resolved
+ // take a conn who can activate and is experienced
+ for (uint32_t index = 0; index < activeLen; ++index) {
+ HttpConnectionBase* tmp = mActiveConns[index];
+ if (tmp->CanDirectlyActivate()) {
+ if (tmp->IsExperienced()) {
+ experienced = tmp;
+ break;
+ }
+ noExperience = tmp; // keep looking for a better option
+ }
+ }
+
+ // if that worked, cleanup anything else and exit
+ if (experienced) {
+ for (uint32_t index = 0; index < activeLen; ++index) {
+ HttpConnectionBase* tmp = mActiveConns[index];
+ // in the case where there is a functional h2 session, drop the others
+ if (tmp != experienced) {
+ tmp->DontReuse();
+ }
+ }
+
+ LOG(
+ ("GetH2orH3ActiveConn() request for ent %p %s "
+ "found an active experienced connection %p in native connection "
+ "entry\n",
+ this, mConnInfo->HashKey().get(), experienced));
+ return experienced;
+ }
+
+ if (noExperience) {
+ LOG(
+ ("GetH2orH3ActiveConn() request for ent %p %s "
+ "found an active but inexperienced connection %p in native connection "
+ "entry\n",
+ this, mConnInfo->HashKey().get(), noExperience));
+ return noExperience;
+ }
+
+ return nullptr;
+}
+
+void ConnectionEntry::CloseActiveConnections() {
+ while (mActiveConns.Length()) {
+ RefPtr<HttpConnectionBase> conn(mActiveConns[0]);
+ mActiveConns.RemoveElementAt(0);
+ gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
+
+ // Since HttpConnectionBase::Close doesn't break the bond with
+ // the connection's transaction, we must explicitely tell it
+ // to close its transaction and not just self.
+ conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
+ }
+}
+
+void ConnectionEntry::CloseAllActiveConnsWithNullTransactcion(
+ nsresult aCloseCode) {
+ for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
+ RefPtr<HttpConnectionBase> activeConn = mActiveConns[index];
+ nsAHttpTransaction* liveTransaction = activeConn->Transaction();
+ if (liveTransaction && liveTransaction->IsNullTransaction()) {
+ LOG(
+ ("ConnectionEntry::CloseAllActiveConnsWithNullTransactcion "
+ "also canceling Null Transaction %p on conn %p\n",
+ liveTransaction, activeConn.get()));
+ activeConn->CloseTransaction(liveTransaction, aCloseCode);
+ }
+ }
+}
+
+void ConnectionEntry::ClosePendingConnections() {
+ while (mPendingConns.Length()) {
+ RefPtr<HttpConnectionBase> conn(mPendingConns[0]);
+ mPendingConns.RemoveElementAt(0);
+
+ // Since HttpConnectionBase::Close doesn't break the bond with
+ // the connection's transaction, we must explicitely tell it
+ // to close its transaction and not just self.
+ conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
+ }
+}
+
+void ConnectionEntry::PruneNoTraffic() {
+ LOG((" pruning no traffic [ci=%s]\n", mConnInfo->HashKey().get()));
+ if (mConnInfo->IsHttp3()) {
+ return;
+ }
+
+ uint32_t numConns = mActiveConns.Length();
+ if (numConns) {
+ // Walk the list backwards to allow us to remove entries easily.
+ for (int index = numConns - 1; index >= 0; index--) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
+ if (conn && conn->NoTraffic()) {
+ mActiveConns.RemoveElementAt(index);
+ gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
+ conn->Close(NS_ERROR_ABORT);
+ LOG(
+ (" closed active connection due to no traffic "
+ "[conn=%p]\n",
+ conn.get()));
+ }
+ }
+ }
+}
+
+uint32_t ConnectionEntry::TimeoutTick() {
+ uint32_t timeoutTickNext = 3600; // 1hr
+
+ if (mConnInfo->IsHttp3()) {
+ return timeoutTickNext;
+ }
+
+ LOG(
+ ("ConnectionEntry::TimeoutTick() this=%p host=%s "
+ "idle=%zu active=%zu"
+ " dnsAndSock-len=%zu pending=%zu"
+ " urgentStart pending=%zu\n",
+ this, mConnInfo->Origin(), IdleConnectionsLength(), ActiveConnsLength(),
+ mDnsAndConnectSockets.Length(), PendingQueueLength(),
+ UrgentStartQueueLength()));
+
+ // First call the tick handler for each active connection.
+ PRIntervalTime tickTime = PR_IntervalNow();
+ for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
+ if (conn) {
+ uint32_t connNextTimeout = conn->ReadTimeoutTick(tickTime);
+ timeoutTickNext = std::min(timeoutTickNext, connNextTimeout);
+ }
+ }
+
+ // Now check for any stalled DnsAndConnectSockets.
+ if (mDnsAndConnectSockets.Length()) {
+ TimeStamp currentTime = TimeStamp::Now();
+ double maxConnectTime_ms = gHttpHandler->ConnectTimeout();
+
+ for (const auto& dnsAndSock : Reversed(mDnsAndConnectSockets)) {
+ double delta = dnsAndSock->Duration(currentTime);
+ // If the socket has timed out, close it so the waiting
+ // transaction will get the proper signal.
+ if (delta > maxConnectTime_ms) {
+ LOG(("Force timeout of DnsAndConnectSocket to %s after %.2fms.\n",
+ mConnInfo->HashKey().get(), delta));
+ dnsAndSock->CloseTransports(NS_ERROR_NET_TIMEOUT);
+ }
+
+ // If this DnsAndConnectSocket hangs around for 5 seconds after we've
+ // closed() it then just abandon the socket.
+ if (delta > maxConnectTime_ms + 5000) {
+ LOG(("Abandon DnsAndConnectSocket to %s after %.2fms.\n",
+ mConnInfo->HashKey().get(), delta));
+ RemoveDnsAndConnectSocket(dnsAndSock, true);
+ }
+ }
+ }
+ if (mDnsAndConnectSockets.Length()) {
+ timeoutTickNext = 1;
+ }
+
+ return timeoutTickNext;
+}
+
+void ConnectionEntry::MoveConnection(HttpConnectionBase* proxyConn,
+ ConnectionEntry* otherEnt) {
+ // To avoid changing mNumActiveConns/mNumIdleConns counter use internal
+ // functions.
+ RefPtr<HttpConnectionBase> deleteProtector(proxyConn);
+ if (mActiveConns.RemoveElement(proxyConn)) {
+ otherEnt->mActiveConns.AppendElement(proxyConn);
+ return;
+ }
+
+ RefPtr<nsHttpConnection> proxyConnTCP = do_QueryObject(proxyConn);
+ if (proxyConnTCP) {
+ if (mIdleConns.RemoveElement(proxyConnTCP)) {
+ otherEnt->InsertIntoIdleConnections_internal(proxyConnTCP);
+ return;
+ }
+ }
+}
+
+HttpRetParams ConnectionEntry::GetConnectionData() {
+ HttpRetParams data;
+ data.host = mConnInfo->Origin();
+ data.port = mConnInfo->OriginPort();
+ for (uint32_t i = 0; i < mActiveConns.Length(); i++) {
+ HttpConnInfo info;
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(mActiveConns[i]);
+ if (connTCP) {
+ info.ttl = connTCP->TimeToLive();
+ } else {
+ info.ttl = 0;
+ }
+ info.rtt = mActiveConns[i]->Rtt();
+ info.SetHTTPProtocolVersion(mActiveConns[i]->Version());
+ data.active.AppendElement(info);
+ }
+ for (uint32_t i = 0; i < mIdleConns.Length(); i++) {
+ HttpConnInfo info;
+ info.ttl = mIdleConns[i]->TimeToLive();
+ info.rtt = mIdleConns[i]->Rtt();
+ info.SetHTTPProtocolVersion(mIdleConns[i]->Version());
+ data.idle.AppendElement(info);
+ }
+ for (uint32_t i = 0; i < mDnsAndConnectSockets.Length(); i++) {
+ DnsAndConnectSockets dnsAndSock{};
+ dnsAndSock.speculative = mDnsAndConnectSockets[i]->IsSpeculative();
+ data.dnsAndSocks.AppendElement(dnsAndSock);
+ }
+ if (mConnInfo->IsHttp3()) {
+ data.httpVersion = "HTTP/3"_ns;
+ } else if (mUsingSpdy) {
+ data.httpVersion = "HTTP/2"_ns;
+ } else {
+ data.httpVersion = "HTTP <= 1.1"_ns;
+ }
+ data.ssl = mConnInfo->EndToEndSSL();
+ return data;
+}
+
+void ConnectionEntry::LogConnections() {
+ if (!mConnInfo->IsHttp3()) {
+ LOG(("active urgent conns ["));
+ for (HttpConnectionBase* conn : mActiveConns) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ MOZ_ASSERT(connTCP);
+ if (connTCP->IsUrgentStartPreferred()) {
+ LOG((" %p", conn));
+ }
+ }
+ LOG(("] active regular conns ["));
+ for (HttpConnectionBase* conn : mActiveConns) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ MOZ_ASSERT(connTCP);
+ if (!connTCP->IsUrgentStartPreferred()) {
+ LOG((" %p", conn));
+ }
+ }
+
+ LOG(("] idle urgent conns ["));
+ for (nsHttpConnection* conn : mIdleConns) {
+ if (conn->IsUrgentStartPreferred()) {
+ LOG((" %p", conn));
+ }
+ }
+ LOG(("] idle regular conns ["));
+ for (nsHttpConnection* conn : mIdleConns) {
+ if (!conn->IsUrgentStartPreferred()) {
+ LOG((" %p", conn));
+ }
+ }
+ } else {
+ LOG(("active conns ["));
+ for (HttpConnectionBase* conn : mActiveConns) {
+ LOG((" %p", conn));
+ }
+ MOZ_ASSERT(mIdleConns.Length() == 0);
+ }
+ LOG(("]"));
+}
+
+bool ConnectionEntry::RemoveTransFromPendingQ(nsHttpTransaction* aTrans) {
+ // We will abandon all DnsAndConnectSockets belonging to the given
+ // transaction.
+ nsTArray<RefPtr<PendingTransactionInfo>>* infoArray =
+ GetTransactionPendingQHelper(aTrans);
+
+ RefPtr<PendingTransactionInfo> pendingTransInfo;
+ int32_t transIndex =
+ infoArray ? infoArray->IndexOf(aTrans, 0, PendingComparator()) : -1;
+ if (transIndex >= 0) {
+ pendingTransInfo = (*infoArray)[transIndex];
+ infoArray->RemoveElementAt(transIndex);
+ }
+
+ if (!pendingTransInfo) {
+ return false;
+ }
+
+ // Abandon all DnsAndConnectSockets belonging to the given transaction.
+ nsWeakPtr tmp = pendingTransInfo->ForgetDnsAndConnectSocketAndActiveConn();
+ RefPtr<DnsAndConnectSocket> dnsAndSock = do_QueryReferent(tmp);
+ if (dnsAndSock) {
+ RemoveDnsAndConnectSocket(dnsAndSock, true);
+ }
+ return true;
+}
+
+void ConnectionEntry::MaybeUpdateEchConfig(nsHttpConnectionInfo* aConnInfo) {
+ if (!mConnInfo->HashKey().Equals(aConnInfo->HashKey())) {
+ return;
+ }
+
+ const nsCString& echConfig = aConnInfo->GetEchConfig();
+ if (mConnInfo->GetEchConfig().Equals(echConfig)) {
+ return;
+ }
+
+ LOG(("ConnectionEntry::MaybeUpdateEchConfig [ci=%s]\n",
+ mConnInfo->HashKey().get()));
+
+ mConnInfo->SetEchConfig(echConfig);
+
+ // If echConfig is changed, we should close all DnsAndConnectSockets and idle
+ // connections. This is to make sure the new echConfig will be used for the
+ // next connection.
+ CloseAllDnsAndConnectSockets();
+ CloseIdleConnections();
+}
+
+bool ConnectionEntry::MaybeProcessCoalescingKeys(nsIDNSAddrRecord* dnsRecord,
+ bool aIsHttp3) {
+ if (!mConnInfo || !mConnInfo->EndToEndSSL() || (!aIsHttp3 && !AllowHttp2()) ||
+ mConnInfo->UsingProxy() || !mCoalescingKeys.IsEmpty() || !dnsRecord) {
+ return false;
+ }
+
+ nsTArray<NetAddr> addressSet;
+ nsresult rv = dnsRecord->GetAddresses(addressSet);
+
+ if (NS_FAILED(rv) || addressSet.IsEmpty()) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < addressSet.Length(); ++i) {
+ if ((addressSet[i].raw.family == AF_INET && addressSet[i].inet.ip == 0) ||
+ (addressSet[i].raw.family == AF_INET6 &&
+ addressSet[i].inet6.ip.u64[0] == 0 &&
+ addressSet[i].inet6.ip.u64[1] == 0)) {
+ // Bug 1680249 - Don't create the coalescing key if the ip address is
+ // `0.0.0.0` or `::`.
+ LOG(
+ ("ConnectionEntry::MaybeProcessCoalescingKeys skip creating "
+ "Coalescing Key for host [%s]",
+ mConnInfo->Origin()));
+ continue;
+ }
+ nsCString* newKey = mCoalescingKeys.AppendElement(nsCString());
+ newKey->SetLength(kIPv6CStrBufSize + 26);
+ addressSet[i].ToStringBuffer(newKey->BeginWriting(), kIPv6CStrBufSize);
+ newKey->SetLength(strlen(newKey->BeginReading()));
+ if (mConnInfo->GetAnonymous()) {
+ newKey->AppendLiteral("~A:");
+ } else {
+ newKey->AppendLiteral("~.:");
+ }
+ if (mConnInfo->GetFallbackConnection()) {
+ newKey->AppendLiteral("~F:");
+ } else {
+ newKey->AppendLiteral("~.:");
+ }
+ newKey->AppendInt(mConnInfo->OriginPort());
+ newKey->AppendLiteral("/[");
+ nsAutoCString suffix;
+ mConnInfo->GetOriginAttributes().CreateSuffix(suffix);
+ newKey->Append(suffix);
+ newKey->AppendLiteral("]viaDNS");
+ LOG(
+ ("ConnectionEntry::MaybeProcessCoalescingKeys "
+ "Established New Coalescing Key # %d for host "
+ "%s [%s]",
+ i, mConnInfo->Origin(), newKey->get()));
+ }
+ return true;
+}
+
+nsresult ConnectionEntry::CreateDnsAndConnectSocket(
+ nsAHttpTransaction* trans, uint32_t caps, bool speculative,
+ bool isFromPredictor, bool urgentStart, bool allow1918,
+ PendingTransactionInfo* pendingTransInfo) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT((speculative && !pendingTransInfo) ||
+ (!speculative && pendingTransInfo));
+
+ RefPtr<DnsAndConnectSocket> sock = new DnsAndConnectSocket(
+ mConnInfo, trans, caps, speculative, isFromPredictor, urgentStart);
+
+ if (speculative) {
+ sock->SetAllow1918(allow1918);
+ }
+
+ nsresult rv = sock->Init(this);
+ if (NS_FAILED(rv)) {
+ sock->Abandon();
+ return rv;
+ }
+
+ InsertIntoDnsAndConnectSockets(sock);
+
+ if (pendingTransInfo && sock->Claim()) {
+ pendingTransInfo->RememberDnsAndConnectSocket(sock);
+ }
+
+ return NS_OK;
+}
+
+bool ConnectionEntry::AllowToRetryDifferentIPFamilyForHttp3(nsresult aError) {
+ LOG(
+ ("ConnectionEntry::AllowToRetryDifferentIPFamilyForHttp3 %p "
+ "error=%" PRIx32,
+ this, static_cast<uint32_t>(aError)));
+ if (!IsHttp3()) {
+ MOZ_ASSERT(false, "Should not be called for non Http/3 connection");
+ return false;
+ }
+
+ if (!StaticPrefs::network_http_http3_retry_different_ip_family()) {
+ return false;
+ }
+
+ // Only allow to retry with these two errors.
+ if (aError != NS_ERROR_CONNECTION_REFUSED &&
+ aError != NS_ERROR_PROXY_CONNECTION_REFUSED) {
+ return false;
+ }
+
+ // Already retried once.
+ if (mRetriedDifferentIPFamilyForHttp3) {
+ return false;
+ }
+
+ return true;
+}
+
+void ConnectionEntry::SetRetryDifferentIPFamilyForHttp3(uint16_t aIPFamily) {
+ LOG(("ConnectionEntry::SetRetryDifferentIPFamilyForHttp3 %p, af=%u", this,
+ aIPFamily));
+
+ mPreferIPv4 = false;
+ mPreferIPv6 = false;
+
+ if (aIPFamily == AF_INET) {
+ mPreferIPv6 = true;
+ }
+
+ if (aIPFamily == AF_INET6) {
+ mPreferIPv4 = true;
+ }
+
+ mRetriedDifferentIPFamilyForHttp3 = true;
+
+ LOG((" %p prefer ipv4=%d, ipv6=%d", this, (bool)mPreferIPv4,
+ (bool)mPreferIPv6));
+ MOZ_DIAGNOSTIC_ASSERT(mPreferIPv4 ^ mPreferIPv6);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ConnectionEntry.h b/netwerk/protocol/http/ConnectionEntry.h
new file mode 100644
index 0000000000..811c5481de
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionEntry.h
@@ -0,0 +1,231 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ConnectionEntry_h__
+#define ConnectionEntry_h__
+
+#include "PendingTransactionInfo.h"
+#include "PendingTransactionQueue.h"
+#include "DnsAndConnectSocket.h"
+#include "DashboardTypes.h"
+
+namespace mozilla {
+namespace net {
+
+// ConnectionEntry
+//
+// nsHttpConnectionMgr::mCT maps connection info hash key to ConnectionEntry
+// object, which contains list of active and idle connections as well as the
+// list of pending transactions.
+class ConnectionEntry {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ConnectionEntry)
+ explicit ConnectionEntry(nsHttpConnectionInfo* ci);
+
+ void ReschedTransaction(nsHttpTransaction* aTrans);
+
+ nsTArray<RefPtr<PendingTransactionInfo>>* GetTransactionPendingQHelper(
+ nsAHttpTransaction* trans);
+
+ void InsertTransactionSorted(
+ nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority = false);
+
+ void InsertTransaction(PendingTransactionInfo* aPendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority = false);
+
+ size_t UrgentStartQueueLength();
+
+ void PrintPendingQ();
+
+ void Compact();
+
+ void CancelAllTransactions(nsresult reason);
+
+ nsresult CloseIdleConnection(nsHttpConnection* conn);
+ void CloseIdleConnections();
+ void CloseIdleConnections(uint32_t maxToClose);
+ void CloseH2WebsocketConnections();
+ void ClosePendingConnections();
+ nsresult RemoveIdleConnection(nsHttpConnection* conn);
+ bool IsInIdleConnections(HttpConnectionBase* conn);
+ size_t IdleConnectionsLength() const { return mIdleConns.Length(); }
+ void InsertIntoIdleConnections(nsHttpConnection* conn);
+ already_AddRefed<nsHttpConnection> GetIdleConnection(bool respectUrgency,
+ bool urgentTrans,
+ bool* onlyUrgent);
+
+ size_t ActiveConnsLength() const { return mActiveConns.Length(); }
+ void InsertIntoActiveConns(HttpConnectionBase* conn);
+ bool IsInActiveConns(HttpConnectionBase* conn);
+ nsresult RemoveActiveConnection(HttpConnectionBase* conn);
+ nsresult RemovePendingConnection(HttpConnectionBase* conn);
+ void MakeAllDontReuseExcept(HttpConnectionBase* conn);
+ bool FindConnToClaim(PendingTransactionInfo* pendingTransInfo);
+ void CloseActiveConnections();
+ void CloseAllActiveConnsWithNullTransactcion(nsresult aCloseCode);
+
+ bool IsInH2WebsocketConns(HttpConnectionBase* conn);
+ void InsertIntoH2WebsocketConns(HttpConnectionBase* conn);
+ void RemoveH2WebsocketConns(HttpConnectionBase* conn);
+
+ HttpConnectionBase* GetH2orH3ActiveConn();
+ // Make an active spdy connection DontReuse.
+ // TODO: this is a helper function and should nbe improved.
+ bool MakeFirstActiveSpdyConnDontReuse();
+
+ void ClosePersistentConnections();
+
+ uint32_t PruneDeadConnections();
+ void VerifyTraffic();
+ void PruneNoTraffic();
+ uint32_t TimeoutTick();
+
+ void MoveConnection(HttpConnectionBase* proxyConn, ConnectionEntry* otherEnt);
+
+ size_t DnsAndConnectSocketsLength() const {
+ return mDnsAndConnectSockets.Length();
+ }
+
+ void InsertIntoDnsAndConnectSockets(DnsAndConnectSocket* sock);
+ void RemoveDnsAndConnectSocket(DnsAndConnectSocket* dnsAndSock, bool abandon);
+ void CloseAllDnsAndConnectSockets();
+
+ HttpRetParams GetConnectionData();
+ void LogConnections();
+
+ const RefPtr<nsHttpConnectionInfo> mConnInfo;
+
+ bool AvailableForDispatchNow();
+
+ // calculate the number of half open sockets that have not had at least 1
+ // connection complete
+ uint32_t UnconnectedDnsAndConnectSockets() const;
+
+ // Remove a particular DnsAndConnectSocket from the mDnsAndConnectSocket array
+ bool RemoveDnsAndConnectSocket(DnsAndConnectSocket*);
+
+ bool MaybeProcessCoalescingKeys(nsIDNSAddrRecord* dnsRecord,
+ bool aIsHttp3 = false);
+
+ nsresult CreateDnsAndConnectSocket(nsAHttpTransaction* trans, uint32_t caps,
+ bool speculative, bool isFromPredictor,
+ bool urgentStart, bool allow1918,
+ PendingTransactionInfo* pendingTransInfo);
+
+ // Spdy sometimes resolves the address in the socket manager in order
+ // to re-coalesce sharded HTTP hosts. The dotted decimal address is
+ // combined with the Anonymous flag and OA from the connection information
+ // to build the hash key for hosts in the same ip pool.
+ //
+ nsTArray<nsCString> mCoalescingKeys;
+
+ // To have the UsingSpdy flag means some host with the same connection
+ // entry has done NPN=spdy/* at some point. It does not mean every
+ // connection is currently using spdy.
+ bool mUsingSpdy : 1;
+
+ // Determines whether or not we can continue to use spdy-enabled
+ // connections in the future. This is generally set to false either when
+ // some connection here encounters connection-based auth (such as NTLM)
+ // or when some connection here encounters a server that is totally
+ // busted (such as it fails to properly perform the h2 handshake).
+ bool mCanUseSpdy : 1;
+
+ // Flags to remember our happy-eyeballs decision.
+ // Reset only by Ctrl-F5 reload.
+ // True when we've first connected an IPv4 server for this host,
+ // initially false.
+ bool mPreferIPv4 : 1;
+ // True when we've first connected an IPv6 server for this host,
+ // initially false.
+ bool mPreferIPv6 : 1;
+
+ // True if this connection entry has initiated a socket
+ bool mUsedForConnection : 1;
+
+ bool mDoNotDestroy : 1;
+
+ bool IsHttp3() const { return mConnInfo->IsHttp3(); }
+ bool AllowHttp2() const { return mCanUseSpdy; }
+ void DisallowHttp2();
+ void DontReuseHttp3Conn();
+
+ // Set the IP family preference flags according the connected family
+ void RecordIPFamilyPreference(uint16_t family);
+ // Resets all flags to their default values
+ void ResetIPFamilyPreference();
+ // True iff there is currently an established IP family preference
+ bool PreferenceKnown() const;
+
+ // Return the count of pending transactions for all window ids.
+ size_t PendingQueueLength() const;
+ size_t PendingQueueLengthForWindow(uint64_t windowId) const;
+
+ void AppendPendingUrgentStartQ(
+ nsTArray<RefPtr<PendingTransactionInfo>>& result);
+
+ // Append transactions to the |result| whose window id
+ // is equal to |windowId|.
+ // NOTE: maxCount == 0 will get all transactions in the queue.
+ void AppendPendingQForFocusedWindow(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount = 0);
+
+ // Append transactions whose window id isn't equal to |windowId|.
+ // NOTE: windowId == 0 will get all transactions for both
+ // focused and non-focused windows.
+ void AppendPendingQForNonFocusedWindows(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount = 0);
+
+ // Remove the empty pendingQ in |mPendingTransactionTable|.
+ void RemoveEmptyPendingQ();
+
+ void PrintDiagnostics(nsCString& log, uint32_t aMaxPersistConns);
+
+ bool RestrictConnections();
+
+ // Return total active connection count, which is the sum of
+ // active connections and unconnected half open connections.
+ uint32_t TotalActiveConnections() const;
+
+ bool RemoveTransFromPendingQ(nsHttpTransaction* aTrans);
+
+ void MaybeUpdateEchConfig(nsHttpConnectionInfo* aConnInfo);
+
+ bool AllowToRetryDifferentIPFamilyForHttp3(nsresult aError);
+ void SetRetryDifferentIPFamilyForHttp3(uint16_t aIPFamily);
+
+ private:
+ void InsertIntoIdleConnections_internal(nsHttpConnection* conn);
+ void RemoveFromIdleConnectionsIndex(size_t inx);
+ bool RemoveFromIdleConnections(nsHttpConnection* conn);
+
+ nsTArray<RefPtr<nsHttpConnection>> mIdleConns; // idle persistent connections
+ nsTArray<RefPtr<HttpConnectionBase>> mActiveConns; // active connections
+ // When a connection is added to this mPendingConns list, it is primarily
+ // to keep the connection alive and to continue serving its ongoing
+ // transaction. While in this list, the connection will not be available to
+ // serve any new transactions and will remain here until its current
+ // transaction is complete.
+ nsTArray<RefPtr<HttpConnectionBase>> mPendingConns;
+ // "fake" http2 websocket connections that needs to be cleaned up on shutdown
+ nsTArray<RefPtr<HttpConnectionBase>> mH2WebsocketConns;
+
+ nsTArray<RefPtr<DnsAndConnectSocket>>
+ mDnsAndConnectSockets; // dns resolution and half open connections
+
+ PendingTransactionQueue mPendingQ;
+ ~ConnectionEntry();
+
+ bool mRetriedDifferentIPFamilyForHttp3 = false;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !ConnectionEntry_h__
diff --git a/netwerk/protocol/http/ConnectionHandle.cpp b/netwerk/protocol/http/ConnectionHandle.cpp
new file mode 100644
index 0000000000..621c094112
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionHandle.cpp
@@ -0,0 +1,98 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "ConnectionHandle.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+ConnectionHandle::~ConnectionHandle() {
+ if (mConn) {
+ nsresult rv = gHttpHandler->ReclaimConnection(mConn);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ConnectionHandle::~ConnectionHandle\n"
+ " failed to reclaim connection %p\n",
+ mConn.get()));
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS0(ConnectionHandle)
+
+nsresult ConnectionHandle::OnHeadersAvailable(nsAHttpTransaction* trans,
+ nsHttpRequestHead* req,
+ nsHttpResponseHead* resp,
+ bool* reset) {
+ return mConn->OnHeadersAvailable(trans, req, resp, reset);
+}
+
+void ConnectionHandle::CloseTransaction(nsAHttpTransaction* trans,
+ nsresult reason) {
+ mConn->CloseTransaction(trans, reason);
+}
+
+nsresult ConnectionHandle::TakeTransport(nsISocketTransport** aTransport,
+ nsIAsyncInputStream** aInputStream,
+ nsIAsyncOutputStream** aOutputStream) {
+ return mConn->TakeTransport(aTransport, aInputStream, aOutputStream);
+}
+
+Http3WebTransportSession* ConnectionHandle::GetWebTransportSession(
+ nsAHttpTransaction* aTransaction) {
+ return mConn->GetWebTransportSession(aTransaction);
+}
+
+bool ConnectionHandle::IsPersistent() {
+ MOZ_ASSERT(OnSocketThread());
+ return mConn->IsPersistent();
+}
+
+bool ConnectionHandle::IsReused() {
+ MOZ_ASSERT(OnSocketThread());
+ return mConn->IsReused();
+}
+
+void ConnectionHandle::DontReuse() {
+ MOZ_ASSERT(OnSocketThread());
+ mConn->DontReuse();
+}
+
+nsresult ConnectionHandle::PushBack(const char* buf, uint32_t bufLen) {
+ return mConn->PushBack(buf, bufLen);
+}
+
+already_AddRefed<HttpConnectionBase> ConnectionHandle::TakeHttpConnection() {
+ // return our connection object to the caller and clear it internally
+ // do not drop our reference - the caller now owns it.
+ MOZ_ASSERT(mConn);
+ return mConn.forget();
+}
+
+already_AddRefed<HttpConnectionBase> ConnectionHandle::HttpConnection() {
+ RefPtr<HttpConnectionBase> rv(mConn);
+ return rv.forget();
+}
+
+void ConnectionHandle::CurrentBrowserIdChanged(uint64_t id) {
+ // Do nothing.
+}
+
+PRIntervalTime ConnectionHandle::LastWriteTime() {
+ return mConn->LastWriteTime();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ConnectionHandle.h b/netwerk/protocol/http/ConnectionHandle.h
new file mode 100644
index 0000000000..79b627f28a
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionHandle.h
@@ -0,0 +1,41 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ConnectionHandle_h__
+#define ConnectionHandle_h__
+
+#include "nsAHttpConnection.h"
+#include "HttpConnectionBase.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// ConnectionHandle
+//
+// thin wrapper around a real connection, used to keep track of references
+// to the connection to determine when the connection may be reused. the
+// transaction owns a reference to this handle. this extra
+// layer of indirection greatly simplifies consumer code, avoiding the
+// need for consumer code to know when to give the connection back to the
+// connection manager.
+//
+class ConnectionHandle : public nsAHttpConnection {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPCONNECTION(mConn)
+
+ explicit ConnectionHandle(HttpConnectionBase* conn) : mConn(conn) {}
+ void Reset() { mConn = nullptr; }
+
+ private:
+ virtual ~ConnectionHandle();
+ RefPtr<HttpConnectionBase> mConn;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // ConnectionHandle_h__
diff --git a/netwerk/protocol/http/DnsAndConnectSocket.cpp b/netwerk/protocol/http/DnsAndConnectSocket.cpp
new file mode 100644
index 0000000000..8895ac97e1
--- /dev/null
+++ b/netwerk/protocol/http/DnsAndConnectSocket.cpp
@@ -0,0 +1,1391 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "ConnectionHandle.h"
+#include "DnsAndConnectSocket.h"
+#include "nsHttpConnection.h"
+#include "nsIClassOfService.h"
+#include "nsIDNSRecord.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsSocketTransportService2.h"
+#include "nsDNSService2.h"
+#include "nsQueryObject.h"
+#include "nsURLHelper.h"
+#include "mozilla/Components.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsHttpHandler.h"
+#include "ConnectionEntry.h"
+#include "HttpConnectionUDP.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/net/NeckoChannelParams.h" // For HttpActivityArgs.
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+namespace mozilla {
+namespace net {
+
+//////////////////////// DnsAndConnectSocket
+NS_IMPL_ADDREF(DnsAndConnectSocket)
+NS_IMPL_RELEASE(DnsAndConnectSocket)
+
+NS_INTERFACE_MAP_BEGIN(DnsAndConnectSocket)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+ NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(DnsAndConnectSocket)
+NS_INTERFACE_MAP_END
+
+static void NotifyActivity(nsHttpConnectionInfo* aConnInfo, uint32_t aSubtype) {
+ HttpConnectionActivity activity(
+ aConnInfo->HashKey(), aConnInfo->GetOrigin(), aConnInfo->OriginPort(),
+ aConnInfo->EndToEndSSL(), !aConnInfo->GetEchConfig().IsEmpty(),
+ aConnInfo->IsHttp3());
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ activity, NS_ACTIVITY_TYPE_HTTP_CONNECTION, aSubtype, PR_Now(), 0, ""_ns);
+}
+
+DnsAndConnectSocket::DnsAndConnectSocket(nsHttpConnectionInfo* ci,
+ nsAHttpTransaction* trans,
+ uint32_t caps, bool speculative,
+ bool isFromPredictor, bool urgentStart)
+ : mTransaction(trans),
+ mCaps(caps),
+ mSpeculative(speculative),
+ mUrgentStart(urgentStart),
+ mIsFromPredictor(isFromPredictor),
+ mConnInfo(ci) {
+ MOZ_ASSERT(ci && trans, "constructor with null arguments");
+ LOG(("Creating DnsAndConnectSocket [this=%p trans=%p ent=%s key=%s]\n", this,
+ trans, mConnInfo->Origin(), mConnInfo->HashKey().get()));
+
+ mIsHttp3 = mConnInfo->IsHttp3();
+ if (speculative) {
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_TOTAL_SPECULATIVE_CONN>
+ totalSpeculativeConn;
+ ++totalSpeculativeConn;
+
+ if (isFromPredictor) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_CREATED>
+ totalPreconnectsCreated;
+ ++totalPreconnectsCreated;
+ }
+ }
+
+ MOZ_ASSERT(mConnInfo);
+ NotifyActivity(mConnInfo,
+ mSpeculative
+ ? NS_HTTP_ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED
+ : NS_HTTP_ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED);
+}
+
+void DnsAndConnectSocket::CheckIsDone() {
+ MOZ_DIAGNOSTIC_ASSERT(!mPrimaryTransport.mSocketTransport);
+ MOZ_DIAGNOSTIC_ASSERT(!mPrimaryTransport.mStreamOut);
+ MOZ_DIAGNOSTIC_ASSERT(!mPrimaryTransport.mDNSRequest);
+ MOZ_DIAGNOSTIC_ASSERT(!mBackupTransport.mSocketTransport);
+ MOZ_DIAGNOSTIC_ASSERT(!mBackupTransport.mStreamOut);
+ MOZ_DIAGNOSTIC_ASSERT(!mBackupTransport.mDNSRequest);
+}
+
+DnsAndConnectSocket::~DnsAndConnectSocket() {
+ LOG(("Destroying DnsAndConnectSocket [this=%p]\n", this));
+ MOZ_ASSERT(mState == DnsAndSocketState::DONE);
+ CheckIsDone();
+ // Check in case something goes wrong that we decrease
+ // the nsHttpConnectionMgr active connection number.
+ mPrimaryTransport.MaybeSetConnectingDone();
+ mBackupTransport.MaybeSetConnectingDone();
+
+ if (mSpeculative) {
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_UNUSED_SPECULATIVE_CONN>
+ unusedSpeculativeConn;
+ ++unusedSpeculativeConn;
+
+ if (mIsFromPredictor) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_UNUSED>
+ totalPreconnectsUnused;
+ ++totalPreconnectsUnused;
+ }
+ }
+}
+
+nsresult DnsAndConnectSocket::Init(ConnectionEntry* ent) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mState == DnsAndSocketState::INIT);
+
+ if (mConnInfo->GetRoutedHost().IsEmpty()) {
+ mPrimaryTransport.mHost = mConnInfo->GetOrigin();
+ mBackupTransport.mHost = mConnInfo->GetOrigin();
+ } else {
+ mPrimaryTransport.mHost = mConnInfo->GetRoutedHost();
+ mBackupTransport.mHost = mConnInfo->GetRoutedHost();
+ }
+
+ CheckProxyConfig();
+
+ if (!mSkipDnsResolution) {
+ nsresult rv = SetupDnsFlags(ent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return SetupEvent(SetupEvents::INIT_EVENT);
+}
+
+void DnsAndConnectSocket::CheckProxyConfig() {
+ if (nsCOMPtr<nsProxyInfo> proxyInfo = mConnInfo->ProxyInfo()) {
+ nsAutoCString proxyType(proxyInfo->Type());
+
+ bool proxyTransparent = false;
+ if (proxyType.EqualsLiteral("socks") || proxyType.EqualsLiteral("socks4")) {
+ proxyTransparent = true;
+ if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
+ mProxyTransparentResolvesHost = true;
+ }
+ }
+
+ if (mProxyTransparentResolvesHost) {
+ // Name resolution is done on the server side. Just pretend
+ // client resolution is complete, this will get picked up later.
+ // since we don't need to do DNS now, we bypass the resolving
+ // step by initializing mNetAddr to an empty address, but we
+ // must keep the port. The SOCKS IO layer will use the hostname
+ // we send it when it's created, rather than the empty address
+ // we send with the connect call.
+ mPrimaryTransport.mSkipDnsResolution = true;
+ mBackupTransport.mSkipDnsResolution = true;
+ mSkipDnsResolution = true;
+ }
+
+ if (!proxyTransparent && !proxyInfo->Host().IsEmpty()) {
+ mProxyNotTransparent = true;
+ mPrimaryTransport.mHost = proxyInfo->Host();
+ mBackupTransport.mHost = proxyInfo->Host();
+ }
+ }
+}
+
+nsresult DnsAndConnectSocket::SetupDnsFlags(ConnectionEntry* ent) {
+ LOG(("DnsAndConnectSocket::SetupDnsFlags [this=%p] ", this));
+
+ nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ bool disableIpv6ForBackup = false;
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+ if (mCaps & NS_HTTP_DISABLE_IPV4) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
+ } else if (mCaps & NS_HTTP_DISABLE_IPV6) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
+ } else if (ent->PreferenceKnown()) {
+ if (ent->mPreferIPv6) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
+ } else if (ent->mPreferIPv4) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
+ }
+ mPrimaryTransport.mRetryWithDifferentIPFamily = true;
+ mBackupTransport.mRetryWithDifferentIPFamily = true;
+ } else if (gHttpHandler->FastFallbackToIPv4()) {
+ // For backup connections, we disable IPv6. That's because some users have
+ // broken IPv6 connectivity (leading to very long timeouts), and disabling
+ // IPv6 on the backup connection gives them a much better user experience
+ // with dual-stack hosts, though they still pay the 250ms delay for each new
+ // connection. This strategy is also known as "happy eyeballs".
+ disableIpv6ForBackup = true;
+ }
+
+ if (ent->mConnInfo->HasIPHintAddress()) {
+ nsresult rv;
+ nsCOMPtr<nsIDNSService> dns =
+ do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // The spec says: "If A and AAAA records for TargetName are locally
+ // available, the client SHOULD ignore these hints.", so we check if the DNS
+ // record is in cache before setting USE_IP_HINT_ADDRESS.
+ nsCOMPtr<nsIDNSRecord> record;
+ rv = dns->ResolveNative(
+ mPrimaryTransport.mHost, nsIDNSService::RESOLVE_OFFLINE,
+ mConnInfo->GetOriginAttributes(), getter_AddRefs(record));
+ if (NS_FAILED(rv) || !record) {
+ LOG(("Setting Socket to use IP hint address"));
+ dnsFlags |= nsIDNSService::RESOLVE_IP_HINT;
+ }
+ }
+
+ dnsFlags |=
+ nsIDNSService::GetFlagsFromTRRMode(NS_HTTP_TRR_MODE_FROM_FLAGS(mCaps));
+
+ // When we get here, we are not resolving using any configured proxy likely
+ // because of individual proxy setting on the request or because the host is
+ // excluded from proxying. Hence, force resolution despite global proxy-DNS
+ // configuration.
+ dnsFlags |= nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS;
+
+ NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
+ !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
+ "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
+
+ mPrimaryTransport.mDnsFlags = dnsFlags;
+ mBackupTransport.mDnsFlags = dnsFlags;
+ if (disableIpv6ForBackup) {
+ mBackupTransport.mDnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
+ }
+ LOG(("DnsAndConnectSocket::SetupDnsFlags flags=%u flagsBackup=%u [this=%p]",
+ mPrimaryTransport.mDnsFlags, mBackupTransport.mDnsFlags, this));
+ NS_ASSERTION(
+ !(mBackupTransport.mDnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
+ !(mBackupTransport.mDnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
+ "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
+ return NS_OK;
+}
+
+nsresult DnsAndConnectSocket::SetupEvent(SetupEvents event) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("DnsAndConnectSocket::SetupEvent state=%d event=%d this=%p", mState,
+ event, this));
+ nsresult rv = NS_OK;
+ switch (event) {
+ case SetupEvents::INIT_EVENT:
+ MOZ_ASSERT(mState == DnsAndSocketState::INIT);
+ rv = mPrimaryTransport.Init(this);
+ if (NS_FAILED(rv)) {
+ mState = DnsAndSocketState::DONE;
+ } else if (mPrimaryTransport.FirstResolving()) {
+ mState = DnsAndSocketState::RESOLVING;
+ } else if (!mIsHttp3 && mPrimaryTransport.ConnectingOrRetry()) {
+ mState = DnsAndSocketState::CONNECTING;
+ SetupBackupTimer();
+ } else {
+ MOZ_ASSERT(false);
+ mState = DnsAndSocketState::DONE;
+ Abandon();
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ break;
+ case SetupEvents::RESOLVED_PRIMARY_EVENT:
+ // This event may be posted multiple times if a DNS lookup is
+ // retriggered, e.g with different parameter.
+ if (!mIsHttp3 && (mState == DnsAndSocketState::RESOLVING)) {
+ mState = DnsAndSocketState::CONNECTING;
+ SetupBackupTimer();
+ }
+ break;
+ case SetupEvents::PRIMARY_DONE_EVENT:
+ MOZ_ASSERT((mState == DnsAndSocketState::RESOLVING) ||
+ (mState == DnsAndSocketState::CONNECTING) ||
+ (mState == DnsAndSocketState::ONE_CONNECTED));
+ CancelBackupTimer();
+ mBackupTransport.CancelDnsResolution();
+ if (mBackupTransport.ConnectingOrRetry()) {
+ mState = DnsAndSocketState::ONE_CONNECTED;
+ } else {
+ mState = DnsAndSocketState::DONE;
+ }
+ break;
+ case SetupEvents::BACKUP_DONE_EVENT:
+ MOZ_ASSERT((mState == DnsAndSocketState::CONNECTING) ||
+ (mState == DnsAndSocketState::ONE_CONNECTED));
+ if (mPrimaryTransport.ConnectingOrRetry()) {
+ mState = DnsAndSocketState::ONE_CONNECTED;
+ } else {
+ mState = DnsAndSocketState::DONE;
+ }
+ break;
+ case SetupEvents::BACKUP_TIMER_FIRED_EVENT:
+ MOZ_ASSERT(mState == DnsAndSocketState::CONNECTING);
+ mBackupTransport.Init(this);
+ }
+ LOG(("DnsAndConnectSocket::SetupEvent state=%d", mState));
+
+ if (mState == DnsAndSocketState::DONE) {
+ CheckIsDone();
+ RefPtr<DnsAndConnectSocket> self(this);
+ RefPtr<ConnectionEntry> ent =
+ gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
+ if (ent) {
+ ent->RemoveDnsAndConnectSocket(this, false);
+ }
+ return rv;
+ }
+ return NS_OK;
+}
+
+void DnsAndConnectSocket::SetupBackupTimer() {
+ uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
+ MOZ_ASSERT(!mSynTimer, "timer already initd");
+
+ // When using Fast Open the correct transport will be setup for sure (it is
+ // guaranteed), but it can be that it will happened a bit later.
+ if (timeout && (!mSpeculative || mConnInfo->GetFallbackConnection()) &&
+ !mIsHttp3) {
+ // Setup the timer that will establish a backup socket
+ // if we do not get a writable event on the main one.
+ // We do this because a lost SYN takes a very long time
+ // to repair at the TCP level.
+ //
+ // Failure to setup the timer is something we can live with,
+ // so don't return an error in that case.
+ NS_NewTimerWithCallback(getter_AddRefs(mSynTimer), this, timeout,
+ nsITimer::TYPE_ONE_SHOT);
+ LOG(("DnsAndConnectSocket::SetupBackupTimer() [this=%p]", this));
+ } else if (timeout) {
+ LOG(("DnsAndConnectSocket::SetupBackupTimer() [this=%p], did not arm\n",
+ this));
+ }
+}
+
+void DnsAndConnectSocket::CancelBackupTimer() {
+ // If the syntimer is still armed, we can cancel it because no backup
+ // socket should be formed at this point
+ if (!mSynTimer) {
+ return;
+ }
+
+ LOG(("DnsAndConnectSocket::CancelBackupTimer()"));
+ mSynTimer->Cancel();
+
+ // Keeping the reference to the timer to remember we have already
+ // performed the backup connection.
+}
+
+void DnsAndConnectSocket::Abandon() {
+ LOG(("DnsAndConnectSocket::Abandon [this=%p ent=%s] %p %p %p %p", this,
+ mConnInfo->Origin(), mPrimaryTransport.mSocketTransport.get(),
+ mBackupTransport.mSocketTransport.get(),
+ mPrimaryTransport.mStreamOut.get(), mBackupTransport.mStreamOut.get()));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // Tell socket (and backup socket) to forget the half open socket.
+ mPrimaryTransport.Abandon();
+ mBackupTransport.Abandon();
+
+ // Stop the timer - we don't want any new backups.
+ CancelBackupTimer();
+
+ mState = DnsAndSocketState::DONE;
+}
+
+double DnsAndConnectSocket::Duration(TimeStamp epoch) {
+ if (mPrimaryTransport.mSynStarted.IsNull()) {
+ return 0;
+ }
+
+ return (epoch - mPrimaryTransport.mSynStarted).ToMilliseconds();
+}
+
+NS_IMETHODIMP // method for nsITimerCallback
+DnsAndConnectSocket::Notify(nsITimer* timer) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(timer == mSynTimer, "wrong timer");
+
+ MOZ_ASSERT(!mBackupTransport.mDNSRequest);
+ MOZ_ASSERT(!mBackupTransport.mSocketTransport);
+ MOZ_ASSERT(mSynTimer);
+
+ DebugOnly<nsresult> rv = SetupEvent(BACKUP_TIMER_FIRED_EVENT);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Keeping the reference to the timer to remember we have already
+ // performed the backup connection.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP // method for nsINamed
+DnsAndConnectSocket::GetName(nsACString& aName) {
+ aName.AssignLiteral("DnsAndConnectSocket");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DnsAndConnectSocket::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
+ nsresult status) {
+ LOG((
+ "DnsAndConnectSocket::OnLookupComplete: this=%p mState=%d status %" PRIx32
+ ".",
+ this, mState, static_cast<uint32_t>(status)));
+
+ if (nsCOMPtr<nsIDNSAddrRecord> addrRecord = do_QueryInterface((rec))) {
+ nsIRequest::TRRMode effectivemode = nsIRequest::TRR_DEFAULT_MODE;
+ addrRecord->GetEffectiveTRRMode(&effectivemode);
+ nsITRRSkipReason::value skipReason = nsITRRSkipReason::TRR_UNSET;
+ addrRecord->GetTrrSkipReason(&skipReason);
+ if (mTransaction) {
+ mTransaction->SetTRRInfo(effectivemode, skipReason);
+ }
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(request);
+ RefPtr<DnsAndConnectSocket> deleteProtector(this);
+
+ if (!request || (!IsPrimary(request) && !IsBackup(request))) {
+ return NS_OK;
+ }
+
+ if (IsPrimary(request) && NS_SUCCEEDED(status)) {
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RESOLVED_HOST, 0);
+ }
+
+ // When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP
+ // proxy host is not found, so we fixup the error code.
+ // For SOCKS proxies (mProxyTransparent == true), the socket
+ // transport resolves the real host here, so there's no fixup
+ // (see bug 226943).
+ if (mProxyNotTransparent && (status == NS_ERROR_UNKNOWN_HOST)) {
+ status = NS_ERROR_UNKNOWN_PROXY_HOST;
+ }
+
+ nsresult rv;
+ // remember if it was primary because TransportSetups will delete the ref to
+ // the DNS request and check cannot be performed later.
+ bool isPrimary = IsPrimary(request);
+ if (isPrimary) {
+ rv = mPrimaryTransport.OnLookupComplete(this, rec, status);
+ if ((!mIsHttp3 && mPrimaryTransport.ConnectingOrRetry()) ||
+ (mIsHttp3 && mPrimaryTransport.Resolved())) {
+ SetupEvent(SetupEvents::RESOLVED_PRIMARY_EVENT);
+ }
+ } else {
+ rv = mBackupTransport.OnLookupComplete(this, rec, status);
+ }
+
+ if (NS_FAILED(rv) || mIsHttp3) {
+ // If we are retrying DNS, we should not setup the connection.
+ if (mIsHttp3 && mPrimaryTransport.mState ==
+ TransportSetup::TransportSetupState::RETRY_RESOLVING) {
+ LOG(("Retry DNS for Http3"));
+ return NS_OK;
+ }
+
+ // Before calling SetupConn we need to hold reference to this, i.e. a
+ // delete protector, because the corresponding ConnectionEntry may be
+ // abandoned and that will abandon this DnsAndConnectSocket.
+ SetupConn(isPrimary, rv);
+ // During a connection dispatch that will happen in SetupConn,
+ // a ConnectionEntry may be abandon and that will abandon this
+ // DnsAndConnectSocket. In that case mState will already be
+ // DnsAndSocketState::DONE and we do not need to set it again.
+ if (mState != DnsAndSocketState::DONE) {
+ if (isPrimary) {
+ SetupEvent(SetupEvents::PRIMARY_DONE_EVENT);
+ } else {
+ SetupEvent(SetupEvents::BACKUP_DONE_EVENT);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+// method for nsIAsyncOutputStreamCallback
+NS_IMETHODIMP
+DnsAndConnectSocket::OnOutputStreamReady(nsIAsyncOutputStream* out) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_DIAGNOSTIC_ASSERT(mPrimaryTransport.mSocketTransport ||
+ mBackupTransport.mSocketTransport);
+ MOZ_DIAGNOSTIC_ASSERT(IsPrimary(out) || IsBackup(out), "stream mismatch");
+
+ RefPtr<ConnectionEntry> ent =
+ gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
+ MOZ_DIAGNOSTIC_ASSERT(ent);
+ Unused << ent;
+
+ RefPtr<DnsAndConnectSocket> deleteProtector(this);
+
+ LOG(("DnsAndConnectSocket::OnOutputStreamReady [this=%p ent=%s %s]\n", this,
+ mConnInfo->Origin(), IsPrimary(out) ? "primary" : "backup"));
+
+ // Remember if it was primary or backup reuest.
+ bool isPrimary = IsPrimary(out);
+ nsresult rv = NS_OK;
+ if (isPrimary) {
+ rv = mPrimaryTransport.CheckConnectedResult(this);
+ if (!mPrimaryTransport.DoneConnecting()) {
+ return NS_OK;
+ }
+ MOZ_ASSERT((NS_SUCCEEDED(rv) &&
+ (mPrimaryTransport.mState ==
+ TransportSetup::TransportSetupState::CONNECTING_DONE) &&
+ mPrimaryTransport.mSocketTransport) ||
+ (NS_FAILED(rv) &&
+ (mPrimaryTransport.mState ==
+ TransportSetup::TransportSetupState::DONE) &&
+ !mPrimaryTransport.mSocketTransport));
+ } else if (IsBackup(out)) {
+ rv = mBackupTransport.CheckConnectedResult(this);
+ if (!mBackupTransport.DoneConnecting()) {
+ return NS_OK;
+ }
+ MOZ_ASSERT((NS_SUCCEEDED(rv) &&
+ (mBackupTransport.mState ==
+ TransportSetup::TransportSetupState::CONNECTING_DONE) &&
+ mBackupTransport.mSocketTransport) ||
+ (NS_FAILED(rv) &&
+ (mBackupTransport.mState ==
+ TransportSetup::TransportSetupState::DONE) &&
+ !mBackupTransport.mSocketTransport));
+ } else {
+ MOZ_ASSERT(false, "unexpected stream");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Before calling SetupConn we need to hold a reference to this, i.e. a
+ // delete protector, because the corresponding ConnectionEntry may be
+ // abandoned and that will abandon this DnsAndConnectSocket.
+ rv = SetupConn(isPrimary, rv);
+ if (mState != DnsAndSocketState::DONE) {
+ // During a connection dispatch that will happen in SetupConn,
+ // a ConnectionEntry may be abandon and that will abandon this
+ // DnsAndConnectSocket. In that case mState will already be
+ // DnsAndSocketState::DONE and we do not need to set it again.
+ if (isPrimary) {
+ SetupEvent(SetupEvents::PRIMARY_DONE_EVENT);
+ } else {
+ SetupEvent(SetupEvents::BACKUP_DONE_EVENT);
+ }
+ }
+ return rv;
+}
+
+nsresult DnsAndConnectSocket::SetupConn(bool isPrimary, nsresult status) {
+ // assign the new socket to the http connection
+
+ RefPtr<ConnectionEntry> ent =
+ gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
+ MOZ_DIAGNOSTIC_ASSERT(ent);
+ if (!ent) {
+ Abandon();
+ return NS_OK;
+ }
+
+ RefPtr<HttpConnectionBase> conn;
+
+ nsresult rv = NS_OK;
+ if (isPrimary) {
+ rv = mPrimaryTransport.SetupConn(mTransaction, ent, status, mCaps,
+ getter_AddRefs(conn));
+ } else {
+ rv = mBackupTransport.SetupConn(mTransaction, ent, status, mCaps,
+ getter_AddRefs(conn));
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("DnsAndConnectSocket::SetupConn "
+ "conn->init (%p) failed %" PRIx32 "\n",
+ conn.get(), static_cast<uint32_t>(rv)));
+
+ if (nsHttpTransaction* trans = mTransaction->QueryHttpTransaction()) {
+ if (mIsHttp3 && !mConnInfo->GetWebTransport()) {
+ trans->DisableHttp3(true);
+ gHttpHandler->ExcludeHttp3(mConnInfo);
+ }
+ // The transaction's connection info is changed after DisableHttp3(), so
+ // this is the only point we can remove this transaction from its conn
+ // entry.
+ ent->RemoveTransFromPendingQ(trans);
+ }
+ mTransaction->Close(rv);
+
+ return rv;
+ }
+
+ // This half-open socket has created a connection. This flag excludes it
+ // from counter of actual connections used for checking limits.
+ mHasConnected = true;
+
+ // if this is still in the pending list, remove it and dispatch it
+ RefPtr<PendingTransactionInfo> pendingTransInfo =
+ gHttpHandler->ConnMgr()->FindTransactionHelper(true, ent, mTransaction);
+ if (pendingTransInfo) {
+ MOZ_ASSERT(!mSpeculative, "Speculative Half Open found mTransaction");
+
+ ent->InsertIntoActiveConns(conn);
+ if (mIsHttp3) {
+ // Each connection must have a ConnectionHandle wrapper.
+ // In case of Http < 2 the a ConnectionHandle is created for each
+ // transaction in DispatchAbstractTransaction.
+ // In case of Http2/ and Http3, ConnectionHandle is created only once.
+ // Http2 connection always starts as http1 connection and the first
+ // transaction use DispatchAbstractTransaction to be dispatched and
+ // a ConnectionHandle is created. All consecutive transactions for
+ // Http2 use a short-cut in DispatchTransaction and call
+ // HttpConnectionBase::Activate (DispatchAbstractTransaction is never
+ // called).
+ // In case of Http3 the short-cut HttpConnectionBase::Activate is always
+ // used also for the first transaction, therefore we need to create
+ // ConnectionHandle here.
+ RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
+ pendingTransInfo->Transaction()->SetConnection(handle);
+ }
+ rv = gHttpHandler->ConnMgr()->DispatchTransaction(
+ ent, pendingTransInfo->Transaction(), conn);
+ } else {
+ // this transaction was dispatched off the pending q before all the
+ // sockets established themselves.
+
+ // After about 1 second allow for the possibility of restarting a
+ // transaction due to server close. Keep at sub 1 second as that is the
+ // minimum granularity we can expect a server to be timing out with.
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ if (connTCP) {
+ connTCP->SetIsReusedAfter(950);
+ }
+
+ // if we are using ssl and no other transactions are waiting right now,
+ // then form a null transaction to drive the SSL handshake to
+ // completion. Afterwards the connection will be 100% ready for the next
+ // transaction to use it. Make an exception for SSL tunneled HTTP proxy as
+ // the NullHttpTransaction does not know how to drive Connect
+ // Http3 cannot be dispatched using OnMsgReclaimConnection (see below),
+ // therefore we need to use a Nulltransaction.
+ if (!connTCP ||
+ (ent->mConnInfo->FirstHopSSL() && !ent->UrgentStartQueueLength() &&
+ !ent->PendingQueueLength() && !ent->mConnInfo->UsingConnect())) {
+ LOG(
+ ("DnsAndConnectSocket::SetupConn null transaction will "
+ "be used to finish SSL handshake on conn %p\n",
+ conn.get()));
+ RefPtr<nsAHttpTransaction> trans;
+ if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
+ // null transactions cannot be put in the entry queue, so that
+ // explains why it is not present.
+ mDispatchedMTransaction = true;
+ trans = mTransaction;
+ } else {
+ trans = new NullHttpTransaction(mConnInfo, callbacks, mCaps);
+ }
+
+ ent->InsertIntoActiveConns(conn);
+ rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(ent, trans,
+ mCaps, conn, 0);
+ } else {
+ // otherwise just put this in the persistent connection pool
+ LOG(
+ ("DnsAndConnectSocket::SetupConn no transaction match "
+ "returning conn %p to pool\n",
+ conn.get()));
+ gHttpHandler->ConnMgr()->OnMsgReclaimConnection(conn);
+
+ // We expect that there is at least one tranasction in the pending
+ // queue that can take this connection, but it can happened that
+ // all transactions are blocked or they have took other idle
+ // connections. In that case the connection has been added to the
+ // idle queue.
+ // If the connection is in the idle queue but it is using ssl, make
+ // a nulltransaction for it to finish ssl handshake!
+ if (ent->mConnInfo->FirstHopSSL() && !ent->mConnInfo->UsingConnect()) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ // If RemoveIdleConnection succeeds that means that conn is in the
+ // idle queue.
+ if (connTCP && NS_SUCCEEDED(ent->RemoveIdleConnection(connTCP))) {
+ RefPtr<nsAHttpTransaction> trans;
+ if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
+ mDispatchedMTransaction = true;
+ trans = mTransaction;
+ } else {
+ trans = new NullHttpTransaction(ent->mConnInfo, callbacks, mCaps);
+ }
+ ent->InsertIntoActiveConns(conn);
+ rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(
+ ent, trans, mCaps, conn, 0);
+ }
+ }
+ }
+ }
+
+ // If this halfOpenConn was speculative, but at the end the conn got a
+ // non-null transaction than this halfOpen is not speculative anymore!
+ if (conn->Transaction() && !conn->Transaction()->IsNullTransaction()) {
+ Claim();
+ }
+
+ return rv;
+}
+
+// method for nsITransportEventSink
+NS_IMETHODIMP
+DnsAndConnectSocket::OnTransportStatus(nsITransport* trans, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MOZ_ASSERT(IsPrimary(trans) || IsBackup(trans));
+ if (mTransaction) {
+ if (IsPrimary(trans) ||
+ (IsBackup(trans) && (status == NS_NET_STATUS_CONNECTED_TO) &&
+ mPrimaryTransport.mSocketTransport)) {
+ // Send this status event only if the transaction is still pending,
+ // i.e. it has not found a free already connected socket.
+ // Sockets in halfOpen state can only get following events:
+ // NS_NET_STATUS_CONNECTING_TO and NS_NET_STATUS_CONNECTED_TO.
+ // mBackupTransport is only started after
+ // NS_NET_STATUS_CONNECTING_TO of mSocketTransport, so ignore
+ // NS_NET_STATUS_CONNECTING_TO event for mBackupTransport and
+ // send NS_NET_STATUS_CONNECTED_TO.
+ // mBackupTransport must be connected before mSocketTransport(e.g.
+ // mPrimaryTransport.mSocketTransport != nullpttr).
+ mTransaction->OnTransportStatus(trans, status, progress);
+ }
+ }
+
+ if (status == NS_NET_STATUS_CONNECTED_TO) {
+ if (IsPrimary(trans)) {
+ mPrimaryTransport.mConnectedOK = true;
+ } else {
+ mBackupTransport.mConnectedOK = true;
+ }
+ }
+
+ // The rest of this method only applies to the primary transport
+ if (!IsPrimary(trans)) {
+ return NS_OK;
+ }
+
+ // if we are doing spdy coalescing and haven't recorded the ip address
+ // for this entry before then make the hash key if our dns lookup
+ // just completed. We can't do coalescing if using a proxy because the
+ // ip addresses are not available to the client.
+
+ nsCOMPtr<nsIDNSAddrRecord> dnsRecord(
+ do_GetInterface(mPrimaryTransport.mSocketTransport));
+ if (status == NS_NET_STATUS_CONNECTING_TO &&
+ StaticPrefs::network_http_http2_enabled() &&
+ StaticPrefs::network_http_http2_coalesce_hostnames()) {
+ RefPtr<ConnectionEntry> ent =
+ gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
+ MOZ_DIAGNOSTIC_ASSERT(ent);
+ if (ent) {
+ if (ent->MaybeProcessCoalescingKeys(dnsRecord)) {
+ gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(ent);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+bool DnsAndConnectSocket::IsPrimary(nsITransport* trans) {
+ return trans == mPrimaryTransport.mSocketTransport;
+}
+
+bool DnsAndConnectSocket::IsPrimary(nsIAsyncOutputStream* out) {
+ return out == mPrimaryTransport.mStreamOut;
+}
+
+bool DnsAndConnectSocket::IsPrimary(nsICancelable* dnsRequest) {
+ return dnsRequest == mPrimaryTransport.mDNSRequest;
+}
+
+bool DnsAndConnectSocket::IsBackup(nsITransport* trans) {
+ return trans == mBackupTransport.mSocketTransport;
+}
+
+bool DnsAndConnectSocket::IsBackup(nsIAsyncOutputStream* out) {
+ return out == mBackupTransport.mStreamOut;
+}
+
+bool DnsAndConnectSocket::IsBackup(nsICancelable* dnsRequest) {
+ return dnsRequest == mBackupTransport.mDNSRequest;
+}
+
+// method for nsIInterfaceRequestor
+NS_IMETHODIMP
+DnsAndConnectSocket::GetInterface(const nsIID& iid, void** result) {
+ if (mTransaction) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ return callbacks->GetInterface(iid, result);
+ }
+ }
+ return NS_ERROR_NO_INTERFACE;
+}
+
+bool DnsAndConnectSocket::AcceptsTransaction(nsHttpTransaction* trans) {
+ // When marked as urgent start, only accept urgent start marked transactions.
+ // Otherwise, accept any kind of transaction.
+ return !mUrgentStart || (trans->Caps() & nsIClassOfService::UrgentStart);
+}
+
+bool DnsAndConnectSocket::Claim() {
+ if (mSpeculative) {
+ mSpeculative = false;
+ mAllow1918 = true;
+ auto resetFlag = [](TransportSetup& transport) {
+ uint32_t flags;
+ if (transport.mSocketTransport &&
+ NS_SUCCEEDED(
+ transport.mSocketTransport->GetConnectionFlags(&flags))) {
+ flags &= ~nsISocketTransport::DISABLE_RFC1918;
+ flags &= ~nsISocketTransport::IS_SPECULATIVE_CONNECTION;
+ transport.mSocketTransport->SetConnectionFlags(flags);
+ }
+ };
+ resetFlag(mPrimaryTransport);
+ resetFlag(mBackupTransport);
+
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_USED_SPECULATIVE_CONN>
+ usedSpeculativeConn;
+ ++usedSpeculativeConn;
+
+ if (mIsFromPredictor) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_USED>
+ totalPreconnectsUsed;
+ ++totalPreconnectsUsed;
+ }
+
+ // Http3 has its own syn-retransmission, therefore it does not need a
+ // backup connection.
+ if (mPrimaryTransport.ConnectingOrRetry() &&
+ !mBackupTransport.mSocketTransport && !mSynTimer && !mIsHttp3) {
+ SetupBackupTimer();
+ }
+ }
+
+ if (mFreeToUse) {
+ mFreeToUse = false;
+
+ if (mPrimaryTransport.mSocketTransport) {
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ if (NS_SUCCEEDED(mPrimaryTransport.mSocketTransport->GetTlsSocketControl(
+ getter_AddRefs(tlsSocketControl))) &&
+ tlsSocketControl) {
+ Unused << tlsSocketControl->Claim();
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void DnsAndConnectSocket::Unclaim() {
+ MOZ_ASSERT(!mSpeculative && !mFreeToUse);
+ // We will keep the backup-timer running. Most probably this halfOpen will
+ // be used by a transaction from which this transaction took the halfOpen.
+ // (this is happening because of the transaction priority.)
+ mFreeToUse = true;
+}
+
+void DnsAndConnectSocket::CloseTransports(nsresult error) {
+ if (mPrimaryTransport.mSocketTransport) {
+ mPrimaryTransport.mSocketTransport->Close(error);
+ }
+ if (mBackupTransport.mSocketTransport) {
+ mBackupTransport.mSocketTransport->Close(error);
+ }
+}
+
+DnsAndConnectSocket::TransportSetup::TransportSetup(bool isBackup)
+ : mState(TransportSetup::TransportSetupState::INIT), mIsBackup(isBackup) {}
+
+nsresult DnsAndConnectSocket::TransportSetup::Init(
+ DnsAndConnectSocket* dnsAndSock) {
+ nsresult rv;
+ mSynStarted = TimeStamp::Now();
+ if (mSkipDnsResolution) {
+ mState = TransportSetup::TransportSetupState::CONNECTING;
+ rv = SetupStreams(dnsAndSock);
+ } else {
+ mState = TransportSetup::TransportSetupState::RESOLVING;
+ rv = ResolveHost(dnsAndSock);
+ }
+ if (NS_FAILED(rv)) {
+ CloseAll();
+ mState = TransportSetup::TransportSetupState::DONE;
+ }
+ return rv;
+}
+
+void DnsAndConnectSocket::TransportSetup::CancelDnsResolution() {
+ if (mDNSRequest) {
+ mDNSRequest->Cancel(NS_ERROR_ABORT);
+ mDNSRequest = nullptr;
+ }
+ if (mState == TransportSetup::TransportSetupState::RESOLVING) {
+ mState = TransportSetup::TransportSetupState::INIT;
+ }
+}
+
+void DnsAndConnectSocket::TransportSetup::Abandon() {
+ CloseAll();
+ mState = TransportSetup::TransportSetupState::DONE;
+}
+
+void DnsAndConnectSocket::TransportSetup::SetConnecting() {
+ MOZ_ASSERT(!mWaitingForConnect);
+ mWaitingForConnect = true;
+ gHttpHandler->ConnMgr()->StartedConnect();
+}
+
+void DnsAndConnectSocket::TransportSetup::MaybeSetConnectingDone() {
+ if (mWaitingForConnect) {
+ mWaitingForConnect = false;
+ gHttpHandler->ConnMgr()->RecvdConnect();
+ }
+}
+
+void DnsAndConnectSocket::TransportSetup::CloseAll() {
+ MaybeSetConnectingDone();
+
+ // Tell socket (and backup socket) to forget the half open socket.
+ if (mSocketTransport) {
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport = nullptr;
+ }
+
+ // Tell output stream (and backup) to forget the half open socket.
+ if (mStreamOut) {
+ mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mStreamOut = nullptr;
+ }
+
+ // Lose references to input stream (and backup).
+ if (mStreamIn) {
+ mStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
+ mStreamIn = nullptr;
+ }
+
+ if (mDNSRequest) {
+ mDNSRequest->Cancel(NS_ERROR_ABORT);
+ mDNSRequest = nullptr;
+ }
+
+ mConnectedOK = false;
+}
+
+nsresult DnsAndConnectSocket::TransportSetup::CheckConnectedResult(
+ DnsAndConnectSocket* dnsAndSock) {
+ mState = TransportSetup::TransportSetupState::CONNECTING_DONE;
+ MaybeSetConnectingDone();
+
+ if (mSkipDnsResolution) {
+ return NS_OK;
+ }
+ bool retryDns = false;
+ mSocketTransport->GetRetryDnsIfPossible(&retryDns);
+ if (!retryDns) {
+ return NS_OK;
+ }
+
+ bool retry = false;
+ if (mRetryWithDifferentIPFamily) {
+ mRetryWithDifferentIPFamily = false;
+ mDnsFlags ^= (nsIDNSService::RESOLVE_DISABLE_IPV6 |
+ nsIDNSService::RESOLVE_DISABLE_IPV4);
+ mResetFamilyPreference = true;
+ retry = true;
+ } else if (!(mDnsFlags & nsIDNSService::RESOLVE_DISABLE_TRR)) {
+ bool trrEnabled;
+ mDNSRecord->IsTRR(&trrEnabled);
+ if (trrEnabled) {
+ nsIRequest::TRRMode trrMode = nsIRequest::TRR_DEFAULT_MODE;
+ mDNSRecord->GetEffectiveTRRMode(&trrMode);
+ // If current trr mode is trr only, we should not retry.
+ if (trrMode != nsIRequest::TRR_ONLY_MODE) {
+ // Drop state to closed. This will trigger a new round of
+ // DNS resolving. Bypass the cache this time since the
+ // cached data came from TRR and failed already!
+ LOG((" failed to connect with TRR enabled, try w/o\n"));
+ mDnsFlags |= nsIDNSService::RESOLVE_DISABLE_TRR |
+ nsIDNSService::RESOLVE_BYPASS_CACHE |
+ nsIDNSService::RESOLVE_REFRESH_CACHE;
+ retry = true;
+ }
+ }
+ }
+
+ if (retry) {
+ CloseAll();
+ mState = TransportSetup::TransportSetupState::RETRY_RESOLVING;
+ nsresult rv = ResolveHost(dnsAndSock);
+ if (NS_FAILED(rv)) {
+ CloseAll();
+ mState = TransportSetup::TransportSetupState::DONE;
+ }
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult DnsAndConnectSocket::TransportSetup::SetupConn(
+ nsAHttpTransaction* transaction, ConnectionEntry* ent, nsresult status,
+ uint32_t cap, HttpConnectionBase** connection) {
+ RefPtr<HttpConnectionBase> conn;
+ if (!ent->mConnInfo->IsHttp3()) {
+ conn = new nsHttpConnection();
+ } else {
+ conn = new HttpConnectionUDP();
+ }
+
+ NotifyActivity(ent->mConnInfo, NS_HTTP_ACTIVITY_SUBTYPE_CONNECTION_CREATED);
+
+ LOG(
+ ("DnsAndConnectSocket::SocketTransport::SetupConn "
+ "Created new nshttpconnection %p %s\n",
+ conn.get(), ent->mConnInfo->IsHttp3() ? "using http3" : ""));
+
+ NullHttpTransaction* nullTrans = transaction->QueryNullTransaction();
+ if (nullTrans) {
+ conn->BootstrapTimings(nullTrans->Timings());
+ }
+
+ // Some capabilities are needed before a transaction actually gets
+ // scheduled (e.g. how to negotiate false start)
+ conn->SetTransactionCaps(transaction->Caps());
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ transaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ nsresult rv = NS_OK;
+ if (!ent->mConnInfo->IsHttp3()) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ rv =
+ connTCP->Init(ent->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay,
+ mSocketTransport, mStreamIn, mStreamOut, mConnectedOK,
+ status, callbacks,
+ PR_MillisecondsToInterval(static_cast<uint32_t>(
+ (TimeStamp::Now() - mSynStarted).ToMilliseconds())),
+ cap & NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE);
+ } else {
+ RefPtr<HttpConnectionUDP> connUDP = do_QueryObject(conn);
+ rv = connUDP->Init(ent->mConnInfo, mDNSRecord, status, callbacks, cap);
+ if (NS_SUCCEEDED(rv)) {
+ if (nsHttpHandler::IsHttp3Enabled() &&
+ StaticPrefs::network_http_http2_coalesce_hostnames()) {
+ if (ent->MaybeProcessCoalescingKeys(mDNSRecord, true)) {
+ gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(ent);
+ }
+ }
+ }
+ }
+
+ bool resetPreference = false;
+ if (mResetFamilyPreference ||
+ (mSocketTransport &&
+ NS_SUCCEEDED(
+ mSocketTransport->GetResetIPFamilyPreference(&resetPreference)) &&
+ resetPreference)) {
+ ent->ResetIPFamilyPreference();
+ }
+
+ NetAddr peeraddr;
+ if (mSocketTransport &&
+ NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) {
+ ent->RecordIPFamilyPreference(peeraddr.raw.family);
+ }
+
+ conn.forget(connection);
+ mSocketTransport = nullptr;
+ mStreamOut = nullptr;
+ mStreamIn = nullptr;
+ mState = TransportSetup::TransportSetupState::DONE;
+ return rv;
+}
+
+nsresult DnsAndConnectSocket::TransportSetup::SetupStreams(
+ DnsAndConnectSocket* dnsAndSock) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_DIAGNOSTIC_ASSERT(!mSocketTransport);
+ MOZ_DIAGNOSTIC_ASSERT(!mStreamOut);
+ MOZ_DIAGNOSTIC_ASSERT(!mDNSRequest);
+
+ nsresult rv;
+ nsTArray<nsCString> socketTypes;
+ const nsHttpConnectionInfo* ci = dnsAndSock->mConnInfo;
+ if (dnsAndSock->mIsHttp3) {
+ socketTypes.AppendElement("quic"_ns);
+ } else {
+ if (ci->FirstHopSSL()) {
+ socketTypes.AppendElement("ssl"_ns);
+ } else {
+ const nsCString& defaultType = gHttpHandler->DefaultSocketType();
+ if (!defaultType.IsVoid()) {
+ socketTypes.AppendElement(defaultType);
+ }
+ }
+ }
+
+ nsCOMPtr<nsISocketTransport> socketTransport;
+ nsCOMPtr<nsISocketTransportService> sts;
+
+ sts = components::SocketTransport::Service();
+ if (!sts) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ LOG(
+ ("DnsAndConnectSocket::SetupStreams [this=%p ent=%s] "
+ "setup routed transport to origin %s:%d via %s:%d\n",
+ this, ci->HashKey().get(), ci->Origin(), ci->OriginPort(),
+ ci->RoutedHost(), ci->RoutedPort()));
+
+ nsCOMPtr<nsIRoutedSocketTransportService> routedSTS(do_QueryInterface(sts));
+ if (routedSTS) {
+ rv = routedSTS->CreateRoutedTransport(
+ socketTypes, ci->GetOrigin(), ci->OriginPort(), ci->GetRoutedHost(),
+ ci->RoutedPort(), ci->ProxyInfo(), mDNSRecord,
+ getter_AddRefs(socketTransport));
+ } else {
+ if (!ci->GetRoutedHost().IsEmpty()) {
+ // There is a route requested, but the legacy nsISocketTransportService
+ // can't handle it.
+ // Origin should be reachable on origin host name, so this should
+ // not be a problem - but log it.
+ LOG(
+ ("DnsAndConnectSocket this=%p using legacy nsISocketTransportService "
+ "means explicit route %s:%d will be ignored.\n",
+ this, ci->RoutedHost(), ci->RoutedPort()));
+ }
+
+ rv = sts->CreateTransport(socketTypes, ci->GetOrigin(), ci->OriginPort(),
+ ci->ProxyInfo(), mDNSRecord,
+ getter_AddRefs(socketTransport));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t tmpFlags = 0;
+ if (dnsAndSock->mCaps & NS_HTTP_REFRESH_DNS) {
+ tmpFlags = nsISocketTransport::BYPASS_CACHE;
+ }
+
+ tmpFlags |= nsISocketTransport::GetFlagsFromTRRMode(
+ NS_HTTP_TRR_MODE_FROM_FLAGS(dnsAndSock->mCaps));
+
+ if (dnsAndSock->mCaps & NS_HTTP_LOAD_ANONYMOUS) {
+ tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
+ }
+
+ // When we are making a speculative connection we do not propagate all flags
+ // in mCaps, so we need to query nsHttpConnectionInfo directly as well.
+ if ((dnsAndSock->mCaps & NS_HTTP_LOAD_ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) ||
+ ci->GetAnonymousAllowClientCert()) {
+ tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
+ }
+
+ if (ci->GetPrivate()) {
+ tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
+ }
+
+ Unused << socketTransport->SetIsPrivate(ci->GetPrivate());
+
+ if (dnsAndSock->mCaps & NS_HTTP_DISALLOW_ECH) {
+ tmpFlags |= nsISocketTransport::DONT_TRY_ECH;
+ }
+
+ if (dnsAndSock->mCaps & NS_HTTP_IS_RETRY) {
+ tmpFlags |= nsISocketTransport::IS_RETRY;
+ }
+
+ if (((dnsAndSock->mCaps & NS_HTTP_BE_CONSERVATIVE) ||
+ ci->GetBeConservative()) &&
+ gHttpHandler->ConnMgr()->BeConservativeIfProxied(ci->ProxyInfo())) {
+ LOG(("Setting Socket to BE_CONSERVATIVE"));
+ tmpFlags |= nsISocketTransport::BE_CONSERVATIVE;
+ }
+
+ if (ci->HasIPHintAddress()) {
+ nsCOMPtr<nsIDNSService> dns =
+ do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The spec says: "If A and AAAA records for TargetName are locally
+ // available, the client SHOULD ignore these hints.", so we check if the DNS
+ // record is in cache before setting USE_IP_HINT_ADDRESS.
+ nsCOMPtr<nsIDNSRecord> record;
+ rv = dns->ResolveNative(mHost, nsIDNSService::RESOLVE_OFFLINE,
+ dnsAndSock->mConnInfo->GetOriginAttributes(),
+ getter_AddRefs(record));
+ if (NS_FAILED(rv) || !record) {
+ LOG(("Setting Socket to use IP hint address"));
+ tmpFlags |= nsISocketTransport::USE_IP_HINT_ADDRESS;
+ }
+ }
+
+ if (mRetryWithDifferentIPFamily) {
+ // From the same reason, let the backup socket fail faster to try the other
+ // family.
+ uint16_t fallbackTimeout =
+ mIsBackup ? gHttpHandler->GetFallbackSynTimeout() : 0;
+ if (fallbackTimeout) {
+ socketTransport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT,
+ fallbackTimeout);
+ }
+ }
+
+ if (!dnsAndSock->Allow1918()) {
+ tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
+ }
+
+ if (dnsAndSock->mSpeculative) {
+ tmpFlags |= nsISocketTransport::IS_SPECULATIVE_CONNECTION;
+ }
+
+ socketTransport->SetConnectionFlags(tmpFlags);
+ socketTransport->SetTlsFlags(ci->GetTlsFlags());
+
+ const OriginAttributes& originAttributes =
+ dnsAndSock->mConnInfo->GetOriginAttributes();
+ if (originAttributes != OriginAttributes()) {
+ socketTransport->SetOriginAttributes(originAttributes);
+ }
+
+ socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
+
+ rv = socketTransport->SetEventSink(dnsAndSock, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = socketTransport->SetSecurityCallbacks(dnsAndSock);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (gHttpHandler->EchConfigEnabled() && !ci->GetEchConfig().IsEmpty()) {
+ MOZ_ASSERT(!ci->IsHttp3());
+ LOG(("Setting ECH"));
+ rv = socketTransport->SetEchConfig(ci->GetEchConfig());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NotifyActivity(dnsAndSock->mConnInfo, NS_HTTP_ACTIVITY_SUBTYPE_ECH_SET);
+ }
+
+ RefPtr<ConnectionEntry> ent =
+ gHttpHandler->ConnMgr()->FindConnectionEntry(ci);
+ MOZ_DIAGNOSTIC_ASSERT(ent);
+ if (ent) {
+ Telemetry::Accumulate(Telemetry::HTTP_CONNECTION_ENTRY_CACHE_HIT_1,
+ ent->mUsedForConnection);
+ ent->mUsedForConnection = true;
+ }
+
+ nsCOMPtr<nsIOutputStream> sout;
+ rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
+ getter_AddRefs(sout));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> sin;
+ rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
+ getter_AddRefs(sin));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSocketTransport = socketTransport.forget();
+ mStreamIn = do_QueryInterface(sin);
+ mStreamOut = do_QueryInterface(sout);
+
+ rv = mStreamOut->AsyncWait(dnsAndSock, 0, 0, nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ SetConnecting();
+ }
+
+ return rv;
+}
+
+nsresult DnsAndConnectSocket::TransportSetup::ResolveHost(
+ DnsAndConnectSocket* dnsAndSock) {
+ MOZ_DIAGNOSTIC_ASSERT(!mSocketTransport);
+ MOZ_DIAGNOSTIC_ASSERT(!mStreamOut);
+ MOZ_DIAGNOSTIC_ASSERT(!mDNSRequest);
+ LOG(("DnsAndConnectSocket::TransportSetup::ResolveHost [this=%p %s%s]", this,
+ PromiseFlatCString(mHost).get(),
+ (mDnsFlags & nsIDNSService::RESOLVE_BYPASS_CACHE) ? " bypass cache"
+ : ""));
+ nsCOMPtr<nsIDNSService> dns = GetOrInitDNSService();
+ if (!dns) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mIsBackup) {
+ dnsAndSock->mTransaction->OnTransportStatus(
+ nullptr, NS_NET_STATUS_RESOLVING_HOST, 0);
+ }
+
+ nsresult rv = NS_OK;
+ do {
+ rv = dns->AsyncResolveNative(
+ mHost, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ mDnsFlags | nsIDNSService::RESOLVE_WANT_RECORD_ON_ERROR, nullptr,
+ dnsAndSock, gSocketTransportService,
+ dnsAndSock->mConnInfo->GetOriginAttributes(),
+ getter_AddRefs(mDNSRequest));
+ } while (NS_FAILED(rv) && ShouldRetryDNS());
+
+ if (NS_FAILED(rv)) {
+ mDNSRequest = nullptr;
+ }
+ return rv;
+}
+
+bool DnsAndConnectSocket::TransportSetup::ShouldRetryDNS() {
+ if (mDnsFlags & nsIDNSService::RESOLVE_IP_HINT) {
+ mDnsFlags &= ~nsIDNSService::RESOLVE_IP_HINT;
+ return true;
+ }
+
+ if (mRetryWithDifferentIPFamily) {
+ mRetryWithDifferentIPFamily = false;
+ mDnsFlags ^= (nsIDNSService::RESOLVE_DISABLE_IPV6 |
+ nsIDNSService::RESOLVE_DISABLE_IPV4);
+ mResetFamilyPreference = true;
+ return true;
+ }
+ return false;
+}
+
+nsresult DnsAndConnectSocket::TransportSetup::OnLookupComplete(
+ DnsAndConnectSocket* dnsAndSock, nsIDNSRecord* rec, nsresult status) {
+ mDNSRequest = nullptr;
+ if (NS_SUCCEEDED(status)) {
+ mDNSRecord = do_QueryInterface(rec);
+ MOZ_ASSERT(mDNSRecord);
+
+ if (dnsAndSock->mConnInfo->IsHttp3()) {
+ mState = TransportSetup::TransportSetupState::RESOLVED;
+ return status;
+ }
+ nsresult rv = SetupStreams(dnsAndSock);
+ if (NS_SUCCEEDED(rv)) {
+ mState = TransportSetup::TransportSetupState::CONNECTING;
+ } else {
+ CloseAll();
+ mState = TransportSetup::TransportSetupState::DONE;
+ }
+ return rv;
+ }
+
+ // DNS lookup status failed
+
+ if (ShouldRetryDNS()) {
+ mState = TransportSetup::TransportSetupState::RETRY_RESOLVING;
+ nsresult rv = ResolveHost(dnsAndSock);
+ if (NS_FAILED(rv)) {
+ CloseAll();
+ mState = TransportSetup::TransportSetupState::DONE;
+ }
+ return rv;
+ }
+
+ mState = TransportSetup::TransportSetupState::DONE;
+ return status;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/DnsAndConnectSocket.h b/netwerk/protocol/http/DnsAndConnectSocket.h
new file mode 100644
index 0000000000..8409de3d08
--- /dev/null
+++ b/netwerk/protocol/http/DnsAndConnectSocket.h
@@ -0,0 +1,274 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DnsAndConnectSocket_h__
+#define DnsAndConnectSocket_h__
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "nsAHttpConnection.h"
+#include "nsHttpConnection.h"
+#include "nsHttpTransaction.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsICancelable.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsINamed.h"
+#include "nsITransport.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace net {
+
+// 8d411b53-54bc-4a99-8b78-ff125eab1564
+#define NS_DNSANDCONNECTSOCKET_IID \
+ { \
+ 0x8d411b53, 0x54bc, 0x4a99, { \
+ 0x8b, 0x78, 0xff, 0x12, 0x5e, 0xab, 0x15, 0x64 \
+ } \
+ }
+
+class PendingTransactionInfo;
+class ConnectionEntry;
+
+class DnsAndConnectSocket final : public nsIOutputStreamCallback,
+ public nsITransportEventSink,
+ public nsIInterfaceRequestor,
+ public nsITimerCallback,
+ public nsINamed,
+ public nsSupportsWeakReference,
+ public nsIDNSListener {
+ ~DnsAndConnectSocket();
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_DNSANDCONNECTSOCKET_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+ NS_DECL_NSIDNSLISTENER
+
+ DnsAndConnectSocket(nsHttpConnectionInfo* ci, nsAHttpTransaction* trans,
+ uint32_t caps, bool speculative, bool isFromPredictor,
+ bool urgentStart);
+
+ [[nodiscard]] nsresult Init(ConnectionEntry* ent);
+ void Abandon();
+ double Duration(TimeStamp epoch);
+ void CloseTransports(nsresult error);
+
+ bool IsSpeculative() { return mSpeculative; }
+
+ bool Allow1918() { return mAllow1918; }
+ void SetAllow1918(bool val) { mAllow1918 = val; }
+
+ bool HasConnected() { return mHasConnected; }
+
+ void PrintDiagnostics(nsCString& log);
+
+ // Checks whether the transaction can be dispatched using this
+ // half-open's connection. If this half-open is marked as urgent-start,
+ // it only accepts urgent start transactions. Call only before Claim().
+ bool AcceptsTransaction(nsHttpTransaction* trans);
+ bool Claim();
+ void Unclaim();
+
+ private:
+ // This performs checks that the DnsAndConnectSocket has been properly cleand
+ // up.
+ void CheckIsDone();
+
+ /**
+ * State:
+ * INIT: initial state. From this state:
+ * 1) change the state to RESOLVING and start the primary DNS lookup
+ * if mSkipDnsResolution is false,
+ * 2) or the lookup is skip and the state changes to CONNECTING and
+ * start the backup timer.
+ * 3) or changes to DONE in case of an error.
+ * RESOLVING: the primary DNS resolution is in progress. From this state
+ * we transition into CONNECTING or DONE.
+ * CONNECTING: We change to this state when the primary connection has
+ * started. At that point the backup timer is started.
+ * ONE_CONNECTED: We change into this state when one of the connections
+ * is connected and the second is in progres.
+ * DONE
+ *
+ * Events:
+ * INIT_EVENT: Start the primary dns resolution (if mSkipDnsResolution is
+ * false), otherwise start the primary connection.
+ * RESOLVED_PRIMARY_EVENT: the primary DNS resolution is done. This event
+ * may be resent due to DNS retries
+ * CONNECTED_EVENT: A connecion (primary or backup) is done
+ */
+ enum DnsAndSocketState {
+ INIT,
+ RESOLVING,
+ CONNECTING,
+ ONE_CONNECTED,
+ DONE
+ } mState = INIT;
+
+ enum SetupEvents {
+ INIT_EVENT,
+ RESOLVED_PRIMARY_EVENT,
+ PRIMARY_DONE_EVENT,
+ BACKUP_DONE_EVENT,
+ BACKUP_TIMER_FIRED_EVENT
+ };
+
+ // This structure is responsible for performing DNS lookup, creating socket
+ // and connecting the socket.
+ struct TransportSetup {
+ enum TransportSetupState {
+ INIT,
+ RESOLVING,
+ RESOLVED,
+ RETRY_RESOLVING,
+ CONNECTING,
+ CONNECTING_DONE,
+ DONE
+ } mState;
+
+ bool FirstResolving() {
+ return mState == TransportSetup::TransportSetupState::RESOLVING;
+ }
+ bool ConnectingOrRetry() {
+ return (mState == TransportSetup::TransportSetupState::CONNECTING) ||
+ (mState == TransportSetup::TransportSetupState::RETRY_RESOLVING) ||
+ (mState == TransportSetup::TransportSetupState::CONNECTING_DONE);
+ }
+ bool Resolved() {
+ return mState == TransportSetup::TransportSetupState::RESOLVED;
+ }
+ bool DoneConnecting() {
+ return (mState == TransportSetup::TransportSetupState::CONNECTING_DONE) ||
+ (mState == TransportSetup::TransportSetupState::DONE);
+ }
+
+ nsCString mHost;
+ nsCOMPtr<nsICancelable> mDNSRequest;
+ nsCOMPtr<nsIDNSAddrRecord> mDNSRecord;
+ nsIDNSService::DNSFlags mDnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ bool mRetryWithDifferentIPFamily = false;
+ bool mResetFamilyPreference = false;
+ bool mSkipDnsResolution = false;
+
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+ nsCOMPtr<nsIAsyncOutputStream> mStreamOut;
+ nsCOMPtr<nsIAsyncInputStream> mStreamIn;
+ TimeStamp mSynStarted;
+ bool mConnectedOK = false;
+ bool mIsBackup;
+
+ bool mWaitingForConnect = false;
+ void SetConnecting();
+ void MaybeSetConnectingDone();
+
+ nsresult Init(DnsAndConnectSocket* dnsAndSock);
+ void CancelDnsResolution();
+ void Abandon();
+ void CloseAll();
+ nsresult SetupConn(nsAHttpTransaction* transaction, ConnectionEntry* ent,
+ nsresult status, uint32_t cap,
+ HttpConnectionBase** connection);
+ [[nodiscard]] nsresult SetupStreams(DnsAndConnectSocket* dnsAndSock);
+ nsresult ResolveHost(DnsAndConnectSocket* dnsAndSock);
+ bool ShouldRetryDNS();
+ nsresult OnLookupComplete(DnsAndConnectSocket* dnsAndSock,
+ nsIDNSRecord* rec, nsresult status);
+ nsresult CheckConnectedResult(DnsAndConnectSocket* dnsAndSock);
+
+ protected:
+ explicit TransportSetup(bool isBackup);
+ };
+
+ struct PrimaryTransportSetup final : TransportSetup {
+ PrimaryTransportSetup() : TransportSetup(false) {}
+ };
+
+ struct BackupTransportSetup final : TransportSetup {
+ BackupTransportSetup() : TransportSetup(true) {}
+ };
+
+ nsresult SetupConn(bool isPrimary, nsresult status);
+ void SetupBackupTimer();
+ void CancelBackupTimer();
+
+ bool IsPrimary(nsITransport* trans);
+ bool IsPrimary(nsIAsyncOutputStream* out);
+ bool IsPrimary(nsICancelable* dnsRequest);
+ bool IsBackup(nsITransport* trans);
+ bool IsBackup(nsIAsyncOutputStream* out);
+ bool IsBackup(nsICancelable* dnsRequest);
+
+ // To find out whether |mTransaction| is still in the connection entry's
+ // pending queue. If the transaction is found and |removeWhenFound| is
+ // true, the transaction will be removed from the pending queue.
+ already_AddRefed<PendingTransactionInfo> FindTransactionHelper(
+ bool removeWhenFound);
+
+ void CheckProxyConfig();
+ nsresult SetupDnsFlags(ConnectionEntry* ent);
+ nsresult SetupEvent(SetupEvents event);
+
+ RefPtr<nsAHttpTransaction> mTransaction;
+ bool mDispatchedMTransaction = false;
+
+ PrimaryTransportSetup mPrimaryTransport;
+ uint32_t mCaps;
+
+ // mSpeculative is set if the socket was created from
+ // SpeculativeConnect(). It is cleared when a transaction would normally
+ // start a new connection from scratch but instead finds this one in
+ // the half open list and claims it for its own use. (which due to
+ // the vagaries of scheduling from the pending queue might not actually
+ // match up - but it prevents a speculative connection from opening
+ // more connections that are needed.)
+ bool mSpeculative;
+
+ // If created with a non-null urgent transaction, remember it, so we can
+ // mark the connection as urgent rightaway it's created.
+ bool mUrgentStart;
+
+ // mIsFromPredictor is set if the socket originated from the network
+ // Predictor. It is used to gather telemetry data on used speculative
+ // connections from the predictor.
+ bool mIsFromPredictor;
+
+ bool mAllow1918 = true;
+
+ // mHasConnected tracks whether one of the sockets has completed the
+ // connection process. It may have completed unsuccessfully.
+ bool mHasConnected = false;
+
+ bool mBackupConnStatsSet = false;
+
+ // A DnsAndConnectSocket can be made for a concrete non-null transaction,
+ // but the transaction can be dispatch to another connection. In that
+ // case we can free this transaction to be claimed by other
+ // transactions.
+ bool mFreeToUse = true;
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ nsCOMPtr<nsITimer> mSynTimer;
+ BackupTransportSetup mBackupTransport;
+
+ bool mIsHttp3;
+
+ bool mSkipDnsResolution = false;
+ bool mProxyNotTransparent = false;
+ bool mProxyTransparentResolvesHost = false;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(DnsAndConnectSocket, NS_DNSANDCONNECTSOCKET_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // DnsAndConnectSocket_h__
diff --git a/netwerk/protocol/http/EarlyHintPreconnect.cpp b/netwerk/protocol/http/EarlyHintPreconnect.cpp
new file mode 100644
index 0000000000..4282ae554e
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintPreconnect.cpp
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EarlyHintPreconnect.h"
+
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsIOService.h"
+#include "nsIURI.h"
+#include "SpeculativeTransaction.h"
+
+namespace mozilla::net {
+
+namespace {
+class EarlyHintsPreConnectOverride : public nsIInterfaceRequestor,
+ public nsISpeculativeConnectionOverrider {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ explicit EarlyHintsPreConnectOverride(uint32_t aConnectionLimit)
+ : mConnectionLimit(aConnectionLimit) {}
+
+ private:
+ virtual ~EarlyHintsPreConnectOverride() = default;
+
+ // Only set once on main thread and can be read on multiple threads.
+ const uint32_t mConnectionLimit;
+};
+
+NS_IMPL_ISUPPORTS(EarlyHintsPreConnectOverride, nsIInterfaceRequestor,
+ nsISpeculativeConnectionOverrider)
+
+NS_IMETHODIMP
+EarlyHintsPreConnectOverride::GetInterface(const nsIID& iid, void** result) {
+ if (NS_SUCCEEDED(QueryInterface(iid, result)) && *result) {
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+EarlyHintsPreConnectOverride::GetIgnoreIdle(bool* ignoreIdle) {
+ *ignoreIdle = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EarlyHintsPreConnectOverride::GetParallelSpeculativeConnectLimit(
+ uint32_t* parallelSpeculativeConnectLimit) {
+ *parallelSpeculativeConnectLimit = mConnectionLimit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EarlyHintsPreConnectOverride::GetIsFromPredictor(bool* isFromPredictor) {
+ *isFromPredictor = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EarlyHintsPreConnectOverride::GetAllow1918(bool* allow) {
+ *allow = false;
+ return NS_OK;
+}
+
+} // namespace
+
+void EarlyHintPreconnect::MaybePreconnect(
+ const LinkHeader& aHeader, nsIURI* aBaseURI,
+ OriginAttributes&& aOriginAttributes) {
+ if (!StaticPrefs::network_early_hints_preconnect_enabled()) {
+ return;
+ }
+
+ if (!gIOService) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (NS_FAILED(aHeader.NewResolveHref(getter_AddRefs(uri), aBaseURI))) {
+ return;
+ }
+
+ // only preconnect secure context urls
+ if (!uri->SchemeIs("https")) {
+ return;
+ }
+
+ uint32_t maxPreconnectCount =
+ StaticPrefs::network_early_hints_preconnect_max_connections();
+ nsCOMPtr<nsIInterfaceRequestor> callbacks =
+ new EarlyHintsPreConnectOverride(maxPreconnectCount);
+ // Note that the http connection manager will limit the number of
+ // connections we can make, so it should be fine we don't check duplicate
+ // preconnect attempts here.
+ CORSMode corsMode = dom::Element::StringToCORSMode(aHeader.mCrossOrigin);
+ gIOService->SpeculativeConnectWithOriginAttributesNative(
+ uri, std::move(aOriginAttributes), callbacks, corsMode == CORS_ANONYMOUS);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/EarlyHintPreconnect.h b/netwerk/protocol/http/EarlyHintPreconnect.h
new file mode 100644
index 0000000000..119d425c06
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintPreconnect.h
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_EarlyHintPreconnect_h
+#define mozilla_net_EarlyHintPreconnect_h
+
+#include "nsNetUtil.h"
+
+namespace mozilla::net {
+
+class EarlyHintPreconnect final {
+ public:
+ static void MaybePreconnect(const LinkHeader& aHeader, nsIURI* aBaseURI,
+ OriginAttributes&& aOriginAttributes);
+
+ EarlyHintPreconnect() = delete;
+ EarlyHintPreconnect(const EarlyHintPreconnect&) = delete;
+ EarlyHintPreconnect& operator=(const EarlyHintPreconnect&) = delete;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_EarlyHintPreconnect_h
diff --git a/netwerk/protocol/http/EarlyHintPreloader.cpp b/netwerk/protocol/http/EarlyHintPreloader.cpp
new file mode 100644
index 0000000000..920fea7fcd
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintPreloader.cpp
@@ -0,0 +1,846 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EarlyHintPreloader.h"
+
+#include "EarlyHintRegistrar.h"
+#include "EarlyHintsService.h"
+#include "ErrorList.h"
+#include "HttpChannelParent.h"
+#include "MainThreadUtils.h"
+#include "NeckoCommon.h"
+#include "gfxPlatform.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/EarlyHintRegistrar.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "nsAttrValue.h"
+#include "nsCOMPtr.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsHttpChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIChannel.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIHttpChannel.h"
+#include "nsIInputStream.h"
+#include "nsILoadContext.h"
+#include "nsILoadInfo.h"
+#include "nsIParentChannel.h"
+#include "nsIReferrerInfo.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "ParentChannelListener.h"
+#include "nsIChannel.h"
+#include "nsInterfaceRequestorAgg.h"
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=EarlyHint:5
+// set MOZ_LOG_FILE=earlyhint.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file earlyhint.log
+//
+static mozilla::LazyLogModule gEarlyHintLog("EarlyHint");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gEarlyHintLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gEarlyHintLog, mozilla::LogLevel::Debug)
+
+namespace mozilla::net {
+
+namespace {
+// This id uniquely identifies each early hint preloader in the
+// EarlyHintRegistrar. Must only be accessed from main thread.
+static uint64_t gEarlyHintPreloaderId{0};
+} // namespace
+
+//=============================================================================
+// OngoingEarlyHints
+//=============================================================================
+
+void OngoingEarlyHints::CancelAll(const nsACString& aReason) {
+ for (auto& preloader : mPreloaders) {
+ preloader->CancelChannel(NS_ERROR_ABORT, aReason, /* aDeleteEntry */ true);
+ }
+ mPreloaders.Clear();
+ mStartedPreloads.Clear();
+}
+
+bool OngoingEarlyHints::Contains(const PreloadHashKey& aKey) {
+ return mStartedPreloads.Contains(aKey);
+}
+
+bool OngoingEarlyHints::Add(const PreloadHashKey& aKey,
+ RefPtr<EarlyHintPreloader> aPreloader) {
+ if (!mStartedPreloads.Contains(aKey)) {
+ mStartedPreloads.Insert(aKey);
+ mPreloaders.AppendElement(aPreloader);
+ return true;
+ }
+ return false;
+}
+
+void OngoingEarlyHints::RegisterLinksAndGetConnectArgs(
+ dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks) {
+ // register all channels before returning
+ for (auto& preload : mPreloaders) {
+ EarlyHintConnectArgs args;
+ if (preload->Register(aCpId, args)) {
+ aOutLinks.AppendElement(std::move(args));
+ }
+ }
+}
+
+//=============================================================================
+// EarlyHintPreloader
+//=============================================================================
+
+EarlyHintPreloader::EarlyHintPreloader() {
+ AssertIsOnMainThread();
+ mConnectArgs.earlyHintPreloaderId() = ++gEarlyHintPreloaderId;
+};
+
+EarlyHintPreloader::~EarlyHintPreloader() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ Telemetry::Accumulate(Telemetry::EH_STATE_OF_PRELOAD_REQUEST, mState);
+}
+
+/* static */
+Maybe<PreloadHashKey> EarlyHintPreloader::GenerateHashKey(
+ ASDestination aAs, nsIURI* aURI, nsIPrincipal* aPrincipal,
+ CORSMode aCorsMode, bool aIsModulepreload) {
+ if (aIsModulepreload) {
+ return Some(PreloadHashKey::CreateAsScript(
+ aURI, aCorsMode, JS::loader::ScriptKind::eModule));
+ }
+ if (aAs == ASDestination::DESTINATION_FONT && aCorsMode != CORS_NONE) {
+ return Some(PreloadHashKey::CreateAsFont(aURI, aCorsMode));
+ }
+ if (aAs == ASDestination::DESTINATION_IMAGE) {
+ return Some(PreloadHashKey::CreateAsImage(aURI, aPrincipal, aCorsMode));
+ }
+ if (aAs == ASDestination::DESTINATION_SCRIPT) {
+ return Some(PreloadHashKey::CreateAsScript(
+ aURI, aCorsMode, JS::loader::ScriptKind::eClassic));
+ }
+ if (aAs == ASDestination::DESTINATION_STYLE) {
+ return Some(PreloadHashKey::CreateAsStyle(
+ aURI, aPrincipal, aCorsMode,
+ css::SheetParsingMode::eAuthorSheetFeatures));
+ }
+ if (aAs == ASDestination::DESTINATION_FETCH && aCorsMode != CORS_NONE) {
+ return Some(PreloadHashKey::CreateAsFetch(aURI, aCorsMode));
+ }
+ return Nothing();
+}
+
+/* static */
+nsSecurityFlags EarlyHintPreloader::ComputeSecurityFlags(CORSMode aCORSMode,
+ ASDestination aAs) {
+ if (aAs == ASDestination::DESTINATION_FONT) {
+ return nsContentSecurityManager::ComputeSecurityFlags(
+ CORSMode::CORS_NONE,
+ nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS);
+ }
+ if (aAs == ASDestination::DESTINATION_IMAGE) {
+ return nsContentSecurityManager::ComputeSecurityFlags(
+ aCORSMode, nsContentSecurityManager::CORSSecurityMapping::
+ CORS_NONE_MAPS_TO_INHERITED_CONTEXT) |
+ nsILoadInfo::SEC_ALLOW_CHROME;
+ }
+ if (aAs == ASDestination::DESTINATION_SCRIPT) {
+ return nsContentSecurityManager::ComputeSecurityFlags(
+ aCORSMode, nsContentSecurityManager::CORSSecurityMapping::
+ CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS) |
+ nsILoadInfo::SEC_ALLOW_CHROME;
+ }
+ if (aAs == ASDestination::DESTINATION_STYLE) {
+ return nsContentSecurityManager::ComputeSecurityFlags(
+ aCORSMode, nsContentSecurityManager::CORSSecurityMapping::
+ CORS_NONE_MAPS_TO_INHERITED_CONTEXT) |
+ nsILoadInfo::SEC_ALLOW_CHROME;
+ ;
+ }
+ if (aAs == ASDestination::DESTINATION_FETCH) {
+ return nsContentSecurityManager::ComputeSecurityFlags(
+ aCORSMode, nsContentSecurityManager::CORSSecurityMapping::
+ CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS);
+ }
+ MOZ_ASSERT(false, "Unexpected ASDestination");
+ return nsContentSecurityManager::ComputeSecurityFlags(
+ CORSMode::CORS_NONE,
+ nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS);
+}
+
+// static
+void EarlyHintPreloader::MaybeCreateAndInsertPreload(
+ OngoingEarlyHints* aOngoingEarlyHints, const LinkHeader& aLinkHeader,
+ nsIURI* aBaseURI, nsIPrincipal* aPrincipal,
+ nsICookieJarSettings* aCookieJarSettings,
+ const nsACString& aResponseReferrerPolicy, const nsACString& aCSPHeader,
+ uint64_t aBrowsingContextID,
+ dom::CanonicalBrowsingContext* aLoadingBrowsingContext,
+ bool aIsModulepreload) {
+ nsAttrValue as;
+ ParseAsValue(aLinkHeader.mAs, as);
+
+ ASDestination destination = static_cast<ASDestination>(as.GetEnumValue());
+ CollectResourcesTypeTelemetry(destination);
+
+ if (!StaticPrefs::network_early_hints_enabled()) {
+ return;
+ }
+
+ if (destination == ASDestination::DESTINATION_INVALID && !aIsModulepreload) {
+ // return early when it's definitly not an asset type we preload
+ // would be caught later as well, e.g. when creating the PreloadHashKey
+ return;
+ }
+
+ if (destination == ASDestination::DESTINATION_FONT &&
+ !gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ NS_ENSURE_SUCCESS_VOID(
+ NS_NewURI(getter_AddRefs(uri), aLinkHeader.mHref, nullptr, aBaseURI));
+ // The link relation may apply to a different resource, specified
+ // in the anchor parameter. For the link relations supported so far,
+ // we simply abort if the link applies to a resource different to the
+ // one we've loaded
+ if (!nsContentUtils::LinkContextIsURI(aLinkHeader.mAnchor, uri)) {
+ return;
+ }
+
+ // only preload secure context urls
+ if (!nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(uri)) {
+ return;
+ }
+
+ CORSMode corsMode = dom::Element::StringToCORSMode(aLinkHeader.mCrossOrigin);
+
+ Maybe<PreloadHashKey> hashKey =
+ GenerateHashKey(destination, uri, aPrincipal, corsMode, aIsModulepreload);
+ if (!hashKey) {
+ return;
+ }
+
+ if (aOngoingEarlyHints->Contains(*hashKey)) {
+ return;
+ }
+
+ nsContentPolicyType contentPolicyType =
+ aIsModulepreload ? (IsScriptLikeOrInvalid(aLinkHeader.mAs)
+ ? nsContentPolicyType::TYPE_SCRIPT
+ : nsContentPolicyType::TYPE_INVALID)
+ : AsValueToContentPolicy(as);
+
+ if (contentPolicyType == nsContentPolicyType::TYPE_INVALID) {
+ return;
+ }
+
+ dom::ReferrerPolicy linkReferrerPolicy =
+ dom::ReferrerInfo::ReferrerPolicyAttributeFromString(
+ aLinkHeader.mReferrerPolicy);
+
+ dom::ReferrerPolicy responseReferrerPolicy =
+ dom::ReferrerInfo::ReferrerPolicyAttributeFromString(
+ NS_ConvertUTF8toUTF16(aResponseReferrerPolicy));
+
+ // The early hint may have two referrer policies, one from the response header
+ // and one from the link element.
+ //
+ // For example, in this server response:
+ // HTTP/1.1 103 Early Hints
+ // Referrer-Policy : origin
+ // Link: </style.css>; rel=preload; as=style referrerpolicy=no-referrer
+ //
+ // The link header referrer policy, if present, will take precedence over
+ // the response referrer policy
+ dom::ReferrerPolicy finalReferrerPolicy = responseReferrerPolicy;
+ if (linkReferrerPolicy != dom::ReferrerPolicy::_empty) {
+ finalReferrerPolicy = linkReferrerPolicy;
+ }
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ new dom::ReferrerInfo(aBaseURI, finalReferrerPolicy);
+
+ RefPtr<EarlyHintPreloader> earlyHintPreloader = new EarlyHintPreloader();
+
+ earlyHintPreloader->mLoadContext = aLoadingBrowsingContext;
+
+ // Security flags for modulepreload's request mode are computed here directly
+ // until full support for worker destinations can be added.
+ //
+ // Implements "To fetch a single module script,"
+ // Step 9. If destination is "worker", "sharedworker", or "serviceworker",
+ // and the top-level module fetch flag is set, then set request's
+ // mode to "same-origin".
+ nsSecurityFlags securityFlags =
+ aIsModulepreload
+ ? ((aLinkHeader.mAs.LowerCaseEqualsASCII("worker") ||
+ aLinkHeader.mAs.LowerCaseEqualsASCII("sharedworker") ||
+ aLinkHeader.mAs.LowerCaseEqualsASCII("serviceworker"))
+ ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
+ : nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT) |
+ (corsMode == CORS_USE_CREDENTIALS
+ ? nsILoadInfo::SEC_COOKIES_INCLUDE
+ : nsILoadInfo::SEC_COOKIES_SAME_ORIGIN) |
+ nsILoadInfo::SEC_ALLOW_CHROME
+ : EarlyHintPreloader::ComputeSecurityFlags(corsMode, destination);
+
+ // Verify that the resource should be loaded.
+ // This isn't the ideal way to test the resource against the CSP.
+ // The problem comes from the fact that at the stage of Early Hint
+ // processing we have not yet created a document where we would normally store
+ // the CSP.
+
+ // First we will create a load info,
+ // nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
+ aPrincipal, // loading principal
+ aPrincipal, // triggering principal
+ nullptr /* aLoadingContext node */,
+ nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, contentPolicyType);
+
+ if (aCSPHeader.Length() != 0) {
+ // If the CSP header is present then create a new CSP and apply the header
+ // directives to it
+ nsCOMPtr<nsIContentSecurityPolicy> csp = new nsCSPContext();
+ nsresult rv = csp->SetRequestContextWithPrincipal(
+ aPrincipal, aBaseURI, u""_ns, 0 /* aInnerWindowId */);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ rv = CSP_AppendCSPFromHeader(csp, NS_ConvertUTF8toUTF16(aCSPHeader),
+ false /* report only */);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // We create a temporary ClientInfo. This is required on the loadInfo as
+ // that is how the CSP is queried. More specificially, as a hack to be able
+ // to call NS_CheckContentLoadPolicy on nsILoadInfo which exclusively
+ // accesses the CSP from the ClientInfo, we create a synthetic ClientInfo to
+ // hold the CSP we are creating. This is not a safe thing to do in any other
+ // circumstance because ClientInfos are always describing a ClientSource
+ // that corresponds to a global or potential global, so creating an info
+ // without a source is unsound. For the purposes of doing things before a
+ // global exists, fetch has the concept of a
+ // https://fetch.spec.whatwg.org/#concept-request-reserved-client and
+ // nsILoadInfo explicity has methods around GiveReservedClientSource which
+ // are primarily used by ClientChannelHelper. If you are trying to do real
+ // CSP stuff and the ClientInfo is not there yet, please enhance the logic
+ // around ClientChannelHelper.
+
+ mozilla::ipc::PrincipalInfo principalInfo;
+ rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ dom::ClientInfo clientInfo(nsID::GenerateUUID(), dom::ClientType::Window,
+ principalInfo, TimeStamp::Now());
+
+ // Our newly-created CSP is set on the ClientInfo via the indirect route of
+ // first serializing to CSPInfo
+ ipc::CSPInfo cspInfo;
+ rv = CSPToCSPInfo(csp, &cspInfo);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ clientInfo.SetCspInfo(cspInfo);
+
+ // This ClientInfo is then set on the new loadInfo.
+ // It can now be used to test the resource against the policy
+ secCheckLoadInfo->SetClientInfo(clientInfo);
+ }
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ nsresult rv = NS_CheckContentLoadPolicy(uri, secCheckLoadInfo, &shouldLoad,
+ nsContentUtils::GetContentPolicy());
+
+ if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+ return;
+ }
+
+ NS_ENSURE_SUCCESS_VOID(earlyHintPreloader->OpenChannel(
+ uri, aPrincipal, securityFlags, contentPolicyType, referrerInfo,
+ aCookieJarSettings, aBrowsingContextID));
+
+ earlyHintPreloader->SetLinkHeader(aLinkHeader);
+
+ DebugOnly<bool> result =
+ aOngoingEarlyHints->Add(*hashKey, earlyHintPreloader);
+ MOZ_ASSERT(result);
+}
+
+nsresult EarlyHintPreloader::OpenChannel(
+ nsIURI* aURI, nsIPrincipal* aPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType, nsIReferrerInfo* aReferrerInfo,
+ nsICookieJarSettings* aCookieJarSettings, uint64_t aBrowsingContextID) {
+ MOZ_ASSERT(aContentPolicyType == nsContentPolicyType::TYPE_IMAGE ||
+ aContentPolicyType ==
+ nsContentPolicyType::TYPE_INTERNAL_FETCH_PRELOAD ||
+ aContentPolicyType == nsContentPolicyType::TYPE_SCRIPT ||
+ aContentPolicyType == nsContentPolicyType::TYPE_STYLESHEET ||
+ aContentPolicyType == nsContentPolicyType::TYPE_FONT);
+
+ nsresult rv =
+ NS_NewChannel(getter_AddRefs(mChannel), aURI, aPrincipal, aSecurityFlags,
+ aContentPolicyType, aCookieJarSettings,
+ /* aPerformanceStorage */ nullptr,
+ /* aLoadGroup */ nullptr,
+ /* aCallbacks */ this, nsIRequest::LOAD_NORMAL);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsHttpChannel> httpChannelObject = do_QueryObject(mChannel);
+ if (!httpChannelObject) {
+ mChannel = nullptr;
+ return NS_ERROR_ABORT;
+ }
+
+ // configure HTTP specific stuff
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (!httpChannel) {
+ mChannel = nullptr;
+ return NS_ERROR_ABORT;
+ }
+ DebugOnly<nsresult> success = httpChannel->SetReferrerInfo(aReferrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(success));
+ success = httpChannel->SetRequestHeader("X-Moz"_ns, "early hint"_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(success));
+
+ mParentListener = new ParentChannelListener(this, nullptr);
+
+ PriorizeAsPreload();
+
+ rv = mChannel->AsyncOpen(mParentListener);
+ if (NS_FAILED(rv)) {
+ mParentListener = nullptr;
+ return rv;
+ }
+
+ SetState(ePreloaderOpened);
+
+ // Setting the BrowsingContextID here to let Early Hint requests show up in
+ // devtools. Normally that would automatically happen if we would pass the
+ // nsILoadGroup in ns_NewChannel above, but the nsILoadGroup is inaccessible
+ // here in the ParentProcess. The nsILoadGroup only exists in ContentProcess
+ // as part of the document and nsDocShell. It is also not yet determined which
+ // ContentProcess this load belongs to.
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ static_cast<LoadInfo*>(loadInfo.get())
+ ->UpdateBrowsingContextID(aBrowsingContextID);
+
+ return NS_OK;
+}
+
+void EarlyHintPreloader::PriorizeAsPreload() {
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
+ Unused << mChannel->GetLoadFlags(&loadFlags);
+ Unused << mChannel->SetLoadFlags(loadFlags | nsIRequest::LOAD_BACKGROUND);
+
+ if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel)) {
+ Unused << cos->AddClassFlags(nsIClassOfService::Unblocked);
+ }
+}
+
+void EarlyHintPreloader::SetLinkHeader(const LinkHeader& aLinkHeader) {
+ mConnectArgs.link() = aLinkHeader;
+}
+
+bool EarlyHintPreloader::IsFromContentParent(dom::ContentParentId aCpId) const {
+ return aCpId == mCpId;
+}
+
+bool EarlyHintPreloader::Register(dom::ContentParentId aCpId,
+ EarlyHintConnectArgs& aOut) {
+ mCpId = aCpId;
+
+ // Set minimum delay of 1ms to always start the timer after the function call
+ // completed.
+ nsresult rv = NS_NewTimerWithCallback(
+ getter_AddRefs(mTimer), this,
+ std::max(StaticPrefs::network_early_hints_parent_connect_timeout(),
+ (uint32_t)1),
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(!mTimer);
+ CancelChannel(NS_ERROR_ABORT, "new-timer-failed"_ns,
+ /* aDeleteEntry */ false);
+ return false;
+ }
+
+ // Create an entry in the redirect channel registrar
+ RefPtr<EarlyHintRegistrar> registrar = EarlyHintRegistrar::GetOrCreate();
+ registrar->RegisterEarlyHint(mConnectArgs.earlyHintPreloaderId(), this);
+
+ aOut = mConnectArgs;
+ return true;
+}
+
+nsresult EarlyHintPreloader::CancelChannel(nsresult aStatus,
+ const nsACString& aReason,
+ bool aDeleteEntry) {
+ LOG(("EarlyHintPreloader::CancelChannel [this=%p]\n", this));
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (aDeleteEntry) {
+ RefPtr<EarlyHintRegistrar> registrar = EarlyHintRegistrar::GetOrCreate();
+ registrar->DeleteEntry(mCpId, mConnectArgs.earlyHintPreloaderId());
+ }
+ // clear redirect channel in case this channel is cleared between the call of
+ // EarlyHintPreloader::AsyncOnChannelRedirect and
+ // EarlyHintPreloader::OnRedirectResult
+ mRedirectChannel = nullptr;
+ if (mChannel) {
+ if (mSuspended) {
+ mChannel->Resume();
+ }
+ mChannel->CancelWithReason(aStatus, aReason);
+ // Clearing mChannel is safe, because this EarlyHintPreloader is not in the
+ // EarlyHintRegistrar after this function call and we won't call
+ // SetHttpChannelFromEarlyHintPreloader nor OnStartRequest on mParent.
+ mChannel = nullptr;
+ SetState(ePreloaderCancelled);
+ }
+ return NS_OK;
+}
+
+void EarlyHintPreloader::OnParentReady(nsIParentChannel* aParent) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aParent);
+ LOG(("EarlyHintPreloader::OnParentReady [this=%p]\n", this));
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(mChannel, "earlyhints-connectback", nullptr);
+ }
+
+ mParent = aParent;
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ RefPtr<EarlyHintRegistrar> registrar = EarlyHintRegistrar::GetOrCreate();
+ registrar->DeleteEntry(mCpId, mConnectArgs.earlyHintPreloaderId());
+
+ if (mOnStartRequestCalled) {
+ SetParentChannel();
+ InvokeStreamListenerFunctions();
+ }
+}
+
+void EarlyHintPreloader::SetParentChannel() {
+ RefPtr<HttpBaseChannel> channel = do_QueryObject(mChannel);
+ RefPtr<HttpChannelParent> parent = do_QueryObject(mParent);
+ parent->SetHttpChannelFromEarlyHintPreloader(channel);
+}
+
+// Adapted from
+// https://searchfox.org/mozilla-central/rev/b4150d1c6fae0c51c522df2d2c939cf5ad331d4c/netwerk/ipc/DocumentLoadListener.cpp#1311
+void EarlyHintPreloader::InvokeStreamListenerFunctions() {
+ AssertIsOnMainThread();
+ RefPtr<EarlyHintPreloader> self(this);
+
+ LOG((
+ "EarlyHintPreloader::InvokeStreamListenerFunctions [this=%p parent=%p]\n",
+ this, mParent.get()));
+
+ // If we failed to suspend the channel, then we might have received
+ // some messages while the redirected was being handled.
+ // Manually send them on now.
+ if (!mIsFinished) {
+ // This is safe to do, because OnStartRequest/OnStopRequest/OnDataAvailable
+ // are all called on the main thread. They can't be called until we worked
+ // through all functions in the streamListnerFunctions array.
+ mParentListener->SetListenerAfterRedirect(mParent);
+ }
+ nsTArray<StreamListenerFunction> streamListenerFunctions =
+ std::move(mStreamListenerFunctions);
+
+ ForwardStreamListenerFunctions(std::move(streamListenerFunctions), mParent);
+
+ // We don't expect to get new stream listener functions added
+ // via re-entrancy. If this ever happens, we should understand
+ // exactly why before allowing it.
+ NS_ASSERTION(mStreamListenerFunctions.IsEmpty(),
+ "Should not have added new stream listener function!");
+
+ if (mChannel && mSuspended) {
+ mChannel->Resume();
+ }
+ mChannel = nullptr;
+ mParent = nullptr;
+ mParentListener = nullptr;
+
+ SetState(ePreloaderUsed);
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(EarlyHintPreloader, nsIRequestObserver, nsIStreamListener,
+ nsIChannelEventSink, nsIInterfaceRequestor,
+ nsIRedirectResultListener, nsIMultiPartChannelListener,
+ nsINamed, nsITimerCallback);
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+// Implementation copied and adapted from DocumentLoadListener::OnStartRequest
+// https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/netwerk/ipc/DocumentLoadListener.cpp#2317-2508
+NS_IMETHODIMP
+EarlyHintPreloader::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("EarlyHintPreloader::OnStartRequest [this=%p]\n", this));
+ AssertIsOnMainThread();
+
+ mOnStartRequestCalled = true;
+
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
+ if (multiPartChannel) {
+ multiPartChannel->GetBaseChannel(getter_AddRefs(mChannel));
+ } else {
+ mChannel = do_QueryInterface(aRequest);
+ }
+ MOZ_DIAGNOSTIC_ASSERT(mChannel);
+
+ nsresult status = NS_OK;
+ Unused << aRequest->GetStatus(&status);
+
+ if (mParent) {
+ SetParentChannel();
+ mParent->OnStartRequest(aRequest);
+ InvokeStreamListenerFunctions();
+ } else {
+ // Don't suspend the chanel when the channel got cancelled with
+ // CancelChannel, because then OnStopRequest wouldn't get called and we
+ // wouldn't clean up the channel.
+ if (NS_SUCCEEDED(status)) {
+ mChannel->Suspend();
+ mSuspended = true;
+ }
+ mStreamListenerFunctions.AppendElement(
+ AsVariant(OnStartRequestParams{aRequest}));
+ }
+
+ // return error after adding the OnStartRequest forward. The OnStartRequest
+ // failure has to be forwarded to listener, because they called AsyncOpen on
+ // this channel
+ return status;
+}
+
+// Implementation copied from DocumentLoadListener::OnStopRequest
+// https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/netwerk/ipc/DocumentLoadListener.cpp#2510-2528
+NS_IMETHODIMP
+EarlyHintPreloader::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ AssertIsOnMainThread();
+ LOG(("EarlyHintPreloader::OnStopRequest [this=%p]\n", this));
+ mStreamListenerFunctions.AppendElement(
+ AsVariant(OnStopRequestParams{aRequest, aStatusCode}));
+
+ // If we're not a multi-part channel, then we're finished and we don't
+ // expect any further events. If we are, then this might be called again,
+ // so wait for OnAfterLastPart instead.
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
+ if (!multiPartChannel) {
+ mIsFinished = true;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+// Implementation copied from DocumentLoadListener::OnDataAvailable
+// https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/netwerk/ipc/DocumentLoadListener.cpp#2530-2549
+NS_IMETHODIMP
+EarlyHintPreloader::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ AssertIsOnMainThread();
+ LOG(("EarlyHintPreloader::OnDataAvailable [this=%p]\n", this));
+ // This isn't supposed to happen, since we suspended the channel, but
+ // sometimes Suspend just doesn't work. This can happen when we're routing
+ // through nsUnknownDecoder to sniff the content type, and it doesn't handle
+ // being suspended. Let's just store the data and manually forward it to our
+ // redirected channel when it's ready.
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStreamListenerFunctions.AppendElement(AsVariant(
+ OnDataAvailableParams{aRequest, std::move(data), aOffset, aCount}));
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsIMultiPartChannelListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EarlyHintPreloader::OnAfterLastPart(nsresult aStatus) {
+ LOG(("EarlyHintPreloader::OnAfterLastPart [this=%p]", this));
+ mStreamListenerFunctions.AppendElement(
+ AsVariant(OnAfterLastPartParams{aStatus}));
+ mIsFinished = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EarlyHintPreloader::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ LOG(("EarlyHintPreloader::AsyncOnChannelRedirect [this=%p]", this));
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aNewChannel->GetURI(getter_AddRefs(newURI));
+ if (NS_FAILED(rv)) {
+ callback->OnRedirectVerifyCallback(rv);
+ return NS_OK;
+ }
+
+ // HTTP request headers are not automatically forwarded to the new channel.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
+ NS_ENSURE_STATE(httpChannel);
+
+ rv = httpChannel->SetRequestHeader("X-Moz"_ns, "early hint"_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Assign to mChannel after we get notification about success of the
+ // redirect in OnRedirectResult.
+ mRedirectChannel = aNewChannel;
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsIRedirectResultListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EarlyHintPreloader::OnRedirectResult(nsresult aStatus) {
+ LOG(("EarlyHintPreloader::OnRedirectResult [this=%p] aProceeding=0x%" PRIx32,
+ this, static_cast<uint32_t>(aStatus)));
+ if (NS_SUCCEEDED(aStatus) && mRedirectChannel) {
+ mChannel = mRedirectChannel;
+ }
+
+ mRedirectChannel = nullptr;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsINamed
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EarlyHintPreloader::GetName(nsACString& aName) {
+ aName.AssignLiteral("EarlyHintPreloader");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsITimerCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EarlyHintPreloader::Notify(nsITimer* timer) {
+ // Death grip, because we will most likely remove the last reference when
+ // deleting us from the EarlyHintRegistrar
+ RefPtr<EarlyHintPreloader> deathGrip(this);
+
+ RefPtr<EarlyHintRegistrar> registrar = EarlyHintRegistrar::GetOrCreate();
+ registrar->DeleteEntry(mCpId, mConnectArgs.earlyHintPreloaderId());
+
+ mTimer = nullptr;
+ mRedirectChannel = nullptr;
+ if (mChannel) {
+ if (mSuspended) {
+ mChannel->Resume();
+ }
+ mChannel->CancelWithReason(NS_ERROR_ABORT, "parent-connect-timeout"_ns);
+ mChannel = nullptr;
+ }
+ SetState(ePreloaderTimeout);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EarlyHintPreloader::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EarlyHintPreloader::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIChannelEventSink*>(this);
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIRedirectResultListener*>(this);
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext != nullptr) {
+ nsCOMPtr<nsILoadContext> loadContext = mLoadContext;
+ loadContext.forget(aResult);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+void EarlyHintPreloader::CollectResourcesTypeTelemetry(
+ ASDestination aASDestination) {
+ if (aASDestination == ASDestination::DESTINATION_FONT) {
+ glean::netwerk::early_hints.Get("font"_ns).Add(1);
+ } else if (aASDestination == ASDestination::DESTINATION_SCRIPT) {
+ glean::netwerk::early_hints.Get("script"_ns).Add(1);
+ } else if (aASDestination == ASDestination::DESTINATION_STYLE) {
+ glean::netwerk::early_hints.Get("stylesheet"_ns).Add(1);
+ } else if (aASDestination == ASDestination::DESTINATION_IMAGE) {
+ glean::netwerk::early_hints.Get("image"_ns).Add(1);
+ } else if (aASDestination == ASDestination::DESTINATION_FETCH) {
+ glean::netwerk::early_hints.Get("fetch"_ns).Add(1);
+ } else {
+ glean::netwerk::early_hints.Get("other"_ns).Add(1);
+ }
+}
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/EarlyHintPreloader.h b/netwerk/protocol/http/EarlyHintPreloader.h
new file mode 100644
index 0000000000..782e189731
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintPreloader.h
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_EarlyHintPreloader_h
+#define mozilla_net_EarlyHintPreloader_h
+
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PreloadHashKey.h"
+#include "NeckoCommon.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsHashtablesFwd.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIRedirectResultListener.h"
+#include "nsIStreamListener.h"
+#include "nsITimer.h"
+#include "nsNetUtil.h"
+
+class nsAttrValue;
+class nsICookieJarSettings;
+class nsILoadContext;
+class nsIPrincipal;
+class nsIReferrerInfo;
+
+namespace mozilla::dom {
+class CanonicalBrowsingContext;
+}
+
+namespace mozilla::net {
+
+class EarlyHintPreloader;
+class EarlyHintConnectArgs;
+class ParentChannelListener;
+struct LinkHeader;
+
+// class keeping track of all ongoing early hints
+class OngoingEarlyHints final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(OngoingEarlyHints)
+
+ OngoingEarlyHints() = default;
+
+ // returns whether a preload with that key already existed
+ bool Contains(const PreloadHashKey& aKey);
+ bool Add(const PreloadHashKey& aKey, RefPtr<EarlyHintPreloader> aPreloader);
+
+ void CancelAll(const nsACString& aReason);
+
+ // registers all channels and returns the ids
+ void RegisterLinksAndGetConnectArgs(
+ dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks);
+
+ private:
+ ~OngoingEarlyHints() = default;
+
+ // We need to do two things requiring two separate variables to keep track of
+ // preloads:
+ // - deduplicate Link headers when starting preloads, therefore we store them
+ // hashset with PreloadHashKey to look up whether we started the preload
+ // already
+ // - pass link headers in order they were received when passing all started
+ // preloads to the content process, therefore we store them in a nsTArray
+ nsTHashSet<PreloadHashKey> mStartedPreloads;
+ nsTArray<RefPtr<EarlyHintPreloader>> mPreloaders;
+};
+
+class EarlyHintPreloader final : public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIRedirectResultListener,
+ public nsIInterfaceRequestor,
+ public nsIMultiPartChannelListener,
+ public nsINamed,
+ public nsITimerCallback {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIREDIRECTRESULTLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIMULTIPARTCHANNELLISTENER
+ // required by NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+ NS_DECL_NSITIMERCALLBACK
+
+ public:
+ // Create and insert a preload into OngoingEarlyHints if the same preload
+ // wasn't already issued and the LinkHeader can be parsed correctly.
+ static void MaybeCreateAndInsertPreload(
+ OngoingEarlyHints* aOngoingEarlyHints, const LinkHeader& aHeader,
+ nsIURI* aBaseURI, nsIPrincipal* aPrincipal,
+ nsICookieJarSettings* aCookieJarSettings,
+ const nsACString& aReferrerPolicy, const nsACString& aCSPHeader,
+ uint64_t aBrowsingContextID,
+ dom::CanonicalBrowsingContext* aLoadingBrowsingContext,
+ bool aIsModulepreload);
+
+ // register Channel to EarlyHintRegistrar. Returns true and sets connect args
+ // if successful
+ bool Register(dom::ContentParentId aCpId, EarlyHintConnectArgs& aOut);
+
+ // Allows EarlyHintRegistrar to check if the correct content process accesses
+ // this preload. Preventing compromised content processes to access Early Hint
+ // preloads from other origins
+ bool IsFromContentParent(dom::ContentParentId aCpId) const;
+
+ // Should be called by the preloader service when the preload is not
+ // needed after all, because the final response returns a non-2xx status
+ // code. If aDeleteEntry is false, the calling function MUST make sure that
+ // the EarlyHintPreloader is not in the EarlyHintRegistrar anymore. Because
+ // after this function, the EarlyHintPreloader can't connect back to the
+ // parent anymore.
+ nsresult CancelChannel(nsresult aStatus, const nsACString& aReason,
+ bool aDeleteEntry);
+
+ void OnParentReady(nsIParentChannel* aParent);
+
+ private:
+ void SetParentChannel();
+ void InvokeStreamListenerFunctions();
+
+ EarlyHintPreloader();
+ ~EarlyHintPreloader();
+
+ static Maybe<PreloadHashKey> GenerateHashKey(ASDestination aAs, nsIURI* aURI,
+ nsIPrincipal* aPrincipal,
+ CORSMode corsMode,
+ bool aIsModulepreload);
+
+ static nsSecurityFlags ComputeSecurityFlags(CORSMode aCORSMode,
+ ASDestination aAs);
+
+ // call to start the preload
+ nsresult OpenChannel(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsIReferrerInfo* aReferrerInfo,
+ nsICookieJarSettings* aCookieJarSettings,
+ uint64_t aBrowsingContextID);
+ void PriorizeAsPreload();
+ void SetLinkHeader(const LinkHeader& aLinkHeader);
+
+ static void CollectResourcesTypeTelemetry(ASDestination aASDestination);
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+
+ dom::ContentParentId mCpId;
+ EarlyHintConnectArgs mConnectArgs;
+
+ // Copy behavior from DocumentLoadListener.h:
+ // https://searchfox.org/mozilla-central/rev/c0bed29d643393af6ebe77aa31455f283f169202/netwerk/ipc/DocumentLoadListener.h#487-512
+ // The set of nsIStreamListener functions that got called on this
+ // listener, so that we can replay them onto the replacement channel's
+ // listener. This should generally only be OnStartRequest, since we
+ // Suspend() the channel at that point, but it can fail sometimes
+ // so we have to support holding a list.
+ nsTArray<StreamListenerFunction> mStreamListenerFunctions;
+
+ // Set to true once OnStartRequest is called
+ bool mOnStartRequestCalled = false;
+ // Set to true if we suspended mChannel in the OnStartRequest call
+ bool mSuspended = false;
+ nsCOMPtr<nsIParentChannel> mParent;
+ // Set to true after we've received the last OnStopRequest, and shouldn't
+ // setup a reference from the ParentChannelListener to the replacement
+ // channel.
+ bool mIsFinished = false;
+
+ RefPtr<ParentChannelListener> mParentListener;
+ nsCOMPtr<nsITimer> mTimer;
+
+ // Hold the load context to provide data to web extension and anti tracking.
+ // See Bug 1836289 and Bug 1875268
+ nsCOMPtr<nsILoadContext> mLoadContext;
+
+ private:
+ // IMPORTANT: when adding new values, always add them to the end, otherwise
+ // it will mess up telemetry.
+ enum EHPreloaderState : uint32_t {
+ ePreloaderCreated = 0,
+ ePreloaderOpened,
+ ePreloaderUsed,
+ ePreloaderCancelled,
+ ePreloaderTimeout,
+ };
+ EHPreloaderState mState = ePreloaderCreated;
+ void SetState(EHPreloaderState aState) { mState = aState; }
+};
+
+inline nsISupports* ToSupports(EarlyHintPreloader* aObj) {
+ return static_cast<nsIInterfaceRequestor*>(aObj);
+}
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_EarlyHintPreloader_h
diff --git a/netwerk/protocol/http/EarlyHintRegistrar.cpp b/netwerk/protocol/http/EarlyHintRegistrar.cpp
new file mode 100644
index 0000000000..d77c430819
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintRegistrar.cpp
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EarlyHintRegistrar.h"
+
+#include "EarlyHintPreloader.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsXULAppAPI.h"
+
+namespace {
+mozilla::StaticRefPtr<mozilla::net::EarlyHintRegistrar> gSingleton;
+} // namespace
+
+namespace mozilla::net {
+
+namespace {
+
+class EHShutdownObserver final : public nsIObserver {
+ public:
+ EHShutdownObserver() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ private:
+ ~EHShutdownObserver() = default;
+};
+
+NS_IMPL_ISUPPORTS(EHShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+EHShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ EarlyHintRegistrar::CleanUp();
+ return NS_OK;
+}
+
+} // namespace
+
+EarlyHintRegistrar::EarlyHintRegistrar() {
+ // EarlyHintRegistrar is a main-thread-only object.
+ // All the operations should be run on main thread.
+ // It should be used on chrome process only.
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+EarlyHintRegistrar::~EarlyHintRegistrar() { MOZ_ASSERT(NS_IsMainThread()); }
+
+// static
+void EarlyHintRegistrar::CleanUp() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gSingleton) {
+ return;
+ }
+
+ for (auto& preloader : gSingleton->mEarlyHint) {
+ if (auto p = preloader.GetData()) {
+ // Don't delete entry from EarlyHintPreloader, because that would
+ // invalidate the iterator.
+
+ p->CancelChannel(NS_ERROR_ABORT, "EarlyHintRegistrar::CleanUp"_ns,
+ /* aDeleteEntry */ false);
+ }
+ }
+ gSingleton->mEarlyHint.Clear();
+}
+
+// static
+already_AddRefed<EarlyHintRegistrar> EarlyHintRegistrar::GetOrCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gSingleton) {
+ gSingleton = new EarlyHintRegistrar();
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIObserver> observer = new EHShutdownObserver();
+ nsresult rv =
+ obs->AddObserver(observer, "profile-change-net-teardown", false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ mozilla::ClearOnShutdown(&gSingleton);
+ }
+ return do_AddRef(gSingleton);
+}
+
+void EarlyHintRegistrar::DeleteEntry(dom::ContentParentId aCpId,
+ uint64_t aEarlyHintPreloaderId) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<EarlyHintPreloader> ehp = mEarlyHint.Get(aEarlyHintPreloaderId);
+ if (ehp && ehp->IsFromContentParent(aCpId)) {
+ mEarlyHint.Remove(aEarlyHintPreloaderId);
+ }
+}
+
+void EarlyHintRegistrar::RegisterEarlyHint(uint64_t aEarlyHintPreloaderId,
+ EarlyHintPreloader* aEhp) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aEhp);
+
+ mEarlyHint.InsertOrUpdate(aEarlyHintPreloaderId, RefPtr{aEhp});
+}
+
+bool EarlyHintRegistrar::LinkParentChannel(dom::ContentParentId aCpId,
+ uint64_t aEarlyHintPreloaderId,
+ nsIParentChannel* aParent) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aParent);
+
+ RefPtr<EarlyHintPreloader> ehp;
+ bool found = mEarlyHint.Get(aEarlyHintPreloaderId, getter_AddRefs(ehp));
+ if (ehp && ehp->IsFromContentParent(aCpId)) {
+ ehp->OnParentReady(aParent);
+ }
+ MOZ_ASSERT(ehp || !found);
+ return found;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/EarlyHintRegistrar.h b/netwerk/protocol/http/EarlyHintRegistrar.h
new file mode 100644
index 0000000000..2684301090
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintRegistrar.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_EarlyHintRegistrar_h__
+#define mozilla_net_EarlyHintRegistrar_h__
+
+#include "mozilla/RefCounted.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/AlreadyAddRefed.h"
+
+class nsIParentChannel;
+
+namespace mozilla::net {
+
+class EarlyHintPreloader;
+
+/*
+ * Registrar for pairing EarlyHintPreloader and HttpChannelParent via
+ * earlyHintPreloaderId. EarlyHintPreloader has to be registered first.
+ * EarlyHintPreloader::OnParentReady will be invoked to notify the
+ * EarlyHintpreloader about the existence of the associated HttpChannelParent.
+ */
+class EarlyHintRegistrar final : public RefCounted<EarlyHintRegistrar> {
+ using EarlyHintHashtable =
+ nsRefPtrHashtable<nsUint64HashKey, EarlyHintPreloader>;
+
+ public:
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(EarlyHintRegistrar)
+
+ EarlyHintRegistrar();
+ ~EarlyHintRegistrar();
+
+ /*
+ * Store EarlyHintPreloader for the HttpChannelParent to connect back to.
+ *
+ * @param aEarlyHintPreloaderId identifying the EarlyHintPreloader
+ * @param aEhp the EarlyHintPreloader to store
+ */
+ void RegisterEarlyHint(uint64_t aEarlyHintPreloaderId,
+ EarlyHintPreloader* aEhp);
+
+ /*
+ * Link the provided nsIParentChannel with the associated EarlyHintPreloader.
+ *
+ * @param aEarlyHintPreloaderId identifying the EarlyHintPreloader
+ * @param aParent the nsIParentChannel to be paired
+ * @param aChannelId the id of the nsIChannel connecting to the
+ * EarlyHintPreloader.
+ */
+ bool LinkParentChannel(dom::ContentParentId aCpId,
+ uint64_t aEarlyHintPreloaderId,
+ nsIParentChannel* aParent);
+
+ /*
+ * Delete previous stored EarlyHintPreloader
+ *
+ * @param aEarlyHintPreloaderId identifying the EarlyHintPreloader
+ */
+ void DeleteEntry(dom::ContentParentId aCpId, uint64_t aEarlyHintPreloaderId);
+
+ /*
+ * This is called when "profile-change-net-teardown" is observed. We use this
+ * to cancel the ongoing preload and clear the linked EarlyHintPreloader
+ * objects.
+ */
+ static void CleanUp();
+
+ // Singleton accessor
+ static already_AddRefed<EarlyHintRegistrar> GetOrCreate();
+
+ private:
+ // Store unlinked EarlyHintPreloader objects.
+ EarlyHintHashtable mEarlyHint;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_EarlyHintRegistrar_h__
diff --git a/netwerk/protocol/http/EarlyHintsService.cpp b/netwerk/protocol/http/EarlyHintsService.cpp
new file mode 100644
index 0000000000..d8057259b8
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintsService.cpp
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EarlyHintsService.h"
+#include "EarlyHintPreconnect.h"
+#include "EarlyHintPreloader.h"
+#include "mozilla/dom/LinkStyle.h"
+#include "mozilla/PreloadHashKey.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsICookieJarSettings.h"
+#include "nsILoadInfo.h"
+#include "nsIPrincipal.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+
+namespace mozilla::net {
+
+EarlyHintsService::EarlyHintsService()
+ : mOngoingEarlyHints(new OngoingEarlyHints()) {}
+
+// implementing the destructor in the .cpp file to allow EarlyHintsService.h
+// not to include EarlyHintPreloader.h, decoupling the two files and hopefully
+// allow faster compile times
+EarlyHintsService::~EarlyHintsService() = default;
+
+void EarlyHintsService::EarlyHint(
+ const nsACString& aLinkHeader, nsIURI* aBaseURI, nsIChannel* aChannel,
+ const nsACString& aReferrerPolicy, const nsACString& aCSPHeader,
+ dom::CanonicalBrowsingContext* aLoadingBrowsingContext) {
+ mEarlyHintsCount++;
+ if (mFirstEarlyHint.isNothing()) {
+ mFirstEarlyHint.emplace(TimeStamp::NowLoRes());
+ } else {
+ // Only allow one early hint response with link headers. See
+ // https://html.spec.whatwg.org/multipage/semantics.html#early-hints
+ // > Note: Only the first early hint response served during the navigation
+ // > is handled, and it is discarded if it is succeeded by a cross-origin
+ // > redirect.
+ return;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ // We only follow Early Hints sent on the main document. Make sure that we got
+ // the main document channel here.
+ if (loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ MOZ_ASSERT(false, "Early Hint on non-document channel");
+ return;
+ }
+ nsCOMPtr<nsIPrincipal> principal;
+ // We want to set the top-level document as the triggeringPrincipal for the
+ // load of the sub-resources (image, font, fetch, script, style, fetch and in
+ // the future maybe more). We can't use the `triggeringPrincipal` of the main
+ // document channel, because it is the `systemPrincipal` for user initiated
+ // loads. Same for the `LoadInfo::FindPrincipalToInherit(aChannel)`.
+ //
+ // On 3xx redirects of the main document to cross site locations, all Early
+ // Hint preloads get cancelled as specified in the whatwg spec:
+ //
+ // Note: Only the first early hint response served during the navigation is
+ // handled, and it is discarded if it is succeeded by a cross-origin
+ // redirect. [1]
+ //
+ // Therefore the channel doesn't need to change the principal for any reason
+ // and has the correct principal for the whole lifetime.
+ //
+ // [1]: https://html.spec.whatwg.org/multipage/semantics.html#early-hints
+ nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ aChannel, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ if (NS_FAILED(
+ loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)))) {
+ return;
+ }
+
+ // TODO: find out why LinkHeaderParser uses utf16 and check if it can be
+ // changed to utf8
+ auto linkHeaders = ParseLinkHeader(NS_ConvertUTF8toUTF16(aLinkHeader));
+
+ for (auto& linkHeader : linkHeaders) {
+ CollectLinkTypeTelemetry(linkHeader.mRel);
+ if (linkHeader.mRel.LowerCaseEqualsLiteral("preconnect")) {
+ mLinkType |= dom::LinkStyle::ePRECONNECT;
+ OriginAttributes originAttributes;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(
+ aChannel, originAttributes);
+ EarlyHintPreconnect::MaybePreconnect(linkHeader, aBaseURI,
+ std::move(originAttributes));
+ } else if (linkHeader.mRel.LowerCaseEqualsLiteral("preload")) {
+ mLinkType |= dom::LinkStyle::ePRELOAD;
+ EarlyHintPreloader::MaybeCreateAndInsertPreload(
+ mOngoingEarlyHints, linkHeader, aBaseURI, principal,
+ cookieJarSettings, aReferrerPolicy, aCSPHeader,
+ loadInfo->GetBrowsingContextID(), aLoadingBrowsingContext, false);
+ } else if (linkHeader.mRel.LowerCaseEqualsLiteral("modulepreload")) {
+ mLinkType |= dom::LinkStyle::eMODULE_PRELOAD;
+ EarlyHintPreloader::MaybeCreateAndInsertPreload(
+ mOngoingEarlyHints, linkHeader, aBaseURI, principal,
+ cookieJarSettings, aReferrerPolicy, aCSPHeader,
+ loadInfo->GetBrowsingContextID(), aLoadingBrowsingContext, true);
+ }
+ }
+}
+
+void EarlyHintsService::FinalResponse(uint32_t aResponseStatus) {
+ // We will collect telemetry mosly once for a document.
+ // In case of a reddirect this will be called multiple times.
+ CollectTelemetry(Some(aResponseStatus));
+}
+
+void EarlyHintsService::Cancel(const nsACString& aReason) {
+ CollectTelemetry(Nothing());
+ mOngoingEarlyHints->CancelAll(aReason);
+}
+
+void EarlyHintsService::RegisterLinksAndGetConnectArgs(
+ dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks) {
+ mOngoingEarlyHints->RegisterLinksAndGetConnectArgs(aCpId, aOutLinks);
+}
+
+void EarlyHintsService::CollectTelemetry(Maybe<uint32_t> aResponseStatus) {
+ // EH_NUM_OF_HINTS_PER_PAGE is only collected for the 2xx responses,
+ // regardless of the number of received mEarlyHintsCount.
+ // Other telemetry probes are only collected if there was at least one
+ // EarlyHins response.
+ if (aResponseStatus && (*aResponseStatus <= 299)) {
+ Telemetry::Accumulate(Telemetry::EH_NUM_OF_HINTS_PER_PAGE,
+ mEarlyHintsCount);
+ }
+ if (mEarlyHintsCount == 0) {
+ return;
+ }
+
+ Telemetry::LABELS_EH_FINAL_RESPONSE label =
+ Telemetry::LABELS_EH_FINAL_RESPONSE::Cancel;
+ if (aResponseStatus) {
+ if (*aResponseStatus <= 299) {
+ label = Telemetry::LABELS_EH_FINAL_RESPONSE::R2xx;
+
+ MOZ_ASSERT(mFirstEarlyHint);
+ Telemetry::AccumulateTimeDelta(Telemetry::EH_TIME_TO_FINAL_RESPONSE,
+ *mFirstEarlyHint, TimeStamp::NowLoRes());
+ } else if (*aResponseStatus <= 399) {
+ label = Telemetry::LABELS_EH_FINAL_RESPONSE::R3xx;
+ } else if (*aResponseStatus <= 499) {
+ label = Telemetry::LABELS_EH_FINAL_RESPONSE::R4xx;
+ } else {
+ label = Telemetry::LABELS_EH_FINAL_RESPONSE::Other;
+ }
+ }
+
+ Telemetry::AccumulateCategorical(label);
+
+ // Reset telemetry counters and timestamps.
+ mEarlyHintsCount = 0;
+ mFirstEarlyHint = Nothing();
+}
+
+void EarlyHintsService::CollectLinkTypeTelemetry(const nsAString& aRel) {
+ if (aRel.LowerCaseEqualsLiteral("dns-prefetch")) {
+ glean::netwerk::eh_link_type.Get("dns-prefetch"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("icon")) {
+ glean::netwerk::eh_link_type.Get("icon"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("modulepreload")) {
+ glean::netwerk::eh_link_type.Get("modulepreload"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("preconnect")) {
+ glean::netwerk::eh_link_type.Get("preconnect"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("prefetch")) {
+ glean::netwerk::eh_link_type.Get("prefetch"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("preload")) {
+ glean::netwerk::eh_link_type.Get("preload"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("prerender")) {
+ glean::netwerk::eh_link_type.Get("prerender"_ns).Add(1);
+ } else if (aRel.LowerCaseEqualsLiteral("stylesheet")) {
+ glean::netwerk::eh_link_type.Get("stylesheet"_ns).Add(1);
+ } else {
+ glean::netwerk::eh_link_type.Get("other"_ns).Add(1);
+ }
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/EarlyHintsService.h b/netwerk/protocol/http/EarlyHintsService.h
new file mode 100644
index 0000000000..b256759add
--- /dev/null
+++ b/netwerk/protocol/http/EarlyHintsService.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_EarlyHintsService_h
+#define mozilla_net_EarlyHintsService_h
+
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+
+class nsIChannel;
+class nsIURI;
+class nsIInterfaceRequestor;
+namespace mozilla::dom {
+class CanonicalBrowsingContext;
+}
+
+namespace mozilla::net {
+
+class EarlyHintConnectArgs;
+class OngoingEarlyHints;
+
+class EarlyHintsService {
+ public:
+ EarlyHintsService();
+ ~EarlyHintsService();
+ void EarlyHint(const nsACString& aLinkHeader, nsIURI* aBaseURI,
+ nsIChannel* aChannel, const nsACString& aReferrerPolicy,
+ const nsACString& aCSPHeader,
+ dom::CanonicalBrowsingContext* aLoadingBrowsingContext);
+ void FinalResponse(uint32_t aResponseStatus);
+ void Cancel(const nsACString& aReason);
+
+ void RegisterLinksAndGetConnectArgs(
+ dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks);
+
+ uint32_t LinkType() const { return mLinkType; }
+
+ private:
+ void CollectTelemetry(Maybe<uint32_t> aResponseStatus);
+ void CollectLinkTypeTelemetry(const nsAString& aRel);
+
+ Maybe<TimeStamp> mFirstEarlyHint;
+ uint32_t mEarlyHintsCount{0};
+
+ RefPtr<OngoingEarlyHints> mOngoingEarlyHints;
+ uint32_t mLinkType = 0;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_EarlyHintsService_h
diff --git a/netwerk/protocol/http/HPKEConfigManager.sys.mjs b/netwerk/protocol/http/HPKEConfigManager.sys.mjs
new file mode 100644
index 0000000000..b08cc892c5
--- /dev/null
+++ b/netwerk/protocol/http/HPKEConfigManager.sys.mjs
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let knownConfigs = new Map();
+
+export class HPKEConfigManager {
+ static async get(aURL, aOptions = {}) {
+ try {
+ let config = await this.#getInternal(aURL, aOptions);
+ return new Uint8Array(config);
+ } catch (ex) {
+ console.error(ex);
+ return null;
+ }
+ }
+
+ static async #getInternal(aURL, aOptions = {}) {
+ let { maxAge = -1 } = aOptions;
+ let knownConfig = knownConfigs.get(aURL);
+ if (
+ knownConfig &&
+ (maxAge < 0 || Date.now() - knownConfig.fetchDate < maxAge)
+ ) {
+ return knownConfig.config;
+ }
+ return this.fetchAndStore(aURL, aOptions);
+ }
+
+ static async fetchAndStore(aURL, aOptions = {}) {
+ let fetchDate = Date.now();
+ let resp = await fetch(aURL, { signal: aOptions.abortSignal });
+ if (!resp?.ok) {
+ throw new Error(
+ `Fetching HPKE config from ${aURL} failed with error ${resp.status}`
+ );
+ }
+ let config = await resp.blob().then(b => b.arrayBuffer());
+ knownConfigs.set(aURL, { config, fetchDate });
+ return config;
+ }
+}
diff --git a/netwerk/protocol/http/HTTPSRecordResolver.cpp b/netwerk/protocol/http/HTTPSRecordResolver.cpp
new file mode 100644
index 0000000000..79fd92df7f
--- /dev/null
+++ b/netwerk/protocol/http/HTTPSRecordResolver.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HTTPSRecordResolver.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIDNSAdditionalInfo.h"
+#include "nsIDNSService.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsNetCID.h"
+#include "nsAHttpTransaction.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(HTTPSRecordResolver, nsIDNSListener)
+
+HTTPSRecordResolver::HTTPSRecordResolver(nsAHttpTransaction* aTransaction)
+ : mTransaction(aTransaction),
+ mConnInfo(aTransaction->ConnectionInfo()),
+ mCaps(aTransaction->Caps()) {}
+
+HTTPSRecordResolver::~HTTPSRecordResolver() = default;
+
+nsresult HTTPSRecordResolver::FetchHTTPSRRInternal(
+ nsIEventTarget* aTarget, nsICancelable** aDNSRequest) {
+ NS_ENSURE_ARG_POINTER(aTarget);
+
+ // Only fetch HTTPS RR for https.
+ if (!mConnInfo->FirstHopSSL()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsIDNSService::DNSFlags flags =
+ nsIDNSService::GetFlagsFromTRRMode(mConnInfo->GetTRRMode());
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ flags |= nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+
+ nsCOMPtr<nsIDNSAdditionalInfo> info;
+ if (mConnInfo->OriginPort() != NS_HTTPS_DEFAULT_PORT) {
+ dns->NewAdditionalInfo(""_ns, mConnInfo->OriginPort(),
+ getter_AddRefs(info));
+ }
+ return dns->AsyncResolveNative(
+ mConnInfo->GetOrigin(), nsIDNSService::RESOLVE_TYPE_HTTPSSVC, flags, info,
+ this, aTarget, mConnInfo->GetOriginAttributes(), aDNSRequest);
+}
+
+NS_IMETHODIMP HTTPSRecordResolver::OnLookupComplete(nsICancelable* aRequest,
+ nsIDNSRecord* aRecord,
+ nsresult aStatus) {
+ nsCOMPtr<nsIDNSAddrRecord> addrRecord = do_QueryInterface(aRecord);
+ // This will be called again when receving the result of speculatively loading
+ // the addr records of the target name. In this case, just return NS_OK.
+ if (addrRecord) {
+ return NS_OK;
+ }
+
+ if (!mTransaction) {
+ // The connecttion is not interesed in a response anymore.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> record = do_QueryInterface(aRecord);
+ if (!record || NS_FAILED(aStatus)) {
+ return mTransaction->OnHTTPSRRAvailable(nullptr, nullptr);
+ }
+
+ nsCOMPtr<nsISVCBRecord> svcbRecord;
+ if (NS_FAILED(record->GetServiceModeRecord(mCaps & NS_HTTP_DISALLOW_SPDY,
+ mCaps & NS_HTTP_DISALLOW_HTTP3,
+ getter_AddRefs(svcbRecord)))) {
+ return mTransaction->OnHTTPSRRAvailable(record, nullptr);
+ }
+
+ return mTransaction->OnHTTPSRRAvailable(record, svcbRecord);
+}
+
+void HTTPSRecordResolver::PrefetchAddrRecord(const nsACString& aTargetName,
+ bool aRefreshDNS) {
+ MOZ_ASSERT(mTransaction);
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns) {
+ return;
+ }
+
+ nsIDNSService::DNSFlags flags = nsIDNSService::GetFlagsFromTRRMode(
+ mTransaction->ConnectionInfo()->GetTRRMode());
+ if (aRefreshDNS) {
+ flags |= nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+
+ Unused << dns->AsyncResolveNative(
+ aTargetName, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ flags | nsIDNSService::RESOLVE_SPECULATE, nullptr, this,
+ GetCurrentSerialEventTarget(),
+ mTransaction->ConnectionInfo()->GetOriginAttributes(),
+ getter_AddRefs(tmpOutstanding));
+}
+
+void HTTPSRecordResolver::Close() { mTransaction = nullptr; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HTTPSRecordResolver.h b/netwerk/protocol/http/HTTPSRecordResolver.h
new file mode 100644
index 0000000000..2aa5ae55d7
--- /dev/null
+++ b/netwerk/protocol/http/HTTPSRecordResolver.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HTTPSRecordResolver_h__
+#define HTTPSRecordResolver_h__
+
+#include "nsICancelable.h"
+#include "nsIDNSListener.h"
+#include "nsHttpConnectionInfo.h"
+
+namespace mozilla {
+namespace net {
+
+class nsAHttpTransaction;
+
+// This class is the place to put some common code about fetching HTTPS RR.
+class HTTPSRecordResolver : public nsIDNSListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ explicit HTTPSRecordResolver(nsAHttpTransaction* aTransaction);
+ nsresult FetchHTTPSRRInternal(nsIEventTarget* aTarget,
+ nsICancelable** aDNSRequest);
+ void PrefetchAddrRecord(const nsACString& aTargetName, bool aRefreshDNS);
+
+ void Close();
+
+ protected:
+ virtual ~HTTPSRecordResolver();
+
+ private:
+ RefPtr<nsAHttpTransaction> mTransaction;
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ uint32_t mCaps;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // HTTPSRecordResolver_h__
diff --git a/netwerk/protocol/http/Http2Compression.cpp b/netwerk/protocol/http/Http2Compression.cpp
new file mode 100644
index 0000000000..b95883f13f
--- /dev/null
+++ b/netwerk/protocol/http/Http2Compression.cpp
@@ -0,0 +1,1458 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "Http2Compression.h"
+#include "Http2HuffmanIncoming.h"
+#include "Http2HuffmanOutgoing.h"
+#include "mozilla/StaticPtr.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsIMemoryReporter.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+static nsDeque<nvPair>* gStaticHeaders = nullptr;
+
+class HpackStaticTableReporter final : public nsIMemoryReporter {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ HpackStaticTableReporter() = default;
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override {
+ MOZ_COLLECT_REPORT("explicit/network/hpack/static-table", KIND_HEAP,
+ UNITS_BYTES,
+ gStaticHeaders->SizeOfIncludingThis(MallocSizeOf),
+ "Memory usage of HPACK static table.");
+
+ return NS_OK;
+ }
+
+ private:
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ ~HpackStaticTableReporter() = default;
+};
+
+NS_IMPL_ISUPPORTS(HpackStaticTableReporter, nsIMemoryReporter)
+
+class HpackDynamicTableReporter final : public nsIMemoryReporter {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit HpackDynamicTableReporter(Http2BaseCompressor* aCompressor)
+ : mCompressor(aCompressor) {}
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override {
+ MutexAutoLock lock(mMutex);
+ if (mCompressor) {
+ MOZ_COLLECT_REPORT("explicit/network/hpack/dynamic-tables", KIND_HEAP,
+ UNITS_BYTES,
+ mCompressor->SizeOfExcludingThis(MallocSizeOf),
+ "Aggregate memory usage of HPACK dynamic tables.");
+ }
+ return NS_OK;
+ }
+
+ private:
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ ~HpackDynamicTableReporter() = default;
+
+ Mutex mMutex{"HpackDynamicTableReporter"};
+ Http2BaseCompressor* mCompressor MOZ_GUARDED_BY(mMutex);
+
+ friend class Http2BaseCompressor;
+};
+
+NS_IMPL_ISUPPORTS(HpackDynamicTableReporter, nsIMemoryReporter)
+
+StaticRefPtr<HpackStaticTableReporter> gStaticReporter;
+
+void Http2CompressionCleanup() {
+ // this happens after the socket thread has been destroyed
+ delete gStaticHeaders;
+ gStaticHeaders = nullptr;
+ UnregisterStrongMemoryReporter(gStaticReporter);
+ gStaticReporter = nullptr;
+}
+
+static void AddStaticElement(const nsCString& name, const nsCString& value) {
+ nvPair* pair = new nvPair(name, value);
+ gStaticHeaders->Push(pair);
+}
+
+static void AddStaticElement(const nsCString& name) {
+ AddStaticElement(name, ""_ns);
+}
+
+static void InitializeStaticHeaders() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!gStaticHeaders) {
+ gStaticHeaders = new nsDeque<nvPair>();
+ gStaticReporter = new HpackStaticTableReporter();
+ RegisterStrongMemoryReporter(gStaticReporter);
+ AddStaticElement(":authority"_ns);
+ AddStaticElement(":method"_ns, "GET"_ns);
+ AddStaticElement(":method"_ns, "POST"_ns);
+ AddStaticElement(":path"_ns, "/"_ns);
+ AddStaticElement(":path"_ns, "/index.html"_ns);
+ AddStaticElement(":scheme"_ns, "http"_ns);
+ AddStaticElement(":scheme"_ns, "https"_ns);
+ AddStaticElement(":status"_ns, "200"_ns);
+ AddStaticElement(":status"_ns, "204"_ns);
+ AddStaticElement(":status"_ns, "206"_ns);
+ AddStaticElement(":status"_ns, "304"_ns);
+ AddStaticElement(":status"_ns, "400"_ns);
+ AddStaticElement(":status"_ns, "404"_ns);
+ AddStaticElement(":status"_ns, "500"_ns);
+ AddStaticElement("accept-charset"_ns);
+ AddStaticElement("accept-encoding"_ns, "gzip, deflate"_ns);
+ AddStaticElement("accept-language"_ns);
+ AddStaticElement("accept-ranges"_ns);
+ AddStaticElement("accept"_ns);
+ AddStaticElement("access-control-allow-origin"_ns);
+ AddStaticElement("age"_ns);
+ AddStaticElement("allow"_ns);
+ AddStaticElement("authorization"_ns);
+ AddStaticElement("cache-control"_ns);
+ AddStaticElement("content-disposition"_ns);
+ AddStaticElement("content-encoding"_ns);
+ AddStaticElement("content-language"_ns);
+ AddStaticElement("content-length"_ns);
+ AddStaticElement("content-location"_ns);
+ AddStaticElement("content-range"_ns);
+ AddStaticElement("content-type"_ns);
+ AddStaticElement("cookie"_ns);
+ AddStaticElement("date"_ns);
+ AddStaticElement("etag"_ns);
+ AddStaticElement("expect"_ns);
+ AddStaticElement("expires"_ns);
+ AddStaticElement("from"_ns);
+ AddStaticElement("host"_ns);
+ AddStaticElement("if-match"_ns);
+ AddStaticElement("if-modified-since"_ns);
+ AddStaticElement("if-none-match"_ns);
+ AddStaticElement("if-range"_ns);
+ AddStaticElement("if-unmodified-since"_ns);
+ AddStaticElement("last-modified"_ns);
+ AddStaticElement("link"_ns);
+ AddStaticElement("location"_ns);
+ AddStaticElement("max-forwards"_ns);
+ AddStaticElement("proxy-authenticate"_ns);
+ AddStaticElement("proxy-authorization"_ns);
+ AddStaticElement("range"_ns);
+ AddStaticElement("referer"_ns);
+ AddStaticElement("refresh"_ns);
+ AddStaticElement("retry-after"_ns);
+ AddStaticElement("server"_ns);
+ AddStaticElement("set-cookie"_ns);
+ AddStaticElement("strict-transport-security"_ns);
+ AddStaticElement("transfer-encoding"_ns);
+ AddStaticElement("user-agent"_ns);
+ AddStaticElement("vary"_ns);
+ AddStaticElement("via"_ns);
+ AddStaticElement("www-authenticate"_ns);
+ }
+}
+
+size_t nvPair::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+}
+
+size_t nvPair::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+nvFIFO::nvFIFO() { InitializeStaticHeaders(); }
+
+nvFIFO::~nvFIFO() { Clear(); }
+
+void nvFIFO::AddElement(const nsCString& name, const nsCString& value) {
+ nvPair* pair = new nvPair(name, value);
+ mByteCount += pair->Size();
+ MutexAutoLock lock(mMutex);
+ mTable.PushFront(pair);
+}
+
+void nvFIFO::AddElement(const nsCString& name) { AddElement(name, ""_ns); }
+
+void nvFIFO::RemoveElement() {
+ nvPair* pair = nullptr;
+ {
+ MutexAutoLock lock(mMutex);
+ pair = mTable.Pop();
+ }
+ if (pair) {
+ mByteCount -= pair->Size();
+ delete pair;
+ }
+}
+
+uint32_t nvFIFO::ByteCount() const { return mByteCount; }
+
+uint32_t nvFIFO::Length() const {
+ return mTable.GetSize() + gStaticHeaders->GetSize();
+}
+
+uint32_t nvFIFO::VariableLength() const { return mTable.GetSize(); }
+
+size_t nvFIFO::StaticLength() const { return gStaticHeaders->GetSize(); }
+
+void nvFIFO::Clear() {
+ mByteCount = 0;
+ MutexAutoLock lock(mMutex);
+ while (mTable.GetSize()) {
+ delete mTable.Pop();
+ }
+}
+
+const nvPair* nvFIFO::operator[](size_t index) const {
+ // NWGH - ensure index > 0
+ // NWGH - subtract 1 from index here
+ if (index >= (mTable.GetSize() + gStaticHeaders->GetSize())) {
+ MOZ_ASSERT(false);
+ NS_WARNING("nvFIFO Table Out of Range");
+ return nullptr;
+ }
+ if (index >= gStaticHeaders->GetSize()) {
+ return mTable.ObjectAt(index - gStaticHeaders->GetSize());
+ }
+ return gStaticHeaders->ObjectAt(index);
+}
+
+Http2BaseCompressor::Http2BaseCompressor() {
+ mDynamicReporter = new HpackDynamicTableReporter(this);
+ RegisterStrongMemoryReporter(mDynamicReporter);
+}
+
+Http2BaseCompressor::~Http2BaseCompressor() {
+ if (mPeakSize) {
+ Telemetry::Accumulate(mPeakSizeID, mPeakSize);
+ }
+ if (mPeakCount) {
+ Telemetry::Accumulate(mPeakCountID, mPeakCount);
+ }
+ UnregisterStrongMemoryReporter(mDynamicReporter);
+ {
+ MutexAutoLock lock(mDynamicReporter->mMutex);
+ mDynamicReporter->mCompressor = nullptr;
+ }
+ mDynamicReporter = nullptr;
+}
+
+size_t nvFIFO::SizeOfDynamicTable(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t size = 0;
+ MutexAutoLock lock(mMutex);
+ for (const auto elem : mTable) {
+ size += elem->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return size;
+}
+
+void Http2BaseCompressor::ClearHeaderTable() { mHeaderTable.Clear(); }
+
+size_t Http2BaseCompressor::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return mHeaderTable.SizeOfDynamicTable(aMallocSizeOf);
+}
+
+void Http2BaseCompressor::MakeRoom(uint32_t amount, const char* direction) {
+ uint32_t countEvicted = 0;
+ uint32_t bytesEvicted = 0;
+
+ // make room in the header table
+ while (mHeaderTable.VariableLength() &&
+ ((mHeaderTable.ByteCount() + amount) > mMaxBuffer)) {
+ // NWGH - remove the "- 1" here
+ uint32_t index = mHeaderTable.Length() - 1;
+ LOG(("HTTP %s header table index %u %s %s removed for size.\n", direction,
+ index, mHeaderTable[index]->mName.get(),
+ mHeaderTable[index]->mValue.get()));
+ ++countEvicted;
+ bytesEvicted += mHeaderTable[index]->Size();
+ mHeaderTable.RemoveElement();
+ }
+
+ if (!strcmp(direction, "decompressor")) {
+ Telemetry::Accumulate(Telemetry::HPACK_ELEMENTS_EVICTED_DECOMPRESSOR,
+ countEvicted);
+ Telemetry::Accumulate(Telemetry::HPACK_BYTES_EVICTED_DECOMPRESSOR,
+ bytesEvicted);
+ Telemetry::Accumulate(
+ Telemetry::HPACK_BYTES_EVICTED_RATIO_DECOMPRESSOR,
+ (uint32_t)((100.0 * (double)bytesEvicted) / (double)amount));
+ } else {
+ Telemetry::Accumulate(Telemetry::HPACK_ELEMENTS_EVICTED_COMPRESSOR,
+ countEvicted);
+ Telemetry::Accumulate(Telemetry::HPACK_BYTES_EVICTED_COMPRESSOR,
+ bytesEvicted);
+ Telemetry::Accumulate(
+ Telemetry::HPACK_BYTES_EVICTED_RATIO_COMPRESSOR,
+ (uint32_t)((100.0 * (double)bytesEvicted) / (double)amount));
+ }
+}
+
+void Http2BaseCompressor::DumpState(const char* preamble) {
+ if (!LOG_ENABLED()) {
+ return;
+ }
+
+ if (!mDumpTables) {
+ return;
+ }
+
+ LOG(("%s", preamble));
+
+ LOG(("Header Table"));
+ uint32_t i;
+ uint32_t length = mHeaderTable.Length();
+ uint32_t staticLength = mHeaderTable.StaticLength();
+ // NWGH - make i = 1; i <= length; ++i
+ for (i = 0; i < length; ++i) {
+ const nvPair* pair = mHeaderTable[i];
+ // NWGH - make this <= staticLength
+ LOG(("%sindex %u: %s %s", i < staticLength ? "static " : "", i,
+ pair->mName.get(), pair->mValue.get()));
+ }
+}
+
+void Http2BaseCompressor::SetMaxBufferSizeInternal(uint32_t maxBufferSize) {
+ MOZ_ASSERT(maxBufferSize <= mMaxBufferSetting);
+
+ LOG(("Http2BaseCompressor::SetMaxBufferSizeInternal %u called",
+ maxBufferSize));
+
+ while (mHeaderTable.VariableLength() &&
+ (mHeaderTable.ByteCount() > maxBufferSize)) {
+ mHeaderTable.RemoveElement();
+ }
+
+ mMaxBuffer = maxBufferSize;
+}
+
+nsresult Http2BaseCompressor::SetInitialMaxBufferSize(uint32_t maxBufferSize) {
+ MOZ_ASSERT(mSetInitialMaxBufferSizeAllowed);
+
+ if (mSetInitialMaxBufferSizeAllowed) {
+ mMaxBufferSetting = maxBufferSize;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+void Http2BaseCompressor::SetDumpTables(bool dumpTables) {
+ mDumpTables = dumpTables;
+}
+
+nsresult Http2Decompressor::DecodeHeaderBlock(const uint8_t* data,
+ uint32_t datalen,
+ nsACString& output, bool isPush) {
+ mSetInitialMaxBufferSizeAllowed = false;
+ mOffset = 0;
+ mData = data;
+ mDataLen = datalen;
+ mOutput = &output;
+ // Add in some space to hopefully not have to reallocate while decompressing
+ // the headers. 512 bytes seems like a good enough number.
+ mOutput->Truncate();
+ mOutput->SetCapacity(datalen + 512);
+ mHeaderStatus.Truncate();
+ mHeaderHost.Truncate();
+ mHeaderScheme.Truncate();
+ mHeaderPath.Truncate();
+ mHeaderMethod.Truncate();
+ mSeenNonColonHeader = false;
+ mIsPush = isPush;
+
+ nsresult rv = NS_OK;
+ nsresult softfail_rv = NS_OK;
+ while (NS_SUCCEEDED(rv) && (mOffset < mDataLen)) {
+ bool modifiesTable = true;
+ const char* preamble = "Decompressor state after ?";
+ if (mData[mOffset] & 0x80) {
+ rv = DoIndexed();
+ preamble = "Decompressor state after indexed";
+ } else if (mData[mOffset] & 0x40) {
+ rv = DoLiteralWithIncremental();
+ preamble = "Decompressor state after literal with incremental";
+ } else if (mData[mOffset] & 0x20) {
+ rv = DoContextUpdate();
+ preamble = "Decompressor state after context update";
+ } else if (mData[mOffset] & 0x10) {
+ modifiesTable = false;
+ rv = DoLiteralNeverIndexed();
+ preamble = "Decompressor state after literal never index";
+ } else {
+ modifiesTable = false;
+ rv = DoLiteralWithoutIndex();
+ preamble = "Decompressor state after literal without index";
+ }
+ DumpState(preamble);
+ if (rv == NS_ERROR_ILLEGAL_VALUE) {
+ if (modifiesTable) {
+ // Unfortunately, we can't count on our peer now having the same state
+ // as us, so let's terminate the session and we can try again later.
+ return NS_ERROR_FAILURE;
+ }
+
+ // This is an http-level error that we can handle by resetting the stream
+ // in the upper layers. Let's note that we saw this, then continue
+ // decompressing until we either hit the end of the header block or find a
+ // hard failure. That way we won't get an inconsistent compression state
+ // with the server.
+ softfail_rv = rv;
+ rv = NS_OK;
+ } else if (rv == NS_ERROR_NET_RESET) {
+ // This happens when we detect connection-based auth being requested in
+ // the response headers. We'll paper over it for now, and the session will
+ // handle this as if it received RST_STREAM with HTTP_1_1_REQUIRED.
+ softfail_rv = rv;
+ rv = NS_OK;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return softfail_rv;
+}
+
+nsresult Http2Decompressor::DecodeInteger(uint32_t prefixLen, uint32_t& accum) {
+ accum = 0;
+
+ if (prefixLen) {
+ uint32_t mask = (1 << prefixLen) - 1;
+
+ accum = mData[mOffset] & mask;
+ ++mOffset;
+
+ if (accum != mask) {
+ // the simple case for small values
+ return NS_OK;
+ }
+ }
+
+ uint32_t factor = 1; // 128 ^ 0
+
+ // we need a series of bytes. The high bit signifies if we need another one.
+ // The first one is a a factor of 128 ^ 0, the next 128 ^1, the next 128 ^2,
+ // ..
+
+ if (mOffset >= mDataLen) {
+ NS_WARNING("Ran out of data to decode integer");
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+ bool chainBit = mData[mOffset] & 0x80;
+ accum += (mData[mOffset] & 0x7f) * factor;
+
+ ++mOffset;
+ factor = factor * 128;
+
+ while (chainBit) {
+ // really big offsets are just trawling for overflows
+ if (accum >= 0x800000) {
+ NS_WARNING("Decoding integer >= 0x800000");
+ // This is not strictly fatal to the session, but given the fact that
+ // the value is way to large to be reasonable, let's just tell our peer
+ // to go away.
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mOffset >= mDataLen) {
+ NS_WARNING("Ran out of data to decode integer");
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+ chainBit = mData[mOffset] & 0x80;
+ accum += (mData[mOffset] & 0x7f) * factor;
+ ++mOffset;
+ factor = factor * 128;
+ }
+ return NS_OK;
+}
+
+static bool HasConnectionBasedAuth(const nsACString& headerValue) {
+ for (const nsACString& authMethod :
+ nsCCharSeparatedTokenizer(headerValue, '\n').ToRange()) {
+ if (authMethod.LowerCaseEqualsLiteral("ntlm")) {
+ return true;
+ }
+ if (authMethod.LowerCaseEqualsLiteral("negotiate")) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult Http2Decompressor::OutputHeader(const nsACString& name,
+ const nsACString& value) {
+ // exclusions
+ if (!mIsPush &&
+ (name.EqualsLiteral("connection") || name.EqualsLiteral("host") ||
+ name.EqualsLiteral("keep-alive") ||
+ name.EqualsLiteral("proxy-connection") || name.EqualsLiteral("te") ||
+ name.EqualsLiteral("transfer-encoding") ||
+ name.EqualsLiteral("upgrade") || name.Equals(("accept-encoding")))) {
+ nsCString toLog(name);
+ LOG(("HTTP Decompressor illegal response header found, not gatewaying: %s",
+ toLog.get()));
+ return NS_OK;
+ }
+
+ // Bug 1663836: reject invalid HTTP response header names - RFC7540 Sec 10.3
+ const char* cFirst = name.BeginReading();
+ if (cFirst != nullptr && *cFirst == ':') {
+ ++cFirst;
+ }
+ if (!nsHttp::IsValidToken(cFirst, name.EndReading())) {
+ nsCString toLog(name);
+ LOG(("HTTP Decompressor invalid response header found. [%s]\n",
+ toLog.get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Look for upper case characters in the name.
+ for (const char* cPtr = name.BeginReading(); cPtr && cPtr < name.EndReading();
+ ++cPtr) {
+ if (*cPtr <= 'Z' && *cPtr >= 'A') {
+ nsCString toLog(name);
+ LOG(("HTTP Decompressor upper case response header found. [%s]\n",
+ toLog.get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+
+ // Look for CR, LF or NUL in value - could be smuggling (RFC7540 Sec 10.3)
+ // treat as malformed
+ if (!nsHttp::IsReasonableHeaderValue(value)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Status comes first
+ if (name.EqualsLiteral(":status")) {
+ nsAutoCString status("HTTP/2 "_ns);
+ status.Append(value);
+ status.AppendLiteral("\r\n");
+ mOutput->Insert(status, 0);
+ mHeaderStatus = value;
+ } else if (name.EqualsLiteral(":authority")) {
+ mHeaderHost = value;
+ } else if (name.EqualsLiteral(":scheme")) {
+ mHeaderScheme = value;
+ } else if (name.EqualsLiteral(":path")) {
+ mHeaderPath = value;
+ } else if (name.EqualsLiteral(":method")) {
+ mHeaderMethod = value;
+ }
+
+ // http/2 transport level headers shouldn't be gatewayed into http/1
+ bool isColonHeader = false;
+ for (const char* cPtr = name.BeginReading(); cPtr && cPtr < name.EndReading();
+ ++cPtr) {
+ if (*cPtr == ':') {
+ isColonHeader = true;
+ break;
+ }
+ if (*cPtr != ' ' && *cPtr != '\t') {
+ isColonHeader = false;
+ break;
+ }
+ }
+
+ if (isColonHeader) {
+ // :status is the only pseudo-header field allowed in received HEADERS
+ // frames, PUSH_PROMISE allows the other pseudo-header fields
+ if (!name.EqualsLiteral(":status") && !mIsPush) {
+ LOG(("HTTP Decompressor found illegal response pseudo-header %s",
+ name.BeginReading()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (mSeenNonColonHeader) {
+ LOG(("HTTP Decompressor found illegal : header %s", name.BeginReading()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ LOG(("HTTP Decompressor not gatewaying %s into http/1",
+ name.BeginReading()));
+ return NS_OK;
+ }
+
+ LOG(("Http2Decompressor::OutputHeader %s %s", name.BeginReading(),
+ value.BeginReading()));
+ mSeenNonColonHeader = true;
+ mOutput->Append(name);
+ mOutput->AppendLiteral(": ");
+ mOutput->Append(value);
+ mOutput->AppendLiteral("\r\n");
+
+ // Need to check if the server is going to try to speak connection-based auth
+ // with us. If so, we need to kill this via h2, and dial back with http/1.1.
+ // Technically speaking, the server should've just reset or goaway'd us with
+ // HTTP_1_1_REQUIRED, but there are some busted servers out there, so we need
+ // to check on our own to work around them.
+ if (name.EqualsLiteral("www-authenticate") ||
+ name.EqualsLiteral("proxy-authenticate")) {
+ if (HasConnectionBasedAuth(value)) {
+ LOG3(("Http2Decompressor %p connection-based auth found in %s", this,
+ name.BeginReading()));
+ return NS_ERROR_NET_RESET;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult Http2Decompressor::OutputHeader(uint32_t index) {
+ // NWGH - make this < index
+ // bounds check
+ if (mHeaderTable.Length() <= index) {
+ LOG(("Http2Decompressor::OutputHeader index too large %u", index));
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+
+ return OutputHeader(mHeaderTable[index]->mName, mHeaderTable[index]->mValue);
+}
+
+nsresult Http2Decompressor::CopyHeaderString(uint32_t index, nsACString& name) {
+ // NWGH - make this < index
+ // bounds check
+ if (mHeaderTable.Length() <= index) {
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+
+ name = mHeaderTable[index]->mName;
+ return NS_OK;
+}
+
+nsresult Http2Decompressor::CopyStringFromInput(uint32_t bytes,
+ nsACString& val) {
+ if (mOffset + bytes > mDataLen) {
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+
+ val.Assign(reinterpret_cast<const char*>(mData) + mOffset, bytes);
+ mOffset += bytes;
+ return NS_OK;
+}
+
+nsresult Http2Decompressor::DecodeFinalHuffmanCharacter(
+ const HuffmanIncomingTable* table, uint8_t& c, uint8_t& bitsLeft) {
+ MOZ_ASSERT(mOffset <= mDataLen);
+ if (mOffset > mDataLen) {
+ NS_WARNING("DecodeFinalHuffmanCharacter would read beyond end of buffer");
+ return NS_ERROR_FAILURE;
+ }
+ uint8_t mask = (1 << bitsLeft) - 1;
+ uint8_t idx = mData[mOffset - 1] & mask;
+ idx <<= (8 - bitsLeft);
+ // Don't update bitsLeft yet, because we need to check that value against the
+ // number of bits used by our encoding later on. We'll update when we are sure
+ // how many bits we've actually used.
+
+ if (table->IndexHasANextTable(idx)) {
+ // Can't chain to another table when we're all out of bits in the encoding
+ LOG(("DecodeFinalHuffmanCharacter trying to chain when we're out of bits"));
+ return NS_ERROR_FAILURE;
+ }
+
+ const HuffmanIncomingEntry* entry = table->Entry(idx);
+
+ if (bitsLeft < entry->mPrefixLen) {
+ // We don't have enough bits to actually make a match, this is some sort of
+ // invalid coding
+ LOG(("DecodeFinalHuffmanCharacter does't have enough bits to match"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // This is a character!
+ if (entry->mValue == 256) {
+ // EOS
+ LOG(("DecodeFinalHuffmanCharacter actually decoded an EOS"));
+ return NS_ERROR_FAILURE;
+ }
+ c = static_cast<uint8_t>(entry->mValue & 0xFF);
+ bitsLeft -= entry->mPrefixLen;
+
+ return NS_OK;
+}
+
+uint8_t Http2Decompressor::ExtractByte(uint8_t bitsLeft,
+ uint32_t& bytesConsumed) {
+ MOZ_DIAGNOSTIC_ASSERT(mOffset < mDataLen);
+ uint8_t rv;
+
+ if (bitsLeft) {
+ // Need to extract bitsLeft bits from the previous byte, and 8 - bitsLeft
+ // bits from the current byte
+ uint8_t mask = (1 << bitsLeft) - 1;
+ rv = (mData[mOffset - 1] & mask) << (8 - bitsLeft);
+ rv |= (mData[mOffset] & ~mask) >> bitsLeft;
+ } else {
+ rv = mData[mOffset];
+ }
+
+ // We always update these here, under the assumption that all 8 bits we got
+ // here will be used. These may be re-adjusted later in the case that we don't
+ // use up all 8 bits of the byte.
+ ++mOffset;
+ ++bytesConsumed;
+
+ return rv;
+}
+
+nsresult Http2Decompressor::DecodeHuffmanCharacter(
+ const HuffmanIncomingTable* table, uint8_t& c, uint32_t& bytesConsumed,
+ uint8_t& bitsLeft) {
+ uint8_t idx = ExtractByte(bitsLeft, bytesConsumed);
+
+ if (table->IndexHasANextTable(idx)) {
+ if (mOffset >= mDataLen) {
+ if (!bitsLeft || (mOffset > mDataLen)) {
+ // TODO - does this get me into trouble in the new world?
+ // No info left in input to try to consume, we're done
+ LOG(("DecodeHuffmanCharacter all out of bits to consume, can't chain"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // We might get lucky here!
+ return DecodeFinalHuffmanCharacter(table->NextTable(idx), c, bitsLeft);
+ }
+
+ // We're sorry, Mario, but your princess is in another castle
+ return DecodeHuffmanCharacter(table->NextTable(idx), c, bytesConsumed,
+ bitsLeft);
+ }
+
+ const HuffmanIncomingEntry* entry = table->Entry(idx);
+ if (entry->mValue == 256) {
+ LOG(("DecodeHuffmanCharacter found an actual EOS"));
+ return NS_ERROR_FAILURE;
+ }
+ c = static_cast<uint8_t>(entry->mValue & 0xFF);
+
+ // Need to adjust bitsLeft (and possibly other values) because we may not have
+ // consumed all of the bits of the byte we extracted.
+ if (entry->mPrefixLen <= bitsLeft) {
+ bitsLeft -= entry->mPrefixLen;
+ --mOffset;
+ --bytesConsumed;
+ } else {
+ bitsLeft = 8 - (entry->mPrefixLen - bitsLeft);
+ }
+ MOZ_ASSERT(bitsLeft < 8);
+
+ return NS_OK;
+}
+
+nsresult Http2Decompressor::CopyHuffmanStringFromInput(uint32_t bytes,
+ nsACString& val) {
+ if (mOffset + bytes > mDataLen) {
+ LOG(("CopyHuffmanStringFromInput not enough data"));
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t bytesRead = 0;
+ uint8_t bitsLeft = 0;
+ nsAutoCString buf;
+ nsresult rv;
+ uint8_t c;
+
+ while (bytesRead < bytes) {
+ uint32_t bytesConsumed = 0;
+ rv = DecodeHuffmanCharacter(&HuffmanIncomingRoot, c, bytesConsumed,
+ bitsLeft);
+ if (NS_FAILED(rv)) {
+ LOG(("CopyHuffmanStringFromInput failed to decode a character"));
+ return rv;
+ }
+
+ bytesRead += bytesConsumed;
+ buf.Append(c);
+ }
+
+ if (bytesRead > bytes) {
+ LOG(("CopyHuffmanStringFromInput read more bytes than was allowed!"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (bitsLeft) {
+ // The shortest valid code is 4 bits, so we know there can be at most one
+ // character left that our loop didn't decode. Check to see if that's the
+ // case, and if so, add it to our output.
+ rv = DecodeFinalHuffmanCharacter(&HuffmanIncomingRoot, c, bitsLeft);
+ if (NS_SUCCEEDED(rv)) {
+ buf.Append(c);
+ }
+ }
+
+ if (bitsLeft > 7) {
+ LOG(("CopyHuffmanStringFromInput more than 7 bits of padding"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (bitsLeft) {
+ // Any bits left at this point must belong to the EOS symbol, so make sure
+ // they make sense (ie, are all ones)
+ uint8_t mask = (1 << bitsLeft) - 1;
+ uint8_t bits = mData[mOffset - 1] & mask;
+ if (bits != mask) {
+ LOG(
+ ("CopyHuffmanStringFromInput ran out of data but found possible "
+ "non-EOS symbol"));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ val = buf;
+ LOG(("CopyHuffmanStringFromInput decoded a full string!"));
+ return NS_OK;
+}
+
+nsresult Http2Decompressor::DoIndexed() {
+ // this starts with a 1 bit pattern
+ MOZ_ASSERT(mData[mOffset] & 0x80);
+
+ // This is a 7 bit prefix
+
+ uint32_t index;
+ nsresult rv = DecodeInteger(7, index);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ LOG(("HTTP decompressor indexed entry %u\n", index));
+
+ if (index == 0) {
+ return NS_ERROR_FAILURE;
+ }
+ // NWGH - remove this line, since we'll keep everything 1-indexed
+ index--; // Internally, we 0-index everything, since this is, y'know, C++
+
+ return OutputHeader(index);
+}
+
+nsresult Http2Decompressor::DoLiteralInternal(nsACString& name,
+ nsACString& value,
+ uint32_t namePrefixLen) {
+ // guts of doliteralwithoutindex and doliteralwithincremental
+ MOZ_ASSERT(((mData[mOffset] & 0xF0) == 0x00) || // withoutindex
+ ((mData[mOffset] & 0xF0) == 0x10) || // neverindexed
+ ((mData[mOffset] & 0xC0) == 0x40)); // withincremental
+
+ // first let's get the name
+ uint32_t index;
+ nsresult rv = DecodeInteger(namePrefixLen, index);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // sanity check
+ if (mOffset >= mDataLen) {
+ NS_WARNING("Http2 Decompressor ran out of data");
+ // This is session-fatal
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isHuffmanEncoded;
+
+ if (!index) {
+ // name is embedded as a literal
+ uint32_t nameLen;
+ isHuffmanEncoded = mData[mOffset] & (1 << 7);
+ rv = DecodeInteger(7, nameLen);
+ if (NS_SUCCEEDED(rv)) {
+ if (isHuffmanEncoded) {
+ rv = CopyHuffmanStringFromInput(nameLen, name);
+ } else {
+ rv = CopyStringFromInput(nameLen, name);
+ }
+ }
+ LOG(("Http2Decompressor::DoLiteralInternal literal name %s",
+ name.BeginReading()));
+ } else {
+ // NWGH - make this index, not index - 1
+ // name is from headertable
+ rv = CopyHeaderString(index - 1, name);
+ LOG(("Http2Decompressor::DoLiteralInternal indexed name %d %s", index,
+ name.BeginReading()));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // sanity check
+ if (mOffset >= mDataLen) {
+ NS_WARNING("Http2 Decompressor ran out of data");
+ // This is session-fatal
+ return NS_ERROR_FAILURE;
+ }
+
+ // now the value
+ uint32_t valueLen;
+ isHuffmanEncoded = mData[mOffset] & (1 << 7);
+ rv = DecodeInteger(7, valueLen);
+ if (NS_SUCCEEDED(rv)) {
+ if (isHuffmanEncoded) {
+ rv = CopyHuffmanStringFromInput(valueLen, value);
+ } else {
+ rv = CopyStringFromInput(valueLen, value);
+ }
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ int32_t newline = 0;
+ while ((newline = value.FindChar('\n', newline)) != -1) {
+ if (value[newline + 1] == ' ' || value[newline + 1] == '\t') {
+ LOG(("Http2Decompressor::Disallowing folded header value %s",
+ value.BeginReading()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ // Increment this to avoid always finding the same newline and looping
+ // forever
+ ++newline;
+ }
+
+ LOG(("Http2Decompressor::DoLiteralInternal value %s", value.BeginReading()));
+ return NS_OK;
+}
+
+nsresult Http2Decompressor::DoLiteralWithoutIndex() {
+ // this starts with 0000 bit pattern
+ MOZ_ASSERT((mData[mOffset] & 0xF0) == 0x00);
+
+ nsAutoCString name, value;
+ nsresult rv = DoLiteralInternal(name, value, 4);
+
+ LOG(("HTTP decompressor literal without index %s %s\n", name.get(),
+ value.get()));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = OutputHeader(name, value);
+ }
+ return rv;
+}
+
+nsresult Http2Decompressor::DoLiteralWithIncremental() {
+ // this starts with 01 bit pattern
+ MOZ_ASSERT((mData[mOffset] & 0xC0) == 0x40);
+
+ nsAutoCString name, value;
+ nsresult rv = DoLiteralInternal(name, value, 6);
+ if (NS_SUCCEEDED(rv)) {
+ rv = OutputHeader(name, value);
+ }
+ // Let NET_RESET continue on so that we don't get out of sync, as it is just
+ // used to kill the stream, not the session.
+ if (NS_FAILED(rv) && rv != NS_ERROR_NET_RESET) {
+ return rv;
+ }
+
+ uint32_t room = nvPair(name, value).Size();
+ if (room > mMaxBuffer) {
+ ClearHeaderTable();
+ LOG(
+ ("HTTP decompressor literal with index not inserted due to size %u %s "
+ "%s\n",
+ room, name.get(), value.get()));
+ DumpState("Decompressor state after ClearHeaderTable");
+ return rv;
+ }
+
+ MakeRoom(room, "decompressor");
+
+ // Incremental Indexing implicitly adds a row to the header table.
+ mHeaderTable.AddElement(name, value);
+
+ uint32_t currentSize = mHeaderTable.ByteCount();
+ if (currentSize > mPeakSize) {
+ mPeakSize = currentSize;
+ }
+
+ uint32_t currentCount = mHeaderTable.VariableLength();
+ if (currentCount > mPeakCount) {
+ mPeakCount = currentCount;
+ }
+
+ LOG(("HTTP decompressor literal with index 0 %s %s\n", name.get(),
+ value.get()));
+
+ return rv;
+}
+
+nsresult Http2Decompressor::DoLiteralNeverIndexed() {
+ // This starts with 0001 bit pattern
+ MOZ_ASSERT((mData[mOffset] & 0xF0) == 0x10);
+
+ nsAutoCString name, value;
+ nsresult rv = DoLiteralInternal(name, value, 4);
+
+ LOG(("HTTP decompressor literal never indexed %s %s\n", name.get(),
+ value.get()));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = OutputHeader(name, value);
+ }
+ return rv;
+}
+
+nsresult Http2Decompressor::DoContextUpdate() {
+ // This starts with 001 bit pattern
+ MOZ_ASSERT((mData[mOffset] & 0xE0) == 0x20);
+
+ // Getting here means we have to adjust the max table size, because the
+ // compressor on the other end has signaled to us through HPACK (not H2)
+ // that it's using a size different from the currently-negotiated size.
+ // This change could either come about because we've sent a
+ // SETTINGS_HEADER_TABLE_SIZE, or because the encoder has decided that
+ // the current negotiated size doesn't fit its needs (for whatever reason)
+ // and so it needs to change it (either up to the max allowed by our SETTING,
+ // or down to some value below that)
+ uint32_t newMaxSize;
+ nsresult rv = DecodeInteger(5, newMaxSize);
+ LOG(("Http2Decompressor::DoContextUpdate new maximum size %u", newMaxSize));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (newMaxSize > mMaxBufferSetting) {
+ // This is fatal to the session - peer is trying to use a table larger
+ // than we have made available.
+ return NS_ERROR_FAILURE;
+ }
+
+ SetMaxBufferSizeInternal(newMaxSize);
+
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////
+
+nsresult Http2Compressor::EncodeHeaderBlock(
+ const nsCString& nvInput, const nsACString& method, const nsACString& path,
+ const nsACString& host, const nsACString& scheme,
+ const nsACString& protocol, bool simpleConnectForm, nsACString& output) {
+ mSetInitialMaxBufferSizeAllowed = false;
+ mOutput = &output;
+ output.Truncate();
+ mParsedContentLength = -1;
+
+ bool isWebsocket = (!simpleConnectForm && !protocol.IsEmpty());
+
+ // first thing's first - context size updates (if necessary)
+ if (mBufferSizeChangeWaiting) {
+ if (mLowestBufferSizeWaiting < mMaxBufferSetting) {
+ EncodeTableSizeChange(mLowestBufferSizeWaiting);
+ }
+ EncodeTableSizeChange(mMaxBufferSetting);
+ mBufferSizeChangeWaiting = false;
+ }
+
+ // colon headers first
+ if (!simpleConnectForm) {
+ ProcessHeader(nvPair(":method"_ns, method), false, false);
+ ProcessHeader(nvPair(":path"_ns, path), true, false);
+ ProcessHeader(nvPair(":authority"_ns, host), false, false);
+ ProcessHeader(nvPair(":scheme"_ns, scheme), false, false);
+ if (isWebsocket) {
+ ProcessHeader(nvPair(":protocol"_ns, protocol), false, false);
+ }
+ } else {
+ ProcessHeader(nvPair(":method"_ns, method), false, false);
+ ProcessHeader(nvPair(":authority"_ns, host), false, false);
+ }
+
+ // now the non colon headers
+ const char* beginBuffer = nvInput.BeginReading();
+
+ // This strips off the HTTP/1 method+path+version
+ int32_t crlfIndex = nvInput.Find("\r\n");
+ while (true) {
+ int32_t startIndex = crlfIndex + 2;
+
+ crlfIndex = nvInput.Find("\r\n", startIndex);
+ if (crlfIndex == -1) {
+ break;
+ }
+
+ int32_t colonIndex = Substring(nvInput, 0, crlfIndex).Find(":", startIndex);
+ if (colonIndex == -1) {
+ break;
+ }
+
+ nsDependentCSubstring name =
+ Substring(beginBuffer + startIndex, beginBuffer + colonIndex);
+ // all header names are lower case in http/2
+ ToLowerCase(name);
+
+ // exclusions
+ if (name.EqualsLiteral("connection") || name.EqualsLiteral("host") ||
+ name.EqualsLiteral("keep-alive") ||
+ name.EqualsLiteral("proxy-connection") || name.EqualsLiteral("te") ||
+ name.EqualsLiteral("transfer-encoding") ||
+ name.EqualsLiteral("upgrade") ||
+ name.EqualsLiteral("sec-websocket-key")) {
+ continue;
+ }
+
+ // colon headers are for http/2 and this is http/1 input, so that
+ // is probably a smuggling attack of some kind
+ bool isColonHeader = false;
+ for (const char* cPtr = name.BeginReading();
+ cPtr && cPtr < name.EndReading(); ++cPtr) {
+ if (*cPtr == ':') {
+ isColonHeader = true;
+ break;
+ }
+ if (*cPtr != ' ' && *cPtr != '\t') {
+ isColonHeader = false;
+ break;
+ }
+ }
+ if (isColonHeader) {
+ continue;
+ }
+
+ int32_t valueIndex = colonIndex + 1;
+
+ while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ') {
+ ++valueIndex;
+ }
+
+ nsDependentCSubstring value =
+ Substring(beginBuffer + valueIndex, beginBuffer + crlfIndex);
+
+ if (name.EqualsLiteral("content-length")) {
+ int64_t len;
+ nsCString tmp(value);
+ if (nsHttp::ParseInt64(tmp.get(), nullptr, &len)) {
+ mParsedContentLength = len;
+ }
+ }
+
+ if (name.EqualsLiteral("cookie")) {
+ // cookie crumbling
+ bool haveMoreCookies = true;
+ int32_t nextCookie = valueIndex;
+ while (haveMoreCookies) {
+ int32_t semiSpaceIndex =
+ Substring(nvInput, 0, crlfIndex).Find("; ", nextCookie);
+ if (semiSpaceIndex == -1) {
+ haveMoreCookies = false;
+ semiSpaceIndex = crlfIndex;
+ }
+ nsDependentCSubstring cookie =
+ Substring(beginBuffer + nextCookie, beginBuffer + semiSpaceIndex);
+ // cookies less than 20 bytes are not indexed
+ ProcessHeader(nvPair(name, cookie), false, cookie.Length() < 20);
+ nextCookie = semiSpaceIndex + 2;
+ }
+ } else {
+ // allow indexing of every non-cookie except authorization
+ ProcessHeader(nvPair(name, value), false,
+ name.EqualsLiteral("authorization"));
+ }
+ }
+
+ // NB: This is a *really* ugly hack, but to do this in the right place (the
+ // transaction) would require totally reworking how/when the transaction
+ // creates its request stream, which is not worth the effort and risk of
+ // breakage just to add one header only to h2 connections.
+ if (!simpleConnectForm && !isWebsocket) {
+ // Add in TE: trailers for regular requests
+ nsAutoCString te("te");
+ nsAutoCString trailers("trailers");
+ ProcessHeader(nvPair(te, trailers), false, false);
+ }
+
+ mOutput = nullptr;
+ DumpState("Compressor state after EncodeHeaderBlock");
+ return NS_OK;
+}
+
+void Http2Compressor::DoOutput(Http2Compressor::outputCode code,
+ const class nvPair* pair, uint32_t index) {
+ // start Byte needs to be calculated from the offset after
+ // the opcode has been written out in case the output stream
+ // buffer gets resized/relocated
+ uint32_t offset = mOutput->Length();
+ uint8_t* startByte;
+
+ switch (code) {
+ case kNeverIndexedLiteral:
+ LOG(
+ ("HTTP compressor %p neverindex literal with name reference %u %s "
+ "%s\n",
+ this, index, pair->mName.get(), pair->mValue.get()));
+
+ // In this case, the index will have already been adjusted to be 1-based
+ // instead of 0-based.
+ EncodeInteger(4, index); // 0001 4 bit prefix
+ startByte =
+ reinterpret_cast<unsigned char*>(mOutput->BeginWriting()) + offset;
+ *startByte = (*startByte & 0x0f) | 0x10;
+
+ if (!index) {
+ HuffmanAppend(pair->mName);
+ }
+
+ HuffmanAppend(pair->mValue);
+ break;
+
+ case kPlainLiteral:
+ LOG(("HTTP compressor %p noindex literal with name reference %u %s %s\n",
+ this, index, pair->mName.get(), pair->mValue.get()));
+
+ // In this case, the index will have already been adjusted to be 1-based
+ // instead of 0-based.
+ EncodeInteger(4, index); // 0000 4 bit prefix
+ startByte =
+ reinterpret_cast<unsigned char*>(mOutput->BeginWriting()) + offset;
+ *startByte = *startByte & 0x0f;
+
+ if (!index) {
+ HuffmanAppend(pair->mName);
+ }
+
+ HuffmanAppend(pair->mValue);
+ break;
+
+ case kIndexedLiteral:
+ LOG(("HTTP compressor %p literal with name reference %u %s %s\n", this,
+ index, pair->mName.get(), pair->mValue.get()));
+
+ // In this case, the index will have already been adjusted to be 1-based
+ // instead of 0-based.
+ EncodeInteger(6, index); // 01 2 bit prefix
+ startByte =
+ reinterpret_cast<unsigned char*>(mOutput->BeginWriting()) + offset;
+ *startByte = (*startByte & 0x3f) | 0x40;
+
+ if (!index) {
+ HuffmanAppend(pair->mName);
+ }
+
+ HuffmanAppend(pair->mValue);
+ break;
+
+ case kIndex:
+ LOG(("HTTP compressor %p index %u %s %s\n", this, index,
+ pair->mName.get(), pair->mValue.get()));
+ // NWGH - make this plain old index instead of index + 1
+ // In this case, we are passed the raw 0-based C index, and need to
+ // increment to make it 1-based and comply with the spec
+ EncodeInteger(7, index + 1);
+ startByte =
+ reinterpret_cast<unsigned char*>(mOutput->BeginWriting()) + offset;
+ *startByte = *startByte | 0x80; // 1 1 bit prefix
+ break;
+ }
+}
+
+// writes the encoded integer onto the output
+void Http2Compressor::EncodeInteger(uint32_t prefixLen, uint32_t val) {
+ uint32_t mask = (1 << prefixLen) - 1;
+ uint8_t tmp;
+
+ if (val < mask) {
+ // 1 byte encoding!
+ tmp = val;
+ mOutput->Append(reinterpret_cast<char*>(&tmp), 1);
+ return;
+ }
+
+ if (mask) {
+ val -= mask;
+ tmp = mask;
+ mOutput->Append(reinterpret_cast<char*>(&tmp), 1);
+ }
+
+ uint32_t q, r;
+ do {
+ q = val / 128;
+ r = val % 128;
+ tmp = r;
+ if (q) {
+ tmp |= 0x80; // chain bit
+ }
+ val = q;
+ mOutput->Append(reinterpret_cast<char*>(&tmp), 1);
+ } while (q);
+}
+
+void Http2Compressor::HuffmanAppend(const nsCString& value) {
+ nsAutoCString buf;
+ uint8_t bitsLeft = 8;
+ uint32_t length = value.Length();
+ uint32_t offset;
+ uint8_t* startByte;
+
+ for (uint32_t i = 0; i < length; ++i) {
+ uint8_t idx = static_cast<uint8_t>(value[i]);
+ uint8_t huffLength = HuffmanOutgoing[idx].mLength;
+ uint32_t huffValue = HuffmanOutgoing[idx].mValue;
+
+ if (bitsLeft < 8) {
+ // Fill in the least significant <bitsLeft> bits of the previous byte
+ // first
+ uint32_t val;
+ if (huffLength >= bitsLeft) {
+ val = huffValue & ~((1 << (huffLength - bitsLeft)) - 1);
+ val >>= (huffLength - bitsLeft);
+ } else {
+ val = huffValue << (bitsLeft - huffLength);
+ }
+ val &= ((1 << bitsLeft) - 1);
+ offset = buf.Length() - 1;
+ startByte = reinterpret_cast<unsigned char*>(buf.BeginWriting()) + offset;
+ *startByte = *startByte | static_cast<uint8_t>(val & 0xFF);
+ if (huffLength >= bitsLeft) {
+ huffLength -= bitsLeft;
+ bitsLeft = 8;
+ } else {
+ bitsLeft -= huffLength;
+ huffLength = 0;
+ }
+ }
+
+ while (huffLength >= 8) {
+ uint32_t mask = ~((1 << (huffLength - 8)) - 1);
+ uint8_t val = ((huffValue & mask) >> (huffLength - 8)) & 0xFF;
+ buf.Append(reinterpret_cast<char*>(&val), 1);
+ huffLength -= 8;
+ }
+
+ if (huffLength) {
+ // Fill in the most significant <huffLength> bits of the next byte
+ bitsLeft = 8 - huffLength;
+ uint8_t val = (huffValue & ((1 << huffLength) - 1)) << bitsLeft;
+ buf.Append(reinterpret_cast<char*>(&val), 1);
+ }
+ }
+
+ if (bitsLeft != 8) {
+ // Pad the last <bitsLeft> bits with ones, which corresponds to the EOS
+ // encoding
+ uint8_t val = (1 << bitsLeft) - 1;
+ offset = buf.Length() - 1;
+ startByte = reinterpret_cast<unsigned char*>(buf.BeginWriting()) + offset;
+ *startByte = *startByte | val;
+ }
+
+ // Now we know how long our encoded string is, we can fill in our length
+ uint32_t bufLength = buf.Length();
+ offset = mOutput->Length();
+ EncodeInteger(7, bufLength);
+ startByte =
+ reinterpret_cast<unsigned char*>(mOutput->BeginWriting()) + offset;
+ *startByte = *startByte | 0x80;
+
+ // Finally, we can add our REAL data!
+ mOutput->Append(buf);
+ LOG(
+ ("Http2Compressor::HuffmanAppend %p encoded %d byte original on %d "
+ "bytes.\n",
+ this, length, bufLength));
+}
+
+void Http2Compressor::ProcessHeader(const nvPair inputPair, bool noLocalIndex,
+ bool neverIndex) {
+ uint32_t newSize = inputPair.Size();
+ uint32_t headerTableSize = mHeaderTable.Length();
+ uint32_t matchedIndex = 0u;
+ uint32_t nameReference = 0u;
+ bool match = false;
+
+ LOG(("Http2Compressor::ProcessHeader %s %s", inputPair.mName.get(),
+ inputPair.mValue.get()));
+
+ // NWGH - make this index = 1; index <= headerTableSize; ++index
+ for (uint32_t index = 0; index < headerTableSize; ++index) {
+ if (mHeaderTable[index]->mName.Equals(inputPair.mName)) {
+ // NWGH - make this nameReference = index
+ nameReference = index + 1;
+ if (mHeaderTable[index]->mValue.Equals(inputPair.mValue)) {
+ match = true;
+ matchedIndex = index;
+ break;
+ }
+ }
+ }
+
+ // We need to emit a new literal
+ if (!match || noLocalIndex || neverIndex) {
+ if (neverIndex) {
+ DoOutput(kNeverIndexedLiteral, &inputPair, nameReference);
+ DumpState("Compressor state after literal never index");
+ return;
+ }
+
+ if (noLocalIndex || (newSize > (mMaxBuffer / 2)) || (mMaxBuffer < 128)) {
+ DoOutput(kPlainLiteral, &inputPair, nameReference);
+ DumpState("Compressor state after literal without index");
+ return;
+ }
+
+ // make sure to makeroom() first so that any implied items
+ // get preserved.
+ MakeRoom(newSize, "compressor");
+ DoOutput(kIndexedLiteral, &inputPair, nameReference);
+
+ mHeaderTable.AddElement(inputPair.mName, inputPair.mValue);
+ LOG(("HTTP compressor %p new literal placed at index 0\n", this));
+ DumpState("Compressor state after literal with index");
+ return;
+ }
+
+ // emit an index
+ DoOutput(kIndex, &inputPair, matchedIndex);
+
+ DumpState("Compressor state after index");
+}
+
+void Http2Compressor::EncodeTableSizeChange(uint32_t newMaxSize) {
+ uint32_t offset = mOutput->Length();
+ EncodeInteger(5, newMaxSize);
+ uint8_t* startByte =
+ reinterpret_cast<uint8_t*>(mOutput->BeginWriting()) + offset;
+ *startByte = *startByte | 0x20;
+}
+
+void Http2Compressor::SetMaxBufferSize(uint32_t maxBufferSize) {
+ mMaxBufferSetting = maxBufferSize;
+ SetMaxBufferSizeInternal(maxBufferSize);
+ if (!mBufferSizeChangeWaiting) {
+ mBufferSizeChangeWaiting = true;
+ mLowestBufferSizeWaiting = maxBufferSize;
+ } else if (maxBufferSize < mLowestBufferSizeWaiting) {
+ mLowestBufferSizeWaiting = maxBufferSize;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Compression.h b/netwerk/protocol/http/Http2Compression.h
new file mode 100644
index 0000000000..ad47949938
--- /dev/null
+++ b/netwerk/protocol/http/Http2Compression.h
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http2Compression_Internal_h
+#define mozilla_net_Http2Compression_Internal_h
+
+// HPACK - RFC 7541
+// https://www.rfc-editor.org/rfc/rfc7541.txt
+
+#include "mozilla/Attributes.h"
+#include "nsDeque.h"
+#include "nsString.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanIncomingTable;
+
+void Http2CompressionCleanup();
+
+class nvPair {
+ public:
+ nvPair(const nsACString& name, const nsACString& value)
+ : mName(name), mValue(value) {}
+
+ uint32_t Size() const { return mName.Length() + mValue.Length() + 32; }
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ nsCString mName;
+ nsCString mValue;
+};
+
+class nvFIFO {
+ public:
+ nvFIFO();
+ ~nvFIFO();
+ void AddElement(const nsCString& name, const nsCString& value);
+ void AddElement(const nsCString& name);
+ void RemoveElement();
+ uint32_t ByteCount() const;
+ uint32_t Length() const;
+ uint32_t VariableLength() const;
+ size_t StaticLength() const;
+ void Clear();
+ const nvPair* operator[](size_t index) const;
+ size_t SizeOfDynamicTable(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ uint32_t mByteCount{0};
+ nsDeque<nvPair> mTable;
+
+ // This mutex is held when adding or removing elements in the table
+ // and when accessing the table from the main thread (in SizeOfDynamicTable)
+ // Since the operator[] and other const methods are always called
+ // on the socket thread, they don't need to lock the mutex.
+ // Mutable so it can be locked in SizeOfDynamicTable which is const
+ mutable Mutex mMutex MOZ_UNANNOTATED{"nvFIFO"};
+};
+
+class HpackDynamicTableReporter;
+
+class Http2BaseCompressor {
+ public:
+ Http2BaseCompressor();
+ virtual ~Http2BaseCompressor();
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ nsresult SetInitialMaxBufferSize(uint32_t maxBufferSize);
+ void SetDumpTables(bool dumpTables);
+
+ protected:
+ const static uint32_t kDefaultMaxBuffer = 4096;
+
+ virtual void ClearHeaderTable();
+ virtual void MakeRoom(uint32_t amount, const char* direction);
+ virtual void DumpState(const char*);
+ virtual void SetMaxBufferSizeInternal(uint32_t maxBufferSize);
+
+ nsACString* mOutput{nullptr};
+ nvFIFO mHeaderTable;
+
+ uint32_t mMaxBuffer{kDefaultMaxBuffer};
+ uint32_t mMaxBufferSetting{kDefaultMaxBuffer};
+ bool mSetInitialMaxBufferSizeAllowed{true};
+
+ uint32_t mPeakSize{0};
+ uint32_t mPeakCount{0};
+ MOZ_INIT_OUTSIDE_CTOR
+ Telemetry::HistogramID mPeakSizeID;
+ MOZ_INIT_OUTSIDE_CTOR
+ Telemetry::HistogramID mPeakCountID;
+
+ bool mDumpTables{false};
+
+ private:
+ RefPtr<HpackDynamicTableReporter> mDynamicReporter;
+};
+
+class Http2Compressor;
+
+class Http2Decompressor final : public Http2BaseCompressor {
+ public:
+ Http2Decompressor() {
+ mPeakSizeID = Telemetry::HPACK_PEAK_SIZE_DECOMPRESSOR;
+ mPeakCountID = Telemetry::HPACK_PEAK_COUNT_DECOMPRESSOR;
+ };
+ virtual ~Http2Decompressor() = default;
+
+ // NS_OK: Produces the working set of HTTP/1 formatted headers
+ [[nodiscard]] nsresult DecodeHeaderBlock(const uint8_t* data,
+ uint32_t datalen, nsACString& output,
+ bool isPush);
+
+ void GetStatus(nsACString& hdr) { hdr = mHeaderStatus; }
+ void GetHost(nsACString& hdr) { hdr = mHeaderHost; }
+ void GetScheme(nsACString& hdr) { hdr = mHeaderScheme; }
+ void GetPath(nsACString& hdr) { hdr = mHeaderPath; }
+ void GetMethod(nsACString& hdr) { hdr = mHeaderMethod; }
+
+ private:
+ [[nodiscard]] nsresult DoIndexed();
+ [[nodiscard]] nsresult DoLiteralWithoutIndex();
+ [[nodiscard]] nsresult DoLiteralWithIncremental();
+ [[nodiscard]] nsresult DoLiteralInternal(nsACString&, nsACString&, uint32_t);
+ [[nodiscard]] nsresult DoLiteralNeverIndexed();
+ [[nodiscard]] nsresult DoContextUpdate();
+
+ [[nodiscard]] nsresult DecodeInteger(uint32_t prefixLen, uint32_t& accum);
+ [[nodiscard]] nsresult OutputHeader(uint32_t index);
+ [[nodiscard]] nsresult OutputHeader(const nsACString& name,
+ const nsACString& value);
+
+ [[nodiscard]] nsresult CopyHeaderString(uint32_t index, nsACString& name);
+ [[nodiscard]] nsresult CopyStringFromInput(uint32_t bytes, nsACString& val);
+ uint8_t ExtractByte(uint8_t bitsLeft, uint32_t& bytesConsumed);
+ [[nodiscard]] nsresult CopyHuffmanStringFromInput(uint32_t bytes,
+ nsACString& val);
+ [[nodiscard]] nsresult DecodeHuffmanCharacter(
+ const HuffmanIncomingTable* table, uint8_t& c, uint32_t& bytesConsumed,
+ uint8_t& bitsLeft);
+ [[nodiscard]] nsresult DecodeFinalHuffmanCharacter(
+ const HuffmanIncomingTable* table, uint8_t& c, uint8_t& bitsLeft);
+
+ nsCString mHeaderStatus;
+ nsCString mHeaderHost;
+ nsCString mHeaderScheme;
+ nsCString mHeaderPath;
+ nsCString mHeaderMethod;
+
+ // state variables when DecodeBlock() is on the stack
+ uint32_t mOffset{0};
+ const uint8_t* mData{nullptr};
+ uint32_t mDataLen{0};
+ bool mSeenNonColonHeader{false};
+ bool mIsPush{false};
+};
+
+class Http2Compressor final : public Http2BaseCompressor {
+ public:
+ Http2Compressor() {
+ mPeakSizeID = Telemetry::HPACK_PEAK_SIZE_COMPRESSOR;
+ mPeakCountID = Telemetry::HPACK_PEAK_COUNT_COMPRESSOR;
+ };
+ virtual ~Http2Compressor() = default;
+
+ // HTTP/1 formatted header block as input - HTTP/2 formatted
+ // header block as output
+ [[nodiscard]] nsresult EncodeHeaderBlock(
+ const nsCString& nvInput, const nsACString& method,
+ const nsACString& path, const nsACString& host, const nsACString& scheme,
+ const nsACString& protocol, bool simpleConnectForm, nsACString& output);
+
+ int64_t GetParsedContentLength() {
+ return mParsedContentLength;
+ } // -1 on not found
+
+ void SetMaxBufferSize(uint32_t maxBufferSize);
+
+ private:
+ enum outputCode {
+ kNeverIndexedLiteral,
+ kPlainLiteral,
+ kIndexedLiteral,
+ kIndex
+ };
+
+ void DoOutput(Http2Compressor::outputCode code, const class nvPair* pair,
+ uint32_t index);
+ void EncodeInteger(uint32_t prefixLen, uint32_t val);
+ void ProcessHeader(const nvPair inputPair, bool noLocalIndex,
+ bool neverIndex);
+ void HuffmanAppend(const nsCString& value);
+ void EncodeTableSizeChange(uint32_t newMaxSize);
+
+ int64_t mParsedContentLength{-1};
+ bool mBufferSizeChangeWaiting{false};
+ uint32_t mLowestBufferSizeWaiting{0};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http2Compression_Internal_h
diff --git a/netwerk/protocol/http/Http2HuffmanIncoming.h b/netwerk/protocol/http/Http2HuffmanIncoming.h
new file mode 100644
index 0000000000..0b5a8e9a1f
--- /dev/null
+++ b/netwerk/protocol/http/Http2HuffmanIncoming.h
@@ -0,0 +1,709 @@
+/*
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT!
+ */
+#ifndef mozilla__net__Http2HuffmanIncoming_h
+#define mozilla__net__Http2HuffmanIncoming_h
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanIncomingTable;
+
+struct HuffmanIncomingEntry {
+ uint16_t mValue : 9; // 9 bits so it can hold 0..256
+ uint16_t mPrefixLen : 7; // only holds 1..8
+};
+
+// The data members are public only so they can be statically constructed. All
+// accesses should be done through the functions.
+struct HuffmanIncomingTable {
+ // The normal entries, for indices in the range 0..(mNumEntries-1).
+ const HuffmanIncomingEntry* const mEntries;
+
+ // The next tables, for indices in the range mNumEntries..255. Must be
+ // |nullptr| if mIndexOfFirstNextTable is 256.
+ const HuffmanIncomingTable** const mNextTables;
+
+ // The index of the first next table (equal to the number of entries in
+ // mEntries). This cannot be a uint8_t because it can have the value 256,
+ // in which case there are no next tables and mNextTables must be |nullptr|.
+ const uint16_t mIndexOfFirstNextTable;
+
+ const uint8_t mPrefixLen;
+
+ bool IndexHasANextTable(uint8_t aIndex) const {
+ return aIndex >= mIndexOfFirstNextTable;
+ }
+
+ const HuffmanIncomingEntry* Entry(uint8_t aIndex) const {
+ MOZ_ASSERT(aIndex < mIndexOfFirstNextTable);
+ return &mEntries[aIndex];
+ }
+
+ const HuffmanIncomingTable* NextTable(uint8_t aIndex) const {
+ MOZ_ASSERT(aIndex >= mIndexOfFirstNextTable);
+ return mNextTables[aIndex - mIndexOfFirstNextTable];
+ }
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_254[] = {
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2}, {33, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2}, {34, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2}, {40, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+ {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2}, {41, 2},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_254 = {
+ HuffmanIncomingEntries_254, nullptr, 256, 2};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_254[] = {
+ {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3},
+ {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3},
+ {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3},
+ {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3}, {92, 3},
+ {92, 3}, {92, 3}, {92, 3}, {92, 3}, {195, 3}, {195, 3}, {195, 3},
+ {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3},
+ {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3},
+ {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3},
+ {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3}, {195, 3},
+ {195, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3},
+ {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3},
+ {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3},
+ {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3},
+ {208, 3}, {208, 3}, {208, 3}, {208, 3}, {208, 3}, {128, 4}, {128, 4},
+ {128, 4}, {128, 4}, {128, 4}, {128, 4}, {128, 4}, {128, 4}, {128, 4},
+ {128, 4}, {128, 4}, {128, 4}, {128, 4}, {128, 4}, {128, 4}, {128, 4},
+ {130, 4}, {130, 4}, {130, 4}, {130, 4}, {130, 4}, {130, 4}, {130, 4},
+ {130, 4}, {130, 4}, {130, 4}, {130, 4}, {130, 4}, {130, 4}, {130, 4},
+ {130, 4}, {130, 4}, {131, 4}, {131, 4}, {131, 4}, {131, 4}, {131, 4},
+ {131, 4}, {131, 4}, {131, 4}, {131, 4}, {131, 4}, {131, 4}, {131, 4},
+ {131, 4}, {131, 4}, {131, 4}, {131, 4}, {162, 4}, {162, 4}, {162, 4},
+ {162, 4}, {162, 4}, {162, 4}, {162, 4}, {162, 4}, {162, 4}, {162, 4},
+ {162, 4}, {162, 4}, {162, 4}, {162, 4}, {162, 4}, {162, 4}, {184, 4},
+ {184, 4}, {184, 4}, {184, 4}, {184, 4}, {184, 4}, {184, 4}, {184, 4},
+ {184, 4}, {184, 4}, {184, 4}, {184, 4}, {184, 4}, {184, 4}, {184, 4},
+ {184, 4}, {194, 4}, {194, 4}, {194, 4}, {194, 4}, {194, 4}, {194, 4},
+ {194, 4}, {194, 4}, {194, 4}, {194, 4}, {194, 4}, {194, 4}, {194, 4},
+ {194, 4}, {194, 4}, {194, 4}, {224, 4}, {224, 4}, {224, 4}, {224, 4},
+ {224, 4}, {224, 4}, {224, 4}, {224, 4}, {224, 4}, {224, 4}, {224, 4},
+ {224, 4}, {224, 4}, {224, 4}, {224, 4}, {224, 4}, {226, 4}, {226, 4},
+ {226, 4}, {226, 4}, {226, 4}, {226, 4}, {226, 4}, {226, 4}, {226, 4},
+ {226, 4}, {226, 4}, {226, 4}, {226, 4}, {226, 4}, {226, 4}, {226, 4},
+ {153, 5}, {153, 5}, {153, 5}, {153, 5}, {153, 5}, {153, 5}, {153, 5},
+ {153, 5}, {161, 5}, {161, 5}, {161, 5}, {161, 5}, {161, 5}, {161, 5},
+ {161, 5}, {161, 5}, {167, 5}, {167, 5}, {167, 5}, {167, 5}, {167, 5},
+ {167, 5}, {167, 5}, {167, 5}, {172, 5}, {172, 5}, {172, 5}, {172, 5},
+ {172, 5}, {172, 5}, {172, 5}, {172, 5},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_254 = {
+ HuffmanIncomingEntries_255_254, nullptr, 256, 5};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_246[] = {
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1}, {199, 1},
+ {199, 1}, {199, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1}, {207, 1},
+ {207, 1}, {207, 1}, {207, 1}, {207, 1},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_246 = {
+ HuffmanIncomingEntries_255_255_246, nullptr, 256, 1};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_247[] = {
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1}, {234, 1},
+ {234, 1}, {234, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1}, {235, 1},
+ {235, 1}, {235, 1}, {235, 1}, {235, 1},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_247 = {
+ HuffmanIncomingEntries_255_255_247, nullptr, 256, 1};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_248[] = {
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2}, {192, 2},
+ {192, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2}, {193, 2},
+ {193, 2}, {193, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2}, {200, 2},
+ {200, 2}, {200, 2}, {200, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2}, {201, 2},
+ {201, 2}, {201, 2}, {201, 2}, {201, 2},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_248 = {
+ HuffmanIncomingEntries_255_255_248, nullptr, 256, 2};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_249[] = {
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2}, {202, 2},
+ {202, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2}, {205, 2},
+ {205, 2}, {205, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2}, {210, 2},
+ {210, 2}, {210, 2}, {210, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2}, {213, 2},
+ {213, 2}, {213, 2}, {213, 2}, {213, 2},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_249 = {
+ HuffmanIncomingEntries_255_255_249, nullptr, 256, 2};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_250[] = {
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2}, {218, 2},
+ {218, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2}, {219, 2},
+ {219, 2}, {219, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2}, {238, 2},
+ {238, 2}, {238, 2}, {238, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2}, {240, 2},
+ {240, 2}, {240, 2}, {240, 2}, {240, 2},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_250 = {
+ HuffmanIncomingEntries_255_255_250, nullptr, 256, 2};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_251[] = {
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2}, {242, 2},
+ {242, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2}, {243, 2},
+ {243, 2}, {243, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2}, {255, 2},
+ {255, 2}, {255, 2}, {255, 2}, {203, 3}, {203, 3}, {203, 3}, {203, 3},
+ {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3},
+ {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3},
+ {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3},
+ {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3}, {203, 3},
+ {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3},
+ {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3},
+ {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3},
+ {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3}, {204, 3},
+ {204, 3}, {204, 3}, {204, 3}, {204, 3},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_251 = {
+ HuffmanIncomingEntries_255_255_251, nullptr, 256, 3};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_252[] = {
+ {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3},
+ {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3},
+ {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3},
+ {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3}, {211, 3},
+ {211, 3}, {211, 3}, {211, 3}, {211, 3}, {212, 3}, {212, 3}, {212, 3},
+ {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3},
+ {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3},
+ {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3},
+ {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3}, {212, 3},
+ {212, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3},
+ {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3},
+ {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3},
+ {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3},
+ {214, 3}, {214, 3}, {214, 3}, {214, 3}, {214, 3}, {221, 3}, {221, 3},
+ {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3},
+ {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3},
+ {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3},
+ {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3}, {221, 3},
+ {221, 3}, {221, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3},
+ {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3},
+ {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3},
+ {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3},
+ {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {222, 3}, {223, 3},
+ {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3},
+ {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3},
+ {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3},
+ {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3}, {223, 3},
+ {223, 3}, {223, 3}, {223, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3},
+ {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3},
+ {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3},
+ {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3},
+ {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3}, {241, 3},
+ {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3},
+ {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3},
+ {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3},
+ {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3}, {244, 3},
+ {244, 3}, {244, 3}, {244, 3}, {244, 3},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_252 = {
+ HuffmanIncomingEntries_255_255_252, nullptr, 256, 3};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_253[] = {
+ {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3},
+ {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3},
+ {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3},
+ {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3}, {245, 3},
+ {245, 3}, {245, 3}, {245, 3}, {245, 3}, {246, 3}, {246, 3}, {246, 3},
+ {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3},
+ {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3},
+ {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3},
+ {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3}, {246, 3},
+ {246, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3},
+ {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3},
+ {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3},
+ {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3},
+ {247, 3}, {247, 3}, {247, 3}, {247, 3}, {247, 3}, {248, 3}, {248, 3},
+ {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3},
+ {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3},
+ {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3},
+ {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3}, {248, 3},
+ {248, 3}, {248, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3},
+ {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3},
+ {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3},
+ {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3},
+ {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {250, 3}, {251, 3},
+ {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3},
+ {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3},
+ {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3},
+ {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3}, {251, 3},
+ {251, 3}, {251, 3}, {251, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3},
+ {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3},
+ {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3},
+ {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3},
+ {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3}, {252, 3},
+ {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3},
+ {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3},
+ {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3},
+ {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3}, {253, 3},
+ {253, 3}, {253, 3}, {253, 3}, {253, 3},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_253 = {
+ HuffmanIncomingEntries_255_255_253, nullptr, 256, 3};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_254[] = {
+ {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3},
+ {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3},
+ {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3},
+ {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3}, {254, 3},
+ {254, 3}, {254, 3}, {254, 3}, {254, 3}, {2, 4}, {2, 4}, {2, 4},
+ {2, 4}, {2, 4}, {2, 4}, {2, 4}, {2, 4}, {2, 4}, {2, 4},
+ {2, 4}, {2, 4}, {2, 4}, {2, 4}, {2, 4}, {2, 4}, {3, 4},
+ {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4},
+ {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4},
+ {3, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4},
+ {4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4},
+ {4, 4}, {4, 4}, {4, 4}, {5, 4}, {5, 4}, {5, 4}, {5, 4},
+ {5, 4}, {5, 4}, {5, 4}, {5, 4}, {5, 4}, {5, 4}, {5, 4},
+ {5, 4}, {5, 4}, {5, 4}, {5, 4}, {5, 4}, {6, 4}, {6, 4},
+ {6, 4}, {6, 4}, {6, 4}, {6, 4}, {6, 4}, {6, 4}, {6, 4},
+ {6, 4}, {6, 4}, {6, 4}, {6, 4}, {6, 4}, {6, 4}, {6, 4},
+ {7, 4}, {7, 4}, {7, 4}, {7, 4}, {7, 4}, {7, 4}, {7, 4},
+ {7, 4}, {7, 4}, {7, 4}, {7, 4}, {7, 4}, {7, 4}, {7, 4},
+ {7, 4}, {7, 4}, {8, 4}, {8, 4}, {8, 4}, {8, 4}, {8, 4},
+ {8, 4}, {8, 4}, {8, 4}, {8, 4}, {8, 4}, {8, 4}, {8, 4},
+ {8, 4}, {8, 4}, {8, 4}, {8, 4}, {11, 4}, {11, 4}, {11, 4},
+ {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4},
+ {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {12, 4},
+ {12, 4}, {12, 4}, {12, 4}, {12, 4}, {12, 4}, {12, 4}, {12, 4},
+ {12, 4}, {12, 4}, {12, 4}, {12, 4}, {12, 4}, {12, 4}, {12, 4},
+ {12, 4}, {14, 4}, {14, 4}, {14, 4}, {14, 4}, {14, 4}, {14, 4},
+ {14, 4}, {14, 4}, {14, 4}, {14, 4}, {14, 4}, {14, 4}, {14, 4},
+ {14, 4}, {14, 4}, {14, 4}, {15, 4}, {15, 4}, {15, 4}, {15, 4},
+ {15, 4}, {15, 4}, {15, 4}, {15, 4}, {15, 4}, {15, 4}, {15, 4},
+ {15, 4}, {15, 4}, {15, 4}, {15, 4}, {15, 4}, {16, 4}, {16, 4},
+ {16, 4}, {16, 4}, {16, 4}, {16, 4}, {16, 4}, {16, 4}, {16, 4},
+ {16, 4}, {16, 4}, {16, 4}, {16, 4}, {16, 4}, {16, 4}, {16, 4},
+ {17, 4}, {17, 4}, {17, 4}, {17, 4}, {17, 4}, {17, 4}, {17, 4},
+ {17, 4}, {17, 4}, {17, 4}, {17, 4}, {17, 4}, {17, 4}, {17, 4},
+ {17, 4}, {17, 4}, {18, 4}, {18, 4}, {18, 4}, {18, 4}, {18, 4},
+ {18, 4}, {18, 4}, {18, 4}, {18, 4}, {18, 4}, {18, 4}, {18, 4},
+ {18, 4}, {18, 4}, {18, 4}, {18, 4},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_254 = {
+ HuffmanIncomingEntries_255_255_254, nullptr, 256, 4};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_255[] = {
+ {19, 4}, {19, 4}, {19, 4}, {19, 4}, {19, 4}, {19, 4}, {19, 4},
+ {19, 4}, {19, 4}, {19, 4}, {19, 4}, {19, 4}, {19, 4}, {19, 4},
+ {19, 4}, {19, 4}, {20, 4}, {20, 4}, {20, 4}, {20, 4}, {20, 4},
+ {20, 4}, {20, 4}, {20, 4}, {20, 4}, {20, 4}, {20, 4}, {20, 4},
+ {20, 4}, {20, 4}, {20, 4}, {20, 4}, {21, 4}, {21, 4}, {21, 4},
+ {21, 4}, {21, 4}, {21, 4}, {21, 4}, {21, 4}, {21, 4}, {21, 4},
+ {21, 4}, {21, 4}, {21, 4}, {21, 4}, {21, 4}, {21, 4}, {23, 4},
+ {23, 4}, {23, 4}, {23, 4}, {23, 4}, {23, 4}, {23, 4}, {23, 4},
+ {23, 4}, {23, 4}, {23, 4}, {23, 4}, {23, 4}, {23, 4}, {23, 4},
+ {23, 4}, {24, 4}, {24, 4}, {24, 4}, {24, 4}, {24, 4}, {24, 4},
+ {24, 4}, {24, 4}, {24, 4}, {24, 4}, {24, 4}, {24, 4}, {24, 4},
+ {24, 4}, {24, 4}, {24, 4}, {25, 4}, {25, 4}, {25, 4}, {25, 4},
+ {25, 4}, {25, 4}, {25, 4}, {25, 4}, {25, 4}, {25, 4}, {25, 4},
+ {25, 4}, {25, 4}, {25, 4}, {25, 4}, {25, 4}, {26, 4}, {26, 4},
+ {26, 4}, {26, 4}, {26, 4}, {26, 4}, {26, 4}, {26, 4}, {26, 4},
+ {26, 4}, {26, 4}, {26, 4}, {26, 4}, {26, 4}, {26, 4}, {26, 4},
+ {27, 4}, {27, 4}, {27, 4}, {27, 4}, {27, 4}, {27, 4}, {27, 4},
+ {27, 4}, {27, 4}, {27, 4}, {27, 4}, {27, 4}, {27, 4}, {27, 4},
+ {27, 4}, {27, 4}, {28, 4}, {28, 4}, {28, 4}, {28, 4}, {28, 4},
+ {28, 4}, {28, 4}, {28, 4}, {28, 4}, {28, 4}, {28, 4}, {28, 4},
+ {28, 4}, {28, 4}, {28, 4}, {28, 4}, {29, 4}, {29, 4}, {29, 4},
+ {29, 4}, {29, 4}, {29, 4}, {29, 4}, {29, 4}, {29, 4}, {29, 4},
+ {29, 4}, {29, 4}, {29, 4}, {29, 4}, {29, 4}, {29, 4}, {30, 4},
+ {30, 4}, {30, 4}, {30, 4}, {30, 4}, {30, 4}, {30, 4}, {30, 4},
+ {30, 4}, {30, 4}, {30, 4}, {30, 4}, {30, 4}, {30, 4}, {30, 4},
+ {30, 4}, {31, 4}, {31, 4}, {31, 4}, {31, 4}, {31, 4}, {31, 4},
+ {31, 4}, {31, 4}, {31, 4}, {31, 4}, {31, 4}, {31, 4}, {31, 4},
+ {31, 4}, {31, 4}, {31, 4}, {127, 4}, {127, 4}, {127, 4}, {127, 4},
+ {127, 4}, {127, 4}, {127, 4}, {127, 4}, {127, 4}, {127, 4}, {127, 4},
+ {127, 4}, {127, 4}, {127, 4}, {127, 4}, {127, 4}, {220, 4}, {220, 4},
+ {220, 4}, {220, 4}, {220, 4}, {220, 4}, {220, 4}, {220, 4}, {220, 4},
+ {220, 4}, {220, 4}, {220, 4}, {220, 4}, {220, 4}, {220, 4}, {220, 4},
+ {249, 4}, {249, 4}, {249, 4}, {249, 4}, {249, 4}, {249, 4}, {249, 4},
+ {249, 4}, {249, 4}, {249, 4}, {249, 4}, {249, 4}, {249, 4}, {249, 4},
+ {249, 4}, {249, 4}, {10, 6}, {10, 6}, {10, 6}, {10, 6}, {13, 6},
+ {13, 6}, {13, 6}, {13, 6}, {22, 6}, {22, 6}, {22, 6}, {22, 6},
+ {256, 6}, {256, 6}, {256, 6}, {256, 6},
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_255 = {
+ HuffmanIncomingEntries_255_255_255, nullptr, 256, 6};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255[] = {
+ {176, 5}, {176, 5}, {176, 5}, {176, 5}, {176, 5}, {176, 5}, {176, 5},
+ {176, 5}, {177, 5}, {177, 5}, {177, 5}, {177, 5}, {177, 5}, {177, 5},
+ {177, 5}, {177, 5}, {179, 5}, {179, 5}, {179, 5}, {179, 5}, {179, 5},
+ {179, 5}, {179, 5}, {179, 5}, {209, 5}, {209, 5}, {209, 5}, {209, 5},
+ {209, 5}, {209, 5}, {209, 5}, {209, 5}, {216, 5}, {216, 5}, {216, 5},
+ {216, 5}, {216, 5}, {216, 5}, {216, 5}, {216, 5}, {217, 5}, {217, 5},
+ {217, 5}, {217, 5}, {217, 5}, {217, 5}, {217, 5}, {217, 5}, {227, 5},
+ {227, 5}, {227, 5}, {227, 5}, {227, 5}, {227, 5}, {227, 5}, {227, 5},
+ {229, 5}, {229, 5}, {229, 5}, {229, 5}, {229, 5}, {229, 5}, {229, 5},
+ {229, 5}, {230, 5}, {230, 5}, {230, 5}, {230, 5}, {230, 5}, {230, 5},
+ {230, 5}, {230, 5}, {129, 6}, {129, 6}, {129, 6}, {129, 6}, {132, 6},
+ {132, 6}, {132, 6}, {132, 6}, {133, 6}, {133, 6}, {133, 6}, {133, 6},
+ {134, 6}, {134, 6}, {134, 6}, {134, 6}, {136, 6}, {136, 6}, {136, 6},
+ {136, 6}, {146, 6}, {146, 6}, {146, 6}, {146, 6}, {154, 6}, {154, 6},
+ {154, 6}, {154, 6}, {156, 6}, {156, 6}, {156, 6}, {156, 6}, {160, 6},
+ {160, 6}, {160, 6}, {160, 6}, {163, 6}, {163, 6}, {163, 6}, {163, 6},
+ {164, 6}, {164, 6}, {164, 6}, {164, 6}, {169, 6}, {169, 6}, {169, 6},
+ {169, 6}, {170, 6}, {170, 6}, {170, 6}, {170, 6}, {173, 6}, {173, 6},
+ {173, 6}, {173, 6}, {178, 6}, {178, 6}, {178, 6}, {178, 6}, {181, 6},
+ {181, 6}, {181, 6}, {181, 6}, {185, 6}, {185, 6}, {185, 6}, {185, 6},
+ {186, 6}, {186, 6}, {186, 6}, {186, 6}, {187, 6}, {187, 6}, {187, 6},
+ {187, 6}, {189, 6}, {189, 6}, {189, 6}, {189, 6}, {190, 6}, {190, 6},
+ {190, 6}, {190, 6}, {196, 6}, {196, 6}, {196, 6}, {196, 6}, {198, 6},
+ {198, 6}, {198, 6}, {198, 6}, {228, 6}, {228, 6}, {228, 6}, {228, 6},
+ {232, 6}, {232, 6}, {232, 6}, {232, 6}, {233, 6}, {233, 6}, {233, 6},
+ {233, 6}, {1, 7}, {1, 7}, {135, 7}, {135, 7}, {137, 7}, {137, 7},
+ {138, 7}, {138, 7}, {139, 7}, {139, 7}, {140, 7}, {140, 7}, {141, 7},
+ {141, 7}, {143, 7}, {143, 7}, {147, 7}, {147, 7}, {149, 7}, {149, 7},
+ {150, 7}, {150, 7}, {151, 7}, {151, 7}, {152, 7}, {152, 7}, {155, 7},
+ {155, 7}, {157, 7}, {157, 7}, {158, 7}, {158, 7}, {165, 7}, {165, 7},
+ {166, 7}, {166, 7}, {168, 7}, {168, 7}, {174, 7}, {174, 7}, {175, 7},
+ {175, 7}, {180, 7}, {180, 7}, {182, 7}, {182, 7}, {183, 7}, {183, 7},
+ {188, 7}, {188, 7}, {191, 7}, {191, 7}, {197, 7}, {197, 7}, {231, 7},
+ {231, 7}, {239, 7}, {239, 7}, {9, 8}, {142, 8}, {144, 8}, {145, 8},
+ {148, 8}, {159, 8}, {171, 8}, {206, 8}, {215, 8}, {225, 8}, {236, 8},
+ {237, 8},
+};
+
+static const HuffmanIncomingTable* HuffmanIncomingNextTables_255_255[] = {
+ &HuffmanIncoming_255_255_246, &HuffmanIncoming_255_255_247,
+ &HuffmanIncoming_255_255_248, &HuffmanIncoming_255_255_249,
+ &HuffmanIncoming_255_255_250, &HuffmanIncoming_255_255_251,
+ &HuffmanIncoming_255_255_252, &HuffmanIncoming_255_255_253,
+ &HuffmanIncoming_255_255_254, &HuffmanIncoming_255_255_255,
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255 = {
+ HuffmanIncomingEntries_255_255, HuffmanIncomingNextTables_255_255, 246, 8};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255[] = {
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2}, {63, 2},
+ {63, 2}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3},
+ {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3},
+ {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3},
+ {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3},
+ {39, 3}, {39, 3}, {39, 3}, {39, 3}, {39, 3}, {43, 3}, {43, 3},
+ {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3},
+ {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3},
+ {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3},
+ {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3}, {43, 3},
+ {43, 3}, {43, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3},
+ {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3},
+ {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3},
+ {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3},
+ {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {124, 3}, {35, 4},
+ {35, 4}, {35, 4}, {35, 4}, {35, 4}, {35, 4}, {35, 4}, {35, 4},
+ {35, 4}, {35, 4}, {35, 4}, {35, 4}, {35, 4}, {35, 4}, {35, 4},
+ {35, 4}, {62, 4}, {62, 4}, {62, 4}, {62, 4}, {62, 4}, {62, 4},
+ {62, 4}, {62, 4}, {62, 4}, {62, 4}, {62, 4}, {62, 4}, {62, 4},
+ {62, 4}, {62, 4}, {62, 4}, {0, 5}, {0, 5}, {0, 5}, {0, 5},
+ {0, 5}, {0, 5}, {0, 5}, {0, 5}, {36, 5}, {36, 5}, {36, 5},
+ {36, 5}, {36, 5}, {36, 5}, {36, 5}, {36, 5}, {64, 5}, {64, 5},
+ {64, 5}, {64, 5}, {64, 5}, {64, 5}, {64, 5}, {64, 5}, {91, 5},
+ {91, 5}, {91, 5}, {91, 5}, {91, 5}, {91, 5}, {91, 5}, {91, 5},
+ {93, 5}, {93, 5}, {93, 5}, {93, 5}, {93, 5}, {93, 5}, {93, 5},
+ {93, 5}, {126, 5}, {126, 5}, {126, 5}, {126, 5}, {126, 5}, {126, 5},
+ {126, 5}, {126, 5}, {94, 6}, {94, 6}, {94, 6}, {94, 6}, {125, 6},
+ {125, 6}, {125, 6}, {125, 6}, {60, 7}, {60, 7}, {96, 7}, {96, 7},
+ {123, 7}, {123, 7},
+};
+
+static const HuffmanIncomingTable* HuffmanIncomingNextTables_255[] = {
+ &HuffmanIncoming_255_254,
+ &HuffmanIncoming_255_255,
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255 = {
+ HuffmanIncomingEntries_255, HuffmanIncomingNextTables_255, 254, 7};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntriesRoot[] = {
+ {48, 5}, {48, 5}, {48, 5}, {48, 5}, {48, 5}, {48, 5}, {48, 5},
+ {48, 5}, {49, 5}, {49, 5}, {49, 5}, {49, 5}, {49, 5}, {49, 5},
+ {49, 5}, {49, 5}, {50, 5}, {50, 5}, {50, 5}, {50, 5}, {50, 5},
+ {50, 5}, {50, 5}, {50, 5}, {97, 5}, {97, 5}, {97, 5}, {97, 5},
+ {97, 5}, {97, 5}, {97, 5}, {97, 5}, {99, 5}, {99, 5}, {99, 5},
+ {99, 5}, {99, 5}, {99, 5}, {99, 5}, {99, 5}, {101, 5}, {101, 5},
+ {101, 5}, {101, 5}, {101, 5}, {101, 5}, {101, 5}, {101, 5}, {105, 5},
+ {105, 5}, {105, 5}, {105, 5}, {105, 5}, {105, 5}, {105, 5}, {105, 5},
+ {111, 5}, {111, 5}, {111, 5}, {111, 5}, {111, 5}, {111, 5}, {111, 5},
+ {111, 5}, {115, 5}, {115, 5}, {115, 5}, {115, 5}, {115, 5}, {115, 5},
+ {115, 5}, {115, 5}, {116, 5}, {116, 5}, {116, 5}, {116, 5}, {116, 5},
+ {116, 5}, {116, 5}, {116, 5}, {32, 6}, {32, 6}, {32, 6}, {32, 6},
+ {37, 6}, {37, 6}, {37, 6}, {37, 6}, {45, 6}, {45, 6}, {45, 6},
+ {45, 6}, {46, 6}, {46, 6}, {46, 6}, {46, 6}, {47, 6}, {47, 6},
+ {47, 6}, {47, 6}, {51, 6}, {51, 6}, {51, 6}, {51, 6}, {52, 6},
+ {52, 6}, {52, 6}, {52, 6}, {53, 6}, {53, 6}, {53, 6}, {53, 6},
+ {54, 6}, {54, 6}, {54, 6}, {54, 6}, {55, 6}, {55, 6}, {55, 6},
+ {55, 6}, {56, 6}, {56, 6}, {56, 6}, {56, 6}, {57, 6}, {57, 6},
+ {57, 6}, {57, 6}, {61, 6}, {61, 6}, {61, 6}, {61, 6}, {65, 6},
+ {65, 6}, {65, 6}, {65, 6}, {95, 6}, {95, 6}, {95, 6}, {95, 6},
+ {98, 6}, {98, 6}, {98, 6}, {98, 6}, {100, 6}, {100, 6}, {100, 6},
+ {100, 6}, {102, 6}, {102, 6}, {102, 6}, {102, 6}, {103, 6}, {103, 6},
+ {103, 6}, {103, 6}, {104, 6}, {104, 6}, {104, 6}, {104, 6}, {108, 6},
+ {108, 6}, {108, 6}, {108, 6}, {109, 6}, {109, 6}, {109, 6}, {109, 6},
+ {110, 6}, {110, 6}, {110, 6}, {110, 6}, {112, 6}, {112, 6}, {112, 6},
+ {112, 6}, {114, 6}, {114, 6}, {114, 6}, {114, 6}, {117, 6}, {117, 6},
+ {117, 6}, {117, 6}, {58, 7}, {58, 7}, {66, 7}, {66, 7}, {67, 7},
+ {67, 7}, {68, 7}, {68, 7}, {69, 7}, {69, 7}, {70, 7}, {70, 7},
+ {71, 7}, {71, 7}, {72, 7}, {72, 7}, {73, 7}, {73, 7}, {74, 7},
+ {74, 7}, {75, 7}, {75, 7}, {76, 7}, {76, 7}, {77, 7}, {77, 7},
+ {78, 7}, {78, 7}, {79, 7}, {79, 7}, {80, 7}, {80, 7}, {81, 7},
+ {81, 7}, {82, 7}, {82, 7}, {83, 7}, {83, 7}, {84, 7}, {84, 7},
+ {85, 7}, {85, 7}, {86, 7}, {86, 7}, {87, 7}, {87, 7}, {89, 7},
+ {89, 7}, {106, 7}, {106, 7}, {107, 7}, {107, 7}, {113, 7}, {113, 7},
+ {118, 7}, {118, 7}, {119, 7}, {119, 7}, {120, 7}, {120, 7}, {121, 7},
+ {121, 7}, {122, 7}, {122, 7}, {38, 8}, {42, 8}, {44, 8}, {59, 8},
+ {88, 8}, {90, 8},
+};
+
+static const HuffmanIncomingTable* HuffmanIncomingNextTablesRoot[] = {
+ &HuffmanIncoming_254,
+ &HuffmanIncoming_255,
+};
+
+static const HuffmanIncomingTable HuffmanIncomingRoot = {
+ HuffmanIncomingEntriesRoot, HuffmanIncomingNextTablesRoot, 254, 8};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__Http2HuffmanIncoming_h
diff --git a/netwerk/protocol/http/Http2HuffmanOutgoing.h b/netwerk/protocol/http/Http2HuffmanOutgoing.h
new file mode 100644
index 0000000000..376313ea91
--- /dev/null
+++ b/netwerk/protocol/http/Http2HuffmanOutgoing.h
@@ -0,0 +1,85 @@
+/*
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT!
+ */
+#ifndef mozilla__net__Http2HuffmanOutgoing_h
+#define mozilla__net__Http2HuffmanOutgoing_h
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanOutgoingEntry {
+ uint32_t mValue;
+ uint8_t mLength;
+};
+
+static const HuffmanOutgoingEntry HuffmanOutgoing[] = {
+ {0x00001ff8, 13}, {0x007fffd8, 23}, {0x0fffffe2, 28}, {0x0fffffe3, 28},
+ {0x0fffffe4, 28}, {0x0fffffe5, 28}, {0x0fffffe6, 28}, {0x0fffffe7, 28},
+ {0x0fffffe8, 28}, {0x00ffffea, 24}, {0x3ffffffc, 30}, {0x0fffffe9, 28},
+ {0x0fffffea, 28}, {0x3ffffffd, 30}, {0x0fffffeb, 28}, {0x0fffffec, 28},
+ {0x0fffffed, 28}, {0x0fffffee, 28}, {0x0fffffef, 28}, {0x0ffffff0, 28},
+ {0x0ffffff1, 28}, {0x0ffffff2, 28}, {0x3ffffffe, 30}, {0x0ffffff3, 28},
+ {0x0ffffff4, 28}, {0x0ffffff5, 28}, {0x0ffffff6, 28}, {0x0ffffff7, 28},
+ {0x0ffffff8, 28}, {0x0ffffff9, 28}, {0x0ffffffa, 28}, {0x0ffffffb, 28},
+ {0x00000014, 6}, {0x000003f8, 10}, {0x000003f9, 10}, {0x00000ffa, 12},
+ {0x00001ff9, 13}, {0x00000015, 6}, {0x000000f8, 8}, {0x000007fa, 11},
+ {0x000003fa, 10}, {0x000003fb, 10}, {0x000000f9, 8}, {0x000007fb, 11},
+ {0x000000fa, 8}, {0x00000016, 6}, {0x00000017, 6}, {0x00000018, 6},
+ {0x00000000, 5}, {0x00000001, 5}, {0x00000002, 5}, {0x00000019, 6},
+ {0x0000001a, 6}, {0x0000001b, 6}, {0x0000001c, 6}, {0x0000001d, 6},
+ {0x0000001e, 6}, {0x0000001f, 6}, {0x0000005c, 7}, {0x000000fb, 8},
+ {0x00007ffc, 15}, {0x00000020, 6}, {0x00000ffb, 12}, {0x000003fc, 10},
+ {0x00001ffa, 13}, {0x00000021, 6}, {0x0000005d, 7}, {0x0000005e, 7},
+ {0x0000005f, 7}, {0x00000060, 7}, {0x00000061, 7}, {0x00000062, 7},
+ {0x00000063, 7}, {0x00000064, 7}, {0x00000065, 7}, {0x00000066, 7},
+ {0x00000067, 7}, {0x00000068, 7}, {0x00000069, 7}, {0x0000006a, 7},
+ {0x0000006b, 7}, {0x0000006c, 7}, {0x0000006d, 7}, {0x0000006e, 7},
+ {0x0000006f, 7}, {0x00000070, 7}, {0x00000071, 7}, {0x00000072, 7},
+ {0x000000fc, 8}, {0x00000073, 7}, {0x000000fd, 8}, {0x00001ffb, 13},
+ {0x0007fff0, 19}, {0x00001ffc, 13}, {0x00003ffc, 14}, {0x00000022, 6},
+ {0x00007ffd, 15}, {0x00000003, 5}, {0x00000023, 6}, {0x00000004, 5},
+ {0x00000024, 6}, {0x00000005, 5}, {0x00000025, 6}, {0x00000026, 6},
+ {0x00000027, 6}, {0x00000006, 5}, {0x00000074, 7}, {0x00000075, 7},
+ {0x00000028, 6}, {0x00000029, 6}, {0x0000002a, 6}, {0x00000007, 5},
+ {0x0000002b, 6}, {0x00000076, 7}, {0x0000002c, 6}, {0x00000008, 5},
+ {0x00000009, 5}, {0x0000002d, 6}, {0x00000077, 7}, {0x00000078, 7},
+ {0x00000079, 7}, {0x0000007a, 7}, {0x0000007b, 7}, {0x00007ffe, 15},
+ {0x000007fc, 11}, {0x00003ffd, 14}, {0x00001ffd, 13}, {0x0ffffffc, 28},
+ {0x000fffe6, 20}, {0x003fffd2, 22}, {0x000fffe7, 20}, {0x000fffe8, 20},
+ {0x003fffd3, 22}, {0x003fffd4, 22}, {0x003fffd5, 22}, {0x007fffd9, 23},
+ {0x003fffd6, 22}, {0x007fffda, 23}, {0x007fffdb, 23}, {0x007fffdc, 23},
+ {0x007fffdd, 23}, {0x007fffde, 23}, {0x00ffffeb, 24}, {0x007fffdf, 23},
+ {0x00ffffec, 24}, {0x00ffffed, 24}, {0x003fffd7, 22}, {0x007fffe0, 23},
+ {0x00ffffee, 24}, {0x007fffe1, 23}, {0x007fffe2, 23}, {0x007fffe3, 23},
+ {0x007fffe4, 23}, {0x001fffdc, 21}, {0x003fffd8, 22}, {0x007fffe5, 23},
+ {0x003fffd9, 22}, {0x007fffe6, 23}, {0x007fffe7, 23}, {0x00ffffef, 24},
+ {0x003fffda, 22}, {0x001fffdd, 21}, {0x000fffe9, 20}, {0x003fffdb, 22},
+ {0x003fffdc, 22}, {0x007fffe8, 23}, {0x007fffe9, 23}, {0x001fffde, 21},
+ {0x007fffea, 23}, {0x003fffdd, 22}, {0x003fffde, 22}, {0x00fffff0, 24},
+ {0x001fffdf, 21}, {0x003fffdf, 22}, {0x007fffeb, 23}, {0x007fffec, 23},
+ {0x001fffe0, 21}, {0x001fffe1, 21}, {0x003fffe0, 22}, {0x001fffe2, 21},
+ {0x007fffed, 23}, {0x003fffe1, 22}, {0x007fffee, 23}, {0x007fffef, 23},
+ {0x000fffea, 20}, {0x003fffe2, 22}, {0x003fffe3, 22}, {0x003fffe4, 22},
+ {0x007ffff0, 23}, {0x003fffe5, 22}, {0x003fffe6, 22}, {0x007ffff1, 23},
+ {0x03ffffe0, 26}, {0x03ffffe1, 26}, {0x000fffeb, 20}, {0x0007fff1, 19},
+ {0x003fffe7, 22}, {0x007ffff2, 23}, {0x003fffe8, 22}, {0x01ffffec, 25},
+ {0x03ffffe2, 26}, {0x03ffffe3, 26}, {0x03ffffe4, 26}, {0x07ffffde, 27},
+ {0x07ffffdf, 27}, {0x03ffffe5, 26}, {0x00fffff1, 24}, {0x01ffffed, 25},
+ {0x0007fff2, 19}, {0x001fffe3, 21}, {0x03ffffe6, 26}, {0x07ffffe0, 27},
+ {0x07ffffe1, 27}, {0x03ffffe7, 26}, {0x07ffffe2, 27}, {0x00fffff2, 24},
+ {0x001fffe4, 21}, {0x001fffe5, 21}, {0x03ffffe8, 26}, {0x03ffffe9, 26},
+ {0x0ffffffd, 28}, {0x07ffffe3, 27}, {0x07ffffe4, 27}, {0x07ffffe5, 27},
+ {0x000fffec, 20}, {0x00fffff3, 24}, {0x000fffed, 20}, {0x001fffe6, 21},
+ {0x003fffe9, 22}, {0x001fffe7, 21}, {0x001fffe8, 21}, {0x007ffff3, 23},
+ {0x003fffea, 22}, {0x003fffeb, 22}, {0x01ffffee, 25}, {0x01ffffef, 25},
+ {0x00fffff4, 24}, {0x00fffff5, 24}, {0x03ffffea, 26}, {0x007ffff4, 23},
+ {0x03ffffeb, 26}, {0x07ffffe6, 27}, {0x03ffffec, 26}, {0x03ffffed, 26},
+ {0x07ffffe7, 27}, {0x07ffffe8, 27}, {0x07ffffe9, 27}, {0x07ffffea, 27},
+ {0x07ffffeb, 27}, {0x0ffffffe, 28}, {0x07ffffec, 27}, {0x07ffffed, 27},
+ {0x07ffffee, 27}, {0x07ffffef, 27}, {0x07fffff0, 27}, {0x03ffffee, 26},
+ {0x3fffffff, 30}};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__Http2HuffmanOutgoing_h
diff --git a/netwerk/protocol/http/Http2Push.cpp b/netwerk/protocol/http/Http2Push.cpp
new file mode 100644
index 0000000000..35cee9cd2a
--- /dev/null
+++ b/netwerk/protocol/http/Http2Push.cpp
@@ -0,0 +1,557 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include <algorithm>
+
+#include "Http2Push.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpTransaction.h"
+#include "nsIHttpPushListener.h"
+#include "nsISocketTransport.h"
+#include "nsSocketTransportService2.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+// Because WeakPtr isn't thread-safe we must ensure that the object is destroyed
+// on the socket thread, so any Release() called on a different thread is
+// dispatched to the socket thread.
+bool Http2PushedStreamWrapper::DispatchRelease() {
+ if (OnSocketThread()) {
+ return false;
+ }
+
+ gSocketTransportService->Dispatch(
+ NewNonOwningRunnableMethod("net::Http2PushedStreamWrapper::Release", this,
+ &Http2PushedStreamWrapper::Release),
+ NS_DISPATCH_NORMAL);
+
+ return true;
+}
+
+NS_IMPL_ADDREF(Http2PushedStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+Http2PushedStreamWrapper::Release() {
+ nsrefcnt count = mRefCnt - 1;
+ if (DispatchRelease()) {
+ // Redispatched to the socket thread.
+ return count;
+ }
+
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "Http2PushedStreamWrapper");
+
+ if (0 == count) {
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(Http2PushedStreamWrapper)
+NS_INTERFACE_MAP_END
+
+Http2PushedStreamWrapper::Http2PushedStreamWrapper(
+ Http2PushedStream* aPushStream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mStream = aPushStream;
+ mRequestString = aPushStream->GetRequestString();
+ mResourceUrl = aPushStream->GetResourceUrl();
+ mStreamID = aPushStream->StreamID();
+}
+
+Http2PushedStreamWrapper::~Http2PushedStreamWrapper() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+}
+
+Http2PushedStream* Http2PushedStreamWrapper::GetStream() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mStream) {
+ Http2StreamBase* stream = mStream;
+ return static_cast<Http2PushedStream*>(stream);
+ }
+ return nullptr;
+}
+
+void Http2PushedStreamWrapper::OnPushFailed() {
+ if (OnSocketThread()) {
+ if (mStream) {
+ Http2StreamBase* stream = mStream;
+ static_cast<Http2PushedStream*>(stream)->OnPushFailed();
+ }
+ } else {
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod("net::Http2PushedStreamWrapper::OnPushFailed", this,
+ &Http2PushedStreamWrapper::OnPushFailed),
+ NS_DISPATCH_NORMAL);
+ }
+}
+
+//////////////////////////////////////////
+// Http2PushedStream
+//////////////////////////////////////////
+
+Http2PushedStream::Http2PushedStream(
+ Http2PushTransactionBuffer* aTransaction, Http2Session* aSession,
+ Http2StreamBase* aAssociatedStream, uint32_t aID,
+ uint64_t aCurrentForegroundTabOuterContentWindowId)
+ : Http2StreamBase((aTransaction->QueryHttpTransaction())
+ ? aTransaction->QueryHttpTransaction()->BrowserId()
+ : 0,
+ aSession, 0, aCurrentForegroundTabOuterContentWindowId),
+ mAssociatedTransaction(aAssociatedStream->Transaction()),
+ mBufferedPush(aTransaction),
+ mTransaction(aTransaction) {
+ LOG3(("Http2PushedStream ctor this=%p 0x%X\n", this, aID));
+ mStreamID = aID;
+ MOZ_ASSERT(!(aID & 1)); // must be even to be a pushed stream
+ mBufferedPush->SetPushStream(this);
+ mRequestContext = aAssociatedStream->RequestContext();
+ mLastRead = TimeStamp::Now();
+ mPriorityDependency = aAssociatedStream->PriorityDependency();
+ if (mPriorityDependency == Http2Session::kUrgentStartGroupID ||
+ mPriorityDependency == Http2Session::kLeaderGroupID) {
+ mPriorityDependency = Http2Session::kFollowerGroupID;
+ }
+ // Cache this for later use in case of tab switch.
+ mDefaultPriorityDependency = mPriorityDependency;
+ SetPriorityDependency(aAssociatedStream->Priority() + 1, mPriorityDependency);
+ // Assume we are on the same tab as our associated stream, for priority
+ // purposes. It's possible this could change when we get paired with a sink,
+ // but it's unlikely and doesn't much matter anyway.
+ mTransactionBrowserId = aAssociatedStream->TransactionBrowserId();
+}
+
+bool Http2PushedStream::GetPushComplete() { return mPushCompleted; }
+
+nsresult Http2PushedStream::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ nsresult rv = Http2StreamBase::WriteSegments(writer, count, countWritten);
+ if (NS_SUCCEEDED(rv) && *countWritten) {
+ mLastRead = TimeStamp::Now();
+ }
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ mPushCompleted = true;
+ rv = NS_OK; // this is what a normal HTTP transaction would do
+ }
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv)) mStatus = rv;
+ return rv;
+}
+
+bool Http2PushedStream::DeferCleanup(nsresult status) {
+ LOG3(("Http2PushedStream::DeferCleanup Query %p %" PRIx32 "\n", this,
+ static_cast<uint32_t>(status)));
+
+ if (NS_SUCCEEDED(status) && mDeferCleanupOnSuccess) {
+ LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32 " defer on success\n",
+ this, static_cast<uint32_t>(status)));
+ return true;
+ }
+ if (mDeferCleanupOnPush) {
+ LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32 " defer onPush ref\n",
+ this, static_cast<uint32_t>(status)));
+ return true;
+ }
+ if (mConsumerStream) {
+ LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32
+ " defer active consumer\n",
+ this, static_cast<uint32_t>(status)));
+ return true;
+ }
+ LOG3(("Http2PushedStream::DeferCleanup Query %p %" PRIx32 " not deferred\n",
+ this, static_cast<uint32_t>(status)));
+ return false;
+}
+
+// return true if channel implements nsIHttpPushListener
+bool Http2PushedStream::TryOnPush() {
+ nsHttpTransaction* trans = mAssociatedTransaction->QueryHttpTransaction();
+ if (!trans) {
+ return false;
+ }
+
+ if (!(trans->Caps() & NS_HTTP_ONPUSH_LISTENER)) {
+ return false;
+ }
+
+ mDeferCleanupOnPush = true;
+ mResourceUrl = Origin() + Path();
+ RefPtr<Http2PushedStreamWrapper> stream = new Http2PushedStreamWrapper(this);
+ trans->OnPush(stream);
+ return true;
+}
+
+// side effect free static method to determine if Http2StreamBase implements
+// nsIHttpPushListener
+bool Http2PushedStream::TestOnPush(Http2StreamBase* stream) {
+ if (!stream) {
+ return false;
+ }
+ nsAHttpTransaction* abstractTransaction = stream->Transaction();
+ if (!abstractTransaction) {
+ return false;
+ }
+ nsHttpTransaction* trans = abstractTransaction->QueryHttpTransaction();
+ if (!trans) {
+ return false;
+ }
+ return trans->Caps() & NS_HTTP_ONPUSH_LISTENER;
+}
+
+nsresult Http2PushedStream::ReadSegments(nsAHttpSegmentReader* reader, uint32_t,
+ uint32_t* count) {
+ nsresult rv = NS_OK;
+ *count = 0;
+
+ mozilla::OriginAttributes originAttributes;
+ switch (mUpstreamState) {
+ case GENERATING_HEADERS: {
+ // The request headers for this has been processed, so we need to verify
+ // that :authority, :scheme, and :path MUST be present. :method MUST NOT
+ // be present
+ mSocketTransport->GetOriginAttributes(&originAttributes);
+ RefPtr<Http2Session> session = Session();
+ CreatePushHashKey(mHeaderScheme, mHeaderHost, originAttributes,
+ session->Serial(), mHeaderPath, mOrigin, mHashKey);
+
+ LOG3(("Http2PushStream 0x%X hash key %s\n", mStreamID, mHashKey.get()));
+
+ // the write side of a pushed transaction just involves manipulating a
+ // little state
+ SetSentFin(true);
+ Http2StreamBase::mRequestHeadersDone = 1;
+ Http2StreamBase::mOpenGenerated = 1;
+ Http2StreamBase::ChangeState(UPSTREAM_COMPLETE);
+ } break;
+
+ case UPSTREAM_COMPLETE:
+ // Let's just clear the stream's transmit buffer by pushing it into
+ // the session. This is probably a window adjustment.
+ LOG3(("Http2Push::ReadSegments 0x%X \n", mStreamID));
+ mSegmentReader = reader;
+ rv = TransmitFrame(nullptr, nullptr, true);
+ mSegmentReader = nullptr;
+ break;
+
+ case GENERATING_BODY:
+ case SENDING_BODY:
+ case SENDING_FIN_STREAM:
+ default:
+ break;
+ }
+
+ return rv;
+}
+
+void Http2PushedStream::AdjustInitialWindow() {
+ LOG3(("Http2PushStream %p 0x%X AdjustInitialWindow", this, mStreamID));
+ if (mConsumerStream) {
+ LOG3(
+ ("Http2PushStream::AdjustInitialWindow %p 0x%X "
+ "calling super consumer %p 0x%X\n",
+ this, mStreamID, mConsumerStream, mConsumerStream->StreamID()));
+ Http2StreamBase::AdjustInitialWindow();
+ // Http2PushedStream::ReadSegments is needed to call TransmitFrame()
+ // and actually get this information into the session bytestream
+ RefPtr<Http2Session> session = Session();
+ session->TransactionHasDataToWrite(this);
+ }
+ // Otherwise, when we get hooked up, the initial window will get bumped
+ // anyway, so we're good to go.
+}
+
+void Http2PushedStream::SetConsumerStream(Http2StreamBase* consumer) {
+ LOG3(("Http2PushedStream::SetConsumerStream this=%p consumer=%p", this,
+ consumer));
+
+ mConsumerStream = consumer;
+ mDeferCleanupOnPush = false;
+}
+
+bool Http2PushedStream::GetHashKey(nsCString& key) {
+ if (mHashKey.IsEmpty()) return false;
+
+ key = mHashKey;
+ return true;
+}
+
+void Http2PushedStream::ConnectPushedStream(Http2StreamBase* stream) {
+ RefPtr<Http2Session> session = Session();
+ session->ConnectPushedStream(stream);
+}
+
+bool Http2PushedStream::IsOrphaned(TimeStamp now) {
+ MOZ_ASSERT(!now.IsNull());
+
+ // if session is not transmitting, and is also not connected to a consumer
+ // stream, and its been like that for too long then it is oprhaned
+
+ if (mConsumerStream || mDeferCleanupOnPush) {
+ return false;
+ }
+
+ if (mOnPushFailed) {
+ return true;
+ }
+
+ bool rv = ((now - mLastRead).ToSeconds() > 30.0);
+ if (rv) {
+ LOG3(("Http2PushedStream:IsOrphaned 0x%X IsOrphaned %3.2f\n", mStreamID,
+ (now - mLastRead).ToSeconds()));
+ }
+ return rv;
+}
+
+nsresult Http2PushedStream::GetBufferedData(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ if (NS_FAILED(mStatus)) return mStatus;
+
+ nsresult rv = mBufferedPush->GetBufferedData(buf, count, countWritten);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!*countWritten) {
+ rv = GetPushComplete() ? NS_BASE_STREAM_CLOSED : NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ return rv;
+}
+
+void Http2PushedStream::CurrentBrowserIdChanged(uint64_t id) {
+ if (mConsumerStream) {
+ // Pass through to our sink, who will handle things appropriately.
+ mConsumerStream->CurrentBrowserIdChanged(id);
+ return;
+ }
+
+ MOZ_ASSERT(gHttpHandler->ActiveTabPriority());
+
+ mCurrentBrowserId = id;
+ RefPtr<Http2Session> session = Session();
+ if (!session->UseH2Deps()) {
+ return;
+ }
+
+ uint32_t oldDependency = mPriorityDependency;
+ if (mTransactionBrowserId != mCurrentBrowserId) {
+ mPriorityDependency = Http2Session::kBackgroundGroupID;
+ nsHttp::NotifyActiveTabLoadOptimization();
+ } else {
+ mPriorityDependency = mDefaultPriorityDependency;
+ }
+
+ if (mPriorityDependency != oldDependency) {
+ session->SendPriorityFrame(mStreamID, mPriorityDependency, mPriorityWeight);
+ }
+}
+
+// ConvertPushHeaders is used to convert the pushed request headers
+// into HTTP/1 format and report some telemetry
+nsresult Http2PushedStream::ConvertPushHeaders(Http2Decompressor* decompressor,
+ nsACString& aHeadersIn,
+ nsACString& aHeadersOut) {
+ nsresult rv = decompressor->DecodeHeaderBlock(
+ reinterpret_cast<const uint8_t*>(aHeadersIn.BeginReading()),
+ aHeadersIn.Length(), aHeadersOut, true);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2PushedStream::ConvertPushHeaders %p Error\n", this));
+ return rv;
+ }
+
+ nsCString method;
+ decompressor->GetHost(mHeaderHost);
+ decompressor->GetScheme(mHeaderScheme);
+ decompressor->GetPath(mHeaderPath);
+
+ if (mHeaderHost.IsEmpty() || mHeaderScheme.IsEmpty() ||
+ mHeaderPath.IsEmpty()) {
+ LOG3(
+ ("Http2PushedStream::ConvertPushHeaders %p Error - missing required "
+ "host=%s scheme=%s path=%s\n",
+ this, mHeaderHost.get(), mHeaderScheme.get(), mHeaderPath.get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ decompressor->GetMethod(method);
+ if (!method.EqualsLiteral("GET")) {
+ LOG3(
+ ("Http2PushedStream::ConvertPushHeaders %p Error - method not "
+ "supported: "
+ "%s\n",
+ this, method.get()));
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ aHeadersIn.Truncate();
+ LOG(("id 0x%X decoded push headers %s %s %s are:\n%s", mStreamID,
+ mHeaderScheme.get(), mHeaderHost.get(), mHeaderPath.get(),
+ aHeadersOut.BeginReading()));
+ return NS_OK;
+}
+
+nsresult Http2PushedStream::CallToReadData(uint32_t count,
+ uint32_t* countRead) {
+ return mTransaction->ReadSegments(this, count, countRead);
+}
+
+nsresult Http2PushedStream::CallToWriteData(uint32_t count,
+ uint32_t* countWritten) {
+ return mTransaction->WriteSegments(this, count, countWritten);
+}
+
+nsresult Http2PushedStream::GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) {
+ MOZ_ASSERT(false);
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void Http2PushedStream::CloseStream(nsresult reason) {
+ mTransaction->Close(reason);
+ mSession = nullptr;
+}
+
+//////////////////////////////////////////
+// Http2PushTransactionBuffer
+// This is the nsAHttpTransction owned by the stream when the pushed
+// stream has not yet been matched with a pull request
+//////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS0(Http2PushTransactionBuffer)
+
+Http2PushTransactionBuffer::Http2PushTransactionBuffer() {
+ mBufferedHTTP1 = MakeUnique<char[]>(mBufferedHTTP1Size);
+}
+
+Http2PushTransactionBuffer::~Http2PushTransactionBuffer() {
+ delete mRequestHead;
+}
+
+void Http2PushTransactionBuffer::SetConnection(nsAHttpConnection* conn) {}
+
+nsAHttpConnection* Http2PushTransactionBuffer::Connection() { return nullptr; }
+
+void Http2PushTransactionBuffer::GetSecurityCallbacks(
+ nsIInterfaceRequestor** outCB) {
+ *outCB = nullptr;
+}
+
+void Http2PushTransactionBuffer::OnTransportStatus(nsITransport* transport,
+ nsresult status,
+ int64_t progress) {}
+
+nsHttpConnectionInfo* Http2PushTransactionBuffer::ConnectionInfo() {
+ if (!mPushStream) {
+ return nullptr;
+ }
+ if (!mPushStream->Transaction()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(mPushStream->Transaction() != this);
+ return mPushStream->Transaction()->ConnectionInfo();
+}
+
+bool Http2PushTransactionBuffer::IsDone() { return mIsDone; }
+
+nsresult Http2PushTransactionBuffer::Status() { return mStatus; }
+
+uint32_t Http2PushTransactionBuffer::Caps() { return 0; }
+
+uint64_t Http2PushTransactionBuffer::Available() {
+ return mBufferedHTTP1Used - mBufferedHTTP1Consumed;
+}
+
+nsresult Http2PushTransactionBuffer::ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count,
+ uint32_t* countRead) {
+ *countRead = 0;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult Http2PushTransactionBuffer::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ if ((mBufferedHTTP1Size - mBufferedHTTP1Used) < 20480) {
+ EnsureBuffer(mBufferedHTTP1, mBufferedHTTP1Size + kDefaultBufferSize,
+ mBufferedHTTP1Used, mBufferedHTTP1Size);
+ }
+
+ count = std::min(count, mBufferedHTTP1Size - mBufferedHTTP1Used);
+ nsresult rv = writer->OnWriteSegment(&mBufferedHTTP1[mBufferedHTTP1Used],
+ count, countWritten);
+ if (NS_SUCCEEDED(rv)) {
+ mBufferedHTTP1Used += *countWritten;
+ } else if (rv == NS_BASE_STREAM_CLOSED) {
+ mIsDone = true;
+ }
+
+ if (Available() || mIsDone) {
+ Http2StreamBase* consumer = mPushStream->GetConsumerStream();
+
+ if (consumer) {
+ LOG3(
+ ("Http2PushTransactionBuffer::WriteSegments notifying connection "
+ "consumer data available 0x%X [%" PRIu64 "] done=%d\n",
+ mPushStream->StreamID(), Available(), mIsDone));
+ mPushStream->ConnectPushedStream(consumer);
+ }
+ }
+
+ return rv;
+}
+
+uint32_t Http2PushTransactionBuffer::Http1xTransactionCount() { return 0; }
+
+nsHttpRequestHead* Http2PushTransactionBuffer::RequestHead() {
+ if (!mRequestHead) mRequestHead = new nsHttpRequestHead();
+ return mRequestHead;
+}
+
+nsresult Http2PushTransactionBuffer::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> >& outTransactions) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void Http2PushTransactionBuffer::SetProxyConnectFailed() {}
+
+void Http2PushTransactionBuffer::Close(nsresult reason) {
+ mStatus = reason;
+ mIsDone = true;
+}
+
+nsresult Http2PushTransactionBuffer::GetBufferedData(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ *countWritten = std::min(count, static_cast<uint32_t>(Available()));
+ if (*countWritten) {
+ memcpy(buf, &mBufferedHTTP1[mBufferedHTTP1Consumed], *countWritten);
+ mBufferedHTTP1Consumed += *countWritten;
+ }
+
+ // If all the data has been consumed then reset the buffer
+ if (mBufferedHTTP1Consumed == mBufferedHTTP1Used) {
+ mBufferedHTTP1Consumed = 0;
+ mBufferedHTTP1Used = 0;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Push.h b/netwerk/protocol/http/Http2Push.h
new file mode 100644
index 0000000000..b7e25c2028
--- /dev/null
+++ b/netwerk/protocol/http/Http2Push.h
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http2Push_Internal_h
+#define mozilla_net_Http2Push_Internal_h
+
+// HTTP/2 - RFC 7540
+// https://www.rfc-editor.org/rfc/rfc7540.txt
+
+#include "Http2Session.h"
+#include "Http2StreamBase.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "nsHttpRequestHead.h"
+#include "nsIRequestContext.h"
+#include "nsString.h"
+#include "PSpdyPush.h"
+
+namespace mozilla {
+namespace net {
+
+class Http2PushTransactionBuffer;
+
+class Http2PushedStream final : public Http2StreamBase {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Http2PushedStream, override)
+
+ Http2PushedStream(Http2PushTransactionBuffer* aTransaction,
+ Http2Session* aSession, Http2StreamBase* aAssociatedStream,
+ uint32_t aID,
+ uint64_t aCurrentForegroundTabOuterContentWindowId);
+
+ Http2PushedStream* GetHttp2PushedStream() override { return this; }
+ bool GetPushComplete();
+
+ // The consumer stream is the synthetic pull stream hooked up to this push
+ Http2StreamBase* GetConsumerStream() { return mConsumerStream; };
+
+ void SetConsumerStream(Http2StreamBase* consumer);
+ [[nodiscard]] bool GetHashKey(nsCString& key);
+
+ // override of Http2StreamBase
+ [[nodiscard]] nsresult ReadSegments(nsAHttpSegmentReader*, uint32_t,
+ uint32_t*) override;
+ [[nodiscard]] nsresult WriteSegments(nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*) override;
+ void AdjustInitialWindow() override;
+
+ nsAHttpTransaction* Transaction() override { return mTransaction; }
+ nsIRequestContext* RequestContext() override { return mRequestContext; };
+ void ConnectPushedStream(Http2StreamBase* stream);
+
+ [[nodiscard]] bool TryOnPush();
+ [[nodiscard]] static bool TestOnPush(Http2StreamBase* stream);
+
+ virtual bool DeferCleanup(nsresult status) override;
+ void SetDeferCleanupOnSuccess(bool val) { mDeferCleanupOnSuccess = val; }
+
+ bool IsOrphaned(TimeStamp now);
+ void OnPushFailed() {
+ mDeferCleanupOnPush = false;
+ mOnPushFailed = true;
+ }
+
+ [[nodiscard]] nsresult GetBufferedData(char* buf, uint32_t count,
+ uint32_t* countWritten);
+
+ // overload of Http2StreamBase
+ virtual bool HasSink() override { return !!mConsumerStream; }
+ void SetPushComplete() { mPushCompleted = true; }
+ virtual void CurrentBrowserIdChanged(uint64_t) override;
+
+ nsCString& GetRequestString() { return mRequestString; }
+ nsCString& GetResourceUrl() { return mResourceUrl; }
+
+ nsresult ConvertPushHeaders(Http2Decompressor* decompressor,
+ nsACString& aHeadersIn, nsACString& aHeadersOut);
+
+ void CloseStream(nsresult reason) override;
+
+ protected:
+ nsresult CallToReadData(uint32_t count, uint32_t* countRead) override;
+ nsresult CallToWriteData(uint32_t count, uint32_t* countWritten) override;
+ nsresult GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) override;
+
+ private:
+ virtual ~Http2PushedStream() = default;
+ // paired request stream that consumes from real http/2 one.. null until a
+ // match is made.
+ Http2StreamBase* mConsumerStream{nullptr};
+
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+
+ nsAHttpTransaction* mAssociatedTransaction;
+
+ Http2PushTransactionBuffer* mBufferedPush;
+ mozilla::TimeStamp mLastRead;
+
+ nsCString mHashKey;
+ nsresult mStatus{NS_OK};
+ bool mPushCompleted{false}; // server push FIN received
+ bool mDeferCleanupOnSuccess{true};
+
+ // mDeferCleanupOnPush prevents Http2Session::CleanupStream() from
+ // destroying the push stream on an error code during the period between
+ // when we need to do OnPush() on another thread and the time it takes
+ // for that event to create a synthetic pull stream attached to this
+ // object. That synthetic pull will become mConsuemerStream.
+ // Ths is essentially a delete protecting reference.
+ bool mDeferCleanupOnPush{false};
+ bool mOnPushFailed{false};
+ nsCString mRequestString;
+ nsCString mResourceUrl;
+
+ uint32_t mDefaultPriorityDependency;
+
+ // The underlying HTTP transaction. This pointer is used as the key
+ // in the Http2Session mStreamTransactionHash so it is important to
+ // keep a reference to it as long as this stream is a member of that hash.
+ // (i.e. don't change it or release it after it is set in the ctor).
+ RefPtr<nsAHttpTransaction> const mTransaction;
+};
+
+class Http2PushTransactionBuffer final : public nsAHttpTransaction {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+
+ Http2PushTransactionBuffer();
+
+ [[nodiscard]] nsresult GetBufferedData(char* buf, uint32_t count,
+ uint32_t* countWritten);
+ void SetPushStream(Http2PushedStream* stream) { mPushStream = stream; }
+
+ private:
+ virtual ~Http2PushTransactionBuffer();
+ uint64_t Available();
+
+ const static uint32_t kDefaultBufferSize = 4096;
+
+ nsresult mStatus{NS_OK};
+ nsHttpRequestHead* mRequestHead{nullptr};
+ Http2PushedStream* mPushStream{nullptr};
+ bool mIsDone{false};
+
+ UniquePtr<char[]> mBufferedHTTP1;
+ uint32_t mBufferedHTTP1Size{kDefaultBufferSize};
+ uint32_t mBufferedHTTP1Used{0};
+ uint32_t mBufferedHTTP1Consumed{0};
+};
+
+class Http2PushedStreamWrapper : public nsISupports {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ bool DispatchRelease();
+
+ explicit Http2PushedStreamWrapper(Http2PushedStream* aPushStream);
+
+ nsCString& GetRequestString() { return mRequestString; }
+ nsCString& GetResourceUrl() { return mResourceUrl; }
+ Http2PushedStream* GetStream();
+ void OnPushFailed();
+ uint32_t StreamID() { return mStreamID; }
+
+ private:
+ virtual ~Http2PushedStreamWrapper();
+
+ nsCString mRequestString;
+ nsCString mResourceUrl;
+ uint32_t mStreamID;
+ WeakPtr<Http2StreamBase> mStream;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http2Push_Internal_h
diff --git a/netwerk/protocol/http/Http2Session.cpp b/netwerk/protocol/http/Http2Session.cpp
new file mode 100644
index 0000000000..e969d60c4d
--- /dev/null
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -0,0 +1,4513 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include <algorithm>
+
+#include "AltServiceChild.h"
+#include "CacheControlParser.h"
+#include "CachePushChecker.h"
+#include "Http2Push.h"
+#include "Http2Session.h"
+#include "Http2Stream.h"
+#include "Http2StreamBase.h"
+#include "Http2StreamTunnel.h"
+#include "LoadContextInfo.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "nsHttp.h"
+#include "nsHttpConnection.h"
+#include "nsHttpHandler.h"
+#include "nsIRequestContext.h"
+#include "nsISupportsPriority.h"
+#include "nsITLSSocketControl.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsSocketTransportService2.h"
+#include "nsStandardURL.h"
+#include "nsURLHelper.h"
+#include "prnetdb.h"
+#include "sslerr.h"
+#include "sslt.h"
+
+namespace mozilla {
+namespace net {
+
+// Http2Session has multiple inheritance of things that implement nsISupports
+NS_IMPL_ADDREF(Http2Session)
+NS_IMPL_RELEASE(Http2Session)
+NS_INTERFACE_MAP_BEGIN(Http2Session)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(Http2Session)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
+NS_INTERFACE_MAP_END
+
+static void RemoveStreamFromQueue(Http2StreamBase* aStream,
+ nsTArray<WeakPtr<Http2StreamBase>>& queue) {
+ for (const auto& stream : Reversed(queue)) {
+ if (stream == aStream) {
+ queue.RemoveElement(stream);
+ }
+ }
+}
+
+static void AddStreamToQueue(Http2StreamBase* aStream,
+ nsTArray<WeakPtr<Http2StreamBase>>& queue) {
+ if (!queue.Contains(aStream)) {
+ queue.AppendElement(aStream);
+ }
+}
+
+static already_AddRefed<Http2StreamBase> GetNextStreamFromQueue(
+ nsTArray<WeakPtr<Http2StreamBase>>& queue) {
+ while (!queue.IsEmpty() && !queue[0]) {
+ MOZ_ASSERT(false);
+ queue.RemoveElementAt(0);
+ }
+ if (queue.IsEmpty()) {
+ return nullptr;
+ }
+
+ RefPtr<Http2StreamBase> stream = queue[0].get();
+ queue.RemoveElementAt(0);
+ return stream.forget();
+}
+
+// "magic" refers to the string that preceeds HTTP/2 on the wire
+// to help find any intermediaries speaking an older version of HTTP
+const uint8_t Http2Session::kMagicHello[] = {
+ 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32,
+ 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a};
+
+Http2Session* Http2Session::CreateSession(nsISocketTransport* aSocketTransport,
+ enum SpdyVersion version,
+ bool attemptingEarlyData) {
+ if (!gHttpHandler) {
+ RefPtr<nsHttpHandler> handler = nsHttpHandler::GetInstance();
+ Unused << handler.get();
+ }
+
+ Http2Session* session =
+ new Http2Session(aSocketTransport, version, attemptingEarlyData);
+ session->SendHello();
+ return session;
+}
+
+Http2Session::Http2Session(nsISocketTransport* aSocketTransport,
+ enum SpdyVersion version, bool attemptingEarlyData)
+ : mSocketTransport(aSocketTransport),
+ mSegmentReader(nullptr),
+ mSegmentWriter(nullptr),
+ mNextStreamID(3) // 1 is reserved for Updgrade handshakes
+ ,
+ mLastPushedID(0),
+ mConcurrentHighWater(0),
+ mDownstreamState(BUFFERING_OPENING_SETTINGS),
+ mInputFrameBufferSize(kDefaultBufferSize),
+ mInputFrameBufferUsed(0),
+ mInputFrameDataSize(0),
+ mInputFrameDataRead(0),
+ mInputFrameFinal(false),
+ mInputFrameType(0),
+ mInputFrameFlags(0),
+ mInputFrameID(0),
+ mPaddingLength(0),
+ mInputFrameDataStream(nullptr),
+ mNeedsCleanup(nullptr),
+ mDownstreamRstReason(NO_HTTP_ERROR),
+ mExpectedHeaderID(0),
+ mExpectedPushPromiseID(0),
+ mContinuedPromiseStream(0),
+ mFlatHTTPResponseHeadersOut(0),
+ mShouldGoAway(false),
+ mClosed(false),
+ mCleanShutdown(false),
+ mReceivedSettings(false),
+ mTLSProfileConfirmed(false),
+ mGoAwayReason(NO_HTTP_ERROR),
+ mClientGoAwayReason(UNASSIGNED),
+ mPeerGoAwayReason(UNASSIGNED),
+ mGoAwayID(0),
+ mOutgoingGoAwayID(0),
+ mConcurrent(0),
+ mServerPushedResources(0),
+ mServerInitialStreamWindow(kDefaultRwin),
+ mLocalSessionWindow(kDefaultRwin),
+ mServerSessionWindow(kDefaultRwin),
+ mInitialRwin(ASpdySession::kInitialRwin),
+ mOutputQueueSize(kDefaultQueueSize),
+ mOutputQueueUsed(0),
+ mOutputQueueSent(0),
+ mLastReadEpoch(PR_IntervalNow()),
+ mPingSentEpoch(0),
+ mPreviousUsed(false),
+ mAggregatedHeaderSize(0),
+ mWaitingForSettingsAck(false),
+ mGoAwayOnPush(false),
+ mUseH2Deps(false),
+ mAttemptingEarlyData(attemptingEarlyData),
+ mOriginFrameActivated(false),
+ mCntActivated(0),
+ mTlsHandshakeFinished(false),
+ mPeerFailedHandshake(false),
+ mTrrStreams(0),
+ mEnableWebsockets(false),
+ mPeerAllowsWebsockets(false),
+ mProcessedWaitingWebsockets(false) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ static uint64_t sSerial;
+ mSerial = ++sSerial;
+
+ LOG3(("Http2Session::Http2Session %p serial=0x%" PRIX64 "\n", this, mSerial));
+
+ mInputFrameBuffer = MakeUnique<char[]>(mInputFrameBufferSize);
+ mOutputQueueBuffer = MakeUnique<char[]>(mOutputQueueSize);
+ mDecompressBuffer.SetCapacity(kDefaultBufferSize);
+
+ mPushAllowance = gHttpHandler->SpdyPushAllowance();
+ mInitialRwin = std::max(gHttpHandler->SpdyPullAllowance(), mPushAllowance);
+ mMaxConcurrent = gHttpHandler->DefaultSpdyConcurrent();
+ mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
+
+ mLastDataReadEpoch = mLastReadEpoch;
+
+ mPingThreshold = gHttpHandler->SpdyPingThreshold();
+ mPreviousPingThreshold = mPingThreshold;
+ mCurrentBrowserId = gHttpHandler->ConnMgr()->CurrentBrowserId();
+
+ mEnableWebsockets = StaticPrefs::network_http_http2_websockets();
+
+ bool dumpHpackTables = StaticPrefs::network_http_http2_enable_hpack_dump();
+ mCompressor.SetDumpTables(dumpHpackTables);
+ mDecompressor.SetDumpTables(dumpHpackTables);
+}
+
+void Http2Session::Shutdown(nsresult aReason) {
+ for (const auto& stream : mStreamTransactionHash.Values()) {
+ ShutdownStream(stream, aReason);
+ }
+
+ for (auto& stream : mTunnelStreams) {
+ ShutdownStream(stream, aReason);
+ }
+}
+
+void Http2Session::ShutdownStream(Http2StreamBase* aStream, nsresult aReason) {
+ // On a clean server hangup the server sets the GoAwayID to be the ID of
+ // the last transaction it processed. If the ID of stream in the
+ // local stream is greater than that it can safely be restarted because the
+ // server guarantees it was not partially processed. Streams that have not
+ // registered an ID haven't actually been sent yet so they can always be
+ // restarted.
+ if (mCleanShutdown &&
+ (aStream->StreamID() > mGoAwayID || !aStream->HasRegisteredID())) {
+ CloseStream(aStream, NS_ERROR_NET_RESET); // can be restarted
+ } else if (aStream->RecvdData()) {
+ CloseStream(aStream, NS_ERROR_NET_PARTIAL_TRANSFER);
+ } else if (mGoAwayReason == INADEQUATE_SECURITY) {
+ CloseStream(aStream, NS_ERROR_NET_INADEQUATE_SECURITY);
+ } else if (!mCleanShutdown && (mGoAwayReason != NO_HTTP_ERROR)) {
+ CloseStream(aStream, NS_ERROR_NET_HTTP2_SENT_GOAWAY);
+ } else if (!mCleanShutdown && SecurityErrorThatMayNeedRestart(aReason)) {
+ CloseStream(aStream, aReason);
+ } else {
+ CloseStream(aStream, NS_ERROR_ABORT);
+ }
+}
+
+Http2Session::~Http2Session() {
+ MOZ_DIAGNOSTIC_ASSERT(OnSocketThread());
+ LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X", this,
+ mDownstreamState));
+
+ Shutdown(NS_OK);
+
+ if (mTrrStreams) {
+ Telemetry::Accumulate(Telemetry::DNS_TRR_REQUEST_PER_CONN, mTrrStreams);
+ }
+ Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
+ Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN_3, mCntActivated);
+ Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
+ mServerPushedResources);
+ Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_LOCAL, mClientGoAwayReason);
+ Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_PEER, mPeerGoAwayReason);
+ Telemetry::Accumulate(Telemetry::HTTP2_FAIL_BEFORE_SETTINGS,
+ mPeerFailedHandshake);
+}
+
+inline nsresult Http2Session::SessionError(enum errorType reason) {
+ LOG3(("Http2Session::SessionError %p reason=0x%x mPeerGoAwayReason=0x%x",
+ this, reason, mPeerGoAwayReason));
+ mGoAwayReason = reason;
+
+ if (reason == INADEQUATE_SECURITY) {
+ // This one is special, as we have an error page just for this
+ return NS_ERROR_NET_INADEQUATE_SECURITY;
+ }
+
+ // We're the one sending a generic GOAWAY
+ return NS_ERROR_NET_HTTP2_SENT_GOAWAY;
+}
+
+void Http2Session::LogIO(Http2Session* self, Http2StreamBase* stream,
+ const char* label, const char* data,
+ uint32_t datalen) {
+ if (!LOG5_ENABLED()) return;
+
+ LOG5(("Http2Session::LogIO %p stream=%p id=0x%X [%s]", self, stream,
+ stream ? stream->StreamID() : 0, label));
+
+ // Max line is (16 * 3) + 10(prefix) + newline + null
+ char linebuf[128];
+ uint32_t index;
+ char* line = linebuf;
+
+ linebuf[127] = 0;
+
+ for (index = 0; index < datalen; ++index) {
+ if (!(index % 16)) {
+ if (index) {
+ *line = 0;
+ LOG5(("%s", linebuf));
+ }
+ line = linebuf;
+ snprintf(line, 128, "%08X: ", index);
+ line += 10;
+ }
+ snprintf(line, 128 - (line - linebuf), "%02X ",
+ (reinterpret_cast<const uint8_t*>(data))[index]);
+ line += 3;
+ }
+ if (index) {
+ *line = 0;
+ LOG5(("%s", linebuf));
+ }
+}
+
+using Http2ControlFx = nsresult (*)(Http2Session*);
+static Http2ControlFx sControlFunctions[] = {
+ nullptr, // type 0 data is not a control function
+ Http2Session::RecvHeaders,
+ Http2Session::RecvPriority,
+ Http2Session::RecvRstStream,
+ Http2Session::RecvSettings,
+ Http2Session::RecvPushPromise,
+ Http2Session::RecvPing,
+ Http2Session::RecvGoAway,
+ Http2Session::RecvWindowUpdate,
+ Http2Session::RecvContinuation,
+ Http2Session::RecvAltSvc, // extension for type 0x0A
+ Http2Session::RecvUnused, // 0x0B was BLOCKED still radioactive
+ Http2Session::RecvOrigin // extension for type 0x0C
+};
+
+bool Http2Session::RoomForMoreConcurrent() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return (mConcurrent < mMaxConcurrent);
+}
+
+bool Http2Session::RoomForMoreStreams() {
+ if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID) {
+ return false;
+ }
+
+ return !mShouldGoAway;
+}
+
+PRIntervalTime Http2Session::IdleTime() {
+ return PR_IntervalNow() - mLastDataReadEpoch;
+}
+
+uint32_t Http2Session::ReadTimeoutTick(PRIntervalTime now) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n", this,
+ PR_IntervalToSeconds(now - mLastReadEpoch)));
+
+ if (!mPingThreshold) {
+ return UINT32_MAX;
+ }
+
+ if ((now - mLastReadEpoch) < mPingThreshold) {
+ // recent activity means ping is not an issue
+ if (mPingSentEpoch) {
+ mPingSentEpoch = 0;
+ if (mPreviousUsed) {
+ // restore the former value
+ mPingThreshold = mPreviousPingThreshold;
+ mPreviousUsed = false;
+ }
+ }
+
+ return PR_IntervalToSeconds(mPingThreshold) -
+ PR_IntervalToSeconds(now - mLastReadEpoch);
+ }
+
+ if (mPingSentEpoch) {
+ bool isTrr = (mTrrStreams > 0);
+ uint32_t pingTimeout = isTrr ? StaticPrefs::network_trr_ping_timeout()
+ : gHttpHandler->SpdyPingTimeout();
+ LOG3(
+ ("Http2Session::ReadTimeoutTick %p handle outstanding ping, "
+ "timeout=%d\n",
+ this, pingTimeout));
+ if ((now - mPingSentEpoch) >= pingTimeout) {
+ LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n", this));
+ if (mConnection) {
+ mConnection->SetCloseReason(ConnectionCloseReason::IDLE_TIMEOUT);
+ }
+ mPingSentEpoch = 0;
+ if (isTrr) {
+ // These must be set this way to ensure we gracefully restart all
+ // streams
+ mGoAwayID = 0;
+ mCleanShutdown = true;
+ // If TRR is mode 2, this Http2Session will be closed due to TRR request
+ // timeout, so we won't reach this code. If we are in mode 3, the
+ // request timeout is usually larger than the ping timeout. We close the
+ // stream with NS_ERROR_NET_RESET, so the transactions can be restarted.
+ Close(NS_ERROR_NET_RESET);
+ } else {
+ Close(NS_ERROR_NET_TIMEOUT);
+ }
+ return UINT32_MAX;
+ }
+ return 1; // run the tick aggressively while ping is outstanding
+ }
+
+ LOG3(("Http2Session::ReadTimeoutTick %p generating ping\n", this));
+
+ mPingSentEpoch = PR_IntervalNow();
+ if (!mPingSentEpoch) {
+ mPingSentEpoch = 1; // avoid the 0 sentinel value
+ }
+ GeneratePing(false);
+ Unused << ResumeRecv(); // read the ping reply
+
+ // Check for orphaned push streams. This looks expensive, but generally the
+ // list is empty.
+ Http2PushedStream* deleteMe;
+ TimeStamp timestampNow;
+ do {
+ deleteMe = nullptr;
+
+ for (uint32_t index = mPushedStreams.Length(); index > 0; --index) {
+ Http2PushedStream* pushedStream = mPushedStreams[index - 1];
+
+ if (timestampNow.IsNull()) {
+ timestampNow = TimeStamp::Now(); // lazy initializer
+ }
+
+ // if stream finished, but is not connected, and its been like that for
+ // long then cleanup the stream.
+ if (pushedStream->IsOrphaned(timestampNow)) {
+ LOG3(("Http2Session Timeout Pushed Stream %p 0x%X\n", this,
+ pushedStream->StreamID()));
+ deleteMe = pushedStream;
+ break; // don't CleanupStream() while iterating this vector
+ }
+ }
+ if (deleteMe) CleanupStream(deleteMe, NS_ERROR_ABORT, CANCEL_ERROR);
+
+ } while (deleteMe);
+
+ return 1; // run the tick aggressively while ping is outstanding
+}
+
+uint32_t Http2Session::RegisterStreamID(Http2StreamBase* stream,
+ uint32_t aNewID) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mNextStreamID < 0xfffffff0,
+ "should have stopped admitting streams");
+ MOZ_ASSERT(!(aNewID & 1),
+ "0 for autoassign pull, otherwise explicit even push assignment");
+
+ if (!aNewID) {
+ // auto generate a new pull stream ID
+ aNewID = mNextStreamID;
+ MOZ_ASSERT(aNewID & 1, "pull ID must be odd.");
+ mNextStreamID += 2;
+ }
+
+ LOG1(
+ ("Http2Session::RegisterStreamID session=%p stream=%p id=0x%X "
+ "concurrent=%d",
+ this, stream, aNewID, mConcurrent));
+
+ // We've used up plenty of ID's on this session. Start
+ // moving to a new one before there is a crunch involving
+ // server push streams or concurrent non-registered submits
+ if (aNewID >= kMaxStreamID) mShouldGoAway = true;
+
+ // integrity check
+ if (mStreamIDHash.Contains(aNewID)) {
+ LOG3((" New ID already present\n"));
+ MOZ_ASSERT(false, "New ID already present in mStreamIDHash");
+ mShouldGoAway = true;
+ return kDeadStreamID;
+ }
+
+ mStreamIDHash.InsertOrUpdate(aNewID, stream);
+
+ if (aNewID & 1) {
+ // don't count push streams here
+ RefPtr<nsHttpConnectionInfo> ci(stream->ConnectionInfo());
+ if (ci && ci->GetIsTrrServiceChannel()) {
+ IncrementTrrCounter();
+ }
+ }
+ return aNewID;
+}
+
+bool Http2Session::AddStream(nsAHttpTransaction* aHttpTransaction,
+ int32_t aPriority,
+ nsIInterfaceRequestor* aCallbacks) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // integrity check
+ if (mStreamTransactionHash.Contains(aHttpTransaction)) {
+ LOG3((" New transaction already present\n"));
+ MOZ_ASSERT(false, "AddStream duplicate transaction pointer");
+ return false;
+ }
+
+ if (!mConnection) {
+ mConnection = aHttpTransaction->Connection();
+ }
+
+ if (!mFirstHttpTransaction && !mTlsHandshakeFinished) {
+ mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction();
+ LOG3(("Http2Session::AddStream first session=%p trans=%p ", this,
+ mFirstHttpTransaction.get()));
+ }
+
+ if (mClosed || mShouldGoAway) {
+ nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction();
+ if (trans) {
+ RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper;
+ pushedStreamWrapper = trans->GetPushedStream();
+ if (!pushedStreamWrapper || !pushedStreamWrapper->GetStream()) {
+ LOG3(
+ ("Http2Session::AddStream %p atrans=%p trans=%p session unusable - "
+ "resched.\n",
+ this, aHttpTransaction, trans));
+ aHttpTransaction->SetConnection(nullptr);
+ nsresult rv =
+ gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2Session::AddStream %p atrans=%p trans=%p failed to "
+ "initiate "
+ "transaction (%08x).\n",
+ this, aHttpTransaction, trans, static_cast<uint32_t>(rv)));
+ }
+ return true;
+ }
+ }
+ }
+
+ aHttpTransaction->SetConnection(this);
+ aHttpTransaction->OnActivated();
+
+ CreateStream(aHttpTransaction, aPriority, Http2StreamBaseType::Normal);
+ return true;
+}
+
+void Http2Session::CreateStream(nsAHttpTransaction* aHttpTransaction,
+ int32_t aPriority,
+ Http2StreamBaseType streamType) {
+ RefPtr<Http2StreamBase> refStream;
+ switch (streamType) {
+ case Http2StreamBaseType::Normal:
+ refStream =
+ new Http2Stream(aHttpTransaction, this, aPriority, mCurrentBrowserId);
+ break;
+ case Http2StreamBaseType::WebSocket:
+ case Http2StreamBaseType::Tunnel:
+ case Http2StreamBaseType::ServerPush:
+ MOZ_RELEASE_ASSERT(false);
+ return;
+ }
+
+ LOG3(("Http2Session::AddStream session=%p stream=%p serial=%" PRIu64 " "
+ "NextID=0x%X (tentative)",
+ this, refStream.get(), mSerial, mNextStreamID));
+
+ RefPtr<Http2StreamBase> stream = refStream;
+ mStreamTransactionHash.InsertOrUpdate(aHttpTransaction, std::move(refStream));
+
+ AddStreamToQueue(stream, mReadyForWrite);
+ SetWriteCallbacks();
+
+ // Kick off the SYN transmit without waiting for the poll loop
+ // This won't work for the first stream because there is no segment reader
+ // yet.
+ if (mSegmentReader) {
+ uint32_t countRead;
+ Unused << ReadSegments(nullptr, kDefaultBufferSize, &countRead);
+ }
+
+ if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) &&
+ !aHttpTransaction->IsNullTransaction()) {
+ LOG3(("Http2Session::AddStream %p transaction %p forces keep-alive off.\n",
+ this, aHttpTransaction));
+ DontReuse();
+ }
+}
+
+already_AddRefed<nsHttpConnection> Http2Session::CreateTunnelStream(
+ nsAHttpTransaction* aHttpTransaction, nsIInterfaceRequestor* aCallbacks,
+ PRIntervalTime aRtt, bool aIsWebSocket) {
+ RefPtr<Http2StreamTunnel> refStream = CreateTunnelStreamFromConnInfo(
+ this, mCurrentBrowserId, aHttpTransaction->ConnectionInfo(),
+ aIsWebSocket);
+
+ RefPtr<nsHttpConnection> newConn = refStream->CreateHttpConnection(
+ aHttpTransaction, aCallbacks, aRtt, aIsWebSocket);
+
+ mTunnelStreams.AppendElement(std::move(refStream));
+ return newConn.forget();
+}
+
+void Http2Session::QueueStream(Http2StreamBase* stream) {
+ // will be removed via processpending or a shutdown path
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!stream->CountAsActive());
+ MOZ_ASSERT(!stream->Queued());
+
+ LOG3(("Http2Session::QueueStream %p stream %p queued.", this, stream));
+
+#ifdef DEBUG
+ for (const auto& qStream : mQueuedStreams) {
+ MOZ_ASSERT(qStream != stream);
+ MOZ_ASSERT(qStream->Queued());
+ }
+#endif
+
+ stream->SetQueued(true);
+ AddStreamToQueue(stream, mQueuedStreams);
+}
+
+void Http2Session::ProcessPending() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ RefPtr<Http2StreamBase> stream;
+ while (RoomForMoreConcurrent() &&
+ (stream = GetNextStreamFromQueue(mQueuedStreams))) {
+ LOG3(("Http2Session::ProcessPending %p stream %p woken from queue.", this,
+ stream.get()));
+ MOZ_ASSERT(!stream->CountAsActive());
+ MOZ_ASSERT(stream->Queued());
+ stream->SetQueued(false);
+ AddStreamToQueue(stream, mReadyForWrite);
+ SetWriteCallbacks();
+ }
+}
+
+nsresult Http2Session::NetworkRead(nsAHttpSegmentWriter* writer, char* buf,
+ uint32_t count, uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!count) {
+ *countWritten = 0;
+ return NS_OK;
+ }
+
+ nsresult rv = writer->OnWriteSegment(buf, count, countWritten);
+ if (NS_SUCCEEDED(rv) && *countWritten > 0) {
+ mLastReadEpoch = PR_IntervalNow();
+ }
+ return rv;
+}
+
+void Http2Session::SetWriteCallbacks() {
+ if (mConnection &&
+ (GetWriteQueueSize() || (mOutputQueueUsed > mOutputQueueSent))) {
+ Unused << mConnection->ResumeSend();
+ }
+}
+
+void Http2Session::RealignOutputQueue() {
+ if (mAttemptingEarlyData) {
+ // We can't realign right now, because we may need what's in there if early
+ // data fails.
+ return;
+ }
+
+ mOutputQueueUsed -= mOutputQueueSent;
+ memmove(mOutputQueueBuffer.get(), mOutputQueueBuffer.get() + mOutputQueueSent,
+ mOutputQueueUsed);
+ mOutputQueueSent = 0;
+}
+
+void Http2Session::FlushOutputQueue() {
+ if (!mSegmentReader || !mOutputQueueUsed) return;
+
+ nsresult rv;
+ uint32_t countRead;
+ uint32_t avail = mOutputQueueUsed - mOutputQueueSent;
+
+ if (!avail && mAttemptingEarlyData) {
+ // This is kind of a hack, but there are cases where we'll have already
+ // written the data we want whlie doing early data, but we get called again
+ // with a reader, and we need to avoid calling the reader when there's
+ // nothing for it to read.
+ return;
+ }
+
+ rv = mSegmentReader->OnReadSegment(
+ mOutputQueueBuffer.get() + mOutputQueueSent, avail, &countRead);
+ LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%" PRIx32 " actual=%d",
+ this, avail, static_cast<uint32_t>(rv), countRead));
+
+ // Dont worry about errors on write, we will pick this up as a read error too
+ if (NS_FAILED(rv)) return;
+
+ mOutputQueueSent += countRead;
+
+ if (mAttemptingEarlyData) {
+ return;
+ }
+
+ if (countRead == avail) {
+ mOutputQueueUsed = 0;
+ mOutputQueueSent = 0;
+ return;
+ }
+
+ // If the output queue is close to filling up and we have sent out a good
+ // chunk of data from the beginning then realign it.
+
+ if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
+ ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
+ RealignOutputQueue();
+ }
+}
+
+void Http2Session::DontReuse() {
+ LOG3(("Http2Session::DontReuse %p\n", this));
+ if (!OnSocketThread()) {
+ LOG3(("Http2Session %p not on socket thread\n", this));
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
+ "Http2Session::DontReuse", this, &Http2Session::DontReuse);
+ gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ mShouldGoAway = true;
+ if (!mClosed && !mStreamTransactionHash.Count()) {
+ Close(NS_OK);
+ }
+}
+
+enum SpdyVersion Http2Session::SpdyVersion() { return SpdyVersion::HTTP_2; }
+
+uint32_t Http2Session::GetWriteQueueSize() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ return mReadyForWrite.Length();
+}
+
+void Http2Session::ChangeDownstreamState(enum internalStateType newState) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG3(("Http2Session::ChangeDownstreamState() %p from %X to %X", this,
+ mDownstreamState, newState));
+ mDownstreamState = newState;
+}
+
+void Http2Session::ResetDownstreamState() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG3(("Http2Session::ResetDownstreamState() %p", this));
+ ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+
+ if (mInputFrameFinal && mInputFrameDataStream) {
+ mInputFrameFinal = false;
+ LOG3((" SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID()));
+ mInputFrameDataStream->SetRecvdFin(true);
+ MaybeDecrementConcurrent(mInputFrameDataStream);
+ }
+ mInputFrameFinal = false;
+ mInputFrameBufferUsed = 0;
+ mInputFrameDataStream = nullptr;
+}
+
+// return true if activated (and counted against max)
+// otherwise return false and queue
+bool Http2Session::TryToActivate(Http2StreamBase* aStream) {
+ if (aStream->Queued()) {
+ LOG3(("Http2Session::TryToActivate %p stream=%p already queued.\n", this,
+ aStream));
+ return false;
+ }
+
+ if (!RoomForMoreConcurrent()) {
+ LOG3(
+ ("Http2Session::TryToActivate %p stream=%p no room for more concurrent "
+ "streams\n",
+ this, aStream));
+ QueueStream(aStream);
+ return false;
+ }
+
+ LOG3(("Http2Session::TryToActivate %p stream=%p\n", this, aStream));
+ IncrementConcurrent(aStream);
+
+ mCntActivated++;
+ return true;
+}
+
+void Http2Session::IncrementConcurrent(Http2StreamBase* stream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1),
+ "Do not activate pushed streams");
+
+ nsAHttpTransaction* trans = stream->Transaction();
+ if (!trans || !trans->IsNullTransaction()) {
+ MOZ_ASSERT(!stream->CountAsActive());
+ stream->SetCountAsActive(true);
+ ++mConcurrent;
+
+ if (mConcurrent > mConcurrentHighWater) {
+ mConcurrentHighWater = mConcurrent;
+ }
+ LOG3(
+ ("Http2Session::IncrementCounter %p counting stream %p Currently %d "
+ "streams in session, high water mark is %d\n",
+ this, stream, mConcurrent, mConcurrentHighWater));
+ }
+}
+
+// call with data length (i.e. 0 for 0 data bytes - ignore 9 byte header)
+// dest must have 9 bytes of allocated space
+template <typename charType>
+void Http2Session::CreateFrameHeader(charType dest, uint16_t frameLength,
+ uint8_t frameType, uint8_t frameFlags,
+ uint32_t streamID) {
+ MOZ_ASSERT(frameLength <= kMaxFrameData, "framelength too large");
+ MOZ_ASSERT(!(streamID & 0x80000000));
+ MOZ_ASSERT(!frameFlags || (frameType != FRAME_TYPE_PRIORITY &&
+ frameType != FRAME_TYPE_RST_STREAM &&
+ frameType != FRAME_TYPE_GOAWAY &&
+ frameType != FRAME_TYPE_WINDOW_UPDATE));
+
+ dest[0] = 0x00;
+ NetworkEndian::writeUint16(dest + 1, frameLength);
+ dest[3] = frameType;
+ dest[4] = frameFlags;
+ NetworkEndian::writeUint32(dest + 5, streamID);
+}
+
+char* Http2Session::EnsureOutputBuffer(uint32_t spaceNeeded) {
+ // this is an infallible allocation (if an allocation is
+ // needed, which is probably isn't)
+ EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + spaceNeeded,
+ mOutputQueueUsed, mOutputQueueSize);
+ return mOutputQueueBuffer.get() + mOutputQueueUsed;
+}
+
+template void Http2Session::CreateFrameHeader(char* dest, uint16_t frameLength,
+ uint8_t frameType,
+ uint8_t frameFlags,
+ uint32_t streamID);
+
+template void Http2Session::CreateFrameHeader(uint8_t* dest,
+ uint16_t frameLength,
+ uint8_t frameType,
+ uint8_t frameFlags,
+ uint32_t streamID);
+
+void Http2Session::MaybeDecrementConcurrent(Http2StreamBase* aStream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("MaybeDecrementConcurrent %p id=0x%X concurrent=%d active=%d\n", this,
+ aStream->StreamID(), mConcurrent, aStream->CountAsActive()));
+
+ if (!aStream->CountAsActive()) return;
+
+ MOZ_ASSERT(mConcurrent);
+ aStream->SetCountAsActive(false);
+ --mConcurrent;
+ ProcessPending();
+}
+
+// Need to decompress some data in order to keep the compression
+// context correct, but we really don't care what the result is
+nsresult Http2Session::UncompressAndDiscard(bool isPush) {
+ nsresult rv;
+ nsAutoCString trash;
+
+ rv = mDecompressor.DecodeHeaderBlock(
+ reinterpret_cast<const uint8_t*>(mDecompressBuffer.BeginReading()),
+ mDecompressBuffer.Length(), trash, isPush);
+ mDecompressBuffer.Truncate();
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::UncompressAndDiscard %p Compression Error\n", this));
+ mGoAwayReason = COMPRESSION_ERROR;
+ return rv;
+ }
+ return NS_OK;
+}
+
+void Http2Session::GeneratePing(bool isAck) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::GeneratePing %p isAck=%d\n", this, isAck));
+
+ char* packet = EnsureOutputBuffer(kFrameHeaderBytes + 8);
+ mOutputQueueUsed += kFrameHeaderBytes + 8;
+
+ if (isAck) {
+ CreateFrameHeader(packet, 8, FRAME_TYPE_PING, kFlag_ACK, 0);
+ memcpy(packet + kFrameHeaderBytes,
+ mInputFrameBuffer.get() + kFrameHeaderBytes, 8);
+ } else {
+ CreateFrameHeader(packet, 8, FRAME_TYPE_PING, 0, 0);
+ memset(packet + kFrameHeaderBytes, 0, 8);
+ }
+
+ LogIO(this, nullptr, "Generate Ping", packet, kFrameHeaderBytes + 8);
+ FlushOutputQueue();
+}
+
+void Http2Session::GenerateSettingsAck() {
+ // need to generate ack of this settings frame
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::GenerateSettingsAck %p\n", this));
+
+ char* packet = EnsureOutputBuffer(kFrameHeaderBytes);
+ mOutputQueueUsed += kFrameHeaderBytes;
+ CreateFrameHeader(packet, 0, FRAME_TYPE_SETTINGS, kFlag_ACK, 0);
+ LogIO(this, nullptr, "Generate Settings ACK", packet, kFrameHeaderBytes);
+ FlushOutputQueue();
+}
+
+void Http2Session::GeneratePriority(uint32_t aID, uint8_t aPriorityWeight) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::GeneratePriority %p %X %X\n", this, aID,
+ aPriorityWeight));
+
+ char* packet = CreatePriorityFrame(aID, 0, aPriorityWeight);
+
+ LogIO(this, nullptr, "Generate Priority", packet, kFrameHeaderBytes + 5);
+ FlushOutputQueue();
+}
+
+void Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // make sure we don't do this twice for the same stream (at least if we
+ // have a stream entry for it)
+ Http2StreamBase* stream = mStreamIDHash.Get(aID);
+ if (stream) {
+ if (stream->SentReset()) return;
+ stream->SetSentReset(true);
+ }
+
+ LOG3(("Http2Session::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode));
+
+ uint32_t frameSize = kFrameHeaderBytes + 4;
+ char* packet = EnsureOutputBuffer(frameSize);
+ mOutputQueueUsed += frameSize;
+ CreateFrameHeader(packet, 4, FRAME_TYPE_RST_STREAM, 0, aID);
+
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, aStatusCode);
+
+ LogIO(this, nullptr, "Generate Reset", packet, frameSize);
+ FlushOutputQueue();
+}
+
+void Http2Session::GenerateGoAway(uint32_t aStatusCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::GenerateGoAway %p code=%X\n", this, aStatusCode));
+
+ mClientGoAwayReason = aStatusCode;
+ uint32_t frameSize = kFrameHeaderBytes + 8;
+ char* packet = EnsureOutputBuffer(frameSize);
+ mOutputQueueUsed += frameSize;
+
+ CreateFrameHeader(packet, 8, FRAME_TYPE_GOAWAY, 0, 0);
+
+ // last-good-stream-id are bytes 9-12 reflecting pushes
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, mOutgoingGoAwayID);
+
+ // bytes 13-16 are the status code.
+ NetworkEndian::writeUint32(packet + frameSize - 4, aStatusCode);
+
+ LogIO(this, nullptr, "Generate GoAway", packet, frameSize);
+ FlushOutputQueue();
+}
+
+// The Hello is comprised of
+// 1] 24 octets of magic, which are designed to
+// flush out silent but broken intermediaries
+// 2] a settings frame which sets a small flow control window for pushes
+// 3] a window update frame which creates a large session flow control window
+// 4] 6 priority frames for streams which will never be opened with headers
+// these streams (3, 5, 7, 9, b, d) build a dependency tree that all other
+// streams will be direct leaves of.
+void Http2Session::SendHello() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::SendHello %p\n", this));
+
+ // sized for magic + 5 settings and a session window update and 6 priority
+ // frames 24 magic, 33 for settings (9 header + 4 settings @6), 13 for window
+ // update, 6 priority frames at 14 (9 + 5) each
+ static const uint32_t maxSettings = 5;
+ static const uint32_t prioritySize =
+ kPriorityGroupCount * (kFrameHeaderBytes + 5);
+ static const uint32_t maxDataLen =
+ 24 + kFrameHeaderBytes + maxSettings * 6 + 13 + prioritySize;
+ char* packet = EnsureOutputBuffer(maxDataLen);
+ memcpy(packet, kMagicHello, 24);
+ mOutputQueueUsed += 24;
+ LogIO(this, nullptr, "Magic Connection Header", packet, 24);
+
+ packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ memset(packet, 0, maxDataLen - 24);
+
+ // frame header will be filled in after we know how long the frame is
+ uint8_t numberOfEntries = 0;
+
+ // entries need to be listed in order by ID
+ // 1st entry is bytes 9 to 14
+ // 2nd entry is bytes 15 to 20
+ // 3rd entry is bytes 21 to 26
+ // 4th entry is bytes 27 to 32
+ // 5th entry is bytes 33 to 38
+
+ // Let the other endpoint know about our default HPACK decompress table size
+ uint32_t maxHpackBufferSize = gHttpHandler->DefaultHpackBuffer();
+ mDecompressor.SetInitialMaxBufferSize(maxHpackBufferSize);
+ NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
+ SETTINGS_TYPE_HEADER_TABLE_SIZE);
+ NetworkEndian::writeUint32(
+ packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2,
+ maxHpackBufferSize);
+ numberOfEntries++;
+
+ if (!StaticPrefs::network_http_http2_allow_push()) {
+ // If we don't support push then set MAX_CONCURRENT to 0 and also
+ // set ENABLE_PUSH to 0
+ NetworkEndian::writeUint16(
+ packet + kFrameHeaderBytes + (6 * numberOfEntries),
+ SETTINGS_TYPE_ENABLE_PUSH);
+ // The value portion of the setting pair is already initialized to 0
+ numberOfEntries++;
+
+ NetworkEndian::writeUint16(
+ packet + kFrameHeaderBytes + (6 * numberOfEntries),
+ SETTINGS_TYPE_MAX_CONCURRENT);
+ // The value portion of the setting pair is already initialized to 0
+ numberOfEntries++;
+
+ mWaitingForSettingsAck = true;
+ }
+
+ // Advertise the Push RWIN for the session, and on each new pull stream
+ // send a window update
+ NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
+ SETTINGS_TYPE_INITIAL_WINDOW);
+ NetworkEndian::writeUint32(
+ packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, mPushAllowance);
+ numberOfEntries++;
+
+ // Make sure the other endpoint knows that we're sticking to the default max
+ // frame size
+ NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
+ SETTINGS_TYPE_MAX_FRAME_SIZE);
+ NetworkEndian::writeUint32(
+ packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, kMaxFrameData);
+ numberOfEntries++;
+
+ MOZ_ASSERT(numberOfEntries <= maxSettings);
+ uint32_t dataLen = 6 * numberOfEntries;
+ CreateFrameHeader(packet, dataLen, FRAME_TYPE_SETTINGS, 0, 0);
+ mOutputQueueUsed += kFrameHeaderBytes + dataLen;
+
+ LogIO(this, nullptr, "Generate Settings", packet,
+ kFrameHeaderBytes + dataLen);
+
+ // now bump the local session window from 64KB
+ uint32_t sessionWindowBump = mInitialRwin - kDefaultRwin;
+ if (kDefaultRwin < mInitialRwin) {
+ // send a window update for the session (Stream 0) for something large
+ mLocalSessionWindow = mInitialRwin;
+
+ packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
+ mOutputQueueUsed += kFrameHeaderBytes + 4;
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, sessionWindowBump);
+
+ LOG3(("Session Window increase at start of session %p %u\n", this,
+ sessionWindowBump));
+ LogIO(this, nullptr, "Session Window Bump ", packet, kFrameHeaderBytes + 4);
+ }
+
+ if (StaticPrefs::network_http_http2_enabled_deps() &&
+ gHttpHandler->CriticalRequestPrioritization()) {
+ mUseH2Deps = true;
+ MOZ_ASSERT(mNextStreamID == kLeaderGroupID);
+ CreatePriorityNode(kLeaderGroupID, 0, 200, "leader");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kOtherGroupID);
+ CreatePriorityNode(kOtherGroupID, 0, 100, "other");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kBackgroundGroupID);
+ CreatePriorityNode(kBackgroundGroupID, 0, 0, "background");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kSpeculativeGroupID);
+ CreatePriorityNode(kSpeculativeGroupID, kBackgroundGroupID, 0,
+ "speculative");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kFollowerGroupID);
+ CreatePriorityNode(kFollowerGroupID, kLeaderGroupID, 0, "follower");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kUrgentStartGroupID);
+ CreatePriorityNode(kUrgentStartGroupID, 0, 240, "urgentStart");
+ mNextStreamID += 2;
+ // Hey, you! YES YOU! If you add/remove any groups here, you almost
+ // certainly need to change the lookup of the stream/ID hash in
+ // Http2Session::OnTransportStatus. Yeah, that's right. YOU!
+ }
+
+ FlushOutputQueue();
+}
+
+void Http2Session::SendPriorityFrame(uint32_t streamID, uint32_t dependsOn,
+ uint8_t weight) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(
+ ("Http2Session::SendPriorityFrame %p Frame 0x%X depends on 0x%X "
+ "weight %d\n",
+ this, streamID, dependsOn, weight));
+
+ char* packet = CreatePriorityFrame(streamID, dependsOn, weight);
+
+ LogIO(this, nullptr, "SendPriorityFrame", packet, kFrameHeaderBytes + 5);
+ FlushOutputQueue();
+}
+
+char* Http2Session::CreatePriorityFrame(uint32_t streamID, uint32_t dependsOn,
+ uint8_t weight) {
+ MOZ_ASSERT(streamID, "Priority on stream 0");
+ char* packet = EnsureOutputBuffer(kFrameHeaderBytes + 5);
+ CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, streamID);
+ mOutputQueueUsed += kFrameHeaderBytes + 5;
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes,
+ dependsOn); // depends on
+ packet[kFrameHeaderBytes + 4] = weight; // weight
+ return packet;
+}
+
+void Http2Session::CreatePriorityNode(uint32_t streamID, uint32_t dependsOn,
+ uint8_t weight, const char* label) {
+ char* packet = CreatePriorityFrame(streamID, dependsOn, weight);
+
+ LOG3(
+ ("Http2Session %p generate Priority Frame 0x%X depends on 0x%X "
+ "weight %d for %s class\n",
+ this, streamID, dependsOn, weight, label));
+ LogIO(this, nullptr, "Priority dep node", packet, kFrameHeaderBytes + 5);
+}
+
+// perform a bunch of integrity checks on the stream.
+// returns true if passed, false (plus LOG and ABORT) if failed.
+bool Http2Session::VerifyStream(Http2StreamBase* aStream,
+ uint32_t aOptionalID = 0) {
+ // This is annoying, but at least it is O(1)
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+#ifndef DEBUG
+ // Only do the real verification in debug builds
+ return true;
+#else // DEBUG
+
+ if (!aStream) return true;
+
+ uint32_t test = 0;
+
+ do {
+ if (aStream->StreamID() == kDeadStreamID) break;
+
+ test++;
+ if (aStream->StreamID()) {
+ Http2StreamBase* idStream = mStreamIDHash.Get(aStream->StreamID());
+
+ test++;
+ if (idStream != aStream) break;
+
+ if (aOptionalID) {
+ test++;
+ if (idStream->StreamID() != aOptionalID) break;
+ }
+ }
+
+ if (aStream->IsTunnel()) {
+ return true;
+ }
+
+ nsAHttpTransaction* trans = aStream->Transaction();
+
+ test++;
+ if (!trans) break;
+
+ test++;
+ if (mStreamTransactionHash.GetWeak(trans) != aStream) break;
+
+ // tests passed
+ return true;
+ } while (false);
+
+ LOG3(
+ ("Http2Session %p VerifyStream Failure %p stream->id=0x%X "
+ "optionalID=0x%X trans=%p test=%d\n",
+ this, aStream, aStream->StreamID(), aOptionalID, aStream->Transaction(),
+ test));
+
+ MOZ_ASSERT(false, "VerifyStream");
+ return false;
+#endif // DEBUG
+}
+
+// static
+Http2StreamTunnel* Http2Session::CreateTunnelStreamFromConnInfo(
+ Http2Session* session, uint64_t bcId, nsHttpConnectionInfo* info,
+ bool isWebSocket) {
+ MOZ_ASSERT(info);
+ MOZ_ASSERT(session);
+
+ if (isWebSocket) {
+ LOG(("Http2Session creating Http2StreamWebSocket"));
+ MOZ_ASSERT(session->GetWebSocketSupport() == WebSocketSupport::SUPPORTED);
+ return new Http2StreamWebSocket(
+ session, nsISupportsPriority::PRIORITY_NORMAL, bcId, info);
+ }
+
+ MOZ_ASSERT(info->UsingHttpProxy() && info->UsingConnect());
+ LOG(("Http2Session creating Http2StreamTunnel"));
+ return new Http2StreamTunnel(session, nsISupportsPriority::PRIORITY_NORMAL,
+ bcId, info);
+}
+
+void Http2Session::CleanupStream(Http2StreamBase* aStream, nsresult aResult,
+ errorType aResetCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::CleanupStream %p %p 0x%X %" PRIX32 "\n", this, aStream,
+ aStream ? aStream->StreamID() : 0, static_cast<uint32_t>(aResult)));
+ if (!aStream) {
+ return;
+ }
+
+ Http2PushedStream* pushSource = nullptr;
+ Http2Stream* h2Stream = aStream->GetHttp2Stream();
+ if (h2Stream) {
+ pushSource = h2Stream->PushSource();
+ if (pushSource) {
+ // aStream is a synthetic attached to an even push
+ MOZ_ASSERT(pushSource->GetConsumerStream() == aStream);
+ MOZ_ASSERT(!aStream->StreamID());
+ MOZ_ASSERT(!(pushSource->StreamID() & 0x1));
+ h2Stream->ClearPushSource();
+ }
+ }
+
+ if (aStream->DeferCleanup(aResult)) {
+ LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID()));
+ return;
+ }
+
+ if (!VerifyStream(aStream)) {
+ LOG3(("Http2Session::CleanupStream failed to verify stream\n"));
+ return;
+ }
+
+ // don't reset a stream that has recevied a fin or rst
+ if (!aStream->RecvdFin() && !aStream->RecvdReset() && aStream->StreamID() &&
+ !(mInputFrameFinal &&
+ (aStream == mInputFrameDataStream))) { // !(recvdfin with mark pending)
+ LOG3(("Stream 0x%X had not processed recv FIN, sending RST code %X\n",
+ aStream->StreamID(), aResetCode));
+ GenerateRstStream(aResetCode, aStream->StreamID());
+ }
+
+ CloseStream(aStream, aResult);
+
+ // Remove the stream from the ID hash table and, if an even id, the pushed
+ // table too.
+ uint32_t id = aStream->StreamID();
+ if (id > 0) {
+ mStreamIDHash.Remove(id);
+ if (!(id & 1)) {
+ mPushedStreams.RemoveElement(aStream);
+ Http2PushedStream* pushStream = static_cast<Http2PushedStream*>(aStream);
+ nsAutoCString hashKey;
+ DebugOnly<bool> rv = pushStream->GetHashKey(hashKey);
+ MOZ_ASSERT(rv);
+ nsIRequestContext* requestContext = aStream->RequestContext();
+ if (requestContext) {
+ SpdyPushCache* cache = requestContext->GetSpdyPushCache();
+ if (cache) {
+ // Make sure the id of the stream in the push cache is the same
+ // as the id of the stream we're cleaning up! See bug 1368080.
+ Http2PushedStream* trash =
+ cache->RemovePushedStreamHttp2ByID(hashKey, aStream->StreamID());
+ LOG3(
+ ("Http2Session::CleanupStream %p aStream=%p pushStream=%p "
+ "trash=%p",
+ this, aStream, pushStream, trash));
+ }
+ }
+ }
+ }
+
+ RemoveStreamFromQueues(aStream);
+
+ // removing from the stream transaction hash will
+ // delete the Http2StreamBase and drop the reference to
+ // its transaction
+ mStreamTransactionHash.Remove(aStream->Transaction());
+ mTunnelStreams.RemoveElement(aStream);
+
+ if (mShouldGoAway && !mStreamTransactionHash.Count()) Close(NS_OK);
+
+ if (pushSource) {
+ pushSource->SetDeferCleanupOnSuccess(false);
+ CleanupStream(pushSource, aResult, aResetCode);
+ }
+}
+
+void Http2Session::CleanupStream(uint32_t aID, nsresult aResult,
+ errorType aResetCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ Http2StreamBase* stream = mStreamIDHash.Get(aID);
+ LOG3(("Http2Session::CleanupStream %p by ID 0x%X to stream %p\n", this, aID,
+ stream));
+ if (!stream) {
+ return;
+ }
+ CleanupStream(stream, aResult, aResetCode);
+}
+
+void Http2Session::RemoveStreamFromQueues(Http2StreamBase* aStream) {
+ RemoveStreamFromQueue(aStream, mReadyForWrite);
+ RemoveStreamFromQueue(aStream, mQueuedStreams);
+ RemoveStreamFromQueue(aStream, mPushesReadyForRead);
+ RemoveStreamFromQueue(aStream, mSlowConsumersReadyForRead);
+}
+
+void Http2Session::CloseStream(Http2StreamBase* aStream, nsresult aResult,
+ bool aRemoveFromQueue) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::CloseStream %p %p 0x%x %" PRIX32 "\n", this, aStream,
+ aStream->StreamID(), static_cast<uint32_t>(aResult)));
+
+ MaybeDecrementConcurrent(aStream);
+
+ // Check if partial frame reader
+ if (aStream == mInputFrameDataStream) {
+ LOG3(("Stream had active partial read frame on close"));
+ ChangeDownstreamState(DISCARDING_DATA_FRAME);
+ mInputFrameDataStream = nullptr;
+ }
+
+ if (aRemoveFromQueue) {
+ RemoveStreamFromQueues(aStream);
+ }
+
+ // Send the stream the close() indication
+ aStream->CloseStream(aResult);
+}
+
+nsresult Http2Session::SetInputFrameDataStream(uint32_t streamID) {
+ mInputFrameDataStream = mStreamIDHash.Get(streamID);
+ if (VerifyStream(mInputFrameDataStream, streamID)) return NS_OK;
+
+ LOG3(("Http2Session::SetInputFrameDataStream failed to verify 0x%X\n",
+ streamID));
+ mInputFrameDataStream = nullptr;
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult Http2Session::ParsePadding(uint8_t& paddingControlBytes,
+ uint16_t& paddingLength) {
+ if (mInputFrameFlags & kFlag_PADDED) {
+ paddingLength =
+ *reinterpret_cast<uint8_t*>(&mInputFrameBuffer[kFrameHeaderBytes]);
+ paddingControlBytes = 1;
+ } else {
+ paddingLength = 0;
+ paddingControlBytes = 0;
+ }
+
+ if (static_cast<uint32_t>(paddingLength + paddingControlBytes) >
+ mInputFrameDataSize) {
+ // This is fatal to the session
+ LOG3(
+ ("Http2Session::ParsePadding %p stream 0x%x PROTOCOL_ERROR "
+ "paddingLength %d > frame size %d\n",
+ this, mInputFrameID, paddingLength, mInputFrameDataSize));
+ return SessionError(PROTOCOL_ERROR);
+ }
+
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvHeaders(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_HEADERS ||
+ self->mInputFrameType == FRAME_TYPE_CONTINUATION);
+
+ bool isContinuation = self->mExpectedHeaderID != 0;
+
+ // If this doesn't have END_HEADERS set on it then require the next
+ // frame to be HEADERS of the same ID
+ bool endHeadersFlag = self->mInputFrameFlags & kFlag_END_HEADERS;
+
+ if (endHeadersFlag) {
+ self->mExpectedHeaderID = 0;
+ } else {
+ self->mExpectedHeaderID = self->mInputFrameID;
+ }
+
+ uint32_t priorityLen = 0;
+ if (self->mInputFrameFlags & kFlag_PRIORITY) {
+ priorityLen = 5;
+ }
+ nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Find out how much padding this frame has, so we can only extract the real
+ // header data from the frame.
+ uint16_t paddingLength = 0;
+ uint8_t paddingControlBytes = 0;
+
+ if (!isContinuation) {
+ self->mDecompressBuffer.Truncate();
+ rv = self->ParsePadding(paddingControlBytes, paddingLength);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ LOG3(
+ ("Http2Session::RecvHeaders %p stream 0x%X priorityLen=%d stream=%p "
+ "end_stream=%d end_headers=%d priority_group=%d "
+ "paddingLength=%d padded=%d\n",
+ self, self->mInputFrameID, priorityLen, self->mInputFrameDataStream,
+ self->mInputFrameFlags & kFlag_END_STREAM,
+ self->mInputFrameFlags & kFlag_END_HEADERS,
+ self->mInputFrameFlags & kFlag_PRIORITY, paddingLength,
+ self->mInputFrameFlags & kFlag_PADDED));
+
+ if ((paddingControlBytes + priorityLen + paddingLength) >
+ self->mInputFrameDataSize) {
+ // This is fatal to the session
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ if (!self->mInputFrameDataStream) {
+ // Cannot find stream. We can continue the session, but we need to
+ // uncompress the header block to maintain the correct compression context
+
+ LOG3(
+ ("Http2Session::RecvHeaders %p lookup mInputFrameID stream "
+ "0x%X failed. NextStreamID = 0x%X\n",
+ self, self->mInputFrameID, self->mNextStreamID));
+
+ if (self->mInputFrameID >= self->mNextStreamID) {
+ self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
+ }
+
+ self->mDecompressBuffer.Append(
+ &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
+ priorityLen],
+ self->mInputFrameDataSize - paddingControlBytes - priorityLen -
+ paddingLength);
+
+ if (self->mInputFrameFlags & kFlag_END_HEADERS) {
+ rv = self->UncompressAndDiscard(false);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::RecvHeaders uncompress failed\n"));
+ // this is fatal to the session
+ self->mGoAwayReason = COMPRESSION_ERROR;
+ return rv;
+ }
+ }
+
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ // make sure this is either the first headers or a trailer
+ if (self->mInputFrameDataStream->AllHeadersReceived() &&
+ !(self->mInputFrameFlags & kFlag_END_STREAM)) {
+ // Any header block after the first that does *not* end the stream is
+ // illegal.
+ LOG3(("Http2Session::Illegal Extra HeaderBlock %p 0x%X\n", self,
+ self->mInputFrameID));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ // queue up any compression bytes
+ self->mDecompressBuffer.Append(
+ &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
+ priorityLen],
+ self->mInputFrameDataSize - paddingControlBytes - priorityLen -
+ paddingLength);
+
+ self->mInputFrameDataStream->UpdateTransportReadEvents(
+ self->mInputFrameDataSize);
+ self->mLastDataReadEpoch = self->mLastReadEpoch;
+
+ if (!isContinuation) {
+ self->mAggregatedHeaderSize = self->mInputFrameDataSize -
+ paddingControlBytes - priorityLen -
+ paddingLength;
+ } else {
+ self->mAggregatedHeaderSize += self->mInputFrameDataSize -
+ paddingControlBytes - priorityLen -
+ paddingLength;
+ }
+
+ if (!endHeadersFlag) { // more are coming - don't process yet
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (isContinuation) {
+ Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS,
+ self->mAggregatedHeaderSize);
+ }
+
+ rv = self->ResponseHeadersComplete();
+ if (rv == NS_ERROR_ILLEGAL_VALUE) {
+ LOG3(("Http2Session::RecvHeaders %p PROTOCOL_ERROR detected stream 0x%X\n",
+ self, self->mInputFrameID));
+ self->CleanupStream(self->mInputFrameDataStream, rv, PROTOCOL_ERROR);
+ self->ResetDownstreamState();
+ rv = NS_OK;
+ } else if (NS_FAILED(rv)) {
+ // This is fatal to the session.
+ self->mGoAwayReason = COMPRESSION_ERROR;
+ }
+ return rv;
+}
+
+// ResponseHeadersComplete() returns NS_ERROR_ILLEGAL_VALUE when the stream
+// should be reset with a PROTOCOL_ERROR, NS_OK when the response headers were
+// fine, and any other error is fatal to the session.
+nsresult Http2Session::ResponseHeadersComplete() {
+ LOG3(("Http2Session::ResponseHeadersComplete %p for 0x%X fin=%d", this,
+ mInputFrameDataStream->StreamID(), mInputFrameFinal));
+
+ // Anything prior to AllHeadersReceived() => true is actual headers. After
+ // that, we need to handle them as trailers instead (which are special-cased
+ // so we don't have to use the nasty chunked parser for all h2, just in case).
+ if (mInputFrameDataStream->AllHeadersReceived()) {
+ LOG3(("Http2Session::ResponseHeadersComplete processing trailers"));
+ MOZ_ASSERT(mInputFrameFlags & kFlag_END_STREAM);
+ nsresult rv = mInputFrameDataStream->ConvertResponseTrailers(
+ &mDecompressor, mDecompressBuffer);
+ if (NS_FAILED(rv)) {
+ LOG3((
+ "Http2Session::ResponseHeadersComplete trailer conversion failed\n"));
+ return rv;
+ }
+ mFlatHTTPResponseHeadersOut = 0;
+ mFlatHTTPResponseHeaders.Truncate();
+ if (mInputFrameFinal) {
+ // need to process the fin
+ ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
+ } else {
+ ResetDownstreamState();
+ }
+
+ return NS_OK;
+ }
+
+ // if this turns out to be a 1xx response code we have to
+ // undo the headers received bit that we are setting here.
+ bool didFirstSetAllRecvd = !mInputFrameDataStream->AllHeadersReceived();
+ mInputFrameDataStream->SetAllHeadersReceived();
+
+ // The stream needs to see flattened http headers
+ // Uncompressed http/2 format headers currently live in
+ // Http2StreamBase::mDecompressBuffer - convert that to HTTP format in
+ // mFlatHTTPResponseHeaders via ConvertHeaders()
+
+ nsresult rv;
+ int32_t httpResponseCode; // out param to ConvertResponseHeaders
+ mFlatHTTPResponseHeadersOut = 0;
+ rv = mInputFrameDataStream->ConvertResponseHeaders(
+ &mDecompressor, mDecompressBuffer, mFlatHTTPResponseHeaders,
+ httpResponseCode);
+ if (rv == NS_ERROR_NET_RESET) {
+ LOG(
+ ("Http2Session::ResponseHeadersComplete %p ConvertResponseHeaders "
+ "reset\n",
+ this));
+ // This means the stream found connection-oriented auth. Treat this like we
+ // got a reset with HTTP_1_1_REQUIRED.
+ mInputFrameDataStream->DisableSpdy();
+ CleanupStream(mInputFrameDataStream, NS_ERROR_NET_RESET, CANCEL_ERROR);
+ ResetDownstreamState();
+ return NS_OK;
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // allow more headers in the case of 1xx
+ if (((httpResponseCode / 100) == 1) && didFirstSetAllRecvd) {
+ mInputFrameDataStream->UnsetAllHeadersReceived();
+ }
+
+ ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvPriority(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PRIORITY);
+
+ if (self->mInputFrameDataSize != 5) {
+ LOG3(("Http2Session::RecvPriority %p wrong length data=%d\n", self,
+ self->mInputFrameDataSize));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ if (!self->mInputFrameID) {
+ LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t newPriorityDependency = NetworkEndian::readUint32(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes);
+ bool exclusive = !!(newPriorityDependency & 0x80000000);
+ newPriorityDependency &= 0x7fffffff;
+ uint8_t newPriorityWeight =
+ *(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);
+
+ // undefined what it means when the server sends a priority frame. ignore it.
+ LOG3(
+ ("Http2Session::RecvPriority %p 0x%X received dependency=0x%X "
+ "weight=%u exclusive=%d",
+ self->mInputFrameDataStream, self->mInputFrameID, newPriorityDependency,
+ newPriorityWeight, exclusive));
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvRstStream(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_RST_STREAM);
+
+ if (self->mInputFrameDataSize != 4) {
+ LOG3(("Http2Session::RecvRstStream %p RST_STREAM wrong length data=%d",
+ self, self->mInputFrameDataSize));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ if (!self->mInputFrameID) {
+ LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ self->mDownstreamRstReason = NetworkEndian::readUint32(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes);
+
+ LOG3(("Http2Session::RecvRstStream %p RST_STREAM Reason Code %u ID %x\n",
+ self, self->mDownstreamRstReason, self->mInputFrameID));
+
+ DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!self->mInputFrameDataStream) {
+ // if we can't find the stream just ignore it (4.2 closed)
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ self->mInputFrameDataStream->SetRecvdReset(true);
+ self->MaybeDecrementConcurrent(self->mInputFrameDataStream);
+ self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM);
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvSettings(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_SETTINGS);
+
+ if (self->mInputFrameID) {
+ LOG3(("Http2Session::RecvSettings %p needs stream ID of 0. 0x%X\n", self,
+ self->mInputFrameID));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ if (self->mInputFrameDataSize % 6) {
+ // Number of Settings is determined by dividing by each 6 byte setting
+ // entry. So the payload must be a multiple of 6.
+ LOG3(("Http2Session::RecvSettings %p SETTINGS wrong length data=%d", self,
+ self->mInputFrameDataSize));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ self->mReceivedSettings = true;
+
+ uint32_t numEntries = self->mInputFrameDataSize / 6;
+ LOG3(
+ ("Http2Session::RecvSettings %p SETTINGS Control Frame "
+ "with %d entries ack=%X",
+ self, numEntries, self->mInputFrameFlags & kFlag_ACK));
+
+ if ((self->mInputFrameFlags & kFlag_ACK) && self->mInputFrameDataSize) {
+ LOG3(("Http2Session::RecvSettings %p ACK with non zero payload is err\n",
+ self));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ for (uint32_t index = 0; index < numEntries; ++index) {
+ uint8_t* setting =
+ reinterpret_cast<uint8_t*>(self->mInputFrameBuffer.get()) +
+ kFrameHeaderBytes + index * 6;
+
+ uint16_t id = NetworkEndian::readUint16(setting);
+ uint32_t value = NetworkEndian::readUint32(setting + 2);
+ LOG3(("Settings ID %u, Value %u", id, value));
+
+ switch (id) {
+ case SETTINGS_TYPE_HEADER_TABLE_SIZE:
+ LOG3(("Compression header table setting received: %d\n", value));
+ self->mCompressor.SetMaxBufferSize(value);
+ break;
+
+ case SETTINGS_TYPE_ENABLE_PUSH:
+ LOG3(("Client received an ENABLE Push SETTING. Odd.\n"));
+ // nop
+ break;
+
+ case SETTINGS_TYPE_MAX_CONCURRENT:
+ self->mMaxConcurrent = value;
+ Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value);
+ self->ProcessPending();
+ break;
+
+ case SETTINGS_TYPE_INITIAL_WINDOW: {
+ Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10);
+ int32_t delta = value - self->mServerInitialStreamWindow;
+ self->mServerInitialStreamWindow = value;
+
+ // SETTINGS only adjusts stream windows. Leave the session window alone.
+ // We need to add the delta to all open streams (delta can be negative)
+ for (const auto& stream : self->mStreamTransactionHash.Values()) {
+ stream->UpdateServerReceiveWindow(delta);
+ }
+ } break;
+
+ case SETTINGS_TYPE_MAX_FRAME_SIZE: {
+ if ((value < kMaxFrameData) || (value >= 0x01000000)) {
+ LOG3(("Received invalid max frame size 0x%X", value));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+ // We stick to the default for simplicity's sake, so nothing to change
+ } break;
+
+ case SETTINGS_TYPE_ENABLE_CONNECT_PROTOCOL: {
+ if (value == 1) {
+ LOG3(("Enabling extended CONNECT"));
+ self->mPeerAllowsWebsockets = true;
+ } else if (value > 1) {
+ LOG3(("Peer sent invalid value for ENABLE_CONNECT_PROTOCOL %d",
+ value));
+ return self->SessionError(PROTOCOL_ERROR);
+ } else if (self->mPeerAllowsWebsockets) {
+ LOG3(("Peer tried to re-disable extended CONNECT"));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+ self->mHasTransactionWaitingForWebsockets = true;
+ } break;
+
+ default:
+ LOG3(("Received an unknown SETTING id %d. Ignoring.", id));
+ break;
+ }
+ }
+
+ self->ResetDownstreamState();
+
+ if (!(self->mInputFrameFlags & kFlag_ACK)) {
+ self->GenerateSettingsAck();
+ } else if (self->mWaitingForSettingsAck) {
+ self->mGoAwayOnPush = true;
+ }
+
+ if (!self->mProcessedWaitingWebsockets) {
+ self->mProcessedWaitingWebsockets = true;
+ }
+
+ if (self->mHasTransactionWaitingForWebsockets) {
+ // trigger a queued websockets transaction -- enabled or not
+ LOG3(("Http2Sesssion::RecvSettings triggering queued websocket"));
+ RefPtr<nsHttpConnectionInfo> ci;
+ self->GetConnectionInfo(getter_AddRefs(ci));
+ gHttpHandler->ConnMgr()->ProcessPendingQ(ci);
+ self->mHasTransactionWaitingForWebsockets = false;
+ }
+
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvPushPromise(Http2Session* self) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PUSH_PROMISE ||
+ self->mInputFrameType == FRAME_TYPE_CONTINUATION);
+
+ // Find out how much padding this frame has, so we can only extract the real
+ // header data from the frame.
+ uint16_t paddingLength = 0;
+ uint8_t paddingControlBytes = 0;
+
+ // If this doesn't have END_PUSH_PROMISE set on it then require the next
+ // frame to be PUSH_PROMISE of the same ID
+ uint32_t promiseLen;
+ uint32_t promisedID;
+
+ if (self->mExpectedPushPromiseID) {
+ promiseLen = 0; // really a continuation frame
+ promisedID = self->mContinuedPromiseStream;
+ } else {
+ self->mDecompressBuffer.Truncate();
+ nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ promiseLen = 4;
+ promisedID =
+ NetworkEndian::readUint32(self->mInputFrameBuffer.get() +
+ kFrameHeaderBytes + paddingControlBytes);
+ promisedID &= 0x7fffffff;
+ if (promisedID <= self->mLastPushedID) {
+ LOG3(("Http2Session::RecvPushPromise %p ID too low %u expected > %u.\n",
+ self, promisedID, self->mLastPushedID));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+ self->mLastPushedID = promisedID;
+ }
+
+ uint32_t associatedID = self->mInputFrameID;
+
+ if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
+ self->mExpectedPushPromiseID = 0;
+ self->mContinuedPromiseStream = 0;
+ } else {
+ self->mExpectedPushPromiseID = self->mInputFrameID;
+ self->mContinuedPromiseStream = promisedID;
+ }
+
+ if ((paddingControlBytes + promiseLen + paddingLength) >
+ self->mInputFrameDataSize) {
+ // This is fatal to the session
+ LOG3(
+ ("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
+ "PROTOCOL_ERROR extra %d > frame size %d\n",
+ self, promisedID, associatedID,
+ (paddingControlBytes + promiseLen + paddingLength),
+ self->mInputFrameDataSize));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ LOG3(
+ ("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
+ "paddingLength %d padded %d\n",
+ self, promisedID, associatedID, paddingLength,
+ self->mInputFrameFlags & kFlag_PADDED));
+
+ if (!associatedID || !promisedID || (promisedID & 1)) {
+ LOG3(("Http2Session::RecvPushPromise %p ID invalid.\n", self));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ // confirm associated-to
+ nsresult rv = self->SetInputFrameDataStream(associatedID);
+ if (NS_FAILED(rv)) return rv;
+
+ Http2StreamBase* associatedStream = self->mInputFrameDataStream;
+ ++(self->mServerPushedResources);
+
+ // Anytime we start using the high bit of stream ID (either client or server)
+ // begin to migrate to a new session.
+ if (promisedID >= kMaxStreamID) self->mShouldGoAway = true;
+
+ bool resetStream = true;
+ SpdyPushCache* cache = nullptr;
+
+ if (self->mShouldGoAway && !Http2PushedStream::TestOnPush(associatedStream)) {
+ LOG3(
+ ("Http2Session::RecvPushPromise %p cache push while in GoAway "
+ "mode refused.\n",
+ self));
+ self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
+ } else if (!StaticPrefs::network_http_http2_allow_push()) {
+ // ENABLE_PUSH and MAX_CONCURRENT_STREAMS of 0 in settings disabled push
+ LOG3(("Http2Session::RecvPushPromise Push Recevied when Disabled\n"));
+ if (self->mGoAwayOnPush) {
+ LOG3(("Http2Session::RecvPushPromise sending GOAWAY"));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+ self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
+ } else if (!(associatedID & 1)) {
+ LOG3(
+ ("Http2Session::RecvPushPromise %p assocated=0x%X on pushed (even) "
+ "stream not allowed\n",
+ self, associatedID));
+ self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
+ } else if (!associatedStream) {
+ LOG3(("Http2Session::RecvPushPromise %p lookup associated ID failed.\n",
+ self));
+ self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
+ } else if (Http2PushedStream::TestOnPush(associatedStream)) {
+ LOG3(("Http2Session::RecvPushPromise %p will be handled by push listener.",
+ self));
+ resetStream = false;
+ } else {
+ nsIRequestContext* requestContext = associatedStream->RequestContext();
+ if (requestContext) {
+ cache = requestContext->GetSpdyPushCache();
+ if (!cache) {
+ cache = new SpdyPushCache();
+ requestContext->SetSpdyPushCache(cache);
+ }
+ }
+ if (!cache) {
+ // this is unexpected, but we can handle it just by refusing the push
+ LOG3(
+ ("Http2Session::RecvPushPromise Push Recevied without push cache\n"));
+ self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
+ } else {
+ resetStream = false;
+ }
+ }
+
+ if (resetStream) {
+ // Need to decompress the headers even though we aren't using them yet in
+ // order to keep the compression context consistent for other frames
+ self->mDecompressBuffer.Append(
+ &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
+ promiseLen],
+ self->mInputFrameDataSize - paddingControlBytes - promiseLen -
+ paddingLength);
+ if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
+ rv = self->UncompressAndDiscard(true);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::RecvPushPromise uncompress failed\n"));
+ self->mGoAwayReason = COMPRESSION_ERROR;
+ return rv;
+ }
+ }
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ self->mDecompressBuffer.Append(
+ &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
+ promiseLen],
+ self->mInputFrameDataSize - paddingControlBytes - promiseLen -
+ paddingLength);
+
+ if (self->mInputFrameType != FRAME_TYPE_CONTINUATION) {
+ self->mAggregatedHeaderSize = self->mInputFrameDataSize -
+ paddingControlBytes - promiseLen -
+ paddingLength;
+ } else {
+ self->mAggregatedHeaderSize += self->mInputFrameDataSize -
+ paddingControlBytes - promiseLen -
+ paddingLength;
+ }
+
+ if (!(self->mInputFrameFlags & kFlag_END_PUSH_PROMISE)) {
+ LOG3(
+ ("Http2Session::RecvPushPromise not finishing processing for "
+ "multi-frame push\n"));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (self->mInputFrameType == FRAME_TYPE_CONTINUATION) {
+ Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS,
+ self->mAggregatedHeaderSize);
+ }
+
+ // Create the buffering transaction and push stream
+ RefPtr<Http2PushTransactionBuffer> transactionBuffer =
+ new Http2PushTransactionBuffer();
+ transactionBuffer->SetConnection(self);
+ RefPtr<Http2PushedStream> pushedStream(
+ new Http2PushedStream(transactionBuffer, self, associatedStream,
+ promisedID, self->mCurrentBrowserId));
+
+ rv = pushedStream->ConvertPushHeaders(&self->mDecompressor,
+ self->mDecompressBuffer,
+ pushedStream->GetRequestString());
+
+ if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ LOG3(("Http2Session::PushPromise Semantics not Implemented\n"));
+ self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (rv == NS_ERROR_ILLEGAL_VALUE) {
+ // This means the decompression completed ok, but there was a problem with
+ // the decoded headers. Reset the stream and go away.
+ self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+ if (NS_FAILED(rv)) {
+ // This is fatal to the session.
+ self->mGoAwayReason = COMPRESSION_ERROR;
+ return rv;
+ }
+
+ // Ownership of the pushed stream is by the transaction hash, just as it
+ // is for a client initiated stream. Errors that aren't fatal to the
+ // whole session must call cleanupStream() after this point in order
+ // to remove the stream from that hash.
+ WeakPtr<Http2StreamBase> pushedWeak = pushedStream.get();
+ self->mStreamTransactionHash.InsertOrUpdate(transactionBuffer,
+ std::move(pushedStream));
+ self->mPushedStreams.AppendElement(
+ static_cast<Http2PushedStream*>(pushedWeak.get()));
+
+ if (self->RegisterStreamID(pushedWeak, promisedID) == kDeadStreamID) {
+ LOG3(("Http2Session::RecvPushPromise registerstreamid failed\n"));
+ self->mGoAwayReason = INTERNAL_ERROR;
+ return NS_ERROR_FAILURE;
+ }
+
+ if (promisedID > self->mOutgoingGoAwayID) {
+ self->mOutgoingGoAwayID = promisedID;
+ }
+
+ // Fake the request side of the pushed HTTP transaction. Sets up hash
+ // key and origin
+ uint32_t notUsed;
+ Unused << pushedWeak->ReadSegments(nullptr, 1, &notUsed);
+
+ nsAutoCString key;
+ if (!static_cast<Http2PushedStream*>(pushedWeak.get())->GetHashKey(key)) {
+ LOG3(
+ ("Http2Session::RecvPushPromise one of :authority :scheme :path "
+ "missing from push\n"));
+ self->CleanupStream(pushedWeak, NS_ERROR_FAILURE, PROTOCOL_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ // does the pushed origin belong on this connection?
+ LOG3(("Http2Session::RecvPushPromise %p origin check %s", self,
+ pushedWeak->Origin().get()));
+ nsCOMPtr<nsIURI> pushedOrigin;
+ rv = MakeOriginURL(pushedWeak->Origin(), pushedOrigin);
+ nsAutoCString pushedHostName;
+ int32_t pushedPort = -1;
+ if (NS_SUCCEEDED(rv)) {
+ rv = pushedOrigin->GetHost(pushedHostName);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ rv = pushedOrigin->GetPort(&pushedPort);
+ if (NS_SUCCEEDED(rv) && pushedPort == -1) {
+ // Need to get the right default port, so TestJoinConnection below can
+ // check things correctly. See bug 1397621.
+ if (pushedOrigin->SchemeIs("http")) {
+ pushedPort = NS_HTTP_DEFAULT_PORT;
+ } else {
+ pushedPort = NS_HTTPS_DEFAULT_PORT;
+ }
+ }
+ }
+ if (NS_FAILED(rv) || !self->TestJoinConnection(pushedHostName, pushedPort)) {
+ LOG3((
+ "Http2Session::RecvPushPromise %p pushed stream mismatched origin %s\n",
+ self, pushedWeak->Origin().get()));
+ self->CleanupStream(pushedWeak, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (static_cast<Http2PushedStream*>(pushedWeak.get())->TryOnPush()) {
+ LOG3(
+ ("Http2Session::RecvPushPromise %p channel implements "
+ "nsIHttpPushListener "
+ "stream %p will not be placed into session cache.\n",
+ self, pushedWeak.get()));
+ } else {
+ LOG3(("Http2Session::RecvPushPromise %p place stream into session cache\n",
+ self));
+ if (!cache->RegisterPushedStreamHttp2(
+ key, static_cast<Http2PushedStream*>(pushedWeak.get()))) {
+ // This only happens if they've already pushed us this item.
+ LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n"));
+ self->CleanupStream(pushedWeak, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ // Kick off a lookup into the HTTP cache so we can cancel the push if it's
+ // unneeded (we already have it in our local regular cache). See bug
+ // 1367551.
+
+ // Build up our full URL for the cache lookup
+ nsAutoCString spec;
+ spec.Assign(pushedWeak->Origin());
+ spec.Append(pushedWeak->Path());
+ nsCOMPtr<nsIURI> pushedURL;
+ // Nifty trick: this doesn't actually do anything origin-specific, it's just
+ // named that way. So by passing it the full spec here, we get a URL with
+ // the full path.
+ // Another nifty trick! Even though this is using nsIURIs (which are not
+ // generally ok off the main thread), since we're not using the protocol
+ // handler to create any URIs, this will work just fine here. Don't try this
+ // at home, though, kids. I'm a trained professional.
+ if (NS_SUCCEEDED(MakeOriginURL(spec, pushedURL))) {
+ LOG3(("Http2Session::RecvPushPromise %p check disk cache for entry",
+ self));
+ mozilla::OriginAttributes oa;
+ pushedWeak->GetOriginAttributes(&oa);
+ RefPtr<Http2Session> session = self;
+ auto cachePushCheckCallback = [session, promisedID](bool aAccepted) {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!aAccepted) {
+ session->CleanupStream(promisedID, NS_ERROR_FAILURE,
+ Http2Session::REFUSED_STREAM_ERROR);
+ }
+ };
+ RefPtr<CachePushChecker> checker = new CachePushChecker(
+ pushedURL, oa,
+ static_cast<Http2PushedStream*>(pushedWeak.get())->GetRequestString(),
+ std::move(cachePushCheckCallback));
+ if (NS_FAILED(checker->DoCheck())) {
+ LOG3(
+ ("Http2Session::RecvPushPromise %p failed to open cache entry for "
+ "push check",
+ self));
+ }
+ }
+ }
+
+ if (!pushedWeak) {
+ // We have an up to date entry in our cache, so the stream was closed
+ // and released in the cachePushCheckCallback above.
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ pushedWeak->SetHTTPState(Http2StreamBase::RESERVED_BY_REMOTE);
+ static_assert(Http2StreamBase::kWorstPriority >= 0,
+ "kWorstPriority out of range");
+ uint32_t priorityDependency = pushedWeak->PriorityDependency();
+ uint8_t priorityWeight = pushedWeak->PriorityWeight();
+ self->SendPriorityFrame(promisedID, priorityDependency, priorityWeight);
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvPing(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PING);
+
+ LOG3(("Http2Session::RecvPing %p PING Flags 0x%X.", self,
+ self->mInputFrameFlags));
+
+ if (self->mInputFrameDataSize != 8) {
+ LOG3(("Http2Session::RecvPing %p PING had wrong amount of data %d", self,
+ self->mInputFrameDataSize));
+ return self->SessionError(FRAME_SIZE_ERROR);
+ }
+
+ if (self->mInputFrameID) {
+ LOG3(("Http2Session::RecvPing %p PING needs stream ID of 0. 0x%X\n", self,
+ self->mInputFrameID));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ if (self->mInputFrameFlags & kFlag_ACK) {
+ // presumably a reply to our timeout ping.. don't reply to it
+ self->mPingSentEpoch = 0;
+ // We need to reset mPreviousUsed. If we don't, the next time
+ // Http2Session::SendPing is called, it will have no effect.
+ self->mPreviousUsed = false;
+ } else {
+ // reply with a ack'd ping
+ self->GeneratePing(true);
+ }
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvGoAway(Http2Session* self) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_GOAWAY);
+
+ if (self->mInputFrameDataSize < 8) {
+ // data > 8 is an opaque token that we can't interpret. NSPR Logs will
+ // have the hex of all packets so there is no point in separately logging.
+ LOG3(("Http2Session::RecvGoAway %p GOAWAY had wrong amount of data %d",
+ self, self->mInputFrameDataSize));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ if (self->mInputFrameID) {
+ LOG3(("Http2Session::RecvGoAway %p GOAWAY had non zero stream ID 0x%X\n",
+ self, self->mInputFrameID));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ self->mConnection->SetCloseReason(ConnectionCloseReason::GO_AWAY);
+ self->mShouldGoAway = true;
+ self->mGoAwayID = NetworkEndian::readUint32(self->mInputFrameBuffer.get() +
+ kFrameHeaderBytes);
+ self->mGoAwayID &= 0x7fffffff;
+ self->mCleanShutdown = true;
+ self->mPeerGoAwayReason = NetworkEndian::readUint32(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);
+
+ // Find streams greater than the last-good ID and mark them for deletion
+ // in the mGoAwayStreamsToRestart queue. The underlying transaction can be
+ // restarted.
+ for (const auto& stream : self->mStreamTransactionHash.Values()) {
+ // these streams were not processed by the server and can be restarted.
+ // Do that after the enumerator completes to avoid the risk of
+ // a restart event re-entrantly modifying this hash. Be sure not to restart
+ // a pushed (even numbered) stream
+ if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) ||
+ !stream->HasRegisteredID()) {
+ self->mGoAwayStreamsToRestart.Push(stream);
+ }
+ }
+
+ // Process the streams marked for deletion and restart.
+ size_t size = self->mGoAwayStreamsToRestart.GetSize();
+ for (size_t count = 0; count < size; ++count) {
+ Http2StreamBase* stream =
+ static_cast<Http2StreamBase*>(self->mGoAwayStreamsToRestart.PopFront());
+
+ if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
+ stream->DisableSpdy();
+ }
+ self->CloseStream(stream, NS_ERROR_NET_RESET);
+ if (stream->HasRegisteredID()) {
+ self->mStreamIDHash.Remove(stream->StreamID());
+ }
+ self->mStreamTransactionHash.Remove(stream->Transaction());
+ }
+
+ // Queued streams can also be deleted from this session and restarted
+ // in another one. (they were never sent on the network so they implicitly
+ // are not covered by the last-good id.
+ for (const auto& stream : self->mQueuedStreams) {
+ MOZ_ASSERT(stream->Queued());
+ stream->SetQueued(false);
+ if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
+ stream->DisableSpdy();
+ }
+ self->CloseStream(stream, NS_ERROR_NET_RESET, false);
+ self->mStreamTransactionHash.Remove(stream->Transaction());
+ }
+ self->mQueuedStreams.Clear();
+
+ LOG3(
+ ("Http2Session::RecvGoAway %p GOAWAY Last-Good-ID 0x%X status 0x%X "
+ "live streams=%d\n",
+ self, self->mGoAwayID, self->mPeerGoAwayReason,
+ self->mStreamTransactionHash.Count()));
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvWindowUpdate(Http2Session* self) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_WINDOW_UPDATE);
+
+ if (self->mInputFrameDataSize != 4) {
+ LOG3(("Http2Session::RecvWindowUpdate %p Window Update wrong length %d\n",
+ self, self->mInputFrameDataSize));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ uint32_t delta = NetworkEndian::readUint32(self->mInputFrameBuffer.get() +
+ kFrameHeaderBytes);
+ delta &= 0x7fffffff;
+
+ LOG3(("Http2Session::RecvWindowUpdate %p len=%d Stream 0x%X.\n", self, delta,
+ self->mInputFrameID));
+
+ if (self->mInputFrameID) { // stream window
+ nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!self->mInputFrameDataStream) {
+ LOG3(("Http2Session::RecvWindowUpdate %p lookup streamID 0x%X failed.\n",
+ self, self->mInputFrameID));
+ // only reset the session if the ID is one we haven't ever opened
+ if (self->mInputFrameID >= self->mNextStreamID) {
+ self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
+ }
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (delta == 0) {
+ LOG3(("Http2Session::RecvWindowUpdate %p received 0 stream window update",
+ self));
+ self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
+ PROTOCOL_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ int64_t oldRemoteWindow =
+ self->mInputFrameDataStream->ServerReceiveWindow();
+ self->mInputFrameDataStream->UpdateServerReceiveWindow(delta);
+ if (self->mInputFrameDataStream->ServerReceiveWindow() >= 0x80000000) {
+ // a window cannot reach 2^31 and be in compliance. Our calculations
+ // are 64 bit safe though.
+ LOG3(
+ ("Http2Session::RecvWindowUpdate %p stream window "
+ "exceeds 2^31 - 1\n",
+ self));
+ self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
+ FLOW_CONTROL_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ LOG3(
+ ("Http2Session::RecvWindowUpdate %p stream 0x%X window "
+ "%" PRId64 " increased by %" PRIu32 " now %" PRId64 ".\n",
+ self, self->mInputFrameID, oldRemoteWindow, delta,
+ oldRemoteWindow + delta));
+
+ } else { // session window update
+ if (delta == 0) {
+ LOG3(
+ ("Http2Session::RecvWindowUpdate %p received 0 session window update",
+ self));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ int64_t oldRemoteWindow = self->mServerSessionWindow;
+ self->mServerSessionWindow += delta;
+
+ if (self->mServerSessionWindow >= 0x80000000) {
+ // a window cannot reach 2^31 and be in compliance. Our calculations
+ // are 64 bit safe though.
+ LOG3(
+ ("Http2Session::RecvWindowUpdate %p session window "
+ "exceeds 2^31 - 1\n",
+ self));
+ return self->SessionError(FLOW_CONTROL_ERROR);
+ }
+
+ if ((oldRemoteWindow <= 0) && (self->mServerSessionWindow > 0)) {
+ LOG3(
+ ("Http2Session::RecvWindowUpdate %p restart session window\n", self));
+ for (const auto& stream : self->mStreamTransactionHash.Values()) {
+ MOZ_ASSERT(self->mServerSessionWindow > 0);
+
+ if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0) {
+ continue;
+ }
+
+ AddStreamToQueue(stream, self->mReadyForWrite);
+ self->SetWriteCallbacks();
+ }
+ }
+ LOG3(
+ ("Http2Session::RecvWindowUpdate %p session window "
+ "%" PRId64 " increased by %d now %" PRId64 ".\n",
+ self, oldRemoteWindow, delta, oldRemoteWindow + delta));
+ }
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvContinuation(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_CONTINUATION);
+ MOZ_ASSERT(self->mInputFrameID);
+ MOZ_ASSERT(self->mExpectedPushPromiseID || self->mExpectedHeaderID);
+ MOZ_ASSERT(!(self->mExpectedPushPromiseID && self->mExpectedHeaderID));
+
+ LOG3(
+ ("Http2Session::RecvContinuation %p Flags 0x%X id 0x%X "
+ "promise id 0x%X header id 0x%X\n",
+ self, self->mInputFrameFlags, self->mInputFrameID,
+ self->mExpectedPushPromiseID, self->mExpectedHeaderID));
+
+ DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (!self->mInputFrameDataStream) {
+ LOG3(("Http2Session::RecvContination stream ID 0x%X not found.",
+ self->mInputFrameID));
+ return self->SessionError(PROTOCOL_ERROR);
+ }
+
+ // continued headers
+ if (self->mExpectedHeaderID) {
+ self->mInputFrameFlags &= ~kFlag_PRIORITY;
+ return RecvHeaders(self);
+ }
+
+ // continued push promise
+ if (self->mInputFrameFlags & kFlag_END_HEADERS) {
+ self->mInputFrameFlags &= ~kFlag_END_HEADERS;
+ self->mInputFrameFlags |= kFlag_END_PUSH_PROMISE;
+ }
+ return RecvPushPromise(self);
+}
+
+class UpdateAltSvcEvent : public Runnable {
+ public:
+ UpdateAltSvcEvent(const nsCString& header, const nsCString& aOrigin,
+ nsHttpConnectionInfo* aCI)
+ : Runnable("net::UpdateAltSvcEvent"),
+ mHeader(header),
+ mOrigin(aOrigin),
+ mCI(aCI) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCString originScheme;
+ nsCString originHost;
+ int32_t originPort = -1;
+
+ nsCOMPtr<nsIURI> uri;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), mOrigin))) {
+ LOG(("UpdateAltSvcEvent origin does not parse %s\n", mOrigin.get()));
+ return NS_OK;
+ }
+ uri->GetScheme(originScheme);
+ uri->GetHost(originHost);
+ uri->GetPort(&originPort);
+
+ if (XRE_IsSocketProcess()) {
+ AltServiceChild::ProcessHeader(
+ mHeader, originScheme, originHost, originPort, mCI->GetUsername(),
+ mCI->GetPrivate(), nullptr, mCI->ProxyInfo(), 0,
+ mCI->GetOriginAttributes());
+ return NS_OK;
+ }
+
+ AltSvcMapping::ProcessHeader(mHeader, originScheme, originHost, originPort,
+ mCI->GetUsername(), mCI->GetPrivate(), nullptr,
+ mCI->ProxyInfo(), 0,
+ mCI->GetOriginAttributes());
+ return NS_OK;
+ }
+
+ private:
+ nsCString mHeader;
+ nsCString mOrigin;
+ RefPtr<nsHttpConnectionInfo> mCI;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+};
+
+// defined as an http2 extension - alt-svc
+// defines receipt of frame type 0x0A.. See AlternateSevices.h at least draft
+// -06 sec 4 as this is an extension, never generate protocol error - just
+// ignore problems
+nsresult Http2Session::RecvAltSvc(Http2Session* self) {
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ALTSVC);
+ LOG3(("Http2Session::RecvAltSvc %p Flags 0x%X id 0x%X\n", self,
+ self->mInputFrameFlags, self->mInputFrameID));
+
+ if (self->mInputFrameDataSize < 2) {
+ LOG3(("Http2Session::RecvAltSvc %p frame too small", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ uint16_t originLen = NetworkEndian::readUint16(self->mInputFrameBuffer.get() +
+ kFrameHeaderBytes);
+ if (originLen + 2U > self->mInputFrameDataSize) {
+ LOG3(("Http2Session::RecvAltSvc %p origin len too big for frame", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (!gHttpHandler->AllowAltSvc()) {
+ LOG3(("Http2Session::RecvAltSvc %p frame alt service pref'd off", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ uint16_t altSvcFieldValueLen =
+ static_cast<uint16_t>(self->mInputFrameDataSize) - 2U - originLen;
+ LOG3((
+ "Http2Session::RecvAltSvc %p frame originLen=%u altSvcFieldValueLen=%u\n",
+ self, originLen, altSvcFieldValueLen));
+
+ if (self->mInputFrameDataSize > 2000) {
+ LOG3(("Http2Session::RecvAltSvc %p frame too large to parse sensibly",
+ self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ nsAutoCString origin;
+ bool impliedOrigin = true;
+ if (originLen) {
+ origin.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2,
+ originLen);
+ impliedOrigin = false;
+ }
+
+ nsAutoCString altSvcFieldValue;
+ if (altSvcFieldValueLen) {
+ altSvcFieldValue.Assign(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2 + originLen,
+ altSvcFieldValueLen);
+ }
+
+ if (altSvcFieldValue.IsEmpty() ||
+ !nsHttp::IsReasonableHeaderValue(altSvcFieldValue)) {
+ LOG(
+ ("Http2Session %p Alt-Svc Response Header seems unreasonable - "
+ "skipping\n",
+ self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (self->mInputFrameID & 1) {
+ // pulled streams apply to the origin of the pulled stream.
+ // If the origin field is filled in the frame, the frame should be ignored
+ if (!origin.IsEmpty()) {
+ LOG(("Http2Session %p Alt-Svc pulled stream has non empty origin\n",
+ self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (NS_FAILED(self->SetInputFrameDataStream(self->mInputFrameID)) ||
+ !self->mInputFrameDataStream ||
+ !self->mInputFrameDataStream->Transaction() ||
+ !self->mInputFrameDataStream->Transaction()->RequestHead()) {
+ LOG3(
+ ("Http2Session::RecvAltSvc %p got frame w/o origin on invalid stream",
+ self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ self->mInputFrameDataStream->Transaction()->RequestHead()->Origin(origin);
+ } else if (!self->mInputFrameID) {
+ // ID 0 streams must supply their own origin
+ if (origin.IsEmpty()) {
+ LOG(("Http2Session %p Alt-Svc Stream 0 has empty origin\n", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+ } else {
+ // handling of push streams is not defined. Let's ignore it
+ LOG(("Http2Session %p Alt-Svc received on pushed stream - ignoring\n",
+ self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ RefPtr<nsHttpConnectionInfo> ci(self->ConnectionInfo());
+ if (!self->mConnection || !ci) {
+ LOG3(("Http2Session::RecvAltSvc %p no connection or conninfo for %d", self,
+ self->mInputFrameID));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (!impliedOrigin) {
+ bool okToReroute = true;
+ nsCOMPtr<nsITLSSocketControl> ssl;
+ self->mConnection->GetTLSSocketControl(getter_AddRefs(ssl));
+ if (!ssl) {
+ okToReroute = false;
+ }
+
+ // a little off main thread origin parser. This is a non critical function
+ // because any alternate route created has to be verified anyhow
+ nsAutoCString specifiedOriginHost;
+ if (StringBeginsWith(origin, "https://"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ specifiedOriginHost.Assign(origin.get() + 8, origin.Length() - 8);
+ } else if (StringBeginsWith(origin, "http://"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ specifiedOriginHost.Assign(origin.get() + 7, origin.Length() - 7);
+ }
+
+ int32_t colonOffset = specifiedOriginHost.FindCharInSet(":", 0);
+ if (colonOffset != kNotFound) {
+ specifiedOriginHost.Truncate(colonOffset);
+ }
+
+ if (okToReroute) {
+ ssl->IsAcceptableForHost(specifiedOriginHost, &okToReroute);
+ }
+
+ if (!okToReroute) {
+ LOG3(
+ ("Http2Session::RecvAltSvc %p can't reroute non-authoritative origin "
+ "%s",
+ self, origin.BeginReading()));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+ }
+
+ RefPtr<UpdateAltSvcEvent> event =
+ new UpdateAltSvcEvent(altSvcFieldValue, origin, ci);
+ NS_DispatchToMainThread(event);
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+void Http2Session::Received421(nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::Recevied421 %p %d\n", this, mOriginFrameActivated));
+ if (!mOriginFrameActivated || !ci) {
+ return;
+ }
+
+ nsAutoCString key(ci->GetOrigin());
+ key.Append(':');
+ key.AppendInt(ci->OriginPort());
+ mOriginFrame.Remove(key);
+ LOG3(("Http2Session::Received421 %p key %s removed\n", this, key.get()));
+}
+
+nsresult Http2Session::RecvUnused(Http2Session* self) {
+ LOG3(("Http2Session %p unknown frame type %x ignored\n", self,
+ self->mInputFrameType));
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+// defined as an http2 extension - origin
+// defines receipt of frame type 0x0b..
+// http://httpwg.org/http-extensions/origin-frame.html as this is an extension,
+// never generate protocol error - just ignore problems
+nsresult Http2Session::RecvOrigin(Http2Session* self) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ORIGIN);
+ LOG3(("Http2Session::RecvOrigin %p Flags 0x%X id 0x%X\n", self,
+ self->mInputFrameFlags, self->mInputFrameID));
+
+ if (self->mInputFrameFlags & 0x0F) {
+ LOG3(("Http2Session::RecvOrigin %p leading flags must be 0", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (self->mInputFrameID) {
+ LOG3(("Http2Session::RecvOrigin %p not stream 0", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (self->ConnectionInfo()->UsingProxy()) {
+ LOG3(("Http2Session::RecvOrigin %p must not use proxy", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (!gHttpHandler->AllowOriginExtension()) {
+ LOG3(("Http2Session::RecvOrigin %p origin extension pref'd off", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ uint32_t offset = 0;
+ self->mOriginFrameActivated = true;
+
+ while (self->mInputFrameDataSize >= (offset + 2U)) {
+ uint16_t originLen = NetworkEndian::readUint16(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset);
+ LOG3(("Http2Session::RecvOrigin %p origin extension defined as %d bytes\n",
+ self, originLen));
+ if (originLen + 2U + offset > self->mInputFrameDataSize) {
+ LOG3(("Http2Session::RecvOrigin %p origin len too big for frame", self));
+ break;
+ }
+
+ nsAutoCString originString;
+ nsCOMPtr<nsIURI> originURL;
+ originString.Assign(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset + 2,
+ originLen);
+ offset += originLen + 2;
+ if (NS_FAILED(MakeOriginURL(originString, originURL))) {
+ LOG3(
+ ("Http2Session::RecvOrigin %p origin frame string %s failed to "
+ "parse\n",
+ self, originString.get()));
+ continue;
+ }
+
+ LOG3(("Http2Session::RecvOrigin %p origin frame string %s parsed OK\n",
+ self, originString.get()));
+ if (!originURL->SchemeIs("https")) {
+ LOG3(("Http2Session::RecvOrigin %p origin frame not https\n", self));
+ continue;
+ }
+
+ int32_t port = -1;
+ originURL->GetPort(&port);
+ if (port == -1) {
+ port = 443;
+ }
+ // dont use ->GetHostPort because we want explicit 443
+ nsAutoCString host;
+ originURL->GetHost(host);
+ nsAutoCString key(host);
+ key.Append(':');
+ key.AppendInt(port);
+ self->mOriginFrame.WithEntryHandle(key, [&](auto&& entry) {
+ if (!entry) {
+ entry.Insert(true);
+ RefPtr<HttpConnectionBase> conn(self->HttpConnection());
+ MOZ_ASSERT(conn.get());
+ gHttpHandler->ConnMgr()->RegisterOriginCoalescingKey(conn, host, port);
+ } else {
+ LOG3(("Http2Session::RecvOrigin %p origin frame already in set\n",
+ self));
+ }
+ });
+ }
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpTransaction. It is expected that nsHttpConnection is the caller
+// of these methods
+//-----------------------------------------------------------------------------
+
+void Http2Session::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
+ int64_t aProgress) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ switch (aStatus) {
+ // These should appear only once, deliver to the first
+ // transaction on the session.
+ case NS_NET_STATUS_RESOLVING_HOST:
+ case NS_NET_STATUS_RESOLVED_HOST:
+ case NS_NET_STATUS_CONNECTING_TO:
+ case NS_NET_STATUS_CONNECTED_TO:
+ case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
+ case NS_NET_STATUS_TLS_HANDSHAKE_ENDED: {
+ if (!mFirstHttpTransaction) {
+ // if we still do not have a HttpTransaction store timings info in
+ // a HttpConnection.
+ // If some error occur it can happen that we do not have a connection.
+ if (mConnection) {
+ RefPtr<HttpConnectionBase> conn = mConnection->HttpConnection();
+ conn->SetEvent(aStatus);
+ }
+ } else {
+ mFirstHttpTransaction->OnTransportStatus(aTransport, aStatus,
+ aProgress);
+ }
+
+ if (aStatus == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
+ mFirstHttpTransaction = nullptr;
+ mTlsHandshakeFinished = true;
+ }
+ break;
+ }
+
+ default:
+ // The other transport events are ignored here because there is no good
+ // way to map them to the right transaction in http/2. Instead, the events
+ // are generated again from the http/2 code and passed directly to the
+ // correct transaction.
+
+ // NS_NET_STATUS_SENDING_TO:
+ // This is generated by the socket transport when (part) of
+ // a transaction is written out
+ //
+ // There is no good way to map it to the right transaction in http/2,
+ // so it is ignored here and generated separately when the request
+ // is sent from Http2StreamBase::TransmitFrame
+
+ // NS_NET_STATUS_WAITING_FOR:
+ // Created by nsHttpConnection when the request has been totally sent.
+ // There is no good way to map it to the right transaction in http/2,
+ // so it is ignored here and generated separately when the same
+ // condition is complete in Http2StreamBase when there is no more
+ // request body left to be transmitted.
+
+ // NS_NET_STATUS_RECEIVING_FROM
+ // Generated in session whenever we read a data frame or a HEADERS
+ // that can be attributed to a particular stream/transaction
+
+ break;
+ }
+}
+
+// ReadSegments() is used to write data to the network. Generally, HTTP
+// request data is pulled from the approriate transaction and
+// converted to http/2 data. Sometimes control data like window-update are
+// generated instead.
+
+nsresult Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader* reader,
+ uint32_t count, uint32_t* countRead,
+ bool* again) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mSegmentReader || !reader || (mSegmentReader == reader),
+ "Inconsistent Write Function Callback");
+
+ nsresult rv = ConfirmTLSProfile();
+ if (NS_FAILED(rv)) {
+ if (mGoAwayReason == INADEQUATE_SECURITY) {
+ LOG3(
+ ("Http2Session::ReadSegments %p returning INADEQUATE_SECURITY "
+ "%" PRIx32,
+ this, static_cast<uint32_t>(NS_ERROR_NET_INADEQUATE_SECURITY)));
+ rv = NS_ERROR_NET_INADEQUATE_SECURITY;
+ }
+ return rv;
+ }
+
+ if (reader) mSegmentReader = reader;
+
+ *countRead = 0;
+
+ LOG3(("Http2Session::ReadSegments %p", this));
+
+ RefPtr<Http2StreamBase> stream = GetNextStreamFromQueue(mReadyForWrite);
+
+ if (!stream) {
+ LOG3(("Http2Session %p could not identify a stream to write; suspending.",
+ this));
+ uint32_t availBeforeFlush = mOutputQueueUsed - mOutputQueueSent;
+ FlushOutputQueue();
+ uint32_t availAfterFlush = mOutputQueueUsed - mOutputQueueSent;
+ if (availBeforeFlush != availAfterFlush) {
+ LOG3(("Http2Session %p ResumeRecv After early flush in ReadSegments",
+ this));
+ Unused << ResumeRecv();
+ }
+ SetWriteCallbacks();
+ if (mAttemptingEarlyData) {
+ // We can still try to send our preamble as early-data
+ *countRead = mOutputQueueUsed - mOutputQueueSent;
+ LOG(("Http2Session %p nothing to send because of 0RTT failed", this));
+ Unused << ResumeRecv();
+ }
+ return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ uint32_t earlyDataUsed = 0;
+ if (mAttemptingEarlyData) {
+ if (!stream->Do0RTT()) {
+ LOG3(
+ ("Http2Session %p will not get early data from Http2StreamBase %p "
+ "0x%X",
+ this, stream.get(), stream->StreamID()));
+ FlushOutputQueue();
+ SetWriteCallbacks();
+ if (!mCannotDo0RTTStreams.Contains(stream)) {
+ mCannotDo0RTTStreams.AppendElement(stream);
+ }
+ // We can still send our preamble
+ *countRead = mOutputQueueUsed - mOutputQueueSent;
+ return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // Need to adjust this to only take as much as we can fit in with the
+ // preamble/settings/priority stuff
+ count -= (mOutputQueueUsed - mOutputQueueSent);
+
+ // Keep track of this to add it into countRead later, as
+ // stream->ReadSegments will likely change the value of mOutputQueueUsed.
+ earlyDataUsed = mOutputQueueUsed - mOutputQueueSent;
+ }
+
+ LOG3(
+ ("Http2Session %p will write from Http2StreamBase %p 0x%X "
+ "block-input=%d block-output=%d\n",
+ this, stream.get(), stream->StreamID(), stream->RequestBlockedOnRead(),
+ stream->BlockedOnRwin()));
+
+ rv = stream->ReadSegments(this, count, countRead);
+
+ if (earlyDataUsed) {
+ // Do this here because countRead could get reset somewhere down the rabbit
+ // hole of stream->ReadSegments, and we want to make sure we return the
+ // proper value to our caller.
+ *countRead += earlyDataUsed;
+ }
+
+ if (mAttemptingEarlyData && !m0RTTStreams.Contains(stream)) {
+ LOG3(("Http2Session::ReadSegmentsAgain adding stream %d to m0RTTStreams\n",
+ stream->StreamID()));
+ m0RTTStreams.AppendElement(stream);
+ }
+
+ // Not every permutation of stream->ReadSegents produces data (and therefore
+ // tries to flush the output queue) - SENDING_FIN_STREAM can be an example
+ // of that. But we might still have old data buffered that would be good
+ // to flush.
+ FlushOutputQueue();
+
+ // Allow new server reads - that might be data or control information
+ // (e.g. window updates or http replies) that are responses to these writes
+ Unused << ResumeRecv();
+
+ if (stream->RequestBlockedOnRead()) {
+ // We are blocked waiting for input - either more http headers or
+ // any request body data. When more data from the request stream
+ // becomes available the httptransaction will call conn->ResumeSend().
+
+ LOG3(("Http2Session::ReadSegments %p dealing with block on read", this));
+
+ // call readsegments again if there are other streams ready
+ // to run in this session
+ if (GetWriteQueueSize()) {
+ rv = NS_OK;
+ } else {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ SetWriteCallbacks();
+ return rv;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::ReadSegments %p may return FAIL code %" PRIX32, this,
+ static_cast<uint32_t>(rv)));
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ return rv;
+ }
+
+ CleanupStream(stream, rv, CANCEL_ERROR);
+ if (SoftStreamError(rv)) {
+ LOG3(("Http2Session::ReadSegments %p soft error override\n", this));
+ *again = false;
+ SetWriteCallbacks();
+ rv = NS_OK;
+ }
+ return rv;
+ }
+
+ if (*countRead > 0) {
+ LOG3(("Http2Session::ReadSegments %p stream=%p countread=%d", this,
+ stream.get(), *countRead));
+ AddStreamToQueue(stream, mReadyForWrite);
+ SetWriteCallbacks();
+ return rv;
+ }
+
+ if (stream->BlockedOnRwin()) {
+ LOG3(("Http2Session %p will stream %p 0x%X suspended for flow control\n",
+ this, stream.get(), stream->StreamID()));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ LOG3(("Http2Session::ReadSegments %p stream=%p stream send complete", this,
+ stream.get()));
+
+ // call readsegments again if there are other streams ready
+ // to go in this session
+ SetWriteCallbacks();
+
+ return rv;
+}
+
+nsresult Http2Session::ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count, uint32_t* countRead) {
+ bool again = false;
+ return ReadSegmentsAgain(reader, count, countRead, &again);
+}
+
+nsresult Http2Session::ReadyToProcessDataFrame(
+ enum internalStateType newState) {
+ MOZ_ASSERT(newState == PROCESSING_DATA_FRAME ||
+ newState == DISCARDING_DATA_FRAME_PADDING);
+ ChangeDownstreamState(newState);
+
+ Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, mInputFrameDataSize >> 10);
+ mLastDataReadEpoch = mLastReadEpoch;
+
+ if (!mInputFrameID) {
+ LOG3(("Http2Session::ReadyToProcessDataFrame %p data frame stream 0\n",
+ this));
+ return SessionError(PROTOCOL_ERROR);
+ }
+
+ nsresult rv = SetInputFrameDataStream(mInputFrameID);
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
+ "failed. probably due to verification.\n",
+ this, mInputFrameID));
+ return rv;
+ }
+ if (!mInputFrameDataStream) {
+ LOG3(
+ ("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
+ "failed. Next = 0x%X",
+ this, mInputFrameID, mNextStreamID));
+ if (mInputFrameID >= mNextStreamID) {
+ GenerateRstStream(PROTOCOL_ERROR, mInputFrameID);
+ }
+ ChangeDownstreamState(DISCARDING_DATA_FRAME);
+ } else if (mInputFrameDataStream->RecvdFin() ||
+ mInputFrameDataStream->RecvdReset() ||
+ mInputFrameDataStream->SentReset()) {
+ LOG3(
+ ("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
+ "Data arrived for already server closed stream.\n",
+ this, mInputFrameID));
+ if (mInputFrameDataStream->RecvdFin() ||
+ mInputFrameDataStream->RecvdReset()) {
+ GenerateRstStream(STREAM_CLOSED_ERROR, mInputFrameID);
+ }
+ ChangeDownstreamState(DISCARDING_DATA_FRAME);
+ } else if (mInputFrameDataSize == 0 && !mInputFrameFinal) {
+ // Only if non-final because the stream properly handles final frames of any
+ // size, and we want the stream to be able to notice its own end flag.
+ LOG3(
+ ("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
+ "Ignoring 0-length non-terminal data frame.",
+ this, mInputFrameID));
+ ChangeDownstreamState(DISCARDING_DATA_FRAME);
+ } else if (newState == PROCESSING_DATA_FRAME &&
+ !mInputFrameDataStream->AllHeadersReceived()) {
+ LOG3(
+ ("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
+ "Receiving data frame without having headers.",
+ this, mInputFrameID));
+ CleanupStream(mInputFrameDataStream, NS_ERROR_NET_HTTP2_SENT_GOAWAY,
+ PROTOCOL_ERROR);
+ return NS_OK;
+ }
+
+ LOG3(
+ ("Start Processing Data Frame. "
+ "Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d",
+ this, mInputFrameID, mInputFrameDataStream, mInputFrameFinal,
+ mInputFrameDataSize));
+ UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize);
+
+ if (mInputFrameDataStream) {
+ mInputFrameDataStream->SetRecvdData(true);
+ }
+
+ return NS_OK;
+}
+
+// WriteSegments() is used to read data off the socket. Generally this is
+// just the http2 frame header and from there the appropriate *Stream
+// is identified from the Stream-ID. The http transaction associated with
+// that read then pulls in the data directly, which it will feed to
+// OnWriteSegment(). That function will gateway it into http and feed
+// it to the appropriate transaction.
+
+// we call writer->OnWriteSegment via NetworkRead() to get a http2 header..
+// and decide if it is data or control.. if it is control, just deal with it.
+// if it is data, identify the stream
+// call stream->WriteSegments which can call this::OnWriteSegment to get the
+// data. It always gets full frames if they are part of the stream
+
+nsresult Http2Session::WriteSegmentsAgain(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten, bool* again) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG3(("Http2Session::WriteSegments %p InternalState %X\n", this,
+ mDownstreamState));
+
+ *countWritten = 0;
+
+ if (mClosed) {
+ LOG(("Http2Session::WriteSegments %p already closed", this));
+ // We return NS_ERROR_ABORT (a "soft" error) here, so when this error is
+ // propagated to another Http2Session, the Http2Session will not be closed
+ // due to this error code.
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv = ConfirmTLSProfile();
+ if (NS_FAILED(rv)) return rv;
+
+ SetWriteCallbacks();
+
+ // If there are http transactions attached to a push stream with filled
+ // buffers trigger that data pump here. This only reads from buffers (not the
+ // network) so mDownstreamState doesn't matter.
+ RefPtr<Http2StreamBase> pushConnectedStream =
+ GetNextStreamFromQueue(mPushesReadyForRead);
+ if (pushConnectedStream) {
+ return ProcessConnectedPush(pushConnectedStream, writer, count,
+ countWritten);
+ }
+
+ // feed gecko channels that previously stopped consuming data
+ // only take data from stored buffers
+ RefPtr<Http2StreamBase> slowConsumer =
+ GetNextStreamFromQueue(mSlowConsumersReadyForRead);
+ if (slowConsumer) {
+ internalStateType savedState = mDownstreamState;
+ mDownstreamState = NOT_USING_NETWORK;
+ rv = ProcessSlowConsumer(slowConsumer, writer, count, countWritten);
+ mDownstreamState = savedState;
+ return rv;
+ }
+
+ // The BUFFERING_OPENING_SETTINGS state is just like any
+ // BUFFERING_FRAME_HEADER except the only frame type it will allow is SETTINGS
+
+ // The session layer buffers the leading 8 byte header of every frame.
+ // Non-Data frames are then buffered for their full length, but data
+ // frames (type 0) are passed through to the http stack unprocessed
+
+ if (mDownstreamState == BUFFERING_OPENING_SETTINGS ||
+ mDownstreamState == BUFFERING_FRAME_HEADER) {
+ // The first 9 bytes of every frame is header information that
+ // we are going to want to strip before passing to http. That is
+ // true of both control and data packets.
+
+ MOZ_ASSERT(mInputFrameBufferUsed < kFrameHeaderBytes,
+ "Frame Buffer Used Too Large for State");
+
+ rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
+ kFrameHeaderBytes - mInputFrameBufferUsed, countWritten);
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p buffering frame header read failure %" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv)));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
+ return rv;
+ }
+
+ LogIO(this, nullptr, "Reading Frame Header",
+ &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);
+
+ mInputFrameBufferUsed += *countWritten;
+
+ if (mInputFrameBufferUsed < kFrameHeaderBytes) {
+ LOG3(
+ ("Http2Session::WriteSegments %p "
+ "BUFFERING FRAME HEADER incomplete size=%d",
+ this, mInputFrameBufferUsed));
+ return rv;
+ }
+
+ // 3 bytes of length, 1 type byte, 1 flag byte, 1 unused bit, 31 bits of ID
+ uint8_t totallyWastedByte = mInputFrameBuffer.get()[0];
+ mInputFrameDataSize =
+ NetworkEndian::readUint16(mInputFrameBuffer.get() + 1);
+ if (totallyWastedByte || (mInputFrameDataSize > kMaxFrameData)) {
+ LOG3(("Got frame too large 0x%02X%04X", totallyWastedByte,
+ mInputFrameDataSize));
+ return SessionError(PROTOCOL_ERROR);
+ }
+ mInputFrameType = *reinterpret_cast<uint8_t*>(mInputFrameBuffer.get() +
+ kFrameLengthBytes);
+ mInputFrameFlags = *reinterpret_cast<uint8_t*>(
+ mInputFrameBuffer.get() + kFrameLengthBytes + kFrameTypeBytes);
+ mInputFrameID =
+ NetworkEndian::readUint32(mInputFrameBuffer.get() + kFrameLengthBytes +
+ kFrameTypeBytes + kFrameFlagBytes);
+ mInputFrameID &= 0x7fffffff;
+ mInputFrameDataRead = 0;
+
+ if (mInputFrameType == FRAME_TYPE_DATA ||
+ mInputFrameType == FRAME_TYPE_HEADERS) {
+ mInputFrameFinal = mInputFrameFlags & kFlag_END_STREAM;
+ } else {
+ mInputFrameFinal = false;
+ }
+
+ mPaddingLength = 0;
+
+ LOG3(("Http2Session::WriteSegments[%p::%" PRIu64 "] Frame Header Read "
+ "type %X data len %u flags %x id 0x%X",
+ this, mSerial, mInputFrameType, mInputFrameDataSize, mInputFrameFlags,
+ mInputFrameID));
+
+ // if mExpectedHeaderID is non 0, it means this frame must be a CONTINUATION
+ // of a HEADERS frame with a matching ID (section 6.2)
+ if (mExpectedHeaderID && ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
+ (mExpectedHeaderID != mInputFrameID))) {
+ LOG3(
+ ("Expected CONINUATION OF HEADERS for ID 0x%X\n", mExpectedHeaderID));
+ return SessionError(PROTOCOL_ERROR);
+ }
+
+ // if mExpectedPushPromiseID is non 0, it means this frame must be a
+ // CONTINUATION of a PUSH_PROMISE with a matching ID (section 6.2)
+ if (mExpectedPushPromiseID &&
+ ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
+ (mExpectedPushPromiseID != mInputFrameID))) {
+ LOG3(("Expected CONTINUATION of PUSH PROMISE for ID 0x%X\n",
+ mExpectedPushPromiseID));
+ return SessionError(PROTOCOL_ERROR);
+ }
+
+ if (mDownstreamState == BUFFERING_OPENING_SETTINGS &&
+ mInputFrameType != FRAME_TYPE_SETTINGS) {
+ LOG3(("First Frame Type Must Be Settings\n"));
+ mPeerFailedHandshake = true;
+
+ // Don't allow any more h2 connections to this host
+ RefPtr<nsHttpConnectionInfo> ci = ConnectionInfo();
+ if (ci) {
+ gHttpHandler->ExcludeHttp2(ci);
+ }
+
+ // Go through and re-start all of our transactions with h2 disabled.
+ for (const auto& stream : mStreamTransactionHash.Values()) {
+ stream->DisableSpdy();
+ CloseStream(stream, NS_ERROR_NET_RESET);
+ }
+ mStreamTransactionHash.Clear();
+ return SessionError(PROTOCOL_ERROR);
+ }
+
+ if (mInputFrameType != FRAME_TYPE_DATA) { // control frame
+ EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + kFrameHeaderBytes,
+ kFrameHeaderBytes, mInputFrameBufferSize);
+ ChangeDownstreamState(BUFFERING_CONTROL_FRAME);
+ } else if (mInputFrameFlags & kFlag_PADDED) {
+ ChangeDownstreamState(PROCESSING_DATA_FRAME_PADDING_CONTROL);
+ } else {
+ rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ if (mDownstreamState == PROCESSING_DATA_FRAME_PADDING_CONTROL) {
+ MOZ_ASSERT(mInputFrameFlags & kFlag_PADDED,
+ "Processing padding control on unpadded frame");
+
+ MOZ_ASSERT(mInputFrameBufferUsed < (kFrameHeaderBytes + 1),
+ "Frame buffer used too large for state");
+
+ rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
+ (kFrameHeaderBytes + 1) - mInputFrameBufferUsed,
+ countWritten);
+
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2Session %p buffering data frame padding control read failure "
+ "%" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv)));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
+ return rv;
+ }
+
+ LogIO(this, nullptr, "Reading Data Frame Padding Control",
+ &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);
+
+ mInputFrameBufferUsed += *countWritten;
+
+ if (mInputFrameBufferUsed - kFrameHeaderBytes < 1) {
+ LOG3(
+ ("Http2Session::WriteSegments %p "
+ "BUFFERING DATA FRAME CONTROL PADDING incomplete size=%d",
+ this, mInputFrameBufferUsed - 8));
+ return rv;
+ }
+
+ ++mInputFrameDataRead;
+
+ char* control = &mInputFrameBuffer[kFrameHeaderBytes];
+ mPaddingLength = static_cast<uint8_t>(*control);
+
+ LOG3(("Http2Session::WriteSegments %p stream 0x%X mPaddingLength=%d", this,
+ mInputFrameID, mPaddingLength));
+
+ if (1U + mPaddingLength > mInputFrameDataSize) {
+ LOG3(
+ ("Http2Session::WriteSegments %p stream 0x%X padding too large for "
+ "frame",
+ this, mInputFrameID));
+ return SessionError(PROTOCOL_ERROR);
+ }
+ if (1U + mPaddingLength == mInputFrameDataSize) {
+ // This frame consists entirely of padding, we can just discard it
+ LOG3(
+ ("Http2Session::WriteSegments %p stream 0x%X frame with only padding",
+ this, mInputFrameID));
+ rv = ReadyToProcessDataFrame(DISCARDING_DATA_FRAME_PADDING);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ LOG3(
+ ("Http2Session::WriteSegments %p stream 0x%X ready to read HTTP data",
+ this, mInputFrameID));
+ rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) {
+ nsresult streamCleanupCode;
+
+ // There is no bounds checking on the error code.. we provide special
+ // handling for a couple of cases and all others (including unknown) are
+ // equivalent to cancel.
+ if (mDownstreamRstReason == REFUSED_STREAM_ERROR) {
+ streamCleanupCode = NS_ERROR_NET_RESET; // can retry this 100% safely
+ mInputFrameDataStream->ReuseConnectionOnRestartOK(true);
+ } else if (mDownstreamRstReason == HTTP_1_1_REQUIRED) {
+ streamCleanupCode = NS_ERROR_NET_RESET;
+ mInputFrameDataStream->ReuseConnectionOnRestartOK(true);
+ mInputFrameDataStream->DisableSpdy();
+ // actually allow restart by unsticking
+ mInputFrameDataStream->MakeNonSticky();
+ } else {
+ streamCleanupCode = mInputFrameDataStream->RecvdData()
+ ? NS_ERROR_NET_PARTIAL_TRANSFER
+ : NS_ERROR_NET_INTERRUPT;
+ }
+
+ if (mDownstreamRstReason == COMPRESSION_ERROR) {
+ mShouldGoAway = true;
+ }
+
+ // mInputFrameDataStream is reset by ChangeDownstreamState
+ Http2StreamBase* stream = mInputFrameDataStream;
+ ResetDownstreamState();
+ LOG3(
+ ("Http2Session::WriteSegments cleanup stream on recv of rst "
+ "session=%p stream=%p 0x%X\n",
+ this, stream, stream ? stream->StreamID() : 0));
+ CleanupStream(stream, streamCleanupCode, CANCEL_ERROR);
+ return NS_OK;
+ }
+
+ if (mDownstreamState == PROCESSING_DATA_FRAME ||
+ mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
+ // The cleanup stream should only be set while stream->WriteSegments is
+ // on the stack and then cleaned up in this code block afterwards.
+ MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly");
+ mNeedsCleanup = nullptr; /* just in case */
+
+ if (!mInputFrameDataStream) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ uint32_t streamID = mInputFrameDataStream->StreamID();
+ mSegmentWriter = writer;
+ rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
+ mSegmentWriter = nullptr;
+
+ mLastDataReadEpoch = mLastReadEpoch;
+
+ if (SoftStreamError(rv)) {
+ // This will happen when the transaction figures out it is EOF, generally
+ // due to a content-length match being made. Return OK from this function
+ // otherwise the whole session would be torn down.
+
+ // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state
+ // back to PROCESSING_DATA_FRAME where we came from
+ mDownstreamState = PROCESSING_DATA_FRAME;
+
+ if (mInputFrameDataRead == mInputFrameDataSize) ResetDownstreamState();
+ LOG3(
+ ("Http2Session::WriteSegments session=%p id 0x%X "
+ "needscleanup=%p. cleanup stream based on "
+ "stream->writeSegments returning code %" PRIx32 "\n",
+ this, streamID, mNeedsCleanup, static_cast<uint32_t>(rv)));
+ MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup->StreamID() == streamID);
+ CleanupStream(
+ streamID,
+ (rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED : NS_OK,
+ CANCEL_ERROR);
+ mNeedsCleanup = nullptr;
+ *again = false;
+ rv = ResumeRecv();
+ if (NS_FAILED(rv)) {
+ LOG3(("ResumeRecv returned code %x", static_cast<uint32_t>(rv)));
+ }
+ return NS_OK;
+ }
+
+ if (mNeedsCleanup) {
+ LOG3(
+ ("Http2Session::WriteSegments session=%p stream=%p 0x%X "
+ "cleanup stream based on mNeedsCleanup.\n",
+ this, mNeedsCleanup, mNeedsCleanup ? mNeedsCleanup->StreamID() : 0));
+ CleanupStream(mNeedsCleanup, NS_OK, CANCEL_ERROR);
+ mNeedsCleanup = nullptr;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p data frame read failure %" PRIx32 "\n", this,
+ static_cast<uint32_t>(rv)));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
+ }
+
+ return rv;
+ }
+
+ if (mDownstreamState == DISCARDING_DATA_FRAME ||
+ mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
+ char trash[4096];
+ uint32_t discardCount =
+ std::min(mInputFrameDataSize - mInputFrameDataRead, 4096U);
+ LOG3(("Http2Session::WriteSegments %p trying to discard %d bytes of %s",
+ this, discardCount,
+ mDownstreamState == DISCARDING_DATA_FRAME ? "data" : "padding"));
+
+ if (!discardCount && mDownstreamState == DISCARDING_DATA_FRAME) {
+ // Only do this short-cirtuit if we're not discarding a pure padding
+ // frame, as we need to potentially handle the stream FIN in those cases.
+ // See bug 1381016 comment 36 for more details.
+ ResetDownstreamState();
+ Unused << ResumeRecv();
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = NetworkRead(writer, trash, discardCount, countWritten);
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p discard frame read failure %" PRIx32 "\n", this,
+ static_cast<uint32_t>(rv)));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
+ return rv;
+ }
+
+ LogIO(this, nullptr, "Discarding Frame", trash, *countWritten);
+
+ mInputFrameDataRead += *countWritten;
+
+ if (mInputFrameDataRead == mInputFrameDataSize) {
+ Http2StreamBase* streamToCleanup = nullptr;
+ if (mInputFrameFinal) {
+ streamToCleanup = mInputFrameDataStream;
+ }
+
+ bool discardedPadding =
+ (mDownstreamState == DISCARDING_DATA_FRAME_PADDING);
+ ResetDownstreamState();
+
+ if (streamToCleanup) {
+ Http2PushedStream* pushed = streamToCleanup->GetHttp2PushedStream();
+ if (discardedPadding && pushed) {
+ // Pushed streams are special on padding-only final data frames.
+ // See bug 1409570 comments 6-8 for details.
+ pushed->SetPushComplete();
+ Http2StreamBase* pushSink = pushed->GetConsumerStream();
+ if (pushSink) {
+ bool enqueueSink = true;
+ for (const auto& s : mPushesReadyForRead) {
+ if (s == pushSink) {
+ enqueueSink = false;
+ break;
+ }
+ }
+ if (enqueueSink) {
+ AddStreamToQueue(pushSink, mPushesReadyForRead);
+ // No use trying to clean up, it won't do anything, anyway
+ streamToCleanup = nullptr;
+ }
+ }
+ }
+ CleanupStream(streamToCleanup, NS_OK, CANCEL_ERROR);
+ }
+ }
+ return rv;
+ }
+
+ if (mDownstreamState != BUFFERING_CONTROL_FRAME) {
+ MOZ_ASSERT(false); // this cannot happen
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mInputFrameBufferUsed == kFrameHeaderBytes,
+ "Frame Buffer Header Not Present");
+ MOZ_ASSERT(mInputFrameDataSize + kFrameHeaderBytes <= mInputFrameBufferSize,
+ "allocation for control frame insufficient");
+
+ rv = NetworkRead(writer,
+ &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead],
+ mInputFrameDataSize - mInputFrameDataRead, countWritten);
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p buffering control frame read failure %" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv)));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
+ return rv;
+ }
+
+ LogIO(this, nullptr, "Reading Control Frame",
+ &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead],
+ *countWritten);
+
+ mInputFrameDataRead += *countWritten;
+
+ if (mInputFrameDataRead != mInputFrameDataSize) return NS_OK;
+
+ MOZ_ASSERT(mInputFrameType != FRAME_TYPE_DATA);
+ if (mInputFrameType < FRAME_TYPE_LAST) {
+ rv = sControlFunctions[mInputFrameType](this);
+ } else {
+ // Section 4.1 requires this to be ignored; though protocol_error would
+ // be better
+ LOG3(("Http2Session %p unknown frame type %x ignored\n", this,
+ mInputFrameType));
+ ResetDownstreamState();
+ rv = NS_OK;
+ }
+
+ MOZ_ASSERT(NS_FAILED(rv) || mDownstreamState != BUFFERING_CONTROL_FRAME,
+ "Control Handler returned OK but did not change state");
+
+ if (mShouldGoAway && !mStreamTransactionHash.Count()) Close(NS_OK);
+ return rv;
+}
+
+nsresult Http2Session::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count, uint32_t* countWritten) {
+ bool again = false;
+ return WriteSegmentsAgain(writer, count, countWritten, &again);
+}
+
+nsresult Http2Session::Finish0RTT(bool aRestart, bool aAlpnChanged) {
+ MOZ_ASSERT(mAttemptingEarlyData);
+ LOG3(("Http2Session::Finish0RTT %p aRestart=%d aAlpnChanged=%d", this,
+ aRestart, aAlpnChanged));
+
+ for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
+ if (m0RTTStreams[i]) {
+ m0RTTStreams[i]->Finish0RTT(aRestart, aAlpnChanged);
+ }
+ }
+
+ if (aRestart) {
+ // 0RTT failed
+ if (aAlpnChanged) {
+ // This is a slightly more involved case - we need to get all our streams/
+ // transactions back in the queue so they can restart as http/1
+
+ // These must be set this way to ensure we gracefully restart all streams
+ mGoAwayID = 0;
+ mCleanShutdown = true;
+
+ // Close takes care of the rest of our work for us. The reason code here
+ // doesn't matter, as we aren't actually going to send a GOAWAY frame, but
+ // we use NS_ERROR_NET_RESET as it's closest to the truth.
+ Close(NS_ERROR_NET_RESET);
+ } else {
+ // This is the easy case - early data failed, but we're speaking h2, so
+ // we just need to rewind to the beginning of the preamble and try again.
+ mOutputQueueSent = 0;
+
+ for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
+ if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) {
+ TransactionHasDataToWrite(mCannotDo0RTTStreams[i]);
+ }
+ }
+ }
+ } else {
+ // 0RTT succeeded
+ for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
+ if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) {
+ TransactionHasDataToWrite(mCannotDo0RTTStreams[i]);
+ }
+ }
+ // Make sure we look for any incoming data in repsonse to our early data.
+ Unused << ResumeRecv();
+ }
+
+ mAttemptingEarlyData = false;
+ m0RTTStreams.Clear();
+ mCannotDo0RTTStreams.Clear();
+ RealignOutputQueue();
+
+ return NS_OK;
+}
+
+nsresult Http2Session::ProcessConnectedPush(
+ Http2StreamBase* pushConnectedStream, nsAHttpSegmentWriter* writer,
+ uint32_t count, uint32_t* countWritten) {
+ LOG3(("Http2Session::ProcessConnectedPush %p 0x%X\n", this,
+ pushConnectedStream->StreamID()));
+ mSegmentWriter = writer;
+ nsresult rv = pushConnectedStream->WriteSegments(this, count, countWritten);
+ mSegmentWriter = nullptr;
+
+ // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK
+ // so we need this check to determine the truth.
+ Http2Stream* h2Stream = pushConnectedStream->GetHttp2Stream();
+ MOZ_ASSERT(h2Stream);
+ if (NS_SUCCEEDED(rv) && !*countWritten && h2Stream &&
+ h2Stream->PushSource() && h2Stream->PushSource()->GetPushComplete()) {
+ rv = NS_BASE_STREAM_CLOSED;
+ }
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ CleanupStream(pushConnectedStream, NS_OK, CANCEL_ERROR);
+ rv = NS_OK;
+ }
+
+ // if we return OK to nsHttpConnection it will use mSocketInCondition
+ // to determine whether to schedule more reads, incorrectly
+ // assuming that nsHttpConnection::OnSocketWrite() was called.
+ if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ Unused << ResumeRecv();
+ }
+ return rv;
+}
+
+nsresult Http2Session::ProcessSlowConsumer(Http2StreamBase* slowConsumer,
+ nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ LOG3(("Http2Session::ProcessSlowConsumer %p 0x%X\n", this,
+ slowConsumer->StreamID()));
+ mSegmentWriter = writer;
+ nsresult rv = slowConsumer->WriteSegments(this, count, countWritten);
+ mSegmentWriter = nullptr;
+ LOG3(("Http2Session::ProcessSlowConsumer Writesegments %p 0x%X rv %" PRIX32
+ " %d\n",
+ this, slowConsumer->StreamID(), static_cast<uint32_t>(rv),
+ *countWritten));
+ if (NS_SUCCEEDED(rv) && !*countWritten && slowConsumer->RecvdFin()) {
+ rv = NS_BASE_STREAM_CLOSED;
+ }
+
+ if (NS_SUCCEEDED(rv) && (*countWritten > 0)) {
+ // There have been buffered bytes successfully fed into the
+ // formerly blocked consumer. Repeat until buffer empty or
+ // consumer is blocked again.
+ UpdateLocalRwin(slowConsumer, 0);
+ ConnectSlowConsumer(slowConsumer);
+ }
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ CleanupStream(slowConsumer, NS_OK, CANCEL_ERROR);
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+void Http2Session::UpdateLocalStreamWindow(Http2StreamBase* stream,
+ uint32_t bytes) {
+ if (!stream) { // this is ok - it means there was a data frame for a rst
+ // stream
+ return;
+ }
+
+ // If this data packet was not for a valid or live stream then there
+ // is no reason to mess with the flow control
+ if (!stream || stream->RecvdFin() || stream->RecvdReset() ||
+ mInputFrameFinal) {
+ return;
+ }
+
+ stream->DecrementClientReceiveWindow(bytes);
+
+ // Don't necessarily ack every data packet. Only do it
+ // after a significant amount of data.
+ uint64_t unacked = stream->LocalUnAcked();
+ int64_t localWindow = stream->ClientReceiveWindow();
+
+ LOG3(
+ ("Http2Session::UpdateLocalStreamWindow this=%p id=0x%X newbytes=%u "
+ "unacked=%" PRIu64 " localWindow=%" PRId64 "\n",
+ this, stream->StreamID(), bytes, unacked, localWindow));
+
+ if (!unacked) return;
+
+ if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold)) {
+ return;
+ }
+
+ if (!stream->HasSink()) {
+ LOG3(
+ ("Http2Session::UpdateLocalStreamWindow %p 0x%X Pushed Stream Has No "
+ "Sink\n",
+ this, stream->StreamID()));
+ return;
+ }
+
+ // Generate window updates directly out of session instead of the stream
+ // in order to avoid queue delays in getting the 'ACK' out.
+ uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU;
+
+ LOG3(
+ ("Http2Session::UpdateLocalStreamWindow Ack this=%p id=0x%X acksize=%d\n",
+ this, stream->StreamID(), toack));
+ stream->IncrementClientReceiveWindow(toack);
+ if (toack == 0) {
+ // Ensure we never send an illegal 0 window update
+ return;
+ }
+
+ // room for this packet needs to be ensured before calling this function
+ char* packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ mOutputQueueUsed += kFrameHeaderBytes + 4;
+ MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
+
+ CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, stream->StreamID());
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);
+
+ LogIO(this, stream, "Stream Window Update", packet, kFrameHeaderBytes + 4);
+ // dont flush here, this write can commonly be coalesced with a
+ // session window update to immediately follow.
+}
+
+void Http2Session::UpdateLocalSessionWindow(uint32_t bytes) {
+ if (!bytes) return;
+
+ mLocalSessionWindow -= bytes;
+
+ LOG3(
+ ("Http2Session::UpdateLocalSessionWindow this=%p newbytes=%u "
+ "localWindow=%" PRId64 "\n",
+ this, bytes, mLocalSessionWindow));
+
+ // Don't necessarily ack every data packet. Only do it
+ // after a significant amount of data.
+ if ((mLocalSessionWindow > (mInitialRwin - kMinimumToAck)) &&
+ (mLocalSessionWindow > kEmergencyWindowThreshold)) {
+ return;
+ }
+
+ // Only send max bits of window updates at a time.
+ uint64_t toack64 = mInitialRwin - mLocalSessionWindow;
+ uint32_t toack = (toack64 <= 0x7fffffffU) ? toack64 : 0x7fffffffU;
+
+ LOG3(("Http2Session::UpdateLocalSessionWindow Ack this=%p acksize=%u\n", this,
+ toack));
+ mLocalSessionWindow += toack;
+
+ if (toack == 0) {
+ // Ensure we never send an illegal 0 window update
+ return;
+ }
+
+ // room for this packet needs to be ensured before calling this function
+ char* packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ mOutputQueueUsed += kFrameHeaderBytes + 4;
+ MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
+
+ CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);
+
+ LogIO(this, nullptr, "Session Window Update", packet, kFrameHeaderBytes + 4);
+ // dont flush here, this write can commonly be coalesced with others
+}
+
+void Http2Session::UpdateLocalRwin(Http2StreamBase* stream, uint32_t bytes) {
+ // make sure there is room for 2 window updates even though
+ // we may not generate any.
+ EnsureOutputBuffer(2 * (kFrameHeaderBytes + 4));
+
+ UpdateLocalStreamWindow(stream, bytes);
+ UpdateLocalSessionWindow(bytes);
+ FlushOutputQueue();
+}
+
+void Http2Session::Close(nsresult aReason) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mClosed) return;
+
+ LOG3(("Http2Session::Close %p %" PRIX32, this,
+ static_cast<uint32_t>(aReason)));
+
+ mClosed = true;
+
+ Shutdown(aReason);
+
+ mStreamIDHash.Clear();
+ mStreamTransactionHash.Clear();
+ mTunnelStreams.Clear();
+ mProcessedWaitingWebsockets = true;
+
+ uint32_t goAwayReason;
+ if (mGoAwayReason != NO_HTTP_ERROR) {
+ goAwayReason = mGoAwayReason;
+ } else if (NS_SUCCEEDED(aReason)) {
+ goAwayReason = NO_HTTP_ERROR;
+ } else if (aReason == NS_ERROR_NET_HTTP2_SENT_GOAWAY) {
+ goAwayReason = PROTOCOL_ERROR;
+ } else if (mCleanShutdown) {
+ goAwayReason = NO_HTTP_ERROR;
+ } else {
+ goAwayReason = INTERNAL_ERROR;
+ }
+ if (!mAttemptingEarlyData) {
+ GenerateGoAway(goAwayReason);
+ }
+ mConnection = nullptr;
+ mSegmentReader = nullptr;
+ mSegmentWriter = nullptr;
+}
+
+nsHttpConnectionInfo* Http2Session::ConnectionInfo() {
+ RefPtr<nsHttpConnectionInfo> ci;
+ GetConnectionInfo(getter_AddRefs(ci));
+ return ci.get();
+}
+
+void Http2Session::CloseTransaction(nsAHttpTransaction* aTransaction,
+ nsresult aResult) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32, this, aTransaction,
+ static_cast<uint32_t>(aResult)));
+
+ // Generally this arrives as a cancel event from the connection manager.
+
+ // need to find the stream and call CleanupStream() on it.
+ RefPtr<Http2StreamBase> stream = mStreamTransactionHash.Get(aTransaction);
+ if (!stream) {
+ LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32 " - not found.", this,
+ aTransaction, static_cast<uint32_t>(aResult)));
+ return;
+ }
+ LOG3(
+ ("Http2Session::CloseTransaction probably a cancel. "
+ "this=%p, trans=%p, result=%" PRIx32 ", streamID=0x%X stream=%p",
+ this, aTransaction, static_cast<uint32_t>(aResult), stream->StreamID(),
+ stream.get()));
+ CleanupStream(stream, aResult, CANCEL_ERROR);
+ nsresult rv = ResumeRecv();
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::CloseTransaction %p %p %x ResumeRecv returned %x",
+ this, aTransaction, static_cast<uint32_t>(aResult),
+ static_cast<uint32_t>(rv)));
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+nsresult Http2Session::OnReadSegment(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsresult rv;
+
+ // If we can release old queued data then we can try and write the new
+ // data directly to the network without using the output queue at all
+ if (mOutputQueueUsed) FlushOutputQueue();
+
+ if (!mOutputQueueUsed && mSegmentReader) {
+ // try and write directly without output queue
+ rv = mSegmentReader->OnReadSegment(buf, count, countRead);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ *countRead = 0;
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (*countRead < count) {
+ uint32_t required = count - *countRead;
+ // assuming a commitment() happened, this ensurebuffer is a nop
+ // but just in case the queuesize is too small for the required data
+ // call ensurebuffer().
+ EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize);
+ memcpy(mOutputQueueBuffer.get(), buf + *countRead, required);
+ mOutputQueueUsed = required;
+ }
+
+ *countRead = count;
+ return NS_OK;
+ }
+
+ // At this point we are going to buffer the new data in the output
+ // queue if it fits. By coalescing multiple small submissions into one larger
+ // buffer we can get larger writes out to the network later on.
+
+ // This routine should not be allowed to fill up the output queue
+ // all on its own - at least kQueueReserved bytes are always left
+ // for other routines to use - but this is an all-or-nothing function,
+ // so if it will not all fit just return WOULD_BLOCK
+
+ if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved)) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count);
+ mOutputQueueUsed += count;
+ *countRead = count;
+
+ FlushOutputQueue();
+
+ return NS_OK;
+}
+
+nsresult Http2Session::CommitToSegmentSize(uint32_t count,
+ bool forceCommitment) {
+ if (mOutputQueueUsed && !mAttemptingEarlyData) FlushOutputQueue();
+
+ // would there be enough room to buffer this if needed?
+ if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved)) {
+ return NS_OK;
+ }
+
+ // if we are using part of our buffers already, try again later unless
+ // forceCommitment is set.
+ if (mOutputQueueUsed && !forceCommitment) return NS_BASE_STREAM_WOULD_BLOCK;
+
+ if (mOutputQueueUsed) {
+ // normally we avoid the memmove of RealignOutputQueue, but we'll try
+ // it if forceCommitment is set before growing the buffer.
+ RealignOutputQueue();
+
+ // is there enough room now?
+ if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved)) {
+ return NS_OK;
+ }
+ }
+
+ // resize the buffers as needed
+ EnsureOutputBuffer(count + kQueueReserved);
+
+ MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved),
+ "buffer not as large as expected");
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+nsresult Http2Session::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsresult rv;
+
+ if (!mSegmentWriter) {
+ // the only way this could happen would be if Close() were called on the
+ // stack with WriteSegments()
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mDownstreamState == NOT_USING_NETWORK ||
+ mDownstreamState == BUFFERING_FRAME_HEADER ||
+ mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ if (mDownstreamState == PROCESSING_DATA_FRAME) {
+ if (mInputFrameFinal && mInputFrameDataRead == mInputFrameDataSize) {
+ *countWritten = 0;
+ SetNeedsCleanup();
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ count = std::min(count, mInputFrameDataSize - mInputFrameDataRead);
+ rv = NetworkRead(mSegmentWriter, buf, count, countWritten);
+ if (NS_FAILED(rv)) return rv;
+
+ LogIO(this, mInputFrameDataStream, "Reading Data Frame", buf,
+ *countWritten);
+
+ mInputFrameDataRead += *countWritten;
+ if (mPaddingLength &&
+ (mInputFrameDataSize - mInputFrameDataRead <= mPaddingLength)) {
+ // We are crossing from real HTTP data into the realm of padding. If
+ // we've actually crossed the line, we need to munge countWritten for the
+ // sake of goodness and sanity. No matter what, any future calls to
+ // WriteSegments need to just discard data until we reach the end of this
+ // frame.
+ if (mInputFrameDataSize != mInputFrameDataRead) {
+ // Only change state if we still have padding to read. If we don't do
+ // this, we can end up hanging on frames that combine real data,
+ // padding, and END_STREAM (see bug 1019921)
+ ChangeDownstreamState(DISCARDING_DATA_FRAME_PADDING);
+ }
+ uint32_t paddingRead =
+ mPaddingLength - (mInputFrameDataSize - mInputFrameDataRead);
+ LOG3(
+ ("Http2Session::OnWriteSegment %p stream 0x%X len=%d read=%d "
+ "crossed from HTTP data into padding (%d of %d) countWritten=%d",
+ this, mInputFrameID, mInputFrameDataSize, mInputFrameDataRead,
+ paddingRead, mPaddingLength, *countWritten));
+ *countWritten -= paddingRead;
+ LOG3(("Http2Session::OnWriteSegment %p stream 0x%X new countWritten=%d",
+ this, mInputFrameID, *countWritten));
+ }
+
+ mInputFrameDataStream->UpdateTransportReadEvents(*countWritten);
+ if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameFinal) {
+ ResetDownstreamState();
+ }
+
+ return rv;
+ }
+
+ if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
+ if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
+ mInputFrameFinal) {
+ *countWritten = 0;
+ SetNeedsCleanup();
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ count = std::min<uint32_t>(
+ count, mFlatHTTPResponseHeaders.Length() - mFlatHTTPResponseHeadersOut);
+ memcpy(buf, mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut,
+ count);
+ mFlatHTTPResponseHeadersOut += count;
+ *countWritten = count;
+
+ if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) {
+ // Since mInputFrameFinal can be reset, we need to also check RecvdFin to
+ // see if a stream doesn’t expect more frames.
+ if (!mInputFrameFinal && !mInputFrameDataStream->RecvdFin()) {
+ // If more frames are expected in this stream, then reset the state so
+ // they can be handled. Otherwise (e.g. a 0 length response with the fin
+ // on the incoming headers) stay in PROCESSING_COMPLETE_HEADERS state so
+ // the SetNeedsCleanup() code above can cleanup the stream.
+ ResetDownstreamState();
+ }
+ }
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(false);
+ return NS_ERROR_UNEXPECTED;
+}
+
+void Http2Session::SetNeedsCleanup() {
+ LOG3(
+ ("Http2Session::SetNeedsCleanup %p - recorded downstream fin of "
+ "stream %p 0x%X",
+ this, mInputFrameDataStream, mInputFrameDataStream->StreamID()));
+
+ // This will result in Close() being called
+ MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set");
+ mInputFrameDataStream->SetResponseIsComplete();
+ mNeedsCleanup = mInputFrameDataStream;
+ ResetDownstreamState();
+}
+
+void Http2Session::ConnectPushedStream(Http2StreamBase* stream) {
+ AddStreamToQueue(stream, mPushesReadyForRead);
+ Unused << ForceRecv();
+}
+
+void Http2Session::ConnectSlowConsumer(Http2StreamBase* stream) {
+ LOG3(("Http2Session::ConnectSlowConsumer %p 0x%X\n", this,
+ stream->StreamID()));
+ AddStreamToQueue(stream, mSlowConsumersReadyForRead);
+ Unused << ForceRecv();
+}
+
+nsresult Http2Session::BufferOutput(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ RefPtr<nsAHttpSegmentReader> old;
+ mSegmentReader.swap(old); // Make mSegmentReader null
+ nsresult rv = OnReadSegment(buf, count, countRead);
+ mSegmentReader.swap(old); // Restore the old mSegmentReader
+ return rv;
+}
+
+bool // static
+Http2Session::ALPNCallback(nsITLSSocketControl* tlsSocketControl) {
+ LOG3(("Http2Session::ALPNCallback sslsocketcontrol=%p\n", tlsSocketControl));
+ if (tlsSocketControl) {
+ int16_t version = tlsSocketControl->GetSSLVersionOffered();
+ LOG3(("Http2Session::ALPNCallback version=%x\n", version));
+
+ if (version == nsITLSSocketControl::TLS_VERSION_1_2 &&
+ !gHttpHandler->IsH2MandatorySuiteEnabled()) {
+ LOG3(("Http2Session::ALPNCallback Mandatory Cipher Suite Unavailable\n"));
+ return false;
+ }
+
+ if (version >= nsITLSSocketControl::TLS_VERSION_1_2) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsresult Http2Session::ConfirmTLSProfile() {
+ if (mTLSProfileConfirmed) {
+ return NS_OK;
+ }
+
+ LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n", this,
+ mConnection.get()));
+
+ if (mAttemptingEarlyData) {
+ LOG3(
+ ("Http2Session::ConfirmTLSProfile %p temporarily passing due to early "
+ "data\n",
+ this));
+ return NS_OK;
+ }
+
+ if (!StaticPrefs::network_http_http2_enforce_tls_profile()) {
+ LOG3(
+ ("Http2Session::ConfirmTLSProfile %p passed due to configuration "
+ "bypass\n",
+ this));
+ mTLSProfileConfirmed = true;
+ return NS_OK;
+ }
+
+ if (!mConnection) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsITLSSocketControl> ssl;
+ mConnection->GetTLSSocketControl(getter_AddRefs(ssl));
+ LOG3(("Http2Session::ConfirmTLSProfile %p sslsocketcontrol=%p\n", this,
+ ssl.get()));
+ if (!ssl) return NS_ERROR_FAILURE;
+
+ int16_t version = ssl->GetSSLVersionUsed();
+ LOG3(("Http2Session::ConfirmTLSProfile %p version=%x\n", this, version));
+ if (version < nsITLSSocketControl::TLS_VERSION_1_2) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of TLS1.2\n",
+ this));
+ return SessionError(INADEQUATE_SECURITY);
+ }
+
+ uint16_t kea = ssl->GetKEAUsed();
+ if (kea == ssl_kea_ecdh_hybrid && !StaticPrefs::security_tls_enable_kyber()) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to disabled KEA %d\n",
+ this, kea));
+ return SessionError(INADEQUATE_SECURITY);
+ }
+
+ if (kea != ssl_kea_dh && kea != ssl_kea_ecdh && kea != ssl_kea_ecdh_hybrid) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to invalid KEA %d\n",
+ this, kea));
+ return SessionError(INADEQUATE_SECURITY);
+ }
+
+ uint32_t keybits = ssl->GetKEAKeyBits();
+ if (kea == ssl_kea_dh && keybits < 2048) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to DH %d < 2048\n",
+ this, keybits));
+ return SessionError(INADEQUATE_SECURITY);
+ }
+ if (kea == ssl_kea_ecdh && keybits < 224) { // see rfc7540 9.2.1.
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to ECDH %d < 224\n",
+ this, keybits));
+ return SessionError(INADEQUATE_SECURITY);
+ }
+
+ int16_t macAlgorithm = ssl->GetMACAlgorithmUsed();
+ LOG3(("Http2Session::ConfirmTLSProfile %p MAC Algortihm (aead==6) %d\n", this,
+ macAlgorithm));
+ if (macAlgorithm != nsITLSSocketControl::SSL_MAC_AEAD) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of AEAD\n",
+ this));
+ return SessionError(INADEQUATE_SECURITY);
+ }
+
+ /* We are required to send SNI. We do that already, so no check is done
+ * here to make sure we did. */
+
+ /* We really should check to ensure TLS compression isn't enabled on
+ * this connection. However, we never enable TLS compression on our end,
+ * anyway, so it'll never be on. All the same, see https://bugzil.la/965881
+ * for the possibility for an interface to ensure it never gets turned on. */
+
+ mTLSProfileConfirmed = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Modified methods of nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+void Http2Session::TransactionHasDataToWrite(nsAHttpTransaction* caller) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::TransactionHasDataToWrite %p trans=%p", this, caller));
+
+ // a trapped signal from the http transaction to the connection that
+ // it is no longer blocked on read.
+
+ RefPtr<Http2StreamBase> stream = mStreamTransactionHash.Get(caller);
+ if (!stream || !VerifyStream(stream)) {
+ LOG3(("Http2Session::TransactionHasDataToWrite %p caller %p not found",
+ this, caller));
+ return;
+ }
+
+ LOG3(("Http2Session::TransactionHasDataToWrite %p ID is 0x%X\n", this,
+ stream->StreamID()));
+
+ if (!mClosed) {
+ AddStreamToQueue(stream, mReadyForWrite);
+ SetWriteCallbacks();
+ } else {
+ LOG3(
+ ("Http2Session::TransactionHasDataToWrite %p closed so not setting "
+ "Ready4Write\n",
+ this));
+ }
+
+ // NSPR poll will not poll the network if there are non system PR_FileDesc's
+ // that are ready - so we can get into a deadlock waiting for the system IO
+ // to come back here if we don't force the send loop manually.
+ Unused << ForceSend();
+}
+
+void Http2Session::TransactionHasDataToRecv(nsAHttpTransaction* caller) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::TransactionHasDataToRecv %p trans=%p", this, caller));
+
+ // a signal from the http transaction to the connection that it will consume
+ // more
+ RefPtr<Http2StreamBase> stream = mStreamTransactionHash.Get(caller);
+ if (!stream || !VerifyStream(stream)) {
+ LOG3(("Http2Session::TransactionHasDataToRecv %p caller %p not found", this,
+ caller));
+ return;
+ }
+
+ LOG3(("Http2Session::TransactionHasDataToRecv %p ID is 0x%X\n", this,
+ stream->StreamID()));
+ TransactionHasDataToRecv(stream);
+}
+
+void Http2Session::TransactionHasDataToWrite(Http2StreamBase* stream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x", this,
+ stream, stream->StreamID()));
+
+ AddStreamToQueue(stream, mReadyForWrite);
+ SetWriteCallbacks();
+ Unused << ForceSend();
+}
+
+void Http2Session::TransactionHasDataToRecv(Http2StreamBase* caller) {
+ ConnectSlowConsumer(caller);
+}
+
+bool Http2Session::IsPersistent() { return true; }
+
+nsresult Http2Session::TakeTransport(nsISocketTransport**,
+ nsIAsyncInputStream**,
+ nsIAsyncOutputStream**) {
+ MOZ_ASSERT(false, "TakeTransport of Http2Session");
+ return NS_ERROR_UNEXPECTED;
+}
+
+Http3WebTransportSession* Http2Session::GetWebTransportSession(
+ nsAHttpTransaction* aTransaction) {
+ MOZ_ASSERT(false, "GetWebTransportSession of Http2Session");
+ return nullptr;
+}
+
+already_AddRefed<HttpConnectionBase> Http2Session::TakeHttpConnection() {
+ MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
+ return nullptr;
+}
+
+already_AddRefed<HttpConnectionBase> Http2Session::HttpConnection() {
+ if (mConnection) {
+ return mConnection->HttpConnection();
+ }
+ return nullptr;
+}
+
+void Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor** aOut) {
+ *aOut = nullptr;
+}
+
+void Http2Session::SetConnection(nsAHttpConnection* aConn) {
+ mConnection = aConn;
+}
+
+//-----------------------------------------------------------------------------
+// unused methods of nsAHttpTransaction
+// We can be sure of this because Http2Session is only constructed in
+// nsHttpConnection and is never passed out of that object or a
+// TLSFilterTransaction TLS tunnel
+//-----------------------------------------------------------------------------
+
+void Http2Session::SetProxyConnectFailed() {
+ MOZ_ASSERT(false, "Http2Session::SetProxyConnectFailed()");
+}
+
+bool Http2Session::IsDone() { return !mStreamTransactionHash.Count(); }
+
+nsresult Http2Session::Status() {
+ MOZ_ASSERT(false, "Http2Session::Status()");
+ return NS_ERROR_UNEXPECTED;
+}
+
+uint32_t Http2Session::Caps() {
+ MOZ_ASSERT(false, "Http2Session::Caps()");
+ return 0;
+}
+
+nsHttpRequestHead* Http2Session::RequestHead() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(false,
+ "Http2Session::RequestHead() "
+ "should not be called after http/2 is setup");
+ return nullptr;
+}
+
+uint32_t Http2Session::Http1xTransactionCount() { return 0; }
+
+nsresult Http2Session::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // Generally this cannot be done with http/2 as transactions are
+ // started right away.
+
+ LOG3(("Http2Session::TakeSubTransactions %p\n", this));
+
+ if (mConcurrentHighWater > 0) return NS_ERROR_ALREADY_OPENED;
+
+ LOG3((" taking %d\n", mStreamTransactionHash.Count()));
+
+ for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
+ outTransactions.AppendElement(iter.Key());
+
+ // Removing the stream from the hash will delete the stream and drop the
+ // transaction reference the hash held.
+ iter.Remove();
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Pass through methods of nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+nsAHttpConnection* Http2Session::Connection() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return mConnection;
+}
+
+nsresult Http2Session::OnHeadersAvailable(nsAHttpTransaction* transaction,
+ nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ bool* reset) {
+ return NS_OK;
+}
+
+bool Http2Session::IsReused() {
+ if (!mConnection) {
+ return false;
+ }
+
+ return mConnection->IsReused();
+}
+
+nsresult Http2Session::PushBack(const char* buf, uint32_t len) {
+ return mConnection->PushBack(buf, len);
+}
+
+void Http2Session::SendPing() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http2Session::SendPing %p mPreviousUsed=%d", this, mPreviousUsed));
+
+ if (mPreviousUsed) {
+ // alredy in progress, get out
+ return;
+ }
+
+ mPingSentEpoch = PR_IntervalNow();
+ if (!mPingSentEpoch) {
+ mPingSentEpoch = 1; // avoid the 0 sentinel value
+ }
+ if (!mPingThreshold ||
+ (mPingThreshold > gHttpHandler->NetworkChangedTimeout())) {
+ mPreviousPingThreshold = mPingThreshold;
+ mPreviousUsed = true;
+ mPingThreshold = gHttpHandler->NetworkChangedTimeout();
+ // Reset mLastReadEpoch, so we can really check when do we got pong from the
+ // server.
+ mLastReadEpoch = 0;
+ }
+ GeneratePing(false);
+ Unused << ResumeRecv();
+}
+
+bool Http2Session::TestOriginFrame(const nsACString& hostname, int32_t port) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mOriginFrameActivated);
+
+ nsAutoCString key(hostname);
+ key.Append(':');
+ key.AppendInt(port);
+ bool rv = mOriginFrame.Get(key);
+ LOG3(("TestOriginFrame() hash.get %p %s %d\n", this, key.get(), rv));
+ if (!rv && ConnectionInfo()) {
+ // the SNI is also implicitly in this list, so consult that too
+ nsHttpConnectionInfo* ci = ConnectionInfo();
+ rv = nsCString(hostname).EqualsIgnoreCase(ci->Origin()) &&
+ (port == ci->OriginPort());
+ LOG3(("TestOriginFrame() %p sni test %d\n", this, rv));
+ }
+ return rv;
+}
+
+bool Http2Session::TestJoinConnection(const nsACString& hostname,
+ int32_t port) {
+ return RealJoinConnection(hostname, port, true);
+}
+
+bool Http2Session::JoinConnection(const nsACString& hostname, int32_t port) {
+ return RealJoinConnection(hostname, port, false);
+}
+
+bool Http2Session::RealJoinConnection(const nsACString& hostname, int32_t port,
+ bool justKidding) {
+ if (!mConnection || mClosed || mShouldGoAway) {
+ return false;
+ }
+
+ nsHttpConnectionInfo* ci = ConnectionInfo();
+ if (nsCString(hostname).EqualsIgnoreCase(ci->Origin()) &&
+ (port == ci->OriginPort())) {
+ return true;
+ }
+
+ if (!mReceivedSettings) {
+ return false;
+ }
+
+ if (mOriginFrameActivated) {
+ bool originFrameResult = TestOriginFrame(hostname, port);
+ if (!originFrameResult) {
+ return false;
+ }
+ } else {
+ LOG3(("JoinConnection %p no origin frame check used.\n", this));
+ }
+
+ nsAutoCString key(hostname);
+ key.Append(':');
+ key.Append(justKidding ? 'k' : '.');
+ key.AppendInt(port);
+ bool cachedResult;
+ if (mJoinConnectionCache.Get(key, &cachedResult)) {
+ LOG(("joinconnection [%p %s] %s result=%d cache\n", this,
+ ConnectionInfo()->HashKey().get(), key.get(), cachedResult));
+ return cachedResult;
+ }
+
+ nsresult rv;
+ bool isJoined = false;
+
+ nsCOMPtr<nsITLSSocketControl> sslSocketControl;
+ mConnection->GetTLSSocketControl(getter_AddRefs(sslSocketControl));
+ if (!sslSocketControl) {
+ return false;
+ }
+
+ // try all the coalescable versions we support.
+ const SpdyInformation* info = gHttpHandler->SpdyInfo();
+ bool joinedReturn = false;
+ if (StaticPrefs::network_http_http2_enabled()) {
+ if (justKidding) {
+ rv = sslSocketControl->TestJoinConnection(info->VersionString, hostname,
+ port, &isJoined);
+ } else {
+ rv = sslSocketControl->JoinConnection(info->VersionString, hostname, port,
+ &isJoined);
+ }
+ if (NS_SUCCEEDED(rv) && isJoined) {
+ joinedReturn = true;
+ }
+ }
+
+ LOG(("joinconnection [%p %s] %s result=%d lookup\n", this,
+ ConnectionInfo()->HashKey().get(), key.get(), joinedReturn));
+ mJoinConnectionCache.InsertOrUpdate(key, joinedReturn);
+ if (!justKidding) {
+ // cache a kidding entry too as this one is good for both
+ nsAutoCString key2(hostname);
+ key2.Append(':');
+ key2.Append('k');
+ key2.AppendInt(port);
+ if (!mJoinConnectionCache.Get(key2)) {
+ mJoinConnectionCache.InsertOrUpdate(key2, joinedReturn);
+ }
+ }
+ return joinedReturn;
+}
+
+void Http2Session::CurrentBrowserIdChanged(uint64_t id) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mCurrentBrowserId = id;
+
+ for (const auto& stream : mStreamTransactionHash.Values()) {
+ stream->CurrentBrowserIdChanged(id);
+ }
+}
+
+void Http2Session::SetCleanShutdown(bool aCleanShutdown) {
+ mCleanShutdown = aCleanShutdown;
+}
+
+WebSocketSupport Http2Session::GetWebSocketSupport() {
+ LOG3(("Http2Session::GetWebSocketSupport %p enable=%d allow=%d processed=%d",
+ this, mEnableWebsockets, mPeerAllowsWebsockets,
+ mProcessedWaitingWebsockets));
+
+ if (!mEnableWebsockets) {
+ return WebSocketSupport::NO_SUPPORT;
+ }
+
+ if (mPeerAllowsWebsockets) {
+ return WebSocketSupport::SUPPORTED;
+ }
+
+ if (!mProcessedWaitingWebsockets) {
+ mHasTransactionWaitingForWebsockets = true;
+ return WebSocketSupport::UNSURE;
+ }
+
+ return WebSocketSupport::NO_SUPPORT;
+}
+
+PRIntervalTime Http2Session::LastWriteTime() {
+ return mConnection->LastWriteTime();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Session.h b/netwerk/protocol/http/Http2Session.h
new file mode 100644
index 0000000000..767e5cf9f7
--- /dev/null
+++ b/netwerk/protocol/http/Http2Session.h
@@ -0,0 +1,626 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http2Session_h
+#define mozilla_net_Http2Session_h
+
+// HTTP/2 - RFC 7540
+// https://www.rfc-editor.org/rfc/rfc7540.txt
+
+#include "ASpdySession.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "nsAHttpConnection.h"
+#include "nsCOMArray.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTHashMap.h"
+#include "nsDeque.h"
+#include "nsHashKeys.h"
+#include "nsHttpRequestHead.h"
+#include "nsICacheEntryOpenCallback.h"
+
+#include "Http2Compression.h"
+
+class nsISocketTransport;
+
+namespace mozilla {
+namespace net {
+
+class Http2PushedStream;
+class Http2StreamBase;
+class Http2StreamTunnel;
+class nsHttpTransaction;
+class nsHttpConnection;
+
+enum Http2StreamBaseType { Normal, WebSocket, Tunnel, ServerPush };
+
+// b23b147c-c4f8-4d6e-841a-09f29a010de7
+#define NS_HTTP2SESSION_IID \
+ { \
+ 0xb23b147c, 0xc4f8, 0x4d6e, { \
+ 0x84, 0x1a, 0x09, 0xf2, 0x9a, 0x01, 0x0d, 0xe7 \
+ } \
+ }
+
+class Http2Session final : public ASpdySession,
+ public nsAHttpConnection,
+ public nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter {
+ ~Http2Session();
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTP2SESSION_IID)
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_NSAHTTPCONNECTION(mConnection)
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+
+ static Http2Session* CreateSession(nsISocketTransport*,
+ enum SpdyVersion version,
+ bool attemptingEarlyData);
+
+ [[nodiscard]] bool AddStream(nsAHttpTransaction*, int32_t,
+ nsIInterfaceRequestor*) override;
+ bool CanReuse() override { return !mShouldGoAway && !mClosed; }
+ bool RoomForMoreStreams() override;
+ enum SpdyVersion SpdyVersion() override;
+ bool TestJoinConnection(const nsACString& hostname, int32_t port) override;
+ bool JoinConnection(const nsACString& hostname, int32_t port) override;
+
+ // When the connection is active this is called up to once every 1 second
+ // return the interval (in seconds) that the connection next wants to
+ // have this invoked. It might happen sooner depending on the needs of
+ // other connections.
+ uint32_t ReadTimeoutTick(PRIntervalTime now) override;
+
+ // Idle time represents time since "goodput".. e.g. a data or header frame
+ PRIntervalTime IdleTime() override;
+
+ // Registering with a newID of 0 means pick the next available odd ID
+ uint32_t RegisterStreamID(Http2StreamBase*, uint32_t aNewID = 0);
+
+ /*
+ HTTP/2 framing
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Length (16) | Type (8) | Flags (8) |
+ +-+-------------+---------------+-------------------------------+
+ |R| Stream Identifier (31) |
+ +-+-------------------------------------------------------------+
+ | Frame Data (0...) ...
+ +---------------------------------------------------------------+
+ */
+
+ enum FrameType {
+ FRAME_TYPE_DATA = 0x0,
+ FRAME_TYPE_HEADERS = 0x1,
+ FRAME_TYPE_PRIORITY = 0x2,
+ FRAME_TYPE_RST_STREAM = 0x3,
+ FRAME_TYPE_SETTINGS = 0x4,
+ FRAME_TYPE_PUSH_PROMISE = 0x5,
+ FRAME_TYPE_PING = 0x6,
+ FRAME_TYPE_GOAWAY = 0x7,
+ FRAME_TYPE_WINDOW_UPDATE = 0x8,
+ FRAME_TYPE_CONTINUATION = 0x9,
+ FRAME_TYPE_ALTSVC = 0xA,
+ FRAME_TYPE_UNUSED = 0xB,
+ FRAME_TYPE_ORIGIN = 0xC,
+ FRAME_TYPE_LAST = 0xD
+ };
+
+ // NO_ERROR is a macro defined on windows, so we'll name the HTTP2 goaway
+ // code NO_ERROR to be NO_HTTP_ERROR
+ enum errorType {
+ NO_HTTP_ERROR = 0,
+ PROTOCOL_ERROR = 1,
+ INTERNAL_ERROR = 2,
+ FLOW_CONTROL_ERROR = 3,
+ SETTINGS_TIMEOUT_ERROR = 4,
+ STREAM_CLOSED_ERROR = 5,
+ FRAME_SIZE_ERROR = 6,
+ REFUSED_STREAM_ERROR = 7,
+ CANCEL_ERROR = 8,
+ COMPRESSION_ERROR = 9,
+ CONNECT_ERROR = 10,
+ ENHANCE_YOUR_CALM = 11,
+ INADEQUATE_SECURITY = 12,
+ HTTP_1_1_REQUIRED = 13,
+ UNASSIGNED = 31
+ };
+
+ // These are frame flags. If they, or other undefined flags, are
+ // used on frames other than the comments indicate they MUST be ignored.
+ const static uint8_t kFlag_END_STREAM = 0x01; // data, headers
+ const static uint8_t kFlag_END_HEADERS = 0x04; // headers, continuation
+ const static uint8_t kFlag_END_PUSH_PROMISE = 0x04; // push promise
+ const static uint8_t kFlag_ACK = 0x01; // ping and settings
+ const static uint8_t kFlag_PADDED =
+ 0x08; // data, headers, push promise, continuation
+ const static uint8_t kFlag_PRIORITY = 0x20; // headers
+
+ enum {
+ SETTINGS_TYPE_HEADER_TABLE_SIZE = 1, // compression table size
+ SETTINGS_TYPE_ENABLE_PUSH = 2, // can be used to disable push
+ SETTINGS_TYPE_MAX_CONCURRENT = 3, // streams recvr allowed to initiate
+ SETTINGS_TYPE_INITIAL_WINDOW = 4, // bytes for flow control default
+ SETTINGS_TYPE_MAX_FRAME_SIZE =
+ 5, // max frame size settings sender allows receipt of
+ // 6 is SETTINGS_TYPE_MAX_HEADER_LIST - advisory, we ignore it
+ // 7 is unassigned
+ SETTINGS_TYPE_ENABLE_CONNECT_PROTOCOL =
+ 8 // if sender implements extended CONNECT for websockets
+ };
+
+ // This should be big enough to hold all of your control packets,
+ // but if it needs to grow for huge headers it can do so dynamically.
+ const static uint32_t kDefaultBufferSize = 2048;
+
+ // kDefaultQueueSize must be >= other queue size constants
+ const static uint32_t kDefaultQueueSize = 32768;
+ const static uint32_t kQueueMinimumCleanup = 24576;
+ const static uint32_t kQueueTailRoom = 4096;
+ const static uint32_t kQueueReserved = 1024;
+
+ const static uint32_t kMaxStreamID = 0x7800000;
+
+ // This is a sentinel for a deleted stream. It is not a valid
+ // 31 bit stream ID.
+ const static uint32_t kDeadStreamID = 0xffffdead;
+
+ // below the emergency threshold of local window we ack every received
+ // byte. Above that we coalesce bytes into the MinimumToAck size.
+ const static int32_t kEmergencyWindowThreshold = 96 * 1024;
+ const static uint32_t kMinimumToAck = 4 * 1024 * 1024;
+
+ // The default rwin is 64KB - 1 unless updated by a settings frame
+ const static uint32_t kDefaultRwin = 65535;
+
+ // We limit frames to 2^14 bytes of length in order to preserve responsiveness
+ // This is the smallest allowed value for SETTINGS_MAX_FRAME_SIZE
+ const static uint32_t kMaxFrameData = 0x4000;
+
+ const static uint8_t kFrameLengthBytes = 3;
+ const static uint8_t kFrameStreamIDBytes = 4;
+ const static uint8_t kFrameFlagBytes = 1;
+ const static uint8_t kFrameTypeBytes = 1;
+ const static uint8_t kFrameHeaderBytes = kFrameLengthBytes + kFrameFlagBytes +
+ kFrameTypeBytes +
+ kFrameStreamIDBytes;
+
+ enum {
+ kLeaderGroupID = 0x3,
+ kOtherGroupID = 0x5,
+ kBackgroundGroupID = 0x7,
+ kSpeculativeGroupID = 0x9,
+ kFollowerGroupID = 0xB,
+ kUrgentStartGroupID = 0xD
+ // Hey, you! YES YOU! If you add/remove any groups here, you almost
+ // certainly need to change the lookup of the stream/ID hash in
+ // Http2Session::OnTransportStatus and |kPriorityGroupCount| below.
+ // Yeah, that's right. YOU!
+ };
+ const static uint8_t kPriorityGroupCount = 6;
+
+ static nsresult RecvHeaders(Http2Session*);
+ static nsresult RecvPriority(Http2Session*);
+ static nsresult RecvRstStream(Http2Session*);
+ static nsresult RecvSettings(Http2Session*);
+ static nsresult RecvPushPromise(Http2Session*);
+ static nsresult RecvPing(Http2Session*);
+ static nsresult RecvGoAway(Http2Session*);
+ static nsresult RecvWindowUpdate(Http2Session*);
+ static nsresult RecvContinuation(Http2Session*);
+ static nsresult RecvAltSvc(Http2Session*);
+ static nsresult RecvUnused(Http2Session*);
+ static nsresult RecvOrigin(Http2Session*);
+
+ char* EnsureOutputBuffer(uint32_t needed);
+
+ template <typename charType>
+ void CreateFrameHeader(charType dest, uint16_t frameLength, uint8_t frameType,
+ uint8_t frameFlags, uint32_t streamID);
+
+ // For writing the data stream to LOG4
+ static void LogIO(Http2Session*, Http2StreamBase*, const char*, const char*,
+ uint32_t);
+
+ // overload of nsAHttpConnection
+ void TransactionHasDataToWrite(nsAHttpTransaction*) override;
+ void TransactionHasDataToRecv(nsAHttpTransaction*) override;
+
+ // a similar version for Http2StreamBase
+ void TransactionHasDataToWrite(Http2StreamBase*);
+ void TransactionHasDataToRecv(Http2StreamBase* caller);
+
+ // an overload of nsAHttpSegementReader
+ [[nodiscard]] virtual nsresult CommitToSegmentSize(
+ uint32_t count, bool forceCommitment) override;
+ [[nodiscard]] nsresult BufferOutput(const char*, uint32_t, uint32_t*);
+ void FlushOutputQueue();
+ uint32_t AmountOfOutputBuffered() {
+ return mOutputQueueUsed - mOutputQueueSent;
+ }
+
+ uint32_t GetServerInitialStreamWindow() { return mServerInitialStreamWindow; }
+
+ [[nodiscard]] bool TryToActivate(Http2StreamBase* stream);
+ void ConnectPushedStream(Http2StreamBase* stream);
+ void ConnectSlowConsumer(Http2StreamBase* stream);
+
+ [[nodiscard]] nsresult ConfirmTLSProfile();
+ [[nodiscard]] static bool ALPNCallback(nsITLSSocketControl* tlsSocketControl);
+
+ uint64_t Serial() { return mSerial; }
+
+ void PrintDiagnostics(nsCString& log) override;
+
+ // Streams need access to these
+ uint32_t SendingChunkSize() { return mSendingChunkSize; }
+ uint32_t PushAllowance() { return mPushAllowance; }
+ Http2Compressor* Compressor() { return &mCompressor; }
+ nsISocketTransport* SocketTransport() { return mSocketTransport; }
+ int64_t ServerSessionWindow() { return mServerSessionWindow; }
+ void DecrementServerSessionWindow(uint32_t bytes) {
+ mServerSessionWindow -= bytes;
+ }
+ uint32_t InitialRwin() { return mInitialRwin; }
+
+ void SendPing() override;
+ bool UseH2Deps() { return mUseH2Deps; }
+ void SetCleanShutdown(bool) override;
+
+ // overload of nsAHttpTransaction
+ [[nodiscard]] nsresult ReadSegmentsAgain(nsAHttpSegmentReader*, uint32_t,
+ uint32_t*, bool*) final;
+ [[nodiscard]] nsresult WriteSegmentsAgain(nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*, bool*) final;
+ [[nodiscard]] bool Do0RTT() final { return true; }
+ [[nodiscard]] nsresult Finish0RTT(bool aRestart, bool aAlpnChanged) final;
+
+ // For use by an HTTP2Stream
+ void Received421(nsHttpConnectionInfo* ci);
+
+ void SendPriorityFrame(uint32_t streamID, uint32_t dependsOn, uint8_t weight);
+ void IncrementTrrCounter() { mTrrStreams++; }
+
+ WebSocketSupport GetWebSocketSupport() override;
+
+ already_AddRefed<nsHttpConnection> CreateTunnelStream(
+ nsAHttpTransaction* aHttpTransaction, nsIInterfaceRequestor* aCallbacks,
+ PRIntervalTime aRtt, bool aIsWebSocket = false) override;
+
+ void CleanupStream(Http2StreamBase*, nsresult, errorType);
+
+ private:
+ Http2Session(nsISocketTransport*, enum SpdyVersion version,
+ bool attemptingEarlyData);
+
+ static Http2StreamTunnel* CreateTunnelStreamFromConnInfo(
+ Http2Session* session, uint64_t bcId, nsHttpConnectionInfo* connInfo,
+ bool isWebSocket);
+
+ // These internal states do not correspond to the states of the HTTP/2
+ // specification
+ enum internalStateType {
+ BUFFERING_OPENING_SETTINGS,
+ BUFFERING_FRAME_HEADER,
+ BUFFERING_CONTROL_FRAME,
+ PROCESSING_DATA_FRAME_PADDING_CONTROL,
+ PROCESSING_DATA_FRAME,
+ DISCARDING_DATA_FRAME_PADDING,
+ DISCARDING_DATA_FRAME,
+ PROCESSING_COMPLETE_HEADERS,
+ PROCESSING_CONTROL_RST_STREAM,
+ NOT_USING_NETWORK
+ };
+
+ static const uint8_t kMagicHello[24];
+
+ void CreateStream(nsAHttpTransaction* aHttpTransaction, int32_t aPriority,
+ Http2StreamBaseType streamType);
+
+ [[nodiscard]] nsresult ResponseHeadersComplete();
+ uint32_t GetWriteQueueSize();
+ void ChangeDownstreamState(enum internalStateType);
+ void ResetDownstreamState();
+ [[nodiscard]] nsresult ReadyToProcessDataFrame(enum internalStateType);
+ [[nodiscard]] nsresult UncompressAndDiscard(bool);
+ void GeneratePing(bool);
+ void GenerateSettingsAck();
+ void GeneratePriority(uint32_t, uint8_t);
+ void GenerateRstStream(uint32_t, uint32_t);
+ void GenerateGoAway(uint32_t);
+ void CleanupStream(uint32_t, nsresult, errorType);
+ void CloseStream(Http2StreamBase* aStream, nsresult aResult,
+ bool aRemoveFromQueue = true);
+ void SendHello();
+ void RemoveStreamFromQueues(Http2StreamBase*);
+ [[nodiscard]] nsresult ParsePadding(uint8_t&, uint16_t&);
+
+ void SetWriteCallbacks();
+ void RealignOutputQueue();
+
+ void ProcessPending();
+ [[nodiscard]] nsresult ProcessConnectedPush(Http2StreamBase*,
+ nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*);
+ [[nodiscard]] nsresult ProcessSlowConsumer(Http2StreamBase*,
+ nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*);
+
+ [[nodiscard]] nsresult SetInputFrameDataStream(uint32_t);
+ void CreatePriorityNode(uint32_t, uint32_t, uint8_t, const char*);
+ char* CreatePriorityFrame(uint32_t, uint32_t, uint8_t);
+ bool VerifyStream(Http2StreamBase*, uint32_t);
+ void SetNeedsCleanup();
+
+ void UpdateLocalRwin(Http2StreamBase* stream, uint32_t bytes);
+ void UpdateLocalStreamWindow(Http2StreamBase* stream, uint32_t bytes);
+ void UpdateLocalSessionWindow(uint32_t bytes);
+
+ void MaybeDecrementConcurrent(Http2StreamBase* stream);
+ bool RoomForMoreConcurrent();
+ void IncrementConcurrent(Http2StreamBase* stream);
+ void QueueStream(Http2StreamBase* stream);
+
+ // a wrapper for all calls to the nshttpconnection level segment writer. Used
+ // to track network I/O for timeout purposes
+ [[nodiscard]] nsresult NetworkRead(nsAHttpSegmentWriter*, char*, uint32_t,
+ uint32_t*);
+
+ void Shutdown(nsresult aReason);
+ void ShutdownStream(Http2StreamBase* aStream, nsresult aResult);
+
+ nsresult SessionError(enum errorType);
+
+ // This is intended to be nsHttpConnectionMgr:nsConnectionHandle taken
+ // from the first transaction on this session. That object contains the
+ // pointer to the real network-level nsHttpConnection object.
+ RefPtr<nsAHttpConnection> mConnection;
+
+ // The underlying socket transport object is needed to propogate some events
+ nsISocketTransport* mSocketTransport;
+
+ // These are temporary state variables to hold the argument to
+ // Read/WriteSegments so it can be accessed by On(read/write)segment
+ // further up the stack.
+ RefPtr<nsAHttpSegmentReader> mSegmentReader;
+ nsAHttpSegmentWriter* mSegmentWriter;
+
+ uint32_t mSendingChunkSize; /* the transmission chunk size */
+ uint32_t mNextStreamID; /* 24 bits */
+ uint32_t mLastPushedID;
+ uint32_t mConcurrentHighWater; /* max parallelism on session */
+ uint32_t mPushAllowance; /* rwin for unmatched pushes */
+
+ internalStateType mDownstreamState; /* in frame, between frames, etc.. */
+
+ // Maintain 2 indexes - one by stream ID, one by transaction pointer.
+ // There are also several lists of streams: ready to write, queued due to
+ // max parallelism, streams that need to force a read for push, and the full
+ // set of pushed streams.
+ nsTHashMap<nsUint32HashKey, Http2StreamBase*> mStreamIDHash;
+ nsRefPtrHashtable<nsPtrHashKey<nsAHttpTransaction>, Http2StreamBase>
+ mStreamTransactionHash;
+ nsTArray<RefPtr<Http2StreamTunnel>> mTunnelStreams;
+
+ nsTArray<WeakPtr<Http2StreamBase>> mReadyForWrite;
+ nsTArray<WeakPtr<Http2StreamBase>> mQueuedStreams;
+ nsTArray<WeakPtr<Http2StreamBase>> mPushesReadyForRead;
+ nsTArray<WeakPtr<Http2StreamBase>> mSlowConsumersReadyForRead;
+ nsTArray<Http2PushedStream*> mPushedStreams;
+
+ // Compression contexts for header transport.
+ // HTTP/2 compresses only HTTP headers and does not reset the context in
+ // between frames. Even data that is not associated with a stream (e.g invalid
+ // stream ID) is passed through these contexts to keep the compression
+ // context correct.
+ Http2Compressor mCompressor;
+ Http2Decompressor mDecompressor;
+ nsCString mDecompressBuffer;
+
+ // mInputFrameBuffer is used to store received control packets and the 8 bytes
+ // of header on data packets
+ uint32_t mInputFrameBufferSize; // buffer allocation
+ uint32_t mInputFrameBufferUsed; // amt of allocation used
+ UniquePtr<char[]> mInputFrameBuffer;
+
+ // mInputFrameDataSize/Read are used for tracking the amount of data consumed
+ // in a frame after the 8 byte header. Control frames are always fully
+ // buffered and the fixed 8 byte leading header is at mInputFrameBuffer + 0,
+ // the first data byte (i.e. the first settings/goaway/etc.. specific byte) is
+ // at mInputFrameBuffer + 8 The frame size is mInputFrameDataSize + the
+ // constant 8 byte header
+ uint32_t mInputFrameDataSize;
+ uint32_t mInputFrameDataRead;
+ bool mInputFrameFinal; // This frame was marked FIN
+ uint8_t mInputFrameType;
+ uint8_t mInputFrameFlags;
+ uint32_t mInputFrameID;
+ uint16_t mPaddingLength;
+
+ // When a frame has been received that is addressed to a particular stream
+ // (e.g. a data frame after the stream-id has been decoded), this points
+ // to the stream.
+ Http2StreamBase* mInputFrameDataStream;
+
+ // mNeedsCleanup is a state variable to defer cleanup of a closed stream
+ // If needed, It is set in session::OnWriteSegments() and acted on and
+ // cleared when the stack returns to session::WriteSegments(). The stream
+ // cannot be destroyed directly out of OnWriteSegments because
+ // stream::writeSegments() is on the stack at that time.
+ Http2StreamBase* mNeedsCleanup;
+
+ // This reason code in the last processed RESET frame
+ uint32_t mDownstreamRstReason;
+
+ // When HEADERS/PROMISE are chained together, this is the expected ID of the
+ // next recvd frame which must be the same type
+ uint32_t mExpectedHeaderID;
+ uint32_t mExpectedPushPromiseID;
+ uint32_t mContinuedPromiseStream;
+
+ // for the conversion of downstream http headers into http/2 formatted headers
+ // The data here does not persist between frames
+ nsCString mFlatHTTPResponseHeaders;
+ uint32_t mFlatHTTPResponseHeadersOut;
+
+ // when set, the session will go away when it reaches 0 streams. This flag
+ // is set when: the stream IDs are running out (at either the client or the
+ // server), when DontReuse() is called, a RST that is not specific to a
+ // particular stream is received, a GOAWAY frame has been received from
+ // the server.
+ bool mShouldGoAway;
+
+ // the session has received a nsAHttpTransaction::Close() call
+ bool mClosed;
+
+ // the session received a GoAway frame with a valid GoAwayID
+ bool mCleanShutdown;
+
+ // the session received the opening SETTINGS frame from the server
+ bool mReceivedSettings;
+
+ // The TLS comlpiance checks are not done in the ctor beacuse of bad
+ // exception handling - so we do them at IO time and cache the result
+ bool mTLSProfileConfirmed;
+
+ // A specifc reason code for the eventual GoAway frame. If set to
+ // NO_HTTP_ERROR only NO_HTTP_ERROR, PROTOCOL_ERROR, or INTERNAL_ERROR will be
+ // sent.
+ errorType mGoAwayReason;
+
+ // The error code sent/received on the session goaway frame. UNASSIGNED/31
+ // if not transmitted.
+ int32_t mClientGoAwayReason;
+ int32_t mPeerGoAwayReason;
+
+ // If a GoAway message was received this is the ID of the last valid
+ // stream. 0 otherwise. (0 is never a valid stream id.)
+ uint32_t mGoAwayID;
+
+ // The last stream processed ID we will send in our GoAway frame.
+ uint32_t mOutgoingGoAwayID;
+
+ // The limit on number of concurrent streams for this session. Normally it
+ // is basically unlimited, but the SETTINGS control message from the
+ // server might bring it down.
+ uint32_t mMaxConcurrent;
+
+ // The actual number of concurrent streams at this moment. Generally below
+ // mMaxConcurrent, but the max can be lowered in real time to a value
+ // below the current value
+ uint32_t mConcurrent;
+
+ // The number of server initiated promises, tracked for telemetry
+ uint32_t mServerPushedResources;
+
+ // The server rwin for new streams as determined from a SETTINGS frame
+ uint32_t mServerInitialStreamWindow;
+
+ // The Local Session window is how much data the server is allowed to send
+ // (across all streams) without getting a window update to stream 0. It is
+ // signed because asynchronous changes via SETTINGS can drive it negative.
+ int64_t mLocalSessionWindow;
+
+ // The Remote Session Window is how much data the client is allowed to send
+ // (across all streams) without receiving a window update to stream 0. It is
+ // signed because asynchronous changes via SETTINGS can drive it negative.
+ int64_t mServerSessionWindow;
+
+ // The initial value of the local stream and session window
+ uint32_t mInitialRwin;
+
+ // This is a output queue of bytes ready to be written to the SSL stream.
+ // When that streams returns WOULD_BLOCK on direct write the bytes get
+ // coalesced together here. This results in larger writes to the SSL layer.
+ // The buffer is not dynamically grown to accomodate stream writes, but
+ // does expand to accept infallible session wide frames like GoAway and RST.
+ uint32_t mOutputQueueSize;
+ uint32_t mOutputQueueUsed;
+ uint32_t mOutputQueueSent;
+ UniquePtr<char[]> mOutputQueueBuffer;
+
+ PRIntervalTime mPingThreshold;
+ PRIntervalTime mLastReadEpoch; // used for ping timeouts
+ PRIntervalTime mLastDataReadEpoch; // used for IdleTime()
+ PRIntervalTime mPingSentEpoch;
+
+ PRIntervalTime mPreviousPingThreshold; // backup for the former value
+ bool mPreviousUsed; // true when backup is used
+
+ // used as a temporary buffer while enumerating the stream hash during GoAway
+ nsDeque<Http2StreamBase> mGoAwayStreamsToRestart;
+
+ // Each session gets a unique serial number because the push cache is
+ // correlated by the load group and the serial number can be used as part of
+ // the cache key to make sure streams aren't shared across sessions.
+ uint64_t mSerial;
+
+ // Telemetry for continued headers (pushed and pulled) for quic design
+ uint32_t mAggregatedHeaderSize;
+
+ // If push is disabled, we want to be able to send PROTOCOL_ERRORs if we
+ // receive a PUSH_PROMISE, but we have to wait for the SETTINGS ACK before
+ // we can actually tell the other end to go away. These help us keep track
+ // of that state so we can behave appropriately.
+ bool mWaitingForSettingsAck;
+ bool mGoAwayOnPush;
+
+ bool mUseH2Deps;
+
+ bool mAttemptingEarlyData;
+ // The ID(s) of the stream(s) that we are getting 0RTT data from.
+ nsTArray<WeakPtr<Http2StreamBase>> m0RTTStreams;
+ // The ID(s) of the stream(s) that are not able to send 0RTT data. We need to
+ // remember them put them into mReadyForWrite queue when 0RTT finishes.
+ nsTArray<WeakPtr<Http2StreamBase>> mCannotDo0RTTStreams;
+
+ bool RealJoinConnection(const nsACString& hostname, int32_t port,
+ bool justKidding);
+ bool TestOriginFrame(const nsACString& name, int32_t port);
+ bool mOriginFrameActivated;
+ nsTHashMap<nsCStringHashKey, bool> mOriginFrame;
+
+ nsTHashMap<nsCStringHashKey, bool> mJoinConnectionCache;
+
+ uint64_t mCurrentBrowserId;
+
+ uint32_t mCntActivated;
+
+ // A h2 session will be created before all socket events are trigered,
+ // e.g. NS_NET_STATUS_TLS_HANDSHAKE_ENDED.
+ // We should propagate this events to the first nsHttpTransaction.
+ RefPtr<nsHttpTransaction> mFirstHttpTransaction;
+ bool mTlsHandshakeFinished;
+
+ bool mPeerFailedHandshake;
+
+ private:
+ uint32_t mTrrStreams;
+
+ // websockets
+ bool mEnableWebsockets; // Whether we allow websockets, based on a pref
+ bool mPeerAllowsWebsockets; // Whether our peer allows websockets, based on
+ // SETTINGS
+ bool mProcessedWaitingWebsockets; // True once we've received at least one
+ // SETTINGS
+ // Setting this to true means there is a transaction waiting for the result of
+ // WebSocket support. We'll need to process the pending queue once we've
+ // received the settings.
+ bool mHasTransactionWaitingForWebsockets = false;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Http2Session, NS_HTTP2SESSION_IID);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http2Session_h
diff --git a/netwerk/protocol/http/Http2Stream.cpp b/netwerk/protocol/http/Http2Stream.cpp
new file mode 100644
index 0000000000..cd758f694d
--- /dev/null
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -0,0 +1,298 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "Http2Push.h"
+#include "Http2Stream.h"
+#include "nsHttp.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpRequestHead.h"
+#include "nsISocketTransport.h"
+#include "Http2Session.h"
+#include "PSpdyPush.h"
+#include "nsIRequestContext.h"
+#include "nsHttpTransaction.h"
+#include "nsSocketTransportService2.h"
+
+namespace mozilla::net {
+
+Http2Stream::Http2Stream(nsAHttpTransaction* httpTransaction,
+ Http2Session* session, int32_t priority, uint64_t bcId)
+ : Http2StreamBase((httpTransaction->QueryHttpTransaction())
+ ? httpTransaction->QueryHttpTransaction()->BrowserId()
+ : 0,
+ session, priority, bcId),
+ mTransaction(httpTransaction) {
+ LOG1(("Http2Stream::Http2Stream %p trans=%p", this, httpTransaction));
+}
+
+Http2Stream::~Http2Stream() { ClearPushSource(); }
+
+void Http2Stream::CloseStream(nsresult reason) {
+ // In case we are connected to a push, make sure the push knows we are closed,
+ // so it doesn't try to give us any more DATA that comes on it after our
+ // close.
+ ClearPushSource();
+
+ mTransaction->Close(reason);
+ mSession = nullptr;
+}
+
+void Http2Stream::ClearPushSource() {
+ if (mPushSource) {
+ mPushSource->SetConsumerStream(nullptr);
+ mPushSource = nullptr;
+ }
+}
+
+nsresult Http2Stream::CheckPushCache() {
+ nsHttpRequestHead* head = mTransaction->RequestHead();
+
+ // check the push cache for GET
+ if (!head->IsGet()) {
+ return NS_OK;
+ }
+
+ RefPtr<Http2Session> session = Session();
+
+ nsAutoCString authorityHeader;
+ nsAutoCString hashkey;
+ nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false);
+ return rv;
+ }
+
+ nsAutoCString requestURI;
+ head->RequestURI(requestURI);
+
+ mozilla::OriginAttributes originAttributes;
+ mSocketTransport->GetOriginAttributes(&originAttributes);
+
+ CreatePushHashKey(nsDependentCString(head->IsHTTPS() ? "https" : "http"),
+ authorityHeader, originAttributes, session->Serial(),
+ requestURI, mOrigin, hashkey);
+
+ // from :scheme, :authority, :path
+ nsIRequestContext* requestContext = mTransaction->RequestContext();
+ SpdyPushCache* cache = nullptr;
+ if (requestContext) {
+ cache = requestContext->GetSpdyPushCache();
+ }
+
+ RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper;
+ Http2PushedStream* pushedStream = nullptr;
+
+ // If a push stream is attached to the transaction via onPush, match only
+ // with that one. This occurs when a push was made with in conjunction with
+ // a nsIHttpPushListener
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (trans && (pushedStreamWrapper = trans->TakePushedStream()) &&
+ (pushedStream = pushedStreamWrapper->GetStream())) {
+ RefPtr<Http2Session> pushSession = pushedStream->Session();
+ if (pushSession == session) {
+ LOG3(
+ ("Pushed Stream match based on OnPush correlation %p", pushedStream));
+ } else {
+ LOG3(("Pushed Stream match failed due to stream mismatch %p %" PRId64
+ " %" PRId64 "\n",
+ pushedStream, pushSession->Serial(), session->Serial()));
+ pushedStream->OnPushFailed();
+ pushedStream = nullptr;
+ }
+ }
+
+ // we remove the pushedstream from the push cache so that
+ // it will not be used for another GET. This does not destroy the
+ // stream itself - that is done when the transactionhash is done with it.
+ if (cache && !pushedStream) {
+ pushedStream = cache->RemovePushedStreamHttp2(hashkey);
+ }
+
+ LOG3(
+ ("Pushed Stream Lookup "
+ "session=%p key=%s requestcontext=%p cache=%p hit=%p\n",
+ session.get(), hashkey.get(), requestContext, cache, pushedStream));
+
+ if (pushedStream) {
+ LOG3(("Pushed Stream Match located %p id=0x%X key=%s\n", pushedStream,
+ pushedStream->StreamID(), hashkey.get()));
+ pushedStream->SetConsumerStream(this);
+ mPushSource = pushedStream;
+ SetSentFin(true);
+ AdjustPushedPriority();
+
+ // There is probably pushed data buffered so trigger a read manually
+ // as we can't rely on future network events to do it
+ session->ConnectPushedStream(this);
+ mOpenGenerated = 1;
+
+ // if the "mother stream" had TRR, this one is a TRR stream too!
+ RefPtr<nsHttpConnectionInfo> ci(Transaction()->ConnectionInfo());
+ if (ci && ci->GetIsTrrServiceChannel()) {
+ session->IncrementTrrCounter();
+ }
+ }
+
+ return NS_OK;
+}
+
+uint32_t Http2Stream::GetWireStreamId() {
+ // >0 even numbered IDs are pushed streams.
+ // odd numbered IDs are pulled streams.
+ // 0 is the sink for a pushed stream.
+ if (!mStreamID) {
+ MOZ_ASSERT(mPushSource);
+ if (!mPushSource) {
+ return 0;
+ }
+
+ MOZ_ASSERT(mPushSource->StreamID());
+ MOZ_ASSERT(!(mPushSource->StreamID() & 1)); // is a push stream
+
+ // If the pushed stream has recvd a FIN, there is no reason to update
+ // the window
+ if (mPushSource->RecvdFin() || mPushSource->RecvdReset() ||
+ (mPushSource->HTTPState() == RESERVED_BY_REMOTE)) {
+ return 0;
+ }
+ return mPushSource->StreamID();
+ }
+
+ if (mState == RESERVED_BY_REMOTE) {
+ // h2-14 prevents sending a window update in this state
+ return 0;
+ }
+ return mStreamID;
+}
+
+void Http2Stream::AdjustPushedPriority() {
+ // >0 even numbered IDs are pushed streams. odd numbered IDs are pulled
+ // streams. 0 is the sink for a pushed stream.
+
+ if (mStreamID || !mPushSource) return;
+
+ MOZ_ASSERT(mPushSource->StreamID() && !(mPushSource->StreamID() & 1));
+
+ // If the pushed stream has recvd a FIN, there is no reason to update
+ // the window
+ if (mPushSource->RecvdFin() || mPushSource->RecvdReset()) return;
+
+ // Ensure we pick up the right dependency to place the pushed stream under.
+ UpdatePriorityDependency();
+
+ EnsureBuffer(mTxInlineFrame,
+ mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 5,
+ mTxInlineFrameUsed, mTxInlineFrameSize);
+ uint8_t* packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
+ mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 5;
+
+ RefPtr<Http2Session> session = Session();
+ session->CreateFrameHeader(packet, 5, Http2Session::FRAME_TYPE_PRIORITY, 0,
+ mPushSource->StreamID());
+
+ mPushSource->SetPriorityDependency(mPriority, mPriorityDependency);
+ uint32_t wireDep = PR_htonl(mPriorityDependency);
+ memcpy(packet + Http2Session::kFrameHeaderBytes, &wireDep, 4);
+ memcpy(packet + Http2Session::kFrameHeaderBytes + 4, &mPriorityWeight, 1);
+
+ LOG3(("AdjustPushedPriority %p id 0x%X to dep %X weight %X\n", this,
+ mPushSource->StreamID(), mPriorityDependency, mPriorityWeight));
+}
+
+bool Http2Stream::IsReadingFromPushStream() { return !!mPushSource; }
+
+nsresult Http2Stream::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ LOG3(("Http2Stream::OnWriteSegment %p count=%d state=%x 0x%X\n", this, count,
+ mUpstreamState, mStreamID));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mSegmentWriter);
+
+ if (mPushSource) {
+ nsresult rv;
+ rv = mPushSource->GetBufferedData(buf, count, countWritten);
+ if (NS_FAILED(rv)) return rv;
+
+ RefPtr<Http2Session> session = Session();
+ session->ConnectPushedStream(this);
+ return NS_OK;
+ }
+
+ return Http2StreamBase::OnWriteSegment(buf, count, countWritten);
+}
+
+nsresult Http2Stream::CallToReadData(uint32_t count, uint32_t* countRead) {
+ return mTransaction->ReadSegments(this, count, countRead);
+}
+
+nsresult Http2Stream::CallToWriteData(uint32_t count, uint32_t* countWritten) {
+ return mTransaction->WriteSegments(this, count, countWritten);
+}
+
+// This is really a headers frame, but open is pretty clear from a workflow pov
+nsresult Http2Stream::GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) {
+ nsHttpRequestHead* head = mTransaction->RequestHead();
+ nsAutoCString requestURI;
+ head->RequestURI(requestURI);
+ RefPtr<Http2Session> session = Session();
+ LOG3(("Http2Stream %p Stream ID 0x%X [session=%p] for URI %s\n", this,
+ mStreamID, session.get(), requestURI.get()));
+
+ nsAutoCString authorityHeader;
+ nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false);
+ return rv;
+ }
+
+ nsDependentCString scheme(head->IsHTTPS() ? "https" : "http");
+
+ nsAutoCString method;
+ nsAutoCString path;
+ head->Method(method);
+ head->Path(path);
+
+ rv = session->Compressor()->EncodeHeaderBlock(
+ mFlatHttpRequestHeaders, method, path, authorityHeader, scheme,
+ EmptyCString(), false, aCompressedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t clVal = session->Compressor()->GetParsedContentLength();
+ if (clVal != -1) {
+ mRequestBodyLenRemaining = clVal;
+ }
+
+ // Determine whether to put the fin bit on the header frame or whether
+ // to wait for a data packet to put it on.
+
+ if (head->IsGet() || head->IsHead()) {
+ // for GET and HEAD place the fin bit right on the
+ // header packet
+ firstFrameFlags |= Http2Session::kFlag_END_STREAM;
+ } else if (head->IsPost() || head->IsPut() || head->IsConnect()) {
+ // place fin in a data frame even for 0 length messages for iterop
+ } else if (!mRequestBodyLenRemaining) {
+ // for other HTTP extension methods, rely on the content-length
+ // to determine whether or not to put fin on headers
+ firstFrameFlags |= Http2Session::kFlag_END_STREAM;
+ }
+
+ // The size of the input headers is approximate
+ uint32_t ratio =
+ aCompressedData.Length() * 100 /
+ (11 + requestURI.Length() + mFlatHttpRequestHeaders.Length());
+
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
+
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/Http2Stream.h b/netwerk/protocol/http/Http2Stream.h
new file mode 100644
index 0000000000..aae1ca1b5d
--- /dev/null
+++ b/netwerk/protocol/http/Http2Stream.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http2Stream_h
+#define mozilla_net_Http2Stream_h
+
+#include "Http2StreamBase.h"
+
+namespace mozilla::net {
+
+class Http2Stream : public Http2StreamBase {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Http2Stream, override)
+
+ Http2Stream(nsAHttpTransaction* httpTransaction, Http2Session* session,
+ int32_t priority, uint64_t bcId);
+
+ void CloseStream(nsresult reason) override;
+ Http2Stream* GetHttp2Stream() override { return this; }
+ uint32_t GetWireStreamId() override;
+
+ nsresult OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) override;
+
+ nsresult CheckPushCache();
+ Http2PushedStream* PushSource() { return mPushSource; }
+ bool IsReadingFromPushStream();
+ void ClearPushSource();
+
+ nsAHttpTransaction* Transaction() override { return mTransaction; }
+ nsIRequestContext* RequestContext() override {
+ return mTransaction ? mTransaction->RequestContext() : nullptr;
+ }
+
+ protected:
+ ~Http2Stream();
+ nsresult CallToReadData(uint32_t count, uint32_t* countRead) override;
+ nsresult CallToWriteData(uint32_t count, uint32_t* countWritten) override;
+ nsresult GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) override;
+
+ private:
+ // For Http2Push
+ void AdjustPushedPriority();
+ Http2PushedStream* mPushSource{nullptr};
+
+ // The underlying HTTP transaction. This pointer is used as the key
+ // in the Http2Session mStreamTransactionHash so it is important to
+ // keep a reference to it as long as this stream is a member of that hash.
+ // (i.e. don't change it or release it after it is set in the ctor).
+ RefPtr<nsAHttpTransaction> mTransaction;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_Http2Stream_h
diff --git a/netwerk/protocol/http/Http2StreamBase.cpp b/netwerk/protocol/http/Http2StreamBase.cpp
new file mode 100644
index 0000000000..bb135a59d9
--- /dev/null
+++ b/netwerk/protocol/http/Http2StreamBase.cpp
@@ -0,0 +1,1324 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include <algorithm>
+
+#include "Http2Compression.h"
+#include "Http2Session.h"
+#include "Http2StreamBase.h"
+#include "Http2Stream.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "nsAlgorithm.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsIClassOfService.h"
+#include "prnetdb.h"
+
+namespace mozilla::net {
+
+Http2StreamBase::Http2StreamBase(uint64_t aTransactionBrowserId,
+ Http2Session* session, int32_t priority,
+ uint64_t currentBrowserId)
+ : mSession(
+ do_GetWeakReference(static_cast<nsISupportsWeakReference*>(session))),
+ mRequestHeadersDone(0),
+ mOpenGenerated(0),
+ mAllHeadersReceived(0),
+ mQueued(0),
+ mSocketTransport(session->SocketTransport()),
+ mCurrentBrowserId(currentBrowserId),
+ mTransactionBrowserId(aTransactionBrowserId),
+ mTxInlineFrameSize(Http2Session::kDefaultBufferSize),
+ mChunkSize(session->SendingChunkSize()),
+ mRequestBlockedOnRead(0),
+ mRecvdFin(0),
+ mReceivedData(0),
+ mRecvdReset(0),
+ mSentReset(0),
+ mCountAsActive(0),
+ mSentFin(0),
+ mSentWaitingFor(0),
+ mSetTCPSocketBuffer(0),
+ mBypassInputBuffer(0) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG1(("Http2StreamBase::Http2StreamBase %p", this));
+
+ mServerReceiveWindow = session->GetServerInitialStreamWindow();
+ mClientReceiveWindow = session->PushAllowance();
+
+ mTxInlineFrame = MakeUnique<uint8_t[]>(mTxInlineFrameSize);
+
+ static_assert(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority,
+ "Lowest Priority should be less than kNormalPriority");
+
+ // values of priority closer to 0 are higher priority for the priority
+ // argument. This value is used as a group, which maps to a
+ // weight that is related to the nsISupportsPriority that we are given.
+ int32_t httpPriority;
+ if (priority >= nsISupportsPriority::PRIORITY_LOWEST) {
+ httpPriority = kWorstPriority;
+ } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) {
+ httpPriority = kBestPriority;
+ } else {
+ httpPriority = kNormalPriority + priority;
+ }
+ MOZ_ASSERT(httpPriority >= 0);
+ SetPriority(static_cast<uint32_t>(httpPriority));
+}
+
+Http2StreamBase::~Http2StreamBase() {
+ MOZ_DIAGNOSTIC_ASSERT(OnSocketThread());
+
+ mStreamID = Http2Session::kDeadStreamID;
+
+ LOG3(("Http2StreamBase::~Http2StreamBase %p", this));
+}
+
+already_AddRefed<Http2Session> Http2StreamBase::Session() {
+ RefPtr<Http2Session> session = do_QueryReferent(mSession);
+ return session.forget();
+}
+
+// ReadSegments() is used to write data down the socket. Generally, HTTP
+// request data is pulled from the approriate transaction and
+// converted to HTTP/2 data. Sometimes control data like a window-update is
+// generated instead.
+
+nsresult Http2StreamBase::ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count, uint32_t* countRead) {
+ LOG3(("Http2StreamBase %p ReadSegments reader=%p count=%d state=%x", this,
+ reader, count, mUpstreamState));
+ RefPtr<Http2Session> session = Session();
+ // Reader is nullptr when this is a push stream.
+ MOZ_DIAGNOSTIC_ASSERT(!reader || (reader == session) ||
+ (IsTunnel() && NS_FAILED(Condition())));
+
+ if (NS_FAILED(Condition())) {
+ return Condition();
+ }
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ mRequestBlockedOnRead = 0;
+
+ if (mRecvdFin || mRecvdReset) {
+ // Don't transmit any request frames if the peer cannot respond
+ LOG3(
+ ("Http2StreamBase %p ReadSegments request stream aborted due to"
+ " response side closure\n",
+ this));
+ return NS_ERROR_ABORT;
+ }
+
+ // avoid runt chunks if possible by anticipating
+ // full data frames
+ if (count > (mChunkSize + 8)) {
+ uint32_t numchunks = count / (mChunkSize + 8);
+ count = numchunks * (mChunkSize + 8);
+ }
+
+ switch (mUpstreamState) {
+ case GENERATING_HEADERS:
+ case GENERATING_BODY:
+ case SENDING_BODY:
+ // Call into the HTTP Transaction to generate the HTTP request
+ // stream. That stream will show up in OnReadSegment().
+ mSegmentReader = reader;
+ rv = CallToReadData(count, countRead);
+ mSegmentReader = nullptr;
+
+ LOG3(("Http2StreamBase::ReadSegments %p trans readsegments rv %" PRIx32
+ " read=%d\n",
+ this, static_cast<uint32_t>(rv), *countRead));
+
+ // Check to see if the transaction's request could be written out now.
+ // If not, mark the stream for callback when writing can proceed.
+ if (NS_SUCCEEDED(rv) && mUpstreamState == GENERATING_HEADERS &&
+ !mRequestHeadersDone) {
+ session->TransactionHasDataToWrite(this);
+ }
+
+ // mTxinlineFrameUsed represents any queued un-sent frame. It might
+ // be 0 if there is no such frame, which is not a gurantee that we
+ // don't have more request body to send - just that any data that was
+ // sent comprised a complete HTTP/2 frame. Likewise, a non 0 value is
+ // a queued, but complete, http/2 frame length.
+
+ // Mark that we are blocked on read if the http transaction needs to
+ // provide more of the request message body and there is nothing queued
+ // for writing
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed) {
+ LOG(("Http2StreamBase %p mRequestBlockedOnRead = 1", this));
+ mRequestBlockedOnRead = 1;
+ }
+
+ // A transaction that had already generated its headers before it was
+ // queued at the session level (due to concurrency concerns) may not call
+ // onReadSegment off the ReadSegments() stack above.
+
+ // When mTransaction->ReadSegments returns NS_BASE_STREAM_WOULD_BLOCK it
+ // means it may have already finished providing all the request data
+ // necessary to generate open, calling OnReadSegment will drive sending
+ // the request; this may happen after dequeue of the stream.
+
+ if (mUpstreamState == GENERATING_HEADERS &&
+ (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK)) {
+ LOG3(("Http2StreamBase %p ReadSegments forcing OnReadSegment call\n",
+ this));
+ uint32_t wasted = 0;
+ mSegmentReader = reader;
+ nsresult rv2 = OnReadSegment("", 0, &wasted);
+ mSegmentReader = nullptr;
+
+ LOG3((" OnReadSegment returned 0x%08" PRIx32,
+ static_cast<uint32_t>(rv2)));
+ if (NS_SUCCEEDED(rv2)) {
+ mRequestBlockedOnRead = 0;
+ }
+ }
+
+ // If the sending flow control window is open (!mBlockedOnRwin) then
+ // continue sending the request
+ if (!mBlockedOnRwin && mOpenGenerated && !mTxInlineFrameUsed &&
+ NS_SUCCEEDED(rv) && (!*countRead) && CloseSendStreamWhenDone()) {
+ MOZ_ASSERT(!mQueued);
+ MOZ_ASSERT(mRequestHeadersDone);
+ LOG3(
+ ("Http2StreamBase::ReadSegments %p 0x%X: Sending request data "
+ "complete, "
+ "mUpstreamState=%x\n",
+ this, mStreamID, mUpstreamState));
+ if (mSentFin) {
+ ChangeState(UPSTREAM_COMPLETE);
+ } else {
+ GenerateDataFrameHeader(0, true);
+ ChangeState(SENDING_FIN_STREAM);
+ session->TransactionHasDataToWrite(this);
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ }
+ break;
+
+ case SENDING_FIN_STREAM:
+ // We were trying to send the FIN-STREAM but were blocked from
+ // sending it out - try again.
+ if (!mSentFin) {
+ mSegmentReader = reader;
+ rv = TransmitFrame(nullptr, nullptr, false);
+ mSegmentReader = nullptr;
+ MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
+ "Transmit Frame should be all or nothing");
+ if (NS_SUCCEEDED(rv)) ChangeState(UPSTREAM_COMPLETE);
+ } else {
+ rv = NS_OK;
+ mTxInlineFrameUsed = 0; // cancel fin data packet
+ ChangeState(UPSTREAM_COMPLETE);
+ }
+
+ *countRead = 0;
+
+ // don't change OK to WOULD BLOCK. we are really done sending if OK
+ break;
+
+ case UPSTREAM_COMPLETE:
+ *countRead = 0;
+ rv = NS_OK;
+ break;
+
+ default:
+ MOZ_ASSERT(false, "Http2StreamBase::ReadSegments unknown state");
+ break;
+ }
+
+ return rv;
+}
+
+uint64_t Http2StreamBase::LocalUnAcked() {
+ // reduce unacked by the amount of undelivered data
+ // to help assert flow control
+ uint64_t undelivered = mSimpleBuffer.Available();
+
+ if (undelivered > mLocalUnacked) {
+ return 0;
+ }
+ return mLocalUnacked - undelivered;
+}
+
+nsresult Http2StreamBase::BufferInput(uint32_t count, uint32_t* countWritten) {
+ char buf[SimpleBufferPage::kSimpleBufferPageSize];
+ if (SimpleBufferPage::kSimpleBufferPageSize < count) {
+ count = SimpleBufferPage::kSimpleBufferPageSize;
+ }
+
+ mBypassInputBuffer = 1;
+ nsresult rv = mSegmentWriter->OnWriteSegment(buf, count, countWritten);
+ mBypassInputBuffer = 0;
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = mSimpleBuffer.Write(buf, *countWritten);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return rv;
+}
+
+bool Http2StreamBase::DeferCleanup(nsresult status) {
+ // do not cleanup a stream that has data buffered for the transaction
+ return (NS_SUCCEEDED(status) && mSimpleBuffer.Available());
+}
+
+// WriteSegments() is used to read data off the socket. Generally this is
+// just a call through to the associated nsHttpTransaction for this stream
+// for the remaining data bytes indicated by the current DATA frame.
+
+nsresult Http2StreamBase::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");
+
+ LOG3(("Http2StreamBase::WriteSegments %p count=%d state=%x", this, count,
+ mUpstreamState));
+
+ mSegmentWriter = writer;
+ nsresult rv = CallToWriteData(count, countWritten);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ // consuming transaction won't take data. but we need to read it into a
+ // buffer so that it won't block other streams. but we should not advance
+ // the flow control window so that we'll eventually push back on the sender.
+ rv = BufferInput(count, countWritten);
+ LOG3(("Http2StreamBase::WriteSegments %p Buffered %" PRIX32 " %d\n", this,
+ static_cast<uint32_t>(rv), *countWritten));
+ }
+
+ LOG3(("Http2StreamBase::WriteSegments %" PRIX32 "",
+ static_cast<uint32_t>(rv)));
+ mSegmentWriter = nullptr;
+ return rv;
+}
+
+nsresult Http2StreamBase::ParseHttpRequestHeaders(const char* buf,
+ uint32_t avail,
+ uint32_t* countUsed) {
+ // Returns NS_OK even if the headers are incomplete
+ // set mRequestHeadersDone flag if they are complete
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS);
+ MOZ_ASSERT(!mRequestHeadersDone);
+
+ LOG3(("Http2StreamBase::ParseHttpRequestHeaders %p avail=%d state=%x", this,
+ avail, mUpstreamState));
+
+ mFlatHttpRequestHeaders.Append(buf, avail);
+
+ // We can use the simple double crlf because firefox is the
+ // only client we are parsing
+ int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
+
+ if (endHeader == kNotFound) {
+ // We don't have all the headers yet
+ LOG3(
+ ("Http2StreamBase::ParseHttpRequestHeaders %p "
+ "Need more header bytes. Len = %zd",
+ this, mFlatHttpRequestHeaders.Length()));
+ *countUsed = avail;
+ return NS_OK;
+ }
+
+ // We have recvd all the headers, trim the local
+ // buffer of the final empty line, and set countUsed to reflect
+ // the whole header has been consumed.
+ uint32_t oldLen = mFlatHttpRequestHeaders.Length();
+ mFlatHttpRequestHeaders.SetLength(endHeader + 2);
+ *countUsed = avail - (oldLen - endHeader) + 4;
+ mRequestHeadersDone = 1;
+
+ Http2Stream* selfRegularStream = this->GetHttp2Stream();
+ if (selfRegularStream) {
+ return selfRegularStream->CheckPushCache();
+ }
+
+ return NS_OK;
+}
+
+// This is really a headers frame, but open is pretty clear from a workflow pov
+nsresult Http2StreamBase::GenerateOpen() {
+ // It is now OK to assign a streamID that we are assured will
+ // be monotonically increasing amongst new streams on this
+ // session
+ RefPtr<Http2Session> session = Session();
+ mStreamID = session->RegisterStreamID(this);
+ MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd");
+ MOZ_ASSERT(!mOpenGenerated);
+
+ mOpenGenerated = 1;
+
+ LOG3(("Http2StreamBase %p Stream ID 0x%X [session=%p]\n", this, mStreamID,
+ session.get()));
+
+ if (mStreamID >= 0x80000000) {
+ // streamID must fit in 31 bits. Evading This is theoretically possible
+ // because stream ID assignment is asynchronous to stream creation
+ // because of the protocol requirement that the new stream ID
+ // be monotonically increasing. In reality this is really not possible
+ // because new streams stop being added to a session with millions of
+ // IDs still available and no race condition is going to bridge that gap;
+ // so we can be comfortable on just erroring out for correctness in that
+ // case.
+ LOG3(("Stream assigned out of range ID: 0x%X", mStreamID));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Now we need to convert the flat http headers into a set
+ // of HTTP/2 headers by writing to mTxInlineFrame{sz}
+
+ nsCString compressedData;
+ uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY;
+
+ nsresult rv = GenerateHeaders(compressedData, firstFrameFlags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (firstFrameFlags & Http2Session::kFlag_END_STREAM) {
+ SetSentFin(true);
+ }
+
+ // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it
+ // exceeds the 2^14-1 limit for 1 frame. Do it by inserting header size gaps
+ // in the existing frame for the new headers and for the first one a priority
+ // field. There is no question this is ugly, but a 16KB HEADERS frame should
+ // be a long tail event, so this is really just for correctness and a nop in
+ // the base case.
+ //
+
+ MOZ_ASSERT(!mTxInlineFrameUsed);
+
+ uint32_t dataLength = compressedData.Length();
+ uint32_t maxFrameData =
+ Http2Session::kMaxFrameData - 5; // 5 bytes for priority
+ uint32_t numFrames = 1;
+
+ if (dataLength > maxFrameData) {
+ numFrames +=
+ ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) /
+ Http2Session::kMaxFrameData;
+ MOZ_ASSERT(numFrames > 1);
+ }
+
+ // note that we could still have 1 frame for 0 bytes of data. that's ok.
+
+ uint32_t messageSize = dataLength;
+ messageSize += Http2Session::kFrameHeaderBytes +
+ 5; // frame header + priority overhead in HEADERS frame
+ messageSize += (numFrames - 1) *
+ Http2Session::kFrameHeaderBytes; // frame header overhead in
+ // CONTINUATION frames
+
+ EnsureBuffer(mTxInlineFrame, messageSize, mTxInlineFrameUsed,
+ mTxInlineFrameSize);
+
+ mTxInlineFrameUsed += messageSize;
+ UpdatePriorityDependency();
+ LOG1(
+ ("Http2StreamBase %p Generating %d bytes of HEADERS for stream 0x%X with "
+ "priority weight %u dep 0x%X frames %u\n",
+ this, mTxInlineFrameUsed, mStreamID, mPriorityWeight,
+ mPriorityDependency, numFrames));
+
+ uint32_t outputOffset = 0;
+ uint32_t compressedDataOffset = 0;
+ for (uint32_t idx = 0; idx < numFrames; ++idx) {
+ uint32_t flags, frameLen;
+ bool lastFrame = (idx == numFrames - 1);
+
+ flags = 0;
+ frameLen = maxFrameData;
+ if (!idx) {
+ flags |= firstFrameFlags;
+ // Only the first frame needs the 4-byte offset
+ maxFrameData = Http2Session::kMaxFrameData;
+ }
+ if (lastFrame) {
+ frameLen = dataLength;
+ flags |= Http2Session::kFlag_END_HEADERS;
+ }
+ dataLength -= frameLen;
+
+ session->CreateFrameHeader(mTxInlineFrame.get() + outputOffset,
+ frameLen + (idx ? 0 : 5),
+ (idx) ? Http2Session::FRAME_TYPE_CONTINUATION
+ : Http2Session::FRAME_TYPE_HEADERS,
+ flags, mStreamID);
+ outputOffset += Http2Session::kFrameHeaderBytes;
+
+ if (!idx) {
+ uint32_t wireDep = PR_htonl(mPriorityDependency);
+ memcpy(mTxInlineFrame.get() + outputOffset, &wireDep, 4);
+ memcpy(mTxInlineFrame.get() + outputOffset + 4, &mPriorityWeight, 1);
+ outputOffset += 5;
+ }
+
+ memcpy(mTxInlineFrame.get() + outputOffset,
+ compressedData.BeginReading() + compressedDataOffset, frameLen);
+ compressedDataOffset += frameLen;
+ outputOffset += frameLen;
+ }
+
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, compressedData.Length());
+
+ mFlatHttpRequestHeaders.Truncate();
+
+ return NS_OK;
+}
+
+void Http2StreamBase::AdjustInitialWindow() {
+ // The default initial_window is sized for pushed streams. When we
+ // generate a client pulled stream we want to disable flow control for
+ // the stream with a window update. Do the same for pushed streams
+ // when they connect to a pull.
+
+ uint32_t wireStreamId = GetWireStreamId();
+ if (wireStreamId == 0) {
+ return;
+ }
+
+ // right now mClientReceiveWindow is the lower push limit
+ // bump it up to the pull limit set by the channel or session
+ // don't allow windows less than push
+ uint32_t bump = 0;
+ RefPtr<Http2Session> session = Session();
+ nsHttpTransaction* trans = HttpTransaction();
+ if (trans && trans->InitialRwin()) {
+ bump = (trans->InitialRwin() > mClientReceiveWindow)
+ ? (trans->InitialRwin() - mClientReceiveWindow)
+ : 0;
+ } else {
+ MOZ_ASSERT(session->InitialRwin() >= mClientReceiveWindow);
+ bump = session->InitialRwin() - mClientReceiveWindow;
+ }
+
+ LOG3(("AdjustInitialwindow increased flow control window %p 0x%X %u\n", this,
+ wireStreamId, bump));
+ if (!bump) { // nothing to do
+ return;
+ }
+
+ EnsureBuffer(mTxInlineFrame,
+ mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 4,
+ mTxInlineFrameUsed, mTxInlineFrameSize);
+ uint8_t* packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
+ mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 4;
+
+ session->CreateFrameHeader(packet, 4, Http2Session::FRAME_TYPE_WINDOW_UPDATE,
+ 0, wireStreamId);
+
+ mClientReceiveWindow += bump;
+ bump = PR_htonl(bump);
+ memcpy(packet + Http2Session::kFrameHeaderBytes, &bump, 4);
+}
+
+void Http2StreamBase::UpdateTransportReadEvents(uint32_t count) {
+ mTotalRead += count;
+ if (!mSocketTransport) {
+ return;
+ }
+
+ if (Transaction()) {
+ Transaction()->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_RECEIVING_FROM, mTotalRead);
+ }
+}
+
+void Http2StreamBase::UpdateTransportSendEvents(uint32_t count) {
+ mTotalSent += count;
+
+ // Setting the TCP send buffer, introduced in
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=790184, which the following
+ // comment refers to, is being removed once we verify no increases in error
+ // rate.
+ //
+ // normally on non-windows platform we use TCP autotuning for
+ // the socket buffers, and this works well (managing enough
+ // buffers for BDP while conserving memory) for HTTP even when
+ // it creates really deep queues. However this 'buffer bloat' is
+ // a problem for http/2 because it ruins the low latency properties
+ // necessary for PING and cancel to work meaningfully.
+
+ // If this stream represents a large upload, disable autotuning for
+ // the session and cap the send buffers by default at 128KB.
+ // (10Mbit/sec @ 100ms)
+ //
+ uint32_t bufferSize = gHttpHandler->SpdySendBufferSize();
+ if (StaticPrefs::network_http_http2_send_buffer_size() > 0 &&
+ (mTotalSent > bufferSize) && !mSetTCPSocketBuffer) {
+ mSetTCPSocketBuffer = 1;
+ mSocketTransport->SetSendBufferSize(bufferSize);
+ }
+
+ if ((mUpstreamState != SENDING_FIN_STREAM) && Transaction()) {
+ Transaction()->OnTransportStatus(mSocketTransport, NS_NET_STATUS_SENDING_TO,
+ mTotalSent);
+ }
+
+ if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
+ mSentWaitingFor = 1;
+ if (Transaction()) {
+ Transaction()->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_WAITING_FOR, 0);
+ }
+ }
+}
+
+nsresult Http2StreamBase::TransmitFrame(const char* buf, uint32_t* countUsed,
+ bool forceCommitment) {
+ // If TransmitFrame returns SUCCESS than all the data is sent (or at least
+ // buffered at the session level), if it returns WOULD_BLOCK then none of
+ // the data is sent.
+
+ // You can call this function with no data and no out parameter in order to
+ // flush internal buffers that were previously blocked on writing. You can
+ // of course feed new data to it as well.
+
+ LOG3(("Http2StreamBase::TransmitFrame %p inline=%d stream=%d", this,
+ mTxInlineFrameUsed, mTxStreamFrameSize));
+ if (countUsed) *countUsed = 0;
+
+ if (!mTxInlineFrameUsed) {
+ MOZ_ASSERT(!buf);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit");
+ MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader");
+ MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed),
+ "TransmitFrame arguments inconsistent");
+
+ uint32_t transmittedCount;
+ nsresult rv;
+ RefPtr<Http2Session> session = Session();
+
+ // In the (relatively common) event that we have a small amount of data
+ // split between the inlineframe and the streamframe, then move the stream
+ // data into the inlineframe via copy in order to coalesce into one write.
+ // Given the interaction with ssl this is worth the small copy cost.
+ if (mTxStreamFrameSize && mTxInlineFrameUsed &&
+ mTxStreamFrameSize < Http2Session::kDefaultBufferSize &&
+ mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) {
+ LOG3(("Coalesce Transmit"));
+ memcpy(&mTxInlineFrame[mTxInlineFrameUsed], buf, mTxStreamFrameSize);
+ if (countUsed) *countUsed += mTxStreamFrameSize;
+ mTxInlineFrameUsed += mTxStreamFrameSize;
+ mTxStreamFrameSize = 0;
+ }
+
+ rv = mSegmentReader->CommitToSegmentSize(
+ mTxStreamFrameSize + mTxInlineFrameUsed, forceCommitment);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK");
+ session->TransactionHasDataToWrite(this);
+ }
+ if (NS_FAILED(rv)) { // this will include WOULD_BLOCK
+ return rv;
+ }
+
+ // This function calls mSegmentReader->OnReadSegment to report the actual
+ // http/2 bytes through to the session object and then the HttpConnection
+ // which calls the socket write function. It will accept all of the inline and
+ // stream data because of the above 'commitment' even if it has to buffer
+
+ rv = session->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()),
+ mTxInlineFrameUsed, &transmittedCount);
+ LOG3(
+ ("Http2StreamBase::TransmitFrame for inline BufferOutput session=%p "
+ "stream=%p result %" PRIx32 " len=%d",
+ session.get(), this, static_cast<uint32_t>(rv), transmittedCount));
+
+ MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
+ "inconsistent inline commitment result");
+
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed,
+ "inconsistent inline commitment count");
+
+ Http2Session::LogIO(session, this, "Writing from Inline Buffer",
+ reinterpret_cast<char*>(mTxInlineFrame.get()),
+ transmittedCount);
+
+ if (mTxStreamFrameSize) {
+ if (!buf) {
+ // this cannot happen
+ MOZ_ASSERT(false,
+ "Stream transmit with null buf argument to "
+ "TransmitFrame()");
+ LOG3(("Stream transmit with null buf argument to TransmitFrame()\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // If there is already data buffered, just add to that to form
+ // a single TLS Application Data Record - otherwise skip the memcpy
+ if (session->AmountOfOutputBuffered()) {
+ rv = session->BufferOutput(buf, mTxStreamFrameSize, &transmittedCount);
+ } else {
+ rv = session->OnReadSegment(buf, mTxStreamFrameSize, &transmittedCount);
+ }
+
+ LOG3(
+ ("Http2StreamBase::TransmitFrame for regular session=%p "
+ "stream=%p result %" PRIx32 " len=%d",
+ session.get(), this, static_cast<uint32_t>(rv), transmittedCount));
+
+ MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
+ "inconsistent stream commitment result");
+
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_ASSERT(transmittedCount == mTxStreamFrameSize,
+ "inconsistent stream commitment count");
+
+ Http2Session::LogIO(session, this, "Writing from Transaction Buffer", buf,
+ transmittedCount);
+
+ *countUsed += mTxStreamFrameSize;
+ }
+
+ if (!mAttempting0RTT) {
+ session->FlushOutputQueue();
+ }
+
+ // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
+ UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
+
+ mTxInlineFrameUsed = 0;
+ mTxStreamFrameSize = 0;
+
+ return NS_OK;
+}
+
+void Http2StreamBase::ChangeState(enum upstreamStateType newState) {
+ LOG3(("Http2StreamBase::ChangeState() %p from %X to %X", this, mUpstreamState,
+ newState));
+ mUpstreamState = newState;
+}
+
+void Http2StreamBase::GenerateDataFrameHeader(uint32_t dataLength,
+ bool lastFrame) {
+ LOG3(("Http2StreamBase::GenerateDataFrameHeader %p len=%d last=%d", this,
+ dataLength, lastFrame));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty");
+ MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty");
+
+ uint8_t frameFlags = 0;
+ if (lastFrame) {
+ frameFlags |= Http2Session::kFlag_END_STREAM;
+ if (dataLength) SetSentFin(true);
+ }
+
+ RefPtr<Http2Session> session = Session();
+ session->CreateFrameHeader(mTxInlineFrame.get(), dataLength,
+ Http2Session::FRAME_TYPE_DATA, frameFlags,
+ mStreamID);
+
+ mTxInlineFrameUsed = Http2Session::kFrameHeaderBytes;
+ mTxStreamFrameSize = dataLength;
+}
+
+// ConvertResponseHeaders is used to convert the response headers
+// into HTTP/1 format and report some telemetry
+nsresult Http2StreamBase::ConvertResponseHeaders(
+ Http2Decompressor* decompressor, nsACString& aHeadersIn,
+ nsACString& aHeadersOut, int32_t& httpResponseCode) {
+ nsresult rv = decompressor->DecodeHeaderBlock(
+ reinterpret_cast<const uint8_t*>(aHeadersIn.BeginReading()),
+ aHeadersIn.Length(), aHeadersOut, false);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2StreamBase::ConvertResponseHeaders %p decode Error\n", this));
+ return rv;
+ }
+
+ nsAutoCString statusString;
+ decompressor->GetStatus(statusString);
+ if (statusString.IsEmpty()) {
+ LOG3(("Http2StreamBase::ConvertResponseHeaders %p Error - no status\n",
+ this));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult errcode;
+ httpResponseCode = statusString.ToInteger(&errcode);
+
+ // Ensure the :status is just an HTTP status code
+ // https://tools.ietf.org/html/rfc7540#section-8.1.2.4
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1352146
+ nsAutoCString parsedStatusString;
+ parsedStatusString.AppendInt(httpResponseCode);
+ if (!parsedStatusString.Equals(statusString)) {
+ LOG3(
+ ("Http2StreamBase::ConvertResposeHeaders %p status %s is not just a "
+ "code",
+ this, statusString.BeginReading()));
+ // Results in stream reset with PROTOCOL_ERROR
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ LOG3(("Http2StreamBase::ConvertResponseHeaders %p response code %d\n", this,
+ httpResponseCode));
+
+ if (httpResponseCode == 421) {
+ // Origin Frame requires 421 to remove this origin from the origin set
+ RefPtr<Http2Session> session = Session();
+ session->Received421(ConnectionInfo());
+ }
+
+ if (aHeadersIn.Length() && aHeadersOut.Length()) {
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length());
+ uint32_t ratio = aHeadersIn.Length() * 100 / aHeadersOut.Length();
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
+ }
+
+ // The decoding went ok. Now we can customize and clean up.
+
+ aHeadersIn.Truncate();
+ aHeadersOut.AppendLiteral("X-Firefox-Spdy: h2");
+ aHeadersOut.AppendLiteral("\r\n\r\n");
+ LOG(("decoded response headers are:\n%s", aHeadersOut.BeginReading()));
+ HandleResponseHeaders(aHeadersOut, httpResponseCode);
+
+ return NS_OK;
+}
+
+nsresult Http2StreamBase::ConvertResponseTrailers(
+ Http2Decompressor* decompressor, nsACString& aTrailersIn) {
+ LOG3(("Http2StreamBase::ConvertResponseTrailers %p", this));
+ nsAutoCString flatTrailers;
+
+ nsresult rv = decompressor->DecodeHeaderBlock(
+ reinterpret_cast<const uint8_t*>(aTrailersIn.BeginReading()),
+ aTrailersIn.Length(), flatTrailers, false);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2StreamBase::ConvertResponseTrailers %p decode Error", this));
+ return rv;
+ }
+
+ nsHttpTransaction* trans = HttpTransaction();
+ if (trans) {
+ trans->SetHttpTrailers(flatTrailers);
+ } else {
+ LOG3(("Http2StreamBase::ConvertResponseTrailers %p no trans", this));
+ }
+
+ return NS_OK;
+}
+
+void Http2StreamBase::SetResponseIsComplete() {
+ nsHttpTransaction* trans = HttpTransaction();
+ if (trans) {
+ trans->SetResponseIsComplete();
+ }
+}
+
+void Http2StreamBase::SetAllHeadersReceived() {
+ if (mAllHeadersReceived) {
+ return;
+ }
+
+ if (mState == RESERVED_BY_REMOTE) {
+ // pushed streams needs to wait until headers have
+ // arrived to open up their window
+ LOG3(
+ ("Http2StreamBase::SetAllHeadersReceived %p state OPEN from reserved\n",
+ this));
+ mState = OPEN;
+ AdjustInitialWindow();
+ }
+
+ mAllHeadersReceived = 1;
+}
+
+bool Http2StreamBase::AllowFlowControlledWrite() {
+ RefPtr<Http2Session> session = Session();
+ return (session->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0);
+}
+
+void Http2StreamBase::UpdateServerReceiveWindow(int32_t delta) {
+ mServerReceiveWindow += delta;
+
+ if (mBlockedOnRwin && AllowFlowControlledWrite()) {
+ LOG3(
+ ("Http2StreamBase::UpdateServerReceived UnPause %p 0x%X "
+ "Open stream window\n",
+ this, mStreamID));
+ RefPtr<Http2Session> session = Session();
+ session->TransactionHasDataToWrite(this);
+ }
+}
+
+void Http2StreamBase::SetPriority(uint32_t newPriority) {
+ int32_t httpPriority = static_cast<int32_t>(newPriority);
+ if (httpPriority > kWorstPriority) {
+ httpPriority = kWorstPriority;
+ } else if (httpPriority < kBestPriority) {
+ httpPriority = kBestPriority;
+ }
+ mPriority = static_cast<uint32_t>(httpPriority);
+ mPriorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) -
+ (httpPriority - kNormalPriority);
+
+ mPriorityDependency = 0; // maybe adjusted later
+}
+
+void Http2StreamBase::SetPriorityDependency(uint32_t newPriority,
+ uint32_t newDependency) {
+ SetPriority(newPriority);
+ mPriorityDependency = newDependency;
+}
+
+static uint32_t GetPriorityDependencyFromTransaction(nsHttpTransaction* trans) {
+ MOZ_ASSERT(trans);
+
+ uint32_t classFlags = trans->GetClassOfService().Flags();
+
+ if (classFlags & nsIClassOfService::UrgentStart) {
+ return Http2Session::kUrgentStartGroupID;
+ }
+
+ if (classFlags & nsIClassOfService::Leader) {
+ return Http2Session::kLeaderGroupID;
+ }
+
+ if (classFlags & nsIClassOfService::Follower) {
+ return Http2Session::kFollowerGroupID;
+ }
+
+ if (classFlags & nsIClassOfService::Speculative) {
+ return Http2Session::kSpeculativeGroupID;
+ }
+
+ if (classFlags & nsIClassOfService::Background) {
+ return Http2Session::kBackgroundGroupID;
+ }
+
+ if (classFlags & nsIClassOfService::Unblocked) {
+ return Http2Session::kOtherGroupID;
+ }
+
+ return Http2Session::kFollowerGroupID; // unmarked followers
+}
+
+void Http2StreamBase::UpdatePriorityDependency() {
+ RefPtr<Http2Session> session = Session();
+ if (!session->UseH2Deps()) {
+ return;
+ }
+
+ nsHttpTransaction* trans = HttpTransaction();
+ if (!trans) {
+ return;
+ }
+
+ // we create 6 fake dependency streams per session,
+ // these streams are never opened with HEADERS. our first opened stream is 0xd
+ // 3 depends 0, weight 200, leader class (kLeaderGroupID)
+ // 5 depends 0, weight 100, other (kOtherGroupID)
+ // 7 depends 0, weight 0, background (kBackgroundGroupID)
+ // 9 depends 7, weight 0, speculative (kSpeculativeGroupID)
+ // b depends 3, weight 0, follower class (kFollowerGroupID)
+ // d depends 0, weight 240, urgent-start class (kUrgentStartGroupID)
+ //
+ // streams for leaders (html, js, css) depend on 3
+ // streams for folowers (images) depend on b
+ // default streams (xhr, async js) depend on 5
+ // explicit bg streams (beacon, etc..) depend on 7
+ // spculative bg streams depend on 9
+ // urgent-start streams depend on d
+
+ mPriorityDependency = GetPriorityDependencyFromTransaction(trans);
+
+ if (gHttpHandler->ActiveTabPriority() &&
+ mTransactionBrowserId != mCurrentBrowserId &&
+ mPriorityDependency != Http2Session::kUrgentStartGroupID) {
+ LOG3(
+ ("Http2StreamBase::UpdatePriorityDependency %p "
+ " depends on background group for trans %p\n",
+ this, trans));
+ mPriorityDependency = Http2Session::kBackgroundGroupID;
+
+ nsHttp::NotifyActiveTabLoadOptimization();
+ }
+
+ LOG1(
+ ("Http2StreamBase::UpdatePriorityDependency %p "
+ "depends on stream 0x%X\n",
+ this, mPriorityDependency));
+}
+
+void Http2StreamBase::CurrentBrowserIdChanged(uint64_t id) {
+ if (!mStreamID) {
+ // For pushed streams, we ignore the direct call from the session and
+ // instead let it come to the internal function from the pushed stream, so
+ // we don't accidentally send two PRIORITY frames for the same stream.
+ return;
+ }
+
+ CurrentBrowserIdChangedInternal(id);
+}
+
+void Http2StreamBase::CurrentBrowserIdChangedInternal(uint64_t id) {
+ MOZ_ASSERT(gHttpHandler->ActiveTabPriority());
+ RefPtr<Http2Session> session = Session();
+ LOG3(
+ ("Http2StreamBase::CurrentBrowserIdChangedInternal "
+ "%p browserId=%" PRIx64 "\n",
+ this, id));
+
+ mCurrentBrowserId = id;
+
+ if (!session->UseH2Deps()) {
+ return;
+ }
+
+ // Urgent start takes an absolute precedence, so don't
+ // change mPriorityDependency here.
+ if (mPriorityDependency == Http2Session::kUrgentStartGroupID) {
+ return;
+ }
+
+ if (mTransactionBrowserId != mCurrentBrowserId) {
+ mPriorityDependency = Http2Session::kBackgroundGroupID;
+ LOG3(
+ ("Http2StreamBase::CurrentBrowserIdChangedInternal %p "
+ "move into background group.\n",
+ this));
+
+ nsHttp::NotifyActiveTabLoadOptimization();
+ } else {
+ nsHttpTransaction* trans = HttpTransaction();
+ if (!trans) {
+ return;
+ }
+
+ mPriorityDependency = GetPriorityDependencyFromTransaction(trans);
+ LOG3(
+ ("Http2StreamBase::CurrentBrowserIdChangedInternal %p "
+ "depends on stream 0x%X\n",
+ this, mPriorityDependency));
+ }
+
+ uint32_t modifyStreamID = GetWireStreamId();
+
+ if (modifyStreamID) {
+ session->SendPriorityFrame(modifyStreamID, mPriorityDependency,
+ mPriorityWeight);
+ }
+}
+
+void Http2StreamBase::SetRecvdFin(bool aStatus) {
+ mRecvdFin = aStatus ? 1 : 0;
+ if (!aStatus) return;
+
+ if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
+ mState = CLOSED_BY_REMOTE;
+ } else if (mState == CLOSED_BY_LOCAL) {
+ mState = CLOSED;
+ }
+}
+
+void Http2StreamBase::SetSentFin(bool aStatus) {
+ mSentFin = aStatus ? 1 : 0;
+ if (!aStatus) return;
+
+ if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
+ mState = CLOSED_BY_LOCAL;
+ } else if (mState == CLOSED_BY_REMOTE) {
+ mState = CLOSED;
+ }
+}
+
+void Http2StreamBase::SetRecvdReset(bool aStatus) {
+ mRecvdReset = aStatus ? 1 : 0;
+ if (!aStatus) return;
+ mState = CLOSED;
+}
+
+void Http2StreamBase::SetSentReset(bool aStatus) {
+ mSentReset = aStatus ? 1 : 0;
+ if (!aStatus) return;
+ mState = CLOSED;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+nsresult Http2StreamBase::OnReadSegment(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ LOG3(("Http2StreamBase::OnReadSegment %p count=%d state=%x", this, count,
+ mUpstreamState));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mSegmentReader) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ uint32_t dataLength;
+ RefPtr<Http2Session> session = Session();
+
+ switch (mUpstreamState) {
+ case GENERATING_HEADERS:
+ // The buffer is the HTTP request stream, including at least part of the
+ // HTTP request header. This state's job is to build a HEADERS frame
+ // from the header information. count is the number of http bytes
+ // available (which may include more than the header), and in countRead we
+ // return the number of those bytes that we consume (i.e. the portion that
+ // are header bytes)
+
+ if (!mRequestHeadersDone) {
+ if (NS_FAILED(rv = ParseHttpRequestHeaders(buf, count, countRead))) {
+ return rv;
+ }
+ }
+
+ if (mRequestHeadersDone && !mOpenGenerated) {
+ if (!session->TryToActivate(this)) {
+ LOG3(
+ ("Http2StreamBase::OnReadSegment %p cannot activate now. "
+ "queued.\n",
+ this));
+ return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ if (NS_FAILED(rv = GenerateOpen())) {
+ return rv;
+ }
+ }
+
+ LOG3(
+ ("ParseHttpRequestHeaders %p used %d of %d. "
+ "requestheadersdone = %d mOpenGenerated = %d\n",
+ this, *countRead, count, mRequestHeadersDone, mOpenGenerated));
+ if (mOpenGenerated) {
+ SetHTTPState(OPEN);
+ AdjustInitialWindow();
+ // This version of TransmitFrame cannot block
+ rv = TransmitFrame(nullptr, nullptr, true);
+ ChangeState(GENERATING_BODY);
+ break;
+ }
+ MOZ_ASSERT(*countRead == count,
+ "Header parsing not complete but unused data");
+ break;
+
+ case GENERATING_BODY:
+ // if there is session flow control and either the stream window is active
+ // and exhaused or the session window is exhausted then suspend
+ if (!AllowFlowControlledWrite()) {
+ *countRead = 0;
+ LOG3(
+ ("Http2StreamBase this=%p, id 0x%X request body suspended because "
+ "remote window is stream=%" PRId64 " session=%" PRId64 ".\n",
+ this, mStreamID, mServerReceiveWindow,
+ session->ServerSessionWindow()));
+ mBlockedOnRwin = true;
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ mBlockedOnRwin = false;
+
+ // The chunk is the smallest of: availableData, configured chunkSize,
+ // stream window, session window, or 14 bit framing limit.
+ // Its amazing we send anything at all.
+ dataLength = std::min(count, mChunkSize);
+
+ if (dataLength > Http2Session::kMaxFrameData) {
+ dataLength = Http2Session::kMaxFrameData;
+ }
+
+ if (dataLength > session->ServerSessionWindow()) {
+ dataLength = static_cast<uint32_t>(session->ServerSessionWindow());
+ }
+
+ if (dataLength > mServerReceiveWindow) {
+ dataLength = static_cast<uint32_t>(mServerReceiveWindow);
+ }
+
+ LOG3(
+ ("Http2StreamBase this=%p id 0x%X send calculation "
+ "avail=%d chunksize=%d stream window=%" PRId64
+ " session window=%" PRId64 " "
+ "max frame=%d USING=%u\n",
+ this, mStreamID, count, mChunkSize, mServerReceiveWindow,
+ session->ServerSessionWindow(), Http2Session::kMaxFrameData,
+ dataLength));
+
+ session->DecrementServerSessionWindow(dataLength);
+ mServerReceiveWindow -= dataLength;
+
+ LOG3(("Http2StreamBase %p id 0x%x request len remaining %" PRId64 ", "
+ "count avail %u, chunk used %u",
+ this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
+ if (!dataLength && mRequestBodyLenRemaining) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ if (dataLength > mRequestBodyLenRemaining) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mRequestBodyLenRemaining -= dataLength;
+ GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
+ ChangeState(SENDING_BODY);
+ [[fallthrough]];
+
+ case SENDING_BODY:
+ MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
+ rv = TransmitFrame(buf, countRead, false);
+ MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
+ "Transmit Frame should be all or nothing");
+
+ LOG3(("TransmitFrame() rv=%" PRIx32 " returning %d data bytes. "
+ "Header is %d Body is %d.",
+ static_cast<uint32_t>(rv), *countRead, mTxInlineFrameUsed,
+ mTxStreamFrameSize));
+
+ // normalize a partial write with a WOULD_BLOCK into just a partial write
+ // as some code will take WOULD_BLOCK to mean an error with nothing
+ // written (e.g. nsHttpTransaction::ReadRequestSegment()
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) rv = NS_OK;
+
+ // If that frame was all sent, look for another one
+ if (!mTxInlineFrameUsed) ChangeState(GENERATING_BODY);
+ break;
+
+ case SENDING_FIN_STREAM:
+ MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment");
+ break;
+
+ case UPSTREAM_COMPLETE: {
+ MOZ_ASSERT(this->GetHttp2Stream() &&
+ this->GetHttp2Stream()->IsReadingFromPushStream());
+ rv = TransmitFrame(nullptr, nullptr, true);
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "Http2StreamBase::OnReadSegment non-write state");
+ break;
+ }
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+nsresult Http2StreamBase::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ LOG3(("Http2StreamBase::OnWriteSegment %p count=%d state=%x 0x%X\n", this,
+ count, mUpstreamState, mStreamID));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mSegmentWriter) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // sometimes we have read data from the network and stored it in a pipe
+ // so that other streams can proceed when the gecko caller is not processing
+ // data events fast enough and flow control hasn't caught up yet. This
+ // gets the stored data out of that pipe
+ if (!mBypassInputBuffer && mSimpleBuffer.Available()) {
+ *countWritten = mSimpleBuffer.Read(buf, count);
+ MOZ_ASSERT(*countWritten);
+ LOG3(
+ ("Http2StreamBase::OnWriteSegment read from flow control buffer %p %x "
+ "%d\n",
+ this, mStreamID, *countWritten));
+ return NS_OK;
+ }
+
+ // read from the network
+ return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
+}
+
+// -----------------------------------------------------------------------------
+// mirror nsAHttpTransaction
+// -----------------------------------------------------------------------------
+
+bool Http2StreamBase::Do0RTT() {
+ MOZ_ASSERT(Transaction());
+ mAttempting0RTT = false;
+ nsAHttpTransaction* trans = Transaction();
+ if (trans) {
+ mAttempting0RTT = trans->Do0RTT();
+ }
+ return mAttempting0RTT;
+}
+
+nsresult Http2StreamBase::Finish0RTT(bool aRestart, bool aAlpnChanged) {
+ MOZ_ASSERT(Transaction());
+ mAttempting0RTT = false;
+ // Instead of passing (aRestart, aAlpnChanged) here, we use aAlpnChanged for
+ // both arguments because as long as the alpn token stayed the same, we can
+ // just reuse what we have in our buffer to send instead of having to have
+ // the transaction rewind and read it all over again. We only need to rewind
+ // the transaction if we're switching to a new protocol, because our buffer
+ // won't get used in that case.
+ // ..
+ // however, we send in the aRestart value to indicate that early data failed
+ // for devtools purposes
+ nsresult rv = NS_OK;
+ nsAHttpTransaction* trans = Transaction();
+ if (trans) {
+ rv = trans->Finish0RTT(aAlpnChanged, aAlpnChanged);
+ if (aRestart) {
+ nsHttpTransaction* hTrans = trans->QueryHttpTransaction();
+ if (hTrans) {
+ hTrans->Refused0RTT();
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult Http2StreamBase::GetOriginAttributes(mozilla::OriginAttributes* oa) {
+ if (!mSocketTransport) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return mSocketTransport->GetOriginAttributes(oa);
+}
+
+nsHttpTransaction* Http2StreamBase::HttpTransaction() {
+ return (Transaction()) ? Transaction()->QueryHttpTransaction() : nullptr;
+}
+
+nsHttpConnectionInfo* Http2StreamBase::ConnectionInfo() {
+ if (Transaction()) {
+ return Transaction()->ConnectionInfo();
+ }
+ return nullptr;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/Http2StreamBase.h b/netwerk/protocol/http/Http2StreamBase.h
new file mode 100644
index 0000000000..80d13f3dcd
--- /dev/null
+++ b/netwerk/protocol/http/Http2StreamBase.h
@@ -0,0 +1,377 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http2StreamBase_h
+#define mozilla_net_Http2StreamBase_h
+
+// HTTP/2 - RFC7540
+// https://www.rfc-editor.org/rfc/rfc7540.txt
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "nsAHttpTransaction.h"
+#include "nsISupportsPriority.h"
+#include "SimpleBuffer.h"
+#include "nsISupportsImpl.h"
+#include "nsIURI.h"
+
+class nsISocketTransport;
+class nsIInputStream;
+class nsIOutputStream;
+
+namespace mozilla {
+class OriginAttributes;
+}
+
+namespace mozilla::net {
+
+class nsStandardURL;
+class Http2Session;
+class Http2Stream;
+class Http2PushedStream;
+class Http2Decompressor;
+
+class Http2StreamBase : public nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter,
+ public SupportsWeakPtr {
+ public:
+ NS_DECL_NSAHTTPSEGMENTREADER
+
+ enum stateType {
+ IDLE,
+ RESERVED_BY_REMOTE,
+ OPEN,
+ CLOSED_BY_LOCAL,
+ CLOSED_BY_REMOTE,
+ CLOSED
+ };
+
+ const static int32_t kNormalPriority = 0x1000;
+ const static int32_t kWorstPriority =
+ kNormalPriority + nsISupportsPriority::PRIORITY_LOWEST;
+ const static int32_t kBestPriority =
+ kNormalPriority + nsISupportsPriority::PRIORITY_HIGHEST;
+
+ Http2StreamBase(uint64_t, Http2Session*, int32_t, uint64_t);
+
+ uint32_t StreamID() { return mStreamID; }
+
+ stateType HTTPState() { return mState; }
+ void SetHTTPState(stateType val) { mState = val; }
+
+ [[nodiscard]] virtual nsresult ReadSegments(nsAHttpSegmentReader*, uint32_t,
+ uint32_t*);
+ [[nodiscard]] virtual nsresult WriteSegments(nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*);
+ virtual bool DeferCleanup(nsresult status);
+
+ const nsCString& Origin() const { return mOrigin; }
+ const nsCString& Host() const { return mHeaderHost; }
+ const nsCString& Path() const { return mHeaderPath; }
+
+ bool RequestBlockedOnRead() {
+ return static_cast<bool>(mRequestBlockedOnRead);
+ }
+
+ bool HasRegisteredID() { return mStreamID != 0; }
+
+ virtual nsAHttpTransaction* Transaction() { return nullptr; }
+ nsHttpTransaction* HttpTransaction();
+ virtual nsIRequestContext* RequestContext() { return nullptr; }
+
+ virtual void CloseStream(nsresult reason) = 0;
+ void SetResponseIsComplete();
+
+ void SetRecvdFin(bool aStatus);
+ bool RecvdFin() { return mRecvdFin; }
+
+ void SetRecvdData(bool aStatus) { mReceivedData = aStatus ? 1 : 0; }
+ bool RecvdData() { return mReceivedData; }
+
+ void SetSentFin(bool aStatus);
+ bool SentFin() { return mSentFin; }
+
+ void SetRecvdReset(bool aStatus);
+ bool RecvdReset() { return mRecvdReset; }
+
+ void SetSentReset(bool aStatus);
+ bool SentReset() { return mSentReset; }
+
+ void SetQueued(bool aStatus) { mQueued = aStatus ? 1 : 0; }
+ bool Queued() { return mQueued; }
+
+ void SetCountAsActive(bool aStatus) { mCountAsActive = aStatus ? 1 : 0; }
+ bool CountAsActive() { return mCountAsActive; }
+
+ void SetAllHeadersReceived();
+ void UnsetAllHeadersReceived() { mAllHeadersReceived = 0; }
+ bool AllHeadersReceived() { return mAllHeadersReceived; }
+
+ void UpdateTransportSendEvents(uint32_t count);
+ void UpdateTransportReadEvents(uint32_t count);
+
+ // NS_ERROR_ABORT terminates stream, other failure terminates session
+ [[nodiscard]] nsresult ConvertResponseHeaders(Http2Decompressor*, nsACString&,
+ nsACString&, int32_t&);
+ [[nodiscard]] nsresult ConvertResponseTrailers(Http2Decompressor*,
+ nsACString&);
+
+ bool AllowFlowControlledWrite();
+ void UpdateServerReceiveWindow(int32_t delta);
+ int64_t ServerReceiveWindow() { return mServerReceiveWindow; }
+
+ void DecrementClientReceiveWindow(uint32_t delta) {
+ mClientReceiveWindow -= delta;
+ mLocalUnacked += delta;
+ }
+
+ void IncrementClientReceiveWindow(uint32_t delta) {
+ mClientReceiveWindow += delta;
+ mLocalUnacked -= delta;
+ }
+
+ uint64_t LocalUnAcked();
+ int64_t ClientReceiveWindow() { return mClientReceiveWindow; }
+
+ bool BlockedOnRwin() { return mBlockedOnRwin; }
+
+ uint32_t Priority() { return mPriority; }
+ uint32_t PriorityDependency() { return mPriorityDependency; }
+ uint8_t PriorityWeight() { return mPriorityWeight; }
+ void SetPriority(uint32_t);
+ void SetPriorityDependency(uint32_t, uint32_t);
+ void UpdatePriorityDependency();
+
+ uint64_t TransactionBrowserId() { return mTransactionBrowserId; }
+
+ // A pull stream has an implicit sink, a pushed stream has a sink
+ // once it is matched to a pull stream.
+ virtual bool HasSink() { return true; }
+
+ already_AddRefed<Http2Session> Session();
+
+ // Mirrors nsAHttpTransaction
+ bool Do0RTT();
+ nsresult Finish0RTT(bool aRestart, bool aAlpnChanged);
+
+ nsresult GetOriginAttributes(mozilla::OriginAttributes* oa);
+
+ virtual void CurrentBrowserIdChanged(uint64_t id);
+ void CurrentBrowserIdChangedInternal(
+ uint64_t id); // For use by pushed streams only
+
+ virtual bool IsTunnel() { return false; }
+
+ virtual uint32_t GetWireStreamId() { return mStreamID; }
+ virtual Http2Stream* GetHttp2Stream() { return nullptr; }
+ virtual Http2PushedStream* GetHttp2PushedStream() { return nullptr; }
+
+ [[nodiscard]] virtual nsresult OnWriteSegment(char*, uint32_t,
+ uint32_t*) override;
+
+ virtual nsHttpConnectionInfo* ConnectionInfo();
+
+ bool DataBuffered() { return mSimpleBuffer.Available(); }
+
+ virtual nsresult Condition() { return NS_OK; }
+
+ virtual void DisableSpdy() {
+ if (Transaction()) {
+ Transaction()->DisableSpdy();
+ }
+ }
+ virtual void ReuseConnectionOnRestartOK(bool aReuse) {
+ if (Transaction()) {
+ Transaction()->ReuseConnectionOnRestartOK(aReuse);
+ }
+ }
+ virtual void MakeNonSticky() {
+ if (Transaction()) {
+ Transaction()->MakeNonSticky();
+ }
+ }
+
+ protected:
+ virtual ~Http2StreamBase();
+
+ virtual void HandleResponseHeaders(nsACString& aHeadersOut,
+ int32_t httpResponseCode) {}
+ virtual nsresult CallToWriteData(uint32_t count, uint32_t* countRead) = 0;
+ virtual nsresult CallToReadData(uint32_t count, uint32_t* countWritten) = 0;
+ virtual bool CloseSendStreamWhenDone() { return true; }
+
+ // These internal states track request generation
+ enum upstreamStateType {
+ GENERATING_HEADERS,
+ GENERATING_BODY,
+ SENDING_BODY,
+ SENDING_FIN_STREAM,
+ UPSTREAM_COMPLETE
+ };
+
+ uint32_t mStreamID{0};
+
+ // The session that this stream is a subset of
+ nsWeakPtr mSession;
+
+ // These are temporary state variables to hold the argument to
+ // Read/WriteSegments so it can be accessed by On(read/write)segment
+ // further up the stack.
+ RefPtr<nsAHttpSegmentReader> mSegmentReader;
+ nsAHttpSegmentWriter* mSegmentWriter{nullptr};
+
+ nsCString mOrigin;
+ nsCString mHeaderHost;
+ nsCString mHeaderScheme;
+ nsCString mHeaderPath;
+
+ // Each stream goes from generating_headers to upstream_complete, perhaps
+ // looping on multiple instances of generating_body and
+ // sending_body for each frame in the upload.
+ enum upstreamStateType mUpstreamState { GENERATING_HEADERS };
+
+ // The HTTP/2 state for the stream from section 5.1
+ enum stateType mState { IDLE };
+
+ // Flag is set when all http request headers have been read ID is not stable
+ uint32_t mRequestHeadersDone : 1;
+
+ // Flag is set when ID is stable and concurrency limits are met
+ uint32_t mOpenGenerated : 1;
+
+ // Flag is set when all http response headers have been read
+ uint32_t mAllHeadersReceived : 1;
+
+ // Flag is set when stream is queued inside the session due to
+ // concurrency limits being exceeded
+ uint32_t mQueued : 1;
+
+ void ChangeState(enum upstreamStateType);
+
+ virtual void AdjustInitialWindow();
+ [[nodiscard]] nsresult TransmitFrame(const char*, uint32_t*,
+ bool forceCommitment);
+
+ // The underlying socket transport object is needed to propogate some events
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+
+ uint8_t mPriorityWeight = 0; // h2 weight
+ uint32_t mPriorityDependency = 0; // h2 stream id this one depends on
+ uint64_t mCurrentBrowserId;
+ uint64_t mTransactionBrowserId{0};
+
+ // The InlineFrame and associated data is used for composing control
+ // frames and data frame headers.
+ UniquePtr<uint8_t[]> mTxInlineFrame;
+ uint32_t mTxInlineFrameSize{0};
+ uint32_t mTxInlineFrameUsed{0};
+
+ uint32_t mPriority = 0; // geckoish weight
+
+ // Buffer for request header compression.
+ nsCString mFlatHttpRequestHeaders;
+
+ // Track the content-length of a request body so that we can
+ // place the fin flag on the last data packet instead of waiting
+ // for a stream closed indication. Relying on stream close results
+ // in an extra 0-length runt packet and seems to have some interop
+ // problems with the google servers. Connect does rely on stream
+ // close by setting this to the max value.
+ int64_t mRequestBodyLenRemaining{0};
+
+ private:
+ friend class mozilla::DefaultDelete<Http2StreamBase>;
+
+ [[nodiscard]] nsresult ParseHttpRequestHeaders(const char*, uint32_t,
+ uint32_t*);
+ [[nodiscard]] nsresult GenerateOpen();
+
+ virtual nsresult GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) = 0;
+
+ void GenerateDataFrameHeader(uint32_t, bool);
+
+ [[nodiscard]] nsresult BufferInput(uint32_t, uint32_t*);
+
+ // The quanta upstream data frames are chopped into
+ uint32_t mChunkSize;
+
+ // Flag is set when the HTTP processor has more data to send
+ // but has blocked in doing so.
+ uint32_t mRequestBlockedOnRead : 1;
+
+ // Flag is set after the response frame bearing the fin bit has
+ // been processed. (i.e. after the server has closed).
+ uint32_t mRecvdFin : 1;
+
+ // Flag is set after 1st DATA frame has been passed to stream
+ uint32_t mReceivedData : 1;
+
+ // Flag is set after RST_STREAM has been received for this stream
+ uint32_t mRecvdReset : 1;
+
+ // Flag is set after RST_STREAM has been generated for this stream
+ uint32_t mSentReset : 1;
+
+ // Flag is set when stream is counted towards MAX_CONCURRENT streams in
+ // session
+ uint32_t mCountAsActive : 1;
+
+ // Flag is set when a FIN has been placed on a data or header frame
+ // (i.e after the client has closed)
+ uint32_t mSentFin : 1;
+
+ // Flag is set after the WAITING_FOR Transport event has been generated
+ uint32_t mSentWaitingFor : 1;
+
+ // Flag is set after TCP send autotuning has been disabled
+ uint32_t mSetTCPSocketBuffer : 1;
+
+ // Flag is set when OnWriteSegment is being called directly from stream
+ // instead of transaction
+ uint32_t mBypassInputBuffer : 1;
+
+ // mTxStreamFrameSize tracks the progress of
+ // transmitting a request body data frame. The data frame itself
+ // is never copied into the spdy layer.
+ uint32_t mTxStreamFrameSize{0};
+
+ // mClientReceiveWindow, mServerReceiveWindow, and mLocalUnacked are for flow
+ // control. *window are signed because the race conditions in asynchronous
+ // SETTINGS messages can force them temporarily negative.
+
+ // mClientReceiveWindow is how much data the server will send without getting
+ // a
+ // window update
+ int64_t mClientReceiveWindow;
+
+ // mServerReceiveWindow is how much data the client is allowed to send without
+ // getting a window update
+ int64_t mServerReceiveWindow;
+
+ // LocalUnacked is the number of bytes received by the client but not
+ // yet reflected in a window update. Sending that update will increment
+ // ClientReceiveWindow
+ uint64_t mLocalUnacked{0};
+
+ // True when sending is suspended becuase the server receive window is
+ // <= 0
+ bool mBlockedOnRwin{false};
+
+ // For Progress Events
+ uint64_t mTotalSent{0};
+ uint64_t mTotalRead{0};
+
+ // Used to store stream data when the transaction channel cannot keep up
+ // and flow control has not yet kicked in.
+ SimpleBuffer mSimpleBuffer;
+
+ bool mAttempting0RTT{false};
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_Http2StreamBase_h
diff --git a/netwerk/protocol/http/Http2StreamTunnel.cpp b/netwerk/protocol/http/Http2StreamTunnel.cpp
new file mode 100644
index 0000000000..4e0f05d58b
--- /dev/null
+++ b/netwerk/protocol/http/Http2StreamTunnel.cpp
@@ -0,0 +1,764 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "nsHttpHandler.h"
+#include "Http2StreamTunnel.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsQueryObject.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla::net {
+
+bool Http2StreamTunnel::DispatchRelease() {
+ if (OnSocketThread()) {
+ return false;
+ }
+
+ gSocketTransportService->Dispatch(
+ NewNonOwningRunnableMethod("net::Http2StreamTunnel::Release", this,
+ &Http2StreamTunnel::Release),
+ NS_DISPATCH_NORMAL);
+
+ return true;
+}
+
+NS_IMPL_ADDREF(Http2StreamTunnel)
+NS_IMETHODIMP_(MozExternalRefCountType)
+Http2StreamTunnel::Release() {
+ nsrefcnt count = mRefCnt - 1;
+ if (DispatchRelease()) {
+ // Redispatched to the socket thread.
+ return count;
+ }
+
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "Http2StreamTunnel");
+
+ if (0 == count) {
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(Http2StreamTunnel)
+ NS_INTERFACE_MAP_ENTRY(nsITransport)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(Http2StreamTunnel)
+ NS_INTERFACE_MAP_ENTRY(nsITransport)
+ NS_INTERFACE_MAP_ENTRY(nsISocketTransport)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_INHERITING(Http2StreamTunnel)
+
+Http2StreamTunnel::Http2StreamTunnel(Http2Session* session, int32_t priority,
+ uint64_t bcId,
+ nsHttpConnectionInfo* aConnectionInfo)
+ : Http2StreamBase(0, session, priority, bcId),
+ mConnectionInfo(aConnectionInfo) {}
+
+Http2StreamTunnel::~Http2StreamTunnel() { ClearTransactionsBlockedOnTunnel(); }
+
+void Http2StreamTunnel::HandleResponseHeaders(nsACString& aHeadersOut,
+ int32_t httpResponseCode) {}
+
+// TODO We do not need this. Fix in bug 1772212.
+void Http2StreamTunnel::ClearTransactionsBlockedOnTunnel() {
+ nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnectionInfo);
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2StreamTunnel::ClearTransactionsBlockedOnTunnel %p\n"
+ " ProcessPendingQ failed: %08x\n",
+ this, static_cast<uint32_t>(rv)));
+ }
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetKeepaliveEnabled(bool aKeepaliveEnabled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetKeepaliveVals(int32_t keepaliveIdleTime,
+ int32_t keepaliveRetryInterval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetSecurityCallbacks(
+ nsIInterfaceRequestor** aSecurityCallbacks) {
+ return mSocketTransport->GetSecurityCallbacks(aSecurityCallbacks);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetSecurityCallbacks(
+ nsIInterfaceRequestor* aSecurityCallbacks) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIInputStream** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIOutputStream** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void Http2StreamTunnel::CloseStream(nsresult aReason) {
+ LOG(("Http2StreamTunnel::CloseStream this=%p", this));
+ RefPtr<Http2Session> session = Session();
+ if (NS_SUCCEEDED(mCondition)) {
+ mSession = nullptr;
+ // Let the session pickup that the stream has been closed.
+ mCondition = aReason;
+ if (NS_SUCCEEDED(aReason)) {
+ aReason = NS_BASE_STREAM_CLOSED;
+ }
+ mOutput->OnSocketReady(aReason);
+ mInput->OnSocketReady(aReason);
+ }
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::Close(nsresult aReason) {
+ LOG(("Http2StreamTunnel::Close this=%p", this));
+ RefPtr<Http2Session> session = Session();
+ if (NS_SUCCEEDED(mCondition)) {
+ mSession = nullptr;
+ if (NS_SUCCEEDED(aReason)) {
+ aReason = NS_BASE_STREAM_CLOSED;
+ }
+ mOutput->CloseWithStatus(aReason);
+ mInput->CloseWithStatus(aReason);
+ // Let the session pickup that the stream has been closed.
+ mCondition = aReason;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetEventSink(nsITransportEventSink* aSink,
+ nsIEventTarget* aEventTarget) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::Bind(NetAddr* aLocalAddr) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetEchConfigUsed(bool* aEchConfigUsed) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetEchConfig(const nsACString& aEchConfig) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::ResolvedByTRR(bool* aResolvedByTRR) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP Http2StreamTunnel::GetEffectiveTRRMode(
+ nsIRequest::TRRMode* aEffectiveTRRMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP Http2StreamTunnel::GetTrrSkipReason(
+ nsITRRSkipReason::value* aTrrSkipReason) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::IsAlive(bool* aAlive) {
+ RefPtr<Http2Session> session = Session();
+ if (mSocketTransport && session) {
+ return mSocketTransport->IsAlive(aAlive);
+ }
+ *aAlive = false;
+ return NS_OK;
+}
+
+#define FWD_TS_T_PTR(fx, ts) \
+ NS_IMETHODIMP \
+ Http2StreamTunnel::fx(ts* arg) { return mSocketTransport->fx(arg); }
+
+#define FWD_TS_T_ADDREF(fx, ts) \
+ NS_IMETHODIMP \
+ Http2StreamTunnel::fx(ts** arg) { return mSocketTransport->fx(arg); }
+
+#define FWD_TS_T(fx, ts) \
+ NS_IMETHODIMP \
+ Http2StreamTunnel::fx(ts arg) { return mSocketTransport->fx(arg); }
+
+FWD_TS_T_PTR(GetKeepaliveEnabled, bool);
+FWD_TS_T_PTR(GetSendBufferSize, uint32_t);
+FWD_TS_T(SetSendBufferSize, uint32_t);
+FWD_TS_T_PTR(GetPort, int32_t);
+FWD_TS_T_PTR(GetPeerAddr, mozilla::net::NetAddr);
+FWD_TS_T_PTR(GetSelfAddr, mozilla::net::NetAddr);
+FWD_TS_T_ADDREF(GetScriptablePeerAddr, nsINetAddr);
+FWD_TS_T_ADDREF(GetScriptableSelfAddr, nsINetAddr);
+FWD_TS_T_ADDREF(GetTlsSocketControl, nsITLSSocketControl);
+FWD_TS_T_PTR(GetConnectionFlags, uint32_t);
+FWD_TS_T(SetConnectionFlags, uint32_t);
+FWD_TS_T(SetIsPrivate, bool);
+FWD_TS_T_PTR(GetTlsFlags, uint32_t);
+FWD_TS_T(SetTlsFlags, uint32_t);
+FWD_TS_T_PTR(GetRecvBufferSize, uint32_t);
+FWD_TS_T(SetRecvBufferSize, uint32_t);
+FWD_TS_T_PTR(GetResetIPFamilyPreference, bool);
+
+nsresult Http2StreamTunnel::GetOriginAttributes(
+ mozilla::OriginAttributes* aOriginAttributes) {
+ return mSocketTransport->GetOriginAttributes(aOriginAttributes);
+}
+
+nsresult Http2StreamTunnel::SetOriginAttributes(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ return mSocketTransport->SetOriginAttributes(aOriginAttributes);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
+ return mSocketTransport->GetScriptableOriginAttributes(aCx,
+ aOriginAttributes);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetScriptableOriginAttributes(
+ JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
+ return mSocketTransport->SetScriptableOriginAttributes(aCx,
+ aOriginAttributes);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetHost(nsACString& aHost) {
+ return mSocketTransport->GetHost(aHost);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetTimeout(uint32_t aType, uint32_t* _retval) {
+ return mSocketTransport->GetTimeout(aType, _retval);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetTimeout(uint32_t aType, uint32_t aValue) {
+ return mSocketTransport->SetTimeout(aType, aValue);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetReuseAddrPort(bool aReuseAddrPort) {
+ return mSocketTransport->SetReuseAddrPort(aReuseAddrPort);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetLinger(bool aPolarity, int16_t aTimeout) {
+ return mSocketTransport->SetLinger(aPolarity, aTimeout);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetQoSBits(uint8_t* aQoSBits) {
+ return mSocketTransport->GetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::SetQoSBits(uint8_t aQoSBits) {
+ return mSocketTransport->SetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetRetryDnsIfPossible(bool* aRetry) {
+ return mSocketTransport->GetRetryDnsIfPossible(aRetry);
+}
+
+NS_IMETHODIMP
+Http2StreamTunnel::GetStatus(nsresult* aStatus) {
+ return mSocketTransport->GetStatus(aStatus);
+}
+
+already_AddRefed<nsHttpConnection> Http2StreamTunnel::CreateHttpConnection(
+ nsAHttpTransaction* httpTransaction, nsIInterfaceRequestor* aCallbacks,
+ PRIntervalTime aRtt, bool aIsWebSocket) {
+ mInput = new InputStreamTunnel(this);
+ mOutput = new OutputStreamTunnel(this);
+ RefPtr<nsHttpConnection> conn = new nsHttpConnection();
+
+ conn->SetTransactionCaps(httpTransaction->Caps());
+ nsresult rv =
+ conn->Init(httpTransaction->ConnectionInfo(),
+ gHttpHandler->ConnMgr()->MaxRequestDelay(), this, mInput,
+ mOutput, true, NS_OK, aCallbacks, aRtt, aIsWebSocket);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ mTransaction = httpTransaction;
+ return conn.forget();
+}
+
+nsresult Http2StreamTunnel::CallToReadData(uint32_t count,
+ uint32_t* countRead) {
+ LOG(("Http2StreamTunnel::CallToReadData this=%p", this));
+ return mOutput->OnSocketReady(NS_OK);
+}
+
+nsresult Http2StreamTunnel::CallToWriteData(uint32_t count,
+ uint32_t* countWritten) {
+ LOG(("Http2StreamTunnel::CallToWriteData this=%p", this));
+ if (!mInput->HasCallback()) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ return mInput->OnSocketReady(NS_OK);
+}
+
+nsresult Http2StreamTunnel::GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) {
+ nsAutoCString authorityHeader;
+ authorityHeader = mConnectionInfo->GetOrigin();
+ authorityHeader.Append(':');
+ authorityHeader.AppendInt(mConnectionInfo->OriginPort());
+
+ RefPtr<Http2Session> session = Session();
+
+ LOG3(("Http2StreamTunnel %p Stream ID 0x%X [session=%p] for %s\n", this,
+ mStreamID, session.get(), authorityHeader.get()));
+
+ mRequestBodyLenRemaining = 0x0fffffffffffffffULL;
+
+ nsresult rv = session->Compressor()->EncodeHeaderBlock(
+ mFlatHttpRequestHeaders, "CONNECT"_ns, EmptyCString(), authorityHeader,
+ EmptyCString(), EmptyCString(), true, aCompressedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The size of the input headers is approximate
+ uint32_t ratio =
+ aCompressedData.Length() * 100 /
+ (11 + authorityHeader.Length() + mFlatHttpRequestHeaders.Length());
+
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
+
+ return NS_OK;
+}
+
+OutputStreamTunnel::OutputStreamTunnel(Http2StreamTunnel* aStream) {
+ mWeakStream = do_GetWeakReference(aStream);
+}
+
+OutputStreamTunnel::~OutputStreamTunnel() {
+ NS_ProxyRelease("OutputStreamTunnel::~OutputStreamTunnel",
+ gSocketTransportService, mWeakStream.forget());
+}
+
+nsresult OutputStreamTunnel::OnSocketReady(nsresult condition) {
+ LOG(("OutputStreamTunnel::OnSocketReady [this=%p cond=%" PRIx32
+ " callback=%p]\n",
+ this, static_cast<uint32_t>(condition), mCallback.get()));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIOutputStreamCallback> callback;
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition)) {
+ mCondition = condition;
+ }
+ callback = std::move(mCallback);
+
+ nsresult rv = NS_OK;
+ if (callback) {
+ rv = callback->OnOutputStreamReady(this);
+ MaybeSetRequestDone(callback);
+ }
+
+ return rv;
+}
+
+void OutputStreamTunnel::MaybeSetRequestDone(
+ nsIOutputStreamCallback* aCallback) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(aCallback);
+ if (!conn) {
+ return;
+ }
+
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (conn->RequestDone()) {
+ tunnel->SetRequestDone();
+ }
+}
+
+NS_IMPL_ISUPPORTS(OutputStreamTunnel, nsIOutputStream, nsIAsyncOutputStream)
+
+NS_IMETHODIMP
+OutputStreamTunnel::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
+
+NS_IMETHODIMP
+OutputStreamTunnel::Flush() { return NS_OK; }
+
+NS_IMETHODIMP
+OutputStreamTunnel::StreamStatus() { return mCondition; }
+
+NS_IMETHODIMP
+OutputStreamTunnel::Write(const char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ LOG(("OutputStreamTunnel::Write [this=%p count=%u]\n", this, count));
+
+ *countWritten = 0;
+ if (NS_FAILED(mCondition)) {
+ return mCondition;
+ }
+
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return tunnel->OnReadSegment(buf, count, countWritten);
+}
+
+NS_IMETHODIMP
+OutputStreamTunnel::WriteSegments(nsReadSegmentFun reader, void* closure,
+ uint32_t count, uint32_t* countRead) {
+ // stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamTunnel::WriteFrom(nsIInputStream* stream, uint32_t count,
+ uint32_t* countRead) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamTunnel::IsNonBlocking(bool* nonblocking) {
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OutputStreamTunnel::CloseWithStatus(nsresult reason) {
+ LOG(("OutputStreamTunnel::CloseWithStatus [this=%p reason=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(reason)));
+ mCondition = reason;
+
+ RefPtr<Http2StreamTunnel> tunnel = do_QueryReferent(mWeakStream);
+ mWeakStream = nullptr;
+ if (!tunnel) {
+ return NS_OK;
+ }
+ RefPtr<Http2Session> session = tunnel->Session();
+ if (!session) {
+ return NS_OK;
+ }
+ session->CleanupStream(tunnel, reason, Http2Session::CANCEL_ERROR);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OutputStreamTunnel::AsyncWait(nsIOutputStreamCallback* callback, uint32_t flags,
+ uint32_t amount, nsIEventTarget* target) {
+ LOG(("OutputStreamTunnel::AsyncWait [this=%p]\n", this));
+
+ // The following parametr are not used:
+ MOZ_ASSERT(!flags);
+ MOZ_ASSERT(!amount);
+ Unused << target;
+
+ RefPtr<OutputStreamTunnel> self(this);
+ if (NS_FAILED(mCondition)) {
+ Unused << NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "OutputStreamTunnel::CallOnSocketReady",
+ [self{std::move(self)}]() { self->OnSocketReady(NS_OK); }));
+ } else if (callback) {
+ // Inform the proxy connection that the inner connetion wants to
+ // read data.
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ RefPtr<Http2Session> session;
+ rv = GetSession(getter_AddRefs(session));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ session->TransactionHasDataToWrite(tunnel);
+ }
+
+ mCallback = callback;
+ return NS_OK;
+}
+
+InputStreamTunnel::InputStreamTunnel(Http2StreamTunnel* aStream) {
+ mWeakStream = do_GetWeakReference(aStream);
+}
+
+InputStreamTunnel::~InputStreamTunnel() {
+ NS_ProxyRelease("InputStreamTunnel::~InputStreamTunnel",
+ gSocketTransportService, mWeakStream.forget());
+}
+
+nsresult InputStreamTunnel::OnSocketReady(nsresult condition) {
+ LOG(("InputStreamTunnel::OnSocketReady [this=%p cond=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(condition)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIInputStreamCallback> callback;
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition)) {
+ mCondition = condition;
+ }
+ callback = std::move(mCallback);
+
+ return callback ? callback->OnInputStreamReady(this) : NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(InputStreamTunnel, nsIInputStream, nsIAsyncInputStream)
+
+NS_IMETHODIMP
+InputStreamTunnel::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
+
+NS_IMETHODIMP
+InputStreamTunnel::Available(uint64_t* avail) {
+ LOG(("InputStreamTunnel::Available [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition)) {
+ return mCondition;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+InputStreamTunnel::StreamStatus() {
+ LOG(("InputStreamTunnel::StreamStatus [this=%p]\n", this));
+
+ return mCondition;
+}
+
+NS_IMETHODIMP
+InputStreamTunnel::Read(char* buf, uint32_t count, uint32_t* countRead) {
+ LOG(("InputStreamTunnel::Read [this=%p count=%u]\n", this, count));
+
+ *countRead = 0;
+
+ if (NS_FAILED(mCondition)) {
+ return mCondition;
+ }
+
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return tunnel->OnWriteSegment(buf, count, countRead);
+}
+
+NS_IMETHODIMP
+InputStreamTunnel::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* countRead) {
+ // socket stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InputStreamTunnel::IsNonBlocking(bool* nonblocking) {
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamTunnel::CloseWithStatus(nsresult reason) {
+ LOG(("InputStreamTunnel::CloseWithStatus [this=%p reason=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(reason)));
+ mCondition = reason;
+
+ RefPtr<Http2StreamTunnel> tunnel = do_QueryReferent(mWeakStream);
+ mWeakStream = nullptr;
+ if (!tunnel) {
+ return NS_OK;
+ }
+ RefPtr<Http2Session> session = tunnel->Session();
+ if (!session) {
+ return NS_OK;
+ }
+ session->CleanupStream(tunnel, reason, Http2Session::CANCEL_ERROR);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamTunnel::AsyncWait(nsIInputStreamCallback* callback, uint32_t flags,
+ uint32_t amount, nsIEventTarget* target) {
+ LOG(("InputStreamTunnel::AsyncWait [this=%p mCondition=%x]\n", this,
+ static_cast<uint32_t>(mCondition)));
+
+ // The following parametr are not used:
+ MOZ_ASSERT(!flags);
+ MOZ_ASSERT(!amount);
+ Unused << target;
+
+ RefPtr<InputStreamTunnel> self(this);
+ if (NS_FAILED(mCondition)) {
+ Unused << NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "InputStreamTunnel::CallOnSocketReady",
+ [self{std::move(self)}]() { self->OnSocketReady(NS_OK); }));
+ } else if (callback) {
+ // Inform the proxy connection that the inner connetion wants to
+ // read data.
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ RefPtr<Http2Session> session;
+ rv = GetSession(getter_AddRefs(session));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (tunnel->DataBuffered()) {
+ session->TransactionHasDataToRecv(tunnel);
+ }
+ }
+
+ mCallback = callback;
+ return NS_OK;
+}
+
+nsresult OutputStreamTunnel::GetStream(Http2StreamTunnel** aStream) {
+ RefPtr<Http2StreamTunnel> tunnel = do_QueryReferent(mWeakStream);
+ MOZ_ASSERT(tunnel);
+ if (!tunnel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ tunnel.forget(aStream);
+ return NS_OK;
+}
+
+nsresult OutputStreamTunnel::GetSession(Http2Session** aSession) {
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ RefPtr<Http2Session> session = tunnel->Session();
+ MOZ_ASSERT(session);
+ if (!session) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ session.forget(aSession);
+ return NS_OK;
+}
+
+nsresult InputStreamTunnel::GetStream(Http2StreamTunnel** aStream) {
+ RefPtr<Http2StreamTunnel> tunnel = do_QueryReferent(mWeakStream);
+ MOZ_ASSERT(tunnel);
+ if (!tunnel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ tunnel.forget(aStream);
+ return NS_OK;
+}
+
+nsresult InputStreamTunnel::GetSession(Http2Session** aSession) {
+ RefPtr<Http2StreamTunnel> tunnel;
+ nsresult rv = GetStream(getter_AddRefs(tunnel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ RefPtr<Http2Session> session = tunnel->Session();
+ if (!session) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ session.forget(aSession);
+ return NS_OK;
+}
+
+Http2StreamWebSocket::Http2StreamWebSocket(
+ Http2Session* session, int32_t priority, uint64_t bcId,
+ nsHttpConnectionInfo* aConnectionInfo)
+ : Http2StreamTunnel(session, priority, bcId, aConnectionInfo) {
+ LOG(("Http2StreamWebSocket ctor:%p", this));
+}
+
+Http2StreamWebSocket::~Http2StreamWebSocket() {
+ LOG(("Http2StreamWebSocket dtor:%p", this));
+}
+
+nsresult Http2StreamWebSocket::GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) {
+ nsHttpRequestHead* head = mTransaction->RequestHead();
+
+ nsAutoCString authorityHeader;
+ nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<Http2Session> session = Session();
+ LOG3(("Http2StreamWebSocket %p Stream ID 0x%X [session=%p] for %s\n", this,
+ mStreamID, session.get(), authorityHeader.get()));
+
+ nsDependentCString scheme(head->IsHTTPS() ? "https" : "http");
+ nsAutoCString path;
+ head->Path(path);
+
+ rv = session->Compressor()->EncodeHeaderBlock(
+ mFlatHttpRequestHeaders, "CONNECT"_ns, path, authorityHeader, scheme,
+ "websocket"_ns, false, aCompressedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRequestBodyLenRemaining = 0x0fffffffffffffffULL;
+
+ // The size of the input headers is approximate
+ uint32_t ratio =
+ aCompressedData.Length() * 100 /
+ (11 + authorityHeader.Length() + mFlatHttpRequestHeaders.Length());
+
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
+ return NS_OK;
+}
+
+void Http2StreamWebSocket::CloseStream(nsresult aReason) {
+ LOG(("Http2StreamWebSocket::CloseStream this=%p aReason=%x", this,
+ static_cast<uint32_t>(aReason)));
+ if (mTransaction) {
+ mTransaction->Close(aReason);
+ mTransaction = nullptr;
+ }
+ Http2StreamTunnel::CloseStream(aReason);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/Http2StreamTunnel.h b/netwerk/protocol/http/Http2StreamTunnel.h
new file mode 100644
index 0000000000..78f0300f4c
--- /dev/null
+++ b/netwerk/protocol/http/Http2StreamTunnel.h
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http2StreamTunnel_h
+#define mozilla_net_Http2StreamTunnel_h
+
+#include "Http2StreamBase.h"
+#include "nsHttpConnection.h"
+#include "nsWeakReference.h"
+
+namespace mozilla::net {
+
+class OutputStreamTunnel;
+class InputStreamTunnel;
+
+// c881f764-a183-45cb-9dec-d9872b2f47b2
+#define NS_HTTP2STREAMTUNNEL_IID \
+ { \
+ 0xc881f764, 0xa183, 0x45cb, { \
+ 0x9d, 0xec, 0xd9, 0x87, 0x2b, 0x2f, 0x47, 0xb2 \
+ } \
+ }
+
+class Http2StreamTunnel : public Http2StreamBase,
+ public nsISocketTransport,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTP2STREAMTUNNEL_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSISOCKETTRANSPORT
+
+ Http2StreamTunnel(Http2Session* session, int32_t priority, uint64_t bcId,
+ nsHttpConnectionInfo* aConnectionInfo);
+ bool IsTunnel() override { return true; }
+
+ already_AddRefed<nsHttpConnection> CreateHttpConnection(
+ nsAHttpTransaction* httpTransaction, nsIInterfaceRequestor* aCallbacks,
+ PRIntervalTime aRtt, bool aIsWebSocket);
+
+ nsHttpConnectionInfo* ConnectionInfo() override { return mConnectionInfo; }
+
+ void SetRequestDone() { mSendClosed = true; }
+ nsresult Condition() override { return mCondition; }
+ void CloseStream(nsresult reason) override;
+ void DisableSpdy() override {
+ if (mTransaction) {
+ mTransaction->DisableHttp2ForProxy();
+ }
+ }
+ void ReuseConnectionOnRestartOK(bool aReuse) override {
+ // Do nothing here. We'll never reuse the connection.
+ }
+ void MakeNonSticky() override {}
+
+ protected:
+ ~Http2StreamTunnel();
+ nsresult CallToReadData(uint32_t count, uint32_t* countRead) override;
+ nsresult CallToWriteData(uint32_t count, uint32_t* countWritten) override;
+ void HandleResponseHeaders(nsACString& aHeadersOut,
+ int32_t httpResponseCode) override;
+ nsresult GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) override;
+ bool CloseSendStreamWhenDone() override { return mSendClosed; }
+
+ RefPtr<nsAHttpTransaction> mTransaction;
+
+ void ClearTransactionsBlockedOnTunnel();
+ bool DispatchRelease();
+
+ RefPtr<OutputStreamTunnel> mOutput;
+ RefPtr<InputStreamTunnel> mInput;
+ RefPtr<nsHttpConnectionInfo> mConnectionInfo;
+ bool mSendClosed{false};
+ nsresult mCondition{NS_OK};
+};
+
+// f9d10060-f5d4-443e-ba59-f84ea975c5f0
+#define NS_OUTPUTSTREAMTUNNEL_IID \
+ { \
+ 0xf9d10060, 0xf5d4, 0x443e, { \
+ 0xba, 0x59, 0xf8, 0x4e, 0xa9, 0x75, 0xc5, 0xf0 \
+ } \
+ }
+
+class OutputStreamTunnel : public nsIAsyncOutputStream {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_OUTPUTSTREAMTUNNEL_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ explicit OutputStreamTunnel(Http2StreamTunnel* aStream);
+
+ nsresult OnSocketReady(nsresult condition);
+ void MaybeSetRequestDone(nsIOutputStreamCallback* aCallback);
+
+ private:
+ virtual ~OutputStreamTunnel();
+
+ nsresult GetStream(Http2StreamTunnel** aStream);
+ nsresult GetSession(Http2Session** aSession);
+
+ nsWeakPtr mWeakStream;
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ nsresult mCondition{NS_OK};
+};
+
+class InputStreamTunnel : public nsIAsyncInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit InputStreamTunnel(Http2StreamTunnel* aStream);
+
+ nsresult OnSocketReady(nsresult condition);
+ bool HasCallback() { return !!mCallback; }
+
+ private:
+ virtual ~InputStreamTunnel();
+
+ nsresult GetStream(Http2StreamTunnel** aStream);
+ nsresult GetSession(Http2Session** aSession);
+
+ nsWeakPtr mWeakStream;
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsresult mCondition{NS_OK};
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Http2StreamTunnel, NS_HTTP2STREAMTUNNEL_IID)
+NS_DEFINE_STATIC_IID_ACCESSOR(OutputStreamTunnel, NS_OUTPUTSTREAMTUNNEL_IID)
+
+class Http2StreamWebSocket : public Http2StreamTunnel {
+ public:
+ Http2StreamWebSocket(Http2Session* session, int32_t priority, uint64_t bcId,
+ nsHttpConnectionInfo* aConnectionInfo);
+ void CloseStream(nsresult reason) override;
+
+ protected:
+ virtual ~Http2StreamWebSocket();
+ nsresult GenerateHeaders(nsCString& aCompressedData,
+ uint8_t& firstFrameFlags) override;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_Http2StreamTunnel_h
diff --git a/netwerk/protocol/http/Http3Session.cpp b/netwerk/protocol/http/Http3Session.cpp
new file mode 100644
index 0000000000..0369da94ac
--- /dev/null
+++ b/netwerk/protocol/http/Http3Session.cpp
@@ -0,0 +1,2522 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ASpdySession.h" // because of SoftStreamError()
+#include "Http3Session.h"
+#include "Http3Stream.h"
+#include "Http3StreamBase.h"
+#include "Http3WebTransportSession.h"
+#include "Http3WebTransportStream.h"
+#include "HttpConnectionUDP.h"
+#include "HttpLog.h"
+#include "QuicSocketControl.h"
+#include "SSLServerCertVerification.h"
+#include "SSLTokensCache.h"
+#include "ScopedNSSTypes.h"
+#include "mozilla/RandomNum.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/net/DNS.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsIOService.h"
+#include "nsITLSSocketControl.h"
+#include "nsNetAddr.h"
+#include "nsQueryObject.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "sslerr.h"
+
+namespace mozilla::net {
+
+const uint64_t HTTP3_APP_ERROR_NO_ERROR = 0x100;
+// const uint64_t HTTP3_APP_ERROR_GENERAL_PROTOCOL_ERROR = 0x101;
+// const uint64_t HTTP3_APP_ERROR_INTERNAL_ERROR = 0x102;
+// const uint64_t HTTP3_APP_ERROR_STREAM_CREATION_ERROR = 0x103;
+// const uint64_t HTTP3_APP_ERROR_CLOSED_CRITICAL_STREAM = 0x104;
+// const uint64_t HTTP3_APP_ERROR_FRAME_UNEXPECTED = 0x105;
+// const uint64_t HTTP3_APP_ERROR_FRAME_ERROR = 0x106;
+// const uint64_t HTTP3_APP_ERROR_EXCESSIVE_LOAD = 0x107;
+// const uint64_t HTTP3_APP_ERROR_ID_ERROR = 0x108;
+// const uint64_t HTTP3_APP_ERROR_SETTINGS_ERROR = 0x109;
+// const uint64_t HTTP3_APP_ERROR_MISSING_SETTINGS = 0x10a;
+const uint64_t HTTP3_APP_ERROR_REQUEST_REJECTED = 0x10b;
+const uint64_t HTTP3_APP_ERROR_REQUEST_CANCELLED = 0x10c;
+// const uint64_t HTTP3_APP_ERROR_REQUEST_INCOMPLETE = 0x10d;
+// const uint64_t HTTP3_APP_ERROR_EARLY_RESPONSE = 0x10e;
+// const uint64_t HTTP3_APP_ERROR_CONNECT_ERROR = 0x10f;
+const uint64_t HTTP3_APP_ERROR_VERSION_FALLBACK = 0x110;
+
+// const uint32_t UDP_MAX_PACKET_SIZE = 4096;
+const uint32_t MAX_PTO_COUNTS = 16;
+
+const uint32_t TRANSPORT_ERROR_STATELESS_RESET = 20;
+
+NS_IMPL_ADDREF(Http3Session)
+NS_IMPL_RELEASE(Http3Session)
+NS_INTERFACE_MAP_BEGIN(Http3Session)
+ NS_INTERFACE_MAP_ENTRY(nsAHttpConnection)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(Http3Session)
+NS_INTERFACE_MAP_END
+
+Http3Session::Http3Session() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3Session::Http3Session [this=%p]", this));
+
+ mCurrentBrowserId = gHttpHandler->ConnMgr()->CurrentBrowserId();
+ mThroughCaptivePortal = gHttpHandler->GetThroughCaptivePortal();
+}
+
+static nsresult RawBytesToNetAddr(uint16_t aFamily, const uint8_t* aRemoteAddr,
+ uint16_t remotePort, NetAddr* netAddr) {
+ if (aFamily == AF_INET) {
+ netAddr->inet.family = AF_INET;
+ netAddr->inet.port = htons(remotePort);
+ memcpy(&netAddr->inet.ip, aRemoteAddr, 4);
+ } else if (aFamily == AF_INET6) {
+ netAddr->inet6.family = AF_INET6;
+ netAddr->inet6.port = htons(remotePort);
+ memcpy(&netAddr->inet6.ip.u8, aRemoteAddr, 16);
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+nsresult Http3Session::Init(const nsHttpConnectionInfo* aConnInfo,
+ nsINetAddr* aSelfAddr, nsINetAddr* aPeerAddr,
+ HttpConnectionUDP* udpConn, uint32_t controlFlags,
+ nsIInterfaceRequestor* callbacks) {
+ LOG3(("Http3Session::Init %p", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(udpConn);
+
+ mConnInfo = aConnInfo->Clone();
+ mNetAddr = aPeerAddr;
+
+ bool httpsProxy =
+ aConnInfo->ProxyInfo() ? aConnInfo->ProxyInfo()->IsHTTPS() : false;
+
+ // Create security control and info object for quic.
+ mSocketControl = new QuicSocketControl(
+ httpsProxy ? aConnInfo->ProxyInfo()->Host() : aConnInfo->GetOrigin(),
+ httpsProxy ? aConnInfo->ProxyInfo()->Port() : aConnInfo->OriginPort(),
+ controlFlags, this);
+
+ NetAddr selfAddr;
+ MOZ_ALWAYS_SUCCEEDS(aSelfAddr->GetNetAddr(&selfAddr));
+ NetAddr peerAddr;
+ MOZ_ALWAYS_SUCCEEDS(aPeerAddr->GetNetAddr(&peerAddr));
+
+ LOG3(
+ ("Http3Session::Init origin=%s, alpn=%s, selfAddr=%s, peerAddr=%s,"
+ " qpack table size=%u, max blocked streams=%u webtransport=%d "
+ "[this=%p]",
+ PromiseFlatCString(mConnInfo->GetOrigin()).get(),
+ PromiseFlatCString(mConnInfo->GetNPNToken()).get(),
+ selfAddr.ToString().get(), peerAddr.ToString().get(),
+ gHttpHandler->DefaultQpackTableSize(),
+ gHttpHandler->DefaultHttp3MaxBlockedStreams(),
+ mConnInfo->GetWebTransport(), this));
+
+ if (mConnInfo->GetWebTransport()) {
+ mWebTransportNegotiationStatus = WebTransportNegotiation::NEGOTIATING;
+ }
+
+ uint32_t datagramSize =
+ StaticPrefs::network_webtransport_datagrams_enabled()
+ ? StaticPrefs::network_webtransport_datagram_size()
+ : 0;
+ nsresult rv = NeqoHttp3Conn::Init(
+ mConnInfo->GetOrigin(), mConnInfo->GetNPNToken(), selfAddr, peerAddr,
+ gHttpHandler->DefaultQpackTableSize(),
+ gHttpHandler->DefaultHttp3MaxBlockedStreams(),
+ StaticPrefs::network_http_http3_max_data(),
+ StaticPrefs::network_http_http3_max_stream_data(),
+ StaticPrefs::network_http_http3_version_negotiation_enabled(),
+ mConnInfo->GetWebTransport(), gHttpHandler->Http3QlogDir(), datagramSize,
+ StaticPrefs::network_http_http3_max_accumlated_time_ms(),
+ getter_AddRefs(mHttp3Connection));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString peerId;
+ mSocketControl->GetPeerId(peerId);
+ nsTArray<uint8_t> token;
+ SessionCacheInfo info;
+ udpConn->ChangeConnectionState(ConnectionState::TLS_HANDSHAKING);
+
+ if (StaticPrefs::network_http_http3_enable_0rtt() &&
+ NS_SUCCEEDED(SSLTokensCache::Get(peerId, token, info))) {
+ LOG(("Found a resumption token in the cache."));
+ mHttp3Connection->SetResumptionToken(token);
+ mSocketControl->SetSessionCacheInfo(std::move(info));
+ if (mHttp3Connection->IsZeroRtt()) {
+ LOG(("Can send ZeroRtt data"));
+ RefPtr<Http3Session> self(this);
+ mState = ZERORTT;
+ udpConn->ChangeConnectionState(ConnectionState::ZERORTT);
+ mZeroRttStarted = TimeStamp::Now();
+ // Let the nsHttpConnectionMgr know that the connection can accept
+ // transactions.
+ // We need to dispatch the following function to this thread so that
+ // it is executed after the current function. At this point a
+ // Http3Session is still being initialized and ReportHttp3Connection
+ // will try to dispatch transaction on this session therefore it
+ // needs to be executed after the initializationg is done.
+ DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(
+ NS_NewRunnableFunction("Http3Session::ReportHttp3Connection",
+ [self]() { self->ReportHttp3Connection(); }));
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "NS_DispatchToCurrentThread failed");
+ }
+ }
+
+ if (mState != ZERORTT) {
+ ZeroRttTelemetry(ZeroRttOutcome::NOT_USED);
+ }
+
+ auto config = mConnInfo->GetEchConfig();
+ if (config.IsEmpty()) {
+ if (StaticPrefs::security_tls_ech_grease_http3() && config.IsEmpty()) {
+ if ((RandomUint64().valueOr(0) % 100) >=
+ 100 - StaticPrefs::security_tls_ech_grease_probability()) {
+ // Setting an empty config enables GREASE mode.
+ mSocketControl->SetEchConfig(config);
+ mEchExtensionStatus = EchExtensionStatus::kGREASE;
+ }
+ }
+ } else if (gHttpHandler->EchConfigEnabled(true) && !config.IsEmpty()) {
+ mSocketControl->SetEchConfig(config);
+ mEchExtensionStatus = EchExtensionStatus::kReal;
+ HttpConnectionActivity activity(
+ mConnInfo->HashKey(), mConnInfo->GetOrigin(), mConnInfo->OriginPort(),
+ mConnInfo->EndToEndSSL(), !mConnInfo->GetEchConfig().IsEmpty(),
+ mConnInfo->IsHttp3());
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ activity, NS_ACTIVITY_TYPE_HTTP_CONNECTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_ECH_SET, PR_Now(), 0, ""_ns);
+ } else {
+ mEchExtensionStatus = EchExtensionStatus::kNotPresent;
+ }
+
+ // After this line, Http3Session and HttpConnectionUDP become a cycle. We put
+ // this line in the end of Http3Session::Init to make sure Http3Session can be
+ // released when Http3Session::Init early returned.
+ mUdpConn = udpConn;
+ return NS_OK;
+}
+
+void Http3Session::DoSetEchConfig(const nsACString& aEchConfig) {
+ LOG(("Http3Session::DoSetEchConfig %p of length %zu", this,
+ aEchConfig.Length()));
+ nsTArray<uint8_t> config;
+ config.AppendElements(
+ reinterpret_cast<const uint8_t*>(aEchConfig.BeginReading()),
+ aEchConfig.Length());
+ mHttp3Connection->SetEchConfig(config);
+}
+
+nsresult Http3Session::SendPriorityUpdateFrame(uint64_t aStreamId,
+ uint8_t aPriorityUrgency,
+ bool aPriorityIncremental) {
+ return mHttp3Connection->PriorityUpdate(aStreamId, aPriorityUrgency,
+ aPriorityIncremental);
+}
+
+// Shutdown the http3session and close all transactions.
+void Http3Session::Shutdown() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+
+ bool isEchRetry = mError == mozilla::psm::GetXPCOMFromNSSError(
+ SSL_ERROR_ECH_RETRY_WITH_ECH);
+ bool allowToRetryWithDifferentIPFamily =
+ mBeforeConnectedError &&
+ gHttpHandler->ConnMgr()->AllowToRetryDifferentIPFamilyForHttp3(mConnInfo,
+ mError);
+ LOG(("Http3Session::Shutdown %p allowToRetryWithDifferentIPFamily=%d", this,
+ allowToRetryWithDifferentIPFamily));
+ if ((mBeforeConnectedError ||
+ (mError == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR)) &&
+ (mError !=
+ mozilla::psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERT_DOMAIN)) &&
+ !isEchRetry && !mConnInfo->GetWebTransport() &&
+ !allowToRetryWithDifferentIPFamily && !mDontExclude) {
+ gHttpHandler->ExcludeHttp3(mConnInfo);
+ }
+
+ for (const auto& stream : mStreamTransactionHash.Values()) {
+ if (mBeforeConnectedError) {
+ // We have an error before we were connected, just restart transactions.
+ // The transaction restart code path will remove AltSvc mapping and the
+ // direct path will be used.
+ MOZ_ASSERT(NS_FAILED(mError));
+ if (isEchRetry) {
+ // We have to propagate this error to nsHttpTransaction, so the
+ // transaction will be restarted with a new echConfig.
+ stream->Close(mError);
+ } else {
+ if (allowToRetryWithDifferentIPFamily && mNetAddr) {
+ NetAddr addr;
+ mNetAddr->GetNetAddr(&addr);
+ gHttpHandler->ConnMgr()->SetRetryDifferentIPFamilyForHttp3(
+ mConnInfo, addr.raw.family);
+ // We want the transaction to be restarted with the same connection
+ // info.
+ stream->Transaction()->DoNotRemoveAltSvc();
+ // We already set the preference in SetRetryDifferentIPFamilyForHttp3,
+ // so we want to keep it for the next retry.
+ stream->Transaction()->DoNotResetIPFamilyPreference();
+ stream->Close(NS_ERROR_NET_RESET);
+ // Since Http3Session::Shutdown can be called multiple times, we set
+ // mDontExclude for not putting this domain into the excluded list.
+ mDontExclude = true;
+ } else {
+ stream->Close(NS_ERROR_NET_RESET);
+ }
+ }
+ } else if (!stream->HasStreamId()) {
+ if (NS_SUCCEEDED(mError)) {
+ // Connection has not been started yet. We can restart it.
+ stream->Transaction()->DoNotRemoveAltSvc();
+ }
+ stream->Close(NS_ERROR_NET_RESET);
+ } else if (stream->GetHttp3Stream() &&
+ stream->GetHttp3Stream()->RecvdData()) {
+ stream->Close(NS_ERROR_NET_PARTIAL_TRANSFER);
+ } else if (mError == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR) {
+ stream->Close(NS_ERROR_NET_HTTP3_PROTOCOL_ERROR);
+ } else if (mError == NS_ERROR_NET_RESET) {
+ stream->Close(NS_ERROR_NET_RESET);
+ } else {
+ stream->Close(NS_ERROR_ABORT);
+ }
+ RemoveStreamFromQueues(stream);
+ if (stream->HasStreamId()) {
+ mStreamIdHash.Remove(stream->StreamId());
+ }
+ }
+ mStreamTransactionHash.Clear();
+
+ for (const auto& stream : mWebTransportSessions) {
+ stream->Close(NS_ERROR_ABORT);
+ RemoveStreamFromQueues(stream);
+ mStreamIdHash.Remove(stream->StreamId());
+ }
+ mWebTransportSessions.Clear();
+
+ for (const auto& stream : mWebTransportStreams) {
+ stream->Close(NS_ERROR_ABORT);
+ RemoveStreamFromQueues(stream);
+ mStreamIdHash.Remove(stream->StreamId());
+ }
+
+ RefPtr<Http3StreamBase> stream;
+ while ((stream = mQueuedStreams.PopFront())) {
+ LOG(("Close remaining stream in queue:%p", stream.get()));
+ stream->SetQueued(false);
+ stream->Close(NS_ERROR_ABORT);
+ }
+ mWebTransportStreams.Clear();
+}
+
+Http3Session::~Http3Session() {
+ LOG3(("Http3Session::~Http3Session %p", this));
+
+ EchOutcomeTelemetry();
+ Telemetry::Accumulate(Telemetry::HTTP3_REQUEST_PER_CONN, mTransactionCount);
+ Telemetry::Accumulate(Telemetry::HTTP3_BLOCKED_BY_STREAM_LIMIT_PER_CONN,
+ mBlockedByStreamLimitCount);
+ Telemetry::Accumulate(Telemetry::HTTP3_TRANS_BLOCKED_BY_STREAM_LIMIT_PER_CONN,
+ mTransactionsBlockedByStreamLimitCount);
+
+ Telemetry::Accumulate(
+ Telemetry::HTTP3_TRANS_SENDING_BLOCKED_BY_FLOW_CONTROL_PER_CONN,
+ mTransactionsSenderBlockedByFlowControlCount);
+
+ if (mThroughCaptivePortal) {
+ if (mTotalBytesRead || mTotalBytesWritten) {
+ auto total =
+ Clamp<uint32_t>((mTotalBytesRead >> 10) + (mTotalBytesWritten >> 10),
+ 0, std::numeric_limits<uint32_t>::max());
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_DATA_TRANSFERRED_CAPTIVE_PORTAL,
+ total);
+ }
+
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_HTTP_CONNECTIONS_CAPTIVE_PORTAL, 1);
+ }
+
+ Shutdown();
+}
+
+// This function may return a socket error.
+// It will not return an error if socket error is
+// NS_BASE_STREAM_WOULD_BLOCK.
+// A caller of this function will close the Http3 connection
+// in case of a error.
+// The only callers is:
+// HttpConnectionUDP::RecvData ->
+// Http3Session::RecvData
+void Http3Session::ProcessInput(nsIUDPSocket* socket) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mUdpConn);
+
+ LOG(("Http3Session::ProcessInput writer=%p [this=%p state=%d]",
+ mUdpConn.get(), this, mState));
+
+ while (true) {
+ nsTArray<uint8_t> data;
+ NetAddr addr{};
+ // RecvWithAddr actually does not return an error.
+ nsresult rv = socket->RecvWithAddr(&addr, data);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ if (NS_FAILED(rv) || data.IsEmpty()) {
+ break;
+ }
+ rv = mHttp3Connection->ProcessInput(addr, data);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ LOG(("Http3Session::ProcessInput received=%zu", data.Length()));
+ mTotalBytesRead += data.Length();
+ }
+}
+
+nsresult Http3Session::ProcessTransactionRead(uint64_t stream_id) {
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(stream_id);
+ if (!stream) {
+ LOG(
+ ("Http3Session::ProcessTransactionRead - stream not found "
+ "stream_id=0x%" PRIx64 " [this=%p].",
+ stream_id, this));
+ return NS_OK;
+ }
+
+ return ProcessTransactionRead(stream);
+}
+
+nsresult Http3Session::ProcessTransactionRead(Http3StreamBase* stream) {
+ nsresult rv = stream->WriteSegments();
+
+ if (ASpdySession::SoftStreamError(rv) || stream->Done()) {
+ LOG3(
+ ("Http3Session::ProcessSingleTransactionRead session=%p stream=%p "
+ "0x%" PRIx64 " cleanup stream rv=0x%" PRIx32 " done=%d.\n",
+ this, stream, stream->StreamId(), static_cast<uint32_t>(rv),
+ stream->Done()));
+ CloseStream(stream,
+ (rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED : NS_OK);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+nsresult Http3Session::ProcessEvents() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3Session::ProcessEvents [this=%p]", this));
+
+ // We need an array to pick up header data or a resumption token.
+ nsTArray<uint8_t> data;
+ Http3Event event{};
+ event.tag = Http3Event::Tag::NoEvent;
+
+ nsresult rv = mHttp3Connection->GetEvent(&event, data);
+ if (NS_FAILED(rv)) {
+ LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ while (event.tag != Http3Event::Tag::NoEvent) {
+ switch (event.tag) {
+ case Http3Event::Tag::HeaderReady: {
+ MOZ_ASSERT(mState == CONNECTED);
+ LOG(("Http3Session::ProcessEvents - HeaderReady"));
+ uint64_t id = event.header_ready.stream_id;
+
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id);
+ if (!stream) {
+ LOG(
+ ("Http3Session::ProcessEvents - HeaderReady - stream not found "
+ "stream_id=0x%" PRIx64 " [this=%p].",
+ id, this));
+ break;
+ }
+
+ MOZ_RELEASE_ASSERT(stream->GetHttp3Stream(),
+ "This must be a Http3Stream");
+
+ stream->SetResponseHeaders(data, event.header_ready.fin,
+ event.header_ready.interim);
+
+ rv = ProcessTransactionRead(stream);
+
+ if (NS_FAILED(rv)) {
+ LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ mUdpConn->NotifyDataRead();
+ break;
+ }
+ case Http3Event::Tag::DataReadable: {
+ MOZ_ASSERT(mState == CONNECTED);
+ LOG(("Http3Session::ProcessEvents - DataReadable"));
+ uint64_t id = event.data_readable.stream_id;
+
+ nsresult rv = ProcessTransactionRead(id);
+
+ if (NS_FAILED(rv)) {
+ LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ break;
+ }
+ case Http3Event::Tag::DataWritable: {
+ MOZ_ASSERT(CanSendData());
+ LOG(("Http3Session::ProcessEvents - DataWritable"));
+
+ RefPtr<Http3StreamBase> stream =
+ mStreamIdHash.Get(event.data_writable.stream_id);
+
+ if (stream) {
+ StreamReadyToWrite(stream);
+ }
+ } break;
+ case Http3Event::Tag::Reset:
+ LOG(("Http3Session::ProcessEvents %p - Reset", this));
+ ResetOrStopSendingRecvd(event.reset.stream_id, event.reset.error,
+ RESET);
+ break;
+ case Http3Event::Tag::StopSending:
+ LOG(
+ ("Http3Session::ProcessEvents %p - StopSeniding with error "
+ "0x%" PRIx64,
+ this, event.stop_sending.error));
+ if (event.stop_sending.error == HTTP3_APP_ERROR_NO_ERROR) {
+ RefPtr<Http3StreamBase> stream =
+ mStreamIdHash.Get(event.data_writable.stream_id);
+ if (stream) {
+ RefPtr<Http3Stream> httpStream = stream->GetHttp3Stream();
+ MOZ_RELEASE_ASSERT(httpStream, "This must be a Http3Stream");
+ httpStream->StopSending();
+ }
+ } else {
+ ResetOrStopSendingRecvd(event.reset.stream_id, event.reset.error,
+ STOP_SENDING);
+ }
+ break;
+ case Http3Event::Tag::PushPromise:
+ LOG(("Http3Session::ProcessEvents - PushPromise"));
+ break;
+ case Http3Event::Tag::PushHeaderReady:
+ LOG(("Http3Session::ProcessEvents - PushHeaderReady"));
+ break;
+ case Http3Event::Tag::PushDataReadable:
+ LOG(("Http3Session::ProcessEvents - PushDataReadable"));
+ break;
+ case Http3Event::Tag::PushCanceled:
+ LOG(("Http3Session::ProcessEvents - PushCanceled"));
+ break;
+ case Http3Event::Tag::RequestsCreatable:
+ LOG(("Http3Session::ProcessEvents - StreamCreatable"));
+ ProcessPending();
+ break;
+ case Http3Event::Tag::AuthenticationNeeded:
+ LOG(("Http3Session::ProcessEvents - AuthenticationNeeded %d",
+ mAuthenticationStarted));
+ if (!mAuthenticationStarted) {
+ mAuthenticationStarted = true;
+ LOG(("Http3Session::ProcessEvents - AuthenticationNeeded called"));
+ CallCertVerification(Nothing());
+ }
+ break;
+ case Http3Event::Tag::ZeroRttRejected:
+ LOG(("Http3Session::ProcessEvents - ZeroRttRejected"));
+ if (mState == ZERORTT) {
+ mState = INITIALIZING;
+ mTransactionCount = 0;
+ Finish0Rtt(true);
+ ZeroRttTelemetry(ZeroRttOutcome::USED_REJECTED);
+ }
+ break;
+ case Http3Event::Tag::ResumptionToken: {
+ LOG(("Http3Session::ProcessEvents - ResumptionToken"));
+ if (StaticPrefs::network_http_http3_enable_0rtt() && !data.IsEmpty()) {
+ LOG(("Got a resumption token"));
+ nsAutoCString peerId;
+ mSocketControl->GetPeerId(peerId);
+ if (NS_FAILED(SSLTokensCache::Put(
+ peerId, data.Elements(), data.Length(), mSocketControl,
+ PR_Now() + event.resumption_token.expire_in))) {
+ LOG(("Adding resumption token failed"));
+ }
+ }
+ } break;
+ case Http3Event::Tag::ConnectionConnected: {
+ LOG(("Http3Session::ProcessEvents - ConnectionConnected"));
+ bool was0RTT = mState == ZERORTT;
+ mState = CONNECTED;
+ SetSecInfo();
+ mSocketControl->HandshakeCompleted();
+ if (was0RTT) {
+ Finish0Rtt(false);
+ ZeroRttTelemetry(ZeroRttOutcome::USED_SUCCEEDED);
+ }
+
+ OnTransportStatus(mSocketTransport, NS_NET_STATUS_CONNECTED_TO, 0);
+ // Also send the NS_NET_STATUS_TLS_HANDSHAKE_ENDED event.
+ OnTransportStatus(mSocketTransport, NS_NET_STATUS_TLS_HANDSHAKE_ENDED,
+ 0);
+
+ ReportHttp3Connection();
+ // Maybe call ResumeSend:
+ // In case ZeroRtt has been used and it has been rejected, 2 events will
+ // be received: ZeroRttRejected and ConnectionConnected. ZeroRttRejected
+ // that will put all transaction into mReadyForWrite queue and it will
+ // call MaybeResumeSend, but that will not have an effect because the
+ // connection is ont in CONNECTED state. When ConnectionConnected event
+ // is received call MaybeResumeSend to trigger reads for the
+ // zero-rtt-rejected transactions.
+ MaybeResumeSend();
+ } break;
+ case Http3Event::Tag::GoawayReceived:
+ LOG(("Http3Session::ProcessEvents - GoawayReceived"));
+ mUdpConn->SetCloseReason(ConnectionCloseReason::GO_AWAY);
+ mGoawayReceived = true;
+ break;
+ case Http3Event::Tag::ConnectionClosing:
+ LOG(("Http3Session::ProcessEvents - ConnectionClosing"));
+ if (NS_SUCCEEDED(mError) && !IsClosing()) {
+ mError = NS_ERROR_NET_HTTP3_PROTOCOL_ERROR;
+ CloseConnectionTelemetry(event.connection_closing.error, true);
+
+ auto isStatelessResetOrNoError = [](CloseError& aError) -> bool {
+ if (aError.tag == CloseError::Tag::TransportInternalErrorOther &&
+ aError.transport_internal_error_other._0 ==
+ TRANSPORT_ERROR_STATELESS_RESET) {
+ return true;
+ }
+ if (aError.tag == CloseError::Tag::TransportError &&
+ aError.transport_error._0 == 0) {
+ return true;
+ }
+ if (aError.tag == CloseError::Tag::PeerError &&
+ aError.peer_error._0 == 0) {
+ return true;
+ }
+ if (aError.tag == CloseError::Tag::AppError &&
+ aError.app_error._0 == HTTP3_APP_ERROR_NO_ERROR) {
+ return true;
+ }
+ if (aError.tag == CloseError::Tag::PeerAppError &&
+ aError.peer_app_error._0 == HTTP3_APP_ERROR_NO_ERROR) {
+ return true;
+ }
+ return false;
+ };
+ if (isStatelessResetOrNoError(event.connection_closing.error)) {
+ mError = NS_ERROR_NET_RESET;
+ }
+ if (event.connection_closing.error.tag == CloseError::Tag::EchRetry) {
+ mSocketControl->SetRetryEchConfig(Substring(
+ reinterpret_cast<const char*>(data.Elements()), data.Length()));
+ mError = psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH);
+ }
+ }
+ return mError;
+ break;
+ case Http3Event::Tag::ConnectionClosed:
+ LOG(("Http3Session::ProcessEvents - ConnectionClosed"));
+ if (NS_SUCCEEDED(mError)) {
+ mError = NS_ERROR_NET_TIMEOUT;
+ mUdpConn->SetCloseReason(ConnectionCloseReason::IDLE_TIMEOUT);
+ CloseConnectionTelemetry(event.connection_closed.error, false);
+ }
+ mIsClosedByNeqo = true;
+ if (event.connection_closed.error.tag == CloseError::Tag::EchRetry) {
+ mSocketControl->SetRetryEchConfig(Substring(
+ reinterpret_cast<const char*>(data.Elements()), data.Length()));
+ mError = psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH);
+ }
+ LOG(("Http3Session::ProcessEvents - ConnectionClosed error=%" PRIx32,
+ static_cast<uint32_t>(mError)));
+ // We need to return here and let HttpConnectionUDP close the session.
+ return mError;
+ break;
+ case Http3Event::Tag::EchFallbackAuthenticationNeeded: {
+ nsCString echPublicName(reinterpret_cast<const char*>(data.Elements()),
+ data.Length());
+ LOG(
+ ("Http3Session::ProcessEvents - EchFallbackAuthenticationNeeded "
+ "echPublicName=%s",
+ echPublicName.get()));
+ if (!mAuthenticationStarted) {
+ mAuthenticationStarted = true;
+ CallCertVerification(Some(echPublicName));
+ }
+ } break;
+ case Http3Event::Tag::WebTransport: {
+ switch (event.web_transport._0.tag) {
+ case WebTransportEventExternal::Tag::Negotiated:
+ LOG(("Http3Session::ProcessEvents - WebTransport %d",
+ event.web_transport._0.negotiated._0));
+ MOZ_ASSERT(mWebTransportNegotiationStatus ==
+ WebTransportNegotiation::NEGOTIATING);
+ mWebTransportNegotiationStatus =
+ event.web_transport._0.negotiated._0
+ ? WebTransportNegotiation::SUCCEEDED
+ : WebTransportNegotiation::FAILED;
+ WebTransportNegotiationDone();
+ break;
+ case WebTransportEventExternal::Tag::Session: {
+ MOZ_ASSERT(mState == CONNECTED);
+
+ uint64_t id = event.web_transport._0.session._0;
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport Session "
+ " sessionId=0x%" PRIx64,
+ id));
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id);
+ if (!stream) {
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport Session - "
+ "stream not found "
+ "stream_id=0x%" PRIx64 " [this=%p].",
+ id, this));
+ break;
+ }
+
+ MOZ_RELEASE_ASSERT(stream->GetHttp3WebTransportSession(),
+ "It must be a WebTransport session");
+ stream->SetResponseHeaders(data, false, false);
+
+ rv = stream->WriteSegments();
+
+ if (ASpdySession::SoftStreamError(rv) || stream->Done()) {
+ LOG3(
+ ("Http3Session::ProcessSingleTransactionRead session=%p "
+ "stream=%p "
+ "0x%" PRIx64 " cleanup stream rv=0x%" PRIx32 " done=%d.\n",
+ this, stream.get(), stream->StreamId(),
+ static_cast<uint32_t>(rv), stream->Done()));
+ // We need to keep the transaction, so we can use it to remove the
+ // stream from mStreamTransactionHash.
+ nsAHttpTransaction* trans = stream->Transaction();
+ if (mStreamTransactionHash.Contains(trans)) {
+ CloseStream(stream, (rv == NS_BINDING_RETARGETED)
+ ? NS_BINDING_RETARGETED
+ : NS_OK);
+ mStreamTransactionHash.Remove(trans);
+ } else {
+ stream->GetHttp3WebTransportSession()->TransactionIsDone(
+ (rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED
+ : NS_OK);
+ }
+ break;
+ }
+
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ } break;
+ case WebTransportEventExternal::Tag::SessionClosed: {
+ uint64_t id = event.web_transport._0.session_closed.stream_id;
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport SessionClosed "
+ " sessionId=0x%" PRIx64,
+ id));
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id);
+ if (!stream) {
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport SessionClosed - "
+ "stream not found "
+ "stream_id=0x%" PRIx64 " [this=%p].",
+ id, this));
+ break;
+ }
+
+ RefPtr<Http3WebTransportSession> wt =
+ stream->GetHttp3WebTransportSession();
+ MOZ_RELEASE_ASSERT(wt, "It must be a WebTransport session");
+
+ bool cleanly = false;
+
+ // TODO we do not handle the case when a WebTransport session stream
+ // is closed before headers are sent.
+ SessionCloseReasonExternal& reasonExternal =
+ event.web_transport._0.session_closed.reason;
+ uint32_t status = 0;
+ nsCString reason = ""_ns;
+ if (reasonExternal.tag == SessionCloseReasonExternal::Tag::Error) {
+ status = reasonExternal.error._0;
+ } else if (reasonExternal.tag ==
+ SessionCloseReasonExternal::Tag::Status) {
+ status = reasonExternal.status._0;
+ cleanly = true;
+ } else {
+ status = reasonExternal.clean._0;
+ reason.Assign(reinterpret_cast<const char*>(data.Elements()),
+ data.Length());
+ cleanly = true;
+ }
+ LOG(("reason.tag=%u err=%u data=%s\n",
+ static_cast<uint32_t>(reasonExternal.tag), status,
+ reason.get()));
+ wt->OnSessionClosed(cleanly, status, reason);
+
+ } break;
+ case WebTransportEventExternal::Tag::NewStream: {
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport NewStream "
+ "streamId=0x%" PRIx64 " sessionId=0x%" PRIx64,
+ event.web_transport._0.new_stream.stream_id,
+ event.web_transport._0.new_stream.session_id));
+ uint64_t sessionId = event.web_transport._0.new_stream.session_id;
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(sessionId);
+ if (!stream) {
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport NewStream - "
+ "session not found "
+ "sessionId=0x%" PRIx64 " [this=%p].",
+ sessionId, this));
+ break;
+ }
+
+ RefPtr<Http3WebTransportSession> wt =
+ stream->GetHttp3WebTransportSession();
+ if (!wt) {
+ break;
+ }
+
+ RefPtr<Http3WebTransportStream> wtStream =
+ wt->OnIncomingWebTransportStream(
+ event.web_transport._0.new_stream.stream_type,
+ event.web_transport._0.new_stream.stream_id);
+ if (!wtStream) {
+ break;
+ }
+
+ // WebTransportStream is managed by Http3Session now.
+ mWebTransportStreams.AppendElement(wtStream);
+ mWebTransportStreamToSessionMap.InsertOrUpdate(wtStream->StreamId(),
+ wt->StreamId());
+ mStreamIdHash.InsertOrUpdate(wtStream->StreamId(),
+ std::move(wtStream));
+ } break;
+ case WebTransportEventExternal::Tag::Datagram:
+ LOG(
+ ("Http3Session::ProcessEvents - "
+ "WebTransportEventExternal::Tag::Datagram [this=%p]",
+ this));
+ uint64_t sessionId = event.web_transport._0.datagram.session_id;
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(sessionId);
+ if (!stream) {
+ LOG(
+ ("Http3Session::ProcessEvents - WebTransport Datagram - "
+ "session not found "
+ "sessionId=0x%" PRIx64 " [this=%p].",
+ sessionId, this));
+ break;
+ }
+
+ RefPtr<Http3WebTransportSession> wt =
+ stream->GetHttp3WebTransportSession();
+ if (!wt) {
+ break;
+ }
+
+ wt->OnDatagramReceived(std::move(data));
+ break;
+ }
+ } break;
+ default:
+ break;
+ }
+ // Delete previous content of data
+ data.TruncateLength(0);
+ rv = mHttp3Connection->GetEvent(&event, data);
+ if (NS_FAILED(rv)) {
+ LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ }
+
+ return NS_OK;
+} // namespace net
+
+// This function may return a socket error.
+// It will not return an error if socket error is
+// NS_BASE_STREAM_WOULD_BLOCK.
+// A Caller of this function will close the Http3 connection
+// if this function returns an error.
+// Callers are:
+// 1) HttpConnectionUDP::OnQuicTimeoutExpired
+// 2) HttpConnectionUDP::SendData ->
+// Http3Session::SendData
+nsresult Http3Session::ProcessOutput(nsIUDPSocket* socket) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mUdpConn);
+
+ LOG(("Http3Session::ProcessOutput reader=%p, [this=%p]", mUdpConn.get(),
+ this));
+
+ mSocket = socket;
+ nsresult rv = mHttp3Connection->ProcessOutputAndSend(
+ this,
+ [](void* aContext, uint16_t aFamily, const uint8_t* aAddr, uint16_t aPort,
+ const uint8_t* aData, uint32_t aLength) {
+ Http3Session* self = (Http3Session*)aContext;
+
+ uint32_t written = 0;
+ NetAddr addr;
+ if (NS_FAILED(RawBytesToNetAddr(aFamily, aAddr, aPort, &addr))) {
+ return NS_OK;
+ }
+
+ LOG3(
+ ("Http3Session::ProcessOutput sending packet with %u bytes to %s "
+ "port=%d [this=%p].",
+ aLength, addr.ToString().get(), aPort, self));
+
+ nsresult rv =
+ self->mSocket->SendWithAddress(&addr, aData, aLength, &written);
+
+ LOG(("Http3Session::ProcessOutput sending packet rv=%d osError=%d",
+ static_cast<int32_t>(rv), NS_FAILED(rv) ? PR_GetOSError() : 0));
+ if (NS_FAILED(rv) && (rv != NS_BASE_STREAM_WOULD_BLOCK)) {
+ self->mSocketError = rv;
+ // If there was an error that is not NS_BASE_STREAM_WOULD_BLOCK
+ // return from here. We do not need to set a timer, because we
+ // will close the connection.
+ return rv;
+ }
+ self->mTotalBytesWritten += aLength;
+ self->mLastWriteTime = PR_IntervalNow();
+ return NS_OK;
+ },
+ [](void* aContext, uint64_t timeout) {
+ Http3Session* self = (Http3Session*)aContext;
+ self->SetupTimer(timeout);
+ });
+ mSocket = nullptr;
+ return rv;
+}
+
+// This is only called when timer expires.
+// It is called by HttpConnectionUDP::OnQuicTimeout.
+// If tihs function returns an error OnQuicTimeout will handle the error
+// properly and close the connection.
+nsresult Http3Session::ProcessOutputAndEvents(nsIUDPSocket* socket) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // ProcessOutput could fire another timer. Need to unset the flag before that.
+ mTimerActive = false;
+
+ MOZ_ASSERT(mTimerShouldTrigger);
+
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_TIMER_DELAYED,
+ mTimerShouldTrigger, TimeStamp::Now());
+
+ mTimerShouldTrigger = TimeStamp();
+
+ nsresult rv = SendData(socket);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return ProcessEvents();
+}
+
+void Http3Session::SetupTimer(uint64_t aTimeout) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // UINT64_MAX indicated a no-op from neqo, which only happens when a
+ // connection is in or going to be Closed state.
+ if (aTimeout == UINT64_MAX) {
+ return;
+ }
+
+ LOG3(
+ ("Http3Session::SetupTimer to %" PRIu64 "ms [this=%p].", aTimeout, this));
+
+ // Remember the time when the timer should trigger.
+ mTimerShouldTrigger =
+ TimeStamp::Now() + TimeDuration::FromMilliseconds(aTimeout);
+
+ if (mTimerActive && mTimer) {
+ LOG(
+ (" -- Previous timer has not fired. Update the delay instead of "
+ "re-initializing the timer"));
+ mTimer->SetDelay(aTimeout);
+ return;
+ }
+
+ nsresult rv = NS_NewTimerWithCallback(
+ getter_AddRefs(mTimer),
+ [conn = RefPtr{mUdpConn}](nsITimer*) { conn->OnQuicTimeoutExpired(); },
+ aTimeout, nsITimer::TYPE_ONE_SHOT,
+ "net::HttpConnectionUDP::OnQuicTimeout");
+
+ mTimerActive = true;
+
+ if (NS_FAILED(rv)) {
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("net::HttpConnectionUDP::OnQuicTimeoutExpired",
+ mUdpConn, &HttpConnectionUDP::OnQuicTimeoutExpired));
+ }
+}
+
+bool Http3Session::AddStream(nsAHttpTransaction* aHttpTransaction,
+ int32_t aPriority,
+ nsIInterfaceRequestor* aCallbacks) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction();
+
+ if (!mConnection) {
+ // Get the connection from the first transaction.
+ mConnection = aHttpTransaction->Connection();
+ }
+
+ if (IsClosing()) {
+ LOG3(
+ ("Http3Session::AddStream %p atrans=%p trans=%p session unusable - "
+ "resched.\n",
+ this, aHttpTransaction, trans));
+ aHttpTransaction->SetConnection(nullptr);
+ nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http3Session::AddStream %p atrans=%p trans=%p failed to initiate "
+ "transaction (0x%" PRIx32 ").\n",
+ this, aHttpTransaction, trans, static_cast<uint32_t>(rv)));
+ }
+ return true;
+ }
+
+ aHttpTransaction->SetConnection(this);
+ aHttpTransaction->OnActivated();
+ // reset the read timers to wash away any idle time
+ mLastWriteTime = PR_IntervalNow();
+
+ ClassOfService cos;
+ if (trans) {
+ cos = trans->GetClassOfService();
+ }
+
+ Http3StreamBase* stream = nullptr;
+
+ if (trans && trans->IsForWebTransport()) {
+ LOG3(("Http3Session::AddStream new WeTransport session %p atrans=%p.\n",
+ this, aHttpTransaction));
+ stream = new Http3WebTransportSession(aHttpTransaction, this);
+ mHasWebTransportSession = true;
+ } else {
+ LOG3(("Http3Session::AddStream %p atrans=%p.\n", this, aHttpTransaction));
+ stream = new Http3Stream(aHttpTransaction, this, cos, mCurrentBrowserId);
+ }
+
+ mStreamTransactionHash.InsertOrUpdate(aHttpTransaction, RefPtr{stream});
+
+ if (mState == ZERORTT) {
+ if (!stream->Do0RTT()) {
+ LOG(("Http3Session %p will not get early data from Http3Stream %p", this,
+ stream));
+ if (!mCannotDo0RTTStreams.Contains(stream)) {
+ mCannotDo0RTTStreams.AppendElement(stream);
+ }
+ if ((mWebTransportNegotiationStatus ==
+ WebTransportNegotiation::NEGOTIATING) &&
+ (trans && trans->IsForWebTransport())) {
+ LOG(("waiting for negotiation"));
+ mWaitingForWebTransportNegotiation.AppendElement(stream);
+ }
+ return true;
+ }
+ m0RTTStreams.AppendElement(stream);
+ }
+
+ if ((mWebTransportNegotiationStatus ==
+ WebTransportNegotiation::NEGOTIATING) &&
+ (trans && trans->IsForWebTransport())) {
+ LOG(("waiting for negotiation"));
+ mWaitingForWebTransportNegotiation.AppendElement(stream);
+ return true;
+ }
+
+ if (!mFirstHttpTransaction && !IsConnected()) {
+ mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction();
+ LOG3(("Http3Session::AddStream first session=%p trans=%p ", this,
+ mFirstHttpTransaction.get()));
+ }
+ StreamReadyToWrite(stream);
+
+ return true;
+}
+
+bool Http3Session::CanReuse() {
+ // TODO: we assume "pooling" is disabled here, so we don't allow this session
+ // to be reused. "pooling" will be implemented in bug 1815735.
+ return CanSendData() && !(mGoawayReceived || mShouldClose) &&
+ !mHasWebTransportSession;
+}
+
+void Http3Session::QueueStream(Http3StreamBase* stream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!stream->Queued());
+
+ LOG3(("Http3Session::QueueStream %p stream %p queued.", this, stream));
+
+ stream->SetQueued(true);
+ mQueuedStreams.Push(stream);
+}
+
+void Http3Session::ProcessPending() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ RefPtr<Http3StreamBase> stream;
+ while ((stream = mQueuedStreams.PopFront())) {
+ LOG3(("Http3Session::ProcessPending %p stream %p woken from queue.", this,
+ stream.get()));
+ MOZ_ASSERT(stream->Queued());
+ stream->SetQueued(false);
+ mReadyForWrite.Push(stream);
+ }
+ MaybeResumeSend();
+}
+
+static void RemoveStreamFromQueue(Http3StreamBase* aStream,
+ nsRefPtrDeque<Http3StreamBase>& queue) {
+ size_t size = queue.GetSize();
+ for (size_t count = 0; count < size; ++count) {
+ RefPtr<Http3StreamBase> stream = queue.PopFront();
+ if (stream != aStream) {
+ queue.Push(stream);
+ }
+ }
+}
+
+void Http3Session::RemoveStreamFromQueues(Http3StreamBase* aStream) {
+ RemoveStreamFromQueue(aStream, mReadyForWrite);
+ RemoveStreamFromQueue(aStream, mQueuedStreams);
+ mSlowConsumersReadyForRead.RemoveElement(aStream);
+}
+
+// This is called by Http3Stream::OnReadSegment.
+// ProcessOutput will be called in Http3Session::ReadSegment that
+// calls Http3Stream::OnReadSegment.
+nsresult Http3Session::TryActivating(
+ const nsACString& aMethod, const nsACString& aScheme,
+ const nsACString& aAuthorityHeader, const nsACString& aPath,
+ const nsACString& aHeaders, uint64_t* aStreamId, Http3StreamBase* aStream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(*aStreamId == UINT64_MAX);
+
+ LOG(("Http3Session::TryActivating [stream=%p, this=%p state=%d]", aStream,
+ this, mState));
+
+ if (IsClosing()) {
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aStream->Queued()) {
+ LOG3(("Http3Session::TryActivating %p stream=%p already queued.\n", this,
+ aStream));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ if (mState == ZERORTT) {
+ if (!aStream->Do0RTT()) {
+ MOZ_ASSERT(!mCannotDo0RTTStreams.Contains(aStream));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ }
+
+ nsresult rv = NS_OK;
+ RefPtr<Http3Stream> httpStream = aStream->GetHttp3Stream();
+ if (httpStream) {
+ rv = mHttp3Connection->Fetch(
+ aMethod, aScheme, aAuthorityHeader, aPath, aHeaders, aStreamId,
+ httpStream->PriorityUrgency(), httpStream->PriorityIncremental());
+ } else {
+ MOZ_RELEASE_ASSERT(aStream->GetHttp3WebTransportSession(),
+ "It must be a WebTransport session");
+ // Don't call CreateWebTransport if we are still waiting for the negotiation
+ // result.
+ if (mWebTransportNegotiationStatus ==
+ WebTransportNegotiation::NEGOTIATING) {
+ if (!mWaitingForWebTransportNegotiation.Contains(aStream)) {
+ mWaitingForWebTransportNegotiation.AppendElement(aStream);
+ }
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ rv = mHttp3Connection->CreateWebTransport(aAuthorityHeader, aPath, aHeaders,
+ aStreamId);
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("Http3Session::TryActivating returns error=0x%" PRIx32 "[stream=%p, "
+ "this=%p]",
+ static_cast<uint32_t>(rv), aStream, this));
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG3(
+ ("Http3Session::TryActivating %p stream=%p no room for more "
+ "concurrent streams\n",
+ this, aStream));
+ mTransactionsBlockedByStreamLimitCount++;
+ if (mQueuedStreams.GetSize() == 0) {
+ mBlockedByStreamLimitCount++;
+ }
+ QueueStream(aStream);
+ return rv;
+ }
+ // Ignore this error. This may happen if some events are not handled yet.
+ // TODO we may try to add an assertion here.
+ return NS_OK;
+ }
+
+ LOG(("Http3Session::TryActivating streamId=0x%" PRIx64
+ " for stream=%p [this=%p].",
+ *aStreamId, aStream, this));
+
+ MOZ_ASSERT(*aStreamId != UINT64_MAX);
+
+ if (mTransactionCount > 0 && mStreamIdHash.IsEmpty()) {
+ MOZ_ASSERT(mConnectionIdleStart);
+ MOZ_ASSERT(mFirstStreamIdReuseIdleConnection.isNothing());
+
+ mConnectionIdleEnd = TimeStamp::Now();
+ mFirstStreamIdReuseIdleConnection = Some(*aStreamId);
+ }
+ mStreamIdHash.InsertOrUpdate(*aStreamId, RefPtr{aStream});
+ mTransactionCount++;
+
+ return NS_OK;
+}
+
+// This is called by Http3WebTransportStream::OnReadSegment.
+// TODO: this function is almost the same as TryActivating().
+// We should try to reduce the duplicate code.
+nsresult Http3Session::TryActivatingWebTransportStream(
+ uint64_t* aStreamId, Http3StreamBase* aStream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(*aStreamId == UINT64_MAX);
+
+ LOG(
+ ("Http3Session::TryActivatingWebTransportStream [stream=%p, this=%p "
+ "state=%d]",
+ aStream, this, mState));
+
+ if (IsClosing()) {
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aStream->Queued()) {
+ LOG3(
+ ("Http3Session::TryActivatingWebTransportStream %p stream=%p already "
+ "queued.\n",
+ this, aStream));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ nsresult rv = NS_OK;
+ RefPtr<Http3WebTransportStream> wtStream =
+ aStream->GetHttp3WebTransportStream();
+ MOZ_RELEASE_ASSERT(wtStream, "It must be a WebTransport stream");
+ rv = mHttp3Connection->CreateWebTransportStream(
+ wtStream->SessionId(), wtStream->StreamType(), aStreamId);
+
+ if (NS_FAILED(rv)) {
+ LOG((
+ "Http3Session::TryActivatingWebTransportStream returns error=0x%" PRIx32
+ "[stream=%p, "
+ "this=%p]",
+ static_cast<uint32_t>(rv), aStream, this));
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG3(
+ ("Http3Session::TryActivatingWebTransportStream %p stream=%p no room "
+ "for more "
+ "concurrent streams\n",
+ this, aStream));
+ QueueStream(aStream);
+ return rv;
+ }
+
+ return rv;
+ }
+
+ LOG(("Http3Session::TryActivatingWebTransportStream streamId=0x%" PRIx64
+ " for stream=%p [this=%p].",
+ *aStreamId, aStream, this));
+
+ MOZ_ASSERT(*aStreamId != UINT64_MAX);
+
+ RefPtr<Http3StreamBase> session = mStreamIdHash.Get(wtStream->SessionId());
+ MOZ_ASSERT(session);
+ Http3WebTransportSession* wtSession = session->GetHttp3WebTransportSession();
+ MOZ_ASSERT(wtSession);
+
+ wtSession->RemoveWebTransportStream(wtStream);
+
+ // WebTransportStream is managed by Http3Session now.
+ mWebTransportStreams.AppendElement(wtStream);
+ mWebTransportStreamToSessionMap.InsertOrUpdate(*aStreamId,
+ session->StreamId());
+ mStreamIdHash.InsertOrUpdate(*aStreamId, std::move(wtStream));
+ return NS_OK;
+}
+
+// This is only called by Http3Stream::OnReadSegment.
+// ProcessOutput will be called in Http3Session::ReadSegment that
+// calls Http3Stream::OnReadSegment.
+void Http3Session::CloseSendingSide(uint64_t aStreamId) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mHttp3Connection->CloseStream(aStreamId);
+}
+
+// This is only called by Http3Stream::OnReadSegment.
+// ProcessOutput will be called in Http3Session::ReadSegment that
+// calls Http3Stream::OnReadSegment.
+nsresult Http3Session::SendRequestBody(uint64_t aStreamId, const char* buf,
+ uint32_t count, uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsresult rv = mHttp3Connection->SendRequestBody(
+ aStreamId, (const uint8_t*)buf, count, countRead);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mTransactionsSenderBlockedByFlowControlCount++;
+ } else if (NS_FAILED(rv)) {
+ // Ignore this error. This may happen if some events are not handled yet.
+ // TODO we may try to add an assertion here.
+ // We will pretend that sender is blocked, that will cause the caller to
+ // stop trying.
+ *countRead = 0;
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ MOZ_ASSERT((*countRead != 0) || NS_FAILED(rv));
+ return rv;
+}
+
+void Http3Session::ResetOrStopSendingRecvd(uint64_t aStreamId, uint64_t aError,
+ ResetType aType) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ uint64_t sessionId = 0;
+ if (mWebTransportStreamToSessionMap.Get(aStreamId, &sessionId)) {
+ uint8_t wtError = Http3ErrorToWebTransportError(aError);
+ nsresult rv = GetNSResultFromWebTransportError(wtError);
+
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(aStreamId);
+ if (stream) {
+ if (aType == RESET) {
+ stream->SetRecvdReset();
+ }
+ RefPtr<Http3WebTransportStream> wtStream =
+ stream->GetHttp3WebTransportStream();
+ if (wtStream) {
+ CloseWebTransportStream(wtStream, rv);
+ }
+ }
+
+ RefPtr<Http3StreamBase> session = mStreamIdHash.Get(sessionId);
+ if (session) {
+ Http3WebTransportSession* wtSession =
+ session->GetHttp3WebTransportSession();
+ MOZ_ASSERT(wtSession);
+ if (wtSession) {
+ if (aType == RESET) {
+ wtSession->OnStreamReset(aStreamId, rv);
+ } else {
+ wtSession->OnStreamStopSending(aStreamId, rv);
+ }
+ }
+ }
+ return;
+ }
+
+ RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(aStreamId);
+ if (!stream) {
+ return;
+ }
+
+ RefPtr<Http3Stream> httpStream = stream->GetHttp3Stream();
+ if (!httpStream) {
+ return;
+ }
+
+ // We only handle some of Http3 error as epecial, the rest are just equivalent
+ // to cancel.
+ if (aError == HTTP3_APP_ERROR_VERSION_FALLBACK) {
+ // We will restart the request and the alt-svc will be removed
+ // automatically.
+ // Also disable http3 we want http1.1.
+ httpStream->Transaction()->DisableHttp3(false);
+ httpStream->Transaction()->DisableSpdy();
+ CloseStream(stream, NS_ERROR_NET_RESET);
+ } else if (aError == HTTP3_APP_ERROR_REQUEST_REJECTED) {
+ // This request was rejected because server is probably busy or going away.
+ // We can restart the request using alt-svc. Without calling
+ // DoNotRemoveAltSvc the alt-svc route will be removed.
+ httpStream->Transaction()->DoNotRemoveAltSvc();
+ CloseStream(stream, NS_ERROR_NET_RESET);
+ } else {
+ if (httpStream->RecvdData()) {
+ CloseStream(stream, NS_ERROR_NET_PARTIAL_TRANSFER);
+ } else {
+ CloseStream(stream, NS_ERROR_NET_INTERRUPT);
+ }
+ }
+}
+
+void Http3Session::SetConnection(nsAHttpConnection* aConn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mConnection = aConn;
+}
+
+void Http3Session::GetSecurityCallbacks(nsIInterfaceRequestor** aOut) {
+ *aOut = nullptr;
+}
+
+// TODO
+void Http3Session::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
+ int64_t aProgress) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if ((aStatus == NS_NET_STATUS_CONNECTED_TO) && !IsConnected()) {
+ // We should ignore the event. This is sent by the nsSocketTranpsort
+ // and it does not mean that HTTP3 session is connected.
+ // We will use this event to mark start of TLS handshake
+ aStatus = NS_NET_STATUS_TLS_HANDSHAKE_STARTING;
+ }
+
+ switch (aStatus) {
+ // These should appear only once, deliver to the first
+ // transaction on the session.
+ case NS_NET_STATUS_RESOLVING_HOST:
+ case NS_NET_STATUS_RESOLVED_HOST:
+ case NS_NET_STATUS_CONNECTING_TO:
+ case NS_NET_STATUS_CONNECTED_TO:
+ case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
+ case NS_NET_STATUS_TLS_HANDSHAKE_ENDED: {
+ if (!mFirstHttpTransaction) {
+ // if we still do not have a HttpTransaction store timings info in
+ // a HttpConnection.
+ // If some error occur it can happen that we do not have a connection.
+ if (mConnection) {
+ RefPtr<HttpConnectionBase> conn = mConnection->HttpConnection();
+ conn->SetEvent(aStatus);
+ }
+ } else {
+ mFirstHttpTransaction->OnTransportStatus(aTransport, aStatus,
+ aProgress);
+ }
+
+ if (aStatus == NS_NET_STATUS_CONNECTED_TO) {
+ mFirstHttpTransaction = nullptr;
+ }
+ break;
+ }
+
+ default:
+ // The other transport events are ignored here because there is no good
+ // way to map them to the right transaction in HTTP3. Instead, the events
+ // are generated again from the HTTP3 code and passed directly to the
+ // correct transaction.
+
+ // NS_NET_STATUS_SENDING_TO:
+ // This is generated by the socket transport when (part) of
+ // a transaction is written out
+ //
+ // There is no good way to map it to the right transaction in HTTP3,
+ // so it is ignored here and generated separately when the request
+ // is sent from Http3Stream.
+
+ // NS_NET_STATUS_WAITING_FOR:
+ // Created by nsHttpConnection when the request has been totally sent.
+ // There is no good way to map it to the right transaction in HTTP3,
+ // so it is ignored here and generated separately when the same
+ // condition is complete in Http3Stream when there is no more
+ // request body left to be transmitted.
+
+ // NS_NET_STATUS_RECEIVING_FROM
+ // Generated in Http3Stream whenever the stream reads data.
+
+ break;
+ }
+}
+
+bool Http3Session::IsDone() { return mState == CLOSED; }
+
+nsresult Http3Session::Status() {
+ MOZ_ASSERT(false, "Http3Session::Status()");
+ return NS_ERROR_UNEXPECTED;
+}
+
+uint32_t Http3Session::Caps() {
+ MOZ_ASSERT(false, "Http3Session::Caps()");
+ return 0;
+}
+
+nsresult Http3Session::ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count, uint32_t* countRead) {
+ MOZ_ASSERT(false, "Http3Session::ReadSegments()");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult Http3Session::SendData(nsIUDPSocket* socket) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3Session::SendData [this=%p]", this));
+
+ // 1) go through all streams/transactions that are ready to write and
+ // write their data into quic streams (no network write yet).
+ // 2) call ProcessOutput that will loop until all available packets are
+ // written to a socket or the socket returns an error code.
+ // 3) if we still have streams ready to write call ResumeSend()(we may
+ // still have such streams because on an stream error we return earlier
+ // to let the error be handled).
+ // 4)
+
+ nsresult rv = NS_OK;
+ RefPtr<Http3StreamBase> stream;
+
+ // Step 1)
+ while (CanSendData() && (stream = mReadyForWrite.PopFront())) {
+ LOG(("Http3Session::SendData call ReadSegments from stream=%p [this=%p]",
+ stream.get(), this));
+
+ rv = stream->ReadSegments();
+
+ // on stream error we return earlier to let the error be handled.
+ if (NS_FAILED(rv)) {
+ LOG3(("Http3Session::SendData %p returns error code 0x%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) { // Just in case!
+ rv = NS_OK;
+ } else if (ASpdySession::SoftStreamError(rv)) {
+ CloseStream(stream, rv);
+ LOG3(("Http3Session::SendData %p soft error override\n", this));
+ rv = NS_OK;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // Step 2:
+ // Call actual network write.
+ rv = ProcessOutput(socket);
+ }
+
+ // Step 3:
+ MaybeResumeSend();
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+
+ // Let the connection know we sent some app data successfully.
+ if (stream && NS_SUCCEEDED(rv)) {
+ mUdpConn->NotifyDataWrite();
+ }
+
+ return rv;
+}
+
+void Http3Session::StreamReadyToWrite(Http3StreamBase* aStream) {
+ MOZ_ASSERT(aStream);
+ mReadyForWrite.Push(aStream);
+ if (CanSendData() && mConnection) {
+ Unused << mConnection->ResumeSend();
+ }
+}
+
+void Http3Session::MaybeResumeSend() {
+ if ((mReadyForWrite.GetSize() > 0) && CanSendData() && mConnection) {
+ Unused << mConnection->ResumeSend();
+ }
+}
+
+nsresult Http3Session::ProcessSlowConsumers() {
+ if (mSlowConsumersReadyForRead.IsEmpty()) {
+ return NS_OK;
+ }
+
+ RefPtr<Http3StreamBase> slowConsumer =
+ mSlowConsumersReadyForRead.ElementAt(0);
+ mSlowConsumersReadyForRead.RemoveElementAt(0);
+
+ nsresult rv = ProcessTransactionRead(slowConsumer);
+
+ return rv;
+}
+
+nsresult Http3Session::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count, uint32_t* countWritten) {
+ MOZ_ASSERT(false, "Http3Session::WriteSegments()");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult Http3Session::RecvData(nsIUDPSocket* socket) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // Process slow consumers.
+ nsresult rv = ProcessSlowConsumers();
+ if (NS_FAILED(rv)) {
+ LOG3(("Http3Session %p ProcessSlowConsumers returns 0x%" PRIx32 "\n", this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ ProcessInput(socket);
+
+ rv = ProcessEvents();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = SendData(socket);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+const uint32_t HTTP3_TELEMETRY_APP_NECKO = 42;
+
+void Http3Session::Close(nsresult aReason) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3Session::Close [this=%p]", this));
+
+ if (NS_FAILED(mError)) {
+ CloseInternal(false);
+ } else {
+ mError = aReason;
+ // If necko closes connection, this will map to the "closing" key and the
+ // value HTTP3_TELEMETRY_APP_NECKO.
+ Telemetry::Accumulate(Telemetry::HTTP3_CONNECTION_CLOSE_CODE_3,
+ "app_closing"_ns, HTTP3_TELEMETRY_APP_NECKO);
+ CloseInternal(true);
+ }
+
+ if (mCleanShutdown || mIsClosedByNeqo || NS_FAILED(mSocketError)) {
+ // It is network-tear-down, a socker error or neqo is state CLOSED
+ // (it does not need to send any more packets or wait for new packets).
+ // We need to remove all references, so that
+ // Http3Session will be destroyed.
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+ mConnection = nullptr;
+ mUdpConn = nullptr;
+ mState = CLOSED;
+ }
+ if (mConnection) {
+ // resume sending to send CLOSE_CONNECTION frame.
+ Unused << mConnection->ResumeSend();
+ }
+}
+
+void Http3Session::CloseInternal(bool aCallNeqoClose) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (IsClosing()) {
+ return;
+ }
+
+ LOG(("Http3Session::Closing [this=%p]", this));
+
+ if (mState != CONNECTED) {
+ mBeforeConnectedError = true;
+ }
+
+ if (mState == ZERORTT) {
+ ZeroRttTelemetry(aCallNeqoClose ? ZeroRttOutcome::USED_CONN_CLOSED_BY_NECKO
+ : ZeroRttOutcome::USED_CONN_ERROR);
+ }
+
+ mState = CLOSING;
+ Shutdown();
+
+ if (aCallNeqoClose) {
+ mHttp3Connection->Close(HTTP3_APP_ERROR_NO_ERROR);
+ }
+
+ mStreamIdHash.Clear();
+ mStreamTransactionHash.Clear();
+}
+
+nsHttpConnectionInfo* Http3Session::ConnectionInfo() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ RefPtr<nsHttpConnectionInfo> ci;
+ GetConnectionInfo(getter_AddRefs(ci));
+ return ci.get();
+}
+
+void Http3Session::SetProxyConnectFailed() {
+ MOZ_ASSERT(false, "Http3Session::SetProxyConnectFailed()");
+}
+
+nsHttpRequestHead* Http3Session::RequestHead() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(false,
+ "Http3Session::RequestHead() "
+ "should not be called after http/3 is setup");
+ return nullptr;
+}
+
+uint32_t Http3Session::Http1xTransactionCount() { return 0; }
+
+nsresult Http3Session::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) {
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Pass through methods of nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+nsAHttpConnection* Http3Session::Connection() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return mConnection;
+}
+
+nsresult Http3Session::OnHeadersAvailable(nsAHttpTransaction* transaction,
+ nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ bool* reset) {
+ MOZ_ASSERT(mConnection);
+ if (mConnection) {
+ return mConnection->OnHeadersAvailable(transaction, requestHead,
+ responseHead, reset);
+ }
+ return NS_OK;
+}
+
+bool Http3Session::IsReused() {
+ if (mConnection) {
+ return mConnection->IsReused();
+ }
+ return true;
+}
+
+nsresult Http3Session::PushBack(const char* buf, uint32_t len) {
+ return NS_ERROR_UNEXPECTED;
+}
+
+already_AddRefed<HttpConnectionBase> Http3Session::TakeHttpConnection() {
+ MOZ_ASSERT(false, "TakeHttpConnection of Http3Session");
+ return nullptr;
+}
+
+already_AddRefed<HttpConnectionBase> Http3Session::HttpConnection() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mConnection) {
+ return mConnection->HttpConnection();
+ }
+ return nullptr;
+}
+
+void Http3Session::CloseTransaction(nsAHttpTransaction* aTransaction,
+ nsresult aResult) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Session::CloseTransaction %p %p 0x%" PRIx32, this, aTransaction,
+ static_cast<uint32_t>(aResult)));
+
+ // Generally this arrives as a cancel event from the connection manager.
+
+ // need to find the stream and call CloseStream() on it.
+ RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(aTransaction);
+ if (!stream) {
+ LOG3(("Http3Session::CloseTransaction %p %p 0x%" PRIx32 " - not found.",
+ this, aTransaction, static_cast<uint32_t>(aResult)));
+ return;
+ }
+ LOG3(
+ ("Http3Session::CloseTransaction probably a cancel. this=%p, "
+ "trans=%p, result=0x%" PRIx32 ", streamId=0x%" PRIx64 " stream=%p",
+ this, aTransaction, static_cast<uint32_t>(aResult), stream->StreamId(),
+ stream.get()));
+ CloseStream(stream, aResult);
+ if (mConnection) {
+ Unused << mConnection->ResumeSend();
+ }
+}
+
+void Http3Session::CloseStream(Http3StreamBase* aStream, nsresult aResult) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ RefPtr<Http3WebTransportStream> wtStream =
+ aStream->GetHttp3WebTransportStream();
+ if (wtStream) {
+ CloseWebTransportStream(wtStream, aResult);
+ return;
+ }
+
+ RefPtr<Http3Stream> httpStream = aStream->GetHttp3Stream();
+ if (httpStream && !httpStream->RecvdFin() && !httpStream->RecvdReset() &&
+ httpStream->HasStreamId()) {
+ mHttp3Connection->CancelFetch(httpStream->StreamId(),
+ HTTP3_APP_ERROR_REQUEST_CANCELLED);
+ }
+
+ aStream->Close(aResult);
+ CloseStreamInternal(aStream, aResult);
+}
+
+void Http3Session::CloseStreamInternal(Http3StreamBase* aStream,
+ nsresult aResult) {
+ LOG3(("Http3Session::CloseStreamInternal %p %p 0x%" PRIx32, this, aStream,
+ static_cast<uint32_t>(aResult)));
+ if (aStream->HasStreamId()) {
+ // We know the transaction reusing an idle connection has succeeded or
+ // failed.
+ if (mFirstStreamIdReuseIdleConnection.isSome() &&
+ aStream->StreamId() == *mFirstStreamIdReuseIdleConnection) {
+ MOZ_ASSERT(mConnectionIdleStart);
+ MOZ_ASSERT(mConnectionIdleEnd);
+
+ if (mConnectionIdleStart) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::HTTP3_TIME_TO_REUSE_IDLE_CONNECTTION_MS,
+ NS_SUCCEEDED(aResult) ? "succeeded"_ns : "failed"_ns,
+ mConnectionIdleStart, mConnectionIdleEnd);
+ }
+
+ mConnectionIdleStart = TimeStamp();
+ mConnectionIdleEnd = TimeStamp();
+ mFirstStreamIdReuseIdleConnection.reset();
+ }
+
+ mStreamIdHash.Remove(aStream->StreamId());
+
+ // Start to idle when we remove the last stream.
+ if (mStreamIdHash.IsEmpty()) {
+ mConnectionIdleStart = TimeStamp::Now();
+ }
+ }
+ RemoveStreamFromQueues(aStream);
+ if (nsAHttpTransaction* transaction = aStream->Transaction()) {
+ mStreamTransactionHash.Remove(transaction);
+ }
+ mWebTransportSessions.RemoveElement(aStream);
+ mWebTransportStreams.RemoveElement(aStream);
+ // Close(NS_OK) implies that the NeqoHttp3Conn will be closed, so we can only
+ // do this when there is no Http3Steeam, WebTransportSession and
+ // WebTransportStream.
+ if ((mShouldClose || mGoawayReceived) &&
+ (!mStreamTransactionHash.Count() && mWebTransportSessions.IsEmpty() &&
+ mWebTransportStreams.IsEmpty())) {
+ MOZ_ASSERT(!IsClosing());
+ Close(NS_OK);
+ }
+}
+
+void Http3Session::CloseWebTransportStream(Http3WebTransportStream* aStream,
+ nsresult aResult) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Session::CloseWebTransportStream %p %p 0x%" PRIx32, this, aStream,
+ static_cast<uint32_t>(aResult)));
+ if (aStream && !aStream->RecvdFin() && !aStream->RecvdReset() &&
+ (aStream->HasStreamId())) {
+ mHttp3Connection->ResetStream(aStream->StreamId(),
+ HTTP3_APP_ERROR_REQUEST_CANCELLED);
+ }
+
+ aStream->Close(aResult);
+ CloseStreamInternal(aStream, aResult);
+}
+
+void Http3Session::ResetWebTransportStream(Http3WebTransportStream* aStream,
+ uint64_t aErrorCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Session::ResetWebTransportStream %p %p 0x%" PRIx64, this, aStream,
+ aErrorCode));
+ mHttp3Connection->ResetStream(aStream->StreamId(), aErrorCode);
+}
+
+void Http3Session::StreamStopSending(Http3WebTransportStream* aStream,
+ uint8_t aErrorCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3Session::StreamStopSending %p %p 0x%" PRIx32, this, aStream,
+ static_cast<uint32_t>(aErrorCode)));
+ mHttp3Connection->StreamStopSending(aStream->StreamId(), aErrorCode);
+}
+
+nsresult Http3Session::TakeTransport(nsISocketTransport**,
+ nsIAsyncInputStream**,
+ nsIAsyncOutputStream**) {
+ MOZ_ASSERT(false, "TakeTransport of Http3Session");
+ return NS_ERROR_UNEXPECTED;
+}
+
+Http3WebTransportSession* Http3Session::GetWebTransportSession(
+ nsAHttpTransaction* aTransaction) {
+ RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(aTransaction);
+
+ if (!stream || !stream->GetHttp3WebTransportSession()) {
+ MOZ_ASSERT(false, "There must be a stream");
+ return nullptr;
+ }
+ RemoveStreamFromQueues(stream);
+ mStreamTransactionHash.Remove(aTransaction);
+ mWebTransportSessions.AppendElement(stream);
+ return stream->GetHttp3WebTransportSession();
+}
+
+bool Http3Session::IsPersistent() { return true; }
+
+void Http3Session::DontReuse() {
+ LOG3(("Http3Session::DontReuse %p\n", this));
+ if (!OnSocketThread()) {
+ LOG3(("Http3Session %p not on socket thread\n", this));
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
+ "Http3Session::DontReuse", this, &Http3Session::DontReuse);
+ gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ if (mGoawayReceived || IsClosing()) {
+ return;
+ }
+
+ mShouldClose = true;
+ if (!mStreamTransactionHash.Count()) {
+ Close(NS_OK);
+ }
+}
+
+void Http3Session::CloseWebTransportConn() {
+ LOG3(("Http3Session::CloseWebTransportConn %p\n", this));
+ // We need to dispatch, since Http3Session could be released in
+ // HttpConnectionUDP::CloseTransaction.
+ gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction("Http3Session::CloseWebTransportConn",
+ [self = RefPtr{this}]() {
+ if (self->mUdpConn) {
+ self->mUdpConn->CloseTransaction(
+ self, NS_ERROR_ABORT);
+ }
+ }),
+ NS_DISPATCH_NORMAL);
+}
+
+void Http3Session::CurrentBrowserIdChanged(uint64_t id) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mCurrentBrowserId = id;
+
+ for (const auto& stream : mStreamTransactionHash.Values()) {
+ RefPtr<Http3Stream> httpStream = stream->GetHttp3Stream();
+ if (httpStream) {
+ httpStream->CurrentBrowserIdChanged(id);
+ }
+ }
+}
+
+// This is called by Http3Stream::OnWriteSegment.
+nsresult Http3Session::ReadResponseData(uint64_t aStreamId, char* aBuf,
+ uint32_t aCount,
+ uint32_t* aCountWritten, bool* aFin) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv = mHttp3Connection->ReadResponseData(aStreamId, (uint8_t*)aBuf,
+ aCount, aCountWritten, aFin);
+
+ // This should not happen, i.e. stream must be present in neqo and in necko at
+ // the same time.
+ MOZ_ASSERT(rv != NS_ERROR_INVALID_ARG);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http3Session::ReadResponseData return an error %" PRIx32
+ " [this=%p]",
+ static_cast<uint32_t>(rv), this));
+ // This error will be handled by neqo and the whole connection will be
+ // closed. We will return NS_BASE_STREAM_WOULD_BLOCK here.
+ *aCountWritten = 0;
+ *aFin = false;
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ MOZ_ASSERT((*aCountWritten != 0) || aFin || NS_FAILED(rv));
+ return rv;
+}
+
+void Http3Session::TransactionHasDataToWrite(nsAHttpTransaction* caller) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Session::TransactionHasDataToWrite %p trans=%p", this, caller));
+
+ // a trapped signal from the http transaction to the connection that
+ // it is no longer blocked on read.
+
+ RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(caller);
+ if (!stream) {
+ LOG3(("Http3Session::TransactionHasDataToWrite %p caller %p not found",
+ this, caller));
+ return;
+ }
+
+ LOG3(("Http3Session::TransactionHasDataToWrite %p ID is 0x%" PRIx64, this,
+ stream->StreamId()));
+
+ StreamHasDataToWrite(stream);
+}
+
+void Http3Session::StreamHasDataToWrite(Http3StreamBase* aStream) {
+ if (!IsClosing()) {
+ StreamReadyToWrite(aStream);
+ } else {
+ LOG3(
+ ("Http3Session::TransactionHasDataToWrite %p closed so not setting "
+ "Ready4Write\n",
+ this));
+ }
+
+ // NSPR poll will not poll the network if there are non system PR_FileDesc's
+ // that are ready - so we can get into a deadlock waiting for the system IO
+ // to come back here if we don't force the send loop manually.
+ Unused << ForceSend();
+}
+
+void Http3Session::TransactionHasDataToRecv(nsAHttpTransaction* caller) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Session::TransactionHasDataToRecv %p trans=%p", this, caller));
+
+ // a signal from the http transaction to the connection that it will consume
+ // more
+ RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(caller);
+ if (!stream) {
+ LOG3(("Http3Session::TransactionHasDataToRecv %p caller %p not found", this,
+ caller));
+ return;
+ }
+
+ LOG3(("Http3Session::TransactionHasDataToRecv %p ID is 0x%" PRIx64 "\n", this,
+ stream->StreamId()));
+ ConnectSlowConsumer(stream);
+}
+
+void Http3Session::ConnectSlowConsumer(Http3StreamBase* stream) {
+ LOG3(("Http3Session::ConnectSlowConsumer %p 0x%" PRIx64 "\n", this,
+ stream->StreamId()));
+ mSlowConsumersReadyForRead.AppendElement(stream);
+ Unused << ForceRecv();
+}
+
+bool Http3Session::TestJoinConnection(const nsACString& hostname,
+ int32_t port) {
+ return RealJoinConnection(hostname, port, true);
+}
+
+bool Http3Session::JoinConnection(const nsACString& hostname, int32_t port) {
+ return RealJoinConnection(hostname, port, false);
+}
+
+// TODO test
+bool Http3Session::RealJoinConnection(const nsACString& hostname, int32_t port,
+ bool justKidding) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mConnection || !CanSendData() || mShouldClose || mGoawayReceived) {
+ return false;
+ }
+
+ nsHttpConnectionInfo* ci = ConnectionInfo();
+ if (nsCString(hostname).EqualsIgnoreCase(ci->Origin()) &&
+ (port == ci->OriginPort())) {
+ return true;
+ }
+
+ nsAutoCString key(hostname);
+ key.Append(':');
+ key.Append(justKidding ? 'k' : '.');
+ key.AppendInt(port);
+ bool cachedResult;
+ if (mJoinConnectionCache.Get(key, &cachedResult)) {
+ LOG(("joinconnection [%p %s] %s result=%d cache\n", this,
+ ConnectionInfo()->HashKey().get(), key.get(), cachedResult));
+ return cachedResult;
+ }
+
+ nsresult rv;
+ bool isJoined = false;
+
+ nsCOMPtr<nsITLSSocketControl> sslSocketControl;
+ mConnection->GetTLSSocketControl(getter_AddRefs(sslSocketControl));
+ if (!sslSocketControl) {
+ return false;
+ }
+
+ bool joinedReturn = false;
+ if (justKidding) {
+ rv = sslSocketControl->TestJoinConnection(mConnInfo->GetNPNToken(),
+ hostname, port, &isJoined);
+ } else {
+ rv = sslSocketControl->JoinConnection(mConnInfo->GetNPNToken(), hostname,
+ port, &isJoined);
+ }
+ if (NS_SUCCEEDED(rv) && isJoined) {
+ joinedReturn = true;
+ }
+
+ LOG(("joinconnection [%p %s] %s result=%d lookup\n", this,
+ ConnectionInfo()->HashKey().get(), key.get(), joinedReturn));
+ mJoinConnectionCache.InsertOrUpdate(key, joinedReturn);
+ if (!justKidding) {
+ // cache a kidding entry too as this one is good for both
+ nsAutoCString key2(hostname);
+ key2.Append(':');
+ key2.Append('k');
+ key2.AppendInt(port);
+ if (!mJoinConnectionCache.Get(key2)) {
+ mJoinConnectionCache.InsertOrUpdate(key2, joinedReturn);
+ }
+ }
+ return joinedReturn;
+}
+
+void Http3Session::CallCertVerification(Maybe<nsCString> aEchPublicName) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3Session::CallCertVerification [this=%p]", this));
+
+ NeqoCertificateInfo certInfo;
+ if (NS_FAILED(mHttp3Connection->PeerCertificateInfo(&certInfo))) {
+ LOG(("Http3Session::CallCertVerification [this=%p] - no cert", this));
+ mHttp3Connection->PeerAuthenticated(SSL_ERROR_BAD_CERTIFICATE);
+ mError = psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERTIFICATE);
+ return;
+ }
+
+ Maybe<nsTArray<nsTArray<uint8_t>>> stapledOCSPResponse;
+ if (certInfo.stapled_ocsp_responses_present) {
+ stapledOCSPResponse.emplace(std::move(certInfo.stapled_ocsp_responses));
+ }
+
+ Maybe<nsTArray<uint8_t>> sctsFromTLSExtension;
+ if (certInfo.signed_cert_timestamp_present) {
+ sctsFromTLSExtension.emplace(std::move(certInfo.signed_cert_timestamp));
+ }
+
+ uint32_t providerFlags;
+ // the return value is always NS_OK, just ignore it.
+ Unused << mSocketControl->GetProviderFlags(&providerFlags);
+
+ nsCString echConfig;
+ nsresult nsrv = mSocketControl->GetEchConfig(echConfig);
+ bool verifyToEchPublicName = NS_SUCCEEDED(nsrv) && !echConfig.IsEmpty() &&
+ aEchPublicName && !aEchPublicName->IsEmpty();
+ const nsACString& hostname =
+ verifyToEchPublicName ? *aEchPublicName : mSocketControl->GetHostName();
+
+ SECStatus rv = psm::AuthCertificateHookWithInfo(
+ mSocketControl, hostname, static_cast<const void*>(this),
+ std::move(certInfo.certs), stapledOCSPResponse, sctsFromTLSExtension,
+ providerFlags);
+ if ((rv != SECSuccess) && (rv != SECWouldBlock)) {
+ LOG(("Http3Session::CallCertVerification [this=%p] AuthCertificate failed",
+ this));
+ mHttp3Connection->PeerAuthenticated(SSL_ERROR_BAD_CERTIFICATE);
+ mError = psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERTIFICATE);
+ }
+}
+
+void Http3Session::Authenticated(int32_t aError) {
+ LOG(("Http3Session::Authenticated error=0x%" PRIx32 " [this=%p].", aError,
+ this));
+ if ((mState == INITIALIZING) || (mState == ZERORTT)) {
+ if (psm::IsNSSErrorCode(aError)) {
+ mError = psm::GetXPCOMFromNSSError(aError);
+ LOG(("Http3Session::Authenticated psm-error=0x%" PRIx32 " [this=%p].",
+ static_cast<uint32_t>(mError), this));
+ }
+ mHttp3Connection->PeerAuthenticated(aError);
+
+ // Call OnQuicTimeoutExpired to properly process neqo events and outputs.
+ // We call OnQuicTimeoutExpired instead of ProcessOutputAndEvents, because
+ // HttpConnectionUDP must close this session in case of an error.
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("net::HttpConnectionUDP::OnQuicTimeoutExpired",
+ mUdpConn, &HttpConnectionUDP::OnQuicTimeoutExpired));
+ mUdpConn->ChangeConnectionState(ConnectionState::TRANSFERING);
+ }
+}
+
+void Http3Session::SetSecInfo() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ NeqoSecretInfo secInfo;
+ if (NS_SUCCEEDED(mHttp3Connection->GetSecInfo(&secInfo))) {
+ mSocketControl->SetSSLVersionUsed(secInfo.version);
+ mSocketControl->SetResumed(secInfo.resumed);
+ mSocketControl->SetNegotiatedNPN(secInfo.alpn);
+
+ mSocketControl->SetInfo(secInfo.cipher, secInfo.version, secInfo.group,
+ secInfo.signature_scheme, secInfo.ech_accepted);
+ mHandshakeSucceeded = true;
+ }
+
+ if (!mSocketControl->HasServerCert()) {
+ mSocketControl->RebuildCertificateInfoFromSSLTokenCache();
+ }
+}
+
+// Transport error have values from 0x0 to 0x11.
+// (https://tools.ietf.org/html/draft-ietf-quic-transport-34#section-20.1)
+// We will map this error to 0-16.
+// 17 will capture error codes between and including 0x12 and 0x0ff. This
+// error codes are not define by the spec but who know peer may sent them.
+// CryptoAlerts have value 0x100 + alert code. The range of alert code is
+// 0x00-0xff. (https://tools.ietf.org/html/draft-ietf-quic-tls_34#section-4.8)
+// Since telemetry does not allow more than 100 bucket, we use three diffrent
+// keys to map all alert codes.
+const uint32_t HTTP3_TELEMETRY_TRANSPORT_INTERNAL_ERROR = 15;
+const uint32_t HTTP3_TELEMETRY_TRANSPORT_END = 16;
+const uint32_t HTTP3_TELEMETRY_TRANSPORT_UNKNOWN = 17;
+const uint32_t HTTP3_TELEMETRY_TRANSPORT_CRYPTO_UNKNOWN = 18;
+// All errors from CloseError::Tag::CryptoError will be map to 19
+const uint32_t HTTP3_TELEMETRY_CRYPTO_ERROR = 19;
+
+uint64_t GetCryptoAlertCode(nsCString& key, uint64_t error) {
+ if (error < 100) {
+ key.Append("_a"_ns);
+ return error;
+ }
+ if (error < 200) {
+ error -= 100;
+ key.Append("_b"_ns);
+ return error;
+ }
+ if (error < 256) {
+ error -= 200;
+ key.Append("_c"_ns);
+ return error;
+ }
+ return HTTP3_TELEMETRY_TRANSPORT_CRYPTO_UNKNOWN;
+}
+
+uint64_t GetTransportErrorCodeForTelemetry(nsCString& key, uint64_t error) {
+ if (error <= HTTP3_TELEMETRY_TRANSPORT_END) {
+ return error;
+ }
+ if (error < 0x100) {
+ return HTTP3_TELEMETRY_TRANSPORT_UNKNOWN;
+ }
+
+ return GetCryptoAlertCode(key, error - 0x100);
+}
+
+// Http3 error codes are 0x100-0x110.
+// (https://tools.ietf.org/html/draft-ietf-quic-http-33#section-8.1)
+// The mapping is described below.
+// 0x00-0x10 mapped to 0-16
+// 0x11-0xff mapped to 17
+// 0x100-0x110 mapped to 18-36
+// 0x111-0x1ff mapped to 37
+// 0x200-0x202 mapped to 38-40
+// Others mapped to 41
+const uint32_t HTTP3_TELEMETRY_APP_UNKNOWN_1 = 17;
+const uint32_t HTTP3_TELEMETRY_APP_START = 18;
+// Values between 0x111 and 0x1ff are no definded and will be map to 18.
+const uint32_t HTTP3_TELEMETRY_APP_UNKNOWN_2 = 37;
+// Error codes between 0x200 and 0x202 are related to qpack.
+// (https://tools.ietf.org/html/draft-ietf-quic-qpack-20#section-6)
+// They will be mapped to 19-21
+const uint32_t HTTP3_TELEMETRY_APP_QPACK_START = 38;
+// Values greater or equal to 0x203 are no definded and will be map to 41.
+const uint32_t HTTP3_TELEMETRY_APP_UNKNOWN_3 = 41;
+
+uint64_t GetAppErrorCodeForTelemetry(uint64_t error) {
+ if (error <= 0x10) {
+ return error;
+ }
+ if (error <= 0xff) {
+ return HTTP3_TELEMETRY_APP_UNKNOWN_1;
+ }
+ if (error <= 0x110) {
+ return error - 0x100 + HTTP3_TELEMETRY_APP_START;
+ }
+ if (error < 0x200) {
+ return HTTP3_TELEMETRY_APP_UNKNOWN_2;
+ }
+ if (error <= 0x202) {
+ return error - 0x200 + HTTP3_TELEMETRY_APP_QPACK_START;
+ }
+ return HTTP3_TELEMETRY_APP_UNKNOWN_3;
+}
+
+void Http3Session::CloseConnectionTelemetry(CloseError& aError, bool aClosing) {
+ uint64_t value = 0;
+ nsCString key = EmptyCString();
+
+ switch (aError.tag) {
+ case CloseError::Tag::TransportInternalError:
+ key = "transport_internal"_ns;
+ value = HTTP3_TELEMETRY_TRANSPORT_INTERNAL_ERROR;
+ break;
+ case CloseError::Tag::TransportInternalErrorOther:
+ key = "transport_other"_ns;
+ value = aError.transport_internal_error_other._0;
+ break;
+ case CloseError::Tag::TransportError:
+ key = "transport"_ns;
+ value = GetTransportErrorCodeForTelemetry(key, aError.transport_error._0);
+ break;
+ case CloseError::Tag::CryptoError:
+ key = "transport"_ns;
+ value = HTTP3_TELEMETRY_CRYPTO_ERROR;
+ break;
+ case CloseError::Tag::CryptoAlert:
+ key = "transport_crypto_alert"_ns;
+ value = GetCryptoAlertCode(key, aError.crypto_alert._0);
+ break;
+ case CloseError::Tag::PeerAppError:
+ key = "peer_app"_ns;
+ value = GetAppErrorCodeForTelemetry(aError.peer_app_error._0);
+ break;
+ case CloseError::Tag::PeerError:
+ key = "peer_transport"_ns;
+ value = GetTransportErrorCodeForTelemetry(key, aError.peer_error._0);
+ break;
+ case CloseError::Tag::AppError:
+ key = "app"_ns;
+ value = GetAppErrorCodeForTelemetry(aError.app_error._0);
+ break;
+ case CloseError::Tag::EchRetry:
+ key = "transport_crypto_alert"_ns;
+ value = 100;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(value <= 100);
+
+ key.Append(aClosing ? "_closing"_ns : "_closed"_ns);
+
+ Telemetry::Accumulate(Telemetry::HTTP3_CONNECTION_CLOSE_CODE_3, key, value);
+
+ Http3Stats stats{};
+ mHttp3Connection->GetStats(&stats);
+
+ if (stats.packets_tx > 0) {
+ unsigned long loss = (stats.lost * 10000) / stats.packets_tx;
+ Telemetry::Accumulate(Telemetry::HTTP3_LOSS_RATIO, loss);
+
+ Telemetry::Accumulate(Telemetry::HTTP3_LATE_ACK, "ack"_ns, stats.late_ack);
+ Telemetry::Accumulate(Telemetry::HTTP3_LATE_ACK, "pto"_ns, stats.pto_ack);
+
+ unsigned long late_ack_ratio = (stats.late_ack * 10000) / stats.packets_tx;
+ unsigned long pto_ack_ratio = (stats.pto_ack * 10000) / stats.packets_tx;
+ Telemetry::Accumulate(Telemetry::HTTP3_LATE_ACK_RATIO, "ack"_ns,
+ late_ack_ratio);
+ Telemetry::Accumulate(Telemetry::HTTP3_LATE_ACK_RATIO, "pto"_ns,
+ pto_ack_ratio);
+
+ for (uint32_t i = 0; i < MAX_PTO_COUNTS; i++) {
+ nsAutoCString key;
+ key.AppendInt(i);
+ Telemetry::Accumulate(Telemetry::HTTP3_COUNTS_PTO, key,
+ stats.pto_counts[i]);
+ }
+
+ Telemetry::Accumulate(Telemetry::HTTP3_DROP_DGRAMS, stats.dropped_rx);
+ Telemetry::Accumulate(Telemetry::HTTP3_SAVED_DGRAMS, stats.saved_datagrams);
+ }
+
+ Telemetry::Accumulate(Telemetry::HTTP3_RECEIVED_SENT_DGRAMS, "received"_ns,
+ stats.packets_rx);
+ Telemetry::Accumulate(Telemetry::HTTP3_RECEIVED_SENT_DGRAMS, "sent"_ns,
+ stats.packets_tx);
+}
+
+void Http3Session::Finish0Rtt(bool aRestart) {
+ for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
+ if (m0RTTStreams[i]) {
+ if (aRestart) {
+ // When we need to restart transactions remove them from all lists.
+ if (m0RTTStreams[i]->HasStreamId()) {
+ mStreamIdHash.Remove(m0RTTStreams[i]->StreamId());
+ }
+ RemoveStreamFromQueues(m0RTTStreams[i]);
+ // The stream is ready to write again.
+ mReadyForWrite.Push(m0RTTStreams[i]);
+ }
+ m0RTTStreams[i]->Finish0RTT(aRestart);
+ }
+ }
+
+ for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
+ if (mCannotDo0RTTStreams[i]) {
+ mReadyForWrite.Push(mCannotDo0RTTStreams[i]);
+ }
+ }
+ m0RTTStreams.Clear();
+ mCannotDo0RTTStreams.Clear();
+ MaybeResumeSend();
+}
+
+void Http3Session::ReportHttp3Connection() {
+ if (CanSendData() && !mHttp3ConnectionReported) {
+ mHttp3ConnectionReported = true;
+ gHttpHandler->ConnMgr()->ReportHttp3Connection(mUdpConn);
+ MaybeResumeSend();
+ }
+}
+
+void Http3Session::EchOutcomeTelemetry() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsAutoCString key;
+ switch (mEchExtensionStatus) {
+ case EchExtensionStatus::kNotPresent:
+ key = "NONE";
+ break;
+ case EchExtensionStatus::kGREASE:
+ key = "GREASE";
+ break;
+ case EchExtensionStatus::kReal:
+ key = "REAL";
+ break;
+ }
+
+ Telemetry::Accumulate(Telemetry::HTTP3_ECH_OUTCOME, key,
+ mHandshakeSucceeded ? 0 : 1);
+}
+
+void Http3Session::ZeroRttTelemetry(ZeroRttOutcome aOutcome) {
+ Telemetry::Accumulate(Telemetry::HTTP3_0RTT_STATE, aOutcome);
+
+ nsAutoCString key;
+
+ switch (aOutcome) {
+ case USED_SUCCEEDED:
+ key = "succeeded"_ns;
+ break;
+ case USED_REJECTED:
+ key = "rejected"_ns;
+ break;
+ case USED_CONN_ERROR:
+ key = "conn_error"_ns;
+ break;
+ case USED_CONN_CLOSED_BY_NECKO:
+ key = "conn_closed_by_necko"_ns;
+ break;
+ default:
+ break;
+ }
+
+ if (!key.IsEmpty()) {
+ MOZ_ASSERT(mZeroRttStarted);
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_0RTT_STATE_DURATION, key,
+ mZeroRttStarted, TimeStamp::Now());
+ }
+}
+
+nsresult Http3Session::GetTransactionTLSSocketControl(
+ nsITLSSocketControl** tlsSocketControl) {
+ NS_IF_ADDREF(*tlsSocketControl = mSocketControl);
+ return NS_OK;
+}
+
+PRIntervalTime Http3Session::LastWriteTime() { return mLastWriteTime; }
+
+void Http3Session::WebTransportNegotiationDone() {
+ for (size_t i = 0; i < mWaitingForWebTransportNegotiation.Length(); ++i) {
+ if (mWaitingForWebTransportNegotiation[i]) {
+ mReadyForWrite.Push(mWaitingForWebTransportNegotiation[i]);
+ }
+ }
+ mWaitingForWebTransportNegotiation.Clear();
+ MaybeResumeSend();
+}
+
+//=========================================================================
+// WebTransport
+//=========================================================================
+
+nsresult Http3Session::CloseWebTransport(uint64_t aSessionId, uint32_t aError,
+ const nsACString& aMessage) {
+ return mHttp3Connection->CloseWebTransport(aSessionId, aError, aMessage);
+}
+
+nsresult Http3Session::CreateWebTransportStream(
+ uint64_t aSessionId, WebTransportStreamType aStreamType,
+ uint64_t* aStreamId) {
+ return mHttp3Connection->CreateWebTransportStream(aSessionId, aStreamType,
+ aStreamId);
+}
+
+void Http3Session::SendDatagram(Http3WebTransportSession* aSession,
+ nsTArray<uint8_t>& aData,
+ uint64_t aTrackingId) {
+ nsresult rv = mHttp3Connection->WebTransportSendDatagram(aSession->StreamId(),
+ aData, aTrackingId);
+ LOG(("Http3Session::SendDatagram %p res=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ if (!aTrackingId) {
+ return;
+ }
+
+ switch (rv) {
+ case NS_OK:
+ aSession->OnOutgoingDatagramOutCome(
+ aTrackingId, WebTransportSessionEventListener::DatagramOutcome::SENT);
+ break;
+ case NS_ERROR_NOT_AVAILABLE:
+ aSession->OnOutgoingDatagramOutCome(
+ aTrackingId, WebTransportSessionEventListener::DatagramOutcome::
+ DROPPED_TOO_MUCH_DATA);
+ break;
+ default:
+ aSession->OnOutgoingDatagramOutCome(
+ aTrackingId,
+ WebTransportSessionEventListener::DatagramOutcome::UNKNOWN);
+ break;
+ }
+}
+
+uint64_t Http3Session::MaxDatagramSize(uint64_t aSessionId) {
+ uint64_t size = 0;
+ Unused << mHttp3Connection->WebTransportMaxDatagramSize(aSessionId, &size);
+ return size;
+}
+
+void Http3Session::SetSendOrder(Http3StreamBase* aStream,
+ Maybe<int64_t> aSendOrder) {
+ if (!IsClosing()) {
+ nsresult rv = mHttp3Connection->WebTransportSetSendOrder(
+ aStream->StreamId(), aSendOrder);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ Unused << rv;
+ }
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/Http3Session.h b/netwerk/protocol/http/Http3Session.h
new file mode 100644
index 0000000000..c72f24959e
--- /dev/null
+++ b/netwerk/protocol/http/Http3Session.h
@@ -0,0 +1,388 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef Http3Session_H__
+#define Http3Session_H__
+
+#include "HttpTrafficAnalyzer.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/net/NeqoHttp3Conn.h"
+#include "nsAHttpConnection.h"
+#include "nsDeque.h"
+#include "nsISupportsImpl.h"
+#include "nsITimer.h"
+#include "nsIUDPSocket.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTHashMap.h"
+#include "nsWeakReference.h"
+
+/*
+ * WebTransport
+ *
+ * Http3Session and the underlying neqo code support multiplexing of multiple
+ * WebTransport and multiplexing WebTransport sessions with regular HTTP
+ * traffic. Whether WebTransport sessions are polled, will be controlled by the
+ * nsHttpConnectionMgr.
+ *
+ * WebTransport support is negotiated using HTTP/3 setting. Before the settings
+ * are available all WebTransport transactions are queued in
+ * mWaitingForWebTransportNegotiation. The information on whether WebTransport
+ * is supported is received via an HTTP/3 event. The event is
+ * Http3Event::Tag::WebTransport with the value
+ * WebTransportEventExternal::Tag::Negotiated that can be true or false. If
+ * the WebTransport feature has been negotiated, queued transactions will be
+ * activated otherwise they will be canceled(i.e. WebTransportNegotiationDone).
+ *
+ * The lifetime of a WebTransport session
+ *
+ * A WebTransport lifetime consists of 2 parts:
+ * - WebTransport session setup
+ * - WebTransport session active time
+ *
+ * WebTransport session setup:
+ * A WebTransport session uses a regular HTTP request for negotiation.
+ * Therefore when a new WebTransport is started a nsHttpChannel and the
+ * corresponding nsHttpTransaction are created. The nsHttpTransaction is
+ * dispatched to a Http3Session and after the
+ * WebTransportEventExternal::Tag::Negotiated event it behaves almost the same
+ * as a regular transaction, e.g. it is added to mStreamTransactionHash and
+ * mStreamIdHash. For activating the session NeqoHttp3Conn::CreateWebTransport
+ * is called instead of NeqoHttp3Conn::Fetch(this is called for the regular
+ * HTTP requests). In this phase, the WebTransport session is canceled in the
+ * same way a regular request is canceled, by canceling the corresponding
+ * nsHttpChannel. If HTTP/3 connection is closed in this phase the
+ * corresponding nsHttpTransaction is canceled and this may cause the
+ * transaction to be restarted (this is the existing restart logic) or the
+ * error is propagated to the nsHttpChannel and its listener(via OnStartRequest
+ * and OnStopRequest as the regular HTTP request).
+ * The phase ends when a connection breaks or when the event
+ * Http3Event::Tag::WebTransport with the value
+ * WebTransportEventExternal::Tag::Session is received. The parameter
+ * aData(from NeqoHttp3Conn::GetEvent) contain the HTTP head of the response.
+ * The headers may be:
+ * - failed code, i.e. anything except 200. In this case, the nsHttpTransaction
+ * behaves the same as a normal HTTP request and the nsHttpChannel listener
+ * will be informed via OnStartRequest and OnStopRequest calls.
+ * - success code, i.e. 200. The code will be propagated to the
+ * nsHttpTransaction. The transaction will parse the header and call
+ * Http3Session::GetWebTransportSession. The function transfers WebTransport
+ * session into the next phase:
+ * - Removes nsHttpTransaction from mStreamTransactionHash.
+ * - Adds the stream to mWebTransportSessions
+ * - The nsHttpTransaction supplies Http3WebTransportSession to the
+ * WebTransportSessionProxy and vice versa.
+ * TODO remove this circular referencing.
+ *
+ * WebTransport session active time:
+ * During this phase the following actions are possible:
+ * - Cancelling a WebTransport session by the application:
+ * The application calls Http3WebTransportSession::CloseSession. This
+ * transfers Http3WebTransportSession into the CLOSE_PENDING state and calls
+ * Http3Session::ConnectSlowConsumer to add itself to the “ready for reading
+ * queue”. Consequently, the Http3Session will call
+ * Http3WebTransportSession::WriteSegments and
+ * Http3Session::CloseWebTransport will be called to send the closing signal
+ * to the peer. After this, the Http3WebTransportSession is in the state DONE
+ * and it will be removed from the Http3Session(the CloseStream function
+ * takes care of this).
+ * - The peer sending a session closing signal:
+ * Http3Session will receive a Http3Event::Tag::WebTransport event with value
+ * WebTransportEventExternal::Tag::SessionClosed. The
+ * Http3WebTransportSession::OnSessionClosed function for the corresponding
+ * stream wil be called. The function will inform the corresponding
+ * WebTransportSessionProxy by calling OnSessionClosed function. The
+ * Http3WebTransportSession is in the state DONE and will be removed from the
+ * Http3Session(the CloseStream function takes care of this).
+ */
+
+namespace mozilla::net {
+
+class HttpConnectionUDP;
+class Http3StreamBase;
+class QuicSocketControl;
+
+// IID for the Http3Session interface
+#define NS_HTTP3SESSION_IID \
+ { \
+ 0x8fc82aaf, 0xc4ef, 0x46ed, { \
+ 0x89, 0x41, 0x93, 0x95, 0x8f, 0xac, 0x4f, 0x21 \
+ } \
+ }
+
+enum class EchExtensionStatus {
+ kNotPresent, // No ECH Extension was sent
+ kGREASE, // A GREASE ECH Extension was sent
+ kReal // A 'real' ECH Extension was sent
+};
+
+class Http3Session final : public nsAHttpTransaction, public nsAHttpConnection {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTP3SESSION_IID)
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_NSAHTTPCONNECTION(mConnection)
+
+ Http3Session();
+ nsresult Init(const nsHttpConnectionInfo* aConnInfo, nsINetAddr* selfAddr,
+ nsINetAddr* peerAddr, HttpConnectionUDP* udpConn,
+ uint32_t controlFlags, nsIInterfaceRequestor* callbacks);
+
+ bool IsConnected() const { return mState == CONNECTED; }
+ bool CanSendData() const {
+ return (mState == CONNECTED) || (mState == ZERORTT);
+ }
+ bool IsClosing() const { return (mState == CLOSING || mState == CLOSED); }
+ bool IsClosed() const { return mState == CLOSED; }
+
+ bool AddStream(nsAHttpTransaction* aHttpTransaction, int32_t aPriority,
+ nsIInterfaceRequestor* aCallbacks);
+
+ bool CanReuse();
+
+ // The following functions are used by Http3Stream and
+ // Http3WebTransportSession:
+ nsresult TryActivating(const nsACString& aMethod, const nsACString& aScheme,
+ const nsACString& aAuthorityHeader,
+ const nsACString& aPath, const nsACString& aHeaders,
+ uint64_t* aStreamId, Http3StreamBase* aStream);
+ // The folowing functions are used by Http3Stream:
+ void CloseSendingSide(uint64_t aStreamId);
+ nsresult SendRequestBody(uint64_t aStreamId, const char* buf, uint32_t count,
+ uint32_t* countRead);
+ nsresult ReadResponseHeaders(uint64_t aStreamId,
+ nsTArray<uint8_t>& aResponseHeaders, bool* aFin);
+ nsresult ReadResponseData(uint64_t aStreamId, char* aBuf, uint32_t aCount,
+ uint32_t* aCountWritten, bool* aFin);
+
+ // The folowing functions are used by Http3WebTransportSession:
+ nsresult CloseWebTransport(uint64_t aSessionId, uint32_t aError,
+ const nsACString& aMessage);
+ nsresult CreateWebTransportStream(uint64_t aSessionId,
+ WebTransportStreamType aStreamType,
+ uint64_t* aStreamId);
+ void CloseStream(Http3StreamBase* aStream, nsresult aResult);
+ void CloseStreamInternal(Http3StreamBase* aStream, nsresult aResult);
+
+ void SetCleanShutdown(bool aCleanShutdown) {
+ mCleanShutdown = aCleanShutdown;
+ }
+
+ bool TestJoinConnection(const nsACString& hostname, int32_t port);
+ bool JoinConnection(const nsACString& hostname, int32_t port);
+
+ void TransactionHasDataToWrite(nsAHttpTransaction* caller) override;
+ void TransactionHasDataToRecv(nsAHttpTransaction* caller) override;
+ [[nodiscard]] nsresult GetTransactionTLSSocketControl(
+ nsITLSSocketControl**) override;
+
+ // This function will be called by QuicSocketControl when the certificate
+ // verification is done.
+ void Authenticated(int32_t aError);
+
+ nsresult ProcessOutputAndEvents(nsIUDPSocket* socket);
+
+ void ReportHttp3Connection();
+
+ int64_t GetBytesWritten() { return mTotalBytesWritten; }
+ int64_t BytesRead() { return mTotalBytesRead; }
+
+ nsresult SendData(nsIUDPSocket* socket);
+ nsresult RecvData(nsIUDPSocket* socket);
+
+ void DoSetEchConfig(const nsACString& aEchConfig);
+
+ nsresult SendPriorityUpdateFrame(uint64_t aStreamId, uint8_t aPriorityUrgency,
+ bool aPriorityIncremental);
+
+ void ConnectSlowConsumer(Http3StreamBase* stream);
+
+ nsresult TryActivatingWebTransportStream(uint64_t* aStreamId,
+ Http3StreamBase* aStream);
+ void CloseWebTransportStream(Http3WebTransportStream* aStream,
+ nsresult aResult);
+ void StreamHasDataToWrite(Http3StreamBase* aStream);
+ void ResetWebTransportStream(Http3WebTransportStream* aStream,
+ uint64_t aErrorCode);
+ void StreamStopSending(Http3WebTransportStream* aStream, uint8_t aErrorCode);
+
+ void SendDatagram(Http3WebTransportSession* aSession,
+ nsTArray<uint8_t>& aData, uint64_t aTrackingId);
+
+ uint64_t MaxDatagramSize(uint64_t aSessionId);
+
+ void SetSendOrder(Http3StreamBase* aStream, Maybe<int64_t> aSendOrder);
+
+ void CloseWebTransportConn();
+
+ private:
+ ~Http3Session();
+
+ void CloseInternal(bool aCallNeqoClose);
+ void Shutdown();
+
+ bool RealJoinConnection(const nsACString& hostname, int32_t port,
+ bool justKidding);
+
+ nsresult ProcessOutput(nsIUDPSocket* socket);
+ void ProcessInput(nsIUDPSocket* socket);
+ nsresult ProcessEvents();
+
+ nsresult ProcessTransactionRead(uint64_t stream_id);
+ nsresult ProcessTransactionRead(Http3StreamBase* stream);
+ nsresult ProcessSlowConsumers();
+
+ void SetupTimer(uint64_t aTimeout);
+
+ enum ResetType {
+ RESET,
+ STOP_SENDING,
+ };
+ void ResetOrStopSendingRecvd(uint64_t aStreamId, uint64_t aError,
+ ResetType aType);
+
+ void QueueStream(Http3StreamBase* stream);
+ void RemoveStreamFromQueues(Http3StreamBase*);
+ void ProcessPending();
+
+ void CallCertVerification(Maybe<nsCString> aEchPublicName);
+ void SetSecInfo();
+
+ void EchOutcomeTelemetry();
+
+ void StreamReadyToWrite(Http3StreamBase* aStream);
+ void MaybeResumeSend();
+
+ void CloseConnectionTelemetry(CloseError& aError, bool aClosing);
+ void Finish0Rtt(bool aRestart);
+
+ enum ZeroRttOutcome {
+ NOT_USED,
+ USED_SUCCEEDED,
+ USED_REJECTED,
+ USED_CONN_ERROR,
+ USED_CONN_CLOSED_BY_NECKO
+ };
+ void ZeroRttTelemetry(ZeroRttOutcome aOutcome);
+
+ RefPtr<NeqoHttp3Conn> mHttp3Connection;
+ RefPtr<nsAHttpConnection> mConnection;
+ // We need an extra map to store the mapping of WebTransportSession and
+ // WebTransportStreams to handle the case that a stream is already removed
+ // from mStreamIdHash and we still need the WebTransportSession.
+ nsTHashMap<nsUint64HashKey, uint64_t> mWebTransportStreamToSessionMap;
+ nsRefPtrHashtable<nsUint64HashKey, Http3StreamBase> mStreamIdHash;
+ nsRefPtrHashtable<nsPtrHashKey<nsAHttpTransaction>, Http3StreamBase>
+ mStreamTransactionHash;
+
+ nsRefPtrDeque<Http3StreamBase> mReadyForWrite;
+
+ nsTArray<RefPtr<Http3StreamBase>> mSlowConsumersReadyForRead;
+ nsRefPtrDeque<Http3StreamBase> mQueuedStreams;
+
+ enum State {
+ INITIALIZING,
+ ZERORTT,
+ CONNECTED,
+ CLOSING,
+ CLOSED
+ } mState{INITIALIZING};
+
+ bool mAuthenticationStarted{false};
+ bool mCleanShutdown{false};
+ bool mGoawayReceived{false};
+ bool mShouldClose{false};
+ bool mIsClosedByNeqo{false};
+ bool mHttp3ConnectionReported = false;
+ // mError is neqo error (a protocol error) and that may mean that we will
+ // send some packets after that.
+ nsresult mError{NS_OK};
+ // This is a socket error, there is no poioint in sending anything on that
+ // socket.
+ nsresult mSocketError{NS_OK};
+ bool mBeforeConnectedError{false};
+ uint64_t mCurrentBrowserId;
+
+ // True if the mTimer is inited and waiting for firing.
+ bool mTimerActive{false};
+
+ RefPtr<HttpConnectionUDP> mUdpConn;
+
+ // The underlying socket transport object is needed to propogate some events
+ RefPtr<nsISocketTransport> mSocketTransport;
+
+ nsCOMPtr<nsITimer> mTimer;
+
+ nsTHashMap<nsCStringHashKey, bool> mJoinConnectionCache;
+
+ RefPtr<QuicSocketControl> mSocketControl;
+
+ uint64_t mTransactionCount = 0;
+
+ // The stream(s) that we are getting 0RTT data from.
+ nsTArray<WeakPtr<Http3StreamBase>> m0RTTStreams;
+ // The stream(s) that are not able to send 0RTT data. We need to
+ // remember them put them into mReadyForWrite queue when 0RTT finishes.
+ nsTArray<WeakPtr<Http3StreamBase>> mCannotDo0RTTStreams;
+
+ // The following variables are needed for telemetry.
+ TimeStamp mConnectionIdleStart;
+ TimeStamp mConnectionIdleEnd;
+ Maybe<uint64_t> mFirstStreamIdReuseIdleConnection;
+ TimeStamp mTimerShouldTrigger;
+ TimeStamp mZeroRttStarted;
+ uint64_t mBlockedByStreamLimitCount = 0;
+ uint64_t mTransactionsBlockedByStreamLimitCount = 0;
+ uint64_t mTransactionsSenderBlockedByFlowControlCount = 0;
+
+ // NS_NET_STATUS_CONNECTED_TO event will be created by the Http3Session.
+ // We want to propagate it to the first transaction.
+ RefPtr<nsHttpTransaction> mFirstHttpTransaction;
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+
+ bool mThroughCaptivePortal = false;
+ int64_t mTotalBytesRead = 0; // total data read
+ int64_t mTotalBytesWritten = 0; // total data read
+ PRIntervalTime mLastWriteTime = 0;
+
+ // Records whether we sent an ECH Extension and whether it was a GREASE Xtn
+ EchExtensionStatus mEchExtensionStatus = EchExtensionStatus::kNotPresent;
+
+ // Records whether the handshake finished successfully and we established a
+ // a connection.
+ bool mHandshakeSucceeded = false;
+
+ nsCOMPtr<nsINetAddr> mNetAddr;
+
+ enum WebTransportNegotiation { DISABLED, NEGOTIATING, FAILED, SUCCEEDED };
+ WebTransportNegotiation mWebTransportNegotiationStatus{
+ WebTransportNegotiation::DISABLED};
+
+ nsTArray<WeakPtr<Http3StreamBase>> mWaitingForWebTransportNegotiation;
+ // 1795854 implement the case when WebTransport is not supported.
+ // Also, implement the case when the HTTP/3 session fails before settings
+ // are exchanged.
+ void WebTransportNegotiationDone();
+
+ nsTArray<RefPtr<Http3StreamBase>> mWebTransportSessions;
+ nsTArray<RefPtr<Http3StreamBase>> mWebTransportStreams;
+
+ bool mHasWebTransportSession = false;
+ // When true, we don't add this connection info into the Http/3 excluded list.
+ bool mDontExclude = false;
+ // The lifetime of the UDP socket is managed by the HttpConnectionUDP. This
+ // is only used in Http3Session::ProcessOutput. Using raw pointer here to
+ // improve performance.
+ nsIUDPSocket* mSocket;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Http3Session, NS_HTTP3SESSION_IID);
+
+} // namespace mozilla::net
+
+#endif // Http3Session_H__
diff --git a/netwerk/protocol/http/Http3Stream.cpp b/netwerk/protocol/http/Http3Stream.cpp
new file mode 100644
index 0000000000..4f33ca07e2
--- /dev/null
+++ b/netwerk/protocol/http/Http3Stream.cpp
@@ -0,0 +1,533 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+#include "Http3Session.h"
+#include "Http3Stream.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpTransaction.h"
+#include "nsIClassOfService.h"
+#include "nsISocketTransport.h"
+#include "nsSocketTransportService2.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "nsIOService.h"
+#include "nsHttpHandler.h"
+
+#include <stdio.h>
+
+namespace mozilla {
+namespace net {
+
+Http3StreamBase::Http3StreamBase(nsAHttpTransaction* trans,
+ Http3Session* session)
+ : mTransaction(trans), mSession(session) {}
+
+Http3StreamBase::~Http3StreamBase() = default;
+
+Http3Stream::Http3Stream(nsAHttpTransaction* httpTransaction,
+ Http3Session* session, const ClassOfService& cos,
+ uint64_t currentBrowserId)
+ : Http3StreamBase(httpTransaction, session),
+ mCurrentBrowserId(currentBrowserId) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Stream::Http3Stream [this=%p]", this));
+
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (trans) {
+ mTransactionBrowserId = trans->BrowserId();
+ }
+
+ SetPriority(cos.Flags());
+ SetIncremental(cos.Incremental());
+}
+
+void Http3Stream::Close(nsresult aResult) {
+ mRecvState = RECV_DONE;
+ mTransaction->Close(aResult);
+ // Clear the mSession to break the cycle.
+ mSession = nullptr;
+}
+
+bool Http3Stream::GetHeadersString(const char* buf, uint32_t avail,
+ uint32_t* countUsed) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Stream::GetHeadersString %p avail=%u.", this, avail));
+
+ mFlatHttpRequestHeaders.Append(buf, avail);
+ // We can use the simple double crlf because firefox is the
+ // only client we are parsing
+ int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
+
+ if (endHeader == kNotFound) {
+ // We don't have all the headers yet
+ LOG3(
+ ("Http3Stream::GetHeadersString %p "
+ "Need more header bytes. Len = %zu",
+ this, mFlatHttpRequestHeaders.Length()));
+ *countUsed = avail;
+ return false;
+ }
+
+ uint32_t oldLen = mFlatHttpRequestHeaders.Length();
+ mFlatHttpRequestHeaders.SetLength(endHeader + 2);
+ *countUsed = avail - (oldLen - endHeader) + 4;
+
+ return true;
+}
+
+void Http3Stream::SetPriority(uint32_t aCos) {
+ if (aCos & nsIClassOfService::UrgentStart) {
+ // coming from an user interaction => response should be the highest
+ // priority
+ mPriorityUrgency = 1;
+ } else if (aCos & nsIClassOfService::Leader) {
+ // main html document normal priority
+ mPriorityUrgency = 2;
+ } else if (aCos & nsIClassOfService::Unblocked) {
+ mPriorityUrgency = 3;
+ } else if (aCos & nsIClassOfService::Follower) {
+ mPriorityUrgency = 4;
+ } else if (aCos & nsIClassOfService::Speculative) {
+ mPriorityUrgency = 6;
+ } else if (aCos & nsIClassOfService::Background) {
+ // background tasks can be deprioritzed to the lowest priority
+ mPriorityUrgency = 6;
+ } else if (aCos & nsIClassOfService::Tail) {
+ mPriorityUrgency = 6;
+ } else {
+ // all others get a lower priority than the main html document
+ mPriorityUrgency = 4;
+ }
+}
+
+void Http3Stream::SetIncremental(bool incremental) {
+ mPriorityIncremental = incremental;
+}
+
+nsresult Http3Stream::TryActivating() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3Stream::TryActivating [this=%p]", this));
+ nsHttpRequestHead* head = mTransaction->RequestHead();
+
+ nsAutoCString authorityHeader;
+ nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false);
+ return rv;
+ }
+
+ nsDependentCString scheme(head->IsHTTPS() ? "https" : "http");
+
+ nsAutoCString method;
+ nsAutoCString path;
+ head->Method(method);
+ head->Path(path);
+
+#ifdef DEBUG
+ nsAutoCString contentLength;
+ if (NS_SUCCEEDED(head->GetHeader(nsHttp::Content_Length, contentLength))) {
+ int64_t len;
+ if (nsHttp::ParseInt64(contentLength.get(), nullptr, &len)) {
+ mRequestBodyLenExpected = len;
+ }
+ }
+#endif
+
+ return mSession->TryActivating(method, scheme, authorityHeader, path,
+ mFlatHttpRequestHeaders, &mStreamId, this);
+}
+
+void Http3Stream::CurrentBrowserIdChanged(uint64_t id) {
+ MOZ_ASSERT(gHttpHandler->ActiveTabPriority());
+
+ bool previouslyFocused = (mCurrentBrowserId == mTransactionBrowserId);
+ mCurrentBrowserId = id;
+ bool nowFocused = (mCurrentBrowserId == mTransactionBrowserId);
+
+ if (!StaticPrefs::
+ network_http_http3_send_background_tabs_deprioritization() ||
+ previouslyFocused == nowFocused) {
+ return;
+ }
+
+ mSession->SendPriorityUpdateFrame(mStreamId, PriorityUrgency(),
+ PriorityIncremental());
+}
+
+nsresult Http3Stream::OnReadSegment(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3Stream::OnReadSegment count=%u state=%d [this=%p]", count,
+ mSendState, this));
+
+ nsresult rv = NS_OK;
+
+ switch (mSendState) {
+ case PREPARING_HEADERS: {
+ bool done = GetHeadersString(buf, count, countRead);
+
+ if (*countRead) {
+ mTotalSent += *countRead;
+ }
+
+ if (!done) {
+ break;
+ }
+ mSendState = WAITING_TO_ACTIVATE;
+ }
+ [[fallthrough]];
+ case WAITING_TO_ACTIVATE:
+ rv = TryActivating();
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG3(("Http3Stream::OnReadSegment %p cannot activate now. queued.\n",
+ this));
+ rv = *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+ break;
+ }
+ if (NS_FAILED(rv)) {
+ LOG3(("Http3Stream::OnReadSegment %p cannot activate error=0x%" PRIx32
+ ".",
+ this, static_cast<uint32_t>(rv)));
+ break;
+ }
+
+ // Successfully activated.
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_SENDING_TO,
+ mTotalSent);
+
+ mSendState = SENDING_BODY;
+ break;
+ case SENDING_BODY: {
+ rv = mSession->SendRequestBody(mStreamId, buf, count, countRead);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSendingBlockedByFlowControlCount++;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http3Stream::OnReadSegment %p sending body returns "
+ "error=0x%" PRIx32 ".",
+ this, static_cast<uint32_t>(rv)));
+ break;
+ }
+
+#ifdef DEBUG
+ mRequestBodyLenSent += *countRead;
+#endif
+ mTotalSent += *countRead;
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_SENDING_TO,
+ mTotalSent);
+ } break;
+ case EARLY_RESPONSE:
+ // We do not need to send the rest of the request, so just ignore it.
+ *countRead = count;
+#ifdef DEBUG
+ mRequestBodyLenSent += count;
+#endif
+ break;
+ default:
+ MOZ_ASSERT(false, "We are done sending this request!");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ mSocketOutCondition = rv;
+
+ return mSocketOutCondition;
+}
+
+void Http3Stream::SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders,
+ bool aFin, bool interim) {
+ MOZ_ASSERT(mRecvState == BEFORE_HEADERS ||
+ mRecvState == READING_INTERIM_HEADERS);
+ mFlatResponseHeaders.AppendElements(aResponseHeaders);
+ mRecvState = (interim) ? READING_INTERIM_HEADERS : READING_HEADERS;
+ mDataReceived = true;
+ mFin = aFin;
+}
+
+void Http3Stream::StopSending() {
+ MOZ_ASSERT((mSendState == SENDING_BODY) || (mSendState == SEND_DONE));
+ if (mSendState == SENDING_BODY) {
+ mSendState = EARLY_RESPONSE;
+ }
+}
+
+nsresult Http3Stream::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3Stream::OnWriteSegment [this=%p, state=%d", this, mRecvState));
+ nsresult rv = NS_OK;
+ switch (mRecvState) {
+ case BEFORE_HEADERS: {
+ *countWritten = 0;
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ } break;
+ case READING_HEADERS:
+ case READING_INTERIM_HEADERS: {
+ // SetResponseHeaders should have been previously called.
+ MOZ_ASSERT(!mFlatResponseHeaders.IsEmpty(), "Headers empty!");
+ *countWritten = (mFlatResponseHeaders.Length() > count)
+ ? count
+ : mFlatResponseHeaders.Length();
+ memcpy(buf, mFlatResponseHeaders.Elements(), *countWritten);
+
+ mFlatResponseHeaders.RemoveElementsAt(0, *countWritten);
+ if (mFlatResponseHeaders.Length() == 0) {
+ if (mRecvState == READING_INTERIM_HEADERS) {
+ // neqo makes sure that fin cannot be received before the final
+ // headers are received.
+ MOZ_ASSERT(!mFin);
+ mRecvState = BEFORE_HEADERS;
+ } else {
+ mRecvState = mFin ? RECEIVED_FIN : READING_DATA;
+ }
+ }
+
+ if (*countWritten == 0) {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ } else {
+ mTotalRead += *countWritten;
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RECEIVING_FROM,
+ mTotalRead);
+ }
+ } break;
+ case READING_DATA: {
+ rv = mSession->ReadResponseData(mStreamId, buf, count, countWritten,
+ &mFin);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ if (*countWritten == 0) {
+ if (mFin) {
+ mRecvState = RECV_DONE;
+ rv = NS_BASE_STREAM_CLOSED;
+ } else {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ } else {
+ mTotalRead += *countWritten;
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RECEIVING_FROM,
+ mTotalRead);
+
+ if (mFin) {
+ mRecvState = RECEIVED_FIN;
+ }
+ }
+ } break;
+ case RECEIVED_FIN:
+ rv = NS_BASE_STREAM_CLOSED;
+ mRecvState = RECV_DONE;
+ break;
+ case RECV_DONE:
+ rv = NS_ERROR_UNEXPECTED;
+ }
+
+ // Remember the error received from lower layers. A stream pipe may overwrite
+ // it.
+ // If rv == NS_OK this will reset mSocketInCondition.
+ mSocketInCondition = rv;
+
+ return rv;
+}
+
+nsresult Http3Stream::ReadSegments() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mRecvState == RECV_DONE) {
+ // Don't transmit any request frames if the peer cannot respond or respone
+ // is already done.
+ LOG3(
+ ("Http3Stream %p ReadSegments request stream aborted due to"
+ " response side closure\n",
+ this));
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv = NS_OK;
+ uint32_t transactionBytes;
+ bool again = true;
+ do {
+ transactionBytes = 0;
+ rv = mSocketOutCondition = NS_OK;
+ LOG(("Http3Stream::ReadSegments state=%d [this=%p]", mSendState, this));
+ switch (mSendState) {
+ case WAITING_TO_ACTIVATE: {
+ // A transaction that had already generated its headers before it was
+ // queued at the session level (due to concurrency concerns) may not
+ // call onReadSegment off the ReadSegments() stack above.
+ LOG3(
+ ("Http3Stream %p ReadSegments forcing OnReadSegment call\n", this));
+ uint32_t wasted = 0;
+ nsresult rv2 = OnReadSegment("", 0, &wasted);
+ LOG3((" OnReadSegment returned 0x%08" PRIx32,
+ static_cast<uint32_t>(rv2)));
+ if (mSendState != SENDING_BODY) {
+ break;
+ }
+ }
+ // If we are in state SENDING_BODY we can continue sending data.
+ [[fallthrough]];
+ case PREPARING_HEADERS:
+ case SENDING_BODY: {
+ rv = mTransaction->ReadSegmentsAgain(
+ this, nsIOService::gDefaultSegmentSize, &transactionBytes, &again);
+ } break;
+ default:
+ transactionBytes = 0;
+ rv = NS_OK;
+ break;
+ }
+
+ LOG(("Http3Stream::ReadSegments rv=0x%" PRIx32 " read=%u sock-cond=%" PRIx32
+ " again=%d [this=%p]",
+ static_cast<uint32_t>(rv), transactionBytes,
+ static_cast<uint32_t>(mSocketOutCondition), again, this));
+
+ // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
+ if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) {
+ rv = NS_OK;
+ transactionBytes = 0;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to write any more data, then
+ // wait for the transaction to call ResumeSend.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else if (NS_FAILED(mSocketOutCondition)) {
+ if (mSocketOutCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSocketOutCondition;
+ }
+ again = false;
+ } else if (!transactionBytes) {
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_WAITING_FOR, 0);
+ mSession->CloseSendingSide(mStreamId);
+ mSendState = SEND_DONE;
+ Telemetry::Accumulate(
+ Telemetry::HTTP3_SENDING_BLOCKED_BY_FLOW_CONTROL_PER_TRANS,
+ mSendingBlockedByFlowControlCount);
+
+#ifdef DEBUG
+ MOZ_ASSERT(mRequestBodyLenSent == mRequestBodyLenExpected);
+#endif
+ rv = NS_OK;
+ again = false;
+ }
+ // write more to the socket until error or end-of-request...
+ } while (again && gHttpHandler->Active());
+ return rv;
+}
+
+nsresult Http3Stream::WriteSegments() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3Stream::WriteSegments [this=%p]", this));
+ nsresult rv = NS_OK;
+ uint32_t countWrittenSingle = 0;
+ bool again = true;
+
+ do {
+ mSocketInCondition = NS_OK;
+ countWrittenSingle = 0;
+ rv = mTransaction->WriteSegmentsAgain(
+ this, nsIOService::gDefaultSegmentSize, &countWrittenSingle, &again);
+ LOG(("Http3Stream::WriteSegments rv=0x%" PRIx32
+ " countWrittenSingle=%" PRIu32 " socketin=%" PRIx32 " [this=%p]",
+ static_cast<uint32_t>(rv), countWrittenSingle,
+ static_cast<uint32_t>(mSocketInCondition), this));
+ if (mTransaction->IsDone()) {
+ // If a transaction has read the amount of data specified in
+ // Content-Length it is marked as done.The Http3Stream should be
+ // marked as done as well to start the process of cleanup and
+ // closure.
+ mRecvState = RECV_DONE;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to take any more data, then
+ // wait for the transaction to call ResumeRecv.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else if (NS_FAILED(mSocketInCondition)) {
+ if (mSocketInCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSocketInCondition;
+ }
+ again = false;
+ }
+ // read more from the socket until error...
+ } while (again && gHttpHandler->Active());
+
+ return rv;
+}
+
+bool Http3Stream::Do0RTT() {
+ MOZ_ASSERT(mTransaction);
+ mAttempting0RTT = mTransaction->Do0RTT();
+ return mAttempting0RTT;
+}
+
+nsresult Http3Stream::Finish0RTT(bool aRestart) {
+ MOZ_ASSERT(mTransaction);
+ mAttempting0RTT = false;
+ nsresult rv = mTransaction->Finish0RTT(aRestart, false);
+ if (aRestart) {
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (trans) {
+ trans->Refused0RTT();
+ }
+
+ // Reset Http3Sream states as well.
+ mSendState = PREPARING_HEADERS;
+ mRecvState = BEFORE_HEADERS;
+ mStreamId = UINT64_MAX;
+ mFlatHttpRequestHeaders = "";
+ mQueued = false;
+ mDataReceived = false;
+ mResetRecv = false;
+ mFlatResponseHeaders.TruncateLength(0);
+ mTotalSent = 0;
+ mTotalRead = 0;
+ mFin = false;
+ mSendingBlockedByFlowControlCount = 0;
+ mSocketInCondition = NS_ERROR_NOT_INITIALIZED;
+ mSocketOutCondition = NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return rv;
+}
+
+uint8_t Http3Stream::PriorityUrgency() {
+ if (!StaticPrefs::network_http_http3_priority()) {
+ // send default priority which is equivalent to sending no priority
+ return 3;
+ }
+
+ if (StaticPrefs::network_http_http3_send_background_tabs_deprioritization() &&
+ mCurrentBrowserId != mTransactionBrowserId) {
+ // Low priority
+ return 6;
+ }
+ return mPriorityUrgency;
+}
+
+bool Http3Stream::PriorityIncremental() {
+ if (!StaticPrefs::network_http_http3_priority()) {
+ // send default priority which is equivalent to sending no priority
+ return false;
+ }
+ return mPriorityIncremental;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http3Stream.h b/netwerk/protocol/http/Http3Stream.h
new file mode 100644
index 0000000000..1048b20ef5
--- /dev/null
+++ b/netwerk/protocol/http/Http3Stream.h
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http3Stream_h
+#define mozilla_net_Http3Stream_h
+
+#include "nsAHttpTransaction.h"
+#include "ARefBase.h"
+#include "Http3StreamBase.h"
+#include "mozilla/WeakPtr.h"
+#include "nsIClassOfService.h"
+
+namespace mozilla {
+namespace net {
+
+class Http3Session;
+
+class Http3Stream final : public nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter,
+ public Http3StreamBase {
+ public:
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ // for RefPtr
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Http3Stream, override)
+
+ Http3Stream(nsAHttpTransaction*, Http3Session*, const ClassOfService&,
+ uint64_t);
+
+ Http3WebTransportSession* GetHttp3WebTransportSession() override {
+ return nullptr;
+ }
+ Http3WebTransportStream* GetHttp3WebTransportStream() override {
+ return nullptr;
+ }
+ Http3Stream* GetHttp3Stream() override { return this; }
+
+ nsresult TryActivating();
+
+ void CurrentBrowserIdChanged(uint64_t id);
+
+ [[nodiscard]] nsresult ReadSegments() override;
+ [[nodiscard]] nsresult WriteSegments() override;
+
+ bool Done() const override { return mRecvState == RECV_DONE; }
+
+ void Close(nsresult aResult) override;
+ bool RecvdData() const { return mDataReceived; }
+
+ void StopSending();
+
+ void SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders, bool fin,
+ bool interim) override;
+
+ // Mirrors nsAHttpTransaction
+ bool Do0RTT() override;
+ nsresult Finish0RTT(bool aRestart) override;
+
+ uint8_t PriorityUrgency();
+ bool PriorityIncremental();
+
+ private:
+ ~Http3Stream() = default;
+
+ bool GetHeadersString(const char* buf, uint32_t avail, uint32_t* countUsed);
+ nsresult StartRequest();
+
+ void SetPriority(uint32_t aCos);
+ void SetIncremental(bool incremental);
+
+ /**
+ * SendStreamState:
+ * While sending request:
+ * - PREPARING_HEADERS:
+ * In this state we are collecting the headers and in some cases also
+ * waiting to be able to create a new stream.
+ * We need to read all headers into a buffer before calling
+ * Http3Session::TryActivating. Neqo may not have place for a new
+ * stream if it hits MAX_STREAMS limit. In that case the steam will be
+ * queued and dequeue when neqo can again create new stream
+ * (RequestsCreatable will be called).
+ * If transaction has data to send state changes to SENDING_BODY,
+ * otherwise the state transfers to READING_HEADERS.
+ * - SENDING_BODY:
+ * The stream will be in this state while the transaction is sending
+ * request body. Http3Session::SendRequestBody will be call to give
+ * the data to neqo.
+ * After SENDING_BODY, the state transfers to READING_HEADERS.
+ * - EARLY_RESPONSE:
+ * The server may send STOP_SENDING frame with error HTTP_NO_ERROR.
+ * That error means that the server is not interested in the request
+ * body. In this state the server will just ignore the request body.
+ **/
+ enum SendStreamState {
+ PREPARING_HEADERS,
+ WAITING_TO_ACTIVATE,
+ SENDING_BODY,
+ EARLY_RESPONSE,
+ SEND_DONE
+ } mSendState{PREPARING_HEADERS};
+
+ /**
+ * RecvStreamState:
+ * - BEFORE_HEADERS:
+ * The stream has not received headers yet.
+ * - READING_HEADERS and READING_INTERIM_HEADERS:
+ * In this state Http3Session::ReadResponseHeaders will be called to
+ * read the response headers. All headers will be read at once into
+ * mFlatResponseHeaders. The stream will be in this state until all
+ * headers are given to the transaction.
+ * If the steam was in the READING_INTERIM_HEADERS state it will
+ * change back to the BEFORE_HEADERS state. If the stream has been
+ * in the READING_HEADERS state it will change to the READING_DATA
+ * state. If the stream was closed by the server after sending headers
+ * the stream will transit into RECEIVED_FIN state. neqo makes sure
+ * that response headers and data are received in the right order,
+ * e.g. 1xx cannot be received after a non-1xx response, fin cannot
+ * follow 1xx response, etc.
+ * - READING_DATA:
+ * In this state Http3Session::ReadResponseData will be called and the
+ * response body will be given to the transaction.
+ * This state may transfer to RECEIVED_FIN or DONE state.
+ * - DONE:
+ * The transaction is done.
+ **/
+ enum RecvStreamState {
+ BEFORE_HEADERS,
+ READING_HEADERS,
+ READING_INTERIM_HEADERS,
+ READING_DATA,
+ RECEIVED_FIN,
+ RECV_DONE
+ } mRecvState{BEFORE_HEADERS};
+
+ nsCString mFlatHttpRequestHeaders;
+ bool mDataReceived{false};
+ nsTArray<uint8_t> mFlatResponseHeaders;
+ uint64_t mTransactionBrowserId{0};
+ uint64_t mCurrentBrowserId;
+ uint8_t mPriorityUrgency{3}; // urgency field of http priority
+ bool mPriorityIncremental{false};
+
+ // For Progress Events
+ uint64_t mTotalSent{0};
+ uint64_t mTotalRead{0};
+
+ bool mAttempting0RTT = false;
+
+ uint32_t mSendingBlockedByFlowControlCount = 0;
+
+ nsresult mSocketInCondition = NS_ERROR_NOT_INITIALIZED;
+ nsresult mSocketOutCondition = NS_ERROR_NOT_INITIALIZED;
+
+#ifdef DEBUG
+ uint32_t mRequestBodyLenExpected{0};
+ uint32_t mRequestBodyLenSent{0};
+#endif
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http3Stream_h
diff --git a/netwerk/protocol/http/Http3StreamBase.h b/netwerk/protocol/http/Http3StreamBase.h
new file mode 100644
index 0000000000..9ca85de98e
--- /dev/null
+++ b/netwerk/protocol/http/Http3StreamBase.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http3StreamBase_h
+#define mozilla_net_Http3StreamBase_h
+
+#include "nsAHttpTransaction.h"
+#include "ARefBase.h"
+#include "mozilla/WeakPtr.h"
+#include "nsIClassOfService.h"
+
+namespace mozilla::net {
+
+class Http3Session;
+class Http3Stream;
+class Http3WebTransportSession;
+class Http3WebTransportStream;
+
+class Http3StreamBase : public SupportsWeakPtr, public ARefBase {
+ public:
+ Http3StreamBase(nsAHttpTransaction* trans, Http3Session* session);
+
+ virtual Http3WebTransportSession* GetHttp3WebTransportSession() = 0;
+ virtual Http3WebTransportStream* GetHttp3WebTransportStream() = 0;
+ virtual Http3Stream* GetHttp3Stream() = 0;
+
+ bool HasStreamId() const { return mStreamId != UINT64_MAX; }
+ uint64_t StreamId() const { return mStreamId; }
+
+ [[nodiscard]] virtual nsresult ReadSegments() = 0;
+ [[nodiscard]] virtual nsresult WriteSegments() = 0;
+
+ virtual bool Done() const = 0;
+
+ virtual void SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders, bool fin,
+ bool interim) = 0;
+
+ void SetQueued(bool aStatus) { mQueued = aStatus; }
+ bool Queued() const { return mQueued; }
+
+ virtual void Close(nsresult aResult) = 0;
+
+ nsAHttpTransaction* Transaction() { return mTransaction; }
+
+ // Mirrors nsAHttpTransaction
+ virtual bool Do0RTT() { return false; }
+ virtual nsresult Finish0RTT(bool aRestart) { return NS_OK; }
+
+ virtual bool RecvdFin() const { return mFin; }
+ virtual bool RecvdReset() const { return mResetRecv; }
+ virtual void SetRecvdReset() { mResetRecv = true; }
+
+ protected:
+ ~Http3StreamBase();
+
+ uint64_t mStreamId{UINT64_MAX};
+ int64_t mSendOrder{0};
+ bool mSendOrderIsSet{false};
+ RefPtr<nsAHttpTransaction> mTransaction;
+ RefPtr<Http3Session> mSession;
+ bool mQueued{false};
+ bool mFin{false};
+ bool mResetRecv{false};
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_Http3StreamBase_h
diff --git a/netwerk/protocol/http/Http3WebTransportSession.cpp b/netwerk/protocol/http/Http3WebTransportSession.cpp
new file mode 100644
index 0000000000..9ef4da70c0
--- /dev/null
+++ b/netwerk/protocol/http/Http3WebTransportSession.cpp
@@ -0,0 +1,518 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+#include "Http3WebTransportSession.h"
+#include "Http3WebTransportStream.h"
+#include "Http3Session.h"
+#include "Http3Stream.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpTransaction.h"
+#include "nsIClassOfService.h"
+#include "nsISocketTransport.h"
+#include "nsSocketTransportService2.h"
+#include "nsIOService.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla::net {
+
+Http3WebTransportSession::Http3WebTransportSession(nsAHttpTransaction* trans,
+ Http3Session* aHttp3Session)
+ : Http3StreamBase(trans, aHttp3Session) {}
+
+Http3WebTransportSession::~Http3WebTransportSession() = default;
+
+nsresult Http3WebTransportSession::ReadSegments() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportSession::ReadSegments %p mSendState=%d mRecvState=%d",
+ this, mSendState, mRecvState));
+ if (mSendState == PROCESSING_DATAGRAM) {
+ return NS_OK;
+ }
+
+ if ((mRecvState == RECV_DONE) || (mRecvState == ACTIVE) ||
+ (mRecvState == CLOSE_PENDING)) {
+ // Don't transmit any request frames if the peer cannot respond or respone
+ // is already done.
+ LOG3((
+ "Http3WebTransportSession %p ReadSegments request stream aborted due to"
+ " response side closure\n",
+ this));
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv = NS_OK;
+ uint32_t transactionBytes;
+ bool again = true;
+ do {
+ transactionBytes = 0;
+ rv = mSocketOutCondition = NS_OK;
+ LOG(("Http3WebTransportSession::ReadSegments state=%d [this=%p]",
+ mSendState, this));
+ switch (mSendState) {
+ case PREPARING_HEADERS: {
+ rv = mTransaction->ReadSegmentsAgain(
+ this, nsIOService::gDefaultSegmentSize, &transactionBytes, &again);
+ } break;
+ case WAITING_TO_ACTIVATE: {
+ // A transaction that had already generated its headers before it was
+ // queued at the session level (due to concurrency concerns) may not
+ // call onReadSegment off the ReadSegments() stack above.
+ LOG3(
+ ("Http3WebTransportSession %p ReadSegments forcing OnReadSegment "
+ "call\n",
+ this));
+ uint32_t wasted = 0;
+ nsresult rv2 = OnReadSegment("", 0, &wasted);
+ LOG3((" OnReadSegment returned 0x%08" PRIx32,
+ static_cast<uint32_t>(rv2)));
+ } break;
+ default:
+ transactionBytes = 0;
+ rv = NS_OK;
+ break;
+ }
+
+ LOG(("Http3WebTransportSession::ReadSegments rv=0x%" PRIx32
+ " read=%u sock-cond=%" PRIx32 " again=%d [this=%p]",
+ static_cast<uint32_t>(rv), transactionBytes,
+ static_cast<uint32_t>(mSocketOutCondition), again, this));
+
+ // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
+ if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) {
+ rv = NS_OK;
+ transactionBytes = 0;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to write any more data, then
+ // wait for the transaction to call ResumeSend.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else if (NS_FAILED(mSocketOutCondition)) {
+ if (mSocketOutCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSocketOutCondition;
+ }
+ again = false;
+ } else if (!transactionBytes) {
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_WAITING_FOR, 0);
+
+ mSendState = PROCESSING_DATAGRAM;
+ rv = NS_OK;
+ again = false;
+ }
+ // write more to the socket until error or end-of-request...
+ } while (again && gHttpHandler->Active());
+ return rv;
+}
+
+bool Http3WebTransportSession::ConsumeHeaders(const char* buf, uint32_t avail,
+ uint32_t* countUsed) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3WebTransportSession::ConsumeHeaders %p avail=%u.", this, avail));
+
+ mFlatHttpRequestHeaders.Append(buf, avail);
+ // We can use the simple double crlf because firefox is the
+ // only client we are parsing
+ int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
+
+ if (endHeader == kNotFound) {
+ // We don't have all the headers yet
+ LOG3(
+ ("Http3WebTransportSession::ConsumeHeaders %p "
+ "Need more header bytes. Len = %zu",
+ this, mFlatHttpRequestHeaders.Length()));
+ *countUsed = avail;
+ return false;
+ }
+
+ uint32_t oldLen = mFlatHttpRequestHeaders.Length();
+ mFlatHttpRequestHeaders.SetLength(endHeader + 2);
+ *countUsed = avail - (oldLen - endHeader) + 4;
+
+ return true;
+}
+
+nsresult Http3WebTransportSession::TryActivating() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportSession::TryActivating [this=%p]", this));
+ nsHttpRequestHead* head = mTransaction->RequestHead();
+
+ nsAutoCString host;
+ nsresult rv = head->GetHeader(nsHttp::Host, host);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false);
+ return rv;
+ }
+ nsAutoCString path;
+ head->Path(path);
+
+ return mSession->TryActivating(""_ns, ""_ns, host, path,
+ mFlatHttpRequestHeaders, &mStreamId, this);
+}
+
+nsresult Http3WebTransportSession::OnReadSegment(const char* buf,
+ uint32_t count,
+ uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3WebTransportSession::OnReadSegment count=%u state=%d [this=%p]",
+ count, mSendState, this));
+
+ nsresult rv = NS_OK;
+
+ switch (mSendState) {
+ case PREPARING_HEADERS: {
+ if (!ConsumeHeaders(buf, count, countRead)) {
+ break;
+ }
+ mSendState = WAITING_TO_ACTIVATE;
+ }
+ [[fallthrough]];
+ case WAITING_TO_ACTIVATE:
+ rv = TryActivating();
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG3(
+ ("Http3WebTransportSession::OnReadSegment %p cannot activate now. "
+ "queued.\n",
+ this));
+ break;
+ }
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http3WebTransportSession::OnReadSegment %p cannot activate "
+ "error=0x%" PRIx32 ".",
+ this, static_cast<uint32_t>(rv)));
+ break;
+ }
+
+ // Successfully activated.
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_SENDING_TO, 0);
+
+ mSendState = PROCESSING_DATAGRAM;
+ break;
+ default:
+ MOZ_ASSERT(false, "We are done sending this request!");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ mSocketOutCondition = rv;
+
+ return mSocketOutCondition;
+}
+
+nsresult Http3WebTransportSession::WriteSegments() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportSession::WriteSegments [this=%p]", this));
+ nsresult rv = NS_OK;
+ uint32_t countWrittenSingle = 0;
+ bool again = true;
+
+ if (mRecvState == CLOSE_PENDING) {
+ mSession->CloseWebTransport(mStreamId, mStatus, mReason);
+ mRecvState = RECV_DONE;
+ // This will closed the steam because the stream is Done().
+ return NS_OK;
+ }
+
+ do {
+ mSocketInCondition = NS_OK;
+ countWrittenSingle = 0;
+ rv = mTransaction->WriteSegmentsAgain(
+ this, nsIOService::gDefaultSegmentSize, &countWrittenSingle, &again);
+ LOG(("Http3WebTransportSession::WriteSegments rv=0x%" PRIx32
+ " countWrittenSingle=%" PRIu32 " socketin=%" PRIx32 " [this=%p]",
+ static_cast<uint32_t>(rv), countWrittenSingle,
+ static_cast<uint32_t>(mSocketInCondition), this));
+ if (mTransaction->IsDone()) {
+ // An HTTP transaction used for setting up a WebTransport session will
+ // receive only response headers and afterward, it will be marked as
+ // done. At this point, the session negotiation has finished and the
+ // WebTransport session transfers into the ACTIVE state.
+ mRecvState = ACTIVE;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to take any more data, then
+ // wait for the transaction to call ResumeRecv.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else if (NS_FAILED(mSocketInCondition)) {
+ if (mSocketInCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSocketInCondition;
+ }
+ again = false;
+ }
+ // read more from the socket until error...
+ } while (again && gHttpHandler->Active());
+
+ return rv;
+}
+
+void Http3WebTransportSession::SetResponseHeaders(
+ nsTArray<uint8_t>& aResponseHeaders, bool fin, bool interim) {
+ MOZ_ASSERT(mRecvState == BEFORE_HEADERS ||
+ mRecvState == READING_INTERIM_HEADERS);
+ mFlatResponseHeaders.AppendElements(aResponseHeaders);
+ mRecvState = (interim) ? READING_INTERIM_HEADERS : READING_HEADERS;
+}
+
+nsresult Http3WebTransportSession::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3WebTransportSession::OnWriteSegment [this=%p, state=%d", this,
+ mRecvState));
+ nsresult rv = NS_OK;
+ switch (mRecvState) {
+ case BEFORE_HEADERS: {
+ *countWritten = 0;
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ } break;
+ case READING_HEADERS:
+ case READING_INTERIM_HEADERS: {
+ // SetResponseHeaders should have been previously called.
+ MOZ_ASSERT(!mFlatResponseHeaders.IsEmpty(), "Headers empty!");
+ *countWritten = (mFlatResponseHeaders.Length() > count)
+ ? count
+ : mFlatResponseHeaders.Length();
+ memcpy(buf, mFlatResponseHeaders.Elements(), *countWritten);
+
+ mFlatResponseHeaders.RemoveElementsAt(0, *countWritten);
+ if (mFlatResponseHeaders.Length() == 0) {
+ if (mRecvState == READING_INTERIM_HEADERS) {
+ // neqo makes sure that fin cannot be received before the final
+ // headers are received.
+ mRecvState = BEFORE_HEADERS;
+ } else {
+ mRecvState = ACTIVE;
+ }
+ }
+
+ if (*countWritten == 0) {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ } else {
+ mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RECEIVING_FROM,
+ 0);
+ }
+ } break;
+ case ACTIVE:
+ case CLOSE_PENDING:
+ case RECV_DONE:
+ rv = NS_ERROR_UNEXPECTED;
+ }
+
+ // Remember the error received from lower layers. A stream pipe may overwrite
+ // it.
+ // If rv == NS_OK this will reset mSocketInCondition.
+ mSocketInCondition = rv;
+
+ return rv;
+}
+
+void Http3WebTransportSession::Close(nsresult aResult) {
+ LOG(("Http3WebTransportSession::Close %p", this));
+ if (mListener) {
+ mListener->OnSessionClosed(NS_SUCCEEDED(aResult), 0, ""_ns);
+ mListener = nullptr;
+ }
+ if (mTransaction) {
+ mTransaction->Close(aResult);
+ mTransaction = nullptr;
+ }
+ mRecvState = RECV_DONE;
+ mSendState = SEND_DONE;
+
+ if (mSession) {
+ mSession->CloseWebTransportConn();
+ mSession = nullptr;
+ }
+}
+
+void Http3WebTransportSession::OnSessionClosed(bool aCleanly, uint32_t aStatus,
+ const nsACString& aReason) {
+ if (mTransaction) {
+ mTransaction->Close(NS_BASE_STREAM_CLOSED);
+ mTransaction = nullptr;
+ }
+ if (mListener) {
+ mListener->OnSessionClosed(aCleanly, aStatus, aReason);
+ mListener = nullptr;
+ }
+ mRecvState = RECV_DONE;
+ mSendState = SEND_DONE;
+
+ mSession->CloseWebTransportConn();
+}
+
+void Http3WebTransportSession::CloseSession(uint32_t aStatus,
+ const nsACString& aReason) {
+ if ((mRecvState != CLOSE_PENDING) && (mRecvState != RECV_DONE)) {
+ mStatus = aStatus;
+ mReason = aReason;
+ mSession->ConnectSlowConsumer(this);
+ mRecvState = CLOSE_PENDING;
+ mSendState = SEND_DONE;
+ }
+ mListener = nullptr;
+}
+
+void Http3WebTransportSession::TransactionIsDone(nsresult aResult) {
+ mTransaction->Close(aResult);
+ mTransaction = nullptr;
+}
+
+void Http3WebTransportSession::CreateOutgoingBidirectionalStream(
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback) {
+ return CreateStreamInternal(true, std::move(aCallback));
+}
+
+void Http3WebTransportSession::CreateOutgoingUnidirectionalStream(
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback) {
+ return CreateStreamInternal(false, std::move(aCallback));
+}
+
+void Http3WebTransportSession::CreateStreamInternal(
+ bool aBidi,
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback) {
+ LOG(("Http3WebTransportSession::CreateStreamInternal this=%p aBidi=%d", this,
+ aBidi));
+ if (mRecvState != ACTIVE) {
+ aCallback(Err(NS_ERROR_NOT_AVAILABLE));
+ return;
+ }
+
+ RefPtr<Http3WebTransportStream> stream =
+ aBidi ? new Http3WebTransportStream(mSession, mStreamId,
+ WebTransportStreamType::BiDi,
+ std::move(aCallback))
+ : new Http3WebTransportStream(mSession, mStreamId,
+ WebTransportStreamType::UniDi,
+ std::move(aCallback));
+ mSession->StreamHasDataToWrite(stream);
+ // Put the newly created stream in to |mStreams| to keep it alive.
+ mStreams.AppendElement(std::move(stream));
+}
+
+// This is called by Http3Session::TryActivatingWebTransportStream. When called,
+// this means a WebTransport stream is successfully activated and the stream
+// will be managed by Http3Session.
+void Http3WebTransportSession::RemoveWebTransportStream(
+ Http3WebTransportStream* aStream) {
+ LOG(
+ ("Http3WebTransportSession::RemoveWebTransportStream "
+ "this=%p aStream=%p",
+ this, aStream));
+ DebugOnly<bool> existed = mStreams.RemoveElement(aStream);
+ MOZ_ASSERT(existed);
+}
+
+already_AddRefed<Http3WebTransportStream>
+Http3WebTransportSession::OnIncomingWebTransportStream(
+ WebTransportStreamType aType, uint64_t aId) {
+ LOG(
+ ("Http3WebTransportSession::OnIncomingWebTransportStream "
+ "this=%p",
+ this));
+
+ if (mRecvState != ACTIVE) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!mTransaction);
+ RefPtr<Http3WebTransportStream> stream =
+ new Http3WebTransportStream(mSession, mStreamId, aType, aId);
+ if (NS_FAILED(stream->InitInputPipe())) {
+ return nullptr;
+ }
+
+ if (aType == WebTransportStreamType::BiDi) {
+ if (NS_FAILED(stream->InitOutputPipe())) {
+ return nullptr;
+ }
+ }
+
+ if (!mListener) {
+ return nullptr;
+ }
+
+ mListener->OnIncomingStreamAvailableInternal(stream);
+ return stream.forget();
+}
+
+void Http3WebTransportSession::SendDatagram(nsTArray<uint8_t>&& aData,
+ uint64_t aTrackingId) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportSession::SendDatagram this=%p", this));
+ if (mSendState != PROCESSING_DATAGRAM) {
+ return;
+ }
+
+ mSession->SendDatagram(this, aData, aTrackingId);
+ mSession->StreamHasDataToWrite(this);
+}
+
+void Http3WebTransportSession::OnDatagramReceived(nsTArray<uint8_t>&& aData) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportSession::OnDatagramReceived this=%p", this));
+ if (mRecvState != ACTIVE || !mListener) {
+ return;
+ }
+
+ mListener->OnDatagramReceivedInternal(std::move(aData));
+}
+
+void Http3WebTransportSession::GetMaxDatagramSize() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mRecvState != ACTIVE || !mListener) {
+ return;
+ }
+
+ uint64_t size = mSession->MaxDatagramSize(mStreamId);
+ mListener->OnMaxDatagramSize(size);
+}
+
+void Http3WebTransportSession::OnOutgoingDatagramOutCome(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportSession::OnOutgoingDatagramOutCome this=%p id=%" PRIx64
+ ", outCome=%d mRecvState=%d",
+ this, aId, static_cast<uint32_t>(aOutCome), mRecvState));
+ if (mRecvState != ACTIVE || !mListener || !aId) {
+ return;
+ }
+
+ mListener->OnOutgoingDatagramOutCome(aId, aOutCome);
+}
+
+void Http3WebTransportSession::OnStreamStopSending(uint64_t aId,
+ nsresult aError) {
+ LOG(("OnStreamStopSending id:%" PRId64, aId));
+ if (!mListener) {
+ return;
+ }
+
+ mListener->OnStopSending(aId, aError);
+}
+
+void Http3WebTransportSession::OnStreamReset(uint64_t aId, nsresult aError) {
+ LOG(("OnStreamReset id:%" PRId64, aId));
+ if (!mListener) {
+ return;
+ }
+
+ mListener->OnResetReceived(aId, aError);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/Http3WebTransportSession.h b/netwerk/protocol/http/Http3WebTransportSession.h
new file mode 100644
index 0000000000..19afb1f172
--- /dev/null
+++ b/netwerk/protocol/http/Http3WebTransportSession.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http3WebTransportSession_h
+#define mozilla_net_Http3WebTransportSession_h
+
+#include "ARefBase.h"
+#include "Http3StreamBase.h"
+#include "nsIWebTransport.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/net/NeqoHttp3Conn.h"
+
+namespace mozilla::net {
+
+class Http3Session;
+
+// TODO Http3WebTransportSession is very similar to Http3Stream. It should
+// be built on top of it with a couple of small changes. The docs will be added
+// when this is implemented.
+
+class Http3WebTransportSession final : public Http3StreamBase,
+ public nsAHttpSegmentWriter,
+ public nsAHttpSegmentReader {
+ public:
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Http3WebTransportSession, override)
+
+ Http3WebTransportSession(nsAHttpTransaction*, Http3Session*);
+
+ Http3WebTransportSession* GetHttp3WebTransportSession() override {
+ return this;
+ }
+ Http3WebTransportStream* GetHttp3WebTransportStream() override {
+ return nullptr;
+ }
+ Http3Stream* GetHttp3Stream() override { return nullptr; }
+
+ [[nodiscard]] nsresult ReadSegments() override;
+ [[nodiscard]] nsresult WriteSegments() override;
+
+ bool Done() const override { return mRecvState == RECV_DONE; }
+
+ void Close(nsresult aResult) override;
+
+ void SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders, bool fin,
+ bool interim) override;
+ void SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* listener) {
+ mListener = listener;
+ }
+
+ nsresult TryActivating();
+ void TransactionIsDone(nsresult aResult);
+ void CloseSession(uint32_t aStatus, const nsACString& aReason);
+ void OnSessionClosed(bool aCleanly, uint32_t aStatus,
+ const nsACString& aReason);
+
+ void CreateOutgoingBidirectionalStream(
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback);
+ void CreateOutgoingUnidirectionalStream(
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback);
+ void RemoveWebTransportStream(Http3WebTransportStream* aStream);
+
+ already_AddRefed<Http3WebTransportStream> OnIncomingWebTransportStream(
+ WebTransportStreamType aType, uint64_t aId);
+
+ void SendDatagram(nsTArray<uint8_t>&& aData, uint64_t aTrackingId);
+
+ void OnDatagramReceived(nsTArray<uint8_t>&& aData);
+
+ void GetMaxDatagramSize();
+
+ void OnOutgoingDatagramOutCome(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome);
+
+ void OnStreamStopSending(uint64_t aId, nsresult aError);
+ void OnStreamReset(uint64_t aId, nsresult aError);
+
+ private:
+ virtual ~Http3WebTransportSession();
+
+ bool ConsumeHeaders(const char* buf, uint32_t avail, uint32_t* countUsed);
+
+ void CreateStreamInternal(
+ bool aBidi,
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback);
+
+ enum RecvStreamState {
+ BEFORE_HEADERS,
+ READING_HEADERS,
+ READING_INTERIM_HEADERS,
+ ACTIVE,
+ CLOSE_PENDING,
+ RECV_DONE
+ } mRecvState{BEFORE_HEADERS};
+
+ enum SendStreamState {
+ PREPARING_HEADERS,
+ WAITING_TO_ACTIVATE,
+ PROCESSING_DATAGRAM,
+ SEND_DONE,
+ } mSendState{PREPARING_HEADERS};
+
+ nsCString mFlatHttpRequestHeaders;
+ nsTArray<uint8_t> mFlatResponseHeaders;
+ nsTArray<RefPtr<Http3WebTransportStream>> mStreams;
+
+ nsresult mSocketInCondition = NS_ERROR_NOT_INITIALIZED;
+ nsresult mSocketOutCondition = NS_ERROR_NOT_INITIALIZED;
+
+ RefPtr<WebTransportSessionEventListener> mListener;
+ uint32_t mStatus{0};
+ nsCString mReason;
+};
+
+} // namespace mozilla::net
+#endif // mozilla_net_Http3WebTransportSession_h
diff --git a/netwerk/protocol/http/Http3WebTransportStream.cpp b/netwerk/protocol/http/Http3WebTransportStream.cpp
new file mode 100644
index 0000000000..c76d8ef798
--- /dev/null
+++ b/netwerk/protocol/http/Http3WebTransportStream.cpp
@@ -0,0 +1,667 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Http3WebTransportStream.h"
+
+#include "HttpLog.h"
+#include "Http3Session.h"
+#include "Http3WebTransportSession.h"
+#include "mozilla/TimeStamp.h"
+#include "nsHttpHandler.h"
+#include "nsIOService.h"
+#include "nsIPipe.h"
+#include "nsSocketTransportService2.h"
+#include "nsIWebTransportStream.h"
+
+namespace mozilla::net {
+
+namespace {
+
+// This is an nsAHttpTransaction that does nothing.
+class DummyWebTransportStreamTransaction : public nsAHttpTransaction {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ DummyWebTransportStreamTransaction() = default;
+
+ void SetConnection(nsAHttpConnection*) override {}
+ nsAHttpConnection* Connection() override { return nullptr; }
+ void GetSecurityCallbacks(nsIInterfaceRequestor**) override {}
+ void OnTransportStatus(nsITransport* transport, nsresult status,
+ int64_t progress) override {}
+ bool IsDone() override { return false; }
+ nsresult Status() override { return NS_OK; }
+ uint32_t Caps() override { return 0; }
+ [[nodiscard]] nsresult ReadSegments(nsAHttpSegmentReader*, uint32_t,
+ uint32_t*) override {
+ return NS_OK;
+ }
+ [[nodiscard]] nsresult WriteSegments(nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*) override {
+ return NS_OK;
+ }
+ void Close(nsresult reason) override {}
+ nsHttpConnectionInfo* ConnectionInfo() override { return nullptr; }
+ void SetProxyConnectFailed() override {}
+ nsHttpRequestHead* RequestHead() override { return nullptr; }
+ uint32_t Http1xTransactionCount() override { return 0; }
+ [[nodiscard]] nsresult TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) override {
+ return NS_OK;
+ }
+
+ private:
+ virtual ~DummyWebTransportStreamTransaction() = default;
+};
+
+NS_IMPL_ISUPPORTS(DummyWebTransportStreamTransaction, nsISupportsWeakReference)
+
+class WebTransportSendStreamStats : public nsIWebTransportSendStreamStats {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit WebTransportSendStreamStats(uint64_t aSent, uint64_t aAcked)
+ : mTimeStamp(TimeStamp::Now()),
+ mTotalSent(aSent),
+ mTotalAcknowledged(aAcked) {}
+
+ NS_IMETHOD GetTimestamp(mozilla::TimeStamp* aTimestamp) override {
+ *aTimestamp = mTimeStamp;
+ return NS_OK;
+ }
+ NS_IMETHOD GetBytesSent(uint64_t* aBytesSent) override {
+ *aBytesSent = mTotalSent;
+ return NS_OK;
+ }
+ NS_IMETHOD GetBytesAcknowledged(uint64_t* aBytesAcknowledged) override {
+ *aBytesAcknowledged = mTotalAcknowledged;
+ return NS_OK;
+ }
+
+ private:
+ virtual ~WebTransportSendStreamStats() = default;
+
+ TimeStamp mTimeStamp;
+ uint64_t mTotalSent;
+ uint64_t mTotalAcknowledged;
+};
+
+NS_IMPL_ISUPPORTS(WebTransportSendStreamStats, nsIWebTransportSendStreamStats)
+
+class WebTransportReceiveStreamStats
+ : public nsIWebTransportReceiveStreamStats {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit WebTransportReceiveStreamStats(uint64_t aReceived)
+ : mTimeStamp(TimeStamp::Now()), mTotalReceived(aReceived) {}
+
+ NS_IMETHOD GetTimestamp(mozilla::TimeStamp* aTimestamp) override {
+ *aTimestamp = mTimeStamp;
+ return NS_OK;
+ }
+ NS_IMETHOD GetBytesReceived(uint64_t* aByteReceived) override {
+ *aByteReceived = mTotalReceived;
+ return NS_OK;
+ }
+
+ private:
+ virtual ~WebTransportReceiveStreamStats() = default;
+
+ TimeStamp mTimeStamp;
+ uint64_t mTotalReceived;
+};
+
+NS_IMPL_ISUPPORTS(WebTransportReceiveStreamStats,
+ nsIWebTransportReceiveStreamStats)
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(Http3WebTransportStream, nsIInputStreamCallback)
+
+Http3WebTransportStream::Http3WebTransportStream(
+ Http3Session* aSession, uint64_t aSessionId, WebTransportStreamType aType,
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback)
+ : Http3StreamBase(new DummyWebTransportStreamTransaction(), aSession),
+ mSessionId(aSessionId),
+ mStreamType(aType),
+ mStreamRole(OUTGOING),
+ mStreamReadyCallback(std::move(aCallback)) {
+ LOG(("Http3WebTransportStream outgoing ctor %p", this));
+}
+
+Http3WebTransportStream::Http3WebTransportStream(Http3Session* aSession,
+ uint64_t aSessionId,
+ WebTransportStreamType aType,
+ uint64_t aStreamId)
+ : Http3StreamBase(new DummyWebTransportStreamTransaction(), aSession),
+ mSessionId(aSessionId),
+ mStreamType(aType),
+ mStreamRole(INCOMING),
+ // WAITING_DATA indicates we are waiting
+ // Http3WebTransportStream::OnInputStreamReady to be called.
+ mSendState(WAITING_DATA),
+ mStreamReadyCallback(nullptr) {
+ LOG(("Http3WebTransportStream incoming ctor %p", this));
+ mStreamId = aStreamId;
+}
+
+Http3WebTransportStream::~Http3WebTransportStream() {
+ LOG(("Http3WebTransportStream dtor %p", this));
+}
+
+nsresult Http3WebTransportStream::TryActivating() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return mSession->TryActivatingWebTransportStream(&mStreamId, this);
+}
+
+NS_IMETHODIMP Http3WebTransportStream::OnInputStreamReady(
+ nsIAsyncInputStream* aStream) {
+ LOG1(
+ ("Http3WebTransportStream::OnInputStreamReady [this=%p stream=%p "
+ "state=%d]",
+ this, aStream, mSendState));
+ if (mSendState == SEND_DONE) {
+ // already closed
+ return NS_OK;
+ }
+
+ mSendState = SENDING;
+ mSession->StreamHasDataToWrite(this);
+ return NS_OK;
+}
+
+nsresult Http3WebTransportStream::InitOutputPipe() {
+ nsCOMPtr<nsIAsyncOutputStream> out;
+ nsCOMPtr<nsIAsyncInputStream> in;
+ NS_NewPipe2(getter_AddRefs(in), getter_AddRefs(out), true, true,
+ nsIOService::gDefaultSegmentSize,
+ nsIOService::gDefaultSegmentCount);
+
+ {
+ MutexAutoLock lock(mMutex);
+ mSendStreamPipeIn = std::move(in);
+ mSendStreamPipeOut = std::move(out);
+ }
+
+ nsresult rv =
+ mSendStreamPipeIn->AsyncWait(this, 0, 0, gSocketTransportService);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mSendState = WAITING_DATA;
+ return NS_OK;
+}
+
+nsresult Http3WebTransportStream::InitInputPipe() {
+ nsCOMPtr<nsIAsyncOutputStream> out;
+ nsCOMPtr<nsIAsyncInputStream> in;
+ NS_NewPipe2(getter_AddRefs(in), getter_AddRefs(out), true, true,
+ nsIOService::gDefaultSegmentSize,
+ nsIOService::gDefaultSegmentCount);
+
+ {
+ MutexAutoLock lock(mMutex);
+ mReceiveStreamPipeIn = std::move(in);
+ mReceiveStreamPipeOut = std::move(out);
+ }
+
+ mRecvState = READING;
+ return NS_OK;
+}
+
+void Http3WebTransportStream::GetWriterAndReader(
+ nsIAsyncOutputStream** aOutOutputStream,
+ nsIAsyncInputStream** aOutInputStream) {
+ nsCOMPtr<nsIAsyncOutputStream> output;
+ nsCOMPtr<nsIAsyncInputStream> input;
+ {
+ MutexAutoLock lock(mMutex);
+ output = mSendStreamPipeOut;
+ input = mReceiveStreamPipeIn;
+ }
+
+ output.forget(aOutOutputStream);
+ input.forget(aOutInputStream);
+}
+
+already_AddRefed<nsIWebTransportSendStreamStats>
+Http3WebTransportStream::GetSendStreamStats() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIWebTransportSendStreamStats> stats =
+ new WebTransportSendStreamStats(mTotalSent, mTotalAcknowledged);
+ return stats.forget();
+}
+
+already_AddRefed<nsIWebTransportReceiveStreamStats>
+Http3WebTransportStream::GetReceiveStreamStats() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIWebTransportReceiveStreamStats> stats =
+ new WebTransportReceiveStreamStats(mTotalReceived);
+ return stats.forget();
+}
+
+nsresult Http3WebTransportStream::OnReadSegment(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3WebTransportStream::OnReadSegment count=%u state=%d [this=%p]",
+ count, mSendState, this));
+
+ nsresult rv = NS_OK;
+
+ switch (mSendState) {
+ case WAITING_TO_ACTIVATE:
+ rv = TryActivating();
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG3(
+ ("Http3WebTransportStream::OnReadSegment %p cannot activate now. "
+ "queued.\n",
+ this));
+ break;
+ }
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http3WebTransportStream::OnReadSegment %p cannot activate "
+ "error=0x%" PRIx32 ".",
+ this, static_cast<uint32_t>(rv)));
+ mStreamReadyCallback(Err(rv));
+ mStreamReadyCallback = nullptr;
+ break;
+ }
+
+ rv = InitOutputPipe();
+ if (NS_SUCCEEDED(rv) && mStreamType == WebTransportStreamType::BiDi) {
+ rv = InitInputPipe();
+ }
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http3WebTransportStream::OnReadSegment %p failed to create pipe "
+ "error=0x%" PRIx32 ".",
+ this, static_cast<uint32_t>(rv)));
+ mSendState = SEND_DONE;
+ mStreamReadyCallback(Err(rv));
+ mStreamReadyCallback = nullptr;
+ break;
+ }
+
+ // Successfully activated.
+ mStreamReadyCallback(RefPtr{this});
+ mStreamReadyCallback = nullptr;
+ break;
+ case SENDING: {
+ rv = mSession->SendRequestBody(mStreamId, buf, count, countRead);
+ LOG3(
+ ("Http3WebTransportStream::OnReadSegment %p sending body returns "
+ "error=0x%" PRIx32 ".",
+ this, static_cast<uint32_t>(rv)));
+ mTotalSent += *countRead;
+ } break;
+ case WAITING_DATA:
+ // Still waiting
+ LOG3((
+ "Http3WebTransportStream::OnReadSegment %p Still waiting for data...",
+ this));
+ break;
+ case SEND_DONE:
+ LOG3(("Http3WebTransportStream::OnReadSegment %p called after SEND_DONE ",
+ this));
+ MOZ_ASSERT(false, "We are done sending this request!");
+ MOZ_ASSERT(mStreamReadyCallback);
+ rv = NS_ERROR_UNEXPECTED;
+ mStreamReadyCallback(Err(rv));
+ mStreamReadyCallback = nullptr;
+ break;
+ }
+
+ mSocketOutCondition = rv;
+
+ return mSocketOutCondition;
+}
+
+// static
+nsresult Http3WebTransportStream::ReadRequestSegment(
+ nsIInputStream* stream, void* closure, const char* buf, uint32_t offset,
+ uint32_t count, uint32_t* countRead) {
+ Http3WebTransportStream* wtStream = (Http3WebTransportStream*)closure;
+ nsresult rv = wtStream->OnReadSegment(buf, count, countRead);
+ LOG(("Http3WebTransportStream::ReadRequestSegment %p read=%u", wtStream,
+ *countRead));
+ return rv;
+}
+
+nsresult Http3WebTransportStream::ReadSegments() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportStream::ReadSegments [this=%p]", this));
+ nsresult rv = NS_OK;
+ uint32_t sendBytes = 0;
+ bool again = true;
+ do {
+ sendBytes = 0;
+ rv = mSocketOutCondition = NS_OK;
+ LOG(("Http3WebTransportStream::ReadSegments state=%d [this=%p]", mSendState,
+ this));
+ switch (mSendState) {
+ case WAITING_TO_ACTIVATE: {
+ LOG3(
+ ("Http3WebTransportStream %p ReadSegments forcing OnReadSegment "
+ "call\n",
+ this));
+ uint32_t wasted = 0;
+ nsresult rv2 = OnReadSegment("", 0, &wasted);
+ LOG3((" OnReadSegment returned 0x%08" PRIx32,
+ static_cast<uint32_t>(rv2)));
+ if (mSendState != WAITING_DATA) {
+ break;
+ }
+ }
+ [[fallthrough]];
+ case WAITING_DATA:
+ [[fallthrough]];
+ case SENDING: {
+ if (mStreamRole == INCOMING &&
+ mStreamType == WebTransportStreamType::UniDi) {
+ rv = NS_OK;
+ break;
+ }
+ mSendState = SENDING;
+ rv = mSendStreamPipeIn->ReadSegments(ReadRequestSegment, this,
+ nsIOService::gDefaultSegmentSize,
+ &sendBytes);
+ } break;
+ case SEND_DONE: {
+ return NS_OK;
+ }
+ default:
+ sendBytes = 0;
+ rv = NS_OK;
+ break;
+ }
+
+ LOG(("Http3WebTransportStream::ReadSegments rv=0x%" PRIx32
+ " read=%u sock-cond=%" PRIx32 " again=%d mSendFin=%d [this=%p]",
+ static_cast<uint32_t>(rv), sendBytes,
+ static_cast<uint32_t>(mSocketOutCondition), again, mSendFin, this));
+
+ // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
+ if (rv == NS_BASE_STREAM_CLOSED || !mPendingTasks.IsEmpty()) {
+ rv = NS_OK;
+ sendBytes = 0;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the writer didn't want to write any more data, then
+ // wait for the transaction to call ResumeSend.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSendState = WAITING_DATA;
+ rv = mSendStreamPipeIn->AsyncWait(this, 0, 0, gSocketTransportService);
+ }
+ again = false;
+
+ // Got a WebTransport specific error
+ if (rv >= NS_ERROR_WEBTRANSPORT_CODE_BASE &&
+ rv <= NS_ERROR_WEBTRANSPORT_CODE_END) {
+ uint8_t errorCode = GetWebTransportErrorFromNSResult(rv);
+ mSendState = SEND_DONE;
+ Reset(WebTransportErrorToHttp3Error(errorCode));
+ rv = NS_OK;
+ }
+ } else if (NS_FAILED(mSocketOutCondition)) {
+ if (mSocketOutCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSocketOutCondition;
+ }
+ again = false;
+ } else if (!sendBytes) {
+ mSendState = SEND_DONE;
+ rv = NS_OK;
+ again = false;
+ if (!mPendingTasks.IsEmpty()) {
+ LOG(("Has pending tasks to do"));
+ nsTArray<std::function<void()>> tasks = std::move(mPendingTasks);
+ for (const auto& task : tasks) {
+ task();
+ }
+ }
+ // Tell the underlying stream we're done
+ SendFin();
+ }
+
+ // write more to the socket until error or end-of-request...
+ } while (again && gHttpHandler->Active());
+ return rv;
+}
+
+nsresult Http3WebTransportStream::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3WebTransportStream::OnWriteSegment [this=%p, state=%d", this,
+ static_cast<uint32_t>(mRecvState)));
+ nsresult rv = NS_OK;
+ switch (mRecvState) {
+ case READING: {
+ rv = mSession->ReadResponseData(mStreamId, buf, count, countWritten,
+ &mFin);
+ if (*countWritten == 0) {
+ if (mFin) {
+ mRecvState = RECV_DONE;
+ rv = NS_BASE_STREAM_CLOSED;
+ } else {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ } else {
+ mTotalReceived += *countWritten;
+ if (mFin) {
+ mRecvState = RECEIVED_FIN;
+ }
+ }
+ } break;
+ case RECEIVED_FIN:
+ rv = NS_BASE_STREAM_CLOSED;
+ mRecvState = RECV_DONE;
+ break;
+ case RECV_DONE:
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ default:
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ // Remember the error received from lower layers. A stream pipe may overwrite
+ // it.
+ // If rv == NS_OK this will reset mSocketInCondition.
+ mSocketInCondition = rv;
+
+ return rv;
+}
+
+// static
+nsresult Http3WebTransportStream::WritePipeSegment(nsIOutputStream* stream,
+ void* closure, char* buf,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t* countWritten) {
+ Http3WebTransportStream* self = (Http3WebTransportStream*)closure;
+
+ nsresult rv = self->OnWriteSegment(buf, count, countWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ LOG(("Http3WebTransportStream::WritePipeSegment %p written=%u", self,
+ *countWritten));
+
+ return rv;
+}
+
+nsresult Http3WebTransportStream::WriteSegments() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mReceiveStreamPipeOut) {
+ return NS_OK;
+ }
+
+ LOG(("Http3WebTransportStream::WriteSegments [this=%p]", this));
+
+ nsresult rv = NS_OK;
+ uint32_t countWrittenSingle = 0;
+ bool again = true;
+
+ do {
+ mSocketInCondition = NS_OK;
+ countWrittenSingle = 0;
+ rv = mReceiveStreamPipeOut->WriteSegments(WritePipeSegment, this,
+ nsIOService::gDefaultSegmentSize,
+ &countWrittenSingle);
+ LOG(("Http3WebTransportStream::WriteSegments rv=0x%" PRIx32
+ " countWrittenSingle=%" PRIu32 " socketin=%" PRIx32 " [this=%p]",
+ static_cast<uint32_t>(rv), countWrittenSingle,
+ static_cast<uint32_t>(mSocketInCondition), this));
+ if (NS_FAILED(rv)) {
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else if (NS_FAILED(mSocketInCondition)) {
+ if (mSocketInCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSocketInCondition;
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ mReceiveStreamPipeOut->Close();
+ rv = NS_OK;
+ }
+ }
+ again = false;
+ }
+ // read more from the socket until error...
+ } while (again && gHttpHandler->Active());
+
+ return rv;
+}
+
+bool Http3WebTransportStream::Done() const {
+ return mSendState == SEND_DONE && mRecvState == RECV_DONE;
+}
+
+void Http3WebTransportStream::Close(nsresult aResult) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportStream::Close [this=%p]", this));
+ mTransaction = nullptr;
+ if (mSendStreamPipeIn) {
+ mSendStreamPipeIn->AsyncWait(nullptr, 0, 0, nullptr);
+ mSendStreamPipeIn->CloseWithStatus(aResult);
+ }
+ if (mReceiveStreamPipeOut) {
+ mReceiveStreamPipeOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mReceiveStreamPipeOut->CloseWithStatus(aResult);
+ }
+ mSendState = SEND_DONE;
+ mRecvState = RECV_DONE;
+ mSession = nullptr;
+}
+
+void Http3WebTransportStream::SendFin() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportStream::SendFin [this=%p mSendState=%d]", this,
+ mSendState));
+
+ if (mSendFin || !mSession || mResetError) {
+ // Already closed.
+ return;
+ }
+
+ mSendFin = true;
+
+ switch (mSendState) {
+ case SENDING: {
+ mPendingTasks.AppendElement([self = RefPtr{this}]() {
+ self->mSession->CloseSendingSide(self->mStreamId);
+ });
+ } break;
+ case WAITING_DATA:
+ mSendState = SEND_DONE;
+ [[fallthrough]];
+ case SEND_DONE:
+ mSession->CloseSendingSide(mStreamId);
+ // StreamHasDataToWrite needs to be called to trigger ProcessOutput.
+ mSession->StreamHasDataToWrite(this);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("invalid mSendState!");
+ break;
+ }
+}
+
+void Http3WebTransportStream::Reset(uint64_t aErrorCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportStream::Reset [this=%p, mSendState=%d]", this,
+ mSendState));
+
+ if (mResetError || !mSession || mSendFin) {
+ // The stream is already reset.
+ return;
+ }
+
+ mResetError = Some(aErrorCode);
+
+ switch (mSendState) {
+ case SENDING: {
+ LOG(("Http3WebTransportStream::Reset [this=%p] reset after sending data",
+ this));
+ mPendingTasks.AppendElement([self = RefPtr{this}]() {
+ // "Reset" needs a special treatment here. If we are sending data and
+ // ResetWebTransportStream is called before Http3Session::ProcessOutput,
+ // neqo will drop the last piece of data.
+ NS_DispatchToCurrentThread(
+ NS_NewRunnableFunction("Http3WebTransportStream::Reset", [self]() {
+ self->mSession->ResetWebTransportStream(self, *self->mResetError);
+ self->mSession->StreamHasDataToWrite(self);
+ self->mSession->ConnectSlowConsumer(self);
+ }));
+ });
+ } break;
+ case WAITING_DATA:
+ mSendState = SEND_DONE;
+ [[fallthrough]];
+ case SEND_DONE:
+ mSession->ResetWebTransportStream(this, *mResetError);
+ // StreamHasDataToWrite needs to be called to trigger ProcessOutput.
+ mSession->StreamHasDataToWrite(this);
+ mSession->ConnectSlowConsumer(this);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("invalid mSendState!");
+ break;
+ }
+}
+
+void Http3WebTransportStream::SendStopSending(uint8_t aErrorCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3WebTransportStream::SendStopSending [this=%p, mSendState=%d]",
+ this, mSendState));
+
+ if (mSendState == WAITING_TO_ACTIVATE) {
+ return;
+ }
+
+ if (mStopSendingError || !mSession) {
+ return;
+ }
+
+ mStopSendingError = Some(aErrorCode);
+
+ mSession->StreamStopSending(this, *mStopSendingError);
+ // StreamHasDataToWrite needs to be called to trigger ProcessOutput.
+ mSession->StreamHasDataToWrite(this);
+}
+
+void Http3WebTransportStream::SetSendOrder(Maybe<int64_t> aSendOrder) {
+ mSession->SetSendOrder(this, aSendOrder);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/Http3WebTransportStream.h b/netwerk/protocol/http/Http3WebTransportStream.h
new file mode 100644
index 0000000000..545e793246
--- /dev/null
+++ b/netwerk/protocol/http/Http3WebTransportStream.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http3WebTransportStream_h
+#define mozilla_net_Http3WebTransportStream_h
+
+#include <functional>
+#include "Http3StreamBase.h"
+#include "mozilla/net/NeqoHttp3Conn.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultVariant.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+
+class nsIWebTransportSendStreamStats;
+class nsIWebTransportReceiveStreamStats;
+
+namespace mozilla::net {
+
+class Http3WebTransportSession;
+
+class Http3WebTransportStream final : public Http3StreamBase,
+ public nsAHttpSegmentWriter,
+ public nsAHttpSegmentReader,
+ public nsIInputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+
+ explicit Http3WebTransportStream(
+ Http3Session* aSession, uint64_t aSessionId, WebTransportStreamType aType,
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>&&
+ aCallback);
+ explicit Http3WebTransportStream(Http3Session* aSession, uint64_t aSessionId,
+ WebTransportStreamType aType,
+ uint64_t aStreamId);
+
+ Http3WebTransportSession* GetHttp3WebTransportSession() override {
+ return nullptr;
+ }
+ Http3WebTransportStream* GetHttp3WebTransportStream() override {
+ return this;
+ }
+ Http3Stream* GetHttp3Stream() override { return nullptr; }
+
+ void SetSendOrder(Maybe<int64_t> aSendOrder);
+
+ [[nodiscard]] nsresult ReadSegments() override;
+ [[nodiscard]] nsresult WriteSegments() override;
+
+ bool Done() const override;
+ void Close(nsresult aResult) override;
+
+ void SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders, bool fin,
+ bool interim) override {}
+
+ uint64_t SessionId() const { return mSessionId; }
+ WebTransportStreamType StreamType() const { return mStreamType; }
+
+ void SendFin();
+ void Reset(uint64_t aErrorCode);
+ void SendStopSending(uint8_t aErrorCode);
+
+ already_AddRefed<nsIWebTransportSendStreamStats> GetSendStreamStats();
+ already_AddRefed<nsIWebTransportReceiveStreamStats> GetReceiveStreamStats();
+ void GetWriterAndReader(nsIAsyncOutputStream** aOutOutputStream,
+ nsIAsyncInputStream** aOutInputStream);
+
+ // When mRecvState is RECV_DONE, this means we already received the FIN.
+ bool RecvDone() const { return mRecvState == RECV_DONE; }
+
+ private:
+ friend class Http3WebTransportSession;
+ virtual ~Http3WebTransportStream();
+
+ nsresult TryActivating();
+ static nsresult ReadRequestSegment(nsIInputStream*, void*, const char*,
+ uint32_t, uint32_t, uint32_t*);
+ static nsresult WritePipeSegment(nsIOutputStream*, void*, char*, uint32_t,
+ uint32_t, uint32_t*);
+ nsresult InitOutputPipe();
+ nsresult InitInputPipe();
+
+ uint64_t mSessionId{UINT64_MAX};
+ WebTransportStreamType mStreamType{WebTransportStreamType::BiDi};
+
+ enum StreamRole {
+ INCOMING,
+ OUTGOING,
+ } mStreamRole{INCOMING};
+
+ enum SendStreamState {
+ WAITING_TO_ACTIVATE,
+ WAITING_DATA,
+ SENDING,
+ SEND_DONE,
+ } mSendState{WAITING_TO_ACTIVATE};
+
+ enum RecvStreamState { BEFORE_READING, READING, RECEIVED_FIN, RECV_DONE };
+ Atomic<RecvStreamState> mRecvState{BEFORE_READING};
+
+ nsresult mSocketOutCondition = NS_ERROR_NOT_INITIALIZED;
+ nsresult mSocketInCondition = NS_ERROR_NOT_INITIALIZED;
+
+ std::function<void(Result<RefPtr<Http3WebTransportStream>, nsresult>&&)>
+ mStreamReadyCallback;
+
+ Mutex mMutex{"Http3WebTransportStream::mMutex"};
+ nsCOMPtr<nsIAsyncInputStream> mSendStreamPipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSendStreamPipeOut MOZ_GUARDED_BY(mMutex);
+
+ nsCOMPtr<nsIAsyncInputStream> mReceiveStreamPipeIn MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsIAsyncOutputStream> mReceiveStreamPipeOut;
+
+ uint64_t mTotalSent = 0;
+ uint64_t mTotalReceived = 0;
+ // TODO: neqo doesn't expose this information for now.
+ uint64_t mTotalAcknowledged = 0;
+ bool mSendFin{false};
+ // The error code used to reset the stream. Should be only set once.
+ Maybe<uint64_t> mResetError;
+ // The error code used for STOP_SENDING. Should be only set once.
+ Maybe<uint8_t> mStopSendingError;
+
+ // This is used when SendFin or Reset is called when mSendState is SENDING.
+ nsTArray<std::function<void()>> mPendingTasks;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_Http3WebTransportStream_h
diff --git a/netwerk/protocol/http/HttpAuthUtils.cpp b/netwerk/protocol/http/HttpAuthUtils.cpp
new file mode 100644
index 0000000000..9bd34ef769
--- /dev/null
+++ b/netwerk/protocol/http/HttpAuthUtils.cpp
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/HttpAuthUtils.h"
+#include "mozilla/Tokenizer.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsUnicharUtils.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+
+namespace mozilla {
+namespace net {
+namespace auth {
+
+namespace detail {
+
+bool MatchesBaseURI(const nsACString& matchScheme, const nsACString& matchHost,
+ int32_t matchPort, nsDependentCSubstring const& url) {
+ // check if scheme://host:port matches baseURI
+
+ // parse the base URI
+ mozilla::Tokenizer t(url);
+ mozilla::Tokenizer::Token token;
+
+ t.SkipWhites();
+
+ // We don't know if the url to check against starts with scheme
+ // or a host name. Start recording here.
+ t.Record();
+
+ mozilla::Unused << t.Next(token);
+
+ // The ipv6 literals MUST be enclosed with [] in the preference.
+ bool ipv6 = false;
+ if (token.Equals(mozilla::Tokenizer::Token::Char('['))) {
+ nsDependentCSubstring ipv6BareLiteral;
+ if (!t.ReadUntil(mozilla::Tokenizer::Token::Char(']'), ipv6BareLiteral)) {
+ // Broken ipv6 literal
+ return false;
+ }
+
+ nsDependentCSubstring ipv6Literal;
+ t.Claim(ipv6Literal, mozilla::Tokenizer::INCLUDE_LAST);
+ if (!matchHost.Equals(ipv6Literal, nsCaseInsensitiveUTF8StringComparator) &&
+ !matchHost.Equals(ipv6BareLiteral,
+ nsCaseInsensitiveUTF8StringComparator)) {
+ return false;
+ }
+
+ ipv6 = true;
+ } else if (t.CheckChar(':') && t.CheckChar('/') && t.CheckChar('/')) {
+ if (!matchScheme.Equals(token.Fragment())) {
+ return false;
+ }
+ // Re-start recording the hostname from the point after scheme://.
+ t.Record();
+ }
+
+ while (t.Next(token)) {
+ bool eof = token.Equals(mozilla::Tokenizer::Token::EndOfFile());
+ bool port = token.Equals(mozilla::Tokenizer::Token::Char(':'));
+
+ if (eof || port) {
+ if (!ipv6) { // Match already performed above.
+ nsDependentCSubstring hostName;
+ t.Claim(hostName);
+
+ // An empty hostname means to accept everything for the schema
+ if (!hostName.IsEmpty()) {
+ /*
+ host: bar.com foo.bar.com foobar.com foo.bar.com bar.com
+ pref: bar.com bar.com bar.com .bar.com .bar.com
+ result: accept accept reject accept reject
+ */
+ if (!StringEndsWith(matchHost, hostName,
+ nsCaseInsensitiveUTF8StringComparator)) {
+ return false;
+ }
+ if (matchHost.Length() > hostName.Length() &&
+ matchHost[matchHost.Length() - hostName.Length() - 1] != '.' &&
+ hostName[0] != '.') {
+ return false;
+ }
+ }
+ }
+
+ if (port) {
+ uint16_t portNumber;
+ if (!t.ReadInteger(&portNumber)) {
+ // Missing port number
+ return false;
+ }
+ if (matchPort != portNumber) {
+ return false;
+ }
+ if (!t.CheckEOF()) {
+ return false;
+ }
+ }
+ } else if (ipv6) {
+ // After an ipv6 literal there can only be EOF or :port. Everything else
+ // must be treated as non-match/broken input.
+ return false;
+ }
+ }
+
+ // All negative checks has passed positively.
+ return true;
+}
+
+} // namespace detail
+
+bool URIMatchesPrefPattern(nsIURI* uri, const char* pref) {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) {
+ return false;
+ }
+
+ nsAutoCString scheme, host;
+ int32_t port;
+
+ if (NS_FAILED(uri->GetScheme(scheme))) {
+ return false;
+ }
+ if (NS_FAILED(uri->GetAsciiHost(host))) {
+ return false;
+ }
+
+ port = NS_GetRealPort(uri);
+ if (port == -1) {
+ return false;
+ }
+
+ nsAutoCString hostList;
+ if (NS_FAILED(prefs->GetCharPref(pref, hostList))) {
+ return false;
+ }
+
+ // pseudo-BNF
+ // ----------
+ //
+ // url-list base-url ( base-url "," LWS )*
+ // base-url ( scheme-part | host-part | scheme-part host-part )
+ // scheme-part scheme "://"
+ // host-part host [":" port]
+ //
+ // for example:
+ // "https://, http://office.foo.com"
+ //
+
+ mozilla::Tokenizer t(hostList);
+ while (!t.CheckEOF()) {
+ t.SkipWhites();
+ nsDependentCSubstring url;
+ mozilla::Unused << t.ReadUntil(mozilla::Tokenizer::Token::Char(','), url);
+ if (url.IsEmpty()) {
+ continue;
+ }
+ if (detail::MatchesBaseURI(scheme, host, port, url)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace auth
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpAuthUtils.h b/netwerk/protocol/http/HttpAuthUtils.h
new file mode 100644
index 0000000000..c045f81701
--- /dev/null
+++ b/netwerk/protocol/http/HttpAuthUtils.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpAuthUtils_h__
+#define HttpAuthUtils_h__
+
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+namespace auth {
+
+/* Tries to match the given URI against the value of a given pref
+ *
+ * The pref should be in pseudo-BNF format.
+ * url-list base-url ( base-url "," LWS )*
+ * base-url ( scheme-part | host-part | scheme-part host-part )
+ * scheme-part scheme "://"
+ * host-part host [":" port]
+ *
+ * for example:
+ * "https://, http://office.foo.com"
+ *
+ * Will return true if the URI matches any of the patterns, or false otherwise.
+ */
+bool URIMatchesPrefPattern(nsIURI* uri, const char* pref);
+
+} // namespace auth
+} // namespace net
+} // namespace mozilla
+
+#endif // HttpAuthUtils_h__
diff --git a/netwerk/protocol/http/HttpBackgroundChannelChild.cpp b/netwerk/protocol/http/HttpBackgroundChannelChild.cpp
new file mode 100644
index 0000000000..bb113064a4
--- /dev/null
+++ b/netwerk/protocol/http/HttpBackgroundChannelChild.cpp
@@ -0,0 +1,499 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HttpBackgroundChannelChild.h"
+
+#include "HttpChannelChild.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/net/BackgroundDataBridgeChild.h"
+#include "mozilla/Unused.h"
+#include "nsSocketTransportService2.h"
+
+using mozilla::ipc::BackgroundChild;
+using mozilla::ipc::IPCResult;
+
+namespace mozilla {
+namespace net {
+
+// HttpBackgroundChannelChild
+HttpBackgroundChannelChild::HttpBackgroundChannelChild() = default;
+
+HttpBackgroundChannelChild::~HttpBackgroundChannelChild() = default;
+
+nsresult HttpBackgroundChannelChild::Init(HttpChannelChild* aChannelChild) {
+ LOG(
+ ("HttpBackgroundChannelChild::Init [this=%p httpChannel=%p "
+ "channelId=%" PRIu64 "]\n",
+ this, aChannelChild, aChannelChild->ChannelId()));
+ MOZ_ASSERT(OnSocketThread());
+ NS_ENSURE_ARG(aChannelChild);
+
+ mChannelChild = aChannelChild;
+
+ if (NS_WARN_IF(!CreateBackgroundChannel())) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ // mChannelChild may be nulled already. Use aChannelChild
+ aChannelChild->mCreateBackgroundChannelFailed = true;
+#endif
+ mChannelChild = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ mFirstODASource = ODA_PENDING;
+ mOnStopRequestCalled = false;
+ return NS_OK;
+}
+
+void HttpBackgroundChannelChild::CreateDataBridge(
+ Endpoint<PBackgroundDataBridgeChild>&& aEndpoint) {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!mChannelChild) {
+ return;
+ }
+
+ RefPtr<BackgroundDataBridgeChild> dataBridgeChild =
+ new BackgroundDataBridgeChild(this);
+ aEndpoint.Bind(dataBridgeChild);
+}
+
+void HttpBackgroundChannelChild::OnChannelClosed() {
+ LOG(("HttpBackgroundChannelChild::OnChannelClosed [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (mChannelChild) {
+ mChannelChild->mBackgroundChildQueueFinalState =
+ mQueuedRunnables.IsEmpty() ? HttpChannelChild::BCKCHILD_EMPTY
+ : HttpChannelChild::BCKCHILD_NON_EMPTY;
+ }
+#endif
+
+ // HttpChannelChild is not going to handle any incoming message.
+ mChannelChild = nullptr;
+
+ // Remove pending IPC messages as well.
+ mQueuedRunnables.Clear();
+ mConsoleReportTask = nullptr;
+}
+
+bool HttpBackgroundChannelChild::ChannelClosed() {
+ MOZ_ASSERT(OnSocketThread());
+
+ return !mChannelChild;
+}
+
+void HttpBackgroundChannelChild::OnStartRequestReceived(
+ Maybe<uint32_t> aMultiPartID) {
+ LOG(("HttpBackgroundChannelChild::OnStartRequestReceived [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ MOZ_ASSERT(mChannelChild);
+ MOZ_ASSERT(!mStartReceived || *aMultiPartID > 0);
+
+ mStartReceived = true;
+
+ nsTArray<nsCOMPtr<nsIRunnable>> runnables = std::move(mQueuedRunnables);
+
+ for (const auto& event : runnables) {
+ // Note: these runnables call Recv* methods on HttpBackgroundChannelChild
+ // but not the Process* methods on HttpChannelChild.
+ event->Run();
+ }
+
+ // Ensure no new message is enqueued.
+ MOZ_ASSERT(mQueuedRunnables.IsEmpty());
+}
+
+bool HttpBackgroundChannelChild::CreateBackgroundChannel() {
+ LOG(("HttpBackgroundChannelChild::CreateBackgroundChannel [this=%p]\n",
+ this));
+ MOZ_ASSERT(OnSocketThread());
+ MOZ_ASSERT(mChannelChild);
+
+ PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!actorChild)) {
+ return false;
+ }
+
+ const uint64_t channelId = mChannelChild->ChannelId();
+ if (!actorChild->SendPHttpBackgroundChannelConstructor(this, channelId)) {
+ return false;
+ }
+
+ mChannelChild->OnBackgroundChildReady(this);
+ return true;
+}
+
+IPCResult HttpBackgroundChannelChild::RecvOnAfterLastPart(
+ const nsresult& aStatus) {
+ LOG(("HttpBackgroundChannelChild::RecvOnAfterLastPart [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ mChannelChild->ProcessOnAfterLastPart(aStatus);
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvOnProgress(
+ const int64_t& aProgress, const int64_t& aProgressMax) {
+ LOG(("HttpBackgroundChannelChild::RecvOnProgress [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ mChannelChild->ProcessOnProgress(aProgress, aProgressMax);
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvOnStatus(const nsresult& aStatus) {
+ LOG(("HttpBackgroundChannelChild::RecvOnStatus [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ mChannelChild->ProcessOnStatus(aStatus);
+ return IPC_OK();
+}
+
+bool HttpBackgroundChannelChild::IsWaitingOnStartRequest() {
+ MOZ_ASSERT(OnSocketThread());
+
+ // Need to wait for OnStartRequest if it is sent by
+ // parent process but not received by content process.
+ return !mStartReceived;
+}
+
+// PHttpBackgroundChannelChild
+IPCResult HttpBackgroundChannelChild::RecvOnStartRequest(
+ const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs,
+ const HttpChannelAltDataStream& aAltData,
+ const TimeStamp& aOnStartRequestStart) {
+ LOG((
+ "HttpBackgroundChannelChild::RecvOnStartRequest [this=%p, status=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(aArgs.channelStatus())));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ mFirstODASource =
+ aArgs.dataFromSocketProcess() ? ODA_FROM_SOCKET : ODA_FROM_PARENT;
+
+ mChannelChild->ProcessOnStartRequest(aResponseHead, aUseResponseHead,
+ aRequestHeaders, aArgs, aAltData,
+ aOnStartRequestStart);
+ // Allow to queue other runnable since OnStartRequest Event already hits the
+ // child's mEventQ.
+ OnStartRequestReceived(aArgs.multiPartID());
+
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvOnTransportAndData(
+ const nsresult& aChannelStatus, const nsresult& aTransportStatus,
+ const uint64_t& aOffset, const uint32_t& aCount, const nsACString& aData,
+ const bool& aDataFromSocketProcess,
+ const TimeStamp& aOnDataAvailableStart) {
+ RefPtr<HttpBackgroundChannelChild> self = this;
+ std::function<void()> callProcessOnTransportAndData =
+ [self, aChannelStatus, aTransportStatus, aOffset, aCount,
+ data = nsCString(aData), aDataFromSocketProcess,
+ aOnDataAvailableStart]() {
+ LOG(
+ ("HttpBackgroundChannelChild::RecvOnTransportAndData [this=%p, "
+ "aDataFromSocketProcess=%d, mFirstODASource=%d]\n",
+ self.get(), aDataFromSocketProcess, self->mFirstODASource));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!self->mChannelChild)) {
+ return;
+ }
+
+ if (((self->mFirstODASource == ODA_FROM_SOCKET) &&
+ !aDataFromSocketProcess) ||
+ ((self->mFirstODASource == ODA_FROM_PARENT) &&
+ aDataFromSocketProcess)) {
+ return;
+ }
+
+ // The HttpTransactionChild in socket process may not know that this
+ // request is cancelled or failed due to the IPC delay. In this case, we
+ // should not forward ODA to HttpChannelChild.
+ nsresult channelStatus;
+ self->mChannelChild->GetStatus(&channelStatus);
+ if (NS_FAILED(channelStatus)) {
+ return;
+ }
+
+ self->mChannelChild->ProcessOnTransportAndData(
+ aChannelStatus, aTransportStatus, aOffset, aCount, data,
+ aOnDataAvailableStart);
+ };
+
+ // Bug 1641336: Race only happens if the data is from socket process.
+ if (IsWaitingOnStartRequest()) {
+ LOG((" > pending until OnStartRequest [offset=%" PRIu64 " count=%" PRIu32
+ "]\n",
+ aOffset, aCount));
+
+ mQueuedRunnables.AppendElement(NS_NewRunnableFunction(
+ "HttpBackgroundChannelChild::RecvOnTransportAndData",
+ std::move(callProcessOnTransportAndData)));
+ return IPC_OK();
+ }
+
+ callProcessOnTransportAndData();
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvOnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers,
+ nsTArray<ConsoleReportCollected>&& aConsoleReports,
+ const bool& aFromSocketProcess, const TimeStamp& aOnStopRequestStart) {
+ LOG(
+ ("HttpBackgroundChannelChild::RecvOnStopRequest [this=%p, "
+ "aFromSocketProcess=%d, mFirstODASource=%d]\n",
+ this, aFromSocketProcess, mFirstODASource));
+ MOZ_ASSERT(gSocketTransportService);
+ MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible());
+
+ // It's enough to set this from (just before) OnStopRequest notification,
+ // since we don't need this value sooner than a channel was done loading -
+ // everything this timestamp affects takes place only after a channel's
+ // OnStopRequest.
+ nsHttp::SetLastActiveTabLoadOptimizationHit(aLastActiveTabOptHit);
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ if (IsWaitingOnStartRequest()) {
+ LOG((" > pending until OnStartRequest [status=%" PRIx32 "]\n",
+ static_cast<uint32_t>(aChannelStatus)));
+
+ RefPtr<HttpBackgroundChannelChild> self = this;
+
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
+ "HttpBackgroundChannelChild::RecvOnStopRequest",
+ [self, aChannelStatus, aTiming, aLastActiveTabOptHit, aResponseTrailers,
+ consoleReports = CopyableTArray{std::move(aConsoleReports)},
+ aFromSocketProcess, aOnStopRequestStart]() mutable {
+ self->RecvOnStopRequest(aChannelStatus, aTiming, aLastActiveTabOptHit,
+ aResponseTrailers, std::move(consoleReports),
+ aFromSocketProcess, aOnStopRequestStart);
+ });
+
+ mQueuedRunnables.AppendElement(task.forget());
+ return IPC_OK();
+ }
+
+ if (mFirstODASource != ODA_FROM_SOCKET) {
+ if (!aFromSocketProcess) {
+ mOnStopRequestCalled = true;
+ mChannelChild->ProcessOnStopRequest(
+ aChannelStatus, aTiming, aResponseTrailers,
+ std::move(aConsoleReports), false, aOnStopRequestStart);
+ }
+ return IPC_OK();
+ }
+
+ MOZ_ASSERT(mFirstODASource == ODA_FROM_SOCKET);
+
+ if (aFromSocketProcess) {
+ MOZ_ASSERT(!mOnStopRequestCalled);
+ mOnStopRequestCalled = true;
+ mChannelChild->ProcessOnStopRequest(
+ aChannelStatus, aTiming, aResponseTrailers, std::move(aConsoleReports),
+ true, aOnStopRequestStart);
+ if (mConsoleReportTask) {
+ mConsoleReportTask();
+ mConsoleReportTask = nullptr;
+ }
+ return IPC_OK();
+ }
+
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvOnConsoleReport(
+ nsTArray<ConsoleReportCollected>&& aConsoleReports) {
+ LOG(("HttpBackgroundChannelChild::RecvOnConsoleReport [this=%p]\n", this));
+ MOZ_ASSERT(mFirstODASource == ODA_FROM_SOCKET);
+ MOZ_ASSERT(gSocketTransportService);
+ MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible());
+
+ if (IsWaitingOnStartRequest()) {
+ LOG((" > pending until OnStartRequest\n"));
+
+ RefPtr<HttpBackgroundChannelChild> self = this;
+
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
+ "HttpBackgroundChannelChild::RecvOnConsoleReport",
+ [self, consoleReports =
+ CopyableTArray{std::move(aConsoleReports)}]() mutable {
+ self->RecvOnConsoleReport(std::move(consoleReports));
+ });
+
+ mQueuedRunnables.AppendElement(task.forget());
+ return IPC_OK();
+ }
+
+ if (mOnStopRequestCalled) {
+ mChannelChild->ProcessOnConsoleReport(std::move(aConsoleReports));
+ } else {
+ RefPtr<HttpBackgroundChannelChild> self = this;
+ mConsoleReportTask = [self, consoleReports = CopyableTArray{
+ std::move(aConsoleReports)}]() mutable {
+ self->mChannelChild->ProcessOnConsoleReport(std::move(consoleReports));
+ };
+ }
+
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvNotifyClassificationFlags(
+ const uint32_t& aClassificationFlags, const bool& aIsThirdParty) {
+ LOG(
+ ("HttpBackgroundChannelChild::RecvNotifyClassificationFlags "
+ "classificationFlags=%" PRIu32 ", thirdparty=%d [this=%p]\n",
+ aClassificationFlags, static_cast<int>(aIsThirdParty), this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ // NotifyClassificationFlags has no order dependency to OnStartRequest.
+ // It this be handled as soon as possible
+ mChannelChild->ProcessNotifyClassificationFlags(aClassificationFlags,
+ aIsThirdParty);
+
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvSetClassifierMatchedInfo(
+ const ClassifierInfo& info) {
+ LOG(("HttpBackgroundChannelChild::RecvSetClassifierMatchedInfo [this=%p]\n",
+ this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ // SetClassifierMatchedInfo has no order dependency to OnStartRequest.
+ // It this be handled as soon as possible
+ mChannelChild->ProcessSetClassifierMatchedInfo(info.list(), info.provider(),
+ info.fullhash());
+
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvSetClassifierMatchedTrackingInfo(
+ const ClassifierInfo& info) {
+ LOG(
+ ("HttpBackgroundChannelChild::RecvSetClassifierMatchedTrackingInfo "
+ "[this=%p]\n",
+ this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ // SetClassifierMatchedTrackingInfo has no order dependency to
+ // OnStartRequest. It this be handled as soon as possible
+ mChannelChild->ProcessSetClassifierMatchedTrackingInfo(info.list(),
+ info.fullhash());
+
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvAttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aEndpoint) {
+ LOG(("HttpBackgroundChannelChild::RecvAttachStreamFilter [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ mChannelChild->ProcessAttachStreamFilter(std::move(aEndpoint));
+ return IPC_OK();
+}
+
+IPCResult HttpBackgroundChannelChild::RecvDetachStreamFilters() {
+ LOG(("HttpBackgroundChannelChild::RecvDetachStreamFilters [this=%p]\n",
+ this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ mChannelChild->ProcessDetachStreamFilters();
+ return IPC_OK();
+}
+
+void HttpBackgroundChannelChild::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("HttpBackgroundChannelChild::ActorDestroy[this=%p]\n", this));
+ // This function might be called during shutdown phase, so OnSocketThread()
+ // might return false even on STS thread. Use IsOnCurrentThreadInfallible()
+ // to get correct information.
+ MOZ_ASSERT(gSocketTransportService);
+ MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible());
+
+ // Ensure all IPC messages received before ActorDestroy can be
+ // handled correctly. If there is any pending IPC message, destroyed
+ // mChannelChild until those messages are flushed.
+ // If background channel is not closed by normal IPDL actor deletion,
+ // remove the HttpChannelChild reference and notify background channel
+ // destroyed immediately.
+ if (aWhy == Deletion && !mQueuedRunnables.IsEmpty()) {
+ LOG((" > pending until queued messages are flushed\n"));
+ RefPtr<HttpBackgroundChannelChild> self = this;
+ mQueuedRunnables.AppendElement(NS_NewRunnableFunction(
+ "HttpBackgroundChannelChild::ActorDestroy", [self]() {
+ MOZ_ASSERT(OnSocketThread());
+ RefPtr<HttpChannelChild> channelChild =
+ std::move(self->mChannelChild);
+
+ if (channelChild) {
+ channelChild->OnBackgroundChildDestroyed(self);
+ }
+ }));
+ return;
+ }
+
+ if (mChannelChild) {
+ RefPtr<HttpChannelChild> channelChild = std::move(mChannelChild);
+
+ channelChild->OnBackgroundChildDestroyed(this);
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpBackgroundChannelChild.h b/netwerk/protocol/http/HttpBackgroundChannelChild.h
new file mode 100644
index 0000000000..da88ec9501
--- /dev/null
+++ b/netwerk/protocol/http/HttpBackgroundChannelChild.h
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_HttpBackgroundChannelChild_h
+#define mozilla_net_HttpBackgroundChannelChild_h
+
+#include "mozilla/net/PHttpBackgroundChannelChild.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "nsIRunnable.h"
+#include "nsTArray.h"
+
+using mozilla::ipc::IPCResult;
+
+namespace mozilla {
+namespace net {
+
+class PBackgroundDataBridgeChild;
+class BackgroundDataBridgeChild;
+class HttpChannelChild;
+
+class HttpBackgroundChannelChild final : public PHttpBackgroundChannelChild {
+ friend class BackgroundChannelCreateCallback;
+ friend class PHttpBackgroundChannelChild;
+ friend class HttpChannelChild;
+ friend class BackgroundDataBridgeChild;
+
+ public:
+ explicit HttpBackgroundChannelChild();
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HttpBackgroundChannelChild, final)
+
+ // Associate this background channel with a HttpChannelChild and
+ // initiate the createion of the PBackground IPC channel.
+ nsresult Init(HttpChannelChild* aChannelChild);
+
+ // Callback while the associated HttpChannelChild is not going to
+ // handle any incoming messages over background channel.
+ void OnChannelClosed();
+
+ // Return true if OnChannelClosed has been called.
+ bool ChannelClosed();
+
+ // Callback when OnStartRequest is received and handled by HttpChannelChild.
+ // Enqueued messages in background channel will be flushed.
+ void OnStartRequestReceived(Maybe<uint32_t> aMultiPartID);
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ bool IsQueueEmpty() const { return mQueuedRunnables.IsEmpty(); }
+#endif
+
+ protected:
+ IPCResult RecvOnStartRequest(const nsHttpResponseHead& aResponseHead,
+ const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs,
+ const HttpChannelAltDataStream& aAltData,
+ const TimeStamp& aOnStartRequestStart);
+
+ IPCResult RecvOnTransportAndData(const nsresult& aChannelStatus,
+ const nsresult& aTransportStatus,
+ const uint64_t& aOffset,
+ const uint32_t& aCount,
+ const nsACString& aData,
+ const bool& aDataFromSocketProcess,
+ const TimeStamp& aOnDataAvailableStart);
+
+ IPCResult RecvOnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers,
+ nsTArray<ConsoleReportCollected>&& aConsoleReports,
+ const bool& aFromSocketProcess, const TimeStamp& aOnStopRequestStart);
+
+ IPCResult RecvOnConsoleReport(
+ nsTArray<ConsoleReportCollected>&& aConsoleReports);
+
+ IPCResult RecvOnAfterLastPart(const nsresult& aStatus);
+
+ IPCResult RecvOnProgress(const int64_t& aProgress,
+ const int64_t& aProgressMax);
+
+ IPCResult RecvOnStatus(const nsresult& aStatus);
+
+ IPCResult RecvNotifyClassificationFlags(const uint32_t& aClassificationFlags,
+ const bool& aIsThirdParty);
+
+ IPCResult RecvSetClassifierMatchedInfo(const ClassifierInfo& info);
+
+ IPCResult RecvSetClassifierMatchedTrackingInfo(const ClassifierInfo& info);
+
+ IPCResult RecvAttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aEndpoint);
+
+ IPCResult RecvDetachStreamFilters();
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ void CreateDataBridge(Endpoint<PBackgroundDataBridgeChild>&& aEndpoint);
+
+ private:
+ virtual ~HttpBackgroundChannelChild();
+
+ // Initiate the creation of the PBckground IPC channel.
+ // Return false if failed.
+ bool CreateBackgroundChannel();
+
+ // Check OnStartRequest is sent by parent process over main thread IPC
+ // but not yet received on child process.
+ // return true before RecvOnStartRequestSent is invoked.
+ // return false if RecvOnStartRequestSent is invoked but not
+ // OnStartRequestReceived.
+ // return true after both RecvOnStartRequestSend and OnStartRequestReceived
+ // are invoked.
+ bool IsWaitingOnStartRequest();
+
+ // Associated HttpChannelChild for handling the channel events.
+ // Will be removed while failed to create background channel,
+ // destruction of the background channel, or explicitly dissociation
+ // via OnChannelClosed callback.
+ RefPtr<HttpChannelChild> mChannelChild;
+
+ // True if OnStartRequest is received by HttpChannelChild.
+ // Should only access on STS thread.
+ bool mStartReceived = false;
+
+ // Store pending messages that require to be handled after OnStartRequest.
+ // Should be flushed after OnStartRequest is received and handled.
+ // Should only access on STS thread.
+ nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
+
+ enum ODASource {
+ ODA_PENDING = 0, // ODA is pending
+ ODA_FROM_PARENT = 1, // ODA from parent process.
+ ODA_FROM_SOCKET = 2 // response coming from the network
+ };
+ // We need to know the first ODA will be from socket process or parent
+ // process. This information is from OnStartRequest message from parent
+ // process.
+ ODASource mFirstODASource;
+
+ // Indicate whether HttpChannelChild::ProcessOnStopRequest is called.
+ bool mOnStopRequestCalled = false;
+
+ // This is used when we receive the console report from parent process, but
+ // still not get the OnStopRequest from socket process.
+ std::function<void()> mConsoleReportTask;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpBackgroundChannelChild_h
diff --git a/netwerk/protocol/http/HttpBackgroundChannelParent.cpp b/netwerk/protocol/http/HttpBackgroundChannelParent.cpp
new file mode 100644
index 0000000000..e30fa13d37
--- /dev/null
+++ b/netwerk/protocol/http/HttpBackgroundChannelParent.cpp
@@ -0,0 +1,526 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HttpBackgroundChannelParent.h"
+
+#include "HttpChannelParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Unused.h"
+#include "mozilla/net/BackgroundChannelRegistrar.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "nsNetCID.h"
+#include "nsQueryObject.h"
+#include "nsThreadUtils.h"
+
+using mozilla::dom::ContentParent;
+using mozilla::ipc::AssertIsInMainProcess;
+using mozilla::ipc::AssertIsOnBackgroundThread;
+using mozilla::ipc::BackgroundParent;
+using mozilla::ipc::IPCResult;
+using mozilla::ipc::IsOnBackgroundThread;
+
+namespace mozilla {
+namespace net {
+
+/*
+ * Helper class for continuing the AsyncOpen procedure on main thread.
+ */
+class ContinueAsyncOpenRunnable final : public Runnable {
+ public:
+ ContinueAsyncOpenRunnable(HttpBackgroundChannelParent* aActor,
+ const uint64_t& aChannelId)
+ : Runnable("net::ContinueAsyncOpenRunnable"),
+ mActor(aActor),
+ mChannelId(aChannelId) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActor);
+ }
+
+ NS_IMETHOD Run() override {
+ LOG(
+ ("HttpBackgroundChannelParent::ContinueAsyncOpen [this=%p "
+ "channelId=%" PRIu64 "]\n",
+ mActor.get(), mChannelId));
+ AssertIsInMainProcess();
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIBackgroundChannelRegistrar> registrar =
+ BackgroundChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+
+ registrar->LinkBackgroundChannel(mChannelId, mActor);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<HttpBackgroundChannelParent> mActor;
+ const uint64_t mChannelId;
+};
+
+HttpBackgroundChannelParent::HttpBackgroundChannelParent()
+ : mIPCOpened(true),
+ mBgThreadMutex("HttpBackgroundChannelParent::BgThreadMutex") {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ {
+ MutexAutoLock lock(mBgThreadMutex);
+ mBackgroundThread = NS_GetCurrentThread();
+ }
+}
+
+HttpBackgroundChannelParent::~HttpBackgroundChannelParent() {
+ MOZ_ASSERT(NS_IsMainThread() || IsOnBackgroundThread());
+ MOZ_ASSERT(!mIPCOpened);
+}
+
+nsresult HttpBackgroundChannelParent::Init(const uint64_t& aChannelId) {
+ LOG(("HttpBackgroundChannelParent::Init [this=%p channelId=%" PRIu64 "]\n",
+ this, aChannelId));
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<ContinueAsyncOpenRunnable> runnable =
+ new ContinueAsyncOpenRunnable(this, aChannelId);
+
+ return NS_DispatchToMainThread(runnable);
+}
+
+void HttpBackgroundChannelParent::LinkToChannel(
+ HttpChannelParent* aChannelParent) {
+ LOG(("HttpBackgroundChannelParent::LinkToChannel [this=%p channel=%p]\n",
+ this, aChannelParent));
+ AssertIsInMainProcess();
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mIPCOpened) {
+ return;
+ }
+
+ mChannelParent = aChannelParent;
+}
+
+void HttpBackgroundChannelParent::OnChannelClosed() {
+ LOG(("HttpBackgroundChannelParent::OnChannelClosed [this=%p]\n", this));
+ AssertIsInMainProcess();
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mIPCOpened) {
+ return;
+ }
+
+ nsresult rv;
+
+ {
+ MutexAutoLock lock(mBgThreadMutex);
+ RefPtr<HttpBackgroundChannelParent> self = this;
+ rv = mBackgroundThread->Dispatch(
+ NS_NewRunnableFunction(
+ "net::HttpBackgroundChannelParent::OnChannelClosed",
+ [self]() {
+ LOG(("HttpBackgroundChannelParent::DeleteRunnable [this=%p]\n",
+ self.get()));
+ AssertIsOnBackgroundThread();
+
+ if (!self->mIPCOpened.compareExchange(true, false)) {
+ return;
+ }
+
+ Unused << self->Send__delete__(self);
+ }),
+ NS_DISPATCH_NORMAL);
+ }
+
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
+bool HttpBackgroundChannelParent::OnStartRequest(
+ const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs,
+ const nsCOMPtr<nsICacheEntry>& aAltDataSource,
+ TimeStamp aOnStartRequestStart) {
+ LOG(("HttpBackgroundChannelParent::OnStartRequest [this=%p]\n", this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsHttpResponseHead, const bool,
+ const nsHttpHeaderArray,
+ const HttpChannelOnStartRequestArgs,
+ const nsCOMPtr<nsICacheEntry>, TimeStamp>(
+ "net::HttpBackgroundChannelParent::OnStartRequest", this,
+ &HttpBackgroundChannelParent::OnStartRequest, aResponseHead,
+ aUseResponseHead, aRequestHeaders, aArgs, aAltDataSource,
+ aOnStartRequestStart),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ HttpChannelAltDataStream altData;
+ if (aAltDataSource) {
+ nsAutoCString altDataType;
+ Unused << aAltDataSource->GetAltDataType(altDataType);
+
+ if (!altDataType.IsEmpty()) {
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = aAltDataSource->OpenAlternativeInputStream(
+ altDataType, getter_AddRefs(inputStream));
+ if (NS_SUCCEEDED(rv)) {
+ Unused << mozilla::ipc::SerializeIPCStream(inputStream.forget(),
+ altData.altDataInputStream(),
+ /* aAllowLazy */ true);
+ }
+ }
+ }
+
+ return SendOnStartRequest(aResponseHead, aUseResponseHead, aRequestHeaders,
+ aArgs, altData, aOnStartRequestStart);
+}
+
+bool HttpBackgroundChannelParent::OnTransportAndData(
+ const nsresult& aChannelStatus, const nsresult& aTransportStatus,
+ const uint64_t& aOffset, const uint32_t& aCount, const nsCString& aData,
+ TimeStamp aOnDataAvailableStart) {
+ LOG(("HttpBackgroundChannelParent::OnTransportAndData [this=%p]\n", this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsresult, const nsresult, const uint64_t,
+ const uint32_t, const nsCString, TimeStamp>(
+ "net::HttpBackgroundChannelParent::OnTransportAndData", this,
+ &HttpBackgroundChannelParent::OnTransportAndData, aChannelStatus,
+ aTransportStatus, aOffset, aCount, aData, aOnDataAvailableStart),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ nsHttp::SendFunc<nsDependentCSubstring> sendFunc =
+ [self = UnsafePtr<HttpBackgroundChannelParent>(this), aChannelStatus,
+ aTransportStatus,
+ aOnDataAvailableStart](const nsDependentCSubstring& aData,
+ uint64_t aOffset, uint32_t aCount) {
+ return self->SendOnTransportAndData(aChannelStatus, aTransportStatus,
+ aOffset, aCount, aData, false,
+ aOnDataAvailableStart);
+ };
+
+ return nsHttp::SendDataInChunks(aData, aOffset, aCount, sendFunc);
+}
+
+bool HttpBackgroundChannelParent::OnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const nsTArray<ConsoleReportCollected>& aConsoleReports,
+ TimeStamp aOnStopRequestStart) {
+ LOG(
+ ("HttpBackgroundChannelParent::OnStopRequest [this=%p "
+ "status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aChannelStatus)));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsresult, const ResourceTimingStructArgs,
+ const nsHttpHeaderArray,
+ const CopyableTArray<ConsoleReportCollected>,
+ TimeStamp>(
+ "net::HttpBackgroundChannelParent::OnStopRequest", this,
+ &HttpBackgroundChannelParent::OnStopRequest, aChannelStatus,
+ aTiming, aResponseTrailers, aConsoleReports, aOnStopRequestStart),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ // See the child code for why we do this.
+ TimeStamp lastActTabOpt = nsHttp::GetLastActiveTabLoadOptimizationHit();
+
+ return SendOnStopRequest(aChannelStatus, aTiming, lastActTabOpt,
+ aResponseTrailers, aConsoleReports, false,
+ aOnStopRequestStart);
+}
+
+bool HttpBackgroundChannelParent::OnConsoleReport(
+ const nsTArray<ConsoleReportCollected>& aConsoleReports) {
+ LOG(("HttpBackgroundChannelParent::OnConsoleReport [this=%p]", this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const CopyableTArray<ConsoleReportCollected>>(
+ "net::HttpBackgroundChannelParent::OnConsoleReport", this,
+ &HttpBackgroundChannelParent::OnConsoleReport, aConsoleReports),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ return SendOnConsoleReport(aConsoleReports);
+}
+
+bool HttpBackgroundChannelParent::OnAfterLastPart(const nsresult aStatus) {
+ LOG(("HttpBackgroundChannelParent::OnAfterLastPart [this=%p]\n", this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsresult>(
+ "net::HttpBackgroundChannelParent::OnAfterLastPart", this,
+ &HttpBackgroundChannelParent::OnAfterLastPart, aStatus),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ return SendOnAfterLastPart(aStatus);
+}
+
+bool HttpBackgroundChannelParent::OnProgress(const int64_t aProgress,
+ const int64_t aProgressMax) {
+ LOG(("HttpBackgroundChannelParent::OnProgress [this=%p]\n", this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const int64_t, const int64_t>(
+ "net::HttpBackgroundChannelParent::OnProgress", this,
+ &HttpBackgroundChannelParent::OnProgress, aProgress, aProgressMax),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ return SendOnProgress(aProgress, aProgressMax);
+}
+
+bool HttpBackgroundChannelParent::OnStatus(const nsresult aStatus) {
+ LOG(("HttpBackgroundChannelParent::OnStatus [this=%p]\n", this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsresult>(
+ "net::HttpBackgroundChannelParent::OnStatus", this,
+ &HttpBackgroundChannelParent::OnStatus, aStatus),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ return SendOnStatus(aStatus);
+}
+
+bool HttpBackgroundChannelParent::OnNotifyClassificationFlags(
+ uint32_t aClassificationFlags, bool aIsThirdParty) {
+ LOG(
+ ("HttpBackgroundChannelParent::OnNotifyClassificationFlags "
+ "classificationFlags=%" PRIu32 ", thirdparty=%d [this=%p]\n",
+ aClassificationFlags, static_cast<int>(aIsThirdParty), this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<uint32_t, bool>(
+ "net::HttpBackgroundChannelParent::OnNotifyClassificationFlags",
+ this, &HttpBackgroundChannelParent::OnNotifyClassificationFlags,
+ aClassificationFlags, aIsThirdParty),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ return SendNotifyClassificationFlags(aClassificationFlags, aIsThirdParty);
+}
+
+bool HttpBackgroundChannelParent::OnSetClassifierMatchedInfo(
+ const nsACString& aList, const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ LOG(("HttpBackgroundChannelParent::OnSetClassifierMatchedInfo [this=%p]\n",
+ this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsCString, const nsCString, const nsCString>(
+ "net::HttpBackgroundChannelParent::OnSetClassifierMatchedInfo",
+ this, &HttpBackgroundChannelParent::OnSetClassifierMatchedInfo,
+ aList, aProvider, aFullHash),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ ClassifierInfo info;
+ info.list() = aList;
+ info.fullhash() = aFullHash;
+ info.provider() = aProvider;
+
+ return SendSetClassifierMatchedInfo(info);
+}
+
+bool HttpBackgroundChannelParent::OnSetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ LOG(
+ ("HttpBackgroundChannelParent::OnSetClassifierMatchedTrackingInfo "
+ "[this=%p]\n",
+ this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ nsresult rv = mBackgroundThread->Dispatch(
+ NewRunnableMethod<const nsCString, const nsCString>(
+ "net::HttpBackgroundChannelParent::"
+ "OnSetClassifierMatchedTrackingInfo",
+ this,
+ &HttpBackgroundChannelParent::OnSetClassifierMatchedTrackingInfo,
+ aLists, aFullHashes),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ ClassifierInfo info;
+ info.list() = aLists;
+ info.fullhash() = aFullHashes;
+
+ return SendSetClassifierMatchedTrackingInfo(info);
+}
+
+nsISerialEventTarget* HttpBackgroundChannelParent::GetBackgroundTarget() {
+ MOZ_ASSERT(mBackgroundThread);
+ return mBackgroundThread.get();
+}
+
+auto HttpBackgroundChannelParent::AttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aParentEndpoint,
+ Endpoint<extensions::PStreamFilterChild>&& aChildEndpoint)
+ -> RefPtr<ChildEndpointPromise> {
+ LOG(("HttpBackgroundChannelParent::AttachStreamFilter [this=%p]\n", this));
+ MOZ_ASSERT(IsOnBackgroundThread());
+
+ if (NS_WARN_IF(!mIPCOpened) ||
+ !SendAttachStreamFilter(std::move(aParentEndpoint))) {
+ return ChildEndpointPromise::CreateAndReject(false, __func__);
+ }
+
+ return ChildEndpointPromise::CreateAndResolve(std::move(aChildEndpoint),
+ __func__);
+}
+
+auto HttpBackgroundChannelParent::DetachStreamFilters()
+ -> RefPtr<GenericPromise> {
+ LOG(("HttpBackgroundChannelParent::DetachStreamFilters [this=%p]\n", this));
+ if (NS_WARN_IF(!mIPCOpened) || !SendDetachStreamFilters()) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ return GenericPromise::CreateAndResolve(true, __func__);
+}
+
+void HttpBackgroundChannelParent::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("HttpBackgroundChannelParent::ActorDestroy [this=%p]\n", this));
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ mIPCOpened = false;
+
+ RefPtr<HttpBackgroundChannelParent> self = this;
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::HttpBackgroundChannelParent::ActorDestroy", [self]() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<HttpChannelParent> channelParent =
+ std::move(self->mChannelParent);
+
+ if (channelParent) {
+ channelParent->OnBackgroundParentDestroyed();
+ }
+ }));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpBackgroundChannelParent.h b/netwerk/protocol/http/HttpBackgroundChannelParent.h
new file mode 100644
index 0000000000..7715d9bdfb
--- /dev/null
+++ b/netwerk/protocol/http/HttpBackgroundChannelParent.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_HttpBackgroundChannelParent_h
+#define mozilla_net_HttpBackgroundChannelParent_h
+
+#include "mozilla/net/PHttpBackgroundChannelParent.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+#include "nsID.h"
+#include "nsISupportsImpl.h"
+
+class nsISerialEventTarget;
+
+namespace mozilla {
+namespace net {
+
+class HttpChannelParent;
+
+class HttpBackgroundChannelParent final : public PHttpBackgroundChannelParent {
+ public:
+ explicit HttpBackgroundChannelParent();
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HttpBackgroundChannelParent, final)
+
+ // Try to find associated HttpChannelParent with the same
+ // channel Id.
+ nsresult Init(const uint64_t& aChannelId);
+
+ // Callbacks for BackgroundChannelRegistrar to notify
+ // the associated HttpChannelParent is found.
+ void LinkToChannel(HttpChannelParent* aChannelParent);
+
+ // Callbacks for HttpChannelParent to close the background
+ // IPC channel.
+ void OnChannelClosed();
+
+ // To send OnStartRequest message over background channel.
+ bool OnStartRequest(const nsHttpResponseHead& aResponseHead,
+ const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs,
+ const nsCOMPtr<nsICacheEntry>& aCacheEntry,
+ TimeStamp aOnStartRequestStart);
+
+ // To send OnTransportAndData message over background channel.
+ bool OnTransportAndData(const nsresult& aChannelStatus,
+ const nsresult& aTransportStatus,
+ const uint64_t& aOffset, const uint32_t& aCount,
+ const nsCString& aData,
+ TimeStamp aOnDataAvailableStart);
+
+ // To send OnStopRequest message over background channel.
+ bool OnStopRequest(const nsresult& aChannelStatus,
+ const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const nsTArray<ConsoleReportCollected>& aConsoleReports,
+ TimeStamp aOnStopRequestStart);
+
+ // When ODA and OnStopRequest are sending from socket process to child
+ // process, this is the last IPC message sent from parent process.
+ bool OnConsoleReport(const nsTArray<ConsoleReportCollected>& aConsoleReports);
+
+ // To send OnAfterLastPart message over background channel.
+ bool OnAfterLastPart(const nsresult aStatus);
+
+ // To send OnProgress message over background channel.
+ bool OnProgress(const int64_t aProgress, const int64_t aProgressMax);
+
+ // To send OnStatus message over background channel.
+ bool OnStatus(const nsresult aStatus);
+
+ // To send FlushedForDiversion and DivertMessages messages
+ // over background channel.
+ bool OnDiversion();
+
+ // To send NotifyClassificationFlags message over background channel.
+ bool OnNotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty);
+
+ // To send SetClassifierMatchedInfo message over background channel.
+ bool OnSetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash);
+
+ // To send SetClassifierMatchedTrackingInfo message over background channel.
+ bool OnSetClassifierMatchedTrackingInfo(const nsACString& aLists,
+ const nsACString& aFullHashes);
+
+ nsISerialEventTarget* GetBackgroundTarget();
+
+ using ChildEndpointPromise =
+ MozPromise<ipc::Endpoint<extensions::PStreamFilterChild>, bool, true>;
+ [[nodiscard]] RefPtr<ChildEndpointPromise> AttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aParentEndpoint,
+ Endpoint<extensions::PStreamFilterChild>&& aChildEndpoint);
+
+ [[nodiscard]] RefPtr<GenericPromise> DetachStreamFilters();
+
+ protected:
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+ virtual ~HttpBackgroundChannelParent();
+
+ Atomic<bool> mIPCOpened;
+
+ // Used to ensure atomicity of mBackgroundThread
+ Mutex mBgThreadMutex MOZ_UNANNOTATED;
+
+ nsCOMPtr<nsISerialEventTarget> mBackgroundThread;
+
+ // associated HttpChannelParent for generating the channel events
+ RefPtr<HttpChannelParent> mChannelParent;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpBackgroundChannelParent_h
diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp
new file mode 100644
index 0000000000..59242f6933
--- /dev/null
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -0,0 +1,6479 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "mozilla/net/HttpBaseChannel.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "HttpBaseChannel.h"
+#include "HttpLog.h"
+#include "LoadInfo.h"
+#include "ReferrerInfo.h"
+#include "mozIRemoteLazyInputStream.h"
+#include "mozIThirdPartyUtil.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/InputStreamLengthHelper.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/PermissionManager.h"
+#include "mozilla/Components.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/browser/NimbusFeatures.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/dom/ProcessIsolation.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/net/OpaqueResponseUtils.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "nsBufferedStreams.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentSecurityUtils.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsEscape.h"
+#include "nsGlobalWindowInner.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsHttpChannel.h"
+#include "nsHTTPCompressConv.h"
+#include "nsHttpHandler.h"
+#include "nsICacheInfoChannel.h"
+#include "nsICachingChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIConsoleService.h"
+#include "nsIContentPolicy.h"
+#include "nsICookieService.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIDocShell.h"
+#include "nsIDNSService.h"
+#include "nsIEncodedChannel.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsILoadGroupChild.h"
+#include "nsIMIMEInputStream.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIMutableArray.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIObserverService.h"
+#include "nsIPrincipal.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISecurityConsoleMessage.h"
+#include "nsISeekableStream.h"
+#include "nsIStorageStream.h"
+#include "nsIStreamConverterService.h"
+#include "nsITimedChannel.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIURIMutator.h"
+#include "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsProxyRelease.h"
+#include "nsReadableUtils.h"
+#include "nsRedirectHistoryEntry.h"
+#include "nsServerTiming.h"
+#include "nsStreamListenerWrapper.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsURLHelper.h"
+#include "mozilla/RemoteLazyInputStreamChild.h"
+#include "mozilla/net/SFVService.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsQueryObject.h"
+
+using mozilla::dom::RequestMode;
+
+#define LOGORB(msg, ...) \
+ MOZ_LOG(GetORBLog(), LogLevel::Debug, \
+ ("%s: %p " msg, __func__, this, ##__VA_ARGS__))
+
+namespace mozilla {
+namespace net {
+
+static bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader) {
+ // IMPORTANT: keep this list ASCII-code sorted
+ static nsHttpAtomLiteral const* blackList[] = {
+ &nsHttp::Accept,
+ &nsHttp::Accept_Encoding,
+ &nsHttp::Accept_Language,
+ &nsHttp::Alternate_Service_Used,
+ &nsHttp::Authentication,
+ &nsHttp::Authorization,
+ &nsHttp::Connection,
+ &nsHttp::Content_Length,
+ &nsHttp::Cookie,
+ &nsHttp::Host,
+ &nsHttp::If,
+ &nsHttp::If_Match,
+ &nsHttp::If_Modified_Since,
+ &nsHttp::If_None_Match,
+ &nsHttp::If_None_Match_Any,
+ &nsHttp::If_Range,
+ &nsHttp::If_Unmodified_Since,
+ &nsHttp::Proxy_Authenticate,
+ &nsHttp::Proxy_Authorization,
+ &nsHttp::Range,
+ &nsHttp::TE,
+ &nsHttp::Transfer_Encoding,
+ &nsHttp::Upgrade,
+ &nsHttp::User_Agent,
+ &nsHttp::WWW_Authenticate};
+
+ class HttpAtomComparator {
+ nsHttpAtom const& mTarget;
+
+ public:
+ explicit HttpAtomComparator(nsHttpAtom const& aTarget) : mTarget(aTarget) {}
+ int operator()(nsHttpAtom const* aVal) const {
+ if (mTarget == *aVal) {
+ return 0;
+ }
+ return strcmp(mTarget.get(), aVal->get());
+ }
+ int operator()(nsHttpAtomLiteral const* aVal) const {
+ if (mTarget == *aVal) {
+ return 0;
+ }
+ return strcmp(mTarget.get(), aVal->get());
+ }
+ };
+
+ size_t unused;
+ return BinarySearchIf(blackList, 0, ArrayLength(blackList),
+ HttpAtomComparator(aHeader), &unused);
+}
+
+class AddHeadersToChannelVisitor final : public nsIHttpHeaderVisitor {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit AddHeadersToChannelVisitor(nsIHttpChannel* aChannel)
+ : mChannel(aChannel) {}
+
+ NS_IMETHOD VisitHeader(const nsACString& aHeader,
+ const nsACString& aValue) override {
+ nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+ if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
+ DebugOnly<nsresult> rv =
+ mChannel->SetRequestHeader(aHeader, aValue, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ return NS_OK;
+ }
+
+ private:
+ ~AddHeadersToChannelVisitor() = default;
+
+ nsCOMPtr<nsIHttpChannel> mChannel;
+};
+
+NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor)
+
+static OpaqueResponseFilterFetch ConfiguredFilterFetchResponseBehaviour() {
+ uint32_t pref = StaticPrefs::
+ browser_opaqueResponseBlocking_filterFetchResponse_DoNotUseDirectly();
+ if (NS_WARN_IF(pref >
+ static_cast<uint32_t>(OpaqueResponseFilterFetch::All))) {
+ return OpaqueResponseFilterFetch::All;
+ }
+
+ return static_cast<OpaqueResponseFilterFetch>(pref);
+}
+
+HttpBaseChannel::HttpBaseChannel()
+ : mReportCollector(new ConsoleReportCollector()),
+ mHttpHandler(gHttpHandler),
+ mChannelCreationTime(0),
+ mComputedCrossOriginOpenerPolicy(nsILoadInfo::OPENER_POLICY_UNSAFE_NONE),
+ mStartPos(UINT64_MAX),
+ mTransferSize(0),
+ mRequestSize(0),
+ mDecodedBodySize(0),
+ mSupportsHTTP3(false),
+ mEncodedBodySize(0),
+ mRequestContextID(0),
+ mContentWindowId(0),
+ mBrowserId(0),
+ mAltDataLength(-1),
+ mChannelId(0),
+ mReqContentLength(0U),
+ mStatus(NS_OK),
+ mCanceled(false),
+ mFirstPartyClassificationFlags(0),
+ mThirdPartyClassificationFlags(0),
+ mLoadFlags(LOAD_NORMAL),
+ mCaps(0),
+ mClassOfService(0, false),
+ mTlsFlags(0),
+ mSuspendCount(0),
+ mInitialRwin(0),
+ mProxyResolveFlags(0),
+ mContentDispositionHint(UINT32_MAX),
+ mRequestMode(RequestMode::No_cors),
+ mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW),
+ mLastRedirectFlags(0),
+ mPriority(PRIORITY_NORMAL),
+ mRedirectionLimit(gHttpHandler->RedirectionLimit()),
+ mRedirectCount(0),
+ mInternalRedirectCount(0),
+ mCachedOpaqueResponseBlockingPref(
+ StaticPrefs::browser_opaqueResponseBlocking()),
+ mChannelBlockedByOpaqueResponse(false),
+ mDummyChannelForImageCache(false),
+ mHasContentDecompressed(false) {
+ StoreApplyConversion(true);
+ StoreAllowSTS(true);
+ StoreTracingEnabled(true);
+ StoreReportTiming(true);
+ StoreAllowSpdy(true);
+ StoreAllowHttp3(true);
+ StoreAllowAltSvc(true);
+ StoreResponseTimeoutEnabled(true);
+ StoreAllRedirectsSameOrigin(true);
+ StoreAllRedirectsPassTimingAllowCheck(true);
+ StoreUpgradableToSecure(true);
+ StoreIsUserAgentHeaderModified(false);
+
+ this->mSelfAddr.inet = {};
+ this->mPeerAddr.inet = {};
+ LOG(("Creating HttpBaseChannel @%p\n", this));
+
+ // Subfields of unions cannot be targeted in an initializer list.
+#ifdef MOZ_VALGRIND
+ // Zero the entire unions so that Valgrind doesn't complain when we send them
+ // to another process.
+ memset(&mSelfAddr, 0, sizeof(NetAddr));
+ memset(&mPeerAddr, 0, sizeof(NetAddr));
+#endif
+ mSelfAddr.raw.family = PR_AF_UNSPEC;
+ mPeerAddr.raw.family = PR_AF_UNSPEC;
+}
+
+HttpBaseChannel::~HttpBaseChannel() {
+ LOG(("Destroying HttpBaseChannel @%p\n", this));
+
+ // Make sure we don't leak
+ CleanRedirectCacheChainIfNecessary();
+
+ ReleaseMainThreadOnlyReferences();
+}
+
+namespace { // anon
+
+class NonTailRemover : public nsISupports {
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit NonTailRemover(nsIRequestContext* rc) : mRequestContext(rc) {}
+
+ private:
+ virtual ~NonTailRemover() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mRequestContext->RemoveNonTailRequest();
+ }
+
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+};
+
+NS_IMPL_ISUPPORTS0(NonTailRemover)
+
+} // namespace
+
+void HttpBaseChannel::ReleaseMainThreadOnlyReferences() {
+ if (NS_IsMainThread()) {
+ // Already on main thread, let dtor to
+ // take care of releasing references
+ RemoveAsNonTailRequest();
+ return;
+ }
+
+ nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
+ arrayToRelease.AppendElement(mLoadGroup.forget());
+ arrayToRelease.AppendElement(mLoadInfo.forget());
+ arrayToRelease.AppendElement(mCallbacks.forget());
+ arrayToRelease.AppendElement(mProgressSink.forget());
+ arrayToRelease.AppendElement(mPrincipal.forget());
+ arrayToRelease.AppendElement(mListener.forget());
+ arrayToRelease.AppendElement(mCompressListener.forget());
+ arrayToRelease.AppendElement(mORB.forget());
+
+ if (LoadAddedAsNonTailRequest()) {
+ // RemoveNonTailRequest() on our request context must be called on the main
+ // thread
+ MOZ_RELEASE_ASSERT(mRequestContext,
+ "Someone released rc or set flags w/o having it?");
+
+ nsCOMPtr<nsISupports> nonTailRemover(new NonTailRemover(mRequestContext));
+ arrayToRelease.AppendElement(nonTailRemover.forget());
+ }
+
+ NS_DispatchToMainThread(new ProxyReleaseRunnable(std::move(arrayToRelease)));
+}
+
+void HttpBaseChannel::AddClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ LOG(
+ ("HttpBaseChannel::AddClassificationFlags classificationFlags=%d "
+ "thirdparty=%d %p",
+ aClassificationFlags, static_cast<int>(aIsThirdParty), this));
+
+ if (aIsThirdParty) {
+ mThirdPartyClassificationFlags |= aClassificationFlags;
+ } else {
+ mFirstPartyClassificationFlags |= aClassificationFlags;
+ }
+}
+
+static bool isSecureOrTrustworthyURL(nsIURI* aURI) {
+ return aURI->SchemeIs("https") ||
+ (StaticPrefs::network_http_encoding_trustworthy_is_https() &&
+ nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aURI));
+}
+
+nsresult HttpBaseChannel::Init(nsIURI* aURI, uint32_t aCaps,
+ nsProxyInfo* aProxyInfo,
+ uint32_t aProxyResolveFlags, nsIURI* aProxyURI,
+ uint64_t aChannelId,
+ ExtContentPolicyType aContentPolicyType,
+ nsILoadInfo* aLoadInfo) {
+ LOG1(("HttpBaseChannel::Init [this=%p]\n", this));
+
+ MOZ_ASSERT(aURI, "null uri");
+
+ mURI = aURI;
+ mOriginalURI = aURI;
+ mDocumentURI = nullptr;
+ mCaps = aCaps;
+ mProxyResolveFlags = aProxyResolveFlags;
+ mProxyURI = aProxyURI;
+ mChannelId = aChannelId;
+ mLoadInfo = aLoadInfo;
+
+ // Construct connection info object
+ nsAutoCString host;
+ int32_t port = -1;
+ bool isHTTPS = isSecureOrTrustworthyURL(mURI);
+
+ nsresult rv = mURI->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ // Reject the URL if it doesn't specify a host
+ if (host.IsEmpty()) return NS_ERROR_MALFORMED_URI;
+
+ rv = mURI->GetPort(&port);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG1(("host=%s port=%d\n", host.get(), port));
+
+ rv = mURI->GetAsciiSpec(mSpec);
+ if (NS_FAILED(rv)) return rv;
+ LOG1(("uri=%s\n", mSpec.get()));
+
+ // Assert default request method
+ MOZ_ASSERT(mRequestHead.EqualsMethod(nsHttpRequestHead::kMethod_Get));
+
+ // Set request headers
+ nsAutoCString hostLine;
+ rv = nsHttpHandler::GenerateHostPort(host, port, hostLine);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mRequestHead.SetHeader(nsHttp::Host, hostLine);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = gHttpHandler->AddStandardRequestHeaders(
+ &mRequestHead, isHTTPS, aContentPolicyType,
+ nsContentUtils::ShouldResistFingerprinting(this,
+ RFPTarget::HttpUserAgent));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString type;
+ if (aProxyInfo && NS_SUCCEEDED(aProxyInfo->GetType(type)) &&
+ !type.EqualsLiteral("unknown")) {
+ mProxyInfo = aProxyInfo;
+ }
+
+ mCurrentThread = GetCurrentSerialEventTarget();
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpBaseChannel)
+NS_IMPL_RELEASE(HttpBaseChannel)
+
+NS_INTERFACE_MAP_BEGIN(HttpBaseChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIIdentChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY(nsIForcePendingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsITimedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIConsoleReportCollector)
+ NS_INTERFACE_MAP_ENTRY(nsIThrottledInputChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIClassifiedChannel)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpBaseChannel)
+NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetName(nsACString& aName) {
+ aName = mSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsPending(bool* aIsPending) {
+ NS_ENSURE_ARG_POINTER(aIsPending);
+ *aIsPending = LoadIsPending() || LoadForcePending();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetStatus(nsresult* aStatus) {
+ NS_ENSURE_ARG_POINTER(aStatus);
+ *aStatus = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ NS_ENSURE_ARG_POINTER(aLoadGroup);
+ *aLoadGroup = do_AddRef(mLoadGroup).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+
+ if (!CanSetLoadGroup(aLoadGroup)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoadGroup = aLoadGroup;
+ mProgressSink = nullptr;
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ NS_ENSURE_ARG_POINTER(aLoadFlags);
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ if (!LoadIsOCSP()) {
+ return GetTRRModeImpl(aTRRMode);
+ }
+
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ nsIDNSService::ResolverMode trrMode = nsIDNSService::MODE_NATIVEONLY;
+ // If this is an OCSP channel, and the global TRR mode is TRR_ONLY (3)
+ // then we set the mode for this channel as TRR_DISABLED_MODE.
+ // We do this to prevent a TRR service channel's OCSP validation from
+ // blocking DNS resolution completely.
+ if (dns && NS_SUCCEEDED(dns->GetCurrentTrrMode(&trrMode)) &&
+ trrMode == nsIDNSService::MODE_TRRONLY) {
+ *aTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return NS_OK;
+ }
+
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDocshellUserAgentOverride() {
+ RefPtr<dom::BrowsingContext> bc;
+ MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetBrowsingContext(getter_AddRefs(bc)));
+ if (!bc) {
+ return NS_OK;
+ }
+
+ nsAutoString customUserAgent;
+ bc->GetCustomUserAgent(customUserAgent);
+ if (customUserAgent.IsEmpty() || customUserAgent.IsVoid()) {
+ return NS_OK;
+ }
+
+ NS_ConvertUTF16toUTF8 utf8CustomUserAgent(customUserAgent);
+ nsresult rv = SetRequestHeader("User-Agent"_ns, utf8CustomUserAgent, false);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOriginalURI(nsIURI** aOriginalURI) {
+ NS_ENSURE_ARG_POINTER(aOriginalURI);
+ *aOriginalURI = do_AddRef(mOriginalURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetOriginalURI(nsIURI* aOriginalURI) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ NS_ENSURE_ARG_POINTER(aOriginalURI);
+ mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetURI(nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ *aURI = do_AddRef(mURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOwner(nsISupports** aOwner) {
+ NS_ENSURE_ARG_POINTER(aOwner);
+ *aOwner = do_AddRef(mOwner).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetOwner(nsISupports* aOwner) {
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ *aLoadInfo = do_AddRef(mLoadInfo).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_GetIsDocumentChannel(this, aIsDocument);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
+ *aCallbacks = do_AddRef(mCallbacks).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+
+ if (!CanSetCallbacks(aCallbacks)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCallbacks = aCallbacks;
+ mProgressSink = nullptr;
+
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentType(nsACString& aContentType) {
+ if (!mResponseHead) {
+ aContentType.Truncate();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mResponseHead->ContentType(aContentType);
+ if (!aContentType.IsEmpty()) {
+ return NS_OK;
+ }
+
+ aContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentType(const nsACString& aContentType) {
+ if (mListener || LoadWasOpened() || mDummyChannelForImageCache) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoCString contentTypeBuf, charsetBuf;
+ bool hadCharset;
+ net_ParseContentType(aContentType, contentTypeBuf, charsetBuf, &hadCharset);
+
+ mResponseHead->SetContentType(contentTypeBuf);
+
+ // take care not to stomp on an existing charset
+ if (hadCharset) mResponseHead->SetContentCharset(charsetBuf);
+
+ } else {
+ // We are being given a content-type hint.
+ bool dummy;
+ net_ParseContentType(aContentType, mContentTypeHint, mContentCharsetHint,
+ &dummy);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentCharset(nsACString& aContentCharset) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ mResponseHead->ContentCharset(aContentCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentCharset(const nsACString& aContentCharset) {
+ if (mListener) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ mResponseHead->SetContentCharset(aContentCharset);
+ } else {
+ // Charset hint
+ mContentCharsetHint = aContentCharset;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ // See bug 1658877. If mContentDispositionHint is already
+ // DISPOSITION_ATTACHMENT, it means this channel is created from a
+ // download attribute. In this case, we should prefer the value from the
+ // download attribute rather than the value in content disposition header.
+ // DISPOSITION_FORCE_INLINE is used to explicitly set inline, used by
+ // the pdf reader when loading a attachment pdf without having to
+ // download it.
+ if (mContentDispositionHint == nsIChannel::DISPOSITION_ATTACHMENT ||
+ mContentDispositionHint == nsIChannel::DISPOSITION_FORCE_INLINE) {
+ *aContentDisposition = mContentDispositionHint;
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCString header;
+
+ rv = GetContentDispositionHeader(header);
+ if (NS_FAILED(rv)) {
+ if (mContentDispositionHint == UINT32_MAX) return rv;
+
+ *aContentDisposition = mContentDispositionHint;
+ return NS_OK;
+ }
+
+ *aContentDisposition = NS_GetContentDispositionFromHeader(header, this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ mContentDispositionHint = aContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ aContentDispositionFilename.Truncate();
+ nsresult rv;
+ nsCString header;
+
+ rv = GetContentDispositionHeader(header);
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_GetFilenameFromDisposition(aContentDispositionFilename, header);
+ }
+
+ // If we failed to get the filename from header, we should use
+ // mContentDispositionFilename, since mContentDispositionFilename is set from
+ // the download attribute.
+ if (NS_FAILED(rv)) {
+ if (!mContentDispositionFilename) {
+ return rv;
+ }
+
+ aContentDispositionFilename = *mContentDispositionFilename;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ mContentDispositionFilename =
+ MakeUnique<nsString>(aContentDispositionFilename);
+
+ // For safety reasons ensure the filename doesn't contain null characters and
+ // replace them with underscores. We may later pass the extension to system
+ // MIME APIs that expect null terminated strings.
+ mContentDispositionFilename->ReplaceChar(char16_t(0), '_');
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = mResponseHead->GetHeader(nsHttp::Content_Disposition,
+ aContentDispositionHeader);
+ if (NS_FAILED(rv) || aContentDispositionHeader.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentLength(int64_t* aContentLength) {
+ NS_ENSURE_ARG_POINTER(aContentLength);
+
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ if (LoadDeliveringAltData()) {
+ MOZ_ASSERT(!mAvailableCachedAltDataType.IsEmpty());
+ *aContentLength = mAltDataLength;
+ return NS_OK;
+ }
+
+ *aContentLength = mResponseHead->ContentLength();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentLength(int64_t value) {
+ if (!mDummyChannelForImageCache) {
+ MOZ_ASSERT_UNREACHABLE("HttpBaseChannel::SetContentLength");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ MOZ_ASSERT(mResponseHead);
+ mResponseHead->SetContentLength(value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::Open(nsIInputStream** aStream) {
+ if (!gHttpHandler->Active()) {
+ LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_IN_PROGRESS);
+
+ if (!gHttpHandler->Active()) {
+ LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_ImplementChannelOpen(this, aStream);
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIUploadChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetUploadStream(nsIInputStream** stream) {
+ NS_ENSURE_ARG_POINTER(stream);
+ *stream = do_AddRef(mUploadStream).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetUploadStream(nsIInputStream* stream,
+ const nsACString& contentTypeArg,
+ int64_t contentLength) {
+ // NOTE: for backwards compatibility and for compatibility with old style
+ // plugins, |stream| may include headers, specifically Content-Type and
+ // Content-Length headers. in this case, |contentType| and |contentLength|
+ // would be unspecified. this is traditionally the case of a POST request,
+ // and so we select POST as the request method if contentType and
+ // contentLength are unspecified.
+
+ if (stream) {
+ nsAutoCString method;
+ bool hasHeaders = false;
+
+ // This method and ExplicitSetUploadStream mean different things by "empty
+ // content type string". This method means "no header", but
+ // ExplicitSetUploadStream means "header with empty value". So we have to
+ // massage the contentType argument into the form ExplicitSetUploadStream
+ // expects.
+ nsCOMPtr<nsIMIMEInputStream> mimeStream;
+ nsCString contentType(contentTypeArg);
+ if (contentType.IsEmpty()) {
+ contentType.SetIsVoid(true);
+ method = "POST"_ns;
+
+ // MIME streams are a special case, and include headers which need to be
+ // copied to the channel.
+ mimeStream = do_QueryInterface(stream);
+ if (mimeStream) {
+ // Copy non-origin related headers to the channel.
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new AddHeadersToChannelVisitor(this);
+ mimeStream->VisitHeaders(visitor);
+
+ return ExplicitSetUploadStream(stream, contentType, contentLength,
+ method, hasHeaders);
+ }
+
+ hasHeaders = true;
+ } else {
+ method = "PUT"_ns;
+
+ MOZ_ASSERT(
+ NS_FAILED(CallQueryInterface(stream, getter_AddRefs(mimeStream))),
+ "nsIMIMEInputStream should not be set with an explicit content type");
+ }
+ return ExplicitSetUploadStream(stream, contentType, contentLength, method,
+ hasHeaders);
+ }
+
+ // if stream is null, ExplicitSetUploadStream returns error.
+ // So we need special case for GET method.
+ StoreUploadStreamHasHeaders(false);
+ mRequestHead.SetMethod("GET"_ns); // revert to GET request
+ mUploadStream = nullptr;
+ return NS_OK;
+}
+
+namespace {
+
+class MIMEHeaderCopyVisitor final : public nsIHttpHeaderVisitor {
+ public:
+ explicit MIMEHeaderCopyVisitor(nsIMIMEInputStream* aDest) : mDest(aDest) {}
+
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD VisitHeader(const nsACString& aName,
+ const nsACString& aValue) override {
+ return mDest->AddHeader(PromiseFlatCString(aName).get(),
+ PromiseFlatCString(aValue).get());
+ }
+
+ private:
+ ~MIMEHeaderCopyVisitor() = default;
+
+ nsCOMPtr<nsIMIMEInputStream> mDest;
+};
+
+NS_IMPL_ISUPPORTS(MIMEHeaderCopyVisitor, nsIHttpHeaderVisitor)
+
+static void NormalizeCopyComplete(void* aClosure, nsresult aStatus) {
+#ifdef DEBUG
+ // Called on the STS thread by NS_AsyncCopy
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ bool result = false;
+ sts->IsOnCurrentThread(&result);
+ MOZ_ASSERT(result, "Should only be called on the STS thread.");
+#endif
+
+ RefPtr<GenericPromise::Private> ready =
+ already_AddRefed(static_cast<GenericPromise::Private*>(aClosure));
+ if (NS_SUCCEEDED(aStatus)) {
+ ready->Resolve(true, __func__);
+ } else {
+ ready->Reject(aStatus, __func__);
+ }
+}
+
+// Normalize the upload stream for a HTTP channel, so that is one of the
+// expected and compatible types. Components like WebExtensions and DevTools
+// expect that upload streams in the parent process are cloneable, seekable, and
+// synchronous to read, which this function helps guarantee somewhat efficiently
+// and without loss of information.
+//
+// If the replacement stream outparameter is not initialized to `nullptr`, the
+// returned stream should be used instead of `aUploadStream` as the upload
+// stream for the HTTP channel, and the previous stream should not be touched
+// again.
+//
+// If aReadyPromise is non-nullptr after the function is called, it is a promise
+// which should be awaited before continuing to `AsyncOpen` the HTTP channel,
+// as the replacement stream will not be ready until it is resolved.
+static nsresult NormalizeUploadStream(nsIInputStream* aUploadStream,
+ nsIInputStream** aReplacementStream,
+ GenericPromise** aReadyPromise) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ *aReplacementStream = nullptr;
+ *aReadyPromise = nullptr;
+
+ // Unwrap RemoteLazyInputStream and normalize the contents as we're in the
+ // parent process.
+ if (nsCOMPtr<mozIRemoteLazyInputStream> lazyStream =
+ do_QueryInterface(aUploadStream)) {
+ nsCOMPtr<nsIInputStream> internal;
+ if (NS_SUCCEEDED(
+ lazyStream->TakeInternalStream(getter_AddRefs(internal)))) {
+ nsCOMPtr<nsIInputStream> replacement;
+ nsresult rv = NormalizeUploadStream(internal, getter_AddRefs(replacement),
+ aReadyPromise);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (replacement) {
+ replacement.forget(aReplacementStream);
+ } else {
+ internal.forget(aReplacementStream);
+ }
+ return NS_OK;
+ }
+ }
+
+ // Preserve MIME information on the stream when normalizing.
+ if (nsCOMPtr<nsIMIMEInputStream> mime = do_QueryInterface(aUploadStream)) {
+ nsCOMPtr<nsIInputStream> data;
+ nsresult rv = mime->GetData(getter_AddRefs(data));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> replacement;
+ rv =
+ NormalizeUploadStream(data, getter_AddRefs(replacement), aReadyPromise);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (replacement) {
+ nsCOMPtr<nsIMIMEInputStream> replacementMime(
+ do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new MIMEHeaderCopyVisitor(replacementMime);
+ rv = mime->VisitHeaders(visitor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = replacementMime->SetData(replacement);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ replacementMime.forget(aReplacementStream);
+ }
+ return NS_OK;
+ }
+
+ // Preserve "real" buffered input streams which wrap data (i.e. are backed by
+ // nsBufferedInputStream), but normalize the wrapped stream.
+ if (nsCOMPtr<nsIBufferedInputStream> buffered =
+ do_QueryInterface(aUploadStream)) {
+ nsCOMPtr<nsIInputStream> data;
+ if (NS_SUCCEEDED(buffered->GetData(getter_AddRefs(data)))) {
+ nsCOMPtr<nsIInputStream> replacement;
+ nsresult rv = NormalizeUploadStream(data, getter_AddRefs(replacement),
+ aReadyPromise);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (replacement) {
+ // This buffer size should be kept in sync with HTMLFormSubmission.
+ rv = NS_NewBufferedInputStream(aReplacementStream, replacement.forget(),
+ 8192);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+ }
+ }
+
+ // Preserve multiplex input streams, normalizing each individual inner stream
+ // to avoid unnecessary copying.
+ if (nsCOMPtr<nsIMultiplexInputStream> multiplex =
+ do_QueryInterface(aUploadStream)) {
+ uint32_t count = multiplex->GetCount();
+ nsTArray<nsCOMPtr<nsIInputStream>> streams(count);
+ nsTArray<RefPtr<GenericPromise>> promises(count);
+ bool replace = false;
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIInputStream> inner;
+ nsresult rv = multiplex->GetStream(i, getter_AddRefs(inner));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<GenericPromise> promise;
+ nsCOMPtr<nsIInputStream> replacement;
+ rv = NormalizeUploadStream(inner, getter_AddRefs(replacement),
+ getter_AddRefs(promise));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (promise) {
+ promises.AppendElement(promise);
+ }
+ if (replacement) {
+ streams.AppendElement(replacement);
+ replace = true;
+ } else {
+ streams.AppendElement(inner);
+ }
+ }
+
+ // If any of the inner streams needed to be replaced, replace the entire
+ // nsIMultiplexInputStream.
+ if (replace) {
+ nsresult rv;
+ nsCOMPtr<nsIMultiplexInputStream> replacement =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (auto& stream : streams) {
+ rv = replacement->AppendStream(stream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(CallQueryInterface(replacement, aReplacementStream));
+ }
+
+ // Wait for all inner promises to settle before resolving the final promise.
+ if (!promises.IsEmpty()) {
+ RefPtr<GenericPromise> ready =
+ GenericPromise::AllSettled(GetCurrentSerialEventTarget(), promises)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [](GenericPromise::AllSettledPromiseType::
+ ResolveOrRejectValue&& aResults)
+ -> RefPtr<GenericPromise> {
+ MOZ_ASSERT(aResults.IsResolve(),
+ "AllSettled never rejects");
+ for (auto& result : aResults.ResolveValue()) {
+ if (result.IsReject()) {
+ return GenericPromise::CreateAndReject(
+ result.RejectValue(), __func__);
+ }
+ }
+ return GenericPromise::CreateAndResolve(true, __func__);
+ });
+ ready.forget(aReadyPromise);
+ }
+ return NS_OK;
+ }
+
+ // If the stream is cloneable, seekable and non-async, we can allow it. Async
+ // input streams can cause issues, as various consumers of input streams
+ // expect the payload to be synchronous and `Available()` to be the length of
+ // the stream, which is not true for asynchronous streams.
+ nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(aUploadStream);
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aUploadStream);
+ if (NS_InputStreamIsCloneable(aUploadStream) && seekable && !async) {
+ return NS_OK;
+ }
+
+ // Asynchronously copy our non-normalized stream into a StorageStream so that
+ // it is seekable, cloneable, and synchronous once the copy completes.
+
+ NS_WARNING("Upload Stream is being copied into StorageStream");
+
+ nsCOMPtr<nsIStorageStream> storageStream;
+ nsresult rv =
+ NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storageStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> sink;
+ rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> replacementStream;
+ rv = storageStream->NewInputStream(0, getter_AddRefs(replacementStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensure the source stream is buffered before starting the copy so we can use
+ // ReadSegments, as nsStorageStream doesn't implement WriteSegments.
+ nsCOMPtr<nsIInputStream> source = aUploadStream;
+ if (!NS_InputStreamIsBuffered(aUploadStream)) {
+ nsCOMPtr<nsIInputStream> bufferedSource;
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedSource),
+ source.forget(), 4096);
+ NS_ENSURE_SUCCESS(rv, rv);
+ source = bufferedSource.forget();
+ }
+
+ // Perform an AsyncCopy into the input stream on the STS.
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ RefPtr<GenericPromise::Private> ready = new GenericPromise::Private(__func__);
+ rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS, 4096,
+ NormalizeCopyComplete, do_AddRef(ready).take());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ready.get()->Release();
+ return rv;
+ }
+
+ replacementStream.forget(aReplacementStream);
+ ready.forget(aReadyPromise);
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+HttpBaseChannel::CloneUploadStream(int64_t* aContentLength,
+ nsIInputStream** aClonedStream) {
+ NS_ENSURE_ARG_POINTER(aContentLength);
+ NS_ENSURE_ARG_POINTER(aClonedStream);
+ *aClonedStream = nullptr;
+
+ if (!XRE_IsParentProcess()) {
+ NS_WARNING("CloneUploadStream is only supported in the parent process");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!mUploadStream) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> clonedStream;
+ nsresult rv =
+ NS_CloneInputStream(mUploadStream, getter_AddRefs(clonedStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ clonedStream.forget(aClonedStream);
+
+ *aContentLength = mReqContentLength;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIUploadChannel2
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream* aStream,
+ const nsACString& aContentType,
+ int64_t aContentLength,
+ const nsACString& aMethod,
+ bool aStreamHasHeaders) {
+ // Ensure stream is set and method is valid
+ NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE);
+
+ {
+ DebugOnly<nsCOMPtr<nsIMIMEInputStream>> mimeStream;
+ MOZ_ASSERT(
+ !aStreamHasHeaders || NS_FAILED(CallQueryInterface(
+ aStream, getter_AddRefs(mimeStream.value))),
+ "nsIMIMEInputStream should not include headers");
+ }
+
+ nsresult rv = SetRequestMethod(aMethod);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aStreamHasHeaders && !aContentType.IsVoid()) {
+ if (aContentType.IsEmpty()) {
+ SetEmptyRequestHeader("Content-Type"_ns);
+ } else {
+ SetRequestHeader("Content-Type"_ns, aContentType, false);
+ }
+ }
+
+ StoreUploadStreamHasHeaders(aStreamHasHeaders);
+
+ return InternalSetUploadStream(aStream, aContentLength, !aStreamHasHeaders);
+}
+
+nsresult HttpBaseChannel::InternalSetUploadStream(
+ nsIInputStream* aUploadStream, int64_t aContentLength,
+ bool aSetContentLengthHeader) {
+ // If we're not on the main thread, such as for TRR, the content length must
+ // be provided, as we can't normalize our upload stream.
+ if (!NS_IsMainThread()) {
+ if (aContentLength < 0) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Upload content length must be explicit off-main-thread");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aUploadStream);
+ if (!NS_InputStreamIsCloneable(aUploadStream) || !seekable) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Upload stream must be cloneable & seekable off-main-thread");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mUploadStream = aUploadStream;
+ ExplicitSetUploadStreamLength(aContentLength, aSetContentLengthHeader);
+ return NS_OK;
+ }
+
+ // Normalize the upload stream we're provided to ensure that it is cloneable,
+ // seekable, and synchronous when in the parent process.
+ //
+ // This might be an async operation, in which case ready will be returned and
+ // resolved when the operation is complete.
+ nsCOMPtr<nsIInputStream> replacement;
+ RefPtr<GenericPromise> ready;
+ if (XRE_IsParentProcess()) {
+ nsresult rv = NormalizeUploadStream(
+ aUploadStream, getter_AddRefs(replacement), getter_AddRefs(ready));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mUploadStream = replacement ? replacement.get() : aUploadStream;
+
+ // Once the upload stream is ready, fetch its length before proceeding with
+ // AsyncOpen.
+ auto onReady = [self = RefPtr{this}, aContentLength, aSetContentLengthHeader,
+ stream = mUploadStream]() {
+ auto setLengthAndResume = [self, aSetContentLengthHeader](int64_t aLength) {
+ self->StorePendingUploadStreamNormalization(false);
+ self->ExplicitSetUploadStreamLength(aLength >= 0 ? aLength : 0,
+ aSetContentLengthHeader);
+ self->MaybeResumeAsyncOpen();
+ };
+
+ if (aContentLength >= 0) {
+ setLengthAndResume(aContentLength);
+ return;
+ }
+
+ int64_t length;
+ if (InputStreamLengthHelper::GetSyncLength(stream, &length)) {
+ setLengthAndResume(length);
+ return;
+ }
+
+ InputStreamLengthHelper::GetAsyncLength(stream, setLengthAndResume);
+ };
+ StorePendingUploadStreamNormalization(true);
+
+ // Resolve onReady synchronously unless a promise is returned.
+ if (ready) {
+ ready->Then(GetCurrentSerialEventTarget(), __func__,
+ [onReady = std::move(onReady)](
+ GenericPromise::ResolveOrRejectValue&&) { onReady(); });
+ } else {
+ onReady();
+ }
+ return NS_OK;
+}
+
+void HttpBaseChannel::ExplicitSetUploadStreamLength(
+ uint64_t aContentLength, bool aSetContentLengthHeader) {
+ // We already have the content length. We don't need to determinate it.
+ mReqContentLength = aContentLength;
+
+ if (!aSetContentLengthHeader) {
+ return;
+ }
+
+ nsAutoCString header;
+ header.AssignLiteral("Content-Length");
+
+ // Maybe the content-length header has been already set.
+ nsAutoCString value;
+ nsresult rv = GetRequestHeader(header, value);
+ if (NS_SUCCEEDED(rv) && !value.IsEmpty()) {
+ return;
+ }
+
+ // SetRequestHeader propagates headers to chrome if HttpChannelChild
+ MOZ_ASSERT(!LoadWasOpened());
+ nsAutoCString contentLengthStr;
+ contentLengthStr.AppendInt(aContentLength);
+ SetRequestHeader(header, contentLengthStr, false);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetUploadStreamHasHeaders(bool* hasHeaders) {
+ NS_ENSURE_ARG(hasHeaders);
+
+ *hasHeaders = LoadUploadStreamHasHeaders();
+ return NS_OK;
+}
+
+bool HttpBaseChannel::MaybeWaitForUploadStreamNormalization(
+ nsIStreamListener* aListener, nsISupports* aContext) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!LoadAsyncOpenWaitingForStreamNormalization(),
+ "AsyncOpen() called twice?");
+
+ if (!LoadPendingUploadStreamNormalization()) {
+ return false;
+ }
+
+ mListener = aListener;
+ StoreAsyncOpenWaitingForStreamNormalization(true);
+ return true;
+}
+
+void HttpBaseChannel::MaybeResumeAsyncOpen() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!LoadPendingUploadStreamNormalization());
+
+ if (!LoadAsyncOpenWaitingForStreamNormalization()) {
+ return;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ listener.swap(mListener);
+
+ StoreAsyncOpenWaitingForStreamNormalization(false);
+
+ nsresult rv = AsyncOpen(listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ DoAsyncAbort(rv);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIEncodedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetApplyConversion(bool* value) {
+ *value = LoadApplyConversion();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetApplyConversion(bool value) {
+ LOG(("HttpBaseChannel::SetApplyConversion [this=%p value=%d]\n", this,
+ value));
+ StoreApplyConversion(value);
+ return NS_OK;
+}
+
+nsresult HttpBaseChannel::DoApplyContentConversions(
+ nsIStreamListener* aNextListener, nsIStreamListener** aNewNextListener) {
+ return DoApplyContentConversions(aNextListener, aNewNextListener, nullptr);
+}
+
+// create a listener chain that looks like this
+// http-channel -> decompressor (n times) -> InterceptFailedOnSTop ->
+// channel-creator-listener
+//
+// we need to do this because not every decompressor has fully streamed output
+// so may need a call to OnStopRequest to identify its completion state.. and if
+// it creates an error there the channel status code needs to be updated before
+// calling the terminal listener. Having the decompress do it via cancel() means
+// channels cannot effectively be used in two contexts (specifically this one
+// and a peek context for sniffing)
+//
+class InterceptFailedOnStop : public nsIStreamListener {
+ virtual ~InterceptFailedOnStop() = default;
+ nsCOMPtr<nsIStreamListener> mNext;
+ HttpBaseChannel* mChannel;
+
+ public:
+ InterceptFailedOnStop(nsIStreamListener* arg, HttpBaseChannel* chan)
+ : mNext(arg), mChannel(chan) {}
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override {
+ return mNext->OnStartRequest(aRequest);
+ }
+
+ NS_IMETHOD OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) override {
+ if (NS_FAILED(aStatusCode) && NS_SUCCEEDED(mChannel->mStatus)) {
+ LOG(("HttpBaseChannel::InterceptFailedOnStop %p seting status %" PRIx32,
+ mChannel, static_cast<uint32_t>(aStatusCode)));
+ mChannel->mStatus = aStatusCode;
+ }
+ return mNext->OnStopRequest(aRequest, aStatusCode);
+ }
+
+ NS_IMETHOD OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) override {
+ return mNext->OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
+ }
+};
+
+NS_IMPL_ISUPPORTS(InterceptFailedOnStop, nsIStreamListener, nsIRequestObserver)
+
+NS_IMETHODIMP
+HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
+ nsIStreamListener** aNewNextListener,
+ nsISupports* aCtxt) {
+ *aNewNextListener = nullptr;
+ if (!mResponseHead || !aNextListener) {
+ return NS_OK;
+ }
+
+ LOG(("HttpBaseChannel::DoApplyContentConversions [this=%p]\n", this));
+
+ if (!LoadApplyConversion()) {
+ LOG(("not applying conversion per ApplyConversion\n"));
+ return NS_OK;
+ }
+
+ if (LoadHasAppliedConversion()) {
+ LOG(("not applying conversion because HasAppliedConversion is true\n"));
+ return NS_OK;
+ }
+
+ if (LoadDeliveringAltData()) {
+ MOZ_ASSERT(!mAvailableCachedAltDataType.IsEmpty());
+ LOG(("not applying conversion because delivering alt-data\n"));
+ return NS_OK;
+ }
+
+ nsAutoCString contentEncoding;
+ nsresult rv =
+ mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+ if (NS_FAILED(rv) || contentEncoding.IsEmpty()) return NS_OK;
+
+ nsCOMPtr<nsIStreamListener> nextListener =
+ new InterceptFailedOnStop(aNextListener, this);
+
+ // The encodings are listed in the order they were applied
+ // (see rfc 2616 section 14.11), so they need to removed in reverse
+ // order. This is accomplished because the converter chain ends up
+ // being a stack with the last converter created being the first one
+ // to accept the raw network data.
+
+ char* cePtr = contentEncoding.BeginWriting();
+ uint32_t count = 0;
+ while (char* val = nsCRT::strtok(cePtr, HTTP_LWS ",", &cePtr)) {
+ if (++count > 16) {
+ // That's ridiculous. We only understand 2 different ones :)
+ // but for compatibility with old code, we will just carry on without
+ // removing the encodings
+ LOG(("Too many Content-Encodings. Ignoring remainder.\n"));
+ break;
+ }
+
+ if (gHttpHandler->IsAcceptableEncoding(val,
+ isSecureOrTrustworthyURL(mURI))) {
+ RefPtr<nsHTTPCompressConv> converter = new nsHTTPCompressConv();
+ nsAutoCString from(val);
+ ToLowerCase(from);
+ rv = converter->AsyncConvertData(from.get(), "uncompressed", nextListener,
+ aCtxt);
+ if (NS_FAILED(rv)) {
+ LOG(("Unexpected failure of AsyncConvertData %s\n", val));
+ return rv;
+ }
+
+ LOG(("converter removed '%s' content-encoding\n", val));
+ if (Telemetry::CanRecordPrereleaseData()) {
+ int mode = 0;
+ if (from.EqualsLiteral("gzip") || from.EqualsLiteral("x-gzip")) {
+ mode = 1;
+ } else if (from.EqualsLiteral("deflate") ||
+ from.EqualsLiteral("x-deflate")) {
+ mode = 2;
+ } else if (from.EqualsLiteral("br")) {
+ mode = 3;
+ }
+ Telemetry::Accumulate(Telemetry::HTTP_CONTENT_ENCODING, mode);
+ }
+ nextListener = converter;
+ } else {
+ if (val) LOG(("Unknown content encoding '%s', ignoring\n", val));
+ }
+ }
+ *aNewNextListener = do_AddRef(nextListener).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentEncodings(nsIUTF8StringEnumerator** aEncodings) {
+ if (!mResponseHead) {
+ *aEncodings = nullptr;
+ return NS_OK;
+ }
+
+ nsAutoCString encoding;
+ Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, encoding);
+ if (encoding.IsEmpty()) {
+ *aEncodings = nullptr;
+ return NS_OK;
+ }
+ RefPtr<nsContentEncodings> enumerator =
+ new nsContentEncodings(this, encoding.get());
+ enumerator.forget(aEncodings);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings <public>
+//-----------------------------------------------------------------------------
+
+HttpBaseChannel::nsContentEncodings::nsContentEncodings(
+ nsIHttpChannel* aChannel, const char* aEncodingHeader)
+ : mEncodingHeader(aEncodingHeader), mChannel(aChannel), mReady(false) {
+ mCurEnd = aEncodingHeader + strlen(aEncodingHeader);
+ mCurStart = mCurEnd;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings::nsISimpleEnumerator
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::nsContentEncodings::HasMore(bool* aMoreEncodings) {
+ if (mReady) {
+ *aMoreEncodings = true;
+ return NS_OK;
+ }
+
+ nsresult rv = PrepareForNext();
+ *aMoreEncodings = NS_SUCCEEDED(rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::nsContentEncodings::GetNext(nsACString& aNextEncoding) {
+ aNextEncoding.Truncate();
+ if (!mReady) {
+ nsresult rv = PrepareForNext();
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ const nsACString& encoding = Substring(mCurStart, mCurEnd);
+
+ nsACString::const_iterator start, end;
+ encoding.BeginReading(start);
+ encoding.EndReading(end);
+
+ bool haveType = false;
+ if (CaseInsensitiveFindInReadable("gzip"_ns, start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_GZIP);
+ haveType = true;
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable("compress"_ns, start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_COMPRESS);
+ haveType = true;
+ }
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable("deflate"_ns, start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_ZIP);
+ haveType = true;
+ }
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable("br"_ns, start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_BROTLI);
+ haveType = true;
+ }
+ }
+
+ // Prepare to fetch the next encoding
+ mCurEnd = mCurStart;
+ mReady = false;
+
+ if (haveType) return NS_OK;
+
+ NS_WARNING("Unknown encoding type");
+ return NS_ERROR_FAILURE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(HttpBaseChannel::nsContentEncodings, nsIUTF8StringEnumerator,
+ nsIStringEnumerator)
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings <private>
+//-----------------------------------------------------------------------------
+
+nsresult HttpBaseChannel::nsContentEncodings::PrepareForNext(void) {
+ MOZ_ASSERT(mCurStart == mCurEnd, "Indeterminate state");
+
+ // At this point both mCurStart and mCurEnd point to somewhere
+ // past the end of the next thing we want to return
+
+ while (mCurEnd != mEncodingHeader) {
+ --mCurEnd;
+ if (*mCurEnd != ',' && !nsCRT::IsAsciiSpace(*mCurEnd)) break;
+ }
+ if (mCurEnd == mEncodingHeader) {
+ return NS_ERROR_NOT_AVAILABLE; // no more encodings
+ }
+ ++mCurEnd;
+
+ // At this point mCurEnd points to the first char _after_ the
+ // header we want. Furthermore, mCurEnd - 1 != mEncodingHeader
+
+ mCurStart = mCurEnd - 1;
+ while (mCurStart != mEncodingHeader && *mCurStart != ',' &&
+ !nsCRT::IsAsciiSpace(*mCurStart)) {
+ --mCurStart;
+ }
+ if (*mCurStart == ',' || nsCRT::IsAsciiSpace(*mCurStart)) {
+ ++mCurStart; // we stopped because of a weird char, so move up one
+ }
+
+ // At this point mCurStart and mCurEnd bracket the encoding string
+ // we want. Check that it's not "identity"
+ if (Substring(mCurStart, mCurEnd)
+ .Equals("identity", nsCaseInsensitiveCStringComparator)) {
+ mCurEnd = mCurStart;
+ return PrepareForNext();
+ }
+
+ mReady = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetChannelId(uint64_t* aChannelId) {
+ NS_ENSURE_ARG_POINTER(aChannelId);
+ *aChannelId = mChannelId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetChannelId(uint64_t aChannelId) {
+ mChannelId = aChannelId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetTopLevelContentWindowId(uint64_t* aWindowId) {
+ if (!mContentWindowId) {
+ nsCOMPtr<nsILoadContext> loadContext;
+ GetCallback(loadContext);
+ if (loadContext) {
+ nsCOMPtr<mozIDOMWindowProxy> topWindow;
+ loadContext->GetTopWindow(getter_AddRefs(topWindow));
+ if (topWindow) {
+ if (nsPIDOMWindowInner* inner =
+ nsPIDOMWindowOuter::From(topWindow)->GetCurrentInnerWindow()) {
+ mContentWindowId = inner->WindowID();
+ }
+ }
+ }
+ }
+ *aWindowId = mContentWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetBrowserId(uint64_t aId) {
+ mBrowserId = aId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetBrowserId(uint64_t* aId) {
+ EnsureBrowserId();
+ *aId = mBrowserId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetTopLevelContentWindowId(uint64_t aWindowId) {
+ mContentWindowId = aWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsThirdPartyTrackingResource(bool* aIsTrackingResource) {
+ MOZ_ASSERT(
+ !(mFirstPartyClassificationFlags && mThirdPartyClassificationFlags));
+ *aIsTrackingResource = UrlClassifierCommon::IsTrackingClassificationFlag(
+ mThirdPartyClassificationFlags,
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsThirdPartySocialTrackingResource(
+ bool* aIsThirdPartySocialTrackingResource) {
+ MOZ_ASSERT(!mFirstPartyClassificationFlags ||
+ !mThirdPartyClassificationFlags);
+ *aIsThirdPartySocialTrackingResource =
+ UrlClassifierCommon::IsSocialTrackingClassificationFlag(
+ mThirdPartyClassificationFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetClassificationFlags(uint32_t* aFlags) {
+ if (mThirdPartyClassificationFlags) {
+ *aFlags = mThirdPartyClassificationFlags;
+ } else {
+ *aFlags = mFirstPartyClassificationFlags;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetFirstPartyClassificationFlags(uint32_t* aFlags) {
+ *aFlags = mFirstPartyClassificationFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetThirdPartyClassificationFlags(uint32_t* aFlags) {
+ *aFlags = mThirdPartyClassificationFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTransferSize(uint64_t* aTransferSize) {
+ MutexAutoLock lock(mOnDataFinishedMutex);
+ *aTransferSize = mTransferSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestSize(uint64_t* aRequestSize) {
+ *aRequestSize = mRequestSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDecodedBodySize(uint64_t* aDecodedBodySize) {
+ *aDecodedBodySize = mDecodedBodySize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) {
+ MutexAutoLock lock(mOnDataFinishedMutex);
+ *aEncodedBodySize = mEncodedBodySize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetSupportsHTTP3(bool* aSupportsHTTP3) {
+ *aSupportsHTTP3 = mSupportsHTTP3;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetHasHTTPSRR(bool* aHasHTTPSRR) {
+ *aHasHTTPSRR = LoadHasHTTPSRR();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestMethod(nsACString& aMethod) {
+ mRequestHead.Method(aMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestMethod(const nsACString& aMethod) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ const nsCString& flatMethod = PromiseFlatCString(aMethod);
+
+ // Method names are restricted to valid HTTP tokens.
+ if (!nsHttp::IsValidToken(flatMethod)) return NS_ERROR_INVALID_ARG;
+
+ mRequestHead.SetMethod(flatMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ NS_ENSURE_ARG_POINTER(aReferrerInfo);
+ *aReferrerInfo = do_AddRef(mReferrerInfo).take();
+ return NS_OK;
+}
+
+nsresult HttpBaseChannel::SetReferrerInfoInternal(
+ nsIReferrerInfo* aReferrerInfo, bool aClone, bool aCompute,
+ bool aRespectBeforeConnect) {
+ LOG(
+ ("HttpBaseChannel::SetReferrerInfoInternal [this=%p aClone(%d) "
+ "aCompute(%d)]\n",
+ this, aClone, aCompute));
+ if (aRespectBeforeConnect) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ }
+
+ mReferrerInfo = aReferrerInfo;
+
+ // clear existing referrer, if any
+ nsresult rv = ClearReferrerHeader();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!mReferrerInfo) {
+ return NS_OK;
+ }
+
+ if (aClone) {
+ mReferrerInfo = static_cast<dom::ReferrerInfo*>(aReferrerInfo)->Clone();
+ }
+
+ dom::ReferrerInfo* referrerInfo =
+ static_cast<dom::ReferrerInfo*>(mReferrerInfo.get());
+
+ // Don't set referrerInfo if it has not been initialized.
+ if (!referrerInfo->IsInitialized()) {
+ mReferrerInfo = nullptr;
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (aClone) {
+ // Record the telemetry once we set the referrer info to the channel
+ // successfully.
+ referrerInfo->RecordTelemetry(this);
+ }
+
+ if (aCompute) {
+ rv = referrerInfo->ComputeReferrer(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIURI> computedReferrer = mReferrerInfo->GetComputedReferrer();
+ if (!computedReferrer) {
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ rv = computedReferrer->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return SetReferrerHeader(spec, aRespectBeforeConnect);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ return SetReferrerInfoInternal(aReferrerInfo, true, true, true);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetReferrerInfoWithoutClone(nsIReferrerInfo* aReferrerInfo) {
+ return SetReferrerInfoInternal(aReferrerInfo, false, true, true);
+}
+
+// Return the channel's proxy URI, or if it doesn't exist, the
+// channel's main URI.
+NS_IMETHODIMP
+HttpBaseChannel::GetProxyURI(nsIURI** aOut) {
+ NS_ENSURE_ARG_POINTER(aOut);
+ nsCOMPtr<nsIURI> result(mProxyURI);
+ result.forget(aOut);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestHeader(const nsACString& aHeader,
+ nsACString& aValue) {
+ aValue.Truncate();
+
+ // XXX might be better to search the header list directly instead of
+ // hitting the http atom hash table.
+ nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+ if (!atom) return NS_ERROR_NOT_AVAILABLE;
+
+ return mRequestHead.GetHeader(atom, aValue);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) {
+ const nsCString& flatHeader = PromiseFlatCString(aHeader);
+ const nsCString& flatValue = PromiseFlatCString(aValue);
+
+ LOG(
+ ("HttpBaseChannel::SetRequestHeader [this=%p header=\"%s\" value=\"%s\" "
+ "merge=%u]\n",
+ this, flatHeader.get(), flatValue.get(), aMerge));
+
+ // Verify header names are valid HTTP tokens and header values are reasonably
+ // close to whats allowed in RFC 2616.
+ if (!nsHttp::IsValidToken(flatHeader) ||
+ !nsHttp::IsReasonableHeaderValue(flatValue)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Mark that the User-Agent header has been modified.
+ if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) {
+ StoreIsUserAgentHeaderModified(true);
+ }
+
+ return mRequestHead.SetHeader(aHeader, flatValue, aMerge);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNewReferrerInfo(const nsACString& aUrl,
+ nsIReferrerInfo::ReferrerPolicyIDL aPolicy,
+ bool aSendReferrer) {
+ nsresult rv;
+ // Create URI from string
+ nsCOMPtr<nsIURI> aURI;
+ rv = NS_NewURI(getter_AddRefs(aURI), aUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Create new ReferrerInfo and initialize it.
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new mozilla::dom::ReferrerInfo();
+ rv = referrerInfo->Init(aPolicy, aSendReferrer, aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Set ReferrerInfo
+ return SetReferrerInfo(referrerInfo);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetEmptyRequestHeader(const nsACString& aHeader) {
+ const nsCString& flatHeader = PromiseFlatCString(aHeader);
+
+ LOG(("HttpBaseChannel::SetEmptyRequestHeader [this=%p header=\"%s\"]\n", this,
+ flatHeader.get()));
+
+ // Verify header names are valid HTTP tokens and header values are reasonably
+ // close to whats allowed in RFC 2616.
+ if (!nsHttp::IsValidToken(flatHeader)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Mark that the User-Agent header has been modified.
+ if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) {
+ StoreIsUserAgentHeaderModified(true);
+ }
+
+ return mRequestHead.SetEmptyHeader(aHeader);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitRequestHeaders(nsIHttpHeaderVisitor* visitor) {
+ return mRequestHead.VisitHeaders(visitor);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor* visitor) {
+ return mRequestHead.VisitHeaders(visitor,
+ nsHttpHeaderArray::eFilterSkipDefault);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseHeader(const nsACString& header,
+ nsACString& value) {
+ value.Truncate();
+
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(header);
+ if (!atom) return NS_ERROR_NOT_AVAILABLE;
+
+ return mResponseHead->GetHeader(atom, value);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetResponseHeader(const nsACString& header,
+ const nsACString& value, bool merge) {
+ LOG(
+ ("HttpBaseChannel::SetResponseHeader [this=%p header=\"%s\" value=\"%s\" "
+ "merge=%u]\n",
+ this, PromiseFlatCString(header).get(), PromiseFlatCString(value).get(),
+ merge));
+
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(header);
+ if (!atom) return NS_ERROR_NOT_AVAILABLE;
+
+ // these response headers must not be changed
+ if (atom == nsHttp::Content_Type || atom == nsHttp::Content_Length ||
+ atom == nsHttp::Content_Encoding || atom == nsHttp::Trailer ||
+ atom == nsHttp::Transfer_Encoding) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ StoreResponseHeadersModified(true);
+
+ return mResponseHead->SetHeader(header, value, merge);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitResponseHeaders(nsIHttpHeaderVisitor* visitor) {
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mResponseHead->VisitHeaders(visitor,
+ nsHttpHeaderArray::eFilterResponse);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOriginalResponseHeader(const nsACString& aHeader,
+ nsIHttpHeaderVisitor* aVisitor) {
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+ if (!atom) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mResponseHead->GetOriginalHeader(atom, aVisitor);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mResponseHead->VisitHeaders(
+ aVisitor, nsHttpHeaderArray::eFilterResponseOriginal);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowSTS(bool* value) {
+ NS_ENSURE_ARG_POINTER(value);
+ *value = LoadAllowSTS();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowSTS(bool value) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ StoreAllowSTS(value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsOCSP(bool* value) {
+ NS_ENSURE_ARG_POINTER(value);
+ *value = LoadIsOCSP();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIsOCSP(bool value) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ StoreIsOCSP(value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsUserAgentHeaderModified(bool* value) {
+ NS_ENSURE_ARG_POINTER(value);
+ *value = LoadIsUserAgentHeaderModified();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIsUserAgentHeaderModified(bool value) {
+ StoreIsUserAgentHeaderModified(value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectionLimit(uint32_t* value) {
+ NS_ENSURE_ARG_POINTER(value);
+ *value = mRedirectionLimit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectionLimit(uint32_t value) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mRedirectionLimit = std::min<uint32_t>(value, 0xff);
+ return NS_OK;
+}
+
+nsresult HttpBaseChannel::OverrideSecurityInfo(
+ nsITransportSecurityInfo* aSecurityInfo) {
+ MOZ_ASSERT(!mSecurityInfo,
+ "This can only be called when we don't have a security info "
+ "object already");
+ MOZ_RELEASE_ASSERT(
+ aSecurityInfo,
+ "This can only be called with a valid security info object");
+ MOZ_ASSERT(!BypassServiceWorker(),
+ "This can only be called on channels that are not bypassing "
+ "interception");
+ MOZ_ASSERT(LoadResponseCouldBeSynthesized(),
+ "This can only be called on channels that can be intercepted");
+ if (mSecurityInfo) {
+ LOG(
+ ("HttpBaseChannel::OverrideSecurityInfo mSecurityInfo is null! "
+ "[this=%p]\n",
+ this));
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (!LoadResponseCouldBeSynthesized()) {
+ LOG(
+ ("HttpBaseChannel::OverrideSecurityInfo channel cannot be intercepted! "
+ "[this=%p]\n",
+ this));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mSecurityInfo = aSecurityInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsNoStoreResponse(bool* value) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ *value = mResponseHead->NoStore();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsNoCacheResponse(bool* value) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ *value = mResponseHead->NoCache();
+ if (!*value) *value = mResponseHead->ExpiresInPast();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsPrivateResponse(bool* value) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ *value = mResponseHead->Private();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseStatus(uint32_t* aValue) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ *aValue = mResponseHead->Status();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseStatusText(nsACString& aValue) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ nsAutoCString version;
+ // https://fetch.spec.whatwg.org :
+ // Responses over an HTTP/2 connection will always have the empty byte
+ // sequence as status message as HTTP/2 does not support them.
+ if (NS_WARN_IF(NS_FAILED(GetProtocolVersion(version))) ||
+ !version.EqualsLiteral("h2")) {
+ mResponseHead->StatusText(aValue);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestSucceeded(bool* aValue) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ uint32_t status = mResponseHead->Status();
+ *aValue = (status / 100 == 2);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::RedirectTo(nsIURI* targetURI) {
+ NS_ENSURE_ARG(targetURI);
+
+ nsAutoCString spec;
+ targetURI->GetAsciiSpec(spec);
+ LOG(("HttpBaseChannel::RedirectTo [this=%p, uri=%s]", this, spec.get()));
+ LogCallingScriptLocation(this);
+
+ // We cannot redirect after OnStartRequest of the listener
+ // has been called, since to redirect we have to switch channels
+ // and the dance with OnStartRequest et al has to start over.
+ // This would break the nsIStreamListener contract.
+ NS_ENSURE_FALSE(LoadOnStartRequestCalled(), NS_ERROR_NOT_AVAILABLE);
+
+ mAPIRedirectToURI = targetURI;
+ // Only Web Extensions are allowed to redirect a channel to a data:
+ // URI. To avoid any bypasses after the channel was flagged by
+ // the WebRequst API, we are dropping the flag here.
+ mLoadInfo->SetAllowInsecureRedirectToDataURI(false);
+
+ // We may want to rewrite origin allowance, hence we need an
+ // artificial response head.
+ if (!mResponseHead) {
+ mResponseHead.reset(new nsHttpResponseHead());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::UpgradeToSecure() {
+ // Upgrades are handled internally between http-on-modify-request and
+ // http-on-before-connect, which means upgrades are only possible during
+ // on-modify, or WebRequest.onBeforeRequest in Web Extensions. Once we are
+ // past the code path where upgrades are handled, attempting an upgrade
+ // will throw an error.
+ NS_ENSURE_TRUE(LoadUpgradableToSecure(), NS_ERROR_NOT_AVAILABLE);
+
+ StoreUpgradeToSecure(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestContextID(uint64_t* aRCID) {
+ NS_ENSURE_ARG_POINTER(aRCID);
+ *aRCID = mRequestContextID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestContextID(uint64_t aRCID) {
+ mRequestContextID = aRCID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsMainDocumentChannel(bool* aValue) {
+ NS_ENSURE_ARG_POINTER(aValue);
+ *aValue = IsNavigation();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIsMainDocumentChannel(bool aValue) {
+ StoreForceMainDocumentChannel(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetProtocolVersion(nsACString& aProtocolVersion) {
+ // Try to use ALPN if available and if it is not for a proxy, i.e if an
+ // https proxy was not used or if https proxy was used but the connection to
+ // the origin server is also https. In the case, an https proxy was used and
+ // the connection to the origin server was http, mSecurityInfo will be from
+ // the proxy.
+ if (!mConnectionInfo || !mConnectionInfo->UsingHttpsProxy() ||
+ mConnectionInfo->EndToEndSSL()) {
+ nsAutoCString protocol;
+ if (mSecurityInfo &&
+ NS_SUCCEEDED(mSecurityInfo->GetNegotiatedNPN(protocol)) &&
+ !protocol.IsEmpty()) {
+ // The negotiated protocol was not empty so we can use it.
+ aProtocolVersion = protocol;
+ return NS_OK;
+ }
+ }
+
+ if (mResponseHead) {
+ HttpVersion version = mResponseHead->Version();
+ aProtocolVersion.Assign(nsHttp::GetProtocolVersion(version));
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTopWindowURIIfUnknown(nsIURI* aTopWindowURI) {
+ if (!aTopWindowURI) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mTopWindowURI) {
+ LOG(
+ ("HttpChannelBase::SetTopWindowURIIfUnknown [this=%p] "
+ "mTopWindowURI is already set.\n",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> topWindowURI;
+ Unused << GetTopWindowURI(getter_AddRefs(topWindowURI));
+
+ // Don't modify |mTopWindowURI| if we can get one from GetTopWindowURI().
+ if (topWindowURI) {
+ LOG(
+ ("HttpChannelBase::SetTopWindowURIIfUnknown [this=%p] "
+ "Return an error since we got a top window uri.\n",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ mTopWindowURI = aTopWindowURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTopWindowURI(nsIURI** aTopWindowURI) {
+ nsCOMPtr<nsIURI> uriBeingLoaded =
+ AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(this);
+ return GetTopWindowURI(uriBeingLoaded, aTopWindowURI);
+}
+
+nsresult HttpBaseChannel::GetTopWindowURI(nsIURI* aURIBeingLoaded,
+ nsIURI** aTopWindowURI) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<mozIThirdPartyUtil> util;
+ // Only compute the top window URI once. In e10s, this must be computed in the
+ // child. The parent gets the top window URI through HttpChannelOpenArgs.
+ if (!mTopWindowURI) {
+ util = components::ThirdPartyUtil::Service();
+ if (!util) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ rv = util->GetTopWindowForChannel(this, aURIBeingLoaded,
+ getter_AddRefs(win));
+ if (NS_SUCCEEDED(rv)) {
+ rv = util->GetURIFromWindow(win, getter_AddRefs(mTopWindowURI));
+#if DEBUG
+ if (mTopWindowURI) {
+ nsCString spec;
+ if (NS_SUCCEEDED(mTopWindowURI->GetSpec(spec))) {
+ LOG(("HttpChannelBase::Setting topwindow URI spec %s [this=%p]\n",
+ spec.get(), this));
+ }
+ }
+#endif
+ }
+ }
+ *aTopWindowURI = do_AddRef(mTopWindowURI).take();
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDocumentURI(nsIURI** aDocumentURI) {
+ NS_ENSURE_ARG_POINTER(aDocumentURI);
+ *aDocumentURI = do_AddRef(mDocumentURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDocumentURI(nsIURI* aDocumentURI) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mDocumentURI = aDocumentURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestVersion(uint32_t* major, uint32_t* minor) {
+ HttpVersion version = mRequestHead.Version();
+
+ if (major) {
+ *major = static_cast<uint32_t>(version) / 10;
+ }
+ if (minor) {
+ *minor = static_cast<uint32_t>(version) % 10;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseVersion(uint32_t* major, uint32_t* minor) {
+ if (!mResponseHead) {
+ *major = *minor = 0; // we should at least be kind about it
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ HttpVersion version = mResponseHead->Version();
+
+ if (major) {
+ *major = static_cast<uint32_t>(version) / 10;
+ }
+ if (minor) {
+ *minor = static_cast<uint32_t>(version) % 10;
+ }
+
+ return NS_OK;
+}
+
+void HttpBaseChannel::NotifySetCookie(const nsACString& aCookie) {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(static_cast<nsIChannel*>(this),
+ "http-on-response-set-cookie",
+ NS_ConvertASCIItoUTF16(aCookie).get());
+ }
+}
+
+bool HttpBaseChannel::IsBrowsingContextDiscarded() const {
+ // If there is no loadGroup attached to the current channel, we check the
+ // global private browsing state for the private channel instead. For
+ // non-private channel, we will always return false here.
+ //
+ // Note that we can only access the global private browsing state in the
+ // parent process. So, we will fallback to just return false in the content
+ // process.
+ if (!mLoadGroup) {
+ if (!XRE_IsParentProcess()) {
+ return false;
+ }
+
+ return mLoadInfo->GetOriginAttributes().mPrivateBrowsingId != 0 &&
+ !dom::CanonicalBrowsingContext::IsPrivateBrowsingActive();
+ }
+
+ return mLoadGroup->GetIsBrowsingContextDiscarded();
+}
+
+// https://mikewest.github.io/corpp/#process-navigation-response
+nsresult HttpBaseChannel::ProcessCrossOriginEmbedderPolicyHeader() {
+ nsresult rv;
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ return NS_OK;
+ }
+
+ // Only consider Cross-Origin-Embedder-Policy for document loads.
+ if (mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT &&
+ mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ return NS_OK;
+ }
+
+ nsILoadInfo::CrossOriginEmbedderPolicy resultPolicy =
+ nsILoadInfo::EMBEDDER_POLICY_NULL;
+ bool isCoepCredentiallessEnabled;
+ rv = mLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ &isCoepCredentiallessEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetResponseEmbedderPolicy(isCoepCredentiallessEnabled, &resultPolicy);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ // https://html.spec.whatwg.org/multipage/origin.html#coep
+ if (mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SUBDOCUMENT &&
+ !nsHttpChannel::IsRedirectStatus(mResponseHead->Status()) &&
+ mLoadInfo->GetLoadingEmbedderPolicy() !=
+ nsILoadInfo::EMBEDDER_POLICY_NULL &&
+ resultPolicy != nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP &&
+ resultPolicy != nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) {
+ return NS_ERROR_DOM_COEP_FAILED;
+ }
+
+ return NS_OK;
+}
+
+// https://mikewest.github.io/corpp/#corp-check
+nsresult HttpBaseChannel::ProcessCrossOriginResourcePolicyHeader() {
+ // Fetch 4.5.9
+ dom::RequestMode requestMode;
+ MOZ_ALWAYS_SUCCEEDS(GetRequestMode(&requestMode));
+ // XXX this seems wrong per spec? What about navigate
+ if (requestMode != RequestMode::No_cors) {
+ return NS_OK;
+ }
+
+ // We only apply this for resources.
+ auto extContentPolicyType = mLoadInfo->GetExternalContentPolicyType();
+ if (extContentPolicyType == ExtContentPolicy::TYPE_DOCUMENT ||
+ extContentPolicyType == ExtContentPolicy::TYPE_WEBSOCKET ||
+ extContentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
+ return NS_OK;
+ }
+
+ if (extContentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ // COEP pref off, skip CORP checking for subdocument.
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ return NS_OK;
+ }
+ // COEP 3.2.1.2 when request targets a nested browsing context then embedder
+ // policy value is "unsafe-none", then return allowed.
+ if (mLoadInfo->GetLoadingEmbedderPolicy() ==
+ nsILoadInfo::EMBEDDER_POLICY_NULL) {
+ return NS_OK;
+ }
+ }
+
+ MOZ_ASSERT(mLoadInfo->GetLoadingPrincipal(),
+ "Resources should always have a LoadingPrincipal");
+ if (!mResponseHead) {
+ return NS_OK;
+ }
+
+ if (mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()) {
+ return NS_OK;
+ }
+
+ nsAutoCString content;
+ Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Resource_Policy,
+ content);
+
+ if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ if (content.IsEmpty()) {
+ if (mLoadInfo->GetLoadingEmbedderPolicy() ==
+ nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) {
+ bool requestIncludesCredentials = false;
+ nsresult rv = GetCorsIncludeCredentials(&requestIncludesCredentials);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+ // COEP: Set policy to `same-origin` if: response’s
+ // request-includes-credentials is true, or forNavigation is true.
+ if (requestIncludesCredentials ||
+ extContentPolicyType == ExtContentPolicyType::TYPE_SUBDOCUMENT) {
+ content = "same-origin"_ns;
+ }
+ } else if (mLoadInfo->GetLoadingEmbedderPolicy() ==
+ nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) {
+ // COEP 3.2.1.6 If policy is null, and embedder policy is
+ // "require-corp", set policy to "same-origin". Note that we treat
+ // invalid value as "cross-origin", which spec indicates. We might want
+ // to make that stricter.
+ content = "same-origin"_ns;
+ }
+ }
+ }
+
+ if (content.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> channelOrigin;
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(channelOrigin));
+
+ // Cross-Origin-Resource-Policy = %s"same-origin" / %s"same-site" /
+ // %s"cross-origin"
+ if (content.EqualsLiteral("same-origin")) {
+ if (!channelOrigin->Equals(mLoadInfo->GetLoadingPrincipal())) {
+ return NS_ERROR_DOM_CORP_FAILED;
+ }
+ return NS_OK;
+ }
+ if (content.EqualsLiteral("same-site")) {
+ nsAutoCString documentBaseDomain;
+ nsAutoCString resourceBaseDomain;
+ mLoadInfo->GetLoadingPrincipal()->GetBaseDomain(documentBaseDomain);
+ channelOrigin->GetBaseDomain(resourceBaseDomain);
+ if (documentBaseDomain != resourceBaseDomain) {
+ return NS_ERROR_DOM_CORP_FAILED;
+ }
+
+ nsCOMPtr<nsIURI> resourceURI = channelOrigin->GetURI();
+ if (!mLoadInfo->GetLoadingPrincipal()->SchemeIs("https") &&
+ resourceURI->SchemeIs("https")) {
+ return NS_ERROR_DOM_CORP_FAILED;
+ }
+
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+// See https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
+// This method runs steps 1-4 of the algorithm to compare
+// cross-origin-opener policies
+static bool CompareCrossOriginOpenerPolicies(
+ nsILoadInfo::CrossOriginOpenerPolicy documentPolicy,
+ nsIPrincipal* documentOrigin,
+ nsILoadInfo::CrossOriginOpenerPolicy resultPolicy,
+ nsIPrincipal* resultOrigin) {
+ if (documentPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE &&
+ resultPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
+ return true;
+ }
+
+ if (documentPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE ||
+ resultPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
+ return false;
+ }
+
+ if (documentPolicy == resultPolicy && documentOrigin->Equals(resultOrigin)) {
+ return true;
+ }
+
+ return false;
+}
+
+// This runs steps 1-5 of the algorithm when navigating a top level document.
+// See https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
+nsresult HttpBaseChannel::ComputeCrossOriginOpenerPolicyMismatch() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ StoreHasCrossOriginOpenerPolicyMismatch(false);
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginOpenerPolicy()) {
+ return NS_OK;
+ }
+
+ // Only consider Cross-Origin-Opener-Policy for toplevel document loads.
+ if (mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ return NS_OK;
+ }
+
+ // Maybe the channel failed and we have no response head?
+ if (!mResponseHead) {
+ // Not having a response head is not a hard failure at the point where
+ // this method is called.
+ return NS_OK;
+ }
+
+ RefPtr<mozilla::dom::BrowsingContext> ctx;
+ mLoadInfo->GetBrowsingContext(getter_AddRefs(ctx));
+
+ // In xpcshell-tests we don't always have a browsingContext
+ if (!ctx) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> resultOrigin;
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(resultOrigin));
+
+ // Get the policy of the active document, and the policy for the result.
+ nsILoadInfo::CrossOriginOpenerPolicy documentPolicy = ctx->GetOpenerPolicy();
+ nsILoadInfo::CrossOriginOpenerPolicy resultPolicy =
+ nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+ Unused << ComputeCrossOriginOpenerPolicy(documentPolicy, &resultPolicy);
+ mComputedCrossOriginOpenerPolicy = resultPolicy;
+
+ // Add a permission to mark this site as high-value into the permission DB.
+ if (resultPolicy != nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
+ mozilla::dom::AddHighValuePermission(
+ resultOrigin, mozilla::dom::kHighValueCOOPPermission);
+ }
+
+ // If bc's popup sandboxing flag set is not empty and potentialCOOP is
+ // non-null, then navigate bc to a network error and abort these steps.
+ if (resultPolicy != nsILoadInfo::OPENER_POLICY_UNSAFE_NONE &&
+ mLoadInfo->GetSandboxFlags()) {
+ LOG((
+ "HttpBaseChannel::ComputeCrossOriginOpenerPolicyMismatch network error "
+ "for non empty sandboxing and non null COOP"));
+ return NS_ERROR_DOM_COOP_FAILED;
+ }
+
+ // In xpcshell-tests we don't always have a current window global
+ RefPtr<mozilla::dom::WindowGlobalParent> currentWindowGlobal =
+ ctx->Canonical()->GetCurrentWindowGlobal();
+ if (!currentWindowGlobal) {
+ return NS_OK;
+ }
+
+ // We use the top window principal as the documentOrigin
+ nsCOMPtr<nsIPrincipal> documentOrigin =
+ currentWindowGlobal->DocumentPrincipal();
+
+ bool compareResult = CompareCrossOriginOpenerPolicies(
+ documentPolicy, documentOrigin, resultPolicy, resultOrigin);
+
+ if (LOG_ENABLED()) {
+ LOG(
+ ("HttpBaseChannel::HasCrossOriginOpenerPolicyMismatch - "
+ "doc:%d result:%d - compare:%d\n",
+ documentPolicy, resultPolicy, compareResult));
+ nsAutoCString docOrigin("(null)");
+ nsCOMPtr<nsIURI> uri = documentOrigin->GetURI();
+ if (uri) {
+ uri->GetSpec(docOrigin);
+ }
+ nsAutoCString resOrigin("(null)");
+ uri = resultOrigin->GetURI();
+ if (uri) {
+ uri->GetSpec(resOrigin);
+ }
+ LOG(("doc origin:%s - res origin: %s\n", docOrigin.get(), resOrigin.get()));
+ }
+
+ if (compareResult) {
+ return NS_OK;
+ }
+
+ // If one of the following is false:
+ // - document's policy is same-origin-allow-popups
+ // - resultPolicy is null
+ // - doc is the initial about:blank document
+ // then we have a mismatch.
+
+ if (documentPolicy != nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS) {
+ StoreHasCrossOriginOpenerPolicyMismatch(true);
+ return NS_OK;
+ }
+
+ if (resultPolicy != nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
+ StoreHasCrossOriginOpenerPolicyMismatch(true);
+ return NS_OK;
+ }
+
+ if (!currentWindowGlobal->IsInitialDocument()) {
+ StoreHasCrossOriginOpenerPolicyMismatch(true);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+nsresult HttpBaseChannel::ProcessCrossOriginSecurityHeaders() {
+ StoreProcessCrossOriginSecurityHeadersCalled(true);
+ nsresult rv = ProcessCrossOriginEmbedderPolicyHeader();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = ProcessCrossOriginResourcePolicyHeader();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return ComputeCrossOriginOpenerPolicyMismatch();
+}
+
+enum class Report { Error, Warning };
+
+// Helper Function to report messages to the console when the loaded
+// script had a wrong MIME type.
+void ReportMimeTypeMismatch(HttpBaseChannel* aChannel, const char* aMessageName,
+ nsIURI* aURI, const nsACString& aContentType,
+ Report report) {
+ NS_ConvertUTF8toUTF16 spec(aURI->GetSpecOrDefault());
+ NS_ConvertUTF8toUTF16 contentType(aContentType);
+
+ aChannel->LogMimeTypeMismatch(nsCString(aMessageName),
+ report == Report::Warning, spec, contentType);
+}
+
+// Check and potentially enforce X-Content-Type-Options: nosniff
+nsresult ProcessXCTO(HttpBaseChannel* aChannel, nsIURI* aURI,
+ nsHttpResponseHead* aResponseHead,
+ nsILoadInfo* aLoadInfo) {
+ if (!aURI || !aResponseHead || !aLoadInfo) {
+ // if there is no uri, no response head or no loadInfo, then there is
+ // nothing to do
+ return NS_OK;
+ }
+
+ // 1) Query the XCTO header and check if 'nosniff' is the first value.
+ nsAutoCString contentTypeOptionsHeader;
+ if (!aResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader)) {
+ // if failed to get XCTO header, then there is nothing to do.
+ return NS_OK;
+ }
+
+ // let's compare the header (ignoring case)
+ // e.g. "NoSniFF" -> "nosniff"
+ // if it's not 'nosniff' then there is nothing to do here
+ if (!contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) {
+ // since we are getting here, the XCTO header was sent;
+ // a non matching value most likely means a mistake happenend;
+ // e.g. sending 'nosnif' instead of 'nosniff', let's log a warning.
+ AutoTArray<nsString, 1> params;
+ CopyUTF8toUTF16(contentTypeOptionsHeader, *params.AppendElement());
+ RefPtr<dom::Document> doc;
+ aLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "XCTO"_ns, doc,
+ nsContentUtils::eSECURITY_PROPERTIES,
+ "XCTOHeaderValueMissing", params);
+ return NS_OK;
+ }
+
+ // 2) Query the content type from the channel
+ nsAutoCString contentType;
+ aResponseHead->ContentType(contentType);
+
+ // 3) Compare the expected MIME type with the actual type
+ if (aLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_STYLESHEET) {
+ if (contentType.EqualsLiteral(TEXT_CSS)) {
+ return NS_OK;
+ }
+ ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
+ Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SCRIPT) {
+ if (nsContentUtils::IsJavascriptMIMEType(
+ NS_ConvertUTF8toUTF16(contentType))) {
+ return NS_OK;
+ }
+ ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
+ Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ auto policyType = aLoadInfo->GetExternalContentPolicyType();
+ if (policyType == ExtContentPolicy::TYPE_DOCUMENT ||
+ policyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ // If the header XCTO nosniff is set for any browsing context, then
+ // we set the skipContentSniffing flag on the Loadinfo. Within
+ // GetMIMETypeFromContent we then bail early and do not do any sniffing.
+ aLoadInfo->SetSkipContentSniffing(true);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+// Ensure that a load of type script has correct MIME type
+nsresult EnsureMIMEOfScript(HttpBaseChannel* aChannel, nsIURI* aURI,
+ nsHttpResponseHead* aResponseHead,
+ nsILoadInfo* aLoadInfo) {
+ if (!aURI || !aResponseHead || !aLoadInfo) {
+ // if there is no uri, no response head or no loadInfo, then there is
+ // nothing to do
+ return NS_OK;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_SCRIPT) {
+ // if this is not a script load, then there is nothing to do
+ return NS_OK;
+ }
+
+ nsAutoCString contentType;
+ aResponseHead->ContentType(contentType);
+ NS_ConvertUTF8toUTF16 typeString(contentType);
+
+ if (nsContentUtils::IsJavascriptMIMEType(typeString)) {
+ // script load has type script
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::javaScript);
+ return NS_OK;
+ }
+
+ switch (aLoadInfo->InternalContentPolicyType()) {
+ case nsIContentPolicy::TYPE_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::script_load);
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER:
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE:
+ case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worker_load);
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::serviceworker_load);
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::importScript_load);
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
+ case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worklet_load);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected script type");
+ break;
+ }
+
+ if (aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(aURI)) {
+ // same origin
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::same_origin);
+ } else {
+ bool cors = false;
+ nsAutoCString corsOrigin;
+ nsresult rv = aResponseHead->GetHeader(
+ nsHttp::ResolveAtom("Access-Control-Allow-Origin"_ns), corsOrigin);
+ if (NS_SUCCEEDED(rv)) {
+ if (corsOrigin.Equals("*")) {
+ cors = true;
+ } else {
+ nsCOMPtr<nsIURI> corsOriginURI;
+ rv = NS_NewURI(getter_AddRefs(corsOriginURI), corsOrigin);
+ if (NS_SUCCEEDED(rv)) {
+ if (aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(corsOriginURI)) {
+ cors = true;
+ }
+ }
+ }
+ }
+ if (cors) {
+ // cors origin
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::CORS_origin);
+ } else {
+ // cross origin
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::cross_origin);
+ }
+ }
+
+ bool block = false;
+ if (StringBeginsWith(contentType, "image/"_ns)) {
+ // script load has type image
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::image);
+ block = true;
+ } else if (StringBeginsWith(contentType, "audio/"_ns)) {
+ // script load has type audio
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::audio);
+ block = true;
+ } else if (StringBeginsWith(contentType, "video/"_ns)) {
+ // script load has type video
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::video);
+ block = true;
+ } else if (StringBeginsWith(contentType, "text/csv"_ns)) {
+ // script load has type text/csv
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_csv);
+ block = true;
+ }
+
+ if (block) {
+ ReportMimeTypeMismatch(aChannel, "BlockScriptWithWrongMimeType2", aURI,
+ contentType, Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (StringBeginsWith(contentType, "text/plain"_ns)) {
+ // script load has type text/plain
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_plain);
+ } else if (StringBeginsWith(contentType, "text/xml"_ns)) {
+ // script load has type text/xml
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_xml);
+ } else if (StringBeginsWith(contentType, "application/octet-stream"_ns)) {
+ // script load has type application/octet-stream
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_octet_stream);
+ } else if (StringBeginsWith(contentType, "application/xml"_ns)) {
+ // script load has type application/xml
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_xml);
+ } else if (StringBeginsWith(contentType, "application/json"_ns)) {
+ // script load has type application/json
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_json);
+ } else if (StringBeginsWith(contentType, "text/json"_ns)) {
+ // script load has type text/json
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_json);
+ } else if (StringBeginsWith(contentType, "text/html"_ns)) {
+ // script load has type text/html
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_html);
+ } else if (contentType.IsEmpty()) {
+ // script load has no type
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::empty);
+ } else {
+ // script load has unknown type
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::unknown);
+ }
+
+ // We restrict importScripts() in worker code to JavaScript MIME types.
+ nsContentPolicyType internalType = aLoadInfo->InternalContentPolicyType();
+ if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS ||
+ internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE) {
+ ReportMimeTypeMismatch(aChannel, "BlockImportScriptsWithWrongMimeType",
+ aURI, contentType, Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+ internalType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER) {
+ // Do not block the load if the feature is not enabled.
+ if (!StaticPrefs::security_block_Worker_with_wrong_mime()) {
+ return NS_OK;
+ }
+
+ ReportMimeTypeMismatch(aChannel, "BlockWorkerWithWrongMimeType", aURI,
+ contentType, Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ // ES6 modules require a strict MIME type check.
+ if (internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE ||
+ internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD) {
+ ReportMimeTypeMismatch(aChannel, "BlockModuleWithWrongMimeType", aURI,
+ contentType, Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ return NS_OK;
+}
+
+// Warn when a load of type script uses a wrong MIME type and
+// wasn't blocked by EnsureMIMEOfScript or ProcessXCTO.
+void WarnWrongMIMEOfScript(HttpBaseChannel* aChannel, nsIURI* aURI,
+ nsHttpResponseHead* aResponseHead,
+ nsILoadInfo* aLoadInfo) {
+ if (!aURI || !aResponseHead || !aLoadInfo) {
+ // If there is no uri, no response head or no loadInfo, then there is
+ // nothing to do.
+ return;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_SCRIPT) {
+ // If this is not a script load, then there is nothing to do.
+ return;
+ }
+
+ bool succeeded;
+ MOZ_ALWAYS_SUCCEEDS(aChannel->GetRequestSucceeded(&succeeded));
+ if (!succeeded) {
+ // Do not warn for failed loads: HTTP error pages are usually in HTML.
+ return;
+ }
+
+ nsAutoCString contentType;
+ aResponseHead->ContentType(contentType);
+ NS_ConvertUTF8toUTF16 typeString(contentType);
+ if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
+ ReportMimeTypeMismatch(aChannel, "WarnScriptWithWrongMimeType", aURI,
+ contentType, Report::Warning);
+ }
+}
+
+nsresult HttpBaseChannel::ValidateMIMEType() {
+ nsresult rv = EnsureMIMEOfScript(this, mURI, mResponseHead.get(), mLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ProcessXCTO(this, mURI, mResponseHead.get(), mLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ WarnWrongMIMEOfScript(this, mURI, mResponseHead.get(), mLoadInfo);
+ return NS_OK;
+}
+
+bool HttpBaseChannel::ShouldFilterOpaqueResponse(
+ OpaqueResponseFilterFetch aFilterType) const {
+ MOZ_DIAGNOSTIC_ASSERT(ShouldBlockOpaqueResponse());
+
+ if (!mLoadInfo || ConfiguredFilterFetchResponseBehaviour() != aFilterType) {
+ return false;
+ }
+
+ // We should filter a response in the parent if it is opaque and is the result
+ // of a fetch() function from the Fetch specification.
+ return mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_FETCH;
+}
+
+bool HttpBaseChannel::ShouldBlockOpaqueResponse() const {
+ if (!mURI || !mResponseHead || !mLoadInfo) {
+ // if there is no uri, no response head or no loadInfo, then there is
+ // nothing to do
+ LOGORB("No block: no mURI, mResponseHead, or mLoadInfo");
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = mLoadInfo->GetLoadingPrincipal();
+ if (!principal || principal->IsSystemPrincipal()) {
+ // If it's a top-level load or a system principal, then there is nothing to
+ // do.
+ LOGORB("No block: top-level load or system principal");
+ return false;
+ }
+
+ // Check if the response is a opaque response, which means requestMode should
+ // be RequestMode::No_cors and responseType should be ResponseType::Opaque.
+ nsContentPolicyType contentPolicy = mLoadInfo->InternalContentPolicyType();
+
+ // Skip the RequestMode would be RequestMode::Navigate
+ if (contentPolicy == nsIContentPolicy::TYPE_DOCUMENT ||
+ contentPolicy == nsIContentPolicy::TYPE_SUBDOCUMENT ||
+ contentPolicy == nsIContentPolicy::TYPE_INTERNAL_FRAME ||
+ contentPolicy == nsIContentPolicy::TYPE_INTERNAL_IFRAME ||
+ // Skip the RequestMode would be RequestMode::Same_origin
+ contentPolicy == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+ contentPolicy == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER) {
+ return false;
+ }
+
+ uint32_t securityMode = mLoadInfo->GetSecurityMode();
+ // Skip when RequestMode would not be RequestMode::no_cors
+ if (securityMode !=
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT &&
+ securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL) {
+ LOGORB("No block: not no_cors requests");
+ return false;
+ }
+
+ // Only continue when ResponseType would be ResponseType::Opaque
+ if (mLoadInfo->GetTainting() != mozilla::LoadTainting::Opaque) {
+ LOGORB("No block: not opaque response");
+ return false;
+ }
+
+ auto extContentPolicyType = mLoadInfo->GetExternalContentPolicyType();
+ if (extContentPolicyType == ExtContentPolicy::TYPE_OBJECT ||
+ extContentPolicyType == ExtContentPolicy::TYPE_OBJECT_SUBREQUEST ||
+ extContentPolicyType == ExtContentPolicy::TYPE_WEBSOCKET ||
+ extContentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
+ LOGORB("No block: object || websocket request || save as download");
+ return false;
+ }
+
+ // Ignore the request from object or embed elements
+ if (mLoadInfo->GetIsFromObjectOrEmbed()) {
+ LOGORB("No block: Request From <object> or <embed>");
+ return false;
+ }
+
+ // Exclude no_cors System XHR
+ if (extContentPolicyType == ExtContentPolicy::TYPE_XMLHTTPREQUEST) {
+ if (securityMode ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT) {
+ LOGORB("No block: System XHR");
+ return false;
+ }
+ }
+
+ uint32_t httpsOnlyStatus = mLoadInfo->GetHttpsOnlyStatus();
+ if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_BYPASS_ORB) {
+ LOGORB("No block: HTTPS_ONLY_BYPASS_ORB");
+ return false;
+ }
+
+ bool isInDevToolsContext;
+ mLoadInfo->GetIsInDevToolsContext(&isInDevToolsContext);
+ if (isInDevToolsContext) {
+ LOGORB("No block: Request created by devtools");
+ return false;
+ }
+
+ return true;
+}
+
+OpaqueResponse HttpBaseChannel::BlockOrFilterOpaqueResponse(
+ OpaqueResponseBlocker* aORB, const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason,
+ const char* aFormat, ...) {
+ NimbusFeatures::RecordExposureEvent("opaqueResponseBlocking"_ns, true);
+
+ const bool shouldFilter =
+ ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::BlockedByORB);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(GetORBLog(), LogLevel::Debug))) {
+ va_list ap;
+ va_start(ap, aFormat);
+ nsVprintfCString logString(aFormat, ap);
+ va_end(ap);
+
+ LOGORB("%s: %s", shouldFilter ? "Filtered" : "Blocked", logString.get());
+ }
+
+ if (shouldFilter) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::FILTERED_FETCH);
+ // The existence of `mORB` depends on `BlockOrFilterOpaqueResponse` being
+ // called before or after sniffing has completed.
+ // Another requirement is that `OpaqueResponseFilter` must come after
+ // `OpaqueResponseBlocker`, which is why in the case of having an
+ // `OpaqueResponseBlocker` we let it handle creating an
+ // `OpaqueResponseFilter`.
+ if (aORB) {
+ MOZ_DIAGNOSTIC_ASSERT(!mORB || aORB == mORB);
+ aORB->FilterResponse();
+ } else {
+ mListener = new OpaqueResponseFilter(mListener);
+ }
+ return OpaqueResponse::Allow;
+ }
+
+ LogORBError(aReason, aTelemetryReason);
+ return OpaqueResponse::Block;
+}
+
+// The specification for ORB is currently being written:
+// https://whatpr.org/fetch/1442.html#orb-algorithm
+// The `opaque-response-safelist check` is implemented in:
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff`
+// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff`
+// * `OpaqueResponseBlocker::ValidateJavaScript`
+OpaqueResponse
+HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // https://whatpr.org/fetch/1442.html#http-fetch, step 6.4
+ if (!ShouldBlockOpaqueResponse()) {
+ return OpaqueResponse::Allow;
+ }
+
+ // Regardless of if ORB is enabled or not, we check if we should filter the
+ // response in the parent. This way data won't reach a content process that
+ // will create a filtered `Response` object. This is enabled when
+ // 'browser.opaqueResponseBlocking.filterFetchResponse' is
+ // `OpaqueResponseFilterFetch::All`.
+ // See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
+ if (ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::All)) {
+ mListener = new OpaqueResponseFilter(mListener);
+
+ // If we're filtering a response in the parent, there will be no data to
+ // determine if it should be blocked or not so the only option we have is to
+ // allow it.
+ return OpaqueResponse::Allow;
+ }
+
+ if (!mCachedOpaqueResponseBlockingPref) {
+ return OpaqueResponse::Allow;
+ }
+
+ // If ORB is enabled, we check if we should filter the response in the parent.
+ // This way data won't reach a content process that will create a filtered
+ // `Response` object. We allow ORB to determine if the response should be
+ // blocked or filtered, but regardless no data should reach the content
+ // process. This is enabled when
+ // 'browser.opaqueResponseBlocking.filterFetchResponse' is
+ // `OpaqueResponseFilterFetch::AllowedByORB`.
+ // See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
+ if (ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::AllowedByORB)) {
+ mListener = new OpaqueResponseFilter(mListener);
+ }
+
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::
+ OPAQUE_RESPONSE_BLOCKING_CROSS_ORIGIN_OPAQUE_RESPONSE_COUNT,
+ 1);
+
+ PROFILER_MARKER_TEXT("ORB safelist check", NETWORK, {}, "Before sniff"_ns);
+
+ // https://whatpr.org/fetch/1442.html#orb-algorithm
+ // Step 1
+ nsAutoCString contentType;
+ mResponseHead->ContentType(contentType);
+
+ // Step 2
+ nsAutoCString contentTypeOptionsHeader;
+ bool nosniff =
+ mResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader) &&
+ contentTypeOptionsHeader.EqualsIgnoreCase("nosniff");
+
+ // Step 3
+ switch (GetOpaqueResponseBlockedReason(contentType, mResponseHead->Status(),
+ nosniff)) {
+ case OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED:
+ // Step 3.1
+ return OpaqueResponse::Allow;
+ case OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED_SPEC_BREAKING:
+ LOGORB("Allowed %s in a spec breaking way", contentType.get());
+ return OpaqueResponse::Allow;
+ case OpaqueResponseBlockedReason::BLOCKED_BLOCKLISTED_NEVER_SNIFFED:
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"mimeType is an opaque-blocklisted-never-sniffed MIME type"_ns,
+ OpaqueResponseBlockedTelemetryReason::MIME_NEVER_SNIFFED,
+ "BLOCKED_BLOCKLISTED_NEVER_SNIFFED");
+ case OpaqueResponseBlockedReason::BLOCKED_206_AND_BLOCKLISTED:
+ // Step 3.3
+ return BlockOrFilterOpaqueResponse(
+ mORB,
+ u"response's status is 206 and mimeType is an opaque-blocklisted MIME type"_ns,
+ OpaqueResponseBlockedTelemetryReason::RESP_206_BLCLISTED,
+ "BLOCKED_206_AND_BLOCKEDLISTED");
+ case OpaqueResponseBlockedReason::
+ BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN:
+ // Step 3.4
+ return BlockOrFilterOpaqueResponse(
+ mORB,
+ u"nosniff is true and mimeType is an opaque-blocklisted MIME type or its essence is 'text/plain'"_ns,
+ OpaqueResponseBlockedTelemetryReason::NOSNIFF_BLC_OR_TEXTP,
+ "BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN");
+ default:
+ break;
+ }
+
+ // Step 4
+ // If it's a media subsequent request, we assume that it will only be made
+ // after a successful initial request.
+ bool isMediaRequest;
+ mLoadInfo->GetIsMediaRequest(&isMediaRequest);
+ if (isMediaRequest) {
+ bool isMediaInitialRequest;
+ mLoadInfo->GetIsMediaInitialRequest(&isMediaInitialRequest);
+ if (!isMediaInitialRequest) {
+ return OpaqueResponse::Allow;
+ }
+ }
+
+ // Step 5
+ if (mResponseHead->Status() == 206 &&
+ !IsFirstPartialResponse(*mResponseHead)) {
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"response status is 206 and not first partial response"_ns,
+ OpaqueResponseBlockedTelemetryReason::RESP_206_BLCLISTED,
+ "Is not a valid partial response given 0");
+ }
+
+ // Setup for steps 6, 7, 8 and 10.
+ // Steps 6 and 7 are handled by the sniffer framework.
+ // Steps 8 and 10 by are handled by
+ // `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
+ if (mLoadFlags & nsIChannel::LOAD_CALL_CONTENT_SNIFFERS) {
+ mSnifferCategoryType = SnifferCategoryType::All;
+ } else {
+ mSnifferCategoryType = SnifferCategoryType::OpaqueResponseBlocking;
+ }
+
+ mLoadFlags |= (nsIChannel::LOAD_CALL_CONTENT_SNIFFERS |
+ nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE);
+
+ // Install an input stream listener that performs ORB checks that depend on
+ // inspecting the incoming data. It is crucial that `OnStartRequest` is called
+ // on this listener either after sniffing is completed or that we skip
+ // sniffing, otherwise `OpaqueResponseBlocker` will allow responses that it
+ // shouldn't.
+ mORB = new OpaqueResponseBlocker(mListener, this, contentType, nosniff);
+ mListener = mORB;
+
+ nsAutoCString contentEncoding;
+ nsresult rv =
+ mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+
+ if (NS_SUCCEEDED(rv) && !contentEncoding.IsEmpty()) {
+ return OpaqueResponse::SniffCompressed;
+ }
+ mLoadFlags |= (nsIChannel::LOAD_CALL_CONTENT_SNIFFERS |
+ nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE);
+ return OpaqueResponse::Sniff;
+}
+
+// The specification for ORB is currently being written:
+// https://whatpr.org/fetch/1442.html#orb-algorithm
+// The `opaque-response-safelist check` is implemented in:
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff`
+// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff`
+// * `OpaqueResponseBlocker::ValidateJavaScript`
+OpaqueResponse HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff(
+ const nsACString& aContentType, bool aNoSniff) {
+ PROFILER_MARKER_TEXT("ORB safelist check", NETWORK, {}, "After sniff"_ns);
+
+ // https://whatpr.org/fetch/1442.html#orb-algorithm
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(mCachedOpaqueResponseBlockingPref);
+
+ // Step 9
+ bool isMediaRequest;
+ mLoadInfo->GetIsMediaRequest(&isMediaRequest);
+ if (isMediaRequest) {
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"after sniff: media request"_ns,
+ OpaqueResponseBlockedTelemetryReason::AFTER_SNIFF_MEDIA,
+ "media request");
+ }
+
+ // Step 11
+ if (aNoSniff) {
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"after sniff: nosniff is true"_ns,
+ OpaqueResponseBlockedTelemetryReason::AFTER_SNIFF_NOSNIFF, "nosniff");
+ }
+
+ // Step 12
+ if (mResponseHead &&
+ (mResponseHead->Status() < 200 || mResponseHead->Status() > 299)) {
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"after sniff: status code is not in allowed range"_ns,
+ OpaqueResponseBlockedTelemetryReason::AFTER_SNIFF_STA_CODE,
+ "status code (%d) is not allowed", mResponseHead->Status());
+ }
+
+ // Step 13
+ if (!mResponseHead || aContentType.IsEmpty()) {
+ LOGORB("Allowed: mimeType is failure");
+ return OpaqueResponse::Allow;
+ }
+
+ // Step 14
+ if (StringBeginsWith(aContentType, "image/"_ns) ||
+ StringBeginsWith(aContentType, "video/"_ns) ||
+ StringBeginsWith(aContentType, "audio/"_ns)) {
+ return BlockOrFilterOpaqueResponse(
+ mORB,
+ u"after sniff: content-type declares image/video/audio, but sniffing fails"_ns,
+ OpaqueResponseBlockedTelemetryReason::AFTER_SNIFF_CT_FAIL,
+ "ContentType is image/video/audio");
+ }
+
+ return OpaqueResponse::Sniff;
+}
+
+bool HttpBaseChannel::NeedOpaqueResponseAllowedCheckAfterSniff() const {
+ return mORB ? mORB->IsSniffing() : false;
+}
+
+void HttpBaseChannel::BlockOpaqueResponseAfterSniff(
+ const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason) {
+ MOZ_DIAGNOSTIC_ASSERT(mORB);
+ LogORBError(aReason, aTelemetryReason);
+ mORB->BlockResponse(this, NS_ERROR_FAILURE);
+}
+
+void HttpBaseChannel::AllowOpaqueResponseAfterSniff() {
+ MOZ_DIAGNOSTIC_ASSERT(mORB);
+ mORB->AllowResponse();
+}
+
+void HttpBaseChannel::SetChannelBlockedByOpaqueResponse() {
+ mChannelBlockedByOpaqueResponse = true;
+
+ RefPtr<dom::BrowsingContext> browsingContext =
+ dom::BrowsingContext::GetCurrentTopByBrowserId(mBrowserId);
+ if (!browsingContext) {
+ return;
+ }
+
+ dom::WindowContext* windowContext = browsingContext->GetTopWindowContext();
+ if (windowContext) {
+ windowContext->Canonical()->SetShouldReportHasBlockedOpaqueResponse(
+ mLoadInfo->InternalContentPolicyType());
+ }
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCookie(const nsACString& aCookieHeader) {
+ if (mLoadFlags & LOAD_ANONYMOUS) return NS_OK;
+
+ if (IsBrowsingContextDiscarded()) {
+ return NS_OK;
+ }
+
+ // empty header isn't an error
+ if (aCookieHeader.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsICookieService* cs = gHttpHandler->GetCookieService();
+ NS_ENSURE_TRUE(cs, NS_ERROR_FAILURE);
+
+ nsresult rv = cs->SetCookieStringFromHttp(mURI, aCookieHeader, this);
+ if (NS_SUCCEEDED(rv)) {
+ NotifySetCookie(aCookieHeader);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetThirdPartyFlags(uint32_t* aFlags) {
+ *aFlags = LoadThirdPartyFlags();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetThirdPartyFlags(uint32_t aFlags) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ StoreThirdPartyFlags(aFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetForceAllowThirdPartyCookie(bool* aForce) {
+ *aForce = !!(LoadThirdPartyFlags() &
+ nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetForceAllowThirdPartyCookie(bool aForce) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ if (aForce) {
+ StoreThirdPartyFlags(LoadThirdPartyFlags() |
+ nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ } else {
+ StoreThirdPartyFlags(LoadThirdPartyFlags() &
+ ~nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCanceled(bool* aCanceled) {
+ *aCanceled = mCanceled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetChannelIsForDownload(bool* aChannelIsForDownload) {
+ *aChannelIsForDownload = LoadChannelIsForDownload();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetChannelIsForDownload(bool aChannelIsForDownload) {
+ StoreChannelIsForDownload(aChannelIsForDownload);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCacheKeysRedirectChain(nsTArray<nsCString>* cacheKeys) {
+ auto RedirectedCachekeys = mRedirectedCachekeys.Lock();
+ auto& ref = RedirectedCachekeys.ref();
+ ref = WrapUnique(cacheKeys);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLocalAddress(nsACString& addr) {
+ if (mSelfAddr.raw.family == PR_AF_UNSPEC) return NS_ERROR_NOT_AVAILABLE;
+
+ addr.SetLength(kIPv6CStrBufSize);
+ mSelfAddr.ToStringBuffer(addr.BeginWriting(), kIPv6CStrBufSize);
+ addr.SetLength(strlen(addr.BeginReading()));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::TakeAllSecurityMessages(
+ nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ aMessages.Clear();
+ for (const auto& pair : mSecurityConsoleMessages) {
+ nsresult rv;
+ nsCOMPtr<nsISecurityConsoleMessage> message =
+ do_CreateInstance(NS_SECURITY_CONSOLE_MESSAGE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ message->SetTag(pair.first);
+ message->SetCategory(pair.second);
+ aMessages.AppendElement(message);
+ }
+
+ MOZ_ASSERT(mSecurityConsoleMessages.Length() == aMessages.Length());
+ mSecurityConsoleMessages.Clear();
+
+ return NS_OK;
+}
+
+/* Please use this method with care. This can cause the message
+ * queue to grow large and cause the channel to take up a lot
+ * of memory. Use only static string messages and do not add
+ * server side data to the queue, as that can be large.
+ * Add only a limited number of messages to the queue to keep
+ * the channel size down and do so only in rare erroneous situations.
+ * More information can be found here:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=846918
+ */
+nsresult HttpBaseChannel::AddSecurityMessage(
+ const nsAString& aMessageTag, const nsAString& aMessageCategory) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ // nsSecurityConsoleMessage is not thread-safe refcounted.
+ // Delay the object construction until requested.
+ // See TakeAllSecurityMessages()
+ std::pair<nsString, nsString> pair(aMessageTag, aMessageCategory);
+ mSecurityConsoleMessages.AppendElement(std::move(pair));
+
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ if (!console) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
+
+ auto innerWindowID = loadInfo->GetInnerWindowID();
+
+ nsAutoString errorText;
+ rv = nsContentUtils::GetLocalizedString(
+ nsContentUtils::eSECURITY_PROPERTIES,
+ NS_ConvertUTF16toUTF8(aMessageTag).get(), errorText);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ error->InitWithSourceURI(
+ errorText, mURI, u""_ns, 0, 0, nsIScriptError::warningFlag,
+ NS_ConvertUTF16toUTF8(aMessageCategory), innerWindowID);
+
+ console->LogMessage(error);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLocalPort(int32_t* port) {
+ NS_ENSURE_ARG_POINTER(port);
+
+ if (mSelfAddr.raw.family == PR_AF_INET) {
+ *port = (int32_t)ntohs(mSelfAddr.inet.port);
+ } else if (mSelfAddr.raw.family == PR_AF_INET6) {
+ *port = (int32_t)ntohs(mSelfAddr.inet6.port);
+ } else {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRemoteAddress(nsACString& addr) {
+ if (mPeerAddr.raw.family == PR_AF_UNSPEC) return NS_ERROR_NOT_AVAILABLE;
+
+ addr.SetLength(kIPv6CStrBufSize);
+ mPeerAddr.ToStringBuffer(addr.BeginWriting(), kIPv6CStrBufSize);
+ addr.SetLength(strlen(addr.BeginReading()));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRemotePort(int32_t* port) {
+ NS_ENSURE_ARG_POINTER(port);
+
+ if (mPeerAddr.raw.family == PR_AF_INET) {
+ *port = (int32_t)ntohs(mPeerAddr.inet.port);
+ } else if (mPeerAddr.raw.family == PR_AF_INET6) {
+ *port = (int32_t)ntohs(mPeerAddr.inet6.port);
+ } else {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::HTTPUpgrade(const nsACString& aProtocolName,
+ nsIHttpUpgradeListener* aListener) {
+ NS_ENSURE_ARG(!aProtocolName.IsEmpty());
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mUpgradeProtocol = aProtocolName;
+ mUpgradeProtocolCallback = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOnlyConnect(bool* aOnlyConnect) {
+ NS_ENSURE_ARG_POINTER(aOnlyConnect);
+
+ *aOnlyConnect = mCaps & NS_HTTP_CONNECT_ONLY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetConnectOnly() {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ if (!mUpgradeProtocolCallback) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCaps |= NS_HTTP_CONNECT_ONLY;
+ mProxyResolveFlags = nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL;
+ return SetLoadFlags(nsIRequest::INHIBIT_CACHING | nsIChannel::LOAD_ANONYMOUS |
+ nsIRequest::LOAD_BYPASS_CACHE |
+ nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowSpdy(bool* aAllowSpdy) {
+ NS_ENSURE_ARG_POINTER(aAllowSpdy);
+
+ *aAllowSpdy = LoadAllowSpdy();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowSpdy(bool aAllowSpdy) {
+ StoreAllowSpdy(aAllowSpdy);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowHttp3(bool* aAllowHttp3) {
+ NS_ENSURE_ARG_POINTER(aAllowHttp3);
+
+ *aAllowHttp3 = LoadAllowHttp3();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowHttp3(bool aAllowHttp3) {
+ StoreAllowHttp3(aAllowHttp3);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowAltSvc(bool* aAllowAltSvc) {
+ NS_ENSURE_ARG_POINTER(aAllowAltSvc);
+
+ *aAllowAltSvc = LoadAllowAltSvc();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowAltSvc(bool aAllowAltSvc) {
+ StoreAllowAltSvc(aAllowAltSvc);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetBeConservative(bool* aBeConservative) {
+ NS_ENSURE_ARG_POINTER(aBeConservative);
+
+ *aBeConservative = LoadBeConservative();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetBeConservative(bool aBeConservative) {
+ StoreBeConservative(aBeConservative);
+ return NS_OK;
+}
+
+bool HttpBaseChannel::BypassProxy() {
+ return StaticPrefs::network_proxy_allow_bypass() && LoadBypassProxy();
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetBypassProxy(bool* aBypassProxy) {
+ NS_ENSURE_ARG_POINTER(aBypassProxy);
+
+ *aBypassProxy = BypassProxy();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetBypassProxy(bool aBypassProxy) {
+ if (StaticPrefs::network_proxy_allow_bypass()) {
+ StoreBypassProxy(aBypassProxy);
+ } else {
+ NS_WARNING("bypassProxy set but network.proxy.allow_bypass is disabled");
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsTRRServiceChannel(bool* aIsTRRServiceChannel) {
+ NS_ENSURE_ARG_POINTER(aIsTRRServiceChannel);
+
+ *aIsTRRServiceChannel = LoadIsTRRServiceChannel();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIsTRRServiceChannel(bool aIsTRRServiceChannel) {
+ StoreIsTRRServiceChannel(aIsTRRServiceChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsResolvedByTRR(bool* aResolvedByTRR) {
+ NS_ENSURE_ARG_POINTER(aResolvedByTRR);
+ *aResolvedByTRR = LoadResolvedByTRR();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEffectiveTRRMode(nsIRequest::TRRMode* aEffectiveTRRMode) {
+ *aEffectiveTRRMode = mEffectiveTRRMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTrrSkipReason(nsITRRSkipReason::value* aTrrSkipReason) {
+ *aTrrSkipReason = mTRRSkipReason;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsLoadedBySocketProcess(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = LoadLoadedBySocketProcess();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTlsFlags(uint32_t* aTlsFlags) {
+ NS_ENSURE_ARG_POINTER(aTlsFlags);
+
+ *aTlsFlags = mTlsFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTlsFlags(uint32_t aTlsFlags) {
+ mTlsFlags = aTlsFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetApiRedirectToURI(nsIURI** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = do_AddRef(mAPIRedirectToURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseTimeoutEnabled(bool* aEnable) {
+ if (NS_WARN_IF(!aEnable)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aEnable = LoadResponseTimeoutEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetResponseTimeoutEnabled(bool aEnable) {
+ StoreResponseTimeoutEnabled(aEnable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetInitialRwin(uint32_t* aRwin) {
+ if (NS_WARN_IF(!aRwin)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aRwin = mInitialRwin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetInitialRwin(uint32_t aRwin) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ mInitialRwin = aRwin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::ForcePending(bool aForcePending) {
+ StoreForcePending(aForcePending);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLastModifiedTime(PRTime* lastModifiedTime) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ uint32_t lastMod;
+ nsresult rv = mResponseHead->GetLastModifiedValue(&lastMod);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *lastModifiedTime = lastMod;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCorsIncludeCredentials(bool* aInclude) {
+ *aInclude = LoadCorsIncludeCredentials();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCorsIncludeCredentials(bool aInclude) {
+ StoreCorsIncludeCredentials(aInclude);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestMode(RequestMode* aMode) {
+ *aMode = mRequestMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestMode(RequestMode aMode) {
+ MOZ_ASSERT(aMode != RequestMode::EndGuard_);
+ mRequestMode = aMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectMode(uint32_t* aMode) {
+ *aMode = mRedirectMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectMode(uint32_t aMode) {
+ mRedirectMode = aMode;
+ return NS_OK;
+}
+
+namespace {
+
+bool ContainsAllFlags(uint32_t aLoadFlags, uint32_t aMask) {
+ return (aLoadFlags & aMask) == aMask;
+}
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+HttpBaseChannel::GetFetchCacheMode(uint32_t* aFetchCacheMode) {
+ NS_ENSURE_ARG_POINTER(aFetchCacheMode);
+
+ // Otherwise try to guess an appropriate cache mode from the load flags.
+ if (ContainsAllFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE;
+ } else if (ContainsAllFlags(mLoadFlags, LOAD_BYPASS_CACHE)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD;
+ } else if (ContainsAllFlags(mLoadFlags, VALIDATE_ALWAYS) ||
+ LoadForceValidateCacheContent()) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE;
+ } else if (ContainsAllFlags(
+ mLoadFlags,
+ VALIDATE_NEVER | nsICachingChannel::LOAD_ONLY_FROM_CACHE)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED;
+ } else if (ContainsAllFlags(mLoadFlags, VALIDATE_NEVER)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE;
+ } else {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT;
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+void SetCacheFlags(uint32_t& aLoadFlags, uint32_t aFlags) {
+ // First, clear any possible cache related flags.
+ uint32_t allPossibleFlags =
+ nsIRequest::INHIBIT_CACHING | nsIRequest::LOAD_BYPASS_CACHE |
+ nsIRequest::VALIDATE_ALWAYS | nsIRequest::LOAD_FROM_CACHE |
+ nsICachingChannel::LOAD_ONLY_FROM_CACHE;
+ aLoadFlags &= ~allPossibleFlags;
+
+ // Then set the new flags.
+ aLoadFlags |= aFlags;
+}
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+HttpBaseChannel::SetFetchCacheMode(uint32_t aFetchCacheMode) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ // Now, set the load flags that implement each cache mode.
+ switch (aFetchCacheMode) {
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT:
+ // The "default" mode means to use the http cache normally and
+ // respect any http cache-control headers. We effectively want
+ // to clear our cache related load flags.
+ SetCacheFlags(mLoadFlags, 0);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE:
+ // no-store means don't consult the cache on the way to the network, and
+ // don't store the response in the cache even if it's cacheable.
+ SetCacheFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD:
+ // reload means don't consult the cache on the way to the network, but
+ // do store the response in the cache if possible.
+ SetCacheFlags(mLoadFlags, LOAD_BYPASS_CACHE);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE:
+ // no-cache means always validate what's in the cache.
+ SetCacheFlags(mLoadFlags, VALIDATE_ALWAYS);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE:
+ // force-cache means don't validate unless if the response would vary.
+ SetCacheFlags(mLoadFlags, VALIDATE_NEVER);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED:
+ // only-if-cached means only from cache, no network, no validation,
+ // generate a network error if the document was't in the cache. The
+ // privacy implications of these flags (making it fast/easy to check if
+ // the user has things in their cache without any network traffic side
+ // effects) are addressed in the Request constructor which
+ // enforces/requires same-origin request mode.
+ SetCacheFlags(mLoadFlags,
+ VALIDATE_NEVER | nsICachingChannel::LOAD_ONLY_FROM_CACHE);
+ break;
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ uint32_t finalMode = 0;
+ MOZ_ALWAYS_SUCCEEDS(GetFetchCacheMode(&finalMode));
+ MOZ_DIAGNOSTIC_ASSERT(finalMode == aFetchCacheMode);
+#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIntegrityMetadata(const nsAString& aIntegrityMetadata) {
+ mIntegrityMetadata = aIntegrityMetadata;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIntegrityMetadata(nsAString& aIntegrityMetadata) {
+ aIntegrityMetadata = mIntegrityMetadata;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsISupportsPriority
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetPriority(int32_t* value) {
+ *value = mPriority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::AdjustPriority(int32_t delta) {
+ return SetPriority(mPriority + delta);
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIResumableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEntityID(nsACString& aEntityID) {
+ // Don't return an entity ID for Non-GET requests which require
+ // additional data
+ if (!mRequestHead.IsGet()) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ uint64_t size = UINT64_MAX;
+ nsAutoCString etag, lastmod;
+ if (mResponseHead) {
+ // Don't return an entity if the server sent the following header:
+ // Accept-Ranges: none
+ // Not sending the Accept-Ranges header means we can still try
+ // sending range requests.
+ nsAutoCString acceptRanges;
+ Unused << mResponseHead->GetHeader(nsHttp::Accept_Ranges, acceptRanges);
+ if (!acceptRanges.IsEmpty() &&
+ !nsHttp::FindToken(acceptRanges.get(), "bytes",
+ HTTP_HEADER_VALUE_SEPS)) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ size = mResponseHead->TotalEntitySize();
+ Unused << mResponseHead->GetHeader(nsHttp::Last_Modified, lastmod);
+ Unused << mResponseHead->GetHeader(nsHttp::ETag, etag);
+ }
+ nsCString entityID;
+ NS_EscapeURL(etag.BeginReading(), etag.Length(),
+ esc_AlwaysCopy | esc_FileBaseName | esc_Forced, entityID);
+ entityID.Append('/');
+ entityID.AppendInt(int64_t(size));
+ entityID.Append('/');
+ entityID.Append(lastmod);
+ // NOTE: Appending lastmod as the last part avoids having to escape it
+
+ aEntityID = entityID;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIConsoleReportCollector
+//-----------------------------------------------------------------------------
+
+void HttpBaseChannel::AddConsoleReport(
+ uint32_t aErrorFlags, const nsACString& aCategory,
+ nsContentUtils::PropertiesFile aPropertiesFile,
+ const nsACString& aSourceFileURI, uint32_t aLineNumber,
+ uint32_t aColumnNumber, const nsACString& aMessageName,
+ const nsTArray<nsString>& aStringParams) {
+ mReportCollector->AddConsoleReport(aErrorFlags, aCategory, aPropertiesFile,
+ aSourceFileURI, aLineNumber, aColumnNumber,
+ aMessageName, aStringParams);
+
+ // If this channel is already part of a loadGroup, we can flush this console
+ // report immediately.
+ HttpBaseChannel::MaybeFlushConsoleReports();
+}
+
+void HttpBaseChannel::FlushReportsToConsole(uint64_t aInnerWindowID,
+ ReportAction aAction) {
+ mReportCollector->FlushReportsToConsole(aInnerWindowID, aAction);
+}
+
+void HttpBaseChannel::FlushReportsToConsoleForServiceWorkerScope(
+ const nsACString& aScope, ReportAction aAction) {
+ mReportCollector->FlushReportsToConsoleForServiceWorkerScope(aScope, aAction);
+}
+
+void HttpBaseChannel::FlushConsoleReports(dom::Document* aDocument,
+ ReportAction aAction) {
+ mReportCollector->FlushConsoleReports(aDocument, aAction);
+}
+
+void HttpBaseChannel::FlushConsoleReports(nsILoadGroup* aLoadGroup,
+ ReportAction aAction) {
+ mReportCollector->FlushConsoleReports(aLoadGroup, aAction);
+}
+
+void HttpBaseChannel::FlushConsoleReports(
+ nsIConsoleReportCollector* aCollector) {
+ mReportCollector->FlushConsoleReports(aCollector);
+}
+
+void HttpBaseChannel::StealConsoleReports(
+ nsTArray<net::ConsoleReportCollected>& aReports) {
+ mReportCollector->StealConsoleReports(aReports);
+}
+
+void HttpBaseChannel::ClearConsoleReports() {
+ mReportCollector->ClearConsoleReports();
+}
+
+bool HttpBaseChannel::IsNavigation() {
+ return LoadForceMainDocumentChannel() || (mLoadFlags & LOAD_DOCUMENT_URI);
+}
+
+bool HttpBaseChannel::BypassServiceWorker() const {
+ return mLoadFlags & LOAD_BYPASS_SERVICE_WORKER;
+}
+
+bool HttpBaseChannel::ShouldIntercept(nsIURI* aURI) {
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+ bool shouldIntercept = false;
+
+ if (!StaticPrefs::dom_serviceWorkers_enabled()) {
+ return false;
+ }
+
+ // We should never intercept internal redirects. The ServiceWorker code
+ // can trigger interntal redirects as the result of a FetchEvent. If
+ // we re-intercept then an infinite loop can occur.
+ //
+ // Its also important that we do not set the LOAD_BYPASS_SERVICE_WORKER
+ // flag because an internal redirect occurs. Its possible that another
+ // interception should occur after the internal redirect. For example,
+ // if the ServiceWorker chooses not to call respondWith() the channel
+ // will be reset with an internal redirect. If the request is a navigation
+ // and the network then triggers a redirect its possible the new URL
+ // should be intercepted again.
+ //
+ // Note, HSTS upgrade redirects are often treated the same as internal
+ // redirects. In this case, however, we intentionally allow interception
+ // of HSTS upgrade redirects. This matches the expected spec behavior and
+ // does not run the risk of infinite loops as described above.
+ bool internalRedirect =
+ mLastRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL;
+
+ if (controller && mLoadInfo && !BypassServiceWorker() && !internalRedirect) {
+ nsresult rv = controller->ShouldPrepareForIntercept(
+ aURI ? aURI : mURI.get(), this, &shouldIntercept);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ }
+ return shouldIntercept;
+}
+
+void HttpBaseChannel::AddAsNonTailRequest() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (EnsureRequestContext()) {
+ LOG((
+ "HttpBaseChannel::AddAsNonTailRequest this=%p, rc=%p, already added=%d",
+ this, mRequestContext.get(), (bool)LoadAddedAsNonTailRequest()));
+
+ if (!LoadAddedAsNonTailRequest()) {
+ mRequestContext->AddNonTailRequest();
+ StoreAddedAsNonTailRequest(true);
+ }
+ }
+}
+
+void HttpBaseChannel::RemoveAsNonTailRequest() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mRequestContext) {
+ LOG(
+ ("HttpBaseChannel::RemoveAsNonTailRequest this=%p, rc=%p, already "
+ "added=%d",
+ this, mRequestContext.get(), (bool)LoadAddedAsNonTailRequest()));
+
+ if (LoadAddedAsNonTailRequest()) {
+ mRequestContext->RemoveNonTailRequest();
+ StoreAddedAsNonTailRequest(false);
+ }
+ }
+}
+
+#ifdef DEBUG
+void HttpBaseChannel::AssertPrivateBrowsingId() {
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+
+ if (!loadContext) {
+ return;
+ }
+
+ // We skip testing of favicon loading here since it could be triggered by XUL
+ // image which uses SystemPrincipal. The SystemPrincpal doesn't have
+ // mPrivateBrowsingId.
+ if (mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal() &&
+ mLoadInfo->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
+ return;
+ }
+
+ OriginAttributes docShellAttrs;
+ loadContext->GetOriginAttributes(docShellAttrs);
+ MOZ_ASSERT(mLoadInfo->GetOriginAttributes().mPrivateBrowsingId ==
+ docShellAttrs.mPrivateBrowsingId,
+ "PrivateBrowsingId values are not the same between LoadInfo and "
+ "LoadContext.");
+}
+#endif
+
+already_AddRefed<nsILoadInfo> HttpBaseChannel::CloneLoadInfoForRedirect(
+ nsIURI* aNewURI, uint32_t aRedirectFlags) {
+ // make a copy of the loadinfo, append to the redirectchain
+ // this will be set on the newly created channel for the redirect target.
+ nsCOMPtr<nsILoadInfo> newLoadInfo =
+ static_cast<mozilla::net::LoadInfo*>(mLoadInfo.get())->Clone();
+
+ ExtContentPolicyType contentPolicyType =
+ mLoadInfo->GetExternalContentPolicyType();
+ if (contentPolicyType == ExtContentPolicy::TYPE_DOCUMENT ||
+ contentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ // Reset PrincipalToInherit to a null principal. We'll credit the the
+ // redirecting resource's result principal as the new principal's precursor.
+ // This means that a data: URI will end up loading in a process based on the
+ // redirected-from URI.
+ nsCOMPtr<nsIPrincipal> redirectPrincipal;
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(redirectPrincipal));
+ nsCOMPtr<nsIPrincipal> nullPrincipalToInherit =
+ NullPrincipal::CreateWithInheritedAttributes(redirectPrincipal);
+ newLoadInfo->SetPrincipalToInherit(nullPrincipalToInherit);
+ }
+
+ bool isTopLevelDoc = newLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT;
+
+ if (isTopLevelDoc) {
+ // re-compute the origin attributes of the loadInfo if it's top-level load.
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ OriginAttributes docShellAttrs;
+ if (loadContext) {
+ loadContext->GetOriginAttributes(docShellAttrs);
+ }
+
+ OriginAttributes attrs = newLoadInfo->GetOriginAttributes();
+
+ MOZ_ASSERT(
+ docShellAttrs.mUserContextId == attrs.mUserContextId,
+ "docshell and necko should have the same userContextId attribute.");
+ MOZ_ASSERT(
+ docShellAttrs.mPrivateBrowsingId == attrs.mPrivateBrowsingId,
+ "docshell and necko should have the same privateBrowsingId attribute.");
+ MOZ_ASSERT(docShellAttrs.mGeckoViewSessionContextId ==
+ attrs.mGeckoViewSessionContextId,
+ "docshell and necko should have the same "
+ "geckoViewSessionContextId attribute");
+
+ attrs = docShellAttrs;
+ attrs.SetFirstPartyDomain(true, aNewURI);
+ newLoadInfo->SetOriginAttributes(attrs);
+
+ // re-compute the upgrade insecure requests bit for document navigations
+ // since it should only apply to same-origin navigations (redirects).
+ // we only do this if the CSP of the triggering element (the cspToInherit)
+ // uses 'upgrade-insecure-requests', otherwise UIR does not apply.
+ nsCOMPtr<nsIContentSecurityPolicy> csp = newLoadInfo->GetCspToInherit();
+ if (csp) {
+ bool upgradeInsecureRequests = false;
+ csp->GetUpgradeInsecureRequests(&upgradeInsecureRequests);
+ if (upgradeInsecureRequests) {
+ nsCOMPtr<nsIPrincipal> resultPrincipal =
+ BasePrincipal::CreateContentPrincipal(
+ aNewURI, newLoadInfo->GetOriginAttributes());
+ bool isConsideredSameOriginforUIR =
+ nsContentSecurityUtils::IsConsideredSameOriginForUIR(
+ newLoadInfo->TriggeringPrincipal(), resultPrincipal);
+ static_cast<mozilla::net::LoadInfo*>(newLoadInfo.get())
+ ->SetUpgradeInsecureRequests(isConsideredSameOriginforUIR);
+ }
+ }
+ }
+
+ // Leave empty, we want a 'clean ground' when creating the new channel.
+ // This will be ensured to be either set by the protocol handler or set
+ // to the redirect target URI properly after the channel creation.
+ newLoadInfo->SetResultPrincipalURI(nullptr);
+
+ bool isInternalRedirect =
+ (aRedirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+
+ // Reset our sandboxed null principal ID when cloning loadInfo for an
+ // externally visible redirect.
+ if (!isInternalRedirect) {
+ // If we've redirected from http to something that isn't, clear
+ // the "external" flag, as loads that now go to other apps should be
+ // allowed to go ahead and not trip infinite-loop protection
+ // (see bug 1717314 for context).
+ if (!aNewURI->SchemeIs("http") && !aNewURI->SchemeIs("https")) {
+ newLoadInfo->SetLoadTriggeredFromExternal(false);
+ }
+ newLoadInfo->ResetSandboxedNullPrincipalID();
+ }
+
+ newLoadInfo->AppendRedirectHistoryEntry(this, isInternalRedirect);
+
+ return newLoadInfo.forget();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsITraceableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNewListener(nsIStreamListener* aListener,
+ bool aMustApplyContentConversion,
+ nsIStreamListener** _retval) {
+ LOG((
+ "HttpBaseChannel::SetNewListener [this=%p, mListener=%p, newListener=%p]",
+ this, mListener.get(), aListener));
+
+ if (!LoadTracingEnabled()) return NS_ERROR_FAILURE;
+
+ NS_ENSURE_STATE(mListener);
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ nsCOMPtr<nsIStreamListener> wrapper = new nsStreamListenerWrapper(mListener);
+
+ wrapper.forget(_retval);
+ mListener = aListener;
+ if (aMustApplyContentConversion) {
+ StoreListenerRequiresContentConversion(true);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel helpers
+//-----------------------------------------------------------------------------
+
+void HttpBaseChannel::ReleaseListeners() {
+ MOZ_ASSERT(mCurrentThread->IsOnCurrentThread(),
+ "Should only be called on the current thread");
+
+ mListener = nullptr;
+ mCallbacks = nullptr;
+ mProgressSink = nullptr;
+ mCompressListener = nullptr;
+ mORB = nullptr;
+}
+
+void HttpBaseChannel::DoNotifyListener() {
+ LOG(("HttpBaseChannel::DoNotifyListener this=%p", this));
+
+ // In case nsHttpChannel::OnStartRequest wasn't called (e.g. due to flag
+ // LOAD_ONLY_IF_MODIFIED) we want to set AfterOnStartRequestBegun to true
+ // before notifying listener.
+ if (!LoadAfterOnStartRequestBegun()) {
+ StoreAfterOnStartRequestBegun(true);
+ }
+
+ if (mListener && !LoadOnStartRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ StoreOnStartRequestCalled(true);
+ listener->OnStartRequest(this);
+ }
+ StoreOnStartRequestCalled(true);
+
+ // Make sure IsPending is set to false. At this moment we are done from
+ // the point of view of our consumer and we have to report our self
+ // as not-pending.
+ StoreIsPending(false);
+
+ if (mListener && !LoadOnStopRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ StoreOnStopRequestCalled(true);
+ listener->OnStopRequest(this, mStatus);
+ }
+ StoreOnStopRequestCalled(true);
+
+ // notify "http-on-stop-connect" observers
+ gHttpHandler->OnStopRequest(this);
+
+ // This channel has finished its job, potentially release any tail-blocked
+ // requests with this.
+ RemoveAsNonTailRequest();
+
+ // We have to make sure to drop the references to listeners and callbacks
+ // no longer needed.
+ ReleaseListeners();
+
+ DoNotifyListenerCleanup();
+
+ // If this is a navigation, then we must let the docshell flush the reports
+ // to the console later. The LoadDocument() is pointing at the detached
+ // document that started the navigation. We want to show the reports on the
+ // new document. Otherwise the console is wiped and the user never sees
+ // the information.
+ if (!IsNavigation()) {
+ if (mLoadGroup) {
+ FlushConsoleReports(mLoadGroup);
+ } else {
+ RefPtr<dom::Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+ FlushConsoleReports(doc);
+ }
+ }
+}
+
+void HttpBaseChannel::AddCookiesToRequest() {
+ if (mLoadFlags & LOAD_ANONYMOUS) {
+ return;
+ }
+
+ bool useCookieService = (XRE_IsParentProcess());
+ nsAutoCString cookie;
+ if (useCookieService) {
+ nsICookieService* cs = gHttpHandler->GetCookieService();
+ if (cs) {
+ cs->GetCookieStringFromHttp(mURI, this, cookie);
+ }
+
+ if (cookie.IsEmpty()) {
+ cookie = mUserSetCookieHeader;
+ } else if (!mUserSetCookieHeader.IsEmpty()) {
+ cookie.AppendLiteral("; ");
+ cookie.Append(mUserSetCookieHeader);
+ }
+ } else {
+ cookie = mUserSetCookieHeader;
+ }
+
+ // If we are in the child process, we want the parent seeing any
+ // cookie headers that might have been set by SetRequestHeader()
+ SetRequestHeader(nsHttp::Cookie.val(), cookie, false);
+}
+
+/* static */
+void HttpBaseChannel::PropagateReferenceIfNeeded(
+ nsIURI* aURI, nsCOMPtr<nsIURI>& aRedirectURI) {
+ bool hasRef = false;
+ nsresult rv = aRedirectURI->GetHasRef(&hasRef);
+ if (NS_SUCCEEDED(rv) && !hasRef) {
+ nsAutoCString ref;
+ aURI->GetRef(ref);
+ if (!ref.IsEmpty()) {
+ // NOTE: SetRef will fail if mRedirectURI is immutable
+ // (e.g. an about: URI)... Oh well.
+ Unused << NS_MutateURI(aRedirectURI).SetRef(ref).Finalize(aRedirectURI);
+ }
+ }
+}
+
+bool HttpBaseChannel::ShouldRewriteRedirectToGET(
+ uint32_t httpStatus, nsHttpRequestHead::ParsedMethodType method) {
+ // for 301 and 302, only rewrite POST
+ if (httpStatus == 301 || httpStatus == 302) {
+ return method == nsHttpRequestHead::kMethod_Post;
+ }
+
+ // rewrite for 303 unless it was HEAD
+ if (httpStatus == 303) return method != nsHttpRequestHead::kMethod_Head;
+
+ // otherwise, such as for 307, do not rewrite
+ return false;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::ShouldStripRequestBodyHeader(const nsACString& aMethod,
+ bool* aResult) {
+ *aResult = false;
+ uint32_t httpStatus = 0;
+ if (NS_FAILED(GetResponseStatus(&httpStatus))) {
+ return NS_OK;
+ }
+
+ nsAutoCString method(aMethod);
+ nsHttpRequestHead::ParsedMethodType parsedMethod;
+ nsHttpRequestHead::ParseMethod(method, parsedMethod);
+ // Fetch 4.4.11, which is slightly different than the perserved method
+ // algrorithm: strip request-body-header for GET->GET redirection for 303.
+ *aResult =
+ ShouldRewriteRedirectToGET(httpStatus, parsedMethod) &&
+ !(httpStatus == 303 && parsedMethod == nsHttpRequestHead::kMethod_Get);
+
+ return NS_OK;
+}
+
+HttpBaseChannel::ReplacementChannelConfig
+HttpBaseChannel::CloneReplacementChannelConfig(bool aPreserveMethod,
+ uint32_t aRedirectFlags,
+ ReplacementReason aReason) {
+ ReplacementChannelConfig config;
+ config.redirectFlags = aRedirectFlags;
+ config.classOfService = mClassOfService;
+
+ if (mPrivateBrowsingOverriden) {
+ config.privateBrowsing = Some(mPrivateBrowsing);
+ }
+
+ if (mReferrerInfo) {
+ // When cloning for a document channel replacement (parent process
+ // copying values for a new content process channel), this happens after
+ // OnStartRequest so we have the headers for the response available.
+ // We don't want to apply them to the referrer for the channel though,
+ // since that is the referrer for the current document, and the header
+ // should only apply to navigations from the current document.
+ if (aReason == ReplacementReason::DocumentChannel) {
+ config.referrerInfo = mReferrerInfo;
+ } else {
+ dom::ReferrerPolicy referrerPolicy = dom::ReferrerPolicy::_empty;
+ nsAutoCString tRPHeaderCValue;
+ Unused << GetResponseHeader("referrer-policy"_ns, tRPHeaderCValue);
+ NS_ConvertUTF8toUTF16 tRPHeaderValue(tRPHeaderCValue);
+
+ if (!tRPHeaderValue.IsEmpty()) {
+ referrerPolicy =
+ dom::ReferrerInfo::ReferrerPolicyFromHeaderString(tRPHeaderValue);
+ }
+
+ // In case we are here because an upgrade happened through mixed content
+ // upgrading, CSP upgrade-insecure-requests, HTTPS-Only or HTTPS-First, we
+ // have to recalculate the referrer based on the original referrer to
+ // account for the different scheme. This does NOT apply to HSTS.
+ // See Bug 1857894 and order of https://fetch.spec.whatwg.org/#main-fetch.
+ // Otherwise, if we have a new referrer policy, we want to recalculate the
+ // referrer based on the old computed referrer (Bug 1678545).
+ bool wasNonHSTSUpgrade =
+ (aRedirectFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE) &&
+ (!mLoadInfo->GetHstsStatus());
+ if (wasNonHSTSUpgrade) {
+ nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetOriginalReferrer();
+ config.referrerInfo =
+ new dom::ReferrerInfo(referrer, mReferrerInfo->ReferrerPolicy(),
+ mReferrerInfo->GetSendReferrer());
+ } else if (referrerPolicy != dom::ReferrerPolicy::_empty) {
+ nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
+ config.referrerInfo = new dom::ReferrerInfo(
+ referrer, referrerPolicy, mReferrerInfo->GetSendReferrer());
+ } else {
+ config.referrerInfo = mReferrerInfo;
+ }
+ }
+ }
+
+ nsCOMPtr<nsITimedChannel> oldTimedChannel(
+ do_QueryInterface(static_cast<nsIHttpChannel*>(this)));
+ if (oldTimedChannel) {
+ config.timedChannelInfo = Some(dom::TimedChannelInfo());
+ config.timedChannelInfo->timingEnabled() = LoadTimingEnabled();
+ config.timedChannelInfo->redirectCount() = mRedirectCount;
+ config.timedChannelInfo->internalRedirectCount() = mInternalRedirectCount;
+ config.timedChannelInfo->asyncOpen() = mAsyncOpenTime;
+ config.timedChannelInfo->channelCreation() = mChannelCreationTimestamp;
+ config.timedChannelInfo->redirectStart() = mRedirectStartTimeStamp;
+ config.timedChannelInfo->redirectEnd() = mRedirectEndTimeStamp;
+ config.timedChannelInfo->initiatorType() = mInitiatorType;
+ config.timedChannelInfo->allRedirectsSameOrigin() =
+ LoadAllRedirectsSameOrigin();
+ config.timedChannelInfo->allRedirectsPassTimingAllowCheck() =
+ LoadAllRedirectsPassTimingAllowCheck();
+ // Execute the timing allow check to determine whether
+ // to report the redirect timing info
+ nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
+ // TYPE_DOCUMENT loads don't have a loadingPrincipal, so we can't set
+ // AllRedirectsPassTimingAllowCheck on them.
+ if (loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal();
+ config.timedChannelInfo->timingAllowCheckForPrincipal() =
+ Some(oldTimedChannel->TimingAllowCheck(principal));
+ }
+
+ config.timedChannelInfo->allRedirectsPassTimingAllowCheck() =
+ LoadAllRedirectsPassTimingAllowCheck();
+ config.timedChannelInfo->launchServiceWorkerStart() =
+ mLaunchServiceWorkerStart;
+ config.timedChannelInfo->launchServiceWorkerEnd() = mLaunchServiceWorkerEnd;
+ config.timedChannelInfo->dispatchFetchEventStart() =
+ mDispatchFetchEventStart;
+ config.timedChannelInfo->dispatchFetchEventEnd() = mDispatchFetchEventEnd;
+ config.timedChannelInfo->handleFetchEventStart() = mHandleFetchEventStart;
+ config.timedChannelInfo->handleFetchEventEnd() = mHandleFetchEventEnd;
+ config.timedChannelInfo->responseStart() =
+ mTransactionTimings.responseStart;
+ config.timedChannelInfo->responseEnd() = mTransactionTimings.responseEnd;
+ }
+
+ if (aPreserveMethod) {
+ // since preserveMethod is true, we need to ensure that the appropriate
+ // request method gets set on the channel, regardless of whether or not
+ // we set the upload stream above. This means SetRequestMethod() will
+ // be called twice if ExplicitSetUploadStream() gets called above.
+
+ nsAutoCString method;
+ mRequestHead.Method(method);
+ config.method = Some(method);
+
+ if (mUploadStream) {
+ // rewind upload stream
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
+ if (seekable) {
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ }
+ config.uploadStream = mUploadStream;
+ }
+ config.uploadStreamLength = mReqContentLength;
+ config.uploadStreamHasHeaders = LoadUploadStreamHasHeaders();
+
+ nsAutoCString contentType;
+ nsresult rv = mRequestHead.GetHeader(nsHttp::Content_Type, contentType);
+ if (NS_SUCCEEDED(rv)) {
+ config.contentType = Some(contentType);
+ }
+
+ nsAutoCString contentLength;
+ rv = mRequestHead.GetHeader(nsHttp::Content_Length, contentLength);
+ if (NS_SUCCEEDED(rv)) {
+ config.contentLength = Some(contentLength);
+ }
+ }
+
+ return config;
+}
+
+/* static */ void HttpBaseChannel::ConfigureReplacementChannel(
+ nsIChannel* newChannel, const ReplacementChannelConfig& config,
+ ReplacementReason aReason) {
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(newChannel));
+ if (cos) {
+ cos->SetClassOfService(config.classOfService);
+ }
+
+ // Try to preserve the privacy bit if it has been overridden
+ if (config.privateBrowsing) {
+ nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
+ do_QueryInterface(newChannel);
+ if (newPBChannel) {
+ newPBChannel->SetPrivate(*config.privateBrowsing);
+ }
+ }
+
+ // Transfer the timing data (if we are dealing with an nsITimedChannel).
+ nsCOMPtr<nsITimedChannel> newTimedChannel(do_QueryInterface(newChannel));
+ if (config.timedChannelInfo && newTimedChannel) {
+ newTimedChannel->SetTimingEnabled(config.timedChannelInfo->timingEnabled());
+
+ // If we're an internal redirect, or a document channel replacement,
+ // then we shouldn't record any new timing for this and just copy
+ // over the existing values.
+ bool shouldHideTiming = aReason != ReplacementReason::Redirect;
+ if (shouldHideTiming) {
+ newTimedChannel->SetRedirectCount(
+ config.timedChannelInfo->redirectCount());
+ int32_t newCount = config.timedChannelInfo->internalRedirectCount() + 1;
+ newTimedChannel->SetInternalRedirectCount(std::max(
+ newCount, static_cast<int32_t>(
+ config.timedChannelInfo->internalRedirectCount())));
+ } else {
+ int32_t newCount = config.timedChannelInfo->redirectCount() + 1;
+ newTimedChannel->SetRedirectCount(std::max(
+ newCount,
+ static_cast<int32_t>(config.timedChannelInfo->redirectCount())));
+ newTimedChannel->SetInternalRedirectCount(
+ config.timedChannelInfo->internalRedirectCount());
+ }
+
+ if (shouldHideTiming) {
+ if (!config.timedChannelInfo->channelCreation().IsNull()) {
+ newTimedChannel->SetChannelCreation(
+ config.timedChannelInfo->channelCreation());
+ }
+
+ if (!config.timedChannelInfo->asyncOpen().IsNull()) {
+ newTimedChannel->SetAsyncOpen(config.timedChannelInfo->asyncOpen());
+ }
+ }
+
+ // If the RedirectStart is null, we will use the AsyncOpen value of the
+ // previous channel (this is the first redirect in the redirects chain).
+ if (config.timedChannelInfo->redirectStart().IsNull()) {
+ // Only do this for real redirects. Internal redirects should be hidden.
+ if (!shouldHideTiming) {
+ newTimedChannel->SetRedirectStart(config.timedChannelInfo->asyncOpen());
+ }
+ } else {
+ newTimedChannel->SetRedirectStart(
+ config.timedChannelInfo->redirectStart());
+ }
+
+ // For internal redirects just propagate the last redirect end time
+ // forward. Otherwise the new redirect end time is the last response
+ // end time.
+ TimeStamp newRedirectEnd;
+ if (shouldHideTiming) {
+ newRedirectEnd = config.timedChannelInfo->redirectEnd();
+ } else if (!config.timedChannelInfo->responseEnd().IsNull()) {
+ newRedirectEnd = config.timedChannelInfo->responseEnd();
+ } else {
+ newRedirectEnd = TimeStamp::Now();
+ }
+ newTimedChannel->SetRedirectEnd(newRedirectEnd);
+
+ newTimedChannel->SetInitiatorType(config.timedChannelInfo->initiatorType());
+
+ nsCOMPtr<nsILoadInfo> loadInfo = newChannel->LoadInfo();
+ MOZ_ASSERT(loadInfo);
+
+ newTimedChannel->SetAllRedirectsSameOrigin(
+ config.timedChannelInfo->allRedirectsSameOrigin());
+
+ if (config.timedChannelInfo->timingAllowCheckForPrincipal()) {
+ newTimedChannel->SetAllRedirectsPassTimingAllowCheck(
+ config.timedChannelInfo->allRedirectsPassTimingAllowCheck() &&
+ *config.timedChannelInfo->timingAllowCheckForPrincipal());
+ }
+
+ // Propagate service worker measurements across redirects. The
+ // PeformanceResourceTiming.workerStart API expects to see the
+ // worker start time after a redirect.
+ newTimedChannel->SetLaunchServiceWorkerStart(
+ config.timedChannelInfo->launchServiceWorkerStart());
+ newTimedChannel->SetLaunchServiceWorkerEnd(
+ config.timedChannelInfo->launchServiceWorkerEnd());
+ newTimedChannel->SetDispatchFetchEventStart(
+ config.timedChannelInfo->dispatchFetchEventStart());
+ newTimedChannel->SetDispatchFetchEventEnd(
+ config.timedChannelInfo->dispatchFetchEventEnd());
+ newTimedChannel->SetHandleFetchEventStart(
+ config.timedChannelInfo->handleFetchEventStart());
+ newTimedChannel->SetHandleFetchEventEnd(
+ config.timedChannelInfo->handleFetchEventEnd());
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+ if (!httpChannel) {
+ return; // no other options to set
+ }
+
+ if (config.uploadStream) {
+ nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(httpChannel);
+ nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(httpChannel);
+ if (uploadChannel2 || uploadChannel) {
+ // replicate original call to SetUploadStream...
+ if (uploadChannel2) {
+ const nsACString& ctype =
+ config.contentType ? *config.contentType : VoidCString();
+ // If header is not present mRequestHead.HasHeaderValue will truncated
+ // it. But we want to end up with a void string, not an empty string,
+ // because ExplicitSetUploadStream treats the former as "no header" and
+ // the latter as "header with empty string value".
+
+ const nsACString& method =
+ config.method ? *config.method : VoidCString();
+
+ uploadChannel2->ExplicitSetUploadStream(
+ config.uploadStream, ctype, config.uploadStreamLength, method,
+ config.uploadStreamHasHeaders);
+ } else {
+ if (config.uploadStreamHasHeaders) {
+ uploadChannel->SetUploadStream(config.uploadStream, ""_ns,
+ config.uploadStreamLength);
+ } else {
+ nsAutoCString ctype;
+ if (config.contentType) {
+ ctype = *config.contentType;
+ } else {
+ ctype = "application/octet-stream"_ns;
+ }
+ if (config.contentLength && !config.contentLength->IsEmpty()) {
+ uploadChannel->SetUploadStream(
+ config.uploadStream, ctype,
+ nsCRT::atoll(config.contentLength->get()));
+ }
+ }
+ }
+ }
+ }
+
+ if (config.referrerInfo) {
+ DebugOnly<nsresult> success{};
+ success = httpChannel->SetReferrerInfo(config.referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(success));
+ }
+
+ if (config.method) {
+ DebugOnly<nsresult> rv = httpChannel->SetRequestMethod(*config.method);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+HttpBaseChannel::ReplacementChannelConfig::ReplacementChannelConfig(
+ const dom::ReplacementChannelConfigInit& aInit) {
+ redirectFlags = aInit.redirectFlags();
+ classOfService = aInit.classOfService();
+ privateBrowsing = aInit.privateBrowsing();
+ method = aInit.method();
+ referrerInfo = aInit.referrerInfo();
+ timedChannelInfo = aInit.timedChannelInfo();
+ uploadStream = aInit.uploadStream();
+ uploadStreamLength = aInit.uploadStreamLength();
+ uploadStreamHasHeaders = aInit.uploadStreamHasHeaders();
+ contentType = aInit.contentType();
+ contentLength = aInit.contentLength();
+}
+
+dom::ReplacementChannelConfigInit
+HttpBaseChannel::ReplacementChannelConfig::Serialize() {
+ dom::ReplacementChannelConfigInit config;
+ config.redirectFlags() = redirectFlags;
+ config.classOfService() = classOfService;
+ config.privateBrowsing() = privateBrowsing;
+ config.method() = method;
+ config.referrerInfo() = referrerInfo;
+ config.timedChannelInfo() = timedChannelInfo;
+ config.uploadStream() =
+ uploadStream ? RemoteLazyInputStream::WrapStream(uploadStream) : nullptr;
+ config.uploadStreamLength() = uploadStreamLength;
+ config.uploadStreamHasHeaders() = uploadStreamHasHeaders;
+ config.contentType() = contentType;
+ config.contentLength() = contentLength;
+
+ return config;
+}
+
+nsresult HttpBaseChannel::SetupReplacementChannel(nsIURI* newURI,
+ nsIChannel* newChannel,
+ bool preserveMethod,
+ uint32_t redirectFlags) {
+ nsresult rv;
+
+ LOG(
+ ("HttpBaseChannel::SetupReplacementChannel "
+ "[this=%p newChannel=%p preserveMethod=%d]",
+ this, newChannel, preserveMethod));
+
+ // Ensure the channel's loadInfo's result principal URI so that it's
+ // either non-null or updated to the redirect target URI.
+ // We must do this because in case the loadInfo's result principal URI
+ // is null, it would be taken from OriginalURI of the channel. But we
+ // overwrite it with the whole redirect chain first URI before opening
+ // the target channel, hence the information would be lost.
+ // If the protocol handler that created the channel wants to use
+ // the originalURI of the channel as the principal URI, this fulfills
+ // that request - newURI is the original URI of the channel.
+ nsCOMPtr<nsILoadInfo> newLoadInfo = newChannel->LoadInfo();
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ rv = newLoadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!resultPrincipalURI) {
+ rv = newLoadInfo->SetResultPrincipalURI(newURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsLoadFlags loadFlags = mLoadFlags;
+ loadFlags |= LOAD_REPLACE;
+
+ // if the original channel was using SSL and this channel is not using
+ // SSL, then no need to inhibit persistent caching. however, if the
+ // original channel was not using SSL and has INHIBIT_PERSISTENT_CACHING
+ // set, then allow the flag to apply to the redirected channel as well.
+ // since we force set INHIBIT_PERSISTENT_CACHING on all HTTPS channels,
+ // we only need to check if the original channel was using SSL.
+ if (mURI->SchemeIs("https")) {
+ loadFlags &= ~INHIBIT_PERSISTENT_CACHING;
+ }
+
+ newChannel->SetLoadFlags(loadFlags);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+
+ ReplacementReason redirectType =
+ (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)
+ ? ReplacementReason::InternalRedirect
+ : ReplacementReason::Redirect;
+ ReplacementChannelConfig config = CloneReplacementChannelConfig(
+ preserveMethod, redirectFlags, redirectType);
+ ConfigureReplacementChannel(newChannel, config, redirectType);
+
+ // Check whether or not this was a cross-domain redirect.
+ nsCOMPtr<nsITimedChannel> newTimedChannel(do_QueryInterface(newChannel));
+ bool sameOriginWithOriginalUri = SameOriginWithOriginalUri(newURI);
+ if (config.timedChannelInfo && newTimedChannel) {
+ newTimedChannel->SetAllRedirectsSameOrigin(
+ config.timedChannelInfo->allRedirectsSameOrigin() &&
+ sameOriginWithOriginalUri);
+ }
+
+ newChannel->SetLoadGroup(mLoadGroup);
+ newChannel->SetNotificationCallbacks(mCallbacks);
+ // TODO: create tests for cross-origin redirect in bug 1662896.
+ if (sameOriginWithOriginalUri) {
+ newChannel->SetContentDisposition(mContentDispositionHint);
+ if (mContentDispositionFilename) {
+ newChannel->SetContentDispositionFilename(*mContentDispositionFilename);
+ }
+ }
+
+ if (!httpChannel) return NS_OK; // no other options to set
+
+ // Preserve the CORS preflight information.
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel);
+ if (httpInternal) {
+ httpInternal->SetLastRedirectFlags(redirectFlags);
+
+ if (LoadRequireCORSPreflight()) {
+ httpInternal->SetCorsPreflightParameters(mUnsafeHeaders, false);
+ }
+ }
+
+ // convey the LoadAllowSTS() flags
+ rv = httpChannel->SetAllowSTS(LoadAllowSTS());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // convey the Accept header value
+ {
+ nsAutoCString oldAcceptValue;
+ nsresult hasHeader = mRequestHead.GetHeader(nsHttp::Accept, oldAcceptValue);
+ if (NS_SUCCEEDED(hasHeader)) {
+ rv = httpChannel->SetRequestHeader("Accept"_ns, oldAcceptValue, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // convey the User-Agent header value
+ // since we might be setting custom user agent from DevTools.
+ if (httpInternal && mRequestMode == RequestMode::No_cors &&
+ redirectType == ReplacementReason::Redirect) {
+ nsAutoCString oldUserAgent;
+ nsresult hasHeader =
+ mRequestHead.GetHeader(nsHttp::User_Agent, oldUserAgent);
+ if (NS_SUCCEEDED(hasHeader)) {
+ rv = httpChannel->SetRequestHeader("User-Agent"_ns, oldUserAgent, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // convery the IsUserAgentHeaderModified value.
+ if (httpInternal) {
+ rv = httpInternal->SetIsUserAgentHeaderModified(
+ LoadIsUserAgentHeaderModified());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // share the request context - see bug 1236650
+ rv = httpChannel->SetRequestContextID(mRequestContextID);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // When on the parent process, the channel can't attempt to get it itself.
+ // When on the child process, it would be waste to query it again.
+ rv = httpChannel->SetBrowserId(mBrowserId);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Not setting this flag would break carrying permissions down to the child
+ // process when the channel is artificially forced to be a main document load.
+ rv = httpChannel->SetIsMainDocumentChannel(LoadForceMainDocumentChannel());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Preserve the loading order
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(newChannel);
+ if (p) {
+ p->SetPriority(mPriority);
+ }
+
+ if (httpInternal) {
+ // Convey third party cookie, conservative, and spdy flags.
+ rv = httpInternal->SetThirdPartyFlags(LoadThirdPartyFlags());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetAllowSpdy(LoadAllowSpdy());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetAllowHttp3(LoadAllowHttp3());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetAllowAltSvc(LoadAllowAltSvc());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetBeConservative(LoadBeConservative());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetIsTRRServiceChannel(LoadIsTRRServiceChannel());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetTlsFlags(mTlsFlags);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Ensure the type of realChannel involves all types it may redirect to.
+ // Such as nsHttpChannel and InterceptedChannel.
+ // Even thought InterceptedChannel itself doesn't require these information,
+ // it may still be necessary for the following redirections.
+ // E.g. nsHttpChannel -> InterceptedChannel -> nsHttpChannel
+ RefPtr<HttpBaseChannel> realChannel;
+ CallQueryInterface(newChannel, realChannel.StartAssignment());
+ if (realChannel) {
+ realChannel->SetTopWindowURI(mTopWindowURI);
+
+ realChannel->StoreTaintedOriginFlag(
+ ShouldTaintReplacementChannelOrigin(newChannel, redirectFlags));
+ }
+
+ // update the DocumentURI indicator since we are being redirected.
+ // if this was a top-level document channel, then the new channel
+ // should have its mDocumentURI point to newURI; otherwise, we
+ // just need to pass along our mDocumentURI to the new channel.
+ if (newURI && (mURI == mDocumentURI)) {
+ rv = httpInternal->SetDocumentURI(newURI);
+ } else {
+ rv = httpInternal->SetDocumentURI(mDocumentURI);
+ }
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // if there is a chain of keys for redirect-responses we transfer it to
+ // the new channel (see bug #561276)
+ {
+ auto redirectedCachekeys = mRedirectedCachekeys.Lock();
+ auto& ref = redirectedCachekeys.ref();
+ if (ref) {
+ LOG(
+ ("HttpBaseChannel::SetupReplacementChannel "
+ "[this=%p] transferring chain of redirect cache-keys",
+ this));
+ rv = httpInternal->SetCacheKeysRedirectChain(ref.release());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // Preserve Request mode.
+ rv = httpInternal->SetRequestMode(mRequestMode);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Preserve Redirect mode flag.
+ rv = httpInternal->SetRedirectMode(mRedirectMode);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Preserve Integrity metadata.
+ rv = httpInternal->SetIntegrityMetadata(mIntegrityMetadata);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ httpInternal->SetAltDataForChild(LoadAltDataForChild());
+ if (LoadDisableAltDataCache()) {
+ httpInternal->DisableAltDataCache();
+ }
+ }
+
+ // transfer any properties
+ nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
+ if (bag) {
+ for (const auto& entry : mPropertyHash) {
+ bag->SetProperty(entry.GetKey(), entry.GetWeak());
+ }
+ }
+
+ // Pass the preferred alt-data type on to the new channel.
+ nsCOMPtr<nsICacheInfoChannel> cacheInfoChan(do_QueryInterface(newChannel));
+ if (cacheInfoChan) {
+ for (auto& data : mPreferredCachedAltDataTypes) {
+ cacheInfoChan->PreferAlternativeDataType(data.type(), data.contentType(),
+ data.deliverAltData());
+ }
+
+ if (LoadForceValidateCacheContent()) {
+ Unused << cacheInfoChan->SetForceValidateCacheContent(true);
+ }
+ }
+
+ if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
+ // Copy non-origin related headers to the new channel.
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new AddHeadersToChannelVisitor(httpChannel);
+ rv = mRequestHead.VisitHeaders(visitor);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // we need to strip Authentication headers for cross-origin requests
+ // Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch
+ nsAutoCString authHeader;
+ if (StaticPrefs::network_http_redirect_stripAuthHeader() &&
+ NS_SUCCEEDED(
+ httpChannel->GetRequestHeader("Authorization"_ns, authHeader))) {
+ if (NS_ShouldRemoveAuthHeaderOnRedirect(static_cast<nsIChannel*>(this),
+ newChannel, redirectFlags)) {
+ rv = httpChannel->SetRequestHeader("Authorization"_ns, ""_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ return NS_OK;
+}
+
+// check whether the new channel is of same origin as the current channel
+bool HttpBaseChannel::IsNewChannelSameOrigin(nsIChannel* aNewChannel) {
+ bool isSameOrigin = false;
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+
+ if (!ssm) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> newURI;
+ NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
+
+ nsresult rv = ssm->CheckSameOriginURI(newURI, mURI, false, false);
+ if (NS_SUCCEEDED(rv)) {
+ isSameOrigin = true;
+ }
+
+ return isSameOrigin;
+}
+
+bool HttpBaseChannel::ShouldTaintReplacementChannelOrigin(
+ nsIChannel* aNewChannel, uint32_t aRedirectFlags) {
+ if (LoadTaintedOriginFlag()) {
+ return true;
+ }
+
+ if (NS_IsInternalSameURIRedirect(this, aNewChannel, aRedirectFlags) ||
+ NS_IsHSTSUpgradeRedirect(this, aNewChannel, aRedirectFlags)) {
+ return false;
+ }
+
+ // If new channel is not of same origin we need to taint unless
+ // mURI <-> mOriginalURI/LoadingPrincipal are same origin.
+ if (IsNewChannelSameOrigin(aNewChannel)) {
+ return false;
+ }
+
+ nsresult rv;
+
+ if (mLoadInfo->GetLoadingPrincipal()) {
+ bool sameOrigin = false;
+ rv = mLoadInfo->GetLoadingPrincipal()->IsSameOrigin(mURI, &sameOrigin);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ return !sameOrigin;
+ }
+ if (!mOriginalURI) {
+ return true;
+ }
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ if (!ssm) {
+ return true;
+ }
+
+ rv = ssm->CheckSameOriginURI(mOriginalURI, mURI, false, false);
+ return NS_FAILED(rv);
+}
+
+// Redirect Tracking
+bool HttpBaseChannel::SameOriginWithOriginalUri(nsIURI* aURI) {
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ bool isPrivateWin = mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
+ nsresult rv =
+ ssm->CheckSameOriginURI(aURI, mOriginalURI, false, isPrivateWin);
+ return (NS_SUCCEEDED(rv));
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIClassifiedChannel
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedList(nsACString& aList) {
+ aList = mMatchedList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedProvider(nsACString& aProvider) {
+ aProvider = mMatchedProvider;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedFullHash(nsACString& aFullHash) {
+ aFullHash = mMatchedFullHash;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ NS_ENSURE_ARG(!aList.IsEmpty());
+
+ mMatchedList = aList;
+ mMatchedProvider = aProvider;
+ mMatchedFullHash = aFullHash;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedTrackingLists(nsTArray<nsCString>& aLists) {
+ aLists = mMatchedTrackingLists.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedTrackingFullHashes(
+ nsTArray<nsCString>& aFullHashes) {
+ aFullHashes = mMatchedTrackingFullHashes.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetMatchedTrackingInfo(
+ const nsTArray<nsCString>& aLists, const nsTArray<nsCString>& aFullHashes) {
+ NS_ENSURE_ARG(!aLists.IsEmpty());
+ // aFullHashes can be empty for non hash-matching algorithm, for example,
+ // host based test entries in preference.
+
+ mMatchedTrackingLists = aLists.Clone();
+ mMatchedTrackingFullHashes = aFullHashes.Clone();
+ return NS_OK;
+}
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsITimedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTimingEnabled(bool enabled) {
+ StoreTimingEnabled(enabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTimingEnabled(bool* _retval) {
+ *_retval = LoadTimingEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetChannelCreation(TimeStamp* _retval) {
+ *_retval = mChannelCreationTimestamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetChannelCreation(TimeStamp aValue) {
+ MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
+ TimeDuration adjust = aValue - mChannelCreationTimestamp;
+ mChannelCreationTimestamp = aValue;
+ mChannelCreationTime += (PRTime)adjust.ToMicroseconds();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAsyncOpen(TimeStamp* _retval) {
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAsyncOpen(TimeStamp aValue) {
+ MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
+ mAsyncOpenTime = aValue;
+ StoreAsyncOpenTimeOverriden(true);
+ return NS_OK;
+}
+
+/**
+ * @return the number of redirects. There is no check for cross-domain
+ * redirects. This check must be done by the consumers.
+ */
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectCount(uint8_t* aRedirectCount) {
+ *aRedirectCount = mRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectCount(uint8_t aRedirectCount) {
+ mRedirectCount = aRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetInternalRedirectCount(uint8_t* aRedirectCount) {
+ *aRedirectCount = mInternalRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetInternalRedirectCount(uint8_t aRedirectCount) {
+ mInternalRedirectCount = aRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectStart(TimeStamp* _retval) {
+ *_retval = mRedirectStartTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectStart(TimeStamp aRedirectStart) {
+ mRedirectStartTimeStamp = aRedirectStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectEnd(TimeStamp* _retval) {
+ *_retval = mRedirectEndTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectEnd(TimeStamp aRedirectEnd) {
+ mRedirectEndTimeStamp = aRedirectEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllRedirectsSameOrigin(bool* aAllRedirectsSameOrigin) {
+ *aAllRedirectsSameOrigin = LoadAllRedirectsSameOrigin();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllRedirectsSameOrigin(bool aAllRedirectsSameOrigin) {
+ StoreAllRedirectsSameOrigin(aAllRedirectsSameOrigin);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllRedirectsPassTimingAllowCheck(bool* aPassesCheck) {
+ *aPassesCheck = LoadAllRedirectsPassTimingAllowCheck();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllRedirectsPassTimingAllowCheck(bool aPassesCheck) {
+ StoreAllRedirectsPassTimingAllowCheck(aPassesCheck);
+ return NS_OK;
+}
+
+// https://fetch.spec.whatwg.org/#tao-check
+NS_IMETHODIMP
+HttpBaseChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* _retval) {
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ nsCOMPtr<nsIPrincipal> resourcePrincipal;
+ nsresult rv =
+ ssm->GetChannelURIPrincipal(this, getter_AddRefs(resourcePrincipal));
+ if (NS_FAILED(rv) || !resourcePrincipal || !aOrigin) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ bool sameOrigin = false;
+ rv = resourcePrincipal->Equals(aOrigin, &sameOrigin);
+
+ nsAutoCString serializedOrigin;
+ nsContentSecurityManager::GetSerializedOrigin(aOrigin, resourcePrincipal,
+ serializedOrigin, mLoadInfo);
+
+ // All redirects are same origin
+ if (sameOrigin && (!serializedOrigin.IsEmpty() &&
+ !serializedOrigin.EqualsLiteral("null"))) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ nsAutoCString headerValue;
+ rv = GetResponseHeader("Timing-Allow-Origin"_ns, headerValue);
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ Tokenizer p(headerValue);
+ Tokenizer::Token t;
+
+ p.Record();
+ nsAutoCString headerItem;
+ while (p.Next(t)) {
+ if (t.Type() == Tokenizer::TOKEN_EOF ||
+ t.Equals(Tokenizer::Token::Char(','))) {
+ p.Claim(headerItem);
+ nsHttp::TrimHTTPWhitespace(headerItem, headerItem);
+ // If the list item contains a case-sensitive match for the value of the
+ // origin, or a wildcard, return pass
+ if (headerItem == serializedOrigin || headerItem == "*") {
+ *_retval = true;
+ return NS_OK;
+ }
+ // We start recording again for the following items in the list
+ p.Record();
+ }
+ }
+
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLaunchServiceWorkerStart(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mLaunchServiceWorkerStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) {
+ mLaunchServiceWorkerStart = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLaunchServiceWorkerEnd(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mLaunchServiceWorkerEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) {
+ mLaunchServiceWorkerEnd = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDispatchFetchEventStart(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mDispatchFetchEventStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDispatchFetchEventStart(TimeStamp aTimeStamp) {
+ mDispatchFetchEventStart = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDispatchFetchEventEnd(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mDispatchFetchEventEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDispatchFetchEventEnd(TimeStamp aTimeStamp) {
+ mDispatchFetchEventEnd = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetHandleFetchEventStart(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mHandleFetchEventStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetHandleFetchEventStart(TimeStamp aTimeStamp) {
+ mHandleFetchEventStart = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetHandleFetchEventEnd(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mHandleFetchEventEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetHandleFetchEventEnd(TimeStamp aTimeStamp) {
+ mHandleFetchEventEnd = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDomainLookupStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.domainLookupStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDomainLookupEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.domainLookupEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetConnectStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.connectStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTcpConnectEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.tcpConnectEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetSecureConnectionStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.secureConnectionStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetConnectEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.connectEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.requestStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.responseStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.responseEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCacheReadStart(TimeStamp* _retval) {
+ *_retval = mCacheReadStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCacheReadEnd(TimeStamp* _retval) {
+ *_retval = mCacheReadEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTransactionPending(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.transactionPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetInitiatorType(nsAString& aInitiatorType) {
+ aInitiatorType = mInitiatorType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetInitiatorType(const nsAString& aInitiatorType) {
+ mInitiatorType = aInitiatorType;
+ return NS_OK;
+}
+
+#define IMPL_TIMING_ATTR(name) \
+ NS_IMETHODIMP \
+ HttpBaseChannel::Get##name##Time(PRTime* _retval) { \
+ TimeStamp stamp; \
+ Get##name(&stamp); \
+ if (stamp.IsNull()) { \
+ *_retval = 0; \
+ return NS_OK; \
+ } \
+ *_retval = \
+ mChannelCreationTime + \
+ (PRTime)((stamp - mChannelCreationTimestamp).ToSeconds() * 1e6); \
+ return NS_OK; \
+ }
+
+IMPL_TIMING_ATTR(ChannelCreation)
+IMPL_TIMING_ATTR(AsyncOpen)
+IMPL_TIMING_ATTR(LaunchServiceWorkerStart)
+IMPL_TIMING_ATTR(LaunchServiceWorkerEnd)
+IMPL_TIMING_ATTR(DispatchFetchEventStart)
+IMPL_TIMING_ATTR(DispatchFetchEventEnd)
+IMPL_TIMING_ATTR(HandleFetchEventStart)
+IMPL_TIMING_ATTR(HandleFetchEventEnd)
+IMPL_TIMING_ATTR(DomainLookupStart)
+IMPL_TIMING_ATTR(DomainLookupEnd)
+IMPL_TIMING_ATTR(ConnectStart)
+IMPL_TIMING_ATTR(TcpConnectEnd)
+IMPL_TIMING_ATTR(SecureConnectionStart)
+IMPL_TIMING_ATTR(ConnectEnd)
+IMPL_TIMING_ATTR(RequestStart)
+IMPL_TIMING_ATTR(ResponseStart)
+IMPL_TIMING_ATTR(ResponseEnd)
+IMPL_TIMING_ATTR(CacheReadStart)
+IMPL_TIMING_ATTR(CacheReadEnd)
+IMPL_TIMING_ATTR(RedirectStart)
+IMPL_TIMING_ATTR(RedirectEnd)
+IMPL_TIMING_ATTR(TransactionPending)
+
+#undef IMPL_TIMING_ATTR
+
+void HttpBaseChannel::MaybeReportTimingData() {
+ // If performance timing is disabled, there is no need for the Performance
+ // object anymore.
+ if (!LoadTimingEnabled()) {
+ return;
+ }
+
+ // There is no point in continuing, since the performance object in the parent
+ // isn't the same as the one in the child which will be reporting resource
+ // performance.
+ if (XRE_IsE10sParentProcess()) {
+ return;
+ }
+
+ // Devtools can create fetch requests on behalf the content document.
+ // If we don't exclude these requests, they'd also be reported
+ // to the content document.
+ bool isInDevToolsContext;
+ mLoadInfo->GetIsInDevToolsContext(&isInDevToolsContext);
+ if (isInDevToolsContext) {
+ return;
+ }
+
+ mozilla::dom::PerformanceStorage* documentPerformance =
+ mLoadInfo->GetPerformanceStorage();
+ if (documentPerformance) {
+ documentPerformance->AddEntry(this, this);
+ return;
+ }
+
+ if (!nsGlobalWindowInner::GetInnerWindowWithId(
+ mLoadInfo->GetInnerWindowID())) {
+ // The inner window is in a different process.
+ dom::ContentChild* child = dom::ContentChild::GetSingleton();
+
+ if (!child) {
+ return;
+ }
+ nsAutoString initiatorType;
+ nsAutoString entryName;
+
+ UniquePtr<dom::PerformanceTimingData> performanceTimingData(
+ dom::PerformanceTimingData::Create(this, this, 0, initiatorType,
+ entryName));
+ if (!performanceTimingData) {
+ return;
+ }
+
+ LoadInfoArgs loadInfoArgs;
+ mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfoArgs);
+ child->SendReportFrameTimingData(loadInfoArgs, entryName, initiatorType,
+ std::move(performanceTimingData));
+ }
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetReportResourceTiming(bool enabled) {
+ StoreReportTiming(enabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetReportResourceTiming(bool* _retval) {
+ *_retval = LoadReportTiming();
+ return NS_OK;
+}
+
+nsIURI* HttpBaseChannel::GetReferringPage() {
+ nsCOMPtr<nsPIDOMWindowInner> pDomWindow = GetInnerDOMWindow();
+ if (!pDomWindow) {
+ return nullptr;
+ }
+ return pDomWindow->GetDocumentURI();
+}
+
+nsPIDOMWindowInner* HttpBaseChannel::GetInnerDOMWindow() {
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ if (!loadContext) {
+ return nullptr;
+ }
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
+ if (!domWindow) {
+ return nullptr;
+ }
+ auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow);
+ if (!pDomWindow) {
+ return nullptr;
+ }
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow =
+ pDomWindow->GetCurrentInnerWindow();
+ if (!innerWindow) {
+ return nullptr;
+ }
+
+ return innerWindow;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIThrottledInputChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetThrottleQueue(nsIInputChannelThrottleQueue* aQueue) {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mThrottleQueue = aQueue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetThrottleQueue(nsIInputChannelThrottleQueue** aQueue) {
+ NS_ENSURE_ARG_POINTER(aQueue);
+ nsCOMPtr<nsIInputChannelThrottleQueue> queue = mThrottleQueue;
+ queue.forget(aQueue);
+ return NS_OK;
+}
+
+//------------------------------------------------------------------------------
+
+bool HttpBaseChannel::EnsureRequestContextID() {
+ if (mRequestContextID) {
+ // Already have a request context ID, no need to do the rest of this work
+ LOG(("HttpBaseChannel::EnsureRequestContextID this=%p id=%" PRIx64, this,
+ mRequestContextID));
+ return true;
+ }
+
+ // Find the loadgroup at the end of the chain in order
+ // to make sure all channels derived from the load group
+ // use the same connection scope.
+ nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(mLoadGroup);
+ if (!childLoadGroup) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadGroup> rootLoadGroup;
+ childLoadGroup->GetRootLoadGroup(getter_AddRefs(rootLoadGroup));
+ if (!rootLoadGroup) {
+ return false;
+ }
+
+ // Set the load group connection scope on this channel and its transaction
+ rootLoadGroup->GetRequestContextID(&mRequestContextID);
+
+ LOG(("HttpBaseChannel::EnsureRequestContextID this=%p id=%" PRIx64, this,
+ mRequestContextID));
+
+ return true;
+}
+
+bool HttpBaseChannel::EnsureRequestContext() {
+ if (mRequestContext) {
+ // Already have a request context, no need to do the rest of this work
+ return true;
+ }
+
+ if (!EnsureRequestContextID()) {
+ return false;
+ }
+
+ nsIRequestContextService* rcsvc = gHttpHandler->GetRequestContextService();
+ if (!rcsvc) {
+ return false;
+ }
+
+ rcsvc->GetRequestContext(mRequestContextID, getter_AddRefs(mRequestContext));
+ return static_cast<bool>(mRequestContext);
+}
+
+void HttpBaseChannel::EnsureBrowserId() {
+ if (mBrowserId) {
+ return;
+ }
+
+ RefPtr<dom::BrowsingContext> bc;
+ MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetBrowsingContext(getter_AddRefs(bc)));
+
+ if (bc) {
+ mBrowserId = bc->GetBrowserId();
+ }
+}
+
+void HttpBaseChannel::SetCorsPreflightParameters(
+ const nsTArray<nsCString>& aUnsafeHeaders,
+ bool aShouldStripRequestBodyHeader) {
+ MOZ_RELEASE_ASSERT(!LoadRequestObserversCalled());
+
+ StoreRequireCORSPreflight(true);
+ mUnsafeHeaders = aUnsafeHeaders.Clone();
+ if (aShouldStripRequestBodyHeader) {
+ mUnsafeHeaders.RemoveElementsBy([&](const nsCString& aHeader) {
+ return aHeader.LowerCaseEqualsASCII("content-type") ||
+ aHeader.LowerCaseEqualsASCII("content-encoding") ||
+ aHeader.LowerCaseEqualsASCII("content-language") ||
+ aHeader.LowerCaseEqualsASCII("content-location");
+ });
+ }
+}
+
+void HttpBaseChannel::SetAltDataForChild(bool aIsForChild) {
+ StoreAltDataForChild(aIsForChild);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetBlockAuthPrompt(bool* aValue) {
+ if (!aValue) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aValue = LoadBlockAuthPrompt();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetBlockAuthPrompt(bool aValue) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ StoreBlockAuthPrompt(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetConnectionInfoHashKey(nsACString& aConnectionInfoHashKey) {
+ if (!mConnectionInfo) {
+ return NS_ERROR_FAILURE;
+ }
+ aConnectionInfoHashKey.Assign(mConnectionInfo->HashKey());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLastRedirectFlags(uint32_t* aValue) {
+ NS_ENSURE_ARG(aValue);
+ *aValue = mLastRedirectFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLastRedirectFlags(uint32_t aValue) {
+ mLastRedirectFlags = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNavigationStartTimeStamp(TimeStamp aTimeStamp) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult HttpBaseChannel::CheckRedirectLimit(uint32_t aRedirectFlags) const {
+ if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
+ // for internal redirect due to auth retry we do not have any limit
+ // as we might restrict the number of times a user might retry
+ // authentication
+ if (aRedirectFlags & nsIChannelEventSink::REDIRECT_AUTH_RETRY) {
+ return NS_OK;
+ }
+ // Some platform features, like Service Workers, depend on internal
+ // redirects. We should allow some number of internal redirects above
+ // and beyond the normal redirect limit so these features continue
+ // to work.
+ static const int8_t kMinInternalRedirects = 5;
+
+ if (mInternalRedirectCount >= (mRedirectionLimit + kMinInternalRedirects)) {
+ LOG(("internal redirection limit reached!\n"));
+ return NS_ERROR_REDIRECT_LOOP;
+ }
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aRedirectFlags & (nsIChannelEventSink::REDIRECT_TEMPORARY |
+ nsIChannelEventSink::REDIRECT_PERMANENT |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+
+ if (mRedirectCount >= mRedirectionLimit) {
+ LOG(("redirection limit reached!\n"));
+ return NS_ERROR_REDIRECT_LOOP;
+ }
+
+ // in case https-only mode is enabled which upgrades top-level requests to
+ // https and the page answers with a redirect (meta, 302, win.location, ...)
+ // then this method can break the cycle which causes the https-only exception
+ // page to appear. Note that https-first mode breaks upgrade downgrade endless
+ // loops within ShouldUpgradeHTTPSFirstRequest because https-first does not
+ // display an exception page but needs a soft fallback/downgrade.
+ if (nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
+ mURI, mLoadInfo,
+ {nsHTTPSOnlyUtils::UpgradeDowngradeEndlessLoopOptions::
+ EnforceForHTTPSOnlyMode})) {
+ LOG(("upgrade downgrade redirect loop!\n"));
+ return NS_ERROR_REDIRECT_LOOP;
+ }
+
+ return NS_OK;
+}
+
+// NOTE: This function duplicates code from nsBaseChannel. This will go away
+// once HTTP uses nsBaseChannel (part of bug 312760)
+/* static */
+void HttpBaseChannel::CallTypeSniffers(void* aClosure, const uint8_t* aData,
+ uint32_t aCount) {
+ nsIChannel* chan = static_cast<nsIChannel*>(aClosure);
+ const char* snifferType = [chan]() {
+ if (RefPtr<nsHttpChannel> httpChannel = do_QueryObject(chan)) {
+ switch (httpChannel->GetSnifferCategoryType()) {
+ case SnifferCategoryType::NetContent:
+ return NS_CONTENT_SNIFFER_CATEGORY;
+ case SnifferCategoryType::OpaqueResponseBlocking:
+ return NS_ORB_SNIFFER_CATEGORY;
+ case SnifferCategoryType::All:
+ return NS_CONTENT_AND_ORB_SNIFFER_CATEGORY;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected SnifferCategoryType!");
+ }
+ }
+
+ return NS_CONTENT_SNIFFER_CATEGORY;
+ }();
+
+ nsAutoCString newType;
+ NS_SniffContent(snifferType, chan, aData, aCount, newType);
+ if (!newType.IsEmpty()) {
+ chan->SetContentType(newType);
+ }
+}
+
+template <class T>
+static void ParseServerTimingHeader(
+ const UniquePtr<T>& aHeader, nsTArray<nsCOMPtr<nsIServerTiming>>& aOutput) {
+ if (!aHeader) {
+ return;
+ }
+
+ nsAutoCString serverTimingHeader;
+ Unused << aHeader->GetHeader(nsHttp::Server_Timing, serverTimingHeader);
+ if (serverTimingHeader.IsEmpty()) {
+ return;
+ }
+
+ ServerTimingParser parser(serverTimingHeader);
+ parser.Parse();
+
+ nsTArray<nsCOMPtr<nsIServerTiming>> array = parser.TakeServerTimingHeaders();
+ aOutput.AppendElements(array);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetServerTiming(nsIArray** aServerTiming) {
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aServerTiming);
+
+ nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsCOMPtr<nsIServerTiming>> data;
+ rv = GetNativeServerTiming(data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (const auto& entry : data) {
+ array->AppendElement(entry);
+ }
+
+ array.forget(aServerTiming);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetNativeServerTiming(
+ nsTArray<nsCOMPtr<nsIServerTiming>>& aServerTiming) {
+ aServerTiming.Clear();
+
+ if (nsContentUtils::ComputeIsSecureContext(this)) {
+ ParseServerTimingHeader(mResponseHead, aServerTiming);
+ ParseServerTimingHeader(mResponseTrailers, aServerTiming);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::CancelByURLClassifier(nsresult aErrorCode) {
+ MOZ_ASSERT(
+ UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
+ return Cancel(aErrorCode);
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetIPv4Disabled() {
+ mCaps |= NS_HTTP_DISABLE_IPV4;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetIPv6Disabled() {
+ mCaps |= NS_HTTP_DISABLE_IPV6;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetResponseEmbedderPolicy(
+ bool aIsOriginTrialCoepCredentiallessEnabled,
+ nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) {
+ *aOutPolicy = nsILoadInfo::EMBEDDER_POLICY_NULL;
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!nsContentUtils::ComputeIsSecureContext(this)) {
+ // Feature is only available for secure contexts.
+ return NS_OK;
+ }
+
+ nsAutoCString content;
+ Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Embedder_Policy,
+ content);
+ *aOutPolicy = NS_GetCrossOriginEmbedderPolicyFromHeader(
+ content, aIsOriginTrialCoepCredentiallessEnabled);
+ return NS_OK;
+}
+
+// Obtain a cross-origin opener-policy from a response response and a
+// cross-origin opener policy initiator.
+// https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
+NS_IMETHODIMP HttpBaseChannel::ComputeCrossOriginOpenerPolicy(
+ nsILoadInfo::CrossOriginOpenerPolicy aInitiatorPolicy,
+ nsILoadInfo::CrossOriginOpenerPolicy* aOutPolicy) {
+ MOZ_ASSERT(aOutPolicy);
+ *aOutPolicy = nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // COOP headers are ignored for insecure-context loads.
+ if (!nsContentUtils::ComputeIsSecureContext(this)) {
+ return NS_OK;
+ }
+
+ nsAutoCString openerPolicy;
+ Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Opener_Policy,
+ openerPolicy);
+
+ // Cross-Origin-Opener-Policy = %s"same-origin" /
+ // %s"same-origin-allow-popups" /
+ // %s"unsafe-none"; case-sensitive
+
+ nsCOMPtr<nsISFVService> sfv = GetSFVService();
+
+ nsCOMPtr<nsISFVItem> item;
+ nsresult rv = sfv->ParseItem(openerPolicy, getter_AddRefs(item));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISFVBareItem> value;
+ rv = item->GetValue(getter_AddRefs(value));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISFVToken> token = do_QueryInterface(value);
+ if (!token) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = token->GetValue(openerPolicy);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsILoadInfo::CrossOriginOpenerPolicy policy =
+ nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+
+ if (openerPolicy.EqualsLiteral("same-origin")) {
+ policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN;
+ } else if (openerPolicy.EqualsLiteral("same-origin-allow-popups")) {
+ policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS;
+ }
+ if (policy == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN) {
+ nsILoadInfo::CrossOriginEmbedderPolicy coep =
+ nsILoadInfo::EMBEDDER_POLICY_NULL;
+ bool isCoepCredentiallessEnabled;
+ rv = mLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ &isCoepCredentiallessEnabled);
+ if (!isCoepCredentiallessEnabled) {
+ nsAutoCString originTrialToken;
+ Unused << mResponseHead->GetHeader(nsHttp::OriginTrial, originTrialToken);
+ if (!originTrialToken.IsEmpty()) {
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(resultPrincipal));
+ if (!NS_WARN_IF(NS_FAILED(rv))) {
+ OriginTrials trials;
+ trials.UpdateFromToken(NS_ConvertASCIItoUTF16(originTrialToken),
+ resultPrincipal);
+ if (trials.IsEnabled(OriginTrial::CoepCredentialless)) {
+ isCoepCredentiallessEnabled = true;
+ }
+ }
+ }
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (NS_SUCCEEDED(
+ GetResponseEmbedderPolicy(isCoepCredentiallessEnabled, &coep)) &&
+ (coep == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP ||
+ coep == nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS)) {
+ policy =
+ nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
+ }
+ }
+
+ *aOutPolicy = policy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCrossOriginOpenerPolicy(
+ nsILoadInfo::CrossOriginOpenerPolicy* aPolicy) {
+ MOZ_ASSERT(aPolicy);
+ if (!aPolicy) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ // If this method is called before OnStartRequest (ie. before we call
+ // ComputeCrossOriginOpenerPolicy) or if we were unable to compute the
+ // policy we'll throw an error.
+ if (!LoadOnStartRequestCalled()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aPolicy = mComputedCrossOriginOpenerPolicy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::HasCrossOriginOpenerPolicyMismatch(bool* aIsMismatch) {
+ // This should only be called in parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+ *aIsMismatch = LoadHasCrossOriginOpenerPolicyMismatch();
+ return NS_OK;
+}
+
+void HttpBaseChannel::MaybeFlushConsoleReports() {
+ // Flush if we have a known window ID.
+ if (mLoadInfo->GetInnerWindowID() > 0) {
+ FlushReportsToConsole(mLoadInfo->GetInnerWindowID());
+ return;
+ }
+
+ // If this channel is part of a loadGroup, we can flush the console reports
+ // immediately.
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsresult rv = GetLoadGroup(getter_AddRefs(loadGroup));
+ if (NS_SUCCEEDED(rv) && loadGroup) {
+ FlushConsoleReports(loadGroup);
+ }
+}
+
+void HttpBaseChannel::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {}
+
+NS_IMETHODIMP HttpBaseChannel::SetWaitForHTTPSSVCRecord() {
+ mCaps |= NS_HTTP_FORCE_WAIT_HTTP_RR;
+ return NS_OK;
+}
+
+bool HttpBaseChannel::Http3Allowed() const {
+ bool isDirectOrNoProxy =
+ mProxyInfo ? static_cast<nsProxyInfo*>(mProxyInfo.get())->IsDirect()
+ : true;
+ return !mUpgradeProtocolCallback && isDirectOrNoProxy &&
+ !(mCaps & NS_HTTP_BE_CONSERVATIVE) && !LoadBeConservative() &&
+ LoadAllowHttp3();
+}
+
+void HttpBaseChannel::SetDummyChannelForImageCache() {
+ mDummyChannelForImageCache = true;
+ MOZ_ASSERT(!mResponseHead,
+ "SetDummyChannelForImageCache should only be called once");
+ mResponseHead = MakeUnique<nsHttpResponseHead>();
+}
+
+void HttpBaseChannel::SetEarlyHints(
+ nsTArray<EarlyHintConnectArgs>&& aEarlyHints) {
+ mEarlyHints = std::move(aEarlyHints);
+}
+
+nsTArray<EarlyHintConnectArgs>&& HttpBaseChannel::TakeEarlyHints() {
+ return std::move(mEarlyHints);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetEarlyHintPreloaderId(uint64_t aEarlyHintPreloaderId) {
+ mEarlyHintPreloaderId = aEarlyHintPreloaderId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEarlyHintPreloaderId(uint64_t* aEarlyHintPreloaderId) {
+ NS_ENSURE_ARG_POINTER(aEarlyHintPreloaderId);
+ *aEarlyHintPreloaderId = mEarlyHintPreloaderId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetClassicScriptHintCharset(
+ const nsAString& aClassicScriptHintCharset) {
+ mClassicScriptHintCharset = aClassicScriptHintCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetClassicScriptHintCharset(
+ nsAString& aClassicScriptHintCharset) {
+ aClassicScriptHintCharset = mClassicScriptHintCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetDocumentCharacterSet(
+ const nsAString& aDocumentCharacterSet) {
+ mDocumentCharacterSet = aDocumentCharacterSet;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetDocumentCharacterSet(
+ nsAString& aDocumentCharacterSet) {
+ aDocumentCharacterSet = mDocumentCharacterSet;
+ return NS_OK;
+}
+
+void HttpBaseChannel::SetConnectionInfo(nsHttpConnectionInfo* aCI) {
+ mConnectionInfo = aCI ? aCI->Clone() : nullptr;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsProxyUsed(bool* aIsProxyUsed) {
+ if (mProxyInfo) {
+ if (!static_cast<nsProxyInfo*>(mProxyInfo.get())->IsDirect()) {
+ StoreIsProxyUsed(true);
+ }
+ }
+ *aIsProxyUsed = LoadIsProxyUsed();
+ return NS_OK;
+}
+
+static void CollectORBBlockTelemetry(
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason,
+ ExtContentPolicy aPolicy) {
+ Telemetry::LABELS_ORB_BLOCK_REASON label{
+ static_cast<uint32_t>(aTelemetryReason)};
+ Telemetry::AccumulateCategorical(label);
+
+ switch (aPolicy) {
+ case ExtContentPolicy::TYPE_INVALID:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::INVALID);
+ break;
+ case ExtContentPolicy::TYPE_OTHER:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::OTHER);
+ break;
+ case ExtContentPolicy::TYPE_FETCH:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::BLOCKED_FETCH);
+ break;
+ case ExtContentPolicy::TYPE_SCRIPT:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::SCRIPT);
+ break;
+ case ExtContentPolicy::TYPE_IMAGE:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::IMAGE);
+ break;
+ case ExtContentPolicy::TYPE_STYLESHEET:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::STYLESHEET);
+ break;
+ case ExtContentPolicy::TYPE_XMLHTTPREQUEST:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::XMLHTTPREQUEST);
+ break;
+ case ExtContentPolicy::TYPE_DTD:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::DTD);
+ break;
+ case ExtContentPolicy::TYPE_FONT:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::FONT);
+ break;
+ case ExtContentPolicy::TYPE_MEDIA:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::MEDIA);
+ break;
+ case ExtContentPolicy::TYPE_CSP_REPORT:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::CSP_REPORT);
+ break;
+ case ExtContentPolicy::TYPE_XSLT:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::XSLT);
+ break;
+ case ExtContentPolicy::TYPE_IMAGESET:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::IMAGESET);
+ break;
+ case ExtContentPolicy::TYPE_WEB_MANIFEST:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::WEB_MANIFEST);
+ break;
+ case ExtContentPolicy::TYPE_SPECULATIVE:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::SPECULATIVE);
+ break;
+ case ExtContentPolicy::TYPE_UA_FONT:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::UA_FONT);
+ break;
+ case ExtContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::PROXIED_WEBRTC_MEDIA);
+ break;
+ case ExtContentPolicy::TYPE_PING:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::PING);
+ break;
+ case ExtContentPolicy::TYPE_BEACON:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::BEACON);
+ break;
+ case ExtContentPolicy::TYPE_WEB_TRANSPORT:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::WEB_TRANSPORT);
+ break;
+ case ExtContentPolicy::TYPE_WEB_IDENTITY:
+ // Don't bother extending the telemetry for this.
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::OTHER);
+ break;
+ case ExtContentPolicy::TYPE_DOCUMENT:
+ case ExtContentPolicy::TYPE_SUBDOCUMENT:
+ case ExtContentPolicy::TYPE_OBJECT:
+ case ExtContentPolicy::TYPE_OBJECT_SUBREQUEST:
+ case ExtContentPolicy::TYPE_WEBSOCKET:
+ case ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD:
+ MOZ_ASSERT_UNREACHABLE("Shouldn't block this type");
+ // DOCUMENT, SUBDOCUMENT, OBJECT, OBJECT_SUBREQUEST,
+ // WEBSOCKET and SAVEAS_DOWNLOAD are excluded from ORB
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::EXCLUDED);
+ break;
+ // Do not add default: so that compilers can catch the missing case.
+ }
+}
+
+void HttpBaseChannel::LogORBError(
+ const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason) {
+ auto policy = mLoadInfo->GetExternalContentPolicyType();
+ CollectORBBlockTelemetry(aTelemetryReason, policy);
+
+ // Blocking `ExtContentPolicy::TYPE_BEACON` isn't web observable, so keep
+ // quiet in the console about blocking it.
+ if (policy == ExtContentPolicy::TYPE_BEACON) {
+ return;
+ }
+
+ RefPtr<dom::Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+
+ nsAutoCString uri;
+ nsresult rv = nsContentUtils::AnonymizeURI(mURI, uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ uint64_t contentWindowId;
+ GetTopLevelContentWindowId(&contentWindowId);
+ if (contentWindowId) {
+ nsContentUtils::ReportToConsoleByWindowID(
+ u"A resource is blocked by OpaqueResponseBlocking, please check browser console for details."_ns,
+ nsIScriptError::warningFlag, "ORB"_ns, contentWindowId, mURI);
+ }
+
+ AutoTArray<nsString, 2> params;
+ params.AppendElement(NS_ConvertUTF8toUTF16(uri));
+ params.AppendElement(aReason);
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "ORB"_ns, doc,
+ nsContentUtils::eNECKO_PROPERTIES,
+ "ResourceBlockedORB", params);
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetEarlyHintLinkType(
+ uint32_t aEarlyHintLinkType) {
+ mEarlyHintLinkType = aEarlyHintLinkType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetEarlyHintLinkType(
+ uint32_t* aEarlyHintLinkType) {
+ *aEarlyHintLinkType = mEarlyHintLinkType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetHasContentDecompressed(bool aValue) {
+ LOG(("HttpBaseChannel::SetHasContentDecompressed [this=%p value=%d]\n", this,
+ aValue));
+ mHasContentDecompressed = aValue;
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpBaseChannel::GetHasContentDecompressed(bool* value) {
+ *value = mHasContentDecompressed;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h
new file mode 100644
index 0000000000..f3678bb0c9
--- /dev/null
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -0,0 +1,1194 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_HttpBaseChannel_h
+#define mozilla_net_HttpBaseChannel_h
+
+#include <utility>
+
+#include "OpaqueResponseUtils.h"
+#include "mozilla/AtomicBitfields.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/PrivateBrowsingChannel.h"
+#include "nsCOMPtr.h"
+#include "nsHashPropertyBag.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsIClassOfService.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIEncodedChannel.h"
+#include "nsIForcePendingChannel.h"
+#include "nsIFormPOSTActionChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsILoadInfo.h"
+#include "nsIResumableChannel.h"
+#include "nsIStringEnumerator.h"
+#include "nsISupportsPriority.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITimedChannel.h"
+#include "nsITraceableChannel.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIURI.h"
+#include "nsIUploadChannel2.h"
+#include "nsStringEnumerator.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+#define HTTP_BASE_CHANNEL_IID \
+ { \
+ 0x9d5cde03, 0xe6e9, 0x4612, { \
+ 0xbf, 0xef, 0xbb, 0x66, 0xf3, 0xbb, 0x74, 0x46 \
+ } \
+ }
+
+class nsIProgressEventSink;
+class nsISecurityConsoleMessage;
+class nsIPrincipal;
+
+namespace mozilla {
+
+namespace dom {
+class PerformanceStorage;
+class ContentParent;
+} // namespace dom
+
+class LogCollector;
+
+namespace net {
+extern mozilla::LazyLogModule gHttpLog;
+
+class OpaqueResponseBlocker;
+class PreferredAlternativeDataTypeParams;
+
+enum CacheDisposition : uint8_t {
+ kCacheUnresolved = 0,
+ kCacheHit = 1,
+ kCacheHitViaReval = 2,
+ kCacheMissedViaReval = 3,
+ kCacheMissed = 4,
+ kCacheUnknown = 5
+};
+
+// These need to be kept in sync with
+// "browser.opaqueResponseBlocking.filterFetchResponse"
+enum class OpaqueResponseFilterFetch { Never, AllowedByORB, BlockedByORB, All };
+
+/*
+ * This class is a partial implementation of nsIHttpChannel. It contains code
+ * shared by nsHttpChannel and HttpChannelChild.
+ * - Note that this class has nothing to do with nsBaseChannel, which is an
+ * earlier effort at a base class for channels that somehow never made it all
+ * the way to the HTTP channel.
+ */
+class HttpBaseChannel : public nsHashPropertyBag,
+ public nsIEncodedChannel,
+ public nsIHttpChannel,
+ public nsIHttpChannelInternal,
+ public nsIFormPOSTActionChannel,
+ public nsIUploadChannel2,
+ public nsISupportsPriority,
+ public nsIClassOfService,
+ public nsIResumableChannel,
+ public nsITraceableChannel,
+ public PrivateBrowsingChannel<HttpBaseChannel>,
+ public nsITimedChannel,
+ public nsIForcePendingChannel,
+ public nsIConsoleReportCollector,
+ public nsIThrottledInputChannel,
+ public nsIClassifiedChannel {
+ protected:
+ virtual ~HttpBaseChannel();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIUPLOADCHANNEL
+ NS_DECL_NSIFORMPOSTACTIONCHANNEL
+ NS_DECL_NSIUPLOADCHANNEL2
+ NS_DECL_NSITRACEABLECHANNEL
+ NS_DECL_NSITIMEDCHANNEL
+ NS_DECL_NSITHROTTLEDINPUTCHANNEL
+ NS_DECL_NSICLASSIFIEDCHANNEL
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_BASE_CHANNEL_IID)
+
+ HttpBaseChannel();
+
+ [[nodiscard]] virtual nsresult Init(nsIURI* aURI, uint32_t aCaps,
+ nsProxyInfo* aProxyInfo,
+ uint32_t aProxyResolveFlags,
+ nsIURI* aProxyURI, uint64_t aChannelId,
+ ExtContentPolicyType aContentPolicyType,
+ nsILoadInfo* aLoadInfo);
+
+ // nsIRequest
+ NS_IMETHOD GetName(nsACString& aName) override;
+ NS_IMETHOD IsPending(bool* aIsPending) override;
+ NS_IMETHOD GetStatus(nsresult* aStatus) override;
+ NS_IMETHOD GetLoadGroup(nsILoadGroup** aLoadGroup) override;
+ NS_IMETHOD SetLoadGroup(nsILoadGroup* aLoadGroup) override;
+ NS_IMETHOD GetLoadFlags(nsLoadFlags* aLoadFlags) override;
+ NS_IMETHOD SetLoadFlags(nsLoadFlags aLoadFlags) override;
+ NS_IMETHOD GetTRRMode(nsIRequest::TRRMode* aTRRMode) override;
+ NS_IMETHOD SetTRRMode(nsIRequest::TRRMode aTRRMode) override;
+ NS_IMETHOD SetDocshellUserAgentOverride();
+
+ // nsIChannel
+ NS_IMETHOD GetOriginalURI(nsIURI** aOriginalURI) override;
+ NS_IMETHOD SetOriginalURI(nsIURI* aOriginalURI) override;
+ NS_IMETHOD GetURI(nsIURI** aURI) override;
+ NS_IMETHOD GetOwner(nsISupports** aOwner) override;
+ NS_IMETHOD SetOwner(nsISupports* aOwner) override;
+ NS_IMETHOD GetLoadInfo(nsILoadInfo** aLoadInfo) override;
+ NS_IMETHOD SetLoadInfo(nsILoadInfo* aLoadInfo) override;
+ NS_IMETHOD GetIsDocument(bool* aIsDocument) override;
+ NS_IMETHOD GetNotificationCallbacks(
+ nsIInterfaceRequestor** aCallbacks) override;
+ NS_IMETHOD SetNotificationCallbacks(
+ nsIInterfaceRequestor* aCallbacks) override;
+ NS_IMETHOD GetContentType(nsACString& aContentType) override;
+ NS_IMETHOD SetContentType(const nsACString& aContentType) override;
+ NS_IMETHOD GetContentCharset(nsACString& aContentCharset) override;
+ NS_IMETHOD SetContentCharset(const nsACString& aContentCharset) override;
+ NS_IMETHOD GetContentDisposition(uint32_t* aContentDisposition) override;
+ NS_IMETHOD SetContentDisposition(uint32_t aContentDisposition) override;
+ NS_IMETHOD GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) override;
+ NS_IMETHOD SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) override;
+ NS_IMETHOD GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) override;
+ NS_IMETHOD GetContentLength(int64_t* aContentLength) override;
+ NS_IMETHOD SetContentLength(int64_t aContentLength) override;
+ NS_IMETHOD Open(nsIInputStream** aResult) override;
+ NS_IMETHOD GetBlockAuthPrompt(bool* aValue) override;
+ NS_IMETHOD SetBlockAuthPrompt(bool aValue) override;
+ NS_IMETHOD GetCanceled(bool* aCanceled) override;
+
+ // nsIEncodedChannel
+ NS_IMETHOD GetApplyConversion(bool* value) override;
+ NS_IMETHOD SetApplyConversion(bool value) override;
+ NS_IMETHOD GetContentEncodings(nsIUTF8StringEnumerator** aEncodings) override;
+ NS_IMETHOD DoApplyContentConversions(nsIStreamListener* aNextListener,
+ nsIStreamListener** aNewNextListener,
+ nsISupports* aCtxt) override;
+ NS_IMETHOD SetHasContentDecompressed(bool value) override;
+ NS_IMETHOD GetHasContentDecompressed(bool* value) override;
+
+ // HttpBaseChannel::nsIHttpChannel
+ NS_IMETHOD GetRequestMethod(nsACString& aMethod) override;
+ NS_IMETHOD SetRequestMethod(const nsACString& aMethod) override;
+ NS_IMETHOD GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) override;
+ NS_IMETHOD SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) override;
+ NS_IMETHOD SetReferrerInfoWithoutClone(
+ nsIReferrerInfo* aReferrerInfo) override;
+ NS_IMETHOD GetRequestHeader(const nsACString& aHeader,
+ nsACString& aValue) override;
+ NS_IMETHOD SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) override;
+ NS_IMETHOD SetNewReferrerInfo(const nsACString& aUrl,
+ nsIReferrerInfo::ReferrerPolicyIDL aPolicy,
+ bool aSendReferrer) override;
+ NS_IMETHOD SetEmptyRequestHeader(const nsACString& aHeader) override;
+ NS_IMETHOD VisitRequestHeaders(nsIHttpHeaderVisitor* visitor) override;
+ NS_IMETHOD VisitNonDefaultRequestHeaders(
+ nsIHttpHeaderVisitor* visitor) override;
+ NS_IMETHOD ShouldStripRequestBodyHeader(const nsACString& aMethod,
+ bool* aResult) override;
+ NS_IMETHOD GetResponseHeader(const nsACString& header,
+ nsACString& value) override;
+ NS_IMETHOD SetResponseHeader(const nsACString& header,
+ const nsACString& value, bool merge) override;
+ NS_IMETHOD VisitResponseHeaders(nsIHttpHeaderVisitor* visitor) override;
+ NS_IMETHOD GetOriginalResponseHeader(const nsACString& aHeader,
+ nsIHttpHeaderVisitor* aVisitor) override;
+ NS_IMETHOD VisitOriginalResponseHeaders(
+ nsIHttpHeaderVisitor* aVisitor) override;
+ NS_IMETHOD GetAllowSTS(bool* value) override;
+ NS_IMETHOD SetAllowSTS(bool value) override;
+ NS_IMETHOD GetRedirectionLimit(uint32_t* value) override;
+ NS_IMETHOD SetRedirectionLimit(uint32_t value) override;
+ NS_IMETHOD IsNoStoreResponse(bool* value) override;
+ NS_IMETHOD IsNoCacheResponse(bool* value) override;
+ NS_IMETHOD IsPrivateResponse(bool* value) override;
+ NS_IMETHOD GetResponseStatus(uint32_t* aValue) override;
+ NS_IMETHOD GetResponseStatusText(nsACString& aValue) override;
+ NS_IMETHOD GetRequestSucceeded(bool* aValue) override;
+ NS_IMETHOD RedirectTo(nsIURI* newURI) override;
+ NS_IMETHOD UpgradeToSecure() override;
+ NS_IMETHOD GetRequestContextID(uint64_t* aRCID) override;
+ NS_IMETHOD GetTransferSize(uint64_t* aTransferSize) override;
+ NS_IMETHOD GetRequestSize(uint64_t* aRequestSize) override;
+ NS_IMETHOD GetDecodedBodySize(uint64_t* aDecodedBodySize) override;
+ NS_IMETHOD GetEncodedBodySize(uint64_t* aEncodedBodySize) override;
+ NS_IMETHOD GetSupportsHTTP3(bool* aSupportsHTTP3) override;
+ NS_IMETHOD GetHasHTTPSRR(bool* aHasHTTPSRR) override;
+ NS_IMETHOD SetRequestContextID(uint64_t aRCID) override;
+ NS_IMETHOD GetIsMainDocumentChannel(bool* aValue) override;
+ NS_IMETHOD SetIsMainDocumentChannel(bool aValue) override;
+ NS_IMETHOD GetProtocolVersion(nsACString& aProtocolVersion) override;
+ NS_IMETHOD GetChannelId(uint64_t* aChannelId) override;
+ NS_IMETHOD SetChannelId(uint64_t aChannelId) override;
+ NS_IMETHOD GetTopLevelContentWindowId(uint64_t* aContentWindowId) override;
+ NS_IMETHOD SetTopLevelContentWindowId(uint64_t aContentWindowId) override;
+ NS_IMETHOD GetBrowserId(uint64_t* aId) override;
+ NS_IMETHOD SetBrowserId(uint64_t aId) override;
+ NS_IMETHOD GetIsProxyUsed(bool* aIsProxyUsed) override;
+
+ using nsIClassifiedChannel::IsThirdPartyTrackingResource;
+
+ virtual void SetSource(UniquePtr<ProfileChunkedBuffer> aSource) override {
+ mSource = std::move(aSource);
+ }
+
+ // nsIHttpChannelInternal
+ NS_IMETHOD GetDocumentURI(nsIURI** aDocumentURI) override;
+ NS_IMETHOD SetDocumentURI(nsIURI* aDocumentURI) override;
+ NS_IMETHOD GetRequestVersion(uint32_t* major, uint32_t* minor) override;
+ NS_IMETHOD GetResponseVersion(uint32_t* major, uint32_t* minor) override;
+ NS_IMETHOD SetCookie(const nsACString& aCookieHeader) override;
+ NS_IMETHOD GetThirdPartyFlags(uint32_t* aForce) override;
+ NS_IMETHOD SetThirdPartyFlags(uint32_t aForce) override;
+ NS_IMETHOD GetForceAllowThirdPartyCookie(bool* aForce) override;
+ NS_IMETHOD SetForceAllowThirdPartyCookie(bool aForce) override;
+ NS_IMETHOD GetChannelIsForDownload(bool* aChannelIsForDownload) override;
+ NS_IMETHOD SetChannelIsForDownload(bool aChannelIsForDownload) override;
+ NS_IMETHOD SetCacheKeysRedirectChain(nsTArray<nsCString>* cacheKeys) override;
+ NS_IMETHOD GetLocalAddress(nsACString& addr) override;
+ NS_IMETHOD GetLocalPort(int32_t* port) override;
+ NS_IMETHOD GetRemoteAddress(nsACString& addr) override;
+ NS_IMETHOD GetRemotePort(int32_t* port) override;
+ NS_IMETHOD GetOnlyConnect(bool* aOnlyConnect) override;
+ NS_IMETHOD SetConnectOnly() override;
+ NS_IMETHOD GetAllowSpdy(bool* aAllowSpdy) override;
+ NS_IMETHOD SetAllowSpdy(bool aAllowSpdy) override;
+ NS_IMETHOD GetAllowHttp3(bool* aAllowHttp3) override;
+ NS_IMETHOD SetAllowHttp3(bool aAllowHttp3) override;
+ NS_IMETHOD GetAllowAltSvc(bool* aAllowAltSvc) override;
+ NS_IMETHOD SetAllowAltSvc(bool aAllowAltSvc) override;
+ NS_IMETHOD GetBeConservative(bool* aBeConservative) override;
+ NS_IMETHOD SetBeConservative(bool aBeConservative) override;
+ NS_IMETHOD GetBypassProxy(bool* aBypassProxy) override;
+ NS_IMETHOD SetBypassProxy(bool aBypassProxy) override;
+ bool BypassProxy();
+
+ NS_IMETHOD GetIsTRRServiceChannel(bool* aTRR) override;
+ NS_IMETHOD SetIsTRRServiceChannel(bool aTRR) override;
+ NS_IMETHOD GetIsResolvedByTRR(bool* aResolvedByTRR) override;
+ NS_IMETHOD GetEffectiveTRRMode(
+ nsIRequest::TRRMode* aEffectiveTRRMode) override;
+ NS_IMETHOD GetTrrSkipReason(nsITRRSkipReason::value* aTrrSkipReason) override;
+ NS_IMETHOD GetIsLoadedBySocketProcess(bool* aResult) override;
+ NS_IMETHOD GetIsOCSP(bool* value) override;
+ NS_IMETHOD SetIsOCSP(bool value) override;
+ NS_IMETHOD GetTlsFlags(uint32_t* aTlsFlags) override;
+ NS_IMETHOD SetTlsFlags(uint32_t aTlsFlags) override;
+ NS_IMETHOD GetApiRedirectToURI(nsIURI** aApiRedirectToURI) override;
+ [[nodiscard]] virtual nsresult AddSecurityMessage(
+ const nsAString& aMessageTag, const nsAString& aMessageCategory);
+ NS_IMETHOD TakeAllSecurityMessages(
+ nsCOMArray<nsISecurityConsoleMessage>& aMessages) override;
+ NS_IMETHOD GetResponseTimeoutEnabled(bool* aEnable) override;
+ NS_IMETHOD SetResponseTimeoutEnabled(bool aEnable) override;
+ NS_IMETHOD GetInitialRwin(uint32_t* aRwin) override;
+ NS_IMETHOD SetInitialRwin(uint32_t aRwin) override;
+ NS_IMETHOD ForcePending(bool aForcePending) override;
+ NS_IMETHOD GetLastModifiedTime(PRTime* lastModifiedTime) override;
+ NS_IMETHOD GetCorsIncludeCredentials(bool* aInclude) override;
+ NS_IMETHOD SetCorsIncludeCredentials(bool aInclude) override;
+ NS_IMETHOD GetRequestMode(dom::RequestMode* aRequestMode) override;
+ NS_IMETHOD SetRequestMode(dom::RequestMode aRequestMode) override;
+ NS_IMETHOD GetRedirectMode(uint32_t* aRedirectMode) override;
+ NS_IMETHOD SetRedirectMode(uint32_t aRedirectMode) override;
+ NS_IMETHOD GetFetchCacheMode(uint32_t* aFetchCacheMode) override;
+ NS_IMETHOD SetFetchCacheMode(uint32_t aFetchCacheMode) override;
+ NS_IMETHOD GetTopWindowURI(nsIURI** aTopWindowURI) override;
+ NS_IMETHOD SetTopWindowURIIfUnknown(nsIURI* aTopWindowURI) override;
+ NS_IMETHOD GetProxyURI(nsIURI** proxyURI) override;
+ virtual void SetCorsPreflightParameters(
+ const nsTArray<nsCString>& unsafeHeaders,
+ bool aShouldStripRequestBodyHeader) override;
+ virtual void SetAltDataForChild(bool aIsForChild) override;
+ virtual void DisableAltDataCache() override {
+ StoreDisableAltDataCache(true);
+ };
+
+ NS_IMETHOD GetConnectionInfoHashKey(
+ nsACString& aConnectionInfoHashKey) override;
+ NS_IMETHOD GetIntegrityMetadata(nsAString& aIntegrityMetadata) override;
+ NS_IMETHOD SetIntegrityMetadata(const nsAString& aIntegrityMetadata) override;
+ NS_IMETHOD GetLastRedirectFlags(uint32_t* aValue) override;
+ NS_IMETHOD SetLastRedirectFlags(uint32_t aValue) override;
+ NS_IMETHOD GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) override;
+ NS_IMETHOD SetNavigationStartTimeStamp(TimeStamp aTimeStamp) override;
+ NS_IMETHOD CancelByURLClassifier(nsresult aErrorCode) override;
+ NS_IMETHOD SetIPv4Disabled(void) override;
+ NS_IMETHOD SetIPv6Disabled(void) override;
+ NS_IMETHOD GetCrossOriginOpenerPolicy(
+ nsILoadInfo::CrossOriginOpenerPolicy* aCrossOriginOpenerPolicy) override;
+ NS_IMETHOD ComputeCrossOriginOpenerPolicy(
+ nsILoadInfo::CrossOriginOpenerPolicy aInitiatorPolicy,
+ nsILoadInfo::CrossOriginOpenerPolicy* aOutPolicy) override;
+ NS_IMETHOD HasCrossOriginOpenerPolicyMismatch(bool* aIsMismatch) override;
+ NS_IMETHOD GetResponseEmbedderPolicy(
+ bool aIsOriginTrialCoepCredentiallessEnabled,
+ nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) override;
+
+ inline void CleanRedirectCacheChainIfNecessary() {
+ auto redirectedCachekeys = mRedirectedCachekeys.Lock();
+ redirectedCachekeys.ref() = nullptr;
+ }
+ NS_IMETHOD HTTPUpgrade(const nsACString& aProtocolName,
+ nsIHttpUpgradeListener* aListener) override;
+ void DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() override;
+
+ NS_IMETHOD SetWaitForHTTPSSVCRecord() override;
+
+ NS_IMETHOD SetEarlyHintPreloaderId(uint64_t aEarlyHintPreloaderId) override;
+ NS_IMETHOD GetEarlyHintPreloaderId(uint64_t* aEarlyHintPreloaderId) override;
+
+ NS_IMETHOD SetEarlyHintLinkType(uint32_t aEarlyHintLinkType) override;
+ NS_IMETHOD GetEarlyHintLinkType(uint32_t* aEarlyHintLinkType) override;
+
+ NS_IMETHOD SetIsUserAgentHeaderModified(bool value) override;
+ NS_IMETHOD GetIsUserAgentHeaderModified(bool* value) override;
+
+ NS_IMETHOD SetClassicScriptHintCharset(
+ const nsAString& aClassicScriptHintCharset) override;
+ NS_IMETHOD GetClassicScriptHintCharset(
+ nsAString& aClassicScriptHintCharset) override;
+
+ NS_IMETHOD SetDocumentCharacterSet(
+ const nsAString& aDocumentCharacterSet) override;
+ NS_IMETHOD GetDocumentCharacterSet(nsAString& aDocumentCharacterSet) override;
+
+ virtual void SetConnectionInfo(
+ mozilla::net::nsHttpConnectionInfo* aCI) override;
+
+ // nsISupportsPriority
+ NS_IMETHOD GetPriority(int32_t* value) override;
+ NS_IMETHOD AdjustPriority(int32_t delta) override;
+
+ // nsIClassOfService
+ NS_IMETHOD GetClassFlags(uint32_t* outFlags) override {
+ *outFlags = mClassOfService.Flags();
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetIncremental(bool* outIncremental) override {
+ *outIncremental = mClassOfService.Incremental();
+ return NS_OK;
+ }
+
+ // nsIResumableChannel
+ NS_IMETHOD GetEntityID(nsACString& aEntityID) override;
+
+ // nsIConsoleReportCollector
+ void AddConsoleReport(uint32_t aErrorFlags, const nsACString& aCategory,
+ nsContentUtils::PropertiesFile aPropertiesFile,
+ const nsACString& aSourceFileURI, uint32_t aLineNumber,
+ uint32_t aColumnNumber, const nsACString& aMessageName,
+ const nsTArray<nsString>& aStringParams) override;
+
+ void FlushReportsToConsole(
+ uint64_t aInnerWindowID,
+ ReportAction aAction = ReportAction::Forget) override;
+
+ void FlushReportsToConsoleForServiceWorkerScope(
+ const nsACString& aScope,
+ ReportAction aAction = ReportAction::Forget) override;
+
+ void FlushConsoleReports(
+ dom::Document* aDocument,
+ ReportAction aAction = ReportAction::Forget) override;
+
+ void FlushConsoleReports(
+ nsILoadGroup* aLoadGroup,
+ ReportAction aAction = ReportAction::Forget) override;
+
+ void FlushConsoleReports(nsIConsoleReportCollector* aCollector) override;
+
+ void StealConsoleReports(
+ nsTArray<net::ConsoleReportCollected>& aReports) override;
+
+ void ClearConsoleReports() override;
+
+ class nsContentEncodings : public nsStringEnumeratorBase {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+
+ using nsStringEnumeratorBase::GetNext;
+
+ nsContentEncodings(nsIHttpChannel* aChannel, const char* aEncodingHeader);
+
+ private:
+ virtual ~nsContentEncodings() = default;
+
+ [[nodiscard]] nsresult PrepareForNext(void);
+
+ // We do not own the buffer. The channel owns it.
+ const char* mEncodingHeader;
+ const char* mCurStart; // points to start of current header
+ const char* mCurEnd; // points to end of current header
+
+ // Hold a ref to our channel so that it can't go away and take the
+ // header with it.
+ nsCOMPtr<nsIHttpChannel> mChannel;
+
+ bool mReady;
+ };
+
+ nsHttpResponseHead* GetResponseHead() const { return mResponseHead.get(); }
+ nsHttpRequestHead* GetRequestHead() { return &mRequestHead; }
+ nsHttpHeaderArray* GetResponseTrailers() const {
+ return mResponseTrailers.get();
+ }
+
+ void SetDummyChannelForImageCache();
+
+ const NetAddr& GetSelfAddr() { return mSelfAddr; }
+ const NetAddr& GetPeerAddr() { return mPeerAddr; }
+
+ [[nodiscard]] nsresult OverrideSecurityInfo(
+ nsITransportSecurityInfo* aSecurityInfo);
+
+ void LogORBError(const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason);
+
+ public: /* Necko internal use only... */
+ int64_t GetAltDataLength() { return mAltDataLength; }
+ bool IsNavigation();
+
+ bool IsDeliveringAltData() const { return LoadDeliveringAltData(); }
+
+ static void PropagateReferenceIfNeeded(nsIURI* aURI,
+ nsCOMPtr<nsIURI>& aRedirectURI);
+
+ // Return whether upon a redirect code of httpStatus for method, the
+ // request method should be rewritten to GET.
+ static bool ShouldRewriteRedirectToGET(
+ uint32_t httpStatus, nsHttpRequestHead::ParsedMethodType method);
+
+ // Like nsIEncodedChannel::DoApplyConversions except context is set to
+ // mListenerContext.
+ [[nodiscard]] nsresult DoApplyContentConversions(
+ nsIStreamListener* aNextListener, nsIStreamListener** aNewNextListener);
+
+ void AddClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty);
+
+ const uint64_t& ChannelId() const { return mChannelId; }
+
+ nsresult InternalSetUploadStream(nsIInputStream* uploadStream,
+ int64_t aContentLength = -1,
+ bool aSetContentLengthHeader = false);
+
+ void SetUploadStreamHasHeaders(bool hasHeaders) {
+ StoreUploadStreamHasHeaders(hasHeaders);
+ }
+
+ virtual nsresult SetReferrerHeader(const nsACString& aReferrer,
+ bool aRespectBeforeConnect = true) {
+ if (aRespectBeforeConnect) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ }
+ return mRequestHead.SetHeader(nsHttp::Referer, aReferrer);
+ }
+
+ nsresult ClearReferrerHeader() {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ return mRequestHead.ClearHeader(nsHttp::Referer);
+ }
+
+ void SetTopWindowURI(nsIURI* aTopWindowURI) { mTopWindowURI = aTopWindowURI; }
+
+ // Set referrerInfo and compute the referrer header if neccessary.
+ // Pass true for aSetOriginal if this is a new referrer and should
+ // overwrite the 'original' value, false if this is a mutation (like
+ // stripping the path).
+ nsresult SetReferrerInfoInternal(nsIReferrerInfo* aReferrerInfo, bool aClone,
+ bool aCompute, bool aRespectBeforeConnect);
+
+ struct ReplacementChannelConfig {
+ ReplacementChannelConfig() = default;
+ explicit ReplacementChannelConfig(
+ const dom::ReplacementChannelConfigInit& aInit);
+
+ uint32_t redirectFlags = 0;
+ ClassOfService classOfService = {0, false};
+ Maybe<bool> privateBrowsing = Nothing();
+ Maybe<nsCString> method;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ Maybe<dom::TimedChannelInfo> timedChannelInfo;
+ nsCOMPtr<nsIInputStream> uploadStream;
+ uint64_t uploadStreamLength = 0;
+ bool uploadStreamHasHeaders = false;
+ Maybe<nsCString> contentType;
+ Maybe<nsCString> contentLength;
+
+ dom::ReplacementChannelConfigInit Serialize();
+ };
+
+ enum class ReplacementReason {
+ Redirect,
+ InternalRedirect,
+ DocumentChannel,
+ };
+
+ // Create a ReplacementChannelConfig object that can be used to duplicate the
+ // current channel.
+ ReplacementChannelConfig CloneReplacementChannelConfig(
+ bool aPreserveMethod, uint32_t aRedirectFlags, ReplacementReason aReason);
+
+ static void ConfigureReplacementChannel(nsIChannel*,
+ const ReplacementChannelConfig&,
+ ReplacementReason);
+
+ // Called before we create the redirect target channel.
+ already_AddRefed<nsILoadInfo> CloneLoadInfoForRedirect(
+ nsIURI* aNewURI, uint32_t aRedirectFlags);
+
+ // True if we've already applied content conversion to the data
+ // passed to mListener.
+ bool HasAppliedConversion() { return LoadHasAppliedConversion(); }
+
+ // https://fetch.spec.whatwg.org/#concept-request-tainted-origin
+ bool HasRedirectTaintedOrigin() { return LoadTaintedOriginFlag(); }
+
+ bool ChannelBlockedByOpaqueResponse() const {
+ return mChannelBlockedByOpaqueResponse;
+ }
+ bool CachedOpaqueResponseBlockingPref() const {
+ return mCachedOpaqueResponseBlockingPref;
+ }
+
+ TimeStamp GetOnStartRequestStartTime() const {
+ return mOnStartRequestStartTime;
+ }
+ TimeStamp GetDataAvailableStartTime() const {
+ return mOnDataAvailableStartTime;
+ }
+ TimeStamp GetOnStopRequestStartTime() const {
+ return mOnStopRequestStartTime;
+ }
+
+ protected:
+ nsresult GetTopWindowURI(nsIURI* aURIBeingLoaded, nsIURI** aTopWindowURI);
+
+ // Handle notifying listener, removing from loadgroup if request failed.
+ void DoNotifyListener();
+ virtual void DoNotifyListenerCleanup() = 0;
+
+ // drop reference to listener, its callbacks, and the progress sink
+ virtual void ReleaseListeners();
+
+ // Call AsyncAbort().
+ virtual void DoAsyncAbort(nsresult aStatus) = 0;
+
+ // This is fired only when a cookie is created due to the presence of
+ // Set-Cookie header in the response header of any network request.
+ // This notification will come only after the "http-on-examine-response"
+ // was fired.
+ void NotifySetCookie(const nsACString& aCookie);
+
+ void MaybeReportTimingData();
+ nsIURI* GetReferringPage();
+ nsPIDOMWindowInner* GetInnerDOMWindow();
+
+ void AddCookiesToRequest();
+ [[nodiscard]] virtual nsresult SetupReplacementChannel(
+ nsIURI*, nsIChannel*, bool preserveMethod, uint32_t redirectFlags);
+
+ bool IsNewChannelSameOrigin(nsIChannel* aNewChannel);
+
+ // WHATWG Fetch Standard 4.4. HTTP-redirect fetch, step 10
+ virtual bool ShouldTaintReplacementChannelOrigin(nsIChannel* aNewChannel,
+ uint32_t aRedirectFlags);
+
+ // bundle calling OMR observers and marking flag into one function
+ inline void CallOnModifyRequestObservers() {
+ gHttpHandler->OnModifyRequest(this);
+ MOZ_ASSERT(!LoadRequestObserversCalled());
+ StoreRequestObserversCalled(true);
+ }
+
+ // Helper function to simplify getting notification callbacks.
+ template <class T>
+ void GetCallback(nsCOMPtr<T>& aResult) {
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_TEMPLATE_IID(T),
+ getter_AddRefs(aResult));
+ }
+
+ // Redirect tracking
+ // Checks whether or not aURI and mOriginalURI share the same domain.
+ virtual bool SameOriginWithOriginalUri(nsIURI* aURI);
+
+ [[nodiscard]] bool BypassServiceWorker() const;
+
+ // Returns true if this channel should intercept the network request and
+ // prepare for a possible synthesized response instead.
+ bool ShouldIntercept(nsIURI* aURI = nullptr);
+
+#ifdef DEBUG
+ // Check if mPrivateBrowsingId matches between LoadInfo and LoadContext.
+ void AssertPrivateBrowsingId();
+#endif
+
+ static void CallTypeSniffers(void* aClosure, const uint8_t* aData,
+ uint32_t aCount);
+
+ nsresult CheckRedirectLimit(uint32_t aRedirectFlags) const;
+
+ bool MaybeWaitForUploadStreamNormalization(nsIStreamListener* aListener,
+ nsISupports* aContext);
+
+ void MaybeFlushConsoleReports();
+
+ bool IsBrowsingContextDiscarded() const;
+
+ nsresult ProcessCrossOriginEmbedderPolicyHeader();
+
+ nsresult ProcessCrossOriginResourcePolicyHeader();
+
+ nsresult ComputeCrossOriginOpenerPolicyMismatch();
+
+ nsresult ProcessCrossOriginSecurityHeaders();
+
+ nsresult ValidateMIMEType();
+
+ bool ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch aFilterType) const;
+ bool ShouldBlockOpaqueResponse() const;
+ OpaqueResponse BlockOrFilterOpaqueResponse(
+ OpaqueResponseBlocker* aORB, const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason,
+ const char* aFormat, ...);
+
+ OpaqueResponse PerformOpaqueResponseSafelistCheckBeforeSniff();
+
+ OpaqueResponse PerformOpaqueResponseSafelistCheckAfterSniff(
+ const nsACString& aContentType, bool aNoSniff);
+
+ bool NeedOpaqueResponseAllowedCheckAfterSniff() const;
+ void BlockOpaqueResponseAfterSniff(
+ const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason);
+ void AllowOpaqueResponseAfterSniff();
+ void SetChannelBlockedByOpaqueResponse();
+ bool Http3Allowed() const;
+
+ friend class OpaqueResponseBlocker;
+ friend class PrivateBrowsingChannel<HttpBaseChannel>;
+ friend class InterceptFailedOnStop;
+ friend class HttpChannelParent;
+
+ protected:
+ // this section is for main-thread-only object
+ // all the references need to be proxy released on main thread.
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ nsCOMPtr<nsIURI> mAPIRedirectToURI;
+ nsCOMPtr<nsIURI> mProxyURI;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIURI> mTopWindowURI;
+ nsCOMPtr<nsIStreamListener> mListener;
+ // An instance of nsHTTPCompressConv
+ nsCOMPtr<nsIStreamListener> mCompressListener;
+ nsCOMPtr<nsIEventTarget> mCurrentThread;
+
+ RefPtr<OpaqueResponseBlocker> mORB;
+
+ private:
+ // Proxy release all members above on main thread.
+ void ReleaseMainThreadOnlyReferences();
+
+ void ExplicitSetUploadStreamLength(uint64_t aContentLength,
+ bool aSetContentLengthHeader);
+
+ void MaybeResumeAsyncOpen();
+
+ protected:
+ nsCString mSpec; // ASCII encoded URL spec
+ nsCString mContentTypeHint;
+ nsCString mContentCharsetHint;
+ nsCString mUserSetCookieHeader;
+ // HTTP Upgrade Data
+ nsCString mUpgradeProtocol;
+ // Resumable channel specific data
+ nsCString mEntityID;
+ // The initiator type (for this resource) - how was the resource referenced in
+ // the HTML file.
+ nsString mInitiatorType;
+ // Holds the name of the preferred alt-data type for each contentType.
+ nsTArray<PreferredAlternativeDataTypeParams> mPreferredCachedAltDataTypes;
+ // Holds the name of the alternative data type the channel returned.
+ nsCString mAvailableCachedAltDataType;
+ nsString mIntegrityMetadata;
+
+ // Classified channel's matched information
+ nsCString mMatchedList;
+ nsCString mMatchedProvider;
+ nsCString mMatchedFullHash;
+
+ nsTArray<nsCString> mMatchedTrackingLists;
+ nsTArray<nsCString> mMatchedTrackingFullHashes;
+
+ nsCOMPtr<nsISupports> mOwner;
+
+ nsHttpRequestHead mRequestHead;
+ // Upload throttling.
+ nsCOMPtr<nsIInputChannelThrottleQueue> mThrottleQueue;
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ UniquePtr<nsHttpResponseHead> mResponseHead;
+ UniquePtr<nsHttpHeaderArray> mResponseTrailers;
+ RefPtr<nsHttpConnectionInfo> mConnectionInfo;
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+ nsCOMPtr<nsIHttpUpgradeListener> mUpgradeProtocolCallback;
+ UniquePtr<nsString> mContentDispositionFilename;
+ nsCOMPtr<nsIConsoleReportCollector> mReportCollector;
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+ // Accessed on MainThread and Cache2 IO thread
+ DataMutex<UniquePtr<nsTArray<nsCString>>> mRedirectedCachekeys{
+ "mRedirectedCacheKeys"};
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+
+ nsTArray<std::pair<nsString, nsString>> mSecurityConsoleMessages;
+ nsTArray<nsCString> mUnsafeHeaders;
+
+ // A time value equal to the starting time of the fetch that initiates the
+ // redirect.
+ mozilla::TimeStamp mRedirectStartTimeStamp;
+ // A time value equal to the time immediately after receiving the last byte of
+ // the response of the last redirect.
+ mozilla::TimeStamp mRedirectEndTimeStamp;
+
+ PRTime mChannelCreationTime;
+ TimeStamp mChannelCreationTimestamp;
+ TimeStamp mAsyncOpenTime;
+ TimeStamp mCacheReadStart;
+ TimeStamp mCacheReadEnd;
+ TimeStamp mLaunchServiceWorkerStart;
+ TimeStamp mLaunchServiceWorkerEnd;
+ TimeStamp mDispatchFetchEventStart;
+ TimeStamp mDispatchFetchEventEnd;
+ TimeStamp mHandleFetchEventStart;
+ TimeStamp mHandleFetchEventEnd;
+ TimeStamp mOnStartRequestStartTime;
+ TimeStamp mOnDataAvailableStartTime;
+ TimeStamp mOnStopRequestStartTime;
+ // copied from the transaction before we null out mTransaction
+ // so that the timing can still be queried from OnStopRequest
+ TimingStruct mTransactionTimings;
+
+ // Gets computed during ComputeCrossOriginOpenerPolicyMismatch so we have
+ // the channel's policy even if we don't know policy initiator.
+ nsILoadInfo::CrossOriginOpenerPolicy mComputedCrossOriginOpenerPolicy;
+
+ uint64_t mStartPos;
+ uint64_t mTransferSize;
+ uint64_t mRequestSize;
+ uint64_t mDecodedBodySize;
+ // True only when the channel supports any of the versions of HTTP3
+ bool mSupportsHTTP3;
+ uint64_t mEncodedBodySize;
+ uint64_t mRequestContextID;
+ // ID of the top-level document's inner window this channel is being
+ // originated from.
+ uint64_t mContentWindowId;
+ uint64_t mBrowserId;
+ int64_t mAltDataLength;
+ uint64_t mChannelId;
+ uint64_t mReqContentLength;
+
+ Atomic<nsresult, ReleaseAcquire> mStatus;
+
+ // Use Release-Acquire ordering to ensure the OMT ODA is ignored while channel
+ // is canceled on main thread.
+ Atomic<bool, ReleaseAcquire> mCanceled;
+ Atomic<uint32_t, ReleaseAcquire> mFirstPartyClassificationFlags;
+ Atomic<uint32_t, ReleaseAcquire> mThirdPartyClassificationFlags;
+
+ // mutex to guard members accessed during OnDataFinished in
+ // HttpChannelChild.cpp
+ Mutex mOnDataFinishedMutex{"HttpChannelChild::OnDataFinishedMutex"};
+
+ UniquePtr<ProfileChunkedBuffer> mSource;
+
+ uint32_t mLoadFlags;
+ uint32_t mCaps;
+
+ ClassOfService mClassOfService;
+ // This should be set the the actual TRR mode used to resolve the request.
+ // Is initially set to TRR_DEFAULT_MODE, but should be updated to the actual
+ // mode used by the request
+ nsIRequest::TRRMode mEffectiveTRRMode = nsIRequest::TRR_DEFAULT_MODE;
+ TRRSkippedReason mTRRSkipReason = TRRSkippedReason::TRR_UNSET;
+
+ public:
+ void SetEarlyHints(
+ nsTArray<mozilla::net::EarlyHintConnectArgs>&& aEarlyHints);
+ nsTArray<mozilla::net::EarlyHintConnectArgs>&& TakeEarlyHints();
+
+ protected:
+ // Storing Http 103 Early Hint preloads. The parent process is responsible to
+ // start the early hint preloads, but the http child needs to be able to look
+ // them up. They are sent via IPC and stored in this variable. This is set on
+ // main document channel
+ nsTArray<EarlyHintConnectArgs> mEarlyHints;
+ // EarlyHintRegistrar id to connect back to the preload. Set on preload
+ // channels started from the above list
+ uint64_t mEarlyHintPreloaderId = 0;
+ uint32_t mEarlyHintLinkType = 0;
+
+ nsString mClassicScriptHintCharset;
+ nsString mDocumentCharacterSet;
+
+ // clang-format off
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields1, 32, (
+ (uint32_t, UpgradeToSecure, 1),
+ (uint32_t, ApplyConversion, 1),
+ // Set to true if DoApplyContentConversions has been applied to
+ // our default mListener.
+ (uint32_t, HasAppliedConversion, 1),
+ (uint32_t, IsPending, 1),
+ (uint32_t, WasOpened, 1),
+ // if 1 all "http-on-{opening|modify|etc}-request" observers have been
+ // called.
+ (uint32_t, RequestObserversCalled, 1),
+ (uint32_t, ResponseHeadersModified, 1),
+ (uint32_t, AllowSTS, 1),
+ (uint32_t, ThirdPartyFlags, 3),
+ (uint32_t, UploadStreamHasHeaders, 1),
+ (uint32_t, ChannelIsForDownload, 1),
+ (uint32_t, TracingEnabled, 1),
+ // True if timing collection is enabled
+ (uint32_t, TimingEnabled, 1),
+ (uint32_t, ReportTiming, 1),
+ (uint32_t, AllowSpdy, 1),
+ (uint32_t, AllowHttp3, 1),
+ (uint32_t, AllowAltSvc, 1),
+ // !!! This is also used by the URL classifier to exempt channels from
+ // classification. If this is changed or removed, make sure we also update
+ // NS_ShouldClassifyChannel accordingly !!!
+ (uint32_t, BeConservative, 1),
+ // If the current channel is used to as a TRR connection.
+ (uint32_t, IsTRRServiceChannel, 1),
+ // If the request was performed to a TRR resolved IP address.
+ // Will be false if loading the resource does not create a connection
+ // (for example when it's loaded from the cache).
+ (uint32_t, ResolvedByTRR, 1),
+ (uint32_t, ResponseTimeoutEnabled, 1),
+ // A flag that should be false only if a cross-domain redirect occurred
+ (uint32_t, AllRedirectsSameOrigin, 1),
+
+ // Is 1 if no redirects have occured or if all redirects
+ // pass the Resource Timing timing-allow-check
+ (uint32_t, AllRedirectsPassTimingAllowCheck, 1),
+
+ // True if this channel was intercepted and could receive a synthesized
+ // response.
+ (uint32_t, ResponseCouldBeSynthesized, 1),
+
+ (uint32_t, BlockAuthPrompt, 1),
+
+ // If true, we behave as if the LOAD_FROM_CACHE flag has been set.
+ // Used to enforce that flag's behavior but not expose it externally.
+ (uint32_t, AllowStaleCacheContent, 1),
+
+ // If true, we behave as if the VALIDATE_ALWAYS flag has been set.
+ // Used to force validate the cached content.
+ (uint32_t, ForceValidateCacheContent, 1),
+
+ // If true, we prefer the LOAD_FROM_CACHE flag over LOAD_BYPASS_CACHE or
+ // LOAD_BYPASS_LOCAL_CACHE.
+ (uint32_t, PreferCacheLoadOverBypass, 1),
+
+ (uint32_t, IsProxyUsed, 1)
+ ))
+
+ // Broken up into two bitfields to avoid alignment requirements of uint64_t.
+ // (Too many bits used for one uint32_t.)
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields2, 32, (
+ // True iff this request has been calculated in its request context as
+ // a non tail request. We must remove it again when this channel is done.
+ (uint32_t, AddedAsNonTailRequest, 1),
+
+ // True if AsyncOpen() is called when the upload stream normalization or
+ // length is still unknown. AsyncOpen() will be retriggered when
+ // normalization is complete and length has been determined.
+ (uint32_t, AsyncOpenWaitingForStreamNormalization, 1),
+
+ // Defaults to true. This is set to false when it is no longer possible
+ // to upgrade the request to a secure channel.
+ (uint32_t, UpgradableToSecure, 1),
+
+ // Tainted origin flag of a request, specified by
+ // WHATWG Fetch Standard 2.2.5.
+ (uint32_t, TaintedOriginFlag, 1),
+
+ // If the channel is being used to check OCSP
+ (uint32_t, IsOCSP, 1),
+
+ // Used by system requests such as remote settings and updates to
+ // retry requests without proxies.
+ (uint32_t, BypassProxy, 1),
+
+ // Indicate whether the response of this channel is coming from
+ // socket process.
+ (uint32_t, LoadedBySocketProcess, 1),
+
+ // Indicates whether the user-agent header has been modifed since the channel
+ // was created.
+ (uint32_t, IsUserAgentHeaderModified, 1)
+ ))
+ // clang-format on
+
+ // An opaque flags for non-standard behavior of the TLS system.
+ // It is unlikely this will need to be set outside of telemetry studies
+ // relating to the TLS implementation.
+ uint32_t mTlsFlags;
+
+ // Current suspension depth for this channel object
+ uint32_t mSuspendCount;
+
+ // Per channel transport window override (0 means no override)
+ uint32_t mInitialRwin;
+
+ uint32_t mProxyResolveFlags;
+
+ uint32_t mContentDispositionHint;
+
+ dom::RequestMode mRequestMode;
+ uint32_t mRedirectMode;
+
+ // If this channel was created as the result of a redirect, then this value
+ // will reflect the redirect flags passed to the SetupReplacementChannel()
+ // method.
+ uint32_t mLastRedirectFlags;
+
+ int16_t mPriority;
+ uint8_t mRedirectionLimit;
+
+ // Performance tracking
+ // Number of redirects that has occurred.
+ int8_t mRedirectCount;
+ // Number of internal redirects that has occurred.
+ int8_t mInternalRedirectCount;
+
+ enum class SnifferCategoryType {
+ NetContent = 0,
+ OpaqueResponseBlocking,
+ All
+ };
+ SnifferCategoryType mSnifferCategoryType = SnifferCategoryType::NetContent;
+
+ // Used to ensure the same pref value is being used across the
+ // lifetime of this http channel.
+ const bool mCachedOpaqueResponseBlockingPref;
+ bool mChannelBlockedByOpaqueResponse;
+
+ bool mDummyChannelForImageCache;
+
+ bool mHasContentDecompressed;
+
+ // clang-format off
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields3, 8, (
+ (bool, AsyncOpenTimeOverriden, 1),
+ (bool, ForcePending, 1),
+
+ // true if the channel is deliving alt-data.
+ (bool, DeliveringAltData, 1),
+
+ (bool, CorsIncludeCredentials, 1),
+
+ // These parameters are used to ensure that we do not call OnStartRequest
+ // and OnStopRequest more than once.
+ (bool, OnStartRequestCalled, 1),
+ (bool, OnStopRequestCalled, 1),
+
+ // Defaults to false. Is set to true at the begining of OnStartRequest.
+ // Used to ensure methods can't be called before OnStartRequest.
+ (bool, AfterOnStartRequestBegun, 1),
+
+ (bool, RequireCORSPreflight, 1)
+ ))
+
+ // Broken up into two bitfields to avoid alignment requirements of uint16_t.
+ // (Too many bits used for one uint8_t.)
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields4, 8, (
+ // This flag will be true if the consumer is requesting alt-data AND the
+ // consumer is in the child process.
+ (bool, AltDataForChild, 1),
+ // This flag will be true if the consumer cannot process alt-data. This
+ // is used in the webextension StreamFilter handler. If true, we bypass
+ // using alt-data for the request.
+ (bool, DisableAltDataCache, 1),
+
+ (bool, ForceMainDocumentChannel, 1),
+ // This is set true if the channel is waiting for upload stream
+ // normalization or the InputStreamLengthHelper::GetAsyncLength callback.
+ (bool, PendingUploadStreamNormalization, 1),
+
+ // Set to true if our listener has indicated that it requires
+ // content conversion to be done by us.
+ (bool, ListenerRequiresContentConversion, 1),
+
+ // True if this is a navigation to a page with a different cross origin
+ // opener policy ( see ComputeCrossOriginOpenerPolicyMismatch )
+ (uint32_t, HasCrossOriginOpenerPolicyMismatch, 1),
+
+ // True if HTTPS RR is used during the connection establishment of this
+ // channel.
+ (uint32_t, HasHTTPSRR, 1),
+
+ // Ensures that ProcessCrossOriginSecurityHeadersCalled has been called
+ // before calling CallOnStartRequest.
+ (uint32_t, ProcessCrossOriginSecurityHeadersCalled, 1)
+ ))
+ // clang-format on
+
+ bool EnsureRequestContextID();
+ bool EnsureRequestContext();
+
+ // Adds/removes this channel as a non-tailed request in its request context
+ // these helpers ensure we add it only once and remove it only when added
+ // via AddedAsNonTailRequest member tracking.
+ void AddAsNonTailRequest();
+ void RemoveAsNonTailRequest();
+
+ void EnsureBrowserId();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpBaseChannel, HTTP_BASE_CHANNEL_IID)
+
+// Share some code while working around C++'s absurd inability to handle casting
+// of member functions between base/derived types.
+// - We want to store member function pointer to call at resume time, but one
+// such function--HandleAsyncAbort--we want to share between the
+// nsHttpChannel/HttpChannelChild. Can't define it in base class, because
+// then we'd have to cast member function ptr between base/derived class
+// types. Sigh...
+template <class T>
+class HttpAsyncAborter {
+ public:
+ explicit HttpAsyncAborter(T* derived)
+ : mThis(derived), mCallOnResume(nullptr) {}
+
+ // Aborts channel: calls OnStart/Stop with provided status, removes channel
+ // from loadGroup.
+ [[nodiscard]] nsresult AsyncAbort(nsresult status);
+
+ // Does most the actual work.
+ void HandleAsyncAbort();
+
+ // AsyncCall calls a member function asynchronously (via an event).
+ // retval isn't refcounted and is set only when event was successfully
+ // posted, the event is returned for the purpose of cancelling when needed
+ [[nodiscard]] virtual nsresult AsyncCall(
+ void (T::*funcPtr)(), nsRunnableMethod<T>** retval = nullptr);
+
+ private:
+ T* mThis;
+
+ protected:
+ // Function to be called at resume time
+ std::function<nsresult(T*)> mCallOnResume;
+};
+
+template <class T>
+[[nodiscard]] nsresult HttpAsyncAborter<T>::AsyncAbort(nsresult status) {
+ MOZ_LOG(gHttpLog, LogLevel::Debug,
+ ("HttpAsyncAborter::AsyncAbort [this=%p status=%" PRIx32 "]\n", mThis,
+ static_cast<uint32_t>(status)));
+
+ mThis->mStatus = status;
+
+ // if this fails? Callers ignore our return value anyway....
+ return AsyncCall(&T::HandleAsyncAbort);
+}
+
+// Each subclass needs to define its own version of this (which just calls this
+// base version), else we wind up casting base/derived member function ptrs
+template <class T>
+inline void HttpAsyncAborter<T>::HandleAsyncAbort() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mThis->mSuspendCount) {
+ MOZ_LOG(
+ gHttpLog, LogLevel::Debug,
+ ("Waiting until resume to do async notification [this=%p]\n", mThis));
+ mCallOnResume = [](T* self) {
+ self->HandleAsyncAbort();
+ return NS_OK;
+ };
+ return;
+ }
+
+ mThis->DoNotifyListener();
+
+ // finally remove ourselves from the load group.
+ if (mThis->mLoadGroup) {
+ mThis->mLoadGroup->RemoveRequest(mThis, nullptr, mThis->mStatus);
+ }
+}
+
+template <class T>
+nsresult HttpAsyncAborter<T>::AsyncCall(void (T::*funcPtr)(),
+ nsRunnableMethod<T>** retval) {
+ nsresult rv;
+
+ RefPtr<nsRunnableMethod<T>> event =
+ NewRunnableMethod("net::HttpAsyncAborter::AsyncCall", mThis, funcPtr);
+ rv = NS_DispatchToCurrentThread(event);
+ if (NS_SUCCEEDED(rv) && retval) {
+ *retval = event;
+ }
+
+ return rv;
+}
+
+class ProxyReleaseRunnable final : public mozilla::Runnable {
+ public:
+ explicit ProxyReleaseRunnable(nsTArray<nsCOMPtr<nsISupports>>&& aDoomed)
+ : Runnable("ProxyReleaseRunnable"), mDoomed(std::move(aDoomed)) {}
+
+ NS_IMETHOD
+ Run() override {
+ mDoomed.Clear();
+ return NS_OK;
+ }
+
+ private:
+ virtual ~ProxyReleaseRunnable() = default;
+
+ nsTArray<nsCOMPtr<nsISupports>> mDoomed;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpBaseChannel_h
diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp
new file mode 100644
index 0000000000..393d0aa37d
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -0,0 +1,3391 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/net/PBackgroundDataBridge.h"
+#include "nsHttp.h"
+#include "nsICacheEntry.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/LinkStyle.h"
+#include "mozilla/extensions/StreamFilterParent.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/HttpChannelChild.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+
+#include "AltDataOutputStreamChild.h"
+#include "CookieServiceChild.h"
+#include "HttpBackgroundChannelChild.h"
+#include "NetworkMarker.h"
+#include "nsCOMPtr.h"
+#include "nsContentPolicyUtils.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsStringStream.h"
+#include "nsHttpChannel.h"
+#include "nsHttpHandler.h"
+#include "nsQueryObject.h"
+#include "nsNetUtil.h"
+#include "nsSerializationHelper.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/SocketProcessBridgeChild.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "SerializedLoadContext.h"
+#include "nsInputStreamPump.h"
+#include "nsContentSecurityManager.h"
+#include "nsICompressConvStats.h"
+#include "mozilla/dom/Document.h"
+#include "nsIScriptError.h"
+#include "nsISerialEventTarget.h"
+#include "nsRedirectHistoryEntry.h"
+#include "nsSocketTransportService2.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsCORSListenerProxy.h"
+#include "nsIOService.h"
+
+#include <functional>
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla::net {
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild
+//-----------------------------------------------------------------------------
+
+HttpChannelChild::HttpChannelChild()
+ : HttpAsyncAborter<HttpChannelChild>(this),
+ NeckoTargetHolder(nullptr),
+ mCacheEntryAvailable(false),
+ mAltDataCacheEntryAvailable(false),
+ mSendResumeAt(false),
+ mKeptAlive(false),
+ mIPCActorDeleted(false),
+ mSuspendSent(false),
+ mIsFirstPartOfMultiPart(false),
+ mIsLastPartOfMultiPart(false),
+ mSuspendForWaitCompleteRedirectSetup(false),
+ mRecvOnStartRequestSentCalled(false),
+ mSuspendedByWaitingForPermissionCookie(false),
+ mAlreadyReleased(false) {
+ LOG(("Creating HttpChannelChild @%p\n", this));
+
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+ mLastStatusReported =
+ mChannelCreationTimestamp; // in case we enable the profiler after Init()
+ mAsyncOpenTime = TimeStamp::Now();
+ mEventQ = new ChannelEventQueue(static_cast<nsIHttpChannel*>(this));
+
+ // Ensure that the cookie service is initialized before the first
+ // IPC HTTP channel is created.
+ // We require that the parent cookie service actor exists while
+ // processing HTTP responses.
+ RefPtr<CookieServiceChild> cookieService = CookieServiceChild::GetSingleton();
+}
+
+HttpChannelChild::~HttpChannelChild() {
+ LOG(("Destroying HttpChannelChild @%p\n", this));
+
+ // See HttpChannelChild::Release, HttpChannelChild should be always destroyed
+ // on the main thread.
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (mDoDiagnosticAssertWhenOnStopNotCalledOnDestroy && mAsyncOpenSucceeded &&
+ !mSuccesfullyRedirected && !LoadOnStopRequestCalled()) {
+ bool emptyBgChildQueue, nullBgChild;
+ {
+ MutexAutoLock lock(mBgChildMutex);
+ nullBgChild = !mBgChild;
+ emptyBgChildQueue = !nullBgChild && mBgChild->IsQueueEmpty();
+ }
+
+ uint32_t flags =
+ (mRedirectChannelChild ? 1 << 0 : 0) |
+ (mEventQ->IsEmpty() ? 1 << 1 : 0) | (nullBgChild ? 1 << 2 : 0) |
+ (emptyBgChildQueue ? 1 << 3 : 0) |
+ (LoadOnStartRequestCalled() ? 1 << 4 : 0) |
+ (mBackgroundChildQueueFinalState == BCKCHILD_EMPTY ? 1 << 5 : 0) |
+ (mBackgroundChildQueueFinalState == BCKCHILD_NON_EMPTY ? 1 << 6 : 0) |
+ (mRemoteChannelExistedAtCancel ? 1 << 7 : 0) |
+ (mEverHadBgChildAtAsyncOpen ? 1 << 8 : 0) |
+ (mEverHadBgChildAtConnectParent ? 1 << 9 : 0) |
+ (mCreateBackgroundChannelFailed ? 1 << 10 : 0) |
+ (mBgInitFailCallbackTriggered ? 1 << 11 : 0) |
+ (mCanSendAtCancel ? 1 << 12 : 0) | (!!mSuspendCount ? 1 << 13 : 0) |
+ (!!mCallOnResume ? 1 << 14 : 0);
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "~HttpChannelChild, LoadOnStopRequestCalled()=false, mStatus=0x%08x, "
+ "mActorDestroyReason=%d, 20200717 flags=%u",
+ static_cast<uint32_t>(nsresult(mStatus)),
+ static_cast<int32_t>(mActorDestroyReason ? *mActorDestroyReason : -1),
+ flags);
+ }
+#endif
+
+ mEventQ->NotifyReleasingOwner();
+
+ ReleaseMainThreadOnlyReferences();
+}
+
+void HttpChannelChild::ReleaseMainThreadOnlyReferences() {
+ if (NS_IsMainThread()) {
+ // Already on main thread, let dtor to
+ // take care of releasing references
+ return;
+ }
+
+ NS_ReleaseOnMainThread("HttpChannelChild::mRedirectChannelChild",
+ mRedirectChannelChild.forget());
+}
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpChannelChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) HttpChannelChild::Release() {
+ if (!NS_IsMainThread()) {
+ nsrefcnt count = mRefCnt;
+ nsresult rv = NS_DispatchToMainThread(NewNonOwningRunnableMethod(
+ "HttpChannelChild::Release", this, &HttpChannelChild::Release));
+
+ // Continue Release procedure if failed to dispatch to main thread.
+ if (!NS_WARN_IF(NS_FAILED(rv))) {
+ return count - 1;
+ }
+ }
+
+ nsrefcnt count = --mRefCnt;
+ MOZ_ASSERT(int32_t(count) >= 0, "dup release");
+
+ // Normally we Send_delete in OnStopRequest, but when we need to retain the
+ // remote channel for security info IPDL itself holds 1 reference, so we
+ // Send_delete when refCnt==1. But if !CanSend(), then there's nobody to send
+ // to, so we fall through.
+ if (mKeptAlive && count == 1 && CanSend()) {
+ NS_LOG_RELEASE(this, 1, "HttpChannelChild");
+ mKeptAlive = false;
+ // We send a message to the parent, which calls SendDelete, and then the
+ // child calling Send__delete__() to finally drop the refcount to 0.
+ TrySendDeletingChannel();
+ return 1;
+ }
+
+ if (count == 0) {
+ mRefCnt = 1; /* stabilize */
+
+ // We don't have a listener when AsyncOpen has failed or when this channel
+ // has been sucessfully redirected.
+ if (MOZ_LIKELY(LoadOnStartRequestCalled() && LoadOnStopRequestCalled()) ||
+ !mListener || mAlreadyReleased) {
+ NS_LOG_RELEASE(this, 0, "HttpChannelChild");
+ delete this;
+ return 0;
+ }
+
+ // This ensures that when the refcount goes to 0 again, we don't dispatch
+ // yet another runnable and get in a loop.
+ mAlreadyReleased = true;
+
+ // This makes sure we fulfill the stream listener contract all the time.
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = NS_ERROR_ABORT;
+ }
+
+ // Turn the stabilization refcount into a regular strong reference.
+
+ // 1) We tell refcount logging about the "stabilization" AddRef, which
+ // will become the reference for |channel|. We do this first so that we
+ // don't tell refcount logging that the refcount has dropped to zero, which
+ // it will interpret as destroying the object.
+ NS_LOG_ADDREF(this, 2, "HttpChannelChild", sizeof(*this));
+
+ // 2) We tell refcount logging about the original call to Release().
+ NS_LOG_RELEASE(this, 1, "HttpChannelChild");
+
+ // 3) Finally, we turn the reference into a regular smart pointer.
+ RefPtr<HttpChannelChild> channel = dont_AddRef(this);
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "~HttpChannelChild>DoNotifyListener",
+ [chan = std::move(channel)] { chan->DoNotifyListener(false); }));
+ // If NS_DispatchToMainThread failed then we're going to leak the runnable,
+ // and thus the channel, so there's no need to do anything else.
+ return mRefCnt;
+ }
+
+ NS_LOG_RELEASE(this, count, "HttpChannelChild");
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(HttpChannelChild)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICacheInfoChannel,
+ !mMultiPartID.isSome())
+ NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
+ NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIChildChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelChild)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMultiPartChannel, mMultiPartID.isSome())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIThreadRetargetableRequest,
+ !mMultiPartID.isSome())
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpChannelChild)
+NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::PHttpChannelChild
+//-----------------------------------------------------------------------------
+
+void HttpChannelChild::OnBackgroundChildReady(
+ HttpBackgroundChannelChild* aBgChild) {
+ LOG(("HttpChannelChild::OnBackgroundChildReady [this=%p, bgChild=%p]\n", this,
+ aBgChild));
+ MOZ_ASSERT(OnSocketThread());
+
+ {
+ MutexAutoLock lock(mBgChildMutex);
+
+ // mBgChild might be removed or replaced while the original background
+ // channel is inited on STS thread.
+ if (mBgChild != aBgChild) {
+ return;
+ }
+
+ MOZ_ASSERT(mBgInitFailCallback);
+ mBgInitFailCallback = nullptr;
+ }
+}
+
+void HttpChannelChild::OnBackgroundChildDestroyed(
+ HttpBackgroundChannelChild* aBgChild) {
+ LOG(("HttpChannelChild::OnBackgroundChildDestroyed [this=%p]\n", this));
+ // This function might be called during shutdown phase, so OnSocketThread()
+ // might return false even on STS thread. Use IsOnCurrentThreadInfallible()
+ // to get correct information.
+ MOZ_ASSERT(gSocketTransportService);
+ MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible());
+
+ nsCOMPtr<nsIRunnable> callback;
+ {
+ MutexAutoLock lock(mBgChildMutex);
+
+ // mBgChild might be removed or replaced while the original background
+ // channel is destroyed on STS thread.
+ if (aBgChild != mBgChild) {
+ return;
+ }
+
+ mBgChild = nullptr;
+ callback = std::move(mBgInitFailCallback);
+ }
+
+ if (callback) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mBgInitFailCallbackTriggered = true;
+#endif
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ neckoTarget->Dispatch(callback, NS_DISPATCH_NORMAL);
+ }
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvOnStartRequestSent() {
+ LOG(("HttpChannelChild::RecvOnStartRequestSent [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mRecvOnStartRequestSentCalled);
+
+ mRecvOnStartRequestSentCalled = true;
+
+ if (mSuspendedByWaitingForPermissionCookie) {
+ mSuspendedByWaitingForPermissionCookie = false;
+ mEventQ->Resume();
+ }
+ return IPC_OK();
+}
+
+void HttpChannelChild::ProcessOnStartRequest(
+ const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs,
+ const HttpChannelAltDataStream& aAltData,
+ const TimeStamp& aOnStartRequestStartTime) {
+ LOG(("HttpChannelChild::ProcessOnStartRequest [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ TimeStamp start = TimeStamp::Now();
+
+ mAltDataInputStream = DeserializeIPCStream(aAltData.altDataInputStream());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aResponseHead,
+ aUseResponseHead, aRequestHeaders, aArgs, start]() {
+ TimeDuration delay = TimeStamp::Now() - start;
+ glean::networking::http_content_onstart_delay.AccumulateRawDuration(
+ delay);
+
+ self->OnStartRequest(aResponseHead, aUseResponseHead, aRequestHeaders,
+ aArgs);
+ }));
+}
+
+static void ResourceTimingStructArgsToTimingsStruct(
+ const ResourceTimingStructArgs& aArgs, TimingStruct& aTimings) {
+ aTimings.domainLookupStart = aArgs.domainLookupStart();
+ aTimings.domainLookupEnd = aArgs.domainLookupEnd();
+ aTimings.connectStart = aArgs.connectStart();
+ aTimings.tcpConnectEnd = aArgs.tcpConnectEnd();
+ aTimings.secureConnectionStart = aArgs.secureConnectionStart();
+ aTimings.connectEnd = aArgs.connectEnd();
+ aTimings.requestStart = aArgs.requestStart();
+ aTimings.responseStart = aArgs.responseStart();
+ aTimings.responseEnd = aArgs.responseEnd();
+ aTimings.transactionPending = aArgs.transactionPending();
+}
+
+void HttpChannelChild::OnStartRequest(
+ const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs) {
+ LOG(("HttpChannelChild::OnStartRequest [this=%p]\n", this));
+
+ // If this channel was aborted by ActorDestroy, then there may be other
+ // OnStartRequest/OnStopRequest/OnDataAvailable IPC messages that need to
+ // be handled. In that case we just ignore them to avoid calling the listener
+ // twice.
+ if (LoadOnStartRequestCalled() && mIPCActorDeleted) {
+ return;
+ }
+
+ // Copy arguments only. It's possible to handle other IPC between
+ // OnStartRequest and DoOnStartRequest.
+ mComputedCrossOriginOpenerPolicy = aArgs.openerPolicy();
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aArgs.channelStatus();
+ }
+
+ // Cookies headers should not be visible to the child process
+ MOZ_ASSERT(!aRequestHeaders.HasHeader(nsHttp::Cookie));
+ MOZ_ASSERT(!nsHttpResponseHead(aResponseHead).HasHeader(nsHttp::Set_Cookie));
+
+ if (aUseResponseHead && !mCanceled) {
+ mResponseHead = MakeUnique<nsHttpResponseHead>(aResponseHead);
+ }
+
+ mSecurityInfo = aArgs.securityInfo();
+
+ ipc::MergeParentLoadInfoForwarder(aArgs.loadInfoForwarder(), mLoadInfo);
+
+ mIsFromCache = aArgs.isFromCache();
+ mIsRacing = aArgs.isRacing();
+ mCacheEntryAvailable = aArgs.cacheEntryAvailable();
+ mCacheEntryId = aArgs.cacheEntryId();
+ mCacheFetchCount = aArgs.cacheFetchCount();
+ mProtocolVersion = aArgs.protocolVersion();
+ mCacheExpirationTime = aArgs.cacheExpirationTime();
+ mSelfAddr = aArgs.selfAddr();
+ mPeerAddr = aArgs.peerAddr();
+
+ mRedirectCount = aArgs.redirectCount();
+ mAvailableCachedAltDataType = aArgs.altDataType();
+ StoreDeliveringAltData(aArgs.deliveringAltData());
+ mAltDataLength = aArgs.altDataLength();
+ StoreResolvedByTRR(aArgs.isResolvedByTRR());
+ mEffectiveTRRMode = aArgs.effectiveTRRMode();
+ mTRRSkipReason = aArgs.trrSkipReason();
+
+ SetApplyConversion(aArgs.applyConversion());
+
+ StoreAfterOnStartRequestBegun(true);
+ StoreHasHTTPSRR(aArgs.hasHTTPSRR());
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ mCacheKey = aArgs.cacheKey();
+
+ StoreIsProxyUsed(aArgs.isProxyUsed());
+
+ // replace our request headers with what actually got sent in the parent
+ mRequestHead.SetHeaders(aRequestHeaders);
+
+ // Note: this is where we would notify "http-on-examine-response" observers.
+ // We have deliberately disabled this for child processes (see bug 806753)
+ //
+ // gHttpHandler->OnExamineResponse(this);
+
+ ResourceTimingStructArgsToTimingsStruct(aArgs.timing(), mTransactionTimings);
+
+ nsAutoCString cosString;
+ ClassOfService::ToString(mClassOfService, cosString);
+ if (!mAsyncOpenTime.IsNull() &&
+ !aArgs.timing().transactionPending().IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_ASYNC_OPEN_CHILD_TO_TRANSACTION_PENDING_EXP_MS,
+ cosString, mAsyncOpenTime, aArgs.timing().transactionPending());
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::HttpChannelAsyncOpenToTransactionPending,
+ aArgs.timing().transactionPending() - mAsyncOpenTime);
+ }
+
+ const TimeStamp now = TimeStamp::Now();
+ if (!aArgs.timing().responseStart().IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_RESPONSE_START_PARENT_TO_CONTENT_EXP_MS, cosString,
+ aArgs.timing().responseStart(), now);
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::HttpChannelResponseStartParentToContent,
+ now - aArgs.timing().responseStart());
+ }
+ if (!mOnStartRequestStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::OnStartRequestToContent,
+ now - mOnStartRequestStartTime);
+ }
+
+ StoreAllRedirectsSameOrigin(aArgs.allRedirectsSameOrigin());
+
+ mMultiPartID = aArgs.multiPartID();
+ mIsFirstPartOfMultiPart = aArgs.isFirstPartOfMultiPart();
+ mIsLastPartOfMultiPart = aArgs.isLastPartOfMultiPart();
+
+ if (aArgs.overrideReferrerInfo()) {
+ // The arguments passed to SetReferrerInfoInternal here should mirror the
+ // arguments passed in
+ // nsHttpChannel::ReEvaluateReferrerAfterTrackingStatusIsKnown(), except for
+ // aRespectBeforeConnect which we pass false here since we're intentionally
+ // overriding the referrer after BeginConnect().
+ Unused << SetReferrerInfoInternal(aArgs.overrideReferrerInfo(), false, true,
+ false);
+ }
+
+ if (!aArgs.cookie().IsEmpty()) {
+ SetCookie(aArgs.cookie());
+ }
+
+ if (aArgs.shouldWaitForOnStartRequestSent() &&
+ !mRecvOnStartRequestSentCalled) {
+ LOG((" > pending DoOnStartRequest until RecvOnStartRequestSent\n"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mEventQ->Suspend();
+ mSuspendedByWaitingForPermissionCookie = true;
+ mEventQ->PrependEvent(MakeUnique<NeckoTargetChannelFunctionEvent>(
+ this, [self = UnsafePtr<HttpChannelChild>(this)]() {
+ self->DoOnStartRequest(self);
+ }));
+ return;
+ }
+
+ // Remember whether HTTP3 is supported
+ if (mResponseHead) {
+ mSupportsHTTP3 =
+ nsHttpHandler::IsHttp3SupportedByServer(mResponseHead.get());
+ }
+
+ DoOnStartRequest(this);
+}
+
+void HttpChannelChild::ProcessOnAfterLastPart(const nsresult& aStatus) {
+ LOG(("HttpChannelChild::ProcessOnAfterLastPart [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aStatus]() {
+ self->OnAfterLastPart(aStatus);
+ }));
+}
+
+void HttpChannelChild::OnAfterLastPart(const nsresult& aStatus) {
+ if (LoadOnStopRequestCalled()) {
+ return;
+ }
+ StoreOnStopRequestCalled(true);
+
+ // notify "http-on-stop-connect" observers
+ gHttpHandler->OnStopRequest(this);
+
+ ReleaseListeners();
+
+ // If a preferred alt-data type was set, the parent would hold a reference to
+ // the cache entry in case the child calls openAlternativeOutputStream().
+ // (see nsHttpChannel::OnStopRequest)
+ if (!mPreferredCachedAltDataTypes.IsEmpty()) {
+ mAltDataCacheEntryAvailable = mCacheEntryAvailable;
+ }
+ mCacheEntryAvailable = false;
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ CleanupBackgroundChannel();
+
+ if (mLoadFlags & LOAD_DOCUMENT_URI) {
+ // Keep IPDL channel open, but only for updating security info.
+ // If IPDL is already closed, then do nothing.
+ if (CanSend()) {
+ mKeptAlive = true;
+ SendDocumentChannelCleanup(true);
+ }
+ } else {
+ // The parent process will respond by sending a DeleteSelf message and
+ // making sure not to send any more messages after that.
+ TrySendDeletingChannel();
+ }
+}
+
+void HttpChannelChild::DoOnStartRequest(nsIRequest* aRequest) {
+ nsresult rv;
+
+ LOG(("HttpChannelChild::DoOnStartRequest [this=%p]\n", this));
+
+ // We handle all the listener chaining before OnStartRequest at this moment.
+ // Prevent additional listeners being added to the chain after the request
+ // as started.
+ StoreTracingEnabled(false);
+
+ // mListener could be null if the redirect setup is not completed.
+ MOZ_ASSERT(mListener || LoadOnStartRequestCalled());
+ if (!mListener) {
+ Cancel(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (mListener) {
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ StoreOnStartRequestCalled(true);
+ rv = listener->OnStartRequest(aRequest);
+ } else {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ StoreOnStartRequestCalled(true);
+
+ if (NS_FAILED(rv)) {
+ CancelWithReason(rv, "HttpChannelChild listener->OnStartRequest failed"_ns);
+ return;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = DoApplyContentConversions(mListener, getter_AddRefs(listener), nullptr);
+ if (NS_FAILED(rv)) {
+ CancelWithReason(rv,
+ "HttpChannelChild DoApplyContentConversions failed"_ns);
+ } else if (listener) {
+ mListener = listener;
+ mCompressListener = listener;
+ }
+}
+
+void HttpChannelChild::ProcessOnTransportAndData(
+ const nsresult& aChannelStatus, const nsresult& aTransportStatus,
+ const uint64_t& aOffset, const uint32_t& aCount, const nsACString& aData,
+ const TimeStamp& aOnDataAvailableStartTime) {
+ LOG(("HttpChannelChild::ProcessOnTransportAndData [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ mEventQ->RunOrEnqueue(new ChannelFunctionEvent(
+ [self = UnsafePtr<HttpChannelChild>(this)]() {
+ return self->GetODATarget();
+ },
+ [self = UnsafePtr<HttpChannelChild>(this), aChannelStatus,
+ aTransportStatus, aOffset, aCount, aData = nsCString(aData),
+ aOnDataAvailableStartTime]() {
+ self->mOnDataAvailableStartTime = aOnDataAvailableStartTime;
+ self->OnTransportAndData(aChannelStatus, aTransportStatus, aOffset,
+ aCount, aData);
+ }));
+}
+
+void HttpChannelChild::OnTransportAndData(const nsresult& aChannelStatus,
+ const nsresult& aTransportStatus,
+ const uint64_t& aOffset,
+ const uint32_t& aCount,
+ const nsACString& aData) {
+ LOG(("HttpChannelChild::OnTransportAndData [this=%p]\n", this));
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aChannelStatus;
+ }
+
+ if (mCanceled || NS_FAILED(mStatus)) {
+ return;
+ }
+
+ if (!mOnDataAvailableStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::OnDataAvailableToContent,
+ TimeStamp::Now() - mOnDataAvailableStartTime);
+ }
+
+ // Hold queue lock throughout all three calls, else we might process a later
+ // necko msg in between them.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ int64_t progressMax;
+ if (NS_FAILED(GetContentLength(&progressMax))) {
+ progressMax = -1;
+ }
+
+ const int64_t progress = aOffset + aCount;
+
+ // OnTransportAndData will be run on retargeted thread if applicable, however
+ // OnStatus/OnProgress event can only be fired on main thread. We need to
+ // dispatch the status/progress event handling back to main thread with the
+ // appropriate event target for networking.
+ if (NS_IsMainThread()) {
+ DoOnStatus(this, aTransportStatus);
+ DoOnProgress(this, progress, progressMax);
+ } else {
+ RefPtr<HttpChannelChild> self = this;
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ DebugOnly<nsresult> rv = neckoTarget->Dispatch(
+ NS_NewRunnableFunction(
+ "net::HttpChannelChild::OnTransportAndData",
+ [self, aTransportStatus, progress, progressMax]() {
+ self->DoOnStatus(self, aTransportStatus);
+ self->DoOnProgress(self, progress, progressMax);
+ }),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // OnDataAvailable
+ //
+ // NOTE: the OnDataAvailable contract requires the client to read all the data
+ // in the inputstream. This code relies on that ('data' will go away after
+ // this function). Apparently the previous, non-e10s behavior was to actually
+ // support only reading part of the data, allowing later calls to read the
+ // rest.
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv =
+ NS_NewByteInputStream(getter_AddRefs(stringStream),
+ Span(aData).To(aCount), NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ CancelWithReason(rv, "HttpChannelChild NS_NewByteInputStream failed"_ns);
+ return;
+ }
+
+ DoOnDataAvailable(this, stringStream, aOffset, aCount);
+ stringStream->Close();
+
+ // TODO: Bug 1523916 backpressure needs to take into account if the data is
+ // coming from the main process or from the socket process via PBackground.
+ if (NeedToReportBytesRead()) {
+ mUnreportBytesRead += aCount;
+ if (mUnreportBytesRead >= gHttpHandler->SendWindowSize() >> 2) {
+ if (NS_IsMainThread()) {
+ Unused << SendBytesRead(mUnreportBytesRead);
+ } else {
+ // PHttpChannel connects to the main thread
+ RefPtr<HttpChannelChild> self = this;
+ int32_t bytesRead = mUnreportBytesRead;
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ DebugOnly<nsresult> rv = neckoTarget->Dispatch(
+ NS_NewRunnableFunction("net::HttpChannelChild::SendBytesRead",
+ [self, bytesRead]() {
+ Unused << self->SendBytesRead(bytesRead);
+ }),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ mUnreportBytesRead = 0;
+ }
+ }
+}
+
+bool HttpChannelChild::NeedToReportBytesRead() {
+ if (mCacheNeedToReportBytesReadInitialized) {
+ return mNeedToReportBytesRead;
+ }
+
+ // Might notify parent for partial cache, and the IPC message is ignored by
+ // parent.
+ int64_t contentLength = -1;
+ if (gHttpHandler->SendWindowSize() == 0 || mIsFromCache ||
+ NS_FAILED(GetContentLength(&contentLength)) ||
+ contentLength < gHttpHandler->SendWindowSize()) {
+ mNeedToReportBytesRead = false;
+ }
+
+ mCacheNeedToReportBytesReadInitialized = true;
+ return mNeedToReportBytesRead;
+}
+
+void HttpChannelChild::DoOnStatus(nsIRequest* aRequest, nsresult status) {
+ LOG(("HttpChannelChild::DoOnStatus [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mCanceled) return;
+
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink) GetCallback(mProgressSink);
+
+ // block status/progress after Cancel or OnStopRequest has been called,
+ // or if channel has LOAD_BACKGROUND set.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && LoadIsPending() &&
+ !(mLoadFlags & LOAD_BACKGROUND)) {
+ nsAutoCString host;
+ mURI->GetHost(host);
+ mProgressSink->OnStatus(aRequest, status,
+ NS_ConvertUTF8toUTF16(host).get());
+ }
+}
+
+void HttpChannelChild::DoOnProgress(nsIRequest* aRequest, int64_t progress,
+ int64_t progressMax) {
+ LOG(("HttpChannelChild::DoOnProgress [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mCanceled) return;
+
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink) GetCallback(mProgressSink);
+
+ // block status/progress after Cancel or OnStopRequest has been called,
+ // or if channel has LOAD_BACKGROUND set.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && LoadIsPending()) {
+ // OnProgress
+ //
+ if (progress > 0) {
+ mProgressSink->OnProgress(aRequest, progress, progressMax);
+ }
+ }
+
+ // mOnProgressEventSent indicates we have flushed all the
+ // progress events on the main thread. It is needed if
+ // we do not want to dispatch OnDataFinished before sending
+ // all of the progress updates.
+ if (progress == progressMax) {
+ mOnProgressEventSent = true;
+ }
+}
+
+void HttpChannelChild::DoOnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ AUTO_PROFILER_LABEL("HttpChannelChild::DoOnDataAvailable", NETWORK);
+ LOG(("HttpChannelChild::DoOnDataAvailable [this=%p]\n", this));
+ if (mCanceled) return;
+
+ if (mListener) {
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ nsresult rv = listener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
+ if (NS_FAILED(rv)) {
+ CancelOnMainThread(rv, "HttpChannelChild OnDataAvailable failed"_ns);
+ }
+ }
+}
+
+void HttpChannelChild::SendOnDataFinished(const nsresult& aChannelStatus) {
+ LOG(("HttpChannelChild::SendOnDataFinished [this=%p]\n", this));
+
+ if (mCanceled) return;
+
+ // we need to ensure we OnDataFinished only after all the progress
+ // updates are dispatched on the main thread
+ if (StaticPrefs::network_send_OnDataFinished_after_progress_updates() &&
+ !mOnProgressEventSent) {
+ return;
+ }
+
+ if (mListener) {
+ nsCOMPtr<nsIThreadRetargetableStreamListener> omtEventListener =
+ do_QueryInterface(mListener);
+ if (omtEventListener) {
+ LOG(
+ ("HttpChannelChild::SendOnDataFinished sending data end "
+ "notification[this=%p]\n",
+ this));
+ // We want to calculate the delta time between this call and
+ // ProcessOnStopRequest. Complicating things is that OnStopRequest
+ // could come first, and that it will run on a different thread, so
+ // we need to synchronize and lock data.
+ omtEventListener->OnDataFinished(aChannelStatus);
+ } else {
+ LOG(
+ ("HttpChannelChild::SendOnDataFinished missing "
+ "nsIThreadRetargetableStreamListener "
+ "implementation [this=%p]\n",
+ this));
+ }
+ }
+}
+
+class RecordStopRequestDelta final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RecordStopRequestDelta);
+
+ TimeStamp mOnStopRequestTime;
+ TimeStamp mOnDataFinishedTime;
+
+ private:
+ ~RecordStopRequestDelta() {
+ if (mOnDataFinishedTime.IsNull() || mOnStopRequestTime.IsNull()) {
+ return;
+ }
+
+ TimeDuration delta = (mOnStopRequestTime - mOnDataFinishedTime);
+ if (delta.ToMilliseconds() < 0) {
+ // Because Telemetry can't handle negatives
+ delta = -delta;
+ glean::networking::http_content_ondatafinished_to_onstop_delay_negative
+ .AccumulateRawDuration(delta);
+ } else {
+ glean::networking::http_content_ondatafinished_to_onstop_delay
+ .AccumulateRawDuration(delta);
+ }
+ }
+};
+
+void HttpChannelChild::ProcessOnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers,
+ nsTArray<ConsoleReportCollected>&& aConsoleReports, bool aFromSocketProcess,
+ const TimeStamp& aOnStopRequestStartTime) {
+ LOG(
+ ("HttpChannelChild::ProcessOnStopRequest [this=%p, "
+ "aFromSocketProcess=%d]\n",
+ this, aFromSocketProcess));
+ MOZ_ASSERT(OnSocketThread());
+ { // assign some of the members that would be accessed by the listeners
+ // upon getting OnDataFinished notications
+ MutexAutoLock lock(mOnDataFinishedMutex);
+ mTransferSize = aTiming.transferSize();
+ mEncodedBodySize = aTiming.encodedBodySize();
+ }
+
+ RefPtr<RecordStopRequestDelta> timing;
+ TimeStamp start = TimeStamp::Now();
+ if (StaticPrefs::network_send_OnDataFinished()) {
+ timing = new RecordStopRequestDelta;
+ mEventQ->RunOrEnqueue(new ChannelFunctionEvent(
+ [self = UnsafePtr<HttpChannelChild>(this)]() {
+ return self->GetODATarget();
+ },
+ [self = UnsafePtr<HttpChannelChild>(this), status = aChannelStatus,
+ start, timing]() {
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration delay = now - start;
+ glean::networking::http_content_ondatafinished_delay
+ .AccumulateRawDuration(delay);
+ timing->mOnDataFinishedTime = now;
+ self->SendOnDataFinished(status);
+ }));
+ }
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aChannelStatus, aTiming,
+ aResponseTrailers,
+ consoleReports = CopyableTArray{aConsoleReports.Clone()},
+ aFromSocketProcess, start, timing]() mutable {
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration delay = now - start;
+ glean::networking::http_content_onstop_delay.AccumulateRawDuration(
+ delay);
+ if (timing) {
+ timing->mOnStopRequestTime = now;
+ }
+ self->OnStopRequest(aChannelStatus, aTiming, aResponseTrailers);
+ if (!aFromSocketProcess) {
+ self->DoOnConsoleReport(std::move(consoleReports));
+ self->ContinueOnStopRequest();
+ }
+ }));
+}
+
+void HttpChannelChild::ProcessOnConsoleReport(
+ nsTArray<ConsoleReportCollected>&& aConsoleReports) {
+ LOG(("HttpChannelChild::ProcessOnConsoleReport [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<HttpChannelChild>(this),
+ consoleReports = CopyableTArray{aConsoleReports.Clone()}]() mutable {
+ self->DoOnConsoleReport(std::move(consoleReports));
+ self->ContinueOnStopRequest();
+ }));
+}
+
+void HttpChannelChild::DoOnConsoleReport(
+ nsTArray<ConsoleReportCollected>&& aConsoleReports) {
+ if (aConsoleReports.IsEmpty()) {
+ return;
+ }
+
+ for (ConsoleReportCollected& report : aConsoleReports) {
+ if (report.propertiesFile() <
+ nsContentUtils::PropertiesFile::PropertiesFile_COUNT) {
+ AddConsoleReport(report.errorFlags(), report.category(),
+ nsContentUtils::PropertiesFile(report.propertiesFile()),
+ report.sourceFileURI(), report.lineNumber(),
+ report.columnNumber(), report.messageName(),
+ report.stringParams());
+ }
+ }
+ MaybeFlushConsoleReports();
+}
+
+void HttpChannelChild::RecordChannelCompletionDurationForEarlyHint() {
+ if (!mLoadGroup) {
+ return;
+ }
+
+ uint32_t earlyHintType = 0;
+ nsCOMPtr<nsIRequest> req;
+ Unused << mLoadGroup->GetDefaultLoadRequest(getter_AddRefs(req));
+ if (nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(req)) {
+ Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType);
+ }
+
+ if (!earlyHintType) {
+ return;
+ }
+
+ nsAutoCString earlyHintKey;
+ if (mIsFromCache) {
+ earlyHintKey.Append("cache_"_ns);
+ } else {
+ earlyHintKey.Append("net_"_ns);
+ }
+ if (earlyHintType & LinkStyle::ePRECONNECT) {
+ earlyHintKey.Append("preconnect_"_ns);
+ }
+ if (earlyHintType & LinkStyle::ePRELOAD) {
+ earlyHintKey.Append("preload_"_ns);
+ earlyHintKey.Append(mEarlyHintPreloaderId ? "1"_ns : "0"_ns);
+ }
+
+ Telemetry::AccumulateTimeDelta(Telemetry::EH_PERF_CHANNEL_COMPLETION_TIME,
+ earlyHintKey, mAsyncOpenTime,
+ TimeStamp::Now());
+}
+
+void HttpChannelChild::OnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers) {
+ LOG(("HttpChannelChild::OnStopRequest [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aChannelStatus)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If this channel was aborted by ActorDestroy, then there may be other
+ // OnStartRequest/OnStopRequest/OnDataAvailable IPC messages that need to
+ // be handled. In that case we just ignore them to avoid calling the listener
+ // twice.
+ if (LoadOnStopRequestCalled() && mIPCActorDeleted) {
+ return;
+ }
+
+ nsCOMPtr<nsICompressConvStats> conv = do_QueryInterface(mCompressListener);
+ if (conv) {
+ conv->GetDecodedDataLength(&mDecodedBodySize);
+ }
+
+ ResourceTimingStructArgsToTimingsStruct(aTiming, mTransactionTimings);
+
+ // Do not overwrite or adjust the original mAsyncOpenTime by timing.fetchStart
+ // We must use the original child process time in order to account for child
+ // side work and IPC transit overhead.
+ // XXX: This depends on TimeStamp being equivalent across processes.
+ // This is true for modern hardware but for older platforms it is not always
+ // true.
+
+ mRedirectStartTimeStamp = aTiming.redirectStart();
+ mRedirectEndTimeStamp = aTiming.redirectEnd();
+ // mTransferSize and mEncodedBodySize are set in ProcessOnStopRequest
+ // TODO: check if we need to move assignments of other members to
+ // ProcessOnStopRequest
+
+ mCacheReadStart = aTiming.cacheReadStart();
+ mCacheReadEnd = aTiming.cacheReadEnd();
+
+ const TimeStamp now = TimeStamp::Now();
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_STOP,
+ mLastStatusReported, now, mTransferSize, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())));
+ }
+
+ RecordChannelCompletionDurationForEarlyHint();
+
+ TimeDuration channelCompletionDuration = now - mAsyncOpenTime;
+ if (mIsFromCache) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::HttpChannelCompletion_Cache,
+ channelCompletionDuration);
+ } else {
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::HttpChannelCompletion_Network,
+ channelCompletionDuration);
+ }
+ PerfStats::RecordMeasurement(PerfStats::Metric::HttpChannelCompletion,
+ channelCompletionDuration);
+
+ if (!aTiming.responseEnd().IsNull()) {
+ nsAutoCString cosString;
+ ClassOfService::ToString(mClassOfService, cosString);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_RESPONSE_END_PARENT_TO_CONTENT_MS, cosString,
+ aTiming.responseEnd(), now);
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::HttpChannelResponseEndParentToContent,
+ now - aTiming.responseEnd());
+ }
+
+ if (!mOnStopRequestStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::OnStopRequestToContent,
+ now - mOnStopRequestStartTime);
+ }
+
+ mResponseTrailers = MakeUnique<nsHttpHeaderArray>(aResponseTrailers);
+
+ DoPreOnStopRequest(aChannelStatus);
+
+ { // We must flush the queue before we Send__delete__
+ // (although we really shouldn't receive any msgs after OnStop),
+ // so make sure this goes out of scope before then.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ DoOnStopRequest(this, aChannelStatus);
+ // DoOnStopRequest() calls ReleaseListeners()
+ }
+}
+
+void HttpChannelChild::ContinueOnStopRequest() {
+ // If we're a multi-part stream, then don't cleanup yet, and we'll do so
+ // in OnAfterLastPart.
+ if (mMultiPartID) {
+ LOG(
+ ("HttpChannelChild::OnStopRequest - Expecting future parts on a "
+ "multipart channel postpone cleaning up."));
+ return;
+ }
+
+ CollectMixedContentTelemetry();
+
+ CleanupBackgroundChannel();
+
+ // If there is a possibility we might want to write alt data to the cache
+ // entry, we keep the channel alive. We still send the DocumentChannelCleanup
+ // message but request the cache entry to be kept by the parent.
+ // If the channel has failed, the cache entry is in a non-writtable state and
+ // we want to release it to not block following consumers.
+ if (NS_SUCCEEDED(mStatus) && !mPreferredCachedAltDataTypes.IsEmpty()) {
+ mKeptAlive = true;
+ SendDocumentChannelCleanup(false); // don't clear cache entry
+ return;
+ }
+
+ if (mLoadFlags & LOAD_DOCUMENT_URI) {
+ // Keep IPDL channel open, but only for updating security info.
+ // If IPDL is already closed, then do nothing.
+ if (CanSend()) {
+ mKeptAlive = true;
+ SendDocumentChannelCleanup(true);
+ }
+ } else {
+ // The parent process will respond by sending a DeleteSelf message and
+ // making sure not to send any more messages after that.
+ TrySendDeletingChannel();
+ }
+}
+
+void HttpChannelChild::DoPreOnStopRequest(nsresult aStatus) {
+ AUTO_PROFILER_LABEL("HttpChannelChild::DoPreOnStopRequest", NETWORK);
+ LOG(("HttpChannelChild::DoPreOnStopRequest [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aStatus)));
+ StoreIsPending(false);
+
+ MaybeReportTimingData();
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ CollectOMTTelemetry();
+}
+
+void HttpChannelChild::CollectOMTTelemetry() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Only collect telemetry for HTTP channel that is loaded successfully and
+ // completely.
+ if (mCanceled || NS_FAILED(mStatus)) {
+ return;
+ }
+
+ // Use content policy type to accumulate data by usage.
+ nsAutoCString key(
+ NS_CP_ContentTypeName(mLoadInfo->InternalContentPolicyType()));
+
+ Telemetry::AccumulateCategoricalKeyed(
+ key, static_cast<LABELS_HTTP_CHILD_OMT_STATS>(mOMTResult));
+}
+
+void HttpChannelChild::CollectMixedContentTelemetry() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsContentPolicyType internalLoadType;
+ mLoadInfo->GetInternalContentPolicyType(&internalLoadType);
+ bool statusIsSuccess = NS_SUCCEEDED(mStatus);
+ RefPtr<Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+ if (!doc) {
+ return;
+ }
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE ||
+ internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD) {
+ if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentUpgradedImageSuccess
+ : eUseCounter_custom_MixedContentUpgradedImageFailure);
+ } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentNotUpgradedImageSuccess
+ : eUseCounter_custom_MixedContentNotUpgradedImageFailure);
+ }
+ return;
+ }
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_VIDEO) {
+ if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentUpgradedVideoSuccess
+ : eUseCounter_custom_MixedContentUpgradedVideoFailure);
+ } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentNotUpgradedVideoSuccess
+ : eUseCounter_custom_MixedContentNotUpgradedVideoFailure);
+ }
+ return;
+ }
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_AUDIO) {
+ if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentUpgradedAudioSuccess
+ : eUseCounter_custom_MixedContentUpgradedAudioFailure);
+ } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentNotUpgradedAudioSuccess
+ : eUseCounter_custom_MixedContentNotUpgradedAudioFailure);
+ }
+ }
+}
+
+void HttpChannelChild::DoOnStopRequest(nsIRequest* aRequest,
+ nsresult aChannelStatus) {
+ AUTO_PROFILER_LABEL("HttpChannelChild::DoOnStopRequest", NETWORK);
+ LOG(("HttpChannelChild::DoOnStopRequest [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!LoadIsPending());
+
+ auto checkForBlockedContent = [&]() {
+ // NB: We use aChannelStatus here instead of mStatus because if there was an
+ // nsCORSListenerProxy on this request, it will override the tracking
+ // protection's return value.
+ if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
+ aChannelStatus) ||
+ aChannelStatus == NS_ERROR_MALWARE_URI ||
+ aChannelStatus == NS_ERROR_UNWANTED_URI ||
+ aChannelStatus == NS_ERROR_BLOCKED_URI ||
+ aChannelStatus == NS_ERROR_HARMFUL_URI ||
+ aChannelStatus == NS_ERROR_PHISHING_URI) {
+ nsCString list, provider, fullhash;
+
+ nsresult rv = GetMatchedList(list);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = GetMatchedProvider(provider);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = GetMatchedFullHash(fullhash);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ UrlClassifierCommon::SetBlockedContent(this, aChannelStatus, list,
+ provider, fullhash);
+ }
+ };
+ checkForBlockedContent();
+
+ MaybeLogCOEPError(aChannelStatus);
+
+ // See bug 1587686. If the redirect setup is not completed, the post-redirect
+ // channel will be not opened and mListener will be null.
+ MOZ_ASSERT(mListener || !LoadWasOpened());
+ if (!mListener) {
+ return;
+ }
+
+ MOZ_ASSERT(!LoadOnStopRequestCalled(),
+ "We should not call OnStopRequest twice");
+
+ if (mListener) {
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ StoreOnStopRequestCalled(true);
+ listener->OnStopRequest(aRequest, mStatus);
+ }
+ StoreOnStopRequestCalled(true);
+
+ // If we're a multi-part stream, then don't cleanup yet, and we'll do so
+ // in OnAfterLastPart.
+ if (mMultiPartID) {
+ LOG(
+ ("HttpChannelChild::DoOnStopRequest - Expecting future parts on a "
+ "multipart channel not releasing listeners."));
+ StoreOnStopRequestCalled(false);
+ StoreOnStartRequestCalled(false);
+ return;
+ }
+
+ // notify "http-on-stop-connect" observers
+ gHttpHandler->OnStopRequest(this);
+
+ ReleaseListeners();
+
+ // If a preferred alt-data type was set, the parent would hold a reference to
+ // the cache entry in case the child calls openAlternativeOutputStream().
+ // (see nsHttpChannel::OnStopRequest)
+ if (!mPreferredCachedAltDataTypes.IsEmpty()) {
+ mAltDataCacheEntryAvailable = mCacheEntryAvailable;
+ }
+ mCacheEntryAvailable = false;
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+}
+
+void HttpChannelChild::ProcessOnProgress(const int64_t& aProgress,
+ const int64_t& aProgressMax) {
+ MOZ_ASSERT(OnSocketThread());
+ LOG(("HttpChannelChild::ProcessOnProgress [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<HttpChannelChild>(this), aProgress, aProgressMax]() {
+ AutoEventEnqueuer ensureSerialDispatch(self->mEventQ);
+ self->DoOnProgress(self, aProgress, aProgressMax);
+ }));
+}
+
+void HttpChannelChild::ProcessOnStatus(const nsresult& aStatus) {
+ MOZ_ASSERT(OnSocketThread());
+ LOG(("HttpChannelChild::ProcessOnStatus [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aStatus]() {
+ AutoEventEnqueuer ensureSerialDispatch(self->mEventQ);
+ self->DoOnStatus(self, aStatus);
+ }));
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvFailedAsyncOpen(
+ const nsresult& aStatus) {
+ LOG(("HttpChannelChild::RecvFailedAsyncOpen [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aStatus]() {
+ self->FailedAsyncOpen(aStatus);
+ }));
+ return IPC_OK();
+}
+
+// We need to have an implementation of this function just so that we can keep
+// all references to mCallOnResume of type HttpChannelChild: it's not OK in C++
+// to set a member function ptr to a base class function.
+void HttpChannelChild::HandleAsyncAbort() {
+ HttpAsyncAborter<HttpChannelChild>::HandleAsyncAbort();
+
+ // Ignore all the messages from background channel after channel aborted.
+ CleanupBackgroundChannel();
+}
+
+void HttpChannelChild::FailedAsyncOpen(const nsresult& status) {
+ LOG(("HttpChannelChild::FailedAsyncOpen [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(status)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Might be called twice in race condition in theory.
+ // (one by RecvFailedAsyncOpen, another by
+ // HttpBackgroundChannelChild::ActorFailed)
+ if (LoadOnStartRequestCalled()) {
+ return;
+ }
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = status;
+ }
+
+ // We're already being called from IPDL, therefore already "async"
+ HandleAsyncAbort();
+
+ if (CanSend()) {
+ TrySendDeletingChannel();
+ }
+}
+
+void HttpChannelChild::CleanupBackgroundChannel() {
+ MutexAutoLock lock(mBgChildMutex);
+
+ AUTO_PROFILER_LABEL("HttpChannelChild::CleanupBackgroundChannel", NETWORK);
+ LOG(("HttpChannelChild::CleanupBackgroundChannel [this=%p bgChild=%p]\n",
+ this, mBgChild.get()));
+
+ mBgInitFailCallback = nullptr;
+
+ if (!mBgChild) {
+ return;
+ }
+
+ RefPtr<HttpBackgroundChannelChild> bgChild = std::move(mBgChild);
+
+ MOZ_RELEASE_ASSERT(gSocketTransportService);
+ if (!OnSocketThread()) {
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod("HttpBackgroundChannelChild::OnChannelClosed",
+ bgChild,
+ &HttpBackgroundChannelChild::OnChannelClosed),
+ NS_DISPATCH_NORMAL);
+ } else {
+ bgChild->OnChannelClosed();
+ }
+}
+
+void HttpChannelChild::DoNotifyListenerCleanup() {
+ LOG(("HttpChannelChild::DoNotifyListenerCleanup [this=%p]\n", this));
+}
+
+void HttpChannelChild::DoAsyncAbort(nsresult aStatus) {
+ Unused << AsyncAbort(aStatus);
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvDeleteSelf() {
+ LOG(("HttpChannelChild::RecvDeleteSelf [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // The redirection is vetoed. No need to suspend the event queue.
+ if (mSuspendForWaitCompleteRedirectSetup) {
+ mSuspendForWaitCompleteRedirectSetup = false;
+ mEventQ->Resume();
+ }
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<HttpChannelChild>(this)]() { self->DeleteSelf(); }));
+ return IPC_OK();
+}
+
+void HttpChannelChild::DeleteSelf() { Send__delete__(this); }
+
+void HttpChannelChild::NotifyOrReleaseListeners(nsresult rv) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_SUCCEEDED(rv) ||
+ (LoadOnStartRequestCalled() && LoadOnStopRequestCalled())) {
+ ReleaseListeners();
+ return;
+ }
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = rv;
+ }
+
+ // This is enough what we need. Undelivered notifications will be pushed.
+ // DoNotifyListener ensures the call to ReleaseListeners when done.
+ DoNotifyListener();
+}
+
+void HttpChannelChild::DoNotifyListener(bool aUseEventQueue) {
+ LOG(("HttpChannelChild::DoNotifyListener this=%p", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // In case nsHttpChannel::OnStartRequest wasn't called (e.g. due to flag
+ // LOAD_ONLY_IF_MODIFIED) we want to set LoadAfterOnStartRequestBegun() to
+ // true before notifying listener.
+ if (!LoadAfterOnStartRequestBegun()) {
+ StoreAfterOnStartRequestBegun(true);
+ }
+
+ if (mListener && !LoadOnStartRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ // avoid reentrancy bugs by setting this now
+ StoreOnStartRequestCalled(true);
+ listener->OnStartRequest(this);
+ }
+ StoreOnStartRequestCalled(true);
+
+ if (aUseEventQueue) {
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this)] {
+ self->ContinueDoNotifyListener();
+ }));
+ } else {
+ ContinueDoNotifyListener();
+ }
+}
+
+void HttpChannelChild::ContinueDoNotifyListener() {
+ LOG(("HttpChannelChild::ContinueDoNotifyListener this=%p", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Make sure IsPending is set to false. At this moment we are done from
+ // the point of view of our consumer and we have to report our self
+ // as not-pending.
+ StoreIsPending(false);
+
+ if (mListener && !LoadOnStopRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ StoreOnStopRequestCalled(true);
+ listener->OnStopRequest(this, mStatus);
+ }
+ StoreOnStopRequestCalled(true);
+
+ // notify "http-on-stop-request" observers
+ gHttpHandler->OnStopRequest(this);
+
+ // This channel has finished its job, potentially release any tail-blocked
+ // requests with this.
+ RemoveAsNonTailRequest();
+
+ // We have to make sure to drop the references to listeners and callbacks
+ // no longer needed.
+ ReleaseListeners();
+
+ DoNotifyListenerCleanup();
+
+ // If this is a navigation, then we must let the docshell flush the reports
+ // to the console later. The LoadDocument() is pointing at the detached
+ // document that started the navigation. We want to show the reports on the
+ // new document. Otherwise the console is wiped and the user never sees
+ // the information.
+ if (!IsNavigation()) {
+ if (mLoadGroup) {
+ FlushConsoleReports(mLoadGroup);
+ } else {
+ RefPtr<dom::Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+ FlushConsoleReports(doc);
+ }
+ }
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvReportSecurityMessage(
+ const nsAString& messageTag, const nsAString& messageCategory) {
+ DebugOnly<nsresult> rv = AddSecurityMessage(messageTag, messageCategory);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvRedirect1Begin(
+ const uint32_t& aRegistrarId, nsIURI* aNewUri,
+ const uint32_t& aNewLoadFlags, const uint32_t& aRedirectFlags,
+ const ParentLoadInfoForwarderArgs& aLoadInfoForwarder,
+ const nsHttpResponseHead& aResponseHead,
+ nsITransportSecurityInfo* aSecurityInfo, const uint64_t& aChannelId,
+ const NetAddr& aOldPeerAddr, const ResourceTimingStructArgs& aTiming) {
+ // TODO: handle security info
+ LOG(("HttpChannelChild::RecvRedirect1Begin [this=%p]\n", this));
+ // We set peer address of child to the old peer,
+ // Then it will be updated to new peer in OnStartRequest
+ mPeerAddr = aOldPeerAddr;
+
+ // Cookies headers should not be visible to the child process
+ MOZ_ASSERT(!nsHttpResponseHead(aResponseHead).HasHeader(nsHttp::Set_Cookie));
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aRegistrarId,
+ newUri = RefPtr{aNewUri}, aNewLoadFlags, aRedirectFlags,
+ aLoadInfoForwarder, aResponseHead,
+ aSecurityInfo = nsCOMPtr{aSecurityInfo}, aChannelId, aTiming]() {
+ self->Redirect1Begin(aRegistrarId, newUri, aNewLoadFlags,
+ aRedirectFlags, aLoadInfoForwarder, aResponseHead,
+ aSecurityInfo, aChannelId, aTiming);
+ }));
+ return IPC_OK();
+}
+
+nsresult HttpChannelChild::SetupRedirect(nsIURI* uri,
+ const nsHttpResponseHead* responseHead,
+ const uint32_t& redirectFlags,
+ nsIChannel** outChannel) {
+ LOG(("HttpChannelChild::SetupRedirect [this=%p]\n", this));
+
+ if (mCanceled) {
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(uri, redirectFlags);
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel), uri, redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We won't get OnStartRequest, set cookies here.
+ mResponseHead = MakeUnique<nsHttpResponseHead>(*responseHead);
+
+ bool rewriteToGET = HttpBaseChannel::ShouldRewriteRedirectToGET(
+ mResponseHead->Status(), mRequestHead.ParsedMethod());
+
+ rv = SetupReplacementChannel(uri, newChannel, !rewriteToGET, redirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRedirectChannelChild = do_QueryInterface(newChannel);
+ newChannel.forget(outChannel);
+
+ return NS_OK;
+}
+
+void HttpChannelChild::Redirect1Begin(
+ const uint32_t& registrarId, nsIURI* newOriginalURI,
+ const uint32_t& newLoadFlags, const uint32_t& redirectFlags,
+ const ParentLoadInfoForwarderArgs& loadInfoForwarder,
+ const nsHttpResponseHead& responseHead,
+ nsITransportSecurityInfo* securityInfo, const uint64_t& channelId,
+ const ResourceTimingStructArgs& timing) {
+ nsresult rv;
+
+ LOG(("HttpChannelChild::Redirect1Begin [this=%p]\n", this));
+
+ MOZ_ASSERT(newOriginalURI, "newOriginalURI should not be null");
+
+ ipc::MergeParentLoadInfoForwarder(loadInfoForwarder, mLoadInfo);
+ ResourceTimingStructArgsToTimingsStruct(timing, mTransactionTimings);
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+ nsAutoCString contentType;
+ responseHead.ContentType(contentType);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId,
+ NetworkLoadType::LOAD_REDIRECT, mLastStatusReported, TimeStamp::Now(),
+ 0, kCacheUnknown, mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())), newOriginalURI,
+ redirectFlags, channelId);
+ }
+
+ mSecurityInfo = securityInfo;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = SetupRedirect(newOriginalURI, &responseHead, redirectFlags,
+ getter_AddRefs(newChannel));
+
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ALWAYS_SUCCEEDS(newChannel->SetLoadFlags(newLoadFlags));
+
+ if (mRedirectChannelChild) {
+ // Set the channelId allocated in parent to the child instance
+ nsCOMPtr<nsIHttpChannel> httpChannel =
+ do_QueryInterface(mRedirectChannelChild);
+ if (httpChannel) {
+ rv = httpChannel->SetChannelId(channelId);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ mRedirectChannelChild->ConnectParent(registrarId);
+ }
+
+ nsCOMPtr<nsISerialEventTarget> target = GetNeckoTarget();
+ MOZ_ASSERT(target);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags,
+ target);
+ }
+
+ if (NS_FAILED(rv)) OnRedirectVerifyCallback(rv);
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvRedirect3Complete() {
+ LOG(("HttpChannelChild::RecvRedirect3Complete [this=%p]\n", this));
+ nsCOMPtr<nsIChannel> redirectChannel =
+ do_QueryInterface(mRedirectChannelChild);
+ MOZ_ASSERT(redirectChannel);
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), redirectChannel]() {
+ nsresult rv = NS_OK;
+ Unused << self->GetStatus(&rv);
+ if (NS_FAILED(rv)) {
+ // Pre-redirect channel was canceled. Call |HandleAsyncAbort|, so
+ // mListener's OnStart/StopRequest can be called. Nothing else will
+ // trigger these notification after this point.
+ // We do this before |CompleteRedirectSetup|, so post-redirect channel
+ // stays unopened and we also make sure that OnStart/StopRequest won't
+ // be called twice.
+ self->HandleAsyncAbort();
+
+ nsCOMPtr<nsIHttpChannelChild> chan =
+ do_QueryInterface(redirectChannel);
+ RefPtr<HttpChannelChild> httpChannelChild =
+ static_cast<HttpChannelChild*>(chan.get());
+ if (httpChannelChild) {
+ // For sending an IPC message to parent channel so that the loading
+ // can be cancelled.
+ Unused << httpChannelChild->CancelWithReason(
+ rv, "HttpChannelChild Redirect3 failed"_ns);
+
+ // The post-redirect channel could still get OnStart/StopRequest IPC
+ // messages from parent, but the mListener is still null. So, we
+ // call |DoNotifyListener| to pretend that OnStart/StopRequest are
+ // already called.
+ httpChannelChild->DoNotifyListener();
+ }
+ return;
+ }
+
+ self->Redirect3Complete();
+ }));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvRedirectFailed(
+ const nsresult& status) {
+ LOG(("HttpChannelChild::RecvRedirectFailed this=%p status=%X\n", this,
+ static_cast<uint32_t>(status)));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), status]() {
+ nsCOMPtr<nsIRedirectResultListener> vetoHook;
+ self->GetCallback(vetoHook);
+ if (vetoHook) {
+ vetoHook->OnRedirectResult(status);
+ }
+
+ if (RefPtr<HttpChannelChild> httpChannelChild =
+ do_QueryObject(self->mRedirectChannelChild)) {
+ // For sending an IPC message to parent channel so that the loading
+ // can be cancelled.
+ Unused << httpChannelChild->CancelWithReason(
+ status, "HttpChannelChild RecvRedirectFailed"_ns);
+
+ // The post-redirect channel could still get OnStart/StopRequest IPC
+ // messages from parent, but the mListener is still null. So, we
+ // call |DoNotifyListener| to pretend that OnStart/StopRequest are
+ // already called.
+ httpChannelChild->DoNotifyListener();
+ }
+ }));
+
+ return IPC_OK();
+}
+
+void HttpChannelChild::ProcessNotifyClassificationFlags(
+ uint32_t aClassificationFlags, bool aIsThirdParty) {
+ LOG(
+ ("HttpChannelChild::ProcessNotifyClassificationFlags thirdparty=%d "
+ "flags=%" PRIu32 " [this=%p]\n",
+ static_cast<int>(aIsThirdParty), aClassificationFlags, this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aClassificationFlags,
+ aIsThirdParty]() {
+ self->AddClassificationFlags(aClassificationFlags, aIsThirdParty);
+ }));
+}
+
+void HttpChannelChild::ProcessSetClassifierMatchedInfo(
+ const nsACString& aList, const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ LOG(("HttpChannelChild::ProcessSetClassifierMatchedInfo [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<HttpChannelChild>(this), aList = nsCString(aList),
+ aProvider = nsCString(aProvider), aFullHash = nsCString(aFullHash)]() {
+ self->SetMatchedInfo(aList, aProvider, aFullHash);
+ }));
+}
+
+void HttpChannelChild::ProcessSetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ LOG(("HttpChannelChild::ProcessSetClassifierMatchedTrackingInfo [this=%p]\n",
+ this));
+ MOZ_ASSERT(OnSocketThread());
+
+ nsTArray<nsCString> lists, fullhashes;
+ for (const nsACString& token : aLists.Split(',')) {
+ lists.AppendElement(token);
+ }
+ for (const nsACString& token : aFullHashes.Split(',')) {
+ fullhashes.AppendElement(token);
+ }
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this),
+ lists = CopyableTArray{std::move(lists)},
+ fullhashes = CopyableTArray{std::move(fullhashes)}]() {
+ self->SetMatchedTrackingInfo(lists, fullhashes);
+ }));
+}
+
+// Completes the redirect and cleans up the old channel.
+void HttpChannelChild::Redirect3Complete() {
+ LOG(("HttpChannelChild::Redirect3Complete [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Using an error as the default so that when we fail to forward this redirect
+ // to the target channel, we make sure to notify the current listener from
+ // CleanupRedirectingChannel.
+ nsresult rv = NS_BINDING_ABORTED;
+
+ nsCOMPtr<nsIRedirectResultListener> vetoHook;
+ GetCallback(vetoHook);
+ if (vetoHook) {
+ vetoHook->OnRedirectResult(NS_OK);
+ }
+
+ // Chrome channel has been AsyncOpen'd. Reflect this in child.
+ if (mRedirectChannelChild) {
+ rv = mRedirectChannelChild->CompleteRedirectSetup(mListener);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mSuccesfullyRedirected = NS_SUCCEEDED(rv);
+#endif
+ }
+
+ CleanupRedirectingChannel(rv);
+}
+
+void HttpChannelChild::CleanupRedirectingChannel(nsresult rv) {
+ // Redirecting to new channel: shut this down and init new channel
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_ABORTED);
+
+ if (NS_SUCCEEDED(rv)) {
+ mLoadInfo->AppendRedirectHistoryEntry(this, false);
+ } else {
+ NS_WARNING("CompleteRedirectSetup failed, HttpChannelChild already open?");
+ }
+
+ // Release ref to new channel.
+ mRedirectChannelChild = nullptr;
+
+ NotifyOrReleaseListeners(rv);
+ CleanupBackgroundChannel();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIChildChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::ConnectParent(uint32_t registrarId) {
+ LOG(("HttpChannelChild::ConnectParent [this=%p, id=%" PRIu32 "]\n", this,
+ registrarId));
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::dom::BrowserChild* browserChild = nullptr;
+ nsCOMPtr<nsIBrowserChild> iBrowserChild;
+ GetCallback(iBrowserChild);
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get());
+ }
+
+ if (browserChild && !browserChild->IPCOpen()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ // This must happen before the constructor message is sent. Otherwise messages
+ // from the parent could arrive quickly and be delivered to the wrong event
+ // target.
+ SetEventTarget();
+
+ if (browserChild) {
+ MOZ_ASSERT(browserChild->WebNavigation());
+ if (BrowsingContext* bc = browserChild->GetBrowsingContext()) {
+ mBrowserId = bc->BrowserId();
+ }
+ }
+
+ HttpChannelConnectArgs connectArgs(registrarId);
+ if (!gNeckoChild->SendPHttpChannelConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), connectArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+ MutexAutoLock lock(mBgChildMutex);
+
+ MOZ_ASSERT(!mBgChild);
+ MOZ_ASSERT(!mBgInitFailCallback);
+
+ mBgInitFailCallback = NewRunnableMethod<nsresult>(
+ "HttpChannelChild::OnRedirectVerifyCallback", this,
+ &HttpChannelChild::OnRedirectVerifyCallback, NS_ERROR_FAILURE);
+
+ RefPtr<HttpBackgroundChannelChild> bgChild =
+ new HttpBackgroundChannelChild();
+
+ MOZ_RELEASE_ASSERT(gSocketTransportService);
+
+ RefPtr<HttpChannelChild> self = this;
+ nsresult rv = gSocketTransportService->Dispatch(
+ NewRunnableMethod<RefPtr<HttpChannelChild>>(
+ "HttpBackgroundChannelChild::Init", bgChild,
+ &HttpBackgroundChannelChild::Init, std::move(self)),
+ NS_DISPATCH_NORMAL);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mBgChild = std::move(bgChild);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mEverHadBgChildAtConnectParent = true;
+#endif
+ }
+
+ // Should wait for CompleteRedirectSetup to set the listener.
+ mEventQ->Suspend();
+ MOZ_ASSERT(!mSuspendForWaitCompleteRedirectSetup);
+ mSuspendForWaitCompleteRedirectSetup = true;
+
+ // Connect to socket process after mEventQ is suspended.
+ MaybeConnectToSocketProcess();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) {
+ LOG(("HttpChannelChild::CompleteRedirectSetup [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
+
+ // Resume the suspension in ConnectParent.
+ auto eventQueueResumeGuard = MakeScopeExit([&] {
+ MOZ_ASSERT(mSuspendForWaitCompleteRedirectSetup);
+ mEventQ->Resume();
+ mSuspendForWaitCompleteRedirectSetup = false;
+ });
+
+ /*
+ * No need to check for cancel: we don't get here if nsHttpChannel canceled
+ * before AsyncOpen(); if it's canceled after that, OnStart/Stop will just
+ * get called with error code as usual. So just setup mListener and make the
+ * channel reflect AsyncOpen'ed state.
+ */
+
+ mLastStatusReported = TimeStamp::Now();
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ }
+ StoreIsPending(true);
+ StoreWasOpened(true);
+ mListener = aListener;
+
+ // add ourselves to the load group.
+ if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr);
+
+ // We already have an open IPDL connection to the parent. If on-modify-request
+ // listeners or load group observers canceled us, let the parent handle it
+ // and send it back to us naturally.
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIAsyncVerifyRedirectCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::OnRedirectVerifyCallback(nsresult aResult) {
+ LOG(("HttpChannelChild::OnRedirectVerifyCallback [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIURI> redirectURI;
+
+ DebugOnly<nsresult> rv = NS_OK;
+
+ nsCOMPtr<nsIHttpChannel> newHttpChannel =
+ do_QueryInterface(mRedirectChannelChild);
+
+ if (NS_SUCCEEDED(aResult) && !mRedirectChannelChild) {
+ // mRedirectChannelChild doesn't exist means we're redirecting to a protocol
+ // that doesn't implement nsIChildChannel. The redirect result should be set
+ // as failed by veto listeners and shouldn't enter this condition. As the
+ // last resort, we synthesize the error result as NS_ERROR_DOM_BAD_URI here
+ // to let nsHttpChannel::ContinueProcessResponse2 know it's redirecting to
+ // another protocol and throw an error.
+ LOG((" redirecting to a protocol that doesn't implement nsIChildChannel"));
+ aResult = NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ if (newHttpChannel) {
+ // Must not be called until after redirect observers called.
+ newHttpChannel->SetOriginalURI(mOriginalURI);
+ referrerInfo = newHttpChannel->GetReferrerInfo();
+ }
+
+ RequestHeaderTuples emptyHeaders;
+ RequestHeaderTuples* headerTuples = &emptyHeaders;
+ nsLoadFlags loadFlags = 0;
+ Maybe<CorsPreflightArgs> corsPreflightArgs;
+
+ nsCOMPtr<nsIHttpChannelChild> newHttpChannelChild =
+ do_QueryInterface(mRedirectChannelChild);
+ if (newHttpChannelChild && NS_SUCCEEDED(aResult)) {
+ rv = newHttpChannelChild->AddCookiesToRequest();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = newHttpChannelChild->GetClientSetRequestHeaders(&headerTuples);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ newHttpChannelChild->GetClientSetCorsPreflightParameters(corsPreflightArgs);
+ }
+
+ if (NS_SUCCEEDED(aResult)) {
+ // Note: this is where we would notify "http-on-modify-response" observers.
+ // We have deliberately disabled this for child processes (see bug 806753)
+ //
+ // After we verify redirect, nsHttpChannel may hit the network: must give
+ // "http-on-modify-request" observers the chance to cancel before that.
+ // base->CallOnModifyRequestObservers();
+
+ nsCOMPtr<nsIHttpChannelInternal> newHttpChannelInternal =
+ do_QueryInterface(mRedirectChannelChild);
+ if (newHttpChannelInternal) {
+ Unused << newHttpChannelInternal->GetApiRedirectToURI(
+ getter_AddRefs(redirectURI));
+ }
+
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mRedirectChannelChild);
+ if (request) {
+ request->GetLoadFlags(&loadFlags);
+ }
+ }
+
+ uint32_t sourceRequestBlockingReason = 0;
+ mLoadInfo->GetRequestBlockingReason(&sourceRequestBlockingReason);
+
+ Maybe<ChildLoadInfoForwarderArgs> targetLoadInfoForwarder;
+ nsCOMPtr<nsIChannel> newChannel = do_QueryInterface(mRedirectChannelChild);
+ if (newChannel) {
+ ChildLoadInfoForwarderArgs args;
+ nsCOMPtr<nsILoadInfo> loadInfo = newChannel->LoadInfo();
+ LoadInfoToChildLoadInfoForwarder(loadInfo, &args);
+ targetLoadInfoForwarder.emplace(args);
+ }
+
+ if (CanSend()) {
+ SendRedirect2Verify(aResult, *headerTuples, sourceRequestBlockingReason,
+ targetLoadInfoForwarder, loadFlags, referrerInfo,
+ redirectURI, corsPreflightArgs);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP HttpChannelChild::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP HttpChannelChild::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP
+HttpChannelChild::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Cancel(nsresult aStatus) {
+ LOG(("HttpChannelChild::Cancel [this=%p, status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aStatus)));
+ // only logging on parent is necessary
+ Maybe<nsCString> logStack = CallingScriptLocationString();
+ Maybe<nsCString> logOnParent;
+ if (logStack.isSome()) {
+ logOnParent = Some(""_ns);
+ logOnParent->AppendPrintf(
+ "[this=%p] cancelled call in child process from script: %s", this,
+ logStack->get());
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mCanceled) {
+ // If this cancel occurs before nsHttpChannel has been set up, AsyncOpen
+ // is responsible for cleaning up.
+ mCanceled = true;
+ mStatus = aStatus;
+
+ bool remoteChannelExists = RemoteChannelExists();
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mCanSendAtCancel = CanSend();
+ mRemoteChannelExistedAtCancel = remoteChannelExists;
+#endif
+
+ if (remoteChannelExists) {
+ SendCancel(aStatus, mLoadInfo->GetRequestBlockingReason(),
+ mCanceledReason, logOnParent);
+ } else if (MOZ_UNLIKELY(!LoadOnStartRequestCalled() ||
+ !LoadOnStopRequestCalled())) {
+ Unused << AsyncAbort(mStatus);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Suspend() {
+ LOG(("HttpChannelChild::Suspend [this=%p, mSuspendCount=%" PRIu32 "\n", this,
+ mSuspendCount + 1));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LogCallingScriptLocation(this);
+
+ // SendSuspend only once, when suspend goes from 0 to 1.
+ // Don't SendSuspend at all if we're diverting callbacks to the parent;
+ // suspend will be called at the correct time in the parent itself.
+ if (!mSuspendCount++) {
+ if (RemoteChannelExists()) {
+ SendSuspend();
+ mSuspendSent = true;
+ }
+ }
+ mEventQ->Suspend();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Resume() {
+ LOG(("HttpChannelChild::Resume [this=%p, mSuspendCount=%" PRIu32 "\n", this,
+ mSuspendCount - 1));
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+
+ LogCallingScriptLocation(this);
+
+ nsresult rv = NS_OK;
+
+ // SendResume only once, when suspend count drops to 0.
+ // Don't SendResume at all if we're diverting callbacks to the parent (unless
+ // suspend was sent earlier); otherwise, resume will be called at the correct
+ // time in the parent itself.
+ if (!--mSuspendCount) {
+ if (RemoteChannelExists() && mSuspendSent) {
+ SendResume();
+ }
+ if (mCallOnResume) {
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ RefPtr<HttpChannelChild> self = this;
+ std::function<nsresult(HttpChannelChild*)> callOnResume = nullptr;
+ std::swap(callOnResume, mCallOnResume);
+ rv = neckoTarget->Dispatch(
+ NS_NewRunnableFunction(
+ "net::HttpChannelChild::mCallOnResume",
+ [callOnResume, self{std::move(self)}]() { callOnResume(self); }),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+ mEventQ->Resume();
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
+ NS_ENSURE_ARG_POINTER(aSecurityInfo);
+ *aSecurityInfo = do_AddRef(mSecurityInfo).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::AsyncOpen(nsIStreamListener* aListener) {
+ LOG(("HttpChannelChild::AsyncOpen [this=%p uri=%s]\n", this, mSpec.get()));
+
+ nsresult rv = AsyncOpenInternal(aListener);
+ if (NS_FAILED(rv)) {
+ uint32_t blockingReason = 0;
+ mLoadInfo->GetRequestBlockingReason(&blockingReason);
+ LOG(
+ ("HttpChannelChild::AsyncOpen failed [this=%p rv=0x%08x "
+ "blocking-reason=%u]\n",
+ this, static_cast<uint32_t>(rv), blockingReason));
+
+ gHttpHandler->OnFailedOpeningRequest(this);
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mAsyncOpenSucceeded = NS_SUCCEEDED(rv);
+#endif
+ return rv;
+}
+
+nsresult HttpChannelChild::AsyncOpenInternal(nsIStreamListener* aListener) {
+ nsresult rv;
+
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ReleaseListeners();
+ return rv;
+ }
+
+ MOZ_ASSERT(
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL &&
+ mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()),
+ "security flags in loadInfo but doContentSecurityCheck() not called");
+
+ LogCallingScriptLocation(this);
+
+ if (!mLoadGroup && !mCallbacks) {
+ // If no one called SetLoadGroup or SetNotificationCallbacks, the private
+ // state has not been updated on PrivateBrowsingChannel (which we derive
+ // from) Hence, we have to call UpdatePrivateBrowsing() here
+ UpdatePrivateBrowsing();
+ }
+
+#ifdef DEBUG
+ AssertPrivateBrowsingId();
+#endif
+
+ if (mCanceled) {
+ ReleaseListeners();
+ return mStatus;
+ }
+
+ NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
+
+ if (MaybeWaitForUploadStreamNormalization(listener, nullptr)) {
+ return NS_OK;
+ }
+
+ if (!LoadAsyncOpenTimeOverriden()) {
+ mAsyncOpenTime = TimeStamp::Now();
+ }
+
+ // Port checked in parent, but duplicate here so we can return with error
+ // immediately
+ rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv)) {
+ ReleaseListeners();
+ return rv;
+ }
+
+ nsAutoCString cookie;
+ if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookie))) {
+ mUserSetCookieHeader = cookie;
+ }
+
+ DebugOnly<nsresult> check = AddCookiesToRequest();
+ MOZ_ASSERT(NS_SUCCEEDED(check));
+
+ //
+ // NOTE: From now on we must return NS_OK; all errors must be handled via
+ // OnStart/OnStopRequest
+ //
+
+ // We notify "http-on-opening-request" observers in the child
+ // process so that devtools can capture a stack trace at the
+ // appropriate spot. See bug 806753 for some information about why
+ // other http-* notifications are disabled in child processes.
+ gHttpHandler->OnOpeningRequest(this);
+
+ mLastStatusReported = TimeStamp::Now();
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ }
+ StoreIsPending(true);
+ StoreWasOpened(true);
+ mListener = listener;
+
+ if (mCanceled) {
+ // We may have been canceled already, either by on-modify-request
+ // listeners or by load group observers; in that case, don't create IPDL
+ // connection. See nsHttpChannel::AsyncOpen().
+ ReleaseListeners();
+ return mStatus;
+ }
+
+ // Set user agent override from docshell
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ rv = ContinueAsyncOpen();
+ if (NS_FAILED(rv)) {
+ ReleaseListeners();
+ }
+ return rv;
+}
+
+// Assigns an nsISerialEventTarget to our IPDL actor so that IPC messages are
+// sent to the correct DocGroup/TabGroup.
+void HttpChannelChild::SetEventTarget() {
+ MutexAutoLock lock(mEventTargetMutex);
+ mNeckoTarget = GetMainThreadSerialEventTarget();
+}
+
+already_AddRefed<nsISerialEventTarget> HttpChannelChild::GetNeckoTarget() {
+ nsCOMPtr<nsISerialEventTarget> target;
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ target = mNeckoTarget;
+ }
+
+ if (!target) {
+ target = GetMainThreadSerialEventTarget();
+ }
+ return target.forget();
+}
+
+already_AddRefed<nsIEventTarget> HttpChannelChild::GetODATarget() {
+ nsCOMPtr<nsIEventTarget> target;
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ if (mODATarget) {
+ target = mODATarget;
+ } else {
+ target = mNeckoTarget;
+ }
+ }
+
+ if (!target) {
+ target = GetMainThreadSerialEventTarget();
+ }
+ return target.forget();
+}
+
+nsresult HttpChannelChild::ContinueAsyncOpen() {
+ nsresult rv;
+ //
+ // Send request to the chrome process...
+ //
+
+ mozilla::dom::BrowserChild* browserChild = nullptr;
+ nsCOMPtr<nsIBrowserChild> iBrowserChild;
+ GetCallback(iBrowserChild);
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get());
+ }
+
+ // This id identifies the inner window's top-level document,
+ // which changes on every new load or navigation.
+ uint64_t contentWindowId = 0;
+ TimeStamp navigationStartTimeStamp;
+ if (browserChild) {
+ MOZ_ASSERT(browserChild->WebNavigation());
+ if (RefPtr<Document> document = browserChild->GetTopLevelDocument()) {
+ contentWindowId = document->InnerWindowID();
+ nsDOMNavigationTiming* navigationTiming = document->GetNavigationTiming();
+ if (navigationTiming) {
+ navigationStartTimeStamp =
+ navigationTiming->GetNavigationStartTimeStamp();
+ }
+ }
+ if (BrowsingContext* bc = browserChild->GetBrowsingContext()) {
+ mBrowserId = bc->BrowserId();
+ }
+ }
+ SetTopLevelContentWindowId(contentWindowId);
+
+ if (browserChild && !browserChild->IPCOpen()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // add ourselves to the load group.
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ HttpChannelOpenArgs openArgs;
+ // No access to HttpChannelOpenArgs members, but they each have a
+ // function with the struct name that returns a ref.
+ openArgs.uri() = mURI;
+ openArgs.original() = mOriginalURI;
+ openArgs.doc() = mDocumentURI;
+ openArgs.apiRedirectTo() = mAPIRedirectToURI;
+ openArgs.loadFlags() = mLoadFlags;
+ openArgs.requestHeaders() = mClientSetRequestHeaders;
+ mRequestHead.Method(openArgs.requestMethod());
+ openArgs.preferredAlternativeTypes() = mPreferredCachedAltDataTypes.Clone();
+ openArgs.referrerInfo() = mReferrerInfo;
+
+ if (mUploadStream) {
+ MOZ_ALWAYS_TRUE(SerializeIPCStream(do_AddRef(mUploadStream),
+ openArgs.uploadStream(),
+ /* aAllowLazy */ false));
+ }
+
+ Maybe<CorsPreflightArgs> optionalCorsPreflightArgs;
+ GetClientSetCorsPreflightParameters(optionalCorsPreflightArgs);
+
+ // NB: This call forces us to cache mTopWindowURI if we haven't already.
+ nsCOMPtr<nsIURI> uri;
+ GetTopWindowURI(mURI, getter_AddRefs(uri));
+
+ openArgs.topWindowURI() = mTopWindowURI;
+
+ openArgs.preflightArgs() = optionalCorsPreflightArgs;
+
+ openArgs.uploadStreamHasHeaders() = LoadUploadStreamHasHeaders();
+ openArgs.priority() = mPriority;
+ openArgs.classOfService() = mClassOfService;
+ openArgs.redirectionLimit() = mRedirectionLimit;
+ openArgs.allowSTS() = LoadAllowSTS();
+ openArgs.thirdPartyFlags() = LoadThirdPartyFlags();
+ openArgs.resumeAt() = mSendResumeAt;
+ openArgs.startPos() = mStartPos;
+ openArgs.entityID() = mEntityID;
+ openArgs.allowSpdy() = LoadAllowSpdy();
+ openArgs.allowHttp3() = LoadAllowHttp3();
+ openArgs.allowAltSvc() = LoadAllowAltSvc();
+ openArgs.beConservative() = LoadBeConservative();
+ openArgs.bypassProxy() = BypassProxy();
+ openArgs.tlsFlags() = mTlsFlags;
+ openArgs.initialRwin() = mInitialRwin;
+
+ openArgs.cacheKey() = mCacheKey;
+
+ openArgs.blockAuthPrompt() = LoadBlockAuthPrompt();
+
+ openArgs.allowStaleCacheContent() = LoadAllowStaleCacheContent();
+ openArgs.preferCacheLoadOverBypass() = LoadPreferCacheLoadOverBypass();
+
+ openArgs.contentTypeHint() = mContentTypeHint;
+
+ rv = mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &openArgs.loadInfo());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ EnsureRequestContextID();
+ openArgs.requestContextID() = mRequestContextID;
+
+ openArgs.requestMode() = mRequestMode;
+ openArgs.redirectMode() = mRedirectMode;
+
+ openArgs.channelId() = mChannelId;
+
+ openArgs.integrityMetadata() = mIntegrityMetadata;
+
+ openArgs.contentWindowId() = contentWindowId;
+ openArgs.browserId() = mBrowserId;
+
+ LOG(("HttpChannelChild::ContinueAsyncOpen this=%p gid=%" PRIu64
+ " browser id=%" PRIx64,
+ this, mChannelId, mBrowserId));
+
+ openArgs.launchServiceWorkerStart() = mLaunchServiceWorkerStart;
+ openArgs.launchServiceWorkerEnd() = mLaunchServiceWorkerEnd;
+ openArgs.dispatchFetchEventStart() = mDispatchFetchEventStart;
+ openArgs.dispatchFetchEventEnd() = mDispatchFetchEventEnd;
+ openArgs.handleFetchEventStart() = mHandleFetchEventStart;
+ openArgs.handleFetchEventEnd() = mHandleFetchEventEnd;
+
+ openArgs.forceMainDocumentChannel() = LoadForceMainDocumentChannel();
+
+ openArgs.navigationStartTimeStamp() = navigationStartTimeStamp;
+ openArgs.earlyHintPreloaderId() = mEarlyHintPreloaderId;
+
+ openArgs.classicScriptHintCharset() = mClassicScriptHintCharset;
+
+ openArgs.isUserAgentHeaderModified() = LoadIsUserAgentHeaderModified();
+
+ RefPtr<Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+
+ if (doc) {
+ nsAutoString documentCharacterSet;
+ doc->GetCharacterSet(documentCharacterSet);
+ openArgs.documentCharacterSet() = documentCharacterSet;
+ }
+
+ // This must happen before the constructor message is sent. Otherwise messages
+ // from the parent could arrive quickly and be delivered to the wrong event
+ // target.
+ SetEventTarget();
+
+ if (!gNeckoChild->SendPHttpChannelConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), openArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+ MutexAutoLock lock(mBgChildMutex);
+
+ MOZ_RELEASE_ASSERT(gSocketTransportService);
+
+ // Service worker might use the same HttpChannelChild to do async open
+ // twice. Need to disconnect with previous background channel before
+ // creating the new one, to prevent receiving further notification
+ // from it.
+ if (mBgChild) {
+ RefPtr<HttpBackgroundChannelChild> prevBgChild = std::move(mBgChild);
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod("HttpBackgroundChannelChild::OnChannelClosed",
+ prevBgChild,
+ &HttpBackgroundChannelChild::OnChannelClosed),
+ NS_DISPATCH_NORMAL);
+ }
+
+ MOZ_ASSERT(!mBgInitFailCallback);
+
+ mBgInitFailCallback = NewRunnableMethod<nsresult>(
+ "HttpChannelChild::FailedAsyncOpen", this,
+ &HttpChannelChild::FailedAsyncOpen, NS_ERROR_FAILURE);
+
+ RefPtr<HttpBackgroundChannelChild> bgChild =
+ new HttpBackgroundChannelChild();
+
+ RefPtr<HttpChannelChild> self = this;
+ nsresult rv = gSocketTransportService->Dispatch(
+ NewRunnableMethod<RefPtr<HttpChannelChild>>(
+ "HttpBackgroundChannelChild::Init", bgChild,
+ &HttpBackgroundChannelChild::Init, self),
+ NS_DISPATCH_NORMAL);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mBgChild = std::move(bgChild);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mEverHadBgChildAtAsyncOpen = true;
+#endif
+ }
+
+ MaybeConnectToSocketProcess();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) {
+ LOG(("HttpChannelChild::SetRequestHeader [this=%p]\n", this));
+ nsresult rv = HttpBaseChannel::SetRequestHeader(aHeader, aValue, aMerge);
+ if (NS_FAILED(rv)) return rv;
+
+ RequestHeaderTuple* tuple = mClientSetRequestHeaders.AppendElement();
+ if (!tuple) return NS_ERROR_OUT_OF_MEMORY;
+
+ // Mark that the User-Agent header has been modified.
+ if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) {
+ StoreIsUserAgentHeaderModified(true);
+ }
+
+ tuple->mHeader = aHeader;
+ tuple->mValue = aValue;
+ tuple->mMerge = aMerge;
+ tuple->mEmpty = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetEmptyRequestHeader(const nsACString& aHeader) {
+ LOG(("HttpChannelChild::SetEmptyRequestHeader [this=%p]\n", this));
+ nsresult rv = HttpBaseChannel::SetEmptyRequestHeader(aHeader);
+ if (NS_FAILED(rv)) return rv;
+
+ RequestHeaderTuple* tuple = mClientSetRequestHeaders.AppendElement();
+ if (!tuple) return NS_ERROR_OUT_OF_MEMORY;
+
+ // Mark that the User-Agent header has been modified.
+ if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) {
+ StoreIsUserAgentHeaderModified(true);
+ }
+
+ tuple->mHeader = aHeader;
+ tuple->mMerge = false;
+ tuple->mEmpty = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::RedirectTo(nsIURI* newURI) {
+ // disabled until/unless addons run in child or something else needs this
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::UpgradeToSecure() {
+ // disabled until/unless addons run in child or something else needs this
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetProtocolVersion(nsACString& aProtocolVersion) {
+ aProtocolVersion = mProtocolVersion;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetIsAuthChannel(bool* aIsAuthChannel) { DROP_DEAD(); }
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsICacheInfoChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheTokenFetchCount(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mCacheEntryAvailable && !mAltDataCacheEntryAvailable) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *_retval = mCacheFetchCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheTokenExpirationTime(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mCacheEntryAvailable) return NS_ERROR_NOT_AVAILABLE;
+
+ *_retval = mCacheExpirationTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::IsFromCache(bool* value) {
+ if (!LoadIsPending()) return NS_ERROR_NOT_AVAILABLE;
+
+ *value = mIsFromCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheEntryId(uint64_t* aCacheEntryId) {
+ bool fromCache = false;
+ if (NS_FAILED(IsFromCache(&fromCache)) || !fromCache ||
+ !mCacheEntryAvailable) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aCacheEntryId = mCacheEntryId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::IsRacing(bool* aIsRacing) {
+ if (!LoadAfterOnStartRequestBegun()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aIsRacing = mIsRacing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheKey(uint32_t* cacheKey) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ *cacheKey = mCacheKey;
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetCacheKey(uint32_t cacheKey) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ mCacheKey = cacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetAllowStaleCacheContent(bool aAllowStaleCacheContent) {
+ StoreAllowStaleCacheContent(aAllowStaleCacheContent);
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::GetAllowStaleCacheContent(bool* aAllowStaleCacheContent) {
+ NS_ENSURE_ARG(aAllowStaleCacheContent);
+ *aAllowStaleCacheContent = LoadAllowStaleCacheContent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetForceValidateCacheContent(
+ bool aForceValidateCacheContent) {
+ StoreForceValidateCacheContent(aForceValidateCacheContent);
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::GetForceValidateCacheContent(
+ bool* aForceValidateCacheContent) {
+ NS_ENSURE_ARG(aForceValidateCacheContent);
+ *aForceValidateCacheContent = LoadForceValidateCacheContent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetPreferCacheLoadOverBypass(
+ bool aPreferCacheLoadOverBypass) {
+ StorePreferCacheLoadOverBypass(aPreferCacheLoadOverBypass);
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::GetPreferCacheLoadOverBypass(
+ bool* aPreferCacheLoadOverBypass) {
+ NS_ENSURE_ARG(aPreferCacheLoadOverBypass);
+ *aPreferCacheLoadOverBypass = LoadPreferCacheLoadOverBypass();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::PreferAlternativeDataType(
+ const nsACString& aType, const nsACString& aContentType,
+ PreferredAlternativeDataDeliveryType aDeliverAltData) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ mPreferredCachedAltDataTypes.AppendElement(PreferredAlternativeDataTypeParams(
+ nsCString(aType), nsCString(aContentType), aDeliverAltData));
+ return NS_OK;
+}
+
+const nsTArray<PreferredAlternativeDataTypeParams>&
+HttpChannelChild::PreferredAlternativeDataTypes() {
+ return mPreferredCachedAltDataTypes;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetAlternativeDataType(nsACString& aType) {
+ // Must be called during or after OnStartRequest
+ if (!LoadAfterOnStartRequestBegun()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aType = mAvailableCachedAltDataType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::OpenAlternativeOutputStream(const nsACString& aType,
+ int64_t aPredictedSize,
+ nsIAsyncOutputStream** _retval) {
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ RefPtr<AltDataOutputStreamChild> stream = new AltDataOutputStreamChild();
+ stream->AddIPDLReference();
+
+ if (!gNeckoChild->SendPAltDataOutputStreamConstructor(
+ stream, nsCString(aType), aPredictedSize, WrapNotNull(this))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ stream.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetOriginalInputStream(nsIInputStreamReceiver* aReceiver) {
+ if (aReceiver == nullptr) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mOriginalInputStreamReceiver = aReceiver;
+ Unused << SendOpenOriginalCacheInputStream();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetAlternativeDataInputStream(nsIInputStream** aInputStream) {
+ NS_ENSURE_ARG_POINTER(aInputStream);
+
+ nsCOMPtr<nsIInputStream> is = mAltDataInputStream;
+ is.forget(aInputStream);
+
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvOriginalCacheInputStreamAvailable(
+ const Maybe<IPCStream>& aStream) {
+ nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStream);
+ nsCOMPtr<nsIInputStreamReceiver> receiver;
+ receiver.swap(mOriginalInputStreamReceiver);
+ if (receiver) {
+ receiver->OnInputStreamReady(stream);
+ }
+
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIResumableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::ResumeAt(uint64_t startPos, const nsACString& entityID) {
+ LOG(("HttpChannelChild::ResumeAt [this=%p]\n", this));
+ ENSURE_CALLED_BEFORE_CONNECT();
+ mStartPos = startPos;
+ mEntityID = entityID;
+ mSendResumeAt = true;
+ return NS_OK;
+}
+
+// GetEntityID is shared in HttpBaseChannel
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsISupportsPriority
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::SetPriority(int32_t aPriority) {
+ LOG(("HttpChannelChild::SetPriority %p p=%d", this, aPriority));
+ int16_t newValue = clamped<int32_t>(aPriority, INT16_MIN, INT16_MAX);
+ if (mPriority == newValue) return NS_OK;
+ mPriority = newValue;
+ if (RemoteChannelExists()) SendSetPriority(mPriority);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIClassOfService
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+HttpChannelChild::SetClassFlags(uint32_t inFlags) {
+ if (mClassOfService.Flags() == inFlags) {
+ return NS_OK;
+ }
+
+ mClassOfService.SetFlags(inFlags);
+
+ LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::AddClassFlags(uint32_t inFlags) {
+ mClassOfService.SetFlags(inFlags | mClassOfService.Flags());
+
+ LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::ClearClassFlags(uint32_t inFlags) {
+ mClassOfService.SetFlags(~inFlags & mClassOfService.Flags());
+
+ LOG(("HttpChannelChild %p ClassOfService=%lu", this,
+ mClassOfService.Flags()));
+
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetClassOfService(ClassOfService inCos) {
+ mClassOfService = inCos;
+ LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetIncremental(bool inIncremental) {
+ mClassOfService.SetIncremental(inIncremental);
+ LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIProxiedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetProxyInfo(nsIProxyInfo** aProxyInfo) { DROP_DEAD(); }
+
+NS_IMETHODIMP HttpChannelChild::GetHttpProxyConnectResponseCode(
+ int32_t* aResponseCode) {
+ DROP_DEAD();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIHttpChannelChild
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP HttpChannelChild::AddCookiesToRequest() {
+ HttpBaseChannel::AddCookiesToRequest();
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpChannelChild::GetClientSetRequestHeaders(
+ RequestHeaderTuples** aRequestHeaders) {
+ *aRequestHeaders = &mClientSetRequestHeaders;
+ return NS_OK;
+}
+
+void HttpChannelChild::GetClientSetCorsPreflightParameters(
+ Maybe<CorsPreflightArgs>& aArgs) {
+ if (LoadRequireCORSPreflight()) {
+ CorsPreflightArgs args;
+ args.unsafeHeaders() = mUnsafeHeaders.Clone();
+ aArgs.emplace(args);
+ } else {
+ aArgs = Nothing();
+ }
+}
+
+NS_IMETHODIMP
+HttpChannelChild::RemoveCorsPreflightCacheEntry(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ const OriginAttributes& aOriginAttributes) {
+ PrincipalInfo principalInfo;
+ MOZ_ASSERT(aURI, "aURI should not be null");
+ nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ bool result = false;
+ // Be careful to not attempt to send a message to the parent after the
+ // actor has been destroyed.
+ if (CanSend()) {
+ result = SendRemoveCorsPreflightCacheEntry(aURI, principalInfo,
+ aOriginAttributes);
+ }
+ return result ? NS_OK : NS_ERROR_FAILURE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIMuliPartChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetBaseChannel(nsIChannel** aBaseChannel) {
+ if (!mMultiPartID) {
+ MOZ_ASSERT(false, "Not a multipart channel");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsCOMPtr<nsIChannel> channel = this;
+ channel.forget(aBaseChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetPartID(uint32_t* aPartID) {
+ if (!mMultiPartID) {
+ MOZ_ASSERT(false, "Not a multipart channel");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aPartID = *mMultiPartID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetIsFirstPart(bool* aIsFirstPart) {
+ if (!mMultiPartID) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aIsFirstPart = mIsFirstPartOfMultiPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetIsLastPart(bool* aIsLastPart) {
+ if (!mMultiPartID) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aIsLastPart = mIsLastPartOfMultiPart;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) {
+ LOG(("HttpChannelChild::RetargetDeliveryTo [this=%p, aNewTarget=%p]", this,
+ aNewTarget));
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
+ MOZ_ASSERT(aNewTarget);
+
+ NS_ENSURE_ARG(aNewTarget);
+ if (aNewTarget->IsOnCurrentThread()) {
+ NS_WARNING("Retargeting delivery to same thread");
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::successMainThread;
+ return NS_OK;
+ }
+
+ if (mMultiPartID) {
+ // TODO: Maybe add a new label for this? Maybe it doesn't
+ // matter though, since we also blocked QI, so we shouldn't
+ // ever get here.
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::failListener;
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ // Ensure that |mListener| and any subsequent listeners can be retargeted
+ // to another thread.
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (!retargetableListener || NS_FAILED(rv)) {
+ NS_WARNING("Listener is not retargetable");
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::failListener;
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ rv = retargetableListener->CheckListenerChain();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Subsequent listeners are not retargetable");
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::failListenerChain;
+ return rv;
+ }
+
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ MOZ_ASSERT(!mODATarget);
+ mODATarget = aNewTarget;
+ }
+
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::success;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) {
+ MutexAutoLock lock(mEventTargetMutex);
+
+ nsCOMPtr<nsISerialEventTarget> target = mODATarget;
+ if (!mODATarget) {
+ target = GetCurrentSerialEventTarget();
+ }
+ target.forget(aEventTarget);
+ return NS_OK;
+}
+
+void HttpChannelChild::TrySendDeletingChannel() {
+ AUTO_PROFILER_LABEL("HttpChannelChild::TrySendDeletingChannel", NETWORK);
+ if (!mDeletingChannelSent.compareExchange(false, true)) {
+ // SendDeletingChannel is already sent.
+ return;
+ }
+
+ if (NS_IsMainThread()) {
+ if (NS_WARN_IF(!CanSend())) {
+ // IPC actor is destroyed already, do not send more messages.
+ return;
+ }
+
+ Unused << PHttpChannelChild::SendDeletingChannel();
+ return;
+ }
+
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ DebugOnly<nsresult> rv = neckoTarget->Dispatch(
+ NewNonOwningRunnableMethod(
+ "net::HttpChannelChild::TrySendDeletingChannel", this,
+ &HttpChannelChild::TrySendDeletingChannel),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+nsresult HttpChannelChild::AsyncCallImpl(
+ void (HttpChannelChild::*funcPtr)(),
+ nsRunnableMethod<HttpChannelChild>** retval) {
+ nsresult rv;
+
+ RefPtr<nsRunnableMethod<HttpChannelChild>> event =
+ NewRunnableMethod("net::HttpChannelChild::AsyncCall", this, funcPtr);
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ rv = neckoTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ if (NS_SUCCEEDED(rv) && retval) {
+ *retval = event;
+ }
+
+ return rv;
+}
+
+nsresult HttpChannelChild::SetReferrerHeader(const nsACString& aReferrer,
+ bool aRespectBeforeConnect) {
+ // Normally this would be ENSURE_CALLED_BEFORE_CONNECT, but since the
+ // "connect" is done in the main process, and LoadRequestObserversCalled() is
+ // never set in the ChannelChild, before connect basically means before
+ // asyncOpen.
+ if (aRespectBeforeConnect) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+ }
+
+ // remove old referrer if any
+ mClientSetRequestHeaders.RemoveElementsBy(
+ [](const auto& header) { return "Referer"_ns.Equals(header.mHeader); });
+
+ return HttpBaseChannel::SetReferrerHeader(aReferrer, aRespectBeforeConnect);
+}
+
+void HttpChannelChild::CancelOnMainThread(nsresult aRv,
+ const nsACString& aReason) {
+ LOG(("HttpChannelChild::CancelOnMainThread [this=%p]", this));
+
+ if (NS_IsMainThread()) {
+ CancelWithReason(aRv, aReason);
+ return;
+ }
+
+ mEventQ->Suspend();
+ // Cancel is expected to preempt any other channel events, thus we put this
+ // event in the front of mEventQ to make sure nsIStreamListener not receiving
+ // any ODA/OnStopRequest callbacks.
+ nsCString reason(aReason);
+ mEventQ->PrependEvent(MakeUnique<NeckoTargetChannelFunctionEvent>(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aRv, reason]() {
+ self->CancelWithReason(aRv, reason);
+ }));
+ mEventQ->Resume();
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvSetPriority(
+ const int16_t& aPriority) {
+ mPriority = aPriority;
+ return IPC_OK();
+}
+
+// We don't have a copyable Endpoint and NeckoTargetChannelFunctionEvent takes
+// std::function<void()>. It's not possible to avoid the copy from the type of
+// lambda to std::function, so does the capture list. Hence, we're forced to
+// use the old-fashioned channel event inheritance.
+class AttachStreamFilterEvent : public ChannelEvent {
+ public:
+ AttachStreamFilterEvent(HttpChannelChild* aChild,
+ already_AddRefed<nsIEventTarget> aTarget,
+ Endpoint<extensions::PStreamFilterParent>&& aEndpoint)
+ : mChild(aChild), mTarget(aTarget), mEndpoint(std::move(aEndpoint)) {}
+
+ already_AddRefed<nsIEventTarget> GetEventTarget() override {
+ nsCOMPtr<nsIEventTarget> target = mTarget;
+ return target.forget();
+ }
+
+ void Run() override {
+ extensions::StreamFilterParent::Attach(mChild, std::move(mEndpoint));
+ }
+
+ private:
+ HttpChannelChild* mChild;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ Endpoint<extensions::PStreamFilterParent> mEndpoint;
+};
+
+void HttpChannelChild::RegisterStreamFilter(
+ RefPtr<extensions::StreamFilterParent>& aStreamFilter) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mStreamFilters.AppendElement(aStreamFilter);
+}
+
+void HttpChannelChild::ProcessAttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aEndpoint) {
+ LOG(("HttpChannelChild::ProcessAttachStreamFilter [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new AttachStreamFilterEvent(this, GetNeckoTarget(),
+ std::move(aEndpoint)));
+}
+
+void HttpChannelChild::OnDetachStreamFilters() {
+ LOG(("HttpChannelChild::OnDetachStreamFilters [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+ for (auto& StreamFilter : mStreamFilters) {
+ StreamFilter->Disconnect("ServiceWorker fallback redirection"_ns);
+ }
+ mStreamFilters.Clear();
+}
+
+void HttpChannelChild::ProcessDetachStreamFilters() {
+ LOG(("HttpChannelChild::ProcessDetachStreamFilter [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this)]() {
+ self->OnDetachStreamFilters();
+ }));
+}
+
+void HttpChannelChild::ActorDestroy(ActorDestroyReason aWhy) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mActorDestroyReason.emplace(aWhy);
+#endif
+
+ // OnStartRequest might be dropped if IPDL is destroyed abnormally
+ // and BackgroundChild might have pending IPC messages.
+ // Clean up BackgroundChild at this time to prevent memleak.
+ if (aWhy != Deletion) {
+ // Make sure all the messages are processed.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ mStatus = NS_ERROR_DOCSHELL_DYING;
+ HandleAsyncAbort();
+
+ // Cleanup the background channel before we resume the eventQ so we don't
+ // get any other events.
+ CleanupBackgroundChannel();
+
+ mIPCActorDeleted = true;
+ mCanceled = true;
+ }
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvLogBlockedCORSRequest(
+ const nsAString& aMessage, const nsACString& aCategory,
+ const bool& aIsWarning) {
+ Unused << LogBlockedCORSRequest(aMessage, aCategory, aIsWarning);
+ return IPC_OK();
+}
+
+NS_IMETHODIMP
+HttpChannelChild::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ uint64_t innerWindowID = mLoadInfo->GetInnerWindowID();
+ bool privateBrowsing = !!mLoadInfo->GetOriginAttributes().mPrivateBrowsingId;
+ bool fromChromeContext =
+ mLoadInfo->TriggeringPrincipal()->IsSystemPrincipal();
+ nsCORSListenerProxy::LogBlockedCORSRequest(innerWindowID, privateBrowsing,
+ fromChromeContext, aMessage,
+ aCategory, aIsWarning);
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvLogMimeTypeMismatch(
+ const nsACString& aMessageName, const bool& aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ Unused << LogMimeTypeMismatch(aMessageName, aWarning, aURL, aContentType);
+ return IPC_OK();
+}
+
+NS_IMETHODIMP
+HttpChannelChild::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ RefPtr<Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+
+ AutoTArray<nsString, 2> params;
+ params.AppendElement(aURL);
+ params.AppendElement(aContentType);
+ nsContentUtils::ReportToConsole(
+ aWarning ? nsIScriptError::warningFlag : nsIScriptError::errorFlag,
+ "MIMEMISMATCH"_ns, doc, nsContentUtils::eSECURITY_PROPERTIES,
+ nsCString(aMessageName).get(), params);
+ return NS_OK;
+}
+
+nsresult HttpChannelChild::MaybeLogCOEPError(nsresult aStatus) {
+ if (aStatus == NS_ERROR_DOM_CORP_FAILED) {
+ RefPtr<Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+
+ nsAutoCString url;
+ mURI->GetSpec(url);
+
+ AutoTArray<nsString, 2> params;
+ params.AppendElement(NS_ConvertUTF8toUTF16(url));
+ // The MDN URL intentionally ends with a # so the webconsole linkification
+ // doesn't ignore the final ) of the URL
+ params.AppendElement(
+ u"https://developer.mozilla.org/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP)#"_ns);
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "COEP"_ns, doc,
+ nsContentUtils::eNECKO_PROPERTIES,
+ "CORPBlocked", params);
+ }
+
+ return NS_OK;
+}
+
+nsresult HttpChannelChild::CrossProcessRedirectFinished(nsresult aStatus) {
+ if (!CanSend()) {
+ return NS_BINDING_FAILED;
+ }
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ return mStatus;
+}
+
+void HttpChannelChild::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mDoDiagnosticAssertWhenOnStopNotCalledOnDestroy = true;
+#endif
+}
+
+void HttpChannelChild::MaybeConnectToSocketProcess() {
+ if (!nsIOService::UseSocketProcess()) {
+ return;
+ }
+
+ if (!StaticPrefs::network_send_ODA_to_content_directly()) {
+ return;
+ }
+
+ RefPtr<HttpBackgroundChannelChild> bgChild;
+ {
+ MutexAutoLock lock(mBgChildMutex);
+ bgChild = mBgChild;
+ }
+ SocketProcessBridgeChild::GetSocketProcessBridge()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [bgChild, channelId = ChannelId()](
+ const RefPtr<SocketProcessBridgeChild>& aBridge) {
+ Endpoint<PBackgroundDataBridgeParent> parentEndpoint;
+ Endpoint<PBackgroundDataBridgeChild> childEndpoint;
+ PBackgroundDataBridge::CreateEndpoints(&parentEndpoint, &childEndpoint);
+ aBridge->SendInitBackgroundDataBridge(std::move(parentEndpoint),
+ channelId);
+
+ gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction(
+ "HttpBackgroundChannelChild::CreateDataBridge",
+ [bgChild, endpoint = std::move(childEndpoint)]() mutable {
+ bgChild->CreateDataBridge(std::move(endpoint));
+ }),
+ NS_DISPATCH_NORMAL);
+ },
+ []() { NS_WARNING("Failed to create SocketProcessBridgeChild"); });
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpChannelChild::SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* aListener) {
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/HttpChannelChild.h b/netwerk/protocol/http/HttpChannelChild.h
new file mode 100644
index 0000000000..38895a0555
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -0,0 +1,481 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_HttpChannelChild_h
+#define mozilla_net_HttpChannelChild_h
+
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPrefsBase.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/extensions/StreamFilterParent.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/net/NeckoTargetHolder.h"
+#include "mozilla/net/PHttpChannelChild.h"
+#include "mozilla/net/ChannelEventQueue.h"
+
+#include "nsIStreamListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIProgressEventSink.h"
+#include "nsICacheEntry.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIResumableChannel.h"
+#include "nsIProxiedChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIChildChannel.h"
+#include "nsIHttpChannelChild.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "mozilla/net/DNS.h"
+
+using mozilla::Telemetry::LABELS_HTTP_CHILD_OMT_STATS;
+
+class nsIEventTarget;
+class nsIInterceptedBodyCallback;
+class nsISerialEventTarget;
+class nsITransportSecurityInfo;
+class nsInputStreamPump;
+
+#define HTTP_CHANNEL_CHILD_IID \
+ { \
+ 0x321bd99e, 0x2242, 0x4dc6, { \
+ 0xbb, 0xec, 0xd5, 0x06, 0x29, 0x7c, 0x39, 0x83 \
+ } \
+ }
+
+namespace mozilla::net {
+
+class HttpBackgroundChannelChild;
+
+class HttpChannelChild final : public PHttpChannelChild,
+ public HttpBaseChannel,
+ public HttpAsyncAborter<HttpChannelChild>,
+ public nsICacheInfoChannel,
+ public nsIProxiedChannel,
+ public nsIAsyncVerifyRedirectCallback,
+ public nsIChildChannel,
+ public nsIHttpChannelChild,
+ public nsIMultiPartChannel,
+ public nsIThreadRetargetableRequest,
+ public NeckoTargetHolder {
+ virtual ~HttpChannelChild();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICACHEINFOCHANNEL
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSICHILDCHANNEL
+ NS_DECL_NSIHTTPCHANNELCHILD
+ NS_DECL_NSIMULTIPARTCHANNEL
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_CHANNEL_CHILD_IID)
+
+ HttpChannelChild();
+
+ // Methods HttpBaseChannel didn't implement for us or that we override.
+ //
+ // nsIRequest
+ NS_IMETHOD SetCanceledReason(const nsACString& aReason) override;
+ NS_IMETHOD GetCanceledReason(nsACString& aReason) override;
+ NS_IMETHOD CancelWithReason(nsresult status,
+ const nsACString& reason) override;
+ NS_IMETHOD Cancel(nsresult status) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+ // nsIChannel
+ NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+
+ // HttpBaseChannel::nsIHttpChannel
+ NS_IMETHOD SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) override;
+ NS_IMETHOD SetEmptyRequestHeader(const nsACString& aHeader) override;
+ NS_IMETHOD RedirectTo(nsIURI* newURI) override;
+ NS_IMETHOD UpgradeToSecure() override;
+ NS_IMETHOD GetProtocolVersion(nsACString& aProtocolVersion) override;
+ void DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() override;
+ // nsIHttpChannelInternal
+ NS_IMETHOD GetIsAuthChannel(bool* aIsAuthChannel) override;
+ NS_IMETHOD SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) override;
+ NS_IMETHOD SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* aListener) override;
+ // nsISupportsPriority
+ NS_IMETHOD SetPriority(int32_t value) override;
+ // nsIClassOfService
+ NS_IMETHOD SetClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD AddClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD ClearClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD SetClassOfService(ClassOfService inCos) override;
+ NS_IMETHOD SetIncremental(bool inIncremental) override;
+ // nsIResumableChannel
+ NS_IMETHOD ResumeAt(uint64_t startPos, const nsACString& entityID) override;
+
+ nsresult SetReferrerHeader(const nsACString& aReferrer,
+ bool aRespectBeforeConnect) override;
+
+ [[nodiscard]] bool IsSuspended();
+
+ // Callback while background channel is ready.
+ void OnBackgroundChildReady(HttpBackgroundChannelChild* aBgChild);
+ // Callback while background channel is destroyed.
+ void OnBackgroundChildDestroyed(HttpBackgroundChannelChild* aBgChild);
+
+ nsresult CrossProcessRedirectFinished(nsresult aStatus);
+
+ void RegisterStreamFilter(
+ RefPtr<extensions::StreamFilterParent>& aStreamFilter);
+
+ protected:
+ mozilla::ipc::IPCResult RecvOnStartRequestSent() override;
+ mozilla::ipc::IPCResult RecvFailedAsyncOpen(const nsresult& status) override;
+ mozilla::ipc::IPCResult RecvRedirect1Begin(
+ const uint32_t& registrarId, nsIURI* newOriginalURI,
+ const uint32_t& newLoadFlags, const uint32_t& redirectFlags,
+ const ParentLoadInfoForwarderArgs& loadInfoForwarder,
+ const nsHttpResponseHead& responseHead,
+ nsITransportSecurityInfo* securityInfo, const uint64_t& channelId,
+ const NetAddr& oldPeerAddr,
+ const ResourceTimingStructArgs& aTiming) override;
+ mozilla::ipc::IPCResult RecvRedirect3Complete() override;
+ mozilla::ipc::IPCResult RecvRedirectFailed(const nsresult& status) override;
+ mozilla::ipc::IPCResult RecvDeleteSelf() override;
+
+ mozilla::ipc::IPCResult RecvReportSecurityMessage(
+ const nsAString& messageTag, const nsAString& messageCategory) override;
+
+ mozilla::ipc::IPCResult RecvSetPriority(const int16_t& aPriority) override;
+
+ mozilla::ipc::IPCResult RecvOriginalCacheInputStreamAvailable(
+ const Maybe<IPCStream>& aStream) override;
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual void DoNotifyListenerCleanup() override;
+
+ virtual void DoAsyncAbort(nsresult aStatus) override;
+
+ nsresult AsyncCall(
+ void (HttpChannelChild::*funcPtr)(),
+ nsRunnableMethod<HttpChannelChild>** retval = nullptr) override {
+ // Normally, this method would just be implemented directly, but clang
+ // miscompiles the corresponding non-virtual thunk on linux x86.
+ // It however doesn't when going though a non-virtual method.
+ // https://bugs.llvm.org/show_bug.cgi?id=38466
+ return AsyncCallImpl(funcPtr, retval);
+ };
+
+ // Get event target for processing network events.
+ already_AddRefed<nsISerialEventTarget> GetNeckoTarget() override;
+
+ virtual mozilla::ipc::IPCResult RecvLogBlockedCORSRequest(
+ const nsAString& aMessage, const nsACString& aCategory,
+ const bool& aIsWarning) override;
+ NS_IMETHOD LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning = false) override;
+
+ virtual mozilla::ipc::IPCResult RecvLogMimeTypeMismatch(
+ const nsACString& aMessageName, const bool& aWarning,
+ const nsAString& aURL, const nsAString& aContentType) override;
+ NS_IMETHOD LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override;
+
+ private:
+ // We want to handle failure result of AsyncOpen, hence AsyncOpen calls the
+ // Internal method
+ nsresult AsyncOpenInternal(nsIStreamListener* aListener);
+
+ nsresult AsyncCallImpl(void (HttpChannelChild::*funcPtr)(),
+ nsRunnableMethod<HttpChannelChild>** retval);
+
+ // Sets the event target for future IPC messages. Messages will either be
+ // directed to the TabGroup or DocGroup, depending on the LoadInfo associated
+ // with the channel. Should be called when a new channel is being set up,
+ // before the constructor message is sent to the parent.
+ void SetEventTarget();
+
+ // Get event target for ODA.
+ already_AddRefed<nsIEventTarget> GetODATarget();
+
+ [[nodiscard]] nsresult ContinueAsyncOpen();
+ void ProcessOnStartRequest(const nsHttpResponseHead& aResponseHead,
+ const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs,
+ const HttpChannelAltDataStream& aAltData,
+ const TimeStamp& aOnStartRequestStartTime);
+
+ // Callbacks while receiving OnTransportAndData/OnStopRequest/OnProgress/
+ // OnStatus/FlushedForDiversion/DivertMessages on background IPC channel.
+ void ProcessOnTransportAndData(const nsresult& aChannelStatus,
+ const nsresult& aTransportStatus,
+ const uint64_t& aOffset,
+ const uint32_t& aCount,
+ const nsACString& aData,
+ const TimeStamp& aOnDataAvailableStartTime);
+ void ProcessOnStopRequest(const nsresult& aChannelStatus,
+ const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers,
+ nsTArray<ConsoleReportCollected>&& aConsoleReports,
+ bool aFromSocketProcess,
+ const TimeStamp& aOnStopRequestStartTime);
+ void ProcessOnConsoleReport(
+ nsTArray<ConsoleReportCollected>&& aConsoleReports);
+
+ void ProcessNotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty);
+ void ProcessSetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash);
+ void ProcessSetClassifierMatchedTrackingInfo(const nsACString& aLists,
+ const nsACString& aFullHashes);
+ void ProcessOnAfterLastPart(const nsresult& aStatus);
+ void ProcessOnProgress(const int64_t& aProgress, const int64_t& aProgressMax);
+
+ void ProcessOnStatus(const nsresult& aStatus);
+
+ void ProcessAttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aEndpoint);
+ void ProcessDetachStreamFilters();
+
+ // Return true if we need to tell the parent the size of unreported received
+ // data
+ bool NeedToReportBytesRead();
+ int32_t mUnreportBytesRead = 0;
+
+ void DoOnConsoleReport(nsTArray<ConsoleReportCollected>&& aConsoleReports);
+ void DoOnStartRequest(nsIRequest* aRequest);
+ void DoOnStatus(nsIRequest* aRequest, nsresult status);
+ void DoOnProgress(nsIRequest* aRequest, int64_t progress,
+ int64_t progressMax);
+ void DoOnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream,
+ uint64_t offset, uint32_t count);
+ void DoPreOnStopRequest(nsresult aStatus);
+ void DoOnStopRequest(nsIRequest* aRequest, nsresult aChannelStatus);
+ void ContinueOnStopRequest();
+
+ // Try send DeletingChannel message to parent side. Dispatch an async task to
+ // main thread if invoking on non-main thread.
+ void TrySendDeletingChannel();
+
+ // Try invoke Cancel if on main thread, or prepend a CancelEvent in mEventQ to
+ // ensure Cacnel is processed before any other channel events.
+ void CancelOnMainThread(nsresult aRv, const nsACString& aReason);
+
+ nsresult MaybeLogCOEPError(nsresult aStatus);
+
+ private:
+ // this section is for main-thread-only object
+ // all the references need to be proxy released on main thread.
+ nsCOMPtr<nsIChildChannel> mRedirectChannelChild;
+
+ // Proxy release all members above on main thread.
+ void ReleaseMainThreadOnlyReferences();
+
+ private:
+ nsCString mProtocolVersion;
+
+ RequestHeaderTuples mClientSetRequestHeaders;
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ nsCOMPtr<nsIInputStreamReceiver> mOriginalInputStreamReceiver;
+ nsCOMPtr<nsIInputStream> mAltDataInputStream;
+
+ // Used to ensure atomicity of mBgChild and mBgInitFailCallback
+ Mutex mBgChildMutex{"HttpChannelChild::BgChildMutex"};
+
+ // Associated HTTP background channel
+ RefPtr<HttpBackgroundChannelChild> mBgChild MOZ_GUARDED_BY(mBgChildMutex);
+
+ // Error handling procedure if failed to establish PBackground IPC
+ nsCOMPtr<nsIRunnable> mBgInitFailCallback MOZ_GUARDED_BY(mBgChildMutex);
+
+ // Remove the association with background channel after OnStopRequest
+ // or AsyncAbort.
+ void CleanupBackgroundChannel();
+
+ // Target thread for delivering ODA.
+ nsCOMPtr<nsISerialEventTarget> mODATarget MOZ_GUARDED_BY(mEventTargetMutex);
+ // Used to ensure atomicity of mNeckoTarget / mODATarget;
+ Mutex mEventTargetMutex{"HttpChannelChild::EventTargetMutex"};
+
+ TimeStamp mLastStatusReported;
+
+ uint64_t mCacheEntryId{0};
+
+ // The result of RetargetDeliveryTo for this channel.
+ // |notRequested| represents OMT is not requested by the channel owner.
+ Atomic<LABELS_HTTP_CHILD_OMT_STATS, mozilla::Relaxed> mOMTResult{
+ LABELS_HTTP_CHILD_OMT_STATS::notRequested};
+
+ uint32_t mCacheKey{0};
+ int32_t mCacheFetchCount{0};
+ uint32_t mCacheExpirationTime{
+ static_cast<uint32_t>(nsICacheEntry::NO_EXPIRATION_TIME)};
+
+ // If we're handling a multi-part response, then this is set to the current
+ // part ID during OnStartRequest.
+ Maybe<uint32_t> mMultiPartID;
+
+ // To ensure only one SendDeletingChannel is triggered.
+ Atomic<bool> mDeletingChannelSent{false};
+
+ Atomic<bool, SequentiallyConsistent> mIsFromCache{false};
+ Atomic<bool, SequentiallyConsistent> mIsRacing{false};
+ // Set if we get the result and cache |mNeedToReportBytesRead|
+ Atomic<bool, SequentiallyConsistent> mCacheNeedToReportBytesReadInitialized{
+ false};
+ // True if we need to tell the parent the size of unreported received data
+ Atomic<bool, SequentiallyConsistent> mNeedToReportBytesRead{true};
+ Atomic<uint32_t, mozilla::Relaxed> mOnProgressEventSent{false};
+ // Attached StreamFilterParents
+ // Using raw pointer here since StreamFilterParent owns the channel.
+ // Should be only accessed on the main thread.
+ using StreamFilters = nsTArray<extensions::StreamFilterParent*>;
+ StreamFilters mStreamFilters;
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ bool mDoDiagnosticAssertWhenOnStopNotCalledOnDestroy = false;
+ bool mAsyncOpenSucceeded = false;
+ bool mSuccesfullyRedirected = false;
+ bool mRemoteChannelExistedAtCancel = false;
+ bool mEverHadBgChildAtAsyncOpen = false;
+ bool mEverHadBgChildAtConnectParent = false;
+ bool mCreateBackgroundChannelFailed = false;
+ bool mBgInitFailCallbackTriggered = false;
+ bool mCanSendAtCancel = false;
+ // State of the HttpBackgroundChannelChild's event queue during destruction.
+ enum BckChildQueueStatus {
+ // BckChild never told us
+ BCKCHILD_UNKNOWN,
+ // BckChild was empty at the time of destruction
+ BCKCHILD_EMPTY,
+ // BckChild was keeping events in the queue at the destruction time!
+ BCKCHILD_NON_EMPTY
+ };
+ Atomic<BckChildQueueStatus> mBackgroundChildQueueFinalState{BCKCHILD_UNKNOWN};
+ Maybe<ActorDestroyReason> mActorDestroyReason;
+#endif
+
+ uint8_t mCacheEntryAvailable : 1;
+ uint8_t mAltDataCacheEntryAvailable : 1;
+
+ // If ResumeAt is called before AsyncOpen, we need to send extra data upstream
+ uint8_t mSendResumeAt : 1;
+
+ uint8_t mKeptAlive : 1; // IPC kept open, but only for security info
+
+ // Set when ActorDestroy(ActorDestroyReason::Deletion) is called
+ // The channel must ignore any following OnStart/Stop/DataAvailable messages
+ uint8_t mIPCActorDeleted : 1;
+
+ // Set if SendSuspend is called. Determines if SendResume is needed when
+ // diverting callbacks to parent.
+ uint8_t mSuspendSent : 1;
+
+ // True if this channel is a multi-part channel, and the first part
+ // is currently being processed.
+ uint8_t mIsFirstPartOfMultiPart : 1;
+
+ // True if this channel is a multi-part channel, and the last part
+ // is currently being processed.
+ uint8_t mIsLastPartOfMultiPart : 1;
+
+ // True if this channel is suspended by ConnectParent and not resumed by
+ // CompleteRedirectSetup/RecvDeleteSelf.
+ uint8_t mSuspendForWaitCompleteRedirectSetup : 1;
+
+ // True if RecvOnStartRequestSent was received.
+ uint8_t mRecvOnStartRequestSentCalled : 1;
+
+ // True if this channel is for a document and suspended by waiting for
+ // permission or cookie. That is, RecvOnStartRequestSent is received.
+ uint8_t mSuspendedByWaitingForPermissionCookie : 1;
+
+ // HttpChannelChild::Release has some special logic that makes sure
+ // OnStart/OnStop are always called when releasing the channel.
+ // But we have to make sure we only do this once - otherwise we could
+ // get stuck in a loop.
+ uint8_t mAlreadyReleased : 1;
+
+ void CleanupRedirectingChannel(nsresult rv);
+
+ // Calls OnStartRequest and/or OnStopRequest on our listener in case we didn't
+ // do that so far. If we already did, it will just release references to
+ // cleanup.
+ void NotifyOrReleaseListeners(nsresult rv);
+
+ // true after successful AsyncOpen until OnStopRequest completes.
+ bool RemoteChannelExists() { return CanSend() && !mKeptAlive; }
+
+ void OnStartRequest(const nsHttpResponseHead& aResponseHead,
+ const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs);
+ void OnTransportAndData(const nsresult& channelStatus, const nsresult& status,
+ const uint64_t& offset, const uint32_t& count,
+ const nsACString& data);
+ void OnStopRequest(const nsresult& channelStatus,
+ const ResourceTimingStructArgs& timing,
+ const nsHttpHeaderArray& aResponseTrailers);
+ void FailedAsyncOpen(const nsresult& status);
+ void HandleAsyncAbort();
+ void Redirect1Begin(const uint32_t& registrarId, nsIURI* newOriginalURI,
+ const uint32_t& newLoadFlags,
+ const uint32_t& redirectFlags,
+ const ParentLoadInfoForwarderArgs& loadInfoForwarder,
+ const nsHttpResponseHead& responseHead,
+ nsITransportSecurityInfo* securityInfo,
+ const uint64_t& channelId,
+ const ResourceTimingStructArgs& timing);
+ void Redirect3Complete();
+ void DeleteSelf();
+ // aUseEventQueue should only be false when called from
+ // HttpChannelChild::Release to make sure OnStopRequest is called syncly.
+ void DoNotifyListener(bool aUseEventQueue = true);
+ void ContinueDoNotifyListener();
+ void OnAfterLastPart(const nsresult& aStatus);
+ void MaybeConnectToSocketProcess();
+ void OnDetachStreamFilters();
+ void SendOnDataFinished(const nsresult& aChannelStatus);
+
+ // Create a a new channel to be used in a redirection, based on the provided
+ // response headers.
+ [[nodiscard]] nsresult SetupRedirect(nsIURI* uri,
+ const nsHttpResponseHead* responseHead,
+ const uint32_t& redirectFlags,
+ nsIChannel** outChannel);
+
+ // Collect telemetry for the successful rate of OMT.
+ void CollectOMTTelemetry();
+
+ // Collect telemetry for mixed content.
+ void CollectMixedContentTelemetry();
+
+ void RecordChannelCompletionDurationForEarlyHint();
+
+ friend class HttpAsyncAborter<HttpChannelChild>;
+ friend class InterceptStreamListener;
+ friend class InterceptedChannelContent;
+ friend class HttpBackgroundChannelChild;
+ friend class NeckoTargetChannelFunctionEvent;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpChannelChild, HTTP_CHANNEL_CHILD_IID)
+
+//-----------------------------------------------------------------------------
+// inline functions
+//-----------------------------------------------------------------------------
+
+inline bool HttpChannelChild::IsSuspended() { return mSuspendCount != 0; }
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_HttpChannelChild_h
diff --git a/netwerk/protocol/http/HttpChannelParams.ipdlh b/netwerk/protocol/http/HttpChannelParams.ipdlh
new file mode 100644
index 0000000000..f7601a7785
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParams.ipdlh
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+include IPCServiceWorkerDescriptor;
+include NeckoChannelParams;
+include IPCStream;
+
+include "mozilla/dom/ReferrerInfoUtils.h";
+include "mozilla/ipc/TransportSecurityInfoUtils.h";
+include "mozilla/net/NeckoMessageUtils.h";
+
+using class mozilla::net::nsHttpHeaderArray from "nsHttpHeaderArray.h";
+using mozilla::net::NetAddr from "mozilla/net/DNS.h";
+using nsILoadInfo::CrossOriginOpenerPolicy from "nsILoadInfo.h";
+[RefCounted] using class nsIReferrerInfo from "nsIReferrerInfo.h";
+[RefCounted] using class nsITransportSecurityInfo from "nsITransportSecurityInfo.h";
+using nsIRequest::TRRMode from "nsIRequest.h";
+using mozilla::net::TRRSkippedReason from "nsITRRSkipReason.h";
+
+namespace mozilla {
+namespace net {
+
+struct HttpChannelOnStartRequestArgs
+{
+ nullable nsITransportSecurityInfo securityInfo;
+ nullable nsIReferrerInfo overrideReferrerInfo;
+ uint64_t cacheEntryId;
+ int64_t altDataLength;
+ nsCString altDataType;
+ nsCString cookie;
+ NetAddr selfAddr;
+ NetAddr peerAddr;
+ ResourceTimingStructArgs timing;
+ ParentLoadInfoForwarderArgs loadInfoForwarder;
+ nsresult channelStatus;
+ TRRMode effectiveTRRMode;
+ TRRSkippedReason trrSkipReason;
+ uint32_t cacheFetchCount;
+ uint32_t cacheExpirationTime;
+ uint32_t cacheKey;
+ uint32_t? multiPartID;
+ bool isFromCache;
+ bool isRacing;
+ bool cacheEntryAvailable;
+ bool deliveringAltData;
+ bool applyConversion;
+ bool isResolvedByTRR;
+ bool allRedirectsSameOrigin;
+ bool isFirstPartOfMultiPart;
+ bool isLastPartOfMultiPart;
+ CrossOriginOpenerPolicy openerPolicy;
+ bool shouldWaitForOnStartRequestSent;
+ bool dataFromSocketProcess;
+ bool hasHTTPSRR;
+ bool isProxyUsed;
+ uint8_t redirectCount;
+ nsCString protocolVersion;
+};
+
+struct HttpChannelAltDataStream
+{
+ IPCStream? altDataInputStream;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp
new file mode 100644
index 0000000000..e734d15a7d
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -0,0 +1,2202 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "ErrorList.h"
+#include "HttpLog.h"
+
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/EarlyHintRegistrar.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentProcessManager.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/net/CookieServiceParent.h"
+#include "mozilla/InputStreamLengthHelper.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "HttpBackgroundChannelParent.h"
+#include "ParentChannelListener.h"
+#include "nsDebug.h"
+#include "nsICacheInfoChannel.h"
+#include "nsHttpHandler.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsISupportsPriority.h"
+#include "mozilla/net/BackgroundChannelRegistrar.h"
+#include "nsSerializationHelper.h"
+#include "nsISerializable.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "nsQueryObject.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsCORSListenerProxy.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsIPrompt.h"
+#include "nsIPromptFactory.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "mozilla/net/RedirectChannelRegistrar.h"
+#include "nsIWindowWatcher.h"
+#include "mozilla/dom/Document.h"
+#include "nsISecureBrowserUI.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsThreadUtils.h"
+#include "nsQueryObject.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIViewSourceChannel.h"
+
+using namespace mozilla;
+
+namespace geckoprofiler::markers {
+
+struct ChannelMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("ChannelMarker");
+ }
+ static void StreamJSONMarkerData(
+ mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
+ const mozilla::ProfilerString8View& aURL, uint64_t aChannelId) {
+ if (aURL.Length() != 0) {
+ aWriter.StringProperty("url", aURL);
+ }
+ aWriter.IntProperty("channelId", static_cast<int64_t>(aChannelId));
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema(MS::Location::MarkerChart, MS::Location::MarkerTable);
+ schema.SetTableLabel("{marker.name} - {marker.data.url}");
+ schema.AddKeyFormatSearchable("url", MS::Format::Url,
+ MS::Searchable::Searchable);
+ schema.AddStaticLabelValue(
+ "Description",
+ "Timestamp capturing various phases of a network channel's lifespan.");
+ return schema;
+ }
+};
+
+} // namespace geckoprofiler::markers
+
+using mozilla::BasePrincipal;
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla::net {
+
+HttpChannelParent::HttpChannelParent(dom::BrowserParent* iframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus)
+ : mLoadContext(aLoadContext),
+ mIPCClosed(false),
+ mPBOverride(aOverrideStatus),
+ mStatus(NS_OK),
+ mIgnoreProgress(false),
+ mHasSuspendedByBackPressure(false),
+ mCacheNeedFlowControlInitialized(false),
+ mNeedFlowControl(true),
+ mSuspendedForFlowControl(false),
+ mAfterOnStartRequestBegun(false),
+ mDataSentToChildProcess(false) {
+ LOG(("Creating HttpChannelParent [this=%p]\n", this));
+
+ // Ensure gHttpHandler is initialized: we need the atom table up and running.
+ nsCOMPtr<nsIHttpProtocolHandler> dummyInitializer =
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http");
+
+ MOZ_ASSERT(gHttpHandler);
+ mHttpHandler = gHttpHandler;
+
+ mBrowserParent = iframeEmbedding;
+
+ mSendWindowSize = gHttpHandler->SendWindowSize();
+
+ mEventQ =
+ new ChannelEventQueue(static_cast<nsIParentRedirectingChannel*>(this));
+}
+
+HttpChannelParent::~HttpChannelParent() {
+ LOG(("Destroying HttpChannelParent [this=%p]\n", this));
+ CleanupBackgroundChannel();
+
+ MOZ_ASSERT(!mRedirectCallback);
+ if (NS_WARN_IF(mRedirectCallback)) {
+ mRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_UNEXPECTED);
+ mRedirectCallback = nullptr;
+ }
+ mEventQ->NotifyReleasingOwner();
+}
+
+void HttpChannelParent::ActorDestroy(ActorDestroyReason why) {
+ // We may still have refcount>0 if nsHttpChannel hasn't called OnStopRequest
+ // yet, but child process has crashed. We must not try to send any more msgs
+ // to child, or IPDL will kill chrome process, too.
+ mIPCClosed = true;
+ CleanupBackgroundChannel();
+}
+
+bool HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs) {
+ LOG(("HttpChannelParent::Init [this=%p]\n", this));
+ AUTO_PROFILER_LABEL("HttpChannelParent::Init", NETWORK);
+ switch (aArgs.type()) {
+ case HttpChannelCreationArgs::THttpChannelOpenArgs: {
+ const HttpChannelOpenArgs& a = aArgs.get_HttpChannelOpenArgs();
+ return DoAsyncOpen(
+ a.uri(), a.original(), a.doc(), a.referrerInfo(), a.apiRedirectTo(),
+ a.topWindowURI(), a.loadFlags(), a.requestHeaders(),
+ a.requestMethod(), a.uploadStream(), a.uploadStreamHasHeaders(),
+ a.priority(), a.classOfService(), a.redirectionLimit(), a.allowSTS(),
+ a.thirdPartyFlags(), a.resumeAt(), a.startPos(), a.entityID(),
+ a.allowSpdy(), a.allowHttp3(), a.allowAltSvc(), a.beConservative(),
+ a.bypassProxy(), a.tlsFlags(), a.loadInfo(), a.cacheKey(),
+ a.requestContextID(), a.preflightArgs(), a.initialRwin(),
+ a.blockAuthPrompt(), a.allowStaleCacheContent(),
+ a.preferCacheLoadOverBypass(), a.contentTypeHint(), a.requestMode(),
+ a.redirectMode(), a.channelId(), a.integrityMetadata(),
+ a.contentWindowId(), a.preferredAlternativeTypes(), a.browserId(),
+ a.launchServiceWorkerStart(), a.launchServiceWorkerEnd(),
+ a.dispatchFetchEventStart(), a.dispatchFetchEventEnd(),
+ a.handleFetchEventStart(), a.handleFetchEventEnd(),
+ a.forceMainDocumentChannel(), a.navigationStartTimeStamp(),
+ a.earlyHintPreloaderId(), a.classicScriptHintCharset(),
+ a.documentCharacterSet(), a.isUserAgentHeaderModified());
+ }
+ case HttpChannelCreationArgs::THttpChannelConnectArgs: {
+ const HttpChannelConnectArgs& cArgs = aArgs.get_HttpChannelConnectArgs();
+ return ConnectChannel(cArgs.registrarId());
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown open type");
+ return false;
+ }
+}
+
+void HttpChannelParent::TryInvokeAsyncOpen(nsresult aRv) {
+ LOG(("HttpChannelParent::TryInvokeAsyncOpen [this=%p barrier=%u rv=%" PRIx32
+ "]\n",
+ this, mAsyncOpenBarrier, static_cast<uint32_t>(aRv)));
+ MOZ_ASSERT(NS_IsMainThread());
+ AUTO_PROFILER_LABEL("HttpChannelParent::TryInvokeAsyncOpen", NETWORK);
+
+ // TryInvokeAsyncOpen is called more than we expected.
+ // Assert in nightly build but ignore it in release channel.
+ MOZ_DIAGNOSTIC_ASSERT(mAsyncOpenBarrier > 0);
+ if (NS_WARN_IF(!mAsyncOpenBarrier)) {
+ return;
+ }
+
+ if (--mAsyncOpenBarrier > 0 && NS_SUCCEEDED(aRv)) {
+ // Need to wait for more events.
+ return;
+ }
+
+ InvokeAsyncOpen(aRv);
+}
+
+void HttpChannelParent::OnBackgroundParentReady(
+ HttpBackgroundChannelParent* aBgParent) {
+ LOG(("HttpChannelParent::OnBackgroundParentReady [this=%p bgParent=%p]\n",
+ this, aBgParent));
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mBgParent);
+
+ mBgParent = aBgParent;
+
+ mPromise.ResolveIfExists(true, __func__);
+}
+
+void HttpChannelParent::OnBackgroundParentDestroyed() {
+ LOG(("HttpChannelParent::OnBackgroundParentDestroyed [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mPromise.IsEmpty()) {
+ MOZ_ASSERT(!mBgParent);
+ mPromise.Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+
+ if (!mBgParent) {
+ return;
+ }
+
+ // Background channel is closed unexpectly, abort PHttpChannel operation.
+ mBgParent = nullptr;
+ Delete();
+}
+
+void HttpChannelParent::CleanupBackgroundChannel() {
+ LOG(("HttpChannelParent::CleanupBackgroundChannel [this=%p bgParent=%p]\n",
+ this, mBgParent.get()));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mBgParent) {
+ RefPtr<HttpBackgroundChannelParent> bgParent = std::move(mBgParent);
+ bgParent->OnChannelClosed();
+ return;
+ }
+
+ // The nsHttpChannel may have a reference to this parent, release it
+ // to avoid circular references.
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+ if (httpChannelImpl) {
+ httpChannelImpl->SetWarningReporter(nullptr);
+ }
+
+ if (!mPromise.IsEmpty()) {
+ mRequest.DisconnectIfExists();
+ mPromise.Reject(NS_ERROR_FAILURE, __func__);
+
+ if (!mChannel) {
+ return;
+ }
+
+ // This HttpChannelParent might still have a reference from
+ // BackgroundChannelRegistrar.
+ nsCOMPtr<nsIBackgroundChannelRegistrar> registrar =
+ BackgroundChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+
+ registrar->DeleteChannel(mChannel->ChannelId());
+
+ // If mAsyncOpenBarrier is greater than zero, it means AsyncOpen procedure
+ // is still on going. we need to abort AsyncOpen with failure to destroy
+ // PHttpChannel actor.
+ if (mAsyncOpenBarrier) {
+ TryInvokeAsyncOpen(NS_ERROR_FAILURE);
+ }
+ }
+}
+
+base::ProcessId HttpChannelParent::OtherPid() const {
+ if (mIPCClosed) {
+ return 0;
+ }
+ return PHttpChannelParent::OtherPid();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpChannelParent)
+NS_IMPL_RELEASE(HttpChannelParent)
+NS_INTERFACE_MAP_BEGIN(HttpChannelParent)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIParentRedirectingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIRedirectResultListener)
+ NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIParentRedirectingChannel)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpChannelParent)
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::GetInterface(const nsIID& aIID, void** result) {
+ // A system XHR can be created without reference to a window, hence mTabParent
+ // may be null. In that case we want to let the window watcher pick a prompt
+ // directly.
+ if (!mBrowserParent && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ aIID.Equals(NS_GET_IID(nsIAuthPrompt2)))) {
+ nsresult rv;
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+
+ bool hasWindowCreator = false;
+ Unused << wwatch->HasWindowCreator(&hasWindowCreator);
+ if (!hasWindowCreator) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsCOMPtr<nsIPromptFactory> factory = do_QueryInterface(wwatch);
+ if (!factory) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+ rv = factory->GetPrompt(nullptr, aIID, reinterpret_cast<void**>(result));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+ return NS_OK;
+ }
+
+ // Only support nsILoadContext if child channel's callbacks did too
+ if (aIID.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(result);
+ return NS_OK;
+ }
+
+ return QueryInterface(aIID, result);
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::PHttpChannelParent
+//-----------------------------------------------------------------------------
+
+void HttpChannelParent::AsyncOpenFailed(nsresult aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(NS_FAILED(aRv));
+
+ // Break the reference cycle among HttpChannelParent,
+ // ParentChannelListener, and nsHttpChannel to avoid memory leakage.
+ mChannel = nullptr;
+ mParentListener = nullptr;
+
+ if (!mIPCClosed) {
+ Unused << SendFailedAsyncOpen(aRv);
+ }
+}
+
+void HttpChannelParent::InvokeAsyncOpen(nsresult rv) {
+ LOG(("HttpChannelParent::InvokeAsyncOpen [this=%p rv=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(rv)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(rv)) {
+ AsyncOpenFailed(rv);
+ return;
+ }
+
+ rv = mChannel->AsyncOpen(mParentListener);
+ if (NS_FAILED(rv)) {
+ AsyncOpenFailed(rv);
+ }
+}
+
+void HttpChannelParent::InvokeEarlyHintPreloader(
+ nsresult rv, uint64_t aEarlyHintPreloaderId) {
+ LOG(("HttpChannelParent::InvokeEarlyHintPreloader [this=%p rv=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(rv)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ContentParentId cpId =
+ static_cast<ContentParent*>(Manager()->Manager())->ChildID();
+
+ RefPtr<EarlyHintRegistrar> ehr = EarlyHintRegistrar::GetOrCreate();
+ if (NS_SUCCEEDED(rv)) {
+ rv = ehr->LinkParentChannel(cpId, aEarlyHintPreloaderId, this)
+ ? NS_OK
+ : NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(rv)) {
+ ehr->DeleteEntry(cpId, aEarlyHintPreloaderId);
+ AsyncOpenFailed(NS_ERROR_FAILURE);
+ }
+}
+
+bool HttpChannelParent::DoAsyncOpen(
+ nsIURI* aURI, nsIURI* aOriginalURI, nsIURI* aDocURI,
+ nsIReferrerInfo* aReferrerInfo, nsIURI* aAPIRedirectToURI,
+ nsIURI* aTopWindowURI, const uint32_t& aLoadFlags,
+ const RequestHeaderTuples& requestHeaders, const nsCString& requestMethod,
+ const Maybe<IPCStream>& uploadStream, const bool& uploadStreamHasHeaders,
+ const int16_t& priority, const ClassOfService& classOfService,
+ const uint8_t& redirectionLimit, const bool& allowSTS,
+ const uint32_t& thirdPartyFlags, const bool& doResumeAt,
+ const uint64_t& startPos, const nsCString& entityID, const bool& allowSpdy,
+ const bool& allowHttp3, const bool& allowAltSvc, const bool& beConservative,
+ const bool& bypassProxy, const uint32_t& tlsFlags,
+ const LoadInfoArgs& aLoadInfoArgs, const uint32_t& aCacheKey,
+ const uint64_t& aRequestContextID,
+ const Maybe<CorsPreflightArgs>& aCorsPreflightArgs,
+ const uint32_t& aInitialRwin, const bool& aBlockAuthPrompt,
+ const bool& aAllowStaleCacheContent, const bool& aPreferCacheLoadOverBypass,
+ const nsCString& aContentTypeHint, const dom::RequestMode& aRequestMode,
+ const uint32_t& aRedirectMode, const uint64_t& aChannelId,
+ const nsString& aIntegrityMetadata, const uint64_t& aContentWindowId,
+ const nsTArray<PreferredAlternativeDataTypeParams>&
+ aPreferredAlternativeTypes,
+ const uint64_t& aBrowserId, const TimeStamp& aLaunchServiceWorkerStart,
+ const TimeStamp& aLaunchServiceWorkerEnd,
+ const TimeStamp& aDispatchFetchEventStart,
+ const TimeStamp& aDispatchFetchEventEnd,
+ const TimeStamp& aHandleFetchEventStart,
+ const TimeStamp& aHandleFetchEventEnd,
+ const bool& aForceMainDocumentChannel,
+ const TimeStamp& aNavigationStartTimeStamp,
+ const uint64_t& aEarlyHintPreloaderId,
+ const nsAString& aClassicScriptHintCharset,
+ const nsAString& aDocumentCharacterSet,
+ const bool& aIsUserAgentHeaderModified) {
+ MOZ_ASSERT(aURI, "aURI should not be NULL");
+
+ if (aEarlyHintPreloaderId) {
+ // Wait for HttpBackgrounChannel to continue the async open procedure.
+ mEarlyHintPreloaderId = aEarlyHintPreloaderId;
+ RefPtr<HttpChannelParent> self = this;
+ WaitForBgParent(aChannelId)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self, aEarlyHintPreloaderId]() {
+ self->mRequest.Complete();
+ self->InvokeEarlyHintPreloader(NS_OK, aEarlyHintPreloaderId);
+ },
+ [self, aEarlyHintPreloaderId](nsresult aStatus) {
+ self->mRequest.Complete();
+ self->InvokeEarlyHintPreloader(aStatus, aEarlyHintPreloaderId);
+ })
+ ->Track(mRequest);
+ return true;
+ }
+
+ if (!aURI) {
+ // this check is neccessary to prevent null deref
+ // in opt builds
+ return false;
+ }
+
+ LOG(("HttpChannelParent RecvAsyncOpen [this=%p uri=%s, gid=%" PRIu64
+ " browserid=%" PRIx64 "]\n",
+ this, aURI->GetSpecOrDefault().get(), aChannelId, aBrowserId));
+
+ PROFILER_MARKER("Receive AsyncOpen in Parent", NETWORK, {}, ChannelMarker,
+ aURI->GetSpecOrDefault(), aChannelId);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsAutoCString remoteType;
+ rv = GetRemoteType(remoteType);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs, remoteType,
+ getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannelInternal(getter_AddRefs(channel), aURI, loadInfo, nullptr,
+ nullptr, nullptr, aLoadFlags, ios);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(channel, &rv);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ // Set attributes needed to create a FetchEvent from this channel.
+ httpChannel->SetRequestMode(aRequestMode);
+ httpChannel->SetRedirectMode(aRedirectMode);
+
+ // Set the channelId allocated in child to the parent instance
+ httpChannel->SetChannelId(aChannelId);
+ httpChannel->SetTopLevelContentWindowId(aContentWindowId);
+ httpChannel->SetBrowserId(aBrowserId);
+
+ httpChannel->SetIntegrityMetadata(aIntegrityMetadata);
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(httpChannel);
+ if (httpChannelImpl) {
+ httpChannelImpl->SetWarningReporter(this);
+ }
+ httpChannel->SetTimingEnabled(true);
+ if (mPBOverride != kPBOverride_Unset) {
+ httpChannel->SetPrivate(mPBOverride == kPBOverride_Private);
+ }
+
+ if (doResumeAt) httpChannel->ResumeAt(startPos, entityID);
+
+ if (aOriginalURI) {
+ httpChannel->SetOriginalURI(aOriginalURI);
+ }
+
+ if (aDocURI) {
+ httpChannel->SetDocumentURI(aDocURI);
+ }
+
+ if (aReferrerInfo) {
+ // Referrer header is computed in child no need to recompute here
+ rv =
+ httpChannel->SetReferrerInfoInternal(aReferrerInfo, false, false, true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ httpChannel->SetClassicScriptHintCharset(aClassicScriptHintCharset);
+ httpChannel->SetDocumentCharacterSet(aDocumentCharacterSet);
+
+ if (aAPIRedirectToURI) {
+ httpChannel->RedirectTo(aAPIRedirectToURI);
+ }
+
+ if (aTopWindowURI) {
+ httpChannel->SetTopWindowURI(aTopWindowURI);
+ }
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
+ httpChannel->SetLoadFlags(aLoadFlags);
+ }
+
+ if (aForceMainDocumentChannel) {
+ httpChannel->SetIsMainDocumentChannel(true);
+ }
+
+ for (uint32_t i = 0; i < requestHeaders.Length(); i++) {
+ if (requestHeaders[i].mEmpty) {
+ httpChannel->SetEmptyRequestHeader(requestHeaders[i].mHeader);
+ } else {
+ httpChannel->SetRequestHeader(requestHeaders[i].mHeader,
+ requestHeaders[i].mValue,
+ requestHeaders[i].mMerge);
+ }
+ }
+
+ httpChannel->SetIsUserAgentHeaderModified(aIsUserAgentHeaderModified);
+
+ RefPtr<ParentChannelListener> parentListener = new ParentChannelListener(
+ this, mBrowserParent ? mBrowserParent->GetBrowsingContext() : nullptr);
+
+ httpChannel->SetRequestMethod(nsDependentCString(requestMethod.get()));
+
+ if (aCorsPreflightArgs.isSome()) {
+ const CorsPreflightArgs& args = aCorsPreflightArgs.ref();
+ httpChannel->SetCorsPreflightParameters(args.unsafeHeaders(), false);
+ }
+
+ nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(uploadStream);
+ if (stream) {
+ rv = httpChannel->InternalSetUploadStream(stream);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ httpChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
+ }
+
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel =
+ do_QueryInterface(static_cast<nsIChannel*>(httpChannel.get()));
+ if (cacheChannel) {
+ cacheChannel->SetCacheKey(aCacheKey);
+ for (const auto& data : aPreferredAlternativeTypes) {
+ cacheChannel->PreferAlternativeDataType(data.type(), data.contentType(),
+ data.deliverAltData());
+ }
+
+ cacheChannel->SetAllowStaleCacheContent(aAllowStaleCacheContent);
+ cacheChannel->SetPreferCacheLoadOverBypass(aPreferCacheLoadOverBypass);
+
+ // This is to mark that the results are going to the content process.
+ if (httpChannelImpl) {
+ httpChannelImpl->SetAltDataForChild(true);
+ }
+ }
+
+ httpChannel->SetContentType(aContentTypeHint);
+
+ if (priority != nsISupportsPriority::PRIORITY_NORMAL) {
+ httpChannel->SetPriority(priority);
+ }
+ if (classOfService.Flags() || classOfService.Incremental()) {
+ httpChannel->SetClassOfService(classOfService);
+ }
+ httpChannel->SetRedirectionLimit(redirectionLimit);
+ httpChannel->SetAllowSTS(allowSTS);
+ httpChannel->SetThirdPartyFlags(thirdPartyFlags);
+ httpChannel->SetAllowSpdy(allowSpdy);
+ httpChannel->SetAllowHttp3(allowHttp3);
+ httpChannel->SetAllowAltSvc(allowAltSvc);
+ httpChannel->SetBeConservative(beConservative);
+ httpChannel->SetTlsFlags(tlsFlags);
+ httpChannel->SetInitialRwin(aInitialRwin);
+ httpChannel->SetBlockAuthPrompt(aBlockAuthPrompt);
+
+ httpChannel->SetLaunchServiceWorkerStart(aLaunchServiceWorkerStart);
+ httpChannel->SetLaunchServiceWorkerEnd(aLaunchServiceWorkerEnd);
+ httpChannel->SetDispatchFetchEventStart(aDispatchFetchEventStart);
+ httpChannel->SetDispatchFetchEventEnd(aDispatchFetchEventEnd);
+ httpChannel->SetHandleFetchEventStart(aHandleFetchEventStart);
+ httpChannel->SetHandleFetchEventEnd(aHandleFetchEventEnd);
+
+ httpChannel->SetNavigationStartTimeStamp(aNavigationStartTimeStamp);
+ httpChannel->SetRequestContextID(aRequestContextID);
+
+ // Store the strong reference of channel and parent listener object until
+ // all the initialization procedure is complete without failure, to remove
+ // cycle reference in fail case and to avoid memory leakage.
+ mChannel = std::move(httpChannel);
+ mParentListener = std::move(parentListener);
+ mChannel->SetNotificationCallbacks(mParentListener);
+
+ MOZ_ASSERT(!mBgParent);
+ MOZ_ASSERT(mPromise.IsEmpty());
+ // Wait for HttpBackgrounChannel to continue the async open procedure.
+ ++mAsyncOpenBarrier;
+ RefPtr<HttpChannelParent> self = this;
+ WaitForBgParent(mChannel->ChannelId())
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self]() {
+ self->mRequest.Complete();
+ self->TryInvokeAsyncOpen(NS_OK);
+ },
+ [self](nsresult aStatus) {
+ self->mRequest.Complete();
+ self->TryInvokeAsyncOpen(aStatus);
+ })
+ ->Track(mRequest);
+ return true;
+}
+
+RefPtr<GenericNonExclusivePromise> HttpChannelParent::WaitForBgParent(
+ uint64_t aChannelId) {
+ LOG(("HttpChannelParent::WaitForBgParent [this=%p]\n", this));
+ MOZ_ASSERT(!mBgParent);
+
+ if (!mChannel && !mEarlyHintPreloaderId) {
+ return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+
+ nsCOMPtr<nsIBackgroundChannelRegistrar> registrar =
+ BackgroundChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+ registrar->LinkHttpChannel(aChannelId, this);
+
+ if (mBgParent) {
+ return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
+ }
+
+ return mPromise.Ensure(__func__);
+}
+
+bool HttpChannelParent::ConnectChannel(const uint32_t& registrarId) {
+ nsresult rv;
+
+ LOG(
+ ("HttpChannelParent::ConnectChannel: Looking for a registered channel "
+ "[this=%p, id=%" PRIu32 "]\n",
+ this, registrarId));
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_LinkRedirectChannels(registrarId, this, getter_AddRefs(channel));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not find the http channel to connect its IPC parent");
+ // This makes the channel delete itself safely. It's the only thing
+ // we can do now, since this parent channel cannot be used and there is
+ // no other way to tell the child side there were something wrong.
+ Delete();
+ return true;
+ }
+
+ LOG((" found channel %p, rv=%08" PRIx32, channel.get(),
+ static_cast<uint32_t>(rv)));
+ mChannel = do_QueryObject(channel);
+ if (!mChannel) {
+ LOG((" but it's not HttpBaseChannel"));
+ Delete();
+ return true;
+ }
+
+ LOG((" and it is HttpBaseChannel %p", mChannel.get()));
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+ if (httpChannelImpl) {
+ httpChannelImpl->SetWarningReporter(this);
+ }
+
+ if (mPBOverride != kPBOverride_Unset) {
+ // redirected-to channel may not support PB
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryObject(mChannel);
+ if (pbChannel) {
+ pbChannel->SetPrivate(mPBOverride == kPBOverride_Private);
+ }
+ }
+
+ MOZ_ASSERT(!mBgParent);
+ MOZ_ASSERT(mPromise.IsEmpty());
+ // Waiting for background channel
+ RefPtr<HttpChannelParent> self = this;
+ WaitForBgParent(mChannel->ChannelId())
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self]() { self->mRequest.Complete(); },
+ [self](const nsresult& aResult) {
+ NS_ERROR("failed to establish the background channel");
+ self->mRequest.Complete();
+ })
+ ->Track(mRequest);
+ return true;
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvSetPriority(
+ const int16_t& priority) {
+ LOG(("HttpChannelParent::RecvSetPriority [this=%p, priority=%d]\n", this,
+ priority));
+ AUTO_PROFILER_LABEL("HttpChannelParent::RecvSetPriority", NETWORK);
+
+ if (mChannel) {
+ mChannel->SetPriority(priority);
+ }
+
+ nsCOMPtr<nsISupportsPriority> priorityRedirectChannel =
+ do_QueryInterface(mRedirectChannel);
+ if (priorityRedirectChannel) priorityRedirectChannel->SetPriority(priority);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvSetClassOfService(
+ const ClassOfService& cos) {
+ if (mChannel) {
+ mChannel->SetClassOfService(cos);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvSuspend() {
+ LOG(("HttpChannelParent::RecvSuspend [this=%p]\n", this));
+
+ if (mChannel) {
+ mChannel->Suspend();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvResume() {
+ LOG(("HttpChannelParent::RecvResume [this=%p]\n", this));
+
+ if (mChannel) {
+ mChannel->Resume();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvCancel(
+ const nsresult& status, const uint32_t& requestBlockingReason,
+ const nsACString& reason, const mozilla::Maybe<nsCString>& logString) {
+ LOG(("HttpChannelParent::RecvCancel [this=%p, reason=%s]\n", this,
+ PromiseFlatCString(reason).get()));
+
+ // logging child cancel reason on the parent side
+ if (logString.isSome()) {
+ LOG(("HttpChannelParent::RecvCancel: %s", logString->get()));
+ }
+
+ // May receive cancel before channel has been constructed!
+ if (mChannel) {
+ mChannel->CancelWithReason(status, reason);
+
+ if (MOZ_UNLIKELY(requestBlockingReason !=
+ nsILoadInfo::BLOCKING_REASON_NONE)) {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ loadInfo->SetRequestBlockingReason(requestBlockingReason);
+ }
+
+ // Once we receive |Cancel|, child will stop sending RecvBytesRead. Force
+ // the channel resumed if needed.
+ if (mSuspendedForFlowControl) {
+ LOG((" resume the channel due to e10s backpressure relief by cancel"));
+ Unused << mChannel->Resume();
+ mSuspendedForFlowControl = false;
+ }
+ } else if (!mIPCClosed) {
+ // Make sure that the child correctly delivers all stream listener
+ // notifications.
+ Unused << SendFailedAsyncOpen(status);
+ }
+
+ // We won't need flow control anymore. Toggle the flag to avoid |Suspend|
+ // since OnDataAvailable could be off-main-thread.
+ mCacheNeedFlowControlInitialized = true;
+ mNeedFlowControl = false;
+
+ // If the channel is cancelled before the redirect is completed
+ // RecvRedirect2Verify will not be called, so we must clear the callback.
+ if (mRedirectCallback) {
+ mRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_UNEXPECTED);
+ mRedirectCallback = nullptr;
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvRedirect2Verify(
+ const nsresult& aResult, const RequestHeaderTuples& changedHeaders,
+ const uint32_t& aSourceRequestBlockingReason,
+ const Maybe<ChildLoadInfoForwarderArgs>& aTargetLoadInfoForwarder,
+ const uint32_t& loadFlags, nsIReferrerInfo* aReferrerInfo,
+ nsIURI* aAPIRedirectURI,
+ const Maybe<CorsPreflightArgs>& aCorsPreflightArgs) {
+ LOG(("HttpChannelParent::RecvRedirect2Verify [this=%p result=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aResult)));
+
+ // Result from the child. If something fails here, we might overwrite a
+ // success with a further failure.
+ nsresult result = aResult;
+
+ // Local results.
+ nsresult rv;
+
+ if (NS_SUCCEEDED(result)) {
+ nsCOMPtr<nsIHttpChannel> newHttpChannel =
+ do_QueryInterface(mRedirectChannel);
+
+ if (newHttpChannel) {
+ if (aAPIRedirectURI) {
+ rv = newHttpChannel->RedirectTo(aAPIRedirectURI);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ for (uint32_t i = 0; i < changedHeaders.Length(); i++) {
+ if (changedHeaders[i].mEmpty) {
+ rv = newHttpChannel->SetEmptyRequestHeader(changedHeaders[i].mHeader);
+ } else {
+ rv = newHttpChannel->SetRequestHeader(changedHeaders[i].mHeader,
+ changedHeaders[i].mValue,
+ changedHeaders[i].mMerge);
+ }
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // A successfully redirected channel must have the LOAD_REPLACE flag.
+ MOZ_ASSERT(loadFlags & nsIChannel::LOAD_REPLACE);
+ if (loadFlags & nsIChannel::LOAD_REPLACE) {
+ newHttpChannel->SetLoadFlags(loadFlags);
+ }
+
+ if (aCorsPreflightArgs.isSome()) {
+ nsCOMPtr<nsIHttpChannelInternal> newInternalChannel =
+ do_QueryInterface(newHttpChannel);
+ MOZ_RELEASE_ASSERT(newInternalChannel);
+ const CorsPreflightArgs& args = aCorsPreflightArgs.ref();
+ newInternalChannel->SetCorsPreflightParameters(args.unsafeHeaders(),
+ false);
+ }
+
+ if (aReferrerInfo) {
+ RefPtr<HttpBaseChannel> baseChannel = do_QueryObject(newHttpChannel);
+ MOZ_ASSERT(baseChannel);
+ if (baseChannel) {
+ // Referrer header is computed in child no need to recompute here
+ rv = baseChannel->SetReferrerInfoInternal(aReferrerInfo, false, false,
+ true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ if (aTargetLoadInfoForwarder.isSome()) {
+ nsCOMPtr<nsILoadInfo> newLoadInfo = newHttpChannel->LoadInfo();
+ rv = MergeChildLoadInfoForwarder(aTargetLoadInfoForwarder.ref(),
+ newLoadInfo);
+ if (NS_FAILED(rv) && NS_SUCCEEDED(result)) {
+ result = rv;
+ }
+ }
+ }
+ }
+
+ // If the redirect is vetoed, reason is set on the source (current) channel's
+ // load info, so we must carry iver the change.
+ // The channel may have already been cleaned up, so there is nothing we can
+ // do.
+ if (MOZ_UNLIKELY(aSourceRequestBlockingReason !=
+ nsILoadInfo::BLOCKING_REASON_NONE) &&
+ mChannel) {
+ nsCOMPtr<nsILoadInfo> sourceLoadInfo = mChannel->LoadInfo();
+ sourceLoadInfo->SetRequestBlockingReason(aSourceRequestBlockingReason);
+ }
+
+ // Continue the verification procedure if child has veto the redirection.
+ if (NS_FAILED(result)) {
+ ContinueRedirect2Verify(result);
+ return IPC_OK();
+ }
+
+ // Wait for background channel ready on target channel
+ nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(redirectReg);
+
+ nsCOMPtr<nsIParentChannel> redirectParentChannel;
+ rv = redirectReg->GetParentChannel(mRedirectChannelId,
+ getter_AddRefs(redirectParentChannel));
+ if (!redirectParentChannel) {
+ ContinueRedirect2Verify(rv);
+ return IPC_OK();
+ }
+
+ nsCOMPtr<nsIParentRedirectingChannel> redirectedParent =
+ do_QueryInterface(redirectParentChannel);
+ if (!redirectedParent) {
+ // Continue verification procedure if redirecting to non-Http protocol
+ ContinueRedirect2Verify(result);
+ return IPC_OK();
+ }
+
+ // Ask redirected channel if verification can proceed.
+ // ContinueRedirect2Verify will be invoked when redirected channel is ready.
+ redirectedParent->ContinueVerification(this);
+
+ return IPC_OK();
+}
+
+// from nsIParentRedirectingChannel
+NS_IMETHODIMP
+HttpChannelParent::ContinueVerification(
+ nsIAsyncVerifyRedirectReadyCallback* aCallback) {
+ LOG(("HttpChannelParent::ContinueVerification [this=%p callback=%p]\n", this,
+ aCallback));
+
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aCallback);
+
+ // Continue the verification procedure if background channel is ready.
+ if (mBgParent) {
+ aCallback->ReadyToVerify(NS_OK);
+ return NS_OK;
+ }
+
+ // ConnectChannel must be received before Redirect2Verify.
+ MOZ_ASSERT(!mPromise.IsEmpty());
+
+ // Otherwise, wait for the background channel.
+ nsCOMPtr<nsIAsyncVerifyRedirectReadyCallback> callback = aCallback;
+ if (mChannel) {
+ WaitForBgParent(mChannel->ChannelId())
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [callback]() { callback->ReadyToVerify(NS_OK); },
+ [callback](const nsresult& aResult) {
+ NS_ERROR("failed to establish the background channel");
+ callback->ReadyToVerify(aResult);
+ });
+ } else {
+ // mChannel can be null for several reasons (AsyncOpenFailed, etc)
+ NS_ERROR("No channel for ContinueVerification");
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__, [callback] { callback->ReadyToVerify(NS_ERROR_FAILURE); }));
+ }
+ return NS_OK;
+}
+
+void HttpChannelParent::ContinueRedirect2Verify(const nsresult& aResult) {
+ LOG(
+ ("HttpChannelParent::ContinueRedirect2Verify "
+ "[this=%p result=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aResult)));
+
+ if (mRedirectCallback) {
+ LOG(
+ ("HttpChannelParent::ContinueRedirect2Verify call "
+ "OnRedirectVerifyCallback"
+ " [this=%p result=%" PRIx32 ", mRedirectCallback=%p]\n",
+ this, static_cast<uint32_t>(aResult), mRedirectCallback.get()));
+ mRedirectCallback->OnRedirectVerifyCallback(aResult);
+ mRedirectCallback = nullptr;
+ } else {
+ LOG(
+ ("RecvRedirect2Verify[%p]: NO CALLBACKS! | "
+ "mRedirectChannelId: %" PRIx64 ", mRedirectChannel: %p",
+ this, mRedirectChannelId, mRedirectChannel.get()));
+ }
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvDocumentChannelCleanup(
+ const bool& clearCacheEntry) {
+ CleanupBackgroundChannel(); // Background channel can be closed.
+ mChannel = nullptr; // Reclaim some memory sooner.
+ if (clearCacheEntry) {
+ mCacheEntry = nullptr; // Else we'll block other channels reading same URI
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvRemoveCorsPreflightCacheEntry(
+ nsIURI* uri, const mozilla::ipc::PrincipalInfo& requestingPrincipal,
+ const OriginAttributes& originAttributes) {
+ if (!uri) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ auto principalOrErr = PrincipalInfoToPrincipal(requestingPrincipal);
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+ nsCORSListenerProxy::RemoveFromCorsPreflightCache(uri, principal,
+ originAttributes);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvSetCookies(
+ const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes,
+ nsIURI* aHost, const bool& aFromHttp, nsTArray<CookieStruct>&& aCookies) {
+ net::PCookieServiceParent* csParent =
+ LoneManagedOrNullAsserts(Manager()->ManagedPCookieServiceParent());
+ NS_ENSURE_TRUE(csParent, IPC_OK());
+
+ auto* cs = static_cast<net::CookieServiceParent*>(csParent);
+
+ BrowsingContext* browsingContext = nullptr;
+ if (mBrowserParent) {
+ browsingContext = mBrowserParent->GetBrowsingContext();
+ }
+
+ return cs->SetCookies(nsCString(aBaseDomain), aOriginAttributes, aHost,
+ aFromHttp, aCookies, browsingContext);
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+static ResourceTimingStructArgs GetTimingAttributes(HttpBaseChannel* aChannel) {
+ ResourceTimingStructArgs args;
+ TimeStamp timeStamp;
+ aChannel->GetDomainLookupStart(&timeStamp);
+ args.domainLookupStart() = timeStamp;
+ aChannel->GetDomainLookupEnd(&timeStamp);
+ args.domainLookupEnd() = timeStamp;
+ aChannel->GetConnectStart(&timeStamp);
+ args.connectStart() = timeStamp;
+ aChannel->GetTcpConnectEnd(&timeStamp);
+ args.tcpConnectEnd() = timeStamp;
+ aChannel->GetSecureConnectionStart(&timeStamp);
+ args.secureConnectionStart() = timeStamp;
+ aChannel->GetConnectEnd(&timeStamp);
+ args.connectEnd() = timeStamp;
+ aChannel->GetRequestStart(&timeStamp);
+ args.requestStart() = timeStamp;
+ aChannel->GetResponseStart(&timeStamp);
+ args.responseStart() = timeStamp;
+ aChannel->GetResponseEnd(&timeStamp);
+ args.responseEnd() = timeStamp;
+ aChannel->GetAsyncOpen(&timeStamp);
+ args.fetchStart() = timeStamp;
+ aChannel->GetRedirectStart(&timeStamp);
+ args.redirectStart() = timeStamp;
+ aChannel->GetRedirectEnd(&timeStamp);
+ args.redirectEnd() = timeStamp;
+
+ uint64_t size = 0;
+ aChannel->GetTransferSize(&size);
+ args.transferSize() = size;
+
+ aChannel->GetEncodedBodySize(&size);
+ args.encodedBodySize() = size;
+ // decodedBodySize can be computed in the child process so it doesn't need
+ // to be passed down.
+
+ aChannel->GetCacheReadStart(&timeStamp);
+ args.cacheReadStart() = timeStamp;
+
+ aChannel->GetCacheReadEnd(&timeStamp);
+ args.cacheReadEnd() = timeStamp;
+
+ aChannel->GetTransactionPending(&timeStamp);
+ args.transactionPending() = timeStamp;
+ return args;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::OnStartRequest(nsIRequest* aRequest) {
+ nsresult rv;
+
+ LOG(("HttpChannelParent::OnStartRequest [this=%p, aRequest=%p]\n", this,
+ aRequest));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Maybe<uint32_t> multiPartID;
+ bool isFirstPartOfMultiPart = false;
+ bool isLastPartOfMultiPart = false;
+ DebugOnly<bool> isMultiPart = false;
+
+ RefPtr<HttpBaseChannel> chan = do_QueryObject(aRequest);
+ if (!chan) {
+ if (nsCOMPtr<nsIMultiPartChannel> multiPartChannel =
+ do_QueryInterface(aRequest)) {
+ isMultiPart = true;
+ nsCOMPtr<nsIChannel> baseChannel;
+ multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel));
+ chan = do_QueryObject(baseChannel);
+
+ uint32_t partID = 0;
+ multiPartChannel->GetPartID(&partID);
+ multiPartID = Some(partID);
+ multiPartChannel->GetIsFirstPart(&isFirstPartOfMultiPart);
+ multiPartChannel->GetIsLastPart(&isLastPartOfMultiPart);
+ } else if (nsCOMPtr<nsIViewSourceChannel> viewSourceChannel =
+ do_QueryInterface(aRequest)) {
+ chan = do_QueryObject(viewSourceChannel->GetInnerChannel());
+ }
+ }
+ MOZ_ASSERT(multiPartID || !isMultiPart, "Changed multi-part state?");
+
+ if (!chan) {
+ LOG((" aRequest is not HttpBaseChannel"));
+ NS_ERROR(
+ "Expecting only HttpBaseChannel as aRequest in "
+ "HttpChannelParent::OnStartRequest");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mAfterOnStartRequestBegun = true;
+
+ // Todo: re-enable when bug 1589749 is fixed.
+ /*MOZ_ASSERT(mChannel == chan,
+ "HttpChannelParent getting OnStartRequest from a different "
+ "HttpBaseChannel instance");*/
+
+ HttpChannelOnStartRequestArgs args;
+
+ // Send down any permissions/cookies which are relevant to this URL if we are
+ // performing a document load. We can't do that if mIPCClosed is set.
+ if (!mIPCClosed) {
+ PContentParent* pcp = Manager()->Manager();
+ MOZ_ASSERT(pcp, "We should have a manager if our IPC isn't closed");
+ DebugOnly<nsresult> rv =
+ static_cast<ContentParent*>(pcp)->AboutToLoadHttpFtpDocumentForChild(
+ chan, &args.shouldWaitForOnStartRequestSent());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ args.multiPartID() = multiPartID;
+ args.isFirstPartOfMultiPart() = isFirstPartOfMultiPart;
+ args.isLastPartOfMultiPart() = isLastPartOfMultiPart;
+
+ args.cacheExpirationTime() = nsICacheEntry::NO_EXPIRATION_TIME;
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(chan);
+
+ if (httpChannelImpl) {
+ httpChannelImpl->IsFromCache(&args.isFromCache());
+ httpChannelImpl->IsRacing(&args.isRacing());
+ httpChannelImpl->GetCacheEntryId(&args.cacheEntryId());
+ httpChannelImpl->GetCacheTokenFetchCount(&args.cacheFetchCount());
+ httpChannelImpl->GetCacheTokenExpirationTime(&args.cacheExpirationTime());
+ httpChannelImpl->GetProtocolVersion(args.protocolVersion());
+
+ mDataSentToChildProcess = httpChannelImpl->DataSentToChildProcess();
+
+ // If RCWN is enabled and cache wins, we can't use the ODA from socket
+ // process.
+ if (args.isRacing()) {
+ mDataSentToChildProcess =
+ httpChannelImpl->DataSentToChildProcess() && !args.isFromCache();
+ }
+ args.dataFromSocketProcess() = mDataSentToChildProcess;
+ }
+
+ // Propagate whether or not conversion should occur from the parent-side
+ // channel to the child-side channel. Then disable the parent-side
+ // conversion so that it only occurs in the child.
+ Unused << chan->GetApplyConversion(&args.applyConversion());
+ chan->SetApplyConversion(false);
+
+ // If we've already applied the conversion (as can happen if we installed
+ // a multipart converted), then don't apply it again on the child.
+ if (chan->HasAppliedConversion()) {
+ args.applyConversion() = false;
+ }
+
+ chan->GetStatus(&args.channelStatus());
+
+ // Keep the cache entry for future use when opening alternative streams.
+ // It could be already released by nsHttpChannel at that time.
+ nsCOMPtr<nsISupports> cacheEntry;
+
+ if (httpChannelImpl) {
+ httpChannelImpl->GetCacheToken(getter_AddRefs(cacheEntry));
+ mCacheEntry = do_QueryInterface(cacheEntry);
+ args.cacheEntryAvailable() = static_cast<bool>(mCacheEntry);
+
+ httpChannelImpl->GetCacheKey(&args.cacheKey());
+ httpChannelImpl->GetAlternativeDataType(args.altDataType());
+ }
+
+ args.altDataLength() = chan->GetAltDataLength();
+ args.deliveringAltData() = chan->IsDeliveringAltData();
+
+ args.securityInfo() = SecurityInfo();
+
+ chan->GetRedirectCount(&args.redirectCount());
+ chan->GetHasHTTPSRR(&args.hasHTTPSRR());
+
+ chan->GetIsProxyUsed(&args.isProxyUsed());
+
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+ mozilla::ipc::LoadInfoToParentLoadInfoForwarder(loadInfo,
+ &args.loadInfoForwarder());
+
+ nsHttpResponseHead* responseHead = chan->GetResponseHead();
+ bool useResponseHead = !!responseHead;
+ nsHttpResponseHead cleanedUpResponseHead;
+
+ if (responseHead &&
+ (responseHead->HasHeader(nsHttp::Set_Cookie) || multiPartID)) {
+ cleanedUpResponseHead = *responseHead;
+ cleanedUpResponseHead.ClearHeader(nsHttp::Set_Cookie);
+ if (multiPartID) {
+ nsCOMPtr<nsIChannel> multiPartChannel = do_QueryInterface(aRequest);
+ // For the multipart channel, use the parsed subtype instead. Note that
+ // `chan` is the underlying base channel of the multipart channel in this
+ // case, which is different from `multiPartChannel`.
+ MOZ_ASSERT(multiPartChannel);
+ nsAutoCString contentType;
+ multiPartChannel->GetContentType(contentType);
+ cleanedUpResponseHead.SetContentType(contentType);
+ }
+ responseHead = &cleanedUpResponseHead;
+ }
+
+ if (!responseHead) {
+ responseHead = &cleanedUpResponseHead;
+ }
+
+ if (chan->ChannelBlockedByOpaqueResponse() &&
+ chan->CachedOpaqueResponseBlockingPref()) {
+ responseHead->ClearHeaders();
+ }
+
+ chan->GetIsResolvedByTRR(&args.isResolvedByTRR());
+ chan->GetAllRedirectsSameOrigin(&args.allRedirectsSameOrigin());
+ chan->GetCrossOriginOpenerPolicy(&args.openerPolicy());
+ args.selfAddr() = chan->GetSelfAddr();
+ args.peerAddr() = chan->GetPeerAddr();
+ args.timing() = GetTimingAttributes(mChannel);
+ if (mOverrideReferrerInfo) {
+ args.overrideReferrerInfo() = ToRefPtr(std::move(mOverrideReferrerInfo));
+ }
+ if (!mCookie.IsEmpty()) {
+ args.cookie() = std::move(mCookie);
+ }
+
+ nsHttpRequestHead* requestHead = chan->GetRequestHead();
+ // !!! We need to lock headers and please don't forget to unlock them !!!
+ requestHead->Enter();
+
+ nsHttpHeaderArray cleanedUpRequestHeaders;
+ bool cleanedUpRequest = false;
+ if (requestHead->HasHeader(nsHttp::Cookie)) {
+ cleanedUpRequestHeaders = requestHead->Headers();
+ cleanedUpRequestHeaders.ClearHeader(nsHttp::Cookie);
+ cleanedUpRequest = true;
+ }
+
+ rv = NS_OK;
+
+ nsCOMPtr<nsICacheEntry> altDataSource;
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel =
+ do_QueryInterface(static_cast<nsIChannel*>(mChannel.get()));
+ if (cacheChannel) {
+ for (const auto& pref : cacheChannel->PreferredAlternativeDataTypes()) {
+ if (pref.type() == args.altDataType() &&
+ pref.deliverAltData() ==
+ nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::
+ SERIALIZE) {
+ altDataSource = mCacheEntry;
+ break;
+ }
+ }
+ }
+
+ nsIRequest::TRRMode effectiveMode = nsIRequest::TRR_DEFAULT_MODE;
+ mChannel->GetEffectiveTRRMode(&effectiveMode);
+ args.effectiveTRRMode() = effectiveMode;
+
+ TRRSkippedReason reason = TRRSkippedReason::TRR_UNSET;
+ mChannel->GetTrrSkipReason(&reason);
+ args.trrSkipReason() = reason;
+
+ if (mIPCClosed ||
+ !mBgParent->OnStartRequest(
+ *responseHead, useResponseHead,
+ cleanedUpRequest ? cleanedUpRequestHeaders : requestHead->Headers(),
+ args, altDataSource, chan->GetOnStartRequestStartTime())) {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ requestHead->Exit();
+
+ // Need to wait for the cookies/permissions to content process, which is sent
+ // via PContent in AboutToLoadHttpFtpDocumentForChild. For multipart channel,
+ // send only one time since the cookies/permissions are the same.
+ if (NS_SUCCEEDED(rv) && args.shouldWaitForOnStartRequestSent() &&
+ multiPartID.valueOr(0) == 0) {
+ LOG(("HttpChannelParent::SendOnStartRequestSent\n"));
+ Unused << SendOnStartRequestSent();
+ }
+
+ if (!args.timing().domainLookupEnd().IsNull() &&
+ !args.timing().connectStart().IsNull()) {
+ nsAutoCString protocolVersion;
+ mChannel->GetProtocolVersion(protocolVersion);
+ uint32_t classOfServiceFlags = 0;
+ mChannel->GetClassFlags(&classOfServiceFlags);
+ nsAutoCString cosString;
+ ClassOfService::ToString(classOfServiceFlags, cosString);
+ nsAutoCString key(
+ nsPrintfCString("%s_%s", protocolVersion.get(), cosString.get()));
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_DNS_END_TO_CONNECT_START_EXP_MS, key,
+ args.timing().domainLookupEnd(), args.timing().connectStart());
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ LOG(("HttpChannelParent::OnStopRequest: [this=%p aRequest=%p status=%" PRIx32
+ "]\n",
+ this, aRequest, static_cast<uint32_t>(aStatusCode)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+ if (httpChannelImpl) {
+ httpChannelImpl->SetWarningReporter(nullptr);
+ }
+
+ nsHttpHeaderArray* responseTrailer = mChannel->GetResponseTrailers();
+
+ nsTArray<ConsoleReportCollected> consoleReports;
+
+ RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(mChannel);
+ TimeStamp onStopRequestStart;
+ if (httpChannel) {
+ httpChannel->StealConsoleReports(consoleReports);
+ onStopRequestStart = httpChannel->GetOnStopRequestStartTime();
+ }
+
+ // Either IPC channel is closed or background channel
+ // is ready to send OnStopRequest.
+ MOZ_ASSERT(mIPCClosed || mBgParent);
+
+ if (mDataSentToChildProcess) {
+ if (mIPCClosed || !mBgParent ||
+ !mBgParent->OnConsoleReport(consoleReports)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+ }
+
+ // If we're handling a multi-part stream, then send this directly
+ // over PHttpChannel to make synchronization easier.
+ if (mIPCClosed || !mBgParent ||
+ !mBgParent->OnStopRequest(
+ aStatusCode, GetTimingAttributes(mChannel),
+ responseTrailer ? *responseTrailer : nsHttpHeaderArray(),
+ consoleReports, onStopRequestStart)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (NeedFlowControl()) {
+ bool isLocal = false;
+ NetAddr peerAddr = mChannel->GetPeerAddr();
+
+#if defined(XP_UNIX)
+ // Unix-domain sockets are always local.
+ isLocal = (peerAddr.raw.family == PR_AF_LOCAL);
+#endif
+
+ isLocal = isLocal || peerAddr.IsLoopbackAddr();
+
+ if (!isLocal) {
+ if (!mHasSuspendedByBackPressure) {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
+ NotSuspended);
+ } else {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
+ Suspended);
+
+ // Only analyze non-local suspended cases, which we are interested in.
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ Telemetry::Accumulate(
+ Telemetry::NETWORK_BACK_PRESSURE_SUSPENSION_CP_TYPE,
+ loadInfo->InternalContentPolicyType());
+ }
+ } else {
+ if (!mHasSuspendedByBackPressure) {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
+ NotSuspendedLocal);
+ } else {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
+ SuspendedLocal);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIMultiPartChannelListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnAfterLastPart(nsresult aStatus) {
+ LOG(("HttpChannelParent::OnAfterLastPart [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If IPC channel is closed, there is nothing we can do. Just return NS_OK.
+ if (mIPCClosed) {
+ return NS_OK;
+ }
+
+ // If IPC channel is open, background channel should be ready to send
+ // OnAfterLastPart.
+ MOZ_ASSERT(mBgParent);
+
+ if (!mBgParent || !mBgParent->OnAfterLastPart(aStatus)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOG(("HttpChannelParent::OnDataAvailable [this=%p aRequest=%p offset=%" PRIu64
+ " count=%" PRIu32 "]\n",
+ this, aRequest, aOffset, aCount));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mDataSentToChildProcess) {
+ uint32_t n;
+ return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &n);
+ }
+
+ nsresult channelStatus = NS_OK;
+ mChannel->GetStatus(&channelStatus);
+
+ nsresult transportStatus = NS_NET_STATUS_RECEIVING_FROM;
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+ TimeStamp onDataAvailableStart = TimeStamp::Now();
+ if (httpChannelImpl) {
+ if (httpChannelImpl->IsReadingFromCache()) {
+ transportStatus = NS_NET_STATUS_READING;
+ }
+ onDataAvailableStart = httpChannelImpl->GetDataAvailableStartTime();
+ }
+
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Either IPC channel is closed or background channel
+ // is ready to send OnTransportAndData.
+ MOZ_ASSERT(mIPCClosed || mBgParent);
+
+ if (mIPCClosed || !mBgParent ||
+ !mBgParent->OnTransportAndData(channelStatus, transportStatus, aOffset,
+ aCount, data, onDataAvailableStart)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ int32_t count = static_cast<int32_t>(aCount);
+
+ if (NeedFlowControl()) {
+ // We're going to run out of sending window size
+ if (mSendWindowSize > 0 && mSendWindowSize <= count) {
+ MOZ_ASSERT(!mSuspendedForFlowControl);
+ LOG((" suspend the channel due to e10s backpressure"));
+ Unused << mChannel->Suspend();
+ mSuspendedForFlowControl = true;
+ mHasSuspendedByBackPressure = true;
+ } else if (!mResumedTimestamp.IsNull()) {
+ // Calculate the delay when the first packet arrived after resume
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_BACK_PRESSURE_SUSPENSION_DELAY_TIME_MS,
+ mResumedTimestamp);
+ mResumedTimestamp = TimeStamp();
+ }
+ mSendWindowSize -= count;
+ }
+
+ return NS_OK;
+}
+
+bool HttpChannelParent::NeedFlowControl() {
+ if (mCacheNeedFlowControlInitialized) {
+ return mNeedFlowControl;
+ }
+
+ int64_t contentLength = -1;
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+
+ // By design, we won't trigger the flow control if
+ // a. pref-out
+ // b. the resource is from cache or partial cache
+ // c. the resource is small
+ // d. data will be sent from socket process to child process directly
+ // Note that we served the cached resource first for partical cache, which is
+ // ignored here since we only take the first ODA into consideration.
+ if (gHttpHandler->SendWindowSize() == 0 || !httpChannelImpl ||
+ httpChannelImpl->IsReadingFromCache() ||
+ NS_FAILED(httpChannelImpl->GetContentLength(&contentLength)) ||
+ contentLength < gHttpHandler->SendWindowSize() ||
+ mDataSentToChildProcess) {
+ mNeedFlowControl = false;
+ }
+ mCacheNeedFlowControlInitialized = true;
+ return mNeedFlowControl;
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvBytesRead(
+ const int32_t& aCount) {
+ if (!NeedFlowControl()) {
+ return IPC_OK();
+ }
+
+ LOG(("HttpChannelParent::RecvBytesRead [this=%p count=%" PRId32 "]\n", this,
+ aCount));
+
+ if (mSendWindowSize <= 0 && mSendWindowSize + aCount > 0) {
+ MOZ_ASSERT(mSuspendedForFlowControl);
+ LOG((" resume the channel due to e10s backpressure relief"));
+ Unused << mChannel->Resume();
+ mSuspendedForFlowControl = false;
+
+ mResumedTimestamp = TimeStamp::Now();
+ }
+ mSendWindowSize += aCount;
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvOpenOriginalCacheInputStream() {
+ if (mIPCClosed) {
+ return IPC_OK();
+ }
+ Maybe<IPCStream> ipcStream;
+ if (mCacheEntry) {
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(inputStream));
+ if (NS_SUCCEEDED(rv)) {
+ Unused << mozilla::ipc::SerializeIPCStream(
+ inputStream.forget(), ipcStream, /* aAllowLazy */ false);
+ }
+ }
+
+ Unused << SendOriginalCacheInputStreamAvailable(ipcStream);
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIProgressEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnProgress(nsIRequest* aRequest, int64_t aProgress,
+ int64_t aProgressMax) {
+ LOG(("HttpChannelParent::OnProgress [this=%p progress=%" PRId64 "max=%" PRId64
+ "]\n",
+ this, aProgress, aProgressMax));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If IPC channel is closed, there is nothing we can do. Just return NS_OK.
+ if (mIPCClosed) {
+ return NS_OK;
+ }
+
+ // If it indicates this precedes OnDataAvailable, child can derive the value
+ // in ODA.
+ if (mIgnoreProgress) {
+ mIgnoreProgress = false;
+ return NS_OK;
+ }
+
+ // If IPC channel is open, background channel should be ready to send
+ // OnProgress.
+ MOZ_ASSERT(mBgParent);
+
+ // Send OnProgress events to the child for data upload progress notifications
+ // (i.e. status == NS_NET_STATUS_SENDING_TO) or if the channel has
+ // LOAD_BACKGROUND set.
+ if (!mBgParent || !mBgParent->OnProgress(aProgress, aProgressMax)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::OnStatus(nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aStatusArg) {
+ LOG(("HttpChannelParent::OnStatus [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aStatus)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If IPC channel is closed, there is nothing we can do. Just return NS_OK.
+ if (mIPCClosed) {
+ return NS_OK;
+ }
+
+ // If this precedes OnDataAvailable, transportStatus will be derived in ODA.
+ if (aStatus == NS_NET_STATUS_RECEIVING_FROM ||
+ aStatus == NS_NET_STATUS_READING) {
+ // The transport status and progress generated by ODA will be coalesced
+ // into one IPC message. Therefore, we can ignore the next OnProgress event
+ // since it is generated by ODA as well.
+ mIgnoreProgress = true;
+ return NS_OK;
+ }
+
+ // If IPC channel is open, background channel should be ready to send
+ // OnStatus.
+ MOZ_ASSERT(mIPCClosed || mBgParent);
+
+ // Otherwise, send to child now
+ if (!mBgParent || !mBgParent->OnStatus(aStatus)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIParentChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::SetParentListener(ParentChannelListener* aListener) {
+ LOG(("HttpChannelParent::SetParentListener [this=%p aListener=%p]\n", this,
+ aListener));
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(!mParentListener,
+ "SetParentListener should only be called for "
+ "new HttpChannelParents after a redirect, when "
+ "mParentListener is null.");
+ mParentListener = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ LOG(("HttpChannelParent::SetClassifierMatchedInfo [this=%p]\n", this));
+ if (!mIPCClosed) {
+ MOZ_ASSERT(mBgParent);
+ Unused << mBgParent->OnSetClassifierMatchedInfo(aList, aProvider,
+ aFullHash);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ LOG(("HttpChannelParent::SetClassifierMatchedTrackingInfo [this=%p]\n",
+ this));
+ if (!mIPCClosed) {
+ MOZ_ASSERT(mBgParent);
+ Unused << mBgParent->OnSetClassifierMatchedTrackingInfo(aLists,
+ aFullHashes);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ LOG(
+ ("HttpChannelParent::NotifyClassificationFlags "
+ "classificationFlags=%" PRIu32 ", thirdparty=%d [this=%p]\n",
+ aClassificationFlags, static_cast<int>(aIsThirdParty), this));
+ if (!mIPCClosed) {
+ MOZ_ASSERT(mBgParent);
+ Unused << mBgParent->OnNotifyClassificationFlags(aClassificationFlags,
+ aIsThirdParty);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::Delete() {
+ if (!mIPCClosed) Unused << DoSendDeleteSelf();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::GetRemoteType(nsACString& aRemoteType) {
+ if (!CanSend()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ dom::PContentParent* pcp = Manager()->Manager();
+ aRemoteType = static_cast<dom::ContentParent*>(pcp)->GetRemoteType();
+ return NS_OK;
+}
+
+bool HttpChannelParent::IsRedirectDueToAuthRetry(uint32_t redirectFlags) {
+ return (redirectFlags & nsIChannelEventSink::REDIRECT_AUTH_RETRY);
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIParentRedirectingChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::StartRedirect(nsIChannel* newChannel, uint32_t redirectFlags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ nsresult rv;
+
+ LOG(("HttpChannelParent::StartRedirect [this=%p, newChannel=%p callback=%p]",
+ this, newChannel, callback));
+
+ // Register the new channel and obtain id for it
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+
+ mRedirectChannelId = nsContentUtils::GenerateLoadIdentifier();
+ rv = registrar->RegisterChannel(newChannel, mRedirectChannelId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("Registered %p channel under id=%" PRIx64, newChannel,
+ mRedirectChannelId));
+
+ if (mIPCClosed) {
+ return NS_BINDING_ABORTED;
+ }
+
+ // If this is an internal redirect for service worker interception or
+ // internal redirect due to auth retries, then hide it from the child
+ // process. The original e10s interception code was not designed with this
+ // in mind and its not necessary to replace the HttpChannelChild/Parent
+ // objects in this case.
+ if (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
+ nsCOMPtr<nsIInterceptedChannel> oldIntercepted =
+ do_QueryInterface(static_cast<nsIChannel*>(mChannel.get()));
+ nsCOMPtr<nsIInterceptedChannel> newIntercepted =
+ do_QueryInterface(newChannel);
+
+ // 1. We only want to hide the special internal redirects from
+ // nsHttpChannel to InterceptedHttpChannel.
+ // 2. We want to allow through internal redirects
+ // initiated from the InterceptedHttpChannel even if they are to another
+ // InterceptedHttpChannel, except the interception reset, since
+ // corresponding HttpChannelChild/Parent objects can be reused for reset
+ // case.
+ // 3. If this is an internal redirect due to auth retry then we will
+ // hide it from the child process
+
+ if ((!oldIntercepted && newIntercepted) ||
+ (oldIntercepted && !newIntercepted && oldIntercepted->IsReset()) ||
+ (IsRedirectDueToAuthRetry(redirectFlags))) {
+ // We need to move across the reserved and initial client information
+ // to the new channel. Normally this would be handled by the child
+ // ClientChannelHelper, but that is not notified of this redirect since
+ // we're not propagating it back to the child process.
+ nsCOMPtr<nsILoadInfo> oldLoadInfo = mChannel->LoadInfo();
+
+ nsCOMPtr<nsILoadInfo> newLoadInfo = newChannel->LoadInfo();
+
+ Maybe<ClientInfo> reservedClientInfo(
+ oldLoadInfo->GetReservedClientInfo());
+ if (reservedClientInfo.isSome()) {
+ newLoadInfo->SetReservedClientInfo(reservedClientInfo.ref());
+ }
+
+ Maybe<ClientInfo> initialClientInfo(oldLoadInfo->GetInitialClientInfo());
+ if (initialClientInfo.isSome()) {
+ newLoadInfo->SetInitialClientInfo(initialClientInfo.ref());
+ }
+
+ // If this is ServiceWorker fallback redirect, info HttpChannelChild to
+ // detach StreamFilters. Otherwise StreamFilters will be attached twice
+ // on the same HttpChannelChild when opening the new nsHttpChannel.
+ if (oldIntercepted) {
+ Unused << DetachStreamFilters();
+ }
+
+ // Re-link the HttpChannelParent to the new channel.
+ nsCOMPtr<nsIChannel> linkedChannel;
+ rv = NS_LinkRedirectChannels(mRedirectChannelId, this,
+ getter_AddRefs(linkedChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(linkedChannel == newChannel);
+
+ // We immediately store the channel as our nested mChannel.
+ // None of the redirect IPC messaging takes place.
+ mChannel = do_QueryObject(newChannel);
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+ }
+ }
+
+ // Sending down the original URI, because that is the URI we have
+ // to construct the channel from - this is the URI we've been actually
+ // redirected to. URI of the channel may be an inner channel URI.
+ // URI of the channel will be reconstructed by the protocol handler
+ // on the child process, no need to send it then.
+ nsCOMPtr<nsIURI> newOriginalURI;
+ newChannel->GetOriginalURI(getter_AddRefs(newOriginalURI));
+
+ uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL;
+ MOZ_ALWAYS_SUCCEEDS(newChannel->GetLoadFlags(&newLoadFlags));
+
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(SecurityInfo());
+
+ // If the channel is a HTTP channel, we also want to inform the child
+ // about the parent's channelId attribute, so that both parent and child
+ // share the same ID. Useful for monitoring channel activity in devtools.
+ uint64_t channelId = 0;
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+ if (httpChannel) {
+ rv = httpChannel->GetChannelId(&channelId);
+ NS_ENSURE_SUCCESS(rv, NS_BINDING_ABORTED);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+
+ ParentLoadInfoForwarderArgs loadInfoForwarderArg;
+ mozilla::ipc::LoadInfoToParentLoadInfoForwarder(loadInfo,
+ &loadInfoForwarderArg);
+
+ nsHttpResponseHead* responseHead = mChannel->GetResponseHead();
+
+ nsHttpResponseHead cleanedUpResponseHead;
+ if (responseHead && responseHead->HasHeader(nsHttp::Set_Cookie)) {
+ cleanedUpResponseHead = *responseHead;
+ cleanedUpResponseHead.ClearHeader(nsHttp::Set_Cookie);
+ responseHead = &cleanedUpResponseHead;
+ }
+
+ if (!responseHead) {
+ responseHead = &cleanedUpResponseHead;
+ }
+
+ if (!mIPCClosed) {
+ if (!SendRedirect1Begin(mRedirectChannelId, newOriginalURI, newLoadFlags,
+ redirectFlags, loadInfoForwarderArg, *responseHead,
+ securityInfo, channelId, mChannel->GetPeerAddr(),
+ GetTimingAttributes(mChannel))) {
+ return NS_BINDING_ABORTED;
+ }
+ }
+
+ // Result is handled in RecvRedirect2Verify above
+
+ mRedirectChannel = newChannel;
+ mRedirectCallback = callback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::CompleteRedirect(nsresult status) {
+ LOG(("HttpChannelParent::CompleteRedirect [this=%p status=0x%X]\n", this,
+ static_cast<uint32_t>(status)));
+
+ // If this was an internal redirect for a service worker interception then
+ // we will not have a redirecting channel here. Hide this redirect from
+ // the child.
+ if (!mRedirectChannel) {
+ return NS_OK;
+ }
+
+ if (!mIPCClosed) {
+ // TODO: check return value: assume child dead if failed
+ if (NS_SUCCEEDED(status)) {
+ Unused << SendRedirect3Complete();
+ } else {
+ Unused << SendRedirectFailed(status);
+ }
+ }
+
+ mRedirectChannel = nullptr;
+ return NS_OK;
+}
+
+nsresult HttpChannelParent::OpenAlternativeOutputStream(
+ const nsACString& type, int64_t predictedSize,
+ nsIAsyncOutputStream** _retval) {
+ // We need to make sure the child does not call SendDocumentChannelCleanup()
+ // before opening the altOutputStream, because that clears mCacheEntry.
+ if (!mCacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsresult rv =
+ mCacheEntry->OpenAlternativeOutputStream(type, predictedSize, _retval);
+ if (NS_SUCCEEDED(rv)) {
+ mCacheEntry->SetMetaDataElement("alt-data-from-child", "1");
+ }
+ return rv;
+}
+
+already_AddRefed<nsITransportSecurityInfo> HttpChannelParent::SecurityInfo() {
+ if (!mChannel) {
+ return nullptr;
+ }
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ mChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
+ return securityInfo.forget();
+}
+
+bool HttpChannelParent::DoSendDeleteSelf() {
+ mIPCClosed = true;
+ bool rv = SendDeleteSelf();
+
+ CleanupBackgroundChannel();
+
+ return rv;
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvDeletingChannel() {
+ // We need to ensure that the parent channel will not be sending any more IPC
+ // messages after this, as the child is going away. DoSendDeleteSelf will
+ // set mIPCClosed = true;
+ if (!DoSendDeleteSelf()) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelSecurityWarningReporter
+//-----------------------------------------------------------------------------
+
+nsresult HttpChannelParent::ReportSecurityMessage(
+ const nsAString& aMessageTag, const nsAString& aMessageCategory) {
+ if (mIPCClosed || NS_WARN_IF(!SendReportSecurityMessage(
+ nsString(aMessageTag), nsString(aMessageCategory)))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIAsyncVerifyRedirectReadyCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::ReadyToVerify(nsresult aResult) {
+ LOG(("HttpChannelParent::ReadyToVerify [this=%p result=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aResult)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ContinueRedirect2Verify(aResult);
+
+ return NS_OK;
+}
+
+void HttpChannelParent::DoSendSetPriority(int16_t aValue) {
+ if (!mIPCClosed) {
+ Unused << SendSetPriority(aValue);
+ }
+}
+
+nsresult HttpChannelParent::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ if (mIPCClosed ||
+ NS_WARN_IF(!SendLogBlockedCORSRequest(
+ nsString(aMessage), nsCString(aCategory), aIsWarning))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult HttpChannelParent::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) {
+ if (mIPCClosed || NS_WARN_IF(!SendLogMimeTypeMismatch(
+ nsCString(aMessageName), aWarning, nsString(aURL),
+ nsString(aContentType)))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aRedirectFlags,
+ nsIAsyncVerifyRedirectCallback* aCallback) {
+ LOG(
+ ("HttpChannelParent::AsyncOnChannelRedirect [this=%p, old=%p, "
+ "new=%p, flags=%u]",
+ this, aOldChannel, aNewChannel, aRedirectFlags));
+
+ return StartRedirect(aNewChannel, aRedirectFlags, aCallback);
+}
+
+//-----------------------------------------------------------------------------
+// nsIRedirectResultListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnRedirectResult(nsresult status) {
+ LOG(("HttpChannelParent::OnRedirectResult [this=%p, status=0x%X]", this,
+ static_cast<uint32_t>(status)));
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIParentChannel> redirectChannel;
+ if (mRedirectChannelId) {
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+
+ rv = registrar->GetParentChannel(mRedirectChannelId,
+ getter_AddRefs(redirectChannel));
+ if (NS_FAILED(rv) || !redirectChannel) {
+ // Redirect might get canceled before we got AsyncOnChannelRedirect
+ LOG(("Registered parent channel not found under id=%" PRIx64,
+ mRedirectChannelId));
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = registrar->GetRegisteredChannel(mRedirectChannelId,
+ getter_AddRefs(newChannel));
+ MOZ_ASSERT(newChannel, "Already registered channel not found");
+
+ if (NS_SUCCEEDED(rv)) {
+ newChannel->Cancel(NS_BINDING_ABORTED);
+ }
+ }
+
+ // Release all previously registered channels, they are no longer need to be
+ // kept in the registrar from this moment.
+ registrar->DeregisterChannels(mRedirectChannelId);
+
+ mRedirectChannelId = 0;
+ }
+
+ if (!redirectChannel) {
+ if (NS_FAILED(rv)) {
+ status = rv;
+ } else {
+ status = NS_ERROR_NULL_POINTER;
+ }
+ }
+
+ CompleteRedirect(status);
+
+ if (NS_SUCCEEDED(status)) {
+ if (!SameCOMIdentity(redirectChannel,
+ static_cast<nsIParentRedirectingChannel*>(this))) {
+ Delete();
+ mParentListener->SetListenerAfterRedirect(redirectChannel);
+ redirectChannel->SetParentListener(mParentListener);
+ }
+ } else if (redirectChannel) {
+ // Delete the redirect target channel: continue using old channel
+ redirectChannel->Delete();
+ }
+
+ return NS_OK;
+}
+
+void HttpChannelParent::OverrideReferrerInfoDuringBeginConnect(
+ nsIReferrerInfo* aReferrerInfo) {
+ MOZ_ASSERT(aReferrerInfo);
+ MOZ_ASSERT(!mAfterOnStartRequestBegun);
+
+ mOverrideReferrerInfo = aReferrerInfo;
+}
+
+auto HttpChannelParent::AttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aParentEndpoint,
+ Endpoint<extensions::PStreamFilterChild>&& aChildEndpoint)
+ -> RefPtr<ChildEndpointPromise> {
+ LOG(("HttpChannelParent::AttachStreamFilter [this=%p]", this));
+ MOZ_ASSERT(!mAfterOnStartRequestBegun);
+
+ if (mIPCClosed) {
+ return ChildEndpointPromise::CreateAndReject(false, __func__);
+ }
+
+ // If IPC channel is open, background channel should be ready to send
+ // SendAttachStreamFilter.
+ MOZ_ASSERT(mBgParent);
+ return InvokeAsync(mBgParent->GetBackgroundTarget(), mBgParent.get(),
+ __func__, &HttpBackgroundChannelParent::AttachStreamFilter,
+ std::move(aParentEndpoint), std::move(aChildEndpoint));
+}
+
+auto HttpChannelParent::DetachStreamFilters() -> RefPtr<GenericPromise> {
+ LOG(("HttpChannelParent::DeattachStreamFilter [this=%p]", this));
+ MOZ_ASSERT(!mAfterOnStartRequestBegun);
+
+ if (NS_WARN_IF(mIPCClosed)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ MOZ_ASSERT(mBgParent);
+ return InvokeAsync(mBgParent->GetBackgroundTarget(), mBgParent.get(),
+ __func__,
+ &HttpBackgroundChannelParent::DetachStreamFilters);
+}
+
+void HttpChannelParent::SetHttpChannelFromEarlyHintPreloader(
+ HttpBaseChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+ if (mChannel) {
+ MOZ_ASSERT(false, "SetHttpChannel called with mChannel aready set");
+ return;
+ }
+
+ mChannel = aChannel;
+}
+
+void HttpChannelParent::SetCookie(nsCString&& aCookie) {
+ LOG(("HttpChannelParent::SetCookie [this=%p]", this));
+ MOZ_ASSERT(!mAfterOnStartRequestBegun);
+ MOZ_ASSERT(mCookie.IsEmpty());
+
+ // The loadGroup of the channel in the parent process could be null in the
+ // XPCShell content process test, see test_cookiejars_wrap.js. In this case,
+ // we cannot explicitly set the loadGroup for the parent channel because it's
+ // created from the content process. To workaround this, we add a testing pref
+ // to skip this check.
+ if (!Preferences::GetBool(
+ "network.cookie.skip_browsing_context_check_in_parent_for_testing") &&
+ mChannel->IsBrowsingContextDiscarded()) {
+ return;
+ }
+ mCookie = std::move(aCookie);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h
new file mode 100644
index 0000000000..fdd55f9a1d
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -0,0 +1,333 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_HttpChannelParent_h
+#define mozilla_net_HttpChannelParent_h
+
+#include "HttpBaseChannel.h"
+#include "nsHttp.h"
+#include "mozilla/net/PHttpChannelParent.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/MozPromise.h"
+#include "nsIParentRedirectingChannel.h"
+#include "nsIProgressEventSink.h"
+#include "nsIChannelEventSink.h"
+#include "nsIRedirectResultListener.h"
+#include "nsHttpChannel.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIURI.h"
+
+class nsICacheEntry;
+
+#define HTTP_CHANNEL_PARENT_IID \
+ { \
+ 0x982b2372, 0x7aa5, 0x4e8a, { \
+ 0xbd, 0x9f, 0x89, 0x74, 0xd7, 0xf0, 0x58, 0xeb \
+ } \
+ }
+
+namespace mozilla {
+
+namespace dom {
+class BrowserParent;
+} // namespace dom
+
+namespace net {
+
+class HttpBackgroundChannelParent;
+class ParentChannelListener;
+class ChannelEventQueue;
+
+class HttpChannelParent final : public nsIInterfaceRequestor,
+ public PHttpChannelParent,
+ public nsIParentRedirectingChannel,
+ public nsIProgressEventSink,
+ public HttpChannelSecurityWarningReporter,
+ public nsIAsyncVerifyRedirectReadyCallback,
+ public nsIChannelEventSink,
+ public nsIRedirectResultListener,
+ public nsIMultiPartChannelListener {
+ virtual ~HttpChannelParent();
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIPARENTREDIRECTINGCHANNEL
+ NS_DECL_NSIPROGRESSEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIASYNCVERIFYREDIRECTREADYCALLBACK
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIREDIRECTRESULTLISTENER
+ NS_DECL_NSIMULTIPARTCHANNELLISTENER
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_CHANNEL_PARENT_IID)
+
+ HttpChannelParent(dom::BrowserParent* iframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus);
+
+ [[nodiscard]] bool Init(const HttpChannelCreationArgs& aArgs);
+
+ // Forwarded to nsHttpChannel::SetApplyConversion.
+ void SetApplyConversion(bool aApplyConversion) {
+ if (mChannel) {
+ mChannel->SetApplyConversion(aApplyConversion);
+ }
+ }
+
+ [[nodiscard]] nsresult OpenAlternativeOutputStream(
+ const nsACString& type, int64_t predictedSize,
+ nsIAsyncOutputStream** _retval);
+
+ // Callbacks for each asynchronous tasks required in AsyncOpen
+ // procedure, will call InvokeAsyncOpen when all the expected
+ // tasks is finished successfully or when any failure happened.
+ // @see mAsyncOpenBarrier.
+ void TryInvokeAsyncOpen(nsresult aRv);
+
+ void InvokeAsyncOpen(nsresult rv);
+
+ void InvokeEarlyHintPreloader(nsresult rv, uint64_t aEarlyHintPreloaderId);
+
+ // Calls SendSetPriority if mIPCClosed is false.
+ void DoSendSetPriority(int16_t aValue);
+
+ // Callback while background channel is ready.
+ void OnBackgroundParentReady(HttpBackgroundChannelParent* aBgParent);
+ // Callback while background channel is destroyed.
+ void OnBackgroundParentDestroyed();
+
+ base::ProcessId OtherPid() const;
+
+ // Inform the child actor that our referrer info was modified late during
+ // BeginConnect.
+ void OverrideReferrerInfoDuringBeginConnect(nsIReferrerInfo* aReferrerInfo);
+
+ // Set the cookie string, which will be informed to the child actor during
+ // PHttpBackgroundChannel::OnStartRequest. Note that CookieService also sends
+ // the information to all actors via PContent, a main thread IPC, which could
+ // be slower than background IPC PHttpBackgroundChannel::OnStartRequest.
+ // Therefore, another cookie notification via PBackground is needed to
+ // guarantee the listener in child has the necessary cookies before
+ // OnStartRequest.
+ void SetCookie(nsCString&& aCookie);
+
+ using ChildEndpointPromise =
+ MozPromise<ipc::Endpoint<extensions::PStreamFilterChild>, bool, true>;
+ [[nodiscard]] RefPtr<ChildEndpointPromise> AttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aParentEndpoint,
+ Endpoint<extensions::PStreamFilterChild>&& aChildEndpoint);
+ [[nodiscard]] RefPtr<GenericPromise> DetachStreamFilters();
+
+ // Should only be called from EarlyHintPreloader. mChannel should be null at
+ // the point of calling. Sets mChannel to aChannel. Used by the
+ // EarlyHintPreloader to redirect the channel to this parent as soon as the
+ // final channel becomes available after all http redirects.
+ void SetHttpChannelFromEarlyHintPreloader(HttpBaseChannel* aChannel);
+
+ protected:
+ // used to connect redirected-to channel in parent with just created
+ // ChildChannel. Used during redirects.
+ [[nodiscard]] bool ConnectChannel(const uint32_t& registrarId);
+
+ [[nodiscard]] bool DoAsyncOpen(
+ nsIURI* uri, nsIURI* originalUri, nsIURI* docUri,
+ nsIReferrerInfo* aReferrerInfo, nsIURI* aAPIRedirectToURI,
+ nsIURI* topWindowUri, const uint32_t& loadFlags,
+ const RequestHeaderTuples& requestHeaders, const nsCString& requestMethod,
+ const Maybe<IPCStream>& uploadStream, const bool& uploadStreamHasHeaders,
+ const int16_t& priority, const ClassOfService& classOfService,
+ const uint8_t& redirectionLimit, const bool& allowSTS,
+ const uint32_t& thirdPartyFlags, const bool& doResumeAt,
+ const uint64_t& startPos, const nsCString& entityID,
+ const bool& allowSpdy, const bool& allowHttp3, const bool& allowAltSvc,
+ const bool& beConservative, const bool& bypassProxy,
+ const uint32_t& tlsFlags, const LoadInfoArgs& aLoadInfoArgs,
+ const uint32_t& aCacheKey, const uint64_t& aRequestContextID,
+ const Maybe<CorsPreflightArgs>& aCorsPreflightArgs,
+ const uint32_t& aInitialRwin, const bool& aBlockAuthPrompt,
+ const bool& aAllowStaleCacheContent,
+ const bool& aPreferCacheLoadOverBypass, const nsCString& aContentTypeHint,
+ const dom::RequestMode& aRequestMode, const uint32_t& aRedirectMode,
+ const uint64_t& aChannelId, const nsString& aIntegrityMetadata,
+ const uint64_t& aContentWindowId,
+ const nsTArray<PreferredAlternativeDataTypeParams>&
+ aPreferredAlternativeTypes,
+ const uint64_t& aBrowserId, const TimeStamp& aLaunchServiceWorkerStart,
+ const TimeStamp& aLaunchServiceWorkerEnd,
+ const TimeStamp& aDispatchFetchEventStart,
+ const TimeStamp& aDispatchFetchEventEnd,
+ const TimeStamp& aHandleFetchEventStart,
+ const TimeStamp& aHandleFetchEventEnd,
+ const bool& aForceMainDocumentChannel,
+ const TimeStamp& aNavigationStartTimeStamp,
+ const uint64_t& aEarlyHintPreloaderId,
+ const nsAString& aClassicScriptHintCharset,
+ const nsAString& aDocumentCharacterSet,
+ const bool& aIsUserAgentHeaderModified);
+
+ virtual mozilla::ipc::IPCResult RecvSetPriority(
+ const int16_t& priority) override;
+ virtual mozilla::ipc::IPCResult RecvSetClassOfService(
+ const ClassOfService& cos) override;
+ virtual mozilla::ipc::IPCResult RecvSuspend() override;
+ virtual mozilla::ipc::IPCResult RecvResume() override;
+ virtual mozilla::ipc::IPCResult RecvCancel(
+ const nsresult& status, const uint32_t& requestBlockingReason,
+ const nsACString& reason,
+ const mozilla::Maybe<nsCString>& logString) override;
+ virtual mozilla::ipc::IPCResult RecvRedirect2Verify(
+ const nsresult& result, const RequestHeaderTuples& changedHeaders,
+ const uint32_t& aSourceRequestBlockingReason,
+ const Maybe<ChildLoadInfoForwarderArgs>& aTargetLoadInfoForwarder,
+ const uint32_t& loadFlags, nsIReferrerInfo* aReferrerInfo,
+ nsIURI* apiRedirectUri,
+ const Maybe<CorsPreflightArgs>& aCorsPreflightArgs) override;
+ virtual mozilla::ipc::IPCResult RecvDocumentChannelCleanup(
+ const bool& clearCacheEntry) override;
+ virtual mozilla::ipc::IPCResult RecvRemoveCorsPreflightCacheEntry(
+ nsIURI* uri, const mozilla::ipc::PrincipalInfo& requestingPrincipal,
+ const OriginAttributes& originAttributes) override;
+ virtual mozilla::ipc::IPCResult RecvSetCookies(
+ const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes,
+ nsIURI* aHost, const bool& aFromHttp,
+ nsTArray<CookieStruct>&& aCookies) override;
+ virtual mozilla::ipc::IPCResult RecvBytesRead(const int32_t& aCount) override;
+ virtual mozilla::ipc::IPCResult RecvOpenOriginalCacheInputStream() override;
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ friend class ParentChannelListener;
+ RefPtr<mozilla::dom::BrowserParent> mBrowserParent;
+
+ [[nodiscard]] nsresult ReportSecurityMessage(
+ const nsAString& aMessageTag, const nsAString& aMessageCategory) override;
+ nsresult LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning = false) override;
+ nsresult LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override;
+
+ // Calls SendDeleteSelf and sets mIPCClosed to true because we should not
+ // send any more messages after that. Bug 1274886
+ [[nodiscard]] bool DoSendDeleteSelf();
+ // Called to notify the parent channel to not send any more IPC messages.
+ virtual mozilla::ipc::IPCResult RecvDeletingChannel() override;
+
+ private:
+ already_AddRefed<nsITransportSecurityInfo> SecurityInfo();
+
+ // final step for Redirect2Verify procedure, will be invoked while both
+ // redirecting and redirected channel are ready or any error happened.
+ // OnRedirectVerifyCallback will be invoked for finishing the async
+ // redirect verification procedure.
+ void ContinueRedirect2Verify(const nsresult& aResult);
+
+ void AsyncOpenFailed(nsresult aRv);
+
+ // Request to pair with a HttpBackgroundChannelParent with the same channel
+ // id, a promise will be returned so the caller can append callbacks on it.
+ // If called multiple times before mBgParent is available, the same promise
+ // will be returned and the callbacks will be invoked in order.
+ [[nodiscard]] RefPtr<GenericNonExclusivePromise> WaitForBgParent(
+ uint64_t aChannelId);
+
+ // Remove the association with background channel after main-thread IPC
+ // is about to be destroyed or no further event is going to be sent, i.e.,
+ // DocumentChannelCleanup.
+ void CleanupBackgroundChannel();
+
+ // Check if the channel needs to enable the flow control on the IPC channel.
+ // That is, we may suspend the channel if the ODA-s to child process are not
+ // consumed quickly enough. Otherwise, memory explosion could happen.
+ bool NeedFlowControl();
+
+ bool IsRedirectDueToAuthRetry(uint32_t redirectFlags);
+
+ int32_t mSendWindowSize;
+
+ friend class HttpBackgroundChannelParent;
+
+ uint64_t mEarlyHintPreloaderId{};
+
+ RefPtr<HttpBaseChannel> mChannel;
+ nsCOMPtr<nsICacheEntry> mCacheEntry;
+
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ RefPtr<nsHttpHandler> mHttpHandler;
+
+ RefPtr<ParentChannelListener> mParentListener;
+
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ RefPtr<HttpBackgroundChannelParent> mBgParent;
+
+ MozPromiseHolder<GenericNonExclusivePromise> mPromise;
+ MozPromiseRequestHolder<GenericNonExclusivePromise> mRequest;
+
+ // To calculate the delay caused by the e10s back-pressure suspension
+ TimeStamp mResumedTimestamp;
+
+ Atomic<bool> mIPCClosed; // PHttpChannel actor has been Closed()
+
+ // Corresponding redirect channel registrar Id. 0 means redirection is not
+ // started.
+ uint64_t mRedirectChannelId = 0;
+
+ PBOverrideStatus mPBOverride;
+
+ // Set to the canceled status value if the main channel was canceled.
+ nsresult mStatus;
+
+ // The referrer info, set during nsHttpChannel::BeginConnect, to override the
+ // original one. This info will be sent in OnStartRequest.
+ nsCOMPtr<nsIReferrerInfo> mOverrideReferrerInfo;
+
+ // The cookie string in Set-Cookie header. This info will be sent in
+ // OnStartRequest.
+ nsCString mCookie;
+
+ // OnStatus is always called before OnProgress.
+ // Set true in OnStatus if next OnProgress can be ignored
+ // since the information can be recontructed from ODA.
+ uint8_t mIgnoreProgress : 1;
+
+ uint8_t mHasSuspendedByBackPressure : 1;
+
+ // Set if we get the result of and cache |mNeedFlowControl|
+ uint8_t mCacheNeedFlowControlInitialized : 1;
+ uint8_t mNeedFlowControl : 1;
+ uint8_t mSuspendedForFlowControl : 1;
+
+ // Defaults to false. Is set to true at the begining of OnStartRequest.
+ // Used to ensure methods can't be called before OnStartRequest.
+ uint8_t mAfterOnStartRequestBegun : 1;
+
+ // Number of events to wait before actually invoking AsyncOpen on the main
+ // channel. For each asynchronous step required before InvokeAsyncOpen, should
+ // increase 1 to mAsyncOpenBarrier and invoke TryInvokeAsyncOpen after
+ // finished. This attribute is main thread only.
+ uint8_t mAsyncOpenBarrier = 0;
+
+ // When true, ODAs are sent from the socket process to the child process
+ // directly.
+ uint8_t mDataSentToChildProcess : 1;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpChannelParent, HTTP_CHANNEL_PARENT_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpChannelParent_h
diff --git a/netwerk/protocol/http/HttpConnectionBase.cpp b/netwerk/protocol/http/HttpConnectionBase.cpp
new file mode 100644
index 0000000000..a94bc839ba
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionBase.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#define TLS_EARLY_DATA_NOT_AVAILABLE 0
+#define TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED 1
+#define TLS_EARLY_DATA_AVAILABLE_AND_USED 2
+
+#include "mozilla/Telemetry.h"
+#include "HttpConnectionBase.h"
+#include "nsHttpHandler.h"
+#include "nsIClassOfService.h"
+#include "nsIOService.h"
+#include "nsISocketTransport.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection <public>
+//-----------------------------------------------------------------------------
+
+HttpConnectionBase::HttpConnectionBase() {
+ LOG(("Creating HttpConnectionBase @%p\n", this));
+}
+
+void HttpConnectionBase::BootstrapTimings(TimingStruct times) {
+ mBootstrappedTimingsSet = true;
+ mBootstrappedTimings = times;
+}
+
+void HttpConnectionBase::SetSecurityCallbacks(
+ nsIInterfaceRequestor* aCallbacks) {
+ MutexAutoLock lock(mCallbacksLock);
+ // This is called both on and off the main thread. For JS-implemented
+ // callbacks, we requires that the call happen on the main thread, but
+ // for C++-implemented callbacks we don't care. Use a pointer holder with
+ // strict checking disabled.
+ mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(
+ "nsHttpConnection::mCallbacks", aCallbacks, false);
+}
+
+void HttpConnectionBase::SetTrafficCategory(HttpTrafficCategory aCategory) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (aCategory == HttpTrafficCategory::eInvalid ||
+ mTrafficCategory.Contains(aCategory)) {
+ return;
+ }
+ Unused << mTrafficCategory.AppendElement(aCategory);
+}
+
+void HttpConnectionBase::ChangeConnectionState(ConnectionState aState) {
+ LOG(("HttpConnectionBase::ChangeConnectionState this=%p (%d->%d)", this,
+ static_cast<uint32_t>(mConnectionState), static_cast<uint32_t>(aState)));
+
+ // The state can't move backward.
+ if (aState <= mConnectionState) {
+ return;
+ }
+
+ mConnectionState = aState;
+}
+
+void HttpConnectionBase::RecordConnectionCloseTelemetry(nsresult aReason) {
+ /**
+ *
+ * The returned telemetry key has the format:
+ * "Version_EndToEndSSL_IsTrrServiceChannel_ExperienceState_ConnectionState"
+ *
+ * - Version: The HTTP version of the connection.
+ * - EndToEndSSL: Indicates whether SSL encryption is end-to-end.
+ * - IsTrrServiceChannel: Specifies if the connection is used to send TRR
+ * requests.
+ * - ExperienceState: ConnectionExperienceState
+ * - ConnectionState: The connection state before closing.
+ */
+ auto key = nsPrintfCString("%d_%d_%d_%d_%d", static_cast<uint32_t>(Version()),
+ mConnInfo->EndToEndSSL(),
+ mConnInfo->GetIsTrrServiceChannel(),
+ static_cast<uint32_t>(mExperienceState),
+ static_cast<uint32_t>(mConnectionState));
+ SetCloseReason(ToCloseReason(aReason));
+ LOG(("RecordConnectionCloseTelemetry key=%s reason=%d\n", key.get(),
+ static_cast<uint32_t>(mCloseReason)));
+ Telemetry::Accumulate(Telemetry::HTTP_CONNECTION_CLOSE_REASON, key,
+ static_cast<uint32_t>(mCloseReason));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpConnectionBase.h b/netwerk/protocol/http/HttpConnectionBase.h
new file mode 100644
index 0000000000..a4ccac735f
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionBase.h
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpConnectionBase_h__
+#define HttpConnectionBase_h__
+
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpResponseHead.h"
+#include "nsAHttpTransaction.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+#include "prinrval.h"
+#include "mozilla/Mutex.h"
+#include "ARefBase.h"
+#include "TimingStruct.h"
+#include "HttpTrafficAnalyzer.h"
+
+#include "mozilla/net/DNS.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsITimer.h"
+
+class nsISocketTransport;
+class nsITLSSocketControl;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpHandler;
+class ASpdySession;
+class Http3WebTransportSession;
+
+enum class ConnectionState : uint32_t {
+ HALF_OPEN = 0,
+ INITED,
+ TLS_HANDSHAKING,
+ ZERORTT,
+ TRANSFERING,
+ CLOSED
+};
+
+enum class ConnectionExperienceState : uint32_t {
+ Not_Experienced = 0,
+ First_Request_Sent = (1 << 0),
+ First_Response_Received = (1 << 1),
+ Experienced = (1 << 2),
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ConnectionExperienceState);
+
+// 1dcc863e-db90-4652-a1fe-13fea0b54e46
+#define HTTPCONNECTIONBASE_IID \
+ { \
+ 0x437e7d26, 0xa2fd, 0x49f2, { \
+ 0xb3, 0x7c, 0x84, 0x23, 0xf0, 0x94, 0x72, 0x36 \
+ } \
+ }
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection - represents a connection to a HTTP server (or proxy)
+//
+// NOTE: this objects lives on the socket thread only. it should not be
+// accessed from any other thread.
+//-----------------------------------------------------------------------------
+
+class HttpConnectionBase : public nsSupportsWeakReference {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTPCONNECTIONBASE_IID)
+
+ HttpConnectionBase();
+
+ // Activate causes the given transaction to be processed on this
+ // connection. It fails if there is already an existing transaction unless
+ // a multiplexing protocol such as SPDY is being used
+ [[nodiscard]] virtual nsresult Activate(nsAHttpTransaction*, uint32_t caps,
+ int32_t pri) = 0;
+
+ // Close the underlying socket transport.
+ virtual void Close(nsresult reason, bool aIsShutdown = false) = 0;
+
+ virtual bool CanReuse() = 0; // can this connection be reused?
+ virtual bool CanDirectlyActivate() = 0;
+
+ virtual void DontReuse() = 0;
+
+ virtual nsAHttpTransaction* Transaction() = 0;
+ nsHttpConnectionInfo* ConnectionInfo() { return mConnInfo; }
+
+ virtual void CloseTransaction(nsAHttpTransaction*, nsresult,
+ bool aIsShutdown = false) = 0;
+
+ [[nodiscard]] virtual nsresult OnHeadersAvailable(nsAHttpTransaction*,
+ nsHttpRequestHead*,
+ nsHttpResponseHead*,
+ bool* reset) = 0;
+
+ [[nodiscard]] virtual nsresult TakeTransport(nsISocketTransport**,
+ nsIAsyncInputStream**,
+ nsIAsyncOutputStream**) = 0;
+
+ Http3WebTransportSession* GetWebTransportSession(
+ nsAHttpTransaction* aTransaction) {
+ return nullptr;
+ }
+
+ virtual bool UsingSpdy() { return false; }
+ virtual bool UsingHttp3() { return false; }
+
+ virtual void SetTransactionCaps(uint32_t aCaps) { mTransactionCaps = aCaps; }
+
+ virtual void PrintDiagnostics(nsCString& log) = 0;
+
+ // IsExperienced() returns true when the connection has started at least one
+ // non null HTTP transaction of any version.
+ bool IsExperienced() { return mExperienced; }
+
+ virtual bool TestJoinConnection(const nsACString& hostname, int32_t port) = 0;
+ virtual bool JoinConnection(const nsACString& hostname, int32_t port) = 0;
+
+ // Return true when the socket this connection is using has not been
+ // authenticated using a client certificate. Before SSL negotiation
+ // has finished this returns false.
+ virtual bool NoClientCertAuth() const { return true; }
+
+ // HTTP/2 websocket support
+ virtual WebSocketSupport GetWebSocketSupport() {
+ return WebSocketSupport::NO_SUPPORT;
+ }
+
+ void GetConnectionInfo(nsHttpConnectionInfo** ci) {
+ *ci = do_AddRef(mConnInfo).take();
+ }
+ virtual void GetTLSSocketControl(nsITLSSocketControl** result) = 0;
+
+ [[nodiscard]] virtual nsresult ResumeSend() = 0;
+ [[nodiscard]] virtual nsresult ResumeRecv() = 0;
+ [[nodiscard]] virtual nsresult ForceSend() = 0;
+ [[nodiscard]] virtual nsresult ForceRecv() = 0;
+ virtual HttpVersion Version() = 0;
+ virtual bool IsProxyConnectInProgress() = 0;
+ virtual bool LastTransactionExpectedNoContent() = 0;
+ virtual void SetLastTransactionExpectedNoContent(bool) = 0;
+ virtual int64_t BytesWritten() = 0; // includes TLS
+ void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks);
+ void SetTrafficCategory(HttpTrafficCategory);
+
+ void BootstrapTimings(TimingStruct times);
+
+ virtual bool IsPersistent() = 0;
+ virtual bool IsReused() = 0;
+ [[nodiscard]] virtual nsresult PushBack(const char* data,
+ uint32_t length) = 0;
+ PRIntervalTime Rtt() { return mRtt; }
+ virtual void SetEvent(nsresult aStatus) = 0;
+
+ virtual nsISocketTransport* Transport() { return nullptr; }
+
+ virtual nsresult GetSelfAddr(NetAddr* addr) = 0;
+ virtual nsresult GetPeerAddr(NetAddr* addr) = 0;
+ virtual bool ResolvedByTRR() = 0;
+ virtual nsIRequest::TRRMode EffectiveTRRMode() = 0;
+ virtual TRRSkippedReason TRRSkipReason() = 0;
+ virtual bool GetEchConfigUsed() = 0;
+ virtual PRIntervalTime LastWriteTime() = 0;
+
+ void ChangeConnectionState(ConnectionState aState);
+ void SetCloseReason(ConnectionCloseReason aReason) {
+ if (mCloseReason == ConnectionCloseReason::UNSET) {
+ mCloseReason = aReason;
+ }
+ }
+
+ void RecordConnectionCloseTelemetry(nsresult aReason);
+
+ protected:
+ // The capabailities associated with the most recent transaction
+ uint32_t mTransactionCaps{0};
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+
+ bool mExperienced{false};
+ // Used to track whether this connection is serving the first request.
+ bool mHasFirstHttpTransaction{false};
+
+ bool mBootstrappedTimingsSet{false};
+ TimingStruct mBootstrappedTimings;
+
+ Mutex mCallbacksLock MOZ_UNANNOTATED{"nsHttpConnection::mCallbacksLock"};
+ nsMainThreadPtrHandle<nsIInterfaceRequestor> mCallbacks;
+
+ nsTArray<HttpTrafficCategory> mTrafficCategory;
+ PRIntervalTime mRtt{0};
+ nsresult mErrorBeforeConnect = NS_OK;
+
+ ConnectionState mConnectionState = ConnectionState::HALF_OPEN;
+
+ // Represent if the connection has served more than one request.
+ ConnectionExperienceState mExperienceState =
+ ConnectionExperienceState::Not_Experienced;
+
+ ConnectionCloseReason mCloseReason = ConnectionCloseReason::UNSET;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpConnectionBase, HTTPCONNECTIONBASE_IID)
+
+#define NS_DECL_HTTPCONNECTIONBASE \
+ [[nodiscard]] nsresult Activate(nsAHttpTransaction*, uint32_t, int32_t) \
+ override; \
+ [[nodiscard]] nsresult OnHeadersAvailable( \
+ nsAHttpTransaction*, nsHttpRequestHead*, nsHttpResponseHead*, \
+ bool* reset) override; \
+ [[nodiscard]] nsresult TakeTransport( \
+ nsISocketTransport**, nsIAsyncInputStream**, nsIAsyncOutputStream**) \
+ override; \
+ void Close(nsresult, bool aIsShutdown = false) override; \
+ bool CanReuse() override; \
+ bool CanDirectlyActivate() override; \
+ void DontReuse() override; \
+ void CloseTransaction(nsAHttpTransaction*, nsresult, \
+ bool aIsShutdown = false) override; \
+ void PrintDiagnostics(nsCString&) override; \
+ bool TestJoinConnection(const nsACString&, int32_t) override; \
+ bool JoinConnection(const nsACString&, int32_t) override; \
+ void GetTLSSocketControl(nsITLSSocketControl** result) override; \
+ [[nodiscard]] nsresult ResumeSend() override; \
+ [[nodiscard]] nsresult ResumeRecv() override; \
+ [[nodiscard]] nsresult ForceSend() override; \
+ [[nodiscard]] nsresult ForceRecv() override; \
+ HttpVersion Version() override; \
+ bool IsProxyConnectInProgress() override; \
+ bool LastTransactionExpectedNoContent() override; \
+ void SetLastTransactionExpectedNoContent(bool val) override; \
+ bool IsPersistent() override; \
+ bool IsReused() override; \
+ [[nodiscard]] nsresult PushBack(const char* data, uint32_t length) override; \
+ void SetEvent(nsresult aStatus) override; \
+ virtual nsAHttpTransaction* Transaction() override; \
+ PRIntervalTime LastWriteTime() override;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // HttpConnectionBase_h__
diff --git a/netwerk/protocol/http/HttpConnectionMgrChild.cpp b/netwerk/protocol/http/HttpConnectionMgrChild.cpp
new file mode 100644
index 0000000000..96756ca035
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrChild.cpp
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HttpConnectionMgrChild.h"
+#include "HttpTransactionChild.h"
+#include "AltSvcTransactionChild.h"
+#include "EventTokenBucket.h"
+#include "mozilla/net/WebSocketConnectionChild.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpConnectionMgr.h"
+#include "nsHttpHandler.h"
+#include "nsISpeculativeConnect.h"
+
+namespace mozilla::net {
+
+HttpConnectionMgrChild::HttpConnectionMgrChild()
+ : mConnMgr(gHttpHandler->ConnMgr()) {
+ MOZ_ASSERT(mConnMgr);
+}
+
+HttpConnectionMgrChild::~HttpConnectionMgrChild() {
+ LOG(("HttpConnectionMgrChild dtor:%p", this));
+}
+
+void HttpConnectionMgrChild::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("HttpConnectionMgrChild::ActorDestroy [this=%p]\n", this));
+}
+
+mozilla::ipc::IPCResult
+HttpConnectionMgrChild::RecvDoShiftReloadConnectionCleanupWithConnInfo(
+ const HttpConnectionInfoCloneArgs& aArgs) {
+ RefPtr<nsHttpConnectionInfo> cinfo =
+ nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(aArgs);
+ nsresult rv = mConnMgr->DoShiftReloadConnectionCleanupWithConnInfo(cinfo);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("HttpConnectionMgrChild::DoShiftReloadConnectionCleanupWithConnInfo "
+ "failed "
+ "(%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpConnectionMgrChild::RecvUpdateCurrentBrowserId(
+ const uint64_t& aId) {
+ mConnMgr->UpdateCurrentBrowserId(aId);
+ return IPC_OK();
+}
+
+nsHttpTransaction* ToRealHttpTransaction(PHttpTransactionChild* aTrans) {
+ HttpTransactionChild* transChild = static_cast<HttpTransactionChild*>(aTrans);
+ LOG(("ToRealHttpTransaction: [transChild=%p] \n", transChild));
+ RefPtr<nsHttpTransaction> trans = transChild->GetHttpTransaction();
+ MOZ_ASSERT(trans);
+ return trans;
+}
+
+mozilla::ipc::IPCResult HttpConnectionMgrChild::RecvAddTransaction(
+ PHttpTransactionChild* aTrans, const int32_t& aPriority) {
+ Unused << mConnMgr->AddTransaction(ToRealHttpTransaction(aTrans), aPriority);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+HttpConnectionMgrChild::RecvAddTransactionWithStickyConn(
+ PHttpTransactionChild* aTrans, const int32_t& aPriority,
+ PHttpTransactionChild* aTransWithStickyConn) {
+ Unused << mConnMgr->AddTransactionWithStickyConn(
+ ToRealHttpTransaction(aTrans), aPriority,
+ ToRealHttpTransaction(aTransWithStickyConn));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpConnectionMgrChild::RecvRescheduleTransaction(
+ PHttpTransactionChild* aTrans, const int32_t& aPriority) {
+ Unused << mConnMgr->RescheduleTransaction(ToRealHttpTransaction(aTrans),
+ aPriority);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+HttpConnectionMgrChild::RecvUpdateClassOfServiceOnTransaction(
+ PHttpTransactionChild* aTrans, const ClassOfService& aClassOfService) {
+ mConnMgr->UpdateClassOfServiceOnTransaction(ToRealHttpTransaction(aTrans),
+ aClassOfService);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpConnectionMgrChild::RecvCancelTransaction(
+ PHttpTransactionChild* aTrans, const nsresult& aReason) {
+ Unused << mConnMgr->CancelTransaction(ToRealHttpTransaction(aTrans), aReason);
+ return IPC_OK();
+}
+
+namespace {
+
+class SpeculativeConnectionOverrider final
+ : public nsIInterfaceRequestor,
+ public nsISpeculativeConnectionOverrider {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+
+ explicit SpeculativeConnectionOverrider(
+ SpeculativeConnectionOverriderArgs&& aArgs)
+ : mArgs(std::move(aArgs)) {}
+
+ private:
+ virtual ~SpeculativeConnectionOverrider() = default;
+
+ SpeculativeConnectionOverriderArgs mArgs;
+};
+
+NS_IMPL_ISUPPORTS(SpeculativeConnectionOverrider, nsIInterfaceRequestor,
+ nsISpeculativeConnectionOverrider)
+
+NS_IMETHODIMP
+SpeculativeConnectionOverrider::GetInterface(const nsIID& iid, void** result) {
+ if (NS_SUCCEEDED(QueryInterface(iid, result)) && *result) {
+ return NS_OK;
+ }
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+SpeculativeConnectionOverrider::GetIgnoreIdle(bool* aIgnoreIdle) {
+ *aIgnoreIdle = mArgs.ignoreIdle();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SpeculativeConnectionOverrider::GetParallelSpeculativeConnectLimit(
+ uint32_t* aParallelSpeculativeConnectLimit) {
+ *aParallelSpeculativeConnectLimit = mArgs.parallelSpeculativeConnectLimit();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SpeculativeConnectionOverrider::GetIsFromPredictor(bool* aIsFromPredictor) {
+ *aIsFromPredictor = mArgs.isFromPredictor();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SpeculativeConnectionOverrider::GetAllow1918(bool* aAllow) {
+ *aAllow = mArgs.allow1918();
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+mozilla::ipc::IPCResult HttpConnectionMgrChild::RecvSpeculativeConnect(
+ const HttpConnectionInfoCloneArgs& aConnInfo,
+ Maybe<SpeculativeConnectionOverriderArgs> aOverriderArgs, uint32_t aCaps,
+ Maybe<PAltSvcTransactionChild*> aTrans, const bool& aFetchHTTPSRR) {
+ RefPtr<nsHttpConnectionInfo> cinfo =
+ nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(aConnInfo);
+ nsCOMPtr<nsIInterfaceRequestor> overrider =
+ aOverriderArgs
+ ? new SpeculativeConnectionOverrider(std::move(aOverriderArgs.ref()))
+ : nullptr;
+ RefPtr<SpeculativeTransaction> trans;
+ if (aTrans) {
+ trans = static_cast<AltSvcTransactionChild*>(*aTrans)->CreateTransaction();
+ }
+
+ Unused << mConnMgr->SpeculativeConnect(cinfo, overrider, aCaps, trans,
+ aFetchHTTPSRR);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpConnectionMgrChild::RecvStartWebSocketConnection(
+ PHttpTransactionChild* aTransWithStickyConn, uint32_t aListenerId) {
+ RefPtr<WebSocketConnectionChild> child = new WebSocketConnectionChild();
+ child->Init(aListenerId);
+ nsCOMPtr<nsIHttpUpgradeListener> listener =
+ static_cast<nsIHttpUpgradeListener*>(child.get());
+ Unused << mConnMgr->CompleteUpgrade(
+ ToRealHttpTransaction(aTransWithStickyConn), listener);
+ return IPC_OK();
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/HttpConnectionMgrChild.h b/netwerk/protocol/http/HttpConnectionMgrChild.h
new file mode 100644
index 0000000000..2279f9ec46
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrChild.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpConnectionMgrChild_h__
+#define HttpConnectionMgrChild_h__
+
+#include "mozilla/net/PHttpConnectionMgrChild.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla::net {
+
+class nsHttpConnectionMgr;
+
+class HttpConnectionMgrChild final : public PHttpConnectionMgrChild {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(HttpConnectionMgrChild, override)
+
+ explicit HttpConnectionMgrChild();
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvDoShiftReloadConnectionCleanupWithConnInfo(
+ const HttpConnectionInfoCloneArgs& aArgs);
+ mozilla::ipc::IPCResult RecvUpdateCurrentBrowserId(const uint64_t& aId);
+ mozilla::ipc::IPCResult RecvAddTransaction(PHttpTransactionChild* aTrans,
+ const int32_t& aPriority);
+ mozilla::ipc::IPCResult RecvAddTransactionWithStickyConn(
+ PHttpTransactionChild* aTrans, const int32_t& aPriority,
+ PHttpTransactionChild* aTransWithStickyConn);
+ mozilla::ipc::IPCResult RecvRescheduleTransaction(
+ PHttpTransactionChild* aTrans, const int32_t& aPriority);
+ mozilla::ipc::IPCResult RecvUpdateClassOfServiceOnTransaction(
+ PHttpTransactionChild* aTrans, const ClassOfService& aClassOfService);
+ mozilla::ipc::IPCResult RecvCancelTransaction(PHttpTransactionChild* aTrans,
+ const nsresult& aReason);
+ mozilla::ipc::IPCResult RecvSpeculativeConnect(
+ const HttpConnectionInfoCloneArgs& aConnInfo,
+ Maybe<SpeculativeConnectionOverriderArgs> aOverriderArgs, uint32_t aCaps,
+ Maybe<PAltSvcTransactionChild*> aTrans, const bool& aFetchHTTPSRR);
+ mozilla::ipc::IPCResult RecvStartWebSocketConnection(
+ PHttpTransactionChild* aTransWithStickyConn, uint32_t aListenerId);
+
+ private:
+ virtual ~HttpConnectionMgrChild();
+
+ RefPtr<nsHttpConnectionMgr> mConnMgr;
+};
+
+} // namespace mozilla::net
+
+#endif // HttpConnectionMgrChild_h__
diff --git a/netwerk/protocol/http/HttpConnectionMgrParent.cpp b/netwerk/protocol/http/HttpConnectionMgrParent.cpp
new file mode 100644
index 0000000000..594fea20ec
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrParent.cpp
@@ -0,0 +1,319 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HttpConnectionMgrParent.h"
+#include "AltSvcTransactionParent.h"
+#include "mozilla/net/HttpTransactionParent.h"
+#include "mozilla/net/WebSocketConnectionParent.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsISpeculativeConnect.h"
+#include "nsIOService.h"
+#include "nsQueryObject.h"
+
+namespace mozilla::net {
+
+nsTHashMap<uint32_t, nsCOMPtr<nsIHttpUpgradeListener>>
+ HttpConnectionMgrParent::sHttpUpgradeListenerMap;
+uint32_t HttpConnectionMgrParent::sListenerId = 0;
+StaticMutex HttpConnectionMgrParent::sLock;
+
+// static
+uint32_t HttpConnectionMgrParent::AddHttpUpgradeListenerToMap(
+ nsIHttpUpgradeListener* aListener) {
+ StaticMutexAutoLock lock(sLock);
+ uint32_t id = sListenerId++;
+ sHttpUpgradeListenerMap.InsertOrUpdate(id, nsCOMPtr{aListener});
+ return id;
+}
+
+// static
+void HttpConnectionMgrParent::RemoveHttpUpgradeListenerFromMap(uint32_t aId) {
+ StaticMutexAutoLock lock(sLock);
+ sHttpUpgradeListenerMap.Remove(aId);
+}
+
+// static
+Maybe<nsCOMPtr<nsIHttpUpgradeListener>>
+HttpConnectionMgrParent::GetAndRemoveHttpUpgradeListener(uint32_t aId) {
+ StaticMutexAutoLock lock(sLock);
+ return sHttpUpgradeListenerMap.Extract(aId);
+}
+
+NS_IMPL_ISUPPORTS0(HttpConnectionMgrParent)
+
+nsresult HttpConnectionMgrParent::Init(
+ uint16_t maxUrgentExcessiveConns, uint16_t maxConnections,
+ uint16_t maxPersistentConnectionsPerHost,
+ uint16_t maxPersistentConnectionsPerProxy, uint16_t maxRequestDelay,
+ bool throttleEnabled, uint32_t throttleVersion, uint32_t throttleSuspendFor,
+ uint32_t throttleResumeFor, uint32_t throttleReadLimit,
+ uint32_t throttleReadInterval, uint32_t throttleHoldTime,
+ uint32_t throttleMaxTime, bool beConservativeForProxy) {
+ // We don't have to do anything here. nsHttpConnectionMgr in socket process is
+ // initialized by nsHttpHandler.
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::Shutdown() {
+ if (mShutDown) {
+ return NS_OK;
+ }
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mShutDown = true;
+ Unused << Send__delete__(this);
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::UpdateRequestTokenBucket(
+ EventTokenBucket* aBucket) {
+ // We don't have to do anything here. UpdateRequestTokenBucket() will be
+ // triggered by pref change in socket process.
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::DoShiftReloadConnectionCleanup() {
+ // Do nothing here. DoShiftReloadConnectionCleanup() will be triggered by
+ // observer notification or pref change in socket process.
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::DoShiftReloadConnectionCleanupWithConnInfo(
+ nsHttpConnectionInfo* aCi) {
+ if (!aCi) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ HttpConnectionInfoCloneArgs connInfoArgs;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(aCi, connInfoArgs);
+
+ RefPtr<HttpConnectionMgrParent> self = this;
+ auto task = [self, connInfoArgs{std::move(connInfoArgs)}]() {
+ Unused << self->SendDoShiftReloadConnectionCleanupWithConnInfo(
+ connInfoArgs);
+ };
+ gIOService->CallOrWaitForSocketProcess(std::move(task));
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::PruneDeadConnections() {
+ // Do nothing here. PruneDeadConnections() will be triggered by
+ // observer notification or pref change in socket process.
+ return NS_OK;
+}
+
+void HttpConnectionMgrParent::AbortAndCloseAllConnections(int32_t, ARefBase*) {
+ // Do nothing here. AbortAndCloseAllConnections() will be triggered by
+ // observer notification in socket process.
+}
+
+nsresult HttpConnectionMgrParent::UpdateParam(nsParamName name,
+ uint16_t value) {
+ // Do nothing here. UpdateParam() will be triggered by pref change in
+ // socket process.
+ return NS_OK;
+}
+
+void HttpConnectionMgrParent::PrintDiagnostics() {
+ // Do nothing here. PrintDiagnostics() will be triggered by pref change in
+ // socket process.
+}
+
+nsresult HttpConnectionMgrParent::UpdateCurrentBrowserId(uint64_t aId) {
+ RefPtr<HttpConnectionMgrParent> self = this;
+ auto task = [self, aId]() {
+ Unused << self->SendUpdateCurrentBrowserId(aId);
+ };
+ gIOService->CallOrWaitForSocketProcess(std::move(task));
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::AddTransaction(HttpTransactionShell* aTrans,
+ int32_t aPriority) {
+ MOZ_ASSERT(gIOService->SocketProcessReady());
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Unused << SendAddTransaction(WrapNotNull(aTrans->AsHttpTransactionParent()),
+ aPriority);
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::AddTransactionWithStickyConn(
+ HttpTransactionShell* aTrans, int32_t aPriority,
+ HttpTransactionShell* aTransWithStickyConn) {
+ MOZ_ASSERT(gIOService->SocketProcessReady());
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Unused << SendAddTransactionWithStickyConn(
+ WrapNotNull(aTrans->AsHttpTransactionParent()), aPriority,
+ WrapNotNull(aTransWithStickyConn->AsHttpTransactionParent()));
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::RescheduleTransaction(
+ HttpTransactionShell* aTrans, int32_t aPriority) {
+ MOZ_ASSERT(gIOService->SocketProcessReady());
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Unused << SendRescheduleTransaction(
+ WrapNotNull(aTrans->AsHttpTransactionParent()), aPriority);
+ return NS_OK;
+}
+
+void HttpConnectionMgrParent::UpdateClassOfServiceOnTransaction(
+ HttpTransactionShell* aTrans, const ClassOfService& aClassOfService) {
+ MOZ_ASSERT(gIOService->SocketProcessReady());
+
+ if (!CanSend()) {
+ return;
+ }
+
+ Unused << SendUpdateClassOfServiceOnTransaction(
+ WrapNotNull(aTrans->AsHttpTransactionParent()), aClassOfService);
+}
+
+nsresult HttpConnectionMgrParent::CancelTransaction(
+ HttpTransactionShell* aTrans, nsresult aReason) {
+ MOZ_ASSERT(gIOService->SocketProcessReady());
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Unused << SendCancelTransaction(
+ WrapNotNull(aTrans->AsHttpTransactionParent()), aReason);
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::ReclaimConnection(HttpConnectionBase*) {
+ MOZ_ASSERT_UNREACHABLE("ReclaimConnection should not be called");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult HttpConnectionMgrParent::ProcessPendingQ(nsHttpConnectionInfo*) {
+ MOZ_ASSERT_UNREACHABLE("ProcessPendingQ should not be called");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult HttpConnectionMgrParent::ProcessPendingQ() {
+ MOZ_ASSERT_UNREACHABLE("ProcessPendingQ should not be called");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult HttpConnectionMgrParent::GetSocketThreadTarget(nsIEventTarget**) {
+ MOZ_ASSERT_UNREACHABLE("GetSocketThreadTarget should not be called");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult HttpConnectionMgrParent::SpeculativeConnect(
+ nsHttpConnectionInfo* aConnInfo, nsIInterfaceRequestor* aCallbacks,
+ uint32_t aCaps, SpeculativeTransaction* aTransaction, bool aFetchHTTPSRR) {
+ NS_ENSURE_ARG_POINTER(aConnInfo);
+
+ nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
+ do_GetInterface(aCallbacks);
+ Maybe<SpeculativeConnectionOverriderArgs> overriderArgs;
+ if (overrider) {
+ overriderArgs.emplace();
+ overriderArgs->parallelSpeculativeConnectLimit() =
+ overrider->GetParallelSpeculativeConnectLimit();
+ overriderArgs->ignoreIdle() = overrider->GetIgnoreIdle();
+ overriderArgs->isFromPredictor() = overrider->GetIsFromPredictor();
+ overriderArgs->allow1918() = overrider->GetAllow1918();
+ }
+
+ HttpConnectionInfoCloneArgs connInfo;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(aConnInfo, connInfo);
+ RefPtr<AltSvcTransactionParent> trans = do_QueryObject(aTransaction);
+ RefPtr<HttpConnectionMgrParent> self = this;
+ auto task = [self, connInfo{std::move(connInfo)},
+ overriderArgs{std::move(overriderArgs)}, aCaps,
+ trans{std::move(trans)}, aFetchHTTPSRR]() {
+ Maybe<NotNull<AltSvcTransactionParent*>> maybeTrans;
+ if (trans) {
+ maybeTrans.emplace(WrapNotNull(trans.get()));
+ }
+ Unused << self->SendSpeculativeConnect(connInfo, overriderArgs, aCaps,
+ maybeTrans, aFetchHTTPSRR);
+ };
+
+ gIOService->CallOrWaitForSocketProcess(std::move(task));
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::VerifyTraffic() {
+ // Do nothing here. VerifyTraffic() will be triggered by observer notification
+ // in socket process.
+ return NS_OK;
+}
+
+void HttpConnectionMgrParent::ExcludeHttp2(const nsHttpConnectionInfo* ci) {
+ // Do nothing.
+}
+
+void HttpConnectionMgrParent::ExcludeHttp3(const nsHttpConnectionInfo* ci) {
+ // Do nothing.
+}
+
+nsresult HttpConnectionMgrParent::ClearConnectionHistory() {
+ // Do nothing here. ClearConnectionHistory() will be triggered by
+ // observer notification in socket process.
+ return NS_OK;
+}
+
+nsresult HttpConnectionMgrParent::CompleteUpgrade(
+ HttpTransactionShell* aTrans, nsIHttpUpgradeListener* aUpgradeListener) {
+ MOZ_ASSERT(aTrans->AsHttpTransactionParent());
+
+ if (!CanSend()) {
+ // OnUpgradeFailed is expected to be called on socket thread.
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ if (target) {
+ nsCOMPtr<nsIHttpUpgradeListener> listener = aUpgradeListener;
+ target->Dispatch(NS_NewRunnableFunction(
+ "HttpConnectionMgrParent::CompleteUpgrade", [listener]() {
+ Unused << listener->OnUpgradeFailed(NS_ERROR_NOT_AVAILABLE);
+ }));
+ }
+ return NS_OK;
+ }
+
+ // We need to link the id and the upgrade listener here, so
+ // WebSocketConnectionParent can connect to the listener correctly later.
+ uint32_t id = AddHttpUpgradeListenerToMap(aUpgradeListener);
+ Unused << SendStartWebSocketConnection(
+ WrapNotNull(aTrans->AsHttpTransactionParent()), id);
+ return NS_OK;
+}
+
+nsHttpConnectionMgr* HttpConnectionMgrParent::AsHttpConnectionMgr() {
+ return nullptr;
+}
+
+HttpConnectionMgrParent* HttpConnectionMgrParent::AsHttpConnectionMgrParent() {
+ return this;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/HttpConnectionMgrParent.h b/netwerk/protocol/http/HttpConnectionMgrParent.h
new file mode 100644
index 0000000000..f884dfd0a5
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrParent.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpConnectionMgrParent_h__
+#define HttpConnectionMgrParent_h__
+
+#include "HttpConnectionMgrShell.h"
+#include "mozilla/net/PHttpConnectionMgrParent.h"
+#include "mozilla/StaticMutex.h"
+
+namespace mozilla::net {
+
+// HttpConnectionMgrParent plays the role of nsHttpConnectionMgr and delegates
+// the work to the nsHttpConnectionMgr in socket process.
+class HttpConnectionMgrParent final : public PHttpConnectionMgrParent,
+ public HttpConnectionMgrShell {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_HTTPCONNECTIONMGRSHELL
+
+ explicit HttpConnectionMgrParent() = default;
+
+ static uint32_t AddHttpUpgradeListenerToMap(
+ nsIHttpUpgradeListener* aListener);
+ static void RemoveHttpUpgradeListenerFromMap(uint32_t aId);
+ static Maybe<nsCOMPtr<nsIHttpUpgradeListener>>
+ GetAndRemoveHttpUpgradeListener(uint32_t aId);
+
+ private:
+ virtual ~HttpConnectionMgrParent() = default;
+
+ bool mShutDown{false};
+ static uint32_t sListenerId;
+ static StaticMutex sLock MOZ_UNANNOTATED;
+ static nsTHashMap<uint32_t, nsCOMPtr<nsIHttpUpgradeListener>>
+ sHttpUpgradeListenerMap;
+};
+
+} // namespace mozilla::net
+
+#endif // HttpConnectionMgrParent_h__
diff --git a/netwerk/protocol/http/HttpConnectionMgrShell.h b/netwerk/protocol/http/HttpConnectionMgrShell.h
new file mode 100644
index 0000000000..9c58ad5e63
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrShell.h
@@ -0,0 +1,229 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpConnectionMgrShell_h__
+#define HttpConnectionMgrShell_h__
+
+#include "nsISupports.h"
+
+class nsIEventTarget;
+class nsIHttpUpgradeListener;
+class nsIInterfaceRequestor;
+
+namespace mozilla::net {
+
+class ARefBase;
+class EventTokenBucket;
+class HttpTransactionShell;
+class nsHttpConnectionInfo;
+class HttpConnectionBase;
+class nsHttpConnectionMgr;
+class HttpConnectionMgrParent;
+class SpeculativeTransaction;
+class ClassOfService;
+
+//----------------------------------------------------------------------------
+// Abstract base class for HTTP connection manager in chrome process
+//----------------------------------------------------------------------------
+
+// f5379ff9-2758-4bec-9992-2351c258aed6
+#define HTTPCONNECTIONMGRSHELL_IID \
+ { \
+ 0xf5379ff9, 0x2758, 0x4bec, { \
+ 0x99, 0x92, 0x23, 0x51, 0xc2, 0x58, 0xae, 0xd6 \
+ } \
+ }
+
+class HttpConnectionMgrShell : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTPCONNECTIONMGRSHELL_IID)
+
+ enum nsParamName : uint32_t {
+ MAX_URGENT_START_Q,
+ MAX_CONNECTIONS,
+ MAX_PERSISTENT_CONNECTIONS_PER_HOST,
+ MAX_PERSISTENT_CONNECTIONS_PER_PROXY,
+ MAX_REQUEST_DELAY,
+ THROTTLING_ENABLED,
+ THROTTLING_SUSPEND_FOR,
+ THROTTLING_RESUME_FOR,
+ THROTTLING_READ_LIMIT,
+ THROTTLING_READ_INTERVAL,
+ THROTTLING_HOLD_TIME,
+ THROTTLING_MAX_TIME,
+ PROXY_BE_CONSERVATIVE
+ };
+
+ [[nodiscard]] virtual nsresult Init(
+ uint16_t maxUrgentExcessiveConns, uint16_t maxConnections,
+ uint16_t maxPersistentConnectionsPerHost,
+ uint16_t maxPersistentConnectionsPerProxy, uint16_t maxRequestDelay,
+ bool throttleEnabled, uint32_t throttleVersion,
+ uint32_t throttleSuspendFor, uint32_t throttleResumeFor,
+ uint32_t throttleReadLimit, uint32_t throttleReadInterval,
+ uint32_t throttleHoldTime, uint32_t throttleMaxTime,
+ bool beConservativeForProxy) = 0;
+
+ [[nodiscard]] virtual nsresult Shutdown() = 0;
+
+ // called from main thread to post a new request token bucket
+ // to the socket thread
+ [[nodiscard]] virtual nsresult UpdateRequestTokenBucket(
+ EventTokenBucket* aBucket) = 0;
+
+ // Close all idle persistent connections and prevent any active connections
+ // from being reused.
+ [[nodiscard]] virtual nsresult DoShiftReloadConnectionCleanup() = 0;
+
+ // Like DoShiftReloadConnectionCleanup() above, but also resets CI specific
+ // information such as Happy Eyeballs history.
+ [[nodiscard]] virtual nsresult DoShiftReloadConnectionCleanupWithConnInfo(
+ nsHttpConnectionInfo*) = 0;
+
+ // called to force the connection manager to prune its list of idle
+ // connections.
+ [[nodiscard]] virtual nsresult PruneDeadConnections() = 0;
+
+ // this cancels all outstanding transactions but does not shut down the mgr
+ virtual void AbortAndCloseAllConnections(int32_t, ARefBase*) = 0;
+
+ // called to update a parameter after the connection manager has already
+ // been initialized.
+ [[nodiscard]] virtual nsresult UpdateParam(nsParamName name,
+ uint16_t value) = 0;
+
+ // Causes a large amount of connection diagnostic information to be
+ // printed to the javascript console
+ virtual void PrintDiagnostics() = 0;
+
+ virtual nsresult UpdateCurrentBrowserId(uint64_t aId) = 0;
+
+ // adds a transaction to the list of managed transactions.
+ [[nodiscard]] virtual nsresult AddTransaction(HttpTransactionShell*,
+ int32_t priority) = 0;
+
+ // Add a new transaction with a sticky connection from |transWithStickyConn|.
+ [[nodiscard]] virtual nsresult AddTransactionWithStickyConn(
+ HttpTransactionShell* trans, int32_t priority,
+ HttpTransactionShell* transWithStickyConn) = 0;
+
+ // called to reschedule the given transaction. it must already have been
+ // added to the connection manager via AddTransaction.
+ [[nodiscard]] virtual nsresult RescheduleTransaction(HttpTransactionShell*,
+ int32_t priority) = 0;
+
+ void virtual UpdateClassOfServiceOnTransaction(
+ HttpTransactionShell*, const ClassOfService& classOfService) = 0;
+
+ // cancels a transaction w/ the given reason.
+ [[nodiscard]] virtual nsresult CancelTransaction(HttpTransactionShell*,
+ nsresult reason) = 0;
+
+ // called when a connection is done processing a transaction. if the
+ // connection can be reused then it will be added to the idle list, else
+ // it will be closed.
+ [[nodiscard]] virtual nsresult ReclaimConnection(
+ HttpConnectionBase* conn) = 0;
+
+ // called to force the transaction queue to be processed once more, giving
+ // preference to the specified connection.
+ [[nodiscard]] virtual nsresult ProcessPendingQ(nsHttpConnectionInfo*) = 0;
+
+ // Try and process all pending transactions
+ [[nodiscard]] virtual nsresult ProcessPendingQ() = 0;
+
+ // called to get a reference to the socket transport service. the socket
+ // transport service is not available when the connection manager is down.
+ [[nodiscard]] virtual nsresult GetSocketThreadTarget(nsIEventTarget**) = 0;
+
+ // called to indicate a transaction for the connectionInfo is likely coming
+ // soon. The connection manager may use this information to start a TCP
+ // and/or SSL level handshake for that resource immediately so that it is
+ // ready when the transaction is submitted. No obligation is taken on by the
+ // connection manager, nor is the submitter obligated to actually submit a
+ // real transaction for this connectionInfo.
+ [[nodiscard]] virtual nsresult SpeculativeConnect(
+ nsHttpConnectionInfo*, nsIInterfaceRequestor*, uint32_t caps = 0,
+ SpeculativeTransaction* = nullptr, bool aFetchHTTPSRR = false) = 0;
+
+ // "VerifyTraffic" means marking connections now, and then check again in
+ // N seconds to see if there's been any traffic and if not, kill
+ // that connection.
+ [[nodiscard]] virtual nsresult VerifyTraffic() = 0;
+
+ virtual void ExcludeHttp2(const nsHttpConnectionInfo* ci) = 0;
+ virtual void ExcludeHttp3(const nsHttpConnectionInfo* ci) = 0;
+
+ // clears the connection history mCT
+ [[nodiscard]] virtual nsresult ClearConnectionHistory() = 0;
+
+ // called by the main thread to execute the taketransport() logic on the
+ // socket thread after a 101 response has been received and the socket
+ // needs to be transferred to an expectant upgrade listener such as
+ // websockets.
+ // @param aTrans: a transaction that contains a sticky connection. We'll
+ // take the transport of this connection.
+ [[nodiscard]] virtual nsresult CompleteUpgrade(
+ HttpTransactionShell* aTrans,
+ nsIHttpUpgradeListener* aUpgradeListener) = 0;
+
+ virtual nsHttpConnectionMgr* AsHttpConnectionMgr() = 0;
+ virtual HttpConnectionMgrParent* AsHttpConnectionMgrParent() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpConnectionMgrShell,
+ HTTPCONNECTIONMGRSHELL_IID)
+
+#define NS_DECL_HTTPCONNECTIONMGRSHELL \
+ virtual nsresult Init( \
+ uint16_t maxUrgentExcessiveConns, uint16_t maxConnections, \
+ uint16_t maxPersistentConnectionsPerHost, \
+ uint16_t maxPersistentConnectionsPerProxy, uint16_t maxRequestDelay, \
+ bool throttleEnabled, uint32_t throttleVersion, \
+ uint32_t throttleSuspendFor, uint32_t throttleResumeFor, \
+ uint32_t throttleReadLimit, uint32_t throttleReadInterval, \
+ uint32_t throttleHoldTime, uint32_t throttleMaxTime, \
+ bool beConservativeForProxy) override; \
+ virtual nsresult Shutdown() override; \
+ virtual nsresult UpdateRequestTokenBucket(EventTokenBucket* aBucket) \
+ override; \
+ virtual nsresult DoShiftReloadConnectionCleanup() override; \
+ virtual nsresult DoShiftReloadConnectionCleanupWithConnInfo( \
+ nsHttpConnectionInfo*) override; \
+ virtual nsresult PruneDeadConnections() override; \
+ virtual void AbortAndCloseAllConnections(int32_t, ARefBase*) override; \
+ virtual nsresult UpdateParam(nsParamName name, uint16_t value) override; \
+ virtual void PrintDiagnostics() override; \
+ virtual nsresult UpdateCurrentBrowserId(uint64_t aId) override; \
+ virtual nsresult AddTransaction(HttpTransactionShell*, int32_t priority) \
+ override; \
+ virtual nsresult AddTransactionWithStickyConn( \
+ HttpTransactionShell* trans, int32_t priority, \
+ HttpTransactionShell* transWithStickyConn) override; \
+ virtual nsresult RescheduleTransaction(HttpTransactionShell*, \
+ int32_t priority) override; \
+ void virtual UpdateClassOfServiceOnTransaction( \
+ HttpTransactionShell*, const ClassOfService& classOfService) override; \
+ virtual nsresult CancelTransaction(HttpTransactionShell*, nsresult reason) \
+ override; \
+ virtual nsresult ReclaimConnection(HttpConnectionBase* conn) override; \
+ virtual nsresult ProcessPendingQ(nsHttpConnectionInfo*) override; \
+ virtual nsresult ProcessPendingQ() override; \
+ virtual nsresult GetSocketThreadTarget(nsIEventTarget**) override; \
+ virtual nsresult SpeculativeConnect( \
+ nsHttpConnectionInfo*, nsIInterfaceRequestor*, uint32_t caps = 0, \
+ SpeculativeTransaction* = nullptr, bool aFetchHTTPSRR = false) override; \
+ virtual nsresult VerifyTraffic() override; \
+ virtual void ExcludeHttp2(const nsHttpConnectionInfo* ci) override; \
+ virtual void ExcludeHttp3(const nsHttpConnectionInfo* ci) override; \
+ virtual nsresult ClearConnectionHistory() override; \
+ virtual nsresult CompleteUpgrade(HttpTransactionShell* aTrans, \
+ nsIHttpUpgradeListener* aUpgradeListener) \
+ override; \
+ nsHttpConnectionMgr* AsHttpConnectionMgr() override; \
+ HttpConnectionMgrParent* AsHttpConnectionMgrParent() override;
+
+} // namespace mozilla::net
+
+#endif // HttpConnectionMgrShell_h__
diff --git a/netwerk/protocol/http/HttpConnectionUDP.cpp b/netwerk/protocol/http/HttpConnectionUDP.cpp
new file mode 100644
index 0000000000..b0e39a6d56
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionUDP.cpp
@@ -0,0 +1,706 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#define TLS_EARLY_DATA_NOT_AVAILABLE 0
+#define TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED 1
+#define TLS_EARLY_DATA_AVAILABLE_AND_USED 2
+
+#include "ASpdySession.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "HttpConnectionUDP.h"
+#include "nsHttpHandler.h"
+#include "Http3Session.h"
+#include "nsComponentManagerUtils.h"
+#include "nsISocketProvider.h"
+#include "nsNetAddr.h"
+#include "nsINetAddr.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP <public>
+//-----------------------------------------------------------------------------
+
+HttpConnectionUDP::HttpConnectionUDP() : mHttpHandler(gHttpHandler) {
+ LOG(("Creating HttpConnectionUDP @%p\n", this));
+}
+
+HttpConnectionUDP::~HttpConnectionUDP() {
+ LOG(("Destroying HttpConnectionUDP @%p\n", this));
+
+ if (mForceSendTimer) {
+ mForceSendTimer->Cancel();
+ mForceSendTimer = nullptr;
+ }
+}
+
+nsresult HttpConnectionUDP::Init(nsHttpConnectionInfo* info,
+ nsIDNSRecord* dnsRecord, nsresult status,
+ nsIInterfaceRequestor* callbacks,
+ uint32_t caps) {
+ LOG1(("HttpConnectionUDP::Init this=%p", this));
+ NS_ENSURE_ARG_POINTER(info);
+ NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
+
+ mConnInfo = info;
+ MOZ_ASSERT(mConnInfo);
+ MOZ_ASSERT(mConnInfo->IsHttp3());
+
+ mErrorBeforeConnect = status;
+ mAlpnToken = mConnInfo->GetNPNToken();
+ if (NS_FAILED(mErrorBeforeConnect)) {
+ // See explanation for non-strictness of this operation in
+ // SetSecurityCallbacks.
+ mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(
+ "HttpConnectionUDP::mCallbacks", callbacks, false);
+ SetCloseReason(ToCloseReason(mErrorBeforeConnect));
+ return mErrorBeforeConnect;
+ }
+
+ nsCOMPtr<nsIDNSAddrRecord> dnsAddrRecord = do_QueryInterface(dnsRecord);
+ if (!dnsAddrRecord) {
+ return NS_ERROR_FAILURE;
+ }
+ dnsAddrRecord->IsTRR(&mResolvedByTRR);
+ dnsAddrRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ dnsAddrRecord->GetTrrSkipReason(&mTRRSkipReason);
+ NetAddr peerAddr;
+ nsresult rv = dnsAddrRecord->GetNextAddr(mConnInfo->GetRoutedHost().IsEmpty()
+ ? mConnInfo->OriginPort()
+ : mConnInfo->RoutedPort(),
+ &peerAddr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mSocket = do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // We need an address here so that we can convey the IP version of the
+ // socket.
+ NetAddr local;
+ memset(&local, 0, sizeof(local));
+ local.raw.family = peerAddr.raw.family;
+ rv = mSocket->InitWithAddress(&local, nullptr, false, 1);
+ if (NS_FAILED(rv)) {
+ mSocket = nullptr;
+ return rv;
+ }
+
+ rv = mSocket->SetRecvBufferSize(
+ StaticPrefs::network_http_http3_recvBufferSize());
+ if (NS_FAILED(rv)) {
+ LOG(("HttpConnectionUDP::Init SetRecvBufferSize failed %d [this=%p]",
+ static_cast<uint32_t>(rv), this));
+ mSocket->Close();
+ mSocket = nullptr;
+ return rv;
+ }
+
+ if (peerAddr.raw.family == AF_INET) {
+ rv = mSocket->SetDontFragment(true);
+ if (NS_FAILED(rv)) {
+ LOG(("HttpConnectionUDP::Init SetDontFragment failed %d [this=%p]",
+ static_cast<uint32_t>(rv), this));
+ }
+ }
+
+ // get the resulting socket address.
+ rv = mSocket->GetLocalAddr(getter_AddRefs(mSelfAddr));
+ if (NS_FAILED(rv)) {
+ mSocket->Close();
+ mSocket = nullptr;
+ return rv;
+ }
+
+ uint32_t controlFlags = 0;
+ if (caps & NS_HTTP_LOAD_ANONYMOUS) {
+ controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT;
+ }
+ if (mConnInfo->GetPrivate()) {
+ controlFlags |= nsISocketProvider::NO_PERMANENT_STORAGE;
+ }
+ if (((caps & NS_HTTP_BE_CONSERVATIVE) || mConnInfo->GetBeConservative()) &&
+ gHttpHandler->ConnMgr()->BeConservativeIfProxied(
+ mConnInfo->ProxyInfo())) {
+ controlFlags |= nsISocketProvider::BE_CONSERVATIVE;
+ }
+
+ if (mResolvedByTRR) {
+ controlFlags |= nsISocketProvider::USED_PRIVATE_DNS;
+ }
+
+ mPeerAddr = new nsNetAddr(&peerAddr);
+ mHttp3Session = new Http3Session();
+ rv = mHttp3Session->Init(mConnInfo, mSelfAddr, mPeerAddr, this, controlFlags,
+ callbacks);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("HttpConnectionUDP::Init mHttp3Session->Init failed "
+ "[this=%p rv=%x]\n",
+ this, static_cast<uint32_t>(rv)));
+ mSocket->Close();
+ mSocket = nullptr;
+ mHttp3Session = nullptr;
+ return rv;
+ }
+
+ ChangeConnectionState(ConnectionState::INITED);
+ // See explanation for non-strictness of this operation in
+ // SetSecurityCallbacks.
+ mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(
+ "HttpConnectionUDP::mCallbacks", callbacks, false);
+
+ // Call SyncListen at the end of this function. This call will actually
+ // attach the sockte to SocketTransportService.
+ rv = mSocket->SyncListen(this);
+ if (NS_FAILED(rv)) {
+ mSocket->Close();
+ mSocket = nullptr;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// called on the socket thread
+nsresult HttpConnectionUDP::Activate(nsAHttpTransaction* trans, uint32_t caps,
+ int32_t pri) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG1(("HttpConnectionUDP::Activate [this=%p trans=%p caps=%x]\n", this, trans,
+ caps));
+
+ if (!mExperienced && !trans->IsNullTransaction()) {
+ mHasFirstHttpTransaction = true;
+ // For QUIC we have HttpConnecitonUDP before the actual connection
+ // has been establish so wait for TLS handshake to be finished before
+ // we mark the connection 'experienced'.
+ if (!mExperienced && mHttp3Session && mHttp3Session->IsConnected()) {
+ mExperienced = true;
+ }
+ if (mBootstrappedTimingsSet) {
+ mBootstrappedTimingsSet = false;
+ nsHttpTransaction* hTrans = trans->QueryHttpTransaction();
+ if (hTrans) {
+ hTrans->BootstrapTimings(mBootstrappedTimings);
+ }
+ }
+ mBootstrappedTimings = TimingStruct();
+ }
+
+ mTransactionCaps = caps;
+ mPriority = pri;
+
+ NS_ENSURE_ARG_POINTER(trans);
+
+ // Connection failures are Activated() just like regular transacions.
+ // If we don't have a confirmation of a connected socket then test it
+ // with a write() to get relevant error code.
+ if (NS_FAILED(mErrorBeforeConnect)) {
+ CloseTransaction(nullptr, mErrorBeforeConnect);
+ trans->Close(mErrorBeforeConnect);
+ gHttpHandler->ExcludeHttp3(mConnInfo);
+ return mErrorBeforeConnect;
+ }
+
+ if (!mHttp3Session->AddStream(trans, pri, mCallbacks)) {
+ MOZ_ASSERT(false); // this cannot happen!
+ trans->Close(NS_ERROR_ABORT);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mHasFirstHttpTransaction && mExperienced) {
+ mHasFirstHttpTransaction = false;
+ mExperienceState |= ConnectionExperienceState::Experienced;
+ }
+
+ Unused << ResumeSend();
+ return NS_OK;
+}
+
+void HttpConnectionUDP::Close(nsresult reason, bool aIsShutdown) {
+ LOG(("HttpConnectionUDP::Close [this=%p reason=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(reason)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mConnectionState != ConnectionState::CLOSED) {
+ RecordConnectionCloseTelemetry(reason);
+ ChangeConnectionState(ConnectionState::CLOSED);
+ }
+
+ if (mForceSendTimer) {
+ mForceSendTimer->Cancel();
+ mForceSendTimer = nullptr;
+ }
+
+ if (!mTrafficCategory.IsEmpty()) {
+ HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer();
+ if (hta) {
+ hta->IncrementHttpConnection(std::move(mTrafficCategory));
+ MOZ_ASSERT(mTrafficCategory.IsEmpty());
+ }
+ }
+ if (mSocket) {
+ mSocket->Close();
+ mSocket = nullptr;
+ }
+}
+
+void HttpConnectionUDP::DontReuse() {
+ LOG(("HttpConnectionUDP::DontReuse %p http3session=%p\n", this,
+ mHttp3Session.get()));
+ mDontReuse = true;
+ if (mHttp3Session) {
+ mHttp3Session->DontReuse();
+ }
+}
+
+bool HttpConnectionUDP::TestJoinConnection(const nsACString& hostname,
+ int32_t port) {
+ if (mHttp3Session && CanDirectlyActivate()) {
+ return mHttp3Session->TestJoinConnection(hostname, port);
+ }
+
+ return false;
+}
+
+bool HttpConnectionUDP::JoinConnection(const nsACString& hostname,
+ int32_t port) {
+ if (mHttp3Session && CanDirectlyActivate()) {
+ return mHttp3Session->JoinConnection(hostname, port);
+ }
+
+ return false;
+}
+
+bool HttpConnectionUDP::CanReuse() {
+ if (NS_FAILED(mErrorBeforeConnect)) {
+ return false;
+ }
+ if (mDontReuse) {
+ return false;
+ }
+
+ if (mHttp3Session) {
+ return mHttp3Session->CanReuse();
+ }
+ return false;
+}
+
+bool HttpConnectionUDP::CanDirectlyActivate() {
+ // return true if a new transaction can be addded to ths connection at any
+ // time through Activate(). In practice this means this is a healthy SPDY
+ // connection with room for more concurrent streams.
+
+ if (mHttp3Session) {
+ return CanReuse();
+ }
+ return false;
+}
+
+//----------------------------------------------------------------------------
+// HttpConnectionUDP::nsAHttpConnection compatible methods
+//----------------------------------------------------------------------------
+
+nsresult HttpConnectionUDP::OnHeadersAvailable(nsAHttpTransaction* trans,
+ nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ bool* reset) {
+ LOG(
+ ("HttpConnectionUDP::OnHeadersAvailable [this=%p trans=%p "
+ "response-head=%p]\n",
+ this, trans, responseHead));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ NS_ENSURE_ARG_POINTER(trans);
+ MOZ_ASSERT(responseHead, "No response head?");
+
+ DebugOnly<nsresult> rv =
+ responseHead->SetHeader(nsHttp::X_Firefox_Http3, mAlpnToken);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // deal with 408 Server Timeouts
+ uint16_t responseStatus = responseHead->Status();
+ static const PRIntervalTime k1000ms = PR_MillisecondsToInterval(1000);
+ if (responseStatus == 408) {
+ // If this error could be due to a persistent connection reuse then
+ // we pass an error code of NS_ERROR_NET_RESET to
+ // trigger the transaction 'restart' mechanism. We tell it to reset its
+ // response headers so that it will be ready to receive the new response.
+ if (mIsReused &&
+ ((PR_IntervalNow() - mHttp3Session->LastWriteTime()) < k1000ms)) {
+ Close(NS_ERROR_NET_RESET);
+ *reset = true;
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+bool HttpConnectionUDP::IsReused() { return mIsReused; }
+
+nsresult HttpConnectionUDP::TakeTransport(
+ nsISocketTransport** aTransport, nsIAsyncInputStream** aInputStream,
+ nsIAsyncOutputStream** aOutputStream) {
+ return NS_ERROR_FAILURE;
+}
+
+void HttpConnectionUDP::GetTLSSocketControl(nsITLSSocketControl** secinfo) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("HttpConnectionUDP::GetTLSSocketControl http3Session=%p\n",
+ mHttp3Session.get()));
+
+ if (mHttp3Session &&
+ NS_SUCCEEDED(mHttp3Session->GetTransactionTLSSocketControl(secinfo))) {
+ return;
+ }
+
+ *secinfo = nullptr;
+}
+
+nsresult HttpConnectionUDP::PushBack(const char* data, uint32_t length) {
+ LOG(("HttpConnectionUDP::PushBack [this=%p, length=%d]\n", this, length));
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+class HttpConnectionUDPForceIO : public Runnable {
+ public:
+ HttpConnectionUDPForceIO(HttpConnectionUDP* aConn, bool doRecv)
+ : Runnable("net::HttpConnectionUDPForceIO"),
+ mConn(aConn),
+ mDoRecv(doRecv) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mDoRecv) {
+ return mConn->RecvData();
+ }
+
+ MOZ_ASSERT(mConn->mForceSendPending);
+ mConn->mForceSendPending = false;
+
+ return mConn->SendData();
+ }
+
+ private:
+ RefPtr<HttpConnectionUDP> mConn;
+ bool mDoRecv;
+};
+
+nsresult HttpConnectionUDP::ResumeSend() {
+ LOG(("HttpConnectionUDP::ResumeSend [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ RefPtr<HttpConnectionUDP> self(this);
+ NS_DispatchToCurrentThread(
+ NS_NewRunnableFunction("HttpConnectionUDP::CallSendData",
+ [self{std::move(self)}]() { self->SendData(); }));
+ return NS_OK;
+}
+
+nsresult HttpConnectionUDP::ResumeRecv() { return NS_OK; }
+
+void HttpConnectionUDP::ForceSendIO(nsITimer* aTimer, void* aClosure) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ HttpConnectionUDP* self = static_cast<HttpConnectionUDP*>(aClosure);
+ MOZ_ASSERT(aTimer == self->mForceSendTimer);
+ self->mForceSendTimer = nullptr;
+ NS_DispatchToCurrentThread(new HttpConnectionUDPForceIO(self, false));
+}
+
+nsresult HttpConnectionUDP::MaybeForceSendIO() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // due to bug 1213084 sometimes real I/O events do not get serviced when
+ // NSPR derived I/O events are ready and this can cause a deadlock with
+ // https over https proxying. Normally we would expect the write callback to
+ // be invoked before this timer goes off, but set it at the old windows
+ // tick interval (kForceDelay) as a backup for those circumstances.
+ static const uint32_t kForceDelay = 17; // ms
+
+ if (mForceSendPending) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(!mForceSendTimer);
+ mForceSendPending = true;
+ return NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mForceSendTimer), HttpConnectionUDP::ForceSendIO, this,
+ kForceDelay, nsITimer::TYPE_ONE_SHOT,
+ "net::HttpConnectionUDP::MaybeForceSendIO");
+}
+
+// trigger an asynchronous read
+nsresult HttpConnectionUDP::ForceRecv() {
+ LOG(("HttpConnectionUDP::ForceRecv [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ return NS_DispatchToCurrentThread(new HttpConnectionUDPForceIO(this, true));
+}
+
+// trigger an asynchronous write
+nsresult HttpConnectionUDP::ForceSend() {
+ LOG(("HttpConnectionUDP::ForceSend [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ return MaybeForceSendIO();
+}
+
+HttpVersion HttpConnectionUDP::Version() { return HttpVersion::v3_0; }
+
+PRIntervalTime HttpConnectionUDP::LastWriteTime() {
+ return mHttp3Session->LastWriteTime();
+}
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP <private>
+//-----------------------------------------------------------------------------
+
+void HttpConnectionUDP::CloseTransaction(nsAHttpTransaction* trans,
+ nsresult reason, bool aIsShutdown) {
+ LOG(("HttpConnectionUDP::CloseTransaction[this=%p trans=%p reason=%" PRIx32
+ "]\n",
+ this, trans, static_cast<uint32_t>(reason)));
+
+ MOZ_ASSERT(trans == mHttp3Session);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (NS_SUCCEEDED(reason) || (reason == NS_BASE_STREAM_CLOSED)) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ // The connection and security errors clear out alt-svc mappings
+ // in case any previously validated ones are now invalid
+ if (((reason == NS_ERROR_NET_RESET) ||
+ (NS_ERROR_GET_MODULE(reason) == NS_ERROR_MODULE_SECURITY)) &&
+ mConnInfo && !(mTransactionCaps & NS_HTTP_ERROR_SOFTLY)) {
+ gHttpHandler->ClearHostMapping(mConnInfo);
+ }
+
+ mDontReuse = true;
+ if (mHttp3Session) {
+ mHttp3Session->SetCleanShutdown(aIsShutdown);
+ mHttp3Session->Close(reason);
+ if (!mHttp3Session->IsClosed()) {
+ // During closing phase we still keep mHttp3Session session,
+ // to resend CLOSE_CONNECTION frames.
+ return;
+ }
+ }
+
+ mHttp3Session = nullptr;
+
+ {
+ MutexAutoLock lock(mCallbacksLock);
+ mCallbacks = nullptr;
+ }
+
+ Close(reason, aIsShutdown);
+
+ // flag the connection as reused here for convenience sake. certainly
+ // it might be going away instead ;-)
+ mIsReused = true;
+}
+
+void HttpConnectionUDP::OnQuicTimeoutExpired() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("HttpConnectionUDP::OnQuicTimeoutExpired [this=%p]\n", this));
+ // if the transaction was dropped...
+ if (!mHttp3Session) {
+ LOG((" no transaction; ignoring event\n"));
+ return;
+ }
+
+ nsresult rv = mHttp3Session->ProcessOutputAndEvents(mSocket);
+ if (NS_FAILED(rv)) {
+ CloseTransaction(mHttp3Session, rv);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpConnectionUDP)
+NS_IMPL_RELEASE(HttpConnectionUDP)
+
+NS_INTERFACE_MAP_BEGIN(HttpConnectionUDP)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIUDPSocketSyncListener)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(HttpConnectionBase)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpConnectionUDP)
+NS_INTERFACE_MAP_END
+
+void HttpConnectionUDP::NotifyDataRead() {
+ mExperienceState |= ConnectionExperienceState::First_Response_Received;
+}
+
+void HttpConnectionUDP::NotifyDataWrite() {
+ mExperienceState |= ConnectionExperienceState::First_Request_Sent;
+}
+
+// called on the socket transport thread
+nsresult HttpConnectionUDP::RecvData() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // if the transaction was dropped...
+ if (!mHttp3Session) {
+ LOG((" no Http3Session; ignoring event\n"));
+ return NS_OK;
+ }
+
+ nsresult rv = mHttp3Session->RecvData(mSocket);
+ LOG(("HttpConnectionUDP::OnInputReady %p rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+
+ if (NS_FAILED(rv)) CloseTransaction(mHttp3Session, rv);
+
+ return NS_OK;
+}
+
+// called on the socket transport thread
+nsresult HttpConnectionUDP::SendData() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // if the transaction was dropped...
+ if (!mHttp3Session) {
+ LOG((" no Http3Session; ignoring event\n"));
+ return NS_OK;
+ }
+
+ nsresult rv = mHttp3Session->SendData(mSocket);
+ LOG(("HttpConnectionUDP::OnInputReady %p rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+
+ if (NS_FAILED(rv)) CloseTransaction(mHttp3Session, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+// not called on the socket transport thread
+NS_IMETHODIMP
+HttpConnectionUDP::GetInterface(const nsIID& iid, void** result) {
+ // NOTE: This function is only called on the UI thread via sync proxy from
+ // the socket transport thread. If that weren't the case, then we'd
+ // have to worry about the possibility of mHttp3Session going away
+ // part-way through this function call. See CloseTransaction.
+
+ // NOTE - there is a bug here, the call to getinterface is proxied off the
+ // nss thread, not the ui thread as the above comment says. So there is
+ // indeed a chance of mSession going away. bug 615342
+
+ MOZ_ASSERT(!OnSocketThread(), "on socket thread");
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ {
+ MutexAutoLock lock(mCallbacksLock);
+ callbacks = mCallbacks;
+ }
+ if (callbacks) return callbacks->GetInterface(iid, result);
+ return NS_ERROR_NO_INTERFACE;
+}
+
+void HttpConnectionUDP::SetEvent(nsresult aStatus) {
+ switch (aStatus) {
+ case NS_NET_STATUS_RESOLVING_HOST:
+ mBootstrappedTimings.domainLookupStart = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_RESOLVED_HOST:
+ mBootstrappedTimings.domainLookupEnd = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_CONNECTING_TO:
+ mBootstrappedTimings.connectStart = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_CONNECTED_TO:
+ mBootstrappedTimings.connectEnd = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
+ mBootstrappedTimings.secureConnectionStart = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
+ mBootstrappedTimings.connectEnd = TimeStamp::Now();
+ break;
+ default:
+ break;
+ }
+}
+
+bool HttpConnectionUDP::IsProxyConnectInProgress() { return false; }
+
+bool HttpConnectionUDP::LastTransactionExpectedNoContent() {
+ return mLastTransactionExpectedNoContent;
+}
+
+void HttpConnectionUDP::SetLastTransactionExpectedNoContent(bool val) {
+ mLastTransactionExpectedNoContent = val;
+}
+
+bool HttpConnectionUDP::IsPersistent() { return !mDontReuse; }
+
+nsAHttpTransaction* HttpConnectionUDP::Transaction() { return mHttp3Session; }
+
+int64_t HttpConnectionUDP::BytesWritten() {
+ if (!mHttp3Session) {
+ return 0;
+ }
+ return mHttp3Session->GetBytesWritten();
+}
+
+NS_IMETHODIMP HttpConnectionUDP::OnPacketReceived(nsIUDPSocket* aSocket) {
+ RecvData();
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpConnectionUDP::OnStopListening(nsIUDPSocket* aSocket,
+ nsresult aStatus) {
+ CloseTransaction(mHttp3Session, aStatus);
+ return NS_OK;
+}
+
+nsresult HttpConnectionUDP::GetSelfAddr(NetAddr* addr) {
+ if (mSelfAddr) {
+ return mSelfAddr->GetNetAddr(addr);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult HttpConnectionUDP::GetPeerAddr(NetAddr* addr) {
+ if (mPeerAddr) {
+ return mPeerAddr->GetNetAddr(addr);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+bool HttpConnectionUDP::ResolvedByTRR() { return mResolvedByTRR; }
+
+nsIRequest::TRRMode HttpConnectionUDP::EffectiveTRRMode() {
+ return mEffectiveTRRMode;
+}
+
+TRRSkippedReason HttpConnectionUDP::TRRSkipReason() { return mTRRSkipReason; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpConnectionUDP.h b/netwerk/protocol/http/HttpConnectionUDP.h
new file mode 100644
index 0000000000..e5cae24586
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionUDP.h
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpConnectionUDP_h__
+#define HttpConnectionUDP_h__
+
+#include "HttpConnectionBase.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpResponseHead.h"
+#include "nsAHttpTransaction.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+#include "prinrval.h"
+#include "mozilla/Mutex.h"
+#include "ARefBase.h"
+#include "TimingStruct.h"
+#include "HttpTrafficAnalyzer.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsISupportsPriority.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsITimer.h"
+#include "Http3Session.h"
+
+class nsIDNSRecord;
+class nsISocketTransport;
+class nsITLSSocketControl;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpHandler;
+class ASpdySession;
+
+// 1dcc863e-db90-4652-a1fe-13fea0b54e46
+#define HTTPCONNECTIONUDP_IID \
+ { \
+ 0xb97d2036, 0xb441, 0x48be, { \
+ 0xb3, 0x1e, 0x25, 0x3e, 0xe8, 0x32, 0xdd, 0x67 \
+ } \
+ }
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP - represents a connection to a HTTP3 server
+//
+// NOTE: this objects lives on the socket thread only. it should not be
+// accessed from any other thread.
+//-----------------------------------------------------------------------------
+
+class HttpConnectionUDP final : public HttpConnectionBase,
+ public nsIUDPSocketSyncListener,
+ public nsIInterfaceRequestor {
+ private:
+ virtual ~HttpConnectionUDP();
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTPCONNECTIONUDP_IID)
+ NS_DECL_HTTPCONNECTIONBASE
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETSYNCLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ HttpConnectionUDP();
+
+ [[nodiscard]] nsresult Init(nsHttpConnectionInfo* info,
+ nsIDNSRecord* dnsRecord, nsresult status,
+ nsIInterfaceRequestor* callbacks, uint32_t caps);
+
+ friend class HttpConnectionUDPForceIO;
+
+ [[nodiscard]] static nsresult ReadFromStream(nsIInputStream*, void*,
+ const char*, uint32_t, uint32_t,
+ uint32_t*);
+
+ bool UsingHttp3() override { return true; }
+
+ void OnQuicTimeoutExpired();
+
+ int64_t BytesWritten() override;
+
+ nsresult GetSelfAddr(NetAddr* addr) override;
+ nsresult GetPeerAddr(NetAddr* addr) override;
+ bool ResolvedByTRR() override;
+ nsIRequest::TRRMode EffectiveTRRMode() override;
+ TRRSkippedReason TRRSkipReason() override;
+ bool GetEchConfigUsed() override { return false; }
+
+ void NotifyDataRead();
+ void NotifyDataWrite();
+
+ private:
+ [[nodiscard]] nsresult OnTransactionDone(nsresult reason);
+ nsresult RecvData();
+ nsresult SendData();
+
+ private:
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+
+ RefPtr<nsIAsyncInputStream> mInputOverflow;
+
+ bool mConnectedTransport = false;
+ bool mDontReuse = false;
+ bool mIsReused = false;
+ bool mLastTransactionExpectedNoContent = false;
+
+ int32_t mPriority = nsISupportsPriority::PRIORITY_NORMAL;
+
+ private:
+ // For ForceSend()
+ static void ForceSendIO(nsITimer* aTimer, void* aClosure);
+ [[nodiscard]] nsresult MaybeForceSendIO();
+ bool mForceSendPending = false;
+ nsCOMPtr<nsITimer> mForceSendTimer;
+
+ PRIntervalTime mLastRequestBytesSentTime = 0;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+
+ nsCOMPtr<nsINetAddr> mSelfAddr;
+ nsCOMPtr<nsINetAddr> mPeerAddr;
+ bool mResolvedByTRR = false;
+ nsIRequest::TRRMode mEffectiveTRRMode = nsIRequest::TRR_DEFAULT_MODE;
+ TRRSkippedReason mTRRSkipReason = nsITRRSkipReason::TRR_UNSET;
+
+ private:
+ // Http3
+ RefPtr<Http3Session> mHttp3Session;
+ nsCString mAlpnToken;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpConnectionUDP, HTTPCONNECTIONUDP_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // HttpConnectionUDP_h__
diff --git a/netwerk/protocol/http/HttpInfo.cpp b/netwerk/protocol/http/HttpInfo.cpp
new file mode 100644
index 0000000000..b3574cab1c
--- /dev/null
+++ b/netwerk/protocol/http/HttpInfo.cpp
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http:mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpHandler.h"
+#include "HttpInfo.h"
+
+void mozilla::net::HttpInfo::GetHttpConnectionData(
+ nsTArray<HttpRetParams>* args) {
+ if (gHttpHandler && gHttpHandler->ConnMgr()) {
+ gHttpHandler->ConnMgr()->GetConnectionData(args);
+ }
+}
diff --git a/netwerk/protocol/http/HttpInfo.h b/netwerk/protocol/http/HttpInfo.h
new file mode 100644
index 0000000000..981a3df56b
--- /dev/null
+++ b/netwerk/protocol/http/HttpInfo.h
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http:mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpInfo__
+#define nsHttpInfo__
+
+#include "nsTArrayForwardDeclare.h"
+
+namespace mozilla {
+namespace net {
+
+struct HttpRetParams;
+
+class HttpInfo {
+ public:
+ /* Calls getConnectionData method in nsHttpConnectionMgr. */
+ static void GetHttpConnectionData(nsTArray<HttpRetParams>*);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpInfo__
diff --git a/netwerk/protocol/http/HttpLog.h b/netwerk/protocol/http/HttpLog.h
new file mode 100644
index 0000000000..58de97590b
--- /dev/null
+++ b/netwerk/protocol/http/HttpLog.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpLog_h__
+#define HttpLog_h__
+
+/*******************************************************************************
+ * This file should ONLY be #included by source (.cpp) files in the /http
+ * directory, not headers (.h). If you need to use LOG() in a .h file, call
+ * PR_LOG directly.
+ *
+ * This file should also be the first #include in your file.
+ *
+ * Yes, this is kludgy.
+ *******************************************************************************/
+
+#include "mozilla/net/NeckoChild.h"
+
+// Get rid of Chromium's LOG definition
+#undef LOG
+
+//
+// Log module for HTTP Protocol logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=nsHttp:5
+// set MOZ_LOG_FILE=http.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file http.log.
+//
+namespace mozilla {
+namespace net {
+Maybe<nsCString> CallingScriptLocationString();
+void LogCallingScriptLocation(void* instance);
+void LogCallingScriptLocation(void* instance,
+ const Maybe<nsCString>& aLogLocation);
+extern LazyLogModule gHttpLog;
+} // namespace net
+} // namespace mozilla
+
+// http logging
+#define LOG1(args) \
+ MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Error, args)
+#define LOG2(args) \
+ MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Warning, args)
+#define LOG3(args) \
+ MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Info, args)
+#define LOG4(args) \
+ MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Debug, args)
+#define LOG5(args) \
+ MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Verbose, args)
+#define LOG(args) LOG4(args)
+#define LOGTIME(start, args) \
+ MOZ_LOG_TIME(mozilla::net::gHttpLog, mozilla::LogLevel::Debug, &(start), args)
+
+#define LOG1_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Error)
+#define LOG2_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Warning)
+#define LOG3_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Info)
+#define LOG4_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Debug)
+#define LOG5_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Verbose)
+#define LOG_ENABLED() LOG4_ENABLED()
+
+#endif // HttpLog_h__
diff --git a/netwerk/protocol/http/HttpTrafficAnalyzer.cpp b/netwerk/protocol/http/HttpTrafficAnalyzer.cpp
new file mode 100644
index 0000000000..5b8f3bbb21
--- /dev/null
+++ b/netwerk/protocol/http/HttpTrafficAnalyzer.cpp
@@ -0,0 +1,269 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HttpTrafficAnalyzer.h"
+#include "HttpLog.h"
+
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "nsSocketTransportService2.h"
+
+namespace mozilla {
+namespace net {
+
+constexpr auto kInvalidCategory = "INVALID_CATEGORY"_ns;
+
+#define DEFINE_CATEGORY(_name, _idx) nsLiteralCString("Y" #_idx "_" #_name),
+static constexpr nsLiteralCString gKeyName[] = {
+#include "HttpTrafficAnalyzer.inc"
+ kInvalidCategory,
+};
+#undef DEFINE_CATEGORY
+
+#define DEFINE_CATEGORY(_name, _idx) \
+ Telemetry::LABELS_HTTP_TRAFFIC_ANALYSIS_3::Y##_idx##_##_name,
+static const Telemetry::LABELS_HTTP_TRAFFIC_ANALYSIS_3 gTelemetryLabel[] = {
+#include "HttpTrafficAnalyzer.inc"
+};
+#undef DEFINE_CATEGORY
+
+// ----------------------------------------------------
+// | Flags | Load Type |
+// ----------------------------------------------------
+// | nsIClassOfService::Leader | A |
+// | w/o nsIRequest::LOAD_BACKGROUND | B |
+// | w/ nsIRequest::LOAD_BACKGROUND | C |
+// ----------------------------------------------------
+// | Category | List Category |
+// ----------------------------------------------------
+// | Basic Disconnected List | I |
+// | Content | II |
+// | Fingerprinting | III |
+// ----------------------------------------------------
+// ====================================================
+// | Normal Mode |
+// ----------------------------------------------------
+// | Y = 0 for system principals |
+// | Y = 1 for first party |
+// | Y = 2 for non-listed third party type |
+// ----------------------------------------------------
+// | \Y\ | Type A | Type B | Type C |
+// ----------------------------------------------------
+// | Category I | 3 | 4 | 5 |
+// | Category II | 6 | 7 | 8 |
+// | Category III | 9 | 10 | 11 |
+// ====================================================
+// | Private Mode |
+// ----------------------------------------------------
+// | Y = 12 for system principals |
+// | Y = 13 for first party |
+// | Y = 14 for non-listed third party type |
+// ----------------------------------------------------
+// | \Y\ | Type A | Type B | Type C |
+// ----------------------------------------------------
+// | Category I | 15 | 16 | 17 |
+// | Category II | 18 | 19 | 20 |
+// | Category III | 21 | 22 | 23 |
+// ====================================================
+
+HttpTrafficCategory HttpTrafficAnalyzer::CreateTrafficCategory(
+ bool aIsPrivateMode, bool aIsSystemPrincipal, bool aIsThirdParty,
+ ClassOfService aClassOfService, TrackingClassification aClassification) {
+ uint8_t category = aIsPrivateMode ? 12 : 0;
+ if (aIsSystemPrincipal) {
+ MOZ_ASSERT_IF(!aIsPrivateMode,
+ gKeyName[category].EqualsLiteral("Y0_N1Sys"));
+ MOZ_ASSERT_IF(aIsPrivateMode,
+ gKeyName[category].EqualsLiteral("Y12_P1Sys"));
+ return static_cast<HttpTrafficCategory>(category);
+ }
+ ++category;
+
+ if (!aIsThirdParty) {
+ MOZ_ASSERT_IF(!aIsPrivateMode, gKeyName[category].EqualsLiteral("Y1_N1"));
+ MOZ_ASSERT_IF(aIsPrivateMode, gKeyName[category].EqualsLiteral("Y13_P1"));
+ return static_cast<HttpTrafficCategory>(category);
+ }
+
+ switch (aClassification) {
+ case TrackingClassification::eNone:
+ ++category;
+ MOZ_ASSERT_IF(!aIsPrivateMode,
+ gKeyName[category].EqualsLiteral("Y2_N3Oth"));
+ MOZ_ASSERT_IF(aIsPrivateMode,
+ gKeyName[category].EqualsLiteral("Y14_P3Oth"));
+ return static_cast<HttpTrafficCategory>(category);
+ case TrackingClassification::eBasic:
+ category += 2;
+ break;
+ case TrackingClassification::eContent:
+ category += 5;
+ break;
+ case TrackingClassification::eFingerprinting:
+ category += 8;
+ break;
+ default:
+ MOZ_ASSERT(false, "incorrect classification");
+ return HttpTrafficCategory::eInvalid;
+ }
+
+ switch (aClassOfService) {
+ case ClassOfService::eLeader:
+ MOZ_ASSERT_IF(
+ !aIsPrivateMode,
+ (aClassification == TrackingClassification::eBasic &&
+ gKeyName[category].EqualsLiteral("Y3_N3BasicLead")) ||
+ (aClassification == TrackingClassification::eContent &&
+ gKeyName[category].EqualsLiteral("Y6_N3ContentLead")) ||
+ (aClassification == TrackingClassification::eFingerprinting &&
+ gKeyName[category].EqualsLiteral("Y9_N3FpLead")));
+ MOZ_ASSERT_IF(
+ aIsPrivateMode,
+ (aClassification == TrackingClassification::eBasic &&
+ gKeyName[category].EqualsLiteral("Y15_P3BasicLead")) ||
+ (aClassification == TrackingClassification::eContent &&
+ gKeyName[category].EqualsLiteral("Y18_P3ContentLead")) ||
+ (aClassification == TrackingClassification::eFingerprinting &&
+ gKeyName[category].EqualsLiteral("Y21_P3FpLead")));
+ return static_cast<HttpTrafficCategory>(category);
+ case ClassOfService::eBackground:
+ ++category;
+
+ MOZ_ASSERT_IF(
+ !aIsPrivateMode,
+ (aClassification == TrackingClassification::eBasic &&
+ gKeyName[category].EqualsLiteral("Y4_N3BasicBg")) ||
+ (aClassification == TrackingClassification::eContent &&
+ gKeyName[category].EqualsLiteral("Y7_N3ContentBg")) ||
+ (aClassification == TrackingClassification::eFingerprinting &&
+ gKeyName[category].EqualsLiteral("Y10_N3FpBg")));
+ MOZ_ASSERT_IF(
+ aIsPrivateMode,
+ (aClassification == TrackingClassification::eBasic &&
+ gKeyName[category].EqualsLiteral("Y16_P3BasicBg")) ||
+ (aClassification == TrackingClassification::eContent &&
+ gKeyName[category].EqualsLiteral("Y19_P3ContentBg")) ||
+ (aClassification == TrackingClassification::eFingerprinting &&
+ gKeyName[category].EqualsLiteral("Y22_P3FpBg")));
+
+ return static_cast<HttpTrafficCategory>(category);
+ case ClassOfService::eOther:
+ category += 2;
+
+ MOZ_ASSERT_IF(
+ !aIsPrivateMode,
+ (aClassification == TrackingClassification::eBasic &&
+ gKeyName[category].EqualsLiteral("Y5_N3BasicOth")) ||
+ (aClassification == TrackingClassification::eContent &&
+ gKeyName[category].EqualsLiteral("Y8_N3ContentOth")) ||
+ (aClassification == TrackingClassification::eFingerprinting &&
+ gKeyName[category].EqualsLiteral("Y11_N3FpOth")));
+ MOZ_ASSERT_IF(
+ aIsPrivateMode,
+ (aClassification == TrackingClassification::eBasic &&
+ gKeyName[category].EqualsLiteral("Y17_P3BasicOth")) ||
+ (aClassification == TrackingClassification::eContent &&
+ gKeyName[category].EqualsLiteral("Y20_P3ContentOth")) ||
+ (aClassification == TrackingClassification::eFingerprinting &&
+ gKeyName[category].EqualsLiteral("Y23_P3FpOth")));
+
+ return static_cast<HttpTrafficCategory>(category);
+ }
+
+ MOZ_ASSERT(false, "incorrect class of service");
+ return HttpTrafficCategory::eInvalid;
+}
+
+void HttpTrafficAnalyzer::IncrementHttpTransaction(
+ HttpTrafficCategory aCategory) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+ MOZ_ASSERT(aCategory != HttpTrafficCategory::eInvalid, "invalid category");
+
+ LOG(("HttpTrafficAnalyzer::IncrementHttpTransaction [%s] [this=%p]\n",
+ gKeyName[aCategory].get(), this));
+
+ Telemetry::AccumulateCategoricalKeyed("Transaction"_ns,
+ gTelemetryLabel[aCategory]);
+}
+
+void HttpTrafficAnalyzer::IncrementHttpConnection(
+ HttpTrafficCategory aCategory) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+ MOZ_ASSERT(aCategory != HttpTrafficCategory::eInvalid, "invalid category");
+
+ LOG(("HttpTrafficAnalyzer::IncrementHttpConnection [%s] [this=%p]\n",
+ gKeyName[aCategory].get(), this));
+
+ Telemetry::AccumulateCategoricalKeyed("Connection"_ns,
+ gTelemetryLabel[aCategory]);
+}
+
+void HttpTrafficAnalyzer::IncrementHttpConnection(
+ nsTArray<HttpTrafficCategory>&& aCategories) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+ MOZ_ASSERT(!aCategories.IsEmpty(), "empty category");
+
+ nsTArray<HttpTrafficCategory> categories(std::move(aCategories));
+
+ LOG(("HttpTrafficAnalyzer::IncrementHttpConnection size=%" PRIuPTR
+ " [this=%p]\n",
+ categories.Length(), this));
+
+ // divide categories into 4 parts:
+ // 1) normal 1st-party (Y in {0, 1})
+ // 2) normal 3rd-party (1 < Y < 12)
+ // 3) private 1st-party (Y in {12, 13})
+ // 4) private 3rd-party (13 < Y < 24)
+ // Normal and private transaction should not share the same connection,
+ // and we choose 3rd-party prior than 1st-party.
+ HttpTrafficCategory best = categories[0];
+ for (auto category : categories) {
+ MOZ_ASSERT(category != HttpTrafficCategory::eInvalid, "invalid category");
+
+ if (category == 0 || category == 1 || category == 12 || category == 13) {
+ // first party
+ MOZ_ASSERT(gKeyName[category].EqualsLiteral("Y0_N1Sys") ||
+ gKeyName[category].EqualsLiteral("Y1_N1") ||
+ gKeyName[category].EqualsLiteral("Y12_P1Sys") ||
+ gKeyName[category].EqualsLiteral("Y13_P1"));
+ continue;
+ }
+ // third party
+ MOZ_ASSERT(gKeyName[24].Equals(kInvalidCategory),
+ "category definition isn't consistent");
+ best = category;
+ break;
+ }
+
+ IncrementHttpConnection(best);
+}
+
+#define CLAMP_U32(num) \
+ Clamp<uint32_t>(num, 0, std::numeric_limits<uint32_t>::max())
+
+void HttpTrafficAnalyzer::AccumulateHttpTransferredSize(
+ HttpTrafficCategory aCategory, uint64_t aBytesRead, uint64_t aBytesSent) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+ MOZ_ASSERT(aCategory != HttpTrafficCategory::eInvalid, "invalid category");
+
+ LOG(("HttpTrafficAnalyzer::AccumulateHttpTransferredSize [%s] rb=%" PRIu64 " "
+ "sb=%" PRIu64 " [this=%p]\n",
+ gKeyName[aCategory].get(), aBytesRead, aBytesSent, this));
+
+ // Telemetry supports uint32_t only, and we send KB here.
+ auto total = CLAMP_U32((aBytesRead >> 10) + (aBytesSent >> 10));
+ if (aBytesRead || aBytesSent) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::NETWORKING_DATA_TRANSFERRED_V3_KB,
+ NS_ConvertUTF8toUTF16(gKeyName[aCategory]), total);
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpTrafficAnalyzer.h b/netwerk/protocol/http/HttpTrafficAnalyzer.h
new file mode 100644
index 0000000000..e319a0697e
--- /dev/null
+++ b/netwerk/protocol/http/HttpTrafficAnalyzer.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_netwerk_protocol_http_HttpTrafficAnalyzer_h
+#define mozilla_netwerk_protocol_http_HttpTrafficAnalyzer_h
+
+#include <stdint.h>
+#include "nsTArrayForwardDeclare.h"
+
+namespace mozilla {
+namespace net {
+
+#define DEFINE_CATEGORY(_name, _idx) e##_name = _idx##u,
+enum HttpTrafficCategory : uint8_t {
+#include "HttpTrafficAnalyzer.inc"
+ eInvalid,
+};
+#undef DEFINE_CATEGORY
+
+class HttpTrafficAnalyzer final {
+ public:
+ enum ClassOfService : uint8_t {
+ eLeader = 0,
+ eBackground = 1,
+ eOther = 255,
+ };
+
+ enum TrackingClassification : uint8_t {
+ eNone = 0,
+ eBasic = 1,
+ eContent = 2,
+ eFingerprinting = 3,
+ };
+
+ static HttpTrafficCategory CreateTrafficCategory(
+ bool aIsPrivateMode, bool aIsSystemPrincipal, bool aIsThirdParty,
+ ClassOfService aClassOfService, TrackingClassification aClassification);
+
+ void IncrementHttpTransaction(HttpTrafficCategory aCategory);
+ void IncrementHttpConnection(HttpTrafficCategory aCategory);
+ void IncrementHttpConnection(nsTArray<HttpTrafficCategory>&& aCategories);
+ void AccumulateHttpTransferredSize(HttpTrafficCategory aCategory,
+ uint64_t aBytesRead, uint64_t aBytesSent);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_protocol_http_HttpTrafficAnalyzer_h
diff --git a/netwerk/protocol/http/HttpTrafficAnalyzer.inc b/netwerk/protocol/http/HttpTrafficAnalyzer.inc
new file mode 100644
index 0000000000..3311d33ee8
--- /dev/null
+++ b/netwerk/protocol/http/HttpTrafficAnalyzer.inc
@@ -0,0 +1,106 @@
+// Type A) Parser blocking script loads will have an mContentPolicyType
+// indicating a script load and a nsIClassOfService::Leader class of service.
+// Type B) I couldn’t find a class flag that corresponds to synchronous loads
+// that block the load event, but I think a simple way to determine
+// which ones they are is to examine whether the channel belongs to a
+// load group and do not have a LOAD_BACKGROUND load flag.
+// If that condition holds, then it is blocking the load event of some document.
+// Type C) I think a simple way to find channels that don’t block document loads
+// (e.g. XHR and fetch) is to look for those which are in a load group
+// and have a LOAD_BACKGROUND load flag.
+
+
+// Y=0 - all system principal connections.
+DEFINE_CATEGORY(N1Sys, 0)
+
+// Y=1 - all requests/connections/bytes that are first party.
+DEFINE_CATEGORY(N1, 1)
+
+// Y=2 - all requests/connections/bytes that are third party
+// but don’t fall into other categories
+DEFINE_CATEGORY(N3Oth, 2)
+
+// Y=3 - all requests/connections/bytes associated with third party loads that
+// match the Analytics/Social/Advertising (Basic) Category and have a load of type (A)
+DEFINE_CATEGORY(N3BasicLead, 3)
+
+// Y=4 - all requests/connections/bytes associated with third party loads that
+// match the Analytics/Social/Advertising (Basic) Category and have a load of type (B)
+DEFINE_CATEGORY(N3BasicBg, 4)
+
+// Y=5 - all requests/connections/bytes associated with third party loads that
+// match the Analytics/Social/Advertising (Basic) Category and have a load of type (C)
+DEFINE_CATEGORY(N3BasicOth, 5)
+
+// Y=6 - all requests/connections/bytes associated with third party loads that
+// match the Content Category and have a load of type (A)
+DEFINE_CATEGORY(N3ContentLead, 6)
+
+// Y=7 - all requests/connections/bytes associated with third party loads that
+// match the Content Category and have a load of type (B)
+DEFINE_CATEGORY(N3ContentBg, 7)
+
+// Y=8 - all requests/connections/bytes associated with third party loads that
+// match the Content Category and have a load of type (C)
+DEFINE_CATEGORY(N3ContentOth, 8)
+
+// Y=9 - all requests/connections/bytes associated with third party loads that
+// match the Fingerprinting Category and have a load of type (A)
+DEFINE_CATEGORY(N3FpLead, 9)
+
+// Y=10 - all requests/connections/bytes associated with third party loads that
+// match the Fingerprinting Category and have a load of type (B)
+DEFINE_CATEGORY(N3FpBg, 10)
+
+// Y=11 - all requests/connections/bytes associated with third party loads that
+// match the Fingerprinting Category and have a load of type (C)
+DEFINE_CATEGORY(N3FpOth, 11)
+
+// Y=12 - private mode system principal connections.
+DEFINE_CATEGORY(P1Sys, 12)
+
+// Y=13 - private mode and all requests/connections/bytes that are first party.
+DEFINE_CATEGORY(P1, 13)
+
+// Y=14 - private mode and all requests/connections/bytes that are third party
+// but don’t fall into other categories
+DEFINE_CATEGORY(P3Oth, 14)
+
+// Y=15 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Analytics/Social/Advertising (Basic) Category
+// and have a load of type (A)
+DEFINE_CATEGORY(P3BasicLead, 15)
+
+// Y=16 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Analytics/Social/Advertising (Basic) Category
+// and have a load of type (B)
+DEFINE_CATEGORY(P3BasicBg, 16)
+
+// Y=17 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Analytics/Social/Advertising (Basic) Category
+// and have a load of type (C)
+DEFINE_CATEGORY(P3BasicOth, 17)
+
+// Y=18 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Content Category and have a load of type (A)
+DEFINE_CATEGORY(P3ContentLead, 18)
+
+// Y=19 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Content Category and have a load of type (B)
+DEFINE_CATEGORY(P3ContentBg, 19)
+
+// Y=20 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Content Category and have a load of type (C)
+DEFINE_CATEGORY(P3ContentOth, 20)
+
+// Y=21 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Fingerprinting Category and have a load of type (A)
+DEFINE_CATEGORY(P3FpLead, 21)
+
+// Y=22 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Fingerprinting Category and have a load of type (B)
+DEFINE_CATEGORY(P3FpBg, 22)
+
+// Y=23 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Fingerprinting Category and have a load of type (C)
+DEFINE_CATEGORY(P3FpOth, 23)
diff --git a/netwerk/protocol/http/HttpTransactionChild.cpp b/netwerk/protocol/http/HttpTransactionChild.cpp
new file mode 100644
index 0000000000..c4294cb0b4
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionChild.cpp
@@ -0,0 +1,665 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HttpTransactionChild.h"
+
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/BackgroundDataBridgeParent.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "mozilla/net/InputChannelThrottleQueueChild.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsInputStreamPump.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsHttpHandler.h"
+#include "nsNetUtil.h"
+#include "nsProxyInfo.h"
+#include "nsProxyRelease.h"
+#include "nsQueryObject.h"
+#include "nsSerializationHelper.h"
+#include "OpaqueResponseUtils.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(HttpTransactionChild, nsIRequestObserver, nsIStreamListener,
+ nsITransportEventSink, nsIThrottledInputChannel,
+ nsIThreadRetargetableStreamListener, nsIEarlyHintObserver);
+
+//-----------------------------------------------------------------------------
+// HttpTransactionChild <public>
+//-----------------------------------------------------------------------------
+
+HttpTransactionChild::HttpTransactionChild() {
+ LOG(("Creating HttpTransactionChild @%p\n", this));
+}
+
+HttpTransactionChild::~HttpTransactionChild() {
+ LOG(("Destroying HttpTransactionChild @%p\n", this));
+}
+
+static already_AddRefed<nsIRequestContext> CreateRequestContext(
+ uint64_t aRequestContextID) {
+ if (!aRequestContextID) {
+ return nullptr;
+ }
+
+ nsIRequestContextService* rcsvc = gHttpHandler->GetRequestContextService();
+ if (!rcsvc) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIRequestContext> requestContext;
+ rcsvc->GetRequestContext(aRequestContextID, getter_AddRefs(requestContext));
+
+ return requestContext.forget();
+}
+
+nsresult HttpTransactionChild::InitInternal(
+ uint32_t caps, const HttpConnectionInfoCloneArgs& infoArgs,
+ nsHttpRequestHead* requestHead, nsIInputStream* requestBody,
+ uint64_t requestContentLength, bool requestBodyHasHeaders,
+ uint64_t browserId, uint8_t httpTrafficCategory, uint64_t requestContextID,
+ ClassOfService classOfService, uint32_t initialRwin,
+ bool responseTimeoutEnabled, uint64_t channelId,
+ bool aHasTransactionObserver,
+ const Maybe<H2PushedStreamArg>& aPushedStreamArg) {
+ LOG(("HttpTransactionChild::InitInternal [this=%p caps=%x]\n", this, caps));
+
+ RefPtr<nsHttpConnectionInfo> cinfo =
+ nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(infoArgs);
+ nsCOMPtr<nsIRequestContext> rc = CreateRequestContext(requestContextID);
+
+ HttpTransactionShell::OnPushCallback pushCallback = nullptr;
+ if (caps & NS_HTTP_ONPUSH_LISTENER) {
+ RefPtr<HttpTransactionChild> self = this;
+ pushCallback = [self](uint32_t aPushedStreamId, const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction) {
+ bool res = false;
+ if (self->CanSend()) {
+ res =
+ self->SendOnH2PushStream(aPushedStreamId, PromiseFlatCString(aUrl),
+ PromiseFlatCString(aRequestString));
+ }
+ return res ? NS_OK : NS_ERROR_FAILURE;
+ };
+ }
+
+ std::function<void(TransactionObserverResult&&)> observer;
+ if (aHasTransactionObserver) {
+ nsMainThreadPtrHandle<HttpTransactionChild> handle(
+ new nsMainThreadPtrHolder<HttpTransactionChild>(
+ "HttpTransactionChildProxy", this, false));
+ observer = [handle](TransactionObserverResult&& aResult) {
+ handle->mTransactionObserverResult.emplace(std::move(aResult));
+ };
+ }
+
+ RefPtr<nsHttpTransaction> transWithPushedStream;
+ uint32_t pushedStreamId = 0;
+ if (aPushedStreamArg) {
+ HttpTransactionChild* transChild = static_cast<HttpTransactionChild*>(
+ aPushedStreamArg.ref().transWithPushedStream().AsChild().get());
+ transWithPushedStream = transChild->GetHttpTransaction();
+ pushedStreamId = aPushedStreamArg.ref().pushedStreamId();
+ }
+
+ nsresult rv = mTransaction->Init(
+ caps, cinfo, requestHead, requestBody, requestContentLength,
+ requestBodyHasHeaders, GetCurrentSerialEventTarget(),
+ nullptr, // TODO: security callback, fix in bug 1512479.
+ this, browserId, static_cast<HttpTrafficCategory>(httpTrafficCategory),
+ rc, classOfService, initialRwin, responseTimeoutEnabled, channelId,
+ std::move(observer), std::move(pushCallback), transWithPushedStream,
+ pushedStreamId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mTransaction = nullptr;
+ return rv;
+ }
+
+ Unused << mTransaction->AsyncRead(this, getter_AddRefs(mTransactionPump));
+ return rv;
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvCancelPump(
+ const nsresult& aStatus) {
+ LOG(("HttpTransactionChild::RecvCancelPump start [this=%p]\n", this));
+ CancelInternal(aStatus);
+ return IPC_OK();
+}
+
+void HttpTransactionChild::CancelInternal(nsresult aStatus) {
+ MOZ_ASSERT(NS_FAILED(aStatus));
+
+ mCanceled = true;
+ mStatus = aStatus;
+ if (mTransactionPump) {
+ mTransactionPump->Cancel(mStatus);
+ }
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvSuspendPump() {
+ LOG(("HttpTransactionChild::RecvSuspendPump start [this=%p]\n", this));
+
+ if (mTransactionPump) {
+ mTransactionPump->Suspend();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvResumePump() {
+ LOG(("HttpTransactionChild::RecvResumePump start [this=%p]\n", this));
+
+ if (mTransactionPump) {
+ mTransactionPump->Resume();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvInit(
+ const uint32_t& aCaps, const HttpConnectionInfoCloneArgs& aArgs,
+ const nsHttpRequestHead& aReqHeaders, const Maybe<IPCStream>& aRequestBody,
+ const uint64_t& aReqContentLength, const bool& aReqBodyIncludesHeaders,
+ const uint64_t& aTopLevelOuterContentWindowId,
+ const uint8_t& aHttpTrafficCategory, const uint64_t& aRequestContextID,
+ const ClassOfService& aClassOfService, const uint32_t& aInitialRwin,
+ const bool& aResponseTimeoutEnabled, const uint64_t& aChannelId,
+ const bool& aHasTransactionObserver,
+ const Maybe<H2PushedStreamArg>& aPushedStreamArg,
+ const mozilla::Maybe<PInputChannelThrottleQueueChild*>& aThrottleQueue,
+ const bool& aIsDocumentLoad, const TimeStamp& aRedirectStart,
+ const TimeStamp& aRedirectEnd) {
+ mRequestHead = aReqHeaders;
+ if (aRequestBody) {
+ mUploadStream = mozilla::ipc::DeserializeIPCStream(aRequestBody);
+ }
+
+ mTransaction = new nsHttpTransaction();
+ mChannelId = aChannelId;
+ mIsDocumentLoad = aIsDocumentLoad;
+ mRedirectStart = aRedirectStart;
+ mRedirectEnd = aRedirectEnd;
+
+ if (aThrottleQueue.isSome()) {
+ mThrottleQueue =
+ static_cast<InputChannelThrottleQueueChild*>(aThrottleQueue.ref());
+ }
+
+ nsresult rv = InitInternal(
+ aCaps, aArgs, &mRequestHead, mUploadStream, aReqContentLength,
+ aReqBodyIncludesHeaders, aTopLevelOuterContentWindowId,
+ aHttpTrafficCategory, aRequestContextID, aClassOfService, aInitialRwin,
+ aResponseTimeoutEnabled, aChannelId, aHasTransactionObserver,
+ aPushedStreamArg);
+ if (NS_FAILED(rv)) {
+ LOG(("HttpTransactionChild::RecvInit: [this=%p] InitInternal failed!\n",
+ this));
+ mTransaction = nullptr;
+ SendOnInitFailed(rv);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvSetDNSWasRefreshed() {
+ LOG(("HttpTransactionChild::SetDNSWasRefreshed [this=%p]\n", this));
+ if (mTransaction) {
+ mTransaction->SetDNSWasRefreshed();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvDontReuseConnection() {
+ LOG(("HttpTransactionChild::RecvDontReuseConnection [this=%p]\n", this));
+ if (mTransaction) {
+ mTransaction->DontReuseConnection();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionChild::RecvSetH2WSConnRefTaken() {
+ LOG(("HttpTransactionChild::RecvSetH2WSConnRefTaken [this=%p]\n", this));
+ if (mTransaction) {
+ mTransaction->SetH2WSConnRefTaken();
+ }
+ return IPC_OK();
+}
+
+void HttpTransactionChild::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("HttpTransactionChild::ActorDestroy [this=%p]\n", this));
+ mTransaction = nullptr;
+ mTransactionPump = nullptr;
+}
+
+nsHttpTransaction* HttpTransactionChild::GetHttpTransaction() {
+ return mTransaction.get();
+}
+
+//-----------------------------------------------------------------------------
+// HttpTransactionChild <nsIStreamListener>
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpTransactionChild::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOG(("HttpTransactionChild::OnDataAvailable [this=%p, aOffset= %" PRIu64
+ " aCount=%" PRIu32 "]\n",
+ this, aOffset, aCount));
+
+ // Don't bother sending IPC if already canceled.
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ // TODO: send string data in chunks and handle errors. Bug 1600129.
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mLogicalOffset += aCount;
+
+ if (NS_IsMainThread()) {
+ if (!CanSend()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsHttp::SendFunc<nsCString> sendFunc =
+ [self = UnsafePtr<HttpTransactionChild>(this)](
+ const nsCString& aData, uint64_t aOffset, uint32_t aCount) {
+ return self->SendOnDataAvailable(aData, aOffset, aCount,
+ TimeStamp::Now());
+ };
+
+ LOG((" ODA to parent process"));
+ if (!nsHttp::SendDataInChunks(data, aOffset, aCount, sendFunc)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mDataBridgeParent);
+
+ if (!mDataBridgeParent->CanSend()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsHttp::SendFunc<nsDependentCSubstring> sendFunc =
+ [self = UnsafePtr<HttpTransactionChild>(this)](
+ const nsDependentCSubstring& aData, uint64_t aOffset,
+ uint32_t aCount) {
+ return self->mDataBridgeParent->SendOnTransportAndData(
+ aOffset, aCount, aData, TimeStamp::Now());
+ };
+
+ LOG((" ODA to content process"));
+ if (!nsHttp::SendDataInChunks(data, aOffset, aCount, sendFunc)) {
+ MOZ_ASSERT(false, "Send ODA to content process failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ // We still need to send ODA to parent process, because the data needs to be
+ // saved in cache. Note that we set dataSentToChildProcess to true, so this
+ // ODA will not be sent to child process.
+ RefPtr<HttpTransactionChild> self = this;
+ rv = NS_DispatchToMainThread(
+ NS_NewRunnableFunction(
+ "HttpTransactionChild::OnDataAvailable",
+ [self, offset(aOffset), count(aCount), data(data)]() {
+ nsHttp::SendFunc<nsCString> sendFunc =
+ [self](const nsCString& aData, uint64_t aOffset,
+ uint32_t aCount) {
+ return self->SendOnDataAvailable(aData, aOffset, aCount,
+ TimeStamp::Now());
+ };
+
+ if (!nsHttp::SendDataInChunks(data, offset, count, sendFunc)) {
+ self->CancelInternal(NS_ERROR_FAILURE);
+ }
+ }),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_OK;
+}
+
+static TimingStructArgs ToTimingStructArgs(TimingStruct aTiming) {
+ TimingStructArgs args;
+ args.domainLookupStart() = aTiming.domainLookupStart;
+ args.domainLookupEnd() = aTiming.domainLookupEnd;
+ args.connectStart() = aTiming.connectStart;
+ args.tcpConnectEnd() = aTiming.tcpConnectEnd;
+ args.secureConnectionStart() = aTiming.secureConnectionStart;
+ args.connectEnd() = aTiming.connectEnd;
+ args.requestStart() = aTiming.requestStart;
+ args.responseStart() = aTiming.responseStart;
+ args.responseEnd() = aTiming.responseEnd;
+ args.transactionPending() = aTiming.transactionPending;
+ return args;
+}
+
+// The maximum number of bytes to consider when attempting to sniff.
+// See https://mimesniff.spec.whatwg.org/#reading-the-resource-header.
+static const uint32_t MAX_BYTES_SNIFFED = 1445;
+
+static void GetDataForSniffer(void* aClosure, const uint8_t* aData,
+ uint32_t aCount) {
+ nsTArray<uint8_t>* outData = static_cast<nsTArray<uint8_t>*>(aClosure);
+ outData->AppendElements(aData, std::min(aCount, MAX_BYTES_SNIFFED));
+}
+
+bool HttpTransactionChild::CanSendODAToContentProcessDirectly(
+ const Maybe<nsHttpResponseHead>& aHead) {
+ if (!StaticPrefs::network_send_ODA_to_content_directly()) {
+ return false;
+ }
+
+ // If this is a document load, the content process that receives ODA is not
+ // decided yet, so don't bother to do the rest check.
+ if (mIsDocumentLoad) {
+ return false;
+ }
+
+ if (!aHead) {
+ return false;
+ }
+
+ // We only need to deliver ODA when the response is succeed.
+ if (aHead->Status() != 200) {
+ return false;
+ }
+
+ // UnknownDecoder could be used in parent process, so we can't send ODA to
+ // content process.
+ if (!aHead->HasContentType()) {
+ return false;
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+HttpTransactionChild::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("HttpTransactionChild::OnStartRequest start [this=%p] mTransaction=%p\n",
+ this, mTransaction.get()));
+
+ // Don't bother sending IPC to parent process if already canceled.
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (!CanSend()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mTransaction);
+
+ nsresult status;
+ aRequest->GetStatus(&status);
+
+ mProtocolVersion.Truncate();
+
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(mTransaction->SecurityInfo());
+ if (securityInfo) {
+ nsAutoCString protocol;
+ if (NS_SUCCEEDED(securityInfo->GetNegotiatedNPN(protocol)) &&
+ !protocol.IsEmpty()) {
+ mProtocolVersion.Assign(protocol);
+ }
+ }
+
+ UniquePtr<nsHttpResponseHead> head(mTransaction->TakeResponseHead());
+ Maybe<nsHttpResponseHead> optionalHead;
+ nsTArray<uint8_t> dataForSniffer;
+ if (head) {
+ if (mProtocolVersion.IsEmpty()) {
+ HttpVersion version = head->Version();
+ mProtocolVersion.Assign(nsHttp::GetProtocolVersion(version));
+ }
+ optionalHead = Some(*head);
+
+ if (GetOpaqueResponseBlockedReason(*head) ==
+ OpaqueResponseBlockedReason::BLOCKED_SHOULD_SNIFF) {
+ RefPtr<nsInputStreamPump> pump = do_QueryObject(mTransactionPump);
+ pump->PeekStream(GetDataForSniffer, &dataForSniffer);
+ }
+ }
+
+ Maybe<nsCString> optionalAltSvcUsed;
+ nsCString altSvcUsed;
+ if (NS_SUCCEEDED(mTransaction->RequestHead()->GetHeader(
+ nsHttp::Alternate_Service_Used, altSvcUsed)) &&
+ !altSvcUsed.IsEmpty()) {
+ optionalAltSvcUsed.emplace(altSvcUsed);
+ }
+
+ if (CanSendODAToContentProcessDirectly(optionalHead)) {
+ Maybe<RefPtr<BackgroundDataBridgeParent>> dataBridgeParent =
+ SocketProcessChild::GetSingleton()->GetAndRemoveDataBridge(mChannelId);
+ // Check if there is a registered BackgroundDataBridgeParent.
+ if (dataBridgeParent) {
+ mDataBridgeParent = std::move(dataBridgeParent.ref());
+
+ nsCOMPtr<nsISerialEventTarget> backgroundThread =
+ mDataBridgeParent->GetBackgroundThread();
+ nsCOMPtr<nsIThreadRetargetableRequest> retargetableTransactionPump;
+ retargetableTransactionPump = do_QueryObject(mTransactionPump);
+ // nsInputStreamPump should implement this interface.
+ MOZ_ASSERT(retargetableTransactionPump);
+
+ nsresult rv =
+ retargetableTransactionPump->RetargetDeliveryTo(backgroundThread);
+ LOG((" Retarget to background thread [this=%p rv=%08x]\n", this,
+ static_cast<uint32_t>(rv)));
+ if (NS_FAILED(rv)) {
+ mDataBridgeParent->Destroy();
+ mDataBridgeParent = nullptr;
+ }
+ }
+ }
+
+ int32_t proxyConnectResponseCode =
+ mTransaction->GetProxyConnectResponseCode();
+
+ nsIRequest::TRRMode mode = nsIRequest::TRR_DEFAULT_MODE;
+ TRRSkippedReason reason = nsITRRSkipReason::TRR_UNSET;
+ {
+ NetAddr selfAddr;
+ NetAddr peerAddr;
+ bool isTrr = false;
+ bool echConfigUsed = false;
+ if (mTransaction) {
+ mTransaction->GetNetworkAddresses(selfAddr, peerAddr, isTrr, mode, reason,
+ echConfigUsed);
+ }
+ }
+
+ Unused << SendOnStartRequest(
+ status, optionalHead, securityInfo, mTransaction->ProxyConnectFailed(),
+ ToTimingStructArgs(mTransaction->Timings()), proxyConnectResponseCode,
+ dataForSniffer, optionalAltSvcUsed, !!mDataBridgeParent,
+ mTransaction->TakeRestartedState(), mTransaction->HTTPSSVCReceivedStage(),
+ mTransaction->GetSupportsHTTP3(), mode, reason, mTransaction->Caps(),
+ TimeStamp::Now());
+ return NS_OK;
+}
+
+ResourceTimingStructArgs HttpTransactionChild::GetTimingAttributes() {
+ // Note that not all fields in ResourceTimingStructArgs are filled, since
+ // we only need some in HttpChannelChild::OnStopRequest.
+ ResourceTimingStructArgs args;
+ args.domainLookupStart() = mTransaction->GetDomainLookupStart();
+ args.domainLookupEnd() = mTransaction->GetDomainLookupEnd();
+ args.connectStart() = mTransaction->GetConnectStart();
+ args.tcpConnectEnd() = mTransaction->GetTcpConnectEnd();
+ args.secureConnectionStart() = mTransaction->GetSecureConnectionStart();
+ args.connectEnd() = mTransaction->GetConnectEnd();
+ args.requestStart() = mTransaction->GetRequestStart();
+ args.responseStart() = mTransaction->GetResponseStart();
+ args.responseEnd() = mTransaction->GetResponseEnd();
+ args.transferSize() = mTransaction->GetTransferSize();
+ args.encodedBodySize() = mLogicalOffset;
+ args.redirectStart() = mRedirectStart;
+ args.redirectEnd() = mRedirectEnd;
+ args.transferSize() = mTransaction->GetTransferSize();
+ args.transactionPending() = mTransaction->GetPendingTime();
+ return args;
+}
+
+NS_IMETHODIMP
+HttpTransactionChild::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ LOG(("HttpTransactionChild::OnStopRequest [this=%p]\n", this));
+
+ mTransactionPump = nullptr;
+
+ auto onStopGuard = MakeScopeExit([&] {
+ LOG((" calling mDataBridgeParent->OnStopRequest by ScopeExit [this=%p]\n",
+ this));
+ MOZ_ASSERT(NS_FAILED(mStatus), "This shoule be only called when failure");
+ if (mDataBridgeParent) {
+ mDataBridgeParent->OnStopRequest(mStatus, ResourceTimingStructArgs(),
+ TimeStamp(), nsHttpHeaderArray(),
+ TimeStamp::Now());
+ mDataBridgeParent = nullptr;
+ }
+ });
+
+ // Don't bother sending IPC to parent process if already canceled.
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (!CanSend()) {
+ mStatus = NS_ERROR_UNEXPECTED;
+ return mStatus;
+ }
+
+ MOZ_ASSERT(mTransaction);
+
+ UniquePtr<nsHttpHeaderArray> headerArray(
+ mTransaction->TakeResponseTrailers());
+ Maybe<nsHttpHeaderArray> responseTrailers;
+ if (headerArray) {
+ responseTrailers.emplace(*headerArray);
+ }
+
+ onStopGuard.release();
+
+ TimeStamp lastActTabOpt = nsHttp::GetLastActiveTabLoadOptimizationHit();
+
+ if (mDataBridgeParent) {
+ mDataBridgeParent->OnStopRequest(
+ aStatus, GetTimingAttributes(), lastActTabOpt,
+ responseTrailers ? *responseTrailers : nsHttpHeaderArray(),
+ TimeStamp::Now());
+ mDataBridgeParent = nullptr;
+ }
+
+ RefPtr<nsHttpConnectionInfo> connInfo = mTransaction->GetConnInfo();
+ HttpConnectionInfoCloneArgs infoArgs;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(connInfo, infoArgs);
+ Unused << SendOnStopRequest(aStatus, mTransaction->ResponseIsComplete(),
+ mTransaction->GetTransferSize(),
+ ToTimingStructArgs(mTransaction->Timings()),
+ responseTrailers, mTransactionObserverResult,
+ lastActTabOpt, infoArgs, TimeStamp::Now());
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpTransactionChild <nsITransportEventSink>
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpTransactionChild::OnTransportStatus(nsITransport* aTransport,
+ nsresult aStatus, int64_t aProgress,
+ int64_t aProgressMax) {
+ LOG(("HttpTransactionChild::OnTransportStatus [this=%p status=%" PRIx32
+ " progress=%" PRId64 "]\n",
+ this, static_cast<uint32_t>(aStatus), aProgress));
+
+ if (!CanSend()) {
+ return NS_OK;
+ }
+
+ Maybe<NetworkAddressArg> arg;
+ if (aStatus == NS_NET_STATUS_CONNECTED_TO ||
+ aStatus == NS_NET_STATUS_WAITING_FOR) {
+ NetAddr selfAddr;
+ NetAddr peerAddr;
+ bool isTrr = false;
+ bool echConfigUsed = false;
+ nsIRequest::TRRMode mode = nsIRequest::TRR_DEFAULT_MODE;
+ TRRSkippedReason reason = nsITRRSkipReason::TRR_UNSET;
+ if (mTransaction) {
+ mTransaction->GetNetworkAddresses(selfAddr, peerAddr, isTrr, mode, reason,
+ echConfigUsed);
+ } else {
+ nsCOMPtr<nsISocketTransport> socketTransport =
+ do_QueryInterface(aTransport);
+ if (socketTransport) {
+ socketTransport->GetSelfAddr(&selfAddr);
+ socketTransport->GetPeerAddr(&peerAddr);
+ socketTransport->ResolvedByTRR(&isTrr);
+ socketTransport->GetEffectiveTRRMode(&mode);
+ socketTransport->GetTrrSkipReason(&reason);
+ socketTransport->GetEchConfigUsed(&echConfigUsed);
+ }
+ }
+ arg.emplace(selfAddr, peerAddr, isTrr, mode, reason, echConfigUsed);
+ }
+
+ Unused << SendOnTransportStatus(aStatus, aProgress, aProgressMax, arg);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIThrottledInputChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpTransactionChild::SetThrottleQueue(nsIInputChannelThrottleQueue* aQueue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpTransactionChild::GetThrottleQueue(nsIInputChannelThrottleQueue** aQueue) {
+ nsCOMPtr<nsIInputChannelThrottleQueue> queue =
+ static_cast<nsIInputChannelThrottleQueue*>(mThrottleQueue.get());
+ queue.forget(aQueue);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EventSourceImpl::nsIThreadRetargetableStreamListener
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+HttpTransactionChild::CheckListenerChain() {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread!");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpTransactionChild::OnDataFinished(nsresult aStatus) { return NS_OK; }
+
+NS_IMETHODIMP
+HttpTransactionChild::EarlyHint(const nsACString& aValue,
+ const nsACString& aReferrerPolicy,
+ const nsACString& aCSPHeader) {
+ LOG(("HttpTransactionChild::EarlyHint"));
+ if (CanSend()) {
+ Unused << SendEarlyHint(aValue, aReferrerPolicy, aCSPHeader);
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/HttpTransactionChild.h b/netwerk/protocol/http/HttpTransactionChild.h
new file mode 100644
index 0000000000..7dcb4d4c63
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionChild.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpTransactionChild_h__
+#define HttpTransactionChild_h__
+
+#include "mozilla/Atomics.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/PHttpTransactionChild.h"
+#include "nsHttpRequestHead.h"
+#include "nsIEarlyHintObserver.h"
+#include "nsIRequest.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITransport.h"
+
+class nsInputStreamPump;
+
+namespace mozilla::net {
+
+class BackgroundDataBridgeParent;
+class InputChannelThrottleQueueChild;
+class nsHttpConnectionInfo;
+class nsHttpTransaction;
+class nsProxyInfo;
+
+//-----------------------------------------------------------------------------
+// HttpTransactionChild commutes between parent process and socket process,
+// manages the real nsHttpTransaction and transaction pump.
+//-----------------------------------------------------------------------------
+class HttpTransactionChild final : public PHttpTransactionChild,
+ public nsITransportEventSink,
+ public nsIThrottledInputChannel,
+ public nsIThreadRetargetableStreamListener,
+ public nsIEarlyHintObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSITHROTTLEDINPUTCHANNEL
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+ NS_DECL_NSIEARLYHINTOBSERVER
+
+ explicit HttpTransactionChild();
+
+ mozilla::ipc::IPCResult RecvInit(
+ const uint32_t& aCaps, const HttpConnectionInfoCloneArgs& aArgs,
+ const nsHttpRequestHead& aReqHeaders,
+ const Maybe<IPCStream>& aRequestBody, const uint64_t& aReqContentLength,
+ const bool& aReqBodyIncludesHeaders,
+ const uint64_t& aTopLevelOuterContentWindowId,
+ const uint8_t& aHttpTrafficCategory, const uint64_t& aRequestContextID,
+ const ClassOfService& aClassOfService, const uint32_t& aInitialRwin,
+ const bool& aResponseTimeoutEnabled, const uint64_t& aChannelId,
+ const bool& aHasTransactionObserver,
+ const Maybe<H2PushedStreamArg>& aPushedStreamArg,
+ const mozilla::Maybe<PInputChannelThrottleQueueChild*>& aThrottleQueue,
+ const bool& aIsDocumentLoad, const TimeStamp& aRedirectStart,
+ const TimeStamp& aRedirectEnd);
+ mozilla::ipc::IPCResult RecvCancelPump(const nsresult& aStatus);
+ mozilla::ipc::IPCResult RecvSuspendPump();
+ mozilla::ipc::IPCResult RecvResumePump();
+ mozilla::ipc::IPCResult RecvSetDNSWasRefreshed();
+ mozilla::ipc::IPCResult RecvDontReuseConnection();
+ mozilla::ipc::IPCResult RecvSetH2WSConnRefTaken();
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ nsHttpTransaction* GetHttpTransaction();
+
+ private:
+ virtual ~HttpTransactionChild();
+
+ nsProxyInfo* ProxyInfoCloneArgsToProxyInfo(
+ const nsTArray<ProxyInfoCloneArgs>& aArgs);
+ already_AddRefed<nsHttpConnectionInfo> DeserializeHttpConnectionInfoCloneArgs(
+ const HttpConnectionInfoCloneArgs& aInfoArgs);
+ // Initialize the *real* nsHttpTransaction. See |nsHttpTransaction::Init|
+ // for the parameters.
+ [[nodiscard]] nsresult InitInternal(
+ uint32_t caps, const HttpConnectionInfoCloneArgs& infoArgs,
+ nsHttpRequestHead* requestHead,
+ nsIInputStream* requestBody, // use the trick in bug 1277681
+ uint64_t requestContentLength, bool requestBodyHasHeaders,
+ uint64_t topLevelOuterContentWindowId, uint8_t httpTrafficCategory,
+ uint64_t requestContextID, ClassOfService classOfService,
+ uint32_t initialRwin, bool responseTimeoutEnabled, uint64_t channelId,
+ bool aHasTransactionObserver,
+ const Maybe<H2PushedStreamArg>& aPushedStreamArg);
+
+ void CancelInternal(nsresult aStatus);
+
+ bool CanSendODAToContentProcessDirectly(
+ const Maybe<nsHttpResponseHead>& aHead);
+
+ ResourceTimingStructArgs GetTimingAttributes();
+
+ // Use Release-Acquire ordering to ensure the OMT ODA is ignored while
+ // transaction is canceled on main thread.
+ Atomic<bool, ReleaseAcquire> mCanceled{false};
+ Atomic<nsresult, ReleaseAcquire> mStatus{NS_OK};
+ uint64_t mChannelId{0};
+ nsHttpRequestHead mRequestHead;
+ bool mIsDocumentLoad{false};
+ uint64_t mLogicalOffset{0};
+ TimeStamp mRedirectStart;
+ TimeStamp mRedirectEnd;
+ nsCString mProtocolVersion;
+
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ RefPtr<nsHttpTransaction> mTransaction;
+ nsCOMPtr<nsIRequest> mTransactionPump;
+ Maybe<TransactionObserverResult> mTransactionObserverResult;
+ RefPtr<InputChannelThrottleQueueChild> mThrottleQueue;
+ RefPtr<BackgroundDataBridgeParent> mDataBridgeParent;
+};
+
+} // namespace mozilla::net
+
+inline nsISupports* ToSupports(mozilla::net::HttpTransactionChild* p) {
+ return static_cast<nsIStreamListener*>(p);
+}
+
+#endif // nsHttpTransactionChild_h__
diff --git a/netwerk/protocol/http/HttpTransactionParent.cpp b/netwerk/protocol/http/HttpTransactionParent.cpp
new file mode 100644
index 0000000000..5408aee641
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionParent.cpp
@@ -0,0 +1,924 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HttpTransactionParent.h"
+
+#include "HttpTrafficAnalyzer.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "mozilla/net/InputChannelThrottleQueueParent.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "nsHttpHandler.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsSerializationHelper.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ADDREF(HttpTransactionParent)
+NS_INTERFACE_MAP_BEGIN(HttpTransactionParent)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpTransactionParent)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequest)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP_(MozExternalRefCountType) HttpTransactionParent::Release(void) {
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "HttpTransactionParent");
+ if (count == 0) {
+ mRefCnt = 1; /* stabilize */
+ delete (this);
+ return 0;
+ }
+
+ // When ref count goes down to 1 (held internally by IPDL), it means that
+ // we are done with this transaction. We should send a delete message
+ // to delete the transaction child in socket process.
+ if (count == 1 && CanSend()) {
+ if (!NS_IsMainThread()) {
+ RefPtr<HttpTransactionParent> self = this;
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
+ NS_NewRunnableFunction("HttpTransactionParent::Release", [self]() {
+ mozilla::Unused << self->Send__delete__(self);
+ // Make sure we can not send IPC after Send__delete__().
+ MOZ_ASSERT(!self->CanSend());
+ })));
+ } else {
+ mozilla::Unused << Send__delete__(this);
+ }
+ return 1;
+ }
+ return count;
+}
+
+//-----------------------------------------------------------------------------
+// HttpTransactionParent <public>
+//-----------------------------------------------------------------------------
+
+HttpTransactionParent::HttpTransactionParent(bool aIsDocumentLoad)
+ : mIsDocumentLoad(aIsDocumentLoad) {
+ LOG(("Creating HttpTransactionParent @%p\n", this));
+ mEventQ = new ChannelEventQueue(static_cast<nsIRequest*>(this));
+}
+
+HttpTransactionParent::~HttpTransactionParent() {
+ LOG(("Destroying HttpTransactionParent @%p\n", this));
+ mEventQ->NotifyReleasingOwner();
+}
+
+//-----------------------------------------------------------------------------
+// HttpTransactionParent <nsAHttpTransactionShell>
+//-----------------------------------------------------------------------------
+
+// Let socket process init the *real* nsHttpTransaction.
+nsresult HttpTransactionParent::Init(
+ uint32_t caps, nsHttpConnectionInfo* cinfo, nsHttpRequestHead* requestHead,
+ nsIInputStream* requestBody, uint64_t requestContentLength,
+ bool requestBodyHasHeaders, nsIEventTarget* target,
+ nsIInterfaceRequestor* callbacks, nsITransportEventSink* eventsink,
+ uint64_t browserId, HttpTrafficCategory trafficCategory,
+ nsIRequestContext* requestContext, ClassOfService classOfService,
+ uint32_t initialRwin, bool responseTimeoutEnabled, uint64_t channelId,
+ TransactionObserverFunc&& transactionObserver,
+ OnPushCallback&& aOnPushCallback,
+ HttpTransactionShell* aTransWithPushedStream, uint32_t aPushedStreamId) {
+ LOG(("HttpTransactionParent::Init [this=%p caps=%x]\n", this, caps));
+
+ if (!CanSend()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mEventsink = eventsink;
+ mTargetThread = GetCurrentSerialEventTarget();
+ mChannelId = channelId;
+ mTransactionObserver = std::move(transactionObserver);
+ mOnPushCallback = std::move(aOnPushCallback);
+ mCaps = caps;
+ mConnInfo = cinfo->Clone();
+ mIsHttp3Used = cinfo->IsHttp3();
+
+ HttpConnectionInfoCloneArgs infoArgs;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(cinfo, infoArgs);
+
+ Maybe<mozilla::ipc::IPCStream> ipcStream;
+ if (!mozilla::ipc::SerializeIPCStream(do_AddRef(requestBody), ipcStream,
+ /* aAllowLazy */ false)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint64_t requestContextID = requestContext ? requestContext->GetID() : 0;
+
+ Maybe<H2PushedStreamArg> pushedStreamArg;
+ if (aTransWithPushedStream && aPushedStreamId) {
+ MOZ_ASSERT(aTransWithPushedStream->AsHttpTransactionParent());
+ pushedStreamArg.emplace(
+ WrapNotNull(aTransWithPushedStream->AsHttpTransactionParent()),
+ aPushedStreamId);
+ }
+
+ nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(mEventsink);
+ Maybe<NotNull<PInputChannelThrottleQueueParent*>> throttleQueue;
+ if (throttled) {
+ nsCOMPtr<nsIInputChannelThrottleQueue> queue;
+ nsresult rv = throttled->GetThrottleQueue(getter_AddRefs(queue));
+ // In case of failure, just carry on without throttling.
+ if (NS_SUCCEEDED(rv) && queue) {
+ LOG1(("HttpTransactionParent::Init %p using throttle queue %p\n", this,
+ queue.get()));
+ RefPtr<InputChannelThrottleQueueParent> tqParent = do_QueryObject(queue);
+ MOZ_ASSERT(tqParent);
+ throttleQueue.emplace(WrapNotNull(tqParent.get()));
+ }
+ }
+
+ // TODO: Figure out if we have to implement nsIThreadRetargetableRequest in
+ // bug 1544378.
+ if (!SendInit(caps, infoArgs, *requestHead, ipcStream, requestContentLength,
+ requestBodyHasHeaders, browserId,
+ static_cast<uint8_t>(trafficCategory), requestContextID,
+ classOfService, initialRwin, responseTimeoutEnabled, mChannelId,
+ !!mTransactionObserver, pushedStreamArg, throttleQueue,
+ mIsDocumentLoad, mRedirectStart, mRedirectEnd)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString reqHeaderBuf = nsHttp::ConvertRequestHeadToString(
+ *requestHead, !!requestBody, requestBodyHasHeaders,
+ cinfo->UsingConnect());
+ requestContentLength += reqHeaderBuf.Length();
+
+ mRequestSize = InScriptableRange(requestContentLength)
+ ? static_cast<int64_t>(requestContentLength)
+ : -1;
+
+ return NS_OK;
+}
+
+nsresult HttpTransactionParent::AsyncRead(nsIStreamListener* listener,
+ nsIRequest** pump) {
+ MOZ_ASSERT(pump);
+
+ *pump = do_AddRef(this).take();
+ mChannel = listener;
+ return NS_OK;
+}
+
+UniquePtr<nsHttpResponseHead> HttpTransactionParent::TakeResponseHead() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x");
+
+ mResponseHeadTaken = true;
+ return std::move(mResponseHead);
+}
+
+UniquePtr<nsHttpHeaderArray> HttpTransactionParent::TakeResponseTrailers() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mResponseTrailersTaken, "TakeResponseTrailers called 2x");
+
+ mResponseTrailersTaken = true;
+ return std::move(mResponseTrailers);
+}
+
+void HttpTransactionParent::SetSniffedTypeToChannel(
+ nsInputStreamPump::PeekSegmentFun aCallTypeSniffers, nsIChannel* aChannel) {
+ if (!mDataForSniffer.IsEmpty()) {
+ aCallTypeSniffers(aChannel, mDataForSniffer.Elements(),
+ mDataForSniffer.Length());
+ }
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) {
+ MutexAutoLock lock(mEventTargetMutex);
+
+ nsCOMPtr<nsISerialEventTarget> target = mODATarget;
+ if (!mODATarget) {
+ target = mTargetThread;
+ }
+ target.forget(aEventTarget);
+ return NS_OK;
+}
+
+already_AddRefed<nsISerialEventTarget> HttpTransactionParent::GetODATarget() {
+ nsCOMPtr<nsISerialEventTarget> target;
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ target = mODATarget ? mODATarget : mTargetThread;
+ }
+
+ if (!target) {
+ target = GetMainThreadSerialEventTarget();
+ }
+ return target.forget();
+}
+
+NS_IMETHODIMP HttpTransactionParent::RetargetDeliveryTo(
+ nsISerialEventTarget* aEventTarget) {
+ LOG(("HttpTransactionParent::RetargetDeliveryTo [this=%p, aTarget=%p]", this,
+ aEventTarget));
+
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
+ MOZ_ASSERT(!mODATarget);
+ NS_ENSURE_ARG(aEventTarget);
+
+ if (aEventTarget->IsOnCurrentThread()) {
+ NS_WARNING("Retargeting delivery to same thread");
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mChannel, &rv);
+ if (!retargetableListener || NS_FAILED(rv)) {
+ NS_WARNING("Listener is not retargetable");
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ rv = retargetableListener->CheckListenerChain();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Subsequent listeners are not retargetable");
+ return rv;
+ }
+
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ mODATarget = aEventTarget;
+ }
+
+ return NS_OK;
+}
+
+void HttpTransactionParent::SetDNSWasRefreshed() {
+ MOZ_ASSERT(NS_IsMainThread(), "SetDNSWasRefreshed on main thread only!");
+ Unused << SendSetDNSWasRefreshed();
+}
+
+void HttpTransactionParent::GetNetworkAddresses(
+ NetAddr& self, NetAddr& peer, bool& aResolvedByTRR,
+ nsIRequest::TRRMode& aEffectiveTRRMode, TRRSkippedReason& aSkipReason,
+ bool& aEchConfigUsed) {
+ self = mSelfAddr;
+ peer = mPeerAddr;
+ aResolvedByTRR = mResolvedByTRR;
+ aEffectiveTRRMode = mEffectiveTRRMode;
+ aSkipReason = mTRRSkipReason;
+ aEchConfigUsed = mEchConfigUsed;
+}
+
+bool HttpTransactionParent::HasStickyConnection() const {
+ return mCaps & NS_HTTP_STICKY_CONNECTION;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetDomainLookupStart() {
+ return mTimings.domainLookupStart;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetDomainLookupEnd() {
+ return mTimings.domainLookupEnd;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetConnectStart() {
+ return mTimings.connectStart;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetTcpConnectEnd() {
+ return mTimings.tcpConnectEnd;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetSecureConnectionStart() {
+ return mTimings.secureConnectionStart;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetConnectEnd() {
+ return mTimings.connectEnd;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetRequestStart() {
+ return mTimings.requestStart;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetResponseStart() {
+ return mTimings.responseStart;
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetResponseEnd() {
+ return mTimings.responseEnd;
+}
+
+TimingStruct HttpTransactionParent::Timings() { return mTimings; }
+
+bool HttpTransactionParent::ResponseIsComplete() { return mResponseIsComplete; }
+
+int64_t HttpTransactionParent::GetTransferSize() { return mTransferSize; }
+
+int64_t HttpTransactionParent::GetRequestSize() { return mRequestSize; }
+
+bool HttpTransactionParent::IsHttp3Used() { return mIsHttp3Used; }
+
+bool HttpTransactionParent::DataSentToChildProcess() {
+ return mDataSentToChildProcess;
+}
+
+already_AddRefed<nsITransportSecurityInfo>
+HttpTransactionParent::SecurityInfo() {
+ return do_AddRef(mSecurityInfo);
+}
+
+bool HttpTransactionParent::ProxyConnectFailed() { return mProxyConnectFailed; }
+
+bool HttpTransactionParent::TakeRestartedState() {
+ bool result = mRestarted;
+ mRestarted = false;
+ return result;
+}
+
+uint32_t HttpTransactionParent::HTTPSSVCReceivedStage() {
+ return mHTTPSSVCReceivedStage;
+}
+
+void HttpTransactionParent::DontReuseConnection() {
+ MOZ_ASSERT(NS_IsMainThread());
+ Unused << SendDontReuseConnection();
+}
+
+void HttpTransactionParent::SetH2WSConnRefTaken() {
+ MOZ_ASSERT(NS_IsMainThread());
+ Unused << SendSetH2WSConnRefTaken();
+}
+
+void HttpTransactionParent::SetSecurityCallbacks(
+ nsIInterfaceRequestor* aCallbacks) {
+ // TODO: we might don't need to implement this.
+ // Will figure out in bug 1512479.
+}
+
+void HttpTransactionParent::SetDomainLookupStart(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mDomainLookupStart = timeStamp;
+ mTimings.domainLookupStart = mDomainLookupStart;
+}
+void HttpTransactionParent::SetDomainLookupEnd(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mDomainLookupEnd = timeStamp;
+ mTimings.domainLookupEnd = mDomainLookupEnd;
+}
+
+nsHttpTransaction* HttpTransactionParent::AsHttpTransaction() {
+ return nullptr;
+}
+
+HttpTransactionParent* HttpTransactionParent::AsHttpTransactionParent() {
+ return this;
+}
+
+int32_t HttpTransactionParent::GetProxyConnectResponseCode() {
+ return mProxyConnectResponseCode;
+}
+
+bool HttpTransactionParent::Http2Disabled() const {
+ return mCaps & NS_HTTP_DISALLOW_SPDY;
+}
+
+bool HttpTransactionParent::Http3Disabled() const {
+ return mCaps & NS_HTTP_DISALLOW_HTTP3;
+}
+
+already_AddRefed<nsHttpConnectionInfo> HttpTransactionParent::GetConnInfo()
+ const {
+ RefPtr<nsHttpConnectionInfo> connInfo = mConnInfo->Clone();
+ return connInfo.forget();
+}
+
+already_AddRefed<nsIEventTarget> HttpTransactionParent::GetNeckoTarget() {
+ nsCOMPtr<nsIEventTarget> target = GetMainThreadSerialEventTarget();
+ return target.forget();
+}
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvOnStartRequest(
+ const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
+ nsITransportSecurityInfo* aSecurityInfo, const bool& aProxyConnectFailed,
+ const TimingStructArgs& aTimings, const int32_t& aProxyConnectResponseCode,
+ nsTArray<uint8_t>&& aDataForSniffer, const Maybe<nsCString>& aAltSvcUsed,
+ const bool& aDataToChildProcess, const bool& aRestarted,
+ const uint32_t& aHTTPSSVCReceivedStage, const bool& aSupportsHttp3,
+ const nsIRequest::TRRMode& aMode, const TRRSkippedReason& aTrrSkipReason,
+ const uint32_t& aCaps, const TimeStamp& aOnStartRequestStartTime) {
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpTransactionParent>(this), aStatus,
+ aResponseHead, securityInfo = nsCOMPtr{aSecurityInfo},
+ aProxyConnectFailed, aTimings, aProxyConnectResponseCode,
+ aDataForSniffer = CopyableTArray{std::move(aDataForSniffer)},
+ aAltSvcUsed, aDataToChildProcess, aRestarted,
+ aHTTPSSVCReceivedStage, aSupportsHttp3, aMode, aTrrSkipReason,
+ aCaps, aOnStartRequestStartTime]() mutable {
+ self->DoOnStartRequest(
+ aStatus, aResponseHead, securityInfo, aProxyConnectFailed, aTimings,
+ aProxyConnectResponseCode, std::move(aDataForSniffer), aAltSvcUsed,
+ aDataToChildProcess, aRestarted, aHTTPSSVCReceivedStage,
+ aSupportsHttp3, aMode, aTrrSkipReason, aCaps,
+ aOnStartRequestStartTime);
+ }));
+ return IPC_OK();
+}
+
+static void TimingStructArgsToTimingsStruct(const TimingStructArgs& aArgs,
+ TimingStruct& aTimings) {
+ // If domainLookupStart/End was set by the channel before, we use these
+ // timestamps instead the ones from the transaction.
+ if (aTimings.domainLookupStart.IsNull() &&
+ aTimings.domainLookupEnd.IsNull()) {
+ aTimings.domainLookupStart = aArgs.domainLookupStart();
+ aTimings.domainLookupEnd = aArgs.domainLookupEnd();
+ }
+ aTimings.connectStart = aArgs.connectStart();
+ aTimings.tcpConnectEnd = aArgs.tcpConnectEnd();
+ aTimings.secureConnectionStart = aArgs.secureConnectionStart();
+ aTimings.connectEnd = aArgs.connectEnd();
+ aTimings.requestStart = aArgs.requestStart();
+ aTimings.responseStart = aArgs.responseStart();
+ aTimings.responseEnd = aArgs.responseEnd();
+ aTimings.transactionPending = aArgs.transactionPending();
+}
+
+void HttpTransactionParent::DoOnStartRequest(
+ const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
+ nsITransportSecurityInfo* aSecurityInfo, const bool& aProxyConnectFailed,
+ const TimingStructArgs& aTimings, const int32_t& aProxyConnectResponseCode,
+ nsTArray<uint8_t>&& aDataForSniffer, const Maybe<nsCString>& aAltSvcUsed,
+ const bool& aDataToChildProcess, const bool& aRestarted,
+ const uint32_t& aHTTPSSVCReceivedStage, const bool& aSupportsHttp3,
+ const nsIRequest::TRRMode& aMode, const TRRSkippedReason& aSkipReason,
+ const uint32_t& aCaps, const TimeStamp& aOnStartRequestStartTime) {
+ LOG(("HttpTransactionParent::DoOnStartRequest [this=%p aStatus=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(aStatus)));
+
+ if (mCanceled) {
+ return;
+ }
+
+ MOZ_ASSERT(!mOnStartRequestCalled);
+
+ mStatus = aStatus;
+ mDataSentToChildProcess = aDataToChildProcess;
+ mHTTPSSVCReceivedStage = aHTTPSSVCReceivedStage;
+ mSupportsHTTP3 = aSupportsHttp3;
+ mEffectiveTRRMode = aMode;
+ mTRRSkipReason = aSkipReason;
+ mCaps = aCaps;
+ mSecurityInfo = aSecurityInfo;
+ mOnStartRequestStartTime = aOnStartRequestStartTime;
+
+ if (aResponseHead.isSome()) {
+ mResponseHead = MakeUnique<nsHttpResponseHead>(aResponseHead.ref());
+ }
+ mProxyConnectFailed = aProxyConnectFailed;
+ TimingStructArgsToTimingsStruct(aTimings, mTimings);
+
+ mProxyConnectResponseCode = aProxyConnectResponseCode;
+ mDataForSniffer = std::move(aDataForSniffer);
+ mRestarted = aRestarted;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ MOZ_ASSERT(httpChannel, "mChannel is expected to implement nsIHttpChannel");
+ if (httpChannel) {
+ if (aAltSvcUsed.isSome()) {
+ Unused << httpChannel->SetRequestHeader(
+ nsHttp::Alternate_Service_Used.val(), aAltSvcUsed.ref(), false);
+ }
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ nsresult rv = mChannel->OnStartRequest(this);
+ mOnStartRequestCalled = true;
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+}
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvOnTransportStatus(
+ const nsresult& aStatus, const int64_t& aProgress,
+ const int64_t& aProgressMax,
+ Maybe<NetworkAddressArg>&& aNetworkAddressArg) {
+ if (aNetworkAddressArg) {
+ mSelfAddr = aNetworkAddressArg->selfAddr();
+ mPeerAddr = aNetworkAddressArg->peerAddr();
+ mResolvedByTRR = aNetworkAddressArg->resolvedByTRR();
+ mEffectiveTRRMode = aNetworkAddressArg->mode();
+ mTRRSkipReason = aNetworkAddressArg->trrSkipReason();
+ mEchConfigUsed = aNetworkAddressArg->echConfigUsed();
+ }
+ mEventsink->OnTransportStatus(nullptr, aStatus, aProgress, aProgressMax);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvOnDataAvailable(
+ const nsCString& aData, const uint64_t& aOffset, const uint32_t& aCount,
+ const TimeStamp& aOnDataAvailableStartTime) {
+ LOG(("HttpTransactionParent::RecvOnDataAvailable [this=%p, aOffset= %" PRIu64
+ " aCount=%" PRIu32,
+ this, aOffset, aCount));
+
+ // The final transfer size is updated in OnStopRequest ipc message, but in the
+ // case that the socket process is crashed or something went wrong, we might
+ // not get the OnStopRequest. So, let's update the transfer size here.
+ mTransferSize += aCount;
+
+ if (mCanceled) {
+ return IPC_OK();
+ }
+
+ mEventQ->RunOrEnqueue(new ChannelFunctionEvent(
+ [self = UnsafePtr<HttpTransactionParent>(this)]() {
+ return self->GetODATarget();
+ },
+ [self = UnsafePtr<HttpTransactionParent>(this), aData, aOffset, aCount,
+ aOnDataAvailableStartTime]() {
+ self->DoOnDataAvailable(aData, aOffset, aCount,
+ aOnDataAvailableStartTime);
+ }));
+ return IPC_OK();
+}
+
+void HttpTransactionParent::DoOnDataAvailable(
+ const nsCString& aData, const uint64_t& aOffset, const uint32_t& aCount,
+ const TimeStamp& aOnDataAvailableStartTime) {
+ LOG(("HttpTransactionParent::DoOnDataAvailable [this=%p]\n", this));
+ if (mCanceled) {
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv =
+ NS_NewByteInputStream(getter_AddRefs(stringStream),
+ Span(aData.get(), aCount), NS_ASSIGNMENT_DEPEND);
+
+ if (NS_FAILED(rv)) {
+ CancelOnMainThread(rv);
+ return;
+ }
+
+ mOnDataAvailableStartTime = aOnDataAvailableStartTime;
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ rv = mChannel->OnDataAvailable(this, stringStream, aOffset, aCount);
+ if (NS_FAILED(rv)) {
+ CancelOnMainThread(rv);
+ }
+}
+
+// Note: Copied from HttpChannelChild.
+void HttpTransactionParent::CancelOnMainThread(nsresult aRv) {
+ LOG(("HttpTransactionParent::CancelOnMainThread [this=%p]", this));
+
+ if (NS_IsMainThread()) {
+ Cancel(aRv);
+ return;
+ }
+
+ mEventQ->Suspend();
+ // Cancel is expected to preempt any other channel events, thus we put this
+ // event in the front of mEventQ to make sure nsIStreamListener not receiving
+ // any ODA/OnStopRequest callbacks.
+ mEventQ->PrependEvent(MakeUnique<NeckoTargetChannelFunctionEvent>(
+ this, [self = UnsafePtr<HttpTransactionParent>(this), aRv]() {
+ self->Cancel(aRv);
+ }));
+ mEventQ->Resume();
+}
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvOnStopRequest(
+ const nsresult& aStatus, const bool& aResponseIsComplete,
+ const int64_t& aTransferSize, const TimingStructArgs& aTimings,
+ const Maybe<nsHttpHeaderArray>& aResponseTrailers,
+ Maybe<TransactionObserverResult>&& aTransactionObserverResult,
+ const TimeStamp& aLastActiveTabOptHit,
+ const HttpConnectionInfoCloneArgs& aArgs,
+ const TimeStamp& aOnStopRequestStartTime) {
+ LOG(("HttpTransactionParent::RecvOnStopRequest [this=%p status=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(aStatus)));
+
+ nsHttp::SetLastActiveTabLoadOptimizationHit(aLastActiveTabOptHit);
+
+ if (mCanceled) {
+ return IPC_OK();
+ }
+ RefPtr<nsHttpConnectionInfo> cinfo =
+ nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(aArgs);
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpTransactionParent>(this), aStatus,
+ aResponseIsComplete, aTransferSize, aTimings, aResponseTrailers,
+ aTransactionObserverResult{std::move(aTransactionObserverResult)},
+ cinfo{std::move(cinfo)}, aOnStopRequestStartTime]() mutable {
+ self->DoOnStopRequest(aStatus, aResponseIsComplete, aTransferSize,
+ aTimings, aResponseTrailers,
+ std::move(aTransactionObserverResult), cinfo,
+ aOnStopRequestStartTime);
+ }));
+ return IPC_OK();
+}
+
+void HttpTransactionParent::DoOnStopRequest(
+ const nsresult& aStatus, const bool& aResponseIsComplete,
+ const int64_t& aTransferSize, const TimingStructArgs& aTimings,
+ const Maybe<nsHttpHeaderArray>& aResponseTrailers,
+ Maybe<TransactionObserverResult>&& aTransactionObserverResult,
+ nsHttpConnectionInfo* aConnInfo, const TimeStamp& aOnStopRequestStartTime) {
+ LOG(("HttpTransactionParent::DoOnStopRequest [this=%p]\n", this));
+ if (mCanceled) {
+ return;
+ }
+
+ MOZ_ASSERT(!mOnStopRequestCalled, "We should not call OnStopRequest twice");
+
+ mStatus = aStatus;
+
+ nsCOMPtr<nsIRequest> deathGrip = this;
+
+ mResponseIsComplete = aResponseIsComplete;
+ mTransferSize = aTransferSize;
+ mOnStopRequestStartTime = aOnStopRequestStartTime;
+
+ TimingStructArgsToTimingsStruct(aTimings, mTimings);
+
+ if (aResponseTrailers.isSome()) {
+ mResponseTrailers = MakeUnique<nsHttpHeaderArray>(aResponseTrailers.ref());
+ }
+ mConnInfo = aConnInfo;
+ if (aTransactionObserverResult.isSome()) {
+ TransactionObserverFunc obs = nullptr;
+ std::swap(obs, mTransactionObserver);
+ obs(std::move(*aTransactionObserverResult));
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ Unused << mChannel->OnStopRequest(this, mStatus);
+ mOnStopRequestCalled = true;
+}
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvOnInitFailed(
+ const nsresult& aStatus) {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mEventsink);
+ if (request) {
+ request->Cancel(aStatus);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvOnH2PushStream(
+ const uint32_t& aPushedStreamId, const nsCString& aResourceUrl,
+ const nsCString& aRequestString) {
+ MOZ_ASSERT(mOnPushCallback);
+
+ mOnPushCallback(aPushedStreamId, aResourceUrl, aRequestString, this);
+ return IPC_OK();
+} // namespace net
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvEarlyHint(
+ const nsCString& aValue, const nsACString& aReferrerPolicy,
+ const nsACString& aCSPHeader) {
+ LOG(
+ ("HttpTransactionParent::RecvEarlyHint header=%s aReferrerPolicy=%s "
+ "aCSPHeader=%s",
+ PromiseFlatCString(aValue).get(),
+ PromiseFlatCString(aReferrerPolicy).get(),
+ PromiseFlatCString(aCSPHeader).get()));
+ nsCOMPtr<nsIEarlyHintObserver> obs = do_QueryInterface(mChannel);
+ if (obs) {
+ Unused << obs->EarlyHint(aValue, aReferrerPolicy, aCSPHeader);
+ }
+
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// HttpTransactionParent <nsIRequest>
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpTransactionParent::GetName(nsACString& aResult) {
+ aResult.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::IsPending(bool* aRetval) {
+ *aRetval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::GetStatus(nsresult* aStatus) {
+ *aStatus = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpTransactionParent::SetCanceledReason(
+ const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP HttpTransactionParent::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP HttpTransactionParent::CancelWithReason(
+ nsresult aStatus, const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::Cancel(nsresult aStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("HttpTransactionParent::Cancel [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aStatus)));
+
+ if (mCanceled) {
+ LOG((" already canceled\n"));
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(NS_FAILED(aStatus), "cancel with non-failure status code");
+
+ mCanceled = true;
+ mStatus = aStatus;
+ if (CanSend()) {
+ Unused << SendCancelPump(mStatus);
+ }
+
+ // Put DoNotifyListener() in front of the queue to avoid OnDataAvailable
+ // being called after cancellation. Note that
+ // HttpTransactionParent::OnStart/StopRequest are driven by IPC messages and
+ // HttpTransactionChild won't send IPC if already canceled. That's why we have
+ // to call DoNotifyListener().
+ mEventQ->Suspend();
+ mEventQ->PrependEvent(MakeUnique<NeckoTargetChannelFunctionEvent>(
+ this, [self = UnsafePtr<HttpTransactionParent>(this)]() {
+ self->DoNotifyListener();
+ }));
+ mEventQ->Resume();
+ return NS_OK;
+}
+
+void HttpTransactionParent::DoNotifyListener() {
+ LOG(("HttpTransactionParent::DoNotifyListener this=%p", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mChannel && !mOnStartRequestCalled) {
+ nsCOMPtr<nsIStreamListener> listener = mChannel;
+ mOnStartRequestCalled = true;
+ listener->OnStartRequest(this);
+ }
+ mOnStartRequestCalled = true;
+
+ // This is to make sure that ODA in the event queue can be processed before
+ // OnStopRequest.
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpTransactionParent>(this)] {
+ self->ContinueDoNotifyListener();
+ }));
+}
+
+void HttpTransactionParent::ContinueDoNotifyListener() {
+ LOG(("HttpTransactionParent::ContinueDoNotifyListener this=%p", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mChannel && !mOnStopRequestCalled) {
+ nsCOMPtr<nsIStreamListener> listener = mChannel;
+ mOnStopRequestCalled = true; // avoid reentrancy bugs by setting this now
+ listener->OnStopRequest(this, mStatus);
+ }
+ mOnStopRequestCalled = true;
+
+ mChannel = nullptr;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::Suspend() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // SendSuspend only once, when suspend goes from 0 to 1.
+ if (!mSuspendCount++ && CanSend()) {
+ Unused << SendSuspendPump();
+ }
+ mEventQ->Suspend();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::Resume() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mSuspendCount, "Resume called more than Suspend");
+
+ // SendResume only once, when suspend count drops to 0.
+ if (mSuspendCount && !--mSuspendCount) {
+ if (CanSend()) {
+ Unused << SendResumePump();
+ }
+
+ if (mCallOnResume) {
+ nsCOMPtr<nsIEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ RefPtr<HttpTransactionParent> self = this;
+ std::function<void()> callOnResume = nullptr;
+ std::swap(callOnResume, mCallOnResume);
+ neckoTarget->Dispatch(
+ NS_NewRunnableFunction("net::HttpTransactionParent::mCallOnResume",
+ [callOnResume]() { callOnResume(); }),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+ mEventQ->Resume();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ MOZ_ASSERT(false, "Should not be called.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ MOZ_ASSERT(false, "Should not be called.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ MOZ_ASSERT(false, "Should not be called.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ MOZ_ASSERT(false, "Should not be called.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ MOZ_ASSERT(false, "Should not be called.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpTransactionParent::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ MOZ_ASSERT(false, "Should not be called.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void HttpTransactionParent::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("HttpTransactionParent::ActorDestroy [this=%p]\n", this));
+ if (aWhy != Deletion) {
+ // Make sure all the messages are processed.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ mStatus = NS_ERROR_FAILURE;
+ HandleAsyncAbort();
+
+ mCanceled = true;
+ }
+}
+
+void HttpTransactionParent::HandleAsyncAbort() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(
+ ("HttpTransactionParent Waiting until resume to do async notification "
+ "[this=%p]\n",
+ this));
+ RefPtr<HttpTransactionParent> self = this;
+ mCallOnResume = [self]() { self->HandleAsyncAbort(); };
+ return;
+ }
+
+ DoNotifyListener();
+}
+
+bool HttpTransactionParent::GetSupportsHTTP3() { return mSupportsHTTP3; }
+
+void HttpTransactionParent::SetIsForWebTransport(bool SetIsForWebTransport) {
+ // TODO: bug 1791727
+}
+
+mozilla::TimeStamp HttpTransactionParent::GetPendingTime() {
+ return mTimings.transactionPending;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/HttpTransactionParent.h b/netwerk/protocol/http/HttpTransactionParent.h
new file mode 100644
index 0000000000..e6e74ebf97
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionParent.h
@@ -0,0 +1,197 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpTransactionParent_h__
+#define HttpTransactionParent_h__
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/net/HttpTransactionShell.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/PHttpTransactionParent.h"
+#include "nsCOMPtr.h"
+#include "nsHttp.h"
+#include "nsIRequest.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsITransport.h"
+#include "nsITransportSecurityInfo.h"
+
+namespace mozilla::net {
+
+class ChannelEventQueue;
+class nsHttpConnectionInfo;
+
+#define HTTP_TRANSACTION_PARENT_IID \
+ { \
+ 0xb83695cb, 0xc24b, 0x4c53, { \
+ 0x85, 0x9b, 0x77, 0x77, 0x3e, 0xc5, 0x44, 0xe5 \
+ } \
+ }
+
+// HttpTransactionParent plays the role of nsHttpTransaction and delegates the
+// work to the nsHttpTransaction in socket process.
+class HttpTransactionParent final : public PHttpTransactionParent,
+ public HttpTransactionShell,
+ public nsIRequest,
+ public nsIThreadRetargetableRequest {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_HTTPTRANSACTIONSHELL
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_TRANSACTION_PARENT_IID)
+
+ explicit HttpTransactionParent(bool aIsDocumentLoad);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvOnStartRequest(
+ const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
+ nsITransportSecurityInfo* aSecurityInfo, const bool& aProxyConnectFailed,
+ const TimingStructArgs& aTimings,
+ const int32_t& aProxyConnectResponseCode,
+ nsTArray<uint8_t>&& aDataForSniffer, const Maybe<nsCString>& aAltSvcUsed,
+ const bool& aDataToChildProcess, const bool& aRestarted,
+ const uint32_t& aHTTPSSVCReceivedStage, const bool& aSupportsHttp3,
+ const nsIRequest::TRRMode& aMode, const TRRSkippedReason& aSkipReason,
+ const uint32_t& aCaps, const TimeStamp& aOnStartRequestStartTime);
+ mozilla::ipc::IPCResult RecvOnTransportStatus(
+ const nsresult& aStatus, const int64_t& aProgress,
+ const int64_t& aProgressMax,
+ Maybe<NetworkAddressArg>&& aNetworkAddressArg);
+ mozilla::ipc::IPCResult RecvOnDataAvailable(
+ const nsCString& aData, const uint64_t& aOffset, const uint32_t& aCount,
+ const TimeStamp& aOnDataAvailableStartTime);
+ mozilla::ipc::IPCResult RecvOnStopRequest(
+ const nsresult& aStatus, const bool& aResponseIsComplete,
+ const int64_t& aTransferSize, const TimingStructArgs& aTimings,
+ const Maybe<nsHttpHeaderArray>& responseTrailers,
+ Maybe<TransactionObserverResult>&& aTransactionObserverResult,
+ const TimeStamp& aLastActiveTabOptHit,
+ const HttpConnectionInfoCloneArgs& aArgs,
+ const TimeStamp& aOnStopRequestStartTime);
+ mozilla::ipc::IPCResult RecvOnInitFailed(const nsresult& aStatus);
+
+ mozilla::ipc::IPCResult RecvOnH2PushStream(const uint32_t& aPushedStreamId,
+ const nsCString& aResourceUrl,
+ const nsCString& aRequestString);
+ mozilla::ipc::IPCResult RecvEarlyHint(const nsCString& aValue,
+ const nsACString& aReferrerPolicy,
+ const nsACString& aCSPHeader);
+
+ virtual mozilla::TimeStamp GetPendingTime() override;
+
+ already_AddRefed<nsIEventTarget> GetNeckoTarget();
+
+ void SetSniffedTypeToChannel(
+ nsInputStreamPump::PeekSegmentFun aCallTypeSniffers,
+ nsIChannel* aChannel);
+
+ void SetRedirectTimestamp(TimeStamp aRedirectStart, TimeStamp aRedirectEnd) {
+ mRedirectStart = aRedirectStart;
+ mRedirectEnd = aRedirectEnd;
+ }
+
+ virtual TimeStamp GetOnStartRequestStartTime() const override {
+ return mOnStartRequestStartTime;
+ }
+ virtual TimeStamp GetDataAvailableStartTime() const override {
+ return mOnDataAvailableStartTime;
+ }
+ virtual TimeStamp GetOnStopRequestStartTime() const override {
+ return mOnStopRequestStartTime;
+ }
+
+ private:
+ virtual ~HttpTransactionParent();
+
+ void GetStructFromInfo(nsHttpConnectionInfo* aInfo,
+ HttpConnectionInfoCloneArgs& aArgs);
+ void DoOnStartRequest(
+ const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
+ nsITransportSecurityInfo* aSecurityInfo, const bool& aProxyConnectFailed,
+ const TimingStructArgs& aTimings,
+ const int32_t& aProxyConnectResponseCode,
+ nsTArray<uint8_t>&& aDataForSniffer, const Maybe<nsCString>& aAltSvcUsed,
+ const bool& aDataToChildProcess, const bool& aRestarted,
+ const uint32_t& aHTTPSSVCReceivedStage, const bool& aSupportsHttp3,
+ const nsIRequest::TRRMode& aMode, const TRRSkippedReason& aSkipReason,
+ const uint32_t& aCaps, const TimeStamp& aOnStartRequestStartTime);
+ void DoOnDataAvailable(const nsCString& aData, const uint64_t& aOffset,
+ const uint32_t& aCount,
+ const TimeStamp& aOnDataAvailableStartTime);
+ void DoOnStopRequest(
+ const nsresult& aStatus, const bool& aResponseIsComplete,
+ const int64_t& aTransferSize, const TimingStructArgs& aTimings,
+ const Maybe<nsHttpHeaderArray>& responseTrailers,
+ Maybe<TransactionObserverResult>&& aTransactionObserverResult,
+ nsHttpConnectionInfo* aConnInfo,
+ const TimeStamp& aOnStopRequestStartTime);
+ void DoNotifyListener();
+ void ContinueDoNotifyListener();
+ // Get event target for ODA.
+ already_AddRefed<nsISerialEventTarget> GetODATarget();
+ void CancelOnMainThread(nsresult aRv);
+ void HandleAsyncAbort();
+
+ nsCOMPtr<nsITransportEventSink> mEventsink;
+ nsCOMPtr<nsIStreamListener> mChannel;
+ nsCOMPtr<nsISerialEventTarget> mTargetThread;
+ nsCOMPtr<nsISerialEventTarget> mODATarget;
+ Mutex mEventTargetMutex MOZ_UNANNOTATED{
+ "HttpTransactionParent::EventTargetMutex"};
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+ UniquePtr<nsHttpResponseHead> mResponseHead;
+ UniquePtr<nsHttpHeaderArray> mResponseTrailers;
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ bool mResponseIsComplete{false};
+ int64_t mTransferSize{0};
+ int64_t mRequestSize{0};
+ bool mIsHttp3Used = false;
+ bool mProxyConnectFailed{false};
+ Atomic<bool, ReleaseAcquire> mCanceled{false};
+ Atomic<nsresult, ReleaseAcquire> mStatus{NS_OK};
+ int32_t mSuspendCount{0};
+ bool mResponseHeadTaken{false};
+ bool mResponseTrailersTaken{false};
+ bool mOnStartRequestCalled{false};
+ bool mOnStopRequestCalled{false};
+ bool mResolvedByTRR{false};
+ nsIRequest::TRRMode mEffectiveTRRMode{nsIRequest::TRR_DEFAULT_MODE};
+ TRRSkippedReason mTRRSkipReason{nsITRRSkipReason::TRR_UNSET};
+ bool mEchConfigUsed = false;
+ int32_t mProxyConnectResponseCode{0};
+ uint64_t mChannelId{0};
+ bool mDataSentToChildProcess{false};
+ bool mIsDocumentLoad;
+ bool mRestarted{false};
+ Atomic<uint32_t, ReleaseAcquire> mCaps{0};
+ TimeStamp mRedirectStart;
+ TimeStamp mRedirectEnd;
+
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+ TimingStruct mTimings;
+ TimeStamp mDomainLookupStart;
+ TimeStamp mDomainLookupEnd;
+ TimeStamp mOnStartRequestStartTime;
+ TimeStamp mOnDataAvailableStartTime;
+ TimeStamp mOnStopRequestStartTime;
+ TransactionObserverFunc mTransactionObserver;
+ OnPushCallback mOnPushCallback;
+ nsTArray<uint8_t> mDataForSniffer;
+ std::function<void()> mCallOnResume;
+ uint32_t mHTTPSSVCReceivedStage{};
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ bool mSupportsHTTP3 = false;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpTransactionParent,
+ HTTP_TRANSACTION_PARENT_IID)
+
+} // namespace mozilla::net
+
+#endif // nsHttpTransactionParent_h__
diff --git a/netwerk/protocol/http/HttpTransactionShell.h b/netwerk/protocol/http/HttpTransactionShell.h
new file mode 100644
index 0000000000..495ab19a1b
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionShell.h
@@ -0,0 +1,242 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpTransactionShell_h__
+#define HttpTransactionShell_h__
+
+#include <functional>
+
+#include "TimingStruct.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIClassOfService.h"
+#include "nsIEarlyHintObserver.h"
+#include "nsISupports.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsInputStreamPump.h"
+
+class nsIEventTraget;
+class nsIInputStream;
+class nsIInterfaceRequestor;
+class nsIRequest;
+class nsIRequestContext;
+class nsITransportEventSink;
+
+namespace mozilla::net {
+
+enum HttpTrafficCategory : uint8_t;
+class Http2PushedStreamWrapper;
+class HttpTransactionParent;
+class nsHttpConnectionInfo;
+class nsHttpHeaderArray;
+class nsHttpRequestHead;
+class nsHttpTransaction;
+class TransactionObserverResult;
+union NetAddr;
+
+//----------------------------------------------------------------------------
+// Abstract base class for a HTTP transaction in the chrome process
+//----------------------------------------------------------------------------
+
+// 95e5a5b7-6aa2-4011-920a-0908b52f95d4
+#define HTTPTRANSACTIONSHELL_IID \
+ { \
+ 0x95e5a5b7, 0x6aa2, 0x4011, { \
+ 0x92, 0x0a, 0x09, 0x08, 0xb5, 0x2f, 0x95, 0xd4 \
+ } \
+ }
+
+class HttpTransactionShell : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTPTRANSACTIONSHELL_IID)
+
+ using TransactionObserverFunc =
+ std::function<void(TransactionObserverResult&&)>;
+ using OnPushCallback = std::function<nsresult(
+ uint32_t, const nsACString&, const nsACString&, HttpTransactionShell*)>;
+
+ //
+ // called to initialize the transaction
+ //
+ // @param caps
+ // the transaction capabilities (see nsHttp.h)
+ // @param connInfo
+ // the connection type for this transaction.
+ // @param reqHeaders
+ // the request header struct
+ // @param reqBody
+ // the request body (POST or PUT data stream)
+ // @param reqBodyIncludesHeaders
+ // fun stuff to support NPAPI plugins.
+ // @param target
+ // the dispatch target were notifications should be sent.
+ // @param callbacks
+ // the notification callbacks to be given to PSM.
+ // @param browserId
+ // indicate the id of the browser in which this transaction is being
+ // loaded.
+ [[nodiscard]] nsresult virtual Init(
+ uint32_t caps, nsHttpConnectionInfo* connInfo,
+ nsHttpRequestHead* reqHeaders, nsIInputStream* reqBody,
+ uint64_t reqContentLength, bool reqBodyIncludesHeaders,
+ nsIEventTarget* consumerTarget, nsIInterfaceRequestor* callbacks,
+ nsITransportEventSink* eventsink, uint64_t browserId,
+ HttpTrafficCategory trafficCategory, nsIRequestContext* requestContext,
+ ClassOfService classOfService, uint32_t initialRwin,
+ bool responseTimeoutEnabled, uint64_t channelId,
+ TransactionObserverFunc&& transactionObserver,
+ OnPushCallback&& aOnPushCallback,
+ HttpTransactionShell* aTransWithPushedStream,
+ uint32_t aPushedStreamId) = 0;
+
+ // @param aListener
+ // receives notifications.
+ // @param pump
+ // the pump that will contain the response data. async wait on this
+ // input stream for data. On first notification, headers should be
+ // available (check transaction status).
+ virtual nsresult AsyncRead(nsIStreamListener* listener,
+ nsIRequest** pump) = 0;
+
+ // Called to take ownership of the response headers; the transaction
+ // will drop any reference to the response headers after this call.
+ virtual UniquePtr<nsHttpResponseHead> TakeResponseHead() = 0;
+
+ // Called to take ownership of the trailer headers.
+ // Returning null if there is no trailer.
+ virtual UniquePtr<nsHttpHeaderArray> TakeResponseTrailers() = 0;
+
+ virtual already_AddRefed<nsITransportSecurityInfo> SecurityInfo() = 0;
+ virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) = 0;
+
+ virtual void GetNetworkAddresses(NetAddr& self, NetAddr& peer,
+ bool& aResolvedByTRR,
+ nsIRequest::TRRMode& aEffectiveTRRMode,
+ TRRSkippedReason& aSkipReason,
+ bool& aEchConfigUsed) = 0;
+
+ // Functions for Timing interface
+ virtual mozilla::TimeStamp GetDomainLookupStart() = 0;
+ virtual mozilla::TimeStamp GetDomainLookupEnd() = 0;
+ virtual mozilla::TimeStamp GetConnectStart() = 0;
+ virtual mozilla::TimeStamp GetTcpConnectEnd() = 0;
+ virtual mozilla::TimeStamp GetSecureConnectionStart() = 0;
+
+ virtual mozilla::TimeStamp GetConnectEnd() = 0;
+ virtual mozilla::TimeStamp GetRequestStart() = 0;
+ virtual mozilla::TimeStamp GetResponseStart() = 0;
+ virtual mozilla::TimeStamp GetResponseEnd() = 0;
+
+ virtual void SetDomainLookupStart(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull = false) = 0;
+ virtual void SetDomainLookupEnd(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull = false) = 0;
+
+ virtual TimingStruct Timings() = 0;
+
+ virtual mozilla::TimeStamp GetPendingTime() = 0;
+
+ // Called to set/find out if the transaction generated a complete response.
+ virtual bool ResponseIsComplete() = 0;
+ virtual int64_t GetTransferSize() = 0;
+ virtual int64_t GetRequestSize() = 0;
+ virtual bool IsHttp3Used() = 0;
+
+ // Called to notify that a requested DNS cache entry was refreshed.
+ virtual void SetDNSWasRefreshed() = 0;
+
+ virtual void DontReuseConnection() = 0;
+ virtual bool HasStickyConnection() const = 0;
+
+ virtual void SetH2WSConnRefTaken() = 0;
+
+ virtual bool ProxyConnectFailed() = 0;
+ virtual int32_t GetProxyConnectResponseCode() = 0;
+
+ virtual bool DataSentToChildProcess() = 0;
+
+ virtual nsHttpTransaction* AsHttpTransaction() = 0;
+ virtual HttpTransactionParent* AsHttpTransactionParent() = 0;
+
+ virtual bool TakeRestartedState() = 0;
+ virtual uint32_t HTTPSSVCReceivedStage() = 0;
+
+ virtual bool Http2Disabled() const = 0;
+ virtual bool Http3Disabled() const = 0;
+ virtual already_AddRefed<nsHttpConnectionInfo> GetConnInfo() const = 0;
+
+ virtual bool GetSupportsHTTP3() = 0;
+
+ virtual void SetIsForWebTransport(bool aIsForWebTransport) = 0;
+
+ virtual TimeStamp GetOnStartRequestStartTime() const { return TimeStamp(); }
+ virtual TimeStamp GetDataAvailableStartTime() const { return TimeStamp(); }
+ virtual TimeStamp GetOnStopRequestStartTime() const { return TimeStamp(); }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpTransactionShell, HTTPTRANSACTIONSHELL_IID)
+
+#define NS_DECL_HTTPTRANSACTIONSHELL \
+ virtual nsresult Init( \
+ uint32_t caps, nsHttpConnectionInfo* connInfo, \
+ nsHttpRequestHead* reqHeaders, nsIInputStream* reqBody, \
+ uint64_t reqContentLength, bool reqBodyIncludesHeaders, \
+ nsIEventTarget* consumerTarget, nsIInterfaceRequestor* callbacks, \
+ nsITransportEventSink* eventsink, uint64_t browserId, \
+ HttpTrafficCategory trafficCategory, nsIRequestContext* requestContext, \
+ ClassOfService classOfService, uint32_t initialRwin, \
+ bool responseTimeoutEnabled, uint64_t channelId, \
+ TransactionObserverFunc&& transactionObserver, \
+ OnPushCallback&& aOnPushCallback, \
+ HttpTransactionShell* aTransWithPushedStream, uint32_t aPushedStreamId) \
+ override; \
+ virtual nsresult AsyncRead(nsIStreamListener* listener, nsIRequest** pump) \
+ override; \
+ virtual UniquePtr<nsHttpResponseHead> TakeResponseHead() override; \
+ virtual UniquePtr<nsHttpHeaderArray> TakeResponseTrailers() override; \
+ virtual already_AddRefed<nsITransportSecurityInfo> SecurityInfo() override; \
+ virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) \
+ override; \
+ virtual void GetNetworkAddresses( \
+ NetAddr& self, NetAddr& peer, bool& aResolvedByTRR, \
+ nsIRequest::TRRMode& aEffectiveTRRMode, TRRSkippedReason& aSkipReason, \
+ bool& aEchConfigUsed) override; \
+ virtual mozilla::TimeStamp GetDomainLookupStart() override; \
+ virtual mozilla::TimeStamp GetDomainLookupEnd() override; \
+ virtual mozilla::TimeStamp GetConnectStart() override; \
+ virtual mozilla::TimeStamp GetTcpConnectEnd() override; \
+ virtual mozilla::TimeStamp GetSecureConnectionStart() override; \
+ virtual mozilla::TimeStamp GetConnectEnd() override; \
+ virtual mozilla::TimeStamp GetRequestStart() override; \
+ virtual mozilla::TimeStamp GetResponseStart() override; \
+ virtual mozilla::TimeStamp GetResponseEnd() override; \
+ virtual void SetDomainLookupStart(mozilla::TimeStamp timeStamp, \
+ bool onlyIfNull = false) override; \
+ virtual void SetDomainLookupEnd(mozilla::TimeStamp timeStamp, \
+ bool onlyIfNull = false) override; \
+ virtual TimingStruct Timings() override; \
+ virtual bool ResponseIsComplete() override; \
+ virtual int64_t GetTransferSize() override; \
+ virtual int64_t GetRequestSize() override; \
+ virtual bool IsHttp3Used() override; \
+ virtual void SetDNSWasRefreshed() override; \
+ virtual void DontReuseConnection() override; \
+ virtual bool HasStickyConnection() const override; \
+ virtual void SetH2WSConnRefTaken() override; \
+ virtual bool ProxyConnectFailed() override; \
+ virtual int32_t GetProxyConnectResponseCode() override; \
+ virtual bool DataSentToChildProcess() override; \
+ virtual nsHttpTransaction* AsHttpTransaction() override; \
+ virtual HttpTransactionParent* AsHttpTransactionParent() override; \
+ virtual bool TakeRestartedState() override; \
+ virtual uint32_t HTTPSSVCReceivedStage() override; \
+ virtual bool Http2Disabled() const override; \
+ virtual bool Http3Disabled() const override; \
+ virtual already_AddRefed<nsHttpConnectionInfo> GetConnInfo() const override; \
+ virtual bool GetSupportsHTTP3() override; \
+ virtual void SetIsForWebTransport(bool aIsForWebTransport) override;
+
+} // namespace mozilla::net
+
+#endif // HttpTransactionShell_h__
diff --git a/netwerk/protocol/http/HttpWinUtils.cpp b/netwerk/protocol/http/HttpWinUtils.cpp
new file mode 100644
index 0000000000..b566cdc4f6
--- /dev/null
+++ b/netwerk/protocol/http/HttpWinUtils.cpp
@@ -0,0 +1,111 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HttpWinUtils.h"
+#include "nsIURI.h"
+#include "nsHttpChannel.h"
+#include "mozilla/ClearOnShutdown.h"
+#include <proofofpossessioncookieinfo.h>
+
+namespace mozilla {
+namespace net {
+
+static StaticRefPtr<IProofOfPossessionCookieInfoManager> sPopCookieManager;
+static bool sPopCookieManagerAvailable = true;
+
+void AddWindowsSSO(nsHttpChannel* channel) {
+ if (!sPopCookieManagerAvailable) {
+ return;
+ }
+ HRESULT hr;
+ if (!sPopCookieManager) {
+ GUID CLSID_ProofOfPossessionCookieInfoManager;
+ GUID IID_IProofOfPossessionCookieInfoManager;
+
+ CLSIDFromString(L"{A9927F85-A304-4390-8B23-A75F1C668600}",
+ &CLSID_ProofOfPossessionCookieInfoManager);
+ IIDFromString(L"{CDAECE56-4EDF-43DF-B113-88E4556FA1BB}",
+ &IID_IProofOfPossessionCookieInfoManager);
+
+ hr = CoCreateInstance(CLSID_ProofOfPossessionCookieInfoManager, NULL,
+ CLSCTX_INPROC_SERVER,
+ IID_IProofOfPossessionCookieInfoManager,
+ reinterpret_cast<void**>(&sPopCookieManager));
+ if (FAILED(hr)) {
+ sPopCookieManagerAvailable = false;
+ return;
+ }
+
+ RunOnShutdown([&] {
+ if (sPopCookieManager) {
+ sPopCookieManager = nullptr;
+ }
+ });
+ }
+
+ DWORD cookieCount = 0;
+ ProofOfPossessionCookieInfo* cookieInfo = nullptr;
+
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+
+ nsAutoCString urispec;
+ uri->GetSpec(urispec);
+
+ hr = sPopCookieManager->GetCookieInfoForUri(
+ NS_ConvertUTF8toUTF16(urispec).get(), &cookieCount, &cookieInfo);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ nsAutoCString host;
+ uri->GetHost(host);
+ bool addCookies = false;
+ if (StringEndsWith(host, ".live.com"_ns)) {
+ addCookies = true;
+ }
+
+ nsAutoString allCookies;
+
+ for (DWORD i = 0; i < cookieCount; i++) {
+ nsAutoString cookieData;
+ cookieData.Assign(cookieInfo[i].data);
+ // Strip old Set-Cookie info for WinInet
+ int32_t semicolon = cookieData.FindChar(';');
+ if (semicolon >= 0) {
+ cookieData.SetLength(semicolon);
+ }
+ if (StringBeginsWith(nsDependentString(cookieInfo[i].name), u"x-ms-"_ns)) {
+ channel->SetRequestHeader(NS_ConvertUTF16toUTF8(cookieInfo[i].name),
+ NS_ConvertUTF16toUTF8(cookieData),
+ true /* merge */);
+ } else if (addCookies) {
+ if (!allCookies.IsEmpty()) {
+ allCookies.AppendLiteral("; ");
+ }
+ allCookies.Append(cookieInfo[i].name);
+ allCookies.AppendLiteral("=");
+ allCookies.Append(cookieData);
+ }
+ }
+
+ // Merging cookie headers doesn't work correctly as it separates the new
+ // cookies using commas instead of semicolons, so we have to replace
+ // the entire header.
+ if (!allCookies.IsEmpty()) {
+ nsAutoCString cookieHeader;
+ channel->GetRequestHeader(nsHttp::Cookie.val(), cookieHeader);
+ if (!cookieHeader.IsEmpty()) {
+ cookieHeader.AppendLiteral("; ");
+ }
+ cookieHeader.Append(NS_ConvertUTF16toUTF8(allCookies));
+ channel->SetRequestHeader(nsHttp::Cookie.val(), cookieHeader, false);
+ }
+ if (cookieInfo) {
+ FreeProofOfPossessionCookieInfoArray(cookieInfo, cookieCount);
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpWinUtils.h b/netwerk/protocol/http/HttpWinUtils.h
new file mode 100644
index 0000000000..7d78f7188b
--- /dev/null
+++ b/netwerk/protocol/http/HttpWinUtils.h
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpWinUtils_h__
+#define HttpWinUtils_h__
+
+namespace mozilla {
+namespace net {
+
+class nsHttpChannel;
+
+void AddWindowsSSO(nsHttpChannel* channel);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // HttpWinUtils_h__
diff --git a/netwerk/protocol/http/InterceptedHttpChannel.cpp b/netwerk/protocol/http/InterceptedHttpChannel.cpp
new file mode 100644
index 0000000000..c58fbc9639
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp
@@ -0,0 +1,1714 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "InterceptedHttpChannel.h"
+#include "NetworkMarker.h"
+#include "nsContentSecurityManager.h"
+#include "nsEscape.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/ChannelInfo.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "nsHttpChannel.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIRedirectResultListener.h"
+#include "nsStringStream.h"
+#include "nsStreamUtils.h"
+#include "nsQueryObject.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla::net {
+
+mozilla::LazyLogModule gInterceptedLog("Intercepted");
+
+#define INTERCEPTED_LOG(args) MOZ_LOG(gInterceptedLog, LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS_INHERITED(InterceptedHttpChannel, HttpBaseChannel,
+ nsIInterceptedChannel, nsICacheInfoChannel,
+ nsIAsyncVerifyRedirectCallback, nsIRequestObserver,
+ nsIStreamListener, nsIThreadRetargetableRequest,
+ nsIThreadRetargetableStreamListener,
+ nsIClassOfService)
+
+InterceptedHttpChannel::InterceptedHttpChannel(
+ PRTime aCreationTime, const TimeStamp& aCreationTimestamp,
+ const TimeStamp& aAsyncOpenTimestamp)
+ : HttpAsyncAborter<InterceptedHttpChannel>(this),
+ mProgress(0),
+ mProgressReported(0),
+ mSynthesizedStreamLength(-1),
+ mResumeStartPos(0),
+ mCallingStatusAndProgress(false) {
+ // Pre-set the creation and AsyncOpen times based on the original channel
+ // we are intercepting. We don't want our extra internal redirect to mask
+ // any time spent processing the channel.
+ INTERCEPTED_LOG(("Creating InterceptedHttpChannel [%p]", this));
+ mChannelCreationTime = aCreationTime;
+ mChannelCreationTimestamp = aCreationTimestamp;
+ mInterceptedChannelCreationTimestamp = TimeStamp::Now();
+ mAsyncOpenTime = aAsyncOpenTimestamp;
+}
+
+void InterceptedHttpChannel::ReleaseListeners() {
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ }
+ HttpBaseChannel::ReleaseListeners();
+ mSynthesizedResponseHead.reset();
+ mRedirectChannel = nullptr;
+ mBodyReader = nullptr;
+ mReleaseHandle = nullptr;
+ mProgressSink = nullptr;
+ mBodyCallback = nullptr;
+ mPump = nullptr;
+
+ MOZ_DIAGNOSTIC_ASSERT(!LoadIsPending());
+}
+
+nsresult InterceptedHttpChannel::SetupReplacementChannel(
+ nsIURI* aURI, nsIChannel* aChannel, bool aPreserveMethod,
+ uint32_t aRedirectFlags) {
+ INTERCEPTED_LOG(
+ ("InterceptedHttpChannel::SetupReplacementChannel [%p] flag: %u", this,
+ aRedirectFlags));
+ nsresult rv = HttpBaseChannel::SetupReplacementChannel(
+ aURI, aChannel, aPreserveMethod, aRedirectFlags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = CheckRedirectLimit(aRedirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // While we can't resume an synthetic response, we can still propagate
+ // the resume params across redirects for other channels to handle.
+ if (mResumeStartPos > 0) {
+ nsCOMPtr<nsIResumableChannel> resumable = do_QueryInterface(aChannel);
+ if (!resumable) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ resumable->ResumeAt(mResumeStartPos, mResumeEntityId);
+ }
+
+ return NS_OK;
+}
+
+void InterceptedHttpChannel::AsyncOpenInternal() {
+ // We save this timestamp from outside of the if block in case we enable the
+ // profiler after AsyncOpen().
+ INTERCEPTED_LOG(("InterceptedHttpChannel::AsyncOpenInternal [%p]", this));
+ mLastStatusReported = TimeStamp::Now();
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ }
+
+ // If an error occurs in this file we must ensure mListener callbacks are
+ // invoked in some way. We either Cancel() or ResetInterception below
+ // depending on which path we take.
+ nsresult rv = NS_OK;
+
+ // Start the interception, record the start time.
+ mTimeStamps.Init(this);
+ mTimeStamps.RecordTime();
+
+ // We should have pre-set the AsyncOpen time based on the original channel if
+ // timings are enabled.
+ if (LoadTimingEnabled()) {
+ MOZ_DIAGNOSTIC_ASSERT(!mAsyncOpenTime.IsNull());
+ }
+
+ StoreIsPending(true);
+ StoreResponseCouldBeSynthesized(true);
+
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ // If we already have a synthesized body then we are pre-synthesized.
+ // This can happen for two reasons:
+ // 1. We have a pre-synthesized redirect in e10s mode. In this case
+ // we should follow the redirect.
+ // 2. We are handling a "fake" redirect for an opaque response. Here
+ // we should just process the synthetic body.
+ if (mBodyReader) {
+ // If we fail in this path, then cancel the channel. We don't want
+ // to ResetInterception() after a synthetic result has already been
+ // produced by the ServiceWorker.
+ auto autoCancel = MakeScopeExit([&] {
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ });
+
+ // The fetch event will not be dispatched, record current time for
+ // FetchHandlerStart and FetchHandlerFinish.
+ SetFetchHandlerStart(TimeStamp::Now());
+ SetFetchHandlerFinish(TimeStamp::Now());
+
+ if (ShouldRedirect()) {
+ rv = FollowSyntheticRedirect();
+ return;
+ }
+
+ rv = StartPump();
+ return;
+ }
+
+ // If we fail the initial interception, then attempt to ResetInterception
+ // to fall back to network. We only cancel if the reset fails.
+ auto autoReset = MakeScopeExit([&] {
+ if (NS_FAILED(rv)) {
+ rv = ResetInterception(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Cancel(rv);
+ }
+ }
+ });
+
+ // Otherwise we need to trigger a FetchEvent in a ServiceWorker.
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+
+ if (NS_WARN_IF(!controller)) {
+ rv = NS_ERROR_DOM_INVALID_STATE_ERR;
+ return;
+ }
+
+ rv = controller->ChannelIntercepted(this);
+ NS_ENSURE_SUCCESS_VOID(rv);
+}
+
+bool InterceptedHttpChannel::ShouldRedirect() const {
+ // Determine if the synthetic response requires us to perform a real redirect.
+ return nsHttpChannel::WillRedirect(*mResponseHead) &&
+ !mLoadInfo->GetDontFollowRedirects();
+}
+
+nsresult InterceptedHttpChannel::FollowSyntheticRedirect() {
+ // Perform a real redirect based on the synthetic response.
+
+ nsCOMPtr<nsIIOService> ioService;
+ nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString location;
+ rv = mResponseHead->GetHeader(nsHttp::Location, location);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // make sure non-ASCII characters in the location header are escaped.
+ nsAutoCString locationBuf;
+ if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII | esc_Spaces,
+ locationBuf)) {
+ location = locationBuf;
+ }
+
+ nsCOMPtr<nsIURI> redirectURI;
+ rv = ioService->NewURI(nsDependentCString(location.get()), nullptr, mURI,
+ getter_AddRefs(redirectURI));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_CORRUPTED_CONTENT);
+
+ uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
+ if (nsHttp::IsPermanentRedirect(mResponseHead->Status())) {
+ redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
+ }
+
+ PropagateReferenceIfNeeded(mURI, redirectURI);
+
+ bool rewriteToGET = ShouldRewriteRedirectToGET(mResponseHead->Status(),
+ mRequestHead.ParsedMethod());
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(redirectURI, redirectFlags);
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel), redirectURI,
+ redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ mLoadFlags, ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupReplacementChannel(redirectURI, newChannel, !rewriteToGET,
+ redirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRedirectChannel = std::move(newChannel);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel,
+ redirectFlags);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ OnRedirectVerifyCallback(rv);
+ } else {
+ // Redirect success, record the finish time and the final status.
+ mTimeStamps.RecordTime(InterceptionTimeStamps::Redirected);
+ }
+
+ return rv;
+}
+
+nsresult InterceptedHttpChannel::RedirectForResponseURL(
+ nsIURI* aResponseURI, bool aResponseRedirected) {
+ // Perform a service worker redirect to another InterceptedHttpChannel using
+ // the given response URL. It allows content to see the final URL where
+ // appropriate and also helps us enforce cross-origin restrictions. The
+ // resulting channel will then process the synthetic response as normal. This
+ // extra redirect is performed so that listeners treat the result as unsafe
+ // cross-origin data.
+
+ nsresult rv = NS_OK;
+
+ // We want to pass ownership of the body callback to the new synthesized
+ // channel. We need to hold a reference to the callbacks on the stack
+ // as well, though, so we can call them if a failure occurs.
+ nsCOMPtr<nsIInterceptedBodyCallback> bodyCallback = std::move(mBodyCallback);
+
+ RefPtr<InterceptedHttpChannel> newChannel = CreateForSynthesis(
+ mResponseHead.get(), mBodyReader, bodyCallback, mChannelCreationTime,
+ mChannelCreationTimestamp, mAsyncOpenTime);
+
+ // If the response has been redirected, propagate all the URLs to content.
+ // Thus, the exact value of the redirect flag does not matter as long as it's
+ // not REDIRECT_INTERNAL.
+ uint32_t flags = aResponseRedirected ? nsIChannelEventSink::REDIRECT_TEMPORARY
+ : nsIChannelEventSink::REDIRECT_INTERNAL;
+
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(aResponseURI, flags);
+
+ ExtContentPolicyType contentPolicyType =
+ redirectLoadInfo->GetExternalContentPolicyType();
+
+ rv = newChannel->Init(aResponseURI, mCaps,
+ static_cast<nsProxyInfo*>(mProxyInfo.get()),
+ mProxyResolveFlags, mProxyURI, mChannelId,
+ contentPolicyType, redirectLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Normally we don't propagate the LoadInfo's service worker tainting
+ // synthesis flag on redirect. A real redirect normally will want to allow
+ // normal tainting to proceed from its starting taint. For this particular
+ // redirect, though, we are performing a redirect to communicate the URL of
+ // the service worker synthetic response itself. This redirect still
+ // represents the synthetic response, so we must preserve the flag.
+ if (redirectLoadInfo && mLoadInfo &&
+ mLoadInfo->GetServiceWorkerTaintingSynthesized()) {
+ redirectLoadInfo->SynthesizeServiceWorkerTainting(mLoadInfo->GetTainting());
+ }
+
+ rv = SetupReplacementChannel(aResponseURI, newChannel, true, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRedirectChannel = newChannel;
+
+ MOZ_ASSERT(mBodyReader);
+ MOZ_ASSERT(!LoadApplyConversion());
+ newChannel->SetApplyConversion(false);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel, flags);
+
+ if (NS_FAILED(rv)) {
+ // Make sure to call the body callback since we took ownership
+ // above. Neither the new channel or our standard
+ // OnRedirectVerifyCallback() code will invoke the callback. Do it here.
+ bodyCallback->BodyComplete(rv);
+
+ OnRedirectVerifyCallback(rv);
+ }
+
+ return rv;
+}
+
+nsresult InterceptedHttpChannel::StartPump() {
+ MOZ_DIAGNOSTIC_ASSERT(!mPump);
+ MOZ_DIAGNOSTIC_ASSERT(mBodyReader);
+
+ // We don't support resuming an intercepted channel. We can't guarantee the
+ // ServiceWorker will always return the same data and we can't rely on the
+ // http cache code to detect changes. For now, just force the channel to
+ // NS_ERROR_NOT_RESUMABLE which should cause the front-end to recreate the
+ // channel without calling ResumeAt().
+ //
+ // It would also be possible to convert this information to a range request,
+ // but its unclear if we should do that for ServiceWorker FetchEvents. See:
+ //
+ // https://github.com/w3c/ServiceWorker/issues/1201
+ if (mResumeStartPos > 0) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ // For progress we trust the content-length for the "maximum" size.
+ // We can't determine the full size from the stream itself since
+ // we may only receive the data incrementally. We can't trust
+ // Available() here.
+ // TODO: We could implement an nsIFixedLengthInputStream interface and
+ // QI to it here. This would let us determine the total length
+ // for streams that support it. See bug 1388774.
+ Unused << GetContentLength(&mSynthesizedStreamLength);
+
+ nsresult rv =
+ nsInputStreamPump::Create(getter_AddRefs(mPump), mBodyReader, 0, 0, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mPump->AsyncRead(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t suspendCount = mSuspendCount;
+ while (suspendCount--) {
+ mPump->Suspend();
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!mCanceled);
+
+ return rv;
+}
+
+nsresult InterceptedHttpChannel::OpenRedirectChannel() {
+ INTERCEPTED_LOG(
+ ("InterceptedHttpChannel::OpenRedirectChannel [%p], mRedirectChannel: %p",
+ this, mRedirectChannel.get()));
+ nsresult rv = NS_OK;
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ if (!mRedirectChannel) {
+ return NS_ERROR_DOM_ABORT_ERR;
+ }
+
+ // Make sure to do this after we received redirect veto answer,
+ // i.e. after all sinks had been notified
+ mRedirectChannel->SetOriginalURI(mOriginalURI);
+
+ // open new channel
+ rv = mRedirectChannel->AsyncOpen(mListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = NS_BINDING_REDIRECTED;
+
+ return rv;
+}
+
+void InterceptedHttpChannel::MaybeCallStatusAndProgress() {
+ // OnStatus() and OnProgress() must only be called on the main thread. If
+ // we are on a separate thread, then we maybe need to schedule a runnable
+ // to call them asynchronousnly.
+ if (!NS_IsMainThread()) {
+ // Check to see if we are already trying to call OnStatus/OnProgress
+ // asynchronously. If we are, then don't queue up another runnable.
+ // We don't want to flood the main thread.
+ if (mCallingStatusAndProgress) {
+ return;
+ }
+ mCallingStatusAndProgress = true;
+
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ "InterceptedHttpChannel::MaybeCallStatusAndProgress", this,
+ &InterceptedHttpChannel::MaybeCallStatusAndProgress);
+ MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget()));
+
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We are about to capture out progress position. Clear the flag we use
+ // to de-duplicate progress report runnables. We want any further progress
+ // updates to trigger another runnable. We do this before capture the
+ // progress value since we're using atomics and not a mutex lock.
+ mCallingStatusAndProgress = false;
+
+ // Capture the current status from our atomic count.
+ int64_t progress = mProgress;
+
+ MOZ_DIAGNOSTIC_ASSERT(progress >= mProgressReported);
+
+ // Do nothing if we've already made the calls for this amount of progress
+ // or if the channel is not configured for these calls. Note, the check
+ // for mProgressSink here means we will not fire any spurious late calls
+ // after ReleaseListeners() is executed.
+ if (progress <= mProgressReported || mCanceled || !mProgressSink ||
+ (mLoadFlags & HttpBaseChannel::LOAD_BACKGROUND)) {
+ return;
+ }
+
+ // Capture the host name on the first set of calls to avoid doing this
+ // string processing repeatedly.
+ if (mProgressReported == 0) {
+ nsAutoCString host;
+ MOZ_ALWAYS_SUCCEEDS(mURI->GetHost(host));
+ CopyUTF8toUTF16(host, mStatusHost);
+ }
+
+ mProgressSink->OnStatus(this, NS_NET_STATUS_READING, mStatusHost.get());
+
+ mProgressSink->OnProgress(this, progress, mSynthesizedStreamLength);
+
+ mProgressReported = progress;
+}
+
+void InterceptedHttpChannel::MaybeCallBodyCallback() {
+ nsCOMPtr<nsIInterceptedBodyCallback> callback = std::move(mBodyCallback);
+ if (callback) {
+ callback->BodyComplete(mStatus);
+ }
+}
+
+// static
+already_AddRefed<InterceptedHttpChannel>
+InterceptedHttpChannel::CreateForInterception(
+ PRTime aCreationTime, const TimeStamp& aCreationTimestamp,
+ const TimeStamp& aAsyncOpenTimestamp) {
+ // Create an InterceptedHttpChannel that will trigger a FetchEvent
+ // in a ServiceWorker when opened.
+ RefPtr<InterceptedHttpChannel> ref = new InterceptedHttpChannel(
+ aCreationTime, aCreationTimestamp, aAsyncOpenTimestamp);
+
+ return ref.forget();
+}
+
+// static
+already_AddRefed<InterceptedHttpChannel>
+InterceptedHttpChannel::CreateForSynthesis(
+ const nsHttpResponseHead* aHead, nsIInputStream* aBody,
+ nsIInterceptedBodyCallback* aBodyCallback, PRTime aCreationTime,
+ const TimeStamp& aCreationTimestamp, const TimeStamp& aAsyncOpenTimestamp) {
+ MOZ_DIAGNOSTIC_ASSERT(aHead);
+ MOZ_DIAGNOSTIC_ASSERT(aBody);
+
+ // Create an InterceptedHttpChannel that already has a synthesized response.
+ // The synthetic response will be processed when opened. A FetchEvent
+ // will not be triggered.
+ RefPtr<InterceptedHttpChannel> ref = new InterceptedHttpChannel(
+ aCreationTime, aCreationTimestamp, aAsyncOpenTimestamp);
+
+ ref->mResponseHead = MakeUnique<nsHttpResponseHead>(*aHead);
+ ref->mBodyReader = aBody;
+ ref->mBodyCallback = aBodyCallback;
+
+ return ref.forget();
+}
+
+NS_IMETHODIMP InterceptedHttpChannel::SetCanceledReason(
+ const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP InterceptedHttpChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::Cancel(nsresult aStatus) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::Cancel [%p]", this));
+ // Note: This class has been designed to send all error results through
+ // Cancel(). Don't add calls directly to AsyncAbort() or
+ // DoNotifyListener(). Instead call Cancel().
+
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ // The interception is canceled, record the finish time stamp and the final
+ // status
+ mTimeStamps.RecordTime(InterceptionTimeStamps::Canceled);
+
+ mCanceled = true;
+
+ if (mLastStatusReported && profiler_thread_is_being_profiled_for_markers()) {
+ // These do allocations/frees/etc; avoid if not active
+ // mLastStatusReported can be null if Cancel is called before we added the
+ // start marker.
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_CANCEL,
+ mLastStatusReported, TimeStamp::Now(), size, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource));
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aStatus));
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ if (mPump) {
+ return mPump->Cancel(mStatus);
+ }
+
+ return AsyncAbort(mStatus);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::Suspend(void) {
+ ++mSuspendCount;
+ if (mPump) {
+ return mPump->Suspend();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::Resume(void) {
+ --mSuspendCount;
+ if (mPump) {
+ return mPump->Resume();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ nsCOMPtr<nsITransportSecurityInfo> ref(mSecurityInfo);
+ ref.forget(aSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::AsyncOpen(nsIStreamListener* aListener) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::AsyncOpen [%p], listener: %p", this,
+ aListener));
+ nsCOMPtr<nsIStreamListener> listener(aListener);
+
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Cancel(rv);
+ return rv;
+ }
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ // After this point we should try to return NS_OK and notify the listener
+ // of the result.
+ mListener = aListener;
+
+ AsyncOpenInternal();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ // Synthetic responses should not trigger CORS blocking.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetIsAuthChannel(bool* aIsAuthChannel) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetPriority(int32_t aPriority) {
+ mPriority = clamped<int32_t>(aPriority, INT16_MIN, INT16_MAX);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetClassFlags(uint32_t aClassFlags) {
+ mClassOfService.SetFlags(aClassFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::ClearClassFlags(uint32_t aClassFlags) {
+ mClassOfService.SetFlags(~aClassFlags & mClassOfService.Flags());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::AddClassFlags(uint32_t aClassFlags) {
+ mClassOfService.SetFlags(aClassFlags | mClassOfService.Flags());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetClassOfService(ClassOfService cos) {
+ mClassOfService = cos;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetIncremental(bool incremental) {
+ mClassOfService.SetIncremental(incremental);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::ResumeAt(uint64_t aStartPos,
+ const nsACString& aEntityId) {
+ // We don't support resuming synthesized responses, but we do track this
+ // information so it can be passed on to the resulting nsHttpChannel if
+ // ResetInterception is called.
+ mResumeStartPos = aStartPos;
+ mResumeEntityId = aEntityId;
+ return NS_OK;
+}
+
+void InterceptedHttpChannel::DoNotifyListenerCleanup() {
+ // Prefer to cleanup in ReleaseListeners() as it seems to be called
+ // more consistently in necko.
+}
+
+void InterceptedHttpChannel::DoAsyncAbort(nsresult aStatus) {
+ Unused << AsyncAbort(aStatus);
+}
+
+namespace {
+
+class ResetInterceptionHeaderVisitor final : public nsIHttpHeaderVisitor {
+ nsCOMPtr<nsIHttpChannel> mTarget;
+
+ ~ResetInterceptionHeaderVisitor() = default;
+
+ NS_IMETHOD
+ VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
+ // We skip Cookie header here, since it will be added during
+ // nsHttpChannel::AsyncOpen.
+ if (aHeader.Equals(nsHttp::Cookie.val())) {
+ return NS_OK;
+ }
+ if (aValue.IsEmpty()) {
+ return mTarget->SetEmptyRequestHeader(aHeader);
+ }
+ return mTarget->SetRequestHeader(aHeader, aValue, false /* merge */);
+ }
+
+ public:
+ explicit ResetInterceptionHeaderVisitor(nsIHttpChannel* aTarget)
+ : mTarget(aTarget) {
+ MOZ_DIAGNOSTIC_ASSERT(mTarget);
+ }
+
+ NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(ResetInterceptionHeaderVisitor, nsIHttpHeaderVisitor)
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+InterceptedHttpChannel::ResetInterception(bool aBypass) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::ResetInterception [%p] bypass: %s",
+ this, aBypass ? "true" : "false"));
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ mInterceptionReset = true;
+
+ uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(mURI, flags);
+
+ if (aBypass) {
+ redirectLoadInfo->ClearController();
+ // TODO: Audit whether we should also be calling
+ // ServiceWorkerManager::StopControllingClient for maximum correctness.
+ }
+
+ nsresult rv =
+ NS_NewChannelInternal(getter_AddRefs(newChannel), mURI, redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ mLoadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+
+ RefPtr<HttpBaseChannel> newBaseChannel = do_QueryObject(newChannel);
+ MOZ_ASSERT(newBaseChannel,
+ "The redirect channel should be a base channel.");
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId,
+ NetworkLoadType::LOAD_REDIRECT, mLastStatusReported, TimeStamp::Now(),
+ size, kCacheUnknown, mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())), mURI, flags,
+ newBaseChannel->ChannelId());
+ }
+
+ rv = SetupReplacementChannel(mURI, newChannel, true, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Restore the non-default headers for fallback channel.
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(newChannel));
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new ResetInterceptionHeaderVisitor(httpChannel);
+ rv = VisitNonDefaultRequestHeaders(visitor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsITimedChannel> newTimedChannel = do_QueryInterface(newChannel);
+ if (newTimedChannel) {
+ if (!mAsyncOpenTime.IsNull()) {
+ newTimedChannel->SetAsyncOpen(mAsyncOpenTime);
+ }
+ if (!mChannelCreationTimestamp.IsNull()) {
+ newTimedChannel->SetChannelCreation(mChannelCreationTimestamp);
+ }
+ }
+
+ if (mRedirectMode != nsIHttpChannelInternal::REDIRECT_MODE_MANUAL) {
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
+ rv = newChannel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+ rv = newChannel->SetLoadFlags(loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mRedirectChannel = std::move(newChannel);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel, flags);
+
+ if (NS_FAILED(rv)) {
+ OnRedirectVerifyCallback(rv);
+ } else {
+ // ResetInterception success, record the finish time stamps and the final
+ // status.
+ mTimeStamps.RecordTime(InterceptionTimeStamps::Reset);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SynthesizeStatus(uint16_t aStatus,
+ const nsACString& aReason) {
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (!mSynthesizedResponseHead) {
+ mSynthesizedResponseHead.reset(new nsHttpResponseHead());
+ }
+
+ nsAutoCString statusLine;
+ statusLine.AppendLiteral("HTTP/1.1 ");
+ statusLine.AppendInt(aStatus);
+ statusLine.AppendLiteral(" ");
+ statusLine.Append(aReason);
+
+ NS_ENSURE_SUCCESS(mSynthesizedResponseHead->ParseStatusLine(statusLine),
+ NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SynthesizeHeader(const nsACString& aName,
+ const nsACString& aValue) {
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (!mSynthesizedResponseHead) {
+ mSynthesizedResponseHead.reset(new nsHttpResponseHead());
+ }
+
+ nsAutoCString header = aName + ": "_ns + aValue;
+ // Overwrite any existing header.
+ nsresult rv = mSynthesizedResponseHead->ParseHeaderLine(header);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::StartSynthesizedResponse(
+ nsIInputStream* aBody, nsIInterceptedBodyCallback* aBodyCallback,
+ nsICacheInfoChannel* aSynthesizedCacheInfo, const nsACString& aFinalURLSpec,
+ bool aResponseRedirected) {
+ nsresult rv = NS_OK;
+
+ auto autoCleanup = MakeScopeExit([&] {
+ // Auto-cancel on failure. Do this first to get mStatus set, if necessary.
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+
+ // If we early exit before taking ownership of the body, then automatically
+ // invoke the callback. This could be due to an error or because we're not
+ // going to consume it due to a redirect, etc.
+ if (aBodyCallback) {
+ aBodyCallback->BodyComplete(mStatus);
+ }
+ });
+
+ if (NS_FAILED(mStatus)) {
+ // Return NS_OK. The channel should fire callbacks with an error code
+ // if it was cancelled before this point.
+ return NS_OK;
+ }
+
+ // Take ownership of the body callbacks If a failure occurs we will
+ // automatically Cancel() the channel. This will then invoke OnStopRequest()
+ // which will invoke the correct callback. In the case of an opaque response
+ // redirect we pass ownership of the callback to the new channel.
+ mBodyCallback = aBodyCallback;
+ aBodyCallback = nullptr;
+
+ mSynthesizedCacheInfo = aSynthesizedCacheInfo;
+
+ if (!mSynthesizedResponseHead) {
+ mSynthesizedResponseHead.reset(new nsHttpResponseHead());
+ }
+
+ mResponseHead = std::move(mSynthesizedResponseHead);
+
+ if (ShouldRedirect()) {
+ rv = FollowSyntheticRedirect();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ // Intercepted responses should already be decoded.
+ SetApplyConversion(false);
+
+ // Errors and redirects may not have a body. Synthesize an empty string
+ // stream here so later code can be simpler.
+ mBodyReader = aBody;
+ if (!mBodyReader) {
+ rv = NS_NewCStringInputStream(getter_AddRefs(mBodyReader), ""_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIURI> responseURI;
+ if (!aFinalURLSpec.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ responseURI = mURI;
+ }
+
+ bool equal = false;
+ Unused << mURI->Equals(responseURI, &equal);
+ if (!equal) {
+ rv = RedirectForResponseURL(responseURI, aResponseRedirected);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ rv = StartPump();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::FinishSynthesizedResponse() {
+ if (mCanceled) {
+ // Return NS_OK. The channel should fire callbacks with an error code
+ // if it was cancelled before this point.
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::CancelInterception(nsresult aStatus) {
+ return Cancel(aStatus);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetChannel(nsIChannel** aChannel) {
+ nsCOMPtr<nsIChannel> ref(this);
+ ref.forget(aChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetSecureUpgradedChannelURI(
+ nsIURI** aSecureUpgradedChannelURI) {
+ nsCOMPtr<nsIURI> ref(mURI);
+ ref.forget(aSecureUpgradedChannelURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetChannelInfo(
+ mozilla::dom::ChannelInfo* aChannelInfo) {
+ return aChannelInfo->ResurrectInfoOnChannel(this);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetInternalContentPolicyType(
+ nsContentPolicyType* aPolicyType) {
+ if (mLoadInfo) {
+ *aPolicyType = mLoadInfo->InternalContentPolicyType();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetConsoleReportCollector(
+ nsIConsoleReportCollector** aConsoleReportCollector) {
+ nsCOMPtr<nsIConsoleReportCollector> ref(this);
+ ref.forget(aConsoleReportCollector);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetFetchHandlerStart(TimeStamp aTimeStamp) {
+ mTimeStamps.RecordTime(std::move(aTimeStamp));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetFetchHandlerFinish(TimeStamp aTimeStamp) {
+ mTimeStamps.RecordTime(std::move(aTimeStamp));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetRemoteWorkerLaunchStart(TimeStamp aTimeStamp) {
+ mServiceWorkerLaunchStart = aTimeStamp > mTimeStamps.mInterceptionStart
+ ? aTimeStamp
+ : mTimeStamps.mInterceptionStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetRemoteWorkerLaunchEnd(TimeStamp aTimeStamp) {
+ mServiceWorkerLaunchEnd = aTimeStamp > mTimeStamps.mInterceptionStart
+ ? aTimeStamp
+ : mTimeStamps.mInterceptionStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) {
+ mServiceWorkerLaunchStart = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetLaunchServiceWorkerStart(TimeStamp* aRetVal) {
+ MOZ_ASSERT(aRetVal);
+ *aRetVal = mServiceWorkerLaunchStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) {
+ mServiceWorkerLaunchEnd = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetLaunchServiceWorkerEnd(TimeStamp* aRetVal) {
+ MOZ_ASSERT(aRetVal);
+ *aRetVal = mServiceWorkerLaunchEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetDispatchFetchEventStart(TimeStamp* aRetVal) {
+ MOZ_ASSERT(aRetVal);
+ *aRetVal = mTimeStamps.mInterceptionStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetDispatchFetchEventEnd(TimeStamp* aRetVal) {
+ MOZ_ASSERT(aRetVal);
+ *aRetVal = mTimeStamps.mFetchHandlerStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetHandleFetchEventStart(TimeStamp* aRetVal) {
+ MOZ_ASSERT(aRetVal);
+ *aRetVal = mTimeStamps.mFetchHandlerStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetHandleFetchEventEnd(TimeStamp* aRetVal) {
+ MOZ_ASSERT(aRetVal);
+ *aRetVal = mTimeStamps.mFetchHandlerFinish;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetIsReset(bool* aResult) {
+ *aResult = mInterceptionReset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetReleaseHandle(nsISupports* aHandle) {
+ mReleaseHandle = aHandle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnRedirectVerifyCallback(nsresult rv) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = OpenRedirectChannel();
+ }
+
+ nsCOMPtr<nsIRedirectResultListener> hook;
+ GetCallback(hook);
+ if (hook) {
+ hook->OnRedirectResult(rv);
+ }
+
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+
+ MaybeCallBodyCallback();
+
+ StoreIsPending(false);
+ // We can only release listeners after the redirected channel really owns
+ // mListener. Otherwise, the OnStart/OnStopRequest functions of mListener will
+ // not be called.
+ if (NS_SUCCEEDED(rv)) {
+ ReleaseListeners();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::OnStartRequest [%p]", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mProgressSink) {
+ GetCallback(mProgressSink);
+ }
+
+ MOZ_ASSERT_IF(!mLoadInfo->GetServiceWorkerTaintingSynthesized(),
+ mLoadInfo->GetLoadingPrincipal());
+ // No need to do ORB checks if these conditions hold.
+ MOZ_DIAGNOSTIC_ASSERT(mLoadInfo->GetServiceWorkerTaintingSynthesized() ||
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal());
+
+ if (mPump && mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
+ mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
+ }
+
+ nsresult rv = ProcessCrossOriginEmbedderPolicyHeader();
+ if (NS_FAILED(rv)) {
+ mStatus = NS_ERROR_BLOCKED_BY_POLICY;
+ Cancel(mStatus);
+ }
+
+ rv = ProcessCrossOriginResourcePolicyHeader();
+ if (NS_FAILED(rv)) {
+ mStatus = NS_ERROR_DOM_CORP_FAILED;
+ Cancel(mStatus);
+ }
+
+ rv = ComputeCrossOriginOpenerPolicyMismatch();
+ if (rv == NS_ERROR_BLOCKED_BY_POLICY) {
+ mStatus = NS_ERROR_BLOCKED_BY_POLICY;
+ Cancel(mStatus);
+ }
+
+ rv = ValidateMIMEType();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ Cancel(mStatus);
+ }
+
+ StoreOnStartRequestCalled(true);
+ if (mListener) {
+ return mListener->OnStartRequest(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::OnStopRequest [%p]", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ MaybeCallBodyCallback();
+
+ mTimeStamps.RecordTime(InterceptionTimeStamps::Synthesized);
+
+ // Its possible that we have any async runnable queued to report some
+ // progress when OnStopRequest() is triggered. Report any left over
+ // progress immediately. The extra runnable will then do nothing thanks
+ // to the ReleaseListeners() call below.
+ MaybeCallStatusAndProgress();
+
+ StoreIsPending(false);
+
+ // Register entry to the PerformanceStorage resource timing
+ MaybeReportTimingData();
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ // These do allocations/frees/etc; avoid if not active
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_STOP,
+ mLastStatusReported, TimeStamp::Now(), size, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())));
+ }
+
+ nsresult rv = NS_OK;
+ if (mListener) {
+ rv = mListener->OnStopRequest(this, mStatus);
+ }
+
+ gHttpHandler->OnStopRequest(this);
+
+ ReleaseListeners();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ // Any thread if the channel has been retargeted.
+
+ if (mCanceled || !mListener) {
+ // If there is no listener, we still need to drain the stream in order
+ // maintain necko invariants.
+ uint32_t unused = 0;
+ aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &unused);
+ return mStatus;
+ }
+ if (mProgressSink) {
+ if (!(mLoadFlags & HttpBaseChannel::LOAD_BACKGROUND)) {
+ mProgress = aOffset + aCount;
+ MaybeCallStatusAndProgress();
+ }
+ }
+
+ return mListener->OnDataAvailable(this, aInputStream, aOffset, aCount);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnDataFinished(nsresult aStatus) {
+ if (mCanceled || !mListener) {
+ return aStatus;
+ }
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener);
+ if (retargetableListener) {
+ return retargetableListener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aNewTarget);
+
+ // If retargeting to the main thread, do nothing.
+ if (aNewTarget->IsOnCurrentThread()) {
+ return NS_OK;
+ }
+
+ // Retargeting is only valid during OnStartRequest for nsIChannels. So
+ // we should only be called if we have a pump.
+ if (!mPump) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mPump->RetargetDeliveryTo(aNewTarget);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) {
+ if (!mPump) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mPump->GetDeliveryTarget(aEventTarget);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::CheckListenerChain() {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// InterceptedHttpChannel::nsICacheInfoChannel
+//-----------------------------------------------------------------------------
+// InterceptedHttpChannel does not really implement the nsICacheInfoChannel
+// interface, we tranfers parameters to the saved
+// nsICacheInfoChannel(mSynthesizedCacheInfo) from StartSynthesizedResponse. And
+// we return false in IsFromCache and NS_ERROR_NOT_AVAILABLE for all other
+// methods while the saved mSynthesizedCacheInfo does not exist.
+NS_IMETHODIMP
+InterceptedHttpChannel::IsFromCache(bool* value) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->IsFromCache(value);
+ }
+ *value = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::IsRacing(bool* value) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->IsRacing(value);
+ }
+ *value = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetCacheEntryId(uint64_t* aCacheEntryId) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetCacheEntryId(aCacheEntryId);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetCacheTokenFetchCount(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetCacheTokenFetchCount(_retval);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetCacheTokenExpirationTime(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetCacheTokenExpirationTime(_retval);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetAllowStaleCacheContent(
+ bool aAllowStaleCacheContent) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->SetAllowStaleCacheContent(
+ aAllowStaleCacheContent);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetAllowStaleCacheContent(
+ bool* aAllowStaleCacheContent) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetAllowStaleCacheContent(
+ aAllowStaleCacheContent);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetForceValidateCacheContent(
+ bool aForceValidateCacheContent) {
+ // We store aForceValidateCacheContent locally because
+ // mSynthesizedCacheInfo isn't present until a response
+ // is actually synthesized, which is too late for the value
+ // to be forwarded during the redirect to the intercepted
+ // channel.
+ StoreForceValidateCacheContent(aForceValidateCacheContent);
+
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->SetForceValidateCacheContent(
+ aForceValidateCacheContent);
+ }
+ return NS_OK;
+}
+NS_IMETHODIMP
+InterceptedHttpChannel::GetForceValidateCacheContent(
+ bool* aForceValidateCacheContent) {
+ *aForceValidateCacheContent = LoadForceValidateCacheContent();
+#ifdef DEBUG
+ if (mSynthesizedCacheInfo) {
+ bool synthesizedForceValidateCacheContent;
+ mSynthesizedCacheInfo->GetForceValidateCacheContent(
+ &synthesizedForceValidateCacheContent);
+ MOZ_ASSERT(*aForceValidateCacheContent ==
+ synthesizedForceValidateCacheContent);
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetPreferCacheLoadOverBypass(
+ bool* aPreferCacheLoadOverBypass) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetPreferCacheLoadOverBypass(
+ aPreferCacheLoadOverBypass);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetPreferCacheLoadOverBypass(
+ bool aPreferCacheLoadOverBypass) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->SetPreferCacheLoadOverBypass(
+ aPreferCacheLoadOverBypass);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::PreferAlternativeDataType(
+ const nsACString& aType, const nsACString& aContentType,
+ PreferredAlternativeDataDeliveryType aDeliverAltData) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+ mPreferredCachedAltDataTypes.AppendElement(PreferredAlternativeDataTypeParams(
+ nsCString(aType), nsCString(aContentType), aDeliverAltData));
+ return NS_OK;
+}
+
+const nsTArray<PreferredAlternativeDataTypeParams>&
+InterceptedHttpChannel::PreferredAlternativeDataTypes() {
+ return mPreferredCachedAltDataTypes;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetAlternativeDataType(nsACString& aType) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetAlternativeDataType(aType);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OpenAlternativeOutputStream(
+ const nsACString& type, int64_t predictedSize,
+ nsIAsyncOutputStream** _retval) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->OpenAlternativeOutputStream(
+ type, predictedSize, _retval);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetOriginalInputStream(
+ nsIInputStreamReceiver* aReceiver) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetOriginalInputStream(aReceiver);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetAlternativeDataInputStream(
+ nsIInputStream** aInputStream) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetAlternativeDataInputStream(aInputStream);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetCacheKey(uint32_t* key) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetCacheKey(key);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetCacheKey(uint32_t key) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->SetCacheKey(key);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+// InterceptionTimeStamps implementation
+InterceptedHttpChannel::InterceptionTimeStamps::InterceptionTimeStamps()
+ : mStage(InterceptedHttpChannel::InterceptionTimeStamps::InterceptionStart),
+ mStatus(InterceptedHttpChannel::InterceptionTimeStamps::Created) {}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::Init(
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+ MOZ_ASSERT(mStatus == Created);
+
+ mStatus = Initialized;
+
+ mIsNonSubresourceRequest = nsContentUtils::IsNonSubresourceRequest(aChannel);
+ mKey = mIsNonSubresourceRequest ? "navigation"_ns : "subresource"_ns;
+ nsCOMPtr<nsIInterceptedChannel> interceptedChannel =
+ do_QueryInterface(aChannel);
+ // It must be a InterceptedHttpChannel
+ MOZ_ASSERT(interceptedChannel);
+ if (!mIsNonSubresourceRequest) {
+ interceptedChannel->GetSubresourceTimeStampKey(aChannel, mSubresourceKey);
+ }
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::RecordTime(
+ InterceptedHttpChannel::InterceptionTimeStamps::Status&& aStatus,
+ TimeStamp&& aTimeStamp) {
+ // Only allow passing Synthesized, Reset, Redirected, and Canceled in this
+ // method.
+ MOZ_ASSERT(aStatus == Synthesized || aStatus == Reset ||
+ aStatus == Canceled || aStatus == Redirected);
+ if (mStatus == Canceled) {
+ return;
+ }
+
+ // If current status is not Initialized, only Canceled can be recorded.
+ // That means it is canceled after other operation is done, ex. synthesized.
+ MOZ_ASSERT(mStatus == Initialized || aStatus == Canceled);
+
+ switch (mStatus) {
+ case Initialized:
+ mStatus = aStatus;
+ break;
+ case Synthesized:
+ mStatus = CanceledAfterSynthesized;
+ break;
+ case Reset:
+ mStatus = CanceledAfterReset;
+ break;
+ case Redirected:
+ mStatus = CanceledAfterRedirected;
+ break;
+ // Channel is cancelled before calling AsyncOpenInternal(), no need to
+ // record the cancel time stamp.
+ case Created:
+ return;
+ default:
+ MOZ_ASSERT(false);
+ break;
+ }
+
+ RecordTimeInternal(std::move(aTimeStamp));
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::RecordTime(
+ TimeStamp&& aTimeStamp) {
+ MOZ_ASSERT(mStatus == Initialized || mStatus == Canceled);
+ if (mStatus == Canceled) {
+ return;
+ }
+ RecordTimeInternal(std::move(aTimeStamp));
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::RecordTimeInternal(
+ TimeStamp&& aTimeStamp) {
+ MOZ_ASSERT(mStatus != Created);
+
+ if (mStatus == Canceled && mStage != InterceptionFinish) {
+ mFetchHandlerStart = aTimeStamp;
+ mFetchHandlerFinish = aTimeStamp;
+ mStage = InterceptionFinish;
+ }
+
+ switch (mStage) {
+ case InterceptionStart: {
+ MOZ_ASSERT(mInterceptionStart.IsNull());
+ mInterceptionStart = aTimeStamp;
+ mStage = FetchHandlerStart;
+ break;
+ }
+ case (FetchHandlerStart): {
+ MOZ_ASSERT(mFetchHandlerStart.IsNull());
+ mFetchHandlerStart = aTimeStamp;
+ mStage = FetchHandlerFinish;
+ break;
+ }
+ case (FetchHandlerFinish): {
+ MOZ_ASSERT(mFetchHandlerFinish.IsNull());
+ mFetchHandlerFinish = aTimeStamp;
+ mStage = InterceptionFinish;
+ break;
+ }
+ case InterceptionFinish: {
+ mInterceptionFinish = aTimeStamp;
+ SaveTimeStamps();
+ return;
+ }
+ default: {
+ return;
+ }
+ }
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::GenKeysWithStatus(
+ nsCString& aKey, nsCString& aSubresourceKey) {
+ nsAutoCString statusString;
+ switch (mStatus) {
+ case Synthesized:
+ statusString = "synthesized"_ns;
+ break;
+ case Reset:
+ statusString = "reset"_ns;
+ break;
+ case Redirected:
+ statusString = "redirected"_ns;
+ break;
+ case Canceled:
+ statusString = "canceled"_ns;
+ break;
+ case CanceledAfterSynthesized:
+ statusString = "canceled-after-synthesized"_ns;
+ break;
+ case CanceledAfterReset:
+ statusString = "canceled-after-reset"_ns;
+ break;
+ case CanceledAfterRedirected:
+ statusString = "canceled-after-redirected"_ns;
+ break;
+ default:
+ return;
+ }
+ aKey = mKey;
+ aSubresourceKey = mSubresourceKey;
+ aKey.AppendLiteral("_");
+ aSubresourceKey.AppendLiteral("_");
+ aKey.Append(statusString);
+ aSubresourceKey.Append(statusString);
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::SaveTimeStamps() {
+ MOZ_ASSERT(mStatus != Initialized && mStatus != Created);
+
+ if (mStatus == Synthesized || mStatus == Reset) {
+ Telemetry::HistogramID id =
+ Telemetry::SERVICE_WORKER_FETCH_EVENT_FINISH_SYNTHESIZED_RESPONSE_MS_2;
+ if (mStatus == Reset) {
+ id = Telemetry::SERVICE_WORKER_FETCH_EVENT_CHANNEL_RESET_MS_2;
+ }
+
+ Telemetry::Accumulate(
+ id, mKey,
+ static_cast<uint32_t>(
+ (mInterceptionFinish - mFetchHandlerFinish).ToMilliseconds()));
+ if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(
+ id, mSubresourceKey,
+ static_cast<uint32_t>(
+ (mInterceptionFinish - mFetchHandlerFinish).ToMilliseconds()));
+ }
+ }
+
+ if (!mFetchHandlerStart.IsNull()) {
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS_2, mKey,
+ static_cast<uint32_t>(
+ (mFetchHandlerStart - mInterceptionStart).ToMilliseconds()));
+
+ if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS_2, mSubresourceKey,
+ static_cast<uint32_t>(
+ (mFetchHandlerStart - mInterceptionStart).ToMilliseconds()));
+ }
+ }
+
+ nsAutoCString key, subresourceKey;
+ GenKeysWithStatus(key, subresourceKey);
+
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS_2, key,
+ static_cast<uint32_t>(
+ (mInterceptionFinish - mInterceptionStart).ToMilliseconds()));
+ if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS_2,
+ subresourceKey,
+ static_cast<uint32_t>(
+ (mInterceptionFinish - mInterceptionStart).ToMilliseconds()));
+ }
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/InterceptedHttpChannel.h b/netwerk/protocol/http/InterceptedHttpChannel.h
new file mode 100644
index 0000000000..b534999513
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedHttpChannel.h
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_InterceptedHttpChannel_h
+#define mozilla_net_InterceptedHttpChannel_h
+
+#include "HttpBaseChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIInputStream.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+
+namespace mozilla::net {
+
+// This class represents an http channel that is being intercepted by a
+// ServiceWorker. This means that when the channel is opened a FetchEvent
+// will be fired on the ServiceWorker thread. The channel will complete
+// depending on what the worker does. The options are:
+//
+// 1. If the ServiceWorker does not handle the FetchEvent or does not call
+// FetchEvent.respondWith(), then the channel needs to fall back to a
+// normal request. When this happens ResetInterception() is called and
+// the channel will perform an internal redirect back to an nsHttpChannel.
+//
+// 2. If the ServiceWorker provides a Response to FetchEvent.respondWith()
+// then the status, headers, and body must be synthesized. When
+// FinishSynthesizedResponse() is called the synthesized data must be
+// reported back to the channel listener. This is handled in a few
+// different ways:
+// a. If a redirect was synthesized, then we perform the redirect to
+// a new nsHttpChannel. This new channel might trigger yet another
+// interception.
+// b. If a same-origin or CORS Response was synthesized, then we simply
+// crate an nsInputStreamPump to process it and call back to the
+// listener.
+// c. If an opaque Response was synthesized, then we perform an internal
+// redirect to a new InterceptedHttpChannel using the cross-origin URL.
+// When this new channel is opened, it then creates a pump as in case
+// (b). The extra redirect here is to make sure the various listeners
+// treat the result as unsafe cross-origin data.
+//
+// 3. If an error occurs, such as the ServiceWorker passing garbage to
+// FetchEvent.respondWith(), then CancelInterception() is called. This is
+// handled the same as a normal nsIChannel::Cancel() call. We abort the
+// channel and end up calling OnStopRequest() with an error code.
+class InterceptedHttpChannel final
+ : public HttpBaseChannel,
+ public HttpAsyncAborter<InterceptedHttpChannel>,
+ public nsIInterceptedChannel,
+ public nsICacheInfoChannel,
+ public nsIAsyncVerifyRedirectCallback,
+ public nsIThreadRetargetableRequest,
+ public nsIThreadRetargetableStreamListener {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINTERCEPTEDCHANNEL
+ NS_DECL_NSICACHEINFOCHANNEL
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ private:
+ friend class HttpAsyncAborter<InterceptedHttpChannel>;
+
+ UniquePtr<nsHttpResponseHead> mSynthesizedResponseHead;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsCOMPtr<nsIInputStream> mBodyReader;
+ nsCOMPtr<nsISupports> mReleaseHandle;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIInterceptedBodyCallback> mBodyCallback;
+ nsCOMPtr<nsICacheInfoChannel> mSynthesizedCacheInfo;
+ RefPtr<nsInputStreamPump> mPump;
+ TimeStamp mInterceptedChannelCreationTimestamp;
+
+ // For the profiler markers
+ TimeStamp mLastStatusReported;
+
+ Atomic<int64_t> mProgress;
+ int64_t mProgressReported;
+ int64_t mSynthesizedStreamLength;
+ uint64_t mResumeStartPos;
+ nsCString mResumeEntityId;
+ nsString mStatusHost;
+ Atomic<bool> mCallingStatusAndProgress;
+ bool mInterceptionReset{false};
+
+ /**
+ * InterceptionTimeStamps is used to record the time stamps of the
+ * interception.
+ * The general usage:
+ * Step 1. Initialize the InterceptionTimeStamps;
+ * InterceptionTimeStamps::Init(channel);
+ * Step 2. Record time for each stage
+ * InterceptionTimeStamps::RecordTime(); or
+ * InterceptionTimeStamps::RecordTime(timeStamp);
+ * Step 3. Record time for the last stage with the final status
+ * InterceptionTimeStamps::RecordTime(InterceptionTimeStamps::Synthesized);
+ */
+ class InterceptionTimeStamps final {
+ public:
+ // The possible status of the interception.
+ enum Status {
+ Created,
+ Initialized,
+ Synthesized,
+ Reset,
+ Redirected,
+ Canceled,
+ CanceledAfterSynthesized,
+ CanceledAfterReset,
+ CanceledAfterRedirected
+ };
+
+ InterceptionTimeStamps();
+ ~InterceptionTimeStamps() = default;
+
+ /**
+ * Initialize with the given channel.
+ * This method should be called before any RecordTime().
+ */
+ void Init(nsIChannel* aChannel);
+
+ /**
+ * Record the given time stamp for current stage. If there is no given time
+ * stamp, TimeStamp::Now() will be recorded.
+ * The current stage is auto moved to the next one.
+ */
+ void RecordTime(TimeStamp&& aTimeStamp = TimeStamp::Now());
+
+ /**
+ * Record the given time stamp for the last stage(InterceptionFinish) and
+ * set the final status to the given status.
+ * If these is no given time stamp, TimeStamp::Now() will be recorded.
+ * Notice that this method is for the last stage, it calls SaveTimeStamps()
+ * to write data into telemetries.
+ */
+ void RecordTime(Status&& aStatus,
+ TimeStamp&& aTimeStamp = TimeStamp::Now());
+
+ // The time stamp which the intercepted channel is created and async opend.
+ TimeStamp mInterceptionStart;
+
+ // The time stamp which the interception finishes.
+ TimeStamp mInterceptionFinish;
+
+ // The time stamp which the fetch event starts to be handled by fetch event
+ // handler.
+ TimeStamp mFetchHandlerStart;
+
+ // The time stamp which the fetch event handling finishes. It would the time
+ // which remote worker sends result back.
+ TimeStamp mFetchHandlerFinish;
+
+ private:
+ // The stage of interception.
+ enum Stage {
+ InterceptionStart,
+ FetchHandlerStart,
+ FetchHandlerFinish,
+ InterceptionFinish
+ } mStage;
+
+ // The final status of the interception.
+ Status mStatus;
+
+ bool mIsNonSubresourceRequest;
+ // The keys used for telemetries.
+ nsCString mKey;
+ nsCString mSubresourceKey;
+
+ void RecordTimeInternal(TimeStamp&& aTimeStamp);
+
+ // Generate the record keys with final status.
+ void GenKeysWithStatus(nsCString& aKey, nsCString& aSubresourceKey);
+
+ // Save the time stamps into telemetries.
+ void SaveTimeStamps();
+ };
+
+ InterceptionTimeStamps mTimeStamps;
+
+ InterceptedHttpChannel(PRTime aCreationTime,
+ const TimeStamp& aCreationTimestamp,
+ const TimeStamp& aAsyncOpenTimestamp);
+ ~InterceptedHttpChannel() = default;
+
+ virtual void ReleaseListeners() override;
+
+ [[nodiscard]] virtual nsresult SetupReplacementChannel(
+ nsIURI* aURI, nsIChannel* aChannel, bool aPreserveMethod,
+ uint32_t aRedirectFlags) override;
+
+ void AsyncOpenInternal();
+
+ bool ShouldRedirect() const;
+
+ nsresult FollowSyntheticRedirect();
+
+ // If the response's URL is different from the request's then do a service
+ // worker redirect. If Response.redirected is false we do an internal
+ // redirect. Otherwise, if Response.redirect is true do a non-internal
+ // redirect so end consumers detect the redirected state.
+ nsresult RedirectForResponseURL(nsIURI* aResponseURI,
+ bool aResponseRedirected);
+
+ nsresult StartPump();
+
+ nsresult OpenRedirectChannel();
+
+ void MaybeCallStatusAndProgress();
+
+ void MaybeCallBodyCallback();
+
+ TimeStamp mServiceWorkerLaunchStart;
+ TimeStamp mServiceWorkerLaunchEnd;
+
+ public:
+ static already_AddRefed<InterceptedHttpChannel> CreateForInterception(
+ PRTime aCreationTime, const TimeStamp& aCreationTimestamp,
+ const TimeStamp& aAsyncOpenTimestamp);
+
+ static already_AddRefed<InterceptedHttpChannel> CreateForSynthesis(
+ const nsHttpResponseHead* aHead, nsIInputStream* aBody,
+ nsIInterceptedBodyCallback* aBodyCallback, PRTime aCreationTime,
+ const TimeStamp& aCreationTimestamp,
+ const TimeStamp& aAsyncOpenTimestamp);
+
+ NS_IMETHOD SetCanceledReason(const nsACString& aReason) override;
+ NS_IMETHOD GetCanceledReason(nsACString& aReason) override;
+ NS_IMETHOD CancelWithReason(nsresult status,
+ const nsACString& reason) override;
+
+ NS_IMETHOD
+ Cancel(nsresult aStatus) override;
+
+ NS_IMETHOD
+ Suspend(void) override;
+
+ NS_IMETHOD
+ Resume(void) override;
+
+ NS_IMETHOD
+ GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+
+ NS_IMETHOD
+ AsyncOpen(nsIStreamListener* aListener) override;
+
+ NS_IMETHOD
+ LogBlockedCORSRequest(const nsAString& aMessage, const nsACString& aCategory,
+ bool aIsWarning) override;
+
+ NS_IMETHOD
+ LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override;
+
+ NS_IMETHOD
+ GetIsAuthChannel(bool* aIsAuthChannel) override;
+
+ NS_IMETHOD
+ SetPriority(int32_t aPriority) override;
+
+ NS_IMETHOD
+ SetClassFlags(uint32_t aClassFlags) override;
+
+ NS_IMETHOD
+ ClearClassFlags(uint32_t flags) override;
+
+ NS_IMETHOD
+ AddClassFlags(uint32_t flags) override;
+
+ NS_IMETHOD
+ SetClassOfService(ClassOfService cos) override;
+
+ NS_IMETHOD
+ SetIncremental(bool incremental) override;
+
+ NS_IMETHOD
+ ResumeAt(uint64_t startPos, const nsACString& entityID) override;
+
+ NS_IMETHOD
+ SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) override {
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* aListener) override {
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) override;
+ NS_IMETHOD GetLaunchServiceWorkerStart(TimeStamp* aRetVal) override;
+
+ NS_IMETHOD SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) override;
+ NS_IMETHOD GetLaunchServiceWorkerEnd(TimeStamp* aRetVal) override;
+
+ NS_IMETHOD GetDispatchFetchEventStart(TimeStamp* aRetVal) override;
+ NS_IMETHOD GetDispatchFetchEventEnd(TimeStamp* aRetVal) override;
+
+ NS_IMETHOD GetHandleFetchEventStart(TimeStamp* aRetVal) override;
+ NS_IMETHOD GetHandleFetchEventEnd(TimeStamp* aRetVal) override;
+
+ void DoNotifyListenerCleanup() override;
+
+ void DoAsyncAbort(nsresult aStatus) override;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_InterceptedHttpChannel_h
diff --git a/netwerk/protocol/http/MockHttpAuth.cpp b/netwerk/protocol/http/MockHttpAuth.cpp
new file mode 100644
index 0000000000..588b30fadc
--- /dev/null
+++ b/netwerk/protocol/http/MockHttpAuth.cpp
@@ -0,0 +1,46 @@
+/* vim:set ts=4 sw=2 sts=2 et ci: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "MockHttpAuth.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(MockHttpAuth, nsIHttpAuthenticator)
+
+NS_IMETHODIMP MockHttpAuth::ChallengeReceived(
+ nsIHttpAuthenticableChannel* aChannel, const nsACString& aChallenge,
+ bool aProxyAuth, nsISupports** aSessionState,
+ nsISupports** aContinuationState, bool* aInvalidatesIdentity) {
+ *aInvalidatesIdentity = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP MockHttpAuth::GenerateCredentialsAsync(
+ nsIHttpAuthenticableChannel* aChannel,
+ nsIHttpAuthenticatorCallback* aCallback, const nsACString& aChallenge,
+ bool aProxyAuth, const nsAString& aDomain, const nsAString& aUser,
+ const nsAString& aPassword, nsISupports* aSessionState,
+ nsISupports* aContinuationState, nsICancelable** aCancel) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP MockHttpAuth::GenerateCredentials(
+ nsIHttpAuthenticableChannel* aChannel, const nsACString& aChallenge,
+ bool aProxyAuth, const nsAString& aDomain, const nsAString& aUser,
+ const nsAString& aPassword, nsISupports** aSessionState,
+ nsISupports** aContinuationState, uint32_t* aFlags, nsACString& _retval) {
+ _retval.Assign("moz_test_credentials");
+ return NS_OK;
+}
+
+NS_IMETHODIMP MockHttpAuth::GetAuthFlags(uint32_t* aAuthFlags) {
+ *aAuthFlags |= nsIHttpAuthenticator::CONNECTION_BASED;
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/MockHttpAuth.h b/netwerk/protocol/http/MockHttpAuth.h
new file mode 100644
index 0000000000..a1f737c955
--- /dev/null
+++ b/netwerk/protocol/http/MockHttpAuth.h
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MockHttpAuth_h__
+#define MockHttpAuth_h__
+
+#include "nsIHttpAuthenticator.h"
+
+namespace mozilla::net {
+
+// This authenticator is only used for testing purposes.
+class MockHttpAuth : public nsIHttpAuthenticator {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHENTICATOR
+
+ MockHttpAuth() = default;
+
+ static already_AddRefed<nsIHttpAuthenticator> Create() {
+ nsCOMPtr<nsIHttpAuthenticator> authenticator = new MockHttpAuth();
+ return authenticator.forget();
+ }
+
+ private:
+ virtual ~MockHttpAuth() = default;
+};
+
+} // namespace mozilla::net
+
+#endif // MockHttpAuth_h__
diff --git a/netwerk/protocol/http/NetworkMarker.cpp b/netwerk/protocol/http/NetworkMarker.cpp
new file mode 100644
index 0000000000..ece1845fef
--- /dev/null
+++ b/netwerk/protocol/http/NetworkMarker.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=4 sw=2 sts=2 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NetworkMarker.h"
+
+#include "HttpBaseChannel.h"
+#include "nsIChannelEventSink.h"
+
+namespace mozilla {
+namespace net {
+
+static constexpr net::TimingStruct scEmptyNetTimingStruct;
+
+void profiler_add_network_marker(
+ nsIURI* aURI, const nsACString& aRequestMethod, int32_t aPriority,
+ uint64_t aChannelId, NetworkLoadType aType, mozilla::TimeStamp aStart,
+ mozilla::TimeStamp aEnd, int64_t aCount,
+ mozilla::net::CacheDisposition aCacheDisposition, uint64_t aInnerWindowID,
+ bool aIsPrivateBrowsing, const mozilla::net::TimingStruct* aTimings,
+ UniquePtr<ProfileChunkedBuffer> aSource,
+ const Maybe<nsDependentCString>& aContentType, nsIURI* aRedirectURI,
+ uint32_t aRedirectFlags, uint64_t aRedirectChannelId) {
+ if (!profiler_thread_is_being_profiled_for_markers()) {
+ return;
+ }
+
+ nsAutoCStringN<2048> name;
+ name.AppendASCII("Load ");
+ // top 32 bits are process id of the load
+ name.AppendInt(aChannelId & 0xFFFFFFFFu);
+
+ // These can do allocations/frees/etc; avoid if not active
+ nsAutoCStringN<2048> spec;
+ if (aURI) {
+ aURI->GetAsciiSpec(spec);
+ name.AppendASCII(": ");
+ name.Append(spec);
+ }
+
+ nsAutoCString redirect_spec;
+ if (aRedirectURI) {
+ aRedirectURI->GetAsciiSpec(redirect_spec);
+ }
+
+ struct NetworkMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("Network");
+ }
+ static void StreamJSONMarkerData(
+ baseprofiler::SpliceableJSONWriter& aWriter, mozilla::TimeStamp aStart,
+ mozilla::TimeStamp aEnd, int64_t aID, const ProfilerString8View& aURI,
+ const ProfilerString8View& aRequestMethod, NetworkLoadType aType,
+ int32_t aPri, int64_t aCount, net::CacheDisposition aCacheDisposition,
+ bool aIsPrivateBrowsing, const net::TimingStruct& aTimings,
+ const ProfilerString8View& aRedirectURI,
+ const ProfilerString8View& aContentType, uint32_t aRedirectFlags,
+ int64_t aRedirectChannelId) {
+ // This payload still streams a startTime and endTime property because it
+ // made the migration to MarkerTiming on the front-end easier.
+ aWriter.TimeProperty("startTime", aStart);
+ aWriter.TimeProperty("endTime", aEnd);
+
+ aWriter.IntProperty("id", aID);
+ aWriter.StringProperty("status", GetNetworkState(aType));
+ if (Span<const char> cacheString = GetCacheState(aCacheDisposition);
+ !cacheString.IsEmpty()) {
+ aWriter.StringProperty("cache", cacheString);
+ }
+ aWriter.IntProperty("pri", aPri);
+ if (aCount > 0) {
+ aWriter.IntProperty("count", aCount);
+ }
+ if (aURI.Length() != 0) {
+ aWriter.StringProperty("URI", aURI);
+ }
+ if (aRedirectURI.Length() != 0) {
+ aWriter.StringProperty("RedirectURI", aRedirectURI);
+ aWriter.StringProperty("redirectType", getRedirectType(aRedirectFlags));
+ aWriter.BoolProperty(
+ "isHttpToHttpsRedirect",
+ aRedirectFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE);
+
+ if (aRedirectChannelId != 0) {
+ aWriter.IntProperty("redirectId", aRedirectChannelId);
+ }
+ }
+
+ aWriter.StringProperty("requestMethod", aRequestMethod);
+
+ if (aContentType.Length() != 0) {
+ aWriter.StringProperty("contentType", aContentType);
+ } else {
+ aWriter.NullProperty("contentType");
+ }
+
+ if (aIsPrivateBrowsing) {
+ aWriter.BoolProperty("isPrivateBrowsing", aIsPrivateBrowsing);
+ }
+
+ if (aType != NetworkLoadType::LOAD_START) {
+ aWriter.TimeProperty("domainLookupStart", aTimings.domainLookupStart);
+ aWriter.TimeProperty("domainLookupEnd", aTimings.domainLookupEnd);
+ aWriter.TimeProperty("connectStart", aTimings.connectStart);
+ aWriter.TimeProperty("tcpConnectEnd", aTimings.tcpConnectEnd);
+ aWriter.TimeProperty("secureConnectionStart",
+ aTimings.secureConnectionStart);
+ aWriter.TimeProperty("connectEnd", aTimings.connectEnd);
+ aWriter.TimeProperty("requestStart", aTimings.requestStart);
+ aWriter.TimeProperty("responseStart", aTimings.responseStart);
+ aWriter.TimeProperty("responseEnd", aTimings.responseEnd);
+ }
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ return MarkerSchema::SpecialFrontendLocation{};
+ }
+
+ private:
+ static Span<const char> GetNetworkState(NetworkLoadType aType) {
+ switch (aType) {
+ case NetworkLoadType::LOAD_START:
+ return MakeStringSpan("STATUS_START");
+ case NetworkLoadType::LOAD_STOP:
+ return MakeStringSpan("STATUS_STOP");
+ case NetworkLoadType::LOAD_REDIRECT:
+ return MakeStringSpan("STATUS_REDIRECT");
+ case NetworkLoadType::LOAD_CANCEL:
+ return MakeStringSpan("STATUS_CANCEL");
+ default:
+ MOZ_ASSERT(false, "Unexpected NetworkLoadType enum value.");
+ return MakeStringSpan("");
+ }
+ }
+
+ static Span<const char> GetCacheState(
+ net::CacheDisposition aCacheDisposition) {
+ switch (aCacheDisposition) {
+ case net::kCacheUnresolved:
+ return MakeStringSpan("Unresolved");
+ case net::kCacheHit:
+ return MakeStringSpan("Hit");
+ case net::kCacheHitViaReval:
+ return MakeStringSpan("HitViaReval");
+ case net::kCacheMissedViaReval:
+ return MakeStringSpan("MissedViaReval");
+ case net::kCacheMissed:
+ return MakeStringSpan("Missed");
+ case net::kCacheUnknown:
+ return MakeStringSpan("");
+ default:
+ MOZ_ASSERT(false, "Unexpected CacheDisposition enum value.");
+ return MakeStringSpan("");
+ }
+ }
+
+ static Span<const char> getRedirectType(uint32_t aRedirectFlags) {
+ MOZ_ASSERT(aRedirectFlags != 0, "aRedirectFlags should be non-zero");
+ if (aRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) {
+ return MakeStringSpan("Temporary");
+ }
+ if (aRedirectFlags & nsIChannelEventSink::REDIRECT_PERMANENT) {
+ return MakeStringSpan("Permanent");
+ }
+ if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
+ return MakeStringSpan("Internal");
+ }
+ MOZ_ASSERT(false, "Couldn't find a redirect type from aRedirectFlags");
+ return MakeStringSpan("");
+ }
+ };
+
+ profiler_add_marker(
+ name, geckoprofiler::category::NETWORK,
+ {MarkerTiming::Interval(aStart, aEnd),
+ MarkerStack::TakeBacktrace(std::move(aSource)),
+ MarkerInnerWindowId(aInnerWindowID)},
+ NetworkMarker{}, aStart, aEnd, static_cast<int64_t>(aChannelId), spec,
+ aRequestMethod, aType, aPriority, aCount, aCacheDisposition,
+ aIsPrivateBrowsing, aTimings ? *aTimings : scEmptyNetTimingStruct,
+ redirect_spec,
+ aContentType ? ProfilerString8View(*aContentType) : ProfilerString8View(),
+ aRedirectFlags, aRedirectChannelId);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/NetworkMarker.h b/netwerk/protocol/http/NetworkMarker.h
new file mode 100644
index 0000000000..cab1de5f07
--- /dev/null
+++ b/netwerk/protocol/http/NetworkMarker.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et cin ts=4 sw=2 sts=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetworkMarker_h__
+#define NetworkMarker_h__
+
+#include "mozilla/ProfilerMarkers.h"
+
+namespace mozilla {
+namespace net {
+
+struct TimingStruct;
+enum CacheDisposition : uint8_t;
+
+enum class NetworkLoadType {
+ LOAD_START,
+ LOAD_STOP,
+ LOAD_REDIRECT,
+ LOAD_CANCEL
+};
+
+void profiler_add_network_marker(
+ nsIURI* aURI, const nsACString& aRequestMethod, int32_t aPriority,
+ uint64_t aChannelId, NetworkLoadType aType, mozilla::TimeStamp aStart,
+ mozilla::TimeStamp aEnd, int64_t aCount,
+ mozilla::net::CacheDisposition aCacheDisposition, uint64_t aInnerWindowID,
+ bool aIsPrivateBrowsing,
+ const mozilla::net::TimingStruct* aTimings = nullptr,
+ mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> aSource = nullptr,
+ const mozilla::Maybe<nsDependentCString>& aContentType = mozilla::Nothing(),
+ nsIURI* aRedirectURI = nullptr, uint32_t aRedirectFlags = 0,
+ uint64_t aRedirectChannelId = 0);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // NetworkMarker_h__
diff --git a/netwerk/protocol/http/NullHttpChannel.cpp b/netwerk/protocol/http/NullHttpChannel.cpp
new file mode 100644
index 0000000000..eae11441af
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpChannel.cpp
@@ -0,0 +1,865 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NullHttpChannel.h"
+#include "nsContentUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(NullHttpChannel, nsINullChannel, nsIHttpChannel,
+ nsIIdentChannel, nsITimedChannel)
+
+NullHttpChannel::NullHttpChannel() {
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+ mAsyncOpenTime = TimeStamp::Now();
+}
+
+NullHttpChannel::NullHttpChannel(nsIHttpChannel* chan)
+ : mAllRedirectsSameOrigin(false), mAllRedirectsPassTimingAllowCheck(false) {
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ ssm->GetChannelURIPrincipal(chan, getter_AddRefs(mResourcePrincipal));
+
+ Unused << chan->GetResponseHeader("Timing-Allow-Origin"_ns,
+ mTimingAllowOriginHeader);
+ chan->GetURI(getter_AddRefs(mURI));
+ chan->GetOriginalURI(getter_AddRefs(mOriginalURI));
+
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+ mAsyncOpenTime = TimeStamp::Now();
+
+ nsCOMPtr<nsITimedChannel> timedChanel(do_QueryInterface(chan));
+ if (timedChanel) {
+ timedChanel->GetInitiatorType(mInitiatorType);
+ }
+}
+
+nsresult NullHttpChannel::Init(nsIURI* aURI, uint32_t aCaps,
+ nsProxyInfo* aProxyInfo,
+ uint32_t aProxyResolveFlags, nsIURI* aProxyURI) {
+ mURI = aURI;
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsIHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetChannelId(uint64_t* aChannelId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetChannelId(uint64_t aChannelId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetTopLevelContentWindowId(uint64_t* aWindowId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetTopLevelContentWindowId(uint64_t aWindowId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetBrowserId(uint64_t*) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::SetBrowserId(uint64_t) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::GetTransferSize(uint64_t* aTransferSize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestSize(uint64_t* aRequestSize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDecodedBodySize(uint64_t* aDecodedBodySize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestMethod(nsACString& aRequestMethod) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRequestMethod(const nsACString& aRequestMethod) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetReferrerInfoWithoutClone(nsIReferrerInfo* aReferrerInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestHeader(const nsACString& aHeader,
+ nsACString& _retval) {
+ _retval.Truncate();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetNewReferrerInfo(const nsACString& aUrl,
+ nsIReferrerInfo::ReferrerPolicyIDL aPolicy,
+ bool aSendReferrer) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetEmptyRequestHeader(const nsACString& aHeader) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::VisitRequestHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAllowSTS(bool* aAllowSTS) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetAllowSTS(bool aAllowSTS) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRedirectionLimit(uint32_t* aRedirectionLimit) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRedirectionLimit(uint32_t aRedirectionLimit) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseStatus(uint32_t* aResponseStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseStatusText(nsACString& aResponseStatusText) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestSucceeded(bool* aRequestSucceeded) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseHeader(const nsACString& header,
+ nsACString& _retval) {
+ _retval.Truncate();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetResponseHeader(const nsACString& header,
+ const nsACString& value, bool merge) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::VisitResponseHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetOriginalResponseHeader(const nsACString& header,
+ nsIHttpHeaderVisitor* aVisitor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::ShouldStripRequestBodyHeader(const nsACString& aMethod,
+ bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::IsNoStoreResponse(bool* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::IsNoCacheResponse(bool* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::IsPrivateResponse(bool* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::RedirectTo(nsIURI* aNewURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::UpgradeToSecure() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestContextID(uint64_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRequestContextID(uint64_t rcID) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetProtocolVersion(nsACString& aProtocolVersion) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void NullHttpChannel::SetSource(
+ mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> aSource) {}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetOriginalURI(nsIURI** aOriginalURI) {
+ *aOriginalURI = do_AddRef(mOriginalURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetOriginalURI(nsIURI* aOriginalURI) {
+ mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetURI(nsIURI** aURI) {
+ *aURI = do_AddRef(mURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetOwner(nsISupports** aOwner) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetOwner(nsISupports* aOwner) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentType(nsACString& aContentType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentType(const nsACString& aContentType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentCharset(nsACString& aContentCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentCharset(const nsACString& aContentCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentLength(int64_t* aContentLength) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentLength(int64_t aContentLength) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::Open(nsIInputStream** aStream) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetName(nsACString& aName) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::IsPending(bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::GetStatus(nsresult* aStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::SetCanceledReason(const nsACString& aReason) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::GetCanceledReason(nsACString& aReason) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::Cancel(nsresult aStatus) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::GetCanceled(bool* aCanceled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+NullHttpChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsITimedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetTimingEnabled(bool* aTimingEnabled) {
+ // We don't want to report timing for null channels.
+ *aTimingEnabled = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetTimingEnabled(bool aTimingEnabled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRedirectCount(uint8_t* aRedirectCount) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRedirectCount(uint8_t aRedirectCount) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetInternalRedirectCount(uint8_t* aRedirectCount) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetInternalRedirectCount(uint8_t aRedirectCount) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetChannelCreation(mozilla::TimeStamp* aChannelCreation) {
+ *aChannelCreation = mChannelCreationTimestamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetChannelCreation(TimeStamp aValue) {
+ MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
+ TimeDuration adjust = aValue - mChannelCreationTimestamp;
+ mChannelCreationTimestamp = aValue;
+ mChannelCreationTime += (PRTime)adjust.ToMicroseconds();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAsyncOpen(mozilla::TimeStamp* aAsyncOpen) {
+ *aAsyncOpen = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetAsyncOpen(TimeStamp aValue) {
+ MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
+ mAsyncOpenTime = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetLaunchServiceWorkerStart(mozilla::TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLaunchServiceWorkerStart(mozilla::TimeStamp aTimeStamp) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetLaunchServiceWorkerEnd(mozilla::TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLaunchServiceWorkerEnd(mozilla::TimeStamp aTimeStamp) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDispatchFetchEventStart(mozilla::TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetDispatchFetchEventStart(mozilla::TimeStamp aTimeStamp) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDispatchFetchEventEnd(mozilla::TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetDispatchFetchEventEnd(mozilla::TimeStamp aTimeStamp) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetHandleFetchEventStart(mozilla::TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetHandleFetchEventStart(mozilla::TimeStamp aTimeStamp) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetHandleFetchEventEnd(mozilla::TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetHandleFetchEventEnd(mozilla::TimeStamp aTimeStamp) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDomainLookupStart(mozilla::TimeStamp* aDomainLookupStart) {
+ *aDomainLookupStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDomainLookupEnd(mozilla::TimeStamp* aDomainLookupEnd) {
+ *aDomainLookupEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetConnectStart(mozilla::TimeStamp* aConnectStart) {
+ *aConnectStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetTcpConnectEnd(mozilla::TimeStamp* aTcpConnectEnd) {
+ *aTcpConnectEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetSecureConnectionStart(
+ mozilla::TimeStamp* aSecureConnectionStart) {
+ *aSecureConnectionStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetConnectEnd(mozilla::TimeStamp* aConnectEnd) {
+ *aConnectEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestStart(mozilla::TimeStamp* aRequestStart) {
+ *aRequestStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseStart(mozilla::TimeStamp* aResponseStart) {
+ *aResponseStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseEnd(mozilla::TimeStamp* aResponseEnd) {
+ *aResponseEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRedirectStart(mozilla::TimeStamp* aRedirectStart) {
+ *aRedirectStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRedirectStart(mozilla::TimeStamp aRedirectStart) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRedirectEnd(mozilla::TimeStamp* aRedirectEnd) {
+ *aRedirectEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRedirectEnd(mozilla::TimeStamp aRedirectEnd) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetInitiatorType(nsAString& aInitiatorType) {
+ aInitiatorType = mInitiatorType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetInitiatorType(const nsAString& aInitiatorType) {
+ mInitiatorType = aInitiatorType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAllRedirectsSameOrigin(bool* aAllRedirectsSameOrigin) {
+ *aAllRedirectsSameOrigin = mAllRedirectsSameOrigin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetAllRedirectsSameOrigin(bool aAllRedirectsSameOrigin) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAllRedirectsPassTimingAllowCheck(
+ bool* aAllRedirectsPassTimingAllowCheck) {
+ *aAllRedirectsPassTimingAllowCheck = mAllRedirectsPassTimingAllowCheck;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetAllRedirectsPassTimingAllowCheck(
+ bool aAllRedirectsPassTimingAllowCheck) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetCacheReadStart(mozilla::TimeStamp* aCacheReadStart) {
+ *aCacheReadStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetCacheReadEnd(mozilla::TimeStamp* aCacheReadEnd) {
+ *aCacheReadEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetTransactionPending(mozilla::TimeStamp* aRetVal) {
+ *aRetVal = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetIsMainDocumentChannel(bool* aValue) {
+ *aValue = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetIsMainDocumentChannel(bool aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategor,
+ bool aIsWarning) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetReportResourceTiming(bool enabled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetReportResourceTiming(bool* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetServerTiming(nsIArray** aServerTiming) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetNativeServerTiming(
+ nsTArray<nsCOMPtr<nsIServerTiming>>& aServerTiming) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::SetClassicScriptHintCharset(
+ const nsAString& aClassicScriptHintCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::GetClassicScriptHintCharset(
+ nsAString& aClassicScriptHintCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::SetDocumentCharacterSet(
+ const nsAString& aDocumentCharacterSet) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP NullHttpChannel::GetDocumentCharacterSet(
+ nsAString& aDocumentCharacterSet) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#define IMPL_TIMING_ATTR(name) \
+ NS_IMETHODIMP \
+ NullHttpChannel::Get##name##Time(PRTime* _retval) { \
+ TimeStamp stamp; \
+ Get##name(&stamp); \
+ if (stamp.IsNull()) { \
+ *_retval = 0; \
+ return NS_OK; \
+ } \
+ *_retval = \
+ mChannelCreationTime + \
+ (PRTime)((stamp - mChannelCreationTimestamp).ToSeconds() * 1e6); \
+ return NS_OK; \
+ }
+
+IMPL_TIMING_ATTR(ChannelCreation)
+IMPL_TIMING_ATTR(AsyncOpen)
+IMPL_TIMING_ATTR(LaunchServiceWorkerStart)
+IMPL_TIMING_ATTR(LaunchServiceWorkerEnd)
+IMPL_TIMING_ATTR(DispatchFetchEventStart)
+IMPL_TIMING_ATTR(DispatchFetchEventEnd)
+IMPL_TIMING_ATTR(HandleFetchEventStart)
+IMPL_TIMING_ATTR(HandleFetchEventEnd)
+IMPL_TIMING_ATTR(DomainLookupStart)
+IMPL_TIMING_ATTR(DomainLookupEnd)
+IMPL_TIMING_ATTR(ConnectStart)
+IMPL_TIMING_ATTR(TcpConnectEnd)
+IMPL_TIMING_ATTR(SecureConnectionStart)
+IMPL_TIMING_ATTR(ConnectEnd)
+IMPL_TIMING_ATTR(RequestStart)
+IMPL_TIMING_ATTR(ResponseStart)
+IMPL_TIMING_ATTR(ResponseEnd)
+IMPL_TIMING_ATTR(CacheReadStart)
+IMPL_TIMING_ATTR(CacheReadEnd)
+IMPL_TIMING_ATTR(RedirectStart)
+IMPL_TIMING_ATTR(RedirectEnd)
+IMPL_TIMING_ATTR(TransactionPending)
+
+#undef IMPL_TIMING_ATTR
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/NullHttpChannel.h b/netwerk/protocol/http/NullHttpChannel.h
new file mode 100644
index 0000000000..478d13e0a2
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpChannel.h
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_NullHttpChannel_h
+#define mozilla_net_NullHttpChannel_h
+
+#include "nsINullChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsITimedChannel.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsString.h"
+#include "prtime.h"
+
+namespace mozilla {
+namespace net {
+
+class nsProxyInfo;
+
+class NullHttpChannel final : public nsINullChannel,
+ public nsIHttpChannel,
+ public nsITimedChannel {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINULLCHANNEL
+ NS_DECL_NSIHTTPCHANNEL
+ NS_DECL_NSITIMEDCHANNEL
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIIDENTCHANNEL
+
+ NullHttpChannel();
+
+ // Copies the URI, Principal and Timing-Allow-Origin headers from the
+ // passed channel to this object, to be used for resource timing checks
+ explicit NullHttpChannel(nsIHttpChannel* chan);
+
+ // Same signature as nsHttpChannel::Init
+ [[nodiscard]] nsresult Init(nsIURI* aURI, uint32_t aCaps,
+ nsProxyInfo* aProxyInfo,
+ uint32_t aProxyResolveFlags, nsIURI* aProxyURI);
+
+ private:
+ ~NullHttpChannel() = default;
+
+ protected:
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+
+ nsString mInitiatorType;
+ PRTime mChannelCreationTime;
+ TimeStamp mAsyncOpenTime;
+ TimeStamp mChannelCreationTimestamp;
+ nsCOMPtr<nsIPrincipal> mResourcePrincipal;
+ nsCString mTimingAllowOriginHeader;
+ bool mAllRedirectsSameOrigin{false};
+ bool mAllRedirectsPassTimingAllowCheck{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_NullHttpChannel_h
diff --git a/netwerk/protocol/http/NullHttpTransaction.cpp b/netwerk/protocol/http/NullHttpTransaction.cpp
new file mode 100644
index 0000000000..f27eb16138
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpTransaction.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/net/NeckoChannelParams.h" // For HttpActivityArgs.
+#include "nsHttp.h"
+#include "NullHttpTransaction.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsQueryObject.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(NullHttpTransaction, NullHttpTransaction,
+ nsISupportsWeakReference)
+
+NullHttpTransaction::NullHttpTransaction(nsHttpConnectionInfo* ci,
+ nsIInterfaceRequestor* callbacks,
+ uint32_t caps)
+ : mStatus(NS_OK),
+ mCaps(caps | NS_HTTP_ALLOW_KEEPALIVE),
+ mRequestHead(nullptr),
+ mIsDone(false),
+ mClaimed(false),
+ mCallbacks(callbacks),
+ mConnectionInfo(ci) {
+ nsresult rv;
+ mActivityDistributor =
+ do_GetService(NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ bool activityDistributorActive;
+ rv = mActivityDistributor->GetIsActive(&activityDistributorActive);
+ if (NS_SUCCEEDED(rv) && activityDistributorActive) {
+ // There are some observers registered at activity distributor.
+ LOG(
+ ("NulHttpTransaction::NullHttpTransaction() "
+ "mActivityDistributor is active "
+ "[this=%p, %s]",
+ this, ci->GetOrigin().get()));
+ } else {
+ // There is no observer, so don't use it.
+ mActivityDistributor = nullptr;
+ }
+}
+
+NullHttpTransaction::~NullHttpTransaction() {
+ mCallbacks = nullptr;
+ delete mRequestHead;
+}
+
+bool NullHttpTransaction::Claim() {
+ if (mClaimed) {
+ return false;
+ }
+ mClaimed = true;
+ return true;
+}
+
+void NullHttpTransaction::Unclaim() { mClaimed = false; }
+
+void NullHttpTransaction::SetConnection(nsAHttpConnection* conn) {
+ mConnection = conn;
+}
+
+nsAHttpConnection* NullHttpTransaction::Connection() {
+ return mConnection.get();
+}
+
+void NullHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor** outCB) {
+ nsCOMPtr<nsIInterfaceRequestor> copyCB(mCallbacks);
+ *outCB = copyCB.forget().take();
+}
+
+void NullHttpTransaction::OnTransportStatus(nsITransport* transport,
+ nsresult status, int64_t progress) {
+ if (status == NS_NET_STATUS_RESOLVING_HOST) {
+ if (mTimings.domainLookupStart.IsNull()) {
+ mTimings.domainLookupStart = TimeStamp::Now();
+ }
+ } else if (status == NS_NET_STATUS_RESOLVED_HOST) {
+ if (mTimings.domainLookupEnd.IsNull()) {
+ mTimings.domainLookupEnd = TimeStamp::Now();
+ }
+ } else if (status == NS_NET_STATUS_CONNECTING_TO) {
+ if (mTimings.connectStart.IsNull()) {
+ mTimings.connectStart = TimeStamp::Now();
+ }
+ } else if (status == NS_NET_STATUS_CONNECTED_TO) {
+ TimeStamp tnow = TimeStamp::Now();
+ if (mTimings.connectEnd.IsNull()) {
+ mTimings.connectEnd = tnow;
+ }
+ if (mTimings.tcpConnectEnd.IsNull()) {
+ mTimings.tcpConnectEnd = tnow;
+ }
+ } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_STARTING) {
+ if (mTimings.secureConnectionStart.IsNull()) {
+ mTimings.secureConnectionStart = TimeStamp::Now();
+ }
+ } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
+ mTimings.connectEnd = TimeStamp::Now();
+ ;
+ }
+
+ if (mActivityDistributor) {
+ Unused << mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivity(mConnectionInfo->GetOrigin(),
+ mConnectionInfo->OriginPort(),
+ mConnectionInfo->EndToEndSSL()),
+ NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT, static_cast<uint32_t>(status),
+ PR_Now(), progress, ""_ns);
+ }
+}
+
+bool NullHttpTransaction::IsDone() { return mIsDone; }
+
+nsresult NullHttpTransaction::Status() { return mStatus; }
+
+uint32_t NullHttpTransaction::Caps() { return mCaps; }
+
+nsresult NullHttpTransaction::ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count,
+ uint32_t* countRead) {
+ *countRead = 0;
+ mIsDone = true;
+ return NS_BASE_STREAM_CLOSED;
+}
+
+nsresult NullHttpTransaction::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ *countWritten = 0;
+ return NS_BASE_STREAM_CLOSED;
+}
+
+uint32_t NullHttpTransaction::Http1xTransactionCount() { return 0; }
+
+nsHttpRequestHead* NullHttpTransaction::RequestHead() {
+ // We suport a requesthead at all so that a CONNECT tunnel transaction
+ // can obtain a Host header from it, but we lazy-popualate that header.
+
+ if (!mRequestHead) {
+ mRequestHead = new nsHttpRequestHead();
+
+ nsAutoCString hostHeader;
+ nsCString host(mConnectionInfo->GetOrigin());
+ nsresult rv = nsHttpHandler::GenerateHostPort(
+ host, mConnectionInfo->OriginPort(), hostHeader);
+ if (NS_SUCCEEDED(rv)) {
+ rv = mRequestHead->SetHeader(nsHttp::Host, hostHeader);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (mActivityDistributor) {
+ // Report request headers.
+ nsCString reqHeaderBuf;
+ mRequestHead->Flatten(reqHeaderBuf, false);
+ Unused << mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivity(mConnectionInfo->GetOrigin(),
+ mConnectionInfo->OriginPort(),
+ mConnectionInfo->EndToEndSSL()),
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER, PR_Now(), 0, reqHeaderBuf);
+ }
+ }
+
+ // CONNECT tunnels may also want Proxy-Authorization but that is a lot
+ // harder to determine, so for now we will let those connections fail in
+ // the NullHttpTransaction and let them be retried from the pending queue
+ // with a bound transaction
+ }
+
+ return mRequestHead;
+}
+
+nsresult NullHttpTransaction::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> >& outTransactions) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void NullHttpTransaction::SetProxyConnectFailed() {}
+
+void NullHttpTransaction::Close(nsresult reason) {
+ mStatus = reason;
+ mConnection = nullptr;
+ mIsDone = true;
+ if (mActivityDistributor) {
+ // Report that this transaction is closing.
+ Unused << mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivity(mConnectionInfo->GetOrigin(),
+ mConnectionInfo->OriginPort(),
+ mConnectionInfo->EndToEndSSL()),
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE, PR_Now(), 0, ""_ns);
+ }
+}
+
+nsHttpConnectionInfo* NullHttpTransaction::ConnectionInfo() {
+ return mConnectionInfo;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/NullHttpTransaction.h b/netwerk/protocol/http/NullHttpTransaction.h
new file mode 100644
index 0000000000..cedbc6fd63
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpTransaction.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_NullHttpTransaction_h
+#define mozilla_net_NullHttpTransaction_h
+
+#include "nsAHttpTransaction.h"
+#include "mozilla/Attributes.h"
+#include "TimingStruct.h"
+
+// This is the minimal nsAHttpTransaction implementation. A NullHttpTransaction
+// can be used to drive connection level semantics (such as SSL handshakes
+// tunnels) so that a nsHttpConnection becomes fully established in
+// anticipation of a real transaction needing to use it soon.
+
+class nsIHttpActivityObserver;
+
+namespace mozilla {
+namespace net {
+
+class nsAHttpConnection;
+class nsHttpConnectionInfo;
+class nsHttpRequestHead;
+
+// 6c445340-3b82-4345-8efa-4902c3b8805a
+#define NS_NULLHTTPTRANSACTION_IID \
+ { \
+ 0x6c445340, 0x3b82, 0x4345, { \
+ 0x8e, 0xfa, 0x49, 0x02, 0xc3, 0xb8, 0x80, 0x5a \
+ } \
+ }
+
+class NullHttpTransaction : public nsAHttpTransaction {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_NULLHTTPTRANSACTION_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+
+ NullHttpTransaction(nsHttpConnectionInfo* ci,
+ nsIInterfaceRequestor* callbacks, uint32_t caps);
+
+ [[nodiscard]] bool Claim();
+ void Unclaim();
+
+ // Overload of nsAHttpTransaction methods
+ bool IsNullTransaction() final { return true; }
+ NullHttpTransaction* QueryNullTransaction() final { return this; }
+ bool ResponseTimeoutEnabled() const final { return true; }
+ PRIntervalTime ResponseTimeout() final { return PR_SecondsToInterval(15); }
+
+ // We have to override this function because |mTransaction| in
+ // nsHalfOpenSocket could be either nsHttpTransaction or NullHttpTransaction.
+ // NullHttpTransaction will be activated on the connection immediately after
+ // creation and be never put in a pending queue, so it's OK to just return 0.
+ uint64_t BrowserId() override { return 0; }
+
+ TimingStruct Timings() { return mTimings; }
+
+ mozilla::TimeStamp GetTcpConnectEnd() { return mTimings.tcpConnectEnd; }
+ mozilla::TimeStamp GetSecureConnectionStart() {
+ return mTimings.secureConnectionStart;
+ }
+
+ protected:
+ virtual ~NullHttpTransaction();
+
+ private:
+ nsresult mStatus;
+
+ protected:
+ uint32_t mCaps;
+ nsHttpRequestHead* mRequestHead;
+
+ private:
+ bool mIsDone;
+ bool mClaimed;
+ TimingStruct mTimings;
+
+ protected:
+ RefPtr<nsAHttpConnection> mConnection;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ RefPtr<nsHttpConnectionInfo> mConnectionInfo;
+ nsCOMPtr<nsIHttpActivityObserver> mActivityDistributor;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(NullHttpTransaction, NS_NULLHTTPTRANSACTION_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_NullHttpTransaction_h
diff --git a/netwerk/protocol/http/ObliviousHttpChannel.cpp b/netwerk/protocol/http/ObliviousHttpChannel.cpp
new file mode 100644
index 0000000000..fafbf84b0b
--- /dev/null
+++ b/netwerk/protocol/http/ObliviousHttpChannel.cpp
@@ -0,0 +1,860 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "ObliviousHttpChannel.h"
+
+#include "BinaryHttpRequest.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsStringStream.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(ObliviousHttpChannel, nsIChannel, nsIHttpChannel,
+ nsIObliviousHttpChannel, nsIHttpChannelInternal,
+ nsIIdentChannel, nsIRequest, nsIRequestObserver,
+ nsIStreamListener, nsIUploadChannel2, nsITimedChannel)
+
+ObliviousHttpChannel::ObliviousHttpChannel(
+ nsIURI* targetURI, const nsTArray<uint8_t>& encodedConfig,
+ nsIHttpChannel* innerChannel)
+ : mTargetURI(targetURI),
+ mEncodedConfig(encodedConfig.Clone()),
+ mInnerChannel(innerChannel),
+ mInnerChannelInternal(do_QueryInterface(innerChannel)),
+ mInnerChannelTimed(do_QueryInterface(innerChannel)) {
+ LOG(("ObliviousHttpChannel ctor [this=%p]", this));
+ MOZ_ASSERT(mInnerChannel);
+ MOZ_ASSERT(mInnerChannelInternal);
+ MOZ_ASSERT(mInnerChannelTimed);
+}
+
+ObliviousHttpChannel::~ObliviousHttpChannel() {
+ LOG(("ObliviousHttpChannel dtor [this=%p]", this));
+}
+
+//-----------------------------------------------------------------------------
+// ObliviousHttpChannel::nsIHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetTopLevelContentWindowId(uint64_t* aWindowId) {
+ return mInnerChannel->GetTopLevelContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetTopLevelContentWindowId(uint64_t aWindowId) {
+ return mInnerChannel->SetTopLevelContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetBrowserId(uint64_t* aWindowId) {
+ return mInnerChannel->GetBrowserId(aWindowId);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetBrowserId(uint64_t aId) {
+ return mInnerChannel->SetBrowserId(aId);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetTransferSize(uint64_t* aTransferSize) {
+ return mInnerChannel->GetTransferSize(aTransferSize);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRequestSize(uint64_t* aRequestSize) {
+ return mInnerChannel->GetRequestSize(aRequestSize);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetDecodedBodySize(uint64_t* aDecodedBodySize) {
+ return mInnerChannel->GetDecodedBodySize(aDecodedBodySize);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRequestMethod(nsACString& aRequestMethod) {
+ aRequestMethod.Assign(mMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetRequestMethod(const nsACString& aRequestMethod) {
+ mMethod.Assign(aRequestMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ return mInnerChannel->GetReferrerInfo(aReferrerInfo);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ return mInnerChannel->SetReferrerInfo(aReferrerInfo);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetReferrerInfoWithoutClone(
+ nsIReferrerInfo* aReferrerInfo) {
+ return mInnerChannel->SetReferrerInfoWithoutClone(aReferrerInfo);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRequestHeader(const nsACString& aHeader,
+ nsACString& _retval) {
+ _retval.Truncate();
+ auto value = mHeaders.Lookup(aHeader);
+ if (!value) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ _retval.Assign(*value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) {
+ mHeaders.WithEntryHandle(aHeader, [&aValue, aMerge](auto&& entry) {
+ if (!entry) {
+ entry.Insert(aValue);
+ return;
+ }
+ if (!aMerge) {
+ entry.Update(aValue);
+ return;
+ }
+ nsAutoCString newValue(*entry);
+ newValue.AppendLiteral(", ");
+ newValue.Append(aValue);
+ entry.Update(newValue);
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetNewReferrerInfo(
+ const nsACString& aUrl, nsIReferrerInfo::ReferrerPolicyIDL aPolicy,
+ bool aSendReferrer) {
+ return mInnerChannel->SetNewReferrerInfo(aUrl, aPolicy, aSendReferrer);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetEmptyRequestHeader(const nsACString& aHeader) {
+ return SetRequestHeader(aHeader, EmptyCString(), false);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::VisitRequestHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ for (auto iter = mHeaders.ConstIter(); !iter.Done(); iter.Next()) {
+ nsresult rv = aVisitor->VisitHeader(iter.Key(), iter.Data());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::VisitNonDefaultRequestHeaders(
+ nsIHttpHeaderVisitor* aVisitor) {
+ return mInnerChannel->VisitNonDefaultRequestHeaders(aVisitor);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetAllowSTS(bool* aAllowSTS) {
+ return mInnerChannel->GetAllowSTS(aAllowSTS);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetAllowSTS(bool aAllowSTS) {
+ return mInnerChannel->SetAllowSTS(aAllowSTS);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRedirectionLimit(uint32_t* aRedirectionLimit) {
+ return mInnerChannel->GetRedirectionLimit(aRedirectionLimit);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetRedirectionLimit(uint32_t aRedirectionLimit) {
+ return mInnerChannel->SetRedirectionLimit(aRedirectionLimit);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetResponseStatus(uint32_t* aResponseStatus) {
+ if (!mBinaryHttpResponse) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ uint16_t responseStatus;
+ nsresult rv = mBinaryHttpResponse->GetStatus(&responseStatus);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aResponseStatus = responseStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetResponseStatusText(nsACString& aResponseStatusText) {
+ LOG(("ObliviousHttpChannel::GetResponseStatusText NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRequestSucceeded(bool* aRequestSucceeded) {
+ uint32_t responseStatus;
+ nsresult rv = GetResponseStatus(&responseStatus);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aRequestSucceeded = (responseStatus / 100) == 2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetIsMainDocumentChannel(bool* aValue) {
+ return mInnerChannel->GetIsMainDocumentChannel(aValue);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetIsMainDocumentChannel(bool aValue) {
+ return mInnerChannel->SetIsMainDocumentChannel(aValue);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetResponseHeader(const nsACString& header,
+ nsACString& _retval) {
+ if (!mBinaryHttpResponse) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsTArray<nsCString> responseHeaderNames;
+ nsTArray<nsCString> responseHeaderValues;
+ nsresult rv = mBinaryHttpResponse->GetHeaderNames(responseHeaderNames);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = mBinaryHttpResponse->GetHeaderValues(responseHeaderValues);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ for (size_t i = 0;
+ i < responseHeaderNames.Length() && i < responseHeaderValues.Length();
+ i++) {
+ if (responseHeaderNames[i].EqualsIgnoreCase(header)) {
+ _retval.Assign(responseHeaderValues[i]);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetResponseHeader(const nsACString& header,
+ const nsACString& value, bool merge) {
+ LOG(("ObliviousHttpChannel::SetResponseHeader NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::VisitResponseHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ if (!mBinaryHttpResponse) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsTArray<nsCString> responseHeaderNames;
+ nsTArray<nsCString> responseHeaderValues;
+ nsresult rv = mBinaryHttpResponse->GetHeaderNames(responseHeaderNames);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = mBinaryHttpResponse->GetHeaderValues(responseHeaderValues);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ for (size_t i = 0;
+ i < responseHeaderNames.Length() && i < responseHeaderValues.Length();
+ i++) {
+ rv = aVisitor->VisitHeader(responseHeaderNames[i], responseHeaderValues[i]);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetOriginalResponseHeader(
+ const nsACString& header, nsIHttpHeaderVisitor* aVisitor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::VisitOriginalResponseHeaders(
+ nsIHttpHeaderVisitor* aVisitor) {
+ LOG(
+ ("ObliviousHttpChannel::VisitOriginalResponseHeaders NOT IMPLEMENTED "
+ "[this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::ShouldStripRequestBodyHeader(const nsACString& aMethod,
+ bool* aResult) {
+ LOG(
+ ("ObliviousHttpChannel::ShouldStripRequestBodyHeader NOT IMPLEMENTED "
+ "[this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::IsNoStoreResponse(bool* _retval) {
+ LOG(("ObliviousHttpChannel::IsNoStoreResponse NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::IsNoCacheResponse(bool* _retval) {
+ LOG(("ObliviousHttpChannel::IsNoCacheResponse NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::IsPrivateResponse(bool* _retval) {
+ LOG(("ObliviousHttpChannel::IsPrivateResponse NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::RedirectTo(nsIURI* aNewURI) {
+ LOG(("ObliviousHttpChannel::RedirectTo NOT IMPLEMENTED [this=%p]", this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::UpgradeToSecure() {
+ LOG(("ObliviousHttpChannel::UpgradeToSecure NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRequestContextID(uint64_t* _retval) {
+ return mInnerChannel->GetRequestContextID(_retval);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetRequestContextID(uint64_t rcID) {
+ return mInnerChannel->SetRequestContextID(rcID);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetProtocolVersion(nsACString& aProtocolVersion) {
+ return mInnerChannel->GetProtocolVersion(aProtocolVersion);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) {
+ LOG(("ObliviousHttpChannel::GetEncodedBodySize NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ return mInnerChannel->LogBlockedCORSRequest(aMessage, aCategory, aIsWarning);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ return mInnerChannel->LogMimeTypeMismatch(aMessageName, aWarning, aURL,
+ aContentType);
+}
+
+void ObliviousHttpChannel::SetSource(
+ mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> aSource) {
+ LOG(("ObliviousHttpChannel::SetSource NOT IMPLEMENTED [this=%p]", this));
+ // NS_ERROR_NOT_IMPLEMENTED
+}
+
+void ObliviousHttpChannel::SetConnectionInfo(nsHttpConnectionInfo* aCi) {
+ if (mInnerChannelInternal) {
+ mInnerChannelInternal->SetConnectionInfo(aCi);
+ }
+}
+
+void ObliviousHttpChannel::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {
+ if (mInnerChannelInternal) {
+ mInnerChannelInternal->DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();
+ }
+}
+
+void ObliviousHttpChannel::DisableAltDataCache() {
+ if (mInnerChannelInternal) {
+ mInnerChannelInternal->DisableAltDataCache();
+ }
+}
+
+void ObliviousHttpChannel::SetAltDataForChild(bool aIsForChild) {
+ if (mInnerChannelInternal) {
+ mInnerChannelInternal->SetAltDataForChild(aIsForChild);
+ }
+}
+
+void ObliviousHttpChannel::SetCorsPreflightParameters(
+ nsTArray<nsTString<char>> const& aUnsafeHeaders,
+ bool aShouldStripRequestBodyHeader) {
+ if (mInnerChannelInternal) {
+ mInnerChannelInternal->SetCorsPreflightParameters(
+ aUnsafeHeaders, aShouldStripRequestBodyHeader);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// ObliviousHttpChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetOriginalURI(nsIURI** aOriginalURI) {
+ return mInnerChannel->GetOriginalURI(aOriginalURI);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetOriginalURI(nsIURI* aOriginalURI) {
+ return mInnerChannel->SetOriginalURI(aOriginalURI);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetURI(nsIURI** aURI) {
+ *aURI = do_AddRef(mTargetURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetOwner(nsISupports** aOwner) {
+ return mInnerChannel->GetOwner(aOwner);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetOwner(nsISupports* aOwner) {
+ return mInnerChannel->SetOwner(aOwner);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) {
+ return mInnerChannel->GetNotificationCallbacks(aNotificationCallbacks);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) {
+ return mInnerChannel->SetNotificationCallbacks(aNotificationCallbacks);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ return mInnerChannel->GetSecurityInfo(aSecurityInfo);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetContentType(nsACString& aContentType) {
+ return GetResponseHeader("content-type"_ns, aContentType);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetContentType(const nsACString& aContentType) {
+ mContentType = aContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetContentCharset(nsACString& aContentCharset) {
+ return mInnerChannel->GetContentCharset(aContentCharset);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetContentCharset(const nsACString& aContentCharset) {
+ return mInnerChannel->SetContentCharset(aContentCharset);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetContentLength(int64_t* aContentLength) {
+ return mInnerChannel->GetContentLength(aContentLength);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetContentLength(int64_t aContentLength) {
+ return mInnerChannel->SetContentLength(aContentLength);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::Open(nsIInputStream** aStream) {
+ LOG(("ObliviousHttpChannel::Open NOT IMPLEMENTED [this=%p]", this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::AsyncOpen(nsIStreamListener* aListener) {
+ LOG(("ObliviousHttpChannel::AsyncOpen [this=%p, listener=%p]", this,
+ aListener));
+ mStreamListener = aListener;
+ nsresult rv = mInnerChannel->SetRequestMethod("POST"_ns);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = mInnerChannel->SetRequestHeader("Content-Type"_ns,
+ "message/ohttp-req"_ns, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsAutoCString scheme;
+ if (!mTargetURI) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ rv = mTargetURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsAutoCString authority;
+ rv = mTargetURI->GetHostPort(authority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsAutoCString path;
+ rv = mTargetURI->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsTArray<nsCString> headerNames;
+ nsTArray<nsCString> headerValues;
+ for (auto iter = mHeaders.ConstIter(); !iter.Done(); iter.Next()) {
+ headerNames.AppendElement(iter.Key());
+ headerValues.AppendElement(iter.Data());
+ }
+ if (!mContentType.IsEmpty() && !headerNames.Contains("Content-Type")) {
+ headerNames.AppendElement("Content-Type"_ns);
+ headerValues.AppendElement(mContentType);
+ }
+ nsCOMPtr<nsIBinaryHttp> bhttp(
+ do_GetService("@mozilla.org/network/binary-http;1"));
+ nsCOMPtr<nsIBinaryHttpRequest> bhttpRequest(new BinaryHttpRequest(
+ mMethod, scheme, authority, path, std::move(headerNames),
+ std::move(headerValues), std::move(mContent)));
+ nsTArray<uint8_t> encodedRequest;
+ rv = bhttp->EncodeRequest(bhttpRequest, encodedRequest);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsIObliviousHttp> obliviousHttp(
+ do_GetService("@mozilla.org/network/oblivious-http;1"));
+ if (!obliviousHttp) {
+ return NS_ERROR_FAILURE;
+ }
+ rv = obliviousHttp->EncapsulateRequest(mEncodedConfig, encodedRequest,
+ getter_AddRefs(mEncapsulatedRequest));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsTArray<uint8_t> encRequest;
+ rv = mEncapsulatedRequest->GetEncRequest(encRequest);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsIUploadChannel2> uploadChannel(do_QueryInterface(mInnerChannel));
+ if (!uploadChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsCOMPtr<nsIInputStream> uploadStream;
+ uint32_t streamLength = encRequest.Length();
+ rv = NS_NewByteInputStream(getter_AddRefs(uploadStream),
+ std::move(encRequest));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = uploadChannel->ExplicitSetUploadStream(
+ uploadStream, "message/ohttp-req"_ns, streamLength, "POST"_ns, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return mInnerChannel->AsyncOpen(this);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetCanceled(bool* aCanceled) {
+ return mInnerChannel->GetCanceled(aCanceled);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ return mInnerChannel->GetContentDisposition(aContentDisposition);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ return mInnerChannel->SetContentDisposition(aContentDisposition);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ return mInnerChannel->GetContentDispositionFilename(
+ aContentDispositionFilename);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ return mInnerChannel->SetContentDispositionFilename(
+ aContentDispositionFilename);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ return mInnerChannel->GetContentDispositionHeader(aContentDispositionHeader);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ return mInnerChannel->GetLoadInfo(aLoadInfo);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ return mInnerChannel->SetLoadInfo(aLoadInfo);
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetIsDocument(bool* aIsDocument) {
+ return mInnerChannel->GetIsDocument(aIsDocument);
+}
+
+//-----------------------------------------------------------------------------
+// ObliviousHttpChannel::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ObliviousHttpChannel::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream, uint64_t aOffset,
+ uint32_t aCount) {
+ LOG(
+ ("ObliviousHttpChannel::OnDataAvailable [this=%p, request=%p, stream=%p, "
+ "offset=%" PRIu64 ", count=%u]",
+ this, aRequest, aStream, aOffset, aCount));
+ MOZ_ASSERT(aOffset == mRawResponse.Length());
+ size_t oldLength = mRawResponse.Length();
+ size_t newLength = oldLength + aCount;
+ if (newLength < oldLength) { // i.e., overflow
+ return NS_ERROR_FAILURE;
+ }
+ mRawResponse.SetCapacity(newLength);
+ mRawResponse.SetLengthAndRetainStorage(newLength);
+ void* dest = mRawResponse.Elements() + oldLength;
+ uint64_t written = 0;
+ nsresult rv = NS_ReadInputStreamToBuffer(aStream, &dest, aCount, &written);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (written != aCount) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ObliviousHttpChannel::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ObliviousHttpChannel::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("ObliviousHttpChannel::OnStartRequest [this=%p, request=%p]", this,
+ aRequest));
+ return NS_OK;
+}
+
+nsresult ObliviousHttpChannel::ProcessOnStopRequest() {
+ if (mRawResponse.IsEmpty()) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIObliviousHttp> obliviousHttp(
+ do_GetService("@mozilla.org/network/oblivious-http;1"));
+ if (!obliviousHttp) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIObliviousHttpClientResponse> response;
+ nsresult rv = mEncapsulatedRequest->GetResponse(getter_AddRefs(response));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsTArray<uint8_t> decapsulated;
+ rv = response->Decapsulate(mRawResponse, decapsulated);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsIBinaryHttp> bhttp(
+ do_GetService("@mozilla.org/network/binary-http;1"));
+ if (!bhttp) {
+ return NS_ERROR_FAILURE;
+ }
+ return bhttp->DecodeResponse(decapsulated,
+ getter_AddRefs(mBinaryHttpResponse));
+}
+
+void ObliviousHttpChannel::EmitOnDataAvailable() {
+ if (!mBinaryHttpResponse) {
+ return;
+ }
+ nsTArray<uint8_t> content;
+ nsresult rv = mBinaryHttpResponse->GetContent(content);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ if (content.IsEmpty()) {
+ return;
+ }
+ if (content.Length() > std::numeric_limits<uint32_t>::max()) {
+ return;
+ }
+ uint32_t contentLength = (uint32_t)content.Length();
+ nsCOMPtr<nsIInputStream> contentStream;
+ rv = NS_NewByteInputStream(getter_AddRefs(contentStream), std::move(content));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ rv = mStreamListener->OnDataAvailable(this, contentStream, 0, contentLength);
+ Unused << rv;
+}
+
+NS_IMETHODIMP
+ObliviousHttpChannel::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ LOG(("ObliviousHttpChannel::OnStopRequest [this=%p, request=%p, status=%u]",
+ this, aRequest, (uint32_t)aStatusCode));
+
+ auto releaseStreamListener = MakeScopeExit(
+ [self = RefPtr{this}]() mutable { self->mStreamListener = nullptr; });
+
+ if (NS_SUCCEEDED(aStatusCode)) {
+ bool requestSucceeded;
+ nsresult rv = mInnerChannel->GetRequestSucceeded(&requestSucceeded);
+ if (NS_SUCCEEDED(rv) && requestSucceeded) {
+ aStatusCode = ProcessOnStopRequest();
+ }
+ }
+ Unused << mStreamListener->OnStartRequest(this);
+ if (NS_SUCCEEDED(aStatusCode)) {
+ EmitOnDataAvailable();
+ }
+ Unused << mStreamListener->OnStopRequest(this, aStatusCode);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ObliviousHttpChannel::nsIObliviousHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ObliviousHttpChannel::GetRelayChannel(nsIHttpChannel** aChannel) {
+ if (!aChannel) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aChannel = do_AddRef(mInnerChannel).take();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ObliviousHttpChannel::nsIUploadChannel2
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP ObliviousHttpChannel::ExplicitSetUploadStream(
+ nsIInputStream* aStream, const nsACString& aContentType,
+ int64_t aContentLength, const nsACString& aMethod, bool aStreamHasHeaders) {
+ // This function should only be called before AsyncOpen.
+ if (mStreamListener) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ if (aMethod != "POST"_ns || aStreamHasHeaders) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ mMethod.Assign(aMethod);
+ uint64_t available;
+ if (aContentLength < 0) {
+ nsresult rv = aStream->Available(&available);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ available = aContentLength;
+ }
+ if (available > std::numeric_limits<int64_t>::max()) {
+ return NS_ERROR_FAILURE;
+ }
+ mContent.SetCapacity(available);
+ mContent.SetLengthAndRetainStorage(available);
+ void* dest = mContent.Elements();
+ uint64_t written = 0;
+ nsresult rv =
+ NS_ReadInputStreamToBuffer(aStream, &dest, (int64_t)available, &written);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (written != available) {
+ return NS_ERROR_FAILURE;
+ }
+ mContentType = aContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ObliviousHttpChannel::GetUploadStreamHasHeaders(
+ bool* aUploadStreamHasHeaders) {
+ *aUploadStreamHasHeaders = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ObliviousHttpChannel::CloneUploadStream(
+ int64_t* aContentLength, nsIInputStream** _retval) {
+ LOG(("ObliviousHttpChannel::CloneUploadStream NOT IMPLEMENTED [this=%p]",
+ this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ObliviousHttpChannel::SetClassicScriptHintCharset(
+ const nsAString& aClassicScriptHintCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ObliviousHttpChannel::GetClassicScriptHintCharset(
+ nsAString& aClassicScriptHintCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ObliviousHttpChannel::SetDocumentCharacterSet(
+ const nsAString& aDocumentCharacterSet) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ObliviousHttpChannel::GetDocumentCharacterSet(
+ nsAString& aDocumenharacterSet) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/ObliviousHttpChannel.h b/netwerk/protocol/http/ObliviousHttpChannel.h
new file mode 100644
index 0000000000..9dc9c9f689
--- /dev/null
+++ b/netwerk/protocol/http/ObliviousHttpChannel.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ObliviousHttpChannel_h
+#define mozilla_net_ObliviousHttpChannel_h
+
+#include "nsIBinaryHttp.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIObliviousHttp.h"
+#include "nsIObliviousHttpChannel.h"
+#include "nsIStreamListener.h"
+#include "nsITimedChannel.h"
+#include "nsIUploadChannel2.h"
+#include "nsTHashMap.h"
+
+namespace mozilla::net {
+
+class ObliviousHttpChannel final : public nsIObliviousHttpChannel,
+ public nsIHttpChannelInternal,
+ public nsIStreamListener,
+ public nsIUploadChannel2,
+ public nsITimedChannel {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIHTTPCHANNEL
+ NS_DECL_NSIOBLIVIOUSHTTPCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIUPLOADCHANNEL2
+
+ ObliviousHttpChannel(nsIURI* targetURI,
+ const nsTArray<uint8_t>& encodedConfig,
+ nsIHttpChannel* innerChannel);
+
+ NS_FORWARD_SAFE_NSIREQUEST(mInnerChannel)
+ NS_FORWARD_SAFE_NSIIDENTCHANNEL(mInnerChannel)
+ NS_FORWARD_SAFE_NSIHTTPCHANNELINTERNAL(mInnerChannelInternal)
+ NS_FORWARD_SAFE_NSITIMEDCHANNEL(mInnerChannelTimed)
+
+ protected:
+ ~ObliviousHttpChannel();
+
+ nsresult ProcessOnStopRequest();
+ void EmitOnDataAvailable();
+
+ nsCOMPtr<nsIURI> mTargetURI;
+ nsTArray<uint8_t> mEncodedConfig;
+
+ nsCString mMethod{"GET"_ns};
+ nsCString mContentType;
+ nsTHashMap<nsCStringHashKey, nsCString> mHeaders;
+ nsTArray<uint8_t> mContent;
+
+ nsCOMPtr<nsIHttpChannel> mInnerChannel;
+ nsCOMPtr<nsIHttpChannelInternal> mInnerChannelInternal;
+ nsCOMPtr<nsITimedChannel> mInnerChannelTimed;
+ nsCOMPtr<nsIObliviousHttpClientRequest> mEncapsulatedRequest;
+ nsTArray<uint8_t> mRawResponse;
+ nsCOMPtr<nsIBinaryHttpResponse> mBinaryHttpResponse;
+
+ nsCOMPtr<nsIStreamListener> mStreamListener;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_ObliviousHttpChannel_h
diff --git a/netwerk/protocol/http/ObliviousHttpService.cpp b/netwerk/protocol/http/ObliviousHttpService.cpp
new file mode 100644
index 0000000000..f1a1cc7b88
--- /dev/null
+++ b/netwerk/protocol/http/ObliviousHttpService.cpp
@@ -0,0 +1,235 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ObliviousHttpService.h"
+
+#include "DNSUtils.h"
+#include "ObliviousHttpChannel.h"
+#include "mozilla/Base64.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsIObserverService.h"
+#include "nsIPrefService.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(ObliviousHttpService, nsIObliviousHttpService, nsIObserver,
+ nsIStreamLoaderObserver)
+
+ObliviousHttpService::ObliviousHttpService()
+ : mTRRConfig(ObliviousHttpConfig(), "ObliviousHttpService::mTRRConfig") {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch) {
+ prefBranch->AddObserver("network.trr.ohttp", this, false);
+ }
+
+ if (nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService()) {
+ obs->AddObserver(this, "xpcom-shutdown", false);
+ obs->AddObserver(this, "network:captive-portal-connectivity-changed",
+ false);
+ obs->AddObserver(this, "network:trr-confirmation", false);
+ }
+
+ ReadPrefs("*"_ns);
+}
+
+static constexpr nsLiteralCString kTRRohttpConfigURIPref =
+ "network.trr.ohttp.config_uri"_ns;
+static constexpr nsLiteralCString kTRRohttpRelayURIPref =
+ "network.trr.ohttp.relay_uri"_ns;
+
+void ObliviousHttpService::FetchConfig(bool aConfigURIChanged) {
+ auto scopeExit = MakeScopeExit([&] {
+ nsCOMPtr<nsIObserverService> obs(mozilla::services::GetObserverService());
+ if (!obs) {
+ return;
+ }
+ obs->NotifyObservers(nullptr, "ohttp-service-config-loaded", u"no-changes");
+ });
+
+ {
+ auto trrConfig = mTRRConfig.Lock();
+ if (aConfigURIChanged) {
+ // If the config URI has changed, we need to clear the config.
+ trrConfig->mEncodedConfig.Clear();
+ } else if (trrConfig->mEncodedConfig.Length()) {
+ // The URI hasn't changed and we already have a config. No need to reload
+ return;
+ }
+ }
+
+ nsAutoCString configURIString;
+ nsresult rv =
+ Preferences::GetCString(kTRRohttpConfigURIPref.get(), configURIString);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ nsCOMPtr<nsIURI> configURI;
+ rv = NS_NewURI(getter_AddRefs(configURI), configURIString);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = DNSUtils::CreateChannelHelper(configURI, getter_AddRefs(channel));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ rv = channel->SetLoadFlags(
+ nsIRequest::LOAD_ANONYMOUS | nsIRequest::INHIBIT_CACHING |
+ nsIRequest::LOAD_BYPASS_CACHE | nsIChannel::LOAD_BYPASS_URL_CLASSIFIER);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (!httpChannel) {
+ return;
+ }
+ // This connection should not use TRR
+ rv = httpChannel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ rv = httpChannel->AsyncOpen(loader);
+
+ if (NS_SUCCEEDED(rv)) {
+ scopeExit.release();
+ return;
+ }
+
+ nsPrintfCString msg(
+ "ObliviousHttpService::FetchConfig AsyncOpen failed rv=%X",
+ static_cast<uint32_t>(rv));
+ NS_WARNING(msg.get());
+}
+
+void ObliviousHttpService::ReadPrefs(const nsACString& whichPref) {
+ if (whichPref.Equals(kTRRohttpRelayURIPref) || whichPref.EqualsLiteral("*")) {
+ nsAutoCString relayURIString;
+ nsresult rv =
+ Preferences::GetCString(kTRRohttpRelayURIPref.get(), relayURIString);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ nsCOMPtr<nsIURI> relayURI;
+ rv = NS_NewURI(getter_AddRefs(relayURI), relayURIString);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ auto trrConfig = mTRRConfig.Lock();
+ trrConfig->mRelayURI = relayURI;
+ }
+
+ if (whichPref.Equals(kTRRohttpConfigURIPref) ||
+ whichPref.EqualsLiteral("*")) {
+ FetchConfig(true);
+ }
+}
+
+// nsIObliviousHttpService
+
+NS_IMETHODIMP
+ObliviousHttpService::NewChannel(nsIURI* relayURI, nsIURI* targetURI,
+ const nsTArray<uint8_t>& encodedConfig,
+ nsIChannel** result) {
+ nsCOMPtr<nsIChannel> innerChannel;
+ nsresult rv =
+ DNSUtils::CreateChannelHelper(relayURI, getter_AddRefs(innerChannel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsIHttpChannel> innerHttpChannel(do_QueryInterface(innerChannel));
+ if (!innerHttpChannel) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIChannel> obliviousHttpChannel(
+ new ObliviousHttpChannel(targetURI, encodedConfig, innerHttpChannel));
+ obliviousHttpChannel.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpService::GetTRRSettings(nsIURI** relayURI,
+ nsTArray<uint8_t>& encodedConfig) {
+ auto trrConfig = mTRRConfig.Lock();
+ *relayURI = do_AddRef(trrConfig->mRelayURI).take();
+ encodedConfig.Assign(trrConfig->mEncodedConfig.Clone());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ObliviousHttpService::ClearTRRConfig() {
+ auto trrConfig = mTRRConfig.Lock();
+ trrConfig->mEncodedConfig.Clear();
+ return NS_OK;
+}
+
+// nsIObserver
+
+NS_IMETHODIMP
+ObliviousHttpService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ ReadPrefs(NS_ConvertUTF16toUTF8(data));
+ } else if (!strcmp(topic, "xpcom-shutdown")) {
+ if (nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID)) {
+ prefBranch->RemoveObserver("network.trr.ohttp", this);
+ }
+
+ if (nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService()) {
+ obs->RemoveObserver(this, "xpcom-shutdown");
+ obs->RemoveObserver(this, "network:captive-portal-connectivity-changed");
+ obs->RemoveObserver(this, "network:trr-confirmation");
+ }
+ } else if (!strcmp(topic, "network:captive-portal-connectivity-changed") ||
+ (!strcmp(topic, "network:trr-confirmation") && data &&
+ u"CONFIRM_FAILED"_ns.Equals(data))) {
+ FetchConfig(false);
+ }
+ return NS_OK;
+}
+
+// nsIStreamLoaderObserver
+
+NS_IMETHODIMP
+ObliviousHttpService::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* aContext, nsresult aStatus,
+ uint32_t aLength,
+ const uint8_t* aContent) {
+ if (NS_SUCCEEDED(aStatus)) {
+ auto trrConfig = mTRRConfig.Lock();
+ trrConfig->mEncodedConfig.Clear();
+ trrConfig->mEncodedConfig.AppendElements(aContent, aLength);
+ }
+
+ nsCOMPtr<nsIObserverService> observerService(
+ mozilla::services::GetObserverService());
+ if (!observerService) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = observerService->NotifyObservers(
+ nullptr, "ohttp-service-config-loaded",
+ NS_SUCCEEDED(aStatus) ? u"success" : u"failed");
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/ObliviousHttpService.h b/netwerk/protocol/http/ObliviousHttpService.h
new file mode 100644
index 0000000000..2d5b390072
--- /dev/null
+++ b/netwerk/protocol/http/ObliviousHttpService.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ObliviousHttpService_h
+#define mozilla_net_ObliviousHttpService_h
+
+#include "mozilla/DataMutex.h"
+#include "nsCOMPtr.h"
+#include "nsIObliviousHttp.h"
+#include "nsIObserver.h"
+#include "nsIStreamLoader.h"
+
+namespace mozilla::net {
+
+class ObliviousHttpConfig {
+ public:
+ nsCOMPtr<nsIURI> mRelayURI;
+ nsTArray<uint8_t> mEncodedConfig;
+};
+
+class ObliviousHttpService final : public nsIObliviousHttpService,
+ public nsIObserver,
+ public nsIStreamLoaderObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBLIVIOUSHTTPSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+ ObliviousHttpService();
+
+ private:
+ ~ObliviousHttpService() = default;
+ void ReadPrefs(const nsACString& whichPref);
+ void FetchConfig(bool aConfigURIChanged);
+
+ DataMutex<ObliviousHttpConfig> mTRRConfig;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_ObliviousHttpService_h
diff --git a/netwerk/protocol/http/OpaqueResponseUtils.cpp b/netwerk/protocol/http/OpaqueResponseUtils.cpp
new file mode 100644
index 0000000000..6597c0b771
--- /dev/null
+++ b/netwerk/protocol/http/OpaqueResponseUtils.cpp
@@ -0,0 +1,679 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/OpaqueResponseUtils.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/dom/JSValidatorParent.h"
+#include "ErrorList.h"
+#include "nsContentUtils.h"
+#include "nsHttpResponseHead.h"
+#include "nsISupports.h"
+#include "nsMimeTypes.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsStringStream.h"
+#include "HttpBaseChannel.h"
+
+static mozilla::LazyLogModule gORBLog("ORB");
+
+#define LOGORB(msg, ...) \
+ MOZ_LOG(gORBLog, LogLevel::Debug, \
+ ("%s: %p " msg, __func__, this, ##__VA_ARGS__))
+
+namespace mozilla::net {
+
+static bool IsOpaqueSafeListedMIMEType(const nsACString& aContentType) {
+ if (aContentType.EqualsLiteral(TEXT_CSS) ||
+ aContentType.EqualsLiteral(IMAGE_SVG_XML)) {
+ return true;
+ }
+
+ NS_ConvertUTF8toUTF16 typeString(aContentType);
+ return nsContentUtils::IsJavascriptMIMEType(typeString);
+}
+
+// These need to be kept in sync with
+// "browser.opaqueResponseBlocking.mediaExceptionsStrategy"
+enum class OpaqueResponseMediaException { NoExceptions, AllowSome, AllowAll };
+
+static OpaqueResponseMediaException ConfiguredMediaExceptionsStrategy() {
+ uint32_t pref = StaticPrefs::
+ browser_opaqueResponseBlocking_mediaExceptionsStrategy_DoNotUseDirectly();
+ if (NS_WARN_IF(pref > static_cast<uint32_t>(
+ OpaqueResponseMediaException::AllowAll))) {
+ return OpaqueResponseMediaException::AllowAll;
+ }
+
+ return static_cast<OpaqueResponseMediaException>(pref);
+}
+
+static bool IsOpaqueSafeListedSpecBreakingMIMEType(
+ const nsACString& aContentType, bool aNoSniff) {
+ // Avoid trouble with DASH/HLS. See bug 1698040.
+ if (aContentType.EqualsLiteral(APPLICATION_DASH_XML) ||
+ aContentType.EqualsLiteral(APPLICATION_MPEGURL) ||
+ aContentType.EqualsLiteral(AUDIO_MPEG_URL) ||
+ aContentType.EqualsLiteral(TEXT_VTT)) {
+ return true;
+ }
+
+ // Do what Chromium does. This is from bug 1828375, and we should ideally
+ // revert this.
+ if (aContentType.EqualsLiteral(TEXT_PLAIN) && aNoSniff) {
+ return true;
+ }
+
+ switch (ConfiguredMediaExceptionsStrategy()) {
+ case OpaqueResponseMediaException::NoExceptions:
+ break;
+ case OpaqueResponseMediaException::AllowSome:
+ if (aContentType.EqualsLiteral(AUDIO_MP3) ||
+ aContentType.EqualsLiteral(AUDIO_AAC) ||
+ aContentType.EqualsLiteral(AUDIO_AACP) ||
+ aContentType.EqualsLiteral(MULTIPART_MIXED_REPLACE)) {
+ return true;
+ }
+ break;
+ case OpaqueResponseMediaException::AllowAll:
+ if (StringBeginsWith(aContentType, "audio/"_ns) ||
+ StringBeginsWith(aContentType, "video/"_ns) ||
+ aContentType.EqualsLiteral(MULTIPART_MIXED_REPLACE)) {
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+static bool IsOpaqueBlockListedMIMEType(const nsACString& aContentType) {
+ return aContentType.EqualsLiteral(TEXT_HTML) ||
+ StringEndsWith(aContentType, "+json"_ns) ||
+ aContentType.EqualsLiteral(APPLICATION_JSON) ||
+ aContentType.EqualsLiteral(TEXT_JSON) ||
+ StringEndsWith(aContentType, "+xml"_ns) ||
+ aContentType.EqualsLiteral(APPLICATION_XML) ||
+ aContentType.EqualsLiteral(TEXT_XML);
+}
+
+static bool IsOpaqueBlockListedNeverSniffedMIMEType(
+ const nsACString& aContentType) {
+ return aContentType.EqualsLiteral(APPLICATION_GZIP2) ||
+ aContentType.EqualsLiteral(APPLICATION_MSEXCEL) ||
+ aContentType.EqualsLiteral(APPLICATION_MSPPT) ||
+ aContentType.EqualsLiteral(APPLICATION_MSWORD) ||
+ aContentType.EqualsLiteral(APPLICATION_MSWORD_TEMPLATE) ||
+ aContentType.EqualsLiteral(APPLICATION_PDF) ||
+ aContentType.EqualsLiteral(APPLICATION_MPEGURL) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_CES_QUICKPOINT) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_CES_QUICKSHEET) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_CES_QUICKWORD) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_EXCEL) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_EXCEL2) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_PPT) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_PPT2) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_WORD) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_WORD2) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MS_WORD3) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_MSWORD) ||
+ aContentType.EqualsLiteral(
+ APPLICATION_VND_PRESENTATIONML_PRESENTATION) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_PRESENTATIONML_TEMPLATE) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_SPREADSHEETML_SHEET) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_SPREADSHEETML_TEMPLATE) ||
+ aContentType.EqualsLiteral(
+ APPLICATION_VND_WORDPROCESSINGML_DOCUMENT) ||
+ aContentType.EqualsLiteral(
+ APPLICATION_VND_WORDPROCESSINGML_TEMPLATE) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_PRESENTATION_OPENXML) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_PRESENTATION_OPENXMLM) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_SPREADSHEET_OPENXML) ||
+ aContentType.EqualsLiteral(APPLICATION_VND_WORDPROSSING_OPENXML) ||
+ aContentType.EqualsLiteral(APPLICATION_GZIP) ||
+ aContentType.EqualsLiteral(APPLICATION_XPROTOBUF) ||
+ aContentType.EqualsLiteral(APPLICATION_XPROTOBUFFER) ||
+ aContentType.EqualsLiteral(APPLICATION_ZIP) ||
+ aContentType.EqualsLiteral(AUDIO_MPEG_URL) ||
+ aContentType.EqualsLiteral(MULTIPART_BYTERANGES) ||
+ aContentType.EqualsLiteral(MULTIPART_SIGNED) ||
+ aContentType.EqualsLiteral(TEXT_EVENT_STREAM) ||
+ aContentType.EqualsLiteral(TEXT_CSV) ||
+ aContentType.EqualsLiteral(TEXT_VTT) ||
+ aContentType.EqualsLiteral(APPLICATION_DASH_XML);
+}
+
+OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason(
+ const nsACString& aContentType, uint16_t aStatus, bool aNoSniff) {
+ if (aContentType.IsEmpty()) {
+ return OpaqueResponseBlockedReason::BLOCKED_SHOULD_SNIFF;
+ }
+
+ if (IsOpaqueSafeListedMIMEType(aContentType)) {
+ return OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED;
+ }
+
+ // For some MIME types we deviate from spec and allow when we ideally
+ // shouldn't. These are returnened before any blocking takes place.
+ if (IsOpaqueSafeListedSpecBreakingMIMEType(aContentType, aNoSniff)) {
+ return OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED_SPEC_BREAKING;
+ }
+
+ if (IsOpaqueBlockListedNeverSniffedMIMEType(aContentType)) {
+ return OpaqueResponseBlockedReason::BLOCKED_BLOCKLISTED_NEVER_SNIFFED;
+ }
+
+ if (aStatus == 206 && IsOpaqueBlockListedMIMEType(aContentType)) {
+ return OpaqueResponseBlockedReason::BLOCKED_206_AND_BLOCKLISTED;
+ }
+
+ nsAutoCString contentTypeOptionsHeader;
+ if (aNoSniff && (IsOpaqueBlockListedMIMEType(aContentType) ||
+ aContentType.EqualsLiteral(TEXT_PLAIN))) {
+ return OpaqueResponseBlockedReason::
+ BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN;
+ }
+
+ return OpaqueResponseBlockedReason::BLOCKED_SHOULD_SNIFF;
+}
+
+OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason(
+ nsHttpResponseHead& aResponseHead) {
+ nsAutoCString contentType;
+ aResponseHead.ContentType(contentType);
+
+ nsAutoCString contentTypeOptionsHeader;
+ bool nosniff =
+ aResponseHead.GetContentTypeOptionsHeader(contentTypeOptionsHeader) &&
+ contentTypeOptionsHeader.EqualsIgnoreCase("nosniff");
+
+ return GetOpaqueResponseBlockedReason(contentType, aResponseHead.Status(),
+ nosniff);
+}
+
+Result<std::tuple<int64_t, int64_t, int64_t>, nsresult>
+ParseContentRangeHeaderString(const nsAutoCString& aRangeStr) {
+ // Parse the range header: e.g. Content-Range: bytes 7000-7999/8000.
+ const int32_t spacePos = aRangeStr.Find(" "_ns);
+ const int32_t dashPos = aRangeStr.Find("-"_ns, spacePos);
+ const int32_t slashPos = aRangeStr.Find("/"_ns, dashPos);
+
+ nsAutoCString rangeStartText;
+ aRangeStr.Mid(rangeStartText, spacePos + 1, dashPos - (spacePos + 1));
+
+ nsresult rv;
+ const int64_t rangeStart = rangeStartText.ToInteger64(&rv);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+ if (0 > rangeStart) {
+ return Err(NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ nsAutoCString rangeEndText;
+ aRangeStr.Mid(rangeEndText, dashPos + 1, slashPos - (dashPos + 1));
+ const int64_t rangeEnd = rangeEndText.ToInteger64(&rv);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+ if (rangeStart > rangeEnd) {
+ return Err(NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ nsAutoCString rangeTotalText;
+ aRangeStr.Right(rangeTotalText, aRangeStr.Length() - (slashPos + 1));
+ if (rangeTotalText[0] == '*') {
+ return std::make_tuple(rangeStart, rangeEnd, (int64_t)-1);
+ }
+
+ const int64_t rangeTotal = rangeTotalText.ToInteger64(&rv);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+ if (rangeEnd >= rangeTotal) {
+ return Err(NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ return std::make_tuple(rangeStart, rangeEnd, rangeTotal);
+}
+
+bool IsFirstPartialResponse(nsHttpResponseHead& aResponseHead) {
+ MOZ_ASSERT(aResponseHead.Status() == 206);
+
+ nsAutoCString contentRange;
+ Unused << aResponseHead.GetHeader(nsHttp::Content_Range, contentRange);
+
+ auto rangeOrErr = ParseContentRangeHeaderString(contentRange);
+ if (rangeOrErr.isErr()) {
+ return false;
+ }
+
+ const int64_t responseFirstBytePos = std::get<0>(rangeOrErr.unwrap());
+ return responseFirstBytePos == 0;
+}
+
+LogModule* GetORBLog() { return gORBLog; }
+
+OpaqueResponseFilter::OpaqueResponseFilter(nsIStreamListener* aNext)
+ : mNext(aNext) {
+ LOGORB();
+}
+
+NS_IMETHODIMP
+OpaqueResponseFilter::OnStartRequest(nsIRequest* aRequest) {
+ LOGORB();
+ nsCOMPtr<HttpBaseChannel> httpBaseChannel = do_QueryInterface(aRequest);
+ MOZ_ASSERT(httpBaseChannel);
+
+ nsHttpResponseHead* responseHead = httpBaseChannel->GetResponseHead();
+
+ if (responseHead) {
+ // Filtered opaque responses doesn't need headers, so we just drop them.
+ responseHead->ClearHeaders();
+ }
+
+ mNext->OnStartRequest(aRequest);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OpaqueResponseFilter::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOGORB();
+ uint32_t result;
+ // No data for filtered opaque responses should reach the content process, so
+ // we just discard them.
+ return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount,
+ &result);
+}
+
+NS_IMETHODIMP
+OpaqueResponseFilter::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ LOGORB();
+ mNext->OnStopRequest(aRequest, aStatusCode);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(OpaqueResponseFilter, nsIStreamListener, nsIRequestObserver)
+
+OpaqueResponseBlocker::OpaqueResponseBlocker(nsIStreamListener* aNext,
+ HttpBaseChannel* aChannel,
+ const nsCString& aContentType,
+ bool aNoSniff)
+ : mNext(aNext), mContentType(aContentType), mNoSniff(aNoSniff) {
+ // Storing aChannel as a member is tricky as aChannel owns us and it's
+ // hard to ensure aChannel is alive when we about to use it without
+ // creating a cycle. This is all doable but need some extra efforts.
+ //
+ // So we are just passing aChannel from the caller when we need to use it.
+ MOZ_ASSERT(aChannel);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gORBLog, LogLevel::Debug))) {
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ LOGORB(" channel=%p, uri=%s", aChannel, uri->GetSpecOrDefault().get());
+ }
+ }
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ MOZ_DIAGNOSTIC_ASSERT(aChannel->CachedOpaqueResponseBlockingPref());
+}
+
+NS_IMETHODIMP
+OpaqueResponseBlocker::OnStartRequest(nsIRequest* aRequest) {
+ LOGORB();
+
+ if (mState == State::Sniffing) {
+ Unused << EnsureOpaqueResponseIsAllowedAfterSniff(aRequest);
+ }
+
+ // mState will remain State::Sniffing if we need to wait
+ // for JS validator to make a decision.
+ //
+ // When the state is Sniffing, we can't call mNext->OnStartRequest
+ // because fetch requests need the cancellation to be done
+ // before its FetchDriver::OnStartRequest is called, otherwise it'll
+ // resolve the promise regardless the decision of JS validator.
+ if (mState != State::Sniffing) {
+ nsresult rv = mNext->OnStartRequest(aRequest);
+ return NS_SUCCEEDED(mStatus) ? rv : mStatus;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OpaqueResponseBlocker::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ LOGORB();
+
+ nsresult statusForStop = aStatusCode;
+
+ if (mState == State::Blocked && NS_FAILED(mStatus)) {
+ statusForStop = mStatus;
+ }
+
+ if (mState == State::Sniffing) {
+ // It is the call to JSValidatorParent::OnStopRequest that will trigger the
+ // JS parser.
+ mStartOfJavaScriptValidation = TimeStamp::Now();
+
+ MOZ_ASSERT(mJSValidator);
+ mPendingOnStopRequestStatus = Some(aStatusCode);
+ mJSValidator->OnStopRequest(aStatusCode, *aRequest);
+ return NS_OK;
+ }
+
+ return mNext->OnStopRequest(aRequest, statusForStop);
+}
+
+NS_IMETHODIMP
+OpaqueResponseBlocker::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOGORB();
+
+ if (mState == State::Allowed) {
+ return mNext->OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
+ }
+
+ if (mState == State::Blocked) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mState == State::Sniffing);
+
+ nsCString data;
+ if (!data.SetLength(aCount, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t read;
+ nsresult rv = aInputStream->Read(data.BeginWriting(), aCount, &read);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ MOZ_ASSERT(mJSValidator);
+
+ mJSValidator->OnDataAvailable(data);
+
+ return NS_OK;
+}
+
+nsresult OpaqueResponseBlocker::EnsureOpaqueResponseIsAllowedAfterSniff(
+ nsIRequest* aRequest) {
+ nsCOMPtr<HttpBaseChannel> httpBaseChannel = do_QueryInterface(aRequest);
+ MOZ_ASSERT(httpBaseChannel);
+
+ // The `AfterSniff` check shouldn't be run when
+ // 1. We have made a decision already
+ // 2. The JS validator is running, so we should wait
+ // for its result.
+ if (mState != State::Sniffing || mJSValidator) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+
+ nsresult rv =
+ httpBaseChannel->GetLoadInfo(getter_AddRefs<nsILoadInfo>(loadInfo));
+ if (NS_FAILED(rv)) {
+ LOGORB("Failed to get LoadInfo");
+ BlockResponse(httpBaseChannel, rv);
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = httpBaseChannel->GetURI(getter_AddRefs<nsIURI>(uri));
+ if (NS_FAILED(rv)) {
+ LOGORB("Failed to get uri");
+ BlockResponse(httpBaseChannel, rv);
+ return rv;
+ }
+
+ switch (httpBaseChannel->PerformOpaqueResponseSafelistCheckAfterSniff(
+ mContentType, mNoSniff)) {
+ case OpaqueResponse::Block:
+ BlockResponse(httpBaseChannel, NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ case OpaqueResponse::Allow:
+ AllowResponse();
+ return NS_OK;
+ case OpaqueResponse::Sniff:
+ case OpaqueResponse::SniffCompressed:
+ break;
+ }
+
+ MOZ_ASSERT(mState == State::Sniffing);
+ return ValidateJavaScript(httpBaseChannel, uri, loadInfo);
+}
+
+OpaqueResponse
+OpaqueResponseBlocker::EnsureOpaqueResponseIsAllowedAfterJavaScriptValidation(
+ HttpBaseChannel* aChannel, bool aAllow) {
+ if (aAllow) {
+ return OpaqueResponse::Allow;
+ }
+
+ return aChannel->BlockOrFilterOpaqueResponse(
+ this, u"Javascript validation failed"_ns,
+ OpaqueResponseBlockedTelemetryReason::JS_VALIDATION_FAILED,
+ "Javascript validation failed");
+}
+
+static void RecordTelemetry(const TimeStamp& aStartOfValidation,
+ const TimeStamp& aStartOfJavaScriptValidation,
+ OpaqueResponseBlocker::ValidatorResult aResult) {
+ using ValidatorResult = OpaqueResponseBlocker::ValidatorResult;
+ MOZ_DIAGNOSTIC_ASSERT(aStartOfValidation);
+
+ auto key = [aResult]() {
+ switch (aResult) {
+ case ValidatorResult::JavaScript:
+ return "javascript"_ns;
+ case ValidatorResult::JSON:
+ return "json"_ns;
+ case ValidatorResult::Other:
+ return "other"_ns;
+ case ValidatorResult::Failure:
+ return "failure"_ns;
+ }
+ MOZ_ASSERT_UNREACHABLE("Switch statement should be saturated");
+ return "failure"_ns;
+ }();
+
+ TimeStamp now = TimeStamp::Now();
+ PROFILER_MARKER_TEXT(
+ "ORB safelist check", NETWORK,
+ MarkerTiming::Interval(aStartOfValidation, aStartOfJavaScriptValidation),
+ nsPrintfCString("Receive data for validation (%s)", key.get()));
+
+ PROFILER_MARKER_TEXT(
+ "ORB safelist check", NETWORK,
+ MarkerTiming::Interval(aStartOfJavaScriptValidation, now),
+ nsPrintfCString("JS Validation (%s)", key.get()));
+
+ Telemetry::AccumulateTimeDelta(Telemetry::ORB_RECEIVE_DATA_FOR_VALIDATION_MS,
+ key, aStartOfValidation,
+ aStartOfJavaScriptValidation);
+
+ Telemetry::AccumulateTimeDelta(Telemetry::ORB_JAVASCRIPT_VALIDATION_MS, key,
+ aStartOfJavaScriptValidation, now);
+}
+
+// The specification for ORB is currently being written:
+// https://whatpr.org/fetch/1442.html#orb-algorithm
+// The `opaque-response-safelist check` is implemented in:
+// * `HttpBaseChannel::OpaqueResponseSafelistCheckBeforeSniff`
+// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
+// * `HttpBaseChannel::OpaqueResponseSafelistCheckAfterSniff`
+// * `OpaqueResponseBlocker::ValidateJavaScript`
+nsresult OpaqueResponseBlocker::ValidateJavaScript(HttpBaseChannel* aChannel,
+ nsIURI* aURI,
+ nsILoadInfo* aLoadInfo) {
+ MOZ_DIAGNOSTIC_ASSERT(aChannel);
+ MOZ_ASSERT(aURI && aLoadInfo);
+
+ if (!StaticPrefs::browser_opaqueResponseBlocking_javascriptValidator()) {
+ LOGORB("Allowed: JS Validator is disabled");
+ AllowResponse();
+ return NS_OK;
+ }
+
+ int64_t contentLength;
+ nsresult rv = aChannel->GetContentLength(&contentLength);
+ if (NS_FAILED(rv)) {
+ LOGORB("Blocked: No Content Length");
+ BlockResponse(aChannel, rv);
+ return rv;
+ }
+
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::OPAQUE_RESPONSE_BLOCKING_JAVASCRIPT_VALIDATION_COUNT,
+ 1);
+
+ LOGORB("Send %s to the validator", aURI->GetSpecOrDefault().get());
+ // https://whatpr.org/fetch/1442.html#orb-algorithm, step 15
+ mJSValidator = dom::JSValidatorParent::Create();
+ mJSValidator->IsOpaqueResponseAllowed(
+ [self = RefPtr{this}, channel = nsCOMPtr{aChannel}, uri = nsCOMPtr{aURI},
+ loadInfo = nsCOMPtr{aLoadInfo}, startOfValidation = TimeStamp::Now()](
+ Maybe<ipc::Shmem> aSharedData, ValidatorResult aResult) {
+ MOZ_LOG(gORBLog, LogLevel::Debug,
+ ("JSValidator resolved for %s with %s",
+ uri->GetSpecOrDefault().get(),
+ aSharedData.isSome() ? "true" : "false"));
+ bool allowed = aResult == ValidatorResult::JavaScript;
+ switch (self->EnsureOpaqueResponseIsAllowedAfterJavaScriptValidation(
+ channel, allowed)) {
+ case OpaqueResponse::Allow:
+ // It's possible that the JS validation failed for this request,
+ // however we decided that we need to filter the response instead
+ // of blocking. So we set allowed to true manually when that's the
+ // case.
+ allowed = true;
+ self->AllowResponse();
+ break;
+ case OpaqueResponse::Block:
+ // We'll filter the data out later
+ self->AllowResponse();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "We should only ever have Allow or Block here.");
+ allowed = false;
+ self->BlockResponse(channel, NS_ERROR_FAILURE);
+ break;
+ }
+
+ self->ResolveAndProcessData(channel, allowed, aSharedData);
+ if (aSharedData.isSome()) {
+ self->mJSValidator->DeallocShmem(aSharedData.ref());
+ }
+
+ RecordTelemetry(startOfValidation, self->mStartOfJavaScriptValidation,
+ aResult);
+
+ Unused << dom::PJSValidatorParent::Send__delete__(self->mJSValidator);
+ self->mJSValidator = nullptr;
+ });
+
+ return NS_OK;
+}
+
+bool OpaqueResponseBlocker::IsSniffing() const {
+ return mState == State::Sniffing;
+}
+
+void OpaqueResponseBlocker::AllowResponse() {
+ LOGORB("Sniffer is done, allow response, this=%p", this);
+ MOZ_ASSERT(mState == State::Sniffing);
+ mState = State::Allowed;
+}
+
+void OpaqueResponseBlocker::BlockResponse(HttpBaseChannel* aChannel,
+ nsresult aStatus) {
+ LOGORB("Sniffer is done, block response, this=%p", this);
+ MOZ_ASSERT(mState == State::Sniffing);
+ mState = State::Blocked;
+ mStatus = aStatus;
+ aChannel->SetChannelBlockedByOpaqueResponse();
+ aChannel->CancelWithReason(mStatus,
+ "OpaqueResponseBlocker::BlockResponse"_ns);
+}
+
+void OpaqueResponseBlocker::FilterResponse() {
+ MOZ_ASSERT(mState == State::Sniffing);
+
+ if (mShouldFilter) {
+ return;
+ }
+
+ mShouldFilter = true;
+
+ mNext = new OpaqueResponseFilter(mNext);
+}
+
+void OpaqueResponseBlocker::ResolveAndProcessData(
+ HttpBaseChannel* aChannel, bool aAllowed, Maybe<ipc::Shmem>& aSharedData) {
+ if (!aAllowed) {
+ // OpaqueResponseFilter allows us to filter the headers
+ mNext = new OpaqueResponseFilter(mNext);
+ }
+
+ nsresult rv = OnStartRequest(aChannel);
+
+ if (!aAllowed || NS_FAILED(rv)) {
+ MOZ_ASSERT_IF(!aAllowed, mState == State::Allowed);
+ // No need to call OnDataAvailable because
+ // 1. The input stream is consumed by
+ // OpaqueResponseBlocker::OnDataAvailable already
+ // 2. We don't want to pass any data over
+ MaybeRunOnStopRequest(aChannel);
+ return;
+ }
+
+ MOZ_ASSERT(mState == State::Allowed);
+
+ if (aSharedData.isNothing()) {
+ MaybeRunOnStopRequest(aChannel);
+ return;
+ }
+
+ const ipc::Shmem& mem = aSharedData.ref();
+ nsCOMPtr<nsIInputStream> input;
+ rv = NS_NewByteInputStream(getter_AddRefs(input),
+ Span(mem.get<char>(), mem.Size<char>()),
+ NS_ASSIGNMENT_DEPEND);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ BlockResponse(aChannel, rv);
+ MaybeRunOnStopRequest(aChannel);
+ return;
+ }
+
+ // When this line reaches, the state is either State::Allowed or
+ // State::Blocked. The OnDataAvailable call will either call
+ // the next listener or reject the request.
+ OnDataAvailable(aChannel, input, 0, mem.Size<char>());
+
+ MaybeRunOnStopRequest(aChannel);
+}
+
+void OpaqueResponseBlocker::MaybeRunOnStopRequest(HttpBaseChannel* aChannel) {
+ MOZ_ASSERT(mState != State::Sniffing);
+ if (mPendingOnStopRequestStatus.isSome()) {
+ OnStopRequest(aChannel, mPendingOnStopRequestStatus.value());
+ }
+}
+
+NS_IMPL_ISUPPORTS(OpaqueResponseBlocker, nsIStreamListener, nsIRequestObserver)
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/OpaqueResponseUtils.h b/netwerk/protocol/http/OpaqueResponseUtils.h
new file mode 100644
index 0000000000..7b37095b01
--- /dev/null
+++ b/netwerk/protocol/http/OpaqueResponseUtils.h
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_OpaqueResponseUtils_h
+#define mozilla_net_OpaqueResponseUtils_h
+
+#include "ipc/EnumSerializer.h"
+#include "mozilla/TimeStamp.h"
+#include "nsIContentPolicy.h"
+#include "nsIEncodedChannel.h"
+#include "nsIStreamListener.h"
+#include "nsUnknownDecoder.h"
+#include "nsMimeTypes.h"
+#include "nsIHttpChannel.h"
+
+#include "mozilla/Variant.h"
+#include "mozilla/Logging.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsIContentSniffer;
+
+namespace mozilla::dom {
+class JSValidatorParent;
+}
+
+namespace mozilla::ipc {
+class Shmem;
+}
+
+namespace mozilla::net {
+
+class HttpBaseChannel;
+class nsHttpResponseHead;
+
+enum class OpaqueResponseBlockedReason : uint32_t {
+ ALLOWED_SAFE_LISTED,
+ ALLOWED_SAFE_LISTED_SPEC_BREAKING,
+ BLOCKED_BLOCKLISTED_NEVER_SNIFFED,
+ BLOCKED_206_AND_BLOCKLISTED,
+ BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN,
+ BLOCKED_SHOULD_SNIFF
+};
+
+enum class OpaqueResponseBlockedTelemetryReason : uint32_t {
+ MIME_NEVER_SNIFFED,
+ RESP_206_BLCLISTED,
+ NOSNIFF_BLC_OR_TEXTP,
+ RESP_206_NO_FIRST,
+ AFTER_SNIFF_MEDIA,
+ AFTER_SNIFF_NOSNIFF,
+ AFTER_SNIFF_STA_CODE,
+ AFTER_SNIFF_CT_FAIL,
+ MEDIA_NOT_INITIAL,
+ MEDIA_INCORRECT_RESP,
+ JS_VALIDATION_FAILED
+};
+
+enum class OpaqueResponse { Block, Allow, SniffCompressed, Sniff };
+
+OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason(
+ const nsACString& aContentType, uint16_t aStatus, bool aNoSniff);
+
+OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason(
+ nsHttpResponseHead& aResponseHead);
+
+// Returns a tuple of (rangeStart, rangeEnd, rangeTotal) from the input range
+// header string if succeed.
+Result<std::tuple<int64_t, int64_t, int64_t>, nsresult>
+ParseContentRangeHeaderString(const nsAutoCString& aRangeStr);
+
+bool IsFirstPartialResponse(nsHttpResponseHead& aResponseHead);
+
+LogModule* GetORBLog();
+
+// Helper class to filter data for opaque responses destined for `Window.fetch`.
+// See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque.
+class OpaqueResponseFilter final : public nsIStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER;
+
+ explicit OpaqueResponseFilter(nsIStreamListener* aNext);
+
+ private:
+ virtual ~OpaqueResponseFilter() = default;
+
+ nsCOMPtr<nsIStreamListener> mNext;
+};
+
+class OpaqueResponseBlocker final : public nsIStreamListener {
+ enum class State { Sniffing, Allowed, Blocked };
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER;
+
+ OpaqueResponseBlocker(nsIStreamListener* aNext, HttpBaseChannel* aChannel,
+ const nsCString& aContentType, bool aNoSniff);
+
+ bool IsSniffing() const;
+ void AllowResponse();
+ void BlockResponse(HttpBaseChannel* aChannel, nsresult aStatus);
+ void FilterResponse();
+
+ nsresult EnsureOpaqueResponseIsAllowedAfterSniff(nsIRequest* aRequest);
+
+ OpaqueResponse EnsureOpaqueResponseIsAllowedAfterJavaScriptValidation(
+ HttpBaseChannel* aChannel, bool aAllow);
+
+ // The four possible results for validation. `JavaScript` and `JSON` are
+ // self-explanatory. `JavaScript` is the only successful result, in the sense
+ // that it will allow the opaque response, whereas `JSON` will block. `Other`
+ // is the case where validation fails, because the response is neither
+ // `JavaScript` nor `JSON`, but the framework itself works as intended.
+ // `Failure` implies that something has gone wrong, such as allocation, etc.
+ enum class ValidatorResult : uint32_t { JavaScript, JSON, Other, Failure };
+
+ private:
+ virtual ~OpaqueResponseBlocker() = default;
+
+ nsresult ValidateJavaScript(HttpBaseChannel* aChannel, nsIURI* aURI,
+ nsILoadInfo* aLoadInfo);
+
+ void ResolveAndProcessData(HttpBaseChannel* aChannel, bool aAllowed,
+ Maybe<ipc::Shmem>& aSharedData);
+
+ void MaybeRunOnStopRequest(HttpBaseChannel* aChannel);
+
+ nsCOMPtr<nsIStreamListener> mNext;
+
+ const nsCString mContentType;
+ const bool mNoSniff;
+ bool mShouldFilter = false;
+
+ State mState = State::Sniffing;
+ nsresult mStatus = NS_OK;
+
+ TimeStamp mStartOfJavaScriptValidation;
+
+ RefPtr<dom::JSValidatorParent> mJSValidator;
+
+ Maybe<nsresult> mPendingOnStopRequestStatus{Nothing()};
+};
+
+class nsCompressedAudioVideoImageDetector : public nsUnknownDecoder {
+ const std::function<void(void*, const uint8_t*, uint32_t)> mCallback;
+
+ public:
+ nsCompressedAudioVideoImageDetector(
+ nsIStreamListener* aListener,
+ std::function<void(void*, const uint8_t*, uint32_t)>&& aCallback)
+ : nsUnknownDecoder(aListener), mCallback(aCallback) {}
+
+ protected:
+ virtual void DetermineContentType(nsIRequest* aRequest) override {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (!httpChannel) {
+ return;
+ }
+
+ const char* testData = mBuffer;
+ uint32_t testDataLen = mBufferLen;
+ // Check if data are compressed.
+ nsAutoCString decodedData;
+
+ // ConvertEncodedData is always called only on a single thread for each
+ // instance of an object.
+ nsresult rv = ConvertEncodedData(aRequest, mBuffer, mBufferLen);
+ if (NS_SUCCEEDED(rv)) {
+ MutexAutoLock lock(mMutex);
+ decodedData = mDecodedData;
+ }
+ if (!decodedData.IsEmpty()) {
+ testData = decodedData.get();
+ testDataLen = std::min<uint32_t>(decodedData.Length(), 512u);
+ }
+
+ mCallback(httpChannel, (const uint8_t*)testData, testDataLen);
+
+ nsAutoCString contentType;
+ rv = httpChannel->GetContentType(contentType);
+
+ MutexAutoLock lock(mMutex);
+ if (!contentType.IsEmpty()) {
+ mContentType = contentType;
+ } else {
+ mContentType = UNKNOWN_CONTENT_TYPE;
+ }
+
+ nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
+ if (encodedChannel) {
+ encodedChannel->SetHasContentDecompressed(true);
+ }
+ }
+};
+} // namespace mozilla::net
+
+namespace IPC {
+template <>
+struct ParamTraits<mozilla::net::OpaqueResponseBlocker::ValidatorResult>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::net::OpaqueResponseBlocker::ValidatorResult,
+ mozilla::net::OpaqueResponseBlocker::ValidatorResult::JavaScript,
+ mozilla::net::OpaqueResponseBlocker::ValidatorResult::Failure> {};
+} // namespace IPC
+
+#endif // mozilla_net_OpaqueResponseUtils_h
diff --git a/netwerk/protocol/http/PAltDataOutputStream.ipdl b/netwerk/protocol/http/PAltDataOutputStream.ipdl
new file mode 100644
index 0000000000..9fbf589e95
--- /dev/null
+++ b/netwerk/protocol/http/PAltDataOutputStream.ipdl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+namespace mozilla {
+namespace net {
+
+[ManualDealloc]
+protocol PAltDataOutputStream
+{
+ manager PNecko;
+
+parent:
+ // Sends data from the child to the parent that will be written to the cache.
+ async WriteData(nsCString data);
+ // Signals that writing to the output stream is done.
+ async Close(nsresult status);
+
+ async __delete__();
+
+child:
+ // The parent calls this method to signal that an error has ocurred.
+ // This may mean that opening the output stream has failed or that writing to
+ // the stream has returned an error.
+ async Error(nsresult err);
+
+both:
+ // After sending this message, the parent will respond by sending DeleteSelf
+ // back to the child, after which it is guaranteed to not send any more IPC
+ // messages.
+ // When receiving this message, the child will send __delete__ tearing down
+ // the IPC channel.
+ async DeleteSelf();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PAltService.ipdl b/netwerk/protocol/http/PAltService.ipdl
new file mode 100644
index 0000000000..c4b4f494b5
--- /dev/null
+++ b/netwerk/protocol/http/PAltService.ipdl
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PSocketProcess;
+include NeckoChannelParams;
+
+using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+
+namespace mozilla {
+namespace net {
+
+protocol PAltService
+{
+ manager PSocketProcess;
+
+parent:
+ async ClearHostMapping(nsCString host, int32_t port,
+ OriginAttributes originAttributes);
+
+ async ProcessHeader(nsCString buf,
+ nsCString originScheme,
+ nsCString originHost,
+ int32_t originPort,
+ nsCString username,
+ bool privateBrowsing,
+ ProxyInfoCloneArgs[] proxyInfo,
+ uint32_t caps,
+ OriginAttributes originAttributes);
+
+child:
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PAltSvcTransaction.ipdl b/netwerk/protocol/http/PAltSvcTransaction.ipdl
new file mode 100644
index 0000000000..001b53a4c7
--- /dev/null
+++ b/netwerk/protocol/http/PAltSvcTransaction.ipdl
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PSocketProcess;
+
+namespace mozilla {
+namespace net {
+
+protocol PAltSvcTransaction
+{
+ manager PSocketProcess;
+
+parent:
+ async __delete__(bool aValidateResult);
+ async OnTransactionClose(bool aValidateResult);
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PBackgroundDataBridge.ipdl b/netwerk/protocol/http/PBackgroundDataBridge.ipdl
new file mode 100644
index 0000000000..d5a3be4fab
--- /dev/null
+++ b/netwerk/protocol/http/PBackgroundDataBridge.ipdl
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include HttpChannelParams;
+include NeckoChannelParams;
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+[ParentProc=Socket, ChildProc=Content]
+async protocol PBackgroundDataBridge
+{
+child:
+ async OnTransportAndData(uint64_t offset,
+ uint32_t count,
+ nsCString data,
+ TimeStamp onDataAvailableStart);
+
+ async OnStopRequest(nsresult aStatus,
+ ResourceTimingStructArgs timing,
+ TimeStamp lastActiveTabOptimization,
+ nsHttpHeaderArray responseTrailers,
+ TimeStamp onStopRequestStart);
+
+both:
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PHttpBackgroundChannel.ipdl b/netwerk/protocol/http/PHttpBackgroundChannel.ipdl
new file mode 100644
index 0000000000..1f930992a9
--- /dev/null
+++ b/netwerk/protocol/http/PHttpBackgroundChannel.ipdl
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PBackground;
+include protocol PStreamFilter;
+include NeckoChannelParams;
+include HttpChannelParams;
+include PURLClassifierInfo;
+
+include "mozilla/net/NeckoMessageUtils.h";
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+async protocol PHttpBackgroundChannel
+{
+ manager PBackground;
+
+child:
+ async OnStartRequest(nsHttpResponseHead responseHead,
+ bool useResponseHead,
+ nsHttpHeaderArray requestHeaders,
+ HttpChannelOnStartRequestArgs args,
+ HttpChannelAltDataStream altData,
+ TimeStamp onStartRequestStart);
+
+ // Combines a single OnDataAvailable and its associated OnProgress &
+ // OnStatus calls into one IPDL message
+ async OnTransportAndData(nsresult channelStatus,
+ nsresult transportStatus,
+ uint64_t offset,
+ uint32_t count,
+ nsCString data,
+ bool dataFromSocketProcess,
+ TimeStamp onDataAvailableStart);
+
+
+ async OnStopRequest(nsresult channelStatus,
+ ResourceTimingStructArgs timing,
+ TimeStamp lastActiveTabOptimization,
+ nsHttpHeaderArray responseTrailers,
+ ConsoleReportCollected[] consoleReport,
+ bool fromSocketProcess,
+ TimeStamp onStopRequestStart);
+
+ async OnConsoleReport(ConsoleReportCollected[] consoleReport);
+
+ async OnProgress(int64_t progress, int64_t progressMax);
+
+ async OnStatus(nsresult status);
+
+ // Forwards nsIMultiPartChannelListener::onAfterLastPart. Make sure we've
+ // disconnected our listeners, since we might not have on the last
+ // OnStopRequest.
+ async OnAfterLastPart(nsresult aStatus);
+
+ // Tell the child that the resource being loaded has been classified.
+ async NotifyClassificationFlags(uint32_t aClassificationFlags, bool aIsThirdParty);
+
+ // Tell the child information of matched URL againts SafeBrowsing list
+ async SetClassifierMatchedInfo(ClassifierInfo info);
+
+ // Tell the child information of matched URL againts SafeBrowsing tracking list
+ async SetClassifierMatchedTrackingInfo(ClassifierInfo info);
+
+ async AttachStreamFilter(Endpoint<PStreamFilterParent> aEndpoint);
+
+ async DetachStreamFilters();
+
+ async __delete__();
+
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PHttpChannel.ipdl b/netwerk/protocol/http/PHttpChannel.ipdl
new file mode 100644
index 0000000000..76243a0635
--- /dev/null
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include InputStreamParams;
+include PBackgroundSharedTypes;
+include NeckoChannelParams;
+include IPCServiceWorkerDescriptor;
+include IPCStream;
+include HttpChannelParams;
+
+include "mozilla/dom/ReferrerInfoUtils.h";
+include "mozilla/net/NeckoMessageUtils.h";
+include "mozilla/net/ClassOfService.h";
+
+using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+using mozilla::net::ClassOfService from "mozilla/net/ClassOfService.h";
+[RefCounted] using class nsIURI from "mozilla/ipc/URIUtils.h";
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+[ChildImpl=virtual, ParentImpl=virtual]
+protocol PHttpChannel
+{
+ manager PNecko;
+
+parent:
+ // Note: channels are opened during construction, so no open method here:
+ // see PNecko.ipdl
+
+ async SetClassOfService(ClassOfService cos);
+
+ async Suspend();
+ async Resume();
+
+ async Cancel(nsresult status, uint32_t requestBlockingReason,
+ nsCString aReason, nsCString? logString);
+
+ // Reports approval/veto of redirect by child process redirect observers
+ async Redirect2Verify(nsresult result, RequestHeaderTuples changedHeaders,
+ uint32_t sourceRequestBlockingReason,
+ ChildLoadInfoForwarderArgs? targetLoadInfoForwarder,
+ uint32_t loadFlags,
+ nullable nsIReferrerInfo referrerInfo,
+ nullable nsIURI apiRedirectTo,
+ CorsPreflightArgs? corsPreflightArgs);
+
+ // For document loads we keep this protocol open after child's
+ // OnStopRequest, and send this msg (instead of __delete__) to allow
+ // partial cleanup on parent.
+ async DocumentChannelCleanup(bool clearCacheEntry);
+
+ // Child has detected a CORS check failure, so needs to tell the parent
+ // to remove any matching entry from the CORS preflight cache.
+ async RemoveCorsPreflightCacheEntry(nullable nsIURI uri,
+ PrincipalInfo requestingPrincipal,
+ OriginAttributes originAttributes);
+
+ // Send cookies to the parent for a given channel. Compared to PCookieService
+ // SetCookies this method allows the parent to determine which BrowsingContext
+ // the request was sent for.
+ async SetCookies(nsCString baseDomain,
+ OriginAttributes attrs,
+ nullable nsIURI host,
+ bool fromHttp,
+ CookieStruct[] cookies);
+
+ // After receiving this message, the parent calls SendDeleteSelf, and makes
+ // sure not to send any more messages after that.
+ async DeletingChannel();
+
+ // Called to get the input stream when altData was delivered.
+ async OpenOriginalCacheInputStream();
+
+ // Tell the parent the amount bytes read by child for the e10s back pressure
+ // flow control
+ async BytesRead(int32_t count);
+
+ async __delete__();
+
+child:
+ // Used to cancel child channel if we hit errors during creating and
+ // AsyncOpen of nsHttpChannel on the parent.
+ async FailedAsyncOpen(nsresult status);
+
+ // OnStartRequest is sent over PHttpBackgroundChannel. However, sometime we
+ // need to wait for some PContent IPCs, e.g., permission, cookies. Those IPC
+ // are sent just before the background thread OnStartRequest, which is racy.
+ // Therefore, need one main thread IPC event for synchronizing the event
+ // sequence.
+ async OnStartRequestSent();
+
+ // Called to initiate content channel redirect, starts talking to sinks
+ // on the content process and reports result via Redirect2Verify above
+ async Redirect1Begin(uint32_t registrarId,
+ nullable nsIURI newOriginalUri,
+ uint32_t newLoadFlags,
+ uint32_t redirectFlags,
+ ParentLoadInfoForwarderArgs loadInfoForwarder,
+ nsHttpResponseHead responseHead,
+ nullable nsITransportSecurityInfo securityInfo,
+ uint64_t channelId,
+ NetAddr oldPeerAddr,
+ ResourceTimingStructArgs timing);
+
+ // Called if redirect successful so that child can complete setup.
+ async Redirect3Complete();
+
+ // Called if the redirect failed/was vetoed
+ async RedirectFailed(nsresult status);
+
+ // Report a security message to the console associated with this
+ // channel.
+ async ReportSecurityMessage(nsString messageTag, nsString messageCategory);
+
+ // Tell child to delete channel (all IPDL deletes must be done from child to
+ // avoid races: see bug 591708).
+ async DeleteSelf();
+
+ // When CORS blocks the request in the parent process, it doesn't have the
+ // correct window ID, so send the message to the child for logging to the web
+ // console.
+ async LogBlockedCORSRequest(nsString message,
+ nsCString category,
+ bool isWarning);
+
+ async LogMimeTypeMismatch(nsCString messageName,
+ bool warning,
+ nsString url,
+ nsString contentType);
+
+ async OriginalCacheInputStreamAvailable(IPCStream? stream);
+
+
+both:
+ async SetPriority(int16_t priority);
+};
+
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PHttpChannelParams.h b/netwerk/protocol/http/PHttpChannelParams.h
new file mode 100644
index 0000000000..8e2840db68
--- /dev/null
+++ b/netwerk/protocol/http/PHttpChannelParams.h
@@ -0,0 +1,290 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_PHttpChannelParams_h
+#define mozilla_net_PHttpChannelParams_h
+
+#define ALLOW_LATE_NSHTTP_H_INCLUDE 1
+#include "base/basictypes.h"
+
+#include "ipc/IPCMessageUtils.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "nsHttp.h"
+#include "nsHttpHeaderArray.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+
+namespace mozilla {
+namespace net {
+
+struct RequestHeaderTuple {
+ nsCString mHeader;
+ nsCString mValue;
+ bool mMerge;
+ bool mEmpty;
+
+ bool operator==(const RequestHeaderTuple& other) const {
+ return mHeader.Equals(other.mHeader) && mValue.Equals(other.mValue) &&
+ mMerge == other.mMerge && mEmpty == other.mEmpty;
+ }
+};
+
+typedef CopyableTArray<RequestHeaderTuple> RequestHeaderTuples;
+
+} // namespace net
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::net::RequestHeaderTuple> {
+ typedef mozilla::net::RequestHeaderTuple paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mHeader);
+ WriteParam(aWriter, aParam.mValue);
+ WriteParam(aWriter, aParam.mMerge);
+ WriteParam(aWriter, aParam.mEmpty);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mHeader) ||
+ !ReadParam(aReader, &aResult->mValue) ||
+ !ReadParam(aReader, &aResult->mMerge) ||
+ !ReadParam(aReader, &aResult->mEmpty))
+ return false;
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::nsHttpAtom> {
+ typedef mozilla::net::nsHttpAtom paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ // aParam.get() cannot be null.
+ MOZ_ASSERT(aParam.get(), "null nsHTTPAtom value");
+ nsAutoCString value(aParam.get());
+ WriteParam(aWriter, value);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ nsAutoCString value;
+ if (!ReadParam(aReader, &value)) return false;
+
+ *aResult = mozilla::net::nsHttp::ResolveAtom(value);
+ MOZ_ASSERT(aResult->get(), "atom table not initialized");
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::nsHttpHeaderArray::nsEntry> {
+ typedef mozilla::net::nsHttpHeaderArray::nsEntry paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ if (aParam.headerNameOriginal.IsEmpty()) {
+ WriteParam(aWriter, aParam.header);
+ } else {
+ WriteParam(aWriter, aParam.headerNameOriginal);
+ }
+ WriteParam(aWriter, aParam.value);
+ switch (aParam.variety) {
+ case mozilla::net::nsHttpHeaderArray::eVarietyUnknown:
+ WriteParam(aWriter, (uint8_t)0);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyRequestOverride:
+ WriteParam(aWriter, (uint8_t)1);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyRequestDefault:
+ WriteParam(aWriter, (uint8_t)2);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyRequestEnforceDefault:
+ WriteParam(aWriter, (uint8_t)3);
+ break;
+ case mozilla::net::nsHttpHeaderArray::
+ eVarietyResponseNetOriginalAndResponse:
+ WriteParam(aWriter, (uint8_t)4);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginal:
+ WriteParam(aWriter, (uint8_t)5);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyResponse:
+ WriteParam(aWriter, (uint8_t)6);
+ }
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint8_t variety;
+ nsAutoCString header;
+ if (!ReadParam(aReader, &header) || !ReadParam(aReader, &aResult->value) ||
+ !ReadParam(aReader, &variety))
+ return false;
+
+ mozilla::net::nsHttpAtom atom = mozilla::net::nsHttp::ResolveAtom(header);
+ aResult->header = atom;
+ if (!header.Equals(atom.get())) {
+ aResult->headerNameOriginal = header;
+ }
+
+ switch (variety) {
+ case 0:
+ aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyUnknown;
+ break;
+ case 1:
+ aResult->variety =
+ mozilla::net::nsHttpHeaderArray::eVarietyRequestOverride;
+ break;
+ case 2:
+ aResult->variety =
+ mozilla::net::nsHttpHeaderArray::eVarietyRequestDefault;
+ break;
+ case 3:
+ aResult->variety =
+ mozilla::net::nsHttpHeaderArray::eVarietyRequestEnforceDefault;
+ break;
+ case 4:
+ aResult->variety = mozilla::net::nsHttpHeaderArray::
+ eVarietyResponseNetOriginalAndResponse;
+ break;
+ case 5:
+ aResult->variety =
+ mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginal;
+ break;
+ case 6:
+ aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyResponse;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::nsHttpHeaderArray> {
+ typedef mozilla::net::nsHttpHeaderArray paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ paramType& p = const_cast<paramType&>(aParam);
+
+ WriteParam(aWriter, p.mHeaders);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mHeaders)) return false;
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::nsHttpRequestHead> {
+ typedef mozilla::net::nsHttpRequestHead paramType;
+
+ static void Write(MessageWriter* aWriter,
+ const paramType& aParam) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ aParam.Enter();
+ WriteParam(aWriter, aParam.mHeaders);
+ WriteParam(aWriter, aParam.mMethod);
+ WriteParam(aWriter, static_cast<uint32_t>(aParam.mVersion));
+ WriteParam(aWriter, aParam.mRequestURI);
+ WriteParam(aWriter, aParam.mPath);
+ WriteParam(aWriter, aParam.mOrigin);
+ WriteParam(aWriter, static_cast<uint8_t>(aParam.mParsedMethod));
+ WriteParam(aWriter, aParam.mHTTPS);
+ aParam.Exit();
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint32_t version;
+ uint8_t method;
+ aResult->Enter();
+ if (!ReadParam(aReader, &aResult->mHeaders) ||
+ !ReadParam(aReader, &aResult->mMethod) ||
+ !ReadParam(aReader, &version) ||
+ !ReadParam(aReader, &aResult->mRequestURI) ||
+ !ReadParam(aReader, &aResult->mPath) ||
+ !ReadParam(aReader, &aResult->mOrigin) ||
+ !ReadParam(aReader, &method) || !ReadParam(aReader, &aResult->mHTTPS)) {
+ aResult->Exit();
+ return false;
+ }
+
+ aResult->mVersion = static_cast<mozilla::net::HttpVersion>(version);
+ aResult->mParsedMethod =
+ static_cast<mozilla::net::nsHttpRequestHead::ParsedMethodType>(method);
+ aResult->Exit();
+ return true;
+ }
+};
+
+// Note that the code below MUST be synchronized with the code in
+// nsHttpRequestHead's copy constructor.
+template <>
+struct ParamTraits<mozilla::net::nsHttpResponseHead> {
+ typedef mozilla::net::nsHttpResponseHead paramType;
+
+ static void Write(MessageWriter* aWriter,
+ const paramType& aParam) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ aParam.Enter();
+ WriteParam(aWriter, aParam.mHeaders);
+ WriteParam(aWriter, static_cast<uint32_t>(aParam.mVersion));
+ WriteParam(aWriter, aParam.mStatus);
+ WriteParam(aWriter, aParam.mStatusText);
+ WriteParam(aWriter, aParam.mContentLength);
+ WriteParam(aWriter, aParam.mContentType);
+ WriteParam(aWriter, aParam.mContentCharset);
+ WriteParam(aWriter, aParam.mHasCacheControl);
+ WriteParam(aWriter, aParam.mCacheControlPublic);
+ WriteParam(aWriter, aParam.mCacheControlPrivate);
+ WriteParam(aWriter, aParam.mCacheControlNoStore);
+ WriteParam(aWriter, aParam.mCacheControlNoCache);
+ WriteParam(aWriter, aParam.mCacheControlImmutable);
+ WriteParam(aWriter, aParam.mCacheControlStaleWhileRevalidateSet);
+ WriteParam(aWriter, aParam.mCacheControlStaleWhileRevalidate);
+ WriteParam(aWriter, aParam.mCacheControlMaxAgeSet);
+ WriteParam(aWriter, aParam.mCacheControlMaxAge);
+ WriteParam(aWriter, aParam.mPragmaNoCache);
+ aParam.Exit();
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint32_t version;
+ aResult->Enter();
+ if (!ReadParam(aReader, &aResult->mHeaders) ||
+ !ReadParam(aReader, &version) ||
+ !ReadParam(aReader, &aResult->mStatus) ||
+ !ReadParam(aReader, &aResult->mStatusText) ||
+ !ReadParam(aReader, &aResult->mContentLength) ||
+ !ReadParam(aReader, &aResult->mContentType) ||
+ !ReadParam(aReader, &aResult->mContentCharset) ||
+ !ReadParam(aReader, &aResult->mHasCacheControl) ||
+ !ReadParam(aReader, &aResult->mCacheControlPublic) ||
+ !ReadParam(aReader, &aResult->mCacheControlPrivate) ||
+ !ReadParam(aReader, &aResult->mCacheControlNoStore) ||
+ !ReadParam(aReader, &aResult->mCacheControlNoCache) ||
+ !ReadParam(aReader, &aResult->mCacheControlImmutable) ||
+ !ReadParam(aReader, &aResult->mCacheControlStaleWhileRevalidateSet) ||
+ !ReadParam(aReader, &aResult->mCacheControlStaleWhileRevalidate) ||
+ !ReadParam(aReader, &aResult->mCacheControlMaxAgeSet) ||
+ !ReadParam(aReader, &aResult->mCacheControlMaxAge) ||
+ !ReadParam(aReader, &aResult->mPragmaNoCache)) {
+ aResult->Exit();
+ return false;
+ }
+
+ aResult->mVersion = static_cast<mozilla::net::HttpVersion>(version);
+ aResult->Exit();
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_net_PHttpChannelParams_h
diff --git a/netwerk/protocol/http/PHttpConnectionMgr.ipdl b/netwerk/protocol/http/PHttpConnectionMgr.ipdl
new file mode 100644
index 0000000000..dfd46bc3cb
--- /dev/null
+++ b/netwerk/protocol/http/PHttpConnectionMgr.ipdl
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PAltSvcTransaction;
+include protocol PSocketProcess;
+include protocol PHttpTransaction;
+
+include NeckoChannelParams;
+
+include "mozilla/net/ClassOfService.h";
+
+using mozilla::net::ClassOfService from "mozilla/net/ClassOfService.h";
+
+namespace mozilla {
+namespace net {
+
+async protocol PHttpConnectionMgr
+{
+ manager PSocketProcess;
+
+child:
+ async __delete__();
+
+ async DoShiftReloadConnectionCleanupWithConnInfo(HttpConnectionInfoCloneArgs aArgs);
+ async UpdateCurrentBrowserId(uint64_t aId);
+ async AddTransaction(PHttpTransaction aTrans, int32_t aPriority);
+ async AddTransactionWithStickyConn(PHttpTransaction aTrans, int32_t aPriority,
+ PHttpTransaction aTransWithStickyConn);
+ async RescheduleTransaction(PHttpTransaction aTrans, int32_t aPriority);
+ async UpdateClassOfServiceOnTransaction(PHttpTransaction aTrans,
+ ClassOfService aClassOfService);
+ async CancelTransaction(PHttpTransaction aTrans, nsresult aReason);
+ async SpeculativeConnect(HttpConnectionInfoCloneArgs aConnInfo,
+ SpeculativeConnectionOverriderArgs? aOverriderArgs,
+ uint32_t aCaps, PAltSvcTransaction? aTrans,
+ bool aFetchHTTPSRR);
+ async StartWebSocketConnection(PHttpTransaction aTransWithStickyConn,
+ uint32_t aListenerId);
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PHttpTransaction.ipdl b/netwerk/protocol/http/PHttpTransaction.ipdl
new file mode 100644
index 0000000000..670dc296ce
--- /dev/null
+++ b/netwerk/protocol/http/PHttpTransaction.ipdl
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PSocketProcess;
+include protocol PInputChannelThrottleQueue;
+
+include IPCStream;
+include NeckoChannelParams;
+
+include "mozilla/ipc/TransportSecurityInfoUtils.h";
+include "mozilla/net/NeckoMessageUtils.h";
+include "mozilla/net/ClassOfService.h";
+
+using class mozilla::net::nsHttpRequestHead from "nsHttpRequestHead.h";
+using class mozilla::net::nsHttpHeaderArray from "nsHttpHeaderArray.h";
+using mozilla::net::NetAddr from "mozilla/net/DNS.h";
+using mozilla::net::ClassOfService from "mozilla/net/ClassOfService.h";
+[RefCounted] using class nsITransportSecurityInfo from "nsITransportSecurityInfo.h";
+using mozilla::net::TRRSkippedReason from "nsITRRSkipReason.h";
+using nsIRequest::TRRMode from "nsIRequest.h";
+
+namespace mozilla {
+namespace net {
+
+struct H2PushedStreamArg {
+ PHttpTransaction transWithPushedStream;
+ uint32_t pushedStreamId;
+};
+
+struct NetworkAddressArg {
+ NetAddr selfAddr;
+ NetAddr peerAddr;
+ bool resolvedByTRR;
+ TRRMode mode;
+ TRRSkippedReason trrSkipReason;
+ bool echConfigUsed;
+};
+
+protocol PHttpTransaction
+{
+ manager PSocketProcess;
+
+parent:
+ async OnStartRequest(nsresult status,
+ nsHttpResponseHead? responseHead,
+ nullable nsITransportSecurityInfo securityInfo,
+ bool proxyConnectFailed,
+ TimingStructArgs timings,
+ int32_t proxyConnectResponseCode,
+ uint8_t[] dataForSniffer,
+ nsCString? altSvcUsed,
+ bool dataToChildProcess,
+ bool restarted,
+ uint32_t HTTPSSVCReceivedStage,
+ bool supportsHttp3,
+ TRRMode trrMode,
+ TRRSkippedReason trrSkipReason,
+ uint32_t caps,
+ TimeStamp onStartRequestStart);
+ async OnTransportStatus(nsresult status,
+ int64_t progress,
+ int64_t progressMax,
+ NetworkAddressArg? networkAddressArg);
+ async OnDataAvailable(nsCString data,
+ uint64_t offset,
+ uint32_t count,
+ TimeStamp onDataAvailableStart);
+ async OnStopRequest(nsresult status,
+ bool responseIsComplete,
+ int64_t transferSize,
+ TimingStructArgs timings,
+ nsHttpHeaderArray? responseTrailers,
+ TransactionObserverResult? transactionObserverResult,
+ TimeStamp lastActiveTabOptimization,
+ HttpConnectionInfoCloneArgs connInfoArgs,
+ TimeStamp onStopRequestStart);
+ async OnInitFailed(nsresult status);
+ async OnH2PushStream(uint32_t pushedStreamId,
+ nsCString resourceUrl,
+ nsCString requestString);
+ async EarlyHint(nsCString linkHeader, nsCString referrerPolicy, nsCString cspHeader);
+
+child:
+ async __delete__();
+ async Init(uint32_t caps,
+ HttpConnectionInfoCloneArgs aArgs,
+ nsHttpRequestHead reqHeaders,
+ IPCStream? requestBody,
+ uint64_t reqContentLength,
+ bool reqBodyIncludesHeaders,
+ uint64_t topLevelOuterContentWindowId,
+ uint8_t httpTrafficCategory,
+ uint64_t requestContextID,
+ ClassOfService classOfService,
+ uint32_t initialRwin,
+ bool responseTimeoutEnabled,
+ uint64_t channelId,
+ bool hasTransactionObserver,
+ H2PushedStreamArg? pushedStreamArg,
+ PInputChannelThrottleQueue? throttleQueue,
+ bool aIsDocumentLoad,
+ TimeStamp aRedirectStart,
+ TimeStamp aRedirectEnd);
+
+ async CancelPump(nsresult status);
+ async SuspendPump();
+ async ResumePump();
+
+ async SetDNSWasRefreshed();
+ async DontReuseConnection();
+ async SetH2WSConnRefTaken();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PSpdyPush.h b/netwerk/protocol/http/PSpdyPush.h
new file mode 100644
index 0000000000..bea6d6be7c
--- /dev/null
+++ b/netwerk/protocol/http/PSpdyPush.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// SPDY Server Push
+
+/*
+ A pushed stream is put into a memory buffer (The SpdyPushTransactionBuffer)
+ and spooled there until a GET is made that can be matched up with it. At
+ that time we have two spdy streams - the GET (aka the sink) and the PUSH
+ (aka the source). Data is copied between those two streams for the lifetime
+ of the transaction. This is true even if the transaction buffer is empty,
+ partly complete, or totally loaded at the time the GET correspondence is made.
+
+ correspondence is done through a hash table of the full url, the spdy session,
+ and the load group. The load group is implicit because that's where the
+ hash is stored, the other items comprise the hash key.
+
+ Pushed streams are subject to aggressive flow control before they are matched
+ with a GET at which point flow control is effectively disabled to match the
+ client pull behavior.
+*/
+
+#ifndef mozilla_net_SpdyPush_Public_h
+#define mozilla_net_SpdyPush_Public_h
+
+#include "nsTHashMap.h"
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+namespace net {
+
+class Http2PushedStream;
+
+// One cache per load group
+class SpdyPushCache {
+ public:
+ // The cache holds only weak pointers - no references
+ SpdyPushCache() = default;
+ virtual ~SpdyPushCache();
+ [[nodiscard]] bool RegisterPushedStreamHttp2(const nsCString& key,
+ Http2PushedStream* stream);
+ Http2PushedStream* RemovePushedStreamHttp2(const nsCString& key);
+ Http2PushedStream* RemovePushedStreamHttp2ByID(const nsCString& key,
+ const uint32_t& streamID);
+
+ private:
+ nsTHashMap<nsCStringHashKey, Http2PushedStream*> mHashHttp2;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_SpdyPush_Public_h
diff --git a/netwerk/protocol/http/ParentChannelListener.cpp b/netwerk/protocol/http/ParentChannelListener.cpp
new file mode 100644
index 0000000000..d35a0a9a12
--- /dev/null
+++ b/netwerk/protocol/http/ParentChannelListener.cpp
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "ParentChannelListener.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/ServiceWorkerInterceptController.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/net/RedirectChannelRegistrar.h"
+#include "mozilla/SchedulerGroup.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIPrompt.h"
+#include "nsISecureBrowserUI.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIWindowWatcher.h"
+#include "nsQueryObject.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIPromptFactory.h"
+#include "Element.h"
+#include "nsILoginManagerAuthPrompter.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/LoadURIOptionsBinding.h"
+#include "nsIWebNavigation.h"
+
+using mozilla::dom::ServiceWorkerInterceptController;
+
+namespace mozilla {
+namespace net {
+
+ParentChannelListener::ParentChannelListener(
+ nsIStreamListener* aListener,
+ dom::CanonicalBrowsingContext* aBrowsingContext)
+ : mNextListener(aListener), mBrowsingContext(aBrowsingContext) {
+ LOG(("ParentChannelListener::ParentChannelListener [this=%p, next=%p]", this,
+ aListener));
+
+ mInterceptController = new ServiceWorkerInterceptController();
+}
+
+ParentChannelListener::~ParentChannelListener() {
+ LOG(("ParentChannelListener::~ParentChannelListener %p", this));
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(ParentChannelListener)
+NS_IMPL_RELEASE(ParentChannelListener)
+NS_INTERFACE_MAP_BEGIN(ParentChannelListener)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
+ NS_INTERFACE_MAP_ENTRY(nsINetworkInterceptController)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAuthPromptProvider, mBrowsingContext)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(ParentChannelListener)
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ParentChannelListener::OnStartRequest(nsIRequest* aRequest) {
+ if (!mNextListener) return NS_ERROR_UNEXPECTED;
+
+ // If we're not a multi-part channel, then we can drop mListener and break the
+ // reference cycle. If we are, then this might be called again, so wait for
+ // OnAfterLastPart instead.
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
+ if (multiPartChannel) {
+ mIsMultiPart = true;
+ }
+
+ LOG(("ParentChannelListener::OnStartRequest [this=%p]\n", this));
+ return mNextListener->OnStartRequest(aRequest);
+}
+
+NS_IMETHODIMP
+ParentChannelListener::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ if (!mNextListener) return NS_ERROR_UNEXPECTED;
+
+ LOG(("ParentChannelListener::OnStopRequest: [this=%p status=%" PRIu32 "]\n",
+ this, static_cast<uint32_t>(aStatusCode)));
+ nsresult rv = mNextListener->OnStopRequest(aRequest, aStatusCode);
+
+ if (!mIsMultiPart) {
+ mNextListener = nullptr;
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ParentChannelListener::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ if (!mNextListener) return NS_ERROR_UNEXPECTED;
+
+ LOG(("ParentChannelListener::OnDataAvailable [this=%p]\n", this));
+ return mNextListener->OnDataAvailable(aRequest, aInputStream, aOffset,
+ aCount);
+}
+
+NS_IMETHODIMP
+ParentChannelListener::OnDataFinished(nsresult aStatus) {
+ if (!mNextListener) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mNextListener);
+ if (listener) {
+ return listener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsIMultiPartChannelListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ParentChannelListener::OnAfterLastPart(nsresult aStatus) {
+ nsCOMPtr<nsIMultiPartChannelListener> multiListener =
+ do_QueryInterface(mNextListener);
+ if (multiListener) {
+ multiListener->OnAfterLastPart(aStatus);
+ }
+
+ mNextListener = nullptr;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ParentChannelListener::GetInterface(const nsIID& aIID, void** result) {
+ if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController))) {
+ return QueryInterface(aIID, result);
+ }
+
+ if (mBrowsingContext && aIID.Equals(NS_GET_IID(nsIPrompt))) {
+ nsCOMPtr<dom::Element> frameElement =
+ mBrowsingContext->Top()->GetEmbedderElement();
+ if (frameElement) {
+ nsCOMPtr<nsPIDOMWindowOuter> win = frameElement->OwnerDoc()->GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_NO_INTERFACE);
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+
+ if (NS_WARN_IF(!NS_SUCCEEDED(rv))) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsCOMPtr<nsIPrompt> prompt;
+ rv = wwatch->GetNewPrompter(win, getter_AddRefs(prompt));
+ if (NS_WARN_IF(!NS_SUCCEEDED(rv))) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ prompt.forget(result);
+ return NS_OK;
+ }
+ }
+
+ if (mBrowsingContext && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ aIID.Equals(NS_GET_IID(nsIAuthPrompt2)))) {
+ nsresult rv =
+ GetAuthPrompt(nsIAuthPromptProvider::PROMPT_NORMAL, aIID, result);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> ir;
+ if (mNextListener && NS_SUCCEEDED(CallQueryInterface(mNextListener.get(),
+ getter_AddRefs(ir)))) {
+ return ir->GetInterface(aIID, result);
+ }
+
+ return NS_NOINTERFACE;
+}
+
+void ParentChannelListener::SetListenerAfterRedirect(
+ nsIStreamListener* aListener) {
+ mNextListener = aListener;
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsINetworkInterceptController
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ParentChannelListener::ShouldPrepareForIntercept(nsIURI* aURI,
+ nsIChannel* aChannel,
+ bool* aShouldIntercept) {
+ // If parent-side interception is enabled just forward to the real
+ // network controler.
+ if (mInterceptController) {
+ return mInterceptController->ShouldPrepareForIntercept(aURI, aChannel,
+ aShouldIntercept);
+ }
+ *aShouldIntercept = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ParentChannelListener::ChannelIntercepted(nsIInterceptedChannel* aChannel) {
+ // If parent-side interception is enabled just forward to the real
+ // network controler.
+ if (mInterceptController) {
+ return mInterceptController->ChannelIntercepted(aChannel);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsIAuthPromptProvider
+//
+
+NS_IMETHODIMP
+ParentChannelListener::GetAuthPrompt(uint32_t aPromptReason, const nsIID& iid,
+ void** aResult) {
+ if (!mBrowsingContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ // we're either allowing auth, or it's a proxy request
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ RefPtr<dom::Element> frame = mBrowsingContext->Top()->GetEmbedderElement();
+ if (frame) window = frame->OwnerDoc()->GetWindow();
+
+ // Get an auth prompter for our window so that the parenting
+ // of the dialogs works as it should when using tabs.
+ nsCOMPtr<nsISupports> prompt;
+ rv = wwatch->GetPrompt(window, iid, getter_AddRefs(prompt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoginManagerAuthPrompter> prompter = do_QueryInterface(prompt);
+ if (prompter) {
+ prompter->SetBrowser(frame);
+ }
+
+ *aResult = prompt.forget().take();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ParentChannelListener::nsIThreadRetargetableStreamListener
+//
+
+NS_IMETHODIMP
+ParentChannelListener::CheckListenerChain() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mNextListener);
+ if (!listener) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return listener->CheckListenerChain();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ParentChannelListener.h b/netwerk/protocol/http/ParentChannelListener.h
new file mode 100644
index 0000000000..cd0fbfeadb
--- /dev/null
+++ b/netwerk/protocol/http/ParentChannelListener.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ParentChannelListener_h
+#define mozilla_net_ParentChannelListener_h
+
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIMultiPartChannel.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+#define PARENT_CHANNEL_LISTENER \
+ { \
+ 0xa4e2c10c, 0xceba, 0x457f, { \
+ 0xa8, 0x0d, 0x78, 0x2b, 0x23, 0xba, 0xbd, 0x16 \
+ } \
+ }
+
+class ParentChannelListener final : public nsIInterfaceRequestor,
+ public nsIMultiPartChannelListener,
+ public nsINetworkInterceptController,
+ public nsIThreadRetargetableStreamListener,
+ private nsIAuthPromptProvider {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIMULTIPARTCHANNELLISTENER
+ NS_DECL_NSINETWORKINTERCEPTCONTROLLER
+ NS_DECL_NSIAUTHPROMPTPROVIDER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(PARENT_CHANNEL_LISTENER)
+
+ explicit ParentChannelListener(
+ nsIStreamListener* aListener,
+ dom::CanonicalBrowsingContext* aBrowsingContext);
+
+ // Called to set a new listener which replaces the old one after a redirect.
+ void SetListenerAfterRedirect(nsIStreamListener* aListener);
+
+ dom::CanonicalBrowsingContext* GetBrowsingContext() const {
+ return mBrowsingContext;
+ }
+
+ private:
+ virtual ~ParentChannelListener();
+
+ // Can be the original HttpChannelParent that created this object (normal
+ // case), a different {HTTP|FTP}ChannelParent that we've been redirected to,
+ // or some other listener that we have been diverted to via
+ // nsIDivertableChannel.
+ nsCOMPtr<nsIStreamListener> mNextListener;
+
+ // This will be populated with a real network controller if parent-side
+ // interception is enabled.
+ nsCOMPtr<nsINetworkInterceptController> mInterceptController;
+
+ RefPtr<dom::CanonicalBrowsingContext> mBrowsingContext;
+
+ // True if we received OnStartRequest for a nsIMultiPartChannel, and are
+ // expected AllPartsStopped to be called when complete.
+ bool mIsMultiPart = false;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(ParentChannelListener, PARENT_CHANNEL_LISTENER)
+
+inline nsISupports* ToSupports(ParentChannelListener* aDoc) {
+ return static_cast<nsIInterfaceRequestor*>(aDoc);
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ParentChannelListener_h
diff --git a/netwerk/protocol/http/PendingTransactionInfo.cpp b/netwerk/protocol/http/PendingTransactionInfo.cpp
new file mode 100644
index 0000000000..53c457a4b1
--- /dev/null
+++ b/netwerk/protocol/http/PendingTransactionInfo.cpp
@@ -0,0 +1,136 @@
+/* vim:t ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "PendingTransactionInfo.h"
+#include "NullHttpTransaction.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+namespace mozilla {
+namespace net {
+
+PendingTransactionInfo::~PendingTransactionInfo() {
+ if (mDnsAndSock) {
+ RefPtr<DnsAndConnectSocket> dnsAndSock = do_QueryReferent(mDnsAndSock);
+ LOG(
+ ("PendingTransactionInfo::PendingTransactionInfo "
+ "[trans=%p halfOpen=%p]",
+ mTransaction.get(), dnsAndSock.get()));
+ if (dnsAndSock) {
+ dnsAndSock->Unclaim();
+ }
+ mDnsAndSock = nullptr;
+ } else if (mActiveConn) {
+ RefPtr<HttpConnectionBase> activeConn = do_QueryReferent(mActiveConn);
+ if (activeConn && activeConn->Transaction() &&
+ activeConn->Transaction()->IsNullTransaction()) {
+ NullHttpTransaction* nullTrans =
+ activeConn->Transaction()->QueryNullTransaction();
+ nullTrans->Unclaim();
+ LOG((
+ "PendingTransactionInfo::PendingTransactionInfo - mark %p unclaimed.",
+ activeConn.get()));
+ }
+ }
+}
+
+bool PendingTransactionInfo::IsAlreadyClaimedInitializingConn() {
+ LOG(
+ ("PendingTransactionInfo::IsAlreadyClaimedInitializingConn "
+ "[trans=%p, halfOpen=%p, activeConn=%p]\n",
+ mTransaction.get(), mDnsAndSock.get(), mActiveConn.get()));
+
+ // When this transaction has already established a half-open
+ // connection, we want to prevent any duplicate half-open
+ // connections from being established and bound to this
+ // transaction. Allow only use of an idle persistent connection
+ // (if found) for transactions referred by a half-open connection.
+ bool alreadyDnsAndSockOrWaitingForTLS = false;
+ if (mDnsAndSock) {
+ MOZ_ASSERT(!mActiveConn);
+ RefPtr<DnsAndConnectSocket> dnsAndSock = do_QueryReferent(mDnsAndSock);
+ LOG(
+ ("PendingTransactionInfo::IsAlreadyClaimedInitializingConn "
+ "[trans=%p, dnsAndSock=%p]\n",
+ mTransaction.get(), dnsAndSock.get()));
+ if (dnsAndSock) {
+ alreadyDnsAndSockOrWaitingForTLS = true;
+ } else {
+ // If we have not found the halfOpen socket, remove the pointer.
+ mDnsAndSock = nullptr;
+ }
+ } else if (mActiveConn) {
+ MOZ_ASSERT(!mDnsAndSock);
+ RefPtr<HttpConnectionBase> activeConn = do_QueryReferent(mActiveConn);
+ LOG(
+ ("PendingTransactionInfo::IsAlreadyClaimedInitializingConn "
+ "[trans=%p, activeConn=%p]\n",
+ mTransaction.get(), activeConn.get()));
+ // Check if this transaction claimed a connection that is still
+ // performing tls handshake with a NullHttpTransaction or it is between
+ // finishing tls and reclaiming (When nullTrans finishes tls handshake,
+ // httpConnection does not have a transaction any more and a
+ // ReclaimConnection is dispatched). But if an error occurred the
+ // connection will be closed, it will exist but CanReused will be
+ // false.
+ if (activeConn &&
+ ((activeConn->Transaction() &&
+ activeConn->Transaction()->IsNullTransaction()) ||
+ (!activeConn->Transaction() && activeConn->CanReuse()))) {
+ alreadyDnsAndSockOrWaitingForTLS = true;
+ } else {
+ // If we have not found the connection, remove the pointer.
+ mActiveConn = nullptr;
+ }
+ }
+
+ return alreadyDnsAndSockOrWaitingForTLS;
+}
+
+nsWeakPtr PendingTransactionInfo::ForgetDnsAndConnectSocketAndActiveConn() {
+ nsWeakPtr dnsAndSock = mDnsAndSock;
+
+ mDnsAndSock = nullptr;
+ mActiveConn = nullptr;
+ return dnsAndSock;
+}
+
+void PendingTransactionInfo::RememberDnsAndConnectSocket(
+ DnsAndConnectSocket* sock) {
+ mDnsAndSock =
+ do_GetWeakReference(static_cast<nsISupportsWeakReference*>(sock));
+}
+
+bool PendingTransactionInfo::TryClaimingActiveConn(HttpConnectionBase* conn) {
+ nsAHttpTransaction* activeTrans = conn->Transaction();
+ NullHttpTransaction* nullTrans =
+ activeTrans ? activeTrans->QueryNullTransaction() : nullptr;
+ if (nullTrans && nullTrans->Claim()) {
+ mActiveConn =
+ do_GetWeakReference(static_cast<nsISupportsWeakReference*>(conn));
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ conn->GetTLSSocketControl(getter_AddRefs(tlsSocketControl));
+ if (tlsSocketControl) {
+ Unused << tlsSocketControl->Claim();
+ }
+ return true;
+ }
+ return false;
+}
+
+void PendingTransactionInfo::AddDnsAndConnectSocket(DnsAndConnectSocket* sock) {
+ mDnsAndSock =
+ do_GetWeakReference(static_cast<nsISupportsWeakReference*>(sock));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PendingTransactionInfo.h b/netwerk/protocol/http/PendingTransactionInfo.h
new file mode 100644
index 0000000000..863b43ae03
--- /dev/null
+++ b/netwerk/protocol/http/PendingTransactionInfo.h
@@ -0,0 +1,63 @@
+/* vim:t ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PendingTransactionInfo_h__
+#define PendingTransactionInfo_h__
+
+#include "DnsAndConnectSocket.h"
+
+namespace mozilla {
+namespace net {
+
+class PendingTransactionInfo final : public ARefBase {
+ public:
+ explicit PendingTransactionInfo(nsHttpTransaction* trans)
+ : mTransaction(trans) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PendingTransactionInfo, override)
+
+ void PrintDiagnostics(nsCString& log);
+
+ // Return true if the transaction has claimed a DnsAndConnectSocket or
+ // a connection in TLS handshake phase.
+ bool IsAlreadyClaimedInitializingConn();
+
+ // This function return a weak poointer to DnsAndConnectSocket.
+ // The pointer is used by the caller(ConnectionEntry) to remove the
+ // DnsAndConnectSocket from the internal list. PendingTransactionInfo
+ // cannot perform this opereation.
+ [[nodiscard]] nsWeakPtr ForgetDnsAndConnectSocketAndActiveConn();
+
+ // Remember associated DnsAndConnectSocket.
+ void RememberDnsAndConnectSocket(DnsAndConnectSocket* sock);
+ // Similar as above, but for a ActiveConn that is performing a TLS handshake
+ // and has only a NullTransaction associated.
+ bool TryClaimingActiveConn(HttpConnectionBase* conn);
+ // It is similar as above, but in tihs case the halfOpen is made for this
+ // PendingTransactionInfo and it is already claimed.
+ void AddDnsAndConnectSocket(DnsAndConnectSocket* sock);
+
+ nsHttpTransaction* Transaction() const { return mTransaction; }
+
+ private:
+ RefPtr<nsHttpTransaction> mTransaction;
+ nsWeakPtr mDnsAndSock;
+ nsWeakPtr mActiveConn;
+
+ ~PendingTransactionInfo();
+};
+
+class PendingComparator {
+ public:
+ bool Equals(const PendingTransactionInfo* aPendingTrans,
+ const nsAHttpTransaction* aTrans) const {
+ return aPendingTrans->Transaction() == aTrans;
+ }
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !PendingTransactionInfo_h__
diff --git a/netwerk/protocol/http/PendingTransactionQueue.cpp b/netwerk/protocol/http/PendingTransactionQueue.cpp
new file mode 100644
index 0000000000..9598769cc8
--- /dev/null
+++ b/netwerk/protocol/http/PendingTransactionQueue.cpp
@@ -0,0 +1,287 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "PendingTransactionQueue.h"
+#include "nsHttpHandler.h"
+#include "mozilla/ChaosMode.h"
+
+namespace mozilla {
+namespace net {
+
+static uint64_t TabIdForQueuing(nsAHttpTransaction* transaction) {
+ return gHttpHandler->ActiveTabPriority() ? transaction->BrowserId() : 0;
+}
+
+// This function decides the transaction's order in the pending queue.
+// Given two transactions t1 and t2, returning true means that t2 is
+// more important than t1 and thus should be dispatched first.
+static bool TransactionComparator(nsHttpTransaction* t1,
+ nsHttpTransaction* t2) {
+ bool t1Blocking =
+ t1->Caps() & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED);
+ bool t2Blocking =
+ t2->Caps() & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED);
+
+ if (t1Blocking > t2Blocking) {
+ return false;
+ }
+
+ if (t2Blocking > t1Blocking) {
+ return true;
+ }
+
+ return t1->Priority() >= t2->Priority();
+}
+
+void PendingTransactionQueue::InsertTransactionNormal(
+ PendingTransactionInfo* info,
+ bool aInsertAsFirstForTheSamePriority /*= false*/) {
+ LOG(
+ ("PendingTransactionQueue::InsertTransactionNormal"
+ " trans=%p, bid=%" PRIu64 "\n",
+ info->Transaction(), info->Transaction()->BrowserId()));
+
+ uint64_t windowId = TabIdForQueuing(info->Transaction());
+ nsTArray<RefPtr<PendingTransactionInfo>>* const infoArray =
+ mPendingTransactionTable.GetOrInsertNew(windowId);
+
+ // XXX At least if a new array was empty before, this isn't efficient, as it
+ // does an insert-sort. It would be better to just append all elements and
+ // then sort.
+ InsertTransactionSorted(*infoArray, info, aInsertAsFirstForTheSamePriority);
+}
+
+void PendingTransactionQueue::InsertTransactionSorted(
+ nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority /*= false*/) {
+ // insert the transaction into the front of the queue based on following
+ // rules:
+ // 1. The transaction has NS_HTTP_LOAD_AS_BLOCKING or NS_HTTP_LOAD_UNBLOCKED.
+ // 2. The transaction's priority is higher.
+ //
+ // search in reverse order under the assumption that many of the
+ // existing transactions will have the same priority (usually 0).
+
+ nsHttpTransaction* trans = pendingTransInfo->Transaction();
+
+ for (int32_t i = pendingQ.Length() - 1; i >= 0; --i) {
+ nsHttpTransaction* t = pendingQ[i]->Transaction();
+ if (TransactionComparator(trans, t)) {
+ if (ChaosMode::isActive(ChaosFeature::NetworkScheduling) ||
+ aInsertAsFirstForTheSamePriority) {
+ int32_t samePriorityCount;
+ for (samePriorityCount = 0; i - samePriorityCount >= 0;
+ ++samePriorityCount) {
+ if (pendingQ[i - samePriorityCount]->Transaction()->Priority() !=
+ trans->Priority()) {
+ break;
+ }
+ }
+ if (aInsertAsFirstForTheSamePriority) {
+ i -= samePriorityCount;
+ } else {
+ // skip over 0...all of the elements with the same priority.
+ i -= ChaosMode::randomUint32LessThan(samePriorityCount + 1);
+ }
+ }
+ pendingQ.InsertElementAt(i + 1, pendingTransInfo);
+ return;
+ }
+ }
+ pendingQ.InsertElementAt(0, pendingTransInfo);
+}
+
+void PendingTransactionQueue::InsertTransaction(
+ PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority /* = false */) {
+ if (pendingTransInfo->Transaction()->Caps() & NS_HTTP_URGENT_START) {
+ LOG(
+ (" adding transaction to pending queue "
+ "[trans=%p urgent-start-count=%zu]\n",
+ pendingTransInfo->Transaction(), mUrgentStartQ.Length() + 1));
+ // put this transaction on the urgent-start queue...
+ InsertTransactionSorted(mUrgentStartQ, pendingTransInfo);
+ } else {
+ LOG(
+ (" adding transaction to pending queue "
+ "[trans=%p pending-count=%zu]\n",
+ pendingTransInfo->Transaction(), PendingQueueLength() + 1));
+ // put this transaction on the pending queue...
+ InsertTransactionNormal(pendingTransInfo);
+ }
+}
+
+nsTArray<RefPtr<PendingTransactionInfo>>*
+PendingTransactionQueue::GetTransactionPendingQHelper(
+ nsAHttpTransaction* trans) {
+ nsTArray<RefPtr<PendingTransactionInfo>>* pendingQ = nullptr;
+ int32_t caps = trans->Caps();
+ if (caps & NS_HTTP_URGENT_START) {
+ pendingQ = &(mUrgentStartQ);
+ } else {
+ pendingQ = mPendingTransactionTable.Get(TabIdForQueuing(trans));
+ }
+ return pendingQ;
+}
+
+void PendingTransactionQueue::AppendPendingUrgentStartQ(
+ nsTArray<RefPtr<PendingTransactionInfo>>& result) {
+ result.InsertElementsAt(0, mUrgentStartQ.Elements(), mUrgentStartQ.Length());
+ mUrgentStartQ.Clear();
+}
+
+void PendingTransactionQueue::AppendPendingQForFocusedWindow(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount) {
+ nsTArray<RefPtr<PendingTransactionInfo>>* infoArray = nullptr;
+ if (!mPendingTransactionTable.Get(windowId, &infoArray)) {
+ result.Clear();
+ return;
+ }
+
+ uint32_t countToAppend = maxCount;
+ countToAppend = countToAppend > infoArray->Length() || countToAppend == 0
+ ? infoArray->Length()
+ : countToAppend;
+
+ result.InsertElementsAt(result.Length(), infoArray->Elements(),
+ countToAppend);
+ infoArray->RemoveElementsAt(0, countToAppend);
+
+ LOG(
+ ("PendingTransactionQueue::AppendPendingQForFocusedWindow, "
+ "pendingQ count=%zu window.count=%zu for focused window (id=%" PRIu64
+ ")\n",
+ result.Length(), infoArray->Length(), windowId));
+}
+
+void PendingTransactionQueue::AppendPendingQForNonFocusedWindows(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount) {
+ // XXX Adjust the order of transactions in a smarter manner.
+ uint32_t totalCount = 0;
+ for (const auto& entry : mPendingTransactionTable) {
+ if (windowId && entry.GetKey() == windowId) {
+ continue;
+ }
+
+ uint32_t count = 0;
+ for (; count < entry.GetWeak()->Length(); ++count) {
+ if (maxCount && totalCount == maxCount) {
+ break;
+ }
+
+ // Because elements in |result| could come from multiple penndingQ,
+ // call |InsertTransactionSorted| to make sure the order is correct.
+ InsertTransactionSorted(result, entry.GetWeak()->ElementAt(count));
+ ++totalCount;
+ }
+ entry.GetWeak()->RemoveElementsAt(0, count);
+
+ if (maxCount && totalCount == maxCount) {
+ if (entry.GetWeak()->Length()) {
+ // There are still some pending transactions for background
+ // tabs but we limit their dispatch. This is considered as
+ // an active tab optimization.
+ nsHttp::NotifyActiveTabLoadOptimization();
+ }
+ break;
+ }
+ }
+}
+
+void PendingTransactionQueue::ReschedTransaction(nsHttpTransaction* aTrans) {
+ nsTArray<RefPtr<PendingTransactionInfo>>* pendingQ =
+ GetTransactionPendingQHelper(aTrans);
+
+ int32_t index =
+ pendingQ ? pendingQ->IndexOf(aTrans, 0, PendingComparator()) : -1;
+ if (index >= 0) {
+ RefPtr<PendingTransactionInfo> pendingTransInfo = (*pendingQ)[index];
+ pendingQ->RemoveElementAt(index);
+ InsertTransactionSorted(*pendingQ, pendingTransInfo);
+ }
+}
+
+void PendingTransactionQueue::RemoveEmptyPendingQ() {
+ for (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) {
+ if (it.UserData()->IsEmpty()) {
+ it.Remove();
+ }
+ }
+}
+
+size_t PendingTransactionQueue::PendingQueueLength() const {
+ size_t length = 0;
+ for (const auto& data : mPendingTransactionTable.Values()) {
+ length += data->Length();
+ }
+
+ return length;
+}
+
+size_t PendingTransactionQueue::PendingQueueLengthForWindow(
+ uint64_t windowId) const {
+ auto* pendingQ = mPendingTransactionTable.Get(windowId);
+ return (pendingQ) ? pendingQ->Length() : 0;
+}
+
+size_t PendingTransactionQueue::UrgentStartQueueLength() {
+ return mUrgentStartQ.Length();
+}
+
+void PendingTransactionQueue::PrintPendingQ() {
+ LOG(("urgent queue ["));
+ for (const auto& info : mUrgentStartQ) {
+ LOG((" %p", info->Transaction()));
+ }
+ for (const auto& entry : mPendingTransactionTable) {
+ LOG(("] window id = %" PRIx64 " queue [", entry.GetKey()));
+ for (const auto& info : *entry.GetWeak()) {
+ LOG((" %p", info->Transaction()));
+ }
+ }
+ LOG(("]"));
+}
+
+void PendingTransactionQueue::Compact() {
+ mUrgentStartQ.Compact();
+ for (const auto& data : mPendingTransactionTable.Values()) {
+ data->Compact();
+ }
+}
+
+void PendingTransactionQueue::CancelAllTransactions(nsresult reason) {
+ for (const auto& pendingTransInfo : mUrgentStartQ) {
+ LOG(("PendingTransactionQueue::CancelAllTransactions %p\n",
+ pendingTransInfo->Transaction()));
+ pendingTransInfo->Transaction()->Close(reason);
+ }
+ mUrgentStartQ.Clear();
+
+ for (const auto& data : mPendingTransactionTable.Values()) {
+ for (const auto& pendingTransInfo : *data) {
+ LOG(("PendingTransactionQueue::CancelAllTransactions %p\n",
+ pendingTransInfo->Transaction()));
+ pendingTransInfo->Transaction()->Close(reason);
+ }
+ data->Clear();
+ }
+
+ mPendingTransactionTable.Clear();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PendingTransactionQueue.h b/netwerk/protocol/http/PendingTransactionQueue.h
new file mode 100644
index 0000000000..0b0d51e2cd
--- /dev/null
+++ b/netwerk/protocol/http/PendingTransactionQueue.h
@@ -0,0 +1,92 @@
+/* vim:t ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PendingTransactionQueue_h__
+#define PendingTransactionQueue_h__
+
+#include "nsClassHashtable.h"
+#include "nsHttpTransaction.h"
+#include "PendingTransactionInfo.h"
+
+namespace mozilla {
+namespace net {
+
+class PendingTransactionQueue {
+ public:
+ PendingTransactionQueue() = default;
+
+ void ReschedTransaction(nsHttpTransaction* aTrans);
+
+ nsTArray<RefPtr<PendingTransactionInfo>>* GetTransactionPendingQHelper(
+ nsAHttpTransaction* trans);
+
+ void InsertTransactionSorted(
+ nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority = false);
+
+ // Add a transaction information into the pending queue in
+ // |mPendingTransactionTable| according to the transaction's
+ // top level outer content window id.
+ void InsertTransaction(PendingTransactionInfo* pendingTransInfo,
+ bool aInsertAsFirstForTheSamePriority = false);
+
+ void AppendPendingUrgentStartQ(
+ nsTArray<RefPtr<PendingTransactionInfo>>& result);
+
+ // Append transactions to the |result| whose window id
+ // is equal to |windowId|.
+ // NOTE: maxCount == 0 will get all transactions in the queue.
+ void AppendPendingQForFocusedWindow(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount = 0);
+
+ // Append transactions whose window id isn't equal to |windowId|.
+ // NOTE: windowId == 0 will get all transactions for both
+ // focused and non-focused windows.
+ void AppendPendingQForNonFocusedWindows(
+ uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
+ uint32_t maxCount = 0);
+
+ // Return the count of pending transactions for all window ids.
+ size_t PendingQueueLength() const;
+ size_t PendingQueueLengthForWindow(uint64_t windowId) const;
+
+ // Remove the empty pendingQ in |mPendingTransactionTable|.
+ void RemoveEmptyPendingQ();
+
+ void PrintDiagnostics(nsCString& log);
+
+ size_t UrgentStartQueueLength();
+
+ void PrintPendingQ();
+
+ void Compact();
+
+ void CancelAllTransactions(nsresult reason);
+
+ ~PendingTransactionQueue() = default;
+
+ private:
+ void InsertTransactionNormal(PendingTransactionInfo* info,
+ bool aInsertAsFirstForTheSamePriority = false);
+
+ nsTArray<RefPtr<PendingTransactionInfo>>
+ mUrgentStartQ; // the urgent start transaction queue
+
+ // This table provides a mapping from top level outer content window id
+ // to a queue of pending transaction information.
+ // The transaction's order in pending queue is decided by whether it's a
+ // blocking transaction and its priority.
+ // Note that the window id could be 0 if the http request
+ // is initialized without a window.
+ nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<PendingTransactionInfo>>>
+ mPendingTransactionTable;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !PendingTransactionQueue_h__
diff --git a/netwerk/protocol/http/QuicSocketControl.cpp b/netwerk/protocol/http/QuicSocketControl.cpp
new file mode 100644
index 0000000000..183b9f5fd5
--- /dev/null
+++ b/netwerk/protocol/http/QuicSocketControl.cpp
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "QuicSocketControl.h"
+
+#include "Http3Session.h"
+#include "SharedCertVerifier.h"
+#include "nsISocketProvider.h"
+#include "nsIWebProgressListener.h"
+#include "nsNSSComponent.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "sslt.h"
+#include "ssl.h"
+
+namespace mozilla {
+namespace net {
+
+QuicSocketControl::QuicSocketControl(const nsCString& aHostName, int32_t aPort,
+ uint32_t aProviderFlags,
+ Http3Session* aHttp3Session)
+ : CommonSocketControl(aHostName, aPort, aProviderFlags) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ mHttp3Session = do_GetWeakReference(
+ static_cast<nsISupportsWeakReference*>(aHttp3Session));
+}
+
+void QuicSocketControl::SetCertVerificationResult(PRErrorCode errorCode) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ SetUsedPrivateDNS(GetProviderFlags() & nsISocketProvider::USED_PRIVATE_DNS);
+
+ if (errorCode) {
+ mFailedVerification = true;
+ SetCanceled(errorCode);
+ }
+
+ CallAuthenticated();
+}
+
+NS_IMETHODIMP
+QuicSocketControl::GetSSLVersionOffered(int16_t* aSSLVersionOffered) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ *aSSLVersionOffered = nsITLSSocketControl::TLS_VERSION_1_3;
+ return NS_OK;
+}
+
+void QuicSocketControl::CallAuthenticated() {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ RefPtr<Http3Session> http3Session = do_QueryReferent(mHttp3Session);
+ if (http3Session) {
+ http3Session->Authenticated(GetErrorCode());
+ }
+}
+
+void QuicSocketControl::HandshakeCompleted() {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ uint32_t state = nsIWebProgressListener::STATE_IS_SECURE;
+
+ // If we're here, the TLS handshake has succeeded. If the overridable error
+ // category is nonzero, the user has added an override for a certificate
+ // error.
+ if (mOverridableErrorCategory.isSome() &&
+ *mOverridableErrorCategory !=
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET) {
+ state |= nsIWebProgressListener::STATE_CERT_USER_OVERRIDDEN;
+ }
+
+ SetSecurityState(state);
+ mHandshakeCompleted = true;
+}
+
+void QuicSocketControl::SetNegotiatedNPN(const nsACString& aValue) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ mNegotiatedNPN = aValue;
+ mNPNCompleted = true;
+}
+
+void QuicSocketControl::SetInfo(uint16_t aCipherSuite,
+ uint16_t aProtocolVersion,
+ uint16_t aKeaGroupName,
+ uint16_t aSignatureScheme, bool aEchAccepted) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ SSLCipherSuiteInfo cipherInfo;
+ if (SSL_GetCipherSuiteInfo(aCipherSuite, &cipherInfo, sizeof cipherInfo) ==
+ SECSuccess) {
+ mCipherSuite.emplace(aCipherSuite);
+ mProtocolVersion.emplace(aProtocolVersion & 0xFF);
+ mKeaGroupName.emplace(getKeaGroupName(aKeaGroupName));
+ mSignatureSchemeName.emplace(getSignatureName(aSignatureScheme));
+ mIsAcceptedEch.emplace(aEchAccepted);
+ }
+}
+
+NS_IMETHODIMP
+QuicSocketControl::GetEchConfig(nsACString& aEchConfig) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ aEchConfig = mEchConfig;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+QuicSocketControl::SetEchConfig(const nsACString& aEchConfig) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ mEchConfig = aEchConfig;
+ RefPtr<Http3Session> http3Session = do_QueryReferent(mHttp3Session);
+ if (http3Session) {
+ http3Session->DoSetEchConfig(mEchConfig);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+QuicSocketControl::GetRetryEchConfig(nsACString& aEchConfig) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ aEchConfig = mRetryEchConfig;
+ return NS_OK;
+}
+
+void QuicSocketControl::SetRetryEchConfig(const nsACString& aEchConfig) {
+ COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
+ mRetryEchConfig = aEchConfig;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/QuicSocketControl.h b/netwerk/protocol/http/QuicSocketControl.h
new file mode 100644
index 0000000000..1042a2d659
--- /dev/null
+++ b/netwerk/protocol/http/QuicSocketControl.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef QuicSocketControl_h
+#define QuicSocketControl_h
+
+#include "CommonSocketControl.h"
+#include "nsIWeakReferenceUtils.h"
+
+namespace mozilla {
+namespace net {
+
+class Http3Session;
+
+// IID for the QuicSocketControl interface
+#define NS_QUICSOCKETCONTROL_IID \
+ { \
+ 0xdbc67fd0, 0x1ac6, 0x457b, { \
+ 0x91, 0x4e, 0x4c, 0x86, 0x60, 0xff, 0x00, 0x69 \
+ } \
+ }
+
+class QuicSocketControl final : public CommonSocketControl {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_QUICSOCKETCONTROL_IID);
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(QuicSocketControl, CommonSocketControl);
+
+ NS_IMETHOD GetSSLVersionOffered(int16_t* aSSLVersionOffered) override;
+
+ QuicSocketControl(const nsCString& aHostName, int32_t aPort,
+ uint32_t aProviderFlags, Http3Session* aHttp3Session);
+
+ void SetNegotiatedNPN(const nsACString& aValue);
+ void SetInfo(uint16_t aCipherSuite, uint16_t aProtocolVersion,
+ uint16_t aKeaGroup, uint16_t aSignatureScheme,
+ bool aEchAccepted);
+
+ void CallAuthenticated();
+
+ void HandshakeCompleted();
+ void SetCertVerificationResult(PRErrorCode errorCode) override;
+
+ NS_IMETHOD GetEchConfig(nsACString& aEchConfig) override;
+ NS_IMETHOD SetEchConfig(const nsACString& aEchConfig) override;
+ NS_IMETHOD GetRetryEchConfig(nsACString& aEchConfig) override;
+ void SetRetryEchConfig(const nsACString& aEchConfig);
+
+ private:
+ ~QuicSocketControl() = default;
+
+ // For Authentication done callback and echConfig.
+ nsWeakPtr mHttp3Session;
+
+ nsCString mEchConfig;
+ nsCString mRetryEchConfig;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(QuicSocketControl, NS_QUICSOCKETCONTROL_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // QuicSocketControl_h
diff --git a/netwerk/protocol/http/README b/netwerk/protocol/http/README
new file mode 100644
index 0000000000..621e9e950c
--- /dev/null
+++ b/netwerk/protocol/http/README
@@ -0,0 +1,119 @@
+ Darin Fisher
+ darin@netscape.com
+ 8/8/2001
+
+ HTTP DESIGN NOTES
+
+
+CLASS BREAKDOWN
+
+ nsHttpHandler
+ - implements nsIProtocolHandler
+ - manages preferences
+ - owns the authentication cache
+ - holds references to frequently used services
+
+ nsHttpChannel
+ - implements nsIHttpChannel
+ - talks to the cache
+ - initiates http transactions
+ - processes http response codes
+ - intercepts progress notifications
+
+ nsHttpConnection
+ - implements nsIStreamListener & nsIStreamProvider
+ - talks to the socket transport service
+ - feeds data to its transaction object
+ - routes progress notifications
+
+ nsHttpConnectionInfo
+ - identifies a connection
+
+ nsHttpTransaction
+ - implements nsIRequest
+ - encapsulates a http request and response
+ - parses incoming data
+
+ nsHttpChunkedDecoder
+ - owned by a transaction
+ - removes chunked decoding
+
+ nsHttpRequestHead
+ - owns a nsHttpHeaderArray
+ - knows how to fill a request buffer
+
+ nsHttpResponseHead
+ - owns a nsHttpHeaderArray
+ - knows how to parse response lines
+ - performs common header manipulations/calculations
+
+ nsHttpHeaderArray
+ - stores http "<header>:<value>" pairs
+
+ nsHttpAuthCache
+ - stores authentication credentials for http auth domains
+
+ nsHttpBasicAuth
+ - implements nsIHttpAuthenticator
+ - generates BASIC auth credentials from user:pass
+
+
+ATOMS
+
+ nsHttp:: (header namespace)
+
+ eg. nsHttp::Content_Length
+
+
+TRANSACTION MODEL
+
+ InitiateTransaction -> ActivateConnection -> AsyncWrite, AsyncRead
+
+ The channel creates transactions, and passes them to the handler via
+ InitiateTransaction along with a nsHttpConnectionInfo object
+ identifying the requested connection. The handler either dispatches
+ the transaction immediately or queues it up to be dispatched later,
+ depending on whether or not the limit on the number of connections
+ to the requested server has been reached. Once the transaction can
+ be run, the handler looks for an idle connection or creates a new
+ connection, and then (re)activates the connection, assigning it the
+ new transaction.
+
+ Once activated the connection ensures that it has a socket transport,
+ and then calls AsyncWrite and AsyncRead on the socket transport. This
+ begins the process of talking to the server. To minimize buffering,
+ socket transport thread-proxying is completely disabled (using the flags
+ DONT_PROXY_LISTENER | DONT_PROXY_PROVIDER | DONT_PROXY_OBSERVER with
+ both AsyncWrite and AsyncRead). This means that the nsHttpConnection's
+ OnStartRequest, OnDataAvailable, OnDataWritable, and OnStopRequest
+ methods will execute on the socket transport thread.
+
+ The transaction defines (non-virtual) OnDataReadable, OnDataWritable, and
+ OnStopTransaction methods, which the connection calls in response to
+ its OnDataAvailable, OnDataWritable, and OnStopRequest methods, respectively.
+ The transaction owns a nsStreamListenerProxy created by the channel, which
+ it uses to transfer data from the socket thread over to the client's thread.
+ To mimize buffering, the transaction implements nsIInputStream, and passes
+ itself to the stream listener proxy's OnDataAvailable. In this way, we
+ have effectively wedged the response parsing between the socket and the
+ thread proxy's buffer. When read, the transaction turns around and reads
+ from the socket using the buffer passed to it. The transaction scans the
+ buffer for headers, removes them as they are detected, and copies the headers
+ into its nsHttpResponseHead object. The rest of the data remains in the
+ buffer, and is proxied over to the client's thread to be handled first by the
+ http channel and eventually by the client.
+
+ There are several other major design factors, including:
+
+ - transaction cancelation
+ - progress notification
+ - SSL tunneling
+ - chunked decoding
+ - thread safety
+ - premature EOF detection and transaction restarting
+ - pipelining (not yet implemented)
+
+
+CACHING
+
+<EOF>
diff --git a/netwerk/protocol/http/SpeculativeTransaction.cpp b/netwerk/protocol/http/SpeculativeTransaction.cpp
new file mode 100644
index 0000000000..eab54c24d5
--- /dev/null
+++ b/netwerk/protocol/http/SpeculativeTransaction.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "SpeculativeTransaction.h"
+#include "HTTPSRecordResolver.h"
+#include "nsICachingChannel.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+SpeculativeTransaction::SpeculativeTransaction(
+ nsHttpConnectionInfo* aConnInfo, nsIInterfaceRequestor* aCallbacks,
+ uint32_t aCaps, std::function<void(bool)>&& aCallback)
+ : NullHttpTransaction(aConnInfo, aCallbacks, aCaps),
+ mCloseCallback(std::move(aCallback)) {}
+
+already_AddRefed<SpeculativeTransaction>
+SpeculativeTransaction::CreateWithNewConnInfo(nsHttpConnectionInfo* aConnInfo) {
+ RefPtr<SpeculativeTransaction> trans =
+ new SpeculativeTransaction(aConnInfo, mCallbacks, mCaps);
+ trans->mParallelSpeculativeConnectLimit = mParallelSpeculativeConnectLimit;
+ trans->mIgnoreIdle = mIgnoreIdle;
+ trans->mIsFromPredictor = mIsFromPredictor;
+ trans->mAllow1918 = mAllow1918;
+ return trans.forget();
+}
+
+nsresult SpeculativeTransaction::FetchHTTPSRR() {
+ LOG(("SpeculativeTransaction::FetchHTTPSRR [this=%p]", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ RefPtr<HTTPSRecordResolver> resolver = new HTTPSRecordResolver(this);
+ nsCOMPtr<nsICancelable> dnsRequest;
+ return resolver->FetchHTTPSRRInternal(GetCurrentSerialEventTarget(),
+ getter_AddRefs(dnsRequest));
+}
+
+nsresult SpeculativeTransaction::OnHTTPSRRAvailable(
+ nsIDNSHTTPSSVCRecord* aHTTPSSVCRecord,
+ nsISVCBRecord* aHighestPriorityRecord) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("SpeculativeTransaction::OnHTTPSRRAvailable [this=%p]", this));
+
+ if (!aHTTPSSVCRecord || !aHighestPriorityRecord) {
+ gHttpHandler->ConnMgr()->DoSpeculativeConnection(this, false);
+ return NS_OK;
+ }
+
+ RefPtr<nsHttpConnectionInfo> connInfo = ConnectionInfo();
+ RefPtr<nsHttpConnectionInfo> newInfo =
+ connInfo->CloneAndAdoptHTTPSSVCRecord(aHighestPriorityRecord);
+ RefPtr<SpeculativeTransaction> newTrans = CreateWithNewConnInfo(newInfo);
+ gHttpHandler->ConnMgr()->DoSpeculativeConnection(newTrans, false);
+ return NS_OK;
+}
+
+nsresult SpeculativeTransaction::ReadSegments(nsAHttpSegmentReader* aReader,
+ uint32_t aCount,
+ uint32_t* aCountRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mTriedToWrite = true;
+ return NullHttpTransaction::ReadSegments(aReader, aCount, aCountRead);
+}
+
+void SpeculativeTransaction::Close(nsresult aReason) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ NullHttpTransaction::Close(aReason);
+
+ if (mCloseCallback) {
+ mCloseCallback(mTriedToWrite && aReason == NS_BASE_STREAM_CLOSED);
+ mCloseCallback = nullptr;
+ }
+}
+
+void SpeculativeTransaction::InvokeCallback() {
+ if (mCloseCallback) {
+ mCloseCallback(true);
+ mCloseCallback = nullptr;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/SpeculativeTransaction.h b/netwerk/protocol/http/SpeculativeTransaction.h
new file mode 100644
index 0000000000..851fe2b39f
--- /dev/null
+++ b/netwerk/protocol/http/SpeculativeTransaction.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SpeculativeTransaction_h__
+#define SpeculativeTransaction_h__
+
+#include "mozilla/Maybe.h"
+#include "NullHttpTransaction.h"
+
+namespace mozilla {
+namespace net {
+
+class HTTPSRecordResolver;
+
+class SpeculativeTransaction : public NullHttpTransaction {
+ public:
+ SpeculativeTransaction(nsHttpConnectionInfo* aConnInfo,
+ nsIInterfaceRequestor* aCallbacks, uint32_t aCaps,
+ std::function<void(bool)>&& aCallback = nullptr);
+
+ already_AddRefed<SpeculativeTransaction> CreateWithNewConnInfo(
+ nsHttpConnectionInfo* aConnInfo);
+
+ virtual nsresult FetchHTTPSRR() override;
+
+ virtual nsresult OnHTTPSRRAvailable(
+ nsIDNSHTTPSSVCRecord* aHTTPSSVCRecord,
+ nsISVCBRecord* aHighestPriorityRecord) override;
+
+ void SetParallelSpeculativeConnectLimit(uint32_t aLimit) {
+ mParallelSpeculativeConnectLimit.emplace(aLimit);
+ }
+ void SetIgnoreIdle(bool aIgnoreIdle) { mIgnoreIdle.emplace(aIgnoreIdle); }
+ void SetIsFromPredictor(bool aIsFromPredictor) {
+ mIsFromPredictor.emplace(aIsFromPredictor);
+ }
+ void SetAllow1918(bool aAllow1918) { mAllow1918.emplace(aAllow1918); }
+
+ const Maybe<uint32_t>& ParallelSpeculativeConnectLimit() {
+ return mParallelSpeculativeConnectLimit;
+ }
+ const Maybe<bool>& IgnoreIdle() { return mIgnoreIdle; }
+ const Maybe<bool>& IsFromPredictor() { return mIsFromPredictor; }
+ const Maybe<bool>& Allow1918() { return mAllow1918; }
+
+ void Close(nsresult aReason) override;
+ nsresult ReadSegments(nsAHttpSegmentReader* aReader, uint32_t aCount,
+ uint32_t* aCountRead) override;
+ void InvokeCallback();
+
+ protected:
+ virtual ~SpeculativeTransaction() = default;
+
+ private:
+ Maybe<uint32_t> mParallelSpeculativeConnectLimit;
+ Maybe<bool> mIgnoreIdle;
+ Maybe<bool> mIsFromPredictor;
+ Maybe<bool> mAllow1918;
+
+ bool mTriedToWrite = false;
+ std::function<void(bool)> mCloseCallback;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // SpeculativeTransaction_h__
diff --git a/netwerk/protocol/http/TLSTransportLayer.cpp b/netwerk/protocol/http/TLSTransportLayer.cpp
new file mode 100644
index 0000000000..eff46898ea
--- /dev/null
+++ b/netwerk/protocol/http/TLSTransportLayer.cpp
@@ -0,0 +1,866 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "Http2StreamTunnel.h"
+#include "TLSTransportLayer.h"
+#include "nsISocketProvider.h"
+#include "nsITLSSocketControl.h"
+#include "nsQueryObject.h"
+#include "nsSocketProviderService.h"
+#include "nsSocketTransport2.h"
+
+namespace mozilla::net {
+
+//-----------------------------------------------------------------------------
+// TLSTransportLayerInputStream impl
+//-----------------------------------------------------------------------------
+
+NS_IMPL_QUERY_INTERFACE(TLSTransportLayer::InputStreamWrapper, nsIInputStream,
+ nsIAsyncInputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+TLSTransportLayer::InputStreamWrapper::AddRef() { return mTransport->AddRef(); }
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+TLSTransportLayer::InputStreamWrapper::Release() {
+ return mTransport->Release();
+}
+
+TLSTransportLayer::InputStreamWrapper::InputStreamWrapper(
+ nsIAsyncInputStream* aInputStream, TLSTransportLayer* aTransport)
+ : mSocketIn(aInputStream), mTransport(aTransport) {}
+
+NS_IMETHODIMP
+TLSTransportLayer::InputStreamWrapper::Close() {
+ LOG(("TLSTransportLayer::InputStreamWrapper::Close [this=%p]\n", this));
+ return mSocketIn->Close();
+}
+
+NS_IMETHODIMP TLSTransportLayer::InputStreamWrapper::Available(
+ uint64_t* avail) {
+ LOG(("TLSTransportLayer::InputStreamWrapper::Available [this=%p]\n", this));
+ return mSocketIn->Available(avail);
+}
+
+NS_IMETHODIMP TLSTransportLayer::InputStreamWrapper::StreamStatus() {
+ LOG(("TLSTransportLayer::InputStreamWrapper::StreamStatus [this=%p]\n",
+ this));
+ return mSocketIn->StreamStatus();
+}
+
+nsresult TLSTransportLayer::InputStreamWrapper::ReadDirectly(
+ char* buf, uint32_t count, uint32_t* countRead) {
+ LOG(("TLSTransportLayer::InputStreamWrapper::ReadDirectly [this=%p]\n",
+ this));
+ return mSocketIn->Read(buf, count, countRead);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::InputStreamWrapper::Read(char* buf, uint32_t count,
+ uint32_t* countRead) {
+ LOG(("TLSTransportLayer::InputStreamWrapper::Read [this=%p]\n", this));
+
+ *countRead = 0;
+
+ if (NS_FAILED(mStatus)) {
+ return (mStatus == NS_BASE_STREAM_CLOSED) ? NS_OK : mStatus;
+ }
+
+ int32_t bytesRead = PR_Read(mTransport->mFD, buf, count);
+ if (bytesRead > 0) {
+ *countRead = bytesRead;
+ } else if (bytesRead < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ LOG((
+ "TLSTransportLayer::InputStreamWrapper::Read %p PR_Read would block ",
+ this));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ // If reading from the socket succeeded (NS_SUCCEEDED(mStatus)),
+ // but the nss layer encountered an error remember the error.
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = ErrorAccordingToNSPR(code);
+ LOG(("TLSTransportLayer::InputStreamWrapper::Read %p nss error %" PRIx32
+ ".\n",
+ this, static_cast<uint32_t>(mStatus)));
+ }
+ }
+
+ if (NS_SUCCEEDED(mStatus) && !bytesRead) {
+ LOG(
+ ("TLSTransportLayer::InputStreamWrapper::Read %p "
+ "Second layer of TLS stripping results in STREAM_CLOSED\n",
+ this));
+ mStatus = NS_BASE_STREAM_CLOSED;
+ }
+
+ LOG(("TLSTransportLayer::InputStreamWrapper::Read %p rv=%" PRIx32
+ " didread=%d "
+ "2 layers of ssl stripped to plaintext\n",
+ this, static_cast<uint32_t>(mStatus), bytesRead));
+ return mStatus;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::InputStreamWrapper::ReadSegments(nsWriteSegmentFun writer,
+ void* closure,
+ uint32_t count,
+ uint32_t* countRead) {
+ LOG(("TLSTransportLayer::InputStreamWrapper::ReadSegments [this=%p]\n",
+ this));
+ return mSocketIn->ReadSegments(writer, closure, count, countRead);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::InputStreamWrapper::IsNonBlocking(bool* nonblocking) {
+ return mSocketIn->IsNonBlocking(nonblocking);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::InputStreamWrapper::CloseWithStatus(nsresult reason) {
+ LOG(
+ ("TLSTransportLayer::InputStreamWrapper::CloseWithStatus [this=%p "
+ "reason=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(reason)));
+ return mSocketIn->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::InputStreamWrapper::AsyncWait(
+ nsIInputStreamCallback* callback, uint32_t flags, uint32_t amount,
+ nsIEventTarget* target) {
+ LOG(
+ ("TLSTransportLayer::InputStreamWrapper::AsyncWait [this=%p, "
+ "callback=%p]\n",
+ this, callback));
+ mTransport->mInputCallback = callback;
+ // Don't bother to call PR_POLL when |callback| is NULL. We call |AsyncWait|
+ // directly to null out the underlying callback.
+ if (!callback) {
+ return mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+
+ PRPollDesc pd;
+ pd.fd = mTransport->mFD;
+ pd.in_flags = PR_POLL_READ | PR_POLL_EXCEPT;
+ // Only run PR_Poll on the socket thread. Also, make sure this lives at least
+ // as long as that operation.
+ auto DoPoll = [self = RefPtr{this}, pd(pd)]() mutable {
+ int32_t rv = PR_Poll(&pd, 1, PR_INTERVAL_NO_TIMEOUT);
+ LOG(("TLSTransportLayer::InputStreamWrapper::AsyncWait rv=%d", rv));
+ };
+ if (OnSocketThread()) {
+ DoPoll();
+ } else {
+ gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "TLSTransportLayer::InputStreamWrapper::AsyncWait", DoPoll));
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// TLSTransportLayerOutputStream impl
+//-----------------------------------------------------------------------------
+
+NS_IMPL_QUERY_INTERFACE(TLSTransportLayer::OutputStreamWrapper, nsIOutputStream,
+ nsIAsyncOutputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+TLSTransportLayer::OutputStreamWrapper::AddRef() {
+ return mTransport->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+TLSTransportLayer::OutputStreamWrapper::Release() {
+ return mTransport->Release();
+}
+
+TLSTransportLayer::OutputStreamWrapper::OutputStreamWrapper(
+ nsIAsyncOutputStream* aOutputStream, TLSTransportLayer* aTransport)
+ : mSocketOut(aOutputStream), mTransport(aTransport) {}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::Close() {
+ LOG(("TLSTransportLayer::OutputStreamWrapper::Close [this=%p]\n", this));
+ return mSocketOut->Close();
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::Flush() {
+ LOG(("TLSTransportLayerOutputStream::Flush [this=%p]\n", this));
+ return mSocketOut->Flush();
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::StreamStatus() {
+ LOG(("TLSTransportLayerOutputStream::StreamStatus [this=%p]\n", this));
+ return mSocketOut->StreamStatus();
+}
+
+nsresult TLSTransportLayer::OutputStreamWrapper::WriteDirectly(
+ const char* buf, uint32_t count, uint32_t* countWritten) {
+ LOG(
+ ("TLSTransportLayer::OutputStreamWrapper::WriteDirectly [this=%p "
+ "count=%u]\n",
+ this, count));
+ return mSocketOut->Write(buf, count, countWritten);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::Write(const char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ LOG(("TLSTransportLayer::OutputStreamWrapper::Write [this=%p count=%u]\n",
+ this, count));
+
+ *countWritten = 0;
+
+ if (NS_FAILED(mStatus)) {
+ return (mStatus == NS_BASE_STREAM_CLOSED) ? NS_OK : mStatus;
+ }
+
+ int32_t written = PR_Write(mTransport->mFD, buf, count);
+ LOG(
+ ("TLSTransportLayer::OutputStreamWrapper::Write %p PRWrite(%d) = %d "
+ "%d\n",
+ this, count, written, PR_GetError() == PR_WOULD_BLOCK_ERROR));
+
+ if (written > 0) {
+ *countWritten = written;
+ } else if (written < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ LOG(
+ ("TLSTransportLayer::OutputStreamWrapper::Write %p PRWrite would "
+ "block ",
+ this));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // Writing to the socket succeeded, but failed in nss layer.
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = ErrorAccordingToNSPR(code);
+ }
+ }
+
+ return mStatus;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::WriteSegments(nsReadSegmentFun reader,
+ void* closure,
+ uint32_t count,
+ uint32_t* countRead) {
+ return mSocketOut->WriteSegments(reader, closure, count, countRead);
+}
+
+// static
+nsresult TLSTransportLayer::OutputStreamWrapper::WriteFromSegments(
+ nsIInputStream* input, void* closure, const char* fromSegment,
+ uint32_t offset, uint32_t count, uint32_t* countRead) {
+ OutputStreamWrapper* self = (OutputStreamWrapper*)closure;
+ return self->Write(fromSegment, count, countRead);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::WriteFrom(nsIInputStream* stream,
+ uint32_t count,
+ uint32_t* countRead) {
+ return stream->ReadSegments(WriteFromSegments, this, count, countRead);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::IsNonBlocking(bool* nonblocking) {
+ return mSocketOut->IsNonBlocking(nonblocking);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::CloseWithStatus(nsresult reason) {
+ LOG(("OutputStreamWrapper::CloseWithStatus [this=%p reason=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(reason)));
+ return mSocketOut->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OutputStreamWrapper::AsyncWait(
+ nsIOutputStreamCallback* callback, uint32_t flags, uint32_t amount,
+ nsIEventTarget* target) {
+ LOG(
+ ("TLSTransportLayer::OutputStreamWrapper::AsyncWait [this=%p, "
+ "mOutputCallback=%p "
+ "callback=%p]\n",
+ this, mTransport->mOutputCallback.get(), callback));
+ mTransport->mOutputCallback = callback;
+ // Don't bother to call PR_POLL when |callback| is NULL. We call |AsyncWait|
+ // directly to null out the underlying callback.
+ if (!callback) {
+ return mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+
+ PRPollDesc pd;
+ pd.fd = mTransport->mFD;
+ pd.in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT;
+ int32_t rv = PR_Poll(&pd, 1, PR_INTERVAL_NO_TIMEOUT);
+ LOG(("TLSTransportLayer::OutputStreamWrapper::AsyncWait rv=%d", rv));
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// TLSTransportLayer impl
+//-----------------------------------------------------------------------------
+
+static PRDescIdentity sTLSTransportLayerIdentity;
+static PRIOMethods sTLSTransportLayerMethods;
+static PRIOMethods* sTLSTransportLayerMethodsPtr = nullptr;
+
+bool TLSTransportLayer::DispatchRelease() {
+ if (OnSocketThread()) {
+ return false;
+ }
+
+ gSocketTransportService->Dispatch(
+ NewNonOwningRunnableMethod("net::TLSTransportLayer::Release", this,
+ &TLSTransportLayer::Release),
+ NS_DISPATCH_NORMAL);
+
+ return true;
+}
+
+NS_IMPL_ADDREF(TLSTransportLayer)
+NS_IMETHODIMP_(MozExternalRefCountType)
+TLSTransportLayer::Release() {
+ nsrefcnt count = mRefCnt - 1;
+ if (DispatchRelease()) {
+ // Redispatched to the socket thread.
+ return count;
+ }
+
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "TLSTransportLayer");
+
+ if (0 == count) {
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(TLSTransportLayer)
+ NS_INTERFACE_MAP_ENTRY(nsISocketTransport)
+ NS_INTERFACE_MAP_ENTRY(nsITransport)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(TLSTransportLayer)
+NS_INTERFACE_MAP_END
+
+TLSTransportLayer::TLSTransportLayer(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aInputStream,
+ nsIAsyncOutputStream* aOutputStream,
+ nsIInputStreamCallback* aOwner)
+ : mSocketTransport(aTransport),
+ mSocketInWrapper(aInputStream, this),
+ mSocketOutWrapper(aOutputStream, this),
+ mOwner(aOwner) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("TLSTransportLayer ctor this=[%p]", this));
+}
+
+TLSTransportLayer::~TLSTransportLayer() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("TLSTransportLayer dtor this=[%p]", this));
+ if (mFD) {
+ PR_Close(mFD);
+ mFD = nullptr;
+ }
+ mTLSSocketControl = nullptr;
+}
+
+bool TLSTransportLayer::Init(const char* aTLSHost, int32_t aTLSPort) {
+ LOG(("TLSTransportLayer::Init this=[%p]", this));
+ nsCOMPtr<nsISocketProvider> provider;
+ nsCOMPtr<nsISocketProviderService> spserv =
+ nsSocketProviderService::GetOrCreate();
+ if (!spserv) {
+ return false;
+ }
+
+ spserv->GetSocketProvider("ssl", getter_AddRefs(provider));
+ if (!provider) {
+ return false;
+ }
+
+ // Install an NSPR layer to handle getpeername() with a failure. This is kind
+ // of silly, but the default one used by the pipe asserts when called and the
+ // nss code calls it to see if we are connected to a real socket or not.
+ if (!sTLSTransportLayerMethodsPtr) {
+ // one time initialization
+ sTLSTransportLayerIdentity = PR_GetUniqueIdentity("TLSTransportLayer");
+ sTLSTransportLayerMethods = *PR_GetDefaultIOMethods();
+ sTLSTransportLayerMethods.getpeername = GetPeerName;
+ sTLSTransportLayerMethods.getsocketoption = GetSocketOption;
+ sTLSTransportLayerMethods.setsocketoption = SetSocketOption;
+ sTLSTransportLayerMethods.read = Read;
+ sTLSTransportLayerMethods.write = Write;
+ sTLSTransportLayerMethods.send = Send;
+ sTLSTransportLayerMethods.recv = Recv;
+ sTLSTransportLayerMethods.close = Close;
+ sTLSTransportLayerMethods.poll = Poll;
+ sTLSTransportLayerMethodsPtr = &sTLSTransportLayerMethods;
+ }
+
+ mFD = PR_CreateIOLayerStub(sTLSTransportLayerIdentity,
+ &sTLSTransportLayerMethods);
+ if (!mFD) {
+ return false;
+ }
+
+ mFD->secret = reinterpret_cast<PRFilePrivate*>(this);
+
+ return NS_SUCCEEDED(provider->AddToSocket(
+ PR_AF_INET, aTLSHost, aTLSPort, nullptr, OriginAttributes(), 0, 0, mFD,
+ getter_AddRefs(mTLSSocketControl)));
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OnInputStreamReady(nsIAsyncInputStream* in) {
+ nsCOMPtr<nsIInputStreamCallback> callback = std::move(mInputCallback);
+ if (callback) {
+ return callback->OnInputStreamReady(&mSocketInWrapper);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OnOutputStreamReady(nsIAsyncOutputStream* out) {
+ nsCOMPtr<nsIOutputStreamCallback> callback = std::move(mOutputCallback);
+ nsresult rv = NS_OK;
+ if (callback) {
+ rv = callback->OnOutputStreamReady(&mSocketOutWrapper);
+
+ RefPtr<OutputStreamTunnel> tunnel = do_QueryObject(out);
+ if (tunnel) {
+ tunnel->MaybeSetRequestDone(callback);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetKeepaliveEnabled(bool aKeepaliveEnabled) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetKeepaliveEnabled(aKeepaliveEnabled);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetKeepaliveVals(int32_t keepaliveIdleTime,
+ int32_t keepaliveRetryInterval) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetKeepaliveVals(keepaliveIdleTime,
+ keepaliveRetryInterval);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetSecurityCallbacks(
+ nsIInterfaceRequestor** aSecurityCallbacks) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetSecurityCallbacks(aSecurityCallbacks);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetSecurityCallbacks(
+ nsIInterfaceRequestor* aSecurityCallbacks) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mSocketTransport->SetSecurityCallbacks(aSecurityCallbacks);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIInputStream** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIOutputStream** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::Close(nsresult aReason) {
+ LOG(("TLSTransportLayer::Close [this=%p reason=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aReason)));
+
+ mInputCallback = nullptr;
+ mOutputCallback = nullptr;
+ if (mSocketTransport) {
+ mSocketTransport->Close(aReason);
+ mSocketTransport = nullptr;
+ }
+ mSocketInWrapper.AsyncWait(nullptr, 0, 0, nullptr);
+ mSocketOutWrapper.AsyncWait(nullptr, 0, 0, nullptr);
+
+ if (mOwner) {
+ RefPtr<TLSTransportLayer> self = this;
+ Unused << NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "TLSTransportLayer::Close", [self{std::move(self)}]() {
+ nsCOMPtr<nsIInputStreamCallback> inputCallback =
+ std::move(self->mOwner);
+ if (inputCallback) {
+ // This is hack. We need to make
+ // nsHttpConnection::OnInputStreamReady be called, so
+ // nsHttpConnection::CloseTransaction can be called to release the
+ // transaction.
+ Unused << inputCallback->OnInputStreamReady(
+ &self->mSocketInWrapper);
+ }
+ }));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetEventSink(nsITransportEventSink* aSink,
+ nsIEventTarget* aEventTarget) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetEventSink(aSink, aEventTarget);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::Bind(NetAddr* aLocalAddr) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->Bind(aLocalAddr);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetEchConfigUsed(bool* aEchConfigUsed) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetEchConfigUsed(aEchConfigUsed);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetEchConfig(const nsACString& aEchConfig) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetEchConfig(aEchConfig);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::ResolvedByTRR(bool* aResolvedByTRR) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->ResolvedByTRR(aResolvedByTRR);
+}
+
+NS_IMETHODIMP TLSTransportLayer::GetEffectiveTRRMode(
+ nsIRequest::TRRMode* aEffectiveTRRMode) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetEffectiveTRRMode(aEffectiveTRRMode);
+}
+
+NS_IMETHODIMP TLSTransportLayer::GetTrrSkipReason(
+ nsITRRSkipReason::value* aTrrSkipReason) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetTrrSkipReason(aTrrSkipReason);
+}
+
+#define FWD_TS_PTR(fx, ts) \
+ NS_IMETHODIMP \
+ TLSTransportLayer::fx(ts* arg) { \
+ if (!mSocketTransport) return NS_ERROR_FAILURE; \
+ return mSocketTransport->fx(arg); \
+ }
+
+#define FWD_TS_ADDREF(fx, ts) \
+ NS_IMETHODIMP \
+ TLSTransportLayer::fx(ts** arg) { \
+ if (!mSocketTransport) return NS_ERROR_FAILURE; \
+ return mSocketTransport->fx(arg); \
+ }
+
+#define FWD_TS(fx, ts) \
+ NS_IMETHODIMP \
+ TLSTransportLayer::fx(ts arg) { \
+ if (!mSocketTransport) return NS_ERROR_FAILURE; \
+ return mSocketTransport->fx(arg); \
+ }
+
+FWD_TS_PTR(GetKeepaliveEnabled, bool);
+FWD_TS_PTR(GetSendBufferSize, uint32_t);
+FWD_TS(SetSendBufferSize, uint32_t);
+FWD_TS_PTR(GetPort, int32_t);
+FWD_TS_PTR(GetPeerAddr, mozilla::net::NetAddr);
+FWD_TS_PTR(GetSelfAddr, mozilla::net::NetAddr);
+FWD_TS_ADDREF(GetScriptablePeerAddr, nsINetAddr);
+FWD_TS_ADDREF(GetScriptableSelfAddr, nsINetAddr);
+FWD_TS_PTR(IsAlive, bool);
+FWD_TS_PTR(GetConnectionFlags, uint32_t);
+FWD_TS(SetConnectionFlags, uint32_t);
+FWD_TS(SetIsPrivate, bool);
+FWD_TS_PTR(GetTlsFlags, uint32_t);
+FWD_TS(SetTlsFlags, uint32_t);
+FWD_TS_PTR(GetRecvBufferSize, uint32_t);
+FWD_TS(SetRecvBufferSize, uint32_t);
+FWD_TS_PTR(GetResetIPFamilyPreference, bool);
+
+nsresult TLSTransportLayer::GetTlsSocketControl(
+ nsITLSSocketControl** tlsSocketControl) {
+ if (!mTLSSocketControl) {
+ return NS_ERROR_ABORT;
+ }
+
+ *tlsSocketControl = do_AddRef(mTLSSocketControl).take();
+ return NS_OK;
+}
+
+nsresult TLSTransportLayer::GetOriginAttributes(
+ mozilla::OriginAttributes* aOriginAttributes) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetOriginAttributes(aOriginAttributes);
+}
+
+nsresult TLSTransportLayer::SetOriginAttributes(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetOriginAttributes(aOriginAttributes);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetScriptableOriginAttributes(aCx,
+ aOriginAttributes);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetScriptableOriginAttributes(
+ JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetScriptableOriginAttributes(aCx,
+ aOriginAttributes);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetHost(nsACString& aHost) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetHost(aHost);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetTimeout(uint32_t aType, uint32_t* _retval) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetTimeout(aType, _retval);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetTimeout(uint32_t aType, uint32_t aValue) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetTimeout(aType, aValue);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetReuseAddrPort(bool aReuseAddrPort) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetReuseAddrPort(aReuseAddrPort);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetLinger(bool aPolarity, int16_t aTimeout) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetLinger(aPolarity, aTimeout);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetQoSBits(uint8_t* aQoSBits) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::SetQoSBits(uint8_t aQoSBits) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->SetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetRetryDnsIfPossible(bool* aRetry) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetRetryDnsIfPossible(aRetry);
+}
+
+NS_IMETHODIMP
+TLSTransportLayer::GetStatus(nsresult* aStatus) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetStatus(aStatus);
+}
+
+int32_t TLSTransportLayer::OutputInternal(const char* aBuf, int32_t aAmount) {
+ LOG(("TLSTransportLayer::OutputInternal %p %d", this, aAmount));
+
+ uint32_t outCountWrite = 0;
+ nsresult rv = mSocketOutWrapper.WriteDirectly(aBuf, aAmount, &outCountWrite);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ } else {
+ PR_SetError(PR_UNKNOWN_ERROR, 0);
+ }
+ return -1;
+ }
+
+ return outCountWrite;
+}
+
+int32_t TLSTransportLayer::InputInternal(char* aBuf, int32_t aAmount) {
+ LOG(("TLSTransportLayer::InputInternal aAmount=%d\n", aAmount));
+
+ uint32_t outCountRead = 0;
+ nsresult rv = mSocketInWrapper.ReadDirectly(aBuf, aAmount, &outCountRead);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ } else {
+ PR_SetError(PR_UNKNOWN_ERROR, 0);
+ }
+ return -1;
+ }
+ return outCountRead;
+}
+
+PRStatus TLSTransportLayer::GetPeerName(PRFileDesc* aFD, PRNetAddr* addr) {
+ TLSTransportLayer* self = reinterpret_cast<TLSTransportLayer*>(aFD->secret);
+ NetAddr peeraddr;
+ if (NS_FAILED(self->Transport()->GetPeerAddr(&peeraddr))) {
+ return PR_FAILURE;
+ }
+ NetAddrToPRNetAddr(&peeraddr, addr);
+ return PR_SUCCESS;
+}
+
+PRStatus TLSTransportLayer::GetSocketOption(PRFileDesc* aFD,
+ PRSocketOptionData* aOpt) {
+ if (aOpt->option == PR_SockOpt_Nonblocking) {
+ aOpt->value.non_blocking = PR_TRUE;
+ return PR_SUCCESS;
+ }
+ return PR_FAILURE;
+}
+
+PRStatus TLSTransportLayer::SetSocketOption(PRFileDesc* aFD,
+ const PRSocketOptionData* aOpt) {
+ return PR_FAILURE;
+}
+
+PRStatus TLSTransportLayer::Close(PRFileDesc* aFD) { return PR_SUCCESS; }
+
+int32_t TLSTransportLayer::Write(PRFileDesc* aFD, const void* aBuf,
+ int32_t aAmount) {
+ TLSTransportLayer* self = reinterpret_cast<TLSTransportLayer*>(aFD->secret);
+ return self->OutputInternal(static_cast<const char*>(aBuf), aAmount);
+}
+
+int32_t TLSTransportLayer::Send(PRFileDesc* aFD, const void* aBuf,
+ int32_t aAmount, int, PRIntervalTime) {
+ return Write(aFD, aBuf, aAmount);
+}
+
+int32_t TLSTransportLayer::Read(PRFileDesc* aFD, void* aBuf, int32_t aAmount) {
+ TLSTransportLayer* self = reinterpret_cast<TLSTransportLayer*>(aFD->secret);
+ return self->InputInternal(static_cast<char*>(aBuf), aAmount);
+}
+
+int32_t TLSTransportLayer::Recv(PRFileDesc* aFD, void* aBuf, int32_t aAmount,
+ int, PRIntervalTime) {
+ return Read(aFD, aBuf, aAmount);
+}
+
+int16_t TLSTransportLayer::Poll(PRFileDesc* fd, int16_t in_flags,
+ int16_t* out_flags) {
+ LOG(("TLSTransportLayer::Poll fd=%p inf_flags=%d\n", fd, (int)in_flags));
+ *out_flags = in_flags;
+
+ TLSTransportLayer* self = reinterpret_cast<TLSTransportLayer*>(fd->secret);
+ if (!self) {
+ return 0;
+ }
+
+ if (in_flags & PR_POLL_READ) {
+ self->mSocketInWrapper.mSocketIn->AsyncWait(self, 0, 0, nullptr);
+ } else if (in_flags & PR_POLL_WRITE) {
+ self->mSocketOutWrapper.mSocketOut->AsyncWait(self, 0, 0, nullptr);
+ }
+
+ return in_flags;
+}
+
+bool TLSTransportLayer::HasDataToRecv() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mFD) {
+ return false;
+ }
+ int32_t n = 0;
+ char c;
+ n = PR_Recv(mFD, &c, 1, PR_MSG_PEEK, 0);
+ return n > 0;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/TLSTransportLayer.h b/netwerk/protocol/http/TLSTransportLayer.h
new file mode 100644
index 0000000000..85489ffa40
--- /dev/null
+++ b/netwerk/protocol/http/TLSTransportLayer.h
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TLSTransportLayer_h__
+#define TLSTransportLayer_h__
+
+#include "nsSocketTransportService2.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "prio.h"
+
+namespace mozilla::net {
+
+// TLSTransportLayer will provide a secondary TLS layer. It will be added as a
+// layer between nsHttpConnection and nsSocketTransport.
+// The mSocketTransport, mSocketIn, and mSocketOut of nsHttpConnection will be
+// replaced by TLSTransportLayer.
+//
+// The input path of reading data from a socket is shown below.
+// nsHttpConnection::OnSocketReadable
+// nsHttpConnection::OnWriteSegment
+// nsHttpConnection::mSocketIn->Read
+// TLSTransportLayer::InputStreamWrapper::Read
+// TLSTransportLayer::InputInternal
+// TLSTransportLayer::InputStreamWrapper::ReadDirectly
+// nsSocketInputStream::Read
+//
+// The output path of writing data to a socket is shown below.
+// nsHttpConnection::OnSocketWritable
+// nsHttpConnection::OnReadSegment
+// TLSTransportLayer::OutputStreamWrapper::Write
+// TLSTransportLayer::OutputInternal
+// TLSTransportLayer::OutputStreamWrapper::WriteDirectly
+// nsSocketOutputStream::Write
+
+// 9d6a3bc6-1f90-41d0-9b02-33ccd169052b
+#define NS_TLSTRANSPORTLAYER_IID \
+ { \
+ 0x9d6a3bc6, 0x1f90, 0x41d0, { \
+ 0x9b, 0x02, 0x33, 0xcc, 0xd1, 0x69, 0x05, 0x2b \
+ } \
+ }
+
+class TLSTransportLayer final : public nsISocketTransport,
+ public nsIInputStreamCallback,
+ public nsIOutputStreamCallback {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TLSTRANSPORTLAYER_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSISOCKETTRANSPORT
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+
+ explicit TLSTransportLayer(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aInputStream,
+ nsIAsyncOutputStream* aOutputStream,
+ nsIInputStreamCallback* aOwner);
+ bool Init(const char* aTLSHost, int32_t aTLSPort);
+ already_AddRefed<nsIAsyncInputStream> GetInputStreamWrapper() {
+ nsCOMPtr<nsIAsyncInputStream> stream = &mSocketInWrapper;
+ return stream.forget();
+ }
+ already_AddRefed<nsIAsyncOutputStream> GetOutputStreamWrapper() {
+ nsCOMPtr<nsIAsyncOutputStream> stream = &mSocketOutWrapper;
+ return stream.forget();
+ }
+
+ bool HasDataToRecv();
+
+ void ReleaseOwner() { mOwner = nullptr; }
+
+ private:
+ class InputStreamWrapper : public nsIAsyncInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit InputStreamWrapper(nsIAsyncInputStream* aInputStream,
+ TLSTransportLayer* aTransport);
+
+ nsresult ReadDirectly(char* buf, uint32_t count, uint32_t* countRead);
+ nsresult Status() { return mStatus; }
+ void SetStatus(nsresult aStatus) { mStatus = aStatus; }
+
+ private:
+ friend class TLSTransportLayer;
+ virtual ~InputStreamWrapper() = default;
+ nsresult ReturnDataFromBuffer(char* buf, uint32_t count,
+ uint32_t* countRead);
+
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+
+ nsresult mStatus{NS_OK};
+ // The lifetime of InputStreamWrapper and OutputStreamWrapper are bound to
+ // TLSTransportLayer, so using |mTransport| as a raw pointer should be safe.
+ TLSTransportLayer* MOZ_OWNING_REF mTransport;
+ };
+
+ class OutputStreamWrapper : public nsIAsyncOutputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ explicit OutputStreamWrapper(nsIAsyncOutputStream* aOutputStream,
+ TLSTransportLayer* aTransport);
+
+ nsresult WriteDirectly(const char* buf, uint32_t count,
+ uint32_t* countWritten);
+ nsresult Status() { return mStatus; }
+ void SetStatus(nsresult aStatus) { mStatus = aStatus; }
+
+ private:
+ friend class TLSTransportLayer;
+ virtual ~OutputStreamWrapper() = default;
+ static nsresult WriteFromSegments(nsIInputStream*, void*, const char*,
+ uint32_t offset, uint32_t count,
+ uint32_t* countRead);
+
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+
+ nsresult mStatus{NS_OK};
+ TLSTransportLayer* MOZ_OWNING_REF mTransport;
+ };
+
+ virtual ~TLSTransportLayer();
+ bool DispatchRelease();
+
+ nsISocketTransport* Transport() { return mSocketTransport; }
+
+ int32_t OutputInternal(const char* aBuf, int32_t aAmount);
+ int32_t InputInternal(char* aBuf, int32_t aAmount);
+
+ static PRStatus GetPeerName(PRFileDesc* fd, PRNetAddr* addr);
+ static PRStatus GetSocketOption(PRFileDesc* fd, PRSocketOptionData* aOpt);
+ static PRStatus SetSocketOption(PRFileDesc* fd,
+ const PRSocketOptionData* data);
+ static int32_t Write(PRFileDesc* fd, const void* buf, int32_t amount);
+ static int32_t Read(PRFileDesc* fd, void* buf, int32_t amount);
+ static int32_t Send(PRFileDesc* fd, const void* buf, int32_t amount,
+ int flags, PRIntervalTime timeout);
+ static int32_t Recv(PRFileDesc* fd, void* buf, int32_t amount, int flags,
+ PRIntervalTime timeout);
+ static PRStatus Close(PRFileDesc* fd);
+ static int16_t Poll(PRFileDesc* fd, int16_t in_flags, int16_t* out_flags);
+
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+ InputStreamWrapper mSocketInWrapper;
+ OutputStreamWrapper mSocketOutWrapper;
+ nsCOMPtr<nsITLSSocketControl> mTLSSocketControl;
+ nsCOMPtr<nsIInputStreamCallback> mInputCallback;
+ nsCOMPtr<nsIOutputStreamCallback> mOutputCallback;
+ PRFileDesc* mFD{nullptr};
+ nsCOMPtr<nsIInputStreamCallback> mOwner;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(TLSTransportLayer, NS_TLSTRANSPORTLAYER_IID)
+
+} // namespace mozilla::net
+
+inline nsISupports* ToSupports(mozilla::net::TLSTransportLayer* aTransport) {
+ return static_cast<nsISocketTransport*>(aTransport);
+}
+
+#endif
diff --git a/netwerk/protocol/http/TRRServiceChannel.cpp b/netwerk/protocol/http/TRRServiceChannel.cpp
new file mode 100644
index 0000000000..3a316d1e45
--- /dev/null
+++ b/netwerk/protocol/http/TRRServiceChannel.cpp
@@ -0,0 +1,1581 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TRRServiceChannel.h"
+
+#include "HttpLog.h"
+#include "AltServiceChild.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Unused.h"
+#include "nsDNSPrefetch.h"
+#include "nsEscape.h"
+#include "nsHttpTransaction.h"
+#include "nsICancelable.h"
+#include "nsICachingChannel.h"
+#include "nsIHttpPushListener.h"
+#include "nsIProtocolProxyService2.h"
+#include "nsIOService.h"
+#include "nsISeekableStream.h"
+#include "nsURLHelper.h"
+#include "ProxyConfigLookup.h"
+#include "TRRLoadInfo.h"
+#include "ReferrerInfo.h"
+#include "TRR.h"
+#include "TRRService.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ADDREF(TRRServiceChannel)
+
+// Because nsSupportsWeakReference isn't thread-safe we must ensure that
+// TRRServiceChannel is destroyed on the target thread. Any Release() called
+// on a different thread is dispatched to the target thread.
+bool TRRServiceChannel::DispatchRelease() {
+ if (mCurrentEventTarget->IsOnCurrentThread()) {
+ return false;
+ }
+
+ mCurrentEventTarget->Dispatch(
+ NewNonOwningRunnableMethod("net::TRRServiceChannel::Release", this,
+ &TRRServiceChannel::Release),
+ NS_DISPATCH_NORMAL);
+
+ return true;
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+TRRServiceChannel::Release() {
+ nsrefcnt count = mRefCnt - 1;
+ if (DispatchRelease()) {
+ // Redispatched to the target thread.
+ return count;
+ }
+
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "TRRServiceChannel");
+
+ if (0 == count) {
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(TRRServiceChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
+ NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(TRRServiceChannel)
+NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
+
+TRRServiceChannel::TRRServiceChannel()
+ : HttpAsyncAborter<TRRServiceChannel>(this),
+ mProxyRequest(nullptr, "TRRServiceChannel::mProxyRequest"),
+ mCurrentEventTarget(GetCurrentSerialEventTarget()) {
+ LOG(("TRRServiceChannel ctor [this=%p]\n", this));
+}
+
+TRRServiceChannel::~TRRServiceChannel() {
+ LOG(("TRRServiceChannel dtor [this=%p]\n", this));
+}
+
+NS_IMETHODIMP TRRServiceChannel::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP TRRServiceChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::Cancel(nsresult status) {
+ LOG(("TRRServiceChannel::Cancel [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(status)));
+ if (mCanceled) {
+ LOG((" ignoring; already canceled\n"));
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = status;
+
+ nsCOMPtr<nsICancelable> proxyRequest;
+ {
+ auto req = mProxyRequest.Lock();
+ proxyRequest.swap(*req);
+ }
+
+ if (proxyRequest) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction(
+ "CancelProxyRequest",
+ [proxyRequest, status]() { proxyRequest->Cancel(status); }),
+ NS_DISPATCH_NORMAL);
+ }
+
+ CancelNetworkRequest(status);
+ return NS_OK;
+}
+
+void TRRServiceChannel::CancelNetworkRequest(nsresult aStatus) {
+ if (mTransaction) {
+ nsresult rv = gHttpHandler->CancelTransaction(mTransaction, aStatus);
+ if (NS_FAILED(rv)) {
+ LOG(("failed to cancel the transaction\n"));
+ }
+ }
+ if (mTransactionPump) mTransactionPump->Cancel(aStatus);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::Suspend() {
+ LOG(("TRRServiceChannel::SuspendInternal [this=%p]\n", this));
+
+ if (mTransactionPump) {
+ return mTransactionPump->Suspend();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::Resume() {
+ LOG(("TRRServiceChannel::Resume [this=%p]\n", this));
+
+ if (mTransactionPump) {
+ return mTransactionPump->Resume();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetSecurityInfo(nsITransportSecurityInfo** securityInfo) {
+ NS_ENSURE_ARG_POINTER(securityInfo);
+ *securityInfo = do_AddRef(mSecurityInfo).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::AsyncOpen(nsIStreamListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+ NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
+
+ if (mCanceled) {
+ ReleaseListeners();
+ return mStatus;
+ }
+
+ // HttpBaseChannel::MaybeWaitForUploadStreamNormalization can only be used on
+ // main thread, so we can only return an error here.
+#ifdef NIGHTLY_BUILD
+ MOZ_ASSERT(!LoadPendingUploadStreamNormalization());
+#endif
+ if (LoadPendingUploadStreamNormalization()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!gHttpHandler->Active()) {
+ LOG((" after HTTP shutdown..."));
+ ReleaseListeners();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv)) {
+ ReleaseListeners();
+ return rv;
+ }
+
+ StoreIsPending(true);
+ StoreWasOpened(true);
+
+ mListener = aListener;
+
+ mAsyncOpenTime = TimeStamp::Now();
+
+ rv = MaybeResolveProxyAndBeginConnect();
+ if (NS_FAILED(rv)) {
+ Unused << AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult TRRServiceChannel::MaybeResolveProxyAndBeginConnect() {
+ nsresult rv;
+
+ // The common case for HTTP channels is to begin proxy resolution and return
+ // at this point. The only time we know mProxyInfo already is if we're
+ // proxying a non-http protocol like ftp. We don't need to discover proxy
+ // settings if we are never going to make a network connection.
+ // If mConnectionInfo is already supplied, we don't need to do proxy
+ // resolution again.
+ if (!mProxyInfo && !mConnectionInfo &&
+ !(mLoadFlags & (nsICachingChannel::LOAD_ONLY_FROM_CACHE |
+ nsICachingChannel::LOAD_NO_NETWORK_IO)) &&
+ NS_SUCCEEDED(ResolveProxy())) {
+ return NS_OK;
+ }
+
+ rv = BeginConnect();
+ if (NS_FAILED(rv)) {
+ Unused << AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult TRRServiceChannel::ResolveProxy() {
+ LOG(("TRRServiceChannel::ResolveProxy [this=%p]\n", this));
+ if (!NS_IsMainThread()) {
+ return NS_DispatchToMainThread(
+ NewRunnableMethod("TRRServiceChannel::ResolveProxy", this,
+ &TRRServiceChannel::ResolveProxy),
+ NS_DISPATCH_NORMAL);
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // TODO: bug 1625171. Consider moving proxy resolution to socket process.
+ RefPtr<TRRServiceChannel> self = this;
+ nsCOMPtr<nsICancelable> proxyRequest;
+ nsresult rv = ProxyConfigLookup::Create(
+ [self](nsIProxyInfo* aProxyInfo, nsresult aStatus) {
+ self->OnProxyAvailable(nullptr, nullptr, aProxyInfo, aStatus);
+ },
+ mURI, mProxyResolveFlags, getter_AddRefs(proxyRequest));
+
+ if (NS_FAILED(rv)) {
+ if (!mCurrentEventTarget->IsOnCurrentThread()) {
+ return mCurrentEventTarget->Dispatch(
+ NewRunnableMethod<nsresult>("TRRServiceChannel::AsyncAbort", this,
+ &TRRServiceChannel::AsyncAbort, rv),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+
+ {
+ auto req = mProxyRequest.Lock();
+ // We only set mProxyRequest if the channel hasn't already been cancelled
+ // on another thread.
+ if (!mCanceled) {
+ *req = proxyRequest.forget();
+ }
+ }
+
+ // If the channel has been cancelled, we go ahead and cancel the proxy
+ // request right here.
+ if (proxyRequest) {
+ proxyRequest->Cancel(mStatus);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::OnProxyAvailable(nsICancelable* request, nsIChannel* channel,
+ nsIProxyInfo* pi, nsresult status) {
+ LOG(("TRRServiceChannel::OnProxyAvailable [this=%p pi=%p status=%" PRIx32
+ " mStatus=%" PRIx32 "]\n",
+ this, pi, static_cast<uint32_t>(status),
+ static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
+
+ if (!mCurrentEventTarget->IsOnCurrentThread()) {
+ RefPtr<TRRServiceChannel> self = this;
+ nsCOMPtr<nsIProxyInfo> info = pi;
+ return mCurrentEventTarget->Dispatch(
+ NS_NewRunnableFunction("TRRServiceChannel::OnProxyAvailable",
+ [self, info, status]() {
+ self->OnProxyAvailable(nullptr, nullptr, info,
+ status);
+ }),
+ NS_DISPATCH_NORMAL);
+ }
+
+ MOZ_ASSERT(mCurrentEventTarget->IsOnCurrentThread());
+
+ {
+ auto proxyRequest = mProxyRequest.Lock();
+ *proxyRequest = nullptr;
+ }
+
+ nsresult rv;
+
+ // If status is a failure code, then it means that we failed to resolve
+ // proxy info. That is a non-fatal error assuming it wasn't because the
+ // request was canceled. We just failover to DIRECT when proxy resolution
+ // fails (failure can mean that the PAC URL could not be loaded).
+
+ if (NS_SUCCEEDED(status)) mProxyInfo = pi;
+
+ if (!gHttpHandler->Active()) {
+ LOG(
+ ("nsHttpChannel::OnProxyAvailable [this=%p] "
+ "Handler no longer active.\n",
+ this));
+ rv = NS_ERROR_NOT_AVAILABLE;
+ } else {
+ rv = BeginConnect();
+ }
+
+ if (NS_FAILED(rv)) {
+ Unused << AsyncAbort(rv);
+ }
+ return rv;
+}
+
+nsresult TRRServiceChannel::BeginConnect() {
+ LOG(("TRRServiceChannel::BeginConnect [this=%p]\n", this));
+ nsresult rv;
+
+ // Construct connection info object
+ nsAutoCString host;
+ nsAutoCString scheme;
+ int32_t port = -1;
+ bool isHttps = mURI->SchemeIs("https");
+
+ rv = mURI->GetScheme(scheme);
+ if (NS_SUCCEEDED(rv)) rv = mURI->GetAsciiHost(host);
+ if (NS_SUCCEEDED(rv)) rv = mURI->GetPort(&port);
+ if (NS_SUCCEEDED(rv)) rv = mURI->GetAsciiSpec(mSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Just a warning here because some nsIURIs do not implement this method.
+ Unused << NS_WARN_IF(NS_FAILED(mURI->GetUsername(mUsername)));
+
+ // Reject the URL if it doesn't specify a host
+ if (host.IsEmpty()) {
+ rv = NS_ERROR_MALFORMED_URI;
+ return rv;
+ }
+ LOG(("host=%s port=%d\n", host.get(), port));
+ LOG(("uri=%s\n", mSpec.get()));
+
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (mProxyInfo) proxyInfo = do_QueryInterface(mProxyInfo);
+
+ mRequestHead.SetHTTPS(isHttps);
+ mRequestHead.SetOrigin(scheme, host, port);
+
+ RefPtr<nsHttpConnectionInfo> connInfo = new nsHttpConnectionInfo(
+ host, port, ""_ns, mUsername, proxyInfo, OriginAttributes(), isHttps);
+ // TODO: Bug 1622778 for using AltService in socket process.
+ StoreAllowAltSvc(XRE_IsParentProcess() && LoadAllowAltSvc());
+ bool http2Allowed = !gHttpHandler->IsHttp2Excluded(connInfo);
+ bool http3Allowed = Http3Allowed();
+ if (!http3Allowed) {
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+
+ RefPtr<AltSvcMapping> mapping;
+ if (!mConnectionInfo && LoadAllowAltSvc() && // per channel
+ (http2Allowed || http3Allowed) && !(mLoadFlags & LOAD_FRESH_CONNECTION) &&
+ AltSvcMapping::AcceptableProxy(proxyInfo) &&
+ (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) &&
+ (mapping = gHttpHandler->GetAltServiceMapping(
+ scheme, host, port, mPrivateBrowsing, OriginAttributes(),
+ http2Allowed, http3Allowed))) {
+ LOG(("TRRServiceChannel %p Alt Service Mapping Found %s://%s:%d [%s]\n",
+ this, scheme.get(), mapping->AlternateHost().get(),
+ mapping->AlternatePort(), mapping->HashKey().get()));
+
+ if (!(mLoadFlags & LOAD_ANONYMOUS) && !mPrivateBrowsing) {
+ nsAutoCString altUsedLine(mapping->AlternateHost());
+ bool defaultPort =
+ mapping->AlternatePort() ==
+ (isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT);
+ if (!defaultPort) {
+ altUsedLine.AppendLiteral(":");
+ altUsedLine.AppendInt(mapping->AlternatePort());
+ }
+ rv = mRequestHead.SetHeader(nsHttp::Alternate_Service_Used, altUsedLine);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ LOG(("TRRServiceChannel %p Using connection info from altsvc mapping",
+ this));
+ mapping->GetConnectionInfo(getter_AddRefs(mConnectionInfo), proxyInfo,
+ OriginAttributes());
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, true);
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC_OE, !isHttps);
+ } else if (mConnectionInfo) {
+ LOG(("TRRServiceChannel %p Using channel supplied connection info", this));
+ } else {
+ LOG(("TRRServiceChannel %p Using default connection info", this));
+
+ mConnectionInfo = connInfo;
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
+ }
+
+ // Need to re-ask the handler, since mConnectionInfo may not be the connInfo
+ // we used earlier
+ if (gHttpHandler->IsHttp2Excluded(mConnectionInfo)) {
+ StoreAllowSpdy(0);
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ mConnectionInfo->SetNoSpdy(true);
+ }
+
+ // If TimingEnabled flag is not set after OnModifyRequest() then
+ // clear the already recorded AsyncOpen value for consistency.
+ if (!LoadTimingEnabled()) mAsyncOpenTime = TimeStamp();
+
+ // if this somehow fails we can go on without it
+ Unused << gHttpHandler->AddConnectionHeader(&mRequestHead, mCaps);
+
+ // Adjust mCaps according to our request headers:
+ // - If "Connection: close" is set as a request header, then do not bother
+ // trying to establish a keep-alive connection.
+ if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close")) {
+ mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE);
+ }
+
+ if (gHttpHandler->CriticalRequestPrioritization()) {
+ if (mClassOfService.Flags() & nsIClassOfService::Leader) {
+ mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
+ }
+ if (mClassOfService.Flags() & nsIClassOfService::Unblocked) {
+ mCaps |= NS_HTTP_LOAD_UNBLOCKED;
+ }
+ if (mClassOfService.Flags() & nsIClassOfService::UrgentStart &&
+ gHttpHandler->IsUrgentStartEnabled()) {
+ mCaps |= NS_HTTP_URGENT_START;
+ SetPriority(nsISupportsPriority::PRIORITY_HIGHEST);
+ }
+ }
+
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ MaybeStartDNSPrefetch();
+
+ rv = ContinueOnBeforeConnect();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult TRRServiceChannel::ContinueOnBeforeConnect() {
+ LOG(("TRRServiceChannel::ContinueOnBeforeConnect [this=%p]\n", this));
+
+ // ensure that we are using a valid hostname
+ if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Origin()))) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (LoadIsTRRServiceChannel()) {
+ mCaps |= NS_HTTP_LARGE_KEEPALIVE;
+ mCaps |= NS_HTTP_DISALLOW_HTTPS_RR;
+ }
+
+ mCaps |= NS_HTTP_TRR_FLAGS_FROM_MODE(nsIRequest::GetTRRMode());
+
+ // Finalize ConnectionInfo flags before SpeculativeConnect
+ mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
+ mConnectionInfo->SetPrivate(mPrivateBrowsing);
+ mConnectionInfo->SetNoSpdy(mCaps & NS_HTTP_DISALLOW_SPDY);
+ mConnectionInfo->SetBeConservative((mCaps & NS_HTTP_BE_CONSERVATIVE) ||
+ LoadBeConservative());
+ mConnectionInfo->SetTlsFlags(mTlsFlags);
+ mConnectionInfo->SetIsTrrServiceChannel(LoadIsTRRServiceChannel());
+ mConnectionInfo->SetTRRMode(nsIRequest::GetTRRMode());
+ mConnectionInfo->SetIPv4Disabled(mCaps & NS_HTTP_DISABLE_IPV4);
+ mConnectionInfo->SetIPv6Disabled(mCaps & NS_HTTP_DISABLE_IPV6);
+
+ if (mLoadFlags & LOAD_FRESH_CONNECTION) {
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_TRR_CONNECTION_CYCLE_COUNT,
+ NS_ConvertUTF8toUTF16(TRRService::ProviderKey()), 1);
+ nsresult rv =
+ gHttpHandler->ConnMgr()->DoSingleConnectionCleanup(mConnectionInfo);
+ LOG(
+ ("TRRServiceChannel::BeginConnect "
+ "DoSingleConnectionCleanup succeeded=%d %08x [this=%p]",
+ NS_SUCCEEDED(rv), static_cast<uint32_t>(rv), this));
+ }
+
+ return Connect();
+}
+
+nsresult TRRServiceChannel::Connect() {
+ LOG(("TRRServiceChannel::Connect [this=%p]\n", this));
+
+ nsresult rv = SetupTransaction();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return mTransaction->AsyncRead(this, getter_AddRefs(mTransactionPump));
+}
+
+nsresult TRRServiceChannel::SetupTransaction() {
+ LOG((
+ "TRRServiceChannel::SetupTransaction "
+ "[this=%p, cos=%lu, inc=%d, prio=%d]\n",
+ this, mClassOfService.Flags(), mClassOfService.Incremental(), mPriority));
+
+ NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
+
+ nsresult rv;
+
+ if (!LoadAllowSpdy()) {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+ // Check a proxy info from mConnectionInfo. TRR channel may use a proxy that
+ // is set in mConnectionInfo but acutally the channel do not have mProxyInfo
+ // set. This can happend when network.trr.async_connInfo is true.
+ bool useNonDirectProxy = mConnectionInfo->ProxyInfo()
+ ? !mConnectionInfo->ProxyInfo()->IsDirect()
+ : false;
+ if (!Http3Allowed() || useNonDirectProxy) {
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+ if (LoadBeConservative()) {
+ mCaps |= NS_HTTP_BE_CONSERVATIVE;
+ }
+
+ // Use the URI path if not proxying (transparent proxying such as proxy
+ // CONNECT does not count here). Also figure out what HTTP version to use.
+ nsAutoCString buf, path;
+ nsCString* requestURI;
+
+ // This is the normal e2e H1 path syntax "/index.html"
+ rv = mURI->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // path may contain UTF-8 characters, so ensure that they're escaped.
+ if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII | esc_Spaces,
+ buf)) {
+ requestURI = &buf;
+ } else {
+ requestURI = &path;
+ }
+
+ // trim off the #ref portion if any...
+ int32_t ref1 = requestURI->FindChar('#');
+ if (ref1 != kNotFound) {
+ requestURI->SetLength(ref1);
+ }
+
+ if (mConnectionInfo->UsingConnect() || !mConnectionInfo->UsingHttpProxy()) {
+ mRequestHead.SetVersion(gHttpHandler->HttpVersion());
+ } else {
+ mRequestHead.SetPath(*requestURI);
+
+ // RequestURI should be the absolute uri H1 proxy syntax
+ // "http://foo/index.html" so we will overwrite the relative version in
+ // requestURI
+ rv = mURI->GetUserPass(buf);
+ if (NS_FAILED(rv)) return rv;
+ if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
+ strncmp(mSpec.get(), "https:", 6) == 0)) {
+ nsCOMPtr<nsIURI> tempURI = nsIOService::CreateExposableURI(mURI);
+ rv = tempURI->GetAsciiSpec(path);
+ if (NS_FAILED(rv)) return rv;
+ requestURI = &path;
+ } else {
+ requestURI = &mSpec;
+ }
+
+ // trim off the #ref portion if any...
+ int32_t ref2 = requestURI->FindChar('#');
+ if (ref2 != kNotFound) {
+ requestURI->SetLength(ref2);
+ }
+
+ mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
+ }
+
+ mRequestHead.SetRequestURI(*requestURI);
+
+ // Force setting no-cache header for TRRServiceChannel.
+ // We need to send 'Pragma:no-cache' to inhibit proxy caching even if
+ // no proxy is configured since we might be talking with a transparent
+ // proxy, i.e. one that operates at the network level. See bug #14772.
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
+ // no-cache'
+ if (mRequestHead.Version() >= HttpVersion::v1_1) {
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "no-cache", true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // create wrapper for this channel's notification callbacks
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+
+ // create the transaction object
+ mTransaction = new nsHttpTransaction();
+ LOG1(("TRRServiceChannel %p created nsHttpTransaction %p\n", this,
+ mTransaction.get()));
+
+ // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
+ if (mLoadFlags & LOAD_ANONYMOUS) mCaps |= NS_HTTP_LOAD_ANONYMOUS;
+
+ if (LoadTimingEnabled()) mCaps |= NS_HTTP_TIMING_ENABLED;
+
+ nsCOMPtr<nsIHttpPushListener> pushListener;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIHttpPushListener),
+ getter_AddRefs(pushListener));
+ HttpTransactionShell::OnPushCallback pushCallback = nullptr;
+ if (pushListener) {
+ mCaps |= NS_HTTP_ONPUSH_LISTENER;
+ nsWeakPtr weakPtrThis(
+ do_GetWeakReference(static_cast<nsIHttpChannel*>(this)));
+ pushCallback = [weakPtrThis](uint32_t aPushedStreamId,
+ const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction) {
+ if (nsCOMPtr<nsIHttpChannel> channel = do_QueryReferent(weakPtrThis)) {
+ return static_cast<TRRServiceChannel*>(channel.get())
+ ->OnPush(aPushedStreamId, aUrl, aRequestString, aTransaction);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ };
+ }
+
+ EnsureRequestContext();
+
+ rv = mTransaction->Init(
+ mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
+ LoadUploadStreamHasHeaders(), mCurrentEventTarget, callbacks, this,
+ mBrowserId, HttpTrafficCategory::eInvalid, mRequestContext,
+ mClassOfService, mInitialRwin, LoadResponseTimeoutEnabled(), mChannelId,
+ nullptr, std::move(pushCallback), mTransWithPushedStream,
+ mPushedStreamId);
+
+ mTransWithPushedStream = nullptr;
+
+ if (NS_FAILED(rv)) {
+ mTransaction = nullptr;
+ return rv;
+ }
+
+ return rv;
+}
+
+void TRRServiceChannel::SetPushedStreamTransactionAndId(
+ HttpTransactionShell* aTransWithPushedStream, uint32_t aPushedStreamId) {
+ MOZ_ASSERT(!mTransWithPushedStream);
+ LOG(("TRRServiceChannel::SetPushedStreamTransaction [this=%p] trans=%p", this,
+ aTransWithPushedStream));
+
+ mTransWithPushedStream = aTransWithPushedStream;
+ mPushedStreamId = aPushedStreamId;
+}
+
+nsresult TRRServiceChannel::OnPush(uint32_t aPushedStreamId,
+ const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction) {
+ MOZ_ASSERT(aTransaction);
+ LOG(("TRRServiceChannel::OnPush [this=%p, trans=%p]\n", this, aTransaction));
+
+ MOZ_ASSERT(mCaps & NS_HTTP_ONPUSH_LISTENER);
+ nsCOMPtr<nsIHttpPushListener> pushListener;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIHttpPushListener),
+ getter_AddRefs(pushListener));
+
+ if (!pushListener) {
+ LOG(
+ ("TRRServiceChannel::OnPush [this=%p] notification callbacks do not "
+ "implement nsIHttpPushListener\n",
+ this));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIURI> pushResource;
+ nsresult rv;
+
+ // Create a Channel for the Push Resource
+ rv = NS_NewURI(getter_AddRefs(pushResource), aUrl);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ static_cast<TRRLoadInfo*>(mLoadInfo.get())->Clone();
+ nsCOMPtr<nsIChannel> pushHttpChannel;
+ rv = gHttpHandler->CreateTRRServiceChannel(pushResource, nullptr, 0, nullptr,
+ loadInfo,
+ getter_AddRefs(pushHttpChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pushHttpChannel->SetLoadFlags(mLoadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<TRRServiceChannel> channel;
+ CallQueryInterface(pushHttpChannel, channel.StartAssignment());
+ MOZ_ASSERT(channel);
+ if (!channel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // new channel needs mrqeuesthead and headers from pushedStream
+ channel->mRequestHead.ParseHeaderSet(aRequestString.BeginReading());
+ channel->mLoadGroup = mLoadGroup;
+ channel->mCallbacks = mCallbacks;
+
+ // Link the pushed stream with the new channel and call listener
+ channel->SetPushedStreamTransactionAndId(aTransaction, aPushedStreamId);
+ rv = pushListener->OnPush(this, channel);
+ return rv;
+}
+
+void TRRServiceChannel::MaybeStartDNSPrefetch() {
+ if (mConnectionInfo->UsingHttpProxy() ||
+ (mLoadFlags & (nsICachingChannel::LOAD_NO_NETWORK_IO |
+ nsICachingChannel::LOAD_ONLY_FROM_CACHE))) {
+ return;
+ }
+
+ LOG(
+ ("TRRServiceChannel::MaybeStartDNSPrefetch [this=%p] "
+ "prefetching%s\n",
+ this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : ""));
+
+ OriginAttributes originAttributes;
+ mDNSPrefetch =
+ new nsDNSPrefetch(mURI, originAttributes, nsIRequest::GetTRRMode(), this,
+ LoadTimingEnabled());
+ nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ dnsFlags |= nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+ nsresult rv = mDNSPrefetch->PrefetchHigh(dnsFlags);
+ NS_ENSURE_SUCCESS_VOID(rv);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::OnTransportStatus(nsITransport* trans, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ return NS_OK;
+}
+
+nsresult TRRServiceChannel::CallOnStartRequest() {
+ LOG(("TRRServiceChannel::CallOnStartRequest [this=%p]", this));
+
+ if (LoadOnStartRequestCalled()) {
+ LOG(("CallOnStartRequest already invoked before"));
+ return mStatus;
+ }
+
+ nsresult rv = NS_OK;
+ StoreTracingEnabled(false);
+
+ // Ensure mListener->OnStartRequest will be invoked before exiting
+ // this function.
+ auto onStartGuard = MakeScopeExit([&] {
+ LOG(
+ (" calling mListener->OnStartRequest by ScopeExit [this=%p, "
+ "listener=%p]\n",
+ this, mListener.get()));
+ MOZ_ASSERT(!LoadOnStartRequestCalled());
+
+ if (mListener) {
+ nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
+ StoreOnStartRequestCalled(true);
+ deleteProtector->OnStartRequest(this);
+ }
+ StoreOnStartRequestCalled(true);
+ });
+
+ if (mResponseHead && !mResponseHead->HasContentCharset()) {
+ mResponseHead->SetContentCharset(mContentCharsetHint);
+ }
+
+ LOG((" calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
+ mListener.get()));
+
+ // About to call OnStartRequest, dismiss the guard object.
+ onStartGuard.release();
+
+ if (mListener) {
+ MOZ_ASSERT(!LoadOnStartRequestCalled(),
+ "We should not call OsStartRequest twice");
+ nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
+ StoreOnStartRequestCalled(true);
+ rv = deleteProtector->OnStartRequest(this);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ NS_WARNING("OnStartRequest skipped because of null listener");
+ StoreOnStartRequestCalled(true);
+ }
+
+ if (!mResponseHead) {
+ return NS_OK;
+ }
+
+ nsAutoCString contentEncoding;
+ rv = mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+ if (NS_FAILED(rv) || contentEncoding.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // DoApplyContentConversions can only be called on the main thread.
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIStreamListener> listener;
+ rv =
+ DoApplyContentConversions(mListener, getter_AddRefs(listener), nullptr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ AfterApplyContentConversions(rv, listener);
+ return NS_OK;
+ }
+
+ Suspend();
+
+ RefPtr<TRRServiceChannel> self = this;
+ rv = NS_DispatchToMainThread(
+ NS_NewRunnableFunction("TRRServiceChannel::DoApplyContentConversions",
+ [self]() {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = self->DoApplyContentConversions(
+ self->mListener, getter_AddRefs(listener),
+ nullptr);
+ self->AfterApplyContentConversions(rv, listener);
+ }),
+ NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ Resume();
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void TRRServiceChannel::AfterApplyContentConversions(
+ nsresult aResult, nsIStreamListener* aListener) {
+ LOG(("TRRServiceChannel::AfterApplyContentConversions [this=%p]", this));
+ if (!mCurrentEventTarget->IsOnCurrentThread()) {
+ RefPtr<TRRServiceChannel> self = this;
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ self->mCurrentEventTarget->Dispatch(
+ NS_NewRunnableFunction(
+ "TRRServiceChannel::AfterApplyContentConversions",
+ [self, aResult, listener]() {
+ self->Resume();
+ self->AfterApplyContentConversions(aResult, listener);
+ }),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ if (mCanceled) {
+ return;
+ }
+
+ if (NS_FAILED(aResult)) {
+ Unused << AsyncAbort(aResult);
+ return;
+ }
+
+ if (aListener) {
+ mListener = aListener;
+ mCompressListener = aListener;
+ StoreHasAppliedConversion(true);
+ }
+}
+
+void TRRServiceChannel::ProcessAltService() {
+ // e.g. Alt-Svc: h2=":443"; ma=60
+ // e.g. Alt-Svc: h2="otherhost:443"
+ // Alt-Svc = 1#( alternative *( OWS ";" OWS parameter ) )
+ // alternative = protocol-id "=" alt-authority
+ // protocol-id = token ; percent-encoded ALPN protocol identifier
+ // alt-authority = quoted-string ; containing [ uri-host ] ":" port
+
+ if (!LoadAllowAltSvc()) { // per channel opt out
+ return;
+ }
+
+ if (!gHttpHandler->AllowAltSvc() || (mCaps & NS_HTTP_DISALLOW_SPDY)) {
+ return;
+ }
+
+ nsCString scheme;
+ mURI->GetScheme(scheme);
+ bool isHttp = scheme.EqualsLiteral("http");
+ if (!isHttp && !scheme.EqualsLiteral("https")) {
+ return;
+ }
+
+ nsCString altSvc;
+ Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, altSvc);
+ if (altSvc.IsEmpty()) {
+ return;
+ }
+
+ if (!nsHttp::IsReasonableHeaderValue(altSvc)) {
+ LOG(("Alt-Svc Response Header seems unreasonable - skipping\n"));
+ return;
+ }
+
+ nsCString originHost;
+ int32_t originPort = 80;
+ mURI->GetPort(&originPort);
+ if (NS_FAILED(mURI->GetAsciiHost(originHost))) {
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+ if (mProxyInfo) {
+ proxyInfo = do_QueryInterface(mProxyInfo);
+ }
+
+ auto processHeaderTask = [altSvc, scheme, originHost, originPort,
+ userName(mUsername),
+ privateBrowsing(mPrivateBrowsing), callbacks,
+ proxyInfo, caps(mCaps)]() {
+ if (XRE_IsSocketProcess()) {
+ AltServiceChild::ProcessHeader(altSvc, scheme, originHost, originPort,
+ userName, privateBrowsing, callbacks,
+ proxyInfo, caps & NS_HTTP_DISALLOW_SPDY,
+ OriginAttributes());
+ return;
+ }
+
+ AltSvcMapping::ProcessHeader(
+ altSvc, scheme, originHost, originPort, userName, privateBrowsing,
+ callbacks, proxyInfo, caps & NS_HTTP_DISALLOW_SPDY, OriginAttributes());
+ };
+
+ if (NS_IsMainThread()) {
+ processHeaderTask();
+ return;
+ }
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "TRRServiceChannel::ProcessAltService", std::move(processHeaderTask)));
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::OnStartRequest(nsIRequest* request) {
+ LOG(("TRRServiceChannel::OnStartRequest [this=%p request=%p status=%" PRIx32
+ "]\n",
+ this, request, static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
+
+ if (!(mCanceled || NS_FAILED(mStatus))) {
+ // capture the request's status, so our consumers will know ASAP of any
+ // connection failures, etc - bug 93581
+ nsresult status;
+ request->GetStatus(&status);
+ mStatus = status;
+ }
+
+ MOZ_ASSERT(request == mTransactionPump, "Unexpected request");
+
+ StoreAfterOnStartRequestBegun(true);
+ if (mTransaction) {
+ if (!mSecurityInfo) {
+ // grab the security info from the connection object; the transaction
+ // is guaranteed to own a reference to the connection.
+ mSecurityInfo = mTransaction->SecurityInfo();
+ }
+ }
+
+ if (NS_SUCCEEDED(mStatus) && mTransaction) {
+ // mTransactionPump doesn't hit OnInputStreamReady and call this until
+ // all of the response headers have been acquired, so we can take
+ // ownership of them from the transaction.
+ mResponseHead = mTransaction->TakeResponseHead();
+ if (mResponseHead) {
+ uint32_t httpStatus = mResponseHead->Status();
+ if (mTransaction->ProxyConnectFailed()) {
+ LOG(("TRRServiceChannel proxy connect failed httpStatus: %d",
+ httpStatus));
+ MOZ_ASSERT(mConnectionInfo->UsingConnect(),
+ "proxy connect failed but not using CONNECT?");
+ nsresult rv = HttpProxyResponseToErrorCode(httpStatus);
+ mTransaction->DontReuseConnection();
+ Cancel(rv);
+ return CallOnStartRequest();
+ }
+
+ if ((httpStatus < 500) && (httpStatus != 421) && (httpStatus != 407)) {
+ ProcessAltService();
+ }
+
+ if (httpStatus == 300 || httpStatus == 301 || httpStatus == 302 ||
+ httpStatus == 303 || httpStatus == 307 || httpStatus == 308) {
+ nsresult rv = SyncProcessRedirection(httpStatus);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ mStatus = rv;
+ DoNotifyListener();
+ return rv;
+ }
+ } else {
+ NS_WARNING("No response head in OnStartRequest");
+ }
+ }
+
+ // avoid crashing if mListener happens to be null...
+ if (!mListener) {
+ MOZ_ASSERT_UNREACHABLE("mListener is null");
+ return NS_OK;
+ }
+
+ return CallOnStartRequest();
+}
+
+nsresult TRRServiceChannel::SyncProcessRedirection(uint32_t aHttpStatus) {
+ nsAutoCString location;
+
+ // if a location header was not given, then we can't perform the redirect,
+ // so just carry on as though this were a normal response.
+ if (NS_FAILED(mResponseHead->GetHeader(nsHttp::Location, location))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // make sure non-ASCII characters in the location header are escaped.
+ nsAutoCString locationBuf;
+ if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII | esc_Spaces,
+ locationBuf)) {
+ location = locationBuf;
+ }
+
+ LOG(("redirecting to: %s [redirection-limit=%u]\n", location.get(),
+ uint32_t(mRedirectionLimit)));
+
+ nsCOMPtr<nsIURI> redirectURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(redirectURI), location);
+
+ if (NS_FAILED(rv)) {
+ LOG(("Invalid URI for redirect: Location: %s\n", location.get()));
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ // move the reference of the old location to the new one if the new
+ // one has none.
+ PropagateReferenceIfNeeded(mURI, redirectURI);
+
+ bool rewriteToGET =
+ ShouldRewriteRedirectToGET(aHttpStatus, mRequestHead.ParsedMethod());
+
+ // Let's not rewrite the method to GET for TRR requests.
+ if (rewriteToGET) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If the method is not safe (such as POST, PUT, DELETE, ...)
+ if (!mRequestHead.IsSafeMethod()) {
+ LOG(("TRRServiceChannel: unsafe redirect to:%s\n", location.get()));
+ }
+
+ uint32_t redirectFlags;
+ if (nsHttp::IsPermanentRedirect(aHttpStatus)) {
+ redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
+ } else {
+ redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
+ }
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ static_cast<TRRLoadInfo*>(mLoadInfo.get())->Clone();
+ rv = gHttpHandler->CreateTRRServiceChannel(redirectURI, nullptr, 0, nullptr,
+ redirectLoadInfo,
+ getter_AddRefs(newChannel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = SetupReplacementChannel(redirectURI, newChannel, !rewriteToGET,
+ redirectFlags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Make sure to do this after we received redirect veto answer,
+ // i.e. after all sinks had been notified
+ newChannel->SetOriginalURI(mOriginalURI);
+
+ rv = newChannel->AsyncOpen(mListener);
+ LOG((" new channel AsyncOpen returned %" PRIX32, static_cast<uint32_t>(rv)));
+
+ // close down this channel
+ Cancel(NS_BINDING_REDIRECTED);
+
+ ReleaseListeners();
+
+ return NS_OK;
+}
+
+nsresult TRRServiceChannel::SetupReplacementChannel(nsIURI* aNewURI,
+ nsIChannel* aNewChannel,
+ bool aPreserveMethod,
+ uint32_t aRedirectFlags) {
+ LOG(
+ ("TRRServiceChannel::SetupReplacementChannel "
+ "[this=%p newChannel=%p preserveMethod=%d]",
+ this, aNewChannel, aPreserveMethod));
+
+ nsresult rv = HttpBaseChannel::SetupReplacementChannel(
+ aNewURI, aNewChannel, aPreserveMethod, aRedirectFlags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = CheckRedirectLimit(aRedirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
+ if (!httpChannel) {
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ // convey the ApplyConversion flag (bug 91862)
+ nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
+ if (encodedChannel) {
+ encodedChannel->SetApplyConversion(LoadApplyConversion());
+ }
+
+ if (mContentTypeHint.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // Make sure we set content-type on the old channel properly.
+ MOZ_ASSERT(mContentTypeHint.Equals("application/dns-message"));
+
+ // Apply TRR specific settings. Note that we already know mContentTypeHint is
+ // "application/dns-message" here.
+ return TRR::SetupTRRServiceChannelInternal(
+ httpChannel,
+ mRequestHead.ParsedMethod() == nsHttpRequestHead::kMethod_Get,
+ mContentTypeHint);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::OnDataAvailable(nsIRequest* request, nsIInputStream* input,
+ uint64_t offset, uint32_t count) {
+ LOG(("TRRServiceChannel::OnDataAvailable [this=%p request=%p offset=%" PRIu64
+ " count=%" PRIu32 "]\n",
+ this, request, offset, count));
+
+ // don't send out OnDataAvailable notifications if we've been canceled.
+ if (mCanceled) return mStatus;
+
+ MOZ_ASSERT(mResponseHead, "No response head in ODA!!");
+
+ if (mListener) {
+ return mListener->OnDataAvailable(this, input, offset, count);
+ }
+
+ return NS_ERROR_ABORT;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::OnStopRequest(nsIRequest* request, nsresult status) {
+ LOG(("TRRServiceChannel::OnStopRequest [this=%p request=%p status=%" PRIx32
+ "]\n",
+ this, request, static_cast<uint32_t>(status)));
+
+ if (mCanceled || NS_FAILED(mStatus)) status = mStatus;
+
+ mTransactionTimings = mTransaction->Timings();
+ mTransaction = nullptr;
+ mTransactionPump = nullptr;
+
+ if (mListener) {
+ LOG(("TRRServiceChannel %p calling OnStopRequest\n", this));
+ MOZ_ASSERT(LoadOnStartRequestCalled(),
+ "OnStartRequest should be called before OnStopRequest");
+ MOZ_ASSERT(!LoadOnStopRequestCalled(),
+ "We should not call OnStopRequest twice");
+ StoreOnStopRequestCalled(true);
+ mListener->OnStopRequest(this, status);
+ }
+ StoreOnStopRequestCalled(true);
+
+ mDNSPrefetch = nullptr;
+
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, status);
+ }
+
+ ReleaseListeners();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
+ nsresult status) {
+ LOG(
+ ("TRRServiceChannel::OnLookupComplete [this=%p] prefetch complete%s: "
+ "%s status[0x%" PRIx32 "]\n",
+ this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : "",
+ NS_SUCCEEDED(status) ? "success" : "failure",
+ static_cast<uint32_t>(status)));
+
+ // We no longer need the dns prefetch object. Note: mDNSPrefetch could be
+ // validly null if OnStopRequest has already been called.
+ // We only need the domainLookup timestamps when not loading from cache
+ if (mDNSPrefetch && mDNSPrefetch->TimingsValid() && mTransaction) {
+ TimeStamp connectStart = mTransaction->GetConnectStart();
+ TimeStamp requestStart = mTransaction->GetRequestStart();
+ // We only set the domainLookup timestamps if we're not using a
+ // persistent connection.
+ if (requestStart.IsNull() && connectStart.IsNull()) {
+ mTransaction->SetDomainLookupStart(mDNSPrefetch->StartTimestamp());
+ mTransaction->SetDomainLookupEnd(mDNSPrefetch->EndTimestamp());
+ }
+ }
+ mDNSPrefetch = nullptr;
+
+ // Unset DNS cache refresh if it was requested,
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ mCaps &= ~NS_HTTP_REFRESH_DNS;
+ if (mTransaction) {
+ mTransaction->SetDNSWasRefreshed();
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetIsAuthChannel(bool* aIsAuthChannel) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ mCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::SetPriority(int32_t value) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void TRRServiceChannel::OnClassOfServiceUpdated() {
+ LOG(("TRRServiceChannel::OnClassOfServiceUpdated this=%p, cos=%lu inc=%d",
+ this, mClassOfService.Flags(), mClassOfService.Incremental()));
+
+ if (mTransaction) {
+ gHttpHandler->UpdateClassOfServiceOnTransaction(mTransaction,
+ mClassOfService);
+ }
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::SetClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService.Flags();
+ mClassOfService.SetFlags(inFlags);
+ if (previous != mClassOfService.Flags()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::SetIncremental(bool inFlag) {
+ bool previous = mClassOfService.Incremental();
+ mClassOfService.SetIncremental(inFlag);
+ if (previous != mClassOfService.Incremental()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::SetClassOfService(ClassOfService cos) {
+ ClassOfService previous = mClassOfService;
+ mClassOfService = cos;
+ if (previous != mClassOfService) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::AddClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService.Flags();
+ mClassOfService.SetFlags(inFlags | mClassOfService.Flags());
+ if (previous != mClassOfService.Flags()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::ClearClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService.Flags();
+ mClassOfService.SetFlags(~inFlags & mClassOfService.Flags());
+ if (previous != mClassOfService.Flags()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::ResumeAt(uint64_t aStartPos, const nsACString& aEntityID) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void TRRServiceChannel::DoAsyncAbort(nsresult aStatus) {
+ Unused << AsyncAbort(aStatus);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetProxyInfo(nsIProxyInfo** result) {
+ if (!mConnectionInfo) {
+ *result = do_AddRef(mProxyInfo).take();
+ } else {
+ *result = do_AddRef(mConnectionInfo->ProxyInfo()).take();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP TRRServiceChannel::GetHttpProxyConnectResponseCode(
+ int32_t* aResponseCode) {
+ NS_ENSURE_ARG_POINTER(aResponseCode);
+
+ *aResponseCode = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ return HttpBaseChannel::GetLoadFlags(aLoadFlags);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ if (aLoadFlags & (nsICachingChannel::LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE |
+ nsICachingChannel::LOAD_NO_NETWORK_IO)) {
+ MOZ_ASSERT(false, "Wrong load flags!");
+ return NS_ERROR_FAILURE;
+ }
+
+ return HttpBaseChannel::SetLoadFlags(aLoadFlags);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetURI(nsIURI** aURI) {
+ return HttpBaseChannel::GetURI(aURI);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aCallbacks) {
+ return HttpBaseChannel::GetNotificationCallbacks(aCallbacks);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ return HttpBaseChannel::GetLoadGroup(aLoadGroup);
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetRequestMethod(nsACString& aMethod) {
+ return HttpBaseChannel::GetRequestMethod(aMethod);
+}
+
+void TRRServiceChannel::DoNotifyListener() {
+ LOG(("TRRServiceChannel::DoNotifyListener this=%p", this));
+
+ // In case nsHttpChannel::OnStartRequest wasn't called (e.g. due to flag
+ // LOAD_ONLY_IF_MODIFIED) we want to set AfterOnStartRequestBegun to true
+ // before notifying listener.
+ if (!LoadAfterOnStartRequestBegun()) {
+ StoreAfterOnStartRequestBegun(true);
+ }
+
+ if (mListener && !LoadOnStartRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ StoreOnStartRequestCalled(true);
+ listener->OnStartRequest(this);
+ }
+ StoreOnStartRequestCalled(true);
+
+ // Make sure IsPending is set to false. At this moment we are done from
+ // the point of view of our consumer and we have to report our self
+ // as not-pending.
+ StoreIsPending(false);
+
+ if (mListener && !LoadOnStopRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ StoreOnStopRequestCalled(true);
+ listener->OnStopRequest(this, mStatus);
+ }
+ StoreOnStopRequestCalled(true);
+
+ // We have to make sure to drop the references to listeners and callbacks
+ // no longer needed.
+ ReleaseListeners();
+
+ DoNotifyListenerCleanup();
+}
+
+void TRRServiceChannel::DoNotifyListenerCleanup() {}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetDomainLookupStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetDomainLookupStart();
+ } else {
+ *_retval = mTransactionTimings.domainLookupStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetDomainLookupEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetDomainLookupEnd();
+ } else {
+ *_retval = mTransactionTimings.domainLookupEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetConnectStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetConnectStart();
+ } else {
+ *_retval = mTransactionTimings.connectStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetTcpConnectEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetTcpConnectEnd();
+ } else {
+ *_retval = mTransactionTimings.tcpConnectEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetSecureConnectionStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetSecureConnectionStart();
+ } else {
+ *_retval = mTransactionTimings.secureConnectionStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetConnectEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetConnectEnd();
+ } else {
+ *_retval = mTransactionTimings.connectEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetRequestStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetRequestStart();
+ } else {
+ *_retval = mTransactionTimings.requestStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetResponseStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetResponseStart();
+ } else {
+ *_retval = mTransactionTimings.responseStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::GetResponseEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetResponseEnd();
+ } else {
+ *_retval = mTransactionTimings.responseEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP TRRServiceChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true;
+ return NS_OK;
+}
+
+bool TRRServiceChannel::SameOriginWithOriginalUri(nsIURI* aURI) { return true; }
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/TRRServiceChannel.h b/netwerk/protocol/http/TRRServiceChannel.h
new file mode 100644
index 0000000000..0d726deb57
--- /dev/null
+++ b/netwerk/protocol/http/TRRServiceChannel.h
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_TRRServiceChannel_h
+#define mozilla_net_TRRServiceChannel_h
+
+#include "HttpBaseChannel.h"
+#include "mozilla/DataMutex.h"
+#include "nsIDNSListener.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIProxiedChannel.h"
+#include "nsIStreamListener.h"
+#include "nsWeakReference.h"
+
+class nsDNSPrefetch;
+
+namespace mozilla::net {
+
+class HttpTransactionShell;
+class nsHttpHandler;
+
+// Use to support QI nsIChannel to TRRServiceChannel
+#define NS_TRRSERVICECHANNEL_IID \
+ { \
+ 0x361c4bb1, 0xd6b2, 0x493b, { \
+ 0x86, 0xbc, 0x88, 0xd3, 0x5d, 0x16, 0x38, 0xfa \
+ } \
+ }
+
+// TRRServiceChannel is designed to fetch DNS data from DoH server. This channel
+// MUST only be used by TRR.
+class TRRServiceChannel : public HttpBaseChannel,
+ public HttpAsyncAborter<TRRServiceChannel>,
+ public nsIDNSListener,
+ public nsIStreamListener,
+ public nsITransportEventSink,
+ public nsIProxiedChannel,
+ public nsIProtocolProxyCallback,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TRRSERVICECHANNEL_IID)
+
+ // nsIRequest
+ NS_IMETHOD SetCanceledReason(const nsACString& aReason) override;
+ NS_IMETHOD GetCanceledReason(nsACString& aReason) override;
+ NS_IMETHOD CancelWithReason(nsresult status,
+ const nsACString& reason) override;
+ NS_IMETHOD Cancel(nsresult status) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+ NS_IMETHOD GetLoadFlags(nsLoadFlags* aLoadFlags) override;
+ NS_IMETHOD SetLoadFlags(nsLoadFlags aLoadFlags) override;
+ NS_IMETHOD GetURI(nsIURI** aURI) override;
+ NS_IMETHOD GetNotificationCallbacks(
+ nsIInterfaceRequestor** aCallbacks) override;
+ NS_IMETHOD GetLoadGroup(nsILoadGroup** aLoadGroup) override;
+ NS_IMETHOD GetRequestMethod(nsACString& aMethod) override;
+ // nsIChannel
+ NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+
+ NS_IMETHOD LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) override;
+ NS_IMETHOD LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override;
+ NS_IMETHOD GetIsAuthChannel(bool* aIsAuthChannel) override;
+
+ NS_IMETHOD SetNotificationCallbacks(
+ nsIInterfaceRequestor* aCallbacks) override;
+ // nsISupportsPriority
+ NS_IMETHOD SetPriority(int32_t value) override;
+ // nsIClassOfService
+ NS_IMETHOD SetClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD AddClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD ClearClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD SetIncremental(bool inFlag) override;
+ NS_IMETHOD SetClassOfService(ClassOfService cos) override;
+ // nsIResumableChannel
+ NS_IMETHOD ResumeAt(uint64_t startPos, const nsACString& entityID) override;
+ NS_IMETHOD SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) override {
+ return NS_OK;
+ }
+ NS_IMETHOD SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* aListener) override {
+ return NS_OK;
+ }
+
+ [[nodiscard]] nsresult OnPush(uint32_t aPushedStreamId,
+ const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction);
+ void SetPushedStreamTransactionAndId(
+ HttpTransactionShell* aTransWithPushedStream, uint32_t aPushedStreamId);
+
+ // nsITimedChannel
+ NS_IMETHOD GetDomainLookupStart(
+ mozilla::TimeStamp* aDomainLookupStart) override;
+ NS_IMETHOD GetDomainLookupEnd(mozilla::TimeStamp* aDomainLookupEnd) override;
+ NS_IMETHOD GetConnectStart(mozilla::TimeStamp* aConnectStart) override;
+ NS_IMETHOD GetTcpConnectEnd(mozilla::TimeStamp* aTcpConnectEnd) override;
+ NS_IMETHOD GetSecureConnectionStart(
+ mozilla::TimeStamp* aSecureConnectionStart) override;
+ NS_IMETHOD GetConnectEnd(mozilla::TimeStamp* aConnectEnd) override;
+ NS_IMETHOD GetRequestStart(mozilla::TimeStamp* aRequestStart) override;
+ NS_IMETHOD GetResponseStart(mozilla::TimeStamp* aResponseStart) override;
+ NS_IMETHOD GetResponseEnd(mozilla::TimeStamp* aResponseEnd) override;
+ NS_IMETHOD SetLoadGroup(nsILoadGroup* aLoadGroup) override;
+ NS_IMETHOD TimingAllowCheck(nsIPrincipal* aOrigin, bool* aResult) override;
+
+ protected:
+ TRRServiceChannel();
+ virtual ~TRRServiceChannel();
+
+ void CancelNetworkRequest(nsresult aStatus);
+ nsresult BeginConnect();
+ nsresult ContinueOnBeforeConnect();
+ nsresult Connect();
+ nsresult SetupTransaction();
+ void OnClassOfServiceUpdated();
+ virtual void DoNotifyListenerCleanup() override;
+ virtual void DoAsyncAbort(nsresult aStatus) override;
+ bool IsIsolated() { return false; };
+ void ProcessAltService();
+ nsresult CallOnStartRequest();
+
+ void MaybeStartDNSPrefetch();
+ void DoNotifyListener();
+ nsresult MaybeResolveProxyAndBeginConnect();
+ nsresult ResolveProxy();
+ void AfterApplyContentConversions(nsresult aResult,
+ nsIStreamListener* aListener);
+ nsresult SyncProcessRedirection(uint32_t aHttpStatus);
+ [[nodiscard]] virtual nsresult SetupReplacementChannel(
+ nsIURI* aNewURI, nsIChannel* aNewChannel, bool aPreserveMethod,
+ uint32_t aRedirectFlags) override;
+ // Skip this check for TRRServiceChannel.
+ virtual bool ShouldTaintReplacementChannelOrigin(
+ nsIChannel* aNewChannel, uint32_t aRedirectFlags) override {
+ return false;
+ }
+ virtual bool SameOriginWithOriginalUri(nsIURI* aURI) override;
+ bool DispatchRelease();
+
+ nsCString mUsername;
+
+ // Needed for accurate DNS timing
+ RefPtr<nsDNSPrefetch> mDNSPrefetch;
+
+ nsCOMPtr<nsIRequest> mTransactionPump;
+ RefPtr<HttpTransactionShell> mTransaction;
+ uint32_t mPushedStreamId{0};
+ RefPtr<HttpTransactionShell> mTransWithPushedStream;
+ DataMutex<nsCOMPtr<nsICancelable>> mProxyRequest;
+ nsCOMPtr<nsIEventTarget> mCurrentEventTarget;
+
+ friend class HttpAsyncAborter<TRRServiceChannel>;
+ friend class nsHttpHandler;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(TRRServiceChannel, NS_TRRSERVICECHANNEL_IID)
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_TRRServiceChannel_h
diff --git a/netwerk/protocol/http/TimingStruct.h b/netwerk/protocol/http/TimingStruct.h
new file mode 100644
index 0000000000..74645e2bff
--- /dev/null
+++ b/netwerk/protocol/http/TimingStruct.h
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TimingStruct_h_
+#define TimingStruct_h_
+
+#include "mozilla/TimeStamp.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+struct TimingStruct {
+ TimeStamp domainLookupStart;
+ TimeStamp domainLookupEnd;
+ TimeStamp connectStart;
+ TimeStamp tcpConnectEnd;
+ TimeStamp secureConnectionStart;
+ TimeStamp connectEnd;
+ TimeStamp requestStart;
+ TimeStamp responseStart;
+ TimeStamp responseEnd;
+ TimeStamp transactionPending;
+};
+
+struct ResourceTimingStruct : TimingStruct {
+ TimeStamp fetchStart;
+ TimeStamp redirectStart;
+ TimeStamp redirectEnd;
+ uint64_t transferSize;
+ uint64_t encodedBodySize;
+
+ // Not actually part of resource timing, but not part of the transaction
+ // timings either. These need to be passed to HttpChannelChild along with
+ // the rest of the timings so the timing information in the child is complete.
+ TimeStamp cacheReadStart;
+ TimeStamp cacheReadEnd;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/TlsHandshaker.cpp b/netwerk/protocol/http/TlsHandshaker.cpp
new file mode 100644
index 0000000000..5288fb939d
--- /dev/null
+++ b/netwerk/protocol/http/TlsHandshaker.cpp
@@ -0,0 +1,336 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "TlsHandshaker.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsHttpConnection.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpHandler.h"
+#include "nsITLSSocketControl.h"
+
+#define TLS_EARLY_DATA_NOT_AVAILABLE 0
+#define TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED 1
+#define TLS_EARLY_DATA_AVAILABLE_AND_USED 2
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(TlsHandshaker, nsITlsHandshakeCallbackListener)
+
+TlsHandshaker::TlsHandshaker(nsHttpConnectionInfo* aInfo,
+ nsHttpConnection* aOwner)
+ : mConnInfo(aInfo), mOwner(aOwner) {
+ LOG(("TlsHandshaker ctor %p", this));
+}
+
+TlsHandshaker::~TlsHandshaker() { LOG(("TlsHandshaker dtor %p", this)); }
+
+NS_IMETHODIMP
+TlsHandshaker::CertVerificationDone() {
+ LOG(("TlsHandshaker::CertVerificationDone mOwner=%p", mOwner.get()));
+ if (mOwner) {
+ Unused << mOwner->ResumeSend();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TlsHandshaker::ClientAuthCertificateSelected() {
+ LOG(("TlsHandshaker::ClientAuthCertificateSelected mOwner=%p", mOwner.get()));
+ if (mOwner) {
+ Unused << mOwner->ResumeSend();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TlsHandshaker::HandshakeDone() {
+ LOG(("TlsHandshaker::HandshakeDone mOwner=%p", mOwner.get()));
+ if (mOwner) {
+ mTlsHandshakeComplitionPending = true;
+
+ // HandshakeDone needs to be dispatched so that it is not called inside
+ // nss locks.
+ RefPtr<TlsHandshaker> self(this);
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "TlsHandshaker::HandshakeDoneInternal", [self{std::move(self)}]() {
+ if (self->mTlsHandshakeComplitionPending && self->mOwner) {
+ self->mOwner->HandshakeDoneInternal();
+ self->mTlsHandshakeComplitionPending = false;
+ }
+ }));
+ }
+ return NS_OK;
+}
+
+void TlsHandshaker::SetupSSL(bool aInSpdyTunnel, bool aForcePlainText) {
+ if (!mOwner) {
+ return;
+ }
+
+ LOG1(("TlsHandshaker::SetupSSL %p caps=0x%X %s\n", mOwner.get(),
+ mOwner->TransactionCaps(), mConnInfo->HashKey().get()));
+
+ if (mSetupSSLCalled) { // do only once
+ return;
+ }
+ mSetupSSLCalled = true;
+
+ if (mNPNComplete) {
+ return;
+ }
+
+ // we flip this back to false if SetNPNList succeeds at the end
+ // of this function
+ mNPNComplete = true;
+
+ if (!mConnInfo->FirstHopSSL() || aForcePlainText) {
+ return;
+ }
+
+ // if we are connected to the proxy with TLS, start the TLS
+ // flow immediately without waiting for a CONNECT sequence.
+ DebugOnly<nsresult> rv{};
+ if (aInSpdyTunnel) {
+ rv = InitSSLParams(false, true);
+ } else {
+ bool usingHttpsProxy = mConnInfo->UsingHttpsProxy();
+ rv = InitSSLParams(usingHttpsProxy, usingHttpsProxy);
+ }
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+nsresult TlsHandshaker::InitSSLParams(bool connectingToProxy,
+ bool proxyStartSSL) {
+ LOG(("TlsHandshaker::InitSSLParams [mOwner=%p] connectingToProxy=%d\n",
+ mOwner.get(), connectingToProxy));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!mOwner) {
+ return NS_ERROR_ABORT;
+ }
+
+ nsCOMPtr<nsITLSSocketControl> ssl;
+ mOwner->GetTLSSocketControl(getter_AddRefs(ssl));
+ if (!ssl) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If proxy is use or 0RTT is excluded for a origin, don't use early-data.
+ if (mConnInfo->UsingProxy() || gHttpHandler->Is0RttTcpExcluded(mConnInfo)) {
+ ssl->DisableEarlyData();
+ }
+
+ if (proxyStartSSL) {
+ nsresult rv = ssl->ProxyStartSSL();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (NS_SUCCEEDED(
+ SetupNPNList(ssl, mOwner->TransactionCaps(), connectingToProxy)) &&
+ NS_SUCCEEDED(ssl->SetHandshakeCallbackListener(this))) {
+ LOG(("InitSSLParams Setting up SPDY Negotiation OK mOwner=%p",
+ mOwner.get()));
+ mNPNComplete = false;
+ }
+
+ return NS_OK;
+}
+
+// The naming of NPN is historical - this function creates the basic
+// offer list for both NPN and ALPN. ALPN validation callbacks are made
+// now before the handshake is complete, and NPN validation callbacks
+// are made during the handshake.
+nsresult TlsHandshaker::SetupNPNList(nsITLSSocketControl* ssl, uint32_t caps,
+ bool connectingToProxy) {
+ nsTArray<nsCString> protocolArray;
+
+ // The first protocol is used as the fallback if none of the
+ // protocols supported overlap with the server's list.
+ // When using ALPN the advertised preferences are protocolArray indicies
+ // {1, .., N, 0} in decreasing order.
+ // For NPN, In the case of overlap, matching priority is driven by
+ // the order of the server's advertisement - with index 0 used when
+ // there is no match.
+ protocolArray.AppendElement("http/1.1"_ns);
+
+ if (StaticPrefs::network_http_http2_enabled() &&
+ (connectingToProxy || !(caps & NS_HTTP_DISALLOW_SPDY)) &&
+ !(connectingToProxy && (caps & NS_HTTP_DISALLOW_HTTP2_PROXY))) {
+ LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
+ const SpdyInformation* info = gHttpHandler->SpdyInfo();
+ if (info->ALPNCallbacks(ssl)) {
+ protocolArray.AppendElement(info->VersionString);
+ }
+ } else {
+ LOG(("nsHttpConnection::SetupSSL Disallow SPDY NPN selection"));
+ }
+
+ nsresult rv = ssl->SetNPNList(protocolArray);
+ LOG(("TlsHandshaker::SetupNPNList %p %" PRIx32 "\n", mOwner.get(),
+ static_cast<uint32_t>(rv)));
+ return rv;
+}
+
+// Checks if TLS handshake is needed and it is responsible to move it forward.
+bool TlsHandshaker::EnsureNPNComplete() {
+ if (!mOwner) {
+ mNPNComplete = true;
+ return true;
+ }
+
+ nsCOMPtr<nsISocketTransport> transport = mOwner->Transport();
+ MOZ_ASSERT(transport);
+ if (!transport) {
+ // this cannot happen
+ mNPNComplete = true;
+ return true;
+ }
+
+ if (mNPNComplete) {
+ return true;
+ }
+
+ if (mTlsHandshakeComplitionPending) {
+ return false;
+ }
+
+ nsCOMPtr<nsITLSSocketControl> ssl;
+ mOwner->GetTLSSocketControl(getter_AddRefs(ssl));
+ if (!ssl) {
+ FinishNPNSetup(false, false);
+ return true;
+ }
+
+ if (!m0RTTChecked) {
+ // We reuse m0RTTChecked. We want to send this status only once.
+ RefPtr<nsAHttpTransaction> transaction = mOwner->Transaction();
+ nsCOMPtr<nsISocketTransport> transport = mOwner->Transport();
+ if (transaction && transport) {
+ transaction->OnTransportStatus(transport,
+ NS_NET_STATUS_TLS_HANDSHAKE_STARTING, 0);
+ }
+ }
+
+ LOG(("TlsHandshaker::EnsureNPNComplete [mOwner=%p] drive TLS handshake",
+ mOwner.get()));
+ nsresult rv = ssl->DriveHandshake();
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ FinishNPNSetup(false, true);
+ return true;
+ }
+
+ Check0RttEnabled(ssl);
+ return false;
+}
+
+void TlsHandshaker::EarlyDataDone() {
+ if (mEarlyDataState == EarlyData::USED) {
+ mEarlyDataState = EarlyData::DONE_USED;
+ } else if (mEarlyDataState == EarlyData::CANNOT_BE_USED) {
+ mEarlyDataState = EarlyData::DONE_CANNOT_BE_USED;
+ } else if (mEarlyDataState == EarlyData::NOT_AVAILABLE) {
+ mEarlyDataState = EarlyData::DONE_NOT_AVAILABLE;
+ }
+}
+
+void TlsHandshaker::FinishNPNSetup(bool handshakeSucceeded,
+ bool hasSecurityInfo) {
+ LOG(("TlsHandshaker::FinishNPNSetup mOwner=%p", mOwner.get()));
+ mNPNComplete = true;
+
+ mOwner->PostProcessNPNSetup(handshakeSucceeded, hasSecurityInfo,
+ EarlyDataUsed());
+ EarlyDataDone();
+}
+
+void TlsHandshaker::Check0RttEnabled(nsITLSSocketControl* ssl) {
+ if (!mOwner) {
+ return;
+ }
+
+ if (m0RTTChecked) {
+ return;
+ }
+
+ m0RTTChecked = true;
+
+ if (mConnInfo->UsingProxy()) {
+ return;
+ }
+
+ // There is no ALPN info (yet!). We need to consider doing 0RTT. We
+ // will do so if there is ALPN information from a previous session
+ // (AlpnEarlySelection), we are using HTTP/1, and the request data can
+ // be safely retried.
+ if (NS_FAILED(ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN))) {
+ LOG1(
+ ("TlsHandshaker::Check0RttEnabled %p - "
+ "early selected alpn not available",
+ mOwner.get()));
+ } else {
+ mOwner->ChangeConnectionState(ConnectionState::ZERORTT);
+ LOG1(
+ ("TlsHandshaker::Check0RttEnabled %p -"
+ "early selected alpn: %s",
+ mOwner.get(), mEarlyNegotiatedALPN.get()));
+ const SpdyInformation* info = gHttpHandler->SpdyInfo();
+ if (!mEarlyNegotiatedALPN.Equals(info->VersionString)) {
+ // This is the HTTP/1 case.
+ // Check if early-data is allowed for this transaction.
+ RefPtr<nsAHttpTransaction> transaction = mOwner->Transaction();
+ if (transaction && transaction->Do0RTT()) {
+ LOG(
+ ("TlsHandshaker::Check0RttEnabled [mOwner=%p] - We "
+ "can do 0RTT (http/1)!",
+ mOwner.get()));
+ mEarlyDataState = EarlyData::USED;
+ } else {
+ mEarlyDataState = EarlyData::CANNOT_BE_USED;
+ // Poll for read now. Polling for write will cause us to busy wait.
+ // When the handshake is done the polling flags will be set correctly.
+ Unused << mOwner->ResumeRecv();
+ }
+ } else {
+ // We have h2, we can at least 0-RTT the preamble and opening
+ // SETTINGS, etc, and maybe some of the first request
+ LOG(
+ ("TlsHandshaker::Check0RttEnabled [mOwner=%p] - Starting "
+ "0RTT for h2!",
+ mOwner.get()));
+ mEarlyDataState = EarlyData::USED;
+ mOwner->Start0RTTSpdy(info->Version);
+ }
+ }
+}
+
+void TlsHandshaker::EarlyDataTelemetry(int16_t tlsVersion,
+ bool earlyDataAccepted,
+ int64_t aContentBytesWritten0RTT) {
+ // Send the 0RTT telemetry only for tls1.3
+ if (tlsVersion > nsITLSSocketControl::TLS_VERSION_1_2) {
+ Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_NEGOTIATED,
+ (mEarlyDataState == EarlyData::NOT_AVAILABLE)
+ ? TLS_EARLY_DATA_NOT_AVAILABLE
+ : ((mEarlyDataState == EarlyData::USED)
+ ? TLS_EARLY_DATA_AVAILABLE_AND_USED
+ : TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED));
+ if (EarlyDataUsed()) {
+ Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_ACCEPTED,
+ earlyDataAccepted);
+ }
+ if (earlyDataAccepted) {
+ Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_BYTES_WRITTEN,
+ aContentBytesWritten0RTT);
+ }
+ }
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/TlsHandshaker.h b/netwerk/protocol/http/TlsHandshaker.h
new file mode 100644
index 0000000000..fc01b07518
--- /dev/null
+++ b/netwerk/protocol/http/TlsHandshaker.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TlsHandshaker_h__
+#define TlsHandshaker_h__
+
+#include "nsITlsHandshakeListener.h"
+
+class nsISocketTransport;
+class nsITLSSocketControl;
+
+namespace mozilla::net {
+
+class nsHttpConnection;
+class nsHttpConnectionInfo;
+
+class TlsHandshaker : public nsITlsHandshakeCallbackListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITLSHANDSHAKECALLBACKLISTENER
+
+ TlsHandshaker(nsHttpConnectionInfo* aInfo, nsHttpConnection* aOwner);
+
+ void SetupSSL(bool aInSpdyTunnel, bool aForcePlainText);
+ [[nodiscard]] nsresult InitSSLParams(bool connectingToProxy,
+ bool ProxyStartSSL);
+ [[nodiscard]] nsresult SetupNPNList(nsITLSSocketControl* ssl, uint32_t caps,
+ bool connectingToProxy);
+ // Makes certain the SSL handshake is complete and NPN negotiation
+ // has had a chance to happen
+ [[nodiscard]] bool EnsureNPNComplete();
+ void FinishNPNSetup(bool handshakeSucceeded, bool hasSecurityInfo);
+ bool EarlyDataAvailable() const {
+ return mEarlyDataState == EarlyData::USED ||
+ mEarlyDataState == EarlyData::CANNOT_BE_USED;
+ }
+ bool EarlyDataWasAvailable() const {
+ return mEarlyDataState != EarlyData::NOT_AVAILABLE &&
+ mEarlyDataState != EarlyData::DONE_NOT_AVAILABLE;
+ }
+ bool EarlyDataUsed() const { return mEarlyDataState == EarlyData::USED; }
+ bool EarlyDataCanNotBeUsed() const {
+ return mEarlyDataState == EarlyData::CANNOT_BE_USED;
+ }
+ void EarlyDataDone();
+ void EarlyDataTelemetry(int16_t tlsVersion, bool earlyDataAccepted,
+ int64_t aContentBytesWritten0RTT);
+
+ bool NPNComplete() const { return mNPNComplete; }
+ bool SetupSSLCalled() const { return mSetupSSLCalled; }
+ bool TlsHandshakeComplitionPending() const {
+ return mTlsHandshakeComplitionPending;
+ }
+ const nsCString& EarlyNegotiatedALPN() const { return mEarlyNegotiatedALPN; }
+ void SetNPNComplete() { mNPNComplete = true; }
+ void NotifyClose() {
+ mTlsHandshakeComplitionPending = false;
+ mOwner = nullptr;
+ }
+
+ private:
+ virtual ~TlsHandshaker();
+
+ void Check0RttEnabled(nsITLSSocketControl* ssl);
+
+ // SPDY related
+ bool mSetupSSLCalled{false};
+ bool mNPNComplete{false};
+
+ bool mTlsHandshakeComplitionPending{false};
+ // Helper variable for 0RTT handshake;
+ // Possible 0RTT has been checked.
+ bool m0RTTChecked{false};
+ // 0RTT data state.
+ enum EarlyData {
+ NOT_AVAILABLE,
+ USED,
+ CANNOT_BE_USED,
+ DONE_NOT_AVAILABLE,
+ DONE_USED,
+ DONE_CANNOT_BE_USED,
+ };
+ EarlyData mEarlyDataState{EarlyData::NOT_AVAILABLE};
+ nsCString mEarlyNegotiatedALPN;
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ // nsHttpConnection and TlsHandshaker create a reference cycle. To break this
+ // cycle, NotifyClose() needs to be called in nsHttpConnection::Close().
+ RefPtr<nsHttpConnection> mOwner;
+};
+
+} // namespace mozilla::net
+
+#endif // TlsHandshaker_h__
diff --git a/netwerk/protocol/http/WellKnownOpportunisticUtils.sys.mjs b/netwerk/protocol/http/WellKnownOpportunisticUtils.sys.mjs
new file mode 100644
index 0000000000..c37eb104d0
--- /dev/null
+++ b/netwerk/protocol/http/WellKnownOpportunisticUtils.sys.mjs
@@ -0,0 +1,26 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+export function WellKnownOpportunisticUtils() {
+ this.valid = false;
+ this.mixed = false;
+ this.lifetime = 0;
+}
+
+WellKnownOpportunisticUtils.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIWellKnownOpportunisticUtils"]),
+
+ verify(aJSON, aOrigin) {
+ try {
+ let arr = JSON.parse(aJSON.toLowerCase());
+ if (!arr.includes(aOrigin.toLowerCase())) {
+ throw new Error("invalid origin");
+ }
+ } catch (e) {
+ return;
+ }
+ this.valid = true;
+ },
+};
diff --git a/netwerk/protocol/http/binary_http/Cargo.toml b/netwerk/protocol/http/binary_http/Cargo.toml
new file mode 100644
index 0000000000..97db55bc33
--- /dev/null
+++ b/netwerk/protocol/http/binary_http/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "binary_http"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+nserror = { path = "../../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../../xpcom/rust/nsstring" }
+bhttp = "0.3"
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
+xpcom = { path = "../../../../xpcom/rust/xpcom" }
diff --git a/netwerk/protocol/http/binary_http/src/binary_http.h b/netwerk/protocol/http/binary_http/src/binary_http.h
new file mode 100644
index 0000000000..2051a00e83
--- /dev/null
+++ b/netwerk/protocol/http/binary_http/src/binary_http.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _binary_http_h_
+#define _binary_http_h_
+
+#include "nsISupportsUtils.h" // for nsresult, etc.
+
+// {b43b3f73-8160-4ab2-9f5d-4129a9708081}
+#define NS_BINARY_HTTP_CID \
+ { \
+ 0xb43b3f73, 0x8160, 0x4ab2, { \
+ 0x9f, 0x5d, 0x41, 0x29, 0xa9, 0x70, 0x80, 0x81 \
+ } \
+ }
+
+extern "C" {
+nsresult binary_http_constructor(REFNSIID iid, void** result);
+};
+
+#endif // _binary_http_h_
diff --git a/netwerk/protocol/http/binary_http/src/lib.rs b/netwerk/protocol/http/binary_http/src/lib.rs
new file mode 100644
index 0000000000..fab357dcca
--- /dev/null
+++ b/netwerk/protocol/http/binary_http/src/lib.rs
@@ -0,0 +1,264 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate bhttp;
+extern crate nserror;
+extern crate nsstring;
+extern crate thin_vec;
+#[macro_use]
+extern crate xpcom;
+
+use bhttp::{Message, Mode};
+use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG, NS_ERROR_UNEXPECTED, NS_OK};
+use nsstring::{nsACString, nsCString};
+use std::io::Cursor;
+use thin_vec::ThinVec;
+use xpcom::interfaces::{nsIBinaryHttpRequest, nsIBinaryHttpResponse};
+use xpcom::RefPtr;
+
+enum HeaderComponent {
+ Name,
+ Value,
+}
+
+// Extracts either the names or the values of a slice of header (name, value) pairs.
+fn extract_header_components(
+ headers: &[(Vec<u8>, Vec<u8>)],
+ component: HeaderComponent,
+) -> ThinVec<nsCString> {
+ let mut header_components = ThinVec::with_capacity(headers.len());
+ for (name, value) in headers {
+ match component {
+ HeaderComponent::Name => header_components.push(nsCString::from(name.clone())),
+ HeaderComponent::Value => header_components.push(nsCString::from(value.clone())),
+ }
+ }
+ header_components
+}
+
+#[xpcom(implement(nsIBinaryHttpRequest), atomic)]
+struct BinaryHttpRequest {
+ method: Vec<u8>,
+ scheme: Vec<u8>,
+ authority: Vec<u8>,
+ path: Vec<u8>,
+ headers: Vec<(Vec<u8>, Vec<u8>)>,
+ content: Vec<u8>,
+}
+
+impl BinaryHttpRequest {
+ xpcom_method!(get_method => GetMethod() -> nsACString);
+ fn get_method(&self) -> Result<nsCString, nsresult> {
+ Ok(nsCString::from(self.method.clone()))
+ }
+
+ xpcom_method!(get_scheme => GetScheme() -> nsACString);
+ fn get_scheme(&self) -> Result<nsCString, nsresult> {
+ Ok(nsCString::from(self.scheme.clone()))
+ }
+
+ xpcom_method!(get_authority => GetAuthority() -> nsACString);
+ fn get_authority(&self) -> Result<nsCString, nsresult> {
+ Ok(nsCString::from(self.authority.clone()))
+ }
+
+ xpcom_method!(get_path => GetPath() -> nsACString);
+ fn get_path(&self) -> Result<nsCString, nsresult> {
+ Ok(nsCString::from(self.path.clone()))
+ }
+
+ xpcom_method!(get_content => GetContent() -> ThinVec<u8>);
+ fn get_content(&self) -> Result<ThinVec<u8>, nsresult> {
+ Ok(self.content.clone().into_iter().collect())
+ }
+
+ xpcom_method!(get_header_names => GetHeaderNames() -> ThinVec<nsCString>);
+ fn get_header_names(&self) -> Result<ThinVec<nsCString>, nsresult> {
+ Ok(extract_header_components(
+ &self.headers,
+ HeaderComponent::Name,
+ ))
+ }
+
+ xpcom_method!(get_header_values => GetHeaderValues() -> ThinVec<nsCString>);
+ fn get_header_values(&self) -> Result<ThinVec<nsCString>, nsresult> {
+ Ok(extract_header_components(
+ &self.headers,
+ HeaderComponent::Value,
+ ))
+ }
+}
+
+#[xpcom(implement(nsIBinaryHttpResponse), atomic)]
+struct BinaryHttpResponse {
+ status: u16,
+ headers: Vec<(Vec<u8>, Vec<u8>)>,
+ content: Vec<u8>,
+}
+
+impl BinaryHttpResponse {
+ xpcom_method!(get_status => GetStatus() -> u16);
+ fn get_status(&self) -> Result<u16, nsresult> {
+ Ok(self.status)
+ }
+
+ xpcom_method!(get_content => GetContent() -> ThinVec<u8>);
+ fn get_content(&self) -> Result<ThinVec<u8>, nsresult> {
+ Ok(self.content.clone().into_iter().collect())
+ }
+
+ xpcom_method!(get_header_names => GetHeaderNames() -> ThinVec<nsCString>);
+ fn get_header_names(&self) -> Result<ThinVec<nsCString>, nsresult> {
+ Ok(extract_header_components(
+ &self.headers,
+ HeaderComponent::Name,
+ ))
+ }
+
+ xpcom_method!(get_header_values => GetHeaderValues() -> ThinVec<nsCString>);
+ fn get_header_values(&self) -> Result<ThinVec<nsCString>, nsresult> {
+ Ok(extract_header_components(
+ &self.headers,
+ HeaderComponent::Value,
+ ))
+ }
+}
+
+#[xpcom(implement(nsIBinaryHttp), atomic)]
+struct BinaryHttp {}
+
+impl BinaryHttp {
+ xpcom_method!(encode_request => EncodeRequest(request: *const nsIBinaryHttpRequest) -> ThinVec<u8>);
+ fn encode_request(&self, request: &nsIBinaryHttpRequest) -> Result<ThinVec<u8>, nsresult> {
+ let mut method = nsCString::new();
+ unsafe { request.GetMethod(&mut *method) }.to_result()?;
+ let mut scheme = nsCString::new();
+ unsafe { request.GetScheme(&mut *scheme) }.to_result()?;
+ let mut authority = nsCString::new();
+ unsafe { request.GetAuthority(&mut *authority) }.to_result()?;
+ let mut path = nsCString::new();
+ unsafe { request.GetPath(&mut *path) }.to_result()?;
+ let mut message = Message::request(
+ method.to_vec(),
+ scheme.to_vec(),
+ authority.to_vec(),
+ path.to_vec(),
+ );
+ let mut header_names = ThinVec::new();
+ unsafe { request.GetHeaderNames(&mut header_names) }.to_result()?;
+ let mut header_values = ThinVec::with_capacity(header_names.len());
+ unsafe { request.GetHeaderValues(&mut header_values) }.to_result()?;
+ if header_names.len() != header_values.len() {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+ for (name, value) in header_names.iter().zip(header_values.iter()) {
+ message.put_header(name.to_vec(), value.to_vec());
+ }
+ let mut content = ThinVec::new();
+ unsafe { request.GetContent(&mut content) }.to_result()?;
+ message.write_content(content);
+ let mut encoded = ThinVec::new();
+ message
+ .write_bhttp(Mode::KnownLength, &mut encoded)
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(encoded)
+ }
+
+ xpcom_method!(decode_response => DecodeResponse(response: *const ThinVec<u8>) -> *const nsIBinaryHttpResponse);
+ fn decode_response(
+ &self,
+ response: &ThinVec<u8>,
+ ) -> Result<RefPtr<nsIBinaryHttpResponse>, nsresult> {
+ let mut cursor = Cursor::new(response);
+ let decoded = Message::read_bhttp(&mut cursor).map_err(|_| NS_ERROR_UNEXPECTED)?;
+ let status = decoded.control().status().ok_or(NS_ERROR_UNEXPECTED)?;
+ let headers = decoded
+ .header()
+ .iter()
+ .map(|field| (field.name().to_vec(), field.value().to_vec()))
+ .collect();
+ let content = decoded.content().to_vec();
+ let binary_http_response = BinaryHttpResponse::allocate(InitBinaryHttpResponse {
+ status,
+ headers,
+ content,
+ });
+ binary_http_response
+ .query_interface::<nsIBinaryHttpResponse>()
+ .ok_or(NS_ERROR_FAILURE)
+ }
+
+ xpcom_method!(decode_request => DecodeRequest(request: *const ThinVec<u8>) -> *const nsIBinaryHttpRequest);
+ fn decode_request(
+ &self,
+ request: &ThinVec<u8>,
+ ) -> Result<RefPtr<nsIBinaryHttpRequest>, nsresult> {
+ let mut cursor = Cursor::new(request);
+ let decoded = Message::read_bhttp(&mut cursor).map_err(|_| NS_ERROR_UNEXPECTED)?;
+ let method = decoded
+ .control()
+ .method()
+ .ok_or(NS_ERROR_UNEXPECTED)?
+ .to_vec();
+ let scheme = decoded
+ .control()
+ .scheme()
+ .ok_or(NS_ERROR_UNEXPECTED)?
+ .to_vec();
+ // authority and path can be empty, in which case we return empty arrays
+ let authority = decoded.control().authority().unwrap_or(&[]).to_vec();
+ let path = decoded.control().path().unwrap_or(&[]).to_vec();
+ let headers = decoded
+ .header()
+ .iter()
+ .map(|field| (field.name().to_vec(), field.value().to_vec()))
+ .collect();
+ let content = decoded.content().to_vec();
+ let binary_http_request = BinaryHttpRequest::allocate(InitBinaryHttpRequest {
+ method,
+ scheme,
+ authority,
+ path,
+ headers,
+ content,
+ });
+ binary_http_request
+ .query_interface::<nsIBinaryHttpRequest>()
+ .ok_or(NS_ERROR_FAILURE)
+ }
+
+ xpcom_method!(encode_response => EncodeResponse(response: *const nsIBinaryHttpResponse) -> ThinVec<u8>);
+ fn encode_response(&self, response: &nsIBinaryHttpResponse) -> Result<ThinVec<u8>, nsresult> {
+ let mut status = 0;
+ unsafe { response.GetStatus(&mut status) }.to_result()?;
+ let mut message = Message::response(status);
+ let mut header_names = ThinVec::new();
+ unsafe { response.GetHeaderNames(&mut header_names) }.to_result()?;
+ let mut header_values = ThinVec::with_capacity(header_names.len());
+ unsafe { response.GetHeaderValues(&mut header_values) }.to_result()?;
+ if header_names.len() != header_values.len() {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+ for (name, value) in header_names.iter().zip(header_values.iter()) {
+ message.put_header(name.to_vec(), value.to_vec());
+ }
+ let mut content = ThinVec::new();
+ unsafe { response.GetContent(&mut content) }.to_result()?;
+ message.write_content(content);
+ let mut encoded = ThinVec::new();
+ message
+ .write_bhttp(Mode::KnownLength, &mut encoded)
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(encoded)
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn binary_http_constructor(
+ iid: *const xpcom::nsIID,
+ result: *mut *mut xpcom::reexports::libc::c_void,
+) -> nsresult {
+ let binary_http = BinaryHttp::allocate(InitBinaryHttp {});
+ unsafe { binary_http.QueryInterface(iid, result) }
+}
diff --git a/netwerk/protocol/http/components.conf b/netwerk/protocol/http/components.conf
new file mode 100644
index 0000000000..8c489d0105
--- /dev/null
+++ b/netwerk/protocol/http/components.conf
@@ -0,0 +1,33 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{b4f96c89-5238-450c-8bda-e12c26f1d150}',
+ 'contract_ids': ['@mozilla.org/network/well-known-opportunistic-utils;1'],
+ 'esModule': 'resource://gre/modules/WellKnownOpportunisticUtils.sys.mjs',
+ 'constructor': 'WellKnownOpportunisticUtils',
+ },
+ {
+ 'cid': '{b43b3f73-8160-4ab2-9f5d-4129a9708081}',
+ 'contract_ids': ['@mozilla.org/network/binary-http;1'],
+ 'headers': ['/netwerk/protocol/http/binary_http/src/binary_http.h'],
+ 'legacy_constructor': 'binary_http_constructor',
+ },
+ {
+ 'cid': '{d581149e-3319-4563-b95e-46c64af5c4e8}',
+ 'contract_ids': ['@mozilla.org/network/oblivious-http;1'],
+ 'headers': ['/netwerk/protocol/http/oblivious_http/src/oblivious_http.h'],
+ 'legacy_constructor': 'oblivious_http_constructor',
+ },
+ {
+ 'cid': '{b1f08d56-fca6-4290-9500-d5168dc9d8c3}',
+ 'contract_ids': ['@mozilla.org/network/oblivious-http-service;1'],
+ 'interfaces': ['nsIObliviousHttpService'],
+ 'type': 'mozilla::net::ObliviousHttpService',
+ 'headers': ['/netwerk/protocol/http/ObliviousHttpService.h'],
+ },
+]
diff --git a/netwerk/protocol/http/http2_huffman_table.txt b/netwerk/protocol/http/http2_huffman_table.txt
new file mode 100644
index 0000000000..bfde068cd9
--- /dev/null
+++ b/netwerk/protocol/http/http2_huffman_table.txt
@@ -0,0 +1,257 @@
+ ( 0) |11111111|11000 1ff8 [13]
+ ( 1) |11111111|11111111|1011000 7fffd8 [23]
+ ( 2) |11111111|11111111|11111110|0010 fffffe2 [28]
+ ( 3) |11111111|11111111|11111110|0011 fffffe3 [28]
+ ( 4) |11111111|11111111|11111110|0100 fffffe4 [28]
+ ( 5) |11111111|11111111|11111110|0101 fffffe5 [28]
+ ( 6) |11111111|11111111|11111110|0110 fffffe6 [28]
+ ( 7) |11111111|11111111|11111110|0111 fffffe7 [28]
+ ( 8) |11111111|11111111|11111110|1000 fffffe8 [28]
+ ( 9) |11111111|11111111|11101010 ffffea [24]
+ ( 10) |11111111|11111111|11111111|111100 3ffffffc [30]
+ ( 11) |11111111|11111111|11111110|1001 fffffe9 [28]
+ ( 12) |11111111|11111111|11111110|1010 fffffea [28]
+ ( 13) |11111111|11111111|11111111|111101 3ffffffd [30]
+ ( 14) |11111111|11111111|11111110|1011 fffffeb [28]
+ ( 15) |11111111|11111111|11111110|1100 fffffec [28]
+ ( 16) |11111111|11111111|11111110|1101 fffffed [28]
+ ( 17) |11111111|11111111|11111110|1110 fffffee [28]
+ ( 18) |11111111|11111111|11111110|1111 fffffef [28]
+ ( 19) |11111111|11111111|11111111|0000 ffffff0 [28]
+ ( 20) |11111111|11111111|11111111|0001 ffffff1 [28]
+ ( 21) |11111111|11111111|11111111|0010 ffffff2 [28]
+ ( 22) |11111111|11111111|11111111|111110 3ffffffe [30]
+ ( 23) |11111111|11111111|11111111|0011 ffffff3 [28]
+ ( 24) |11111111|11111111|11111111|0100 ffffff4 [28]
+ ( 25) |11111111|11111111|11111111|0101 ffffff5 [28]
+ ( 26) |11111111|11111111|11111111|0110 ffffff6 [28]
+ ( 27) |11111111|11111111|11111111|0111 ffffff7 [28]
+ ( 28) |11111111|11111111|11111111|1000 ffffff8 [28]
+ ( 29) |11111111|11111111|11111111|1001 ffffff9 [28]
+ ( 30) |11111111|11111111|11111111|1010 ffffffa [28]
+ ( 31) |11111111|11111111|11111111|1011 ffffffb [28]
+ ' ' ( 32) |010100 14 [ 6]
+ '!' ( 33) |11111110|00 3f8 [10]
+ '"' ( 34) |11111110|01 3f9 [10]
+ '#' ( 35) |11111111|1010 ffa [12]
+ '$' ( 36) |11111111|11001 1ff9 [13]
+ '%' ( 37) |010101 15 [ 6]
+ '&' ( 38) |11111000 f8 [ 8]
+ ''' ( 39) |11111111|010 7fa [11]
+ '(' ( 40) |11111110|10 3fa [10]
+ ')' ( 41) |11111110|11 3fb [10]
+ '*' ( 42) |11111001 f9 [ 8]
+ '+' ( 43) |11111111|011 7fb [11]
+ ',' ( 44) |11111010 fa [ 8]
+ '-' ( 45) |010110 16 [ 6]
+ '.' ( 46) |010111 17 [ 6]
+ '/' ( 47) |011000 18 [ 6]
+ '0' ( 48) |00000 0 [ 5]
+ '1' ( 49) |00001 1 [ 5]
+ '2' ( 50) |00010 2 [ 5]
+ '3' ( 51) |011001 19 [ 6]
+ '4' ( 52) |011010 1a [ 6]
+ '5' ( 53) |011011 1b [ 6]
+ '6' ( 54) |011100 1c [ 6]
+ '7' ( 55) |011101 1d [ 6]
+ '8' ( 56) |011110 1e [ 6]
+ '9' ( 57) |011111 1f [ 6]
+ ':' ( 58) |1011100 5c [ 7]
+ ';' ( 59) |11111011 fb [ 8]
+ '<' ( 60) |11111111|1111100 7ffc [15]
+ '=' ( 61) |100000 20 [ 6]
+ '>' ( 62) |11111111|1011 ffb [12]
+ '?' ( 63) |11111111|00 3fc [10]
+ '@' ( 64) |11111111|11010 1ffa [13]
+ 'A' ( 65) |100001 21 [ 6]
+ 'B' ( 66) |1011101 5d [ 7]
+ 'C' ( 67) |1011110 5e [ 7]
+ 'D' ( 68) |1011111 5f [ 7]
+ 'E' ( 69) |1100000 60 [ 7]
+ 'F' ( 70) |1100001 61 [ 7]
+ 'G' ( 71) |1100010 62 [ 7]
+ 'H' ( 72) |1100011 63 [ 7]
+ 'I' ( 73) |1100100 64 [ 7]
+ 'J' ( 74) |1100101 65 [ 7]
+ 'K' ( 75) |1100110 66 [ 7]
+ 'L' ( 76) |1100111 67 [ 7]
+ 'M' ( 77) |1101000 68 [ 7]
+ 'N' ( 78) |1101001 69 [ 7]
+ 'O' ( 79) |1101010 6a [ 7]
+ 'P' ( 80) |1101011 6b [ 7]
+ 'Q' ( 81) |1101100 6c [ 7]
+ 'R' ( 82) |1101101 6d [ 7]
+ 'S' ( 83) |1101110 6e [ 7]
+ 'T' ( 84) |1101111 6f [ 7]
+ 'U' ( 85) |1110000 70 [ 7]
+ 'V' ( 86) |1110001 71 [ 7]
+ 'W' ( 87) |1110010 72 [ 7]
+ 'X' ( 88) |11111100 fc [ 8]
+ 'Y' ( 89) |1110011 73 [ 7]
+ 'Z' ( 90) |11111101 fd [ 8]
+ '[' ( 91) |11111111|11011 1ffb [13]
+ '\' ( 92) |11111111|11111110|000 7fff0 [19]
+ ']' ( 93) |11111111|11100 1ffc [13]
+ '^' ( 94) |11111111|111100 3ffc [14]
+ '_' ( 95) |100010 22 [ 6]
+ '`' ( 96) |11111111|1111101 7ffd [15]
+ 'a' ( 97) |00011 3 [ 5]
+ 'b' ( 98) |100011 23 [ 6]
+ 'c' ( 99) |00100 4 [ 5]
+ 'd' (100) |100100 24 [ 6]
+ 'e' (101) |00101 5 [ 5]
+ 'f' (102) |100101 25 [ 6]
+ 'g' (103) |100110 26 [ 6]
+ 'h' (104) |100111 27 [ 6]
+ 'i' (105) |00110 6 [ 5]
+ 'j' (106) |1110100 74 [ 7]
+ 'k' (107) |1110101 75 [ 7]
+ 'l' (108) |101000 28 [ 6]
+ 'm' (109) |101001 29 [ 6]
+ 'n' (110) |101010 2a [ 6]
+ 'o' (111) |00111 7 [ 5]
+ 'p' (112) |101011 2b [ 6]
+ 'q' (113) |1110110 76 [ 7]
+ 'r' (114) |101100 2c [ 6]
+ 's' (115) |01000 8 [ 5]
+ 't' (116) |01001 9 [ 5]
+ 'u' (117) |101101 2d [ 6]
+ 'v' (118) |1110111 77 [ 7]
+ 'w' (119) |1111000 78 [ 7]
+ 'x' (120) |1111001 79 [ 7]
+ 'y' (121) |1111010 7a [ 7]
+ 'z' (122) |1111011 7b [ 7]
+ '{' (123) |11111111|1111110 7ffe [15]
+ '|' (124) |11111111|100 7fc [11]
+ '}' (125) |11111111|111101 3ffd [14]
+ '~' (126) |11111111|11101 1ffd [13]
+ (127) |11111111|11111111|11111111|1100 ffffffc [28]
+ (128) |11111111|11111110|0110 fffe6 [20]
+ (129) |11111111|11111111|010010 3fffd2 [22]
+ (130) |11111111|11111110|0111 fffe7 [20]
+ (131) |11111111|11111110|1000 fffe8 [20]
+ (132) |11111111|11111111|010011 3fffd3 [22]
+ (133) |11111111|11111111|010100 3fffd4 [22]
+ (134) |11111111|11111111|010101 3fffd5 [22]
+ (135) |11111111|11111111|1011001 7fffd9 [23]
+ (136) |11111111|11111111|010110 3fffd6 [22]
+ (137) |11111111|11111111|1011010 7fffda [23]
+ (138) |11111111|11111111|1011011 7fffdb [23]
+ (139) |11111111|11111111|1011100 7fffdc [23]
+ (140) |11111111|11111111|1011101 7fffdd [23]
+ (141) |11111111|11111111|1011110 7fffde [23]
+ (142) |11111111|11111111|11101011 ffffeb [24]
+ (143) |11111111|11111111|1011111 7fffdf [23]
+ (144) |11111111|11111111|11101100 ffffec [24]
+ (145) |11111111|11111111|11101101 ffffed [24]
+ (146) |11111111|11111111|010111 3fffd7 [22]
+ (147) |11111111|11111111|1100000 7fffe0 [23]
+ (148) |11111111|11111111|11101110 ffffee [24]
+ (149) |11111111|11111111|1100001 7fffe1 [23]
+ (150) |11111111|11111111|1100010 7fffe2 [23]
+ (151) |11111111|11111111|1100011 7fffe3 [23]
+ (152) |11111111|11111111|1100100 7fffe4 [23]
+ (153) |11111111|11111110|11100 1fffdc [21]
+ (154) |11111111|11111111|011000 3fffd8 [22]
+ (155) |11111111|11111111|1100101 7fffe5 [23]
+ (156) |11111111|11111111|011001 3fffd9 [22]
+ (157) |11111111|11111111|1100110 7fffe6 [23]
+ (158) |11111111|11111111|1100111 7fffe7 [23]
+ (159) |11111111|11111111|11101111 ffffef [24]
+ (160) |11111111|11111111|011010 3fffda [22]
+ (161) |11111111|11111110|11101 1fffdd [21]
+ (162) |11111111|11111110|1001 fffe9 [20]
+ (163) |11111111|11111111|011011 3fffdb [22]
+ (164) |11111111|11111111|011100 3fffdc [22]
+ (165) |11111111|11111111|1101000 7fffe8 [23]
+ (166) |11111111|11111111|1101001 7fffe9 [23]
+ (167) |11111111|11111110|11110 1fffde [21]
+ (168) |11111111|11111111|1101010 7fffea [23]
+ (169) |11111111|11111111|011101 3fffdd [22]
+ (170) |11111111|11111111|011110 3fffde [22]
+ (171) |11111111|11111111|11110000 fffff0 [24]
+ (172) |11111111|11111110|11111 1fffdf [21]
+ (173) |11111111|11111111|011111 3fffdf [22]
+ (174) |11111111|11111111|1101011 7fffeb [23]
+ (175) |11111111|11111111|1101100 7fffec [23]
+ (176) |11111111|11111111|00000 1fffe0 [21]
+ (177) |11111111|11111111|00001 1fffe1 [21]
+ (178) |11111111|11111111|100000 3fffe0 [22]
+ (179) |11111111|11111111|00010 1fffe2 [21]
+ (180) |11111111|11111111|1101101 7fffed [23]
+ (181) |11111111|11111111|100001 3fffe1 [22]
+ (182) |11111111|11111111|1101110 7fffee [23]
+ (183) |11111111|11111111|1101111 7fffef [23]
+ (184) |11111111|11111110|1010 fffea [20]
+ (185) |11111111|11111111|100010 3fffe2 [22]
+ (186) |11111111|11111111|100011 3fffe3 [22]
+ (187) |11111111|11111111|100100 3fffe4 [22]
+ (188) |11111111|11111111|1110000 7ffff0 [23]
+ (189) |11111111|11111111|100101 3fffe5 [22]
+ (190) |11111111|11111111|100110 3fffe6 [22]
+ (191) |11111111|11111111|1110001 7ffff1 [23]
+ (192) |11111111|11111111|11111000|00 3ffffe0 [26]
+ (193) |11111111|11111111|11111000|01 3ffffe1 [26]
+ (194) |11111111|11111110|1011 fffeb [20]
+ (195) |11111111|11111110|001 7fff1 [19]
+ (196) |11111111|11111111|100111 3fffe7 [22]
+ (197) |11111111|11111111|1110010 7ffff2 [23]
+ (198) |11111111|11111111|101000 3fffe8 [22]
+ (199) |11111111|11111111|11110110|0 1ffffec [25]
+ (200) |11111111|11111111|11111000|10 3ffffe2 [26]
+ (201) |11111111|11111111|11111000|11 3ffffe3 [26]
+ (202) |11111111|11111111|11111001|00 3ffffe4 [26]
+ (203) |11111111|11111111|11111011|110 7ffffde [27]
+ (204) |11111111|11111111|11111011|111 7ffffdf [27]
+ (205) |11111111|11111111|11111001|01 3ffffe5 [26]
+ (206) |11111111|11111111|11110001 fffff1 [24]
+ (207) |11111111|11111111|11110110|1 1ffffed [25]
+ (208) |11111111|11111110|010 7fff2 [19]
+ (209) |11111111|11111111|00011 1fffe3 [21]
+ (210) |11111111|11111111|11111001|10 3ffffe6 [26]
+ (211) |11111111|11111111|11111100|000 7ffffe0 [27]
+ (212) |11111111|11111111|11111100|001 7ffffe1 [27]
+ (213) |11111111|11111111|11111001|11 3ffffe7 [26]
+ (214) |11111111|11111111|11111100|010 7ffffe2 [27]
+ (215) |11111111|11111111|11110010 fffff2 [24]
+ (216) |11111111|11111111|00100 1fffe4 [21]
+ (217) |11111111|11111111|00101 1fffe5 [21]
+ (218) |11111111|11111111|11111010|00 3ffffe8 [26]
+ (219) |11111111|11111111|11111010|01 3ffffe9 [26]
+ (220) |11111111|11111111|11111111|1101 ffffffd [28]
+ (221) |11111111|11111111|11111100|011 7ffffe3 [27]
+ (222) |11111111|11111111|11111100|100 7ffffe4 [27]
+ (223) |11111111|11111111|11111100|101 7ffffe5 [27]
+ (224) |11111111|11111110|1100 fffec [20]
+ (225) |11111111|11111111|11110011 fffff3 [24]
+ (226) |11111111|11111110|1101 fffed [20]
+ (227) |11111111|11111111|00110 1fffe6 [21]
+ (228) |11111111|11111111|101001 3fffe9 [22]
+ (229) |11111111|11111111|00111 1fffe7 [21]
+ (230) |11111111|11111111|01000 1fffe8 [21]
+ (231) |11111111|11111111|1110011 7ffff3 [23]
+ (232) |11111111|11111111|101010 3fffea [22]
+ (233) |11111111|11111111|101011 3fffeb [22]
+ (234) |11111111|11111111|11110111|0 1ffffee [25]
+ (235) |11111111|11111111|11110111|1 1ffffef [25]
+ (236) |11111111|11111111|11110100 fffff4 [24]
+ (237) |11111111|11111111|11110101 fffff5 [24]
+ (238) |11111111|11111111|11111010|10 3ffffea [26]
+ (239) |11111111|11111111|1110100 7ffff4 [23]
+ (240) |11111111|11111111|11111010|11 3ffffeb [26]
+ (241) |11111111|11111111|11111100|110 7ffffe6 [27]
+ (242) |11111111|11111111|11111011|00 3ffffec [26]
+ (243) |11111111|11111111|11111011|01 3ffffed [26]
+ (244) |11111111|11111111|11111100|111 7ffffe7 [27]
+ (245) |11111111|11111111|11111101|000 7ffffe8 [27]
+ (246) |11111111|11111111|11111101|001 7ffffe9 [27]
+ (247) |11111111|11111111|11111101|010 7ffffea [27]
+ (248) |11111111|11111111|11111101|011 7ffffeb [27]
+ (249) |11111111|11111111|11111111|1110 ffffffe [28]
+ (250) |11111111|11111111|11111101|100 7ffffec [27]
+ (251) |11111111|11111111|11111101|101 7ffffed [27]
+ (252) |11111111|11111111|11111101|110 7ffffee [27]
+ (253) |11111111|11111111|11111101|111 7ffffef [27]
+ (254) |11111111|11111111|11111110|000 7fffff0 [27]
+ (255) |11111111|11111111|11111011|10 3ffffee [26]
+ EOS (256) |11111111|11111111|11111111|111111 3fffffff [30]
diff --git a/netwerk/protocol/http/make_incoming_tables.py b/netwerk/protocol/http/make_incoming_tables.py
new file mode 100644
index 0000000000..be917a6f0b
--- /dev/null
+++ b/netwerk/protocol/http/make_incoming_tables.py
@@ -0,0 +1,202 @@
+# This script exists to auto-generate Http2HuffmanIncoming.h from the table
+# contained in the HPACK spec. It's pretty simple to run:
+# python make_incoming_tables.py < http2_huffman_table.txt > Http2HuffmanIncoming.h
+# where huff_incoming.txt is copy/pasted text from the latest version of the
+# HPACK spec, with all non-relevant lines removed (the most recent version
+# of huff_incoming.txt also lives in this directory as an example).
+import sys
+
+
+def char_cmp(x, y):
+ rv = cmp(x["nbits"], y["nbits"])
+ if not rv:
+ rv = cmp(x["bpat"], y["bpat"])
+ if not rv:
+ rv = cmp(x["ascii"], y["ascii"])
+ return rv
+
+
+characters = []
+
+for line in sys.stdin:
+ line = line.rstrip()
+ obracket = line.rfind("[")
+ nbits = int(line[obracket + 1 : -1])
+
+ oparen = line.find(" (")
+ ascii = int(line[oparen + 2 : oparen + 5].strip())
+
+ bar = line.find("|", oparen)
+ space = line.find(" ", bar)
+ bpat = line[bar + 1 : space].strip().rstrip("|")
+
+ characters.append({"ascii": ascii, "nbits": nbits, "bpat": bpat})
+
+characters.sort(cmp=char_cmp)
+raw_entries = []
+for c in characters:
+ raw_entries.append((c["ascii"], c["bpat"]))
+
+
+class DefaultList(list):
+ def __init__(self, default=None):
+ self.__default = default
+
+ def __ensure_size(self, sz):
+ while sz > len(self):
+ self.append(self.__default)
+
+ def __getitem__(self, idx):
+ self.__ensure_size(idx + 1)
+ rv = super(DefaultList, self).__getitem__(idx)
+ return rv
+
+ def __setitem__(self, idx, val):
+ self.__ensure_size(idx + 1)
+ super(DefaultList, self).__setitem__(idx, val)
+
+
+def expand_to_8bit(bstr):
+ while len(bstr) < 8:
+ bstr += "0"
+ return int(bstr, 2)
+
+
+table = DefaultList()
+for r in raw_entries:
+ ascii, bpat = r
+ ascii = int(ascii)
+ bstrs = bpat.split("|")
+ curr_table = table
+ while len(bstrs) > 1:
+ idx = expand_to_8bit(bstrs[0])
+ if curr_table[idx] is None:
+ curr_table[idx] = DefaultList()
+ curr_table = curr_table[idx]
+ bstrs.pop(0)
+
+ idx = expand_to_8bit(bstrs[0])
+ curr_table[idx] = {
+ "prefix_len": len(bstrs[0]),
+ "mask": int(bstrs[0], 2),
+ "value": ascii,
+ }
+
+
+def output_table(table, name_suffix=""):
+ max_prefix_len = 0
+ for i, t in enumerate(table):
+ if isinstance(t, dict):
+ if t["prefix_len"] > max_prefix_len:
+ max_prefix_len = t["prefix_len"]
+ elif t is not None:
+ output_table(t, "%s_%s" % (name_suffix, i))
+
+ tablename = "HuffmanIncoming%s" % (name_suffix if name_suffix else "Root",)
+ entriestable = tablename.replace("HuffmanIncoming", "HuffmanIncomingEntries")
+ nexttable = tablename.replace("HuffmanIncoming", "HuffmanIncomingNextTables")
+ sys.stdout.write("static const HuffmanIncomingEntry %s[] = {\n" % (entriestable,))
+ prefix_len = 0
+ value = 0
+ i = 0
+ while i < 256:
+ t = table[i]
+ if isinstance(t, dict):
+ value = t["value"]
+ prefix_len = t["prefix_len"]
+ elif t is not None:
+ break
+
+ sys.stdout.write(" { %s, %s }" % (value, prefix_len))
+ sys.stdout.write(",\n")
+ i += 1
+
+ indexOfFirstNextTable = i
+ if i < 256:
+ sys.stdout.write("};\n")
+ sys.stdout.write("\n")
+ sys.stdout.write("static const HuffmanIncomingTable* %s[] = {\n" % (nexttable,))
+ while i < 256:
+ subtable = "%s_%s" % (name_suffix, i)
+ ptr = "&HuffmanIncoming%s" % (subtable,)
+ sys.stdout.write(" %s" % (ptr))
+ sys.stdout.write(",\n")
+ i += 1
+ else:
+ nexttable = "nullptr"
+
+ sys.stdout.write("};\n")
+ sys.stdout.write("\n")
+ sys.stdout.write("static const HuffmanIncomingTable %s = {\n" % (tablename,))
+ sys.stdout.write(" %s,\n" % (entriestable,))
+ sys.stdout.write(" %s,\n" % (nexttable,))
+ sys.stdout.write(" %s,\n" % (indexOfFirstNextTable,))
+ sys.stdout.write(" %s\n" % (max_prefix_len,))
+ sys.stdout.write("};\n")
+ sys.stdout.write("\n")
+
+
+sys.stdout.write(
+ """/*
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT!
+ */
+#ifndef mozilla__net__Http2HuffmanIncoming_h
+#define mozilla__net__Http2HuffmanIncoming_h
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanIncomingTable;
+
+struct HuffmanIncomingEntry {
+ uint16_t mValue:9; // 9 bits so it can hold 0..256
+ uint16_t mPrefixLen:7; // only holds 1..8
+};
+
+// The data members are public only so they can be statically constructed. All
+// accesses should be done through the functions.
+struct HuffmanIncomingTable {
+ // The normal entries, for indices in the range 0..(mNumEntries-1).
+ const HuffmanIncomingEntry* const mEntries;
+
+ // The next tables, for indices in the range mNumEntries..255. Must be
+ // |nullptr| if mIndexOfFirstNextTable is 256.
+ const HuffmanIncomingTable** const mNextTables;
+
+ // The index of the first next table (equal to the number of entries in
+ // mEntries). This cannot be a uint8_t because it can have the value 256,
+ // in which case there are no next tables and mNextTables must be |nullptr|.
+ const uint16_t mIndexOfFirstNextTable;
+
+ const uint8_t mPrefixLen;
+
+ bool IndexHasANextTable(uint8_t aIndex) const
+ {
+ return aIndex >= mIndexOfFirstNextTable;
+ }
+
+ const HuffmanIncomingEntry* Entry(uint8_t aIndex) const
+ {
+ MOZ_ASSERT(aIndex < mIndexOfFirstNextTable);
+ return &mEntries[aIndex];
+ }
+
+ const HuffmanIncomingTable* NextTable(uint8_t aIndex) const
+ {
+ MOZ_ASSERT(aIndex >= mIndexOfFirstNextTable);
+ return mNextTables[aIndex - mIndexOfFirstNextTable];
+ }
+};
+
+"""
+)
+
+output_table(table)
+
+sys.stdout.write(
+ """} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__Http2HuffmanIncoming_h
+"""
+)
diff --git a/netwerk/protocol/http/make_outgoing_tables.py b/netwerk/protocol/http/make_outgoing_tables.py
new file mode 100644
index 0000000000..b9a4268202
--- /dev/null
+++ b/netwerk/protocol/http/make_outgoing_tables.py
@@ -0,0 +1,58 @@
+# This script exists to auto-generate Http2HuffmanOutgoing.h from the table
+# contained in the HPACK spec. It's pretty simple to run:
+# python make_outgoing_tables.py < http2_huffman_table.txt > Http2HuffmanOutgoing.h
+# where huff_outgoing.txt is copy/pasted text from the latest version of the
+# HPACK spec, with all non-relevant lines removed (the most recent version
+# of huff_outgoing.txt also lives in this directory as an example).
+import sys
+
+sys.stdout.write(
+ """/*
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT!
+ */
+#ifndef mozilla__net__Http2HuffmanOutgoing_h
+#define mozilla__net__Http2HuffmanOutgoing_h
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanOutgoingEntry {
+ uint32_t mValue;
+ uint8_t mLength;
+};
+
+static const HuffmanOutgoingEntry HuffmanOutgoing[] = {
+"""
+)
+
+entries = []
+for line in sys.stdin:
+ line = line.strip()
+ obracket = line.rfind("[")
+ nbits = int(line[obracket + 1 : -1])
+
+ lastbar = line.rfind("|")
+ space = line.find(" ", lastbar)
+ encend = line.rfind(" ", 0, obracket)
+
+ enc = line[space:encend].strip()
+ val = int(enc, 16)
+
+ entries.append({"length": nbits, "value": val})
+
+line = []
+for i, e in enumerate(entries):
+ sys.stdout.write(" { 0x%08x, %s }" % (e["value"], e["length"]))
+ if i < (len(entries) - 1):
+ sys.stdout.write(",")
+ sys.stdout.write("\n")
+
+sys.stdout.write(
+ """};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__Http2HuffmanOutgoing_h
+"""
+)
diff --git a/netwerk/protocol/http/metrics.yaml b/netwerk/protocol/http/metrics.yaml
new file mode 100644
index 0000000000..5c92eb2afd
--- /dev/null
+++ b/netwerk/protocol/http/metrics.yaml
@@ -0,0 +1,275 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Adding a new metric? We have docs for that!
+# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
+$tags:
+ - 'Core :: Networking: HTTP'
+
+
+netwerk:
+ early_hints:
+ type: labeled_counter
+ labels:
+ - stylesheet
+ - fonts
+ - scripts
+ - fetch
+ - image
+ - other
+ description: >
+ Counts the different type of resources that are sent for early hints.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1772124
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1772124
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1797695
+ notification_emails:
+ - necko@mozilla.com
+ - vgosu@mozilla.com
+ expires: never
+ eh_link_type:
+ type: labeled_counter
+ labels:
+ - dns-prefetch
+ - icon
+ - modulepreload
+ - preconnect
+ - prefetch
+ - preload
+ - prerender
+ - stylesheet
+ - other
+ description: >
+ Counts different type of link headers that are sent in early hint
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1797936
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1797936
+ notification_emails:
+ - necko@mozilla.com
+ - manuel@mozilla.com
+ expires: never
+
+network:
+ data_size_per_type:
+ type: labeled_counter
+ labels:
+ - text_html
+ - text_css
+ - text_json
+ - text_plain
+ - text_javascript
+ - text_other
+ - audio
+ - video
+ - multipart
+ - icon
+ - image
+ - ocsp
+ - xpinstall
+ - wasm
+ - pdf
+ - octet_stream
+ - proxy
+ - compressed
+ - x509
+ - application_other
+ description: >
+ Number of KB we transferred keyed by "contentType"
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1808695
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1808695
+ notification_emails:
+ - necko@mozilla.com
+ - rtestard@mozilla.com
+ expires: 130
+ telemetry_mirror: NETWORKING_DATA_TRANSFERRED_PER_CONTENT_TYPE
+ no_lint:
+ - COMMON_PREFIX
+
+ data_size_pb_per_type:
+ type: labeled_counter
+ labels:
+ - text_html
+ - text_css
+ - text_json
+ - text_plain
+ - text_javascript
+ - text_other
+ - audio
+ - video
+ - multipart
+ - icon
+ - image
+ - ocsp
+ - xpinstall
+ - wasm
+ - pdf
+ - octet_stream
+ - proxy
+ - compressed
+ - x509
+ - application_other
+ description: >
+ Number of KB we transferred keyed by "contentType"
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1808695
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1808695
+ notification_emails:
+ - necko@mozilla.com
+ - rtestard@mozilla.com
+ expires: 130
+ telemetry_mirror: NETWORKING_DATA_TRANSFERRED_PB_PER_CONTENT_TYPE
+ no_lint:
+ - COMMON_PREFIX
+
+ cors_authorization_header:
+ type: labeled_counter
+ labels:
+ - allowed
+ - disallowed
+ - covered_by_wildcard
+ description: >
+ Count how many times we see `Authorization` header in
+ `Access-Control-Request-Headers` header and the possible outcomes.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1687364
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1687364
+ notification_emails:
+ - necko@mozilla.com
+ - kershaw@mozilla.com
+ expires: 130
+
+ cache_hit_time:
+ type: timing_distribution
+ time_unit: millisecond
+ telemetry_mirror: NETWORK_CACHE_V2_HIT_TIME_MS
+ description: >
+ Time to open existing cache entry file.
+ (Migrated from the geckoview metric of the same name).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1489524
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - necko@mozilla.com
+ expires: never
+
+ font_download_end:
+ type: timing_distribution
+ time_unit: millisecond
+ telemetry_mirror: WEBFONT_DOWNLOAD_TIME_AFTER_START
+ description: >
+ Time after navigationStart that all webfont downloads are completed.
+ (Migrated from the geckoview metric of the same name).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - perf-telemetry-alerts@mozilla.com
+ - necko@mozilla.com
+ - bdekoz@mozilla.com
+ expires: never
+
+ first_from_cache:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ In the HTTP page channel, time from connection open to cache read start.
+ Corresponds to Legacy histogram HTTP_PAGE_OPEN_TO_FIRST_FROM_CACHE_V2 in
+ Desktop.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - necko@mozilla.com
+ expires: never
+
+ tcp_connection:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ In the HTTP page channel, time from the TCP SYN packet is received to
+ the connection is established and ready for HTTP.
+ Corresponds to Legacy histogram HTTP_PAGE_TCP_CONNECTION_2 in Desktop
+ (Migrated from the geckoview metric of the same name).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=772589
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - necko@mozilla.com
+ - vgosu@mozilla.com
+ expires: never
+
+ dns_start:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ In the HTTP page channel, time from connection open to the DNS request
+ being issued.
+ Corresponds to Legacy histogram HTTP_PAGE_DNS_ISSUE_TIME in Desktop.
+ (Migrated from the geckoview metric of the same name).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - necko@mozilla.com
+ - vgosu@mozilla.com
+ expires: never
+
+ dns_end:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ In the HTTP page channel, time from the DNS request being issued to
+ the response.
+ Corresponds to Legacy histogram HTTP_PAGE_DNS_LOOKUP_TIME in Desktop.
+ (Migrated from the geckoview metric of the same name).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - necko@mozilla.com
+ - vgosu@mozilla.com
+ expires: never
+
+ tls_handshake:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ In the HTTP page channel, time from after the TCP SYN packet is
+ received to the secure connection is established and ready for HTTP.
+ Corresponds to Legacy histogram HTTP_PAGE_TLS_HANDSHAKE in Desktop.
+ (Migrated from the geckoview metric of the same name).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=772589
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877839
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - necko@mozilla.com
+ - vgosu@mozilla.com
+ expires: never
diff --git a/netwerk/protocol/http/moz.build b/netwerk/protocol/http/moz.build
new file mode 100644
index 0000000000..db6b2a1a68
--- /dev/null
+++ b/netwerk/protocol/http/moz.build
@@ -0,0 +1,219 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: HTTP")
+
+XPIDL_SOURCES += [
+ "nsIBackgroundChannelRegistrar.idl",
+ "nsIBinaryHttp.idl",
+ "nsIEarlyHintObserver.idl",
+ "nsIHttpActivityObserver.idl",
+ "nsIHttpAuthenticableChannel.idl",
+ "nsIHttpAuthenticator.idl",
+ "nsIHttpAuthManager.idl",
+ "nsIHttpChannel.idl",
+ "nsIHttpChannelAuthProvider.idl",
+ "nsIHttpChannelChild.idl",
+ "nsIHttpChannelInternal.idl",
+ "nsIHttpHeaderVisitor.idl",
+ "nsIHttpProtocolHandler.idl",
+ "nsIObliviousHttp.idl",
+ "nsIObliviousHttpChannel.idl",
+ "nsIRaceCacheWithNetwork.idl",
+ "nsITlsHandshakeListener.idl",
+ "nsIWellKnownOpportunisticUtils.idl",
+]
+
+XPIDL_MODULE = "necko_http"
+
+EXPORTS += [
+ "nsCORSListenerProxy.h",
+ "nsHttp.h",
+ "nsHttpAtomList.h",
+ "nsHttpHeaderArray.h",
+ "nsHttpRequestHead.h",
+ "nsHttpResponseHead.h",
+]
+
+EXPORTS.mozilla.net += [
+ "AltDataOutputStreamChild.h",
+ "AltDataOutputStreamParent.h",
+ "AltServiceChild.h",
+ "AltServiceParent.h",
+ "AltSvcTransactionChild.h",
+ "AltSvcTransactionParent.h",
+ "BackgroundChannelRegistrar.h",
+ "BackgroundDataBridgeChild.h",
+ "BackgroundDataBridgeParent.h",
+ "ClassOfService.h",
+ "EarlyHintPreloader.h",
+ "EarlyHintRegistrar.h",
+ "EarlyHintsService.h",
+ "HttpAuthUtils.h",
+ "HttpBackgroundChannelChild.h",
+ "HttpBackgroundChannelParent.h",
+ "HttpBaseChannel.h",
+ "HttpChannelChild.h",
+ "HttpChannelParent.h",
+ "HttpConnectionMgrChild.h",
+ "HttpConnectionMgrParent.h",
+ "HttpConnectionMgrShell.h",
+ "HttpInfo.h",
+ "HttpTransactionChild.h",
+ "HttpTransactionParent.h",
+ "HttpTransactionShell.h",
+ "nsAHttpTransaction.h",
+ "nsServerTiming.h",
+ "NullHttpChannel.h",
+ "NullHttpTransaction.h",
+ "OpaqueResponseUtils.h",
+ "ParentChannelListener.h",
+ "PHttpChannelParams.h",
+ "PSpdyPush.h",
+ "SpeculativeTransaction.h",
+ "TimingStruct.h",
+]
+
+SOURCES += [
+ "nsHttpChannelAuthProvider.cpp", # redefines GetAuthType
+]
+
+UNIFIED_SOURCES += [
+ "AltDataOutputStreamChild.cpp",
+ "AltDataOutputStreamParent.cpp",
+ "AlternateServices.cpp",
+ "AltServiceChild.cpp",
+ "AltServiceParent.cpp",
+ "AltSvcTransactionChild.cpp",
+ "AltSvcTransactionParent.cpp",
+ "ASpdySession.cpp",
+ "BackgroundChannelRegistrar.cpp",
+ "BackgroundDataBridgeChild.cpp",
+ "BackgroundDataBridgeParent.cpp",
+ "BinaryHttpRequest.cpp",
+ "CacheControlParser.cpp",
+ "CachePushChecker.cpp",
+ "ConnectionDiagnostics.cpp",
+ "ConnectionEntry.cpp",
+ "ConnectionHandle.cpp",
+ "DnsAndConnectSocket.cpp",
+ "EarlyHintPreconnect.cpp",
+ "EarlyHintPreloader.cpp",
+ "EarlyHintRegistrar.cpp",
+ "EarlyHintsService.cpp",
+ "Http2Compression.cpp",
+ "Http2Push.cpp",
+ "Http2Session.cpp",
+ "Http2Stream.cpp",
+ "Http2StreamBase.cpp",
+ "Http2StreamTunnel.cpp",
+ "Http3Session.cpp",
+ "Http3Stream.cpp",
+ "Http3WebTransportSession.cpp",
+ "Http3WebTransportStream.cpp",
+ "HttpAuthUtils.cpp",
+ "HttpBackgroundChannelChild.cpp",
+ "HttpBackgroundChannelParent.cpp",
+ "HttpBaseChannel.cpp",
+ "HttpChannelChild.cpp",
+ "HttpChannelParent.cpp",
+ "HttpConnectionBase.cpp",
+ "HttpConnectionMgrChild.cpp",
+ "HttpConnectionMgrParent.cpp",
+ "HttpConnectionUDP.cpp",
+ "HttpInfo.cpp",
+ "HTTPSRecordResolver.cpp",
+ "HttpTrafficAnalyzer.cpp",
+ "HttpTransactionChild.cpp",
+ "HttpTransactionParent.cpp",
+ "InterceptedHttpChannel.cpp",
+ "MockHttpAuth.cpp",
+ "NetworkMarker.cpp",
+ "nsCORSListenerProxy.cpp",
+ "nsHttp.cpp",
+ "nsHttpActivityDistributor.cpp",
+ "nsHttpAuthCache.cpp",
+ "nsHttpAuthManager.cpp",
+ "nsHttpBasicAuth.cpp",
+ "nsHttpChannel.cpp",
+ "nsHttpChunkedDecoder.cpp",
+ "nsHttpConnection.cpp",
+ "nsHttpConnectionInfo.cpp",
+ "nsHttpConnectionMgr.cpp",
+ "nsHttpDigestAuth.cpp",
+ "nsHttpHeaderArray.cpp",
+ "nsHttpNTLMAuth.cpp",
+ "nsHttpRequestHead.cpp",
+ "nsHttpResponseHead.cpp",
+ "nsHttpTransaction.cpp",
+ "nsServerTiming.cpp",
+ "NullHttpChannel.cpp",
+ "NullHttpTransaction.cpp",
+ "ObliviousHttpChannel.cpp",
+ "ObliviousHttpService.cpp",
+ "OpaqueResponseUtils.cpp",
+ "ParentChannelListener.cpp",
+ "PendingTransactionInfo.cpp",
+ "PendingTransactionQueue.cpp",
+ "QuicSocketControl.cpp",
+ "SpeculativeTransaction.cpp",
+ "TlsHandshaker.cpp",
+ "TLSTransportLayer.cpp",
+ "TRRServiceChannel.cpp",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ UNIFIED_SOURCES += [
+ "HttpWinUtils.cpp",
+ ]
+
+# These files cannot be built in unified mode because of OS X headers.
+SOURCES += [
+ "nsHttpHandler.cpp",
+]
+
+IPDL_SOURCES += [
+ "HttpChannelParams.ipdlh",
+ "PAltDataOutputStream.ipdl",
+ "PAltService.ipdl",
+ "PAltSvcTransaction.ipdl",
+ "PBackgroundDataBridge.ipdl",
+ "PHttpBackgroundChannel.ipdl",
+ "PHttpChannel.ipdl",
+ "PHttpConnectionMgr.ipdl",
+ "PHttpTransaction.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/netwerk/base",
+ "/netwerk/cookie",
+ "/netwerk/dns",
+ "/netwerk/ipc",
+ "/netwerk/socket/neqo_glue",
+ "/netwerk/url-classifier",
+]
+
+if CONFIG["MOZ_AUTH_EXTENSION"]:
+ LOCAL_INCLUDES += [
+ "/extensions/auth",
+ ]
+
+EXTRA_JS_MODULES += [
+ "HPKEConfigManager.sys.mjs",
+ "WellKnownOpportunisticUtils.sys.mjs",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/netwerk/protocol/http/nsAHttpConnection.h b/netwerk/protocol/http/nsAHttpConnection.h
new file mode 100644
index 0000000000..7b28096bb4
--- /dev/null
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -0,0 +1,292 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAHttpConnection_h__
+#define nsAHttpConnection_h__
+
+#include "mozilla/net/DNS.h"
+#include "nsHttp.h"
+#include "nsISupports.h"
+#include "nsAHttpTransaction.h"
+#include "Http3WebTransportSession.h"
+#include "HttpTrafficAnalyzer.h"
+#include "nsIRequest.h"
+
+class nsIAsyncInputStream;
+class nsIAsyncOutputStream;
+class nsISocketTransport;
+class nsITLSSocketControl;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpConnectionInfo;
+class HttpConnectionBase;
+class nsHttpRequestHead;
+class nsHttpResponseHead;
+
+//-----------------------------------------------------------------------------
+// Abstract base class for a HTTP connection
+//-----------------------------------------------------------------------------
+
+// 5a66aed7-eede-468b-ac2b-e5fb431fcc5c
+#define NS_AHTTPCONNECTION_IID \
+ { \
+ 0x5a66aed7, 0xeede, 0x468b, { \
+ 0xac, 0x2b, 0xe5, 0xfb, 0x43, 0x1f, 0xcc, 0x5c \
+ } \
+ }
+
+class nsAHttpConnection : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_AHTTPCONNECTION_IID)
+
+ //-------------------------------------------------------------------------
+ // NOTE: these methods may only be called on the socket thread.
+ //-------------------------------------------------------------------------
+
+ //
+ // called by a transaction when the response headers have all been read.
+ // the connection can force the transaction to reset it's response headers,
+ // and prepare for a new set of response headers, by setting |*reset=TRUE|.
+ //
+ // @return failure code to close the transaction.
+ //
+ [[nodiscard]] virtual nsresult OnHeadersAvailable(nsAHttpTransaction*,
+ nsHttpRequestHead*,
+ nsHttpResponseHead*,
+ bool* reset) = 0;
+
+ //
+ // called by a transaction to resume either sending or receiving data
+ // after a transaction returned NS_BASE_STREAM_WOULD_BLOCK from its
+ // ReadSegments/WriteSegments methods.
+ //
+ [[nodiscard]] virtual nsresult ResumeSend() = 0;
+ [[nodiscard]] virtual nsresult ResumeRecv() = 0;
+
+ // called by a transaction to force a "send/recv from network" iteration
+ // even if not scheduled by socket associated with connection
+ [[nodiscard]] virtual nsresult ForceSend() = 0;
+ [[nodiscard]] virtual nsresult ForceRecv() = 0;
+
+ // After a connection has had ResumeSend() called by a transaction,
+ // and it is ready to write to the network it may need to know the
+ // transaction that has data to write. This is only an issue for
+ // multiplexed protocols like SPDY - h1
+ // implicitly has this information in a 1:1 relationship with the
+ // transaction(s) they manage.
+ virtual void TransactionHasDataToWrite(nsAHttpTransaction*) {
+ // by default do nothing - only multiplexed protocols need to overload
+ }
+
+ // This is the companion to *HasDataToWrite() for the case
+ // when a gecko caller has called ResumeRecv() after being paused
+ virtual void TransactionHasDataToRecv(nsAHttpTransaction*) {
+ // by default do nothing - only multiplexed protocols need to overload
+ }
+
+ // called by the connection manager to close a transaction being processed
+ // by this connection.
+ //
+ // @param transaction
+ // the transaction being closed.
+ // @param reason
+ // the reason for closing the transaction. NS_BASE_STREAM_CLOSED
+ // is equivalent to NS_OK.
+ //
+ virtual void CloseTransaction(nsAHttpTransaction* transaction,
+ nsresult reason) = 0;
+
+ // get a reference to the connection's connection info object.
+ virtual void GetConnectionInfo(nsHttpConnectionInfo**) = 0;
+
+ // get the transport level information for this connection. This may fail
+ // if it is in use.
+ [[nodiscard]] virtual nsresult TakeTransport(nsISocketTransport**,
+ nsIAsyncInputStream**,
+ nsIAsyncOutputStream**) = 0;
+
+ [[nodiscard]] virtual Http3WebTransportSession* GetWebTransportSession(
+ nsAHttpTransaction* aTransaction) = 0;
+
+ // called by a transaction to get the TLS socket control from the socket.
+ virtual void GetTLSSocketControl(nsITLSSocketControl**) = 0;
+
+ // called by a transaction to determine whether or not the connection is
+ // persistent... important in determining the end of a response.
+ virtual bool IsPersistent() = 0;
+
+ // called to determine or set if a connection has been reused.
+ virtual bool IsReused() = 0;
+ virtual void DontReuse() = 0;
+
+ // called by a transaction when the transaction reads more from the socket
+ // than it should have (eg. containing part of the next response).
+ [[nodiscard]] virtual nsresult PushBack(const char* data,
+ uint32_t length) = 0;
+
+ // Used to determine if the connection wants read events even though
+ // it has not written out a transaction. Used when a connection has issued
+ // a preamble such as a proxy ssl CONNECT sequence.
+ virtual bool IsProxyConnectInProgress() = 0;
+
+ // Used by a transaction to manage the state of previous response bodies on
+ // the same connection and work around buggy servers.
+ virtual bool LastTransactionExpectedNoContent() = 0;
+ virtual void SetLastTransactionExpectedNoContent(bool) = 0;
+
+ // Transfer the base http connection object along with a
+ // reference to it to the caller.
+ virtual already_AddRefed<HttpConnectionBase> TakeHttpConnection() = 0;
+
+ // Like TakeHttpConnection() but do not drop our own ref
+ virtual already_AddRefed<HttpConnectionBase> HttpConnection() = 0;
+
+ // Get the nsISocketTransport used by the connection without changing
+ // references or ownership.
+ virtual nsISocketTransport* Transport() = 0;
+
+ // The number of transaction bytes written out on this HTTP Connection, does
+ // not count CONNECT tunnel setup
+ virtual int64_t BytesWritten() = 0;
+
+ // Update the callbacks used to provide security info. May be called on
+ // any thread.
+ virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) = 0;
+
+ // nsHttp.h version
+ virtual HttpVersion Version() = 0;
+
+ // A notification of the current active tab id change.
+ virtual void CurrentBrowserIdChanged(uint64_t id) = 0;
+
+ // categories set by nsHttpTransaction to identify how this connection is
+ // being used.
+ virtual void SetTrafficCategory(HttpTrafficCategory) = 0;
+
+ virtual nsresult GetSelfAddr(NetAddr* addr) = 0;
+ virtual nsresult GetPeerAddr(NetAddr* addr) = 0;
+ virtual bool ResolvedByTRR() = 0;
+ virtual nsIRequest::TRRMode EffectiveTRRMode() = 0;
+ virtual nsITRRSkipReason::value TRRSkipReason() = 0;
+ virtual bool GetEchConfigUsed() = 0;
+ virtual PRIntervalTime LastWriteTime() = 0;
+ virtual void SetCloseReason(ConnectionCloseReason aReason) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpConnection, NS_AHTTPCONNECTION_IID)
+
+#define NS_DECL_NSAHTTPCONNECTION(fwdObject) \
+ [[nodiscard]] nsresult OnHeadersAvailable( \
+ nsAHttpTransaction*, nsHttpRequestHead*, nsHttpResponseHead*, \
+ bool* reset) override; \
+ void CloseTransaction(nsAHttpTransaction*, nsresult) override; \
+ [[nodiscard]] nsresult TakeTransport( \
+ nsISocketTransport**, nsIAsyncInputStream**, nsIAsyncOutputStream**) \
+ override; \
+ [[nodiscard]] Http3WebTransportSession* GetWebTransportSession( \
+ nsAHttpTransaction* aTransaction) override; \
+ bool IsPersistent() override; \
+ bool IsReused() override; \
+ void DontReuse() override; \
+ [[nodiscard]] nsresult PushBack(const char*, uint32_t) override; \
+ already_AddRefed<HttpConnectionBase> TakeHttpConnection() override; \
+ already_AddRefed<HttpConnectionBase> HttpConnection() override; \
+ void CurrentBrowserIdChanged(uint64_t id) override; \
+ /* \
+ Thes methods below have automatic definitions that just forward the \
+ function to a lower level connection object \
+ */ \
+ void GetConnectionInfo(nsHttpConnectionInfo** result) override { \
+ if (!(fwdObject)) { \
+ *result = nullptr; \
+ return; \
+ } \
+ return (fwdObject)->GetConnectionInfo(result); \
+ } \
+ void GetTLSSocketControl(nsITLSSocketControl** result) override { \
+ if (!(fwdObject)) { \
+ *result = nullptr; \
+ return; \
+ } \
+ return (fwdObject)->GetTLSSocketControl(result); \
+ } \
+ [[nodiscard]] nsresult ResumeSend() override { \
+ if (!(fwdObject)) return NS_ERROR_FAILURE; \
+ return (fwdObject)->ResumeSend(); \
+ } \
+ [[nodiscard]] nsresult ResumeRecv() override { \
+ if (!(fwdObject)) return NS_ERROR_FAILURE; \
+ return (fwdObject)->ResumeRecv(); \
+ } \
+ [[nodiscard]] nsresult ForceSend() override { \
+ if (!(fwdObject)) return NS_ERROR_FAILURE; \
+ return (fwdObject)->ForceSend(); \
+ } \
+ [[nodiscard]] nsresult ForceRecv() override { \
+ if (!(fwdObject)) return NS_ERROR_FAILURE; \
+ return (fwdObject)->ForceRecv(); \
+ } \
+ nsISocketTransport* Transport() override { \
+ if (!(fwdObject)) return nullptr; \
+ return (fwdObject)->Transport(); \
+ } \
+ HttpVersion Version() override { \
+ return (fwdObject) ? (fwdObject)->Version() \
+ : mozilla::net::HttpVersion::UNKNOWN; \
+ } \
+ bool IsProxyConnectInProgress() override { \
+ return (!(fwdObject)) ? false : (fwdObject)->IsProxyConnectInProgress(); \
+ } \
+ bool LastTransactionExpectedNoContent() override { \
+ return (!(fwdObject)) ? false \
+ : (fwdObject)->LastTransactionExpectedNoContent(); \
+ } \
+ void SetLastTransactionExpectedNoContent(bool val) override { \
+ if (fwdObject) (fwdObject)->SetLastTransactionExpectedNoContent(val); \
+ } \
+ int64_t BytesWritten() override { \
+ return (fwdObject) ? (fwdObject)->BytesWritten() : 0; \
+ } \
+ void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) override { \
+ if (fwdObject) (fwdObject)->SetSecurityCallbacks(aCallbacks); \
+ } \
+ void SetTrafficCategory(HttpTrafficCategory aCategory) override { \
+ if (fwdObject) (fwdObject)->SetTrafficCategory(aCategory); \
+ } \
+ nsresult GetSelfAddr(NetAddr* addr) override { \
+ if (!(fwdObject)) return NS_ERROR_FAILURE; \
+ return (fwdObject)->GetSelfAddr(addr); \
+ } \
+ nsresult GetPeerAddr(NetAddr* addr) override { \
+ if (!(fwdObject)) return NS_ERROR_FAILURE; \
+ return (fwdObject)->GetPeerAddr(addr); \
+ } \
+ bool ResolvedByTRR() override { \
+ return (!(fwdObject)) ? false : (fwdObject)->ResolvedByTRR(); \
+ } \
+ nsIRequest::TRRMode EffectiveTRRMode() override { \
+ return (!(fwdObject)) ? nsIRequest::TRR_DEFAULT_MODE \
+ : (fwdObject)->EffectiveTRRMode(); \
+ } \
+ nsITRRSkipReason::value TRRSkipReason() override { \
+ return (!(fwdObject)) ? nsITRRSkipReason::TRR_UNSET \
+ : (fwdObject)->TRRSkipReason(); \
+ } \
+ bool GetEchConfigUsed() override { \
+ return (!(fwdObject)) ? false : (fwdObject)->GetEchConfigUsed(); \
+ } \
+ void SetCloseReason(ConnectionCloseReason aReason) override { \
+ if (fwdObject) (fwdObject)->SetCloseReason(aReason); \
+ } \
+ PRIntervalTime LastWriteTime() override;
+
+// ThrottleResponse deliberately ommited since we want different implementation
+// for h1 and h2 connections.
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsAHttpConnection_h__
diff --git a/netwerk/protocol/http/nsAHttpTransaction.h b/netwerk/protocol/http/nsAHttpTransaction.h
new file mode 100644
index 0000000000..06912c3695
--- /dev/null
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -0,0 +1,307 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAHttpTransaction_h__
+#define nsAHttpTransaction_h__
+
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+#include "nsIRequest.h"
+#include "nsITRRSkipReason.h"
+
+#ifdef Status
+/* Xlib headers insist on this for some reason... Nuke it because
+ it'll override our member name */
+typedef Status __StatusTmp;
+# undef Status
+typedef __StatusTmp Status;
+#endif
+
+class nsIDNSHTTPSSVCRecord;
+class nsIInterfaceRequestor;
+class nsIRequestContext;
+class nsISVCBRecord;
+class nsITLSSocketControl;
+class nsITransport;
+
+namespace mozilla {
+namespace net {
+
+class nsAHttpConnection;
+class nsAHttpSegmentReader;
+class nsAHttpSegmentWriter;
+class nsHttpTransaction;
+class nsHttpRequestHead;
+class nsHttpConnectionInfo;
+class NullHttpTransaction;
+
+enum class WebSocketSupport { UNSURE, NO_SUPPORT, SUPPORTED };
+
+//----------------------------------------------------------------------------
+// Abstract base class for a HTTP transaction:
+//
+// A transaction is a "sink" for the response data. The connection pushes
+// data to the transaction by writing to it. The transaction supports
+// WriteSegments and may refuse to accept data if its buffers are full (its
+// write function returns NS_BASE_STREAM_WOULD_BLOCK in this case).
+//----------------------------------------------------------------------------
+
+// 2af6d634-13e3-494c-8903-c9dce5c22fc0
+#define NS_AHTTPTRANSACTION_IID \
+ { \
+ 0x2af6d634, 0x13e3, 0x494c, { \
+ 0x89, 0x03, 0xc9, 0xdc, 0xe5, 0xc2, 0x2f, 0xc0 \
+ } \
+ }
+
+class nsAHttpTransaction : public nsSupportsWeakReference {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_AHTTPTRANSACTION_IID)
+
+ // called by the connection when it takes ownership of the transaction.
+ virtual void SetConnection(nsAHttpConnection*) = 0;
+
+ // called by the connection after a successfull activation of this transaction
+ // in other words, tells the transaction it transitioned to the "active"
+ // state.
+ virtual void OnActivated() {}
+
+ // used to obtain the connection associated with this transaction
+ virtual nsAHttpConnection* Connection() = 0;
+
+ // called by the connection to get security callbacks to set on the
+ // socket transport.
+ virtual void GetSecurityCallbacks(nsIInterfaceRequestor**) = 0;
+
+ // called to report socket status (see nsITransportEventSink)
+ virtual void OnTransportStatus(nsITransport* transport, nsresult status,
+ int64_t progress) = 0;
+
+ // called to check the transaction status.
+ virtual bool IsDone() = 0;
+ virtual nsresult Status() = 0;
+ virtual uint32_t Caps() = 0;
+
+ // called to read request data from the transaction.
+ [[nodiscard]] virtual nsresult ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count,
+ uint32_t* countRead) = 0;
+
+ // called to write response data to the transaction.
+ [[nodiscard]] virtual nsresult WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) = 0;
+
+ // These versions of the functions allow the overloader to specify whether or
+ // not it is safe to call *Segments() in a loop while they return OK.
+ // The callee should turn again to false if it is not, otherwise leave
+ // untouched
+ [[nodiscard]] virtual nsresult ReadSegmentsAgain(nsAHttpSegmentReader* reader,
+ uint32_t count,
+ uint32_t* countRead,
+ bool* again) {
+ return ReadSegments(reader, count, countRead);
+ }
+ [[nodiscard]] virtual nsresult WriteSegmentsAgain(
+ nsAHttpSegmentWriter* writer, uint32_t count, uint32_t* countWritten,
+ bool* again) {
+ return WriteSegments(writer, count, countWritten);
+ }
+
+ // called to close the transaction
+ virtual void Close(nsresult reason) = 0;
+
+ // called to indicate a failure with proxy CONNECT
+ virtual void SetProxyConnectFailed() = 0;
+
+ // called to retrieve the request headers of the transaction
+ virtual nsHttpRequestHead* RequestHead() = 0;
+
+ // determine the number of real http/1.x transactions on this
+ // abstract object. Pipelines had multiple, SPDY has 0,
+ // normal http transactions have 1.
+ virtual uint32_t Http1xTransactionCount() = 0;
+
+ // called to remove the unused sub transactions from an object that can
+ // handle multiple transactions simultaneously (i.e. h2).
+ //
+ // Returns NS_ERROR_NOT_IMPLEMENTED if the object does not implement
+ // sub-transactions.
+ //
+ // Returns NS_ERROR_ALREADY_OPENED if the subtransactions have been
+ // at least partially written and cannot be moved.
+ //
+ [[nodiscard]] virtual nsresult TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> >& outTransactions) = 0;
+
+ // Occasionally the abstract interface has to give way to base implementations
+ // to respect differences between spdy, h2, etc..
+ // These Query* (and IsNullTransaction()) functions provide a way to do
+ // that without using xpcom or rtti. Any calling code that can't deal with
+ // a null response from one of them probably shouldn't be using
+ // nsAHttpTransaction
+
+ // equivalent to !!dynamic_cast<NullHttpTransaction *>(this)
+ // A null transaction is expected to return BASE_STREAM_CLOSED on all of
+ // its IO functions all the time.
+ virtual bool IsNullTransaction() { return false; }
+ virtual NullHttpTransaction* QueryNullTransaction() { return nullptr; }
+
+ // If we used rtti this would be the result of doing
+ // dynamic_cast<nsHttpTransaction *>(this).. i.e. it can be nullptr for
+ // non nsHttpTransaction implementations of nsAHttpTransaction
+ virtual nsHttpTransaction* QueryHttpTransaction() { return nullptr; }
+
+ // return the request context associated with the transaction
+ virtual nsIRequestContext* RequestContext() { return nullptr; }
+
+ // return the connection information associated with the transaction
+ virtual nsHttpConnectionInfo* ConnectionInfo() = 0;
+
+ // The base definition of these is done in nsHttpTransaction.cpp
+ virtual bool ResponseTimeoutEnabled() const;
+ virtual PRIntervalTime ResponseTimeout();
+
+ // conceptually the socket control is part of the connection, but sometimes
+ // in the case of TLS tunneled within TLS the transaction might present
+ // a more specific socket control that cannot be represented as a layer in
+ // the connection due to multiplexing. This interface represents such an
+ // overload. If it returns NS_FAILURE the connection should be considered
+ // authoritative.
+ [[nodiscard]] virtual nsresult GetTransactionTLSSocketControl(
+ nsITLSSocketControl**) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ virtual void DisableSpdy() {}
+ // When called, we disallow to connect through a Http/2 proxy.
+ virtual void DisableHttp2ForProxy() {}
+ virtual void DisableHttp3(bool aAllowRetryHTTPSRR) {}
+ virtual void MakeNonSticky() {}
+ virtual void MakeRestartable() {}
+ virtual void ReuseConnectionOnRestartOK(bool) {}
+ virtual void SetIsHttp2Websocket(bool) {}
+ virtual bool IsHttp2Websocket() { return false; }
+ virtual void SetTRRInfo(nsIRequest::TRRMode aMode,
+ TRRSkippedReason aSkipReason){};
+
+ // We call this function if we want to use alt-svc host again on the next
+ // restart. If this function is not called on the next restart the
+ // transaction will use the original route.
+ // For example in case we receive a GOAWAY frame from a server, we can
+ // restart and use the same alt-svc. If we get VersionFallback we do not
+ // want to use the alt-svc on the restart.
+ virtual void DoNotRemoveAltSvc() {}
+
+ // We call this function if we do want to reset IP family preference again on
+ // the next restart.
+ virtual void DoNotResetIPFamilyPreference() {}
+
+ // Returns true if early-data is possible and transaction will remember
+ // that it is in 0RTT mode (to know should it rewide transaction or not
+ // in the case of an error).
+ [[nodiscard]] virtual bool Do0RTT() { return false; }
+ // This function will be called when a tls handshake has been finished and
+ // we know whether early-data that was sent has been accepted or not, e.g.
+ // do we need to restart a transaction. This will be called only if Do0RTT
+ // returns true.
+ // If aRestart parameter is true we need to restart the transaction,
+ // otherwise the erly-data has been accepted and we can continue the
+ // transaction.
+ // If aAlpnChanged is true (and we were assuming http/2), we'll need to take
+ // the transactions out of the session, rewind them all, and start them back
+ // over as http/1 transactions
+ // The function will return success or failure of the transaction restart.
+ [[nodiscard]] virtual nsresult Finish0RTT(bool aRestart, bool aAlpnChanged) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ virtual uint64_t BrowserId() {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+
+ virtual void OnProxyConnectComplete(int32_t aResponseCode) {}
+
+ virtual nsresult FetchHTTPSRR() { return NS_ERROR_NOT_IMPLEMENTED; }
+ virtual nsresult OnHTTPSRRAvailable(nsIDNSHTTPSSVCRecord* aHTTPSSVCRecord,
+ nsISVCBRecord* aHighestPriorityRecord) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpTransaction, NS_AHTTPTRANSACTION_IID)
+
+#define NS_DECL_NSAHTTPTRANSACTION \
+ void SetConnection(nsAHttpConnection*) override; \
+ nsAHttpConnection* Connection() override; \
+ void GetSecurityCallbacks(nsIInterfaceRequestor**) override; \
+ void OnTransportStatus(nsITransport* transport, nsresult status, \
+ int64_t progress) override; \
+ bool IsDone() override; \
+ nsresult Status() override; \
+ uint32_t Caps() override; \
+ [[nodiscard]] virtual nsresult ReadSegments(nsAHttpSegmentReader*, uint32_t, \
+ uint32_t*) override; \
+ [[nodiscard]] virtual nsresult WriteSegments(nsAHttpSegmentWriter*, \
+ uint32_t, uint32_t*) override; \
+ virtual void Close(nsresult reason) override; \
+ nsHttpConnectionInfo* ConnectionInfo() override; \
+ void SetProxyConnectFailed() override; \
+ virtual nsHttpRequestHead* RequestHead() override; \
+ uint32_t Http1xTransactionCount() override; \
+ [[nodiscard]] nsresult TakeSubTransactions( \
+ nsTArray<RefPtr<nsAHttpTransaction> >& outTransactions) override;
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+class nsAHttpSegmentReader {
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ public:
+ // any returned failure code stops segment iteration
+ [[nodiscard]] virtual nsresult OnReadSegment(const char* segment,
+ uint32_t count,
+ uint32_t* countRead) = 0;
+
+ // Ask the segment reader to commit to accepting size bytes of
+ // data from subsequent OnReadSegment() calls or throw hard
+ // (i.e. not wouldblock) exceptions. Implementations
+ // can return NS_ERROR_FAILURE if they never make commitments of that size
+ // (the default), NS_OK if they make the commitment, or
+ // NS_BASE_STREAM_WOULD_BLOCK if they cannot make the
+ // commitment now but might in the future and forceCommitment is not true .
+ // (forceCommitment requires a hard failure or OK at this moment.)
+ //
+ // SpdySession uses this to make sure frames are atomic.
+ [[nodiscard]] virtual nsresult CommitToSegmentSize(uint32_t size,
+ bool forceCommitment) {
+ return NS_ERROR_FAILURE;
+ }
+};
+
+#define NS_DECL_NSAHTTPSEGMENTREADER \
+ [[nodiscard]] nsresult OnReadSegment(const char*, uint32_t, uint32_t*) \
+ override;
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+class nsAHttpSegmentWriter {
+ public:
+ // any returned failure code stops segment iteration
+ [[nodiscard]] virtual nsresult OnWriteSegment(char* segment, uint32_t count,
+ uint32_t* countWritten) = 0;
+};
+
+#define NS_DECL_NSAHTTPSEGMENTWRITER \
+ [[nodiscard]] nsresult OnWriteSegment(char*, uint32_t, uint32_t*) override;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsAHttpTransaction_h__
diff --git a/netwerk/protocol/http/nsCORSListenerProxy.cpp b/netwerk/protocol/http/nsCORSListenerProxy.cpp
new file mode 100644
index 0000000000..90ac6a4ddc
--- /dev/null
+++ b/netwerk/protocol/http/nsCORSListenerProxy.cpp
@@ -0,0 +1,1753 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsString.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/StaticPrefs_content.h"
+#include "mozilla/StoragePrincipalHelper.h"
+
+#include "nsCORSListenerProxy.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "HttpChannelChild.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsError.h"
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMimeTypes.h"
+#include "nsStringStream.h"
+#include "nsGkAtoms.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsStreamUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsIScriptError.h"
+#include "nsILoadGroup.h"
+#include "nsILoadContext.h"
+#include "nsIConsoleService.h"
+#include "nsINetworkInterceptController.h"
+#include "nsICorsPreflightCallback.h"
+#include "nsISupportsImpl.h"
+#include "nsHttpChannel.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ExpandedPrincipal.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/NullPrincipal.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsQueryObject.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+#define PREFLIGHT_CACHE_SIZE 100
+// 5 seconds is chosen to be compatible with Chromium.
+#define PREFLIGHT_DEFAULT_EXPIRY_SECONDS 5
+
+static inline nsAutoString GetStatusCodeAsString(nsIHttpChannel* aHttp) {
+ nsAutoString result;
+ uint32_t code;
+ if (NS_SUCCEEDED(aHttp->GetResponseStatus(&code))) {
+ result.AppendInt(code);
+ }
+ return result;
+}
+
+static void LogBlockedRequest(nsIRequest* aRequest, const char* aProperty,
+ const char16_t* aParam, uint32_t aBlockingReason,
+ nsIHttpChannel* aCreatingChannel,
+ bool aIsWarning = false) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+
+ if (!aIsWarning) {
+ NS_SetRequestBlockingReason(channel, aBlockingReason);
+ }
+
+ nsCOMPtr<nsIURI> aUri;
+ channel->GetURI(getter_AddRefs(aUri));
+ nsAutoCString spec;
+ if (aUri) {
+ spec = aUri->GetSpecOrDefault();
+ }
+
+ // Generate the error message
+ nsAutoString blockedMessage;
+ AutoTArray<nsString, 2> params;
+ CopyUTF8toUTF16(spec, *params.AppendElement());
+ if (aParam) {
+ params.AppendElement(aParam);
+ }
+ NS_ConvertUTF8toUTF16 specUTF16(spec);
+ rv = nsContentUtils::FormatLocalizedString(
+ nsContentUtils::eSECURITY_PROPERTIES, aProperty, params, blockedMessage);
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to log blocked cross-site request (no formalizedStr");
+ return;
+ }
+
+ nsAutoString msg(blockedMessage.get());
+ nsDependentCString category(aProperty);
+
+ if (XRE_IsParentProcess()) {
+ if (aCreatingChannel) {
+ rv = aCreatingChannel->LogBlockedCORSRequest(msg, category, aIsWarning);
+ if (NS_SUCCEEDED(rv)) {
+ return;
+ }
+ }
+ NS_WARNING(
+ "Failed to log blocked cross-site request to web console from "
+ "parent->child, falling back to browser console");
+ }
+
+ bool privateBrowsing = false;
+ if (aRequest) {
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = aRequest->GetLoadGroup(getter_AddRefs(loadGroup));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ privateBrowsing = nsContentUtils::IsInPrivateBrowsing(loadGroup);
+ }
+
+ bool fromChromeContext = false;
+ if (channel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ fromChromeContext = loadInfo->TriggeringPrincipal()->IsSystemPrincipal();
+ }
+
+ // we are passing aProperty as the category so we can link to the
+ // appropriate MDN docs depending on the specific error.
+ uint64_t innerWindowID = nsContentUtils::GetInnerWindowID(aRequest);
+ // The |innerWindowID| could be 0 if this request is created from script.
+ // We can always try top level content window id in this case,
+ // since the window id can lead to current top level window's web console.
+ if (!innerWindowID) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (httpChannel) {
+ Unused << httpChannel->GetTopLevelContentWindowId(&innerWindowID);
+ }
+ }
+ nsCORSListenerProxy::LogBlockedCORSRequest(innerWindowID, privateBrowsing,
+ fromChromeContext, msg, category,
+ aIsWarning);
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Preflight cache
+
+class nsPreflightCache {
+ public:
+ struct TokenTime {
+ nsCString token;
+ TimeStamp expirationTime;
+ };
+
+ struct CacheEntry : public LinkedListElement<CacheEntry> {
+ explicit CacheEntry(nsCString& aKey, bool aPrivateBrowsing)
+ : mKey(aKey), mPrivateBrowsing(aPrivateBrowsing) {
+ MOZ_COUNT_CTOR(nsPreflightCache::CacheEntry);
+ }
+
+ ~CacheEntry() { MOZ_COUNT_DTOR(nsPreflightCache::CacheEntry); }
+
+ void PurgeExpired(TimeStamp now);
+ bool CheckRequest(const nsCString& aMethod,
+ const nsTArray<nsCString>& aHeaders);
+
+ nsCString mKey;
+ bool mPrivateBrowsing{false};
+ nsTArray<TokenTime> mMethods;
+ nsTArray<TokenTime> mHeaders;
+ };
+
+ MOZ_COUNTED_DEFAULT_CTOR(nsPreflightCache)
+
+ ~nsPreflightCache() {
+ Clear();
+ MOZ_COUNT_DTOR(nsPreflightCache);
+ }
+
+ bool Initialize() { return true; }
+
+ CacheEntry* GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ bool aWithCredentials,
+ const OriginAttributes& aOriginAttributes, bool aCreate);
+ void RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ const OriginAttributes& aOriginAttributes);
+ void PurgePrivateBrowsingEntries();
+
+ void Clear();
+
+ private:
+ nsClassHashtable<nsCStringHashKey, CacheEntry> mTable;
+ LinkedList<CacheEntry> mList;
+};
+
+// Will be initialized in EnsurePreflightCache.
+static nsPreflightCache* sPreflightCache = nullptr;
+
+static bool EnsurePreflightCache() {
+ if (sPreflightCache) return true;
+
+ UniquePtr<nsPreflightCache> newCache(new nsPreflightCache());
+
+ if (newCache->Initialize()) {
+ sPreflightCache = newCache.release();
+ return true;
+ }
+
+ return false;
+}
+
+void nsPreflightCache::PurgePrivateBrowsingEntries() {
+ for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
+ auto* entry = iter.UserData();
+ if (entry->mPrivateBrowsing) {
+ // last private browsing window closed, remove preflight cache entries
+ entry->removeFrom(sPreflightCache->mList);
+ iter.Remove();
+ }
+ }
+}
+
+void nsPreflightCache::CacheEntry::PurgeExpired(TimeStamp now) {
+ for (uint32_t i = 0, len = mMethods.Length(); i < len; ++i) {
+ if (now >= mMethods[i].expirationTime) {
+ mMethods.UnorderedRemoveElementAt(i);
+ --i; // Examine the element again, if necessary.
+ --len;
+ }
+ }
+ for (uint32_t i = 0, len = mHeaders.Length(); i < len; ++i) {
+ if (now >= mHeaders[i].expirationTime) {
+ mHeaders.UnorderedRemoveElementAt(i);
+ --i; // Examine the element again, if necessary.
+ --len;
+ }
+ }
+}
+
+bool nsPreflightCache::CacheEntry::CheckRequest(
+ const nsCString& aMethod, const nsTArray<nsCString>& aHeaders) {
+ PurgeExpired(TimeStamp::NowLoRes());
+
+ if (!aMethod.EqualsLiteral("GET") && !aMethod.EqualsLiteral("POST")) {
+ struct CheckToken {
+ bool Equals(const TokenTime& e, const nsCString& method) const {
+ return e.token.Equals(method);
+ }
+ };
+
+ if (!mMethods.Contains(aMethod, CheckToken())) {
+ return false;
+ }
+ }
+
+ struct CheckHeaderToken {
+ bool Equals(const TokenTime& e, const nsCString& header) const {
+ return e.token.Equals(header, nsCaseInsensitiveCStringComparator);
+ }
+ } checker;
+ for (uint32_t i = 0; i < aHeaders.Length(); ++i) {
+ if (!mHeaders.Contains(aHeaders[i], checker)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsPreflightCache::CacheEntry* nsPreflightCache::GetEntry(
+ nsIURI* aURI, nsIPrincipal* aPrincipal, bool aWithCredentials,
+ const OriginAttributes& aOriginAttributes, bool aCreate) {
+ nsCString key;
+ if (NS_FAILED(aPrincipal->GetPrefLightCacheKey(aURI, aWithCredentials,
+ aOriginAttributes, key))) {
+ NS_WARNING("Invalid cache key!");
+ return nullptr;
+ }
+
+ CacheEntry* existingEntry = nullptr;
+
+ if (mTable.Get(key, &existingEntry)) {
+ // Entry already existed so just return it. Also update the LRU list.
+
+ // Move to the head of the list.
+ existingEntry->removeFrom(mList);
+ mList.insertFront(existingEntry);
+
+ return existingEntry;
+ }
+
+ if (!aCreate) {
+ return nullptr;
+ }
+
+ // This is a new entry, allocate and insert into the table now so that any
+ // failures don't cause items to be removed from a full cache.
+ auto newEntry =
+ MakeUnique<CacheEntry>(key, aOriginAttributes.mPrivateBrowsingId != 0);
+
+ NS_ASSERTION(mTable.Count() <= PREFLIGHT_CACHE_SIZE,
+ "Something is borked, too many entries in the cache!");
+
+ // Now enforce the max count.
+ if (mTable.Count() == PREFLIGHT_CACHE_SIZE) {
+ // Try to kick out all the expired entries.
+ TimeStamp now = TimeStamp::NowLoRes();
+ for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
+ auto* entry = iter.UserData();
+ entry->PurgeExpired(now);
+
+ if (entry->mHeaders.IsEmpty() && entry->mMethods.IsEmpty()) {
+ // Expired, remove from the list as well as the hash table.
+ entry->removeFrom(sPreflightCache->mList);
+ iter.Remove();
+ }
+ }
+
+ // If that didn't remove anything then kick out the least recently used
+ // entry.
+ if (mTable.Count() == PREFLIGHT_CACHE_SIZE) {
+ CacheEntry* lruEntry = static_cast<CacheEntry*>(mList.popLast());
+ MOZ_ASSERT(lruEntry);
+
+ // This will delete 'lruEntry'.
+ mTable.Remove(lruEntry->mKey);
+
+ NS_ASSERTION(mTable.Count() == PREFLIGHT_CACHE_SIZE - 1,
+ "Somehow tried to remove an entry that was never added!");
+ }
+ }
+
+ auto* newEntryWeakRef = mTable.InsertOrUpdate(key, std::move(newEntry)).get();
+ mList.insertFront(newEntryWeakRef);
+
+ return newEntryWeakRef;
+}
+
+void nsPreflightCache::RemoveEntries(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ const OriginAttributes& aOriginAttributes) {
+ CacheEntry* entry;
+ nsCString key;
+ if (NS_SUCCEEDED(aPrincipal->GetPrefLightCacheKey(aURI, true,
+ aOriginAttributes, key)) &&
+ mTable.Get(key, &entry)) {
+ entry->removeFrom(mList);
+ mTable.Remove(key);
+ }
+
+ if (NS_SUCCEEDED(aPrincipal->GetPrefLightCacheKey(aURI, false,
+ aOriginAttributes, key)) &&
+ mTable.Get(key, &entry)) {
+ entry->removeFrom(mList);
+ mTable.Remove(key);
+ }
+}
+
+void nsPreflightCache::Clear() {
+ mList.clear();
+ mTable.Clear();
+}
+
+//////////////////////////////////////////////////////////////////////////
+// nsCORSListenerProxy
+
+NS_IMPL_ISUPPORTS(nsCORSListenerProxy, nsIStreamListener, nsIRequestObserver,
+ nsIChannelEventSink, nsIInterfaceRequestor,
+ nsIThreadRetargetableStreamListener)
+
+/* static */
+void nsCORSListenerProxy::Shutdown() {
+ delete sPreflightCache;
+ sPreflightCache = nullptr;
+}
+
+/* static */
+void nsCORSListenerProxy::ClearCache() {
+ if (!sPreflightCache) {
+ return;
+ }
+ sPreflightCache->Clear();
+}
+
+// static
+void nsCORSListenerProxy::ClearPrivateBrowsingCache() {
+ if (!sPreflightCache) {
+ return;
+ }
+ sPreflightCache->PurgePrivateBrowsingEntries();
+}
+
+// Usually, when using an expanded principal, there's no particularly good
+// origin to do the request with. However if the expanded principal only wraps
+// one principal, we can use that one instead.
+//
+// This is needed so that DevTools can still do CORS-enabled requests (since
+// DevTools uses a triggering principal expanding the node principal to bypass
+// CSP checks, see Element::CreateDevToolsPrincipal(), bug 1604562, and bug
+// 1391994).
+static nsIPrincipal* GetOriginHeaderPrincipal(nsIPrincipal* aPrincipal) {
+ while (aPrincipal && aPrincipal->GetIsExpandedPrincipal()) {
+ auto* ep = BasePrincipal::Cast(aPrincipal)->As<ExpandedPrincipal>();
+ if (ep->AllowList().Length() != 1) {
+ break;
+ }
+ aPrincipal = ep->AllowList()[0];
+ }
+ return aPrincipal;
+}
+
+nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter,
+ nsIPrincipal* aRequestingPrincipal,
+ bool aWithCredentials)
+ : mOuterListener(aOuter),
+ mRequestingPrincipal(aRequestingPrincipal),
+ mOriginHeaderPrincipal(GetOriginHeaderPrincipal(aRequestingPrincipal)),
+ mWithCredentials(aWithCredentials),
+ mRequestApproved(false),
+ mHasBeenCrossSite(false),
+#ifdef DEBUG
+ mInited(false),
+#endif
+ mMutex("nsCORSListenerProxy") {
+}
+
+nsresult nsCORSListenerProxy::Init(nsIChannel* aChannel,
+ DataURIHandling aAllowDataURI) {
+ aChannel->GetNotificationCallbacks(
+ getter_AddRefs(mOuterNotificationCallbacks));
+ aChannel->SetNotificationCallbacks(this);
+
+ nsresult rv = UpdateChannel(aChannel, aAllowDataURI, UpdateType::Default);
+ if (NS_FAILED(rv)) {
+ {
+ MutexAutoLock lock(mMutex);
+ mOuterListener = nullptr;
+ }
+ mRequestingPrincipal = nullptr;
+ mOriginHeaderPrincipal = nullptr;
+ mOuterNotificationCallbacks = nullptr;
+ mHttpChannel = nullptr;
+ }
+#ifdef DEBUG
+ mInited = true;
+#endif
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnStartRequest(nsIRequest* aRequest) {
+ MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
+ nsresult rv = CheckRequestApproved(aRequest);
+ mRequestApproved = NS_SUCCEEDED(rv);
+ if (!mRequestApproved) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
+ if (uri) {
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(channel,
+ attrs);
+
+ if (sPreflightCache) {
+ // OK to use mRequestingPrincipal since preflights never get
+ // redirected.
+ sPreflightCache->RemoveEntries(uri, mRequestingPrincipal, attrs);
+ } else {
+ nsCOMPtr<nsIHttpChannelChild> httpChannelChild =
+ do_QueryInterface(channel);
+ if (httpChannelChild) {
+ rv = httpChannelChild->RemoveCorsPreflightCacheEntry(
+ uri, mRequestingPrincipal, attrs);
+ if (NS_FAILED(rv)) {
+ // Only warn here to ensure we fall through the request Cancel()
+ // and outer listener OnStartRequest() calls.
+ NS_WARNING("Failed to remove CORS preflight cache entry!");
+ }
+ }
+ }
+ }
+ }
+
+ aRequest->Cancel(NS_ERROR_DOM_BAD_URI);
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mOuterListener;
+ }
+ listener->OnStartRequest(aRequest);
+
+ // Reason for NS_ERROR_DOM_BAD_URI already logged in CheckRequestApproved()
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mOuterListener;
+ }
+ return listener->OnStartRequest(aRequest);
+}
+
+namespace {
+class CheckOriginHeader final : public nsIHttpHeaderVisitor {
+ public:
+ NS_DECL_ISUPPORTS
+
+ CheckOriginHeader() = default;
+
+ NS_IMETHOD
+ VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
+ if (aHeader.EqualsLiteral("Access-Control-Allow-Origin")) {
+ mHeaderCount++;
+ }
+
+ if (mHeaderCount > 1) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ return NS_OK;
+ }
+
+ private:
+ uint32_t mHeaderCount{0};
+
+ ~CheckOriginHeader() = default;
+};
+
+NS_IMPL_ISUPPORTS(CheckOriginHeader, nsIHttpHeaderVisitor)
+} // namespace
+
+nsresult nsCORSListenerProxy::CheckRequestApproved(nsIRequest* aRequest) {
+ // Check if this was actually a cross domain request
+ if (!mHasBeenCrossSite) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIHttpChannel> topChannel;
+ topChannel.swap(mHttpChannel);
+
+ if (StaticPrefs::content_cors_disable()) {
+ LogBlockedRequest(aRequest, "CORSDisabled", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSDISABLED, topChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // Check if the request failed
+ nsresult status;
+ nsresult rv = aRequest->GetStatus(&status);
+ if (NS_FAILED(rv)) {
+ LogBlockedRequest(aRequest, "CORSDidNotSucceed2", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSDIDNOTSUCCEED,
+ topChannel);
+ return rv;
+ }
+
+ if (NS_FAILED(status)) {
+ if (NS_BINDING_ABORTED != status) {
+ // Don't want to log mere cancellation as an error.
+ LogBlockedRequest(aRequest, "CORSDidNotSucceed2", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSDIDNOTSUCCEED,
+ topChannel);
+ }
+ return status;
+ }
+
+ // Test that things worked on a HTTP level
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
+ if (!http) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ nsCOMPtr<nsIURI> uri;
+ NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
+ if (uri && uri->SchemeIs("moz-extension")) {
+ // moz-extension:-URLs do not support CORS, but can universally be read
+ // if an extension lists the resource in web_accessible_resources.
+ // Access will be checked in UpdateChannel.
+ return NS_OK;
+ }
+ LogBlockedRequest(aRequest, "CORSRequestNotHttp", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSREQUESTNOTHTTP,
+ topChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = http->LoadInfo();
+ if (loadInfo->GetServiceWorkerTaintingSynthesized()) {
+ // For synthesized responses, we don't need to perform any checks.
+ // Note: This would be unsafe if we ever changed our behavior to allow
+ // service workers to intercept CORS preflights.
+ return NS_OK;
+ }
+
+ // Check the Access-Control-Allow-Origin header
+ RefPtr<CheckOriginHeader> visitor = new CheckOriginHeader();
+ nsAutoCString allowedOriginHeader;
+
+ // check for duplicate headers
+ rv = http->VisitOriginalResponseHeaders(visitor);
+ if (NS_FAILED(rv)) {
+ LogBlockedRequest(
+ aRequest, "CORSMultipleAllowOriginNotAllowed", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSMULTIPLEALLOWORIGINNOTALLOWED,
+ topChannel);
+ return rv;
+ }
+
+ rv = http->GetResponseHeader("Access-Control-Allow-Origin"_ns,
+ allowedOriginHeader);
+ if (NS_FAILED(rv)) {
+ auto statusCode = GetStatusCodeAsString(http);
+ LogBlockedRequest(aRequest, "CORSMissingAllowOrigin2", statusCode.get(),
+ nsILoadInfo::BLOCKING_REASON_CORSMISSINGALLOWORIGIN,
+ topChannel);
+ return rv;
+ }
+
+ // Bug 1210985 - Explicitly point out the error that the credential is
+ // not supported if the allowing origin is '*'. Note that this check
+ // has to be done before the condition
+ //
+ // >> if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*"))
+ //
+ // below since "if (A && B)" is included in "if (A || !B)".
+ //
+ if (mWithCredentials && allowedOriginHeader.EqualsLiteral("*")) {
+ LogBlockedRequest(aRequest, "CORSNotSupportingCredentials", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSNOTSUPPORTINGCREDENTIALS,
+ topChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*")) {
+ MOZ_ASSERT(!mOriginHeaderPrincipal->GetIsExpandedPrincipal());
+ nsAutoCString origin;
+ mOriginHeaderPrincipal->GetWebExposedOriginSerialization(origin);
+
+ if (!allowedOriginHeader.Equals(origin)) {
+ LogBlockedRequest(
+ aRequest, "CORSAllowOriginNotMatchingOrigin",
+ NS_ConvertUTF8toUTF16(allowedOriginHeader).get(),
+ nsILoadInfo::BLOCKING_REASON_CORSALLOWORIGINNOTMATCHINGORIGIN,
+ topChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+
+ // Check Access-Control-Allow-Credentials header
+ if (mWithCredentials) {
+ nsAutoCString allowCredentialsHeader;
+ rv = http->GetResponseHeader("Access-Control-Allow-Credentials"_ns,
+ allowCredentialsHeader);
+
+ if (!allowCredentialsHeader.EqualsLiteral("true")) {
+ LogBlockedRequest(
+ aRequest, "CORSMissingAllowCredentials", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSMISSINGALLOWCREDENTIALS, topChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = std::move(mOuterListener);
+ }
+ nsresult rv = listener->OnStopRequest(aRequest, aStatusCode);
+ mOuterNotificationCallbacks = nullptr;
+ mHttpChannel = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ // NB: This can be called on any thread! But we're guaranteed that it is
+ // called between OnStartRequest and OnStopRequest, so we don't need to worry
+ // about races.
+
+ MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
+ if (!mRequestApproved) {
+ // Reason for NS_ERROR_DOM_BAD_URI already logged in CheckRequestApproved()
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mOuterListener;
+ }
+ return listener->OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnDataFinished(nsresult aStatus) {
+ nsCOMPtr<nsIStreamListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mOuterListener;
+ }
+ if (!listener) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(listener);
+ if (retargetableListener) {
+ return retargetableListener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+
+void nsCORSListenerProxy::SetInterceptController(
+ nsINetworkInterceptController* aInterceptController) {
+ mInterceptController = aInterceptController;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ *aResult = static_cast<nsIChannelEventSink*>(this);
+ NS_ADDREF_THIS();
+
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) &&
+ mInterceptController) {
+ nsCOMPtr<nsINetworkInterceptController> copy(mInterceptController);
+ *aResult = copy.forget().take();
+
+ return NS_OK;
+ }
+
+ return mOuterNotificationCallbacks
+ ? mOuterNotificationCallbacks->GetInterface(aIID, aResult)
+ : NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* aCb) {
+ nsresult rv;
+ if (NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags) ||
+ NS_IsHSTSUpgradeRedirect(aOldChannel, aNewChannel, aFlags)) {
+ // Internal redirects still need to be updated in order to maintain
+ // the correct headers. We use DataURIHandling::Allow, since unallowed
+ // data URIs should have been blocked before we got to the internal
+ // redirect.
+ rv = UpdateChannel(aNewChannel, DataURIHandling::Allow,
+ UpdateType::InternalOrHSTSRedirect);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "nsCORSListenerProxy::AsyncOnChannelRedirect: "
+ "internal redirect UpdateChannel() returned failure");
+ aOldChannel->Cancel(rv);
+ return rv;
+ }
+ } else {
+ mIsRedirect = true;
+ // A real, external redirect. Perform CORS checking on new URL.
+ rv = CheckRequestApproved(aOldChannel);
+ if (NS_FAILED(rv)) {
+ nsCOMPtr<nsIURI> oldURI;
+ NS_GetFinalChannelURI(aOldChannel, getter_AddRefs(oldURI));
+ if (oldURI) {
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(aOldChannel,
+ attrs);
+ if (sPreflightCache) {
+ // OK to use mRequestingPrincipal since preflights never get
+ // redirected.
+ sPreflightCache->RemoveEntries(oldURI, mRequestingPrincipal, attrs);
+ } else {
+ nsCOMPtr<nsIHttpChannelChild> httpChannelChild =
+ do_QueryInterface(aOldChannel);
+ if (httpChannelChild) {
+ rv = httpChannelChild->RemoveCorsPreflightCacheEntry(
+ oldURI, mRequestingPrincipal, attrs);
+ if (NS_FAILED(rv)) {
+ // Only warn here to ensure we call the channel Cancel() below
+ NS_WARNING("Failed to remove CORS preflight cache entry!");
+ }
+ }
+ }
+ }
+ aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
+ // Reason for NS_ERROR_DOM_BAD_URI already logged in
+ // CheckRequestApproved()
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ if (mHasBeenCrossSite) {
+ // Once we've been cross-site, cross-origin redirects reset our source
+ // origin. Note that we need to call GetChannelURIPrincipal() because
+ // we are looking for the principal that is actually being loaded and not
+ // the principal that initiated the load.
+ nsCOMPtr<nsIPrincipal> oldChannelPrincipal;
+ nsContentUtils::GetSecurityManager()->GetChannelURIPrincipal(
+ aOldChannel, getter_AddRefs(oldChannelPrincipal));
+ nsCOMPtr<nsIPrincipal> newChannelPrincipal;
+ nsContentUtils::GetSecurityManager()->GetChannelURIPrincipal(
+ aNewChannel, getter_AddRefs(newChannelPrincipal));
+ if (!oldChannelPrincipal || !newChannelPrincipal) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (NS_FAILED(rv)) {
+ aOldChannel->Cancel(rv);
+ return rv;
+ }
+
+ if (!oldChannelPrincipal->Equals(newChannelPrincipal)) {
+ // Spec says to set our source origin to a unique origin.
+ mOriginHeaderPrincipal =
+ NullPrincipal::CreateWithInheritedAttributes(oldChannelPrincipal);
+ }
+ }
+
+ bool rewriteToGET = false;
+ nsCOMPtr<nsIHttpChannel> oldHttpChannel = do_QueryInterface(aOldChannel);
+ if (oldHttpChannel) {
+ nsAutoCString method;
+ Unused << oldHttpChannel->GetRequestMethod(method);
+ Unused << oldHttpChannel->ShouldStripRequestBodyHeader(method,
+ &rewriteToGET);
+ }
+
+ rv = UpdateChannel(aNewChannel, DataURIHandling::Disallow,
+ rewriteToGET ? UpdateType::StripRequestBodyHeader
+ : UpdateType::Default);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "nsCORSListenerProxy::AsyncOnChannelRedirect: "
+ "UpdateChannel() returned failure");
+ aOldChannel->Cancel(rv);
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIChannelEventSink> outer =
+ do_GetInterface(mOuterNotificationCallbacks);
+ if (outer) {
+ return outer->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCb);
+ }
+
+ aCb->OnRedirectVerifyCallback(NS_OK);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::CheckListenerChain() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener;
+ {
+ MutexAutoLock lock(mMutex);
+ retargetableListener = do_QueryInterface(mOuterListener);
+ }
+ if (!retargetableListener) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return retargetableListener->CheckListenerChain();
+}
+
+// Please note that the CSP directive 'upgrade-insecure-requests' and the
+// HTTPS-Only Mode are relying on the promise that channels get updated from
+// http: to https: before the channel fetches any data from the netwerk. Such
+// channels should not be blocked by CORS and marked as cross origin requests.
+// E.g.: toplevel page: https://www.example.com loads
+// xhr: http://www.example.com/foo which gets updated to
+// https://www.example.com/foo
+// In such a case we should bail out of CORS and rely on the promise that
+// nsHttpChannel::Connect() upgrades the request from http to https.
+bool CheckInsecureUpgradePreventsCORS(nsIPrincipal* aRequestingPrincipal,
+ nsIChannel* aChannel) {
+ nsCOMPtr<nsIURI> channelURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // upgrade insecure requests is only applicable to http requests
+ if (!channelURI->SchemeIs("http")) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> originalURI;
+ rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoCString principalHost, channelHost, origChannelHost;
+
+ // if we can not query a host from the uri, there is nothing to do
+ if (NS_FAILED(aRequestingPrincipal->GetAsciiHost(principalHost)) ||
+ NS_FAILED(channelURI->GetAsciiHost(channelHost)) ||
+ NS_FAILED(originalURI->GetAsciiHost(origChannelHost))) {
+ return false;
+ }
+
+ // if the hosts do not match, there is nothing to do
+ if (!principalHost.EqualsIgnoreCase(channelHost.get())) {
+ return false;
+ }
+
+ // also check that uri matches the one of the originalURI
+ if (!channelHost.EqualsIgnoreCase(origChannelHost.get())) {
+ return false;
+ }
+
+ return true;
+}
+
+nsresult nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel,
+ DataURIHandling aAllowDataURI,
+ UpdateType aUpdateType) {
+ nsCOMPtr<nsIURI> uri, originalURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ // Introduced for DevTools in order to allow overriding some requests
+ // with the content of data: URIs.
+ if (loadInfo->GetAllowInsecureRedirectToDataURI() && uri->SchemeIs("data")) {
+ return NS_OK;
+ }
+
+ // exempt data URIs from the same origin check.
+ if (aAllowDataURI == DataURIHandling::Allow && originalURI == uri) {
+ if (uri->SchemeIs("data")) {
+ return NS_OK;
+ }
+ if (loadInfo->GetAboutBlankInherits() && NS_IsAboutBlank(uri)) {
+ return NS_OK;
+ }
+ }
+
+ // Set CORS attributes on channel so that intercepted requests get correct
+ // values. We have to do this here because the CheckMayLoad checks may lead
+ // to early return. We can't be sure this is an http channel though, so we
+ // can't return early on failure.
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aChannel);
+ if (internal) {
+ rv = internal->SetRequestMode(dom::RequestMode::Cors);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = internal->SetCorsIncludeCredentials(mWithCredentials);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // TODO: Bug 1353683
+ // consider calling SetBlockedRequest in nsCORSListenerProxy::UpdateChannel
+ //
+ // Check that the uri is ok to load
+ uint32_t flags = loadInfo->CheckLoadURIFlags();
+ rv = nsContentUtils::GetSecurityManager()->CheckLoadURIWithPrincipal(
+ mRequestingPrincipal, uri, flags, loadInfo->GetInnerWindowID());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (originalURI != uri) {
+ rv = nsContentUtils::GetSecurityManager()->CheckLoadURIWithPrincipal(
+ mRequestingPrincipal, originalURI, flags, loadInfo->GetInnerWindowID());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (uri->SchemeIs("moz-extension")) {
+ // moz-extension:-URLs do not support CORS, but can universally be read
+ // if an extension lists the resource in web_accessible_resources.
+ // This is enforced via the CheckLoadURIWithPrincipal call above:
+ // moz-extension resources have the URI_DANGEROUS_TO_LOAD flag, unless
+ // listed in web_accessible_resources.
+ return NS_OK;
+ }
+
+ if (!mHasBeenCrossSite &&
+ NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(uri, false)) &&
+ (originalURI == uri ||
+ NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(originalURI, false)))) {
+ return NS_OK;
+ }
+
+ // If the CSP directive 'upgrade-insecure-requests' is used or the HTTPS-Only
+ // Mode is enabled then we should not incorrectly require CORS if the only
+ // difference of a subresource request and the main page is the scheme. e.g.
+ // toplevel page: https://www.example.com loads
+ // xhr: http://www.example.com/somefoo,
+ // then the xhr request will be upgraded to https before it fetches any data
+ // from the netwerk, hence we shouldn't require CORS in that specific case.
+ if (CheckInsecureUpgradePreventsCORS(mRequestingPrincipal, aChannel)) {
+ // Check if https-only mode upgrades this later anyway
+ nsCOMPtr<nsILoadInfo> loadinfo = aChannel->LoadInfo();
+ if (nsHTTPSOnlyUtils::IsSafeToAcceptCORSOrMixedContent(loadinfo)) {
+ return NS_OK;
+ }
+ // Check if 'upgrade-insecure-requests' is used
+ if (loadInfo->GetUpgradeInsecureRequests() ||
+ loadInfo->GetBrowserUpgradeInsecureRequests()) {
+ return NS_OK;
+ }
+ }
+
+ // Check if we need to do a preflight, and if so set one up. This must be
+ // called once we know that the request is going, or has gone, cross-origin.
+ rv = CheckPreflightNeeded(aChannel, aUpdateType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // It's a cross site load
+ mHasBeenCrossSite = true;
+
+ if (mIsRedirect || StaticPrefs::network_cors_preflight_block_userpass_uri()) {
+ // https://fetch.spec.whatwg.org/#http-redirect-fetch
+ // Step 9. If request’s mode is "cors", locationURL includes credentials,
+ // and request’s origin is not same origin with locationURL’s origin,
+ // then return a network error.
+
+ nsAutoCString userpass;
+ uri->GetUserPass(userpass);
+ NS_ENSURE_TRUE(userpass.IsEmpty(), NS_ERROR_DOM_BAD_URI);
+ }
+
+ // If we have an expanded principal here, we'll reject the CORS request,
+ // because we can't send a useful Origin header which is required for CORS.
+ if (nsContentUtils::IsExpandedPrincipal(mOriginHeaderPrincipal)) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ LogBlockedRequest(aChannel, "CORSOriginHeaderNotAdded", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSORIGINHEADERNOTADDED,
+ httpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // Add the Origin header
+ nsAutoCString origin;
+ rv = mOriginHeaderPrincipal->GetWebExposedOriginSerialization(origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
+ NS_ENSURE_TRUE(http, NS_ERROR_FAILURE);
+
+ // hide the Origin header when requesting from .onion and requesting CORS
+ if (StaticPrefs::network_http_referer_hideOnionSource()) {
+ if (mOriginHeaderPrincipal->GetIsOnion()) {
+ origin.AssignLiteral("null");
+ }
+ }
+
+ rv = http->SetRequestHeader(net::nsHttp::Origin.val(), origin, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make cookie-less if needed. We don't need to do anything here if the
+ // channel was opened with AsyncOpen, since then AsyncOpen will take
+ // care of the cookie policy for us.
+ if (!mWithCredentials) {
+ nsLoadFlags flags;
+ rv = http->GetLoadFlags(&flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ flags |= nsIRequest::LOAD_ANONYMOUS;
+ if (StaticPrefs::network_cors_preflight_allow_client_cert()) {
+ flags |= nsIRequest::LOAD_ANONYMOUS_ALLOW_CLIENT_CERT;
+ }
+ rv = http->SetLoadFlags(flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mHttpChannel = http;
+
+ return NS_OK;
+}
+
+nsresult nsCORSListenerProxy::CheckPreflightNeeded(nsIChannel* aChannel,
+ UpdateType aUpdateType) {
+ // If this caller isn't using AsyncOpen, or if this *is* a preflight channel,
+ // then we shouldn't initiate preflight for this channel.
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ if (loadInfo->GetSecurityMode() !=
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT ||
+ loadInfo->GetIsPreflight()) {
+ return NS_OK;
+ }
+
+ bool doPreflight = loadInfo->GetForcePreflight();
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
+ if (!http) {
+ // Note: A preflight is not needed for moz-extension:-requests either, but
+ // there is already a check for that in the caller of CheckPreflightNeeded,
+ // in UpdateChannel.
+ LogBlockedRequest(aChannel, "CORSRequestNotHttp", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSREQUESTNOTHTTP,
+ mHttpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsAutoCString method;
+ Unused << http->GetRequestMethod(method);
+ if (!method.LowerCaseEqualsLiteral("get") &&
+ !method.LowerCaseEqualsLiteral("post") &&
+ !method.LowerCaseEqualsLiteral("head")) {
+ doPreflight = true;
+ }
+
+ // Avoid copying the array here
+ const nsTArray<nsCString>& loadInfoHeaders = loadInfo->CorsUnsafeHeaders();
+ if (!loadInfoHeaders.IsEmpty()) {
+ doPreflight = true;
+ }
+
+ // Add Content-Type header if needed
+ nsTArray<nsCString> headers;
+ nsAutoCString contentTypeHeader;
+ nsresult rv = http->GetRequestHeader("Content-Type"_ns, contentTypeHeader);
+ // GetRequestHeader return an error if the header is not set. Don't add
+ // "content-type" to the list if that's the case.
+ if (NS_SUCCEEDED(rv) &&
+ !nsContentUtils::IsAllowedNonCorsContentType(contentTypeHeader) &&
+ !loadInfoHeaders.Contains("content-type"_ns,
+ nsCaseInsensitiveCStringArrayComparator())) {
+ headers.AppendElements(loadInfoHeaders);
+ headers.AppendElement("content-type"_ns);
+ doPreflight = true;
+ }
+
+ if (!doPreflight) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(http);
+ if (!internal) {
+ auto statusCode = GetStatusCodeAsString(http);
+ LogBlockedRequest(aChannel, "CORSDidNotSucceed2", statusCode.get(),
+ nsILoadInfo::BLOCKING_REASON_CORSDIDNOTSUCCEED,
+ mHttpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ internal->SetCorsPreflightParameters(
+ headers.IsEmpty() ? loadInfoHeaders : headers,
+ aUpdateType == UpdateType::StripRequestBodyHeader);
+
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Preflight proxy
+
+// Class used as streamlistener and notification callback when
+// doing the initial OPTIONS request for a CORS check
+class nsCORSPreflightListener final : public nsIStreamListener,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink {
+ public:
+ nsCORSPreflightListener(nsIPrincipal* aReferrerPrincipal,
+ nsICorsPreflightCallback* aCallback,
+ nsILoadContext* aLoadContext, bool aWithCredentials,
+ const nsCString& aPreflightMethod,
+ const nsTArray<nsCString>& aPreflightHeaders)
+ : mPreflightMethod(aPreflightMethod),
+ mPreflightHeaders(aPreflightHeaders.Clone()),
+ mReferrerPrincipal(aReferrerPrincipal),
+ mCallback(aCallback),
+ mLoadContext(aLoadContext),
+ mWithCredentials(aWithCredentials) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+
+ nsresult CheckPreflightRequestApproved(nsIRequest* aRequest);
+
+ private:
+ ~nsCORSPreflightListener() = default;
+
+ void AddResultToCache(nsIRequest* aRequest);
+
+ nsCString mPreflightMethod;
+ nsTArray<nsCString> mPreflightHeaders;
+ nsCOMPtr<nsIPrincipal> mReferrerPrincipal;
+ nsCOMPtr<nsICorsPreflightCallback> mCallback;
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ bool mWithCredentials;
+};
+
+NS_IMPL_ISUPPORTS(nsCORSPreflightListener, nsIStreamListener,
+ nsIRequestObserver, nsIInterfaceRequestor,
+ nsIChannelEventSink)
+
+void nsCORSPreflightListener::AddResultToCache(nsIRequest* aRequest) {
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
+ NS_ASSERTION(http, "Request was not http");
+
+ // The "Access-Control-Max-Age" header should return an age in seconds.
+ nsAutoCString headerVal;
+ uint32_t age = 0;
+ Unused << http->GetResponseHeader("Access-Control-Max-Age"_ns, headerVal);
+ if (headerVal.IsEmpty()) {
+ age = PREFLIGHT_DEFAULT_EXPIRY_SECONDS;
+ } else {
+ // Sanitize the string. We only allow 'delta-seconds' as specified by
+ // http://dev.w3.org/2006/waf/access-control (digits 0-9 with no leading or
+ // trailing non-whitespace characters).
+ nsACString::const_char_iterator iter, end;
+ headerVal.BeginReading(iter);
+ headerVal.EndReading(end);
+ while (iter != end) {
+ if (*iter < '0' || *iter > '9') {
+ return;
+ }
+ age = age * 10 + (*iter - '0');
+ // Cap at 24 hours. This also avoids overflow
+ age = std::min(age, 86400U);
+ ++iter;
+ }
+ }
+
+ if (!age || !EnsurePreflightCache()) {
+ return;
+ }
+
+ // String seems fine, go ahead and cache.
+ // Note that we have already checked that these headers follow the correct
+ // syntax.
+
+ nsCOMPtr<nsIURI> uri;
+ NS_GetFinalChannelURI(http, getter_AddRefs(uri));
+
+ TimeStamp expirationTime =
+ TimeStamp::NowLoRes() + TimeDuration::FromSeconds(age);
+
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(http, attrs);
+
+ nsPreflightCache::CacheEntry* entry = sPreflightCache->GetEntry(
+ uri, mReferrerPrincipal, mWithCredentials, attrs, true);
+ if (!entry) {
+ return;
+ }
+
+ // The "Access-Control-Allow-Methods" header contains a comma separated
+ // list of method names.
+ Unused << http->GetResponseHeader("Access-Control-Allow-Methods"_ns,
+ headerVal);
+
+ for (const nsACString& method :
+ nsCCharSeparatedTokenizer(headerVal, ',').ToRange()) {
+ if (method.IsEmpty()) {
+ continue;
+ }
+ uint32_t i;
+ for (i = 0; i < entry->mMethods.Length(); ++i) {
+ if (entry->mMethods[i].token.Equals(method)) {
+ entry->mMethods[i].expirationTime = expirationTime;
+ break;
+ }
+ }
+ if (i == entry->mMethods.Length()) {
+ nsPreflightCache::TokenTime* newMethod = entry->mMethods.AppendElement();
+ if (!newMethod) {
+ return;
+ }
+
+ newMethod->token = method;
+ newMethod->expirationTime = expirationTime;
+ }
+ }
+
+ // The "Access-Control-Allow-Headers" header contains a comma separated
+ // list of method names.
+ Unused << http->GetResponseHeader("Access-Control-Allow-Headers"_ns,
+ headerVal);
+
+ for (const nsACString& header :
+ nsCCharSeparatedTokenizer(headerVal, ',').ToRange()) {
+ if (header.IsEmpty()) {
+ continue;
+ }
+ uint32_t i;
+ for (i = 0; i < entry->mHeaders.Length(); ++i) {
+ if (entry->mHeaders[i].token.Equals(header)) {
+ entry->mHeaders[i].expirationTime = expirationTime;
+ break;
+ }
+ }
+ if (i == entry->mHeaders.Length()) {
+ nsPreflightCache::TokenTime* newHeader = entry->mHeaders.AppendElement();
+ if (!newHeader) {
+ return;
+ }
+
+ newHeader->token = header;
+ newHeader->expirationTime = expirationTime;
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsCORSPreflightListener::OnStartRequest(nsIRequest* aRequest) {
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ nsCOMPtr<nsILoadInfo> loadInfo = channel ? channel->LoadInfo() : nullptr;
+ MOZ_ASSERT(!loadInfo || !loadInfo->GetServiceWorkerTaintingSynthesized());
+ }
+#endif
+
+ nsresult rv = CheckPreflightRequestApproved(aRequest);
+
+ if (NS_SUCCEEDED(rv)) {
+ // Everything worked, try to cache and then fire off the actual request.
+ AddResultToCache(aRequest);
+
+ mCallback->OnPreflightSucceeded();
+ } else {
+ mCallback->OnPreflightFailed(rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCORSPreflightListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ mCallback = nullptr;
+ return NS_OK;
+}
+
+/** nsIStreamListener methods **/
+
+NS_IMETHODIMP
+nsCORSPreflightListener::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* inStr,
+ uint64_t sourceOffset,
+ uint32_t count) {
+ uint32_t totalRead;
+ return inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &totalRead);
+}
+
+NS_IMETHODIMP
+nsCORSPreflightListener::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ // Only internal redirects allowed for now.
+ if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags) &&
+ !NS_IsHSTSUpgradeRedirect(aOldChannel, aNewChannel, aFlags)) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aOldChannel);
+ LogBlockedRequest(
+ aOldChannel, "CORSExternalRedirectNotAllowed", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSEXTERNALREDIRECTNOTALLOWED,
+ httpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+nsresult nsCORSPreflightListener::CheckPreflightRequestApproved(
+ nsIRequest* aRequest) {
+ nsresult status;
+ nsresult rv = aRequest->GetStatus(&status);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(status, status);
+
+ // Test that things worked on a HTTP level
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
+ NS_ENSURE_STATE(internal);
+ nsCOMPtr<nsIHttpChannel> parentHttpChannel = do_QueryInterface(mCallback);
+
+ bool succeedded;
+ rv = http->GetRequestSucceeded(&succeedded);
+ if (NS_FAILED(rv) || !succeedded) {
+ auto statusCode = GetStatusCodeAsString(http);
+ LogBlockedRequest(aRequest, "CORSPreflightDidNotSucceed3", statusCode.get(),
+ nsILoadInfo::BLOCKING_REASON_CORSPREFLIGHTDIDNOTSUCCEED,
+ parentHttpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsAutoCString headerVal;
+ // The "Access-Control-Allow-Methods" header contains a comma separated
+ // list of method names.
+ Unused << http->GetResponseHeader("Access-Control-Allow-Methods"_ns,
+ headerVal);
+ bool foundMethod = mPreflightMethod.EqualsLiteral("GET") ||
+ mPreflightMethod.EqualsLiteral("HEAD") ||
+ mPreflightMethod.EqualsLiteral("POST");
+ for (const nsACString& method :
+ nsCCharSeparatedTokenizer(headerVal, ',').ToRange()) {
+ if (method.IsEmpty()) {
+ continue;
+ }
+ if (!NS_IsValidHTTPToken(method)) {
+ LogBlockedRequest(aRequest, "CORSInvalidAllowMethod",
+ NS_ConvertUTF8toUTF16(method).get(),
+ nsILoadInfo::BLOCKING_REASON_CORSINVALIDALLOWMETHOD,
+ parentHttpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ if (method.EqualsLiteral("*") && !mWithCredentials) {
+ foundMethod = true;
+ } else {
+ foundMethod |= mPreflightMethod.Equals(method);
+ }
+ }
+ if (!foundMethod) {
+ LogBlockedRequest(aRequest, "CORSMethodNotFound", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSMETHODNOTFOUND,
+ parentHttpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // The "Access-Control-Allow-Headers" header contains a comma separated
+ // list of header names.
+ Unused << http->GetResponseHeader("Access-Control-Allow-Headers"_ns,
+ headerVal);
+ nsTArray<nsCString> headers;
+ bool wildcard = false;
+ bool hasAuthorizationHeader = false;
+ for (const nsACString& header :
+ nsCCharSeparatedTokenizer(headerVal, ',').ToRange()) {
+ if (header.IsEmpty()) {
+ continue;
+ }
+ if (!NS_IsValidHTTPToken(header)) {
+ LogBlockedRequest(aRequest, "CORSInvalidAllowHeader",
+ NS_ConvertUTF8toUTF16(header).get(),
+ nsILoadInfo::BLOCKING_REASON_CORSINVALIDALLOWHEADER,
+ parentHttpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ if (header.EqualsLiteral("*") && !mWithCredentials) {
+ wildcard = true;
+ } else {
+ headers.AppendElement(header);
+ }
+
+ if (header.LowerCaseEqualsASCII("authorization")) {
+ hasAuthorizationHeader = true;
+ }
+ }
+
+ bool authorizationInPreflightHeaders = false;
+ bool authorizationCoveredByWildcard = false;
+ for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
+ // Cache the result of the authorization header.
+ bool isAuthorization =
+ mPreflightHeaders[i].LowerCaseEqualsASCII("authorization");
+ if (wildcard) {
+ if (!isAuthorization) {
+ continue;
+ } else {
+ authorizationInPreflightHeaders = true;
+ if (StaticPrefs::
+ network_cors_preflight_authorization_covered_by_wildcard() &&
+ !hasAuthorizationHeader) {
+ // When `Access-Control-Allow-Headers` is `*` and there is no
+ // `Authorization` header listed, we send a deprecation warning to the
+ // console.
+ LogBlockedRequest(aRequest, "CORSAllowHeaderFromPreflightDeprecation",
+ nullptr, 0, parentHttpChannel, true);
+ glean::network::cors_authorization_header
+ .Get("covered_by_wildcard"_ns)
+ .Add(1);
+ authorizationCoveredByWildcard = true;
+ continue;
+ }
+ }
+ }
+
+ const auto& comparator = nsCaseInsensitiveCStringArrayComparator();
+ if (!headers.Contains(mPreflightHeaders[i], comparator)) {
+ LogBlockedRequest(
+ aRequest, "CORSMissingAllowHeaderFromPreflight2",
+ NS_ConvertUTF8toUTF16(mPreflightHeaders[i]).get(),
+ nsILoadInfo::BLOCKING_REASON_CORSMISSINGALLOWHEADERFROMPREFLIGHT,
+ parentHttpChannel);
+ if (isAuthorization) {
+ glean::network::cors_authorization_header.Get("disallowed"_ns).Add(1);
+ }
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+
+ if (authorizationInPreflightHeaders && !authorizationCoveredByWildcard) {
+ glean::network::cors_authorization_header.Get("allowed"_ns).Add(1);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCORSPreflightListener::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(aResult);
+ return NS_OK;
+ }
+
+ return QueryInterface(aIID, aResult);
+}
+
+void nsCORSListenerProxy::RemoveFromCorsPreflightCache(
+ nsIURI* aURI, nsIPrincipal* aRequestingPrincipal,
+ const OriginAttributes& aOriginAttributes) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (sPreflightCache) {
+ sPreflightCache->RemoveEntries(aURI, aRequestingPrincipal,
+ aOriginAttributes);
+ }
+}
+
+// static
+nsresult nsCORSListenerProxy::StartCORSPreflight(
+ nsIChannel* aRequestChannel, nsICorsPreflightCallback* aCallback,
+ nsTArray<nsCString>& aUnsafeHeaders, nsIChannel** aPreflightChannel) {
+ *aPreflightChannel = nullptr;
+
+ if (StaticPrefs::content_cors_disable()) {
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequestChannel);
+ LogBlockedRequest(aRequestChannel, "CORSDisabled", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSDISABLED, http);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsAutoCString method;
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequestChannel));
+ NS_ENSURE_TRUE(httpChannel, NS_ERROR_UNEXPECTED);
+ Unused << httpChannel->GetRequestMethod(method);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadInfo> originalLoadInfo = aRequestChannel->LoadInfo();
+ MOZ_ASSERT(originalLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT,
+ "how did we end up here?");
+
+ nsCOMPtr<nsIPrincipal> principal = originalLoadInfo->GetLoadingPrincipal();
+ MOZ_ASSERT(principal && originalLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT,
+ "Should not do CORS loads for top-level loads, so a "
+ "loadingPrincipal should always exist.");
+ bool withCredentials =
+ originalLoadInfo->GetCookiePolicy() == nsILoadInfo::SEC_COOKIES_INCLUDE;
+
+ nsPreflightCache::CacheEntry* entry = nullptr;
+
+ nsLoadFlags loadFlags;
+ rv = aRequestChannel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Disable preflight cache if devtools says so or on other force reloads
+ bool disableCache = (loadFlags & nsIRequest::LOAD_BYPASS_CACHE);
+
+ if (sPreflightCache && !disableCache) {
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(aRequestChannel,
+ attrs);
+ entry = sPreflightCache->GetEntry(uri, principal, withCredentials, attrs,
+ false);
+ }
+
+ if (entry && entry->CheckRequest(method, aUnsafeHeaders)) {
+ aCallback->OnPreflightSucceeded();
+ return NS_OK;
+ }
+
+ // Either it wasn't cached or the cached result has expired. Build a
+ // channel for the OPTIONS request.
+
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ static_cast<mozilla::net::LoadInfo*>(originalLoadInfo.get())
+ ->CloneForNewRequest();
+ static_cast<mozilla::net::LoadInfo*>(loadInfo.get())->SetIsPreflight();
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We want to give the preflight channel's notification callbacks the same
+ // load context as the original channel's notification callbacks had. We
+ // don't worry about a load context provided via the loadgroup here, since
+ // they have the same loadgroup.
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ rv = aRequestChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
+
+ // Preflight requests should never be intercepted by service workers and
+ // are always anonymous.
+ // NOTE: We ignore CORS checks on synthesized responses (see the CORS
+ // preflights, then we need to extend the GetResponseSynthesized() check in
+ // nsCORSListenerProxy::CheckRequestApproved()). If we change our behavior
+ // here and allow service workers to intercept CORS preflights, then that
+ // check won't be safe any more.
+ loadFlags |=
+ nsIChannel::LOAD_BYPASS_SERVICE_WORKER | nsIRequest::LOAD_ANONYMOUS;
+
+ if (StaticPrefs::network_cors_preflight_allow_client_cert()) {
+ loadFlags |= nsIRequest::LOAD_ANONYMOUS_ALLOW_CLIENT_CERT;
+ }
+
+ nsCOMPtr<nsIChannel> preflightChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(preflightChannel), uri, loadInfo,
+ nullptr, // PerformanceStorage
+ loadGroup,
+ nullptr, // aCallbacks
+ loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set method and headers
+ nsCOMPtr<nsIHttpChannel> preHttp = do_QueryInterface(preflightChannel);
+ NS_ASSERTION(preHttp, "Failed to QI to nsIHttpChannel!");
+
+ rv = preHttp->SetRequestMethod("OPTIONS"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = preHttp->SetRequestHeader("Access-Control-Request-Method"_ns, method,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set the CORS preflight channel's warning reporter to be the same as the
+ // requesting channel so that all log messages are able to be reported through
+ // the warning reporter.
+ RefPtr<nsHttpChannel> reqCh = do_QueryObject(aRequestChannel);
+ RefPtr<nsHttpChannel> preCh = do_QueryObject(preHttp);
+ if (preCh && reqCh) { // there are other implementers of nsIHttpChannel
+ preCh->SetWarningReporter(reqCh->GetWarningReporter());
+ }
+
+ nsTArray<nsCString> preflightHeaders;
+ if (!aUnsafeHeaders.IsEmpty()) {
+ for (uint32_t i = 0; i < aUnsafeHeaders.Length(); ++i) {
+ preflightHeaders.AppendElement();
+ ToLowerCase(aUnsafeHeaders[i], preflightHeaders[i]);
+ }
+ preflightHeaders.Sort();
+ nsAutoCString headers;
+ for (uint32_t i = 0; i < preflightHeaders.Length(); ++i) {
+ if (i != 0) {
+ headers += ',';
+ }
+ headers += preflightHeaders[i];
+ }
+ rv = preHttp->SetRequestHeader("Access-Control-Request-Headers"_ns, headers,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Set up listener which will start the original channel
+ RefPtr<nsCORSPreflightListener> preflightListener =
+ new nsCORSPreflightListener(principal, aCallback, loadContext,
+ withCredentials, method, preflightHeaders);
+
+ rv = preflightChannel->SetNotificationCallbacks(preflightListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (preCh && reqCh) {
+ // Per https://fetch.spec.whatwg.org/#cors-preflight-fetch step 1, the
+ // request's referrer and referrer policy should match the original request.
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ rv = reqCh->GetReferrerInfo(getter_AddRefs(referrerInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (referrerInfo) {
+ nsCOMPtr<nsIReferrerInfo> newReferrerInfo =
+ static_cast<dom::ReferrerInfo*>(referrerInfo.get())->Clone();
+ rv = preCh->SetReferrerInfo(newReferrerInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // Start preflight
+ rv = preflightChannel->AsyncOpen(preflightListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Return newly created preflight channel
+ preflightChannel.forget(aPreflightChannel);
+
+ return NS_OK;
+}
+
+// static
+void nsCORSListenerProxy::LogBlockedCORSRequest(
+ uint64_t aInnerWindowID, bool aPrivateBrowsing, bool aFromChromeContext,
+ const nsAString& aMessage, const nsACString& aCategory, bool aIsWarning) {
+ nsresult rv = NS_OK;
+
+ // Build the error object and log it to the console
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to log blocked cross-site request (no console)");
+ return;
+ }
+
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to log blocked cross-site request (no scriptError)");
+ return;
+ }
+
+ uint32_t errorFlag =
+ aIsWarning ? nsIScriptError::warningFlag : nsIScriptError::errorFlag;
+
+ // query innerWindowID and log to web console, otherwise log to
+ // the error to the browser console.
+ if (aInnerWindowID > 0) {
+ rv = scriptError->InitWithSanitizedSource(aMessage,
+ u""_ns, // sourceName
+ u""_ns, // sourceLine
+ 0, // lineNumber
+ 0, // columnNumber
+ errorFlag, aCategory,
+ aInnerWindowID);
+ } else {
+ rv = scriptError->Init(aMessage,
+ u""_ns, // sourceName
+ u""_ns, // sourceLine
+ 0, // lineNumber
+ 0, // columnNumber
+ errorFlag, aCategory, aPrivateBrowsing,
+ aFromChromeContext); // From chrome context
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "Failed to log blocked cross-site request (scriptError init failed)");
+ return;
+ }
+ console->LogMessage(scriptError);
+}
diff --git a/netwerk/protocol/http/nsCORSListenerProxy.h b/netwerk/protocol/http/nsCORSListenerProxy.h
new file mode 100644
index 0000000000..e96b0a8aca
--- /dev/null
+++ b/netwerk/protocol/http/nsCORSListenerProxy.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCORSListenerProxy_h__
+#define nsCORSListenerProxy_h__
+
+#include "nsIStreamListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIURI.h"
+#include "nsTArray.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIChannelEventSink.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+
+class nsIHttpChannel;
+class nsIURI;
+class nsIPrincipal;
+class nsINetworkInterceptController;
+class nsICorsPreflightCallback;
+
+namespace mozilla {
+namespace net {
+class HttpChannelParent;
+class nsHttpChannel;
+} // namespace net
+} // namespace mozilla
+
+enum class DataURIHandling { Allow, Disallow };
+
+enum class UpdateType {
+ Default,
+ StripRequestBodyHeader,
+ InternalOrHSTSRedirect
+};
+
+class nsCORSListenerProxy final : public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public nsIThreadRetargetableStreamListener {
+ public:
+ nsCORSListenerProxy(nsIStreamListener* aOuter,
+ nsIPrincipal* aRequestingPrincipal,
+ bool aWithCredentials);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ static void Shutdown();
+ static void ClearCache();
+ static void ClearPrivateBrowsingCache();
+
+ [[nodiscard]] nsresult Init(nsIChannel* aChannel,
+ DataURIHandling aAllowDataURI);
+
+ void SetInterceptController(
+ nsINetworkInterceptController* aInterceptController);
+
+ // When CORS blocks a request, log the message to the web console, or the
+ // browser console if no valid inner window ID is found.
+ static void LogBlockedCORSRequest(uint64_t aInnerWindowID,
+ bool aPrivateBrowsing,
+ bool aFromChromeContext,
+ const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning = false);
+
+ private:
+ // Only HttpChannelParent can call RemoveFromCorsPreflightCache
+ friend class mozilla::net::HttpChannelParent;
+ // Only nsHttpChannel can invoke CORS preflights
+ friend class mozilla::net::nsHttpChannel;
+
+ static void RemoveFromCorsPreflightCache(
+ nsIURI* aURI, nsIPrincipal* aRequestingPrincipal,
+ const mozilla::OriginAttributes& aOriginAttributes);
+ [[nodiscard]] static nsresult StartCORSPreflight(
+ nsIChannel* aRequestChannel, nsICorsPreflightCallback* aCallback,
+ nsTArray<nsCString>& aUnsafeHeaders, nsIChannel** aPreflightChannel);
+
+ ~nsCORSListenerProxy() = default;
+
+ [[nodiscard]] nsresult UpdateChannel(nsIChannel* aChannel,
+ DataURIHandling aAllowDataURI,
+ UpdateType aUpdateType);
+ [[nodiscard]] nsresult CheckRequestApproved(nsIRequest* aRequest);
+ [[nodiscard]] nsresult CheckPreflightNeeded(nsIChannel* aChannel,
+ UpdateType aUpdateType);
+
+ nsCOMPtr<nsIStreamListener> mOuterListener;
+ // The principal that originally kicked off the request
+ nsCOMPtr<nsIPrincipal> mRequestingPrincipal;
+ // The principal to use for our Origin header ("source origin" in spec terms).
+ // This can get changed during redirects, unlike mRequestingPrincipal.
+ nsCOMPtr<nsIPrincipal> mOriginHeaderPrincipal;
+ nsCOMPtr<nsIInterfaceRequestor> mOuterNotificationCallbacks;
+ nsCOMPtr<nsINetworkInterceptController> mInterceptController;
+ bool mWithCredentials;
+ mozilla::Atomic<bool, mozilla::Relaxed> mRequestApproved;
+ // Please note that the member variable mHasBeenCrossSite may rely on the
+ // promise that the CSP directive 'upgrade-insecure-requests' upgrades
+ // an http: request to https: in nsHttpChannel::Connect() and hence
+ // a request might not be marked as cross site request based on that promise.
+ bool mHasBeenCrossSite;
+ bool mIsRedirect = false;
+ // Under e10s, logging happens in the child process. Keep a reference to the
+ // creator nsIHttpChannel in order to find the way back to the child. Released
+ // in OnStopRequest().
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+#ifdef DEBUG
+ bool mInited;
+#endif
+
+ // only locking mOuterListener, because it can be used on different threads.
+ // We guarantee that OnStartRequest, OnDataAvailable and OnStopReques will be
+ // called in order, but to make tsan happy we will lock mOuterListener.
+ mutable mozilla::Mutex mMutex MOZ_UNANNOTATED;
+};
+
+#endif
diff --git a/netwerk/protocol/http/nsHttp.cpp b/netwerk/protocol/http/nsHttp.cpp
new file mode 100644
index 0000000000..768ad91729
--- /dev/null
+++ b/netwerk/protocol/http/nsHttp.cpp
@@ -0,0 +1,1160 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttp.h"
+#include "CacheControlParser.h"
+#include "PLDHashTable.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsCRT.h"
+#include "nsContentUtils.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+#include "nsHttpHandler.h"
+#include "nsICacheEntry.h"
+#include "nsIRequest.h"
+#include "nsIStandardURL.h"
+#include "nsJSUtils.h"
+#include "nsStandardURL.h"
+#include "sslerr.h"
+#include <errno.h>
+#include <functional>
+#include "nsLiteralString.h"
+#include <string.h>
+
+namespace mozilla {
+namespace net {
+
+const uint32_t kHttp3VersionCount = 5;
+const nsCString kHttp3Versions[] = {"h3-29"_ns, "h3-30"_ns, "h3-31"_ns,
+ "h3-32"_ns, "h3"_ns};
+
+// https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-4.3
+constexpr uint64_t kWebTransportErrorCodeStart = 0x52e4a40fa8db;
+constexpr uint64_t kWebTransportErrorCodeEnd = 0x52e4a40fa9e2;
+
+// find out how many atoms we have
+#define HTTP_ATOM(_name, _value) Unused_##_name,
+enum {
+#include "nsHttpAtomList.h"
+ NUM_HTTP_ATOMS
+};
+#undef HTTP_ATOM
+
+static StaticDataMutex<nsTHashtable<nsCStringASCIICaseInsensitiveHashKey>>
+ sAtomTable("nsHttp::sAtomTable");
+
+// This is set to true in DestroyAtomTable so we don't try to repopulate the
+// table if ResolveAtom gets called during shutdown for some reason.
+static Atomic<bool> sTableDestroyed{false};
+
+// We put the atoms in a hash table for speedy lookup.. see ResolveAtom.
+namespace nsHttp {
+
+nsresult CreateAtomTable(
+ nsTHashtable<nsCStringASCIICaseInsensitiveHashKey>& base) {
+ if (sTableDestroyed) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+ // fill the table with our known atoms
+ const nsHttpAtomLiteral* atoms[] = {
+#define HTTP_ATOM(_name, _value) &(_name),
+#include "nsHttpAtomList.h"
+#undef HTTP_ATOM
+ };
+
+ if (!base.IsEmpty()) {
+ return NS_OK;
+ }
+ for (const auto* atom : atoms) {
+ Unused << base.PutEntry(atom->val(), fallible);
+ }
+
+ LOG(("Added static atoms to atomTable"));
+ return NS_OK;
+}
+
+nsresult CreateAtomTable() {
+ LOG(("CreateAtomTable"));
+ auto atomTable = sAtomTable.Lock();
+ return CreateAtomTable(atomTable.ref());
+}
+
+void DestroyAtomTable() {
+ LOG(("DestroyAtomTable"));
+ sTableDestroyed = true;
+ auto atomTable = sAtomTable.Lock();
+ atomTable.ref().Clear();
+}
+
+// this function may be called from multiple threads
+nsHttpAtom ResolveAtom(const nsACString& str) {
+ if (str.IsEmpty()) {
+ return {};
+ }
+
+ auto atomTable = sAtomTable.Lock();
+
+ if (atomTable.ref().IsEmpty()) {
+ if (sTableDestroyed) {
+ NS_WARNING("ResolveAtom called during shutdown");
+ return {};
+ }
+
+ NS_WARNING("ResolveAtom called before CreateAtomTable");
+ if (NS_FAILED(CreateAtomTable(atomTable.ref()))) {
+ return {};
+ }
+ }
+
+ // Check if we already have an entry in the table
+ auto* entry = atomTable.ref().GetEntry(str);
+ if (entry) {
+ return nsHttpAtom(entry->GetKey());
+ }
+
+ LOG(("Putting %s header into atom table", nsPromiseFlatCString(str).get()));
+ // Put the string in the table. If it works create the atom.
+ entry = atomTable.ref().PutEntry(str, fallible);
+ if (entry) {
+ return nsHttpAtom(entry->GetKey());
+ }
+ return {};
+}
+
+//
+// From section 2.2 of RFC 2616, a token is defined as:
+//
+// token = 1*<any CHAR except CTLs or separators>
+// CHAR = <any US-ASCII character (octets 0 - 127)>
+// separators = "(" | ")" | "<" | ">" | "@"
+// | "," | ";" | ":" | "\" | <">
+// | "/" | "[" | "]" | "?" | "="
+// | "{" | "}" | SP | HT
+// CTL = <any US-ASCII control character
+// (octets 0 - 31) and DEL (127)>
+// SP = <US-ASCII SP, space (32)>
+// HT = <US-ASCII HT, horizontal-tab (9)>
+//
+static const char kValidTokenMap[128] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24
+
+ 0, 1, 0, 1, 1, 1, 1, 1, // 32
+ 0, 0, 1, 1, 0, 1, 1, 0, // 40
+ 1, 1, 1, 1, 1, 1, 1, 1, // 48
+ 1, 1, 0, 0, 0, 0, 0, 0, // 56
+
+ 0, 1, 1, 1, 1, 1, 1, 1, // 64
+ 1, 1, 1, 1, 1, 1, 1, 1, // 72
+ 1, 1, 1, 1, 1, 1, 1, 1, // 80
+ 1, 1, 1, 0, 0, 0, 1, 1, // 88
+
+ 1, 1, 1, 1, 1, 1, 1, 1, // 96
+ 1, 1, 1, 1, 1, 1, 1, 1, // 104
+ 1, 1, 1, 1, 1, 1, 1, 1, // 112
+ 1, 1, 1, 0, 1, 0, 1, 0 // 120
+};
+bool IsValidToken(const char* start, const char* end) {
+ if (start == end) return false;
+
+ for (; start != end; ++start) {
+ const unsigned char idx = *start;
+ if (idx > 127 || !kValidTokenMap[idx]) return false;
+ }
+
+ return true;
+}
+
+const char* GetProtocolVersion(HttpVersion pv) {
+ switch (pv) {
+ case HttpVersion::v3_0:
+ return "h3";
+ case HttpVersion::v2_0:
+ return "h2";
+ case HttpVersion::v1_0:
+ return "http/1.0";
+ case HttpVersion::v1_1:
+ return "http/1.1";
+ default:
+ NS_WARNING(nsPrintfCString("Unkown protocol version: 0x%X. "
+ "Please file a bug",
+ static_cast<uint32_t>(pv))
+ .get());
+ return "http/1.1";
+ }
+}
+
+// static
+void TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest) {
+ nsAutoCString str(aSource);
+
+ // HTTP whitespace 0x09: '\t', 0x0A: '\n', 0x0D: '\r', 0x20: ' '
+ static const char kHTTPWhitespace[] = "\t\n\r ";
+ str.Trim(kHTTPWhitespace);
+ aDest.Assign(str);
+}
+
+// static
+bool IsReasonableHeaderValue(const nsACString& s) {
+ // Header values MUST NOT contain line-breaks. RFC 2616 technically
+ // permits CTL characters, including CR and LF, in header values provided
+ // they are quoted. However, this can lead to problems if servers do not
+ // interpret quoted strings properly. Disallowing CR and LF here seems
+ // reasonable and keeps things simple. We also disallow a null byte.
+ const nsACString::char_type* end = s.EndReading();
+ for (const nsACString::char_type* i = s.BeginReading(); i != end; ++i) {
+ if (*i == '\r' || *i == '\n' || *i == '\0') {
+ return false;
+ }
+ }
+ return true;
+}
+
+const char* FindToken(const char* input, const char* token, const char* seps) {
+ if (!input) return nullptr;
+
+ int inputLen = strlen(input);
+ int tokenLen = strlen(token);
+
+ if (inputLen < tokenLen) return nullptr;
+
+ const char* inputTop = input;
+ const char* inputEnd = input + inputLen - tokenLen;
+ for (; input <= inputEnd; ++input) {
+ if (nsCRT::strncasecmp(input, token, tokenLen) == 0) {
+ if (input > inputTop && !strchr(seps, *(input - 1))) continue;
+ if (input < inputEnd && !strchr(seps, *(input + tokenLen))) continue;
+ return input;
+ }
+ }
+
+ return nullptr;
+}
+
+bool ParseInt64(const char* input, const char** next, int64_t* r) {
+ MOZ_ASSERT(input);
+ MOZ_ASSERT(r);
+
+ char* end = nullptr;
+ errno = 0; // Clear errno to make sure its value is set by strtoll
+ int64_t value = strtoll(input, &end, /* base */ 10);
+
+ // Fail if: - the parsed number overflows.
+ // - the end points to the start of the input string.
+ // - we parsed a negative value. Consumers don't expect that.
+ if (errno != 0 || end == input || value < 0) {
+ LOG(("nsHttp::ParseInt64 value=%" PRId64 " errno=%d", value, errno));
+ return false;
+ }
+
+ if (next) {
+ *next = end;
+ }
+ *r = value;
+ return true;
+}
+
+bool IsPermanentRedirect(uint32_t httpStatus) {
+ return httpStatus == 301 || httpStatus == 308;
+}
+
+bool ValidationRequired(bool isForcedValid,
+ nsHttpResponseHead* cachedResponseHead,
+ uint32_t loadFlags, bool allowStaleCacheContent,
+ bool forceValidateCacheContent, bool isImmutable,
+ bool customConditionalRequest,
+ nsHttpRequestHead& requestHead, nsICacheEntry* entry,
+ CacheControlParser& cacheControlRequest,
+ bool fromPreviousSession,
+ bool* performBackgroundRevalidation) {
+ if (performBackgroundRevalidation) {
+ *performBackgroundRevalidation = false;
+ }
+
+ // Check isForcedValid to see if it is possible to skip validation.
+ // Don't skip validation if we have serious reason to believe that this
+ // content is invalid (it's expired).
+ // See netwerk/cache2/nsICacheEntry.idl for details
+ if (isForcedValid && (!cachedResponseHead->ExpiresInPast() ||
+ !cachedResponseHead->MustValidateIfExpired())) {
+ LOG(("NOT validating based on isForcedValid being true.\n"));
+ return false;
+ }
+
+ // If the LOAD_FROM_CACHE flag is set, any cached data can simply be used
+ if (loadFlags & nsIRequest::LOAD_FROM_CACHE || allowStaleCacheContent) {
+ LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
+ return false;
+ }
+
+ // If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
+ // it's revalidated with the server.
+ if (((loadFlags & nsIRequest::VALIDATE_ALWAYS) ||
+ forceValidateCacheContent) &&
+ !isImmutable) {
+ LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
+ return true;
+ }
+
+ // Even if the VALIDATE_NEVER flag is set, there are still some cases in
+ // which we must validate the cached response with the server.
+ if (loadFlags & nsIRequest::VALIDATE_NEVER) {
+ LOG(("VALIDATE_NEVER set\n"));
+ // if no-store validate cached response (see bug 112564)
+ if (cachedResponseHead->NoStore()) {
+ LOG(("Validating based on no-store logic\n"));
+ return true;
+ }
+ LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
+ return false;
+ }
+
+ // check if validation is strictly required...
+ if (cachedResponseHead->MustValidate()) {
+ LOG(("Validating based on MustValidate() returning TRUE\n"));
+ return true;
+ }
+
+ // possibly serve from cache for a custom If-Match/If-Unmodified-Since
+ // conditional request
+ if (customConditionalRequest && !requestHead.HasHeader(nsHttp::If_Match) &&
+ !requestHead.HasHeader(nsHttp::If_Unmodified_Since)) {
+ LOG(("Validating based on a custom conditional request\n"));
+ return true;
+ }
+
+ // previously we also checked for a query-url w/out expiration
+ // and didn't do heuristic on it. but defacto that is allowed now.
+ //
+ // Check if the cache entry has expired...
+
+ bool doValidation = true;
+ uint32_t now = NowInSeconds();
+
+ uint32_t age = 0;
+ nsresult rv = cachedResponseHead->ComputeCurrentAge(now, now, &age);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ uint32_t freshness = 0;
+ rv = cachedResponseHead->ComputeFreshnessLifetime(&freshness);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ uint32_t expiration = 0;
+ rv = entry->GetExpirationTime(&expiration);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ uint32_t maxAgeRequest, maxStaleRequest, minFreshRequest;
+
+ LOG((" NowInSeconds()=%u, expiration time=%u, freshness lifetime=%u, age=%u",
+ now, expiration, freshness, age));
+
+ if (cacheControlRequest.NoCache()) {
+ LOG((" validating, no-cache request"));
+ doValidation = true;
+ } else if (cacheControlRequest.MaxStale(&maxStaleRequest)) {
+ uint32_t staleTime = age > freshness ? age - freshness : 0;
+ doValidation = staleTime > maxStaleRequest;
+ LOG((" validating=%d, max-stale=%u requested", doValidation,
+ maxStaleRequest));
+ } else if (cacheControlRequest.MaxAge(&maxAgeRequest)) {
+ // The input information for age and freshness calculation are in seconds.
+ // Hence, the internal logic can't have better resolution than seconds too.
+ // To make max-age=0 case work even for requests made in less than a second
+ // after the last response has been received, we use >= for compare. This
+ // is correct because of the rounding down of the age calculated value.
+ doValidation = age >= maxAgeRequest;
+ LOG((" validating=%d, max-age=%u requested", doValidation, maxAgeRequest));
+ } else if (cacheControlRequest.MinFresh(&minFreshRequest)) {
+ uint32_t freshTime = freshness > age ? freshness - age : 0;
+ doValidation = freshTime < minFreshRequest;
+ LOG((" validating=%d, min-fresh=%u requested", doValidation,
+ minFreshRequest));
+ } else if (now < expiration) {
+ doValidation = false;
+ LOG((" not validating, expire time not in the past"));
+ } else if (cachedResponseHead->MustValidateIfExpired()) {
+ doValidation = true;
+ } else if (cachedResponseHead->StaleWhileRevalidate(now, expiration) &&
+ StaticPrefs::network_http_stale_while_revalidate_enabled()) {
+ LOG((" not validating, in the stall-while-revalidate window"));
+ doValidation = false;
+ if (performBackgroundRevalidation) {
+ *performBackgroundRevalidation = true;
+ }
+ } else if (loadFlags & nsIRequest::VALIDATE_ONCE_PER_SESSION) {
+ // If the cached response does not include expiration infor-
+ // mation, then we must validate the response, despite whether
+ // or not this is the first access this session. This behavior
+ // is consistent with existing browsers and is generally expected
+ // by web authors.
+ if (freshness == 0) {
+ doValidation = true;
+ } else {
+ doValidation = fromPreviousSession;
+ }
+ } else {
+ doValidation = true;
+ }
+
+ LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
+ return doValidation;
+}
+
+nsresult GetHttpResponseHeadFromCacheEntry(
+ nsICacheEntry* entry, nsHttpResponseHead* cachedResponseHead) {
+ nsCString buf;
+ // A "original-response-headers" metadata element holds network original
+ // headers, i.e. the headers in the form as they arrieved from the network. We
+ // need to get the network original headers first, because we need to keep
+ // them in order.
+ nsresult rv = entry->GetMetaDataElement("original-response-headers",
+ getter_Copies(buf));
+ if (NS_SUCCEEDED(rv)) {
+ rv = cachedResponseHead->ParseCachedOriginalHeaders((char*)buf.get());
+ if (NS_FAILED(rv)) {
+ LOG((" failed to parse original-response-headers\n"));
+ }
+ }
+
+ buf.Adopt(nullptr);
+ // A "response-head" metadata element holds response head, e.g. response
+ // status line and headers in the form Firefox uses them internally (no
+ // dupicate headers, etc.).
+ rv = entry->GetMetaDataElement("response-head", getter_Copies(buf));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Parse string stored in a "response-head" metadata element.
+ // These response headers will be merged with the orignal headers (i.e. the
+ // headers stored in a "original-response-headers" metadata element).
+ rv = cachedResponseHead->ParseCachedHead(buf.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ buf.Adopt(nullptr);
+
+ return NS_OK;
+}
+
+nsresult CheckPartial(nsICacheEntry* aEntry, int64_t* aSize,
+ int64_t* aContentLength,
+ nsHttpResponseHead* responseHead) {
+ nsresult rv;
+
+ rv = aEntry->GetDataSize(aSize);
+
+ if (NS_ERROR_IN_PROGRESS == rv) {
+ *aSize = -1;
+ rv = NS_OK;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!responseHead) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aContentLength = responseHead->ContentLength();
+
+ return NS_OK;
+}
+
+void DetermineFramingAndImmutability(nsICacheEntry* entry,
+ nsHttpResponseHead* responseHead,
+ bool isHttps, bool* weaklyFramed,
+ bool* isImmutable) {
+ nsCString framedBuf;
+ nsresult rv =
+ entry->GetMetaDataElement("strongly-framed", getter_Copies(framedBuf));
+ // describe this in terms of explicitly weakly framed so as to be backwards
+ // compatible with old cache contents which dont have strongly-framed makers
+ *weaklyFramed = NS_SUCCEEDED(rv) && framedBuf.EqualsLiteral("0");
+ *isImmutable = !*weaklyFramed && isHttps && responseHead->Immutable();
+}
+
+bool IsBeforeLastActiveTabLoadOptimization(TimeStamp const& when) {
+ return gHttpHandler &&
+ gHttpHandler->IsBeforeLastActiveTabLoadOptimization(when);
+}
+
+nsCString ConvertRequestHeadToString(nsHttpRequestHead& aRequestHead,
+ bool aHasRequestBody,
+ bool aRequestBodyHasHeaders,
+ bool aUsingConnect) {
+ // Make sure that there is "Content-Length: 0" header in the requestHead
+ // in case of POST and PUT methods when there is no requestBody and
+ // requestHead doesn't contain "Transfer-Encoding" header.
+ //
+ // RFC1945 section 7.2.2:
+ // HTTP/1.0 requests containing an entity body must include a valid
+ // Content-Length header field.
+ //
+ // RFC2616 section 4.4:
+ // For compatibility with HTTP/1.0 applications, HTTP/1.1 requests
+ // containing a message-body MUST include a valid Content-Length header
+ // field unless the server is known to be HTTP/1.1 compliant.
+ if ((aRequestHead.IsPost() || aRequestHead.IsPut()) && !aHasRequestBody &&
+ !aRequestHead.HasHeader(nsHttp::Transfer_Encoding)) {
+ DebugOnly<nsresult> rv =
+ aRequestHead.SetHeader(nsHttp::Content_Length, "0"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ nsCString reqHeaderBuf;
+ reqHeaderBuf.Truncate();
+
+ // make sure we eliminate any proxy specific headers from
+ // the request if we are using CONNECT
+ aRequestHead.Flatten(reqHeaderBuf, aUsingConnect);
+
+ if (!aRequestBodyHasHeaders || !aHasRequestBody) {
+ reqHeaderBuf.AppendLiteral("\r\n");
+ }
+
+ return reqHeaderBuf;
+}
+
+void NotifyActiveTabLoadOptimization() {
+ if (gHttpHandler) {
+ gHttpHandler->NotifyActiveTabLoadOptimization();
+ }
+}
+
+TimeStamp GetLastActiveTabLoadOptimizationHit() {
+ return gHttpHandler ? gHttpHandler->GetLastActiveTabLoadOptimizationHit()
+ : TimeStamp();
+}
+
+void SetLastActiveTabLoadOptimizationHit(TimeStamp const& when) {
+ if (gHttpHandler) {
+ gHttpHandler->SetLastActiveTabLoadOptimizationHit(when);
+ }
+}
+
+} // namespace nsHttp
+
+template <typename T>
+void localEnsureBuffer(UniquePtr<T[]>& buf, uint32_t newSize, uint32_t preserve,
+ uint32_t& objSize) {
+ if (objSize >= newSize) return;
+
+ // Leave a little slop on the new allocation - add 2KB to
+ // what we need and then round the result up to a 4KB (page)
+ // boundary.
+
+ objSize = (newSize + 2048 + 4095) & ~4095;
+
+ static_assert(sizeof(T) == 1, "sizeof(T) must be 1");
+ auto tmp = MakeUnique<T[]>(objSize);
+ if (preserve) {
+ memcpy(tmp.get(), buf.get(), preserve);
+ }
+ buf = std::move(tmp);
+}
+
+void EnsureBuffer(UniquePtr<char[]>& buf, uint32_t newSize, uint32_t preserve,
+ uint32_t& objSize) {
+ localEnsureBuffer<char>(buf, newSize, preserve, objSize);
+}
+
+void EnsureBuffer(UniquePtr<uint8_t[]>& buf, uint32_t newSize,
+ uint32_t preserve, uint32_t& objSize) {
+ localEnsureBuffer<uint8_t>(buf, newSize, preserve, objSize);
+}
+
+static bool IsTokenSymbol(signed char chr) {
+ return !(chr < 33 || chr == 127 || chr == '(' || chr == ')' || chr == '<' ||
+ chr == '>' || chr == '@' || chr == ',' || chr == ';' || chr == ':' ||
+ chr == '"' || chr == '/' || chr == '[' || chr == ']' || chr == '?' ||
+ chr == '=' || chr == '{' || chr == '}' || chr == '\\');
+}
+
+ParsedHeaderPair::ParsedHeaderPair(const char* name, int32_t nameLen,
+ const char* val, int32_t valLen,
+ bool isQuotedValue)
+ : mName(nsDependentCSubstring(nullptr, size_t(0))),
+ mValue(nsDependentCSubstring(nullptr, size_t(0))),
+ mIsQuotedValue(isQuotedValue) {
+ if (nameLen > 0) {
+ mName.Rebind(name, name + nameLen);
+ }
+ if (valLen > 0) {
+ if (mIsQuotedValue) {
+ RemoveQuotedStringEscapes(val, valLen);
+ mValue.Rebind(mUnquotedValue.BeginWriting(), mUnquotedValue.Length());
+ } else {
+ mValue.Rebind(val, val + valLen);
+ }
+ }
+}
+
+void ParsedHeaderPair::RemoveQuotedStringEscapes(const char* val,
+ int32_t valLen) {
+ mUnquotedValue.Truncate();
+ const char* c = val;
+ for (int32_t i = 0; i < valLen; ++i) {
+ if (c[i] == '\\' && c[i + 1]) {
+ ++i;
+ }
+ mUnquotedValue.Append(c[i]);
+ }
+}
+
+static void Tokenize(
+ const char* input, uint32_t inputLen, const char token,
+ const std::function<void(const char*, uint32_t)>& consumer) {
+ auto trimWhitespace = [](const char* in, uint32_t inLen, const char** out,
+ uint32_t* outLen) {
+ *out = in;
+ *outLen = inLen;
+ if (inLen == 0) {
+ return;
+ }
+
+ // Trim leading space
+ while (nsCRT::IsAsciiSpace(**out)) {
+ (*out)++;
+ --(*outLen);
+ }
+
+ // Trim tailing space
+ for (const char* i = *out + *outLen - 1; i >= *out; --i) {
+ if (!nsCRT::IsAsciiSpace(*i)) {
+ break;
+ }
+ --(*outLen);
+ }
+ };
+
+ const char* first = input;
+ bool inQuote = false;
+ const char* result = nullptr;
+ uint32_t resultLen = 0;
+ for (uint32_t index = 0; index < inputLen; ++index) {
+ if (inQuote && input[index] == '\\' && input[index + 1]) {
+ index++;
+ continue;
+ }
+ if (input[index] == '"') {
+ inQuote = !inQuote;
+ continue;
+ }
+ if (inQuote) {
+ continue;
+ }
+ if (input[index] == token) {
+ trimWhitespace(first, (input + index) - first, &result, &resultLen);
+ consumer(result, resultLen);
+ first = input + index + 1;
+ }
+ }
+
+ trimWhitespace(first, (input + inputLen) - first, &result, &resultLen);
+ consumer(result, resultLen);
+}
+
+ParsedHeaderValueList::ParsedHeaderValueList(const char* t, uint32_t len,
+ bool allowInvalidValue) {
+ if (!len) {
+ return;
+ }
+
+ ParsedHeaderValueList* self = this;
+ auto consumer = [=](const char* output, uint32_t outputLength) {
+ self->ParseNameAndValue(output, allowInvalidValue);
+ };
+
+ Tokenize(t, len, ';', consumer);
+}
+
+void ParsedHeaderValueList::ParseNameAndValue(const char* input,
+ bool allowInvalidValue) {
+ const char* nameStart = input;
+ const char* nameEnd = nullptr;
+ const char* valueStart = input;
+ const char* valueEnd = nullptr;
+ bool isQuotedString = false;
+ bool invalidValue = false;
+
+ for (; *input && *input != ';' && *input != ',' &&
+ !nsCRT::IsAsciiSpace(*input) && *input != '=';
+ input++) {
+ ;
+ }
+
+ nameEnd = input;
+
+ if (!(nameEnd - nameStart)) {
+ return;
+ }
+
+ // Check whether param name is a valid token.
+ for (const char* c = nameStart; c < nameEnd; c++) {
+ if (!IsTokenSymbol(*c)) {
+ nameEnd = c;
+ break;
+ }
+ }
+
+ if (!(nameEnd - nameStart)) {
+ return;
+ }
+
+ while (nsCRT::IsAsciiSpace(*input)) {
+ ++input;
+ }
+
+ if (!*input || *input++ != '=') {
+ mValues.AppendElement(
+ ParsedHeaderPair(nameStart, nameEnd - nameStart, nullptr, 0, false));
+ return;
+ }
+
+ while (nsCRT::IsAsciiSpace(*input)) {
+ ++input;
+ }
+
+ if (*input != '"') {
+ // The value is a token, not a quoted string.
+ valueStart = input;
+ for (valueEnd = input; *valueEnd && !nsCRT::IsAsciiSpace(*valueEnd) &&
+ *valueEnd != ';' && *valueEnd != ',';
+ valueEnd++) {
+ ;
+ }
+ if (!allowInvalidValue) {
+ for (const char* c = valueStart; c < valueEnd; c++) {
+ if (!IsTokenSymbol(*c)) {
+ valueEnd = c;
+ break;
+ }
+ }
+ }
+ } else {
+ bool foundQuotedEnd = false;
+ isQuotedString = true;
+
+ ++input;
+ valueStart = input;
+ for (valueEnd = input; *valueEnd; ++valueEnd) {
+ if (*valueEnd == '\\' && *(valueEnd + 1)) {
+ ++valueEnd;
+ } else if (*valueEnd == '"') {
+ foundQuotedEnd = true;
+ break;
+ }
+ }
+ if (!foundQuotedEnd) {
+ invalidValue = true;
+ }
+
+ input = valueEnd;
+ // *valueEnd != null means that *valueEnd is quote character.
+ if (*valueEnd) {
+ input++;
+ }
+ }
+
+ if (invalidValue) {
+ valueEnd = valueStart;
+ }
+
+ mValues.AppendElement(ParsedHeaderPair(nameStart, nameEnd - nameStart,
+ valueStart, valueEnd - valueStart,
+ isQuotedString));
+}
+
+ParsedHeaderValueListList::ParsedHeaderValueListList(
+ const nsCString& fullHeader, bool allowInvalidValue)
+ : mFull(fullHeader) {
+ auto& values = mValues;
+ auto consumer = [&values, allowInvalidValue](const char* output,
+ uint32_t outputLength) {
+ values.AppendElement(
+ ParsedHeaderValueList(output, outputLength, allowInvalidValue));
+ };
+
+ Tokenize(mFull.BeginReading(), mFull.Length(), ',', consumer);
+}
+
+Maybe<nsCString> CallingScriptLocationString() {
+ if (!LOG4_ENABLED() && !xpc::IsInAutomation()) {
+ return Nothing();
+ }
+
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ if (!cx) {
+ return Nothing();
+ }
+
+ nsAutoCString fileNameString;
+ uint32_t line = 0, col = 0;
+ if (!nsJSUtils::GetCallingLocation(cx, fileNameString, &line, &col)) {
+ return Nothing();
+ }
+
+ nsCString logString = ""_ns;
+ logString.AppendPrintf("%s:%u:%u", fileNameString.get(), line, col);
+ return Some(logString);
+}
+
+void LogCallingScriptLocation(void* instance) {
+ Maybe<nsCString> logLocation = CallingScriptLocationString();
+ LogCallingScriptLocation(instance, logLocation);
+}
+
+void LogCallingScriptLocation(void* instance,
+ const Maybe<nsCString>& aLogLocation) {
+ if (aLogLocation.isNothing()) {
+ return;
+ }
+
+ nsCString logString;
+ logString.AppendPrintf("%p called from script: ", instance);
+ logString.AppendPrintf("%s", aLogLocation->get());
+ LOG(("%s", logString.get()));
+}
+
+void LogHeaders(const char* lineStart) {
+ nsAutoCString buf;
+ const char* endOfLine;
+ while ((endOfLine = strstr(lineStart, "\r\n"))) {
+ buf.Assign(lineStart, endOfLine - lineStart);
+ if (StaticPrefs::network_http_sanitize_headers_in_logs() &&
+ (nsCRT::strcasestr(buf.get(), "authorization: ") ||
+ nsCRT::strcasestr(buf.get(), "proxy-authorization: "))) {
+ char* p = strchr(buf.BeginWriting(), ' ');
+ while (p && *++p) {
+ *p = '*';
+ }
+ }
+ LOG1((" %s\n", buf.get()));
+ lineStart = endOfLine + 2;
+ }
+}
+
+nsresult HttpProxyResponseToErrorCode(uint32_t aStatusCode) {
+ // In proxy CONNECT case, we treat every response code except 200 as an error.
+ // Even if the proxy server returns other 2xx codes (i.e. 206), this function
+ // still returns an error code.
+ MOZ_ASSERT(aStatusCode != 200);
+
+ nsresult rv;
+ switch (aStatusCode) {
+ case 300:
+ case 301:
+ case 302:
+ case 303:
+ case 307:
+ case 308:
+ // Bad redirect: not top-level, or it's a POST, bad/missing Location,
+ // or ProcessRedirect() failed for some other reason. Legal
+ // redirects that fail because site not available, etc., are handled
+ // elsewhere, in the regular codepath.
+ rv = NS_ERROR_CONNECTION_REFUSED;
+ break;
+ // Squid sends 404 if DNS fails (regular 404 from target is tunneled)
+ case 404: // HTTP/1.1: "Not Found"
+ // RFC 2616: "some deployed proxies are known to return 400 or
+ // 500 when DNS lookups time out." (Squid uses 500 if it runs
+ // out of sockets: so we have a conflict here).
+ case 400: // HTTP/1.1 "Bad Request"
+ case 500: // HTTP/1.1: "Internal Server Error"
+ rv = NS_ERROR_UNKNOWN_HOST;
+ break;
+ case 401:
+ rv = NS_ERROR_PROXY_UNAUTHORIZED;
+ break;
+ case 402:
+ rv = NS_ERROR_PROXY_PAYMENT_REQUIRED;
+ break;
+ case 403:
+ rv = NS_ERROR_PROXY_FORBIDDEN;
+ break;
+ case 405:
+ rv = NS_ERROR_PROXY_METHOD_NOT_ALLOWED;
+ break;
+ case 406:
+ rv = NS_ERROR_PROXY_NOT_ACCEPTABLE;
+ break;
+ case 407: // ProcessAuthentication() failed (e.g. no header)
+ rv = NS_ERROR_PROXY_AUTHENTICATION_FAILED;
+ break;
+ case 408:
+ rv = NS_ERROR_PROXY_REQUEST_TIMEOUT;
+ break;
+ case 409:
+ rv = NS_ERROR_PROXY_CONFLICT;
+ break;
+ case 410:
+ rv = NS_ERROR_PROXY_GONE;
+ break;
+ case 411:
+ rv = NS_ERROR_PROXY_LENGTH_REQUIRED;
+ break;
+ case 412:
+ rv = NS_ERROR_PROXY_PRECONDITION_FAILED;
+ break;
+ case 413:
+ rv = NS_ERROR_PROXY_REQUEST_ENTITY_TOO_LARGE;
+ break;
+ case 414:
+ rv = NS_ERROR_PROXY_REQUEST_URI_TOO_LONG;
+ break;
+ case 415:
+ rv = NS_ERROR_PROXY_UNSUPPORTED_MEDIA_TYPE;
+ break;
+ case 416:
+ rv = NS_ERROR_PROXY_REQUESTED_RANGE_NOT_SATISFIABLE;
+ break;
+ case 417:
+ rv = NS_ERROR_PROXY_EXPECTATION_FAILED;
+ break;
+ case 421:
+ rv = NS_ERROR_PROXY_MISDIRECTED_REQUEST;
+ break;
+ case 425:
+ rv = NS_ERROR_PROXY_TOO_EARLY;
+ break;
+ case 426:
+ rv = NS_ERROR_PROXY_UPGRADE_REQUIRED;
+ break;
+ case 428:
+ rv = NS_ERROR_PROXY_PRECONDITION_REQUIRED;
+ break;
+ case 429:
+ rv = NS_ERROR_PROXY_TOO_MANY_REQUESTS;
+ break;
+ case 431:
+ rv = NS_ERROR_PROXY_REQUEST_HEADER_FIELDS_TOO_LARGE;
+ break;
+ case 451:
+ rv = NS_ERROR_PROXY_UNAVAILABLE_FOR_LEGAL_REASONS;
+ break;
+ case 501:
+ rv = NS_ERROR_PROXY_NOT_IMPLEMENTED;
+ break;
+ case 502:
+ rv = NS_ERROR_PROXY_BAD_GATEWAY;
+ break;
+ case 503:
+ // Squid returns 503 if target request fails for anything but DNS.
+ /* User sees: "Failed to Connect:
+ * Firefox can't establish a connection to the server at
+ * www.foo.com. Though the site seems valid, the browser
+ * was unable to establish a connection."
+ */
+ rv = NS_ERROR_CONNECTION_REFUSED;
+ break;
+ // RFC 2616 uses 504 for both DNS and target timeout, so not clear what to
+ // do here: picking target timeout, as DNS covered by 400/404/500
+ case 504:
+ rv = NS_ERROR_PROXY_GATEWAY_TIMEOUT;
+ break;
+ case 505:
+ rv = NS_ERROR_PROXY_VERSION_NOT_SUPPORTED;
+ break;
+ case 506:
+ rv = NS_ERROR_PROXY_VARIANT_ALSO_NEGOTIATES;
+ break;
+ case 510:
+ rv = NS_ERROR_PROXY_NOT_EXTENDED;
+ break;
+ case 511:
+ rv = NS_ERROR_PROXY_NETWORK_AUTHENTICATION_REQUIRED;
+ break;
+ default:
+ rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
+ break;
+ }
+
+ return rv;
+}
+
+SupportedAlpnRank H3VersionToRank(const nsACString& aVersion) {
+ for (uint32_t i = 0; i < kHttp3VersionCount; i++) {
+ if (aVersion.Equals(kHttp3Versions[i])) {
+ return static_cast<SupportedAlpnRank>(
+ static_cast<uint32_t>(SupportedAlpnRank::HTTP_3_DRAFT_29) + i);
+ }
+ }
+
+ return SupportedAlpnRank::NOT_SUPPORTED;
+}
+
+SupportedAlpnRank IsAlpnSupported(const nsACString& aAlpn) {
+ if (nsHttpHandler::IsHttp3Enabled() &&
+ gHttpHandler->IsHttp3VersionSupported(aAlpn)) {
+ return H3VersionToRank(aAlpn);
+ }
+
+ if (StaticPrefs::network_http_http2_enabled()) {
+ SpdyInformation* spdyInfo = gHttpHandler->SpdyInfo();
+ if (aAlpn.Equals(spdyInfo->VersionString)) {
+ return SupportedAlpnRank::HTTP_2;
+ }
+ }
+
+ if (aAlpn.LowerCaseEqualsASCII("http/1.1")) {
+ return SupportedAlpnRank::HTTP_1_1;
+ }
+
+ return SupportedAlpnRank::NOT_SUPPORTED;
+}
+
+// On some security error when 0RTT is used we want to restart transactions
+// without 0RTT. Some firewalls do not behave well with 0RTT and cause this
+// errors.
+bool SecurityErrorThatMayNeedRestart(nsresult aReason) {
+ return (aReason ==
+ psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT)) ||
+ (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_MAC_ALERT));
+}
+
+nsresult MakeOriginURL(const nsACString& origin, nsCOMPtr<nsIURI>& url) {
+ nsAutoCString scheme;
+ nsresult rv = net_ExtractURLScheme(origin, scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return MakeOriginURL(scheme, origin, url);
+}
+
+nsresult MakeOriginURL(const nsACString& scheme, const nsACString& origin,
+ nsCOMPtr<nsIURI>& url) {
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_AUTHORITY,
+ scheme.EqualsLiteral("http") ? NS_HTTP_DEFAULT_PORT
+ : NS_HTTPS_DEFAULT_PORT,
+ origin, nullptr, nullptr, nullptr)
+ .Finalize(url);
+}
+
+void CreatePushHashKey(const nsCString& scheme, const nsCString& hostHeader,
+ const mozilla::OriginAttributes& originAttributes,
+ uint64_t serial, const nsACString& pathInfo,
+ nsCString& outOrigin, nsCString& outKey) {
+ nsCString fullOrigin = scheme;
+ fullOrigin.AppendLiteral("://");
+ fullOrigin.Append(hostHeader);
+
+ nsCOMPtr<nsIURI> origin;
+ nsresult rv = MakeOriginURL(scheme, fullOrigin, origin);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = origin->GetAsciiSpec(outOrigin);
+ outOrigin.Trim("/", false, true, false);
+ }
+
+ if (NS_FAILED(rv)) {
+ // Fallback to plain text copy - this may end up behaving poorly
+ outOrigin = fullOrigin;
+ }
+
+ outKey = outOrigin;
+ outKey.AppendLiteral("/[");
+ nsAutoCString suffix;
+ originAttributes.CreateSuffix(suffix);
+ outKey.Append(suffix);
+ outKey.Append(']');
+ outKey.AppendLiteral("/[http2.");
+ outKey.AppendInt(serial);
+ outKey.Append(']');
+ outKey.Append(pathInfo);
+}
+
+nsresult GetNSResultFromWebTransportError(uint8_t aErrorCode) {
+ return static_cast<nsresult>((uint32_t)NS_ERROR_WEBTRANSPORT_CODE_BASE +
+ (uint32_t)aErrorCode);
+}
+
+uint8_t GetWebTransportErrorFromNSResult(nsresult aResult) {
+ if (aResult < NS_ERROR_WEBTRANSPORT_CODE_BASE ||
+ aResult > NS_ERROR_WEBTRANSPORT_CODE_END) {
+ return 0;
+ }
+
+ return static_cast<uint8_t>((uint32_t)aResult -
+ (uint32_t)NS_ERROR_WEBTRANSPORT_CODE_BASE);
+}
+
+uint64_t WebTransportErrorToHttp3Error(uint8_t aErrorCode) {
+ return kWebTransportErrorCodeStart + aErrorCode + aErrorCode / 0x1e;
+}
+
+uint8_t Http3ErrorToWebTransportError(uint64_t aErrorCode) {
+ // Ensure the code is within the valid range.
+ if (aErrorCode < kWebTransportErrorCodeStart ||
+ aErrorCode > kWebTransportErrorCodeEnd) {
+ return 0;
+ }
+
+ uint64_t shifted = aErrorCode - kWebTransportErrorCodeStart;
+ uint64_t result = shifted - shifted / 0x1f;
+
+ if (result <= std::numeric_limits<uint8_t>::max()) {
+ return (uint8_t)result;
+ }
+
+ return 0;
+}
+
+ConnectionCloseReason ToCloseReason(nsresult aErrorCode) {
+ if (NS_SUCCEEDED(aErrorCode)) {
+ return ConnectionCloseReason::OK;
+ }
+
+ if (aErrorCode == NS_ERROR_UNKNOWN_HOST) {
+ return ConnectionCloseReason::DNS_ERROR;
+ }
+ if (aErrorCode == NS_ERROR_NET_RESET) {
+ return ConnectionCloseReason::NET_RESET;
+ }
+ if (aErrorCode == NS_ERROR_CONNECTION_REFUSED) {
+ return ConnectionCloseReason::NET_REFUSED;
+ }
+ if (aErrorCode == NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED) {
+ return ConnectionCloseReason::SOCKET_ADDRESS_NOT_SUPPORTED;
+ }
+ if (aErrorCode == NS_ERROR_NET_TIMEOUT) {
+ return ConnectionCloseReason::NET_TIMEOUT;
+ }
+ if (aErrorCode == NS_ERROR_OUT_OF_MEMORY) {
+ return ConnectionCloseReason::OUT_OF_MEMORY;
+ }
+ if (aErrorCode == NS_ERROR_SOCKET_ADDRESS_IN_USE) {
+ return ConnectionCloseReason::SOCKET_ADDRESS_IN_USE;
+ }
+ if (aErrorCode == NS_BINDING_ABORTED) {
+ return ConnectionCloseReason::BINDING_ABORTED;
+ }
+ if (aErrorCode == NS_BINDING_REDIRECTED) {
+ return ConnectionCloseReason::BINDING_REDIRECTED;
+ }
+ if (aErrorCode == NS_ERROR_ABORT) {
+ return ConnectionCloseReason::ERROR_ABORT;
+ }
+
+ int32_t code = -1 * NS_ERROR_GET_CODE(aErrorCode);
+ if (mozilla::psm::IsNSSErrorCode(code)) {
+ return ConnectionCloseReason::SECURITY_ERROR;
+ }
+
+ return ConnectionCloseReason::OTHER_NET_ERROR;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h
new file mode 100644
index 0000000000..d0bd4cfe67
--- /dev/null
+++ b/netwerk/protocol/http/nsHttp.h
@@ -0,0 +1,527 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttp_h__
+#define nsHttp_h__
+
+#include <stdint.h>
+#include "prtime.h"
+#include "nsString.h"
+#include "nsError.h"
+#include "nsTArray.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/TimeStamp.h"
+
+#include "mozilla/UniquePtr.h"
+#include "NSSErrorsService.h"
+
+class nsICacheEntry;
+
+namespace mozilla {
+
+namespace net {
+class nsHttpResponseHead;
+class nsHttpRequestHead;
+class CacheControlParser;
+
+enum class HttpVersion {
+ UNKNOWN = 0,
+ v0_9 = 9,
+ v1_0 = 10,
+ v1_1 = 11,
+ v2_0 = 20,
+ v3_0 = 30
+};
+
+enum class SpdyVersion { NONE = 0, HTTP_2 = 5 };
+
+enum class SupportedAlpnRank : uint8_t {
+ NOT_SUPPORTED = 0,
+ HTTP_1_1 = 1,
+ HTTP_2 = 2,
+ // Note that the order here MUST be the same as the order in kHttp3Versions.
+ HTTP_3_DRAFT_29 = 3,
+ HTTP_3_DRAFT_30 = 4,
+ HTTP_3_DRAFT_31 = 5,
+ HTTP_3_DRAFT_32 = 6,
+ HTTP_3_VER_1 = 7,
+};
+
+// IMPORTANT: when adding new values, always add them to the end, otherwise
+// it will mess up telemetry.
+enum class ConnectionCloseReason : uint32_t {
+ UNSET = 0,
+ OK,
+ IDLE_TIMEOUT,
+ TLS_TIMEOUT,
+ GO_AWAY,
+ DNS_ERROR,
+ NET_RESET,
+ NET_TIMEOUT,
+ NET_REFUSED,
+ NET_INTERRUPT,
+ NET_INADEQ_SEQURITY,
+ SOCKET_ADDRESS_NOT_SUPPORTED,
+ OUT_OF_MEMORY,
+ SOCKET_ADDRESS_IN_USE,
+ BINDING_ABORTED,
+ BINDING_REDIRECTED,
+ ERROR_ABORT,
+ CLOSE_EXISTING_CONN_FOR_COALESCING,
+ CLOSE_NEW_CONN_FOR_COALESCING,
+ CANT_REUSED,
+ OTHER_NET_ERROR,
+ SECURITY_ERROR,
+};
+
+ConnectionCloseReason ToCloseReason(nsresult aErrorCode);
+
+inline bool IsHttp3(SupportedAlpnRank aRank) {
+ return aRank >= SupportedAlpnRank::HTTP_3_DRAFT_29;
+}
+
+extern const uint32_t kHttp3VersionCount;
+extern const nsCString kHttp3Versions[];
+
+//-----------------------------------------------------------------------------
+// http connection capabilities
+//-----------------------------------------------------------------------------
+
+#define NS_HTTP_ALLOW_KEEPALIVE (1 << 0)
+#define NS_HTTP_LARGE_KEEPALIVE (1 << 1)
+
+// a transaction with this caps flag will continue to own the connection,
+// preventing it from being reclaimed, even after the transaction completes.
+#define NS_HTTP_STICKY_CONNECTION (1 << 2)
+
+// a transaction with this caps flag will, upon opening a new connection,
+// bypass the local DNS cache
+#define NS_HTTP_REFRESH_DNS (1 << 3)
+
+// a transaction with this caps flag will not pass SSL client-certificates
+// to the server (see bug #466080), but is may also be used for other things
+#define NS_HTTP_LOAD_ANONYMOUS (1 << 4)
+
+// a transaction with this caps flag keeps timing information
+#define NS_HTTP_TIMING_ENABLED (1 << 5)
+
+// a transaction with this flag blocks the initiation of other transactons
+// in the same load group until it is complete
+#define NS_HTTP_LOAD_AS_BLOCKING (1 << 6)
+
+// Disallow the use of the SPDY protocol. This is meant for the contexts
+// such as HTTP upgrade which are nonsensical for SPDY, it is not the
+// SPDY configuration variable.
+#define NS_HTTP_DISALLOW_SPDY (1 << 7)
+
+// a transaction with this flag loads without respect to whether the load
+// group is currently blocking on some resources
+#define NS_HTTP_LOAD_UNBLOCKED (1 << 8)
+
+// This flag indicates the transaction should accept associated pushes
+#define NS_HTTP_ONPUSH_LISTENER (1 << 9)
+
+// Transactions with this flag should react to errors without side effects
+// First user is to prevent clearing of alt-svc cache on failed probe
+#define NS_HTTP_ERROR_SOFTLY (1 << 10)
+
+// This corresponds to nsIHttpChannelInternal.beConservative
+// it disables any cutting edge features that we are worried might result in
+// interop problems with critical infrastructure
+#define NS_HTTP_BE_CONSERVATIVE (1 << 11)
+
+// Transactions with this flag should be processed first.
+#define NS_HTTP_URGENT_START (1 << 12)
+
+// A sticky connection of the transaction is explicitly allowed to be restarted
+// on ERROR_NET_RESET.
+#define NS_HTTP_CONNECTION_RESTARTABLE (1 << 13)
+
+// Allow re-using a spdy/http2 connection with NS_HTTP_ALLOW_KEEPALIVE not set.
+// This is primarily used to allow connection sharing for websockets over http/2
+// without accidentally allowing it for websockets not over http/2
+#define NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE (1 << 15)
+
+// Only permit CONNECTing to a proxy. A channel with this flag will not send an
+// http request after CONNECT or setup tls. An http upgrade handler MUST be
+// set. An ALPN header is set using the upgrade protocol.
+#define NS_HTTP_CONNECT_ONLY (1 << 16)
+
+// The connection should not use IPv4.
+#define NS_HTTP_DISABLE_IPV4 (1 << 17)
+
+// The connection should not use IPv6
+#define NS_HTTP_DISABLE_IPV6 (1 << 18)
+
+// Encodes the TRR mode.
+#define NS_HTTP_TRR_MODE_MASK ((1 << 19) | (1 << 20))
+
+// Disallow the use of the HTTP3 protocol. This is meant for the contexts
+// such as HTTP upgrade which are not supported by HTTP3.
+#define NS_HTTP_DISALLOW_HTTP3 (1 << 21)
+
+// Force a transaction to stay in pending queue until the HTTPS RR is
+// available.
+#define NS_HTTP_FORCE_WAIT_HTTP_RR (1 << 22)
+
+// This is used for a temporary workaround for a web-compat issue. The flag is
+// only set on CORS preflight request to allowed sending client certificates
+// on a connection for an anonymous request.
+#define NS_HTTP_LOAD_ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT (1 << 23)
+
+#define NS_HTTP_DISALLOW_HTTPS_RR (1 << 24)
+
+#define NS_HTTP_DISALLOW_ECH (1 << 25)
+
+// Used to indicate that an HTTP Connection should obey Resist Fingerprinting
+// and set the User-Agent accordingly.
+#define NS_HTTP_USE_RFP (1 << 26)
+
+// If set, then the initial TLS handshake failed.
+#define NS_HTTP_IS_RETRY (1 << 27)
+
+// When set, disallow to connect to a HTTP/2 proxy.
+#define NS_HTTP_DISALLOW_HTTP2_PROXY (1 << 28)
+
+#define NS_HTTP_TRR_FLAGS_FROM_MODE(x) ((static_cast<uint32_t>(x) & 3) << 19)
+
+#define NS_HTTP_TRR_MODE_FROM_FLAGS(x) \
+ (static_cast<nsIRequest::TRRMode>((((x) & NS_HTTP_TRR_MODE_MASK) >> 19) & 3))
+
+//-----------------------------------------------------------------------------
+// some default values
+//-----------------------------------------------------------------------------
+
+#define NS_HTTP_DEFAULT_PORT 80
+#define NS_HTTPS_DEFAULT_PORT 443
+
+#define NS_HTTP_HEADER_SEP ','
+
+//-----------------------------------------------------------------------------
+// http atoms...
+//-----------------------------------------------------------------------------
+
+struct nsHttpAtom;
+struct nsHttpAtomLiteral;
+
+namespace nsHttp {
+[[nodiscard]] nsresult CreateAtomTable();
+void DestroyAtomTable();
+
+// will dynamically add atoms to the table if they don't already exist
+nsHttpAtom ResolveAtom(const nsACString& s);
+
+// returns true if the specified token [start,end) is valid per RFC 2616
+// section 2.2
+bool IsValidToken(const char* start, const char* end);
+
+inline bool IsValidToken(const nsACString& s) {
+ return IsValidToken(s.BeginReading(), s.EndReading());
+}
+
+// Strip the leading or trailing HTTP whitespace per fetch spec section 2.2.
+void TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest);
+
+// Returns true if the specified value is reasonable given the defintion
+// in RFC 2616 section 4.2. Full strict validation is not performed
+// currently as it would require full parsing of the value.
+bool IsReasonableHeaderValue(const nsACString& s);
+
+// find the first instance (case-insensitive comparison) of the given
+// |token| in the |input| string. the |token| is bounded by elements of
+// |separators| and may appear at the beginning or end of the |input|
+// string. null is returned if the |token| is not found. |input| may be
+// null, in which case null is returned.
+const char* FindToken(const char* input, const char* token, const char* seps);
+
+// This function parses a string containing a decimal-valued, non-negative
+// 64-bit integer. If the value would exceed INT64_MAX, then false is
+// returned. Otherwise, this function returns true and stores the
+// parsed value in |result|. The next unparsed character in |input| is
+// optionally returned via |next| if |next| is non-null.
+//
+// TODO(darin): Replace this with something generic.
+//
+[[nodiscard]] bool ParseInt64(const char* input, const char** next,
+ int64_t* result);
+
+// Variant on ParseInt64 that expects the input string to contain nothing
+// more than the value being parsed.
+[[nodiscard]] inline bool ParseInt64(const char* input, int64_t* result) {
+ const char* next;
+ return ParseInt64(input, &next, result) && *next == '\0';
+}
+
+// Return whether the HTTP status code represents a permanent redirect
+bool IsPermanentRedirect(uint32_t httpStatus);
+
+// Returns the APLN token which represents the used protocol version.
+const char* GetProtocolVersion(HttpVersion pv);
+
+bool ValidationRequired(bool isForcedValid,
+ nsHttpResponseHead* cachedResponseHead,
+ uint32_t loadFlags, bool allowStaleCacheContent,
+ bool forceValidateCacheContent, bool isImmutable,
+ bool customConditionalRequest,
+ nsHttpRequestHead& requestHead, nsICacheEntry* entry,
+ CacheControlParser& cacheControlRequest,
+ bool fromPreviousSession,
+ bool* performBackgroundRevalidation = nullptr);
+
+nsresult GetHttpResponseHeadFromCacheEntry(
+ nsICacheEntry* entry, nsHttpResponseHead* cachedResponseHead);
+
+nsresult CheckPartial(nsICacheEntry* aEntry, int64_t* aSize,
+ int64_t* aContentLength,
+ nsHttpResponseHead* responseHead);
+
+void DetermineFramingAndImmutability(nsICacheEntry* entry,
+ nsHttpResponseHead* cachedResponseHead,
+ bool isHttps, bool* weaklyFramed,
+ bool* isImmutable);
+
+// Called when an optimization feature affecting active vs background tab load
+// took place. Called only on the parent process and only updates
+// mLastActiveTabLoadOptimizationHit timestamp to now.
+void NotifyActiveTabLoadOptimization();
+TimeStamp GetLastActiveTabLoadOptimizationHit();
+void SetLastActiveTabLoadOptimizationHit(TimeStamp const& when);
+bool IsBeforeLastActiveTabLoadOptimization(TimeStamp const& when);
+
+nsCString ConvertRequestHeadToString(nsHttpRequestHead& aRequestHead,
+ bool aHasRequestBody,
+ bool aRequestBodyHasHeaders,
+ bool aUsingConnect);
+
+template <typename T>
+using SendFunc = std::function<bool(const T&, uint64_t, uint32_t)>;
+
+template <typename T>
+bool SendDataInChunks(const nsCString& aData, uint64_t aOffset, uint32_t aCount,
+ const SendFunc<T>& aSendFunc) {
+ static uint32_t const kCopyChunkSize = 128 * 1024;
+ uint32_t toRead = std::min<uint32_t>(aCount, kCopyChunkSize);
+
+ uint32_t start = 0;
+ while (aCount) {
+ T data(Substring(aData, start, toRead));
+
+ if (!aSendFunc(data, aOffset, toRead)) {
+ return false;
+ }
+
+ aOffset += toRead;
+ start += toRead;
+ aCount -= toRead;
+ toRead = std::min<uint32_t>(aCount, kCopyChunkSize);
+ }
+
+ return true;
+}
+
+} // namespace nsHttp
+
+struct nsHttpAtomLiteral;
+struct nsHttpAtom {
+ nsHttpAtom() = default;
+ nsHttpAtom(const nsHttpAtom& other) = default;
+
+ explicit operator bool() const { return !_val.IsEmpty(); }
+
+ const char* get() const {
+ if (_val.IsEmpty()) {
+ return nullptr;
+ }
+ return _val.BeginReading();
+ }
+
+ const nsCString& val() const { return _val; }
+
+ void operator=(const nsHttpAtom& a) { _val = a._val; }
+
+ // This constructor is mainly used to build the static atom list
+ // Avoid using it for anything else.
+ explicit nsHttpAtom(const nsACString& val) : _val(val) {}
+
+ private:
+ nsCString _val;
+ friend nsHttpAtom nsHttp::ResolveAtom(const nsACString& s);
+};
+
+struct nsHttpAtomLiteral {
+ const char* get() const { return _data.get(); }
+ nsLiteralCString const& val() const { return _data; }
+
+ template <size_t N>
+ constexpr explicit nsHttpAtomLiteral(const char (&val)[N]) : _data(val) {}
+
+ operator nsHttpAtom() const { return nsHttpAtom(_data); }
+
+ private:
+ nsLiteralCString _data;
+};
+
+inline bool operator==(nsHttpAtomLiteral const& self,
+ nsHttpAtomLiteral const& other) {
+ return self.get() == other.get();
+}
+inline bool operator!=(nsHttpAtomLiteral const& self,
+ nsHttpAtomLiteral const& other) {
+ return self.get() != other.get();
+}
+
+inline bool operator==(nsHttpAtom const& self, nsHttpAtomLiteral const& other) {
+ return self.val() == other.val();
+}
+inline bool operator!=(nsHttpAtom const& self, nsHttpAtomLiteral const& other) {
+ return self.val() != other.val();
+}
+
+inline bool operator==(nsHttpAtomLiteral const& self, nsHttpAtom const& other) {
+ return self.val() == other.val();
+}
+inline bool operator!=(nsHttpAtomLiteral const& self, nsHttpAtom const& other) {
+ return self.val() != other.val();
+}
+
+inline bool operator==(nsHttpAtom const& self, nsHttpAtom const& other) {
+ return self.val() == other.val();
+}
+inline bool operator!=(nsHttpAtom const& self, nsHttpAtom const& other) {
+ return self.val() != other.val();
+}
+
+namespace nsHttp {
+
+// Declare all atoms
+//
+// The atom names and values are stored in nsHttpAtomList.h and are brought
+// to you by the magic of C preprocessing. Add new atoms to nsHttpAtomList
+// and all support logic will be auto-generated.
+//
+#define HTTP_ATOM(_name, _value) \
+ inline constexpr nsHttpAtomLiteral _name(_value);
+#include "nsHttpAtomList.h"
+#undef HTTP_ATOM
+} // namespace nsHttp
+
+//-----------------------------------------------------------------------------
+// utilities...
+//-----------------------------------------------------------------------------
+
+static inline uint32_t PRTimeToSeconds(PRTime t_usec) {
+ return uint32_t(t_usec / PR_USEC_PER_SEC);
+}
+
+#define NowInSeconds() PRTimeToSeconds(PR_Now())
+
+// Round q-value to 2 decimal places; return 2 most significant digits as uint.
+#define QVAL_TO_UINT(q) ((unsigned int)(((q) + 0.005) * 100.0))
+
+#define HTTP_LWS " \t"
+#define HTTP_HEADER_VALUE_SEPS HTTP_LWS ","
+
+void EnsureBuffer(UniquePtr<char[]>& buf, uint32_t newSize, uint32_t preserve,
+ uint32_t& objSize);
+void EnsureBuffer(UniquePtr<uint8_t[]>& buf, uint32_t newSize,
+ uint32_t preserve, uint32_t& objSize);
+
+// h2=":443"; ma=60; single
+// results in 3 mValues = {{h2, :443}, {ma, 60}, {single}}
+
+class ParsedHeaderPair {
+ public:
+ ParsedHeaderPair(const char* name, int32_t nameLen, const char* val,
+ int32_t valLen, bool isQuotedValue);
+
+ ParsedHeaderPair(ParsedHeaderPair const& copy)
+ : mName(copy.mName),
+ mValue(copy.mValue),
+ mUnquotedValue(copy.mUnquotedValue),
+ mIsQuotedValue(copy.mIsQuotedValue) {
+ if (mIsQuotedValue) {
+ mValue.Rebind(mUnquotedValue.BeginReading(), mUnquotedValue.Length());
+ }
+ }
+
+ nsDependentCSubstring mName;
+ nsDependentCSubstring mValue;
+
+ private:
+ nsCString mUnquotedValue;
+ bool mIsQuotedValue;
+
+ void RemoveQuotedStringEscapes(const char* val, int32_t valLen);
+};
+
+class ParsedHeaderValueList {
+ public:
+ ParsedHeaderValueList(const char* t, uint32_t len, bool allowInvalidValue);
+ nsTArray<ParsedHeaderPair> mValues;
+
+ private:
+ void ParseNameAndValue(const char* input, bool allowInvalidValue);
+};
+
+class ParsedHeaderValueListList {
+ public:
+ // RFC 7231 section 3.2.6 defines the syntax of the header field values.
+ // |allowInvalidValue| indicates whether the rule will be used to check
+ // the input text.
+ // Note that ParsedHeaderValueListList is currently used to parse
+ // Alt-Svc and Server-Timing header. |allowInvalidValue| is set to true
+ // when parsing Alt-Svc for historical reasons.
+ explicit ParsedHeaderValueListList(const nsCString& fullHeader,
+ bool allowInvalidValue = true);
+ nsTArray<ParsedHeaderValueList> mValues;
+
+ private:
+ nsCString mFull;
+};
+
+void LogHeaders(const char* lineStart);
+
+// Convert HTTP response codes returned by a proxy to nsresult.
+// This function should be only used when we get a failed response to the
+// CONNECT method.
+nsresult HttpProxyResponseToErrorCode(uint32_t aStatusCode);
+
+// Convert an alpn string to SupportedAlpnType.
+SupportedAlpnRank IsAlpnSupported(const nsACString& aAlpn);
+
+static inline bool AllowedErrorForHTTPSRRFallback(nsresult aError) {
+ return psm::IsNSSErrorCode(-1 * NS_ERROR_GET_CODE(aError)) ||
+ aError == NS_ERROR_NET_RESET ||
+ aError == NS_ERROR_CONNECTION_REFUSED ||
+ aError == NS_ERROR_UNKNOWN_HOST || aError == NS_ERROR_NET_TIMEOUT;
+}
+
+bool SecurityErrorThatMayNeedRestart(nsresult aReason);
+
+[[nodiscard]] nsresult MakeOriginURL(const nsACString& origin,
+ nsCOMPtr<nsIURI>& url);
+
+[[nodiscard]] nsresult MakeOriginURL(const nsACString& scheme,
+ const nsACString& origin,
+ nsCOMPtr<nsIURI>& url);
+
+void CreatePushHashKey(const nsCString& scheme, const nsCString& hostHeader,
+ const mozilla::OriginAttributes& originAttributes,
+ uint64_t serial, const nsACString& pathInfo,
+ nsCString& outOrigin, nsCString& outKey);
+
+nsresult GetNSResultFromWebTransportError(uint8_t aErrorCode);
+
+uint8_t GetWebTransportErrorFromNSResult(nsresult aResult);
+
+uint64_t WebTransportErrorToHttp3Error(uint8_t aErrorCode);
+
+uint8_t Http3ErrorToWebTransportError(uint64_t aErrorCode);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttp_h__
diff --git a/netwerk/protocol/http/nsHttpActivityDistributor.cpp b/netwerk/protocol/http/nsHttpActivityDistributor.cpp
new file mode 100644
index 0000000000..b432bb5ea8
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpActivityDistributor.cpp
@@ -0,0 +1,298 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "nsHttpActivityDistributor.h"
+#include "nsHttpHandler.h"
+#include "nsCOMPtr.h"
+#include "nsIOService.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsThreadUtils.h"
+#include "NullHttpChannel.h"
+
+namespace mozilla {
+namespace net {
+
+using ObserverHolder = nsMainThreadPtrHolder<nsIHttpActivityObserver>;
+using ObserverHandle = nsMainThreadPtrHandle<nsIHttpActivityObserver>;
+
+NS_IMPL_ISUPPORTS(nsHttpActivityDistributor, nsIHttpActivityDistributor,
+ nsIHttpActivityObserver)
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::ObserveActivity(nsISupports* aHttpChannel,
+ uint32_t aActivityType,
+ uint32_t aActivitySubtype,
+ PRTime aTimestamp,
+ uint64_t aExtraSizeData,
+ const nsACString& aExtraStringData) {
+ MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
+
+ for (size_t i = 0; i < mObservers.Length(); i++) {
+ Unused << mObservers[i]->ObserveActivity(aHttpChannel, aActivityType,
+ aActivitySubtype, aTimestamp,
+ aExtraSizeData, aExtraStringData);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::ObserveConnectionActivity(
+ const nsACString& aHost, int32_t aPort, bool aSSL, bool aHasECH,
+ bool aIsHttp3, uint32_t aActivityType, uint32_t aActivitySubtype,
+ PRTime aTimestamp, const nsACString& aExtraStringData) {
+ MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
+
+ for (size_t i = 0; i < mObservers.Length(); i++) {
+ Unused << mObservers[i]->ObserveConnectionActivity(
+ aHost, aPort, aSSL, aHasECH, aIsHttp3, aActivityType, aActivitySubtype,
+ aTimestamp, aExtraStringData);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::ObserveActivityWithArgs(
+ const HttpActivityArgs& aArgs, uint32_t aActivityType,
+ uint32_t aActivitySubtype, PRTime aTimestamp, uint64_t aExtraSizeData,
+ const nsACString& aExtraStringData) {
+ HttpActivityArgs args(aArgs);
+ nsCString extraStringData(aExtraStringData);
+ if (XRE_IsSocketProcess()) {
+ auto task = [args{std::move(args)}, aActivityType, aActivitySubtype,
+ aTimestamp, aExtraSizeData,
+ extraStringData{std::move(extraStringData)}]() {
+ SocketProcessChild::GetSingleton()->SendObserveHttpActivity(
+ args, aActivityType, aActivitySubtype, aTimestamp, aExtraSizeData,
+ extraStringData);
+ };
+
+ if (!NS_IsMainThread()) {
+ return NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::nsHttpActivityDistributor::ObserveActivityWithArgs", task));
+ }
+
+ task();
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<nsHttpActivityDistributor> self = this;
+ auto task = [args{std::move(args)}, aActivityType, aActivitySubtype,
+ aTimestamp, aExtraSizeData,
+ extraStringData{std::move(extraStringData)},
+ self{std::move(self)}]() {
+ if (args.type() == HttpActivityArgs::Tuint64_t) {
+ nsWeakPtr weakPtr = gHttpHandler->GetWeakHttpChannel(args.get_uint64_t());
+ if (nsCOMPtr<nsIHttpChannel> channel = do_QueryReferent(weakPtr)) {
+ Unused << self->ObserveActivity(channel, aActivityType,
+ aActivitySubtype, aTimestamp,
+ aExtraSizeData, extraStringData);
+ }
+ } else if (args.type() == HttpActivityArgs::THttpActivity) {
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString portStr(""_ns);
+ int32_t port = args.get_HttpActivity().port();
+ bool endToEndSSL = args.get_HttpActivity().endToEndSSL();
+ if (port != -1 &&
+ ((endToEndSSL && port != 443) || (!endToEndSSL && port != 80))) {
+ portStr.AppendInt(port);
+ }
+
+ nsresult rv = NS_NewURI(getter_AddRefs(uri),
+ (endToEndSSL ? "https://"_ns : "http://"_ns) +
+ args.get_HttpActivity().host() + portStr);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ RefPtr<NullHttpChannel> channel = new NullHttpChannel();
+ rv = channel->Init(uri, 0, nullptr, 0, nullptr);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ Unused << self->ObserveActivity(
+ static_cast<nsIChannel*>(channel), aActivityType, aActivitySubtype,
+ aTimestamp, aExtraSizeData, extraStringData);
+ } else if (args.type() == HttpActivityArgs::THttpConnectionActivity) {
+ const HttpConnectionActivity& activity =
+ args.get_HttpConnectionActivity();
+ Unused << self->ObserveConnectionActivity(
+ activity.host(), activity.port(), activity.ssl(), activity.hasECH(),
+ activity.isHttp3(), aActivityType, aActivitySubtype, aTimestamp,
+ activity.connInfoKey());
+ }
+ };
+
+ if (!NS_IsMainThread()) {
+ return NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::nsHttpActivityDistributor::ObserveActivityWithArgs", task));
+ }
+
+ task();
+ return NS_OK;
+}
+
+bool nsHttpActivityDistributor::Activated() { return mActivated; }
+
+bool nsHttpActivityDistributor::ObserveProxyResponseEnabled() {
+ return mObserveProxyResponse;
+}
+
+bool nsHttpActivityDistributor::ObserveConnectionEnabled() {
+ return mObserveConnection;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::GetIsActive(bool* isActive) {
+ NS_ENSURE_ARG_POINTER(isActive);
+ if (XRE_IsSocketProcess()) {
+ *isActive = mActivated;
+ return NS_OK;
+ }
+
+ MutexAutoLock lock(mLock);
+ *isActive = mActivated = !!mObservers.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpActivityDistributor::SetIsActive(bool aActived) {
+ MOZ_RELEASE_ASSERT(XRE_IsSocketProcess());
+
+ mActivated = aActived;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::AddObserver(nsIHttpActivityObserver* aObserver) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!NS_IsMainThread()) {
+ // We only support calling this from the main thread.
+ return NS_ERROR_FAILURE;
+ }
+
+ ObserverHandle observer(
+ new ObserverHolder("nsIHttpActivityObserver", aObserver));
+
+ bool wasEmpty = false;
+ {
+ MutexAutoLock lock(mLock);
+ wasEmpty = mObservers.IsEmpty();
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ mObservers.AppendElement(observer);
+ }
+
+ if (wasEmpty) {
+ mActivated = true;
+ if (nsIOService::UseSocketProcess()) {
+ auto task = []() {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (parent && parent->CanSend()) {
+ Unused << parent->SendOnHttpActivityDistributorActivated(true);
+ }
+ };
+ gIOService->CallOrWaitForSocketProcess(task);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::RemoveObserver(nsIHttpActivityObserver* aObserver) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!NS_IsMainThread()) {
+ // We only support calling this from the main thread.
+ return NS_ERROR_FAILURE;
+ }
+
+ ObserverHandle observer(
+ new ObserverHolder("nsIHttpActivityObserver", aObserver));
+
+ {
+ MutexAutoLock lock(mLock);
+ if (!mObservers.RemoveElement(observer)) {
+ return NS_ERROR_FAILURE;
+ }
+ mActivated = mObservers.IsEmpty();
+ }
+
+ if (nsIOService::UseSocketProcess() && !mActivated) {
+ auto task = []() {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (parent && parent->CanSend()) {
+ Unused << parent->SendOnHttpActivityDistributorActivated(false);
+ }
+ };
+ gIOService->CallOrWaitForSocketProcess(task);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::GetObserveProxyResponse(
+ bool* aObserveProxyResponse) {
+ NS_ENSURE_ARG_POINTER(aObserveProxyResponse);
+ bool result = mObserveProxyResponse;
+ *aObserveProxyResponse = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::SetObserveProxyResponse(bool aObserveProxyResponse) {
+ if (!NS_IsMainThread()) {
+ // We only support calling this from the main thread.
+ return NS_ERROR_FAILURE;
+ }
+
+ mObserveProxyResponse = aObserveProxyResponse;
+ if (nsIOService::UseSocketProcess()) {
+ auto task = [aObserveProxyResponse]() {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (parent && parent->CanSend()) {
+ Unused << parent->SendOnHttpActivityDistributorObserveProxyResponse(
+ aObserveProxyResponse);
+ }
+ };
+ gIOService->CallOrWaitForSocketProcess(task);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::GetObserveConnection(bool* aObserveConnection) {
+ NS_ENSURE_ARG_POINTER(aObserveConnection);
+
+ *aObserveConnection = mObserveConnection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::SetObserveConnection(bool aObserveConnection) {
+ if (!NS_IsMainThread()) {
+ // We only support calling this from the main thread.
+ return NS_ERROR_FAILURE;
+ }
+
+ mObserveConnection = aObserveConnection;
+ if (nsIOService::UseSocketProcess()) {
+ auto task = [aObserveConnection]() {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (parent && parent->CanSend()) {
+ Unused << parent->SendOnHttpActivityDistributorObserveConnection(
+ aObserveConnection);
+ }
+ };
+ gIOService->CallOrWaitForSocketProcess(task);
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpActivityDistributor.h b/netwerk/protocol/http/nsHttpActivityDistributor.h
new file mode 100644
index 0000000000..b2d9ee20a4
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpActivityDistributor.h
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpActivityDistributor_h__
+#define nsHttpActivityDistributor_h__
+
+#include "nsIHttpActivityObserver.h"
+#include "nsTArray.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+class nsHttpActivityDistributor : public nsIHttpActivityDistributor {
+ public:
+ using ObserverArray =
+ nsTArray<nsMainThreadPtrHandle<nsIHttpActivityObserver>>;
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIHTTPACTIVITYOBSERVER
+ NS_DECL_NSIHTTPACTIVITYDISTRIBUTOR
+
+ nsHttpActivityDistributor() = default;
+
+ protected:
+ virtual ~nsHttpActivityDistributor() = default;
+
+ ObserverArray mObservers;
+ Mutex mLock MOZ_UNANNOTATED{"nsHttpActivityDistributor.mLock"};
+ Atomic<bool, Relaxed> mActivated{false};
+ Atomic<bool, Relaxed> mObserveProxyResponse{false};
+ Atomic<bool, Relaxed> mObserveConnection{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpActivityDistributor_h__
diff --git a/netwerk/protocol/http/nsHttpAtomList.h b/netwerk/protocol/http/nsHttpAtomList.h
new file mode 100644
index 0000000000..02e51e5ab1
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAtomList.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/******
+ This file contains the list of all HTTP atoms
+ See nsHttp.h for access to the atoms.
+
+ It is designed to be used as inline input to nsHttp.cpp *only*
+ through the magic of C preprocessing.
+
+ All entries must be enclosed in the macro HTTP_ATOM which will have cruel
+ and unusual things done to it.
+
+ The first argument to HTTP_ATOM is the C++ name of the atom.
+ The second argument to HTTP_ATOM is the string value of the atom.
+ ******/
+
+HTTP_ATOM(Accept, "Accept")
+HTTP_ATOM(Accept_Encoding, "Accept-Encoding")
+HTTP_ATOM(Accept_Language, "Accept-Language")
+HTTP_ATOM(Accept_Ranges, "Accept-Ranges")
+HTTP_ATOM(Access_Control_Allow_Origin, "Access-Control-Allow-Origin")
+HTTP_ATOM(Age, "Age")
+HTTP_ATOM(Allow, "Allow")
+HTTP_ATOM(Alternate_Service, "Alt-Svc")
+HTTP_ATOM(Alternate_Service_Used, "Alt-Used")
+HTTP_ATOM(Assoc_Req, "Assoc-Req")
+HTTP_ATOM(Authentication, "Authentication")
+HTTP_ATOM(Authorization, "Authorization")
+HTTP_ATOM(Cache_Control, "Cache-Control")
+HTTP_ATOM(Connection, "Connection")
+HTTP_ATOM(Content_Disposition, "Content-Disposition")
+HTTP_ATOM(Content_Encoding, "Content-Encoding")
+HTTP_ATOM(Content_Language, "Content-Language")
+HTTP_ATOM(Content_Length, "Content-Length")
+HTTP_ATOM(Content_Location, "Content-Location")
+HTTP_ATOM(Content_MD5, "Content-MD5")
+HTTP_ATOM(Content_Range, "Content-Range")
+HTTP_ATOM(Content_Security_Policy, "Content-Security-Policy")
+HTTP_ATOM(Content_Type, "Content-Type")
+HTTP_ATOM(Cookie, "Cookie")
+HTTP_ATOM(Cross_Origin_Embedder_Policy, "Cross-Origin-Embedder-Policy")
+HTTP_ATOM(Cross_Origin_Opener_Policy, "Cross-Origin-Opener-Policy")
+HTTP_ATOM(Cross_Origin_Resource_Policy, "Cross-Origin-Resource-Policy")
+HTTP_ATOM(Date, "Date")
+HTTP_ATOM(DAV, "DAV")
+HTTP_ATOM(Depth, "Depth")
+HTTP_ATOM(Destination, "Destination")
+HTTP_ATOM(DoNotTrack, "DNT")
+HTTP_ATOM(ETag, "Etag")
+HTTP_ATOM(Expect, "Expect")
+HTTP_ATOM(Expires, "Expires")
+HTTP_ATOM(From, "From")
+HTTP_ATOM(GlobalPrivacyControl, "Sec-GPC")
+HTTP_ATOM(Host, "Host")
+HTTP_ATOM(If, "If")
+HTTP_ATOM(If_Match, "If-Match")
+HTTP_ATOM(If_Modified_Since, "If-Modified-Since")
+HTTP_ATOM(If_None_Match, "If-None-Match")
+HTTP_ATOM(If_None_Match_Any, "If-None-Match-Any")
+HTTP_ATOM(If_Range, "If-Range")
+HTTP_ATOM(If_Unmodified_Since, "If-Unmodified-Since")
+HTTP_ATOM(Keep_Alive, "Keep-Alive")
+HTTP_ATOM(Last_Modified, "Last-Modified")
+HTTP_ATOM(Lock_Token, "Lock-Token")
+HTTP_ATOM(Link, "Link")
+HTTP_ATOM(Location, "Location")
+HTTP_ATOM(Max_Forwards, "Max-Forwards")
+HTTP_ATOM(Origin, "Origin")
+HTTP_ATOM(OriginTrial, "Origin-Trial")
+HTTP_ATOM(Overwrite, "Overwrite")
+HTTP_ATOM(Pragma, "Pragma")
+HTTP_ATOM(Prefer, "Prefer")
+HTTP_ATOM(Proxy_Authenticate, "Proxy-Authenticate")
+HTTP_ATOM(Proxy_Authorization, "Proxy-Authorization")
+HTTP_ATOM(Proxy_Connection, "Proxy-Connection")
+HTTP_ATOM(Range, "Range")
+HTTP_ATOM(Referer, "Referer")
+HTTP_ATOM(Referrer_Policy, "Referrer-Policy")
+HTTP_ATOM(Retry_After, "Retry-After")
+HTTP_ATOM(Sec_WebSocket_Extensions, "Sec-WebSocket-Extensions")
+HTTP_ATOM(Sec_WebSocket_Protocol, "Sec-WebSocket-Protocol")
+HTTP_ATOM(Sec_WebSocket_Version, "Sec-WebSocket-Version")
+HTTP_ATOM(Server, "Server")
+HTTP_ATOM(Server_Timing, "Server-Timing")
+HTTP_ATOM(Service_Worker_Allowed, "Service-Worker-Allowed")
+HTTP_ATOM(Set_Cookie, "Set-Cookie")
+HTTP_ATOM(Status_URI, "Status-URI")
+HTTP_ATOM(Strict_Transport_Security, "Strict-Transport-Security")
+HTTP_ATOM(TE, "TE")
+HTTP_ATOM(Title, "Title")
+HTTP_ATOM(Timeout, "Timeout")
+HTTP_ATOM(Trailer, "Trailer")
+HTTP_ATOM(Transfer_Encoding, "Transfer-Encoding")
+HTTP_ATOM(URI, "URI")
+HTTP_ATOM(Upgrade, "Upgrade")
+HTTP_ATOM(User_Agent, "User-Agent")
+HTTP_ATOM(Vary, "Vary")
+HTTP_ATOM(Version, "Version")
+HTTP_ATOM(WWW_Authenticate, "WWW-Authenticate")
+HTTP_ATOM(Warning, "Warning")
+HTTP_ATOM(X_Content_Type_Options, "X-Content-Type-Options")
+HTTP_ATOM(X_Firefox_Spdy, "X-Firefox-Spdy")
+HTTP_ATOM(X_Firefox_Spdy_Proxy, "X-Firefox-Spdy-Proxy")
+HTTP_ATOM(X_Firefox_Early_Data, "X-Firefox-Early-Data")
+HTTP_ATOM(X_Firefox_Http3, "X-Firefox-Http3")
+HTTP_ATOM(X_Frame_Options, "X-Frame-Options")
+
+// methods are case sensitive and do not use atom table
diff --git a/netwerk/protocol/http/nsHttpAuthCache.cpp b/netwerk/protocol/http/nsHttpAuthCache.cpp
new file mode 100644
index 0000000000..6b2eb81d9e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthCache.cpp
@@ -0,0 +1,349 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpAuthCache.h"
+
+#include <algorithm>
+#include <stdlib.h>
+
+#include "mozilla/Attributes.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/DebugOnly.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+static inline void GetAuthKey(const nsACString& scheme, const nsACString& host,
+ int32_t port, nsACString const& originSuffix,
+ nsCString& key) {
+ key.Truncate();
+ key.Append(originSuffix);
+ key.Append(':');
+ key.Append(scheme);
+ key.AppendLiteral("://");
+ key.Append(host);
+ key.Append(':');
+ key.AppendInt(port);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthCache <public>
+//-----------------------------------------------------------------------------
+
+nsHttpAuthCache::nsHttpAuthCache()
+ : mDB(128), mObserver(new OriginClearObserver(this)) {
+ LOG(("nsHttpAuthCache::nsHttpAuthCache %p", this));
+
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->AddObserver(mObserver, "clear-origin-attributes-data", false);
+ }
+}
+
+nsHttpAuthCache::~nsHttpAuthCache() {
+ LOG(("nsHttpAuthCache::~nsHttpAuthCache %p", this));
+
+ ClearAll();
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(mObserver, "clear-origin-attributes-data");
+ mObserver->mOwner = nullptr;
+ }
+}
+
+nsresult nsHttpAuthCache::GetAuthEntryForPath(const nsACString& scheme,
+ const nsACString& host,
+ int32_t port,
+ const nsACString& path,
+ nsACString const& originSuffix,
+ nsHttpAuthEntry** entry) {
+ LOG(("nsHttpAuthCache::GetAuthEntryForPath %p [path=%s]\n", this,
+ path.BeginReading()));
+
+ nsAutoCString key;
+ nsHttpAuthNode* node = LookupAuthNode(scheme, host, port, originSuffix, key);
+ if (!node) return NS_ERROR_NOT_AVAILABLE;
+
+ *entry = node->LookupEntryByPath(path);
+ LOG((" returning %p", *entry));
+ return *entry ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsHttpAuthCache::GetAuthEntryForDomain(const nsACString& scheme,
+ const nsACString& host,
+ int32_t port,
+ const nsACString& realm,
+ nsACString const& originSuffix,
+ nsHttpAuthEntry** entry)
+
+{
+ LOG(("nsHttpAuthCache::GetAuthEntryForDomain %p [realm=%s]\n", this,
+ realm.BeginReading()));
+
+ nsAutoCString key;
+ nsHttpAuthNode* node = LookupAuthNode(scheme, host, port, originSuffix, key);
+ if (!node) return NS_ERROR_NOT_AVAILABLE;
+
+ *entry = node->LookupEntryByRealm(realm);
+ LOG((" returning %p", *entry));
+ return *entry ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsHttpAuthCache::SetAuthEntry(
+ const nsACString& scheme, const nsACString& host, int32_t port,
+ const nsACString& path, const nsACString& realm, const nsACString& creds,
+ const nsACString& challenge, nsACString const& originSuffix,
+ const nsHttpAuthIdentity* ident, nsISupports* metadata) {
+ nsresult rv;
+
+ LOG(("nsHttpAuthCache::SetAuthEntry %p [realm=%s path=%s metadata=%p]\n",
+ this, realm.BeginReading(), path.BeginReading(), metadata));
+
+ nsAutoCString key;
+ nsHttpAuthNode* node = LookupAuthNode(scheme, host, port, originSuffix, key);
+
+ if (!node) {
+ // create a new entry node and set the given entry
+ auto node = UniquePtr<nsHttpAuthNode>(new nsHttpAuthNode);
+ LOG((" new nsHttpAuthNode %p for key='%s'", node.get(), key.get()));
+ rv = node->SetAuthEntry(path, realm, creds, challenge, ident, metadata);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mDB.InsertOrUpdate(key, std::move(node));
+ return NS_OK;
+ }
+
+ return node->SetAuthEntry(path, realm, creds, challenge, ident, metadata);
+}
+
+void nsHttpAuthCache::ClearAuthEntry(const nsACString& scheme,
+ const nsACString& host, int32_t port,
+ const nsACString& realm,
+ nsACString const& originSuffix) {
+ nsAutoCString key;
+ GetAuthKey(scheme, host, port, originSuffix, key);
+ LOG(("nsHttpAuthCache::ClearAuthEntry %p key='%s'\n", this, key.get()));
+ mDB.Remove(key);
+}
+
+void nsHttpAuthCache::ClearAll() {
+ LOG(("nsHttpAuthCache::ClearAll %p\n", this));
+ mDB.Clear();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthCache <private>
+//-----------------------------------------------------------------------------
+
+nsHttpAuthNode* nsHttpAuthCache::LookupAuthNode(const nsACString& scheme,
+ const nsACString& host,
+ int32_t port,
+ nsACString const& originSuffix,
+ nsCString& key) {
+ GetAuthKey(scheme, host, port, originSuffix, key);
+ nsHttpAuthNode* result = mDB.Get(key);
+
+ LOG(("nsHttpAuthCache::LookupAuthNode %p key='%s' found node=%p", this,
+ key.get(), result));
+ return result;
+}
+
+NS_IMPL_ISUPPORTS(nsHttpAuthCache::OriginClearObserver, nsIObserver)
+
+NS_IMETHODIMP
+nsHttpAuthCache::OriginClearObserver::Observe(nsISupports* subject,
+ const char* topic,
+ const char16_t* data_unicode) {
+ NS_ENSURE_TRUE(mOwner, NS_ERROR_NOT_AVAILABLE);
+
+ OriginAttributesPattern pattern;
+ if (!pattern.Init(nsDependentString(data_unicode))) {
+ NS_ERROR("Cannot parse origin attributes pattern");
+ return NS_ERROR_FAILURE;
+ }
+
+ mOwner->ClearOriginData(pattern);
+ return NS_OK;
+}
+
+void nsHttpAuthCache::ClearOriginData(OriginAttributesPattern const& pattern) {
+ LOG(("nsHttpAuthCache::ClearOriginData %p", this));
+
+ for (auto iter = mDB.Iter(); !iter.Done(); iter.Next()) {
+ const nsACString& key = iter.Key();
+
+ // Extract the origin attributes suffix from the key.
+ int32_t colon = key.FindChar(':');
+ MOZ_ASSERT(colon != kNotFound);
+ nsDependentCSubstring oaSuffix = StringHead(key, colon);
+
+ // Build the OriginAttributes object of it...
+ OriginAttributes oa;
+ DebugOnly<bool> rv = oa.PopulateFromSuffix(oaSuffix);
+ MOZ_ASSERT(rv);
+
+ // ...and match it against the given pattern.
+ if (pattern.Matches(oa)) {
+ iter.Remove();
+ }
+ }
+}
+
+void nsHttpAuthCache::CollectKeys(nsTArray<nsCString>& aValue) {
+ AppendToArray(aValue, mDB.Keys());
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthIdentity
+//-----------------------------------------------------------------------------
+
+void nsHttpAuthIdentity::Clear() {
+ mUser.Truncate();
+ mPass.Truncate();
+ mDomain.Truncate();
+}
+
+bool nsHttpAuthIdentity::Equals(const nsHttpAuthIdentity& ident) const {
+ // we could probably optimize this with a single loop, but why bother?
+ return mUser == ident.mUser && mPass == ident.mPass &&
+ mDomain == ident.mDomain;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthEntry
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpAuthEntry::AddPath(const nsACString& aPath) {
+ for (const auto& p : mPaths) {
+ if (StringBeginsWith(aPath, p)) {
+ return NS_OK; // subpath already exists in the list
+ }
+ }
+
+ mPaths.AppendElement(aPath);
+ return NS_OK;
+}
+
+nsresult nsHttpAuthEntry::Set(const nsACString& path, const nsACString& realm,
+ const nsACString& creds, const nsACString& chall,
+ const nsHttpAuthIdentity* ident,
+ nsISupports* metadata) {
+ if (ident) {
+ mIdent = *ident;
+ } else if (mIdent.IsEmpty()) {
+ // If we are not given an identity and our cached identity has not been
+ // initialized yet (so is currently empty), initialize it now by
+ // filling it with nulls. We need to do that because consumers expect
+ // that mIdent is initialized after this function returns.
+ mIdent.Clear();
+ }
+
+ nsresult rv = AddPath(path);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mRealm = realm;
+ mCreds = creds;
+ mChallenge = chall;
+ mMetaData = metadata;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthNode
+//-----------------------------------------------------------------------------
+
+nsHttpAuthNode::nsHttpAuthNode() {
+ LOG(("Creating nsHttpAuthNode @%p\n", this));
+}
+
+nsHttpAuthNode::~nsHttpAuthNode() {
+ LOG(("Destroying nsHttpAuthNode @%p\n", this));
+
+ mList.Clear();
+}
+
+nsHttpAuthEntry* nsHttpAuthNode::LookupEntryByPath(const nsACString& aPath) {
+ // look for an entry that either matches or contains this directory.
+ // ie. we'll give out credentials if the given directory is a sub-
+ // directory of an existing entry.
+ for (uint32_t i = 0; i < mList.Length(); ++i) {
+ const auto& entry = mList[i];
+
+ for (const auto& entryPath : entry->mPaths) {
+ // proxy auth entries have no path, so require exact match on
+ // empty path string.
+ if (entryPath.IsEmpty()) {
+ if (aPath.IsEmpty()) {
+ return entry.get();
+ }
+ } else if (StringBeginsWith(aPath, entryPath)) {
+ return entry.get();
+ }
+ }
+ }
+ return nullptr;
+}
+
+nsHttpAuthNode::EntryList::const_iterator nsHttpAuthNode::LookupEntryItrByRealm(
+ const nsACString& realm) const {
+ return std::find_if(mList.cbegin(), mList.cend(), [&realm](const auto& val) {
+ return realm.Equals(val->Realm());
+ });
+}
+
+nsHttpAuthEntry* nsHttpAuthNode::LookupEntryByRealm(const nsACString& realm) {
+ auto itr = LookupEntryItrByRealm(realm);
+ if (itr != mList.cend()) {
+ return itr->get();
+ }
+
+ return nullptr;
+}
+
+nsresult nsHttpAuthNode::SetAuthEntry(const nsACString& path,
+ const nsACString& realm,
+ const nsACString& creds,
+ const nsACString& challenge,
+ const nsHttpAuthIdentity* ident,
+ nsISupports* metadata) {
+ // look for an entry with a matching realm
+ nsHttpAuthEntry* entry = LookupEntryByRealm(realm);
+ if (!entry) {
+ // We want the latest identity be at the begining of the list so that
+ // the newest working credentials are sent first on new requests.
+ // Changing a realm is sometimes used to "timeout" authrozization.
+ mList.InsertElementAt(
+ 0, WrapUnique(new nsHttpAuthEntry(path, realm, creds, challenge, ident,
+ metadata)));
+ } else {
+ // update the entry...
+ nsresult rv = entry->Set(path, realm, creds, challenge, ident, metadata);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+void nsHttpAuthNode::ClearAuthEntry(const nsACString& realm) {
+ auto idx = LookupEntryItrByRealm(realm);
+ if (idx != mList.cend()) {
+ mList.RemoveElementAt(idx);
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpAuthCache.h b/netwerk/protocol/http/nsHttpAuthCache.h
new file mode 100644
index 0000000000..66bcfb36e2
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthCache.h
@@ -0,0 +1,219 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpAuthCache_h__
+#define nsHttpAuthCache_h__
+
+#include "nsError.h"
+#include "nsTArray.h"
+#include "nsClassHashtable.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsStringFwd.h"
+#include "nsIObserver.h"
+
+namespace mozilla {
+
+class OriginAttributesPattern;
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthIdentity
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthIdentity {
+ public:
+ nsHttpAuthIdentity() = default;
+ nsHttpAuthIdentity(const nsAString& domain, const nsAString& user,
+ const nsAString& password)
+ : mUser(user), mPass(password), mDomain(domain) {}
+ ~nsHttpAuthIdentity() { Clear(); }
+
+ const nsString& Domain() const { return mDomain; }
+ const nsString& User() const { return mUser; }
+ const nsString& Password() const { return mPass; }
+
+ void Clear();
+
+ bool Equals(const nsHttpAuthIdentity& ident) const;
+ bool IsEmpty() const {
+ return mUser.IsEmpty() && mPass.IsEmpty() && mDomain.IsEmpty();
+ }
+
+ private:
+ nsString mUser;
+ nsString mPass;
+ nsString mDomain;
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthEntry
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthEntry {
+ public:
+ const nsCString& Realm() const { return mRealm; }
+ const nsCString& Creds() const { return mCreds; }
+ const nsCString& Challenge() const { return mChallenge; }
+ const nsString& Domain() const { return mIdent.Domain(); }
+ const nsString& User() const { return mIdent.User(); }
+ const nsString& Pass() const { return mIdent.Password(); }
+
+ const nsHttpAuthIdentity& Identity() const { return mIdent; }
+
+ [[nodiscard]] nsresult AddPath(const nsACString& aPath);
+
+ nsCOMPtr<nsISupports> mMetaData;
+
+ private:
+ nsHttpAuthEntry(const nsACString& path, const nsACString& realm,
+ const nsACString& creds, const nsACString& challenge,
+ const nsHttpAuthIdentity* ident, nsISupports* metadata) {
+ DebugOnly<nsresult> rv =
+ Set(path, realm, creds, challenge, ident, metadata);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ ~nsHttpAuthEntry() = default;
+
+ [[nodiscard]] nsresult Set(const nsACString& path, const nsACString& realm,
+ const nsACString& creds,
+ const nsACString& challenge,
+ const nsHttpAuthIdentity* ident,
+ nsISupports* metadata);
+
+ nsHttpAuthIdentity mIdent;
+
+ nsTArray<nsCString> mPaths;
+
+ nsCString mRealm;
+ nsCString mCreds;
+ nsCString mChallenge;
+
+ friend class nsHttpAuthNode;
+ friend class nsHttpAuthCache;
+ friend class mozilla::DefaultDelete<nsHttpAuthEntry>; // needs to call the
+ // destructor
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthNode
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthNode {
+ private:
+ using EntryList = nsTArray<UniquePtr<nsHttpAuthEntry>>;
+
+ nsHttpAuthNode();
+ ~nsHttpAuthNode();
+
+ // path can be null, in which case we'll search for an entry
+ // with a null path.
+ nsHttpAuthEntry* LookupEntryByPath(const nsACString& path);
+
+ // realm must not be null
+ nsHttpAuthEntry* LookupEntryByRealm(const nsACString& realm);
+ EntryList::const_iterator LookupEntryItrByRealm(
+ const nsACString& realm) const;
+
+ // if a matching entry is found, then credentials will be changed.
+ [[nodiscard]] nsresult SetAuthEntry(const nsACString& path,
+ const nsACString& realm,
+ const nsACString& creds,
+ const nsACString& challenge,
+ const nsHttpAuthIdentity* ident,
+ nsISupports* metadata);
+
+ void ClearAuthEntry(const nsACString& realm);
+
+ uint32_t EntryCount() { return mList.Length(); }
+
+ private:
+ EntryList mList;
+
+ friend class nsHttpAuthCache;
+ friend class mozilla::DefaultDelete<nsHttpAuthNode>; // needs to call the
+ // destructor
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthCache
+// (holds a hash table from host:port to nsHttpAuthNode)
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthCache {
+ public:
+ nsHttpAuthCache();
+ ~nsHttpAuthCache();
+
+ // |scheme|, |host|, and |port| are required
+ // |path| can be null
+ // |entry| is either null or a weak reference
+ [[nodiscard]] nsresult GetAuthEntryForPath(const nsACString& scheme,
+ const nsACString& host,
+ int32_t port,
+ const nsACString& path,
+ nsACString const& originSuffix,
+ nsHttpAuthEntry** entry);
+
+ // |scheme|, |host|, and |port| are required
+ // |realm| must not be null
+ // |entry| is either null or a weak reference
+ [[nodiscard]] nsresult GetAuthEntryForDomain(const nsACString& scheme,
+ const nsACString& host,
+ int32_t port,
+ const nsACString& realm,
+ nsACString const& originSuffix,
+ nsHttpAuthEntry** entry);
+
+ // |scheme|, |host|, and |port| are required
+ // |path| can be null
+ // |realm| must not be null
+ // if |credentials|, |user|, |pass|, and |challenge| are each
+ // null, then the entry is deleted.
+ [[nodiscard]] nsresult SetAuthEntry(
+ const nsACString& scheme, const nsACString& host, int32_t port,
+ const nsACString& path, const nsACString& realm, const nsACString& creds,
+ const nsACString& challenge, nsACString const& originSuffix,
+ const nsHttpAuthIdentity* ident, nsISupports* metadata);
+
+ void ClearAuthEntry(const nsACString& scheme, const nsACString& host,
+ int32_t port, const nsACString& realm,
+ nsACString const& originSuffix);
+
+ // expire all existing auth list entries including proxy auths.
+ void ClearAll();
+
+ // For testing only.
+ void CollectKeys(nsTArray<nsCString>& aValue);
+
+ private:
+ nsHttpAuthNode* LookupAuthNode(const nsACString& scheme,
+ const nsACString& host, int32_t port,
+ nsACString const& originSuffix,
+ nsCString& key);
+
+ class OriginClearObserver : public nsIObserver {
+ virtual ~OriginClearObserver() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ explicit OriginClearObserver(nsHttpAuthCache* aOwner) : mOwner(aOwner) {}
+ nsHttpAuthCache* mOwner;
+ };
+
+ void ClearOriginData(OriginAttributesPattern const& pattern);
+
+ private:
+ using AuthNodeTable = nsClassHashtable<nsCStringHashKey, nsHttpAuthNode>;
+ AuthNodeTable mDB; // "host:port" --> nsHttpAuthNode
+ RefPtr<OriginClearObserver> mObserver;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpAuthCache_h__
diff --git a/netwerk/protocol/http/nsHttpAuthManager.cpp b/netwerk/protocol/http/nsHttpAuthManager.cpp
new file mode 100644
index 0000000000..14c4e46fce
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthManager.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpHandler.h"
+#include "nsHttpAuthManager.h"
+#include "nsNetUtil.h"
+#include "nsIPrincipal.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsHttpAuthManager, nsIHttpAuthManager)
+
+nsresult nsHttpAuthManager::Init() {
+ // get reference to the auth cache. we assume that we will live
+ // as long as gHttpHandler. instantiate it if necessary.
+
+ if (!gHttpHandler) {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ios->GetProtocolHandler("http", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ // maybe someone is overriding our HTTP handler implementation?
+ NS_ENSURE_TRUE(gHttpHandler, NS_ERROR_UNEXPECTED);
+ }
+
+ mAuthCache = gHttpHandler->AuthCache(false);
+ mPrivateAuthCache = gHttpHandler->AuthCache(true);
+ NS_ENSURE_TRUE(mAuthCache, NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(mPrivateAuthCache, NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpAuthManager::GetAuthIdentity(
+ const nsACString& aScheme, const nsACString& aHost, int32_t aPort,
+ const nsACString& aAuthType, const nsACString& aRealm,
+ const nsACString& aPath, nsAString& aUserDomain, nsAString& aUserName,
+ nsAString& aUserPassword, bool aIsPrivate, nsIPrincipal* aPrincipal) {
+ nsHttpAuthCache* auth_cache = aIsPrivate ? mPrivateAuthCache : mAuthCache;
+ nsHttpAuthEntry* entry = nullptr;
+ nsresult rv;
+
+ nsAutoCString originSuffix;
+ if (aPrincipal) {
+ aPrincipal->OriginAttributesRef().CreateSuffix(originSuffix);
+ }
+
+ if (!aPath.IsEmpty()) {
+ rv = auth_cache->GetAuthEntryForPath(aScheme, aHost, aPort, aPath,
+ originSuffix, &entry);
+ } else {
+ rv = auth_cache->GetAuthEntryForDomain(aScheme, aHost, aPort, aRealm,
+ originSuffix, &entry);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+ if (!entry) return NS_ERROR_UNEXPECTED;
+
+ aUserDomain.Assign(entry->Domain());
+ aUserName.Assign(entry->User());
+ aUserPassword.Assign(entry->Pass());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpAuthManager::SetAuthIdentity(
+ const nsACString& aScheme, const nsACString& aHost, int32_t aPort,
+ const nsACString& aAuthType, const nsACString& aRealm,
+ const nsACString& aPath, const nsAString& aUserDomain,
+ const nsAString& aUserName, const nsAString& aUserPassword, bool aIsPrivate,
+ nsIPrincipal* aPrincipal) {
+ nsHttpAuthIdentity ident(aUserDomain, aUserName, aUserPassword);
+
+ nsAutoCString originSuffix;
+ if (aPrincipal) {
+ aPrincipal->OriginAttributesRef().CreateSuffix(originSuffix);
+ }
+
+ nsHttpAuthCache* auth_cache = aIsPrivate ? mPrivateAuthCache : mAuthCache;
+ return auth_cache->SetAuthEntry(aScheme, aHost, aPort, aPath, aRealm,
+ ""_ns, // credentials
+ ""_ns, // challenge
+ originSuffix, &ident,
+ nullptr); // metadata
+}
+
+NS_IMETHODIMP
+nsHttpAuthManager::ClearAll() {
+ mAuthCache->ClearAll();
+ mPrivateAuthCache->ClearAll();
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpAuthManager.h b/netwerk/protocol/http/nsHttpAuthManager.h
new file mode 100644
index 0000000000..b49c4a44e6
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthManager.h
@@ -0,0 +1,34 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpAuthManager_h__
+#define nsHttpAuthManager_h__
+
+#include "nsIHttpAuthManager.h"
+
+namespace mozilla {
+namespace net {
+
+class nsHttpAuthCache;
+
+class nsHttpAuthManager : public nsIHttpAuthManager {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHMANAGER
+
+ nsHttpAuthManager() = default;
+ [[nodiscard]] nsresult Init();
+
+ protected:
+ virtual ~nsHttpAuthManager() = default;
+
+ nsHttpAuthCache* mAuthCache{nullptr};
+ nsHttpAuthCache* mPrivateAuthCache{nullptr};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpAuthManager_h__
diff --git a/netwerk/protocol/http/nsHttpBasicAuth.cpp b/netwerk/protocol/http/nsHttpBasicAuth.cpp
new file mode 100644
index 0000000000..c3de5cb5e8
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpBasicAuth.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpBasicAuth.h"
+#include "nsCRT.h"
+#include "nsString.h"
+#include "mozilla/Base64.h"
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla {
+namespace net {
+
+StaticRefPtr<nsHttpBasicAuth> nsHttpBasicAuth::gSingleton;
+
+already_AddRefed<nsIHttpAuthenticator> nsHttpBasicAuth::GetOrCreate() {
+ nsCOMPtr<nsIHttpAuthenticator> authenticator;
+ if (gSingleton) {
+ authenticator = gSingleton;
+ } else {
+ gSingleton = new nsHttpBasicAuth();
+ ClearOnShutdown(&gSingleton);
+ authenticator = gSingleton;
+ }
+
+ return authenticator.forget();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpBasicAuth::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpBasicAuth, nsIHttpAuthenticator)
+
+//-----------------------------------------------------------------------------
+// nsHttpBasicAuth::nsIHttpAuthenticator
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpBasicAuth::ChallengeReceived(nsIHttpAuthenticableChannel* authChannel,
+ const nsACString& challenge,
+ bool isProxyAuth, nsISupports** sessionState,
+ nsISupports** continuationState,
+ bool* identityInvalid) {
+ // if challenged, then the username:password that was sent must
+ // have been wrong.
+ *identityInvalid = true;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsHttpBasicAuth::GenerateCredentialsAsync(
+ nsIHttpAuthenticableChannel* authChannel,
+ nsIHttpAuthenticatorCallback* aCallback, const nsACString& aChallenge,
+ bool isProxyAuth, const nsAString& domain, const nsAString& username,
+ const nsAString& password, nsISupports* sessionState,
+ nsISupports* continuationState, nsICancelable** aCancellable) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpBasicAuth::GenerateCredentials(
+ nsIHttpAuthenticableChannel* authChannel, const nsACString& aChallenge,
+ bool isProxyAuth, const nsAString& domain, const nsAString& user,
+ const nsAString& password, nsISupports** sessionState,
+ nsISupports** continuationState, uint32_t* aFlags, nsACString& creds) {
+ LOG(("nsHttpBasicAuth::GenerateCredentials [challenge=%s]\n",
+ aChallenge.BeginReading()));
+
+ *aFlags = 0;
+
+ // we only know how to deal with Basic auth for http.
+ bool isBasicAuth = StringBeginsWith(aChallenge, "basic"_ns,
+ nsCaseInsensitiveCStringComparator);
+ NS_ENSURE_TRUE(isBasicAuth, NS_ERROR_UNEXPECTED);
+
+ // we work with UTF-8 around here
+ nsAutoCString userpass;
+ CopyUTF16toUTF8(user, userpass);
+ userpass.Append(':'); // always send a ':' (see bug 129565)
+ AppendUTF16toUTF8(password, userpass);
+
+ nsAutoCString authString{"Basic "_ns};
+ nsresult rv = Base64EncodeAppend(userpass, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ creds = authString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpBasicAuth::GetAuthFlags(uint32_t* flags) {
+ *flags = REQUEST_BASED | REUSABLE_CREDENTIALS | REUSABLE_CHALLENGE;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpBasicAuth.h b/netwerk/protocol/http/nsHttpBasicAuth.h
new file mode 100644
index 0000000000..cac5db2c49
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpBasicAuth.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBasicAuth_h__
+#define nsBasicAuth_h__
+
+#include "nsIHttpAuthenticator.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// The nsHttpBasicAuth class produces HTTP Basic-auth responses for a username/
+// (optional)password pair, BASE64("user:pass").
+//-----------------------------------------------------------------------------
+
+class nsHttpBasicAuth : public nsIHttpAuthenticator {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHENTICATOR
+
+ nsHttpBasicAuth() = default;
+
+ static already_AddRefed<nsIHttpAuthenticator> GetOrCreate();
+
+ private:
+ virtual ~nsHttpBasicAuth() = default;
+
+ static StaticRefPtr<nsHttpBasicAuth> gSingleton;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsHttpBasicAuth_h__
diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp
new file mode 100644
index 0000000000..b3806f761b
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -0,0 +1,10430 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=4 sw=2 sts=2 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include <inttypes.h>
+
+#include "DocumentChannelParent.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsCSPService.h"
+#include "mozilla/StoragePrincipalHelper.h"
+
+#include "nsContentSecurityUtils.h"
+#include "nsHttp.h"
+#include "nsHttpChannel.h"
+#include "nsHttpChannelAuthProvider.h"
+#include "nsHttpHandler.h"
+#include "nsString.h"
+#include "nsICacheStorageService.h"
+#include "nsICacheStorage.h"
+#include "nsICacheEntry.h"
+#include "nsICryptoHash.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsINetworkInterceptController.h"
+#include "nsINSSErrorsService.h"
+#include "nsIStringBundle.h"
+#include "nsIStreamListenerTee.h"
+#include "nsISeekableStream.h"
+#include "nsIProtocolProxyService2.h"
+#include "nsIURLQueryStringStripper.h"
+#include "nsIWebTransport.h"
+#include "nsCRT.h"
+#include "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIURL.h"
+#include "nsIStreamTransportService.h"
+#include "prnetdb.h"
+#include "nsEscape.h"
+#include "nsComponentManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsIOService.h"
+#include "nsDNSPrefetch.h"
+#include "nsChannelClassifier.h"
+#include "nsIRedirectResultListener.h"
+#include "mozilla/TimeStamp.h"
+#include "nsError.h"
+#include "nsPrintfCString.h"
+#include "nsAlgorithm.h"
+#include "nsQueryObject.h"
+#include "nsThreadUtils.h"
+#include "nsIConsoleService.h"
+#include "mozilla/AntiTrackingRedirectHeuristic.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/Components.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "sslt.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsIClassOfService.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIWebProgressListener.h"
+#include "LoadContextInfo.h"
+#include "netCore.h"
+#include "nsHttpTransaction.h"
+#include "nsICancelable.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIPrompt.h"
+#include "nsInputStreamPump.h"
+#include "nsURLHelper.h"
+#include "nsISocketTransport.h"
+#include "nsIStreamConverterService.h"
+#include "nsISiteSecurityService.h"
+#include "nsString.h"
+#include "CacheObserver.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/Telemetry.h"
+#include "AlternateServices.h"
+#include "NetworkMarker.h"
+#include "nsIHttpPushListener.h"
+#include "nsIX509Cert.h"
+#include "ScopedNSSTypes.h"
+#include "nsIDNSRecord.h"
+#include "mozilla/dom/Document.h"
+#include "nsICompressConvStats.h"
+#include "nsCORSListenerProxy.h"
+#include "nsISocketProvider.h"
+#include "mozilla/extensions/StreamFilterParent.h"
+#include "mozilla/net/Predictor.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/NullPrincipal.h"
+#include "CacheControlParser.h"
+#include "nsMixedContentBlocker.h"
+#include "CacheStorageService.h"
+#include "HttpChannelParent.h"
+#include "HttpTransactionParent.h"
+#include "ParentChannelListener.h"
+#include "ThirdPartyUtil.h"
+#include "InterceptedHttpChannel.h"
+#include "../../cache2/CacheFileUtils.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsINetworkLinkService.h"
+#include "mozilla/ContentBlockingAllowList.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/dom/nsHTTPSOnlyStreamListener.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/net/AsyncUrlChannelClassifier.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/OpaqueResponseUtils.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "HttpTrafficAnalyzer.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "js/Conversions.h"
+#include "mozilla/dom/SecFetch.h"
+#include "mozilla/net/TRRService.h"
+#include "nsUnknownDecoder.h"
+#ifdef XP_WIN
+# include "HttpWinUtils.h"
+#endif
+#ifdef FUZZING
+# include "mozilla/StaticPrefs_fuzzing.h"
+#endif
+
+namespace mozilla {
+
+using namespace dom;
+
+namespace net {
+
+namespace {
+
+// True if the local cache should be bypassed when processing a request.
+#define BYPASS_LOCAL_CACHE(loadFlags, isPreferCacheLoadOverBypass) \
+ ((loadFlags) & (nsIRequest::LOAD_BYPASS_CACHE | \
+ nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE) && \
+ !(((loadFlags) & nsIRequest::LOAD_FROM_CACHE) && \
+ (isPreferCacheLoadOverBypass)))
+
+#define RECOVER_FROM_CACHE_FILE_ERROR(result) \
+ ((result) == NS_ERROR_FILE_NOT_FOUND || \
+ (result) == NS_ERROR_FILE_CORRUPTED || (result) == NS_ERROR_OUT_OF_MEMORY)
+
+#define WRONG_RACING_RESPONSE_SOURCE(req) \
+ (mRaceCacheWithNetwork && \
+ (((mFirstResponseSource == RESPONSE_FROM_CACHE) && \
+ ((req) != mCachePump)) || \
+ ((mFirstResponseSource == RESPONSE_FROM_NETWORK) && \
+ ((req) != mTransactionPump))))
+
+static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
+
+void AccumulateCacheHitTelemetry(CacheDisposition hitOrMiss,
+ nsIChannel* aChannel) {
+ nsCString key("UNKNOWN");
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ nsAutoCString contentType;
+ if (NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
+ if (nsContentUtils::IsJavascriptMIMEType(
+ NS_ConvertUTF8toUTF16(contentType))) {
+ key.AssignLiteral("JAVASCRIPT");
+ } else if (StringBeginsWith(contentType, "text/css"_ns) ||
+ (loadInfo && loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_STYLESHEET)) {
+ key.AssignLiteral("STYLESHEET");
+ } else if (StringBeginsWith(contentType, "application/wasm"_ns)) {
+ key.AssignLiteral("WASM");
+ } else if (StringBeginsWith(contentType, "image/"_ns)) {
+ key.AssignLiteral("IMAGE");
+ } else if (StringBeginsWith(contentType, "video/"_ns)) {
+ key.AssignLiteral("MEDIA");
+ } else if (StringBeginsWith(contentType, "audio/"_ns)) {
+ key.AssignLiteral("MEDIA");
+ } else if (!StringBeginsWith(contentType,
+ nsLiteralCString(UNKNOWN_CONTENT_TYPE))) {
+ key.AssignLiteral("OTHER");
+ }
+ }
+
+ Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3 label =
+ Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Unresolved;
+ switch (hitOrMiss) {
+ case kCacheUnresolved:
+ label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Unresolved;
+ break;
+ case kCacheHit:
+ label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Hit;
+ break;
+ case kCacheHitViaReval:
+ label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::HitViaReval;
+ break;
+ case kCacheMissedViaReval:
+ label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::MissedViaReval;
+ break;
+ case kCacheMissed:
+ label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Missed;
+ break;
+ case kCacheUnknown:
+ label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Unknown;
+ break;
+ }
+
+ Telemetry::AccumulateCategoricalKeyed(key, label);
+ Telemetry::AccumulateCategoricalKeyed("ALL"_ns, label);
+}
+
+// Computes and returns a SHA1 hash of the input buffer. The input buffer
+// must be a null-terminated string.
+nsresult Hash(const char* buf, nsACString& hash) {
+ nsresult rv;
+
+ nsCOMPtr<nsICryptoHash> hasher =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Init(nsICryptoHash::SHA1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Update(reinterpret_cast<unsigned const char*>(buf), strlen(buf));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Finish(true, hash);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+} // unnamed namespace
+
+// We only treat 3xx responses as redirects if they have a Location header and
+// the status code is in a whitelist.
+bool nsHttpChannel::WillRedirect(const nsHttpResponseHead& response) {
+ return IsRedirectStatus(response.Status()) &&
+ response.HasHeader(nsHttp::Location);
+}
+
+nsresult StoreAuthorizationMetaData(nsICacheEntry* entry,
+ nsHttpRequestHead* requestHead);
+
+class MOZ_STACK_CLASS AutoRedirectVetoNotifier {
+ public:
+ explicit AutoRedirectVetoNotifier(nsHttpChannel* channel, nsresult& aRv)
+ : mChannel(channel), mRv(aRv) {
+ if (mChannel->LoadHasAutoRedirectVetoNotifier()) {
+ MOZ_CRASH("Nested AutoRedirectVetoNotifier on the stack");
+ mChannel = nullptr;
+ return;
+ }
+
+ mChannel->StoreHasAutoRedirectVetoNotifier(true);
+ }
+ ~AutoRedirectVetoNotifier() { ReportRedirectResult(mRv); }
+ void RedirectSucceeded() { ReportRedirectResult(NS_OK); }
+
+ private:
+ nsHttpChannel* mChannel;
+ bool mCalledReport = false;
+ nsresult& mRv;
+ void ReportRedirectResult(nsresult aRv);
+};
+
+void AutoRedirectVetoNotifier::ReportRedirectResult(nsresult aRv) {
+ if (!mChannel) return;
+
+ if (mCalledReport) {
+ return;
+ }
+ mCalledReport = true;
+
+ mChannel->mRedirectChannel = nullptr;
+
+ if (NS_SUCCEEDED(aRv)) {
+ mChannel->RemoveAsNonTailRequest();
+ }
+
+ nsCOMPtr<nsIRedirectResultListener> vetoHook;
+ NS_QueryNotificationCallbacks(mChannel, NS_GET_IID(nsIRedirectResultListener),
+ getter_AddRefs(vetoHook));
+
+ nsHttpChannel* channel = mChannel;
+ mChannel = nullptr;
+
+ if (vetoHook) vetoHook->OnRedirectResult(aRv);
+
+ // Drop after the notification
+ channel->StoreHasAutoRedirectVetoNotifier(false);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <public>
+//-----------------------------------------------------------------------------
+
+nsHttpChannel::nsHttpChannel() : HttpAsyncAborter<nsHttpChannel>(this) {
+ LOG(("Creating nsHttpChannel [this=%p, nsIChannel=%p]\n", this,
+ static_cast<nsIChannel*>(this)));
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+}
+
+nsHttpChannel::~nsHttpChannel() {
+ LOG(("Destroying nsHttpChannel [this=%p, nsIChannel=%p]\n", this,
+ static_cast<nsIChannel*>(this)));
+
+ if (LOG_ENABLED()) {
+ nsCString webExtension;
+ this->GetPropertyAsACString(u"cancelledByExtension"_ns, webExtension);
+ if (!webExtension.IsEmpty()) {
+ LOG(("channel [%p] cancelled by extension [id=%s]", this,
+ webExtension.get()));
+ }
+ }
+
+ if (mAuthProvider) {
+ DebugOnly<nsresult> rv = mAuthProvider->Disconnect(NS_ERROR_ABORT);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ ReleaseMainThreadOnlyReferences();
+ if (gHttpHandler) {
+ gHttpHandler->RemoveHttpChannel(mChannelId);
+ }
+}
+
+void nsHttpChannel::ReleaseMainThreadOnlyReferences() {
+ if (NS_IsMainThread()) {
+ // Already on main thread, let dtor to
+ // take care of releasing references
+ return;
+ }
+
+ nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
+ arrayToRelease.AppendElement(mAuthProvider.forget());
+ arrayToRelease.AppendElement(mRedirectChannel.forget());
+ arrayToRelease.AppendElement(mPreflightChannel.forget());
+ arrayToRelease.AppendElement(mDNSPrefetch.forget());
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mEarlyHintObserver,
+ "Early hint observer should have been released in ReleaseListeners()");
+ arrayToRelease.AppendElement(mEarlyHintObserver.forget());
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mChannelClassifier,
+ "Channel classifier should have been released in ReleaseListeners()");
+ arrayToRelease.AppendElement(
+ mChannelClassifier.forget().downcast<nsIURIClassifierCallback>());
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mWarningReporter,
+ "Warning reporter should have been released in ReleaseListeners()");
+ arrayToRelease.AppendElement(mWarningReporter.forget());
+
+ NS_DispatchToMainThread(new ProxyReleaseRunnable(std::move(arrayToRelease)));
+}
+
+nsresult nsHttpChannel::Init(nsIURI* uri, uint32_t caps, nsProxyInfo* proxyInfo,
+ uint32_t proxyResolveFlags, nsIURI* proxyURI,
+ uint64_t channelId,
+ ExtContentPolicyType aContentPolicyType,
+ nsILoadInfo* aLoadInfo) {
+ nsresult rv =
+ HttpBaseChannel::Init(uri, caps, proxyInfo, proxyResolveFlags, proxyURI,
+ channelId, aContentPolicyType, aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG1(("nsHttpChannel::Init [this=%p]\n", this));
+
+ return rv;
+}
+
+nsresult nsHttpChannel::AddSecurityMessage(const nsAString& aMessageTag,
+ const nsAString& aMessageCategory) {
+ if (mWarningReporter) {
+ return mWarningReporter->ReportSecurityMessage(aMessageTag,
+ aMessageCategory);
+ }
+ return HttpBaseChannel::AddSecurityMessage(aMessageTag, aMessageCategory);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ if (mWarningReporter) {
+ return mWarningReporter->LogBlockedCORSRequest(aMessage, aCategory,
+ aIsWarning);
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ if (mWarningReporter) {
+ return mWarningReporter->LogMimeTypeMismatch(aMessageName, aWarning, aURL,
+ aContentType);
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <private>
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpChannel::PrepareToConnect() {
+ LOG(("nsHttpChannel::PrepareToConnect [this=%p]\n", this));
+
+ // notify "http-on-modify-request-before-cookies" observers
+ gHttpHandler->OnModifyRequestBeforeCookies(this);
+
+ AddCookiesToRequest();
+
+#ifdef XP_WIN
+
+ auto prefEnabledForCurrentContainer = [&]() {
+ uint32_t containerId = mLoadInfo->GetOriginAttributes().mUserContextId;
+ // Make sure that the default container ID is 0
+ static_assert(nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID == 0);
+ nsPrintfCString prefName("network.http.windows-sso.container-enabled.%u",
+ containerId);
+
+ bool enabled = false;
+ Preferences::GetBool(prefName.get(), &enabled);
+
+ LOG(("Pref for %s is %d\n", prefName.get(), enabled));
+
+ return enabled;
+ };
+
+ // If Windows 10 SSO is enabled, we potentially add auth information to
+ // secure top level loads (DOCUMENTs) and iframes (SUBDOCUMENTs) that
+ // aren't anonymous or private browsing.
+ if (StaticPrefs::network_http_windows_sso_enabled() &&
+ mURI->SchemeIs("https") && !(mLoadFlags & LOAD_ANONYMOUS) &&
+ !mPrivateBrowsing) {
+ ExtContentPolicyType type = mLoadInfo->GetExternalContentPolicyType();
+ if ((type == ExtContentPolicy::TYPE_DOCUMENT ||
+ type == ExtContentPolicy::TYPE_SUBDOCUMENT) &&
+ prefEnabledForCurrentContainer()) {
+ AddWindowsSSO(this);
+ }
+ }
+#endif
+
+ // notify "http-on-modify-request" observers
+ CallOnModifyRequestObservers();
+
+ return CallOrWaitForResume(
+ [](auto* self) { return self->OnBeforeConnect(); });
+}
+
+void nsHttpChannel::HandleContinueCancellingByURLClassifier(
+ nsresult aErrorCode) {
+ MOZ_ASSERT(
+ UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(
+ ("Waiting until resume HandleContinueCancellingByURLClassifier "
+ "[this=%p]\n",
+ this));
+ mCallOnResume = [aErrorCode](nsHttpChannel* self) {
+ self->HandleContinueCancellingByURLClassifier(aErrorCode);
+ return NS_OK;
+ };
+ return;
+ }
+
+ LOG(("nsHttpChannel::HandleContinueCancellingByURLClassifier [this=%p]\n",
+ this));
+ ContinueCancellingByURLClassifier(aErrorCode);
+}
+
+nsresult nsHttpChannel::OnBeforeConnect() {
+ nsresult rv = NS_OK;
+
+ // Check if request was cancelled during suspend AFTER on-modify-request
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ // Check to see if we should redirect this channel elsewhere by
+ // nsIHttpChannel.redirectTo API request
+ if (mAPIRedirectToURI) {
+ return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
+ }
+
+ // Note that we are only setting the "Upgrade-Insecure-Requests" request
+ // header for *all* navigational requests instead of all requests as
+ // defined in the spec, see:
+ // https://www.w3.org/TR/upgrade-insecure-requests/#preference
+ ExtContentPolicyType type = mLoadInfo->GetExternalContentPolicyType();
+
+ if (type == ExtContentPolicy::TYPE_DOCUMENT ||
+ type == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ rv = SetRequestHeader("Upgrade-Insecure-Requests"_ns, "1"_ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (LoadAuthRedirectedChannel()) {
+ // This channel is a result of a redirect due to auth retry
+ // We have already checked for HSTS upgarde in the redirecting channel.
+ // We can safely skip those checks
+ return ContinueOnBeforeConnect(false, rv);
+ }
+
+ SecFetch::AddSecFetchHeader(this);
+
+ // Check to see if we should redirect this channel to the unstripped URI. To
+ // revert the query stripping if the loading channel is in the content
+ // blocking allow list.
+ if (ContentBlockingAllowList::Check(this)) {
+ nsCOMPtr<nsIURI> unstrippedURI;
+ mLoadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI));
+
+ if (unstrippedURI) {
+ return AsyncCall(&nsHttpChannel::HandleAsyncRedirectToUnstrippedURI);
+ }
+ }
+
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ if (!mURI->SchemeIs("https")) {
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(resultPrincipal));
+ }
+
+ // Check if we already know about the HSTS status of the host
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+ bool isSecureURI;
+ OriginAttributes originAttributes;
+ if (!StoragePrincipalHelper::GetOriginAttributesForHSTS(this,
+ originAttributes)) {
+ return NS_ERROR_FAILURE;
+ }
+ rv = sss->IsSecureURI(mURI, originAttributes, &isSecureURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Save that on the loadInfo so it can later be consumed by SecurityInfo.jsm
+ mLoadInfo->SetHstsStatus(isSecureURI);
+
+ RefPtr<mozilla::dom::BrowsingContext> bc;
+ mLoadInfo->GetBrowsingContext(getter_AddRefs(bc));
+ if (bc && bc->Top()->GetForceOffline()) {
+ return NS_ERROR_OFFLINE;
+ }
+
+ // At this point it is no longer possible to call
+ // HttpBaseChannel::UpgradeToSecure.
+ StoreUpgradableToSecure(false);
+ bool shouldUpgrade = LoadUpgradeToSecure();
+ if (mURI->SchemeIs("http")) {
+ OriginAttributes originAttributes;
+ if (!StoragePrincipalHelper::GetOriginAttributesForHSTS(this,
+ originAttributes)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!shouldUpgrade) {
+ // Make sure http channel is released on main thread.
+ // See bug 1539148 for details.
+ nsMainThreadPtrHandle<nsHttpChannel> self(
+ new nsMainThreadPtrHolder<nsHttpChannel>(
+ "nsHttpChannel::OnBeforeConnect::self", this));
+ auto resultCallback = [self(self)](bool aResult, nsresult aStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = self->MaybeUseHTTPSRRForUpgrade(aResult, aStatus);
+ if (NS_FAILED(rv)) {
+ self->CloseCacheEntry(false);
+ Unused << self->AsyncAbort(rv);
+ }
+ };
+
+ bool willCallback = false;
+ rv = NS_ShouldSecureUpgrade(
+ mURI, mLoadInfo, resultPrincipal, LoadAllowSTS(), originAttributes,
+ shouldUpgrade, std::move(resultCallback), willCallback);
+ // If the request gets upgraded because of the HTTPS-Only mode, but no
+ // event listener has been registered so far, we want to do that here.
+ uint32_t httpOnlyStatus = mLoadInfo->GetHttpsOnlyStatus();
+ if (httpOnlyStatus &
+ nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED) {
+ RefPtr<nsHTTPSOnlyStreamListener> httpsOnlyListener =
+ new nsHTTPSOnlyStreamListener(mListener, mLoadInfo);
+ mListener = httpsOnlyListener;
+
+ httpOnlyStatus ^=
+ nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED;
+ httpOnlyStatus |= nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED;
+ mLoadInfo->SetHttpsOnlyStatus(httpOnlyStatus);
+ }
+ LOG(
+ ("nsHttpChannel::OnBeforeConnect "
+ "[this=%p willCallback=%d rv=%" PRIx32 "]\n",
+ this, willCallback, static_cast<uint32_t>(rv)));
+
+ if (NS_FAILED(rv) || MOZ_UNLIKELY(willCallback)) {
+ return rv;
+ }
+ }
+ }
+
+ return MaybeUseHTTPSRRForUpgrade(shouldUpgrade, NS_OK);
+}
+
+nsresult nsHttpChannel::MaybeUseHTTPSRRForUpgrade(bool aShouldUpgrade,
+ nsresult aStatus) {
+ if (NS_FAILED(aStatus)) {
+ return aStatus;
+ }
+
+ if (mURI->SchemeIs("https") || aShouldUpgrade || !LoadUseHTTPSSVC()) {
+ return ContinueOnBeforeConnect(aShouldUpgrade, aStatus);
+ }
+
+ auto shouldSkipUpgradeWithHTTPSRR = [&]() -> bool {
+ // Skip using HTTPS RR to upgrade when this is not a top-level load and the
+ // loading principal is http.
+ if ((mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) &&
+ (mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->SchemeIs("http"))) {
+ return true;
+ }
+
+ nsAutoCString uriHost;
+ mURI->GetAsciiHost(uriHost);
+
+ if (gHttpHandler->IsHostExcludedForHTTPSRR(uriHost)) {
+ return true;
+ }
+
+ if (nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
+ mURI, mLoadInfo,
+ {nsHTTPSOnlyUtils::UpgradeDowngradeEndlessLoopOptions::
+ EnforceForHTTPSRR})) {
+ // Add the host to a excluded list because:
+ // 1. We don't need to do the same check again.
+ // 2. Other subresources in the same host will be also excluded.
+ gHttpHandler->ExcludeHTTPSRRHost(uriHost);
+ LOG(("[%p] skip HTTPS upgrade for host [%s]", this, uriHost.get()));
+ return true;
+ }
+
+ return false;
+ };
+
+ if (shouldSkipUpgradeWithHTTPSRR()) {
+ StoreUseHTTPSSVC(false);
+ // If the website does not want to use HTTPS RR, we should set
+ // NS_HTTP_DISALLOW_HTTPS_RR. This is for avoiding HTTPS RR being used by
+ // the transaction.
+ mCaps |= NS_HTTP_DISALLOW_HTTPS_RR;
+ return ContinueOnBeforeConnect(aShouldUpgrade, aStatus);
+ }
+
+ if (mHTTPSSVCRecord.isSome()) {
+ LOG((
+ "nsHttpChannel::MaybeUseHTTPSRRForUpgrade [%p] mHTTPSSVCRecord is some",
+ this));
+ StoreWaitHTTPSSVCRecord(false);
+ bool hasHTTPSRR = (mHTTPSSVCRecord.ref() != nullptr);
+ return ContinueOnBeforeConnect(hasHTTPSRR, aStatus, hasHTTPSRR);
+ }
+
+ auto dnsStrategy = GetProxyDNSStrategy();
+ if (!(dnsStrategy & DNS_PREFETCH_ORIGIN)) {
+ return ContinueOnBeforeConnect(aShouldUpgrade, aStatus);
+ }
+
+ LOG(("nsHttpChannel::MaybeUseHTTPSRRForUpgrade [%p] wait for HTTPS RR",
+ this));
+
+ OriginAttributes originAttributes;
+ StoragePrincipalHelper::GetOriginAttributesForHTTPSRR(this, originAttributes);
+
+ RefPtr<nsDNSPrefetch> resolver =
+ new nsDNSPrefetch(mURI, originAttributes, nsIRequest::GetTRRMode());
+ nsWeakPtr weakPtrThis(
+ do_GetWeakReference(static_cast<nsIHttpChannel*>(this)));
+ nsresult rv = resolver->FetchHTTPSSVC(
+ mCaps & NS_HTTP_REFRESH_DNS, !LoadUseHTTPSSVC(),
+ [weakPtrThis](nsIDNSHTTPSSVCRecord* aRecord) {
+ nsCOMPtr<nsIHttpChannel> channel = do_QueryReferent(weakPtrThis);
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(channel);
+ if (httpChannelImpl) {
+ httpChannelImpl->OnHTTPSRRAvailable(aRecord);
+ }
+ });
+ if (NS_FAILED(rv)) {
+ LOG((" FetchHTTPSSVC failed with 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ return ContinueOnBeforeConnect(aShouldUpgrade, aStatus);
+ }
+
+ StoreWaitHTTPSSVCRecord(true);
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::ContinueOnBeforeConnect(bool aShouldUpgrade,
+ nsresult aStatus,
+ bool aUpgradeWithHTTPSRR) {
+ LOG(
+ ("nsHttpChannel::ContinueOnBeforeConnect "
+ "[this=%p aShouldUpgrade=%d rv=%" PRIx32 "]\n",
+ this, aShouldUpgrade, static_cast<uint32_t>(aStatus)));
+
+ MOZ_ASSERT(!LoadWaitHTTPSSVCRecord());
+
+ if (NS_FAILED(aStatus)) {
+ return aStatus;
+ }
+
+ if (aShouldUpgrade && !mURI->SchemeIs("https")) {
+ mozilla::glean::networking::https_upgrade_with_https_rr
+ .Get(aUpgradeWithHTTPSRR ? "https_rr"_ns : "others"_ns)
+ .Add(1);
+ return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
+ }
+
+ // ensure that we are using a valid hostname
+ if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Origin()))) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (mUpgradeProtocolCallback) {
+ // Websockets can run over HTTP/2, but other upgrades can't.
+ if (mUpgradeProtocol.EqualsLiteral("websocket") &&
+ StaticPrefs::network_http_http2_websockets()) {
+ // Need to tell the conn manager that we're ok with http/2 even with
+ // the allow keepalive bit not set. That bit needs to stay off,
+ // though, in case we end up having to fallback to http/1.1 (where
+ // we absolutely do want to disable keepalive).
+ mCaps |= NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE;
+ } else {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+ // Upgrades cannot use HTTP/3.
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ // Because NS_HTTP_STICKY_CONNECTION breaks HTTPS RR fallabck mecnahism, we
+ // can not use HTTPS RR for upgrade requests.
+ mCaps |= NS_HTTP_DISALLOW_HTTPS_RR;
+ }
+
+ if (LoadIsTRRServiceChannel()) {
+ mCaps |= NS_HTTP_LARGE_KEEPALIVE;
+ mCaps |= NS_HTTP_DISALLOW_HTTPS_RR;
+ }
+
+ if (mTransactionSticky) {
+ MOZ_ASSERT(LoadAuthRedirectedChannel());
+ // this means this is a redirected channel channel due to auth retry and a
+ // connection based auth scheme was used
+ // we have a reference to the old-transaction with sticky connection which
+ // we need to use
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ }
+
+ mCaps |= NS_HTTP_TRR_FLAGS_FROM_MODE(nsIRequest::GetTRRMode());
+
+ // Finalize ConnectionInfo flags before SpeculativeConnect
+ mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
+ mConnectionInfo->SetPrivate(mPrivateBrowsing);
+ mConnectionInfo->SetNoSpdy(mCaps & NS_HTTP_DISALLOW_SPDY);
+ mConnectionInfo->SetBeConservative((mCaps & NS_HTTP_BE_CONSERVATIVE) ||
+ LoadBeConservative());
+ mConnectionInfo->SetTlsFlags(mTlsFlags);
+ mConnectionInfo->SetIsTrrServiceChannel(LoadIsTRRServiceChannel());
+ mConnectionInfo->SetTRRMode(nsIRequest::GetTRRMode());
+ mConnectionInfo->SetIPv4Disabled(mCaps & NS_HTTP_DISABLE_IPV4);
+ mConnectionInfo->SetIPv6Disabled(mCaps & NS_HTTP_DISABLE_IPV6);
+ mConnectionInfo->SetAnonymousAllowClientCert(
+ (mLoadFlags & LOAD_ANONYMOUS_ALLOW_CLIENT_CERT) != 0);
+
+ // notify "http-on-before-connect" observers
+ gHttpHandler->OnBeforeConnect(this);
+
+ return CallOrWaitForResume([](auto* self) { return self->Connect(); });
+}
+
+nsresult nsHttpChannel::Connect() {
+ LOG(("nsHttpChannel::Connect [this=%p]\n", this));
+
+ // Don't allow resuming when cache must be used
+ if (LoadResuming() && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
+ LOG(("Resuming from cache is not supported yet"));
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+
+ if (ShouldIntercept()) {
+ return RedirectToInterceptedChannel();
+ }
+
+ // Step 8.18 of HTTP-network-or-cache fetch
+ // https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
+ nsAutoCString rangeVal;
+ if (NS_SUCCEEDED(GetRequestHeader("Range"_ns, rangeVal))) {
+ SetRequestHeader("Accept-Encoding"_ns, "identity"_ns, true);
+ }
+
+ bool isTrackingResource = IsThirdPartyTrackingResource();
+ LOG(("nsHttpChannel %p tracking resource=%d, cos=%lu, inc=%d", this,
+ isTrackingResource, mClassOfService.Flags(),
+ mClassOfService.Incremental()));
+
+ if (isTrackingResource) {
+ AddClassFlags(nsIClassOfService::Tail);
+ }
+
+ if (WaitingForTailUnblock()) {
+ MOZ_DIAGNOSTIC_ASSERT(!mOnTailUnblock);
+ mOnTailUnblock = &nsHttpChannel::ConnectOnTailUnblock;
+ return NS_OK;
+ }
+
+ return ConnectOnTailUnblock();
+}
+
+nsresult nsHttpChannel::ConnectOnTailUnblock() {
+ nsresult rv;
+
+ LOG(("nsHttpChannel::ConnectOnTailUnblock [this=%p]\n", this));
+
+ // Consider opening a TCP connection right away.
+ SpeculativeConnect();
+
+ // open a cache entry for this channel...
+ rv = OpenCacheEntry(mURI->SchemeIs("https"));
+
+ // do not continue if asyncOpenCacheEntry is in progress
+ if (AwaitingCacheCallbacks()) {
+ LOG(("nsHttpChannel::Connect %p AwaitingCacheCallbacks forces async\n",
+ this));
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Unexpected state");
+
+ if (mNetworkTriggered && mWaitingForProxy) {
+ // Someone has called TriggerNetwork(), meaning we are racing the
+ // network with the cache.
+ mWaitingForProxy = false;
+ return ContinueConnect();
+ }
+
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("OpenCacheEntry failed [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ // if this channel is only allowed to pull from the cache, then
+ // we must fail if we were unable to open a cache entry.
+ if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+ // otherwise, let's just proceed without using the cache.
+ }
+
+ if (mRaceCacheWithNetwork && ((mCacheEntry && !mCachedContentIsValid &&
+ (mDidReval || LoadCachedContentIsPartial())) ||
+ mIgnoreCacheEntry)) {
+ // We won't send the conditional request because the unconditional
+ // request was already sent (see bug 1377223).
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent);
+ }
+
+ // When racing, if OnCacheEntryAvailable is called before AsyncOpenURI
+ // returns, then we may not have started reading from the cache.
+ // If the content is valid, we should attempt to do so, as technically the
+ // cache has won the race.
+ if (mRaceCacheWithNetwork && mCachedContentIsValid) {
+ Unused << ReadFromCache(true);
+ }
+
+ return TriggerNetwork();
+}
+
+nsresult nsHttpChannel::ContinueConnect() {
+ // If we need to start a CORS preflight, do it now!
+ // Note that it is important to do this before the early returns below.
+ if (!LoadIsCorsPreflightDone() && LoadRequireCORSPreflight()) {
+ MOZ_ASSERT(!mPreflightChannel);
+ nsresult rv = nsCORSListenerProxy::StartCORSPreflight(
+ this, this, mUnsafeHeaders, getter_AddRefs(mPreflightChannel));
+ return rv;
+ }
+
+ MOZ_RELEASE_ASSERT(!LoadRequireCORSPreflight() || LoadIsCorsPreflightDone(),
+ "CORS preflight must have been finished by the time we "
+ "do the rest of ContinueConnect");
+
+ // we may or may not have a cache entry at this point
+ if (mCacheEntry) {
+ // read straight from the cache if possible...
+ if (mCachedContentIsValid) {
+ nsRunnableMethod<nsHttpChannel>* event = nullptr;
+ nsresult rv;
+ if (!LoadCachedContentIsPartial()) {
+ rv = AsyncCall(&nsHttpChannel::AsyncOnExamineCachedResponse, &event);
+ if (NS_FAILED(rv)) {
+ LOG((" AsyncCall failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }
+ rv = ReadFromCache(true);
+ if (NS_FAILED(rv) && event) {
+ event->Revoke();
+ }
+
+ AccumulateCacheHitTelemetry(kCacheHit, this);
+ mCacheDisposition = kCacheHit;
+
+ return rv;
+ }
+ if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+ // the cache contains the requested resource, but it must be
+ // validated before we can reuse it. since we are not allowed
+ // to hit the net, there's nothing more to do. the document
+ // is effectively not in the cache.
+ LOG((" !mCachedContentIsValid && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+ } else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+ LOG((" !mCacheEntry && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+
+ if (mLoadFlags & LOAD_NO_NETWORK_IO) {
+ LOG((" mLoadFlags & LOAD_NO_NETWORK_IO"));
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+
+ // hit the net...
+ nsresult rv = DoConnect(mTransactionSticky);
+ mTransactionSticky = nullptr;
+ return rv;
+}
+
+nsresult nsHttpChannel::DoConnect(HttpTransactionShell* aTransWithStickyConn) {
+ LOG(("nsHttpChannel::DoConnect [this=%p]\n", this));
+
+ if (!mDNSBlockingPromise.IsEmpty()) {
+ LOG((" waiting for DNS prefetch"));
+
+ // Transaction is passed only from auth retry for which we will definitely
+ // not block on DNS to alter the origin server name for IP; it has already
+ // been done.
+ MOZ_ASSERT(!aTransWithStickyConn);
+ MOZ_ASSERT(mDNSBlockingThenable);
+
+ nsCOMPtr<nsISerialEventTarget> target(do_GetMainThread());
+ RefPtr<nsHttpChannel> self(this);
+ mDNSBlockingThenable->Then(
+ target, __func__,
+ [self](const nsCOMPtr<nsIDNSRecord>& aRec) {
+ nsresult rv = self->DoConnectActual(nullptr);
+ if (NS_FAILED(rv)) {
+ self->CloseCacheEntry(false);
+ Unused << self->AsyncAbort(rv);
+ }
+ },
+ [self](nsresult err) {
+ self->CloseCacheEntry(false);
+ Unused << self->AsyncAbort(err);
+ });
+
+ // The connection will continue when the promise is resolved in
+ // OnLookupComplete.
+ return NS_OK;
+ }
+
+ return DoConnectActual(aTransWithStickyConn);
+}
+
+nsresult nsHttpChannel::DoConnectActual(
+ HttpTransactionShell* aTransWithStickyConn) {
+ LOG(("nsHttpChannel::DoConnectActual [this=%p, aTransWithStickyConn=%p]\n",
+ this, aTransWithStickyConn));
+
+ nsresult rv = SetupTransaction();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (aTransWithStickyConn) {
+ rv = gHttpHandler->InitiateTransactionWithStickyConn(
+ mTransaction, mPriority, aTransWithStickyConn);
+ } else {
+ rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = mTransaction->AsyncRead(this, getter_AddRefs(mTransactionPump));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint32_t suspendCount = mSuspendCount;
+ if (LoadAsyncResumePending()) {
+ LOG(
+ (" Suspend()'ing transaction pump once because of async resume pending"
+ ", sc=%u, pump=%p, this=%p",
+ suspendCount, mTransactionPump.get(), this));
+ ++suspendCount;
+ }
+ while (suspendCount--) {
+ mTransactionPump->Suspend();
+ }
+
+ return NS_OK;
+}
+
+void nsHttpChannel::SpeculativeConnect() {
+ // Before we take the latency hit of dealing with the cache, try and
+ // get the TCP (and SSL) handshakes going so they can overlap.
+
+ // don't speculate if we are offline, when doing http upgrade (i.e.
+ // websockets bootstrap), or if we can't do keep-alive (because then we
+ // couldn't reuse the speculative connection anyhow).
+ RefPtr<mozilla::dom::BrowsingContext> bc;
+ mLoadInfo->GetBrowsingContext(getter_AddRefs(bc));
+
+ if (gIOService->IsOffline() || mUpgradeProtocolCallback ||
+ !(mCaps & NS_HTTP_ALLOW_KEEPALIVE) ||
+ (bc && bc->Top()->GetForceOffline())) {
+ return;
+ }
+
+ // LOAD_ONLY_FROM_CACHE and LOAD_NO_NETWORK_IO must not hit network.
+ // LOAD_FROM_CACHE is unlikely to hit network, so skip preconnects for it.
+ if (mLoadFlags &
+ (LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE | LOAD_NO_NETWORK_IO)) {
+ return;
+ }
+
+ if (LoadAllowStaleCacheContent()) {
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+ if (!callbacks) return;
+
+ Unused << gHttpHandler->SpeculativeConnect(
+ mConnectionInfo, callbacks,
+ mCaps & (NS_HTTP_DISALLOW_SPDY | NS_HTTP_TRR_MODE_MASK |
+ NS_HTTP_DISABLE_IPV4 | NS_HTTP_DISABLE_IPV6 |
+ NS_HTTP_DISALLOW_HTTP3 | NS_HTTP_REFRESH_DNS),
+ gHttpHandler->EchConfigEnabled());
+}
+
+void nsHttpChannel::DoNotifyListenerCleanup() {
+ // We don't need this info anymore
+ CleanRedirectCacheChainIfNecessary();
+}
+
+void nsHttpChannel::ReleaseListeners() {
+ HttpBaseChannel::ReleaseListeners();
+ mChannelClassifier = nullptr;
+ mWarningReporter = nullptr;
+ mEarlyHintObserver = nullptr;
+ mWebTransportSessionEventListener = nullptr;
+
+ for (StreamFilterRequest& request : mStreamFilterRequests) {
+ request.mPromise->Reject(false, __func__);
+ }
+ mStreamFilterRequests.Clear();
+}
+
+void nsHttpChannel::DoAsyncAbort(nsresult aStatus) {
+ Unused << AsyncAbort(aStatus);
+}
+
+void nsHttpChannel::HandleAsyncRedirect() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async redirect [this=%p]\n", this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->HandleAsyncRedirect();
+ return NS_OK;
+ };
+ return;
+ }
+
+ nsresult rv = NS_OK;
+
+ LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this));
+
+ // since this event is handled asynchronously, it is possible that this
+ // channel could have been canceled, in which case there would be no point
+ // in processing the redirect.
+ if (NS_SUCCEEDED(mStatus)) {
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
+ rv = AsyncProcessRedirection(mResponseHead->Status());
+ if (NS_FAILED(rv)) {
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
+ // TODO: if !DoNotRender3xxBody(), render redirect body instead.
+ // But first we need to cache 3xx bodies (bug 748510)
+ rv = ContinueHandleAsyncRedirect(rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ } else {
+ rv = ContinueHandleAsyncRedirect(mStatus);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+nsresult nsHttpChannel::ContinueHandleAsyncRedirect(nsresult rv) {
+ if (NS_FAILED(rv)) {
+ // If AsyncProcessRedirection fails, then we have to send out the
+ // OnStart/OnStop notifications.
+ LOG(("ContinueHandleAsyncRedirect got failure result [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+
+ bool redirectsEnabled = !mLoadInfo->GetDontFollowRedirects();
+
+ if (redirectsEnabled) {
+ // TODO: stop failing original channel if redirect vetoed?
+ mStatus = rv;
+
+ DoNotifyListener();
+
+ // Blow away cache entry if we couldn't process the redirect
+ // for some reason (the cache entry might be corrupt).
+ if (mCacheEntry) {
+ mCacheEntry->AsyncDoom(nullptr);
+ }
+ } else {
+ DoNotifyListener();
+ }
+ }
+
+ CloseCacheEntry(true);
+
+ StoreIsPending(false);
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ return NS_OK;
+}
+
+void nsHttpChannel::HandleAsyncNotModified() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async not-modified [this=%p]\n", this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->HandleAsyncNotModified();
+ return NS_OK;
+ };
+ return;
+ }
+
+ LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
+
+ DoNotifyListener();
+
+ CloseCacheEntry(false);
+
+ StoreIsPending(false);
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+}
+
+nsresult nsHttpChannel::SetupTransaction() {
+ LOG(("nsHttpChannel::SetupTransaction [this=%p, cos=%lu, inc=%d prio=%d]\n",
+ this, mClassOfService.Flags(), mClassOfService.Incremental(),
+ mPriority));
+
+ NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
+
+ nsresult rv;
+
+ mozilla::MutexAutoLock lock(mRCWNLock);
+
+ // If we're racing cache with network, conditional or byte range header
+ // could be added in OnCacheEntryCheck. We cannot send conditional request
+ // without having the entry, so we need to remove the headers here and
+ // ignore the cache entry in OnCacheEntryAvailable.
+ if (mRaceCacheWithNetwork && AwaitingCacheCallbacks()) {
+ if (mDidReval) {
+ LOG((" Removing conditional request headers"));
+ UntieValidationRequest();
+ mDidReval = false;
+ mIgnoreCacheEntry = true;
+ }
+
+ if (LoadCachedContentIsPartial()) {
+ LOG((" Removing byte range request headers"));
+ UntieByteRangeRequest();
+ StoreCachedContentIsPartial(false);
+ mIgnoreCacheEntry = true;
+ }
+
+ if (mIgnoreCacheEntry) {
+ mAvailableCachedAltDataType.Truncate();
+ StoreDeliveringAltData(false);
+ mAltDataLength = -1;
+ mCacheInputStream.CloseAndRelease();
+ }
+ }
+
+ StoreUsedNetwork(1);
+
+ if (!LoadAllowSpdy()) {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+ if (!LoadAllowHttp3()) {
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+ if (LoadBeConservative()) {
+ mCaps |= NS_HTTP_BE_CONSERVATIVE;
+ }
+
+ if (mLoadFlags & LOAD_ANONYMOUS_ALLOW_CLIENT_CERT) {
+ mCaps |= NS_HTTP_LOAD_ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
+ }
+
+ if (nsContentUtils::ShouldResistFingerprinting(this,
+ RFPTarget::HttpUserAgent)) {
+ mCaps |= NS_HTTP_USE_RFP;
+ }
+
+ // Use the URI path if not proxying (transparent proxying such as proxy
+ // CONNECT does not count here). Also figure out what HTTP version to use.
+ nsAutoCString buf, path;
+ nsCString* requestURI;
+
+ // This is the normal e2e H1 path syntax "/index.html"
+ rv = mURI->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // path may contain UTF-8 characters, so ensure that they're escaped.
+ if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII | esc_Spaces,
+ buf)) {
+ requestURI = &buf;
+ } else {
+ requestURI = &path;
+ }
+
+ // trim off the #ref portion if any...
+ int32_t ref1 = requestURI->FindChar('#');
+ if (ref1 != kNotFound) {
+ requestURI->SetLength(ref1);
+ }
+
+ if (mConnectionInfo->UsingConnect() || !mConnectionInfo->UsingHttpProxy()) {
+ mRequestHead.SetVersion(gHttpHandler->HttpVersion());
+ } else {
+ mRequestHead.SetPath(*requestURI);
+
+ // RequestURI should be the absolute uri H1 proxy syntax
+ // "http://foo/index.html" so we will overwrite the relative version in
+ // requestURI
+ rv = mURI->GetUserPass(buf);
+ if (NS_FAILED(rv)) return rv;
+ if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
+ strncmp(mSpec.get(), "https:", 6) == 0)) {
+ nsCOMPtr<nsIURI> tempURI = nsIOService::CreateExposableURI(mURI);
+ rv = tempURI->GetAsciiSpec(path);
+ if (NS_FAILED(rv)) return rv;
+ requestURI = &path;
+ } else {
+ requestURI = &mSpec;
+ }
+
+ // trim off the #ref portion if any...
+ int32_t ref2 = requestURI->FindChar('#');
+ if (ref2 != kNotFound) {
+ requestURI->SetLength(ref2);
+ }
+
+ mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
+ }
+
+ mRequestHead.SetRequestURI(*requestURI);
+
+ // set the request time for cache expiration calculations
+ mRequestTime = NowInSeconds();
+ StoreRequestTimeInitialized(true);
+
+ // if doing a reload, force end-to-end
+ if (mLoadFlags & LOAD_BYPASS_CACHE) {
+ // We need to send 'Pragma:no-cache' to inhibit proxy caching even if
+ // no proxy is configured since we might be talking with a transparent
+ // proxy, i.e. one that operates at the network level. See bug #14772.
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
+ // no-cache'
+ if (mRequestHead.Version() >= HttpVersion::v1_1) {
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "no-cache", true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ } else if ((mLoadFlags & VALIDATE_ALWAYS) && !LoadCacheEntryIsWriteOnly()) {
+ // We need to send 'Cache-Control: max-age=0' to force each cache along
+ // the path to the origin server to revalidate its own entry, if any,
+ // with the next cache or server. See bug #84847.
+ //
+ // If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache'
+ if (mRequestHead.Version() >= HttpVersion::v1_1) {
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "max-age=0", true);
+ } else {
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
+ }
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ if (LoadResuming()) {
+ char byteRange[32];
+ SprintfLiteral(byteRange, "bytes=%" PRIu64 "-", mStartPos);
+ rv = mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (!mEntityID.IsEmpty()) {
+ // Also, we want an error if this resource changed in the meantime
+ // Format of the entity id is: escaped_etag/size/lastmod
+ nsCString::const_iterator start, end, slash;
+ mEntityID.BeginReading(start);
+ mEntityID.EndReading(end);
+ mEntityID.BeginReading(slash);
+
+ if (FindCharInReadable('/', slash, end)) {
+ nsAutoCString ifMatch;
+ rv = mRequestHead.SetHeader(
+ nsHttp::If_Match,
+ NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ ++slash; // Incrementing, so that searching for '/' won't find
+ // the same slash again
+ }
+
+ if (FindCharInReadable('/', slash, end)) {
+ rv = mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
+ Substring(++slash, end));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+ }
+
+ // create wrapper for this channel's notification callbacks
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+
+ // create the transaction object
+ if (nsIOService::UseSocketProcess()) {
+ if (NS_WARN_IF(!gIOService->SocketProcessReady())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ SocketProcessParent* socketProcess = SocketProcessParent::GetSingleton();
+ if (!socketProcess->CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ RefPtr<DocumentLoadListener> documentChannelParent =
+ do_QueryObject(parentChannel);
+ // See HttpTransactionChild::CanSendODAToContentProcessDirectly() and
+ // nsHttpChannel::CallOnStartRequest() for the reason why we need to know if
+ // this is a document load. We only send ODA directly to child process for
+ // non document loads.
+ RefPtr<HttpTransactionParent> transParent =
+ new HttpTransactionParent(!!documentChannelParent);
+ LOG1(("nsHttpChannel %p created HttpTransactionParent %p\n", this,
+ transParent.get()));
+
+ // Since OnStopRequest could be sent to child process from socket process
+ // directly, we need to store these two values in HttpTransactionChild and
+ // forward to child process until HttpTransactionChild::OnStopRequest is
+ // called.
+ transParent->SetRedirectTimestamp(mRedirectStartTimeStamp,
+ mRedirectEndTimeStamp);
+
+ if (socketProcess) {
+ MOZ_ALWAYS_TRUE(
+ socketProcess->SendPHttpTransactionConstructor(transParent));
+ }
+ mTransaction = transParent;
+ } else {
+ mTransaction = new nsHttpTransaction();
+ LOG1(("nsHttpChannel %p created nsHttpTransaction %p\n", this,
+ mTransaction.get()));
+ }
+
+ // Save the mapping of channel id and the channel. We need this mapping for
+ // nsIHttpActivityObserver.
+ gHttpHandler->AddHttpChannel(mChannelId, ToSupports(this));
+
+ // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
+ if (mLoadFlags & LOAD_ANONYMOUS) mCaps |= NS_HTTP_LOAD_ANONYMOUS;
+
+ if (LoadTimingEnabled()) mCaps |= NS_HTTP_TIMING_ENABLED;
+
+ if (mUpgradeProtocolCallback) {
+ rv = mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = mRequestHead.SetHeaderOnce(nsHttp::Connection, nsHttp::Upgrade.get(),
+ true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
+ }
+
+ if (mWebTransportSessionEventListener) {
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ }
+
+ nsCOMPtr<nsIHttpPushListener> pushListener;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIHttpPushListener),
+ getter_AddRefs(pushListener));
+ HttpTransactionShell::OnPushCallback pushCallback = nullptr;
+ if (pushListener) {
+ mCaps |= NS_HTTP_ONPUSH_LISTENER;
+ nsWeakPtr weakPtrThis(
+ do_GetWeakReference(static_cast<nsIHttpChannel*>(this)));
+ pushCallback = [weakPtrThis](uint32_t aPushedStreamId,
+ const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction) {
+ if (nsCOMPtr<nsIHttpChannel> channel = do_QueryReferent(weakPtrThis)) {
+ return static_cast<nsHttpChannel*>(channel.get())
+ ->OnPush(aPushedStreamId, aUrl, aRequestString, aTransaction);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ };
+ }
+
+ EnsureBrowserId();
+ EnsureRequestContext();
+
+ HttpTrafficCategory category = CreateTrafficCategory();
+ std::function<void(TransactionObserverResult&&)> observer;
+ if (mTransactionObserver) {
+ observer = [transactionObserver{std::move(mTransactionObserver)}](
+ TransactionObserverResult&& aResult) {
+ transactionObserver->Complete(aResult.versionOk(), aResult.authOk(),
+ aResult.closeReason());
+ };
+ }
+ mTransaction->SetIsForWebTransport(!!mWebTransportSessionEventListener);
+ rv = mTransaction->Init(
+ mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
+ LoadUploadStreamHasHeaders(), GetCurrentSerialEventTarget(), callbacks,
+ this, mBrowserId, category, mRequestContext, mClassOfService,
+ mInitialRwin, LoadResponseTimeoutEnabled(), mChannelId,
+ std::move(observer), std::move(pushCallback), mTransWithPushedStream,
+ mPushedStreamId);
+ if (NS_FAILED(rv)) {
+ mTransaction = nullptr;
+ return rv;
+ }
+
+ return rv;
+}
+
+HttpTrafficCategory nsHttpChannel::CreateTrafficCategory() {
+ MOZ_ASSERT(!mFirstPartyClassificationFlags ||
+ !mThirdPartyClassificationFlags);
+
+ if (!StaticPrefs::network_traffic_analyzer_enabled()) {
+ return HttpTrafficCategory::eInvalid;
+ }
+
+ HttpTrafficAnalyzer::ClassOfService cos;
+ {
+ if ((mClassOfService.Flags() & nsIClassOfService::Leader) &&
+ mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SCRIPT) {
+ cos = HttpTrafficAnalyzer::ClassOfService::eLeader;
+ } else if (mLoadFlags & nsIRequest::LOAD_BACKGROUND) {
+ cos = HttpTrafficAnalyzer::ClassOfService::eBackground;
+ } else {
+ cos = HttpTrafficAnalyzer::ClassOfService::eOther;
+ }
+ }
+
+ bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(this);
+
+ HttpTrafficAnalyzer::TrackingClassification tc;
+ {
+ uint32_t flags = isThirdParty ? mThirdPartyClassificationFlags
+ : mFirstPartyClassificationFlags;
+
+ using CF = nsIClassifiedChannel::ClassificationFlags;
+ using TC = HttpTrafficAnalyzer::TrackingClassification;
+
+ if (flags & CF::CLASSIFIED_TRACKING_CONTENT) {
+ tc = TC::eContent;
+ } else if (flags & CF::CLASSIFIED_FINGERPRINTING_CONTENT) {
+ tc = TC::eFingerprinting;
+ } else if (flags & CF::CLASSIFIED_ANY_BASIC_TRACKING) {
+ tc = TC::eBasic;
+ } else {
+ tc = TC::eNone;
+ }
+ }
+
+ bool isSystemPrincipal =
+ mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal();
+ return HttpTrafficAnalyzer::CreateTrafficCategory(
+ NS_UsePrivateBrowsing(this), isSystemPrincipal, isThirdParty, cos, tc);
+}
+
+void nsHttpChannel::SetCachedContentType() {
+ if (!mResponseHead) {
+ return;
+ }
+
+ nsAutoCString contentTypeStr;
+ mResponseHead->ContentType(contentTypeStr);
+
+ uint8_t contentType = nsICacheEntry::CONTENT_TYPE_OTHER;
+ if (nsContentUtils::IsJavascriptMIMEType(
+ NS_ConvertUTF8toUTF16(contentTypeStr))) {
+ contentType = nsICacheEntry::CONTENT_TYPE_JAVASCRIPT;
+ } else if (StringBeginsWith(contentTypeStr, "text/css"_ns) ||
+ (mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_STYLESHEET)) {
+ contentType = nsICacheEntry::CONTENT_TYPE_STYLESHEET;
+ } else if (StringBeginsWith(contentTypeStr, "application/wasm"_ns)) {
+ contentType = nsICacheEntry::CONTENT_TYPE_WASM;
+ } else if (StringBeginsWith(contentTypeStr, "image/"_ns)) {
+ contentType = nsICacheEntry::CONTENT_TYPE_IMAGE;
+ } else if (StringBeginsWith(contentTypeStr, "video/"_ns)) {
+ contentType = nsICacheEntry::CONTENT_TYPE_MEDIA;
+ } else if (StringBeginsWith(contentTypeStr, "audio/"_ns)) {
+ contentType = nsICacheEntry::CONTENT_TYPE_MEDIA;
+ }
+
+ mCacheEntry->SetContentType(contentType);
+}
+
+nsresult nsHttpChannel::CallOnStartRequest() {
+ LOG(("nsHttpChannel::CallOnStartRequest [this=%p]", this));
+
+ MOZ_RELEASE_ASSERT(!LoadRequireCORSPreflight() || LoadIsCorsPreflightDone(),
+ "CORS preflight must have been finished by the time we "
+ "call OnStartRequest");
+
+ MOZ_RELEASE_ASSERT(mCanceled || LoadProcessCrossOriginSecurityHeadersCalled(),
+ "Security headers need to have been processed before "
+ "calling CallOnStartRequest");
+
+ mEarlyHintObserver = nullptr;
+
+ if (LoadOnStartRequestCalled()) {
+ // This can only happen when a range request loading rest of the data
+ // after interrupted concurrent cache read asynchronously failed, e.g.
+ // the response range bytes are not as expected or this channel has
+ // been externally canceled.
+ //
+ // It's legal to bypass CallOnStartRequest for that case since we've
+ // already called OnStartRequest on our listener and also added all
+ // content converters before.
+ MOZ_ASSERT(LoadConcurrentCacheAccess());
+ LOG(("CallOnStartRequest already invoked before"));
+ return mStatus;
+ }
+
+ // Ensure mListener->OnStartRequest will be invoked before exiting
+ // this function.
+ auto onStartGuard = MakeScopeExit([&] {
+ LOG(
+ (" calling mListener->OnStartRequest by ScopeExit [this=%p, "
+ "listener=%p]\n",
+ this, mListener.get()));
+ MOZ_ASSERT(!LoadOnStartRequestCalled());
+
+ if (mListener) {
+ nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
+ StoreOnStartRequestCalled(true);
+ deleteProtector->OnStartRequest(this);
+ }
+ StoreOnStartRequestCalled(true);
+ });
+
+ nsresult rv = ValidateMIMEType();
+ // Since ODA and OnStopRequest could be sent from socket process directly, we
+ // need to update the channel status before calling mListener->OnStartRequest.
+ // This is the only way to let child process discard the already received ODA
+ // messages.
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ return mStatus;
+ }
+
+ // EnsureOpaqueResponseIsAllowed and EnsureOpauqeResponseIsAllowedAfterSniff
+ // are the checks for Opaque Response Blocking to ensure that we block as many
+ // cross-origin responses with CORS headers as possible that are not either
+ // Javascript or media to avoid leaking their contents through side channels.
+ OpaqueResponse opaqueResponse =
+ PerformOpaqueResponseSafelistCheckBeforeSniff();
+ if (opaqueResponse == OpaqueResponse::Block) {
+ SetChannelBlockedByOpaqueResponse();
+ CancelWithReason(NS_ERROR_FAILURE,
+ "OpaqueResponseBlocker::BlockResponse"_ns);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Allow consumers to override our content type
+ if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
+ // NOTE: We can have both a txn pump and a cache pump when the cache
+ // content is partial. In that case, we need to read from the cache,
+ // because that's the one that has the initial contents. If that fails
+ // then give the transaction pump a shot.
+
+ nsIChannel* thisChannel = static_cast<nsIChannel*>(this);
+
+ bool typeSniffersCalled = false;
+ if (mCachePump) {
+ typeSniffersCalled =
+ NS_SUCCEEDED(mCachePump->PeekStream(CallTypeSniffers, thisChannel));
+ }
+
+ if (!typeSniffersCalled && mTransactionPump) {
+ RefPtr<nsInputStreamPump> pump = do_QueryObject(mTransactionPump);
+ if (pump) {
+ pump->PeekStream(CallTypeSniffers, thisChannel);
+ } else {
+ MOZ_ASSERT(nsIOService::UseSocketProcess());
+ RefPtr<HttpTransactionParent> trans = do_QueryObject(mTransactionPump);
+ MOZ_ASSERT(trans);
+ trans->SetSniffedTypeToChannel(CallTypeSniffers, thisChannel);
+ }
+ }
+ }
+
+ // Note that the code below should be synced with the code in
+ // HttpTransactionChild::CanSendODAToContentProcessDirectly(). We MUST make
+ // sure HttpTransactionChild::CanSendODAToContentProcessDirectly() returns
+ // false when a stream converter is applied.
+ bool unknownDecoderStarted = false;
+ if (mResponseHead && !mResponseHead->HasContentType()) {
+ MOZ_ASSERT(mConnectionInfo, "Should have connection info here");
+ if (!mContentTypeHint.IsEmpty()) {
+ mResponseHead->SetContentType(mContentTypeHint);
+ } else if (mResponseHead->Version() == HttpVersion::v0_9 &&
+ mConnectionInfo->OriginPort() !=
+ mConnectionInfo->DefaultPort()) {
+ mResponseHead->SetContentType(nsLiteralCString(TEXT_PLAIN));
+ } else {
+ // Uh-oh. We had better find out what type we are!
+ mListener = new nsUnknownDecoder(mListener);
+ unknownDecoderStarted = true;
+ }
+ }
+
+ // If unknownDecoder is not going to be launched, call
+ // EnsureOpaqueResponseIsAllowedAfterSniff immediately.
+ if (!unknownDecoderStarted) {
+ if (opaqueResponse == OpaqueResponse::SniffCompressed) {
+ mListener = new nsCompressedAudioVideoImageDetector(
+ mListener, &HttpBaseChannel::CallTypeSniffers);
+ } else if (opaqueResponse == OpaqueResponse::Sniff) {
+ MOZ_DIAGNOSTIC_ASSERT(mORB);
+ nsresult rv = mORB->EnsureOpaqueResponseIsAllowedAfterSniff(this);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ // If the content is multipart/x-mixed-replace, we'll insert a MIME decoder
+ // in the pipeline to handle the content and pass it along to our
+ // original listener. nsUnknownDecoder doesn't support detecting this type,
+ // so we only need to insert this using the response header's mime type.
+ //
+ // We only do this for unwrapped document loads, since we might want to send
+ // parts to the external protocol handler without leaving the parent process.
+ bool mustRunStreamFilterInParent = false;
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ RefPtr<DocumentLoadListener> docListener = do_QueryObject(parentChannel);
+ if (mResponseHead && docListener && docListener->GetChannel() == this) {
+ nsAutoCString contentType;
+ mResponseHead->ContentType(contentType);
+
+ if (contentType.Equals("multipart/x-mixed-replace"_ns)) {
+ nsCOMPtr<nsIStreamConverterService> convServ(
+ do_GetService("@mozilla.org/streamConverters;1", &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIStreamListener> toListener(mListener);
+ nsCOMPtr<nsIStreamListener> fromListener;
+
+ rv = convServ->AsyncConvertData("multipart/x-mixed-replace", "*/*",
+ toListener, nullptr,
+ getter_AddRefs(fromListener));
+ if (NS_SUCCEEDED(rv)) {
+ mListener = fromListener;
+ mustRunStreamFilterInParent = true;
+ }
+ }
+ }
+ }
+
+ // If we installed a multipart converter, then we need to add StreamFilter
+ // object before it, so that extensions see the un-parsed original stream.
+ // We may want to add an option for extensions to opt-in to proper multipart
+ // handling.
+ // If not, then pass the StreamFilter promise on to DocumentLoadListener,
+ // where it'll be added in the content process.
+ for (StreamFilterRequest& request : mStreamFilterRequests) {
+ if (mustRunStreamFilterInParent) {
+ mozilla::ipc::Endpoint<extensions::PStreamFilterParent> parent;
+ mozilla::ipc::Endpoint<extensions::PStreamFilterChild> child;
+ nsresult rv = extensions::PStreamFilter::CreateEndpoints(&parent, &child);
+ if (NS_FAILED(rv)) {
+ request.mPromise->Reject(false, __func__);
+ } else {
+ extensions::StreamFilterParent::Attach(this, std::move(parent));
+ request.mPromise->Resolve(std::move(child), __func__);
+ }
+ } else {
+ if (docListener) {
+ docListener->AttachStreamFilter()->ChainTo(request.mPromise.forget(),
+ __func__);
+ } else {
+ request.mPromise->Reject(false, __func__);
+ }
+ }
+ request.mPromise = nullptr;
+ }
+ mStreamFilterRequests.Clear();
+ StoreTracingEnabled(false);
+
+ if (mResponseHead && !mResponseHead->HasContentCharset()) {
+ mResponseHead->SetContentCharset(mContentCharsetHint);
+ }
+
+ if (mCacheEntry && LoadCacheEntryIsWriteOnly()) {
+ SetCachedContentType();
+ }
+
+ LOG((" calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
+ mListener.get()));
+
+ // About to call OnStartRequest, dismiss the guard object.
+ onStartGuard.release();
+
+ if (mListener) {
+ MOZ_ASSERT(!LoadOnStartRequestCalled(),
+ "We should not call OsStartRequest twice");
+ nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
+ StoreOnStartRequestCalled(true);
+ rv = deleteProtector->OnStartRequest(this);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ NS_WARNING("OnStartRequest skipped because of null listener");
+ StoreOnStartRequestCalled(true);
+ }
+
+ // Install stream converter if required.
+ // Normally, we expect the listener to disable content conversion during
+ // OnStartRequest if it wants to handle it itself (which is common case with
+ // HttpChannelParent, disabling so that it can be done in the content
+ // process). If we've installed an nsUnknownDecoder, then we won't yet have
+ // called OnStartRequest on the final listener (that happens after we send
+ // OnDataAvailable to the nsUnknownDecoder), so it can't yet have disabled
+ // content conversion.
+ // In that case, assume that the listener will disable content conversion,
+ // unless it's specifically told us that it won't.
+ if (!unknownDecoderStarted || LoadListenerRequiresContentConversion()) {
+ nsCOMPtr<nsIStreamListener> listener;
+ rv =
+ DoApplyContentConversions(mListener, getter_AddRefs(listener), nullptr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (listener) {
+ MOZ_ASSERT(!LoadDataSentToChildProcess(),
+ "DataSentToChildProcess being true means ODAs are sent to "
+ "the child process directly. We MUST NOT apply content "
+ "converter in this case.");
+ mListener = listener;
+ mCompressListener = listener;
+ StoreHasAppliedConversion(true);
+ }
+ }
+
+ // if this channel is for a download, close off access to the cache.
+ if (mCacheEntry && LoadChannelIsForDownload()) {
+ mCacheEntry->AsyncDoom(nullptr);
+
+ // We must keep the cache entry in case of partial request.
+ // Concurrent access is the same, we need the entry in
+ // OnStopRequest.
+ // We also need the cache entry when racing cache with network to find
+ // out what is the source of the data.
+ if (!LoadCachedContentIsPartial() && !LoadConcurrentCacheAccess() &&
+ !(mRaceCacheWithNetwork &&
+ mFirstResponseSource == RESPONSE_FROM_CACHE)) {
+ CloseCacheEntry(false);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::GetHttpProxyConnectResponseCode(
+ int32_t* aResponseCode) {
+ NS_ENSURE_ARG_POINTER(aResponseCode);
+
+ if (mConnectionInfo && mConnectionInfo->UsingConnect()) {
+ *aResponseCode = mProxyConnectResponseCode;
+ } else {
+ *aResponseCode = -1;
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus) {
+ // Failure to set up a proxy tunnel via CONNECT means one of the following:
+ // 1) Proxy wants authorization, or forbids.
+ // 2) DNS at proxy couldn't resolve target URL.
+ // 3) Proxy connection to target failed or timed out.
+ // 4) Eve intercepted our CONNECT, and is replying with malicious HTML.
+ //
+ // Our current architecture would parse the proxy's response content with
+ // the permission of the target URL. Given #4, we must avoid rendering the
+ // body of the reply, and instead give the user a (hopefully helpful)
+ // boilerplate error page, based on just the HTTP status of the reply.
+
+ MOZ_ASSERT(mConnectionInfo->UsingConnect(),
+ "proxy connect failed but not using CONNECT?");
+ nsresult rv = HttpProxyResponseToErrorCode(httpStatus);
+ LOG(("Cancelling failed proxy CONNECT [this=%p httpStatus=%u]\n", this,
+ httpStatus));
+
+ // Make sure the connection is thrown away as it can be in a bad state
+ // and the proxy may just hang on the next request.
+ MOZ_ASSERT(mTransaction);
+ mTransaction->DontReuseConnection();
+
+ Cancel(rv);
+ {
+ nsresult rv = CallOnStartRequest();
+ if (NS_FAILED(rv)) {
+ LOG(("CallOnStartRequest failed [this=%p httpStatus=%u rv=%08x]\n", this,
+ httpStatus, static_cast<uint32_t>(rv)));
+ }
+ }
+ return rv;
+}
+
+static void GetSTSConsoleErrorTag(uint32_t failureResult,
+ nsAString& consoleErrorTag) {
+ switch (failureResult) {
+ case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
+ consoleErrorTag = u"STSCouldNotParseHeader"_ns;
+ break;
+ case nsISiteSecurityService::ERROR_NO_MAX_AGE:
+ consoleErrorTag = u"STSNoMaxAge"_ns;
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
+ consoleErrorTag = u"STSMultipleMaxAges"_ns;
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
+ consoleErrorTag = u"STSInvalidMaxAge"_ns;
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
+ consoleErrorTag = u"STSMultipleIncludeSubdomains"_ns;
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
+ consoleErrorTag = u"STSInvalidIncludeSubdomains"_ns;
+ break;
+ case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
+ consoleErrorTag = u"STSCouldNotSaveState"_ns;
+ break;
+ default:
+ consoleErrorTag = u"STSUnknownError"_ns;
+ break;
+ }
+}
+
+/**
+ * Process an HTTP Strict Transport Security (HSTS) header.
+ */
+nsresult nsHttpChannel::ProcessHSTSHeader(nsITransportSecurityInfo* aSecInfo) {
+ nsHttpAtom atom(nsHttp::ResolveAtom("Strict-Transport-Security"_ns));
+
+ nsAutoCString securityHeader;
+ nsresult rv = mResponseHead->GetHeader(atom, securityHeader);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG(("nsHttpChannel: No %s header, continuing load.\n", atom.get()));
+ return NS_OK;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!aSecInfo) {
+ LOG(("nsHttpChannel::ProcessHSTSHeader: no securityInfo?"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsITransportSecurityInfo::OverridableErrorCategory overridableErrorCategory;
+ rv = aSecInfo->GetOverridableErrorCategory(&overridableErrorCategory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (overridableErrorCategory !=
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET) {
+ LOG(
+ ("nsHttpChannel::ProcessHSTSHeader: untrustworthy connection - not "
+ "processing header"));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+
+ OriginAttributes originAttributes;
+ if (NS_WARN_IF(!StoragePrincipalHelper::GetOriginAttributesForHSTS(
+ this, originAttributes))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t failureResult;
+ rv = sss->ProcessHeader(mURI, securityHeader, originAttributes, nullptr,
+ nullptr, &failureResult);
+ if (NS_FAILED(rv)) {
+ nsAutoString consoleErrorCategory(u"Invalid HSTS Headers"_ns);
+ nsAutoString consoleErrorTag;
+ GetSTSConsoleErrorTag(failureResult, consoleErrorTag);
+ Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
+ LOG(("nsHttpChannel: Failed to parse %s header, continuing load.\n",
+ atom.get()));
+ }
+ return NS_OK;
+}
+
+/**
+ * Decide whether or not to remember Strict-Transport-Security, and whether
+ * or not to enforce channel integrity.
+ *
+ * @return NS_ERROR_FAILURE if there's security information missing even though
+ * it's an HTTPS connection.
+ */
+nsresult nsHttpChannel::ProcessSecurityHeaders() {
+ // If this channel is not loading securely, STS or PKP doesn't do anything.
+ // In the case of HSTS, the upgrade to HTTPS takes place earlier in the
+ // channel load process.
+ if (!mURI->SchemeIs("https")) {
+ return NS_OK;
+ }
+
+ if (IsBrowsingContextDiscarded()) {
+ return NS_OK;
+ }
+
+ nsAutoCString asciiHost;
+ nsresult rv = mURI->GetAsciiHost(asciiHost);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ // If the channel is not a hostname, but rather an IP, do not process STS
+ // or PKP headers
+ if (HostIsIPLiteral(asciiHost)) {
+ return NS_OK;
+ }
+
+ // mSecurityInfo may not always be present, and if it's not then it is okay
+ // to just disregard any security headers since we know nothing about the
+ // security of the connection.
+ NS_ENSURE_TRUE(mSecurityInfo, NS_OK);
+
+ // Only process HSTS headers for first-party loads. This prevents a
+ // proliferation of useless HSTS state for partitioned third parties.
+ if (!mLoadInfo->GetIsThirdPartyContextToTopWindow()) {
+ rv = ProcessHSTSHeader(mSecurityInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+bool nsHttpChannel::IsHTTPS() { return mURI->SchemeIs("https"); }
+
+void nsHttpChannel::ProcessSSLInformation() {
+ // If this is HTTPS, record any use of RSA so that Key Exchange Algorithm
+ // can be whitelisted for TLS False Start in future sessions. We could
+ // do the same for DH but its rarity doesn't justify the lookup.
+
+ if (mCanceled || NS_FAILED(mStatus) || !mSecurityInfo || !IsHTTPS() ||
+ mPrivateBrowsing) {
+ return;
+ }
+
+ if (!mSecurityInfo) {
+ return;
+ }
+
+ uint32_t state;
+ if (NS_SUCCEEDED(mSecurityInfo->GetSecurityState(&state)) &&
+ (state & nsIWebProgressListener::STATE_IS_BROKEN)) {
+ // Send weak crypto warnings to the web console
+ if (state & nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) {
+ nsString consoleErrorTag = u"WeakCipherSuiteWarning"_ns;
+ nsString consoleErrorCategory = u"SSL"_ns;
+ Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
+ }
+ }
+
+ uint16_t tlsVersion;
+ nsresult rv = mSecurityInfo->GetProtocolVersion(&tlsVersion);
+ if (NS_SUCCEEDED(rv) &&
+ tlsVersion != nsITransportSecurityInfo::TLS_VERSION_1_2 &&
+ tlsVersion != nsITransportSecurityInfo::TLS_VERSION_1_3) {
+ nsString consoleErrorTag = u"DeprecatedTLSVersion2"_ns;
+ nsString consoleErrorCategory = u"TLS"_ns;
+ Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
+ }
+}
+
+void nsHttpChannel::ProcessAltService() {
+ // e.g. Alt-Svc: h2=":443"; ma=60
+ // e.g. Alt-Svc: h2="otherhost:443"
+ // Alt-Svc = 1#( alternative *( OWS ";" OWS parameter ) )
+ // alternative = protocol-id "=" alt-authority
+ // protocol-id = token ; percent-encoded ALPN protocol identifier
+ // alt-authority = quoted-string ; containing [ uri-host ] ":" port
+
+ if (!LoadAllowAltSvc()) { // per channel opt out
+ return;
+ }
+
+ if (mWebTransportSessionEventListener) {
+ return;
+ }
+
+ if (!gHttpHandler->AllowAltSvc() || (mCaps & NS_HTTP_DISALLOW_SPDY)) {
+ return;
+ }
+
+ if (IsBrowsingContextDiscarded()) {
+ return;
+ }
+
+ nsAutoCString scheme;
+ mURI->GetScheme(scheme);
+ bool isHttp = scheme.EqualsLiteral("http");
+ if (!isHttp && !scheme.EqualsLiteral("https")) {
+ return;
+ }
+
+ nsAutoCString altSvc;
+ Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, altSvc);
+ if (altSvc.IsEmpty()) {
+ return;
+ }
+
+ if (!nsHttp::IsReasonableHeaderValue(altSvc)) {
+ LOG(("Alt-Svc Response Header seems unreasonable - skipping\n"));
+ return;
+ }
+
+ nsAutoCString originHost;
+ int32_t originPort = 80;
+ mURI->GetPort(&originPort);
+ if (NS_FAILED(mURI->GetAsciiHost(originHost))) {
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+
+ if (mProxyInfo) {
+ proxyInfo = do_QueryInterface(mProxyInfo);
+ }
+
+ OriginAttributes originAttributes;
+ // Regular principal in case we have a proxy.
+ if (proxyInfo &&
+ !StaticPrefs::privacy_partition_network_state_connection_with_proxy()) {
+ StoragePrincipalHelper::GetOriginAttributes(
+ this, originAttributes, StoragePrincipalHelper::eRegularPrincipal);
+ } else {
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(
+ this, originAttributes);
+ }
+
+ AltSvcMapping::ProcessHeader(
+ altSvc, scheme, originHost, originPort, mUsername, mPrivateBrowsing,
+ callbacks, proxyInfo, mCaps & NS_HTTP_DISALLOW_SPDY, originAttributes);
+}
+
+nsresult nsHttpChannel::ProcessResponse() {
+ uint32_t httpStatus = mResponseHead->Status();
+
+ LOG(("nsHttpChannel::ProcessResponse [this=%p httpStatus=%u]\n", this,
+ httpStatus));
+
+ // Gather data on whether the transaction and page (if this is
+ // the initial page load) is being loaded with SSL.
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_IS_SSL,
+ mConnectionInfo->EndToEndSSL());
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+ Telemetry::Accumulate(Telemetry::HTTP_PAGELOAD_IS_SSL,
+ mConnectionInfo->EndToEndSSL());
+ }
+
+ if (Telemetry::CanRecordPrereleaseData()) {
+ // how often do we see something like Alt-Svc: "443:quic,p=1"
+ // and Alt-Svc: "h3-****"
+ nsAutoCString alt_service;
+ Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, alt_service);
+ uint32_t saw_quic = 0;
+ if (!alt_service.IsEmpty()) {
+ if (strstr(alt_service.get(), "h3-")) {
+ saw_quic = 1;
+ } else if (strstr(alt_service.get(), "quic")) {
+ saw_quic = 2;
+ }
+ }
+ Telemetry::Accumulate(Telemetry::HTTP_SAW_QUIC_ALT_PROTOCOL_2, saw_quic);
+
+ // Gather data on how many URLS get redirected
+ switch (httpStatus) {
+ case 200:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 0);
+ break;
+ case 301:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 1);
+ break;
+ case 302:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 2);
+ break;
+ case 304:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 3);
+ break;
+ case 307:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 4);
+ break;
+ case 308:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 5);
+ break;
+ case 400:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 6);
+ break;
+ case 401:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 7);
+ break;
+ case 403:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 8);
+ break;
+ case 404:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 9);
+ break;
+ case 500:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 10);
+ break;
+ default:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 11);
+ break;
+ }
+ }
+
+ // Let the predictor know whether this was a cacheable response or not so
+ // that it knows whether or not to possibly prefetch this resource in the
+ // future.
+ // We use GetReferringPage because mReferrerInfo may not be set at all(this is
+ // especially useful in xpcshell tests, where we don't have an actual pageload
+ // to get a referrer from).
+ nsCOMPtr<nsIURI> referrer = GetReferringPage();
+ if (!referrer && mReferrerInfo) {
+ referrer = mReferrerInfo->GetOriginalReferrer();
+ }
+
+ if (referrer) {
+ nsCOMPtr<nsILoadContextInfo> lci = GetLoadContextInfo(this);
+ mozilla::net::Predictor::UpdateCacheability(
+ referrer, mURI, httpStatus, mRequestHead, mResponseHead.get(), lci,
+ IsThirdPartyTrackingResource());
+ }
+
+ // Only allow 407 (authentication required) to continue
+ if (mTransaction && mTransaction->ProxyConnectFailed() && httpStatus != 407) {
+ return ProcessFailedProxyConnect(httpStatus);
+ }
+
+ MOZ_ASSERT(!mCachedContentIsValid || mRaceCacheWithNetwork,
+ "We should not be hitting the network if we have valid cached "
+ "content unless we are racing the network and cache");
+
+ ProcessSSLInformation();
+
+ // notify "http-on-examine-response" observers
+ gHttpHandler->OnExamineResponse(this);
+
+ return ContinueProcessResponse1();
+}
+
+void nsHttpChannel::AsyncContinueProcessResponse() {
+ nsresult rv;
+ rv = ContinueProcessResponse1();
+ if (NS_FAILED(rv)) {
+ // A synchronous failure here would normally be passed as the return
+ // value from OnStartRequest, which would in turn cancel the request.
+ // If we're continuing asynchronously, we need to cancel the request
+ // ourselves.
+ Unused << Cancel(rv);
+ }
+}
+
+nsresult nsHttpChannel::ContinueProcessResponse1() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+ nsresult rv = NS_OK;
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to finish processing response [this=%p]\n",
+ this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->AsyncContinueProcessResponse();
+ return NS_OK;
+ };
+ return NS_OK;
+ }
+
+ // Check if request was cancelled during http-on-examine-response.
+ if (mCanceled) {
+ return CallOnStartRequest();
+ }
+
+ uint32_t httpStatus = mResponseHead->Status();
+
+ // STS, Cookies and Alt-Service should not be handled on proxy failure.
+ // If proxy CONNECT response needs to complete, wait to process connection
+ // for Strict-Transport-Security.
+ if (!(mTransaction && mTransaction->ProxyConnectFailed()) &&
+ (httpStatus != 407)) {
+ if (nsAutoCString cookie;
+ NS_SUCCEEDED(mResponseHead->GetHeader(nsHttp::Set_Cookie, cookie))) {
+ SetCookie(cookie);
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ if (RefPtr<HttpChannelParent> httpParent =
+ do_QueryObject(parentChannel)) {
+ httpParent->SetCookie(std::move(cookie));
+ }
+ }
+
+ // Given a successful connection, process any STS or PKP data that's
+ // relevant.
+ DebugOnly<nsresult> rv = ProcessSecurityHeaders();
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load.");
+
+ if ((httpStatus < 500) && (httpStatus != 421)) {
+ ProcessAltService();
+ }
+ }
+
+ if (LoadConcurrentCacheAccess() && LoadCachedContentIsPartial() &&
+ httpStatus != 206) {
+ LOG(
+ (" only expecting 206 when doing partial request during "
+ "interrupted cache concurrent read"));
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ // handle unused username and password in url (see bug 232567)
+ if (httpStatus != 401 && httpStatus != 407) {
+ if (!mAuthRetryPending) {
+ rv = mAuthProvider->CheckForSuperfluousAuth();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ LOG((" CheckForSuperfluousAuth failed (%08x)",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ if (mCanceled) return CallOnStartRequest();
+
+ // reset the authentication's current continuation state because ourvr
+ // last authentication attempt has been completed successfully
+ rv = mAuthProvider->Disconnect(NS_ERROR_ABORT);
+ if (NS_FAILED(rv)) {
+ LOG((" Disconnect failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ mAuthProvider = nullptr;
+ LOG((" continuation state has been reset"));
+ }
+
+ // No process switch needed, continue as normal.
+ return ContinueProcessResponse2(rv);
+}
+
+nsresult nsHttpChannel::ContinueProcessResponse2(nsresult rv) {
+ if (NS_FAILED(rv) && !mCanceled) {
+ // The process switch failed, cancel this channel.
+ Cancel(rv);
+ return CallOnStartRequest();
+ }
+
+ if (mAPIRedirectToURI && !mCanceled) {
+ MOZ_ASSERT(!LoadOnStartRequestCalled());
+ nsCOMPtr<nsIURI> redirectTo;
+ mAPIRedirectToURI.swap(redirectTo);
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
+ rv = StartRedirectChannelToURI(redirectTo,
+ nsIChannelEventSink::REDIRECT_TEMPORARY);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
+ }
+
+ // Hack: ContinueProcessResponse3 uses NS_OK to detect successful
+ // redirects, so we distinguish this codepath (a non-redirect that's
+ // processing normally) by passing in a bogus error code.
+ return ContinueProcessResponse3(NS_BINDING_FAILED);
+}
+
+nsresult nsHttpChannel::ContinueProcessResponse3(nsresult rv) {
+ LOG(("nsHttpChannel::ContinueProcessResponse3 [this=%p, rv=%" PRIx32 "]",
+ this, static_cast<uint32_t>(rv)));
+
+ if (NS_SUCCEEDED(rv)) {
+ // redirectTo() has passed through, we don't want to go on with
+ // this channel. It will now be canceled by the redirect handling
+ // code that called this function.
+ return NS_OK;
+ }
+
+ rv = NS_OK;
+
+ uint32_t httpStatus = mResponseHead->Status();
+ bool transactionRestarted = mTransaction->TakeRestartedState();
+
+ // handle different server response categories. Note that we handle
+ // caching or not caching of error pages in
+ // nsHttpResponseHead::MustValidate; if you change this switch, update that
+ // one
+ switch (httpStatus) {
+ case 200:
+ case 203:
+ // Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
+ // So if a server does that and sends 200 instead of 206 that we
+ // expect, notify our caller.
+ // However, if we wanted to start from the beginning, let it go through
+ if (LoadResuming() && mStartPos != 0) {
+ LOG(("Server ignored our Range header, cancelling [this=%p]\n", this));
+ Cancel(NS_ERROR_NOT_RESUMABLE);
+ rv = CallOnStartRequest();
+ break;
+ }
+ // these can normally be cached
+ rv = ProcessNormal();
+ MaybeInvalidateCacheEntryForSubsequentGet();
+ break;
+ case 206:
+ if (LoadCachedContentIsPartial()) { // an internal byte range request...
+ auto func = [](auto* self, nsresult aRv) {
+ return self->ContinueProcessResponseAfterPartialContent(aRv);
+ };
+ rv = ProcessPartialContent(func);
+ // Directly call ContinueProcessResponseAfterPartialContent if channel
+ // is not suspended or ProcessPartialContent throws.
+ if (!mSuspendCount || NS_FAILED(rv)) {
+ return ContinueProcessResponseAfterPartialContent(rv);
+ }
+ return NS_OK;
+ } else {
+ mCacheInputStream.CloseAndRelease();
+ rv = ProcessNormal();
+ }
+ break;
+ case 300:
+ case 301:
+ case 302:
+ case 307:
+ case 308:
+ case 303:
+#if 0
+ case 305: // disabled as a security measure (see bug 187996).
+#endif
+ // don't store the response body for redirects
+ MaybeInvalidateCacheEntryForSubsequentGet();
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse4);
+ rv = AsyncProcessRedirection(httpStatus);
+ if (NS_FAILED(rv)) {
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse4);
+ LOG(("AsyncProcessRedirection failed [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ // don't cache failed redirect responses.
+ if (mCacheEntry) mCacheEntry->AsyncDoom(nullptr);
+ if (DoNotRender3xxBody(rv)) {
+ mStatus = rv;
+ DoNotifyListener();
+ } else {
+ rv = ContinueProcessResponse4(rv);
+ }
+ }
+ break;
+ case 304:
+ if (!ShouldBypassProcessNotModified()) {
+ auto func = [](auto* self, nsresult aRv) {
+ return self->ContinueProcessResponseAfterNotModified(aRv);
+ };
+ rv = ProcessNotModified(func);
+ // Directly call ContinueProcessResponseAfterNotModified if channel
+ // is not suspended or ProcessNotModified throws.
+ if (!mSuspendCount || NS_FAILED(rv)) {
+ return ContinueProcessResponseAfterNotModified(rv);
+ }
+ return NS_OK;
+ }
+
+ // Don't cache uninformative 304
+ if (LoadCustomConditionalRequest()) {
+ CloseCacheEntry(false);
+ }
+
+ if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
+ rv = ProcessNormal();
+ }
+ break;
+ case 401:
+ case 407:
+ if (MOZ_UNLIKELY(httpStatus == 407 && transactionRestarted)) {
+ // The transaction has been internally restarted. We want to
+ // authenticate to the proxy again, so reuse either cached credentials
+ // or use default credentials for NTLM/Negotiate. This prevents
+ // considering the previously used credentials as invalid.
+ mAuthProvider->ClearProxyIdent();
+ }
+ if (!LoadAuthRedirectedChannel() &&
+ MOZ_UNLIKELY(LoadCustomAuthHeader()) && httpStatus == 401) {
+ // When a custom auth header fails, we don't want to try
+ // any cached credentials, nor we want to ask the user.
+ // It's up to the consumer to re-try w/o setting a custom
+ // auth header if cached credentials should be attempted.
+ rv = NS_ERROR_FAILURE;
+ } else if (httpStatus == 401 &&
+ StaticPrefs::
+ network_auth_supress_auth_prompt_for_XFO_failures() &&
+ !nsContentSecurityUtils::CheckCSPFrameAncestorAndXFO(this)) {
+ // CSP Frame Ancestor and X-Frame-Options check has failed
+ // Do not prompt http auth - Bug 1629307
+ rv = NS_ERROR_FAILURE;
+ } else {
+ rv = mAuthProvider->ProcessAuthentication(
+ httpStatus, mConnectionInfo->EndToEndSSL() && mTransaction &&
+ mTransaction->ProxyConnectFailed());
+ }
+ if (rv == NS_ERROR_IN_PROGRESS) {
+ // authentication prompt has been invoked and result
+ // is expected asynchronously
+ mIsAuthChannel = true;
+ mAuthRetryPending = true;
+ if (httpStatus == 407 ||
+ (mTransaction && mTransaction->ProxyConnectFailed())) {
+ StoreProxyAuthPending(true);
+ }
+
+ // suspend the transaction pump to stop receiving the
+ // unauthenticated content data. We will throw that data
+ // away when user provides credentials or resume the pump
+ // when user refuses to authenticate.
+ LOG(
+ ("Suspending the transaction, asynchronously prompting for "
+ "credentials"));
+ mTransactionPump->Suspend();
+
+#ifdef DEBUG
+ // This is for test purposes only. See bug 1683176 for details.
+ gHttpHandler->OnTransactionSuspendedDueToAuthentication(this);
+#endif
+ rv = NS_OK;
+ } else if (NS_FAILED(rv)) {
+ LOG(("ProcessAuthentication failed [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ if (mTransaction && mTransaction->ProxyConnectFailed()) {
+ return ProcessFailedProxyConnect(httpStatus);
+ }
+ if (!mAuthRetryPending) {
+ rv = mAuthProvider->CheckForSuperfluousAuth();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ LOG(("CheckForSuperfluousAuth failed [rv=%x]\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ rv = ProcessNormal();
+ } else {
+ mIsAuthChannel = true;
+ mAuthRetryPending = true;
+ if (StaticPrefs::network_auth_use_redirect_for_retries()) {
+ if (NS_SUCCEEDED(RedirectToNewChannelForAuthRetry())) {
+ return NS_OK;
+ }
+ mAuthRetryPending = false;
+ rv = ProcessNormal();
+ }
+ }
+ break;
+
+ case 408:
+ case 425:
+ case 429:
+ // Do not cache 408, 425 and 429.
+ CloseCacheEntry(false);
+ [[fallthrough]]; // process normally
+ default:
+ rv = ProcessNormal();
+ MaybeInvalidateCacheEntryForSubsequentGet();
+ break;
+ }
+
+ UpdateCacheDisposition(false, false);
+ return rv;
+}
+
+nsresult nsHttpChannel::ContinueProcessResponseAfterPartialContent(
+ nsresult aRv) {
+ LOG(
+ ("nsHttpChannel::ContinueProcessResponseAfterPartialContent "
+ "[this=%p, rv=%" PRIx32 "]",
+ this, static_cast<uint32_t>(aRv)));
+
+ UpdateCacheDisposition(false, NS_SUCCEEDED(aRv));
+ return aRv;
+}
+
+nsresult nsHttpChannel::ContinueProcessResponseAfterNotModified(nsresult aRv) {
+ LOG(
+ ("nsHttpChannel::ContinueProcessResponseAfterNotModified "
+ "[this=%p, rv=%" PRIx32 "]",
+ this, static_cast<uint32_t>(aRv)));
+
+ if (NS_SUCCEEDED(aRv)) {
+ StoreTransactionReplaced(true);
+ UpdateCacheDisposition(true, false);
+ return NS_OK;
+ }
+
+ LOG(("ProcessNotModified failed [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(aRv)));
+
+ // We cannot read from the cache entry, it might be in an
+ // incosistent state. Doom it and redirect the channel
+ // to the same URI to reload from the network.
+ mCacheInputStream.CloseAndRelease();
+ if (mCacheEntry) {
+ mCacheEntry->AsyncDoom(nullptr);
+ mCacheEntry = nullptr;
+ }
+
+ nsresult rv =
+ StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+
+ // Don't cache uninformative 304
+ if (LoadCustomConditionalRequest()) {
+ CloseCacheEntry(false);
+ }
+
+ if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
+ rv = ProcessNormal();
+ }
+
+ UpdateCacheDisposition(false, false);
+ return rv;
+}
+
+static void ReportHttpResponseVersion(HttpVersion version) {
+ if (Telemetry::CanRecordPrereleaseData()) {
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_VERSION,
+ static_cast<uint32_t>(version));
+ }
+
+ nsAutoCString versionLabel;
+ switch (version) {
+ case HttpVersion::v0_9:
+ case HttpVersion::v1_0:
+ case HttpVersion::v1_1:
+ versionLabel = "http_1"_ns;
+ break;
+ case HttpVersion::v2_0:
+ versionLabel = "http_2"_ns;
+ break;
+ case HttpVersion::v3_0:
+ versionLabel = "http_3"_ns;
+ break;
+ default:
+ versionLabel = "unknown"_ns;
+ break;
+ }
+ mozilla::glean::networking::http_response_version.Get(versionLabel).Add(1);
+}
+
+void nsHttpChannel::UpdateCacheDisposition(bool aSuccessfulReval,
+ bool aPartialContentUsed) {
+ if (mRaceDelay && !mRaceCacheWithNetwork &&
+ (LoadCachedContentIsPartial() || mDidReval)) {
+ if (aSuccessfulReval || aPartialContentUsed) {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::CachedContentUsed);
+ } else {
+ AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::
+ CachedContentNotUsed);
+ }
+ }
+
+ if (Telemetry::CanRecordPrereleaseData()) {
+ CacheDisposition cacheDisposition;
+ if (!mDidReval) {
+ cacheDisposition = kCacheMissed;
+ } else if (aSuccessfulReval) {
+ cacheDisposition = kCacheHitViaReval;
+ } else {
+ cacheDisposition = kCacheMissedViaReval;
+ }
+ AccumulateCacheHitTelemetry(cacheDisposition, this);
+ mCacheDisposition = cacheDisposition;
+
+ if (mResponseHead->Version() == HttpVersion::v0_9) {
+ // DefaultPortTopLevel = 0, DefaultPortSubResource = 1,
+ // NonDefaultPortTopLevel = 2, NonDefaultPortSubResource = 3
+ uint32_t v09Info = 0;
+ if (!(mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)) {
+ v09Info += 1;
+ }
+ if (mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort()) {
+ v09Info += 2;
+ }
+ Telemetry::Accumulate(Telemetry::HTTP_09_INFO, v09Info);
+ }
+ }
+
+ ReportHttpResponseVersion(mResponseHead->Version());
+}
+
+nsresult nsHttpChannel::ContinueProcessResponse4(nsresult rv) {
+ bool doNotRender = DoNotRender3xxBody(rv);
+
+ if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) {
+ bool isHTTP =
+ mRedirectURI->SchemeIs("http") || mRedirectURI->SchemeIs("https");
+ if (!isHTTP) {
+ // This was a blocked attempt to redirect and subvert the system by
+ // redirecting to another protocol (perhaps javascript:)
+ // In that case we want to throw an error instead of displaying the
+ // non-redirected response body.
+ LOG(("ContinueProcessResponse4 detected rejected Non-HTTP Redirection"));
+ doNotRender = true;
+ rv = NS_ERROR_CORRUPTED_CONTENT;
+ }
+ }
+
+ if (doNotRender) {
+ Cancel(rv);
+ DoNotifyListener();
+ return rv;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ UpdateInhibitPersistentCachingFlag();
+
+ MaybeCreateCacheEntryWhenRCWN();
+
+ rv = InitCacheEntry();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ContinueProcessResponse4 "
+ "failed to init cache entry [rv=%x]\n",
+ static_cast<uint32_t>(rv)));
+ }
+ CloseCacheEntry(false);
+ return NS_OK;
+ }
+
+ LOG(("ContinueProcessResponse4 got failure result [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ if (mTransaction && mTransaction->ProxyConnectFailed()) {
+ return ProcessFailedProxyConnect(mRedirectType);
+ }
+ return ProcessNormal();
+}
+
+nsresult nsHttpChannel::ProcessNormal() {
+ LOG(("nsHttpChannel::ProcessNormal [this=%p]\n", this));
+
+ return ContinueProcessNormal(NS_OK);
+}
+
+nsresult nsHttpChannel::ContinueProcessNormal(nsresult rv) {
+ LOG(("nsHttpChannel::ContinueProcessNormal [this=%p]", this));
+
+ if (NS_FAILED(rv)) {
+ // Fill the failure status here, we have failed to fall back, thus we
+ // have to report our status as failed.
+ mStatus = rv;
+ DoNotifyListener();
+ return rv;
+ }
+
+ rv = ProcessCrossOriginSecurityHeaders();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ HandleAsyncAbort();
+ return rv;
+ }
+
+ // if we're here, then any byte-range requests failed to result in a partial
+ // response. we must clear this flag to prevent BufferPartialContent from
+ // being called inside our OnDataAvailable (see bug 136678).
+ StoreCachedContentIsPartial(false);
+
+ UpdateInhibitPersistentCachingFlag();
+
+ MaybeCreateCacheEntryWhenRCWN();
+
+ // this must be called before firing OnStartRequest, since http clients,
+ // such as imagelib, expect our cache entry to already have the correct
+ // expiration time (bug 87710).
+ if (mCacheEntry) {
+ rv = InitCacheEntry();
+ if (NS_FAILED(rv)) CloseCacheEntry(true);
+ }
+
+ // Check that the server sent us what we were asking for
+ if (LoadResuming()) {
+ // Create an entity id from the response
+ nsAutoCString id;
+ rv = GetEntityID(id);
+ if (NS_FAILED(rv)) {
+ // If creating an entity id is not possible -> error
+ Cancel(NS_ERROR_NOT_RESUMABLE);
+ } else if (mResponseHead->Status() != 206 &&
+ mResponseHead->Status() != 200) {
+ // Probably 404 Not Found, 412 Precondition Failed or
+ // 416 Invalid Range -> error
+ LOG(("Unexpected response status while resuming, aborting [this=%p]\n",
+ this));
+ Cancel(NS_ERROR_ENTITY_CHANGED);
+ }
+ // If we were passed an entity id, verify it's equal to the server's
+ else if (!mEntityID.IsEmpty()) {
+ if (!mEntityID.Equals(id)) {
+ LOG(("Entity mismatch, expected '%s', got '%s', aborting [this=%p]",
+ mEntityID.get(), id.get(), this));
+ Cancel(NS_ERROR_ENTITY_CHANGED);
+ }
+ }
+ }
+
+ rv = CallOnStartRequest();
+ if (NS_FAILED(rv)) return rv;
+
+ // install cache listener if we still have a cache entry open
+ if (mCacheEntry && !LoadCacheEntryIsReadOnly()) {
+ rv = InstallCacheListener();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::PromptTempRedirect() {
+ if (!gHttpHandler->PromptTempRedirect()) {
+ return NS_OK;
+ }
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ rv =
+ bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoString messageString;
+ rv = stringBundle->GetStringFromName("RepostFormData", messageString);
+ if (NS_SUCCEEDED(rv)) {
+ bool repost = false;
+
+ nsCOMPtr<nsIPrompt> prompt;
+ GetCallback(prompt);
+ if (!prompt) return NS_ERROR_NO_INTERFACE;
+
+ prompt->Confirm(nullptr, messageString.get(), &repost);
+ if (!repost) return NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::ProxyFailover() {
+ LOG(("nsHttpChannel::ProxyFailover [this=%p]\n", this));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus,
+ getter_AddRefs(pi));
+#ifdef MOZ_PROXY_DIRECT_FAILOVER
+ if (NS_FAILED(rv)) {
+ if (!StaticPrefs::network_proxy_failover_direct()) {
+ return rv;
+ }
+ // If this request used a failed proxy and there is no failover available,
+ // fallback to DIRECT connections for conservative requests.
+ if (LoadBeConservative()) {
+ rv = pps->NewProxyInfo("direct"_ns, ""_ns, 0, ""_ns, ""_ns, 0, UINT32_MAX,
+ nullptr, getter_AddRefs(pi));
+ }
+#endif
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+#ifdef MOZ_PROXY_DIRECT_FAILOVER
+ }
+#endif
+
+ // XXXbz so where does this codepath remove us from the loadgroup,
+ // exactly?
+ return AsyncDoReplaceWithProxy(pi);
+}
+
+void nsHttpChannel::SetHTTPSSVCRecord(
+ already_AddRefed<nsIDNSHTTPSSVCRecord>&& aRecord) {
+ LOG(("nsHttpChannel::SetHTTPSSVCRecord [this=%p]\n", this));
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> record = aRecord;
+ MOZ_ASSERT(!mHTTPSSVCRecord);
+ mHTTPSSVCRecord.emplace(std::move(record));
+}
+
+void nsHttpChannel::HandleAsyncRedirectChannelToHttps() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async redirect to https [this=%p]\n",
+ this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->HandleAsyncRedirectChannelToHttps();
+ return NS_OK;
+ };
+ return;
+ }
+
+ nsresult rv = StartRedirectChannelToHttps();
+ if (NS_FAILED(rv)) {
+ rv = ContinueAsyncRedirectChannelToURI(rv);
+ if (NS_FAILED(rv)) {
+ LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
+ static_cast<uint32_t>(rv), this));
+ }
+ }
+}
+
+nsresult nsHttpChannel::StartRedirectChannelToHttps() {
+ LOG(("nsHttpChannel::HandleAsyncRedirectChannelToHttps() [STS]\n"));
+
+ nsCOMPtr<nsIURI> upgradedURI;
+ nsresult rv = NS_GetSecureUpgradedURI(mURI, getter_AddRefs(upgradedURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return StartRedirectChannelToURI(
+ upgradedURI, nsIChannelEventSink::REDIRECT_PERMANENT |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE);
+}
+
+void nsHttpChannel::HandleAsyncAPIRedirect() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+ MOZ_ASSERT(mAPIRedirectToURI, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async API redirect [this=%p]\n", this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->HandleAsyncAPIRedirect();
+ return NS_OK;
+ };
+ return;
+ }
+
+ nsresult rv = StartRedirectChannelToURI(
+ mAPIRedirectToURI, nsIChannelEventSink::REDIRECT_PERMANENT);
+ if (NS_FAILED(rv)) {
+ rv = ContinueAsyncRedirectChannelToURI(rv);
+ if (NS_FAILED(rv)) {
+ LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
+ static_cast<uint32_t>(rv), this));
+ }
+ }
+}
+
+void nsHttpChannel::HandleAsyncRedirectToUnstrippedURI() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(
+ ("Waiting until resume to do async redirect to unstripped URI "
+ "[this=%p]\n",
+ this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->HandleAsyncRedirectToUnstrippedURI();
+ return NS_OK;
+ };
+ return;
+ }
+
+ nsCOMPtr<nsIURI> unstrippedURI;
+ mLoadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI));
+
+ // Clear the unstripped URI from the loadInfo before starting redirect in case
+ // endless redirect.
+ mLoadInfo->SetUnstrippedURI(nullptr);
+
+ nsresult rv = StartRedirectChannelToURI(
+ unstrippedURI, nsIChannelEventSink::REDIRECT_PERMANENT);
+
+ if (NS_FAILED(rv)) {
+ rv = ContinueAsyncRedirectChannelToURI(rv);
+ if (NS_FAILED(rv)) {
+ LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
+ static_cast<uint32_t>(rv), this));
+ }
+ }
+}
+nsresult nsHttpChannel::RedirectToNewChannelForAuthRetry() {
+ LOG(("nsHttpChannel::RedirectToNewChannelForAuthRetry %p", this));
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo = CloneLoadInfoForRedirect(
+ mURI, nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_AUTH_RETRY);
+
+ nsCOMPtr<nsIIOService> ioService;
+
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = gHttpHandler->NewProxiedChannel(mURI, mProxyInfo, mProxyResolveFlags,
+ mProxyURI, mLoadInfo,
+ getter_AddRefs(newChannel));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupReplacementChannel(mURI, newChannel, true,
+ nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_AUTH_RETRY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // rewind the upload stream
+ if (mUploadStream) {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
+ nsresult rv = NS_ERROR_NO_INTERFACE;
+ if (seekable) {
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ }
+
+ // This should not normally happen, but it's possible that big memory
+ // blobs originating in the other process can't be rewinded.
+ // In that case we just fail the request, otherwise the content length
+ // will not match and this load will never complete.
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(newChannel);
+
+ MOZ_ASSERT(mAuthProvider);
+ httpChannelImpl->mAuthProvider = std::move(mAuthProvider);
+
+ httpChannelImpl->mProxyInfo = mProxyInfo;
+
+ if ((mCaps & NS_HTTP_STICKY_CONNECTION) ||
+ mTransaction->HasStickyConnection()) {
+ mConnectionInfo = mTransaction->GetConnInfo();
+
+ httpChannelImpl->mTransactionSticky = mTransaction;
+
+ if (mTransaction->Http2Disabled()) {
+ httpChannelImpl->mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+ if (mTransaction->Http3Disabled()) {
+ httpChannelImpl->mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+ }
+ httpChannelImpl->mCaps |= NS_HTTP_STICKY_CONNECTION;
+ if (LoadAuthConnectionRestartable()) {
+ httpChannelImpl->mCaps |= NS_HTTP_CONNECTION_RESTARTABLE;
+ } else {
+ httpChannelImpl->mCaps &= ~NS_HTTP_CONNECTION_RESTARTABLE;
+ }
+
+ MOZ_ASSERT(mConnectionInfo);
+ httpChannelImpl->mConnectionInfo = mConnectionInfo->Clone();
+
+ // we need to store the state to skip unnecessary checks in the new channel
+ httpChannelImpl->StoreAuthRedirectedChannel(true);
+
+ // We must copy proxy and auth header to the new channel.
+ // Although the new channel can populate auth headers from auth cache, we
+ // would still like to use the auth headers generated in this channel. The
+ // main reason for doing this is that certain connection-based/stateful auth
+ // schemes like NTLM will fail when we try generate the credentials more than
+ // the number of times the server has presented us the challenge due to the
+ // usage of nonce in generating the credentials Copying the auth header will
+ // bypass generation of the credentials
+ nsAutoCString authVal;
+ if (NS_SUCCEEDED(GetRequestHeader("Proxy-Authorization"_ns, authVal))) {
+ httpChannelImpl->SetRequestHeader("Proxy-Authorization"_ns, authVal, false);
+ }
+ if (NS_SUCCEEDED(GetRequestHeader("Authorization"_ns, authVal))) {
+ httpChannelImpl->SetRequestHeader("Authorization"_ns, authVal, false);
+ }
+
+ httpChannelImpl->SetBlockAuthPrompt(LoadBlockAuthPrompt());
+ mRedirectChannel = newChannel;
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(
+ this, newChannel,
+ nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_AUTH_RETRY);
+
+ if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
+
+ // redirected channel will be opened after we receive the OnStopRequest
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+ mRedirectChannel = nullptr;
+ }
+
+ return rv;
+}
+nsresult nsHttpChannel::StartRedirectChannelToURI(nsIURI* upgradedURI,
+ uint32_t flags) {
+ nsresult rv = NS_OK;
+ LOG(("nsHttpChannel::StartRedirectChannelToURI()\n"));
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(upgradedURI, flags);
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel), upgradedURI,
+ redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupReplacementChannel(upgradedURI, newChannel, true, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mHTTPSSVCRecord) {
+ RefPtr<nsHttpChannel> httpChan = do_QueryObject(newChannel);
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> rec = mHTTPSSVCRecord.ref();
+ if (httpChan && rec) {
+ httpChan->SetHTTPSSVCRecord(rec.forget());
+ }
+ }
+
+ // Inform consumers about this fake redirect
+ mRedirectChannel = newChannel;
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
+
+ if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+
+ /* Remove the async call to ContinueAsyncRedirectChannelToURI().
+ * It is called directly by our callers upon return (to clean up
+ * the failed redirect). */
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::ContinueAsyncRedirectChannelToURI(nsresult rv) {
+ LOG(("nsHttpChannel::ContinueAsyncRedirectChannelToURI [this=%p]", this));
+
+ // Since we handle mAPIRedirectToURI also after on-examine-response handler
+ // rather drop it here to avoid any redirect loops, even just hypothetical.
+ mAPIRedirectToURI = nullptr;
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = OpenRedirectChannel(rv);
+ }
+
+ if (NS_FAILED(rv)) {
+ // Cancel the channel here, the update to https had been vetoed
+ // but from the security reasons we have to discard the whole channel
+ // load.
+ Cancel(rv);
+ }
+
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ }
+
+ if (NS_FAILED(rv) && !mCachePump && !mTransactionPump) {
+ // We have to manually notify the listener because there is not any pump
+ // that would call our OnStart/StopRequest after resume from waiting for
+ // the redirect callback.
+ DoNotifyListener();
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::OpenRedirectChannel(nsresult rv) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mRedirectChannel) {
+ LOG((
+ "nsHttpChannel::OpenRedirectChannel unexpected null redirect channel"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Make sure to do this after we received redirect veto answer,
+ // i.e. after all sinks had been notified
+ mRedirectChannel->SetOriginalURI(mOriginalURI);
+
+ // open new channel
+ rv = mRedirectChannel->AsyncOpen(mListener);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = NS_BINDING_REDIRECTED;
+
+ notifier.RedirectSucceeded();
+
+ ReleaseListeners();
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo* pi) {
+ LOG(("nsHttpChannel::AsyncDoReplaceWithProxy [this=%p pi=%p]", this, pi));
+ nsresult rv;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = gHttpHandler->NewProxiedChannel(mURI, pi, mProxyResolveFlags, mProxyURI,
+ mLoadInfo, getter_AddRefs(newChannel));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;
+
+ rv = SetupReplacementChannel(mURI, newChannel, true, flags);
+ if (NS_FAILED(rv)) return rv;
+
+ // Inform consumers about this fake redirect
+ mRedirectChannel = newChannel;
+
+ PushRedirectAsyncFunc(&nsHttpChannel::OpenRedirectChannel);
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
+
+ if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+ PopRedirectAsyncFunc(&nsHttpChannel::OpenRedirectChannel);
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::ResolveProxy() {
+ LOG(("nsHttpChannel::ResolveProxy [this=%p]\n", this));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // using the nsIProtocolProxyService2 allows a minor performance
+ // optimization, but if an add-on has only provided the original interface
+ // then it is ok to use that version.
+ nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
+ if (pps2) {
+ rv = pps2->AsyncResolve2(this, mProxyResolveFlags, this, nullptr,
+ getter_AddRefs(mProxyRequest));
+ } else {
+ rv = pps->AsyncResolve(static_cast<nsIChannel*>(this), mProxyResolveFlags,
+ this, nullptr, getter_AddRefs(mProxyRequest));
+ }
+
+ return rv;
+}
+
+bool nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry) {
+ nsresult rv;
+ nsAutoCString buf, metaKey;
+ Unused << mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
+
+ constexpr auto prefix = "request-"_ns;
+
+ // enumerate the elements of the Vary header...
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(buf, NS_HTTP_HEADER_SEP).ToRange()) {
+ LOG(
+ ("nsHttpChannel::ResponseWouldVary [channel=%p] "
+ "processing %s\n",
+ this, nsPromiseFlatCString(token).get()));
+ //
+ // if "*", then assume response would vary. technically speaking,
+ // "Vary: header, *" is not permitted, but we allow it anyways.
+ //
+ // We hash values of cookie-headers for the following reasons:
+ //
+ // 1- cookies can be very large in size
+ //
+ // 2- cookies may contain sensitive information. (for parity with
+ // out policy of not storing Set-cookie headers in the cache
+ // meta data, we likewise do not want to store cookie headers
+ // here.)
+ //
+ if (token.EqualsLiteral("*")) {
+ return true; // if we encounter this, just get out of here
+ }
+
+ // build cache meta data key...
+ metaKey = prefix + token;
+
+ // check the last value of the given request header to see if it has
+ // since changed. if so, then indeed the cached response is invalid.
+ nsCString lastVal;
+ entry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
+ LOG(
+ ("nsHttpChannel::ResponseWouldVary [channel=%p] "
+ "stored value = \"%s\"\n",
+ this, lastVal.get()));
+
+ // Look for value of "Cookie" in the request headers
+ nsHttpAtom atom = nsHttp::ResolveAtom(token);
+ nsAutoCString newVal;
+ bool hasHeader = NS_SUCCEEDED(mRequestHead.GetHeader(atom, newVal));
+ if (!lastVal.IsEmpty()) {
+ // value for this header in cache, but no value in request
+ if (!hasHeader) {
+ return true; // yes - response would vary
+ }
+
+ // If this is a cookie-header, stored metadata is not
+ // the value itself but the hash. So we also hash the
+ // outgoing value here in order to compare the hashes
+ nsAutoCString hash;
+ if (atom == nsHttp::Cookie) {
+ rv = Hash(newVal.get(), hash);
+ // If hash failed, be conservative (the cached hash
+ // exists at this point) and claim response would vary
+ if (NS_FAILED(rv)) return true;
+ newVal = hash;
+
+ LOG(
+ ("nsHttpChannel::ResponseWouldVary [this=%p] "
+ "set-cookie value hashed to %s\n",
+ this, newVal.get()));
+ }
+
+ if (!newVal.Equals(lastVal)) {
+ return true; // yes, response would vary
+ }
+
+ } else if (hasHeader) { // old value is empty, but newVal is set
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// We need to have an implementation of this function just so that we can keep
+// all references to mCallOnResume of type nsHttpChannel: it's not OK in C++
+// to set a member function ptr to a base class function.
+void nsHttpChannel::HandleAsyncAbort() {
+ HttpAsyncAborter<nsHttpChannel>::HandleAsyncAbort();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <byte-range>
+//-----------------------------------------------------------------------------
+
+bool nsHttpChannel::IsResumable(int64_t partialLen, int64_t contentLength,
+ bool ignoreMissingPartialLen) const {
+ bool hasContentEncoding =
+ mCachedResponseHead->HasHeader(nsHttp::Content_Encoding);
+
+ nsAutoCString etag;
+ Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, etag);
+ bool hasWeakEtag = !etag.IsEmpty() && StringBeginsWith(etag, "W/"_ns);
+
+ return (partialLen < contentLength) &&
+ (partialLen > 0 || ignoreMissingPartialLen) && !hasContentEncoding &&
+ !hasWeakEtag && mCachedResponseHead->IsResumable() &&
+ !LoadCustomConditionalRequest() && !mCachedResponseHead->NoStore();
+}
+
+nsresult nsHttpChannel::MaybeSetupByteRangeRequest(
+ int64_t partialLen, int64_t contentLength, bool ignoreMissingPartialLen) {
+ // Be pesimistic
+ StoreIsPartialRequest(false);
+
+ if (!IsResumable(partialLen, contentLength, ignoreMissingPartialLen)) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ // looks like a partial entry we can reuse; add If-Range
+ // and Range headers.
+ nsresult rv = SetupByteRangeRequest(partialLen);
+ if (NS_FAILED(rv)) {
+ // Make the request unconditional again.
+ UntieByteRangeRequest();
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::SetupByteRangeRequest(int64_t partialLen) {
+ // cached content has been found to be partial, add necessary request
+ // headers to complete cache entry.
+
+ // use strongest validator available...
+ nsAutoCString val;
+ Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, val);
+ if (val.IsEmpty()) {
+ Unused << mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
+ }
+ if (val.IsEmpty()) {
+ // if we hit this code it means mCachedResponseHead->IsResumable() is
+ // either broken or not being called.
+ MOZ_ASSERT_UNREACHABLE("no cache validator");
+ StoreIsPartialRequest(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ char buf[64];
+ SprintfLiteral(buf, "bytes=%" PRId64 "-", partialLen);
+
+ DebugOnly<nsresult> rv{};
+ rv = mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = mRequestHead.SetHeader(nsHttp::If_Range, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ StoreIsPartialRequest(true);
+
+ return NS_OK;
+}
+
+void nsHttpChannel::UntieByteRangeRequest() {
+ DebugOnly<nsresult> rv{};
+ rv = mRequestHead.ClearHeader(nsHttp::Range);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = mRequestHead.ClearHeader(nsHttp::If_Range);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+nsresult nsHttpChannel::ProcessPartialContent(
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueProcessResponseFunc) {
+ // ok, we've just received a 206
+ //
+ // we need to stream whatever data is in the cache out first, and then
+ // pick up whatever data is on the wire, writing it into the cache.
+
+ LOG(("nsHttpChannel::ProcessPartialContent [this=%p]\n", this));
+
+ NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
+
+ // Check if the content-encoding we now got is different from the one we
+ // got before
+ nsAutoCString contentEncoding, cachedContentEncoding;
+ // It is possible that there is not such headers
+ Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+ Unused << mCachedResponseHead->GetHeader(nsHttp::Content_Encoding,
+ cachedContentEncoding);
+ if (nsCRT::strcasecmp(contentEncoding.get(), cachedContentEncoding.get()) !=
+ 0) {
+ Cancel(NS_ERROR_INVALID_CONTENT_ENCODING);
+ return CallOnStartRequest();
+ }
+
+ nsresult rv;
+
+ int64_t cachedContentLength = mCachedResponseHead->ContentLength();
+ int64_t entitySize = mResponseHead->TotalEntitySize();
+
+ nsAutoCString contentRange;
+ Unused << mResponseHead->GetHeader(nsHttp::Content_Range, contentRange);
+ LOG(
+ ("nsHttpChannel::ProcessPartialContent [this=%p trans=%p] "
+ "original content-length %" PRId64 ", entity-size %" PRId64
+ ", content-range %s\n",
+ this, mTransaction.get(), cachedContentLength, entitySize,
+ contentRange.get()));
+
+ if ((entitySize >= 0) && (cachedContentLength >= 0) &&
+ (entitySize != cachedContentLength)) {
+ LOG(
+ ("nsHttpChannel::ProcessPartialContent [this=%p] "
+ "206 has different total entity size than the content length "
+ "of the original partially cached entity.\n",
+ this));
+
+ mCacheEntry->AsyncDoom(nullptr);
+ Cancel(NS_ERROR_CORRUPTED_CONTENT);
+ return CallOnStartRequest();
+ }
+
+ if (LoadConcurrentCacheAccess()) {
+ // We started to read cached data sooner than its write has been done.
+ // But the concurrent write has not finished completely, so we had to
+ // do a range request. Now let the content coming from the network
+ // be presented to consumers and also stored to the cache entry.
+
+ rv = InstallCacheListener(mLogicalOffset);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ // suspend the current transaction
+ rv = mTransactionPump->Suspend();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // merge any new headers with the cached response headers
+ mCachedResponseHead->UpdateHeaders(mResponseHead.get());
+
+ // update the cached response head
+ nsAutoCString head;
+ mCachedResponseHead->Flatten(head, true);
+ rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // make the cached response be the current response
+ mResponseHead = std::move(mCachedResponseHead);
+
+ UpdateInhibitPersistentCachingFlag();
+
+ rv = UpdateExpirationTime();
+ if (NS_FAILED(rv)) return rv;
+
+ // notify observers interested in looking at a response that has been
+ // merged with any cached headers (http-on-examine-merged-response).
+ gHttpHandler->OnExamineMergedResponse(this);
+
+ if (LoadConcurrentCacheAccess()) {
+ StoreCachedContentIsPartial(false);
+ // Leave the ConcurrentCacheAccess flag set, we want to use it
+ // to prevent duplicate OnStartRequest call on the target listener
+ // in case this channel is canceled before it gets its OnStartRequest
+ // from the http transaction.
+ return rv;
+ }
+
+ // Now we continue reading the network response.
+ // the cached content is valid, although incomplete.
+ mCachedContentIsValid = true;
+ return CallOrWaitForResume([aContinueProcessResponseFunc](auto* self) {
+ nsresult rv = self->ReadFromCache(false);
+ return aContinueProcessResponseFunc(self, rv);
+ });
+}
+
+nsresult nsHttpChannel::OnDoneReadingPartialCacheEntry(bool* streamDone) {
+ nsresult rv;
+
+ LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%p]", this));
+
+ // by default, assume we would have streamed all data or failed...
+ *streamDone = true;
+
+ // setup cache listener to append to cache entry
+ int64_t size;
+ rv = mCacheEntry->GetDataSize(&size);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = InstallCacheListener(size);
+ if (NS_FAILED(rv)) return rv;
+
+ // Entry is valid, do it now, after the output stream has been opened,
+ // otherwise when done earlier, pending readers would consider the cache
+ // entry still as partial (CacheEntry::GetDataSize would return the partial
+ // data size) and consumers would do the conditional request again.
+ rv = mCacheEntry->SetValid();
+ if (NS_FAILED(rv)) return rv;
+
+ // need to track the logical offset of the data being sent to our listener
+ mLogicalOffset = size;
+
+ // we're now completing the cached content, so we can clear this flag.
+ // this puts us in the state of a regular download.
+ StoreCachedContentIsPartial(false);
+ // The cache input stream pump is finished, we do not need it any more.
+ // (see bug 1313923)
+ mCachePump = nullptr;
+
+ // resume the transaction if it exists, otherwise the pipe contained the
+ // remaining part of the document and we've now streamed all of the data.
+ if (mTransactionPump) {
+ rv = mTransactionPump->Resume();
+ if (NS_SUCCEEDED(rv)) *streamDone = false;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("no transaction");
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <cache>
+//-----------------------------------------------------------------------------
+
+bool nsHttpChannel::ShouldBypassProcessNotModified() {
+ if (LoadCustomConditionalRequest()) {
+ LOG(("Bypassing ProcessNotModified due to custom conditional headers"));
+ return true;
+ }
+
+ if (!mDidReval) {
+ LOG(
+ ("Server returned a 304 response even though we did not send a "
+ "conditional request"));
+ return true;
+ }
+
+ return false;
+}
+
+nsresult nsHttpChannel::ProcessNotModified(
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueProcessResponseFunc) {
+ nsresult rv;
+
+ LOG(("nsHttpChannel::ProcessNotModified [this=%p]\n", this));
+
+ // Assert ShouldBypassProcessNotModified() has been checked before call to
+ // ProcessNotModified().
+ MOZ_ASSERT(!ShouldBypassProcessNotModified());
+
+ MOZ_ASSERT(mCachedResponseHead);
+ MOZ_ASSERT(mCacheEntry);
+ NS_ENSURE_TRUE(mCachedResponseHead && mCacheEntry, NS_ERROR_UNEXPECTED);
+
+ // If the 304 response contains a Last-Modified different than the
+ // one in our cache that is pretty suspicious and is, in at least the
+ // case of bug 716840, a sign of the server having previously corrupted
+ // our cache with a bad response. Take the minor step here of just dooming
+ // that cache entry so there is a fighting chance of getting things on the
+ // right track.
+
+ nsAutoCString lastModifiedCached;
+ nsAutoCString lastModified304;
+
+ rv =
+ mCachedResponseHead->GetHeader(nsHttp::Last_Modified, lastModifiedCached);
+ if (NS_SUCCEEDED(rv)) {
+ rv = mResponseHead->GetHeader(nsHttp::Last_Modified, lastModified304);
+ }
+
+ if (NS_SUCCEEDED(rv) && !lastModified304.Equals(lastModifiedCached)) {
+ LOG(
+ ("Cache Entry and 304 Last-Modified Headers Do Not Match "
+ "[%s] and [%s]\n",
+ lastModifiedCached.get(), lastModified304.get()));
+
+ mCacheEntry->AsyncDoom(nullptr);
+ Telemetry::Accumulate(Telemetry::CACHE_LM_INCONSISTENT, true);
+ }
+
+ // merge any new headers with the cached response headers
+ mCachedResponseHead->UpdateHeaders(mResponseHead.get());
+
+ // update the cached response head
+ nsAutoCString head;
+ mCachedResponseHead->Flatten(head, true);
+ rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // make the cached response be the current response
+ mResponseHead = std::move(mCachedResponseHead);
+
+ UpdateInhibitPersistentCachingFlag();
+
+ rv = UpdateExpirationTime();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = AddCacheEntryHeaders(mCacheEntry);
+ if (NS_FAILED(rv)) return rv;
+
+ // notify observers interested in looking at a reponse that has been
+ // merged with any cached headers
+ gHttpHandler->OnExamineMergedResponse(this);
+
+ mCachedContentIsValid = true;
+
+ // Tell other consumers the entry is OK to use
+ rv = mCacheEntry->SetValid();
+ if (NS_FAILED(rv)) return rv;
+
+ return CallOrWaitForResume([aContinueProcessResponseFunc](auto* self) {
+ nsresult rv = self->ReadFromCache(false);
+ return aContinueProcessResponseFunc(self, rv);
+ });
+}
+
+// Determines if a request is a byte range request for a subrange,
+// i.e. is a byte range request, but not a 0- byte range request.
+static bool IsSubRangeRequest(nsHttpRequestHead& aRequestHead) {
+ nsAutoCString byteRange;
+ if (NS_FAILED(aRequestHead.GetHeader(nsHttp::Range, byteRange))) {
+ return false;
+ }
+ return !byteRange.EqualsLiteral("bytes=0-");
+}
+
+nsresult nsHttpChannel::OpenCacheEntry(bool isHttps) {
+ // Drop this flag here
+ StoreConcurrentCacheAccess(0);
+
+ LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));
+
+ // make sure we're not abusing this function
+ MOZ_ASSERT(!mCacheEntry, "cache entry already open");
+
+ if (mRequestHead.IsPost()) {
+ // If the post id is already set then this is an attempt to replay
+ // a post transaction via the cache. Otherwise, we need a unique
+ // post id for this transaction.
+ if (mPostID == 0) mPostID = gHttpHandler->GenerateUniqueID();
+ } else if (!mRequestHead.IsGet() && !mRequestHead.IsHead()) {
+ // don't use the cache for other types of requests
+ return NS_OK;
+ }
+
+ return OpenCacheEntryInternal(isHttps);
+}
+
+nsresult nsHttpChannel::OpenCacheEntryInternal(bool isHttps) {
+ nsresult rv;
+
+ if (LoadResuming()) {
+ // We don't support caching for requests initiated
+ // via nsIResumableChannel.
+ return NS_OK;
+ }
+
+ // Don't cache byte range requests which are subranges, only cache 0-
+ // byte range requests.
+ if (IsSubRangeRequest(mRequestHead)) {
+ return NS_OK;
+ }
+
+ // Handle correctly WaitForCacheEntry
+ AutoCacheWaitFlags waitFlags(this);
+
+ nsAutoCString cacheKey;
+
+ nsCOMPtr<nsICacheStorageService> cacheStorageService(
+ components::CacheStorage::Service());
+ if (!cacheStorageService) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ mCacheEntryURI = mURI;
+
+ RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
+ if (!info) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t cacheEntryOpenFlags;
+ bool offline = gIOService->IsOffline();
+
+ RefPtr<mozilla::dom::BrowsingContext> bc;
+ mLoadInfo->GetBrowsingContext(getter_AddRefs(bc));
+
+ bool maybeRCWN = false;
+
+ nsAutoCString cacheControlRequestHeader;
+ Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
+ cacheControlRequestHeader);
+ CacheControlParser cacheControlRequest(cacheControlRequestHeader);
+ if (cacheControlRequest.NoStore()) {
+ return NS_OK;
+ }
+
+ if (offline || (mLoadFlags & INHIBIT_CACHING) ||
+ (bc && bc->Top()->GetForceOffline())) {
+ if (BYPASS_LOCAL_CACHE(mLoadFlags, LoadPreferCacheLoadOverBypass()) &&
+ !offline) {
+ return NS_OK;
+ }
+ cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY;
+ StoreCacheEntryIsReadOnly(true);
+ } else if (BYPASS_LOCAL_CACHE(mLoadFlags, LoadPreferCacheLoadOverBypass())) {
+ cacheEntryOpenFlags = nsICacheStorage::OPEN_TRUNCATE;
+ } else {
+ cacheEntryOpenFlags =
+ nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED;
+ }
+
+ // Remember the request is a custom conditional request so that we can
+ // process any 304 response correctly.
+ StoreCustomConditionalRequest(
+ mRequestHead.HasHeader(nsHttp::If_Modified_Since) ||
+ mRequestHead.HasHeader(nsHttp::If_None_Match) ||
+ mRequestHead.HasHeader(nsHttp::If_Unmodified_Since) ||
+ mRequestHead.HasHeader(nsHttp::If_Match) ||
+ mRequestHead.HasHeader(nsHttp::If_Range));
+
+ if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
+ rv = cacheStorageService->MemoryCacheStorage(
+ info, // ? choose app cache as well...
+ getter_AddRefs(cacheStorage));
+ } else if (LoadPinCacheContent()) {
+ rv = cacheStorageService->PinningCacheStorage(info,
+ getter_AddRefs(cacheStorage));
+ } else {
+ // Try to race only if we use disk cache storage
+ maybeRCWN = mRequestHead.IsSafeMethod();
+ rv = cacheStorageService->DiskCacheStorage(info,
+ getter_AddRefs(cacheStorage));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if ((mClassOfService.Flags() & nsIClassOfService::Leader) ||
+ (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)) {
+ cacheEntryOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
+ }
+
+ // Only for backward compatibility with the old cache back end.
+ // When removed, remove the flags and related code snippets.
+ if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
+ cacheEntryOpenFlags |= nsICacheStorage::OPEN_BYPASS_IF_BUSY;
+ }
+
+ if (mPostID) {
+ mCacheIdExtension.Append(nsPrintfCString("%d", mPostID));
+ }
+ if (LoadIsTRRServiceChannel()) {
+ mCacheIdExtension.Append("TRR");
+ }
+ if (mRequestHead.IsHead()) {
+ mCacheIdExtension.Append("HEAD");
+ }
+ bool isThirdParty = false;
+ if (StaticPrefs::network_fetch_cache_partition_cross_origin() &&
+ (NS_FAILED(mLoadInfo->TriggeringPrincipal()->IsThirdPartyChannel(
+ this, &isThirdParty)) ||
+ isThirdParty) &&
+ (mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_FETCH ||
+ mLoadInfo->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_XMLHTTPREQUEST ||
+ mLoadInfo->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST)) {
+ mCacheIdExtension.Append("FETCH");
+ }
+
+ mCacheOpenWithPriority = cacheEntryOpenFlags & nsICacheStorage::OPEN_PRIORITY;
+ mCacheQueueSizeWhenOpen =
+ CacheStorageService::CacheQueueSize(mCacheOpenWithPriority);
+
+ if ((mNetworkTriggerDelay || StaticPrefs::network_http_rcwn_enabled()) &&
+ maybeRCWN) {
+ bool hasAltData = false;
+ uint32_t sizeInKb = 0;
+ rv = cacheStorage->GetCacheIndexEntryAttrs(
+ mCacheEntryURI, mCacheIdExtension, &hasAltData, &sizeInKb);
+
+ // We will attempt to race the network vs the cache if we've found
+ // this entry in the cache index, and it has appropriate attributes
+ // (doesn't have alt-data, and has a small size)
+ if (NS_SUCCEEDED(rv) && !hasAltData &&
+ sizeInKb < StaticPrefs::network_http_rcwn_small_resource_size_kb()) {
+ MaybeRaceCacheWithNetwork();
+ }
+ }
+
+ if (!mCacheOpenDelay) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread");
+ if (mNetworkTriggered) {
+ mRaceCacheWithNetwork = StaticPrefs::network_http_rcwn_enabled();
+ }
+ rv = cacheStorage->AsyncOpenURI(mCacheEntryURI, mCacheIdExtension,
+ cacheEntryOpenFlags, this);
+ } else {
+ // We pass `this` explicitly as a parameter due to the raw pointer
+ // to refcounted object in lambda analysis.
+ mCacheOpenFunc = [cacheEntryOpenFlags,
+ cacheStorage](nsHttpChannel* self) -> void {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread");
+ cacheStorage->AsyncOpenURI(self->mCacheEntryURI, self->mCacheIdExtension,
+ cacheEntryOpenFlags, self);
+ };
+
+ // calls nsHttpChannel::Notify after `mCacheOpenDelay` milliseconds
+ auto callback = MakeRefPtr<TimerCallback>(this);
+ NS_NewTimerWithCallback(getter_AddRefs(mCacheOpenTimer), callback,
+ mCacheOpenDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ waitFlags.Keep(WAIT_FOR_CACHE_ENTRY);
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::CheckPartial(nsICacheEntry* aEntry, int64_t* aSize,
+ int64_t* aContentLength) {
+ return nsHttp::CheckPartial(
+ aEntry, aSize, aContentLength,
+ mCachedResponseHead ? mCachedResponseHead.get() : mResponseHead.get());
+}
+
+void nsHttpChannel::UntieValidationRequest() {
+ DebugOnly<nsresult> rv{};
+ // Make the request unconditional again.
+ rv = mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = mRequestHead.ClearHeader(nsHttp::If_None_Match);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = mRequestHead.ClearHeader(nsHttp::ETag);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* aResult) {
+ nsresult rv = NS_OK;
+
+ LOG(("nsHttpChannel::OnCacheEntryCheck enter [channel=%p entry=%p]", this,
+ entry));
+
+ mozilla::MutexAutoLock lock(mRCWNLock);
+
+ if (mRaceCacheWithNetwork && mFirstResponseSource == RESPONSE_FROM_NETWORK) {
+ LOG(
+ ("Not using cached response because we've already got one from the "
+ "network\n"));
+ *aResult = ENTRY_NOT_WANTED;
+
+ // Net-win indicates that mOnStartRequestTimestamp is from net.
+ int64_t savedTime =
+ (TimeStamp::Now() - mOnStartRequestTimestamp).ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME,
+ savedTime);
+ return NS_OK;
+ }
+ if (mRaceCacheWithNetwork && mFirstResponseSource == RESPONSE_PENDING) {
+ mOnCacheEntryCheckTimestamp = TimeStamp::Now();
+ }
+
+ nsAutoCString cacheControlRequestHeader;
+ Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
+ cacheControlRequestHeader);
+ CacheControlParser cacheControlRequest(cacheControlRequestHeader);
+
+ if (cacheControlRequest.NoStore()) {
+ LOG(
+ ("Not using cached response based on no-store request cache "
+ "directive\n"));
+ *aResult = ENTRY_NOT_WANTED;
+ return NS_OK;
+ }
+
+ // Be pessimistic: assume the cache entry has no useful data.
+ *aResult = ENTRY_WANTED;
+ mCachedContentIsValid = false;
+
+ nsCString buf;
+
+ // Get the method that was used to generate the cached response
+ rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool methodWasHead = buf.EqualsLiteral("HEAD");
+ bool methodWasGet = buf.EqualsLiteral("GET");
+
+ if (methodWasHead) {
+ // The cached response does not contain an entity. We can only reuse
+ // the response if the current request is also HEAD.
+ if (!mRequestHead.IsHead()) {
+ *aResult = ENTRY_NOT_WANTED;
+ return NS_OK;
+ }
+ }
+ buf.Adopt(nullptr);
+
+ // We'll need this value in later computations...
+ uint32_t lastModifiedTime;
+ rv = entry->GetLastModified(&lastModifiedTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Determine if this is the first time that this cache entry
+ // has been accessed during this session.
+ bool fromPreviousSession =
+ (gHttpHandler->SessionStartTime() > lastModifiedTime);
+
+ // Get the cached HTTP response headers
+ mCachedResponseHead = MakeUnique<nsHttpResponseHead>();
+
+ rv = nsHttp::GetHttpResponseHeadFromCacheEntry(entry,
+ mCachedResponseHead.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isCachedRedirect = WillRedirect(*mCachedResponseHead);
+
+ // Do not return 304 responses from the cache, and also do not return
+ // any other non-redirect 3xx responses from the cache (see bug 759043).
+ NS_ENSURE_TRUE((mCachedResponseHead->Status() / 100 != 3) || isCachedRedirect,
+ NS_ERROR_ABORT);
+
+ if (mCachedResponseHead->NoStore() && LoadCacheEntryIsReadOnly()) {
+ // This prevents loading no-store responses when navigating back
+ // while the browser is set to work offline.
+ LOG((" entry loading as read-only but is no-store, set INHIBIT_CACHING"));
+ mLoadFlags |= nsIRequest::INHIBIT_CACHING;
+ }
+
+ // Don't bother to validate items that are read-only,
+ // unless they are read-only because of INHIBIT_CACHING
+ if ((LoadCacheEntryIsReadOnly() &&
+ !(mLoadFlags & nsIRequest::INHIBIT_CACHING))) {
+ int64_t size, contentLength;
+ rv = CheckPartial(entry, &size, &contentLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (contentLength != int64_t(-1) && contentLength != size) {
+ *aResult = ENTRY_NOT_WANTED;
+ return NS_OK;
+ }
+
+ rv = OpenCacheInputStream(entry, true);
+ if (NS_SUCCEEDED(rv)) {
+ mCachedContentIsValid = true;
+ entry->MaybeMarkValid();
+ }
+ return rv;
+ }
+
+ bool wantCompleteEntry = false;
+
+ if (!methodWasHead && !isCachedRedirect) {
+ // If the cached content-length is set and it does not match the data
+ // size of the cached content, then the cached response is partial...
+ // either we need to issue a byte range request or we need to refetch
+ // the entire document.
+ //
+ // We exclude redirects from this check because we (usually) strip the
+ // entity when we store the cache entry, and even if we didn't, we
+ // always ignore a cached redirect's entity anyway. See bug 759043.
+ int64_t size, contentLength;
+ rv = CheckPartial(entry, &size, &contentLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (size == int64_t(-1)) {
+ LOG((" write is in progress"));
+ if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
+ LOG(
+ (" not interested in the entry, "
+ "LOAD_BYPASS_LOCAL_CACHE_IF_BUSY specified"));
+
+ *aResult = ENTRY_NOT_WANTED;
+ return NS_OK;
+ }
+
+ // Ignore !(size > 0) from the resumability condition
+ if (!IsResumable(size, contentLength, true)) {
+ if (IsNavigation()) {
+ LOG(
+ (" bypassing wait for the entry, "
+ "this is a navigational load"));
+ *aResult = ENTRY_NOT_WANTED;
+ return NS_OK;
+ }
+
+ LOG(
+ (" wait for entry completion, "
+ "response is not resumable"));
+
+ wantCompleteEntry = true;
+ } else {
+ StoreConcurrentCacheAccess(1);
+ }
+ } else if (contentLength != int64_t(-1) && contentLength != size) {
+ LOG(
+ ("Cached data size does not match the Content-Length header "
+ "[content-length=%" PRId64 " size=%" PRId64 "]\n",
+ contentLength, size));
+
+ rv = MaybeSetupByteRangeRequest(size, contentLength);
+ StoreCachedContentIsPartial(NS_SUCCEEDED(rv) && LoadIsPartialRequest());
+ if (LoadCachedContentIsPartial()) {
+ rv = OpenCacheInputStream(entry, false);
+ if (NS_FAILED(rv)) {
+ UntieByteRangeRequest();
+ return rv;
+ }
+
+ *aResult = ENTRY_NEEDS_REVALIDATION;
+ return NS_OK;
+ }
+
+ if (size == 0 && LoadCacheOnlyMetadata()) {
+ // Don't break cache entry load when the entry's data size
+ // is 0 and CacheOnlyMetadata flag is set. In that case we
+ // want to proceed since the LOAD_ONLY_IF_MODIFIED flag is
+ // also set.
+ MOZ_ASSERT(mLoadFlags & LOAD_ONLY_IF_MODIFIED);
+ } else {
+ return rv;
+ }
+ }
+ }
+
+ bool isHttps = mURI->SchemeIs("https");
+
+ bool doValidation = false;
+ bool doBackgroundValidation = false;
+ bool canAddImsHeader = true;
+
+ bool isForcedValid = false;
+ entry->GetIsForcedValid(&isForcedValid);
+ auto prefetchStatus = Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Used;
+
+ bool weaklyFramed, isImmutable;
+ nsHttp::DetermineFramingAndImmutability(entry, mCachedResponseHead.get(),
+ isHttps, &weaklyFramed, &isImmutable);
+
+ // Cached entry is not the entity we request (see bug #633743)
+ if (ResponseWouldVary(entry)) {
+ LOG(("Validating based on Vary headers returning TRUE\n"));
+ canAddImsHeader = false;
+ doValidation = true;
+ prefetchStatus = Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::WouldVary;
+ } else {
+ if (mCachedResponseHead->ExpiresInPast() ||
+ mCachedResponseHead->MustValidateIfExpired()) {
+ prefetchStatus = Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Expired;
+ }
+ doValidation = nsHttp::ValidationRequired(
+ isForcedValid, mCachedResponseHead.get(), mLoadFlags,
+ LoadAllowStaleCacheContent(), LoadForceValidateCacheContent(),
+ isImmutable, LoadCustomConditionalRequest(), mRequestHead, entry,
+ cacheControlRequest, fromPreviousSession, &doBackgroundValidation);
+ }
+
+ nsAutoCString requestedETag;
+ if (!doValidation &&
+ NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag)) &&
+ (methodWasGet || methodWasHead)) {
+ nsAutoCString cachedETag;
+ Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, cachedETag);
+ if (!cachedETag.IsEmpty() && (StringBeginsWith(cachedETag, "W/"_ns) ||
+ !requestedETag.Equals(cachedETag))) {
+ // User has defined If-Match header, if the cached entry is not
+ // matching the provided header value or the cached ETag is weak,
+ // force validation.
+ doValidation = true;
+ }
+ }
+
+ // Previous error should not be propagated.
+ rv = NS_OK;
+
+ if (!doValidation) {
+ //
+ // Check the authorization headers used to generate the cache entry.
+ // We must validate the cache entry if:
+ //
+ // 1) the cache entry was generated prior to this session w/
+ // credentials (see bug 103402).
+ // 2) the cache entry was generated w/o credentials, but would now
+ // require credentials (see bug 96705).
+ //
+ // NOTE: this does not apply to proxy authentication.
+ //
+ entry->GetMetaDataElement("auth", getter_Copies(buf));
+ doValidation =
+ (fromPreviousSession && !buf.IsEmpty()) ||
+ (buf.IsEmpty() && mRequestHead.HasHeader(nsHttp::Authorization));
+ if (doValidation) {
+ prefetchStatus = Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Auth;
+ }
+ }
+
+ // Bug #561276: We maintain a chain of cache-keys which returns cached
+ // 3xx-responses (redirects) in order to detect cycles. If a cycle is
+ // found, ignore the cached response and hit the net. Otherwise, use
+ // the cached response and add the cache-key to the chain. Note that
+ // a limited number of redirects (cached or not) is allowed and is
+ // enforced independently of this mechanism
+ if (!doValidation && isCachedRedirect) {
+ nsAutoCString cacheKey;
+ rv = GenerateCacheKey(mPostID, cacheKey);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ auto redirectedCachekeys = mRedirectedCachekeys.Lock();
+ auto& ref = redirectedCachekeys.ref();
+ if (!ref) {
+ ref = MakeUnique<nsTArray<nsCString>>();
+ } else if (ref->Contains(cacheKey)) {
+ doValidation = true;
+ }
+
+ LOG(("Redirection-chain %s key %s\n",
+ doValidation ? "contains" : "does not contain", cacheKey.get()));
+
+ // Append cacheKey if not in the chain already
+ if (!doValidation) {
+ ref->AppendElement(cacheKey);
+ } else {
+ prefetchStatus =
+ Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Redirect;
+ }
+ }
+
+ mCachedContentIsValid = !doValidation;
+
+ if (isForcedValid) {
+ // Telemetry value is only useful if this was a prefetched item
+ if (!doValidation) {
+ // Could have gotten to a funky state with some of the if chain above
+ // and in nsHttp::ValidationRequired. Make sure we get it right here.
+ prefetchStatus = Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Used;
+
+ entry->MarkForcedValidUse();
+ }
+ Telemetry::AccumulateCategorical(prefetchStatus);
+ }
+
+ if (doValidation) {
+ //
+ // now, we are definitely going to issue a HTTP request to the server.
+ // make it conditional if possible.
+ //
+ // do not attempt to validate no-store content, since servers will not
+ // expect it to be cached. (we only keep it in our cache for the
+ // purposes of back/forward, etc.)
+ //
+ // the request method MUST be either GET or HEAD (see bug 175641) and
+ // the cached response code must be < 400
+ //
+ // the cached content must not be weakly framed
+ //
+ // do not override conditional headers when consumer has defined its own
+ if (!mCachedResponseHead->NoStore() &&
+ (mRequestHead.IsGet() || mRequestHead.IsHead()) &&
+ !LoadCustomConditionalRequest() && !weaklyFramed &&
+ (mCachedResponseHead->Status() < 400)) {
+ if (LoadConcurrentCacheAccess()) {
+ // In case of concurrent read and also validation request we
+ // must wait for the current writer to close the output stream
+ // first. Otherwise, when the writer's job would have been interrupted
+ // before all the data were downloaded, we'd have to do a range request
+ // which would be a second request in line during this channel's
+ // life-time. nsHttpChannel is not designed to do that, so rather
+ // turn off concurrent read and wait for entry's completion.
+ // Then only re-validation or range-re-validation request will go out.
+ StoreConcurrentCacheAccess(0);
+ // This will cause that OnCacheEntryCheck is called again with the same
+ // entry after the writer is done.
+ wantCompleteEntry = true;
+ } else {
+ nsAutoCString val;
+ // Add If-Modified-Since header if a Last-Modified was given
+ // and we are allowed to do this (see bugs 510359 and 269303)
+ if (canAddImsHeader) {
+ Unused << mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
+ if (!val.IsEmpty()) {
+ rv = mRequestHead.SetHeader(nsHttp::If_Modified_Since, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+ // Add If-None-Match header if an ETag was given in the response
+ Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, val);
+ if (!val.IsEmpty()) {
+ rv = mRequestHead.SetHeader(nsHttp::If_None_Match, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ mDidReval = true;
+ }
+ }
+ }
+
+ if (mCachedContentIsValid || mDidReval) {
+ rv = OpenCacheInputStream(entry, mCachedContentIsValid);
+ if (NS_FAILED(rv)) {
+ // If we can't get the entity then we have to act as though we
+ // don't have the cache entry.
+ if (mDidReval) {
+ UntieValidationRequest();
+ mDidReval = false;
+ }
+ mCachedContentIsValid = false;
+ }
+ }
+
+ if (mDidReval) {
+ *aResult = ENTRY_NEEDS_REVALIDATION;
+ } else if (wantCompleteEntry) {
+ *aResult = RECHECK_AFTER_WRITE_FINISHED;
+ } else {
+ *aResult = ENTRY_WANTED;
+
+ if (doBackgroundValidation) {
+ PerformBackgroundCacheRevalidation();
+ }
+ }
+
+ if (mCachedContentIsValid) {
+ entry->MaybeMarkValid();
+ }
+
+ LOG(
+ ("nsHTTPChannel::OnCacheEntryCheck exit [this=%p doValidation=%d "
+ "result=%d]\n",
+ this, doValidation, *aResult));
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnCacheEntryAvailable(nsICacheEntry* entry, bool aNew,
+ nsresult status) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ LOG(
+ ("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p "
+ "new=%d status=%" PRIx32 "]\n",
+ this, entry, aNew, static_cast<uint32_t>(status)));
+
+ // if the channel's already fired onStopRequest, then we should ignore
+ // this event.
+ if (!LoadIsPending()) {
+ mCacheInputStream.CloseAndRelease();
+ return NS_OK;
+ }
+
+ rv = OnCacheEntryAvailableInternal(entry, aNew, status);
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ if (mRaceCacheWithNetwork && mNetworkTriggered &&
+ mFirstResponseSource != RESPONSE_FROM_CACHE) {
+ // Ignore the error if we're racing cache with network and the cache
+ // didn't win, The network part will handle cancelation or any other
+ // error. Otherwise we could end up calling the listener twice, see
+ // bug 1397593.
+ LOG(
+ (" not calling AsyncAbort() because we're racing cache with "
+ "network"));
+ } else {
+ Unused << AsyncAbort(rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::OnCacheEntryAvailableInternal(nsICacheEntry* entry,
+ bool aNew,
+ nsresult status) {
+ nsresult rv;
+
+ if (mCanceled) {
+ LOG(("channel was canceled [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
+ return mStatus;
+ }
+
+ if (mIgnoreCacheEntry) {
+ if (!entry || aNew) {
+ // We use this flag later to decide whether to report
+ // LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent. We didn't have
+ // an usable entry, so drop the flag.
+ mIgnoreCacheEntry = false;
+ }
+ entry = nullptr;
+ status = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = OnNormalCacheEntryAvailable(entry, aNew, status);
+
+ if (NS_FAILED(rv) && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // We may be waiting for more callbacks...
+ if (AwaitingCacheCallbacks()) {
+ return NS_OK;
+ }
+
+ if (mRaceCacheWithNetwork && ((mCacheEntry && !mCachedContentIsValid &&
+ (mDidReval || LoadCachedContentIsPartial())) ||
+ mIgnoreCacheEntry)) {
+ // We won't send the conditional request because the unconditional
+ // request was already sent (see bug 1377223).
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent);
+ }
+
+ if (mRaceCacheWithNetwork && mCachedContentIsValid) {
+ Unused << ReadFromCache(true);
+ }
+
+ return TriggerNetwork();
+}
+
+nsresult nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry* aEntry,
+ bool aNew,
+ nsresult aEntryStatus) {
+ StoreWaitForCacheEntry(LoadWaitForCacheEntry() & ~WAIT_FOR_CACHE_ENTRY);
+
+ if (NS_FAILED(aEntryStatus) || aNew) {
+ // Make sure this flag is dropped. It may happen the entry is doomed
+ // between OnCacheEntryCheck and OnCacheEntryAvailable.
+ mCachedContentIsValid = false;
+
+ // From the same reason remove any conditional headers added
+ // in OnCacheEntryCheck.
+ if (mDidReval) {
+ LOG((" Removing conditional request headers"));
+ UntieValidationRequest();
+ mDidReval = false;
+ }
+
+ if (LoadCachedContentIsPartial()) {
+ LOG((" Removing byte range request headers"));
+ UntieByteRangeRequest();
+ StoreCachedContentIsPartial(false);
+ }
+
+ if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+ // if this channel is only allowed to pull from the cache, then
+ // we must fail if we were unable to open a cache entry for read.
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+ }
+
+ if (NS_SUCCEEDED(aEntryStatus)) {
+ mCacheEntry = aEntry;
+ StoreCacheEntryIsWriteOnly(aNew);
+
+ if (!aNew && !mAsyncOpenTime.IsNull()) {
+ // We use microseconds for IO operations. For consistency let's use
+ // microseconds here too.
+ uint32_t duration = (TimeStamp::Now() - mAsyncOpenTime).ToMicroseconds();
+ bool isSlow = false;
+ if ((mCacheOpenWithPriority &&
+ mCacheQueueSizeWhenOpen >=
+ StaticPrefs::
+ network_http_rcwn_cache_queue_priority_threshold()) ||
+ (!mCacheOpenWithPriority &&
+ mCacheQueueSizeWhenOpen >=
+ StaticPrefs::network_http_rcwn_cache_queue_normal_threshold())) {
+ isSlow = true;
+ }
+ CacheFileUtils::CachePerfStats::AddValue(
+ CacheFileUtils::CachePerfStats::ENTRY_OPEN, duration, isSlow);
+ }
+ }
+
+ return NS_OK;
+}
+
+// Generates the proper cache-key for this instance of nsHttpChannel
+nsresult nsHttpChannel::GenerateCacheKey(uint32_t postID,
+ nsACString& cacheKey) {
+ AssembleCacheKey(mSpec.get(), postID, cacheKey);
+ return NS_OK;
+}
+
+// Assembles a cache-key from the given pieces of information and |mLoadFlags|
+void nsHttpChannel::AssembleCacheKey(const char* spec, uint32_t postID,
+ nsACString& cacheKey) {
+ cacheKey.Truncate();
+
+ if (mLoadFlags & LOAD_ANONYMOUS) {
+ cacheKey.AssignLiteral("anon&");
+ }
+
+ if (postID) {
+ char buf[32];
+ SprintfLiteral(buf, "id=%x&", postID);
+ cacheKey.Append(buf);
+ }
+
+ if (!cacheKey.IsEmpty()) {
+ cacheKey.AppendLiteral("uri=");
+ }
+
+ // Strip any trailing #ref from the URL before using it as the key
+ const char* p = strchr(spec, '#');
+ if (p) {
+ cacheKey.Append(spec, p - spec);
+ } else {
+ cacheKey.Append(spec);
+ }
+}
+
+nsresult DoUpdateExpirationTime(nsHttpChannel* aSelf,
+ nsICacheEntry* aCacheEntry,
+ nsHttpResponseHead* aResponseHead,
+ uint32_t& aExpirationTime) {
+ MOZ_ASSERT(aExpirationTime == 0);
+ NS_ENSURE_TRUE(aResponseHead, NS_ERROR_FAILURE);
+
+ nsresult rv;
+
+ if (!aResponseHead->MustValidate()) {
+ // For stale-while-revalidate we use expiration time as the absolute base
+ // for calculation of the stale window absolute end time. Hence, when the
+ // entry may be served w/o revalidation, we need a non-zero value for the
+ // expiration time. Let's set it to |now|, which basicly means "expired",
+ // same as when set to 0.
+ uint32_t now = NowInSeconds();
+ aExpirationTime = now;
+
+ uint32_t freshnessLifetime = 0;
+
+ rv = aResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
+ if (NS_FAILED(rv)) return rv;
+
+ if (freshnessLifetime > 0) {
+ uint32_t currentAge = 0;
+
+ rv = aResponseHead->ComputeCurrentAge(now, aSelf->GetRequestTime(),
+ &currentAge);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG(("freshnessLifetime = %u, currentAge = %u\n", freshnessLifetime,
+ currentAge));
+
+ if (freshnessLifetime > currentAge) {
+ uint32_t timeRemaining = freshnessLifetime - currentAge;
+ // be careful... now + timeRemaining may overflow
+ if (now + timeRemaining < now) {
+ aExpirationTime = uint32_t(-1);
+ } else {
+ aExpirationTime = now + timeRemaining;
+ }
+ }
+ }
+ }
+
+ rv = aCacheEntry->SetExpirationTime(aExpirationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+// UpdateExpirationTime is called when a new response comes in from the server.
+// It updates the stored response-time and sets the expiration time on the
+// cache entry.
+//
+// From section 13.2.4 of RFC2616, we compute expiration time as follows:
+//
+// timeRemaining = freshnessLifetime - currentAge
+// expirationTime = now + timeRemaining
+//
+nsresult nsHttpChannel::UpdateExpirationTime() {
+ uint32_t expirationTime = 0;
+ nsresult rv = DoUpdateExpirationTime(this, mCacheEntry, mResponseHead.get(),
+ expirationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::OpenCacheInputStream(nsICacheEntry* cacheEntry,
+ bool startBuffering) {
+ nsresult rv;
+
+ if (mURI->SchemeIs("https")) {
+ rv = cacheEntry->GetSecurityInfo(getter_AddRefs(mCachedSecurityInfo));
+ if (NS_FAILED(rv)) {
+ LOG(("failed to parse security-info [channel=%p, entry=%p]", this,
+ cacheEntry));
+ NS_WARNING("failed to parse security-info");
+ cacheEntry->AsyncDoom(nullptr);
+ return rv;
+ }
+
+ MOZ_ASSERT(mCachedSecurityInfo);
+ if (!mCachedSecurityInfo) {
+ LOG(
+ ("mCacheEntry->GetSecurityInfo returned success but did not "
+ "return the security info [channel=%p, entry=%p]",
+ this, cacheEntry));
+ cacheEntry->AsyncDoom(nullptr);
+ return NS_ERROR_UNEXPECTED; // XXX error code
+ }
+ }
+
+ // Keep the conditions below in sync with the conditions in ReadFromCache.
+
+ rv = NS_OK;
+
+ if (WillRedirect(*mCachedResponseHead)) {
+ // Do not even try to read the entity for a redirect because we do not
+ // return an entity to the application when we process redirects.
+ LOG(("Will skip read of cached redirect entity\n"));
+ return NS_OK;
+ }
+
+ if ((mLoadFlags & nsICachingChannel::LOAD_ONLY_IF_MODIFIED) &&
+ !LoadCachedContentIsPartial()) {
+ // For LOAD_ONLY_IF_MODIFIED, we usually don't have to deal with the
+ // cached entity.
+ LOG(
+ ("Will skip read from cache based on LOAD_ONLY_IF_MODIFIED "
+ "load flag\n"));
+ return NS_OK;
+ }
+
+ // Open an input stream for the entity, so that the call to OpenInputStream
+ // happens off the main thread.
+ nsCOMPtr<nsIInputStream> stream;
+
+ // If an alternate representation was requested, try to open the alt
+ // input stream.
+ // If the entry has a "is-from-child" metadata, then only open the altdata
+ // stream if the consumer is also from child.
+ bool altDataFromChild = false;
+ {
+ nsCString value;
+ rv = cacheEntry->GetMetaDataElement("alt-data-from-child",
+ getter_Copies(value));
+ altDataFromChild = !value.IsEmpty();
+ }
+
+ nsAutoCString altDataType;
+ Unused << cacheEntry->GetAltDataType(altDataType);
+
+ nsAutoCString contentType;
+ mCachedResponseHead->ContentType(contentType);
+
+ bool foundAltData = false;
+ bool deliverAltData = true;
+ if (!LoadDisableAltDataCache() && !altDataType.IsEmpty() &&
+ !mPreferredCachedAltDataTypes.IsEmpty() &&
+ altDataFromChild == LoadAltDataForChild()) {
+ for (auto& pref : mPreferredCachedAltDataTypes) {
+ if (pref.type() == altDataType &&
+ (pref.contentType().IsEmpty() || pref.contentType() == contentType)) {
+ foundAltData = true;
+ deliverAltData =
+ pref.deliverAltData() ==
+ nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC;
+ break;
+ }
+ }
+ }
+
+ nsCOMPtr<nsIInputStream> altData;
+ int64_t altDataSize = -1;
+ if (foundAltData) {
+ rv = cacheEntry->OpenAlternativeInputStream(altDataType,
+ getter_AddRefs(altData));
+ if (NS_SUCCEEDED(rv)) {
+ // We have succeeded.
+ mAvailableCachedAltDataType = altDataType;
+ StoreDeliveringAltData(deliverAltData);
+
+ // Set the correct data size on the channel.
+ Unused << cacheEntry->GetAltDataSize(&altDataSize);
+ mAltDataLength = altDataSize;
+
+ LOG(("Opened alt-data input stream [type=%s, size=%" PRId64
+ ", deliverAltData=%d]",
+ altDataType.get(), mAltDataLength, deliverAltData));
+
+ if (deliverAltData) {
+ stream = altData;
+ }
+ }
+ }
+
+ if (!stream) {
+ rv = cacheEntry->OpenInputStream(0, getter_AddRefs(stream));
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("Failed to open cache input stream [channel=%p, "
+ "mCacheEntry=%p]",
+ this, cacheEntry));
+ return rv;
+ }
+
+ if (startBuffering) {
+ bool nonBlocking;
+ rv = stream->IsNonBlocking(&nonBlocking);
+ if (NS_SUCCEEDED(rv) && nonBlocking) startBuffering = false;
+ }
+
+ if (!startBuffering) {
+ // Bypass wrapping the input stream for the new cache back-end since
+ // nsIStreamTransportService expects a blocking stream. Preloading of
+ // the data must be done on the level of the cache backend, internally.
+ //
+ // We do not connect the stream to the stream transport service if we
+ // have to validate the entry with the server. If we did, we would get
+ // into a race condition between the stream transport service reading
+ // the existing contents and the opening of the cache entry's output
+ // stream to write the new contents in the case where we get a non-304
+ // response.
+ LOG(
+ ("Opened cache input stream without buffering [channel=%p, "
+ "mCacheEntry=%p, stream=%p]",
+ this, cacheEntry, stream.get()));
+ mCacheInputStream.takeOver(stream);
+ return rv;
+ }
+
+ // Have the stream transport service start reading the entity on one of its
+ // background threads.
+
+ nsCOMPtr<nsITransport> transport;
+ nsCOMPtr<nsIInputStream> wrapper;
+
+ nsCOMPtr<nsIStreamTransportService> sts(
+ components::StreamTransport::Service());
+ rv = sts ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+ if (NS_SUCCEEDED(rv)) {
+ rv = sts->CreateInputTransport(stream, true, getter_AddRefs(transport));
+ }
+ if (NS_SUCCEEDED(rv)) {
+ rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(wrapper));
+ }
+ if (NS_SUCCEEDED(rv)) {
+ LOG(
+ ("Opened cache input stream [channel=%p, wrapper=%p, "
+ "transport=%p, stream=%p]",
+ this, wrapper.get(), transport.get(), stream.get()));
+ } else {
+ LOG(
+ ("Failed to open cache input stream [channel=%p, "
+ "wrapper=%p, transport=%p, stream=%p]",
+ this, wrapper.get(), transport.get(), stream.get()));
+
+ stream->Close();
+ return rv;
+ }
+
+ mCacheInputStream.takeOver(wrapper);
+
+ return NS_OK;
+}
+
+// Actually process the cached response that we started to handle in CheckCache
+// and/or StartBufferingCachedEntity.
+nsresult nsHttpChannel::ReadFromCache(bool alreadyMarkedValid) {
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(!mCachePump, NS_OK); // already opened
+
+ LOG(
+ ("nsHttpChannel::ReadFromCache [this=%p] "
+ "Using cached copy of: %s\n",
+ this, mSpec.get()));
+
+ // When racing the cache with the network with a timer, and we get data from
+ // the cache, we should prevent the timer from triggering a network request.
+ if (mNetworkTriggerTimer) {
+ mNetworkTriggerTimer->Cancel();
+ mNetworkTriggerTimer = nullptr;
+ }
+
+ if (mRaceCacheWithNetwork) {
+ MOZ_ASSERT(mFirstResponseSource != RESPONSE_FROM_CACHE);
+ if (mFirstResponseSource == RESPONSE_PENDING) {
+ LOG(("First response from cache\n"));
+ mFirstResponseSource = RESPONSE_FROM_CACHE;
+
+ // Cancel the transaction because we will serve the request from the cache
+ CancelNetworkRequest(NS_BINDING_ABORTED);
+ if (mTransactionPump && mSuspendCount) {
+ uint32_t suspendCount = mSuspendCount;
+ while (suspendCount--) {
+ mTransactionPump->Resume();
+ }
+ }
+ mTransaction = nullptr;
+ mTransactionPump = nullptr;
+ } else {
+ MOZ_ASSERT(mFirstResponseSource == RESPONSE_FROM_NETWORK);
+ LOG(
+ ("Skipping read from cache because first response was from "
+ "network\n"));
+
+ if (!mOnCacheEntryCheckTimestamp.IsNull()) {
+ TimeStamp currentTime = TimeStamp::Now();
+ int64_t savedTime =
+ (currentTime - mOnStartRequestTimestamp).ToMilliseconds();
+ Telemetry::Accumulate(
+ Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME, savedTime);
+
+ int64_t diffTime =
+ (currentTime - mOnCacheEntryCheckTimestamp).ToMilliseconds();
+ Telemetry::Accumulate(
+ Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_OCEC_ON_START_DIFF,
+ diffTime);
+ }
+ return NS_OK;
+ }
+ }
+
+ if (mCachedResponseHead) mResponseHead = std::move(mCachedResponseHead);
+
+ UpdateInhibitPersistentCachingFlag();
+
+ // if we don't already have security info, try to get it from the cache
+ // entry. there are two cases to consider here: 1) we are just reading
+ // from the cache, or 2) this may be due to a 304 not modified response,
+ // in which case we could have security info from a socket transport.
+ if (!mSecurityInfo) mSecurityInfo = mCachedSecurityInfo;
+
+ if (!alreadyMarkedValid && !LoadCachedContentIsPartial()) {
+ // We validated the entry, and we have write access to the cache, so
+ // mark the cache entry as valid in order to allow others access to
+ // this cache entry.
+ //
+ // TODO: This should be done asynchronously so we don't take the cache
+ // service lock on the main thread.
+ mCacheEntry->MaybeMarkValid();
+ }
+
+ nsresult rv;
+
+ // Keep the conditions below in sync with the conditions in
+ // StartBufferingCachedEntity.
+
+ if (WillRedirect(*mResponseHead)) {
+ // TODO: Bug 759040 - We should call HandleAsyncRedirect directly here,
+ // to avoid event dispatching latency.
+ MOZ_ASSERT(!mCacheInputStream);
+ LOG(("Skipping skip read of cached redirect entity\n"));
+ return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
+ }
+
+ if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !LoadCachedContentIsPartial()) {
+ LOG(
+ ("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
+ "load flag\n"));
+ MOZ_ASSERT(!mCacheInputStream);
+ // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
+ // here, to avoid event dispatching latency.
+ return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
+ }
+
+ MOZ_ASSERT(mCacheInputStream);
+ if (!mCacheInputStream) {
+ NS_ERROR(
+ "mCacheInputStream is null but we're expecting to "
+ "be able to read from it.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream = mCacheInputStream.forget();
+
+ rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), inputStream, 0, 0,
+ true);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ rv = mCachePump->AsyncRead(this);
+ if (NS_FAILED(rv)) return rv;
+
+ if (LoadTimingEnabled()) mCacheReadStart = TimeStamp::Now();
+
+ uint32_t suspendCount = mSuspendCount;
+ if (LoadAsyncResumePending()) {
+ LOG(
+ (" Suspend()'ing cache pump once because of async resume pending"
+ ", sc=%u, pump=%p, this=%p",
+ suspendCount, mCachePump.get(), this));
+ ++suspendCount;
+ }
+ while (suspendCount--) {
+ mCachePump->Suspend();
+ }
+
+ return NS_OK;
+}
+
+void nsHttpChannel::CloseCacheEntry(bool doomOnFailure) {
+ mCacheInputStream.CloseAndRelease();
+
+ if (!mCacheEntry) return;
+
+ LOG(("nsHttpChannel::CloseCacheEntry [this=%p] mStatus=%" PRIx32
+ " CacheEntryIsWriteOnly=%x",
+ this, static_cast<uint32_t>(static_cast<nsresult>(mStatus)),
+ LoadCacheEntryIsWriteOnly()));
+
+ // If we have begun to create or replace a cache entry, and that cache
+ // entry is not complete and not resumable, then it needs to be doomed.
+ // Otherwise, CheckCache will make the mistake of thinking that the
+ // partial cache entry is complete.
+
+ bool doom = false;
+ if (LoadInitedCacheEntry()) {
+ MOZ_ASSERT(mResponseHead, "oops");
+ if (NS_FAILED(mStatus) && doomOnFailure && LoadCacheEntryIsWriteOnly() &&
+ !mResponseHead->IsResumable()) {
+ doom = true;
+ }
+ } else if (LoadCacheEntryIsWriteOnly()) {
+ doom = true;
+ }
+
+ if (doom) {
+ LOG((" dooming cache entry!!"));
+ mCacheEntry->AsyncDoom(nullptr);
+ } else {
+ // Store updated security info, makes cached EV status race less likely
+ // (see bug 1040086)
+ if (mSecurityInfo) {
+ mCacheEntry->SetSecurityInfo(mSecurityInfo);
+ }
+ }
+
+ mCachedResponseHead = nullptr;
+
+ mCachePump = nullptr;
+ // This releases the entry for other consumers to use.
+ // We call Dismiss() in case someone still keeps a reference
+ // to this entry handle.
+ mCacheEntry->Dismiss();
+ mCacheEntry = nullptr;
+ StoreCacheEntryIsWriteOnly(false);
+ StoreInitedCacheEntry(false);
+}
+
+void nsHttpChannel::MaybeCreateCacheEntryWhenRCWN() {
+ mozilla::MutexAutoLock lock(mRCWNLock);
+
+ // Create cache entry for writing only when we're racing cache with network
+ // and we don't have the entry because network won.
+ if (mCacheEntry || !mRaceCacheWithNetwork ||
+ mFirstResponseSource != RESPONSE_FROM_NETWORK ||
+ LoadCacheEntryIsReadOnly()) {
+ return;
+ }
+
+ LOG(("nsHttpChannel::MaybeCreateCacheEntryWhenRCWN [this=%p]", this));
+
+ nsCOMPtr<nsICacheStorageService> cacheStorageService(
+ components::CacheStorage::Service());
+ if (!cacheStorageService) {
+ return;
+ }
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
+ Unused << cacheStorageService->DiskCacheStorage(info,
+ getter_AddRefs(cacheStorage));
+ if (!cacheStorage) {
+ return;
+ }
+
+ Unused << cacheStorage->OpenTruncate(mCacheEntryURI, mCacheIdExtension,
+ getter_AddRefs(mCacheEntry));
+
+ LOG((" created entry %p", mCacheEntry.get()));
+
+ if (AwaitingCacheCallbacks()) {
+ // Setting mIgnoreCacheEntry to true ensures that we won't close this
+ // write-only entry in OnCacheEntryAvailable() if this method was called
+ // after OnCacheEntryCheck().
+ mIgnoreCacheEntry = true;
+ }
+
+ mAvailableCachedAltDataType.Truncate();
+ StoreDeliveringAltData(false);
+ mAltDataLength = -1;
+ mCacheInputStream.CloseAndRelease();
+ mCachedContentIsValid = false;
+}
+
+// Initialize the cache entry for writing.
+// - finalize storage policy
+// - store security info
+// - update expiration time
+// - store headers and other meta data
+nsresult nsHttpChannel::InitCacheEntry() {
+ nsresult rv;
+
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
+ // if only reading, nothing to be done here.
+ if (LoadCacheEntryIsReadOnly()) return NS_OK;
+
+ // Don't cache the response again if already cached...
+ if (mCachedContentIsValid) return NS_OK;
+
+ LOG(("nsHttpChannel::InitCacheEntry [this=%p entry=%p]\n", this,
+ mCacheEntry.get()));
+
+ bool recreate = !LoadCacheEntryIsWriteOnly();
+ bool dontPersist = mLoadFlags & INHIBIT_PERSISTENT_CACHING;
+
+ if (!recreate && dontPersist) {
+ // If the current entry is persistent but we inhibit peristence
+ // then force recreation of the entry as memory/only.
+ rv = mCacheEntry->GetPersistent(&recreate);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (recreate) {
+ LOG(
+ (" we have a ready entry, but reading it again from the server -> "
+ "recreating cache entry\n"));
+ // clean the altData cache and reset this to avoid wrong content length
+ mAvailableCachedAltDataType.Truncate();
+ StoreDeliveringAltData(false);
+
+ nsCOMPtr<nsICacheEntry> currentEntry;
+ currentEntry.swap(mCacheEntry);
+ rv = currentEntry->Recreate(dontPersist, getter_AddRefs(mCacheEntry));
+ if (NS_FAILED(rv)) {
+ LOG((" recreation failed, the response will not be cached"));
+ return NS_OK;
+ }
+
+ StoreCacheEntryIsWriteOnly(true);
+ }
+
+ // Set the expiration time for this cache entry
+ rv = UpdateExpirationTime();
+ if (NS_FAILED(rv)) return rv;
+
+ // mark this weakly framed until a response body is seen
+ mCacheEntry->SetMetaDataElement("strongly-framed", "0");
+
+ rv = AddCacheEntryHeaders(mCacheEntry);
+ if (NS_FAILED(rv)) return rv;
+
+ StoreInitedCacheEntry(true);
+
+ // Don't perform the check when writing (doesn't make sense)
+ StoreConcurrentCacheAccess(0);
+
+ return NS_OK;
+}
+
+void nsHttpChannel::UpdateInhibitPersistentCachingFlag() {
+ // The no-store directive within the 'Cache-Control:' header indicates
+ // that we must not store the response in a persistent cache.
+ if (mResponseHead->NoStore()) mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
+
+ // Only cache SSL content on disk if the pref is set
+ if (!gHttpHandler->IsPersistentHttpsCachingEnabled() &&
+ mURI->SchemeIs("https")) {
+ mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
+ }
+}
+
+nsresult DoAddCacheEntryHeaders(nsHttpChannel* self, nsICacheEntry* entry,
+ nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ nsITransportSecurityInfo* securityInfo) {
+ nsresult rv;
+
+ LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] begin", self));
+ // Store secure data in memory only
+ if (securityInfo) {
+ entry->SetSecurityInfo(securityInfo);
+ }
+
+ // Store the HTTP request method with the cache entry so we can distinguish
+ // for example GET and HEAD responses.
+ nsAutoCString method;
+ requestHead->Method(method);
+ rv = entry->SetMetaDataElement("request-method", method.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // Store the HTTP authorization scheme used if any...
+ rv = StoreAuthorizationMetaData(entry, requestHead);
+ if (NS_FAILED(rv)) return rv;
+
+ // Iterate over the headers listed in the Vary response header, and
+ // store the value of the corresponding request header so we can verify
+ // that it has not varied when we try to re-use the cached response at
+ // a later time. Take care to store "Cookie" headers only as hashes
+ // due to security considerations and the fact that they can be pretty
+ // large (bug 468426). We take care of "Vary: cookie" in ResponseWouldVary.
+ //
+ // NOTE: if "Vary: accept, cookie", then we will store the "accept" header
+ // in the cache. we could try to avoid needlessly storing the "accept"
+ // header in this case, but it doesn't seem worth the extra code to perform
+ // the check.
+ {
+ nsAutoCString buf, metaKey;
+ Unused << responseHead->GetHeader(nsHttp::Vary, buf);
+
+ constexpr auto prefix = "request-"_ns;
+
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(buf, NS_HTTP_HEADER_SEP).ToRange()) {
+ LOG(
+ ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
+ "processing %s",
+ self, nsPromiseFlatCString(token).get()));
+ if (!token.EqualsLiteral("*")) {
+ nsHttpAtom atom = nsHttp::ResolveAtom(token);
+ nsAutoCString val;
+ nsAutoCString hash;
+ if (NS_SUCCEEDED(requestHead->GetHeader(atom, val))) {
+ // If cookie-header, store a hash of the value
+ if (atom == nsHttp::Cookie) {
+ LOG(
+ ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
+ "cookie-value %s",
+ self, val.get()));
+ rv = Hash(val.get(), hash);
+ // If hash failed, store a string not very likely
+ // to be the result of subsequent hashes
+ if (NS_FAILED(rv)) {
+ val = "<hash failed>"_ns;
+ } else {
+ val = hash;
+ }
+
+ LOG((" hashed to %s\n", val.get()));
+ }
+
+ // build cache meta data key and set meta data element...
+ metaKey = prefix + token;
+ entry->SetMetaDataElement(metaKey.get(), val.get());
+ } else {
+ LOG(
+ ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
+ "clearing metadata for %s",
+ self, nsPromiseFlatCString(token).get()));
+ metaKey = prefix + token;
+ entry->SetMetaDataElement(metaKey.get(), nullptr);
+ }
+ }
+ }
+ }
+
+ // Store the received HTTP head with the cache entry as an element of
+ // the meta data.
+ nsAutoCString head;
+ responseHead->Flatten(head, true);
+ rv = entry->SetMetaDataElement("response-head", head.get());
+ if (NS_FAILED(rv)) return rv;
+ head.Truncate();
+ responseHead->FlattenNetworkOriginalHeaders(head);
+ rv = entry->SetMetaDataElement("original-response-headers", head.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // Indicate we have successfully finished setting metadata on the cache entry.
+ rv = entry->MetaDataReady();
+
+ return rv;
+}
+
+nsresult nsHttpChannel::AddCacheEntryHeaders(nsICacheEntry* entry) {
+ return DoAddCacheEntryHeaders(this, entry, &mRequestHead, mResponseHead.get(),
+ mSecurityInfo);
+}
+
+inline void GetAuthType(const char* challenge, nsCString& authType) {
+ const char* p;
+
+ // get the challenge type
+ if ((p = strchr(challenge, ' ')) != nullptr) {
+ authType.Assign(challenge, p - challenge);
+ } else {
+ authType.Assign(challenge);
+ }
+}
+
+nsresult StoreAuthorizationMetaData(nsICacheEntry* entry,
+ nsHttpRequestHead* requestHead) {
+ // Not applicable to proxy authorization...
+ nsAutoCString val;
+ if (NS_FAILED(requestHead->GetHeader(nsHttp::Authorization, val))) {
+ return NS_OK;
+ }
+
+ // eg. [Basic realm="wally world"]
+ nsAutoCString buf;
+ GetAuthType(val.get(), buf);
+ return entry->SetMetaDataElement("auth", buf.get());
+}
+
+// Finalize the cache entry
+// - may need to rewrite response headers if any headers changed
+// - may need to recalculate the expiration time if any headers changed
+// - called only for freshly written cache entries
+nsresult nsHttpChannel::FinalizeCacheEntry() {
+ LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p]\n", this));
+
+ // Don't update this meta-data on 304
+ if (LoadStronglyFramed() && !mCachedContentIsValid && mCacheEntry) {
+ LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p] Is Strongly Framed\n",
+ this));
+ mCacheEntry->SetMetaDataElement("strongly-framed", "1");
+ }
+
+ if (mResponseHead && LoadResponseHeadersModified()) {
+ // Set the expiration time for this cache entry
+ nsresult rv = UpdateExpirationTime();
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+// Open an output stream to the cache entry and insert a listener tee into
+// the chain of response listeners.
+nsresult nsHttpChannel::InstallCacheListener(int64_t offset) {
+ nsresult rv;
+
+ LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));
+
+ MOZ_ASSERT(mCacheEntry);
+ MOZ_ASSERT(LoadCacheEntryIsWriteOnly() || LoadCachedContentIsPartial() ||
+ mRaceCacheWithNetwork);
+ MOZ_ASSERT(mListener);
+
+ nsAutoCString contentEncoding, contentType;
+ Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+ mResponseHead->ContentType(contentType);
+ // If the content is compressible and the server has not compressed it,
+ // mark the cache entry for compression.
+ if (contentEncoding.IsEmpty() &&
+ (contentType.EqualsLiteral(TEXT_HTML) ||
+ contentType.EqualsLiteral(TEXT_PLAIN) ||
+ contentType.EqualsLiteral(TEXT_CSS) ||
+ contentType.EqualsLiteral(TEXT_JAVASCRIPT) ||
+ contentType.EqualsLiteral(TEXT_ECMASCRIPT) ||
+ contentType.EqualsLiteral(TEXT_XML) ||
+ contentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
+ contentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
+ contentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
+ contentType.EqualsLiteral(APPLICATION_XHTML_XML))) {
+ rv = mCacheEntry->SetMetaDataElement("uncompressed-len", "0");
+ if (NS_FAILED(rv)) {
+ LOG(("unable to mark cache entry for compression"));
+ }
+ }
+
+ LOG(("Trading cache input stream for output stream [channel=%p]", this));
+
+ // We must close the input stream first because cache entries do not
+ // correctly handle having an output stream and input streams open at
+ // the same time.
+ mCacheInputStream.CloseAndRelease();
+
+ int64_t predictedSize = mResponseHead->TotalEntitySize();
+ if (predictedSize != -1) {
+ predictedSize -= offset;
+ }
+
+ nsCOMPtr<nsIOutputStream> out;
+ rv =
+ mCacheEntry->OpenOutputStream(offset, predictedSize, getter_AddRefs(out));
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG((" entry doomed, not writing it [channel=%p]", this));
+ // Entry is already doomed.
+ // This may happen when expiration time is set to past and the entry
+ // has been removed by the background eviction logic.
+ return NS_OK;
+ }
+ if (rv == NS_ERROR_FILE_TOO_BIG) {
+ LOG((" entry would exceed max allowed size, not writing it [channel=%p]",
+ this));
+ mCacheEntry->AsyncDoom(nullptr);
+ return NS_OK;
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ if (LoadCacheOnlyMetadata()) {
+ LOG(("Not storing content, cacheOnlyMetadata set"));
+ // We must open and then close the output stream of the cache entry.
+ // This way we indicate the content has been written (despite with zero
+ // length) and the entry is now in the ready state with "having data".
+
+ out->Close();
+ return NS_OK;
+ }
+
+ // XXX disk cache does not support overlapped i/o yet
+#if 0
+ // Mark entry valid inorder to allow simultaneous reading...
+ rv = mCacheEntry->MarkValid();
+ if (NS_FAILED(rv)) return rv;
+#endif
+
+ nsCOMPtr<nsIStreamListenerTee> tee =
+ do_CreateInstance(kStreamListenerTeeCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%" PRIx32, tee.get(),
+ static_cast<uint32_t>(rv)));
+ rv = tee->Init(mListener, out, nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ mListener = tee;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <redirect>
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpChannel::SetupReplacementChannel(nsIURI* newURI,
+ nsIChannel* newChannel,
+ bool preserveMethod,
+ uint32_t redirectFlags) {
+ LOG(
+ ("nsHttpChannel::SetupReplacementChannel "
+ "[this=%p newChannel=%p preserveMethod=%d]",
+ this, newChannel, preserveMethod));
+
+ if (!mEndMarkerAdded && profiler_thread_is_being_profiled_for_markers()) {
+ mEndMarkerAdded = true;
+
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ TimingStruct timings;
+ if (mTransaction) {
+ timings = mTransaction->Timings();
+ }
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+
+ RefPtr<nsIIdentChannel> newIdentChannel = do_QueryObject(newChannel);
+ uint64_t channelId = 0;
+ if (newIdentChannel) {
+ channelId = newIdentChannel->ChannelId();
+ }
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId,
+ NetworkLoadType::LOAD_REDIRECT, mLastStatusReported, TimeStamp::Now(),
+ size, mCacheDisposition, mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0, &timings,
+ std::move(mSource), Some(nsDependentCString(contentType.get())), newURI,
+ redirectFlags, channelId);
+ }
+
+ nsresult rv = HttpBaseChannel::SetupReplacementChannel(
+ newURI, newChannel, preserveMethod, redirectFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = CheckRedirectLimit(redirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // pass on the early hint observer to be able to process `103 Early Hints`
+ // responses after cross origin redirects
+ if (mEarlyHintObserver) {
+ if (RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(newChannel)) {
+ httpChannelImpl->SetEarlyHintObserver(mEarlyHintObserver);
+ }
+ mEarlyHintObserver = nullptr;
+ }
+
+ // We don't support redirection for WebTransport for now.
+ mWebTransportSessionEventListener = nullptr;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+ if (!httpChannel) return NS_OK; // no other options to set
+
+ // convey the ApplyConversion flag (bug 91862)
+ nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
+ if (encodedChannel) encodedChannel->SetApplyConversion(LoadApplyConversion());
+
+ // transfer the resume information
+ if (LoadResuming()) {
+ nsCOMPtr<nsIResumableChannel> resumableChannel(
+ do_QueryInterface(newChannel));
+ if (!resumableChannel) {
+ NS_WARNING(
+ "Got asked to resume, but redirected to non-resumable channel!");
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+ resumableChannel->ResumeAt(mStartPos, mEntityID);
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel =
+ do_QueryInterface(newChannel, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ TimeStamp timestamp;
+ rv = GetNavigationStartTimeStamp(&timestamp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (timestamp) {
+ Unused << internalChannel->SetNavigationStartTimeStamp(timestamp);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::AsyncProcessRedirection(uint32_t redirectType) {
+ LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n", this,
+ redirectType));
+
+ nsresult rv = ProcessCrossOriginSecurityHeaders();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ HandleAsyncAbort();
+ return rv;
+ }
+
+ nsAutoCString location;
+
+ // if a location header was not given, then we can't perform the redirect,
+ // so just carry on as though this were a normal response.
+ if (NS_FAILED(mResponseHead->GetHeader(nsHttp::Location, location))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we were told to not follow redirects automatically, then again
+ // carry on as though this were a normal response.
+ if (mLoadInfo->GetDontFollowRedirects()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // make sure non-ASCII characters in the location header are escaped.
+ nsAutoCString locationBuf;
+ if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII | esc_Spaces,
+ locationBuf)) {
+ location = locationBuf;
+ }
+
+ mRedirectType = redirectType;
+
+ LOG(("redirecting to: %s [redirection-limit=%u]\n", location.get(),
+ uint32_t(mRedirectionLimit)));
+
+ rv = CreateNewURI(location.get(), getter_AddRefs(mRedirectURI));
+
+ if (NS_FAILED(rv)) {
+ LOG(("Invalid URI for redirect: Location: %s\n", location.get()));
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (!StaticPrefs::network_allow_redirect_to_data() &&
+ !mLoadInfo->GetAllowInsecureRedirectToDataURI() &&
+ mRedirectURI->SchemeIs("data")) {
+ LOG(("Invalid data URI for redirect!"));
+ nsContentSecurityManager::ReportBlockedDataURI(mRedirectURI, mLoadInfo,
+ true);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // Perform the URL query string stripping for redirects. We will only strip
+ // the query string if it is redirecting to a third-party URI in the top
+ // level.
+ if (StaticPrefs::privacy_query_stripping_redirect()) {
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+ bool isThirdPartyRedirectURI = true;
+ thirdPartyUtil->IsThirdPartyURI(mURI, mRedirectURI,
+ &isThirdPartyRedirectURI);
+ if (isThirdPartyRedirectURI && mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUERY_STRIPPING_COUNT::Redirect);
+
+ nsCOMPtr<nsIPrincipal> prin;
+ ContentBlockingAllowList::RecomputePrincipal(
+ mRedirectURI, mLoadInfo->GetOriginAttributes(), getter_AddRefs(prin));
+
+ bool isRedirectURIInAllowList = false;
+ if (prin) {
+ ContentBlockingAllowList::Check(prin, mPrivateBrowsing,
+ isRedirectURIInAllowList);
+ }
+
+ if (!isRedirectURIInAllowList) {
+ nsCOMPtr<nsIURI> strippedURI;
+
+ nsCOMPtr<nsIURLQueryStringStripper> queryStripper =
+ components::URLQueryStringStripper::Service(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numStripped;
+
+ rv = queryStripper->Strip(mRedirectURI, mPrivateBrowsing,
+ getter_AddRefs(strippedURI), &numStripped);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (numStripped) {
+ mUnstrippedRedirectURI = mRedirectURI;
+ mRedirectURI = strippedURI;
+
+ // Record telemetry, but only if we stripped any query params.
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUERY_STRIPPING_COUNT::StripForRedirect);
+ Telemetry::Accumulate(Telemetry::QUERY_STRIPPING_PARAM_COUNT,
+ numStripped);
+ }
+ }
+ }
+ }
+
+ if (NS_WARN_IF(!mRedirectURI)) {
+ LOG(("Invalid redirect URI after performaing query string stripping"));
+ return NS_ERROR_FAILURE;
+ }
+
+ return ContinueProcessRedirectionAfterFallback(NS_OK);
+}
+
+nsresult nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv) {
+ // Kill the current cache entry if we are redirecting
+ // back to ourself.
+ bool redirectingBackToSameURI = false;
+ if (mCacheEntry && LoadCacheEntryIsWriteOnly() &&
+ NS_SUCCEEDED(mURI->Equals(mRedirectURI, &redirectingBackToSameURI)) &&
+ redirectingBackToSameURI) {
+ mCacheEntry->AsyncDoom(nullptr);
+ }
+
+ // move the reference of the old location to the new one if the new
+ // one has none.
+ PropagateReferenceIfNeeded(mURI, mRedirectURI);
+
+ bool rewriteToGET =
+ ShouldRewriteRedirectToGET(mRedirectType, mRequestHead.ParsedMethod());
+
+ // prompt if the method is not safe (such as POST, PUT, DELETE, ...)
+ if (!rewriteToGET && !mRequestHead.IsSafeMethod()) {
+ rv = PromptTempRedirect();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ uint32_t redirectFlags;
+ if (nsHttp::IsPermanentRedirect(mRedirectType)) {
+ redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
+ } else {
+ redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
+ }
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(mRedirectURI, redirectFlags);
+
+ // Propagate the unstripped redirect URI.
+ redirectLoadInfo->SetUnstrippedURI(mUnstrippedRedirectURI);
+
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel), mRedirectURI,
+ redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupReplacementChannel(mRedirectURI, newChannel, !rewriteToGET,
+ redirectFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // verify that this is a legal redirect
+ mRedirectChannel = newChannel;
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
+
+ if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::ContinueProcessRedirection(nsresult rv) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+
+ LOG(("nsHttpChannel::ContinueProcessRedirection [rv=%" PRIx32 ",this=%p]\n",
+ static_cast<uint32_t>(rv), this));
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_ASSERT(mRedirectChannel, "No redirect channel?");
+
+ // Make sure to do this after we received redirect veto answer,
+ // i.e. after all sinks had been notified
+ mRedirectChannel->SetOriginalURI(mOriginalURI);
+
+ // XXX we used to talk directly with the script security manager, but that
+ // should really be handled by the event sink implementation.
+
+ // begin loading the new channel
+ rv = mRedirectChannel->AsyncOpen(mListener);
+ LOG((" new channel AsyncOpen returned %" PRIX32, static_cast<uint32_t>(rv)));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // close down this channel
+ Cancel(NS_BINDING_REDIRECTED);
+
+ notifier.RedirectSucceeded();
+
+ ReleaseListeners();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <auth>
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP nsHttpChannel::OnAuthAvailable() {
+ LOG(("nsHttpChannel::OnAuthAvailable [this=%p]", this));
+
+ // setting mAuthRetryPending flag and resuming the transaction
+ // triggers process of throwing away the unauthenticated data already
+ // coming from the network
+ mIsAuthChannel = true;
+ mAuthRetryPending = true;
+ StoreProxyAuthPending(false);
+ LOG(("Resuming the transaction, we got credentials from user"));
+ if (mTransactionPump) {
+ mTransactionPump->Resume();
+ }
+
+ if (StaticPrefs::network_auth_use_redirect_for_retries()) {
+ return CallOrWaitForResume(
+ [](auto* self) { return self->RedirectToNewChannelForAuthRetry(); });
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(bool userCancel) {
+ LOG(("nsHttpChannel::OnAuthCancelled [this=%p]", this));
+ MOZ_ASSERT(mAuthRetryPending, "OnAuthCancelled should not be called twice");
+
+ if (mTransactionPump) {
+ // If the channel is trying to authenticate to a proxy and
+ // that was canceled we cannot show the http response body
+ // from the 40x as that might mislead the user into thinking
+ // it was a end host response instead of a proxy reponse.
+ // This must check explicitly whether a proxy auth was being done
+ // because we do want to show the content if this is an error from
+ // the origin server.
+ if (LoadProxyAuthPending()) Cancel(NS_ERROR_PROXY_CONNECTION_REFUSED);
+
+ // Make sure to process security headers before calling CallOnStartRequest.
+ nsresult rv = ProcessCrossOriginSecurityHeaders();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ HandleAsyncAbort();
+ return rv;
+ }
+
+ // ensure call of OnStartRequest of the current listener here,
+ // it would not be called otherwise at all
+ rv = CallOnStartRequest();
+
+ // drop mAuthRetryPending flag and resume the transaction
+ // this resumes load of the unauthenticated content data (which
+ // may have been canceled if we don't want to show it)
+ mAuthRetryPending = false;
+ LOG(("Resuming the transaction, user cancelled the auth dialog"));
+ mTransactionPump->Resume();
+
+ if (NS_FAILED(rv)) mTransactionPump->Cancel(rv);
+ }
+
+ StoreProxyAuthPending(false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::CloseStickyConnection() {
+ LOG(("nsHttpChannel::CloseStickyConnection this=%p", this));
+
+ // Require we are between OnStartRequest and OnStopRequest, because
+ // what we do here takes effect in OnStopRequest (not reusing the
+ // connection for next authentication round).
+ if (!LoadIsPending()) {
+ LOG((" channel not pending"));
+ NS_ERROR(
+ "CloseStickyConnection not called before OnStopRequest, won't have any "
+ "effect");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mTransaction);
+ if (!mTransaction) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!(mCaps & NS_HTTP_STICKY_CONNECTION ||
+ mTransaction->HasStickyConnection())) {
+ LOG((" not sticky"));
+ return NS_OK;
+ }
+
+ mTransaction->DontReuseConnection();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::ConnectionRestartable(bool aRestartable) {
+ LOG(("nsHttpChannel::ConnectionRestartable this=%p, restartable=%d", this,
+ aRestartable));
+ StoreAuthConnectionRestartable(aRestartable);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF_INHERITED(nsHttpChannel, HttpBaseChannel)
+NS_IMPL_RELEASE_INHERITED(nsHttpChannel, HttpBaseChannel)
+
+NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel)
+ NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
+ NS_INTERFACE_MAP_ENTRY(nsICacheEntryOpenCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpAuthenticableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsICorsPreflightCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIRaceCacheWithNetwork)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestTailUnblockCallback)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(nsHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIEarlyHintObserver)
+NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP nsHttpChannel::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsHttpChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::CancelWithReason(nsresult aStatus, const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Cancel(nsresult status) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // We should never have a pump open while a CORS preflight is in progress.
+ MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
+#ifdef DEBUG
+ // We want to perform this check only when the chanel is being cancelled the
+ // first time with a URL classifier blocking error code. If mStatus is
+ // already set to such an error code then Cancel() may be called for some
+ // other reason, for example because we've received notification about our
+ // parent process side channel being canceled, in which case we cannot expect
+ // that CancelByURLClassifier() would have handled this case.
+ if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status) &&
+ !UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(mStatus)) {
+ MOZ_CRASH_UNSAFE_PRINTF("Blocking classifier error %" PRIx32
+ " need to be handled by CancelByURLClassifier()",
+ static_cast<uint32_t>(status));
+ }
+#endif
+
+ LOG(("nsHttpChannel::Cancel [this=%p status=%" PRIx32 ", reason=%s]\n", this,
+ static_cast<uint32_t>(status), mCanceledReason.get()));
+ MOZ_ASSERT_IF(!(mConnectionInfo && mConnectionInfo->UsingConnect()) &&
+ NS_SUCCEEDED(mStatus),
+ !AllowedErrorForHTTPSRRFallback(status));
+
+ mEarlyHintObserver = nullptr;
+ mWebTransportSessionEventListener = nullptr;
+
+ if (mCanceled) {
+ LOG((" ignoring; already canceled\n"));
+ return NS_OK;
+ }
+
+ LogCallingScriptLocation(this);
+
+ if (LoadWaitingForRedirectCallback()) {
+ LOG(("channel canceled during wait for redirect callback"));
+ }
+
+ return CancelInternal(status);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::CancelByURLClassifier(nsresult aErrorCode) {
+ MOZ_ASSERT(
+ UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
+ MOZ_ASSERT(NS_IsMainThread());
+ // We should never have a pump open while a CORS preflight is in progress.
+ MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
+
+ LOG(("nsHttpChannel::CancelByURLClassifier [this=%p]\n", this));
+
+ if (mCanceled) {
+ LOG((" ignoring; already canceled\n"));
+ return NS_OK;
+ }
+
+ // We are being canceled by the channel classifier because of tracking
+ // protection, but we haven't yet had a chance to dispatch the
+ // "http-on-modify-request" notifications yet (this would normally be
+ // done in PrepareToConnect()). So do that now, before proceeding to
+ // cancel.
+ //
+ // Note that running these observers can itself result in the channel
+ // being canceled. In that case, we accept that cancelation code as
+ // the cause of the cancelation, as if the classification of the channel
+ // would have occurred past this point!
+
+ // notify "http-on-modify-request" observers
+ CallOnModifyRequestObservers();
+
+ // Check if request was cancelled during on-modify-request
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume in Cancel [this=%p]\n", this));
+ MOZ_ASSERT(!mCallOnResume);
+ StoreChannelClassifierCancellationPending(1);
+ mCallOnResume = [aErrorCode](nsHttpChannel* self) {
+ self->HandleContinueCancellingByURLClassifier(aErrorCode);
+ return NS_OK;
+ };
+ return NS_OK;
+ }
+
+ // Check to see if we should redirect this channel elsewhere by
+ // nsIHttpChannel.redirectTo API request
+ if (mAPIRedirectToURI) {
+ StoreChannelClassifierCancellationPending(1);
+ return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
+ }
+
+ return CancelInternal(aErrorCode);
+}
+
+void nsHttpChannel::ContinueCancellingByURLClassifier(nsresult aErrorCode) {
+ MOZ_ASSERT(
+ UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
+ MOZ_ASSERT(NS_IsMainThread());
+ // We should never have a pump open while a CORS preflight is in progress.
+ MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
+
+ LOG(("nsHttpChannel::ContinueCancellingByURLClassifier [this=%p]\n", this));
+ if (mCanceled) {
+ LOG((" ignoring; already canceled\n"));
+ return;
+ }
+
+ // Check to see if we should redirect this channel elsewhere by
+ // nsIHttpChannel.redirectTo API request
+ if (mAPIRedirectToURI) {
+ Unused << AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
+ return;
+ }
+
+ Unused << CancelInternal(aErrorCode);
+}
+
+nsresult nsHttpChannel::CancelInternal(nsresult status) {
+ LOG(("nsHttpChannel::CancelInternal [this=%p]\n", this));
+ bool channelClassifierCancellationPending =
+ !!LoadChannelClassifierCancellationPending();
+ if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status)) {
+ StoreChannelClassifierCancellationPending(0);
+ }
+
+ // We don't want the content process to see any header values
+ // when the request is blocked by ORB
+ if (mChannelBlockedByOpaqueResponse && mCachedOpaqueResponseBlockingPref) {
+ mResponseHead->ClearHeaders();
+ }
+
+ mEarlyHintObserver = nullptr;
+ mWebTransportSessionEventListener = nullptr;
+ mCanceled = true;
+ mStatus = NS_FAILED(status) ? status : NS_ERROR_ABORT;
+
+ if (mLastStatusReported && !mEndMarkerAdded &&
+ profiler_thread_is_being_profiled_for_markers()) {
+ // These do allocations/frees/etc; avoid if not active
+ // mLastStatusReported can be null if Cancel is called before we added the
+ // start marker.
+ mEndMarkerAdded = true;
+
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_CANCEL,
+ mLastStatusReported, TimeStamp::Now(), size, mCacheDisposition,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource));
+ }
+
+ // If we don't have mTransactionPump and mCachePump, we need to call
+ // AsyncAbort to make sure this channel's listener got notified.
+ bool needAsyncAbort = !mTransactionPump && !mCachePump;
+
+ if (mProxyRequest) mProxyRequest->Cancel(status);
+ CancelNetworkRequest(status);
+ mCacheInputStream.CloseAndRelease();
+ if (mCachePump) mCachePump->Cancel(status);
+ if (mAuthProvider) mAuthProvider->Cancel(status);
+ if (mPreflightChannel) mPreflightChannel->Cancel(status);
+ if (mRequestContext && mOnTailUnblock) {
+ mOnTailUnblock = nullptr;
+ mRequestContext->CancelTailedRequest(this);
+ CloseCacheEntry(false);
+ needAsyncAbort = false;
+ Unused << AsyncAbort(status);
+ } else if (channelClassifierCancellationPending) {
+ // If mCallOnResume is not null here, it's set in
+ // nsHttpChannel::CancelByURLClassifier. We can override mCallOnResume since
+ // mCanceled is true and nsHttpChannel::ContinueCancellingByURLClassifier
+ // does nothing.
+ if (mCallOnResume) {
+ mCallOnResume = nullptr;
+ }
+ // If we're coming from an asynchronous path when canceling a channel due
+ // to safe-browsing protection, we need to AsyncAbort the channel now.
+ needAsyncAbort = false;
+ Unused << AsyncAbort(status);
+ }
+
+ // If we already have mCallOnResume, AsyncAbort will be called in
+ // ResumeInternal.
+ if (needAsyncAbort && !mCallOnResume && !mSuspendCount) {
+ LOG(("nsHttpChannel::CancelInternal do AsyncAbort [this=%p]\n", this));
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(status);
+ }
+ return NS_OK;
+}
+
+void nsHttpChannel::CancelNetworkRequest(nsresult aStatus) {
+ if (mTransaction) {
+ nsresult rv = gHttpHandler->CancelTransaction(mTransaction, aStatus);
+ if (NS_FAILED(rv)) {
+ LOG(("failed to cancel the transaction\n"));
+ }
+ }
+ if (mTransactionPump) mTransactionPump->Cancel(aStatus);
+
+ mEarlyHintObserver = nullptr;
+ mWebTransportSessionEventListener = nullptr;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Suspend() {
+ NS_ENSURE_TRUE(LoadIsPending(), NS_ERROR_NOT_AVAILABLE);
+
+ LOG(("nsHttpChannel::SuspendInternal [this=%p]\n", this));
+ LogCallingScriptLocation(this);
+
+ ++mSuspendCount;
+
+ if (mSuspendCount == 1) {
+ mSuspendTimestamp = TimeStamp::NowLoRes();
+ }
+
+ nsresult rvTransaction = NS_OK;
+ if (mTransactionPump) {
+ rvTransaction = mTransactionPump->Suspend();
+ }
+ nsresult rvCache = NS_OK;
+ if (mCachePump) {
+ rvCache = mCachePump->Suspend();
+ }
+
+ return NS_FAILED(rvTransaction) ? rvTransaction : rvCache;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Resume() {
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+
+ LOG(("nsHttpChannel::ResumeInternal [this=%p]\n", this));
+ LogCallingScriptLocation(this);
+
+ if (--mSuspendCount == 0) {
+ mSuspendTotalTime +=
+ (TimeStamp::NowLoRes() - mSuspendTimestamp).ToMilliseconds();
+
+ if (mCallOnResume) {
+ // Resume the interrupted procedure first, then resume
+ // the pump to continue process the input stream.
+ // Any newly created pump MUST be suspended to prevent calling
+ // its OnStartRequest before OnStopRequest of any pre-existing
+ // pump. AsyncResumePending ensures that.
+ MOZ_ASSERT(!LoadAsyncResumePending());
+ StoreAsyncResumePending(1);
+
+ std::function<nsresult(nsHttpChannel*)> callOnResume = nullptr;
+ std::swap(callOnResume, mCallOnResume);
+
+ RefPtr<nsHttpChannel> self(this);
+ nsCOMPtr<nsIRequest> transactionPump = mTransactionPump;
+ RefPtr<nsInputStreamPump> cachePump = mCachePump;
+
+ nsresult rv = NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "nsHttpChannel::CallOnResume",
+ [callOnResume{std::move(callOnResume)}, self{std::move(self)},
+ transactionPump{std::move(transactionPump)},
+ cachePump{std::move(cachePump)}]() {
+ MOZ_ASSERT(self->LoadAsyncResumePending());
+ nsresult rv = self->CallOrWaitForResume(callOnResume);
+ if (NS_FAILED(rv)) {
+ self->CloseCacheEntry(false);
+ Unused << self->AsyncAbort(rv);
+ }
+ MOZ_ASSERT(self->LoadAsyncResumePending());
+
+ self->StoreAsyncResumePending(0);
+
+ // And now actually resume the previously existing pumps.
+ if (transactionPump) {
+ LOG(
+ ("nsHttpChannel::CallOnResume resuming previous transaction "
+ "pump %p, this=%p",
+ transactionPump.get(), self.get()));
+ transactionPump->Resume();
+ }
+ if (cachePump) {
+ LOG(
+ ("nsHttpChannel::CallOnResume resuming previous cache pump "
+ "%p, this=%p",
+ cachePump.get(), self.get()));
+ cachePump->Resume();
+ }
+
+ // Any newly created pumps were suspended once because of
+ // AsyncResumePending. Problem is that the stream listener
+ // notification is already pending in the queue right now, because
+ // AsyncRead doesn't (regardless if called after Suspend) respect
+ // the suspend coutner and the right order would not be preserved.
+ // Hence, we do another dispatch round to actually Resume after
+ // the notification from the original pump.
+ if (transactionPump != self->mTransactionPump &&
+ self->mTransactionPump) {
+ LOG(
+ ("nsHttpChannel::CallOnResume async-resuming new "
+ "transaction "
+ "pump %p, this=%p",
+ self->mTransactionPump.get(), self.get()));
+
+ nsCOMPtr<nsIRequest> pump = self->mTransactionPump;
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "nsHttpChannel::CallOnResume new transaction",
+ [pump{std::move(pump)}]() { pump->Resume(); }));
+ }
+ if (cachePump != self->mCachePump && self->mCachePump) {
+ LOG(
+ ("nsHttpChannel::CallOnResume async-resuming new cache pump "
+ "%p, this=%p",
+ self->mCachePump.get(), self.get()));
+
+ RefPtr<nsInputStreamPump> pump = self->mCachePump;
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "nsHttpChannel::CallOnResume new pump",
+ [pump{std::move(pump)}]() { pump->Resume(); }));
+ }
+ }));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+ }
+ }
+
+ nsresult rvTransaction = NS_OK;
+ if (mTransactionPump) {
+ rvTransaction = mTransactionPump->Resume();
+ }
+
+ nsresult rvCache = NS_OK;
+ if (mCachePump) {
+ rvCache = mCachePump->Resume();
+ }
+
+ return NS_FAILED(rvTransaction) ? rvTransaction : rvCache;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetSecurityInfo(nsITransportSecurityInfo** securityInfo) {
+ NS_ENSURE_ARG_POINTER(securityInfo);
+ *securityInfo = do_AddRef(mSecurityInfo).take();
+ return NS_OK;
+}
+
+// If any of the functions that AsyncOpen calls returns immediately an error
+// AsyncAbort(which calls onStart/onStopRequest) does not need to be call.
+// To be sure that they are not call ReleaseListeners() is called.
+// If AsyncOpen returns NS_OK, after that point AsyncAbort must be called on
+// any error.
+NS_IMETHODIMP
+nsHttpChannel::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ReleaseListeners();
+ return rv;
+ }
+
+ MOZ_ASSERT(
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL &&
+ mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()),
+ "security flags in loadInfo but doContentSecurityCheck() not called");
+
+ LOG(("nsHttpChannel::AsyncOpen [this=%p]\n", this));
+ mOpenerCallingScriptLocation = CallingScriptLocationString();
+ LogCallingScriptLocation(this, mOpenerCallingScriptLocation);
+ NS_CompareLoadInfoAndLoadContext(this);
+
+#ifdef DEBUG
+ AssertPrivateBrowsingId();
+#endif
+
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
+
+ if (mCanceled) {
+ ReleaseListeners();
+ return NS_FAILED(mStatus) ? mStatus : NS_ERROR_FAILURE;
+ }
+
+ if (MaybeWaitForUploadStreamNormalization(listener, nullptr)) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gHttpHandler->Active()) {
+ LOG((" after HTTP shutdown..."));
+ ReleaseListeners();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv)) {
+ ReleaseListeners();
+ return rv;
+ }
+
+ // If no one called SetLoadGroup or SetNotificationCallbacks, the private
+ // state has not been updated on PrivateBrowsingChannel (which we derive
+ // from) Same if the loadinfo has changed since the creation of the channel.
+ // Hence, we have to call UpdatePrivateBrowsing() here
+ UpdatePrivateBrowsing();
+
+ AntiTrackingUtils::UpdateAntiTrackingInfoForChannel(this);
+
+ // Recalculate the default userAgent header after the AntiTrackingInfo gets
+ // updated because we can only know whether the site is exempted from
+ // fingerprinting protection after we have the AntiTracking Info.
+ //
+ // Note that we don't recalculate the header if it has been modified since the
+ // channel was created because we want to preserve the modified header.
+ if (!LoadIsUserAgentHeaderModified()) {
+ rv = mRequestHead.SetHeader(
+ nsHttp::User_Agent,
+ gHttpHandler->UserAgent(nsContentUtils::ShouldResistFingerprinting(
+ this, RFPTarget::HttpUserAgent)),
+ false, nsHttpHeaderArray::eVarietyRequestEnforceDefault);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ if (WaitingForTailUnblock()) {
+ // This channel is marked as Tail and is part of a request context
+ // that has positive number of non-tailed requestst, hence this channel
+ // has been put to a queue.
+ // When tail is unblocked, OnTailUnblock on this channel will be called
+ // to continue AsyncOpen.
+ mListener = listener;
+ MOZ_DIAGNOSTIC_ASSERT(!mOnTailUnblock);
+ mOnTailUnblock = &nsHttpChannel::AsyncOpenOnTailUnblock;
+
+ LOG((" put on hold until tail is unblocked"));
+ return NS_OK;
+ }
+
+ // Remember the cookie header that was set, if any
+ nsAutoCString cookieHeader;
+ if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookieHeader))) {
+ mUserSetCookieHeader = cookieHeader;
+ }
+
+ // Set user agent override, do so before OnOpeningRequest notification
+ // since we want to allow consumers of that notification change or remove
+ // the User-Agent request header.
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ // After we notify any observers (on-opening-request, loadGroup, etc) we
+ // must return NS_OK and return any errors asynchronously via
+ // OnStart/OnStopRequest. Observers may add a reference to the channel
+ // and expect to get OnStopRequest so they know when to drop the reference,
+ // etc.
+
+ // notify "http-on-opening-request" observers, but not if this is a redirect
+ if (!(mLoadFlags & LOAD_REPLACE)) {
+ gHttpHandler->OnOpeningRequest(this);
+ }
+
+ StoreIsPending(true);
+ StoreWasOpened(true);
+
+ mListener = listener;
+
+ if (nsIOService::UseSocketProcess() &&
+ !gIOService->IsSocketProcessLaunchComplete()) {
+ RefPtr<nsHttpChannel> self = this;
+ gIOService->CallOrWaitForSocketProcess(
+ [self]() { self->AsyncOpenFinal(TimeStamp::Now()); });
+ return NS_OK;
+ }
+
+ AsyncOpenFinal(TimeStamp::Now());
+
+ return NS_OK;
+}
+
+void nsHttpChannel::AsyncOpenFinal(TimeStamp aTimeStamp) {
+ // We save this timestamp from outside of the if block in case we enable the
+ // profiler after AsyncOpen().
+ mLastStatusReported = TimeStamp::Now();
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, mCacheDisposition,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ }
+
+ // Added due to PauseTask/DelayHttpChannel
+ if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr);
+
+ // record asyncopen time unconditionally and clear it if we
+ // don't want it after OnModifyRequest() weighs in. But waiting for
+ // that to complete would mean we don't include proxy resolution in the
+ // timing.
+ if (!LoadAsyncOpenTimeOverriden()) {
+ mAsyncOpenTime = aTimeStamp;
+ }
+
+ // Remember we have Authorization header set here. We need to check on it
+ // just once and early, AsyncOpen is the best place.
+ StoreCustomAuthHeader(mRequestHead.HasHeader(nsHttp::Authorization));
+
+ bool willCallback = false;
+ // We are about to do an async lookup to check if the URI is a tracker. If
+ // yes, this channel will be canceled by channel classifier. Chances are the
+ // lookup is not needed so CheckIsTrackerWithLocalTable() will return an
+ // error and then we can MaybeResolveProxyAndBeginConnect() right away.
+ // We skip the check in case this is an internal redirected channel
+ if (!LoadAuthRedirectedChannel() && NS_ShouldClassifyChannel(this)) {
+ RefPtr<nsHttpChannel> self = this;
+ willCallback = NS_SUCCEEDED(
+ AsyncUrlChannelClassifier::CheckChannel(this, [self]() -> void {
+ nsCOMPtr<nsIURI> uri;
+ self->GetURI(getter_AddRefs(uri));
+ MOZ_ASSERT(uri);
+
+ // Finish the AntiTracking Heuristic before
+ // MaybeResolveProxyAndBeginConnect().
+ FinishAntiTrackingRedirectHeuristic(self, uri);
+
+ self->MaybeResolveProxyAndBeginConnect();
+ }));
+ }
+
+ if (!willCallback) {
+ // We can do MaybeResolveProxyAndBeginConnect immediately if
+ // CheckIsTrackerWithLocalTable is failed. Note that we don't need to
+ // handle the failure because BeginConnect() will return synchronously and
+ // the caller will be responsible for handling it.
+ MaybeResolveProxyAndBeginConnect();
+ }
+}
+
+void nsHttpChannel::MaybeResolveProxyAndBeginConnect() {
+ nsresult rv;
+
+ // The common case for HTTP channels is to begin proxy resolution and return
+ // at this point. The only time we know mProxyInfo already is if we're
+ // proxying a non-http protocol like ftp. We don't need to discover proxy
+ // settings if we are never going to make a network connection.
+ if (!mProxyInfo &&
+ !(mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_NO_NETWORK_IO)) &&
+ !BypassProxy() && NS_SUCCEEDED(ResolveProxy())) {
+ return;
+ }
+
+ if (!gHttpHandler->Active()) {
+ LOG(
+ ("nsHttpChannel::MaybeResolveProxyAndBeginConnect [this=%p] "
+ "Handler no longer active.\n",
+ this));
+ rv = NS_ERROR_NOT_AVAILABLE;
+ } else {
+ rv = BeginConnect();
+ }
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(rv);
+ }
+}
+
+nsresult nsHttpChannel::AsyncOpenOnTailUnblock() {
+ return AsyncOpen(mListener);
+}
+
+already_AddRefed<nsChannelClassifier>
+nsHttpChannel::GetOrCreateChannelClassifier() {
+ if (!mChannelClassifier) {
+ mChannelClassifier = new nsChannelClassifier(this);
+ LOG(("nsHttpChannel [%p] created nsChannelClassifier [%p]\n", this,
+ mChannelClassifier.get()));
+ }
+
+ RefPtr<nsChannelClassifier> classifier = mChannelClassifier;
+ return classifier.forget();
+}
+
+uint16_t nsHttpChannel::GetProxyDNSStrategy() {
+ // This function currently only supports returning DNS_PREFETCH_ORIGIN.
+ // Support for the rest of the DNS_* flags will be added later.
+
+ if (!mProxyInfo) {
+ return DNS_PREFETCH_ORIGIN;
+ }
+
+ nsAutoCString type;
+ mProxyInfo->GetType(type);
+
+ if (!StaticPrefs::network_proxy_socks_remote_dns()) {
+ if (type.EqualsLiteral("socks")) {
+ return DNS_PREFETCH_ORIGIN;
+ }
+ }
+
+ return 0;
+}
+
+// BeginConnect() SHOULD NOT call AsyncAbort(). AsyncAbort will be called by
+// functions that called BeginConnect if needed. Only
+// MaybeResolveProxyAndBeginConnect and OnProxyAvailable ever call
+// BeginConnect.
+nsresult nsHttpChannel::BeginConnect() {
+ LOG(("nsHttpChannel::BeginConnect [this=%p]\n", this));
+ nsresult rv;
+
+ // It is the caller's responsibility to not call us late in shutdown.
+ MOZ_ASSERT(gHttpHandler->Active());
+
+ // Construct connection info object
+ nsAutoCString host;
+ nsAutoCString scheme;
+ int32_t port = -1;
+ bool isHttps = mURI->SchemeIs("https");
+
+ rv = mURI->GetScheme(scheme);
+ if (NS_SUCCEEDED(rv)) rv = mURI->GetAsciiHost(host);
+ if (NS_SUCCEEDED(rv)) rv = mURI->GetPort(&port);
+ if (NS_SUCCEEDED(rv)) rv = mURI->GetAsciiSpec(mSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Just a warning here because some nsIURIs do not implement this method.
+ Unused << NS_WARN_IF(NS_FAILED(mURI->GetUsername(mUsername)));
+
+ // Reject the URL if it doesn't specify a host
+ if (host.IsEmpty()) {
+ rv = NS_ERROR_MALFORMED_URI;
+ return rv;
+ }
+ LOG(("host=%s port=%d\n", host.get(), port));
+ LOG(("uri=%s\n", mSpec.get()));
+
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (mProxyInfo) proxyInfo = do_QueryInterface(mProxyInfo);
+
+ if (mCaps & NS_HTTP_CONNECT_ONLY) {
+ if (!proxyInfo) {
+ LOG(("return failure: no proxy for connect-only channel\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!proxyInfo->IsHTTP() && !proxyInfo->IsHTTPS()) {
+ LOG(("return failure: non-http proxy for connect-only channel\n"));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ mRequestHead.SetHTTPS(isHttps);
+ mRequestHead.SetOrigin(scheme, host, port);
+
+ SetOriginHeader();
+ SetDoNotTrack();
+ SetGlobalPrivacyControl();
+
+ OriginAttributes originAttributes;
+ // Regular principal in case we have a proxy.
+ if (proxyInfo &&
+ !StaticPrefs::privacy_partition_network_state_connection_with_proxy()) {
+ StoragePrincipalHelper::GetOriginAttributes(
+ this, originAttributes, StoragePrincipalHelper::eRegularPrincipal);
+ } else {
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(
+ this, originAttributes);
+ }
+
+ // Adjust mCaps according to our request headers:
+ // - If "Connection: close" is set as a request header, then do not bother
+ // trying to establish a keep-alive connection.
+ if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close")) {
+ mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE);
+ StoreAllowHttp3(false);
+ }
+
+ gHttpHandler->MaybeAddAltSvcForTesting(mURI, mUsername, mPrivateBrowsing,
+ mCallbacks, originAttributes);
+
+ RefPtr<nsHttpConnectionInfo> connInfo;
+#ifdef FUZZING
+ if (StaticPrefs::fuzzing_necko_http3()) {
+ connInfo =
+ new nsHttpConnectionInfo(host, port, "h3"_ns, mUsername, proxyInfo,
+ originAttributes, host, port, true);
+ } else {
+#endif
+ if (mWebTransportSessionEventListener) {
+ connInfo =
+ new nsHttpConnectionInfo(host, port, "h3"_ns, mUsername, proxyInfo,
+ originAttributes, isHttps, true, true);
+ } else {
+ connInfo = new nsHttpConnectionInfo(host, port, ""_ns, mUsername,
+ proxyInfo, originAttributes, isHttps);
+ }
+#ifdef FUZZING
+ }
+#endif
+
+ bool http2Allowed = !gHttpHandler->IsHttp2Excluded(connInfo);
+
+ bool http3Allowed = Http3Allowed();
+ if (!http3Allowed) {
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+
+ RefPtr<AltSvcMapping> mapping;
+ if (!mConnectionInfo && LoadAllowAltSvc() && // per channel
+ !mWebTransportSessionEventListener && (http2Allowed || http3Allowed) &&
+ !(mLoadFlags & LOAD_FRESH_CONNECTION) &&
+ AltSvcMapping::AcceptableProxy(proxyInfo) &&
+ (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) &&
+ (mapping = gHttpHandler->GetAltServiceMapping(
+ scheme, host, port, mPrivateBrowsing, originAttributes, http2Allowed,
+ http3Allowed))) {
+ LOG(("nsHttpChannel %p Alt Service Mapping Found %s://%s:%d [%s]\n", this,
+ scheme.get(), mapping->AlternateHost().get(), mapping->AlternatePort(),
+ mapping->HashKey().get()));
+
+ if (!(mLoadFlags & LOAD_ANONYMOUS) && !mPrivateBrowsing) {
+ nsAutoCString altUsedLine(mapping->AlternateHost());
+ bool defaultPort =
+ mapping->AlternatePort() ==
+ (isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT);
+ if (!defaultPort) {
+ altUsedLine.AppendLiteral(":");
+ altUsedLine.AppendInt(mapping->AlternatePort());
+ }
+ // Like what we did for 'Authorization' header, we need to do the same for
+ // 'Alt-Used' for avoiding this header being shown in the ServiceWorker
+ // FetchEvent.
+ Unused << mRequestHead.ClearHeader(nsHttp::Alternate_Service_Used);
+ rv = mRequestHead.SetHeader(nsHttp::Alternate_Service_Used, altUsedLine,
+ false,
+ nsHttpHeaderArray::eVarietyRequestDefault);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService && !host.Equals(mapping->AlternateHost())) {
+ nsAutoString message(u"Alternate Service Mapping found: "_ns);
+ AppendASCIItoUTF16(scheme, message);
+ message.AppendLiteral(u"://");
+ AppendASCIItoUTF16(host, message);
+ message.AppendLiteral(u":");
+ message.AppendInt(port);
+ message.AppendLiteral(u" to ");
+ AppendASCIItoUTF16(scheme, message);
+ message.AppendLiteral(u"://");
+ AppendASCIItoUTF16(mapping->AlternateHost(), message);
+ message.AppendLiteral(u":");
+ message.AppendInt(mapping->AlternatePort());
+ consoleService->LogStringMessage(message.get());
+ }
+
+ LOG(("nsHttpChannel %p Using connection info from altsvc mapping", this));
+ mapping->GetConnectionInfo(getter_AddRefs(mConnectionInfo), proxyInfo,
+ originAttributes);
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, true);
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC_OE, !isHttps);
+ } else if (mConnectionInfo) {
+ LOG(("nsHttpChannel %p Using channel supplied connection info", this));
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
+ } else {
+ LOG(("nsHttpChannel %p Using default connection info", this));
+
+ mConnectionInfo = connInfo;
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
+ }
+
+ bool httpsRRAllowed =
+ !LoadBeConservative() && !(mCaps & NS_HTTP_BE_CONSERVATIVE) &&
+ !(mLoadInfo->TriggeringPrincipal()->IsSystemPrincipal() &&
+ mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) &&
+ !mConnectionInfo->UsingConnect();
+ if (!httpsRRAllowed) {
+ mCaps |= NS_HTTP_DISALLOW_HTTPS_RR;
+ }
+ // No need to lookup HTTPSSVC record if mHTTPSSVCRecord already contains a
+ // value.
+ StoreUseHTTPSSVC(StaticPrefs::network_dns_upgrade_with_https_rr() &&
+ httpsRRAllowed && mHTTPSSVCRecord.isNothing());
+
+ // Need to re-ask the handler, since mConnectionInfo may not be the connInfo
+ // we used earlier
+ if (!mConnectionInfo->IsHttp3() &&
+ gHttpHandler->IsHttp2Excluded(mConnectionInfo)) {
+ StoreAllowSpdy(0);
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ mConnectionInfo->SetNoSpdy(true);
+ }
+
+ // We can be passed with the auth provider if this channel was
+ // a result of redirect due to auth retry
+ if (!mAuthProvider) {
+ mAuthProvider = new nsHttpChannelAuthProvider();
+ }
+
+ rv = mAuthProvider->Init(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // check to see if authorization headers should be included
+ // CustomAuthHeader is set in AsyncOpen if we find Authorization header
+ rv = mAuthProvider->AddAuthorizationHeaders(LoadCustomAuthHeader());
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpChannel %p AddAuthorizationHeaders failed (%08x)", this,
+ static_cast<uint32_t>(rv)));
+ }
+
+ // If TimingEnabled flag is not set after OnModifyRequest() then
+ // clear the already recorded AsyncOpen value for consistency.
+ if (!LoadTimingEnabled()) mAsyncOpenTime = TimeStamp();
+
+ // if this somehow fails we can go on without it
+ Unused << gHttpHandler->AddConnectionHeader(&mRequestHead, mCaps);
+
+ if (!LoadIsTRRServiceChannel() &&
+ (mLoadFlags & VALIDATE_ALWAYS ||
+ BYPASS_LOCAL_CACHE(mLoadFlags, LoadPreferCacheLoadOverBypass()))) {
+ mCaps |= NS_HTTP_REFRESH_DNS;
+ }
+
+ if (gHttpHandler->CriticalRequestPrioritization()) {
+ if (mClassOfService.Flags() & nsIClassOfService::Leader) {
+ mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
+ }
+ if (mClassOfService.Flags() & nsIClassOfService::Unblocked) {
+ mCaps |= NS_HTTP_LOAD_UNBLOCKED;
+ }
+ if (mClassOfService.Flags() & nsIClassOfService::UrgentStart &&
+ gHttpHandler->IsUrgentStartEnabled()) {
+ mCaps |= NS_HTTP_URGENT_START;
+ SetPriority(nsISupportsPriority::PRIORITY_HIGHEST);
+ }
+ }
+
+ // Force-Reload should reset the persistent connection pool for this host
+ if (mLoadFlags & LOAD_FRESH_CONNECTION) {
+ // just the initial document resets the whole pool
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+ gHttpHandler->AltServiceCache()->ClearAltServiceMappings();
+ rv = gHttpHandler->DoShiftReloadConnectionCleanupWithConnInfo(
+ mConnectionInfo);
+ if (NS_FAILED(rv)) {
+ LOG((
+ "nsHttpChannel::BeginConnect "
+ "DoShiftReloadConnectionCleanupWithConnInfo failed: %08x [this=%p]",
+ static_cast<uint32_t>(rv), this));
+ }
+ }
+ }
+
+ // We may have been cancelled already, either by on-modify-request
+ // listeners or load group observers; in that case, we should not send the
+ // request to the server
+ if (mCanceled) {
+ return mStatus;
+ }
+ // skip classifier checks if this channel was the result of internal auth
+ // redirect
+ bool shouldBeClassified =
+ !LoadAuthRedirectedChannel() && NS_ShouldClassifyChannel(this);
+
+ if (shouldBeClassified) {
+ if (LoadChannelClassifierCancellationPending()) {
+ LOG(
+ ("Waiting for safe-browsing protection cancellation in BeginConnect "
+ "[this=%p]\n",
+ this));
+ return NS_OK;
+ }
+
+ ReEvaluateReferrerAfterTrackingStatusIsKnown();
+ }
+
+ rv = MaybeStartDNSPrefetch();
+ if (NS_FAILED(rv)) {
+ auto dnsStrategy = GetProxyDNSStrategy();
+ if (dnsStrategy & DNS_BLOCK_ON_ORIGIN_RESOLVE) {
+ // TODO: Should this be fatal?
+ return rv;
+ }
+ // Otherwise this shouldn't be fatal.
+ return NS_OK;
+ }
+
+ rv = CallOrWaitForResume(
+ [](nsHttpChannel* self) { return self->PrepareToConnect(); });
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (shouldBeClassified) {
+ // Start nsChannelClassifier to catch phishing and malware URIs.
+ RefPtr<nsChannelClassifier> channelClassifier =
+ GetOrCreateChannelClassifier();
+ LOG(("nsHttpChannel::Starting nsChannelClassifier %p [this=%p]",
+ channelClassifier.get(), this));
+ channelClassifier->Start();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::MaybeStartDNSPrefetch() {
+ // Start a DNS lookup very early in case the real open is queued the DNS can
+ // happen in parallel. Do not do so in the presence of an HTTP proxy as
+ // all lookups other than for the proxy itself are done by the proxy.
+ // Also we don't do a lookup if the LOAD_NO_NETWORK_IO or
+ // LOAD_ONLY_FROM_CACHE flags are set.
+ //
+ // We keep the DNS prefetch object around so that we can retrieve
+ // timing information from it. There is no guarantee that we actually
+ // use the DNS prefetch data for the real connection, but as we keep
+ // this data around for 3 minutes by default, this should almost always
+ // be correct, and even when it isn't, the timing still represents _a_
+ // valid DNS lookup timing for the site, even if it is not _the_
+ // timing we used.
+ if ((mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE)) ||
+ LoadAuthRedirectedChannel()) {
+ return NS_OK;
+ }
+
+ auto dnsStrategy = GetProxyDNSStrategy();
+
+ LOG(
+ ("nsHttpChannel::MaybeStartDNSPrefetch [this=%p, strategy=%u] "
+ "prefetching%s\n",
+ this, dnsStrategy,
+ mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : ""));
+
+ if (dnsStrategy & DNS_PREFETCH_ORIGIN) {
+ OriginAttributes originAttributes;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(
+ this, originAttributes);
+
+ mDNSPrefetch =
+ new nsDNSPrefetch(mURI, originAttributes, nsIRequest::GetTRRMode(),
+ this, LoadTimingEnabled());
+ nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ dnsFlags |= nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+ nsresult rv = mDNSPrefetch->PrefetchHigh(dnsFlags);
+
+ if (dnsStrategy & DNS_BLOCK_ON_ORIGIN_RESOLVE) {
+ LOG((" blocking on prefetching origin"));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG((" lookup failed with 0x%08" PRIx32 ", aborting request",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ // Resolved in OnLookupComplete.
+ mDNSBlockingThenable = mDNSBlockingPromise.Ensure(__func__);
+ }
+
+ if (gHttpHandler->UseHTTPSRRAsAltSvcEnabled() && !mHTTPSSVCRecord &&
+ !(mCaps & NS_HTTP_DISALLOW_HTTPS_RR)) {
+ MOZ_ASSERT(!mHTTPSSVCRecord);
+
+ OriginAttributes originAttributes;
+ StoragePrincipalHelper::GetOriginAttributesForHTTPSRR(this,
+ originAttributes);
+
+ RefPtr<nsDNSPrefetch> resolver =
+ new nsDNSPrefetch(mURI, originAttributes, nsIRequest::GetTRRMode());
+ Unused << resolver->FetchHTTPSSVC(mCaps & NS_HTTP_REFRESH_DNS, true,
+ [](nsIDNSHTTPSSVCRecord*) {
+ // Do nothing. This is a DNS prefetch.
+ });
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) {
+ if (mCacheEntry && !LoadCacheEntryIsWriteOnly()) {
+ int64_t dataSize = 0;
+ mCacheEntry->GetDataSize(&dataSize);
+ *aEncodedBodySize = dataSize;
+ } else {
+ *aEncodedBodySize = mLogicalOffset;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetIsAuthChannel(bool* aIsAuthChannel) {
+ *aIsAuthChannel = mIsAuthChannel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetChannelIsForDownload(bool aChannelIsForDownload) {
+ if (aChannelIsForDownload) {
+ AddClassFlags(nsIClassOfService::Throttleable);
+ } else {
+ ClearClassFlags(nsIClassOfService::Throttleable);
+ }
+
+ return HttpBaseChannel::SetChannelIsForDownload(aChannelIsForDownload);
+}
+
+base::ProcessId nsHttpChannel::ProcessId() {
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel)) {
+ return httpParent->OtherPid();
+ }
+ if (RefPtr<DocumentLoadListener> docParent = do_QueryObject(parentChannel)) {
+ return docParent->OtherPid();
+ }
+ return base::GetCurrentProcId();
+}
+
+auto nsHttpChannel::AttachStreamFilter() -> RefPtr<ChildEndpointPromise> {
+ LOG(("nsHttpChannel::AttachStreamFilter [this=%p]", this));
+ MOZ_ASSERT(!LoadOnStartRequestCalled());
+
+ if (!ProcessId()) {
+ return ChildEndpointPromise::CreateAndReject(false, __func__);
+ }
+
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+
+ // If our listener is a DocumentLoadListener, then we might handle
+ // multi-part responses here in the parent process. The current extension
+ // API doesn't understand the parsed multipart format, so we defer responding
+ // here until CallOnStartRequest, and attach the StreamFilter before the
+ // multipart handler (in the parent process!) if applicable.
+ if (RefPtr<DocumentLoadListener> docParent = do_QueryObject(parentChannel)) {
+ StreamFilterRequest* request = mStreamFilterRequests.AppendElement();
+ request->mPromise = new ChildEndpointPromise::Private(__func__);
+ return request->mPromise;
+ }
+
+ mozilla::ipc::Endpoint<extensions::PStreamFilterParent> parent;
+ mozilla::ipc::Endpoint<extensions::PStreamFilterChild> child;
+ nsresult rv = extensions::PStreamFilter::CreateEndpoints(&parent, &child);
+ if (NS_FAILED(rv)) {
+ return ChildEndpointPromise::CreateAndReject(false, __func__);
+ }
+
+ if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel)) {
+ return httpParent->AttachStreamFilter(std::move(parent), std::move(child));
+ }
+
+ extensions::StreamFilterParent::Attach(this, std::move(parent));
+ return ChildEndpointPromise::CreateAndResolve(std::move(child), __func__);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) {
+ LOG(("nsHttpChannel::GetNavigationStartTimeStamp [this=%p]", this));
+ MOZ_ASSERT(aTimeStamp);
+ *aTimeStamp = mNavigationStartTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetNavigationStartTimeStamp(TimeStamp aTimeStamp) {
+ LOG(("nsHttpChannel::SetNavigationStartTimeStamp [this=%p]", this));
+ mNavigationStartTimeStamp = aTimeStamp;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsISupportsPriority
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::SetPriority(int32_t value) {
+ int16_t newValue = clamped<int32_t>(value, INT16_MIN, INT16_MAX);
+ if (mPriority == newValue) return NS_OK;
+
+ LOG(("nsHttpChannel::SetPriority %p p=%d", this, newValue));
+
+ mPriority = newValue;
+ if (mTransaction) {
+ nsresult rv = gHttpHandler->RescheduleTransaction(mTransaction, mPriority);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpChannel::SetPriority [this=%p] "
+ "RescheduleTransaction failed (%08x)",
+ this, static_cast<uint32_t>(rv)));
+ }
+ }
+
+ // If this channel is the real channel for an e10s channel, notify the
+ // child side about the priority change as well.
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel);
+ if (httpParent) {
+ httpParent->DoSendSetPriority(newValue);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannel::nsIClassOfService
+//-----------------------------------------------------------------------------
+
+void nsHttpChannel::OnClassOfServiceUpdated() {
+ LOG(("nsHttpChannel::OnClassOfServiceUpdated this=%p, cos=%lu, inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+
+ if (mTransaction) {
+ gHttpHandler->UpdateClassOfServiceOnTransaction(mTransaction,
+ mClassOfService);
+ }
+ if (EligibleForTailing()) {
+ RemoveAsNonTailRequest();
+ } else {
+ AddAsNonTailRequest();
+ }
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService.Flags();
+ mClassOfService.SetFlags(inFlags);
+ if (previous != mClassOfService.Flags()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::AddClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService.Flags();
+ mClassOfService.SetFlags(inFlags | mClassOfService.Flags());
+ if (previous != mClassOfService.Flags()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::ClearClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService.Flags();
+ mClassOfService.SetFlags(~inFlags & mClassOfService.Flags());
+ if (previous != mClassOfService.Flags()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetClassOfService(ClassOfService cos) {
+ ClassOfService previous = mClassOfService;
+ mClassOfService = cos;
+ if (previous != mClassOfService) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetIncremental(bool incremental) {
+ bool previous = mClassOfService.Incremental();
+ mClassOfService.SetIncremental(incremental);
+ if (previous != mClassOfService.Incremental()) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIProtocolProxyCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::OnProxyAvailable(nsICancelable* request, nsIChannel* channel,
+ nsIProxyInfo* pi, nsresult status) {
+ LOG(("nsHttpChannel::OnProxyAvailable [this=%p pi=%p status=%" PRIx32
+ " mStatus=%" PRIx32 "]\n",
+ this, pi, static_cast<uint32_t>(status),
+ static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
+ mProxyRequest = nullptr;
+
+ nsresult rv;
+
+ // If status is a failure code, then it means that we failed to resolve
+ // proxy info. That is a non-fatal error assuming it wasn't because the
+ // request was canceled. We just failover to DIRECT when proxy resolution
+ // fails (failure can mean that the PAC URL could not be loaded).
+
+ if (NS_SUCCEEDED(status)) mProxyInfo = pi;
+
+ if (!gHttpHandler->Active()) {
+ LOG(
+ ("nsHttpChannel::OnProxyAvailable [this=%p] "
+ "Handler no longer active.\n",
+ this));
+ rv = NS_ERROR_NOT_AVAILABLE;
+ } else {
+ rv = BeginConnect();
+ }
+
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(rv);
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIProxiedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetProxyInfo(nsIProxyInfo** result) {
+ if (!mConnectionInfo) {
+ *result = do_AddRef(mProxyInfo).take();
+ } else {
+ *result = do_AddRef(mConnectionInfo->ProxyInfo()).take();
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsITimedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetDomainLookupStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetDomainLookupStart();
+ } else {
+ *_retval = mTransactionTimings.domainLookupStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetDomainLookupEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetDomainLookupEnd();
+ } else {
+ *_retval = mTransactionTimings.domainLookupEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetConnectStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetConnectStart();
+ } else {
+ *_retval = mTransactionTimings.connectStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetTcpConnectEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetTcpConnectEnd();
+ } else {
+ *_retval = mTransactionTimings.tcpConnectEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetSecureConnectionStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetSecureConnectionStart();
+ } else {
+ *_retval = mTransactionTimings.secureConnectionStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetConnectEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetConnectEnd();
+ } else {
+ *_retval = mTransactionTimings.connectEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetRequestStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetRequestStart();
+ } else {
+ *_retval = mTransactionTimings.requestStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetResponseStart(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetResponseStart();
+ } else {
+ *_retval = mTransactionTimings.responseStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetResponseEnd(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetResponseEnd();
+ } else {
+ *_retval = mTransactionTimings.responseEnd;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetTransactionPending(TimeStamp* _retval) {
+ if (mTransaction) {
+ *_retval = mTransaction->GetPendingTime();
+ } else {
+ *_retval = mTransactionTimings.transactionPending;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIHttpAuthenticableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetIsSSL(bool* aIsSSL) {
+ // this attribute is really misnamed - it wants to know if
+ // https:// is being used. SSL might be used to cover http://
+ // in some circumstances (proxies, http/2, etc..)
+ return mURI->SchemeIs("https", aIsSSL);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetProxyMethodIsConnect(bool* aProxyMethodIsConnect) {
+ *aProxyMethodIsConnect = mConnectionInfo->UsingConnect();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetServerResponseHeader(nsACString& value) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ return mResponseHead->GetHeader(nsHttp::Server, value);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetProxyChallenges(nsACString& value) {
+ if (!mResponseHead) return NS_ERROR_UNEXPECTED;
+ return mResponseHead->GetHeader(nsHttp::Proxy_Authenticate, value);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetWWWChallenges(nsACString& value) {
+ if (!mResponseHead) return NS_ERROR_UNEXPECTED;
+ return mResponseHead->GetHeader(nsHttp::WWW_Authenticate, value);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetProxyCredentials(const nsACString& value) {
+ return mRequestHead.SetHeader(nsHttp::Proxy_Authorization, value);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetWWWCredentials(const nsACString& value) {
+ // This method is called when various browser initiated authorization
+ // code sets the credentials. We need to flag this header as the
+ // "browser default" so it does not show up in the ServiceWorker
+ // FetchEvent. This may actually get called more than once, though,
+ // so we clear the header first since "default" headers are not
+ // allowed to overwrite normally.
+ Unused << mRequestHead.ClearHeader(nsHttp::Authorization);
+ return mRequestHead.SetHeader(nsHttp::Authorization, value, false,
+ nsHttpHeaderArray::eVarietyRequestDefault);
+}
+
+//-----------------------------------------------------------------------------
+// Methods that nsIHttpAuthenticableChannel dupes from other IDLs, which we
+// get from HttpBaseChannel, must be explicitly forwarded, because C++ sucks.
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ return HttpBaseChannel::GetLoadFlags(aLoadFlags);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetURI(nsIURI** aURI) { return HttpBaseChannel::GetURI(aURI); }
+
+NS_IMETHODIMP
+nsHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
+ return HttpBaseChannel::GetNotificationCallbacks(aCallbacks);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ return HttpBaseChannel::GetLoadGroup(aLoadGroup);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetRequestMethod(nsACString& aMethod) {
+ return HttpBaseChannel::GetRequestMethod(aMethod);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+void nsHttpChannel::RecordOnStartTelemetry(nsresult aStatus,
+ bool aIsNavigation) {
+ Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_ONSTART_SUCCESS,
+ NS_SUCCEEDED(aStatus));
+
+ if (mTransaction) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP3_CHANNEL_ONSTART_SUCCESS,
+ (mTransaction->IsHttp3Used()) ? "http3"_ns : "no_http3"_ns,
+ NS_SUCCEEDED(aStatus));
+ }
+
+ enum class HttpOnStartState : uint32_t {
+ Success = 0,
+ DNSError = 1,
+ Others = 2,
+ };
+
+ if (TRRService::Get() && TRRService::Get()->IsConfirmed()) {
+ // Note this telemetry probe is not working when DNS resolution is done in
+ // the socket process.
+ HttpOnStartState state = HttpOnStartState::Others;
+ if (NS_SUCCEEDED(aStatus)) {
+ state = HttpOnStartState::Success;
+ } else if (aStatus == NS_ERROR_UNKNOWN_HOST ||
+ aStatus == NS_ERROR_UNKNOWN_PROXY_HOST) {
+ state = HttpOnStartState::DNSError;
+ }
+
+ if (aIsNavigation) {
+ Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_PAGE_ONSTART_SUCCESS_TRR3,
+ TRRService::ProviderKey(),
+ static_cast<uint32_t>(state));
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_SUB_ONSTART_SUCCESS_TRR3,
+ TRRService::ProviderKey(),
+ static_cast<uint32_t>(state));
+ }
+ }
+
+ if (nsIOService::UseSocketProcess() && mTransaction) {
+ const TimeStamp now = TimeStamp::Now();
+ TimeStamp responseEnd = mTransaction->GetResponseEnd();
+ if (!responseEnd.IsNull()) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::ResponseEndSocketToParent,
+ now - responseEnd);
+ }
+
+ mOnStartRequestStartTime = mTransaction->GetOnStartRequestStartTime();
+ if (!mOnStartRequestStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::OnStartRequestSocketToParent,
+ now - mOnStartRequestStartTime);
+ }
+ } else {
+ mOnStartRequestStartTime = TimeStamp::Now();
+ }
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnStartRequest(nsIRequest* request) {
+ nsresult rv;
+
+ MOZ_ASSERT(LoadRequestObserversCalled());
+
+ AUTO_PROFILER_LABEL("nsHttpChannel::OnStartRequest", NETWORK);
+
+ if (!(mCanceled || NS_FAILED(mStatus)) &&
+ !WRONG_RACING_RESPONSE_SOURCE(request)) {
+ // capture the request's status, so our consumers will know ASAP of any
+ // connection failures, etc - bug 93581
+ nsresult status;
+ request->GetStatus(&status);
+ mStatus = status;
+ }
+
+ if (mStatus == NS_ERROR_NON_LOCAL_CONNECTION_REFUSED) {
+ MOZ_CRASH_UNSAFE(nsPrintfCString("Attempting to connect to non-local "
+ "address! opener is [%s], uri is "
+ "[%s]",
+ mOpenerCallingScriptLocation
+ ? mOpenerCallingScriptLocation->get()
+ : "unknown",
+ mURI->GetSpecOrDefault().get())
+ .get());
+ }
+
+ LOG(("nsHttpChannel::OnStartRequest [this=%p request=%p status=%" PRIx32
+ "]\n",
+ this, request, static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
+
+ RecordOnStartTelemetry(mStatus, IsNavigation());
+
+ if (mRaceCacheWithNetwork) {
+ LOG(
+ (" racingNetAndCache - mFirstResponseSource:%d fromCache:%d "
+ "fromNet:%d\n",
+ static_cast<int32_t>(mFirstResponseSource), request == mCachePump,
+ request == mTransactionPump));
+ if (mFirstResponseSource == RESPONSE_PENDING) {
+ // When the cache wins mFirstResponseSource is set to
+ // RESPONSE_FROM_CACHE earlier in ReadFromCache, so this must be a
+ // response from the network.
+ MOZ_ASSERT(request == mTransactionPump);
+ LOG((" First response from network\n"));
+ {
+ // Race condition with OnCacheEntryCheck, which is not limited
+ // to main thread.
+ mozilla::MutexAutoLock lock(mRCWNLock);
+ mFirstResponseSource = RESPONSE_FROM_NETWORK;
+ mOnStartRequestTimestamp = TimeStamp::Now();
+
+ // Conditional or byte range header could be added in
+ // OnCacheEntryCheck. We need to remove them because the
+ // request might be sent again due to auth retry and we must
+ // not send these headers without having the entry.
+ if (mDidReval) {
+ LOG((" Removing conditional request headers"));
+ UntieValidationRequest();
+ mDidReval = false;
+ }
+ if (LoadCachedContentIsPartial()) {
+ LOG((" Removing byte range request headers"));
+ UntieByteRangeRequest();
+ StoreCachedContentIsPartial(false);
+ }
+ }
+ mAvailableCachedAltDataType.Truncate();
+ StoreDeliveringAltData(false);
+ } else if (WRONG_RACING_RESPONSE_SOURCE(request)) {
+ LOG((" Early return when racing. This response not needed."));
+ return NS_OK;
+ }
+ }
+
+ // Make sure things are what we expect them to be...
+ MOZ_ASSERT(request == mCachePump || request == mTransactionPump,
+ "Unexpected request");
+
+ MOZ_ASSERT(mRaceCacheWithNetwork || !(mTransactionPump && mCachePump) ||
+ LoadCachedContentIsPartial() || LoadTransactionReplaced(),
+ "If we have both pumps, we're racing cache with network, the cache"
+ " content is partial, or the cache entry was revalidated and "
+ "OnStopRequest was not called yet for the transaction pump.");
+
+ StoreAfterOnStartRequestBegun(true);
+ if (mOnStartRequestTimestamp.IsNull()) {
+ mOnStartRequestTimestamp = TimeStamp::Now();
+ }
+
+ Telemetry::Accumulate(Telemetry::HTTP_ONSTART_SUSPEND_TOTAL_TIME,
+ mSuspendTotalTime);
+
+ if (mTransaction) {
+ mProxyConnectResponseCode = mTransaction->GetProxyConnectResponseCode();
+ if (request == mTransactionPump) {
+ StoreDataSentToChildProcess(mTransaction->DataSentToChildProcess());
+ }
+
+ if (!mSecurityInfo && !mCachePump) {
+ // grab the security info from the connection object; the transaction
+ // is guaranteed to own a reference to the connection.
+ mSecurityInfo = mTransaction->SecurityInfo();
+ }
+
+ uint32_t stage = mTransaction->HTTPSSVCReceivedStage();
+ if (!LoadHTTPSSVCTelemetryReported() && stage != HTTPSSVC_NOT_USED) {
+ Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_RECORD_RECEIVING_STAGE,
+ stage);
+ }
+
+ if (HTTPS_RR_IS_USED(stage)) {
+ nsAutoCString suffix(LoadEchConfigUsed() ? "_ech_used" : "");
+ // Determine the result string based on the status.
+ nsAutoCString result(NS_SUCCEEDED(mStatus) ? "success" : "failure");
+ result.Append(suffix);
+
+ mozilla::glean::networking::http_channel_onstart_success_https_rr
+ .Get(result)
+ .Add(1);
+ StoreHasHTTPSRR(true);
+ }
+
+ StoreLoadedBySocketProcess(mTransaction->AsHttpTransactionParent() !=
+ nullptr);
+
+ bool isTrr;
+ bool echConfigUsed;
+ mTransaction->GetNetworkAddresses(mSelfAddr, mPeerAddr, isTrr,
+ mEffectiveTRRMode, mTRRSkipReason,
+ echConfigUsed);
+ StoreResolvedByTRR(isTrr);
+ StoreEchConfigUsed(echConfigUsed);
+ }
+
+ // don't enter this block if we're reading from the cache...
+ if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
+ // mTransactionPump doesn't hit OnInputStreamReady and call this until
+ // all of the response headers have been acquired, so we can take
+ // ownership of them from the transaction.
+ mResponseHead = mTransaction->TakeResponseHead();
+ mSupportsHTTP3 = mTransaction->GetSupportsHTTP3();
+ // the response head may be null if the transaction was cancelled. in
+ // which case we just need to call OnStartRequest/OnStopRequest.
+ if (mResponseHead) return ProcessResponse();
+
+ NS_WARNING("No response head in OnStartRequest");
+ }
+
+ // cache file could be deleted on our behalf, it could contain errors or
+ // it failed to allocate memory, reload from network here.
+ if (mCacheEntry && mCachePump && RECOVER_FROM_CACHE_FILE_ERROR(mStatus)) {
+ LOG((" cache file error, reloading from server"));
+ mCacheEntry->AsyncDoom(nullptr);
+ rv =
+ StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+ if (NS_SUCCEEDED(rv)) return NS_OK;
+ }
+
+ // avoid crashing if mListener happens to be null...
+ if (!mListener) {
+ MOZ_ASSERT_UNREACHABLE("mListener is null");
+ return NS_OK;
+ }
+
+ rv = ProcessCrossOriginSecurityHeaders();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ HandleAsyncAbort();
+ return rv;
+ }
+
+ // No process change is needed, so continue on to ContinueOnStartRequest1.
+ return ContinueOnStartRequest1(rv);
+}
+
+nsresult nsHttpChannel::ContinueOnStartRequest1(nsresult result) {
+ nsresult rv;
+
+ // if process selection failed, cancel this load.
+ if (NS_FAILED(result) && !mCanceled) {
+ Cancel(result);
+ return CallOnStartRequest();
+ }
+
+ // before we start any content load, check for redirectTo being called
+ // this code is executed mainly before we start load from the cache
+ if (mAPIRedirectToURI && !mCanceled) {
+ nsAutoCString redirectToSpec;
+ mAPIRedirectToURI->GetAsciiSpec(redirectToSpec);
+ LOG((" redirectTo called with uri=%s", redirectToSpec.BeginReading()));
+
+ MOZ_ASSERT(!LoadOnStartRequestCalled());
+
+ nsCOMPtr<nsIURI> redirectTo;
+ mAPIRedirectToURI.swap(redirectTo);
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest2);
+ rv = StartRedirectChannelToURI(redirectTo,
+ nsIChannelEventSink::REDIRECT_TEMPORARY);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest2);
+ }
+
+ // Hack: ContinueOnStartRequest2 uses NS_OK to detect successful redirects,
+ // so we distinguish this codepath (a non-redirect that's processing
+ // normally) by passing in a bogus error code.
+ return ContinueOnStartRequest2(NS_BINDING_FAILED);
+}
+
+nsresult nsHttpChannel::ContinueOnStartRequest2(nsresult result) {
+ if (NS_SUCCEEDED(result)) {
+ // Redirect has passed through, we don't want to go on with this
+ // channel. It will now be canceled by the redirect handling code
+ // that called this function.
+ return NS_OK;
+ }
+
+ // on proxy errors, try to failover
+ if (mConnectionInfo->ProxyInfo() &&
+ (mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
+ mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
+ mStatus == NS_ERROR_NET_TIMEOUT || mStatus == NS_ERROR_NET_RESET)) {
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
+ if (NS_SUCCEEDED(ProxyFailover())) {
+ mProxyConnectResponseCode = 0;
+ return NS_OK;
+ }
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
+ }
+
+ // Hack: ContinueOnStartRequest3 uses NS_OK to detect successful redirects,
+ // so we distinguish this codepath (a non-redirect that's processing
+ // normally) by passing in a bogus error code.
+ return ContinueOnStartRequest3(NS_BINDING_FAILED);
+}
+
+nsresult nsHttpChannel::ContinueOnStartRequest3(nsresult result) {
+ if (NS_SUCCEEDED(result)) {
+ // Redirect has passed through, we don't want to go on with this
+ // channel. It will now be canceled by the redirect handling code
+ // that called this function.
+ return NS_OK;
+ }
+
+ return ContinueOnStartRequest4(NS_OK);
+}
+
+nsresult nsHttpChannel::ContinueOnStartRequest4(nsresult result) {
+ LOG(("nsHttpChannel::ContinueOnStartRequest4 [this=%p]", this));
+
+ if (NS_SUCCEEDED(mStatus) && mResponseHead && mAuthProvider) {
+ uint32_t httpStatus = mResponseHead->Status();
+ if (httpStatus != 401 && httpStatus != 407) {
+ nsresult rv = mAuthProvider->CheckForSuperfluousAuth();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ LOG((" CheckForSuperfluousAuth failed (%08x)",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+
+ return CallOnStartRequest();
+}
+
+static void ReportHTTPSRRTelemetry(
+ const Maybe<nsCOMPtr<nsIDNSHTTPSSVCRecord>>& aMaybeRecord) {
+ bool hasHTTPSRR = aMaybeRecord && (aMaybeRecord.ref() != nullptr);
+ if (!hasHTTPSRR) {
+ mozilla::glean::networking::https_rr_presented.Get("none"_ns).Add(1);
+ return;
+ }
+
+ const nsCOMPtr<nsIDNSHTTPSSVCRecord>& record = aMaybeRecord.ref();
+ nsCOMPtr<nsISVCBRecord> svcbRecord;
+ if (NS_SUCCEEDED(record->GetServiceModeRecord(false, false,
+ getter_AddRefs(svcbRecord)))) {
+ MOZ_ASSERT(svcbRecord);
+
+ Maybe<std::tuple<nsCString, SupportedAlpnRank>> alpn =
+ svcbRecord->GetAlpn();
+ bool isHttp3 = alpn ? IsHttp3(std::get<1>(*alpn)) : false;
+ mozilla::glean::networking::https_rr_presented
+ .Get(isHttp3 ? "presented_with_http3"_ns : "presented"_ns)
+ .Add(1);
+ }
+}
+
+static nsLiteralCString ContentTypeToTelemetryLabel(nsHttpChannel* aChannel) {
+ nsAutoCString contentType;
+ aChannel->GetContentType(contentType);
+
+ if (StringBeginsWith(contentType, "text/"_ns)) {
+ if (contentType.EqualsLiteral(TEXT_HTML)) {
+ return "text_html"_ns;
+ }
+ if (contentType.EqualsLiteral(TEXT_CSS)) {
+ return "text_css"_ns;
+ }
+ if (contentType.EqualsLiteral(TEXT_JSON)) {
+ return "text_json"_ns;
+ }
+ if (contentType.EqualsLiteral(TEXT_PLAIN)) {
+ return "text_plain"_ns;
+ }
+ if (contentType.EqualsLiteral(TEXT_JAVASCRIPT)) {
+ return "text_javascript"_ns;
+ }
+ return "text_other"_ns;
+ }
+
+ if (StringBeginsWith(contentType, "audio/"_ns)) {
+ return "audio"_ns;
+ }
+
+ if (StringBeginsWith(contentType, "video/"_ns)) {
+ return "video"_ns;
+ }
+
+ if (StringBeginsWith(contentType, "multipart/"_ns)) {
+ return "multipart"_ns;
+ }
+
+ if (StringBeginsWith(contentType, "image/"_ns)) {
+ if (contentType.EqualsLiteral(IMAGE_ICO) ||
+ contentType.EqualsLiteral(IMAGE_ICO_MS) ||
+ contentType.EqualsLiteral(IMAGE_ICON_MS)) {
+ return "icon"_ns;
+ }
+ return "image"_ns;
+ }
+
+ if (StringBeginsWith(contentType, "application/"_ns)) {
+ if (contentType.EqualsLiteral(APPLICATION_JSON)) {
+ return "text_json"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_OGG)) {
+ return "video"_ns;
+ }
+ if (contentType.EqualsLiteral("application/ocsp-response")) {
+ return "ocsp"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_XPINSTALL)) {
+ return "xpinstall"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_WASM)) {
+ return "wasm"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_PDF) ||
+ contentType.EqualsLiteral(APPLICATION_POSTSCRIPT)) {
+ return "pdf"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_OCTET_STREAM)) {
+ return "octet_stream"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
+ contentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
+ contentType.EqualsLiteral(APPLICATION_XJAVASCRIPT)) {
+ return "text_javascript"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_NS_PROXY_AUTOCONFIG) ||
+ contentType.EqualsLiteral(APPLICATION_NS_JAVASCRIPT_AUTOCONFIG)) {
+ return "proxy"_ns;
+ }
+ if (contentType.EqualsLiteral(APPLICATION_BROTLI) ||
+ contentType.Find("zip") != kNotFound ||
+ contentType.Find("compress") != kNotFound) {
+ return "compressed"_ns;
+ }
+ if (contentType.Find("x509") != kNotFound) {
+ return "x509"_ns;
+ }
+ return "application_other"_ns;
+ }
+
+ if (contentType.EqualsLiteral(BINARY_OCTET_STREAM)) {
+ return "octet_stream"_ns;
+ }
+
+ return "other"_ns;
+}
+
+nsresult nsHttpChannel::LogConsoleError(const char* aTag) {
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(console, NS_ERROR_OUT_OF_MEMORY);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
+ NS_ENSURE_TRUE(console, NS_ERROR_OUT_OF_MEMORY);
+ uint64_t innerWindowID = loadInfo->GetInnerWindowID();
+
+ nsAutoString errorText;
+ nsresult rv = nsContentUtils::GetLocalizedString(
+ nsContentUtils::eNECKO_PROPERTIES, aTag, errorText);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ NS_ENSURE_TRUE(error, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = error->InitWithSourceURI(errorText, mURI, u""_ns, 0, 0,
+ nsIScriptError::errorFlag,
+ "Invalid HTTP Status Lines"_ns, innerWindowID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ console->LogMessage(error);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnStopRequest(nsIRequest* request, nsresult status) {
+ AUTO_PROFILER_LABEL("nsHttpChannel::OnStopRequest", NETWORK);
+
+ LOG(("nsHttpChannel::OnStopRequest [this=%p request=%p status=%" PRIx32 "]\n",
+ this, request, static_cast<uint32_t>(status)));
+
+ LOG(("OnStopRequest %p requestFromCache: %d mFirstResponseSource: %d\n", this,
+ request == mCachePump, static_cast<int32_t>(mFirstResponseSource)));
+
+ MOZ_ASSERT(NS_IsMainThread(),
+ "OnStopRequest should only be called from the main thread");
+
+ if (mStatus == NS_ERROR_PARSING_HTTP_STATUS_LINE) {
+ Unused << LogConsoleError("InvalidHTTPResponseStatusLine");
+ }
+
+ if (WRONG_RACING_RESPONSE_SOURCE(request)) {
+ return NS_OK;
+ }
+
+ // It's possible that LoadUseHTTPSSVC() is false, but we already have
+ // mHTTPSSVCRecord.
+ if (LoadUseHTTPSSVC() || mHTTPSSVCRecord) {
+ ReportHTTPSRRTelemetry(mHTTPSSVCRecord);
+ }
+
+ // If this load failed because of a security error, it may be because we
+ // are in a captive portal - trigger an async check to make sure.
+ int32_t nsprError = -1 * NS_ERROR_GET_CODE(status);
+ if (mozilla::psm::IsNSSErrorCode(nsprError) && IsHTTPS()) {
+ gIOService->RecheckCaptivePortal();
+ }
+
+ if (LoadTimingEnabled() && request == mCachePump) {
+ mCacheReadEnd = TimeStamp::Now();
+
+ ReportNetVSCacheTelemetry();
+ }
+
+ // allow content to be cached if it was loaded successfully (bug #482935)
+ bool contentComplete = NS_SUCCEEDED(status);
+
+ // honor the cancelation status even if the underlying transaction
+ // completed.
+ if (mCanceled || NS_FAILED(mStatus)) status = mStatus;
+
+ if (LoadCachedContentIsPartial()) {
+ if (NS_SUCCEEDED(status)) {
+ // mTransactionPump should be suspended
+ MOZ_ASSERT(request != mTransactionPump,
+ "byte-range transaction finished prematurely");
+
+ if (request == mCachePump) {
+ bool streamDone;
+ status = OnDoneReadingPartialCacheEntry(&streamDone);
+ if (NS_SUCCEEDED(status) && !streamDone) return status;
+ // otherwise, fall through and fire OnStopRequest...
+ } else if (request == mTransactionPump) {
+ MOZ_ASSERT(LoadConcurrentCacheAccess());
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected request");
+ }
+ }
+ // Do not to leave the transaction in a suspended state in error cases.
+ if (NS_FAILED(status) && mTransaction) {
+ nsresult rv = gHttpHandler->CancelTransaction(mTransaction, status);
+ if (NS_FAILED(rv)) {
+ LOG((" CancelTransaction failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+
+ nsCOMPtr<nsICompressConvStats> conv = do_QueryInterface(mCompressListener);
+ if (conv) {
+ conv->GetDecodedDataLength(&mDecodedBodySize);
+ }
+
+ bool isFromNet = request == mTransactionPump;
+
+ if (mTransaction) {
+ // determine if we should call DoAuthRetry
+ bool authRetry = (mAuthRetryPending && NS_SUCCEEDED(status) &&
+ // we should only auth retry in this channel if are not
+ // redirecting a new channel for authentication retries
+ !StaticPrefs::network_auth_use_redirect_for_retries());
+
+ StoreStronglyFramed(mTransaction->ResponseIsComplete());
+ LOG(("nsHttpChannel %p has a strongly framed transaction: %d", this,
+ LoadStronglyFramed()));
+
+ // Save the reference of |mTransaction| to |transactionWithStickyConn|
+ // when it has a sticky connection.
+ // In the case we need to retry an authentication request, we need to
+ // reuse the connection of |transactionWithStickyConn|.
+ RefPtr<HttpTransactionShell> transactionWithStickyConn;
+ if (mCaps & NS_HTTP_STICKY_CONNECTION ||
+ mTransaction->HasStickyConnection()) {
+ transactionWithStickyConn = mTransaction;
+ // Make sure we use the updated caps and connection info from transaction.
+ // We read these values when the transaction is already closed, so there
+ // should be no race.
+ if (mTransaction->Http2Disabled()) {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+ if (mTransaction->Http3Disabled()) {
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+ mConnectionInfo = mTransaction->GetConnInfo();
+ LOG((" transaction %p has sticky connection",
+ transactionWithStickyConn.get()));
+ }
+
+ // this code relies on the code in nsHttpTransaction::Close, which
+ // tests for NS_HTTP_STICKY_CONNECTION to determine whether or not to
+ // keep the connection around after the transaction is finished.
+ //
+ LOG((" mAuthRetryPending=%d, status=%" PRIx32 ", sticky conn cap=%d",
+ static_cast<bool>(mAuthRetryPending), static_cast<uint32_t>(status),
+ mCaps & NS_HTTP_STICKY_CONNECTION));
+ // We must check caps for stickinness also on the transaction because it
+ // might have been updated by the transaction itself during inspection of
+ // the reposnse headers yet on the socket thread (found connection based
+ // auth schema).
+
+ if ((NS_FAILED(status)) && transactionWithStickyConn) {
+ // Close (don't reuse) the sticky connection if this channel has been
+ // cancelled. There are proxy servers known to get confused when we send
+ // a new request over such a half-stated connection.
+ if (!LoadAuthConnectionRestartable()) {
+ LOG((" not reusing a half-authenticated sticky connection"));
+ transactionWithStickyConn->DontReuseConnection();
+ }
+ }
+
+ if (mCaps & NS_HTTP_STICKY_CONNECTION) {
+ mTransaction->SetH2WSConnRefTaken();
+ }
+
+ mTransferSize = mTransaction->GetTransferSize();
+ mRequestSize = mTransaction->GetRequestSize();
+
+ // Make sure the size does not overflow.
+ int32_t totalSize = static_cast<int32_t>(
+ std::clamp<uint64_t>(mRequestSize + mTransferSize, 0LU,
+ std::numeric_limits<int32_t>::max()));
+
+ // Record telemetry for transferred size keyed by contentType
+ nsLiteralCString label = ContentTypeToTelemetryLabel(this);
+ if (mPrivateBrowsing) {
+ mozilla::glean::network::data_size_pb_per_type.Get(label).Add(totalSize);
+ } else {
+ mozilla::glean::network::data_size_per_type.Get(label).Add(totalSize);
+ }
+
+ // If we are using the transaction to serve content, we also save the
+ // time since async open in the cache entry so we can compare telemetry
+ // between cache and net response.
+ // Do not store the time of conditional requests because even if we
+ // fetch the data from the server, the time includes loading of the old
+ // cache entry which would skew the network load time.
+ if (request == mTransactionPump && mCacheEntry && !mDidReval &&
+ !LoadCustomConditionalRequest() && !mAsyncOpenTime.IsNull() &&
+ !mOnStartRequestTimestamp.IsNull()) {
+ uint64_t onStartTime =
+ (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds();
+ uint64_t onStopTime =
+ (TimeStamp::Now() - mAsyncOpenTime).ToMilliseconds();
+ Unused << mCacheEntry->SetNetworkTimes(onStartTime, onStopTime);
+ }
+
+ mResponseTrailers = mTransaction->TakeResponseTrailers();
+
+ if (nsIOService::UseSocketProcess() && mTransaction) {
+ mOnStopRequestStartTime = mTransaction->GetOnStopRequestStartTime();
+ if (!mOnStopRequestStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::OnStopRequestSocketToParent,
+ TimeStamp::Now() - mOnStopRequestStartTime);
+ }
+ } else {
+ mOnStopRequestStartTime = TimeStamp::Now();
+ }
+
+ // at this point, we're done with the transaction
+ mTransactionTimings = mTransaction->Timings();
+ mTransaction = nullptr;
+ mTransactionPump = nullptr;
+
+ // We no longer need the dns prefetch object
+ if (mDNSPrefetch && mDNSPrefetch->TimingsValid() &&
+ !mTransactionTimings.requestStart.IsNull() &&
+ !mTransactionTimings.connectStart.IsNull() &&
+ mDNSPrefetch->EndTimestamp() <= mTransactionTimings.connectStart) {
+ // We only need the domainLookup timestamps when not using a
+ // persistent connection, meaning if the endTimestamp < connectStart
+ mTransactionTimings.domainLookupStart = mDNSPrefetch->StartTimestamp();
+ mTransactionTimings.domainLookupEnd = mDNSPrefetch->EndTimestamp();
+ }
+ mDNSPrefetch = nullptr;
+
+ // handle auth retry...
+ if (authRetry) {
+ mAuthRetryPending = false;
+ auto continueOSR = [authRetry, isFromNet, contentComplete,
+ transactionWithStickyConn](auto* self,
+ nsresult aStatus) {
+ return self->ContinueOnStopRequestAfterAuthRetry(
+ aStatus, authRetry, isFromNet, contentComplete,
+ transactionWithStickyConn);
+ };
+ status = DoAuthRetry(transactionWithStickyConn, continueOSR);
+ if (NS_SUCCEEDED(status)) {
+ return NS_OK;
+ }
+ }
+ return ContinueOnStopRequestAfterAuthRetry(status, authRetry, isFromNet,
+ contentComplete,
+ transactionWithStickyConn);
+ }
+
+ return ContinueOnStopRequest(status, isFromNet, contentComplete);
+}
+
+nsresult nsHttpChannel::ContinueOnStopRequestAfterAuthRetry(
+ nsresult aStatus, bool aAuthRetry, bool aIsFromNet, bool aContentComplete,
+ HttpTransactionShell* aTransWithStickyConn) {
+ LOG(
+ ("nsHttpChannel::ContinueOnStopRequestAfterAuthRetry "
+ "[this=%p, aStatus=%" PRIx32
+ " aAuthRetry=%d, aIsFromNet=%d, aTransWithStickyConn=%p]\n",
+ this, static_cast<uint32_t>(aStatus), aAuthRetry, aIsFromNet,
+ aTransWithStickyConn));
+
+ if (aAuthRetry && NS_SUCCEEDED(aStatus)) {
+ return NS_OK;
+ }
+
+ // If DoAuthRetry failed, or if we have been cancelled since showing
+ // the auth. dialog, then we need to send OnStartRequest now
+ if (aAuthRetry || (mAuthRetryPending && NS_FAILED(aStatus))) {
+ MOZ_ASSERT(NS_FAILED(aStatus), "should have a failure code here");
+ // NOTE: since we have a failure status, we can ignore the return
+ // value from onStartRequest.
+ LOG((" calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
+ mListener.get()));
+ if (mListener) {
+ MOZ_ASSERT(!LoadOnStartRequestCalled(),
+ "We should not call OnStartRequest twice.");
+ if (!LoadOnStartRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ StoreOnStartRequestCalled(true);
+ listener->OnStartRequest(this);
+ }
+ } else {
+ StoreOnStartRequestCalled(true);
+ NS_WARNING("OnStartRequest skipped because of null listener");
+ }
+ mAuthRetryPending = false;
+ }
+
+ // if this transaction has been replaced, then bail.
+ if (LoadTransactionReplaced()) {
+ LOG(("Transaction replaced\n"));
+ // This was just the network check for a 304 response.
+ mFirstResponseSource = RESPONSE_PENDING;
+ return NS_OK;
+ }
+
+ bool upgradeWebsocket = mUpgradeProtocolCallback && aTransWithStickyConn &&
+ mResponseHead &&
+ ((mResponseHead->Status() == 101 &&
+ mResponseHead->Version() == HttpVersion::v1_1) ||
+ (mResponseHead->Status() == 200 &&
+ mResponseHead->Version() == HttpVersion::v2_0));
+
+ bool upgradeConnect = mUpgradeProtocolCallback && aTransWithStickyConn &&
+ (mCaps & NS_HTTP_CONNECT_ONLY) && mResponseHead &&
+ mResponseHead->Status() == 200;
+
+ if (upgradeWebsocket || upgradeConnect) {
+ if (nsIOService::UseSocketProcess() && upgradeConnect) {
+ // TODO: Support connection upgrade for socket process in bug 1632809.
+ Unused << mUpgradeProtocolCallback->OnUpgradeFailed(
+ NS_ERROR_NOT_IMPLEMENTED);
+ return ContinueOnStopRequest(aStatus, aIsFromNet, aContentComplete);
+ }
+
+ nsresult rv = gHttpHandler->CompleteUpgrade(aTransWithStickyConn,
+ mUpgradeProtocolCallback);
+ mUpgradeProtocolCallback = nullptr;
+ if (NS_FAILED(rv)) {
+ LOG((" CompleteUpgrade failed with %" PRIx32,
+ static_cast<uint32_t>(rv)));
+
+ // This ensures that WebSocketChannel::OnStopRequest will be
+ // called with an error so the session is properly aborted.
+ aStatus = rv;
+ }
+ }
+
+ return ContinueOnStopRequest(aStatus, aIsFromNet, aContentComplete);
+}
+
+nsresult nsHttpChannel::ContinueOnStopRequest(nsresult aStatus, bool aIsFromNet,
+ bool aContentComplete) {
+ LOG(
+ ("nsHttpChannel::ContinueOnStopRequest "
+ "[this=%p aStatus=%" PRIx32 ", aIsFromNet=%d]\n",
+ this, static_cast<uint32_t>(aStatus), aIsFromNet));
+
+ // HTTP_CHANNEL_DISPOSITION TELEMETRY
+ enum ChannelDisposition {
+ kHttpCanceled = 0,
+ kHttpDisk = 1,
+ kHttpNetOK = 2,
+ kHttpNetEarlyFail = 3,
+ kHttpNetLateFail = 4,
+ kHttpsCanceled = 8,
+ kHttpsDisk = 9,
+ kHttpsNetOK = 10,
+ kHttpsNetEarlyFail = 11,
+ kHttpsNetLateFail = 12
+ } chanDisposition = kHttpCanceled;
+ // HTTP_CHANNEL_DISPOSITION_UPGRADE TELEMETRY
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE upgradeChanDisposition =
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::cancel;
+
+ // HTTP 0.9 is more likely to be an error than really 0.9, so count it that
+ // way
+ if (mCanceled) {
+ chanDisposition = kHttpCanceled;
+ upgradeChanDisposition =
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::cancel;
+ } else if (!LoadUsedNetwork() ||
+ (mRaceCacheWithNetwork &&
+ mFirstResponseSource == RESPONSE_FROM_CACHE)) {
+ chanDisposition = kHttpDisk;
+ upgradeChanDisposition =
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::disk;
+ } else if (NS_SUCCEEDED(aStatus) && mResponseHead &&
+ mResponseHead->Version() != HttpVersion::v0_9) {
+ chanDisposition = kHttpNetOK;
+ upgradeChanDisposition =
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::netOk;
+ } else if (!mTransferSize) {
+ chanDisposition = kHttpNetEarlyFail;
+ upgradeChanDisposition =
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::netEarlyFail;
+ } else {
+ chanDisposition = kHttpNetLateFail;
+ upgradeChanDisposition =
+ Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::netLateFail;
+ }
+ // Browser upgrading only happens on HTTPS pages for mixed passive content
+ // when upgrading is enabled.
+ nsCString upgradeKey;
+ if (IsHTTPS()) {
+ // Browser upgrading is disabled and the content is already HTTPS
+ upgradeKey = "disabledNoReason"_ns;
+ // Checks "security.mixed_content.upgrade_display_content" is true
+ if (StaticPrefs::security_mixed_content_upgrade_display_content()) {
+ if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) {
+ // HTTP content the browser has upgraded to HTTPS
+ upgradeKey = "enabledUpgrade"_ns;
+ } else {
+ // Content wasn't upgraded but is already HTTPS
+ upgradeKey = "enabledNoReason"_ns;
+ }
+ }
+ // shift http to https disposition enums
+ chanDisposition =
+ static_cast<ChannelDisposition>(chanDisposition + kHttpsCanceled);
+ } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
+ // HTTP content the browser would upgrade to HTTPS if upgrading was
+ // enabled
+ upgradeKey = "disabledUpgrade"_ns;
+ } else {
+ // HTTP content that wouldn't upgrade
+ upgradeKey = StaticPrefs::security_mixed_content_upgrade_display_content()
+ ? "enabledWont"_ns
+ : "disabledWont"_ns;
+ }
+ Telemetry::AccumulateCategoricalKeyed(upgradeKey, upgradeChanDisposition);
+ LOG((" nsHttpChannel::OnStopRequest ChannelDisposition %d\n",
+ chanDisposition));
+ Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_DISPOSITION, chanDisposition);
+
+ // Collect specific telemetry for measuring image, video, audio
+ // success/failure rates in regular browsing mode and when auto upgrading of
+ // subresources is enabled. Note that we only evaluate actual image types, not
+ // favicons.
+ nsContentPolicyType internalLoadType;
+ mLoadInfo->GetInternalContentPolicyType(&internalLoadType);
+ bool statusIsSuccess = NS_SUCCEEDED(aStatus);
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE ||
+ internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD) {
+ if (mLoadInfo->GetBrowserDidUpgradeInsecureRequests()) {
+ Telemetry::AccumulateCategorical(
+ statusIsSuccess
+ ? Telemetry::LABELS_MIXED_CONTENT_IMAGES::ImgUpSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_IMAGES::ImgUpFailure);
+ } else {
+ Telemetry::AccumulateCategorical(
+ statusIsSuccess
+ ? Telemetry::LABELS_MIXED_CONTENT_IMAGES::ImgNoUpSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_IMAGES::ImgNoUpFailure);
+ }
+ }
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_VIDEO) {
+ if (mLoadInfo->GetBrowserDidUpgradeInsecureRequests()) {
+ Telemetry::AccumulateCategorical(
+ statusIsSuccess
+ ? Telemetry::LABELS_MIXED_CONTENT_VIDEO::VideoUpSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_VIDEO::VideoUpFailure);
+ } else {
+ Telemetry::AccumulateCategorical(
+ statusIsSuccess
+ ? Telemetry::LABELS_MIXED_CONTENT_VIDEO::VideoNoUpSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_VIDEO::VideoNoUpFailure);
+ }
+ }
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_AUDIO) {
+ if (mLoadInfo->GetBrowserDidUpgradeInsecureRequests()) {
+ Telemetry::AccumulateCategorical(
+ statusIsSuccess
+ ? Telemetry::LABELS_MIXED_CONTENT_AUDIO::AudioUpSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_AUDIO::AudioUpFailure);
+ } else {
+ Telemetry::AccumulateCategorical(
+ statusIsSuccess
+ ? Telemetry::LABELS_MIXED_CONTENT_AUDIO::AudioNoUpSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_AUDIO::AudioNoUpFailure);
+ }
+ }
+
+ // if needed, check cache entry has all data we expect
+ if (mCacheEntry && mCachePump && LoadConcurrentCacheAccess() &&
+ aContentComplete) {
+ int64_t size, contentLength;
+ nsresult rv = CheckPartial(mCacheEntry, &size, &contentLength);
+ if (NS_SUCCEEDED(rv)) {
+ if (size == int64_t(-1)) {
+ // mayhemer TODO - we have to restart read from cache here at the size
+ // offset
+ MOZ_ASSERT(false);
+ LOG(
+ (" cache entry write is still in progress, but we just "
+ "finished reading the cache entry"));
+ } else if (contentLength != int64_t(-1) && contentLength != size) {
+ LOG((" concurrent cache entry write has been interrupted"));
+ mCachedResponseHead = std::move(mResponseHead);
+ // Ignore zero partial length because we also want to resume when
+ // no data at all has been read from the cache.
+ rv = MaybeSetupByteRangeRequest(size, contentLength, true);
+ if (NS_SUCCEEDED(rv) && LoadIsPartialRequest()) {
+ // Prevent read from cache again
+ mCachedContentIsValid = false;
+ StoreCachedContentIsPartial(1);
+
+ // We are about to perform a different network request.
+ // We must set mRaceCacheWithNetwork to false because otherwise
+ // we would ignore the network response thinking we didn't need it.
+ mRaceCacheWithNetwork = false;
+
+ // Perform the range request
+ rv = ContinueConnect();
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" performing range request"));
+ mCachePump = nullptr;
+ return NS_OK;
+ }
+ LOG((" but range request perform failed 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ aStatus = NS_ERROR_NET_INTERRUPT;
+ } else {
+ LOG((" but range request setup failed rv=0x%08" PRIx32
+ ", failing load",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ }
+
+ StoreIsPending(false);
+ mStatus = aStatus;
+
+ // perform any final cache operations before we close the cache entry.
+ if (mCacheEntry && LoadRequestTimeInitialized()) {
+ bool writeAccess;
+ // New implementation just returns value of the !LoadCacheEntryIsReadOnly()
+ // flag passed in. Old implementation checks on nsICache::ACCESS_WRITE
+ // flag.
+ mCacheEntry->HasWriteAccess(!LoadCacheEntryIsReadOnly(), &writeAccess);
+ if (writeAccess) {
+ nsresult rv = FinalizeCacheEntry();
+ if (NS_FAILED(rv)) {
+ LOG(("FinalizeCacheEntry failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+
+ ReportRcwnStats(aIsFromNet);
+
+ // Register entry to the PerformanceStorage resource timing
+ MaybeReportTimingData();
+
+ MaybeFlushConsoleReports();
+
+ if (!mEndMarkerAdded && profiler_thread_is_being_profiled_for_markers()) {
+ // These do allocations/frees/etc; avoid if not active
+ mEndMarkerAdded = true;
+
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_STOP,
+ mLastStatusReported, TimeStamp::Now(), size, mCacheDisposition,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())));
+ }
+
+ if (mAuthRetryPending &&
+ StaticPrefs::network_auth_use_redirect_for_retries()) {
+ nsresult rv = OpenRedirectChannel(aStatus);
+ LOG(("Opening redirect channel for auth retry %x",
+ static_cast<uint32_t>(rv)));
+ if (NS_FAILED(rv)) {
+ if (mListener) {
+ MOZ_ASSERT(!LoadOnStartRequestCalled(),
+ "We should not call OnStartRequest twice.");
+ if (!LoadOnStartRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ StoreOnStartRequestCalled(true);
+ listener->OnStartRequest(this);
+ }
+ } else {
+ StoreOnStartRequestCalled(true);
+ NS_WARNING("OnStartRequest skipped because of null listener");
+ }
+ }
+ mAuthRetryPending = false;
+ }
+ if (mListener) {
+ LOG(("nsHttpChannel %p calling OnStopRequest\n", this));
+ MOZ_ASSERT(LoadOnStartRequestCalled(),
+ "OnStartRequest should be called before OnStopRequest");
+ MOZ_ASSERT(!LoadOnStopRequestCalled(),
+ "We should not call OnStopRequest twice");
+ StoreOnStopRequestCalled(true);
+ mListener->OnStopRequest(this, aStatus);
+ }
+ StoreOnStopRequestCalled(true);
+
+ // The prefetch needs to be released on the main thread
+ mDNSPrefetch = nullptr;
+
+ mRedirectChannel = nullptr;
+
+ // notify "http-on-stop-connect" observers
+ gHttpHandler->OnStopRequest(this);
+
+ RemoveAsNonTailRequest();
+
+ if (mChannelBlockedByOpaqueResponse && mCachedOpaqueResponseBlockingPref) {
+ mResponseHead->ClearHeaders();
+ }
+ // If a preferred alt-data type was set, this signals the consumer is
+ // interested in reading and/or writing the alt-data representation.
+ // We need to hold a reference to the cache entry in case the listener calls
+ // openAlternativeOutputStream() after CloseCacheEntry() clears mCacheEntry.
+ if (!mPreferredCachedAltDataTypes.IsEmpty()) {
+ mAltDataCacheEntry = mCacheEntry;
+ }
+
+ CloseCacheEntry(!aContentComplete);
+
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aStatus);
+ }
+
+ // We don't need this info anymore
+ CleanRedirectCacheChainIfNecessary();
+
+ ReleaseListeners();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+class OnTransportStatusAsyncEvent : public Runnable {
+ public:
+ OnTransportStatusAsyncEvent(nsITransportEventSink* aEventSink,
+ nsresult aTransportStatus, int64_t aProgress,
+ int64_t aProgressMax)
+ : Runnable("net::OnTransportStatusAsyncEvent"),
+ mEventSink(aEventSink),
+ mTransportStatus(aTransportStatus),
+ mProgress(aProgress),
+ mProgressMax(aProgressMax) {
+ MOZ_ASSERT(!NS_IsMainThread(), "Shouldn't be created on main thread");
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread(), "Should run on main thread");
+ if (mEventSink) {
+ mEventSink->OnTransportStatus(nullptr, mTransportStatus, mProgress,
+ mProgressMax);
+ }
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsresult mTransportStatus;
+ int64_t mProgress;
+ int64_t mProgressMax;
+};
+
+NS_IMETHODIMP
+nsHttpChannel::OnDataAvailable(nsIRequest* request, nsIInputStream* input,
+ uint64_t offset, uint32_t count) {
+ nsresult rv;
+ AUTO_PROFILER_LABEL("nsHttpChannel::OnDataAvailable", NETWORK);
+
+ LOG(("nsHttpChannel::OnDataAvailable [this=%p request=%p offset=%" PRIu64
+ " count=%" PRIu32 "]\n",
+ this, request, offset, count));
+
+ LOG((" requestFromCache: %d mFirstResponseSource: %d\n",
+ request == mCachePump, static_cast<int32_t>(mFirstResponseSource)));
+
+ // don't send out OnDataAvailable notifications if we've been canceled.
+ if (mCanceled) return mStatus;
+
+ if (mAuthRetryPending || WRONG_RACING_RESPONSE_SOURCE(request) ||
+ (request == mTransactionPump && LoadTransactionReplaced())) {
+ uint32_t n;
+ return input->ReadSegments(NS_DiscardSegment, nullptr, count, &n);
+ }
+
+ MOZ_ASSERT(mResponseHead, "No response head in ODA!!");
+
+ MOZ_ASSERT(!(LoadCachedContentIsPartial() && (request == mTransactionPump)),
+ "transaction pump not suspended");
+
+ mIsReadingFromCache = (request == mCachePump);
+
+ if (mListener) {
+ //
+ // synthesize transport progress event. we do this here since we want
+ // to delay OnProgress events until we start streaming data. this is
+ // crucially important since it impacts the lock icon (see bug 240053).
+ //
+ nsresult transportStatus;
+ if (request == mCachePump) {
+ transportStatus = NS_NET_STATUS_READING;
+ } else {
+ transportStatus = NS_NET_STATUS_RECEIVING_FROM;
+ }
+
+ // mResponseHead may reference new or cached headers, but either way it
+ // holds our best estimate of the total content length. Even in the case
+ // of a byte range request, the content length stored in the cached
+ // response headers is what we want to use here.
+
+ int64_t progressMax = -1;
+ rv = GetContentLength(&progressMax);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("GetContentLength failed");
+ }
+ int64_t progress = mLogicalOffset + count;
+
+ if ((progress > progressMax) && (progressMax != -1)) {
+ NS_WARNING(
+ "unexpected progress values - "
+ "is server exceeding content length?");
+ }
+
+ // make sure params are in range for js
+ if (!InScriptableRange(progressMax)) {
+ progressMax = -1;
+ }
+
+ if (!InScriptableRange(progress)) {
+ progress = -1;
+ }
+
+ if (NS_IsMainThread()) {
+ OnTransportStatus(nullptr, transportStatus, progress, progressMax);
+ } else {
+ rv = NS_DispatchToMainThread(new OnTransportStatusAsyncEvent(
+ this, transportStatus, progress, progressMax));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ //
+ // we have to manually keep the logical offset of the stream up-to-date.
+ // we cannot depend solely on the offset provided, since we may have
+ // already streamed some data from another source (see, for example,
+ // OnDoneReadingPartialCacheEntry).
+ //
+ int64_t offsetBefore = 0;
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(input);
+ if (seekable && NS_FAILED(seekable->Tell(&offsetBefore))) {
+ seekable = nullptr;
+ }
+
+ if (nsIOService::UseSocketProcess() && mTransaction) {
+ mOnDataAvailableStartTime = mTransaction->GetDataAvailableStartTime();
+ if (!mOnDataAvailableStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::OnDataAvailableSocketToParent,
+ TimeStamp::Now() - mOnDataAvailableStartTime);
+ }
+ } else {
+ mOnDataAvailableStartTime = TimeStamp::Now();
+ }
+ nsresult rv =
+ mListener->OnDataAvailable(this, input, mLogicalOffset, count);
+ if (NS_SUCCEEDED(rv)) {
+ // by contract mListener must read all of "count" bytes, but
+ // nsInputStreamPump is tolerant to seekable streams that violate that
+ // and it will redeliver incompletely read data. So we need to do
+ // the same thing when updating the progress counter to stay in sync.
+ int64_t offsetAfter, delta;
+ if (seekable && NS_SUCCEEDED(seekable->Tell(&offsetAfter))) {
+ delta = offsetAfter - offsetBefore;
+ if (delta != count) {
+ count = delta;
+
+ NS_WARNING("Listener OnDataAvailable contract violation");
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ nsAutoString message(nsLiteralString(
+ u"http channel Listener OnDataAvailable contract violation"));
+ if (consoleService) {
+ consoleService->LogStringMessage(message.get());
+ }
+ }
+ }
+ mLogicalOffset += count;
+ }
+
+ return rv;
+ }
+
+ return NS_ERROR_ABORT;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
+
+ NS_ENSURE_ARG(aNewTarget);
+ if (aNewTarget->IsOnCurrentThread()) {
+ NS_WARNING("Retargeting delivery to same thread");
+ return NS_OK;
+ }
+ if (!mTransactionPump && !mCachePump) {
+ LOG(("nsHttpChannel::RetargetDeliveryTo %p %p no pump available\n", this,
+ aNewTarget));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = NS_OK;
+ // If both cache pump and transaction pump exist, we're probably dealing
+ // with partially cached content. So, we must be able to retarget both.
+ nsCOMPtr<nsIThreadRetargetableRequest> retargetableCachePump;
+ nsCOMPtr<nsIThreadRetargetableRequest> retargetableTransactionPump;
+ if (mCachePump) {
+ retargetableCachePump = do_QueryObject(mCachePump);
+ // nsInputStreamPump should implement this interface.
+ MOZ_ASSERT(retargetableCachePump);
+ rv = retargetableCachePump->RetargetDeliveryTo(aNewTarget);
+ }
+ if (NS_SUCCEEDED(rv) && mTransactionPump) {
+ retargetableTransactionPump = do_QueryObject(mTransactionPump);
+ // nsInputStreamPump should implement this interface.
+ MOZ_ASSERT(retargetableTransactionPump);
+ rv = retargetableTransactionPump->RetargetDeliveryTo(aNewTarget);
+
+ // If retarget fails for transaction pump, we must restore mCachePump.
+ if (NS_FAILED(rv) && retargetableCachePump) {
+ nsCOMPtr<nsISerialEventTarget> main = GetMainThreadSerialEventTarget();
+ NS_ENSURE_TRUE(main, NS_ERROR_UNEXPECTED);
+ rv = retargetableCachePump->RetargetDeliveryTo(main);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) {
+ if (mCachePump) {
+ return mCachePump->GetDeliveryTarget(aEventTarget);
+ }
+ if (mTransactionPump) {
+ nsCOMPtr<nsIThreadRetargetableRequest> request =
+ do_QueryInterface(mTransactionPump);
+ return request->GetDeliveryTarget(aEventTarget);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsThreadRetargetableStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::CheckListenerChain() {
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnDataFinished(nsresult aStatus) {
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mListener);
+
+ if (listener) {
+ return listener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsITransportEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::OnTransportStatus(nsITransport* trans, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread only");
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink) GetCallback(mProgressSink);
+
+ if (status == NS_NET_STATUS_CONNECTED_TO ||
+ status == NS_NET_STATUS_WAITING_FOR) {
+ bool isTrr = false;
+ bool echConfigUsed = false;
+ if (mTransaction) {
+ mTransaction->GetNetworkAddresses(mSelfAddr, mPeerAddr, isTrr,
+ mEffectiveTRRMode, mTRRSkipReason,
+ echConfigUsed);
+ } else {
+ nsCOMPtr<nsISocketTransport> socketTransport = do_QueryInterface(trans);
+ if (socketTransport) {
+ socketTransport->GetSelfAddr(&mSelfAddr);
+ socketTransport->GetPeerAddr(&mPeerAddr);
+ socketTransport->ResolvedByTRR(&isTrr);
+ socketTransport->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ socketTransport->GetEchConfigUsed(&echConfigUsed);
+ }
+ }
+
+ StoreResolvedByTRR(isTrr);
+ StoreEchConfigUsed(echConfigUsed);
+ }
+
+ // block socket status event after Cancel or OnStopRequest has been called.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && LoadIsPending()) {
+ LOG(("sending progress%s notification [this=%p status=%" PRIx32
+ " progress=%" PRId64 "/%" PRId64 "]\n",
+ (mLoadFlags & LOAD_BACKGROUND) ? "" : " and status", this,
+ static_cast<uint32_t>(status), progress, progressMax));
+
+ nsAutoCString host;
+ mURI->GetHost(host);
+ if (!(mLoadFlags & LOAD_BACKGROUND)) {
+ mProgressSink->OnStatus(this, status, NS_ConvertUTF8toUTF16(host).get());
+ } else {
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ // If the event sink is |HttpChannelParent|, we have to send status
+ // events to it even if LOAD_BACKGROUND is set. |HttpChannelParent|
+ // needs to be aware of whether the status is
+ // |NS_NET_STATUS_RECEIVING_FROM| or |NS_NET_STATUS_READING|.
+ // LOAD_BACKGROUND is checked again in |HttpChannelChild|, so the final
+ // consumer won't get this event.
+ if (SameCOMIdentity(parentChannel, mProgressSink)) {
+ mProgressSink->OnStatus(this, status,
+ NS_ConvertUTF8toUTF16(host).get());
+ }
+ }
+
+ if (progress > 0) {
+ if ((progress > progressMax) && (progressMax != -1)) {
+ NS_WARNING("unexpected progress values");
+ }
+
+ // Try to get mProgressSink if it was nulled out during OnStatus.
+ if (!mProgressSink) {
+ GetCallback(mProgressSink);
+ }
+ if (mProgressSink) {
+ mProgressSink->OnProgress(this, progress, progressMax);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsICacheInfoChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::IsFromCache(bool* value) {
+ if (!LoadIsPending()) return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mRaceCacheWithNetwork) {
+ // return false if reading a partial cache entry; the data isn't
+ // entirely from the cache!
+ *value = (mCachePump || (mLoadFlags & LOAD_ONLY_IF_MODIFIED)) &&
+ mCachedContentIsValid && !LoadCachedContentIsPartial();
+ return NS_OK;
+ }
+
+ // If we are racing network and cache (or skipping the cache)
+ // we just return the first response source.
+ *value = mFirstResponseSource == RESPONSE_FROM_CACHE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheEntryId(uint64_t* aCacheEntryId) {
+ bool fromCache = false;
+ if (NS_FAILED(IsFromCache(&fromCache)) || !fromCache || !mCacheEntry ||
+ NS_FAILED(mCacheEntry->GetCacheEntryId(aCacheEntryId))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheTokenFetchCount(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
+ if (!cacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return cacheEntry->GetFetchCount(_retval);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheTokenExpirationTime(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return mCacheEntry->GetExpirationTime(_retval);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetAllowStaleCacheContent(bool aAllowStaleCacheContent) {
+ LOG(("nsHttpChannel::SetAllowStaleCacheContent [this=%p, allow=%d]", this,
+ aAllowStaleCacheContent));
+ StoreAllowStaleCacheContent(aAllowStaleCacheContent);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsHttpChannel::GetAllowStaleCacheContent(bool* aAllowStaleCacheContent) {
+ NS_ENSURE_ARG(aAllowStaleCacheContent);
+ *aAllowStaleCacheContent = LoadAllowStaleCacheContent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetForceValidateCacheContent(bool aForceValidateCacheContent) {
+ LOG(("nsHttpChannel::SetForceValidateCacheContent [this=%p, allow=%d]", this,
+ aForceValidateCacheContent));
+ StoreForceValidateCacheContent(aForceValidateCacheContent);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsHttpChannel::GetForceValidateCacheContent(bool* aForceValidateCacheContent) {
+ NS_ENSURE_ARG(aForceValidateCacheContent);
+ *aForceValidateCacheContent = LoadForceValidateCacheContent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetPreferCacheLoadOverBypass(bool aPreferCacheLoadOverBypass) {
+ StorePreferCacheLoadOverBypass(aPreferCacheLoadOverBypass);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsHttpChannel::GetPreferCacheLoadOverBypass(bool* aPreferCacheLoadOverBypass) {
+ NS_ENSURE_ARG(aPreferCacheLoadOverBypass);
+ *aPreferCacheLoadOverBypass = LoadPreferCacheLoadOverBypass();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::PreferAlternativeDataType(
+ const nsACString& aType, const nsACString& aContentType,
+ PreferredAlternativeDataDeliveryType aDeliverAltData) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+ mPreferredCachedAltDataTypes.AppendElement(PreferredAlternativeDataTypeParams(
+ nsCString(aType), nsCString(aContentType), aDeliverAltData));
+ return NS_OK;
+}
+
+const nsTArray<PreferredAlternativeDataTypeParams>&
+nsHttpChannel::PreferredAlternativeDataTypes() {
+ return mPreferredCachedAltDataTypes;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetAlternativeDataType(nsACString& aType) {
+ // must be called during or after OnStartRequest
+ if (!LoadAfterOnStartRequestBegun()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aType = mAvailableCachedAltDataType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OpenAlternativeOutputStream(const nsACString& type,
+ int64_t predictedSize,
+ nsIAsyncOutputStream** _retval) {
+ // OnStopRequest will clear mCacheEntry, but we may use mAltDataCacheEntry
+ // if the consumer called PreferAlternativeDataType()
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
+ if (!cacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsresult rv =
+ cacheEntry->OpenAlternativeOutputStream(type, predictedSize, _retval);
+ if (NS_SUCCEEDED(rv)) {
+ // Clear this metadata flag in case it exists.
+ // The caller of this method may set it again.
+ cacheEntry->SetMetaDataElement("alt-data-from-child", nullptr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetOriginalInputStream(nsIInputStreamReceiver* aReceiver) {
+ if (aReceiver == nullptr) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
+ if (cacheEntry) {
+ cacheEntry->OpenInputStream(0, getter_AddRefs(inputStream));
+ }
+ aReceiver->OnInputStreamReady(inputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetAlternativeDataInputStream(nsIInputStream** aInputStream) {
+ NS_ENSURE_ARG_POINTER(aInputStream);
+
+ *aInputStream = nullptr;
+
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
+ if (!mAvailableCachedAltDataType.IsEmpty() && cacheEntry) {
+ nsresult rv = cacheEntry->OpenAlternativeInputStream(
+ mAvailableCachedAltDataType, aInputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsICachingChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::IsRacing(bool* aIsRacing) {
+ if (!LoadAfterOnStartRequestBegun()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aIsRacing = mRaceCacheWithNetwork;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheToken(nsISupports** token) {
+ NS_ENSURE_ARG_POINTER(token);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ return CallQueryInterface(mCacheEntry, token);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetCacheToken(nsISupports* token) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheKey(uint32_t* key) {
+ NS_ENSURE_ARG_POINTER(key);
+
+ LOG(("nsHttpChannel::GetCacheKey [this=%p]\n", this));
+
+ *key = mPostID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetCacheKey(uint32_t key) {
+ LOG(("nsHttpChannel::SetCacheKey [this=%p key=%u]\n", this, key));
+
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mPostID = key;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheOnlyMetadata(bool* aOnlyMetadata) {
+ NS_ENSURE_ARG(aOnlyMetadata);
+ *aOnlyMetadata = LoadCacheOnlyMetadata();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetCacheOnlyMetadata(bool aOnlyMetadata) {
+ LOG(("nsHttpChannel::SetCacheOnlyMetadata [this=%p only-metadata=%d]\n", this,
+ aOnlyMetadata));
+
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ StoreCacheOnlyMetadata(aOnlyMetadata);
+ if (aOnlyMetadata) {
+ mLoadFlags |= LOAD_ONLY_IF_MODIFIED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetPin(bool* aPin) {
+ NS_ENSURE_ARG(aPin);
+ *aPin = LoadPinCacheContent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetPin(bool aPin) {
+ LOG(("nsHttpChannel::SetPin [this=%p pin=%d]\n", this, aPin));
+
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ StorePinCacheContent(aPin);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::ForceCacheEntryValidFor(uint32_t aSecondsToTheFuture) {
+ if (!mCacheEntry) {
+ LOG(
+ ("nsHttpChannel::ForceCacheEntryValidFor found no cache entry "
+ "for this channel [this=%p].",
+ this));
+ } else {
+ mCacheEntry->ForceValidFor(aSecondsToTheFuture);
+
+ nsAutoCString key;
+ mCacheEntry->GetKey(key);
+
+ LOG(
+ ("nsHttpChannel::ForceCacheEntryValidFor successfully forced valid "
+ "entry with key %s for %d seconds. [this=%p]",
+ key.get(), aSecondsToTheFuture, this));
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIResumableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::ResumeAt(uint64_t aStartPos, const nsACString& aEntityID) {
+ LOG(("nsHttpChannel::ResumeAt [this=%p startPos=%" PRIu64 " id='%s']\n", this,
+ aStartPos, PromiseFlatCString(aEntityID).get()));
+ mEntityID = aEntityID;
+ mStartPos = aStartPos;
+ StoreResuming(true);
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::DoAuthRetry(
+ HttpTransactionShell* aTransWithStickyConn,
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueOnStopRequestFunc) {
+ LOG(("nsHttpChannel::DoAuthRetry [this=%p, aTransWithStickyConn=%p]\n", this,
+ aTransWithStickyConn));
+
+ MOZ_ASSERT(!mTransaction, "should not have a transaction");
+
+ // Note that we don't have to toggle |IsPending| anymore. See the reasons
+ // below.
+ // 1. We can't suspend the channel during "http-on-modify-request"
+ // when |IsPending| is false.
+ // 2. We don't check |IsPending| in SetRequestHeader now.
+
+ // Reset RequestObserversCalled because we've probably called the request
+ // observers once already.
+ StoreRequestObserversCalled(false);
+
+ // fetch cookies, and add them to the request header.
+ // the server response could have included cookies that must be sent with
+ // this authentication attempt (bug 84794).
+ // TODO: save cookies from auth response and send them here (bug 572151).
+ AddCookiesToRequest();
+
+ // notify "http-on-modify-request" observers
+ CallOnModifyRequestObservers();
+
+ RefPtr<HttpTransactionShell> trans(aTransWithStickyConn);
+ return CallOrWaitForResume(
+ [trans{std::move(trans)}, aContinueOnStopRequestFunc](auto* self) {
+ return self->ContinueDoAuthRetry(trans, aContinueOnStopRequestFunc);
+ });
+}
+
+nsresult nsHttpChannel::ContinueDoAuthRetry(
+ HttpTransactionShell* aTransWithStickyConn,
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueOnStopRequestFunc) {
+ LOG(("nsHttpChannel::ContinueDoAuthRetry [this=%p]\n", this));
+ StoreIsPending(true);
+
+ // get rid of the old response headers
+ mResponseHead = nullptr;
+
+ // rewind the upload stream
+ if (mUploadStream) {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
+ nsresult rv = NS_ERROR_NO_INTERFACE;
+ if (seekable) {
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ }
+
+ // This should not normally happen, but it's possible that big memory
+ // blobs originating in the other process can't be rewinded.
+ // In that case we just fail the request, otherwise the content length
+ // will not match and this load will never complete.
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // always set sticky connection flag
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ // and when needed, allow restart regardless the sticky flag
+ if (LoadAuthConnectionRestartable()) {
+ LOG((" connection made restartable"));
+ mCaps |= NS_HTTP_CONNECTION_RESTARTABLE;
+ StoreAuthConnectionRestartable(false);
+ } else {
+ LOG((" connection made non-restartable"));
+ mCaps &= ~NS_HTTP_CONNECTION_RESTARTABLE;
+ }
+
+ // notify "http-on-before-connect" observers
+ gHttpHandler->OnBeforeConnect(this);
+
+ RefPtr<HttpTransactionShell> trans(aTransWithStickyConn);
+ return CallOrWaitForResume(
+ [trans{std::move(trans)}, aContinueOnStopRequestFunc](auto* self) {
+ nsresult rv = self->DoConnect(trans);
+ return aContinueOnStopRequestFunc(self, rv);
+ });
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIAsyncVerifyRedirectCallback
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpChannel::WaitForRedirectCallback() {
+ nsresult rv;
+ LOG(("nsHttpChannel::WaitForRedirectCallback [this=%p]\n", this));
+
+ if (mTransactionPump) {
+ rv = mTransactionPump->Suspend();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (mCachePump) {
+ rv = mCachePump->Suspend();
+ if (NS_FAILED(rv) && mTransactionPump) {
+#ifdef DEBUG
+ nsresult resume =
+#endif
+ mTransactionPump->Resume();
+ MOZ_ASSERT(NS_SUCCEEDED(resume), "Failed to resume transaction pump");
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ StoreWaitingForRedirectCallback(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnRedirectVerifyCallback(nsresult result) {
+ LOG(
+ ("nsHttpChannel::OnRedirectVerifyCallback [this=%p] "
+ "result=%" PRIx32 " stack=%zu WaitingForRedirectCallback=%u\n",
+ this, static_cast<uint32_t>(result), mRedirectFuncStack.Length(),
+ LoadWaitingForRedirectCallback()));
+ MOZ_ASSERT(LoadWaitingForRedirectCallback(),
+ "Someone forgot to call WaitForRedirectCallback() ?!");
+ StoreWaitingForRedirectCallback(false);
+
+ if (mCanceled && NS_SUCCEEDED(result)) result = NS_BINDING_ABORTED;
+
+ for (uint32_t i = mRedirectFuncStack.Length(); i > 0;) {
+ --i;
+ // Pop the last function pushed to the stack
+ nsContinueRedirectionFunc func = mRedirectFuncStack.PopLastElement();
+
+ // Call it with the result we got from the callback or the deeper
+ // function call.
+ result = (this->*func)(result);
+
+ // If a new function has been pushed to the stack and placed us in the
+ // waiting state, we need to break the chain and wait for the callback
+ // again.
+ if (LoadWaitingForRedirectCallback()) break;
+ }
+
+ if (NS_FAILED(result) && !mCanceled) {
+ // First, cancel this channel if we are in failure state to set mStatus
+ // and let it be propagated to pumps.
+ Cancel(result);
+ }
+
+ if (!LoadWaitingForRedirectCallback()) {
+ // We are not waiting for the callback. At this moment we must release
+ // reference to the redirect target channel, otherwise we may leak.
+ // However, if we are redirecting due to auth retries, we open the
+ // redirected channel after OnStopRequest. In that case we should not
+ // release the reference
+ if (!StaticPrefs::network_auth_use_redirect_for_retries() ||
+ !mAuthRetryPending) {
+ mRedirectChannel = nullptr;
+ }
+ }
+
+ // We always resume the pumps here. If all functions on stack have been
+ // called we need OnStopRequest to be triggered, and if we broke out of the
+ // loop above (and are thus waiting for a new callback) the suspension
+ // count must be balanced in the pumps.
+ if (mTransactionPump) mTransactionPump->Resume();
+ if (mCachePump) mCachePump->Resume();
+
+ return result;
+}
+
+void nsHttpChannel::PushRedirectAsyncFunc(nsContinueRedirectionFunc func) {
+ mRedirectFuncStack.AppendElement(func);
+}
+
+void nsHttpChannel::PopRedirectAsyncFunc(nsContinueRedirectionFunc func) {
+ MOZ_ASSERT(func == mRedirectFuncStack.LastElement(),
+ "Trying to pop wrong method from redirect async stack!");
+
+ mRedirectFuncStack.RemoveLastElement();
+}
+
+//-----------------------------------------------------------------------------
+// nsIDNSListener functions
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
+ nsresult status) {
+ MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
+
+ LOG(
+ ("nsHttpChannel::OnLookupComplete [this=%p] prefetch complete%s: "
+ "%s status[0x%" PRIx32 "]\n",
+ this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : "",
+ NS_SUCCEEDED(status) ? "success" : "failure",
+ static_cast<uint32_t>(status)));
+
+ // Unset DNS cache refresh if it was requested,
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ mCaps &= ~NS_HTTP_REFRESH_DNS;
+ if (mTransaction) {
+ mTransaction->SetDNSWasRefreshed();
+ }
+ }
+
+ if (!mDNSBlockingPromise.IsEmpty()) {
+ if (NS_SUCCEEDED(status)) {
+ nsCOMPtr<nsIDNSRecord> record(rec);
+ mDNSBlockingPromise.Resolve(record, __func__);
+ } else {
+ mDNSBlockingPromise.Reject(status, __func__);
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsHttpChannel::OnHTTPSRRAvailable(nsIDNSHTTPSSVCRecord* aRecord) {
+ MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
+
+ LOG(("nsHttpChannel::OnHTTPSRRAvailable [this=%p, aRecord=%p]\n", this,
+ aRecord));
+
+ if (mHTTPSSVCRecord) {
+ MOZ_ASSERT(false, "OnHTTPSRRAvailable called twice!");
+ return;
+ }
+
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> record = aRecord;
+ mHTTPSSVCRecord.emplace(std::move(record));
+ const nsCOMPtr<nsIDNSHTTPSSVCRecord>& httprr = mHTTPSSVCRecord.ref();
+
+ if (LoadWaitHTTPSSVCRecord()) {
+ MOZ_ASSERT(mURI->SchemeIs("http"));
+
+ StoreWaitHTTPSSVCRecord(false);
+ nsresult rv = ContinueOnBeforeConnect(!!httprr, mStatus, !!httprr);
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(rv);
+ }
+ } else {
+ // This channel is not canceled and the transaction is not created.
+ if (httprr && NS_SUCCEEDED(mStatus) && !mTransaction &&
+ (mFirstResponseSource != RESPONSE_FROM_CACHE)) {
+ bool hasIPAddress = false;
+ Unused << httprr->GetHasIPAddresses(&hasIPAddress);
+ Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_RECORD_RECEIVING_STAGE,
+ hasIPAddress
+ ? HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_0
+ : HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_0);
+ StoreHTTPSSVCTelemetryReported(true);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel internal functions
+//-----------------------------------------------------------------------------
+
+// Creates an URI to the given location using current URI for base and charset
+nsresult nsHttpChannel::CreateNewURI(const char* loc, nsIURI** newURI) {
+ nsCOMPtr<nsIIOService> ioService;
+ nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ if (NS_FAILED(rv)) return rv;
+
+ return ioService->NewURI(nsDependentCString(loc), nullptr, mURI, newURI);
+}
+
+void nsHttpChannel::MaybeInvalidateCacheEntryForSubsequentGet() {
+ // See RFC 2616 section 5.1.1. These are considered valid
+ // methods which DO NOT invalidate cache-entries for the
+ // referred resource. POST, PUT and DELETE as well as any
+ // other method not listed here will potentially invalidate
+ // any cached copy of the resource
+ if (mRequestHead.IsGet() || mRequestHead.IsOptions() ||
+ mRequestHead.IsHead() || mRequestHead.IsTrace() ||
+ mRequestHead.IsConnect()) {
+ return;
+ }
+
+ // Invalidate the request-uri.
+ if (LOG_ENABLED()) {
+ nsAutoCString key;
+ mURI->GetAsciiSpec(key);
+ LOG(("MaybeInvalidateCacheEntryForSubsequentGet [this=%p uri=%s]\n", this,
+ key.get()));
+ }
+
+ DoInvalidateCacheEntry(mURI);
+
+ // Invalidate Location-header if set
+ nsAutoCString location;
+ Unused << mResponseHead->GetHeader(nsHttp::Location, location);
+ if (!location.IsEmpty()) {
+ LOG((" Location-header=%s\n", location.get()));
+ InvalidateCacheEntryForLocation(location.get());
+ }
+
+ // Invalidate Content-Location-header if set
+ Unused << mResponseHead->GetHeader(nsHttp::Content_Location, location);
+ if (!location.IsEmpty()) {
+ LOG((" Content-Location-header=%s\n", location.get()));
+ InvalidateCacheEntryForLocation(location.get());
+ }
+}
+
+void nsHttpChannel::InvalidateCacheEntryForLocation(const char* location) {
+ nsAutoCString tmpCacheKey, tmpSpec;
+ nsCOMPtr<nsIURI> resultingURI;
+ nsresult rv = CreateNewURI(location, getter_AddRefs(resultingURI));
+ if (NS_SUCCEEDED(rv) && HostPartIsTheSame(resultingURI)) {
+ DoInvalidateCacheEntry(resultingURI);
+ } else {
+ LOG((" hosts not matching\n"));
+ }
+}
+
+void nsHttpChannel::DoInvalidateCacheEntry(nsIURI* aURI) {
+ // NOTE:
+ // Following comments 24,32 and 33 in bug #327765, we only care about
+ // the cache in the protocol-handler.
+ // The logic below deviates from the original logic in OpenCacheEntry on
+ // one point by using only READ_ONLY access-policy. I think this is safe.
+
+ nsresult rv;
+
+ nsAutoCString key;
+ if (LOG_ENABLED()) {
+ aURI->GetAsciiSpec(key);
+ }
+
+ LOG(("DoInvalidateCacheEntry [channel=%p key=%s]", this, key.get()));
+
+ nsCOMPtr<nsICacheStorageService> cacheStorageService(
+ components::CacheStorage::Service());
+ rv = cacheStorageService ? NS_OK : NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
+ rv = cacheStorageService->DiskCacheStorage(info,
+ getter_AddRefs(cacheStorage));
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = cacheStorage->AsyncDoomURI(aURI, ""_ns, nullptr);
+ }
+
+ LOG(("DoInvalidateCacheEntry [channel=%p key=%s rv=%d]", this, key.get(),
+ int(rv)));
+}
+
+void nsHttpChannel::AsyncOnExamineCachedResponse() {
+ gHttpHandler->OnExamineCachedResponse(this);
+}
+
+void nsHttpChannel::UpdateAggregateCallbacks() {
+ if (!mTransaction) {
+ return;
+ }
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ GetCurrentSerialEventTarget(),
+ getter_AddRefs(callbacks));
+ mTransaction->SetSecurityCallbacks(callbacks);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
+
+ nsresult rv = HttpBaseChannel::SetLoadGroup(aLoadGroup);
+ if (NS_SUCCEEDED(rv)) {
+ UpdateAggregateCallbacks();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
+
+ nsresult rv = HttpBaseChannel::SetNotificationCallbacks(aCallbacks);
+ if (NS_SUCCEEDED(rv)) {
+ UpdateAggregateCallbacks();
+ }
+ return rv;
+}
+
+bool nsHttpChannel::AwaitingCacheCallbacks() {
+ return LoadWaitForCacheEntry() != 0;
+}
+
+void nsHttpChannel::SetPushedStreamTransactionAndId(
+ HttpTransactionShell* aTransWithPushedStream, uint32_t aPushedStreamId) {
+ MOZ_ASSERT(!mTransWithPushedStream);
+ LOG(("nsHttpChannel::SetPushedStreamTransaction [this=%p] trans=%p", this,
+ aTransWithPushedStream));
+
+ mTransWithPushedStream = aTransWithPushedStream;
+ mPushedStreamId = aPushedStreamId;
+}
+
+nsresult nsHttpChannel::OnPush(uint32_t aPushedStreamId, const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTransaction);
+ LOG(("nsHttpChannel::OnPush [this=%p, trans=%p]\n", this, aTransaction));
+
+ MOZ_ASSERT(mCaps & NS_HTTP_ONPUSH_LISTENER);
+ nsCOMPtr<nsIHttpPushListener> pushListener;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIHttpPushListener),
+ getter_AddRefs(pushListener));
+
+ if (!pushListener) {
+ LOG(
+ ("nsHttpChannel::OnPush [this=%p] notification callbacks do not "
+ "implement nsIHttpPushListener\n",
+ this));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIURI> pushResource;
+ nsresult rv;
+
+ // Create a Channel for the Push Resource
+ rv = NS_NewURI(getter_AddRefs(pushResource), aUrl);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> pushChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(pushChannel), pushResource,
+ mLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> pushHttpChannel = do_QueryInterface(pushChannel);
+ MOZ_ASSERT(pushHttpChannel);
+ if (!pushHttpChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<nsHttpChannel> channel;
+ CallQueryInterface(pushHttpChannel, channel.StartAssignment());
+ MOZ_ASSERT(channel);
+ if (!channel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // new channel needs mrqeuesthead and headers from pushedStream
+ channel->mRequestHead.ParseHeaderSet(aRequestString.BeginReading());
+ channel->mLoadGroup = mLoadGroup;
+ channel->mLoadInfo = mLoadInfo;
+ channel->mCallbacks = mCallbacks;
+
+ // Link the trans with pushed stream and the new channel and call listener
+ channel->SetPushedStreamTransactionAndId(aTransaction, aPushedStreamId);
+ rv = pushListener->OnPush(this, pushHttpChannel);
+ return rv;
+}
+
+// static
+bool nsHttpChannel::IsRedirectStatus(uint32_t status) {
+ // 305 disabled as a security measure (see bug 187996).
+ return status == 300 || status == 301 || status == 302 || status == 303 ||
+ status == 307 || status == 308;
+}
+
+void nsHttpChannel::SetCouldBeSynthesized() {
+ MOZ_ASSERT(!BypassServiceWorker());
+ StoreResponseCouldBeSynthesized(true);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnPreflightSucceeded() {
+ MOZ_ASSERT(LoadRequireCORSPreflight(), "Why did a preflight happen?");
+ StoreIsCorsPreflightDone(1);
+ mPreflightChannel = nullptr;
+
+ return ContinueConnect();
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnPreflightFailed(nsresult aError) {
+ MOZ_ASSERT(LoadRequireCORSPreflight(), "Why did a preflight happen?");
+ StoreIsCorsPreflightDone(1);
+ mPreflightChannel = nullptr;
+
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(aError);
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::CallOrWaitForResume(
+ const std::function<nsresult(nsHttpChannel*)>& aFunc) {
+ if (mCanceled) {
+ MOZ_ASSERT(NS_FAILED(mStatus));
+ return mStatus;
+ }
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume [this=%p]\n", this));
+ MOZ_ASSERT(!mCallOnResume);
+ mCallOnResume = aFunc;
+ return NS_OK;
+ }
+
+ return aFunc(this);
+}
+
+// This is loosely based on:
+// https://fetch.spec.whatwg.org/#serializing-a-request-origin
+static bool HasNullRequestOrigin(nsHttpChannel* aChannel, nsIURI* aURI,
+ bool isAddonRequest) {
+ // Step 1. If request has a redirect-tainted origin, then return "null".
+ if (aChannel->HasRedirectTaintedOrigin()) {
+ if (StaticPrefs::network_http_origin_redirectTainted()) {
+ return true;
+ }
+ }
+
+ // Non-standard: Only allow HTTP and HTTPS origins.
+ if (!ReferrerInfo::IsReferrerSchemeAllowed(aURI)) {
+ // And moz-extension: for add-on initiated requests.
+ if (!aURI->SchemeIs("moz-extension") || !isAddonRequest) {
+ return true;
+ }
+ }
+
+ // Non-standard: Hide onion URLs.
+ if (StaticPrefs::network_http_referer_hideOnionSource()) {
+ nsAutoCString host;
+ if (NS_SUCCEEDED(aURI->GetAsciiHost(host)) &&
+ StringEndsWith(host, ".onion"_ns)) {
+ return ReferrerInfo::IsCrossOriginRequest(aChannel);
+ }
+ }
+
+ // Step 2. Return request’s origin, serialized.
+ return false;
+}
+
+// Step 8.12. of HTTP-network-or-cache fetch
+//
+// https://fetch.spec.whatwg.org/#append-a-request-origin-header
+void nsHttpChannel::SetOriginHeader() {
+ auto* triggeringPrincipal =
+ BasePrincipal::Cast(mLoadInfo->TriggeringPrincipal());
+
+ if (triggeringPrincipal->IsSystemPrincipal()) {
+ // We can't infer an Origin header from the system principal,
+ // this means system requests use whatever Origin header was specified.
+ return;
+ }
+ bool isAddonRequest = triggeringPrincipal->AddonPolicy() ||
+ triggeringPrincipal->ContentScriptAddonPolicy();
+
+ // Non-standard: Handle already existing Origin header.
+ nsAutoCString existingHeader;
+ Unused << mRequestHead.GetHeader(nsHttp::Origin, existingHeader);
+ if (!existingHeader.IsEmpty()) {
+ LOG(("nsHttpChannel::SetOriginHeader Origin header already present"));
+ auto const shouldNullifyOriginHeader =
+ [&existingHeader, isAddonRequest](nsHttpChannel* aChannel) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), existingHeader);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (HasNullRequestOrigin(aChannel, uri, isAddonRequest)) {
+ return true;
+ }
+
+ nsCOMPtr<nsILoadInfo> info = aChannel->LoadInfo();
+ if (info->GetTainting() == mozilla::LoadTainting::CORS) {
+ return false;
+ }
+
+ return ReferrerInfo::ShouldSetNullOriginHeader(aChannel, uri);
+ };
+
+ if (!existingHeader.EqualsLiteral("null") &&
+ shouldNullifyOriginHeader(this)) {
+ LOG(("nsHttpChannel::SetOriginHeader null Origin by Referrer-Policy"));
+ MOZ_ALWAYS_SUCCEEDS(
+ mRequestHead.SetHeader(nsHttp::Origin, "null"_ns, false /* merge */));
+ }
+ return;
+ }
+
+ if (StaticPrefs::network_http_sendOriginHeader() == 0) {
+ // Custom user setting: 0 means never send Origin header.
+ return;
+ }
+
+ // Step 1. Let serializedOrigin be the result of byte-serializing a request
+ // origin with request.
+ nsAutoCString serializedOrigin;
+ nsCOMPtr<nsIURI> uri;
+ {
+ if (NS_FAILED(triggeringPrincipal->GetURI(getter_AddRefs(uri)))) {
+ return;
+ }
+
+ if (!uri) {
+ if (isAddonRequest) {
+ // For add-on compatibility prefer sending no header at all
+ // instead of `Origin: null`.
+ return;
+ }
+
+ // Otherwise use "null" when the triggeringPrincipal's URI is nullptr.
+ serializedOrigin.AssignLiteral("null");
+ } else if (HasNullRequestOrigin(this, uri, isAddonRequest)) {
+ serializedOrigin.AssignLiteral("null");
+ } else {
+ nsContentUtils::GetWebExposedOriginSerialization(uri, serializedOrigin);
+ }
+ }
+
+ // Step 2. If request’s response tainting is "cors" or request’s mode is
+ // "websocket", then append (`Origin`, serializedOrigin) to request’s header
+ // list.
+ //
+ // Note: We don't handle "websocket" here (yet?).
+ if (mLoadInfo->GetTainting() == mozilla::LoadTainting::CORS) {
+ MOZ_ALWAYS_SUCCEEDS(mRequestHead.SetHeader(nsHttp::Origin, serializedOrigin,
+ false /* merge */));
+ return;
+ }
+
+ // Step 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then:
+ if (mRequestHead.IsGet() || mRequestHead.IsHead()) {
+ return;
+ }
+
+ if (!serializedOrigin.EqualsLiteral("null")) {
+ // Step 3.1. (Implemented by ReferrerInfo::ShouldSetNullOriginHeader)
+ if (ReferrerInfo::ShouldSetNullOriginHeader(this, uri)) {
+ serializedOrigin.AssignLiteral("null");
+ } else if (StaticPrefs::network_http_sendOriginHeader() == 1) {
+ // Non-standard: Restrict Origin to same-origin loads if requested by user
+ nsAutoCString currentOrigin;
+ nsContentUtils::GetWebExposedOriginSerialization(mURI, currentOrigin);
+ if (!serializedOrigin.EqualsIgnoreCase(currentOrigin.get())) {
+ // Origin header suppressed by user setting.
+ serializedOrigin.AssignLiteral("null");
+ }
+ }
+ }
+
+ // Step 3.2. Append (`Origin`, serializedOrigin) to request’s header list.
+ MOZ_ALWAYS_SUCCEEDS(mRequestHead.SetHeader(nsHttp::Origin, serializedOrigin,
+ false /* merge */));
+}
+
+void nsHttpChannel::SetDoNotTrack() {
+ /**
+ * 'DoNotTrack' header should be added if 'privacy.donottrackheader.enabled'
+ * is true or tracking protection is enabled. See bug 1258033.
+ */
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+
+ if ((loadContext && loadContext->UseTrackingProtection()) ||
+ StaticPrefs::privacy_donottrackheader_enabled()) {
+ DebugOnly<nsresult> rv =
+ mRequestHead.SetHeader(nsHttp::DoNotTrack, "1"_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void nsHttpChannel::SetGlobalPrivacyControl() {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+
+ if (StaticPrefs::privacy_globalprivacycontrol_functionality_enabled() &&
+ (StaticPrefs::privacy_globalprivacycontrol_enabled() ||
+ (StaticPrefs::privacy_globalprivacycontrol_pbmode_enabled() &&
+ NS_UsePrivateBrowsing(this)))) {
+ // Send the header with a value of 1 to indicate opting-out
+ DebugOnly<nsresult> rv =
+ mRequestHead.SetHeader(nsHttp::GlobalPrivacyControl, "1"_ns, false);
+ }
+}
+
+void nsHttpChannel::ReportRcwnStats(bool isFromNet) {
+ if (!StaticPrefs::network_http_rcwn_enabled()) {
+ return;
+ }
+
+ if (isFromNet) {
+ if (mRaceCacheWithNetwork) {
+ gIOService->IncrementNetWonRequestNumber();
+ Telemetry::Accumulate(
+ Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_RACE_NETWORK_WIN,
+ mTransferSize);
+ if (mRaceDelay) {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
+ NetworkDelayedRace);
+ } else {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
+ NetworkRace);
+ }
+ } else {
+ Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_NOT_RACE,
+ mTransferSize);
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
+ NetworkNoRace);
+ }
+ } else {
+ if (mRaceCacheWithNetwork || mRaceDelay) {
+ gIOService->IncrementCacheWonRequestNumber();
+ Telemetry::Accumulate(
+ Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_RACE_CACHE_WIN,
+ mTransferSize);
+ if (mRaceDelay) {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
+ CacheDelayedRace);
+ } else {
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
+ CacheRace);
+ }
+ } else {
+ Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_NOT_RACE,
+ mTransferSize);
+ AccumulateCategorical(
+ Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
+ CacheNoRace);
+ }
+ }
+
+ gIOService->IncrementRequestNumber();
+}
+
+static const size_t kPositiveBucketNumbers = 34;
+static const int64_t kPositiveBucketLevels[kPositiveBucketNumbers] = {
+ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200,
+ 300, 400, 500, 600, 700, 800, 900, 1000, 2000, 3000, 4000, 5000,
+ 6000, 7000, 8000, 9000, 10000, 20000, 30000, 40000, 50000, 60000};
+
+/**
+ * For space efficiency, we collect finer resolution for small difference
+ * between net and cache time, coarser for larger.
+ * Bucket #40 for a tie.
+ * #41 to #50 indicates cache wins by 1ms to 100ms, split equally.
+ * #51 to #59 indicates cache wins by 101ms to 1000ms.
+ * #60 to #68 indicates cache wins by 1s to 10s.
+ * #69 to #73 indicates cache wins by 11s to 60s.
+ * #74 indicates cache wins by more than 1 minute.
+ *
+ * #39 to #30 indicates network wins by 1ms to 100ms, split equally.
+ * #29 to #21 indicates network wins by 101ms to 1000ms.
+ * #20 to #12 indicates network wins by 1s to 10s.
+ * #11 to #7 indicates network wins by 11s to 60s.
+ * #6 indicates network wins by more than 1 minute.
+ *
+ * Other bucket numbers are reserved.
+ */
+inline int64_t nsHttpChannel::ComputeTelemetryBucketNumber(
+ int64_t difftime_ms) {
+ int64_t absBucketIndex =
+ std::lower_bound(kPositiveBucketLevels,
+ kPositiveBucketLevels + kPositiveBucketNumbers,
+ static_cast<int64_t>(mozilla::Abs(difftime_ms))) -
+ kPositiveBucketLevels;
+
+ return difftime_ms >= 0 ? 40 + absBucketIndex : 40 - absBucketIndex;
+}
+
+void nsHttpChannel::ReportNetVSCacheTelemetry() {
+ nsresult rv;
+ if (!mCacheEntry) {
+ return;
+ }
+
+ // We only report telemetry if the entry is persistent (on disk)
+ bool persistent;
+ rv = mCacheEntry->GetPersistent(&persistent);
+ if (NS_FAILED(rv) || !persistent) {
+ return;
+ }
+
+ uint64_t onStartNetTime = 0;
+ if (NS_FAILED(mCacheEntry->GetOnStartTime(&onStartNetTime))) {
+ return;
+ }
+
+ uint64_t onStopNetTime = 0;
+ if (NS_FAILED(mCacheEntry->GetOnStopTime(&onStopNetTime))) {
+ return;
+ }
+
+ uint64_t onStartCacheTime =
+ (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds();
+ int64_t onStartDiff = onStartNetTime - onStartCacheTime;
+ onStartDiff = ComputeTelemetryBucketNumber(onStartDiff);
+
+ uint64_t onStopCacheTime = (mCacheReadEnd - mAsyncOpenTime).ToMilliseconds();
+ int64_t onStopDiff = onStopNetTime - onStopCacheTime;
+ onStopDiff = ComputeTelemetryBucketNumber(onStopDiff);
+
+ if (mDidReval) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_REVALIDATED_V2,
+ onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_REVALIDATED_V2,
+ onStopDiff);
+ } else {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_NOTREVALIDATED_V2, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_NOTREVALIDATED_V2,
+ onStopDiff);
+ }
+
+ if (mDidReval) {
+ // We don't report revalidated probes as the data would be skewed.
+ return;
+ }
+
+ if (mCacheOpenWithPriority) {
+ if (mCacheQueueSizeWhenOpen < 5) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_QSMALL_HIGHPRI_V2, onStartDiff);
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QSMALL_HIGHPRI_V2, onStopDiff);
+ } else if (mCacheQueueSizeWhenOpen < 10) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_QMED_HIGHPRI_V2, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QMED_HIGHPRI_V2,
+ onStopDiff);
+ } else {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_QBIG_HIGHPRI_V2, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QBIG_HIGHPRI_V2,
+ onStopDiff);
+ }
+ } else { // The limits are higher for normal priority cache queues
+ if (mCacheQueueSizeWhenOpen < 10) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_QSMALL_NORMALPRI_V2,
+ onStartDiff);
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QSMALL_NORMALPRI_V2, onStopDiff);
+ } else if (mCacheQueueSizeWhenOpen < 50) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_QMED_NORMALPRI_V2, onStartDiff);
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QMED_NORMALPRI_V2, onStopDiff);
+ } else {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTART_QBIG_NORMALPRI_V2, onStartDiff);
+ Telemetry::Accumulate(
+ Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QBIG_NORMALPRI_V2, onStopDiff);
+ }
+ }
+
+ uint32_t diskStorageSizeK = 0;
+ rv = mCacheEntry->GetDiskStorageSizeInKB(&diskStorageSizeK);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // No significant difference was observed between different sizes for
+ // |onStartDiff|
+ if (diskStorageSizeK < 256) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_SMALL_V2,
+ onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_LARGE_V2,
+ onStopDiff);
+ }
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Test_delayCacheEntryOpeningBy(int32_t aTimeout) {
+ LOG(("nsHttpChannel::Test_delayCacheEntryOpeningBy this=%p timeout=%d", this,
+ aTimeout));
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+ mRaceCacheWithNetwork = true;
+ mCacheOpenDelay = aTimeout;
+ if (mCacheOpenTimer) {
+ mCacheOpenTimer->SetDelay(aTimeout);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Test_triggerDelayedOpenCacheEntry() {
+ LOG(("nsHttpChannel::Test_triggerDelayedOpenCacheEntry this=%p", this));
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+ nsresult rv;
+ if (!mCacheOpenDelay) {
+ // No delay was set.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (!mCacheOpenFunc) {
+ // There should be a runnable.
+ return NS_ERROR_FAILURE;
+ }
+ if (mCacheOpenTimer) {
+ rv = mCacheOpenTimer->Cancel();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mCacheOpenTimer = nullptr;
+ }
+ mCacheOpenDelay = 0;
+ // Avoid re-entrancy issues by nulling our mCacheOpenFunc before calling it.
+ std::function<void(nsHttpChannel*)> cacheOpenFunc = nullptr;
+ std::swap(cacheOpenFunc, mCacheOpenFunc);
+ cacheOpenFunc(this);
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::TriggerNetworkWithDelay(uint32_t aDelay) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+
+ LOG(("nsHttpChannel::TriggerNetworkWithDelay [this=%p, delay=%u]\n", this,
+ aDelay));
+
+ if (mCanceled) {
+ LOG((" channel was canceled.\n"));
+ return mStatus;
+ }
+
+ // If a network request has already gone out, there is no point in
+ // doing this again.
+ if (mNetworkTriggered) {
+ LOG((" network already triggered. Returning.\n"));
+ return NS_OK;
+ }
+
+ if (mNetworkTriggerDelay) {
+ aDelay = mNetworkTriggerDelay;
+ }
+
+ if (!aDelay) {
+ // We cannot call TriggerNetwork() directly here, because it would
+ // cause performance regression in tp6 tests, see bug 1398847.
+ return NS_DispatchToMainThread(
+ NewRunnableMethod("net::nsHttpChannel::TriggerNetworkWithDelay", this,
+ &nsHttpChannel::TriggerNetwork),
+ NS_DISPATCH_NORMAL);
+ }
+
+ MOZ_ASSERT(!mNetworkTriggerTimer);
+ mNetworkTriggerTimer = NS_NewTimer();
+ auto callback = MakeRefPtr<TimerCallback>(this);
+ LOG(("Creating new networkTriggertimer for delay"));
+ mNetworkTriggerTimer->InitWithCallback(callback, aDelay,
+ nsITimer::TYPE_ONE_SHOT);
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::TriggerNetwork() {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+
+ LOG(("nsHttpChannel::TriggerNetwork [this=%p]\n", this));
+
+ if (mCanceled) {
+ LOG((" channel was canceled.\n"));
+ return mStatus;
+ }
+
+ // If a network request has already gone out, there is no point in
+ // doing this again.
+ if (mNetworkTriggered) {
+ LOG((" network already triggered. Returning.\n"));
+ return NS_OK;
+ }
+
+ mNetworkTriggered = true;
+ if (mNetworkTriggerTimer) {
+ mNetworkTriggerTimer->Cancel();
+ mNetworkTriggerTimer = nullptr;
+ }
+
+ // If we are waiting for a proxy request, that means we can't trigger
+ // the next step just yet. We need for mConnectionInfo to be non-null
+ // before we call ContinueConnect. OnProxyAvailable will trigger
+ // BeginConnect, and Connect will call ContinueConnect even if it's
+ // for the cache callbacks.
+ if (mProxyRequest) {
+ LOG((" proxy request in progress. Delaying network trigger.\n"));
+ mWaitingForProxy = true;
+ return NS_OK;
+ }
+
+ // If |mCacheOpenFunc| is assigned, we're delaying opening the entry to
+ // simulate racing. Although cache entry opening hasn't started yet, we're
+ // actually racing, so we must set mRaceCacheWithNetwork to true now.
+ mRaceCacheWithNetwork =
+ AwaitingCacheCallbacks() &&
+ (mCacheOpenFunc || StaticPrefs::network_http_rcwn_enabled());
+
+ LOG((" triggering network rcwn=%d\n", bool(mRaceCacheWithNetwork)));
+ return ContinueConnect();
+}
+
+void nsHttpChannel::MaybeRaceCacheWithNetwork() {
+ nsresult rv;
+
+ nsCOMPtr<nsINetworkLinkService> netLinkSvc =
+ do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ uint32_t linkType;
+ rv = netLinkSvc->GetLinkType(&linkType);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (!(linkType == nsINetworkLinkService::LINK_TYPE_ETHERNET ||
+#ifndef MOZ_WIDGET_ANDROID
+ // On Android we don't assume an unknown link type is unmetered
+ linkType == nsINetworkLinkService::LINK_TYPE_UNKNOWN ||
+#endif
+ linkType == nsINetworkLinkService::LINK_TYPE_USB ||
+ linkType == nsINetworkLinkService::LINK_TYPE_WIFI)) {
+ return;
+ }
+
+ // Don't trigger the network if the load flags say so.
+ if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_NO_NETWORK_IO)) {
+ return;
+ }
+
+ // We must not race if the channel has a failure status code.
+ if (NS_FAILED(mStatus)) {
+ return;
+ }
+
+ // If a CORS Preflight is required we must not race.
+ if (LoadRequireCORSPreflight() && !LoadIsCorsPreflightDone()) {
+ return;
+ }
+
+ if (CacheFileUtils::CachePerfStats::IsCacheSlow()) {
+ // If the cache is slow, trigger the network request immediately.
+ mRaceDelay = 0;
+ } else {
+ // Give cache a headstart of 3 times the average cache entry open time.
+ mRaceDelay = CacheFileUtils::CachePerfStats::GetAverage(
+ CacheFileUtils::CachePerfStats::ENTRY_OPEN, true) *
+ 3;
+ // We use microseconds in CachePerfStats but we need milliseconds
+ // for TriggerNetwork.
+ mRaceDelay /= 1000;
+ }
+
+ mRaceDelay = clamped<uint32_t>(
+ mRaceDelay, StaticPrefs::network_http_rcwn_min_wait_before_racing_ms(),
+ StaticPrefs::network_http_rcwn_max_wait_before_racing_ms());
+
+ MOZ_ASSERT(StaticPrefs::network_http_rcwn_enabled() || mNetworkTriggerDelay,
+ "The pref must be turned on.");
+ LOG(("nsHttpChannel::MaybeRaceCacheWithNetwork [this=%p, delay=%u]\n", this,
+ mRaceDelay));
+
+ TriggerNetworkWithDelay(mRaceDelay);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Test_triggerNetwork(int32_t aTimeout) {
+ LOG(("nsHttpChannel::Test_triggerNetwork this=%p timeout=%d", this,
+ aTimeout));
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+
+ // We set the trigger delay to the specified timeout.
+ mRaceCacheWithNetwork = true;
+ mNetworkTriggerDelay = aTimeout;
+
+ // If we already have a timer, set the delay/
+ if (mNetworkTriggerTimer) {
+ // If the timeout is 0 and there is a timer, we can trigger
+ // the network immediately.
+ MOZ_ASSERT(LoadWasOpened(), "Must have been opened before");
+ if (!aTimeout) {
+ return TriggerNetwork();
+ }
+ mNetworkTriggerTimer->SetDelay(aTimeout);
+ }
+ return NS_OK;
+}
+
+nsHttpChannel::TimerCallback::TimerCallback(nsHttpChannel* aChannel)
+ : mChannel(aChannel) {}
+
+NS_IMPL_ISUPPORTS(nsHttpChannel::TimerCallback, nsITimerCallback, nsINamed)
+
+NS_IMETHODIMP
+nsHttpChannel::TimerCallback::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsHttpChannel");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::TimerCallback::Notify(nsITimer* aTimer) {
+ if (aTimer == mChannel->mCacheOpenTimer) {
+ return mChannel->Test_triggerDelayedOpenCacheEntry();
+ }
+ if (aTimer == mChannel->mNetworkTriggerTimer) {
+ return mChannel->TriggerNetwork();
+ }
+ MOZ_CRASH("Unknown timer");
+
+ return NS_OK;
+}
+
+bool nsHttpChannel::EligibleForTailing() {
+ if (!(mClassOfService.Flags() & nsIClassOfService::Tail)) {
+ return false;
+ }
+
+ if (mClassOfService.Flags() &
+ (nsIClassOfService::UrgentStart | nsIClassOfService::Leader |
+ nsIClassOfService::TailForbidden)) {
+ return false;
+ }
+
+ if (mClassOfService.Flags() & nsIClassOfService::Unblocked &&
+ !(mClassOfService.Flags() & nsIClassOfService::TailAllowed)) {
+ return false;
+ }
+
+ if (IsNavigation()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool nsHttpChannel::WaitingForTailUnblock() {
+ nsresult rv;
+
+ if (!gHttpHandler->IsTailBlockingEnabled()) {
+ LOG(("nsHttpChannel %p tail-blocking disabled", this));
+ return false;
+ }
+
+ if (!EligibleForTailing()) {
+ LOG(("nsHttpChannel %p not eligible for tail-blocking", this));
+ AddAsNonTailRequest();
+ return false;
+ }
+
+ if (!EnsureRequestContext()) {
+ LOG(("nsHttpChannel %p no request context", this));
+ return false;
+ }
+
+ LOG(("nsHttpChannel::WaitingForTailUnblock this=%p, rc=%p", this,
+ mRequestContext.get()));
+
+ bool blocked;
+ rv = mRequestContext->IsContextTailBlocked(this, &blocked);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ LOG((" blocked=%d", blocked));
+
+ return blocked;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIRequestTailUnblockCallback
+//-----------------------------------------------------------------------------
+
+// Must be implemented in the leaf class because we don't have
+// AsyncAbort in HttpBaseChannel.
+NS_IMETHODIMP
+nsHttpChannel::OnTailUnblock(nsresult rv) {
+ LOG(("nsHttpChannel::OnTailUnblock this=%p rv=%" PRIx32 " rc=%p", this,
+ static_cast<uint32_t>(rv), mRequestContext.get()));
+
+ MOZ_RELEASE_ASSERT(mOnTailUnblock);
+
+ if (NS_FAILED(mStatus)) {
+ rv = mStatus;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ auto callback = mOnTailUnblock;
+ mOnTailUnblock = nullptr;
+ rv = (this->*callback)();
+ }
+
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ return AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+void nsHttpChannel::SetWarningReporter(
+ HttpChannelSecurityWarningReporter* aReporter) {
+ LOG(("nsHttpChannel [this=%p] SetWarningReporter [%p]", this, aReporter));
+ mWarningReporter = aReporter;
+}
+
+HttpChannelSecurityWarningReporter* nsHttpChannel::GetWarningReporter() {
+ LOG(("nsHttpChannel [this=%p] GetWarningReporter [%p]", this,
+ mWarningReporter.get()));
+ return mWarningReporter.get();
+}
+
+// The specification for ORB is currently being written:
+// https://whatpr.org/fetch/1442.html#orb-algorithm
+// The `opaque-response-safelist check` is implemented in:
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff`
+// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff`
+// * `OpaqueResponseBlocker::ValidateJavaScript`
+//
+// Should only be called by nsMediaSniffer::GetMIMETypeFromContent and
+// imageLoader::GetMIMETypeFromContent when the content type can be
+// recognized by these sniffers.
+void nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck(
+ SnifferType aType) {
+ // https://whatpr.org/fetch/1442.html#orb-algorithm
+ // This method covers steps, 8 and 10.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (NeedOpaqueResponseAllowedCheckAfterSniff()) {
+ MOZ_ASSERT(mCachedOpaqueResponseBlockingPref);
+
+ // If the sniffer type is media and the request comes from a media element,
+ // we would like to check:
+ // - Whether the information provided by the media element shows it's an
+ // initial request.
+ // - Whether the response's status is either 200 or 206.
+ //
+ // If any of the results is false, then we set
+ // mBlockOpaqueResponseAfterSniff to true and block the response later.
+ if (aType == SnifferType::Media) {
+ // Step 8
+ MOZ_ASSERT(mLoadInfo);
+
+ bool isMediaRequest;
+ mLoadInfo->GetIsMediaRequest(&isMediaRequest);
+ if (isMediaRequest) {
+ bool isInitialRequest;
+ mLoadInfo->GetIsMediaInitialRequest(&isInitialRequest);
+ MOZ_ASSERT(isInitialRequest);
+
+ if (!isInitialRequest) {
+ // Step 8.1
+ BlockOpaqueResponseAfterSniff(
+ u"media request after sniffing, but not initial request"_ns,
+ OpaqueResponseBlockedTelemetryReason::MEDIA_NOT_INITIAL);
+ return;
+ }
+
+ if (mResponseHead->Status() != 200 && mResponseHead->Status() != 206) {
+ // Step 8.2
+ BlockOpaqueResponseAfterSniff(
+ u"media request's response status is neither 200 nor 206"_ns,
+ OpaqueResponseBlockedTelemetryReason::MEDIA_INCORRECT_RESP);
+ return;
+ }
+ }
+ }
+
+ // Step 8.3 if `aType == SnifferType::Media`
+ // Step 9 can be skipped, only `HTMLMediaElement` ever sets isMediaRequest.
+ // Step 10 if `aType == SnifferType::Image`
+ AllowOpaqueResponseAfterSniff();
+ }
+}
+
+namespace {
+
+class CopyNonDefaultHeaderVisitor final : public nsIHttpHeaderVisitor {
+ nsCOMPtr<nsIHttpChannel> mTarget;
+
+ ~CopyNonDefaultHeaderVisitor() = default;
+
+ NS_IMETHOD
+ VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
+ if (aValue.IsEmpty()) {
+ return mTarget->SetEmptyRequestHeader(aHeader);
+ }
+ return mTarget->SetRequestHeader(aHeader, aValue, false /* merge */);
+ }
+
+ public:
+ explicit CopyNonDefaultHeaderVisitor(nsIHttpChannel* aTarget)
+ : mTarget(aTarget) {
+ MOZ_DIAGNOSTIC_ASSERT(mTarget);
+ }
+
+ NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(CopyNonDefaultHeaderVisitor, nsIHttpHeaderVisitor)
+
+} // anonymous namespace
+
+nsresult nsHttpChannel::RedirectToInterceptedChannel() {
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+
+ RefPtr<InterceptedHttpChannel> intercepted =
+ InterceptedHttpChannel::CreateForInterception(
+ mChannelCreationTime, mChannelCreationTimestamp, mAsyncOpenTime);
+
+ ExtContentPolicyType type = mLoadInfo->GetExternalContentPolicyType();
+
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+
+ nsresult rv = intercepted->Init(
+ mURI, mCaps, static_cast<nsProxyInfo*>(mProxyInfo.get()),
+ mProxyResolveFlags, mProxyURI, mChannelId, type, redirectLoadInfo);
+
+ rv = SetupReplacementChannel(mURI, intercepted, true,
+ nsIChannelEventSink::REDIRECT_INTERNAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Some APIs, like fetch(), allow content to set non-standard headers.
+ // Normally these APIs are responsible for copying these headers across
+ // redirects. In the e10s parent-side intercept case, though, we currently
+ // "hide" the internal redirect to the InterceptedHttpChannel. So the
+ // fetch() API does not have the opportunity to move headers over.
+ // Therefore, we do it automatically here.
+ //
+ // Once child-side interception is removed and the internal redirect no
+ // longer needs to be "hidden", then this header copying code can be
+ // removed.
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new CopyNonDefaultHeaderVisitor(intercepted);
+ rv = VisitNonDefaultRequestHeaders(visitor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRedirectChannel = intercepted;
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(
+ this, intercepted, nsIChannelEventSink::REDIRECT_INTERNAL);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = WaitForRedirectCallback();
+ }
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this, rv);
+
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
+ }
+
+ return rv;
+}
+
+void nsHttpChannel::ReEvaluateReferrerAfterTrackingStatusIsKnown() {
+ nsCOMPtr<nsICookieJarSettings> cjs;
+ if (mLoadInfo) {
+ Unused << mLoadInfo->GetCookieJarSettings(getter_AddRefs(cjs));
+ }
+ if (!cjs) {
+ cjs = net::CookieJarSettings::Create(mLoadInfo->GetLoadingPrincipal());
+ }
+ if (cjs->GetRejectThirdPartyContexts()) {
+ bool isPrivate = mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
+ // If our referrer has been set before, and our referrer policy is unset
+ // (default policy) if we thought the channel wasn't a third-party
+ // tracking channel, we may need to set our referrer with referrer policy
+ // once again to ensure our defaults properly take effect now.
+ if (mReferrerInfo) {
+ ReferrerInfo* referrerInfo =
+ static_cast<ReferrerInfo*>(mReferrerInfo.get());
+
+ if (referrerInfo->IsPolicyOverrided() &&
+ referrerInfo->ReferrerPolicy() ==
+ ReferrerInfo::GetDefaultReferrerPolicy(nullptr, nullptr,
+ isPrivate)) {
+ nsCOMPtr<nsIReferrerInfo> newReferrerInfo =
+ referrerInfo->CloneWithNewPolicy(
+ ReferrerInfo::GetDefaultReferrerPolicy(this, mURI, isPrivate));
+ // The arguments passed to SetReferrerInfoInternal here should mirror
+ // the arguments passed in
+ // HttpChannelChild::RecvOverrideReferrerInfoDuringBeginConnect().
+ SetReferrerInfoInternal(newReferrerInfo, false, true, true);
+
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(this, parentChannel);
+ RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel);
+ if (httpParent) {
+ httpParent->OverrideReferrerInfoDuringBeginConnect(newReferrerInfo);
+ }
+ }
+ }
+ }
+}
+
+namespace {
+
+class BackgroundRevalidatingListener : public nsIStreamListener {
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ private:
+ virtual ~BackgroundRevalidatingListener() = default;
+};
+
+NS_IMETHODIMP
+BackgroundRevalidatingListener::OnStartRequest(nsIRequest* request) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundRevalidatingListener::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* input,
+ uint64_t offset,
+ uint32_t count) {
+ uint32_t bytesRead = 0;
+ return input->ReadSegments(NS_DiscardSegment, nullptr, count, &bytesRead);
+}
+
+NS_IMETHODIMP
+BackgroundRevalidatingListener::OnStopRequest(nsIRequest* request,
+ nsresult status) {
+ if (NS_FAILED(status)) {
+ return status;
+ }
+
+ nsCOMPtr<nsIHttpChannel> channel(do_QueryInterface(request));
+ if (gHttpHandler) {
+ gHttpHandler->OnBackgroundRevalidation(channel);
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(BackgroundRevalidatingListener, nsIStreamListener,
+ nsIRequestObserver)
+
+} // namespace
+
+void nsHttpChannel::PerformBackgroundCacheRevalidation() {
+ if (!StaticPrefs::network_http_stale_while_revalidate_enabled()) {
+ return;
+ }
+
+ // This is a channel doing a revalidation. It shouldn't do it again.
+ if (mStaleRevalidation) {
+ return;
+ }
+
+ LOG(("nsHttpChannel::PerformBackgroundCacheRevalidation %p", this));
+
+ Unused << NS_DispatchToMainThreadQueue(
+ NewIdleRunnableMethod(
+ "nsHttpChannel::PerformBackgroundCacheRevalidation", this,
+ &nsHttpChannel::PerformBackgroundCacheRevalidationNow),
+ EventQueuePriority::Idle);
+}
+
+void nsHttpChannel::PerformBackgroundCacheRevalidationNow() {
+ LOG(("nsHttpChannel::PerformBackgroundCacheRevalidationNow %p", this));
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ nsLoadFlags loadFlags = mLoadFlags | LOAD_ONLY_IF_MODIFIED | VALIDATE_ALWAYS |
+ LOAD_BACKGROUND | LOAD_BYPASS_SERVICE_WORKER;
+
+ nsCOMPtr<nsIChannel> validatingChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(validatingChannel), mURI, mLoadInfo,
+ nullptr /* performance storage */, mLoadGroup,
+ mCallbacks, loadFlags);
+ if (NS_FAILED(rv)) {
+ LOG((" failed to created the channel, rv=0x%08x",
+ static_cast<uint32_t>(rv)));
+ return;
+ }
+
+ nsCOMPtr<nsISupportsPriority> priority(do_QueryInterface(validatingChannel));
+ if (priority) {
+ priority->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
+ }
+
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(validatingChannel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::Tail);
+ }
+
+ RefPtr<nsHttpChannel> httpChan = do_QueryObject(validatingChannel);
+ if (httpChan) {
+ httpChan->mStaleRevalidation = true;
+ }
+
+ RefPtr<BackgroundRevalidatingListener> listener =
+ new BackgroundRevalidatingListener();
+ rv = validatingChannel->AsyncOpen(listener);
+ if (NS_FAILED(rv)) {
+ LOG((" failed to open the channel, rv=0x%08x", static_cast<uint32_t>(rv)));
+ return;
+ }
+
+ LOG((" %p is re-validating with a new channel %p", this,
+ validatingChannel.get()));
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) {
+ mEarlyHintObserver = aObserver;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::EarlyHint(const nsACString& aLinkHeader,
+ const nsACString& aReferrerPolicy,
+ const nsACString& aCspHeader) {
+ LOG(("nsHttpChannel::EarlyHint.\n"));
+
+ if (mEarlyHintObserver && nsContentUtils::ComputeIsSecureContext(this)) {
+ LOG(("nsHttpChannel::EarlyHint propagated.\n"));
+ mEarlyHintObserver->EarlyHint(aLinkHeader, aReferrerPolicy, aCspHeader);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* aListener) {
+ mWebTransportSessionEventListener = aListener;
+ return NS_OK;
+}
+
+already_AddRefed<WebTransportSessionEventListener>
+nsHttpChannel::GetWebTransportSessionEventListener() {
+ RefPtr<WebTransportSessionEventListener> wt =
+ mWebTransportSessionEventListener;
+ return wt.forget();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h
new file mode 100644
index 0000000000..dede167f87
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -0,0 +1,869 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et cin ts=4 sw=2 sts=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpChannel_h__
+#define nsHttpChannel_h__
+
+#include "AlternateServices.h"
+#include "AutoClose.h"
+#include "HttpBaseChannel.h"
+#include "TimingStruct.h"
+#include "mozilla/AtomicBitfields.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/extensions/PStreamFilterParent.h"
+#include "mozilla/net/DocumentLoadListener.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsICacheEntry.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICachingChannel.h"
+#include "nsICorsPreflightCallback.h"
+#include "nsIDNSListener.h"
+#include "nsIEarlyHintObserver.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIRaceCacheWithNetwork.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+
+class nsDNSPrefetch;
+class nsICancelable;
+class nsIDNSRecord;
+class nsIDNSHTTPSSVCRecord;
+class nsIHttpChannelAuthProvider;
+class nsInputStreamPump;
+class nsITransportSecurityInfo;
+
+namespace mozilla {
+namespace net {
+
+class nsChannelClassifier;
+class HttpChannelSecurityWarningReporter;
+
+using DNSPromise = MozPromise<nsCOMPtr<nsIDNSRecord>, nsresult, false>;
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel
+//-----------------------------------------------------------------------------
+
+// Use to support QI nsIChannel to nsHttpChannel
+#define NS_HTTPCHANNEL_IID \
+ { \
+ 0x301bf95b, 0x7bb3, 0x4ae1, { \
+ 0xa9, 0x71, 0x40, 0xbc, 0xfa, 0x81, 0xde, 0x12 \
+ } \
+ }
+
+class nsHttpChannel final : public HttpBaseChannel,
+ public HttpAsyncAborter<nsHttpChannel>,
+ public nsICachingChannel,
+ public nsICacheEntryOpenCallback,
+ public nsITransportEventSink,
+ public nsIProtocolProxyCallback,
+ public nsIHttpAuthenticableChannel,
+ public nsIAsyncVerifyRedirectCallback,
+ public nsIThreadRetargetableRequest,
+ public nsIThreadRetargetableStreamListener,
+ public nsIDNSListener,
+ public nsSupportsWeakReference,
+ public nsICorsPreflightCallback,
+ public nsIRaceCacheWithNetwork,
+ public nsIRequestTailUnblockCallback,
+ public nsIEarlyHintObserver {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+ NS_DECL_NSICACHEINFOCHANNEL
+ NS_DECL_NSICACHINGCHANNEL
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSIDNSLISTENER
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTPCHANNEL_IID)
+ NS_DECL_NSIRACECACHEWITHNETWORK
+ NS_DECL_NSIREQUESTTAILUNBLOCKCALLBACK
+ NS_DECL_NSIEARLYHINTOBSERVER
+
+ // nsIHttpAuthenticableChannel. We can't use
+ // NS_DECL_NSIHTTPAUTHENTICABLECHANNEL because it duplicates cancel() and
+ // others.
+ NS_IMETHOD GetIsSSL(bool* aIsSSL) override;
+ NS_IMETHOD GetProxyMethodIsConnect(bool* aProxyMethodIsConnect) override;
+ NS_IMETHOD GetServerResponseHeader(
+ nsACString& aServerResponseHeader) override;
+ NS_IMETHOD GetProxyChallenges(nsACString& aChallenges) override;
+ NS_IMETHOD GetWWWChallenges(nsACString& aChallenges) override;
+ NS_IMETHOD SetProxyCredentials(const nsACString& aCredentials) override;
+ NS_IMETHOD SetWWWCredentials(const nsACString& aCredentials) override;
+ NS_IMETHOD OnAuthAvailable() override;
+ NS_IMETHOD OnAuthCancelled(bool userCancel) override;
+ NS_IMETHOD CloseStickyConnection() override;
+ NS_IMETHOD ConnectionRestartable(bool) override;
+ // Functions we implement from nsIHttpAuthenticableChannel but are
+ // declared in HttpBaseChannel must be implemented in this class. We
+ // just call the HttpBaseChannel:: impls.
+ NS_IMETHOD GetLoadFlags(nsLoadFlags* aLoadFlags) override;
+ NS_IMETHOD GetURI(nsIURI** aURI) override;
+ NS_IMETHOD GetNotificationCallbacks(
+ nsIInterfaceRequestor** aCallbacks) override;
+ NS_IMETHOD GetLoadGroup(nsILoadGroup** aLoadGroup) override;
+ NS_IMETHOD GetRequestMethod(nsACString& aMethod) override;
+
+ nsHttpChannel();
+
+ [[nodiscard]] virtual nsresult Init(nsIURI* aURI, uint32_t aCaps,
+ nsProxyInfo* aProxyInfo,
+ uint32_t aProxyResolveFlags,
+ nsIURI* aProxyURI, uint64_t aChannelId,
+ ExtContentPolicyType aContentPolicyType,
+ nsILoadInfo* aLoadInfo) override;
+
+ [[nodiscard]] nsresult OnPush(uint32_t aPushedStreamId,
+ const nsACString& aUrl,
+ const nsACString& aRequestString,
+ HttpTransactionShell* aTransaction);
+
+ static bool IsRedirectStatus(uint32_t status);
+ static bool WillRedirect(const nsHttpResponseHead& response);
+
+ // Methods HttpBaseChannel didn't implement for us or that we override.
+ //
+ // nsIRequest
+ NS_IMETHOD SetCanceledReason(const nsACString& aReason) override;
+ NS_IMETHOD GetCanceledReason(nsACString& aReason) override;
+ NS_IMETHOD CancelWithReason(nsresult status,
+ const nsACString& reason) override;
+ NS_IMETHOD Cancel(nsresult status) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+ // nsIChannel
+ NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+ // nsIHttpChannel
+ NS_IMETHOD GetEncodedBodySize(uint64_t* aEncodedBodySize) override;
+ // nsIHttpChannelInternal
+ NS_IMETHOD GetIsAuthChannel(bool* aIsAuthChannel) override;
+ NS_IMETHOD SetChannelIsForDownload(bool aChannelIsForDownload) override;
+ NS_IMETHOD GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) override;
+ NS_IMETHOD SetNavigationStartTimeStamp(TimeStamp aTimeStamp) override;
+ NS_IMETHOD CancelByURLClassifier(nsresult aErrorCode) override;
+ // nsISupportsPriority
+ NS_IMETHOD SetPriority(int32_t value) override;
+ // nsIClassOfService
+ NS_IMETHOD SetClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD AddClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD ClearClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD SetClassOfService(ClassOfService cos) override;
+ NS_IMETHOD SetIncremental(bool incremental) override;
+
+ // nsIResumableChannel
+ NS_IMETHOD ResumeAt(uint64_t startPos, const nsACString& entityID) override;
+
+ NS_IMETHOD SetNotificationCallbacks(
+ nsIInterfaceRequestor* aCallbacks) override;
+ NS_IMETHOD SetLoadGroup(nsILoadGroup* aLoadGroup) override;
+ // nsITimedChannel
+ NS_IMETHOD GetDomainLookupStart(
+ mozilla::TimeStamp* aDomainLookupStart) override;
+ NS_IMETHOD GetDomainLookupEnd(mozilla::TimeStamp* aDomainLookupEnd) override;
+ NS_IMETHOD GetConnectStart(mozilla::TimeStamp* aConnectStart) override;
+ NS_IMETHOD GetTcpConnectEnd(mozilla::TimeStamp* aTcpConnectEnd) override;
+ NS_IMETHOD GetSecureConnectionStart(
+ mozilla::TimeStamp* aSecureConnectionStart) override;
+ NS_IMETHOD GetConnectEnd(mozilla::TimeStamp* aConnectEnd) override;
+ NS_IMETHOD GetRequestStart(mozilla::TimeStamp* aRequestStart) override;
+ NS_IMETHOD GetResponseStart(mozilla::TimeStamp* aResponseStart) override;
+ NS_IMETHOD GetResponseEnd(mozilla::TimeStamp* aResponseEnd) override;
+
+ NS_IMETHOD GetTransactionPending(
+ mozilla::TimeStamp* aTransactionPending) override;
+
+ // nsICorsPreflightCallback
+ NS_IMETHOD OnPreflightSucceeded() override;
+ NS_IMETHOD OnPreflightFailed(nsresult aError) override;
+
+ [[nodiscard]] nsresult AddSecurityMessage(
+ const nsAString& aMessageTag, const nsAString& aMessageCategory) override;
+ NS_IMETHOD LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) override;
+ NS_IMETHOD LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override;
+
+ NS_IMETHOD SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) override;
+ NS_IMETHOD SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* aListener) override;
+
+ void SetWarningReporter(HttpChannelSecurityWarningReporter* aReporter);
+ HttpChannelSecurityWarningReporter* GetWarningReporter();
+
+ bool DataSentToChildProcess() { return LoadDataSentToChildProcess(); }
+
+ enum class SnifferType { Media, Image };
+ void DisableIsOpaqueResponseAllowedAfterSniffCheck(SnifferType aType);
+
+ public: /* internal necko use only */
+ uint32_t GetRequestTime() const { return mRequestTime; }
+
+ void AsyncOpenFinal(TimeStamp aTimeStamp);
+
+ [[nodiscard]] nsresult OpenCacheEntry(bool isHttps);
+ [[nodiscard]] nsresult OpenCacheEntryInternal(bool isHttps);
+ [[nodiscard]] nsresult ContinueConnect();
+
+ [[nodiscard]] nsresult StartRedirectChannelToURI(nsIURI*, uint32_t);
+
+ SnifferCategoryType GetSnifferCategoryType() const {
+ return mSnifferCategoryType;
+ }
+
+ // Helper to keep cache callbacks wait flags consistent
+ class AutoCacheWaitFlags {
+ public:
+ explicit AutoCacheWaitFlags(nsHttpChannel* channel)
+ : mChannel(channel), mKeep(0) {
+ // Flags must be set before entering any AsyncOpenCacheEntry call.
+ mChannel->StoreWaitForCacheEntry(nsHttpChannel::WAIT_FOR_CACHE_ENTRY);
+ }
+
+ void Keep(uint32_t flags) {
+ // Called after successful call to appropriate AsyncOpenCacheEntry call.
+ mKeep |= flags;
+ }
+
+ ~AutoCacheWaitFlags() {
+ // Keep only flags those are left to be wait for.
+ mChannel->StoreWaitForCacheEntry(mChannel->LoadWaitForCacheEntry() &
+ mKeep);
+ }
+
+ private:
+ nsHttpChannel* mChannel;
+ uint32_t mKeep : 1;
+ };
+
+ bool AwaitingCacheCallbacks();
+ void SetCouldBeSynthesized();
+
+ // Return true if the latest ODA is invoked by mCachePump.
+ // Should only be called on the same thread as ODA.
+ bool IsReadingFromCache() const { return mIsReadingFromCache; }
+
+ base::ProcessId ProcessId();
+
+ using ChildEndpointPromise =
+ MozPromise<mozilla::ipc::Endpoint<extensions::PStreamFilterChild>, bool,
+ true>;
+ [[nodiscard]] RefPtr<ChildEndpointPromise> AttachStreamFilter();
+
+ already_AddRefed<WebTransportSessionEventListener>
+ GetWebTransportSessionEventListener();
+
+ private: // used for alternate service validation
+ RefPtr<TransactionObserver> mTransactionObserver;
+
+ public:
+ void SetTransactionObserver(TransactionObserver* arg) {
+ mTransactionObserver = arg;
+ }
+ TransactionObserver* GetTransactionObserver() { return mTransactionObserver; }
+
+ CacheDisposition mCacheDisposition{kCacheUnresolved};
+
+ protected:
+ virtual ~nsHttpChannel();
+
+ private:
+ using nsContinueRedirectionFunc = nsresult (nsHttpChannel::*)(nsresult);
+
+ // Directly call |aFunc| if the channel is not canceled and not suspended.
+ // Otherwise, set |aFunc| to |mCallOnResume| and wait until the channel
+ // resumes.
+ nsresult CallOrWaitForResume(
+ const std::function<nsresult(nsHttpChannel*)>& aFunc);
+
+ bool RequestIsConditional();
+ void HandleContinueCancellingByURLClassifier(nsresult aErrorCode);
+ nsresult CancelInternal(nsresult status);
+ void ContinueCancellingByURLClassifier(nsresult aErrorCode);
+
+ // Connections will only be established in this function.
+ // (including DNS prefetch and speculative connection.)
+ void MaybeResolveProxyAndBeginConnect();
+ nsresult MaybeStartDNSPrefetch();
+
+ // Tells the channel to resolve the origin of the end server we are connecting
+ // to.
+ static uint16_t const DNS_PREFETCH_ORIGIN = 1 << 0;
+ // Tells the channel to resolve the host name of the proxy.
+ static uint16_t const DNS_PREFETCH_PROXY = 1 << 1;
+ // Will be set if the current channel uses an HTTP/HTTPS proxy.
+ static uint16_t const DNS_PROXY_IS_HTTP = 1 << 2;
+ // Tells the channel to wait for the result of the origin server resolution
+ // before any connection attempts are made.
+ static uint16_t const DNS_BLOCK_ON_ORIGIN_RESOLVE = 1 << 3;
+
+ // Based on the proxy configuration determine the strategy for resolving the
+ // end server host name.
+ // Returns a combination of the above flags.
+ uint16_t GetProxyDNSStrategy();
+
+ // We might synchronously or asynchronously call BeginConnect,
+ // which includes DNS prefetch and speculative connection, according to
+ // whether an async tracker lookup is required. If the tracker lookup
+ // is required, this funciton will just return NS_OK and BeginConnect()
+ // will be called when callback. See Bug 1325054 for more information.
+ nsresult BeginConnect();
+ [[nodiscard]] nsresult PrepareToConnect();
+ [[nodiscard]] nsresult OnBeforeConnect();
+ [[nodiscard]] nsresult ContinueOnBeforeConnect(
+ bool aShouldUpgrade, nsresult aStatus, bool aUpgradeWithHTTPSRR = false);
+ nsresult MaybeUseHTTPSRRForUpgrade(bool aShouldUpgrade, nsresult aStatus);
+ void OnHTTPSRRAvailable(nsIDNSHTTPSSVCRecord* aRecord);
+ [[nodiscard]] nsresult Connect();
+ void SpeculativeConnect();
+ [[nodiscard]] nsresult SetupTransaction();
+ [[nodiscard]] nsresult CallOnStartRequest();
+ [[nodiscard]] nsresult ProcessResponse();
+ void AsyncContinueProcessResponse();
+ [[nodiscard]] nsresult ContinueProcessResponse1();
+ [[nodiscard]] nsresult ContinueProcessResponse2(nsresult);
+
+ public:
+ void UpdateCacheDisposition(bool aSuccessfulReval, bool aPartialContentUsed);
+ [[nodiscard]] nsresult ContinueProcessResponse3(nsresult);
+ [[nodiscard]] nsresult ContinueProcessResponse4(nsresult);
+ [[nodiscard]] nsresult ProcessNormal();
+ [[nodiscard]] nsresult ContinueProcessNormal(nsresult);
+ void ProcessAltService();
+ bool ShouldBypassProcessNotModified();
+ [[nodiscard]] nsresult ProcessNotModified(
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueProcessResponseFunc);
+ [[nodiscard]] nsresult ContinueProcessResponseAfterNotModified(nsresult aRv);
+
+ [[nodiscard]] nsresult AsyncProcessRedirection(uint32_t redirectType);
+ [[nodiscard]] nsresult ContinueProcessRedirection(nsresult);
+ [[nodiscard]] nsresult ContinueProcessRedirectionAfterFallback(nsresult);
+ [[nodiscard]] nsresult ProcessFailedProxyConnect(uint32_t httpStatus);
+ void HandleAsyncAbort();
+ [[nodiscard]] nsresult EnsureAssocReq();
+ void ProcessSSLInformation();
+ bool IsHTTPS();
+
+ [[nodiscard]] nsresult ContinueOnStartRequest1(nsresult);
+ [[nodiscard]] nsresult ContinueOnStartRequest2(nsresult);
+ [[nodiscard]] nsresult ContinueOnStartRequest3(nsresult);
+ [[nodiscard]] nsresult ContinueOnStartRequest4(nsresult);
+
+ void OnClassOfServiceUpdated();
+
+ // redirection specific methods
+ void HandleAsyncRedirect();
+ void HandleAsyncAPIRedirect();
+ [[nodiscard]] nsresult ContinueHandleAsyncRedirect(nsresult);
+ void HandleAsyncNotModified();
+ [[nodiscard]] nsresult PromptTempRedirect();
+ [[nodiscard]] virtual nsresult SetupReplacementChannel(
+ nsIURI*, nsIChannel*, bool preserveMethod,
+ uint32_t redirectFlags) override;
+ void HandleAsyncRedirectToUnstrippedURI();
+
+ // proxy specific methods
+ [[nodiscard]] nsresult ProxyFailover();
+ [[nodiscard]] nsresult AsyncDoReplaceWithProxy(nsIProxyInfo*);
+ [[nodiscard]] nsresult ResolveProxy();
+
+ // cache specific methods
+ [[nodiscard]] nsresult OnNormalCacheEntryAvailable(nsICacheEntry* aEntry,
+ bool aNew,
+ nsresult aEntryStatus);
+ [[nodiscard]] nsresult OnCacheEntryAvailableInternal(nsICacheEntry* entry,
+ bool aNew,
+ nsresult status);
+ [[nodiscard]] nsresult GenerateCacheKey(uint32_t postID, nsACString& key);
+ [[nodiscard]] nsresult UpdateExpirationTime();
+ [[nodiscard]] nsresult CheckPartial(nsICacheEntry* aEntry, int64_t* aSize,
+ int64_t* aContentLength);
+ [[nodiscard]] nsresult ReadFromCache(bool alreadyMarkedValid);
+ void CloseCacheEntry(bool doomOnFailure);
+ [[nodiscard]] nsresult InitCacheEntry();
+ void UpdateInhibitPersistentCachingFlag();
+ [[nodiscard]] nsresult AddCacheEntryHeaders(nsICacheEntry* entry);
+ [[nodiscard]] nsresult FinalizeCacheEntry();
+ [[nodiscard]] nsresult InstallCacheListener(int64_t offset = 0);
+ void MaybeInvalidateCacheEntryForSubsequentGet();
+ void AsyncOnExamineCachedResponse();
+
+ // byte range request specific methods
+ [[nodiscard]] nsresult ProcessPartialContent(
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueProcessResponseFunc);
+ [[nodiscard]] nsresult ContinueProcessResponseAfterPartialContent(
+ nsresult aRv);
+ [[nodiscard]] nsresult OnDoneReadingPartialCacheEntry(bool* streamDone);
+
+ [[nodiscard]] nsresult DoAuthRetry(
+ HttpTransactionShell* aTransWithStickyConn,
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueOnStopRequestFunc);
+ [[nodiscard]] nsresult ContinueDoAuthRetry(
+ HttpTransactionShell* aTransWithStickyConn,
+ const std::function<nsresult(nsHttpChannel*, nsresult)>&
+ aContinueOnStopRequestFunc);
+ [[nodiscard]] MOZ_NEVER_INLINE nsresult
+ DoConnect(HttpTransactionShell* aTransWithStickyConn = nullptr);
+ [[nodiscard]] nsresult DoConnectActual(
+ HttpTransactionShell* aTransWithStickyConn);
+ [[nodiscard]] nsresult ContinueOnStopRequestAfterAuthRetry(
+ nsresult aStatus, bool aAuthRetry, bool aIsFromNet, bool aContentComplete,
+ HttpTransactionShell* aTransWithStickyConn);
+ [[nodiscard]] nsresult ContinueOnStopRequest(nsresult status, bool aIsFromNet,
+ bool aContentComplete);
+
+ void HandleAsyncRedirectChannelToHttps();
+ [[nodiscard]] nsresult StartRedirectChannelToHttps();
+ [[nodiscard]] nsresult ContinueAsyncRedirectChannelToURI(nsresult rv);
+ [[nodiscard]] nsresult OpenRedirectChannel(nsresult rv);
+
+ HttpTrafficCategory CreateTrafficCategory();
+
+ /**
+ * A function that takes care of reading STS and PKP headers and enforcing
+ * STS and PKP load rules. After a secure channel is erected, STS and PKP
+ * requires the channel to be trusted or any STS or PKP header data on
+ * the channel is ignored. This is called from ProcessResponse.
+ */
+ [[nodiscard]] nsresult ProcessSecurityHeaders();
+
+ /**
+ * Taking care of the Content-Signature header and fail the channel if
+ * the signature verification fails or is required but the header is not
+ * present.
+ * This sets mListener to ContentVerifier, which buffers the entire response
+ * before verifying the Content-Signature header. If the verification is
+ * successful, the load proceeds as usual. If the verification fails, a
+ * NS_ERROR_INVALID_SIGNATURE is thrown and a fallback loaded in nsDocShell
+ */
+ [[nodiscard]] nsresult ProcessContentSignatureHeader(
+ nsHttpResponseHead* aResponseHead);
+
+ /**
+ * A function to process HTTP Strict Transport Security (HSTS) headers.
+ * Some basic consistency checks have been applied to the channel. Called
+ * from ProcessSecurityHeaders.
+ */
+ [[nodiscard]] nsresult ProcessHSTSHeader(nsITransportSecurityInfo* aSecInfo);
+
+ void InvalidateCacheEntryForLocation(const char* location);
+ void AssembleCacheKey(const char* spec, uint32_t postID, nsACString& key);
+ [[nodiscard]] nsresult CreateNewURI(const char* loc, nsIURI** newURI);
+ void DoInvalidateCacheEntry(nsIURI* aURI);
+
+ // Ref RFC2616 13.10: "invalidation... MUST only be performed if
+ // the host part is the same as in the Request-URI"
+ inline bool HostPartIsTheSame(nsIURI* uri) {
+ nsAutoCString tmpHost1, tmpHost2;
+ return (NS_SUCCEEDED(mURI->GetAsciiHost(tmpHost1)) &&
+ NS_SUCCEEDED(uri->GetAsciiHost(tmpHost2)) &&
+ (tmpHost1 == tmpHost2));
+ }
+
+ inline static bool DoNotRender3xxBody(nsresult rv) {
+ return rv == NS_ERROR_REDIRECT_LOOP || rv == NS_ERROR_CORRUPTED_CONTENT ||
+ rv == NS_ERROR_UNKNOWN_PROTOCOL || rv == NS_ERROR_MALFORMED_URI ||
+ rv == NS_ERROR_PORT_ACCESS_NOT_ALLOWED;
+ }
+
+ // Report net vs cache time telemetry
+ void ReportNetVSCacheTelemetry();
+ int64_t ComputeTelemetryBucketNumber(int64_t difftime_ms);
+
+ // Report telemetry and stats to about:networking
+ void ReportRcwnStats(bool isFromNet);
+
+ // Create a aggregate set of the current notification callbacks
+ // and ensure the transaction is updated to use it.
+ void UpdateAggregateCallbacks();
+
+ static bool HasQueryString(nsHttpRequestHead::ParsedMethodType method,
+ nsIURI* uri);
+ bool ResponseWouldVary(nsICacheEntry* entry);
+ bool IsResumable(int64_t partialLen, int64_t contentLength,
+ bool ignoreMissingPartialLen = false) const;
+ [[nodiscard]] nsresult MaybeSetupByteRangeRequest(
+ int64_t partialLen, int64_t contentLength,
+ bool ignoreMissingPartialLen = false);
+ [[nodiscard]] nsresult SetupByteRangeRequest(int64_t partialLen);
+ void UntieByteRangeRequest();
+ void UntieValidationRequest();
+ [[nodiscard]] nsresult OpenCacheInputStream(nsICacheEntry* cacheEntry,
+ bool startBuffering);
+
+ void SetPushedStreamTransactionAndId(
+ HttpTransactionShell* aTransWithPushedStream, uint32_t aPushedStreamId);
+
+ void SetOriginHeader();
+ void SetDoNotTrack();
+ void SetGlobalPrivacyControl();
+
+ already_AddRefed<nsChannelClassifier> GetOrCreateChannelClassifier();
+
+ // Start an internal redirect to a new InterceptedHttpChannel which will
+ // resolve in firing a ServiceWorker FetchEvent.
+ [[nodiscard]] nsresult RedirectToInterceptedChannel();
+
+ // Start an internal redirect to a new channel for auth retry
+ [[nodiscard]] nsresult RedirectToNewChannelForAuthRetry();
+
+ // Determines and sets content type in the cache entry. It's called when
+ // writing a new entry. The content type is used in cache internally only.
+ void SetCachedContentType();
+
+ private:
+ // this section is for main-thread-only object
+ // all the references need to be proxy released on main thread.
+ // auth specific data
+ nsCOMPtr<nsIHttpChannelAuthProvider> mAuthProvider;
+ nsCOMPtr<nsIURI> mRedirectURI;
+ nsCOMPtr<nsIURI> mUnstrippedRedirectURI;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsCOMPtr<nsIChannel> mPreflightChannel;
+
+ // nsChannelClassifier checks this channel's URI against
+ // the URI classifier service.
+ // nsChannelClassifier will be invoked twice in InitLocalBlockList() and
+ // BeginConnect(), so save the nsChannelClassifier here to keep the
+ // state of whether tracking protection is enabled or not.
+ RefPtr<nsChannelClassifier> mChannelClassifier;
+
+ // Proxy release all members above on main thread.
+ void ReleaseMainThreadOnlyReferences();
+
+ // Called after the channel is made aware of its tracking status in order
+ // to readjust the referrer if needed according to the referrer default
+ // policy preferences.
+ void ReEvaluateReferrerAfterTrackingStatusIsKnown();
+
+ // Create a dummy channel for the same principal, out of the load group
+ // just to revalidate the cache entry. We don't care if this fails.
+ // This method can be called on any thread, and creates an idle task
+ // to perform the revalidation with delay.
+ void PerformBackgroundCacheRevalidation();
+ // This method can only be called on the main thread.
+ void PerformBackgroundCacheRevalidationNow();
+
+ private:
+ nsCOMPtr<nsICancelable> mProxyRequest;
+
+ nsCOMPtr<nsIRequest> mTransactionPump;
+ RefPtr<HttpTransactionShell> mTransaction;
+ RefPtr<HttpTransactionShell> mTransactionSticky;
+
+ uint64_t mLogicalOffset{0};
+
+ // cache specific data
+ nsCOMPtr<nsICacheEntry> mCacheEntry;
+ // This will be set during OnStopRequest() before calling CloseCacheEntry(),
+ // but only if the listener wants to use alt-data (signaled by
+ // HttpBaseChannel::mPreferredCachedAltDataType being not empty)
+ // Needed because calling openAlternativeOutputStream needs a reference
+ // to the cache entry.
+ nsCOMPtr<nsICacheEntry> mAltDataCacheEntry;
+
+ nsCOMPtr<nsIURI> mCacheEntryURI;
+ nsCString mCacheIdExtension;
+
+ // We must close mCacheInputStream explicitly to avoid leaks.
+ AutoClose<nsIInputStream> mCacheInputStream;
+ RefPtr<nsInputStreamPump> mCachePump;
+ UniquePtr<nsHttpResponseHead> mCachedResponseHead;
+ nsCOMPtr<nsITransportSecurityInfo> mCachedSecurityInfo;
+ uint32_t mPostID{0};
+ uint32_t mRequestTime{0};
+
+ nsTArray<StreamFilterRequest> mStreamFilterRequests;
+
+ mozilla::TimeStamp mOnStartRequestTimestamp;
+ // Timestamp of the time the channel was suspended.
+ mozilla::TimeStamp mSuspendTimestamp;
+ mozilla::TimeStamp mOnCacheEntryCheckTimestamp;
+
+ // Properties used for the profiler markers
+ // This keeps the timestamp for the start marker, to be reused for the end
+ // marker.
+ mozilla::TimeStamp mLastStatusReported;
+ // This is true when one end marker is output, so that we never output more
+ // than one.
+ bool mEndMarkerAdded = false;
+
+ // Total time the channel spent suspended. This value is reported to
+ // telemetry in nsHttpChannel::OnStartRequest().
+ uint32_t mSuspendTotalTime{0};
+
+ friend class AutoRedirectVetoNotifier;
+ friend class HttpAsyncAborter<nsHttpChannel>;
+
+ uint32_t mRedirectType{0};
+
+ static const uint32_t WAIT_FOR_CACHE_ENTRY = 1;
+
+ bool mCacheOpenWithPriority{false};
+ uint32_t mCacheQueueSizeWhenOpen{0};
+
+ Atomic<bool, Relaxed> mCachedContentIsValid{false};
+ Atomic<bool> mIsAuthChannel{false};
+ Atomic<bool> mAuthRetryPending{false};
+
+ // clang-format off
+ // state flags
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields5, 32, (
+ (uint32_t, CachedContentIsPartial, 1),
+ (uint32_t, CacheOnlyMetadata, 1),
+ (uint32_t, TransactionReplaced, 1),
+ (uint32_t, ProxyAuthPending, 1),
+ // Set if before the first authentication attempt a custom authorization
+ // header has been set on the channel. This will make that custom header
+ // go to the server instead of any cached credentials.
+ (uint32_t, CustomAuthHeader, 1),
+ (uint32_t, Resuming, 1),
+ (uint32_t, InitedCacheEntry, 1),
+ // True if consumer added its own If-None-Match or If-Modified-Since
+ // headers. In such a case we must not override them in the cache code
+ // and also we want to pass possible 304 code response through.
+ (uint32_t, CustomConditionalRequest, 1),
+ (uint32_t, WaitingForRedirectCallback, 1),
+ // True if mRequestTime has been set. In such a case it is safe to update
+ // the cache entry's expiration time. Otherwise, it is not(see bug 567360).
+ (uint32_t, RequestTimeInitialized, 1),
+ (uint32_t, CacheEntryIsReadOnly, 1),
+ (uint32_t, CacheEntryIsWriteOnly, 1),
+ // see WAIT_FOR_* constants above
+ (uint32_t, WaitForCacheEntry, 1),
+ // whether cache entry data write was in progress during cache entry check
+ // when true, after we finish read from cache we must check all data
+ // had been loaded from cache. If not, then an error has to be propagated
+ // to the consumer.
+ (uint32_t, ConcurrentCacheAccess, 1),
+ // whether the request is setup be byte-range
+ (uint32_t, IsPartialRequest, 1),
+ // true iff there is AutoRedirectVetoNotifier on the stack
+ (uint32_t, HasAutoRedirectVetoNotifier, 1),
+ // consumers set this to true to use cache pinning, this has effect
+ // only when the channel is in an app context
+ (uint32_t, PinCacheContent, 1),
+ // True if CORS preflight has been performed
+ (uint32_t, IsCorsPreflightDone, 1),
+
+ // if the http transaction was performed (i.e. not cached) and
+ // the result in OnStopRequest was known to be correctly delimited
+ // by chunking, content-length, or h2 end-stream framing
+ (uint32_t, StronglyFramed, 1),
+
+ // true if an HTTP transaction is created for the socket thread
+ (uint32_t, UsedNetwork, 1),
+
+ // the next authentication request can be sent on a whole new connection
+ (uint32_t, AuthConnectionRestartable, 1),
+
+ // True if the channel classifier has marked the channel to be cancelled due
+ // to the safe-browsing classifier rules, but the asynchronous cancellation
+ // process hasn't finished yet.
+ (uint32_t, ChannelClassifierCancellationPending, 1),
+
+ // True only when we are between Resume and async fire of mCallOnResume.
+ // Used to suspend any newly created pumps in mCallOnResume handler.
+ (uint32_t, AsyncResumePending, 1),
+
+ // True if the data will be sent from the socket process to the
+ // content process directly.
+ (uint32_t, DataSentToChildProcess, 1),
+
+ (uint32_t, UseHTTPSSVC, 1),
+ (uint32_t, WaitHTTPSSVCRecord, 1)
+ ))
+
+ // Broken up into two bitfields to avoid alignment requirements of uint64_t.
+ // (Too many bits used for one uint32_t.)
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields6, 32, (
+ // Only set to true when we receive an HTTPSSVC record before the
+ // transaction is created.
+ (uint32_t, HTTPSSVCTelemetryReported, 1),
+ (uint32_t, EchConfigUsed, 1),
+ (uint32_t, AuthRedirectedChannel, 1)
+ ))
+ // clang-format on
+
+ nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack;
+
+ // Needed for accurate DNS timing
+ RefPtr<nsDNSPrefetch> mDNSPrefetch;
+
+ uint32_t mPushedStreamId{0};
+ RefPtr<HttpTransactionShell> mTransWithPushedStream;
+
+ // True if the channel's principal was found on a phishing, malware, or
+ // tracking (if tracking protection is enabled) blocklist
+ bool mLocalBlocklist{false};
+
+ [[nodiscard]] nsresult WaitForRedirectCallback();
+ void PushRedirectAsyncFunc(nsContinueRedirectionFunc func);
+ void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
+
+ // If this resource is eligible for tailing based on class-of-service flags
+ // and load flags. We don't tail Leaders/Unblocked/UrgentStart and top-level
+ // loads.
+ bool EligibleForTailing();
+
+ // Called exclusively only from AsyncOpen or after all classification
+ // callbacks. If this channel is 1) Tail, 2) assigned a request context, 3)
+ // the context is still in the tail-blocked phase, then the method will queue
+ // this channel. OnTailUnblock will be called after the context is
+ // tail-unblocked or canceled.
+ bool WaitingForTailUnblock();
+
+ // A function we trigger when untail callback is triggered by our request
+ // context in case this channel was tail-blocked.
+ using TailUnblockCallback = nsresult (nsHttpChannel::*)();
+ TailUnblockCallback mOnTailUnblock{nullptr};
+ // Called on untail when tailed during AsyncOpen execution.
+ nsresult AsyncOpenOnTailUnblock();
+ // Called on untail when tailed because of being a tracking resource.
+ nsresult ConnectOnTailUnblock();
+
+ nsCString mUsername;
+
+ // If non-null, warnings should be reported to this object.
+ RefPtr<HttpChannelSecurityWarningReporter> mWarningReporter;
+
+ // True if the channel is reading from cache.
+ Atomic<bool> mIsReadingFromCache{false};
+
+ // nsITimerCallback is implemented on a subclass so that the name attribute
+ // doesn't conflict with the name attribute of the nsIRequest interface that
+ // might be present on the same object (as seen from JavaScript code).
+ class TimerCallback final : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ explicit TimerCallback(nsHttpChannel* aChannel);
+
+ private:
+ ~TimerCallback() = default;
+
+ RefPtr<nsHttpChannel> mChannel;
+ };
+
+ // These next members are only used in unit tests to delay the call to
+ // cache->AsyncOpenURI in order to race the cache with the network.
+ nsCOMPtr<nsITimer> mCacheOpenTimer;
+ std::function<void(nsHttpChannel*)> mCacheOpenFunc;
+ uint32_t mCacheOpenDelay = 0;
+ uint32_t mNetworkTriggerDelay = 0;
+
+ // We need to remember which is the source of the response we are using.
+ enum ResponseSource {
+ RESPONSE_PENDING = 0, // response is pending
+ RESPONSE_FROM_CACHE = 1, // response coming from cache. no network.
+ RESPONSE_FROM_NETWORK = 2 // response coming from the network
+ };
+ Atomic<ResponseSource, Relaxed> mFirstResponseSource{RESPONSE_PENDING};
+
+ // Determines if it's possible and advisable to race the network request
+ // with the cache fetch, and proceeds to do so.
+ void MaybeRaceCacheWithNetwork();
+
+ // Creates a new cache entry when network wins the race to ensure we have
+ // the latest version of the resource in the cache. Otherwise we might return
+ // an old content when navigating back in history.
+ void MaybeCreateCacheEntryWhenRCWN();
+
+ nsresult TriggerNetworkWithDelay(uint32_t aDelay);
+ nsresult TriggerNetwork();
+ void CancelNetworkRequest(nsresult aStatus);
+
+ nsresult LogConsoleError(const char* aTag);
+
+ void SetHTTPSSVCRecord(already_AddRefed<nsIDNSHTTPSSVCRecord>&& aRecord);
+
+ void RecordOnStartTelemetry(nsresult aStatus, bool aIsNavigation);
+
+ // Timer used to delay the network request, or to trigger the network
+ // request if retrieving the cache entry takes too long.
+ nsCOMPtr<nsITimer> mNetworkTriggerTimer;
+ // Is true if the network request has been triggered.
+ bool mNetworkTriggered = false;
+ bool mWaitingForProxy = false;
+ bool mStaleRevalidation = false;
+ // Will be true if the onCacheEntryAvailable callback is not called by the
+ // time we send the network request
+ Atomic<bool> mRaceCacheWithNetwork{false};
+ uint32_t mRaceDelay{0};
+ // If true then OnCacheEntryAvailable should ignore the entry, because
+ // SetupTransaction removed conditional headers and decisions made in
+ // OnCacheEntryCheck are no longer valid.
+ bool mIgnoreCacheEntry{false};
+ // Lock preventing SetupTransaction/MaybeCreateCacheEntryWhenRCWN and
+ // OnCacheEntryCheck being called at the same time.
+ mozilla::Mutex mRCWNLock MOZ_UNANNOTATED{"nsHttpChannel.mRCWNLock"};
+
+ TimeStamp mNavigationStartTimeStamp;
+
+ // Promise that blocks connection creation when we want to resolve the origin
+ // host name to be able to give the configured proxy only the resolved IP
+ // to not leak names.
+ MozPromiseHolder<DNSPromise> mDNSBlockingPromise;
+ // When we hit DoConnect before the resolution is done, Then() will be set
+ // here to resume DoConnect.
+ RefPtr<DNSPromise> mDNSBlockingThenable;
+
+ // We update the value of mProxyConnectResponseCode when OnStartRequest is
+ // called and reset the value when we switch to another failover proxy.
+ int32_t mProxyConnectResponseCode{0};
+
+ // If mHTTPSSVCRecord has value, it means OnHTTPSRRAvailable() is called and
+ // we got the result of HTTPS RR query. Otherwise, it means we are still
+ // waiting for the result or the query is not performed.
+ Maybe<nsCOMPtr<nsIDNSHTTPSSVCRecord>> mHTTPSSVCRecord;
+
+ protected:
+ virtual void DoNotifyListenerCleanup() override;
+
+ // Override ReleaseListeners() because mChannelClassifier only exists
+ // in nsHttpChannel and it will be released in ReleaseListeners().
+ virtual void ReleaseListeners() override;
+
+ virtual void DoAsyncAbort(nsresult aStatus) override;
+
+ private: // cache telemetry
+ bool mDidReval{false};
+
+ RefPtr<nsIEarlyHintObserver> mEarlyHintObserver;
+ Maybe<nsCString> mOpenerCallingScriptLocation;
+ RefPtr<WebTransportSessionEventListener> mWebTransportSessionEventListener;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpChannel, NS_HTTPCHANNEL_IID)
+} // namespace net
+} // namespace mozilla
+
+inline nsISupports* ToSupports(mozilla::net::nsHttpChannel* aChannel) {
+ return static_cast<nsIHttpChannel*>(aChannel);
+}
+
+#endif // nsHttpChannel_h__
diff --git a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
new file mode 100644
index 0000000000..8e7df0ae3d
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
@@ -0,0 +1,1913 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=4 sw=2 sts=2 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/Tokenizer.h"
+#include "MockHttpAuth.h"
+#include "nsHttpChannelAuthProvider.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpAuthenticator.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsEscape.h"
+#include "nsAuthInformationHolder.h"
+#include "nsIStringBundle.h"
+#include "nsIPromptService.h"
+#include "netCore.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIURI.h"
+#include "nsContentUtils.h"
+#include "nsHttp.h"
+#include "nsHttpBasicAuth.h"
+#include "nsHttpDigestAuth.h"
+#ifdef MOZ_AUTH_EXTENSION
+# include "nsHttpNegotiateAuth.h"
+#endif
+#include "nsHttpNTLMAuth.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIURL.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_prompts.h"
+#include "mozilla/Telemetry.h"
+#include "nsIProxiedChannel.h"
+#include "nsIProxyInfo.h"
+
+namespace mozilla::net {
+
+#define SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL 0
+#define SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN 1
+#define SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL 2
+
+#define HTTP_AUTH_DIALOG_TOP_LEVEL_DOC 29
+#define HTTP_AUTH_DIALOG_SAME_ORIGIN_SUBRESOURCE 30
+#define HTTP_AUTH_DIALOG_SAME_ORIGIN_XHR 31
+#define HTTP_AUTH_DIALOG_NON_WEB_CONTENT 32
+
+#define HTTP_AUTH_BASIC_INSECURE 0
+#define HTTP_AUTH_BASIC_SECURE 1
+#define HTTP_AUTH_DIGEST_INSECURE 2
+#define HTTP_AUTH_DIGEST_SECURE 3
+#define HTTP_AUTH_NTLM_INSECURE 4
+#define HTTP_AUTH_NTLM_SECURE 5
+#define HTTP_AUTH_NEGOTIATE_INSECURE 6
+#define HTTP_AUTH_NEGOTIATE_SECURE 7
+
+#define MAX_DISPLAYED_USER_LENGTH 64
+#define MAX_DISPLAYED_HOST_LENGTH 64
+
+static void GetOriginAttributesSuffix(nsIChannel* aChan, nsACString& aSuffix) {
+ OriginAttributes oa;
+
+ // Deliberately ignoring the result and going with defaults
+ if (aChan) {
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(aChan, oa);
+ }
+
+ oa.CreateSuffix(aSuffix);
+}
+
+nsHttpChannelAuthProvider::nsHttpChannelAuthProvider()
+ : mProxyAuth(false),
+ mTriedProxyAuth(false),
+ mTriedHostAuth(false),
+ mSuppressDefensiveAuth(false),
+ mCrossOrigin(false),
+ mConnectionBased(false),
+ mHttpHandler(gHttpHandler) {}
+
+nsHttpChannelAuthProvider::~nsHttpChannelAuthProvider() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mAuthChannel, "Disconnect wasn't called");
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::Init(nsIHttpAuthenticableChannel* channel) {
+ MOZ_ASSERT(channel, "channel expected!");
+
+ mAuthChannel = channel;
+
+ nsresult rv = mAuthChannel->GetURI(getter_AddRefs(mURI));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mAuthChannel->GetIsSSL(&mUsingSSL);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProxiedChannel> proxied(channel);
+ if (proxied) {
+ nsCOMPtr<nsIProxyInfo> pi;
+ rv = proxied->GetProxyInfo(getter_AddRefs(pi));
+ if (NS_FAILED(rv)) return rv;
+
+ if (pi) {
+ nsAutoCString proxyType;
+ rv = pi->GetType(proxyType);
+ if (NS_FAILED(rv)) return rv;
+
+ mProxyUsingSSL = proxyType.EqualsLiteral("https");
+ }
+ }
+
+ rv = mURI->GetAsciiHost(mHost);
+ if (NS_FAILED(rv)) return rv;
+
+ // reject the URL if it doesn't specify a host
+ if (mHost.IsEmpty()) return NS_ERROR_MALFORMED_URI;
+
+ rv = mURI->GetPort(&mPort);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(channel);
+ mIsPrivate = NS_UsePrivateBrowsing(bareChannel);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::ProcessAuthentication(uint32_t httpStatus,
+ bool SSLConnectFailed) {
+ LOG(
+ ("nsHttpChannelAuthProvider::ProcessAuthentication "
+ "[this=%p channel=%p code=%u SSLConnectFailed=%d]\n",
+ this, mAuthChannel, httpStatus, SSLConnectFailed));
+
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv)) return rv;
+ if (proxyInfo) {
+ mProxyInfo = do_QueryInterface(proxyInfo);
+ if (!mProxyInfo) return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsAutoCString challenges;
+ mProxyAuth = (httpStatus == 407);
+
+ rv = PrepareForAuthentication(mProxyAuth);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mProxyAuth) {
+ // only allow a proxy challenge if we have a proxy server configured.
+ // otherwise, we could inadvertently expose the user's proxy
+ // credentials to an origin server. We could attempt to proceed as
+ // if we had received a 401 from the server, but why risk flirting
+ // with trouble? IE similarly rejects 407s when a proxy server is
+ // not configured, so there's no reason not to do the same.
+ if (!UsingHttpProxy()) {
+ LOG(("rejecting 407 when proxy server not configured!\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (UsingSSL() && !SSLConnectFailed) {
+ // we need to verify that this challenge came from the proxy
+ // server itself, and not some server on the other side of the
+ // SSL tunnel.
+ LOG(("rejecting 407 from origin server!\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ rv = mAuthChannel->GetProxyChallenges(challenges);
+ } else {
+ rv = mAuthChannel->GetWWWChallenges(challenges);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString creds;
+ rv = GetCredentials(challenges, mProxyAuth, creds);
+ if (rv == NS_ERROR_IN_PROGRESS) return rv;
+ if (NS_FAILED(rv)) {
+ LOG(("unable to authenticate\n"));
+ } else {
+ // set the authentication credentials
+ if (mProxyAuth) {
+ rv = mAuthChannel->SetProxyCredentials(creds);
+ } else {
+ rv = mAuthChannel->SetWWWCredentials(creds);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::AddAuthorizationHeaders(
+ bool aDontUseCachedWWWCreds) {
+ LOG(
+ ("nsHttpChannelAuthProvider::AddAuthorizationHeaders? "
+ "[this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv)) return rv;
+ if (proxyInfo) {
+ mProxyInfo = do_QueryInterface(proxyInfo);
+ if (!mProxyInfo) return NS_ERROR_NO_INTERFACE;
+ }
+
+ uint32_t loadFlags;
+ rv = mAuthChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // this getter never fails
+ nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
+
+ // check if proxy credentials should be sent
+ if (!ProxyHost().IsEmpty() && UsingHttpProxy()) {
+ SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization, "http"_ns,
+ ProxyHost(), ProxyPort(),
+ ""_ns, // proxy has no path
+ mProxyIdent);
+ }
+
+ if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
+ LOG(("Skipping Authorization header for anonymous load\n"));
+ return NS_OK;
+ }
+
+ if (aDontUseCachedWWWCreds) {
+ LOG(
+ ("Authorization header already present:"
+ " skipping adding auth header from cache\n"));
+ return NS_OK;
+ }
+
+ // check if server credentials should be sent
+ nsAutoCString path, scheme;
+ if (NS_SUCCEEDED(GetCurrentPath(path)) &&
+ NS_SUCCEEDED(mURI->GetScheme(scheme))) {
+ SetAuthorizationHeader(authCache, nsHttp::Authorization, scheme, Host(),
+ Port(), path, mIdent);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::CheckForSuperfluousAuth() {
+ LOG(
+ ("nsHttpChannelAuthProvider::CheckForSuperfluousAuth? "
+ "[this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ // we've been called because it has been determined that this channel is
+ // getting loaded without taking the userpass from the URL. if the URL
+ // contained a userpass, then (provided some other conditions are true),
+ // we'll give the user an opportunity to abort the channel as this might be
+ // an attempt to spoof a different site (see bug 232567).
+ if (!ConfirmAuth("SuperfluousAuth", true)) {
+ // calling cancel here sets our mStatus and aborts the HTTP
+ // transaction, which prevents OnDataAvailable events.
+ Unused << mAuthChannel->Cancel(NS_ERROR_SUPERFLUOS_AUTH);
+ return NS_ERROR_SUPERFLUOS_AUTH;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::Cancel(nsresult status) {
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ if (mAsyncPromptAuthCancelable) {
+ mAsyncPromptAuthCancelable->Cancel(status);
+ mAsyncPromptAuthCancelable = nullptr;
+ }
+
+ if (mGenerateCredentialsCancelable) {
+ mGenerateCredentialsCancelable->Cancel(status);
+ mGenerateCredentialsCancelable = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::Disconnect(nsresult status) {
+ mAuthChannel = nullptr;
+
+ if (mAsyncPromptAuthCancelable) {
+ mAsyncPromptAuthCancelable->Cancel(status);
+ mAsyncPromptAuthCancelable = nullptr;
+ }
+
+ if (mGenerateCredentialsCancelable) {
+ mGenerateCredentialsCancelable->Cancel(status);
+ mGenerateCredentialsCancelable = nullptr;
+ }
+
+ NS_IF_RELEASE(mProxyAuthContinuationState);
+ NS_IF_RELEASE(mAuthContinuationState);
+
+ return NS_OK;
+}
+
+// helper function for getting an auth prompt from an interface requestor
+static void GetAuthPrompt(nsIInterfaceRequestor* ifreq, bool proxyAuth,
+ nsIAuthPrompt2** result) {
+ if (!ifreq) return;
+
+ uint32_t promptReason;
+ if (proxyAuth) {
+ promptReason = nsIAuthPromptProvider::PROMPT_PROXY;
+ } else {
+ promptReason = nsIAuthPromptProvider::PROMPT_NORMAL;
+ }
+
+ nsCOMPtr<nsIAuthPromptProvider> promptProvider = do_GetInterface(ifreq);
+ if (promptProvider) {
+ promptProvider->GetAuthPrompt(promptReason, NS_GET_IID(nsIAuthPrompt2),
+ reinterpret_cast<void**>(result));
+ } else {
+ NS_QueryAuthPrompt2(ifreq, result);
+ }
+}
+
+// generate credentials for the given challenge, and update the auth cache.
+nsresult nsHttpChannelAuthProvider::GenCredsAndSetEntry(
+ nsIHttpAuthenticator* auth, bool proxyAuth, const nsACString& scheme,
+ const nsACString& host, int32_t port, const nsACString& directory,
+ const nsACString& realm, const nsACString& challenge,
+ const nsHttpAuthIdentity& ident, nsCOMPtr<nsISupports>& sessionState,
+ nsACString& result) {
+ nsresult rv;
+ nsISupports* ss = sessionState;
+
+ // set informations that depend on whether
+ // we're authenticating against a proxy
+ // or a webserver
+ nsISupports** continuationState;
+
+ if (proxyAuth) {
+ continuationState = &mProxyAuthContinuationState;
+ } else {
+ continuationState = &mAuthContinuationState;
+ }
+
+ rv = auth->GenerateCredentialsAsync(
+ mAuthChannel, this, challenge, proxyAuth, ident.Domain(), ident.User(),
+ ident.Password(), ss, *continuationState,
+ getter_AddRefs(mGenerateCredentialsCancelable));
+ if (NS_SUCCEEDED(rv)) {
+ // Calling generate credentials async, results will be dispatched to the
+ // main thread by calling OnCredsGenerated method
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ uint32_t generateFlags;
+ rv = auth->GenerateCredentials(
+ mAuthChannel, challenge, proxyAuth, ident.Domain(), ident.User(),
+ ident.Password(), &ss, &*continuationState, &generateFlags, result);
+
+ sessionState.swap(ss);
+ if (NS_FAILED(rv)) return rv;
+
+ // don't log this in release build since it could contain sensitive info.
+#ifdef DEBUG
+ LOG(("generated creds: %s\n", result.BeginReading()));
+#endif
+
+ return UpdateCache(auth, scheme, host, port, directory, realm, challenge,
+ ident, result, generateFlags, sessionState, proxyAuth);
+}
+
+nsresult nsHttpChannelAuthProvider::UpdateCache(
+ nsIHttpAuthenticator* auth, const nsACString& scheme,
+ const nsACString& host, int32_t port, const nsACString& directory,
+ const nsACString& realm, const nsACString& challenge,
+ const nsHttpAuthIdentity& ident, const nsACString& creds,
+ uint32_t generateFlags, nsISupports* sessionState, bool aProxyAuth) {
+ nsresult rv;
+
+ uint32_t authFlags;
+ rv = auth->GetAuthFlags(&authFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // find out if this authenticator allows reuse of credentials and/or
+ // challenge.
+ bool saveCreds =
+ 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS);
+ bool saveChallenge =
+ 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE);
+
+ bool saveIdentity =
+ 0 == (generateFlags & nsIHttpAuthenticator::USING_INTERNAL_IDENTITY);
+
+ // this getter never fails
+ nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
+
+ nsAutoCString suffix;
+ if (!aProxyAuth) {
+ // We don't isolate proxy credentials cache entries with the origin suffix
+ // as it would only annoy users with authentication dialogs popping up.
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ GetOriginAttributesSuffix(chan, suffix);
+ }
+
+ // create a cache entry. we do this even though we don't yet know that
+ // these credentials are valid b/c we need to avoid prompting the user
+ // more than once in case the credentials are valid.
+ //
+ // if the credentials are not reusable, then we don't bother sticking
+ // them in the auth cache.
+ rv = authCache->SetAuthEntry(scheme, host, port, directory, realm,
+ saveCreds ? creds : ""_ns,
+ saveChallenge ? challenge : ""_ns, suffix,
+ saveIdentity ? &ident : nullptr, sessionState);
+ return rv;
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::ClearProxyIdent() {
+ LOG(("nsHttpChannelAuthProvider::ClearProxyIdent [this=%p]\n", this));
+
+ mProxyIdent.Clear();
+ return NS_OK;
+}
+
+nsresult nsHttpChannelAuthProvider::PrepareForAuthentication(bool proxyAuth) {
+ LOG(
+ ("nsHttpChannelAuthProvider::PrepareForAuthentication "
+ "[this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ if (!proxyAuth) {
+ // reset the current proxy continuation state because our last
+ // authentication attempt was completed successfully.
+ NS_IF_RELEASE(mProxyAuthContinuationState);
+ LOG((" proxy continuation state has been reset"));
+ }
+
+ if (!UsingHttpProxy() || mProxyAuthType.IsEmpty()) return NS_OK;
+
+ // We need to remove any Proxy_Authorization header left over from a
+ // non-request based authentication handshake (e.g., for NTLM auth).
+
+ nsresult rv;
+ nsCOMPtr<nsIHttpAuthenticator> precedingAuth;
+ nsCString proxyAuthType;
+ rv = GetAuthenticator(mProxyAuthType, proxyAuthType,
+ getter_AddRefs(precedingAuth));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t precedingAuthFlags;
+ rv = precedingAuth->GetAuthFlags(&precedingAuthFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!(precedingAuthFlags & nsIHttpAuthenticator::REQUEST_BASED)) {
+ nsAutoCString challenges;
+ rv = mAuthChannel->GetProxyChallenges(challenges);
+ if (NS_FAILED(rv)) {
+ // delete the proxy authorization header because we weren't
+ // asked to authenticate
+ rv = mAuthChannel->SetProxyCredentials(""_ns);
+ if (NS_FAILED(rv)) return rv;
+ LOG((" cleared proxy authorization header"));
+ }
+ }
+
+ return NS_OK;
+}
+
+class MOZ_STACK_CLASS ChallengeParser final : Tokenizer {
+ public:
+ explicit ChallengeParser(const nsACString& aChallenges)
+ : Tokenizer(aChallenges, nullptr, "") {
+ Record();
+ }
+
+ Maybe<nsDependentCSubstring> GetNext() {
+ Token t;
+ nsDependentCSubstring result;
+
+ bool inQuote = false;
+
+ while (Next(t)) {
+ if (t.Type() == TOKEN_EOL) {
+ Claim(result, ClaimInclusion::EXCLUDE_LAST);
+ SkipWhites(WhiteSkipping::INCLUDE_NEW_LINE);
+ Record();
+ inQuote = false;
+ if (!result.IsEmpty()) {
+ return Some(result);
+ }
+ } else if (t.Equals(Token::Char(',')) && !inQuote) {
+ // Sometimes we get multiple challenges separated by a comma.
+ // This is not great, as it's slightly ambiguous. We check if something
+ // is a new challenge by matching agains <param_name> =
+ // If the , isn't followed by a word and = then most likely
+ // it is the name of an authType.
+
+ const char* prevCursorPos = mCursor;
+ const char* prevRollbackPos = mRollback;
+
+ auto hasWordAndEqual = [&]() {
+ SkipWhites();
+ nsDependentCSubstring word;
+ if (!ReadWord(word)) {
+ return false;
+ }
+ SkipWhites();
+ return Check(Token::Char('='));
+ };
+ if (!hasWordAndEqual()) {
+ // This is not a parameter. It means the `,` character starts a
+ // different challenge.
+ // We'll revert the cursor and return the contents so far.
+ mCursor = prevCursorPos;
+ mRollback = prevRollbackPos;
+ Claim(result, ClaimInclusion::EXCLUDE_LAST);
+ SkipWhites();
+ Record();
+ if (!result.IsEmpty()) {
+ return Some(result);
+ }
+ }
+ } else if (t.Equals(Token::Char('"'))) {
+ inQuote = !inQuote;
+ }
+ }
+
+ Claim(result, Tokenizer::ClaimInclusion::INCLUDE_LAST);
+ SkipWhites();
+ Record();
+ if (!result.IsEmpty()) {
+ return Some(result);
+ }
+ return Nothing{};
+ }
+};
+
+enum ChallengeRank {
+ Unknown = 0,
+ Basic = 1,
+ Digest = 2,
+ NTLM = 3,
+ Negotiate = 4,
+};
+
+ChallengeRank Rank(const nsACString& aChallenge) {
+ if (StringBeginsWith(aChallenge, "Negotiate"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return ChallengeRank::Negotiate;
+ }
+
+ if (StringBeginsWith(aChallenge, "NTLM"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return ChallengeRank::NTLM;
+ }
+
+ if (StringBeginsWith(aChallenge, "Digest"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return ChallengeRank::Digest;
+ }
+
+ if (StringBeginsWith(aChallenge, "Basic"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return ChallengeRank::Basic;
+ }
+
+ return ChallengeRank::Unknown;
+}
+
+nsresult nsHttpChannelAuthProvider::GetCredentials(
+ const nsACString& aChallenges, bool proxyAuth, nsCString& creds) {
+ LOG(("nsHttpChannelAuthProvider::GetCredentials"));
+ nsAutoCString challenges(aChallenges);
+
+ using AuthChallenge = struct AuthChallenge {
+ nsDependentCSubstring challenge;
+ uint16_t algorithm = 0;
+ ChallengeRank rank = ChallengeRank::Unknown;
+
+ void operator=(const AuthChallenge& aOther) {
+ challenge.Rebind(aOther.challenge, 0);
+ algorithm = aOther.algorithm;
+ rank = aOther.rank;
+ }
+ };
+
+ nsTArray<AuthChallenge> cc;
+
+ ChallengeParser p(challenges);
+ while (true) {
+ auto next = p.GetNext();
+ if (next.isNothing()) {
+ break;
+ }
+ AuthChallenge ac{next.ref(), 0};
+ nsAutoCString realm, domain, nonce, opaque;
+ bool stale = false;
+ uint16_t qop = 0;
+ ac.rank = Rank(ac.challenge);
+ if (StringBeginsWith(ac.challenge, "Digest"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ Unused << nsHttpDigestAuth::ParseChallenge(ac.challenge, realm, domain,
+ nonce, opaque, &stale,
+ &ac.algorithm, &qop);
+ }
+ cc.AppendElement(ac);
+ }
+
+ cc.StableSort([](const AuthChallenge& lhs, const AuthChallenge& rhs) {
+ if (StaticPrefs::network_auth_choose_most_secure_challenge()) {
+ // Different auth types
+ if (lhs.rank != rhs.rank) {
+ return lhs.rank < rhs.rank ? 1 : -1;
+ }
+
+ // If they're the same auth type, and not a Digest, then we treat them
+ // as equal (don't reorder them).
+ if (lhs.rank != ChallengeRank::Digest) {
+ return 0;
+ }
+ } else {
+ // Non-digest challenges should not be reordered when the pref is off.
+ if (lhs.algorithm == 0 || rhs.algorithm == 0) {
+ return 0;
+ }
+ }
+
+ // Same algorithm.
+ if (lhs.algorithm == rhs.algorithm) {
+ return 0;
+ }
+ return lhs.algorithm < rhs.algorithm ? 1 : -1;
+ });
+
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsCString authType; // force heap allocation to enable string sharing since
+ // we'll be assigning this value into mAuthType.
+
+ // set informations that depend on whether we're authenticating against a
+ // proxy or a webserver
+ nsISupports** currentContinuationState;
+ nsCString* currentAuthType;
+
+ if (proxyAuth) {
+ currentContinuationState = &mProxyAuthContinuationState;
+ currentAuthType = &mProxyAuthType;
+ } else {
+ currentContinuationState = &mAuthContinuationState;
+ currentAuthType = &mAuthType;
+ }
+
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ bool gotCreds = false;
+
+ // figure out which challenge we can handle and which authenticator to use.
+ for (size_t i = 0; i < cc.Length(); i++) {
+ rv = GetAuthenticator(cc[i].challenge, authType, getter_AddRefs(auth));
+ LOG(("trying auth for %s", authType.get()));
+ if (NS_SUCCEEDED(rv)) {
+ //
+ // if we've already selected an auth type from a previous challenge
+ // received while processing this channel, then skip others until
+ // we find a challenge corresponding to the previously tried auth
+ // type.
+ //
+ if (!currentAuthType->IsEmpty() && authType != *currentAuthType) continue;
+
+ //
+ // we allow the routines to run all the way through before we
+ // decide if they are valid.
+ //
+ // we don't worry about the auth cache being altered because that
+ // would have been the last step, and if the error is from updating
+ // the authcache it wasn't really altered anyway. -CTN
+ //
+ // at this point the code is really only useful for client side
+ // errors (it will not automatically fail over to do a different
+ // auth type if the server keeps rejecting what is being sent, even
+ // if a particular auth method only knows 1 thing, like a
+ // non-identity based authentication method)
+ //
+ rv = GetCredentialsForChallenge(cc[i].challenge, authType, proxyAuth,
+ auth, creds);
+ if (NS_SUCCEEDED(rv)) {
+ gotCreds = true;
+ *currentAuthType = authType;
+
+ break;
+ }
+ if (rv == NS_ERROR_IN_PROGRESS) {
+ // authentication prompt has been invoked and result is
+ // expected asynchronously, save current challenge being
+ // processed and all remaining challenges to use later in
+ // OnAuthAvailable and now immediately return
+ mCurrentChallenge = cc[i].challenge;
+ // imperfect; does not save server-side preference ordering.
+ // instead, continues with remaining string as provided by client
+ mRemainingChallenges.Truncate();
+ while (i + 1 < cc.Length()) {
+ i++;
+ mRemainingChallenges.Append(cc[i].challenge);
+ mRemainingChallenges.Append("\n"_ns);
+ }
+ return rv;
+ }
+
+ // reset the auth type and continuation state
+ NS_IF_RELEASE(*currentContinuationState);
+ currentAuthType->Truncate();
+ }
+ }
+
+ if (!gotCreds && !currentAuthType->IsEmpty()) {
+ // looks like we never found the auth type we were looking for.
+ // reset the auth type and continuation state, and try again.
+ currentAuthType->Truncate();
+ NS_IF_RELEASE(*currentContinuationState);
+
+ rv = GetCredentials(challenges, proxyAuth, creds);
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannelAuthProvider::GetAuthorizationMembers(
+ bool proxyAuth, nsACString& scheme, nsCString& host, int32_t& port,
+ nsACString& path, nsHttpAuthIdentity*& ident,
+ nsISupports**& continuationState) {
+ if (proxyAuth) {
+ MOZ_ASSERT(UsingHttpProxy(),
+ "proxyAuth is true, but no HTTP proxy is configured!");
+
+ host = ProxyHost();
+ port = ProxyPort();
+ ident = &mProxyIdent;
+ scheme.AssignLiteral("http");
+
+ continuationState = &mProxyAuthContinuationState;
+ } else {
+ host = Host();
+ port = Port();
+ ident = &mIdent;
+
+ nsresult rv;
+ rv = GetCurrentPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ continuationState = &mAuthContinuationState;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannelAuthProvider::GetCredentialsForChallenge(
+ const nsACString& aChallenge, const nsACString& aAuthType, bool proxyAuth,
+ nsIHttpAuthenticator* auth, nsCString& creds) {
+ LOG(
+ ("nsHttpChannelAuthProvider::GetCredentialsForChallenge "
+ "[this=%p channel=%p proxyAuth=%d challenges=%s]\n",
+ this, mAuthChannel, proxyAuth, nsCString(aChallenge).get()));
+
+ // this getter never fails
+ nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
+
+ uint32_t authFlags;
+ nsresult rv = auth->GetAuthFlags(&authFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString realm;
+ ParseRealm(aChallenge, realm);
+
+ // if no realm, then use the auth type as the realm. ToUpperCase so the
+ // ficticious realm stands out a bit more.
+ // XXX this will cause some single signon misses!
+ // XXX this was meant to be used with NTLM, which supplies no realm.
+ /*
+ if (realm.IsEmpty()) {
+ realm = authType;
+ ToUpperCase(realm);
+ }
+ */
+
+ // set informations that depend on whether
+ // we're authenticating against a proxy
+ // or a webserver
+ nsAutoCString host;
+ int32_t port;
+ nsHttpAuthIdentity* ident;
+ nsAutoCString path, scheme;
+ bool identFromURI = false;
+ nsISupports** continuationState;
+
+ rv = GetAuthorizationMembers(proxyAuth, scheme, host, port, path, ident,
+ continuationState);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t loadFlags;
+ rv = mAuthChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // Fill only for non-proxy auth, proxy credentials are not OA-isolated.
+ nsAutoCString suffix;
+
+ if (!proxyAuth) {
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ GetOriginAttributesSuffix(chan, suffix);
+
+ // if this is the first challenge, then try using the identity
+ // specified in the URL.
+ if (mIdent.IsEmpty()) {
+ GetIdentityFromURI(authFlags, mIdent);
+ identFromURI = !mIdent.IsEmpty();
+ }
+
+ if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !identFromURI) {
+ LOG(("Skipping authentication for anonymous non-proxy request\n"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Let explicit URL credentials pass
+ // regardless of the LOAD_ANONYMOUS flag
+ } else if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !UsingHttpProxy()) {
+ LOG(("Skipping authentication for anonymous non-proxy request\n"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ //
+ // if we already tried some credentials for this transaction, then
+ // we need to possibly clear them from the cache, unless the credentials
+ // in the cache have changed, in which case we'd want to give them a
+ // try instead.
+ //
+ nsHttpAuthEntry* entry = nullptr;
+ Unused << authCache->GetAuthEntryForDomain(scheme, host, port, realm, suffix,
+ &entry);
+
+ // hold reference to the auth session state (in case we clear our
+ // reference to the entry).
+ nsCOMPtr<nsISupports> sessionStateGrip;
+ if (entry) sessionStateGrip = entry->mMetaData;
+
+ // remember if we already had the continuation state. it means we are in
+ // the middle of the authentication exchange and the connection must be
+ // kept sticky then (and only then).
+ bool authAtProgress = !!*continuationState;
+
+ // for digest auth, maybe our cached nonce value simply timed out...
+ bool identityInvalid;
+ nsISupports* sessionState = sessionStateGrip;
+ rv = auth->ChallengeReceived(mAuthChannel, aChallenge, proxyAuth,
+ &sessionState, &*continuationState,
+ &identityInvalid);
+ sessionStateGrip.swap(sessionState);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG((" identity invalid = %d\n", identityInvalid));
+
+ if (mConnectionBased && identityInvalid) {
+ // If the flag is set and identity is invalid, it means we received the
+ // first challange for a new negotiation round after negotiating a
+ // connection based auth failed (invalid password). The mConnectionBased
+ // flag is set later for the newly received challenge, so here it reflects
+ // the previous 401/7 response schema.
+ rv = mAuthChannel->CloseStickyConnection();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!proxyAuth) {
+ // We must clear proxy ident in the following scenario + explanation:
+ // - we are authenticating to an NTLM proxy and an NTLM server
+ // - we successfully authenticated to the proxy, mProxyIdent keeps
+ // the user name/domain and password, the identity has also been cached
+ // - we just threw away the connection because we are now asking for
+ // creds for the server (WWW auth)
+ // - hence, we will have to auth to the proxy again as well
+ // - if we didn't clear the proxy identity, it would be considered
+ // as non-valid and we would ask the user again ; clearing it forces
+ // use of the cached identity and not asking the user again
+ ClearProxyIdent();
+ }
+ }
+
+ mConnectionBased = !!(authFlags & nsIHttpAuthenticator::CONNECTION_BASED);
+
+ // It's legal if the peer closes the connection after the first 401/7.
+ // Making the connection sticky will prevent its restart giving the user
+ // a 'network reset' error every time. Hence, we mark the connection
+ // as restartable.
+ mAuthChannel->ConnectionRestartable(!authAtProgress);
+
+ if (identityInvalid) {
+ if (entry) {
+ if (ident->Equals(entry->Identity())) {
+ if (!identFromURI) {
+ LOG((" clearing bad auth cache entry\n"));
+ // ok, we've already tried this user identity, so clear the
+ // corresponding entry from the auth cache.
+ authCache->ClearAuthEntry(scheme, host, port, realm, suffix);
+ entry = nullptr;
+ ident->Clear();
+ }
+ } else if (!identFromURI ||
+ (ident->User() == entry->Identity().User() &&
+ !(loadFlags & (nsIChannel::LOAD_ANONYMOUS |
+ nsIChannel::LOAD_EXPLICIT_CREDENTIALS)))) {
+ LOG((" taking identity from auth cache\n"));
+ // the password from the auth cache is more likely to be
+ // correct than the one in the URL. at least, we know that it
+ // works with the given username. it is possible for a server
+ // to distinguish logons based on the supplied password alone,
+ // but that would be quite unusual... and i don't think we need
+ // to worry about such unorthodox cases.
+ *ident = entry->Identity();
+ identFromURI = false;
+ if (entry->Creds()[0] != '\0') {
+ LOG((" using cached credentials!\n"));
+ creds.Assign(entry->Creds());
+ return entry->AddPath(path);
+ }
+ }
+ } else if (!identFromURI) {
+ // hmm... identity invalid, but no auth entry! the realm probably
+ // changed (see bug 201986).
+ ident->Clear();
+ }
+
+ if (!entry && ident->IsEmpty()) {
+ uint32_t level = nsIAuthPrompt2::LEVEL_NONE;
+ if ((!proxyAuth && mUsingSSL) || (proxyAuth && mProxyUsingSSL)) {
+ level = nsIAuthPrompt2::LEVEL_SECURE;
+ } else if (authFlags & nsIHttpAuthenticator::IDENTITY_ENCRYPTED) {
+ level = nsIAuthPrompt2::LEVEL_PW_ENCRYPTED;
+ }
+
+ // Collect statistics on how frequently the various types of HTTP
+ // authentication are used over SSL and non-SSL connections.
+ if (Telemetry::CanRecordPrereleaseData()) {
+ if ("basic"_ns.Equals(aAuthType, nsCaseInsensitiveCStringComparator)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_BASIC_SECURE : HTTP_AUTH_BASIC_INSECURE);
+ } else if ("digest"_ns.Equals(aAuthType,
+ nsCaseInsensitiveCStringComparator)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_DIGEST_SECURE : HTTP_AUTH_DIGEST_INSECURE);
+ } else if ("ntlm"_ns.Equals(aAuthType,
+ nsCaseInsensitiveCStringComparator)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_NTLM_SECURE : HTTP_AUTH_NTLM_INSECURE);
+ } else if ("negotiate"_ns.Equals(aAuthType,
+ nsCaseInsensitiveCStringComparator)) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_NEGOTIATE_SECURE
+ : HTTP_AUTH_NEGOTIATE_INSECURE);
+ }
+ }
+
+ // Depending on the pref setting, the authentication dialog may be
+ // blocked for all sub-resources, blocked for cross-origin
+ // sub-resources, or always allowed for sub-resources.
+ // For more details look at the bug 647010.
+ // BlockPrompt will set mCrossOrigin parameter as well.
+ if (BlockPrompt(proxyAuth)) {
+ LOG((
+ "nsHttpChannelAuthProvider::GetCredentialsForChallenge: "
+ "Prompt is blocked [this=%p pref=%d img-pref=%d "
+ "non-web-content-triggered-pref=%d]\n",
+ this, StaticPrefs::network_auth_subresource_http_auth_allow(),
+ StaticPrefs::
+ network_auth_subresource_img_cross_origin_http_auth_allow(),
+ StaticPrefs::
+ network_auth_non_web_content_triggered_resources_http_auth_allow()));
+ return NS_ERROR_ABORT;
+ }
+
+ // at this point we are forced to interact with the user to get
+ // their username and password for this domain.
+ rv = PromptForIdentity(level, proxyAuth, realm, aAuthType, authFlags,
+ *ident);
+ if (NS_FAILED(rv)) return rv;
+ identFromURI = false;
+ }
+ }
+
+ if (identFromURI) {
+ // Warn the user before automatically using the identity from the URL
+ // to automatically log them into a site (see bug 232567).
+ if (!ConfirmAuth("AutomaticAuth", false)) {
+ // calling cancel here sets our mStatus and aborts the HTTP
+ // transaction, which prevents OnDataAvailable events.
+ rv = mAuthChannel->Cancel(NS_ERROR_ABORT);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ // this return code alone is not equivalent to Cancel, since
+ // it only instructs our caller that authentication failed.
+ // without an explicit call to Cancel, our caller would just
+ // load the page that accompanies the HTTP auth challenge.
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ //
+ // get credentials for the given user:pass
+ //
+ // always store the credentials we're trying now so that they will be used
+ // on subsequent links. This will potentially remove good credentials from
+ // the cache. This is ok as we don't want to use cached credentials if the
+ // user specified something on the URI or in another manner. This is so
+ // that we don't transparently authenticate as someone they're not
+ // expecting to authenticate as.
+ //
+ nsCString result;
+ rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, path, realm,
+ aChallenge, *ident, sessionStateGrip, creds);
+ return rv;
+}
+
+bool nsHttpChannelAuthProvider::BlockPrompt(bool proxyAuth) {
+ // Verify that it's ok to prompt for credentials here, per spec
+ // http://xhr.spec.whatwg.org/#the-send%28%29-method
+
+ nsCOMPtr<nsIHttpChannelInternal> chanInternal =
+ do_QueryInterface(mAuthChannel);
+ MOZ_ASSERT(chanInternal);
+
+ if (chanInternal->GetBlockAuthPrompt()) {
+ LOG(
+ ("nsHttpChannelAuthProvider::BlockPrompt: Prompt is blocked "
+ "[this=%p channel=%p]\n",
+ this, mAuthChannel));
+ return true;
+ }
+
+ if (proxyAuth) {
+ // Do not block auth-dialog if this is a proxy authentication.
+ return false;
+ }
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+
+ // We will treat loads w/o loadInfo as a top level document.
+ bool topDoc = true;
+ bool xhr = false;
+ bool nonWebContent = false;
+
+ if (loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ topDoc = false;
+ }
+
+ if (!topDoc) {
+ nsCOMPtr<nsIPrincipal> triggeringPrinc = loadInfo->TriggeringPrincipal();
+ if (triggeringPrinc->IsSystemPrincipal()) {
+ nonWebContent = true;
+ }
+ }
+
+ if (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_XMLHTTPREQUEST) {
+ xhr = true;
+ }
+
+ if (!topDoc && !xhr) {
+ nsCOMPtr<nsIURI> topURI;
+ Unused << chanInternal->GetTopWindowURI(getter_AddRefs(topURI));
+ if (topURI) {
+ mCrossOrigin = !NS_SecurityCompareURIs(topURI, mURI, true);
+ } else {
+ nsIPrincipal* loadingPrinc = loadInfo->GetLoadingPrincipal();
+ MOZ_ASSERT(loadingPrinc);
+ mCrossOrigin = !loadingPrinc->IsSameOrigin(mURI);
+ }
+ }
+
+ if (Telemetry::CanRecordPrereleaseData()) {
+ if (topDoc) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ HTTP_AUTH_DIALOG_TOP_LEVEL_DOC);
+ } else if (nonWebContent) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ HTTP_AUTH_DIALOG_NON_WEB_CONTENT);
+ } else if (!mCrossOrigin) {
+ if (xhr) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ HTTP_AUTH_DIALOG_SAME_ORIGIN_XHR);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ HTTP_AUTH_DIALOG_SAME_ORIGIN_SUBRESOURCE);
+ }
+ } else {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ static_cast<uint32_t>(loadInfo->GetExternalContentPolicyType()));
+ }
+ }
+
+ if (!topDoc &&
+ !StaticPrefs::
+ network_auth_non_web_content_triggered_resources_http_auth_allow() &&
+ nonWebContent) {
+ return true;
+ }
+
+ switch (StaticPrefs::network_auth_subresource_http_auth_allow()) {
+ case SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL:
+ // Do not open the http-authentication credentials dialog for
+ // the sub-resources.
+ return !topDoc && !xhr;
+ case SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN:
+ // Open the http-authentication credentials dialog for
+ // the sub-resources only if they are not cross-origin.
+ return !topDoc && !xhr && mCrossOrigin;
+ case SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL:
+ // Allow the http-authentication dialog for subresources.
+ // If pref network.auth.subresource-img-cross-origin-http-auth-allow
+ // is set, http-authentication dialog for image subresources is
+ // blocked.
+ if (mCrossOrigin &&
+ !StaticPrefs::
+ network_auth_subresource_img_cross_origin_http_auth_allow() &&
+ loadInfo &&
+ ((loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_IMAGE) ||
+ (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_IMAGESET))) {
+ return true;
+ }
+ return false;
+ default:
+ // This is an invalid value.
+ MOZ_ASSERT(false, "A non valid value!");
+ }
+ return false;
+}
+
+inline void GetAuthType(const nsACString& aChallenge, nsCString& authType) {
+ auto spaceIndex = aChallenge.FindChar(' ');
+ authType = Substring(aChallenge, 0, spaceIndex);
+ // normalize to lowercase
+ ToLowerCase(authType);
+}
+
+nsresult nsHttpChannelAuthProvider::GetAuthenticator(
+ const nsACString& aChallenge, nsCString& authType,
+ nsIHttpAuthenticator** auth) {
+ LOG(("nsHttpChannelAuthProvider::GetAuthenticator [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ GetAuthType(aChallenge, authType);
+
+ nsCOMPtr<nsIHttpAuthenticator> authenticator;
+#ifdef MOZ_AUTH_EXTENSION
+ if (authType.EqualsLiteral("negotiate")) {
+ authenticator = nsHttpNegotiateAuth::GetOrCreate();
+ } else
+#endif
+ if (authType.EqualsLiteral("basic")) {
+ authenticator = nsHttpBasicAuth::GetOrCreate();
+ } else if (authType.EqualsLiteral("digest")) {
+ authenticator = nsHttpDigestAuth::GetOrCreate();
+ } else if (authType.EqualsLiteral("ntlm")) {
+ authenticator = nsHttpNTLMAuth::GetOrCreate();
+ } else if (authType.EqualsLiteral("mock_auth") &&
+ PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
+ authenticator = MockHttpAuth::Create();
+ } else {
+ return NS_ERROR_FACTORY_NOT_REGISTERED;
+ }
+
+ if (!authenticator) {
+ // If called during shutdown it's possible that the singleton authenticator
+ // was already cleared so we have a null one here.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MOZ_ASSERT(authenticator);
+ authenticator.forget(auth);
+
+ return NS_OK;
+}
+
+// buf contains "domain\user"
+static void ParseUserDomain(const nsAString& buf, nsDependentSubstring& user,
+ nsDependentSubstring& domain) {
+ auto backslashPos = buf.FindChar(u'\\');
+ if (backslashPos != kNotFound) {
+ domain.Rebind(buf, 0, backslashPos);
+ user.Rebind(buf, backslashPos + 1);
+ }
+}
+
+void nsHttpChannelAuthProvider::GetIdentityFromURI(uint32_t authFlags,
+ nsHttpAuthIdentity& ident) {
+ LOG(("nsHttpChannelAuthProvider::GetIdentityFromURI [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ bool hasUserPass;
+ if (NS_FAILED(mURI->GetHasUserPass(&hasUserPass)) || !hasUserPass) {
+ return;
+ }
+
+ nsAutoString userBuf;
+ nsAutoString passBuf;
+
+ // XXX i18n
+ nsAutoCString buf;
+ nsresult rv = mURI->GetUsername(buf);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ NS_UnescapeURL(buf);
+ CopyUTF8toUTF16(buf, userBuf);
+
+ rv = mURI->GetPassword(buf);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ NS_UnescapeURL(buf);
+ CopyUTF8toUTF16(buf, passBuf);
+
+ nsDependentSubstring user(userBuf, 0);
+ nsDependentSubstring domain(u""_ns, 0);
+
+ if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) {
+ ParseUserDomain(userBuf, user, domain);
+ }
+
+ ident = nsHttpAuthIdentity(domain, user, passBuf);
+}
+
+void nsHttpChannelAuthProvider::ParseRealm(const nsACString& aChallenge,
+ nsACString& realm) {
+ //
+ // From RFC2617 section 1.2, the realm value is defined as such:
+ //
+ // realm = "realm" "=" realm-value
+ // realm-value = quoted-string
+ //
+ // but, we'll accept anything after the the "=" up to the first space, or
+ // end-of-line, if the string is not quoted.
+ //
+
+ Tokenizer t(aChallenge);
+
+ // The challenge begins with the authType.
+ // If we can't find that something has probably gone wrong.
+ t.SkipWhites();
+ nsDependentCSubstring authType;
+ if (!t.ReadWord(authType)) {
+ return;
+ }
+
+ // Will return true if the tokenizer advanced the cursor - false otherwise.
+ auto readParam = [&](nsDependentCSubstring& key, nsAutoCString& value) {
+ key.Rebind(EmptyCString(), 0);
+ value.Truncate();
+
+ t.SkipWhites();
+ if (!t.ReadWord(key)) {
+ return false;
+ }
+ t.SkipWhites();
+ if (!t.CheckChar('=')) {
+ return true;
+ }
+ t.SkipWhites();
+
+ Tokenizer::Token token1;
+
+ t.Record();
+ if (!t.Next(token1)) {
+ return true;
+ }
+ nsDependentCSubstring sub;
+ bool hasQuote = false;
+ if (token1.Equals(Tokenizer::Token::Char('"'))) {
+ hasQuote = true;
+ } else {
+ t.Claim(sub, Tokenizer::ClaimInclusion::INCLUDE_LAST);
+ value.Append(sub);
+ }
+ t.Record();
+ Tokenizer::Token token2;
+ while (t.Next(token2)) {
+ if (hasQuote && token2.Equals(Tokenizer::Token::Char('"')) &&
+ !token1.Equals(Tokenizer::Token::Char('\\'))) {
+ break;
+ }
+ if (!hasQuote && (token2.Type() == Tokenizer::TokenType::TOKEN_WS ||
+ token2.Type() == Tokenizer::TokenType::TOKEN_EOL)) {
+ break;
+ }
+
+ t.Claim(sub, Tokenizer::ClaimInclusion::INCLUDE_LAST);
+ if (!sub.Equals(R"(\)")) {
+ value.Append(sub);
+ }
+ t.Record();
+ token1 = token2;
+ }
+ return true;
+ };
+
+ while (!t.CheckEOF()) {
+ nsDependentCSubstring key;
+ nsAutoCString value;
+ // If we couldn't read anything, and the input isn't followed by a ,
+ // then we exit.
+ if (!readParam(key, value) && !t.Check(Tokenizer::Token::Char(','))) {
+ break;
+ }
+ // When we find the first instance of realm we exit.
+ // Theoretically there should be only one instance and we should fail
+ // if there are more, but we're trying to preserve existing behaviour.
+ if (key.Equals("realm"_ns, nsCaseInsensitiveCStringComparator)) {
+ realm = value;
+ break;
+ }
+ }
+}
+
+class nsHTTPAuthInformation : public nsAuthInformationHolder {
+ public:
+ nsHTTPAuthInformation(uint32_t aFlags, const nsString& aRealm,
+ const nsACString& aAuthType)
+ : nsAuthInformationHolder(aFlags, aRealm, aAuthType) {}
+
+ void SetToHttpAuthIdentity(uint32_t authFlags, nsHttpAuthIdentity& identity);
+};
+
+void nsHTTPAuthInformation::SetToHttpAuthIdentity(
+ uint32_t authFlags, nsHttpAuthIdentity& identity) {
+ identity = nsHttpAuthIdentity(Domain(), User(), Password());
+}
+
+nsresult nsHttpChannelAuthProvider::PromptForIdentity(
+ uint32_t level, bool proxyAuth, const nsACString& realm,
+ const nsACString& authType, uint32_t authFlags, nsHttpAuthIdentity& ident) {
+ LOG(("nsHttpChannelAuthProvider::PromptForIdentity [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIAuthPrompt2> authPrompt;
+ GetAuthPrompt(callbacks, proxyAuth, getter_AddRefs(authPrompt));
+ if (!authPrompt && loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ GetAuthPrompt(cbs, proxyAuth, getter_AddRefs(authPrompt));
+ }
+ if (!authPrompt) return NS_ERROR_NO_INTERFACE;
+
+ // XXX i18n: need to support non-ASCII realm strings (see bug 41489)
+ NS_ConvertASCIItoUTF16 realmU(realm);
+
+ // prompt the user...
+ uint32_t promptFlags = 0;
+ if (proxyAuth) {
+ promptFlags |= nsIAuthInformation::AUTH_PROXY;
+ if (mTriedProxyAuth) promptFlags |= nsIAuthInformation::PREVIOUS_FAILED;
+ mTriedProxyAuth = true;
+ } else {
+ promptFlags |= nsIAuthInformation::AUTH_HOST;
+ if (mTriedHostAuth) promptFlags |= nsIAuthInformation::PREVIOUS_FAILED;
+ mTriedHostAuth = true;
+ }
+
+ if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) {
+ promptFlags |= nsIAuthInformation::NEED_DOMAIN;
+ }
+
+ if (mCrossOrigin) {
+ promptFlags |= nsIAuthInformation::CROSS_ORIGIN_SUB_RESOURCE;
+ }
+
+ RefPtr<nsHTTPAuthInformation> holder =
+ new nsHTTPAuthInformation(promptFlags, realmU, authType);
+ if (!holder) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(mAuthChannel, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = authPrompt->AsyncPromptAuth(channel, this, nullptr, level, holder,
+ getter_AddRefs(mAsyncPromptAuthCancelable));
+
+ if (NS_SUCCEEDED(rv)) {
+ // indicate using this error code that authentication prompt
+ // result is expected asynchronously
+ rv = NS_ERROR_IN_PROGRESS;
+ } else {
+ // Fall back to synchronous prompt
+ bool retval = false;
+ rv = authPrompt->PromptAuth(channel, level, holder, &retval);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!retval) {
+ rv = NS_ERROR_ABORT;
+ } else {
+ holder->SetToHttpAuthIdentity(authFlags, ident);
+ }
+ }
+
+ // remember that we successfully showed the user an auth dialog
+ if (!proxyAuth) mSuppressDefensiveAuth = true;
+
+ if (mConnectionBased) {
+ // Connection can be reset by the server in the meantime user is entering
+ // the credentials. Result would be just a "Connection was reset" error.
+ // Hence, we drop the current regardless if the user would make it on time
+ // to provide credentials.
+ // It's OK to send the NTLM type 1 message (response to the plain "NTLM"
+ // challenge) on a new connection.
+ {
+ DebugOnly<nsresult> rv = mAuthChannel->CloseStickyConnection();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthAvailable(
+ nsISupports* aContext, nsIAuthInformation* aAuthInfo) {
+ LOG(("nsHttpChannelAuthProvider::OnAuthAvailable [this=%p channel=%p]", this,
+ mAuthChannel));
+
+ mAsyncPromptAuthCancelable = nullptr;
+ if (!mAuthChannel) return NS_OK;
+
+ nsresult rv;
+
+ nsAutoCString host;
+ int32_t port;
+ nsHttpAuthIdentity* ident;
+ nsAutoCString path, scheme;
+ nsISupports** continuationState;
+ rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, path, ident,
+ continuationState);
+ if (NS_FAILED(rv)) OnAuthCancelled(aContext, false);
+
+ nsAutoCString realm;
+ ParseRealm(mCurrentChallenge, realm);
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsAutoCString suffix;
+ if (!mProxyAuth) {
+ // Fill only for non-proxy auth, proxy credentials are not OA-isolated.
+ GetOriginAttributesSuffix(chan, suffix);
+ }
+
+ nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
+ nsHttpAuthEntry* entry = nullptr;
+ Unused << authCache->GetAuthEntryForDomain(scheme, host, port, realm, suffix,
+ &entry);
+
+ nsCOMPtr<nsISupports> sessionStateGrip;
+ if (entry) sessionStateGrip = entry->mMetaData;
+
+ nsAuthInformationHolder* holder =
+ static_cast<nsAuthInformationHolder*>(aAuthInfo);
+ *ident =
+ nsHttpAuthIdentity(holder->Domain(), holder->User(), holder->Password());
+
+ nsAutoCString unused;
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ rv = GetAuthenticator(mCurrentChallenge, unused, getter_AddRefs(auth));
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false, "GetAuthenticator failed");
+ OnAuthCancelled(aContext, true);
+ return NS_OK;
+ }
+
+ nsCString creds;
+ rv = GenCredsAndSetEntry(auth, mProxyAuth, scheme, host, port, path, realm,
+ mCurrentChallenge, *ident, sessionStateGrip, creds);
+
+ mCurrentChallenge.Truncate();
+ if (NS_FAILED(rv)) {
+ OnAuthCancelled(aContext, true);
+ return NS_OK;
+ }
+
+ return ContinueOnAuthAvailable(creds);
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthCancelled(nsISupports* aContext,
+ bool userCancel) {
+ LOG(("nsHttpChannelAuthProvider::OnAuthCancelled [this=%p channel=%p]", this,
+ mAuthChannel));
+
+ mAsyncPromptAuthCancelable = nullptr;
+ if (!mAuthChannel) return NS_OK;
+
+ // When user cancels or auth fails we want to close the connection for
+ // connection based schemes like NTLM. Some servers don't like re-negotiation
+ // on the same connection.
+ nsresult rv;
+ if (mConnectionBased) {
+ rv = mAuthChannel->CloseStickyConnection();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mConnectionBased = false;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(mAuthChannel);
+ if (channel) {
+ nsresult status;
+ Unused << channel->GetStatus(&status);
+ if (NS_FAILED(status)) {
+ // If the channel is already cancelled, there is no need to deal with the
+ // rest challenges.
+ LOG((" Clear mRemainingChallenges, since mAuthChannel is cancelled"));
+ mRemainingChallenges.Truncate();
+ }
+ }
+
+ if (userCancel) {
+ if (!mRemainingChallenges.IsEmpty()) {
+ // there are still some challenges to process, do so
+
+ // Get rid of current continuationState to avoid reusing it in
+ // next challenges since it is no longer relevant.
+ if (mProxyAuth) {
+ NS_IF_RELEASE(mProxyAuthContinuationState);
+ } else {
+ NS_IF_RELEASE(mAuthContinuationState);
+ }
+ nsAutoCString creds;
+ rv = GetCredentials(mRemainingChallenges, mProxyAuth, creds);
+ if (NS_SUCCEEDED(rv)) {
+ // GetCredentials loaded the credentials from the cache or
+ // some other way in a synchronous manner, process those
+ // credentials now
+ mRemainingChallenges.Truncate();
+ return ContinueOnAuthAvailable(creds);
+ }
+ if (rv == NS_ERROR_IN_PROGRESS) {
+ // GetCredentials successfully queued another authprompt for
+ // a challenge from the list, we are now waiting for the user
+ // to provide the credentials
+ return NS_OK;
+ }
+
+ // otherwise, we failed...
+ }
+
+ mRemainingChallenges.Truncate();
+ }
+
+ rv = mAuthChannel->OnAuthCancelled(userCancel);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::OnCredsGenerated(
+ const nsACString& aGeneratedCreds, uint32_t aFlags, nsresult aResult,
+ nsISupports* aSessionState, nsISupports* aContinuationState) {
+ nsresult rv;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // When channel is closed, do not proceed
+ if (!mAuthChannel) {
+ return NS_OK;
+ }
+
+ mGenerateCredentialsCancelable = nullptr;
+
+ if (NS_FAILED(aResult)) {
+ return OnAuthCancelled(nullptr, true);
+ }
+
+ // We want to update m(Proxy)AuthContinuationState in case it was changed by
+ // nsHttpNegotiateAuth::GenerateCredentials
+ nsCOMPtr<nsISupports> contState(aContinuationState);
+ if (mProxyAuth) {
+ contState.swap(mProxyAuthContinuationState);
+ } else {
+ contState.swap(mAuthContinuationState);
+ }
+
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsAutoCString unused;
+ rv = GetAuthenticator(mCurrentChallenge, unused, getter_AddRefs(auth));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString host;
+ int32_t port;
+ nsHttpAuthIdentity* ident;
+ nsAutoCString directory, scheme;
+ nsISupports** unusedContinuationState;
+
+ // Get realm from challenge
+ nsAutoCString realm;
+ ParseRealm(mCurrentChallenge, realm);
+
+ rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, directory, ident,
+ unusedContinuationState);
+ if (NS_FAILED(rv)) return rv;
+
+ rv =
+ UpdateCache(auth, scheme, host, port, directory, realm, mCurrentChallenge,
+ *ident, aGeneratedCreds, aFlags, aSessionState, mProxyAuth);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mCurrentChallenge.Truncate();
+
+ rv = ContinueOnAuthAvailable(aGeneratedCreds);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_OK;
+}
+
+nsresult nsHttpChannelAuthProvider::ContinueOnAuthAvailable(
+ const nsACString& creds) {
+ nsresult rv;
+ if (mProxyAuth) {
+ rv = mAuthChannel->SetProxyCredentials(creds);
+ } else {
+ rv = mAuthChannel->SetWWWCredentials(creds);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ // drop our remaining list of challenges. We don't need them, because we
+ // have now authenticated against a challenge and will be sending that
+ // information to the server (or proxy). If it doesn't accept our
+ // authentication it'll respond with failure and resend the challenge list
+ mRemainingChallenges.Truncate();
+
+ Unused << mAuthChannel->OnAuthAvailable();
+
+ return NS_OK;
+}
+
+bool nsHttpChannelAuthProvider::ConfirmAuth(const char* bundleKey,
+ bool doYesNoPrompt) {
+ // skip prompting the user if
+ // 1) prompts are disabled by pref
+ // 2) we've already prompted the user
+ // 3) we're not a toplevel channel
+ // 4) the userpass length is less than the "phishy" threshold
+
+ if (!StaticPrefs::network_auth_confirmAuth_enabled()) {
+ return true;
+ }
+
+ uint32_t loadFlags;
+ nsresult rv = mAuthChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv)) return true;
+
+ if (mSuppressDefensiveAuth ||
+ !(loadFlags & nsIChannel::LOAD_INITIAL_DOCUMENT_URI)) {
+ return true;
+ }
+
+ nsAutoCString userPass;
+ rv = mURI->GetUserPass(userPass);
+ if (NS_FAILED(rv) ||
+ (userPass.Length() < gHttpHandler->PhishyUserPassLength())) {
+ return true;
+ }
+
+ // we try to confirm by prompting the user. if we cannot do so, then
+ // assume the user said ok. this is done to keep things working in
+ // embedded builds, where the string bundle might not be present, etc.
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ if (!bundleService) return true;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
+ if (!bundle) return true;
+
+ nsAutoCString host;
+ rv = mURI->GetHost(host);
+ if (NS_FAILED(rv)) return true;
+
+ nsAutoCString user;
+ rv = mURI->GetUsername(user);
+ if (NS_FAILED(rv)) return true;
+
+ NS_ConvertUTF8toUTF16 ucsHost(host), ucsUser(user);
+
+ size_t userLength = ucsUser.Length();
+ if (userLength > MAX_DISPLAYED_USER_LENGTH) {
+ size_t desiredLength = MAX_DISPLAYED_USER_LENGTH;
+ // Don't cut off right before a low surrogate. Just include it.
+ if (NS_IS_LOW_SURROGATE(ucsUser[desiredLength])) {
+ desiredLength++;
+ }
+ ucsUser.Replace(desiredLength, userLength - desiredLength,
+ nsContentUtils::GetLocalizedEllipsis());
+ }
+
+ size_t hostLen = ucsHost.Length();
+ if (hostLen > MAX_DISPLAYED_HOST_LENGTH) {
+ size_t cutPoint = hostLen - MAX_DISPLAYED_HOST_LENGTH;
+ // Likewise, don't cut off right before a low surrogate here.
+ // Keep the low surrogate
+ if (NS_IS_LOW_SURROGATE(ucsHost[cutPoint])) {
+ cutPoint--;
+ }
+ // It's possible cutPoint was 1 and is now 0. Only insert the ellipsis
+ // if we're actually removing anything.
+ if (cutPoint > 0) {
+ ucsHost.Replace(0, cutPoint, nsContentUtils::GetLocalizedEllipsis());
+ }
+ }
+
+ AutoTArray<nsString, 2> strs = {ucsHost, ucsUser};
+
+ nsAutoString msg;
+ rv = bundle->FormatStringFromName(bundleKey, strs, msg);
+ if (NS_FAILED(rv)) return true;
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (NS_FAILED(rv)) return true;
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (NS_FAILED(rv)) return true;
+
+ nsCOMPtr<nsIPromptService> promptSvc =
+ do_GetService("@mozilla.org/prompter;1", &rv);
+ if (NS_FAILED(rv) || !promptSvc) {
+ return true;
+ }
+
+ // do not prompt again
+ mSuppressDefensiveAuth = true;
+
+ // Get current browsing context to use as prompt parent
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ if (!chan) {
+ return true;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+ RefPtr<mozilla::dom::BrowsingContext> browsingContext;
+ loadInfo->GetBrowsingContext(getter_AddRefs(browsingContext));
+
+ bool confirmed;
+ if (doYesNoPrompt) {
+ int32_t choice;
+ bool checkState = false;
+ rv = promptSvc->ConfirmExBC(
+ browsingContext, StaticPrefs::prompts_modalType_confirmAuth(), nullptr,
+ msg.get(),
+ nsIPromptService::BUTTON_POS_1_DEFAULT +
+ nsIPromptService::STD_YES_NO_BUTTONS,
+ nullptr, nullptr, nullptr, nullptr, &checkState, &choice);
+ if (NS_FAILED(rv)) return true;
+
+ confirmed = choice == 0;
+ } else {
+ rv = promptSvc->ConfirmBC(browsingContext,
+ StaticPrefs::prompts_modalType_confirmAuth(),
+ nullptr, msg.get(), &confirmed);
+ if (NS_FAILED(rv)) return true;
+ }
+
+ return confirmed;
+}
+
+void nsHttpChannelAuthProvider::SetAuthorizationHeader(
+ nsHttpAuthCache* authCache, const nsHttpAtom& header,
+ const nsACString& scheme, const nsACString& host, int32_t port,
+ const nsACString& path, nsHttpAuthIdentity& ident) {
+ nsHttpAuthEntry* entry = nullptr;
+ nsresult rv;
+
+ // set informations that depend on whether
+ // we're authenticating against a proxy
+ // or a webserver
+ nsISupports** continuationState;
+
+ nsAutoCString suffix;
+ if (header == nsHttp::Proxy_Authorization) {
+ continuationState = &mProxyAuthContinuationState;
+
+ if (mProxyInfo) {
+ nsAutoCString type;
+ mProxyInfo->GetType(type);
+ if (type.EqualsLiteral("https")) {
+ // Let this be overriden by anything from the cache.
+ auto const& pa = mProxyInfo->ProxyAuthorizationHeader();
+ if (!pa.IsEmpty()) {
+ rv = mAuthChannel->SetProxyCredentials(pa);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+ }
+ } else {
+ continuationState = &mAuthContinuationState;
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ GetOriginAttributesSuffix(chan, suffix);
+ }
+
+ rv = authCache->GetAuthEntryForPath(scheme, host, port, path, suffix, &entry);
+ if (NS_SUCCEEDED(rv)) {
+ // if we are trying to add a header for origin server auth and if the
+ // URL contains an explicit username, then try the given username first.
+ // we only want to do this, however, if we know the URL requires auth
+ // based on the presence of an auth cache entry for this URL (which is
+ // true since we are here). but, if the username from the URL matches
+ // the username from the cache, then we should prefer the password
+ // stored in the cache since that is most likely to be valid.
+ if (header == nsHttp::Authorization && entry->Domain()[0] == '\0') {
+ GetIdentityFromURI(0, ident);
+ // if the usernames match, then clear the ident so we will pick
+ // up the one from the auth cache instead.
+ // when this is undesired, specify LOAD_EXPLICIT_CREDENTIALS load
+ // flag.
+ if (ident.User() == entry->User()) {
+ uint32_t loadFlags;
+ if (NS_SUCCEEDED(mAuthChannel->GetLoadFlags(&loadFlags)) &&
+ !(loadFlags & nsIChannel::LOAD_EXPLICIT_CREDENTIALS)) {
+ ident.Clear();
+ }
+ }
+ }
+ bool identFromURI;
+ if (ident.IsEmpty()) {
+ ident = entry->Identity();
+ identFromURI = false;
+ } else {
+ identFromURI = true;
+ }
+
+ nsCString temp; // this must have the same lifetime as creds
+ nsAutoCString creds(entry->Creds());
+ // we can only send a preemptive Authorization header if we have either
+ // stored credentials or a stored challenge from which to derive
+ // credentials. if the identity is from the URI, then we cannot use
+ // the stored credentials.
+ if ((creds.IsEmpty() || identFromURI) && !entry->Challenge().IsEmpty()) {
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsAutoCString unused;
+ rv = GetAuthenticator(entry->Challenge(), unused, getter_AddRefs(auth));
+ if (NS_SUCCEEDED(rv)) {
+ bool proxyAuth = (header == nsHttp::Proxy_Authorization);
+ rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, path,
+ entry->Realm(), entry->Challenge(), ident,
+ entry->mMetaData, temp);
+ if (NS_SUCCEEDED(rv)) creds = temp;
+
+ // make sure the continuation state is null since we do not
+ // support mixing preemptive and 'multirequest' authentication.
+ NS_IF_RELEASE(*continuationState);
+ }
+ }
+ if (!creds.IsEmpty()) {
+ LOG((" adding \"%s\" request header\n", header.get()));
+ if (header == nsHttp::Proxy_Authorization) {
+ rv = mAuthChannel->SetProxyCredentials(creds);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ } else {
+ rv = mAuthChannel->SetWWWCredentials(creds);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // suppress defensive auth prompting for this channel since we know
+ // that we already prompted at least once this session. we only do
+ // this for non-proxy auth since the URL's userpass is not used for
+ // proxy auth.
+ if (header == nsHttp::Authorization) mSuppressDefensiveAuth = true;
+ } else {
+ ident.Clear(); // don't remember the identity
+ }
+ }
+}
+
+nsresult nsHttpChannelAuthProvider::GetCurrentPath(nsACString& path) {
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
+ if (url) {
+ rv = url->GetDirectory(path);
+ } else {
+ rv = mURI->GetPathQueryRef(path);
+ }
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsHttpChannelAuthProvider, nsICancelable,
+ nsIHttpChannelAuthProvider, nsIAuthPromptCallback,
+ nsIHttpAuthenticatorCallback)
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/nsHttpChannelAuthProvider.h b/netwerk/protocol/http/nsHttpChannelAuthProvider.h
new file mode 100644
index 0000000000..22f9a27ede
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.h
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et cin ts=4 sw=2 sts=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpChannelAuthProvider_h__
+#define nsHttpChannelAuthProvider_h__
+
+#include "nsIHttpChannelAuthProvider.h"
+#include "nsIAuthPromptCallback.h"
+#include "nsIHttpAuthenticatorCallback.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsHttpAuthCache.h"
+#include "nsProxyInfo.h"
+#include "nsICancelable.h"
+
+class nsIHttpAuthenticableChannel;
+class nsIHttpAuthenticator;
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpHandler;
+struct nsHttpAtom;
+
+class nsHttpChannelAuthProvider final : public nsIHttpChannelAuthProvider,
+ public nsIAuthPromptCallback,
+ public nsIHttpAuthenticatorCallback {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+ NS_DECL_NSIHTTPCHANNELAUTHPROVIDER
+ NS_DECL_NSIAUTHPROMPTCALLBACK
+ NS_DECL_NSIHTTPAUTHENTICATORCALLBACK
+
+ nsHttpChannelAuthProvider();
+
+ private:
+ virtual ~nsHttpChannelAuthProvider();
+
+ const nsCString& ProxyHost() const {
+ return mProxyInfo ? mProxyInfo->Host() : EmptyCString();
+ }
+
+ int32_t ProxyPort() const { return mProxyInfo ? mProxyInfo->Port() : -1; }
+
+ const nsCString& Host() const { return mHost; }
+ int32_t Port() const { return mPort; }
+ bool UsingSSL() const { return mUsingSSL; }
+
+ bool UsingHttpProxy() const {
+ return mProxyInfo && (mProxyInfo->IsHTTP() || mProxyInfo->IsHTTPS());
+ }
+
+ [[nodiscard]] nsresult PrepareForAuthentication(bool proxyAuth);
+ [[nodiscard]] nsresult GenCredsAndSetEntry(
+ nsIHttpAuthenticator*, bool proxyAuth, const nsACString& scheme,
+ const nsACString& host, int32_t port, const nsACString& dir,
+ const nsACString& realm, const nsACString& challenge,
+ const nsHttpAuthIdentity& ident, nsCOMPtr<nsISupports>& session,
+ nsACString& result);
+ [[nodiscard]] nsresult GetAuthenticator(const nsACString& aChallenge,
+ nsCString& authType,
+ nsIHttpAuthenticator** auth);
+ void ParseRealm(const nsACString&, nsACString& realm);
+ void GetIdentityFromURI(uint32_t authFlags, nsHttpAuthIdentity&);
+
+ /**
+ * Following three methods return NS_ERROR_IN_PROGRESS when
+ * nsIAuthPrompt2.asyncPromptAuth method is called. This result indicates
+ * the user's decision will be gathered in a callback and is not an actual
+ * error.
+ */
+ [[nodiscard]] nsresult GetCredentials(const nsACString& challenges,
+ bool proxyAuth, nsCString& creds);
+ [[nodiscard]] nsresult GetCredentialsForChallenge(
+ const nsACString& aChallenge, const nsACString& aAuthType, bool proxyAuth,
+ nsIHttpAuthenticator* auth, nsCString& creds);
+ [[nodiscard]] nsresult PromptForIdentity(uint32_t level, bool proxyAuth,
+ const nsACString& realm,
+ const nsACString& authType,
+ uint32_t authFlags,
+ nsHttpAuthIdentity&);
+
+ bool ConfirmAuth(const char* bundleKey, bool doYesNoPrompt);
+ void SetAuthorizationHeader(nsHttpAuthCache*, const nsHttpAtom& header,
+ const nsACString& scheme, const nsACString& host,
+ int32_t port, const nsACString& path,
+ nsHttpAuthIdentity& ident);
+ [[nodiscard]] nsresult GetCurrentPath(nsACString&);
+ /**
+ * Return all information needed to build authorization information,
+ * all parameters except proxyAuth are out parameters. proxyAuth specifies
+ * with what authorization we work (WWW or proxy).
+ */
+ [[nodiscard]] nsresult GetAuthorizationMembers(
+ bool proxyAuth, nsACString& scheme, nsCString& host, int32_t& port,
+ nsACString& path, nsHttpAuthIdentity*& ident,
+ nsISupports**& continuationState);
+ /**
+ * Method called to resume suspended transaction after we got credentials
+ * from the user. Called from OnAuthAvailable callback or OnAuthCancelled
+ * when credentials for next challenge were obtained synchronously.
+ */
+ [[nodiscard]] nsresult ContinueOnAuthAvailable(const nsACString& creds);
+
+ [[nodiscard]] nsresult DoRedirectChannelToHttps();
+
+ /**
+ * A function that takes care of reading STS headers and enforcing STS
+ * load rules. After a secure channel is erected, STS requires the channel
+ * to be trusted or any STS header data on the channel is ignored.
+ * This is called from ProcessResponse.
+ */
+ [[nodiscard]] nsresult ProcessSTSHeader();
+
+ // Depending on the pref setting, the authentication dialog may be blocked
+ // for all sub-resources, blocked for cross-origin sub-resources, or
+ // always allowed for sub-resources.
+ // For more details look at the bug 647010.
+ bool BlockPrompt(bool proxyAuth);
+
+ // Store credentials to the cache when appropriate aFlags are set.
+ [[nodiscard]] nsresult UpdateCache(
+ nsIHttpAuthenticator* aAuth, const nsACString& aScheme,
+ const nsACString& aHost, int32_t aPort, const nsACString& aDirectory,
+ const nsACString& aRealm, const nsACString& aChallenge,
+ const nsHttpAuthIdentity& aIdent, const nsACString& aCreds,
+ uint32_t aGenerateFlags, nsISupports* aSessionState, bool aProxyAuth);
+
+ private:
+ nsIHttpAuthenticableChannel* mAuthChannel{nullptr}; // weak ref
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsProxyInfo> mProxyInfo;
+ nsCString mHost;
+ int32_t mPort{-1};
+ bool mUsingSSL{false};
+ bool mProxyUsingSSL{false};
+ bool mIsPrivate{false};
+
+ nsISupports* mProxyAuthContinuationState{nullptr};
+ nsCString mProxyAuthType;
+ nsISupports* mAuthContinuationState{nullptr};
+ nsCString mAuthType;
+ nsHttpAuthIdentity mIdent;
+ nsHttpAuthIdentity mProxyIdent;
+
+ // Reference to the prompt waiting in prompt queue. The channel is
+ // responsible to call its cancel method when user in any way cancels
+ // this request.
+ nsCOMPtr<nsICancelable> mAsyncPromptAuthCancelable;
+ // Saved in GetCredentials when prompt is asynchronous, the first challenge
+ // we obtained from the server with 401/407 response, will be processed in
+ // OnAuthAvailable callback.
+ nsCString mCurrentChallenge;
+ // Saved in GetCredentials when prompt is asynchronous, remaning challenges
+ // we have to process when user cancels the auth dialog for the current
+ // challenge.
+ nsCString mRemainingChallenges;
+
+ // True when we need to authenticate to proxy, i.e. when we get 407
+ // response. Used in OnAuthAvailable and OnAuthCancelled callbacks.
+ uint32_t mProxyAuth : 1;
+ uint32_t mTriedProxyAuth : 1;
+ uint32_t mTriedHostAuth : 1;
+ uint32_t mSuppressDefensiveAuth : 1;
+
+ // If a cross-origin sub-resource is being loaded, this flag will be set.
+ // In that case, the prompt text will be different to warn users.
+ uint32_t mCrossOrigin : 1;
+ uint32_t mConnectionBased : 1;
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+
+ nsCOMPtr<nsICancelable> mGenerateCredentialsCancelable;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpChannelAuthProvider_h__
diff --git a/netwerk/protocol/http/nsHttpChunkedDecoder.cpp b/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
new file mode 100644
index 0000000000..5ee8abc088
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include <errno.h>
+#include "nsHttpChunkedDecoder.h"
+#include <algorithm>
+#include <string.h>
+
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpChunkedDecoder <public>
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpChunkedDecoder::HandleChunkedContent(
+ char* buf, uint32_t count, uint32_t* contentRead,
+ uint32_t* contentRemaining) {
+ LOG(("nsHttpChunkedDecoder::HandleChunkedContent [count=%u]\n", count));
+
+ *contentRead = 0;
+
+ // from RFC2616 section 3.6.1, the chunked transfer coding is defined as:
+ //
+ // Chunked-Body = *chunk
+ // last-chunk
+ // trailer
+ // CRLF
+ // chunk = chunk-size [ chunk-extension ] CRLF
+ // chunk-data CRLF
+ // chunk-size = 1*HEX
+ // last-chunk = 1*("0") [ chunk-extension ] CRLF
+ //
+ // chunk-extension = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
+ // chunk-ext-name = token
+ // chunk-ext-val = token | quoted-string
+ // chunk-data = chunk-size(OCTET)
+ // trailer = *(entity-header CRLF)
+ //
+ // the chunk-size field is a string of hex digits indicating the size of the
+ // chunk. the chunked encoding is ended by any chunk whose size is zero,
+ // followed by the trailer, which is terminated by an empty line.
+
+ while (count) {
+ if (mChunkRemaining) {
+ uint32_t amt = std::min(mChunkRemaining, count);
+
+ count -= amt;
+ mChunkRemaining -= amt;
+
+ *contentRead += amt;
+ buf += amt;
+ } else if (mReachedEOF) {
+ break; // done
+ } else {
+ uint32_t bytesConsumed = 0;
+
+ nsresult rv = ParseChunkRemaining(buf, count, &bytesConsumed);
+ if (NS_FAILED(rv)) return rv;
+
+ count -= bytesConsumed;
+
+ if (count) {
+ // shift buf by bytesConsumed
+ memmove(buf, buf + bytesConsumed, count);
+ }
+ }
+ }
+
+ *contentRemaining = count;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChunkedDecoder <private>
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpChunkedDecoder::ParseChunkRemaining(char* buf, uint32_t count,
+ uint32_t* bytesConsumed) {
+ MOZ_ASSERT(mChunkRemaining == 0, "chunk remaining should be zero");
+ MOZ_ASSERT(count, "unexpected");
+
+ *bytesConsumed = 0;
+
+ char* p = static_cast<char*>(memchr(buf, '\n', count));
+ if (p) {
+ *p = 0;
+ count = p - buf; // new length
+ *bytesConsumed = count + 1; // length + newline
+ if ((p > buf) && (*(p - 1) == '\r')) { // eliminate a preceding CR
+ *(p - 1) = 0;
+ count--;
+ }
+
+ // make buf point to the full line buffer to parse
+ if (!mLineBuf.IsEmpty()) {
+ mLineBuf.Append(buf, count);
+ buf = (char*)mLineBuf.get();
+ count = mLineBuf.Length();
+ }
+
+ if (mWaitEOF) {
+ if (*buf) {
+ LOG(("got trailer: %s\n", buf));
+ // allocate a header array for the trailers on demand
+ if (!mTrailers) {
+ mTrailers = MakeUnique<nsHttpHeaderArray>();
+ }
+
+ nsHttpAtom hdr;
+ nsAutoCString headerNameOriginal;
+ nsAutoCString val;
+ if (NS_SUCCEEDED(
+ mTrailers->ParseHeaderLine(nsDependentCSubstring(buf, count),
+ &hdr, &headerNameOriginal, &val))) {
+ if (hdr == nsHttp::Server_Timing) {
+ Unused << mTrailers->SetHeaderFromNet(hdr, headerNameOriginal, val,
+ true);
+ }
+ }
+ } else {
+ mWaitEOF = false;
+ mReachedEOF = true;
+ LOG(("reached end of chunked-body\n"));
+ }
+ } else if (*buf) {
+ char* endptr;
+ unsigned long parsedval; // could be 64 bit, could be 32
+
+ // ignore any chunk-extensions
+ if ((p = strchr(buf, ';')) != nullptr) {
+ *p = 0;
+ }
+
+ // mChunkRemaining is an uint32_t!
+ parsedval = strtoul(buf, &endptr, 16);
+ mChunkRemaining = (uint32_t)parsedval;
+
+ if ((endptr == buf) || ((errno == ERANGE) && (parsedval == ULONG_MAX)) ||
+ (parsedval != mChunkRemaining)) {
+ LOG(("failed parsing hex on string [%s]\n", buf));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // we've discovered the last chunk
+ if (mChunkRemaining == 0) mWaitEOF = true;
+ }
+
+ // ensure that the line buffer is clear
+ mLineBuf.Truncate();
+ } else {
+ // save the partial line; wait for more data
+ *bytesConsumed = count;
+ // ignore a trailing CR
+ if (buf[count - 1] == '\r') count--;
+ mLineBuf.Append(buf, count);
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpChunkedDecoder.h b/netwerk/protocol/http/nsHttpChunkedDecoder.h
new file mode 100644
index 0000000000..0e627087dc
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpChunkedDecoder_h__
+#define nsHttpChunkedDecoder_h__
+
+#include "nsError.h"
+#include "nsString.h"
+#include "nsHttpHeaderArray.h"
+
+namespace mozilla {
+namespace net {
+
+class nsHttpChunkedDecoder {
+ public:
+ nsHttpChunkedDecoder() = default;
+ ~nsHttpChunkedDecoder() = default;
+
+ bool ReachedEOF() { return mReachedEOF; }
+
+ // called by the transaction to handle chunked content.
+ [[nodiscard]] nsresult HandleChunkedContent(char* buf, uint32_t count,
+ uint32_t* contentRead,
+ uint32_t* contentRemaining);
+
+ nsHttpHeaderArray* Trailers() { return mTrailers.get(); }
+
+ UniquePtr<nsHttpHeaderArray> TakeTrailers() { return std::move(mTrailers); }
+
+ // How mush data is still missing(needs to arrive) from the current chunk.
+ uint32_t GetChunkRemaining() { return mChunkRemaining; }
+
+ private:
+ [[nodiscard]] nsresult ParseChunkRemaining(char* buf, uint32_t count,
+ uint32_t* bytesConsumed);
+
+ private:
+ UniquePtr<nsHttpHeaderArray> mTrailers;
+ uint32_t mChunkRemaining{0};
+ nsCString mLineBuf; // may hold a partial line
+ bool mReachedEOF{false};
+ bool mWaitEOF{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp
new file mode 100644
index 0000000000..023c06f3f3
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -0,0 +1,2609 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "ASpdySession.h"
+#include "NSSErrorsService.h"
+#include "TLSTransportLayer.h"
+#include "mozilla/ChaosMode.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "mozpkix/pkixnss.h"
+#include "nsCRT.h"
+#include "nsHttpConnection.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+#include "nsIClassOfService.h"
+#include "nsIOService.h"
+#include "nsISocketTransport.h"
+#include "nsISupportsPriority.h"
+#include "nsITLSSocketControl.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsPreloadedStream.h"
+#include "nsProxyRelease.h"
+#include "nsQueryObject.h"
+#include "nsSocketTransport2.h"
+#include "nsSocketTransportService2.h"
+#include "nsStringStream.h"
+#include "sslerr.h"
+#include "sslt.h"
+
+namespace mozilla::net {
+
+enum TlsHandshakeResult : uint32_t {
+ EchConfigSuccessful = 0,
+ EchConfigFailed,
+ NoEchConfigSuccessful,
+ NoEchConfigFailed,
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection <public>
+//-----------------------------------------------------------------------------
+
+nsHttpConnection::nsHttpConnection() : mHttpHandler(gHttpHandler) {
+ LOG(("Creating nsHttpConnection @%p\n", this));
+
+ // the default timeout is for when this connection has not yet processed a
+ // transaction
+ static const PRIntervalTime k5Sec = PR_SecondsToInterval(5);
+ mIdleTimeout = (k5Sec < gHttpHandler->IdleTimeout())
+ ? k5Sec
+ : gHttpHandler->IdleTimeout();
+
+ mThroughCaptivePortal = gHttpHandler->GetThroughCaptivePortal();
+}
+
+nsHttpConnection::~nsHttpConnection() {
+ LOG(("Destroying nsHttpConnection @%p\n", this));
+
+ if (!mEverUsedSpdy) {
+ LOG(("nsHttpConnection %p performed %d HTTP/1.x transactions\n", this,
+ mHttp1xTransactionCount));
+ Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_CONN,
+ mHttp1xTransactionCount);
+ nsHttpConnectionInfo* ci = nullptr;
+ if (mTransaction) {
+ ci = mTransaction->ConnectionInfo();
+ }
+ if (!ci) {
+ ci = mConnInfo;
+ }
+
+ MOZ_ASSERT(ci);
+ if (ci->GetIsTrrServiceChannel()) {
+ Telemetry::Accumulate(Telemetry::DNS_TRR_REQUEST_PER_CONN,
+ mHttp1xTransactionCount);
+ }
+ }
+
+ if (mTotalBytesRead) {
+ uint32_t totalKBRead = static_cast<uint32_t>(mTotalBytesRead >> 10);
+ LOG(("nsHttpConnection %p read %dkb on connection spdy=%d\n", this,
+ totalKBRead, mEverUsedSpdy));
+ Telemetry::Accumulate(mEverUsedSpdy ? Telemetry::SPDY_KBREAD_PER_CONN2
+ : Telemetry::HTTP_KBREAD_PER_CONN2,
+ totalKBRead);
+ }
+
+ if (mThroughCaptivePortal) {
+ if (mTotalBytesRead || mTotalBytesWritten) {
+ auto total =
+ Clamp<uint32_t>((mTotalBytesRead >> 10) + (mTotalBytesWritten >> 10),
+ 0, std::numeric_limits<uint32_t>::max());
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_DATA_TRANSFERRED_CAPTIVE_PORTAL,
+ total);
+ }
+
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_HTTP_CONNECTIONS_CAPTIVE_PORTAL, 1);
+ }
+
+ if (mForceSendTimer) {
+ mForceSendTimer->Cancel();
+ mForceSendTimer = nullptr;
+ }
+
+ auto ReleaseSocketTransport =
+ [socketTransport(std::move(mSocketTransport))]() mutable {
+ socketTransport = nullptr;
+ };
+ if (OnSocketThread()) {
+ ReleaseSocketTransport();
+ } else {
+ gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "nsHttpConnection::~nsHttpConnection", ReleaseSocketTransport));
+ }
+}
+
+nsresult nsHttpConnection::Init(
+ nsHttpConnectionInfo* info, uint16_t maxHangTime,
+ nsISocketTransport* transport, nsIAsyncInputStream* instream,
+ nsIAsyncOutputStream* outstream, bool connectedTransport, nsresult status,
+ nsIInterfaceRequestor* callbacks, PRIntervalTime rtt, bool forWebSocket) {
+ LOG1(("nsHttpConnection::Init this=%p sockettransport=%p forWebSocket=%d",
+ this, transport, forWebSocket));
+ NS_ENSURE_ARG_POINTER(info);
+ NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
+ MOZ_ASSERT(NS_SUCCEEDED(status) || !connectedTransport);
+
+ mConnectedTransport = connectedTransport;
+ mConnInfo = info;
+ MOZ_ASSERT(mConnInfo);
+
+ mLastWriteTime = mLastReadTime = PR_IntervalNow();
+ mRtt = rtt;
+ mMaxHangTime = PR_SecondsToInterval(maxHangTime);
+
+ mSocketTransport = transport;
+ mSocketIn = instream;
+ mSocketOut = outstream;
+ mForWebSocket = forWebSocket;
+
+ // See explanation for non-strictness of this operation in
+ // SetSecurityCallbacks.
+ mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(
+ "nsHttpConnection::mCallbacks", callbacks, false);
+
+ mErrorBeforeConnect = status;
+ if (NS_SUCCEEDED(mErrorBeforeConnect)) {
+ mSocketTransport->SetEventSink(this, nullptr);
+ mSocketTransport->SetSecurityCallbacks(this);
+ ChangeConnectionState(ConnectionState::INITED);
+ } else {
+ SetCloseReason(ToCloseReason(mErrorBeforeConnect));
+ }
+
+ mTlsHandshaker = new TlsHandshaker(mConnInfo, this);
+ return NS_OK;
+}
+
+void nsHttpConnection::ChangeState(HttpConnectionState newState) {
+ LOG(("nsHttpConnection::ChangeState %d -> %d [this=%p]", mState, newState,
+ this));
+ mState = newState;
+}
+
+nsresult nsHttpConnection::TryTakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> >& list) {
+ nsresult rv = mTransaction->TakeSubTransactions(list);
+
+ if (rv == NS_ERROR_ALREADY_OPENED) {
+ // Has the interface for TakeSubTransactions() changed?
+ LOG(
+ ("TakeSubTransactions somehow called after "
+ "nsAHttpTransaction began processing\n"));
+ MOZ_ASSERT(false,
+ "TakeSubTransactions somehow called after "
+ "nsAHttpTransaction began processing");
+ mTransaction->Close(NS_ERROR_ABORT);
+ return rv;
+ }
+
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
+ // Has the interface for TakeSubTransactions() changed?
+ LOG(("unexpected rv from nnsAHttpTransaction::TakeSubTransactions()"));
+ MOZ_ASSERT(false,
+ "unexpected result from "
+ "nsAHttpTransaction::TakeSubTransactions()");
+ mTransaction->Close(NS_ERROR_ABORT);
+ return rv;
+ }
+
+ return rv;
+}
+
+void nsHttpConnection::ResetTransaction(RefPtr<nsAHttpTransaction>&& trans) {
+ MOZ_ASSERT(trans);
+ mSpdySession->SetConnection(trans->Connection());
+ trans->SetConnection(nullptr);
+ trans->DoNotRemoveAltSvc();
+ trans->Close(NS_ERROR_NET_RESET);
+}
+
+nsresult nsHttpConnection::MoveTransactionsToSpdy(
+ nsresult status, nsTArray<RefPtr<nsAHttpTransaction> >& list) {
+ if (NS_FAILED(status)) { // includes NS_ERROR_NOT_IMPLEMENTED
+ MOZ_ASSERT(list.IsEmpty(), "sub transaction list not empty");
+
+ // If this transaction is used to drive websocket, we reset it to put it in
+ // the pending queue. Once we know if the server supports websocket or not,
+ // the pending queue will be processed.
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (trans && trans->IsWebsocketUpgrade()) {
+ LOG(("nsHttpConnection resetting transaction for websocket upgrade"));
+ // websocket upgrade needs NonSticky for transaction reset
+ mTransaction->MakeNonSticky();
+ ResetTransaction(std::move(mTransaction));
+ mTransaction = nullptr;
+ return NS_OK;
+ }
+
+ // This is ok - treat mTransaction as a single real request.
+ // Wrap the old http transaction into the new spdy session
+ // as the first stream.
+ LOG(
+ ("nsHttpConnection::MoveTransactionsToSpdy moves single transaction %p "
+ "into SpdySession %p\n",
+ mTransaction.get(), mSpdySession.get()));
+ nsresult rv = AddTransaction(mTransaction, mPriority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ int32_t count = list.Length();
+
+ LOG(
+ ("nsHttpConnection::MoveTransactionsToSpdy moving transaction list "
+ "len=%d "
+ "into SpdySession %p\n",
+ count, mSpdySession.get()));
+
+ if (!count) {
+ mTransaction->Close(NS_ERROR_ABORT);
+ return NS_ERROR_ABORT;
+ }
+
+ for (int32_t index = 0; index < count; ++index) {
+ RefPtr<nsAHttpTransaction> transaction = list[index];
+ nsHttpTransaction* trans = transaction->QueryHttpTransaction();
+ if (trans && trans->IsWebsocketUpgrade()) {
+ LOG(("nsHttpConnection resetting a transaction for websocket upgrade"));
+ // websocket upgrade needs NonSticky for transaction reset
+ transaction->MakeNonSticky();
+ ResetTransaction(std::move(transaction));
+ transaction = nullptr;
+ continue;
+ }
+ nsresult rv = AddTransaction(list[index], mPriority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsHttpConnection::Start0RTTSpdy(SpdyVersion spdyVersion) {
+ LOG(("nsHttpConnection::Start0RTTSpdy [this=%p]", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mDid0RTTSpdy = true;
+ mUsingSpdyVersion = spdyVersion;
+ mEverUsedSpdy = true;
+ mSpdySession =
+ ASpdySession::NewSpdySession(spdyVersion, mSocketTransport, true);
+
+ if (mTransaction) {
+ nsTArray<RefPtr<nsAHttpTransaction> > list;
+ nsresult rv = TryTakeSubTransactions(list);
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
+ LOG(
+ ("nsHttpConnection::Start0RTTSpdy [this=%p] failed taking "
+ "subtransactions rv=%" PRIx32,
+ this, static_cast<uint32_t>(rv)));
+ return;
+ }
+
+ rv = MoveTransactionsToSpdy(rv, list);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnection::Start0RTTSpdy [this=%p] failed moving "
+ "transactions rv=%" PRIx32,
+ this, static_cast<uint32_t>(rv)));
+ return;
+ }
+ }
+
+ mTransaction = mSpdySession;
+}
+
+void nsHttpConnection::StartSpdy(nsITLSSocketControl* sslControl,
+ SpdyVersion spdyVersion) {
+ LOG(("nsHttpConnection::StartSpdy [this=%p, mDid0RTTSpdy=%d]\n", this,
+ mDid0RTTSpdy));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mSpdySession || mDid0RTTSpdy);
+
+ mUsingSpdyVersion = spdyVersion;
+ mEverUsedSpdy = true;
+ if (sslControl) {
+ sslControl->SetDenyClientCert(true);
+ }
+
+ if (!mDid0RTTSpdy) {
+ mSpdySession =
+ ASpdySession::NewSpdySession(spdyVersion, mSocketTransport, false);
+ }
+
+ if (!mReportedSpdy) {
+ mReportedSpdy = true;
+ // See bug 1797729.
+ // It's possible that we already have a HTTP/3 connection that can be
+ // coleased with this connection. We should avoid coalescing with the
+ // existing HTTP/3 connection if the transaction doesn't allow to use
+ // HTTP/3.
+ gHttpHandler->ConnMgr()->ReportSpdyConnection(this, true,
+ mTransactionDisallowHttp3);
+ }
+
+ // Setting the connection as reused allows some transactions that fail
+ // with NS_ERROR_NET_RESET to be restarted and SPDY uses that code
+ // to handle clean rejections (such as those that arrived after
+ // a server goaway was generated).
+ mIsReused = true;
+
+ // If mTransaction is a muxed object it might represent
+ // several requests. If so, we need to unpack that and
+ // pack them all into a new spdy session.
+
+ nsTArray<RefPtr<nsAHttpTransaction> > list;
+ nsresult status = NS_OK;
+ if (!mDid0RTTSpdy && mTransaction) {
+ status = TryTakeSubTransactions(list);
+
+ if (NS_FAILED(status) && status != NS_ERROR_NOT_IMPLEMENTED) {
+ return;
+ }
+ }
+
+ if (NeedSpdyTunnel()) {
+ LOG3(
+ ("nsHttpConnection::StartSpdy %p Connecting To a HTTP/2 "
+ "Proxy and Need Connect",
+ this));
+ SetTunnelSetupDone();
+ }
+
+ nsresult rv = NS_OK;
+ bool spdyProxy = mConnInfo->UsingHttpsProxy() && mConnInfo->UsingConnect() &&
+ !mHasTLSTransportLayer;
+ if (spdyProxy) {
+ RefPtr<nsHttpConnectionInfo> wildCardProxyCi;
+ rv = mConnInfo->CreateWildCard(getter_AddRefs(wildCardProxyCi));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ gHttpHandler->ConnMgr()->MoveToWildCardConnEntry(mConnInfo, wildCardProxyCi,
+ this);
+ mConnInfo = wildCardProxyCi;
+ MOZ_ASSERT(mConnInfo);
+ }
+
+ if (!mDid0RTTSpdy && mTransaction) {
+ if (spdyProxy) {
+ if (NS_FAILED(status)) {
+ // proxy upgrade needs Restartable for transaction reset
+ // note that using NonSticky here won't work because it breaks
+ // netwerk/test/unit/test_websocket_server.js - h1 ws with h2 proxy
+ mTransaction->MakeRestartable();
+ ResetTransaction(std::move(mTransaction));
+ mTransaction = nullptr;
+ } else {
+ for (auto trans : list) {
+ if (!mSpdySession->Connection()) {
+ mSpdySession->SetConnection(trans->Connection());
+ }
+ trans->SetConnection(nullptr);
+ trans->DoNotRemoveAltSvc();
+ trans->Close(NS_ERROR_NET_RESET);
+ }
+ }
+ } else {
+ rv = MoveTransactionsToSpdy(status, list);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+ }
+
+ // Disable TCP Keepalives - use SPDY ping instead.
+ rv = DisableTCPKeepalives();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnection::StartSpdy [%p] DisableTCPKeepalives failed "
+ "rv[0x%" PRIx32 "]",
+ this, static_cast<uint32_t>(rv)));
+ }
+
+ mIdleTimeout = gHttpHandler->SpdyTimeout() * mDefaultTimeoutFactor;
+
+ mTransaction = mSpdySession;
+
+ if (mDontReuse) {
+ mSpdySession->DontReuse();
+ }
+}
+
+void nsHttpConnection::PostProcessNPNSetup(bool handshakeSucceeded,
+ bool hasSecurityInfo,
+ bool earlyDataUsed) {
+ if (mTransaction) {
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_TLS_HANDSHAKE_ENDED, 0);
+ }
+
+ // this is happening after the bootstrap was originally written to. so update
+ // it.
+ if (mTransaction && mTransaction->QueryNullTransaction() &&
+ (mBootstrappedTimings.secureConnectionStart.IsNull() ||
+ mBootstrappedTimings.tcpConnectEnd.IsNull())) {
+ mBootstrappedTimings.secureConnectionStart =
+ mTransaction->QueryNullTransaction()->GetSecureConnectionStart();
+ mBootstrappedTimings.tcpConnectEnd =
+ mTransaction->QueryNullTransaction()->GetTcpConnectEnd();
+ }
+
+ if (hasSecurityInfo) {
+ mBootstrappedTimings.connectEnd = TimeStamp::Now();
+ }
+
+ if (earlyDataUsed) {
+ // Didn't get 0RTT OK, back out of the "attempting 0RTT" state
+ LOG(("nsHttpConnection::PostProcessNPNSetup [this=%p] 0rtt failed", this));
+ if (mTransaction && NS_FAILED(mTransaction->Finish0RTT(true, true))) {
+ mTransaction->Close(NS_ERROR_NET_RESET);
+ }
+ mContentBytesWritten0RTT = 0;
+ if (mDid0RTTSpdy) {
+ Reset0RttForSpdy();
+ }
+ }
+
+ if (hasSecurityInfo) {
+ // Telemetry for tls failure rate with and without esni;
+ bool echConfigUsed = false;
+ mSocketTransport->GetEchConfigUsed(&echConfigUsed);
+ TlsHandshakeResult result =
+ echConfigUsed
+ ? (handshakeSucceeded ? TlsHandshakeResult::EchConfigSuccessful
+ : TlsHandshakeResult::EchConfigFailed)
+ : (handshakeSucceeded ? TlsHandshakeResult::NoEchConfigSuccessful
+ : TlsHandshakeResult::NoEchConfigFailed);
+ Telemetry::Accumulate(Telemetry::ECHCONFIG_SUCCESS_RATE, result);
+ }
+}
+
+void nsHttpConnection::Reset0RttForSpdy() {
+ // Reset the work done by Start0RTTSpdy
+ mUsingSpdyVersion = SpdyVersion::NONE;
+ mTransaction = nullptr;
+ mSpdySession = nullptr;
+ // We have to reset this here, just in case we end up starting spdy again,
+ // so it can actually do everything it needs to do.
+ mDid0RTTSpdy = false;
+}
+
+// called on the socket thread
+nsresult nsHttpConnection::Activate(nsAHttpTransaction* trans, uint32_t caps,
+ int32_t pri) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG1(("nsHttpConnection::Activate [this=%p trans=%p caps=%x]\n", this, trans,
+ caps));
+
+ if (!mExperienced && !trans->IsNullTransaction()) {
+ mHasFirstHttpTransaction = true;
+ if (mTlsHandshaker->NPNComplete()) {
+ mExperienced = true;
+ }
+ if (mBootstrappedTimingsSet) {
+ mBootstrappedTimingsSet = false;
+ nsHttpTransaction* hTrans = trans->QueryHttpTransaction();
+ if (hTrans) {
+ hTrans->BootstrapTimings(mBootstrappedTimings);
+ SetUrgentStartPreferred(hTrans->GetClassOfService().Flags() &
+ nsIClassOfService::UrgentStart);
+ }
+ }
+ mBootstrappedTimings = TimingStruct();
+ }
+
+ if (caps & NS_HTTP_LARGE_KEEPALIVE) {
+ mDefaultTimeoutFactor = StaticPrefs::network_http_largeKeepaliveFactor();
+ }
+
+ mTransactionCaps = caps;
+ mPriority = pri;
+
+ if (mHasFirstHttpTransaction && mExperienced) {
+ mHasFirstHttpTransaction = false;
+ mExperienceState |= ConnectionExperienceState::Experienced;
+ }
+
+ if (mTransaction && (mUsingSpdyVersion != SpdyVersion::NONE)) {
+ return AddTransaction(trans, pri);
+ }
+
+ NS_ENSURE_ARG_POINTER(trans);
+ NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS);
+
+ // reset the read timers to wash away any idle time
+ mLastWriteTime = mLastReadTime = PR_IntervalNow();
+
+ // Connection failures are Activated() just like regular transacions.
+ // If we don't have a confirmation of a connected socket then test it
+ // with a write() to get relevant error code.
+ if (NS_FAILED(mErrorBeforeConnect)) {
+ mSocketOutCondition = mErrorBeforeConnect;
+ mTransaction = trans;
+ CloseTransaction(mTransaction, mSocketOutCondition);
+ return mSocketOutCondition;
+ }
+
+ if (!mConnectedTransport) {
+ uint32_t count;
+ mSocketOutCondition = NS_ERROR_FAILURE;
+ if (mSocketOut) {
+ mSocketOutCondition = mSocketOut->Write("", 0, &count);
+ }
+ if (NS_FAILED(mSocketOutCondition) &&
+ mSocketOutCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG(("nsHttpConnection::Activate [this=%p] Bad Socket %" PRIx32 "\n",
+ this, static_cast<uint32_t>(mSocketOutCondition)));
+ mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mTransaction = trans;
+ CloseTransaction(mTransaction, mSocketOutCondition);
+ return mSocketOutCondition;
+ }
+ }
+
+ // Update security callbacks
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ trans->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ SetSecurityCallbacks(callbacks);
+ mTlsHandshaker->SetupSSL(mInSpdyTunnel, mForcePlainText);
+ if (mTlsHandshaker->NPNComplete()) {
+ // For non-HTTPS connection, change the state to TRANSFERING directly.
+ ChangeConnectionState(ConnectionState::TRANSFERING);
+ } else {
+ ChangeConnectionState(ConnectionState::TLS_HANDSHAKING);
+ }
+
+ // take ownership of the transaction
+ mTransaction = trans;
+
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ if (NS_SUCCEEDED(mSocketTransport->GetTlsSocketControl(
+ getter_AddRefs(tlsSocketControl))) &&
+ tlsSocketControl) {
+ tlsSocketControl->SetBrowserId(mTransaction->BrowserId());
+ }
+
+ MOZ_ASSERT(!mIdleMonitoring, "Activating a connection with an Idle Monitor");
+ mIdleMonitoring = false;
+
+ // set mKeepAlive according to what will be requested
+ mKeepAliveMask = mKeepAlive = (caps & NS_HTTP_ALLOW_KEEPALIVE);
+
+ mTransactionDisallowHttp3 |= (caps & NS_HTTP_DISALLOW_HTTP3);
+
+ // need to handle HTTP CONNECT tunnels if this is the first time if
+ // we are tunneling through a proxy
+ nsresult rv = CheckTunnelIsNeeded();
+ if (NS_FAILED(rv)) goto failed_activation;
+
+ // Clear the per activation counter
+ mCurrentBytesRead = 0;
+
+ // The overflow state is not needed between activations
+ mInputOverflow = nullptr;
+
+ mResponseTimeoutEnabled = gHttpHandler->ResponseTimeoutEnabled() &&
+ mTransaction->ResponseTimeout() > 0 &&
+ mTransaction->ResponseTimeoutEnabled();
+
+ rv = StartShortLivedTCPKeepalives();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnection::Activate [%p] "
+ "StartShortLivedTCPKeepalives failed rv[0x%" PRIx32 "]",
+ this, static_cast<uint32_t>(rv)));
+ }
+
+ trans->OnActivated();
+
+ rv = OnOutputStreamReady(mSocketOut);
+
+ if (NS_SUCCEEDED(rv) && mContinueHandshakeDone) {
+ mContinueHandshakeDone();
+ }
+ mContinueHandshakeDone = nullptr;
+
+failed_activation:
+ if (NS_FAILED(rv)) {
+ mTransaction = nullptr;
+ }
+
+ return rv;
+}
+
+nsresult nsHttpConnection::AddTransaction(nsAHttpTransaction* httpTransaction,
+ int32_t priority) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mSpdySession && (mUsingSpdyVersion != SpdyVersion::NONE),
+ "AddTransaction to live http connection without spdy/quic");
+
+ // If this is a wild card nshttpconnection (i.e. a spdy proxy) then
+ // it is important to start the stream using the specific connection
+ // info of the transaction to ensure it is routed on the right tunnel
+
+ nsHttpConnectionInfo* transCI = httpTransaction->ConnectionInfo();
+
+ bool needTunnel = transCI->UsingHttpsProxy();
+ needTunnel = needTunnel && !mHasTLSTransportLayer;
+ needTunnel = needTunnel && transCI->UsingConnect();
+ needTunnel = needTunnel && httpTransaction->QueryHttpTransaction();
+
+ // Let the transaction know that the tunnel is already established and we
+ // don't need to setup the tunnel again.
+ if (transCI->UsingConnect() && mEverUsedSpdy && mHasTLSTransportLayer) {
+ httpTransaction->OnProxyConnectComplete(200);
+ }
+
+ LOG(("nsHttpConnection::AddTransaction [this=%p] for %s%s", this,
+ mSpdySession ? "SPDY" : "QUIC", needTunnel ? " over tunnel" : ""));
+
+ if (mSpdySession) {
+ if (!mSpdySession->AddStream(httpTransaction, priority, mCallbacks)) {
+ MOZ_ASSERT(false); // this cannot happen!
+ httpTransaction->Close(NS_ERROR_ABORT);
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ Unused << ResumeSend();
+ return NS_OK;
+}
+
+nsresult nsHttpConnection::CreateTunnelStream(
+ nsAHttpTransaction* httpTransaction, nsHttpConnection** aHttpConnection,
+ bool aIsWebSocket) {
+ if (!mSpdySession) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<nsHttpConnection> conn = mSpdySession->CreateTunnelStream(
+ httpTransaction, mCallbacks, mRtt, aIsWebSocket);
+ // We need to store the refrence of the Http2Session in the tunneled
+ // connection, so when nsHttpConnection::DontReuse is called the Http2Session
+ // can't be reused.
+ if (aIsWebSocket) {
+ LOG(
+ ("nsHttpConnection::CreateTunnelStream %p Set h2 session %p to "
+ "tunneled conn %p",
+ this, mSpdySession.get(), conn.get()));
+ conn->mWebSocketHttp2Session = mSpdySession;
+ }
+ conn.forget(aHttpConnection);
+ return NS_OK;
+}
+
+void nsHttpConnection::Close(nsresult reason, bool aIsShutdown) {
+ LOG(("nsHttpConnection::Close [this=%p reason=%" PRIx32
+ " mExperienceState=%x]\n",
+ this, static_cast<uint32_t>(reason),
+ static_cast<uint32_t>(mExperienceState)));
+
+ if (mConnectionState != ConnectionState::CLOSED) {
+ RecordConnectionCloseTelemetry(reason);
+ ChangeConnectionState(ConnectionState::CLOSED);
+ }
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mTlsHandshaker->NotifyClose();
+ mContinueHandshakeDone = nullptr;
+ mWebSocketHttp2Session = nullptr;
+ // Ensure TCP keepalive timer is stopped.
+ if (mTCPKeepaliveTransitionTimer) {
+ mTCPKeepaliveTransitionTimer->Cancel();
+ mTCPKeepaliveTransitionTimer = nullptr;
+ }
+ if (mForceSendTimer) {
+ mForceSendTimer->Cancel();
+ mForceSendTimer = nullptr;
+ }
+
+ if (!mTrafficCategory.IsEmpty()) {
+ HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer();
+ if (hta) {
+ hta->IncrementHttpConnection(std::move(mTrafficCategory));
+ MOZ_ASSERT(mTrafficCategory.IsEmpty());
+ }
+ }
+
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ GetTLSSocketControl(getter_AddRefs(tlsSocketControl));
+ if (tlsSocketControl) {
+ tlsSocketControl->SetHandshakeCallbackListener(nullptr);
+ }
+
+ if (NS_FAILED(reason)) {
+ if (mIdleMonitoring) EndIdleMonitoring();
+
+ // The connection and security errors clear out alt-svc mappings
+ // in case any previously validated ones are now invalid
+ if (((reason == NS_ERROR_NET_RESET) ||
+ (NS_ERROR_GET_MODULE(reason) == NS_ERROR_MODULE_SECURITY)) &&
+ mConnInfo && !(mTransactionCaps & NS_HTTP_ERROR_SOFTLY)) {
+ gHttpHandler->ClearHostMapping(mConnInfo);
+ }
+ if (mTlsHandshaker->EarlyDataWasAvailable() &&
+ SecurityErrorThatMayNeedRestart(reason)) {
+ gHttpHandler->Exclude0RttTcp(mConnInfo);
+ }
+
+ if (mSocketTransport) {
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+
+ // If there are bytes sitting in the input queue then read them
+ // into a junk buffer to avoid generating a tcp rst by closing a
+ // socket with data pending. TLS is a classic case of this where
+ // a Alert record might be superfulous to a clean HTTP/SPDY shutdown.
+ // Never block to do this and limit it to a small amount of data.
+ // During shutdown just be fast!
+ if (mSocketIn && !aIsShutdown && !mInSpdyTunnel) {
+ char buffer[4000];
+ uint32_t count, total = 0;
+ nsresult rv;
+ do {
+ rv = mSocketIn->Read(buffer, 4000, &count);
+ if (NS_SUCCEEDED(rv)) total += count;
+ } while (NS_SUCCEEDED(rv) && count > 0 && total < 64000);
+ LOG(("nsHttpConnection::Close drained %d bytes\n", total));
+ }
+
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport->Close(reason);
+ if (mSocketOut) mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+ mKeepAlive = false;
+ }
+}
+
+void nsHttpConnection::MarkAsDontReuse() {
+ LOG(("nsHttpConnection::MarkAsDontReuse %p\n", this));
+ mKeepAliveMask = false;
+ mKeepAlive = false;
+ mDontReuse = true;
+ mIdleTimeout = 0;
+}
+
+void nsHttpConnection::DontReuse() {
+ LOG(("nsHttpConnection::DontReuse %p spdysession=%p\n", this,
+ mSpdySession.get()));
+ MarkAsDontReuse();
+ if (mSpdySession) {
+ mSpdySession->DontReuse();
+ } else if (mWebSocketHttp2Session) {
+ LOG(("nsHttpConnection::DontReuse %p mWebSocketHttp2Session=%p\n", this,
+ mWebSocketHttp2Session.get()));
+ mWebSocketHttp2Session->DontReuse();
+ }
+}
+
+bool nsHttpConnection::TestJoinConnection(const nsACString& hostname,
+ int32_t port) {
+ if (mSpdySession && CanDirectlyActivate()) {
+ return mSpdySession->TestJoinConnection(hostname, port);
+ }
+
+ return false;
+}
+
+bool nsHttpConnection::JoinConnection(const nsACString& hostname,
+ int32_t port) {
+ if (mSpdySession && CanDirectlyActivate()) {
+ return mSpdySession->JoinConnection(hostname, port);
+ }
+
+ return false;
+}
+
+bool nsHttpConnection::CanReuse() {
+ if (mDontReuse || !mRemainingConnectionUses) {
+ return false;
+ }
+
+ if ((mTransaction ? (mTransaction->IsDone() ? 0U : 1U) : 0U) >=
+ mRemainingConnectionUses) {
+ return false;
+ }
+
+ bool canReuse;
+ if (mSpdySession) {
+ canReuse = mSpdySession->CanReuse();
+ } else {
+ canReuse = IsKeepAlive();
+ }
+
+ canReuse = canReuse && (IdleTime() < mIdleTimeout) && IsAlive();
+
+ // An idle persistent connection should not have data waiting to be read
+ // before a request is sent. Data here is likely a 408 timeout response
+ // which we would deal with later on through the restart logic, but that
+ // path is more expensive than just closing the socket now.
+
+ uint64_t dataSize;
+ if (canReuse && mSocketIn && (mUsingSpdyVersion == SpdyVersion::NONE) &&
+ mHttp1xTransactionCount &&
+ NS_SUCCEEDED(mSocketIn->Available(&dataSize)) && dataSize) {
+ LOG(
+ ("nsHttpConnection::CanReuse %p %s"
+ "Socket not reusable because read data pending (%" PRIu64 ") on it.\n",
+ this, mConnInfo->Origin(), dataSize));
+ canReuse = false;
+ }
+ return canReuse;
+}
+
+bool nsHttpConnection::CanDirectlyActivate() {
+ // return true if a new transaction can be addded to ths connection at any
+ // time through Activate(). In practice this means this is a healthy SPDY
+ // connection with room for more concurrent streams.
+
+ return UsingSpdy() && CanReuse() && mSpdySession &&
+ mSpdySession->RoomForMoreStreams();
+}
+
+PRIntervalTime nsHttpConnection::IdleTime() {
+ return mSpdySession ? mSpdySession->IdleTime()
+ : (PR_IntervalNow() - mLastReadTime);
+}
+
+// returns the number of seconds left before the allowable idle period
+// expires, or 0 if the period has already expied.
+uint32_t nsHttpConnection::TimeToLive() {
+ LOG(("nsHttpConnection::TTL: %p %s idle %d timeout %d\n", this,
+ mConnInfo->Origin(), IdleTime(), mIdleTimeout));
+
+ if (IdleTime() >= mIdleTimeout) {
+ return 0;
+ }
+
+ uint32_t timeToLive = PR_IntervalToSeconds(mIdleTimeout - IdleTime());
+
+ // a positive amount of time can be rounded to 0. Because 0 is used
+ // as the expiration signal, round all values from 0 to 1 up to 1.
+ if (!timeToLive) {
+ timeToLive = 1;
+ }
+ return timeToLive;
+}
+
+bool nsHttpConnection::IsAlive() {
+ if (!mSocketTransport || !mConnectedTransport) return false;
+
+ // SocketTransport::IsAlive can run the SSL state machine, so make sure
+ // the NPN options are set before that happens.
+ mTlsHandshaker->SetupSSL(mInSpdyTunnel, mForcePlainText);
+
+ bool alive;
+ nsresult rv = mSocketTransport->IsAlive(&alive);
+ if (NS_FAILED(rv)) alive = false;
+
+// #define TEST_RESTART_LOGIC
+#ifdef TEST_RESTART_LOGIC
+ if (!alive) {
+ LOG(("pretending socket is still alive to test restart logic\n"));
+ alive = true;
+ }
+#endif
+
+ return alive;
+}
+
+void nsHttpConnection::SetUrgentStartPreferred(bool urgent) {
+ if (mExperienced && !mUrgentStartPreferredKnown) {
+ // Set only according the first ever dispatched non-null transaction
+ mUrgentStartPreferredKnown = true;
+ mUrgentStartPreferred = urgent;
+ LOG(("nsHttpConnection::SetUrgentStartPreferred [this=%p urgent=%d]", this,
+ urgent));
+ }
+}
+
+//----------------------------------------------------------------------------
+// nsHttpConnection::nsAHttpConnection compatible methods
+//----------------------------------------------------------------------------
+
+nsresult nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction* trans,
+ nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ bool* reset) {
+ LOG(
+ ("nsHttpConnection::OnHeadersAvailable [this=%p trans=%p "
+ "response-head=%p]\n",
+ this, trans, responseHead));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ NS_ENSURE_ARG_POINTER(trans);
+ MOZ_ASSERT(responseHead, "No response head?");
+
+ if (mInSpdyTunnel) {
+ DebugOnly<nsresult> rv =
+ responseHead->SetHeader(nsHttp::X_Firefox_Spdy_Proxy, "true"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // we won't change our keep-alive policy unless the server has explicitly
+ // told us to do so.
+
+ // inspect the connection headers for keep-alive info provided the
+ // transaction completed successfully. In the case of a non-sensical close
+ // and keep-alive favor the close out of conservatism.
+
+ bool explicitKeepAlive = false;
+ bool explicitClose =
+ responseHead->HasHeaderValue(nsHttp::Connection, "close") ||
+ responseHead->HasHeaderValue(nsHttp::Proxy_Connection, "close");
+ if (!explicitClose) {
+ explicitKeepAlive =
+ responseHead->HasHeaderValue(nsHttp::Connection, "keep-alive") ||
+ responseHead->HasHeaderValue(nsHttp::Proxy_Connection, "keep-alive");
+ }
+
+ // deal with 408 Server Timeouts
+ uint16_t responseStatus = responseHead->Status();
+ if (responseStatus == 408) {
+ // timeouts that are not caused by persistent connection reuse should
+ // not be retried for browser compatibility reasons. bug 907800. The
+ // server driven close is implicit in the 408.
+ explicitClose = true;
+ explicitKeepAlive = false;
+ }
+
+ if ((responseHead->Version() < HttpVersion::v1_1) ||
+ (requestHead->Version() < HttpVersion::v1_1)) {
+ // HTTP/1.0 connections are by default NOT persistent
+ mKeepAlive = explicitKeepAlive;
+ } else {
+ // HTTP/1.1 connections are by default persistent
+ mKeepAlive = !explicitClose;
+ }
+ mKeepAliveMask = mKeepAlive;
+
+ // if this connection is persistent, then the server may send a "Keep-Alive"
+ // header specifying the maximum number of times the connection can be
+ // reused as well as the maximum amount of time the connection can be idle
+ // before the server will close it. we ignore the max reuse count, because
+ // a "keep-alive" connection is by definition capable of being reused, and
+ // we only care about being able to reuse it once. if a timeout is not
+ // specified then we use our advertized timeout value.
+ bool foundKeepAliveMax = false;
+ if (mKeepAlive) {
+ nsAutoCString keepAlive;
+ Unused << responseHead->GetHeader(nsHttp::Keep_Alive, keepAlive);
+
+ if (mUsingSpdyVersion == SpdyVersion::NONE) {
+ const char* cp = nsCRT::strcasestr(keepAlive.get(), "timeout=");
+ if (cp) {
+ mIdleTimeout = PR_SecondsToInterval((uint32_t)atoi(cp + 8));
+ } else {
+ mIdleTimeout = gHttpHandler->IdleTimeout() * mDefaultTimeoutFactor;
+ }
+
+ cp = nsCRT::strcasestr(keepAlive.get(), "max=");
+ if (cp) {
+ int maxUses = atoi(cp + 4);
+ if (maxUses > 0) {
+ foundKeepAliveMax = true;
+ mRemainingConnectionUses = static_cast<uint32_t>(maxUses);
+ }
+ }
+ }
+
+ LOG(("Connection can be reused [this=%p idle-timeout=%usec]\n", this,
+ PR_IntervalToSeconds(mIdleTimeout)));
+ }
+
+ if (!foundKeepAliveMax && mRemainingConnectionUses &&
+ (mUsingSpdyVersion == SpdyVersion::NONE)) {
+ --mRemainingConnectionUses;
+ }
+
+ switch (mState) {
+ case HttpConnectionState::SETTING_UP_TUNNEL: {
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ // Distinguish SETTING_UP_TUNNEL for proxy or websocket via proxy
+ // See bug 1848013. Do not call HandleTunnelResponse for a tunnel
+ // connection created for WebSocket.
+ if (trans && trans->IsWebsocketUpgrade() &&
+ (trans->GetProxyConnectResponseCode() == 200 ||
+ (mForWebSocket && mInSpdyTunnel))) {
+ HandleWebSocketResponse(requestHead, responseHead, responseStatus);
+ } else {
+ HandleTunnelResponse(responseStatus, reset);
+ }
+ break;
+ }
+ default:
+ if (requestHead->HasHeader(nsHttp::Upgrade)) {
+ HandleWebSocketResponse(requestHead, responseHead, responseStatus);
+ } else if (responseStatus == 101) {
+ // We got an 101 but we are not asking of a WebSsocket?
+ Close(NS_ERROR_ABORT);
+ }
+ }
+
+ mLastHttpResponseVersion = responseHead->Version();
+
+ return NS_OK;
+}
+
+void nsHttpConnection::HandleTunnelResponse(uint16_t responseStatus,
+ bool* reset) {
+ LOG(("nsHttpConnection::HandleTunnelResponse()"));
+ MOZ_ASSERT(TunnelSetupInProgress());
+ MOZ_ASSERT(mProxyConnectStream);
+ MOZ_ASSERT(mUsingSpdyVersion == SpdyVersion::NONE,
+ "SPDY NPN Complete while using proxy connect stream");
+ // If we're doing a proxy connect, we need to check whether or not
+ // it was successful. If so, we have to reset the transaction and step-up
+ // the socket connection if using SSL. Finally, we have to wake up the
+ // socket write request.
+
+ if (responseStatus == 200) {
+ ChangeState(HttpConnectionState::REQUEST);
+ }
+ mProxyConnectStream = nullptr;
+ bool isHttps = mTransaction ? mTransaction->ConnectionInfo()->EndToEndSSL()
+ : mConnInfo->EndToEndSSL();
+ bool onlyConnect = mTransactionCaps & NS_HTTP_CONNECT_ONLY;
+
+ mTransaction->OnProxyConnectComplete(responseStatus);
+ if (responseStatus == 200) {
+ LOG(("proxy CONNECT succeeded! endtoendssl=%d onlyconnect=%d\n", isHttps,
+ onlyConnect));
+ // If we're only connecting, we don't need to reset the transaction
+ // state. We need to upgrade the socket now without doing the actual
+ // http request.
+ if (!onlyConnect) {
+ *reset = true;
+ }
+ nsresult rv;
+ // CONNECT only flag doesn't do the tls setup. https here only
+ // ensures a proxy tunnel was used not that tls is setup.
+ if (isHttps) {
+ if (!onlyConnect) {
+ if (mConnInfo->UsingHttpsProxy()) {
+ LOG(("%p new TLSFilterTransaction %s %d\n", this, mConnInfo->Origin(),
+ mConnInfo->OriginPort()));
+ SetupSecondaryTLS();
+ }
+
+ rv = mTlsHandshaker->InitSSLParams(false, true);
+ LOG(("InitSSLParams [rv=%" PRIx32 "]\n", static_cast<uint32_t>(rv)));
+ } else {
+ // We have an https protocol but the CONNECT only flag was
+ // specified. The consumer only wants a raw socket to the
+ // proxy. We have to mark this as complete to finish the
+ // transaction and be upgraded. OnSocketReadable() uses this
+ // to detect an inactive tunnel and blocks completion.
+ mTlsHandshaker->SetNPNComplete();
+ }
+ }
+ rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ // XXX what if this fails -- need to handle this error
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "mSocketOut->AsyncWait failed");
+ } else {
+ LOG(("proxy CONNECT failed! endtoendssl=%d onlyconnect=%d\n", isHttps,
+ onlyConnect));
+ mTransaction->SetProxyConnectFailed();
+ }
+}
+
+void nsHttpConnection::HandleWebSocketResponse(nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ uint16_t responseStatus) {
+ LOG(("nsHttpConnection::HandleWebSocketResponse()"));
+
+ // Don't use persistent connection for Upgrade unless there's an auth failure:
+ // some proxies expect to see auth response on persistent connection.
+ // Also allow persistent conn for h2, as we don't want to waste connections
+ // for multiplexed upgrades.
+ if (responseStatus != 401 && responseStatus != 407 && !mSpdySession) {
+ LOG(("HTTP Upgrade in play - disable keepalive for http/1.x\n"));
+ MarkAsDontReuse();
+ }
+
+ // the new Http2StreamWebSocket breaks wpt on
+ // h2 basic authentication 401, due to MakeSticky() work around
+ // so we DontReuse() in this circumstance
+ if (mInSpdyTunnel && (responseStatus == 401 || responseStatus == 407)) {
+ MarkAsDontReuse();
+ return;
+ }
+
+ if (responseStatus == 101) {
+ nsAutoCString upgradeReq;
+ bool hasUpgradeReq =
+ NS_SUCCEEDED(requestHead->GetHeader(nsHttp::Upgrade, upgradeReq));
+ nsAutoCString upgradeResp;
+ bool hasUpgradeResp =
+ NS_SUCCEEDED(responseHead->GetHeader(nsHttp::Upgrade, upgradeResp));
+ if (!hasUpgradeReq || !hasUpgradeResp ||
+ !nsHttp::FindToken(upgradeResp.get(), upgradeReq.get(),
+ HTTP_HEADER_VALUE_SEPS)) {
+ LOG(("HTTP 101 Upgrade header mismatch req = %s, resp = %s\n",
+ upgradeReq.get(),
+ !upgradeResp.IsEmpty() ? upgradeResp.get()
+ : "RESPONSE's nsHttp::Upgrade is empty"));
+ Close(NS_ERROR_ABORT);
+ } else {
+ LOG(("HTTP Upgrade Response to %s\n", upgradeResp.get()));
+ }
+ }
+}
+
+bool nsHttpConnection::IsReused() {
+ if (mIsReused) return true;
+ if (!mConsiderReusedAfterInterval) return false;
+
+ // ReusedAfter allows a socket to be consider reused only after a certain
+ // interval of time has passed
+ return (PR_IntervalNow() - mConsiderReusedAfterEpoch) >=
+ mConsiderReusedAfterInterval;
+}
+
+void nsHttpConnection::SetIsReusedAfter(uint32_t afterMilliseconds) {
+ mConsiderReusedAfterEpoch = PR_IntervalNow();
+ mConsiderReusedAfterInterval = PR_MillisecondsToInterval(afterMilliseconds);
+}
+
+nsresult nsHttpConnection::TakeTransport(nsISocketTransport** aTransport,
+ nsIAsyncInputStream** aInputStream,
+ nsIAsyncOutputStream** aOutputStream) {
+ if (mUsingSpdyVersion != SpdyVersion::NONE) return NS_ERROR_FAILURE;
+ if (mTransaction && !mTransaction->IsDone()) return NS_ERROR_IN_PROGRESS;
+ if (!(mSocketTransport && mSocketIn && mSocketOut)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (mInputOverflow) mSocketIn = mInputOverflow.forget();
+
+ // Change TCP Keepalive frequency to long-lived if currently short-lived.
+ if (mTCPKeepaliveConfig == kTCPKeepaliveShortLivedConfig) {
+ if (mTCPKeepaliveTransitionTimer) {
+ mTCPKeepaliveTransitionTimer->Cancel();
+ mTCPKeepaliveTransitionTimer = nullptr;
+ }
+ nsresult rv = StartLongLivedTCPKeepalives();
+ LOG(
+ ("nsHttpConnection::TakeTransport [%p] calling "
+ "StartLongLivedTCPKeepalives",
+ this));
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnection::TakeTransport [%p] "
+ "StartLongLivedTCPKeepalives failed rv[0x%" PRIx32 "]",
+ this, static_cast<uint32_t>(rv)));
+ }
+ }
+
+ if (mHasTLSTransportLayer) {
+ RefPtr<TLSTransportLayer> tlsTransportLayer =
+ do_QueryObject(mSocketTransport);
+ if (tlsTransportLayer) {
+ // This transport layer is no longer owned by this connection.
+ tlsTransportLayer->ReleaseOwner();
+ }
+ }
+
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+
+ mSocketTransport.forget(aTransport);
+ mSocketIn.forget(aInputStream);
+ mSocketOut.forget(aOutputStream);
+
+ return NS_OK;
+}
+
+uint32_t nsHttpConnection::ReadTimeoutTick(PRIntervalTime now) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // make sure timer didn't tick before Activate()
+ if (!mTransaction) return UINT32_MAX;
+
+ // Spdy implements some timeout handling using the SPDY ping frame.
+ if (mSpdySession) {
+ return mSpdySession->ReadTimeoutTick(now);
+ }
+
+ uint32_t nextTickAfter = UINT32_MAX;
+ // Timeout if the response is taking too long to arrive.
+ if (mResponseTimeoutEnabled) {
+ NS_WARNING_ASSERTION(
+ gHttpHandler->ResponseTimeoutEnabled(),
+ "Timing out a response, but response timeout is disabled!");
+
+ PRIntervalTime initialResponseDelta = now - mLastWriteTime;
+
+ if (initialResponseDelta > mTransaction->ResponseTimeout()) {
+ LOG(("canceling transaction: no response for %ums: timeout is %dms\n",
+ PR_IntervalToMilliseconds(initialResponseDelta),
+ PR_IntervalToMilliseconds(mTransaction->ResponseTimeout())));
+
+ mResponseTimeoutEnabled = false;
+ SetCloseReason(ConnectionCloseReason::IDLE_TIMEOUT);
+ // This will also close the connection
+ CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT);
+ return UINT32_MAX;
+ }
+ nextTickAfter = PR_IntervalToSeconds(mTransaction->ResponseTimeout()) -
+ PR_IntervalToSeconds(initialResponseDelta);
+ nextTickAfter = std::max(nextTickAfter, 1U);
+ }
+
+ if (!mTlsHandshaker->NPNComplete()) {
+ // We can reuse mLastWriteTime here, because it is set when the
+ // connection is activated and only change when a transaction
+ // succesfullu write to the socket and this can only happen after
+ // the TLS handshake is done.
+ PRIntervalTime initialTLSDelta = now - mLastWriteTime;
+ if (initialTLSDelta >
+ PR_MillisecondsToInterval(gHttpHandler->TLSHandshakeTimeout())) {
+ LOG(
+ ("canceling transaction: tls handshake takes too long: tls handshake "
+ "last %ums, timeout is %dms.",
+ PR_IntervalToMilliseconds(initialTLSDelta),
+ gHttpHandler->TLSHandshakeTimeout()));
+
+ // This will also close the connection
+ SetCloseReason(ConnectionCloseReason::TLS_TIMEOUT);
+ CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT);
+ return UINT32_MAX;
+ }
+ }
+
+ return nextTickAfter;
+}
+
+void nsHttpConnection::UpdateTCPKeepalive(nsITimer* aTimer, void* aClosure) {
+ MOZ_ASSERT(aTimer);
+ MOZ_ASSERT(aClosure);
+
+ nsHttpConnection* self = static_cast<nsHttpConnection*>(aClosure);
+
+ if (NS_WARN_IF(self->mUsingSpdyVersion != SpdyVersion::NONE)) {
+ return;
+ }
+
+ // Do not reduce keepalive probe frequency for idle connections.
+ if (self->mIdleMonitoring) {
+ return;
+ }
+
+ nsresult rv = self->StartLongLivedTCPKeepalives();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnection::UpdateTCPKeepalive [%p] "
+ "StartLongLivedTCPKeepalives failed rv[0x%" PRIx32 "]",
+ self, static_cast<uint32_t>(rv)));
+ }
+}
+
+void nsHttpConnection::GetTLSSocketControl(
+ nsITLSSocketControl** tlsSocketControl) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnection::GetTLSSocketControl trans=%p socket=%p\n",
+ mTransaction.get(), mSocketTransport.get()));
+
+ *tlsSocketControl = nullptr;
+
+ if (mTransaction && NS_SUCCEEDED(mTransaction->GetTransactionTLSSocketControl(
+ tlsSocketControl))) {
+ return;
+ }
+
+ if (mSocketTransport &&
+ NS_SUCCEEDED(mSocketTransport->GetTlsSocketControl(tlsSocketControl))) {
+ return;
+ }
+}
+
+nsresult nsHttpConnection::PushBack(const char* data, uint32_t length) {
+ LOG(("nsHttpConnection::PushBack [this=%p, length=%d]\n", this, length));
+
+ if (mInputOverflow) {
+ NS_ERROR("nsHttpConnection::PushBack only one buffer supported");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mInputOverflow = new nsPreloadedStream(mSocketIn, data, length);
+ return NS_OK;
+}
+
+class HttpConnectionForceIO : public Runnable {
+ public:
+ HttpConnectionForceIO(nsHttpConnection* aConn, bool doRecv)
+ : Runnable("net::HttpConnectionForceIO"), mConn(aConn), mDoRecv(doRecv) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mDoRecv) {
+ if (!mConn->mSocketIn) return NS_OK;
+ return mConn->OnInputStreamReady(mConn->mSocketIn);
+ }
+
+ MOZ_ASSERT(mConn->mForceSendPending);
+ mConn->mForceSendPending = false;
+
+ if (!mConn->mSocketOut) {
+ return NS_OK;
+ }
+ return mConn->OnOutputStreamReady(mConn->mSocketOut);
+ }
+
+ private:
+ RefPtr<nsHttpConnection> mConn;
+ bool mDoRecv;
+};
+
+nsresult nsHttpConnection::ResumeSend() {
+ LOG(("nsHttpConnection::ResumeSend [this=%p]\n", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mSocketOut) {
+ return mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("no socket output stream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult nsHttpConnection::ResumeRecv() {
+ LOG(("nsHttpConnection::ResumeRecv [this=%p]\n", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // the mLastReadTime timestamp is used for finding slowish readers
+ // and can be pretty sensitive. For that reason we actually reset it
+ // when we ask to read (resume recv()) so that when we get called back
+ // with actual read data in OnSocketReadable() we are only measuring
+ // the latency between those two acts and not all the processing that
+ // may get done before the ResumeRecv() call
+ mLastReadTime = PR_IntervalNow();
+
+ if (mSocketIn) {
+ if (mHasTLSTransportLayer) {
+ RefPtr<TLSTransportLayer> tlsTransportLayer =
+ do_QueryObject(mSocketTransport);
+ if (tlsTransportLayer) {
+ bool hasDataToRecv = tlsTransportLayer->HasDataToRecv();
+ if (hasDataToRecv && NS_SUCCEEDED(ForceRecv())) {
+ return NS_OK;
+ }
+ Unused << mSocketIn->AsyncWait(this, 0, 0, nullptr);
+ // We have to return an error here to let the underlying layer know this
+ // connection doesn't read any data.
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ }
+ return mSocketIn->AsyncWait(this, 0, 0, nullptr);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("no socket input stream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+void nsHttpConnection::ForceSendIO(nsITimer* aTimer, void* aClosure) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsHttpConnection* self = static_cast<nsHttpConnection*>(aClosure);
+ MOZ_ASSERT(aTimer == self->mForceSendTimer);
+ self->mForceSendTimer = nullptr;
+ NS_DispatchToCurrentThread(new HttpConnectionForceIO(self, false));
+}
+
+nsresult nsHttpConnection::MaybeForceSendIO() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // due to bug 1213084 sometimes real I/O events do not get serviced when
+ // NSPR derived I/O events are ready and this can cause a deadlock with
+ // https over https proxying. Normally we would expect the write callback to
+ // be invoked before this timer goes off, but set it at the old windows
+ // tick interval (kForceDelay) as a backup for those circumstances.
+ static const uint32_t kForceDelay = 17; // ms
+
+ if (mForceSendPending) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(!mForceSendTimer);
+ mForceSendPending = true;
+ return NS_NewTimerWithFuncCallback(getter_AddRefs(mForceSendTimer),
+ nsHttpConnection::ForceSendIO, this,
+ kForceDelay, nsITimer::TYPE_ONE_SHOT,
+ "net::nsHttpConnection::MaybeForceSendIO");
+}
+
+// trigger an asynchronous read
+nsresult nsHttpConnection::ForceRecv() {
+ LOG(("nsHttpConnection::ForceRecv [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ return NS_DispatchToCurrentThread(new HttpConnectionForceIO(this, true));
+}
+
+// trigger an asynchronous write
+nsresult nsHttpConnection::ForceSend() {
+ LOG(("nsHttpConnection::ForceSend [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ return MaybeForceSendIO();
+}
+
+void nsHttpConnection::BeginIdleMonitoring() {
+ LOG(("nsHttpConnection::BeginIdleMonitoring [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mTransaction, "BeginIdleMonitoring() while active");
+ MOZ_ASSERT(mUsingSpdyVersion == SpdyVersion::NONE,
+ "Idle monitoring of spdy not allowed");
+
+ LOG(("Entering Idle Monitoring Mode [this=%p]", this));
+ mIdleMonitoring = true;
+ if (mSocketIn) mSocketIn->AsyncWait(this, 0, 0, nullptr);
+}
+
+void nsHttpConnection::EndIdleMonitoring() {
+ LOG(("nsHttpConnection::EndIdleMonitoring [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mTransaction, "EndIdleMonitoring() while active");
+
+ if (mIdleMonitoring) {
+ LOG(("Leaving Idle Monitoring Mode [this=%p]", this));
+ mIdleMonitoring = false;
+ if (mSocketIn) mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+}
+
+HttpVersion nsHttpConnection::Version() {
+ if (mUsingSpdyVersion != SpdyVersion::NONE) {
+ return HttpVersion::v2_0;
+ }
+ return mLastHttpResponseVersion;
+}
+
+PRIntervalTime nsHttpConnection::LastWriteTime() { return mLastWriteTime; }
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection <private>
+//-----------------------------------------------------------------------------
+
+void nsHttpConnection::CloseTransaction(nsAHttpTransaction* trans,
+ nsresult reason, bool aIsShutdown) {
+ LOG(("nsHttpConnection::CloseTransaction[this=%p trans=%p reason=%" PRIx32
+ "]\n",
+ this, trans, static_cast<uint32_t>(reason)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mCurrentBytesRead > mMaxBytesRead) mMaxBytesRead = mCurrentBytesRead;
+
+ // mask this error code because its not a real error.
+ if (reason == NS_BASE_STREAM_CLOSED) reason = NS_OK;
+
+ if (mUsingSpdyVersion != SpdyVersion::NONE) {
+ DontReuse();
+ // if !mSpdySession then mUsingSpdyVersion must be false for canreuse()
+ mSpdySession->SetCleanShutdown(aIsShutdown);
+ mUsingSpdyVersion = SpdyVersion::NONE;
+ mSpdySession = nullptr;
+ }
+
+ if (mTransaction) {
+ LOG((" closing associated mTransaction"));
+ mHttp1xTransactionCount += mTransaction->Http1xTransactionCount();
+
+ mTransaction->Close(reason);
+ mTransaction = nullptr;
+ }
+
+ {
+ MutexAutoLock lock(mCallbacksLock);
+ mCallbacks = nullptr;
+ }
+
+ if (NS_FAILED(reason) && (reason != NS_BINDING_RETARGETED)) {
+ Close(reason, aIsShutdown);
+ }
+
+ // flag the connection as reused here for convenience sake. certainly
+ // it might be going away instead ;-)
+ mIsReused = true;
+}
+
+bool nsHttpConnection::CheckCanWrite0RTTData() {
+ MOZ_ASSERT(mTlsHandshaker->EarlyDataAvailable());
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ GetTLSSocketControl(getter_AddRefs(tlsSocketControl));
+ if (!tlsSocketControl) {
+ return false;
+ }
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ if (NS_FAILED(
+ tlsSocketControl->GetSecurityInfo(getter_AddRefs(securityInfo)))) {
+ return false;
+ }
+ if (!securityInfo) {
+ return false;
+ }
+ nsAutoCString negotiatedNPN;
+ // If the following code fails means that the handshake is not done
+ // yet, so continue writing 0RTT data.
+ nsresult rv = securityInfo->GetNegotiatedNPN(negotiatedNPN);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ bool earlyDataAccepted = false;
+ rv = tlsSocketControl->GetEarlyDataAccepted(&earlyDataAccepted);
+ // If 0RTT data is accepted we can continue writing data,
+ // if it is reject stop writing more data.
+ return NS_SUCCEEDED(rv) && earlyDataAccepted;
+}
+
+nsresult nsHttpConnection::OnReadSegment(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ LOG(("nsHttpConnection::OnReadSegment [this=%p]\n", this));
+ if (count == 0) {
+ // some ReadSegments implementations will erroneously call the writer
+ // to consume 0 bytes worth of data. we must protect against this case
+ // or else we'd end up closing the socket prematurely.
+ NS_ERROR("bad ReadSegments implementation");
+ return NS_ERROR_FAILURE; // stop iterating
+ }
+
+ // If we are waiting for 0RTT Response, check maybe nss has finished
+ // handshake already.
+ // IsAlive() calls drive the handshake and that may cause nss and necko
+ // to be out of sync.
+ if (mTlsHandshaker->EarlyDataAvailable() && !CheckCanWrite0RTTData()) {
+ MOZ_DIAGNOSTIC_ASSERT(mTlsHandshaker->TlsHandshakeComplitionPending());
+ LOG(
+ ("nsHttpConnection::OnReadSegment Do not write any data, wait"
+ " for EnsureNPNComplete to be called [this=%p]",
+ this));
+ *countRead = 0;
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ nsresult rv = mSocketOut->Write(buf, count, countRead);
+ if (NS_FAILED(rv)) {
+ mSocketOutCondition = rv;
+ } else if (*countRead == 0) {
+ mSocketOutCondition = NS_BASE_STREAM_CLOSED;
+ } else {
+ mLastWriteTime = PR_IntervalNow();
+ mSocketOutCondition = NS_OK; // reset condition
+ if (!TunnelSetupInProgress()) {
+ mTotalBytesWritten += *countRead;
+ mExperienceState |= ConnectionExperienceState::First_Request_Sent;
+ }
+ }
+
+ return mSocketOutCondition;
+}
+
+nsresult nsHttpConnection::OnSocketWritable() {
+ LOG(("nsHttpConnection::OnSocketWritable [this=%p] host=%s\n", this,
+ mConnInfo->Origin()));
+
+ nsresult rv;
+ uint32_t transactionBytes;
+ bool again = true;
+
+ // Prevent STS thread from being blocked by single OnOutputStreamReady
+ // callback.
+ const uint32_t maxWriteAttempts = 128;
+ uint32_t writeAttempts = 0;
+
+ if (mTransactionCaps & NS_HTTP_CONNECT_ONLY) {
+ if (!mConnInfo->UsingConnect()) {
+ // A CONNECT has been requested for this connection but will never
+ // be performed. This should never happen.
+ MOZ_ASSERT(false, "proxy connect will never happen");
+ LOG(("return failure because proxy connect will never happen\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mState == HttpConnectionState::REQUEST) {
+ // Don't need to check this each write attempt since it is only
+ // updated after OnSocketWritable completes.
+ // We've already done primary tls (if needed) and sent our CONNECT.
+ // If we're doing a CONNECT only request there's no need to write
+ // the http transaction or do the SSL handshake here.
+ LOG(("return ok because proxy connect successful\n"));
+ return NS_OK;
+ }
+ }
+
+ do {
+ ++writeAttempts;
+ rv = mSocketOutCondition = NS_OK;
+ transactionBytes = 0;
+
+ switch (mState) {
+ case HttpConnectionState::SETTING_UP_TUNNEL:
+ if (mConnInfo->UsingHttpsProxy() &&
+ !mTlsHandshaker->EnsureNPNComplete()) {
+ MOZ_DIAGNOSTIC_ASSERT(!mTlsHandshaker->EarlyDataAvailable());
+ mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
+ } else {
+ rv = SendConnectRequest(this, &transactionBytes);
+ }
+ break;
+ default: {
+ // The SSL handshake must be completed before the
+ // transaction->readsegments() processing can proceed because we need to
+ // know how to format the request differently for http/1, http/2, spdy,
+ // etc.. and that is negotiated with NPN/ALPN in the SSL handshake.
+ if (!mTlsHandshaker->EnsureNPNComplete() &&
+ (!mTlsHandshaker->EarlyDataUsed() ||
+ mTlsHandshaker->TlsHandshakeComplitionPending())) {
+ // The handshake is not done and we cannot write 0RTT data or nss has
+ // already finished 0RTT data.
+ mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
+ } else if (!mTransaction) {
+ rv = NS_ERROR_FAILURE;
+ LOG((" No Transaction In OnSocketWritable\n"));
+ } else if (NS_SUCCEEDED(rv)) {
+ // for non spdy sessions let the connection manager know
+ if (!mReportedSpdy && mTlsHandshaker->NPNComplete()) {
+ mReportedSpdy = true;
+ MOZ_ASSERT(!mEverUsedSpdy);
+ gHttpHandler->ConnMgr()->ReportSpdyConnection(this, false, false);
+ }
+
+ LOG((" writing transaction request stream\n"));
+ rv = mTransaction->ReadSegmentsAgain(this,
+ nsIOService::gDefaultSegmentSize,
+ &transactionBytes, &again);
+ if (mTlsHandshaker->EarlyDataUsed()) {
+ mContentBytesWritten0RTT += transactionBytes;
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ // If an error happens while writting 0RTT data, restart
+ // the transactiions without 0RTT.
+ mTlsHandshaker->FinishNPNSetup(false, true);
+ }
+ } else {
+ mContentBytesWritten += transactionBytes;
+ }
+ }
+ }
+ }
+
+ LOG(
+ ("nsHttpConnection::OnSocketWritable %p "
+ "ReadSegments returned [rv=%" PRIx32 " read=%u "
+ "sock-cond=%" PRIx32 " again=%d]\n",
+ this, static_cast<uint32_t>(rv), transactionBytes,
+ static_cast<uint32_t>(mSocketOutCondition), again));
+
+ // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
+ if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) {
+ rv = NS_OK;
+ transactionBytes = 0;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to write any more data, then
+ // wait for the transaction to call ResumeSend.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else if (NS_FAILED(mSocketOutCondition)) {
+ if (mSocketOutCondition == NS_BASE_STREAM_WOULD_BLOCK) {
+ if (!mTlsHandshaker->EarlyDataCanNotBeUsed()) {
+ // continue writing
+ // We are not going to poll for write if the handshake is in progress,
+ // but early data cannot be used.
+ rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ }
+ } else {
+ rv = mSocketOutCondition;
+ }
+ again = false;
+ } else if (!transactionBytes) {
+ rv = NS_OK;
+
+ if (mTransaction) { // in case the ReadSegments stack called
+ // CloseTransaction()
+ //
+ // at this point we've written out the entire transaction, and now we
+ // must wait for the server's response. we manufacture a status message
+ // here to reflect the fact that we are waiting. this message will be
+ // trumped (overwritten) if the server responds quickly.
+ //
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_WAITING_FOR, 0);
+
+ rv = ResumeRecv(); // start reading
+ }
+ // When Spdy tunnel is used we need to explicitly set when a request is
+ // done.
+ if ((mState != HttpConnectionState::SETTING_UP_TUNNEL) && !mSpdySession) {
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ // needed for websocket over h2 (direct)
+ if (!trans || !trans->IsWebsocketUpgrade()) {
+ mRequestDone = true;
+ }
+ }
+ again = false;
+ } else if (writeAttempts >= maxWriteAttempts) {
+ LOG((" yield for other transactions\n"));
+ rv = mSocketOut->AsyncWait(this, 0, 0, nullptr); // continue writing
+ again = false;
+ }
+ // write more to the socket until error or end-of-request...
+ } while (again && gHttpHandler->Active());
+
+ return rv;
+}
+
+nsresult nsHttpConnection::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ if (count == 0) {
+ // some WriteSegments implementations will erroneously call the reader
+ // to provide 0 bytes worth of data. we must protect against this case
+ // or else we'd end up closing the socket prematurely.
+ NS_ERROR("bad WriteSegments implementation");
+ return NS_ERROR_FAILURE; // stop iterating
+ }
+
+ if (ChaosMode::isActive(ChaosFeature::IOAmounts) &&
+ ChaosMode::randomUint32LessThan(2)) {
+ // read 1...count bytes
+ count = ChaosMode::randomUint32LessThan(count) + 1;
+ }
+
+ nsresult rv = mSocketIn->Read(buf, count, countWritten);
+ if (NS_FAILED(rv)) {
+ mSocketInCondition = rv;
+ } else if (*countWritten == 0) {
+ mSocketInCondition = NS_BASE_STREAM_CLOSED;
+ } else {
+ mSocketInCondition = NS_OK; // reset condition
+ mExperienceState |= ConnectionExperienceState::First_Response_Received;
+ }
+
+ return mSocketInCondition;
+}
+
+nsresult nsHttpConnection::OnSocketReadable() {
+ LOG(("nsHttpConnection::OnSocketReadable [this=%p]\n", this));
+
+ PRIntervalTime now = PR_IntervalNow();
+ PRIntervalTime delta = now - mLastReadTime;
+
+ // Reset mResponseTimeoutEnabled to stop response timeout checks.
+ mResponseTimeoutEnabled = false;
+
+ if ((mTransactionCaps & NS_HTTP_CONNECT_ONLY) && !mConnInfo->UsingConnect()) {
+ // A CONNECT has been requested for this connection but will never
+ // be performed. This should never happen.
+ MOZ_ASSERT(false, "proxy connect will never happen");
+ LOG(("return failure because proxy connect will never happen\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mKeepAliveMask && (delta >= mMaxHangTime)) {
+ LOG(("max hang time exceeded!\n"));
+ // give the handler a chance to create a new persistent connection to
+ // this host if we've been busy for too long.
+ mKeepAliveMask = false;
+ Unused << gHttpHandler->ProcessPendingQ(mConnInfo);
+ }
+
+ // Reduce the estimate of the time since last read by up to 1 RTT to
+ // accommodate exhausted sender TCP congestion windows or minor I/O delays.
+ mLastReadTime = now;
+
+ nsresult rv = NS_OK;
+ uint32_t n;
+ bool again = true;
+
+ do {
+ if (!TunnelSetupInProgress() && !mTlsHandshaker->EnsureNPNComplete()) {
+ // Unless we are setting up a tunnel via CONNECT, prevent reading
+ // from the socket until the results of NPN
+ // negotiation are known (which is determined from the write path).
+ // If the server speaks SPDY it is likely the readable data here is
+ // a spdy settings frame and without NPN it would be misinterpreted
+ // as HTTP/*
+
+ LOG(
+ ("nsHttpConnection::OnSocketReadable %p return due to inactive "
+ "tunnel setup but incomplete NPN state\n",
+ this));
+ if (mTlsHandshaker->EarlyDataAvailable() || mHasTLSTransportLayer) {
+ rv = ResumeRecv();
+ }
+ break;
+ }
+
+ mSocketInCondition = NS_OK;
+ if (!mTransaction) {
+ rv = NS_ERROR_FAILURE;
+ LOG((" No Transaction In OnSocketWritable\n"));
+ } else {
+ rv = mTransaction->WriteSegmentsAgain(
+ this, nsIOService::gDefaultSegmentSize, &n, &again);
+ }
+ LOG(("nsHttpConnection::OnSocketReadable %p trans->ws rv=%" PRIx32
+ " n=%d socketin=%" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv), n,
+ static_cast<uint32_t>(mSocketInCondition)));
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to take any more data, then
+ // wait for the transaction to call ResumeRecv.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else {
+ mCurrentBytesRead += n;
+ mTotalBytesRead += n;
+ if (NS_FAILED(mSocketInCondition)) {
+ // continue waiting for the socket if necessary...
+ if (mSocketInCondition == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = ResumeRecv();
+ } else {
+ rv = mSocketInCondition;
+ }
+ again = false;
+ }
+ }
+ // read more from the socket until error...
+ } while (again && gHttpHandler->Active());
+
+ return rv;
+}
+
+void nsHttpConnection::SetupSecondaryTLS() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mHasTLSTransportLayer);
+ LOG(("nsHttpConnection %p SetupSecondaryTLS %s %d\n", this,
+ mConnInfo->Origin(), mConnInfo->OriginPort()));
+
+ nsHttpConnectionInfo* ci = nullptr;
+ if (mTransaction) {
+ ci = mTransaction->ConnectionInfo();
+ }
+ if (!ci) {
+ ci = mConnInfo;
+ }
+ MOZ_ASSERT(ci);
+
+ RefPtr<TLSTransportLayer> transportLayer =
+ new TLSTransportLayer(mSocketTransport, mSocketIn, mSocketOut, this);
+ if (transportLayer->Init(ci->Origin(), ci->OriginPort())) {
+ mSocketIn = transportLayer->GetInputStreamWrapper();
+ mSocketOut = transportLayer->GetOutputStreamWrapper();
+ mSocketTransport = transportLayer;
+ mHasTLSTransportLayer = true;
+ LOG(("Create mTLSTransportLayer %p", this));
+ }
+}
+
+void nsHttpConnection::SetInSpdyTunnel() {
+ mInSpdyTunnel = true;
+ mForcePlainText = true;
+}
+
+// static
+nsresult nsHttpConnection::MakeConnectString(nsAHttpTransaction* trans,
+ nsHttpRequestHead* request,
+ nsACString& result, bool h2ws,
+ bool aShouldResistFingerprinting) {
+ result.Truncate();
+ if (!trans->ConnectionInfo()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ DebugOnly<nsresult> rv{};
+
+ rv = nsHttpHandler::GenerateHostPort(
+ nsDependentCString(trans->ConnectionInfo()->Origin()),
+ trans->ConnectionInfo()->OriginPort(), result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // CONNECT host:port HTTP/1.1
+ request->SetMethod("CONNECT"_ns);
+ request->SetVersion(gHttpHandler->HttpVersion());
+ if (h2ws) {
+ // HTTP/2 websocket CONNECT forms need the full request URI
+ nsAutoCString requestURI;
+ trans->RequestHead()->RequestURI(requestURI);
+ request->SetRequestURI(requestURI);
+
+ request->SetHTTPS(trans->RequestHead()->IsHTTPS());
+
+ nsAutoCString val;
+ if (NS_SUCCEEDED(trans->RequestHead()->GetHeader(
+ nsHttp::Sec_WebSocket_Extensions, val))) {
+ rv = request->SetHeader(nsHttp::Sec_WebSocket_Extensions, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ if (NS_SUCCEEDED(trans->RequestHead()->GetHeader(
+ nsHttp::Sec_WebSocket_Protocol, val))) {
+ rv = request->SetHeader(nsHttp::Sec_WebSocket_Protocol, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ if (NS_SUCCEEDED(trans->RequestHead()->GetHeader(
+ nsHttp::Sec_WebSocket_Version, val))) {
+ rv = request->SetHeader(nsHttp::Sec_WebSocket_Version, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ } else {
+ request->SetRequestURI(result);
+ }
+ rv = request->SetHeader(nsHttp::User_Agent,
+ gHttpHandler->UserAgent(aShouldResistFingerprinting));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // a CONNECT is always persistent
+ rv = request->SetHeader(nsHttp::Proxy_Connection, "keep-alive"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = request->SetHeader(nsHttp::Connection, "keep-alive"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // all HTTP/1.1 requests must include a Host header (even though it
+ // may seem redundant in this case; see bug 82388).
+ rv = request->SetHeader(nsHttp::Host, result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsAutoCString val;
+ if (NS_SUCCEEDED(
+ trans->RequestHead()->GetHeader(nsHttp::Proxy_Authorization, val))) {
+ // we don't know for sure if this authorization is intended for the
+ // SSL proxy, so we add it just in case.
+ rv = request->SetHeader(nsHttp::Proxy_Authorization, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ if ((trans->Caps() & NS_HTTP_CONNECT_ONLY) &&
+ NS_SUCCEEDED(trans->RequestHead()->GetHeader(nsHttp::Upgrade, val))) {
+ // rfc7639 proposes using the ALPN header to indicate the protocol used
+ // in CONNECT when not used for TLS. The protocol is stored in Upgrade.
+ // We have to copy this header here since a new HEAD request is created
+ // for the CONNECT.
+ rv = request->SetHeader("ALPN"_ns, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ result.Truncate();
+ request->Flatten(result, false);
+
+ if (LOG1_ENABLED()) {
+ LOG(("nsHttpConnection::MakeConnectString for transaction=%p h2ws=%d[",
+ trans->QueryHttpTransaction(), h2ws));
+ LogHeaders(result.BeginReading());
+ LOG(("]"));
+ }
+
+ result.AppendLiteral("\r\n");
+ return NS_OK;
+}
+
+nsresult nsHttpConnection::StartShortLivedTCPKeepalives() {
+ if (mUsingSpdyVersion != SpdyVersion::NONE) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(mSocketTransport);
+ if (!mSocketTransport) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv = NS_OK;
+ int32_t idleTimeS = -1;
+ int32_t retryIntervalS = -1;
+ if (gHttpHandler->TCPKeepaliveEnabledForShortLivedConns()) {
+ // Set the idle time.
+ idleTimeS = gHttpHandler->GetTCPKeepaliveShortLivedIdleTime();
+ LOG(
+ ("nsHttpConnection::StartShortLivedTCPKeepalives[%p] "
+ "idle time[%ds].",
+ this, idleTimeS));
+
+ retryIntervalS = std::max<int32_t>((int32_t)PR_IntervalToSeconds(mRtt), 1);
+ rv = mSocketTransport->SetKeepaliveVals(idleTimeS, retryIntervalS);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = mSocketTransport->SetKeepaliveEnabled(true);
+ mTCPKeepaliveConfig = kTCPKeepaliveShortLivedConfig;
+ } else {
+ rv = mSocketTransport->SetKeepaliveEnabled(false);
+ mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Start a timer to move to long-lived keepalive config.
+ if (!mTCPKeepaliveTransitionTimer) {
+ mTCPKeepaliveTransitionTimer = NS_NewTimer();
+ }
+
+ if (mTCPKeepaliveTransitionTimer) {
+ int32_t time = gHttpHandler->GetTCPKeepaliveShortLivedTime();
+
+ // Adjust |time| to ensure a full set of keepalive probes can be sent
+ // at the end of the short-lived phase.
+ if (gHttpHandler->TCPKeepaliveEnabledForShortLivedConns()) {
+ if (NS_WARN_IF(!gSocketTransportService)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ int32_t probeCount = -1;
+ rv = gSocketTransportService->GetKeepaliveProbeCount(&probeCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (NS_WARN_IF(probeCount <= 0)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ // Add time for final keepalive probes, and 2 seconds for a buffer.
+ time += ((probeCount)*retryIntervalS) - (time % idleTimeS) + 2;
+ }
+ mTCPKeepaliveTransitionTimer->InitWithNamedFuncCallback(
+ nsHttpConnection::UpdateTCPKeepalive, this, (uint32_t)time * 1000,
+ nsITimer::TYPE_ONE_SHOT,
+ "net::nsHttpConnection::StartShortLivedTCPKeepalives");
+ } else {
+ NS_WARNING(
+ "nsHttpConnection::StartShortLivedTCPKeepalives failed to "
+ "create timer.");
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpConnection::StartLongLivedTCPKeepalives() {
+ MOZ_ASSERT(mUsingSpdyVersion == SpdyVersion::NONE,
+ "Don't use TCP Keepalive with SPDY!");
+ if (NS_WARN_IF(mUsingSpdyVersion != SpdyVersion::NONE)) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(mSocketTransport);
+ if (!mSocketTransport) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv = NS_OK;
+ if (gHttpHandler->TCPKeepaliveEnabledForLongLivedConns()) {
+ // Increase the idle time.
+ int32_t idleTimeS = gHttpHandler->GetTCPKeepaliveLongLivedIdleTime();
+ LOG(("nsHttpConnection::StartLongLivedTCPKeepalives[%p] idle time[%ds]",
+ this, idleTimeS));
+
+ int32_t retryIntervalS =
+ std::max<int32_t>((int32_t)PR_IntervalToSeconds(mRtt), 1);
+ rv = mSocketTransport->SetKeepaliveVals(idleTimeS, retryIntervalS);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Ensure keepalive is enabled, if current status is disabled.
+ if (mTCPKeepaliveConfig == kTCPKeepaliveDisabled) {
+ rv = mSocketTransport->SetKeepaliveEnabled(true);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ mTCPKeepaliveConfig = kTCPKeepaliveLongLivedConfig;
+ } else {
+ rv = mSocketTransport->SetKeepaliveEnabled(false);
+ mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpConnection::DisableTCPKeepalives() {
+ MOZ_ASSERT(mSocketTransport);
+ if (!mSocketTransport) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ LOG(("nsHttpConnection::DisableTCPKeepalives [%p]", this));
+ if (mTCPKeepaliveConfig != kTCPKeepaliveDisabled) {
+ nsresult rv = mSocketTransport->SetKeepaliveEnabled(false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
+ }
+ if (mTCPKeepaliveTransitionTimer) {
+ mTCPKeepaliveTransitionTimer->Cancel();
+ mTCPKeepaliveTransitionTimer = nullptr;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsHttpConnection)
+NS_IMPL_RELEASE(nsHttpConnection)
+
+NS_INTERFACE_MAP_BEGIN(nsHttpConnection)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(HttpConnectionBase)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(nsHttpConnection)
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsIInputStreamCallback
+//-----------------------------------------------------------------------------
+
+// called on the socket transport thread
+NS_IMETHODIMP
+nsHttpConnection::OnInputStreamReady(nsIAsyncInputStream* in) {
+ MOZ_ASSERT(in == mSocketIn, "unexpected stream");
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mIdleMonitoring) {
+ MOZ_ASSERT(!mTransaction, "Idle Input Event While Active");
+
+ // The only read event that is protocol compliant for an idle connection
+ // is an EOF, which we check for with CanReuse(). If the data is
+ // something else then just ignore it and suspend checking for EOF -
+ // our normal timers or protocol stack are the place to deal with
+ // any exception logic.
+
+ if (!CanReuse()) {
+ LOG(("Server initiated close of idle conn %p\n", this));
+ Unused << gHttpHandler->ConnMgr()->CloseIdleConnection(this);
+ return NS_OK;
+ }
+
+ LOG(("Input data on idle conn %p, but not closing yet\n", this));
+ return NS_OK;
+ }
+
+ // if the transaction was dropped...
+ if (!mTransaction) {
+ LOG((" no transaction; ignoring event\n"));
+ return NS_OK;
+ }
+
+ nsresult rv = OnSocketReadable();
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ return rv;
+ }
+
+ if (NS_FAILED(rv)) {
+ CloseTransaction(mTransaction, rv);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsIOutputStreamCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpConnection::OnOutputStreamReady(nsIAsyncOutputStream* out) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(out == mSocketOut, "unexpected socket");
+ // if the transaction was dropped...
+ if (!mTransaction) {
+ LOG((" no transaction; ignoring event\n"));
+ return NS_OK;
+ }
+
+ nsresult rv = OnSocketWritable();
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) CloseTransaction(mTransaction, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsITransportEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpConnection::OnTransportStatus(nsITransport* trans, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ if (mTransaction) mTransaction->OnTransportStatus(trans, status, progress);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+// not called on the socket transport thread
+NS_IMETHODIMP
+nsHttpConnection::GetInterface(const nsIID& iid, void** result) {
+ // NOTE: This function is only called on the UI thread via sync proxy from
+ // the socket transport thread. If that weren't the case, then we'd
+ // have to worry about the possibility of mTransaction going away
+ // part-way through this function call. See CloseTransaction.
+
+ // NOTE - there is a bug here, the call to getinterface is proxied off the
+ // nss thread, not the ui thread as the above comment says. So there is
+ // indeed a chance of mTransaction going away. bug 615342
+
+ MOZ_ASSERT(!OnSocketThread(), "on socket thread");
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ {
+ MutexAutoLock lock(mCallbacksLock);
+ callbacks = mCallbacks;
+ }
+ if (callbacks) return callbacks->GetInterface(iid, result);
+ return NS_ERROR_NO_INTERFACE;
+}
+
+void nsHttpConnection::CheckForTraffic(bool check) {
+ if (check) {
+ LOG((" CheckForTraffic conn %p\n", this));
+ if (mSpdySession) {
+ if (PR_IntervalToMilliseconds(IdleTime()) >= 500) {
+ // Send a ping to verify it is still alive if it has been idle
+ // more than half a second, the network changed events are
+ // rate-limited to one per 1000 ms.
+ LOG((" SendPing\n"));
+ mSpdySession->SendPing();
+ } else {
+ LOG((" SendPing skipped due to network activity\n"));
+ }
+ } else {
+ // If not SPDY, Store snapshot amount of data right now
+ mTrafficCount = mTotalBytesWritten + mTotalBytesRead;
+ mTrafficStamp = true;
+ }
+ } else {
+ // mark it as not checked
+ mTrafficStamp = false;
+ }
+}
+
+void nsHttpConnection::SetEvent(nsresult aStatus) {
+ switch (aStatus) {
+ case NS_NET_STATUS_RESOLVING_HOST:
+ mBootstrappedTimings.domainLookupStart = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_RESOLVED_HOST:
+ mBootstrappedTimings.domainLookupEnd = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_CONNECTING_TO:
+ mBootstrappedTimings.connectStart = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_CONNECTED_TO: {
+ TimeStamp tnow = TimeStamp::Now();
+ mBootstrappedTimings.tcpConnectEnd = tnow;
+ mBootstrappedTimings.connectEnd = tnow;
+ mBootstrappedTimings.secureConnectionStart = tnow;
+ break;
+ }
+ case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
+ mBootstrappedTimings.secureConnectionStart = TimeStamp::Now();
+ break;
+ case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
+ mBootstrappedTimings.connectEnd = TimeStamp::Now();
+ break;
+ default:
+ break;
+ }
+}
+
+bool nsHttpConnection::NoClientCertAuth() const {
+ if (!mSocketTransport) {
+ return false;
+ }
+
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ mSocketTransport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl));
+ if (!tlsSocketControl) {
+ return false;
+ }
+
+ return !tlsSocketControl->GetClientCertSent();
+}
+
+WebSocketSupport nsHttpConnection::GetWebSocketSupport() {
+ LOG3(("nsHttpConnection::GetWebSocketSupport"));
+ if (!UsingSpdy()) {
+ return WebSocketSupport::SUPPORTED;
+ }
+ LOG3(("nsHttpConnection::GetWebSocketSupport checking spdy session"));
+ if (mSpdySession) {
+ return mSpdySession->GetWebSocketSupport();
+ }
+
+ return WebSocketSupport::NO_SUPPORT;
+}
+
+bool nsHttpConnection::IsProxyConnectInProgress() {
+ return mState == SETTING_UP_TUNNEL;
+}
+
+bool nsHttpConnection::LastTransactionExpectedNoContent() {
+ return mLastTransactionExpectedNoContent;
+}
+
+void nsHttpConnection::SetLastTransactionExpectedNoContent(bool val) {
+ mLastTransactionExpectedNoContent = val;
+}
+
+bool nsHttpConnection::IsPersistent() { return IsKeepAlive() && !mDontReuse; }
+
+nsAHttpTransaction* nsHttpConnection::Transaction() { return mTransaction; }
+
+nsresult nsHttpConnection::GetSelfAddr(NetAddr* addr) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetSelfAddr(addr);
+}
+
+nsresult nsHttpConnection::GetPeerAddr(NetAddr* addr) {
+ if (!mSocketTransport) {
+ return NS_ERROR_FAILURE;
+ }
+ return mSocketTransport->GetPeerAddr(addr);
+}
+
+bool nsHttpConnection::ResolvedByTRR() {
+ bool val = false;
+ if (mSocketTransport) {
+ mSocketTransport->ResolvedByTRR(&val);
+ }
+ return val;
+}
+
+nsIRequest::TRRMode nsHttpConnection::EffectiveTRRMode() {
+ nsIRequest::TRRMode mode = nsIRequest::TRR_DEFAULT_MODE;
+ if (mSocketTransport) {
+ mSocketTransport->GetEffectiveTRRMode(&mode);
+ }
+ return mode;
+}
+
+TRRSkippedReason nsHttpConnection::TRRSkipReason() {
+ TRRSkippedReason reason = nsITRRSkipReason::TRR_UNSET;
+ if (mSocketTransport) {
+ mSocketTransport->GetTrrSkipReason(&reason);
+ }
+ return reason;
+}
+
+bool nsHttpConnection::GetEchConfigUsed() {
+ bool val = false;
+ if (mSocketTransport) {
+ mSocketTransport->GetEchConfigUsed(&val);
+ }
+ return val;
+}
+
+void nsHttpConnection::HandshakeDoneInternal() {
+ LOG(("nsHttpConnection::HandshakeDoneInternal [this=%p]\n", this));
+ if (mTlsHandshaker->NPNComplete()) {
+ return;
+ }
+
+ ChangeConnectionState(ConnectionState::TRANSFERING);
+
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ GetTLSSocketControl(getter_AddRefs(tlsSocketControl));
+ if (!tlsSocketControl) {
+ mTlsHandshaker->FinishNPNSetup(false, false);
+ return;
+ }
+
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ if (NS_FAILED(
+ tlsSocketControl->GetSecurityInfo(getter_AddRefs(securityInfo)))) {
+ mTlsHandshaker->FinishNPNSetup(false, false);
+ return;
+ }
+ if (!securityInfo) {
+ mTlsHandshaker->FinishNPNSetup(false, false);
+ return;
+ }
+
+ nsAutoCString negotiatedNPN;
+ DebugOnly<nsresult> rvDebug = securityInfo->GetNegotiatedNPN(negotiatedNPN);
+ MOZ_ASSERT(NS_SUCCEEDED(rvDebug));
+
+ bool earlyDataAccepted = false;
+ if (mTlsHandshaker->EarlyDataUsed()) {
+ // Check if early data has been accepted.
+ nsresult rvEarlyData =
+ tlsSocketControl->GetEarlyDataAccepted(&earlyDataAccepted);
+ LOG(
+ ("nsHttpConnection::HandshakeDone [this=%p] - early data "
+ "that was sent during 0RTT %s been accepted [rv=%" PRIx32 "].",
+ this, earlyDataAccepted ? "has" : "has not",
+ static_cast<uint32_t>(rvEarlyData)));
+
+ if (NS_FAILED(rvEarlyData) ||
+ (mTransaction &&
+ NS_FAILED(mTransaction->Finish0RTT(
+ !earlyDataAccepted,
+ negotiatedNPN != mTlsHandshaker->EarlyNegotiatedALPN())))) {
+ LOG(
+ ("nsHttpConection::HandshakeDone [this=%p] closing transaction "
+ "%p",
+ this, mTransaction.get()));
+ if (mTransaction) {
+ mTransaction->Close(NS_ERROR_NET_RESET);
+ }
+ mTlsHandshaker->FinishNPNSetup(false, true);
+ return;
+ }
+ if (mDid0RTTSpdy &&
+ (negotiatedNPN != mTlsHandshaker->EarlyNegotiatedALPN())) {
+ Reset0RttForSpdy();
+ }
+ }
+
+ if (mTlsHandshaker->EarlyDataAvailable() && !earlyDataAccepted) {
+ // When the early-data were used but not accepted, we need to start
+ // from the begining here and start writing the request again.
+ // The same is true if 0RTT was available but not used.
+ if (mSocketIn) {
+ mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+ Unused << ResumeSend();
+ }
+
+ int16_t tlsVersion;
+ tlsSocketControl->GetSSLVersionUsed(&tlsVersion);
+ mConnInfo->SetLessThanTls13(
+ (tlsVersion < nsITLSSocketControl::TLS_VERSION_1_3) &&
+ (tlsVersion != nsITLSSocketControl::SSL_VERSION_UNKNOWN));
+
+ mTlsHandshaker->EarlyDataTelemetry(tlsVersion, earlyDataAccepted,
+ mContentBytesWritten0RTT);
+ mTlsHandshaker->EarlyDataDone();
+
+ if (!earlyDataAccepted) {
+ LOG(
+ ("nsHttpConnection::HandshakeDone [this=%p] early data not "
+ "accepted or early data were not used",
+ this));
+
+ const SpdyInformation* info = gHttpHandler->SpdyInfo();
+ if (negotiatedNPN.Equals(info->VersionString)) {
+ if (mTransaction) {
+ StartSpdy(tlsSocketControl, info->Version);
+ } else {
+ LOG(
+ ("nsHttpConnection::HandshakeDone [this=%p] set "
+ "mContinueHandshakeDone",
+ this));
+ RefPtr<nsHttpConnection> self = this;
+ mContinueHandshakeDone = [self = RefPtr{this},
+ tlsSocketControl(tlsSocketControl),
+ info(info->Version)]() {
+ LOG(("nsHttpConnection do mContinueHandshakeDone [this=%p]",
+ self.get()));
+ self->StartSpdy(tlsSocketControl, info);
+ self->mTlsHandshaker->FinishNPNSetup(true, true);
+ };
+ return;
+ }
+ }
+ } else {
+ LOG(("nsHttpConnection::HandshakeDone [this=%p] - %" PRId64 " bytes "
+ "has been sent during 0RTT.",
+ this, mContentBytesWritten0RTT));
+ mContentBytesWritten = mContentBytesWritten0RTT;
+
+ if (mSpdySession) {
+ // We had already started 0RTT-spdy, now we need to fully set up
+ // spdy, since we know we're sticking with it.
+ LOG(
+ ("nsHttpConnection::HandshakeDone [this=%p] - finishing "
+ "StartSpdy for 0rtt spdy session %p",
+ this, mSpdySession.get()));
+ StartSpdy(tlsSocketControl, mSpdySession->SpdyVersion());
+ }
+ }
+
+ Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy());
+
+ mTlsHandshaker->FinishNPNSetup(true, true);
+ Unused << ResumeSend();
+}
+
+void nsHttpConnection::SetTunnelSetupDone() {
+ MOZ_ASSERT(mProxyConnectStream);
+ MOZ_ASSERT(mState == HttpConnectionState::SETTING_UP_TUNNEL);
+
+ ChangeState(HttpConnectionState::REQUEST);
+ mProxyConnectStream = nullptr;
+}
+
+nsresult nsHttpConnection::CheckTunnelIsNeeded() {
+ switch (mState) {
+ case HttpConnectionState::UNINITIALIZED: {
+ // This is is called first time. Check if we need a tunnel.
+ if (!mTransaction->ConnectionInfo()->UsingConnect()) {
+ ChangeState(HttpConnectionState::REQUEST);
+ return NS_OK;
+ }
+ ChangeState(HttpConnectionState::SETTING_UP_TUNNEL);
+ }
+ [[fallthrough]];
+ case HttpConnectionState::SETTING_UP_TUNNEL: {
+ // When a nsHttpConnection is in this state that means that an
+ // authentication was needed and we are resending a CONNECT
+ // request. This request will include authentication headers.
+ nsresult rv = SetupProxyConnectStream();
+ if (NS_FAILED(rv)) {
+ ChangeState(HttpConnectionState::UNINITIALIZED);
+ }
+ return rv;
+ }
+ case HttpConnectionState::REQUEST:
+ return NS_OK;
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpConnection::SetupProxyConnectStream() {
+ LOG(("nsHttpConnection::SetupStream\n"));
+ NS_ENSURE_TRUE(!mProxyConnectStream, NS_ERROR_ALREADY_INITIALIZED);
+ MOZ_ASSERT(mState == HttpConnectionState::SETTING_UP_TUNNEL);
+
+ nsAutoCString buf;
+ nsHttpRequestHead request;
+ nsresult rv = MakeConnectString(mTransaction, &request, buf,
+ mForWebSocket && mInSpdyTunnel,
+ mTransactionCaps & NS_HTTP_USE_RFP);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = NS_NewCStringInputStream(getter_AddRefs(mProxyConnectStream),
+ std::move(buf));
+ return rv;
+}
+
+nsresult nsHttpConnection::ReadFromStream(nsIInputStream* input, void* closure,
+ const char* buf, uint32_t offset,
+ uint32_t count, uint32_t* countRead) {
+ // thunk for nsIInputStream instance
+ nsHttpConnection* conn = (nsHttpConnection*)closure;
+ return conn->OnReadSegment(buf, count, countRead);
+}
+
+nsresult nsHttpConnection::SendConnectRequest(void* closure,
+ uint32_t* transactionBytes) {
+ LOG((" writing CONNECT request stream\n"));
+ return mProxyConnectStream->ReadSegments(ReadFromStream, closure,
+ nsIOService::gDefaultSegmentSize,
+ transactionBytes);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h
new file mode 100644
index 0000000000..6f00591a2e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -0,0 +1,388 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpConnection_h__
+#define nsHttpConnection_h__
+
+#include <functional>
+#include "HttpConnectionBase.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpResponseHead.h"
+#include "nsAHttpTransaction.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+#include "prinrval.h"
+#include "mozilla/Mutex.h"
+#include "ARefBase.h"
+#include "TimingStruct.h"
+#include "HttpTrafficAnalyzer.h"
+#include "TlsHandshaker.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsISocketTransport.h"
+#include "nsISupportsPriority.h"
+#include "nsITimer.h"
+#include "nsITlsHandshakeListener.h"
+
+class nsISocketTransport;
+class nsITLSSocketControl;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpHandler;
+class ASpdySession;
+
+// 1dcc863e-db90-4652-a1fe-13fea0b54e46
+#define NS_HTTPCONNECTION_IID \
+ { \
+ 0x1dcc863e, 0xdb90, 0x4652, { \
+ 0xa1, 0xfe, 0x13, 0xfe, 0xa0, 0xb5, 0x4e, 0x46 \
+ } \
+ }
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection - represents a connection to a HTTP server (or proxy)
+//
+// NOTE: this objects lives on the socket thread only. it should not be
+// accessed from any other thread.
+//-----------------------------------------------------------------------------
+
+class nsHttpConnection final : public HttpConnectionBase,
+ public nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter,
+ public nsIInputStreamCallback,
+ public nsIOutputStreamCallback,
+ public nsITransportEventSink,
+ public nsIInterfaceRequestor {
+ private:
+ virtual ~nsHttpConnection();
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTPCONNECTION_IID)
+ NS_DECL_HTTPCONNECTIONBASE
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ nsHttpConnection();
+
+ // Initialize the connection:
+ // info - specifies the connection parameters.
+ // maxHangTime - limits the amount of time this connection can spend on a
+ // single transaction before it should no longer be kept
+ // alive. a value of 0xffff indicates no limit.
+ [[nodiscard]] virtual nsresult Init(nsHttpConnectionInfo* info,
+ uint16_t maxHangTime, nsISocketTransport*,
+ nsIAsyncInputStream*,
+ nsIAsyncOutputStream*,
+ bool connectedTransport, nsresult status,
+ nsIInterfaceRequestor*, PRIntervalTime,
+ bool forWebSocket);
+
+ //-------------------------------------------------------------------------
+ // XXX document when these are ok to call
+
+ bool IsKeepAlive() {
+ return (mUsingSpdyVersion != SpdyVersion::NONE) ||
+ (mKeepAliveMask && mKeepAlive);
+ }
+
+ // Returns time in seconds for how long connection can be reused.
+ uint32_t TimeToLive();
+
+ bool NeedSpdyTunnel() {
+ return mConnInfo->UsingHttpsProxy() && !mHasTLSTransportLayer &&
+ mConnInfo->UsingConnect();
+ }
+
+ // A connection is forced into plaintext when it is intended to be used as a
+ // CONNECT tunnel but the setup fails. The plaintext only carries the CONNECT
+ // error.
+ void ForcePlainText() { mForcePlainText = true; }
+
+ bool IsUrgentStartPreferred() const {
+ return mUrgentStartPreferredKnown && mUrgentStartPreferred;
+ }
+ void SetUrgentStartPreferred(bool urgent);
+
+ void SetIsReusedAfter(uint32_t afterMilliseconds);
+
+ int64_t MaxBytesRead() { return mMaxBytesRead; }
+ HttpVersion GetLastHttpResponseVersion() { return mLastHttpResponseVersion; }
+
+ friend class HttpConnectionForceIO;
+ friend class TlsHandshaker;
+
+ // When a persistent connection is in the connection manager idle
+ // connection pool, the nsHttpConnection still reads errors and hangups
+ // on the socket so that it can be proactively released if the server
+ // initiates a termination. Only call on socket thread.
+ void BeginIdleMonitoring();
+ void EndIdleMonitoring();
+
+ bool UsingSpdy() override { return (mUsingSpdyVersion != SpdyVersion::NONE); }
+ SpdyVersion GetSpdyVersion() { return mUsingSpdyVersion; }
+ bool EverUsedSpdy() { return mEverUsedSpdy; }
+ bool UsingHttp3() override { return false; }
+
+ // true when connection SSL NPN phase is complete and we know
+ // authoritatively whether UsingSpdy() or not.
+ bool ReportedNPN() { return mReportedSpdy; }
+
+ // When the connection is active this is called up to once every 1 second
+ // return the interval (in seconds) that the connection next wants to
+ // have this invoked. It might happen sooner depending on the needs of
+ // other connections.
+ uint32_t ReadTimeoutTick(PRIntervalTime now);
+
+ // For Active and Idle connections, this will be called when
+ // mTCPKeepaliveTransitionTimer fires, to check if the TCP keepalive config
+ // should move from short-lived (fast-detect) to long-lived.
+ static void UpdateTCPKeepalive(nsITimer* aTimer, void* aClosure);
+
+ // When the connection is active this is called every second
+ void ReadTimeoutTick();
+
+ int64_t ContentBytesWritten() { return mContentBytesWritten; }
+
+ void SetupSecondaryTLS();
+ void SetInSpdyTunnel();
+
+ // Check active connections for traffic (or not). SPDY connections send a
+ // ping, ordinary HTTP connections get some time to get traffic to be
+ // considered alive.
+ void CheckForTraffic(bool check);
+
+ // NoTraffic() returns true if there's been no traffic on the (non-spdy)
+ // connection since CheckForTraffic() was called.
+ bool NoTraffic() {
+ return mTrafficStamp &&
+ (mTrafficCount == (mTotalBytesWritten + mTotalBytesRead));
+ }
+
+ // Return true when the socket this connection is using has not been
+ // authenticated using a client certificate. Before SSL negotiation
+ // has finished this returns false.
+ bool NoClientCertAuth() const override;
+
+ WebSocketSupport GetWebSocketSupport() override;
+
+ int64_t BytesWritten() override { return mTotalBytesWritten; }
+
+ nsISocketTransport* Transport() override { return mSocketTransport; }
+
+ nsresult GetSelfAddr(NetAddr* addr) override;
+ nsresult GetPeerAddr(NetAddr* addr) override;
+ bool ResolvedByTRR() override;
+ bool GetEchConfigUsed() override;
+ nsIRequest::TRRMode EffectiveTRRMode() override;
+ TRRSkippedReason TRRSkipReason() override;
+ bool IsForWebSocket() { return mForWebSocket; }
+
+ // The following functions are related to setting up a tunnel.
+ [[nodiscard]] static nsresult MakeConnectString(
+ nsAHttpTransaction* trans, nsHttpRequestHead* request, nsACString& result,
+ bool h2ws, bool aShouldResistFingerprinting);
+ [[nodiscard]] static nsresult ReadFromStream(nsIInputStream*, void*,
+ const char*, uint32_t, uint32_t,
+ uint32_t*);
+
+ nsresult CreateTunnelStream(nsAHttpTransaction* httpTransaction,
+ nsHttpConnection** aHttpConnection,
+ bool aIsWebSocket = false);
+
+ bool RequestDone() { return mRequestDone; }
+
+ private:
+ enum HttpConnectionState {
+ UNINITIALIZED,
+ SETTING_UP_TUNNEL,
+ REQUEST,
+ } mState{HttpConnectionState::UNINITIALIZED};
+ void ChangeState(HttpConnectionState newState);
+
+ // Tunnel retated functions:
+ bool TunnelSetupInProgress() { return mState == SETTING_UP_TUNNEL; }
+ void SetTunnelSetupDone();
+ nsresult CheckTunnelIsNeeded();
+ nsresult SetupProxyConnectStream();
+ nsresult SendConnectRequest(void* closure, uint32_t* transactionBytes);
+
+ void HandleTunnelResponse(uint16_t responseStatus, bool* reset);
+ void HandleWebSocketResponse(nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ uint16_t responseStatus);
+ void ResetTransaction(RefPtr<nsAHttpTransaction>&& trans);
+
+ // Value (set in mTCPKeepaliveConfig) indicates which set of prefs to use.
+ enum TCPKeepaliveConfig {
+ kTCPKeepaliveDisabled = 0,
+ kTCPKeepaliveShortLivedConfig,
+ kTCPKeepaliveLongLivedConfig
+ };
+
+ [[nodiscard]] nsresult OnTransactionDone(nsresult reason);
+ [[nodiscard]] nsresult OnSocketWritable();
+ [[nodiscard]] nsresult OnSocketReadable();
+
+ PRIntervalTime IdleTime();
+ bool IsAlive();
+
+ // Start the Spdy transaction handler when NPN indicates spdy/*
+ void StartSpdy(nsITLSSocketControl* ssl, SpdyVersion spdyVersion);
+ // Like the above, but do the bare minimum to do 0RTT data, so we can back
+ // it out, if necessary
+ void Start0RTTSpdy(SpdyVersion spdyVersion);
+
+ // Helpers for Start*Spdy
+ nsresult TryTakeSubTransactions(nsTArray<RefPtr<nsAHttpTransaction> >& list);
+ nsresult MoveTransactionsToSpdy(nsresult status,
+ nsTArray<RefPtr<nsAHttpTransaction> >& list);
+
+ // Directly Add a transaction to an active connection for SPDY
+ [[nodiscard]] nsresult AddTransaction(nsAHttpTransaction*, int32_t);
+
+ // Used to set TCP keepalives for fast detection of dead connections during
+ // an initial period, and slower detection for long-lived connections.
+ [[nodiscard]] nsresult StartShortLivedTCPKeepalives();
+ [[nodiscard]] nsresult StartLongLivedTCPKeepalives();
+ [[nodiscard]] nsresult DisableTCPKeepalives();
+
+ bool CheckCanWrite0RTTData();
+ void PostProcessNPNSetup(bool handshakeSucceeded, bool hasSecurityInfo,
+ bool earlyDataUsed);
+ void Reset0RttForSpdy();
+ void HandshakeDoneInternal();
+ uint32_t TransactionCaps() const { return mTransactionCaps; }
+
+ void MarkAsDontReuse();
+
+ private:
+ // mTransaction only points to the HTTP Transaction callbacks if the
+ // transaction is open, otherwise it is null.
+ RefPtr<nsAHttpTransaction> mTransaction;
+
+ RefPtr<TlsHandshaker> mTlsHandshaker;
+
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+
+ nsresult mSocketInCondition{NS_ERROR_NOT_INITIALIZED};
+ nsresult mSocketOutCondition{NS_ERROR_NOT_INITIALIZED};
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+
+ PRIntervalTime mLastReadTime{0};
+ PRIntervalTime mLastWriteTime{0};
+ // max download time before dropping keep-alive status
+ PRIntervalTime mMaxHangTime{0};
+ PRIntervalTime mIdleTimeout; // value of keep-alive: timeout=
+ PRIntervalTime mConsiderReusedAfterInterval{0};
+ PRIntervalTime mConsiderReusedAfterEpoch{0};
+ int64_t mCurrentBytesRead{0}; // data read per activation
+ int64_t mMaxBytesRead{0}; // max read in 1 activation
+ int64_t mTotalBytesRead{0}; // total data read
+ int64_t mContentBytesWritten{0}; // does not include CONNECT tunnel or TLS
+
+ RefPtr<nsIAsyncInputStream> mInputOverflow;
+
+ // Whether the first non-null transaction dispatched on this connection was
+ // urgent-start or not
+ bool mUrgentStartPreferred{false};
+ // A flag to prevent reset of mUrgentStartPreferred by subsequent transactions
+ bool mUrgentStartPreferredKnown{false};
+ bool mConnectedTransport{false};
+ // assume to keep-alive by default
+ bool mKeepAlive{true};
+ bool mKeepAliveMask{true};
+ bool mDontReuse{false};
+ bool mIsReused{false};
+ bool mLastTransactionExpectedNoContent{false};
+ bool mIdleMonitoring{false};
+ bool mInSpdyTunnel{false};
+ bool mForcePlainText{false};
+
+ // A snapshot of current number of transfered bytes
+ int64_t mTrafficCount{0};
+ bool mTrafficStamp{false}; // true then the above is set
+
+ // The number of <= HTTP/1.1 transactions performed on this connection. This
+ // excludes spdy transactions.
+ uint32_t mHttp1xTransactionCount{0};
+
+ // Keep-Alive: max="mRemainingConnectionUses" provides the number of future
+ // transactions (including the current one) that the server expects to allow
+ // on this persistent connection.
+ uint32_t mRemainingConnectionUses{0xffffffff};
+
+ // version level in use, 0 if unused
+ SpdyVersion mUsingSpdyVersion{SpdyVersion::NONE};
+
+ RefPtr<ASpdySession> mSpdySession;
+ RefPtr<ASpdySession> mWebSocketHttp2Session;
+ int32_t mPriority{nsISupportsPriority::PRIORITY_NORMAL};
+ bool mReportedSpdy{false};
+
+ // mUsingSpdyVersion is cleared when mSpdySession is freed, this is permanent
+ bool mEverUsedSpdy{false};
+
+ // mLastHttpResponseVersion stores the last response's http version seen.
+ HttpVersion mLastHttpResponseVersion{HttpVersion::v1_1};
+
+ // If a large keepalive has been requested for any trans,
+ // scale the default by this factor
+ uint32_t mDefaultTimeoutFactor{1};
+
+ bool mResponseTimeoutEnabled{false};
+
+ // Flag to indicate connection is in inital keepalive period (fast detect).
+ uint32_t mTCPKeepaliveConfig{kTCPKeepaliveDisabled};
+ nsCOMPtr<nsITimer> mTCPKeepaliveTransitionTimer;
+
+ private:
+ // For ForceSend()
+ static void ForceSendIO(nsITimer* aTimer, void* aClosure);
+ [[nodiscard]] nsresult MaybeForceSendIO();
+ bool mForceSendPending{false};
+ nsCOMPtr<nsITimer> mForceSendTimer;
+
+ int64_t mContentBytesWritten0RTT{0};
+ bool mDid0RTTSpdy{false};
+
+ nsresult mErrorBeforeConnect = NS_OK;
+
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+
+ // This flag indicates if the connection is used for WebSocket.
+ // - When true and mInSpdyTunnel is also true: WebSocket over HTTP/2.
+ // - When true and mInSpdyTunnel is false: WebSocket over HTTP/1.1.
+ bool mForWebSocket{false};
+
+ std::function<void()> mContinueHandshakeDone{nullptr};
+
+ private:
+ bool mThroughCaptivePortal;
+ int64_t mTotalBytesWritten = 0; // does not include CONNECT tunnel
+
+ nsCOMPtr<nsIInputStream> mProxyConnectStream;
+
+ bool mRequestDone{false};
+ bool mHasTLSTransportLayer{false};
+ bool mTransactionDisallowHttp3{false};
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnection, NS_HTTPCONNECTION_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpConnection_h__
diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.cpp b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
new file mode 100644
index 0000000000..e91128d85a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
@@ -0,0 +1,570 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "nsHttpConnectionInfo.h"
+
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsComponentManagerUtils.h"
+#include "nsICryptoHash.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIProtocolProxyService.h"
+#include "nsHttpHandler.h"
+#include "nsNetCID.h"
+#include "nsProxyInfo.h"
+#include "prnetdb.h"
+
+static nsresult SHA256(const char* aPlainText, nsAutoCString& aResult) {
+ nsresult rv;
+ nsCOMPtr<nsICryptoHash> hasher =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpDigestAuth: no crypto hash!\n"));
+ return rv;
+ }
+ rv = hasher->Init(nsICryptoHash::SHA256);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = hasher->Update((unsigned char*)aPlainText, strlen(aPlainText));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return hasher->Finish(false, aResult);
+}
+
+namespace mozilla {
+namespace net {
+
+nsHttpConnectionInfo::nsHttpConnectionInfo(
+ const nsACString& originHost, int32_t originPort,
+ const nsACString& npnToken, const nsACString& username,
+ nsProxyInfo* proxyInfo, const OriginAttributes& originAttributes,
+ bool endToEndSSL, bool aIsHttp3, bool aWebTransport)
+ : mRoutedPort(443), mLessThanTls13(false) {
+ Init(originHost, originPort, npnToken, username, proxyInfo, originAttributes,
+ endToEndSSL, aIsHttp3, aWebTransport);
+}
+
+nsHttpConnectionInfo::nsHttpConnectionInfo(
+ const nsACString& originHost, int32_t originPort,
+ const nsACString& npnToken, const nsACString& username,
+ nsProxyInfo* proxyInfo, const OriginAttributes& originAttributes,
+ const nsACString& routedHost, int32_t routedPort, bool aIsHttp3,
+ bool aWebTransport)
+ : mLessThanTls13(false) {
+ mEndToEndSSL = true; // so DefaultPort() works
+ mRoutedPort = routedPort == -1 ? DefaultPort() : routedPort;
+
+ if (!originHost.Equals(routedHost) || (originPort != routedPort) ||
+ aIsHttp3) {
+ mRoutedHost = routedHost;
+ }
+ Init(originHost, originPort, npnToken, username, proxyInfo, originAttributes,
+ true, aIsHttp3, aWebTransport);
+}
+
+void nsHttpConnectionInfo::Init(const nsACString& host, int32_t port,
+ const nsACString& npnToken,
+ const nsACString& username,
+ nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes,
+ bool e2eSSL, bool aIsHttp3,
+ bool aWebTransport) {
+ LOG(("Init nsHttpConnectionInfo @%p\n", this));
+
+ MOZ_RELEASE_ASSERT(!aWebTransport || aIsHttp3);
+
+ mUsername = username;
+ mProxyInfo = proxyInfo;
+ mEndToEndSSL = e2eSSL;
+ mUsingConnect = false;
+ mNPNToken = npnToken;
+ mIsHttp3 = aIsHttp3;
+ mWebTransport = aWebTransport;
+ mOriginAttributes = originAttributes;
+ mTlsFlags = 0x0;
+ mIsTrrServiceChannel = false;
+ mTRRMode = nsIRequest::TRR_DEFAULT_MODE;
+ mIPv4Disabled = false;
+ mIPv6Disabled = false;
+ mHasIPHintAddress = false;
+
+ mUsingHttpsProxy = (proxyInfo && proxyInfo->IsHTTPS());
+ mUsingHttpProxy = mUsingHttpsProxy || (proxyInfo && proxyInfo->IsHTTP());
+
+ if (mUsingHttpProxy) {
+ mUsingConnect = mEndToEndSSL; // SSL always uses CONNECT
+ uint32_t resolveFlags = 0;
+ if (NS_SUCCEEDED(mProxyInfo->GetResolveFlags(&resolveFlags)) &&
+ resolveFlags & nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL) {
+ mUsingConnect = true;
+ }
+ }
+
+ SetOriginServer(host, port);
+}
+
+void nsHttpConnectionInfo::BuildHashKey() {
+ //
+ // build hash key:
+ //
+ // the hash key uniquely identifies the connection type. two connections
+ // are "equal" if they end up talking the same protocol to the same server
+ // and are both used for anonymous or non-anonymous connection only;
+ // anonymity of the connection is setup later from nsHttpChannel::AsyncOpen
+ // where we know we use anonymous connection (LOAD_ANONYMOUS load flag)
+ //
+
+ const char* keyHost;
+ int32_t keyPort;
+
+ if (mUsingHttpProxy && !mUsingConnect) {
+ keyHost = ProxyHost();
+ keyPort = ProxyPort();
+ } else {
+ keyHost = Origin();
+ keyPort = OriginPort();
+ }
+
+ // The hashkey has 9 fields followed by host connection info
+ // byte 0 is P/T/. {P,T} for Plaintext/TLS Proxy over HTTP
+ // byte 1 is S/. S is for end to end ssl such as https:// uris
+ // byte 2 is A/. A is for an anonymous channel (no cookies, etc..)
+ // byte 3 is P/. P is for a private browising channel
+ // byte 4 is I/. I is for insecure scheme on TLS for http:// uris
+ // byte 5 is X/. X is for disallow_spdy flag
+ // byte 6 is C/. C is for be Conservative
+ // byte 7 is B/. B is for allowing client certs on an anonymous channel
+ // byte 8 is F/. F is for indicating a fallback connection
+ // byte 9 is W/. W is for indicating a webTransport
+ // Note: when adding/removing fields from this list which do not have
+ // corresponding data fields on the object itself, you may also need to
+ // modify RebuildHashKey.
+
+ const auto keyTemplate =
+ std::string(UnderlyingIndex(HashKeyIndex::End), '.') +
+ std::string("[tlsflags0x00000000]");
+ mHashKey.Assign(keyTemplate.c_str());
+
+ mHashKey.Append(keyHost);
+ mHashKey.Append(':');
+ mHashKey.AppendInt(keyPort);
+ if (!mUsername.IsEmpty()) {
+ mHashKey.Append('[');
+ mHashKey.Append(mUsername);
+ mHashKey.Append(']');
+ }
+
+ if (mUsingHttpsProxy) {
+ SetHashCharAt('T', HashKeyIndex::Proxy);
+ } else if (mUsingHttpProxy) {
+ SetHashCharAt('P', HashKeyIndex::Proxy);
+ }
+ if (mEndToEndSSL) {
+ SetHashCharAt('S', HashKeyIndex::EndToEndSSL);
+ }
+
+ if (mWebTransport) {
+ SetHashCharAt('W', HashKeyIndex::WebTransport);
+ }
+
+ // NOTE: for transparent proxies (e.g., SOCKS) we need to encode the proxy
+ // info in the hash key (this ensures that we will continue to speak the
+ // right protocol even if our proxy preferences change).
+ //
+ // NOTE: for SSL tunnels add the proxy information to the cache key.
+ // We cannot use the proxy as the host parameter (as we do for non SSL)
+ // because this is a single host tunnel, but we need to include the proxy
+ // information so that a change in proxy config will mean this connection
+ // is not reused
+
+ // NOTE: Adding the username and the password provides a means to isolate
+ // keep-alive to the URL bar domain as well: If the username is the URL bar
+ // domain, keep-alive connections are not reused by resources bound to
+ // different URL bar domains as the respective hash keys are not matching.
+
+ if ((!mUsingHttpProxy && ProxyHost()) || (mUsingHttpProxy && mUsingConnect)) {
+ mHashKey.AppendLiteral(" (");
+ mHashKey.Append(ProxyType());
+ mHashKey.Append(':');
+ mHashKey.Append(ProxyHost());
+ mHashKey.Append(':');
+ mHashKey.AppendInt(ProxyPort());
+ mHashKey.Append(')');
+ mHashKey.Append('[');
+ mHashKey.Append(ProxyUsername());
+ mHashKey.Append(':');
+ const char* password = ProxyPassword();
+ if (strlen(password) > 0) {
+ nsAutoCString digestedPassword;
+ nsresult rv = SHA256(password, digestedPassword);
+ if (rv == NS_OK) {
+ mHashKey.Append(digestedPassword);
+ }
+ }
+ mHashKey.Append(']');
+ }
+
+ if (!mRoutedHost.IsEmpty()) {
+ mHashKey.AppendLiteral(" <ROUTE-via ");
+ mHashKey.Append(mRoutedHost);
+ mHashKey.Append(':');
+ mHashKey.AppendInt(mRoutedPort);
+ mHashKey.Append('>');
+ }
+
+ if (!mNPNToken.IsEmpty()) {
+ mHashKey.AppendLiteral(" {NPN-TOKEN ");
+ mHashKey.Append(mNPNToken);
+ mHashKey.AppendLiteral("}");
+ }
+
+ if (GetTRRMode() != nsIRequest::TRR_DEFAULT_MODE) {
+ // When connecting with another TRR mode, we enforce a separate connection
+ // hashkey so that we also can trigger a fresh DNS resolver that then
+ // doesn't use TRR as the previous connection might have.
+ mHashKey.AppendLiteral("[TRR:");
+ mHashKey.AppendInt(GetTRRMode());
+ mHashKey.AppendLiteral("]");
+ }
+
+ if (GetIPv4Disabled()) {
+ mHashKey.AppendLiteral("[!v4]");
+ }
+
+ if (GetIPv6Disabled()) {
+ mHashKey.AppendLiteral("[!v6]");
+ }
+
+ if (mProxyInfo) {
+ const nsCString& connectionIsolationKey =
+ mProxyInfo->ConnectionIsolationKey();
+ if (!connectionIsolationKey.IsEmpty()) {
+ mHashKey.AppendLiteral("{CIK ");
+ mHashKey.Append(connectionIsolationKey);
+ mHashKey.AppendLiteral("}");
+ }
+ if (mProxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
+ mHashKey.AppendLiteral("{TPRH}");
+ }
+ }
+
+ nsAutoCString originAttributes;
+ mOriginAttributes.CreateSuffix(originAttributes);
+ mHashKey.Append(originAttributes);
+}
+
+void nsHttpConnectionInfo::RebuildHashKey() {
+ // Create copies of all properties stored in our hash key.
+ bool isAnonymous = GetAnonymous();
+ bool isPrivate = GetPrivate();
+ bool isInsecureScheme = GetInsecureScheme();
+ bool isNoSpdy = GetNoSpdy();
+ bool isBeConservative = GetBeConservative();
+ bool isAnonymousAllowClientCert = GetAnonymousAllowClientCert();
+ bool isFallback = GetFallbackConnection();
+
+ BuildHashKey();
+
+ // Restore all of those properties.
+ SetAnonymous(isAnonymous);
+ SetPrivate(isPrivate);
+ SetInsecureScheme(isInsecureScheme);
+ SetNoSpdy(isNoSpdy);
+ SetBeConservative(isBeConservative);
+ SetAnonymousAllowClientCert(isAnonymousAllowClientCert);
+ SetFallbackConnection(isFallback);
+}
+
+void nsHttpConnectionInfo::SetOriginServer(const nsACString& host,
+ int32_t port) {
+ mOrigin = host;
+ mOriginPort = port == -1 ? DefaultPort() : port;
+ // Use BuildHashKey() since this can only be called when constructing an
+ // nsHttpConnectionInfo object.
+ MOZ_DIAGNOSTIC_ASSERT(mHashKey.IsEmpty());
+ BuildHashKey();
+}
+
+// Note that this function needs to be synced with
+// nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs to make sure
+// nsHttpConnectionInfo can be serialized/deserialized.
+already_AddRefed<nsHttpConnectionInfo> nsHttpConnectionInfo::Clone() const {
+ RefPtr<nsHttpConnectionInfo> clone;
+ if (mRoutedHost.IsEmpty()) {
+ clone = new nsHttpConnectionInfo(mOrigin, mOriginPort, mNPNToken, mUsername,
+ mProxyInfo, mOriginAttributes,
+ mEndToEndSSL, mIsHttp3, mWebTransport);
+ } else {
+ MOZ_ASSERT(mEndToEndSSL);
+ clone = new nsHttpConnectionInfo(mOrigin, mOriginPort, mNPNToken, mUsername,
+ mProxyInfo, mOriginAttributes, mRoutedHost,
+ mRoutedPort, mIsHttp3, mWebTransport);
+ }
+
+ // Make sure the anonymous, insecure-scheme, and private flags are transferred
+ clone->SetAnonymous(GetAnonymous());
+ clone->SetPrivate(GetPrivate());
+ clone->SetInsecureScheme(GetInsecureScheme());
+ clone->SetNoSpdy(GetNoSpdy());
+ clone->SetBeConservative(GetBeConservative());
+ clone->SetAnonymousAllowClientCert(GetAnonymousAllowClientCert());
+ clone->SetFallbackConnection(GetFallbackConnection());
+ clone->SetTlsFlags(GetTlsFlags());
+ clone->SetIsTrrServiceChannel(GetIsTrrServiceChannel());
+ clone->SetTRRMode(GetTRRMode());
+ clone->SetIPv4Disabled(GetIPv4Disabled());
+ clone->SetIPv6Disabled(GetIPv6Disabled());
+ clone->SetHasIPHintAddress(HasIPHintAddress());
+ clone->SetEchConfig(GetEchConfig());
+ MOZ_ASSERT(clone->Equals(this));
+
+ return clone.forget();
+}
+
+already_AddRefed<nsHttpConnectionInfo>
+nsHttpConnectionInfo::CloneAndAdoptHTTPSSVCRecord(
+ nsISVCBRecord* aRecord) const {
+ MOZ_ASSERT(aRecord);
+
+ // Get the domain name of this HTTPS RR. This name will be assigned to
+ // mRoutedHost in the new connection info.
+ nsAutoCString name;
+ aRecord->GetName(name);
+
+ // Try to get the port and Alpn. If this record has SvcParamKeyPort defined,
+ // the new port will be used as mRoutedPort.
+ Maybe<uint16_t> port = aRecord->GetPort();
+ Maybe<std::tuple<nsCString, SupportedAlpnRank>> alpn = aRecord->GetAlpn();
+
+ // Let the new conn info learn h3 will be used.
+ bool isHttp3 = alpn ? mozilla::net::IsHttp3(std::get<1>(*alpn)) : false;
+
+ LOG(("HTTPSSVC: use new routed host (%s) and new npnToken (%s)", name.get(),
+ alpn ? std::get<0>(*alpn).get() : "None"));
+
+ RefPtr<nsHttpConnectionInfo> clone;
+ if (name.IsEmpty()) {
+ clone = new nsHttpConnectionInfo(
+ mOrigin, mOriginPort, alpn ? std::get<0>(*alpn) : EmptyCString(),
+ mUsername, mProxyInfo, mOriginAttributes, mEndToEndSSL, isHttp3);
+ } else {
+ MOZ_ASSERT(mEndToEndSSL);
+ clone = new nsHttpConnectionInfo(
+ mOrigin, mOriginPort, alpn ? std::get<0>(*alpn) : EmptyCString(),
+ mUsername, mProxyInfo, mOriginAttributes, name,
+ port ? *port : mOriginPort, isHttp3, mWebTransport);
+ }
+
+ // Make sure the anonymous, insecure-scheme, and private flags are transferred
+ clone->SetAnonymous(GetAnonymous());
+ clone->SetPrivate(GetPrivate());
+ clone->SetInsecureScheme(GetInsecureScheme());
+ clone->SetNoSpdy(GetNoSpdy());
+ clone->SetBeConservative(GetBeConservative());
+ clone->SetAnonymousAllowClientCert(GetAnonymousAllowClientCert());
+ clone->SetFallbackConnection(GetFallbackConnection());
+ clone->SetTlsFlags(GetTlsFlags());
+ clone->SetIsTrrServiceChannel(GetIsTrrServiceChannel());
+ clone->SetTRRMode(GetTRRMode());
+ clone->SetIPv4Disabled(GetIPv4Disabled());
+ clone->SetIPv6Disabled(GetIPv6Disabled());
+
+ bool hasIPHint = false;
+ Unused << aRecord->GetHasIPHintAddress(&hasIPHint);
+ if (hasIPHint) {
+ clone->SetHasIPHintAddress(hasIPHint);
+ }
+
+ nsAutoCString echConfig;
+ Unused << aRecord->GetEchConfig(echConfig);
+ clone->SetEchConfig(echConfig);
+
+ return clone.forget();
+}
+
+/* static */
+void nsHttpConnectionInfo::SerializeHttpConnectionInfo(
+ nsHttpConnectionInfo* aInfo, HttpConnectionInfoCloneArgs& aArgs) {
+ aArgs.host() = aInfo->GetOrigin();
+ aArgs.port() = aInfo->OriginPort();
+ aArgs.npnToken() = aInfo->GetNPNToken();
+ aArgs.username() = aInfo->GetUsername();
+ aArgs.originAttributes() = aInfo->GetOriginAttributes();
+ aArgs.endToEndSSL() = aInfo->EndToEndSSL();
+ aArgs.routedHost() = aInfo->GetRoutedHost();
+ aArgs.routedPort() = aInfo->RoutedPort();
+ aArgs.anonymous() = aInfo->GetAnonymous();
+ aArgs.aPrivate() = aInfo->GetPrivate();
+ aArgs.insecureScheme() = aInfo->GetInsecureScheme();
+ aArgs.noSpdy() = aInfo->GetNoSpdy();
+ aArgs.beConservative() = aInfo->GetBeConservative();
+ aArgs.anonymousAllowClientCert() = aInfo->GetAnonymousAllowClientCert();
+ aArgs.tlsFlags() = aInfo->GetTlsFlags();
+ aArgs.isTrrServiceChannel() = aInfo->GetTRRMode();
+ aArgs.trrMode() = aInfo->GetTRRMode();
+ aArgs.isIPv4Disabled() = aInfo->GetIPv4Disabled();
+ aArgs.isIPv6Disabled() = aInfo->GetIPv6Disabled();
+ aArgs.isHttp3() = aInfo->IsHttp3();
+ aArgs.hasIPHintAddress() = aInfo->HasIPHintAddress();
+ aArgs.echConfig() = aInfo->GetEchConfig();
+ aArgs.webTransport() = aInfo->GetWebTransport();
+
+ if (!aInfo->ProxyInfo()) {
+ return;
+ }
+
+ nsTArray<ProxyInfoCloneArgs> proxyInfoArray;
+ nsProxyInfo::SerializeProxyInfo(aInfo->ProxyInfo(), proxyInfoArray);
+ aArgs.proxyInfo() = std::move(proxyInfoArray);
+}
+
+// This function needs to be synced with nsHttpConnectionInfo::Clone.
+/* static */
+already_AddRefed<nsHttpConnectionInfo>
+nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(
+ const HttpConnectionInfoCloneArgs& aInfoArgs) {
+ nsProxyInfo* pi = nsProxyInfo::DeserializeProxyInfo(aInfoArgs.proxyInfo());
+ RefPtr<nsHttpConnectionInfo> cinfo;
+ if (aInfoArgs.routedHost().IsEmpty()) {
+ cinfo = new nsHttpConnectionInfo(
+ aInfoArgs.host(), aInfoArgs.port(), aInfoArgs.npnToken(),
+ aInfoArgs.username(), pi, aInfoArgs.originAttributes(),
+ aInfoArgs.endToEndSSL(), aInfoArgs.isHttp3(), aInfoArgs.webTransport());
+ } else {
+ MOZ_ASSERT(aInfoArgs.endToEndSSL());
+ cinfo = new nsHttpConnectionInfo(
+ aInfoArgs.host(), aInfoArgs.port(), aInfoArgs.npnToken(),
+ aInfoArgs.username(), pi, aInfoArgs.originAttributes(),
+ aInfoArgs.routedHost(), aInfoArgs.routedPort(), aInfoArgs.isHttp3(),
+ aInfoArgs.webTransport());
+ }
+
+ // Make sure the anonymous, insecure-scheme, and private flags are transferred
+ cinfo->SetAnonymous(aInfoArgs.anonymous());
+ cinfo->SetPrivate(aInfoArgs.aPrivate());
+ cinfo->SetInsecureScheme(aInfoArgs.insecureScheme());
+ cinfo->SetNoSpdy(aInfoArgs.noSpdy());
+ cinfo->SetBeConservative(aInfoArgs.beConservative());
+ cinfo->SetAnonymousAllowClientCert(aInfoArgs.anonymousAllowClientCert());
+ cinfo->SetFallbackConnection(aInfoArgs.fallbackConnection());
+ cinfo->SetTlsFlags(aInfoArgs.tlsFlags());
+ cinfo->SetIsTrrServiceChannel(aInfoArgs.isTrrServiceChannel());
+ cinfo->SetTRRMode(static_cast<nsIRequest::TRRMode>(aInfoArgs.trrMode()));
+ cinfo->SetIPv4Disabled(aInfoArgs.isIPv4Disabled());
+ cinfo->SetIPv6Disabled(aInfoArgs.isIPv6Disabled());
+ cinfo->SetHasIPHintAddress(aInfoArgs.hasIPHintAddress());
+ cinfo->SetEchConfig(aInfoArgs.echConfig());
+
+ return cinfo.forget();
+}
+
+void nsHttpConnectionInfo::CloneAsDirectRoute(nsHttpConnectionInfo** outCI) {
+ // Explicitly use an empty npnToken when |mIsHttp3| is true, since we want to
+ // create a non-http3 connection info.
+ RefPtr<nsHttpConnectionInfo> clone = new nsHttpConnectionInfo(
+ mOrigin, mOriginPort,
+ (mRoutedHost.IsEmpty() && !mIsHttp3) ? mNPNToken : ""_ns, mUsername,
+ mProxyInfo, mOriginAttributes, mEndToEndSSL, false, mWebTransport);
+ // Make sure the anonymous, insecure-scheme, and private flags are transferred
+ clone->SetAnonymous(GetAnonymous());
+ clone->SetPrivate(GetPrivate());
+ clone->SetInsecureScheme(GetInsecureScheme());
+ clone->SetNoSpdy(GetNoSpdy());
+ clone->SetBeConservative(GetBeConservative());
+ clone->SetAnonymousAllowClientCert(GetAnonymousAllowClientCert());
+ clone->SetFallbackConnection(GetFallbackConnection());
+ clone->SetTlsFlags(GetTlsFlags());
+ clone->SetIsTrrServiceChannel(GetIsTrrServiceChannel());
+ clone->SetTRRMode(GetTRRMode());
+ clone->SetIPv4Disabled(GetIPv4Disabled());
+ clone->SetIPv6Disabled(GetIPv6Disabled());
+ clone->SetHasIPHintAddress(HasIPHintAddress());
+ clone->SetEchConfig(GetEchConfig());
+
+ clone.forget(outCI);
+}
+
+nsresult nsHttpConnectionInfo::CreateWildCard(nsHttpConnectionInfo** outParam) {
+ // T???mozilla.org:443 (https:proxy.ducksong.com:3128) [specifc form]
+ // TS??*:0 (https:proxy.ducksong.com:3128) [wildcard form]
+
+ if (!mUsingHttpsProxy) {
+ MOZ_ASSERT(false);
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ RefPtr<nsHttpConnectionInfo> clone;
+ clone = new nsHttpConnectionInfo("*"_ns, 0, mNPNToken, mUsername, mProxyInfo,
+ mOriginAttributes, true, mIsHttp3,
+ mWebTransport);
+ // Make sure the anonymous and private flags are transferred!
+ clone->SetAnonymous(GetAnonymous());
+ clone->SetPrivate(GetPrivate());
+ clone.forget(outParam);
+ return NS_OK;
+}
+
+void nsHttpConnectionInfo::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ if (mTRRMode != aTRRMode) {
+ mTRRMode = aTRRMode;
+ RebuildHashKey();
+ }
+}
+
+void nsHttpConnectionInfo::SetIPv4Disabled(bool aNoIPv4) {
+ if (mIPv4Disabled != aNoIPv4) {
+ mIPv4Disabled = aNoIPv4;
+ RebuildHashKey();
+ }
+}
+
+void nsHttpConnectionInfo::SetIPv6Disabled(bool aNoIPv6) {
+ if (mIPv6Disabled != aNoIPv6) {
+ mIPv6Disabled = aNoIPv6;
+ RebuildHashKey();
+ }
+}
+
+void nsHttpConnectionInfo::SetWebTransport(bool aWebTransport) {
+ if (mWebTransport != aWebTransport) {
+ mWebTransport = aWebTransport;
+ RebuildHashKey();
+ }
+}
+
+void nsHttpConnectionInfo::SetTlsFlags(uint32_t aTlsFlags) {
+ mTlsFlags = aTlsFlags;
+ const uint32_t tlsFlagsLength = 8;
+ const uint32_t tlsFlagsIndex =
+ UnderlyingIndex(HashKeyIndex::End) + strlen("[tlsflags0x");
+ mHashKey.Replace(tlsFlagsIndex, tlsFlagsLength,
+ nsPrintfCString("%08x", mTlsFlags));
+}
+
+bool nsHttpConnectionInfo::UsingProxy() {
+ if (!mProxyInfo) return false;
+ return !mProxyInfo->IsDirect();
+}
+
+bool nsHttpConnectionInfo::HostIsLocalIPLiteral() const {
+ NetAddr netAddr;
+ // If the host/proxy host is not an IP address literal, return false.
+ nsAutoCString host(ProxyHost() ? ProxyHost() : Origin());
+ if (NS_FAILED(netAddr.InitFromString(host))) {
+ return false;
+ }
+ return netAddr.IsIPAddrLocal();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.h b/netwerk/protocol/http/nsHttpConnectionInfo.h
new file mode 100644
index 0000000000..892d795b10
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -0,0 +1,317 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpConnectionInfo_h__
+#define nsHttpConnectionInfo_h__
+
+#include "nsHttp.h"
+#include "nsProxyInfo.h"
+#include "nsCOMPtr.h"
+#include "nsStringFwd.h"
+#include "mozilla/Logging.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "ARefBase.h"
+#include "nsIRequest.h"
+
+//-----------------------------------------------------------------------------
+// nsHttpConnectionInfo - holds the properties of a connection
+//-----------------------------------------------------------------------------
+
+// http:// uris through a proxy will all share the same CI, because they can
+// all use the same connection. (modulo pb and anonymous flags). They just use
+// the proxy as the origin host name.
+// however, https:// uris tunnel through the proxy so they will have different
+// CIs - the CI reflects both the proxy and the origin.
+// however, proxy conenctions made with http/2 (or spdy) can tunnel to the
+// origin and multiplex non tunneled transactions at the same time, so they have
+// a special wildcard CI that accepts all origins through that proxy.
+
+class nsISVCBRecord;
+
+namespace mozilla {
+namespace net {
+
+extern LazyLogModule gHttpLog;
+class HttpConnectionInfoCloneArgs;
+class nsHttpTransaction;
+
+class nsHttpConnectionInfo final : public ARefBase {
+ public:
+ nsHttpConnectionInfo(const nsACString& originHost, int32_t originPort,
+ const nsACString& npnToken, const nsACString& username,
+ nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes,
+ bool endToEndSSL = false, bool aIsHttp3 = false,
+ bool aWebTransport = false);
+
+ // this version must use TLS and you may supply separate
+ // connection (aka routing) information than the authenticated
+ // origin information
+ nsHttpConnectionInfo(const nsACString& originHost, int32_t originPort,
+ const nsACString& npnToken, const nsACString& username,
+ nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes,
+ const nsACString& routedHost, int32_t routedPort,
+ bool aIsHttp3, bool aWebTransport = false);
+
+ static void SerializeHttpConnectionInfo(nsHttpConnectionInfo* aInfo,
+ HttpConnectionInfoCloneArgs& aArgs);
+ static already_AddRefed<nsHttpConnectionInfo>
+ DeserializeHttpConnectionInfoCloneArgs(
+ const HttpConnectionInfoCloneArgs& aInfoArgs);
+
+ private:
+ virtual ~nsHttpConnectionInfo() {
+ MOZ_LOG(gHttpLog, LogLevel::Debug,
+ ("Destroying nsHttpConnectionInfo @%p\n", this));
+ }
+
+ void BuildHashKey();
+ void RebuildHashKey();
+
+ // See comments in nsHttpConnectionInfo::BuildHashKey for the meaning of each
+ // field.
+ enum class HashKeyIndex : uint32_t {
+ Proxy = 0,
+ EndToEndSSL,
+ Anonymous,
+ Private,
+ InsecureScheme,
+ NoSpdy,
+ BeConservative,
+ AnonymousAllowClientCert,
+ FallbackConnection,
+ WebTransport,
+ End,
+ };
+ constexpr inline auto UnderlyingIndex(HashKeyIndex aIndex) const {
+ return std::underlying_type_t<HashKeyIndex>(aIndex);
+ }
+
+ public:
+ const nsCString& HashKey() const { return mHashKey; }
+
+ const nsCString& GetOrigin() const { return mOrigin; }
+ const char* Origin() const { return mOrigin.get(); }
+ int32_t OriginPort() const { return mOriginPort; }
+
+ const nsCString& GetRoutedHost() const { return mRoutedHost; }
+ const char* RoutedHost() const { return mRoutedHost.get(); }
+ int32_t RoutedPort() const { return mRoutedPort; }
+
+ // OK to treat these as an infalible allocation
+ already_AddRefed<nsHttpConnectionInfo> Clone() const;
+ // This main prupose of this function is to clone this connection info, but
+ // replace mRoutedHost with SvcDomainName in the given SVCB record. Note that
+ // if SvcParamKeyPort and SvcParamKeyAlpn are presented in the SVCB record,
+ // mRoutedPort and mNPNToken will be replaced as well.
+ already_AddRefed<nsHttpConnectionInfo> CloneAndAdoptHTTPSSVCRecord(
+ nsISVCBRecord* aRecord) const;
+ void CloneAsDirectRoute(nsHttpConnectionInfo** outCI);
+ [[nodiscard]] nsresult CreateWildCard(nsHttpConnectionInfo** outParam);
+
+ const char* ProxyHost() const {
+ return mProxyInfo ? mProxyInfo->Host().get() : nullptr;
+ }
+ int32_t ProxyPort() const { return mProxyInfo ? mProxyInfo->Port() : -1; }
+ const char* ProxyType() const {
+ return mProxyInfo ? mProxyInfo->Type() : nullptr;
+ }
+ const char* ProxyUsername() const {
+ return mProxyInfo ? mProxyInfo->Username().get() : nullptr;
+ }
+ const char* ProxyPassword() const {
+ return mProxyInfo ? mProxyInfo->Password().get() : nullptr;
+ }
+
+ const nsCString& ProxyAuthorizationHeader() const {
+ return mProxyInfo ? mProxyInfo->ProxyAuthorizationHeader() : EmptyCString();
+ }
+ const nsCString& ConnectionIsolationKey() const {
+ return mProxyInfo ? mProxyInfo->ConnectionIsolationKey() : EmptyCString();
+ }
+
+ // Compare this connection info to another...
+ // Two connections are 'equal' if they end up talking the same
+ // protocol to the same server. This is needed to properly manage
+ // persistent connections to proxies
+ // Note that we don't care about transparent proxies -
+ // it doesn't matter if we're talking via socks or not, since
+ // a request will end up at the same host.
+ bool Equals(const nsHttpConnectionInfo* info) {
+ return mHashKey.Equals(info->HashKey());
+ }
+
+ const char* Username() const { return mUsername.get(); }
+ nsProxyInfo* ProxyInfo() const { return mProxyInfo; }
+ int32_t DefaultPort() const {
+ return mEndToEndSSL ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+ }
+ void SetAnonymous(bool anon) {
+ SetHashCharAt(anon ? 'A' : '.', HashKeyIndex::Anonymous);
+ }
+ bool GetAnonymous() const {
+ return GetHashCharAt(HashKeyIndex::Anonymous) == 'A';
+ }
+ void SetPrivate(bool priv) {
+ SetHashCharAt(priv ? 'P' : '.', HashKeyIndex::Private);
+ }
+ bool GetPrivate() const {
+ return GetHashCharAt(HashKeyIndex::Private) == 'P';
+ }
+ void SetInsecureScheme(bool insecureScheme) {
+ SetHashCharAt(insecureScheme ? 'I' : '.', HashKeyIndex::InsecureScheme);
+ }
+ bool GetInsecureScheme() const {
+ return GetHashCharAt(HashKeyIndex::InsecureScheme) == 'I';
+ }
+
+ void SetNoSpdy(bool aNoSpdy) {
+ SetHashCharAt(aNoSpdy ? 'X' : '.', HashKeyIndex::NoSpdy);
+ if (aNoSpdy && mNPNToken == "h2"_ns) {
+ mNPNToken.Truncate();
+ RebuildHashKey();
+ }
+ }
+ bool GetNoSpdy() const { return GetHashCharAt(HashKeyIndex::NoSpdy) == 'X'; }
+
+ void SetBeConservative(bool aBeConservative) {
+ SetHashCharAt(aBeConservative ? 'C' : '.', HashKeyIndex::BeConservative);
+ }
+ bool GetBeConservative() const {
+ return GetHashCharAt(HashKeyIndex::BeConservative) == 'C';
+ }
+
+ void SetAnonymousAllowClientCert(bool anon) {
+ SetHashCharAt(anon ? 'B' : '.', HashKeyIndex::AnonymousAllowClientCert);
+ }
+ bool GetAnonymousAllowClientCert() const {
+ return GetHashCharAt(HashKeyIndex::AnonymousAllowClientCert) == 'B';
+ }
+
+ void SetFallbackConnection(bool aFallback) {
+ SetHashCharAt(aFallback ? 'F' : '.', HashKeyIndex::FallbackConnection);
+ }
+ bool GetFallbackConnection() const {
+ return GetHashCharAt(HashKeyIndex::FallbackConnection) == 'F';
+ }
+
+ void SetTlsFlags(uint32_t aTlsFlags);
+ uint32_t GetTlsFlags() const { return mTlsFlags; }
+
+ // IsTrrServiceChannel means that this connection is used to send TRR requests
+ // over
+ void SetIsTrrServiceChannel(bool aIsTRRChannel) {
+ mIsTrrServiceChannel = aIsTRRChannel;
+ }
+ bool GetIsTrrServiceChannel() const { return mIsTrrServiceChannel; }
+
+ void SetTRRMode(nsIRequest::TRRMode aTRRMode);
+ nsIRequest::TRRMode GetTRRMode() const { return mTRRMode; }
+
+ void SetIPv4Disabled(bool aNoIPv4);
+ bool GetIPv4Disabled() const { return mIPv4Disabled; }
+
+ void SetIPv6Disabled(bool aNoIPv6);
+ bool GetIPv6Disabled() const { return mIPv6Disabled; }
+
+ void SetWebTransport(bool aWebTransport);
+ bool GetWebTransport() const { return mWebTransport; }
+
+ const nsCString& GetNPNToken() { return mNPNToken; }
+ const nsCString& GetUsername() { return mUsername; }
+
+ const OriginAttributes& GetOriginAttributes() { return mOriginAttributes; }
+
+ // Returns true for any kind of proxy (http, socks, https, etc..)
+ bool UsingProxy();
+
+ // Returns true when proxying over HTTP or HTTPS
+ bool UsingHttpProxy() const { return mUsingHttpProxy || mUsingHttpsProxy; }
+
+ // Returns true when proxying over HTTPS
+ bool UsingHttpsProxy() const { return mUsingHttpsProxy; }
+
+ // Returns true when a resource is in SSL end to end (e.g. https:// uri)
+ bool EndToEndSSL() const { return mEndToEndSSL; }
+
+ // Returns true when at least first hop is SSL (e.g. proxy over https or https
+ // uri)
+ bool FirstHopSSL() const { return mEndToEndSSL || mUsingHttpsProxy; }
+
+ // Returns true when CONNECT is used to tunnel through the proxy (e.g.
+ // https:// or ws://)
+ bool UsingConnect() const { return mUsingConnect; }
+
+ // Returns true when origin/proxy is an RFC1918 literal.
+ bool HostIsLocalIPLiteral() const;
+
+ bool GetLessThanTls13() const { return mLessThanTls13; }
+ void SetLessThanTls13(bool aLessThanTls13) {
+ mLessThanTls13 = aLessThanTls13;
+ }
+
+ bool IsHttp3() const { return mIsHttp3; }
+
+ void SetHasIPHintAddress(bool aHasIPHint) { mHasIPHintAddress = aHasIPHint; }
+ bool HasIPHintAddress() const { return mHasIPHintAddress; }
+
+ void SetEchConfig(const nsACString& aEchConfig) { mEchConfig = aEchConfig; }
+ const nsCString& GetEchConfig() const { return mEchConfig; }
+
+ private:
+ void Init(const nsACString& host, int32_t port, const nsACString& npnToken,
+ const nsACString& username, nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes, bool e2eSSL,
+ bool aIsHttp3, bool aWebTransport);
+ void SetOriginServer(const nsACString& host, int32_t port);
+ nsCString::char_type GetHashCharAt(HashKeyIndex aIndex) const {
+ return mHashKey.CharAt(UnderlyingIndex(aIndex));
+ }
+ void SetHashCharAt(nsCString::char_type aValue, HashKeyIndex aIndex) {
+ mHashKey.SetCharAt(aValue, UnderlyingIndex(aIndex));
+ }
+
+ nsCString mOrigin;
+ int32_t mOriginPort = 0;
+ nsCString mRoutedHost;
+ int32_t mRoutedPort;
+
+ nsCString mHashKey;
+ nsCString mUsername;
+ nsCOMPtr<nsProxyInfo> mProxyInfo;
+ bool mUsingHttpProxy = false;
+ bool mUsingHttpsProxy = false;
+ bool mEndToEndSSL = false;
+ // if will use CONNECT with http proxy
+ bool mUsingConnect = false;
+ nsCString mNPNToken;
+ OriginAttributes mOriginAttributes;
+ nsIRequest::TRRMode mTRRMode;
+
+ uint32_t mTlsFlags = 0;
+ uint16_t mIsTrrServiceChannel : 1;
+ uint16_t mIPv4Disabled : 1;
+ uint16_t mIPv6Disabled : 1;
+
+ bool mLessThanTls13; // This will be set to true if we negotiate less than
+ // tls1.3. If the tls version is till not know or it
+ // is 1.3 or greater the value will be false.
+ bool mIsHttp3 = false;
+ bool mWebTransport = false;
+
+ bool mHasIPHintAddress = false;
+ nsCString mEchConfig;
+
+ // for RefPtr
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpConnectionInfo, override)
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpConnectionInfo_h__
diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
new file mode 100644
index 0000000000..2e937d0f2a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -0,0 +1,3863 @@
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include <algorithm>
+#include <utility>
+
+#include "ConnectionHandle.h"
+#include "HttpConnectionUDP.h"
+#include "NullHttpTransaction.h"
+#include "SpeculativeTransaction.h"
+#include "mozilla/Components.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "nsCOMPtr.h"
+#include "nsHttpConnectionMgr.h"
+#include "nsHttpHandler.h"
+#include "nsIClassOfService.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIPipe.h"
+#include "nsIRequestContext.h"
+#include "nsISocketTransport.h"
+#include "nsISocketTransportService.h"
+#include "nsITransport.h"
+#include "nsIXPConnect.h"
+#include "nsInterfaceRequestorAgg.h"
+#include "nsNetCID.h"
+#include "nsNetSegmentUtils.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsSocketTransportService2.h"
+#include "nsStreamUtils.h"
+
+using namespace mozilla;
+
+namespace geckoprofiler::markers {
+
+struct UrlMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("Url");
+ }
+ static void StreamJSONMarkerData(
+ mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
+ const mozilla::ProfilerString8View& aURL) {
+ if (aURL.Length() != 0) {
+ aWriter.StringProperty("url", aURL);
+ }
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema(MS::Location::MarkerChart, MS::Location::MarkerTable);
+ schema.SetTableLabel("{marker.name} - {marker.data.url}");
+ schema.AddKeyFormat("url", MS::Format::Url);
+ return schema;
+ }
+};
+
+} // namespace geckoprofiler::markers
+
+namespace mozilla::net {
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpConnectionMgr, nsIObserver, nsINamed)
+
+//-----------------------------------------------------------------------------
+
+nsHttpConnectionMgr::nsHttpConnectionMgr() {
+ LOG(("Creating nsHttpConnectionMgr @%p\n", this));
+}
+
+nsHttpConnectionMgr::~nsHttpConnectionMgr() {
+ LOG(("Destroying nsHttpConnectionMgr @%p\n", this));
+ MOZ_ASSERT(mCoalescingHash.Count() == 0);
+ if (mTimeoutTick) mTimeoutTick->Cancel();
+}
+
+nsresult nsHttpConnectionMgr::EnsureSocketThreadTarget() {
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = components::IO::Service();
+ if (ioService) {
+ nsCOMPtr<nsISocketTransportService> realSTS =
+ components::SocketTransport::Service();
+ sts = do_QueryInterface(realSTS);
+ }
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // do nothing if already initialized or if we've shut down
+ if (mSocketThreadTarget || mIsShuttingDown) return NS_OK;
+
+ mSocketThreadTarget = sts;
+
+ return sts ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsHttpConnectionMgr::Init(
+ uint16_t maxUrgentExcessiveConns, uint16_t maxConns,
+ uint16_t maxPersistConnsPerHost, uint16_t maxPersistConnsPerProxy,
+ uint16_t maxRequestDelay, bool throttleEnabled, uint32_t throttleVersion,
+ uint32_t throttleSuspendFor, uint32_t throttleResumeFor,
+ uint32_t throttleReadLimit, uint32_t throttleReadInterval,
+ uint32_t throttleHoldTime, uint32_t throttleMaxTime,
+ bool beConservativeForProxy) {
+ LOG(("nsHttpConnectionMgr::Init\n"));
+
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ mMaxUrgentExcessiveConns = maxUrgentExcessiveConns;
+ mMaxConns = maxConns;
+ mMaxPersistConnsPerHost = maxPersistConnsPerHost;
+ mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
+ mMaxRequestDelay = maxRequestDelay;
+
+ mThrottleEnabled = throttleEnabled;
+ mThrottleVersion = throttleVersion;
+ mThrottleSuspendFor = throttleSuspendFor;
+ mThrottleResumeFor = throttleResumeFor;
+ mThrottleReadLimit = throttleReadLimit;
+ mThrottleReadInterval = throttleReadInterval;
+ mThrottleHoldTime = throttleHoldTime;
+ mThrottleMaxTime = TimeDuration::FromMilliseconds(throttleMaxTime);
+
+ mBeConservativeForProxy = beConservativeForProxy;
+
+ mIsShuttingDown = false;
+ }
+
+ return EnsureSocketThreadTarget();
+}
+
+class BoolWrapper : public ARefBase {
+ public:
+ BoolWrapper() = default;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BoolWrapper, override)
+
+ public: // intentional!
+ bool mBool{false};
+
+ private:
+ virtual ~BoolWrapper() = default;
+};
+
+nsresult nsHttpConnectionMgr::Shutdown() {
+ LOG(("nsHttpConnectionMgr::Shutdown\n"));
+
+ RefPtr<BoolWrapper> shutdownWrapper = new BoolWrapper();
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // do nothing if already shutdown
+ if (!mSocketThreadTarget) return NS_OK;
+
+ nsresult rv =
+ PostEvent(&nsHttpConnectionMgr::OnMsgShutdown, 0, shutdownWrapper);
+
+ // release our reference to the STS to prevent further events
+ // from being posted. this is how we indicate that we are
+ // shutting down.
+ mIsShuttingDown = true;
+ mSocketThreadTarget = nullptr;
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to post SHUTDOWN message");
+ return rv;
+ }
+ }
+
+ // wait for shutdown event to complete
+ SpinEventLoopUntil("nsHttpConnectionMgr::Shutdown"_ns,
+ [&, shutdownWrapper]() { return shutdownWrapper->mBool; });
+
+ return NS_OK;
+}
+
+class ConnEvent : public Runnable {
+ public:
+ ConnEvent(nsHttpConnectionMgr* mgr, nsConnEventHandler handler,
+ int32_t iparam, ARefBase* vparam)
+ : Runnable("net::ConnEvent"),
+ mMgr(mgr),
+ mHandler(handler),
+ mIParam(iparam),
+ mVParam(vparam) {}
+
+ NS_IMETHOD Run() override {
+ (mMgr->*mHandler)(mIParam, mVParam);
+ return NS_OK;
+ }
+
+ private:
+ virtual ~ConnEvent() = default;
+
+ RefPtr<nsHttpConnectionMgr> mMgr;
+ nsConnEventHandler mHandler;
+ int32_t mIParam;
+ RefPtr<ARefBase> mVParam;
+};
+
+nsresult nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler,
+ int32_t iparam, ARefBase* vparam) {
+ Unused << EnsureSocketThreadTarget();
+
+ nsCOMPtr<nsIEventTarget> target;
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ target = mSocketThreadTarget;
+ }
+
+ if (!target) {
+ NS_WARNING("cannot post event if not initialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIRunnable> event = new ConnEvent(this, handler, iparam, vparam);
+ return target->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+void nsHttpConnectionMgr::PruneDeadConnectionsAfter(uint32_t timeInSeconds) {
+ LOG(("nsHttpConnectionMgr::PruneDeadConnectionsAfter\n"));
+
+ if (!mTimer) mTimer = NS_NewTimer();
+
+ // failure to create a timer is not a fatal error, but idle connections
+ // will not be cleaned up until we try to use them.
+ if (mTimer) {
+ mTimeOfNextWakeUp = timeInSeconds + NowInSeconds();
+ mTimer->Init(this, timeInSeconds * 1000, nsITimer::TYPE_ONE_SHOT);
+ } else {
+ NS_WARNING("failed to create: timer for pruning the dead connections!");
+ }
+}
+
+void nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer() {
+ // Leave the timer in place if there are connections that potentially
+ // need management
+ if (mNumIdleConns ||
+ (mNumActiveConns && StaticPrefs::network_http_http2_enabled())) {
+ return;
+ }
+
+ LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n"));
+
+ // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
+ mTimeOfNextWakeUp = UINT64_MAX;
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+void nsHttpConnectionMgr::ConditionallyStopTimeoutTick() {
+ LOG(
+ ("nsHttpConnectionMgr::ConditionallyStopTimeoutTick "
+ "armed=%d active=%d\n",
+ mTimeoutTickArmed, mNumActiveConns));
+
+ if (!mTimeoutTickArmed) return;
+
+ if (mNumActiveConns) return;
+
+ LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick stop==true\n"));
+
+ mTimeoutTick->Cancel();
+ mTimeoutTickArmed = false;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnectionMgr::nsINamed
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpConnectionMgr::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsHttpConnectionMgr");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnectionMgr::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpConnectionMgr::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic));
+
+ if (0 == strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) {
+ nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
+ if (timer == mTimer) {
+ Unused << PruneDeadConnections();
+ } else if (timer == mTimeoutTick) {
+ TimeoutTick();
+ } else if (timer == mTrafficTimer) {
+ Unused << PruneNoTraffic();
+ } else if (timer == mThrottleTicker) {
+ ThrottlerTick();
+ } else if (timer == mDelayedResumeReadTimer) {
+ ResumeBackgroundThrottledTransactions();
+ } else {
+ MOZ_ASSERT(false, "unexpected timer-callback");
+ LOG(("Unexpected timer object\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpConnectionMgr::AddTransaction(HttpTransactionShell* trans,
+ int32_t priority) {
+ LOG(("nsHttpConnectionMgr::AddTransaction [trans=%p %d]\n", trans, priority));
+ // Make sure a transaction is not in a pending queue.
+ CheckTransInPendingQueue(trans->AsHttpTransaction());
+ return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority,
+ trans->AsHttpTransaction());
+}
+
+class NewTransactionData : public ARefBase {
+ public:
+ NewTransactionData(nsHttpTransaction* trans, int32_t priority,
+ nsHttpTransaction* transWithStickyConn)
+ : mTrans(trans),
+ mPriority(priority),
+ mTransWithStickyConn(transWithStickyConn) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NewTransactionData, override)
+
+ RefPtr<nsHttpTransaction> mTrans;
+ int32_t mPriority;
+ RefPtr<nsHttpTransaction> mTransWithStickyConn;
+
+ private:
+ virtual ~NewTransactionData() = default;
+};
+
+nsresult nsHttpConnectionMgr::AddTransactionWithStickyConn(
+ HttpTransactionShell* trans, int32_t priority,
+ HttpTransactionShell* transWithStickyConn) {
+ LOG(
+ ("nsHttpConnectionMgr::AddTransactionWithStickyConn "
+ "[trans=%p %d transWithStickyConn=%p]\n",
+ trans, priority, transWithStickyConn));
+ // Make sure a transaction is not in a pending queue.
+ CheckTransInPendingQueue(trans->AsHttpTransaction());
+
+ RefPtr<NewTransactionData> data =
+ new NewTransactionData(trans->AsHttpTransaction(), priority,
+ transWithStickyConn->AsHttpTransaction());
+ return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn, 0,
+ data);
+}
+
+nsresult nsHttpConnectionMgr::RescheduleTransaction(HttpTransactionShell* trans,
+ int32_t priority) {
+ LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans,
+ priority));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority,
+ trans->AsHttpTransaction());
+}
+
+void nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction(
+ HttpTransactionShell* trans, const ClassOfService& classOfService) {
+ LOG(
+ ("nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction [trans=%p "
+ "classOfService flags=%" PRIu32 " inc=%d]\n",
+ trans, static_cast<uint32_t>(classOfService.Flags()),
+ classOfService.Incremental()));
+
+ Unused << EnsureSocketThreadTarget();
+
+ nsCOMPtr<nsIEventTarget> target;
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ target = mSocketThreadTarget;
+ }
+
+ if (!target) {
+ NS_WARNING("cannot post event if not initialized");
+ return;
+ }
+
+ RefPtr<nsHttpConnectionMgr> self(this);
+ Unused << target->Dispatch(NS_NewRunnableFunction(
+ "nsHttpConnectionMgr::CallUpdateClassOfServiceOnTransaction",
+ [cos{classOfService}, self{std::move(self)}, trans = RefPtr{trans}]() {
+ self->OnMsgUpdateClassOfServiceOnTransaction(
+ cos, trans->AsHttpTransaction());
+ }));
+}
+
+nsresult nsHttpConnectionMgr::CancelTransaction(HttpTransactionShell* trans,
+ nsresult reason) {
+ LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%" PRIx32 "]\n",
+ trans, static_cast<uint32_t>(reason)));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
+ static_cast<int32_t>(reason), trans->AsHttpTransaction());
+}
+
+nsresult nsHttpConnectionMgr::PruneDeadConnections() {
+ return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections);
+}
+
+//
+// Called after a timeout. Check for active connections that have had no
+// traffic since they were "marked" and nuke them.
+nsresult nsHttpConnectionMgr::PruneNoTraffic() {
+ LOG(("nsHttpConnectionMgr::PruneNoTraffic\n"));
+ mPruningNoTraffic = true;
+ return PostEvent(&nsHttpConnectionMgr::OnMsgPruneNoTraffic);
+}
+
+nsresult nsHttpConnectionMgr::VerifyTraffic() {
+ LOG(("nsHttpConnectionMgr::VerifyTraffic\n"));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgVerifyTraffic);
+}
+
+nsresult nsHttpConnectionMgr::DoShiftReloadConnectionCleanup() {
+ return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, 0,
+ nullptr);
+}
+
+nsresult nsHttpConnectionMgr::DoShiftReloadConnectionCleanupWithConnInfo(
+ nsHttpConnectionInfo* aCI) {
+ if (!aCI) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<nsHttpConnectionInfo> ci = aCI->Clone();
+ return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, 0,
+ ci);
+}
+
+nsresult nsHttpConnectionMgr::DoSingleConnectionCleanup(
+ nsHttpConnectionInfo* aCI) {
+ if (!aCI) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<nsHttpConnectionInfo> ci = aCI->Clone();
+ return PostEvent(&nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup, 0, ci);
+}
+
+class SpeculativeConnectArgs : public ARefBase {
+ public:
+ SpeculativeConnectArgs() = default;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs, override)
+
+ public: // intentional!
+ RefPtr<SpeculativeTransaction> mTrans;
+
+ bool mFetchHTTPSRR{false};
+
+ private:
+ virtual ~SpeculativeConnectArgs() = default;
+ NS_DECL_OWNINGTHREAD
+};
+
+nsresult nsHttpConnectionMgr::SpeculativeConnect(
+ nsHttpConnectionInfo* ci, nsIInterfaceRequestor* callbacks, uint32_t caps,
+ SpeculativeTransaction* aTransaction, bool aFetchHTTPSRR) {
+ if (!IsNeckoChild() && NS_IsMainThread()) {
+ // HACK: make sure PSM gets initialized on the main thread.
+ net_EnsurePSMInit();
+ }
+
+ LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n",
+ ci->HashKey().get()));
+
+ nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
+ do_GetInterface(callbacks);
+
+ bool allow1918 = overrider ? overrider->GetAllow1918() : false;
+
+ // Hosts that are Local IP Literals should not be speculatively
+ // connected - Bug 853423.
+ if ((!allow1918) && ci && ci->HostIsLocalIPLiteral()) {
+ LOG(
+ ("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 "
+ "address [%s]",
+ ci->Origin()));
+ return NS_OK;
+ }
+
+ nsCString url = ci->EndToEndSSL() ? "https://"_ns : "http://"_ns;
+ url += ci->GetOrigin();
+ PROFILER_MARKER("SpeculativeConnect", NETWORK, {}, UrlMarker, url);
+
+ RefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs();
+
+ // Wrap up the callbacks and the target to ensure they're released on the
+ // target thread properly.
+ nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks;
+ NS_NewInterfaceRequestorAggregation(callbacks, nullptr,
+ getter_AddRefs(wrappedCallbacks));
+
+ caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
+ caps |= NS_HTTP_ERROR_SOFTLY;
+ args->mTrans = aTransaction
+ ? aTransaction
+ : new SpeculativeTransaction(ci, wrappedCallbacks, caps);
+ args->mFetchHTTPSRR = aFetchHTTPSRR;
+
+ if (overrider) {
+ args->mTrans->SetParallelSpeculativeConnectLimit(
+ overrider->GetParallelSpeculativeConnectLimit());
+ args->mTrans->SetIgnoreIdle(overrider->GetIgnoreIdle());
+ args->mTrans->SetIsFromPredictor(overrider->GetIsFromPredictor());
+ args->mTrans->SetAllow1918(overrider->GetAllow1918());
+ }
+
+ return PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
+}
+
+nsresult nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget** target) {
+ Unused << EnsureSocketThreadTarget();
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ nsCOMPtr<nsIEventTarget> temp(mSocketThreadTarget);
+ temp.forget(target);
+ return NS_OK;
+}
+
+nsresult nsHttpConnectionMgr::ReclaimConnection(HttpConnectionBase* conn) {
+ LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%p]\n", conn));
+
+ Unused << EnsureSocketThreadTarget();
+
+ nsCOMPtr<nsIEventTarget> target;
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ target = mSocketThreadTarget;
+ }
+
+ if (!target) {
+ NS_WARNING("cannot post event if not initialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<HttpConnectionBase> connRef(conn);
+ RefPtr<nsHttpConnectionMgr> self(this);
+ return target->Dispatch(NS_NewRunnableFunction(
+ "nsHttpConnectionMgr::CallReclaimConnection",
+ [conn{std::move(connRef)}, self{std::move(self)}]() {
+ self->OnMsgReclaimConnection(conn);
+ }));
+}
+
+// A structure used to marshall 6 pointers across the various necessary
+// threads to complete an HTTP upgrade.
+class nsCompleteUpgradeData : public ARefBase {
+ public:
+ nsCompleteUpgradeData(nsHttpTransaction* aTrans,
+ nsIHttpUpgradeListener* aListener, bool aJsWrapped)
+ : mTrans(aTrans), mUpgradeListener(aListener), mJsWrapped(aJsWrapped) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCompleteUpgradeData, override)
+
+ RefPtr<nsHttpTransaction> mTrans;
+ nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener;
+
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+
+ bool mJsWrapped;
+
+ private:
+ virtual ~nsCompleteUpgradeData() {
+ NS_ReleaseOnMainThread("nsCompleteUpgradeData.mUpgradeListener",
+ mUpgradeListener.forget());
+ }
+};
+
+nsresult nsHttpConnectionMgr::CompleteUpgrade(
+ HttpTransactionShell* aTrans, nsIHttpUpgradeListener* aUpgradeListener) {
+ // test if aUpgradeListener is a wrapped JsObject
+ nsCOMPtr<nsIXPConnectWrappedJS> wrapper = do_QueryInterface(aUpgradeListener);
+
+ bool wrapped = !!wrapper;
+
+ RefPtr<nsCompleteUpgradeData> data = new nsCompleteUpgradeData(
+ aTrans->AsHttpTransaction(), aUpgradeListener, wrapped);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data);
+}
+
+nsresult nsHttpConnectionMgr::UpdateParam(nsParamName name, uint16_t value) {
+ uint32_t param = (uint32_t(name) << 16) | uint32_t(value);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam,
+ static_cast<int32_t>(param), nullptr);
+}
+
+nsresult nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo* aCI) {
+ LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", aCI->HashKey().get()));
+ RefPtr<nsHttpConnectionInfo> ci;
+ if (aCI) {
+ ci = aCI->Clone();
+ }
+ return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci);
+}
+
+nsresult nsHttpConnectionMgr::ProcessPendingQ() {
+ LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n"));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr);
+}
+
+void nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t,
+ ARefBase* param) {
+ EventTokenBucket* tokenBucket = static_cast<EventTokenBucket*>(param);
+ gHttpHandler->SetRequestTokenBucket(tokenBucket);
+}
+
+nsresult nsHttpConnectionMgr::UpdateRequestTokenBucket(
+ EventTokenBucket* aBucket) {
+ // Call From main thread when a new EventTokenBucket has been made in order
+ // to post the new value to the socket thread.
+ return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket, 0,
+ aBucket);
+}
+
+nsresult nsHttpConnectionMgr::ClearConnectionHistory() {
+ return PostEvent(&nsHttpConnectionMgr::OnMsgClearConnectionHistory, 0,
+ nullptr);
+}
+
+void nsHttpConnectionMgr::OnMsgClearConnectionHistory(int32_t,
+ ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("nsHttpConnectionMgr::OnMsgClearConnectionHistory"));
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ConnectionEntry> ent = iter.Data();
+ if (ent->IdleConnectionsLength() == 0 && ent->ActiveConnsLength() == 0 &&
+ ent->DnsAndConnectSocketsLength() == 0 &&
+ ent->UrgentStartQueueLength() == 0 && ent->PendingQueueLength() == 0 &&
+ !ent->mDoNotDestroy) {
+ iter.Remove();
+ }
+ }
+}
+
+nsresult nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p", this, conn));
+
+ if (!conn->ConnectionInfo()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
+
+ if (!ent || NS_FAILED(ent->CloseIdleConnection(conn))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpConnectionMgr::RemoveIdleConnection(nsHttpConnection* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("nsHttpConnectionMgr::RemoveIdleConnection %p conn=%p", this, conn));
+
+ if (!conn->ConnectionInfo()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
+
+ if (!ent || NS_FAILED(ent->RemoveIdleConnection(conn))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+HttpConnectionBase* nsHttpConnectionMgr::FindCoalescableConnectionByHashKey(
+ ConnectionEntry* ent, const nsCString& key, bool justKidding, bool aNoHttp2,
+ bool aNoHttp3) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!aNoHttp2 || !aNoHttp3);
+ MOZ_ASSERT(ent->mConnInfo);
+ nsHttpConnectionInfo* ci = ent->mConnInfo;
+
+ nsTArray<nsWeakPtr>* listOfWeakConns = mCoalescingHash.Get(key);
+ if (!listOfWeakConns) {
+ return nullptr;
+ }
+
+ uint32_t listLen = listOfWeakConns->Length();
+ for (uint32_t j = 0; j < listLen;) {
+ RefPtr<HttpConnectionBase> potentialMatch =
+ do_QueryReferent(listOfWeakConns->ElementAt(j));
+ if (!potentialMatch) {
+ // This is a connection that needs to be removed from the list
+ LOG(
+ ("FindCoalescableConnectionByHashKey() found old conn %p that has "
+ "null weak ptr - removing\n",
+ listOfWeakConns->ElementAt(j).get()));
+ if (j != listLen - 1) {
+ listOfWeakConns->Elements()[j] =
+ listOfWeakConns->Elements()[listLen - 1];
+ }
+ listOfWeakConns->RemoveLastElement();
+ MOZ_ASSERT(listOfWeakConns->Length() == listLen - 1);
+ listLen--;
+ continue; // without adjusting iterator
+ }
+
+ if (aNoHttp3 && potentialMatch->UsingHttp3()) {
+ j++;
+ continue;
+ }
+ if (aNoHttp2 && potentialMatch->UsingSpdy()) {
+ j++;
+ continue;
+ }
+ bool couldJoin;
+ if (justKidding) {
+ couldJoin =
+ potentialMatch->TestJoinConnection(ci->GetOrigin(), ci->OriginPort());
+ } else {
+ couldJoin =
+ potentialMatch->JoinConnection(ci->GetOrigin(), ci->OriginPort());
+ }
+ if (couldJoin) {
+ LOG(
+ ("FindCoalescableConnectionByHashKey() found match conn=%p key=%s "
+ "newCI=%s matchedCI=%s join ok\n",
+ potentialMatch.get(), key.get(), ci->HashKey().get(),
+ potentialMatch->ConnectionInfo()->HashKey().get()));
+ return potentialMatch.get();
+ }
+ LOG(
+ ("FindCoalescableConnectionByHashKey() found match conn=%p key=%s "
+ "newCI=%s matchedCI=%s join failed\n",
+ potentialMatch.get(), key.get(), ci->HashKey().get(),
+ potentialMatch->ConnectionInfo()->HashKey().get()));
+
+ ++j; // bypassed by continue when weakptr fails
+ }
+
+ if (!listLen) { // shrunk to 0 while iterating
+ LOG(("FindCoalescableConnectionByHashKey() removing empty list element\n"));
+ mCoalescingHash.Remove(key);
+ }
+ return nullptr;
+}
+
+static void BuildOriginFrameHashKey(nsACString& newKey,
+ nsHttpConnectionInfo* ci,
+ const nsACString& host, int32_t port) {
+ newKey.Assign(host);
+ if (ci->GetAnonymous()) {
+ newKey.AppendLiteral("~A:");
+ } else {
+ newKey.AppendLiteral("~.:");
+ }
+ if (ci->GetFallbackConnection()) {
+ newKey.AppendLiteral("~F:");
+ } else {
+ newKey.AppendLiteral("~.:");
+ }
+ newKey.AppendInt(port);
+ newKey.AppendLiteral("/[");
+ nsAutoCString suffix;
+ ci->GetOriginAttributes().CreateSuffix(suffix);
+ newKey.Append(suffix);
+ newKey.AppendLiteral("]viaORIGIN.FRAME");
+}
+
+HttpConnectionBase* nsHttpConnectionMgr::FindCoalescableConnection(
+ ConnectionEntry* ent, bool justKidding, bool aNoHttp2, bool aNoHttp3) {
+ MOZ_ASSERT(!aNoHttp2 || !aNoHttp3);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(ent->mConnInfo);
+ nsHttpConnectionInfo* ci = ent->mConnInfo;
+ LOG(("FindCoalescableConnection %s\n", ci->HashKey().get()));
+ // First try and look it up by origin frame
+ nsCString newKey;
+ BuildOriginFrameHashKey(newKey, ci, ci->GetOrigin(), ci->OriginPort());
+ HttpConnectionBase* conn = FindCoalescableConnectionByHashKey(
+ ent, newKey, justKidding, aNoHttp2, aNoHttp3);
+ if (conn) {
+ LOG(("FindCoalescableConnection(%s) match conn %p on frame key %s\n",
+ ci->HashKey().get(), conn, newKey.get()));
+ return conn;
+ }
+
+ // now check for DNS based keys
+ // deleted conns (null weak pointers) are removed from list
+ uint32_t keyLen = ent->mCoalescingKeys.Length();
+ for (uint32_t i = 0; i < keyLen; ++i) {
+ conn = FindCoalescableConnectionByHashKey(ent, ent->mCoalescingKeys[i],
+ justKidding, aNoHttp2, aNoHttp3);
+ if (conn) {
+ LOG(("FindCoalescableConnection(%s) match conn %p on dns key %s\n",
+ ci->HashKey().get(), conn, ent->mCoalescingKeys[i].get()));
+ return conn;
+ }
+ }
+
+ LOG(("FindCoalescableConnection(%s) no matching conn\n",
+ ci->HashKey().get()));
+ return nullptr;
+}
+
+void nsHttpConnectionMgr::UpdateCoalescingForNewConn(
+ HttpConnectionBase* newConn, ConnectionEntry* ent, bool aNoHttp3) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(newConn);
+ MOZ_ASSERT(newConn->ConnectionInfo());
+ MOZ_ASSERT(ent);
+ MOZ_ASSERT(mCT.GetWeak(newConn->ConnectionInfo()->HashKey()) == ent);
+ LOG(("UpdateCoalescingForNewConn newConn=%p aNoHttp3=%d", newConn, aNoHttp3));
+ if (newConn->ConnectionInfo()->GetWebTransport()) {
+ LOG(("Don't coalesce a WebTransport conn %p", newConn));
+ // TODO: implement this properly in bug 1815735.
+ return;
+ }
+
+ HttpConnectionBase* existingConn =
+ FindCoalescableConnection(ent, true, false, false);
+ if (existingConn) {
+ // Prefer http3 connection, but allow an HTTP/2 connection if it is used for
+ // WebSocket.
+ if (newConn->UsingHttp3() && existingConn->UsingSpdy()) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(existingConn);
+ if (connTCP && !connTCP->IsForWebSocket()) {
+ LOG(
+ ("UpdateCoalescingForNewConn() found existing active H2 conn that "
+ "could have served newConn, but new connection is H3, therefore "
+ "close the H2 conncetion"));
+ existingConn->SetCloseReason(
+ ConnectionCloseReason::CLOSE_EXISTING_CONN_FOR_COALESCING);
+ existingConn->DontReuse();
+ }
+ } else if (existingConn->UsingHttp3() && newConn->UsingSpdy()) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(newConn);
+ if (connTCP && !connTCP->IsForWebSocket() && !aNoHttp3) {
+ LOG(
+ ("UpdateCoalescingForNewConn() found existing active H3 conn that "
+ "could have served H2 newConn graceful close of newConn=%p to "
+ "migrate to existingConn %p\n",
+ newConn, existingConn));
+ existingConn->SetCloseReason(
+ ConnectionCloseReason::CLOSE_NEW_CONN_FOR_COALESCING);
+ newConn->DontReuse();
+ return;
+ }
+ } else {
+ LOG(
+ ("UpdateCoalescingForNewConn() found existing active conn that could "
+ "have served newConn "
+ "graceful close of newConn=%p to migrate to existingConn %p\n",
+ newConn, existingConn));
+ existingConn->SetCloseReason(
+ ConnectionCloseReason::CLOSE_NEW_CONN_FOR_COALESCING);
+ newConn->DontReuse();
+ return;
+ }
+ }
+
+ // This connection might go into the mCoalescingHash for new transactions to
+ // be coalesced onto if it can accept new transactions
+ if (!newConn->CanDirectlyActivate()) {
+ return;
+ }
+
+ uint32_t keyLen = ent->mCoalescingKeys.Length();
+ for (uint32_t i = 0; i < keyLen; ++i) {
+ LOG((
+ "UpdateCoalescingForNewConn() registering newConn %p %s under key %s\n",
+ newConn, newConn->ConnectionInfo()->HashKey().get(),
+ ent->mCoalescingKeys[i].get()));
+
+ mCoalescingHash
+ .LookupOrInsertWith(
+ ent->mCoalescingKeys[i],
+ [] {
+ LOG(("UpdateCoalescingForNewConn() need new list element\n"));
+ return MakeUnique<nsTArray<nsWeakPtr>>(1);
+ })
+ ->AppendElement(do_GetWeakReference(
+ static_cast<nsISupportsWeakReference*>(newConn)));
+ }
+
+ // this is a new connection that can be coalesced onto. hooray!
+ // if there are other connection to this entry (e.g.
+ // some could still be handshaking, shutting down, etc..) then close
+ // them down after any transactions that are on them are complete.
+ // This probably happened due to the parallel connection algorithm
+ // that is used only before the host is known to speak h2.
+ ent->MakeAllDontReuseExcept(newConn);
+}
+
+// This function lets a connection, after completing the NPN phase,
+// report whether or not it is using spdy through the usingSpdy
+// argument. It would not be necessary if NPN were driven out of
+// the connection manager. The connection entry associated with the
+// connection is then updated to indicate whether or not we want to use
+// spdy with that host and update the coalescing hash
+// entries used for de-sharding hostsnames.
+void nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection* conn,
+ bool usingSpdy,
+ bool disallowHttp3) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!conn->ConnectionInfo()) {
+ return;
+ }
+ ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
+ if (!ent || !usingSpdy) {
+ return;
+ }
+
+ ent->mUsingSpdy = true;
+ mNumSpdyHttp3ActiveConns++;
+
+ // adjust timeout timer
+ uint32_t ttl = conn->TimeToLive();
+ uint64_t timeOfExpire = NowInSeconds() + ttl;
+ if (!mTimer || timeOfExpire < mTimeOfNextWakeUp) {
+ PruneDeadConnectionsAfter(ttl);
+ }
+
+ UpdateCoalescingForNewConn(conn, ent, disallowHttp3);
+
+ nsresult rv = ProcessPendingQ(ent->mConnInfo);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ReportSpdyConnection conn=%p ent=%p "
+ "failed to process pending queue (%08x)\n",
+ conn, ent, static_cast<uint32_t>(rv)));
+ }
+ rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ReportSpdyConnection conn=%p ent=%p "
+ "failed to post event (%08x)\n",
+ conn, ent, static_cast<uint32_t>(rv)));
+ }
+}
+
+void nsHttpConnectionMgr::ReportHttp3Connection(HttpConnectionBase* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!conn->ConnectionInfo()) {
+ return;
+ }
+ ConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
+ if (!ent) {
+ return;
+ }
+
+ mNumSpdyHttp3ActiveConns++;
+
+ UpdateCoalescingForNewConn(conn, ent, false);
+ nsresult rv = ProcessPendingQ(ent->mConnInfo);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ReportHttp3Connection conn=%p ent=%p "
+ "failed to process pending queue (%08x)\n",
+ conn, ent, static_cast<uint32_t>(rv)));
+ }
+ rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ReportHttp3Connection conn=%p ent=%p "
+ "failed to post event (%08x)\n",
+ conn, ent, static_cast<uint32_t>(rv)));
+ }
+}
+
+//-----------------------------------------------------------------------------
+bool nsHttpConnectionMgr::DispatchPendingQ(
+ nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ, ConnectionEntry* ent,
+ bool considerAll) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ PendingTransactionInfo* pendingTransInfo = nullptr;
+ nsresult rv;
+ bool dispatchedSuccessfully = false;
+
+ // if !considerAll iterate the pending list until one is dispatched
+ // successfully. Keep iterating afterwards only until a transaction fails to
+ // dispatch. if considerAll == true then try and dispatch all items.
+ for (uint32_t i = 0; i < pendingQ.Length();) {
+ pendingTransInfo = pendingQ[i];
+
+ bool alreadyDnsAndConnectSocketOrWaitingForTLS =
+ pendingTransInfo->IsAlreadyClaimedInitializingConn();
+
+ rv = TryDispatchTransaction(ent, alreadyDnsAndConnectSocketOrWaitingForTLS,
+ pendingTransInfo);
+ if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE)) {
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" dispatching pending transaction...\n"));
+ } else {
+ LOG(
+ (" removing pending transaction based on "
+ "TryDispatchTransaction returning hard error %" PRIx32 "\n",
+ static_cast<uint32_t>(rv)));
+ }
+ if (pendingQ.RemoveElement(pendingTransInfo)) {
+ // pendingTransInfo is now potentially destroyed
+ dispatchedSuccessfully = true;
+ continue; // dont ++i as we just made the array shorter
+ }
+
+ LOG((" transaction not found in pending queue\n"));
+ }
+
+ if (dispatchedSuccessfully && !considerAll) break;
+
+ ++i;
+ }
+ return dispatchedSuccessfully;
+}
+
+uint32_t nsHttpConnectionMgr::MaxPersistConnections(
+ ConnectionEntry* ent) const {
+ if (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingConnect()) {
+ return static_cast<uint32_t>(mMaxPersistConnsPerProxy);
+ }
+
+ return static_cast<uint32_t>(mMaxPersistConnsPerHost);
+}
+
+void nsHttpConnectionMgr::PreparePendingQForDispatching(
+ ConnectionEntry* ent, nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ bool considerAll) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ pendingQ.Clear();
+
+ uint32_t totalCount = ent->TotalActiveConnections();
+ uint32_t maxPersistConns = MaxPersistConnections(ent);
+ uint32_t availableConnections =
+ maxPersistConns > totalCount ? maxPersistConns - totalCount : 0;
+
+ // No need to try dispatching if we reach the active connection limit.
+ if (!availableConnections) {
+ return;
+ }
+
+ // Only have to get transactions from the queue whose window id is 0.
+ if (!gHttpHandler->ActiveTabPriority()) {
+ ent->AppendPendingQForFocusedWindow(0, pendingQ, availableConnections);
+ return;
+ }
+
+ uint32_t maxFocusedWindowConnections =
+ availableConnections * gHttpHandler->FocusedWindowTransactionRatio();
+ MOZ_ASSERT(maxFocusedWindowConnections < availableConnections);
+
+ if (!maxFocusedWindowConnections) {
+ maxFocusedWindowConnections = 1;
+ }
+
+ // Only need to dispatch transactions for either focused or
+ // non-focused window because considerAll is false.
+ if (!considerAll) {
+ ent->AppendPendingQForFocusedWindow(mCurrentBrowserId, pendingQ,
+ maxFocusedWindowConnections);
+
+ if (pendingQ.IsEmpty()) {
+ ent->AppendPendingQForNonFocusedWindows(mCurrentBrowserId, pendingQ,
+ availableConnections);
+ }
+ return;
+ }
+
+ uint32_t maxNonFocusedWindowConnections =
+ availableConnections - maxFocusedWindowConnections;
+ nsTArray<RefPtr<PendingTransactionInfo>> remainingPendingQ;
+
+ ent->AppendPendingQForFocusedWindow(mCurrentBrowserId, pendingQ,
+ maxFocusedWindowConnections);
+
+ if (maxNonFocusedWindowConnections) {
+ ent->AppendPendingQForNonFocusedWindows(
+ mCurrentBrowserId, remainingPendingQ, maxNonFocusedWindowConnections);
+ }
+
+ // If the slots for either focused or non-focused window are not filled up
+ // to the availability, try to use the remaining available connections
+ // for the other slot (with preference for the focused window).
+ if (remainingPendingQ.Length() < maxNonFocusedWindowConnections) {
+ ent->AppendPendingQForFocusedWindow(
+ mCurrentBrowserId, pendingQ,
+ maxNonFocusedWindowConnections - remainingPendingQ.Length());
+ } else if (pendingQ.Length() < maxFocusedWindowConnections) {
+ ent->AppendPendingQForNonFocusedWindows(
+ mCurrentBrowserId, remainingPendingQ,
+ maxFocusedWindowConnections - pendingQ.Length());
+ }
+
+ MOZ_ASSERT(pendingQ.Length() + remainingPendingQ.Length() <=
+ availableConnections);
+
+ LOG(
+ ("nsHttpConnectionMgr::PreparePendingQForDispatching "
+ "focused window pendingQ.Length()=%zu"
+ ", remainingPendingQ.Length()=%zu\n",
+ pendingQ.Length(), remainingPendingQ.Length()));
+
+ // Append elements in |remainingPendingQ| to |pendingQ|. The order in
+ // |pendingQ| is like: [focusedWindowTrans...nonFocusedWindowTrans].
+ pendingQ.AppendElements(std::move(remainingPendingQ));
+}
+
+bool nsHttpConnectionMgr::ProcessPendingQForEntry(ConnectionEntry* ent,
+ bool considerAll) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(
+ ("nsHttpConnectionMgr::ProcessPendingQForEntry "
+ "[ci=%s ent=%p active=%zu idle=%zu urgent-start-queue=%zu"
+ " queued=%zu]\n",
+ ent->mConnInfo->HashKey().get(), ent, ent->ActiveConnsLength(),
+ ent->IdleConnectionsLength(), ent->UrgentStartQueueLength(),
+ ent->PendingQueueLength()));
+
+ if (LOG_ENABLED()) {
+ ent->PrintPendingQ();
+ ent->LogConnections();
+ }
+
+ if (!ent->PendingQueueLength() && !ent->UrgentStartQueueLength()) {
+ return false;
+ }
+ ProcessSpdyPendingQ(ent);
+
+ bool dispatchedSuccessfully = false;
+
+ if (ent->UrgentStartQueueLength()) {
+ nsTArray<RefPtr<PendingTransactionInfo>> pendingQ;
+ ent->AppendPendingUrgentStartQ(pendingQ);
+ dispatchedSuccessfully = DispatchPendingQ(pendingQ, ent, considerAll);
+ for (const auto& transactionInfo : Reversed(pendingQ)) {
+ ent->InsertTransaction(transactionInfo);
+ }
+ }
+
+ if (dispatchedSuccessfully && !considerAll) {
+ return dispatchedSuccessfully;
+ }
+
+ nsTArray<RefPtr<PendingTransactionInfo>> pendingQ;
+ PreparePendingQForDispatching(ent, pendingQ, considerAll);
+
+ // The only case that |pendingQ| is empty is when there is no
+ // connection available for dispatching.
+ if (pendingQ.IsEmpty()) {
+ return dispatchedSuccessfully;
+ }
+
+ dispatchedSuccessfully |= DispatchPendingQ(pendingQ, ent, considerAll);
+
+ // Put the leftovers into connection entry, in the same order as they
+ // were before to keep the natural ordering.
+ for (const auto& transactionInfo : Reversed(pendingQ)) {
+ ent->InsertTransaction(transactionInfo, true);
+ }
+
+ // Only remove empty pendingQ when considerAll is true.
+ if (considerAll) {
+ ent->RemoveEmptyPendingQ();
+ }
+
+ return dispatchedSuccessfully;
+}
+
+bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (ent) return ProcessPendingQForEntry(ent, false);
+ return false;
+}
+
+// we're at the active connection limit if any one of the following conditions
+// is true:
+// (1) at max-connections
+// (2) keep-alive enabled and at max-persistent-connections-per-server/proxy
+// (3) keep-alive disabled and at max-connections-per-server
+bool nsHttpConnectionMgr::AtActiveConnectionLimit(ConnectionEntry* ent,
+ uint32_t caps) {
+ nsHttpConnectionInfo* ci = ent->mConnInfo;
+ uint32_t totalCount = ent->TotalActiveConnections();
+
+ if (ci->IsHttp3()) {
+ if (ci->GetWebTransport()) {
+ // TODO: implement this properly in bug 1815735.
+ return false;
+ }
+ return totalCount > 0;
+ }
+
+ uint32_t maxPersistConns = MaxPersistConnections(ent);
+
+ LOG(
+ ("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x,"
+ "totalCount=%u, maxPersistConns=%u]\n",
+ ci->HashKey().get(), caps, totalCount, maxPersistConns));
+
+ if (caps & NS_HTTP_URGENT_START) {
+ if (totalCount >= (mMaxUrgentExcessiveConns + maxPersistConns)) {
+ LOG((
+ "The number of total connections are greater than or equal to sum of "
+ "max urgent-start queue length and the number of max persistent "
+ "connections.\n"));
+ return true;
+ }
+ return false;
+ }
+
+ // update maxconns if potentially limited by the max socket count
+ // this requires a dynamic reduction in the max socket count to a point
+ // lower than the max-connections pref.
+ uint32_t maxSocketCount = gHttpHandler->MaxSocketCount();
+ if (mMaxConns > maxSocketCount) {
+ mMaxConns = maxSocketCount;
+ LOG(("nsHttpConnectionMgr %p mMaxConns dynamically reduced to %u", this,
+ mMaxConns));
+ }
+
+ // If there are more active connections than the global limit, then we're
+ // done. Purging idle connections won't get us below it.
+ if (mNumActiveConns >= mMaxConns) {
+ LOG((" num active conns == max conns\n"));
+ return true;
+ }
+
+ bool result = (totalCount >= maxPersistConns);
+ LOG(("AtActiveConnectionLimit result: %s", result ? "true" : "false"));
+ return result;
+}
+
+// returns NS_OK if a connection was started
+// return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to
+// ephemeral limits
+// returns other NS_ERROR on hard failure conditions
+nsresult nsHttpConnectionMgr::MakeNewConnection(
+ ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo) {
+ LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p", this, ent,
+ pendingTransInfo->Transaction()));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (ent->FindConnToClaim(pendingTransInfo)) {
+ return NS_OK;
+ }
+
+ nsHttpTransaction* trans = pendingTransInfo->Transaction();
+
+ // If this host is trying to negotiate a SPDY session right now,
+ // don't create any new connections until the result of the
+ // negotiation is known.
+ if (!(trans->Caps() & NS_HTTP_DISALLOW_SPDY) &&
+ (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) && ent->RestrictConnections()) {
+ LOG(
+ ("nsHttpConnectionMgr::MakeNewConnection [ci = %s] "
+ "Not Available Due to RestrictConnections()\n",
+ ent->mConnInfo->HashKey().get()));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We need to make a new connection. If that is going to exceed the
+ // global connection limit then try and free up some room by closing
+ // an idle connection to another host. We know it won't select "ent"
+ // because we have already determined there are no idle connections
+ // to our destination
+
+ if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns) {
+ // If the global number of connections is preventing the opening of new
+ // connections to a host without idle connections, then close them
+ // regardless of their TTL.
+ auto iter = mCT.ConstIter();
+ while (mNumIdleConns + mNumActiveConns + 1 >= mMaxConns && !iter.Done()) {
+ RefPtr<ConnectionEntry> entry = iter.Data();
+ entry->CloseIdleConnections((mNumIdleConns + mNumActiveConns + 1) -
+ mMaxConns);
+ iter.Next();
+ }
+ }
+
+ if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumActiveConns &&
+ StaticPrefs::network_http_http2_enabled()) {
+ // If the global number of connections is preventing the opening of new
+ // connections to a host without idle connections, then close any spdy
+ // ASAP.
+ for (const RefPtr<ConnectionEntry>& entry : mCT.Values()) {
+ while (entry->MakeFirstActiveSpdyConnDontReuse()) {
+ // Stop on <= (particularly =) because this dontreuse
+ // causes async close.
+ if (mNumIdleConns + mNumActiveConns + 1 <= mMaxConns) {
+ goto outerLoopEnd;
+ }
+ }
+ }
+ outerLoopEnd:;
+ }
+
+ if (AtActiveConnectionLimit(ent, trans->Caps())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = ent->CreateDnsAndConnectSocket(
+ trans, trans->Caps(), false, false,
+ trans->GetClassOfService().Flags() & nsIClassOfService::UrgentStart, true,
+ pendingTransInfo);
+ if (NS_FAILED(rv)) {
+ /* hard failure */
+ LOG(
+ ("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] "
+ "CreateDnsAndConnectSocket() hard failure.\n",
+ ent->mConnInfo->HashKey().get(), trans));
+ trans->Close(rv);
+ if (rv == NS_ERROR_NOT_AVAILABLE) rv = NS_ERROR_FAILURE;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// returns OK if a connection is found for the transaction
+// and the transaction is started.
+// returns ERROR_NOT_AVAILABLE if no connection can be found and it
+// should be queued until circumstances change
+// returns other ERROR when transaction has a hard failure and should
+// not remain in the pending queue
+nsresult nsHttpConnectionMgr::TryDispatchTransaction(
+ ConnectionEntry* ent, bool onlyReusedConnection,
+ PendingTransactionInfo* pendingTransInfo) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsHttpTransaction* trans = pendingTransInfo->Transaction();
+
+ LOG(
+ ("nsHttpConnectionMgr::TryDispatchTransaction without conn "
+ "[trans=%p ci=%p ci=%s caps=%x onlyreused=%d active=%zu "
+ "idle=%zu]\n",
+ trans, ent->mConnInfo.get(), ent->mConnInfo->HashKey().get(),
+ uint32_t(trans->Caps()), onlyReusedConnection, ent->ActiveConnsLength(),
+ ent->IdleConnectionsLength()));
+
+ uint32_t caps = trans->Caps();
+
+ // 0 - If this should use spdy then dispatch it post haste.
+ // 1 - If there is connection pressure then see if we can pipeline this on
+ // a connection of a matching type instead of using a new conn
+ // 2 - If there is an idle connection, use it!
+ // 3 - if class == reval or script and there is an open conn of that type
+ // then pipeline onto shortest pipeline of that class if limits allow
+ // 4 - If we aren't up against our connection limit,
+ // then open a new one
+ // 5 - Try a pipeline if we haven't already - this will be unusual because
+ // it implies a low connection pressure situation where
+ // MakeNewConnection() failed.. that is possible, but unlikely, due to
+ // global limits
+ // 6 - no connection is available - queue it
+
+ RefPtr<HttpConnectionBase> unusedSpdyPersistentConnection;
+
+ // step 0
+ // look for existing spdy connection - that's always best because it is
+ // essentially pipelining without head of line blocking
+
+ RefPtr<HttpConnectionBase> conn = GetH2orH3ActiveConn(
+ ent,
+ (!StaticPrefs::network_http_http2_enabled() ||
+ (caps & NS_HTTP_DISALLOW_SPDY)),
+ (!nsHttpHandler::IsHttp3Enabled() || (caps & NS_HTTP_DISALLOW_HTTP3)));
+ if (conn) {
+ LOG(("TryingDispatchTransaction: an active h2 connection exists"));
+ WebSocketSupport wsSupp = conn->GetWebSocketSupport();
+ if (trans->IsWebsocketUpgrade()) {
+ LOG(("TryingDispatchTransaction: this is a websocket upgrade"));
+ if (wsSupp == WebSocketSupport::NO_SUPPORT) {
+ LOG((
+ "TryingDispatchTransaction: no support for websockets over Http2"));
+ // This is a websocket transaction and we already have a h2 connection
+ // that do not support websockets, we should disable h2 for this
+ // transaction.
+ trans->DisableSpdy();
+ caps &= NS_HTTP_DISALLOW_SPDY;
+ trans->MakeSticky();
+ } else if (wsSupp == WebSocketSupport::SUPPORTED) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ LOG(("TryingDispatchTransaction: websockets over Http2"));
+
+ // No limit for number of websockets, dispatch transaction to the tunnel
+ RefPtr<nsHttpConnection> connToTunnel;
+ connTCP->CreateTunnelStream(trans, getter_AddRefs(connToTunnel), true);
+ ent->InsertIntoH2WebsocketConns(connToTunnel);
+ trans->SetConnection(nullptr);
+ connToTunnel->SetInSpdyTunnel(); // tells conn it is already in tunnel
+ trans->SetIsHttp2Websocket(true);
+ nsresult rv = DispatchTransaction(ent, trans, connToTunnel);
+ // need to undo NonSticky bypass for transaction reset to continue
+ // for correct websocket upgrade handling
+ trans->MakeSticky();
+ return rv;
+ } else {
+ // if we aren't sure that websockets are supported yet or we are
+ // already at the connection limit then we queue the transaction
+ LOG(("TryingDispatchTransaction: unsure if websockets over Http2"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ } else {
+ if ((caps & NS_HTTP_ALLOW_KEEPALIVE) ||
+ (caps & NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE) ||
+ !conn->IsExperienced()) {
+ LOG((" dispatch to spdy: [conn=%p]\n", conn.get()));
+ trans->RemoveDispatchedAsBlocking(); /* just in case */
+ nsresult rv = DispatchTransaction(ent, trans, conn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ unusedSpdyPersistentConnection = conn;
+ }
+ }
+
+ // If this is not a blocking transaction and the request context for it is
+ // currently processing one or more blocking transactions then we
+ // need to just leave it in the queue until those are complete unless it is
+ // explicitly marked as unblocked.
+ if (!(caps & NS_HTTP_LOAD_AS_BLOCKING)) {
+ if (!(caps & NS_HTTP_LOAD_UNBLOCKED)) {
+ nsIRequestContext* requestContext = trans->RequestContext();
+ if (requestContext) {
+ uint32_t blockers = 0;
+ if (NS_SUCCEEDED(
+ requestContext->GetBlockingTransactionCount(&blockers)) &&
+ blockers) {
+ // need to wait for blockers to clear
+ LOG((" blocked by request context: [rc=%p trans=%p blockers=%d]\n",
+ requestContext, trans, blockers));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ }
+ } else {
+ // Mark the transaction and its load group as blocking right now to prevent
+ // other transactions from being reordered in the queue due to slow syns.
+ trans->DispatchedAsBlocking();
+ }
+
+ // step 1
+ // If connection pressure, then we want to favor pipelining of any kind
+ // h1 pipelining has been removed
+
+ // Subject most transactions at high parallelism to rate pacing.
+ // It will only be actually submitted to the
+ // token bucket once, and if possible it is granted admission synchronously.
+ // It is important to leave a transaction in the pending queue when blocked by
+ // pacing so it can be found on cancel if necessary.
+ // Transactions that cause blocking or bypass it (e.g. js/css) are not rate
+ // limited.
+ if (gHttpHandler->UseRequestTokenBucket()) {
+ // submit even whitelisted transactions to the token bucket though they will
+ // not be slowed by it
+ bool runNow = trans->TryToRunPacedRequest();
+ if (!runNow) {
+ if ((mNumActiveConns - mNumSpdyHttp3ActiveConns) <=
+ gHttpHandler->RequestTokenBucketMinParallelism()) {
+ runNow = true; // white list it
+ } else if (caps & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) {
+ runNow = true; // white list it
+ }
+ }
+ if (!runNow) {
+ LOG((" blocked due to rate pacing trans=%p\n", trans));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ // step 2
+ // consider an idle persistent connection
+ bool idleConnsAllUrgent = false;
+ if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
+ nsresult rv = TryDispatchTransactionOnIdleConn(ent, pendingTransInfo, true,
+ &idleConnsAllUrgent);
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" dispatched step 2 (idle) trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // step 3
+ // consider pipelining scripts and revalidations
+ // h1 pipelining has been removed
+
+ // Don't dispatch if this transaction is waiting for HTTPS RR.
+ // This usually happens when the pref "network.dns.force_waiting_https_rr" is
+ // true or when echConfig is enabled.
+ if (trans->WaitingForHTTPSRR()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // step 4
+ if (!onlyReusedConnection) {
+ nsresult rv = MakeNewConnection(ent, pendingTransInfo);
+ if (NS_SUCCEEDED(rv)) {
+ // this function returns NOT_AVAILABLE for asynchronous connects
+ LOG((" dispatched step 4 (async new conn) trans=%p\n", trans));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ // not available return codes should try next step as they are
+ // not hard errors. Other codes should stop now
+ LOG((" failed step 4 (%" PRIx32 ") trans=%p\n",
+ static_cast<uint32_t>(rv), trans));
+ return rv;
+ }
+
+ // repeat step 2 when there are only idle connections and all are urgent,
+ // don't respect urgency so that non-urgent transaction will be allowed
+ // to dispatch on an urgent-start-only marked connection to avoid
+ // dispatch deadlocks
+ if (!(trans->GetClassOfService().Flags() &
+ nsIClassOfService::UrgentStart) &&
+ idleConnsAllUrgent &&
+ ent->ActiveConnsLength() < MaxPersistConnections(ent)) {
+ rv = TryDispatchTransactionOnIdleConn(ent, pendingTransInfo, false);
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" dispatched step 2a (idle, reuse urgent) trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+ }
+
+ // step 5
+ // previously pipelined anything here if allowed but h1 pipelining has been
+ // removed
+
+ // step 6
+ if (unusedSpdyPersistentConnection) {
+ // to avoid deadlocks, we need to throw away this perfectly valid SPDY
+ // connection to make room for a new one that can service a no KEEPALIVE
+ // request
+ unusedSpdyPersistentConnection->DontReuse();
+ }
+
+ LOG((" not dispatched (queued) trans=%p\n", trans));
+ return NS_ERROR_NOT_AVAILABLE; /* queue it */
+}
+
+nsresult nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn(
+ ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo,
+ bool respectUrgency, bool* allUrgent) {
+ bool onlyUrgent = !!ent->IdleConnectionsLength();
+
+ nsHttpTransaction* trans = pendingTransInfo->Transaction();
+ bool urgentTrans =
+ trans->GetClassOfService().Flags() & nsIClassOfService::UrgentStart;
+
+ LOG(
+ ("nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn, ent=%p, "
+ "trans=%p, urgent=%d",
+ ent, trans, urgentTrans));
+
+ RefPtr<nsHttpConnection> conn =
+ ent->GetIdleConnection(respectUrgency, urgentTrans, &onlyUrgent);
+
+ if (allUrgent) {
+ *allUrgent = onlyUrgent;
+ }
+
+ if (conn) {
+ // This will update the class of the connection to be the class of
+ // the transaction dispatched on it.
+ ent->InsertIntoActiveConns(conn);
+ nsresult rv = DispatchTransaction(ent, trans, conn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsHttpConnectionMgr::DispatchTransaction(ConnectionEntry* ent,
+ nsHttpTransaction* trans,
+ HttpConnectionBase* conn) {
+ uint32_t caps = trans->Caps();
+ int32_t priority = trans->Priority();
+ nsresult rv;
+
+ LOG(
+ ("nsHttpConnectionMgr::DispatchTransaction "
+ "[ent-ci=%s %p trans=%p caps=%x conn=%p priority=%d isHttp2=%d "
+ "isHttp3=%d]\n",
+ ent->mConnInfo->HashKey().get(), ent, trans, caps, conn, priority,
+ conn->UsingSpdy(), conn->UsingHttp3()));
+
+ // It is possible for a rate-paced transaction to be dispatched independent
+ // of the token bucket when the amount of parallelization has changed or
+ // when a muxed connection (e.g. h2) becomes available.
+ trans->CancelPacing(NS_OK);
+
+ TimeStamp now = TimeStamp::Now();
+ auto recordPendingTimeForHTTPSRR = [&](nsCString& aKey) {
+ uint32_t stage = trans->HTTPSSVCReceivedStage();
+ TimeDuration elapsed = now - trans->GetPendingTime();
+ if (HTTPS_RR_IS_USED(stage)) {
+ glean::networking::transaction_wait_time_https_rr.AccumulateRawDuration(
+ elapsed);
+
+ } else {
+ glean::networking::transaction_wait_time.AccumulateRawDuration(elapsed);
+ }
+ PerfStats::RecordMeasurement(PerfStats::Metric::HttpTransactionWaitTime,
+ elapsed);
+ };
+
+ nsAutoCString httpVersionkey("h1"_ns);
+ if (conn->UsingSpdy() || conn->UsingHttp3()) {
+ LOG(
+ ("Spdy Dispatch Transaction via Activate(). Transaction host = %s, "
+ "Connection host = %s\n",
+ trans->ConnectionInfo()->Origin(), conn->ConnectionInfo()->Origin()));
+ rv = conn->Activate(trans, caps, priority);
+ if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
+ if (conn->UsingSpdy()) {
+ httpVersionkey = "h2"_ns;
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_SPDY,
+ trans->GetPendingTime(), now);
+ } else {
+ httpVersionkey = "h3"_ns;
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP3,
+ trans->GetPendingTime(), now);
+ }
+ recordPendingTimeForHTTPSRR(httpVersionkey);
+ trans->SetPendingTime(false);
+ }
+ return rv;
+ }
+
+ MOZ_ASSERT(conn && !conn->Transaction(),
+ "DispatchTranaction() on non spdy active connection");
+
+ rv = DispatchAbstractTransaction(ent, trans, caps, conn, priority);
+
+ if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP,
+ trans->GetPendingTime(), now);
+ recordPendingTimeForHTTPSRR(httpVersionkey);
+ trans->SetPendingTime(false);
+ }
+ return rv;
+}
+
+// Use this method for dispatching nsAHttpTransction's. It can only safely be
+// used upon first use of a connection when NPN has not negotiated SPDY vs
+// HTTP/1 yet as multiplexing onto an existing SPDY session requires a
+// concrete nsHttpTransaction
+nsresult nsHttpConnectionMgr::DispatchAbstractTransaction(
+ ConnectionEntry* ent, nsAHttpTransaction* aTrans, uint32_t caps,
+ HttpConnectionBase* conn, int32_t priority) {
+ MOZ_ASSERT(ent);
+
+ nsresult rv;
+ MOZ_ASSERT(!conn->UsingSpdy(),
+ "Spdy Must Not Use DispatchAbstractTransaction");
+ LOG(
+ ("nsHttpConnectionMgr::DispatchAbstractTransaction "
+ "[ci=%s trans=%p caps=%x conn=%p]\n",
+ ent->mConnInfo->HashKey().get(), aTrans, caps, conn));
+
+ RefPtr<nsAHttpTransaction> transaction(aTrans);
+ RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
+
+ // give the transaction the indirect reference to the connection.
+ transaction->SetConnection(handle);
+
+ rv = conn->Activate(transaction, caps, priority);
+ if (NS_FAILED(rv)) {
+ LOG((" conn->Activate failed [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ DebugOnly<nsresult> rv_remove = ent->RemoveActiveConnection(conn);
+ MOZ_ASSERT(NS_SUCCEEDED(rv_remove));
+
+ // sever back references to connection, and do so without triggering
+ // a call to ReclaimConnection ;-)
+ transaction->SetConnection(nullptr);
+ handle->Reset(); // destroy the connection
+ }
+
+ return rv;
+}
+
+void nsHttpConnectionMgr::ReportProxyTelemetry(ConnectionEntry* ent) {
+ enum { PROXY_NONE = 1, PROXY_HTTP = 2, PROXY_SOCKS = 3, PROXY_HTTPS = 4 };
+
+ if (!ent->mConnInfo->UsingProxy()) {
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_NONE);
+ } else if (ent->mConnInfo->UsingHttpsProxy()) {
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTPS);
+ } else if (ent->mConnInfo->UsingHttpProxy()) {
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTP);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_SOCKS);
+ }
+}
+
+nsresult nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction* trans) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // since "adds" and "cancels" are processed asynchronously and because
+ // various events might trigger an "add" directly on the socket thread,
+ // we must take care to avoid dispatching a transaction that has already
+ // been canceled (see bug 190001).
+ if (NS_FAILED(trans->Status())) {
+ LOG((" transaction was canceled... dropping event!\n"));
+ return NS_OK;
+ }
+
+ // Make sure a transaction is not in a pending queue.
+ CheckTransInPendingQueue(trans);
+
+ trans->SetPendingTime();
+
+ RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper =
+ trans->GetPushedStream();
+ if (pushedStreamWrapper) {
+ Http2PushedStream* pushedStream = pushedStreamWrapper->GetStream();
+ if (pushedStream) {
+ RefPtr<Http2Session> session = pushedStream->Session();
+ LOG((" ProcessNewTransaction %p tied to h2 session push %p\n", trans,
+ session.get()));
+ return session->AddStream(trans, trans->Priority(), nullptr)
+ ? NS_OK
+ : NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ nsresult rv = NS_OK;
+ nsHttpConnectionInfo* ci = trans->ConnectionInfo();
+ MOZ_ASSERT(ci);
+ MOZ_ASSERT(!ci->IsHttp3() || !(trans->Caps() & NS_HTTP_DISALLOW_HTTP3));
+
+ bool isWildcard = false;
+ ConnectionEntry* ent = GetOrCreateConnectionEntry(
+ ci, trans->Caps() & NS_HTTP_DISALLOW_HTTP2_PROXY,
+ trans->Caps() & NS_HTTP_DISALLOW_SPDY,
+ trans->Caps() & NS_HTTP_DISALLOW_HTTP3, &isWildcard);
+ MOZ_ASSERT(ent);
+
+ if (gHttpHandler->EchConfigEnabled(ci->IsHttp3())) {
+ ent->MaybeUpdateEchConfig(ci);
+ }
+
+ ReportProxyTelemetry(ent);
+
+ // Check if the transaction already has a sticky reference to a connection.
+ // If so, then we can just use it directly by transferring its reference
+ // to the new connection variable instead of searching for a new one
+
+ nsAHttpConnection* wrappedConnection = trans->Connection();
+ RefPtr<HttpConnectionBase> conn;
+ RefPtr<PendingTransactionInfo> pendingTransInfo;
+ if (wrappedConnection) conn = wrappedConnection->TakeHttpConnection();
+
+ if (conn) {
+ MOZ_ASSERT(trans->Caps() & NS_HTTP_STICKY_CONNECTION);
+ LOG(
+ ("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+ "sticky connection=%p\n",
+ trans, conn.get()));
+
+ if (!ent->IsInActiveConns(conn)) {
+ LOG(
+ ("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+ "sticky connection=%p needs to go on the active list\n",
+ trans, conn.get()));
+
+ // make sure it isn't on the idle list - we expect this to be an
+ // unknown fresh connection
+ MOZ_ASSERT(!ent->IsInIdleConnections(conn));
+ MOZ_ASSERT(!conn->IsExperienced());
+
+ ent->InsertIntoActiveConns(conn); // make it active
+ }
+
+ trans->SetConnection(nullptr);
+ rv = DispatchTransaction(ent, trans, conn);
+ } else if (isWildcard) {
+ // We have a HTTP/2 session to the proxy, create a new tunneled
+ // connection.
+ RefPtr<HttpConnectionBase> conn = GetH2orH3ActiveConn(ent, false, true);
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ if (ci->UsingHttpsProxy() && ci->UsingConnect()) {
+ LOG(("About to create new tunnel conn from [%p]", connTCP.get()));
+ ConnectionEntry* specificEnt = mCT.GetWeak(ci->HashKey());
+
+ if (!specificEnt) {
+ RefPtr<nsHttpConnectionInfo> clone(ci->Clone());
+ specificEnt = new ConnectionEntry(clone);
+ mCT.InsertOrUpdate(clone->HashKey(), RefPtr{specificEnt});
+ }
+
+ ent = specificEnt;
+ bool atLimit = AtActiveConnectionLimit(ent, trans->Caps());
+ if (atLimit) {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ } else {
+ RefPtr<nsHttpConnection> newTunnel;
+ connTCP->CreateTunnelStream(trans, getter_AddRefs(newTunnel));
+
+ ent->InsertIntoActiveConns(newTunnel);
+ trans->SetConnection(nullptr);
+ newTunnel->SetInSpdyTunnel();
+ rv = DispatchTransaction(ent, trans, newTunnel);
+ // need to undo the bypass for transaction reset for proxy
+ trans->MakeNonRestartable();
+ }
+ } else {
+ rv = DispatchTransaction(ent, trans, connTCP);
+ }
+ } else {
+ if (!ent->AllowHttp2()) {
+ trans->DisableSpdy();
+ }
+ pendingTransInfo = new PendingTransactionInfo(trans);
+ rv = TryDispatchTransaction(ent, false, pendingTransInfo);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" ProcessNewTransaction Dispatch Immediately trans=%p\n", trans));
+ return rv;
+ }
+
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ if (!pendingTransInfo) {
+ pendingTransInfo = new PendingTransactionInfo(trans);
+ }
+
+ ent->InsertTransaction(pendingTransInfo);
+ return NS_OK;
+ }
+
+ LOG((" ProcessNewTransaction Hard Error trans=%p rv=%" PRIx32 "\n", trans,
+ static_cast<uint32_t>(rv)));
+ return rv;
+}
+
+void nsHttpConnectionMgr::IncrementActiveConnCount() {
+ mNumActiveConns++;
+ ActivateTimeoutTick();
+}
+
+void nsHttpConnectionMgr::DecrementActiveConnCount(HttpConnectionBase* conn) {
+ MOZ_DIAGNOSTIC_ASSERT(mNumActiveConns > 0);
+ if (mNumActiveConns > 0) {
+ mNumActiveConns--;
+ }
+
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ if (!connTCP || connTCP->EverUsedSpdy()) mNumSpdyHttp3ActiveConns--;
+ ConditionallyStopTimeoutTick();
+}
+
+void nsHttpConnectionMgr::StartedConnect() {
+ mNumActiveConns++;
+ ActivateTimeoutTick(); // likely disabled by RecvdConnect()
+}
+
+void nsHttpConnectionMgr::RecvdConnect() {
+ MOZ_DIAGNOSTIC_ASSERT(mNumActiveConns > 0);
+ if (mNumActiveConns > 0) {
+ mNumActiveConns--;
+ }
+
+ ConditionallyStopTimeoutTick();
+}
+
+void nsHttpConnectionMgr::DispatchSpdyPendingQ(
+ nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ, ConnectionEntry* ent,
+ HttpConnectionBase* connH2, HttpConnectionBase* connH3) {
+ if (pendingQ.Length() == 0) {
+ return;
+ }
+
+ nsTArray<RefPtr<PendingTransactionInfo>> leftovers;
+ uint32_t index;
+ // Dispatch all the transactions we can
+ for (index = 0; index < pendingQ.Length() &&
+ ((connH3 && connH3->CanDirectlyActivate()) ||
+ (connH2 && connH2->CanDirectlyActivate()));
+ ++index) {
+ PendingTransactionInfo* pendingTransInfo = pendingQ[index];
+
+ // We can not dispatch NS_HTTP_ALLOW_KEEPALIVE transactions.
+ if (!(pendingTransInfo->Transaction()->Caps() & NS_HTTP_ALLOW_KEEPALIVE)) {
+ leftovers.AppendElement(pendingTransInfo);
+ continue;
+ }
+
+ // Try dispatching on HTTP3 first
+ HttpConnectionBase* conn = nullptr;
+ if (!(pendingTransInfo->Transaction()->Caps() & NS_HTTP_DISALLOW_HTTP3) &&
+ connH3 && connH3->CanDirectlyActivate()) {
+ conn = connH3;
+ } else if (!(pendingTransInfo->Transaction()->Caps() &
+ NS_HTTP_DISALLOW_SPDY) &&
+ connH2 && connH2->CanDirectlyActivate()) {
+ conn = connH2;
+ } else {
+ leftovers.AppendElement(pendingTransInfo);
+ continue;
+ }
+
+ nsresult rv =
+ DispatchTransaction(ent, pendingTransInfo->Transaction(), conn);
+ if (NS_FAILED(rv)) {
+ // this cannot happen, but if due to some bug it does then
+ // close the transaction
+ MOZ_ASSERT(false, "Dispatch SPDY Transaction");
+ LOG(("ProcessSpdyPendingQ Dispatch Transaction failed trans=%p\n",
+ pendingTransInfo->Transaction()));
+ pendingTransInfo->Transaction()->Close(rv);
+ }
+ }
+
+ // Slurp up the rest of the pending queue into our leftovers bucket (we
+ // might have some left if conn->CanDirectlyActivate returned false)
+ for (; index < pendingQ.Length(); ++index) {
+ PendingTransactionInfo* pendingTransInfo = pendingQ[index];
+ leftovers.AppendElement(pendingTransInfo);
+ }
+
+ // Put the leftovers back in the pending queue and get rid of the
+ // transactions we dispatched
+ pendingQ = std::move(leftovers);
+}
+
+// This function tries to dispatch the pending h2 or h3 transactions on
+// the connection entry sent in as an argument. It will do so on the
+// active h2 or h3 connection either in that same entry or from the
+// coalescing hash table
+void nsHttpConnectionMgr::ProcessSpdyPendingQ(ConnectionEntry* ent) {
+ // Look for one HTTP2 and one HTTP3 connection.
+ // We may have transactions that have NS_HTTP_DISALLOW_SPDY/HTTP3 set
+ // and we may need an alternative.
+ HttpConnectionBase* connH3 = GetH2orH3ActiveConn(ent, true, false);
+ HttpConnectionBase* connH2 = GetH2orH3ActiveConn(ent, false, true);
+ if ((!connH3 || !connH3->CanDirectlyActivate()) &&
+ (!connH2 || !connH2->CanDirectlyActivate())) {
+ return;
+ }
+
+ nsTArray<RefPtr<PendingTransactionInfo>> urgentQ;
+ ent->AppendPendingUrgentStartQ(urgentQ);
+ DispatchSpdyPendingQ(urgentQ, ent, connH2, connH3);
+ for (const auto& transactionInfo : Reversed(urgentQ)) {
+ ent->InsertTransaction(transactionInfo);
+ }
+
+ if ((!connH3 || !connH3->CanDirectlyActivate()) &&
+ (!connH2 || !connH2->CanDirectlyActivate())) {
+ return;
+ }
+
+ nsTArray<RefPtr<PendingTransactionInfo>> pendingQ;
+ // XXX Get all transactions for SPDY currently.
+ ent->AppendPendingQForNonFocusedWindows(0, pendingQ);
+ DispatchSpdyPendingQ(pendingQ, ent, connH2, connH3);
+
+ // Put the leftovers back in the pending queue.
+ for (const auto& transactionInfo : pendingQ) {
+ ent->InsertTransaction(transactionInfo);
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase*) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
+ for (const auto& entry : mCT.Values()) {
+ ProcessSpdyPendingQ(entry.get());
+ }
+}
+
+// Given a connection entry, return an active h2 or h3 connection
+// that can be directly activated or null.
+HttpConnectionBase* nsHttpConnectionMgr::GetH2orH3ActiveConn(
+ ConnectionEntry* ent, bool aNoHttp2, bool aNoHttp3) {
+ if (aNoHttp2 && aNoHttp3) {
+ return nullptr;
+ }
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(ent);
+
+ // First look at ent. If protocol that ent provides is no forbidden,
+ // i.e. ent use HTTP3 and !aNoHttp3 or en uses HTTP over TCP and !aNoHttp2.
+ if ((!aNoHttp3 && ent->IsHttp3()) || (!aNoHttp2 && !ent->IsHttp3())) {
+ HttpConnectionBase* conn = ent->GetH2orH3ActiveConn();
+ if (conn) {
+ return conn;
+ }
+ }
+
+ nsHttpConnectionInfo* ci = ent->mConnInfo;
+
+ // there was no active HTTP2/3 connection in the connection entry, but
+ // there might be one in the hash table for coalescing
+ HttpConnectionBase* existingConn =
+ FindCoalescableConnection(ent, false, aNoHttp2, aNoHttp3);
+ if (existingConn) {
+ LOG(
+ ("GetH2orH3ActiveConn() request for ent %p %s "
+ "found an active connection %p in the coalescing hashtable\n",
+ ent, ci->HashKey().get(), existingConn));
+ return existingConn;
+ }
+
+ LOG(
+ ("GetH2orH3ActiveConn() request for ent %p %s "
+ "did not find an active connection\n",
+ ent, ci->HashKey().get()));
+ return nullptr;
+}
+
+//-----------------------------------------------------------------------------
+
+void nsHttpConnectionMgr::AbortAndCloseAllConnections(int32_t, ARefBase*) {
+ if (!OnSocketThread()) {
+ Unused << PostEvent(&nsHttpConnectionMgr::AbortAndCloseAllConnections);
+ return;
+ }
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::AbortAndCloseAllConnections\n"));
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ConnectionEntry> ent = iter.Data();
+
+ // Close all active connections.
+ ent->CloseActiveConnections();
+
+ // Close all idle connections.
+ ent->CloseIdleConnections();
+
+ // Close websocket "fake" connections
+ ent->CloseH2WebsocketConnections();
+
+ ent->ClosePendingConnections();
+
+ // Close all pending transactions.
+ ent->CancelAllTransactions(NS_ERROR_ABORT);
+
+ // Close all half open tcp connections.
+ ent->CloseAllDnsAndConnectSockets();
+
+ MOZ_ASSERT(!ent->mDoNotDestroy);
+ iter.Remove();
+ }
+
+ mActiveTransactions[false].Clear();
+ mActiveTransactions[true].Clear();
+}
+
+void nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
+
+ gHttpHandler->StopRequestTokenBucket();
+ AbortAndCloseAllConnections(0, nullptr);
+
+ // If all idle connections are removed we can stop pruning dead
+ // connections.
+ ConditionallyStopPruneDeadConnectionsTimer();
+
+ if (mTimeoutTick) {
+ mTimeoutTick->Cancel();
+ mTimeoutTick = nullptr;
+ mTimeoutTickArmed = false;
+ }
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (mTrafficTimer) {
+ mTrafficTimer->Cancel();
+ mTrafficTimer = nullptr;
+ }
+ DestroyThrottleTicker();
+
+ mCoalescingHash.Clear();
+
+ // signal shutdown complete
+ nsCOMPtr<nsIRunnable> runnable =
+ new ConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm, 0, param);
+ NS_DispatchToMainThread(runnable);
+}
+
+void nsHttpConnectionMgr::OnMsgShutdownConfirm(int32_t priority,
+ ARefBase* param) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("nsHttpConnectionMgr::OnMsgShutdownConfirm\n"));
+
+ BoolWrapper* shutdown = static_cast<BoolWrapper*>(param);
+ shutdown->mBool = true;
+}
+
+void nsHttpConnectionMgr::OnMsgNewTransaction(int32_t priority,
+ ARefBase* param) {
+ nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param);
+
+ LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", trans));
+ trans->SetPriority(priority);
+ nsresult rv = ProcessNewTransaction(trans);
+ if (NS_FAILED(rv)) trans->Close(rv); // for whatever its worth
+}
+
+void nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn(int32_t priority,
+ ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ NewTransactionData* data = static_cast<NewTransactionData*>(param);
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn "
+ "[trans=%p, transWithStickyConn=%p, conn=%p]\n",
+ data->mTrans.get(), data->mTransWithStickyConn.get(),
+ data->mTransWithStickyConn->Connection()));
+
+ MOZ_ASSERT(data->mTransWithStickyConn &&
+ data->mTransWithStickyConn->Caps() & NS_HTTP_STICKY_CONNECTION);
+
+ data->mTrans->SetPriority(data->mPriority);
+
+ RefPtr<nsAHttpConnection> conn = data->mTransWithStickyConn->Connection();
+ if (conn && conn->IsPersistent()) {
+ // This is so far a workaround to only reuse persistent
+ // connection for authentication retry. See bug 459620 comment 4
+ // for details.
+ LOG((" Reuse connection [%p] for transaction [%p]", conn.get(),
+ data->mTrans.get()));
+ data->mTrans->SetConnection(conn);
+ }
+
+ nsresult rv = ProcessNewTransaction(data->mTrans);
+ if (NS_FAILED(rv)) {
+ data->mTrans->Close(rv); // for whatever its worth
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority,
+ ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param));
+
+ RefPtr<nsHttpTransaction> trans = static_cast<nsHttpTransaction*>(param);
+ trans->SetPriority(priority);
+
+ if (!trans->ConnectionInfo()) {
+ return;
+ }
+ ConnectionEntry* ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey());
+
+ if (ent) {
+ ent->ReschedTransaction(trans);
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction(
+ ClassOfService cos, ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction "
+ "[trans=%p]\n",
+ param));
+
+ nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param);
+
+ ClassOfService previous = trans->GetClassOfService();
+ trans->SetClassOfService(cos);
+
+ // incremental change alone will not trigger a reschedule
+ if ((previous.Flags() ^ cos.Flags()) &
+ (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) {
+ Unused << RescheduleTransaction(trans, trans->Priority());
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason,
+ ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
+
+ nsresult closeCode = static_cast<nsresult>(reason);
+
+ // caller holds a ref to param/trans on stack
+ nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param);
+
+ //
+ // if the transaction owns a connection and the transaction is not done,
+ // then ask the connection to close the transaction. otherwise, close the
+ // transaction directly (removing it from the pending queue first).
+ //
+ RefPtr<nsAHttpConnection> conn(trans->Connection());
+ if (conn && !trans->IsDone()) {
+ conn->CloseTransaction(trans, closeCode);
+ } else {
+ ConnectionEntry* ent = nullptr;
+ if (trans->ConnectionInfo()) {
+ ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey());
+ }
+ if (ent && ent->RemoveTransFromPendingQ(trans)) {
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
+ " removed from pending queue\n",
+ trans));
+ }
+
+ trans->Close(closeCode);
+
+ // Cancel is a pretty strong signal that things might be hanging
+ // so we want to cancel any null transactions related to this connection
+ // entry. They are just optimizations, but they aren't hooked up to
+ // anything that might get canceled from the rest of gecko, so best
+ // to assume that's what was meant by the cancel we did receive if
+ // it only applied to something in the queue.
+ if (ent) {
+ ent->CloseAllActiveConnsWithNullTransactcion(closeCode);
+ }
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
+
+ if (!ci) {
+ LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n"));
+ // Try and dispatch everything
+ for (const auto& entry : mCT.Values()) {
+ Unused << ProcessPendingQForEntry(entry.get(), true);
+ }
+ return;
+ }
+
+ LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n",
+ ci->HashKey().get()));
+
+ // start by processing the queue identified by the given connection info.
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (!(ent && ProcessPendingQForEntry(ent, false))) {
+ // if we reach here, it means that we couldn't dispatch a transaction
+ // for the specified connection info. walk the connection table...
+ for (const auto& entry : mCT.Values()) {
+ if (ProcessPendingQForEntry(entry.get(), false)) {
+ break;
+ }
+ }
+ }
+}
+
+nsresult nsHttpConnectionMgr::CancelTransactions(nsHttpConnectionInfo* ci,
+ nsresult code) {
+ LOG(("nsHttpConnectionMgr::CancelTransactions %s\n", ci->HashKey().get()));
+
+ int32_t intReason = static_cast<int32_t>(code);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransactions, intReason,
+ ci);
+}
+
+void nsHttpConnectionMgr::OnMsgCancelTransactions(int32_t code,
+ ARefBase* param) {
+ nsresult reason = static_cast<nsresult>(code);
+ nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p\n",
+ ci->HashKey().get(), ent));
+ if (ent) {
+ ent->CancelAllTransactions(reason);
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, ARefBase*) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
+
+ // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
+ mTimeOfNextWakeUp = UINT64_MAX;
+
+ // check canreuse() for all idle connections plus any active connections on
+ // connection entries that are using spdy.
+ if (mNumIdleConns ||
+ (mNumActiveConns && StaticPrefs::network_http_http2_enabled())) {
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ConnectionEntry> ent = iter.Data();
+
+ LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get()));
+
+ // Find out how long it will take for next idle connection to not
+ // be reusable anymore.
+ uint32_t timeToNextExpire = ent->PruneDeadConnections();
+
+ // If time to next expire found is shorter than time to next
+ // wake-up, we need to change the time for next wake-up.
+ if (timeToNextExpire != UINT32_MAX) {
+ uint32_t now = NowInSeconds();
+ uint64_t timeOfNextExpire = now + timeToNextExpire;
+ // If pruning of dead connections is not already scheduled to
+ // happen or time found for next connection to expire is is
+ // before mTimeOfNextWakeUp, we need to schedule the pruning to
+ // happen after timeToNextExpire.
+ if (!mTimer || timeOfNextExpire < mTimeOfNextWakeUp) {
+ PruneDeadConnectionsAfter(timeToNextExpire);
+ }
+ } else {
+ ConditionallyStopPruneDeadConnectionsTimer();
+ }
+
+ ent->RemoveEmptyPendingQ();
+
+ // If this entry is empty, we have too many entries busy then
+ // we can clean it up and restart
+ if (mCT.Count() > 125 && ent->IdleConnectionsLength() == 0 &&
+ ent->ActiveConnsLength() == 0 &&
+ ent->DnsAndConnectSocketsLength() == 0 &&
+ ent->PendingQueueLength() == 0 &&
+ ent->UrgentStartQueueLength() == 0 && !ent->mDoNotDestroy &&
+ (!ent->mUsingSpdy || mCT.Count() > 300)) {
+ LOG((" removing empty connection entry\n"));
+ iter.Remove();
+ continue;
+ }
+
+ // Otherwise use this opportunity to compact our arrays...
+ ent->Compact();
+ }
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgPruneNoTraffic(int32_t, ARefBase*) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgPruneNoTraffic\n"));
+
+ // Prune connections without traffic
+ for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) {
+ // Close the connections with no registered traffic.
+ ent->PruneNoTraffic();
+ }
+
+ mPruningNoTraffic = false; // not pruning anymore
+}
+
+void nsHttpConnectionMgr::OnMsgVerifyTraffic(int32_t, ARefBase*) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnectionMgr::OnMsgVerifyTraffic\n"));
+
+ if (mPruningNoTraffic) {
+ // Called in the time gap when the timeout to prune notraffic
+ // connections has triggered but the pruning hasn't happened yet.
+ return;
+ }
+
+ mCoalescingHash.Clear();
+
+ // Mark connections for traffic verification
+ for (const auto& entry : mCT.Values()) {
+ entry->VerifyTraffic();
+ }
+
+ // If the timer is already there. we just re-init it
+ if (!mTrafficTimer) {
+ mTrafficTimer = NS_NewTimer();
+ }
+
+ // failure to create a timer is not a fatal error, but dead
+ // connections will not be cleaned up as nicely
+ if (mTrafficTimer) {
+ // Give active connections time to get more traffic before killing
+ // them off. Default: 5000 milliseconds
+ mTrafficTimer->Init(this, gHttpHandler->NetworkChangedTimeout(),
+ nsITimer::TYPE_ONE_SHOT);
+ } else {
+ NS_WARNING("failed to create timer for VerifyTraffic!");
+ }
+ // Calling ActivateTimeoutTick to ensure the next timeout tick is 1s.
+ ActivateTimeoutTick();
+}
+
+void nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup(int32_t,
+ ARefBase* param) {
+ LOG(("nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup\n"));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mCoalescingHash.Clear();
+
+ nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
+
+ for (const auto& entry : mCT.Values()) {
+ entry->ClosePersistentConnections();
+ }
+
+ if (ci) ResetIPFamilyPreference(ci);
+}
+
+void nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup(int32_t,
+ ARefBase* param) {
+ LOG(("nsHttpConnectionMgr::OnMsgDoSingleConnectionCleanup\n"));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
+
+ if (!ci) {
+ return;
+ }
+
+ ConnectionEntry* entry = mCT.GetWeak(ci->HashKey());
+ if (entry) {
+ entry->ClosePersistentConnections();
+ }
+
+ ResetIPFamilyPreference(ci);
+}
+
+void nsHttpConnectionMgr::OnMsgReclaimConnection(HttpConnectionBase* conn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ //
+ // 1) remove the connection from the active list
+ // 2) if keep-alive, add connection to idle list
+ // 3) post event to process the pending transaction queue
+ //
+
+ MOZ_ASSERT(conn);
+ ConnectionEntry* ent = conn->ConnectionInfo()
+ ? mCT.GetWeak(conn->ConnectionInfo()->HashKey())
+ : nullptr;
+
+ if (!ent) {
+ // this can happen if the connection is made outside of the
+ // connection manager and is being "reclaimed" for use with
+ // future transactions. HTTP/2 tunnels work like this.
+ bool isWildcard = false;
+ ent = GetOrCreateConnectionEntry(conn->ConnectionInfo(), true, false, false,
+ &isWildcard);
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgReclaimConnection conn %p "
+ "forced new hash entry %s\n",
+ conn, conn->ConnectionInfo()->HashKey().get()));
+ }
+
+ MOZ_ASSERT(ent);
+ RefPtr<nsHttpConnectionInfo> ci(ent->mConnInfo);
+
+ LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [ent=%p conn=%p]\n", ent,
+ conn));
+
+ // If the connection is in the active list, remove that entry
+ // and the reference held by the mActiveConns list.
+ // This is never the final reference on conn as the event context
+ // is also holding one that is released at the end of this function.
+
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ if (!connTCP || connTCP->EverUsedSpdy()) {
+ // Spdyand Http3 connections aren't reused in the traditional HTTP way in
+ // the idleconns list, they are actively multplexed as active
+ // conns. Even when they have 0 transactions on them they are
+ // considered active connections. So when one is reclaimed it
+ // is really complete and is meant to be shut down and not
+ // reused.
+ conn->DontReuse();
+ }
+
+ // a connection that still holds a reference to a transaction was
+ // not closed naturally (i.e. it was reset or aborted) and is
+ // therefore not something that should be reused.
+ if (conn->Transaction()) {
+ conn->DontReuse();
+ }
+
+ if (NS_SUCCEEDED(ent->RemoveActiveConnection(conn)) ||
+ NS_SUCCEEDED(ent->RemovePendingConnection(conn))) {
+ } else if (!connTCP || connTCP->EverUsedSpdy()) {
+ LOG(("HttpConnectionBase %p not found in its connection entry, try ^anon",
+ conn));
+ // repeat for flipped anon flag as we share connection entries for spdy
+ // connections.
+ RefPtr<nsHttpConnectionInfo> anonInvertedCI(ci->Clone());
+ anonInvertedCI->SetAnonymous(!ci->GetAnonymous());
+
+ ConnectionEntry* ent = mCT.GetWeak(anonInvertedCI->HashKey());
+ if (ent) {
+ if (NS_SUCCEEDED(ent->RemoveActiveConnection(conn))) {
+ } else {
+ LOG(
+ ("HttpConnectionBase %p could not be removed from its entry's "
+ "active list",
+ conn));
+ }
+ }
+ }
+
+ if (connTCP && connTCP->CanReuse()) {
+ LOG((" adding connection to idle list\n"));
+ // Keep The idle connection list sorted with the connections that
+ // have moved the largest data pipelines at the front because these
+ // connections have the largest cwnds on the server.
+
+ // The linear search is ok here because the number of idleconns
+ // in a single entry is generally limited to a small number (i.e. 6)
+
+ ent->InsertIntoIdleConnections(connTCP);
+ } else {
+ if (ent->IsInH2WebsocketConns(conn)) {
+ ent->RemoveH2WebsocketConns(conn);
+ }
+ LOG((" connection cannot be reused; closing connection\n"));
+ conn->SetCloseReason(ConnectionCloseReason::CANT_REUSED);
+ conn->Close(NS_ERROR_ABORT);
+ }
+
+ OnMsgProcessPendingQ(0, ci);
+}
+
+void nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv = NS_OK;
+ nsCompleteUpgradeData* data = static_cast<nsCompleteUpgradeData*>(param);
+ MOZ_ASSERT(data->mTrans && data->mTrans->Caps() & NS_HTTP_STICKY_CONNECTION);
+
+ RefPtr<nsAHttpConnection> conn(data->mTrans->Connection());
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
+ "conn=%p listener=%p wrapped=%d\n",
+ conn.get(), data->mUpgradeListener.get(), data->mJsWrapped));
+
+ if (!conn) {
+ // Delay any error reporting to happen in transportAvailableFunc
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ MOZ_ASSERT(!data->mSocketTransport);
+ rv = conn->TakeTransport(getter_AddRefs(data->mSocketTransport),
+ getter_AddRefs(data->mSocketIn),
+ getter_AddRefs(data->mSocketOut));
+
+ if (NS_FAILED(rv)) {
+ LOG((" conn->TakeTransport failed with %" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ RefPtr<nsCompleteUpgradeData> upgradeData(data);
+
+ nsCOMPtr<nsIAsyncInputStream> socketIn;
+ nsCOMPtr<nsIAsyncOutputStream> socketOut;
+
+ // If this is for JS, the input and output sockets need to be piped over the
+ // socket thread. Otherwise, the JS may attempt to read and/or write the
+ // sockets on the main thread, which could cause network I/O on the main
+ // thread. This is particularly bad in the case of TLS connections, because
+ // PSM and NSS rely on those connections only being used on the socket
+ // thread.
+ if (data->mJsWrapped) {
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ uint32_t segsize = 0;
+ uint32_t segcount = 0;
+ net_ResolveSegmentParams(segsize, segcount);
+ if (NS_SUCCEEDED(rv)) {
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(socketOut), true, true,
+ segsize, segcount);
+ rv = NS_AsyncCopy(pipeIn, data->mSocketOut, gSocketTransportService,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, segsize);
+ }
+
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ if (NS_SUCCEEDED(rv)) {
+ NS_NewPipe2(getter_AddRefs(socketIn), getter_AddRefs(pipeOut), true, true,
+ segsize, segcount);
+ rv = NS_AsyncCopy(data->mSocketIn, pipeOut, gSocketTransportService,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize);
+ }
+ } else {
+ socketIn = upgradeData->mSocketIn;
+ socketOut = upgradeData->mSocketOut;
+ }
+
+ auto transportAvailableFunc = [upgradeData{std::move(upgradeData)}, socketIn,
+ socketOut, aRv(rv)]() {
+ // Handle any potential previous errors first
+ // and call OnUpgradeFailed if necessary.
+ nsresult rv = aRv;
+
+ if (NS_FAILED(rv)) {
+ rv = upgradeData->mUpgradeListener->OnUpgradeFailed(rv);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnUpgradeFailed failed."
+ " listener=%p\n",
+ upgradeData->mUpgradeListener.get()));
+ }
+ return;
+ }
+
+ rv = upgradeData->mUpgradeListener->OnTransportAvailable(
+ upgradeData->mSocketTransport, socketIn, socketOut);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgCompleteUpgrade OnTransportAvailable "
+ "failed. listener=%p\n",
+ upgradeData->mUpgradeListener.get()));
+ }
+ };
+
+ if (data->mJsWrapped) {
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
+ "conn=%p listener=%p wrapped=%d pass to main thread\n",
+ conn.get(), data->mUpgradeListener.get(), data->mJsWrapped));
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("net::nsHttpConnectionMgr::OnMsgCompleteUpgrade",
+ transportAvailableFunc));
+ } else {
+ transportAvailableFunc();
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgUpdateParam(int32_t inParam, ARefBase*) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ uint32_t param = static_cast<uint32_t>(inParam);
+ uint16_t name = ((param) & 0xFFFF0000) >> 16;
+ uint16_t value = param & 0x0000FFFF;
+
+ switch (name) {
+ case MAX_CONNECTIONS:
+ mMaxConns = value;
+ break;
+ case MAX_URGENT_START_Q:
+ mMaxUrgentExcessiveConns = value;
+ break;
+ case MAX_PERSISTENT_CONNECTIONS_PER_HOST:
+ mMaxPersistConnsPerHost = value;
+ break;
+ case MAX_PERSISTENT_CONNECTIONS_PER_PROXY:
+ mMaxPersistConnsPerProxy = value;
+ break;
+ case MAX_REQUEST_DELAY:
+ mMaxRequestDelay = value;
+ break;
+ case THROTTLING_ENABLED:
+ SetThrottlingEnabled(!!value);
+ break;
+ case THROTTLING_SUSPEND_FOR:
+ mThrottleSuspendFor = value;
+ break;
+ case THROTTLING_RESUME_FOR:
+ mThrottleResumeFor = value;
+ break;
+ case THROTTLING_READ_LIMIT:
+ mThrottleReadLimit = value;
+ break;
+ case THROTTLING_READ_INTERVAL:
+ mThrottleReadInterval = value;
+ break;
+ case THROTTLING_HOLD_TIME:
+ mThrottleHoldTime = value;
+ break;
+ case THROTTLING_MAX_TIME:
+ mThrottleMaxTime = TimeDuration::FromMilliseconds(value);
+ break;
+ case PROXY_BE_CONSERVATIVE:
+ mBeConservativeForProxy = !!value;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected parameter name");
+ }
+}
+
+// Read Timeout Tick handlers
+
+void nsHttpConnectionMgr::ActivateTimeoutTick() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(
+ ("nsHttpConnectionMgr::ActivateTimeoutTick() "
+ "this=%p mTimeoutTick=%p\n",
+ this, mTimeoutTick.get()));
+
+ // The timer tick should be enabled if it is not already pending.
+ // Upon running the tick will rearm itself if there are active
+ // connections available.
+
+ if (mTimeoutTick && mTimeoutTickArmed) {
+ // make sure we get one iteration on a quick tick
+ if (mTimeoutTickNext > 1) {
+ mTimeoutTickNext = 1;
+ mTimeoutTick->SetDelay(1000);
+ }
+ return;
+ }
+
+ if (!mTimeoutTick) {
+ mTimeoutTick = NS_NewTimer();
+ if (!mTimeoutTick) {
+ NS_WARNING("failed to create timer for http timeout management");
+ return;
+ }
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ if (!mSocketThreadTarget) {
+ NS_WARNING("cannot activate timout if not initialized or shutdown");
+ return;
+ }
+ mTimeoutTick->SetTarget(mSocketThreadTarget);
+ }
+
+ if (mIsShuttingDown) { // Atomic
+ // don't set a timer to generate an event if we're shutting down
+ // (and mSocketThreadTarget might be null or garbage if we're shutting down)
+ return;
+ }
+ MOZ_ASSERT(!mTimeoutTickArmed, "timer tick armed");
+ mTimeoutTickArmed = true;
+ mTimeoutTick->Init(this, 1000, nsITimer::TYPE_REPEATING_SLACK);
+}
+
+class UINT64Wrapper : public ARefBase {
+ public:
+ explicit UINT64Wrapper(uint64_t aUint64) : mUint64(aUint64) {}
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UINT64Wrapper, override)
+
+ uint64_t GetValue() { return mUint64; }
+
+ private:
+ uint64_t mUint64;
+ virtual ~UINT64Wrapper() = default;
+};
+
+nsresult nsHttpConnectionMgr::UpdateCurrentBrowserId(uint64_t aId) {
+ RefPtr<UINT64Wrapper> idWrapper = new UINT64Wrapper(aId);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId, 0,
+ idWrapper);
+}
+
+void nsHttpConnectionMgr::SetThrottlingEnabled(bool aEnable) {
+ LOG(("nsHttpConnectionMgr::SetThrottlingEnabled enable=%d", aEnable));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mThrottleEnabled = aEnable;
+
+ if (mThrottleEnabled) {
+ EnsureThrottleTickerIfNeeded();
+ } else {
+ DestroyThrottleTicker();
+ ResumeReadOf(mActiveTransactions[false]);
+ ResumeReadOf(mActiveTransactions[true]);
+ }
+}
+
+bool nsHttpConnectionMgr::InThrottlingTimeWindow() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mThrottlingWindowEndsAt.IsNull()) {
+ return true;
+ }
+ return TimeStamp::NowLoRes() <= mThrottlingWindowEndsAt;
+}
+
+void nsHttpConnectionMgr::TouchThrottlingTimeWindow(bool aEnsureTicker) {
+ LOG(("nsHttpConnectionMgr::TouchThrottlingTimeWindow"));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mThrottlingWindowEndsAt = TimeStamp::NowLoRes() + mThrottleMaxTime;
+
+ if (!mThrottleTicker && MOZ_LIKELY(aEnsureTicker) &&
+ MOZ_LIKELY(mThrottleEnabled)) {
+ EnsureThrottleTickerIfNeeded();
+ }
+}
+
+void nsHttpConnectionMgr::LogActiveTransactions(char operation) {
+ if (!LOG_ENABLED()) {
+ return;
+ }
+
+ nsTArray<RefPtr<nsHttpTransaction>>* trs = nullptr;
+ uint32_t au, at, bu = 0, bt = 0;
+
+ trs = mActiveTransactions[false].Get(mCurrentBrowserId);
+ au = trs ? trs->Length() : 0;
+ trs = mActiveTransactions[true].Get(mCurrentBrowserId);
+ at = trs ? trs->Length() : 0;
+
+ for (const auto& data : mActiveTransactions[false].Values()) {
+ bu += data->Length();
+ }
+ bu -= au;
+ for (const auto& data : mActiveTransactions[true].Values()) {
+ bt += data->Length();
+ }
+ bt -= at;
+
+ // Shows counts of:
+ // - unthrottled transaction for the active tab
+ // - throttled transaction for the active tab
+ // - unthrottled transaction for background tabs
+ // - throttled transaction for background tabs
+ LOG(("Active transactions %c[%u,%u,%u,%u]", operation, au, at, bu, bt));
+}
+
+void nsHttpConnectionMgr::AddActiveTransaction(nsHttpTransaction* aTrans) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ uint64_t tabId = aTrans->BrowserId();
+ bool throttled = aTrans->EligibleForThrottling();
+
+ nsTArray<RefPtr<nsHttpTransaction>>* transactions =
+ mActiveTransactions[throttled].GetOrInsertNew(tabId);
+
+ MOZ_ASSERT(!transactions->Contains(aTrans));
+
+ transactions->AppendElement(aTrans);
+
+ LOG(("nsHttpConnectionMgr::AddActiveTransaction t=%p tabid=%" PRIx64
+ "(%d) thr=%d",
+ aTrans, tabId, tabId == mCurrentBrowserId, throttled));
+ LogActiveTransactions('+');
+
+ if (tabId == mCurrentBrowserId) {
+ mActiveTabTransactionsExist = true;
+ if (!throttled) {
+ mActiveTabUnthrottledTransactionsExist = true;
+ }
+ }
+
+ // Shift the throttling window to the future (actually, makes sure
+ // that throttling will engage when there is anything to throttle.)
+ // The |false| argument means we don't need this call to ensure
+ // the ticker, since we do it just below. Calling
+ // EnsureThrottleTickerIfNeeded directly does a bit more than call
+ // from inside of TouchThrottlingTimeWindow.
+ TouchThrottlingTimeWindow(false);
+
+ if (!mThrottleEnabled) {
+ return;
+ }
+
+ EnsureThrottleTickerIfNeeded();
+}
+
+void nsHttpConnectionMgr::RemoveActiveTransaction(
+ nsHttpTransaction* aTrans, Maybe<bool> const& aOverride) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ uint64_t tabId = aTrans->BrowserId();
+ bool forActiveTab = tabId == mCurrentBrowserId;
+ bool throttled = aOverride.valueOr(aTrans->EligibleForThrottling());
+
+ nsTArray<RefPtr<nsHttpTransaction>>* transactions =
+ mActiveTransactions[throttled].Get(tabId);
+
+ if (!transactions || !transactions->RemoveElement(aTrans)) {
+ // Was not tracked as active, probably just ignore.
+ return;
+ }
+
+ LOG(("nsHttpConnectionMgr::RemoveActiveTransaction t=%p tabid=%" PRIx64
+ "(%d) thr=%d",
+ aTrans, tabId, forActiveTab, throttled));
+
+ if (!transactions->IsEmpty()) {
+ // There are still transactions of the type, hence nothing in the throttling
+ // conditions has changed and we don't need to update "Exists" caches nor we
+ // need to wake any now throttled transactions.
+ LogActiveTransactions('-');
+ return;
+ }
+
+ // To optimize the following logic, always remove the entry when the array is
+ // empty.
+ mActiveTransactions[throttled].Remove(tabId);
+ LogActiveTransactions('-');
+
+ if (forActiveTab) {
+ // Update caches of the active tab transaction existence, since it's now
+ // affected
+ if (!throttled) {
+ mActiveTabUnthrottledTransactionsExist = false;
+ }
+ if (mActiveTabTransactionsExist) {
+ mActiveTabTransactionsExist =
+ mActiveTransactions[!throttled].Contains(tabId);
+ }
+ }
+
+ if (!mThrottleEnabled) {
+ return;
+ }
+
+ bool unthrottledExist = !mActiveTransactions[false].IsEmpty();
+ bool throttledExist = !mActiveTransactions[true].IsEmpty();
+
+ if (!unthrottledExist && !throttledExist) {
+ // Nothing active globally, just get rid of the timer completely and we are
+ // done.
+ MOZ_ASSERT(!mActiveTabUnthrottledTransactionsExist);
+ MOZ_ASSERT(!mActiveTabTransactionsExist);
+
+ DestroyThrottleTicker();
+ return;
+ }
+
+ if (mThrottleVersion == 1) {
+ if (!mThrottlingInhibitsReading) {
+ // There is then nothing to wake up. Affected transactions will not be
+ // put to sleep automatically on next tick.
+ LOG((" reading not currently inhibited"));
+ return;
+ }
+ }
+
+ if (mActiveTabUnthrottledTransactionsExist) {
+ // There are still unthrottled transactions for the active tab, hence the
+ // state is unaffected and we don't need to do anything (nothing to wake).
+ LOG((" there are unthrottled for the active tab"));
+ return;
+ }
+
+ if (mActiveTabTransactionsExist) {
+ // There are only trottled transactions for the active tab.
+ // If the last transaction we just removed was a non-throttled for the
+ // active tab we can wake the throttled transactions for the active tab.
+ if (forActiveTab && !throttled) {
+ LOG((" resuming throttled for active tab"));
+ ResumeReadOf(mActiveTransactions[true].Get(mCurrentBrowserId));
+ }
+ return;
+ }
+
+ if (!unthrottledExist) {
+ // There are no unthrottled transactions for any tab. Resume all throttled,
+ // all are only for background tabs.
+ LOG((" delay resuming throttled for background tabs"));
+ DelayedResumeBackgroundThrottledTransactions();
+ return;
+ }
+
+ if (forActiveTab) {
+ // Removing the last transaction for the active tab frees up the unthrottled
+ // background tabs transactions.
+ LOG((" delay resuming unthrottled for background tabs"));
+ DelayedResumeBackgroundThrottledTransactions();
+ return;
+ }
+
+ LOG((" not resuming anything"));
+}
+
+void nsHttpConnectionMgr::UpdateActiveTransaction(nsHttpTransaction* aTrans) {
+ LOG(("nsHttpConnectionMgr::UpdateActiveTransaction ENTER t=%p", aTrans));
+
+ // First remove then add. In case of a download that is the only active
+ // transaction and has just been marked as download (goes unthrottled to
+ // throttled), adding first would cause it to be throttled for first few
+ // milliseconds - becuause it would appear as if there were both throttled
+ // and unthrottled transactions at the time.
+
+ Maybe<bool> reversed;
+ reversed.emplace(!aTrans->EligibleForThrottling());
+ RemoveActiveTransaction(aTrans, reversed);
+
+ AddActiveTransaction(aTrans);
+
+ LOG(("nsHttpConnectionMgr::UpdateActiveTransaction EXIT t=%p", aTrans));
+}
+
+bool nsHttpConnectionMgr::ShouldThrottle(nsHttpTransaction* aTrans) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("nsHttpConnectionMgr::ShouldThrottle trans=%p", aTrans));
+
+ if (mThrottleVersion == 1) {
+ if (!mThrottlingInhibitsReading || !mThrottleEnabled) {
+ return false;
+ }
+ } else {
+ if (!mThrottleEnabled) {
+ return false;
+ }
+ }
+
+ uint64_t tabId = aTrans->BrowserId();
+ bool forActiveTab = tabId == mCurrentBrowserId;
+ bool throttled = aTrans->EligibleForThrottling();
+
+ bool stop = [=]() {
+ if (mActiveTabTransactionsExist) {
+ if (!tabId) {
+ // Chrome initiated and unidentified transactions just respect
+ // their throttle flag, when something for the active tab is happening.
+ // This also includes downloads.
+ LOG((" active tab loads, trans is tab-less, throttled=%d", throttled));
+ return throttled;
+ }
+ if (!forActiveTab) {
+ // This is a background tab request, we want them to always throttle
+ // when there are transactions running for the ative tab.
+ LOG((" active tab loads, trans not of the active tab"));
+ return true;
+ }
+
+ if (mActiveTabUnthrottledTransactionsExist) {
+ // Unthrottled transactions for the active tab take precedence
+ LOG((" active tab loads unthrottled, trans throttled=%d", throttled));
+ return throttled;
+ }
+
+ LOG((" trans for active tab, don't throttle"));
+ return false;
+ }
+
+ MOZ_ASSERT(!forActiveTab);
+
+ if (!mActiveTransactions[false].IsEmpty()) {
+ // This means there are unthrottled active transactions for background
+ // tabs. If we are here, there can't be any transactions for the active
+ // tab. (If there is no transaction for a tab id, there is no entry for it
+ // in the hashtable.)
+ LOG((" backround tab(s) load unthrottled, trans throttled=%d",
+ throttled));
+ return throttled;
+ }
+
+ // There are only unthrottled transactions for background tabs: don't
+ // throttle.
+ LOG((" backround tab(s) load throttled, don't throttle"));
+ return false;
+ }();
+
+ if (forActiveTab && !stop) {
+ // This is an active-tab transaction and is allowed to read. Hence,
+ // prolong the throttle time window to make sure all 'lower-decks'
+ // transactions will actually throttle.
+ TouchThrottlingTimeWindow();
+ return false;
+ }
+
+ // Only stop reading when in the configured throttle max-time (aka time
+ // window). This window is prolonged (restarted) by a call to
+ // TouchThrottlingTimeWindow called on new transaction activation or on
+ // receive of response bytes of an active tab transaction.
+ bool inWindow = InThrottlingTimeWindow();
+
+ LOG((" stop=%d, in-window=%d, delayed-bck-timer=%d", stop, inWindow,
+ !!mDelayedResumeReadTimer));
+
+ if (!forActiveTab) {
+ // If the delayed background resume timer exists, background transactions
+ // are scheduled to be woken after a delay, hence leave them throttled.
+ inWindow = inWindow || mDelayedResumeReadTimer;
+ }
+
+ return stop && inWindow;
+}
+
+bool nsHttpConnectionMgr::IsConnEntryUnderPressure(
+ nsHttpConnectionInfo* connInfo) {
+ ConnectionEntry* ent = mCT.GetWeak(connInfo->HashKey());
+ if (!ent) {
+ // No entry, no pressure.
+ return false;
+ }
+
+ return ent->PendingQueueLengthForWindow(mCurrentBrowserId) > 0;
+}
+
+bool nsHttpConnectionMgr::IsThrottleTickerNeeded() {
+ LOG(("nsHttpConnectionMgr::IsThrottleTickerNeeded"));
+
+ if (mActiveTabUnthrottledTransactionsExist &&
+ mActiveTransactions[false].Count() > 1) {
+ LOG((" there are unthrottled transactions for both active and bck"));
+ return true;
+ }
+
+ if (mActiveTabTransactionsExist && mActiveTransactions[true].Count() > 1) {
+ LOG((" there are throttled transactions for both active and bck"));
+ return true;
+ }
+
+ if (!mActiveTransactions[true].IsEmpty() &&
+ !mActiveTransactions[false].IsEmpty()) {
+ LOG((" there are both throttled and unthrottled transactions"));
+ return true;
+ }
+
+ LOG((" nothing to throttle"));
+ return false;
+}
+
+void nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded"));
+ if (!IsThrottleTickerNeeded()) {
+ return;
+ }
+
+ // There is a new demand to throttle, hence unschedule delayed resume
+ // of background throttled transastions.
+ CancelDelayedResumeBackgroundThrottledTransactions();
+
+ if (mThrottleTicker) {
+ return;
+ }
+
+ mThrottleTicker = NS_NewTimer();
+ if (mThrottleTicker) {
+ if (mThrottleVersion == 1) {
+ MOZ_ASSERT(!mThrottlingInhibitsReading);
+
+ mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
+ mThrottlingInhibitsReading = true;
+ } else {
+ mThrottleTicker->Init(this, mThrottleReadInterval,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ }
+
+ LogActiveTransactions('^');
+}
+
+// Can be called with or without the monitor held
+void nsHttpConnectionMgr::DestroyThrottleTicker() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // Nothing to throttle, hence no need for this timer anymore.
+ CancelDelayedResumeBackgroundThrottledTransactions();
+
+ MOZ_ASSERT(!mThrottleEnabled || !IsThrottleTickerNeeded());
+
+ if (!mThrottleTicker) {
+ return;
+ }
+
+ LOG(("nsHttpConnectionMgr::DestroyThrottleTicker"));
+ mThrottleTicker->Cancel();
+ mThrottleTicker = nullptr;
+
+ if (mThrottleVersion == 1) {
+ mThrottlingInhibitsReading = false;
+ }
+
+ LogActiveTransactions('v');
+}
+
+void nsHttpConnectionMgr::ThrottlerTick() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mThrottleVersion == 1) {
+ mThrottlingInhibitsReading = !mThrottlingInhibitsReading;
+
+ LOG(("nsHttpConnectionMgr::ThrottlerTick inhibit=%d",
+ mThrottlingInhibitsReading));
+
+ // If there are only background transactions to be woken after a delay, keep
+ // the ticker so that we woke them only for the resume-for interval and then
+ // throttle them again until the background-resume delay passes.
+ if (!mThrottlingInhibitsReading && !mDelayedResumeReadTimer &&
+ (!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
+ LOG((" last tick"));
+ mThrottleTicker = nullptr;
+ }
+
+ if (mThrottlingInhibitsReading) {
+ if (mThrottleTicker) {
+ mThrottleTicker->Init(this, mThrottleSuspendFor,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ } else {
+ if (mThrottleTicker) {
+ mThrottleTicker->Init(this, mThrottleResumeFor,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+
+ ResumeReadOf(mActiveTransactions[false], true);
+ ResumeReadOf(mActiveTransactions[true]);
+ }
+ } else {
+ LOG(("nsHttpConnectionMgr::ThrottlerTick"));
+
+ // If there are only background transactions to be woken after a delay, keep
+ // the ticker so that we still keep the low read limit for that time.
+ if (!mDelayedResumeReadTimer &&
+ (!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
+ LOG((" last tick"));
+ mThrottleTicker = nullptr;
+ }
+
+ if (mThrottleTicker) {
+ mThrottleTicker->Init(this, mThrottleReadInterval,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+
+ ResumeReadOf(mActiveTransactions[false], true);
+ ResumeReadOf(mActiveTransactions[true]);
+ }
+}
+
+void nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mThrottleVersion == 1) {
+ if (mDelayedResumeReadTimer) {
+ return;
+ }
+ } else {
+ // If the mThrottleTicker doesn't exist, there is nothing currently
+ // being throttled. Hence, don't invoke the hold time interval.
+ // This is called also when a single download transaction becomes
+ // marked as throttleable. We would otherwise block it unnecessarily.
+ if (mDelayedResumeReadTimer || !mThrottleTicker) {
+ return;
+ }
+ }
+
+ LOG(("nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions"));
+ NS_NewTimerWithObserver(getter_AddRefs(mDelayedResumeReadTimer), this,
+ mThrottleHoldTime, nsITimer::TYPE_ONE_SHOT);
+}
+
+void nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions() {
+ if (!mDelayedResumeReadTimer) {
+ return;
+ }
+
+ LOG(
+ ("nsHttpConnectionMgr::"
+ "CancelDelayedResumeBackgroundThrottledTransactions"));
+ mDelayedResumeReadTimer->Cancel();
+ mDelayedResumeReadTimer = nullptr;
+}
+
+void nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions"));
+ mDelayedResumeReadTimer = nullptr;
+
+ if (!IsThrottleTickerNeeded()) {
+ DestroyThrottleTicker();
+ }
+
+ if (!mActiveTransactions[false].IsEmpty()) {
+ ResumeReadOf(mActiveTransactions[false], true);
+ } else {
+ ResumeReadOf(mActiveTransactions[true], true);
+ }
+}
+
+void nsHttpConnectionMgr::ResumeReadOf(
+ nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>&
+ hashtable,
+ bool excludeForActiveTab) {
+ for (const auto& entry : hashtable) {
+ if (excludeForActiveTab && entry.GetKey() == mCurrentBrowserId) {
+ // These have never been throttled (never stopped reading)
+ continue;
+ }
+ ResumeReadOf(entry.GetWeak());
+ }
+}
+
+void nsHttpConnectionMgr::ResumeReadOf(
+ nsTArray<RefPtr<nsHttpTransaction>>* transactions) {
+ MOZ_ASSERT(transactions);
+
+ for (const auto& trans : *transactions) {
+ trans->ResumeReading();
+ }
+}
+
+void nsHttpConnectionMgr::NotifyConnectionOfBrowserIdChange(
+ uint64_t previousId) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsTArray<RefPtr<nsHttpTransaction>>* transactions = nullptr;
+ nsTArray<RefPtr<nsAHttpConnection>> connections;
+
+ auto addConnectionHelper =
+ [&connections](nsTArray<RefPtr<nsHttpTransaction>>* trans) {
+ if (!trans) {
+ return;
+ }
+
+ for (const auto& t : *trans) {
+ RefPtr<nsAHttpConnection> conn = t->Connection();
+ if (conn && !connections.Contains(conn)) {
+ connections.AppendElement(conn);
+ }
+ }
+ };
+
+ // Get unthrottled transactions with the previous and current window id.
+ transactions = mActiveTransactions[false].Get(previousId);
+ addConnectionHelper(transactions);
+ transactions = mActiveTransactions[false].Get(mCurrentBrowserId);
+ addConnectionHelper(transactions);
+
+ // Get throttled transactions with the previous and current window id.
+ transactions = mActiveTransactions[true].Get(previousId);
+ addConnectionHelper(transactions);
+ transactions = mActiveTransactions[true].Get(mCurrentBrowserId);
+ addConnectionHelper(transactions);
+
+ for (const auto& conn : connections) {
+ conn->CurrentBrowserIdChanged(mCurrentBrowserId);
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId(int32_t aLoading,
+ ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ uint64_t id = static_cast<UINT64Wrapper*>(param)->GetValue();
+
+ if (mCurrentBrowserId == id) {
+ // duplicate notification
+ return;
+ }
+
+ bool activeTabWasLoading = mActiveTabTransactionsExist;
+
+ uint64_t previousId = mCurrentBrowserId;
+ mCurrentBrowserId = id;
+
+ if (gHttpHandler->ActiveTabPriority()) {
+ NotifyConnectionOfBrowserIdChange(previousId);
+ }
+
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgUpdateCurrentBrowserId"
+ " id=%" PRIx64 "\n",
+ mCurrentBrowserId));
+
+ nsTArray<RefPtr<nsHttpTransaction>>* transactions = nullptr;
+
+ // Update the "Exists" caches and resume any transactions that now deserve it,
+ // changing the active tab changes the conditions for throttling.
+ transactions = mActiveTransactions[false].Get(mCurrentBrowserId);
+ mActiveTabUnthrottledTransactionsExist = !!transactions;
+
+ if (!mActiveTabUnthrottledTransactionsExist) {
+ transactions = mActiveTransactions[true].Get(mCurrentBrowserId);
+ }
+ mActiveTabTransactionsExist = !!transactions;
+
+ if (transactions) {
+ // This means there are some transactions for this newly activated tab,
+ // resume them but anything else.
+ LOG((" resuming newly activated tab transactions"));
+ ResumeReadOf(transactions);
+ return;
+ }
+
+ if (!activeTabWasLoading) {
+ // There were no transactions for the previously active tab, hence
+ // all remaning transactions, if there were, were all unthrottled,
+ // no need to wake them.
+ return;
+ }
+
+ if (!mActiveTransactions[false].IsEmpty()) {
+ LOG((" resuming unthrottled background transactions"));
+ ResumeReadOf(mActiveTransactions[false]);
+ return;
+ }
+
+ if (!mActiveTransactions[true].IsEmpty()) {
+ LOG((" resuming throttled background transactions"));
+ ResumeReadOf(mActiveTransactions[true]);
+ return;
+ }
+
+ DestroyThrottleTicker();
+}
+
+void nsHttpConnectionMgr::TimeoutTick() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mTimeoutTick, "no readtimeout tick");
+
+ LOG(("nsHttpConnectionMgr::TimeoutTick active=%d\n", mNumActiveConns));
+ // The next tick will be between 1 second and 1 hr
+ // Set it to the max value here, and the TimeoutTick()s can
+ // reduce it to their local needs.
+ mTimeoutTickNext = 3600; // 1hr
+
+ for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) {
+ uint32_t timeoutTickNext = ent->TimeoutTick();
+ mTimeoutTickNext = std::min(mTimeoutTickNext, timeoutTickNext);
+ }
+
+ if (mTimeoutTick) {
+ mTimeoutTickNext = std::max(mTimeoutTickNext, 1U);
+ mTimeoutTick->SetDelay(mTimeoutTickNext * 1000);
+ }
+}
+
+// GetOrCreateConnectionEntry finds a ent for a particular CI for use in
+// dispatching a transaction according to these rules
+// 1] use an ent that matches the ci that can be dispatched immediately
+// 2] otherwise use an ent of wildcard(ci) than can be dispatched immediately
+// 3] otherwise create an ent that matches ci and make new conn on it
+
+ConnectionEntry* nsHttpConnectionMgr::GetOrCreateConnectionEntry(
+ nsHttpConnectionInfo* specificCI, bool prohibitWildCard, bool aNoHttp2,
+ bool aNoHttp3, bool* aIsWildcard, bool* aAvailableForDispatchNow) {
+ if (aAvailableForDispatchNow) {
+ *aAvailableForDispatchNow = false;
+ }
+ *aIsWildcard = false;
+
+ // step 1
+ ConnectionEntry* specificEnt = mCT.GetWeak(specificCI->HashKey());
+ if (specificEnt && specificEnt->AvailableForDispatchNow()) {
+ if (aAvailableForDispatchNow) {
+ *aAvailableForDispatchNow = true;
+ }
+ return specificEnt;
+ }
+
+ // step 1 repeated for an inverted anonymous flag; we return an entry
+ // only when it has an h2 established connection that is not authenticated
+ // with a client certificate.
+ RefPtr<nsHttpConnectionInfo> anonInvertedCI(specificCI->Clone());
+ anonInvertedCI->SetAnonymous(!specificCI->GetAnonymous());
+ ConnectionEntry* invertedEnt = mCT.GetWeak(anonInvertedCI->HashKey());
+ if (invertedEnt) {
+ HttpConnectionBase* h2orh3conn =
+ GetH2orH3ActiveConn(invertedEnt, aNoHttp2, aNoHttp3);
+ if (h2orh3conn && h2orh3conn->IsExperienced() &&
+ h2orh3conn->NoClientCertAuth()) {
+ MOZ_ASSERT(h2orh3conn->UsingSpdy() || h2orh3conn->UsingHttp3());
+ LOG(
+ ("GetOrCreateConnectionEntry is coalescing h2/3 an/onymous "
+ "connections, ent=%p",
+ invertedEnt));
+ return invertedEnt;
+ }
+ }
+
+ if (!specificCI->UsingHttpsProxy()) {
+ prohibitWildCard = true;
+ }
+
+ // step 2
+ if (!prohibitWildCard && aNoHttp3) {
+ RefPtr<nsHttpConnectionInfo> wildCardProxyCI;
+ DebugOnly<nsresult> rv =
+ specificCI->CreateWildCard(getter_AddRefs(wildCardProxyCI));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ ConnectionEntry* wildCardEnt = mCT.GetWeak(wildCardProxyCI->HashKey());
+ if (wildCardEnt && wildCardEnt->AvailableForDispatchNow()) {
+ if (aAvailableForDispatchNow) {
+ *aAvailableForDispatchNow = true;
+ }
+ *aIsWildcard = true;
+ return wildCardEnt;
+ }
+ }
+
+ // step 3
+ if (!specificEnt) {
+ RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
+ specificEnt = new ConnectionEntry(clone);
+ mCT.InsertOrUpdate(clone->HashKey(), RefPtr{specificEnt});
+ }
+ return specificEnt;
+}
+
+void nsHttpConnectionMgr::DoSpeculativeConnection(
+ SpeculativeTransaction* aTrans, bool aFetchHTTPSRR) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(aTrans);
+
+ bool isWildcard = false;
+ ConnectionEntry* ent = GetOrCreateConnectionEntry(
+ aTrans->ConnectionInfo(), false, aTrans->Caps() & NS_HTTP_DISALLOW_SPDY,
+ aTrans->Caps() & NS_HTTP_DISALLOW_HTTP3, &isWildcard);
+ if (!aFetchHTTPSRR &&
+ gHttpHandler->EchConfigEnabled(aTrans->ConnectionInfo()->IsHttp3())) {
+ // This happens when this is called from
+ // SpeculativeTransaction::OnHTTPSRRAvailable. We have to update this
+ // entry's echConfig so that the newly created connection can use the latest
+ // echConfig.
+ ent->MaybeUpdateEchConfig(aTrans->ConnectionInfo());
+ }
+ DoSpeculativeConnectionInternal(ent, aTrans, aFetchHTTPSRR);
+}
+
+void nsHttpConnectionMgr::DoSpeculativeConnectionInternal(
+ ConnectionEntry* aEnt, SpeculativeTransaction* aTrans, bool aFetchHTTPSRR) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(aTrans);
+ MOZ_ASSERT(aEnt);
+ if (!gHttpHandler->Active()) {
+ // Do nothing if we are shutting down.
+ return;
+ }
+
+ if (aFetchHTTPSRR && NS_SUCCEEDED(aTrans->FetchHTTPSRR())) {
+ // nsHttpConnectionMgr::DoSpeculativeConnection will be called again when
+ // HTTPS RR is available.
+ return;
+ }
+
+ uint32_t parallelSpeculativeConnectLimit =
+ aTrans->ParallelSpeculativeConnectLimit()
+ ? *aTrans->ParallelSpeculativeConnectLimit()
+ : gHttpHandler->ParallelSpeculativeConnectLimit();
+ bool ignoreIdle = aTrans->IgnoreIdle() ? *aTrans->IgnoreIdle() : false;
+ bool isFromPredictor =
+ aTrans->IsFromPredictor() ? *aTrans->IsFromPredictor() : false;
+ bool allow1918 = aTrans->Allow1918() ? *aTrans->Allow1918() : false;
+
+ bool keepAlive = aTrans->Caps() & NS_HTTP_ALLOW_KEEPALIVE;
+ if (mNumDnsAndConnectSockets < parallelSpeculativeConnectLimit &&
+ ((ignoreIdle &&
+ (aEnt->IdleConnectionsLength() < parallelSpeculativeConnectLimit)) ||
+ !aEnt->IdleConnectionsLength()) &&
+ !(keepAlive && aEnt->RestrictConnections()) &&
+ !AtActiveConnectionLimit(aEnt, aTrans->Caps())) {
+ nsresult rv = aEnt->CreateDnsAndConnectSocket(aTrans, aTrans->Caps(), true,
+ isFromPredictor, false,
+ allow1918, nullptr);
+ if (NS_FAILED(rv)) {
+ glean::networking::speculative_connect_outcome
+ .Get("aborted_socket_fail"_ns)
+ .Add(1);
+ LOG(
+ ("DoSpeculativeConnectionInternal Transport socket creation "
+ "failure: %" PRIx32 "\n",
+ static_cast<uint32_t>(rv)));
+ } else {
+ glean::networking::speculative_connect_outcome.Get("successful"_ns)
+ .Add(1);
+ }
+ } else {
+ glean::networking::speculative_connect_outcome
+ .Get("aborted_socket_limit"_ns)
+ .Add(1);
+ LOG(
+ ("DoSpeculativeConnectionInternal Transport ci=%s "
+ "not created due to existing connection count:%d",
+ aEnt->mConnInfo->HashKey().get(), parallelSpeculativeConnectLimit));
+ }
+}
+
+void nsHttpConnectionMgr::DoFallbackConnection(SpeculativeTransaction* aTrans,
+ bool aFetchHTTPSRR) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(aTrans);
+
+ LOG(("nsHttpConnectionMgr::DoFallbackConnection"));
+
+ bool availableForDispatchNow = false;
+ bool aIsWildcard = false;
+ ConnectionEntry* ent = GetOrCreateConnectionEntry(
+ aTrans->ConnectionInfo(), false, aTrans->Caps() & NS_HTTP_DISALLOW_SPDY,
+ aTrans->Caps() & NS_HTTP_DISALLOW_HTTP3, &aIsWildcard,
+ &availableForDispatchNow);
+
+ if (availableForDispatchNow) {
+ LOG(
+ ("nsHttpConnectionMgr::DoFallbackConnection fallback connection is "
+ "ready for dispatching ent=%p",
+ ent));
+ aTrans->InvokeCallback();
+ return;
+ }
+
+ DoSpeculativeConnectionInternal(ent, aTrans, aFetchHTTPSRR);
+}
+
+void nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ SpeculativeConnectArgs* args = static_cast<SpeculativeConnectArgs*>(param);
+
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s, "
+ "mFetchHTTPSRR=%d]\n",
+ args->mTrans->ConnectionInfo()->HashKey().get(), args->mFetchHTTPSRR));
+ DoSpeculativeConnection(args->mTrans, args->mFetchHTTPSRR);
+}
+
+bool nsHttpConnectionMgr::BeConservativeIfProxied(nsIProxyInfo* proxy) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mBeConservativeForProxy) {
+ // The pref says to be conservative for proxies.
+ return true;
+ }
+
+ if (!proxy) {
+ // There is no proxy, so be conservative by default.
+ return true;
+ }
+
+ // Be conservative only if there is no proxy host set either.
+ // This logic was copied from nsSSLIOLayerAddToSocket.
+ nsAutoCString proxyHost;
+ proxy->GetHost(proxyHost);
+ return proxyHost.IsEmpty();
+}
+
+// register a connection to receive CanJoinConnection() for particular
+// origin keys
+void nsHttpConnectionMgr::RegisterOriginCoalescingKey(HttpConnectionBase* conn,
+ const nsACString& host,
+ int32_t port) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsHttpConnectionInfo* ci = conn ? conn->ConnectionInfo() : nullptr;
+ if (!ci || !conn->CanDirectlyActivate()) {
+ return;
+ }
+
+ nsCString newKey;
+ BuildOriginFrameHashKey(newKey, ci, host, port);
+ mCoalescingHash.GetOrInsertNew(newKey, 1)->AppendElement(
+ do_GetWeakReference(static_cast<nsISupportsWeakReference*>(conn)));
+
+ LOG(
+ ("nsHttpConnectionMgr::RegisterOriginCoalescingKey "
+ "Established New Coalescing Key %s to %p %s\n",
+ newKey.get(), conn, ci->HashKey().get()));
+}
+
+bool nsHttpConnectionMgr::GetConnectionData(nsTArray<HttpRetParams>* aArg) {
+ for (const RefPtr<ConnectionEntry>& ent : mCT.Values()) {
+ if (ent->mConnInfo->GetPrivate()) {
+ continue;
+ }
+ aArg->AppendElement(ent->GetConnectionData());
+ }
+
+ return true;
+}
+
+void nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (ent) {
+ ent->ResetIPFamilyPreference();
+ }
+}
+
+void nsHttpConnectionMgr::ExcludeHttp2(const nsHttpConnectionInfo* ci) {
+ LOG(("nsHttpConnectionMgr::ExcludeHttp2 excluding ci %s",
+ ci->HashKey().BeginReading()));
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (!ent) {
+ LOG(("nsHttpConnectionMgr::ExcludeHttp2 no entry found?!"));
+ return;
+ }
+
+ ent->DisallowHttp2();
+}
+
+void nsHttpConnectionMgr::ExcludeHttp3(const nsHttpConnectionInfo* ci) {
+ LOG(("nsHttpConnectionMgr::ExcludeHttp3 exclude ci %s",
+ ci->HashKey().BeginReading()));
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (!ent) {
+ LOG(("nsHttpConnectionMgr::ExcludeHttp3 no entry found?!"));
+ return;
+ }
+
+ ent->DontReuseHttp3Conn();
+}
+
+void nsHttpConnectionMgr::MoveToWildCardConnEntry(
+ nsHttpConnectionInfo* specificCI, nsHttpConnectionInfo* wildCardCI,
+ HttpConnectionBase* proxyConn) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(specificCI->UsingHttpsProxy());
+
+ LOG(
+ ("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p has requested to "
+ "change CI from %s to %s\n",
+ proxyConn, specificCI->HashKey().get(), wildCardCI->HashKey().get()));
+
+ ConnectionEntry* ent = mCT.GetWeak(specificCI->HashKey());
+ LOG(
+ ("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p using ent %p (spdy "
+ "%d)\n",
+ proxyConn, ent, ent ? ent->mUsingSpdy : 0));
+
+ if (!ent || !ent->mUsingSpdy) {
+ return;
+ }
+
+ bool isWildcard = false;
+ ConnectionEntry* wcEnt =
+ GetOrCreateConnectionEntry(wildCardCI, true, false, false, &isWildcard);
+ if (wcEnt == ent) {
+ // nothing to do!
+ return;
+ }
+ wcEnt->mUsingSpdy = true;
+
+ LOG(
+ ("nsHttpConnectionMgr::MakeConnEntryWildCard ent %p "
+ "idle=%zu active=%zu half=%zu pending=%zu\n",
+ ent, ent->IdleConnectionsLength(), ent->ActiveConnsLength(),
+ ent->DnsAndConnectSocketsLength(), ent->PendingQueueLength()));
+
+ LOG(
+ ("nsHttpConnectionMgr::MakeConnEntryWildCard wc-ent %p "
+ "idle=%zu active=%zu half=%zu pending=%zu\n",
+ wcEnt, wcEnt->IdleConnectionsLength(), wcEnt->ActiveConnsLength(),
+ wcEnt->DnsAndConnectSocketsLength(), wcEnt->PendingQueueLength()));
+
+ ent->MoveConnection(proxyConn, wcEnt);
+}
+
+bool nsHttpConnectionMgr::RemoveTransFromConnEntry(nsHttpTransaction* aTrans,
+ const nsACString& aHashKey) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("nsHttpConnectionMgr::RemoveTransFromConnEntry: trans=%p ci=%s", aTrans,
+ PromiseFlatCString(aHashKey).get()));
+
+ if (aHashKey.IsEmpty()) {
+ return false;
+ }
+
+ // Step 1: Get the transaction's connection entry.
+ ConnectionEntry* entry = mCT.GetWeak(aHashKey);
+ if (!entry) {
+ return false;
+ }
+
+ // Step 2: Try to find the undispatched transaction.
+ return entry->RemoveTransFromPendingQ(aTrans);
+}
+
+void nsHttpConnectionMgr::IncreaseNumDnsAndConnectSockets() {
+ mNumDnsAndConnectSockets++;
+}
+
+void nsHttpConnectionMgr::DecreaseNumDnsAndConnectSockets() {
+ MOZ_ASSERT(mNumDnsAndConnectSockets);
+ if (mNumDnsAndConnectSockets) { // just in case
+ mNumDnsAndConnectSockets--;
+ }
+}
+
+already_AddRefed<PendingTransactionInfo>
+nsHttpConnectionMgr::FindTransactionHelper(bool removeWhenFound,
+ ConnectionEntry* aEnt,
+ nsAHttpTransaction* aTrans) {
+ nsTArray<RefPtr<PendingTransactionInfo>>* pendingQ =
+ aEnt->GetTransactionPendingQHelper(aTrans);
+
+ int32_t index =
+ pendingQ ? pendingQ->IndexOf(aTrans, 0, PendingComparator()) : -1;
+
+ RefPtr<PendingTransactionInfo> info;
+ if (index != -1) {
+ info = (*pendingQ)[index];
+ if (removeWhenFound) {
+ pendingQ->RemoveElementAt(index);
+ }
+ }
+ return info.forget();
+}
+
+already_AddRefed<ConnectionEntry> nsHttpConnectionMgr::FindConnectionEntry(
+ const nsHttpConnectionInfo* ci) {
+ return mCT.Get(ci->HashKey());
+}
+
+nsHttpConnectionMgr* nsHttpConnectionMgr::AsHttpConnectionMgr() { return this; }
+
+HttpConnectionMgrParent* nsHttpConnectionMgr::AsHttpConnectionMgrParent() {
+ return nullptr;
+}
+
+void nsHttpConnectionMgr::NewIdleConnectionAdded(uint32_t timeToLive) {
+ mNumIdleConns++;
+
+ // If the added connection was first idle connection or has shortest
+ // time to live among the watched connections, pruning dead
+ // connections needs to be done when it can't be reused anymore.
+ if (!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp) {
+ PruneDeadConnectionsAfter(timeToLive);
+ }
+}
+
+void nsHttpConnectionMgr::DecrementNumIdleConns() {
+ MOZ_ASSERT(mNumIdleConns);
+ mNumIdleConns--;
+ ConditionallyStopPruneDeadConnectionsTimer();
+}
+
+void nsHttpConnectionMgr::CheckTransInPendingQueue(nsHttpTransaction* aTrans) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ // We only do this check on socket thread. When this function is called on
+ // main thread, the transaction is newly created, so we can skip this check.
+ if (!OnSocketThread()) {
+ return;
+ }
+
+ nsAutoCString hashKey;
+ aTrans->GetHashKeyOfConnectionEntry(hashKey);
+ if (hashKey.IsEmpty()) {
+ return;
+ }
+
+ bool foundInPendingQ = RemoveTransFromConnEntry(aTrans, hashKey);
+ MOZ_DIAGNOSTIC_ASSERT(!foundInPendingQ);
+#endif
+}
+
+bool nsHttpConnectionMgr::AllowToRetryDifferentIPFamilyForHttp3(
+ nsHttpConnectionInfo* ci, nsresult aError) {
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (!ent) {
+ return false;
+ }
+
+ return ent->AllowToRetryDifferentIPFamilyForHttp3(aError);
+}
+
+void nsHttpConnectionMgr::SetRetryDifferentIPFamilyForHttp3(
+ nsHttpConnectionInfo* ci, uint16_t aIPFamily) {
+ ConnectionEntry* ent = mCT.GetWeak(ci->HashKey());
+ if (!ent) {
+ return;
+ }
+
+ ent->SetRetryDifferentIPFamilyForHttp3(aIPFamily);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h
new file mode 100644
index 0000000000..2cf4ab7568
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -0,0 +1,469 @@
+/* vim:t ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpConnectionMgr_h__
+#define nsHttpConnectionMgr_h__
+
+#include "DnsAndConnectSocket.h"
+#include "HttpConnectionBase.h"
+#include "HttpConnectionMgrShell.h"
+#include "nsHttpConnection.h"
+#include "nsHttpTransaction.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsClassHashtable.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Attributes.h"
+#include "ARefBase.h"
+#include "nsWeakReference.h"
+#include "ConnectionEntry.h"
+
+#include "nsINamed.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+
+class nsIHttpUpgradeListener;
+
+namespace mozilla::net {
+class EventTokenBucket;
+class NullHttpTransaction;
+struct HttpRetParams;
+
+//-----------------------------------------------------------------------------
+
+// message handlers have this signature
+class nsHttpConnectionMgr;
+using nsConnEventHandler = void (nsHttpConnectionMgr::*)(int32_t, ARefBase*);
+
+class nsHttpConnectionMgr final : public HttpConnectionMgrShell,
+ public nsIObserver,
+ nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_HTTPCONNECTIONMGRSHELL
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINAMED
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may only be called on the main thread.
+ //-------------------------------------------------------------------------
+
+ nsHttpConnectionMgr();
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may be called on any thread.
+ //-------------------------------------------------------------------------
+
+ [[nodiscard]] nsresult CancelTransactions(nsHttpConnectionInfo*,
+ nsresult code);
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may be called only on the socket thread.
+ //-------------------------------------------------------------------------
+
+ // called to change the connection entry associated with conn from specific
+ // into a wildcard (i.e. http2 proxy friendy) mapping
+ void MoveToWildCardConnEntry(nsHttpConnectionInfo* specificCI,
+ nsHttpConnectionInfo* wildcardCI,
+ HttpConnectionBase* conn);
+
+ // Remove a transaction from the pendingQ of it's connection entry. Returns
+ // true if the transaction is removed successfully, otherwise returns false.
+ bool RemoveTransFromConnEntry(nsHttpTransaction* aTrans,
+ const nsACString& aHashKey);
+
+ // Directly dispatch the transaction or insert it in to the pendingQ.
+ [[nodiscard]] nsresult ProcessNewTransaction(nsHttpTransaction* aTrans);
+
+ // This is used to force an idle connection to be closed and removed from
+ // the idle connection list. It is called when the idle connection detects
+ // that the network peer has closed the transport.
+ [[nodiscard]] nsresult CloseIdleConnection(nsHttpConnection*);
+ [[nodiscard]] nsresult RemoveIdleConnection(nsHttpConnection*);
+
+ // Close a single connection and prevent it from being reused.
+ [[nodiscard]] nsresult DoSingleConnectionCleanup(nsHttpConnectionInfo*);
+
+ // The connection manager needs to know when a normal HTTP connection has been
+ // upgraded to SPDY because the dispatch and idle semantics are a little
+ // bit different.
+ void ReportSpdyConnection(nsHttpConnection*, bool usingSpdy,
+ bool disallowHttp3);
+
+ void ReportHttp3Connection(HttpConnectionBase*);
+
+ bool GetConnectionData(nsTArray<HttpRetParams>*);
+
+ void ResetIPFamilyPreference(nsHttpConnectionInfo*);
+
+ uint16_t MaxRequestDelay() { return mMaxRequestDelay; }
+
+ // tracks and untracks active transactions according their throttle status
+ void AddActiveTransaction(nsHttpTransaction* aTrans);
+ void RemoveActiveTransaction(nsHttpTransaction* aTrans,
+ Maybe<bool> const& aOverride = Nothing());
+ void UpdateActiveTransaction(nsHttpTransaction* aTrans);
+
+ // called by nsHttpTransaction::WriteSegments. decides whether the
+ // transaction should limit reading its reponse data. There are various
+ // conditions this methods evaluates. If called by an active-tab
+ // non-throttled transaction, the throttling window time will be prolonged.
+ bool ShouldThrottle(nsHttpTransaction* aTrans);
+
+ // prolongs the throttling time window to now + the window preferred delay.
+ // called when:
+ // - any transaction is activated
+ // - or when a currently unthrottled transaction for the active window
+ // receives data
+ void TouchThrottlingTimeWindow(bool aEnsureTicker = true);
+
+ // return true iff the connection has pending transactions for the active tab.
+ // it's mainly used to disallow throttling (limit reading) of a response
+ // belonging to the same conn info to free up a connection ASAP.
+ // NOTE: relatively expensive to call, there are two hashtable lookups.
+ bool IsConnEntryUnderPressure(nsHttpConnectionInfo*);
+
+ uint64_t CurrentBrowserId() { return mCurrentBrowserId; }
+
+ void DoFallbackConnection(SpeculativeTransaction* aTrans, bool aFetchHTTPSRR);
+ void DoSpeculativeConnection(SpeculativeTransaction* aTrans,
+ bool aFetchHTTPSRR);
+
+ HttpConnectionBase* GetH2orH3ActiveConn(ConnectionEntry* ent, bool aNoHttp2,
+ bool aNoHttp3);
+
+ void IncreaseNumDnsAndConnectSockets();
+ void DecreaseNumDnsAndConnectSockets();
+
+ // Wen a new idle connection has been added, this function is called to
+ // increment mNumIdleConns and update PruneDeadConnections timer.
+ void NewIdleConnectionAdded(uint32_t timeToLive);
+ void DecrementNumIdleConns();
+
+ private:
+ virtual ~nsHttpConnectionMgr();
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may be called on any thread.
+ //-------------------------------------------------------------------------
+
+ // Schedules next pruning of dead connection to happen after
+ // given time.
+ void PruneDeadConnectionsAfter(uint32_t time);
+
+ // Stops timer scheduled for next pruning of dead connections if
+ // there are no more idle connections or active spdy ones
+ void ConditionallyStopPruneDeadConnectionsTimer();
+
+ // Stops timer used for the read timeout tick if there are no currently
+ // active connections.
+ void ConditionallyStopTimeoutTick();
+
+ // called to close active connections with no registered "traffic"
+ [[nodiscard]] nsresult PruneNoTraffic();
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may be called only on the socket thread.
+ //-------------------------------------------------------------------------
+
+ [[nodiscard]] bool ProcessPendingQForEntry(nsHttpConnectionInfo*);
+
+ // public, so that the SPDY/http2 seesions can activate
+ void ActivateTimeoutTick();
+
+ already_AddRefed<PendingTransactionInfo> FindTransactionHelper(
+ bool removeWhenFound, ConnectionEntry* aEnt, nsAHttpTransaction* aTrans);
+
+ void DoSpeculativeConnectionInternal(ConnectionEntry* aEnt,
+ SpeculativeTransaction* aTrans,
+ bool aFetchHTTPSRR);
+
+ already_AddRefed<ConnectionEntry> FindConnectionEntry(
+ const nsHttpConnectionInfo* ci);
+
+ public:
+ void RegisterOriginCoalescingKey(HttpConnectionBase*, const nsACString& host,
+ int32_t port);
+ // A test if be-conservative should be used when proxy is setup for the
+ // connection
+ bool BeConservativeIfProxied(nsIProxyInfo* proxy);
+
+ bool AllowToRetryDifferentIPFamilyForHttp3(nsHttpConnectionInfo* ci,
+ nsresult aError);
+ void SetRetryDifferentIPFamilyForHttp3(nsHttpConnectionInfo* ci,
+ uint16_t aIPFamily);
+
+ protected:
+ friend class ConnectionEntry;
+ void IncrementActiveConnCount();
+ void DecrementActiveConnCount(HttpConnectionBase*);
+
+ private:
+ friend class DnsAndConnectSocket;
+ friend class PendingTransactionInfo;
+
+ //-------------------------------------------------------------------------
+ // NOTE: these members may be accessed from any thread (use mReentrantMonitor)
+ //-------------------------------------------------------------------------
+
+ ReentrantMonitor mReentrantMonitor{"nsHttpConnectionMgr.mReentrantMonitor"};
+ // This is used as a flag that we're shut down, and no new events should be
+ // dispatched.
+ nsCOMPtr<nsIEventTarget> mSocketThreadTarget
+ MOZ_GUARDED_BY(mReentrantMonitor);
+
+ Atomic<bool, mozilla::Relaxed> mIsShuttingDown{false};
+
+ //-------------------------------------------------------------------------
+ // NOTE: these members are only accessed on the socket transport thread
+ //-------------------------------------------------------------------------
+ // connection limits
+ uint16_t mMaxUrgentExcessiveConns{0};
+ uint16_t mMaxConns{0};
+ uint16_t mMaxPersistConnsPerHost{0};
+ uint16_t mMaxPersistConnsPerProxy{0};
+ uint16_t mMaxRequestDelay{0}; // in seconds
+ bool mThrottleEnabled{false};
+ uint32_t mThrottleVersion{2};
+ uint32_t mThrottleSuspendFor{0};
+ uint32_t mThrottleResumeFor{0};
+ uint32_t mThrottleReadLimit{0};
+ uint32_t mThrottleReadInterval{0};
+ uint32_t mThrottleHoldTime{0};
+ TimeDuration mThrottleMaxTime;
+ bool mBeConservativeForProxy{true};
+
+ [[nodiscard]] bool ProcessPendingQForEntry(ConnectionEntry*,
+ bool considerAll);
+ bool DispatchPendingQ(nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ ConnectionEntry* ent, bool considerAll);
+
+ // This function selects transactions from mPendingTransactionTable to
+ // dispatch according to the following conditions:
+ // 1. When ActiveTabPriority() is false, only get transactions from the
+ // queue whose window id is 0.
+ // 2. If |considerAll| is false, either get transactions from the focused
+ // window queue or non-focused ones.
+ // 3. If |considerAll| is true, fill the |pendingQ| with the transactions from
+ // both focused window and non-focused window queues.
+ void PreparePendingQForDispatching(
+ ConnectionEntry* ent, nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ bool considerAll);
+
+ // Return |mMaxPersistConnsPerProxy| or |mMaxPersistConnsPerHost|,
+ // depending whether the proxy is used.
+ uint32_t MaxPersistConnections(ConnectionEntry* ent) const;
+
+ bool AtActiveConnectionLimit(ConnectionEntry*, uint32_t caps);
+ [[nodiscard]] nsresult TryDispatchTransaction(
+ ConnectionEntry* ent, bool onlyReusedConnection,
+ PendingTransactionInfo* pendingTransInfo);
+ [[nodiscard]] nsresult TryDispatchTransactionOnIdleConn(
+ ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo,
+ bool respectUrgency, bool* allUrgent = nullptr);
+ [[nodiscard]] nsresult DispatchTransaction(ConnectionEntry*,
+ nsHttpTransaction*,
+ HttpConnectionBase*);
+ [[nodiscard]] nsresult DispatchAbstractTransaction(ConnectionEntry*,
+ nsAHttpTransaction*,
+ uint32_t,
+ HttpConnectionBase*,
+ int32_t);
+ [[nodiscard]] nsresult EnsureSocketThreadTarget();
+ void ReportProxyTelemetry(ConnectionEntry* ent);
+ void StartedConnect();
+ void RecvdConnect();
+
+ ConnectionEntry* GetOrCreateConnectionEntry(
+ nsHttpConnectionInfo*, bool prohibitWildCard, bool aNoHttp2,
+ bool aNoHttp3, bool* aIsWildcard,
+ bool* aAvailableForDispatchNow = nullptr);
+
+ [[nodiscard]] nsresult MakeNewConnection(
+ ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo);
+
+ // Manage h2/3 connection coalescing
+ // The hashtable contains arrays of weak pointers to HttpConnectionBases
+ nsClassHashtable<nsCStringHashKey, nsTArray<nsWeakPtr>> mCoalescingHash;
+
+ HttpConnectionBase* FindCoalescableConnection(ConnectionEntry* ent,
+ bool justKidding, bool aNoHttp2,
+ bool aNoHttp3);
+ HttpConnectionBase* FindCoalescableConnectionByHashKey(ConnectionEntry* ent,
+ const nsCString& key,
+ bool justKidding,
+ bool aNoHttp2,
+ bool aNoHttp3);
+ void UpdateCoalescingForNewConn(HttpConnectionBase* conn,
+ ConnectionEntry* ent, bool aNoHttp3);
+
+ void ProcessSpdyPendingQ(ConnectionEntry* ent);
+ void DispatchSpdyPendingQ(nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
+ ConnectionEntry* ent, HttpConnectionBase* connH2,
+ HttpConnectionBase* connH3);
+ // used to marshall events to the socket transport thread.
+ [[nodiscard]] nsresult PostEvent(nsConnEventHandler handler,
+ int32_t iparam = 0,
+ ARefBase* vparam = nullptr);
+
+ void OnMsgReclaimConnection(HttpConnectionBase*);
+
+ // message handlers
+ void OnMsgShutdown(int32_t, ARefBase*);
+ void OnMsgShutdownConfirm(int32_t, ARefBase*);
+ void OnMsgNewTransaction(int32_t, ARefBase*);
+ void OnMsgNewTransactionWithStickyConn(int32_t, ARefBase*);
+ void OnMsgReschedTransaction(int32_t, ARefBase*);
+ void OnMsgUpdateClassOfServiceOnTransaction(ClassOfService, ARefBase*);
+ void OnMsgCancelTransaction(int32_t, ARefBase*);
+ void OnMsgCancelTransactions(int32_t, ARefBase*);
+ void OnMsgProcessPendingQ(int32_t, ARefBase*);
+ void OnMsgPruneDeadConnections(int32_t, ARefBase*);
+ void OnMsgSpeculativeConnect(int32_t, ARefBase*);
+ void OnMsgCompleteUpgrade(int32_t, ARefBase*);
+ void OnMsgUpdateParam(int32_t, ARefBase*);
+ void OnMsgDoShiftReloadConnectionCleanup(int32_t, ARefBase*);
+ void OnMsgDoSingleConnectionCleanup(int32_t, ARefBase*);
+ void OnMsgProcessFeedback(int32_t, ARefBase*);
+ void OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase*);
+ void OnMsgUpdateRequestTokenBucket(int32_t, ARefBase*);
+ void OnMsgVerifyTraffic(int32_t, ARefBase*);
+ void OnMsgPruneNoTraffic(int32_t, ARefBase*);
+ void OnMsgUpdateCurrentBrowserId(int32_t, ARefBase*);
+ void OnMsgClearConnectionHistory(int32_t, ARefBase*);
+
+ // Total number of active connections in all of the ConnectionEntry objects
+ // that are accessed from mCT connection table.
+ uint16_t mNumActiveConns{0};
+ // Total number of idle connections in all of the ConnectionEntry objects
+ // that are accessed from mCT connection table.
+ uint16_t mNumIdleConns{0};
+ // Total number of spdy or http3 connections which are a subset of the active
+ // conns
+ uint16_t mNumSpdyHttp3ActiveConns{0};
+ // Total number of connections in DnsAndConnectSockets ConnectionEntry objects
+ // that are accessed from mCT connection table
+ uint32_t mNumDnsAndConnectSockets{0};
+
+ // Holds time in seconds for next wake-up to prune dead connections.
+ uint64_t mTimeOfNextWakeUp{UINT64_MAX};
+ // Timer for next pruning of dead connections.
+ nsCOMPtr<nsITimer> mTimer;
+ // Timer for pruning stalled connections after changed network.
+ nsCOMPtr<nsITimer> mTrafficTimer;
+ bool mPruningNoTraffic{false};
+
+ // A 1s tick to call nsHttpConnection::ReadTimeoutTick on
+ // active http/1 connections and check for orphaned half opens.
+ // Disabled when there are no active or half open connections.
+ nsCOMPtr<nsITimer> mTimeoutTick;
+ bool mTimeoutTickArmed{false};
+ uint32_t mTimeoutTickNext{1};
+
+ //
+ // the connection table
+ //
+ // this table is indexed by connection key. each entry is a
+ // ConnectionEntry object. It is unlocked and therefore must only
+ // be accessed from the socket thread.
+ //
+ nsRefPtrHashtable<nsCStringHashKey, ConnectionEntry> mCT;
+
+ // Read Timeout Tick handlers
+ void TimeoutTick();
+
+ // For diagnostics
+ void OnMsgPrintDiagnostics(int32_t, ARefBase*);
+
+ nsCString mLogData;
+ uint64_t mCurrentBrowserId{0};
+
+ // Called on a pref change
+ void SetThrottlingEnabled(bool aEnable);
+
+ // we only want to throttle for a limited amount of time after a new
+ // active transaction is added so that we don't block downloads on comet,
+ // socket and any kind of longstanding requests that don't need bandwidth.
+ // these methods track this time.
+ bool InThrottlingTimeWindow();
+
+ // Two hashtalbes keeping track of active transactions regarding window id and
+ // throttling. Used by the throttling algorithm to obtain number of
+ // transactions for the active tab and for inactive tabs according their
+ // throttle status. mActiveTransactions[0] are all unthrottled transactions,
+ // mActiveTransactions[1] throttled.
+ nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>
+ mActiveTransactions[2];
+
+ // V1 specific
+ // Whether we are inside the "stop reading" interval, altered by the throttle
+ // ticker
+ bool mThrottlingInhibitsReading{false};
+
+ TimeStamp mThrottlingWindowEndsAt;
+
+ // ticker for the 'stop reading'/'resume reading' signal
+ nsCOMPtr<nsITimer> mThrottleTicker;
+ // Checks if the combination of active transactions requires the ticker.
+ bool IsThrottleTickerNeeded();
+ // The method also unschedules the delayed resume of background tabs timer
+ // if the ticker was about to be scheduled.
+ void EnsureThrottleTickerIfNeeded();
+ // V1:
+ // Drops also the mThrottlingInhibitsReading flag. Immediate or delayed
+ // resume of currently throttled transactions is not affected by this method.
+ // V2:
+ // Immediate or delayed resume of currently throttled transactions is not
+ // affected by this method.
+ void DestroyThrottleTicker();
+ // V1:
+ // Handler for the ticker: alters the mThrottlingInhibitsReading flag.
+ // V2:
+ // Handler for the ticker: calls ResumeReading() for all throttled
+ // transactions.
+ void ThrottlerTick();
+
+ // mechanism to delay immediate resume of background tabs and chrome initiated
+ // throttled transactions after the last transaction blocking their unthrottle
+ // has been removed. Needs to be delayed because during a page load there is
+ // a number of intervals when there is no transaction that would cause
+ // throttling. Hence, throttling of long standing responses, like downloads,
+ // would be mostly ineffective if resumed during every such interval.
+ nsCOMPtr<nsITimer> mDelayedResumeReadTimer;
+ // Schedule the resume
+ void DelayedResumeBackgroundThrottledTransactions();
+ // Simply destroys the timer
+ void CancelDelayedResumeBackgroundThrottledTransactions();
+ // Handler for the timer: resumes all background throttled transactions
+ void ResumeBackgroundThrottledTransactions();
+
+ // Simple helpers, iterates the given hash/array and resume.
+ // @param excludeActive: skip active tabid transactions.
+ void ResumeReadOf(
+ nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>&,
+ bool excludeForActiveTab = false);
+ void ResumeReadOf(nsTArray<RefPtr<nsHttpTransaction>>*);
+
+ // Cached status of the active tab active transactions existence,
+ // saves a lot of hashtable lookups
+ bool mActiveTabTransactionsExist{false};
+ bool mActiveTabUnthrottledTransactionsExist{false};
+
+ void LogActiveTransactions(char);
+
+ // When current active tab is changed, this function uses
+ // |previousId| to select background transactions and
+ // |mCurrentBrowserId| to select foreground transactions.
+ // Then, it notifies selected transactions' connection of the new active tab
+ // id.
+ void NotifyConnectionOfBrowserIdChange(uint64_t previousId);
+
+ void CheckTransInPendingQueue(nsHttpTransaction* aTrans);
+};
+
+} // namespace mozilla::net
+
+#endif // !nsHttpConnectionMgr_h__
diff --git a/netwerk/protocol/http/nsHttpDigestAuth.cpp b/netwerk/protocol/http/nsHttpDigestAuth.cpp
new file mode 100644
index 0000000000..2a98301942
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpDigestAuth.cpp
@@ -0,0 +1,743 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Unused.h"
+
+#include "nsHttp.h"
+#include "nsHttpDigestAuth.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsEscape.h"
+#include "nsNetCID.h"
+#include "nsCRT.h"
+#include "nsICryptoHash.h"
+#include "nsComponentManagerUtils.h"
+
+constexpr uint16_t DigestLength(uint16_t aAlgorithm) {
+ if (aAlgorithm & (ALGO_SHA256 | ALGO_SHA256_SESS)) {
+ return SHA256_DIGEST_LENGTH;
+ }
+ return MD5_DIGEST_LENGTH;
+}
+
+namespace mozilla {
+namespace net {
+
+StaticRefPtr<nsHttpDigestAuth> nsHttpDigestAuth::gSingleton;
+
+already_AddRefed<nsIHttpAuthenticator> nsHttpDigestAuth::GetOrCreate() {
+ nsCOMPtr<nsIHttpAuthenticator> authenticator;
+ if (gSingleton) {
+ authenticator = gSingleton;
+ } else {
+ gSingleton = new nsHttpDigestAuth();
+ ClearOnShutdown(&gSingleton);
+ authenticator = gSingleton;
+ }
+
+ return authenticator.forget();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpDigestAuth, nsIHttpAuthenticator)
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth <protected>
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpDigestAuth::DigestHash(const char* buf, uint32_t len,
+ uint16_t algorithm) {
+ nsresult rv;
+
+ // Cache a reference to the nsICryptoHash instance since we'll be calling
+ // this function frequently.
+ if (!mVerifier) {
+ mVerifier = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpDigestAuth: no crypto hash!\n"));
+ return rv;
+ }
+ }
+
+ uint32_t dlen;
+ if (algorithm & (ALGO_SHA256 | ALGO_SHA256_SESS)) {
+ rv = mVerifier->Init(nsICryptoHash::SHA256);
+ dlen = SHA256_DIGEST_LENGTH;
+ } else {
+ rv = mVerifier->Init(nsICryptoHash::MD5);
+ dlen = MD5_DIGEST_LENGTH;
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mVerifier->Update((unsigned char*)buf, len);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString hashString;
+ rv = mVerifier->Finish(false, hashString);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ENSURE_STATE(hashString.Length() == dlen);
+ memcpy(mHashBuf, hashString.get(), hashString.Length());
+
+ return rv;
+}
+
+nsresult nsHttpDigestAuth::GetMethodAndPath(
+ nsIHttpAuthenticableChannel* authChannel, bool isProxyAuth,
+ nsCString& httpMethod, nsCString& path) {
+ nsresult rv, rv2;
+ nsCOMPtr<nsIURI> uri;
+ rv = authChannel->GetURI(getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv)) {
+ bool proxyMethodIsConnect;
+ rv = authChannel->GetProxyMethodIsConnect(&proxyMethodIsConnect);
+ if (NS_SUCCEEDED(rv)) {
+ if (proxyMethodIsConnect && isProxyAuth) {
+ httpMethod.AssignLiteral("CONNECT");
+ //
+ // generate hostname:port string. (unfortunately uri->GetHostPort
+ // leaves out the port if it matches the default value, so we can't
+ // just call it.)
+ //
+ int32_t port;
+ rv = uri->GetAsciiHost(path);
+ rv2 = uri->GetPort(&port);
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2)) {
+ path.Append(':');
+ path.AppendInt(port < 0 ? NS_HTTPS_DEFAULT_PORT : port);
+ }
+ } else {
+ rv = authChannel->GetRequestMethod(httpMethod);
+ rv2 = uri->GetPathQueryRef(path);
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2)) {
+ //
+ // strip any fragment identifier from the URL path.
+ //
+ int32_t ref = path.RFindChar('#');
+ if (ref != kNotFound) path.Truncate(ref);
+ //
+ // make sure we escape any UTF-8 characters in the URI path. the
+ // digest auth uri attribute needs to match the request-URI.
+ //
+ // XXX we should really ask the HTTP channel for this string
+ // instead of regenerating it here.
+ //
+ nsAutoCString buf;
+ rv = NS_EscapeURL(path, esc_OnlyNonASCII | esc_Spaces, buf,
+ mozilla::fallible);
+ if (NS_SUCCEEDED(rv)) {
+ path = buf;
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth::nsIHttpAuthenticator
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpDigestAuth::ChallengeReceived(nsIHttpAuthenticableChannel* authChannel,
+ const nsACString& challenge,
+ bool isProxyAuth,
+ nsISupports** sessionState,
+ nsISupports** continuationState,
+ bool* result) {
+ nsAutoCString realm, domain, nonce, opaque;
+ bool stale;
+ uint16_t algorithm, qop;
+
+ nsresult rv = ParseChallenge(challenge, realm, domain, nonce, opaque, &stale,
+ &algorithm, &qop);
+
+ if (!(algorithm &
+ (ALGO_MD5 | ALGO_MD5_SESS | ALGO_SHA256 | ALGO_SHA256_SESS))) {
+ // they asked for an algorithm that we do not support yet (like SHA-512/256)
+ NS_WARNING("unsupported algorithm requested by Digest authentication");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (NS_FAILED(rv)) return rv;
+
+ // if the challenge has the "stale" flag set, then the user identity is not
+ // necessarily invalid. by returning FALSE here we can suppress username
+ // and password prompting that usually accompanies a 401/407 challenge.
+ *result = !stale;
+
+ // clear any existing nonce_count since we have a new challenge.
+ NS_IF_RELEASE(*sessionState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpDigestAuth::GenerateCredentialsAsync(
+ nsIHttpAuthenticableChannel* authChannel,
+ nsIHttpAuthenticatorCallback* aCallback, const nsACString& challenge,
+ bool isProxyAuth, const nsAString& domain, const nsAString& username,
+ const nsAString& password, nsISupports* sessionState,
+ nsISupports* continuationState, nsICancelable** aCancellable) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpDigestAuth::GenerateCredentials(
+ nsIHttpAuthenticableChannel* authChannel, const nsACString& aChallenge,
+ bool isProxyAuth, const nsAString& userdomain, const nsAString& username,
+ const nsAString& password, nsISupports** sessionState,
+ nsISupports** continuationState, uint32_t* aFlags, nsACString& creds)
+
+{
+ LOG(("nsHttpDigestAuth::GenerateCredentials [challenge=%s]\n",
+ aChallenge.BeginReading()));
+
+ *aFlags = 0;
+
+ bool isDigestAuth = StringBeginsWith(aChallenge, "digest "_ns,
+ nsCaseInsensitiveCStringComparator);
+ NS_ENSURE_TRUE(isDigestAuth, NS_ERROR_UNEXPECTED);
+
+ // IIS implementation requires extra quotes
+ bool requireExtraQuotes = false;
+ {
+ nsAutoCString serverVal;
+ Unused << authChannel->GetServerResponseHeader(serverVal);
+ if (!serverVal.IsEmpty()) {
+ requireExtraQuotes =
+ !nsCRT::strncasecmp(serverVal.get(), "Microsoft-IIS", 13);
+ }
+ }
+
+ nsresult rv;
+ nsAutoCString httpMethod;
+ nsAutoCString path;
+ rv = GetMethodAndPath(authChannel, isProxyAuth, httpMethod, path);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString realm, domain, nonce, opaque;
+ bool stale;
+ uint16_t algorithm, qop;
+
+ rv = ParseChallenge(aChallenge, realm, domain, nonce, opaque, &stale,
+ &algorithm, &qop);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpDigestAuth::GenerateCredentials [ParseChallenge failed "
+ "rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ const uint32_t dhexlen = 2 * DigestLength(algorithm) + 1;
+ char ha1_digest[dhexlen];
+ char ha2_digest[dhexlen];
+ char response_digest[dhexlen];
+ char upload_data_digest[dhexlen];
+
+ if (qop & QOP_AUTH_INT) {
+ // we do not support auth-int "quality of protection" currently
+ qop &= ~QOP_AUTH_INT;
+
+ NS_WARNING(
+ "no support for Digest authentication with data integrity quality of "
+ "protection");
+
+ /* TODO: to support auth-int, we need to get an MD5 digest of
+ * TODO: the data uploaded with this request.
+ * TODO: however, i am not sure how to read in the file in without
+ * TODO: disturbing the channel''s use of it. do i need to copy it
+ * TODO: somehow?
+ */
+#if 0
+ if (http_channel != nullptr)
+ {
+ nsIInputStream * upload;
+ nsCOMPtr<nsIUploadChannel> uc = do_QueryInterface(http_channel);
+ NS_ENSURE_TRUE(uc, NS_ERROR_UNEXPECTED);
+ uc->GetUploadStream(&upload);
+ if (upload) {
+ char * upload_buffer;
+ int upload_buffer_length = 0;
+ //TODO: read input stream into buffer
+ const char * digest = (const char*)
+ nsNetwerkMD5Digest(upload_buffer, upload_buffer_length);
+ ExpandToHex(digest, upload_data_digest);
+ NS_RELEASE(upload);
+ }
+ }
+#endif
+ }
+
+ if (!(algorithm &
+ (ALGO_MD5 | ALGO_MD5_SESS | ALGO_SHA256 | ALGO_SHA256_SESS))) {
+ // they asked only for algorithms that we do not support
+ NS_WARNING("unsupported algorithm requested by Digest authentication");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ //
+ // the following are for increasing security. see RFC 2617 for more
+ // information.
+ //
+ // nonce_count allows the server to keep track of auth challenges (to help
+ // prevent spoofing). we increase this count every time.
+ //
+ char nonce_count[NONCE_COUNT_LENGTH + 1] = "00000001"; // in hex
+ if (*sessionState) {
+ nsCOMPtr<nsISupportsPRUint32> v(do_QueryInterface(*sessionState));
+ if (v) {
+ uint32_t nc;
+ v->GetData(&nc);
+ SprintfLiteral(nonce_count, "%08x", ++nc);
+ v->SetData(nc);
+ }
+ } else {
+ nsCOMPtr<nsISupportsPRUint32> v(
+ do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID));
+ if (v) {
+ v->SetData(1);
+ v.forget(sessionState);
+ }
+ }
+ LOG((" nonce_count=%s\n", nonce_count));
+
+ //
+ // this lets the client verify the server response (via a server
+ // returned Authentication-Info header). also used for session info.
+ //
+ nsAutoCString cnonce;
+ static const char hexChar[] = "0123456789abcdef";
+ for (int i = 0; i < 16; ++i) {
+ cnonce.Append(hexChar[(int)(15.0 * rand() / (RAND_MAX + 1.0))]);
+ }
+ LOG((" cnonce=%s\n", cnonce.get()));
+
+ //
+ // calculate credentials
+ //
+
+ NS_ConvertUTF16toUTF8 cUser(username), cPass(password);
+ rv = CalculateHA1(cUser, cPass, realm, algorithm, nonce, cnonce, ha1_digest);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = CalculateHA2(httpMethod, path, algorithm, qop, upload_data_digest,
+ ha2_digest);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = CalculateResponse(ha1_digest, ha2_digest, algorithm, nonce, qop,
+ nonce_count, cnonce, response_digest);
+ if (NS_FAILED(rv)) return rv;
+
+ //
+ // Values that need to match the quoted-string production from RFC 2616:
+ //
+ // username
+ // realm
+ // nonce
+ // opaque
+ // cnonce
+ //
+
+ nsAutoCString authString;
+
+ authString.AssignLiteral("Digest username=");
+ rv = AppendQuotedString(cUser, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ authString.AppendLiteral(", realm=");
+ rv = AppendQuotedString(realm, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ authString.AppendLiteral(", nonce=");
+ rv = AppendQuotedString(nonce, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ authString.AppendLiteral(", uri=\"");
+ authString += path;
+ if (algorithm & ALGO_SPECIFIED) {
+ authString.AppendLiteral("\", algorithm=");
+ if (algorithm & ALGO_MD5_SESS) {
+ authString.AppendLiteral("MD5-sess");
+ } else if (algorithm & ALGO_SHA256) {
+ authString.AppendLiteral("SHA-256");
+ } else if (algorithm & ALGO_SHA256_SESS) {
+ authString.AppendLiteral("SHA-256-sess");
+ } else {
+ authString.AppendLiteral("MD5");
+ }
+ } else {
+ authString += '\"';
+ }
+ authString.AppendLiteral(", response=\"");
+ authString += response_digest;
+ authString += '\"';
+
+ if (!opaque.IsEmpty()) {
+ authString.AppendLiteral(", opaque=");
+ rv = AppendQuotedString(opaque, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (qop) {
+ authString.AppendLiteral(", qop=");
+ if (requireExtraQuotes) authString += '\"';
+ authString.AppendLiteral("auth");
+ if (qop & QOP_AUTH_INT) authString.AppendLiteral("-int");
+ if (requireExtraQuotes) authString += '\"';
+ authString.AppendLiteral(", nc=");
+ authString += nonce_count;
+
+ authString.AppendLiteral(", cnonce=");
+ rv = AppendQuotedString(cnonce, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ creds = authString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpDigestAuth::GetAuthFlags(uint32_t* flags) {
+ *flags = REQUEST_BASED | REUSABLE_CHALLENGE | IDENTITY_ENCRYPTED;
+ //
+ // NOTE: digest auth credentials must be uniquely computed for each request,
+ // so we do not set the REUSABLE_CREDENTIALS flag.
+ //
+ return NS_OK;
+}
+
+nsresult nsHttpDigestAuth::CalculateResponse(
+ const char* ha1_digest, const char* ha2_digest, uint16_t algorithm,
+ const nsCString& nonce, uint16_t qop, const char* nonce_count,
+ const nsCString& cnonce, char* result) {
+ const uint32_t dhexlen = 2 * DigestLength(algorithm);
+ uint32_t len = 2 * dhexlen + nonce.Length() + 2;
+
+ if (qop & QOP_AUTH || qop & QOP_AUTH_INT) {
+ len += cnonce.Length() + NONCE_COUNT_LENGTH + 3;
+ if (qop & QOP_AUTH_INT) {
+ len += 8; // length of "auth-int"
+ } else {
+ len += 4; // length of "auth"
+ }
+ }
+
+ nsAutoCString contents;
+ contents.SetCapacity(len);
+
+ contents.Append(ha1_digest, dhexlen);
+ contents.Append(':');
+ contents.Append(nonce);
+ contents.Append(':');
+
+ if (qop & QOP_AUTH || qop & QOP_AUTH_INT) {
+ contents.Append(nonce_count, NONCE_COUNT_LENGTH);
+ contents.Append(':');
+ contents.Append(cnonce);
+ contents.Append(':');
+ if (qop & QOP_AUTH_INT) {
+ contents.AppendLiteral("auth-int:");
+ } else {
+ contents.AppendLiteral("auth:");
+ }
+ }
+
+ contents.Append(ha2_digest, dhexlen);
+
+ nsresult rv = DigestHash(contents.get(), contents.Length(), algorithm);
+ if (NS_SUCCEEDED(rv)) rv = ExpandToHex(mHashBuf, result, algorithm);
+ return rv;
+}
+
+nsresult nsHttpDigestAuth::ExpandToHex(const char* digest, char* result,
+ uint16_t algorithm) {
+ int16_t index, value;
+ const int16_t dlen = DigestLength(algorithm);
+
+ for (index = 0; index < dlen; index++) {
+ value = (digest[index] >> 4) & 0xf;
+ if (value < 10) {
+ result[index * 2] = value + '0';
+ } else {
+ result[index * 2] = value - 10 + 'a';
+ }
+
+ value = digest[index] & 0xf;
+ if (value < 10) {
+ result[(index * 2) + 1] = value + '0';
+ } else {
+ result[(index * 2) + 1] = value - 10 + 'a';
+ }
+ }
+
+ result[2 * dlen] = 0;
+ return NS_OK;
+}
+
+nsresult nsHttpDigestAuth::CalculateHA1(const nsCString& username,
+ const nsCString& password,
+ const nsCString& realm,
+ uint16_t algorithm,
+ const nsCString& nonce,
+ const nsCString& cnonce, char* result) {
+ const int16_t dhexlen = 2 * DigestLength(algorithm);
+ int16_t len = username.Length() + password.Length() + realm.Length() + 2;
+ if (algorithm & (ALGO_MD5_SESS | ALGO_SHA256_SESS)) {
+ int16_t exlen = dhexlen + nonce.Length() + cnonce.Length() + 2;
+ if (exlen > len) len = exlen;
+ }
+
+ nsAutoCString contents;
+ contents.SetCapacity(len);
+
+ contents.Append(username);
+ contents.Append(':');
+ contents.Append(realm);
+ contents.Append(':');
+ contents.Append(password);
+
+ nsresult rv;
+ rv = DigestHash(contents.get(), contents.Length(), algorithm);
+ if (NS_FAILED(rv)) return rv;
+
+ if (algorithm & (ALGO_MD5_SESS | ALGO_SHA256_SESS)) {
+ char part1[dhexlen + 1];
+ rv = ExpandToHex(mHashBuf, part1, algorithm);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ contents.Assign(part1, dhexlen);
+ contents.Append(':');
+ contents.Append(nonce);
+ contents.Append(':');
+ contents.Append(cnonce);
+
+ rv = DigestHash(contents.get(), contents.Length(), algorithm);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return ExpandToHex(mHashBuf, result, algorithm);
+}
+
+nsresult nsHttpDigestAuth::CalculateHA2(const nsCString& method,
+ const nsCString& path,
+ uint16_t algorithm, uint16_t qop,
+ const char* bodyDigest, char* result) {
+ uint16_t methodLen = method.Length();
+ uint32_t pathLen = path.Length();
+ uint32_t len = methodLen + pathLen + 1;
+ const uint32_t dhexlen = 2 * DigestLength(algorithm);
+
+ if (qop & QOP_AUTH_INT) {
+ len += dhexlen + 1;
+ }
+
+ nsAutoCString contents;
+ contents.SetCapacity(len);
+
+ contents.Assign(method);
+ contents.Append(':');
+ contents.Append(path);
+
+ if (qop & QOP_AUTH_INT) {
+ contents.Append(':');
+ contents.Append(bodyDigest, dhexlen);
+ }
+
+ nsresult rv = DigestHash(contents.get(), contents.Length(), algorithm);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return ExpandToHex(mHashBuf, result, algorithm);
+}
+
+nsresult nsHttpDigestAuth::ParseChallenge(const nsACString& aChallenge,
+ nsACString& realm, nsACString& domain,
+ nsACString& nonce, nsACString& opaque,
+ bool* stale, uint16_t* algorithm,
+ uint16_t* qop) {
+ // put an absurd, but maximum, length cap on the challenge so
+ // that calculations are 32 bit safe
+ if (aChallenge.Length() > 16000000) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const char* challenge = aChallenge.BeginReading();
+ const char* end = aChallenge.EndReading();
+ const char* p = challenge + 6; // first 6 characters are "Digest"
+ if (p >= end) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *stale = false;
+ *algorithm = ALGO_MD5; // default is MD5
+ *qop = 0;
+
+ for (;;) {
+ while (p < end && (*p == ',' || nsCRT::IsAsciiSpace(*p))) {
+ ++p;
+ }
+ if (p >= end) {
+ break;
+ }
+
+ // name
+ int32_t nameStart = (p - challenge);
+ while (p < end && !nsCRT::IsAsciiSpace(*p) && *p != '=') {
+ ++p;
+ }
+ if (p >= end) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ int32_t nameLength = (p - challenge) - nameStart;
+
+ while (p < end && (nsCRT::IsAsciiSpace(*p) || *p == '=')) {
+ ++p;
+ }
+ if (p >= end) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool quoted = false;
+ if (*p == '"') {
+ ++p;
+ quoted = true;
+ }
+
+ // value
+ int32_t valueStart = (p - challenge);
+ int32_t valueLength = 0;
+ if (quoted) {
+ while (p < end && *p != '"') {
+ ++p;
+ }
+ if (p >= end || *p != '"') {
+ return NS_ERROR_INVALID_ARG;
+ }
+ valueLength = (p - challenge) - valueStart;
+ ++p;
+ } else {
+ while (p < end && !nsCRT::IsAsciiSpace(*p) && *p != ',') {
+ ++p;
+ }
+ valueLength = (p - challenge) - valueStart;
+ }
+
+ // extract information
+ if (nameLength == 5 &&
+ nsCRT::strncasecmp(challenge + nameStart, "realm", 5) == 0) {
+ realm.Assign(challenge + valueStart, valueLength);
+ } else if (nameLength == 6 &&
+ nsCRT::strncasecmp(challenge + nameStart, "domain", 6) == 0) {
+ domain.Assign(challenge + valueStart, valueLength);
+ } else if (nameLength == 5 &&
+ nsCRT::strncasecmp(challenge + nameStart, "nonce", 5) == 0) {
+ nonce.Assign(challenge + valueStart, valueLength);
+ } else if (nameLength == 6 &&
+ nsCRT::strncasecmp(challenge + nameStart, "opaque", 6) == 0) {
+ opaque.Assign(challenge + valueStart, valueLength);
+ } else if (nameLength == 5 &&
+ nsCRT::strncasecmp(challenge + nameStart, "stale", 5) == 0) {
+ if (nsCRT::strncasecmp(challenge + valueStart, "true", 4) == 0) {
+ *stale = true;
+ } else {
+ *stale = false;
+ }
+ } else if (nameLength == 9 &&
+ nsCRT::strncasecmp(challenge + nameStart, "algorithm", 9) == 0) {
+ // we want to clear the default, so we use = not |= here
+ *algorithm = ALGO_SPECIFIED;
+ if (valueLength == 3 &&
+ nsCRT::strncasecmp(challenge + valueStart, "MD5", 3) == 0) {
+ *algorithm |= ALGO_MD5;
+ } else if (valueLength == 8 && nsCRT::strncasecmp(challenge + valueStart,
+ "MD5-sess", 8) == 0) {
+ *algorithm |= ALGO_MD5_SESS;
+ } else if (valueLength == 7 && nsCRT::strncasecmp(challenge + valueStart,
+ "SHA-256", 7) == 0) {
+ *algorithm |= ALGO_SHA256;
+ } else if (valueLength == 12 &&
+ nsCRT::strncasecmp(challenge + valueStart, "SHA-256-sess",
+ 12) == 0) {
+ *algorithm |= ALGO_SHA256_SESS;
+ }
+ } else if (nameLength == 3 &&
+ nsCRT::strncasecmp(challenge + nameStart, "qop", 3) == 0) {
+ int32_t ipos = valueStart;
+ while (ipos < valueStart + valueLength) {
+ while (
+ ipos < valueStart + valueLength &&
+ (nsCRT::IsAsciiSpace(challenge[ipos]) || challenge[ipos] == ',')) {
+ ipos++;
+ }
+ int32_t algostart = ipos;
+ while (ipos < valueStart + valueLength &&
+ !nsCRT::IsAsciiSpace(challenge[ipos]) &&
+ challenge[ipos] != ',') {
+ ipos++;
+ }
+ if ((ipos - algostart) == 4 &&
+ nsCRT::strncasecmp(challenge + algostart, "auth", 4) == 0) {
+ *qop |= QOP_AUTH;
+ } else if ((ipos - algostart) == 8 &&
+ nsCRT::strncasecmp(challenge + algostart, "auth-int", 8) ==
+ 0) {
+ *qop |= QOP_AUTH_INT;
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpDigestAuth::AppendQuotedString(const nsACString& value,
+ nsACString& aHeaderLine) {
+ nsAutoCString quoted;
+ nsACString::const_iterator s, e;
+ value.BeginReading(s);
+ value.EndReading(e);
+
+ //
+ // Encode string according to RFC 2616 quoted-string production
+ //
+ quoted.Append('"');
+ for (; s != e; ++s) {
+ //
+ // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
+ //
+ if (*s <= 31 || *s == 127) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Escape two syntactically significant characters
+ if (*s == '"' || *s == '\\') {
+ quoted.Append('\\');
+ }
+
+ quoted.Append(*s);
+ }
+ // FIXME: bug 41489
+ // We should RFC2047-encode non-Latin-1 values according to spec
+ quoted.Append('"');
+ aHeaderLine.Append(quoted);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+// vim: ts=2 sw=2
diff --git a/netwerk/protocol/http/nsHttpDigestAuth.h b/netwerk/protocol/http/nsHttpDigestAuth.h
new file mode 100644
index 0000000000..d9830a17dc
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpDigestAuth.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDigestAuth_h__
+#define nsDigestAuth_h__
+
+#include "nsICryptoHash.h"
+#include "nsIHttpAuthenticator.h"
+#include "nsStringFwd.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace net {
+
+#define ALGO_SPECIFIED 0x01
+#define ALGO_MD5 0x02
+#define ALGO_MD5_SESS 0x04
+#define ALGO_SHA256 0x08
+#define ALGO_SHA256_SESS 0x10
+#define QOP_AUTH 0x01
+#define QOP_AUTH_INT 0x02
+
+#define NONCE_COUNT_LENGTH 8
+#ifndef MD5_DIGEST_LENGTH
+# define MD5_DIGEST_LENGTH 16
+#endif
+#ifndef SHA256_DIGEST_LENGTH
+# define SHA256_DIGEST_LENGTH 32
+#endif
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth
+//-----------------------------------------------------------------------------
+
+class nsHttpDigestAuth final : public nsIHttpAuthenticator {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHENTICATOR
+
+ nsHttpDigestAuth() = default;
+
+ static already_AddRefed<nsIHttpAuthenticator> GetOrCreate();
+
+ [[nodiscard]] static nsresult ParseChallenge(
+ const nsACString& aChallenge, nsACString& realm, nsACString& domain,
+ nsACString& nonce, nsACString& opaque, bool* stale, uint16_t* algorithm,
+ uint16_t* qop);
+
+ protected:
+ ~nsHttpDigestAuth() = default;
+
+ [[nodiscard]] nsresult ExpandToHex(const char* digest, char* result,
+ uint16_t algorithm);
+
+ [[nodiscard]] nsresult CalculateResponse(
+ const char* ha1_digest, const char* ha2_digest, uint16_t algorithm,
+ const nsCString& nonce, uint16_t qop, const char* nonce_count,
+ const nsCString& cnonce, char* result);
+
+ [[nodiscard]] nsresult CalculateHA1(const nsCString& username,
+ const nsCString& password,
+ const nsCString& realm,
+ uint16_t algorithm,
+ const nsCString& nonce,
+ const nsCString& cnonce, char* result);
+
+ [[nodiscard]] nsresult CalculateHA2(const nsCString& http_method,
+ const nsCString& http_uri_path,
+ uint16_t algorithm, uint16_t qop,
+ const char* bodyDigest, char* result);
+
+ // result is in mHashBuf
+ [[nodiscard]] nsresult DigestHash(const char* buf, uint32_t len,
+ uint16_t algorithm);
+
+ [[nodiscard]] nsresult GetMethodAndPath(nsIHttpAuthenticableChannel*, bool,
+ nsCString&, nsCString&);
+
+ // append the quoted version of value to aHeaderLine
+ [[nodiscard]] nsresult AppendQuotedString(const nsACString& value,
+ nsACString& aHeaderLine);
+
+ protected:
+ nsCOMPtr<nsICryptoHash> mVerifier;
+ char mHashBuf[SHA256_DIGEST_LENGTH]{0};
+
+ static StaticRefPtr<nsHttpDigestAuth> gSingleton;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpDigestAuth_h__
diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp
new file mode 100644
index 0000000000..dcc5307984
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -0,0 +1,2812 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "prsystem.h"
+
+#include "AltServiceChild.h"
+#include "nsCORSListenerProxy.h"
+#include "nsError.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpChannel.h"
+#include "nsHTTPCompressConv.h"
+#include "nsHttpAuthCache.h"
+#include "nsStandardURL.h"
+#include "LoadContextInfo.h"
+#include "nsCategoryManagerUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsSocketProviderService.h"
+#include "nsISocketProvider.h"
+#include "nsPrintfCString.h"
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Components.h"
+#include "mozilla/Printf.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsSocketTransportService2.h"
+#include "nsAlgorithm.h"
+#include "ASpdySession.h"
+#include "EventTokenBucket.h"
+#include "Tickler.h"
+#include "nsIXULAppInfo.h"
+#include "nsICookieService.h"
+#include "nsIObserverService.h"
+#include "nsISiteSecurityService.h"
+#include "nsIStreamConverterService.h"
+#include "nsCRT.h"
+#include "nsIParentalControlsService.h"
+#include "nsPIDOMWindow.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsHttpChannelAuthProvider.h"
+#include "nsINetworkLinkService.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsSocketTransportService2.h"
+#include "nsIOService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIXULRuntime.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsRFPService.h"
+#include "mozilla/net/rust_helper.h"
+
+#include "mozilla/net/HttpConnectionMgrParent.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/net/RequestContextService.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/AntiTrackingRedirectHeuristic.h"
+#include "mozilla/DynamicFpiRedirectHeuristic.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/StaticPrefs_image.h"
+#include "mozilla/SyncRunnable.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/network/Connection.h"
+
+#include "nsNSSComponent.h"
+#include "TRRServiceChannel.h"
+
+#include <bitset>
+
+#if defined(XP_UNIX)
+# include <sys/utsname.h>
+#endif
+
+#if defined(MOZ_WIDGET_GTK)
+# include "mozilla/WidgetUtilsGtk.h"
+#endif
+
+#if defined(XP_WIN)
+# include <windows.h>
+# include "mozilla/WindowsVersion.h"
+#endif
+
+#if defined(XP_MACOSX)
+# include <CoreServices/CoreServices.h>
+#endif
+
+//-----------------------------------------------------------------------------
+#include "mozilla/net/HttpChannelChild.h"
+
+#define UA_PREF_PREFIX "general.useragent."
+#ifdef XP_WIN
+# define UA_SPARE_PLATFORM
+#endif
+
+#define HTTP_PREF_PREFIX "network.http."
+#define INTL_ACCEPT_LANGUAGES "intl.accept_languages"
+#define BROWSER_PREF_PREFIX "browser.cache."
+#define H2MANDATORY_SUITE "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256"
+#define SAFE_HINT_HEADER_VALUE "safeHint.enabled"
+#define SECURITY_PREFIX "security."
+#define DOM_SECURITY_PREFIX "dom.security"
+
+#define ACCEPT_HEADER_STYLE "text/css,*/*;q=0.1"
+#define ACCEPT_HEADER_ALL "*/*"
+
+#define UA_PREF(_pref) UA_PREF_PREFIX _pref
+#define HTTP_PREF(_pref) HTTP_PREF_PREFIX _pref
+#define BROWSER_PREF(_pref) BROWSER_PREF_PREFIX _pref
+
+#define NS_HTTP_PROTOCOL_FLAGS \
+ (URI_STD | ALLOWS_PROXY | ALLOWS_PROXY_HTTP | URI_LOADABLE_BY_ANYONE)
+
+//-----------------------------------------------------------------------------
+
+using mozilla::dom::Promise;
+
+namespace mozilla::net {
+
+LazyLogModule gHttpLog("nsHttp");
+
+#ifdef ANDROID
+static nsCString GetDeviceModelId() {
+ // Assumed to be running on the main thread
+ // We need the device property in either case
+ nsAutoCString deviceModelId;
+ nsCOMPtr<nsIPropertyBag2> infoService =
+ do_GetService("@mozilla.org/system-info;1");
+ MOZ_ASSERT(infoService, "Could not find a system info service");
+ nsAutoString androidDevice;
+ nsresult rv = infoService->GetPropertyAsAString(u"device"_ns, androidDevice);
+ if (NS_SUCCEEDED(rv)) {
+ deviceModelId = NS_LossyConvertUTF16toASCII(androidDevice);
+ }
+ nsAutoCString deviceString;
+ rv = Preferences::GetCString(UA_PREF("device_string"), deviceString);
+ if (NS_SUCCEEDED(rv)) {
+ deviceString.Trim(" ", true, true);
+ deviceString.ReplaceSubstring("%DEVICEID%"_ns, deviceModelId);
+ return std::move(deviceString);
+ }
+ return std::move(deviceModelId);
+}
+#endif
+
+#ifdef XP_UNIX
+static bool IsRunningUnderUbuntuSnap() {
+# if defined(MOZ_WIDGET_GTK)
+ if (!widget::IsRunningUnderSnap()) {
+ return false;
+ }
+
+ char version[100];
+ if (PR_GetSystemInfo(PR_SI_RELEASE_BUILD, version, sizeof(version)) ==
+ PR_SUCCESS) {
+ if (strstr(version, "Ubuntu")) {
+ return true;
+ }
+ }
+# endif
+ return false;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler <public>
+//-----------------------------------------------------------------------------
+
+StaticRefPtr<nsHttpHandler> gHttpHandler;
+
+/* static */
+already_AddRefed<nsHttpHandler> nsHttpHandler::GetInstance() {
+ if (!gHttpHandler) {
+ gHttpHandler = new nsHttpHandler();
+ DebugOnly<nsresult> rv = gHttpHandler->Init();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ // There is code that may be executed during the final cycle collection
+ // shutdown and still referencing gHttpHandler.
+ ClearOnShutdown(&gHttpHandler, ShutdownPhase::CCPostLastCycleCollection);
+ }
+ RefPtr<nsHttpHandler> httpHandler = gHttpHandler;
+ return httpHandler.forget();
+}
+
+/// Derive the HTTP Accept header for image requests based on the enabled prefs
+/// for non-universal image types. This may be overridden in its entirety by
+/// the image.http.accept pref.
+static nsCString ImageAcceptHeader() {
+ nsCString mimeTypes;
+
+ if (mozilla::StaticPrefs::image_avif_enabled()) {
+ mimeTypes.Append("image/avif,");
+ }
+
+ if (mozilla::StaticPrefs::image_jxl_enabled()) {
+ mimeTypes.Append("image/jxl,");
+ }
+
+ mimeTypes.Append("image/webp,*/*");
+
+ return mimeTypes;
+}
+
+static nsCString DocumentAcceptHeader(const nsCString& aImageAcceptHeader) {
+ nsPrintfCString mimeTypes(
+ "text/html,application/xhtml+xml,application/xml;q=0.9,%s;q=0.8",
+ aImageAcceptHeader.get());
+
+ return std::move(mimeTypes);
+}
+
+nsHttpHandler::nsHttpHandler()
+ : mIdleTimeout(PR_SecondsToInterval(10)),
+ mSpdyTimeout(
+ PR_SecondsToInterval(StaticPrefs::network_http_http2_timeout())),
+ mResponseTimeout(PR_SecondsToInterval(300)),
+ mImageAcceptHeader(ImageAcceptHeader()),
+ mDocumentAcceptHeader(DocumentAcceptHeader(ImageAcceptHeader())),
+ mLastUniqueID(NowInSeconds()),
+ mDebugObservations(false),
+ mEnableAltSvc(false),
+ mEnableAltSvcOE(false),
+ mEnableOriginExtension(false),
+ mSpdyPingThreshold(PR_SecondsToInterval(
+ StaticPrefs::network_http_http2_ping_threshold())),
+ mSpdyPingTimeout(PR_SecondsToInterval(
+ StaticPrefs::network_http_http2_ping_timeout())) {
+ LOG(("Creating nsHttpHandler [this=%p].\n", this));
+
+ mUserAgentOverride.SetIsVoid(true);
+
+ MOZ_ASSERT(!gHttpHandler, "HTTP handler already created!");
+
+ nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
+ if (runtime) {
+ runtime->GetProcessID(&mProcessId);
+ }
+}
+
+nsHttpHandler::~nsHttpHandler() {
+ LOG(("Deleting nsHttpHandler [this=%p]\n", this));
+
+ // make sure the connection manager is shutdown
+ if (mConnMgr) {
+ nsresult rv = mConnMgr->Shutdown();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler [this=%p] "
+ "failed to shutdown connection manager (%08x)\n",
+ this, static_cast<uint32_t>(rv)));
+ }
+ mConnMgr = nullptr;
+ }
+
+ // Note: don't call NeckoChild::DestroyNeckoChild() here, as it's too late
+ // and it'll segfault. NeckoChild will get cleaned up by process exit.
+
+ nsHttp::DestroyAtomTable();
+}
+
+static const char* gCallbackPrefs[] = {
+ HTTP_PREF_PREFIX,
+ UA_PREF_PREFIX,
+ INTL_ACCEPT_LANGUAGES,
+ BROWSER_PREF("disk_cache_ssl"),
+ H2MANDATORY_SUITE,
+ HTTP_PREF("tcp_keepalive.short_lived_connections"),
+ HTTP_PREF("tcp_keepalive.long_lived_connections"),
+ SAFE_HINT_HEADER_VALUE,
+ SECURITY_PREFIX,
+ DOM_SECURITY_PREFIX,
+ "image.http.accept",
+ "image.avif.enabled",
+ "image.jxl.enabled",
+ nullptr,
+};
+
+nsresult nsHttpHandler::Init() {
+ nsresult rv;
+
+ LOG(("nsHttpHandler::Init\n"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We should not create nsHttpHandler during shutdown, but we have some
+ // xpcshell tests doing this.
+ if (MOZ_UNLIKELY(AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown) &&
+ !PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR"))) {
+ MOZ_DIAGNOSTIC_ASSERT(false, "Try to init HttpHandler after shutdown");
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ rv = nsHttp::CreateAtomTable();
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIIOService> service = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to continue without io service");
+ return rv;
+ }
+ mIOService = new nsMainThreadPtrHolder<nsIIOService>(
+ "nsHttpHandler::mIOService", service);
+
+ gIOService->LaunchSocketProcess();
+
+ if (IsNeckoChild()) NeckoChild::InitNeckoChild();
+
+ InitUserAgentComponents();
+
+ // This perference is only used in parent process.
+ if (!IsNeckoChild()) {
+ mActiveTabPriority =
+ Preferences::GetBool(HTTP_PREF("active_tab_priority"), true);
+ std::bitset<3> usageOfHTTPSRRPrefs;
+ usageOfHTTPSRRPrefs[0] = StaticPrefs::network_dns_upgrade_with_https_rr();
+ usageOfHTTPSRRPrefs[1] = StaticPrefs::network_dns_use_https_rr_as_altsvc();
+ usageOfHTTPSRRPrefs[2] = StaticPrefs::network_dns_echconfig_enabled();
+ Telemetry::ScalarSet(Telemetry::ScalarID::NETWORKING_HTTPS_RR_PREFS_USAGE,
+ static_cast<uint32_t>(usageOfHTTPSRRPrefs.to_ulong()));
+ mActivityDistributor = components::HttpActivityDistributor::Service();
+
+ auto initQLogDir = [&]() {
+ if (!StaticPrefs::network_http_http3_enable_qlog()) {
+ return EmptyCString();
+ }
+
+ nsCOMPtr<nsIFile> qlogDir;
+ nsresult rv =
+ NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(qlogDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return EmptyCString();
+ }
+
+ nsAutoCString dirName("qlog_");
+ dirName.AppendInt(mProcessId);
+ rv = qlogDir->AppendNative(dirName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return EmptyCString();
+ }
+
+ return qlogDir->HumanReadablePath();
+ };
+ mHttp3QlogDir = initQLogDir();
+ }
+
+ // monitor some preference changes
+ Preferences::RegisterPrefixCallbacks(nsHttpHandler::PrefsChanged,
+ gCallbackPrefs, this);
+ PrefsChanged(nullptr);
+
+ Telemetry::ScalarSet(Telemetry::ScalarID::NETWORKING_HTTP3_ENABLED,
+ StaticPrefs::network_http_http3_enable());
+
+ mCompatFirefox.AssignLiteral("Firefox/" MOZILLA_UAVERSION);
+
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+
+ mAppName.AssignLiteral(MOZ_APP_UA_NAME);
+ if (mAppName.Length() == 0 && appInfo) {
+ // Try to get the UA name from appInfo, falling back to the name
+ appInfo->GetUAName(mAppName);
+ if (mAppName.Length() == 0) {
+ appInfo->GetName(mAppName);
+ }
+ appInfo->GetVersion(mAppVersion);
+ mAppName.StripChars(R"( ()<>@,;:\"/[]?={})");
+ } else {
+ mAppVersion.AssignLiteral(MOZ_APP_UA_VERSION);
+ }
+
+ mMisc.AssignLiteral("rv:" MOZILLA_UAVERSION);
+
+ // Generate the spoofed User Agent for fingerprinting resistance.
+ nsRFPService::GetSpoofedUserAgent(mSpoofedUserAgent, true);
+
+ mSessionStartTime = NowInSeconds();
+ mHandlerActive = true;
+
+ rv = InitConnectionMgr();
+ if (NS_FAILED(rv)) return rv;
+
+ mAltSvcCache = MakeUnique<AltSvcCache>();
+
+ mRequestContextService = RequestContextService::GetOrCreate();
+
+#if defined(ANDROID)
+ mProductSub.AssignLiteral(MOZILLA_UAVERSION);
+#else
+ mProductSub.AssignLiteral(LEGACY_UA_GECKO_TRAIL);
+#endif
+
+#if DEBUG
+ // dump user agent prefs
+ LOG(("> legacy-app-name = %s\n", mLegacyAppName.get()));
+ LOG(("> legacy-app-version = %s\n", mLegacyAppVersion.get()));
+ LOG(("> platform = %s\n", mPlatform.get()));
+ LOG(("> oscpu = %s\n", mOscpu.get()));
+ LOG(("> misc = %s\n", mMisc.get()));
+ LOG(("> product = %s\n", mProduct.get()));
+ LOG(("> product-sub = %s\n", mProductSub.get()));
+ LOG(("> app-name = %s\n", mAppName.get()));
+ LOG(("> app-version = %s\n", mAppVersion.get()));
+ LOG(("> compat-firefox = %s\n", mCompatFirefox.get()));
+ LOG(("> user-agent = %s\n", UserAgent(false).get()));
+#endif
+
+ // Startup the http category
+ // Bring alive the objects in the http-protocol-startup category
+ NS_CreateServicesFromCategory(
+ NS_HTTP_STARTUP_CATEGORY,
+ static_cast<nsISupports*>(static_cast<void*>(this)),
+ NS_HTTP_STARTUP_TOPIC);
+
+ nsCOMPtr<nsIObserverService> obsService =
+ static_cast<nsIObserverService*>(gIOService);
+ if (obsService) {
+ // register the handler object as a weak callback as we don't need to worry
+ // about shutdown ordering.
+ obsService->AddObserver(this, "profile-change-net-teardown", true);
+ obsService->AddObserver(this, "profile-change-net-restore", true);
+ obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ obsService->AddObserver(this, "net:clear-active-logins", true);
+ obsService->AddObserver(this, "net:prune-dead-connections", true);
+ // Sent by the TorButton add-on in the Tor Browser
+ obsService->AddObserver(this, "net:prune-all-connections", true);
+ obsService->AddObserver(this, "net:cancel-all-connections", true);
+ obsService->AddObserver(this, "last-pb-context-exited", true);
+ obsService->AddObserver(this, "browser:purge-session-history", true);
+ obsService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
+ obsService->AddObserver(this, "application-background", true);
+ obsService->AddObserver(this, "psm:user-certificate-added", true);
+ obsService->AddObserver(this, "psm:user-certificate-deleted", true);
+ obsService->AddObserver(this, "intl:app-locales-changed", true);
+ obsService->AddObserver(this, "browser-delayed-startup-finished", true);
+ obsService->AddObserver(this, "network:captive-portal-connectivity", true);
+ obsService->AddObserver(this, "network:reset-http3-excluded-list", true);
+ obsService->AddObserver(this, "network:socket-process-crashed", true);
+
+ if (!IsNeckoChild()) {
+ obsService->AddObserver(this, "net:current-browser-id", true);
+ }
+
+ // disabled as its a nop right now
+ // obsService->AddObserver(this, "net:failed-to-process-uri-content", true);
+ }
+
+ MakeNewRequestTokenBucket();
+ mWifiTickler = new Tickler();
+ if (NS_FAILED(mWifiTickler->Init())) mWifiTickler = nullptr;
+
+ nsCOMPtr<nsIParentalControlsService> pc =
+ do_CreateInstance("@mozilla.org/parental-controls-service;1");
+ if (pc) {
+ pc->GetParentalControlsEnabled(&mParentalControlEnabled);
+ }
+
+ return NS_OK;
+}
+
+const nsCString& nsHttpHandler::Http3QlogDir() {
+ if (StaticPrefs::network_http_http3_enable_qlog()) {
+ return mHttp3QlogDir;
+ }
+
+ return EmptyCString();
+}
+
+void nsHttpHandler::MakeNewRequestTokenBucket() {
+ LOG(("nsHttpHandler::MakeNewRequestTokenBucket this=%p child=%d\n", this,
+ IsNeckoChild()));
+ if (!mConnMgr || IsNeckoChild()) {
+ return;
+ }
+ RefPtr<EventTokenBucket> tokenBucket =
+ new EventTokenBucket(RequestTokenBucketHz(), RequestTokenBucketBurst());
+ // NOTE The thread or socket may be gone already.
+ nsresult rv = mConnMgr->UpdateRequestTokenBucket(tokenBucket);
+ if (NS_FAILED(rv)) {
+ LOG((" failed to update request token bucket\n"));
+ }
+}
+
+nsresult nsHttpHandler::InitConnectionMgr() {
+ // Init ConnectionManager only on parent!
+ if (IsNeckoChild()) {
+ return NS_OK;
+ }
+
+ if (mConnMgr) {
+ return NS_OK;
+ }
+
+ if (nsIOService::UseSocketProcess(true) && XRE_IsParentProcess()) {
+ mConnMgr = new HttpConnectionMgrParent();
+ RefPtr<nsHttpHandler> self = this;
+ auto task = [self]() {
+ RefPtr<HttpConnectionMgrParent> parent =
+ self->mConnMgr->AsHttpConnectionMgrParent();
+ Unused << SocketProcessParent::GetSingleton()
+ ->SendPHttpConnectionMgrConstructor(
+ parent,
+ HttpHandlerInitArgs(
+ self->mLegacyAppName, self->mLegacyAppVersion,
+ self->mPlatform, self->mOscpu, self->mMisc,
+ self->mProduct, self->mProductSub, self->mAppName,
+ self->mAppVersion, self->mCompatFirefox,
+ self->mCompatDevice, self->mDeviceModelId));
+ };
+ gIOService->CallOrWaitForSocketProcess(std::move(task));
+ } else {
+ MOZ_ASSERT(XRE_IsSocketProcess() || !nsIOService::UseSocketProcess());
+ mConnMgr = new nsHttpConnectionMgr();
+ }
+
+ return mConnMgr->Init(
+ mMaxUrgentExcessiveConns, mMaxConnections,
+ mMaxPersistentConnectionsPerServer, mMaxPersistentConnectionsPerProxy,
+ mMaxRequestDelay, mThrottleEnabled, mThrottleVersion, mThrottleSuspendFor,
+ mThrottleResumeFor, mThrottleReadLimit, mThrottleReadInterval,
+ mThrottleHoldTime, mThrottleMaxTime, mBeConservativeForProxy);
+}
+
+nsresult nsHttpHandler::AddStandardRequestHeaders(
+ nsHttpRequestHead* request, bool isSecure,
+ ExtContentPolicyType aContentPolicyType, bool aShouldResistFingerprinting) {
+ nsresult rv;
+
+ // Add the "User-Agent" header
+ rv = request->SetHeader(nsHttp::User_Agent,
+ UserAgent(aShouldResistFingerprinting), false,
+ nsHttpHeaderArray::eVarietyRequestDefault);
+ if (NS_FAILED(rv)) return rv;
+
+ // MIME based content negotiation lives!
+ // Add the "Accept" header. Note, this is set as an override because the
+ // service worker expects to see it. The other "default" headers are
+ // hidden from service worker interception.
+ nsAutoCString accept;
+ if (aContentPolicyType == ExtContentPolicy::TYPE_DOCUMENT ||
+ aContentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ accept.Assign(mDocumentAcceptHeader);
+ } else if (aContentPolicyType == ExtContentPolicy::TYPE_IMAGE ||
+ aContentPolicyType == ExtContentPolicy::TYPE_IMAGESET) {
+ accept.Assign(mImageAcceptHeader);
+ } else if (aContentPolicyType == ExtContentPolicy::TYPE_STYLESHEET) {
+ accept.Assign(ACCEPT_HEADER_STYLE);
+ } else {
+ accept.Assign(ACCEPT_HEADER_ALL);
+ }
+
+ rv = request->SetHeader(nsHttp::Accept, accept, false,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+ if (NS_FAILED(rv)) return rv;
+
+ // Add the "Accept-Language" header. This header is also exposed to the
+ // service worker.
+ if (mAcceptLanguagesIsDirty) {
+ rv = SetAcceptLanguages();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // Add the "Accept-Language" header
+ if (!mAcceptLanguages.IsEmpty()) {
+ rv = request->SetHeader(nsHttp::Accept_Language, mAcceptLanguages, false,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Add the "Accept-Encoding" header
+ if (isSecure) {
+ rv = request->SetHeader(nsHttp::Accept_Encoding, mHttpsAcceptEncodings,
+ false, nsHttpHeaderArray::eVarietyRequestDefault);
+ } else {
+ rv = request->SetHeader(nsHttp::Accept_Encoding, mHttpAcceptEncodings,
+ false, nsHttpHeaderArray::eVarietyRequestDefault);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ // add the "Send Hint" header
+ if (mSafeHintEnabled || mParentalControlEnabled) {
+ rv = request->SetHeader(nsHttp::Prefer, "safe"_ns, false,
+ nsHttpHeaderArray::eVarietyRequestDefault);
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpHandler::AddConnectionHeader(nsHttpRequestHead* request,
+ uint32_t caps) {
+ // RFC2616 section 19.6.2 states that the "Connection: keep-alive"
+ // and "Keep-alive" request headers should not be sent by HTTP/1.1
+ // user-agents. But this is not a problem in practice, and the
+ // alternative proxy-connection is worse. see 570283
+
+ constexpr auto close = "close"_ns;
+ constexpr auto keepAlive = "keep-alive"_ns;
+
+ const nsLiteralCString* connectionType = &close;
+ if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
+ connectionType = &keepAlive;
+ }
+
+ return request->SetHeader(nsHttp::Connection, *connectionType);
+}
+
+bool nsHttpHandler::IsAcceptableEncoding(const char* enc, bool isSecure) {
+ if (!enc) return false;
+
+ // we used to accept x-foo anytime foo was acceptable, but that's just
+ // continuing bad behavior.. so limit it to known x-* patterns
+ bool rv;
+ if (isSecure) {
+ rv = nsHttp::FindToken(mHttpsAcceptEncodings.get(), enc, HTTP_LWS ",") !=
+ nullptr;
+ } else {
+ rv = nsHttp::FindToken(mHttpAcceptEncodings.get(), enc, HTTP_LWS ",") !=
+ nullptr;
+ }
+ // gzip and deflate are inherently acceptable in modern HTTP - always
+ // process them if a stream converter can also be found.
+ if (!rv &&
+ (!nsCRT::strcasecmp(enc, "gzip") || !nsCRT::strcasecmp(enc, "deflate") ||
+ !nsCRT::strcasecmp(enc, "x-gzip") ||
+ !nsCRT::strcasecmp(enc, "x-deflate"))) {
+ rv = true;
+ }
+ LOG(("nsHttpHandler::IsAceptableEncoding %s https=%d %d\n", enc, isSecure,
+ rv));
+ return rv;
+}
+
+nsISiteSecurityService* nsHttpHandler::GetSSService() {
+ if (!mSSService) {
+ nsCOMPtr<nsISiteSecurityService> service =
+ do_GetService(NS_SSSERVICE_CONTRACTID);
+ mSSService = new nsMainThreadPtrHolder<nsISiteSecurityService>(
+ "nsHttpHandler::mSSService", service);
+ }
+ return mSSService;
+}
+
+nsICookieService* nsHttpHandler::GetCookieService() {
+ if (!mCookieService) {
+ nsCOMPtr<nsICookieService> service =
+ do_GetService(NS_COOKIESERVICE_CONTRACTID);
+ mCookieService = new nsMainThreadPtrHolder<nsICookieService>(
+ "nsHttpHandler::mCookieService", service);
+ }
+ return mCookieService;
+}
+
+nsresult nsHttpHandler::GetIOService(nsIIOService** result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ *result = do_AddRef(mIOService.get()).take();
+ return NS_OK;
+}
+
+void nsHttpHandler::NotifyObservers(nsIChannel* chan, const char* event) {
+ LOG(("nsHttpHandler::NotifyObservers [this=%p chan=%p event=\"%s\"]\n", this,
+ chan, event));
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) obsService->NotifyObservers(chan, event, nullptr);
+}
+
+nsresult nsHttpHandler::AsyncOnChannelRedirect(
+ nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags,
+ nsIEventTarget* mainThreadEventTarget) {
+ MOZ_ASSERT(NS_IsMainThread() && (oldChan && newChan));
+
+ nsCOMPtr<nsIURI> oldURI;
+ oldChan->GetURI(getter_AddRefs(oldURI));
+ MOZ_ASSERT(oldURI);
+
+ nsCOMPtr<nsIURI> newURI;
+ newChan->GetURI(getter_AddRefs(newURI));
+ MOZ_ASSERT(newURI);
+
+ PrepareForAntiTrackingRedirectHeuristic(oldChan, oldURI, newChan, newURI);
+
+ DynamicFpiRedirectHeuristic(oldChan, oldURI, newChan, newURI);
+
+ // TODO E10S This helper has to be initialized on the other process
+ RefPtr<nsAsyncRedirectVerifyHelper> redirectCallbackHelper =
+ new nsAsyncRedirectVerifyHelper();
+
+ return redirectCallbackHelper->Init(oldChan, newChan, flags,
+ mainThreadEventTarget);
+}
+
+/* static */
+nsresult nsHttpHandler::GenerateHostPort(const nsCString& host, int32_t port,
+ nsACString& hostLine) {
+ return NS_GenerateHostPort(host, port, hostLine);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler <private>
+//-----------------------------------------------------------------------------
+
+const nsCString& nsHttpHandler::UserAgent(bool aShouldResistFingerprinting) {
+ if (aShouldResistFingerprinting && !mSpoofedUserAgent.IsEmpty()) {
+ LOG(("using spoofed userAgent : %s\n", mSpoofedUserAgent.get()));
+ return mSpoofedUserAgent;
+ }
+
+ if (!mUserAgentOverride.IsVoid()) {
+ LOG(("using general.useragent.override : %s\n", mUserAgentOverride.get()));
+ return mUserAgentOverride;
+ }
+
+ if (mUserAgentIsDirty) {
+ BuildUserAgent();
+ mUserAgentIsDirty = false;
+ }
+
+ return mUserAgent;
+}
+
+void nsHttpHandler::BuildUserAgent() {
+ LOG(("nsHttpHandler::BuildUserAgent\n"));
+
+ MOZ_ASSERT(!mLegacyAppName.IsEmpty() && !mLegacyAppVersion.IsEmpty(),
+ "HTTP cannot send practical requests without this much");
+
+ // preallocate to worst-case size, which should always be better
+ // than if we didn't preallocate at all.
+ mUserAgent.SetCapacity(mLegacyAppName.Length() + mLegacyAppVersion.Length() +
+#ifndef UA_SPARE_PLATFORM
+ mPlatform.Length() +
+#endif
+ mOscpu.Length() + mMisc.Length() + mProduct.Length() +
+ mProductSub.Length() + mAppName.Length() +
+ mAppVersion.Length() + mCompatFirefox.Length() +
+ mCompatDevice.Length() + mDeviceModelId.Length() + 13);
+
+ // Application portion
+ mUserAgent.Assign(mLegacyAppName);
+ mUserAgent += '/';
+ mUserAgent += mLegacyAppVersion;
+ mUserAgent += ' ';
+
+ // Application comment
+ mUserAgent += '(';
+#ifndef UA_SPARE_PLATFORM
+ if (!mPlatform.IsEmpty()) {
+ mUserAgent += mPlatform;
+ mUserAgent.AppendLiteral("; ");
+ }
+#endif
+ if (!mCompatDevice.IsEmpty()) {
+ mUserAgent += mCompatDevice;
+ mUserAgent.AppendLiteral("; ");
+ } else if (!mOscpu.IsEmpty()) {
+ mUserAgent += mOscpu;
+ mUserAgent.AppendLiteral("; ");
+ }
+ if (!mDeviceModelId.IsEmpty()) {
+ mUserAgent += mDeviceModelId;
+ mUserAgent.AppendLiteral("; ");
+ }
+ mUserAgent += mMisc;
+ mUserAgent += ')';
+
+ // Product portion
+ mUserAgent += ' ';
+ mUserAgent += mProduct;
+ mUserAgent += '/';
+ mUserAgent += mProductSub;
+
+ bool isFirefox = mAppName.EqualsLiteral("Firefox");
+ if (isFirefox || mCompatFirefoxEnabled) {
+ // "Firefox/x.y" (compatibility) app token
+ mUserAgent += ' ';
+ mUserAgent += mCompatFirefox;
+ }
+ if (!isFirefox) {
+ // App portion
+ mUserAgent += ' ';
+ mUserAgent += mAppName;
+ mUserAgent += '/';
+ mUserAgent += mAppVersion;
+ }
+}
+
+#ifdef XP_WIN
+// Hardcode the reported Windows version to 10.0. This way, Microsoft doesn't
+// get to change Web compat-sensitive values without our veto. The compat-
+// sensitivity keeps going up as 10.0 stays as the current value for longer
+// and longer. If the system-reported version ever changes, we'll be able to
+// take our time to evaluate the Web compat impact instead of having to
+// scramble to react like happened with macOS changing from 10.x to 11.x.
+# define OSCPU_WINDOWS "Windows NT 10.0"
+# define OSCPU_WIN64 OSCPU_WINDOWS "; Win64; x64"
+#endif
+
+void nsHttpHandler::InitUserAgentComponents() {
+ // Don't build user agent components in socket process, since the system info
+ // is not available.
+ if (XRE_IsSocketProcess()) {
+ mUserAgentIsDirty = true;
+ return;
+ }
+
+ // Gather platform.
+ mPlatform.AssignLiteral(
+#if defined(ANDROID)
+ "Android"
+#elif defined(XP_WIN)
+ "Windows"
+#elif defined(XP_MACOSX)
+ "Macintosh"
+#elif defined(XP_UNIX)
+ // We historically have always had X11 here,
+ // and there seems little a webpage can sensibly do
+ // based on it being something else, so use X11 for
+ // backwards compatibility in all cases.
+ "X11"
+#endif
+ );
+
+#ifdef XP_UNIX
+ if (IsRunningUnderUbuntuSnap()) {
+ mPlatform.AppendLiteral("; Ubuntu");
+ }
+#endif
+
+#ifdef ANDROID
+ nsCOMPtr<nsIPropertyBag2> infoService =
+ do_GetService("@mozilla.org/system-info;1");
+ MOZ_ASSERT(infoService, "Could not find a system info service");
+ nsresult rv;
+ // Add the Android version number to the Fennec platform identifier.
+ nsAutoString androidVersion;
+ rv = infoService->GetPropertyAsAString(u"release_version"_ns, androidVersion);
+ if (NS_SUCCEEDED(rv)) {
+ mPlatform += " ";
+ mPlatform += NS_LossyConvertUTF16toASCII(androidVersion);
+ }
+
+ // Add the `Mobile` or `TV` token when running on device.
+ bool isTV;
+ rv = infoService->GetPropertyAsBool(u"tv"_ns, &isTV);
+ if (NS_SUCCEEDED(rv) && isTV) {
+ mCompatDevice.AssignLiteral("TV");
+ } else {
+ mCompatDevice.AssignLiteral("Mobile");
+ }
+
+ if (Preferences::GetBool(UA_PREF("use_device"), false)) {
+ mDeviceModelId = mozilla::net::GetDeviceModelId();
+ }
+#endif // ANDROID
+
+ // Gather OS/CPU.
+#if defined(XP_WIN)
+
+# if defined _M_X64 || defined _M_AMD64
+ mOscpu.AssignLiteral(OSCPU_WIN64);
+# elif defined(_ARM64_)
+ // Report ARM64 Windows 11+ as x86_64 and Windows 10 as x86. Windows 11+
+ // supports x86_64 emulation, but Windows 10 only supports x86 emulation.
+ if (IsWin11OrLater()) {
+ mOscpu.AssignLiteral(OSCPU_WIN64);
+ } else {
+ mOscpu.AssignLiteral(OSCPU_WINDOWS);
+ }
+# else
+ BOOL isWow64 = FALSE;
+ if (!IsWow64Process(GetCurrentProcess(), &isWow64)) {
+ isWow64 = FALSE;
+ }
+ if (isWow64) {
+ mOscpu.AssignLiteral(OSCPU_WIN64);
+ } else {
+ mOscpu.AssignLiteral(OSCPU_WINDOWS);
+ }
+# endif
+
+#elif defined(XP_MACOSX)
+ mOscpu.AssignLiteral("Intel Mac OS X 10.15");
+#elif defined(XP_UNIX)
+ if (mozilla::StaticPrefs::network_http_useragent_freezeCpu()) {
+# ifdef ANDROID
+ mOscpu.AssignLiteral("Linux armv81");
+# else
+ mOscpu.AssignLiteral("Linux x86_64");
+# endif
+ } else {
+ struct utsname name {};
+ int ret = uname(&name);
+ if (ret >= 0) {
+ nsAutoCString buf;
+ buf = (char*)name.sysname;
+ buf += ' ';
+
+# ifdef AIX
+ // AIX uname returns machine specific info in the uname.machine
+ // field and does not return the cpu type like other platforms.
+ // We use the AIX version and release numbers instead.
+ buf += (char*)name.version;
+ buf += '.';
+ buf += (char*)name.release;
+# else
+ buf += (char*)name.machine;
+# endif
+
+ mOscpu.Assign(buf);
+ }
+ }
+#endif
+
+ mUserAgentIsDirty = true;
+}
+
+uint32_t nsHttpHandler::MaxSocketCount() {
+ PR_CallOnce(&nsSocketTransportService::gMaxCountInitOnce,
+ nsSocketTransportService::DiscoverMaxCount);
+ // Don't use the full max count because sockets can be held in
+ // the persistent connection pool for a long time and that could
+ // starve other users.
+
+ uint32_t maxCount = nsSocketTransportService::gMaxCount;
+ if (maxCount <= 8) {
+ maxCount = 1;
+ } else {
+ maxCount -= 8;
+ }
+
+ return maxCount;
+}
+
+// static
+void nsHttpHandler::PrefsChanged(const char* pref, void* self) {
+ static_cast<nsHttpHandler*>(self)->PrefsChanged(pref);
+}
+
+void nsHttpHandler::PrefsChanged(const char* pref) {
+ nsresult rv = NS_OK;
+ int32_t val;
+
+ LOG(("nsHttpHandler::PrefsChanged [pref=%s]\n", pref));
+
+ if (pref) {
+ gIOService->NotifySocketProcessPrefsChanged(pref);
+ }
+
+#define PREF_CHANGED(p) ((pref == nullptr) || !strcmp(pref, p))
+#define MULTI_PREF_CHANGED(p) \
+ ((pref == nullptr) || !strncmp(pref, p, sizeof(p) - 1))
+
+ // If a security pref changed, lets clear our connection pool reuse
+ if (MULTI_PREF_CHANGED(SECURITY_PREFIX)) {
+ LOG(("nsHttpHandler::PrefsChanged Security Pref Changed %s\n", pref));
+ if (mConnMgr) {
+ rv = mConnMgr->DoShiftReloadConnectionCleanup();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged "
+ "DoShiftReloadConnectionCleanup failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ rv = mConnMgr->PruneDeadConnections();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged "
+ "PruneDeadConnections failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+
+ //
+ // UA components
+ //
+
+ bool cVar = false;
+
+ if (PREF_CHANGED(UA_PREF("compatMode.firefox"))) {
+ rv = Preferences::GetBool(UA_PREF("compatMode.firefox"), &cVar);
+ mCompatFirefoxEnabled = (NS_SUCCEEDED(rv) && cVar);
+ mUserAgentIsDirty = true;
+ }
+
+ // general.useragent.override
+ if (PREF_CHANGED(UA_PREF("override"))) {
+ Preferences::GetCString(UA_PREF("override"), mUserAgentOverride);
+ mUserAgentIsDirty = true;
+ }
+
+#ifdef ANDROID
+ // general.useragent.use_device
+ if (PREF_CHANGED(UA_PREF("use_device"))) {
+ if (Preferences::GetBool(UA_PREF("use_device"), false)) {
+ if (!XRE_IsSocketProcess()) {
+ mDeviceModelId = mozilla::net::GetDeviceModelId();
+ if (gIOService->SocketProcessReady()) {
+ Unused << SocketProcessParent::GetSingleton()
+ ->SendUpdateDeviceModelId(mDeviceModelId);
+ }
+ }
+ } else {
+ mDeviceModelId.Truncate();
+ }
+ mUserAgentIsDirty = true;
+ }
+#endif
+
+ //
+ // HTTP options
+ //
+
+ if (PREF_CHANGED(HTTP_PREF("keep-alive.timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("keep-alive.timeout"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mIdleTimeout = PR_SecondsToInterval(clamped(val, 1, 0xffff));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("request.max-attempts"))) {
+ rv = Preferences::GetInt(HTTP_PREF("request.max-attempts"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxRequestAttempts = (uint16_t)clamped(val, 1, 0xffff);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("request.max-start-delay"))) {
+ rv = Preferences::GetInt(HTTP_PREF("request.max-start-delay"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxRequestDelay = (uint16_t)clamped(val, 0, 0xffff);
+ if (mConnMgr) {
+ rv = mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_REQUEST_DELAY,
+ mMaxRequestDelay);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged (request.max-start-delay)"
+ "UpdateParam failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("response.timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("response.timeout"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mResponseTimeout = PR_SecondsToInterval(clamped(val, 0, 0xffff));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("network-changed.timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("network-changed.timeout"), &val);
+ if (NS_SUCCEEDED(rv)) mNetworkChangedTimeout = clamped(val, 1, 600) * 1000;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max-connections"))) {
+ rv = Preferences::GetInt(HTTP_PREF("max-connections"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxConnections =
+ (uint16_t)clamped((uint32_t)val, (uint32_t)1, MaxSocketCount());
+
+ if (mConnMgr) {
+ rv = mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_CONNECTIONS,
+ mMaxConnections);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged (max-connections)"
+ "UpdateParam failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(
+ HTTP_PREF("max-urgent-start-excessive-connections-per-host"))) {
+ rv = Preferences::GetInt(
+ HTTP_PREF("max-urgent-start-excessive-connections-per-host"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxUrgentExcessiveConns = (uint8_t)clamped(val, 1, 0xff);
+ if (mConnMgr) {
+ rv = mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_URGENT_START_Q,
+ mMaxUrgentExcessiveConns);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged "
+ "(max-urgent-start-excessive-connections-per-host)"
+ "UpdateParam failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max-persistent-connections-per-server"))) {
+ rv = Preferences::GetInt(HTTP_PREF("max-persistent-connections-per-server"),
+ &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxPersistentConnectionsPerServer = (uint8_t)clamped(val, 1, 0xff);
+ if (mConnMgr) {
+ rv = mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::MAX_PERSISTENT_CONNECTIONS_PER_HOST,
+ mMaxPersistentConnectionsPerServer);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged "
+ "(max-persistent-connections-per-server)"
+ "UpdateParam failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max-persistent-connections-per-proxy"))) {
+ rv = Preferences::GetInt(HTTP_PREF("max-persistent-connections-per-proxy"),
+ &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxPersistentConnectionsPerProxy = (uint8_t)clamped(val, 1, 0xff);
+ if (mConnMgr) {
+ rv = mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::MAX_PERSISTENT_CONNECTIONS_PER_PROXY,
+ mMaxPersistentConnectionsPerProxy);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::PrefsChanged "
+ "(max-persistent-connections-per-proxy)"
+ "UpdateParam failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("redirection-limit"))) {
+ rv = Preferences::GetInt(HTTP_PREF("redirection-limit"), &val);
+ if (NS_SUCCEEDED(rv)) mRedirectionLimit = (uint8_t)clamped(val, 0, 0xff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("connection-retry-timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("connection-retry-timeout"), &val);
+ if (NS_SUCCEEDED(rv)) mIdleSynTimeout = (uint16_t)clamped(val, 0, 3000);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("fast-fallback-to-IPv4"))) {
+ rv = Preferences::GetBool(HTTP_PREF("fast-fallback-to-IPv4"), &cVar);
+ if (NS_SUCCEEDED(rv)) mFastFallbackToIPv4 = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("fallback-connection-timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("fallback-connection-timeout"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mFallbackSynTimeout = (uint16_t)clamped(val, 0, 10 * 60);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("version"))) {
+ nsAutoCString httpVersion;
+ Preferences::GetCString(HTTP_PREF("version"), httpVersion);
+ if (!httpVersion.IsVoid()) {
+ if (httpVersion.EqualsLiteral("1.1")) {
+ mHttpVersion = HttpVersion::v1_1;
+ } else if (httpVersion.EqualsLiteral("0.9")) {
+ mHttpVersion = HttpVersion::v0_9;
+ } else {
+ mHttpVersion = HttpVersion::v1_0;
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("proxy.version"))) {
+ nsAutoCString httpVersion;
+ Preferences::GetCString(HTTP_PREF("proxy.version"), httpVersion);
+ if (!httpVersion.IsVoid()) {
+ if (httpVersion.EqualsLiteral("1.1")) {
+ mProxyHttpVersion = HttpVersion::v1_1;
+ } else {
+ mProxyHttpVersion = HttpVersion::v1_0;
+ }
+ // it does not make sense to issue a HTTP/0.9 request to a proxy server
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("proxy.respect-be-conservative"))) {
+ rv =
+ Preferences::GetBool(HTTP_PREF("proxy.respect-be-conservative"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mBeConservativeForProxy = cVar;
+ if (mConnMgr) {
+ Unused << mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::PROXY_BE_CONSERVATIVE,
+ static_cast<int32_t>(mBeConservativeForProxy));
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("qos"))) {
+ rv = Preferences::GetInt(HTTP_PREF("qos"), &val);
+ if (NS_SUCCEEDED(rv)) mQoSBits = (uint8_t)clamped(val, 0, 0xff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("accept-encoding"))) {
+ nsAutoCString acceptEncodings;
+ rv = Preferences::GetCString(HTTP_PREF("accept-encoding"), acceptEncodings);
+ if (NS_SUCCEEDED(rv)) {
+ rv = SetAcceptEncodings(acceptEncodings.get(), false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("accept-encoding.secure"))) {
+ nsAutoCString acceptEncodings;
+ rv = Preferences::GetCString(HTTP_PREF("accept-encoding.secure"),
+ acceptEncodings);
+ if (NS_SUCCEEDED(rv)) {
+ rv = SetAcceptEncodings(acceptEncodings.get(), true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("default-socket-type"))) {
+ nsAutoCString sval;
+ rv = Preferences::GetCString(HTTP_PREF("default-socket-type"), sval);
+ if (NS_SUCCEEDED(rv)) {
+ if (sval.IsEmpty()) {
+ mDefaultSocketType.SetIsVoid(true);
+ } else {
+ // verify that this socket type is actually valid
+ nsCOMPtr<nsISocketProviderService> sps =
+ nsSocketProviderService::GetOrCreate();
+ if (sps) {
+ nsCOMPtr<nsISocketProvider> sp;
+ rv = sps->GetSocketProvider(sval.get(), getter_AddRefs(sp));
+ if (NS_SUCCEEDED(rv)) {
+ // OK, this looks like a valid socket provider.
+ mDefaultSocketType.Assign(sval);
+ }
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("prompt-temp-redirect"))) {
+ rv = Preferences::GetBool(HTTP_PREF("prompt-temp-redirect"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mPromptTempRedirect = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("assoc-req.enforce"))) {
+ cVar = false;
+ rv = Preferences::GetBool(HTTP_PREF("assoc-req.enforce"), &cVar);
+ if (NS_SUCCEEDED(rv)) mEnforceAssocReq = cVar;
+ }
+
+ // enable Persistent caching for HTTPS - bug#205921
+ if (PREF_CHANGED(BROWSER_PREF("disk_cache_ssl"))) {
+ cVar = false;
+ rv = Preferences::GetBool(BROWSER_PREF("disk_cache_ssl"), &cVar);
+ if (NS_SUCCEEDED(rv)) mEnablePersistentHttpsCaching = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("phishy-userpass-length"))) {
+ rv = Preferences::GetInt(HTTP_PREF("phishy-userpass-length"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mPhishyUserPassLength = (uint8_t)clamped(val, 0, 0xff);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http2.timeout"))) {
+ mSpdyTimeout = PR_SecondsToInterval(
+ clamped(StaticPrefs::network_http_http2_timeout(), 1, 0xffff));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http2.chunk-size"))) {
+ // keep this within http/2 ranges of 1 to 2^24-1
+ mSpdySendingChunkSize = (uint32_t)clamped(
+ StaticPrefs::network_http_http2_chunk_size(), 1, 0xffffff);
+ }
+
+ // The amount of idle seconds on a http2 connection before initiating a
+ // server ping. 0 will disable.
+ if (PREF_CHANGED(HTTP_PREF("http2.ping-threshold"))) {
+ mSpdyPingThreshold = PR_SecondsToInterval((uint16_t)clamped(
+ StaticPrefs::network_http_http2_ping_threshold(), 0, 0x7fffffff));
+ }
+
+ // The amount of seconds to wait for a http2 ping response before
+ // closing the session.
+ if (PREF_CHANGED(HTTP_PREF("http2.ping-timeout"))) {
+ mSpdyPingTimeout = PR_SecondsToInterval((uint16_t)clamped(
+ StaticPrefs::network_http_http2_ping_timeout(), 0, 0x7fffffff));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("altsvc.enabled"))) {
+ rv = Preferences::GetBool(HTTP_PREF("altsvc.enabled"), &cVar);
+ if (NS_SUCCEEDED(rv)) mEnableAltSvc = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("altsvc.oe"))) {
+ rv = Preferences::GetBool(HTTP_PREF("altsvc.oe"), &cVar);
+ if (NS_SUCCEEDED(rv)) mEnableAltSvcOE = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("originextension"))) {
+ rv = Preferences::GetBool(HTTP_PREF("originextension"), &cVar);
+ if (NS_SUCCEEDED(rv)) mEnableOriginExtension = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http2.push-allowance"))) {
+ mSpdyPushAllowance = static_cast<uint32_t>(
+ clamped(StaticPrefs::network_http_http2_push_allowance(), 1024,
+ static_cast<int32_t>(ASpdySession::kInitialRwin)));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http2.pull-allowance"))) {
+ mSpdyPullAllowance = static_cast<uint32_t>(clamped(
+ StaticPrefs::network_http_http2_pull_allowance(), 1024, 0x7fffffff));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http2.default-concurrent"))) {
+ mDefaultSpdyConcurrent = static_cast<uint32_t>(std::max<int32_t>(
+ std::min<int32_t>(StaticPrefs::network_http_http2_default_concurrent(),
+ 9999),
+ 1));
+ }
+
+ // If http2.send-buffer-size is non-zero, the size to set the TCP
+ // sendbuffer to once the stream has surpassed this number of bytes uploaded
+ if (PREF_CHANGED(HTTP_PREF("http2.send-buffer-size"))) {
+ mSpdySendBufferSize = (uint32_t)clamped(
+ StaticPrefs::network_http_http2_send_buffer_size(), 1500, 0x7fffffff);
+ }
+
+ // The maximum amount of time to wait for socket transport to be
+ // established
+ if (PREF_CHANGED(HTTP_PREF("connection-timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("connection-timeout"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ // the pref is in seconds, but the variable is in milliseconds
+ mConnectTimeout = clamped(val, 1, 0xffff) * PR_MSEC_PER_SEC;
+ }
+ }
+
+ // The maximum amount of time to wait for a tls handshake to finish.
+ if (PREF_CHANGED(HTTP_PREF("tls-handshake-timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("tls-handshake-timeout"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ // the pref is in seconds, but the variable is in milliseconds
+ mTLSHandshakeTimeout = clamped(val, 1, 0xffff) * PR_MSEC_PER_SEC;
+ }
+ }
+
+ // The maximum number of current global half open sockets allowable
+ // for starting a new speculative connection.
+ if (PREF_CHANGED(HTTP_PREF("speculative-parallel-limit"))) {
+ rv = Preferences::GetInt(HTTP_PREF("speculative-parallel-limit"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mParallelSpeculativeConnectLimit = (uint32_t)clamped(val, 0, 1024);
+ }
+ }
+
+ // Whether or not to block requests for non head js/css items (e.g. media)
+ // while those elements load.
+ if (PREF_CHANGED(HTTP_PREF("rendering-critical-requests-prioritization"))) {
+ rv = Preferences::GetBool(
+ HTTP_PREF("rendering-critical-requests-prioritization"), &cVar);
+ if (NS_SUCCEEDED(rv)) mCriticalRequestPrioritization = cVar;
+ }
+
+ // on transition of network.http.diagnostics to true print
+ // a bunch of information to the console
+ if (pref && PREF_CHANGED(HTTP_PREF("diagnostics"))) {
+ rv = Preferences::GetBool(HTTP_PREF("diagnostics"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ if (mConnMgr) mConnMgr->PrintDiagnostics();
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max_response_header_size"))) {
+ rv = Preferences::GetInt(HTTP_PREF("max_response_header_size"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxHttpResponseHeaderSize = val;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.enable"))) {
+ rv = Preferences::GetBool(HTTP_PREF("throttle.enable"), &mThrottleEnabled);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_ENABLED,
+ static_cast<int32_t>(mThrottleEnabled));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.version"))) {
+ Unused << Preferences::GetInt(HTTP_PREF("throttle.version"), &val);
+ mThrottleVersion = (uint32_t)clamped(val, 1, 2);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.suspend-for"))) {
+ rv = Preferences::GetInt(HTTP_PREF("throttle.suspend-for"), &val);
+ mThrottleSuspendFor = (uint32_t)clamped(val, 0, 120000);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::THROTTLING_SUSPEND_FOR, mThrottleSuspendFor);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.resume-for"))) {
+ rv = Preferences::GetInt(HTTP_PREF("throttle.resume-for"), &val);
+ mThrottleResumeFor = (uint32_t)clamped(val, 0, 120000);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::THROTTLING_RESUME_FOR, mThrottleResumeFor);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.read-limit-bytes"))) {
+ rv = Preferences::GetInt(HTTP_PREF("throttle.read-limit-bytes"), &val);
+ mThrottleReadLimit = (uint32_t)clamped(val, 0, 500000);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::THROTTLING_READ_LIMIT, mThrottleReadLimit);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.read-interval-ms"))) {
+ rv = Preferences::GetInt(HTTP_PREF("throttle.read-interval-ms"), &val);
+ mThrottleReadInterval = (uint32_t)clamped(val, 0, 120000);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(
+ nsHttpConnectionMgr::THROTTLING_READ_INTERVAL, mThrottleReadInterval);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.hold-time-ms"))) {
+ rv = Preferences::GetInt(HTTP_PREF("throttle.hold-time-ms"), &val);
+ mThrottleHoldTime = (uint32_t)clamped(val, 0, 120000);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_HOLD_TIME,
+ mThrottleHoldTime);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("throttle.max-time-ms"))) {
+ rv = Preferences::GetInt(HTTP_PREF("throttle.max-time-ms"), &val);
+ mThrottleMaxTime = (uint32_t)clamped(val, 0, 120000);
+ if (NS_SUCCEEDED(rv) && mConnMgr) {
+ Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_MAX_TIME,
+ mThrottleMaxTime);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("send_window_size"))) {
+ Unused << Preferences::GetInt(HTTP_PREF("send_window_size"), &val);
+ mSendWindowSize = val >= 0 ? val : 0;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("on_click_priority"))) {
+ Unused << Preferences::GetBool(HTTP_PREF("on_click_priority"),
+ &mUrgentStartEnabled);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("tailing.enabled"))) {
+ Unused << Preferences::GetBool(HTTP_PREF("tailing.enabled"),
+ &mTailBlockingEnabled);
+ }
+ if (PREF_CHANGED(HTTP_PREF("tailing.delay-quantum"))) {
+ Unused << Preferences::GetInt(HTTP_PREF("tailing.delay-quantum"), &val);
+ mTailDelayQuantum = (uint32_t)clamped(val, 0, 60000);
+ }
+ if (PREF_CHANGED(HTTP_PREF("tailing.delay-quantum-after-domcontentloaded"))) {
+ Unused << Preferences::GetInt(
+ HTTP_PREF("tailing.delay-quantum-after-domcontentloaded"), &val);
+ mTailDelayQuantumAfterDCL = (uint32_t)clamped(val, 0, 60000);
+ }
+ if (PREF_CHANGED(HTTP_PREF("tailing.delay-max"))) {
+ Unused << Preferences::GetInt(HTTP_PREF("tailing.delay-max"), &val);
+ mTailDelayMax = (uint32_t)clamped(val, 0, 60000);
+ }
+ if (PREF_CHANGED(HTTP_PREF("tailing.total-max"))) {
+ Unused << Preferences::GetInt(HTTP_PREF("tailing.total-max"), &val);
+ mTailTotalMax = (uint32_t)clamped(val, 0, 60000);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("focused_window_transaction_ratio"))) {
+ float ratio = 0;
+ rv = Preferences::GetFloat(HTTP_PREF("focused_window_transaction_ratio"),
+ &ratio);
+ if (NS_SUCCEEDED(rv)) {
+ if (ratio > 0 && ratio < 1) {
+ mFocusedWindowTransactionRatio = ratio;
+ } else {
+ NS_WARNING("Wrong value for focused_window_transaction_ratio");
+ }
+ }
+ }
+
+ //
+ // INTL options
+ //
+
+ if (PREF_CHANGED(INTL_ACCEPT_LANGUAGES)) {
+ // We don't want to set the new accept languages here since
+ // this pref is a complex type and it may be racy with flushing
+ // string resources.
+ mAcceptLanguagesIsDirty = true;
+ }
+
+ //
+ // Tracking options
+ //
+
+ // Hint option
+ if (PREF_CHANGED(SAFE_HINT_HEADER_VALUE)) {
+ cVar = false;
+ rv = Preferences::GetBool(SAFE_HINT_HEADER_VALUE, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mSafeHintEnabled = cVar;
+ }
+ }
+
+ // toggle to true anytime a token bucket related pref is changed.. that
+ // includes telemetry and allow-experiments because of the abtest profile
+ bool requestTokenBucketUpdated = false;
+
+ // "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256" is the required h2 interop
+ // suite.
+
+ if (PREF_CHANGED(H2MANDATORY_SUITE)) {
+ cVar = false;
+ rv = Preferences::GetBool(H2MANDATORY_SUITE, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mH2MandatorySuiteEnabled = cVar;
+ }
+ }
+
+ // network.http.debug-observations
+ if (PREF_CHANGED("network.http.debug-observations")) {
+ cVar = false;
+ rv = Preferences::GetBool("network.http.debug-observations", &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mDebugObservations = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.enabled"))) {
+ rv = Preferences::GetBool(HTTP_PREF("pacing.requests.enabled"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestTokenBucketEnabled = cVar;
+ requestTokenBucketUpdated = true;
+ }
+ }
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.min-parallelism"))) {
+ rv =
+ Preferences::GetInt(HTTP_PREF("pacing.requests.min-parallelism"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestTokenBucketMinParallelism =
+ static_cast<uint16_t>(clamped(val, 1, 1024));
+ requestTokenBucketUpdated = true;
+ }
+ }
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.hz"))) {
+ rv = Preferences::GetInt(HTTP_PREF("pacing.requests.hz"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestTokenBucketHz = static_cast<uint32_t>(clamped(val, 1, 10000));
+ requestTokenBucketUpdated = true;
+ }
+ }
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.burst"))) {
+ rv = Preferences::GetInt(HTTP_PREF("pacing.requests.burst"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestTokenBucketBurst = val ? val : 1;
+ requestTokenBucketUpdated = true;
+ }
+ }
+ if (requestTokenBucketUpdated) {
+ MakeNewRequestTokenBucket();
+ }
+
+ // Keepalive values for initial and idle connections.
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.short_lived_connections"))) {
+ rv = Preferences::GetBool(
+ HTTP_PREF("tcp_keepalive.short_lived_connections"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar != mTCPKeepaliveShortLivedEnabled) {
+ mTCPKeepaliveShortLivedEnabled = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.short_lived_time"))) {
+ rv = Preferences::GetInt(HTTP_PREF("tcp_keepalive.short_lived_time"), &val);
+ if (NS_SUCCEEDED(rv) && val > 0) {
+ mTCPKeepaliveShortLivedTimeS = clamped(val, 1, 300); // Max 5 mins.
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.short_lived_idle_time"))) {
+ rv = Preferences::GetInt(HTTP_PREF("tcp_keepalive.short_lived_idle_time"),
+ &val);
+ if (NS_SUCCEEDED(rv) && val > 0) {
+ mTCPKeepaliveShortLivedIdleTimeS = clamped(val, 1, kMaxTCPKeepIdle);
+ }
+ }
+
+ // Keepalive values for Long-lived Connections.
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.long_lived_connections"))) {
+ rv = Preferences::GetBool(HTTP_PREF("tcp_keepalive.long_lived_connections"),
+ &cVar);
+ if (NS_SUCCEEDED(rv) && cVar != mTCPKeepaliveLongLivedEnabled) {
+ mTCPKeepaliveLongLivedEnabled = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.long_lived_idle_time"))) {
+ rv = Preferences::GetInt(HTTP_PREF("tcp_keepalive.long_lived_idle_time"),
+ &val);
+ if (NS_SUCCEEDED(rv) && val > 0) {
+ mTCPKeepaliveLongLivedIdleTimeS = clamped(val, 1, kMaxTCPKeepIdle);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("enforce-framing.http1")) ||
+ PREF_CHANGED(HTTP_PREF("enforce-framing.soft")) ||
+ PREF_CHANGED(HTTP_PREF("enforce-framing.strict_chunked_encoding"))) {
+ rv = Preferences::GetBool(HTTP_PREF("enforce-framing.http1"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ mEnforceH1Framing = FRAMECHECK_STRICT;
+ } else {
+ rv = Preferences::GetBool(
+ HTTP_PREF("enforce-framing.strict_chunked_encoding"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ mEnforceH1Framing = FRAMECHECK_STRICT_CHUNKED;
+ } else {
+ rv = Preferences::GetBool(HTTP_PREF("enforce-framing.soft"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ mEnforceH1Framing = FRAMECHECK_BARELY;
+ } else {
+ mEnforceH1Framing = FRAMECHECK_LAX;
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http2.default-hpack-buffer"))) {
+ mDefaultHpackBuffer =
+ StaticPrefs::network_http_http2_default_hpack_buffer();
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http3.default-qpack-table-size"))) {
+ rv = Preferences::GetInt(HTTP_PREF("http3.default-qpack-table-size"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mQpackTableSize = val;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http3.default-max-stream-blocked"))) {
+ rv = Preferences::GetInt(HTTP_PREF("http3.default-max-stream-blocked"),
+ &val);
+ if (NS_SUCCEEDED(rv)) {
+ mHttp3MaxBlockedStreams = clamped(val, 0, 0xffff);
+ }
+ }
+
+ const bool imageAcceptPrefChanged = PREF_CHANGED("image.http.accept") ||
+ PREF_CHANGED("image.avif.enabled") ||
+ PREF_CHANGED("image.jxl.enabled");
+
+ if (imageAcceptPrefChanged) {
+ nsAutoCString userSetImageAcceptHeader;
+
+ if (Preferences::HasUserValue("image.http.accept")) {
+ rv = Preferences::GetCString("image.http.accept",
+ userSetImageAcceptHeader);
+ if (NS_FAILED(rv)) {
+ userSetImageAcceptHeader.Truncate();
+ }
+ }
+
+ if (userSetImageAcceptHeader.IsEmpty()) {
+ mImageAcceptHeader.Assign(ImageAcceptHeader());
+ } else {
+ mImageAcceptHeader.Assign(userSetImageAcceptHeader);
+ }
+ }
+
+ if (PREF_CHANGED("network.http.accept") || imageAcceptPrefChanged) {
+ nsAutoCString userSetDocumentAcceptHeader;
+
+ if (Preferences::HasUserValue("network.http.accept")) {
+ rv = Preferences::GetCString("network.http.accept",
+ userSetDocumentAcceptHeader);
+ if (NS_FAILED(rv)) {
+ userSetDocumentAcceptHeader.Truncate();
+ }
+ }
+
+ if (userSetDocumentAcceptHeader.IsEmpty()) {
+ mDocumentAcceptHeader.Assign(DocumentAcceptHeader(mImageAcceptHeader));
+ } else {
+ mDocumentAcceptHeader.Assign(userSetDocumentAcceptHeader);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http3.alt-svc-mapping-for-testing"))) {
+ nsAutoCString altSvcMappings;
+ rv = Preferences::GetCString(HTTP_PREF("http3.alt-svc-mapping-for-testing"),
+ altSvcMappings);
+ if (NS_SUCCEEDED(rv)) {
+ for (const nsACString& tokenSubstring :
+ nsCCharSeparatedTokenizer(altSvcMappings, ',').ToRange()) {
+ nsAutoCString token{tokenSubstring};
+ int32_t index = token.Find(";");
+ if (index != kNotFound) {
+ mAltSvcMappingTemptativeMap.InsertOrUpdate(
+ Substring(token, 0, index),
+ MakeUnique<nsCString>(Substring(token, index + 1)));
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http3.enable_qlog"))) {
+ // Initialize the directory.
+ nsCOMPtr<nsIFile> qlogDir;
+ if (Preferences::GetBool(HTTP_PREF("http3.enable_qlog")) &&
+ !mHttp3QlogDir.IsEmpty() &&
+ NS_SUCCEEDED(NS_NewNativeLocalFile(mHttp3QlogDir, false,
+ getter_AddRefs(qlogDir)))) {
+ // Here we do main thread IO, but since this only happens
+ // when enabling a developer feature it's not a problem for users.
+ rv = qlogDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+ NS_WARNING("Creating qlog dir failed");
+ }
+ }
+ }
+
+ // Enable HTTP response timeout if TCP Keepalives are disabled.
+ mResponseTimeoutEnabled =
+ !mTCPKeepaliveShortLivedEnabled && !mTCPKeepaliveLongLivedEnabled;
+
+#undef PREF_CHANGED
+#undef MULTI_PREF_CHANGED
+}
+
+/**
+ * Allocates a C string into that contains a ISO 639 language list
+ * notated with HTTP "q" values for output with a HTTP Accept-Language
+ * header. Previous q values will be stripped because the order of
+ * the langs imply the q value. The q values are calculated by dividing
+ * 1.0 amongst the number of languages present.
+ *
+ * Ex: passing: "en, ja"
+ * returns: "en,ja;q=0.5"
+ *
+ * passing: "en, ja, fr_CA"
+ * returns: "en,ja;q=0.7,fr_CA;q=0.3"
+ */
+static nsresult PrepareAcceptLanguages(const char* i_AcceptLanguages,
+ nsACString& o_AcceptLanguages) {
+ if (!i_AcceptLanguages) return NS_OK;
+
+ const nsAutoCString ns_accept_languages(i_AcceptLanguages);
+ return rust_prepare_accept_languages(&ns_accept_languages,
+ &o_AcceptLanguages);
+}
+
+// Ensure that we've fetched the AcceptLanguages setting
+/* static */
+void nsHttpHandler::PresetAcceptLanguages() {
+ if (!gHttpHandler) {
+ RefPtr<nsHttpHandler> handler = nsHttpHandler::GetInstance();
+ Unused << handler.get();
+ }
+ [[maybe_unused]] nsresult rv = gHttpHandler->SetAcceptLanguages();
+}
+
+nsresult nsHttpHandler::SetAcceptLanguages() {
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIThread> mainThread;
+ nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Forward to the main thread synchronously.
+ SyncRunnable::DispatchToThread(
+ mainThread,
+ NS_NewRunnableFunction("nsHttpHandler::SetAcceptLanguages", [&rv]() {
+ rv = gHttpHandler->SetAcceptLanguages();
+ }));
+ return rv;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mAcceptLanguagesIsDirty = false;
+
+ nsAutoCString acceptLanguages;
+ Preferences::GetLocalizedCString(INTL_ACCEPT_LANGUAGES, acceptLanguages);
+
+ nsAutoCString buf;
+ nsresult rv = PrepareAcceptLanguages(acceptLanguages.get(), buf);
+ if (NS_SUCCEEDED(rv)) {
+ mAcceptLanguages.Assign(buf);
+ }
+ return rv;
+}
+
+nsresult nsHttpHandler::SetAcceptEncodings(const char* aAcceptEncodings,
+ bool isSecure) {
+ if (isSecure) {
+ mHttpsAcceptEncodings = aAcceptEncodings;
+ } else {
+ // use legacy list if a secure override is not specified
+ mHttpAcceptEncodings = aAcceptEncodings;
+ if (mHttpsAcceptEncodings.IsEmpty()) {
+ mHttpsAcceptEncodings = aAcceptEncodings;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpHandler, nsIHttpProtocolHandler,
+ nsIProxiedProtocolHandler, nsIProtocolHandler, nsIObserver,
+ nsISupportsWeakReference, nsISpeculativeConnect)
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsIProtocolHandler
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpHandler::GetScheme(nsACString& aScheme) {
+ aScheme.AssignLiteral("http");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ LOG(("nsHttpHandler::NewChannel\n"));
+
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(result);
+
+ // Verify that we have been given a valid scheme
+ if (!uri->SchemeIs("http") && !uri->SchemeIs("https")) {
+ NS_WARNING("Invalid URI scheme");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NewProxiedChannel(uri, nullptr, 0, nullptr, aLoadInfo, result);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::AllowPort(int32_t port, const char* scheme, bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsIProxiedProtocolHandler
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpHandler::SetupChannelInternal(
+ HttpBaseChannel* aChannel, nsIURI* uri, nsIProxyInfo* givenProxyInfo,
+ uint32_t proxyResolveFlags, nsIURI* proxyURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ RefPtr<HttpBaseChannel> httpChannel = aChannel;
+
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (givenProxyInfo) {
+ proxyInfo = do_QueryInterface(givenProxyInfo);
+ NS_ENSURE_ARG(proxyInfo);
+ }
+
+ uint32_t caps = mCapabilities;
+
+ uint64_t channelId;
+ nsresult rv = NewChannelId(channelId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = httpChannel->Init(uri, caps, proxyInfo, proxyResolveFlags, proxyURI,
+ channelId, aLoadInfo->GetExternalContentPolicyType(),
+ aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ httpChannel.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::NewProxiedChannel(nsIURI* uri, nsIProxyInfo* givenProxyInfo,
+ uint32_t proxyResolveFlags, nsIURI* proxyURI,
+ nsILoadInfo* aLoadInfo, nsIChannel** result) {
+ HttpBaseChannel* httpChannel;
+
+ LOG(("nsHttpHandler::NewProxiedChannel [proxyInfo=%p]\n", givenProxyInfo));
+
+ if (IsNeckoChild()) {
+ httpChannel = new HttpChannelChild();
+ } else {
+ // HACK: make sure PSM gets initialized on the main thread.
+ net_EnsurePSMInit();
+ httpChannel = new nsHttpChannel();
+ }
+
+ return SetupChannelInternal(httpChannel, uri, givenProxyInfo,
+ proxyResolveFlags, proxyURI, aLoadInfo, result);
+}
+
+nsresult nsHttpHandler::CreateTRRServiceChannel(
+ nsIURI* uri, nsIProxyInfo* givenProxyInfo, uint32_t proxyResolveFlags,
+ nsIURI* proxyURI, nsILoadInfo* aLoadInfo, nsIChannel** result) {
+ HttpBaseChannel* httpChannel = new TRRServiceChannel();
+
+ LOG(("nsHttpHandler::CreateTRRServiceChannel [proxyInfo=%p]\n",
+ givenProxyInfo));
+
+ return SetupChannelInternal(httpChannel, uri, givenProxyInfo,
+ proxyResolveFlags, proxyURI, aLoadInfo, result);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsIHttpProtocolHandler
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpHandler::GetUserAgent(nsACString& value) {
+ value = UserAgent(false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetRfpUserAgent(nsACString& value) {
+ value = UserAgent(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetAppName(nsACString& value) {
+ value = mLegacyAppName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetAppVersion(nsACString& value) {
+ value = mLegacyAppVersion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetPlatform(nsACString& value) {
+ value = mPlatform;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetOscpu(nsACString& value) {
+ value = mOscpu;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetMisc(nsACString& value) {
+ value = mMisc;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetAltSvcCacheKeys(nsTArray<nsCString>& value) {
+ return mAltSvcCache->GetAltSvcCacheKeys(value);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetAuthCacheKeys(nsTArray<nsCString>& aValues) {
+ mAuthCache.CollectKeys(aValues);
+ mPrivateAuthCache.CollectKeys(aValues);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpHandler::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("nsHttpHandler::Observe [topic=\"%s\"]\n", topic));
+
+ nsresult rv;
+ if (!strcmp(topic, "profile-change-net-teardown") ||
+ !strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ mHandlerActive = false;
+
+ // clear cache of all authentication credentials.
+ mAuthCache.ClearAll();
+ mPrivateAuthCache.ClearAll();
+ if (mWifiTickler) mWifiTickler->Cancel();
+
+ // Inform nsIOService that network is tearing down.
+ gIOService->SetHttpHandlerAlreadyShutingDown();
+
+ ShutdownConnectionManager();
+
+ // need to reset the session start time since cache validation may
+ // depend on this value.
+ mSessionStartTime = NowInSeconds();
+
+ // Since nsHttpHandler::Observe() is also called in socket process, we don't
+ // want to do telemetry twice.
+ if (XRE_IsParentProcess()) {
+ if (!StaticPrefs::privacy_donottrackheader_enabled()) {
+ Telemetry::Accumulate(Telemetry::DNT_USAGE, 2);
+ } else {
+ Telemetry::Accumulate(Telemetry::DNT_USAGE, 1);
+ }
+ }
+
+ mActivityDistributor = nullptr;
+ } else if (!strcmp(topic, "profile-change-net-restore")) {
+ // initialize connection manager
+ rv = InitConnectionMgr();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mAltSvcCache = MakeUnique<AltSvcCache>();
+ } else if (!strcmp(topic, "net:clear-active-logins")) {
+ mAuthCache.ClearAll();
+ mPrivateAuthCache.ClearAll();
+ } else if (!strcmp(topic, "net:cancel-all-connections")) {
+ if (mConnMgr) {
+ mConnMgr->AbortAndCloseAllConnections(0, nullptr);
+ }
+ } else if (!strcmp(topic, "net:prune-dead-connections")) {
+ if (mConnMgr) {
+ rv = mConnMgr->PruneDeadConnections();
+ if (NS_FAILED(rv)) {
+ LOG((" PruneDeadConnections failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ } else if (!strcmp(topic, "net:prune-all-connections")) {
+ if (mConnMgr) {
+ rv = mConnMgr->DoShiftReloadConnectionCleanup();
+ if (NS_FAILED(rv)) {
+ LOG((" DoShiftReloadConnectionCleanup failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ rv = mConnMgr->PruneDeadConnections();
+ if (NS_FAILED(rv)) {
+ LOG((" PruneDeadConnections failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+#if 0
+ } else if (!strcmp(topic, "net:failed-to-process-uri-content")) {
+ // nop right now - we used to cancel h1 pipelines based on this,
+ // but those are no longer implemented
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(subject);
+#endif
+ } else if (!strcmp(topic, "last-pb-context-exited")) {
+ mPrivateAuthCache.ClearAll();
+ if (mAltSvcCache) {
+ mAltSvcCache->ClearAltServiceMappings();
+ }
+ nsCORSListenerProxy::ClearPrivateBrowsingCache();
+ } else if (!strcmp(topic, "browser:purge-session-history")) {
+ if (mConnMgr) {
+ Unused << mConnMgr->ClearConnectionHistory();
+ }
+ if (mAltSvcCache) {
+ mAltSvcCache->ClearAltServiceMappings();
+ }
+ } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ if (mConnMgr) {
+ rv = mConnMgr->PruneDeadConnections();
+ if (NS_FAILED(rv)) {
+ LOG((" PruneDeadConnections failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ rv = mConnMgr->VerifyTraffic();
+ if (NS_FAILED(rv)) {
+ LOG((" VerifyTraffic failed (%08x)\n", static_cast<uint32_t>(rv)));
+ }
+ }
+ } else if (!strcmp(topic, "application-background")) {
+ // going to the background on android means we should close
+ // down idle connections for power conservation
+ if (mConnMgr) {
+ rv = mConnMgr->DoShiftReloadConnectionCleanup();
+ if (NS_FAILED(rv)) {
+ LOG((" DoShiftReloadConnectionCleanup failed (%08x)\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ } else if (!strcmp(topic, "net:current-browser-id")) {
+ // The window id will be updated by HttpConnectionMgrParent.
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(subject);
+ MOZ_RELEASE_ASSERT(wrapper);
+
+ uint64_t id = 0;
+ wrapper->GetData(&id);
+ MOZ_ASSERT(id);
+
+ static uint64_t sCurrentBrowserId = 0;
+ if (sCurrentBrowserId != id) {
+ sCurrentBrowserId = id;
+ if (mConnMgr) {
+ mConnMgr->UpdateCurrentBrowserId(sCurrentBrowserId);
+ }
+ }
+ }
+ } else if (!strcmp(topic, "intl:app-locales-changed")) {
+ // If the locale changed, there's a chance the accept language did too
+ mAcceptLanguagesIsDirty = true;
+ } else if (!strcmp(topic, "network:captive-portal-connectivity")) {
+ nsAutoCString data8 = NS_ConvertUTF16toUTF8(data);
+ mThroughCaptivePortal = data8.EqualsLiteral("captive");
+ } else if (!strcmp(topic, "network:reset-http3-excluded-list")) {
+ MutexAutoLock lock(mHttpExclusionLock);
+ mExcludedHttp3Origins.Clear();
+ } else if (!strcmp(topic, "network:socket-process-crashed")) {
+ ShutdownConnectionManager();
+ mConnMgr = nullptr;
+ Unused << InitConnectionMgr();
+ }
+
+ return NS_OK;
+}
+
+// nsISpeculativeConnect
+
+nsresult nsHttpHandler::SpeculativeConnectInternal(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ Maybe<OriginAttributes>&& aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool anonymous) {
+ if (IsNeckoChild()) {
+ gNeckoChild->SendSpeculativeConnect(
+ aURI, aPrincipal, std::move(aOriginAttributes), anonymous);
+ return NS_OK;
+ }
+
+ if (!mHandlerActive) return NS_OK;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ bool isStsHost = false;
+ if (!sss) return NS_OK;
+
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(aCallbacks);
+ OriginAttributes originAttributes;
+ // If the principal is given, we use the originAttributes from this
+ // principal. Otherwise, we use the originAttributes from the loadContext.
+ if (aOriginAttributes) {
+ originAttributes = std::move(aOriginAttributes.ref());
+ } else if (aPrincipal) {
+ originAttributes = aPrincipal->OriginAttributesRef();
+ StoragePrincipalHelper::UpdateOriginAttributesForNetworkState(
+ aURI, originAttributes);
+ } else if (loadContext) {
+ loadContext->GetOriginAttributes(originAttributes);
+ StoragePrincipalHelper::UpdateOriginAttributesForNetworkState(
+ aURI, originAttributes);
+ }
+
+ nsCOMPtr<nsIURI> clone;
+ if (NS_SUCCEEDED(sss->IsSecureURI(aURI, originAttributes, &isStsHost)) &&
+ isStsHost) {
+ if (NS_SUCCEEDED(NS_GetSecureUpgradedURI(aURI, getter_AddRefs(clone)))) {
+ aURI = clone.get();
+ // (NOTE: We better make sure |clone| stays alive until the end
+ // of the function now, since our aURI arg now points to it!)
+ }
+ }
+
+ nsAutoCString scheme;
+ nsresult rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ // If this is HTTPS, make sure PSM is initialized as the channel
+ // creation path may have been bypassed
+ if (scheme.EqualsLiteral("https")) {
+ if (!IsNeckoChild()) {
+ // make sure PSM gets initialized on the main thread.
+ net_EnsurePSMInit();
+ }
+ }
+ // Ensure that this is HTTP or HTTPS, otherwise we don't do preconnect here
+ else if (!scheme.EqualsLiteral("http")) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoCString host;
+ rv = aURI->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t port = -1;
+ rv = aURI->GetPort(&port);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString username;
+ aURI->GetUsername(username);
+
+ RefPtr<nsHttpConnectionInfo> ci =
+ new nsHttpConnectionInfo(host, port, ""_ns, username, nullptr,
+ originAttributes, aURI->SchemeIs("https"));
+ ci->SetAnonymous(anonymous);
+ if (originAttributes.mPrivateBrowsingId > 0) {
+ ci->SetPrivate(true);
+ }
+
+ if (mDebugObservations) {
+ // this is basically used for test coverage of an otherwise 'hintable'
+ // feature
+
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ // This is used to test if the speculative connection has the right
+ // connection info.
+ nsPrintfCString debugHashKey("%s", ci->HashKey().get());
+ obsService->NotifyObservers(nullptr, "speculative-connect-request",
+ NS_ConvertUTF8toUTF16(debugHashKey).get());
+ for (auto* cp :
+ dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
+ PNeckoParent* neckoParent =
+ SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ Unused << neckoParent->SendSpeculativeConnectRequest();
+ }
+ }
+ }
+
+ return SpeculativeConnect(ci, aCallbacks);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::SpeculativeConnect(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ nsIInterfaceRequestor* aCallbacks,
+ bool aAnonymous) {
+ return SpeculativeConnectInternal(aURI, aPrincipal, Nothing(), aCallbacks,
+ aAnonymous);
+}
+
+NS_IMETHODIMP nsHttpHandler::SpeculativeConnectWithOriginAttributes(
+ nsIURI* aURI, JS::Handle<JS::Value> aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous, JSContext* aCx) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ SpeculativeConnectWithOriginAttributesNative(aURI, std::move(attrs),
+ aCallbacks, aAnonymous);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsHttpHandler::SpeculativeConnectWithOriginAttributesNative(
+ nsIURI* aURI, OriginAttributes&& aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous) {
+ Maybe<OriginAttributes> originAttributes;
+ originAttributes.emplace(aOriginAttributes);
+ Unused << SpeculativeConnectInternal(
+ aURI, nullptr, std::move(originAttributes), aCallbacks, aAnonymous);
+}
+
+void nsHttpHandler::TickleWifi(nsIInterfaceRequestor* cb) {
+ if (!cb || !mWifiTickler) return;
+
+ // If B2G requires a similar mechanism nsINetworkManager, currently only avail
+ // on B2G, contains the necessary information on wifi and gateway
+
+ nsCOMPtr<nsIDOMWindow> domWindow = do_GetInterface(cb);
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow = do_QueryInterface(domWindow);
+ if (!piWindow) return;
+
+ RefPtr<dom::Navigator> navigator = piWindow->GetNavigator();
+ if (!navigator) return;
+
+ RefPtr<dom::network::Connection> networkProperties =
+ navigator->GetConnection(IgnoreErrors());
+ if (!networkProperties) return;
+
+ uint32_t gwAddress = networkProperties->GetDhcpGateway();
+ bool isWifi = networkProperties->GetIsWifi();
+ if (!gwAddress || !isWifi) return;
+
+ mWifiTickler->SetIPV4Address(gwAddress);
+ mWifiTickler->Tickle();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpsHandler implementation
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpsHandler, nsIHttpProtocolHandler,
+ nsIProxiedProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference, nsISpeculativeConnect)
+
+nsresult nsHttpsHandler::Init() {
+ nsCOMPtr<nsIProtocolHandler> httpHandler(
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http"));
+ MOZ_ASSERT(httpHandler.get() != nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::GetScheme(nsACString& aScheme) {
+ aScheme.AssignLiteral("https");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** _retval) {
+ MOZ_ASSERT(gHttpHandler);
+ if (!gHttpHandler) return NS_ERROR_UNEXPECTED;
+ return gHttpHandler->NewChannel(aURI, aLoadInfo, _retval);
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::AllowPort(int32_t aPort, const char* aScheme, bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::EnsureHSTSDataReadyNative(
+ RefPtr<mozilla::net::HSTSDataCallbackWrapper> aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), "http://example.com");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool shouldUpgrade = false;
+ bool willCallback = false;
+ OriginAttributes originAttributes;
+ auto func = [callback(aCallback)](bool aResult, nsresult aStatus) {
+ callback->DoCallback(aResult);
+ };
+ rv = NS_ShouldSecureUpgrade(uri, nullptr, nullptr, false, originAttributes,
+ shouldUpgrade, std::move(func), willCallback);
+ if (NS_FAILED(rv) || !willCallback) {
+ aCallback->DoCallback(false);
+ return rv;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::EnsureHSTSDataReady(JSContext* aCx, Promise** aPromise) {
+ if (NS_WARN_IF(!aCx)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!globalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(globalObject, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ if (IsNeckoChild()) {
+ gNeckoChild->SendEnsureHSTSData()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [promise(promise)](
+ NeckoChild::EnsureHSTSDataPromise::ResolveOrRejectValue&& aResult) {
+ if (aResult.IsResolve()) {
+ promise->MaybeResolve(aResult.ResolveValue());
+ } else {
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ }
+ });
+ promise.forget(aPromise);
+ return NS_OK;
+ }
+
+ auto callback = [promise(promise)](bool aResult) {
+ promise->MaybeResolve(aResult);
+ };
+
+ RefPtr<HSTSDataCallbackWrapper> wrapper =
+ new HSTSDataCallbackWrapper(std::move(callback));
+ promise.forget(aPromise);
+ return EnsureHSTSDataReadyNative(wrapper);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::ClearCORSPreflightCache() {
+ nsCORSListenerProxy::ClearCache();
+ return NS_OK;
+}
+
+void nsHttpHandler::ShutdownConnectionManager() {
+ // ensure connection manager is shutdown
+ if (mConnMgr) {
+ nsresult rv = mConnMgr->Shutdown();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpHandler::ShutdownConnectionManager\n"
+ " failed to shutdown connection manager\n"));
+ }
+ }
+}
+
+nsresult nsHttpHandler::NewChannelId(uint64_t& channelId) {
+ channelId =
+ // channelId is sometimes passed to JavaScript code (e.g. devtools),
+ // and since on Linux PID_MAX_LIMIT is 2^22 we cannot
+ // shift PID more than 31 bits left. Otherwise resulting values
+ // will be exceed safe JavaScript integer range.
+ ((static_cast<uint64_t>(mProcessId) << 31) & 0xFFFFFFFF80000000LL) |
+ mNextChannelId++;
+ return NS_OK;
+}
+
+void nsHttpHandler::NotifyActiveTabLoadOptimization() {
+ SetLastActiveTabLoadOptimizationHit(TimeStamp::Now());
+}
+
+TimeStamp nsHttpHandler::GetLastActiveTabLoadOptimizationHit() {
+ MutexAutoLock lock(mLastActiveTabLoadOptimizationLock);
+
+ return mLastActiveTabLoadOptimizationHit;
+}
+
+void nsHttpHandler::SetLastActiveTabLoadOptimizationHit(TimeStamp const& when) {
+ MutexAutoLock lock(mLastActiveTabLoadOptimizationLock);
+
+ if (mLastActiveTabLoadOptimizationHit.IsNull() ||
+ (!when.IsNull() && mLastActiveTabLoadOptimizationHit < when)) {
+ mLastActiveTabLoadOptimizationHit = when;
+ }
+}
+
+bool nsHttpHandler::IsBeforeLastActiveTabLoadOptimization(
+ TimeStamp const& when) {
+ MutexAutoLock lock(mLastActiveTabLoadOptimizationLock);
+
+ return !mLastActiveTabLoadOptimizationHit.IsNull() &&
+ when <= mLastActiveTabLoadOptimizationHit;
+}
+
+void nsHttpHandler::ExcludeHttp2OrHttp3Internal(
+ const nsHttpConnectionInfo* ci) {
+ LOG(("nsHttpHandler::ExcludeHttp2OrHttp3Internal ci=%s",
+ ci->HashKey().get()));
+ // The excluded list needs to be stayed synced between parent process and
+ // socket process, so we send this information to the parent process here.
+ if (XRE_IsSocketProcess()) {
+ MOZ_ASSERT(OnSocketThread());
+
+ RefPtr<nsHttpConnectionInfo> cinfo = ci->Clone();
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsHttpHandler::ExcludeHttp2OrHttp3Internal",
+ [cinfo{std::move(cinfo)}]() {
+ HttpConnectionInfoCloneArgs connInfoArgs;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(cinfo,
+ connInfoArgs);
+ Unused << SocketProcessChild::GetSingleton()->SendExcludeHttp2OrHttp3(
+ connInfoArgs);
+ }));
+ }
+
+ MOZ_ASSERT_IF(nsIOService::UseSocketProcess() && XRE_IsParentProcess(),
+ NS_IsMainThread());
+ MOZ_ASSERT_IF(!nsIOService::UseSocketProcess(), OnSocketThread());
+
+ if (ci->IsHttp3()) {
+ if (!mExcludedHttp3Origins.Contains(ci->GetRoutedHost())) {
+ MutexAutoLock lock(mHttpExclusionLock);
+ mExcludedHttp3Origins.Insert(ci->GetRoutedHost());
+ }
+ mConnMgr->ExcludeHttp3(ci);
+ } else {
+ if (!mExcludedHttp2Origins.Contains(ci->GetOrigin())) {
+ MutexAutoLock lock(mHttpExclusionLock);
+ mExcludedHttp2Origins.Insert(ci->GetOrigin());
+ }
+ mConnMgr->ExcludeHttp2(ci);
+ }
+}
+
+void nsHttpHandler::ExcludeHttp2(const nsHttpConnectionInfo* ci) {
+ ExcludeHttp2OrHttp3Internal(ci);
+}
+
+bool nsHttpHandler::IsHttp2Excluded(const nsHttpConnectionInfo* ci) {
+ MutexAutoLock lock(mHttpExclusionLock);
+ return mExcludedHttp2Origins.Contains(ci->GetOrigin());
+}
+
+void nsHttpHandler::ExcludeHttp3(const nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(ci->IsHttp3());
+ ExcludeHttp2OrHttp3Internal(ci);
+}
+
+bool nsHttpHandler::IsHttp3Excluded(const nsACString& aRoutedHost) {
+ MutexAutoLock lock(mHttpExclusionLock);
+ return mExcludedHttp3Origins.Contains(aRoutedHost);
+}
+
+HttpTrafficAnalyzer* nsHttpHandler::GetHttpTrafficAnalyzer() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!StaticPrefs::network_traffic_analyzer_enabled()) {
+ return nullptr;
+ }
+
+ return &mHttpTrafficAnalyzer;
+}
+
+bool nsHttpHandler::IsHttp3Enabled() {
+ static const uint32_t TLS3_PREF_VALUE = 4;
+
+ return StaticPrefs::network_http_http3_enable() &&
+ (StaticPrefs::security_tls_version_max() >= TLS3_PREF_VALUE);
+}
+
+bool nsHttpHandler::IsHttp3VersionSupported(const nsACString& version) {
+ if (!StaticPrefs::network_http_http3_support_version1() &&
+ version.EqualsLiteral("h3")) {
+ return false;
+ }
+ for (uint32_t i = 0; i < kHttp3VersionCount; i++) {
+ if (version.Equals(kHttp3Versions[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool nsHttpHandler::IsHttp3SupportedByServer(
+ nsHttpResponseHead* aResponseHead) {
+ if ((aResponseHead->Version() != HttpVersion::v2_0) ||
+ (aResponseHead->Status() >= 500) || (aResponseHead->Status() == 421)) {
+ return false;
+ }
+
+ nsAutoCString altSvc;
+ Unused << aResponseHead->GetHeader(nsHttp::Alternate_Service, altSvc);
+ if (altSvc.IsEmpty() || !nsHttp::IsReasonableHeaderValue(altSvc)) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < kHttp3VersionCount; i++) {
+ nsAutoCString value(kHttp3Versions[i]);
+ value.Append("="_ns);
+ if (strstr(altSvc.get(), value.get())) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult nsHttpHandler::InitiateTransaction(HttpTransactionShell* aTrans,
+ int32_t aPriority) {
+ return mConnMgr->AddTransaction(aTrans, aPriority);
+}
+nsresult nsHttpHandler::InitiateTransactionWithStickyConn(
+ HttpTransactionShell* aTrans, int32_t aPriority,
+ HttpTransactionShell* aTransWithStickyConn) {
+ return mConnMgr->AddTransactionWithStickyConn(aTrans, aPriority,
+ aTransWithStickyConn);
+}
+
+nsresult nsHttpHandler::RescheduleTransaction(HttpTransactionShell* trans,
+ int32_t priority) {
+ return mConnMgr->RescheduleTransaction(trans, priority);
+}
+
+void nsHttpHandler::UpdateClassOfServiceOnTransaction(
+ HttpTransactionShell* trans, const ClassOfService& classOfService) {
+ mConnMgr->UpdateClassOfServiceOnTransaction(trans, classOfService);
+}
+
+nsresult nsHttpHandler::CancelTransaction(HttpTransactionShell* trans,
+ nsresult reason) {
+ return mConnMgr->CancelTransaction(trans, reason);
+}
+
+void nsHttpHandler::AddHttpChannel(uint64_t aId, nsISupports* aChannel) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsWeakPtr channel(do_GetWeakReference(aChannel));
+ mIDToHttpChannelMap.InsertOrUpdate(aId, std::move(channel));
+}
+
+void nsHttpHandler::RemoveHttpChannel(uint64_t aId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> idleRunnable(NewCancelableRunnableMethod<uint64_t>(
+ "nsHttpHandler::RemoveHttpChannel", this,
+ &nsHttpHandler::RemoveHttpChannel, aId));
+
+ NS_DispatchToMainThreadQueue(do_AddRef(idleRunnable),
+ EventQueuePriority::Idle);
+ return;
+ }
+
+ auto entry = mIDToHttpChannelMap.Lookup(aId);
+ if (entry) {
+ entry.Remove();
+ }
+}
+
+nsWeakPtr nsHttpHandler::GetWeakHttpChannel(uint64_t aId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return mIDToHttpChannelMap.Get(aId);
+}
+
+nsresult nsHttpHandler::CompleteUpgrade(
+ HttpTransactionShell* aTrans, nsIHttpUpgradeListener* aUpgradeListener) {
+ return mConnMgr->CompleteUpgrade(aTrans, aUpgradeListener);
+}
+
+nsresult nsHttpHandler::DoShiftReloadConnectionCleanupWithConnInfo(
+ nsHttpConnectionInfo* aCi) {
+ MOZ_ASSERT(aCi);
+ return mConnMgr->DoShiftReloadConnectionCleanupWithConnInfo(aCi);
+}
+
+void nsHttpHandler::ClearHostMapping(nsHttpConnectionInfo* aConnInfo) {
+ if (XRE_IsSocketProcess()) {
+ AltServiceChild::ClearHostMapping(aConnInfo);
+ return;
+ }
+
+ AltServiceCache()->ClearHostMapping(aConnInfo);
+}
+
+void nsHttpHandler::SetHttpHandlerInitArgs(const HttpHandlerInitArgs& aArgs) {
+ MOZ_ASSERT(XRE_IsSocketProcess());
+ mLegacyAppName = aArgs.mLegacyAppName();
+ mLegacyAppVersion = aArgs.mLegacyAppVersion();
+ mPlatform = aArgs.mPlatform();
+ mOscpu = aArgs.mOscpu();
+ mMisc = aArgs.mMisc();
+ mProduct = aArgs.mProduct();
+ mProductSub = aArgs.mProductSub();
+ mAppName = aArgs.mAppName();
+ mAppVersion = aArgs.mAppVersion();
+ mCompatFirefox = aArgs.mCompatFirefox();
+ mCompatDevice = aArgs.mCompatDevice();
+ mDeviceModelId = aArgs.mDeviceModelId();
+}
+
+void nsHttpHandler::SetDeviceModelId(const nsACString& aModelId) {
+ MOZ_ASSERT(XRE_IsSocketProcess());
+ mDeviceModelId = aModelId;
+}
+
+void nsHttpHandler::MaybeAddAltSvcForTesting(
+ nsIURI* aUri, const nsACString& aUsername, bool aPrivateBrowsing,
+ nsIInterfaceRequestor* aCallbacks,
+ const OriginAttributes& aOriginAttributes) {
+ if (!IsHttp3Enabled() || mAltSvcMappingTemptativeMap.IsEmpty()) {
+ return;
+ }
+
+ bool isHttps = false;
+ if (NS_FAILED(aUri->SchemeIs("https", &isHttps)) || !isHttps) {
+ // Only set for HTTPS.
+ return;
+ }
+
+ nsAutoCString originHost;
+ if (NS_FAILED(aUri->GetAsciiHost(originHost))) {
+ return;
+ }
+
+ nsCString* map = mAltSvcMappingTemptativeMap.Get(originHost);
+ if (map) {
+ int32_t originPort = 80;
+ aUri->GetPort(&originPort);
+ LOG(("nsHttpHandler::MaybeAddAltSvcForTesting for %s map: %s",
+ originHost.get(), PromiseFlatCString(*map).get()));
+ AltSvcMapping::ProcessHeader(
+ *map, nsCString("https"), originHost, originPort, aUsername,
+ aPrivateBrowsing, aCallbacks, nullptr, 0, aOriginAttributes, true);
+ }
+}
+
+bool nsHttpHandler::UseHTTPSRRAsAltSvcEnabled() const {
+ return StaticPrefs::network_dns_use_https_rr_as_altsvc();
+}
+
+bool nsHttpHandler::EchConfigEnabled(bool aIsHttp3) const {
+ if (!aIsHttp3) {
+ return StaticPrefs::network_dns_echconfig_enabled();
+ }
+
+ return StaticPrefs::network_dns_echconfig_enabled() &&
+ StaticPrefs::network_dns_http3_echconfig_enabled();
+}
+
+bool nsHttpHandler::FallbackToOriginIfConfigsAreECHAndAllFailed() const {
+ return StaticPrefs::
+ network_dns_echconfig_fallback_to_origin_when_all_failed();
+}
+
+void nsHttpHandler::ExcludeHTTPSRRHost(const nsACString& aHost) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mExcludedHostsForHTTPSRRUpgrade.Insert(aHost);
+}
+
+bool nsHttpHandler::IsHostExcludedForHTTPSRR(const nsACString& aHost) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return mExcludedHostsForHTTPSRRUpgrade.Contains(aHost);
+}
+
+void nsHttpHandler::Exclude0RttTcp(const nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!StaticPrefs::network_http_early_data_disable_on_error() ||
+ (mExcluded0RttTcpOrigins.Count() >=
+ StaticPrefs::network_http_early_data_max_error())) {
+ return;
+ }
+
+ mExcluded0RttTcpOrigins.Insert(ci->GetOrigin());
+}
+
+bool nsHttpHandler::Is0RttTcpExcluded(const nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(OnSocketThread());
+ if (!StaticPrefs::network_http_early_data_disable_on_error()) {
+ return false;
+ }
+
+ if (mExcluded0RttTcpOrigins.Count() >=
+ StaticPrefs::network_http_early_data_max_error()) {
+ return true;
+ }
+
+ return mExcluded0RttTcpOrigins.Contains(ci->GetOrigin());
+}
+
+bool nsHttpHandler::HttpActivityDistributorActivated() {
+ if (!mActivityDistributor) {
+ return false;
+ }
+
+ return mActivityDistributor->Activated();
+}
+
+void nsHttpHandler::ObserveHttpActivityWithArgs(
+ const HttpActivityArgs& aArgs, uint32_t aActivityType,
+ uint32_t aActivitySubtype, PRTime aTimestamp, uint64_t aExtraSizeData,
+ const nsACString& aExtraStringData) {
+ if (!HttpActivityDistributorActivated()) {
+ return;
+ }
+
+ if (aActivitySubtype == NS_HTTP_ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER &&
+ !mActivityDistributor->ObserveProxyResponseEnabled()) {
+ return;
+ }
+
+ if (aActivityType == NS_ACTIVITY_TYPE_HTTP_CONNECTION &&
+ !mActivityDistributor->ObserveConnectionEnabled()) {
+ return;
+ }
+
+ Unused << mActivityDistributor->ObserveActivityWithArgs(
+ aArgs, aActivityType, aActivitySubtype, aTimestamp, aExtraSizeData,
+ aExtraStringData);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h
new file mode 100644
index 0000000000..cf745347ee
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -0,0 +1,918 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpHandler_h__
+#define nsHttpHandler_h__
+
+#include <functional>
+
+#include "nsHttp.h"
+#include "nsHttpAuthCache.h"
+#include "nsHttpConnectionMgr.h"
+#include "AlternateServices.h"
+#include "ASpdySession.h"
+#include "HttpTrafficAnalyzer.h"
+
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+
+#include "nsIHttpProtocolHandler.h"
+#include "nsIObserver.h"
+#include "nsISpeculativeConnect.h"
+#include "nsTHashMap.h"
+#include "nsTHashSet.h"
+
+#ifdef DEBUG
+# include "nsIOService.h"
+#endif
+
+// XXX These includes can be replaced by forward declarations by moving the On*
+// method implementations to the cpp file
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsSocketTransportService2.h"
+
+class nsIHttpActivityDistributor;
+class nsIHttpUpgradeListener;
+class nsIPrefBranch;
+class nsICancelable;
+class nsICookieService;
+class nsIIOService;
+class nsIRequestContextService;
+class nsISiteSecurityService;
+class nsIStreamConverterService;
+
+namespace mozilla::net {
+
+class ATokenBucketEvent;
+class EventTokenBucket;
+class Tickler;
+class nsHttpConnection;
+class nsHttpConnectionInfo;
+class HttpBaseChannel;
+class HttpHandlerInitArgs;
+class HttpTransactionShell;
+class AltSvcMapping;
+class DNSUtils;
+class TRRServiceChannel;
+class SocketProcessChild;
+
+/*
+ * FRAMECHECK_LAX - no check
+ * FRAMECHECK_BARELY - allows:
+ * 1) that chunk-encoding does not have the last 0-size
+ * chunk. So, if a chunked-encoded transfer ends on exactly
+ * a chunk boundary we consider that fine. This will allows
+ * us to accept buggy servers that do not send the last
+ * chunk. It will make us not detect a certain amount of
+ * cut-offs.
+ * 2) When receiving a gzipped response, we consider a
+ * gzip stream that doesn't end fine according to the gzip
+ * decompressing state machine to be a partial transfer.
+ * If a gzipped transfer ends fine according to the
+ * decompressor, we do not check for size unalignments.
+ * This allows to allow HTTP gzipped responses where the
+ * Content-Length is not the same as the actual contents.
+ * 3) When receiving HTTP that isn't
+ * content-encoded/compressed (like in case 2) and not
+ * chunked (like in case 1), perform the size comparison
+ * between Content-Length: and the actual size received
+ * and consider a mismatch to mean a
+ * NS_ERROR_NET_PARTIAL_TRANSFER error.
+ * FRAMECHECK_STRICT_CHUNKED - This is the same as FRAMECHECK_BARELY only we
+ * enforce that the last 0-size chunk is received
+ * in case 1).
+ * FRAMECHECK_STRICT - we also do not allow case 2) and 3) from
+ * FRAMECHECK_BARELY.
+ */
+enum FrameCheckLevel {
+ FRAMECHECK_LAX,
+ FRAMECHECK_BARELY,
+ FRAMECHECK_STRICT_CHUNKED,
+ FRAMECHECK_STRICT
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler - protocol handler for HTTP and HTTPS
+//-----------------------------------------------------------------------------
+
+class nsHttpHandler final : public nsIHttpProtocolHandler,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsISpeculativeConnect {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIPROXIEDPROTOCOLHANDLER
+ NS_DECL_NSIHTTPPROTOCOLHANDLER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSISPECULATIVECONNECT
+
+ static already_AddRefed<nsHttpHandler> GetInstance();
+
+ [[nodiscard]] nsresult AddStandardRequestHeaders(
+ nsHttpRequestHead*, bool isSecure,
+ ExtContentPolicyType aContentPolicyType,
+ bool aShouldResistFingerprinting);
+ [[nodiscard]] nsresult AddConnectionHeader(nsHttpRequestHead*, uint32_t caps);
+ bool IsAcceptableEncoding(const char* encoding, bool isSecure);
+
+ const nsCString& UserAgent(bool aShouldResistFingerprinting);
+
+ enum HttpVersion HttpVersion() { return mHttpVersion; }
+ enum HttpVersion ProxyHttpVersion() { return mProxyHttpVersion; }
+ uint8_t RedirectionLimit() { return mRedirectionLimit; }
+ PRIntervalTime IdleTimeout() { return mIdleTimeout; }
+ PRIntervalTime SpdyTimeout() { return mSpdyTimeout; }
+ PRIntervalTime ResponseTimeout() {
+ return mResponseTimeoutEnabled ? mResponseTimeout : 0;
+ }
+ PRIntervalTime ResponseTimeoutEnabled() { return mResponseTimeoutEnabled; }
+ uint32_t NetworkChangedTimeout() { return mNetworkChangedTimeout; }
+ uint16_t MaxRequestAttempts() { return mMaxRequestAttempts; }
+ const nsCString& DefaultSocketType() { return mDefaultSocketType; }
+ uint32_t PhishyUserPassLength() { return mPhishyUserPassLength; }
+ uint8_t GetQoSBits() { return mQoSBits; }
+ uint16_t GetIdleSynTimeout() { return mIdleSynTimeout; }
+ uint16_t GetFallbackSynTimeout() { return mFallbackSynTimeout; }
+ bool FastFallbackToIPv4() { return mFastFallbackToIPv4; }
+ uint32_t MaxSocketCount();
+ bool EnforceAssocReq() { return mEnforceAssocReq; }
+
+ bool IsPersistentHttpsCachingEnabled() {
+ return mEnablePersistentHttpsCaching;
+ }
+
+ uint32_t SpdySendingChunkSize() { return mSpdySendingChunkSize; }
+ uint32_t SpdySendBufferSize() { return mSpdySendBufferSize; }
+ uint32_t SpdyPushAllowance() { return mSpdyPushAllowance; }
+ uint32_t SpdyPullAllowance() { return mSpdyPullAllowance; }
+ uint32_t DefaultSpdyConcurrent() { return mDefaultSpdyConcurrent; }
+ PRIntervalTime SpdyPingThreshold() { return mSpdyPingThreshold; }
+ PRIntervalTime SpdyPingTimeout() { return mSpdyPingTimeout; }
+ bool AllowAltSvc() { return mEnableAltSvc; }
+ bool AllowAltSvcOE() { return mEnableAltSvcOE; }
+ bool AllowOriginExtension() { return mEnableOriginExtension; }
+ uint32_t ConnectTimeout() { return mConnectTimeout; }
+ uint32_t TLSHandshakeTimeout() { return mTLSHandshakeTimeout; }
+ uint32_t ParallelSpeculativeConnectLimit() {
+ return mParallelSpeculativeConnectLimit;
+ }
+ bool CriticalRequestPrioritization() {
+ return mCriticalRequestPrioritization;
+ }
+
+ uint32_t MaxConnectionsPerOrigin() {
+ return mMaxPersistentConnectionsPerServer;
+ }
+ bool UseRequestTokenBucket() { return mRequestTokenBucketEnabled; }
+ uint16_t RequestTokenBucketMinParallelism() {
+ return mRequestTokenBucketMinParallelism;
+ }
+ uint32_t RequestTokenBucketHz() { return mRequestTokenBucketHz; }
+ uint32_t RequestTokenBucketBurst() { return mRequestTokenBucketBurst; }
+
+ bool PromptTempRedirect() { return mPromptTempRedirect; }
+ bool IsUrgentStartEnabled() { return mUrgentStartEnabled; }
+ bool IsTailBlockingEnabled() { return mTailBlockingEnabled; }
+ uint32_t TailBlockingDelayQuantum(bool aAfterDOMContentLoaded) {
+ return aAfterDOMContentLoaded ? mTailDelayQuantumAfterDCL
+ : mTailDelayQuantum;
+ }
+ uint32_t TailBlockingDelayMax() { return mTailDelayMax; }
+ uint32_t TailBlockingTotalMax() { return mTailTotalMax; }
+
+ uint32_t ThrottlingReadLimit() {
+ return mThrottleVersion == 1 ? 0 : mThrottleReadLimit;
+ }
+ int32_t SendWindowSize() { return mSendWindowSize * 1024; }
+
+ // TCP Keepalive configuration values.
+
+ // Returns true if TCP keepalive should be enabled for short-lived conns.
+ bool TCPKeepaliveEnabledForShortLivedConns() {
+ return mTCPKeepaliveShortLivedEnabled;
+ }
+ // Return time (secs) that a connection is consider short lived (for TCP
+ // keepalive purposes). After this time, the connection is long-lived.
+ int32_t GetTCPKeepaliveShortLivedTime() {
+ return mTCPKeepaliveShortLivedTimeS;
+ }
+ // Returns time (secs) before first TCP keepalive probes should be sent;
+ // same time used between successful keepalive probes.
+ int32_t GetTCPKeepaliveShortLivedIdleTime() {
+ return mTCPKeepaliveShortLivedIdleTimeS;
+ }
+
+ // Returns true if TCP keepalive should be enabled for long-lived conns.
+ bool TCPKeepaliveEnabledForLongLivedConns() {
+ return mTCPKeepaliveLongLivedEnabled;
+ }
+ // Returns time (secs) before first TCP keepalive probes should be sent;
+ // same time used between successful keepalive probes.
+ int32_t GetTCPKeepaliveLongLivedIdleTime() {
+ return mTCPKeepaliveLongLivedIdleTimeS;
+ }
+
+ // returns the HTTP framing check level preference, as controlled with
+ // network.http.enforce-framing.http1 and network.http.enforce-framing.soft
+ FrameCheckLevel GetEnforceH1Framing() { return mEnforceH1Framing; }
+
+ nsHttpAuthCache* AuthCache(bool aPrivate) {
+ return aPrivate ? &mPrivateAuthCache : &mAuthCache;
+ }
+ nsHttpConnectionMgr* ConnMgr() {
+ MOZ_ASSERT_IF(nsIOService::UseSocketProcess(), XRE_IsSocketProcess());
+ return mConnMgr->AsHttpConnectionMgr();
+ }
+
+ AltSvcCache* AltServiceCache() const {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return mAltSvcCache.get();
+ }
+
+ void ClearHostMapping(nsHttpConnectionInfo* aConnInfo);
+
+ // cache support
+ uint32_t GenerateUniqueID() { return ++mLastUniqueID; }
+ uint32_t SessionStartTime() { return mSessionStartTime; }
+
+ //
+ // Connection management methods:
+ //
+ // - the handler only owns idle connections; it does not own active
+ // connections.
+ //
+ // - the handler keeps a count of active connections to enforce the
+ // steady-state max-connections pref.
+ //
+
+ // Called to kick-off a new transaction, by default the transaction
+ // will be put on the pending transaction queue if it cannot be
+ // initiated at this time. Callable from any thread.
+ [[nodiscard]] nsresult InitiateTransaction(HttpTransactionShell* trans,
+ int32_t priority);
+
+ // This function is also called to kick-off a new transaction. But the new
+ // transaction will take a sticky connection from |transWithStickyConn|
+ // and reuse it.
+ [[nodiscard]] nsresult InitiateTransactionWithStickyConn(
+ HttpTransactionShell* trans, int32_t priority,
+ HttpTransactionShell* transWithStickyConn);
+
+ // Called to change the priority of an existing transaction that has
+ // already been initiated.
+ [[nodiscard]] nsresult RescheduleTransaction(HttpTransactionShell* trans,
+ int32_t priority);
+
+ void UpdateClassOfServiceOnTransaction(HttpTransactionShell* trans,
+ const ClassOfService& classOfService);
+
+ // Called to cancel a transaction, which may or may not be assigned to
+ // a connection. Callable from any thread.
+ [[nodiscard]] nsresult CancelTransaction(HttpTransactionShell* trans,
+ nsresult reason);
+
+ // Called when a connection is done processing a transaction. Callable
+ // from any thread.
+ [[nodiscard]] nsresult ReclaimConnection(HttpConnectionBase* conn) {
+ return mConnMgr->ReclaimConnection(conn);
+ }
+
+ [[nodiscard]] nsresult ProcessPendingQ(nsHttpConnectionInfo* cinfo) {
+ return mConnMgr->ProcessPendingQ(cinfo);
+ }
+
+ [[nodiscard]] nsresult ProcessPendingQ() {
+ return mConnMgr->ProcessPendingQ();
+ }
+
+ [[nodiscard]] nsresult GetSocketThreadTarget(nsIEventTarget** target) {
+ return mConnMgr->GetSocketThreadTarget(target);
+ }
+
+ [[nodiscard]] nsresult SpeculativeConnect(nsHttpConnectionInfo* ci,
+ nsIInterfaceRequestor* callbacks,
+ uint32_t caps = 0,
+ bool aFetchHTTPSRR = false) {
+ TickleWifi(callbacks);
+ RefPtr<nsHttpConnectionInfo> clone = ci->Clone();
+ return mConnMgr->SpeculativeConnect(clone, callbacks, caps, nullptr,
+ aFetchHTTPSRR | EchConfigEnabled());
+ }
+
+ [[nodiscard]] nsresult SpeculativeConnect(nsHttpConnectionInfo* ci,
+ nsIInterfaceRequestor* callbacks,
+ uint32_t caps,
+ SpeculativeTransaction* aTrans) {
+ RefPtr<nsHttpConnectionInfo> clone = ci->Clone();
+ return mConnMgr->SpeculativeConnect(clone, callbacks, caps, aTrans);
+ }
+
+ // Alternate Services Maps are main thread only
+ void UpdateAltServiceMapping(AltSvcMapping* map, nsProxyInfo* proxyInfo,
+ nsIInterfaceRequestor* callbacks, uint32_t caps,
+ const OriginAttributes& originAttributes) {
+ mAltSvcCache->UpdateAltServiceMapping(map, proxyInfo, callbacks, caps,
+ originAttributes);
+ }
+
+ void UpdateAltServiceMappingWithoutValidation(
+ AltSvcMapping* map, nsProxyInfo* proxyInfo,
+ nsIInterfaceRequestor* callbacks, uint32_t caps,
+ const OriginAttributes& originAttributes) {
+ mAltSvcCache->UpdateAltServiceMappingWithoutValidation(
+ map, proxyInfo, callbacks, caps, originAttributes);
+ }
+
+ already_AddRefed<AltSvcMapping> GetAltServiceMapping(
+ const nsACString& scheme, const nsACString& host, int32_t port, bool pb,
+ const OriginAttributes& originAttributes, bool aHttp2Allowed,
+ bool aHttp3Allowed) {
+ return mAltSvcCache->GetAltServiceMapping(
+ scheme, host, port, pb, originAttributes, aHttp2Allowed, aHttp3Allowed);
+ }
+
+ //
+ // The HTTP handler caches pointers to specific XPCOM services, and
+ // provides the following helper routines for accessing those services:
+ //
+ [[nodiscard]] nsresult GetIOService(nsIIOService** result);
+ nsICookieService* GetCookieService(); // not addrefed
+ nsISiteSecurityService* GetSSService();
+
+ // Called by the channel synchronously during asyncOpen
+ void OnFailedOpeningRequest(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_FAILED_OPENING_REQUEST_TOPIC);
+ }
+
+ // Called by the channel synchronously during asyncOpen
+ void OnOpeningRequest(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_OPENING_REQUEST_TOPIC);
+ }
+
+ void OnOpeningDocumentRequest(nsIIdentChannel* chan) {
+ NotifyObservers(chan, NS_DOCUMENT_ON_OPENING_REQUEST_TOPIC);
+ }
+
+ // Called by the channel before writing a request
+ void OnModifyRequest(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_MODIFY_REQUEST_TOPIC);
+ }
+
+ // Same as OnModifyRequest but before cookie headers are written.
+ void OnModifyRequestBeforeCookies(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_MODIFY_REQUEST_BEFORE_COOKIES_TOPIC);
+ }
+
+ void OnModifyDocumentRequest(nsIIdentChannel* chan) {
+ NotifyObservers(chan, NS_DOCUMENT_ON_MODIFY_REQUEST_TOPIC);
+ }
+
+ // Called by the channel before writing a request
+ void OnStopRequest(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_STOP_REQUEST_TOPIC);
+ }
+
+ // Called by the channel before setting up the transaction
+ void OnBeforeConnect(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_BEFORE_CONNECT_TOPIC);
+ }
+
+ // Called by the channel once headers are available
+ void OnExamineResponse(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC);
+ }
+
+ // Called by the channel once headers have been merged with cached headers
+ void OnExamineMergedResponse(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_EXAMINE_MERGED_RESPONSE_TOPIC);
+ }
+
+ // Called by the channel once it made background cache revalidation
+ void OnBackgroundRevalidation(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_BACKGROUND_REVALIDATION);
+ }
+
+ // Called by channels before a redirect happens. This notifies both the
+ // channel's and the global redirect observers.
+ [[nodiscard]] nsresult AsyncOnChannelRedirect(
+ nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags,
+ nsIEventTarget* mainThreadEventTarget = nullptr);
+
+ // Called by the channel when the response is read from the cache without
+ // communicating with the server.
+ void OnExamineCachedResponse(nsIHttpChannel* chan) {
+ NotifyObservers(chan, NS_HTTP_ON_EXAMINE_CACHED_RESPONSE_TOPIC);
+ }
+
+ // Called by the channel when the transaction pump is suspended because of
+ // trying to get credentials asynchronously.
+ void OnTransactionSuspendedDueToAuthentication(nsIHttpChannel* chan) {
+ NotifyObservers(chan, "http-on-transaction-suspended-authentication");
+ }
+
+ // Generates the host:port string for use in the Host: header as well as the
+ // CONNECT line for proxies. This handles IPv6 literals correctly.
+ [[nodiscard]] static nsresult GenerateHostPort(const nsCString& host,
+ int32_t port,
+ nsACString& hostLine);
+
+ SpdyInformation* SpdyInfo() { return &mSpdyInfo; }
+ bool IsH2MandatorySuiteEnabled() { return mH2MandatorySuiteEnabled; }
+
+ // returns true in between Init and Shutdown states
+ bool Active() { return mHandlerActive; }
+
+ nsIRequestContextService* GetRequestContextService() {
+ return mRequestContextService.get();
+ }
+
+ void ShutdownConnectionManager();
+
+ uint32_t DefaultHpackBuffer() const { return mDefaultHpackBuffer; }
+
+ static bool IsHttp3Enabled();
+ bool IsHttp3VersionSupported(const nsACString& version);
+
+ static bool IsHttp3SupportedByServer(nsHttpResponseHead* aResponseHead);
+ uint32_t DefaultQpackTableSize() const { return mQpackTableSize; }
+ uint16_t DefaultHttp3MaxBlockedStreams() const {
+ return (uint16_t)mHttp3MaxBlockedStreams;
+ }
+
+ uint32_t MaxHttpResponseHeaderSize() const {
+ return mMaxHttpResponseHeaderSize;
+ }
+
+ const nsCString& Http3QlogDir();
+
+ float FocusedWindowTransactionRatio() const {
+ return mFocusedWindowTransactionRatio;
+ }
+
+ bool ActiveTabPriority() const { return mActiveTabPriority; }
+
+ // Called when an optimization feature affecting active vs background tab load
+ // took place. Called only on the parent process and only updates
+ // mLastActiveTabLoadOptimizationHit timestamp to now.
+ void NotifyActiveTabLoadOptimization();
+ TimeStamp GetLastActiveTabLoadOptimizationHit();
+ void SetLastActiveTabLoadOptimizationHit(TimeStamp const& when);
+ bool IsBeforeLastActiveTabLoadOptimization(TimeStamp const& when);
+
+ HttpTrafficAnalyzer* GetHttpTrafficAnalyzer();
+
+ bool GetThroughCaptivePortal() { return mThroughCaptivePortal; }
+
+ nsresult CompleteUpgrade(HttpTransactionShell* aTrans,
+ nsIHttpUpgradeListener* aUpgradeListener);
+
+ nsresult DoShiftReloadConnectionCleanupWithConnInfo(
+ nsHttpConnectionInfo* aCI);
+
+ void MaybeAddAltSvcForTesting(nsIURI* aUri, const nsACString& aUsername,
+ bool aPrivateBrowsing,
+ nsIInterfaceRequestor* aCallbacks,
+ const OriginAttributes& aOriginAttributes);
+
+ bool UseHTTPSRRAsAltSvcEnabled() const;
+
+ bool EchConfigEnabled(bool aIsHttp3 = false) const;
+ // When EchConfig is enabled and all records with echConfig are failed, this
+ // functon indicate whether we can fallback to the origin server.
+ // In the case an HTTPS RRSet contains some RRs with echConfig and some
+ // without, we always fallback to the origin one.
+ bool FallbackToOriginIfConfigsAreECHAndAllFailed() const;
+
+ // So we can ensure that this is done during process preallocation to
+ // avoid first-use overhead
+ static void PresetAcceptLanguages();
+
+ bool HttpActivityDistributorActivated();
+ void ObserveHttpActivityWithArgs(const HttpActivityArgs& aArgs,
+ uint32_t aActivityType,
+ uint32_t aActivitySubtype, PRTime aTimestamp,
+ uint64_t aExtraSizeData,
+ const nsACString& aExtraStringData);
+
+ private:
+ nsHttpHandler();
+
+ virtual ~nsHttpHandler();
+
+ [[nodiscard]] nsresult Init();
+
+ //
+ // Useragent/prefs helper methods
+ //
+ void BuildUserAgent();
+ void InitUserAgentComponents();
+ static void PrefsChanged(const char* pref, void* self);
+ void PrefsChanged(const char* pref);
+
+ [[nodiscard]] nsresult SetAcceptLanguages();
+ [[nodiscard]] nsresult SetAcceptEncodings(const char*, bool mIsSecure);
+
+ [[nodiscard]] nsresult InitConnectionMgr();
+
+ void NotifyObservers(nsIChannel* chan, const char* event);
+
+ friend class SocketProcessChild;
+ void SetHttpHandlerInitArgs(const HttpHandlerInitArgs& aArgs);
+ void SetDeviceModelId(const nsACString& aModelId);
+
+ // We only allow DNSUtils and TRRServiceChannel itself to create
+ // TRRServiceChannel.
+ friend class TRRServiceChannel;
+ friend class DNSUtils;
+ nsresult CreateTRRServiceChannel(nsIURI* uri, nsIProxyInfo* givenProxyInfo,
+ uint32_t proxyResolveFlags, nsIURI* proxyURI,
+ nsILoadInfo* aLoadInfo, nsIChannel** result);
+ nsresult SetupChannelInternal(HttpBaseChannel* aChannel, nsIURI* uri,
+ nsIProxyInfo* givenProxyInfo,
+ uint32_t proxyResolveFlags, nsIURI* proxyURI,
+ nsILoadInfo* aLoadInfo, nsIChannel** result);
+
+ private:
+ // cached services
+ nsMainThreadPtrHandle<nsIIOService> mIOService;
+ nsMainThreadPtrHandle<nsICookieService> mCookieService;
+ nsMainThreadPtrHandle<nsISiteSecurityService> mSSService;
+
+ // the authentication credentials cache
+ nsHttpAuthCache mAuthCache;
+ nsHttpAuthCache mPrivateAuthCache;
+
+ // the connection manager
+ RefPtr<HttpConnectionMgrShell> mConnMgr;
+
+ UniquePtr<AltSvcCache> mAltSvcCache;
+
+ //
+ // prefs
+ //
+
+ enum HttpVersion mHttpVersion { HttpVersion::v1_1 };
+ enum HttpVersion mProxyHttpVersion { HttpVersion::v1_1 };
+ uint32_t mCapabilities{NS_HTTP_ALLOW_KEEPALIVE};
+
+ bool mFastFallbackToIPv4{false};
+ PRIntervalTime mIdleTimeout;
+ PRIntervalTime mSpdyTimeout;
+ PRIntervalTime mResponseTimeout;
+ Atomic<bool, Relaxed> mResponseTimeoutEnabled{false};
+ uint32_t mNetworkChangedTimeout{5000}; // milliseconds
+ uint16_t mMaxRequestAttempts{6};
+ uint16_t mMaxRequestDelay{10};
+ uint16_t mIdleSynTimeout{250};
+ uint16_t mFallbackSynTimeout{5}; // seconds
+
+ bool mH2MandatorySuiteEnabled{false};
+ uint16_t mMaxUrgentExcessiveConns{3};
+ uint16_t mMaxConnections{24};
+ uint8_t mMaxPersistentConnectionsPerServer{2};
+ uint8_t mMaxPersistentConnectionsPerProxy{4};
+
+ bool mThrottleEnabled{true};
+ uint32_t mThrottleVersion{2};
+ uint32_t mThrottleSuspendFor{3000};
+ uint32_t mThrottleResumeFor{200};
+ uint32_t mThrottleReadLimit{8000};
+ uint32_t mThrottleReadInterval{500};
+ uint32_t mThrottleHoldTime{600};
+ uint32_t mThrottleMaxTime{3000};
+
+ int32_t mSendWindowSize{1024};
+
+ bool mUrgentStartEnabled{true};
+ bool mTailBlockingEnabled{true};
+ uint32_t mTailDelayQuantum{600};
+ uint32_t mTailDelayQuantumAfterDCL{100};
+ uint32_t mTailDelayMax{6000};
+ uint32_t mTailTotalMax{0};
+
+ uint8_t mRedirectionLimit{10};
+
+ bool mBeConservativeForProxy{true};
+
+ // we'll warn the user if we load an URL containing a userpass field
+ // unless its length is less than this threshold. this warning is
+ // intended to protect the user against spoofing attempts that use
+ // the userpass field of the URL to obscure the actual origin server.
+ uint8_t mPhishyUserPassLength{1};
+
+ uint8_t mQoSBits{0x00};
+
+ bool mEnforceAssocReq{false};
+
+ nsCString mImageAcceptHeader;
+ nsCString mDocumentAcceptHeader;
+
+ nsCString mAcceptLanguages;
+ nsCString mHttpAcceptEncodings;
+ nsCString mHttpsAcceptEncodings;
+
+ nsCString mDefaultSocketType;
+
+ // cache support
+ uint32_t mLastUniqueID;
+ Atomic<uint32_t, Relaxed> mSessionStartTime{0};
+
+ // useragent components
+ nsCString mLegacyAppName{"Mozilla"};
+ nsCString mLegacyAppVersion{"5.0"};
+ nsCString mPlatform;
+ nsCString mOscpu;
+ nsCString mMisc;
+ nsCString mProduct{"Gecko"};
+ nsCString mProductSub;
+ nsCString mAppName;
+ nsCString mAppVersion;
+ nsCString mCompatFirefox;
+ bool mCompatFirefoxEnabled{false};
+ nsCString mCompatDevice;
+ nsCString mDeviceModelId;
+
+ nsCString mUserAgent;
+ nsCString mSpoofedUserAgent;
+ nsCString mUserAgentOverride;
+ bool mUserAgentIsDirty{true}; // true if mUserAgent should be rebuilt
+ bool mAcceptLanguagesIsDirty{true};
+
+ bool mPromptTempRedirect{true};
+
+ // Persistent HTTPS caching flag
+ bool mEnablePersistentHttpsCaching{false};
+
+ // for broadcasting safe hint;
+ bool mSafeHintEnabled{false};
+ bool mParentalControlEnabled{false};
+
+ // true in between init and shutdown states
+ Atomic<bool, Relaxed> mHandlerActive{false};
+
+ // The value of 'hidden' network.http.debug-observations : 1;
+ uint32_t mDebugObservations : 1;
+
+ uint32_t mEnableAltSvc : 1;
+ uint32_t mEnableAltSvcOE : 1;
+ uint32_t mEnableOriginExtension : 1;
+
+ // Try to use SPDY features instead of HTTP/1.1 over SSL
+ SpdyInformation mSpdyInfo;
+
+ uint32_t mSpdySendingChunkSize{ASpdySession::kSendingChunkSize};
+ uint32_t mSpdySendBufferSize{ASpdySession::kTCPSendBufferSize};
+ uint32_t mSpdyPushAllowance{
+ ASpdySession::kInitialPushAllowance}; // match default pref
+ uint32_t mSpdyPullAllowance{ASpdySession::kInitialRwin};
+ uint32_t mDefaultSpdyConcurrent{ASpdySession::kDefaultMaxConcurrent};
+ PRIntervalTime mSpdyPingThreshold;
+ PRIntervalTime mSpdyPingTimeout;
+
+ // The maximum amount of time to wait for socket transport to be
+ // established. In milliseconds.
+ uint32_t mConnectTimeout{90000};
+
+ // The maximum amount of time to wait for a tls handshake to be
+ // established. In milliseconds.
+ uint32_t mTLSHandshakeTimeout{30000};
+
+ // The maximum number of current global half open sockets allowable
+ // when starting a new speculative connection.
+ uint32_t mParallelSpeculativeConnectLimit{6};
+
+ // For Rate Pacing of HTTP/1 requests through a netwerk/base/EventTokenBucket
+ // Active requests <= *MinParallelism are not subject to the rate pacing
+ bool mRequestTokenBucketEnabled{true};
+ uint16_t mRequestTokenBucketMinParallelism{6};
+ uint32_t mRequestTokenBucketHz{100}; // EventTokenBucket HZ
+ uint32_t mRequestTokenBucketBurst{32}; // EventTokenBucket Burst
+
+ // Whether or not to block requests for non head js/css items (e.g. media)
+ // while those elements load.
+ bool mCriticalRequestPrioritization{true};
+
+ // TCP Keepalive configuration values.
+
+ // True if TCP keepalive is enabled for short-lived conns.
+ bool mTCPKeepaliveShortLivedEnabled{false};
+ // Time (secs) indicating how long a conn is considered short-lived.
+ int32_t mTCPKeepaliveShortLivedTimeS{60};
+ // Time (secs) before first keepalive probe; between successful probes.
+ int32_t mTCPKeepaliveShortLivedIdleTimeS{10};
+
+ // True if TCP keepalive is enabled for long-lived conns.
+ bool mTCPKeepaliveLongLivedEnabled{false};
+ // Time (secs) before first keepalive probe; between successful probes.
+ int32_t mTCPKeepaliveLongLivedIdleTimeS{600};
+
+ // if true, generate NS_ERROR_PARTIAL_TRANSFER for h1 responses with
+ // incorrect content lengths or malformed chunked encodings
+ FrameCheckLevel mEnforceH1Framing{FRAMECHECK_BARELY};
+
+ nsCOMPtr<nsIRequestContextService> mRequestContextService;
+
+ // The default size (in bytes) of the HPACK decompressor table.
+ uint32_t mDefaultHpackBuffer{4096};
+
+ // Http3 parameters
+ Atomic<uint32_t, Relaxed> mQpackTableSize{4096};
+ // uint16_t is enough here, but Atomic only supports uint32_t or uint64_t.
+ Atomic<uint32_t, Relaxed> mHttp3MaxBlockedStreams{10};
+
+ nsCString mHttp3QlogDir;
+
+ // The max size (in bytes) for received Http response header.
+ uint32_t mMaxHttpResponseHeaderSize{393216};
+
+ // The ratio for dispatching transactions from the focused window.
+ float mFocusedWindowTransactionRatio{0.9f};
+
+ // If true, the transactions from active tab will be dispatched first.
+ bool mActiveTabPriority{true};
+
+ HttpTrafficAnalyzer mHttpTrafficAnalyzer;
+
+ private:
+ // For Rate Pacing Certain Network Events. Only assign this pointer on
+ // socket thread.
+ void MakeNewRequestTokenBucket();
+ RefPtr<EventTokenBucket> mRequestTokenBucket;
+
+ public:
+ // Socket thread only
+ [[nodiscard]] nsresult SubmitPacedRequest(ATokenBucketEvent* event,
+ nsICancelable** cancel) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mRequestTokenBucket) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mRequestTokenBucket->SubmitEvent(event, cancel);
+ }
+
+ // Socket thread only
+ void SetRequestTokenBucket(EventTokenBucket* aTokenBucket) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mRequestTokenBucket = aTokenBucket;
+ }
+
+ void StopRequestTokenBucket() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mRequestTokenBucket) {
+ mRequestTokenBucket->Stop();
+ mRequestTokenBucket = nullptr;
+ }
+ }
+
+ private:
+ RefPtr<Tickler> mWifiTickler;
+ void TickleWifi(nsIInterfaceRequestor* cb);
+
+ private:
+ [[nodiscard]] nsresult SpeculativeConnectInternal(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ Maybe<OriginAttributes>&& aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool anonymous);
+ void ExcludeHttp2OrHttp3Internal(const nsHttpConnectionInfo* ci);
+
+ // State for generating channelIds
+ uint32_t mProcessId{0};
+ Atomic<uint32_t, Relaxed> mNextChannelId{1};
+
+ // The last time any of the active tab page load optimization took place.
+ // This is accessed on multiple threads, hence a lock is needed.
+ // On the parent process this is updated to now every time a scheduling
+ // or rate optimization related to the active/background tab is hit.
+ // We carry this value through each http channel's onstoprequest notification
+ // to the parent process. On the content process then we just update this
+ // value from ipc onstoprequest arguments. This is a sufficent way of passing
+ // it down to the content process, since the value will be used only after
+ // onstoprequest notification coming from an http channel.
+ Mutex mLastActiveTabLoadOptimizationLock{
+ "nsHttpConnectionMgr::LastActiveTabLoadOptimization"};
+ TimeStamp mLastActiveTabLoadOptimizationHit;
+
+ Mutex mHttpExclusionLock MOZ_UNANNOTATED{"nsHttpHandler::HttpExclusion"};
+
+ public:
+ [[nodiscard]] nsresult NewChannelId(uint64_t& channelId);
+ void AddHttpChannel(uint64_t aId, nsISupports* aChannel);
+ void RemoveHttpChannel(uint64_t aId);
+ nsWeakPtr GetWeakHttpChannel(uint64_t aId);
+
+ void ExcludeHttp2(const nsHttpConnectionInfo* ci);
+ [[nodiscard]] bool IsHttp2Excluded(const nsHttpConnectionInfo* ci);
+ void ExcludeHttp3(const nsHttpConnectionInfo* ci);
+ [[nodiscard]] bool IsHttp3Excluded(const nsACString& aRoutedHost);
+ void Exclude0RttTcp(const nsHttpConnectionInfo* ci);
+ [[nodiscard]] bool Is0RttTcpExcluded(const nsHttpConnectionInfo* ci);
+
+ void ExcludeHTTPSRRHost(const nsACString& aHost);
+ [[nodiscard]] bool IsHostExcludedForHTTPSRR(const nsACString& aHost);
+
+ private:
+ nsTHashSet<nsCString> mExcludedHttp2Origins;
+ nsTHashSet<nsCString> mExcludedHttp3Origins;
+ nsTHashSet<nsCString> mExcluded0RttTcpOrigins;
+ // A set of hosts that we should not upgrade to HTTPS with HTTPS RR.
+ nsTHashSet<nsCString> mExcludedHostsForHTTPSRRUpgrade;
+
+ Atomic<bool, Relaxed> mThroughCaptivePortal{false};
+
+ // The mapping of channel id and the weak pointer of nsHttpChannel.
+ nsTHashMap<nsUint64HashKey, nsWeakPtr> mIDToHttpChannelMap;
+
+ // This is parsed pref network.http.http3.alt-svc-mapping-for-testing.
+ // The pref set artificial altSvc-s for origin for testing.
+ // This maps an origin to an altSvc.
+ nsClassHashtable<nsCStringHashKey, nsCString> mAltSvcMappingTemptativeMap;
+
+ nsCOMPtr<nsIHttpActivityDistributor> mActivityDistributor;
+};
+
+extern StaticRefPtr<nsHttpHandler> gHttpHandler;
+
+//-----------------------------------------------------------------------------
+// nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the
+// HTTPS handler (even though they share the same impl).
+//-----------------------------------------------------------------------------
+
+class nsHttpsHandler : public nsIHttpProtocolHandler,
+ public nsSupportsWeakReference,
+ public nsISpeculativeConnect {
+ virtual ~nsHttpsHandler() = default;
+
+ public:
+ // we basically just want to override GetScheme and GetDefaultPort...
+ // all other methods should be forwarded to the nsHttpHandler instance.
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_FORWARD_NSIPROXIEDPROTOCOLHANDLER(gHttpHandler->)
+ NS_FORWARD_NSIHTTPPROTOCOLHANDLER(gHttpHandler->)
+
+ NS_IMETHOD SpeculativeConnect(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ nsIInterfaceRequestor* aCallbacks,
+ bool aAnonymous) override {
+ return gHttpHandler->SpeculativeConnect(aURI, aPrincipal, aCallbacks,
+ aAnonymous);
+ }
+
+ NS_IMETHOD SpeculativeConnectWithOriginAttributes(
+ nsIURI* aURI, JS::Handle<JS::Value> originAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous,
+ JSContext* cx) override {
+ return gHttpHandler->SpeculativeConnectWithOriginAttributes(
+ aURI, originAttributes, aCallbacks, aAnonymous, cx);
+ }
+
+ NS_IMETHOD_(void)
+ SpeculativeConnectWithOriginAttributesNative(
+ nsIURI* aURI, mozilla::OriginAttributes&& originAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous) override {
+ gHttpHandler->SpeculativeConnectWithOriginAttributesNative(
+ aURI, std::move(originAttributes), aCallbacks, aAnonymous);
+ }
+
+ nsHttpsHandler() = default;
+
+ [[nodiscard]] nsresult Init();
+};
+
+//-----------------------------------------------------------------------------
+// HSTSDataCallbackWrapper - A threadsafe helper class to wrap the callback.
+//
+// We need this because dom::promise and EnsureHSTSDataResolver are not
+// threadsafe.
+//-----------------------------------------------------------------------------
+class HSTSDataCallbackWrapper final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HSTSDataCallbackWrapper)
+
+ explicit HSTSDataCallbackWrapper(std::function<void(bool)>&& aCallback)
+ : mCallback(std::move(aCallback)) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ void DoCallback(bool aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mCallback(aResult);
+ }
+
+ private:
+ ~HSTSDataCallbackWrapper() = default;
+
+ std::function<void(bool)> mCallback;
+};
+
+} // namespace mozilla::net
+
+#endif // nsHttpHandler_h__
diff --git a/netwerk/protocol/http/nsHttpHeaderArray.cpp b/netwerk/protocol/http/nsHttpHeaderArray.cpp
new file mode 100644
index 0000000000..8da421e3fa
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHeaderArray.cpp
@@ -0,0 +1,475 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 ci et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpHeaderArray.h"
+#include "nsURLHelper.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpHeaderArray <public>
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpHeaderArray::SetHeader(
+ const nsACString& headerName, const nsACString& value, bool merge,
+ nsHttpHeaderArray::HeaderVariety variety) {
+ nsHttpAtom header = nsHttp::ResolveAtom(headerName);
+ if (!header) {
+ NS_WARNING("failed to resolve atom");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return SetHeader(header, headerName, value, merge, variety);
+}
+
+nsresult nsHttpHeaderArray::SetHeader(
+ const nsHttpAtom& header, const nsACString& value, bool merge,
+ nsHttpHeaderArray::HeaderVariety variety) {
+ return SetHeader(header, ""_ns, value, merge, variety);
+}
+
+nsresult nsHttpHeaderArray::SetHeader(
+ const nsHttpAtom& header, const nsACString& headerName,
+ const nsACString& value, bool merge,
+ nsHttpHeaderArray::HeaderVariety variety) {
+ MOZ_ASSERT(
+ (variety == eVarietyResponse) || (variety == eVarietyRequestDefault) ||
+ (variety == eVarietyRequestOverride) ||
+ (variety == eVarietyRequestEnforceDefault),
+ "Net original headers can only be set using SetHeader_internal().");
+
+ nsEntry* entry = nullptr;
+ int32_t index = LookupEntry(header, &entry);
+
+ // If an empty value is received and we aren't merging headers discard it
+ if (value.IsEmpty() && header != nsHttp::X_Frame_Options) {
+ if (!merge && entry) {
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ } else {
+ mHeaders.RemoveElementAt(index);
+ }
+ }
+ return NS_OK;
+ }
+
+ MOZ_ASSERT((variety == eVarietyRequestEnforceDefault) ||
+ (!entry || variety != eVarietyRequestDefault),
+ "Cannot set default entry which overrides existing entry!");
+
+ // Set the variety to default if we are enforcing it.
+ if (variety == eVarietyRequestEnforceDefault) {
+ variety = eVarietyRequestDefault;
+ }
+ if (!entry) {
+ return SetHeader_internal(header, headerName, value, variety);
+ }
+ if (merge && !IsSingletonHeader(header)) {
+ return MergeHeader(header, entry, value, variety);
+ }
+ if (!IsIgnoreMultipleHeader(header)) {
+ // Replace the existing string with the new value
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ return SetHeader_internal(header, headerName, value, variety);
+ }
+ entry->value = value;
+ entry->variety = variety;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpHeaderArray::SetHeader_internal(
+ const nsHttpAtom& header, const nsACString& headerName,
+ const nsACString& value, nsHttpHeaderArray::HeaderVariety variety) {
+ nsEntry* entry = mHeaders.AppendElement();
+ if (!entry) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ entry->header = header;
+ // Only save original form of a header if it is different than the header
+ // atom string.
+ if (!headerName.Equals(header.get())) {
+ entry->headerNameOriginal = headerName;
+ }
+ entry->value = value;
+ entry->variety = variety;
+ return NS_OK;
+}
+
+nsresult nsHttpHeaderArray::SetEmptyHeader(const nsACString& headerName,
+ HeaderVariety variety) {
+ nsHttpAtom header = nsHttp::ResolveAtom(headerName);
+ if (!header) {
+ NS_WARNING("failed to resolve atom");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MOZ_ASSERT((variety == eVarietyResponse) ||
+ (variety == eVarietyRequestDefault) ||
+ (variety == eVarietyRequestOverride),
+ "Original headers can only be set using SetHeader_internal().");
+ nsEntry* entry = nullptr;
+
+ LookupEntry(header, &entry);
+
+ if (entry && entry->variety != eVarietyResponseNetOriginalAndResponse) {
+ entry->value.Truncate();
+ return NS_OK;
+ }
+ if (entry) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ }
+
+ return SetHeader_internal(header, headerName, ""_ns, variety);
+}
+
+nsresult nsHttpHeaderArray::SetHeaderFromNet(
+ const nsHttpAtom& header, const nsACString& headerNameOriginal,
+ const nsACString& value, bool response) {
+ // mHeader holds the consolidated (merged or updated) headers.
+ // mHeader for response header will keep the original heades as well.
+ nsEntry* entry = nullptr;
+
+ LookupEntry(header, &entry);
+
+ if (!entry) {
+ HeaderVariety variety = eVarietyRequestOverride;
+ if (response) {
+ variety = eVarietyResponseNetOriginalAndResponse;
+ }
+ return SetHeader_internal(header, headerNameOriginal, value, variety);
+ }
+ if (!IsSingletonHeader(header)) {
+ HeaderVariety variety = eVarietyRequestOverride;
+ if (response) {
+ variety = eVarietyResponse;
+ }
+ nsresult rv = MergeHeader(header, entry, value, variety);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (response) {
+ rv = SetHeader_internal(header, headerNameOriginal, value,
+ eVarietyResponseNetOriginal);
+ }
+ return rv;
+ }
+ if (!IsIgnoreMultipleHeader(header)) {
+ // Multiple instances of non-mergeable header received from network
+ // - ignore if same value
+ if (header == nsHttp::Content_Length) {
+ // Content length header needs special handling.
+ // For e.g. for CL all the below headers evaluates to value of X
+ // Content-Length: X
+ // Content-Length: X, X, X
+ // Content-Length: X \n\r Content-Length: X
+ // remove duplicate values from the header-values for comparison
+
+ nsAutoCString headerValue;
+ RemoveDuplicateHeaderValues(value, headerValue);
+
+ nsAutoCString entryValue;
+ RemoveDuplicateHeaderValues(entry->value, entryValue);
+ if (entryValue != headerValue) {
+ // reply may be corrupt/hacked (ex: CLRF injection attacks)
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ } else if (!entry->value.Equals(value)) { // compare remaining headers
+ if (IsSuspectDuplicateHeader(header)) {
+ // reply may be corrupt/hacked (ex: CLRF injection attacks)
+ return NS_ERROR_CORRUPTED_CONTENT;
+ } // else silently drop value: keep value from 1st header seen
+ LOG(("Header %s silently dropped as non mergeable header\n",
+ header.get()));
+ }
+
+ if (response) {
+ return SetHeader_internal(header, headerNameOriginal, value,
+ eVarietyResponseNetOriginal);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpHeaderArray::SetResponseHeaderFromCache(
+ const nsHttpAtom& header, const nsACString& headerNameOriginal,
+ const nsACString& value, nsHttpHeaderArray::HeaderVariety variety) {
+ MOZ_ASSERT(
+ (variety == eVarietyResponse) || (variety == eVarietyResponseNetOriginal),
+ "Headers from cache can only be eVarietyResponse and "
+ "eVarietyResponseNetOriginal");
+
+ if (variety == eVarietyResponseNetOriginal) {
+ return SetHeader_internal(header, headerNameOriginal, value,
+ eVarietyResponseNetOriginal);
+ }
+ nsTArray<nsEntry>::index_type index = 0;
+ do {
+ index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
+ if (index !=
+ CopyableTArray<mozilla::net::nsHttpHeaderArray::nsEntry>::NoIndex) {
+ nsEntry& entry = mHeaders[index];
+ if (value.Equals(entry.value)) {
+ MOZ_ASSERT(
+ (entry.variety == eVarietyResponseNetOriginal) ||
+ (entry.variety == eVarietyResponseNetOriginalAndResponse),
+ "This array must contain only eVarietyResponseNetOriginal"
+ " and eVarietyResponseNetOriginalAndRespons headers!");
+ entry.variety = eVarietyResponseNetOriginalAndResponse;
+ return NS_OK;
+ }
+ index++;
+ }
+ } while (index !=
+ CopyableTArray<mozilla::net::nsHttpHeaderArray::nsEntry>::NoIndex);
+ // If we are here, we have not found an entry so add a new one.
+ return SetHeader_internal(header, headerNameOriginal, value,
+ eVarietyResponse);
+}
+
+void nsHttpHeaderArray::ClearHeader(const nsHttpAtom& header) {
+ nsEntry* entry = nullptr;
+ int32_t index = LookupEntry(header, &entry);
+ if (entry) {
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ entry->variety = eVarietyResponseNetOriginal;
+ } else {
+ mHeaders.RemoveElementAt(index);
+ }
+ }
+}
+
+const char* nsHttpHeaderArray::PeekHeader(const nsHttpAtom& header) const {
+ const nsEntry* entry = nullptr;
+ LookupEntry(header, &entry);
+ return entry ? entry->value.get() : nullptr;
+}
+
+nsresult nsHttpHeaderArray::GetHeader(const nsHttpAtom& header,
+ nsACString& result) const {
+ const nsEntry* entry = nullptr;
+ LookupEntry(header, &entry);
+ if (!entry) return NS_ERROR_NOT_AVAILABLE;
+ result = entry->value;
+ return NS_OK;
+}
+
+nsresult nsHttpHeaderArray::GetOriginalHeader(const nsHttpAtom& aHeader,
+ nsIHttpHeaderVisitor* aVisitor) {
+ NS_ENSURE_ARG_POINTER(aVisitor);
+ uint32_t index = 0;
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ while (true) {
+ index = mHeaders.IndexOf(aHeader, index, nsEntry::MatchHeader());
+ if (index != UINT32_MAX) {
+ const nsEntry& entry = mHeaders[index];
+
+ MOZ_ASSERT((entry.variety == eVarietyResponseNetOriginalAndResponse) ||
+ (entry.variety == eVarietyResponseNetOriginal) ||
+ (entry.variety == eVarietyResponse),
+ "This must be a response header.");
+ index++;
+ if (entry.variety == eVarietyResponse) {
+ continue;
+ }
+
+ const nsCString& hdr = entry.headerNameOriginal.IsEmpty()
+ ? entry.header.val()
+ : entry.headerNameOriginal;
+
+ rv = NS_OK;
+ if (NS_FAILED(aVisitor->VisitHeader(hdr, entry.value))) {
+ break;
+ }
+ } else {
+ // if there is no such a header, it will return
+ // NS_ERROR_NOT_AVAILABLE or NS_OK otherwise.
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+bool nsHttpHeaderArray::HasHeader(const nsHttpAtom& header) const {
+ const nsEntry* entry = nullptr;
+ LookupEntry(header, &entry);
+ return entry;
+}
+
+nsresult nsHttpHeaderArray::VisitHeaders(
+ nsIHttpHeaderVisitor* visitor, nsHttpHeaderArray::VisitorFilter filter) {
+ NS_ENSURE_ARG_POINTER(visitor);
+ nsresult rv;
+
+ uint32_t i, count = mHeaders.Length();
+ for (i = 0; i < count; ++i) {
+ const nsEntry& entry = mHeaders[i];
+ if (filter == eFilterSkipDefault &&
+ entry.variety == eVarietyRequestDefault) {
+ continue;
+ }
+ if (filter == eFilterResponse &&
+ entry.variety == eVarietyResponseNetOriginal) {
+ continue;
+ }
+ if (filter == eFilterResponseOriginal &&
+ entry.variety == eVarietyResponse) {
+ continue;
+ }
+
+ const nsCString& hdr = entry.headerNameOriginal.IsEmpty()
+ ? entry.header.val()
+ : entry.headerNameOriginal;
+ rv = visitor->VisitHeader(hdr, entry.value);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+/*static*/
+nsresult nsHttpHeaderArray::ParseHeaderLine(const nsACString& line,
+ nsHttpAtom* hdr,
+ nsACString* headerName,
+ nsACString* val) {
+ //
+ // BNF from section 4.2 of RFC 2616:
+ //
+ // message-header = field-name ":" [ field-value ]
+ // field-name = token
+ // field-value = *( field-content | LWS )
+ // field-content = <the OCTETs making up the field-value
+ // and consisting of either *TEXT or combinations
+ // of token, separators, and quoted-string>
+ //
+
+ // We skip over mal-formed headers in the hope that we'll still be able to
+ // do something useful with the response.
+ int32_t split = line.FindChar(':');
+
+ if (split == kNotFound) {
+ LOG(("malformed header [%s]: no colon\n", PromiseFlatCString(line).get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ const nsACString& sub = Substring(line, 0, split);
+ const nsACString& sub2 =
+ Substring(line, split + 1, line.Length() - split - 1);
+
+ // make sure we have a valid token for the field-name
+ if (!nsHttp::IsValidToken(sub)) {
+ LOG(("malformed header [%s]: field-name not a token\n",
+ PromiseFlatCString(line).get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(sub);
+ if (!atom) {
+ LOG(("failed to resolve atom [%s]\n", PromiseFlatCString(line).get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ // skip over whitespace
+ char* p =
+ net_FindCharNotInSet(sub2.BeginReading(), sub2.EndReading(), HTTP_LWS);
+
+ // trim trailing whitespace - bug 86608
+ char* p2 = net_RFindCharNotInSet(p, sub2.EndReading(), HTTP_LWS);
+
+ // assign return values
+ if (hdr) *hdr = atom;
+ if (val) val->Assign(p, p2 - p + 1);
+ if (headerName) headerName->Assign(sub);
+
+ return NS_OK;
+}
+
+void nsHttpHeaderArray::Flatten(nsACString& buf, bool pruneProxyHeaders,
+ bool pruneTransients) {
+ uint32_t i, count = mHeaders.Length();
+ for (i = 0; i < count; ++i) {
+ const nsEntry& entry = mHeaders[i];
+ // Skip original header.
+ if (entry.variety == eVarietyResponseNetOriginal) {
+ continue;
+ }
+ // prune proxy headers if requested
+ if (pruneProxyHeaders && ((entry.header == nsHttp::Proxy_Authorization) ||
+ (entry.header == nsHttp::Proxy_Connection))) {
+ continue;
+ }
+ if (pruneTransients &&
+ (entry.value.IsEmpty() || entry.header == nsHttp::Connection ||
+ entry.header == nsHttp::Proxy_Connection ||
+ entry.header == nsHttp::Keep_Alive ||
+ entry.header == nsHttp::WWW_Authenticate ||
+ entry.header == nsHttp::Proxy_Authenticate ||
+ entry.header == nsHttp::Trailer ||
+ entry.header == nsHttp::Transfer_Encoding ||
+ entry.header == nsHttp::Upgrade ||
+ // XXX this will cause problems when we start honoring
+ // Cache-Control: no-cache="set-cookie", what to do?
+ entry.header == nsHttp::Set_Cookie)) {
+ continue;
+ }
+
+ if (entry.headerNameOriginal.IsEmpty()) {
+ buf.Append(entry.header.val());
+ } else {
+ buf.Append(entry.headerNameOriginal);
+ }
+ buf.AppendLiteral(": ");
+ buf.Append(entry.value);
+ buf.AppendLiteral("\r\n");
+ }
+}
+
+void nsHttpHeaderArray::FlattenOriginalHeader(nsACString& buf) {
+ uint32_t i, count = mHeaders.Length();
+ for (i = 0; i < count; ++i) {
+ const nsEntry& entry = mHeaders[i];
+ // Skip changed header.
+ if (entry.variety == eVarietyResponse) {
+ continue;
+ }
+
+ if (entry.headerNameOriginal.IsEmpty()) {
+ buf.Append(entry.header.val());
+ } else {
+ buf.Append(entry.headerNameOriginal);
+ }
+
+ buf.AppendLiteral(": ");
+ buf.Append(entry.value);
+ buf.AppendLiteral("\r\n");
+ }
+}
+
+const char* nsHttpHeaderArray::PeekHeaderAt(
+ uint32_t index, nsHttpAtom& header, nsACString& headerNameOriginal) const {
+ const nsEntry& entry = mHeaders[index];
+
+ header = entry.header;
+ headerNameOriginal = entry.headerNameOriginal;
+ return entry.value.get();
+}
+
+void nsHttpHeaderArray::Clear() { mHeaders.Clear(); }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpHeaderArray.h b/netwerk/protocol/http/nsHttpHeaderArray.h
new file mode 100644
index 0000000000..e9aa0a8e7e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHeaderArray.h
@@ -0,0 +1,334 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpHeaderArray_h__
+#define nsHttpHeaderArray_h__
+
+#include "nsHttp.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+class nsIHttpHeaderVisitor;
+
+// This needs to be forward declared here so we can include only this header
+// without also including PHttpChannelParams.h
+namespace IPC {
+template <typename>
+struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+namespace net {
+
+class nsHttpHeaderArray {
+ public:
+ const char* PeekHeader(const nsHttpAtom& header) const;
+
+ // For nsHttpResponseHead nsHttpHeaderArray will keep track of the original
+ // headers as they come from the network and the parse headers used in
+ // firefox.
+ // If the original and the firefox header are the same, we will keep just
+ // one copy and marked it as eVarietyResponseNetOriginalAndResponse.
+ // If firefox header representation changes a header coming from the
+ // network (e.g. merged it) or a eVarietyResponseNetOriginalAndResponse
+ // header has been changed by SetHeader method, we will keep the original
+ // header as eVarietyResponseNetOriginal and make a copy for the new header
+ // and mark it as eVarietyResponse.
+ enum HeaderVariety {
+ eVarietyUnknown,
+ // Used only for request header.
+ eVarietyRequestOverride,
+ eVarietyRequestDefault,
+ eVarietyRequestEnforceDefault,
+ // Used only for response header.
+ eVarietyResponseNetOriginalAndResponse,
+ eVarietyResponseNetOriginal,
+ eVarietyResponse
+ };
+
+ // Used by internal setters: to set header from network use SetHeaderFromNet
+ [[nodiscard]] nsresult SetHeader(const nsACString& headerName,
+ const nsACString& value, bool merge,
+ HeaderVariety variety);
+ [[nodiscard]] nsresult SetHeader(const nsHttpAtom& header,
+ const nsACString& value, bool merge,
+ HeaderVariety variety);
+ [[nodiscard]] nsresult SetHeader(const nsHttpAtom& header,
+ const nsACString& headerName,
+ const nsACString& value, bool merge,
+ HeaderVariety variety);
+
+ // Used by internal setters to set an empty header
+ [[nodiscard]] nsresult SetEmptyHeader(const nsACString& headerName,
+ HeaderVariety variety);
+
+ // Merges supported headers. For other duplicate values, determines if error
+ // needs to be thrown or 1st value kept.
+ // For the response header we keep the original headers as well.
+ [[nodiscard]] nsresult SetHeaderFromNet(const nsHttpAtom& header,
+ const nsACString& headerNameOriginal,
+ const nsACString& value,
+ bool response);
+
+ [[nodiscard]] nsresult SetResponseHeaderFromCache(
+ const nsHttpAtom& header, const nsACString& headerNameOriginal,
+ const nsACString& value, HeaderVariety variety);
+
+ [[nodiscard]] nsresult GetHeader(const nsHttpAtom& header,
+ nsACString& result) const;
+ [[nodiscard]] nsresult GetOriginalHeader(const nsHttpAtom& aHeader,
+ nsIHttpHeaderVisitor* aVisitor);
+ void ClearHeader(const nsHttpAtom& h);
+
+ // Find the location of the given header value, or null if none exists.
+ const char* FindHeaderValue(const nsHttpAtom& header,
+ const char* value) const {
+ return nsHttp::FindToken(PeekHeader(header), value, HTTP_HEADER_VALUE_SEPS);
+ }
+
+ // Determine if the given header value exists.
+ bool HasHeaderValue(const nsHttpAtom& header, const char* value) const {
+ return FindHeaderValue(header, value) != nullptr;
+ }
+
+ bool HasHeader(const nsHttpAtom& header) const;
+
+ enum VisitorFilter {
+ eFilterAll,
+ eFilterSkipDefault,
+ eFilterResponse,
+ eFilterResponseOriginal
+ };
+
+ [[nodiscard]] nsresult VisitHeaders(nsIHttpHeaderVisitor* visitor,
+ VisitorFilter filter = eFilterAll);
+
+ // parse a header line, return the header atom, the header name, and the
+ // header value
+ [[nodiscard]] static nsresult ParseHeaderLine(
+ const nsACString& line, nsHttpAtom* hdr = nullptr,
+ nsACString* headerNameOriginal = nullptr, nsACString* value = nullptr);
+
+ void Flatten(nsACString&, bool pruneProxyHeaders, bool pruneTransients);
+ void FlattenOriginalHeader(nsACString&);
+
+ uint32_t Count() const { return mHeaders.Length(); }
+
+ const char* PeekHeaderAt(uint32_t i, nsHttpAtom& header,
+ nsACString& headerNameOriginal) const;
+
+ void Clear();
+
+ // Must be copy-constructable and assignable
+ struct nsEntry {
+ nsHttpAtom header;
+ nsCString headerNameOriginal;
+ nsCString value;
+ HeaderVariety variety = eVarietyUnknown;
+
+ struct MatchHeader {
+ bool Equals(const nsEntry& aEntry, const nsHttpAtom& aHeader) const {
+ return aEntry.header == aHeader;
+ }
+ };
+
+ bool operator==(const nsEntry& aOther) const {
+ return header == aOther.header && value == aOther.value;
+ }
+ };
+
+ bool operator==(const nsHttpHeaderArray& aOther) const {
+ return mHeaders == aOther.mHeaders;
+ }
+
+ private:
+ // LookupEntry function will never return eVarietyResponseNetOriginal.
+ // It will ignore original headers from the network.
+ int32_t LookupEntry(const nsHttpAtom& header, const nsEntry**) const;
+ int32_t LookupEntry(const nsHttpAtom& header, nsEntry**);
+ [[nodiscard]] nsresult MergeHeader(const nsHttpAtom& header, nsEntry* entry,
+ const nsACString& value,
+ HeaderVariety variety);
+ [[nodiscard]] nsresult SetHeader_internal(const nsHttpAtom& header,
+ const nsACString& headerName,
+ const nsACString& value,
+ HeaderVariety variety);
+
+ // Header cannot be merged: only one value possible
+ bool IsSingletonHeader(const nsHttpAtom& header);
+ // Header cannot be merged, and subsequent values should be ignored
+ bool IsIgnoreMultipleHeader(const nsHttpAtom& header);
+
+ // Subset of singleton headers: should never see multiple, different
+ // instances of these, else something fishy may be going on (like CLRF
+ // injection)
+ bool IsSuspectDuplicateHeader(const nsHttpAtom& header);
+
+ // Removes duplicate header values entries
+ // Will return unmodified header value if the header values contains
+ // non-duplicate entries
+ void RemoveDuplicateHeaderValues(const nsACString& aHeaderValue,
+ nsACString& aResult);
+
+ // All members must be copy-constructable and assignable
+ CopyableTArray<nsEntry> mHeaders;
+
+ friend struct IPC::ParamTraits<nsHttpHeaderArray>;
+ friend class nsHttpRequestHead;
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpHeaderArray <private>: inline functions
+//-----------------------------------------------------------------------------
+
+inline int32_t nsHttpHeaderArray::LookupEntry(const nsHttpAtom& header,
+ const nsEntry** entry) const {
+ uint32_t index = 0;
+ while (index != UINT32_MAX) {
+ index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
+ if (index != UINT32_MAX) {
+ if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
+ *entry = &mHeaders[index];
+ return index;
+ }
+ index++;
+ }
+ }
+
+ return index;
+}
+
+inline int32_t nsHttpHeaderArray::LookupEntry(const nsHttpAtom& header,
+ nsEntry** entry) {
+ uint32_t index = 0;
+ while (index != UINT32_MAX) {
+ index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
+ if (index != UINT32_MAX) {
+ if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
+ *entry = &mHeaders[index];
+ return index;
+ }
+ index++;
+ }
+ }
+ return index;
+}
+
+inline bool nsHttpHeaderArray::IsSingletonHeader(const nsHttpAtom& header) {
+ return header == nsHttp::Content_Type ||
+ header == nsHttp::Content_Disposition ||
+ header == nsHttp::Content_Length || header == nsHttp::User_Agent ||
+ header == nsHttp::Referer || header == nsHttp::Host ||
+ header == nsHttp::Authorization ||
+ header == nsHttp::Proxy_Authorization ||
+ header == nsHttp::If_Modified_Since ||
+ header == nsHttp::If_Unmodified_Since || header == nsHttp::From ||
+ header == nsHttp::Location || header == nsHttp::Max_Forwards ||
+ header == nsHttp::GlobalPrivacyControl ||
+ // Ignore-multiple-headers are singletons in the sense that they
+ // shouldn't be merged.
+ IsIgnoreMultipleHeader(header);
+}
+
+// These are headers for which, in the presence of multiple values, we only
+// consider the first.
+inline bool nsHttpHeaderArray::IsIgnoreMultipleHeader(
+ const nsHttpAtom& header) {
+ // https://tools.ietf.org/html/rfc6797#section-8:
+ //
+ // If a UA receives more than one STS header field in an HTTP
+ // response message over secure transport, then the UA MUST process
+ // only the first such header field.
+ return header == nsHttp::Strict_Transport_Security;
+}
+
+[[nodiscard]] inline nsresult nsHttpHeaderArray::MergeHeader(
+ const nsHttpAtom& header, nsEntry* entry, const nsACString& value,
+ nsHttpHeaderArray::HeaderVariety variety) {
+ // merge of empty header = no-op
+ if (value.IsEmpty() && header != nsHttp::X_Frame_Options) {
+ return NS_OK;
+ }
+
+ // x-frame-options having an empty header value still has an effect so we make
+ // sure that we retain encountering it
+ nsCString newValue = entry->value;
+ if (!newValue.IsEmpty() || header == nsHttp::X_Frame_Options) {
+ // Append the new value to the existing value
+ if (header == nsHttp::Set_Cookie || header == nsHttp::WWW_Authenticate ||
+ header == nsHttp::Proxy_Authenticate) {
+ // Special case these headers and use a newline delimiter to
+ // delimit the values from one another as commas may appear
+ // in the values of these headers contrary to what the spec says.
+ newValue.Append('\n');
+ } else {
+ // Delimit each value from the others using a comma (per HTTP spec)
+ newValue.AppendLiteral(", ");
+ }
+ }
+
+ newValue.Append(value);
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ // Copy entry->headerNameOriginal because in SetHeader_internal we are going
+ // to a new one and a realocation can happen.
+ nsCString headerNameOriginal = entry->headerNameOriginal;
+ nsresult rv = SetHeader_internal(header, headerNameOriginal, newValue,
+ eVarietyResponse);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ entry->value = newValue;
+ entry->variety = variety;
+ }
+ return NS_OK;
+}
+
+inline bool nsHttpHeaderArray::IsSuspectDuplicateHeader(
+ const nsHttpAtom& header) {
+ bool retval = header == nsHttp::Content_Length ||
+ header == nsHttp::Content_Disposition ||
+ header == nsHttp::Location;
+
+ MOZ_ASSERT(!retval || IsSingletonHeader(header),
+ "Only non-mergeable headers should be in this list\n");
+
+ return retval;
+}
+
+inline void nsHttpHeaderArray::RemoveDuplicateHeaderValues(
+ const nsACString& aHeaderValue, nsACString& aResult) {
+ mozilla::Maybe<nsAutoCString> result;
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(aHeaderValue, ',').ToRange()) {
+ if (result.isNothing()) {
+ // assign the first value
+ result.emplace(token);
+ continue;
+ }
+ if (*result != token) {
+ // non-identical header values. Do not change the header values
+ result.reset();
+ break;
+ }
+ }
+
+ if (result.isSome()) {
+ aResult = *result;
+ } else {
+ // either header values do not have multiple values or
+ // has unequal multiple values
+ // for both the cases restore the original header value
+ aResult = aHeaderValue;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/nsHttpNTLMAuth.cpp b/netwerk/protocol/http/nsHttpNTLMAuth.cpp
new file mode 100644
index 0000000000..0d8f5f083a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpNTLMAuth.cpp
@@ -0,0 +1,404 @@
+/* vim:set ts=4 sw=2 sts=2 et ci: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpNTLMAuth.h"
+#include "nsIAuthModule.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "plbase64.h"
+#include "prnetdb.h"
+
+//-----------------------------------------------------------------------------
+
+#include "nsIPrefBranch.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIURI.h"
+#ifdef XP_WIN
+# include "nsIChannel.h"
+# include "nsIX509Cert.h"
+# include "nsITransportSecurityInfo.h"
+#endif
+#include "mozilla/Attributes.h"
+#include "mozilla/Base64.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/net/HttpAuthUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/StaticPrefs_browser.h"
+
+namespace mozilla {
+namespace net {
+
+static const char kAllowProxies[] = "network.automatic-ntlm-auth.allow-proxies";
+static const char kAllowNonFqdn[] =
+ "network.automatic-ntlm-auth.allow-non-fqdn";
+static const char kTrustedURIs[] = "network.automatic-ntlm-auth.trusted-uris";
+static const char kForceGeneric[] = "network.auth.force-generic-ntlm";
+static const char kSSOinPBmode[] = "network.auth.private-browsing-sso";
+
+StaticRefPtr<nsHttpNTLMAuth> nsHttpNTLMAuth::gSingleton;
+
+static bool IsNonFqdn(nsIURI* uri) {
+ nsAutoCString host;
+ if (NS_FAILED(uri->GetAsciiHost(host))) {
+ return false;
+ }
+
+ // return true if host does not contain a dot and is not an ip address
+ return !host.IsEmpty() && !host.Contains('.') && !HostIsIPLiteral(host);
+}
+
+// Check to see if we should use our generic (internal) NTLM auth module.
+static bool ForceGenericNTLM() {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) return false;
+ bool flag = false;
+
+ if (NS_FAILED(prefs->GetBoolPref(kForceGeneric, &flag))) flag = false;
+
+ LOG(("Force use of generic ntlm auth module: %d\n", flag));
+ return flag;
+}
+
+// Check to see if we should use default credentials for this host or proxy.
+static bool CanUseDefaultCredentials(nsIHttpAuthenticableChannel* channel,
+ bool isProxyAuth) {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) {
+ return false;
+ }
+
+ // Proxy should go all the time, it's not considered a privacy leak
+ // to send default credentials to a proxy.
+ if (isProxyAuth) {
+ bool val;
+ if (NS_FAILED(prefs->GetBoolPref(kAllowProxies, &val))) val = false;
+ LOG(("Default credentials allowed for proxy: %d\n", val));
+ return val;
+ }
+
+ // Prevent using default credentials for authentication when we are in the
+ // private browsing mode (but not in "never remember history" mode) and when
+ // not explicitely allowed. Otherwise, it would cause a privacy data leak.
+ nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(channel);
+ MOZ_ASSERT(bareChannel);
+
+ if (NS_UsePrivateBrowsing(bareChannel)) {
+ bool ssoInPb;
+ if (NS_SUCCEEDED(prefs->GetBoolPref(kSSOinPBmode, &ssoInPb)) && ssoInPb) {
+ return true;
+ }
+
+ if (!StaticPrefs::browser_privatebrowsing_autostart()) {
+ return false;
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ Unused << channel->GetURI(getter_AddRefs(uri));
+
+ bool allowNonFqdn;
+ if (NS_FAILED(prefs->GetBoolPref(kAllowNonFqdn, &allowNonFqdn))) {
+ allowNonFqdn = false;
+ }
+ if (allowNonFqdn && uri && IsNonFqdn(uri)) {
+ LOG(("Host is non-fqdn, default credentials are allowed\n"));
+ return true;
+ }
+
+ bool isTrustedHost = (uri && auth::URIMatchesPrefPattern(uri, kTrustedURIs));
+ LOG(("Default credentials allowed for host: %d\n", isTrustedHost));
+ return isTrustedHost;
+}
+
+// Dummy class for session state object. This class doesn't hold any data.
+// Instead we use its existence as a flag. See ChallengeReceived.
+class nsNTLMSessionState final : public nsISupports {
+ ~nsNTLMSessionState() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+};
+NS_IMPL_ISUPPORTS0(nsNTLMSessionState)
+
+//-----------------------------------------------------------------------------
+
+already_AddRefed<nsIHttpAuthenticator> nsHttpNTLMAuth::GetOrCreate() {
+ nsCOMPtr<nsIHttpAuthenticator> authenticator;
+ if (gSingleton) {
+ authenticator = gSingleton;
+ } else {
+ gSingleton = new nsHttpNTLMAuth();
+ ClearOnShutdown(&gSingleton);
+ authenticator = gSingleton;
+ }
+
+ return authenticator.forget();
+}
+
+NS_IMPL_ISUPPORTS(nsHttpNTLMAuth, nsIHttpAuthenticator)
+
+NS_IMETHODIMP
+nsHttpNTLMAuth::ChallengeReceived(nsIHttpAuthenticableChannel* channel,
+ const nsACString& challenge, bool isProxyAuth,
+ nsISupports** sessionState,
+ nsISupports** continuationState,
+ bool* identityInvalid) {
+ LOG(("nsHttpNTLMAuth::ChallengeReceived [ss=%p cs=%p]\n", *sessionState,
+ *continuationState));
+
+ // Use the native NTLM if available
+ mUseNative = true;
+
+ // NOTE: we don't define any session state, but we do use the pointer.
+
+ *identityInvalid = false;
+
+ // Start a new auth sequence if the challenge is exactly "NTLM".
+ // If native NTLM auth apis are available and enabled through prefs,
+ // try to use them.
+ if (challenge.Equals("NTLM"_ns, nsCaseInsensitiveCStringComparator)) {
+ nsCOMPtr<nsIAuthModule> module;
+
+#ifdef MOZ_AUTH_EXTENSION
+ // Check to see if we should default to our generic NTLM auth module
+ // through UseGenericNTLM. (We use native auth by default if the
+ // system provides it.) If *sessionState is non-null, we failed to
+ // instantiate a native NTLM module the last time, so skip trying again.
+ bool forceGeneric = ForceGenericNTLM();
+ if (!forceGeneric && !*sessionState) {
+ // Check for approved default credentials hosts and proxies. If
+ // *continuationState is non-null, the last authentication attempt
+ // failed so skip default credential use.
+ if (!*continuationState &&
+ CanUseDefaultCredentials(channel, isProxyAuth)) {
+ // Try logging in with the user's default credentials. If
+ // successful, |identityInvalid| is false, which will trigger
+ // a default credentials attempt once we return.
+ module = nsIAuthModule::CreateInstance("sys-ntlm");
+ }
+# ifdef XP_WIN
+ else {
+ // Try to use native NTLM and prompt the user for their domain,
+ // username, and password. (only supported by windows nsAuthSSPI
+ // module.) Note, for servers that use LMv1 a weak hash of the user's
+ // password will be sent. We rely on windows internal apis to decide
+ // whether we should support this older, less secure version of the
+ // protocol.
+ module = nsIAuthModule::CreateInstance("sys-ntlm");
+ *identityInvalid = true;
+ }
+# endif // XP_WIN
+ if (!module) LOG(("Native sys-ntlm auth module not found.\n"));
+ }
+
+# ifdef XP_WIN
+ // On windows, never fall back unless the user has specifically requested
+ // so.
+ if (!forceGeneric && !module) return NS_ERROR_UNEXPECTED;
+# endif
+
+ // If no native support was available. Fall back on our internal NTLM
+ // implementation.
+ if (!module) {
+ if (!*sessionState) {
+ // Remember the fact that we cannot use the "sys-ntlm" module,
+ // so we don't ever bother trying again for this auth domain.
+ RefPtr<nsNTLMSessionState> state = new nsNTLMSessionState();
+ state.forget(sessionState);
+ }
+
+ // Use our internal NTLM implementation. Note, this is less secure,
+ // see bug 520607 for details.
+ LOG(("Trying to fall back on internal ntlm auth.\n"));
+ module = nsIAuthModule::CreateInstance("ntlm");
+
+ mUseNative = false;
+
+ // Prompt user for domain, username, and password.
+ *identityInvalid = true;
+ }
+#endif
+
+ // If this fails, then it means that we cannot do NTLM auth.
+ if (!module) {
+ LOG(("No ntlm auth modules available.\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // A non-null continuation state implies that we failed to authenticate.
+ // Blow away the old authentication state, and use the new one.
+ module.forget(continuationState);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpNTLMAuth::GenerateCredentialsAsync(
+ nsIHttpAuthenticableChannel* authChannel,
+ nsIHttpAuthenticatorCallback* aCallback, const nsACString& challenge,
+ bool isProxyAuth, const nsAString& domain, const nsAString& username,
+ const nsAString& password, nsISupports* sessionState,
+ nsISupports* continuationState, nsICancelable** aCancellable) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpNTLMAuth::GenerateCredentials(
+ nsIHttpAuthenticableChannel* authChannel, const nsACString& aChallenge,
+ bool isProxyAuth, const nsAString& domain, const nsAString& user,
+ const nsAString& pass, nsISupports** sessionState,
+ nsISupports** continuationState, uint32_t* aFlags, nsACString& creds)
+
+{
+ LOG(("nsHttpNTLMAuth::GenerateCredentials\n"));
+
+ creds.Truncate();
+ *aFlags = 0;
+
+ // if user or password is empty, ChallengeReceived returned
+ // identityInvalid = false, that means we are using default user
+ // credentials; see nsAuthSSPI::Init method for explanation of this
+ // condition
+ if (user.IsEmpty() || pass.IsEmpty()) *aFlags = USING_INTERNAL_IDENTITY;
+
+ nsresult rv;
+ nsCOMPtr<nsIAuthModule> module = do_QueryInterface(*continuationState, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ void *inBuf, *outBuf;
+ uint32_t inBufLen, outBufLen;
+ Maybe<nsTArray<uint8_t>> certArray;
+
+ // initial challenge
+ if (aChallenge.Equals("NTLM"_ns, nsCaseInsensitiveCStringComparator)) {
+ // NTLM service name format is 'HTTP@host' for both http and https
+ nsCOMPtr<nsIURI> uri;
+ rv = authChannel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+ nsAutoCString serviceName, host;
+ rv = uri->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+ serviceName.AppendLiteral("HTTP@");
+ serviceName.Append(host);
+ // initialize auth module
+ uint32_t reqFlags = nsIAuthModule::REQ_DEFAULT;
+ if (isProxyAuth) reqFlags |= nsIAuthModule::REQ_PROXY_AUTH;
+
+ rv = module->Init(serviceName, reqFlags, domain, user, pass);
+ if (NS_FAILED(rv)) return rv;
+
+ inBufLen = 0;
+ inBuf = nullptr;
+// This update enables updated Windows machines (Win7 or patched previous
+// versions) and Linux machines running Samba (updated for Channel
+// Binding), to perform Channel Binding when authenticating using NTLMv2
+// and an outer secure channel.
+//
+// Currently only implemented for Windows, linux support will be landing in
+// a separate patch, update this #ifdef accordingly then.
+// Extended protection update is just for Linux and Windows machines.
+#if defined(XP_WIN) /* || defined (LINUX) */
+ // We should retrieve the server certificate and compute the CBT,
+ // but only when we are using the native NTLM implementation and
+ // not the internal one.
+ // It is a valid case not having the security info object. This
+ // occures when we connect an https site through an ntlm proxy.
+ // After the ssl tunnel has been created, we get here the second
+ // time and now generate the CBT from now valid security info.
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(authChannel, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ rv = channel->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (NS_FAILED(rv)) return rv;
+
+ if (mUseNative && securityInfo) {
+ nsCOMPtr<nsIX509Cert> cert;
+ rv = securityInfo->GetServerCert(getter_AddRefs(cert));
+ if (NS_FAILED(rv)) return rv;
+
+ if (cert) {
+ certArray.emplace();
+ rv = cert->GetRawDER(*certArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If there is a server certificate, we pass it along the
+ // first time we call GetNextToken().
+ inBufLen = certArray->Length();
+ inBuf = certArray->Elements();
+ }
+ }
+#endif
+ } else {
+ // decode challenge; skip past "NTLM " to the start of the base64
+ // encoded data.
+ if (aChallenge.Length() < 6) {
+ return NS_ERROR_UNEXPECTED; // bogus challenge
+ }
+
+ // strip off any padding (see bug 230351)
+ nsDependentCSubstring challenge(aChallenge, 5);
+ uint32_t len = challenge.Length();
+ while (len > 0 && challenge[len - 1] == '=') {
+ len--;
+ }
+
+ // decode into the input secbuffer
+ rv = Base64Decode(challenge.BeginReading(), len, (char**)&inBuf, &inBufLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ rv = module->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen);
+ if (NS_SUCCEEDED(rv)) {
+ // base64 encode data in output buffer and prepend "NTLM "
+ CheckedUint32 credsLen = ((CheckedUint32(outBufLen) + 2) / 3) * 4;
+ credsLen += 5; // "NTLM "
+ credsLen += 1; // null terminate
+
+ if (!credsLen.isValid()) {
+ rv = NS_ERROR_FAILURE;
+ } else {
+ nsAutoCString encoded;
+ (void)Base64Encode(nsDependentCSubstring((char*)outBuf, outBufLen),
+ encoded);
+ creds = nsPrintfCString("NTLM %s", encoded.get());
+ }
+
+ // OK, we are done with |outBuf|
+ free(outBuf);
+ }
+
+ // inBuf needs to be freed if it's not pointing into certArray
+ if (inBuf && !certArray) {
+ free(inBuf);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpNTLMAuth::GetAuthFlags(uint32_t* flags) {
+ *flags = CONNECTION_BASED | IDENTITY_INCLUDES_DOMAIN | IDENTITY_ENCRYPTED;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpNTLMAuth.h b/netwerk/protocol/http/nsHttpNTLMAuth.h
new file mode 100644
index 0000000000..a1fafc4a10
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpNTLMAuth.h
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpNTLMAuth_h__
+#define nsHttpNTLMAuth_h__
+
+#include "nsIHttpAuthenticator.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace net {
+
+class nsHttpNTLMAuth : public nsIHttpAuthenticator {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHENTICATOR
+
+ nsHttpNTLMAuth() = default;
+
+ static already_AddRefed<nsIHttpAuthenticator> GetOrCreate();
+
+ private:
+ virtual ~nsHttpNTLMAuth() = default;
+
+ // This flag indicates whether we are using the native NTLM implementation
+ // or the internal one.
+ bool mUseNative{false};
+
+ static StaticRefPtr<nsHttpNTLMAuth> gSingleton;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsHttpNTLMAuth_h__
diff --git a/netwerk/protocol/http/nsHttpRequestHead.cpp b/netwerk/protocol/http/nsHttpRequestHead.cpp
new file mode 100644
index 0000000000..0ce7a14f8a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpRequestHead.cpp
@@ -0,0 +1,367 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpRequestHead.h"
+#include "nsIHttpHeaderVisitor.h"
+
+//-----------------------------------------------------------------------------
+// nsHttpRequestHead
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+nsHttpRequestHead::nsHttpRequestHead() { MOZ_COUNT_CTOR(nsHttpRequestHead); }
+
+nsHttpRequestHead::nsHttpRequestHead(const nsHttpRequestHead& aRequestHead) {
+ MOZ_COUNT_CTOR(nsHttpRequestHead);
+ nsHttpRequestHead& other = const_cast<nsHttpRequestHead&>(aRequestHead);
+ RecursiveMutexAutoLock monitor(other.mRecursiveMutex);
+
+ mHeaders = other.mHeaders;
+ mMethod = other.mMethod;
+ mVersion = other.mVersion;
+ mRequestURI = other.mRequestURI;
+ mPath = other.mPath;
+ mOrigin = other.mOrigin;
+ mParsedMethod = other.mParsedMethod;
+ mHTTPS = other.mHTTPS;
+ mInVisitHeaders = false;
+}
+
+nsHttpRequestHead::nsHttpRequestHead(nsHttpRequestHead&& aRequestHead) {
+ MOZ_COUNT_CTOR(nsHttpRequestHead);
+ nsHttpRequestHead& other = aRequestHead;
+ RecursiveMutexAutoLock monitor(other.mRecursiveMutex);
+
+ mHeaders = std::move(other.mHeaders);
+ mMethod = std::move(other.mMethod);
+ mVersion = std::move(other.mVersion);
+ mRequestURI = std::move(other.mRequestURI);
+ mPath = std::move(other.mPath);
+ mOrigin = std::move(other.mOrigin);
+ mParsedMethod = std::move(other.mParsedMethod);
+ mHTTPS = std::move(other.mHTTPS);
+ mInVisitHeaders = false;
+}
+
+nsHttpRequestHead::~nsHttpRequestHead() { MOZ_COUNT_DTOR(nsHttpRequestHead); }
+
+nsHttpRequestHead& nsHttpRequestHead::operator=(
+ const nsHttpRequestHead& aRequestHead) {
+ nsHttpRequestHead& other = const_cast<nsHttpRequestHead&>(aRequestHead);
+ RecursiveMutexAutoLock monitorOther(other.mRecursiveMutex);
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ mHeaders = other.mHeaders;
+ mMethod = other.mMethod;
+ mVersion = other.mVersion;
+ mRequestURI = other.mRequestURI;
+ mPath = other.mPath;
+ mOrigin = other.mOrigin;
+ mParsedMethod = other.mParsedMethod;
+ mHTTPS = other.mHTTPS;
+ mInVisitHeaders = false;
+ return *this;
+}
+
+// Don't use this function. It is only used by HttpChannelParent to avoid
+// copying of request headers!!!
+const nsHttpHeaderArray& nsHttpRequestHead::Headers() const {
+ nsHttpRequestHead& curr = const_cast<nsHttpRequestHead&>(*this);
+ curr.mRecursiveMutex.AssertCurrentThreadIn();
+ return mHeaders;
+}
+
+void nsHttpRequestHead::SetHeaders(const nsHttpHeaderArray& aHeaders) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mHeaders = aHeaders;
+}
+
+void nsHttpRequestHead::SetVersion(HttpVersion version) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mVersion = version;
+}
+
+void nsHttpRequestHead::SetRequestURI(const nsACString& s) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mRequestURI = s;
+}
+
+void nsHttpRequestHead::SetPath(const nsACString& s) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mPath = s;
+}
+
+uint32_t nsHttpRequestHead::HeaderCount() {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mHeaders.Count();
+}
+
+nsresult nsHttpRequestHead::VisitHeaders(
+ nsIHttpHeaderVisitor* visitor,
+ nsHttpHeaderArray::VisitorFilter
+ filter /* = nsHttpHeaderArray::eFilterAll*/) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mInVisitHeaders = true;
+ nsresult rv = mHeaders.VisitHeaders(visitor, filter);
+ mInVisitHeaders = false;
+ return rv;
+}
+
+void nsHttpRequestHead::Method(nsACString& aMethod) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ aMethod = mMethod;
+}
+
+HttpVersion nsHttpRequestHead::Version() {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mVersion;
+}
+
+void nsHttpRequestHead::RequestURI(nsACString& aRequestURI) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ aRequestURI = mRequestURI;
+}
+
+void nsHttpRequestHead::Path(nsACString& aPath) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ aPath = mPath.IsEmpty() ? mRequestURI : mPath;
+}
+
+void nsHttpRequestHead::SetHTTPS(bool val) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mHTTPS = val;
+}
+
+void nsHttpRequestHead::Origin(nsACString& aOrigin) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ aOrigin = mOrigin;
+}
+
+nsresult nsHttpRequestHead::SetHeader(const nsACString& h, const nsACString& v,
+ bool m /*= false*/) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mHeaders.SetHeader(h, v, m,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+}
+
+nsresult nsHttpRequestHead::SetHeader(const nsHttpAtom& h, const nsACString& v,
+ bool m /*= false*/) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mHeaders.SetHeader(h, v, m,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+}
+
+nsresult nsHttpRequestHead::SetHeader(
+ const nsHttpAtom& h, const nsACString& v, bool m,
+ nsHttpHeaderArray::HeaderVariety variety) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mHeaders.SetHeader(h, v, m, variety);
+}
+
+nsresult nsHttpRequestHead::SetEmptyHeader(const nsACString& h) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mHeaders.SetEmptyHeader(h, nsHttpHeaderArray::eVarietyRequestOverride);
+}
+
+nsresult nsHttpRequestHead::GetHeader(const nsHttpAtom& h, nsACString& v) {
+ v.Truncate();
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mHeaders.GetHeader(h, v);
+}
+
+nsresult nsHttpRequestHead::ClearHeader(const nsHttpAtom& h) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mHeaders.ClearHeader(h);
+ return NS_OK;
+}
+
+void nsHttpRequestHead::ClearHeaders() {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return;
+ }
+
+ mHeaders.Clear();
+}
+
+bool nsHttpRequestHead::HasHeader(const nsHttpAtom& h) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mHeaders.HasHeader(h);
+}
+
+bool nsHttpRequestHead::HasHeaderValue(const nsHttpAtom& h, const char* v) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mHeaders.HasHeaderValue(h, v);
+}
+
+nsresult nsHttpRequestHead::SetHeaderOnce(const nsHttpAtom& h, const char* v,
+ bool merge /*= false */) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!merge || !mHeaders.HasHeaderValue(h, v)) {
+ return mHeaders.SetHeader(h, nsDependentCString(v), merge,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+ }
+ return NS_OK;
+}
+
+nsHttpRequestHead::ParsedMethodType nsHttpRequestHead::ParsedMethod() {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mParsedMethod;
+}
+
+bool nsHttpRequestHead::EqualsMethod(ParsedMethodType aType) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mParsedMethod == aType;
+}
+
+void nsHttpRequestHead::ParseHeaderSet(const char* buffer) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ nsHttpAtom hdr;
+ nsAutoCString headerNameOriginal;
+ nsAutoCString val;
+ while (buffer) {
+ const char* eof = strchr(buffer, '\r');
+ if (!eof) {
+ break;
+ }
+ if (NS_SUCCEEDED(nsHttpHeaderArray::ParseHeaderLine(
+ nsDependentCSubstring(buffer, eof - buffer), &hdr,
+ &headerNameOriginal, &val))) {
+ DebugOnly<nsresult> rv =
+ mHeaders.SetHeaderFromNet(hdr, headerNameOriginal, val, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ buffer = eof + 1;
+ if (*buffer == '\n') {
+ buffer++;
+ }
+ }
+}
+
+bool nsHttpRequestHead::IsHTTPS() {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mHTTPS;
+}
+
+void nsHttpRequestHead::SetMethod(const nsACString& method) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+
+ mMethod = method;
+ ParseMethod(mMethod, mParsedMethod);
+}
+
+// static
+void nsHttpRequestHead::ParseMethod(const nsCString& aRawMethod,
+ ParsedMethodType& aParsedMethod) {
+ aParsedMethod = kMethod_Custom;
+ if (!strcmp(aRawMethod.get(), "GET")) {
+ aParsedMethod = kMethod_Get;
+ } else if (!strcmp(aRawMethod.get(), "POST")) {
+ aParsedMethod = kMethod_Post;
+ } else if (!strcmp(aRawMethod.get(), "OPTIONS")) {
+ aParsedMethod = kMethod_Options;
+ } else if (!strcmp(aRawMethod.get(), "CONNECT")) {
+ aParsedMethod = kMethod_Connect;
+ } else if (!strcmp(aRawMethod.get(), "HEAD")) {
+ aParsedMethod = kMethod_Head;
+ } else if (!strcmp(aRawMethod.get(), "PUT")) {
+ aParsedMethod = kMethod_Put;
+ } else if (!strcmp(aRawMethod.get(), "TRACE")) {
+ aParsedMethod = kMethod_Trace;
+ }
+}
+
+void nsHttpRequestHead::SetOrigin(const nsACString& scheme,
+ const nsACString& host, int32_t port) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ mOrigin.Assign(scheme);
+ mOrigin.AppendLiteral("://");
+ mOrigin.Append(host);
+ if (port >= 0) {
+ mOrigin.AppendLiteral(":");
+ mOrigin.AppendInt(port);
+ }
+}
+
+bool nsHttpRequestHead::IsSafeMethod() {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ // This code will need to be extended for new safe methods, otherwise
+ // they'll default to "not safe".
+ if ((mParsedMethod == kMethod_Get) || (mParsedMethod == kMethod_Head) ||
+ (mParsedMethod == kMethod_Options) || (mParsedMethod == kMethod_Trace)) {
+ return true;
+ }
+
+ if (mParsedMethod != kMethod_Custom) {
+ return false;
+ }
+
+ return (!strcmp(mMethod.get(), "PROPFIND") ||
+ !strcmp(mMethod.get(), "REPORT") || !strcmp(mMethod.get(), "SEARCH"));
+}
+
+void nsHttpRequestHead::Flatten(nsACString& buf, bool pruneProxyHeaders) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ // note: the first append is intentional.
+
+ buf.Append(mMethod);
+ buf.Append(' ');
+ buf.Append(mRequestURI);
+ buf.AppendLiteral(" HTTP/");
+
+ switch (mVersion) {
+ case HttpVersion::v1_1:
+ buf.AppendLiteral("1.1");
+ break;
+ case HttpVersion::v0_9:
+ buf.AppendLiteral("0.9");
+ break;
+ default:
+ buf.AppendLiteral("1.0");
+ }
+
+ buf.AppendLiteral("\r\n");
+
+ mHeaders.Flatten(buf, pruneProxyHeaders, false);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpRequestHead.h b/netwerk/protocol/http/nsHttpRequestHead.h
new file mode 100644
index 0000000000..e308790bd4
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpRequestHead.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpRequestHead_h__
+#define nsHttpRequestHead_h__
+
+#include "nsHttp.h"
+#include "nsHttpHeaderArray.h"
+#include "nsString.h"
+#include "mozilla/RecursiveMutex.h"
+
+class nsIHttpHeaderVisitor;
+
+// This needs to be forward declared here so we can include only this header
+// without also including PHttpChannelParams.h
+namespace IPC {
+template <typename>
+struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpRequestHead represents the request line and headers from an HTTP
+// request.
+//-----------------------------------------------------------------------------
+
+class nsHttpRequestHead {
+ public:
+ nsHttpRequestHead();
+ explicit nsHttpRequestHead(const nsHttpRequestHead& aRequestHead);
+ nsHttpRequestHead(nsHttpRequestHead&& aRequestHead);
+ ~nsHttpRequestHead();
+
+ nsHttpRequestHead& operator=(const nsHttpRequestHead& aRequestHead);
+
+ // The following function is only used in HttpChannelParent to avoid
+ // copying headers. If you use it be careful to do it only under
+ // nsHttpRequestHead lock!!!
+ const nsHttpHeaderArray& Headers() const MOZ_REQUIRES(mRecursiveMutex);
+ void Enter() const MOZ_CAPABILITY_ACQUIRE(mRecursiveMutex) {
+ mRecursiveMutex.Lock();
+ }
+ void Exit() const MOZ_CAPABILITY_RELEASE(mRecursiveMutex) {
+ mRecursiveMutex.Unlock();
+ }
+
+ void SetHeaders(const nsHttpHeaderArray& aHeaders);
+
+ void SetMethod(const nsACString& method);
+ void SetVersion(HttpVersion version);
+ void SetRequestURI(const nsACString& s);
+ void SetPath(const nsACString& s);
+ uint32_t HeaderCount();
+
+ // Using this function it is possible to itereate through all headers
+ // automatically under one lock.
+ [[nodiscard]] nsresult VisitHeaders(
+ nsIHttpHeaderVisitor* visitor,
+ nsHttpHeaderArray::VisitorFilter filter = nsHttpHeaderArray::eFilterAll);
+ void Method(nsACString& aMethod);
+ HttpVersion Version();
+ void RequestURI(nsACString& RequestURI);
+ void Path(nsACString& aPath);
+ void SetHTTPS(bool val);
+ bool IsHTTPS();
+
+ void SetOrigin(const nsACString& scheme, const nsACString& host,
+ int32_t port);
+ void Origin(nsACString& aOrigin);
+
+ [[nodiscard]] nsresult SetHeader(const nsACString& h, const nsACString& v,
+ bool m = false);
+ [[nodiscard]] nsresult SetHeader(const nsHttpAtom& h, const nsACString& v,
+ bool m = false);
+ [[nodiscard]] nsresult SetHeader(const nsHttpAtom& h, const nsACString& v,
+ bool m,
+ nsHttpHeaderArray::HeaderVariety variety);
+ [[nodiscard]] nsresult SetEmptyHeader(const nsACString& h);
+ [[nodiscard]] nsresult GetHeader(const nsHttpAtom& h, nsACString& v);
+
+ [[nodiscard]] nsresult ClearHeader(const nsHttpAtom& h);
+ void ClearHeaders();
+
+ bool HasHeaderValue(const nsHttpAtom& h, const char* v);
+ // This function returns true if header is set even if it is an empty
+ // header.
+ bool HasHeader(const nsHttpAtom& h);
+ void Flatten(nsACString&, bool pruneProxyHeaders = false);
+
+ // Don't allow duplicate values
+ [[nodiscard]] nsresult SetHeaderOnce(const nsHttpAtom& h, const char* v,
+ bool merge = false);
+
+ bool IsSafeMethod();
+
+ enum ParsedMethodType {
+ kMethod_Custom,
+ kMethod_Get,
+ kMethod_Post,
+ kMethod_Options,
+ kMethod_Connect,
+ kMethod_Head,
+ kMethod_Put,
+ kMethod_Trace
+ };
+
+ static void ParseMethod(const nsCString& aRawMethod,
+ ParsedMethodType& aParsedMethod);
+
+ ParsedMethodType ParsedMethod();
+ bool EqualsMethod(ParsedMethodType aType);
+ bool IsGet() { return EqualsMethod(kMethod_Get); }
+ bool IsPost() { return EqualsMethod(kMethod_Post); }
+ bool IsOptions() { return EqualsMethod(kMethod_Options); }
+ bool IsConnect() { return EqualsMethod(kMethod_Connect); }
+ bool IsHead() { return EqualsMethod(kMethod_Head); }
+ bool IsPut() { return EqualsMethod(kMethod_Put); }
+ bool IsTrace() { return EqualsMethod(kMethod_Trace); }
+ void ParseHeaderSet(const char* buffer);
+
+ private:
+ // All members must be copy-constructable and assignable
+ nsHttpHeaderArray mHeaders MOZ_GUARDED_BY(mRecursiveMutex);
+ nsCString mMethod MOZ_GUARDED_BY(mRecursiveMutex){"GET"_ns};
+ HttpVersion mVersion MOZ_GUARDED_BY(mRecursiveMutex){HttpVersion::v1_1};
+
+ // mRequestURI and mPath are strings instead of an nsIURI
+ // because this is used off the main thread
+ // TODO: nsIURI is thread-safe now, should be fixable.
+ nsCString mRequestURI MOZ_GUARDED_BY(mRecursiveMutex);
+ nsCString mPath MOZ_GUARDED_BY(mRecursiveMutex);
+
+ nsCString mOrigin MOZ_GUARDED_BY(mRecursiveMutex);
+ ParsedMethodType mParsedMethod MOZ_GUARDED_BY(mRecursiveMutex){kMethod_Get};
+ bool mHTTPS MOZ_GUARDED_BY(mRecursiveMutex){false};
+
+ // We are using RecursiveMutex instead of a Mutex because VisitHeader
+ // function calls nsIHttpHeaderVisitor::VisitHeader while under lock.
+ mutable RecursiveMutex mRecursiveMutex MOZ_UNANNOTATED{
+ "nsHttpRequestHead.mRecursiveMutex"};
+
+ // During VisitHeader we sould not allow call to SetHeader.
+ bool mInVisitHeaders MOZ_GUARDED_BY(mRecursiveMutex){false};
+
+ friend struct IPC::ParamTraits<nsHttpRequestHead>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpRequestHead_h__
diff --git a/netwerk/protocol/http/nsHttpResponseHead.cpp b/netwerk/protocol/http/nsHttpResponseHead.cpp
new file mode 100644
index 0000000000..18f7beed3a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpResponseHead.cpp
@@ -0,0 +1,1220 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/dom/MimeType.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Unused.h"
+#include "nsHttpResponseHead.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsPrintfCString.h"
+#include "prtime.h"
+#include "nsCRT.h"
+#include "nsURLHelper.h"
+#include "CacheControlParser.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpResponseHead <public>
+//-----------------------------------------------------------------------------
+
+// Note that the code below MUST be synchronized with the IPC
+// serialization/deserialization in PHttpChannelParams.h.
+nsHttpResponseHead::nsHttpResponseHead(const nsHttpResponseHead& aOther) {
+ nsHttpResponseHead& other = const_cast<nsHttpResponseHead&>(aOther);
+ RecursiveMutexAutoLock monitor(other.mRecursiveMutex);
+
+ mHeaders = other.mHeaders;
+ mVersion = other.mVersion;
+ mStatus = other.mStatus;
+ mStatusText = other.mStatusText;
+ mContentLength = other.mContentLength;
+ mContentType = other.mContentType;
+ mContentCharset = other.mContentCharset;
+ mHasCacheControl = other.mHasCacheControl;
+ mCacheControlPublic = other.mCacheControlPublic;
+ mCacheControlPrivate = other.mCacheControlPrivate;
+ mCacheControlNoStore = other.mCacheControlNoStore;
+ mCacheControlNoCache = other.mCacheControlNoCache;
+ mCacheControlImmutable = other.mCacheControlImmutable;
+ mCacheControlStaleWhileRevalidateSet =
+ other.mCacheControlStaleWhileRevalidateSet;
+ mCacheControlStaleWhileRevalidate = other.mCacheControlStaleWhileRevalidate;
+ mCacheControlMaxAgeSet = other.mCacheControlMaxAgeSet;
+ mCacheControlMaxAge = other.mCacheControlMaxAge;
+ mPragmaNoCache = other.mPragmaNoCache;
+}
+
+nsHttpResponseHead& nsHttpResponseHead::operator=(
+ const nsHttpResponseHead& aOther) {
+ nsHttpResponseHead& other = const_cast<nsHttpResponseHead&>(aOther);
+ RecursiveMutexAutoLock monitorOther(other.mRecursiveMutex);
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ mHeaders = other.mHeaders;
+ mVersion = other.mVersion;
+ mStatus = other.mStatus;
+ mStatusText = other.mStatusText;
+ mContentLength = other.mContentLength;
+ mContentType = other.mContentType;
+ mContentCharset = other.mContentCharset;
+ mHasCacheControl = other.mHasCacheControl;
+ mCacheControlPublic = other.mCacheControlPublic;
+ mCacheControlPrivate = other.mCacheControlPrivate;
+ mCacheControlNoStore = other.mCacheControlNoStore;
+ mCacheControlNoCache = other.mCacheControlNoCache;
+ mCacheControlImmutable = other.mCacheControlImmutable;
+ mCacheControlStaleWhileRevalidateSet =
+ other.mCacheControlStaleWhileRevalidateSet;
+ mCacheControlStaleWhileRevalidate = other.mCacheControlStaleWhileRevalidate;
+ mCacheControlMaxAgeSet = other.mCacheControlMaxAgeSet;
+ mCacheControlMaxAge = other.mCacheControlMaxAge;
+ mPragmaNoCache = other.mPragmaNoCache;
+
+ return *this;
+}
+
+HttpVersion nsHttpResponseHead::Version() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mVersion;
+}
+
+uint16_t nsHttpResponseHead::Status() const {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mStatus;
+}
+
+void nsHttpResponseHead::StatusText(nsACString& aStatusText) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ aStatusText = mStatusText;
+}
+
+int64_t nsHttpResponseHead::ContentLength() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mContentLength;
+}
+
+void nsHttpResponseHead::ContentType(nsACString& aContentType) const {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ aContentType = mContentType;
+}
+
+void nsHttpResponseHead::ContentCharset(nsACString& aContentCharset) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ aContentCharset = mContentCharset;
+}
+
+bool nsHttpResponseHead::Public() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mCacheControlPublic;
+}
+
+bool nsHttpResponseHead::Private() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mCacheControlPrivate;
+}
+
+bool nsHttpResponseHead::NoStore() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mCacheControlNoStore;
+}
+
+bool nsHttpResponseHead::NoCache() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return NoCache_locked();
+}
+
+bool nsHttpResponseHead::Immutable() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mCacheControlImmutable;
+}
+
+nsresult nsHttpResponseHead::SetHeader(const nsACString& hdr,
+ const nsACString& val, bool merge) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(hdr);
+ if (!atom.get()) {
+ NS_WARNING("failed to resolve atom");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return SetHeader_locked(atom, hdr, val, merge);
+}
+
+nsresult nsHttpResponseHead::SetHeader(const nsHttpAtom& hdr,
+ const nsACString& val, bool merge) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return SetHeader_locked(hdr, ""_ns, val, merge);
+}
+
+nsresult nsHttpResponseHead::SetHeader_locked(const nsHttpAtom& atom,
+ const nsACString& hdr,
+ const nsACString& val,
+ bool merge) {
+ nsresult rv = mHeaders.SetHeader(atom, hdr, val, merge,
+ nsHttpHeaderArray::eVarietyResponse);
+ if (NS_FAILED(rv)) return rv;
+
+ // respond to changes in these headers. we need to reparse the entire
+ // header since the change may have merged in additional values.
+ if (atom == nsHttp::Cache_Control) {
+ ParseCacheControl(mHeaders.PeekHeader(atom));
+ } else if (atom == nsHttp::Pragma) {
+ ParsePragma(mHeaders.PeekHeader(atom));
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::GetHeader(const nsHttpAtom& h,
+ nsACString& v) const {
+ v.Truncate();
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mHeaders.GetHeader(h, v);
+}
+
+void nsHttpResponseHead::ClearHeader(const nsHttpAtom& h) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mHeaders.ClearHeader(h);
+}
+
+void nsHttpResponseHead::ClearHeaders() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mHeaders.Clear();
+}
+
+bool nsHttpResponseHead::HasHeaderValue(const nsHttpAtom& h, const char* v) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mHeaders.HasHeaderValue(h, v);
+}
+
+bool nsHttpResponseHead::HasHeader(const nsHttpAtom& h) const {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mHeaders.HasHeader(h);
+}
+
+void nsHttpResponseHead::SetContentType(const nsACString& s) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mContentType = s;
+}
+
+void nsHttpResponseHead::SetContentCharset(const nsACString& s) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mContentCharset = s;
+}
+
+void nsHttpResponseHead::SetContentLength(int64_t len) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ mContentLength = len;
+ if (len < 0) {
+ mHeaders.ClearHeader(nsHttp::Content_Length);
+ } else {
+ DebugOnly<nsresult> rv = mHeaders.SetHeader(
+ nsHttp::Content_Length, nsPrintfCString("%" PRId64, len), false,
+ nsHttpHeaderArray::eVarietyResponse);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void nsHttpResponseHead::Flatten(nsACString& buf, bool pruneTransients) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ if (mVersion == HttpVersion::v0_9) return;
+
+ buf.AppendLiteral("HTTP/");
+ if (mVersion == HttpVersion::v3_0) {
+ buf.AppendLiteral("3 ");
+ } else if (mVersion == HttpVersion::v2_0) {
+ buf.AppendLiteral("2 ");
+ } else if (mVersion == HttpVersion::v1_1) {
+ buf.AppendLiteral("1.1 ");
+ } else {
+ buf.AppendLiteral("1.0 ");
+ }
+
+ buf.Append(nsPrintfCString("%u", unsigned(mStatus)) + " "_ns + mStatusText +
+ "\r\n"_ns);
+
+ mHeaders.Flatten(buf, false, pruneTransients);
+}
+
+void nsHttpResponseHead::FlattenNetworkOriginalHeaders(nsACString& buf) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ if (mVersion == HttpVersion::v0_9) {
+ return;
+ }
+
+ mHeaders.FlattenOriginalHeader(buf);
+}
+
+nsresult nsHttpResponseHead::ParseCachedHead(const char* block) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ LOG(("nsHttpResponseHead::ParseCachedHead [this=%p]\n", this));
+
+ // this command works on a buffer as prepared by Flatten, as such it is
+ // not very forgiving ;-)
+
+ const char* p = strstr(block, "\r\n");
+ if (!p) return NS_ERROR_UNEXPECTED;
+
+ ParseStatusLine_locked(nsDependentCSubstring(block, p - block));
+
+ do {
+ block = p + 2;
+
+ if (*block == 0) break;
+
+ p = strstr(block, "\r\n");
+ if (!p) return NS_ERROR_UNEXPECTED;
+
+ Unused << ParseHeaderLine_locked(nsDependentCSubstring(block, p - block),
+ false);
+
+ } while (true);
+
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::ParseCachedOriginalHeaders(char* block) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ LOG(("nsHttpResponseHead::ParseCachedOriginalHeader [this=%p]\n", this));
+
+ // this command works on a buffer as prepared by FlattenOriginalHeader,
+ // as such it is not very forgiving ;-)
+
+ if (!block) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ char* p = block;
+ nsHttpAtom hdr;
+ nsAutoCString headerNameOriginal;
+ nsAutoCString val;
+ nsresult rv;
+
+ do {
+ block = p;
+
+ if (*block == 0) break;
+
+ p = strstr(block, "\r\n");
+ if (!p) return NS_ERROR_UNEXPECTED;
+
+ *p = 0;
+ if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(
+ nsDependentCString(block, p - block), &hdr, &headerNameOriginal,
+ &val))) {
+ return NS_OK;
+ }
+
+ rv = mHeaders.SetResponseHeaderFromCache(
+ hdr, headerNameOriginal, val,
+ nsHttpHeaderArray::eVarietyResponseNetOriginal);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ p = p + 2;
+ } while (true);
+
+ return NS_OK;
+}
+
+void nsHttpResponseHead::AssignDefaultStatusText() {
+ LOG(("response status line needs default reason phrase\n"));
+
+ // if a http response doesn't contain a reason phrase, put one in based
+ // on the status code. The reason phrase is totally meaningless so its
+ // ok to have a default catch all here - but this makes debuggers and addons
+ // a little saner to use if we don't map things to "404 OK" or other nonsense.
+ // In particular, HTTP/2 does not use reason phrases at all so they need to
+ // always be injected.
+
+ net_GetDefaultStatusTextForCode(mStatus, mStatusText);
+}
+
+nsresult nsHttpResponseHead::ParseStatusLine(const nsACString& line) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return ParseStatusLine_locked(line);
+}
+
+nsresult nsHttpResponseHead::ParseStatusLine_locked(const nsACString& line) {
+ //
+ // Parse Status-Line:: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
+ //
+
+ const char* start = line.BeginReading();
+ const char* end = line.EndReading();
+
+ // HTTP-Version
+ ParseVersion(start);
+
+ int32_t index = line.FindChar(' ');
+
+ if ((mVersion == HttpVersion::v0_9) || (index == -1)) {
+ mStatus = 200;
+ AssignDefaultStatusText();
+ } else if (StaticPrefs::network_http_strict_response_status_line_parsing()) {
+ // Status-Code: all ASCII digits after any whitespace
+ const char* p = start + index + 1;
+ while (p < end && NS_IsHTTPWhitespace(*p)) ++p;
+ if (p == end || !mozilla::IsAsciiDigit(*p)) {
+ return NS_ERROR_PARSING_HTTP_STATUS_LINE;
+ }
+ const char* codeStart = p;
+ while (p < end && mozilla::IsAsciiDigit(*p)) ++p;
+
+ // Only accept 3 digits (including all leading zeros)
+ // Also if next char isn't whitespace, fail (ie, code is 0x2)
+ if (p - codeStart > 3 || (p < end && !NS_IsHTTPWhitespace(*p))) {
+ return NS_ERROR_PARSING_HTTP_STATUS_LINE;
+ }
+
+ // At this point the code is between 0 and 999 inclusive
+ nsDependentCSubstring strCode(codeStart, p - codeStart);
+ nsresult rv;
+ mStatus = strCode.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_PARSING_HTTP_STATUS_LINE;
+ }
+
+ // Reason-Phrase: whatever remains after any whitespace (even empty)
+ while (p < end && NS_IsHTTPWhitespace(*p)) ++p;
+ if (p != end) {
+ mStatusText = nsDependentCSubstring(p, end - p);
+ }
+ } else {
+ // Status-Code
+ const char* p = start + index + 1;
+ mStatus = (uint16_t)atoi(p);
+ if (mStatus == 0) {
+ LOG(("mal-formed response status; assuming status = 200\n"));
+ mStatus = 200;
+ }
+
+ // Reason-Phrase is whatever is remaining of the line
+ index = line.FindChar(' ', p - start);
+ if (index == -1) {
+ AssignDefaultStatusText();
+ } else {
+ p = start + index + 1;
+ mStatusText = nsDependentCSubstring(p, end - p);
+ }
+ }
+
+ LOG1(("Have status line [version=%u status=%u statusText=%s]\n",
+ unsigned(mVersion), unsigned(mStatus), mStatusText.get()));
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::ParseHeaderLine(const nsACString& line) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return ParseHeaderLine_locked(line, true);
+}
+
+nsresult nsHttpResponseHead::ParseHeaderLine_locked(
+ const nsACString& line, bool originalFromNetHeaders) {
+ nsHttpAtom hdr;
+ nsAutoCString headerNameOriginal;
+ nsAutoCString val;
+
+ if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(
+ line, &hdr, &headerNameOriginal, &val))) {
+ return NS_OK;
+ }
+
+ // reject the header if there are 0x00 bytes in the value.
+ // (see https://github.com/httpwg/http-core/issues/215 for details).
+ if (StaticPrefs::network_http_reject_NULs_in_response_header_values() &&
+ val.FindChar('\0') >= 0) {
+ return NS_ERROR_DOM_INVALID_HEADER_VALUE;
+ }
+
+ nsresult rv;
+ if (originalFromNetHeaders) {
+ rv = mHeaders.SetHeaderFromNet(hdr, headerNameOriginal, val, true);
+ } else {
+ rv = mHeaders.SetResponseHeaderFromCache(
+ hdr, headerNameOriginal, val, nsHttpHeaderArray::eVarietyResponse);
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // leading and trailing LWS has been removed from |val|
+
+ // handle some special case headers...
+ if (hdr == nsHttp::Content_Length) {
+ rv = ParseResponseContentLength(val);
+ if (rv == NS_ERROR_ILLEGAL_VALUE) {
+ LOG(("illegal content-length! %s\n", val.get()));
+ return rv;
+ }
+
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG(("content-length value ignored! %s\n", val.get()));
+ }
+
+ } else if (hdr == nsHttp::Content_Type) {
+ if (StaticPrefs::network_standard_content_type_parsing_response_headers() &&
+ CMimeType::Parse(val, mContentType, mContentCharset)) {
+ } else {
+ bool dummy;
+ net_ParseContentType(val, mContentType, mContentCharset, &dummy);
+ }
+ LOG(("ParseContentType [input=%s, type=%s, charset=%s]\n", val.get(),
+ mContentType.get(), mContentCharset.get()));
+ } else if (hdr == nsHttp::Cache_Control) {
+ ParseCacheControl(val.get());
+ } else if (hdr == nsHttp::Pragma) {
+ ParsePragma(val.get());
+ }
+ return NS_OK;
+}
+
+// From section 13.2.3 of RFC2616, we compute the current age of a cached
+// response as follows:
+//
+// currentAge = max(max(0, responseTime - dateValue), ageValue)
+// + now - requestTime
+//
+// where responseTime == now
+//
+// This is typically a very small number.
+//
+nsresult nsHttpResponseHead::ComputeCurrentAge(uint32_t now,
+ uint32_t requestTime,
+ uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ uint32_t dateValue;
+ uint32_t ageValue;
+
+ *result = 0;
+
+ if (requestTime > now) {
+ // for calculation purposes lets not allow the request to happen in the
+ // future
+ requestTime = now;
+ }
+
+ if (NS_FAILED(GetDateValue_locked(&dateValue))) {
+ LOG(
+ ("nsHttpResponseHead::ComputeCurrentAge [this=%p] "
+ "Date response header not set!\n",
+ this));
+ // Assume we have a fast connection and that our clock
+ // is in sync with the server.
+ dateValue = now;
+ }
+
+ // Compute apparent age
+ if (now > dateValue) *result = now - dateValue;
+
+ // Compute corrected received age
+ if (NS_SUCCEEDED(GetAgeValue_locked(&ageValue))) {
+ *result = std::max(*result, ageValue);
+ }
+
+ // Compute current age
+ *result += (now - requestTime);
+ return NS_OK;
+}
+
+// From section 13.2.4 of RFC2616, we compute the freshness lifetime of a cached
+// response as follows:
+//
+// freshnessLifetime = max_age_value
+// <or>
+// freshnessLifetime = expires_value - date_value
+// <or>
+// freshnessLifetime = min(one-week,
+// (date_value - last_modified_value) * 0.10)
+// <or>
+// freshnessLifetime = 0
+//
+nsresult nsHttpResponseHead::ComputeFreshnessLifetime(uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ *result = 0;
+
+ // Try HTTP/1.1 style max-age directive...
+ if (NS_SUCCEEDED(GetMaxAgeValue_locked(result))) return NS_OK;
+
+ *result = 0;
+
+ uint32_t date = 0, date2 = 0;
+ if (NS_FAILED(GetDateValue_locked(&date))) {
+ date = NowInSeconds(); // synthesize a date header if none exists
+ }
+
+ // Try HTTP/1.0 style expires header...
+ if (NS_SUCCEEDED(GetExpiresValue_locked(&date2))) {
+ if (date2 > date) *result = date2 - date;
+ // the Expires header can specify a date in the past.
+ return NS_OK;
+ }
+
+ // These responses can be cached indefinitely.
+ if ((mStatus == 300) || (mStatus == 410) ||
+ nsHttp::IsPermanentRedirect(mStatus)) {
+ LOG(
+ ("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
+ "Assign an infinite heuristic lifetime\n",
+ this));
+ *result = uint32_t(-1);
+ return NS_OK;
+ }
+
+ if (mStatus >= 400) {
+ LOG(
+ ("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
+ "Do not calculate heuristic max-age for most responses >= 400\n",
+ this));
+ return NS_OK;
+ }
+
+ // From RFC 7234 Section 4.2.2, heuristics can only be used on responses
+ // without explicit freshness whose status codes are defined as cacheable
+ // by default, and those responses without explicit freshness that have been
+ // marked as explicitly cacheable.
+ // Note that |MustValidate| handled most of non-cacheable status codes.
+ if ((mStatus == 302 || mStatus == 304 || mStatus == 307) &&
+ !mCacheControlPublic && !mCacheControlPrivate) {
+ LOG((
+ "nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
+ "Do not calculate heuristic max-age for non-cacheable status code %u\n",
+ this, unsigned(mStatus)));
+ return NS_OK;
+ }
+
+ // Fallback on heuristic using last modified header...
+ if (NS_SUCCEEDED(GetLastModifiedValue_locked(&date2))) {
+ LOG(("using last-modified to determine freshness-lifetime\n"));
+ LOG(("last-modified = %u, date = %u\n", date2, date));
+ if (date2 <= date) {
+ // this only makes sense if last-modified is actually in the past
+ *result = (date - date2) / 10;
+ const uint32_t kOneWeek = 60 * 60 * 24 * 7;
+ *result = std::min(kOneWeek, *result);
+ return NS_OK;
+ }
+ }
+
+ LOG(
+ ("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
+ "Insufficient information to compute a non-zero freshness "
+ "lifetime!\n",
+ this));
+
+ return NS_OK;
+}
+
+bool nsHttpResponseHead::MustValidate() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ LOG(("nsHttpResponseHead::MustValidate ??\n"));
+
+ // Some response codes are cacheable, but the rest are not. This switch should
+ // stay in sync with the list in nsHttpChannel::ContinueProcessResponse3
+ switch (mStatus) {
+ // Success codes
+ case 200:
+ case 203:
+ case 204:
+ case 206:
+ // Cacheable redirects
+ case 300:
+ case 301:
+ case 302:
+ case 304:
+ case 307:
+ case 308:
+ // Gone forever
+ case 410:
+ break;
+ // Uncacheable redirects
+ case 303:
+ case 305:
+ // Other known errors
+ case 401:
+ case 407:
+ case 412:
+ case 416:
+ case 425:
+ case 429:
+ default: // revalidate unknown error pages
+ LOG(("Must validate since response is an uncacheable error page\n"));
+ return true;
+ }
+
+ // The no-cache response header indicates that we must validate this
+ // cached response before reusing.
+ if (NoCache_locked()) {
+ LOG(("Must validate since response contains 'no-cache' header\n"));
+ return true;
+ }
+
+ // Likewise, if the response is no-store, then we must validate this
+ // cached response before reusing. NOTE: it may seem odd that a no-store
+ // response may be cached, but indeed all responses are cached in order
+ // to support File->SaveAs, View->PageSource, and other browser features.
+ if (mCacheControlNoStore) {
+ LOG(("Must validate since response contains 'no-store' header\n"));
+ return true;
+ }
+
+ // Compare the Expires header to the Date header. If the server sent an
+ // Expires header with a timestamp in the past, then we must validate this
+ // cached response before reusing.
+ if (ExpiresInPast_locked()) {
+ LOG(("Must validate since Expires < Date\n"));
+ return true;
+ }
+
+ LOG(("no mandatory validation requirement\n"));
+ return false;
+}
+
+bool nsHttpResponseHead::MustValidateIfExpired() {
+ // according to RFC2616, section 14.9.4:
+ //
+ // When the must-revalidate directive is present in a response received by a
+ // cache, that cache MUST NOT use the entry after it becomes stale to respond
+ // to a subsequent request without first revalidating it with the origin
+ // server.
+ //
+ return HasHeaderValue(nsHttp::Cache_Control, "must-revalidate");
+}
+
+bool nsHttpResponseHead::StaleWhileRevalidate(uint32_t now,
+ uint32_t expiration) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ if (expiration <= 0 || !mCacheControlStaleWhileRevalidateSet) {
+ return false;
+ }
+
+ // 'expiration' is the expiration time (an absolute unit), the swr window
+ // extends the expiration time.
+ CheckedInt<uint32_t> stallValidUntil = expiration;
+ stallValidUntil += mCacheControlStaleWhileRevalidate;
+ if (!stallValidUntil.isValid()) {
+ // overflow means an indefinite stale window
+ return true;
+ }
+
+ return now <= stallValidUntil.value();
+}
+
+bool nsHttpResponseHead::IsResumable() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ // even though some HTTP/1.0 servers may support byte range requests, we're
+ // not going to bother with them, since those servers wouldn't understand
+ // If-Range. Also, while in theory it may be possible to resume when the
+ // status code is not 200, it is unlikely to be worth the trouble, especially
+ // for non-2xx responses.
+ return mStatus == 200 && mVersion >= HttpVersion::v1_1 &&
+ mHeaders.PeekHeader(nsHttp::Content_Length) &&
+ (mHeaders.PeekHeader(nsHttp::ETag) ||
+ mHeaders.PeekHeader(nsHttp::Last_Modified)) &&
+ mHeaders.HasHeaderValue(nsHttp::Accept_Ranges, "bytes");
+}
+
+bool nsHttpResponseHead::ExpiresInPast() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return ExpiresInPast_locked();
+}
+
+bool nsHttpResponseHead::ExpiresInPast_locked() const {
+ uint32_t maxAgeVal, expiresVal, dateVal;
+
+ // Bug #203271. Ensure max-age directive takes precedence over Expires
+ if (NS_SUCCEEDED(GetMaxAgeValue_locked(&maxAgeVal))) {
+ return false;
+ }
+
+ return NS_SUCCEEDED(GetExpiresValue_locked(&expiresVal)) &&
+ NS_SUCCEEDED(GetDateValue_locked(&dateVal)) && expiresVal < dateVal;
+}
+
+void nsHttpResponseHead::UpdateHeaders(nsHttpResponseHead* aOther) {
+ LOG(("nsHttpResponseHead::UpdateHeaders [this=%p]\n", this));
+
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ RecursiveMutexAutoLock monitorOther(aOther->mRecursiveMutex);
+
+ uint32_t i, count = aOther->mHeaders.Count();
+ for (i = 0; i < count; ++i) {
+ nsHttpAtom header;
+ nsAutoCString headerNameOriginal;
+
+ if (!aOther->mHeaders.PeekHeaderAt(i, header, headerNameOriginal)) {
+ continue;
+ }
+
+ nsAutoCString val;
+ if (NS_FAILED(aOther->GetHeader(header, val))) {
+ continue;
+ }
+
+ // Ignore any hop-by-hop headers...
+ if (header == nsHttp::Connection || header == nsHttp::Proxy_Connection ||
+ header == nsHttp::Keep_Alive || header == nsHttp::Proxy_Authenticate ||
+ header == nsHttp::Proxy_Authorization || // not a response header!
+ header == nsHttp::TE || header == nsHttp::Trailer ||
+ header == nsHttp::Transfer_Encoding || header == nsHttp::Upgrade ||
+ // Ignore any non-modifiable headers...
+ header == nsHttp::Content_Location || header == nsHttp::Content_MD5 ||
+ header == nsHttp::ETag ||
+ // Assume Cache-Control: "no-transform"
+ header == nsHttp::Content_Encoding || header == nsHttp::Content_Range ||
+ header == nsHttp::Content_Type ||
+ // Ignore wacky headers too...
+ // this one is for MS servers that send "Content-Length: 0"
+ // on 304 responses
+ header == nsHttp::Content_Length) {
+ LOG(("ignoring response header [%s: %s]\n", header.get(), val.get()));
+ } else {
+ LOG(("new response header [%s: %s]\n", header.get(), val.get()));
+
+ // overwrite the current header value with the new value...
+ DebugOnly<nsresult> rv =
+ SetHeader_locked(header, headerNameOriginal, val);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+}
+
+void nsHttpResponseHead::Reset() {
+ LOG(("nsHttpResponseHead::Reset\n"));
+
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+
+ mHeaders.Clear();
+
+ mVersion = HttpVersion::v1_1;
+ mStatus = 200;
+ mContentLength = -1;
+ mHasCacheControl = false;
+ mCacheControlPublic = false;
+ mCacheControlPrivate = false;
+ mCacheControlNoStore = false;
+ mCacheControlNoCache = false;
+ mCacheControlImmutable = false;
+ mCacheControlStaleWhileRevalidateSet = false;
+ mCacheControlStaleWhileRevalidate = 0;
+ mCacheControlMaxAgeSet = false;
+ mCacheControlMaxAge = 0;
+ mPragmaNoCache = false;
+ mStatusText.Truncate();
+ mContentType.Truncate();
+ mContentCharset.Truncate();
+}
+
+nsresult nsHttpResponseHead::ParseDateHeader(const nsHttpAtom& header,
+ uint32_t* result) const {
+ const char* val = mHeaders.PeekHeader(header);
+ if (!val) return NS_ERROR_NOT_AVAILABLE;
+
+ PRTime time;
+ PRStatus st = PR_ParseTimeString(val, true, &time);
+ if (st != PR_SUCCESS) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = PRTimeToSeconds(time);
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::GetAgeValue(uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return GetAgeValue_locked(result);
+}
+
+nsresult nsHttpResponseHead::GetAgeValue_locked(uint32_t* result) const {
+ const char* val = mHeaders.PeekHeader(nsHttp::Age);
+ if (!val) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = (uint32_t)atoi(val);
+ return NS_OK;
+}
+
+// Return the value of the (HTTP 1.1) max-age directive, which itself is a
+// component of the Cache-Control response header
+nsresult nsHttpResponseHead::GetMaxAgeValue(uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return GetMaxAgeValue_locked(result);
+}
+
+nsresult nsHttpResponseHead::GetMaxAgeValue_locked(uint32_t* result) const {
+ if (!mCacheControlMaxAgeSet) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *result = mCacheControlMaxAge;
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::GetDateValue(uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return GetDateValue_locked(result);
+}
+
+nsresult nsHttpResponseHead::GetExpiresValue(uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return GetExpiresValue_locked(result);
+}
+
+nsresult nsHttpResponseHead::GetExpiresValue_locked(uint32_t* result) const {
+ const char* val = mHeaders.PeekHeader(nsHttp::Expires);
+ if (!val) return NS_ERROR_NOT_AVAILABLE;
+
+ PRTime time;
+ PRStatus st = PR_ParseTimeString(val, true, &time);
+ if (st != PR_SUCCESS) {
+ // parsing failed... RFC 2616 section 14.21 says we should treat this
+ // as an expiration time in the past.
+ *result = 0;
+ return NS_OK;
+ }
+
+ if (time < 0) {
+ *result = 0;
+ } else {
+ *result = PRTimeToSeconds(time);
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::GetLastModifiedValue(uint32_t* result) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return ParseDateHeader(nsHttp::Last_Modified, result);
+}
+
+bool nsHttpResponseHead::operator==(const nsHttpResponseHead& aOther) const
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ nsHttpResponseHead& curr = const_cast<nsHttpResponseHead&>(*this);
+ nsHttpResponseHead& other = const_cast<nsHttpResponseHead&>(aOther);
+ RecursiveMutexAutoLock monitorOther(other.mRecursiveMutex);
+ RecursiveMutexAutoLock monitor(curr.mRecursiveMutex);
+
+ return mHeaders == aOther.mHeaders && mVersion == aOther.mVersion &&
+ mStatus == aOther.mStatus && mStatusText == aOther.mStatusText &&
+ mContentLength == aOther.mContentLength &&
+ mContentType == aOther.mContentType &&
+ mContentCharset == aOther.mContentCharset &&
+ mHasCacheControl == aOther.mHasCacheControl &&
+ mCacheControlPublic == aOther.mCacheControlPublic &&
+ mCacheControlPrivate == aOther.mCacheControlPrivate &&
+ mCacheControlNoCache == aOther.mCacheControlNoCache &&
+ mCacheControlNoStore == aOther.mCacheControlNoStore &&
+ mCacheControlImmutable == aOther.mCacheControlImmutable &&
+ mCacheControlStaleWhileRevalidateSet ==
+ aOther.mCacheControlStaleWhileRevalidateSet &&
+ mCacheControlStaleWhileRevalidate ==
+ aOther.mCacheControlStaleWhileRevalidate &&
+ mCacheControlMaxAgeSet == aOther.mCacheControlMaxAgeSet &&
+ mCacheControlMaxAge == aOther.mCacheControlMaxAge &&
+ mPragmaNoCache == aOther.mPragmaNoCache;
+}
+
+int64_t nsHttpResponseHead::TotalEntitySize() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ const char* contentRange = mHeaders.PeekHeader(nsHttp::Content_Range);
+ if (!contentRange) return mContentLength;
+
+ // Total length is after a slash
+ const char* slash = strrchr(contentRange, '/');
+ if (!slash) return -1; // No idea what the length is
+
+ slash++;
+ if (*slash == '*') { // Server doesn't know the length
+ return -1;
+ }
+
+ int64_t size;
+ if (!nsHttp::ParseInt64(slash, &size)) size = UINT64_MAX;
+ return size;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpResponseHead <private>
+//-----------------------------------------------------------------------------
+
+void nsHttpResponseHead::ParseVersion(const char* str) {
+ // Parse HTTP-Version:: "HTTP" "/" 1*DIGIT "." 1*DIGIT
+
+ LOG(("nsHttpResponseHead::ParseVersion [version=%s]\n", str));
+
+ Tokenizer t(str, nullptr, "");
+ // make sure we have HTTP at the beginning
+ if (!t.CheckWord("HTTP")) {
+ if (nsCRT::strncasecmp(str, "ICY ", 4) == 0) {
+ // ShoutCast ICY is HTTP/1.0-like. Assume it is HTTP/1.0.
+ LOG(("Treating ICY as HTTP 1.0\n"));
+ mVersion = HttpVersion::v1_0;
+ return;
+ }
+ LOG(("looks like a HTTP/0.9 response\n"));
+ mVersion = HttpVersion::v0_9;
+ return;
+ }
+
+ if (!t.CheckChar('/')) {
+ LOG(("server did not send a version number; assuming HTTP/1.0\n"));
+ // NCSA/1.5.2 has a bug in which it fails to send a version number
+ // if the request version is HTTP/1.1, so we fall back on HTTP/1.0
+ mVersion = HttpVersion::v1_0;
+ return;
+ }
+
+ uint32_t major;
+ if (!t.ReadInteger(&major)) {
+ LOG(("server did not send a correct version number; assuming HTTP/1.0"));
+ mVersion = HttpVersion::v1_0;
+ return;
+ }
+
+ if (major == 3) {
+ mVersion = HttpVersion::v3_0;
+ return;
+ }
+
+ if (major == 2) {
+ mVersion = HttpVersion::v2_0;
+ return;
+ }
+
+ if (major != 1) {
+ LOG(("server did not send a correct version number; assuming HTTP/1.0"));
+ mVersion = HttpVersion::v1_0;
+ return;
+ }
+
+ if (!t.CheckChar('.')) {
+ LOG(("mal-formed server version; assuming HTTP/1.0\n"));
+ mVersion = HttpVersion::v1_0;
+ return;
+ }
+
+ uint32_t minor;
+ if (!t.ReadInteger(&minor)) {
+ LOG(("server did not send a correct version number; assuming HTTP/1.0"));
+ mVersion = HttpVersion::v1_0;
+ return;
+ }
+
+ if (minor >= 1) {
+ // at least HTTP/1.1
+ mVersion = HttpVersion::v1_1;
+ } else {
+ // treat anything else as version 1.0
+ mVersion = HttpVersion::v1_0;
+ }
+}
+
+void nsHttpResponseHead::ParseCacheControl(const char* val) {
+ if (!(val && *val)) {
+ // clear flags
+ mHasCacheControl = false;
+ mCacheControlPublic = false;
+ mCacheControlPrivate = false;
+ mCacheControlNoCache = false;
+ mCacheControlNoStore = false;
+ mCacheControlImmutable = false;
+ mCacheControlStaleWhileRevalidateSet = false;
+ mCacheControlStaleWhileRevalidate = 0;
+ mCacheControlMaxAgeSet = false;
+ mCacheControlMaxAge = 0;
+ return;
+ }
+
+ nsDependentCString cacheControlRequestHeader(val);
+ CacheControlParser cacheControlRequest(cacheControlRequestHeader);
+
+ mHasCacheControl = true;
+ mCacheControlPublic = cacheControlRequest.Public();
+ mCacheControlPrivate = cacheControlRequest.Private();
+ mCacheControlNoCache = cacheControlRequest.NoCache();
+ mCacheControlNoStore = cacheControlRequest.NoStore();
+ mCacheControlImmutable = cacheControlRequest.Immutable();
+ mCacheControlStaleWhileRevalidateSet =
+ cacheControlRequest.StaleWhileRevalidate(
+ &mCacheControlStaleWhileRevalidate);
+ mCacheControlMaxAgeSet = cacheControlRequest.MaxAge(&mCacheControlMaxAge);
+}
+
+void nsHttpResponseHead::ParsePragma(const char* val) {
+ LOG(("nsHttpResponseHead::ParsePragma [val=%s]\n", val));
+
+ if (!(val && *val)) {
+ // clear no-cache flag
+ mPragmaNoCache = false;
+ return;
+ }
+
+ // Although 'Pragma: no-cache' is not a standard HTTP response header (it's a
+ // request header), caching is inhibited when this header is present so as to
+ // match existing Navigator behavior.
+ mPragmaNoCache = nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS);
+}
+
+nsresult nsHttpResponseHead::ParseResponseContentLength(
+ const nsACString& aHeaderStr) {
+ int64_t contentLength = 0;
+ // Ref: https://fetch.spec.whatwg.org/#content-length-header
+ // Step 1. Let values be the result of getting, decoding, and splitting
+ // `Content - Length` from headers.
+ // Step 1 is done by the caller
+ // Step 2. If values is null, then return null.
+ if (aHeaderStr.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Step 3 Let candidateValue be null.
+ Maybe<nsAutoCString> candidateValue;
+ // Step 4 For each value of values
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizerTemplate<
+ NS_IsAsciiWhitespace, nsTokenizerFlags::IncludeEmptyTokenAtEnd>(
+ aHeaderStr, ',')
+ .ToRange()) {
+ // Step 4.1 If candidateValue is null, then set candidateValue to value.
+ if (candidateValue.isNothing()) {
+ candidateValue.emplace(token);
+ }
+ // Step 4.2 Otherwise, if value is not candidateValue, return failure.
+ if (candidateValue.value() != token) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+ // Step 5 If candidateValue is the empty string or has a code point that is
+ // not an ASCII digit, then return null.
+ if (candidateValue.isNothing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Step 6 Return candidateValue, interpreted as decimal number contentLength
+ const char* end = nullptr;
+ if (!net::nsHttp::ParseInt64(candidateValue->get(), &end, &contentLength)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (*end != '\0') {
+ // a number was parsed by ParseInt64 but candidateValue contains non-numeric
+ // characters
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mContentLength = contentLength;
+ return NS_OK;
+}
+
+nsresult nsHttpResponseHead::VisitHeaders(
+ nsIHttpHeaderVisitor* visitor, nsHttpHeaderArray::VisitorFilter filter) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mInVisitHeaders = true;
+ nsresult rv = mHeaders.VisitHeaders(visitor, filter);
+ mInVisitHeaders = false;
+ return rv;
+}
+
+namespace {
+class ContentTypeOptionsVisitor final : public nsIHttpHeaderVisitor {
+ public:
+ NS_DECL_ISUPPORTS
+
+ ContentTypeOptionsVisitor() = default;
+
+ NS_IMETHOD
+ VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
+ if (!mHeaderPresent) {
+ mHeaderPresent = true;
+ } else {
+ // multiple XCTO headers in response, merge them
+ mContentTypeOptionsHeader.Append(", "_ns);
+ }
+ mContentTypeOptionsHeader.Append(aValue);
+ return NS_OK;
+ }
+
+ void GetMergedHeader(nsACString& aValue) {
+ aValue = mContentTypeOptionsHeader;
+ }
+
+ private:
+ ~ContentTypeOptionsVisitor() = default;
+ bool mHeaderPresent{false};
+ nsAutoCString mContentTypeOptionsHeader;
+};
+
+NS_IMPL_ISUPPORTS(ContentTypeOptionsVisitor, nsIHttpHeaderVisitor)
+} // namespace
+
+nsresult nsHttpResponseHead::GetOriginalHeader(const nsHttpAtom& aHeader,
+ nsIHttpHeaderVisitor* aVisitor) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mInVisitHeaders = true;
+ nsresult rv = mHeaders.GetOriginalHeader(aHeader, aVisitor);
+ mInVisitHeaders = false;
+ return rv;
+}
+
+bool nsHttpResponseHead::HasContentType() const {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return !mContentType.IsEmpty();
+}
+
+bool nsHttpResponseHead::HasContentCharset() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return !mContentCharset.IsEmpty();
+}
+
+bool nsHttpResponseHead::GetContentTypeOptionsHeader(nsACString& aOutput) {
+ aOutput.Truncate();
+
+ nsAutoCString contentTypeOptionsHeader;
+ // We need to fetch original headers and manually merge them because empty
+ // header values are not retrieved with GetHeader. Ref - Bug 1819642
+ RefPtr<ContentTypeOptionsVisitor> visitor = new ContentTypeOptionsVisitor();
+ Unused << GetOriginalHeader(nsHttp::X_Content_Type_Options, visitor);
+ visitor->GetMergedHeader(contentTypeOptionsHeader);
+ if (contentTypeOptionsHeader.IsEmpty()) {
+ // if there is no XCTO header, then there is nothing to do.
+ return false;
+ }
+
+ // XCTO header might contain multiple values which are comma separated, so:
+ // a) let's skip all subsequent values
+ // e.g. " NoSniFF , foo " will be " NoSniFF "
+ int32_t idx = contentTypeOptionsHeader.Find(",");
+ if (idx >= 0) {
+ contentTypeOptionsHeader = Substring(contentTypeOptionsHeader, 0, idx);
+ }
+ // b) let's trim all surrounding whitespace
+ // e.g. " NoSniFF " -> "NoSniFF"
+ nsHttp::TrimHTTPWhitespace(contentTypeOptionsHeader,
+ contentTypeOptionsHeader);
+
+ aOutput.Assign(contentTypeOptionsHeader);
+ return true;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpResponseHead.h b/netwerk/protocol/http/nsHttpResponseHead.h
new file mode 100644
index 0000000000..9caf92b3e1
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpResponseHead.h
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpResponseHead_h__
+#define nsHttpResponseHead_h__
+
+#include "nsHttpHeaderArray.h"
+#include "nsHttp.h"
+#include "nsString.h"
+#include "mozilla/RecursiveMutex.h"
+
+#ifdef Status
+/* Xlib headers insist on this for some reason... Nuke it because
+ it'll override our member name */
+typedef Status __StatusTmp;
+# undef Status
+typedef __StatusTmp Status;
+#endif
+
+class nsIHttpHeaderVisitor;
+
+// This needs to be forward declared here so we can include only this header
+// without also including PHttpChannelParams.h
+namespace IPC {
+template <typename>
+struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpResponseHead represents the status line and headers from an HTTP
+// response.
+//-----------------------------------------------------------------------------
+
+class nsHttpResponseHead {
+ public:
+ nsHttpResponseHead() = default;
+
+ nsHttpResponseHead(const nsHttpResponseHead& aOther);
+ nsHttpResponseHead& operator=(const nsHttpResponseHead& aOther);
+
+ void Enter() const MOZ_CAPABILITY_ACQUIRE(mRecursiveMutex) {
+ mRecursiveMutex.Lock();
+ }
+ void Exit() const MOZ_CAPABILITY_RELEASE(mRecursiveMutex) {
+ mRecursiveMutex.Unlock();
+ }
+ void AssertMutexOwned() const { mRecursiveMutex.AssertCurrentThreadIn(); }
+
+ HttpVersion Version();
+ uint16_t Status() const;
+ void StatusText(nsACString& aStatusText);
+ int64_t ContentLength();
+ void ContentType(nsACString& aContentType) const;
+ void ContentCharset(nsACString& aContentCharset);
+ bool Public();
+ bool Private();
+ bool NoStore();
+ bool NoCache();
+ bool Immutable();
+ /**
+ * Full length of the entity. For byte-range requests, this may be larger
+ * than ContentLength(), which will only represent the requested part of the
+ * entity.
+ */
+ int64_t TotalEntitySize();
+
+ [[nodiscard]] nsresult SetHeader(const nsACString& h, const nsACString& v,
+ bool m = false);
+ [[nodiscard]] nsresult SetHeader(const nsHttpAtom& h, const nsACString& v,
+ bool m = false);
+ [[nodiscard]] nsresult GetHeader(const nsHttpAtom& h, nsACString& v) const;
+ void ClearHeader(const nsHttpAtom& h);
+ void ClearHeaders();
+ bool HasHeaderValue(const nsHttpAtom& h, const char* v);
+ bool HasHeader(const nsHttpAtom& h) const;
+
+ void SetContentType(const nsACString& s);
+ void SetContentCharset(const nsACString& s);
+ void SetContentLength(int64_t);
+
+ // write out the response status line and headers as a single text block,
+ // optionally pruning out transient headers (ie. headers that only make
+ // sense the first time the response is handled).
+ // Both functions append to the string supplied string.
+ void Flatten(nsACString&, bool pruneTransients);
+ void FlattenNetworkOriginalHeaders(nsACString& buf);
+
+ // The next 2 functions parse flattened response head and original net
+ // headers. They are used when we are reading an entry from the cache.
+ //
+ // To keep proper order of the original headers we MUST call
+ // ParseCachedOriginalHeaders FIRST and then ParseCachedHead.
+ //
+ // block must be null terminated.
+ [[nodiscard]] nsresult ParseCachedHead(const char* block);
+ [[nodiscard]] nsresult ParseCachedOriginalHeaders(char* block);
+
+ // parse the status line.
+ nsresult ParseStatusLine(const nsACString& line);
+
+ // parse a header line.
+ [[nodiscard]] nsresult ParseHeaderLine(const nsACString& line);
+
+ // cache validation support methods
+ [[nodiscard]] nsresult ComputeFreshnessLifetime(uint32_t*);
+ [[nodiscard]] nsresult ComputeCurrentAge(uint32_t now, uint32_t requestTime,
+ uint32_t* result);
+ bool MustValidate();
+ bool MustValidateIfExpired();
+
+ // return true if the response contains a valid Cache-control:
+ // stale-while-revalidate and |now| is less than or equal |expiration +
+ // stale-while-revalidate|. Otherwise false.
+ bool StaleWhileRevalidate(uint32_t now, uint32_t expiration);
+
+ // returns true if the server appears to support byte range requests.
+ bool IsResumable();
+
+ // returns true if the Expires header has a value in the past relative to the
+ // value of the Date header.
+ bool ExpiresInPast();
+
+ // update headers...
+ void UpdateHeaders(nsHttpResponseHead* aOther);
+
+ // reset the response head to it's initial state
+ void Reset();
+
+ [[nodiscard]] nsresult GetLastModifiedValue(uint32_t* result);
+
+ bool operator==(const nsHttpResponseHead& aOther) const;
+
+ // Using this function it is possible to itereate through all headers
+ // automatically under one lock.
+ [[nodiscard]] nsresult VisitHeaders(nsIHttpHeaderVisitor* visitor,
+ nsHttpHeaderArray::VisitorFilter filter);
+ [[nodiscard]] nsresult GetOriginalHeader(const nsHttpAtom& aHeader,
+ nsIHttpHeaderVisitor* aVisitor);
+
+ bool HasContentType() const;
+ bool HasContentCharset();
+ bool GetContentTypeOptionsHeader(nsACString& aOutput);
+
+ private:
+ [[nodiscard]] nsresult SetHeader_locked(const nsHttpAtom& atom,
+ const nsACString& h,
+ const nsACString& v, bool m = false)
+ MOZ_REQUIRES(mRecursiveMutex);
+ void AssignDefaultStatusText() MOZ_REQUIRES(mRecursiveMutex);
+ void ParseVersion(const char*) MOZ_REQUIRES(mRecursiveMutex);
+ void ParseCacheControl(const char*) MOZ_REQUIRES(mRecursiveMutex);
+ void ParsePragma(const char*) MOZ_REQUIRES(mRecursiveMutex);
+ // Parses a content-length header-value as described in
+ // https://fetch.spec.whatwg.org/#content-length-header
+ nsresult ParseResponseContentLength(const nsACString& aHeaderStr)
+ MOZ_REQUIRES(mRecursiveMutex);
+
+ nsresult ParseStatusLine_locked(const nsACString& line)
+ MOZ_REQUIRES(mRecursiveMutex);
+ [[nodiscard]] nsresult ParseHeaderLine_locked(const nsACString& line,
+ bool originalFromNetHeaders)
+ MOZ_REQUIRES(mRecursiveMutex);
+
+ // these return failure if the header does not exist.
+ [[nodiscard]] nsresult ParseDateHeader(const nsHttpAtom& header,
+ uint32_t* result) const
+ MOZ_REQUIRES(mRecursiveMutex);
+ [[nodiscard]] nsresult GetAgeValue(uint32_t* result);
+ [[nodiscard]] nsresult GetMaxAgeValue(uint32_t* result);
+ [[nodiscard]] nsresult GetStaleWhileRevalidateValue(uint32_t* result);
+ [[nodiscard]] nsresult GetDateValue(uint32_t* result);
+ [[nodiscard]] nsresult GetExpiresValue(uint32_t* result);
+
+ bool ExpiresInPast_locked() const MOZ_REQUIRES(mRecursiveMutex);
+ [[nodiscard]] nsresult GetAgeValue_locked(uint32_t* result) const
+ MOZ_REQUIRES(mRecursiveMutex);
+ [[nodiscard]] nsresult GetExpiresValue_locked(uint32_t* result) const
+ MOZ_REQUIRES(mRecursiveMutex);
+ [[nodiscard]] nsresult GetMaxAgeValue_locked(uint32_t* result) const
+ MOZ_REQUIRES(mRecursiveMutex);
+ [[nodiscard]] nsresult GetStaleWhileRevalidateValue_locked(
+ uint32_t* result) const MOZ_REQUIRES(mRecursiveMutex);
+
+ [[nodiscard]] nsresult GetDateValue_locked(uint32_t* result) const
+ MOZ_REQUIRES(mRecursiveMutex) {
+ return ParseDateHeader(nsHttp::Date, result);
+ }
+
+ [[nodiscard]] nsresult GetLastModifiedValue_locked(uint32_t* result) const
+ MOZ_REQUIRES(mRecursiveMutex) {
+ return ParseDateHeader(nsHttp::Last_Modified, result);
+ }
+
+ bool NoCache_locked() const MOZ_REQUIRES(mRecursiveMutex) {
+ // We ignore Pragma: no-cache if Cache-Control is set.
+ MOZ_ASSERT_IF(mCacheControlNoCache, mHasCacheControl);
+ return mHasCacheControl ? mCacheControlNoCache : mPragmaNoCache;
+ }
+
+ private:
+ // All members must be copy-constructable and assignable
+ nsHttpHeaderArray mHeaders MOZ_GUARDED_BY(mRecursiveMutex);
+ HttpVersion mVersion MOZ_GUARDED_BY(mRecursiveMutex){HttpVersion::v1_1};
+ uint16_t mStatus MOZ_GUARDED_BY(mRecursiveMutex){200};
+ nsCString mStatusText MOZ_GUARDED_BY(mRecursiveMutex);
+ int64_t mContentLength MOZ_GUARDED_BY(mRecursiveMutex){-1};
+ nsCString mContentType MOZ_GUARDED_BY(mRecursiveMutex);
+ nsCString mContentCharset MOZ_GUARDED_BY(mRecursiveMutex);
+ bool mHasCacheControl MOZ_GUARDED_BY(mRecursiveMutex){false};
+ bool mCacheControlPublic MOZ_GUARDED_BY(mRecursiveMutex){false};
+ bool mCacheControlPrivate MOZ_GUARDED_BY(mRecursiveMutex){false};
+ bool mCacheControlNoStore MOZ_GUARDED_BY(mRecursiveMutex){false};
+ bool mCacheControlNoCache MOZ_GUARDED_BY(mRecursiveMutex){false};
+ bool mCacheControlImmutable MOZ_GUARDED_BY(mRecursiveMutex){false};
+ bool mCacheControlStaleWhileRevalidateSet MOZ_GUARDED_BY(mRecursiveMutex){
+ false};
+ uint32_t mCacheControlStaleWhileRevalidate MOZ_GUARDED_BY(mRecursiveMutex){0};
+ bool mCacheControlMaxAgeSet MOZ_GUARDED_BY(mRecursiveMutex){false};
+ uint32_t mCacheControlMaxAge MOZ_GUARDED_BY(mRecursiveMutex){0};
+ bool mPragmaNoCache MOZ_GUARDED_BY(mRecursiveMutex){false};
+
+ // We are using RecursiveMutex instead of a Mutex because VisitHeader
+ // function calls nsIHttpHeaderVisitor::VisitHeader while under lock.
+ mutable RecursiveMutex mRecursiveMutex{"nsHttpResponseHead.mRecursiveMutex"};
+ // During VisitHeader we sould not allow call to SetHeader.
+ bool mInVisitHeaders MOZ_GUARDED_BY(mRecursiveMutex){false};
+
+ friend struct IPC::ParamTraits<nsHttpResponseHead>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpResponseHead_h__
diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp
new file mode 100644
index 0000000000..580ea35841
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -0,0 +1,3628 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "nsHttpTransaction.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "HttpLog.h"
+#include "HTTPSRecordResolver.h"
+#include "NSSErrorsService.h"
+#include "base/basictypes.h"
+#include "mozilla/Components.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/net/SSLTokensCache.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "MockHttpAuth.h"
+#include "nsCRT.h"
+#include "nsComponentManagerUtils.h" // do_CreateInstance
+#include "nsHttpBasicAuth.h"
+#include "nsHttpChannel.h"
+#include "nsHttpChunkedDecoder.h"
+#include "nsHttpDigestAuth.h"
+#include "nsHttpHandler.h"
+#include "nsHttpNTLMAuth.h"
+#ifdef MOZ_AUTH_EXTENSION
+# include "nsHttpNegotiateAuth.h"
+#endif
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+#include "nsICancelable.h"
+#include "nsIClassOfService.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsIEventTarget.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsIHttpAuthenticator.h"
+#include "nsIInputStream.h"
+#include "nsIInputStreamPriority.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIOService.h"
+#include "nsIPipe.h"
+#include "nsIRequestContext.h"
+#include "nsISeekableStream.h"
+#include "nsITLSSocketControl.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITransport.h"
+#include "nsMultiplexInputStream.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsSocketTransportService2.h"
+#include "nsStringStream.h"
+#include "nsTransportUtils.h"
+#include "sslerr.h"
+#include "SpeculativeTransaction.h"
+
+//-----------------------------------------------------------------------------
+
+// Place a limit on how much non-compliant HTTP can be skipped while
+// looking for a response header
+#define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128)
+
+using namespace mozilla::net;
+
+namespace mozilla::net {
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction <public>
+//-----------------------------------------------------------------------------
+
+nsHttpTransaction::nsHttpTransaction() {
+ LOG(("Creating nsHttpTransaction @%p\n", this));
+
+#ifdef MOZ_VALGRIND
+ memset(&mSelfAddr, 0, sizeof(NetAddr));
+ memset(&mPeerAddr, 0, sizeof(NetAddr));
+#endif
+ mSelfAddr.raw.family = PR_AF_UNSPEC;
+ mPeerAddr.raw.family = PR_AF_UNSPEC;
+
+ mThroughCaptivePortal = gHttpHandler->GetThroughCaptivePortal();
+}
+
+void nsHttpTransaction::ResumeReading() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!mReadingStopped) {
+ return;
+ }
+
+ LOG(("nsHttpTransaction::ResumeReading %p", this));
+
+ mReadingStopped = false;
+
+ // This with either reengage the limit when still throttled in WriteSegments
+ // or simply reset to allow unlimeted reading again.
+ mThrottlingReadAllowance = THROTTLE_NO_LIMIT;
+
+ if (mConnection) {
+ mConnection->TransactionHasDataToRecv(this);
+ nsresult rv = mConnection->ResumeRecv();
+ if (NS_FAILED(rv)) {
+ LOG((" resume failed with rv=%" PRIx32, static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+bool nsHttpTransaction::EligibleForThrottling() const {
+ return (mClassOfServiceFlags &
+ (nsIClassOfService::Throttleable | nsIClassOfService::DontThrottle |
+ nsIClassOfService::Leader | nsIClassOfService::Unblocked)) ==
+ nsIClassOfService::Throttleable;
+}
+
+void nsHttpTransaction::SetClassOfService(ClassOfService cos) {
+ if (mClosed) {
+ return;
+ }
+
+ bool wasThrottling = EligibleForThrottling();
+ mClassOfServiceFlags = cos.Flags();
+ mClassOfServiceIncremental = cos.Incremental();
+ bool isThrottling = EligibleForThrottling();
+
+ if (mConnection && wasThrottling != isThrottling) {
+ // Do nothing until we are actually activated. For now
+ // only remember the throttle flag. Call to UpdateActiveTransaction
+ // would add this transaction to the list too early.
+ gHttpHandler->ConnMgr()->UpdateActiveTransaction(this);
+
+ if (mReadingStopped && !isThrottling) {
+ ResumeReading();
+ }
+ }
+}
+
+class ReleaseOnSocketThread final : public mozilla::Runnable {
+ public:
+ explicit ReleaseOnSocketThread(nsTArray<nsCOMPtr<nsISupports>>&& aDoomed)
+ : Runnable("ReleaseOnSocketThread"), mDoomed(std::move(aDoomed)) {}
+
+ NS_IMETHOD
+ Run() override {
+ mDoomed.Clear();
+ return NS_OK;
+ }
+
+ void Dispatch() {
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ Unused << sts->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ private:
+ virtual ~ReleaseOnSocketThread() = default;
+
+ nsTArray<nsCOMPtr<nsISupports>> mDoomed;
+};
+
+nsHttpTransaction::~nsHttpTransaction() {
+ LOG(("Destroying nsHttpTransaction @%p\n", this));
+
+ if (mPushedStream) {
+ mPushedStream->OnPushFailed();
+ mPushedStream = nullptr;
+ }
+
+ if (mTokenBucketCancel) {
+ mTokenBucketCancel->Cancel(NS_ERROR_ABORT);
+ mTokenBucketCancel = nullptr;
+ }
+
+ // Force the callbacks and connection to be released right now
+ {
+ MutexAutoLock lock(mLock);
+ mCallbacks = nullptr;
+ }
+
+ mEarlyHintObserver = nullptr;
+
+ delete mResponseHead;
+ delete mChunkedDecoder;
+ ReleaseBlockingTransaction();
+
+ nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
+ if (mConnection) {
+ arrayToRelease.AppendElement(mConnection.forget());
+ }
+
+ if (!arrayToRelease.IsEmpty()) {
+ RefPtr<ReleaseOnSocketThread> r =
+ new ReleaseOnSocketThread(std::move(arrayToRelease));
+ r->Dispatch();
+ }
+}
+
+nsresult nsHttpTransaction::Init(
+ uint32_t caps, nsHttpConnectionInfo* cinfo, nsHttpRequestHead* requestHead,
+ nsIInputStream* requestBody, uint64_t requestContentLength,
+ bool requestBodyHasHeaders, nsIEventTarget* target,
+ nsIInterfaceRequestor* callbacks, nsITransportEventSink* eventsink,
+ uint64_t browserId, HttpTrafficCategory trafficCategory,
+ nsIRequestContext* requestContext, ClassOfService classOfService,
+ uint32_t initialRwin, bool responseTimeoutEnabled, uint64_t channelId,
+ TransactionObserverFunc&& transactionObserver,
+ OnPushCallback&& aOnPushCallback,
+ HttpTransactionShell* transWithPushedStream, uint32_t aPushedStreamId) {
+ nsresult rv;
+
+ LOG1(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps));
+
+ MOZ_ASSERT(cinfo);
+ MOZ_ASSERT(requestHead);
+ MOZ_ASSERT(target);
+ MOZ_ASSERT(target->IsOnCurrentThread());
+
+ mChannelId = channelId;
+ mTransactionObserver = std::move(transactionObserver);
+ mOnPushCallback = std::move(aOnPushCallback);
+ mBrowserId = browserId;
+
+ mTrafficCategory = trafficCategory;
+
+ LOG1(("nsHttpTransaction %p SetRequestContext %p\n", this, requestContext));
+ mRequestContext = requestContext;
+
+ SetClassOfService(classOfService);
+ mResponseTimeoutEnabled = responseTimeoutEnabled;
+ mInitialRwin = initialRwin;
+
+ // create transport event sink proxy. it coalesces consecutive
+ // events of the same status type.
+ rv = net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink), eventsink,
+ target);
+
+ if (NS_FAILED(rv)) return rv;
+
+ mConnInfo = cinfo;
+ mCallbacks = callbacks;
+ mConsumerTarget = target;
+ mCaps = caps;
+ // eventsink is a nsHttpChannel when we expect "103 Early Hints" responses.
+ // We expect it in document requests and not e.g. in TRR requests.
+ mEarlyHintObserver = do_QueryInterface(eventsink);
+
+ if (requestHead->IsHead()) {
+ mNoContent = true;
+ }
+
+ // grab a weak reference to the request head
+ mRequestHead = requestHead;
+
+ mReqHeaderBuf = nsHttp::ConvertRequestHeadToString(
+ *requestHead, !!requestBody, requestBodyHasHeaders,
+ cinfo->UsingConnect());
+
+ if (LOG1_ENABLED()) {
+ LOG1(("http request [\n"));
+ LogHeaders(mReqHeaderBuf.get());
+ LOG1(("]\n"));
+ }
+
+ // report the request header
+ if (gHttpHandler->HttpActivityDistributorActivated()) {
+ nsCString requestBuf(mReqHeaderBuf);
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "ObserveHttpActivityWithArgs", [channelId(mChannelId), requestBuf]() {
+ if (!gHttpHandler) {
+ return;
+ }
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(channelId),
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER, PR_Now(), 0, requestBuf);
+ }));
+ }
+
+ // Create a string stream for the request header buf (the stream holds
+ // a non-owning reference to the request header data, so we MUST keep
+ // mReqHeaderBuf around).
+ nsCOMPtr<nsIInputStream> headers;
+ rv = NS_NewByteInputStream(getter_AddRefs(headers), mReqHeaderBuf,
+ NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) return rv;
+
+ mHasRequestBody = !!requestBody;
+ if (mHasRequestBody && !requestContentLength) {
+ mHasRequestBody = false;
+ }
+
+ requestContentLength += mReqHeaderBuf.Length();
+
+ if (mHasRequestBody) {
+ // wrap the headers and request body in a multiplexed input stream.
+ nsCOMPtr<nsIMultiplexInputStream> multi;
+ rv = nsMultiplexInputStreamConstructor(NS_GET_IID(nsIMultiplexInputStream),
+ getter_AddRefs(multi));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = multi->AppendStream(headers);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = multi->AppendStream(requestBody);
+ if (NS_FAILED(rv)) return rv;
+
+ // wrap the multiplexed input stream with a buffered input stream, so
+ // that we write data in the largest chunks possible. this is actually
+ // necessary to workaround some common server bugs (see bug 137155).
+ nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multi));
+ rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream),
+ stream.forget(),
+ nsIOService::gDefaultSegmentSize);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ mRequestStream = headers;
+ }
+
+ nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(eventsink);
+ if (throttled) {
+ nsCOMPtr<nsIInputChannelThrottleQueue> queue;
+ rv = throttled->GetThrottleQueue(getter_AddRefs(queue));
+ // In case of failure, just carry on without throttling.
+ if (NS_SUCCEEDED(rv) && queue) {
+ nsCOMPtr<nsIAsyncInputStream> wrappedStream;
+ rv = queue->WrapStream(mRequestStream, getter_AddRefs(wrappedStream));
+ // Failure to throttle isn't sufficient reason to fail
+ // initialization
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(wrappedStream != nullptr);
+ LOG(
+ ("nsHttpTransaction::Init %p wrapping input stream using throttle "
+ "queue %p\n",
+ this, queue.get()));
+ mRequestStream = wrappedStream;
+ }
+ }
+ }
+
+ // make sure request content-length fits within js MAX_SAFE_INTEGER
+ mRequestSize = InScriptableRange(requestContentLength)
+ ? static_cast<int64_t>(requestContentLength)
+ : -1;
+
+ // create pipe for response stream
+ NS_NewPipe2(getter_AddRefs(mPipeIn), getter_AddRefs(mPipeOut), true, true,
+ nsIOService::gDefaultSegmentSize,
+ nsIOService::gDefaultSegmentCount);
+
+ if (transWithPushedStream && aPushedStreamId) {
+ RefPtr<nsHttpTransaction> trans =
+ transWithPushedStream->AsHttpTransaction();
+ MOZ_ASSERT(trans);
+ mPushedStream = trans->TakePushedStreamById(aPushedStreamId);
+ }
+
+ bool forceUseHTTPSRR = StaticPrefs::network_dns_force_use_https_rr();
+ if ((gHttpHandler->UseHTTPSRRAsAltSvcEnabled() &&
+ !(mCaps & NS_HTTP_DISALLOW_HTTPS_RR)) ||
+ forceUseHTTPSRR) {
+ nsCOMPtr<nsIEventTarget> target;
+ Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
+ if (target) {
+ if (StaticPrefs::network_dns_force_waiting_https_rr() ||
+ StaticPrefs::network_dns_echconfig_enabled() || forceUseHTTPSRR) {
+ mCaps |= NS_HTTP_FORCE_WAIT_HTTP_RR;
+ }
+
+ mResolver = new HTTPSRecordResolver(this);
+ nsCOMPtr<nsICancelable> dnsRequest;
+ rv = mResolver->FetchHTTPSRRInternal(target, getter_AddRefs(dnsRequest));
+ if (NS_SUCCEEDED(rv)) {
+ mHTTPSSVCReceivedStage = HTTPSSVC_NOT_PRESENT;
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+ mDNSRequest.swap(dnsRequest);
+ if (NS_FAILED(rv)) {
+ MakeDontWaitHTTPSRR();
+ }
+ }
+ }
+ }
+
+ RefPtr<nsHttpChannel> httpChannel = do_QueryObject(eventsink);
+ RefPtr<WebTransportSessionEventListener> listener =
+ httpChannel ? httpChannel->GetWebTransportSessionEventListener()
+ : nullptr;
+ if (listener) {
+ mWebTransportSessionEventListener = std::move(listener);
+ }
+
+ return NS_OK;
+}
+
+static inline void CreateAndStartTimer(nsCOMPtr<nsITimer>& aTimer,
+ nsITimerCallback* aCallback,
+ uint32_t aTimeout) {
+ MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!aTimer);
+
+ if (!aTimeout) {
+ return;
+ }
+
+ NS_NewTimerWithCallback(getter_AddRefs(aTimer), aCallback, aTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+void nsHttpTransaction::OnPendingQueueInserted(
+ const nsACString& aConnectionHashKey) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ {
+ MutexAutoLock lock(mLock);
+ mHashKeyOfConnectionEntry.Assign(aConnectionHashKey);
+ }
+
+ // Don't create mHttp3BackupTimer if HTTPS RR is in play.
+ if (mConnInfo->IsHttp3() && !mOrigConnInfo && !mConnInfo->GetWebTransport()) {
+ // Backup timer should only be created once.
+ if (!mHttp3BackupTimerCreated) {
+ CreateAndStartTimer(mHttp3BackupTimer, this,
+ StaticPrefs::network_http_http3_backup_timer_delay());
+ mHttp3BackupTimerCreated = true;
+ }
+ }
+}
+
+nsresult nsHttpTransaction::AsyncRead(nsIStreamListener* listener,
+ nsIRequest** pump) {
+ RefPtr<nsInputStreamPump> transactionPump;
+ nsresult rv =
+ nsInputStreamPump::Create(getter_AddRefs(transactionPump), mPipeIn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transactionPump->AsyncRead(listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transactionPump.forget(pump);
+ return NS_OK;
+}
+
+// This method should only be used on the socket thread
+nsAHttpConnection* nsHttpTransaction::Connection() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return mConnection.get();
+}
+
+void nsHttpTransaction::SetH2WSConnRefTaken() {
+ if (!OnSocketThread()) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("nsHttpTransaction::SetH2WSConnRefTaken", this,
+ &nsHttpTransaction::SetH2WSConnRefTaken);
+ gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+ return;
+ }
+}
+
+UniquePtr<nsHttpResponseHead> nsHttpTransaction::TakeResponseHead() {
+ MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x");
+
+ // Lock TakeResponseHead() against main thread
+ MutexAutoLock lock(mLock);
+
+ mResponseHeadTaken = true;
+
+ // Even in OnStartRequest() the headers won't be available if we were
+ // canceled
+ if (!mHaveAllHeaders) {
+ NS_WARNING("response headers not available or incomplete");
+ return nullptr;
+ }
+
+ return WrapUnique(std::exchange(mResponseHead, nullptr));
+}
+
+UniquePtr<nsHttpHeaderArray> nsHttpTransaction::TakeResponseTrailers() {
+ MOZ_ASSERT(!mResponseTrailersTaken, "TakeResponseTrailers called 2x");
+
+ // Lock TakeResponseTrailers() against main thread
+ MutexAutoLock lock(mLock);
+
+ mResponseTrailersTaken = true;
+ return std::move(mForTakeResponseTrailers);
+}
+
+void nsHttpTransaction::SetProxyConnectFailed() { mProxyConnectFailed = true; }
+
+nsHttpRequestHead* nsHttpTransaction::RequestHead() { return mRequestHead; }
+
+uint32_t nsHttpTransaction::Http1xTransactionCount() { return 1; }
+
+nsresult nsHttpTransaction::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//----------------------------------------------------------------------------
+// nsHttpTransaction::nsAHttpTransaction
+//----------------------------------------------------------------------------
+
+void nsHttpTransaction::SetConnection(nsAHttpConnection* conn) {
+ {
+ MutexAutoLock lock(mLock);
+ mConnection = conn;
+ if (mConnection) {
+ mIsHttp3Used = mConnection->Version() == HttpVersion::v3_0;
+ }
+ }
+}
+
+void nsHttpTransaction::OnActivated() {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (mActivated) {
+ return;
+ }
+
+ if (mTrafficCategory != HttpTrafficCategory::eInvalid) {
+ HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer();
+ if (hta) {
+ hta->IncrementHttpTransaction(mTrafficCategory);
+ }
+ if (mConnection) {
+ mConnection->SetTrafficCategory(mTrafficCategory);
+ }
+ }
+
+ if (mConnection && mRequestHead &&
+ mConnection->Version() >= HttpVersion::v2_0) {
+ // So this is fun. On http/2, we want to send TE: trailers, to be
+ // spec-compliant. So we add it to the request head here. The fun part
+ // is that adding a header to the request head at this point has no
+ // effect on what we send on the wire, as the headers are already
+ // flattened (in Init()) by the time we get here. So the *real* adding
+ // of the header happens in the h2 compression code. We still have to
+ // add the header to the request head here, though, so that devtools can
+ // show that we sent the header. FUN!
+ Unused << mRequestHead->SetHeader(nsHttp::TE, "trailers"_ns);
+ }
+
+ mActivated = true;
+ gHttpHandler->ConnMgr()->AddActiveTransaction(this);
+}
+
+void nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor** cb) {
+ MutexAutoLock lock(mLock);
+ nsCOMPtr<nsIInterfaceRequestor> tmp(mCallbacks);
+ tmp.forget(cb);
+}
+
+void nsHttpTransaction::SetSecurityCallbacks(
+ nsIInterfaceRequestor* aCallbacks) {
+ {
+ MutexAutoLock lock(mLock);
+ mCallbacks = aCallbacks;
+ }
+
+ if (gSocketTransportService) {
+ RefPtr<UpdateSecurityCallbacks> event =
+ new UpdateSecurityCallbacks(this, aCallbacks);
+ gSocketTransportService->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+ }
+}
+
+void nsHttpTransaction::OnTransportStatus(nsITransport* transport,
+ nsresult status, int64_t progress) {
+ LOG1(("nsHttpTransaction::OnSocketStatus [this=%p status=%" PRIx32
+ " progress=%" PRId64 "]\n",
+ this, static_cast<uint32_t>(status), progress));
+
+ if (status == NS_NET_STATUS_CONNECTED_TO ||
+ status == NS_NET_STATUS_WAITING_FOR) {
+ if (mConnection) {
+ MutexAutoLock lock(mLock);
+ mConnection->GetSelfAddr(&mSelfAddr);
+ mConnection->GetPeerAddr(&mPeerAddr);
+ mResolvedByTRR = mConnection->ResolvedByTRR();
+ mEffectiveTRRMode = mConnection->EffectiveTRRMode();
+ mTRRSkipReason = mConnection->TRRSkipReason();
+ mEchConfigUsed = mConnection->GetEchConfigUsed();
+ }
+ }
+
+ // If the timing is enabled, and we are not using a persistent connection
+ // then the requestStart timestamp will be null, so we mark the timestamps
+ // for domainLookupStart/End and connectStart/End
+ // If we are using a persistent connection they will remain null,
+ // and the correct value will be returned in Performance.
+ if (TimingEnabled() && GetRequestStart().IsNull()) {
+ if (status == NS_NET_STATUS_RESOLVING_HOST) {
+ SetDomainLookupStart(TimeStamp::Now(), true);
+ } else if (status == NS_NET_STATUS_RESOLVED_HOST) {
+ SetDomainLookupEnd(TimeStamp::Now());
+ } else if (status == NS_NET_STATUS_CONNECTING_TO) {
+ SetConnectStart(TimeStamp::Now());
+ } else if (status == NS_NET_STATUS_CONNECTED_TO) {
+ TimeStamp tnow = TimeStamp::Now();
+ SetConnectEnd(tnow, true);
+ {
+ MutexAutoLock lock(mLock);
+ mTimings.tcpConnectEnd = tnow;
+ }
+ } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_STARTING) {
+ {
+ MutexAutoLock lock(mLock);
+ mTimings.secureConnectionStart = TimeStamp::Now();
+ }
+ } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
+ SetConnectEnd(TimeStamp::Now(), false);
+ } else if (status == NS_NET_STATUS_SENDING_TO) {
+ // Set the timestamp to Now(), only if it null
+ SetRequestStart(TimeStamp::Now(), true);
+ }
+ }
+
+ if (!mTransportSink) return;
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // Need to do this before the STATUS_RECEIVING_FROM check below, to make
+ // sure that the activity distributor gets told about all status events.
+
+ // upon STATUS_WAITING_FOR; report request body sent
+ if ((mHasRequestBody) && (status == NS_NET_STATUS_WAITING_FOR)) {
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT, PR_Now(), 0, ""_ns);
+ }
+
+ // report the status and progress
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
+ static_cast<uint32_t>(status), PR_Now(), progress, ""_ns);
+
+ // nsHttpChannel synthesizes progress events in OnDataAvailable
+ if (status == NS_NET_STATUS_RECEIVING_FROM) return;
+
+ int64_t progressMax;
+
+ if (status == NS_NET_STATUS_SENDING_TO) {
+ // suppress progress when only writing request headers
+ if (!mHasRequestBody) {
+ LOG1(
+ ("nsHttpTransaction::OnTransportStatus %p "
+ "SENDING_TO without request body\n",
+ this));
+ return;
+ }
+
+ if (mReader) {
+ // A mRequestStream method is on the stack - wait.
+ LOG(
+ ("nsHttpTransaction::OnSocketStatus [this=%p] "
+ "Skipping Re-Entrant NS_NET_STATUS_SENDING_TO\n",
+ this));
+ // its ok to coalesce several of these into one deferred event
+ mDeferredSendProgress = true;
+ return;
+ }
+
+ nsCOMPtr<nsITellableStream> tellable = do_QueryInterface(mRequestStream);
+ if (!tellable) {
+ LOG1(
+ ("nsHttpTransaction::OnTransportStatus %p "
+ "SENDING_TO without tellable request stream\n",
+ this));
+ MOZ_ASSERT(
+ !mRequestStream,
+ "mRequestStream should be tellable as it was wrapped in "
+ "nsBufferedInputStream, which provides the tellable interface even "
+ "when wrapping non-tellable streams.");
+ progress = 0;
+ } else {
+ int64_t prog = 0;
+ tellable->Tell(&prog);
+ progress = prog;
+ }
+
+ // when uploading, we include the request headers in the progress
+ // notifications.
+ progressMax = mRequestSize;
+ } else {
+ progress = 0;
+ progressMax = 0;
+ }
+
+ mTransportSink->OnTransportStatus(transport, status, progress, progressMax);
+}
+
+bool nsHttpTransaction::IsDone() { return mTransactionDone; }
+
+nsresult nsHttpTransaction::Status() { return mStatus; }
+
+uint32_t nsHttpTransaction::Caps() { return mCaps & ~mCapsToClear; }
+
+void nsHttpTransaction::SetDNSWasRefreshed() {
+ MOZ_ASSERT(mConsumerTarget->IsOnCurrentThread(),
+ "SetDNSWasRefreshed on target thread only!");
+ mCapsToClear |= NS_HTTP_REFRESH_DNS;
+}
+
+nsresult nsHttpTransaction::ReadRequestSegment(nsIInputStream* stream,
+ void* closure, const char* buf,
+ uint32_t offset, uint32_t count,
+ uint32_t* countRead) {
+ // For the tracking of sent bytes that we used to do for the networkstats
+ // API, please see bug 1318883 where it was removed.
+
+ nsHttpTransaction* trans = (nsHttpTransaction*)closure;
+ nsresult rv = trans->mReader->OnReadSegment(buf, count, countRead);
+ if (NS_FAILED(rv)) {
+ trans->MaybeRefreshSecurityInfo();
+ return rv;
+ }
+
+ LOG(("nsHttpTransaction::ReadRequestSegment %p read=%u", trans, *countRead));
+
+ trans->mSentData = true;
+ return NS_OK;
+}
+
+nsresult nsHttpTransaction::ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count, uint32_t* countRead) {
+ LOG(("nsHttpTransaction::ReadSegments %p", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mTransactionDone) {
+ *countRead = 0;
+ return mStatus;
+ }
+
+ if (!m0RTTInProgress) {
+ MaybeCancelFallbackTimer();
+ }
+
+ if (!mConnected && !m0RTTInProgress) {
+ mConnected = true;
+ MaybeRefreshSecurityInfo();
+ }
+
+ mDeferredSendProgress = false;
+ mReader = reader;
+ nsresult rv =
+ mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead);
+ mReader = nullptr;
+
+ if (m0RTTInProgress && (mEarlyDataDisposition == EARLY_NONE) &&
+ NS_SUCCEEDED(rv) && (*countRead > 0)) {
+ LOG(("mEarlyDataDisposition = EARLY_SENT"));
+ mEarlyDataDisposition = EARLY_SENT;
+ }
+
+ if (mDeferredSendProgress && mConnection) {
+ // to avoid using mRequestStream concurrently, OnTransportStatus()
+ // did not report upload status off the ReadSegments() stack from
+ // nsSocketTransport do it now.
+ OnTransportStatus(mConnection->Transport(), NS_NET_STATUS_SENDING_TO, 0);
+ }
+ mDeferredSendProgress = false;
+
+ if (mForceRestart) {
+ // The forceRestart condition was dealt with on the stack, but it did not
+ // clear the flag because nsPipe in the readsegment stack clears out
+ // return codes, so we need to use the flag here as a cue to return
+ // ERETARGETED
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_BINDING_RETARGETED;
+ }
+ mForceRestart = false;
+ }
+
+ // if read would block then we need to AsyncWait on the request stream.
+ // have callback occur on socket thread so we stay synchronized.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ nsCOMPtr<nsIAsyncInputStream> asyncIn = do_QueryInterface(mRequestStream);
+ if (asyncIn) {
+ nsCOMPtr<nsIEventTarget> target;
+ Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
+ if (target) {
+ asyncIn->AsyncWait(this, 0, 0, target);
+ } else {
+ NS_ERROR("no socket thread event target");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsHttpTransaction::WritePipeSegment(nsIOutputStream* stream,
+ void* closure, char* buf,
+ uint32_t offset, uint32_t count,
+ uint32_t* countWritten) {
+ nsHttpTransaction* trans = (nsHttpTransaction*)closure;
+
+ if (trans->mTransactionDone) return NS_BASE_STREAM_CLOSED; // stop iterating
+
+ if (trans->TimingEnabled()) {
+ // Set the timestamp to Now(), only if it null
+ trans->SetResponseStart(TimeStamp::Now(), true);
+ }
+
+ // Bug 1153929 - add checks to fix windows crash
+ MOZ_ASSERT(trans->mWriter);
+ if (!trans->mWriter) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+ //
+ // OK, now let the caller fill this segment with data.
+ //
+ rv = trans->mWriter->OnWriteSegment(buf, count, countWritten);
+ if (NS_FAILED(rv)) {
+ trans->MaybeRefreshSecurityInfo();
+ return rv; // caller didn't want to write anything
+ }
+
+ LOG(("nsHttpTransaction::WritePipeSegment %p written=%u", trans,
+ *countWritten));
+
+ MOZ_ASSERT(*countWritten > 0, "bad writer");
+ trans->mReceivedData = true;
+ trans->mTransferSize += *countWritten;
+
+ // Let the transaction "play" with the buffer. It is free to modify
+ // the contents of the buffer and/or modify countWritten.
+ // - Bytes in HTTP headers don't count towards countWritten, so the input
+ // side of pipe (aka nsHttpChannel's mTransactionPump) won't hit
+ // OnInputStreamReady until all headers have been parsed.
+ //
+ rv = trans->ProcessData(buf, *countWritten, countWritten);
+ if (NS_FAILED(rv)) trans->Close(rv);
+
+ return rv; // failure code only stops WriteSegments; it is not propagated.
+}
+
+bool nsHttpTransaction::ShouldThrottle() {
+ if (mClassOfServiceFlags & nsIClassOfService::DontThrottle) {
+ // We deliberately don't touch the throttling window here since
+ // DontThrottle requests are expected to be long-standing media
+ // streams and would just unnecessarily block running downloads.
+ // If we want to ballance bandwidth for media responses against
+ // running downloads, we need to find something smarter like
+ // changing the suspend/resume throttling intervals at-runtime.
+ return false;
+ }
+
+ if (!gHttpHandler->ConnMgr()->ShouldThrottle(this)) {
+ // We are not obligated to throttle
+ return false;
+ }
+
+ if (mContentRead < 16000) {
+ // Let the first bytes go, it may also well be all the content we get
+ LOG(("nsHttpTransaction::ShouldThrottle too few content (%" PRIi64
+ ") this=%p",
+ mContentRead, this));
+ return false;
+ }
+
+ if (!(mClassOfServiceFlags & nsIClassOfService::Throttleable) &&
+ gHttpHandler->ConnMgr()->IsConnEntryUnderPressure(mConnInfo)) {
+ LOG(("nsHttpTransaction::ShouldThrottle entry pressure this=%p", this));
+ // This is expensive to check (two hashtable lookups) but may help
+ // freeing connections for active tab transactions.
+ // Checking this only for transactions that are not explicitly marked
+ // as throttleable because trackers and (specially) downloads should
+ // keep throttling even under pressure.
+ return false;
+ }
+
+ return true;
+}
+
+void nsHttpTransaction::DontReuseConnection() {
+ LOG(("nsHttpTransaction::DontReuseConnection %p\n", this));
+ if (!OnSocketThread()) {
+ LOG(("DontReuseConnection %p not on socket thread\n", this));
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("nsHttpTransaction::DontReuseConnection", this,
+ &nsHttpTransaction::DontReuseConnection);
+ gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ if (mConnection) {
+ mConnection->DontReuse();
+ }
+}
+
+nsresult nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ LOG(("nsHttpTransaction::WriteSegments %p", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mTransactionDone) {
+ return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
+ }
+
+ if (ShouldThrottle()) {
+ if (mThrottlingReadAllowance == THROTTLE_NO_LIMIT) { // no limit set
+ // V1: ThrottlingReadLimit() returns 0
+ mThrottlingReadAllowance = gHttpHandler->ThrottlingReadLimit();
+ }
+ } else {
+ mThrottlingReadAllowance = THROTTLE_NO_LIMIT; // don't limit
+ }
+
+ if (mThrottlingReadAllowance == 0) { // depleted
+ if (gHttpHandler->ConnMgr()->CurrentBrowserId() != mBrowserId) {
+ nsHttp::NotifyActiveTabLoadOptimization();
+ }
+
+ // Must remember that we have to call ResumeRecv() on our connection when
+ // called back by the conn manager to resume reading.
+ LOG(("nsHttpTransaction::WriteSegments %p response throttled", this));
+ mReadingStopped = true;
+ // This makes the underlaying connection or stream wait for explicit resume.
+ // For h1 this means we stop reading from the socket.
+ // For h2 this means we stop updating recv window for the stream.
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ mWriter = writer;
+
+ if (!mPipeOut) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mThrottlingReadAllowance > 0) {
+ LOG(("nsHttpTransaction::WriteSegments %p limiting read from %u to %d",
+ this, count, mThrottlingReadAllowance));
+ count = std::min(count, static_cast<uint32_t>(mThrottlingReadAllowance));
+ }
+
+ nsresult rv =
+ mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten);
+
+ mWriter = nullptr;
+
+ if (mForceRestart) {
+ // The forceRestart condition was dealt with on the stack, but it did not
+ // clear the flag because nsPipe in the writesegment stack clears out
+ // return codes, so we need to use the flag here as a cue to return
+ // ERETARGETED
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_BINDING_RETARGETED;
+ }
+ mForceRestart = false;
+ }
+
+ // if pipe would block then we need to AsyncWait on it. have callback
+ // occur on socket thread so we stay synchronized.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ nsCOMPtr<nsIEventTarget> target;
+ Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
+ if (target) {
+ mPipeOut->AsyncWait(this, 0, 0, target);
+ mWaitingOnPipeOut = true;
+ } else {
+ NS_ERROR("no socket thread event target");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ } else if (mThrottlingReadAllowance > 0 && NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(count >= *countWritten);
+ mThrottlingReadAllowance -= *countWritten;
+ }
+
+ return rv;
+}
+
+bool nsHttpTransaction::ProxyConnectFailed() { return mProxyConnectFailed; }
+
+bool nsHttpTransaction::DataSentToChildProcess() { return false; }
+
+already_AddRefed<nsITransportSecurityInfo> nsHttpTransaction::SecurityInfo() {
+ MutexAutoLock lock(mLock);
+ return do_AddRef(mSecurityInfo);
+}
+
+bool nsHttpTransaction::HasStickyConnection() const {
+ return mCaps & NS_HTTP_STICKY_CONNECTION;
+}
+
+bool nsHttpTransaction::ResponseIsComplete() { return mResponseIsComplete; }
+
+int64_t nsHttpTransaction::GetTransferSize() { return mTransferSize; }
+
+int64_t nsHttpTransaction::GetRequestSize() { return mRequestSize; }
+
+bool nsHttpTransaction::IsHttp3Used() { return mIsHttp3Used; }
+
+bool nsHttpTransaction::Http2Disabled() const {
+ return mCaps & NS_HTTP_DISALLOW_SPDY;
+}
+
+bool nsHttpTransaction::Http3Disabled() const {
+ return mCaps & NS_HTTP_DISALLOW_HTTP3;
+}
+
+already_AddRefed<nsHttpConnectionInfo> nsHttpTransaction::GetConnInfo() const {
+ RefPtr<nsHttpConnectionInfo> connInfo = mConnInfo->Clone();
+ return connInfo.forget();
+}
+
+already_AddRefed<Http2PushedStreamWrapper>
+nsHttpTransaction::TakePushedStreamById(uint32_t aStreamId) {
+ MOZ_ASSERT(mConsumerTarget->IsOnCurrentThread());
+ MOZ_ASSERT(aStreamId);
+
+ auto entry = mIDToStreamMap.Lookup(aStreamId);
+ if (entry) {
+ RefPtr<Http2PushedStreamWrapper> stream = entry.Data();
+ entry.Remove();
+ return stream.forget();
+ }
+
+ return nullptr;
+}
+
+void nsHttpTransaction::OnPush(Http2PushedStreamWrapper* aStream) {
+ LOG(("nsHttpTransaction::OnPush %p aStream=%p", this, aStream));
+ MOZ_ASSERT(aStream);
+ MOZ_ASSERT(mOnPushCallback);
+ MOZ_ASSERT(mConsumerTarget);
+
+ RefPtr<Http2PushedStreamWrapper> stream = aStream;
+ if (!mConsumerTarget->IsOnCurrentThread()) {
+ RefPtr<nsHttpTransaction> self = this;
+ if (NS_FAILED(mConsumerTarget->Dispatch(
+ NS_NewRunnableFunction("nsHttpTransaction::OnPush",
+ [self, stream]() { self->OnPush(stream); }),
+ NS_DISPATCH_NORMAL))) {
+ stream->OnPushFailed();
+ }
+ return;
+ }
+
+ mIDToStreamMap.WithEntryHandle(stream->StreamID(), [&](auto&& entry) {
+ MOZ_ASSERT(!entry);
+ entry.OrInsert(stream);
+ });
+
+ if (NS_FAILED(mOnPushCallback(stream->StreamID(), stream->GetResourceUrl(),
+ stream->GetRequestString(), this))) {
+ stream->OnPushFailed();
+ mIDToStreamMap.Remove(stream->StreamID());
+ }
+}
+
+nsHttpTransaction* nsHttpTransaction::AsHttpTransaction() { return this; }
+
+HttpTransactionParent* nsHttpTransaction::AsHttpTransactionParent() {
+ return nullptr;
+}
+
+nsHttpTransaction::HTTPSSVC_CONNECTION_FAILED_REASON
+nsHttpTransaction::ErrorCodeToFailedReason(nsresult aErrorCode) {
+ HTTPSSVC_CONNECTION_FAILED_REASON reason = HTTPSSVC_CONNECTION_OTHERS;
+ switch (aErrorCode) {
+ case NS_ERROR_UNKNOWN_HOST:
+ reason = HTTPSSVC_CONNECTION_UNKNOWN_HOST;
+ break;
+ case NS_ERROR_CONNECTION_REFUSED:
+ reason = HTTPSSVC_CONNECTION_UNREACHABLE;
+ break;
+ default:
+ if (m421Received) {
+ reason = HTTPSSVC_CONNECTION_421_RECEIVED;
+ } else if (NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_SECURITY) {
+ reason = HTTPSSVC_CONNECTION_SECURITY_ERROR;
+ }
+ break;
+ }
+ return reason;
+}
+
+bool nsHttpTransaction::PrepareSVCBRecordsForRetry(
+ const nsACString& aFailedDomainName, const nsACString& aFailedAlpn,
+ bool& aAllRecordsHaveEchConfig) {
+ MOZ_ASSERT(mRecordsForRetry.IsEmpty());
+ if (!mHTTPSSVCRecord) {
+ return false;
+ }
+
+ // If we already failed to connect with h3, don't select records that supports
+ // h3.
+ bool noHttp3 = mCaps & NS_HTTP_DISALLOW_HTTP3;
+
+ bool unused;
+ nsTArray<RefPtr<nsISVCBRecord>> records;
+ Unused << mHTTPSSVCRecord->GetAllRecordsWithEchConfig(
+ mCaps & NS_HTTP_DISALLOW_SPDY, noHttp3, &aAllRecordsHaveEchConfig,
+ &unused, records);
+
+ // Note that it's possible that we can't get any usable record here. For
+ // example, when http3 connection is failed, we won't select records with
+ // http3 alpn.
+
+ // If not all records have echConfig, we'll directly fallback to the origin
+ // server.
+ if (!aAllRecordsHaveEchConfig) {
+ return false;
+ }
+
+ // Take the records behind the failed one and put them into mRecordsForRetry.
+ for (const auto& record : records) {
+ nsAutoCString name;
+ record->GetName(name);
+ nsAutoCString alpn;
+ nsresult rv = record->GetSelectedAlpn(alpn);
+
+ if (name == aFailedDomainName) {
+ // If the record has no alpn or the alpn is already tried, we skip this
+ // record.
+ if (NS_FAILED(rv) || alpn == aFailedAlpn) {
+ continue;
+ }
+ }
+
+ mRecordsForRetry.InsertElementAt(0, record);
+ }
+
+ // Set mHTTPSSVCRecord to null to avoid this function being executed twice.
+ mHTTPSSVCRecord = nullptr;
+ return !mRecordsForRetry.IsEmpty();
+}
+
+already_AddRefed<nsHttpConnectionInfo>
+nsHttpTransaction::PrepareFastFallbackConnInfo(bool aEchConfigUsed) {
+ MOZ_ASSERT(mHTTPSSVCRecord && mOrigConnInfo);
+
+ RefPtr<nsHttpConnectionInfo> fallbackConnInfo;
+ nsCOMPtr<nsISVCBRecord> fastFallbackRecord;
+ Unused << mHTTPSSVCRecord->GetServiceModeRecord(
+ mCaps & NS_HTTP_DISALLOW_SPDY, true, getter_AddRefs(fastFallbackRecord));
+
+ if (fastFallbackRecord && aEchConfigUsed) {
+ nsAutoCString echConfig;
+ Unused << fastFallbackRecord->GetEchConfig(echConfig);
+ if (echConfig.IsEmpty()) {
+ fastFallbackRecord = nullptr;
+ }
+ }
+
+ if (!fastFallbackRecord) {
+ if (aEchConfigUsed) {
+ LOG(
+ ("nsHttpTransaction::PrepareFastFallbackConnInfo [this=%p] no record "
+ "can be used",
+ this));
+ return nullptr;
+ }
+
+ if (mOrigConnInfo->IsHttp3()) {
+ mOrigConnInfo->CloneAsDirectRoute(getter_AddRefs(fallbackConnInfo));
+ } else {
+ fallbackConnInfo = mOrigConnInfo;
+ }
+ return fallbackConnInfo.forget();
+ }
+
+ fallbackConnInfo =
+ mOrigConnInfo->CloneAndAdoptHTTPSSVCRecord(fastFallbackRecord);
+ return fallbackConnInfo.forget();
+}
+
+void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason) {
+ LOG(("nsHttpTransaction::PrepareConnInfoForRetry [this=%p reason=%" PRIx32
+ "]",
+ this, static_cast<uint32_t>(aReason)));
+ RefPtr<nsHttpConnectionInfo> failedConnInfo = mConnInfo->Clone();
+ mConnInfo = nullptr;
+ bool echConfigUsed =
+ gHttpHandler->EchConfigEnabled(failedConnInfo->IsHttp3()) &&
+ !failedConnInfo->GetEchConfig().IsEmpty();
+
+ if (mFastFallbackTriggered) {
+ mFastFallbackTriggered = false;
+ MOZ_ASSERT(mBackupConnInfo);
+ mConnInfo.swap(mBackupConnInfo);
+ return;
+ }
+
+ auto useOrigConnInfoToRetry = [&]() {
+ mOrigConnInfo.swap(mConnInfo);
+ if (mConnInfo->IsHttp3() &&
+ ((mCaps & NS_HTTP_DISALLOW_HTTP3) ||
+ gHttpHandler->IsHttp3Excluded(mConnInfo->GetRoutedHost().IsEmpty()
+ ? mConnInfo->GetOrigin()
+ : mConnInfo->GetRoutedHost()))) {
+ RefPtr<nsHttpConnectionInfo> ci;
+ mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
+ mConnInfo = ci;
+ }
+ };
+
+ if (!echConfigUsed) {
+ LOG((" echConfig is not used, fallback to origin conn info"));
+ useOrigConnInfoToRetry();
+ return;
+ }
+
+ Telemetry::HistogramID id = Telemetry::TRANSACTION_ECH_RETRY_OTHERS_COUNT;
+ auto updateCount = MakeScopeExit([&] {
+ auto entry = mEchRetryCounterMap.Lookup(id);
+ MOZ_ASSERT(entry, "table not initialized");
+ if (entry) {
+ *entry += 1;
+ }
+ });
+
+ if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH)) {
+ LOG((" Got SSL_ERROR_ECH_RETRY_WITHOUT_ECH, use empty echConfig to retry"));
+ failedConnInfo->SetEchConfig(EmptyCString());
+ failedConnInfo.swap(mConnInfo);
+ id = Telemetry::TRANSACTION_ECH_RETRY_WITHOUT_ECH_COUNT;
+ return;
+ }
+
+ if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH)) {
+ LOG((" Got SSL_ERROR_ECH_RETRY_WITH_ECH, use retry echConfig"));
+ MOZ_ASSERT(mConnection);
+
+ nsCOMPtr<nsITLSSocketControl> socketControl;
+ if (mConnection) {
+ mConnection->GetTLSSocketControl(getter_AddRefs(socketControl));
+ }
+ MOZ_ASSERT(socketControl);
+
+ nsAutoCString retryEchConfig;
+ if (socketControl &&
+ NS_SUCCEEDED(socketControl->GetRetryEchConfig(retryEchConfig))) {
+ MOZ_ASSERT(!retryEchConfig.IsEmpty());
+
+ failedConnInfo->SetEchConfig(retryEchConfig);
+ failedConnInfo.swap(mConnInfo);
+ }
+ id = Telemetry::TRANSACTION_ECH_RETRY_WITH_ECH_COUNT;
+ return;
+ }
+
+ // Note that we retry the connection not only for SSL_ERROR_ECH_FAILED, but
+ // also for all failure cases.
+ if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED) ||
+ NS_FAILED(aReason)) {
+ LOG((" Got SSL_ERROR_ECH_FAILED, try other records"));
+ if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED)) {
+ id = Telemetry::TRANSACTION_ECH_RETRY_ECH_FAILED_COUNT;
+ }
+ if (mRecordsForRetry.IsEmpty()) {
+ if (mHTTPSSVCRecord) {
+ bool allRecordsHaveEchConfig = true;
+ if (!PrepareSVCBRecordsForRetry(failedConnInfo->GetRoutedHost(),
+ failedConnInfo->GetNPNToken(),
+ allRecordsHaveEchConfig)) {
+ LOG(
+ (" Can't find other records with echConfig, "
+ "allRecordsHaveEchConfig=%d",
+ allRecordsHaveEchConfig));
+ if (gHttpHandler->FallbackToOriginIfConfigsAreECHAndAllFailed() ||
+ !allRecordsHaveEchConfig) {
+ useOrigConnInfoToRetry();
+ }
+ return;
+ }
+ } else {
+ LOG((" No available records to retry"));
+ if (gHttpHandler->FallbackToOriginIfConfigsAreECHAndAllFailed()) {
+ useOrigConnInfoToRetry();
+ }
+ return;
+ }
+ }
+
+ if (LOG5_ENABLED()) {
+ LOG(("SvcDomainName to retry: ["));
+ for (const auto& r : mRecordsForRetry) {
+ nsAutoCString name;
+ r->GetName(name);
+ nsAutoCString alpn;
+ r->GetSelectedAlpn(alpn);
+ LOG((" name=%s alpn=%s", name.get(), alpn.get()));
+ }
+ LOG(("]"));
+ }
+
+ RefPtr<nsISVCBRecord> recordsForRetry =
+ mRecordsForRetry.PopLastElement().forget();
+ mConnInfo = mOrigConnInfo->CloneAndAdoptHTTPSSVCRecord(recordsForRetry);
+ }
+}
+
+void nsHttpTransaction::MaybeReportFailedSVCDomain(
+ nsresult aReason, nsHttpConnectionInfo* aFailedConnInfo) {
+ if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH) ||
+ aReason != psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH)) {
+ return;
+ }
+
+ Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON,
+ ErrorCodeToFailedReason(aReason));
+
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (dns) {
+ const nsCString& failedHost = aFailedConnInfo->GetRoutedHost().IsEmpty()
+ ? aFailedConnInfo->GetOrigin()
+ : aFailedConnInfo->GetRoutedHost();
+ LOG(("add failed domain name [%s] -> [%s] to exclusion list",
+ aFailedConnInfo->GetOrigin().get(), failedHost.get()));
+ Unused << dns->ReportFailedSVCDomainName(aFailedConnInfo->GetOrigin(),
+ failedHost);
+ }
+}
+
+bool nsHttpTransaction::ShouldRestartOn0RttError(nsresult reason) {
+ LOG(
+ ("nsHttpTransaction::ShouldRestartOn0RttError [this=%p, "
+ "mEarlyDataWasAvailable=%d error=%" PRIx32 "]\n",
+ this, mEarlyDataWasAvailable, static_cast<uint32_t>(reason)));
+ return StaticPrefs::network_http_early_data_disable_on_error() &&
+ mEarlyDataWasAvailable && SecurityErrorThatMayNeedRestart(reason);
+}
+
+static void MaybeRemoveSSLToken(nsITransportSecurityInfo* aSecurityInfo) {
+ if (!StaticPrefs::
+ network_http_remove_resumption_token_when_early_data_failed()) {
+ return;
+ }
+ if (!aSecurityInfo) {
+ return;
+ }
+ nsAutoCString key;
+ aSecurityInfo->GetPeerId(key);
+ nsresult rv = SSLTokensCache::RemoveAll(key);
+ LOG(("RemoveSSLToken [key=%s, rv=%" PRIx32 "]", key.get(),
+ static_cast<uint32_t>(rv)));
+}
+
+void nsHttpTransaction::Close(nsresult reason) {
+ LOG(("nsHttpTransaction::Close [this=%p reason=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(reason)));
+
+ {
+ MutexAutoLock lock(mLock);
+ mEarlyHintObserver = nullptr;
+ mWebTransportSessionEventListener = nullptr;
+ }
+
+ if (!mClosed) {
+ gHttpHandler->ConnMgr()->RemoveActiveTransaction(this);
+ mActivated = false;
+ }
+
+ if (mDNSRequest) {
+ mDNSRequest->Cancel(NS_ERROR_ABORT);
+ mDNSRequest = nullptr;
+ }
+
+ MaybeCancelFallbackTimer();
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (reason == NS_BINDING_RETARGETED) {
+ LOG((" close %p skipped due to ERETARGETED\n", this));
+ return;
+ }
+
+ if (mClosed) {
+ LOG((" already closed\n"));
+ return;
+ }
+
+ NotifyTransactionObserver(reason);
+
+ if (mTokenBucketCancel) {
+ mTokenBucketCancel->Cancel(reason);
+ mTokenBucketCancel = nullptr;
+ }
+
+ // report the reponse is complete if not already reported
+ if (!mResponseIsComplete) {
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, PR_Now(),
+ static_cast<uint64_t>(mContentRead), ""_ns);
+ }
+
+ // report that this transaction is closing
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE, PR_Now(), 0, ""_ns);
+
+ // we must no longer reference the connection! find out if the
+ // connection was being reused before letting it go.
+ bool connReused = false;
+ bool isHttp2or3 = false;
+ if (mConnection) {
+ connReused = mConnection->IsReused();
+ isHttp2or3 = mConnection->Version() >= HttpVersion::v2_0;
+ if (!mConnected) {
+ MaybeRefreshSecurityInfo();
+ }
+ }
+ mConnected = false;
+
+ // When mDoNotRemoveAltSvc is true, this means we want to keep the AltSvc in
+ // in the conncetion info. In this case, let's not apply HTTPS RR retry logic
+ // to make sure this transaction can be restarted with the same conncetion
+ // info.
+ bool shouldRestartTransactionForHTTPSRR =
+ mOrigConnInfo && AllowedErrorForHTTPSRRFallback(reason) &&
+ !mDoNotRemoveAltSvc;
+
+ //
+ // if the connection was reset or closed before we wrote any part of the
+ // request or if we wrote the request but didn't receive any part of the
+ // response and the connection was being reused, then we can (and really
+ // should) assume that we wrote to a stale connection and we must therefore
+ // repeat the request over a new connection.
+ //
+ // We have decided to retry not only in case of the reused connections, but
+ // all safe methods(bug 1236277).
+ //
+ // NOTE: the conditions under which we will automatically retry the HTTP
+ // request have to be carefully selected to avoid duplication of the
+ // request from the point-of-view of the server. such duplication could
+ // have dire consequences including repeated purchases, etc.
+ //
+ // NOTE: because of the way SSL proxy CONNECT is implemented, it is
+ // possible that the transaction may have received data without having
+ // sent any data. for this reason, mSendData == FALSE does not imply
+ // mReceivedData == FALSE. (see bug 203057 for more info.)
+ //
+ // Never restart transactions that are marked as sticky to their conenction.
+ // We use that capability to identify transactions bound to connection based
+ // authentication. Reissuing them on a different connections will break
+ // this bondage. Major issue may arise when there is an NTLM message auth
+ // header on the transaction and we send it to a different NTLM authenticated
+ // connection. It will break that connection and also confuse the channel's
+ // auth provider, beliving the cached credentials are wrong and asking for
+ // the password mistakenly again from the user.
+ if ((reason == NS_ERROR_NET_RESET || reason == NS_OK ||
+ reason ==
+ psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA) ||
+ ShouldRestartOn0RttError(reason) ||
+ shouldRestartTransactionForHTTPSRR) &&
+ (!(mCaps & NS_HTTP_STICKY_CONNECTION) ||
+ (mCaps & NS_HTTP_CONNECTION_RESTARTABLE) ||
+ (mEarlyDataDisposition == EARLY_425))) {
+ if (mForceRestart) {
+ SetRestartReason(TRANSACTION_RESTART_FORCED);
+ if (NS_SUCCEEDED(Restart())) {
+ if (mResponseHead) {
+ mResponseHead->Reset();
+ }
+ mContentRead = 0;
+ mContentLength = -1;
+ delete mChunkedDecoder;
+ mChunkedDecoder = nullptr;
+ mHaveStatusLine = false;
+ mHaveAllHeaders = false;
+ mHttpResponseMatched = false;
+ mResponseIsComplete = false;
+ mDidContentStart = false;
+ mNoContent = false;
+ mSentData = false;
+ mReceivedData = false;
+ mSupportsHTTP3 = false;
+ LOG(("transaction force restarted\n"));
+ return;
+ }
+ }
+
+ mDoNotTryEarlyData = true;
+
+ // reallySentData is meant to separate the instances where data has
+ // been sent by this transaction but buffered at a higher level while
+ // a TLS session (perhaps via a tunnel) is setup.
+ bool reallySentData =
+ mSentData && (!mConnection || mConnection->BytesWritten());
+
+ // If this is true, it means we failed to use the HTTPSSVC connection info
+ // to connect to the server. We need to retry with the original connection
+ // info.
+ shouldRestartTransactionForHTTPSRR &= !reallySentData;
+
+ if (reason ==
+ psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA) ||
+ reason == psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT) ||
+ (!mReceivedData && ((mRequestHead && mRequestHead->IsSafeMethod()) ||
+ !reallySentData || connReused)) ||
+ shouldRestartTransactionForHTTPSRR) {
+ if (shouldRestartTransactionForHTTPSRR) {
+ MaybeReportFailedSVCDomain(reason, mConnInfo);
+ PrepareConnInfoForRetry(reason);
+ mDontRetryWithDirectRoute = true;
+ LOG(
+ ("transaction will be restarted with the fallback connection info "
+ "key=%s",
+ mConnInfo ? mConnInfo->HashKey().get() : "None"));
+ }
+
+ if (shouldRestartTransactionForHTTPSRR) {
+ auto toRestartReason =
+ [](nsresult aStatus) -> TRANSACTION_RESTART_REASON {
+ if (aStatus == NS_ERROR_NET_RESET) {
+ return TRANSACTION_RESTART_HTTPS_RR_NET_RESET;
+ }
+ if (aStatus == NS_ERROR_CONNECTION_REFUSED) {
+ return TRANSACTION_RESTART_HTTPS_RR_CONNECTION_REFUSED;
+ }
+ if (aStatus == NS_ERROR_UNKNOWN_HOST) {
+ return TRANSACTION_RESTART_HTTPS_RR_UNKNOWN_HOST;
+ }
+ if (aStatus == NS_ERROR_NET_TIMEOUT) {
+ return TRANSACTION_RESTART_HTTPS_RR_NET_TIMEOUT;
+ }
+ if (psm::IsNSSErrorCode(-1 * NS_ERROR_GET_CODE(aStatus))) {
+ return TRANSACTION_RESTART_HTTPS_RR_SEC_ERROR;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unexpected reason");
+ return TRANSACTION_RESTART_OTHERS;
+ };
+ SetRestartReason(toRestartReason(reason));
+ } else if (!reallySentData) {
+ SetRestartReason(TRANSACTION_RESTART_NO_DATA_SENT);
+ } else if (reason == psm::GetXPCOMFromNSSError(
+ SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA)) {
+ SetRestartReason(TRANSACTION_RESTART_DOWNGRADE_WITH_EARLY_DATA);
+ } else if (reason ==
+ psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT)) {
+ SetRestartReason(TRANSACTION_RESTART_PROTOCOL_VERSION_ALERT);
+ }
+ // if restarting fails, then we must proceed to close the pipe,
+ // which will notify the channel that the transaction failed.
+ // Note that when echConfig is enabled, it's possible that we don't have a
+ // usable connection info to retry.
+ if (mConnInfo && NS_SUCCEEDED(Restart())) {
+ return;
+ }
+ // mConnInfo could be set to null in PrepareConnInfoForRetry() when we
+ // can't find an available https rr to retry. We have to set mConnInfo
+ // back to mOrigConnInfo to make sure no crash when mConnInfo being
+ // accessed again.
+ if (!mConnInfo) {
+ mConnInfo.swap(mOrigConnInfo);
+ MOZ_ASSERT(mConnInfo);
+ }
+ }
+ }
+
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_RESTART_REASON,
+ mRestartReason);
+
+ if (!mResponseIsComplete && NS_SUCCEEDED(reason) && isHttp2or3) {
+ // Responses without content-length header field are still complete if
+ // they are transfered over http2 or http3 and the stream is properly
+ // closed.
+ mResponseIsComplete = true;
+ }
+
+ if ((mChunkedDecoder || (mContentLength >= int64_t(0))) &&
+ (NS_SUCCEEDED(reason) && !mResponseIsComplete)) {
+ NS_WARNING("Partial transfer, incomplete HTTP response received");
+
+ if ((mHttpResponseCode / 100 == 2) && (mHttpVersion >= HttpVersion::v1_1)) {
+ FrameCheckLevel clevel = gHttpHandler->GetEnforceH1Framing();
+ if (clevel >= FRAMECHECK_BARELY) {
+ // If clevel == FRAMECHECK_STRICT mark any incomplete response as
+ // partial.
+ // if clevel == FRAMECHECK_BARELY: 1) mark a chunked-encoded response
+ // that do not ends on exactly a chunk boundary as partial; We are not
+ // strict about the last 0-size chunk and do not mark as parial
+ // responses that do not have the last 0-size chunk but do end on a
+ // chunk boundary. (check mChunkedDecoder->GetChunkRemaining() != 0)
+ // 2) mark a transfer that is partial and it is not chunk-encoded or
+ // gzip-encoded or other content-encoding as partial. (check
+ // !mChunkedDecoder && !mContentDecoding && mContentDecodingCheck))
+ // if clevel == FRAMECHECK_STRICT_CHUNKED mark a chunked-encoded
+ // response that ends on exactly a chunk boundary also as partial.
+ // Here a response must have the last 0-size chunk.
+ if ((clevel == FRAMECHECK_STRICT) ||
+ (mChunkedDecoder && (mChunkedDecoder->GetChunkRemaining() ||
+ (clevel == FRAMECHECK_STRICT_CHUNKED))) ||
+ (!mChunkedDecoder && !mContentDecoding && mContentDecodingCheck)) {
+ reason = NS_ERROR_NET_PARTIAL_TRANSFER;
+ LOG(("Partial transfer, incomplete HTTP response received: %s",
+ mChunkedDecoder ? "broken chunk" : "c-l underrun"));
+ }
+ }
+ }
+
+ if (mConnection) {
+ // whether or not we generate an error for the transaction
+ // bad framing means we don't want a pconn
+ mConnection->DontReuse();
+ }
+ }
+
+ bool relConn = true;
+ if (NS_SUCCEEDED(reason)) {
+ // the server has not sent the final \r\n terminating the header
+ // section, and there may still be a header line unparsed. let's make
+ // sure we parse the remaining header line, and then hopefully, the
+ // response will be usable (see bug 88792).
+ if (!mHaveAllHeaders) {
+ char data[] = "\n\n";
+ uint32_t unused = 0;
+ // If we have a partial line already, we actually need two \ns to finish
+ // the headers section.
+ Unused << ParseHead(data, mLineBuf.IsEmpty() ? 1 : 2, &unused);
+
+ if (mResponseHead->Version() == HttpVersion::v0_9) {
+ // Reject 0 byte HTTP/0.9 Responses - bug 423506
+ LOG(("nsHttpTransaction::Close %p 0 Byte 0.9 Response", this));
+ reason = NS_ERROR_NET_RESET;
+ }
+ }
+
+ // honor the sticky connection flag...
+ if (mCaps & NS_HTTP_STICKY_CONNECTION) {
+ LOG((" keeping the connection because of STICKY_CONNECTION flag"));
+ relConn = false;
+ }
+
+ // if the proxy connection has failed, we want the connection be held
+ // to allow the upper layers (think nsHttpChannel) to close it when
+ // the failure is unrecoverable.
+ // we can't just close it here, because mProxyConnectFailed is to a general
+ // flag and is also set for e.g. 407 which doesn't mean to kill the
+ // connection, specifically when connection oriented auth may be involved.
+ if (mProxyConnectFailed) {
+ LOG((" keeping the connection because of mProxyConnectFailed"));
+ relConn = false;
+ }
+
+ // Use mOrigConnInfo as an indicator that this transaction is completed
+ // successfully with an HTTPSSVC record.
+ if (mOrigConnInfo) {
+ Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON,
+ HTTPSSVC_CONNECTION_OK);
+ }
+ }
+
+ // mTimings.responseEnd is normally recorded based on the end of a
+ // HTTP delimiter such as chunked-encodings or content-length. However,
+ // EOF or an error still require an end time be recorded.
+ if (TimingEnabled()) {
+ const TimingStruct timings = Timings();
+ if (timings.responseEnd.IsNull() && !timings.responseStart.IsNull()) {
+ SetResponseEnd(TimeStamp::Now());
+ }
+
+ // Accumulate download throughput telemetry
+ const int64_t TELEMETRY_DOWNLOAD_SIZE_GREATER_THAN_10MB =
+ (int64_t)10 * (int64_t)(1 << 20);
+ if ((mContentRead > TELEMETRY_DOWNLOAD_SIZE_GREATER_THAN_10MB) &&
+ !timings.requestStart.IsNull() && !timings.responseEnd.IsNull()) {
+ TimeDuration elapsed = timings.responseEnd - timings.requestStart;
+ double megabits = static_cast<double>(mContentRead) * 8.0 / 1000000.0;
+ uint32_t mpbs = static_cast<uint32_t>(megabits / elapsed.ToSeconds());
+
+ switch (mHttpVersion) {
+ case HttpVersion::v1_0:
+ case HttpVersion::v1_1:
+ glean::networking::http_1_download_throughput.AccumulateSamples(
+ {mpbs});
+ break;
+ case HttpVersion::v2_0:
+ glean::networking::http_2_download_throughput.AccumulateSamples(
+ {mpbs});
+ break;
+ case HttpVersion::v3_0:
+ glean::networking::http_3_download_throughput.AccumulateSamples(
+ {mpbs});
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (mTrafficCategory != HttpTrafficCategory::eInvalid) {
+ HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer();
+ if (hta) {
+ hta->AccumulateHttpTransferredSize(mTrafficCategory, mTransferSize,
+ mContentRead);
+ }
+ }
+
+ if (mThroughCaptivePortal) {
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_HTTP_TRANSACTIONS_CAPTIVE_PORTAL, 1);
+ }
+
+ if (relConn && mConnection) {
+ MutexAutoLock lock(mLock);
+ mConnection = nullptr;
+ }
+
+ if (isHttp2or3 &&
+ reason == psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT)) {
+ // Change reason to NS_ERROR_ABORT, so we avoid showing a missleading
+ // error page tthat TLS1.0 is disabled. H2 or H3 is used here so the
+ // TLS version is not a problem.
+ reason = NS_ERROR_ABORT;
+ }
+ mStatus = reason;
+ mTransactionDone = true; // forcibly flag the transaction as complete
+ mClosed = true;
+ if (mResolver) {
+ mResolver->Close();
+ mResolver = nullptr;
+ }
+ ReleaseBlockingTransaction();
+
+ // release some resources that we no longer need
+ mRequestStream = nullptr;
+ mReqHeaderBuf.Truncate();
+ mLineBuf.Truncate();
+ if (mChunkedDecoder) {
+ delete mChunkedDecoder;
+ mChunkedDecoder = nullptr;
+ }
+
+ for (const auto& entry : mEchRetryCounterMap) {
+ Telemetry::Accumulate(static_cast<Telemetry::HistogramID>(entry.GetKey()),
+ entry.GetData());
+ }
+
+ // closing this pipe triggers the channel's OnStopRequest method.
+ mPipeOut->CloseWithStatus(reason);
+}
+
+nsHttpConnectionInfo* nsHttpTransaction::ConnectionInfo() {
+ return mConnInfo.get();
+}
+
+bool // NOTE BASE CLASS
+nsAHttpTransaction::ResponseTimeoutEnabled() const {
+ return false;
+}
+
+PRIntervalTime // NOTE BASE CLASS
+nsAHttpTransaction::ResponseTimeout() {
+ return gHttpHandler->ResponseTimeout();
+}
+
+bool nsHttpTransaction::ResponseTimeoutEnabled() const {
+ return mResponseTimeoutEnabled;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction <private>
+//-----------------------------------------------------------------------------
+
+static inline void RemoveAlternateServiceUsedHeader(
+ nsHttpRequestHead* aRequestHead) {
+ if (aRequestHead) {
+ DebugOnly<nsresult> rv =
+ aRequestHead->SetHeader(nsHttp::Alternate_Service_Used, "0"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void nsHttpTransaction::SetRestartReason(TRANSACTION_RESTART_REASON aReason) {
+ if (mRestartReason == TRANSACTION_RESTART_NONE) {
+ mRestartReason = aReason;
+ }
+}
+
+nsresult nsHttpTransaction::Restart() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // limit the number of restart attempts - bug 92224
+ if (++mRestartCount >= gHttpHandler->MaxRequestAttempts()) {
+ LOG(("reached max request attempts, failing transaction @%p\n", this));
+ return NS_ERROR_NET_RESET;
+ }
+
+ LOG(("restarting transaction @%p\n", this));
+
+ if (mRequestHead) {
+ // Dispatching on a new connection better w/o an ambient connection proxy
+ // auth request header to not confuse the proxy authenticator.
+ nsAutoCString proxyAuth;
+ if (NS_SUCCEEDED(
+ mRequestHead->GetHeader(nsHttp::Proxy_Authorization, proxyAuth)) &&
+ IsStickyAuthSchemeAt(proxyAuth)) {
+ Unused << mRequestHead->ClearHeader(nsHttp::Proxy_Authorization);
+ }
+ }
+
+ // rewind streams in case we already wrote out the request
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
+ if (seekable) seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+
+ if (mDoNotTryEarlyData) {
+ MutexAutoLock lock(mLock);
+ MaybeRemoveSSLToken(mSecurityInfo);
+ }
+
+ // clear old connection state...
+ {
+ MutexAutoLock lock(mLock);
+ mSecurityInfo = nullptr;
+ }
+
+ if (mConnection) {
+ if (!mReuseOnRestart) {
+ mConnection->DontReuse();
+ }
+ MutexAutoLock lock(mLock);
+ mConnection = nullptr;
+ }
+
+ // Reset this to our default state, since this may change from one restart
+ // to the next
+ mReuseOnRestart = false;
+
+ if (!mDoNotRemoveAltSvc &&
+ (!mConnInfo->GetRoutedHost().IsEmpty() || mConnInfo->IsHttp3()) &&
+ !mDontRetryWithDirectRoute) {
+ RefPtr<nsHttpConnectionInfo> ci;
+ mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
+ mConnInfo = ci;
+ RemoveAlternateServiceUsedHeader(mRequestHead);
+ }
+
+ // Reset mDoNotRemoveAltSvc for the next try.
+ mDoNotRemoveAltSvc = false;
+ mEarlyDataWasAvailable = false;
+ mRestarted = true;
+
+ // If we weren't trying to do 'proper' ECH, disable ECH GREASE when retrying.
+ if (mConnInfo->GetEchConfig().IsEmpty() &&
+ StaticPrefs::security_tls_ech_disable_grease_on_fallback()) {
+ mCaps |= NS_HTTP_DISALLOW_ECH;
+ }
+
+ mCaps |= NS_HTTP_IS_RETRY;
+
+ // Use TRANSACTION_RESTART_OTHERS as a catch-all.
+ SetRestartReason(TRANSACTION_RESTART_OTHERS);
+
+ if (!mDoNotResetIPFamilyPreference) {
+ // Reset the IP family preferences, so the new connection can try to use
+ // another IPv4 or IPv6 address.
+ gHttpHandler->ConnMgr()->ResetIPFamilyPreference(mConnInfo);
+ }
+
+ return gHttpHandler->InitiateTransaction(this, mPriority);
+}
+
+bool nsHttpTransaction::TakeRestartedState() {
+ // This return true if the transaction has been restarted internally. Used to
+ // let the consuming nsHttpChannel reset proxy authentication. The flag is
+ // reset to false by this method.
+ return mRestarted.exchange(false);
+}
+
+char* nsHttpTransaction::LocateHttpStart(char* buf, uint32_t len,
+ bool aAllowPartialMatch) {
+ MOZ_ASSERT(!aAllowPartialMatch || mLineBuf.IsEmpty());
+
+ static const char HTTPHeader[] = "HTTP/1.";
+ static const uint32_t HTTPHeaderLen = sizeof(HTTPHeader) - 1;
+ static const char HTTP2Header[] = "HTTP/2";
+ static const uint32_t HTTP2HeaderLen = sizeof(HTTP2Header) - 1;
+ static const char HTTP3Header[] = "HTTP/3";
+ static const uint32_t HTTP3HeaderLen = sizeof(HTTP3Header) - 1;
+ // ShoutCast ICY is treated as HTTP/1.0
+ static const char ICYHeader[] = "ICY ";
+ static const uint32_t ICYHeaderLen = sizeof(ICYHeader) - 1;
+
+ if (aAllowPartialMatch && (len < HTTPHeaderLen)) {
+ return (nsCRT::strncasecmp(buf, HTTPHeader, len) == 0) ? buf : nullptr;
+ }
+
+ // mLineBuf can contain partial match from previous search
+ if (!mLineBuf.IsEmpty()) {
+ MOZ_ASSERT(mLineBuf.Length() < HTTPHeaderLen);
+ int32_t checkChars =
+ std::min<uint32_t>(len, HTTPHeaderLen - mLineBuf.Length());
+ if (nsCRT::strncasecmp(buf, HTTPHeader + mLineBuf.Length(), checkChars) ==
+ 0) {
+ mLineBuf.Append(buf, checkChars);
+ if (mLineBuf.Length() == HTTPHeaderLen) {
+ // We've found whole HTTPHeader sequence. Return pointer at the
+ // end of matched sequence since it is stored in mLineBuf.
+ return (buf + checkChars);
+ }
+ // Response matches pattern but is still incomplete.
+ return nullptr;
+ }
+ // Previous partial match together with new data doesn't match the
+ // pattern. Start the search again.
+ mLineBuf.Truncate();
+ }
+
+ bool firstByte = true;
+ while (len > 0) {
+ if (nsCRT::strncasecmp(buf, HTTPHeader,
+ std::min<uint32_t>(len, HTTPHeaderLen)) == 0) {
+ if (len < HTTPHeaderLen) {
+ // partial HTTPHeader sequence found
+ // save partial match to mLineBuf
+ mLineBuf.Assign(buf, len);
+ return nullptr;
+ }
+
+ // whole HTTPHeader sequence found
+ return buf;
+ }
+
+ // At least "SmarterTools/2.0.3974.16813" generates nonsensical
+ // HTTP/2.0 responses to our HTTP/1 requests. Treat the minimal case of
+ // it as HTTP/1.1 to be compatible with old versions of ourselves and
+ // other browsers
+
+ if (firstByte && !mInvalidResponseBytesRead && len >= HTTP2HeaderLen &&
+ (nsCRT::strncasecmp(buf, HTTP2Header, HTTP2HeaderLen) == 0)) {
+ LOG(("nsHttpTransaction:: Identified HTTP/2.0 treating as 1.x\n"));
+ return buf;
+ }
+
+ // HTTP/3.0 responses to our HTTP/1 requests. Treat the minimal case of
+ // it as HTTP/1.1 to be compatible with old versions of ourselves and
+ // other browsers
+
+ if (firstByte && !mInvalidResponseBytesRead && len >= HTTP3HeaderLen &&
+ (nsCRT::strncasecmp(buf, HTTP3Header, HTTP3HeaderLen) == 0)) {
+ LOG(("nsHttpTransaction:: Identified HTTP/3.0 treating as 1.x\n"));
+ return buf;
+ }
+
+ // Treat ICY (AOL/Nullsoft ShoutCast) non-standard header in same fashion
+ // as HTTP/2.0 is treated above. This will allow "ICY " to be interpretted
+ // as HTTP/1.0 in nsHttpResponseHead::ParseVersion
+
+ if (firstByte && !mInvalidResponseBytesRead && len >= ICYHeaderLen &&
+ (nsCRT::strncasecmp(buf, ICYHeader, ICYHeaderLen) == 0)) {
+ LOG(("nsHttpTransaction:: Identified ICY treating as HTTP/1.0\n"));
+ return buf;
+ }
+
+ if (!nsCRT::IsAsciiSpace(*buf)) firstByte = false;
+ buf++;
+ len--;
+ }
+ return nullptr;
+}
+
+nsresult nsHttpTransaction::ParseLine(nsACString& line) {
+ LOG1(("nsHttpTransaction::ParseLine [%s]\n", PromiseFlatCString(line).get()));
+ nsresult rv = NS_OK;
+
+ if (!mHaveStatusLine) {
+ rv = mResponseHead->ParseStatusLine(line);
+ if (NS_SUCCEEDED(rv)) {
+ mHaveStatusLine = true;
+ }
+ // XXX this should probably never happen
+ if (mResponseHead->Version() == HttpVersion::v0_9) mHaveAllHeaders = true;
+ } else {
+ rv = mResponseHead->ParseHeaderLine(line);
+ }
+ return rv;
+}
+
+nsresult nsHttpTransaction::ParseLineSegment(char* segment, uint32_t len) {
+ MOZ_ASSERT(!mHaveAllHeaders, "already have all headers");
+
+ if (!mLineBuf.IsEmpty() && mLineBuf.Last() == '\n') {
+ // trim off the new line char, and if this segment is
+ // not a continuation of the previous or if we haven't
+ // parsed the status line yet, then parse the contents
+ // of mLineBuf.
+ mLineBuf.Truncate(mLineBuf.Length() - 1);
+ if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) {
+ nsresult rv = ParseLine(mLineBuf);
+ mLineBuf.Truncate();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ // append segment to mLineBuf...
+ mLineBuf.Append(segment, len);
+
+ // a line buf with only a new line char signifies the end of headers.
+ if (mLineBuf.First() == '\n') {
+ mLineBuf.Truncate();
+ // discard this response if it is a 100 continue or other 1xx status.
+ uint16_t status = mResponseHead->Status();
+ if (status == 103) {
+ nsCString linkHeader;
+ nsresult rv = mResponseHead->GetHeader(nsHttp::Link, linkHeader);
+
+ nsCString referrerPolicy;
+ Unused << mResponseHead->GetHeader(nsHttp::Referrer_Policy,
+ referrerPolicy);
+
+ if (NS_SUCCEEDED(rv) && !linkHeader.IsEmpty()) {
+ nsCString cspHeader;
+ Unused << mResponseHead->GetHeader(nsHttp::Content_Security_Policy,
+ cspHeader);
+
+ nsCOMPtr<nsIEarlyHintObserver> earlyHint;
+ {
+ MutexAutoLock lock(mLock);
+ earlyHint = mEarlyHintObserver;
+ }
+ if (earlyHint) {
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(
+ NS_NewRunnableFunction(
+ "nsIEarlyHintObserver->EarlyHint",
+ [obs{std::move(earlyHint)}, header{std::move(linkHeader)},
+ referrerPolicy{std::move(referrerPolicy)},
+ cspHeader{std::move(cspHeader)}]() {
+ obs->EarlyHint(header, referrerPolicy, cspHeader);
+ }),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+ }
+ if ((status != 101) && (status / 100 == 1)) {
+ LOG(("ignoring 1xx response except 101 and 103\n"));
+ mHaveStatusLine = false;
+ mHttpResponseMatched = false;
+ mConnection->SetLastTransactionExpectedNoContent(true);
+ mResponseHead->Reset();
+ return NS_OK;
+ }
+ if (!mConnection->IsProxyConnectInProgress()) {
+ MutexAutoLock lock(mLock);
+ mEarlyHintObserver = nullptr;
+ }
+ mHaveAllHeaders = true;
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpTransaction::ParseHead(char* buf, uint32_t count,
+ uint32_t* countRead) {
+ nsresult rv;
+ uint32_t len;
+ char* eol;
+
+ LOG(("nsHttpTransaction::ParseHead [count=%u]\n", count));
+
+ *countRead = 0;
+
+ MOZ_ASSERT(!mHaveAllHeaders, "oops");
+
+ // allocate the response head object if necessary
+ if (!mResponseHead) {
+ mResponseHead = new nsHttpResponseHead();
+ if (!mResponseHead) return NS_ERROR_OUT_OF_MEMORY;
+
+ // report that we have a least some of the response
+ if (!mReportedStart) {
+ mReportedStart = true;
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START, PR_Now(), 0, ""_ns);
+ }
+ }
+
+ if (!mHttpResponseMatched) {
+ // Normally we insist on seeing HTTP/1.x in the first few bytes,
+ // but if we are on a persistent connection and the previous transaction
+ // was not supposed to have any content then we need to be prepared
+ // to skip over a response body that the server may have sent even
+ // though it wasn't allowed.
+ if (!mConnection || !mConnection->LastTransactionExpectedNoContent()) {
+ // tolerate only minor junk before the status line
+ mHttpResponseMatched = true;
+ char* p = LocateHttpStart(buf, std::min<uint32_t>(count, 11), true);
+ if (!p) {
+ // Treat any 0.9 style response of a put as a failure.
+ if (mRequestHead->IsPut()) return NS_ERROR_ABORT;
+
+ if (NS_FAILED(mResponseHead->ParseStatusLine(""_ns))) {
+ return NS_ERROR_FAILURE;
+ }
+ mHaveStatusLine = true;
+ mHaveAllHeaders = true;
+ return NS_OK;
+ }
+ if (p > buf) {
+ // skip over the junk
+ mInvalidResponseBytesRead += p - buf;
+ *countRead = p - buf;
+ buf = p;
+ }
+ } else {
+ char* p = LocateHttpStart(buf, count, false);
+ if (p) {
+ mInvalidResponseBytesRead += p - buf;
+ *countRead = p - buf;
+ buf = p;
+ mHttpResponseMatched = true;
+ } else {
+ mInvalidResponseBytesRead += count;
+ *countRead = count;
+ if (mInvalidResponseBytesRead > MAX_INVALID_RESPONSE_BODY_SIZE) {
+ LOG(
+ ("nsHttpTransaction::ParseHead() "
+ "Cannot find Response Header\n"));
+ // cannot go back and call this 0.9 anymore as we
+ // have thrown away a lot of the leading junk
+ return NS_ERROR_ABORT;
+ }
+ return NS_OK;
+ }
+ }
+ }
+ // otherwise we can assume that we don't have a HTTP/0.9 response.
+
+ MOZ_ASSERT(mHttpResponseMatched);
+ while ((eol = static_cast<char*>(memchr(buf, '\n', count - *countRead))) !=
+ nullptr) {
+ // found line in range [buf:eol]
+ len = eol - buf + 1;
+
+ *countRead += len;
+
+ // actually, the line is in the range [buf:eol-1]
+ if ((eol > buf) && (*(eol - 1) == '\r')) len--;
+
+ buf[len - 1] = '\n';
+ rv = ParseLineSegment(buf, len);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mHaveAllHeaders) return NS_OK;
+
+ // skip over line
+ buf = eol + 1;
+
+ if (!mHttpResponseMatched) {
+ // a 100 class response has caused us to throw away that set of
+ // response headers and look for the next response
+ return NS_ERROR_NET_INTERRUPT;
+ }
+ }
+
+ // do something about a partial header line
+ if (!mHaveAllHeaders && (len = count - *countRead)) {
+ *countRead = count;
+ // ignore a trailing carriage return, and don't bother calling
+ // ParseLineSegment if buf only contains a carriage return.
+ if ((buf[len - 1] == '\r') && (--len == 0)) return NS_OK;
+ rv = ParseLineSegment(buf, len);
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+bool nsHttpTransaction::HandleWebTransportResponse(uint16_t aStatus) {
+ MOZ_ASSERT(mIsForWebTransport);
+ if (!(aStatus >= 200 && aStatus < 300)) {
+ return false;
+ }
+
+ RefPtr<Http3WebTransportSession> wtSession =
+ mConnection->GetWebTransportSession(this);
+ if (!wtSession) {
+ return false;
+ }
+
+ nsCOMPtr<WebTransportSessionEventListener> webTransportListener;
+ {
+ MutexAutoLock lock(mLock);
+ webTransportListener = mWebTransportSessionEventListener;
+ mWebTransportSessionEventListener = nullptr;
+ }
+ if (webTransportListener) {
+ webTransportListener->OnSessionReadyInternal(wtSession);
+ wtSession->SetWebTransportSessionEventListener(webTransportListener);
+ }
+
+ return true;
+}
+
+nsresult nsHttpTransaction::HandleContentStart() {
+ LOG(("nsHttpTransaction::HandleContentStart [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mResponseHead) {
+ if (mEarlyDataDisposition == EARLY_ACCEPTED) {
+ if (mResponseHead->Status() == 425) {
+ // We will report this state when the final responce arrives.
+ mEarlyDataDisposition = EARLY_425;
+ } else {
+ Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,
+ "accepted"_ns);
+ }
+ } else if (mEarlyDataDisposition == EARLY_SENT) {
+ Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,
+ "sent"_ns);
+ } else if (mEarlyDataDisposition == EARLY_425) {
+ Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,
+ "received 425"_ns);
+ mEarlyDataDisposition = EARLY_NONE;
+ } // no header on NONE case
+
+ if (LOG3_ENABLED()) {
+ LOG3(("http response [\n"));
+ nsAutoCString headers;
+ mResponseHead->Flatten(headers, false);
+ headers.AppendLiteral(" OriginalHeaders");
+ headers.AppendLiteral("\r\n");
+ mResponseHead->FlattenNetworkOriginalHeaders(headers);
+ LogHeaders(headers.get());
+ LOG3(("]\n"));
+ }
+
+ CheckForStickyAuthScheme();
+
+ // Save http version, mResponseHead isn't available anymore after
+ // TakeResponseHead() is called
+ mHttpVersion = mResponseHead->Version();
+ mHttpResponseCode = mResponseHead->Status();
+
+ // notify the connection, give it a chance to cause a reset.
+ bool reset = false;
+ nsresult rv = mConnection->OnHeadersAvailable(this, mRequestHead,
+ mResponseHead, &reset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // looks like we should ignore this response, resetting...
+ if (reset) {
+ LOG(("resetting transaction's response head\n"));
+ mHaveAllHeaders = false;
+ mHaveStatusLine = false;
+ mReceivedData = false;
+ mSentData = false;
+ mHttpResponseMatched = false;
+ mResponseHead->Reset();
+ // wait to be called again...
+ return NS_OK;
+ }
+
+ bool responseChecked = false;
+ if (mIsForWebTransport) {
+ responseChecked = HandleWebTransportResponse(mResponseHead->Status());
+ LOG(("HandleWebTransportResponse res=%d", responseChecked));
+ if (responseChecked) {
+ mNoContent = true;
+ mPreserveStream = true;
+ }
+ }
+
+ if (!responseChecked) {
+ // check if this is a no-content response
+ switch (mResponseHead->Status()) {
+ case 101:
+ mPreserveStream = true;
+ [[fallthrough]]; // to other no content cases:
+ case 204:
+ case 205:
+ case 304:
+ mNoContent = true;
+ LOG(("this response should not contain a body.\n"));
+ break;
+ case 408:
+ LOG(("408 Server Timeouts"));
+
+ if (mConnection->Version() >= HttpVersion::v2_0) {
+ mForceRestart = true;
+ return NS_ERROR_NET_RESET;
+ }
+
+ // If this error could be due to a persistent connection
+ // reuse then we pass an error code of NS_ERROR_NET_RESET
+ // to trigger the transaction 'restart' mechanism. We
+ // tell it to reset its response headers so that it will
+ // be ready to receive the new response.
+ LOG(("408 Server Timeouts now=%d lastWrite=%d", PR_IntervalNow(),
+ mConnection->LastWriteTime()));
+ if ((PR_IntervalNow() - mConnection->LastWriteTime()) >=
+ PR_MillisecondsToInterval(1000)) {
+ mForceRestart = true;
+ return NS_ERROR_NET_RESET;
+ }
+ break;
+ case 421:
+ LOG(("Misdirected Request.\n"));
+ gHttpHandler->ClearHostMapping(mConnInfo);
+
+ m421Received = true;
+ mCaps |= NS_HTTP_REFRESH_DNS;
+
+ // retry on a new connection - just in case
+ // See bug 1609410, we can't restart the transaction when
+ // NS_HTTP_STICKY_CONNECTION is set. In the case that a connection
+ // already passed NTLM authentication, restarting the transaction will
+ // cause the connection to be closed.
+ if (!mRestartCount && !(mCaps & NS_HTTP_STICKY_CONNECTION)) {
+ mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
+ mForceRestart = true; // force restart has built in loop protection
+ return NS_ERROR_NET_RESET;
+ }
+ break;
+ case 425:
+ LOG(("Too Early."));
+ if ((mEarlyDataDisposition == EARLY_425) && !mDoNotTryEarlyData) {
+ mDoNotTryEarlyData = true;
+ mForceRestart = true; // force restart has built in loop protection
+ if (mConnection->Version() >= HttpVersion::v2_0) {
+ mReuseOnRestart = true;
+ }
+ return NS_ERROR_NET_RESET;
+ }
+ break;
+ }
+ }
+
+ // Remember whether HTTP3 is supported
+ mSupportsHTTP3 = nsHttpHandler::IsHttp3SupportedByServer(mResponseHead);
+
+ CollectTelemetryForUploads();
+
+ // Report telemetry
+ if (mSupportsHTTP3) {
+ Accumulate(Telemetry::TRANSACTION_WAIT_TIME_HTTP2_SUP_HTTP3,
+ mPendingDurationTime.ToMilliseconds());
+ }
+
+ // If we're only connecting then we're going to be upgrading this
+ // connection since we were successful. Any data from now on belongs to
+ // the upgrade handler. If we're not successful the content body doesn't
+ // matter. Proxy http errors are treated as network errors. This
+ // connection won't be reused since it's marked sticky and no
+ // keep-alive.
+ if (mCaps & NS_HTTP_CONNECT_ONLY) {
+ MOZ_ASSERT(!(mCaps & NS_HTTP_ALLOW_KEEPALIVE) &&
+ (mCaps & NS_HTTP_STICKY_CONNECTION),
+ "connection should be sticky and no keep-alive");
+ // The transaction will expect the server to close the socket if
+ // there's no content length instead of doing the upgrade.
+ mNoContent = true;
+ }
+
+ // preserve connection for tunnel setup - h2 websocket upgrade only
+ if (mIsHttp2Websocket && mResponseHead->Status() == 200) {
+ LOG(("nsHttpTransaction::HandleContentStart websocket upgrade resp 200"));
+ mNoContent = true;
+ }
+
+ if (mResponseHead->Status() == 200 &&
+ mConnection->IsProxyConnectInProgress()) {
+ // successful CONNECTs do not have response bodies
+ mNoContent = true;
+ }
+ mConnection->SetLastTransactionExpectedNoContent(mNoContent);
+
+ if (mNoContent) {
+ mContentLength = 0;
+ } else {
+ // grab the content-length from the response headers
+ mContentLength = mResponseHead->ContentLength();
+
+ // handle chunked encoding here, so we'll know immediately when
+ // we're done with the socket. please note that _all_ other
+ // decoding is done when the channel receives the content data
+ // so as not to block the socket transport thread too much.
+ if (mResponseHead->Version() >= HttpVersion::v1_0 &&
+ mResponseHead->HasHeaderValue(nsHttp::Transfer_Encoding, "chunked")) {
+ // we only support the "chunked" transfer encoding right now.
+ mChunkedDecoder = new nsHttpChunkedDecoder();
+ LOG(("nsHttpTransaction %p chunked decoder created\n", this));
+ // Ignore server specified Content-Length.
+ if (mContentLength != int64_t(-1)) {
+ LOG(("nsHttpTransaction %p chunked with C-L ignores C-L\n", this));
+ mContentLength = -1;
+ if (mConnection) {
+ mConnection->DontReuse();
+ }
+ }
+ } else if (mContentLength == int64_t(-1)) {
+ LOG(("waiting for the server to close the connection.\n"));
+ }
+ }
+ }
+
+ mDidContentStart = true;
+ return NS_OK;
+}
+
+// called on the socket thread
+nsresult nsHttpTransaction::HandleContent(char* buf, uint32_t count,
+ uint32_t* contentRead,
+ uint32_t* contentRemaining) {
+ nsresult rv;
+
+ LOG(("nsHttpTransaction::HandleContent [this=%p count=%u]\n", this, count));
+
+ *contentRead = 0;
+ *contentRemaining = 0;
+
+ MOZ_ASSERT(mConnection);
+
+ if (!mDidContentStart) {
+ rv = HandleContentStart();
+ if (NS_FAILED(rv)) return rv;
+ // Do not write content to the pipe if we haven't started streaming yet
+ if (!mDidContentStart) return NS_OK;
+ }
+
+ if (mChunkedDecoder) {
+ // give the buf over to the chunked decoder so it can reformat the
+ // data and tell us how much is really there.
+ rv = mChunkedDecoder->HandleChunkedContent(buf, count, contentRead,
+ contentRemaining);
+ if (NS_FAILED(rv)) return rv;
+ } else if (mContentLength >= int64_t(0)) {
+ // HTTP/1.0 servers have been known to send erroneous Content-Length
+ // headers. So, unless the connection is persistent, we must make
+ // allowances for a possibly invalid Content-Length header. Thus, if
+ // NOT persistent, we simply accept everything in |buf|.
+ if (mConnection->IsPersistent() || mPreserveStream ||
+ mHttpVersion >= HttpVersion::v1_1) {
+ int64_t remaining = mContentLength - mContentRead;
+ *contentRead = uint32_t(std::min<int64_t>(count, remaining));
+ *contentRemaining = count - *contentRead;
+ } else {
+ *contentRead = count;
+ // mContentLength might need to be increased...
+ int64_t position = mContentRead + int64_t(count);
+ if (position > mContentLength) {
+ mContentLength = position;
+ // mResponseHead->SetContentLength(mContentLength);
+ }
+ }
+ } else {
+ // when we are just waiting for the server to close the connection...
+ // (no explicit content-length given)
+ *contentRead = count;
+ }
+
+ if (*contentRead) {
+ // update count of content bytes read and report progress...
+ mContentRead += *contentRead;
+ }
+
+ LOG1(
+ ("nsHttpTransaction::HandleContent [this=%p count=%u read=%u "
+ "mContentRead=%" PRId64 " mContentLength=%" PRId64 "]\n",
+ this, count, *contentRead, mContentRead, mContentLength));
+
+ // check for end-of-file
+ if ((mContentRead == mContentLength) ||
+ (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) {
+ {
+ MutexAutoLock lock(mLock);
+ if (mChunkedDecoder) {
+ mForTakeResponseTrailers = mChunkedDecoder->TakeTrailers();
+ }
+
+ // the transaction is done with a complete response.
+ mTransactionDone = true;
+ mResponseIsComplete = true;
+ }
+ ReleaseBlockingTransaction();
+
+ if (TimingEnabled()) {
+ SetResponseEnd(TimeStamp::Now());
+ }
+
+ // report the entire response has arrived
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, PR_Now(),
+ static_cast<uint64_t>(mContentRead), ""_ns);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpTransaction::ProcessData(char* buf, uint32_t count,
+ uint32_t* countRead) {
+ nsresult rv;
+
+ LOG1(("nsHttpTransaction::ProcessData [this=%p count=%u]\n", this, count));
+
+ *countRead = 0;
+
+ // we may not have read all of the headers yet...
+ if (!mHaveAllHeaders) {
+ uint32_t bytesConsumed = 0;
+
+ do {
+ uint32_t localBytesConsumed = 0;
+ char* localBuf = buf + bytesConsumed;
+ uint32_t localCount = count - bytesConsumed;
+
+ rv = ParseHead(localBuf, localCount, &localBytesConsumed);
+ if (NS_FAILED(rv) && rv != NS_ERROR_NET_INTERRUPT) return rv;
+ bytesConsumed += localBytesConsumed;
+ } while (rv == NS_ERROR_NET_INTERRUPT);
+
+ mCurrentHttpResponseHeaderSize += bytesConsumed;
+ if (mCurrentHttpResponseHeaderSize >
+ gHttpHandler->MaxHttpResponseHeaderSize()) {
+ LOG(("nsHttpTransaction %p The response header exceeds the limit.\n",
+ this));
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+ count -= bytesConsumed;
+
+ // if buf has some content in it, shift bytes to top of buf.
+ if (count && bytesConsumed) memmove(buf, buf + bytesConsumed, count);
+
+ if (mResponseHead && mHaveAllHeaders) {
+ auto reportResponseHeader = [&](uint32_t aSubType) {
+ nsAutoCString completeResponseHeaders;
+ mResponseHead->Flatten(completeResponseHeaders, false);
+ completeResponseHeaders.AppendLiteral("\r\n");
+ gHttpHandler->ObserveHttpActivityWithArgs(
+ HttpActivityArgs(mChannelId),
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, aSubType, PR_Now(), 0,
+ completeResponseHeaders);
+ };
+
+ if (mConnection->IsProxyConnectInProgress()) {
+ reportResponseHeader(NS_HTTP_ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER);
+ } else if (!mReportedResponseHeader) {
+ mReportedResponseHeader = true;
+ reportResponseHeader(NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER);
+ }
+ }
+ }
+
+ // even though count may be 0, we still want to call HandleContent
+ // so it can complete the transaction if this is a "no-content" response.
+ if (mHaveAllHeaders) {
+ uint32_t countRemaining = 0;
+ //
+ // buf layout:
+ //
+ // +--------------------------------------+----------------+-----+
+ // | countRead | countRemaining | |
+ // +--------------------------------------+----------------+-----+
+ //
+ // count : bytes read from the socket
+ // countRead : bytes corresponding to this transaction
+ // countRemaining : bytes corresponding to next transaction on conn
+ //
+ // NOTE:
+ // count > countRead + countRemaining <==> chunked transfer encoding
+ //
+ rv = HandleContent(buf, count, countRead, &countRemaining);
+ if (NS_FAILED(rv)) return rv;
+ // we may have read more than our share, in which case we must give
+ // the excess bytes back to the connection
+ if (mResponseIsComplete && countRemaining &&
+ (mConnection->Version() != HttpVersion::v3_0)) {
+ MOZ_ASSERT(mConnection);
+ rv = mConnection->PushBack(buf + *countRead, countRemaining);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!mContentDecodingCheck && mResponseHead) {
+ mContentDecoding = mResponseHead->HasHeader(nsHttp::Content_Encoding);
+ mContentDecodingCheck = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+// Called when the transaction marked for blocking is associated with a
+// connection (i.e. added to a new h1 conn, an idle http connection, etc..) It
+// is safe to call this multiple times with it only having an effect once.
+void nsHttpTransaction::DispatchedAsBlocking() {
+ if (mDispatchedAsBlocking) return;
+
+ LOG(("nsHttpTransaction %p dispatched as blocking\n", this));
+
+ if (!mRequestContext) return;
+
+ LOG(
+ ("nsHttpTransaction adding blocking transaction %p from "
+ "request context %p\n",
+ this, mRequestContext.get()));
+
+ mRequestContext->AddBlockingTransaction();
+ mDispatchedAsBlocking = true;
+}
+
+void nsHttpTransaction::RemoveDispatchedAsBlocking() {
+ if (!mRequestContext || !mDispatchedAsBlocking) {
+ LOG(("nsHttpTransaction::RemoveDispatchedAsBlocking this=%p not blocking",
+ this));
+ return;
+ }
+
+ uint32_t blockers = 0;
+ nsresult rv = mRequestContext->RemoveBlockingTransaction(&blockers);
+
+ LOG(
+ ("nsHttpTransaction removing blocking transaction %p from "
+ "request context %p. %d blockers remain.\n",
+ this, mRequestContext.get(), blockers));
+
+ if (NS_SUCCEEDED(rv) && !blockers) {
+ LOG(
+ ("nsHttpTransaction %p triggering release of blocked channels "
+ " with request context=%p\n",
+ this, mRequestContext.get()));
+ rv = gHttpHandler->ConnMgr()->ProcessPendingQ();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpTransaction::RemoveDispatchedAsBlocking\n"
+ " failed to process pending queue\n"));
+ }
+ }
+
+ mDispatchedAsBlocking = false;
+}
+
+void nsHttpTransaction::ReleaseBlockingTransaction() {
+ RemoveDispatchedAsBlocking();
+ LOG(
+ ("nsHttpTransaction %p request context set to null "
+ "in ReleaseBlockingTransaction() - was %p\n",
+ this, mRequestContext.get()));
+ mRequestContext = nullptr;
+}
+
+void nsHttpTransaction::DisableSpdy() {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ if (mConnInfo) {
+ // This is our clone of the connection info, not the persistent one that
+ // is owned by the connection manager, so we're safe to change this here
+ mConnInfo->SetNoSpdy(true);
+ }
+}
+
+void nsHttpTransaction::DisableHttp2ForProxy() {
+ mCaps |= NS_HTTP_DISALLOW_HTTP2_PROXY;
+}
+
+void nsHttpTransaction::DisableHttp3(bool aAllowRetryHTTPSRR) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // mOrigConnInfo is an indicator that HTTPS RR is used, so don't mess up the
+ // connection info.
+ // When HTTPS RR is used, PrepareConnInfoForRetry() could select other h3
+ // record to connect.
+ if (mOrigConnInfo) {
+ LOG(
+ ("nsHttpTransaction::DisableHttp3 this=%p mOrigConnInfo=%s "
+ "aAllowRetryHTTPSRR=%d",
+ this, mOrigConnInfo->HashKey().get(), aAllowRetryHTTPSRR));
+ if (!aAllowRetryHTTPSRR) {
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+ return;
+ }
+
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+
+ MOZ_ASSERT(mConnInfo);
+ if (mConnInfo) {
+ // After CloneAsDirectRoute(), http3 will be disabled.
+ RefPtr<nsHttpConnectionInfo> connInfo;
+ mConnInfo->CloneAsDirectRoute(getter_AddRefs(connInfo));
+ RemoveAlternateServiceUsedHeader(mRequestHead);
+ MOZ_ASSERT(!connInfo->IsHttp3());
+ mConnInfo.swap(connInfo);
+ }
+}
+
+void nsHttpTransaction::CheckForStickyAuthScheme() {
+ LOG(("nsHttpTransaction::CheckForStickyAuthScheme this=%p", this));
+
+ MOZ_ASSERT(mHaveAllHeaders);
+ MOZ_ASSERT(mResponseHead);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ CheckForStickyAuthSchemeAt(nsHttp::WWW_Authenticate);
+ CheckForStickyAuthSchemeAt(nsHttp::Proxy_Authenticate);
+}
+
+void nsHttpTransaction::CheckForStickyAuthSchemeAt(nsHttpAtom const& header) {
+ if (mCaps & NS_HTTP_STICKY_CONNECTION) {
+ LOG((" already sticky"));
+ return;
+ }
+
+ nsAutoCString auth;
+ if (NS_FAILED(mResponseHead->GetHeader(header, auth))) {
+ return;
+ }
+
+ if (IsStickyAuthSchemeAt(auth)) {
+ LOG((" connection made sticky"));
+ // This is enough to make this transaction keep it's current connection,
+ // prevents the connection from being released back to the pool.
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ }
+}
+
+bool nsHttpTransaction::IsStickyAuthSchemeAt(nsACString const& auth) {
+ Tokenizer p(auth);
+ nsAutoCString schema;
+ while (p.ReadWord(schema)) {
+ ToLowerCase(schema);
+
+ // using a new instance because of thread safety of auth modules refcnt
+ nsCOMPtr<nsIHttpAuthenticator> authenticator;
+ if (schema.EqualsLiteral("negotiate")) {
+#ifdef MOZ_AUTH_EXTENSION
+ authenticator = new nsHttpNegotiateAuth();
+#endif
+ } else if (schema.EqualsLiteral("basic")) {
+ authenticator = new nsHttpBasicAuth();
+ } else if (schema.EqualsLiteral("digest")) {
+ authenticator = new nsHttpDigestAuth();
+ } else if (schema.EqualsLiteral("ntlm")) {
+ authenticator = new nsHttpNTLMAuth();
+ } else if (schema.EqualsLiteral("mock_auth") &&
+ PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
+ authenticator = new MockHttpAuth();
+ }
+ if (authenticator) {
+ uint32_t flags;
+ nsresult rv = authenticator->GetAuthFlags(&flags);
+ if (NS_SUCCEEDED(rv) &&
+ (flags & nsIHttpAuthenticator::CONNECTION_BASED)) {
+ return true;
+ }
+ }
+
+ // schemes are separated with LFs, nsHttpHeaderArray::MergeHeader
+ p.SkipUntil(Tokenizer::Token::NewLine());
+ p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE);
+ }
+
+ return false;
+}
+
+TimingStruct nsHttpTransaction::Timings() {
+ mozilla::MutexAutoLock lock(mLock);
+ TimingStruct timings = mTimings;
+ return timings;
+}
+
+void nsHttpTransaction::BootstrapTimings(TimingStruct times) {
+ mozilla::MutexAutoLock lock(mLock);
+ mTimings = times;
+}
+
+void nsHttpTransaction::SetDomainLookupStart(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.domainLookupStart.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.domainLookupStart = timeStamp;
+}
+
+void nsHttpTransaction::SetDomainLookupEnd(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.domainLookupEnd.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.domainLookupEnd = timeStamp;
+}
+
+void nsHttpTransaction::SetConnectStart(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.connectStart.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.connectStart = timeStamp;
+}
+
+void nsHttpTransaction::SetConnectEnd(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.connectEnd.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.connectEnd = timeStamp;
+}
+
+void nsHttpTransaction::SetRequestStart(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.requestStart.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.requestStart = timeStamp;
+}
+
+void nsHttpTransaction::SetResponseStart(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.responseStart.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.responseStart = timeStamp;
+}
+
+void nsHttpTransaction::SetResponseEnd(mozilla::TimeStamp timeStamp,
+ bool onlyIfNull) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.responseEnd.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.responseEnd = timeStamp;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetDomainLookupStart() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.domainLookupStart;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetDomainLookupEnd() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.domainLookupEnd;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetConnectStart() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.connectStart;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetTcpConnectEnd() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.tcpConnectEnd;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetSecureConnectionStart() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.secureConnectionStart;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetConnectEnd() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.connectEnd;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetRequestStart() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.requestStart;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetResponseStart() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.responseStart;
+}
+
+mozilla::TimeStamp nsHttpTransaction::GetResponseEnd() {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.responseEnd;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction deletion event
+//-----------------------------------------------------------------------------
+
+class DeleteHttpTransaction : public Runnable {
+ public:
+ explicit DeleteHttpTransaction(nsHttpTransaction* trans)
+ : Runnable("net::DeleteHttpTransaction"), mTrans(trans) {}
+
+ NS_IMETHOD Run() override {
+ delete mTrans;
+ return NS_OK;
+ }
+
+ private:
+ nsHttpTransaction* mTrans;
+};
+
+void nsHttpTransaction::DeleteSelfOnConsumerThread() {
+ LOG(("nsHttpTransaction::DeleteSelfOnConsumerThread [this=%p]\n", this));
+
+ bool val;
+ if (!mConsumerTarget ||
+ (NS_SUCCEEDED(mConsumerTarget->IsOnCurrentThread(&val)) && val)) {
+ delete this;
+ } else {
+ LOG(("proxying delete to consumer thread...\n"));
+ nsCOMPtr<nsIRunnable> event = new DeleteHttpTransaction(this);
+ if (NS_FAILED(mConsumerTarget->Dispatch(event, NS_DISPATCH_NORMAL))) {
+ NS_WARNING("failed to dispatch nsHttpDeleteTransaction event");
+ }
+ }
+}
+
+bool nsHttpTransaction::TryToRunPacedRequest() {
+ if (mSubmittedRatePacing) return mPassedRatePacing;
+
+ mSubmittedRatePacing = true;
+ mSynchronousRatePaceRequest = true;
+ Unused << gHttpHandler->SubmitPacedRequest(
+ this, getter_AddRefs(mTokenBucketCancel));
+ mSynchronousRatePaceRequest = false;
+ return mPassedRatePacing;
+}
+
+void nsHttpTransaction::OnTokenBucketAdmitted() {
+ mPassedRatePacing = true;
+ mTokenBucketCancel = nullptr;
+
+ if (!mSynchronousRatePaceRequest) {
+ nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpTransaction::OnTokenBucketAdmitted\n"
+ " failed to process pending queue\n"));
+ }
+ }
+}
+
+void nsHttpTransaction::CancelPacing(nsresult reason) {
+ if (mTokenBucketCancel) {
+ mTokenBucketCancel->Cancel(reason);
+ mTokenBucketCancel = nullptr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsHttpTransaction)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsHttpTransaction::Release() {
+ nsrefcnt count;
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "nsHttpTransaction");
+ if (0 == count) {
+ mRefCnt = 1; /* stablize */
+ // it is essential that the transaction be destroyed on the consumer
+ // thread (we could be holding the last reference to our consumer).
+ DeleteSelfOnConsumerThread();
+ return 0;
+ }
+ return count;
+}
+
+NS_IMPL_QUERY_INTERFACE(nsHttpTransaction, nsIInputStreamCallback,
+ nsIOutputStreamCallback, nsITimerCallback, nsINamed)
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction::nsIInputStreamCallback
+//-----------------------------------------------------------------------------
+
+// called on the socket thread
+NS_IMETHODIMP
+nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream* out) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mConnection) {
+ mConnection->TransactionHasDataToWrite(this);
+ nsresult rv = mConnection->ResumeSend();
+ if (NS_FAILED(rv)) NS_ERROR("ResumeSend failed");
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction::nsIOutputStreamCallback
+//-----------------------------------------------------------------------------
+
+// called on the socket thread
+NS_IMETHODIMP
+nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream* out) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mWaitingOnPipeOut = false;
+ if (mConnection) {
+ mConnection->TransactionHasDataToRecv(this);
+ nsresult rv = mConnection->ResumeRecv();
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ NS_ERROR("ResumeRecv failed");
+ }
+ }
+ return NS_OK;
+}
+
+void nsHttpTransaction::GetNetworkAddresses(
+ NetAddr& self, NetAddr& peer, bool& aResolvedByTRR,
+ nsIRequest::TRRMode& aEffectiveTRRMode, TRRSkippedReason& aSkipReason,
+ bool& aEchConfigUsed) {
+ MutexAutoLock lock(mLock);
+ self = mSelfAddr;
+ peer = mPeerAddr;
+ aResolvedByTRR = mResolvedByTRR;
+ aEffectiveTRRMode = mEffectiveTRRMode;
+ aSkipReason = mTRRSkipReason;
+ aEchConfigUsed = mEchConfigUsed;
+}
+
+bool nsHttpTransaction::Do0RTT() {
+ LOG(("nsHttpTransaction::Do0RTT"));
+ mEarlyDataWasAvailable = true;
+ if (mRequestHead->IsSafeMethod() && !mDoNotTryEarlyData &&
+ (!mConnection || !mConnection->IsProxyConnectInProgress())) {
+ m0RTTInProgress = true;
+ }
+ return m0RTTInProgress;
+}
+
+nsresult nsHttpTransaction::Finish0RTT(bool aRestart,
+ bool aAlpnChanged /* ignored */) {
+ LOG(("nsHttpTransaction::Finish0RTT %p %d %d\n", this, aRestart,
+ aAlpnChanged));
+ MOZ_ASSERT(m0RTTInProgress);
+ m0RTTInProgress = false;
+
+ MaybeCancelFallbackTimer();
+
+ if (!aRestart && (mEarlyDataDisposition == EARLY_SENT)) {
+ // note that if this is invoked by a 3 param version of finish0rtt this
+ // disposition might be reverted
+ mEarlyDataDisposition = EARLY_ACCEPTED;
+ }
+ if (aRestart) {
+ // Not to use 0RTT when this transaction is restarted next time.
+ mDoNotTryEarlyData = true;
+
+ // Reset request headers to be sent again.
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
+ if (seekable) {
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ } else if (!mConnected) {
+ // this is code that was skipped in ::ReadSegments while in 0RTT
+ mConnected = true;
+ MaybeRefreshSecurityInfo();
+ }
+ return NS_OK;
+}
+
+void nsHttpTransaction::Refused0RTT() {
+ LOG(("nsHttpTransaction::Refused0RTT %p\n", this));
+ if (mEarlyDataDisposition == EARLY_ACCEPTED) {
+ mEarlyDataDisposition = EARLY_SENT; // undo accepted state
+ }
+}
+
+void nsHttpTransaction::SetHttpTrailers(nsCString& aTrailers) {
+ LOG(("nsHttpTransaction::SetHttpTrailers %p", this));
+ LOG(("[\n %s\n]", aTrailers.BeginReading()));
+
+ // Introduce a local variable to minimize the critical section.
+ UniquePtr<nsHttpHeaderArray> httpTrailers(new nsHttpHeaderArray());
+ // Given it's usually null, use double-check locking for performance.
+ if (mForTakeResponseTrailers) {
+ MutexAutoLock lock(mLock);
+ if (mForTakeResponseTrailers) {
+ // Copy the trailer. |TakeResponseTrailers| gets the original trailer
+ // until the final swap.
+ *httpTrailers = *mForTakeResponseTrailers;
+ }
+ }
+
+ int32_t cur = 0;
+ int32_t len = aTrailers.Length();
+ while (cur < len) {
+ int32_t newline = aTrailers.FindCharInSet("\n", cur);
+ if (newline == -1) {
+ newline = len;
+ }
+
+ int32_t end =
+ (newline && aTrailers[newline - 1] == '\r') ? newline - 1 : newline;
+ nsDependentCSubstring line(aTrailers, cur, end);
+ nsHttpAtom hdr;
+ nsAutoCString hdrNameOriginal;
+ nsAutoCString val;
+ if (NS_SUCCEEDED(httpTrailers->ParseHeaderLine(line, &hdr, &hdrNameOriginal,
+ &val))) {
+ if (hdr == nsHttp::Server_Timing) {
+ Unused << httpTrailers->SetHeaderFromNet(hdr, hdrNameOriginal, val,
+ true);
+ }
+ }
+
+ cur = newline + 1;
+ }
+
+ if (httpTrailers->Count() == 0) {
+ // Didn't find a Server-Timing header, so get rid of this.
+ httpTrailers = nullptr;
+ }
+
+ MutexAutoLock lock(mLock);
+ std::swap(mForTakeResponseTrailers, httpTrailers);
+}
+
+bool nsHttpTransaction::IsWebsocketUpgrade() {
+ if (mRequestHead) {
+ nsAutoCString upgradeHeader;
+ if (NS_SUCCEEDED(mRequestHead->GetHeader(nsHttp::Upgrade, upgradeHeader)) &&
+ upgradeHeader.LowerCaseEqualsLiteral("websocket")) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void nsHttpTransaction::OnProxyConnectComplete(int32_t aResponseCode) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mConnInfo->UsingConnect());
+
+ LOG(("nsHttpTransaction::OnProxyConnectComplete %p aResponseCode=%d", this,
+ aResponseCode));
+
+ mProxyConnectResponseCode = aResponseCode;
+}
+
+int32_t nsHttpTransaction::GetProxyConnectResponseCode() {
+ return mProxyConnectResponseCode;
+}
+
+void nsHttpTransaction::SetFlat407Headers(const nsACString& aHeaders) {
+ MOZ_ASSERT(mProxyConnectResponseCode == 407);
+ MOZ_ASSERT(!mResponseHead);
+
+ LOG(("nsHttpTransaction::SetFlat407Headers %p", this));
+ mFlat407Headers = aHeaders;
+}
+
+void nsHttpTransaction::NotifyTransactionObserver(nsresult reason) {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!mTransactionObserver) {
+ return;
+ }
+
+ bool versionOk = false;
+ bool authOk = false;
+
+ LOG(("nsHttpTransaction::NotifyTransactionObserver %p reason %" PRIx32
+ " conn %p\n",
+ this, static_cast<uint32_t>(reason), mConnection.get()));
+
+ if (mConnection) {
+ HttpVersion version = mConnection->Version();
+ versionOk = (((reason == NS_BASE_STREAM_CLOSED) || (reason == NS_OK)) &&
+ ((mConnection->Version() == HttpVersion::v2_0) ||
+ (mConnection->Version() == HttpVersion::v3_0)));
+
+ nsCOMPtr<nsITLSSocketControl> socketControl;
+ mConnection->GetTLSSocketControl(getter_AddRefs(socketControl));
+ LOG(
+ ("nsHttpTransaction::NotifyTransactionObserver"
+ " version %u socketControl %p\n",
+ static_cast<int32_t>(version), socketControl.get()));
+ if (socketControl) {
+ authOk = !socketControl->GetFailedVerification();
+ }
+ }
+
+ TransactionObserverResult result;
+ result.versionOk() = versionOk;
+ result.authOk() = authOk;
+ result.closeReason() = reason;
+
+ TransactionObserverFunc obs = nullptr;
+ std::swap(obs, mTransactionObserver);
+ obs(std::move(result));
+}
+
+void nsHttpTransaction::UpdateConnectionInfo(nsHttpConnectionInfo* aConnInfo) {
+ MOZ_ASSERT(aConnInfo);
+
+ if (mActivated) {
+ MOZ_ASSERT(false, "Should not update conn info after activated");
+ return;
+ }
+
+ mOrigConnInfo = mConnInfo->Clone();
+ mConnInfo = aConnInfo;
+}
+
+nsresult nsHttpTransaction::OnHTTPSRRAvailable(
+ nsIDNSHTTPSSVCRecord* aHTTPSSVCRecord,
+ nsISVCBRecord* aHighestPriorityRecord) {
+ LOG(("nsHttpTransaction::OnHTTPSRRAvailable [this=%p] mActivated=%d", this,
+ mActivated));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ {
+ MutexAutoLock lock(mLock);
+ MakeDontWaitHTTPSRR();
+ mDNSRequest = nullptr;
+ }
+
+ if (!mResolver) {
+ LOG(("The transaction is not interested in HTTPS record anymore."));
+ return NS_OK;
+ }
+
+ RefPtr<nsHttpTransaction> deleteProtector(this);
+
+ uint32_t receivedStage = HTTPSSVC_NO_USABLE_RECORD;
+ // Make sure we set the correct value to |mHTTPSSVCReceivedStage|, since we
+ // also use this value to indicate whether HTTPS RR is used or not.
+ auto updateHTTPSSVCReceivedStage = MakeScopeExit([&] {
+ mHTTPSSVCReceivedStage = receivedStage;
+
+ // In the case that an HTTPS RR is unavailable, we should call
+ // ProcessPendingQ to make sure this transition to be processed soon.
+ if (!mHTTPSSVCRecord) {
+ gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
+ }
+ });
+
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> record = aHTTPSSVCRecord;
+ if (!record) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool hasIPAddress = false;
+ Unused << record->GetHasIPAddresses(&hasIPAddress);
+
+ if (mActivated) {
+ receivedStage = hasIPAddress ? HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_2
+ : HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_2;
+ return NS_OK;
+ }
+
+ receivedStage = hasIPAddress ? HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_1
+ : HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_1;
+
+ nsCOMPtr<nsISVCBRecord> svcbRecord = aHighestPriorityRecord;
+ if (!svcbRecord) {
+ LOG((" no usable record!"));
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ bool allRecordsExcluded = false;
+ Unused << record->GetAllRecordsExcluded(&allRecordsExcluded);
+ Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON,
+ allRecordsExcluded
+ ? HTTPSSVC_CONNECTION_ALL_RECORDS_EXCLUDED
+ : HTTPSSVC_CONNECTION_NO_USABLE_RECORD);
+ if (allRecordsExcluded &&
+ StaticPrefs::network_dns_httpssvc_reset_exclustion_list() && dns) {
+ Unused << dns->ResetExcludedSVCDomainName(mConnInfo->GetOrigin());
+ if (NS_FAILED(record->GetServiceModeRecord(mCaps & NS_HTTP_DISALLOW_SPDY,
+ mCaps & NS_HTTP_DISALLOW_HTTP3,
+ getter_AddRefs(svcbRecord)))) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // Remember this RR set. In the case that the connection establishment failed,
+ // we will use other records to retry.
+ mHTTPSSVCRecord = record;
+
+ RefPtr<nsHttpConnectionInfo> newInfo =
+ mConnInfo->CloneAndAdoptHTTPSSVCRecord(svcbRecord);
+ bool needFastFallback = newInfo->IsHttp3();
+ bool foundInPendingQ = gHttpHandler->ConnMgr()->RemoveTransFromConnEntry(
+ this, mHashKeyOfConnectionEntry);
+
+ // Adopt the new connection info, so this transaction will be added into the
+ // new connection entry.
+ UpdateConnectionInfo(newInfo);
+
+ // If this transaction is sucessfully removed from a connection entry, we call
+ // ProcessNewTransaction to process it immediately.
+ // If not, this means that nsHttpTransaction::OnHTTPSRRAvailable happens
+ // before ProcessNewTransaction and this transaction will be processed later.
+ if (foundInPendingQ) {
+ if (NS_FAILED(gHttpHandler->ConnMgr()->ProcessNewTransaction(this))) {
+ LOG(("Failed to process this transaction."));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // In case we already have mHttp3BackupTimer, cancel it.
+ MaybeCancelFallbackTimer();
+
+ if (needFastFallback) {
+ CreateAndStartTimer(
+ mFastFallbackTimer, this,
+ StaticPrefs::network_dns_httpssvc_http3_fast_fallback_timeout());
+ }
+
+ // Prefetch the A/AAAA records of the target name.
+ nsAutoCString targetName;
+ Unused << svcbRecord->GetName(targetName);
+ if (mResolver) {
+ mResolver->PrefetchAddrRecord(targetName, mCaps & NS_HTTP_REFRESH_DNS);
+ }
+
+ // echConfig is used, so initialize the retry counters to 0.
+ if (!mConnInfo->GetEchConfig().IsEmpty()) {
+ mEchRetryCounterMap.InsertOrUpdate(
+ Telemetry::TRANSACTION_ECH_RETRY_WITH_ECH_COUNT, 0);
+ mEchRetryCounterMap.InsertOrUpdate(
+ Telemetry::TRANSACTION_ECH_RETRY_WITHOUT_ECH_COUNT, 0);
+ mEchRetryCounterMap.InsertOrUpdate(
+ Telemetry::TRANSACTION_ECH_RETRY_ECH_FAILED_COUNT, 0);
+ mEchRetryCounterMap.InsertOrUpdate(
+ Telemetry::TRANSACTION_ECH_RETRY_OTHERS_COUNT, 0);
+ }
+
+ return NS_OK;
+}
+
+uint32_t nsHttpTransaction::HTTPSSVCReceivedStage() {
+ return mHTTPSSVCReceivedStage;
+}
+
+void nsHttpTransaction::MaybeCancelFallbackTimer() {
+ MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mFastFallbackTimer) {
+ mFastFallbackTimer->Cancel();
+ mFastFallbackTimer = nullptr;
+ }
+
+ if (mHttp3BackupTimer) {
+ mHttp3BackupTimer->Cancel();
+ mHttp3BackupTimer = nullptr;
+ }
+}
+
+void nsHttpTransaction::OnBackupConnectionReady(bool aTriggeredByHTTPSRR) {
+ LOG(
+ ("nsHttpTransaction::OnBackupConnectionReady [%p] mConnected=%d "
+ "aTriggeredByHTTPSRR=%d",
+ this, mConnected, aTriggeredByHTTPSRR));
+ if (mConnected || mClosed || mRestarted) {
+ return;
+ }
+
+ // If HTTPS RR is in play, don't mess up the fallback mechansim of HTTPS RR.
+ if (!aTriggeredByHTTPSRR && mOrigConnInfo) {
+ return;
+ }
+
+ if (mConnection) {
+ // The transaction will only be restarted when we already have a connection.
+ // When there is no connection, this transaction will be moved to another
+ // connection entry.
+ SetRestartReason(aTriggeredByHTTPSRR
+ ? TRANSACTION_RESTART_HTTPS_RR_FAST_FALLBACK
+ : TRANSACTION_RESTART_HTTP3_FAST_FALLBACK);
+ }
+
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+
+ // Need to backup the origin conn info, since UpdateConnectionInfo() will be
+ // called in HandleFallback() and mOrigConnInfo will be
+ // replaced.
+ RefPtr<nsHttpConnectionInfo> backup = mOrigConnInfo;
+ HandleFallback(mBackupConnInfo);
+ mOrigConnInfo.swap(backup);
+
+ RemoveAlternateServiceUsedHeader(mRequestHead);
+
+ if (mResolver) {
+ if (mBackupConnInfo) {
+ const nsCString& host = mBackupConnInfo->GetRoutedHost().IsEmpty()
+ ? mBackupConnInfo->GetOrigin()
+ : mBackupConnInfo->GetRoutedHost();
+ mResolver->PrefetchAddrRecord(host, Caps() & NS_HTTP_REFRESH_DNS);
+ }
+
+ if (!aTriggeredByHTTPSRR) {
+ // We are about to use this backup connection. We shoud not try to use
+ // HTTPS RR at this point.
+ mResolver->Close();
+ mResolver = nullptr;
+ }
+ }
+}
+
+static void CreateBackupConnection(
+ nsHttpConnectionInfo* aBackupConnInfo, nsIInterfaceRequestor* aCallbacks,
+ uint32_t aCaps, std::function<void(bool)>&& aResultCallback) {
+ aBackupConnInfo->SetFallbackConnection(true);
+ RefPtr<SpeculativeTransaction> trans = new SpeculativeTransaction(
+ aBackupConnInfo, aCallbacks, aCaps | NS_HTTP_DISALLOW_HTTP3,
+ std::move(aResultCallback));
+ uint32_t limit =
+ StaticPrefs::network_http_http3_parallel_fallback_conn_limit();
+ if (limit) {
+ trans->SetParallelSpeculativeConnectLimit(limit);
+ trans->SetIgnoreIdle(true);
+ }
+ gHttpHandler->ConnMgr()->DoFallbackConnection(trans, false);
+}
+
+void nsHttpTransaction::OnHttp3BackupTimer() {
+ LOG(("nsHttpTransaction::OnHttp3BackupTimer [%p]", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mConnInfo->IsHttp3());
+
+ mHttp3BackupTimer = nullptr;
+
+ mConnInfo->CloneAsDirectRoute(getter_AddRefs(mBackupConnInfo));
+ MOZ_ASSERT(!mBackupConnInfo->IsHttp3());
+
+ RefPtr<nsHttpTransaction> self = this;
+ auto callback = [self](bool aSucceded) {
+ if (aSucceded) {
+ self->OnBackupConnectionReady(false);
+ }
+ };
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ {
+ MutexAutoLock lock(mLock);
+ callbacks = mCallbacks;
+ }
+ CreateBackupConnection(mBackupConnInfo, callbacks, mCaps,
+ std::move(callback));
+}
+
+void nsHttpTransaction::OnFastFallbackTimer() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpTransaction::OnFastFallbackTimer [%p] mConnected=%d", this,
+ mConnected));
+
+ mFastFallbackTimer = nullptr;
+
+ MOZ_ASSERT(mHTTPSSVCRecord && mOrigConnInfo);
+ if (!mHTTPSSVCRecord || !mOrigConnInfo) {
+ return;
+ }
+
+ bool echConfigUsed = gHttpHandler->EchConfigEnabled(mConnInfo->IsHttp3()) &&
+ !mConnInfo->GetEchConfig().IsEmpty();
+ mBackupConnInfo = PrepareFastFallbackConnInfo(echConfigUsed);
+ if (!mBackupConnInfo) {
+ return;
+ }
+
+ MOZ_ASSERT(!mBackupConnInfo->IsHttp3());
+
+ RefPtr<nsHttpTransaction> self = this;
+ auto callback = [self](bool aSucceded) {
+ if (!aSucceded) {
+ return;
+ }
+
+ self->mFastFallbackTriggered = true;
+ self->OnBackupConnectionReady(true);
+ };
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ {
+ MutexAutoLock lock(mLock);
+ callbacks = mCallbacks;
+ }
+ CreateBackupConnection(mBackupConnInfo, callbacks, mCaps,
+ std::move(callback));
+}
+
+void nsHttpTransaction::HandleFallback(
+ nsHttpConnectionInfo* aFallbackConnInfo) {
+ if (mConnection) {
+ MOZ_ASSERT(mActivated);
+ // Close the transaction with NS_ERROR_NET_RESET, since we know doing this
+ // will make transaction to be restarted.
+ mConnection->CloseTransaction(this, NS_ERROR_NET_RESET);
+ return;
+ }
+
+ if (!aFallbackConnInfo) {
+ // Nothing to do here.
+ return;
+ }
+
+ LOG(("nsHttpTransaction %p HandleFallback to connInfo[%s]", this,
+ aFallbackConnInfo->HashKey().get()));
+
+ bool foundInPendingQ = gHttpHandler->ConnMgr()->RemoveTransFromConnEntry(
+ this, mHashKeyOfConnectionEntry);
+ if (!foundInPendingQ) {
+ MOZ_ASSERT(false, "transaction not in entry");
+ return;
+ }
+
+ // rewind streams in case we already wrote out the request
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
+ if (seekable) {
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ }
+
+ UpdateConnectionInfo(aFallbackConnInfo);
+ Unused << gHttpHandler->ConnMgr()->ProcessNewTransaction(this);
+}
+
+NS_IMETHODIMP
+nsHttpTransaction::Notify(nsITimer* aTimer) {
+ MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!gHttpHandler || !gHttpHandler->ConnMgr()) {
+ return NS_OK;
+ }
+
+ if (aTimer == mFastFallbackTimer) {
+ OnFastFallbackTimer();
+ } else if (aTimer == mHttp3BackupTimer) {
+ OnHttp3BackupTimer();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpTransaction::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsHttpTransaction");
+ return NS_OK;
+}
+
+bool nsHttpTransaction::GetSupportsHTTP3() { return mSupportsHTTP3; }
+
+const int64_t TELEMETRY_REQUEST_SIZE_10M = (int64_t)10 * (int64_t)(1 << 20);
+const int64_t TELEMETRY_REQUEST_SIZE_50M = (int64_t)50 * (int64_t)(1 << 20);
+const int64_t TELEMETRY_REQUEST_SIZE_100M = (int64_t)100 * (int64_t)(1 << 20);
+
+void nsHttpTransaction::CollectTelemetryForUploads() {
+ if ((mRequestSize < TELEMETRY_REQUEST_SIZE_10M) ||
+ mTimings.requestStart.IsNull() || mTimings.responseStart.IsNull()) {
+ return;
+ }
+
+ // We will briefly continue to collect HTTP_UPLOAD_BANDWIDTH_MBPS
+ // (a keyed histogram) while live experiments depend on it.
+ // Once complete, we can remove and use the glean probes,
+ // http_1/2/3_upload_throughput.
+ nsAutoCString protocolVersion(nsHttp::GetProtocolVersion(mHttpVersion));
+ TimeDuration sendTime = mTimings.responseStart - mTimings.requestStart;
+ double megabits = static_cast<double>(mRequestSize) * 8.0 / 1000000.0;
+ uint32_t mpbs = static_cast<uint32_t>(megabits / sendTime.ToSeconds());
+ Telemetry::Accumulate(Telemetry::HTTP_UPLOAD_BANDWIDTH_MBPS, protocolVersion,
+ mpbs);
+
+ switch (mHttpVersion) {
+ case HttpVersion::v1_0:
+ case HttpVersion::v1_1:
+ glean::networking::http_1_upload_throughput.AccumulateSamples({mpbs});
+ if (mRequestSize <= TELEMETRY_REQUEST_SIZE_50M) {
+ glean::networking::http_1_upload_throughput_10_50.AccumulateSamples(
+ {mpbs});
+ } else if (mRequestSize <= TELEMETRY_REQUEST_SIZE_100M) {
+ glean::networking::http_1_upload_throughput_50_100.AccumulateSamples(
+ {mpbs});
+ } else {
+ glean::networking::http_1_upload_throughput_100.AccumulateSamples(
+ {mpbs});
+ }
+ break;
+ case HttpVersion::v2_0:
+ glean::networking::http_2_upload_throughput.AccumulateSamples({mpbs});
+ if (mRequestSize <= TELEMETRY_REQUEST_SIZE_50M) {
+ glean::networking::http_2_upload_throughput_10_50.AccumulateSamples(
+ {mpbs});
+ } else if (mRequestSize <= TELEMETRY_REQUEST_SIZE_100M) {
+ glean::networking::http_2_upload_throughput_50_100.AccumulateSamples(
+ {mpbs});
+ } else {
+ glean::networking::http_2_upload_throughput_100.AccumulateSamples(
+ {mpbs});
+ }
+ break;
+ case HttpVersion::v3_0:
+ glean::networking::http_3_upload_throughput.AccumulateSamples({mpbs});
+ if (mRequestSize <= TELEMETRY_REQUEST_SIZE_50M) {
+ glean::networking::http_3_upload_throughput_10_50.AccumulateSamples(
+ {mpbs});
+ } else if (mRequestSize <= TELEMETRY_REQUEST_SIZE_100M) {
+ glean::networking::http_3_upload_throughput_50_100.AccumulateSamples(
+ {mpbs});
+ } else {
+ glean::networking::http_3_upload_throughput_100.AccumulateSamples(
+ {mpbs});
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void nsHttpTransaction::GetHashKeyOfConnectionEntry(nsACString& aResult) {
+ MutexAutoLock lock(mLock);
+ aResult.Assign(mHashKeyOfConnectionEntry);
+}
+
+void nsHttpTransaction::SetIsForWebTransport(bool aIsForWebTransport) {
+ mIsForWebTransport = aIsForWebTransport;
+}
+
+void nsHttpTransaction::RemoveConnection() {
+ MutexAutoLock lock(mLock);
+ mConnection = nullptr;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h
new file mode 100644
index 0000000000..354fa2c4d2
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -0,0 +1,599 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpTransaction_h__
+#define nsHttpTransaction_h__
+
+#include "ARefBase.h"
+#include "EventTokenBucket.h"
+#include "Http2Push.h"
+#include "HttpTransactionShell.h"
+#include "TimingStruct.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsAHttpConnection.h"
+#include "nsAHttpTransaction.h"
+#include "nsCOMPtr.h"
+#include "nsHttp.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIClassOfService.h"
+#include "nsIEarlyHintObserver.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsITLSSocketControl.h"
+#include "nsITimer.h"
+#include "nsIWebTransport.h"
+#include "nsTHashMap.h"
+#include "nsThreadUtils.h"
+
+//-----------------------------------------------------------------------------
+
+class nsIDNSHTTPSSVCRecord;
+class nsIEventTarget;
+class nsIInputStream;
+class nsIOutputStream;
+class nsIRequestContext;
+class nsISVCBRecord;
+
+namespace mozilla::net {
+
+class HTTPSRecordResolver;
+class nsHttpChunkedDecoder;
+class nsHttpHeaderArray;
+class nsHttpRequestHead;
+class nsHttpResponseHead;
+class NullHttpTransaction;
+class Http2ConnectTransaction;
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction represents a single HTTP transaction. It is thread-safe,
+// intended to run on the socket thread.
+//-----------------------------------------------------------------------------
+
+class nsHttpTransaction final : public nsAHttpTransaction,
+ public HttpTransactionShell,
+ public ATokenBucketEvent,
+ public nsIInputStreamCallback,
+ public nsIOutputStreamCallback,
+ public ARefBase,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_HTTPTRANSACTIONSHELL
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ nsHttpTransaction();
+
+ void OnActivated() override;
+
+ // attributes
+ nsHttpResponseHead* ResponseHead() {
+ return mHaveAllHeaders ? mResponseHead : nullptr;
+ }
+
+ nsIEventTarget* ConsumerTarget() { return mConsumerTarget; }
+
+ // Called to set/find out if the transaction generated a complete response.
+ void SetResponseIsComplete() { mResponseIsComplete = true; }
+
+ void EnableKeepAlive() { mCaps |= NS_HTTP_ALLOW_KEEPALIVE; }
+ void MakeSticky() { mCaps |= NS_HTTP_STICKY_CONNECTION; }
+ void MakeNonSticky() override { mCaps &= ~NS_HTTP_STICKY_CONNECTION; }
+ void MakeRestartable() override { mCaps |= NS_HTTP_CONNECTION_RESTARTABLE; }
+ void MakeNonRestartable() { mCaps &= ~NS_HTTP_CONNECTION_RESTARTABLE; }
+ void RemoveConnection();
+ void SetIsHttp2Websocket(bool h2ws) override { mIsHttp2Websocket = h2ws; }
+ bool IsHttp2Websocket() override { return mIsHttp2Websocket; }
+
+ void SetTRRInfo(nsIRequest::TRRMode aMode,
+ TRRSkippedReason aSkipReason) override {
+ mEffectiveTRRMode = aMode;
+ mTRRSkipReason = aSkipReason;
+ }
+
+ bool WaitingForHTTPSRR() const { return mCaps & NS_HTTP_FORCE_WAIT_HTTP_RR; }
+ void MakeDontWaitHTTPSRR() { mCaps &= ~NS_HTTP_FORCE_WAIT_HTTP_RR; }
+
+ // SetPriority() may only be used by the connection manager.
+ void SetPriority(int32_t priority) { mPriority = priority; }
+ int32_t Priority() { return mPriority; }
+
+ void PrintDiagnostics(nsCString& log);
+
+ // Sets mTimings.transactionPending to the current time stamp or to a null
+ // time stamp (if now is false)
+ void SetPendingTime(bool now = true) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (!now && !mTimings.transactionPending.IsNull()) {
+ // Remember how long it took. We will use this value to record
+ // TRANSACTION_WAIT_TIME_HTTP2_SUP_HTTP3 telemetry, but we need to wait
+ // for the response headers.
+ mPendingDurationTime = TimeStamp::Now() - mTimings.transactionPending;
+ }
+ // Note that the transaction could be added in to a pending queue multiple
+ // times (when the transaction is restarted or moved to a new conn entry due
+ // to HTTPS RR), so we should only set the pending time once.
+ if (mTimings.transactionPending.IsNull()) {
+ mTimings.transactionPending = now ? TimeStamp::Now() : TimeStamp();
+ }
+ }
+ TimeStamp GetPendingTime() override {
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.transactionPending;
+ }
+
+ // overload of nsAHttpTransaction::RequestContext()
+ nsIRequestContext* RequestContext() override { return mRequestContext.get(); }
+ void DispatchedAsBlocking();
+ void RemoveDispatchedAsBlocking();
+
+ void DisableSpdy() override;
+ void DisableHttp2ForProxy() override;
+ void DoNotRemoveAltSvc() override { mDoNotRemoveAltSvc = true; }
+ void DoNotResetIPFamilyPreference() override {
+ mDoNotResetIPFamilyPreference = true;
+ }
+ void DisableHttp3(bool aAllowRetryHTTPSRR) override;
+
+ nsHttpTransaction* QueryHttpTransaction() override { return this; }
+
+ already_AddRefed<Http2PushedStreamWrapper> GetPushedStream() {
+ return do_AddRef(mPushedStream);
+ }
+ already_AddRefed<Http2PushedStreamWrapper> TakePushedStream() {
+ return mPushedStream.forget();
+ }
+
+ uint32_t InitialRwin() const { return mInitialRwin; };
+ bool ChannelPipeFull() { return mWaitingOnPipeOut; }
+
+ // Locked methods to get and set timing info
+ void BootstrapTimings(TimingStruct times);
+ void SetConnectStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetConnectEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetRequestStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetResponseStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetResponseEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+
+ [[nodiscard]] bool Do0RTT() override;
+ [[nodiscard]] nsresult Finish0RTT(bool aRestart,
+ bool aAlpnChanged /* ignored */) override;
+
+ // After Finish0RTT early data may have failed but the caller did not request
+ // restart - this indicates that state for dev tools
+ void Refused0RTT();
+
+ uint64_t BrowserId() override { return mBrowserId; }
+
+ void SetHttpTrailers(nsCString& aTrailers);
+
+ bool IsWebsocketUpgrade();
+
+ void OnProxyConnectComplete(int32_t aResponseCode) override;
+ void SetFlat407Headers(const nsACString& aHeaders);
+
+ // This is only called by Http2PushedStream::TryOnPush when a new pushed
+ // stream is available. The newly added stream will be taken by another
+ // transaction.
+ void OnPush(Http2PushedStreamWrapper* aStream);
+
+ void UpdateConnectionInfo(nsHttpConnectionInfo* aConnInfo);
+
+ void SetClassOfService(ClassOfService cos);
+
+ virtual nsresult OnHTTPSRRAvailable(
+ nsIDNSHTTPSSVCRecord* aHTTPSSVCRecord,
+ nsISVCBRecord* aHighestPriorityRecord) override;
+
+ void GetHashKeyOfConnectionEntry(nsACString& aResult);
+
+ bool IsForWebTransport() { return mIsForWebTransport; }
+
+ private:
+ friend class DeleteHttpTransaction;
+ virtual ~nsHttpTransaction();
+
+ [[nodiscard]] nsresult Restart();
+ char* LocateHttpStart(char* buf, uint32_t len, bool aAllowPartialMatch);
+ [[nodiscard]] nsresult ParseLine(nsACString& line);
+ [[nodiscard]] nsresult ParseLineSegment(char* seg, uint32_t len);
+ [[nodiscard]] nsresult ParseHead(char*, uint32_t count, uint32_t* countRead);
+ [[nodiscard]] nsresult HandleContentStart();
+ [[nodiscard]] nsresult HandleContent(char*, uint32_t count,
+ uint32_t* contentRead,
+ uint32_t* contentRemaining);
+ [[nodiscard]] nsresult ProcessData(char*, uint32_t, uint32_t*);
+ void DeleteSelfOnConsumerThread();
+ void ReleaseBlockingTransaction();
+
+ [[nodiscard]] static nsresult ReadRequestSegment(nsIInputStream*, void*,
+ const char*, uint32_t,
+ uint32_t, uint32_t*);
+ [[nodiscard]] static nsresult WritePipeSegment(nsIOutputStream*, void*, char*,
+ uint32_t, uint32_t, uint32_t*);
+
+ bool TimingEnabled() const { return mCaps & NS_HTTP_TIMING_ENABLED; }
+
+ bool ResponseTimeoutEnabled() const final;
+
+ void ReuseConnectionOnRestartOK(bool reuseOk) override {
+ mReuseOnRestart = reuseOk;
+ }
+
+ // Called right after we parsed the response head. Checks for connection
+ // based authentication schemes in reponse headers for WWW and Proxy
+ // authentication. If such is found in any of them, NS_HTTP_STICKY_CONNECTION
+ // is set in mCaps. We need the sticky flag be set early to keep the
+ // connection from very start of the authentication process.
+ void CheckForStickyAuthScheme();
+ void CheckForStickyAuthSchemeAt(nsHttpAtom const& header);
+ bool IsStickyAuthSchemeAt(nsACString const& auth);
+
+ // Called from WriteSegments. Checks for conditions whether to throttle
+ // reading the content. When this returns true, WriteSegments returns
+ // WOULD_BLOCK.
+ bool ShouldThrottle();
+
+ void NotifyTransactionObserver(nsresult reason);
+
+ // When echConfig is enabled, this function put other available records
+ // in mRecordsForRetry. Returns true when mRecordsForRetry is not empty,
+ // otherwise returns false.
+ bool PrepareSVCBRecordsForRetry(const nsACString& aFailedDomainName,
+ const nsACString& aFailedAlpn,
+ bool& aAllRecordsHaveEchConfig);
+ // This function setups a new connection info for restarting this transaction.
+ void PrepareConnInfoForRetry(nsresult aReason);
+ // This function is used to select the next non http3 record and is only
+ // executed when the fast fallback timer is triggered.
+ already_AddRefed<nsHttpConnectionInfo> PrepareFastFallbackConnInfo(
+ bool aEchConfigUsed);
+
+ void MaybeReportFailedSVCDomain(nsresult aReason,
+ nsHttpConnectionInfo* aFailedConnInfo);
+
+ already_AddRefed<Http2PushedStreamWrapper> TakePushedStreamById(
+ uint32_t aStreamId);
+
+ // IMPORTANT: when adding new values, always add them to the end, otherwise
+ // it will mess up telemetry.
+ enum HTTPSSVC_CONNECTION_FAILED_REASON : uint32_t {
+ HTTPSSVC_CONNECTION_OK = 0,
+ HTTPSSVC_CONNECTION_UNKNOWN_HOST = 1,
+ HTTPSSVC_CONNECTION_UNREACHABLE = 2,
+ HTTPSSVC_CONNECTION_421_RECEIVED = 3,
+ HTTPSSVC_CONNECTION_SECURITY_ERROR = 4,
+ HTTPSSVC_CONNECTION_NO_USABLE_RECORD = 5,
+ HTTPSSVC_CONNECTION_ALL_RECORDS_EXCLUDED = 6,
+ HTTPSSVC_CONNECTION_OTHERS = 7,
+ };
+ HTTPSSVC_CONNECTION_FAILED_REASON ErrorCodeToFailedReason(
+ nsresult aErrorCode);
+
+ void OnHttp3BackupTimer();
+ void OnBackupConnectionReady(bool aTriggeredByHTTPSRR);
+ void OnFastFallbackTimer();
+ void HandleFallback(nsHttpConnectionInfo* aFallbackConnInfo);
+ void MaybeCancelFallbackTimer();
+
+ // IMPORTANT: when adding new values, always add them to the end, otherwise
+ // it will mess up telemetry.
+ enum TRANSACTION_RESTART_REASON : uint32_t {
+ TRANSACTION_RESTART_NONE = 0, // The transacion was not restarted.
+ TRANSACTION_RESTART_FORCED, // The transaction was forced to restart.
+ TRANSACTION_RESTART_NO_DATA_SENT,
+ TRANSACTION_RESTART_DOWNGRADE_WITH_EARLY_DATA,
+ TRANSACTION_RESTART_HTTPS_RR_NET_RESET,
+ TRANSACTION_RESTART_HTTPS_RR_CONNECTION_REFUSED,
+ TRANSACTION_RESTART_HTTPS_RR_UNKNOWN_HOST,
+ TRANSACTION_RESTART_HTTPS_RR_NET_TIMEOUT,
+ TRANSACTION_RESTART_HTTPS_RR_SEC_ERROR,
+ TRANSACTION_RESTART_HTTPS_RR_FAST_FALLBACK,
+ TRANSACTION_RESTART_HTTP3_FAST_FALLBACK,
+ TRANSACTION_RESTART_OTHERS,
+ TRANSACTION_RESTART_PROTOCOL_VERSION_ALERT,
+ };
+ void SetRestartReason(TRANSACTION_RESTART_REASON aReason);
+
+ bool HandleWebTransportResponse(uint16_t aStatus);
+
+ void MaybeRefreshSecurityInfo() {
+ MutexAutoLock lock(mLock);
+ if (mConnection) {
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ mConnection->GetTLSSocketControl(getter_AddRefs(tlsSocketControl));
+ if (tlsSocketControl) {
+ tlsSocketControl->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
+ }
+ }
+ }
+
+ private:
+ class UpdateSecurityCallbacks : public Runnable {
+ public:
+ UpdateSecurityCallbacks(nsHttpTransaction* aTrans,
+ nsIInterfaceRequestor* aCallbacks)
+ : Runnable("net::nsHttpTransaction::UpdateSecurityCallbacks"),
+ mTrans(aTrans),
+ mCallbacks(aCallbacks) {}
+
+ NS_IMETHOD Run() override {
+ if (mTrans->mConnection) {
+ mTrans->mConnection->SetSecurityCallbacks(mCallbacks);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsHttpTransaction> mTrans;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ };
+
+ Mutex mLock MOZ_UNANNOTATED{"transaction lock"};
+
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsITransportEventSink> mTransportSink;
+ nsCOMPtr<nsIEventTarget> mConsumerTarget;
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+ nsCOMPtr<nsIAsyncInputStream> mPipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> mPipeOut;
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+
+ uint64_t mChannelId{0};
+
+ nsCString mReqHeaderBuf; // flattened request headers
+ nsCOMPtr<nsIInputStream> mRequestStream;
+ int64_t mRequestSize{0};
+
+ RefPtr<nsAHttpConnection> mConnection;
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ // This is only set in UpdateConnectionInfo() when we have received a SVCB RR.
+ // When echConfig is not used and the connection is failed, this transaction
+ // will be restarted with this origin connection info directly.
+ // When echConfig is enabled, there are two cases below.
+ // 1. If all records have echConfig, we will retry other records except the
+ // failed one. In the case all other records with echConfig are failed and the
+ // pref network.dns.echconfig.fallback_to_origin_when_all_failed is true, this
+ // origin connection info will be used.
+ // 2. If only some records have echConfig and some not, we always fallback to
+ // this origin conn info.
+ RefPtr<nsHttpConnectionInfo> mOrigConnInfo;
+ nsHttpRequestHead* mRequestHead{nullptr}; // weak ref
+ nsHttpResponseHead* mResponseHead{nullptr}; // owning pointer
+
+ nsAHttpSegmentReader* mReader{nullptr};
+ nsAHttpSegmentWriter* mWriter{nullptr};
+
+ nsCString mLineBuf; // may contain a partial line
+
+ int64_t mContentLength{-1}; // equals -1 if unknown
+ int64_t mContentRead{0}; // count of consumed content bytes
+ Atomic<int64_t, ReleaseAcquire> mTransferSize{0}; // count of received bytes
+
+ // After a 304/204 or other "no-content" style response we will skip over
+ // up to MAX_INVALID_RESPONSE_BODY_SZ bytes when looking for the next
+ // response header to deal with servers that actually sent a response
+ // body where they should not have. This member tracks how many bytes have
+ // so far been skipped.
+ uint32_t mInvalidResponseBytesRead{0};
+
+ RefPtr<Http2PushedStreamWrapper> mPushedStream;
+ uint32_t mInitialRwin{0};
+
+ nsHttpChunkedDecoder* mChunkedDecoder{nullptr};
+
+ TimingStruct mTimings;
+
+ nsresult mStatus{NS_OK};
+
+ int16_t mPriority{0};
+
+ // the number of times this transaction has been restarted
+ uint16_t mRestartCount{0};
+ Atomic<uint32_t, ReleaseAcquire> mCaps{0};
+
+ HttpVersion mHttpVersion{HttpVersion::UNKNOWN};
+ uint16_t mHttpResponseCode{0};
+ nsCString mFlat407Headers;
+
+ uint32_t mCurrentHttpResponseHeaderSize{0};
+
+ int32_t const THROTTLE_NO_LIMIT = -1;
+ // This can have 3 possible values:
+ // * THROTTLE_NO_LIMIT - this means the transaction is not in any way limited
+ // to read the response, this is the default
+ // * a positive number - a limit is set because the transaction is obligated
+ // to throttle the response read, this is decresed with
+ // every piece of data the transaction receives
+ // * zero - when the transaction depletes the limit for reading, this makes it
+ // stop reading and return WOULD_BLOCK from WriteSegments;
+ // transaction then waits for a call of ResumeReading that resets
+ // this member back to THROTTLE_NO_LIMIT
+ int32_t mThrottlingReadAllowance{THROTTLE_NO_LIMIT};
+
+ // mCapsToClear holds flags that should be cleared in mCaps, e.g. unset
+ // NS_HTTP_REFRESH_DNS when DNS refresh request has completed to avoid
+ // redundant requests on the network. The member itself is atomic, but
+ // access to it from the networking thread may happen either before or
+ // after the main thread modifies it. To deal with raciness, only unsetting
+ // bitfields should be allowed: 'lost races' will thus err on the
+ // conservative side, e.g. by going ahead with a 2nd DNS refresh.
+ Atomic<uint32_t> mCapsToClear{0};
+ Atomic<bool, ReleaseAcquire> mResponseIsComplete{false};
+ Atomic<bool, ReleaseAcquire> mClosed{false};
+ Atomic<bool, Relaxed> mIsHttp3Used{false};
+
+ // True iff WriteSegments was called while this transaction should be
+ // throttled (stop reading) Used to resume read on unblock of reading. Conn
+ // manager is responsible for calling back to resume reading.
+ bool mReadingStopped{false};
+
+ // state flags, all logically boolean, but not packed together into a
+ // bitfield so as to avoid bitfield-induced races. See bug 560579.
+ bool mConnected{false};
+ bool mActivated{false};
+ bool mHaveStatusLine{false};
+ bool mHaveAllHeaders{false};
+ bool mTransactionDone{false};
+ bool mDidContentStart{false};
+ bool mNoContent{false}; // expecting an empty entity body
+ bool mSentData{false};
+ bool mReceivedData{false};
+ bool mStatusEventPending{false};
+ bool mHasRequestBody{false};
+ bool mProxyConnectFailed{false};
+ bool mHttpResponseMatched{false};
+ bool mPreserveStream{false};
+ bool mDispatchedAsBlocking{false};
+ bool mResponseTimeoutEnabled{true};
+ bool mForceRestart{false};
+ bool mReuseOnRestart{false};
+ bool mContentDecoding{false};
+ bool mContentDecodingCheck{false};
+ bool mDeferredSendProgress{false};
+ bool mWaitingOnPipeOut{false};
+ bool mDoNotRemoveAltSvc{false};
+ bool mDoNotResetIPFamilyPreference{false};
+ bool mIsHttp2Websocket{false};
+
+ // mClosed := transaction has been explicitly closed
+ // mTransactionDone := transaction ran to completion or was interrupted
+ // mResponseComplete := transaction ran to completion
+
+ // For Restart-In-Progress Functionality
+ bool mReportedStart{false};
+ bool mReportedResponseHeader{false};
+
+ // protected by nsHttp::GetLock()
+ bool mResponseHeadTaken{false};
+ UniquePtr<nsHttpHeaderArray> mForTakeResponseTrailers;
+ bool mResponseTrailersTaken{false};
+
+ // Set when this transaction was restarted by call to Restart(). Used to tell
+ // the http channel to reset proxy authentication.
+ Atomic<bool> mRestarted{false};
+
+ // The time when the transaction was submitted to the Connection Manager
+ TimeDuration mPendingDurationTime;
+
+ uint64_t mBrowserId{0};
+
+ // For Rate Pacing via an EventTokenBucket
+ public:
+ // called by the connection manager to run this transaction through the
+ // token bucket. If the token bucket admits the transaction immediately it
+ // returns true. The function is called repeatedly until it returns true.
+ bool TryToRunPacedRequest();
+
+ // ATokenBucketEvent pure virtual implementation. Called by the token bucket
+ // when the transaction is ready to run. If this happens asynchrounously to
+ // token bucket submission the transaction just posts an event that causes
+ // the pending transaction queue to be rerun (and TryToRunPacedRequest() to
+ // be run again.
+ void OnTokenBucketAdmitted() override; // ATokenBucketEvent
+
+ // CancelPacing() can be used to tell the token bucket to remove this
+ // transaction from the list of pending transactions. This is used when a
+ // transaction is believed to be HTTP/1 (and thus subject to rate pacing)
+ // but later can be dispatched via spdy (not subject to rate pacing).
+ void CancelPacing(nsresult reason);
+
+ // Called by the connetion manager on the socket thread when reading for this
+ // previously throttled transaction has to be resumed.
+ void ResumeReading();
+
+ // This examins classification of this transaction whether the Throttleable
+ // class has been set while Leader, Unblocked, DontThrottle has not.
+ bool EligibleForThrottling() const;
+
+ private:
+ bool mSubmittedRatePacing{false};
+ bool mPassedRatePacing{false};
+ bool mSynchronousRatePaceRequest{false};
+ nsCOMPtr<nsICancelable> mTokenBucketCancel;
+
+ void CollectTelemetryForUploads();
+
+ public:
+ ClassOfService GetClassOfService() {
+ return {mClassOfServiceFlags, mClassOfServiceIncremental};
+ }
+
+ private:
+ Atomic<uint32_t, Relaxed> mClassOfServiceFlags{0};
+ Atomic<bool, Relaxed> mClassOfServiceIncremental{false};
+
+ public:
+ nsIInterfaceRequestor* SecurityCallbacks() { return mCallbacks; }
+ // Called when this transaction is inserted in the pending queue.
+ void OnPendingQueueInserted(const nsACString& aConnectionHashKey);
+
+ private:
+ TransactionObserverFunc mTransactionObserver;
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+ bool mResolvedByTRR{false};
+ Atomic<nsIRequest::TRRMode, Relaxed> mEffectiveTRRMode{
+ nsIRequest::TRR_DEFAULT_MODE};
+ Atomic<TRRSkippedReason, Relaxed> mTRRSkipReason{nsITRRSkipReason::TRR_UNSET};
+ bool mEchConfigUsed = false;
+
+ bool m0RTTInProgress{false};
+ bool mDoNotTryEarlyData{false};
+ enum {
+ EARLY_NONE,
+ EARLY_SENT,
+ EARLY_ACCEPTED,
+ EARLY_425
+ } mEarlyDataDisposition{EARLY_NONE};
+
+ HttpTrafficCategory mTrafficCategory{HttpTrafficCategory::eInvalid};
+ bool mThroughCaptivePortal;
+ Atomic<int32_t> mProxyConnectResponseCode{0};
+
+ OnPushCallback mOnPushCallback;
+ nsTHashMap<uint32_t, RefPtr<Http2PushedStreamWrapper>> mIDToStreamMap;
+
+ nsCOMPtr<nsICancelable> mDNSRequest;
+ Atomic<uint32_t, Relaxed> mHTTPSSVCReceivedStage{HTTPSSVC_NOT_USED};
+ bool m421Received = false;
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> mHTTPSSVCRecord;
+ nsTArray<RefPtr<nsISVCBRecord>> mRecordsForRetry;
+ bool mDontRetryWithDirectRoute = false;
+ bool mFastFallbackTriggered = false;
+ bool mHttp3BackupTimerCreated = false;
+ nsCOMPtr<nsITimer> mFastFallbackTimer;
+ nsCOMPtr<nsITimer> mHttp3BackupTimer;
+ RefPtr<nsHttpConnectionInfo> mBackupConnInfo;
+ RefPtr<HTTPSRecordResolver> mResolver;
+ TRANSACTION_RESTART_REASON mRestartReason = TRANSACTION_RESTART_NONE;
+
+ nsTHashMap<nsUint32HashKey, uint32_t> mEchRetryCounterMap;
+
+ bool mSupportsHTTP3 = false;
+ Atomic<bool, Relaxed> mIsForWebTransport{false};
+
+ bool mEarlyDataWasAvailable = false;
+ bool ShouldRestartOn0RttError(nsresult reason);
+
+ nsCOMPtr<nsIEarlyHintObserver> mEarlyHintObserver;
+ // This hash key is set when a transaction is inserted into the connection
+ // entry's pending queue.
+ // See nsHttpConnectionMgr::GetOrCreateConnectionEntry(). A transaction could
+ // be associated with the connection entry whose hash key is not the same as
+ // this transaction's.
+ nsCString mHashKeyOfConnectionEntry;
+
+ nsCOMPtr<WebTransportSessionEventListener> mWebTransportSessionEventListener;
+};
+
+} // namespace mozilla::net
+
+#endif // nsHttpTransaction_h__
diff --git a/netwerk/protocol/http/nsIBackgroundChannelRegistrar.idl b/netwerk/protocol/http/nsIBackgroundChannelRegistrar.idl
new file mode 100644
index 0000000000..025dcebf51
--- /dev/null
+++ b/netwerk/protocol/http/nsIBackgroundChannelRegistrar.idl
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+namespace mozilla {
+namespace net {
+class HttpBackgroundChannelParent;
+class HttpChannelParent;
+}
+}
+%}
+
+[ptr] native HttpChannelParent(mozilla::net::HttpChannelParent);
+[ptr] native HttpBackgroundChannelParent(mozilla::net::HttpBackgroundChannelParent);
+
+/*
+ * Registrar for pairing HttpChannelParent and HttpBackgroundChannelParent via
+ * channel Id. HttpChannelParent::OnBackgroundParentReady and
+ * HttpBackgroundChannelParent::LinkToChannel will be invoked to notify the
+ * existence of associated channel object.
+ */
+[uuid(8acaa9b1-f0c4-4ade-baeb-39b0d4b96e5b)]
+interface nsIBackgroundChannelRegistrar : nsISupports
+{
+ /*
+ * Link the provided channel parent actor with the given channel Id.
+ * callbacks will be invoked immediately when the HttpBackgroundChannelParent
+ * associated with the same channel Id is found. Store the HttpChannelParent
+ * until a matched linkBackgroundChannel is invoked.
+ *
+ * @param aKey the channel Id
+ * @param aChannel the channel parent actor to be paired
+ */
+ [noscript,notxpcom,nostdcall] void linkHttpChannel(in uint64_t aKey,
+ in HttpChannelParent aChannel);
+
+ /*
+ * Link the provided background channel with the given channel Id.
+ * callbacks will be invoked immediately when the HttpChannelParent associated
+ * with the same channel Id is found. Store the HttpBackgroundChannelParent
+ * until a matched linkHttpChannel is invoked.
+ *
+ * @param aKey the channel Id
+ * @param aBgChannel the background channel to be paired
+ */
+ [noscript,notxpcom,nostdcall] void linkBackgroundChannel(in uint64_t aKey,
+ in HttpBackgroundChannelParent aBgChannel);
+
+ /*
+ * Delete previous stored HttpChannelParent or HttpBackgroundChannelParent
+ * if no need to wait for the paired channel object, e.g. background channel
+ * is destroyed before pairing is completed.
+ *
+ * @param aKey the channel Id
+ */
+ [noscript,notxpcom,nostdcall] void deleteChannel(in uint64_t aKey);
+
+};
diff --git a/netwerk/protocol/http/nsIBinaryHttp.idl b/netwerk/protocol/http/nsIBinaryHttp.idl
new file mode 100644
index 0000000000..895f25f2d2
--- /dev/null
+++ b/netwerk/protocol/http/nsIBinaryHttp.idl
@@ -0,0 +1,40 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(f6f899cc-683a-43da-9206-0eb0c09cc758)]
+interface nsIBinaryHttpRequest : nsISupports {
+ readonly attribute ACString method;
+ readonly attribute ACString scheme;
+ readonly attribute ACString authority;
+ readonly attribute ACString path;
+ readonly attribute Array<ACString> headerNames;
+ readonly attribute Array<ACString> headerValues;
+ readonly attribute Array<octet> content;
+};
+
+[scriptable, uuid(6ca85d9c-cdc5-45d4-9adc-005abedce9c9)]
+interface nsIBinaryHttpResponse : nsISupports {
+ readonly attribute uint16_t status;
+ readonly attribute Array<ACString> headerNames;
+ readonly attribute Array<ACString> headerValues;
+ readonly attribute Array<octet> content;
+};
+
+// Implements Binary Representation of HTTP Messages (RFC 9292).
+// In normal operation, encodeRequest and decodeResponse are expected to be
+// used. For testing, decodeRequest and encodeResponse are available as well.
+// Thread safety: this interface may be used on any thread, but objects
+// returned by it are not inherently thread-safe and should only be used on the
+// threads they were created on.
+[scriptable, builtinclass, uuid(b43b3f73-8160-4ab2-9f5d-4129a9708081)]
+interface nsIBinaryHttp : nsISupports {
+ Array<octet> encodeRequest(in nsIBinaryHttpRequest request);
+ nsIBinaryHttpRequest decodeRequest(in Array<octet> request);
+
+ nsIBinaryHttpResponse decodeResponse(in Array<octet> response);
+ Array<octet> encodeResponse(in nsIBinaryHttpResponse response);
+};
diff --git a/netwerk/protocol/http/nsICorsPreflightCallback.h b/netwerk/protocol/http/nsICorsPreflightCallback.h
new file mode 100644
index 0000000000..aa16dbf12d
--- /dev/null
+++ b/netwerk/protocol/http/nsICorsPreflightCallback.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsICorsPreflightCallback_h__
+#define nsICorsPreflightCallback_h__
+
+#include "nsISupports.h"
+#include "nsID.h"
+#include "nsError.h"
+
+#define NS_ICORSPREFLIGHTCALLBACK_IID \
+ { \
+ 0x3758cfbb, 0x259f, 0x4074, { \
+ 0xa8, 0xc0, 0x98, 0xe0, 0x4b, 0x3c, 0xc0, 0xe3 \
+ } \
+ }
+
+class nsICorsPreflightCallback : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(nsICorsPreflightCallback);
+ NS_IMETHOD OnPreflightSucceeded() = 0;
+ NS_IMETHOD OnPreflightFailed(nsresult aError) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsICorsPreflightCallback,
+ NS_ICORSPREFLIGHTCALLBACK_IID);
+
+#endif
diff --git a/netwerk/protocol/http/nsIEarlyHintObserver.idl b/netwerk/protocol/http/nsIEarlyHintObserver.idl
new file mode 100644
index 0000000000..1cc206ae56
--- /dev/null
+++ b/netwerk/protocol/http/nsIEarlyHintObserver.idl
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(97b6be6f-283c-45dd-81a7-4bb2d87d42f8)]
+interface nsIEarlyHintObserver : nsISupports
+{
+ /**
+ * This method is called when the transaction has early hint (i.e. the
+ * '103 Early Hint' informational response) headers.
+ */
+ void earlyHint(in ACString linkHeader, in ACString referrerPolicy, in ACString cspHeader);
+};
diff --git a/netwerk/protocol/http/nsIHttpActivityObserver.idl b/netwerk/protocol/http/nsIHttpActivityObserver.idl
new file mode 100644
index 0000000000..533e6af135
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpActivityObserver.idl
@@ -0,0 +1,214 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+namespace mozilla {
+namespace net {
+class HttpActivityArgs;
+} // namespace net
+} // namespace mozilla
+%}
+
+[ref] native HttpActivityArgs(const mozilla::net::HttpActivityArgs);
+
+/**
+ * nsIHttpActivityObserver
+ *
+ * This interface provides a way for http activities to be reported
+ * to observers.
+ */
+[scriptable, uuid(412880C8-6C36-48d8-BF8F-84F91F892503)]
+interface nsIHttpActivityObserver : nsISupports
+{
+ /**
+ * observe activity from the http transport
+ *
+ * @param aHttpChannel
+ * nsISupports interface for the the http channel that
+ * generated this activity
+ * @param aActivityType
+ * The value of this aActivityType will be one of
+ * ACTIVITY_TYPE_SOCKET_TRANSPORT or
+ * ACTIVITY_TYPE_HTTP_TRANSACTION
+ * @param aActivitySubtype
+ * The value of this aActivitySubtype, will be depend
+ * on the value of aActivityType. When aActivityType
+ * is ACTIVITY_TYPE_SOCKET_TRANSPORT
+ * aActivitySubtype will be one of the
+ * nsISocketTransport::STATUS_???? values defined in
+ * nsISocketTransport.idl
+ * OR when aActivityType
+ * is ACTIVITY_TYPE_HTTP_TRANSACTION
+ * aActivitySubtype will be one of the
+ * nsIHttpActivityObserver::ACTIVITY_SUBTYPE_???? values
+ * defined below
+ * @param aTimestamp
+ * microseconds past the epoch of Jan 1, 1970
+ * @param aExtraSizeData
+ * Any extra size data optionally available with
+ * this activity
+ * @param aExtraStringData
+ * Any extra string data optionally available with
+ * this activity
+ */
+ [must_use]
+ void observeActivity(in nsISupports aHttpChannel,
+ in uint32_t aActivityType,
+ in uint32_t aActivitySubtype,
+ in PRTime aTimestamp,
+ in uint64_t aExtraSizeData,
+ in ACString aExtraStringData);
+
+ /**
+ * This attribute is true when this interface is active and should
+ * observe http activities. When false, observeActivity() should not
+ * be called. It is present for compatibility reasons and should be
+ * implemented only by nsHttpActivityDistributor.
+ */
+ [must_use] readonly attribute boolean isActive;
+
+ /**
+ * This function is for internal use only. Every time a http transaction
+ * is created in socket process, we use this function to set the value of
+ * |isActive|. We need this since the real value of |isActive| is
+ * only available in parent process.
+ */
+ [noscript] void setIsActive(in boolean aActived);
+
+ /**
+ * This function is used when the real http channel is not available.
+ * We use the information in |HttpActivityArgs| to get the http channel or
+ * create a |NullHttpChannel|.
+ *
+ * @param aArgs
+ * See the definition of |HttpActivityArgs| in PSocketProcess.ipdl.
+ */
+ [noscript, must_use]
+ void observeActivityWithArgs(in HttpActivityArgs aArgs,
+ in uint32_t aActivityType,
+ in uint32_t aActivitySubtype,
+ in PRTime aTimestamp,
+ in uint64_t aExtraSizeData,
+ in ACString aExtraStringData);
+
+ /**
+ * This function is for testing only. We use this function to observe the
+ * activities of HTTP connections. To receive this notification,
+ * observeConnection should be set to true.
+ */
+ [must_use]
+ void observeConnectionActivity(in ACString aHost,
+ in int32_t aPort,
+ in boolean aSSL,
+ in boolean aHasECH,
+ in boolean aIsHttp3,
+ in uint32_t aActivityType,
+ in uint32_t aActivitySubtype,
+ in PRTime aTimestamp,
+ in ACString aExtraStringData);
+
+ const unsigned long ACTIVITY_TYPE_SOCKET_TRANSPORT = 0x0001;
+ const unsigned long ACTIVITY_TYPE_HTTP_TRANSACTION = 0x0002;
+ const unsigned long ACTIVITY_TYPE_HTTP_CONNECTION = 0x0003;
+
+ const unsigned long ACTIVITY_SUBTYPE_REQUEST_HEADER = 0x5001;
+ const unsigned long ACTIVITY_SUBTYPE_REQUEST_BODY_SENT = 0x5002;
+ const unsigned long ACTIVITY_SUBTYPE_RESPONSE_START = 0x5003;
+ const unsigned long ACTIVITY_SUBTYPE_RESPONSE_HEADER = 0x5004;
+ const unsigned long ACTIVITY_SUBTYPE_RESPONSE_COMPLETE = 0x5005;
+ const unsigned long ACTIVITY_SUBTYPE_TRANSACTION_CLOSE = 0x5006;
+ const unsigned long ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER = 0x5007;
+ const unsigned long ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED = 0x5008;
+ const unsigned long ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED = 0x5009;
+ const unsigned long ACTIVITY_SUBTYPE_ECH_SET = 0x500A;
+ const unsigned long ACTIVITY_SUBTYPE_CONNECTION_CREATED = 0x500B;
+
+ /**
+ * When aActivityType is ACTIVITY_TYPE_SOCKET_TRANSPORT
+ * and aActivitySubtype is STATUS_SENDING_TO
+ * aExtraSizeData will contain the count of bytes sent
+ * There may be more than one of these activities reported
+ * for a single http transaction, each aExtraSizeData
+ * represents only that portion of the total bytes sent
+ *
+ * When aActivityType is ACTIVITY_TYPE_HTTP_TRANSACTION
+ * and aActivitySubtype is ACTIVITY_SUBTYPE_REQUEST_HEADER
+ * aExtraStringData will contain the text of the header
+ *
+ * When aActivityType is ACTIVITY_TYPE_HTTP_TRANSACTION
+ * and aActivitySubtype is ACTIVITY_SUBTYPE_RESPONSE_HEADER
+ * aExtraStringData will contain the text of the header
+ *
+ * When aActivityType is ACTIVITY_TYPE_HTTP_TRANSACTION
+ * and aActivitySubtype is ACTIVITY_SUBTYPE_RESPONSE_COMPLETE
+ * aExtraSizeData will contain the count of total bytes received
+ */
+};
+
+%{C++
+
+#define NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT \
+ nsIHttpActivityObserver::ACTIVITY_TYPE_SOCKET_TRANSPORT
+#define NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION \
+ nsIHttpActivityObserver::ACTIVITY_TYPE_HTTP_TRANSACTION
+#define NS_ACTIVITY_TYPE_HTTP_CONNECTION \
+ nsIHttpActivityObserver::ACTIVITY_TYPE_HTTP_CONNECTION
+
+#define NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_REQUEST_HEADER
+#define NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_REQUEST_BODY_SENT
+#define NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_RESPONSE_START
+#define NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_RESPONSE_HEADER
+#define NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_RESPONSE_COMPLETE
+#define NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_TRANSACTION_CLOSE
+#define NS_HTTP_ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER
+#define NS_HTTP_ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED
+#define NS_HTTP_ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED
+#define NS_HTTP_ACTIVITY_SUBTYPE_ECH_SET \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_ECH_SET
+#define NS_HTTP_ACTIVITY_SUBTYPE_CONNECTION_CREATED \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_CONNECTION_CREATED
+%}
+
+/**
+ * nsIHttpActivityDistributor
+ *
+ * This interface provides a way to register and unregister observers to the
+ * http activities.
+ */
+[scriptable, builtinclass, uuid(7C512CB8-582A-4625-B5B6-8639755271B5)]
+interface nsIHttpActivityDistributor : nsIHttpActivityObserver
+{
+ void addObserver(in nsIHttpActivityObserver aObserver);
+ void removeObserver(in nsIHttpActivityObserver aObserver);
+
+ /**
+ * C++ friendly getter
+ */
+ [noscript, notxpcom] bool Activated();
+ [noscript, notxpcom] bool ObserveProxyResponseEnabled();
+ [noscript, notxpcom] bool ObserveConnectionEnabled();
+
+ /**
+ * When true, the ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER will be sent to
+ * the observers.
+ */
+ [must_use] attribute boolean observeProxyResponse;
+
+ /**
+ * When true, the ACTIVITY_TYPE_HTTP_CONNECTION will be sent to
+ * the observers.
+ */
+ [must_use] attribute boolean observeConnection;
+};
diff --git a/netwerk/protocol/http/nsIHttpAuthManager.idl b/netwerk/protocol/http/nsIHttpAuthManager.idl
new file mode 100644
index 0000000000..5acd652942
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpAuthManager.idl
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+
+/**
+ * nsIHttpAuthManager
+ *
+ * This service provides access to cached HTTP authentication
+ * user credentials (domain, username, password) for sites
+ * visited during the current browser session.
+ *
+ * This interface exists to provide other HTTP stacks with the
+ * ability to share HTTP authentication credentials with Necko.
+ * This is currently used by the Java plugin (version 1.5 and
+ * higher) to avoid duplicate authentication prompts when the
+ * Java client fetches content from a HTTP site that the user
+ * has already logged into.
+ */
+[scriptable, builtinclass, uuid(54f90444-c52b-4d2d-8916-c59a2bb25938)]
+interface nsIHttpAuthManager : nsISupports
+{
+ /**
+ * Lookup auth identity.
+ *
+ * @param aScheme
+ * the URL scheme (e.g., "http"). NOTE: for proxy authentication,
+ * this should be "http" (this includes authentication for CONNECT
+ * tunneling).
+ * @param aHost
+ * the host of the server issuing a challenge (ASCII only).
+ * @param aPort
+ * the port of the server issuing a challenge.
+ * @param aAuthType
+ * optional string identifying auth type used (e.g., "basic")
+ * @param aRealm
+ * optional string identifying auth realm.
+ * @param aPath
+ * optional string identifying auth path. empty for proxy auth.
+ * @param aUserDomain
+ * return value containing user domain.
+ * @param aUserName
+ * return value containing user name.
+ * @param aUserPassword
+ * return value containing user password.
+ * @param aIsPrivate
+ * whether to look up a private or public identity (they are
+ * stored separately, for use by private browsing)
+ * @param aPrincipal
+ * the principal from which to derive information about which
+ * app/mozbrowser is in use for this request
+ */
+ [must_use] void getAuthIdentity(in ACString aScheme,
+ in ACString aHost,
+ in int32_t aPort,
+ in ACString aAuthType,
+ in ACString aRealm,
+ in ACString aPath,
+ out AString aUserDomain,
+ out AString aUserName,
+ out AString aUserPassword,
+ [optional] in bool aIsPrivate,
+ [optional] in nsIPrincipal aPrincipal);
+
+ /**
+ * Store auth identity.
+ *
+ * @param aScheme
+ * the URL scheme (e.g., "http"). NOTE: for proxy authentication,
+ * this should be "http" (this includes authentication for CONNECT
+ * tunneling).
+ * @param aHost
+ * the host of the server issuing a challenge (ASCII only).
+ * @param aPort
+ * the port of the server issuing a challenge.
+ * @param aAuthType
+ * optional string identifying auth type used (e.g., "basic")
+ * @param aRealm
+ * optional string identifying auth realm.
+ * @param aPath
+ * optional string identifying auth path. empty for proxy auth.
+ * @param aUserDomain
+ * optional string containing user domain.
+ * @param aUserName
+ * optional string containing user name.
+ * @param aUserPassword
+ * optional string containing user password.
+ * @param aIsPrivate
+ * whether to store a private or public identity (they are
+ * stored separately, for use by private browsing)
+ * @param aPrincipal
+ * the principal from which to derive information about which
+ * app/mozbrowser is in use for this request
+ */
+ [must_use] void setAuthIdentity(in ACString aScheme,
+ in ACString aHost,
+ in int32_t aPort,
+ in ACString aAuthType,
+ in ACString aRealm,
+ in ACString aPath,
+ in AString aUserDomain,
+ in AString aUserName,
+ in AString aUserPassword,
+ [optional] in boolean aIsPrivate,
+ [optional] in nsIPrincipal aPrincipal);
+
+ /**
+ * Clear all auth cache.
+ */
+ [must_use] void clearAll();
+};
diff --git a/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl b/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl
new file mode 100644
index 0000000000..f479b6ff58
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProxiedChannel.idl"
+#include "nsIRequest.idl"
+
+interface nsILoadGroup;
+interface nsIURI;
+interface nsIInterfaceRequestor;
+
+[uuid(701093ac-5c7f-429c-99e3-423b041fccb4)]
+interface nsIHttpAuthenticableChannel : nsIProxiedChannel
+{
+ /**
+ * If the channel being authenticated is using SSL.
+ */
+ [must_use] readonly attribute boolean isSSL;
+
+ /**
+ * Returns if the proxy HTTP method used is CONNECT. If no proxy is being
+ * used it must return PR_FALSE.
+ */
+ [must_use] readonly attribute boolean proxyMethodIsConnect;
+
+ /**
+ * Cancels the current request. See nsIRequest.
+ */
+ [must_use] void cancel(in nsresult aStatus);
+
+ /**
+ * The load flags of this request. See nsIRequest.
+ */
+ [must_use] readonly attribute nsLoadFlags loadFlags;
+
+ /**
+ * The URI corresponding to the channel. See nsIChannel.
+ */
+ [must_use] readonly attribute nsIURI URI;
+
+ /**
+ * The load group of this request. It is here for querying its
+ * notificationCallbacks. See nsIRequest.
+ */
+ [must_use] readonly attribute nsILoadGroup loadGroup;
+
+ /**
+ * The notification callbacks for the channel. See nsIChannel.
+ */
+ [must_use] readonly attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * The HTTP request method. See nsIHttpChannel.
+ */
+ [must_use] readonly attribute ACString requestMethod;
+
+ /**
+ * The "Server" response header.
+ * Return NS_ERROR_NOT_AVAILABLE if not available.
+ */
+ [must_use] readonly attribute ACString serverResponseHeader;
+
+ /**
+ * The Proxy-Authenticate response header.
+ */
+ [must_use] readonly attribute ACString proxyChallenges;
+
+ /**
+ * The WWW-Authenticate response header.
+ */
+ [must_use] readonly attribute ACString WWWChallenges;
+
+ /**
+ * Sets the Proxy-Authorization request header. An empty string
+ * will clear it.
+ */
+ [must_use] void setProxyCredentials(in ACString credentials);
+
+ /**
+ * Sets the Authorization request header. An empty string
+ * will clear it.
+ */
+ [must_use] void setWWWCredentials(in ACString credentials);
+
+ /**
+ * Called when authentication information is ready and has been set on this
+ * object using setWWWCredentials/setProxyCredentials. Implementations can
+ * continue with the request and send the given information to the server.
+ *
+ * It is called asynchronously from
+ * nsIHttpChannelAuthProvider::processAuthentication if that method returns
+ * NS_ERROR_IN_PROGRESS.
+ *
+ * @note Any exceptions thrown from this method should be ignored.
+ */
+ [must_use] void onAuthAvailable();
+
+ /**
+ * Notifies that the prompt was cancelled. It is called asynchronously
+ * from nsIHttpChannelAuthProvider::processAuthentication if that method
+ * returns NS_ERROR_IN_PROGRESS.
+ *
+ * @param userCancel
+ * If the user was cancelled has cancelled the authentication prompt.
+ */
+ [must_use] void onAuthCancelled(in boolean userCancel);
+
+ /**
+ * Tells the channel to drop and close any sticky connection, since this
+ * connection oriented schema cannot be negotiated second time on
+ * the same connection.
+ */
+ [must_use] void closeStickyConnection();
+
+ /**
+ * Tells the channel to mark the connection as allowed to restart on
+ * authentication retry. This is allowed when the request is a start
+ * of a new authentication round.
+ */
+ void connectionRestartable(in boolean restartable);
+};
diff --git a/netwerk/protocol/http/nsIHttpAuthenticator.idl b/netwerk/protocol/http/nsIHttpAuthenticator.idl
new file mode 100644
index 0000000000..222bd3837c
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpAuthenticator.idl
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIHttpAuthenticableChannel;
+interface nsIHttpAuthenticatorCallback;
+interface nsICancelable;
+
+/**
+ * nsIHttpAuthenticator
+ *
+ * Interface designed to allow for pluggable HTTP authentication modules.
+ * Implementations are registered under the ContractID:
+ *
+ * "@mozilla.org/network/http-authenticator;1?scheme=<auth-scheme>"
+ *
+ * where <auth-scheme> is the lower-cased value of the authentication scheme
+ * found in the server challenge per the rules of RFC 2617.
+ */
+[uuid(fef7db8a-a4e2-49d1-9685-19ed7e309b7d)]
+interface nsIHttpAuthenticator : nsISupports
+{
+ /**
+ * Upon receipt of a server challenge, this function is called to determine
+ * whether or not the current user identity has been rejected. If true,
+ * then the user will be prompted by the channel to enter (or revise) their
+ * identity. Following this, generateCredentials will be called.
+ *
+ * If the IDENTITY_IGNORED auth flag is set, then the aInvalidateIdentity
+ * return value will be ignored, and user prompting will be suppressed.
+ *
+ * @param aChannel
+ * the http channel that received the challenge.
+ * @param aChallenge
+ * the challenge from the WWW-Authenticate/Proxy-Authenticate
+ * server response header. (possibly from the auth cache.)
+ * @param aProxyAuth
+ * flag indicating whether or not aChallenge is from a proxy.
+ * @param aSessionState
+ * see description below for generateCredentials.
+ * @param aContinuationState
+ * see description below for generateCredentials.
+ * @param aInvalidateIdentity
+ * return value indicating whether or not to prompt the user for a
+ * revised identity.
+ */
+ [must_use]
+ void challengeReceived(in nsIHttpAuthenticableChannel aChannel,
+ in ACString aChallenge,
+ in boolean aProxyAuth,
+ inout nsISupports aSessionState,
+ inout nsISupports aContinuationState,
+ out boolean aInvalidatesIdentity);
+
+ /**
+ * Called to generate the authentication credentials for a particular
+ * server/proxy challenge asynchronously. Credentials will be sent back
+ * to the server via an Authorization/Proxy-Authorization header.
+ *
+ * @param aChannel
+ * the http channel requesting credentials
+ * @param aCallback
+ * callback function to be called when credentials are available
+ * @param aChallenge
+ * the challenge from the WWW-Authenticate/Proxy-Authenticate
+ * server response header. (possibly from the auth cache.)
+ * @param aProxyAuth
+ * flag indicating whether or not aChallenge is from a proxy.
+ * @param aDomain
+ * string containing the domain name (if appropriate)
+ * @param aUser
+ * string containing the user name
+ * @param aPassword
+ * string containing the password
+ * @param aSessionState
+ * state stored along side the user's identity in the auth cache
+ * for the lifetime of the browser session. if a new auth cache
+ * entry is created for this challenge, then this parameter will
+ * be null. on return, the result will be stored in the new auth
+ * cache entry. this parameter is non-null when an auth cache entry
+ * is being reused. currently modification of session state is not
+ * communicated to caller, thus caching credentials obtained by
+ * asynchronous way is not supported.
+ * @param aContinuationState
+ * state held by the channel between consecutive calls to
+ * generateCredentials, assuming multiple calls are required
+ * to authenticate. this state is held for at most the lifetime of
+ * the channel.
+ * @pram aCancel
+ * returns cancellable runnable object which caller can use to cancel
+ * calling aCallback when finished.
+ */
+ [must_use]
+ void generateCredentialsAsync(in nsIHttpAuthenticableChannel aChannel,
+ in nsIHttpAuthenticatorCallback aCallback,
+ in ACString aChallenge,
+ in boolean aProxyAuth,
+ in AString aDomain,
+ in AString aUser,
+ in AString aPassword,
+ in nsISupports aSessionState,
+ in nsISupports aContinuationState,
+ out nsICancelable aCancel);
+ /**
+ * Called to generate the authentication credentials for a particular
+ * server/proxy challenge. This is the value that will be sent back
+ * to the server via an Authorization/Proxy-Authorization header.
+ *
+ * This function may be called using a cached challenge provided the
+ * authenticator sets the REUSABLE_CHALLENGE flag.
+ *
+ * @param aChannel
+ * the http channel requesting credentials
+ * @param aChallenge
+ * the challenge from the WWW-Authenticate/Proxy-Authenticate
+ * server response header. (possibly from the auth cache.)
+ * @param aProxyAuth
+ * flag indicating whether or not aChallenge is from a proxy.
+ * @param aDomain
+ * string containing the domain name (if appropriate)
+ * @param aUser
+ * string containing the user name
+ * @param aPassword
+ * string containing the password
+ * @param aSessionState
+ * state stored along side the user's identity in the auth cache
+ * for the lifetime of the browser session. if a new auth cache
+ * entry is created for this challenge, then this parameter will
+ * be null. on return, the result will be stored in the new auth
+ * cache entry. this parameter is non-null when an auth cache entry
+ * is being reused.
+ * @param aContinuationState
+ * state held by the channel between consecutive calls to
+ * generateCredentials, assuming multiple calls are required
+ * to authenticate. this state is held for at most the lifetime of
+ * the channel.
+ * @param aFlags
+ * authenticator may return one of the generate flags bellow.
+ */
+ [must_use]
+ ACString generateCredentials(in nsIHttpAuthenticableChannel aChannel,
+ in ACString aChallenge,
+ in boolean aProxyAuth,
+ in AString aDomain,
+ in AString aUser,
+ in AString aPassword,
+ inout nsISupports aSessionState,
+ inout nsISupports aContinuationState,
+ out unsigned long aFlags);
+
+ /**
+ * Generate flags
+ */
+
+ /**
+ * Indicates that the authenticator has used an out-of-band or internal
+ * source of identity and tells the consumer that it must not cache
+ * the returned identity because it might not be valid and would overwrite
+ * the cached identity. See bug 542318 comment 32.
+ */
+ const unsigned long USING_INTERNAL_IDENTITY = (1<<0);
+
+ /**
+ * Flags defining various properties of the authenticator.
+ */
+ [must_use] readonly attribute unsigned long authFlags;
+
+ /**
+ * A request based authentication scheme only authenticates an individual
+ * request (or a set of requests under the same authentication domain as
+ * defined by RFC 2617). BASIC and DIGEST are request based authentication
+ * schemes.
+ */
+ const unsigned long REQUEST_BASED = (1<<0);
+
+ /**
+ * A connection based authentication scheme authenticates an individual
+ * connection. Multiple requests may be issued over the connection without
+ * repeating the authentication steps. Connection based authentication
+ * schemes can associate state with the connection being authenticated via
+ * the aContinuationState parameter (see generateCredentials).
+ */
+ const unsigned long CONNECTION_BASED = (1<<1);
+
+ /**
+ * The credentials returned from generateCredentials may be reused with any
+ * other URLs within "the protection space" as defined by RFC 2617 section
+ * 1.2. If this flag is not set, then generateCredentials must be called
+ * for each request within the protection space. REUSABLE_CREDENTIALS
+ * implies REUSABLE_CHALLENGE.
+ */
+ const unsigned long REUSABLE_CREDENTIALS = (1<<2);
+
+ /**
+ * A challenge may be reused to later generate credentials in anticipation
+ * of a duplicate server challenge for URLs within "the protection space"
+ * as defined by RFC 2617 section 1.2.
+ */
+ const unsigned long REUSABLE_CHALLENGE = (1<<3);
+
+ /**
+ * This flag indicates that the identity of the user is not required by
+ * this authentication scheme.
+ */
+ const unsigned long IDENTITY_IGNORED = (1<<10);
+
+ /**
+ * This flag indicates that the identity of the user includes a domain
+ * attribute that the user must supply.
+ */
+ const unsigned long IDENTITY_INCLUDES_DOMAIN = (1<<11);
+
+ /**
+ * This flag indicates that the identity will be sent encrypted. It does
+ * not make sense to combine this flag with IDENTITY_IGNORED.
+ */
+ const unsigned long IDENTITY_ENCRYPTED = (1<<12);
+};
diff --git a/netwerk/protocol/http/nsIHttpChannel.idl b/netwerk/protocol/http/nsIHttpChannel.idl
new file mode 100644
index 0000000000..7ed707eacf
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -0,0 +1,497 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIChannel.idl"
+
+interface nsIHttpHeaderVisitor;
+interface nsIReferrerInfo;
+
+%{C++
+#include "GeckoProfiler.h"
+%}
+
+native UniqueProfileChunkedBuffer(mozilla::UniquePtr<mozilla::ProfileChunkedBuffer>);
+
+/**
+ * nsIHttpChannel
+ *
+ * This interface allows for the modification of HTTP request parameters and
+ * the inspection of the resulting HTTP response status and headers when they
+ * become available.
+ */
+[builtinclass, scriptable, uuid(c5a4a073-4539-49c7-a3f2-cec3f0619c6c)]
+interface nsIHttpChannel : nsIIdentChannel
+{
+ /**************************************************************************
+ * REQUEST CONFIGURATION
+ *
+ * Modifying request parameters after asyncOpen has been called is an error.
+ */
+
+ /**
+ * Set/get the HTTP request method (default is "GET"). Both setter and
+ * getter are case sensitive.
+ *
+ * This attribute may only be set before the channel is opened.
+ *
+ * NOTE: The data for a "POST" or "PUT" request can be configured via
+ * nsIUploadChannel; however, after setting the upload data, it may be
+ * necessary to set the request method explicitly. The documentation
+ * for nsIUploadChannel has further details.
+ *
+ * @throws NS_ERROR_IN_PROGRESS if set after the channel has been opened.
+ */
+ [must_use] attribute ACString requestMethod;
+
+ /**
+ * Get/set the referrer information. This contains the referrer (URI) of the
+ * resource from which this channel's URI was obtained (see RFC2616 section
+ * 14.36) and the referrer policy applied to the referrer.
+ *
+ * This attribute may only be set before the channel is opened.
+ *
+ * Setting this attribute will clone new referrerInfo object by default.
+ *
+ * NOTE: The channel may silently refuse to set the Referer header if the
+ * URI does not pass certain security checks (e.g., a "https://" URL will
+ * never be sent as the referrer for a plaintext HTTP request). The
+ * implementation is not required to throw an exception when the referrer
+ * URI is rejected.
+ *
+ * @throws NS_ERROR_IN_PROGRESS if set after the channel has been opened.
+ * @throws NS_ERROR_FAILURE if used for setting referrer during
+ * visitRequestHeaders. Getting the value will not throw.
+ */
+ [must_use, infallible] attribute nsIReferrerInfo referrerInfo;
+
+ /**
+ * Set referrer Info without clone new object.
+ * Use this api only when you are passing a referrerInfo to the channel with
+ * 1-1 relationship. Don't use this api if you will reuse the referrer info
+ * object later. For example when to use:
+ * channel.setReferrerInfoWithoutClone(new ReferrerInfo());
+ *
+ */
+ [must_use, noscript]
+ void setReferrerInfoWithoutClone(in nsIReferrerInfo aReferrerInfo);
+
+ /**
+ * Returns the network protocol used to fetch the resource as identified
+ * by the ALPN Protocol ID.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] readonly attribute ACString protocolVersion;
+
+ /**
+ * size consumed by the response header fields and the response payload body
+ */
+ [must_use] readonly attribute uint64_t transferSize;
+
+ /**
+ * size consumed by the request header fields and the request payload body
+ */
+ [must_use] readonly attribute uint64_t requestSize;
+
+ /**
+ * The size of the message body received by the client,
+ * after removing any applied content-codings
+ */
+ [must_use] readonly attribute uint64_t decodedBodySize;
+
+ /**
+ * The size in octets of the payload body, prior to removing content-codings
+ */
+ [must_use] readonly attribute uint64_t encodedBodySize;
+
+ /**
+ * Get the value of a particular request header.
+ *
+ * @param aHeader
+ * The case-insensitive name of the request header to query (e.g.,
+ * "Cache-Control").
+ *
+ * @return the value of the request header.
+ * @throws NS_ERROR_NOT_AVAILABLE if the header is not set.
+ */
+ [must_use] ACString getRequestHeader(in ACString aHeader);
+
+ /**
+ * Set the value of a particular request header.
+ *
+ * This method allows, for example, the cookies module to add "Cookie"
+ * headers to the outgoing HTTP request.
+ *
+ * This method may only be called before the channel is opened.
+ *
+ * @param aHeader
+ * The case-insensitive name of the request header to set (e.g.,
+ * "Cookie").
+ * @param aValue
+ * The request header value to set (e.g., "X=1").
+ * @param aMerge
+ * If true, the new header value will be merged with any existing
+ * values for the specified header. This flag is ignored if the
+ * specified header does not support merging (e.g., the "Content-
+ * Type" header can only have one value). The list of headers for
+ * which this flag is ignored is an implementation detail. If this
+ * flag is false, then the header value will be replaced with the
+ * contents of |aValue|.
+ *
+ * If aValue is empty and aMerge is false, the header will be cleared.
+ *
+ * @throws NS_ERROR_IN_PROGRESS if called after the channel has been
+ * opened.
+ * @throws NS_ERROR_FAILURE if called during visitRequestHeaders.
+ */
+ [must_use] void setRequestHeader(in ACString aHeader,
+ in ACString aValue,
+ in boolean aMerge);
+
+ /**
+ * Creates and sets new ReferrerInfo object
+ * @param aUrl referrer url
+ * @param aPolicy referrer policy of the created object
+ * @param aSendReferrer indicates if the referrer should not be sent or not
+ * even when it's available.
+ */
+ [must_use] void setNewReferrerInfo(in ACString aUrl,
+ in nsIReferrerInfo_ReferrerPolicyIDL aPolicy,
+ in boolean aSendReferrer);
+
+ /**
+ * Set a request header with empty value.
+ *
+ * This should be used with caution in the cases where the behavior of
+ * setRequestHeader ignoring empty header values is undesirable.
+ *
+ * This method may only be called before the channel is opened.
+ *
+ * @param aHeader
+ * The case-insensitive name of the request header to set (e.g.,
+ * "Cookie").
+ *
+ * @throws NS_ERROR_IN_PROGRESS if called after the channel has been
+ * opened.
+ * @throws NS_ERROR_FAILURE if called during visitRequestHeaders.
+ */
+ [must_use] void setEmptyRequestHeader(in ACString aHeader);
+
+ /**
+ * Call this method to visit all request headers. Calling setRequestHeader
+ * while visiting request headers has undefined behavior. Don't do it!
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ */
+ [must_use] void visitRequestHeaders(in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Call this method to visit all non-default (UA-provided) request headers.
+ * Calling setRequestHeader while visiting request headers has undefined
+ * behavior. Don't do it!
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ */
+ [must_use]
+ void visitNonDefaultRequestHeaders(in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Call this method to see if we need to strip the request body headers
+ * for the new http channel. This should be called during redirection.
+ */
+ [must_use] bool ShouldStripRequestBodyHeader(in ACString aMethod);
+
+ /**
+ * This attribute of the channel indicates whether or not
+ * the underlying HTTP transaction should be honor stored Strict Transport
+ * Security directives for its principal. It defaults to true. Using
+ * OCSP to bootstrap the HTTPs is the likely use case for setting it to
+ * false.
+ *
+ * This attribute may only be set before the channel is opened.
+ *
+ * @throws NS_ERROR_IN_PROGRESS or NS_ERROR_ALREADY_OPENED
+ * if called after the channel has been opened.
+ */
+ [must_use] attribute boolean allowSTS;
+
+ /**
+ * This attribute specifies the number of redirects this channel is allowed
+ * to make. If zero, the channel will fail to redirect and will generate
+ * a NS_ERROR_REDIRECT_LOOP failure status.
+ *
+ * NOTE: An HTTP redirect results in a new channel being created. If the
+ * new channel supports nsIHttpChannel, then it will be assigned a value
+ * to its |redirectionLimit| attribute one less than the value of the
+ * redirected channel's |redirectionLimit| attribute. The initial value
+ * for this attribute may be a configurable preference (depending on the
+ * implementation).
+ */
+ [must_use] attribute unsigned long redirectionLimit;
+
+ /**************************************************************************
+ * RESPONSE INFO
+ *
+ * Accessing response info before the onStartRequest event is an error.
+ */
+
+ /**
+ * Get the HTTP response code (e.g., 200).
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] readonly attribute unsigned long responseStatus;
+
+ /**
+ * Get the HTTP response status text (e.g., "OK").
+ *
+ * NOTE: This returns the raw (possibly 8-bit) text from the server. There
+ * are no assumptions made about the charset of the returned text. You
+ * have been warned!
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] readonly attribute ACString responseStatusText;
+
+ /**
+ * Returns true if the HTTP response code indicates success. The value of
+ * nsIRequest::status will be NS_OK even when processing a 404 response
+ * because a 404 response may include a message body that (in some cases)
+ * should be shown to the user.
+ *
+ * Use this attribute to distinguish server error pages from normal pages,
+ * instead of comparing the response status manually against the set of
+ * valid response codes, if that is required by your application.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] readonly attribute boolean requestSucceeded;
+
+ /** Indicates whether channel should be treated as the main one for the
+ * current document. If manually set to true, will always remain true. Otherwise,
+ * will be true if LOAD_DOCUMENT_URI is set in the channel's loadflags.
+ */
+ [must_use] attribute boolean isMainDocumentChannel;
+
+ /**
+ * Get the value of a particular response header.
+ *
+ * @param aHeader
+ * The case-insensitive name of the response header to query (e.g.,
+ * "Set-Cookie").
+ *
+ * @return the value of the response header.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest) or if the header is
+ * not set in the response.
+ */
+ [must_use] ACString getResponseHeader(in ACString header);
+
+ /**
+ * Set the value of a particular response header.
+ *
+ * This method allows, for example, the HTML content sink to inform the HTTP
+ * channel about HTTP-EQUIV headers found in HTML <META> tags.
+ *
+ * @param aHeader
+ * The case-insensitive name of the response header to set (e.g.,
+ * "Cache-control").
+ * @param aValue
+ * The response header value to set (e.g., "no-cache").
+ * @param aMerge
+ * If true, the new header value will be merged with any existing
+ * values for the specified header. This flag is ignored if the
+ * specified header does not support merging (e.g., the "Content-
+ * Type" header can only have one value). The list of headers for
+ * which this flag is ignored is an implementation detail. If this
+ * flag is false, then the header value will be replaced with the
+ * contents of |aValue|.
+ *
+ * If aValue is empty and aMerge is false, the header will be cleared.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ * @throws NS_ERROR_ILLEGAL_VALUE if changing the value of this response
+ * header is not allowed.
+ * @throws NS_ERROR_FAILURE if called during visitResponseHeaders,
+ * VisitOriginalResponseHeaders or getOriginalResponseHeader.
+ */
+ [must_use] void setResponseHeader(in ACString header,
+ in ACString value,
+ in boolean merge);
+
+ /**
+ * Call this method to visit all response headers. Calling
+ * setResponseHeader while visiting response headers has undefined
+ * behavior. Don't do it!
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] void visitResponseHeaders(in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Get the value(s) of a particular response header in the form and order
+ * it has been received from the remote peer. There can be multiple headers
+ * with the same name.
+ *
+ * @param aHeader
+ * The case-insensitive name of the response header to query (e.g.,
+ * "Set-Cookie").
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest) or if the header is
+ * not set in the response.
+ */
+ [must_use] void getOriginalResponseHeader(in ACString aHeader,
+ in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Call this method to visit all response headers in the form and order as
+ * they have been received from the remote peer.
+ * Calling setResponseHeader while visiting response headers has undefined
+ * behavior. Don't do it!
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use]
+ void visitOriginalResponseHeaders(in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Returns true if the server sent a "Cache-Control: no-store" response
+ * header.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] boolean isNoStoreResponse();
+
+ /**
+ * Returns true if the server sent the equivalent of a "Cache-control:
+ * no-cache" response header. Equivalent response headers include:
+ * "Pragma: no-cache", "Expires: 0", and "Expires" with a date value
+ * in the past relative to the value of the "Date" header.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] boolean isNoCacheResponse();
+
+ /**
+ * Returns true if the server sent a "Cache-Control: private" response
+ * header.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ [must_use] boolean isPrivateResponse();
+
+ /**
+ * Instructs the channel to immediately redirect to a new destination.
+ * Can only be called on channels that have not yet called their
+ * listener's OnStartRequest(). Generally that means the latest time
+ * this can be used is one of:
+ * "http-on-examine-response"
+ * "http-on-examine-merged-response"
+ * "http-on-examine-cached-response"
+ *
+ * When non-null URL is set before AsyncOpen:
+ * we attempt to redirect to the targetURI before we even start building
+ * and sending the request to the cache or the origin server.
+ * If the redirect is vetoed, we fail the channel.
+ *
+ * When set between AsyncOpen and first call to OnStartRequest being called:
+ * we attempt to redirect before we start delivery of network or cached
+ * response to the listener. If vetoed, we continue with delivery of
+ * the original content to the channel listener.
+ *
+ * When passed aTargetURI is null the channel behaves normally (can be
+ * rewritten).
+ *
+ * This method provides no explicit conflict resolution. The last
+ * caller to call it wins.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called after the channel has already
+ * started to deliver the content to its listener.
+ */
+ [must_use] void redirectTo(in nsIURI aTargetURI);
+
+ /**
+ * Flags a channel to be upgraded to HTTPS.
+ *
+ * Upgrading to a secure channel must happen before or during
+ * "http-on-modify-request". If redirectTo is called early as well, it
+ * will win and upgradeToSecure will be a no-op.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called after the channel has already
+ * started to deliver the content to its listener.
+ */
+ [must_use] void upgradeToSecure();
+
+ /**
+ * Identifies the request context for this load.
+ */
+ [noscript, must_use] attribute uint64_t requestContextID;
+
+ /**
+ * ID of the top-level document's inner window. Identifies the content
+ * this channels is being load in.
+ */
+ [must_use] attribute uint64_t topLevelContentWindowId;
+
+ /**
+ * ID of the browser for this channel.
+ *
+ * NOTE: The setter of this attribute is currently for xpcshell test only.
+ * Don't alter it otherwise.
+ */
+ [must_use] attribute uint64_t browserId;
+
+ /**
+ * In e10s, the information that the CORS response blocks the load is in the
+ * parent, which doesn't know the true window id of the request, so we may
+ * need to proxy the request to the child.
+ *
+ * @param aMessage
+ * The message to print in the console.
+ *
+ * @param aCategory
+ * The category under which the message should be displayed.
+ *
+ * @param aIsWarning
+ * When true, this is a warning message.
+ */
+ void logBlockedCORSRequest(in AString aMessage,
+ in ACString aCategory,
+ in boolean aIsWarning);
+
+ void logMimeTypeMismatch(in ACString aMessageName,
+ in boolean aWarning,
+ in AString aURL,
+ in AString aContentType);
+
+ [notxpcom, nostdcall] void setSource(in UniqueProfileChunkedBuffer aSource);
+
+ [must_use] attribute AString classicScriptHintCharset;
+
+ [must_use] attribute AString documentCharacterSet;
+};
diff --git a/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl b/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl
new file mode 100644
index 0000000000..67a1ae217d
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICancelable.idl"
+
+interface nsIHttpChannel;
+interface nsIHttpAuthenticableChannel;
+
+/**
+ * nsIHttpChannelAuthProvider
+ *
+ * This interface is intended for providing authentication for http-style
+ * channels, like nsIHttpChannel and nsIWebSocket, which implement the
+ * nsIHttpAuthenticableChannel interface.
+ *
+ * When requesting pages AddAuthorizationHeaders MUST be called
+ * in order to get the http cached headers credentials. When the request is
+ * unsuccessful because of receiving either a 401 or 407 http response code
+ * ProcessAuthentication MUST be called and the page MUST be requested again
+ * with the new credentials that the user has provided. After a successful
+ * request, checkForSuperfluousAuth MAY be called, and disconnect MUST be
+ * called.
+ */
+
+[uuid(788f331b-2e1f-436c-b405-4f88a31a105b)]
+interface nsIHttpChannelAuthProvider : nsICancelable
+{
+ /**
+ * Initializes the http authentication support for the channel.
+ * Implementations must hold a weak reference of the channel.
+ */
+ [must_use] void init(in nsIHttpAuthenticableChannel channel);
+
+ /**
+ * Upon receipt of a server challenge, this function is called to determine
+ * the credentials to send.
+ *
+ * @param httpStatus
+ * the http status received.
+ * @param sslConnectFailed
+ * if the last ssl tunnel connection attempt was or not successful.
+ * @param callback
+ * the callback to be called when it returns NS_ERROR_IN_PROGRESS.
+ * The implementation must hold a weak reference.
+ *
+ * @returns NS_OK if the credentials were got and set successfully.
+ * NS_ERROR_IN_PROGRESS if the credentials are going to be asked to
+ * the user. The channel reference must be
+ * alive until the feedback from
+ * nsIHttpAuthenticableChannel's methods or
+ * until disconnect be called.
+ */
+ [must_use] void processAuthentication(in unsigned long httpStatus,
+ in boolean sslConnectFailed);
+
+ /**
+ * Add credentials from the http auth cache.
+ *
+ * @param dontUseCachedWWWCreds
+ * When true, the method will not add any Authorization headers from
+ * the auth cache.
+ */
+ [must_use] void addAuthorizationHeaders(in boolean dontUseCachedWWWCreds);
+
+ /**
+ * Check if an unnecessary(and maybe malicious) url authentication has been
+ * provided.
+ */
+ [must_use] void checkForSuperfluousAuth();
+
+ /**
+ * Cancel pending user auth prompts and release the callback and channel
+ * weak references.
+ */
+ [must_use] void disconnect(in nsresult status);
+
+ /**
+ * Clear the proxy ident to not consider it invalid on re-athentication.
+ * Called when the channel finds out its transaction has been internally
+ * restarted.
+ */
+ void clearProxyIdent();
+};
diff --git a/netwerk/protocol/http/nsIHttpChannelChild.idl b/netwerk/protocol/http/nsIHttpChannelChild.idl
new file mode 100644
index 0000000000..13ff5661b8
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannelChild.idl
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+namespace mozilla {
+class OriginAttributes;
+} // mozilla namespace
+%}
+
+[ptr] native RequestHeaderTuples(mozilla::net::RequestHeaderTuples);
+[ref] native MaybeCorsPreflightArgsRef(mozilla::Maybe<mozilla::net::CorsPreflightArgs>);
+[ref] native const_OriginAttributes(const mozilla::OriginAttributes);
+
+interface nsIPrincipal;
+interface nsIURI;
+
+[uuid(d02b96ed-2789-4f42-a25c-7abe63de7c18)]
+interface nsIHttpChannelChild : nsISupports
+{
+ [must_use] void addCookiesToRequest();
+
+ // Headers that the channel client has set via SetRequestHeader.
+ [must_use] readonly attribute RequestHeaderTuples clientSetRequestHeaders;
+
+ // Headers that the channel client has set via SetRequestHeader.
+ [notxpcom, nostdcall]
+ void GetClientSetCorsPreflightParameters(in MaybeCorsPreflightArgsRef args);
+
+ // This method is called by nsCORSListenerProxy if we need to remove
+ // an entry from the CORS preflight cache in the parent process.
+ [must_use]
+ void removeCorsPreflightCacheEntry(in nsIURI aURI, in nsIPrincipal aRequestingPrincipal,
+ in const_OriginAttributes aOriginAttributes);
+};
diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl
new file mode 100644
index 0000000000..1650b8f35c
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -0,0 +1,522 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsILoadInfo.idl"
+#include "nsIRequest.idl"
+#include "nsITRRSkipReason.idl"
+
+%{C++
+#include "nsStringFwd.h"
+#include "nsTArrayForwardDeclare.h"
+template<class T> class nsCOMArray;
+namespace mozilla {
+class TimeStamp;
+namespace net {
+class nsHttpConnectionInfo;
+class WebSocketConnectionBase;
+class EarlyHintConnectArgs;
+}
+namespace dom {
+enum class RequestMode : uint8_t;
+}
+}
+%}
+[ptr] native nsHttpConnectionInfo(mozilla::net::nsHttpConnectionInfo);
+[ptr] native StringArray(nsTArray<nsCString>);
+[ref] native CStringArrayRef(const nsTArray<nsCString>);
+[ref] native securityMessagesArray(nsCOMArray<nsISecurityConsoleMessage>);
+[ptr] native WebSocketConnectionBase(mozilla::net::WebSocketConnectionBase);
+
+native TimeStamp(mozilla::TimeStamp);
+native RequestMode(mozilla::dom::RequestMode);
+
+interface nsIAsyncInputStream;
+interface nsIAsyncOutputStream;
+interface nsIEarlyHintObserver;
+interface nsIPrincipal;
+interface nsIProxyInfo;
+interface nsISecurityConsoleMessage;
+interface nsISocketTransport;
+interface nsIURI;
+interface WebTransportSessionEventListener;
+
+/**
+ * The callback interface for nsIHttpChannelInternal::HTTPUpgrade()
+ */
+
+[scriptable, uuid(5b515449-ab64-4dba-b3cd-da8fc2f83064)]
+interface nsIHttpUpgradeListener : nsISupports
+{
+ [must_use] void onTransportAvailable(in nsISocketTransport aTransport,
+ in nsIAsyncInputStream aSocketIn,
+ in nsIAsyncOutputStream aSocketOut);
+
+ [must_use] void onUpgradeFailed(in nsresult aErrorCode);
+
+ void onWebSocketConnectionAvailable(in WebSocketConnectionBase aConnection);
+};
+
+/**
+ * Dumping ground for http. This interface will never be frozen. If you are
+ * using any feature exposed by this interface, be aware that this interface
+ * will change and you will be broken. You have been warned.
+ */
+[builtinclass, scriptable, uuid(4e28263d-1e03-46f4-aa5c-9512f91957f9)]
+interface nsIHttpChannelInternal : nsISupports
+{
+ /**
+ * An http channel can own a reference to the document URI
+ */
+ [must_use] attribute nsIURI documentURI;
+
+ /**
+ * Get the major/minor version numbers for the request
+ */
+ [must_use]
+ void getRequestVersion(out unsigned long major, out unsigned long minor);
+
+ /**
+ * Get the major/minor version numbers for the response
+ */
+ [must_use]
+ void getResponseVersion(out unsigned long major, out unsigned long minor);
+
+ /*
+ * Retrieves all security messages from the security message queue
+ * and empties the queue after retrieval
+ */
+ [noscript, must_use]
+ void takeAllSecurityMessages(in securityMessagesArray aMessages);
+
+ /**
+ * Helper method to set a cookie with a consumer-provided
+ * cookie header, _but_ using the channel's other information
+ * (URI's, prompters, date headers etc).
+ *
+ * @param aCookieHeader
+ * The cookie header to be parsed.
+ */
+ [must_use] void setCookie(in ACString aCookieHeader);
+
+ /**
+ * Returns true in case this channel is used for auth;
+ * (the response header includes 'www-authenticate').
+ */
+ [noscript, must_use] readonly attribute bool isAuthChannel;
+
+ /**
+ * This flag is set to force relevant cookies to be sent with this load
+ * even if normally they wouldn't be.
+ */
+ const unsigned long THIRD_PARTY_FORCE_ALLOW = 1 << 0;
+
+ /**
+ * When set, these flags modify the algorithm used to decide whether to
+ * send 3rd party cookies for a given channel.
+ */
+ [must_use] attribute unsigned long thirdPartyFlags;
+
+ /**
+ * This attribute was added before the "flags" above and is retained here
+ * for compatibility. When set to true, has the same effect as
+ * THIRD_PARTY_FORCE_ALLOW, described above.
+ */
+ [must_use] attribute boolean forceAllowThirdPartyCookie;
+
+ /**
+ * External handlers may set this to true to notify the channel
+ * that it is open on behalf of a download.
+ */
+ [must_use] attribute boolean channelIsForDownload;
+
+ /**
+ * The local IP address to which this channel is bound, in the
+ * format produced by PR_NetAddrToString. May be IPv4 or IPv6.
+ * Note: in the presence of NAT, this may not be the same as the
+ * address that the remote host thinks it's talking to.
+ *
+ * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+ * endpoints are not yet determined, or in any case when
+ * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+ */
+ [must_use] readonly attribute AUTF8String localAddress;
+
+ /**
+ * The local port number to which this channel is bound.
+ *
+ * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+ * endpoints are not yet determined, or in any case when
+ * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+ */
+ [must_use] readonly attribute int32_t localPort;
+
+ /**
+ * The IP address of the remote host that this channel is
+ * connected to, in the format produced by PR_NetAddrToString.
+ *
+ * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+ * endpoints are not yet determined, or in any case when
+ * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+ */
+ [must_use] readonly attribute AUTF8String remoteAddress;
+
+ /**
+ * The remote port number that this channel is connected to.
+ *
+ * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+ * endpoints are not yet determined, or in any case when
+ * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+ */
+ [must_use] readonly attribute int32_t remotePort;
+
+ /**
+ * Transfer chain of redirected cache-keys.
+ */
+ [noscript, must_use]
+ void setCacheKeysRedirectChain(in StringArray cacheKeys);
+
+ /**
+ * HTTPUpgrade allows for the use of HTTP to bootstrap another protocol
+ * via the RFC 2616 Upgrade request header in conjunction with a 101 level
+ * response. The nsIHttpUpgradeListener will have its
+ * onTransportAvailable() method invoked if a matching 101 is processed.
+ * The arguments to onTransportAvailable provide the new protocol the low
+ * level tranport streams that are no longer used by HTTP. If any errors
+ * occur during the upgrade but the original request has (potentially)
+ * already received onStopRequest, the nsIHttpUpgradeListener will have its
+ * onUpgradeFailed() method invoked instead of onTransportAvailable().
+ *
+ * The onStartRequest and onStopRequest events are still delivered and the
+ * listener gets full control over the socket if and when onTransportAvailable
+ * is delivered. Note that if onStopRequest is called with an error, no
+ * methods on the nsIHttpUpgradeListener might be invoked at all.
+ *
+ * @param aProtocolName
+ * The value of the HTTP Upgrade request header
+ * @param aListener
+ * The callback object used to handle a successful upgrade
+ */
+ [must_use] void HTTPUpgrade(in ACString aProtocolName,
+ in nsIHttpUpgradeListener aListener);
+
+ /**
+ * Enable only CONNECT to a proxy. Fails if no HTTPUpgrade listener
+ * has been defined. An ALPN header is set using the upgrade protocol.
+ *
+ * Load flags are set with INHIBIT_CACHING, LOAD_ANONYMOUS,
+ * LOAD_BYPASS_CACHE, and LOAD_BYPASS_SERVICE_WORKER.
+ *
+ * Proxy resolve flags are set with RESOLVE_PREFER_HTTPS_PROXY and
+ * RESOLVE_ALWAYS_TUNNEL.
+ */
+ [must_use] void setConnectOnly();
+
+ /**
+ * True iff the channel is CONNECT only.
+ */
+ [must_use] readonly attribute boolean onlyConnect;
+
+ /**
+ * Enable/Disable Spdy negotiation on per channel basis.
+ * The network.http.http2.enabled preference is still a pre-requisite
+ * for starting spdy.
+ */
+ [must_use] attribute boolean allowSpdy;
+
+ /**
+ * Enable/Disable HTTP3 negotiation on per channel basis.
+ * The network.http.http3.enable preference is still a pre-requisite
+ * for starting HTTP3.
+ */
+ [must_use] attribute boolean allowHttp3;
+
+ /**
+ * This attribute en/disables the timeout for the first byte of an HTTP
+ * response. Enabled by default.
+ */
+ [must_use] attribute boolean responseTimeoutEnabled;
+
+ /**
+ * If the underlying transport supports RWIN manipulation, this is the
+ * intiial window value for the channel. HTTP/2 implements this.
+ * 0 means no override from system default. Set before opening channel.
+ */
+ [must_use] attribute unsigned long initialRwin;
+
+ /**
+ * Get value of the URI passed to nsIHttpChannel.redirectTo() if any.
+ * May return null when redirectTo() has not been called.
+ */
+ [must_use] readonly attribute nsIURI apiRedirectToURI;
+
+ /**
+ * Enable/Disable use of Alternate Services with this channel.
+ * The network.http.altsvc.enabled preference is still a pre-requisite.
+ */
+ [must_use] attribute boolean allowAltSvc;
+
+ /**
+ * If true, do not use newer protocol features that might have interop problems
+ * on the Internet. Intended only for use with critical infra like the updater.
+ * default is false.
+ */
+ [must_use] attribute boolean beConservative;
+
+ /**
+ * If true, do not resolve any proxy for this request. Intended only for use with
+ * critical infra like the updater.
+ * default is false.
+ */
+ [must_use] attribute boolean bypassProxy;
+
+ /**
+ * True if channel is used by the internal trusted recursive resolver
+ * This flag places data for the request in a cache segment specific to TRR
+ */
+ [noscript, must_use] attribute boolean isTRRServiceChannel;
+
+ /**
+ * If the channel's remote IP was resolved using TRR.
+ * Is false for resources loaded from the cache or resources that have an
+ * IP literal host.
+ */
+ [must_use] readonly attribute boolean isResolvedByTRR;
+
+
+ /**
+ * The effective TRR mode used to resolve this channel.
+ * This is computed by taking the value returned by nsIRequest.getTRRMode()
+ * and the state of the TRRService. If the domain is excluded from TRR
+ * or the TRRService is disabled, the effective mode would be TRR_DISABLED_MODE
+ * even if the initial mode set on the request was TRR_ONLY_MODE.
+ */
+ [must_use] readonly attribute nsIRequest_TRRMode effectiveTRRMode;
+
+ /**
+ * If the DNS request triggered by this channel didn't use TRR, this value
+ * contains the reason why that was skipped.
+ */
+ [must_use] readonly attribute nsITRRSkipReason_value trrSkipReason;
+
+ /**
+ * True if channel is loaded by socket process.
+ */
+ [must_use] readonly attribute boolean isLoadedBySocketProcess;
+
+ /**
+ * Set to true if the channel is an OCSP check.
+ * Channels with this flag set will skip TRR in mode3 (because the circular
+ * dependency with checking OCSP for the TRR server will cause a failure)
+ */
+ [must_use] attribute boolean isOCSP;
+
+ /**
+ * An opaque flags for non-standard behavior of the TLS system.
+ * It is unlikely this will need to be set outside of telemetry studies
+ * relating to the TLS implementation.
+ */
+ [must_use] attribute unsigned long tlsFlags;
+
+ [must_use] readonly attribute PRTime lastModifiedTime;
+
+ /**
+ * Set by nsCORSListenerProxy if credentials should be included in
+ * cross-origin requests. false indicates "same-origin", users should still
+ * check flag LOAD_ANONYMOUS!
+ */
+ [must_use] attribute boolean corsIncludeCredentials;
+
+ /**
+ * Set by nsCORSListenerProxy to indicate CORS load type. Defaults to CORS_MODE_NO_CORS.
+ */
+ [must_use, noscript] attribute RequestMode requestMode;
+
+ const unsigned long REDIRECT_MODE_FOLLOW = 0;
+ const unsigned long REDIRECT_MODE_ERROR = 1;
+ const unsigned long REDIRECT_MODE_MANUAL = 2;
+ /**
+ * Set to indicate Request.redirect mode exposed during ServiceWorker
+ * interception. No policy enforcement is performed by the channel for this
+ * value.
+ */
+ [must_use] attribute unsigned long redirectMode;
+
+ const unsigned long FETCH_CACHE_MODE_DEFAULT = 0;
+ const unsigned long FETCH_CACHE_MODE_NO_STORE = 1;
+ const unsigned long FETCH_CACHE_MODE_RELOAD = 2;
+ const unsigned long FETCH_CACHE_MODE_NO_CACHE = 3;
+ const unsigned long FETCH_CACHE_MODE_FORCE_CACHE = 4;
+ const unsigned long FETCH_CACHE_MODE_ONLY_IF_CACHED = 5;
+ /**
+ * Set to indicate Request.cache mode, which simulates the fetch API
+ * semantics, and is also used for exposing this value to the Web page
+ * during service worker interception.
+ */
+ [must_use] attribute unsigned long fetchCacheMode;
+
+ /**
+ * The URI of the top-level window that's associated with this channel.
+ */
+ [must_use] readonly attribute nsIURI topWindowURI;
+
+ /**
+ * Set top-level window URI to this channel only when the topWindowURI
+ * is null and there is no window associated to this channel.
+ * Note that the current usage of this method is only for xpcshell test.
+ */
+ [must_use] void setTopWindowURIIfUnknown(in nsIURI topWindowURI);
+
+ /**
+ * Read the proxy URI, which, if non-null, will be used to resolve
+ * proxies for this channel.
+ */
+ [must_use] readonly attribute nsIURI proxyURI;
+
+ /**
+ * Make cross-origin CORS loads happen with a CORS preflight, and specify
+ * the CORS preflight parameters.
+ */
+ [noscript, notxpcom, nostdcall]
+ void setCorsPreflightParameters(in CStringArrayRef unsafeHeaders,
+ in boolean shouldStripRequestBodyHeader);
+
+ [noscript, notxpcom, nostdcall]
+ void setAltDataForChild(in boolean aIsForChild);
+
+ /**
+ * Prevent the use of alt-data cache for this request. Use by the
+ * extension StreamFilter class to force use of the regular cache.
+ */
+ [noscript, notxpcom, nostdcall]
+ void disableAltDataCache();
+
+ /**
+ * When set to true, the channel will not pop any authentication prompts up
+ * to the user. When provided or cached credentials lead to an
+ * authentication failure, that failure will be propagated to the channel
+ * listener. Must be called before opening the channel, otherwise throws.
+ */
+ [infallible]
+ attribute boolean blockAuthPrompt;
+
+ /**
+ * Set to indicate Request.integrity.
+ */
+ [must_use] attribute AString integrityMetadata;
+
+ /**
+ * The connection info's hash key. We use it to test connection separation.
+ */
+ [must_use] readonly attribute ACString connectionInfoHashKey;
+
+ /**
+ * If this channel was created as the result of a redirect, then this
+ * value will reflect the redirect flags passed to the
+ * SetupReplacementChannel() method.
+ */
+ [noscript, infallible]
+ attribute unsigned long lastRedirectFlags;
+
+ // This is use to determine the duration since navigation started.
+ [noscript] attribute TimeStamp navigationStartTimeStamp;
+
+ /**
+ * Cancel a channel because we have determined that it needs to be blocked
+ * for safe-browsing protection. This is an internal API that is meant to
+ * be called by the channel classifier. Please DO NOT use this API if you
+ * don't know whether you should be using it.
+ */
+ [noscript] void cancelByURLClassifier(in nsresult aErrorCode);
+
+ /**
+ * The channel will be loaded over IPv6, disabling IPv4.
+ */
+ void setIPv4Disabled();
+
+ /**
+ * The channel will be loaded over IPv4, disabling IPv6.
+ */
+ void setIPv6Disabled();
+
+ /**
+ * Returns a cached CrossOriginOpenerPolicy that is computed just before we
+ * determine if there is a policy mismatch.
+ * @throws NS_ERROR_NOT_AVAILABLE if it has not been computed yet
+ */
+ readonly attribute nsILoadInfo_CrossOriginOpenerPolicy crossOriginOpenerPolicy;
+
+ /**
+ * Called during onStartRequest to compute the cross-origin-opener-policy
+ * for a given channel.
+ */
+ [noscript]
+ nsILoadInfo_CrossOriginOpenerPolicy computeCrossOriginOpenerPolicy(
+ in nsILoadInfo_CrossOriginOpenerPolicy aInitiatorPolicy);
+
+ [noscript]
+ bool hasCrossOriginOpenerPolicyMismatch();
+
+ [noscript]
+ nsILoadInfo_CrossOriginEmbedderPolicy getResponseEmbedderPolicy(in boolean aIsOriginTrialCoepCredentiallessEnabled);
+
+ [noscript, notxpcom, nostdcall]
+ void DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();
+
+ /**
+ * If this is called, this channel's transaction will not be dispatched
+ * until the HTTPSSVC record is available.
+ */
+ [must_use] void setWaitForHTTPSSVCRecord();
+
+ /**
+ * This attribute indicates if the channel has support for HTTP3
+ */
+ [must_use] readonly attribute boolean supportsHTTP3;
+
+ /**
+ * This attribute indicates if the HTTPS RR is used for this channel.
+ */
+ [must_use] readonly attribute boolean hasHTTPSRR;
+
+ /**
+ * Set Early Hint Observer.
+ */
+ [must_use] void setEarlyHintObserver(in nsIEarlyHintObserver aObserver);
+
+ /*
+ * id of the EarlyHintPreloader to connect back from PreloadService to
+ * EarlyHintPreloader.
+ */
+ [must_use] attribute unsigned long long earlyHintPreloaderId;
+
+ [notxpcom, nostdcall] void setConnectionInfo(in nsHttpConnectionInfo aInfo);
+
+ /*
+ * This attribute indicates if the channel was loaded via Proxy.
+ */
+ [must_use] readonly attribute boolean isProxyUsed;
+
+ /**
+ * Set mWebTransportSessionEventListener.
+ */
+ [must_use] void setWebTransportSessionEventListener(
+ in WebTransportSessionEventListener aListener);
+
+ /**
+ * This attribute indicates the type of Link header in the received
+ * 103 response.
+ */
+ [must_use] attribute unsigned long earlyHintLinkType;
+
+ /**
+ * Indicates whether the User-Agent request header has been modified since
+ * the channel was created. This value will be used to decide if we need to
+ * recalculate the User-Agent header for fingerprinting protection. We won't
+ * recalculate the User-Agent header if it has been modified to preserve the
+ * overridden header value.
+ */
+ [must_use] attribute boolean isUserAgentHeaderModified;
+};
diff --git a/netwerk/protocol/http/nsIHttpHeaderVisitor.idl b/netwerk/protocol/http/nsIHttpHeaderVisitor.idl
new file mode 100644
index 0000000000..b09974de5d
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpHeaderVisitor.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Implement this interface to visit http headers.
+ */
+[scriptable, function, uuid(35412859-b9d9-423c-8866-2d4559fdd2be)]
+interface nsIHttpHeaderVisitor : nsISupports
+{
+ /**
+ * Called by the nsIHttpChannel implementation when visiting request and
+ * response headers.
+ *
+ * @param aHeader
+ * the header being visited.
+ * @param aValue
+ * the header value (possibly a comma delimited list).
+ *
+ * @throw any exception to terminate enumeration
+ */
+ [must_use] void visitHeader(in ACString aHeader, in ACString aValue);
+};
diff --git a/netwerk/protocol/http/nsIHttpProtocolHandler.idl b/netwerk/protocol/http/nsIHttpProtocolHandler.idl
new file mode 100644
index 0000000000..d6a6f8a0c1
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpProtocolHandler.idl
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProxiedProtocolHandler.idl"
+
+%{C++
+namespace mozilla {
+namespace net {
+class HSTSDataCallbackWrapper;
+}
+}
+%}
+
+native HSTSDataCallbackWrapperAlreadyAddRefed(RefPtr<mozilla::net::HSTSDataCallbackWrapper>);
+
+[scriptable, builtinclass, uuid(c48126d9-2ddd-485b-a51a-378e917e75f8)]
+interface nsIHttpProtocolHandler : nsIProxiedProtocolHandler
+{
+ /**
+ * Get the HTTP advertised user agent string.
+ */
+ [must_use] readonly attribute ACString userAgent;
+
+ /**
+ * Get the HTTP advertised user agent string.
+ */
+ [must_use] readonly attribute ACString rfpUserAgent;
+
+ /**
+ * Get the application name.
+ *
+ * @return The name of this application (eg. "Mozilla").
+ */
+ [must_use] readonly attribute ACString appName;
+
+ /**
+ * Get the application version string.
+ *
+ * @return The complete version (major and minor) string. (eg. "5.0")
+ */
+ [must_use] readonly attribute ACString appVersion;
+
+ /**
+ * Get the current platform.
+ *
+ * @return The platform this application is running on
+ * (eg. "Windows", "Macintosh", "X11")
+ */
+ [must_use] readonly attribute ACString platform;
+
+ /**
+ * Get the current oscpu.
+ *
+ * @return The oscpu this application is running on
+ */
+ [must_use] readonly attribute ACString oscpu;
+
+ /**
+ * Get the application comment misc portion.
+ */
+ [must_use] readonly attribute ACString misc;
+
+ /**
+ * Get the Alt-Svc cache keys (used for testing).
+ */
+ [must_use] readonly attribute Array<ACString> altSvcCacheKeys;
+
+ /**
+ * Get the auth cache keys (used for testing).
+ */
+ [must_use] readonly attribute Array<ACString> authCacheKeys;
+
+ /**
+ * This function is used to ensure HSTS data storage is ready to read after
+ * the returned promise is resolved.
+ * Note that this function should only used for testing.
+ * See bug 1521729 for more details.
+ */
+ [implicit_jscontext]
+ Promise EnsureHSTSDataReady();
+
+ /**
+ * A C++ friendly version of EnsureHSTSDataReady
+ */
+ [noscript]
+ void EnsureHSTSDataReadyNative(in HSTSDataCallbackWrapperAlreadyAddRefed aCallback);
+
+ /*
+ * Clears the CORS preflight cache.
+ */
+ void clearCORSPreflightCache();
+};
+
+%{C++
+// ----------- Categories -----------
+/**
+ * At initialization time, the HTTP handler will initialize each service
+ * registered under this category:
+ */
+#define NS_HTTP_STARTUP_CATEGORY "http-startup-category"
+
+// ----------- Observer topics -----------
+/**
+ * nsIObserver notification corresponding to startup category. Services
+ * registered under the startup category will receive this observer topic at
+ * startup if they implement nsIObserver. The "subject" of the notification
+ * is the nsIHttpProtocolHandler instance.
+ */
+#define NS_HTTP_STARTUP_TOPIC "http-startup"
+
+/**
+ * Called when asyncOpen synchronously failes e.g. because of any synchronously
+ * performed security checks. This only fires on the child process, but if
+ * needed can be implemented also on the parent process.
+ */
+#define NS_HTTP_ON_FAILED_OPENING_REQUEST_TOPIC "http-on-failed-opening-request"
+
+ /**
+ * This observer topic is notified when an HTTP channel is opened.
+ * It is similar to http-on-modify-request, except that
+ * 1) The notification is guaranteed to occur before on-modify-request, during
+ * the AsyncOpen call itself.
+ * 2) It only occurs for the initial open of a channel, not for internal
+ * asyncOpens that happen during redirects, etc.
+ * 3) Some information (most notably nsIProxiedChannel.proxyInfo) may not be set
+ * on the channel object yet.
+ *
+ * The "subject" of the notification is the nsIHttpChannel instance.
+ *
+ * Generally the 'http-on-modify-request' notification is preferred unless the
+ * synchronous, during-asyncOpen behavior that this notification provides is
+ * required.
+ */
+#define NS_HTTP_ON_OPENING_REQUEST_TOPIC "http-on-opening-request"
+
+ /**
+ * This observer topic is notified when a document channel is opened.
+ * It is similar to http-on-opening-request.
+ */
+#define NS_DOCUMENT_ON_OPENING_REQUEST_TOPIC "document-on-opening-request"
+
+/**
+ * Before an HTTP request is sent to the server, this observer topic is
+ * notified. The observer of this topic can then choose to set any additional
+ * headers for this request before the request is actually sent to the server.
+ * The "subject" of the notification is the nsIHttpChannel instance.
+ */
+#define NS_HTTP_ON_MODIFY_REQUEST_TOPIC "http-on-modify-request"
+
+/**
+ * Same as http-on-modify-request, but called before the cookie header is set on
+ * the channel.
+ * This allows observers to set cookies via the cookie service to be included in
+ * the request header.
+ * http-on-modify-request is too late for this use-case since the cookie header
+ * has already been populated at that point.
+ */
+#define NS_HTTP_ON_MODIFY_REQUEST_BEFORE_COOKIES_TOPIC "http-on-modify-request-before-cookies"
+
+/**
+ * Before an HTTP request is sent to the server via a document channel this
+ * observer topic is notified.
+ * It is similar to http-on-modify-request.
+*/
+#define NS_DOCUMENT_ON_MODIFY_REQUEST_TOPIC "document-on-modify-request"
+
+/**
+ * Before an HTTP connection to the server is created, this observer topic is
+ * notified. This observer happens after HSTS upgrades, etc. are set, providing
+ * access to the full set of request headers. The observer of this topic can
+ * choose to set any additional headers for this request before the request is
+ * actually sent to the server. The "subject" of the notification is the
+ * nsIHttpChannel instance.
+ */
+#define NS_HTTP_ON_BEFORE_CONNECT_TOPIC "http-on-before-connect"
+
+/**
+ * After an HTTP server response is received, this observer topic is notified.
+ * The observer of this topic can interrogate the response. The "subject" of
+ * the notification is the nsIHttpChannel instance.
+ */
+#define NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC "http-on-examine-response"
+
+/**
+ * The observer of this topic is notified after the received HTTP response
+ * is merged with data from the browser cache. This means that, among other
+ * things, the Content-Type header will be set correctly.
+ */
+#define NS_HTTP_ON_EXAMINE_MERGED_RESPONSE_TOPIC "http-on-examine-merged-response"
+
+/**
+ * The observer of this topic is notified about a background revalidation that
+ * started by hitting a request that fell into stale-while-revalidate window.
+ * This notification points to the channel that performed the revalidation and
+ * after this notification the cache entry has been validated or updated.
+ */
+#define NS_HTTP_ON_BACKGROUND_REVALIDATION "http-on-background-revalidation"
+
+/**
+ * The observer of this topic is notified before data is read from the cache.
+ * The notification is sent if and only if there is no network communication
+ * at all.
+ */
+#define NS_HTTP_ON_EXAMINE_CACHED_RESPONSE_TOPIC "http-on-examine-cached-response"
+
+/**
+ * This topic is notified for every http channel right after it called
+ * OnStopRequest on its listener, regardless whether it was finished
+ * successfully, failed or has been canceled.
+ */
+#define NS_HTTP_ON_STOP_REQUEST_TOPIC "http-on-stop-request"
+
+%}
diff --git a/netwerk/protocol/http/nsIObliviousHttp.idl b/netwerk/protocol/http/nsIObliviousHttp.idl
new file mode 100644
index 0000000000..84bc30d640
--- /dev/null
+++ b/netwerk/protocol/http/nsIObliviousHttp.idl
@@ -0,0 +1,78 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIURI;
+
+[scriptable, builtinclass, uuid(f2a4aaa4-046a-439e-beef-893b15a90cff)]
+interface nsIObliviousHttpClientResponse : nsISupports {
+ // Decrypt an encrypted response ("enc_response" in the RFC).
+ // Can only be called once.
+ Array<octet> decapsulate(in Array<octet> encResponse);
+};
+
+[scriptable, builtinclass, uuid(403af7f9-4a76-49fc-a622-38d6ba3ee496)]
+interface nsIObliviousHttpClientRequest : nsISupports {
+ // The encrypted request ("enc_request" in the RFC).
+ readonly attribute Array<octet> encRequest;
+ // The context for decrypting the eventual response.
+ readonly attribute nsIObliviousHttpClientResponse response;
+};
+
+[scriptable, builtinclass, uuid(105deb62-45b4-407a-b330-550433279111)]
+interface nsIObliviousHttpServerResponse : nsISupports {
+ readonly attribute Array<octet> request;
+
+ Array<octet> encapsulate(in Array<octet> response);
+};
+
+[scriptable, builtinclass, uuid(fb1abc56-b525-4e1a-a4c6-341a9b32084e)]
+interface nsIObliviousHttpServer : nsISupports {
+ readonly attribute Array<octet> encodedConfig;
+
+ nsIObliviousHttpServerResponse decapsulate(in Array<octet> encRequest);
+};
+
+
+// IDL bindings for the rust implementation of oblivious http.
+// Client code will generally call `encapsulateRequest` given an encoded
+// oblivious gateway key configuration and an encoded binary http request.
+// This function returns a nsIObliviousHttpClientRequest. The `encRequest`
+// attribute of that object is the encapsulated request that can be sent to an
+// oblivious relay to be forwarded on to the oblivious gateway and then to the
+// actual target. The `response` attribute is used to decapsulate the response
+// returned by the oblivious relay.
+// For tests, this implementation provides a facility for decapsulating
+// requests and encapsulating responses. Call `server` to get an
+// `nsIObliviousHttpServer`, which has an attribute `encodedConfig` for use
+// with `encapsulateRequest`. It also has a function `decapsulate`, which
+// decapsulates an encapsulated client request and returns an
+// `nsIObliviousHttpServerResponse`. This object can `encapsulate` a response,
+// which the `nsIObliviousHttpClientResponse` from the original request should
+// be able to `decapsulate`.
+// Thread safety: nsIObliviousHttp may be used on any thread, but any objects
+// created by it must only be used on the threads they are created on.
+[scriptable, builtinclass, uuid(d581149e-3319-4563-b95e-46c64af5c4e8)]
+interface nsIObliviousHttp : nsISupports
+{
+ nsIObliviousHttpClientRequest encapsulateRequest(
+ in Array<octet> encodedConfig,
+ in Array<octet> request);
+
+ nsIObliviousHttpServer server();
+};
+
+[scriptable, builtinclass, uuid(b1f08d56-fca6-4290-9500-d5168dc9d8c3)]
+interface nsIObliviousHttpService : nsISupports
+{
+ nsIChannel newChannel(in nsIURI relayURI, in nsIURI targetURI, in Array<octet> encodedConfig);
+
+ void getTRRSettings(out nsIURI relayURI, out Array<octet> encodedConfig);
+
+ // Clears the config
+ void clearTRRConfig();
+};
diff --git a/netwerk/protocol/http/nsIObliviousHttpChannel.idl b/netwerk/protocol/http/nsIObliviousHttpChannel.idl
new file mode 100644
index 0000000000..0373693294
--- /dev/null
+++ b/netwerk/protocol/http/nsIObliviousHttpChannel.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIHttpChannel.idl"
+
+/**
+ * nsIObliviousHttpChannel
+ *
+ * This interface allows consumers to differentiate between the
+ * relayChannel request that transports the OHTTP payload
+ * and the virtual OHTTP channel represented by the
+ * nsIObliviousHttpChannel implementation.
+ */
+[builtinclass, scriptable, uuid(f829f761-0744-4d1c-9c2d-8931c22ae8d5)]
+interface nsIObliviousHttpChannel: nsIHttpChannel
+{
+ /**
+ * Returns the channel used to transport the binary serialization
+ * of the request and response to and from the OHTTP relay.
+ * This can be useful to determine if an HTTP status code or failure
+ * is due to the relay or the gateway response.
+ */
+ readonly attribute nsIHttpChannel relayChannel;
+};
diff --git a/netwerk/protocol/http/nsIRaceCacheWithNetwork.idl b/netwerk/protocol/http/nsIRaceCacheWithNetwork.idl
new file mode 100644
index 0000000000..93f361b198
--- /dev/null
+++ b/netwerk/protocol/http/nsIRaceCacheWithNetwork.idl
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This holds methods used to race the cache with the network for a specific
+ * channel. This interface is was designed with nsHttpChannel in mind, and it's
+ * expected this will be the only class implementing it.
+ */
+[scriptable, builtinclass, uuid(4d963475-8b16-4c58-b804-8a23d49436c5)]
+interface nsIRaceCacheWithNetwork : nsISupports
+{
+
+ /****************************************************************************
+ * TEST ONLY: The following methods are for testing purposes only. Do not use
+ * them to do anything important in your code.
+ ****************************************************************************
+
+ /**
+ * Triggers network activity after given timeout. If timeout is 0, network
+ * activity is triggered immediately if asyncOpen has already been called.
+ * Otherwise the delayed timer will be set when the normal call to
+ * TriggerNetwork is made. If the cache.asyncOpenURI callbacks have already
+ * been called, the network activity may have already been triggered
+ * or the content may have already been delivered from the cache, so this
+ * operation will have no effect.
+ *
+ * @param timeout
+ * - the delay in milliseconds until the network will be triggered.
+ */
+ void test_triggerNetwork(in long timeout);
+
+ /**
+ * Normally a HTTP channel would immediately call AsyncOpenURI leading to the
+ * cache storage to lookup the cache entry and return it. In order to
+ * simmulate real life conditions where fetching a cache entry takes a long
+ * time, we set a timer to delay the operation.
+ * Can only be called on the main thread.
+ *
+ * @param timeout
+ * - the delay in milliseconds until the cache open will be triggered.
+ */
+ void test_delayCacheEntryOpeningBy(in long timeout);
+
+ /**
+ * Immediatelly triggers AsyncOpenURI if the timer hasn't fired.
+ * Can only be called on the main thread.
+ * This is only called in tests to reliably trigger the opening of the cache
+ * entry.
+ * @throws NS_ERROR_NOT_AVAILABLE if opening the cache wasn't delayed.
+ */
+ void test_triggerDelayedOpenCacheEntry();
+};
diff --git a/netwerk/protocol/http/nsITlsHandshakeListener.idl b/netwerk/protocol/http/nsITlsHandshakeListener.idl
new file mode 100644
index 0000000000..0d5806be24
--- /dev/null
+++ b/netwerk/protocol/http/nsITlsHandshakeListener.idl
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[uuid(b4bbe824-ec4c-48be-9a40-6a7339347f40)]
+interface nsITlsHandshakeCallbackListener : nsISupports {
+ [noscript] void handshakeDone();
+ [noscript] void certVerificationDone();
+ [noscript] void clientAuthCertificateSelected();
+};
diff --git a/netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl b/netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl
new file mode 100644
index 0000000000..fa90891172
--- /dev/null
+++ b/netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ For parsing JSON from http://httpwg.org/http-extensions/opsec.html
+*/
+
+#include "nsISupports.idl"
+
+%{C++
+#define NS_WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID "@mozilla.org/network/well-known-opportunistic-utils;1"
+%}
+
+[scriptable, uuid(b4f96c89-5238-450c-8bda-e12c26f1d150)]
+interface nsIWellKnownOpportunisticUtils : nsISupports
+{
+ [must_use] void verify(in ACString aJSON,
+ in ACString aOrigin);
+
+ [must_use] readonly attribute bool valid;
+};
diff --git a/netwerk/protocol/http/nsServerTiming.cpp b/netwerk/protocol/http/nsServerTiming.cpp
new file mode 100644
index 0000000000..b1b2fe54d1
--- /dev/null
+++ b/netwerk/protocol/http/nsServerTiming.cpp
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsServerTiming.h"
+
+#include "nsHttp.h"
+
+NS_IMPL_ISUPPORTS(nsServerTiming, nsIServerTiming)
+
+NS_IMETHODIMP
+nsServerTiming::GetName(nsACString& aName) {
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsServerTiming::GetDuration(double* aDuration) {
+ *aDuration = mDuration;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsServerTiming::GetDescription(nsACString& aDescription) {
+ aDescription.Assign(mDescription);
+ return NS_OK;
+}
+
+namespace mozilla {
+namespace net {
+
+static double ParseDouble(const nsACString& aString) {
+ nsresult rv;
+ double val = PromiseFlatCString(aString).ToDouble(&rv);
+ return NS_FAILED(rv) ? 0.0f : val;
+}
+
+void ServerTimingParser::Parse() {
+ // https://w3c.github.io/server-timing/#the-server-timing-header-field
+ // Server-Timing = #server-timing-metric
+ // server-timing-metric = metric-name *( OWS ";" OWS server-timing-param
+ // ) metric-name = token server-timing-param =
+ // server-timing-param-name OWS "=" OWS
+ // server-timing-param-value
+ // server-timing-param-name = token
+ // server-timing-param-value = token / quoted-string
+
+ ParsedHeaderValueListList parsedHeader(mValue, false);
+ for (uint32_t index = 0; index < parsedHeader.mValues.Length(); ++index) {
+ if (parsedHeader.mValues[index].mValues.IsEmpty()) {
+ continue;
+ }
+
+ // According to spec, the first ParsedHeaderPair's name is metric-name.
+ RefPtr<nsServerTiming> timingHeader = new nsServerTiming();
+ mServerTimingHeaders.AppendElement(timingHeader);
+ timingHeader->SetName(parsedHeader.mValues[index].mValues[0].mName);
+
+ if (parsedHeader.mValues[index].mValues.Length() == 1) {
+ continue;
+ }
+
+ // Try to find duration and description from the rest ParsedHeaderPairs.
+ bool foundDuration = false;
+ bool foundDescription = false;
+ for (uint32_t pairIndex = 1;
+ pairIndex < parsedHeader.mValues[index].mValues.Length();
+ ++pairIndex) {
+ nsDependentCSubstring& currentName =
+ parsedHeader.mValues[index].mValues[pairIndex].mName;
+ nsDependentCSubstring& currentValue =
+ parsedHeader.mValues[index].mValues[pairIndex].mValue;
+
+ // We should only take the value from the first
+ // occurrence of server-timing-param-name ("dur" and "desc").
+ // This is true whether or not the value makes any sense (or, indeed, if
+ // there even is a value).
+ if (currentName.LowerCaseEqualsASCII("dur") && !foundDuration) {
+ if (currentValue.BeginReading()) {
+ timingHeader->SetDuration(ParseDouble(currentValue));
+ } else {
+ timingHeader->SetDuration(0.0);
+ }
+ foundDuration = true;
+ } else if (currentName.LowerCaseEqualsASCII("desc") &&
+ !foundDescription) {
+ if (!currentValue.IsEmpty()) {
+ timingHeader->SetDescription(currentValue);
+ } else {
+ timingHeader->SetDescription(""_ns);
+ }
+ foundDescription = true;
+ }
+
+ if (foundDuration && foundDescription) {
+ break;
+ }
+ }
+ }
+}
+
+nsTArray<nsCOMPtr<nsIServerTiming>>&&
+ServerTimingParser::TakeServerTimingHeaders() {
+ return std::move(mServerTimingHeaders);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsServerTiming.h b/netwerk/protocol/http/nsServerTiming.h
new file mode 100644
index 0000000000..2db1887b80
--- /dev/null
+++ b/netwerk/protocol/http/nsServerTiming.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsServerTiming_h__
+#define nsServerTiming_h__
+
+#include "nsITimedChannel.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsServerTiming final : public nsIServerTiming {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERVERTIMING
+
+ nsServerTiming() = default;
+
+ void SetName(const nsACString& aName) { mName = aName; }
+
+ void SetDuration(double aDuration) { mDuration = aDuration; }
+
+ void SetDescription(const nsACString& aDescription) {
+ mDescription = aDescription;
+ }
+
+ private:
+ virtual ~nsServerTiming() = default;
+
+ nsCString mName;
+ double mDuration = 0;
+ nsCString mDescription;
+};
+
+namespace mozilla {
+namespace net {
+
+class ServerTimingParser {
+ public:
+ explicit ServerTimingParser(const nsCString& value) : mValue(value) {}
+ void Parse();
+ nsTArray<nsCOMPtr<nsIServerTiming>>&& TakeServerTimingHeaders();
+
+ private:
+ nsCString mValue;
+ nsTArray<nsCOMPtr<nsIServerTiming>> mServerTimingHeaders;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/oblivious_http/Cargo.toml b/netwerk/protocol/http/oblivious_http/Cargo.toml
new file mode 100644
index 0000000000..e15cd2f74f
--- /dev/null
+++ b/netwerk/protocol/http/oblivious_http/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "oblivious_http"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+nserror = { path = "../../../../xpcom/rust/nserror" }
+ohttp = { version = "0.3", default-features = false, features = ["gecko", "nss", "client", "server"] }
+rand = "0.8"
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
+xpcom = { path = "../../../../xpcom/rust/xpcom" }
diff --git a/netwerk/protocol/http/oblivious_http/src/lib.rs b/netwerk/protocol/http/oblivious_http/src/lib.rs
new file mode 100644
index 0000000000..948139ab68
--- /dev/null
+++ b/netwerk/protocol/http/oblivious_http/src/lib.rs
@@ -0,0 +1,188 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate nserror;
+extern crate ohttp;
+extern crate rand;
+extern crate thin_vec;
+#[macro_use]
+extern crate xpcom;
+
+use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_NOT_AVAILABLE, NS_OK};
+use ohttp::hpke::{Aead, Kdf, Kem};
+use ohttp::{
+ ClientRequest, ClientResponse, KeyConfig, KeyId, Server, ServerResponse, SymmetricSuite,
+};
+use thin_vec::ThinVec;
+use xpcom::interfaces::{
+ nsIObliviousHttpClientRequest, nsIObliviousHttpClientResponse, nsIObliviousHttpServer,
+ nsIObliviousHttpServerResponse,
+};
+use xpcom::{xpcom_method, RefPtr};
+
+use std::cell::RefCell;
+
+#[xpcom(implement(nsIObliviousHttpClientResponse), atomic)]
+struct ObliviousHttpClientResponse {
+ response: RefCell<Option<ClientResponse>>,
+}
+
+impl ObliviousHttpClientResponse {
+ xpcom_method!(decapsulate => Decapsulate(enc_response: *const ThinVec<u8>) -> ThinVec<u8>);
+ fn decapsulate(&self, enc_response: &ThinVec<u8>) -> Result<ThinVec<u8>, nsresult> {
+ let response = self
+ .response
+ .borrow_mut()
+ .take()
+ .ok_or(NS_ERROR_NOT_AVAILABLE)?;
+ let decapsulated = response
+ .decapsulate(enc_response)
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(decapsulated.into_iter().collect())
+ }
+}
+
+#[xpcom(implement(nsIObliviousHttpClientRequest), atomic)]
+struct ObliviousHttpClientRequest {
+ enc_request: Vec<u8>,
+ response: RefPtr<nsIObliviousHttpClientResponse>,
+}
+
+impl ObliviousHttpClientRequest {
+ xpcom_method!(get_enc_request => GetEncRequest() -> ThinVec<u8>);
+ fn get_enc_request(&self) -> Result<ThinVec<u8>, nsresult> {
+ Ok(self.enc_request.clone().into_iter().collect())
+ }
+
+ xpcom_method!(get_response => GetResponse() -> *const nsIObliviousHttpClientResponse);
+ fn get_response(&self) -> Result<RefPtr<nsIObliviousHttpClientResponse>, nsresult> {
+ Ok(self.response.clone())
+ }
+}
+
+#[xpcom(implement(nsIObliviousHttpServerResponse), atomic)]
+struct ObliviousHttpServerResponse {
+ request: Vec<u8>,
+ server_response: RefCell<Option<ServerResponse>>,
+}
+
+impl ObliviousHttpServerResponse {
+ xpcom_method!(get_request => GetRequest() -> ThinVec<u8>);
+ fn get_request(&self) -> Result<ThinVec<u8>, nsresult> {
+ Ok(self.request.clone().into_iter().collect())
+ }
+
+ xpcom_method!(encapsulate => Encapsulate(response: *const ThinVec<u8>) -> ThinVec<u8>);
+ fn encapsulate(&self, response: &ThinVec<u8>) -> Result<ThinVec<u8>, nsresult> {
+ let server_response = self
+ .server_response
+ .borrow_mut()
+ .take()
+ .ok_or(NS_ERROR_NOT_AVAILABLE)?;
+ Ok(server_response
+ .encapsulate(response)
+ .map_err(|_| NS_ERROR_FAILURE)?
+ .into_iter()
+ .collect())
+ }
+}
+
+#[xpcom(implement(nsIObliviousHttpServer), atomic)]
+struct ObliviousHttpServer {
+ server: RefCell<Server>,
+}
+
+impl ObliviousHttpServer {
+ xpcom_method!(get_encoded_config => GetEncodedConfig() -> ThinVec<u8>);
+ fn get_encoded_config(&self) -> Result<ThinVec<u8>, nsresult> {
+ let server = self.server.borrow_mut();
+ Ok(server
+ .config()
+ .encode()
+ .map_err(|_| NS_ERROR_FAILURE)?
+ .into_iter()
+ .collect())
+ }
+
+ xpcom_method!(decapsulate => Decapsulate(enc_request: *const ThinVec<u8>) -> *const nsIObliviousHttpServerResponse);
+ fn decapsulate(
+ &self,
+ enc_request: &ThinVec<u8>,
+ ) -> Result<RefPtr<nsIObliviousHttpServerResponse>, nsresult> {
+ let mut server = self.server.borrow_mut();
+ let (request, server_response) = server
+ .decapsulate(enc_request)
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ let oblivious_http_server_response =
+ ObliviousHttpServerResponse::allocate(InitObliviousHttpServerResponse {
+ request,
+ server_response: RefCell::new(Some(server_response)),
+ });
+ oblivious_http_server_response
+ .query_interface::<nsIObliviousHttpServerResponse>()
+ .ok_or(NS_ERROR_FAILURE)
+ }
+}
+
+#[xpcom(implement(nsIObliviousHttp), atomic)]
+struct ObliviousHttp {}
+
+impl ObliviousHttp {
+ xpcom_method!(encapsulate_request => EncapsulateRequest(encoded_config: *const ThinVec<u8>,
+ request: *const ThinVec<u8>) -> *const nsIObliviousHttpClientRequest);
+ fn encapsulate_request(
+ &self,
+ encoded_config: &ThinVec<u8>,
+ request: &ThinVec<u8>,
+ ) -> Result<RefPtr<nsIObliviousHttpClientRequest>, nsresult> {
+ ohttp::init();
+
+ let client = ClientRequest::new(encoded_config).map_err(|_| NS_ERROR_FAILURE)?;
+ let (enc_request, response) = client.encapsulate(request).map_err(|_| NS_ERROR_FAILURE)?;
+ let oblivious_http_client_response =
+ ObliviousHttpClientResponse::allocate(InitObliviousHttpClientResponse {
+ response: RefCell::new(Some(response)),
+ });
+ let response = oblivious_http_client_response
+ .query_interface::<nsIObliviousHttpClientResponse>()
+ .ok_or(NS_ERROR_FAILURE)?;
+ let oblivious_http_client_request =
+ ObliviousHttpClientRequest::allocate(InitObliviousHttpClientRequest {
+ enc_request,
+ response,
+ });
+ oblivious_http_client_request
+ .query_interface::<nsIObliviousHttpClientRequest>()
+ .ok_or(NS_ERROR_FAILURE)
+ }
+
+ xpcom_method!(server => Server() -> *const nsIObliviousHttpServer);
+ fn server(&self) -> Result<RefPtr<nsIObliviousHttpServer>, nsresult> {
+ ohttp::init();
+
+ let key_id: KeyId = rand::random::<u8>();
+ let kem: Kem = Kem::X25519Sha256;
+ let symmetric = vec![
+ SymmetricSuite::new(Kdf::HkdfSha256, Aead::Aes128Gcm),
+ SymmetricSuite::new(Kdf::HkdfSha256, Aead::ChaCha20Poly1305),
+ ];
+ let key_config = KeyConfig::new(key_id, kem, symmetric).map_err(|_| NS_ERROR_FAILURE)?;
+ let server = Server::new(key_config).map_err(|_| NS_ERROR_FAILURE)?;
+ let oblivious_http_server = ObliviousHttpServer::allocate(InitObliviousHttpServer {
+ server: RefCell::new(server),
+ });
+ oblivious_http_server
+ .query_interface::<nsIObliviousHttpServer>()
+ .ok_or(NS_ERROR_FAILURE)
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn oblivious_http_constructor(
+ iid: *const xpcom::nsIID,
+ result: *mut *mut xpcom::reexports::libc::c_void,
+) -> nserror::nsresult {
+ let oblivious_http = ObliviousHttp::allocate(InitObliviousHttp {});
+ unsafe { oblivious_http.QueryInterface(iid, result) }
+}
diff --git a/netwerk/protocol/http/oblivious_http/src/oblivious_http.h b/netwerk/protocol/http/oblivious_http/src/oblivious_http.h
new file mode 100644
index 0000000000..176b4909c1
--- /dev/null
+++ b/netwerk/protocol/http/oblivious_http/src/oblivious_http.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _oblivious_http_h_
+#define _oblivious_http_h_
+
+#include "nsISupportsUtils.h" // for nsresult, etc.
+
+// {d581149e-3319-4563-b95e-46c64af5c4e8}
+#define NS_OBLIVIOUS_HTTP_CID \
+ { \
+ 0xd581149e, 0x3319, 0x4563, { \
+ 0xb9, 0x5e, 0x46, 0xc6, 0x4a, 0xf5, 0xc4, 0xe8 \
+ } \
+ }
+
+extern "C" {
+nsresult oblivious_http_constructor(REFNSIID iid, void** result);
+};
+
+#endif // _oblivious_http_h_
diff --git a/netwerk/protocol/moz.build b/netwerk/protocol/moz.build
new file mode 100644
index 0000000000..aa3200c856
--- /dev/null
+++ b/netwerk/protocol/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ["about", "data", "file"]
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ DIRS += ["gio"]
+DIRS += ["http", "res", "viewsource", "websocket", "webtransport"]
diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.cpp b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
new file mode 100644
index 0000000000..d3b1a39164
--- /dev/null
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
@@ -0,0 +1,1039 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ExtensionProtocolHandler.h"
+
+#include "mozilla/BinarySearch.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "mozilla/ExtensionPolicyService.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/Omnijar.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Try.h"
+
+#include "FileDescriptorFile.h"
+#include "LoadInfo.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsICancelable.h"
+#include "nsIFile.h"
+#include "nsIFileChannel.h"
+#include "nsIFileStreams.h"
+#include "nsIFileURL.h"
+#include "nsIJARChannel.h"
+#include "nsIMIMEService.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsIInputStreamPump.h"
+#include "nsIJARURI.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsIStreamConverterService.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "nsURLHelper.h"
+#include "prio.h"
+#include "SimpleChannel.h"
+
+#if defined(XP_WIN)
+# include "nsILocalFileWin.h"
+# include "WinUtils.h"
+#endif
+
+#if defined(XP_MACOSX)
+# include "nsMacUtilsImpl.h"
+#endif
+
+#define EXTENSION_SCHEME "moz-extension"
+using mozilla::dom::Promise;
+using mozilla::ipc::FileDescriptor;
+
+// A list of file extensions containing purely static data, which can be loaded
+// from an extension before the extension is fully ready. The main purpose of
+// this is to allow image resources from theme XPIs to load quickly during
+// browser startup.
+//
+// The layout of this array is chosen in order to prevent the need for runtime
+// relocation, which an array of char* pointers would require. It also has the
+// benefit of being more compact when the difference in length between the
+// longest and average string is less than 8 bytes. The length of the
+// char[] array must match the size of the longest entry in the list.
+//
+// This list must be kept sorted.
+static const char sStaticFileExtensions[][5] = {
+ // clang-format off
+ "bmp",
+ "gif",
+ "ico",
+ "jpeg",
+ "jpg",
+ "png",
+ "svg",
+ // clang-format on
+};
+
+namespace mozilla {
+
+namespace net {
+
+using extensions::URLInfo;
+
+LazyLogModule gExtProtocolLog("ExtProtocol");
+#undef LOG
+#define LOG(...) MOZ_LOG(gExtProtocolLog, LogLevel::Debug, (__VA_ARGS__))
+
+StaticRefPtr<ExtensionProtocolHandler> ExtensionProtocolHandler::sSingleton;
+
+/**
+ * Helper class used with SimpleChannel to asynchronously obtain an input
+ * stream or file descriptor from the parent for a remote moz-extension load
+ * from the child.
+ */
+class ExtensionStreamGetter final : public nsICancelable {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ public:
+ // To use when getting a remote input stream for a resource
+ // in an unpacked extension.
+ ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo)
+ : mURI(aURI), mLoadInfo(aLoadInfo), mIsJarChannel(false) {
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(aLoadInfo);
+
+ SetupEventTarget();
+ }
+
+ // To use when getting an FD for a packed extension JAR file
+ // in order to load a resource.
+ ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ already_AddRefed<nsIJARChannel>&& aJarChannel,
+ nsIFile* aJarFile)
+ : mURI(aURI),
+ mLoadInfo(aLoadInfo),
+ mJarChannel(std::move(aJarChannel)),
+ mJarFile(aJarFile),
+ mIsJarChannel(true) {
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(aLoadInfo);
+ MOZ_ASSERT(mJarChannel);
+ MOZ_ASSERT(aJarFile);
+
+ SetupEventTarget();
+ }
+
+ void SetupEventTarget() {
+ mMainThreadEventTarget = GetMainThreadSerialEventTarget();
+ }
+
+ // Get an input stream or file descriptor from the parent asynchronously.
+ RequestOrReason GetAsync(nsIStreamListener* aListener, nsIChannel* aChannel);
+
+ // Handle an input stream being returned from the parent
+ void OnStream(already_AddRefed<nsIInputStream> aStream);
+
+ // Handle file descriptor being returned from the parent
+ void OnFD(const FileDescriptor& aFD);
+
+ static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel,
+ nsresult aResult);
+
+ private:
+ ~ExtensionStreamGetter() = default;
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIJARChannel> mJarChannel;
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ nsCOMPtr<nsIFile> mJarFile;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+ bool mIsJarChannel;
+ bool mCanceled{false};
+ nsresult mStatus{NS_OK};
+};
+
+NS_IMPL_ISUPPORTS(ExtensionStreamGetter, nsICancelable)
+
+class ExtensionJARFileOpener final : public nsISupports {
+ public:
+ ExtensionJARFileOpener(nsIFile* aFile,
+ NeckoParent::GetExtensionFDResolver& aResolve)
+ : mFile(aFile), mResolve(aResolve) {
+ MOZ_ASSERT(aFile);
+ MOZ_ASSERT(aResolve);
+ }
+
+ NS_IMETHOD OpenFile() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ AutoFDClose prFileDesc;
+
+#if defined(XP_WIN)
+ nsresult rv;
+ nsCOMPtr<nsILocalFileWin> winFile = do_QueryInterface(mFile, &rv);
+ MOZ_ASSERT(winFile);
+ if (NS_SUCCEEDED(rv)) {
+ rv = winFile->OpenNSPRFileDescShareDelete(PR_RDONLY, 0,
+ getter_Transfers(prFileDesc));
+ }
+#else
+ nsresult rv =
+ mFile->OpenNSPRFileDesc(PR_RDONLY, 0, getter_Transfers(prFileDesc));
+#endif /* XP_WIN */
+
+ if (NS_SUCCEEDED(rv)) {
+ mFD = FileDescriptor(FileDescriptor::PlatformHandleType(
+ PR_FileDesc2NativeHandle(prFileDesc.get())));
+ }
+
+ nsCOMPtr<nsIRunnable> event =
+ mozilla::NewRunnableMethod("ExtensionJarFileFDResolver", this,
+ &ExtensionJARFileOpener::SendBackFD);
+
+ rv = NS_DispatchToMainThread(event, nsIEventTarget::DISPATCH_NORMAL);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread");
+ return NS_OK;
+ }
+
+ NS_IMETHOD SendBackFD() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mResolve(mFD);
+ mResolve = nullptr;
+ return NS_OK;
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ private:
+ virtual ~ExtensionJARFileOpener() = default;
+
+ nsCOMPtr<nsIFile> mFile;
+ NeckoParent::GetExtensionFDResolver mResolve;
+ FileDescriptor mFD;
+};
+
+NS_IMPL_ISUPPORTS(ExtensionJARFileOpener, nsISupports)
+
+// The amount of time, in milliseconds, that the file opener thread will remain
+// allocated after it is used. This value chosen because to match other uses
+// of LazyIdleThread.
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+// Request an FD or input stream from the parent.
+RequestOrReason ExtensionStreamGetter::GetAsync(nsIStreamListener* aListener,
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mMainThreadEventTarget);
+
+ mListener = aListener;
+ mChannel = aChannel;
+
+ nsCOMPtr<nsICancelable> cancelableRequest(this);
+
+ RefPtr<ExtensionStreamGetter> self = this;
+ if (mIsJarChannel) {
+ // Request an FD for this moz-extension URI
+ gNeckoChild->SendGetExtensionFD(mURI)->Then(
+ mMainThreadEventTarget, __func__,
+ [self](const FileDescriptor& fd) { self->OnFD(fd); },
+ [self](const mozilla::ipc::ResponseRejectReason) {
+ self->OnFD(FileDescriptor());
+ });
+ return RequestOrCancelable(WrapNotNull(cancelableRequest));
+ }
+
+ // Request an input stream for this moz-extension URI
+ gNeckoChild->SendGetExtensionStream(mURI)->Then(
+ mMainThreadEventTarget, __func__,
+ [self](const RefPtr<nsIInputStream>& stream) {
+ self->OnStream(do_AddRef(stream));
+ },
+ [self](const mozilla::ipc::ResponseRejectReason) {
+ self->OnStream(nullptr);
+ });
+ return RequestOrCancelable(WrapNotNull(cancelableRequest));
+}
+
+// Called to cancel the ongoing async request.
+NS_IMETHODIMP
+ExtensionStreamGetter::Cancel(nsresult aStatus) {
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = aStatus;
+
+ if (mPump) {
+ mPump->CancelWithReason(aStatus, "ExtensionStreamGetter::Cancel"_ns);
+ mPump = nullptr;
+ }
+
+ if (mIsJarChannel && mJarChannel) {
+ mJarChannel->CancelWithReason(aStatus, "ExtensionStreamGetter::Cancel"_ns);
+ }
+
+ return NS_OK;
+}
+
+// static
+void ExtensionStreamGetter::CancelRequest(nsIStreamListener* aListener,
+ nsIChannel* aChannel,
+ nsresult aResult) {
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(aChannel);
+
+ aListener->OnStartRequest(aChannel);
+ aListener->OnStopRequest(aChannel, aResult);
+ aChannel->CancelWithReason(NS_BINDING_ABORTED,
+ "ExtensionStreamGetter::CancelRequest"_ns);
+}
+
+// Handle an input stream sent from the parent.
+void ExtensionStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mListener);
+ MOZ_ASSERT(mMainThreadEventTarget);
+
+ nsCOMPtr<nsIInputStream> stream = std::move(aStream);
+ nsCOMPtr<nsIChannel> channel = std::move(mChannel);
+
+ // We must keep an owning reference to the listener
+ // until we pass it on to AsyncRead.
+ nsCOMPtr<nsIStreamListener> listener = std::move(mListener);
+
+ if (mCanceled) {
+ // The channel that has created this stream getter has been canceled.
+ CancelRequest(listener, channel, mStatus);
+ return;
+ }
+
+ if (!stream) {
+ // The parent didn't send us back a stream.
+ CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0,
+ 0, false, mMainThreadEventTarget);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ return;
+ }
+
+ rv = pump->AsyncRead(listener);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ return;
+ }
+
+ mPump = pump;
+}
+
+// Handle an FD sent from the parent.
+void ExtensionStreamGetter::OnFD(const FileDescriptor& aFD) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mListener);
+
+ nsCOMPtr<nsIChannel> channel = std::move(mChannel);
+
+ // We must keep an owning reference to the listener
+ // until we pass it on to AsyncOpen.
+ nsCOMPtr<nsIStreamListener> listener = std::move(mListener);
+
+ if (mCanceled) {
+ // The channel that has created this stream getter has been canceled.
+ CancelRequest(listener, channel, mStatus);
+ return;
+ }
+
+ if (!aFD.IsValid()) {
+ // The parent didn't send us back a valid file descriptor.
+ CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED);
+ return;
+ }
+
+ RefPtr<FileDescriptorFile> fdFile = new FileDescriptorFile(aFD, mJarFile);
+ mJarChannel->SetJarFile(fdFile);
+ nsresult rv = mJarChannel->AsyncOpen(listener);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ }
+}
+
+NS_IMPL_QUERY_INTERFACE(ExtensionProtocolHandler,
+ nsISubstitutingProtocolHandler, nsIProtocolHandler,
+ nsIProtocolHandlerWithDynamicFlags,
+ nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
+
+already_AddRefed<ExtensionProtocolHandler>
+ExtensionProtocolHandler::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new ExtensionProtocolHandler();
+ ClearOnShutdown(&sSingleton);
+ }
+ return do_AddRef(sSingleton);
+}
+
+ExtensionProtocolHandler::ExtensionProtocolHandler()
+ : SubstitutingProtocolHandler(EXTENSION_SCHEME) {
+ // Note, extensions.webextensions.protocol.remote=false is for
+ // debugging purposes only. With process-level sandboxing, child
+ // processes (specifically content and extension processes), will
+ // not be able to load most moz-extension URI's when the pref is
+ // set to false.
+ mUseRemoteFileChannels =
+ IsNeckoChild() &&
+ Preferences::GetBool("extensions.webextensions.protocol.remote");
+}
+
+static inline ExtensionPolicyService& EPS() {
+ return ExtensionPolicyService::GetSingleton();
+}
+
+nsresult ExtensionProtocolHandler::GetFlagsForURI(nsIURI* aURI,
+ uint32_t* aFlags) {
+ uint32_t flags =
+ URI_STD | URI_IS_LOCAL_RESOURCE | URI_IS_POTENTIALLY_TRUSTWORTHY;
+
+ URLInfo url(aURI);
+ if (auto* policy = EPS().GetByURL(url)) {
+ // In general a moz-extension URI is only loadable by chrome, but an
+ // allowlist subset are web-accessible (and cross-origin fetchable).
+ // The allowlist is checked using EPS.SourceMayLoadExtensionURI in
+ // BasePrincipal and nsScriptSecurityManager.
+ if (policy->IsWebAccessiblePath(url.FilePath())) {
+ if (policy->ManifestVersion() < 3) {
+ flags |= URI_LOADABLE_BY_ANYONE | URI_FETCHABLE_BY_ANYONE;
+ } else {
+ flags |= WEBEXT_URI_WEB_ACCESSIBLE;
+ }
+ } else if (policy->Type() == nsGkAtoms::theme) {
+ // Static themes cannot set web accessible resources, however using this
+ // flag here triggers SourceMayAccessPath calls necessary to allow another
+ // extension to access static theme resources in this extension.
+ flags |= WEBEXT_URI_WEB_ACCESSIBLE;
+ } else {
+ flags |= URI_DANGEROUS_TO_LOAD;
+ }
+
+ // Disallow in private windows if the extension does not have permission.
+ if (!policy->PrivateBrowsingAllowed()) {
+ flags |= URI_DISALLOW_IN_PRIVATE_CONTEXT;
+ }
+ } else {
+ // In case there is no policy, then default to treating moz-extension URIs
+ // as unsafe and generally only allow chrome: to load such.
+ flags |= URI_DANGEROUS_TO_LOAD;
+ }
+
+ *aFlags = flags;
+ return NS_OK;
+}
+
+bool ExtensionProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
+ "The ExtensionPolicyService is not thread safe");
+ // Create special moz-extension://foo/_generated_background_page.html page
+ // for all registered extensions. We can't just do this as a substitution
+ // because substitutions can only match on host.
+ if (!SubstitutingProtocolHandler::HasSubstitution(aHost)) {
+ return false;
+ }
+
+ if (aPathname.EqualsLiteral("/_generated_background_page.html")) {
+ Unused << EPS().GetGeneratedBackgroundPageUrl(aHost, aResult);
+ return !aResult.IsEmpty();
+ }
+
+ return false;
+}
+
+// For file or JAR URI's, substitute in a remote channel.
+Result<Ok, nsresult> ExtensionProtocolHandler::SubstituteRemoteChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_TRY(aURI ? NS_OK : NS_ERROR_INVALID_ARG);
+ MOZ_TRY(aLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG);
+
+ nsAutoCString unResolvedSpec;
+ MOZ_TRY(aURI->GetSpec(unResolvedSpec));
+
+ nsAutoCString resolvedSpec;
+ MOZ_TRY(ResolveURI(aURI, resolvedSpec));
+
+ // Use the target URI scheme to determine if this is a packed or unpacked
+ // extension URI. For unpacked extensions, we'll request an input stream
+ // from the parent. For a packed extension, we'll request a file descriptor
+ // for the JAR file.
+ nsAutoCString scheme;
+ MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme));
+
+ if (scheme.EqualsLiteral("file")) {
+ // Unpacked extension
+ SubstituteRemoteFileChannel(aURI, aLoadInfo, resolvedSpec, aRetVal);
+ return Ok();
+ }
+
+ if (scheme.EqualsLiteral("jar")) {
+ // Packed extension
+ return SubstituteRemoteJarChannel(aURI, aLoadInfo, resolvedSpec, aRetVal);
+ }
+
+ // Only unpacked resource files and JAR files are remoted.
+ // No other moz-extension loads should be reading from the filesystem.
+ return Ok();
+}
+
+void OpenWhenReady(
+ Promise* aPromise, nsIStreamListener* aListener, nsIChannel* aChannel,
+ const std::function<nsresult(nsIStreamListener*, nsIChannel*)>& aCallback) {
+ nsCOMPtr<nsIStreamListener> listener(aListener);
+ nsCOMPtr<nsIChannel> channel(aChannel);
+
+ Unused << aPromise->ThenWithCycleCollectedArgs(
+ [channel, aCallback](
+ JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ nsIStreamListener* aListener) -> already_AddRefed<Promise> {
+ nsresult rv = aCallback(aListener, channel);
+ if (NS_FAILED(rv)) {
+ ExtensionStreamGetter::CancelRequest(aListener, channel, rv);
+ }
+ return nullptr;
+ },
+ listener);
+}
+
+nsresult ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ if (mUseRemoteFileChannels) {
+ MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, result));
+ }
+
+ auto* policy = EPS().GetByURL(aURI);
+ NS_ENSURE_TRUE(policy, NS_ERROR_UNEXPECTED);
+
+ RefPtr<dom::Promise> readyPromise(policy->ReadyPromise());
+
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI, &rv);
+ MOZ_TRY(rv);
+
+ nsAutoCString ext;
+ MOZ_TRY(url->GetFileExtension(ext));
+ ToLowerCase(ext);
+
+ nsCOMPtr<nsIChannel> channel;
+ if (ext.EqualsLiteral("css")) {
+ // Filter CSS files to replace locale message tokens with localized strings.
+ static const auto convert = [](nsIStreamListener* listener,
+ nsIChannel* channel,
+ nsIChannel* origChannel) -> nsresult {
+ nsresult rv;
+ nsCOMPtr<nsIStreamConverterService> convService =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIURI> uri;
+ MOZ_TRY(channel->GetURI(getter_AddRefs(uri)));
+
+ const char* kFromType = "application/vnd.mozilla.webext.unlocalized";
+ const char* kToType = "text/css";
+
+ nsCOMPtr<nsIStreamListener> converter;
+ MOZ_TRY(convService->AsyncConvertData(kFromType, kToType, listener, uri,
+ getter_AddRefs(converter)));
+
+ return origChannel->AsyncOpen(converter);
+ };
+
+ channel = NS_NewSimpleChannel(
+ aURI, aLoadInfo, *result,
+ [readyPromise](nsIStreamListener* listener, nsIChannel* channel,
+ nsIChannel* origChannel) -> RequestOrReason {
+ if (readyPromise) {
+ nsCOMPtr<nsIChannel> chan(channel);
+ OpenWhenReady(
+ readyPromise, listener, origChannel,
+ [chan](nsIStreamListener* aListener, nsIChannel* aChannel) {
+ return convert(aListener, chan, aChannel);
+ });
+ } else {
+ MOZ_TRY(convert(listener, channel, origChannel));
+ }
+ nsCOMPtr<nsIRequest> request(origChannel);
+ return RequestOrCancelable(WrapNotNull(request));
+ });
+ } else if (readyPromise) {
+ size_t matchIdx;
+ if (BinarySearchIf(
+ sStaticFileExtensions, 0, ArrayLength(sStaticFileExtensions),
+ [&ext](const char* aOther) {
+ return Compare(ext, nsDependentCString(aOther));
+ },
+ &matchIdx)) {
+ // This is a static resource that shouldn't depend on the extension being
+ // ready. Don't bother waiting for it.
+ return NS_OK;
+ }
+
+ channel = NS_NewSimpleChannel(
+ aURI, aLoadInfo, *result,
+ [readyPromise](nsIStreamListener* listener, nsIChannel* channel,
+ nsIChannel* origChannel) -> RequestOrReason {
+ OpenWhenReady(readyPromise, listener, origChannel,
+ [](nsIStreamListener* aListener, nsIChannel* aChannel) {
+ return aChannel->AsyncOpen(aListener);
+ });
+
+ nsCOMPtr<nsIRequest> request(origChannel);
+ return RequestOrCancelable(WrapNotNull(request));
+ });
+ } else {
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(channel, NS_ERROR_OUT_OF_MEMORY);
+ if (aLoadInfo) {
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ static_cast<LoadInfo*>(aLoadInfo)->CloneForNewRequest();
+ (*result)->SetLoadInfo(loadInfo);
+ }
+
+ channel.swap(*result);
+ return NS_OK;
+}
+
+Result<bool, nsresult> ExtensionProtocolHandler::AllowExternalResource(
+ nsIFile* aExtensionDir, nsIFile* aRequestedFile) {
+ MOZ_ASSERT(!IsNeckoChild());
+
+#if defined(XP_WIN)
+ // On Windows, non-package builds don't use symlinks so we never need to
+ // allow a resource from outside of the extension dir.
+ return false;
+#else
+ if (mozilla::IsPackagedBuild()) {
+ return false;
+ }
+
+ // On Mac and Linux unpackaged dev builds, system extensions use
+ // symlinks to point to resources in the repo dir which we have to
+ // allow loading. Before we allow an unpacked extension to load a
+ // resource outside of the extension dir, we make sure the extension
+ // dir is within the app directory.
+ bool result;
+ MOZ_TRY_VAR(result, AppDirContains(aExtensionDir));
+ if (!result) {
+ return false;
+ }
+
+# if defined(XP_MACOSX)
+ // Additionally, on Mac dev builds, we make sure that the requested
+ // resource is within the repo dir. We don't perform this check on Linux
+ // because we don't have a reliable path to the repo dir on Linux.
+ return DevRepoContains(aRequestedFile);
+# else /* XP_MACOSX */
+ return true;
+# endif
+#endif /* defined(XP_WIN) */
+}
+
+#if defined(XP_MACOSX)
+// The |aRequestedFile| argument must already be Normalize()'d
+Result<bool, nsresult> ExtensionProtocolHandler::DevRepoContains(
+ nsIFile* aRequestedFile) {
+ MOZ_ASSERT(!mozilla::IsPackagedBuild());
+ MOZ_ASSERT(!IsNeckoChild());
+
+ // On the first invocation, set mDevRepo
+ if (!mAlreadyCheckedDevRepo) {
+ mAlreadyCheckedDevRepo = true;
+ MOZ_TRY(nsMacUtilsImpl::GetRepoDir(getter_AddRefs(mDevRepo)));
+ if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) {
+ nsAutoCString repoPath;
+ Unused << mDevRepo->GetNativePath(repoPath);
+ LOG("Repo path: %s", repoPath.get());
+ }
+ }
+
+ bool result = false;
+ if (mDevRepo) {
+ MOZ_TRY(mDevRepo->Contains(aRequestedFile, &result));
+ }
+ return result;
+}
+#endif /* XP_MACOSX */
+
+#if !defined(XP_WIN)
+Result<bool, nsresult> ExtensionProtocolHandler::AppDirContains(
+ nsIFile* aExtensionDir) {
+ MOZ_ASSERT(!mozilla::IsPackagedBuild());
+ MOZ_ASSERT(!IsNeckoChild());
+
+ // On the first invocation, set mAppDir
+ if (!mAlreadyCheckedAppDir) {
+ mAlreadyCheckedAppDir = true;
+ MOZ_TRY(NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(mAppDir)));
+ if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) {
+ nsAutoCString appDirPath;
+ Unused << mAppDir->GetNativePath(appDirPath);
+ LOG("AppDir path: %s", appDirPath.get());
+ }
+ }
+
+ bool result = false;
+ if (mAppDir) {
+ MOZ_TRY(mAppDir->Contains(aExtensionDir, &result));
+ }
+ return result;
+}
+#endif /* !defined(XP_WIN) */
+
+static void LogExternalResourceError(nsIFile* aExtensionDir,
+ nsIFile* aRequestedFile) {
+ MOZ_ASSERT(aExtensionDir);
+ MOZ_ASSERT(aRequestedFile);
+
+ LOG("Rejecting external unpacked extension resource [%s] from "
+ "extension directory [%s]",
+ aRequestedFile->HumanReadablePath().get(),
+ aExtensionDir->HumanReadablePath().get());
+}
+
+Result<nsCOMPtr<nsIInputStream>, nsresult> ExtensionProtocolHandler::NewStream(
+ nsIURI* aChildURI, bool* aTerminateSender) {
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG);
+ MOZ_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG);
+
+ *aTerminateSender = true;
+ nsresult rv;
+
+ // We should never receive a URI that isn't for a moz-extension because
+ // these requests ordinarily come from the child's ExtensionProtocolHandler.
+ // Ensure this request is for a moz-extension URI. A rogue child process
+ // could send us any URI.
+ if (!aChildURI->SchemeIs(EXTENSION_SCHEME)) {
+ return Err(NS_ERROR_UNKNOWN_PROTOCOL);
+ }
+
+ // For errors after this point, we want to propagate the error to
+ // the child, but we don't force the child to be terminated because
+ // the error is likely to be due to a bug in the extension.
+ *aTerminateSender = false;
+
+ /*
+ * Make sure there is a substitution installed for the host found
+ * in the child's request URI and make sure the host resolves to
+ * a directory.
+ */
+
+ nsAutoCString host;
+ MOZ_TRY(aChildURI->GetAsciiHost(host));
+
+ // Lookup the directory this host string resolves to
+ nsCOMPtr<nsIURI> baseURI;
+ MOZ_TRY(GetSubstitution(host, getter_AddRefs(baseURI)));
+
+ // The result should be a file URL for the extension base dir
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(baseURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> extensionDir;
+ MOZ_TRY(fileURL->GetFile(getter_AddRefs(extensionDir)));
+
+ bool isDirectory = false;
+ MOZ_TRY(extensionDir->IsDirectory(&isDirectory));
+ if (!isDirectory) {
+ // The host should map to a directory for unpacked extensions
+ return Err(NS_ERROR_FILE_NOT_DIRECTORY);
+ }
+
+ // Make sure the child URI resolves to a file URI then get a file
+ // channel for the request. The resultant channel should be a
+ // file channel because we only request remote streams for unpacked
+ // extension resource loads where the URI resolves to a file.
+ nsAutoCString resolvedSpec;
+ MOZ_TRY(ResolveURI(aChildURI, resolvedSpec));
+
+ nsAutoCString resolvedScheme;
+ MOZ_TRY(net_ExtractURLScheme(resolvedSpec, resolvedScheme));
+ if (!resolvedScheme.EqualsLiteral("file")) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ MOZ_TRY(ioService->NewURI(resolvedSpec, nullptr, nullptr,
+ getter_AddRefs(resolvedURI)));
+
+ // We use the system principal to get a file channel for the request,
+ // but only after we've checked (above) that the child URI is of
+ // moz-extension scheme and that the URI host maps to a directory.
+ nsCOMPtr<nsIChannel> channel;
+ MOZ_TRY(NS_NewChannel(getter_AddRefs(channel), resolvedURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER));
+
+ nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(channel, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> requestedFile;
+ MOZ_TRY(fileChannel->GetFile(getter_AddRefs(requestedFile)));
+
+ /*
+ * Make sure the file we resolved to is within the extension directory.
+ */
+
+ // Normalize paths for sane comparisons. nsIFile::Contains depends on
+ // it for reliable subpath checks.
+ MOZ_TRY(extensionDir->Normalize());
+ MOZ_TRY(requestedFile->Normalize());
+#if defined(XP_WIN)
+ if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(extensionDir) ||
+ !widget::WinUtils::ResolveJunctionPointsAndSymLinks(requestedFile)) {
+ return Err(NS_ERROR_FILE_ACCESS_DENIED);
+ }
+#endif
+
+ bool isResourceFromExtensionDir = false;
+ MOZ_TRY(extensionDir->Contains(requestedFile, &isResourceFromExtensionDir));
+ if (!isResourceFromExtensionDir) {
+ bool isAllowed;
+ MOZ_TRY_VAR(isAllowed, AllowExternalResource(extensionDir, requestedFile));
+ if (!isAllowed) {
+ LogExternalResourceError(extensionDir, requestedFile);
+ return Err(NS_ERROR_FILE_ACCESS_DENIED);
+ }
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ MOZ_TRY_VAR(inputStream,
+ NS_NewLocalFileInputStream(requestedFile, PR_RDONLY, -1,
+ nsIFileInputStream::DEFER_OPEN));
+
+ return inputStream;
+}
+
+Result<Ok, nsresult> ExtensionProtocolHandler::NewFD(
+ nsIURI* aChildURI, bool* aTerminateSender,
+ NeckoParent::GetExtensionFDResolver& aResolve) {
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG);
+ MOZ_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG);
+
+ *aTerminateSender = true;
+ nsresult rv;
+
+ // Ensure this is a moz-extension URI
+ if (!aChildURI->SchemeIs(EXTENSION_SCHEME)) {
+ return Err(NS_ERROR_UNKNOWN_PROTOCOL);
+ }
+
+ // For errors after this point, we want to propagate the error to
+ // the child, but we don't force the child to be terminated.
+ *aTerminateSender = false;
+
+ nsAutoCString host;
+ MOZ_TRY(aChildURI->GetAsciiHost(host));
+
+ // We expect the host string to map to a JAR file because the URI
+ // should refer to a web accessible resource for an enabled extension.
+ nsCOMPtr<nsIURI> subURI;
+ MOZ_TRY(GetSubstitution(host, getter_AddRefs(subURI)));
+
+ nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(subURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIURI> innerFileURI;
+ MOZ_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI)));
+
+ nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> jarFile;
+ MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile)));
+
+ if (!mFileOpenerThread) {
+ mFileOpenerThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
+ "ExtensionProtocolHandler");
+ }
+
+ RefPtr<ExtensionJARFileOpener> fileOpener =
+ new ExtensionJARFileOpener(jarFile, aResolve);
+
+ nsCOMPtr<nsIRunnable> event = mozilla::NewRunnableMethod(
+ "ExtensionJarFileOpener", fileOpener, &ExtensionJARFileOpener::OpenFile);
+
+ MOZ_TRY(mFileOpenerThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL));
+
+ return Ok();
+}
+
+// Set the channel's content type using the provided URI's type
+
+// static
+void ExtensionProtocolHandler::SetContentType(nsIURI* aURI,
+ nsIChannel* aChannel) {
+ nsresult rv;
+ nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString contentType;
+ rv = mime->GetTypeFromURI(aURI, contentType);
+ if (NS_SUCCEEDED(rv)) {
+ Unused << aChannel->SetContentType(contentType);
+ }
+ }
+}
+
+// Gets a SimpleChannel that wraps the provided ExtensionStreamGetter
+
+// static
+void ExtensionProtocolHandler::NewSimpleChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadinfo, ExtensionStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal) {
+ nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
+ aURI, aLoadinfo, aStreamGetter,
+ [](nsIStreamListener* listener, nsIChannel* simpleChannel,
+ ExtensionStreamGetter* getter) -> RequestOrReason {
+ return getter->GetAsync(listener, simpleChannel);
+ });
+
+ SetContentType(aURI, channel);
+ channel.swap(*aRetVal);
+}
+
+// Gets a SimpleChannel that wraps the provided channel
+
+// static
+void ExtensionProtocolHandler::NewSimpleChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadinfo,
+ nsIChannel* aChannel,
+ nsIChannel** aRetVal) {
+ nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
+ aURI, aLoadinfo, aChannel,
+ [](nsIStreamListener* listener, nsIChannel* simpleChannel,
+ nsIChannel* origChannel) -> RequestOrReason {
+ nsresult rv = origChannel->AsyncOpen(listener);
+ if (NS_FAILED(rv)) {
+ simpleChannel->Cancel(NS_BINDING_ABORTED);
+ return Err(rv);
+ }
+ nsCOMPtr<nsIRequest> request(origChannel);
+ return RequestOrCancelable(WrapNotNull(request));
+ });
+
+ SetContentType(aURI, channel);
+ channel.swap(*aRetVal);
+}
+
+void ExtensionProtocolHandler::SubstituteRemoteFileChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadinfo, nsACString& aResolvedFileSpec,
+ nsIChannel** aRetVal) {
+ MOZ_ASSERT(IsNeckoChild());
+
+ RefPtr<ExtensionStreamGetter> streamGetter =
+ new ExtensionStreamGetter(aURI, aLoadinfo);
+
+ NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal);
+}
+
+static Result<Ok, nsresult> LogCacheCheck(const nsIJARChannel* aJarChannel,
+ nsIJARURI* aJarURI, bool aIsCached) {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> innerFileURI;
+ MOZ_TRY(aJarURI->GetJARFile(getter_AddRefs(innerFileURI)));
+
+ nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> jarFile;
+ MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile)));
+
+ nsAutoCString uriSpec, jarSpec;
+ Unused << aJarURI->GetSpec(uriSpec);
+ Unused << innerFileURI->GetSpec(jarSpec);
+ LOG("[JARChannel %p] Cache %s: %s (%s)", aJarChannel,
+ aIsCached ? "hit" : "miss", uriSpec.get(), jarSpec.get());
+
+ return Ok();
+}
+
+Result<Ok, nsresult> ExtensionProtocolHandler::SubstituteRemoteJarChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadinfo, nsACString& aResolvedSpec,
+ nsIChannel** aRetVal) {
+ MOZ_ASSERT(IsNeckoChild());
+ nsresult rv;
+
+ // Build a JAR URI for this jar:file:// URI and use it to extract the
+ // inner file URI.
+ nsCOMPtr<nsIURI> uri;
+ MOZ_TRY(NS_NewURI(getter_AddRefs(uri), aResolvedSpec));
+
+ nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(*aRetVal, &rv);
+ MOZ_TRY(rv);
+
+ bool isCached = false;
+ MOZ_TRY(jarChannel->EnsureCached(&isCached));
+ if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) {
+ Unused << LogCacheCheck(jarChannel, jarURI, isCached);
+ }
+
+ if (isCached) {
+ // Using a SimpleChannel with an ExtensionStreamGetter here (like the
+ // non-cached JAR case) isn't needed to load the extension resource
+ // because we don't need to ask the parent for an FD for the JAR, but
+ // wrapping the JARChannel in a SimpleChannel allows HTTP forwarding to
+ // moz-extension URI's to work because HTTP forwarding requires the
+ // target channel implement nsIChildChannel.
+ NewSimpleChannel(aURI, aLoadinfo, jarChannel.get(), aRetVal);
+ return Ok();
+ }
+
+ nsCOMPtr<nsIURI> innerFileURI;
+ MOZ_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI)));
+
+ nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIFile> jarFile;
+ MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile)));
+
+ RefPtr<ExtensionStreamGetter> streamGetter =
+ new ExtensionStreamGetter(aURI, aLoadinfo, jarChannel.forget(), jarFile);
+
+ NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal);
+ return Ok();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.h b/netwerk/protocol/res/ExtensionProtocolHandler.h
new file mode 100644
index 0000000000..0824d7dd99
--- /dev/null
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.h
@@ -0,0 +1,236 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ExtensionProtocolHandler_h___
+#define ExtensionProtocolHandler_h___
+
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/Result.h"
+#include "SubstitutingProtocolHandler.h"
+
+namespace mozilla {
+namespace net {
+
+class ExtensionStreamGetter;
+
+class ExtensionProtocolHandler final
+ : public nsISubstitutingProtocolHandler,
+ public nsIProtocolHandlerWithDynamicFlags,
+ public SubstitutingProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIPROTOCOLHANDLERWITHDYNAMICFLAGS
+ NS_FORWARD_NSIPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+ NS_FORWARD_NSISUBSTITUTINGPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+
+ static already_AddRefed<ExtensionProtocolHandler> GetSingleton();
+
+ /**
+ * To be called in the parent process to obtain an input stream for a
+ * a web accessible resource from an unpacked WebExtension dir.
+ *
+ * @param aChildURI a moz-extension URI sent from the child that refers
+ * to a web accessible resource file in an enabled unpacked extension
+ * @param aTerminateSender out param set to true when the params are invalid
+ * and indicate the child should be terminated. If |aChildURI| is
+ * not a moz-extension URI, the child is in an invalid state and
+ * should be terminated.
+ * @return NS_OK with |aTerminateSender| set to false on success. On
+ * failure, returns an error and sets |aTerminateSender| to indicate
+ * whether or not the child process should be terminated.
+ * A moz-extension URI from the child that doesn't resolve to a
+ * resource file within the extension could be the result of a bug
+ * in the extension and doesn't result in |aTerminateSender| being
+ * set to true.
+ */
+ Result<nsCOMPtr<nsIInputStream>, nsresult> NewStream(nsIURI* aChildURI,
+ bool* aTerminateSender);
+
+ /**
+ * To be called in the parent process to obtain a file descriptor for an
+ * enabled WebExtension JAR file.
+ *
+ * @param aChildURI a moz-extension URI sent from the child that refers
+ * to a web accessible resource file in an enabled unpacked extension
+ * @param aTerminateSender out param set to true when the params are invalid
+ * and indicate the child should be terminated. If |aChildURI| is
+ * not a moz-extension URI, the child is in an invalid state and
+ * should be terminated.
+ * @param aPromise a promise that will be resolved asynchronously when the
+ * file descriptor is available.
+ * @return NS_OK with |aTerminateSender| set to false on success. On
+ * failure, returns an error and sets |aTerminateSender| to indicate
+ * whether or not the child process should be terminated.
+ * A moz-extension URI from the child that doesn't resolve to an
+ * enabled WebExtension JAR could be the result of a bug in the
+ * extension and doesn't result in |aTerminateSender| being
+ * set to true.
+ */
+ Result<Ok, nsresult> NewFD(nsIURI* aChildURI, bool* aTerminateSender,
+ NeckoParent::GetExtensionFDResolver& aResolve);
+
+ protected:
+ ~ExtensionProtocolHandler() = default;
+
+ private:
+ explicit ExtensionProtocolHandler();
+
+ [[nodiscard]] bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) override;
+
+ // |result| is an inout param. On entry to this function, *result
+ // is expected to be non-null and already addrefed. This function
+ // may release the object stored in *result on entry and write
+ // a new pointer to an already addrefed channel to *result.
+ [[nodiscard]] virtual nsresult SubstituteChannel(
+ nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result) override;
+
+ /**
+ * For moz-extension URI's that resolve to file or JAR URI's, replaces
+ * the provided channel with a channel that will proxy the load to the
+ * parent process. For moz-extension URI's that resolve to other types
+ * of URI's (not file or JAR), the provide channel is not replaced and
+ * NS_OK is returned.
+ *
+ * @param aURI the moz-extension URI
+ * @param aLoadInfo the loadinfo for the request
+ * @param aRetVal in/out channel param referring to the channel that
+ * might need to be substituted with a remote channel.
+ * @return NS_OK if the channel does not need to be substituted or
+ * or the replacement channel was created successfully.
+ * Otherwise returns an error.
+ */
+ Result<Ok, nsresult> SubstituteRemoteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aRetVal);
+
+ /**
+ * Replaces a file channel with a remote file channel for loading a
+ * web accessible resource for an unpacked extension from the parent.
+ *
+ * @param aURI the moz-extension URI
+ * @param aLoadInfo the loadinfo for the request
+ * @param aResolvedFileSpec the resolved URI spec for the file.
+ * @param aRetVal in/out param referring to the new remote channel.
+ * The reference to the input param file channel is dropped and
+ * replaced with a reference to a new channel that remotes
+ * the file access. The new channel encapsulates a request to
+ * the parent for an IPCStream for the file.
+ */
+ void SubstituteRemoteFileChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo,
+ nsACString& aResolvedFileSpec,
+ nsIChannel** aRetVal);
+
+ /**
+ * Replaces a JAR channel with a remote JAR channel for loading a
+ * an extension JAR file from the parent.
+ *
+ * @param aURI the moz-extension URI
+ * @param aLoadInfo the loadinfo for the request
+ * @param aResolvedFileSpec the resolved URI spec for the file.
+ * @param aRetVal in/out param referring to the new remote channel.
+ * The input param JAR channel is replaced with a new channel
+ * that remotes the JAR file access. The new channel encapsulates
+ * a request to the parent for the JAR file FD.
+ */
+ Result<Ok, nsresult> SubstituteRemoteJarChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadinfo,
+ nsACString& aResolvedSpec,
+ nsIChannel** aRetVal);
+
+ /**
+ * Sets the aResult outparam to true if this unpacked extension load of
+ * a resource that is outside the extension dir should be allowed. This
+ * is only allowed for system extensions on Mac and Linux dev builds.
+ *
+ * @param aExtensionDir the extension directory. Argument must be an
+ * nsIFile for which Normalize() has already been called.
+ * @param aRequestedFile the requested web-accessible resource file. Argument
+ * must be an nsIFile for which Normalize() has already been called.
+ */
+ Result<bool, nsresult> AllowExternalResource(nsIFile* aExtensionDir,
+ nsIFile* aRequestedFile);
+
+ // Set the channel's content type using the provided URI's type
+ static void SetContentType(nsIURI* aURI, nsIChannel* aChannel);
+
+ // Gets a SimpleChannel that wraps the provided ExtensionStreamGetter
+ static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo,
+ ExtensionStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal);
+
+ // Gets a SimpleChannel that wraps the provided channel
+ static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo,
+ nsIChannel* aChannel, nsIChannel** aRetVal);
+
+#if defined(XP_MACOSX)
+ /**
+ * Sets the aResult outparam to true if we are a developer build with the
+ * repo dir environment variable set and the requested file resides in the
+ * repo dir. Developer builds may load system extensions with web-accessible
+ * resources that are symlinks to files in the repo dir. This method is for
+ * checking if an unpacked resource requested by the child is from the repo.
+ * The requested file must be already Normalized(). Only compile this for
+ * Mac because the repo dir isn't always available on Linux.
+ *
+ * @param aRequestedFile the requested web-accessible resource file. Argument
+ * must be an nsIFile for which Normalize() has already been called.
+ */
+ Result<bool, nsresult> DevRepoContains(nsIFile* aRequestedFile);
+
+ // On development builds, this points to development repo. Lazily set.
+ nsCOMPtr<nsIFile> mDevRepo;
+
+ // Set to true once we've already tried to load the dev repo path,
+ // allowing for lazy initialization of |mDevRepo|.
+ bool mAlreadyCheckedDevRepo{false};
+#endif /* XP_MACOSX */
+
+#if !defined(XP_WIN)
+ /**
+ * Sets the aResult outparam to true if we are a developer build and the
+ * provided directory is within the NS_GRE_DIR directory. Developer builds
+ * may load system extensions with web-accessible resources that are symlinks
+ * to files outside of the extension dir to the repo dir. This method is for
+ * checking if an extension directory is within NS_GRE_DIR. In that case, we
+ * consider the extension a system extension and allow it to use symlinks to
+ * resources outside of the extension dir. This exception is only applied
+ * to loads for unpacked extensions in unpackaged developer builds.
+ * The requested dir must be already Normalized().
+ *
+ * @param aExtensionDir the extension directory. Argument must be an
+ * nsIFile for which Normalize() has already been called.
+ */
+ Result<bool, nsresult> AppDirContains(nsIFile* aExtensionDir);
+
+ // On development builds, cache the NS_GRE_DIR repo. Lazily set.
+ nsCOMPtr<nsIFile> mAppDir;
+
+ // Set to true once we've already read the AppDir, allowing for lazy
+ // initialization of |mAppDir|.
+ bool mAlreadyCheckedAppDir{false};
+#endif /* !defined(XP_WIN) */
+
+ // Used for opening JAR files off the main thread when we just need to
+ // obtain a file descriptor to send back to the child.
+ RefPtr<mozilla::LazyIdleThread> mFileOpenerThread;
+
+ // To allow parent IPDL actors to invoke methods on this handler when
+ // handling moz-extension requests from the child.
+ static StaticRefPtr<ExtensionProtocolHandler> sSingleton;
+
+ // Set to true when this instance of the handler must proxy loads of
+ // extension web-accessible resources to the parent process.
+ bool mUseRemoteFileChannels;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* ExtensionProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/PageThumbProtocolHandler.cpp b/netwerk/protocol/res/PageThumbProtocolHandler.cpp
new file mode 100644
index 0000000000..5918b40fad
--- /dev/null
+++ b/netwerk/protocol/res/PageThumbProtocolHandler.cpp
@@ -0,0 +1,361 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PageThumbProtocolHandler.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ipc/URIParams.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Try.h"
+
+#include "LoadInfo.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+#include "nsIFileChannel.h"
+#include "nsIFileStreams.h"
+#include "nsIMIMEService.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsIPageThumbsStorageService.h"
+#include "nsIInputStreamPump.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "prio.h"
+#include "SimpleChannel.h"
+#include "nsICancelable.h"
+
+#ifdef MOZ_PLACES
+# include "nsIPlacesPreviewsHelperService.h"
+#endif
+
+#define PAGE_THUMB_HOST "thumbnails"
+#define PLACES_PREVIEWS_HOST "places-previews"
+#define PAGE_THUMB_SCHEME "moz-page-thumb"
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gPageThumbProtocolLog("PageThumbProtocol");
+
+#undef LOG
+#define LOG(level, ...) \
+ MOZ_LOG(gPageThumbProtocolLog, LogLevel::level, (__VA_ARGS__))
+
+StaticRefPtr<PageThumbProtocolHandler> PageThumbProtocolHandler::sSingleton;
+
+NS_IMPL_QUERY_INTERFACE(PageThumbProtocolHandler,
+ nsISubstitutingProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(PageThumbProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(PageThumbProtocolHandler, SubstitutingProtocolHandler)
+
+already_AddRefed<PageThumbProtocolHandler>
+PageThumbProtocolHandler::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new PageThumbProtocolHandler();
+ ClearOnShutdown(&sSingleton);
+ }
+
+ return do_AddRef(sSingleton);
+}
+
+// A moz-page-thumb URI is only loadable by chrome pages in the parent process,
+// or privileged content running in the privileged about content process.
+PageThumbProtocolHandler::PageThumbProtocolHandler()
+ : SubstitutingProtocolHandler(PAGE_THUMB_SCHEME) {}
+
+RefPtr<RemoteStreamPromise> PageThumbProtocolHandler::NewStream(
+ nsIURI* aChildURI, bool* aTerminateSender) {
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aChildURI || !aTerminateSender) {
+ return RemoteStreamPromise::CreateAndReject(NS_ERROR_INVALID_ARG, __func__);
+ }
+
+ *aTerminateSender = true;
+ nsresult rv;
+
+ // We should never receive a URI that isn't for a moz-page-thumb because
+ // these requests ordinarily come from the child's PageThumbProtocolHandler.
+ // Ensure this request is for a moz-page-thumb URI. A compromised child
+ // process could send us any URI.
+ bool isPageThumbScheme = false;
+ if (NS_FAILED(aChildURI->SchemeIs(PAGE_THUMB_SCHEME, &isPageThumbScheme)) ||
+ !isPageThumbScheme) {
+ return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNKNOWN_PROTOCOL,
+ __func__);
+ }
+
+ // We should never receive a URI that does not have "thumbnails" as the host.
+ nsAutoCString host;
+ if (NS_FAILED(aChildURI->GetAsciiHost(host)) ||
+ !(host.EqualsLiteral(PAGE_THUMB_HOST) ||
+ host.EqualsLiteral(PLACES_PREVIEWS_HOST))) {
+ return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__);
+ }
+
+ // For errors after this point, we want to propagate the error to
+ // the child, but we don't force the child process to be terminated.
+ *aTerminateSender = false;
+
+ // Make sure the child URI resolves to a file URI. We will then get a file
+ // channel for the request. The resultant channel should be a file channel
+ // because we only request remote streams for resource loads where the URI
+ // resolves to a file.
+ nsAutoCString resolvedSpec;
+ rv = ResolveURI(aChildURI, resolvedSpec);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ nsAutoCString resolvedScheme;
+ rv = net_ExtractURLScheme(resolvedSpec, resolvedScheme);
+ if (NS_FAILED(rv) || !resolvedScheme.EqualsLiteral("file")) {
+ return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__);
+ }
+
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ rv = ioService->NewURI(resolvedSpec, nullptr, nullptr,
+ getter_AddRefs(resolvedURI));
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ // We use the system principal to get a file channel for the request,
+ // but only after we've checked (above) that the child URI is of
+ // moz-page-thumb scheme and that the URI host matches PAGE_THUMB_HOST.
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), resolvedURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ auto promiseHolder = MakeUnique<MozPromiseHolder<RemoteStreamPromise>>();
+ RefPtr<RemoteStreamPromise> promise = promiseHolder->Ensure(__func__);
+
+ nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ nsAutoCString contentType;
+ rv = mime->GetTypeFromURI(aChildURI, contentType);
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ rv = NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction(
+ "PageThumbProtocolHandler::NewStream",
+ [contentType, channel, holder = std::move(promiseHolder)]() {
+ nsresult rv;
+
+ nsCOMPtr<nsIFileChannel> fileChannel =
+ do_QueryInterface(channel, &rv);
+ if (NS_FAILED(rv)) {
+ holder->Reject(rv, __func__);
+ return;
+ }
+
+ nsCOMPtr<nsIFile> requestedFile;
+ rv = fileChannel->GetFile(getter_AddRefs(requestedFile));
+ if (NS_FAILED(rv)) {
+ holder->Reject(rv, __func__);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
+ requestedFile, PR_RDONLY, -1);
+ if (NS_FAILED(rv)) {
+ holder->Reject(rv, __func__);
+ return;
+ }
+
+ RemoteStreamInfo info(inputStream, contentType, -1);
+
+ holder->Resolve(std::move(info), __func__);
+ }),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+
+ if (NS_FAILED(rv)) {
+ return RemoteStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ return promise;
+}
+
+bool PageThumbProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) {
+ // This should match the scheme in PageThumbs.jsm. We will only resolve
+ // URIs for thumbnails generated by PageThumbs here.
+ if (!aHost.EqualsLiteral(PAGE_THUMB_HOST) &&
+ !aHost.EqualsLiteral(PLACES_PREVIEWS_HOST)) {
+ // moz-page-thumb should always have a "thumbnails" host. We do not intend
+ // to allow substitution rules to be created for moz-page-thumb.
+ return false;
+ }
+
+ // Regardless of the outcome, the scheme will be resolved to file://.
+ aResult.Assign("file://");
+
+ if (IsNeckoChild()) {
+ // We will resolve the URI in the parent if load is performed in the child
+ // because the child does not have access to the profile directory path.
+ // Technically we could retrieve the path from dom::ContentChild, but I
+ // would prefer to obtain the path from PageThumbsStorageService (which
+ // depends on OS.Path). Here, we resolve to the same URI, with the file://
+ // scheme. This won't ever be accessed directly by the content process,
+ // and is mainly used to keep the substitution protocol handler mechanism
+ // happy.
+ aResult.Append(aHost);
+ aResult.Append(aPath);
+ } else {
+ // Resolve the URI in the parent to the thumbnail file URI since we will
+ // attempt to open the channel to load the file after this.
+ nsAutoString thumbnailUrl;
+ nsresult rv = GetThumbnailPath(aPath, aHost, thumbnailUrl);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ aResult.Append(NS_ConvertUTF16toUTF8(thumbnailUrl));
+ }
+
+ return true;
+}
+
+nsresult PageThumbProtocolHandler::SubstituteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aRetVal) {
+ // Check if URI resolves to a file URI.
+ nsAutoCString resolvedSpec;
+ MOZ_TRY(ResolveURI(aURI, resolvedSpec));
+
+ nsAutoCString scheme;
+ MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme));
+
+ if (!scheme.EqualsLiteral("file")) {
+ NS_WARNING("moz-page-thumb URIs should only resolve to file URIs.");
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ // Load the URI remotely if accessed from a child.
+ if (IsNeckoChild()) {
+ MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, aRetVal));
+ }
+
+ return NS_OK;
+}
+
+Result<Ok, nsresult> PageThumbProtocolHandler::SubstituteRemoteChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_TRY(aURI ? NS_OK : NS_ERROR_INVALID_ARG);
+ MOZ_TRY(aLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG);
+
+#ifdef DEBUG
+ nsAutoCString resolvedSpec;
+ MOZ_TRY(ResolveURI(aURI, resolvedSpec));
+
+ nsAutoCString scheme;
+ MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme));
+
+ MOZ_ASSERT(scheme.EqualsLiteral("file"));
+#endif /* DEBUG */
+
+ RefPtr<RemoteStreamGetter> streamGetter =
+ new RemoteStreamGetter(aURI, aLoadInfo);
+
+ NewSimpleChannel(aURI, aLoadInfo, streamGetter, aRetVal);
+ return Ok();
+}
+
+nsresult PageThumbProtocolHandler::GetThumbnailPath(const nsACString& aPath,
+ const nsACString& aHost,
+ nsString& aThumbnailPath) {
+ MOZ_ASSERT(!IsNeckoChild());
+
+ // Ensures that the provided path has a query string. We will start parsing
+ // from there.
+ int32_t queryIndex = aPath.FindChar('?');
+ if (queryIndex <= 0) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // Extract URL from query string.
+ nsAutoString url;
+ bool found =
+ URLParams::Extract(Substring(aPath, queryIndex + 1), u"url"_ns, url);
+ if (!found || url.IsVoid()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv;
+ if (aHost.EqualsLiteral(PAGE_THUMB_HOST)) {
+ nsCOMPtr<nsIPageThumbsStorageService> pageThumbsStorage =
+ do_GetService("@mozilla.org/thumbnails/pagethumbs-service;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ // Use PageThumbsStorageService to get the local file path of the screenshot
+ // for the given URL.
+ rv = pageThumbsStorage->GetFilePathForURL(url, aThumbnailPath);
+#ifdef MOZ_PLACES
+ } else if (aHost.EqualsLiteral(PLACES_PREVIEWS_HOST)) {
+ nsCOMPtr<nsIPlacesPreviewsHelperService> helper =
+ do_GetService("@mozilla.org/places/previews-helper;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ rv = helper->GetFilePathForURL(url, aThumbnailPath);
+#endif
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unknown thumbnail host");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+// static
+void PageThumbProtocolHandler::NewSimpleChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadinfo, RemoteStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal) {
+ nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
+ aURI, aLoadinfo, aStreamGetter,
+ [](nsIStreamListener* listener, nsIChannel* simpleChannel,
+ RemoteStreamGetter* getter) -> RequestOrReason {
+ return getter->GetAsync(listener, simpleChannel,
+ &NeckoChild::SendGetPageThumbStream);
+ });
+
+ channel.swap(*aRetVal);
+}
+
+#undef LOG
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/PageThumbProtocolHandler.h b/netwerk/protocol/res/PageThumbProtocolHandler.h
new file mode 100644
index 0000000000..da670d52d4
--- /dev/null
+++ b/netwerk/protocol/res/PageThumbProtocolHandler.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PageThumbProtocolHandler_h___
+#define PageThumbProtocolHandler_h___
+
+#include "mozilla/Result.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/net/RemoteStreamGetter.h"
+#include "SubstitutingProtocolHandler.h"
+#include "nsIInputStream.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace net {
+
+class RemoteStreamGetter;
+
+class PageThumbProtocolHandler final : public nsISubstitutingProtocolHandler,
+ public SubstitutingProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSIPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+ NS_FORWARD_NSISUBSTITUTINGPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+
+ static already_AddRefed<PageThumbProtocolHandler> GetSingleton();
+
+ /**
+ * To be called in the parent process to obtain an input stream for the
+ * given thumbnail.
+ *
+ * @param aChildURI a moz-page-thumb URI sent from the child.
+ * @param aTerminateSender out param set to true when the params are invalid
+ * and indicate the child should be terminated. If |aChildURI| is
+ * not a moz-page-thumb URI, the child is in an invalid state and
+ * should be terminated. This outparam will be set synchronously.
+ *
+ * @return RemoteStreamPromise
+ * The RemoteStreamPromise will resolve with an RemoteStreamInfo on
+ * success, and reject with an nsresult on failure.
+ */
+ RefPtr<RemoteStreamPromise> NewStream(nsIURI* aChildURI,
+ bool* aTerminateSender);
+
+ protected:
+ ~PageThumbProtocolHandler() = default;
+
+ private:
+ explicit PageThumbProtocolHandler();
+
+ [[nodiscard]] bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) override;
+
+ /**
+ * On entry to this function, *aRetVal is expected to be non-null and already
+ * addrefed. This function may release the object stored in *aRetVal on entry
+ * and write a new pointer to an already addrefed channel to *aRetVal.
+ *
+ * @param aURI the moz-page-thumb URI.
+ * @param aLoadInfo the loadinfo for the request.
+ * @param aRetVal in/out channel param referring to the channel that
+ * might need to be substituted with a remote channel.
+ * @return NS_OK if channel has been substituted successfully or no
+ * substitution at all. Otherwise, returns an error. This function
+ * will return NS_ERROR_NO_INTERFACE if the URI resolves to a
+ * non file:// URI.
+ */
+ [[nodiscard]] virtual nsresult SubstituteChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal) override;
+
+ /**
+ * This replaces the provided channel with a channel that will proxy the load
+ * to the parent process.
+ *
+ * @param aURI the moz-page-thumb URI.
+ * @param aLoadInfo the loadinfo for the request.
+ * @param aRetVal in/out channel param referring to the channel that
+ * might need to be substituted with a remote channel.
+ * @return NS_OK if the replacement channel was created successfully.
+ * Otherwise, returns an error.
+ */
+ Result<Ok, nsresult> SubstituteRemoteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aRetVal);
+
+ /*
+ * Extracts the URL from the query string in the given moz-page-thumb URI
+ * and queries PageThumbsStorageService using the extracted URL to obtain
+ * the local file path of the screenshot. This should only be called from
+ * the parent because PageThumbsStorageService relies on the path of the
+ * profile directory, which is unavailable in the child.
+ *
+ * @param aPath the path of the moz-page-thumb URI.
+ * @param aHost the host of the moz-page-thumb URI.
+ * @param aThumbnailPath in/out string param referring to the thumbnail path.
+ * @return NS_OK if the thumbnail path was obtained successfully. Otherwise
+ * returns an error.
+ */
+ nsresult GetThumbnailPath(const nsACString& aPath, const nsACString& aHost,
+ nsString& aThumbnailPath);
+
+ // To allow parent IPDL actors to invoke methods on this handler when
+ // handling moz-page-thumb requests from the child.
+ static StaticRefPtr<PageThumbProtocolHandler> sSingleton;
+
+ // Gets a SimpleChannel that wraps the provided channel.
+ static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo,
+ RemoteStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* PageThumbProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/RemoteStreamGetter.cpp b/netwerk/protocol/res/RemoteStreamGetter.cpp
new file mode 100644
index 0000000000..8bad84ef5e
--- /dev/null
+++ b/netwerk/protocol/res/RemoteStreamGetter.cpp
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RemoteStreamGetter.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ResultExtensions.h"
+#include "nsContentUtils.h"
+#include "nsIInputStreamPump.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(RemoteStreamGetter, nsICancelable)
+
+RemoteStreamGetter::RemoteStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo)
+ : mURI(aURI), mLoadInfo(aLoadInfo) {
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(aLoadInfo);
+}
+
+// Request an input stream from the parent.
+RequestOrReason RemoteStreamGetter::GetAsync(nsIStreamListener* aListener,
+ nsIChannel* aChannel,
+ Method aMethod) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(aMethod);
+
+ mListener = aListener;
+ mChannel = aChannel;
+
+ nsCOMPtr<nsICancelable> cancelableRequest(this);
+
+ RefPtr<RemoteStreamGetter> self = this;
+ LoadInfoArgs loadInfoArgs;
+ nsresult rv = ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfoArgs);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+
+ (gNeckoChild->*aMethod)(mURI, loadInfoArgs)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self](const Maybe<RemoteStreamInfo>& info) { self->OnStream(info); },
+ [self](const mozilla::ipc::ResponseRejectReason) {
+ self->OnStream(Nothing());
+ });
+ return RequestOrCancelable(WrapNotNull(cancelableRequest));
+}
+
+// Called to cancel the ongoing async request.
+NS_IMETHODIMP
+RemoteStreamGetter::Cancel(nsresult aStatus) {
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = aStatus;
+
+ if (mPump) {
+ mPump->Cancel(aStatus);
+ mPump = nullptr;
+ }
+
+ return NS_OK;
+}
+
+// static
+void RemoteStreamGetter::CancelRequest(nsIStreamListener* aListener,
+ nsIChannel* aChannel, nsresult aResult) {
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(aChannel);
+
+ aListener->OnStartRequest(aChannel);
+ aListener->OnStopRequest(aChannel, aResult);
+ aChannel->CancelWithReason(NS_BINDING_ABORTED,
+ "RemoteStreamGetter::CancelRequest"_ns);
+}
+
+// Handle an input stream sent from the parent.
+void RemoteStreamGetter::OnStream(const Maybe<RemoteStreamInfo>& aStreamInfo) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mListener);
+
+ nsCOMPtr<nsIChannel> channel = std::move(mChannel);
+
+ // We must keep an owning reference to the listener until we pass it on
+ // to AsyncRead.
+ nsCOMPtr<nsIStreamListener> listener = mListener.forget();
+
+ if (aStreamInfo.isNothing()) {
+ // The parent didn't send us back a stream.
+ CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED);
+ return;
+ }
+
+ if (mCanceled) {
+ // The channel that has created this stream getter has been canceled.
+ CancelRequest(listener, channel, mStatus);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> stream = std::move(aStreamInfo.ref().inputStream());
+ if (!stream) {
+ // We somehow failed to get a stream, so just cancel the request.
+ CancelRequest(listener, channel, mStatus);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ nsresult rv =
+ NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0, 0, false,
+ GetMainThreadSerialEventTarget());
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ return;
+ }
+
+ channel->SetContentType(aStreamInfo.ref().contentType());
+ channel->SetContentLength(aStreamInfo.ref().contentLength());
+
+ rv = pump->AsyncRead(listener);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, channel, rv);
+ return;
+ }
+
+ mPump = pump;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/RemoteStreamGetter.h b/netwerk/protocol/res/RemoteStreamGetter.h
new file mode 100644
index 0000000000..de2bd6b601
--- /dev/null
+++ b/netwerk/protocol/res/RemoteStreamGetter.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef RemoteStreamGetter_h___
+#define RemoteStreamGetter_h___
+
+#include "nsIChannel.h"
+#include "nsIInputStreamPump.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsICancelable.h"
+#include "SimpleChannel.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/Maybe.h"
+
+class nsILoadInfo;
+
+namespace mozilla {
+namespace net {
+
+using RemoteStreamPromise =
+ mozilla::MozPromise<RemoteStreamInfo, nsresult, false>;
+using Method = RefPtr<
+ MozPromise<Maybe<RemoteStreamInfo>, ipc::ResponseRejectReason, true>> (
+ PNeckoChild::*)(nsIURI*, const LoadInfoArgs&);
+
+/**
+ * Helper class used with SimpleChannel to asynchronously obtain an input
+ * stream and metadata from the parent for a remote protocol load from the
+ * child.
+ */
+class RemoteStreamGetter final : public nsICancelable {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ public:
+ RemoteStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo);
+
+ // Get an input stream from the parent asynchronously.
+ RequestOrReason GetAsync(nsIStreamListener* aListener, nsIChannel* aChannel,
+ Method aMethod);
+
+ // Handle an input stream being returned from the parent
+ void OnStream(const Maybe<RemoteStreamInfo>& aStreamInfo);
+
+ static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel,
+ nsresult aResult);
+
+ private:
+ ~RemoteStreamGetter() = default;
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ bool mCanceled{false};
+ nsresult mStatus{NS_OK};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* RemoteStreamGetter_h___ */
diff --git a/netwerk/protocol/res/SubstitutingJARURI.h b/netwerk/protocol/res/SubstitutingJARURI.h
new file mode 100644
index 0000000000..85976d4908
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingJARURI.h
@@ -0,0 +1,229 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SubstitutingJARURI_h
+#define SubstitutingJARURI_h
+
+#include "nsIStandardURL.h"
+#include "nsIURL.h"
+#include "nsJARURI.h"
+#include "nsISerializable.h"
+
+namespace mozilla {
+namespace net {
+
+#define NS_SUBSTITUTINGJARURI_IMPL_CID \
+ { /* 8f8c54ed-aba7-4ebf-ba6f-e58aec0aba4c */ \
+ 0x8f8c54ed, 0xaba7, 0x4ebf, { \
+ 0xba, 0x6f, 0xe5, 0x8a, 0xec, 0x0a, 0xba, 0x4c \
+ } \
+ }
+
+// Provides a Substituting URI for resource://-like substitutions which
+// allows consumers to access the underlying jar resource.
+class SubstitutingJARURI : public nsIJARURI,
+ public nsIStandardURL,
+ public nsISerializable {
+ protected:
+ // Contains the resource://-like URI to be mapped. nsIURI and nsIURL will
+ // forward to this.
+ nsCOMPtr<nsIURL> mSource;
+ // Contains the resolved jar resource, nsIJARURI forwards to this to let
+ // consumer acccess the underlying jar resource.
+ nsCOMPtr<nsIJARURI> mResolved;
+ virtual ~SubstitutingJARURI() = default;
+
+ public:
+ SubstitutingJARURI(nsIURL* source, nsIJARURI* resolved);
+ // For deserializing
+ explicit SubstitutingJARURI() = default;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERIALIZABLE
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_SUBSTITUTINGJARURI_IMPL_CID)
+
+ NS_FORWARD_SAFE_NSIURL(mSource)
+ NS_FORWARD_SAFE_NSIJARURI(mResolved)
+
+ // enum used in a few places to specify how .ref attribute should be handled
+ enum RefHandlingEnum { eIgnoreRef, eHonorRef };
+
+ // We cannot forward Equals* methods to |mSource| so we override them here
+ NS_IMETHOD EqualsExceptRef(nsIURI* aOther, bool* aResult) override;
+ NS_IMETHOD Equals(nsIURI* aOther, bool* aResult) override;
+
+ // Helper to share code between Equals methods.
+ virtual nsresult EqualsInternal(nsIURI* aOther,
+ RefHandlingEnum aRefHandlingMode,
+ bool* aResult);
+
+ // Forward the rest of nsIURI to mSource
+ NS_IMETHOD GetSpec(nsACString& aSpec) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetSpec(aSpec);
+ }
+ NS_IMETHOD GetPrePath(nsACString& aPrePath) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetPrePath(aPrePath);
+ }
+ NS_IMETHOD GetScheme(nsACString& aScheme) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetScheme(aScheme);
+ }
+ NS_IMETHOD GetUserPass(nsACString& aUserPass) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetUserPass(aUserPass);
+ }
+ NS_IMETHOD GetUsername(nsACString& aUsername) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetUsername(aUsername);
+ }
+ NS_IMETHOD GetPassword(nsACString& aPassword) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetPassword(aPassword);
+ }
+ NS_IMETHOD GetHostPort(nsACString& aHostPort) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHostPort(aHostPort);
+ }
+ NS_IMETHOD GetHost(nsACString& aHost) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHost(aHost);
+ }
+ NS_IMETHOD GetPort(int32_t* aPort) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetPort(aPort);
+ }
+ NS_IMETHOD GetPathQueryRef(nsACString& aPathQueryRef) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetPathQueryRef(aPathQueryRef);
+ }
+ NS_IMETHOD SchemeIs(const char* scheme, bool* _retval) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->SchemeIs(scheme, _retval);
+ }
+ NS_IMETHOD Resolve(const nsACString& relativePath,
+ nsACString& _retval) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->Resolve(relativePath, _retval);
+ }
+ NS_IMETHOD GetAsciiSpec(nsACString& aAsciiSpec) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetAsciiSpec(aAsciiSpec);
+ }
+ NS_IMETHOD GetAsciiHostPort(nsACString& aAsciiHostPort) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetAsciiHostPort(aAsciiHostPort);
+ }
+ NS_IMETHOD GetAsciiHost(nsACString& aAsciiHost) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetAsciiHost(aAsciiHost);
+ }
+ NS_IMETHOD GetRef(nsACString& aRef) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetRef(aRef);
+ }
+ NS_IMETHOD GetSpecIgnoringRef(nsACString& aSpecIgnoringRef) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetSpecIgnoringRef(aSpecIgnoringRef);
+ }
+ NS_IMETHOD GetHasRef(bool* aHasRef) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHasRef(aHasRef);
+ }
+ NS_IMETHOD GetHasUserPass(bool* aHasUserPass) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetHasUserPass(aHasUserPass);
+ }
+ NS_IMETHOD GetHasQuery(bool* aHasQuery) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHasQuery(aHasQuery);
+ }
+ NS_IMETHOD GetFilePath(nsACString& aFilePath) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetFilePath(aFilePath);
+ }
+ NS_IMETHOD GetQuery(nsACString& aQuery) override {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetQuery(aQuery);
+ }
+ NS_IMETHOD GetDisplayHost(nsACString& aDisplayHost) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetDisplayHost(aDisplayHost);
+ }
+ NS_IMETHOD GetDisplayHostPort(nsACString& aDisplayHostPort) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetDisplayHostPort(aDisplayHostPort);
+ }
+ NS_IMETHOD GetDisplaySpec(nsACString& aDisplaySpec) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetDisplaySpec(aDisplaySpec);
+ }
+ NS_IMETHOD GetDisplayPrePath(nsACString& aDisplayPrePath) override {
+ return !mSource ? NS_ERROR_NULL_POINTER
+ : mSource->GetDisplayPrePath(aDisplayPrePath);
+ }
+ NS_IMETHOD Mutate(nsIURIMutator** _retval) override;
+ NS_IMETHOD_(void) Serialize(mozilla::ipc::URIParams& aParams) override;
+
+ private:
+ nsresult Clone(nsIURI** aURI);
+ nsresult SetSpecInternal(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetScheme(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetUserPass(const nsACString& aInput);
+ nsresult SetUsername(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetPassword(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetHostPort(const nsACString& aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetHost(const nsACString& input) { return NS_ERROR_NOT_IMPLEMENTED; }
+ nsresult SetPort(int32_t aPort);
+ nsresult SetPathQueryRef(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetRef(const nsACString& input) { return NS_ERROR_NOT_IMPLEMENTED; }
+ nsresult SetFilePath(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetQuery(const nsACString& input) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult SetQueryWithEncoding(const nsACString& input,
+ const mozilla::Encoding* encoding) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ bool Deserialize(const mozilla::ipc::URIParams& aParams);
+ nsresult ReadPrivate(nsIObjectInputStream* aStream);
+
+ public:
+ class Mutator final : public nsIURIMutator,
+ public BaseURIMutator<SubstitutingJARURI>,
+ public nsISerializable {
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI)
+ NS_DEFINE_NSIMUTATOR_COMMON
+
+ // nsISerializable overrides
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD Read(nsIObjectInputStream* aStream) override {
+ return InitFromInputStream(aStream);
+ }
+
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+
+ friend SubstitutingJARURI;
+ };
+
+ friend BaseURIMutator<SubstitutingJARURI>;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(SubstitutingJARURI,
+ NS_SUBSTITUTINGJARURI_IMPL_CID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SubstitutingJARURI_h */
diff --git a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
new file mode 100644
index 0000000000..523aaa435d
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -0,0 +1,723 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/URIUtils.h"
+
+#include "SubstitutingProtocolHandler.h"
+#include "SubstitutingURL.h"
+#include "SubstitutingJARURI.h"
+#include "nsIChannel.h"
+#include "nsIIOService.h"
+#include "nsIFile.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIClassInfoImpl.h"
+
+using mozilla::dom::ContentParent;
+
+namespace mozilla {
+namespace net {
+
+// Log module for Substituting Protocol logging. We keep the pre-existing module
+// name of "nsResProtocol" to avoid disruption.
+static LazyLogModule gResLog("nsResProtocol");
+
+static NS_DEFINE_CID(kSubstitutingJARURIImplCID,
+ NS_SUBSTITUTINGJARURI_IMPL_CID);
+
+//---------------------------------------------------------------------------------
+// SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile
+// resolution
+//---------------------------------------------------------------------------------
+
+// The list of interfaces should be in sync with nsStandardURL
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(SubstitutingURL::Mutator, nsIURISetters,
+ nsIURIMutator, nsIStandardURLMutator,
+ nsIURLMutator, nsIFileURLMutator,
+ nsISerializable)
+
+NS_IMPL_CLASSINFO(SubstitutingURL, nullptr, nsIClassInfo::THREADSAFE,
+ NS_SUBSTITUTINGURL_CID)
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(SubstitutingURL)
+
+NS_IMPL_ADDREF_INHERITED(SubstitutingURL, nsStandardURL)
+NS_IMPL_RELEASE_INHERITED(SubstitutingURL, nsStandardURL)
+NS_IMPL_QUERY_INTERFACE_CI_INHERITED0(SubstitutingURL, nsStandardURL)
+
+nsresult SubstitutingURL::EnsureFile() {
+ nsAutoCString ourScheme;
+ nsresult rv = GetScheme(ourScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the handler associated with this scheme. It would be nice to just
+ // pass this in when constructing SubstitutingURLs, but we need a generic
+ // factory constructor.
+ nsCOMPtr<nsIIOService> io = do_GetIOService(&rv);
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = io->GetProtocolHandler(ourScheme.get(), getter_AddRefs(handler));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISubstitutingProtocolHandler> substHandler =
+ do_QueryInterface(handler);
+ if (!substHandler) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsAutoCString spec;
+ rv = substHandler->ResolveURI(this, spec);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString scheme;
+ rv = net_ExtractURLScheme(spec, scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ // Bug 585869:
+ // In most cases, the scheme is jar if it's not file.
+ // Regardless, net_GetFileFromURLSpec should be avoided
+ // when the scheme isn't file.
+ if (!scheme.EqualsLiteral("file")) return NS_ERROR_NO_INTERFACE;
+
+ return net_GetFileFromURLSpec(spec, getter_AddRefs(mFile));
+}
+
+/* virtual */
+nsStandardURL* SubstitutingURL::StartClone() {
+ SubstitutingURL* clone = new SubstitutingURL();
+ return clone;
+}
+
+void SubstitutingURL::Serialize(ipc::URIParams& aParams) {
+ nsStandardURL::Serialize(aParams);
+ aParams.get_StandardURLParams().isSubstituting() = true;
+}
+
+// SubstitutingJARURI
+
+SubstitutingJARURI::SubstitutingJARURI(nsIURL* source, nsIJARURI* resolved)
+ : mSource(source), mResolved(resolved) {}
+
+// SubstitutingJARURI::nsIURI
+
+NS_IMETHODIMP
+SubstitutingJARURI::Equals(nsIURI* aOther, bool* aResult) {
+ return EqualsInternal(aOther, eHonorRef, aResult);
+}
+
+NS_IMETHODIMP
+SubstitutingJARURI::EqualsExceptRef(nsIURI* aOther, bool* aResult) {
+ return EqualsInternal(aOther, eIgnoreRef, aResult);
+}
+
+nsresult SubstitutingJARURI::EqualsInternal(nsIURI* aOther,
+ RefHandlingEnum aRefHandlingMode,
+ bool* aResult) {
+ *aResult = false;
+ if (!aOther) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ RefPtr<SubstitutingJARURI> other;
+ rv =
+ aOther->QueryInterface(kSubstitutingJARURIImplCID, getter_AddRefs(other));
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ // We only need to check the source as the resolved URI is the same for a
+ // given source
+ return aRefHandlingMode == eHonorRef
+ ? mSource->Equals(other->mSource, aResult)
+ : mSource->EqualsExceptRef(other->mSource, aResult);
+}
+
+NS_IMETHODIMP
+SubstitutingJARURI::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<SubstitutingJARURI::Mutator> mutator =
+ new SubstitutingJARURI::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
+
+void SubstitutingJARURI::Serialize(mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ SubstitutingJARURIParams params;
+ URIParams source;
+ URIParams resolved;
+
+ mSource->Serialize(source);
+ mResolved->Serialize(resolved);
+ params.source() = source;
+ params.resolved() = resolved;
+ aParams = params;
+}
+
+// SubstitutingJARURI::nsISerializable
+
+NS_IMETHODIMP
+SubstitutingJARURI::Read(nsIObjectInputStream* aStream) {
+ MOZ_ASSERT(!mSource);
+ MOZ_ASSERT(!mResolved);
+ NS_ENSURE_ARG_POINTER(aStream);
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> source;
+ rv = aStream->ReadObject(true, getter_AddRefs(source));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSource = do_QueryInterface(source, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> resolved;
+ rv = aStream->ReadObject(true, getter_AddRefs(resolved));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mResolved = do_QueryInterface(resolved, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SubstitutingJARURI::Write(nsIObjectOutputStream* aStream) {
+ NS_ENSURE_ARG_POINTER(aStream);
+
+ nsresult rv;
+ rv = aStream->WriteCompoundObject(mSource, NS_GET_IID(nsISupports), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteCompoundObject(mResolved, NS_GET_IID(nsISupports), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult SubstitutingJARURI::Clone(nsIURI** aURI) {
+ RefPtr<SubstitutingJARURI> uri = new SubstitutingJARURI();
+ // SubstitutingJARURI's mSource/mResolved isn't mutable.
+ uri->mSource = mSource;
+ uri->mResolved = mResolved;
+ uri.forget(aURI);
+
+ return NS_OK;
+}
+
+nsresult SubstitutingJARURI::SetUserPass(const nsACString& aInput) {
+ // If setting same value in mSource, return NS_OK;
+ if (!mSource) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsAutoCString sourceUserPass;
+ nsresult rv = mSource->GetUserPass(sourceUserPass);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (aInput.Equals(sourceUserPass)) {
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult SubstitutingJARURI::SetPort(int32_t aPort) {
+ // If setting same value in mSource, return NS_OK;
+ if (!mSource) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ int32_t sourcePort = -1;
+ nsresult rv = mSource->GetPort(&sourcePort);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (aPort == sourcePort) {
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+bool SubstitutingJARURI::Deserialize(const mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ if (aParams.type() != URIParams::TSubstitutingJARURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const SubstitutingJARURIParams& jarUriParams =
+ aParams.get_SubstitutingJARURIParams();
+
+ nsCOMPtr<nsIURI> source = DeserializeURI(jarUriParams.source());
+ nsresult rv;
+ mSource = do_QueryInterface(source, &rv);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ nsCOMPtr<nsIURI> jarUri = DeserializeURI(jarUriParams.resolved());
+ mResolved = do_QueryInterface(jarUri, &rv);
+ return NS_SUCCEEDED(rv);
+}
+
+nsresult SubstitutingJARURI::ReadPrivate(nsIObjectInputStream* aStream) {
+ return Read(aStream);
+}
+
+NS_IMPL_CLASSINFO(SubstitutingJARURI, nullptr, 0, NS_SUBSTITUTINGJARURI_CID)
+
+NS_IMPL_ADDREF(SubstitutingJARURI)
+NS_IMPL_RELEASE(SubstitutingJARURI)
+
+NS_INTERFACE_MAP_BEGIN(SubstitutingJARURI)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURI)
+ NS_INTERFACE_MAP_ENTRY(nsIJARURI)
+ NS_INTERFACE_MAP_ENTRY(nsIURL)
+ NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ if (aIID.Equals(kSubstitutingJARURIImplCID)) {
+ foundInterface = static_cast<nsIURI*>(this);
+ } else
+ NS_INTERFACE_MAP_ENTRY(nsIURI)
+ NS_IMPL_QUERY_CLASSINFO(SubstitutingJARURI)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CI_INTERFACE_GETTER(SubstitutingJARURI, nsIURI, nsIJARURI, nsIURL,
+ nsIStandardURL, nsISerializable)
+
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(SubstitutingJARURI::Mutator, nsIURISetters,
+ nsIURIMutator, nsISerializable)
+
+SubstitutingProtocolHandler::SubstitutingProtocolHandler(const char* aScheme,
+ bool aEnforceFileOrJar)
+ : mScheme(aScheme),
+ mSubstitutionsLock("SubstitutingProtocolHandler::mSubstitutions"),
+ mSubstitutions(16),
+ mEnforceFileOrJar(aEnforceFileOrJar) {
+ ConstructInternal();
+}
+
+void SubstitutingProtocolHandler::ConstructInternal() {
+ nsresult rv;
+ mIOService = do_GetIOService(&rv);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOService);
+}
+
+//
+// IPC marshalling.
+//
+
+nsresult SubstitutingProtocolHandler::CollectSubstitutions(
+ nsTArray<SubstitutionMapping>& aMappings) {
+ AutoReadLock lock(mSubstitutionsLock);
+ for (const auto& substitutionEntry : mSubstitutions) {
+ const SubstitutionEntry& entry = substitutionEntry.GetData();
+ nsCOMPtr<nsIURI> uri = entry.baseURI;
+ SerializedURI serialized;
+ if (uri) {
+ nsresult rv = uri->GetSpec(serialized.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ SubstitutionMapping substitution = {mScheme,
+ nsCString(substitutionEntry.GetKey()),
+ serialized, entry.flags};
+ aMappings.AppendElement(substitution);
+ }
+
+ return NS_OK;
+}
+
+nsresult SubstitutingProtocolHandler::SendSubstitution(const nsACString& aRoot,
+ nsIURI* aBaseURI,
+ uint32_t aFlags) {
+ if (GeckoProcessType_Content == XRE_GetProcessType()) {
+ return NS_OK;
+ }
+
+ nsTArray<ContentParent*> parents;
+ ContentParent::GetAll(parents);
+ if (!parents.Length()) {
+ return NS_OK;
+ }
+
+ SubstitutionMapping mapping;
+ mapping.scheme = mScheme;
+ mapping.path = aRoot;
+ if (aBaseURI) {
+ nsresult rv = aBaseURI->GetSpec(mapping.resolvedURI.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ mapping.flags = aFlags;
+
+ for (uint32_t i = 0; i < parents.Length(); i++) {
+ Unused << parents[i]->SendRegisterChromeItem(mapping);
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsIProtocolHandler
+//----------------------------------------------------------------------------
+
+nsresult SubstitutingProtocolHandler::GetScheme(nsACString& result) {
+ result = mScheme;
+ return NS_OK;
+}
+
+nsresult SubstitutingProtocolHandler::NewURI(const nsACString& aSpec,
+ const char* aCharset,
+ nsIURI* aBaseURI,
+ nsIURI** aResult) {
+ // unescape any %2f and %2e to make sure nsStandardURL coalesces them.
+ // Later net_GetFileFromURLSpec() will do a full unescape and we want to
+ // treat them the same way the file system will. (bugs 380994, 394075)
+ nsresult rv;
+ nsAutoCString spec;
+ const char* src = aSpec.BeginReading();
+ const char* end = aSpec.EndReading();
+ const char* last = src;
+
+ spec.SetCapacity(aSpec.Length() + 1);
+ for (; src < end; ++src) {
+ if (*src == '%' && (src < end - 2) && *(src + 1) == '2') {
+ char ch = '\0';
+ if (*(src + 2) == 'f' || *(src + 2) == 'F') {
+ ch = '/';
+ } else if (*(src + 2) == 'e' || *(src + 2) == 'E') {
+ ch = '.';
+ }
+
+ if (ch) {
+ if (last < src) {
+ spec.Append(last, src - last);
+ }
+ spec.Append(ch);
+ src += 2;
+ last = src + 1; // src will be incremented by the loop
+ }
+ }
+ if (*src == '?' || *src == '#') {
+ break; // Don't escape %2f and %2e in the query or ref parts of the URI
+ }
+ }
+
+ if (last < end) {
+ spec.Append(last, end - last);
+ }
+
+ nsCOMPtr<nsIURI> base(aBaseURI);
+ nsCOMPtr<nsIURL> uri;
+ rv =
+ NS_MutateURI(new SubstitutingURL::Mutator())
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_STANDARD,
+ -1, spec, aCharset, base, nullptr)
+ .Finalize(uri);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString host;
+ rv = uri->GetHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ // "android" is the only root that would return the RESOLVE_JAR_URI flag
+ // see nsResProtocolHandler::GetSubstitutionInternal
+ if (MustResolveJAR(host)) {
+ return ResolveJARURI(uri, aResult);
+ }
+
+ uri.forget(aResult);
+ return NS_OK;
+}
+
+nsresult SubstitutingProtocolHandler::ResolveJARURI(nsIURL* aURL,
+ nsIURI** aResult) {
+ nsAutoCString spec;
+ nsresult rv = ResolveURI(aURL, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ rv = NS_NewURI(getter_AddRefs(resolvedURI), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(resolvedURI);
+ nsAutoCString scheme;
+ innermostURI->GetScheme(scheme);
+
+ // We only ever want to resolve to a local jar.
+ NS_ENSURE_TRUE(scheme.EqualsLiteral("file"), NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIJARURI> jarURI(do_QueryInterface(resolvedURI));
+ if (!jarURI) {
+ // This substitution does not resolve to a jar: URL, so we just
+ // return the plain SubstitutionURL
+ nsCOMPtr<nsIURI> url = aURL;
+ url.forget(aResult);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIJARURI> result = new SubstitutingJARURI(aURL, jarURI);
+ result.forget(aResult);
+
+ return rv;
+}
+
+nsresult SubstitutingProtocolHandler::NewChannel(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(aLoadInfo);
+
+ nsAutoCString spec;
+ nsresult rv = ResolveURI(uri, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> newURI;
+ rv = NS_NewURI(getter_AddRefs(newURI), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We don't want to allow the inner protocol handler to modify the result
+ // principal URI since we want either |uri| or anything pre-set by upper
+ // layers to prevail.
+ nsCOMPtr<nsIURI> savedResultPrincipalURI;
+ rv =
+ aLoadInfo->GetResultPrincipalURI(getter_AddRefs(savedResultPrincipalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewChannelInternal(result, newURI, aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetResultPrincipalURI(savedResultPrincipalURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = (*result)->SetOriginalURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return SubstituteChannel(uri, aLoadInfo, result);
+}
+
+nsresult SubstitutingProtocolHandler::AllowPort(int32_t port,
+ const char* scheme,
+ bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsISubstitutingProtocolHandler
+//----------------------------------------------------------------------------
+
+nsresult SubstitutingProtocolHandler::SetSubstitution(const nsACString& root,
+ nsIURI* baseURI) {
+ // Add-ons use this API but they should not be able to make anything
+ // content-accessible.
+ return SetSubstitutionWithFlags(root, baseURI, 0);
+}
+
+nsresult SubstitutingProtocolHandler::SetSubstitutionWithFlags(
+ const nsACString& origRoot, nsIURI* baseURI, uint32_t flags) {
+ nsAutoCString root;
+ ToLowerCase(origRoot, root);
+
+ if (!baseURI) {
+ {
+ AutoWriteLock lock(mSubstitutionsLock);
+ mSubstitutions.Remove(root);
+ }
+
+ return SendSubstitution(root, baseURI, flags);
+ }
+
+ // If baseURI isn't a same-scheme URI, we can set the substitution
+ // immediately.
+ nsAutoCString scheme;
+ nsresult rv = baseURI->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!scheme.Equals(mScheme)) {
+ if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") &&
+ !scheme.EqualsLiteral("jar") && !scheme.EqualsLiteral("app") &&
+ !scheme.EqualsLiteral("resource")) {
+ NS_WARNING("Refusing to create substituting URI to non-file:// target");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ {
+ AutoWriteLock lock(mSubstitutionsLock);
+ mSubstitutions.InsertOrUpdate(root, SubstitutionEntry{baseURI, flags});
+ }
+
+ return SendSubstitution(root, baseURI, flags);
+ }
+
+ // baseURI is a same-type substituting URI, let's resolve it first.
+ nsAutoCString newBase;
+ rv = ResolveURI(baseURI, newBase);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> newBaseURI;
+ rv =
+ mIOService->NewURI(newBase, nullptr, nullptr, getter_AddRefs(newBaseURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ {
+ AutoWriteLock lock(mSubstitutionsLock);
+ mSubstitutions.InsertOrUpdate(root, SubstitutionEntry{newBaseURI, flags});
+ }
+
+ return SendSubstitution(root, newBaseURI, flags);
+}
+
+nsresult SubstitutingProtocolHandler::GetSubstitution(
+ const nsACString& origRoot, nsIURI** result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsAutoCString root;
+ ToLowerCase(origRoot, root);
+
+ {
+ AutoReadLock lock(mSubstitutionsLock);
+ SubstitutionEntry entry;
+ if (mSubstitutions.Get(root, &entry)) {
+ nsCOMPtr<nsIURI> baseURI = entry.baseURI;
+ baseURI.forget(result);
+ return NS_OK;
+ }
+ }
+
+ uint32_t flags;
+ return GetSubstitutionInternal(root, result, &flags);
+}
+
+nsresult SubstitutingProtocolHandler::GetSubstitutionFlags(
+ const nsACString& root, uint32_t* flags) {
+#ifdef DEBUG
+ nsAutoCString lcRoot;
+ ToLowerCase(root, lcRoot);
+ MOZ_ASSERT(root.Equals(lcRoot),
+ "GetSubstitutionFlags should never receive mixed-case root name");
+#endif
+
+ *flags = 0;
+
+ {
+ AutoReadLock lock(mSubstitutionsLock);
+
+ SubstitutionEntry entry;
+ if (mSubstitutions.Get(root, &entry)) {
+ *flags = entry.flags;
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<nsIURI> baseURI;
+ return GetSubstitutionInternal(root, getter_AddRefs(baseURI), flags);
+}
+
+nsresult SubstitutingProtocolHandler::HasSubstitution(
+ const nsACString& origRoot, bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsAutoCString root;
+ ToLowerCase(origRoot, root);
+
+ *result = HasSubstitution(root);
+ return NS_OK;
+}
+
+nsresult SubstitutingProtocolHandler::ResolveURI(nsIURI* uri,
+ nsACString& result) {
+ nsresult rv;
+
+ nsAutoCString host;
+ nsAutoCString path;
+ nsAutoCString pathname;
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
+ if (!url) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ rv = uri->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = url->GetFilePath(pathname);
+ if (NS_FAILED(rv)) return rv;
+
+ if (ResolveSpecialCases(host, path, pathname, result)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> baseURI;
+ rv = GetSubstitution(host, getter_AddRefs(baseURI));
+ if (NS_FAILED(rv)) return rv;
+
+ // Unescape the path so we can perform some checks on it.
+ NS_UnescapeURL(pathname);
+ if (pathname.FindChar('\\') != -1) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // Some code relies on an empty path resolving to a file rather than a
+ // directory.
+ NS_ASSERTION(path.CharAt(0) == '/', "Path must begin with '/'");
+ if (path.Length() == 1) {
+ rv = baseURI->GetSpec(result);
+ } else {
+ // Make sure we always resolve the path as file-relative to our target URI.
+ // When the baseURI is a nsIFileURL, and the directory it points to doesn't
+ // exist, it doesn't end with a /. In that case, a file-relative resolution
+ // is going to pick something in the parent directory, so we resolve using
+ // an absolute path derived from the full path in that case.
+ nsCOMPtr<nsIFileURL> baseDir = do_QueryInterface(baseURI);
+ if (baseDir) {
+ nsAutoCString basePath;
+ rv = baseURI->GetFilePath(basePath);
+ if (NS_SUCCEEDED(rv) && !StringEndsWith(basePath, "/"_ns)) {
+ // Cf. the assertion above, path already starts with a /, so prefixing
+ // with a string that doesn't end with one will leave us wit the right
+ // amount of /.
+ path.Insert(basePath, 0);
+ } else {
+ // Allow to fall through below.
+ baseDir = nullptr;
+ }
+ }
+ if (!baseDir) {
+ path.Insert('.', 0);
+ }
+ rv = baseURI->Resolve(path, result);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (MOZ_LOG_TEST(gResLog, LogLevel::Debug)) {
+ nsAutoCString spec;
+ uri->GetAsciiSpec(spec);
+ MOZ_LOG(gResLog, LogLevel::Debug,
+ ("%s\n -> %s\n", spec.get(), PromiseFlatCString(result).get()));
+ }
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/SubstitutingProtocolHandler.h b/netwerk/protocol/res/SubstitutingProtocolHandler.h
new file mode 100644
index 0000000000..6bdb27f38a
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SubstitutingProtocolHandler_h___
+#define SubstitutingProtocolHandler_h___
+
+#include "nsISubstitutingProtocolHandler.h"
+
+#include "nsTHashMap.h"
+#include "nsStandardURL.h"
+#include "nsJARURI.h"
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RWLock.h"
+
+class nsIIOService;
+
+namespace mozilla {
+namespace net {
+
+//
+// Base class for resource://-like substitution protocols.
+//
+// If you add a new protocol, make sure to change nsChromeRegistryChrome
+// to properly invoke CollectSubstitutions at the right time.
+class SubstitutingProtocolHandler {
+ public:
+ explicit SubstitutingProtocolHandler(const char* aScheme,
+ bool aEnforceFileOrJar = true);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SubstitutingProtocolHandler);
+ NS_DECL_NON_VIRTUAL_NSIPROTOCOLHANDLER;
+ NS_DECL_NON_VIRTUAL_NSISUBSTITUTINGPROTOCOLHANDLER;
+
+ bool HasSubstitution(const nsACString& aRoot) const {
+ AutoReadLock lock(const_cast<RWLock&>(mSubstitutionsLock));
+ return mSubstitutions.Get(aRoot, nullptr);
+ }
+
+ nsresult NewURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** aResult);
+
+ [[nodiscard]] nsresult CollectSubstitutions(
+ nsTArray<SubstitutionMapping>& aMappings);
+
+ protected:
+ virtual ~SubstitutingProtocolHandler() = default;
+ void ConstructInternal();
+
+ [[nodiscard]] nsresult SendSubstitution(const nsACString& aRoot,
+ nsIURI* aBaseURI, uint32_t aFlags);
+
+ nsresult GetSubstitutionFlags(const nsACString& root, uint32_t* flags);
+
+ // Override this in the subclass to try additional lookups after checking
+ // mSubstitutions.
+ [[nodiscard]] virtual nsresult GetSubstitutionInternal(
+ const nsACString& aRoot, nsIURI** aResult, uint32_t* aFlags) {
+ *aResult = nullptr;
+ *aFlags = 0;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Override this in the subclass to check for special case when resolving URIs
+ // _before_ checking substitutions.
+ [[nodiscard]] virtual bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) {
+ return false;
+ }
+
+ // This method should only return true if GetSubstitutionInternal would
+ // return the RESOLVE_JAR_URI flag.
+ [[nodiscard]] virtual bool MustResolveJAR(const nsACString& aRoot) {
+ return false;
+ }
+
+ // Override this in the subclass to check for special case when opening
+ // channels.
+ [[nodiscard]] virtual nsresult SubstituteChannel(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ return NS_OK;
+ }
+
+ nsIIOService* IOService() { return mIOService; }
+
+ private:
+ struct SubstitutionEntry {
+ nsCOMPtr<nsIURI> baseURI;
+ uint32_t flags = 0;
+ };
+
+ // Notifies all observers that a new substitution from |aRoot| to
+ // |aBaseURI| has been set/installed for this protocol handler.
+ void NotifyObservers(const nsACString& aRoot, nsIURI* aBaseURI);
+
+ nsCString mScheme;
+
+ RWLock mSubstitutionsLock;
+ nsTHashMap<nsCStringHashKey, SubstitutionEntry> mSubstitutions
+ MOZ_GUARDED_BY(mSubstitutionsLock);
+ nsCOMPtr<nsIIOService> mIOService;
+
+ // Returns a SubstitutingJARURI if |aUrl| maps to a |jar:| URI,
+ // otherwise will return |aURL|
+ nsresult ResolveJARURI(nsIURL* aURL, nsIURI** aResult);
+
+ // In general, we expect the principal of a document loaded from a
+ // substituting URI to be a content principal for that URI (rather than
+ // a principal for whatever is underneath). However, this only works if
+ // the protocol handler for the underlying URI doesn't set an explicit
+ // owner (which chrome:// does, for example). So we want to require that
+ // substituting URIs only map to other URIs of the same type, or to
+ // file:// and jar:// URIs.
+ //
+ // Enforcing this for ye olde resource:// URIs could carry compat risks, so
+ // we just try to enforce it on new protocols going forward.
+ bool mEnforceFileOrJar;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SubstitutingProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/SubstitutingURL.h b/netwerk/protocol/res/SubstitutingURL.h
new file mode 100644
index 0000000000..b9012f1665
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingURL.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SubstitutingURL_h
+#define SubstitutingURL_h
+
+#include "nsStandardURL.h"
+
+class nsIIOService;
+
+namespace mozilla {
+namespace net {
+
+// SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile
+// resolution
+class SubstitutingURL : public nsStandardURL {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ virtual nsStandardURL* StartClone() override;
+ [[nodiscard]] virtual nsresult EnsureFile() override;
+
+ private:
+ explicit SubstitutingURL() : nsStandardURL(true) {}
+ explicit SubstitutingURL(bool aSupportsFileURL) : nsStandardURL(true) {
+ MOZ_ASSERT(aSupportsFileURL);
+ }
+ virtual nsresult Clone(nsIURI** aURI) override {
+ return nsStandardURL::Clone(aURI);
+ }
+ virtual ~SubstitutingURL() = default;
+
+ public:
+ class Mutator : public TemplatedMutator<SubstitutingURL> {
+ NS_DECL_ISUPPORTS
+ public:
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+
+ SubstitutingURL* Create() override { return new SubstitutingURL(); }
+ };
+
+ NS_IMETHOD Mutate(nsIURIMutator** aMutator) override {
+ RefPtr<SubstitutingURL::Mutator> mutator = new SubstitutingURL::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+ }
+
+ NS_IMETHOD_(void) Serialize(ipc::URIParams& aParams) override;
+
+ friend BaseURIMutator<SubstitutingURL>;
+ friend TemplatedMutator<SubstitutingURL>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SubstitutingURL_h */
diff --git a/netwerk/protocol/res/moz.build b/netwerk/protocol/res/moz.build
new file mode 100644
index 0000000000..63407eebd8
--- /dev/null
+++ b/netwerk/protocol/res/moz.build
@@ -0,0 +1,42 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ "nsIResProtocolHandler.idl",
+ "nsISubstitutingProtocolHandler.idl",
+]
+
+XPIDL_MODULE = "necko_res"
+
+EXPORTS.mozilla.net += [
+ "ExtensionProtocolHandler.h",
+ "PageThumbProtocolHandler.h",
+ "RemoteStreamGetter.h",
+ "SubstitutingJARURI.h",
+ "SubstitutingProtocolHandler.h",
+ "SubstitutingURL.h",
+]
+
+EXPORTS += [
+ "nsResProtocolHandler.h",
+]
+
+UNIFIED_SOURCES += [
+ "ExtensionProtocolHandler.cpp",
+ "nsResProtocolHandler.cpp",
+ "PageThumbProtocolHandler.cpp",
+ "RemoteStreamGetter.cpp",
+ "SubstitutingProtocolHandler.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/xpcom/base",
+]
diff --git a/netwerk/protocol/res/nsIResProtocolHandler.idl b/netwerk/protocol/res/nsIResProtocolHandler.idl
new file mode 100644
index 0000000000..7046f2f1d4
--- /dev/null
+++ b/netwerk/protocol/res/nsIResProtocolHandler.idl
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISubstitutingProtocolHandler.idl"
+
+/**
+ * Protocol handler interface for the resource:// protocol
+ */
+[scriptable, uuid(241d34ac-9ed5-46d7-910c-7a9d914aa0c5)]
+interface nsIResProtocolHandler : nsISubstitutingProtocolHandler
+{
+ boolean allowContentToAccess(in nsIURI url);
+};
diff --git a/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
new file mode 100644
index 0000000000..cf2e0d42ab
--- /dev/null
+++ b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+
+/**
+ * Protocol handler superinterface for a protocol which performs substitutions
+ * from URIs of its scheme to URIs of another scheme.
+ */
+[scriptable, uuid(154c64fd-a69e-4105-89f8-bd7dfe621372)]
+interface nsISubstitutingProtocolHandler : nsIProtocolHandler
+{
+ /**
+ * Content script may access files in this package.
+ */
+ const short ALLOW_CONTENT_ACCESS = 1 << 0;
+ /**
+ * This substitution exposes nsIJARURI instead of a nsIFileURL. By default
+ * NewURI will always return a nsIFileURL even when the URL is jar:
+ */
+ const short RESOLVE_JAR_URI = 1 << 1;
+
+ /**
+ * Sets the substitution for the root key:
+ * resource://root/path ==> baseURI.resolve(path)
+ *
+ * A null baseURI removes the specified substitution.
+ *
+ * The root key will be converted to lower-case to conform to
+ * case-insensitive URI hostname matching behavior.
+ */
+ [must_use] void setSubstitution(in ACString root, in nsIURI baseURI);
+
+ /**
+ * Same as setSubstitution, but with specific flags.
+ */
+ [must_use] void setSubstitutionWithFlags(in ACString root, in nsIURI baseURI, in uint32_t flags);
+
+ /**
+ * Gets the substitution for the root key.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if none exists.
+ */
+ [must_use] nsIURI getSubstitution(in ACString root);
+
+ /**
+ * Returns TRUE if the substitution exists and FALSE otherwise.
+ */
+ [must_use] boolean hasSubstitution(in ACString root);
+
+ /**
+ * Utility function to resolve a substituted URI. A resolved URI is not
+ * guaranteed to reference a resource that exists (ie. opening a channel to
+ * the resolved URI may fail).
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if resURI.host() is an unknown root key.
+ */
+ [must_use] AUTF8String resolveURI(in nsIURI resURI);
+};
diff --git a/netwerk/protocol/res/nsResProtocolHandler.cpp b/netwerk/protocol/res/nsResProtocolHandler.cpp
new file mode 100644
index 0000000000..208baedf2b
--- /dev/null
+++ b/netwerk/protocol/res/nsResProtocolHandler.cpp
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Unused.h"
+
+#include "nsResProtocolHandler.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+
+#include "mozilla/Omnijar.h"
+
+using mozilla::LogLevel;
+using mozilla::Unused;
+using mozilla::dom::ContentParent;
+
+#define kAPP "app"
+#define kGRE "gre"
+#define kAndroid "android"
+
+mozilla::StaticRefPtr<nsResProtocolHandler> nsResProtocolHandler::sSingleton;
+
+already_AddRefed<nsResProtocolHandler> nsResProtocolHandler::GetSingleton() {
+ if (!sSingleton) {
+ RefPtr<nsResProtocolHandler> handler = new nsResProtocolHandler();
+ if (NS_WARN_IF(NS_FAILED(handler->Init()))) {
+ return nullptr;
+ }
+ sSingleton = handler;
+ ClearOnShutdown(&sSingleton);
+ }
+ return do_AddRef(sSingleton);
+}
+
+nsresult nsResProtocolHandler::Init() {
+ nsresult rv;
+ rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::APP, mAppURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::GRE, mGREURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // mozilla::Omnijar::GetURIString always returns a string ending with /,
+ // and we want to remove it.
+ mGREURI.Truncate(mGREURI.Length() - 1);
+ if (mAppURI.Length()) {
+ mAppURI.Truncate(mAppURI.Length() - 1);
+ } else {
+ mAppURI = mGREURI;
+ }
+
+#ifdef ANDROID
+ rv = GetApkURI(mApkURI);
+#endif
+
+ // XXXbsmedberg Neil wants a resource://pchrome/ for the profile chrome dir...
+ // but once I finish multiple chrome registration I'm not sure that it is
+ // needed
+
+ // XXX dveditz: resource://pchrome/ defeats profile directory salting
+ // if web content can load it. Tread carefully.
+
+ return rv;
+}
+
+#ifdef ANDROID
+nsresult nsResProtocolHandler::GetApkURI(nsACString& aResult) {
+ nsCString::const_iterator start, iter;
+ mGREURI.BeginReading(start);
+ mGREURI.EndReading(iter);
+ nsCString::const_iterator start_iter = start;
+
+ // This is like jar:jar:file://path/to/apk/base.apk!/path/to/omni.ja!/
+ bool found = FindInReadable("!/"_ns, start_iter, iter);
+ NS_ENSURE_TRUE(found, NS_ERROR_UNEXPECTED);
+
+ // like jar:jar:file://path/to/apk/base.apk!/
+ const nsDependentCSubstring& withoutPath = Substring(start, iter);
+ NS_ENSURE_TRUE(withoutPath.Length() >= 4, NS_ERROR_UNEXPECTED);
+
+ // Let's make sure we're removing what we expect to remove
+ NS_ENSURE_TRUE(Substring(withoutPath, 0, 4).EqualsLiteral("jar:"),
+ NS_ERROR_UNEXPECTED);
+
+ // like jar:file://path/to/apk/base.apk!/
+ aResult = ToNewCString(Substring(withoutPath, 4));
+
+ // Remove the trailing /
+ NS_ENSURE_TRUE(aResult.Length() >= 1, NS_ERROR_UNEXPECTED);
+ aResult.Truncate(aResult.Length() - 1);
+ return NS_OK;
+}
+#endif
+
+//----------------------------------------------------------------------------
+// nsResProtocolHandler::nsISupports
+//----------------------------------------------------------------------------
+
+NS_IMPL_QUERY_INTERFACE(nsResProtocolHandler, nsIResProtocolHandler,
+ nsISubstitutingProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
+
+NS_IMETHODIMP
+nsResProtocolHandler::AllowContentToAccess(nsIURI* aURI, bool* aResult) {
+ *aResult = false;
+
+ nsAutoCString host;
+ nsresult rv = aURI->GetAsciiHost(host);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t flags;
+ rv = GetSubstitutionFlags(host, &flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = flags & nsISubstitutingProtocolHandler::ALLOW_CONTENT_ACCESS;
+ return NS_OK;
+}
+
+nsresult nsResProtocolHandler::GetSubstitutionInternal(const nsACString& aRoot,
+ nsIURI** aResult,
+ uint32_t* aFlags) {
+ nsAutoCString uri;
+
+ if (!ResolveSpecialCases(aRoot, "/"_ns, "/"_ns, uri)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aRoot.Equals(kAndroid)) {
+ *aFlags = nsISubstitutingProtocolHandler::RESOLVE_JAR_URI;
+ } else {
+ *aFlags = 0; // No content access.
+ }
+ return NS_NewURI(aResult, uri);
+}
+
+bool nsResProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) {
+ if (aHost.EqualsLiteral("") || aHost.EqualsLiteral(kAPP)) {
+ aResult.Assign(mAppURI);
+ } else if (aHost.Equals(kGRE)) {
+ aResult.Assign(mGREURI);
+#ifdef ANDROID
+ } else if (aHost.Equals(kAndroid)) {
+ aResult.Assign(mApkURI);
+#endif
+ } else {
+ return false;
+ }
+ aResult.Append(aPath);
+ return true;
+}
+
+nsresult nsResProtocolHandler::SetSubstitution(const nsACString& aRoot,
+ nsIURI* aBaseURI) {
+ MOZ_ASSERT(!aRoot.EqualsLiteral(""));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kAPP));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kGRE));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kAndroid));
+ return SubstitutingProtocolHandler::SetSubstitution(aRoot, aBaseURI);
+}
+
+nsresult nsResProtocolHandler::SetSubstitutionWithFlags(const nsACString& aRoot,
+ nsIURI* aBaseURI,
+ uint32_t aFlags) {
+ MOZ_ASSERT(!aRoot.EqualsLiteral(""));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kAPP));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kGRE));
+ MOZ_ASSERT(!aRoot.EqualsLiteral(kAndroid));
+ return SubstitutingProtocolHandler::SetSubstitutionWithFlags(aRoot, aBaseURI,
+ aFlags);
+}
+
+nsresult nsResProtocolHandler::HasSubstitution(const nsACString& aRoot,
+ bool* aResult) {
+ if (aRoot.EqualsLiteral(kAPP) || aRoot.EqualsLiteral(kGRE)
+#ifdef ANDROID
+ || aRoot.EqualsLiteral(kAndroid)
+#endif
+ ) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ return mozilla::net::SubstitutingProtocolHandler::HasSubstitution(aRoot,
+ aResult);
+}
diff --git a/netwerk/protocol/res/nsResProtocolHandler.h b/netwerk/protocol/res/nsResProtocolHandler.h
new file mode 100644
index 0000000000..50e790a53a
--- /dev/null
+++ b/netwerk/protocol/res/nsResProtocolHandler.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsResProtocolHandler_h___
+#define nsResProtocolHandler_h___
+
+#include "mozilla/net/SubstitutingProtocolHandler.h"
+
+#include "nsIResProtocolHandler.h"
+#include "nsInterfaceHashtable.h"
+#include "nsWeakReference.h"
+
+struct SubstitutionMapping;
+class nsResProtocolHandler final
+ : public nsIResProtocolHandler,
+ public mozilla::net::SubstitutingProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRESPROTOCOLHANDLER
+
+ static already_AddRefed<nsResProtocolHandler> GetSingleton();
+
+ NS_FORWARD_NSIPROTOCOLHANDLER(mozilla::net::SubstitutingProtocolHandler::)
+
+ nsResProtocolHandler()
+ : mozilla::net::SubstitutingProtocolHandler(
+ "resource",
+ /* aEnforceFileOrJar = */ false) {}
+
+ NS_IMETHOD SetSubstitution(const nsACString& aRoot,
+ nsIURI* aBaseURI) override;
+ NS_IMETHOD SetSubstitutionWithFlags(const nsACString& aRoot, nsIURI* aBaseURI,
+ uint32_t aFlags) override;
+ NS_IMETHOD HasSubstitution(const nsACString& aRoot, bool* aResult) override;
+
+ NS_IMETHOD GetSubstitution(const nsACString& aRoot,
+ nsIURI** aResult) override {
+ return mozilla::net::SubstitutingProtocolHandler::GetSubstitution(aRoot,
+ aResult);
+ }
+
+ NS_IMETHOD ResolveURI(nsIURI* aResURI, nsACString& aResult) override {
+ return mozilla::net::SubstitutingProtocolHandler::ResolveURI(aResURI,
+ aResult);
+ }
+
+ protected:
+ [[nodiscard]] nsresult GetSubstitutionInternal(const nsACString& aRoot,
+ nsIURI** aResult,
+ uint32_t* aFlags) override;
+ virtual ~nsResProtocolHandler() = default;
+
+ [[nodiscard]] bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) override;
+
+ [[nodiscard]] virtual bool MustResolveJAR(const nsACString& aRoot) override {
+ return aRoot.EqualsLiteral("android");
+ }
+
+ private:
+ [[nodiscard]] nsresult Init();
+ static mozilla::StaticRefPtr<nsResProtocolHandler> sSingleton;
+
+ nsCString mAppURI;
+ nsCString mGREURI;
+#ifdef ANDROID
+ // Used for resource://android URIs
+ nsCString mApkURI;
+ nsresult GetApkURI(nsACString& aResult);
+#endif
+};
+
+#endif /* nsResProtocolHandler_h___ */
diff --git a/netwerk/protocol/viewsource/moz.build b/netwerk/protocol/viewsource/moz.build
new file mode 100644
index 0000000000..adf19615f0
--- /dev/null
+++ b/netwerk/protocol/viewsource/moz.build
@@ -0,0 +1,29 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ "nsIViewSourceChannel.idl",
+]
+
+XPIDL_MODULE = "necko_viewsource"
+
+UNIFIED_SOURCES += [
+ "nsViewSourceChannel.cpp",
+ "nsViewSourceHandler.cpp",
+]
+
+EXPORTS += [
+ "nsViewSourceHandler.h",
+]
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ # For nsHttpChannel.h
+ "/netwerk/protocol/http",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/netwerk/protocol/viewsource/nsIViewSourceChannel.idl b/netwerk/protocol/viewsource/nsIViewSourceChannel.idl
new file mode 100644
index 0000000000..036142ba3d
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsIViewSourceChannel.idl
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIChannel.idl"
+
+[uuid(3e9800f8-edb7-4c9a-9285-09b4f045b019)]
+interface nsIViewSourceChannel : nsIChannel
+{
+ /**
+ * The actual (MIME) content type of the data.
+ *
+ * nsIViewSourceChannel returns a content type of
+ * "application/x-view-source" if you ask it for the contentType
+ * attribute.
+ *
+ * However, callers interested in finding out or setting the
+ * actual content type can utilize this attribute.
+ */
+ [must_use] attribute ACString originalContentType;
+
+ /**
+ * Whether the channel was created to view the source of a srcdoc document.
+ */
+ [must_use] readonly attribute boolean isSrcdocChannel;
+
+ /**
+ * Set to indicate the base URI. If this channel is a srcdoc channel, it
+ * returns the base URI provided by the embedded channel. It is used to
+ * provide an indication of the base URI in circumstances where it isn't
+ * otherwise recoverable. Returns null when it isn't set and isn't a
+ * srcdoc channel.
+ */
+ [must_use] attribute nsIURI baseURI;
+
+ /**
+ * Get the inner channel wrapped by this nsIViewSourceChannel.
+ */
+ [notxpcom, nostdcall] nsIChannel getInnerChannel();
+};
diff --git a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
new file mode 100644
index 0000000000..f2428a5744
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -0,0 +1,1220 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsViewSourceChannel.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/NullPrincipal.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+#include "nsHttpChannel.h"
+#include "nsIExternalProtocolHandler.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIIOService.h"
+#include "nsIInputStreamChannel.h"
+#include "nsIReferrerInfo.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+
+NS_IMPL_ADDREF(nsViewSourceChannel)
+NS_IMPL_RELEASE(nsViewSourceChannel)
+/*
+ This QI uses NS_INTERFACE_MAP_ENTRY_CONDITIONAL to check for
+ non-nullness of mHttpChannel, mCachingChannel, and mUploadChannel.
+*/
+NS_INTERFACE_MAP_BEGIN(nsViewSourceChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIViewSourceChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIHttpChannel, mHttpChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIdentChannel, mHttpChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIHttpChannelInternal,
+ mHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICachingChannel, mCachingChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICacheInfoChannel, mCacheInfoChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUploadChannel, mUploadChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFormPOSTActionChannel, mPostChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIChildChannel, mChildChannel)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIRequest, nsIViewSourceChannel)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIChannel, nsIViewSourceChannel)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIViewSourceChannel)
+NS_INTERFACE_MAP_END
+
+static nsresult WillUseExternalProtocolHandler(nsIIOService* aIOService,
+ const char* aScheme) {
+ nsCOMPtr<nsIProtocolHandler> handler;
+ nsresult rv =
+ aIOService->GetProtocolHandler(aScheme, getter_AddRefs(handler));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIExternalProtocolHandler> externalHandler =
+ do_QueryInterface(handler);
+ // We should not allow view-source to open any external app.
+ if (externalHandler) {
+ NS_WARNING(nsPrintfCString("blocking view-source:%s:", aScheme).get());
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsViewSourceChannel::Init(nsIURI* uri, nsILoadInfo* aLoadInfo) {
+ mOriginalURI = uri;
+
+ nsAutoCString path;
+ nsresult rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIIOService> pService(do_GetIOService(&rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString scheme;
+ rv = pService->ExtractScheme(path, scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ // prevent viewing source of javascript URIs (see bug 204779)
+ if (scheme.EqualsLiteral("javascript")) {
+ NS_WARNING("blocking view-source:javascript:");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ rv = WillUseExternalProtocolHandler(pService, scheme.get());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> newChannelURI;
+ rv = pService->NewURI(path, nullptr, nullptr, getter_AddRefs(newChannelURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pService->NewChannelFromURIWithLoadInfo(newChannelURI, aLoadInfo,
+ getter_AddRefs(mChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIsSrcdocChannel = false;
+
+ mChannel->SetOriginalURI(mOriginalURI);
+ UpdateChannelInterfaces();
+
+ return NS_OK;
+}
+
+nsresult nsViewSourceChannel::InitSrcdoc(nsIURI* aURI, nsIURI* aBaseURI,
+ const nsAString& aSrcdoc,
+ nsILoadInfo* aLoadInfo) {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> inStreamURI;
+ // Need to strip view-source: from the URI. Hardcoded to
+ // about:srcdoc as this is the only permissible URI for srcdoc
+ // loads
+ rv = NS_NewURI(getter_AddRefs(inStreamURI), u"about:srcdoc"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel), inStreamURI,
+ aSrcdoc, "text/html"_ns, aLoadInfo,
+ true);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ mOriginalURI = aURI;
+ mIsSrcdocChannel = true;
+
+ mChannel->SetOriginalURI(mOriginalURI);
+ UpdateChannelInterfaces();
+
+ rv = UpdateLoadInfoResultPrincipalURI();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(mChannel);
+ MOZ_ASSERT(isc);
+ isc->SetBaseURI(aBaseURI);
+ return NS_OK;
+}
+
+void nsViewSourceChannel::UpdateChannelInterfaces() {
+ mHttpChannel = do_QueryInterface(mChannel);
+ mHttpChannelInternal = do_QueryInterface(mChannel);
+ mCachingChannel = do_QueryInterface(mChannel);
+ mCacheInfoChannel = do_QueryInterface(mChannel);
+ mUploadChannel = do_QueryInterface(mChannel);
+ mPostChannel = do_QueryInterface(mChannel);
+ mChildChannel = do_QueryInterface(mChannel);
+}
+
+void nsViewSourceChannel::ReleaseListeners() {
+ mListener = nullptr;
+ mCallbacks = nullptr;
+}
+
+nsresult nsViewSourceChannel::UpdateLoadInfoResultPrincipalURI() {
+ nsresult rv;
+
+ MOZ_ASSERT(mChannel);
+
+ nsCOMPtr<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo();
+ nsCOMPtr<nsIURI> channelResultPrincipalURI;
+ rv = channelLoadInfo->GetResultPrincipalURI(
+ getter_AddRefs(channelResultPrincipalURI));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!channelResultPrincipalURI) {
+ mChannel->GetOriginalURI(getter_AddRefs(channelResultPrincipalURI));
+ return NS_OK;
+ }
+
+ if (!channelResultPrincipalURI) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool alreadyViewSource;
+ if (NS_SUCCEEDED(channelResultPrincipalURI->SchemeIs("view-source",
+ &alreadyViewSource)) &&
+ alreadyViewSource) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> updatedResultPrincipalURI;
+ rv = BuildViewSourceURI(channelResultPrincipalURI,
+ getter_AddRefs(updatedResultPrincipalURI));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = channelLoadInfo->SetResultPrincipalURI(updatedResultPrincipalURI);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsViewSourceChannel::BuildViewSourceURI(nsIURI* aURI,
+ nsIURI** aResult) {
+ nsresult rv;
+
+ // protect ourselves against broken channel implementations
+ if (!aURI) {
+ NS_ERROR("no URI to build view-source uri!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_NewURI(aResult, "view-source:"_ns + spec);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetName(nsACString& result) {
+ nsCOMPtr<nsIURI> uri;
+ GetURI(getter_AddRefs(uri));
+ if (uri) {
+ return uri->GetSpec(result);
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetTransferSize(uint64_t* aTransferSize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestSize(uint64_t* aRequestSize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetDecodedBodySize(uint64_t* aDecodedBodySize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::IsPending(bool* result) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->IsPending(result);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetStatus(nsresult* status) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetStatus(status);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::SetCanceledReason(
+ const nsACString& aReason) {
+ return nsIViewSourceChannel::SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::GetCanceledReason(nsACString& aReason) {
+ return nsIViewSourceChannel::GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return nsIViewSourceChannel::CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Cancel(nsresult status) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->Cancel(status);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetCanceled(bool* aCanceled) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetCanceled(aCanceled);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Suspend(void) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->Suspend();
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Resume(void) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->Resume();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIChannel methods:
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetOriginalURI(nsIURI** aURI) {
+ *aURI = do_AddRef(mOriginalURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetOriginalURI(nsIURI* aURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetURI(nsIURI** aURI) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return BuildViewSourceURI(uri, aURI);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Open(nsIInputStream** aStream) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::AsyncOpen(nsIStreamListener* aListener) {
+ // We can't ensure GetInitialSecurityCheckDone here
+
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ mListener = aListener;
+
+ /*
+ * We want to add ourselves to the loadgroup before opening
+ * mChannel, since we want to make sure we're in the loadgroup
+ * when mChannel finishes and fires OnStopRequest()
+ */
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ loadGroup->AddRequest(static_cast<nsIViewSourceChannel*>(this), nullptr);
+ }
+
+ nsresult rv = NS_OK;
+ rv = mChannel->AsyncOpen(this);
+
+ if (NS_FAILED(rv) && loadGroup) {
+ loadGroup->RemoveRequest(static_cast<nsIViewSourceChannel*>(this), nullptr,
+ rv);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // We do this here to make sure all notification callbacks changes have been
+ // made first before we inject this view-source channel.
+ mChannel->GetNotificationCallbacks(getter_AddRefs(mCallbacks));
+ mChannel->SetNotificationCallbacks(this);
+
+ MOZ_ASSERT(mCallbacks != this, "We have a cycle");
+
+ mOpened = true;
+ }
+
+ if (NS_FAILED(rv)) {
+ ReleaseListeners();
+ }
+
+ return rv;
+}
+
+/*
+ * Both the view source channel and mChannel are added to the
+ * loadgroup. There should never be more than one request in the
+ * loadgroup that has LOAD_DOCUMENT_URI set. The one that has this
+ * flag set is the request whose URI is used to refetch the document,
+ * so it better be the viewsource channel.
+ *
+ * Therefore, we need to make sure that
+ * 1) The load flags on mChannel _never_ include LOAD_DOCUMENT_URI
+ * 2) The load flags on |this| include LOAD_DOCUMENT_URI when it was
+ * set via SetLoadFlags (mIsDocument keeps track of this flag).
+ */
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetLoadFlags(uint32_t* aLoadFlags) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ nsresult rv = mChannel->GetLoadFlags(aLoadFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // This should actually be just LOAD_DOCUMENT_URI but the win32 compiler
+ // fails to deal due to amiguous inheritance. nsIChannel::LOAD_DOCUMENT_URI
+ // also fails; the Win32 compiler thinks that's supposed to be a method.
+ if (mIsDocument) *aLoadFlags |= ::nsIChannel::LOAD_DOCUMENT_URI;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetLoadFlags(uint32_t aLoadFlags) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ // "View source" always wants the currently cached content.
+ // We also want to have _this_ channel, not mChannel to be the
+ // 'document' channel in the loadgroup.
+
+ // These should actually be just LOAD_FROM_CACHE and LOAD_DOCUMENT_URI but
+ // the win32 compiler fails to deal due to amiguous inheritance.
+ // nsIChannel::LOAD_DOCUMENT_URI/nsIRequest::LOAD_FROM_CACHE also fails; the
+ // Win32 compiler thinks that's supposed to be a method.
+ mIsDocument = (aLoadFlags & ::nsIChannel::LOAD_DOCUMENT_URI) != 0;
+
+ nsresult rv =
+ mChannel->SetLoadFlags((aLoadFlags | ::nsIRequest::LOAD_FROM_CACHE) &
+ ~::nsIChannel::LOAD_DOCUMENT_URI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mHttpChannel) {
+ rv = mHttpChannel->SetIsMainDocumentChannel(
+ aLoadFlags & ::nsIChannel::LOAD_DOCUMENT_URI);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return nsIViewSourceChannel::GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return nsIViewSourceChannel::SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentType(nsACString& aContentType) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ aContentType.Truncate();
+
+ if (mContentType.IsEmpty()) {
+ // Get the current content type
+ nsresult rv;
+ nsAutoCString contentType;
+ rv = mChannel->GetContentType(contentType);
+ if (NS_FAILED(rv)) return rv;
+
+ // If we don't know our type, just say so. The unknown
+ // content decoder will then kick in automatically, and it
+ // will call our SetOriginalContentType method instead of our
+ // SetContentType method to set the type it determines.
+ if (!contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) &&
+ !contentType.IsEmpty()) {
+ contentType = VIEWSOURCE_CONTENT_TYPE;
+ }
+
+ mContentType = contentType;
+ }
+
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentType(const nsACString& aContentType) {
+ // Our GetContentType() currently returns VIEWSOURCE_CONTENT_TYPE
+ //
+ // However, during the parsing phase the parser calls our
+ // channel's GetContentType(). Returning the string above trips up
+ // the parser. In order to avoid messy changes and not to have the
+ // parser depend on nsIViewSourceChannel Vidur proposed the
+ // following solution:
+ //
+ // The ViewSourceChannel initially returns a content type of
+ // VIEWSOURCE_CONTENT_TYPE. Based on this type decisions to
+ // create a viewer for doing a view source are made. After the
+ // viewer is created, nsLayoutDLF::CreateInstance() calls this
+ // SetContentType() with the original content type. When it's
+ // time for the parser to find out the content type it will call
+ // our channel's GetContentType() and it will get the original
+ // content type, such as, text/html and everything is kosher from
+ // then on.
+
+ if (!mOpened) {
+ // We do not take hints
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mContentType = aContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentCharset(nsACString& aContentCharset) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetContentCharset(aContentCharset);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentCharset(const nsACString& aContentCharset) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetContentCharset(aContentCharset);
+}
+
+// We don't forward these methods becacuse content-disposition isn't whitelisted
+// (see GetResponseHeader/VisitResponseHeaders).
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentLength(int64_t* aContentLength) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetContentLength(aContentLength);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentLength(int64_t aContentLength) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetContentLength(aContentLength);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetLoadGroup(aLoadGroup);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetLoadGroup(aLoadGroup);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetOwner(nsISupports** aOwner) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetOwner(aOwner);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetOwner(nsISupports* aOwner) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetOwner(aOwner);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetLoadInfo(aLoadInfo);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+ MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
+ return mChannel->SetLoadInfo(aLoadInfo);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetIsDocument(bool* aIsDocument) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetIsDocument(aIsDocument);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetNotificationCallbacks(aNotificationCallbacks);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetNotificationCallbacks(aNotificationCallbacks);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetSecurityInfo(aSecurityInfo);
+}
+
+// nsIViewSourceChannel methods
+NS_IMETHODIMP
+nsViewSourceChannel::GetOriginalContentType(nsACString& aContentType) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetContentType(aContentType);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetOriginalContentType(const nsACString& aContentType) {
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ // clear our cached content-type value
+ mContentType.Truncate();
+
+ return mChannel->SetContentType(aContentType);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetIsSrcdocChannel(bool* aIsSrcdocChannel) {
+ *aIsSrcdocChannel = mIsSrcdocChannel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetBaseURI(nsIURI** aBaseURI) {
+ if (mIsSrcdocChannel) {
+ nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(mChannel);
+ if (isc) {
+ return isc->GetBaseURI(aBaseURI);
+ }
+ }
+ *aBaseURI = do_AddRef(mBaseURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetBaseURI(nsIURI* aBaseURI) {
+ mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+nsIChannel* nsViewSourceChannel::GetInnerChannel() { return mChannel; }
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetProtocolVersion(nsACString& aProtocolVersion) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsIRequestObserver methods
+NS_IMETHODIMP
+nsViewSourceChannel::OnStartRequest(nsIRequest* aRequest) {
+ NS_ENSURE_TRUE(mListener, NS_ERROR_FAILURE);
+ // The channel may have gotten redirected... Time to update our info
+ mChannel = do_QueryInterface(aRequest);
+ UpdateChannelInterfaces();
+
+ nsresult rv = UpdateLoadInfoResultPrincipalURI();
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+
+ return mListener->OnStartRequest(static_cast<nsIViewSourceChannel*>(this));
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ NS_ENSURE_TRUE(mListener, NS_ERROR_FAILURE);
+ if (mChannel) {
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ loadGroup->RemoveRequest(static_cast<nsIViewSourceChannel*>(this),
+ nullptr, aStatus);
+ }
+ }
+
+ nsresult rv = mListener->OnStopRequest(
+ static_cast<nsIViewSourceChannel*>(this), aStatus);
+
+ ReleaseListeners();
+
+ return rv;
+}
+
+// nsIStreamListener methods
+NS_IMETHODIMP
+nsViewSourceChannel::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aSourceOffset, uint32_t aLength) {
+ NS_ENSURE_TRUE(mListener, NS_ERROR_FAILURE);
+ return mListener->OnDataAvailable(static_cast<nsIViewSourceChannel*>(this),
+ aInputStream, aSourceOffset, aLength);
+}
+
+// nsIHttpChannel methods
+
+// We want to forward most of nsIHttpChannel over to mHttpChannel, but we want
+// to override GetRequestHeader and VisitHeaders. The reason is that we don't
+// want various headers like Link: and Refresh: applying to view-source.
+NS_IMETHODIMP
+nsViewSourceChannel::GetChannelId(uint64_t* aChannelId) {
+ NS_ENSURE_ARG_POINTER(aChannelId);
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetChannelId(aChannelId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetChannelId(uint64_t aChannelId) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetChannelId(aChannelId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetTopLevelContentWindowId(uint64_t* aWindowId) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetTopLevelContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetTopLevelContentWindowId(uint64_t aWindowId) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetTopLevelContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetBrowserId(uint64_t* aId) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetBrowserId(aId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetBrowserId(uint64_t aId) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetBrowserId(aId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestMethod(nsACString& aRequestMethod) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetRequestMethod(aRequestMethod);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetRequestMethod(const nsACString& aRequestMethod) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetRequestMethod(aRequestMethod);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetReferrerInfo(aReferrerInfo);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetReferrerInfo(aReferrerInfo);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::SetReferrerInfoWithoutClone(
+ nsIReferrerInfo* aReferrerInfo) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetReferrerInfoWithoutClone(aReferrerInfo);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestHeader(const nsACString& aHeader,
+ nsACString& aValue) {
+ aValue.Truncate();
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetRequestHeader(aHeader, aValue);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetRequestHeader(aHeader, aValue, aMerge);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetNewReferrerInfo(
+ const nsACString& aUrl, nsIReferrerInfo::ReferrerPolicyIDL aPolicy,
+ bool aSendReferrer) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetNewReferrerInfo(aUrl, aPolicy, aSendReferrer);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetEmptyRequestHeader(const nsACString& aHeader) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetEmptyRequestHeader(aHeader);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::VisitRequestHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->VisitRequestHeaders(aVisitor);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::VisitNonDefaultRequestHeaders(
+ nsIHttpHeaderVisitor* aVisitor) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->VisitNonDefaultRequestHeaders(aVisitor);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::ShouldStripRequestBodyHeader(const nsACString& aMethod,
+ bool* aResult) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->ShouldStripRequestBodyHeader(aMethod, aResult);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetAllowSTS(bool* aAllowSTS) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetAllowSTS(aAllowSTS);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetAllowSTS(bool aAllowSTS) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetAllowSTS(aAllowSTS);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRedirectionLimit(uint32_t* aRedirectionLimit) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetRedirectionLimit(aRedirectionLimit);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetRedirectionLimit(uint32_t aRedirectionLimit) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetRedirectionLimit(aRedirectionLimit);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetResponseStatus(uint32_t* aResponseStatus) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetResponseStatus(aResponseStatus);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetResponseStatusText(nsACString& aResponseStatusText) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetResponseStatusText(aResponseStatusText);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestSucceeded(bool* aRequestSucceeded) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetRequestSucceeded(aRequestSucceeded);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetResponseHeader(const nsACString& aHeader,
+ nsACString& aValue) {
+ aValue.Truncate();
+ if (!mHttpChannel) return NS_ERROR_NULL_POINTER;
+
+ if (!aHeader.Equals("Content-Type"_ns, nsCaseInsensitiveCStringComparator) &&
+ !aHeader.Equals("Content-Security-Policy"_ns,
+ nsCaseInsensitiveCStringComparator) &&
+ !aHeader.Equals("Content-Security-Policy-Report-Only"_ns,
+ nsCaseInsensitiveCStringComparator) &&
+ !aHeader.Equals("X-Frame-Options"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ // We simulate the NS_ERROR_NOT_AVAILABLE error which is produced by
+ // GetResponseHeader via nsHttpHeaderArray::GetHeader when the entry is
+ // not present, such that it appears as though no headers except for the
+ // whitelisted ones were set on this channel.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mHttpChannel->GetResponseHeader(aHeader, aValue);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetResponseHeader(const nsACString& header,
+ const nsACString& value, bool merge) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetResponseHeader(header, value, merge);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::VisitResponseHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ if (!mHttpChannel) return NS_ERROR_NULL_POINTER;
+
+ constexpr auto contentTypeStr = "Content-Type"_ns;
+ nsAutoCString contentType;
+ nsresult rv = mHttpChannel->GetResponseHeader(contentTypeStr, contentType);
+ if (NS_SUCCEEDED(rv)) {
+ return aVisitor->VisitHeader(contentTypeStr, contentType);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetOriginalResponseHeader(const nsACString& aHeader,
+ nsIHttpHeaderVisitor* aVisitor) {
+ nsAutoCString value;
+ nsresult rv = GetResponseHeader(aHeader, value);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return aVisitor->VisitHeader(aHeader, value);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::VisitOriginalResponseHeaders(
+ nsIHttpHeaderVisitor* aVisitor) {
+ return VisitResponseHeaders(aVisitor);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::IsNoStoreResponse(bool* _retval) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->IsNoStoreResponse(_retval);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::IsNoCacheResponse(bool* _retval) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->IsNoCacheResponse(_retval);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::IsPrivateResponse(bool* _retval) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->IsPrivateResponse(_retval);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::RedirectTo(nsIURI* uri) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER : mHttpChannel->RedirectTo(uri);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::UpgradeToSecure() {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->UpgradeToSecure();
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestContextID(uint64_t* _retval) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetRequestContextID(_retval);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetRequestContextID(uint64_t rcid) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetRequestContextID(rcid);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetIsMainDocumentChannel(bool* aValue) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetIsMainDocumentChannel(aValue);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetIsMainDocumentChannel(bool aValue) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetIsMainDocumentChannel(aValue);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::SetClassicScriptHintCharset(
+ const nsAString& aClassicScriptHintCharset) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetClassicScriptHintCharset(
+ aClassicScriptHintCharset);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::GetClassicScriptHintCharset(
+ nsAString& aClassicScriptHintCharset) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetClassicScriptHintCharset(
+ aClassicScriptHintCharset);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::SetDocumentCharacterSet(
+ const nsAString& aDocumentCharacterSet) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetDocumentCharacterSet(aDocumentCharacterSet);
+}
+
+NS_IMETHODIMP nsViewSourceChannel::GetDocumentCharacterSet(
+ nsAString& aDocumentCharacterSet) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetDocumentCharacterSet(aDocumentCharacterSet);
+}
+// Have to manually forward SetCorsPreflightParameters since it's [notxpcom]
+void nsViewSourceChannel::SetCorsPreflightParameters(
+ const nsTArray<nsCString>& aUnsafeHeaders,
+ bool aShouldStripRequestBodyHeader) {
+ mHttpChannelInternal->SetCorsPreflightParameters(
+ aUnsafeHeaders, aShouldStripRequestBodyHeader);
+}
+
+void nsViewSourceChannel::SetAltDataForChild(bool aIsForChild) {
+ mHttpChannelInternal->SetAltDataForChild(aIsForChild);
+}
+
+void nsViewSourceChannel::DisableAltDataCache() {
+ mHttpChannelInternal->DisableAltDataCache();
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ if (!mHttpChannel) {
+ NS_WARNING(
+ "nsViewSourceChannel::LogBlockedCORSRequest mHttpChannel is null");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mHttpChannel->LogBlockedCORSRequest(aMessage, aCategory, aIsWarning);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ if (!mHttpChannel) {
+ NS_WARNING("nsViewSourceChannel::LogMimeTypeMismatch mHttpChannel is null");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mHttpChannel->LogMimeTypeMismatch(aMessageName, aWarning, aURL,
+ aContentType);
+}
+
+// FIXME: Should this forward to mHttpChannel? This was previously handled by a
+// default empty implementation.
+void nsViewSourceChannel::SetSource(
+ mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> aSource) {}
+
+const nsTArray<mozilla::net::PreferredAlternativeDataTypeParams>&
+nsViewSourceChannel::PreferredAlternativeDataTypes() {
+ if (mCacheInfoChannel) {
+ return mCacheInfoChannel->PreferredAlternativeDataTypes();
+ }
+ return mEmptyArray;
+}
+
+void nsViewSourceChannel::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {
+ if (mHttpChannelInternal) {
+ mHttpChannelInternal->DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();
+ }
+}
+
+// FIXME: Should this forward to mHttpChannelInternal? This was previously
+// handled by a default empty implementation.
+void nsViewSourceChannel::SetConnectionInfo(
+ mozilla::net::nsHttpConnectionInfo* aInfo) {}
+
+// nsIChildChannel methods
+
+NS_IMETHODIMP
+nsViewSourceChannel::ConnectParent(uint32_t aRegistarId) {
+ NS_ENSURE_TRUE(mChildChannel, NS_ERROR_NULL_POINTER);
+
+ return mChildChannel->ConnectParent(aRegistarId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::CompleteRedirectSetup(nsIStreamListener* aListener) {
+ NS_ENSURE_TRUE(mChildChannel, NS_ERROR_NULL_POINTER);
+
+ mListener = aListener;
+
+ /*
+ * We want to add ourselves to the loadgroup before opening
+ * mChannel, since we want to make sure we're in the loadgroup
+ * when mChannel finishes and fires OnStopRequest()
+ */
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ loadGroup->AddRequest(static_cast<nsIViewSourceChannel*>(this), nullptr);
+ }
+
+ nsresult rv = NS_OK;
+ rv = mChildChannel->CompleteRedirectSetup(this);
+
+ if (NS_FAILED(rv) && loadGroup) {
+ loadGroup->RemoveRequest(static_cast<nsIViewSourceChannel*>(this), nullptr,
+ rv);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ mOpened = true;
+ }
+
+ return rv;
+}
+
+// nsIChannelEventSink
+
+NS_IMETHODIMP
+nsViewSourceChannel::AsyncOnChannelRedirect(
+ nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ nsresult rv;
+
+ // We want consumers of the new inner channel be able to recognize it's
+ // actually used for view-source. Hence we modify its original URI here.
+ // This will not affect security checks because those have already been
+ // synchronously done before our event sink is called. Note that result
+ // principal URI is still modified at the OnStartRequest notification.
+ nsCOMPtr<nsIURI> newChannelOrigURI;
+ rv = newChannel->GetOriginalURI(getter_AddRefs(newChannelOrigURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString scheme;
+ rv = newChannelOrigURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIIOService> ioService(do_GetIOService(&rv));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WillUseExternalProtocolHandler(ioService, scheme.get());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> newChannelUpdatedOrigURI;
+ rv = BuildViewSourceURI(newChannelOrigURI,
+ getter_AddRefs(newChannelUpdatedOrigURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = newChannel->SetOriginalURI(newChannelUpdatedOrigURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannelEventSink> sink(do_QueryInterface(mCallbacks));
+ if (sink) {
+ return sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags,
+ callback);
+ }
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ nsCOMPtr<nsIChannelEventSink> self(this);
+ self.forget(aResult);
+ return NS_OK;
+ }
+
+ if (mCallbacks) {
+ return mCallbacks->GetInterface(aIID, aResult);
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
diff --git a/netwerk/protocol/viewsource/nsViewSourceChannel.h b/netwerk/protocol/viewsource/nsViewSourceChannel.h
new file mode 100644
index 0000000000..87c490f6b7
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsViewSourceChannel_h___
+#define nsViewSourceChannel_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsCOMPtr.h"
+#include "nsICachingChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIFormPOSTActionChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+#include "nsIViewSourceChannel.h"
+#include "nsIChildChannel.h"
+#include "nsString.h"
+
+class nsViewSourceChannel final : public nsIViewSourceChannel,
+ public nsIStreamListener,
+ public nsIHttpChannel,
+ public nsIHttpChannelInternal,
+ public nsICachingChannel,
+ public nsIFormPOSTActionChannel,
+ public nsIChildChannel,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIIDENTCHANNEL
+ NS_DECL_NSIVIEWSOURCECHANNEL
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIHTTPCHANNEL
+ NS_DECL_NSICHILDCHANNEL
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_FORWARD_SAFE_NSICACHEINFOCHANNEL(mCacheInfoChannel)
+ NS_FORWARD_SAFE_NSICACHINGCHANNEL(mCachingChannel)
+ NS_FORWARD_SAFE_NSIUPLOADCHANNEL(mUploadChannel)
+ NS_FORWARD_SAFE_NSIFORMPOSTACTIONCHANNEL(mPostChannel)
+ NS_FORWARD_SAFE_NSIHTTPCHANNELINTERNAL(mHttpChannelInternal)
+
+ // nsViewSourceChannel methods:
+ nsViewSourceChannel() = default;
+
+ [[nodiscard]] nsresult Init(nsIURI* uri, nsILoadInfo* aLoadInfo);
+
+ [[nodiscard]] nsresult InitSrcdoc(nsIURI* aURI, nsIURI* aBaseURI,
+ const nsAString& aSrcdoc,
+ nsILoadInfo* aLoadInfo);
+
+ // Updates or sets the result principal URI of the underlying channel's
+ // loadinfo to be prefixed with the "view-source:" schema as:
+ //
+ // mChannel.loadInfo.resultPrincipalURI = "view-source:" +
+ // (mChannel.loadInfo.resultPrincipalURI | mChannel.orignalURI);
+ nsresult UpdateLoadInfoResultPrincipalURI();
+
+ protected:
+ ~nsViewSourceChannel() = default;
+ void ReleaseListeners();
+
+ nsTArray<mozilla::net::PreferredAlternativeDataTypeParams> mEmptyArray;
+
+ // Clones aURI and prefixes it with "view-source:" schema,
+ nsresult BuildViewSourceURI(nsIURI* aURI, nsIURI** aResult);
+
+ // Called to update the forwarding channel members after the `mChannel` field
+ // has been changed to reflect the new inner channel.
+ void UpdateChannelInterfaces();
+
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+ nsCOMPtr<nsIHttpChannelInternal> mHttpChannelInternal;
+ nsCOMPtr<nsICachingChannel> mCachingChannel;
+ nsCOMPtr<nsICacheInfoChannel> mCacheInfoChannel;
+ nsCOMPtr<nsIUploadChannel> mUploadChannel;
+ nsCOMPtr<nsIFormPOSTActionChannel> mPostChannel;
+ nsCOMPtr<nsIChildChannel> mChildChannel;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsCString mContentType;
+ bool mIsDocument{false}; // keeps track of the LOAD_DOCUMENT_URI flag
+ bool mOpened{false};
+ bool mIsSrcdocChannel{false};
+};
+
+#endif /* nsViewSourceChannel_h___ */
diff --git a/netwerk/protocol/viewsource/nsViewSourceHandler.cpp b/netwerk/protocol/viewsource/nsViewSourceHandler.cpp
new file mode 100644
index 0000000000..f7b440d4d7
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceHandler.cpp
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsViewSourceHandler.h"
+#include "nsViewSourceChannel.h"
+#include "nsNetUtil.h"
+#include "nsSimpleNestedURI.h"
+
+#define VIEW_SOURCE "view-source"
+
+#define DEFAULT_FLAGS \
+ (URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD | URI_NON_PERSISTABLE)
+
+namespace mozilla {
+namespace net {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsViewSourceHandler, nsIProtocolHandler,
+ nsIProtocolHandlerWithDynamicFlags)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsViewSourceHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral(VIEW_SOURCE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* result) {
+ *result = DEFAULT_FLAGS;
+ nsCOMPtr<nsINestedURI> nestedURI(do_QueryInterface(aURI));
+ if (!nestedURI) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> innerURI;
+ nestedURI->GetInnerURI(getter_AddRefs(innerURI));
+ nsCOMPtr<nsINetUtil> netUtil = do_GetNetUtil();
+ bool isLoadable = false;
+ nsresult rv =
+ netUtil->ProtocolHasFlags(innerURI, URI_LOADABLE_BY_ANYONE, &isLoadable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isLoadable) {
+ *result |= URI_LOADABLE_BY_EXTENSIONS;
+ }
+ return NS_OK;
+}
+
+// static
+nsresult nsViewSourceHandler::CreateNewURI(const nsACString& aSpec,
+ const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** aResult) {
+ *aResult = nullptr;
+
+ // Extract inner URL and normalize to ASCII. This is done to properly
+ // support IDN in cases like "view-source:http://www.szalagavató.hu/"
+
+ int32_t colon = aSpec.FindChar(':');
+ if (colon == kNotFound) return NS_ERROR_MALFORMED_URI;
+
+ nsCOMPtr<nsIURI> innerURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(innerURI), Substring(aSpec, colon + 1),
+ aCharset, aBaseURI);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString asciiSpec;
+ rv = innerURI->GetAsciiSpec(asciiSpec);
+ if (NS_FAILED(rv)) return rv;
+
+ // put back our scheme and construct a simple-uri wrapper
+
+ asciiSpec.Insert(VIEW_SOURCE ":", 0);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_MutateURI(new nsSimpleNestedURI::Mutator())
+ .Apply(&nsINestedURIMutator::Init, innerURI)
+ .SetSpec(asciiSpec)
+ .Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uri.swap(*aResult);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ NS_ENSURE_ARG_POINTER(uri);
+ RefPtr<nsViewSourceChannel> channel = new nsViewSourceChannel();
+
+ nsresult rv = channel->Init(uri, aLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *result = channel.forget().downcast<nsIViewSourceChannel>().take();
+ return NS_OK;
+}
+
+nsresult nsViewSourceHandler::NewSrcdocChannel(nsIURI* aURI, nsIURI* aBaseURI,
+ const nsAString& aSrcdoc,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** outChannel) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ RefPtr<nsViewSourceChannel> channel = new nsViewSourceChannel();
+
+ nsresult rv = channel->InitSrcdoc(aURI, aBaseURI, aSrcdoc, aLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *outChannel = static_cast<nsIViewSourceChannel*>(channel.forget().take());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::AllowPort(int32_t port, const char* scheme,
+ bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+nsViewSourceHandler::nsViewSourceHandler() { gInstance = this; }
+
+nsViewSourceHandler::~nsViewSourceHandler() { gInstance = nullptr; }
+
+nsViewSourceHandler* nsViewSourceHandler::gInstance = nullptr;
+
+nsViewSourceHandler* nsViewSourceHandler::GetInstance() { return gInstance; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/viewsource/nsViewSourceHandler.h b/netwerk/protocol/viewsource/nsViewSourceHandler.h
new file mode 100644
index 0000000000..b9e9f3ab7c
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceHandler.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsViewSourceHandler_h___
+#define nsViewSourceHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsNetUtil.h"
+#include "mozilla/Attributes.h"
+
+class nsILoadInfo;
+
+namespace mozilla {
+namespace net {
+
+class nsViewSourceHandler final : public nsIProtocolHandlerWithDynamicFlags,
+ public nsIProtocolHandler {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIPROTOCOLHANDLERWITHDYNAMICFLAGS
+
+ nsViewSourceHandler();
+
+ // Creates a new nsViewSourceChannel to view the source of an about:srcdoc
+ // URI with contents specified by srcdoc.
+ [[nodiscard]] nsresult NewSrcdocChannel(nsIURI* aURI, nsIURI* aBaseURI,
+ const nsAString& aSrcdoc,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** outChannel);
+
+ static nsViewSourceHandler* GetInstance();
+
+ static nsresult CreateNewURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** aResult);
+
+ private:
+ ~nsViewSourceHandler();
+
+ static nsViewSourceHandler* gInstance;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* !defined( nsViewSourceHandler_h___ ) */
diff --git a/netwerk/protocol/websocket/BaseWebSocketChannel.cpp b/netwerk/protocol/websocket/BaseWebSocketChannel.cpp
new file mode 100644
index 0000000000..6e9589afb2
--- /dev/null
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.cpp
@@ -0,0 +1,378 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketLog.h"
+#include "BaseWebSocketChannel.h"
+#include "MainThreadUtils.h"
+#include "nsILoadGroup.h"
+#include "nsINode.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsProxyRelease.h"
+#include "nsStandardURL.h"
+#include "LoadInfo.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsITransportProvider.h"
+
+using mozilla::dom::ContentChild;
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule webSocketLog("nsWebSocket");
+static uint64_t gNextWebSocketID = 0;
+
+// We use only 53 bits for the WebSocket serial ID so that it can be converted
+// to and from a JS value without loss of precision. The upper bits of the
+// WebSocket serial ID hold the process ID. The lower bits identify the
+// WebSocket.
+static const uint64_t kWebSocketIDTotalBits = 53;
+static const uint64_t kWebSocketIDProcessBits = 22;
+static const uint64_t kWebSocketIDWebSocketBits =
+ kWebSocketIDTotalBits - kWebSocketIDProcessBits;
+
+BaseWebSocketChannel::BaseWebSocketChannel()
+ : mWasOpened(0),
+ mClientSetPingInterval(0),
+ mClientSetPingTimeout(0),
+ mEncrypted(false),
+ mPingForced(false),
+ mIsServerSide(false),
+ mPingInterval(0),
+ mPingResponseTimeout(10000),
+ mHttpChannelId(0) {
+ // Generation of a unique serial ID.
+ uint64_t processID = 0;
+ if (XRE_IsContentProcess()) {
+ ContentChild* cc = ContentChild::GetSingleton();
+ processID = cc->GetID();
+ }
+
+ uint64_t processBits =
+ processID & ((uint64_t(1) << kWebSocketIDProcessBits) - 1);
+
+ // Make sure no actual webSocket ends up with mWebSocketID == 0 but less then
+ // what the kWebSocketIDProcessBits allows.
+ if (++gNextWebSocketID >= (uint64_t(1) << kWebSocketIDWebSocketBits)) {
+ gNextWebSocketID = 1;
+ }
+
+ uint64_t webSocketBits =
+ gNextWebSocketID & ((uint64_t(1) << kWebSocketIDWebSocketBits) - 1);
+ mSerial = (processBits << kWebSocketIDWebSocketBits) | webSocketBits;
+}
+
+BaseWebSocketChannel::~BaseWebSocketChannel() {
+ NS_ReleaseOnMainThread("BaseWebSocketChannel::mLoadGroup",
+ mLoadGroup.forget());
+ NS_ReleaseOnMainThread("BaseWebSocketChannel::mLoadInfo", mLoadInfo.forget());
+ nsCOMPtr<nsISerialEventTarget> target;
+ {
+ auto lock = mTargetThread.Lock();
+ target.swap(*lock);
+ }
+ NS_ReleaseOnMainThread("BaseWebSocketChannel::mTargetThread",
+ target.forget());
+}
+
+//-----------------------------------------------------------------------------
+// BaseWebSocketChannel::nsIWebSocketChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetOriginalURI(nsIURI** aOriginalURI) {
+ LOG(("BaseWebSocketChannel::GetOriginalURI() %p\n", this));
+
+ if (!mOriginalURI) return NS_ERROR_NOT_INITIALIZED;
+ *aOriginalURI = do_AddRef(mOriginalURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetURI(nsIURI** aURI) {
+ LOG(("BaseWebSocketChannel::GetURI() %p\n", this));
+
+ if (!mOriginalURI) return NS_ERROR_NOT_INITIALIZED;
+ if (mURI) {
+ *aURI = do_AddRef(mURI).take();
+ } else {
+ *aURI = do_AddRef(mOriginalURI).take();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) {
+ LOG(("BaseWebSocketChannel::GetNotificationCallbacks() %p\n", this));
+ *aNotificationCallbacks = do_AddRef(mCallbacks).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) {
+ LOG(("BaseWebSocketChannel::SetNotificationCallbacks() %p\n", this));
+ mCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ LOG(("BaseWebSocketChannel::GetLoadGroup() %p\n", this));
+ *aLoadGroup = do_AddRef(mLoadGroup).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ LOG(("BaseWebSocketChannel::SetLoadGroup() %p\n", this));
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ *aLoadInfo = do_AddRef(mLoadInfo).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetExtensions(nsACString& aExtensions) {
+ LOG(("BaseWebSocketChannel::GetExtensions() %p\n", this));
+ aExtensions = mNegotiatedExtensions;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetProtocol(nsACString& aProtocol) {
+ LOG(("BaseWebSocketChannel::GetProtocol() %p\n", this));
+ aProtocol = mProtocol;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetProtocol(const nsACString& aProtocol) {
+ LOG(("BaseWebSocketChannel::SetProtocol() %p\n", this));
+ mProtocol = aProtocol; /* the sub protocol */
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetPingInterval(uint32_t* aSeconds) {
+ // stored in ms but should only have second resolution
+ MOZ_ASSERT(!(mPingInterval % 1000));
+
+ *aSeconds = mPingInterval / 1000;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetPingInterval(uint32_t aSeconds) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mWasOpened) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ mPingInterval = aSeconds * 1000;
+ mClientSetPingInterval = 1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetPingTimeout(uint32_t* aSeconds) {
+ // stored in ms but should only have second resolution
+ MOZ_ASSERT(!(mPingResponseTimeout % 1000));
+
+ *aSeconds = mPingResponseTimeout / 1000;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetPingTimeout(uint32_t aSeconds) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mWasOpened) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ mPingResponseTimeout = aSeconds * 1000;
+ mClientSetPingTimeout = 1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::InitLoadInfoNative(
+ nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsICookieJarSettings* aCookieJarSettings, uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType, uint32_t aSandboxFlags) {
+ mLoadInfo = new LoadInfo(
+ aLoadingPrincipal, aTriggeringPrincipal, aLoadingNode, aSecurityFlags,
+ aContentPolicyType, Maybe<mozilla::dom::ClientInfo>(),
+ Maybe<mozilla::dom::ServiceWorkerDescriptor>(), aSandboxFlags);
+ if (aCookieJarSettings) {
+ mLoadInfo->SetCookieJarSettings(aCookieJarSettings);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::InitLoadInfo(nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType) {
+ return InitLoadInfoNative(aLoadingNode, aLoadingPrincipal,
+ aTriggeringPrincipal, nullptr, aSecurityFlags,
+ aContentPolicyType, 0);
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetSerial(uint32_t* aSerial) {
+ if (!aSerial) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aSerial = mSerial;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetSerial(uint32_t aSerial) {
+ mSerial = aSerial;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetServerParameters(
+ nsITransportProvider* aProvider, const nsACString& aNegotiatedExtensions) {
+ MOZ_ASSERT(aProvider);
+ mServerTransportProvider = aProvider;
+ mNegotiatedExtensions = aNegotiatedExtensions;
+ mIsServerSide = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetHttpChannelId(uint64_t* aHttpChannelId) {
+ *aHttpChannelId = mHttpChannelId;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// BaseWebSocketChannel::nsIProtocolHandler
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetScheme(nsACString& aScheme) {
+ LOG(("BaseWebSocketChannel::GetScheme() %p\n", this));
+
+ if (mEncrypted) {
+ aScheme.AssignLiteral("wss");
+ } else {
+ aScheme.AssignLiteral("ws");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** outChannel) {
+ LOG(("BaseWebSocketChannel::NewChannel() %p\n", this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::AllowPort(int32_t port, const char* scheme,
+ bool* _retval) {
+ LOG(("BaseWebSocketChannel::AllowPort() %p\n", this));
+
+ // do not override any blacklisted ports
+ *_retval = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// BaseWebSocketChannel::nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+BaseWebSocketChannel::RetargetDeliveryTo(nsISerialEventTarget* aTargetThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTargetThread);
+ MOZ_ASSERT(!mWasOpened, "Should not be called after AsyncOpen!");
+ MOZ_ASSERT(aTargetThread);
+
+ auto lock = mTargetThread.Lock();
+ MOZ_ASSERT(!lock.ref(),
+ "Delivery target should be set once, before AsyncOpen");
+ lock.ref() = aTargetThread;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetDeliveryTarget(nsISerialEventTarget** aTargetThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsISerialEventTarget> target = GetTargetThread();
+ if (!target) {
+ target = GetCurrentSerialEventTarget();
+ }
+ target.forget(aTargetThread);
+ return NS_OK;
+}
+
+already_AddRefed<nsISerialEventTarget> BaseWebSocketChannel::GetTargetThread() {
+ nsCOMPtr<nsISerialEventTarget> target;
+ auto lock = mTargetThread.Lock();
+ target = *lock;
+ return target.forget();
+}
+
+bool BaseWebSocketChannel::IsOnTargetThread() {
+ nsCOMPtr<nsISerialEventTarget> target = GetTargetThread();
+ if (!target) {
+ MOZ_ASSERT(false);
+ return false;
+ }
+
+ bool isOnTargetThread = false;
+ nsresult rv = target->IsOnCurrentThread(&isOnTargetThread);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_FAILED(rv) ? false : isOnTargetThread;
+}
+
+BaseWebSocketChannel::ListenerAndContextContainer::ListenerAndContextContainer(
+ nsIWebSocketListener* aListener, nsISupports* aContext)
+ : mListener(aListener), mContext(aContext) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mListener);
+}
+
+BaseWebSocketChannel::ListenerAndContextContainer::
+ ~ListenerAndContextContainer() {
+ MOZ_ASSERT(mListener);
+
+ NS_ReleaseOnMainThread(
+ "BaseWebSocketChannel::ListenerAndContextContainer::mListener",
+ mListener.forget());
+ NS_ReleaseOnMainThread(
+ "BaseWebSocketChannel::ListenerAndContextContainer::mContext",
+ mContext.forget());
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/BaseWebSocketChannel.h b/netwerk/protocol/websocket/BaseWebSocketChannel.h
new file mode 100644
index 0000000000..ee05e5bf4c
--- /dev/null
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_BaseWebSocketChannel_h
+#define mozilla_net_BaseWebSocketChannel_h
+
+#include "mozilla/DataMutex.h"
+#include "nsIWebSocketChannel.h"
+#include "nsIWebSocketListener.h"
+#include "nsIProtocolHandler.h"
+#include "nsIThread.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+const static int32_t kDefaultWSPort = 80;
+const static int32_t kDefaultWSSPort = 443;
+
+class BaseWebSocketChannel : public nsIWebSocketChannel,
+ public nsIProtocolHandler,
+ public nsIThreadRetargetableRequest {
+ public:
+ BaseWebSocketChannel();
+
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+
+ NS_IMETHOD QueryInterface(const nsIID& uuid, void** result) override = 0;
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override = 0;
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) override = 0;
+
+ // Partial implementation of nsIWebSocketChannel
+ //
+ NS_IMETHOD GetOriginalURI(nsIURI** aOriginalURI) override;
+ NS_IMETHOD GetURI(nsIURI** aURI) override;
+ NS_IMETHOD GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) override;
+ NS_IMETHOD SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) override;
+ NS_IMETHOD GetLoadGroup(nsILoadGroup** aLoadGroup) override;
+ NS_IMETHOD SetLoadGroup(nsILoadGroup* aLoadGroup) override;
+ NS_IMETHOD SetLoadInfo(nsILoadInfo* aLoadInfo) override;
+ NS_IMETHOD GetLoadInfo(nsILoadInfo** aLoadInfo) override;
+ NS_IMETHOD GetExtensions(nsACString& aExtensions) override;
+ NS_IMETHOD GetProtocol(nsACString& aProtocol) override;
+ NS_IMETHOD SetProtocol(const nsACString& aProtocol) override;
+ NS_IMETHOD GetPingInterval(uint32_t* aSeconds) override;
+ NS_IMETHOD SetPingInterval(uint32_t aSeconds) override;
+ NS_IMETHOD GetPingTimeout(uint32_t* aSeconds) override;
+ NS_IMETHOD SetPingTimeout(uint32_t aSeconds) override;
+ NS_IMETHOD InitLoadInfoNative(nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsICookieJarSettings* aCookieJarSettings,
+ uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ uint32_t aSandboxFlags) override;
+ NS_IMETHOD InitLoadInfo(nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType) override;
+ NS_IMETHOD GetSerial(uint32_t* aSerial) override;
+ NS_IMETHOD SetSerial(uint32_t aSerial) override;
+ NS_IMETHOD SetServerParameters(
+ nsITransportProvider* aProvider,
+ const nsACString& aNegotiatedExtensions) override;
+ NS_IMETHOD GetHttpChannelId(uint64_t* aHttpChannelId) override;
+
+ // Off main thread URI access.
+ virtual void GetEffectiveURL(nsAString& aEffectiveURL) const = 0;
+ virtual bool IsEncrypted() const = 0;
+
+ already_AddRefed<nsISerialEventTarget> GetTargetThread();
+ bool IsOnTargetThread();
+
+ class ListenerAndContextContainer final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ListenerAndContextContainer)
+
+ ListenerAndContextContainer(nsIWebSocketListener* aListener,
+ nsISupports* aContext);
+
+ nsCOMPtr<nsIWebSocketListener> mListener;
+ nsCOMPtr<nsISupports> mContext;
+
+ private:
+ ~ListenerAndContextContainer();
+ };
+
+ protected:
+ virtual ~BaseWebSocketChannel();
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mURI;
+ RefPtr<ListenerAndContextContainer> mListenerMT;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsITransportProvider> mServerTransportProvider;
+
+ // Used to ensure atomicity of mTargetThread.
+ // Set before AsyncOpen via RetargetDeliveryTo or in AsyncOpen, never changed
+ // after AsyncOpen
+ DataMutex<nsCOMPtr<nsISerialEventTarget>> mTargetThread{
+ "BaseWebSocketChannel::EventTargetMutex"};
+
+ nsCString mProtocol;
+ nsCString mOrigin;
+
+ nsCString mNegotiatedExtensions;
+
+ uint32_t mWasOpened : 1;
+ uint32_t mClientSetPingInterval : 1;
+ uint32_t mClientSetPingTimeout : 1;
+
+ Atomic<bool> mEncrypted;
+ bool mPingForced;
+ bool mIsServerSide;
+
+ Atomic<uint32_t> mPingInterval; /* milliseconds */
+ uint32_t mPingResponseTimeout; /* milliseconds */
+
+ uint32_t mSerial;
+
+ uint64_t mHttpChannelId;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_BaseWebSocketChannel_h
diff --git a/netwerk/protocol/websocket/IPCTransportProvider.cpp b/netwerk/protocol/websocket/IPCTransportProvider.cpp
new file mode 100644
index 0000000000..ff902ae835
--- /dev/null
+++ b/netwerk/protocol/websocket/IPCTransportProvider.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/IPCTransportProvider.h"
+
+#include "IPCTransportProvider.h"
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(TransportProviderParent, nsITransportProvider,
+ nsIHttpUpgradeListener)
+
+NS_IMETHODIMP
+TransportProviderParent::SetListener(nsIHttpUpgradeListener* aListener) {
+ MOZ_ASSERT(aListener);
+ mListener = aListener;
+
+ MaybeNotify();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransportProviderParent::GetIPCChild(
+ mozilla::net::PTransportProviderChild** aChild) {
+ MOZ_CRASH("Don't call this in parent process");
+ *aChild = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransportProviderParent::OnTransportAvailable(
+ nsISocketTransport* aTransport, nsIAsyncInputStream* aSocketIn,
+ nsIAsyncOutputStream* aSocketOut) {
+ MOZ_ASSERT(aTransport && aSocketOut && aSocketOut);
+ mTransport = aTransport;
+ mSocketIn = aSocketIn;
+ mSocketOut = aSocketOut;
+
+ MaybeNotify();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransportProviderParent::OnUpgradeFailed(nsresult aErrorCode) { return NS_OK; }
+
+NS_IMETHODIMP
+TransportProviderParent::OnWebSocketConnectionAvailable(
+ WebSocketConnectionBase* aConnection) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void TransportProviderParent::MaybeNotify() {
+ if (!mListener || !mTransport) {
+ return;
+ }
+
+ DebugOnly<nsresult> rv =
+ mListener->OnTransportAvailable(mTransport, mSocketIn, mSocketOut);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+NS_IMPL_ISUPPORTS(TransportProviderChild, nsITransportProvider)
+
+TransportProviderChild::~TransportProviderChild() { Send__delete__(this); }
+
+NS_IMETHODIMP
+TransportProviderChild::SetListener(nsIHttpUpgradeListener* aListener) {
+ MOZ_CRASH("Don't call this in child process");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransportProviderChild::GetIPCChild(
+ mozilla::net::PTransportProviderChild** aChild) {
+ *aChild = this;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/IPCTransportProvider.h b/netwerk/protocol/websocket/IPCTransportProvider.h
new file mode 100644
index 0000000000..de54be127a
--- /dev/null
+++ b/netwerk/protocol/websocket/IPCTransportProvider.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_IPCTransportProvider_h
+#define mozilla_net_IPCTransportProvider_h
+
+#include "nsISupportsImpl.h"
+#include "mozilla/net/PTransportProviderParent.h"
+#include "mozilla/net/PTransportProviderChild.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsITransportProvider.h"
+
+/*
+ * No, the ownership model for TransportProvider is that the child object is
+ * refcounted "normally". I.e. ipdl code doesn't hold a strong reference to
+ * TransportProviderChild.
+ *
+ * When TransportProviderChild goes away, it sends a __delete__ message to the
+ * parent.
+ *
+ * On the parent side, ipdl holds a strong reference to TransportProviderParent.
+ * When the actor is deallocatde it releases the reference to the
+ * TransportProviderParent.
+ *
+ * So effectively the child holds a strong reference to the parent, and are
+ * otherwise normally refcounted and have their lifetime determined by that
+ * refcount.
+ *
+ * The only other caveat is that the creation happens from the parent.
+ * So to create a TransportProvider, a constructor is sent from the parent to
+ * the child. At this time the child gets its first addref.
+ *
+ * A reference to the TransportProvider is then sent as part of some other
+ * message from the parent to the child.
+ *
+ * The receiver of that message can then grab the TransportProviderChild and
+ * without addreffing it, effectively using the refcount that the
+ * TransportProviderChild got on creation.
+ */
+
+class nsISocketTransport;
+class nsIAsyncInputStream;
+class nsIAsyncOutputStream;
+
+namespace mozilla {
+namespace net {
+
+class TransportProviderParent final : public PTransportProviderParent,
+ public nsITransportProvider,
+ public nsIHttpUpgradeListener {
+ public:
+ TransportProviderParent() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITRANSPORTPROVIDER
+ NS_DECL_NSIHTTPUPGRADELISTENER
+
+ void ActorDestroy(ActorDestroyReason aWhy) override {}
+
+ private:
+ ~TransportProviderParent() = default;
+
+ void MaybeNotify();
+
+ nsCOMPtr<nsIHttpUpgradeListener> mListener;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+};
+
+class TransportProviderChild final : public PTransportProviderChild,
+ public nsITransportProvider {
+ public:
+ TransportProviderChild() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITRANSPORTPROVIDER
+
+ private:
+ ~TransportProviderChild();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/websocket/PTransportProvider.ipdl b/netwerk/protocol/websocket/PTransportProvider.ipdl
new file mode 100644
index 0000000000..42ff3589a5
--- /dev/null
+++ b/netwerk/protocol/websocket/PTransportProvider.ipdl
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+include "mozilla/net/IPCTransportProvider.h";
+
+namespace mozilla {
+namespace net {
+
+/*
+ * The only thing this protocol manages is used for is passing a
+ * PTransportProvider object from parent to child and then back to the parent
+ * again. Hence there's no need for any messages on the protocol itself.
+ */
+
+[ManualDealloc, ChildImpl="TransportProviderChild", ParentImpl="TransportProviderParent"]
+async protocol PTransportProvider
+{
+ manager PNecko;
+
+parent:
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/PWebSocket.ipdl b/netwerk/protocol/websocket/PWebSocket.ipdl
new file mode 100644
index 0000000000..bb263049f0
--- /dev/null
+++ b/netwerk/protocol/websocket/PWebSocket.ipdl
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include protocol PBrowser;
+include protocol PTransportProvider;
+include IPCStream;
+include NeckoChannelParams;
+
+include "mozilla/net/WebSocketChannelParent.h";
+include "mozilla/net/WebSocketChannelChild.h";
+
+using class IPC::SerializedLoadContext from "SerializedLoadContext.h";
+[RefCounted] using class nsIURI from "mozilla/ipc/URIUtils.h";
+
+namespace mozilla {
+namespace net {
+
+[ManualDealloc, ChildImpl="WebSocketChannelChild", ParentImpl="WebSocketChannelParent"]
+async protocol PWebSocket
+{
+ manager PNecko;
+
+parent:
+ // Forwarded methods corresponding to methods on nsIWebSocketChannel
+ async AsyncOpen(nullable nsIURI aURI,
+ nsCString aOrigin,
+ OriginAttributes aOriginAttributes,
+ uint64_t aInnerWindowID,
+ nsCString aProtocol,
+ bool aSecure,
+ // ping values only meaningful if client set them
+ uint32_t aPingInterval,
+ bool aClientSetPingInterval,
+ uint32_t aPingTimeout,
+ bool aClientSetPingTimeout,
+ LoadInfoArgs aLoadInfoArgs,
+ PTransportProvider? aProvider,
+ nsCString aNegotiatedExtensions);
+ async Close(uint16_t code, nsCString reason);
+ async SendMsg(nsCString aMsg);
+ async SendBinaryMsg(nsCString aMsg);
+ async SendBinaryStream(IPCStream aStream, uint32_t aLength);
+
+ async DeleteSelf();
+
+child:
+ // Forwarded notifications corresponding to the nsIWebSocketListener interface
+ async OnStart(nsCString aProtocol, nsCString aExtensions,
+ nsString aEffectiveURL, bool aEncrypted,
+ uint64_t aHttpChannelId);
+ async OnStop(nsresult aStatusCode);
+ async OnMessageAvailable(nsCString aMsg, bool aMoreData);
+ async OnBinaryMessageAvailable(nsCString aMsg, bool aMoreData);
+ async OnAcknowledge(uint32_t aSize);
+ async OnServerClose(uint16_t code, nsCString aReason);
+
+ async __delete__();
+
+};
+
+} //namespace net
+} //namespace mozilla
diff --git a/netwerk/protocol/websocket/PWebSocketConnection.ipdl b/netwerk/protocol/websocket/PWebSocketConnection.ipdl
new file mode 100644
index 0000000000..b378320a2b
--- /dev/null
+++ b/netwerk/protocol/websocket/PWebSocketConnection.ipdl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include "mozilla/ipc/TransportSecurityInfoUtils.h";
+
+[RefCounted] using class nsITransportSecurityInfo from "nsITransportSecurityInfo.h";
+
+namespace mozilla {
+namespace net {
+
+[ChildProc=Socket]
+protocol PWebSocketConnection
+{
+parent:
+ async OnTransportAvailable(nullable nsITransportSecurityInfo aSecurityInfo);
+ async OnError(nsresult aStatus);
+ async OnTCPClosed();
+ async OnDataReceived(uint8_t[] aData);
+ async OnUpgradeFailed(nsresult aReason);
+
+child:
+ async WriteOutputData(uint8_t[] aData);
+ async StartReading();
+ async DrainSocketData();
+
+ async __delete__();
+};
+
+} //namespace net
+} //namespace mozilla
diff --git a/netwerk/protocol/websocket/PWebSocketEventListener.ipdl b/netwerk/protocol/websocket/PWebSocketEventListener.ipdl
new file mode 100644
index 0000000000..d48224b337
--- /dev/null
+++ b/netwerk/protocol/websocket/PWebSocketEventListener.ipdl
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+using class mozilla::net::WebSocketFrameData from "mozilla/net/WebSocketFrame.h";
+
+namespace mozilla {
+namespace net {
+
+[ManualDealloc]
+async protocol PWebSocketEventListener
+{
+ manager PNecko;
+
+child:
+ async WebSocketCreated(uint32_t awebSocketSerialID,
+ nsString aURI,
+ nsCString aProtocols);
+
+ async WebSocketOpened(uint32_t awebSocketSerialID,
+ nsString aEffectiveURI,
+ nsCString aProtocols,
+ nsCString aExtensions,
+ uint64_t aHttpChannelId);
+
+ async WebSocketMessageAvailable(uint32_t awebSocketSerialID,
+ nsCString aData,
+ uint16_t aMessageType);
+
+ async WebSocketClosed(uint32_t awebSocketSerialID,
+ bool aWasClean,
+ uint16_t aCode,
+ nsString aReason);
+
+ async FrameReceived(uint32_t aWebSocketSerialID,
+ WebSocketFrameData aFrameData);
+
+ async FrameSent(uint32_t aWebSocketSerialID,
+ WebSocketFrameData aFrameData);
+
+ async __delete__();
+
+parent:
+ async Close();
+};
+
+} //namespace net
+} //namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketChannel.cpp b/netwerk/protocol/websocket/WebSocketChannel.cpp
new file mode 100644
index 0000000000..a1ebf85d8e
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -0,0 +1,4336 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+
+#include "WebSocketChannel.h"
+
+#include "WebSocketConnectionBase.h"
+#include "WebSocketFrame.h"
+#include "WebSocketLog.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Base64.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Utf8.h"
+#include "mozilla/net/WebSocketEventService.h"
+#include "nsAlgorithm.h"
+#include "nsCRT.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsComponentManagerUtils.h"
+#include "nsError.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsICancelable.h"
+#include "nsIChannel.h"
+#include "nsIClassOfService.h"
+#include "nsICryptoHash.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsIDashboardEventNotifier.h"
+#include "nsIEventTarget.h"
+#include "nsIHttpChannel.h"
+#include "nsIIOService.h"
+#include "nsINSSErrorsService.h"
+#include "nsINetworkLinkService.h"
+#include "nsINode.h"
+#include "nsIObserverService.h"
+#include "nsIPrefBranch.h"
+#include "nsIProtocolHandler.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIProxiedChannel.h"
+#include "nsIProxyInfo.h"
+#include "nsIRandomGenerator.h"
+#include "nsIRunnable.h"
+#include "nsISocketTransport.h"
+#include "nsITLSSocketControl.h"
+#include "nsITransportProvider.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIURI.h"
+#include "nsIURIMutator.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransportService2.h"
+#include "nsStringStream.h"
+#include "nsThreadUtils.h"
+#include "plbase64.h"
+#include "prmem.h"
+#include "prnetdb.h"
+#include "zlib.h"
+
+// rather than slurp up all of nsIWebSocket.idl, which lives outside necko, just
+// dupe one constant we need from it
+#define CLOSE_GOING_AWAY 1001
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(WebSocketChannel, nsIWebSocketChannel, nsIHttpUpgradeListener,
+ nsIRequestObserver, nsIStreamListener, nsIProtocolHandler,
+ nsIInputStreamCallback, nsIOutputStreamCallback,
+ nsITimerCallback, nsIDNSListener, nsIProtocolProxyCallback,
+ nsIInterfaceRequestor, nsIChannelEventSink,
+ nsIThreadRetargetableRequest, nsIObserver, nsINamed)
+
+// We implement RFC 6455, which uses Sec-WebSocket-Version: 13 on the wire.
+#define SEC_WEBSOCKET_VERSION "13"
+
+/*
+ * About SSL unsigned certificates
+ *
+ * wss will not work to a host using an unsigned certificate unless there
+ * is already an exception (i.e. it cannot popup a dialog asking for
+ * a security exception). This is similar to how an inlined img will
+ * fail without a dialog if fails for the same reason. This should not
+ * be a problem in practice as it is expected the websocket javascript
+ * is served from the same host as the websocket server (or of course,
+ * a valid cert could just be provided).
+ *
+ */
+
+// some helper classes
+
+//-----------------------------------------------------------------------------
+// FailDelayManager
+//
+// Stores entries (searchable by {host, port}) of connections that have recently
+// failed, so we can do delay of reconnects per RFC 6455 Section 7.2.3
+//-----------------------------------------------------------------------------
+
+// Initial reconnect delay is randomly chosen between 200-400 ms.
+// This is a gentler backoff than the 0-5 seconds the spec offhandedly suggests.
+const uint32_t kWSReconnectInitialBaseDelay = 200;
+const uint32_t kWSReconnectInitialRandomDelay = 200;
+
+// Base lifetime (in ms) of a FailDelay: kept longer if more failures occur
+const uint32_t kWSReconnectBaseLifeTime = 60 * 1000;
+// Maximum reconnect delay (in ms)
+const uint32_t kWSReconnectMaxDelay = 60 * 1000;
+
+// hold record of failed connections, and calculates needed delay for reconnects
+// to same host/path/port.
+class FailDelay {
+ public:
+ FailDelay(nsCString address, nsCString path, int32_t port)
+ : mAddress(std::move(address)), mPath(std::move(path)), mPort(port) {
+ mLastFailure = TimeStamp::Now();
+ mNextDelay = kWSReconnectInitialBaseDelay +
+ (rand() % kWSReconnectInitialRandomDelay);
+ }
+
+ // Called to update settings when connection fails again.
+ void FailedAgain() {
+ mLastFailure = TimeStamp::Now();
+ // We use a truncated exponential backoff as suggested by RFC 6455,
+ // but multiply by 1.5 instead of 2 to be more gradual.
+ mNextDelay = static_cast<uint32_t>(
+ std::min<double>(kWSReconnectMaxDelay, mNextDelay * 1.5));
+ LOG(
+ ("WebSocket: FailedAgain: host=%s, path=%s, port=%d: incremented delay "
+ "to "
+ "%" PRIu32,
+ mAddress.get(), mPath.get(), mPort, mNextDelay));
+ }
+
+ // returns 0 if there is no need to delay (i.e. delay interval is over)
+ uint32_t RemainingDelay(TimeStamp rightNow) {
+ TimeDuration dur = rightNow - mLastFailure;
+ uint32_t sinceFail = (uint32_t)dur.ToMilliseconds();
+ if (sinceFail > mNextDelay) return 0;
+
+ return mNextDelay - sinceFail;
+ }
+
+ bool IsExpired(TimeStamp rightNow) {
+ return (mLastFailure + TimeDuration::FromMilliseconds(
+ kWSReconnectBaseLifeTime + mNextDelay)) <=
+ rightNow;
+ }
+
+ nsCString mAddress; // IP address (or hostname if using proxy)
+ nsCString mPath;
+ int32_t mPort;
+
+ private:
+ TimeStamp mLastFailure; // Time of last failed attempt
+ // mLastFailure + mNextDelay is the soonest we'll allow a reconnect
+ uint32_t mNextDelay; // milliseconds
+};
+
+class FailDelayManager {
+ public:
+ FailDelayManager() {
+ MOZ_COUNT_CTOR(FailDelayManager);
+
+ mDelaysDisabled = false;
+
+ nsCOMPtr<nsIPrefBranch> prefService =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefService) {
+ return;
+ }
+ bool boolpref = true;
+ nsresult rv;
+ rv = prefService->GetBoolPref("network.websocket.delay-failed-reconnects",
+ &boolpref);
+ if (NS_SUCCEEDED(rv) && !boolpref) {
+ mDelaysDisabled = true;
+ }
+ }
+
+ ~FailDelayManager() { MOZ_COUNT_DTOR(FailDelayManager); }
+
+ void Add(nsCString& address, nsCString& path, int32_t port) {
+ if (mDelaysDisabled) return;
+
+ UniquePtr<FailDelay> record(new FailDelay(address, path, port));
+ mEntries.AppendElement(std::move(record));
+ }
+
+ // Element returned may not be valid after next main thread event: don't keep
+ // pointer to it around
+ FailDelay* Lookup(nsCString& address, nsCString& path, int32_t port,
+ uint32_t* outIndex = nullptr) {
+ if (mDelaysDisabled) return nullptr;
+
+ FailDelay* result = nullptr;
+ TimeStamp rightNow = TimeStamp::Now();
+
+ // We also remove expired entries during search: iterate from end to make
+ // indexing simpler
+ for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
+ FailDelay* fail = mEntries[i].get();
+ if (fail->mAddress.Equals(address) && fail->mPath.Equals(path) &&
+ fail->mPort == port) {
+ if (outIndex) *outIndex = i;
+ result = fail;
+ // break here: removing more entries would mess up *outIndex.
+ // Any remaining expired entries will be deleted next time Lookup
+ // finds nothing, which is the most common case anyway.
+ break;
+ }
+ if (fail->IsExpired(rightNow)) {
+ mEntries.RemoveElementAt(i);
+ }
+ }
+ return result;
+ }
+
+ // returns true if channel connects immediately, or false if it's delayed
+ void DelayOrBegin(WebSocketChannel* ws) {
+ if (!mDelaysDisabled) {
+ uint32_t failIndex = 0;
+ FailDelay* fail = Lookup(ws->mAddress, ws->mPath, ws->mPort, &failIndex);
+
+ if (fail) {
+ TimeStamp rightNow = TimeStamp::Now();
+
+ uint32_t remainingDelay = fail->RemainingDelay(rightNow);
+ if (remainingDelay) {
+ // reconnecting within delay interval: delay by remaining time
+ nsresult rv;
+ MutexAutoLock lock(ws->mMutex);
+ rv = NS_NewTimerWithCallback(getter_AddRefs(ws->mReconnectDelayTimer),
+ ws, remainingDelay,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_SUCCEEDED(rv)) {
+ LOG(
+ ("WebSocket: delaying websocket [this=%p] by %lu ms, changing"
+ " state to CONNECTING_DELAYED",
+ ws, (unsigned long)remainingDelay));
+ ws->mConnecting = CONNECTING_DELAYED;
+ return;
+ }
+ // if timer fails (which is very unlikely), drop down to BeginOpen
+ // call
+ } else if (fail->IsExpired(rightNow)) {
+ mEntries.RemoveElementAt(failIndex);
+ }
+ }
+ }
+
+ // Delays disabled, or no previous failure, or we're reconnecting after
+ // scheduled delay interval has passed: connect.
+ ws->BeginOpen(true);
+ }
+
+ // Remove() also deletes all expired entries as it iterates: better for
+ // battery life than using a periodic timer.
+ void Remove(nsCString& address, nsCString& path, int32_t port) {
+ TimeStamp rightNow = TimeStamp::Now();
+
+ // iterate from end, to make deletion indexing easier
+ for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
+ FailDelay* entry = mEntries[i].get();
+ if ((entry->mAddress.Equals(address) && entry->mPath.Equals(path) &&
+ entry->mPort == port) ||
+ entry->IsExpired(rightNow)) {
+ mEntries.RemoveElementAt(i);
+ }
+ }
+ }
+
+ private:
+ nsTArray<UniquePtr<FailDelay>> mEntries;
+ bool mDelaysDisabled;
+};
+
+//-----------------------------------------------------------------------------
+// nsWSAdmissionManager
+//
+// 1) Ensures that only one websocket at a time is CONNECTING to a given IP
+// address (or hostname, if using proxy), per RFC 6455 Section 4.1.
+// 2) Delays reconnects to IP/host after connection failure, per Section 7.2.3
+//-----------------------------------------------------------------------------
+
+class nsWSAdmissionManager {
+ public:
+ static void Init() {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ sManager = new nsWSAdmissionManager();
+ }
+ }
+
+ static void Shutdown() {
+ StaticMutexAutoLock lock(sLock);
+ delete sManager;
+ sManager = nullptr;
+ }
+
+ // Determine if we will open connection immediately (returns true), or
+ // delay/queue the connection (returns false)
+ static void ConditionallyConnect(WebSocketChannel* ws) {
+ LOG(("Websocket: ConditionallyConnect: [this=%p]", ws));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(ws->mConnecting == NOT_CONNECTING, "opening state");
+
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+
+ // If there is already another WS channel connecting to this IP address,
+ // defer BeginOpen and mark as waiting in queue.
+ bool hostFound = (sManager->IndexOf(ws->mAddress, ws->mOriginSuffix) >= 0);
+
+ uint32_t failIndex = 0;
+ FailDelay* fail = sManager->mFailures.Lookup(ws->mAddress, ws->mPath,
+ ws->mPort, &failIndex);
+ bool existingFail = fail != nullptr;
+
+ // Always add ourselves to queue, even if we'll connect immediately
+ UniquePtr<nsOpenConn> newdata(
+ new nsOpenConn(ws->mAddress, ws->mOriginSuffix, existingFail, ws));
+
+ // If a connection has not previously failed then prioritize it over
+ // connections that have
+ if (existingFail) {
+ sManager->mQueue.AppendElement(std::move(newdata));
+ } else {
+ uint32_t insertionIndex = sManager->IndexOfFirstFailure();
+ MOZ_ASSERT(insertionIndex <= sManager->mQueue.Length(),
+ "Insertion index outside bounds");
+ sManager->mQueue.InsertElementAt(insertionIndex, std::move(newdata));
+ }
+
+ if (hostFound) {
+ LOG(
+ ("Websocket: some other channel is connecting, changing state to "
+ "CONNECTING_QUEUED"));
+ ws->mConnecting = CONNECTING_QUEUED;
+ } else {
+ sManager->mFailures.DelayOrBegin(ws);
+ }
+ }
+
+ static void OnConnected(WebSocketChannel* aChannel) {
+ LOG(("Websocket: OnConnected: [this=%p]", aChannel));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(aChannel->mConnecting == CONNECTING_IN_PROGRESS,
+ "Channel completed connect, but not connecting?");
+
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+
+ LOG(("Websocket: changing state to NOT_CONNECTING"));
+ aChannel->mConnecting = NOT_CONNECTING;
+
+ // Remove from queue
+ sManager->RemoveFromQueue(aChannel);
+
+ // Connection succeeded, so stop keeping track of any previous failures
+ sManager->mFailures.Remove(aChannel->mAddress, aChannel->mPath,
+ aChannel->mPort);
+
+ // Check for queued connections to same host.
+ // Note: still need to check for failures, since next websocket with same
+ // host may have different port
+ sManager->ConnectNext(aChannel->mAddress, aChannel->mOriginSuffix);
+ }
+
+ // Called every time a websocket channel ends its session (including going
+ // away w/o ever successfully creating a connection)
+ static void OnStopSession(WebSocketChannel* aChannel, nsresult aReason) {
+ LOG(("Websocket: OnStopSession: [this=%p, reason=0x%08" PRIx32 "]",
+ aChannel, static_cast<uint32_t>(aReason)));
+
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+
+ if (NS_FAILED(aReason)) {
+ // Have we seen this failure before?
+ FailDelay* knownFailure = sManager->mFailures.Lookup(
+ aChannel->mAddress, aChannel->mPath, aChannel->mPort);
+ if (knownFailure) {
+ if (aReason == NS_ERROR_NOT_CONNECTED) {
+ // Don't count close() before connection as a network error
+ LOG(
+ ("Websocket close() before connection to %s, %s, %d completed"
+ " [this=%p]",
+ aChannel->mAddress.get(), aChannel->mPath.get(),
+ (int)aChannel->mPort, aChannel));
+ } else {
+ // repeated failure to connect: increase delay for next connection
+ knownFailure->FailedAgain();
+ }
+ } else {
+ // new connection failure: record it.
+ LOG(("WebSocket: connection to %s, %s, %d failed: [this=%p]",
+ aChannel->mAddress.get(), aChannel->mPath.get(),
+ (int)aChannel->mPort, aChannel));
+ sManager->mFailures.Add(aChannel->mAddress, aChannel->mPath,
+ aChannel->mPort);
+ }
+ }
+
+ if (NS_IsMainThread()) {
+ ContinueOnStopSession(aChannel, aReason);
+ } else {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsWSAdmissionManager::ContinueOnStopSession",
+ [channel = RefPtr{aChannel}, reason = aReason]() {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+
+ nsWSAdmissionManager::ContinueOnStopSession(channel, reason);
+ }));
+ }
+ }
+
+ static void ContinueOnStopSession(WebSocketChannel* aChannel,
+ nsresult aReason) {
+ sLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ if (!aChannel->mConnecting) {
+ return;
+ }
+
+ // Only way a connecting channel may get here w/o failing is if it
+ // was closed with GOING_AWAY (1001) because of navigation, tab
+ // close, etc.
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(aChannel->mMutex);
+ MOZ_ASSERT(
+ NS_FAILED(aReason) || aChannel->mScriptCloseCode == CLOSE_GOING_AWAY,
+ "websocket closed while connecting w/o failing?");
+ }
+#endif
+ Unused << aReason;
+
+ sManager->RemoveFromQueue(aChannel);
+
+ bool wasNotQueued = (aChannel->mConnecting != CONNECTING_QUEUED);
+ LOG(("Websocket: changing state to NOT_CONNECTING"));
+ aChannel->mConnecting = NOT_CONNECTING;
+ if (wasNotQueued) {
+ sManager->ConnectNext(aChannel->mAddress, aChannel->mOriginSuffix);
+ }
+ }
+
+ static void IncrementSessionCount() {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+ sManager->mSessionCount++;
+ }
+
+ static void DecrementSessionCount() {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+ sManager->mSessionCount--;
+ }
+
+ static void GetSessionCount(int32_t& aSessionCount) {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+ aSessionCount = sManager->mSessionCount;
+ }
+
+ private:
+ nsWSAdmissionManager() : mSessionCount(0) {
+ MOZ_COUNT_CTOR(nsWSAdmissionManager);
+ }
+
+ ~nsWSAdmissionManager() { MOZ_COUNT_DTOR(nsWSAdmissionManager); }
+
+ class nsOpenConn {
+ public:
+ nsOpenConn(nsCString& addr, nsCString& originSuffix, bool failed,
+ WebSocketChannel* channel)
+ : mAddress(addr),
+ mOriginSuffix(originSuffix),
+ mFailed(failed),
+ mChannel(channel) {
+ MOZ_COUNT_CTOR(nsOpenConn);
+ }
+ MOZ_COUNTED_DTOR(nsOpenConn)
+
+ nsCString mAddress;
+ nsCString mOriginSuffix;
+ bool mFailed = false;
+ RefPtr<WebSocketChannel> mChannel;
+ };
+
+ void ConnectNext(nsCString& hostName, nsCString& originSuffix) {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ int32_t index = IndexOf(hostName, originSuffix);
+ if (index >= 0) {
+ WebSocketChannel* chan = mQueue[index]->mChannel;
+
+ MOZ_ASSERT(chan->mConnecting == CONNECTING_QUEUED,
+ "transaction not queued but in queue");
+ LOG(("WebSocket: ConnectNext: found channel [this=%p] in queue", chan));
+
+ mFailures.DelayOrBegin(chan);
+ }
+ }
+
+ void RemoveFromQueue(WebSocketChannel* aChannel) {
+ LOG(("Websocket: RemoveFromQueue: [this=%p]", aChannel));
+ int32_t index = IndexOf(aChannel);
+ MOZ_ASSERT(index >= 0, "connection to remove not in queue");
+ if (index >= 0) {
+ mQueue.RemoveElementAt(index);
+ }
+ }
+
+ int32_t IndexOf(nsCString& aAddress, nsCString& aOriginSuffix) {
+ for (uint32_t i = 0; i < mQueue.Length(); i++) {
+ bool isPartitioned = StaticPrefs::privacy_partition_network_state() ||
+ StaticPrefs::privacy_firstparty_isolate();
+ if (aAddress == (mQueue[i])->mAddress &&
+ (!isPartitioned || aOriginSuffix == (mQueue[i])->mOriginSuffix)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ int32_t IndexOf(WebSocketChannel* aChannel) {
+ for (uint32_t i = 0; i < mQueue.Length(); i++) {
+ if (aChannel == (mQueue[i])->mChannel) return i;
+ }
+ return -1;
+ }
+
+ // Returns the index of the first entry that failed, or else the last entry if
+ // none found
+ uint32_t IndexOfFirstFailure() {
+ for (uint32_t i = 0; i < mQueue.Length(); i++) {
+ if (mQueue[i]->mFailed) return i;
+ }
+ return mQueue.Length();
+ }
+
+ // SessionCount might be decremented from the main or the socket
+ // thread, so manage it with atomic counters
+ Atomic<int32_t> mSessionCount;
+
+ // Queue for websockets that have not completed connecting yet.
+ // The first nsOpenConn with a given address will be either be
+ // CONNECTING_IN_PROGRESS or CONNECTING_DELAYED. Later ones with the same
+ // hostname must be CONNECTING_QUEUED.
+ //
+ // We could hash hostnames instead of using a single big vector here, but the
+ // dataset is expected to be small.
+ nsTArray<UniquePtr<nsOpenConn>> mQueue;
+
+ FailDelayManager mFailures;
+
+ static nsWSAdmissionManager* sManager MOZ_GUARDED_BY(sLock);
+ static StaticMutex sLock;
+};
+
+nsWSAdmissionManager* nsWSAdmissionManager::sManager;
+StaticMutex nsWSAdmissionManager::sLock;
+
+//-----------------------------------------------------------------------------
+// CallOnMessageAvailable
+//-----------------------------------------------------------------------------
+
+class CallOnMessageAvailable final : public Runnable {
+ public:
+ CallOnMessageAvailable(WebSocketChannel* aChannel, nsACString& aData,
+ int32_t aLen)
+ : Runnable("net::CallOnMessageAvailable"),
+ mChannel(aChannel),
+ mListenerMT(aChannel->mListenerMT),
+ mData(aData),
+ mLen(aLen) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ if (mListenerMT) {
+ nsresult rv;
+ if (mLen < 0) {
+ rv = mListenerMT->mListener->OnMessageAvailable(mListenerMT->mContext,
+ mData);
+ } else {
+ rv = mListenerMT->mListener->OnBinaryMessageAvailable(
+ mListenerMT->mContext, mData);
+ }
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("OnMessageAvailable or OnBinaryMessageAvailable "
+ "failed with 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ ~CallOnMessageAvailable() = default;
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ nsCString mData;
+ int32_t mLen;
+};
+
+//-----------------------------------------------------------------------------
+// CallOnStop
+//-----------------------------------------------------------------------------
+
+class CallOnStop final : public Runnable {
+ public:
+ CallOnStop(WebSocketChannel* aChannel, nsresult aReason)
+ : Runnable("net::CallOnStop"),
+ mChannel(aChannel),
+ mListenerMT(mChannel->mListenerMT),
+ mReason(aReason) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ if (mListenerMT) {
+ nsresult rv =
+ mListenerMT->mListener->OnStop(mListenerMT->mContext, mReason);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::CallOnStop "
+ "OnStop failed (%08" PRIx32 ")\n",
+ static_cast<uint32_t>(rv)));
+ }
+ mChannel->mListenerMT = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ ~CallOnStop() = default;
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ nsresult mReason;
+};
+
+//-----------------------------------------------------------------------------
+// CallOnServerClose
+//-----------------------------------------------------------------------------
+
+class CallOnServerClose final : public Runnable {
+ public:
+ CallOnServerClose(WebSocketChannel* aChannel, uint16_t aCode,
+ nsACString& aReason)
+ : Runnable("net::CallOnServerClose"),
+ mChannel(aChannel),
+ mListenerMT(mChannel->mListenerMT),
+ mCode(aCode),
+ mReason(aReason) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ if (mListenerMT) {
+ nsresult rv = mListenerMT->mListener->OnServerClose(mListenerMT->mContext,
+ mCode, mReason);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::CallOnServerClose "
+ "OnServerClose failed (%08" PRIx32 ")\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ return NS_OK;
+ }
+
+ private:
+ ~CallOnServerClose() = default;
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ uint16_t mCode;
+ nsCString mReason;
+};
+
+//-----------------------------------------------------------------------------
+// CallAcknowledge
+//-----------------------------------------------------------------------------
+
+class CallAcknowledge final : public Runnable {
+ public:
+ CallAcknowledge(WebSocketChannel* aChannel, uint32_t aSize)
+ : Runnable("net::CallAcknowledge"),
+ mChannel(aChannel),
+ mListenerMT(mChannel->mListenerMT),
+ mSize(aSize) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ LOG(("WebSocketChannel::CallAcknowledge: Size %u\n", mSize));
+ if (mListenerMT) {
+ nsresult rv =
+ mListenerMT->mListener->OnAcknowledge(mListenerMT->mContext, mSize);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::CallAcknowledge: Acknowledge failed (%08" PRIx32
+ ")\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ return NS_OK;
+ }
+
+ private:
+ ~CallAcknowledge() = default;
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ uint32_t mSize;
+};
+
+//-----------------------------------------------------------------------------
+// CallOnTransportAvailable
+//-----------------------------------------------------------------------------
+
+class CallOnTransportAvailable final : public Runnable {
+ public:
+ CallOnTransportAvailable(WebSocketChannel* aChannel,
+ nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aSocketIn,
+ nsIAsyncOutputStream* aSocketOut)
+ : Runnable("net::CallOnTransportAvailble"),
+ mChannel(aChannel),
+ mTransport(aTransport),
+ mSocketIn(aSocketIn),
+ mSocketOut(aSocketOut) {}
+
+ NS_IMETHOD Run() override {
+ LOG(("WebSocketChannel::CallOnTransportAvailable %p\n", this));
+ return mChannel->OnTransportAvailable(mTransport, mSocketIn, mSocketOut);
+ }
+
+ private:
+ ~CallOnTransportAvailable() = default;
+
+ RefPtr<WebSocketChannel> mChannel;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+};
+
+//-----------------------------------------------------------------------------
+// PMCECompression
+//-----------------------------------------------------------------------------
+
+class PMCECompression {
+ public:
+ PMCECompression(bool aNoContextTakeover, int32_t aLocalMaxWindowBits,
+ int32_t aRemoteMaxWindowBits)
+ : mActive(false),
+ mNoContextTakeover(aNoContextTakeover),
+ mResetDeflater(false),
+ mMessageDeflated(false) {
+ this->mDeflater.next_in = nullptr;
+ this->mDeflater.avail_in = 0;
+ this->mDeflater.total_in = 0;
+ this->mDeflater.next_out = nullptr;
+ this->mDeflater.avail_out = 0;
+ this->mDeflater.total_out = 0;
+ this->mDeflater.msg = nullptr;
+ this->mDeflater.state = nullptr;
+ this->mDeflater.data_type = 0;
+ this->mDeflater.adler = 0;
+ this->mDeflater.reserved = 0;
+ this->mInflater.next_in = nullptr;
+ this->mInflater.avail_in = 0;
+ this->mInflater.total_in = 0;
+ this->mInflater.next_out = nullptr;
+ this->mInflater.avail_out = 0;
+ this->mInflater.total_out = 0;
+ this->mInflater.msg = nullptr;
+ this->mInflater.state = nullptr;
+ this->mInflater.data_type = 0;
+ this->mInflater.adler = 0;
+ this->mInflater.reserved = 0;
+ MOZ_COUNT_CTOR(PMCECompression);
+
+ mDeflater.zalloc = mInflater.zalloc = Z_NULL;
+ mDeflater.zfree = mInflater.zfree = Z_NULL;
+ mDeflater.opaque = mInflater.opaque = Z_NULL;
+
+ if (deflateInit2(&mDeflater, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ -aLocalMaxWindowBits, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
+ if (inflateInit2(&mInflater, -aRemoteMaxWindowBits) == Z_OK) {
+ mActive = true;
+ } else {
+ deflateEnd(&mDeflater);
+ }
+ }
+ }
+
+ ~PMCECompression() {
+ MOZ_COUNT_DTOR(PMCECompression);
+
+ if (mActive) {
+ inflateEnd(&mInflater);
+ deflateEnd(&mDeflater);
+ }
+ }
+
+ bool Active() { return mActive; }
+
+ void SetMessageDeflated() {
+ MOZ_ASSERT(!mMessageDeflated);
+ mMessageDeflated = true;
+ }
+ bool IsMessageDeflated() { return mMessageDeflated; }
+
+ bool UsingContextTakeover() { return !mNoContextTakeover; }
+
+ nsresult Deflate(uint8_t* data, uint32_t dataLen, nsACString& _retval) {
+ if (mResetDeflater || mNoContextTakeover) {
+ if (deflateReset(&mDeflater) != Z_OK) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mResetDeflater = false;
+ }
+
+ mDeflater.avail_out = kBufferLen;
+ mDeflater.next_out = mBuffer;
+ mDeflater.avail_in = dataLen;
+ mDeflater.next_in = data;
+
+ while (true) {
+ int zerr = deflate(&mDeflater, Z_SYNC_FLUSH);
+
+ if (zerr != Z_OK) {
+ mResetDeflater = true;
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ uint32_t deflated = kBufferLen - mDeflater.avail_out;
+ if (deflated > 0) {
+ _retval.Append(reinterpret_cast<char*>(mBuffer), deflated);
+ }
+
+ mDeflater.avail_out = kBufferLen;
+ mDeflater.next_out = mBuffer;
+
+ if (mDeflater.avail_in > 0) {
+ continue; // There is still some data to deflate
+ }
+
+ if (deflated == kBufferLen) {
+ continue; // There was not enough space in the buffer
+ }
+
+ break;
+ }
+
+ if (_retval.Length() < 4) {
+ MOZ_ASSERT(false, "Expected trailing not found in deflated data!");
+ mResetDeflater = true;
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ _retval.Truncate(_retval.Length() - 4);
+
+ return NS_OK;
+ }
+
+ nsresult Inflate(uint8_t* data, uint32_t dataLen, nsACString& _retval) {
+ mMessageDeflated = false;
+
+ Bytef trailingData[] = {0x00, 0x00, 0xFF, 0xFF};
+ bool trailingDataUsed = false;
+
+ mInflater.avail_out = kBufferLen;
+ mInflater.next_out = mBuffer;
+ mInflater.avail_in = dataLen;
+ mInflater.next_in = data;
+
+ while (true) {
+ int zerr = inflate(&mInflater, Z_NO_FLUSH);
+
+ if (zerr == Z_STREAM_END) {
+ Bytef* saveNextIn = mInflater.next_in;
+ uint32_t saveAvailIn = mInflater.avail_in;
+ Bytef* saveNextOut = mInflater.next_out;
+ uint32_t saveAvailOut = mInflater.avail_out;
+
+ inflateReset(&mInflater);
+
+ mInflater.next_in = saveNextIn;
+ mInflater.avail_in = saveAvailIn;
+ mInflater.next_out = saveNextOut;
+ mInflater.avail_out = saveAvailOut;
+ } else if (zerr != Z_OK && zerr != Z_BUF_ERROR) {
+ return NS_ERROR_INVALID_CONTENT_ENCODING;
+ }
+
+ uint32_t inflated = kBufferLen - mInflater.avail_out;
+ if (inflated > 0) {
+ if (!_retval.Append(reinterpret_cast<char*>(mBuffer), inflated,
+ fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ mInflater.avail_out = kBufferLen;
+ mInflater.next_out = mBuffer;
+
+ if (mInflater.avail_in > 0) {
+ continue; // There is still some data to inflate
+ }
+
+ if (inflated == kBufferLen) {
+ continue; // There was not enough space in the buffer
+ }
+
+ if (!trailingDataUsed) {
+ trailingDataUsed = true;
+ mInflater.avail_in = sizeof(trailingData);
+ mInflater.next_in = trailingData;
+ continue;
+ }
+
+ return NS_OK;
+ }
+ }
+
+ private:
+ bool mActive;
+ bool mNoContextTakeover;
+ bool mResetDeflater;
+ bool mMessageDeflated;
+ z_stream mDeflater{};
+ z_stream mInflater{};
+ const static uint32_t kBufferLen = 4096;
+ uint8_t mBuffer[kBufferLen]{0};
+};
+
+//-----------------------------------------------------------------------------
+// OutboundMessage
+//-----------------------------------------------------------------------------
+
+enum WsMsgType {
+ kMsgTypeString = 0,
+ kMsgTypeBinaryString,
+ kMsgTypeStream,
+ kMsgTypePing,
+ kMsgTypePong,
+ kMsgTypeFin
+};
+
+static const char* msgNames[] = {"text", "binaryString", "binaryStream",
+ "ping", "pong", "close"};
+
+class OutboundMessage {
+ public:
+ OutboundMessage(WsMsgType type, const nsACString& str)
+ : mMsg(mozilla::AsVariant(pString(str))),
+ mMsgType(type),
+ mDeflated(false) {
+ MOZ_COUNT_CTOR(OutboundMessage);
+ }
+
+ OutboundMessage(nsIInputStream* stream, uint32_t length)
+ : mMsg(mozilla::AsVariant(StreamWithLength(stream, length))),
+ mMsgType(kMsgTypeStream),
+ mDeflated(false) {
+ MOZ_COUNT_CTOR(OutboundMessage);
+ }
+
+ ~OutboundMessage() {
+ MOZ_COUNT_DTOR(OutboundMessage);
+ switch (mMsgType) {
+ case kMsgTypeString:
+ case kMsgTypeBinaryString:
+ case kMsgTypePing:
+ case kMsgTypePong:
+ break;
+ case kMsgTypeStream:
+ // for now this only gets hit if msg deleted w/o being sent
+ if (mMsg.as<StreamWithLength>().mStream) {
+ mMsg.as<StreamWithLength>().mStream->Close();
+ }
+ break;
+ case kMsgTypeFin:
+ break; // do-nothing: avoid compiler warning
+ }
+ }
+
+ WsMsgType GetMsgType() const { return mMsgType; }
+ int32_t Length() {
+ if (mMsg.is<pString>()) {
+ return mMsg.as<pString>().mValue.Length();
+ }
+
+ return mMsg.as<StreamWithLength>().mLength;
+ }
+ int32_t OrigLength() {
+ if (mMsg.is<pString>()) {
+ pString& ref = mMsg.as<pString>();
+ return mDeflated ? ref.mOrigValue.Length() : ref.mValue.Length();
+ }
+
+ return mMsg.as<StreamWithLength>().mLength;
+ }
+
+ uint8_t* BeginWriting() {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ if (!mMsg.as<pString>().mValue.IsVoid()) {
+ return (uint8_t*)mMsg.as<pString>().mValue.BeginWriting();
+ }
+ return nullptr;
+ }
+
+ uint8_t* BeginReading() {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ if (!mMsg.as<pString>().mValue.IsVoid()) {
+ return (uint8_t*)mMsg.as<pString>().mValue.BeginReading();
+ }
+ return nullptr;
+ }
+
+ uint8_t* BeginOrigReading() {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ if (!mDeflated) return BeginReading();
+ if (!mMsg.as<pString>().mOrigValue.IsVoid()) {
+ return (uint8_t*)mMsg.as<pString>().mOrigValue.BeginReading();
+ }
+ return nullptr;
+ }
+
+ nsresult ConvertStreamToString() {
+ MOZ_ASSERT(mMsgType == kMsgTypeStream, "Not a stream!");
+ nsAutoCString temp;
+ {
+ StreamWithLength& ref = mMsg.as<StreamWithLength>();
+ nsresult rv = NS_ReadInputStreamToString(ref.mStream, temp, ref.mLength);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (temp.Length() != ref.mLength) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ ref.mStream->Close();
+ }
+
+ mMsg = mozilla::AsVariant(pString(temp));
+ mMsgType = kMsgTypeBinaryString;
+
+ return NS_OK;
+ }
+
+ bool DeflatePayload(PMCECompression* aCompressor) {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ MOZ_ASSERT(!mDeflated);
+
+ nsresult rv;
+ pString& ref = mMsg.as<pString>();
+ if (ref.mValue.Length() == 0) {
+ // Empty message
+ return false;
+ }
+
+ nsAutoCString temp;
+ rv = aCompressor->Deflate(BeginReading(), ref.mValue.Length(), temp);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::OutboundMessage: Deflating payload failed "
+ "[rv=0x%08" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ return false;
+ }
+
+ if (!aCompressor->UsingContextTakeover() &&
+ temp.Length() > ref.mValue.Length()) {
+ // When "<local>_no_context_takeover" was negotiated, do not send deflated
+ // payload if it's larger that the original one. OTOH, it makes sense
+ // to send the larger deflated payload when the sliding window is not
+ // reset between messages because if we would skip some deflated block
+ // we would need to empty the sliding window which could affect the
+ // compression of the subsequent messages.
+ LOG(
+ ("WebSocketChannel::OutboundMessage: Not deflating message since the "
+ "deflated payload is larger than the original one [deflated=%zd, "
+ "original=%zd]",
+ temp.Length(), ref.mValue.Length()));
+ return false;
+ }
+
+ mDeflated = true;
+ mMsg.as<pString>().mOrigValue = mMsg.as<pString>().mValue;
+ mMsg.as<pString>().mValue = temp;
+ return true;
+ }
+
+ private:
+ struct pString {
+ nsCString mValue;
+ nsCString mOrigValue;
+ explicit pString(const nsACString& value)
+ : mValue(value), mOrigValue(VoidCString()) {}
+ };
+ struct StreamWithLength {
+ nsCOMPtr<nsIInputStream> mStream;
+ uint32_t mLength;
+ explicit StreamWithLength(nsIInputStream* stream, uint32_t Length)
+ : mStream(stream), mLength(Length) {}
+ };
+ mozilla::Variant<pString, StreamWithLength> mMsg;
+ WsMsgType mMsgType;
+ bool mDeflated;
+};
+
+//-----------------------------------------------------------------------------
+// OutboundEnqueuer
+//-----------------------------------------------------------------------------
+
+class OutboundEnqueuer final : public Runnable {
+ public:
+ OutboundEnqueuer(WebSocketChannel* aChannel, OutboundMessage* aMsg)
+ : Runnable("OutboundEnquerer"), mChannel(aChannel), mMessage(aMsg) {}
+
+ NS_IMETHOD Run() override {
+ mChannel->EnqueueOutgoingMessage(mChannel->mOutgoingMessages, mMessage);
+ return NS_OK;
+ }
+
+ private:
+ ~OutboundEnqueuer() = default;
+
+ RefPtr<WebSocketChannel> mChannel;
+ OutboundMessage* mMessage;
+};
+
+//-----------------------------------------------------------------------------
+// WebSocketChannel
+//-----------------------------------------------------------------------------
+
+WebSocketChannel::WebSocketChannel()
+ : mPort(0),
+ mCloseTimeout(20000),
+ mOpenTimeout(20000),
+ mConnecting(NOT_CONNECTING),
+ mMaxConcurrentConnections(200),
+ mInnerWindowID(0),
+ mGotUpgradeOK(0),
+ mRecvdHttpUpgradeTransport(0),
+ mAllowPMCE(1),
+ mPingOutstanding(0),
+ mReleaseOnTransmit(0),
+ mDataStarted(false),
+ mRequestedClose(false),
+ mClientClosed(false),
+ mServerClosed(false),
+ mStopped(false),
+ mCalledOnStop(false),
+ mTCPClosed(false),
+ mOpenedHttpChannel(false),
+ mIncrementedSessionCount(false),
+ mDecrementedSessionCount(false),
+ mMaxMessageSize(INT32_MAX),
+ mStopOnClose(NS_OK),
+ mServerCloseCode(CLOSE_ABNORMAL),
+ mScriptCloseCode(0),
+ mFragmentOpcode(nsIWebSocketFrame::OPCODE_CONTINUATION),
+ mFragmentAccumulator(0),
+ mBuffered(0),
+ mBufferSize(kIncomingBufferInitialSize),
+ mCurrentOut(nullptr),
+ mCurrentOutSent(0),
+ mHdrOutToSend(0),
+ mHdrOut(nullptr),
+ mCompressorMutex("WebSocketChannel::mCompressorMutex"),
+ mDynamicOutputSize(0),
+ mDynamicOutput(nullptr),
+ mPrivateBrowsing(false),
+ mConnectionLogService(nullptr),
+ mMutex("WebSocketChannel::mMutex") {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ LOG(("WebSocketChannel::WebSocketChannel() %p\n", this));
+
+ nsWSAdmissionManager::Init();
+
+ mFramePtr = mBuffer = static_cast<uint8_t*>(moz_xmalloc(mBufferSize));
+
+ nsresult rv;
+ mConnectionLogService =
+ do_GetService("@mozilla.org/network/dashboard;1", &rv);
+ if (NS_FAILED(rv)) LOG(("Failed to initiate dashboard service."));
+
+ mService = WebSocketEventService::GetOrCreate();
+}
+
+WebSocketChannel::~WebSocketChannel() {
+ LOG(("WebSocketChannel::~WebSocketChannel() %p\n", this));
+
+ if (mWasOpened) {
+ MOZ_ASSERT(mCalledOnStop, "WebSocket was opened but OnStop was not called");
+ MOZ_ASSERT(mStopped, "WebSocket was opened but never stopped");
+ }
+ MOZ_ASSERT(!mCancelable, "DNS/Proxy Request still alive at destruction");
+ MOZ_ASSERT(!mConnecting, "Should not be connecting in destructor");
+
+ free(mBuffer);
+ free(mDynamicOutput);
+ delete mCurrentOut;
+
+ while ((mCurrentOut = mOutgoingPingMessages.PopFront())) {
+ delete mCurrentOut;
+ }
+ while ((mCurrentOut = mOutgoingPongMessages.PopFront())) {
+ delete mCurrentOut;
+ }
+ while ((mCurrentOut = mOutgoingMessages.PopFront())) {
+ delete mCurrentOut;
+ }
+
+ mListenerMT = nullptr;
+
+ NS_ReleaseOnMainThread("WebSocketChannel::mService", mService.forget());
+}
+
+NS_IMETHODIMP
+WebSocketChannel::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ LOG(("WebSocketChannel::Observe [topic=\"%s\"]\n", topic));
+
+ if (strcmp(topic, NS_NETWORK_LINK_TOPIC) == 0) {
+ nsCString converted = NS_ConvertUTF16toUTF8(data);
+ const char* state = converted.get();
+
+ if (strcmp(state, NS_NETWORK_LINK_DATA_CHANGED) == 0) {
+ LOG(("WebSocket: received network CHANGED event"));
+
+ if (!mIOThread) {
+ // there has not been an asyncopen yet on the object and then we need
+ // no ping.
+ LOG(("WebSocket: early object, no ping needed"));
+ } else {
+ mIOThread->Dispatch(
+ NewRunnableMethod("net::WebSocketChannel::OnNetworkChanged", this,
+ &WebSocketChannel::OnNetworkChanged),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult WebSocketChannel::OnNetworkChanged() {
+ if (!mDataStarted) {
+ LOG(("WebSocket: data not started yet, no ping needed"));
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ LOG(("WebSocketChannel::OnNetworkChanged() - on socket thread %p", this));
+
+ if (mPingOutstanding) {
+ // If there's an outstanding ping that's expected to get a pong back
+ // we let that do its thing.
+ LOG(("WebSocket: pong already pending"));
+ return NS_OK;
+ }
+
+ if (mPingForced) {
+ // avoid more than one
+ LOG(("WebSocket: forced ping timer already fired"));
+ return NS_OK;
+ }
+
+ LOG(("nsWebSocketChannel:: Generating Ping as network changed\n"));
+
+ if (!mPingTimer) {
+ // The ping timer is only conditionally running already. If it wasn't
+ // already created do it here.
+ mPingTimer = NS_NewTimer();
+ if (!mPingTimer) {
+ LOG(("WebSocket: unable to create ping timer!"));
+ NS_WARNING("unable to create ping timer!");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ // Trigger the ping timeout asap to fire off a new ping. Wait just
+ // a little bit to better avoid multi-triggers.
+ mPingForced = true;
+ mPingTimer->InitWithCallback(this, 200, nsITimer::TYPE_ONE_SHOT);
+
+ return NS_OK;
+}
+
+void WebSocketChannel::Shutdown() { nsWSAdmissionManager::Shutdown(); }
+
+void WebSocketChannel::GetEffectiveURL(nsAString& aEffectiveURL) const {
+ aEffectiveURL = mEffectiveURL;
+}
+
+bool WebSocketChannel::IsEncrypted() const { return mEncrypted; }
+
+void WebSocketChannel::BeginOpen(bool aCalledFromAdmissionManager) {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ LOG(("WebSocketChannel::BeginOpen() %p\n", this));
+
+ // Important that we set CONNECTING_IN_PROGRESS before any call to
+ // AbortSession here: ensures that any remaining queued connection(s) are
+ // scheduled in OnStopSession
+ LOG(("Websocket: changing state to CONNECTING_IN_PROGRESS"));
+ mConnecting = CONNECTING_IN_PROGRESS;
+
+ if (aCalledFromAdmissionManager) {
+ // When called from nsWSAdmissionManager post an event to avoid potential
+ // re-entering of nsWSAdmissionManager and its lock.
+ NS_DispatchToMainThread(
+ NewRunnableMethod("net::WebSocketChannel::BeginOpenInternal", this,
+ &WebSocketChannel::BeginOpenInternal),
+ NS_DISPATCH_NORMAL);
+ } else {
+ BeginOpenInternal();
+ }
+}
+
+// MainThread
+void WebSocketChannel::BeginOpenInternal() {
+ LOG(("WebSocketChannel::BeginOpenInternal() %p\n", this));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ nsresult rv;
+
+ if (mRedirectCallback) {
+ LOG(("WebSocketChannel::BeginOpenInternal: Resuming Redirect\n"));
+ rv = mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
+ mRedirectCallback = nullptr;
+ return;
+ }
+
+ nsCOMPtr<nsIChannel> localChannel = do_QueryInterface(mChannel, &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::BeginOpenInternal: cannot async open\n"));
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ rv = localChannel->AsyncOpen(this);
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::BeginOpenInternal: cannot async open\n"));
+ AbortSession(NS_ERROR_WEBSOCKET_CONNECTION_REFUSED);
+ return;
+ }
+ mOpenedHttpChannel = true;
+
+ rv = NS_NewTimerWithCallback(getter_AddRefs(mOpenTimer), this, mOpenTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::BeginOpenInternal: cannot initialize open "
+ "timer\n"));
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return;
+ }
+}
+
+bool WebSocketChannel::IsPersistentFramePtr() {
+ return (mFramePtr >= mBuffer && mFramePtr < mBuffer + mBufferSize);
+}
+
+// Extends the internal buffer by count and returns the total
+// amount of data available for read
+//
+// Accumulated fragment size is passed in instead of using the member
+// variable beacuse when transitioning from the stack to the persistent
+// read buffer we want to explicitly include them in the buffer instead
+// of as already existing data.
+bool WebSocketChannel::UpdateReadBuffer(uint8_t* buffer, uint32_t count,
+ uint32_t accumulatedFragments,
+ uint32_t* available) {
+ LOG(("WebSocketChannel::UpdateReadBuffer() %p [%p %u]\n", this, buffer,
+ count));
+
+ if (!mBuffered) mFramePtr = mBuffer;
+
+ MOZ_ASSERT(IsPersistentFramePtr(), "update read buffer bad mFramePtr");
+ MOZ_ASSERT(mFramePtr - accumulatedFragments >= mBuffer,
+ "reserved FramePtr bad");
+
+ if (mBuffered + count <= mBufferSize) {
+ // append to existing buffer
+ LOG(("WebSocketChannel: update read buffer absorbed %u\n", count));
+ } else if (mBuffered + count - (mFramePtr - accumulatedFragments - mBuffer) <=
+ mBufferSize) {
+ // make room in existing buffer by shifting unused data to start
+ mBuffered -= (mFramePtr - mBuffer - accumulatedFragments);
+ LOG(("WebSocketChannel: update read buffer shifted %u\n", mBuffered));
+ ::memmove(mBuffer, mFramePtr - accumulatedFragments, mBuffered);
+ mFramePtr = mBuffer + accumulatedFragments;
+ } else {
+ // existing buffer is not sufficient, extend it
+ mBufferSize += count + 8192 + mBufferSize / 3;
+ LOG(("WebSocketChannel: update read buffer extended to %u\n", mBufferSize));
+ uint8_t* old = mBuffer;
+ mBuffer = (uint8_t*)realloc(mBuffer, mBufferSize);
+ if (!mBuffer) {
+ mBuffer = old;
+ return false;
+ }
+ mFramePtr = mBuffer + (mFramePtr - old);
+ }
+
+ ::memcpy(mBuffer + mBuffered, buffer, count);
+ mBuffered += count;
+
+ if (available) *available = mBuffered - (mFramePtr - mBuffer);
+
+ return true;
+}
+
+nsresult WebSocketChannel::ProcessInput(uint8_t* buffer, uint32_t count) {
+ LOG(("WebSocketChannel::ProcessInput %p [%d %d]\n", this, count, mBuffered));
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ nsresult rv;
+
+ // The purpose of ping/pong is to actively probe the peer so that an
+ // unreachable peer is not mistaken for a period of idleness. This
+ // implementation accepts any application level read activity as a sign of
+ // life, it does not necessarily have to be a pong.
+ ResetPingTimer();
+
+ uint32_t avail;
+
+ if (!mBuffered) {
+ // Most of the time we can process right off the stack buffer without
+ // having to accumulate anything
+ mFramePtr = buffer;
+ avail = count;
+ } else {
+ if (!UpdateReadBuffer(buffer, count, mFragmentAccumulator, &avail)) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+ }
+
+ uint8_t* payload;
+ uint32_t totalAvail = avail;
+
+ while (avail >= 2) {
+ int64_t payloadLength64 = mFramePtr[1] & kPayloadLengthBitsMask;
+ uint8_t finBit = mFramePtr[0] & kFinalFragBit;
+ uint8_t rsvBits = mFramePtr[0] & kRsvBitsMask;
+ uint8_t rsvBit1 = mFramePtr[0] & kRsv1Bit;
+ uint8_t rsvBit2 = mFramePtr[0] & kRsv2Bit;
+ uint8_t rsvBit3 = mFramePtr[0] & kRsv3Bit;
+ uint8_t opcode = mFramePtr[0] & kOpcodeBitsMask;
+ uint8_t maskBit = mFramePtr[1] & kMaskBit;
+ uint32_t mask = 0;
+
+ uint32_t framingLength = 2;
+ if (maskBit) framingLength += 4;
+
+ if (payloadLength64 < 126) {
+ if (avail < framingLength) break;
+ } else if (payloadLength64 == 126) {
+ // 16 bit length field
+ framingLength += 2;
+ if (avail < framingLength) break;
+
+ payloadLength64 = mFramePtr[2] << 8 | mFramePtr[3];
+
+ if (payloadLength64 < 126) {
+ // Section 5.2 says that the minimal number of bytes MUST
+ // be used to encode the length in all cases
+ LOG(("WebSocketChannel:: non-minimal-encoded payload length"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ } else {
+ // 64 bit length
+ framingLength += 8;
+ if (avail < framingLength) break;
+
+ if (mFramePtr[2] & 0x80) {
+ // Section 4.2 says that the most significant bit MUST be
+ // 0. (i.e. this is really a 63 bit value)
+ LOG(("WebSocketChannel:: high bit of 64 bit length set"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // copy this in case it is unaligned
+ payloadLength64 = NetworkEndian::readInt64(mFramePtr + 2);
+
+ if (payloadLength64 <= 0xffff) {
+ // Section 5.2 says that the minimal number of bytes MUST
+ // be used to encode the length in all cases
+ LOG(("WebSocketChannel:: non-minimal-encoded payload length"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+
+ payload = mFramePtr + framingLength;
+ avail -= framingLength;
+
+ LOG(("WebSocketChannel::ProcessInput: payload %" PRId64 " avail %" PRIu32
+ "\n",
+ payloadLength64, avail));
+
+ CheckedInt<int64_t> payloadLengthChecked(payloadLength64);
+ payloadLengthChecked += mFragmentAccumulator;
+ if (!payloadLengthChecked.isValid() ||
+ payloadLengthChecked.value() > mMaxMessageSize) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ uint32_t payloadLength = static_cast<uint32_t>(payloadLength64);
+
+ if (avail < payloadLength) break;
+
+ LOG(("WebSocketChannel::ProcessInput: Frame accumulated - opcode %d\n",
+ opcode));
+
+ if (!maskBit && mIsServerSide) {
+ LOG(
+ ("WebSocketChannel::ProcessInput: unmasked frame received "
+ "from client\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (maskBit) {
+ if (!mIsServerSide) {
+ // The server should not be allowed to send masked frames to clients.
+ // But we've been allowing it for some time, so this should be
+ // deprecated with care.
+ LOG(("WebSocketChannel:: Client RECEIVING masked frame."));
+ }
+
+ mask = NetworkEndian::readUint32(payload - 4);
+ }
+
+ if (mask) {
+ ApplyMask(mask, payload, payloadLength);
+ } else if (mIsServerSide) {
+ LOG(
+ ("WebSocketChannel::ProcessInput: masked frame with mask 0 received"
+ "from client\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Control codes are required to have the fin bit set
+ if (!finBit && (opcode & kControlFrameMask)) {
+ LOG(("WebSocketChannel:: fragmented control frame code %d\n", opcode));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (rsvBits) {
+ // PMCE sets RSV1 bit in the first fragment when the non-control frame
+ // is deflated
+ MutexAutoLock lock(mCompressorMutex);
+ if (mPMCECompressor && rsvBits == kRsv1Bit && mFragmentAccumulator == 0 &&
+ !(opcode & kControlFrameMask)) {
+ mPMCECompressor->SetMessageDeflated();
+ LOG(("WebSocketChannel::ProcessInput: received deflated frame\n"));
+ } else {
+ LOG(("WebSocketChannel::ProcessInput: unexpected reserved bits %x\n",
+ rsvBits));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+
+ if (!finBit || opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
+ // This is part of a fragment response
+
+ // Only the first frame has a non zero op code: Make sure we don't see a
+ // first frame while some old fragments are open
+ if ((mFragmentAccumulator != 0) &&
+ (opcode != nsIWebSocketFrame::OPCODE_CONTINUATION)) {
+ LOG(("WebSocketChannel:: nested fragments\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ LOG(("WebSocketChannel:: Accumulating Fragment %" PRIu32 "\n",
+ payloadLength));
+
+ if (opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
+ // Make sure this continuation fragment isn't the first fragment
+ if (mFragmentOpcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
+ LOG(("WebSocketHeandler:: continuation code in first fragment\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // For frag > 1 move the data body back on top of the headers
+ // so we have contiguous stream of data
+ MOZ_ASSERT(mFramePtr + framingLength == payload,
+ "payload offset from frameptr wrong");
+ ::memmove(mFramePtr, payload, avail);
+ payload = mFramePtr;
+ if (mBuffered) mBuffered -= framingLength;
+ } else {
+ mFragmentOpcode = opcode;
+ }
+
+ if (finBit) {
+ LOG(("WebSocketChannel:: Finalizing Fragment\n"));
+ payload -= mFragmentAccumulator;
+ payloadLength += mFragmentAccumulator;
+ avail += mFragmentAccumulator;
+ mFragmentAccumulator = 0;
+ opcode = mFragmentOpcode;
+ // reset to detect if next message illegally starts with continuation
+ mFragmentOpcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
+ } else {
+ opcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
+ mFragmentAccumulator += payloadLength;
+ }
+ } else if (mFragmentAccumulator != 0 && !(opcode & kControlFrameMask)) {
+ // This frame is not part of a fragment sequence but we
+ // have an open fragment.. it must be a control code or else
+ // we have a problem
+ LOG(("WebSocketChannel:: illegal fragment sequence\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (mServerClosed) {
+ LOG(("WebSocketChannel:: ignoring read frame code %d after close\n",
+ opcode));
+ // nop
+ } else if (mStopped) {
+ LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n",
+ opcode));
+ } else if (opcode == nsIWebSocketFrame::OPCODE_TEXT) {
+ if (mListenerMT) {
+ nsCString utf8Data;
+ {
+ MutexAutoLock lock(mCompressorMutex);
+ bool isDeflated =
+ mPMCECompressor && mPMCECompressor->IsMessageDeflated();
+ LOG(("WebSocketChannel:: %stext frame received\n",
+ isDeflated ? "deflated " : ""));
+
+ if (isDeflated) {
+ rv = mPMCECompressor->Inflate(payload, payloadLength, utf8Data);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ LOG(
+ ("WebSocketChannel:: message successfully inflated "
+ "[origLength=%d, newLength=%zd]\n",
+ payloadLength, utf8Data.Length()));
+ } else {
+ if (!utf8Data.Assign((const char*)payload, payloadLength,
+ mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+
+ // Section 8.1 says to fail connection if invalid utf-8 in text message
+ if (!IsUtf8(utf8Data)) {
+ LOG(("WebSocketChannel:: text frame invalid utf-8\n"));
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+
+ RefPtr<WebSocketFrame> frame = mService->CreateFrameIfNeeded(
+ finBit, rsvBit1, rsvBit2, rsvBit3, opcode, maskBit, mask, utf8Data);
+
+ if (frame) {
+ mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
+ }
+
+ if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) {
+ target->Dispatch(new CallOnMessageAvailable(this, utf8Data, -1),
+ NS_DISPATCH_NORMAL);
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
+ LOG(("Added new msg received for %s", mHost.get()));
+ }
+ }
+ } else if (opcode & kControlFrameMask) {
+ // control frames
+ if (payloadLength > 125) {
+ LOG(("WebSocketChannel:: bad control frame code %d length %d\n", opcode,
+ payloadLength));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ RefPtr<WebSocketFrame> frame = mService->CreateFrameIfNeeded(
+ finBit, rsvBit1, rsvBit2, rsvBit3, opcode, maskBit, mask, payload,
+ payloadLength);
+
+ if (opcode == nsIWebSocketFrame::OPCODE_CLOSE) {
+ LOG(("WebSocketChannel:: close received\n"));
+ mServerClosed = true;
+
+ mServerCloseCode = CLOSE_NO_STATUS;
+ if (payloadLength >= 2) {
+ mServerCloseCode = NetworkEndian::readUint16(payload);
+ LOG(("WebSocketChannel:: close recvd code %u\n", mServerCloseCode));
+ uint16_t msglen = static_cast<uint16_t>(payloadLength - 2);
+ if (msglen > 0) {
+ mServerCloseReason.SetLength(msglen);
+ memcpy(mServerCloseReason.BeginWriting(), (const char*)payload + 2,
+ msglen);
+
+ // section 8.1 says to replace received non utf-8 sequences
+ // (which are non-conformant to send) with u+fffd,
+ // but secteam feels that silently rewriting messages is
+ // inappropriate - so we will fail the connection instead.
+ if (!IsUtf8(mServerCloseReason)) {
+ LOG(("WebSocketChannel:: close frame invalid utf-8\n"));
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+
+ LOG(("WebSocketChannel:: close msg %s\n",
+ mServerCloseReason.get()));
+ }
+ }
+
+ if (mCloseTimer) {
+ mCloseTimer->Cancel();
+ mCloseTimer = nullptr;
+ }
+
+ if (frame) {
+ // We send the frame immediately becuase we want to have it dispatched
+ // before the CallOnServerClose.
+ mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
+ frame = nullptr;
+ }
+
+ if (mListenerMT) {
+ if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) {
+ target->Dispatch(new CallOnServerClose(this, mServerCloseCode,
+ mServerCloseReason),
+ NS_DISPATCH_NORMAL);
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (mClientClosed) ReleaseSession();
+ } else if (opcode == nsIWebSocketFrame::OPCODE_PING) {
+ LOG(("WebSocketChannel:: ping received\n"));
+ GeneratePong(payload, payloadLength);
+ } else if (opcode == nsIWebSocketFrame::OPCODE_PONG) {
+ // opcode OPCODE_PONG: the mere act of receiving the packet is all we
+ // need to do for the pong to trigger the activity timers
+ LOG(("WebSocketChannel:: pong received\n"));
+ } else {
+ /* unknown control frame opcode */
+ LOG(("WebSocketChannel:: unknown control op code %d\n", opcode));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (mFragmentAccumulator) {
+ // Remove the control frame from the stream so we have a contiguous
+ // data buffer of reassembled fragments
+ LOG(("WebSocketChannel:: Removing Control From Read buffer\n"));
+ MOZ_ASSERT(mFramePtr + framingLength == payload,
+ "payload offset from frameptr wrong");
+ ::memmove(mFramePtr, payload + payloadLength, avail - payloadLength);
+ payload = mFramePtr;
+ avail -= payloadLength;
+ if (mBuffered) mBuffered -= framingLength + payloadLength;
+ payloadLength = 0;
+ }
+
+ if (frame) {
+ mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
+ }
+ } else if (opcode == nsIWebSocketFrame::OPCODE_BINARY) {
+ if (mListenerMT) {
+ nsCString binaryData;
+ {
+ MutexAutoLock lock(mCompressorMutex);
+ bool isDeflated =
+ mPMCECompressor && mPMCECompressor->IsMessageDeflated();
+ LOG(("WebSocketChannel:: %sbinary frame received\n",
+ isDeflated ? "deflated " : ""));
+
+ if (isDeflated) {
+ rv = mPMCECompressor->Inflate(payload, payloadLength, binaryData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ LOG(
+ ("WebSocketChannel:: message successfully inflated "
+ "[origLength=%d, newLength=%zd]\n",
+ payloadLength, binaryData.Length()));
+ } else {
+ if (!binaryData.Assign((const char*)payload, payloadLength,
+ mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+
+ RefPtr<WebSocketFrame> frame =
+ mService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
+ opcode, maskBit, mask, binaryData);
+ if (frame) {
+ mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
+ }
+
+ if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) {
+ target->Dispatch(
+ new CallOnMessageAvailable(this, binaryData, binaryData.Length()),
+ NS_DISPATCH_NORMAL);
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ // To add the header to 'Networking Dashboard' log
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
+ LOG(("Added new received msg for %s", mHost.get()));
+ }
+ }
+ } else if (opcode != nsIWebSocketFrame::OPCODE_CONTINUATION) {
+ /* unknown opcode */
+ LOG(("WebSocketChannel:: unknown op code %d\n", opcode));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mFramePtr = payload + payloadLength;
+ avail -= payloadLength;
+ totalAvail = avail;
+ }
+
+ // Adjust the stateful buffer. If we were operating off the stack and
+ // now have a partial message then transition to the buffer, or if
+ // we were working off the buffer but no longer have any active state
+ // then transition to the stack
+ if (!IsPersistentFramePtr()) {
+ mBuffered = 0;
+
+ if (mFragmentAccumulator) {
+ LOG(("WebSocketChannel:: Setup Buffer due to fragment"));
+
+ if (!UpdateReadBuffer(mFramePtr - mFragmentAccumulator,
+ totalAvail + mFragmentAccumulator, 0, nullptr)) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ // UpdateReadBuffer will reset the frameptr to the beginning
+ // of new saved state, so we need to skip past processed framgents
+ mFramePtr += mFragmentAccumulator;
+ } else if (totalAvail) {
+ LOG(("WebSocketChannel:: Setup Buffer due to partial frame"));
+ if (!UpdateReadBuffer(mFramePtr, totalAvail, 0, nullptr)) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+ }
+ } else if (!mFragmentAccumulator && !totalAvail) {
+ // If we were working off a saved buffer state and there is no partial
+ // frame or fragment in process, then revert to stack behavior
+ LOG(("WebSocketChannel:: Internal buffering not needed anymore"));
+ mBuffered = 0;
+
+ // release memory if we've been processing a large message
+ if (mBufferSize > kIncomingBufferStableSize) {
+ mBufferSize = kIncomingBufferStableSize;
+ free(mBuffer);
+ mBuffer = (uint8_t*)moz_xmalloc(mBufferSize);
+ }
+ }
+ return NS_OK;
+}
+
+/* static */
+void WebSocketChannel::ApplyMask(uint32_t mask, uint8_t* data, uint64_t len) {
+ if (!data || len == 0) return;
+
+ // Optimally we want to apply the mask 32 bits at a time,
+ // but the buffer might not be alligned. So we first deal with
+ // 0 to 3 bytes of preamble individually
+
+ while (len && (reinterpret_cast<uintptr_t>(data) & 3)) {
+ *data ^= mask >> 24;
+ mask = RotateLeft(mask, 8);
+ data++;
+ len--;
+ }
+
+ // perform mask on full words of data
+
+ uint32_t* iData = (uint32_t*)data;
+ uint32_t* end = iData + (len / 4);
+ NetworkEndian::writeUint32(&mask, mask);
+ for (; iData < end; iData++) *iData ^= mask;
+ mask = NetworkEndian::readUint32(&mask);
+ data = (uint8_t*)iData;
+ len = len % 4;
+
+ // There maybe up to 3 trailing bytes that need to be dealt with
+ // individually
+
+ while (len) {
+ *data ^= mask >> 24;
+ mask = RotateLeft(mask, 8);
+ data++;
+ len--;
+ }
+}
+
+void WebSocketChannel::GeneratePing() {
+ nsAutoCString buf;
+ buf.AssignLiteral("PING");
+ EnqueueOutgoingMessage(mOutgoingPingMessages,
+ new OutboundMessage(kMsgTypePing, buf));
+}
+
+void WebSocketChannel::GeneratePong(uint8_t* payload, uint32_t len) {
+ nsAutoCString buf;
+ buf.SetLength(len);
+ if (buf.Length() < len) {
+ LOG(("WebSocketChannel::GeneratePong Allocation Failure\n"));
+ return;
+ }
+
+ memcpy(buf.BeginWriting(), payload, len);
+ EnqueueOutgoingMessage(mOutgoingPongMessages,
+ new OutboundMessage(kMsgTypePong, buf));
+}
+
+void WebSocketChannel::EnqueueOutgoingMessage(nsDeque<OutboundMessage>& aQueue,
+ OutboundMessage* aMsg) {
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ LOG(
+ ("WebSocketChannel::EnqueueOutgoingMessage %p "
+ "queueing msg %p [type=%s len=%d]\n",
+ this, aMsg, msgNames[aMsg->GetMsgType()], aMsg->Length()));
+
+ aQueue.Push(aMsg);
+ if (mSocketOut) {
+ OnOutputStreamReady(mSocketOut);
+ } else {
+ DoEnqueueOutgoingMessage();
+ }
+}
+
+uint16_t WebSocketChannel::ResultToCloseCode(nsresult resultCode) {
+ if (NS_SUCCEEDED(resultCode)) return CLOSE_NORMAL;
+
+ switch (resultCode) {
+ case NS_ERROR_FILE_TOO_BIG:
+ case NS_ERROR_OUT_OF_MEMORY:
+ return CLOSE_TOO_LARGE;
+ case NS_ERROR_CANNOT_CONVERT_DATA:
+ return CLOSE_INVALID_PAYLOAD;
+ case NS_ERROR_UNEXPECTED:
+ return CLOSE_INTERNAL_ERROR;
+ default:
+ return CLOSE_PROTOCOL_ERROR;
+ }
+}
+
+void WebSocketChannel::PrimeNewOutgoingMessage() {
+ LOG(("WebSocketChannel::PrimeNewOutgoingMessage() %p\n", this));
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+ MOZ_ASSERT(!mCurrentOut, "Current message in progress");
+
+ nsresult rv = NS_OK;
+
+ mCurrentOut = mOutgoingPongMessages.PopFront();
+ if (mCurrentOut) {
+ MOZ_ASSERT(mCurrentOut->GetMsgType() == kMsgTypePong, "Not pong message!");
+ } else {
+ mCurrentOut = mOutgoingPingMessages.PopFront();
+ if (mCurrentOut) {
+ MOZ_ASSERT(mCurrentOut->GetMsgType() == kMsgTypePing,
+ "Not ping message!");
+ } else {
+ mCurrentOut = mOutgoingMessages.PopFront();
+ }
+ }
+
+ if (!mCurrentOut) return;
+
+ auto cleanupAfterFailure =
+ MakeScopeExit([&] { DeleteCurrentOutGoingMessage(); });
+
+ WsMsgType msgType = mCurrentOut->GetMsgType();
+
+ LOG(
+ ("WebSocketChannel::PrimeNewOutgoingMessage "
+ "%p found queued msg %p [type=%s len=%d]\n",
+ this, mCurrentOut, msgNames[msgType], mCurrentOut->Length()));
+
+ mCurrentOutSent = 0;
+ mHdrOut = mOutHeader;
+
+ uint8_t maskBit = mIsServerSide ? 0 : kMaskBit;
+ uint8_t maskSize = mIsServerSide ? 0 : 4;
+
+ uint8_t* payload = nullptr;
+
+ if (msgType == kMsgTypeFin) {
+ // This is a demand to create a close message
+ if (mClientClosed) {
+ DeleteCurrentOutGoingMessage();
+ PrimeNewOutgoingMessage();
+ cleanupAfterFailure.release();
+ return;
+ }
+
+ mClientClosed = true;
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_CLOSE;
+ mOutHeader[1] = maskBit;
+
+ // payload is offset 2 plus size of the mask
+ payload = mOutHeader + 2 + maskSize;
+
+ // The close reason code sits in the first 2 bytes of payload
+ // If the channel user provided a code and reason during Close()
+ // and there isn't an internal error, use that.
+ if (NS_SUCCEEDED(mStopOnClose)) {
+ MutexAutoLock lock(mMutex);
+ if (mScriptCloseCode) {
+ NetworkEndian::writeUint16(payload, mScriptCloseCode);
+ mOutHeader[1] += 2;
+ mHdrOutToSend = 4 + maskSize;
+ if (!mScriptCloseReason.IsEmpty()) {
+ MOZ_ASSERT(mScriptCloseReason.Length() <= 123,
+ "Close Reason Too Long");
+ mOutHeader[1] += mScriptCloseReason.Length();
+ mHdrOutToSend += mScriptCloseReason.Length();
+ memcpy(payload + 2, mScriptCloseReason.BeginReading(),
+ mScriptCloseReason.Length());
+ }
+ } else {
+ // No close code/reason, so payload length = 0. We must still send mask
+ // even though it's not used. Keep payload offset so we write mask
+ // below.
+ mHdrOutToSend = 2 + maskSize;
+ }
+ } else {
+ NetworkEndian::writeUint16(payload, ResultToCloseCode(mStopOnClose));
+ mOutHeader[1] += 2;
+ mHdrOutToSend = 4 + maskSize;
+ }
+
+ if (mServerClosed) {
+ /* bidi close complete */
+ mReleaseOnTransmit = 1;
+ } else if (NS_FAILED(mStopOnClose)) {
+ /* result of abort session - give up */
+ StopSession(mStopOnClose);
+ } else {
+ /* wait for reciprocal close from server */
+ rv = NS_NewTimerWithCallback(getter_AddRefs(mCloseTimer), this,
+ mCloseTimeout, nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ StopSession(rv);
+ }
+ }
+ } else {
+ switch (msgType) {
+ case kMsgTypePong:
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PONG;
+ break;
+ case kMsgTypePing:
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PING;
+ break;
+ case kMsgTypeString:
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_TEXT;
+ break;
+ case kMsgTypeStream:
+ // HACK ALERT: read in entire stream into string.
+ // Will block socket transport thread if file is blocking.
+ // TODO: bug 704447: don't block socket thread!
+ rv = mCurrentOut->ConvertStreamToString();
+ if (NS_FAILED(rv)) {
+ AbortSession(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+ // Now we're a binary string
+ msgType = kMsgTypeBinaryString;
+
+ // no break: fall down into binary string case
+ [[fallthrough]];
+
+ case kMsgTypeBinaryString:
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_BINARY;
+ break;
+ case kMsgTypeFin:
+ MOZ_ASSERT(false, "unreachable"); // avoid compiler warning
+ break;
+ }
+
+ // deflate the payload if PMCE is negotiated
+ MutexAutoLock lock(mCompressorMutex);
+ if (mPMCECompressor &&
+ (msgType == kMsgTypeString || msgType == kMsgTypeBinaryString)) {
+ if (mCurrentOut->DeflatePayload(mPMCECompressor.get())) {
+ // The payload was deflated successfully, set RSV1 bit
+ mOutHeader[0] |= kRsv1Bit;
+
+ LOG(
+ ("WebSocketChannel::PrimeNewOutgoingMessage %p current msg %p was "
+ "deflated [origLength=%d, newLength=%d].\n",
+ this, mCurrentOut, mCurrentOut->OrigLength(),
+ mCurrentOut->Length()));
+ }
+ }
+
+ if (mCurrentOut->Length() < 126) {
+ mOutHeader[1] = mCurrentOut->Length() | maskBit;
+ mHdrOutToSend = 2 + maskSize;
+ } else if (mCurrentOut->Length() <= 0xffff) {
+ mOutHeader[1] = 126 | maskBit;
+ NetworkEndian::writeUint16(mOutHeader + sizeof(uint16_t),
+ mCurrentOut->Length());
+ mHdrOutToSend = 4 + maskSize;
+ } else {
+ mOutHeader[1] = 127 | maskBit;
+ NetworkEndian::writeUint64(mOutHeader + 2, mCurrentOut->Length());
+ mHdrOutToSend = 10 + maskSize;
+ }
+ payload = mOutHeader + mHdrOutToSend;
+ }
+
+ MOZ_ASSERT(payload, "payload offset not found");
+
+ uint32_t mask = 0;
+ if (!mIsServerSide) {
+ // Perform the sending mask. Never use a zero mask
+ do {
+ static_assert(4 == sizeof(mask), "Size of the mask should be equal to 4");
+ nsresult rv = mRandomGenerator->GenerateRandomBytesInto(mask);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::PrimeNewOutgoingMessage(): "
+ "GenerateRandomBytes failure %" PRIx32 "\n",
+ static_cast<uint32_t>(rv)));
+ AbortSession(rv);
+ return;
+ }
+ } while (!mask);
+ NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask);
+ }
+
+ LOG(("WebSocketChannel::PrimeNewOutgoingMessage() using mask %08x\n", mask));
+
+ // We don't mask the framing, but occasionally we stick a little payload
+ // data in the buffer used for the framing. Close frames are the current
+ // example. This data needs to be masked, but it is never more than a
+ // handful of bytes and might rotate the mask, so we can just do it locally.
+ // For real data frames we ship the bulk of the payload off to ApplyMask()
+
+ RefPtr<WebSocketFrame> frame = mService->CreateFrameIfNeeded(
+ mOutHeader[0] & WebSocketChannel::kFinalFragBit,
+ mOutHeader[0] & WebSocketChannel::kRsv1Bit,
+ mOutHeader[0] & WebSocketChannel::kRsv2Bit,
+ mOutHeader[0] & WebSocketChannel::kRsv3Bit,
+ mOutHeader[0] & WebSocketChannel::kOpcodeBitsMask,
+ mOutHeader[1] & WebSocketChannel::kMaskBit, mask, payload,
+ mHdrOutToSend - (payload - mOutHeader), mCurrentOut->BeginOrigReading(),
+ mCurrentOut->OrigLength());
+
+ if (frame) {
+ mService->FrameSent(mSerial, mInnerWindowID, frame.forget());
+ }
+
+ if (mask) {
+ while (payload < (mOutHeader + mHdrOutToSend)) {
+ *payload ^= mask >> 24;
+ mask = RotateLeft(mask, 8);
+ payload++;
+ }
+
+ // Mask the real message payloads
+ ApplyMask(mask, mCurrentOut->BeginWriting(), mCurrentOut->Length());
+ }
+
+ int32_t len = mCurrentOut->Length();
+
+ // for small frames, copy it all together for a contiguous write
+ if (len && len <= kCopyBreak) {
+ memcpy(mOutHeader + mHdrOutToSend, mCurrentOut->BeginWriting(), len);
+ mHdrOutToSend += len;
+ mCurrentOutSent = len;
+ }
+
+ // Transmitting begins - mHdrOutToSend bytes from mOutHeader and
+ // mCurrentOut->Length() bytes from mCurrentOut. The latter may be
+ // coaleseced into the former for small messages or as the result of the
+ // compression process.
+
+ cleanupAfterFailure.release();
+}
+
+void WebSocketChannel::DeleteCurrentOutGoingMessage() {
+ delete mCurrentOut;
+ mCurrentOut = nullptr;
+ mCurrentOutSent = 0;
+}
+
+void WebSocketChannel::EnsureHdrOut(uint32_t size) {
+ LOG(("WebSocketChannel::EnsureHdrOut() %p [%d]\n", this, size));
+
+ if (mDynamicOutputSize < size) {
+ mDynamicOutputSize = size;
+ mDynamicOutput = (uint8_t*)moz_xrealloc(mDynamicOutput, mDynamicOutputSize);
+ }
+
+ mHdrOut = mDynamicOutput;
+}
+
+namespace {
+
+class RemoveObserverRunnable : public Runnable {
+ RefPtr<WebSocketChannel> mChannel;
+
+ public:
+ explicit RemoveObserverRunnable(WebSocketChannel* aChannel)
+ : Runnable("net::RemoveObserverRunnable"), mChannel(aChannel) {}
+
+ NS_IMETHOD Run() override {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ NS_WARNING("failed to get observer service");
+ return NS_OK;
+ }
+
+ observerService->RemoveObserver(mChannel, NS_NETWORK_LINK_TOPIC);
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+void WebSocketChannel::CleanupConnection() {
+ // normally this should be called on socket thread, but it may be called
+ // on MainThread
+
+ LOG(("WebSocketChannel::CleanupConnection() %p", this));
+ // This needs to run on the IOThread so we don't need to lock a bunch of these
+ if (!mIOThread->IsOnCurrentThread()) {
+ mIOThread->Dispatch(
+ NewRunnableMethod("net::WebSocketChannel::CleanupConnection", this,
+ &WebSocketChannel::CleanupConnection),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ if (mLingeringCloseTimer) {
+ mLingeringCloseTimer->Cancel();
+ mLingeringCloseTimer = nullptr;
+ }
+
+ if (mSocketIn) {
+ if (mDataStarted) {
+ mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+ mSocketIn = nullptr;
+ }
+
+ if (mSocketOut) {
+ mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mSocketOut = nullptr;
+ }
+
+ if (mTransport) {
+ mTransport->SetSecurityCallbacks(nullptr);
+ mTransport->SetEventSink(nullptr, nullptr);
+ mTransport->Close(NS_BASE_STREAM_CLOSED);
+ mTransport = nullptr;
+ }
+
+ if (mConnection) {
+ mConnection->Close();
+ mConnection = nullptr;
+ }
+
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->RemoveHost(mHost, mSerial);
+ }
+
+ // The observer has to be removed on the main-thread.
+ NS_DispatchToMainThread(new RemoveObserverRunnable(this));
+
+ DecrementSessionCount();
+}
+
+void WebSocketChannel::StopSession(nsresult reason) {
+ LOG(("WebSocketChannel::StopSession() %p [%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(reason)));
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mStopped) {
+ return;
+ }
+ mStopped = true;
+ }
+
+ DoStopSession(reason);
+}
+
+void WebSocketChannel::DoStopSession(nsresult reason) {
+ LOG(("WebSocketChannel::DoStopSession() %p [%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(reason)));
+
+ // normally this should be called on socket thread, but it is ok to call it
+ // from OnStartRequest before the socket thread machine has gotten underway.
+ // If mDataStarted is false, this is called on MainThread for Close().
+ // Otherwise it should be called on the IO thread
+
+ MOZ_ASSERT(mStopped);
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread() || mTCPClosed || !mDataStarted);
+
+ if (!mOpenedHttpChannel) {
+ // The HTTP channel information will never be used in this case
+ NS_ReleaseOnMainThread("WebSocketChannel::mChannel", mChannel.forget());
+ NS_ReleaseOnMainThread("WebSocketChannel::mHttpChannel",
+ mHttpChannel.forget());
+ NS_ReleaseOnMainThread("WebSocketChannel::mLoadGroup", mLoadGroup.forget());
+ NS_ReleaseOnMainThread("WebSocketChannel::mCallbacks", mCallbacks.forget());
+ }
+
+ if (mCloseTimer) {
+ mCloseTimer->Cancel();
+ mCloseTimer = nullptr;
+ }
+
+ // mOpenTimer must be null if mDataStarted is true and we're not on MainThread
+ if (mOpenTimer) {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ mOpenTimer->Cancel();
+ mOpenTimer = nullptr;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mReconnectDelayTimer) {
+ mReconnectDelayTimer->Cancel();
+ NS_ReleaseOnMainThread("WebSocketChannel::mMutex",
+ mReconnectDelayTimer.forget());
+ }
+ }
+
+ if (mPingTimer) {
+ mPingTimer->Cancel();
+ mPingTimer = nullptr;
+ }
+
+ if (!mTCPClosed && mDataStarted) {
+ if (mSocketIn) {
+ // Drain, within reason, this socket. if we leave any data
+ // unconsumed (including the tcp fin) a RST will be generated
+ // The right thing to do here is shutdown(SHUT_WR) and then wait
+ // a little while to see if any data comes in.. but there is no
+ // reason to delay things for that when the websocket handshake
+ // is supposed to guarantee a quiet connection except for that fin.
+
+ char buffer[512];
+ uint32_t count = 0;
+ uint32_t total = 0;
+ nsresult rv;
+ do {
+ total += count;
+ rv = mSocketIn->Read(buffer, 512, &count);
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK && (NS_FAILED(rv) || count == 0)) {
+ mTCPClosed = true;
+ }
+ } while (NS_SUCCEEDED(rv) && count > 0 && total < 32000);
+ } else if (mConnection) {
+ mConnection->DrainSocketData();
+ }
+ }
+
+ int32_t sessionCount = kLingeringCloseThreshold;
+ nsWSAdmissionManager::GetSessionCount(sessionCount);
+
+ if (!mTCPClosed && (mTransport || mConnection) &&
+ sessionCount < kLingeringCloseThreshold) {
+ // 7.1.1 says that the client SHOULD wait for the server to close the TCP
+ // connection. This is so we can reuse port numbers before 2 MSL expires,
+ // which is not really as much of a concern for us as the amount of state
+ // that might be accrued by keeping this channel object around waiting for
+ // the server. We handle the SHOULD by waiting a short time in the common
+ // case, but not waiting in the case of high concurrency.
+ //
+ // Normally this will be taken care of in AbortSession() after mTCPClosed
+ // is set when the server close arrives without waiting for the timeout to
+ // expire.
+
+ LOG(("WebSocketChannel::DoStopSession: Wait for Server TCP close"));
+
+ nsresult rv;
+ rv = NS_NewTimerWithCallback(getter_AddRefs(mLingeringCloseTimer), this,
+ kLingeringCloseTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) CleanupConnection();
+ } else {
+ CleanupConnection();
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mCancelable) {
+ mCancelable->Cancel(NS_ERROR_UNEXPECTED);
+ mCancelable = nullptr;
+ }
+ }
+
+ {
+ MutexAutoLock lock(mCompressorMutex);
+ mPMCECompressor = nullptr;
+ }
+ if (!mCalledOnStop) {
+ mCalledOnStop = true;
+
+ nsWSAdmissionManager::OnStopSession(this, reason);
+
+ RefPtr<CallOnStop> runnable = new CallOnStop(this, reason);
+ if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) {
+ target->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ }
+ }
+}
+
+// Called from MainThread, and called from IOThread in
+// PrimeNewOutgoingMessage
+void WebSocketChannel::AbortSession(nsresult reason) {
+ LOG(("WebSocketChannel::AbortSession() %p [reason %" PRIx32
+ "] stopped = %d\n",
+ this, static_cast<uint32_t>(reason), !!mStopped));
+
+ MOZ_ASSERT(NS_FAILED(reason), "reason must be a failure!");
+
+ // normally this should be called on socket thread, but it is ok to call it
+ // from the main thread before StartWebsocketData() has completed
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread() || !mDataStarted);
+
+ // When we are failing we need to close the TCP connection immediately
+ // as per 7.1.1
+ mTCPClosed = true;
+
+ if (mLingeringCloseTimer) {
+ MOZ_ASSERT(mStopped, "Lingering without Stop");
+ LOG(("WebSocketChannel:: Cleanup connection based on TCP Close"));
+ CleanupConnection();
+ return;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mStopped) {
+ return;
+ }
+
+ if ((mTransport || mConnection) && reason != NS_BASE_STREAM_CLOSED &&
+ !mRequestedClose && !mClientClosed && !mServerClosed && mDataStarted) {
+ mRequestedClose = true;
+ mStopOnClose = reason;
+ mIOThread->Dispatch(
+ new OutboundEnqueuer(this,
+ new OutboundMessage(kMsgTypeFin, VoidCString())),
+ nsIEventTarget::DISPATCH_NORMAL);
+ return;
+ }
+
+ mStopped = true;
+ }
+
+ DoStopSession(reason);
+}
+
+// ReleaseSession is called on orderly shutdown
+void WebSocketChannel::ReleaseSession() {
+ LOG(("WebSocketChannel::ReleaseSession() %p stopped = %d\n", this,
+ !!mStopped));
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ StopSession(NS_OK);
+}
+
+void WebSocketChannel::IncrementSessionCount() {
+ if (!mIncrementedSessionCount) {
+ nsWSAdmissionManager::IncrementSessionCount();
+ mIncrementedSessionCount = true;
+ }
+}
+
+void WebSocketChannel::DecrementSessionCount() {
+ // Make sure we decrement session count only once, and only if we incremented
+ // it. This code is thread-safe: sWebSocketAdmissions->DecrementSessionCount
+ // is atomic, and mIncrementedSessionCount/mDecrementedSessionCount are set at
+ // times when they'll never be a race condition for checking/setting them.
+ if (mIncrementedSessionCount && !mDecrementedSessionCount) {
+ nsWSAdmissionManager::DecrementSessionCount();
+ mDecrementedSessionCount = true;
+ }
+}
+
+namespace {
+enum ExtensionParseMode { eParseServerSide, eParseClientSide };
+}
+
+static nsresult ParseWebSocketExtension(const nsACString& aExtension,
+ ExtensionParseMode aMode,
+ bool& aClientNoContextTakeover,
+ bool& aServerNoContextTakeover,
+ int32_t& aClientMaxWindowBits,
+ int32_t& aServerMaxWindowBits) {
+ nsCCharSeparatedTokenizer tokens(aExtension, ';');
+
+ if (!tokens.hasMoreTokens() ||
+ !tokens.nextToken().EqualsLiteral("permessage-deflate")) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: "
+ "HTTP Sec-WebSocket-Extensions negotiated unknown value %s\n",
+ PromiseFlatCString(aExtension).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ aClientNoContextTakeover = aServerNoContextTakeover = false;
+ aClientMaxWindowBits = aServerMaxWindowBits = -1;
+
+ while (tokens.hasMoreTokens()) {
+ auto token = tokens.nextToken();
+
+ int32_t nameEnd, valueStart;
+ int32_t delimPos = token.FindChar('=');
+ if (delimPos == kNotFound) {
+ nameEnd = token.Length();
+ valueStart = token.Length();
+ } else {
+ nameEnd = delimPos;
+ valueStart = delimPos + 1;
+ }
+
+ auto paramName = Substring(token, 0, nameEnd);
+ auto paramValue = Substring(token, valueStart);
+
+ if (paramName.EqualsLiteral("client_no_context_takeover")) {
+ if (!paramValue.IsEmpty()) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: parameter "
+ "client_no_context_takeover must not have value, found %s\n",
+ PromiseFlatCString(paramValue).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (aClientNoContextTakeover) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found multiple "
+ "parameters client_no_context_takeover\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ aClientNoContextTakeover = true;
+ } else if (paramName.EqualsLiteral("server_no_context_takeover")) {
+ if (!paramValue.IsEmpty()) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: parameter "
+ "server_no_context_takeover must not have value, found %s\n",
+ PromiseFlatCString(paramValue).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (aServerNoContextTakeover) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found multiple "
+ "parameters server_no_context_takeover\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ aServerNoContextTakeover = true;
+ } else if (paramName.EqualsLiteral("client_max_window_bits")) {
+ if (aClientMaxWindowBits != -1) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found multiple "
+ "parameters client_max_window_bits\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (aMode == eParseServerSide && paramValue.IsEmpty()) {
+ // Use -2 to indicate that "client_max_window_bits" has been parsed,
+ // but had no value.
+ aClientMaxWindowBits = -2;
+ } else {
+ nsresult errcode;
+ aClientMaxWindowBits =
+ PromiseFlatCString(paramValue).ToInteger(&errcode);
+ if (NS_FAILED(errcode) || aClientMaxWindowBits < 8 ||
+ aClientMaxWindowBits > 15) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found invalid "
+ "parameter client_max_window_bits %s\n",
+ PromiseFlatCString(paramValue).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+ } else if (paramName.EqualsLiteral("server_max_window_bits")) {
+ if (aServerMaxWindowBits != -1) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found multiple "
+ "parameters server_max_window_bits\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult errcode;
+ aServerMaxWindowBits = PromiseFlatCString(paramValue).ToInteger(&errcode);
+ if (NS_FAILED(errcode) || aServerMaxWindowBits < 8 ||
+ aServerMaxWindowBits > 15) {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found invalid "
+ "parameter server_max_window_bits %s\n",
+ PromiseFlatCString(paramValue).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ } else {
+ LOG(
+ ("WebSocketChannel::ParseWebSocketExtension: found unknown "
+ "parameter %s\n",
+ PromiseFlatCString(paramName).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+
+ if (aClientMaxWindowBits == -2) {
+ aClientMaxWindowBits = -1;
+ }
+
+ return NS_OK;
+}
+
+nsresult WebSocketChannel::HandleExtensions() {
+ LOG(("WebSocketChannel::HandleExtensions() %p\n", this));
+
+ nsresult rv;
+ nsAutoCString extensions;
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ rv = mHttpChannel->GetResponseHeader("Sec-WebSocket-Extensions"_ns,
+ extensions);
+ extensions.CompressWhitespace();
+ if (extensions.IsEmpty()) {
+ return NS_OK;
+ }
+
+ LOG(
+ ("WebSocketChannel::HandleExtensions: received "
+ "Sec-WebSocket-Extensions header: %s\n",
+ extensions.get()));
+
+ bool clientNoContextTakeover;
+ bool serverNoContextTakeover;
+ int32_t clientMaxWindowBits;
+ int32_t serverMaxWindowBits;
+
+ rv = ParseWebSocketExtension(extensions, eParseClientSide,
+ clientNoContextTakeover, serverNoContextTakeover,
+ clientMaxWindowBits, serverMaxWindowBits);
+ if (NS_FAILED(rv)) {
+ AbortSession(rv);
+ return rv;
+ }
+
+ if (!mAllowPMCE) {
+ LOG(
+ ("WebSocketChannel::HandleExtensions: "
+ "Recvd permessage-deflate which wasn't offered\n"));
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (clientMaxWindowBits == -1) {
+ clientMaxWindowBits = 15;
+ }
+ if (serverMaxWindowBits == -1) {
+ serverMaxWindowBits = 15;
+ }
+
+ MutexAutoLock lock(mCompressorMutex);
+ mPMCECompressor = MakeUnique<PMCECompression>(
+ clientNoContextTakeover, clientMaxWindowBits, serverMaxWindowBits);
+ if (mPMCECompressor->Active()) {
+ LOG(
+ ("WebSocketChannel::HandleExtensions: PMCE negotiated, %susing "
+ "context takeover, clientMaxWindowBits=%d, "
+ "serverMaxWindowBits=%d\n",
+ clientNoContextTakeover ? "NOT " : "", clientMaxWindowBits,
+ serverMaxWindowBits));
+
+ mNegotiatedExtensions = "permessage-deflate";
+ } else {
+ LOG(
+ ("WebSocketChannel::HandleExtensions: Cannot init PMCE "
+ "compression object\n"));
+ mPMCECompressor = nullptr;
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+void ProcessServerWebSocketExtensions(const nsACString& aExtensions,
+ nsACString& aNegotiatedExtensions) {
+ aNegotiatedExtensions.Truncate();
+
+ nsCOMPtr<nsIPrefBranch> prefService =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefService) {
+ bool boolpref;
+ nsresult rv = prefService->GetBoolPref(
+ "network.websocket.extensions.permessage-deflate", &boolpref);
+ if (NS_SUCCEEDED(rv) && !boolpref) {
+ return;
+ }
+ }
+
+ for (const auto& ext :
+ nsCCharSeparatedTokenizer(aExtensions, ',').ToRange()) {
+ bool clientNoContextTakeover;
+ bool serverNoContextTakeover;
+ int32_t clientMaxWindowBits;
+ int32_t serverMaxWindowBits;
+
+ nsresult rv = ParseWebSocketExtension(
+ ext, eParseServerSide, clientNoContextTakeover, serverNoContextTakeover,
+ clientMaxWindowBits, serverMaxWindowBits);
+ if (NS_FAILED(rv)) {
+ // Ignore extensions that we can't parse
+ continue;
+ }
+
+ aNegotiatedExtensions.AssignLiteral("permessage-deflate");
+ if (clientNoContextTakeover) {
+ aNegotiatedExtensions.AppendLiteral(";client_no_context_takeover");
+ }
+ if (serverNoContextTakeover) {
+ aNegotiatedExtensions.AppendLiteral(";server_no_context_takeover");
+ }
+ if (clientMaxWindowBits != -1) {
+ aNegotiatedExtensions.AppendLiteral(";client_max_window_bits=");
+ aNegotiatedExtensions.AppendInt(clientMaxWindowBits);
+ }
+ if (serverMaxWindowBits != -1) {
+ aNegotiatedExtensions.AppendLiteral(";server_max_window_bits=");
+ aNegotiatedExtensions.AppendInt(serverMaxWindowBits);
+ }
+
+ return;
+ }
+}
+
+nsresult CalculateWebSocketHashedSecret(const nsACString& aKey,
+ nsACString& aHash) {
+ nsresult rv;
+ nsCString key = aKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"_ns;
+ nsCOMPtr<nsICryptoHash> hasher =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = hasher->Init(nsICryptoHash::SHA1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = hasher->Update((const uint8_t*)key.BeginWriting(), key.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ return hasher->Finish(true, aHash);
+}
+
+nsresult WebSocketChannel::SetupRequest() {
+ LOG(("WebSocketChannel::SetupRequest() %p\n", this));
+
+ nsresult rv;
+
+ if (mLoadGroup) {
+ rv = mHttpChannel->SetLoadGroup(mLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mHttpChannel->SetLoadFlags(
+ nsIRequest::LOAD_BACKGROUND | nsIRequest::INHIBIT_CACHING |
+ nsIRequest::LOAD_BYPASS_CACHE | nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we never let websockets be blocked by head CSS/JS loads to avoid
+ // potential deadlock where server generation of CSS/JS requires
+ // an XHR signal.
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::Unblocked);
+ }
+
+ // draft-ietf-hybi-thewebsocketprotocol-07 illustrates Upgrade: websocket
+ // in lower case, so go with that. It is technically case insensitive.
+ rv = mChannel->HTTPUpgrade("websocket"_ns, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mHttpChannel->SetRequestHeader("Sec-WebSocket-Version"_ns,
+ nsLiteralCString(SEC_WEBSOCKET_VERSION),
+ false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (!mOrigin.IsEmpty()) {
+ rv = mHttpChannel->SetRequestHeader("Origin"_ns, mOrigin, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ if (!mProtocol.IsEmpty()) {
+ rv = mHttpChannel->SetRequestHeader("Sec-WebSocket-Protocol"_ns, mProtocol,
+ true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ if (mAllowPMCE) {
+ rv = mHttpChannel->SetRequestHeader("Sec-WebSocket-Extensions"_ns,
+ "permessage-deflate"_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ uint8_t* secKey;
+ nsAutoCString secKeyString;
+
+ rv = mRandomGenerator->GenerateRandomBytes(16, &secKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = Base64Encode(reinterpret_cast<const char*>(secKey), 16, secKeyString);
+ free(secKey);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = mHttpChannel->SetRequestHeader("Sec-WebSocket-Key"_ns, secKeyString,
+ false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ LOG(("WebSocketChannel::SetupRequest: client key %s\n", secKeyString.get()));
+
+ // prepare the value we expect to see in
+ // the sec-websocket-accept response header
+ rv = CalculateWebSocketHashedSecret(secKeyString, mHashedSecret);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LOG(("WebSocketChannel::SetupRequest: expected server key %s\n",
+ mHashedSecret.get()));
+
+ mHttpChannelId = mHttpChannel->ChannelId();
+
+ return NS_OK;
+}
+
+nsresult WebSocketChannel::DoAdmissionDNS() {
+ nsresult rv;
+
+ nsCString hostName;
+ rv = mURI->GetHost(hostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mAddress = hostName;
+ nsCString path;
+ rv = mURI->GetFilePath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mPath = path;
+ rv = mURI->GetPort(&mPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mPort == -1) mPort = (mEncrypted ? kDefaultWSSPort : kDefaultWSPort);
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIEventTarget> main = GetMainThreadSerialEventTarget();
+ nsCOMPtr<nsICancelable> cancelable;
+ rv = dns->AsyncResolveNative(hostName, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr,
+ this, main, mLoadInfo->GetOriginAttributes(),
+ getter_AddRefs(cancelable));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mCancelable);
+ mCancelable = std::move(cancelable);
+ return rv;
+}
+
+nsresult WebSocketChannel::ApplyForAdmission() {
+ LOG(("WebSocketChannel::ApplyForAdmission() %p\n", this));
+
+ // Websockets has a policy of 1 session at a time being allowed in the
+ // CONNECTING state per server IP address (not hostname)
+
+ // Check to see if a proxy is being used before making DNS call
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
+
+ if (!pps) {
+ // go straight to DNS
+ // expect the callback in ::OnLookupComplete
+ LOG((
+ "WebSocketChannel::ApplyForAdmission: checking for concurrent open\n"));
+ return DoAdmissionDNS();
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsICancelable> cancelable;
+ rv = pps->AsyncResolve(
+ mHttpChannel,
+ nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY |
+ nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
+ this, nullptr, getter_AddRefs(cancelable));
+
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mCancelable);
+ mCancelable = std::move(cancelable);
+ return rv;
+}
+
+// Called after both OnStartRequest and OnTransportAvailable have
+// executed. This essentially ends the handshake and starts the websockets
+// protocol state machine.
+nsresult WebSocketChannel::CallStartWebsocketData() {
+ LOG(("WebSocketChannel::CallStartWebsocketData() %p", this));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ if (mOpenTimer) {
+ mOpenTimer->Cancel();
+ mOpenTimer = nullptr;
+ }
+
+ nsCOMPtr<nsIEventTarget> target = GetTargetThread();
+ if (target && !target->IsOnCurrentThread()) {
+ return target->Dispatch(
+ NewRunnableMethod("net::WebSocketChannel::StartWebsocketData", this,
+ &WebSocketChannel::StartWebsocketData),
+ NS_DISPATCH_NORMAL);
+ }
+
+ return StartWebsocketData();
+}
+
+nsresult WebSocketChannel::StartWebsocketData() {
+ {
+ MutexAutoLock lock(mMutex);
+ LOG(("WebSocketChannel::StartWebsocketData() %p", this));
+ MOZ_ASSERT(!mDataStarted, "StartWebsocketData twice");
+
+ if (mStopped) {
+ LOG(
+ ("WebSocketChannel::StartWebsocketData channel already closed, not "
+ "starting data"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ RefPtr<WebSocketChannel> self = this;
+ mIOThread->Dispatch(NS_NewRunnableFunction(
+ "WebSocketChannel::StartWebsocketData", [self{std::move(self)}] {
+ LOG(("WebSocketChannel::DoStartWebsocketData() %p", self.get()));
+
+ NS_DispatchToMainThread(
+ NewRunnableMethod("net::WebSocketChannel::NotifyOnStart", self,
+ &WebSocketChannel::NotifyOnStart),
+ NS_DISPATCH_NORMAL);
+
+ nsresult rv = self->mConnection ? self->mConnection->StartReading()
+ : self->mSocketIn->AsyncWait(
+ self, 0, 0, self->mIOThread);
+ if (NS_FAILED(rv)) {
+ self->AbortSession(rv);
+ }
+
+ if (self->mPingInterval) {
+ rv = self->StartPinging();
+ if (NS_FAILED(rv)) {
+ LOG((
+ "WebSocketChannel::StartWebsocketData Could not start pinging, "
+ "rv=0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ self->AbortSession(rv);
+ }
+ }
+ }));
+
+ return NS_OK;
+}
+
+void WebSocketChannel::NotifyOnStart() {
+ LOG(("WebSocketChannel::NotifyOnStart Notifying Listener %p",
+ mListenerMT ? mListenerMT->mListener.get() : nullptr));
+ mDataStarted = true;
+ if (mListenerMT) {
+ nsresult rv = mListenerMT->mListener->OnStart(mListenerMT->mContext);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::NotifyOnStart "
+ "mListenerMT->mListener->OnStart() failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+nsresult WebSocketChannel::StartPinging() {
+ LOG(("WebSocketChannel::StartPinging() %p", this));
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+ MOZ_ASSERT(mPingInterval);
+ MOZ_ASSERT(!mPingTimer);
+
+ nsresult rv;
+ rv = NS_NewTimerWithCallback(getter_AddRefs(mPingTimer), this, mPingInterval,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n",
+ (uint32_t)mPingInterval));
+ } else {
+ NS_WARNING("unable to create ping timer. Carrying on.");
+ }
+
+ return NS_OK;
+}
+
+void WebSocketChannel::ReportConnectionTelemetry(nsresult aStatusCode) {
+ // 3 bits are used. high bit is for wss, middle bit for failed,
+ // and low bit for proxy..
+ // 0 - 7 : ws-ok-plain, ws-ok-proxy, ws-failed-plain, ws-failed-proxy,
+ // wss-ok-plain, wss-ok-proxy, wss-failed-plain, wss-failed-proxy
+
+ bool didProxy = false;
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ nsCOMPtr<nsIProxiedChannel> pc = do_QueryInterface(mChannel);
+ if (pc) pc->GetProxyInfo(getter_AddRefs(pi));
+ if (pi) {
+ nsAutoCString proxyType;
+ pi->GetType(proxyType);
+ if (!proxyType.IsEmpty() && !proxyType.EqualsLiteral("direct")) {
+ didProxy = true;
+ }
+ }
+
+ uint8_t value =
+ (mEncrypted ? (1 << 2) : 0) |
+ (!(mGotUpgradeOK && NS_SUCCEEDED(aStatusCode)) ? (1 << 1) : 0) |
+ (didProxy ? (1 << 0) : 0);
+
+ LOG(("WebSocketChannel::ReportConnectionTelemetry() %p %d", this, value));
+ Telemetry::Accumulate(Telemetry::WEBSOCKETS_HANDSHAKE_TYPE, value);
+}
+
+// nsIDNSListener
+
+NS_IMETHODIMP
+WebSocketChannel::OnLookupComplete(nsICancelable* aRequest,
+ nsIDNSRecord* aRecord, nsresult aStatus) {
+ LOG(("WebSocketChannel::OnLookupComplete() %p [%p %p %" PRIx32 "]\n", this,
+ aRequest, aRecord, static_cast<uint32_t>(aStatus)));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ {
+ MutexAutoLock lock(mMutex);
+ mCancelable = nullptr;
+ }
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnLookupComplete: Request Already Stopped\n"));
+ return NS_OK;
+ }
+
+ // These failures are not fatal - we just use the hostname as the key
+ if (NS_FAILED(aStatus)) {
+ LOG(("WebSocketChannel::OnLookupComplete: No DNS Response\n"));
+
+ // set host in case we got here without calling DoAdmissionDNS()
+ mURI->GetHost(mAddress);
+ } else {
+ nsCOMPtr<nsIDNSAddrRecord> record = do_QueryInterface(aRecord);
+ MOZ_ASSERT(record);
+ nsresult rv = record->GetNextAddrAsString(mAddress);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::OnLookupComplete: Failed GetNextAddr\n"));
+ }
+ }
+
+ LOG(("WebSocket OnLookupComplete: Proceeding to ConditionallyConnect\n"));
+ nsWSAdmissionManager::ConditionallyConnect(this);
+
+ return NS_OK;
+}
+
+// nsIProtocolProxyCallback
+NS_IMETHODIMP
+WebSocketChannel::OnProxyAvailable(nsICancelable* aRequest,
+ nsIChannel* aChannel, nsIProxyInfo* pi,
+ nsresult status) {
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mCancelable || (aRequest == mCancelable));
+ mCancelable = nullptr;
+ }
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnProxyAvailable: [%p] Request Already Stopped\n",
+ this));
+ return NS_OK;
+ }
+
+ nsAutoCString type;
+ if (NS_SUCCEEDED(status) && pi && NS_SUCCEEDED(pi->GetType(type)) &&
+ !type.EqualsLiteral("direct")) {
+ LOG(("WebSocket OnProxyAvailable [%p] Proxy found skip DNS lookup\n",
+ this));
+ // call DNS callback directly without DNS resolver
+ OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE);
+ } else {
+ LOG(("WebSocketChannel::OnProxyAvailable[%p] checking DNS resolution\n",
+ this));
+ nsresult rv = DoAdmissionDNS();
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocket OnProxyAvailable [%p] DNS lookup failed\n", this));
+ // call DNS callback directly without DNS resolver
+ OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE);
+ }
+ }
+
+ // notify listener of OnProxyAvailable
+ LOG(("WebSocketChannel::OnProxyAvailable Notifying Listener %p",
+ mListenerMT ? mListenerMT->mListener.get() : nullptr));
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyCallback> ppc(
+ do_QueryInterface(mListenerMT->mListener, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = ppc->OnProxyAvailable(aRequest, aChannel, pi, status);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::OnProxyAvailable notify"
+ " failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+
+NS_IMETHODIMP
+WebSocketChannel::GetInterface(const nsIID& iid, void** result) {
+ LOG(("WebSocketChannel::GetInterface() %p\n", this));
+
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ return QueryInterface(iid, result);
+ }
+
+ if (mCallbacks) return mCallbacks->GetInterface(iid, result);
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+// nsIChannelEventSink
+
+NS_IMETHODIMP
+WebSocketChannel::AsyncOnChannelRedirect(
+ nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ LOG(("WebSocketChannel::AsyncOnChannelRedirect() %p\n", this));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> newuri;
+ rv = newChannel->GetURI(getter_AddRefs(newuri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // newuri is expected to be http or https
+ bool newuriIsHttps = newuri->SchemeIs("https");
+
+ // allow insecure->secure redirects for HTTP Strict Transport Security (from
+ // ws://FOO to https://FOO (mapped to wss://FOO)
+ if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE))) {
+ nsAutoCString newSpec;
+ rv = newuri->GetSpec(newSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("WebSocketChannel: Redirect to %s denied by configuration\n",
+ newSpec.get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mEncrypted && !newuriIsHttps) {
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(newuri->GetSpec(spec))) {
+ LOG(("WebSocketChannel: Redirect to %s violates encryption rule\n",
+ spec.get()));
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel, &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Redirect could not QI to HTTP\n"));
+ return rv;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> newUpgradeChannel =
+ do_QueryInterface(newChannel, &rv);
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Redirect could not QI to HTTP Upgrade\n"));
+ return rv;
+ }
+
+ // The redirect is likely OK
+
+ newChannel->SetNotificationCallbacks(this);
+
+ mEncrypted = newuriIsHttps;
+ rv = NS_MutateURI(newuri)
+ .SetScheme(mEncrypted ? "wss"_ns : "ws"_ns)
+ .Finalize(mURI);
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Could not set the proper scheme\n"));
+ return rv;
+ }
+
+ mHttpChannel = newHttpChannel;
+ mChannel = newUpgradeChannel;
+ rv = SetupRequest();
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Redirect could not SetupRequest()\n"));
+ return rv;
+ }
+
+ // Redirected-to URI may need to be delayed by 1-connecting-per-host and
+ // delay-after-fail algorithms. So hold off calling OnRedirectVerifyCallback
+ // until BeginOpen, when we know it's OK to proceed with new channel.
+ mRedirectCallback = callback;
+
+ // Mark old channel as successfully connected so we'll clear any FailDelay
+ // associated with the old URI. Note: no need to also call OnStopSession:
+ // it's a no-op for successful, already-connected channels.
+ nsWSAdmissionManager::OnConnected(this);
+
+ // ApplyForAdmission as if we were starting from fresh...
+ mAddress.Truncate();
+ mOpenedHttpChannel = false;
+ rv = ApplyForAdmission();
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Redirect failed due to DNS failure\n"));
+ mRedirectCallback = nullptr;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// nsITimerCallback
+
+NS_IMETHODIMP
+WebSocketChannel::Notify(nsITimer* timer) {
+ LOG(("WebSocketChannel::Notify() %p [%p]\n", this, timer));
+
+ if (timer == mCloseTimer) {
+ MOZ_ASSERT(mClientClosed, "Close Timeout without local close");
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ mCloseTimer = nullptr;
+ if (mStopped || mServerClosed) { /* no longer relevant */
+ return NS_OK;
+ }
+
+ LOG(("WebSocketChannel:: Expecting Server Close - Timed Out\n"));
+ AbortSession(NS_ERROR_NET_TIMEOUT_EXTERNAL);
+ } else if (timer == mOpenTimer) {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ mOpenTimer = nullptr;
+ LOG(("WebSocketChannel:: Connection Timed Out\n"));
+ if (mStopped || mServerClosed) { /* no longer relevant */
+ return NS_OK;
+ }
+
+ AbortSession(NS_ERROR_NET_TIMEOUT_EXTERNAL);
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ // mReconnectDelayTimer is only modified on MainThread, we can read it
+ // without a lock, but ONLY if we're on MainThread! And if we're not
+ // on MainThread, it can't be mReconnectDelayTimer
+ } else if (NS_IsMainThread() && timer == mReconnectDelayTimer) {
+ MOZ_POP_THREAD_SAFETY
+ MOZ_ASSERT(mConnecting == CONNECTING_DELAYED,
+ "woke up from delay w/o being delayed?");
+
+ {
+ MutexAutoLock lock(mMutex);
+ mReconnectDelayTimer = nullptr;
+ }
+ LOG(("WebSocketChannel: connecting [this=%p] after reconnect delay", this));
+ BeginOpen(false);
+ } else if (timer == mPingTimer) {
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ if (mClientClosed || mServerClosed || mRequestedClose) {
+ // no point in worrying about ping now
+ mPingTimer = nullptr;
+ return NS_OK;
+ }
+
+ if (!mPingOutstanding) {
+ // Ping interval must be non-null or PING was forced by OnNetworkChanged()
+ MOZ_ASSERT(mPingInterval || mPingForced);
+ LOG(("nsWebSocketChannel:: Generating Ping\n"));
+ mPingOutstanding = 1;
+ mPingForced = false;
+ mPingTimer->InitWithCallback(this, mPingResponseTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ GeneratePing();
+ } else {
+ LOG(("nsWebSocketChannel:: Timed out Ping\n"));
+ mPingTimer = nullptr;
+ AbortSession(NS_ERROR_NET_TIMEOUT_EXTERNAL);
+ }
+ } else if (timer == mLingeringCloseTimer) {
+ LOG(("WebSocketChannel:: Lingering Close Timer"));
+ CleanupConnection();
+ } else {
+ MOZ_ASSERT(0, "Unknown Timer");
+ }
+
+ return NS_OK;
+}
+
+// nsINamed
+
+NS_IMETHODIMP
+WebSocketChannel::GetName(nsACString& aName) {
+ aName.AssignLiteral("WebSocketChannel");
+ return NS_OK;
+}
+
+// nsIWebSocketChannel
+
+NS_IMETHODIMP
+WebSocketChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
+ LOG(("WebSocketChannel::GetSecurityInfo() %p\n", this));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ *aSecurityInfo = nullptr;
+
+ if (mConnection) {
+ nsresult rv = mConnection->GetSecurityInfo(aSecurityInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ if (mTransport) {
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ nsresult rv =
+ mTransport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(
+ do_QueryInterface(tlsSocketControl));
+ if (securityInfo) {
+ securityInfo.forget(aSecurityInfo);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
+ JS::Handle<JS::Value> aOriginAttributes,
+ uint64_t aInnerWindowID,
+ nsIWebSocketListener* aListener,
+ nsISupports* aContext, JSContext* aCx) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return AsyncOpenNative(aURI, aOrigin, attrs, aInnerWindowID, aListener,
+ aContext);
+}
+
+NS_IMETHODIMP
+WebSocketChannel::AsyncOpenNative(nsIURI* aURI, const nsACString& aOrigin,
+ const OriginAttributes& aOriginAttributes,
+ uint64_t aInnerWindowID,
+ nsIWebSocketListener* aListener,
+ nsISupports* aContext) {
+ LOG(("WebSocketChannel::AsyncOpen() %p\n", this));
+
+ aOriginAttributes.CreateSuffix(mOriginSuffix);
+
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(false, "not main thread");
+ LOG(("WebSocketChannel::AsyncOpen() called off the main thread"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if ((!aURI && !mIsServerSide) || !aListener) {
+ LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mListenerMT || mWasOpened) return NS_ERROR_ALREADY_OPENED;
+
+ nsresult rv;
+
+ // Ensure target thread is set if RetargetDeliveryTo isn't called
+ {
+ auto lock = mTargetThread.Lock();
+ if (!lock.ref()) {
+ lock.ref() = GetMainThreadSerialEventTarget();
+ }
+ }
+
+ mIOThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to continue without socket transport service");
+ return rv;
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefService;
+ prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ if (prefService) {
+ int32_t intpref;
+ bool boolpref;
+ rv =
+ prefService->GetIntPref("network.websocket.max-message-size", &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxMessageSize = clamped(intpref, 1024, INT32_MAX);
+ }
+ rv = prefService->GetIntPref("network.websocket.timeout.close", &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ mCloseTimeout = clamped(intpref, 1, 1800) * 1000;
+ }
+ rv = prefService->GetIntPref("network.websocket.timeout.open", &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ mOpenTimeout = clamped(intpref, 1, 1800) * 1000;
+ }
+ rv = prefService->GetIntPref("network.websocket.timeout.ping.request",
+ &intpref);
+ if (NS_SUCCEEDED(rv) && !mClientSetPingInterval) {
+ mPingInterval = clamped(intpref, 0, 86400) * 1000;
+ }
+ rv = prefService->GetIntPref("network.websocket.timeout.ping.response",
+ &intpref);
+ if (NS_SUCCEEDED(rv) && !mClientSetPingTimeout) {
+ mPingResponseTimeout = clamped(intpref, 1, 3600) * 1000;
+ }
+ rv = prefService->GetBoolPref(
+ "network.websocket.extensions.permessage-deflate", &boolpref);
+ if (NS_SUCCEEDED(rv)) {
+ mAllowPMCE = boolpref ? 1 : 0;
+ }
+ rv = prefService->GetIntPref("network.websocket.max-connections", &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxConcurrentConnections = clamped(intpref, 1, 0xffff);
+ }
+ }
+
+ int32_t sessionCount = -1;
+ nsWSAdmissionManager::GetSessionCount(sessionCount);
+ if (sessionCount >= 0) {
+ LOG(("WebSocketChannel::AsyncOpen %p sessionCount=%d max=%d\n", this,
+ sessionCount, mMaxConcurrentConnections));
+ }
+
+ if (sessionCount >= mMaxConcurrentConnections) {
+ LOG(("WebSocketChannel: max concurrency %d exceeded (%d)",
+ mMaxConcurrentConnections, sessionCount));
+
+ // WebSocket connections are expected to be long lived, so return
+ // an error here instead of queueing
+ return NS_ERROR_SOCKET_CREATE_FAILED;
+ }
+
+ mInnerWindowID = aInnerWindowID;
+ mOriginalURI = aURI;
+ mURI = mOriginalURI;
+ mOrigin = aOrigin;
+
+ if (mIsServerSide) {
+ // IncrementSessionCount();
+ mWasOpened = 1;
+ mListenerMT = new ListenerAndContextContainer(aListener, aContext);
+ rv = mServerTransportProvider->SetListener(this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mServerTransportProvider = nullptr;
+
+ return NS_OK;
+ }
+
+ mURI->GetHostPort(mHost);
+
+ mRandomGenerator =
+ do_GetService("@mozilla.org/security/random-generator;1", &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to continue without random number generator");
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> localURI;
+ nsCOMPtr<nsIChannel> localChannel;
+
+ rv = NS_MutateURI(mURI)
+ .SetScheme(mEncrypted ? "https"_ns : "http"_ns)
+ .Finalize(localURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIIOService> ioService;
+ ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to continue without io service");
+ return rv;
+ }
+
+ // Ideally we'd call newChannelFromURIWithLoadInfo here, but that doesn't
+ // allow setting proxy uri/flags
+ rv = ioService->NewChannelFromURIWithProxyFlags(
+ localURI, mURI,
+ nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY |
+ nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
+ mLoadInfo->LoadingNode(), mLoadInfo->GetLoadingPrincipal(),
+ mLoadInfo->TriggeringPrincipal(), mLoadInfo->GetSecurityFlags(),
+ mLoadInfo->InternalContentPolicyType(), getter_AddRefs(localChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Please note that we still call SetLoadInfo on the channel because
+ // we want the same instance of the loadInfo to be set on the channel.
+ rv = localChannel->SetLoadInfo(mLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Pass most GetInterface() requests through to our instantiator, but handle
+ // nsIChannelEventSink in this object in order to deal with redirects
+ localChannel->SetNotificationCallbacks(this);
+
+ class MOZ_STACK_CLASS CleanUpOnFailure {
+ public:
+ explicit CleanUpOnFailure(WebSocketChannel* aWebSocketChannel)
+ : mWebSocketChannel(aWebSocketChannel) {}
+
+ ~CleanUpOnFailure() {
+ if (!mWebSocketChannel->mWasOpened) {
+ mWebSocketChannel->mChannel = nullptr;
+ mWebSocketChannel->mHttpChannel = nullptr;
+ }
+ }
+
+ WebSocketChannel* mWebSocketChannel;
+ };
+
+ CleanUpOnFailure cuof(this);
+
+ mChannel = do_QueryInterface(localChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHttpChannel = do_QueryInterface(localChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupRequest();
+ if (NS_FAILED(rv)) return rv;
+
+ mPrivateBrowsing = NS_UsePrivateBrowsing(localChannel);
+
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->AddHost(mHost, mSerial,
+ BaseWebSocketChannel::mEncrypted);
+ }
+
+ rv = ApplyForAdmission();
+ if (NS_FAILED(rv)) return rv;
+
+ // Register for prefs change notifications
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ NS_WARNING("failed to get observer service");
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Only set these if the open was successful:
+ //
+ mWasOpened = 1;
+ mListenerMT = new ListenerAndContextContainer(aListener, aContext);
+ IncrementSessionCount();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::Close(uint16_t code, const nsACString& reason) {
+ LOG(("WebSocketChannel::Close() %p\n", this));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mRequestedClose) {
+ return NS_OK;
+ }
+
+ if (mStopped) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // The API requires the UTF-8 string to be 123 or less bytes
+ if (reason.Length() > 123) return NS_ERROR_ILLEGAL_VALUE;
+
+ mRequestedClose = true;
+ mScriptCloseReason = reason;
+ mScriptCloseCode = code;
+
+ if (mDataStarted) {
+ return mIOThread->Dispatch(
+ new OutboundEnqueuer(this,
+ new OutboundMessage(kMsgTypeFin, VoidCString())),
+ nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ mStopped = true;
+ }
+
+ nsresult rv;
+ if (code == CLOSE_GOING_AWAY) {
+ // Not an error: for example, tab has closed or navigated away
+ LOG(("WebSocketChannel::Close() GOING_AWAY without transport."));
+ rv = NS_OK;
+ } else {
+ LOG(("WebSocketChannel::Close() without transport - error."));
+ rv = NS_ERROR_NOT_CONNECTED;
+ }
+
+ DoStopSession(rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::SendMsg(const nsACString& aMsg) {
+ LOG(("WebSocketChannel::SendMsg() %p\n", this));
+
+ return SendMsgCommon(aMsg, false, aMsg.Length());
+}
+
+NS_IMETHODIMP
+WebSocketChannel::SendBinaryMsg(const nsACString& aMsg) {
+ LOG(("WebSocketChannel::SendBinaryMsg() %p len=%zu\n", this, aMsg.Length()));
+ return SendMsgCommon(aMsg, true, aMsg.Length());
+}
+
+NS_IMETHODIMP
+WebSocketChannel::SendBinaryStream(nsIInputStream* aStream, uint32_t aLength) {
+ LOG(("WebSocketChannel::SendBinaryStream() %p\n", this));
+
+ return SendMsgCommon(VoidCString(), true, aLength, aStream);
+}
+
+nsresult WebSocketChannel::SendMsgCommon(const nsACString& aMsg, bool aIsBinary,
+ uint32_t aLength,
+ nsIInputStream* aStream) {
+ MOZ_ASSERT(IsOnTargetThread(), "not target thread");
+
+ if (!mDataStarted) {
+ LOG(("WebSocketChannel:: Error: data not started yet\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mRequestedClose) {
+ LOG(("WebSocketChannel:: Error: send when closed\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mStopped) {
+ LOG(("WebSocketChannel:: Error: send when stopped\n"));
+ return NS_ERROR_NOT_CONNECTED;
+ }
+
+ MOZ_ASSERT(mMaxMessageSize >= 0, "max message size negative");
+ if (aLength > static_cast<uint32_t>(mMaxMessageSize)) {
+ LOG(("WebSocketChannel:: Error: message too big\n"));
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->NewMsgSent(mHost, mSerial, aLength);
+ LOG(("Added new msg sent for %s", mHost.get()));
+ }
+
+ return mIOThread->Dispatch(
+ aStream
+ ? new OutboundEnqueuer(this, new OutboundMessage(aStream, aLength))
+ : new OutboundEnqueuer(
+ this,
+ new OutboundMessage(
+ aIsBinary ? kMsgTypeBinaryString : kMsgTypeString, aMsg)),
+ nsIEventTarget::DISPATCH_NORMAL);
+}
+
+// nsIHttpUpgradeListener
+
+NS_IMETHODIMP
+WebSocketChannel::OnTransportAvailable(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aSocketIn,
+ nsIAsyncOutputStream* aSocketOut) {
+ if (!NS_IsMainThread()) {
+ return NS_DispatchToMainThread(
+ new CallOnTransportAvailable(this, aTransport, aSocketIn, aSocketOut));
+ }
+
+ LOG(("WebSocketChannel::OnTransportAvailable %p [%p %p %p] rcvdonstart=%d\n",
+ this, aTransport, aSocketIn, aSocketOut, mGotUpgradeOK));
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnTransportAvailable: Already stopped"));
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(!mRecvdHttpUpgradeTransport, "OTA duplicated");
+ MOZ_ASSERT(aSocketIn, "OTA with invalid socketIn");
+
+ mTransport = aTransport;
+ mSocketIn = aSocketIn;
+ mSocketOut = aSocketOut;
+
+ nsresult rv;
+ rv = mTransport->SetEventSink(nullptr, nullptr);
+ if (NS_FAILED(rv)) return rv;
+ rv = mTransport->SetSecurityCallbacks(this);
+ if (NS_FAILED(rv)) return rv;
+
+ return OnTransportAvailableInternal();
+}
+
+NS_IMETHODIMP
+WebSocketChannel::OnWebSocketConnectionAvailable(
+ WebSocketConnectionBase* aConnection) {
+ if (!NS_IsMainThread()) {
+ RefPtr<WebSocketChannel> self = this;
+ RefPtr<WebSocketConnectionBase> connection = aConnection;
+ return NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "WebSocketChannel::OnWebSocketConnectionAvailable",
+ [self, connection]() {
+ self->OnWebSocketConnectionAvailable(connection);
+ }));
+ }
+
+ LOG(
+ ("WebSocketChannel::OnWebSocketConnectionAvailable %p [%p] "
+ "rcvdonstart=%d\n",
+ this, aConnection, mGotUpgradeOK));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(!mRecvdHttpUpgradeTransport,
+ "OnWebSocketConnectionAvailable duplicated");
+ MOZ_ASSERT(aConnection);
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnWebSocketConnectionAvailable: Already stopped"));
+ aConnection->Close();
+ return NS_OK;
+ }
+
+ nsresult rv = aConnection->Init(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mConnection = aConnection;
+ // Note: mIOThread will be IPDL background thread.
+ mConnection->GetIoTarget(getter_AddRefs(mIOThread));
+ return OnTransportAvailableInternal();
+}
+
+nsresult WebSocketChannel::OnTransportAvailableInternal() {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(!mRecvdHttpUpgradeTransport,
+ "OnWebSocketConnectionAvailable duplicated");
+ MOZ_ASSERT(mSocketIn || mConnection);
+
+ mRecvdHttpUpgradeTransport = 1;
+ if (mGotUpgradeOK) {
+ // We're now done CONNECTING, which means we can now open another,
+ // perhaps parallel, connection to the same host if one
+ // is pending
+ nsWSAdmissionManager::OnConnected(this);
+
+ return CallStartWebsocketData();
+ }
+
+ if (mIsServerSide) {
+ if (!mNegotiatedExtensions.IsEmpty()) {
+ bool clientNoContextTakeover;
+ bool serverNoContextTakeover;
+ int32_t clientMaxWindowBits;
+ int32_t serverMaxWindowBits;
+
+ nsresult rv = ParseWebSocketExtension(
+ mNegotiatedExtensions, eParseServerSide, clientNoContextTakeover,
+ serverNoContextTakeover, clientMaxWindowBits, serverMaxWindowBits);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "illegal value provided by server");
+
+ if (clientMaxWindowBits == -1) {
+ clientMaxWindowBits = 15;
+ }
+ if (serverMaxWindowBits == -1) {
+ serverMaxWindowBits = 15;
+ }
+
+ MutexAutoLock lock(mCompressorMutex);
+ mPMCECompressor = MakeUnique<PMCECompression>(
+ serverNoContextTakeover, serverMaxWindowBits, clientMaxWindowBits);
+ if (mPMCECompressor->Active()) {
+ LOG(
+ ("WebSocketChannel::OnTransportAvailable: PMCE negotiated, %susing "
+ "context takeover, serverMaxWindowBits=%d, "
+ "clientMaxWindowBits=%d\n",
+ serverNoContextTakeover ? "NOT " : "", serverMaxWindowBits,
+ clientMaxWindowBits));
+
+ mNegotiatedExtensions = "permessage-deflate";
+ } else {
+ LOG(
+ ("WebSocketChannel::OnTransportAvailable: Cannot init PMCE "
+ "compression object\n"));
+ mPMCECompressor = nullptr;
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return CallStartWebsocketData();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::OnUpgradeFailed(nsresult aErrorCode) {
+ // When socket process is enabled, this could be called on background thread.
+
+ LOG(("WebSocketChannel::OnUpgradeFailed() %p [aErrorCode %" PRIx32 "]", this,
+ static_cast<uint32_t>(aErrorCode)));
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnUpgradeFailed: Already stopped"));
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mRecvdHttpUpgradeTransport, "OTA already called");
+
+ AbortSession(aErrorCode);
+ return NS_OK;
+}
+
+// nsIRequestObserver (from nsIStreamListener)
+
+NS_IMETHODIMP
+WebSocketChannel::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("WebSocketChannel::OnStartRequest(): %p [%p %p] recvdhttpupgrade=%d\n",
+ this, aRequest, mHttpChannel.get(), mRecvdHttpUpgradeTransport));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(!mGotUpgradeOK, "OTA duplicated");
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnStartRequest: Channel Already Done\n"));
+ AbortSession(NS_ERROR_WEBSOCKET_CONNECTION_REFUSED);
+ return NS_ERROR_WEBSOCKET_CONNECTION_REFUSED;
+ }
+
+ nsresult rv;
+ uint32_t status;
+ char *val, *token;
+
+ rv = mHttpChannel->GetResponseStatus(&status);
+ if (NS_FAILED(rv)) {
+ nsresult httpStatus;
+ rv = NS_ERROR_WEBSOCKET_CONNECTION_REFUSED;
+
+ // If we failed to connect due to unsuccessful TLS handshake, we must
+ // propagate a specific error to mozilla::dom::WebSocketImpl so it can set
+ // status code to 1015. Otherwise return
+ // NS_ERROR_WEBSOCKET_CONNECTION_REFUSED.
+ if (NS_SUCCEEDED(mHttpChannel->GetStatus(&httpStatus))) {
+ uint32_t errorClass;
+ nsCOMPtr<nsINSSErrorsService> errSvc =
+ do_GetService("@mozilla.org/nss_errors_service;1");
+ // If GetErrorClass succeeds httpStatus is TLS related failure.
+ if (errSvc &&
+ NS_SUCCEEDED(errSvc->GetErrorClass(httpStatus, &errorClass))) {
+ rv = NS_ERROR_NET_INADEQUATE_SECURITY;
+ }
+ }
+
+ LOG(("WebSocketChannel::OnStartRequest: No HTTP Response\n"));
+ AbortSession(rv);
+ return rv;
+ }
+
+ LOG(("WebSocketChannel::OnStartRequest: HTTP status %d\n", status));
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel =
+ do_QueryInterface(mHttpChannel);
+ uint32_t versionMajor, versionMinor;
+ rv = internalChannel->GetResponseVersion(&versionMajor, &versionMinor);
+ if (NS_FAILED(rv) ||
+ !((versionMajor == 1 && versionMinor != 0) || versionMajor == 2) ||
+ (versionMajor == 1 && status != 101) ||
+ (versionMajor == 2 && status != 200)) {
+ AbortSession(NS_ERROR_WEBSOCKET_CONNECTION_REFUSED);
+ return NS_ERROR_WEBSOCKET_CONNECTION_REFUSED;
+ }
+
+ if (versionMajor == 1) {
+ // These are only present on http/1.x websocket upgrades
+ nsAutoCString respUpgrade;
+ rv = mHttpChannel->GetResponseHeader("Upgrade"_ns, respUpgrade);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_ERROR_ILLEGAL_VALUE;
+ if (!respUpgrade.IsEmpty()) {
+ val = respUpgrade.BeginWriting();
+ while ((token = nsCRT::strtok(val, ", \t", &val))) {
+ if (nsCRT::strcasecmp(token, "Websocket") == 0) {
+ rv = NS_OK;
+ break;
+ }
+ }
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::OnStartRequest: "
+ "HTTP response header Upgrade: websocket not found\n"));
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return rv;
+ }
+
+ nsAutoCString respConnection;
+ rv = mHttpChannel->GetResponseHeader("Connection"_ns, respConnection);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_ERROR_ILLEGAL_VALUE;
+ if (!respConnection.IsEmpty()) {
+ val = respConnection.BeginWriting();
+ while ((token = nsCRT::strtok(val, ", \t", &val))) {
+ if (nsCRT::strcasecmp(token, "Upgrade") == 0) {
+ rv = NS_OK;
+ break;
+ }
+ }
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::OnStartRequest: "
+ "HTTP response header 'Connection: Upgrade' not found\n"));
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return rv;
+ }
+
+ nsAutoCString respAccept;
+ rv = mHttpChannel->GetResponseHeader("Sec-WebSocket-Accept"_ns, respAccept);
+
+ if (NS_FAILED(rv) || respAccept.IsEmpty() ||
+ !respAccept.Equals(mHashedSecret)) {
+ LOG(
+ ("WebSocketChannel::OnStartRequest: "
+ "HTTP response header Sec-WebSocket-Accept check failed\n"));
+ LOG(("WebSocketChannel::OnStartRequest: Expected %s received %s\n",
+ mHashedSecret.get(), respAccept.get()));
+#ifdef FUZZING
+ if (NS_FAILED(rv) || respAccept.IsEmpty()) {
+#endif
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return NS_ERROR_ILLEGAL_VALUE;
+#ifdef FUZZING
+ }
+#endif
+ }
+ }
+
+ // If we sent a sub protocol header, verify the response matches.
+ // If response contains protocol that was not in request, fail.
+ // If response contained no protocol header, set to "" so the protocol
+ // attribute of the WebSocket JS object reflects that
+ if (!mProtocol.IsEmpty()) {
+ nsAutoCString respProtocol;
+ rv = mHttpChannel->GetResponseHeader("Sec-WebSocket-Protocol"_ns,
+ respProtocol);
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_ERROR_ILLEGAL_VALUE;
+ val = mProtocol.BeginWriting();
+ while ((token = nsCRT::strtok(val, ", \t", &val))) {
+ if (strcmp(token, respProtocol.get()) == 0) {
+ rv = NS_OK;
+ break;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("WebsocketChannel::OnStartRequest: subprotocol %s confirmed",
+ respProtocol.get()));
+ mProtocol = respProtocol;
+ } else {
+ LOG(
+ ("WebsocketChannel::OnStartRequest: "
+ "Server replied with non-matching subprotocol [%s]: aborting",
+ respProtocol.get()));
+ mProtocol.Truncate();
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ } else {
+ LOG(
+ ("WebsocketChannel::OnStartRequest "
+ "subprotocol [%s] not found - none returned",
+ mProtocol.get()));
+ mProtocol.Truncate();
+ }
+ }
+
+ rv = HandleExtensions();
+ if (NS_FAILED(rv)) return rv;
+
+ // Update mEffectiveURL for off main thread URI access.
+ nsCOMPtr<nsIURI> uri = mURI ? mURI : mOriginalURI;
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ CopyUTF8toUTF16(spec, mEffectiveURL);
+
+ mGotUpgradeOK = 1;
+ if (mRecvdHttpUpgradeTransport) {
+ // We're now done CONNECTING, which means we can now open another,
+ // perhaps parallel, connection to the same host if one
+ // is pending
+ nsWSAdmissionManager::OnConnected(this);
+
+ return CallStartWebsocketData();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ LOG(("WebSocketChannel::OnStopRequest() %p [%p %p %" PRIx32 "]\n", this,
+ aRequest, mHttpChannel.get(), static_cast<uint32_t>(aStatusCode)));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ // OnTransportAvailable won't be called if the request is stopped with
+ // an error. Abort the session now instead of waiting for timeout.
+ if (NS_FAILED(aStatusCode) && !mRecvdHttpUpgradeTransport) {
+ AbortSession(aStatusCode);
+ }
+
+ ReportConnectionTelemetry(aStatusCode);
+
+ // This is the end of the HTTP upgrade transaction, the
+ // upgraded streams live on
+
+ mChannel = nullptr;
+ mHttpChannel = nullptr;
+ mLoadGroup = nullptr;
+ mCallbacks = nullptr;
+
+ return NS_OK;
+}
+
+// nsIInputStreamCallback
+
+NS_IMETHODIMP
+WebSocketChannel::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ LOG(("WebSocketChannel::OnInputStreamReady() %p\n", this));
+ MOZ_DIAGNOSTIC_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ if (!mSocketIn) { // did we we clean up the socket after scheduling
+ // InputReady?
+ return NS_OK;
+ }
+
+ // this is after the http upgrade - so we are speaking websockets
+ char buffer[2048];
+ uint32_t count;
+ nsresult rv;
+
+ do {
+ rv = mSocketIn->Read((char*)buffer, sizeof(buffer), &count);
+ LOG(("WebSocketChannel::OnInputStreamReady: read %u rv %" PRIx32 "\n",
+ count, static_cast<uint32_t>(rv)));
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSocketIn->AsyncWait(this, 0, 0, mIOThread);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ AbortSession(rv);
+ return rv;
+ }
+
+ if (count == 0) {
+ AbortSession(NS_BASE_STREAM_CLOSED);
+ return NS_OK;
+ }
+
+ if (mStopped) {
+ continue;
+ }
+
+ rv = ProcessInput((uint8_t*)buffer, count);
+ if (NS_FAILED(rv)) {
+ AbortSession(rv);
+ return rv;
+ }
+ } while (NS_SUCCEEDED(rv) && mSocketIn);
+
+ return NS_OK;
+}
+
+// nsIOutputStreamCallback
+
+NS_IMETHODIMP
+WebSocketChannel::OnOutputStreamReady(nsIAsyncOutputStream* aStream) {
+ LOG(("WebSocketChannel::OnOutputStreamReady() %p\n", this));
+ MOZ_DIAGNOSTIC_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+ nsresult rv;
+
+ if (!mCurrentOut) PrimeNewOutgoingMessage();
+
+ while (mCurrentOut && mSocketOut) {
+ const char* sndBuf;
+ uint32_t toSend;
+ uint32_t amtSent;
+
+ if (mHdrOut) {
+ sndBuf = (const char*)mHdrOut;
+ toSend = mHdrOutToSend;
+ LOG(
+ ("WebSocketChannel::OnOutputStreamReady: "
+ "Try to send %u of hdr/copybreak\n",
+ toSend));
+ } else {
+ sndBuf = (char*)mCurrentOut->BeginReading() + mCurrentOutSent;
+ toSend = mCurrentOut->Length() - mCurrentOutSent;
+ if (toSend > 0) {
+ LOG(
+ ("WebSocketChannel::OnOutputStreamReady [%p]: "
+ "Try to send %u of data\n",
+ this, toSend));
+ }
+ }
+
+ if (toSend == 0) {
+ amtSent = 0;
+ } else {
+ rv = mSocketOut->Write(sndBuf, toSend, &amtSent);
+ LOG(("WebSocketChannel::OnOutputStreamReady [%p]: write %u rv %" PRIx32
+ "\n",
+ this, amtSent, static_cast<uint32_t>(rv)));
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSocketOut->AsyncWait(this, 0, 0, mIOThread);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ AbortSession(rv);
+ return NS_OK;
+ }
+ }
+
+ if (mHdrOut) {
+ if (amtSent == toSend) {
+ mHdrOut = nullptr;
+ mHdrOutToSend = 0;
+ } else {
+ mHdrOut += amtSent;
+ mHdrOutToSend -= amtSent;
+ mSocketOut->AsyncWait(this, 0, 0, mIOThread);
+ }
+ } else {
+ if (amtSent == toSend) {
+ if (!mStopped) {
+ if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) {
+ target->Dispatch(
+ new CallAcknowledge(this, mCurrentOut->OrigLength()),
+ NS_DISPATCH_NORMAL);
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ DeleteCurrentOutGoingMessage();
+ PrimeNewOutgoingMessage();
+ } else {
+ mCurrentOutSent += amtSent;
+ mSocketOut->AsyncWait(this, 0, 0, mIOThread);
+ }
+ }
+ }
+
+ if (mReleaseOnTransmit) ReleaseSession();
+ return NS_OK;
+}
+
+// nsIStreamListener
+
+NS_IMETHODIMP
+WebSocketChannel::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOG(("WebSocketChannel::OnDataAvailable() %p [%p %p %p %" PRIu64 " %u]\n",
+ this, aRequest, mHttpChannel.get(), aInputStream, aOffset, aCount));
+
+ // This is the HTTP OnDataAvailable Method, which means this is http data in
+ // response to the upgrade request and there should be no http response body
+ // if the upgrade succeeded. This generally should be caught by a non 101
+ // response code in OnStartRequest().. so we can ignore the data here
+
+ LOG(("WebSocketChannel::OnDataAvailable: HTTP data unexpected len>=%u\n",
+ aCount));
+
+ return NS_OK;
+}
+
+void WebSocketChannel::DoEnqueueOutgoingMessage() {
+ LOG(("WebSocketChannel::DoEnqueueOutgoingMessage() %p\n", this));
+ MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
+
+ if (!mCurrentOut) {
+ PrimeNewOutgoingMessage();
+ }
+
+ while (mCurrentOut && mConnection) {
+ nsresult rv = NS_OK;
+ if (mCurrentOut->Length() - mCurrentOutSent == 0) {
+ LOG(
+ ("WebSocketChannel::DoEnqueueOutgoingMessage: "
+ "Try to send %u of hdr/copybreak\n",
+ mHdrOutToSend));
+ rv = mConnection->WriteOutputData(mOutHeader, mHdrOutToSend, nullptr, 0);
+ } else {
+ LOG(
+ ("WebSocketChannel::DoEnqueueOutgoingMessage: "
+ "Try to send %u of hdr and %u of data\n",
+ mHdrOutToSend, mCurrentOut->Length()));
+ rv = mConnection->WriteOutputData(mOutHeader, mHdrOutToSend,
+ (uint8_t*)mCurrentOut->BeginReading(),
+ mCurrentOut->Length());
+ }
+
+ LOG(("WebSocketChannel::DoEnqueueOutgoingMessage: rv %" PRIx32 "\n",
+ static_cast<uint32_t>(rv)));
+ if (NS_FAILED(rv)) {
+ AbortSession(rv);
+ return;
+ }
+
+ if (!mStopped) {
+ // TODO: Currently, we assume that data is completely written to the
+ // socket after sending it to socket process, but it's not true. The data
+ // could be queued in socket process and waiting for the socket to be able
+ // to write. We should implement flow control for this in bug 1726552.
+ if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) {
+ target->Dispatch(new CallAcknowledge(this, mCurrentOut->OrigLength()),
+ NS_DISPATCH_NORMAL);
+ } else {
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ }
+ DeleteCurrentOutGoingMessage();
+ PrimeNewOutgoingMessage();
+ }
+
+ if (mReleaseOnTransmit) {
+ ReleaseSession();
+ }
+}
+
+void WebSocketChannel::OnError(nsresult aStatus) { AbortSession(aStatus); }
+
+void WebSocketChannel::OnTCPClosed() { mTCPClosed = true; }
+
+nsresult WebSocketChannel::OnDataReceived(uint8_t* aData, uint32_t aCount) {
+ return ProcessInput(aData, aCount);
+}
+
+} // namespace mozilla::net
+
+#undef CLOSE_GOING_AWAY
diff --git a/netwerk/protocol/websocket/WebSocketChannel.h b/netwerk/protocol/websocket/WebSocketChannel.h
new file mode 100644
index 0000000000..83b1fc6cd2
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannel.h
@@ -0,0 +1,377 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketChannel_h
+#define mozilla_net_WebSocketChannel_h
+
+#include "nsISupports.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsITimer.h"
+#include "nsIDNSListener.h"
+#include "nsINamed.h"
+#include "nsIObserver.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIChannelEventSink.h"
+#include "nsIHttpChannelInternal.h"
+#include "mozilla/net/WebSocketConnectionListener.h"
+#include "mozilla/Mutex.h"
+#include "BaseWebSocketChannel.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsDeque.h"
+#include "mozilla/Atomics.h"
+
+class nsIAsyncVerifyRedirectCallback;
+class nsIDashboardEventNotifier;
+class nsIEventTarget;
+class nsIHttpChannel;
+class nsIRandomGenerator;
+class nsISocketTransport;
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class OutboundMessage;
+class OutboundEnqueuer;
+class nsWSAdmissionManager;
+class PMCECompression;
+class CallOnMessageAvailable;
+class CallOnStop;
+class CallOnServerClose;
+class CallAcknowledge;
+class WebSocketEventService;
+class WebSocketConnectionBase;
+
+[[nodiscard]] extern nsresult CalculateWebSocketHashedSecret(
+ const nsACString& aKey, nsACString& aHash);
+extern void ProcessServerWebSocketExtensions(const nsACString& aExtensions,
+ nsACString& aNegotiatedExtensions);
+
+// Used to enforce "1 connecting websocket per host" rule, and reconnect delays
+enum wsConnectingState {
+ NOT_CONNECTING = 0, // Not yet (or no longer) trying to open connection
+ CONNECTING_QUEUED, // Waiting for other ws to same host to finish opening
+ CONNECTING_DELAYED, // Delayed by "reconnect after failure" algorithm
+ CONNECTING_IN_PROGRESS // Started connection: waiting for result
+};
+
+class WebSocketChannel : public BaseWebSocketChannel,
+ public nsIHttpUpgradeListener,
+ public nsIStreamListener,
+ public nsIInputStreamCallback,
+ public nsIOutputStreamCallback,
+ public nsITimerCallback,
+ public nsIDNSListener,
+ public nsIObserver,
+ public nsIProtocolProxyCallback,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public nsINamed,
+ public WebSocketConnectionListener {
+ friend class WebSocketFrame;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIHTTPUPGRADELISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINAMED
+
+ // nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us
+ //
+ NS_IMETHOD AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
+ JS::Handle<JS::Value> aOriginAttributes,
+ uint64_t aWindowID, nsIWebSocketListener* aListener,
+ nsISupports* aContext, JSContext* aCx) override;
+ NS_IMETHOD AsyncOpenNative(nsIURI* aURI, const nsACString& aOrigin,
+ const OriginAttributes& aOriginAttributes,
+ uint64_t aWindowID,
+ nsIWebSocketListener* aListener,
+ nsISupports* aContext) override;
+ NS_IMETHOD Close(uint16_t aCode, const nsACString& aReason) override;
+ NS_IMETHOD SendMsg(const nsACString& aMsg) override;
+ NS_IMETHOD SendBinaryMsg(const nsACString& aMsg) override;
+ NS_IMETHOD SendBinaryStream(nsIInputStream* aStream,
+ uint32_t length) override;
+ NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+
+ WebSocketChannel();
+ static void Shutdown();
+
+ // Off main thread URI access.
+ void GetEffectiveURL(nsAString& aEffectiveURL) const override;
+ bool IsEncrypted() const override;
+
+ nsresult OnTransportAvailableInternal();
+ void OnError(nsresult aStatus) override;
+ void OnTCPClosed() override;
+ nsresult OnDataReceived(uint8_t* aData, uint32_t aCount) override;
+
+ const static uint32_t kControlFrameMask = 0x8;
+
+ // First byte of the header
+ const static uint8_t kFinalFragBit = 0x80;
+ const static uint8_t kRsvBitsMask = 0x70;
+ const static uint8_t kRsv1Bit = 0x40;
+ const static uint8_t kRsv2Bit = 0x20;
+ const static uint8_t kRsv3Bit = 0x10;
+ const static uint8_t kOpcodeBitsMask = 0x0F;
+
+ // Second byte of the header
+ const static uint8_t kMaskBit = 0x80;
+ const static uint8_t kPayloadLengthBitsMask = 0x7F;
+
+ protected:
+ ~WebSocketChannel() override;
+
+ private:
+ friend class OutboundEnqueuer;
+ friend class nsWSAdmissionManager;
+ friend class FailDelayManager;
+ friend class CallOnMessageAvailable;
+ friend class CallOnStop;
+ friend class CallOnServerClose;
+ friend class CallAcknowledge;
+
+ // Common send code for binary + text msgs
+ [[nodiscard]] nsresult SendMsgCommon(const nsACString& aMsg, bool isBinary,
+ uint32_t length,
+ nsIInputStream* aStream = nullptr);
+
+ void EnqueueOutgoingMessage(nsDeque<OutboundMessage>& aQueue,
+ OutboundMessage* aMsg);
+ void DoEnqueueOutgoingMessage();
+
+ void PrimeNewOutgoingMessage();
+ void DeleteCurrentOutGoingMessage();
+ void GeneratePong(uint8_t* payload, uint32_t len);
+ void GeneratePing();
+
+ [[nodiscard]] nsresult OnNetworkChanged();
+ [[nodiscard]] nsresult StartPinging();
+
+ void BeginOpen(bool aCalledFromAdmissionManager);
+ void BeginOpenInternal();
+ [[nodiscard]] nsresult HandleExtensions();
+ [[nodiscard]] nsresult SetupRequest();
+ [[nodiscard]] nsresult ApplyForAdmission();
+ [[nodiscard]] nsresult DoAdmissionDNS();
+ [[nodiscard]] nsresult CallStartWebsocketData();
+ [[nodiscard]] nsresult StartWebsocketData();
+ uint16_t ResultToCloseCode(nsresult resultCode);
+ void ReportConnectionTelemetry(nsresult aStatusCode);
+
+ void StopSession(nsresult reason);
+ void DoStopSession(nsresult reason);
+ void AbortSession(nsresult reason);
+ void ReleaseSession();
+ void CleanupConnection();
+ void IncrementSessionCount();
+ void DecrementSessionCount();
+
+ void EnsureHdrOut(uint32_t size);
+
+ static void ApplyMask(uint32_t mask, uint8_t* data, uint64_t len);
+
+ bool IsPersistentFramePtr();
+ [[nodiscard]] nsresult ProcessInput(uint8_t* buffer, uint32_t count);
+ [[nodiscard]] bool UpdateReadBuffer(uint8_t* buffer, uint32_t count,
+ uint32_t accumulatedFragments,
+ uint32_t* available);
+
+ inline void ResetPingTimer() {
+ mPingOutstanding = 0;
+ if (mPingTimer) {
+ if (!mPingInterval) {
+ // The timer was created by forced ping and regular pinging is disabled,
+ // so cancel and null out mPingTimer.
+ mPingTimer->Cancel();
+ mPingTimer = nullptr;
+ } else {
+ mPingTimer->SetDelay(mPingInterval);
+ }
+ }
+ }
+
+ void NotifyOnStart();
+
+ nsCOMPtr<nsIEventTarget> mIOThread;
+ // Set in AsyncOpenNative and AsyncOnChannelRedirect, modified in
+ // DoStopSession on IO thread (.forget()). Probably ok...
+ nsCOMPtr<nsIHttpChannelInternal> mChannel;
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+
+ nsCOMPtr<nsICancelable> mCancelable MOZ_GUARDED_BY(mMutex);
+ // Mainthread only
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+ // Set on Mainthread during AsyncOpen, used on IO thread and Mainthread
+ nsCOMPtr<nsIRandomGenerator> mRandomGenerator;
+
+ nsCString mHashedSecret; // MainThread only
+
+ // Used as key for connection managment: Initially set to hostname from URI,
+ // then to IP address (unless we're leaving DNS resolution to a proxy server)
+ // MainThread only
+ nsCString mAddress;
+ nsCString mPath;
+ int32_t mPort; // WS server port
+ // Secondary key for the connection queue. Used by nsWSAdmissionManager.
+ nsCString mOriginSuffix; // MainThread only
+
+ // Used for off main thread access to the URI string.
+ // Set on MainThread in AsyncOpenNative, used on TargetThread and IO thread
+ nsCString mHost;
+ nsString mEffectiveURL;
+
+ // Set on MainThread before multithread use, used on IO thread, cleared on
+ // IOThread
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+ RefPtr<WebSocketConnectionBase> mConnection;
+
+ // Only used on IO Thread (accessed when known-to-be-null in DoStopSession
+ // on MainThread before mDataStarted)
+ nsCOMPtr<nsITimer> mCloseTimer;
+ // set in AsyncOpenInternal on MainThread, used on IO thread.
+ // No multithread use before it's set, no changes after that.
+ uint32_t mCloseTimeout; /* milliseconds */
+
+ nsCOMPtr<nsITimer> mOpenTimer; /* Mainthread only */
+ uint32_t mOpenTimeout; /* milliseconds, MainThread only */
+ wsConnectingState mConnecting; /* 0 if not connecting, MainThread only */
+ // Set on MainThread, deleted on MainThread, used on MainThread or
+ // IO Thread (in DoStopSession). Mutex required to access off-main-thread.
+ nsCOMPtr<nsITimer> mReconnectDelayTimer MOZ_GUARDED_BY(mMutex);
+
+ // Only touched on IOThread (DoStopSession reads it on MainThread if
+ // we haven't connected yet (mDataStarted==false), and it's always null
+ // until mDataStarted=true)
+ nsCOMPtr<nsITimer> mPingTimer;
+
+ // Created in DoStopSession on IO thread (mDataStarted=true), accessed
+ // only from IO Thread
+ nsCOMPtr<nsITimer> mLingeringCloseTimer;
+ const static int32_t kLingeringCloseTimeout = 1000;
+ const static int32_t kLingeringCloseThreshold = 50;
+
+ RefPtr<WebSocketEventService> mService; // effectively const
+
+ int32_t
+ mMaxConcurrentConnections; // only used in AsyncOpenNative on MainThread
+
+ // Set on MainThread in AsyncOpenNative; then used on IO thread
+ uint64_t mInnerWindowID;
+
+ // following members are accessed only on the main thread
+ uint32_t mGotUpgradeOK : 1;
+ uint32_t mRecvdHttpUpgradeTransport : 1;
+ uint32_t mAllowPMCE : 1;
+ uint32_t : 0; // ensure these aren't mixed with the next set
+
+ // following members are accessed only on the IO thread
+ uint32_t mPingOutstanding : 1;
+ uint32_t mReleaseOnTransmit : 1;
+ uint32_t : 0;
+
+ Atomic<bool> mDataStarted;
+ // All changes to mRequestedClose happen under mutex, but since it's atomic,
+ // it can be read anywhere without a lock
+ Atomic<bool> mRequestedClose;
+ // mServer/ClientClosed are only modified on IOThread
+ Atomic<bool> mClientClosed;
+ Atomic<bool> mServerClosed;
+ // All changes to mStopped happen under mutex, but since it's atomic, it
+ // can be read anywhere without a lock
+ Atomic<bool> mStopped;
+ Atomic<bool> mCalledOnStop;
+ Atomic<bool> mTCPClosed;
+ Atomic<bool> mOpenedHttpChannel;
+ Atomic<bool> mIncrementedSessionCount;
+ Atomic<bool> mDecrementedSessionCount;
+
+ int32_t mMaxMessageSize; // set on MainThread in AsyncOpenNative, read on IO
+ // thread
+ // Set on IOThread, or on MainThread before mDataStarted. Used on IO Thread
+ // (after mDataStarted)
+ nsresult mStopOnClose;
+ uint16_t mServerCloseCode; // only used on IO thread
+ nsCString mServerCloseReason; // only used on IO thread
+ uint16_t mScriptCloseCode MOZ_GUARDED_BY(mMutex);
+ nsCString mScriptCloseReason MOZ_GUARDED_BY(mMutex);
+
+ // These are for the read buffers
+ const static uint32_t kIncomingBufferInitialSize = 16 * 1024;
+ // We're ok with keeping a buffer this size or smaller around for the life of
+ // the websocket. If a particular message needs bigger than this we'll
+ // increase the buffer temporarily, then drop back down to this size.
+ const static uint32_t kIncomingBufferStableSize = 128 * 1024;
+
+ // Set at creation, used/modified only on IO thread
+ uint8_t* mFramePtr;
+ uint8_t* mBuffer;
+ uint8_t mFragmentOpcode;
+ uint32_t mFragmentAccumulator;
+ uint32_t mBuffered;
+ uint32_t mBufferSize;
+
+ // These are for the send buffers
+ const static int32_t kCopyBreak = 1000;
+
+ // Only used on IO thread
+ OutboundMessage* mCurrentOut;
+ uint32_t mCurrentOutSent;
+ nsDeque<OutboundMessage> mOutgoingMessages;
+ nsDeque<OutboundMessage> mOutgoingPingMessages;
+ nsDeque<OutboundMessage> mOutgoingPongMessages;
+ uint32_t mHdrOutToSend;
+ uint8_t* mHdrOut;
+ uint8_t mOutHeader[kCopyBreak + 16]{0};
+
+ // Set on MainThread in OnStartRequest (before mDataStarted), or in
+ // HandleExtensions() or OnTransportAvailableInternal(),used on IO Thread
+ // (after mDataStarted), cleared in DoStopSession on IOThread or on
+ // MainThread (if mDataStarted == false).
+ Mutex mCompressorMutex;
+ UniquePtr<PMCECompression> mPMCECompressor MOZ_GUARDED_BY(mCompressorMutex);
+
+ // Used by EnsureHdrOut, which isn't called anywhere
+ uint32_t mDynamicOutputSize;
+ uint8_t* mDynamicOutput;
+ // Set on creation and AsyncOpen, read on both threads
+ Atomic<bool> mPrivateBrowsing;
+
+ nsCOMPtr<nsIDashboardEventNotifier>
+ mConnectionLogService; // effectively const
+
+ mozilla::Mutex mMutex;
+};
+
+class WebSocketSSLChannel : public WebSocketChannel {
+ public:
+ WebSocketSSLChannel() { BaseWebSocketChannel::mEncrypted = true; }
+
+ protected:
+ virtual ~WebSocketSSLChannel() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketChannel_h
diff --git a/netwerk/protocol/websocket/WebSocketChannelChild.cpp b/netwerk/protocol/websocket/WebSocketChannelChild.cpp
new file mode 100644
index 0000000000..2931b47065
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.cpp
@@ -0,0 +1,732 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketLog.h"
+#include "base/compiler_specific.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/net/NeckoChild.h"
+#include "WebSocketChannelChild.h"
+#include "nsContentUtils.h"
+#include "nsIBrowserChild.h"
+#include "nsNetUtil.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsITransportProvider.h"
+
+using namespace mozilla::ipc;
+using mozilla::dom::ContentChild;
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(WebSocketChannelChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) WebSocketChannelChild::Release() {
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "WebSocketChannelChild");
+
+ if (mRefCnt == 1) {
+ MaybeReleaseIPCObject();
+ return mRefCnt;
+ }
+
+ if (mRefCnt == 0) {
+ mRefCnt = 1; /* stabilize */
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+}
+
+NS_INTERFACE_MAP_BEGIN(WebSocketChannelChild)
+ NS_INTERFACE_MAP_ENTRY(nsIWebSocketChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolHandler)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+NS_INTERFACE_MAP_END
+
+WebSocketChannelChild::WebSocketChannelChild(bool aEncrypted)
+ : NeckoTargetHolder(nullptr),
+ mIPCState(Closed),
+ mMutex("WebSocketChannelChild::mMutex") {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ LOG(("WebSocketChannelChild::WebSocketChannelChild() %p\n", this));
+ mEncrypted = aEncrypted;
+ mEventQ = new ChannelEventQueue(static_cast<nsIWebSocketChannel*>(this));
+}
+
+WebSocketChannelChild::~WebSocketChannelChild() {
+ LOG(("WebSocketChannelChild::~WebSocketChannelChild() %p\n", this));
+ mEventQ->NotifyReleasingOwner();
+}
+
+void WebSocketChannelChild::AddIPDLReference() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mIPCState == Closed,
+ "Attempt to retain more than one IPDL reference");
+ mIPCState = Opened;
+ }
+
+ AddRef();
+}
+
+void WebSocketChannelChild::ReleaseIPDLReference() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mIPCState != Closed,
+ "Attempt to release nonexistent IPDL reference");
+ mIPCState = Closed;
+ }
+
+ Release();
+}
+
+void WebSocketChannelChild::MaybeReleaseIPCObject() {
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return;
+ }
+
+ mIPCState = Closing;
+ }
+
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ MOZ_ALWAYS_SUCCEEDS(target->Dispatch(
+ NewRunnableMethod("WebSocketChannelChild::MaybeReleaseIPCObject", this,
+ &WebSocketChannelChild::MaybeReleaseIPCObject),
+ NS_DISPATCH_NORMAL));
+ return;
+ }
+
+ SendDeleteSelf();
+}
+
+void WebSocketChannelChild::GetEffectiveURL(nsAString& aEffectiveURL) const {
+ aEffectiveURL = mEffectiveURL;
+}
+
+bool WebSocketChannelChild::IsEncrypted() const { return mEncrypted; }
+
+class WebSocketEvent {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(WebSocketEvent)
+ MOZ_COUNTED_DTOR_VIRTUAL(WebSocketEvent)
+ virtual void Run(WebSocketChannelChild* aChild) = 0;
+};
+
+class WrappedWebSocketEvent : public Runnable {
+ public:
+ WrappedWebSocketEvent(WebSocketChannelChild* aChild,
+ UniquePtr<WebSocketEvent>&& aWebSocketEvent)
+ : Runnable("net::WrappedWebSocketEvent"),
+ mChild(aChild),
+ mWebSocketEvent(std::move(aWebSocketEvent)) {
+ MOZ_RELEASE_ASSERT(!!mWebSocketEvent);
+ }
+ NS_IMETHOD Run() override {
+ mWebSocketEvent->Run(mChild);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ UniquePtr<WebSocketEvent> mWebSocketEvent;
+};
+
+class EventTargetDispatcher : public ChannelEvent {
+ public:
+ EventTargetDispatcher(WebSocketChannelChild* aChild,
+ WebSocketEvent* aWebSocketEvent)
+ : mChild(aChild),
+ mWebSocketEvent(aWebSocketEvent),
+ mEventTarget(mChild->GetTargetThread()) {}
+
+ void Run() override {
+ if (mEventTarget) {
+ mEventTarget->Dispatch(
+ new WrappedWebSocketEvent(mChild, std::move(mWebSocketEvent)),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+ }
+
+ already_AddRefed<nsIEventTarget> GetEventTarget() override {
+ nsCOMPtr<nsIEventTarget> target = mEventTarget;
+ if (!target) {
+ target = GetMainThreadSerialEventTarget();
+ }
+ return target.forget();
+ }
+
+ private:
+ // The lifetime of the child is ensured by ChannelEventQueue.
+ WebSocketChannelChild* mChild;
+ UniquePtr<WebSocketEvent> mWebSocketEvent;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+class StartEvent : public WebSocketEvent {
+ public:
+ StartEvent(const nsACString& aProtocol, const nsACString& aExtensions,
+ const nsAString& aEffectiveURL, bool aEncrypted,
+ uint64_t aHttpChannelId)
+ : mProtocol(aProtocol),
+ mExtensions(aExtensions),
+ mEffectiveURL(aEffectiveURL),
+ mEncrypted(aEncrypted),
+ mHttpChannelId(aHttpChannelId) {}
+
+ void Run(WebSocketChannelChild* aChild) override {
+ aChild->OnStart(mProtocol, mExtensions, mEffectiveURL, mEncrypted,
+ mHttpChannelId);
+ }
+
+ private:
+ nsCString mProtocol;
+ nsCString mExtensions;
+ nsString mEffectiveURL;
+ bool mEncrypted;
+ uint64_t mHttpChannelId;
+};
+
+mozilla::ipc::IPCResult WebSocketChannelChild::RecvOnStart(
+ const nsACString& aProtocol, const nsACString& aExtensions,
+ const nsAString& aEffectiveURL, const bool& aEncrypted,
+ const uint64_t& aHttpChannelId) {
+ mEventQ->RunOrEnqueue(new EventTargetDispatcher(
+ this, new StartEvent(aProtocol, aExtensions, aEffectiveURL, aEncrypted,
+ aHttpChannelId)));
+
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnStart(const nsACString& aProtocol,
+ const nsACString& aExtensions,
+ const nsAString& aEffectiveURL,
+ const bool& aEncrypted,
+ const uint64_t& aHttpChannelId) {
+ LOG(("WebSocketChannelChild::RecvOnStart() %p\n", this));
+ SetProtocol(aProtocol);
+ mNegotiatedExtensions = aExtensions;
+ mEffectiveURL = aEffectiveURL;
+ mEncrypted = aEncrypted;
+ mHttpChannelId = aHttpChannelId;
+
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ nsresult rv = mListenerMT->mListener->OnStart(mListenerMT->mContext);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannelChild::OnStart "
+ "mListenerMT->mListener->OnStart() failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+class StopEvent : public WebSocketEvent {
+ public:
+ explicit StopEvent(const nsresult& aStatusCode) : mStatusCode(aStatusCode) {}
+
+ void Run(WebSocketChannelChild* aChild) override {
+ aChild->OnStop(mStatusCode);
+ }
+
+ private:
+ nsresult mStatusCode;
+};
+
+mozilla::ipc::IPCResult WebSocketChannelChild::RecvOnStop(
+ const nsresult& aStatusCode) {
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(this, new StopEvent(aStatusCode)));
+
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnStop(const nsresult& aStatusCode) {
+ LOG(("WebSocketChannelChild::RecvOnStop() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ nsresult rv =
+ mListenerMT->mListener->OnStop(mListenerMT->mContext, aStatusCode);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::OnStop "
+ "mListenerMT->mListener->OnStop() failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+class MessageEvent : public WebSocketEvent {
+ public:
+ MessageEvent(const nsACString& aMessage, bool aBinary)
+ : mMessage(aMessage), mBinary(aBinary) {}
+
+ void Run(WebSocketChannelChild* aChild) override {
+ if (!mBinary) {
+ aChild->OnMessageAvailable(mMessage);
+ } else {
+ aChild->OnBinaryMessageAvailable(mMessage);
+ }
+ }
+
+ private:
+ nsCString mMessage;
+ bool mBinary;
+};
+
+bool WebSocketChannelChild::RecvOnMessageAvailableInternal(
+ const nsACString& aMsg, bool aMoreData, bool aBinary) {
+ if (aMoreData) {
+ return mReceivedMsgBuffer.Append(aMsg, fallible);
+ }
+
+ if (!mReceivedMsgBuffer.Append(aMsg, fallible)) {
+ return false;
+ }
+
+ mEventQ->RunOrEnqueue(new EventTargetDispatcher(
+ this, new MessageEvent(mReceivedMsgBuffer, aBinary)));
+ mReceivedMsgBuffer.Truncate();
+ return true;
+}
+
+class OnErrorEvent : public WebSocketEvent {
+ public:
+ OnErrorEvent() = default;
+
+ void Run(WebSocketChannelChild* aChild) override { aChild->OnError(); }
+};
+
+void WebSocketChannelChild::OnError() {
+ LOG(("WebSocketChannelChild::OnError() %p", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ Unused << mListenerMT->mListener->OnError();
+ }
+}
+
+mozilla::ipc::IPCResult WebSocketChannelChild::RecvOnMessageAvailable(
+ const nsACString& aMsg, const bool& aMoreData) {
+ if (!RecvOnMessageAvailableInternal(aMsg, aMoreData, false)) {
+ LOG(("WebSocketChannelChild %p append message failed", this));
+ mEventQ->RunOrEnqueue(new EventTargetDispatcher(this, new OnErrorEvent()));
+ }
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnMessageAvailable(const nsACString& aMsg) {
+ LOG(("WebSocketChannelChild::RecvOnMessageAvailable() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ nsresult rv =
+ mListenerMT->mListener->OnMessageAvailable(mListenerMT->mContext, aMsg);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannelChild::OnMessageAvailable "
+ "mListenerMT->mListener->OnMessageAvailable() "
+ "failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+mozilla::ipc::IPCResult WebSocketChannelChild::RecvOnBinaryMessageAvailable(
+ const nsACString& aMsg, const bool& aMoreData) {
+ if (!RecvOnMessageAvailableInternal(aMsg, aMoreData, true)) {
+ LOG(("WebSocketChannelChild %p append message failed", this));
+ mEventQ->RunOrEnqueue(new EventTargetDispatcher(this, new OnErrorEvent()));
+ }
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnBinaryMessageAvailable(const nsACString& aMsg) {
+ LOG(("WebSocketChannelChild::RecvOnBinaryMessageAvailable() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ nsresult rv = mListenerMT->mListener->OnBinaryMessageAvailable(
+ mListenerMT->mContext, aMsg);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannelChild::OnBinaryMessageAvailable "
+ "mListenerMT->mListener->OnBinaryMessageAvailable() "
+ "failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+class AcknowledgeEvent : public WebSocketEvent {
+ public:
+ explicit AcknowledgeEvent(const uint32_t& aSize) : mSize(aSize) {}
+
+ void Run(WebSocketChannelChild* aChild) override {
+ aChild->OnAcknowledge(mSize);
+ }
+
+ private:
+ uint32_t mSize;
+};
+
+mozilla::ipc::IPCResult WebSocketChannelChild::RecvOnAcknowledge(
+ const uint32_t& aSize) {
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(this, new AcknowledgeEvent(aSize)));
+
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnAcknowledge(const uint32_t& aSize) {
+ LOG(("WebSocketChannelChild::RecvOnAcknowledge() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ nsresult rv =
+ mListenerMT->mListener->OnAcknowledge(mListenerMT->mContext, aSize);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::OnAcknowledge "
+ "mListenerMT->mListener->OnAcknowledge() "
+ "failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+class ServerCloseEvent : public WebSocketEvent {
+ public:
+ ServerCloseEvent(const uint16_t aCode, const nsACString& aReason)
+ : mCode(aCode), mReason(aReason) {}
+
+ void Run(WebSocketChannelChild* aChild) override {
+ aChild->OnServerClose(mCode, mReason);
+ }
+
+ private:
+ uint16_t mCode;
+ nsCString mReason;
+};
+
+mozilla::ipc::IPCResult WebSocketChannelChild::RecvOnServerClose(
+ const uint16_t& aCode, const nsACString& aReason) {
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(this, new ServerCloseEvent(aCode, aReason)));
+
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnServerClose(const uint16_t& aCode,
+ const nsACString& aReason) {
+ LOG(("WebSocketChannelChild::RecvOnServerClose() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ DebugOnly<nsresult> rv = mListenerMT->mListener->OnServerClose(
+ mListenerMT->mContext, aCode, aReason);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void WebSocketChannelChild::SetupNeckoTarget() {
+ mNeckoTarget = GetMainThreadSerialEventTarget();
+}
+
+NS_IMETHODIMP
+WebSocketChannelChild::AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
+ JS::Handle<JS::Value> aOriginAttributes,
+ uint64_t aInnerWindowID,
+ nsIWebSocketListener* aListener,
+ nsISupports* aContext, JSContext* aCx) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return AsyncOpenNative(aURI, aOrigin, attrs, aInnerWindowID, aListener,
+ aContext);
+}
+
+NS_IMETHODIMP
+WebSocketChannelChild::AsyncOpenNative(
+ nsIURI* aURI, const nsACString& aOrigin,
+ const OriginAttributes& aOriginAttributes, uint64_t aInnerWindowID,
+ nsIWebSocketListener* aListener, nsISupports* aContext) {
+ LOG(("WebSocketChannelChild::AsyncOpen() %p\n", this));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT((aURI && !mIsServerSide) || (!aURI && mIsServerSide),
+ "Invalid aURI for WebSocketChannelChild::AsyncOpen");
+ MOZ_ASSERT(aListener && !mListenerMT,
+ "Invalid state for WebSocketChannelChild::AsyncOpen");
+
+ mozilla::dom::BrowserChild* browserChild = nullptr;
+ nsCOMPtr<nsIBrowserChild> iBrowserChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIBrowserChild),
+ getter_AddRefs(iBrowserChild));
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get());
+ }
+
+ ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Corresponding release in DeallocPWebSocket
+ AddIPDLReference();
+
+ nsCOMPtr<nsIURI> uri;
+ LoadInfoArgs loadInfoArgs;
+ Maybe<NotNull<PTransportProviderChild*>> transportProvider;
+
+ if (!mIsServerSide) {
+ uri = aURI;
+ nsresult rv = LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfoArgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transportProvider = Nothing();
+ } else {
+ MOZ_ASSERT(mServerTransportProvider);
+ PTransportProviderChild* ipcChild;
+ nsresult rv = mServerTransportProvider->GetIPCChild(&ipcChild);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transportProvider = Some(WrapNotNull(ipcChild));
+ }
+
+ // This must be called before sending constructor message.
+ SetupNeckoTarget();
+
+ if (!gNeckoChild->SendPWebSocketConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), mSerial)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (!SendAsyncOpen(uri, aOrigin, aOriginAttributes, aInnerWindowID, mProtocol,
+ mEncrypted, mPingInterval, mClientSetPingInterval,
+ mPingResponseTimeout, mClientSetPingTimeout, loadInfoArgs,
+ transportProvider, mNegotiatedExtensions)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mIsServerSide) {
+ mServerTransportProvider = nullptr;
+ }
+
+ mOriginalURI = aURI;
+ mURI = mOriginalURI;
+ mListenerMT = new ListenerAndContextContainer(aListener, aContext);
+ mOrigin = aOrigin;
+ mWasOpened = 1;
+
+ return NS_OK;
+}
+
+class CloseEvent : public Runnable {
+ public:
+ CloseEvent(WebSocketChannelChild* aChild, uint16_t aCode,
+ const nsACString& aReason)
+ : Runnable("net::CloseEvent"),
+ mChild(aChild),
+ mCode(aCode),
+ mReason(aReason) {
+ MOZ_RELEASE_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aChild);
+ }
+ NS_IMETHOD Run() override {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ mChild->Close(mCode, mReason);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ uint16_t mCode;
+ nsCString mReason;
+};
+
+NS_IMETHODIMP
+WebSocketChannelChild::Close(uint16_t code, const nsACString& reason) {
+ if (!NS_IsMainThread()) {
+ MOZ_RELEASE_ASSERT(IsOnTargetThread());
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ return target->Dispatch(new CloseEvent(this, code, reason),
+ NS_DISPATCH_NORMAL);
+ }
+ LOG(("WebSocketChannelChild::Close() %p\n", this));
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendClose(code, reason)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+class MsgEvent : public Runnable {
+ public:
+ MsgEvent(WebSocketChannelChild* aChild, const nsACString& aMsg,
+ bool aBinaryMsg)
+ : Runnable("net::MsgEvent"),
+ mChild(aChild),
+ mMsg(aMsg),
+ mBinaryMsg(aBinaryMsg) {
+ MOZ_RELEASE_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aChild);
+ }
+ NS_IMETHOD Run() override {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (mBinaryMsg) {
+ mChild->SendBinaryMsg(mMsg);
+ } else {
+ mChild->SendMsg(mMsg);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ nsCString mMsg;
+ bool mBinaryMsg;
+};
+
+NS_IMETHODIMP
+WebSocketChannelChild::SendMsg(const nsACString& aMsg) {
+ if (!NS_IsMainThread()) {
+ MOZ_RELEASE_ASSERT(IsOnTargetThread());
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ return target->Dispatch(new MsgEvent(this, aMsg, false),
+ NS_DISPATCH_NORMAL);
+ }
+ LOG(("WebSocketChannelChild::SendMsg() %p\n", this));
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendSendMsg(aMsg)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelChild::SendBinaryMsg(const nsACString& aMsg) {
+ if (!NS_IsMainThread()) {
+ MOZ_RELEASE_ASSERT(IsOnTargetThread());
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ return target->Dispatch(new MsgEvent(this, aMsg, true), NS_DISPATCH_NORMAL);
+ }
+ LOG(("WebSocketChannelChild::SendBinaryMsg() %p\n", this));
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendSendBinaryMsg(aMsg)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+class BinaryStreamEvent : public Runnable {
+ public:
+ BinaryStreamEvent(WebSocketChannelChild* aChild, nsIInputStream* aStream,
+ uint32_t aLength)
+ : Runnable("net::BinaryStreamEvent"),
+ mChild(aChild),
+ mStream(aStream),
+ mLength(aLength) {
+ MOZ_RELEASE_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aChild);
+ }
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv = mChild->SendBinaryStream(mStream, mLength);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannelChild::BinaryStreamEvent %p "
+ "SendBinaryStream failed (%08" PRIx32 ")\n",
+ this, static_cast<uint32_t>(rv)));
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ nsCOMPtr<nsIInputStream> mStream;
+ uint32_t mLength;
+};
+
+NS_IMETHODIMP
+WebSocketChannelChild::SendBinaryStream(nsIInputStream* aStream,
+ uint32_t aLength) {
+ if (!NS_IsMainThread()) {
+ MOZ_RELEASE_ASSERT(IsOnTargetThread());
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ return target->Dispatch(new BinaryStreamEvent(this, aStream, aLength),
+ NS_DISPATCH_NORMAL);
+ }
+
+ LOG(("WebSocketChannelChild::SendBinaryStream() %p\n", this));
+
+ IPCStream ipcStream;
+ if (NS_WARN_IF(!mozilla::ipc::SerializeIPCStream(do_AddRef(aStream),
+ ipcStream,
+ /* aAllowLazy */ false))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendSendBinaryStream(ipcStream, aLength)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelChild::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ LOG(("WebSocketChannelChild::GetSecurityInfo() %p\n", this));
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketChannelChild.h b/netwerk/protocol/websocket/WebSocketChannelChild.h
new file mode 100644
index 0000000000..0221b9fdde
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketChannelChild_h
+#define mozilla_net_WebSocketChannelChild_h
+
+#include "mozilla/net/NeckoTargetHolder.h"
+#include "mozilla/net/PWebSocketChild.h"
+#include "mozilla/net/BaseWebSocketChannel.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+namespace net {
+
+class ChannelEvent;
+class ChannelEventQueue;
+class MessageEvent;
+
+class WebSocketChannelChild final : public BaseWebSocketChannel,
+ public PWebSocketChild,
+ public NeckoTargetHolder {
+ friend class PWebSocketChild;
+
+ public:
+ explicit WebSocketChannelChild(bool aEncrypted);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us
+ //
+ NS_IMETHOD AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
+ JS::Handle<JS::Value> aOriginAttributes,
+ uint64_t aInnerWindowID, nsIWebSocketListener* aListener,
+ nsISupports* aContext, JSContext* aCx) override;
+ NS_IMETHOD AsyncOpenNative(nsIURI* aURI, const nsACString& aOrigin,
+ const OriginAttributes& aOriginAttributes,
+ uint64_t aInnerWindowID,
+ nsIWebSocketListener* aListener,
+ nsISupports* aContext) override;
+ NS_IMETHOD Close(uint16_t code, const nsACString& reason) override;
+ NS_IMETHOD SendMsg(const nsACString& aMsg) override;
+ NS_IMETHOD SendBinaryMsg(const nsACString& aMsg) override;
+ NS_IMETHOD SendBinaryStream(nsIInputStream* aStream,
+ uint32_t aLength) override;
+ NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ // Off main thread URI access.
+ void GetEffectiveURL(nsAString& aEffectiveURL) const override;
+ bool IsEncrypted() const override;
+
+ private:
+ ~WebSocketChannelChild();
+
+ mozilla::ipc::IPCResult RecvOnStart(const nsACString& aProtocol,
+ const nsACString& aExtensions,
+ const nsAString& aEffectiveURL,
+ const bool& aEncrypted,
+ const uint64_t& aHttpChannelId);
+ mozilla::ipc::IPCResult RecvOnStop(const nsresult& aStatusCode);
+ mozilla::ipc::IPCResult RecvOnMessageAvailable(const nsACString& aMsg,
+ const bool& aMoreData);
+ mozilla::ipc::IPCResult RecvOnBinaryMessageAvailable(const nsACString& aMsg,
+ const bool& aMoreData);
+ mozilla::ipc::IPCResult RecvOnAcknowledge(const uint32_t& aSize);
+ mozilla::ipc::IPCResult RecvOnServerClose(const uint16_t& aCode,
+ const nsACString& aReason);
+
+ void OnStart(const nsACString& aProtocol, const nsACString& aExtensions,
+ const nsAString& aEffectiveURL, const bool& aEncrypted,
+ const uint64_t& aHttpChannelId);
+ void OnStop(const nsresult& aStatusCode);
+ void OnMessageAvailable(const nsACString& aMsg);
+ void OnBinaryMessageAvailable(const nsACString& aMsg);
+ void OnAcknowledge(const uint32_t& aSize);
+ void OnServerClose(const uint16_t& aCode, const nsACString& aReason);
+ void AsyncOpenFailed();
+
+ void MaybeReleaseIPCObject();
+
+ // This function tries to get a labeled event target for |mNeckoTarget|.
+ void SetupNeckoTarget();
+
+ bool RecvOnMessageAvailableInternal(const nsACString& aMsg, bool aMoreData,
+ bool aBinary);
+
+ void OnError();
+
+ RefPtr<ChannelEventQueue> mEventQ;
+ nsString mEffectiveURL;
+ nsCString mReceivedMsgBuffer;
+
+ // This variable is protected by mutex.
+ enum { Opened, Closing, Closed } mIPCState;
+
+ mozilla::Mutex mMutex MOZ_UNANNOTATED;
+
+ friend class StartEvent;
+ friend class StopEvent;
+ friend class MessageEvent;
+ friend class AcknowledgeEvent;
+ friend class ServerCloseEvent;
+ friend class AsyncOpenFailedEvent;
+ friend class OnErrorEvent;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketChannelChild_h
diff --git a/netwerk/protocol/websocket/WebSocketChannelParent.cpp b/netwerk/protocol/websocket/WebSocketChannelParent.cpp
new file mode 100644
index 0000000000..b690f3ac27
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.cpp
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketLog.h"
+#include "WebSocketChannelParent.h"
+#include "nsIAuthPromptProvider.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/WebSocketChannel.h"
+#include "nsComponentManagerUtils.h"
+#include "IPCTransportProvider.h"
+#include "mozilla/net/ChannelEventQueue.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(WebSocketChannelParent, nsIWebSocketListener,
+ nsIInterfaceRequestor)
+
+WebSocketChannelParent::WebSocketChannelParent(
+ nsIAuthPromptProvider* aAuthProvider, nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus, uint32_t aSerial)
+ : mAuthProvider(aAuthProvider),
+ mLoadContext(aLoadContext),
+ mSerial(aSerial) {
+ // Websocket channels can't have a private browsing override
+ MOZ_ASSERT_IF(!aLoadContext, aOverrideStatus == kPBOverride_Unset);
+}
+//-----------------------------------------------------------------------------
+// WebSocketChannelParent::PWebSocketChannelParent
+//-----------------------------------------------------------------------------
+
+mozilla::ipc::IPCResult WebSocketChannelParent::RecvDeleteSelf() {
+ LOG(("WebSocketChannelParent::RecvDeleteSelf() %p\n", this));
+ mChannel = nullptr;
+ mAuthProvider = nullptr;
+ IProtocol* mgr = Manager();
+ if (CanRecv() && !Send__delete__(this)) {
+ return IPC_FAIL_NO_REASON(mgr);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketChannelParent::RecvAsyncOpen(
+ nsIURI* aURI, const nsCString& aOrigin,
+ const OriginAttributes& aOriginAttributes, const uint64_t& aInnerWindowID,
+ const nsCString& aProtocol, const bool& aSecure,
+ const uint32_t& aPingInterval, const bool& aClientSetPingInterval,
+ const uint32_t& aPingTimeout, const bool& aClientSetPingTimeout,
+ const LoadInfoArgs& aLoadInfoArgs,
+ const Maybe<PTransportProviderParent*>& aTransportProvider,
+ const nsCString& aNegotiatedExtensions) {
+ LOG(("WebSocketChannelParent::RecvAsyncOpen() %p\n", this));
+
+ nsresult rv;
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsCOMPtr<nsIURI> uri;
+
+ rv = LoadInfoArgsToLoadInfo(
+ aLoadInfoArgs,
+ mozilla::dom::ContentParent::Cast(Manager()->Manager())->GetRemoteType(),
+ getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ goto fail;
+ }
+
+ if (aSecure) {
+ mChannel =
+ do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
+ } else {
+ mChannel =
+ do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
+ }
+ if (NS_FAILED(rv)) goto fail;
+
+ rv = mChannel->SetSerial(mSerial);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ goto fail;
+ }
+
+ rv = mChannel->SetLoadInfo(loadInfo);
+ if (NS_FAILED(rv)) {
+ goto fail;
+ }
+
+ rv = mChannel->SetNotificationCallbacks(this);
+ if (NS_FAILED(rv)) goto fail;
+
+ rv = mChannel->SetProtocol(aProtocol);
+ if (NS_FAILED(rv)) goto fail;
+
+ if (aTransportProvider.isSome()) {
+ RefPtr<TransportProviderParent> provider =
+ static_cast<TransportProviderParent*>(aTransportProvider.value());
+ rv = mChannel->SetServerParameters(provider, aNegotiatedExtensions);
+ if (NS_FAILED(rv)) {
+ goto fail;
+ }
+ } else {
+ uri = aURI;
+ if (!uri) {
+ rv = NS_ERROR_FAILURE;
+ goto fail;
+ }
+ }
+
+ // only use ping values from child if they were overridden by client code.
+ if (aClientSetPingInterval) {
+ // IDL allows setting in seconds, so must be multiple of 1000 ms
+ MOZ_ASSERT(aPingInterval >= 1000 && !(aPingInterval % 1000));
+ DebugOnly<nsresult> rv = mChannel->SetPingInterval(aPingInterval / 1000);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ if (aClientSetPingTimeout) {
+ MOZ_ASSERT(aPingTimeout >= 1000 && !(aPingTimeout % 1000));
+ DebugOnly<nsresult> rv = mChannel->SetPingTimeout(aPingTimeout / 1000);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ rv = mChannel->AsyncOpenNative(uri, aOrigin, aOriginAttributes,
+ aInnerWindowID, this, nullptr);
+ if (NS_FAILED(rv)) goto fail;
+
+ return IPC_OK();
+
+fail:
+ mChannel = nullptr;
+ if (!SendOnStop(rv)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketChannelParent::RecvClose(
+ const uint16_t& code, const nsCString& reason) {
+ LOG(("WebSocketChannelParent::RecvClose() %p\n", this));
+ if (mChannel) {
+ nsresult rv = mChannel->Close(code, reason);
+ NS_ENSURE_SUCCESS(rv, IPC_OK());
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketChannelParent::RecvSendMsg(
+ const nsCString& aMsg) {
+ LOG(("WebSocketChannelParent::RecvSendMsg() %p\n", this));
+ if (mChannel) {
+ nsresult rv = mChannel->SendMsg(aMsg);
+ NS_ENSURE_SUCCESS(rv, IPC_OK());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketChannelParent::RecvSendBinaryMsg(
+ const nsCString& aMsg) {
+ LOG(("WebSocketChannelParent::RecvSendBinaryMsg() %p\n", this));
+ if (mChannel) {
+ nsresult rv = mChannel->SendBinaryMsg(aMsg);
+ NS_ENSURE_SUCCESS(rv, IPC_OK());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketChannelParent::RecvSendBinaryStream(
+ const IPCStream& aStream, const uint32_t& aLength) {
+ LOG(("WebSocketChannelParent::RecvSendBinaryStream() %p\n", this));
+ if (mChannel) {
+ nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStream);
+ if (!stream) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ nsresult rv = mChannel->SendBinaryStream(stream, aLength);
+ NS_ENSURE_SUCCESS(rv, IPC_OK());
+ }
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnStart(nsISupports* aContext) {
+ LOG(("WebSocketChannelParent::OnStart() %p\n", this));
+ nsAutoCString protocol, extensions;
+ nsString effectiveURL;
+ bool encrypted = false;
+ uint64_t httpChannelId = 0;
+ if (mChannel) {
+ DebugOnly<nsresult> rv = mChannel->GetProtocol(protocol);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = mChannel->GetExtensions(extensions);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ RefPtr<WebSocketChannel> channel;
+ channel = static_cast<WebSocketChannel*>(mChannel.get());
+ MOZ_ASSERT(channel);
+
+ channel->GetEffectiveURL(effectiveURL);
+ encrypted = channel->IsEncrypted();
+ httpChannelId = channel->HttpChannelId();
+ }
+ if (!CanRecv() || !SendOnStart(protocol, extensions, effectiveURL, encrypted,
+ httpChannelId)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnStop(nsISupports* aContext, nsresult aStatusCode) {
+ LOG(("WebSocketChannelParent::OnStop() %p\n", this));
+ if (!CanRecv() || !SendOnStop(aStatusCode)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+static bool SendOnMessageAvailableHelper(
+ const nsACString& aMsg,
+ const std::function<bool(const nsDependentCSubstring&, bool)>& aSendFunc) {
+ // To avoid the crash caused by too large IPC message, we have to split the
+ // data in small chunks and send them to child process. Note that the chunk
+ // size used here is the same as what we used for PHttpChannel.
+ static uint32_t const kCopyChunkSize = 128 * 1024;
+ uint32_t count = aMsg.Length();
+ if (count <= kCopyChunkSize) {
+ return aSendFunc(nsDependentCSubstring(aMsg), false);
+ }
+
+ uint32_t start = 0;
+ uint32_t toRead = std::min<uint32_t>(count, kCopyChunkSize);
+ while (count) {
+ nsDependentCSubstring data(Substring(aMsg, start, toRead));
+
+ if (!aSendFunc(data, count > kCopyChunkSize)) {
+ return false;
+ }
+
+ start += toRead;
+ count -= toRead;
+ toRead = std::min<uint32_t>(count, kCopyChunkSize);
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnMessageAvailable(nsISupports* aContext,
+ const nsACString& aMsg) {
+ LOG(("WebSocketChannelParent::OnMessageAvailable() %p\n", this));
+
+ if (!CanRecv()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto sendFunc = [self = UnsafePtr<WebSocketChannelParent>(this)](
+ const nsDependentCSubstring& aMsg, bool aMoreData) {
+ return self->SendOnMessageAvailable(aMsg, aMoreData);
+ };
+
+ if (!SendOnMessageAvailableHelper(aMsg, sendFunc)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnBinaryMessageAvailable(nsISupports* aContext,
+ const nsACString& aMsg) {
+ LOG(("WebSocketChannelParent::OnBinaryMessageAvailable() %p\n", this));
+
+ if (!CanRecv()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto sendFunc = [self = UnsafePtr<WebSocketChannelParent>(this)](
+ const nsDependentCSubstring& aMsg, bool aMoreData) {
+ return self->SendOnBinaryMessageAvailable(aMsg, aMoreData);
+ };
+
+ if (!SendOnMessageAvailableHelper(aMsg, sendFunc)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnAcknowledge(nsISupports* aContext, uint32_t aSize) {
+ LOG(("WebSocketChannelParent::OnAcknowledge() %p\n", this));
+ if (!CanRecv() || !SendOnAcknowledge(aSize)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnServerClose(nsISupports* aContext, uint16_t code,
+ const nsACString& reason) {
+ LOG(("WebSocketChannelParent::OnServerClose() %p\n", this));
+ if (!CanRecv() || !SendOnServerClose(code, reason)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnError() { return NS_OK; }
+
+void WebSocketChannelParent::ActorDestroy(ActorDestroyReason why) {
+ LOG(("WebSocketChannelParent::ActorDestroy() %p\n", this));
+
+ // Make sure we close the channel if the content process dies without going
+ // through a clean shutdown.
+ if (mChannel) {
+ Unused << mChannel->Close(nsIWebSocketChannel::CLOSE_GOING_AWAY,
+ "Child was killed"_ns);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketChannelParent::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketChannelParent::GetInterface(const nsIID& iid, void** result) {
+ LOG(("WebSocketChannelParent::GetInterface() %p\n", this));
+ if (mAuthProvider && iid.Equals(NS_GET_IID(nsIAuthPromptProvider))) {
+ nsresult rv = mAuthProvider->GetAuthPrompt(
+ nsIAuthPromptProvider::PROMPT_NORMAL, iid, result);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+ return NS_OK;
+ }
+
+ // Only support nsILoadContext if child channel's callbacks did too
+ if (iid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(result);
+ return NS_OK;
+ }
+
+ return QueryInterface(iid, result);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketChannelParent.h b/netwerk/protocol/websocket/WebSocketChannelParent.h
new file mode 100644
index 0000000000..534a737dcb
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketChannelParent_h
+#define mozilla_net_WebSocketChannelParent_h
+
+#include "mozilla/net/PWebSocketParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIWebSocketListener.h"
+#include "nsIWebSocketChannel.h"
+#include "nsILoadContext.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+class nsIAuthPromptProvider;
+
+namespace mozilla {
+namespace net {
+
+class WebSocketChannelParent : public PWebSocketParent,
+ public nsIWebSocketListener,
+ public nsIInterfaceRequestor {
+ friend class PWebSocketParent;
+
+ ~WebSocketChannelParent() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBSOCKETLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ WebSocketChannelParent(nsIAuthPromptProvider* aAuthProvider,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus, uint32_t aSerial);
+
+ private:
+ mozilla::ipc::IPCResult RecvAsyncOpen(
+ nsIURI* aURI, const nsCString& aOrigin,
+ const OriginAttributes& aOriginAttributes, const uint64_t& aInnerWindowID,
+ const nsCString& aProtocol, const bool& aSecure,
+ const uint32_t& aPingInterval, const bool& aClientSetPingInterval,
+ const uint32_t& aPingTimeout, const bool& aClientSetPingTimeout,
+ const LoadInfoArgs& aLoadInfoArgs,
+ const Maybe<PTransportProviderParent*>& aTransportProvider,
+ const nsCString& aNegotiatedExtensions);
+ mozilla::ipc::IPCResult RecvClose(const uint16_t& code,
+ const nsCString& reason);
+ mozilla::ipc::IPCResult RecvSendMsg(const nsCString& aMsg);
+ mozilla::ipc::IPCResult RecvSendBinaryMsg(const nsCString& aMsg);
+ mozilla::ipc::IPCResult RecvSendBinaryStream(const IPCStream& aStream,
+ const uint32_t& aLength);
+ mozilla::ipc::IPCResult RecvDeleteSelf();
+
+ void ActorDestroy(ActorDestroyReason why) override;
+
+ nsCOMPtr<nsIAuthPromptProvider> mAuthProvider;
+ nsCOMPtr<nsIWebSocketChannel> mChannel;
+ nsCOMPtr<nsILoadContext> mLoadContext;
+
+ uint32_t mSerial;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketChannelParent_h
diff --git a/netwerk/protocol/websocket/WebSocketConnection.cpp b/netwerk/protocol/websocket/WebSocketConnection.cpp
new file mode 100644
index 0000000000..b57f4fc115
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnection.cpp
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInterfaceRequestor.h"
+#include "WebSocketConnection.h"
+
+#include "WebSocketLog.h"
+#include "mozilla/net/WebSocketConnectionListener.h"
+#include "nsIOService.h"
+#include "nsITLSSocketControl.h"
+#include "nsISocketTransport.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsSocketTransportService2.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(WebSocketConnection, nsIInputStreamCallback,
+ nsIOutputStreamCallback)
+
+WebSocketConnection::WebSocketConnection(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aInputStream,
+ nsIAsyncOutputStream* aOutputStream)
+ : mTransport(aTransport),
+ mSocketIn(aInputStream),
+ mSocketOut(aOutputStream) {
+ LOG(("WebSocketConnection ctor %p\n", this));
+}
+
+WebSocketConnection::~WebSocketConnection() {
+ LOG(("WebSocketConnection dtor %p\n", this));
+}
+
+nsresult WebSocketConnection::Init(WebSocketConnectionListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mListener = aListener;
+ nsresult rv;
+ mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!mTransport) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(mListener);
+ mTransport->SetSecurityCallbacks(callbacks);
+ } else {
+ // NOTE: we don't use security callbacks in socket process.
+ mTransport->SetSecurityCallbacks(nullptr);
+ }
+ return mTransport->SetEventSink(nullptr, nullptr);
+}
+
+void WebSocketConnection::GetIoTarget(nsIEventTarget** aTarget) {
+ nsCOMPtr<nsIEventTarget> target = mSocketThread;
+ return target.forget(aTarget);
+}
+
+void WebSocketConnection::Close() {
+ LOG(("WebSocketConnection::Close %p\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (mTransport) {
+ mTransport->SetSecurityCallbacks(nullptr);
+ mTransport->SetEventSink(nullptr, nullptr);
+ mTransport->Close(NS_BASE_STREAM_CLOSED);
+ mTransport = nullptr;
+ }
+
+ if (mSocketIn) {
+ if (mStartReadingCalled) {
+ mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+ mSocketIn = nullptr;
+ }
+
+ if (mSocketOut) {
+ mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mSocketOut = nullptr;
+ }
+}
+
+nsresult WebSocketConnection::WriteOutputData(nsTArray<uint8_t>&& aData) {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!mSocketOut) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mOutputQueue.emplace_back(std::move(aData));
+ return OnOutputStreamReady(mSocketOut);
+}
+
+nsresult WebSocketConnection::WriteOutputData(const uint8_t* aHdrBuf,
+ uint32_t aHdrBufLength,
+ const uint8_t* aPayloadBuf,
+ uint32_t aPayloadBufLength) {
+ MOZ_ASSERT_UNREACHABLE("Should not be called");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult WebSocketConnection::StartReading() {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!mSocketIn) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MOZ_ASSERT(!mStartReadingCalled, "StartReading twice");
+ mStartReadingCalled = true;
+ return mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
+}
+
+void WebSocketConnection::DrainSocketData() {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!mSocketIn || !mListener) {
+ return;
+ }
+
+ // If we leave any data unconsumed (including the tcp fin) a RST will be
+ // generated The right thing to do here is shutdown(SHUT_WR) and then wait a
+ // little while to see if any data comes in.. but there is no reason to delay
+ // things for that when the websocket handshake is supposed to guarantee a
+ // quiet connection except for that fin.
+ char buffer[512];
+ uint32_t count = 0;
+ uint32_t total = 0;
+ nsresult rv;
+ do {
+ total += count;
+ rv = mSocketIn->Read(buffer, 512, &count);
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK && (NS_FAILED(rv) || count == 0)) {
+ mListener->OnTCPClosed();
+ }
+ } while (NS_SUCCEEDED(rv) && count > 0 && total < 32000);
+}
+
+nsresult WebSocketConnection::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ LOG(("WebSocketConnection::GetSecurityInfo() %p\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ *aSecurityInfo = nullptr;
+
+ if (mTransport) {
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ nsresult rv =
+ mTransport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(
+ do_QueryInterface(tlsSocketControl));
+ if (securityInfo) {
+ securityInfo.forget(aSecurityInfo);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketConnection::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ LOG(("WebSocketConnection::OnInputStreamReady() %p\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ MOZ_ASSERT(mListener);
+
+ // did we we clean up the socket after scheduling InputReady?
+ if (!mSocketIn) {
+ return NS_OK;
+ }
+
+ // this is after the http upgrade - so we are speaking websockets
+ uint8_t buffer[2048];
+ uint32_t count;
+ nsresult rv;
+
+ do {
+ rv = mSocketIn->Read((char*)buffer, 2048, &count);
+ LOG(("WebSocketConnection::OnInputStreamReady: read %u rv %" PRIx32 "\n",
+ count, static_cast<uint32_t>(rv)));
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ mListener->OnError(rv);
+ return rv;
+ }
+
+ if (count == 0) {
+ mListener->OnError(NS_BASE_STREAM_CLOSED);
+ return NS_OK;
+ }
+
+ rv = mListener->OnDataReceived(buffer, count);
+ if (NS_FAILED(rv)) {
+ mListener->OnError(rv);
+ return rv;
+ }
+ } while (NS_SUCCEEDED(rv) && mSocketIn && mListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketConnection::OnOutputStreamReady(nsIAsyncOutputStream* aStream) {
+ LOG(("WebSocketConnection::OnOutputStreamReady() %p\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ MOZ_ASSERT(mListener);
+
+ if (!mSocketOut) {
+ return NS_OK;
+ }
+
+ while (!mOutputQueue.empty()) {
+ const OutputData& data = mOutputQueue.front();
+
+ char* buffer = reinterpret_cast<char*>(
+ const_cast<uint8_t*>(data.GetData().Elements())) +
+ mWriteOffset;
+ uint32_t toWrite = data.GetData().Length() - mWriteOffset;
+
+ uint32_t wrote = 0;
+ nsresult rv = mSocketOut->Write(buffer, toWrite, &wrote);
+ LOG(("WebSocketConnection::OnOutputStreamReady: write %u rv %" PRIx32,
+ wrote, static_cast<uint32_t>(rv)));
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
+ return rv;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketConnection::OnOutputStreamReady %p failed %u\n", this,
+ static_cast<uint32_t>(rv)));
+ mListener->OnError(rv);
+ return NS_OK;
+ }
+
+ mWriteOffset += wrote;
+
+ if (toWrite == wrote) {
+ mWriteOffset = 0;
+ mOutputQueue.pop_front();
+ } else {
+ mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/websocket/WebSocketConnection.h b/netwerk/protocol/websocket/WebSocketConnection.h
new file mode 100644
index 0000000000..4e6a53b013
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnection.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketConnection_h
+#define mozilla_net_WebSocketConnection_h
+
+#include <list>
+
+#include "nsIStreamListener.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "mozilla/net/WebSocketConnectionBase.h"
+#include "nsTArray.h"
+#include "nsISocketTransport.h"
+
+class nsISocketTransport;
+
+namespace mozilla {
+namespace net {
+
+class WebSocketConnectionListener;
+
+class WebSocketConnection : public nsIInputStreamCallback,
+ public nsIOutputStreamCallback,
+ public WebSocketConnectionBase {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+
+ explicit WebSocketConnection(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aInputStream,
+ nsIAsyncOutputStream* aOutputStream);
+
+ nsresult Init(WebSocketConnectionListener* aListener) override;
+ void GetIoTarget(nsIEventTarget** aTarget) override;
+ void Close() override;
+ nsresult WriteOutputData(const uint8_t* aHdrBuf, uint32_t aHdrBufLength,
+ const uint8_t* aPayloadBuf,
+ uint32_t aPayloadBufLength) override;
+ nsresult WriteOutputData(nsTArray<uint8_t>&& aData);
+ nsresult StartReading() override;
+ void DrainSocketData() override;
+ nsresult GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+
+ private:
+ virtual ~WebSocketConnection();
+
+ class OutputData {
+ public:
+ explicit OutputData(nsTArray<uint8_t>&& aData) : mData(std::move(aData)) {
+ MOZ_COUNT_CTOR(OutputData);
+ }
+
+ ~OutputData() { MOZ_COUNT_DTOR(OutputData); }
+
+ const nsTArray<uint8_t>& GetData() const { return mData; }
+
+ private:
+ nsTArray<uint8_t> mData;
+ };
+
+ RefPtr<WebSocketConnectionListener> mListener;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+ nsCOMPtr<nsIEventTarget> mSocketThread;
+ size_t mWriteOffset{0};
+ std::list<OutputData> mOutputQueue;
+ bool mStartReadingCalled{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketConnection_h
diff --git a/netwerk/protocol/websocket/WebSocketConnectionBase.h b/netwerk/protocol/websocket/WebSocketConnectionBase.h
new file mode 100644
index 0000000000..a462b8a346
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnectionBase.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketConnectionBase_h
+#define mozilla_net_WebSocketConnectionBase_h
+
+// Architecture view:
+// Parent Process Socket Process
+// ┌─────────────────┐ IPC ┌────────────────────────┐
+// │ WebSocketChannel│ ┌─────►WebSocketConnectionChild│
+// └─────────┬───────┘ │ └─────────┬──────────────┘
+// │ │ │
+// ┌──────────▼───────────────┐ │ ┌─────────▼─────────┐
+// │ WebSocketConnectionParent├──┘ │WebSocketConnection│
+// └──────────────────────────┘ └─────────┬─────────┘
+// │
+// ┌─────────▼─────────┐
+// │ nsSocketTransport │
+// └───────────────────┘
+// The main reason that we need WebSocketConnectionBase is that we need to
+// encapsulate nsSockTransport, nsSocketOutoutStream, and nsSocketInputStream.
+// These three objects only live in socket process, so we provide the necessary
+// interfaces in WebSocketConnectionBase and WebSocketConnectionListener for
+// reading/writing the real socket.
+//
+// The output path when WebSocketChannel wants to write data to socket:
+// - WebSocketConnectionParent::WriteOutputData
+// - WebSocketConnectionParent::SendWriteOutputData
+// - WebSocketConnectionChild::RecvWriteOutputData
+// - WebSocketConnection::WriteOutputData
+// - WebSocketConnection::OnOutputStreamReady (writes data to the real socket)
+//
+// The input path when data is able to read from the socket
+// - WebSocketConnection::OnInputStreamReady
+// - WebSocketConnectionChild::OnDataReceived
+// - WebSocketConnectionChild::SendOnDataReceived
+// - WebSocketConnectionParent::RecvOnDataReceived
+// - WebSocketChannel::OnDataReceived
+//
+// The path that WebSocketConnection is constructed.
+// - nsHttpChannel::OnStopRequest
+// - HttpConnectionMgrShell::CompleteUpgrade
+// - HttpConnectionMgrParent::CompleteUpgrade (we store the
+// nsIHttpUpgradeListener in a table and send an id to socket process)
+// - HttpConnectionMgrParent::SendStartWebSocketConnection
+// - HttpConnectionMgrChild::RecvStartWebSocketConnection (the listener id is
+// saved in WebSocketConnectionChild and will be used when the socket
+// transport is available)
+// - WebSocketConnectionChild::Init (an IPC channel between socket thread in
+// socket process and background thread in parent process is created)
+// - nsHttpConnectionMgr::CompleteUpgrade
+// - WebSocketConnectionChild::OnTransportAvailable (WebSocketConnection is
+// created)
+// - WebSocketConnectionChild::SendOnTransportAvailable
+// - WebSocketConnectionParent::RecvOnTransportAvailable
+// - WebSocketChannel::OnWebSocketConnectionAvailable
+
+class nsITransportSecurityInfo;
+
+namespace mozilla {
+namespace net {
+
+class WebSocketConnectionListener;
+
+class WebSocketConnectionBase : public nsISupports {
+ public:
+ virtual nsresult Init(WebSocketConnectionListener* aListener) = 0;
+ virtual void GetIoTarget(nsIEventTarget** aTarget) = 0;
+ virtual void Close() = 0;
+ virtual nsresult WriteOutputData(const uint8_t* aHdrBuf,
+ uint32_t aHdrBufLength,
+ const uint8_t* aPayloadBuf,
+ uint32_t aPayloadBufLength) = 0;
+ virtual nsresult StartReading() = 0;
+ virtual void DrainSocketData() = 0;
+ virtual nsresult GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) = 0;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketConnectionBase_h
diff --git a/netwerk/protocol/websocket/WebSocketConnectionChild.cpp b/netwerk/protocol/websocket/WebSocketConnectionChild.cpp
new file mode 100644
index 0000000000..045c3763b7
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnectionChild.cpp
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketLog.h"
+#include "WebSocketConnectionChild.h"
+
+#include "WebSocketConnection.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/net/SocketProcessBackgroundChild.h"
+#include "nsISerializable.h"
+#include "nsITLSSocketControl.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsNetCID.h"
+#include "nsSerializationHelper.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(WebSocketConnectionChild, nsIHttpUpgradeListener)
+
+WebSocketConnectionChild::WebSocketConnectionChild() {
+ LOG(("WebSocketConnectionChild ctor %p\n", this));
+}
+
+WebSocketConnectionChild::~WebSocketConnectionChild() {
+ LOG(("WebSocketConnectionChild dtor %p\n", this));
+}
+
+void WebSocketConnectionChild::Init(uint32_t aListenerId) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!mSocketThread) {
+ return;
+ }
+
+ ipc::Endpoint<PWebSocketConnectionParent> parentEndpoint;
+ ipc::Endpoint<PWebSocketConnectionChild> childEndpoint;
+ PWebSocketConnection::CreateEndpoints(&parentEndpoint, &childEndpoint);
+
+ if (NS_FAILED(SocketProcessBackgroundChild::WithActor(
+ "SendInitWebSocketConnection",
+ [aListenerId, endpoint = std::move(parentEndpoint)](
+ SocketProcessBackgroundChild* aActor) mutable {
+ Unused << aActor->SendInitWebSocketConnection(std::move(endpoint),
+ aListenerId);
+ }))) {
+ return;
+ }
+
+ mSocketThread->Dispatch(NS_NewRunnableFunction(
+ "BindWebSocketConnectionChild",
+ [self = RefPtr{this}, endpoint = std::move(childEndpoint)]() mutable {
+ endpoint.Bind(self);
+ }));
+}
+
+// nsIHttpUpgradeListener
+NS_IMETHODIMP
+WebSocketConnectionChild::OnTransportAvailable(
+ nsISocketTransport* aTransport, nsIAsyncInputStream* aSocketIn,
+ nsIAsyncOutputStream* aSocketOut) {
+ LOG(("WebSocketConnectionChild::OnTransportAvailable %p\n", this));
+ if (!OnSocketThread()) {
+ nsCOMPtr<nsISocketTransport> transport = aTransport;
+ nsCOMPtr<nsIAsyncInputStream> inputStream = aSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> outputStream = aSocketOut;
+ RefPtr<WebSocketConnectionChild> self = this;
+ return mSocketThread->Dispatch(
+ NS_NewRunnableFunction("WebSocketConnectionChild::OnTransportAvailable",
+ [self, transport, inputStream, outputStream]() {
+ Unused << self->OnTransportAvailable(
+ transport, inputStream, outputStream);
+ }),
+ NS_DISPATCH_NORMAL);
+ }
+
+ LOG(("WebSocketConnectionChild::OnTransportAvailable %p\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ MOZ_ASSERT(!mConnection, "already called");
+ MOZ_ASSERT(aTransport);
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ aTransport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl));
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(
+ do_QueryInterface(tlsSocketControl));
+
+ RefPtr<WebSocketConnection> connection =
+ new WebSocketConnection(aTransport, aSocketIn, aSocketOut);
+ nsresult rv = connection->Init(this);
+ if (NS_FAILED(rv)) {
+ Unused << OnUpgradeFailed(rv);
+ return NS_OK;
+ }
+
+ mConnection = std::move(connection);
+
+ Unused << SendOnTransportAvailable(securityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketConnectionChild::OnUpgradeFailed(nsresult aReason) {
+ if (!OnSocketThread()) {
+ return mSocketThread->Dispatch(NewRunnableMethod<nsresult>(
+ "WebSocketConnectionChild::OnUpgradeFailed", this,
+ &WebSocketConnectionChild::OnUpgradeFailed, aReason));
+ }
+
+ if (CanSend()) {
+ Unused << SendOnUpgradeFailed(aReason);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketConnectionChild::OnWebSocketConnectionAvailable(
+ WebSocketConnectionBase* aConnection) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionChild::RecvWriteOutputData(
+ nsTArray<uint8_t>&& aData) {
+ LOG(("WebSocketConnectionChild::RecvWriteOutputData %p\n", this));
+
+ if (!mConnection) {
+ OnError(NS_ERROR_NOT_AVAILABLE);
+ return IPC_OK();
+ }
+
+ mConnection->WriteOutputData(std::move(aData));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionChild::RecvStartReading() {
+ LOG(("WebSocketConnectionChild::RecvStartReading %p\n", this));
+
+ if (!mConnection) {
+ OnError(NS_ERROR_NOT_AVAILABLE);
+ return IPC_OK();
+ }
+
+ mConnection->StartReading();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionChild::RecvDrainSocketData() {
+ LOG(("WebSocketConnectionChild::RecvDrainSocketData %p\n", this));
+
+ if (!mConnection) {
+ OnError(NS_ERROR_NOT_AVAILABLE);
+ return IPC_OK();
+ }
+
+ mConnection->DrainSocketData();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionChild::Recv__delete__() {
+ LOG(("WebSocketConnectionChild::Recv__delete__ %p\n", this));
+
+ if (!mConnection) {
+ OnError(NS_ERROR_NOT_AVAILABLE);
+ return IPC_OK();
+ }
+
+ mConnection->Close();
+ mConnection = nullptr;
+ return IPC_OK();
+}
+
+void WebSocketConnectionChild::OnError(nsresult aStatus) {
+ LOG(("WebSocketConnectionChild::OnError %p\n", this));
+
+ if (CanSend()) {
+ Unused << SendOnError(aStatus);
+ }
+}
+
+void WebSocketConnectionChild::OnTCPClosed() {
+ LOG(("WebSocketConnectionChild::OnTCPClosed %p\n", this));
+
+ if (CanSend()) {
+ Unused << SendOnTCPClosed();
+ }
+}
+
+nsresult WebSocketConnectionChild::OnDataReceived(uint8_t* aData,
+ uint32_t aCount) {
+ LOG(("WebSocketConnectionChild::OnDataReceived %p\n", this));
+
+ if (CanSend()) {
+ nsTArray<uint8_t> data;
+ data.AppendElements(aData, aCount);
+ Unused << SendOnDataReceived(data);
+ }
+
+ return NS_OK;
+}
+
+void WebSocketConnectionChild::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("WebSocketConnectionChild::ActorDestroy %p\n", this));
+ if (mConnection) {
+ mConnection->Close();
+ mConnection = nullptr;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketConnectionChild.h b/netwerk/protocol/websocket/WebSocketConnectionChild.h
new file mode 100644
index 0000000000..a0f16b7fa8
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnectionChild.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketConnectionChild_h
+#define mozilla_net_WebSocketConnectionChild_h
+
+#include "mozilla/net/PWebSocketConnectionChild.h"
+#include "mozilla/net/WebSocketConnectionListener.h"
+#include "nsIHttpChannelInternal.h"
+
+namespace mozilla {
+namespace net {
+
+class WebSocketConnection;
+
+// WebSocketConnectionChild only lives in socket process and uses
+// WebSocketConnection to send/read data from socket. Only IPDL holds a strong
+// reference to WebSocketConnectionChild, so the life time of
+// WebSocketConnectionChild is bound to the IPC actor.
+
+class WebSocketConnectionChild final : public PWebSocketConnectionChild,
+ public nsIHttpUpgradeListener,
+ public WebSocketConnectionListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIHTTPUPGRADELISTENER
+
+ WebSocketConnectionChild();
+ void Init(uint32_t aListenerId);
+ mozilla::ipc::IPCResult RecvWriteOutputData(nsTArray<uint8_t>&& aData);
+ mozilla::ipc::IPCResult RecvStartReading();
+ mozilla::ipc::IPCResult RecvDrainSocketData();
+ mozilla::ipc::IPCResult Recv__delete__() override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ void OnError(nsresult aStatus) override;
+ void OnTCPClosed() override;
+ nsresult OnDataReceived(uint8_t* aData, uint32_t aCount) override;
+
+ private:
+ virtual ~WebSocketConnectionChild();
+
+ RefPtr<WebSocketConnection> mConnection;
+ nsCOMPtr<nsIEventTarget> mSocketThread;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketConnectionChild_h
diff --git a/netwerk/protocol/websocket/WebSocketConnectionListener.h b/netwerk/protocol/websocket/WebSocketConnectionListener.h
new file mode 100644
index 0000000000..31663f17f6
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnectionListener.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketConnectionListener_h
+#define mozilla_net_WebSocketConnectionListener_h
+
+namespace mozilla {
+namespace net {
+
+class WebSocketConnectionListener : public nsISupports {
+ public:
+ virtual void OnError(nsresult aStatus) = 0;
+ virtual void OnTCPClosed() = 0;
+ virtual nsresult OnDataReceived(uint8_t* aData, uint32_t aCount) = 0;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketConnectionListener_h
diff --git a/netwerk/protocol/websocket/WebSocketConnectionParent.cpp b/netwerk/protocol/websocket/WebSocketConnectionParent.cpp
new file mode 100644
index 0000000000..3fdf85337e
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnectionParent.cpp
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketLog.h"
+#include "WebSocketConnectionParent.h"
+
+#include "nsIHttpChannelInternal.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsSerializationHelper.h"
+#include "nsThreadUtils.h"
+#include "WebSocketConnectionListener.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS0(WebSocketConnectionParent)
+
+WebSocketConnectionParent::WebSocketConnectionParent(
+ nsIHttpUpgradeListener* aListener)
+ : mUpgradeListener(aListener),
+ mBackgroundThread(GetCurrentSerialEventTarget()) {
+ LOG(("WebSocketConnectionParent ctor %p\n", this));
+ MOZ_ASSERT(mUpgradeListener);
+}
+
+WebSocketConnectionParent::~WebSocketConnectionParent() {
+ LOG(("WebSocketConnectionParent dtor %p\n", this));
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionParent::RecvOnTransportAvailable(
+ nsITransportSecurityInfo* aSecurityInfo) {
+ LOG(("WebSocketConnectionParent::RecvOnTransportAvailable %p\n", this));
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ if (aSecurityInfo) {
+ MutexAutoLock lock(mMutex);
+ mSecurityInfo = aSecurityInfo;
+ }
+
+ if (mUpgradeListener) {
+ Unused << mUpgradeListener->OnWebSocketConnectionAvailable(this);
+ mUpgradeListener = nullptr;
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionParent::RecvOnError(
+ const nsresult& aStatus) {
+ LOG(("WebSocketConnectionParent::RecvOnError %p\n", this));
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ MOZ_ASSERT(mListener);
+ mListener->OnError(aStatus);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionParent::RecvOnUpgradeFailed(
+ const nsresult& aReason) {
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ if (mUpgradeListener) {
+ Unused << mUpgradeListener->OnUpgradeFailed(aReason);
+ mUpgradeListener = nullptr;
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionParent::RecvOnTCPClosed() {
+ LOG(("WebSocketConnectionParent::RecvOnTCPClosed %p\n", this));
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ MOZ_ASSERT(mListener);
+ mListener->OnTCPClosed();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketConnectionParent::RecvOnDataReceived(
+ nsTArray<uint8_t>&& aData) {
+ LOG(("WebSocketConnectionParent::RecvOnDataReceived %p\n", this));
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ MOZ_ASSERT(mListener);
+ uint8_t* buffer = const_cast<uint8_t*>(aData.Elements());
+ nsresult rv = mListener->OnDataReceived(buffer, aData.Length());
+ if (NS_FAILED(rv)) {
+ mListener->OnError(rv);
+ }
+
+ return IPC_OK();
+}
+
+void WebSocketConnectionParent::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("WebSocketConnectionParent::ActorDestroy %p aWhy=%d\n", this, aWhy));
+ if (!mClosed) {
+ // Treat this as an error when IPC is closed before
+ // WebSocketConnectionParent::Close() is called.
+ RefPtr<WebSocketConnectionListener> listener;
+ listener.swap(mListener);
+ if (listener) {
+ listener->OnError(NS_ERROR_FAILURE);
+ }
+ }
+ mBackgroundThread->Dispatch(NS_NewRunnableFunction(
+ "WebSocketConnectionParent::DefereredDestroy", [self = RefPtr{this}]() {
+ LOG(("WebSocketConnectionParent::DefereredDestroy"));
+ }));
+};
+
+nsresult WebSocketConnectionParent::Init(
+ WebSocketConnectionListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mListener = aListener;
+ return NS_OK;
+}
+
+void WebSocketConnectionParent::GetIoTarget(nsIEventTarget** aTarget) {
+ nsCOMPtr<nsIEventTarget> target = mBackgroundThread;
+ return target.forget(aTarget);
+}
+
+void WebSocketConnectionParent::Close() {
+ LOG(("WebSocketConnectionParent::Close %p\n", this));
+
+ mClosed = true;
+
+ auto task = [self = RefPtr{this}]() {
+ self->PWebSocketConnectionParent::Close();
+ };
+
+ if (mBackgroundThread->IsOnCurrentThread()) {
+ task();
+ } else {
+ mBackgroundThread->Dispatch(NS_NewRunnableFunction(
+ "WebSocketConnectionParent::Close", std::move(task)));
+ }
+}
+
+nsresult WebSocketConnectionParent::WriteOutputData(
+ const uint8_t* aHdrBuf, uint32_t aHdrBufLength, const uint8_t* aPayloadBuf,
+ uint32_t aPayloadBufLength) {
+ LOG(("WebSocketConnectionParent::WriteOutputData %p", this));
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsTArray<uint8_t> data;
+ data.AppendElements(aHdrBuf, aHdrBufLength);
+ data.AppendElements(aPayloadBuf, aPayloadBufLength);
+ return SendWriteOutputData(data) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult WebSocketConnectionParent::StartReading() {
+ LOG(("WebSocketConnectionParent::StartReading %p\n", this));
+
+ RefPtr<WebSocketConnectionParent> self = this;
+ auto task = [self{std::move(self)}]() {
+ if (!self->CanSend()) {
+ if (self->mListener) {
+ self->mListener->OnError(NS_ERROR_NOT_AVAILABLE);
+ }
+ return;
+ }
+
+ Unused << self->SendStartReading();
+ };
+
+ if (mBackgroundThread->IsOnCurrentThread()) {
+ task();
+ } else {
+ mBackgroundThread->Dispatch(NS_NewRunnableFunction(
+ "WebSocketConnectionParent::SendStartReading", std::move(task)));
+ }
+
+ return NS_OK;
+}
+
+void WebSocketConnectionParent::DrainSocketData() {
+ LOG(("WebSocketConnectionParent::DrainSocketData %p\n", this));
+ MOZ_ASSERT(mBackgroundThread->IsOnCurrentThread());
+
+ if (!CanSend()) {
+ MOZ_ASSERT(mListener);
+ mListener->OnError(NS_ERROR_NOT_AVAILABLE);
+
+ return;
+ }
+
+ Unused << SendDrainSocketData();
+}
+
+nsresult WebSocketConnectionParent::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ LOG(("WebSocketConnectionParent::GetSecurityInfo() %p\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_ARG_POINTER(aSecurityInfo);
+
+ MutexAutoLock lock(mMutex);
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketConnectionParent.h b/netwerk/protocol/websocket/WebSocketConnectionParent.h
new file mode 100644
index 0000000000..45b031e9cb
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketConnectionParent.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketConnectionParent_h
+#define mozilla_net_WebSocketConnectionParent_h
+
+#include "mozilla/net/PWebSocketConnectionParent.h"
+#include "mozilla/net/WebSocketConnectionBase.h"
+#include "nsISupportsImpl.h"
+#include "WebSocketConnectionBase.h"
+
+class nsIHttpUpgradeListener;
+
+namespace mozilla {
+namespace net {
+
+class WebSocketConnectionListener;
+
+// WebSocketConnectionParent implements WebSocketConnectionBase and provides
+// interface for WebSocketChannel to send/receive data. The ownership model for
+// WebSocketConnectionParent is that IPDL and WebSocketChannel hold strong
+// reference of this object. When Close() is called, a __delete__ message will
+// be sent and the IPC actor will be deallocated as well.
+
+class WebSocketConnectionParent final : public PWebSocketConnectionParent,
+ public WebSocketConnectionBase {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit WebSocketConnectionParent(nsIHttpUpgradeListener* aListener);
+
+ mozilla::ipc::IPCResult RecvOnTransportAvailable(
+ nsITransportSecurityInfo* aSecurityInfo);
+ mozilla::ipc::IPCResult RecvOnError(const nsresult& aStatus);
+ mozilla::ipc::IPCResult RecvOnTCPClosed();
+ mozilla::ipc::IPCResult RecvOnDataReceived(nsTArray<uint8_t>&& aData);
+ mozilla::ipc::IPCResult RecvOnUpgradeFailed(const nsresult& aReason);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ nsresult Init(WebSocketConnectionListener* aListener) override;
+ void GetIoTarget(nsIEventTarget** aTarget) override;
+ void Close() override;
+ nsresult WriteOutputData(const uint8_t* aHdrBuf, uint32_t aHdrBufLength,
+ const uint8_t* aPayloadBuf,
+ uint32_t aPayloadBufLength) override;
+ nsresult StartReading() override;
+ void DrainSocketData() override;
+ nsresult GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
+
+ private:
+ virtual ~WebSocketConnectionParent();
+
+ nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener;
+ RefPtr<WebSocketConnectionListener> mListener;
+ nsCOMPtr<nsISerialEventTarget> mBackgroundThread;
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+ Atomic<bool> mClosed{false};
+ Mutex mMutex MOZ_UNANNOTATED{"WebSocketConnectionParent::mMutex"};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketConnectionParent_h
diff --git a/netwerk/protocol/websocket/WebSocketEventListenerChild.cpp b/netwerk/protocol/websocket/WebSocketEventListenerChild.cpp
new file mode 100644
index 0000000000..2232475382
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerChild.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketEventListenerChild.h"
+
+#include "WebSocketEventService.h"
+#include "WebSocketFrame.h"
+
+namespace mozilla {
+namespace net {
+
+WebSocketEventListenerChild::WebSocketEventListenerChild(
+ uint64_t aInnerWindowID, nsISerialEventTarget* aTarget)
+ : NeckoTargetHolder(aTarget),
+ mService(WebSocketEventService::GetOrCreate()),
+ mInnerWindowID(aInnerWindowID) {}
+
+WebSocketEventListenerChild::~WebSocketEventListenerChild() {
+ MOZ_ASSERT(!mService);
+}
+
+mozilla::ipc::IPCResult WebSocketEventListenerChild::RecvWebSocketCreated(
+ const uint32_t& aWebSocketSerialID, const nsString& aURI,
+ const nsCString& aProtocols) {
+ if (mService) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ mService->WebSocketCreated(aWebSocketSerialID, mInnerWindowID, aURI,
+ aProtocols, target);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketEventListenerChild::RecvWebSocketOpened(
+ const uint32_t& aWebSocketSerialID, const nsString& aEffectiveURI,
+ const nsCString& aProtocols, const nsCString& aExtensions,
+ const uint64_t& aHttpChannelId) {
+ if (mService) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ mService->WebSocketOpened(aWebSocketSerialID, mInnerWindowID, aEffectiveURI,
+ aProtocols, aExtensions, aHttpChannelId, target);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+WebSocketEventListenerChild::RecvWebSocketMessageAvailable(
+ const uint32_t& aWebSocketSerialID, const nsCString& aData,
+ const uint16_t& aMessageType) {
+ if (mService) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ mService->WebSocketMessageAvailable(aWebSocketSerialID, mInnerWindowID,
+ aData, aMessageType, target);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketEventListenerChild::RecvWebSocketClosed(
+ const uint32_t& aWebSocketSerialID, const bool& aWasClean,
+ const uint16_t& aCode, const nsString& aReason) {
+ if (mService) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ mService->WebSocketClosed(aWebSocketSerialID, mInnerWindowID, aWasClean,
+ aCode, aReason, target);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketEventListenerChild::RecvFrameReceived(
+ const uint32_t& aWebSocketSerialID, const WebSocketFrameData& aFrameData) {
+ if (mService) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ RefPtr<WebSocketFrame> frame = new WebSocketFrame(aFrameData);
+ mService->FrameReceived(aWebSocketSerialID, mInnerWindowID, frame.forget(),
+ target);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebSocketEventListenerChild::RecvFrameSent(
+ const uint32_t& aWebSocketSerialID, const WebSocketFrameData& aFrameData) {
+ if (mService) {
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ RefPtr<WebSocketFrame> frame = new WebSocketFrame(aFrameData);
+ mService->FrameSent(aWebSocketSerialID, mInnerWindowID, frame.forget(),
+ target);
+ }
+
+ return IPC_OK();
+}
+
+void WebSocketEventListenerChild::Close() {
+ mService = nullptr;
+ SendClose();
+}
+
+void WebSocketEventListenerChild::ActorDestroy(ActorDestroyReason aWhy) {
+ mService = nullptr;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketEventListenerChild.h b/netwerk/protocol/websocket/WebSocketEventListenerChild.h
new file mode 100644
index 0000000000..88dddb79a5
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerChild.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketEventListenerChild_h
+#define mozilla_net_WebSocketEventListenerChild_h
+
+#include "mozilla/net/NeckoTargetHolder.h"
+#include "mozilla/net/PWebSocketEventListenerChild.h"
+
+namespace mozilla {
+namespace net {
+
+class WebSocketEventService;
+
+class WebSocketEventListenerChild final : public PWebSocketEventListenerChild,
+ public NeckoTargetHolder {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(WebSocketEventListenerChild)
+
+ explicit WebSocketEventListenerChild(uint64_t aInnerWindowID,
+ nsISerialEventTarget* aTarget);
+
+ mozilla::ipc::IPCResult RecvWebSocketCreated(
+ const uint32_t& aWebSocketSerialID, const nsString& aURI,
+ const nsCString& aProtocols);
+
+ mozilla::ipc::IPCResult RecvWebSocketOpened(
+ const uint32_t& aWebSocketSerialID, const nsString& aEffectiveURI,
+ const nsCString& aProtocols, const nsCString& aExtensions,
+ const uint64_t& aHttpChannelId);
+
+ mozilla::ipc::IPCResult RecvWebSocketMessageAvailable(
+ const uint32_t& aWebSocketSerialID, const nsCString& aData,
+ const uint16_t& aMessageType);
+
+ mozilla::ipc::IPCResult RecvWebSocketClosed(
+ const uint32_t& aWebSocketSerialID, const bool& aWasClean,
+ const uint16_t& aCode, const nsString& aReason);
+
+ mozilla::ipc::IPCResult RecvFrameReceived(
+ const uint32_t& aWebSocketSerialID, const WebSocketFrameData& aFrameData);
+
+ mozilla::ipc::IPCResult RecvFrameSent(const uint32_t& aWebSocketSerialID,
+ const WebSocketFrameData& aFrameData);
+
+ void Close();
+
+ private:
+ ~WebSocketEventListenerChild();
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ RefPtr<WebSocketEventService> mService;
+ uint64_t mInnerWindowID;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketEventListenerChild_h
diff --git a/netwerk/protocol/websocket/WebSocketEventListenerParent.cpp b/netwerk/protocol/websocket/WebSocketEventListenerParent.cpp
new file mode 100644
index 0000000000..5b0ea16838
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerParent.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketEventService.h"
+#include "WebSocketEventListenerParent.h"
+#include "mozilla/Unused.h"
+#include "WebSocketFrame.h"
+
+namespace mozilla {
+namespace net {
+
+NS_INTERFACE_MAP_BEGIN(WebSocketEventListenerParent)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsIWebSocketEventListener)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(WebSocketEventListenerParent)
+NS_IMPL_RELEASE(WebSocketEventListenerParent)
+
+WebSocketEventListenerParent::WebSocketEventListenerParent(
+ uint64_t aInnerWindowID)
+ : mService(WebSocketEventService::GetOrCreate()),
+ mInnerWindowID(aInnerWindowID) {
+ DebugOnly<nsresult> rv = mService->AddListener(mInnerWindowID, this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+WebSocketEventListenerParent::~WebSocketEventListenerParent() {
+ MOZ_ASSERT(!mService);
+}
+
+mozilla::ipc::IPCResult WebSocketEventListenerParent::RecvClose() {
+ if (mService) {
+ UnregisterListener();
+ Unused << Send__delete__(this);
+ }
+
+ return IPC_OK();
+}
+
+void WebSocketEventListenerParent::ActorDestroy(ActorDestroyReason aWhy) {
+ UnregisterListener();
+}
+
+void WebSocketEventListenerParent::UnregisterListener() {
+ if (mService) {
+ DebugOnly<nsresult> rv = mService->RemoveListener(mInnerWindowID, this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mService = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::WebSocketCreated(uint32_t aWebSocketSerialID,
+ const nsAString& aURI,
+ const nsACString& aProtocols) {
+ Unused << SendWebSocketCreated(aWebSocketSerialID, aURI, aProtocols);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::WebSocketOpened(uint32_t aWebSocketSerialID,
+ const nsAString& aEffectiveURI,
+ const nsACString& aProtocols,
+ const nsACString& aExtensions,
+ uint64_t aHttpChannelId) {
+ Unused << SendWebSocketOpened(aWebSocketSerialID, aEffectiveURI, aProtocols,
+ aExtensions, aHttpChannelId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::WebSocketClosed(uint32_t aWebSocketSerialID,
+ bool aWasClean, uint16_t aCode,
+ const nsAString& aReason) {
+ Unused << SendWebSocketClosed(aWebSocketSerialID, aWasClean, aCode, aReason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::WebSocketMessageAvailable(
+ uint32_t aWebSocketSerialID, const nsACString& aData,
+ uint16_t aMessageType) {
+ Unused << SendWebSocketMessageAvailable(aWebSocketSerialID, aData,
+ aMessageType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::FrameReceived(uint32_t aWebSocketSerialID,
+ nsIWebSocketFrame* aFrame) {
+ if (!aFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WebSocketFrame* frame = static_cast<WebSocketFrame*>(aFrame);
+ Unused << SendFrameReceived(aWebSocketSerialID, frame->Data());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::FrameSent(uint32_t aWebSocketSerialID,
+ nsIWebSocketFrame* aFrame) {
+ if (!aFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WebSocketFrame* frame = static_cast<WebSocketFrame*>(aFrame);
+ Unused << SendFrameSent(aWebSocketSerialID, frame->Data());
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketEventListenerParent.h b/netwerk/protocol/websocket/WebSocketEventListenerParent.h
new file mode 100644
index 0000000000..eb5e02679b
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerParent.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketEventListenerParent_h
+#define mozilla_net_WebSocketEventListenerParent_h
+
+#include "mozilla/net/PWebSocketEventListenerParent.h"
+#include "nsIWebSocketEventService.h"
+
+namespace mozilla {
+namespace net {
+
+class WebSocketEventService;
+
+class WebSocketEventListenerParent final : public PWebSocketEventListenerParent,
+ public nsIWebSocketEventListener {
+ friend class PWebSocketEventListenerParent;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBSOCKETEVENTLISTENER
+
+ explicit WebSocketEventListenerParent(uint64_t aInnerWindowID);
+
+ private:
+ ~WebSocketEventListenerParent();
+
+ mozilla::ipc::IPCResult RecvClose();
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ void UnregisterListener();
+
+ RefPtr<WebSocketEventService> mService;
+ uint64_t mInnerWindowID;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketEventListenerParent_h
diff --git a/netwerk/protocol/websocket/WebSocketEventService.cpp b/netwerk/protocol/websocket/WebSocketEventService.cpp
new file mode 100644
index 0000000000..6e9a89a005
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventService.cpp
@@ -0,0 +1,566 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketEventListenerChild.h"
+#include "WebSocketEventService.h"
+#include "WebSocketFrame.h"
+
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/StaticPtr.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIObserverService.h"
+#include "nsXULAppAPI.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Services.h"
+#include "nsIWebSocketImpl.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+StaticRefPtr<WebSocketEventService> gWebSocketEventService;
+
+bool IsChildProcess() {
+ return XRE_GetProcessType() != GeckoProcessType_Default;
+}
+
+} // anonymous namespace
+
+class WebSocketBaseRunnable : public Runnable {
+ public:
+ WebSocketBaseRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID)
+ : Runnable("net::WebSocketBaseRunnable"),
+ mWebSocketSerialID(aWebSocketSerialID),
+ mInnerWindowID(aInnerWindowID) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<WebSocketEventService> service =
+ WebSocketEventService::GetOrCreate();
+ MOZ_ASSERT(service);
+
+ WebSocketEventService::WindowListeners listeners;
+ service->GetListeners(mInnerWindowID, listeners);
+
+ for (uint32_t i = 0; i < listeners.Length(); ++i) {
+ DoWork(listeners[i]);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ ~WebSocketBaseRunnable() = default;
+
+ virtual void DoWork(nsIWebSocketEventListener* aListener) = 0;
+
+ uint32_t mWebSocketSerialID;
+ uint64_t mInnerWindowID;
+};
+
+class WebSocketFrameRunnable final : public WebSocketBaseRunnable {
+ public:
+ WebSocketFrameRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame,
+ bool aFrameSent)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID),
+ mFrame(std::move(aFrame)),
+ mFrameSent(aFrameSent) {}
+
+ private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override {
+ DebugOnly<nsresult> rv{};
+ if (mFrameSent) {
+ rv = aListener->FrameSent(mWebSocketSerialID, mFrame);
+ } else {
+ rv = aListener->FrameReceived(mWebSocketSerialID, mFrame);
+ }
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Frame op failed");
+ }
+
+ RefPtr<WebSocketFrame> mFrame;
+ bool mFrameSent;
+};
+
+class WebSocketCreatedRunnable final : public WebSocketBaseRunnable {
+ public:
+ WebSocketCreatedRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ const nsAString& aURI, const nsACString& aProtocols)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID),
+ mURI(aURI),
+ mProtocols(aProtocols) {}
+
+ private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override {
+ DebugOnly<nsresult> rv =
+ aListener->WebSocketCreated(mWebSocketSerialID, mURI, mProtocols);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketCreated failed");
+ }
+
+ const nsString mURI;
+ const nsCString mProtocols;
+};
+
+class WebSocketOpenedRunnable final : public WebSocketBaseRunnable {
+ public:
+ WebSocketOpenedRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ const nsAString& aEffectiveURI,
+ const nsACString& aProtocols,
+ const nsACString& aExtensions,
+ uint64_t aHttpChannelId)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID),
+ mEffectiveURI(aEffectiveURI),
+ mProtocols(aProtocols),
+ mExtensions(aExtensions),
+ mHttpChannelId(aHttpChannelId) {}
+
+ private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override {
+ DebugOnly<nsresult> rv =
+ aListener->WebSocketOpened(mWebSocketSerialID, mEffectiveURI,
+ mProtocols, mExtensions, mHttpChannelId);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketOpened failed");
+ }
+
+ const nsString mEffectiveURI;
+ const nsCString mProtocols;
+ const nsCString mExtensions;
+ uint64_t mHttpChannelId;
+};
+
+class WebSocketMessageAvailableRunnable final : public WebSocketBaseRunnable {
+ public:
+ WebSocketMessageAvailableRunnable(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsACString& aData,
+ uint16_t aMessageType)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID),
+ mData(aData),
+ mMessageType(aMessageType) {}
+
+ private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override {
+ DebugOnly<nsresult> rv = aListener->WebSocketMessageAvailable(
+ mWebSocketSerialID, mData, mMessageType);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketMessageAvailable failed");
+ }
+
+ const nsCString mData;
+ uint16_t mMessageType;
+};
+
+class WebSocketClosedRunnable final : public WebSocketBaseRunnable {
+ public:
+ WebSocketClosedRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ bool aWasClean, uint16_t aCode,
+ const nsAString& aReason)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID),
+ mWasClean(aWasClean),
+ mCode(aCode),
+ mReason(aReason) {}
+
+ private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override {
+ DebugOnly<nsresult> rv = aListener->WebSocketClosed(
+ mWebSocketSerialID, mWasClean, mCode, mReason);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketClosed failed");
+ }
+
+ bool mWasClean;
+ uint16_t mCode;
+ const nsString mReason;
+};
+
+/* static */
+already_AddRefed<WebSocketEventService> WebSocketEventService::Get() {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<WebSocketEventService> service = gWebSocketEventService.get();
+ return service.forget();
+}
+
+/* static */
+already_AddRefed<WebSocketEventService> WebSocketEventService::GetOrCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gWebSocketEventService) {
+ gWebSocketEventService = new WebSocketEventService();
+ }
+
+ RefPtr<WebSocketEventService> service = gWebSocketEventService.get();
+ return service.forget();
+}
+
+NS_INTERFACE_MAP_BEGIN(WebSocketEventService)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketEventService)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIWebSocketEventService)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(WebSocketEventService)
+NS_IMPL_RELEASE(WebSocketEventService)
+
+WebSocketEventService::WebSocketEventService() : mCountListeners(0) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "xpcom-shutdown", false);
+ obs->AddObserver(this, "inner-window-destroyed", false);
+ }
+}
+
+WebSocketEventService::~WebSocketEventService() {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+void WebSocketEventService::WebSocketCreated(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsAString& aURI,
+ const nsACString& aProtocols,
+ nsIEventTarget* aTarget) {
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketCreatedRunnable> runnable = new WebSocketCreatedRunnable(
+ aWebSocketSerialID, aInnerWindowID, aURI, aProtocols);
+ DebugOnly<nsresult> rv = aTarget
+ ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)
+ : NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void WebSocketEventService::WebSocketOpened(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsAString& aEffectiveURI,
+ const nsACString& aProtocols,
+ const nsACString& aExtensions,
+ uint64_t aHttpChannelId,
+ nsIEventTarget* aTarget) {
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketOpenedRunnable> runnable = new WebSocketOpenedRunnable(
+ aWebSocketSerialID, aInnerWindowID, aEffectiveURI, aProtocols,
+ aExtensions, aHttpChannelId);
+ DebugOnly<nsresult> rv = aTarget
+ ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)
+ : NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void WebSocketEventService::WebSocketMessageAvailable(
+ uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ const nsACString& aData, uint16_t aMessageType, nsIEventTarget* aTarget) {
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketMessageAvailableRunnable> runnable =
+ new WebSocketMessageAvailableRunnable(aWebSocketSerialID, aInnerWindowID,
+ aData, aMessageType);
+ DebugOnly<nsresult> rv = aTarget
+ ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)
+ : NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void WebSocketEventService::WebSocketClosed(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ bool aWasClean, uint16_t aCode,
+ const nsAString& aReason,
+ nsIEventTarget* aTarget) {
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketClosedRunnable> runnable = new WebSocketClosedRunnable(
+ aWebSocketSerialID, aInnerWindowID, aWasClean, aCode, aReason);
+ DebugOnly<nsresult> rv = aTarget
+ ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)
+ : NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void WebSocketEventService::FrameReceived(
+ uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame, nsIEventTarget* aTarget) {
+ RefPtr<WebSocketFrame> frame(std::move(aFrame));
+ MOZ_ASSERT(frame);
+
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketFrameRunnable> runnable =
+ new WebSocketFrameRunnable(aWebSocketSerialID, aInnerWindowID,
+ frame.forget(), false /* frameSent */);
+ DebugOnly<nsresult> rv = aTarget
+ ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)
+ : NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void WebSocketEventService::FrameSent(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame,
+ nsIEventTarget* aTarget) {
+ RefPtr<WebSocketFrame> frame(std::move(aFrame));
+ MOZ_ASSERT(frame);
+
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketFrameRunnable> runnable = new WebSocketFrameRunnable(
+ aWebSocketSerialID, aInnerWindowID, frame.forget(), true /* frameSent */);
+
+ DebugOnly<nsresult> rv = aTarget
+ ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)
+ : NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void WebSocketEventService::AssociateWebSocketImplWithSerialID(
+ nsIWebSocketImpl* aWebSocketImpl, uint32_t aWebSocketSerialID) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mWebSocketImplMap.InsertOrUpdate(aWebSocketSerialID,
+ do_GetWeakReference(aWebSocketImpl));
+}
+
+NS_IMETHODIMP
+WebSocketEventService::SendMessage(uint32_t aWebSocketSerialID,
+ const nsAString& aMessage) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsWeakPtr weakPtr = mWebSocketImplMap.Get(aWebSocketSerialID);
+ nsCOMPtr<nsIWebSocketImpl> webSocketImpl = do_QueryReferent(weakPtr);
+
+ if (!webSocketImpl) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return webSocketImpl->SendMessage(aMessage);
+}
+
+NS_IMETHODIMP
+WebSocketEventService::AddListener(uint64_t aInnerWindowID,
+ nsIWebSocketEventListener* aListener) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aListener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ++mCountListeners;
+
+ mWindows
+ .LookupOrInsertWith(
+ aInnerWindowID,
+ [&] {
+ auto listener = MakeUnique<WindowListener>();
+
+ if (IsChildProcess()) {
+ PWebSocketEventListenerChild* actor =
+ gNeckoChild->SendPWebSocketEventListenerConstructor(
+ aInnerWindowID);
+
+ listener->mActor =
+ static_cast<WebSocketEventListenerChild*>(actor);
+ MOZ_ASSERT(listener->mActor);
+ }
+
+ return listener;
+ })
+ ->mListeners.AppendElement(aListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventService::RemoveListener(uint64_t aInnerWindowID,
+ nsIWebSocketEventListener* aListener) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aListener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WindowListener* listener = mWindows.Get(aInnerWindowID);
+ if (!listener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!listener->mListeners.RemoveElement(aListener)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // The last listener for this window.
+ if (listener->mListeners.IsEmpty()) {
+ if (IsChildProcess()) {
+ ShutdownActorListener(listener);
+ }
+
+ mWindows.Remove(aInnerWindowID);
+ }
+
+ MOZ_ASSERT(mCountListeners);
+ --mCountListeners;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventService::HasListenerFor(uint64_t aInnerWindowID, bool* aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ *aResult = mWindows.Get(aInnerWindowID);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp(aTopic, "xpcom-shutdown")) {
+ Shutdown();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "inner-window-destroyed") && HasListeners()) {
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+ NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+
+ uint64_t innerID;
+ nsresult rv = wrapper->GetData(&innerID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ WindowListener* listener = mWindows.Get(innerID);
+ if (!listener) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mCountListeners >= listener->mListeners.Length());
+ mCountListeners -= listener->mListeners.Length();
+
+ if (IsChildProcess()) {
+ ShutdownActorListener(listener);
+ }
+
+ mWindows.Remove(innerID);
+ }
+
+ // This should not happen.
+ return NS_ERROR_FAILURE;
+}
+
+void WebSocketEventService::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (gWebSocketEventService) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(gWebSocketEventService, "xpcom-shutdown");
+ obs->RemoveObserver(gWebSocketEventService, "inner-window-destroyed");
+ }
+
+ mWindows.Clear();
+ gWebSocketEventService = nullptr;
+ }
+}
+
+bool WebSocketEventService::HasListeners() const { return !!mCountListeners; }
+
+void WebSocketEventService::GetListeners(
+ uint64_t aInnerWindowID,
+ WebSocketEventService::WindowListeners& aListeners) const {
+ aListeners.Clear();
+
+ WindowListener* listener = mWindows.Get(aInnerWindowID);
+ if (!listener) {
+ return;
+ }
+
+ aListeners.AppendElements(listener->mListeners);
+}
+
+void WebSocketEventService::ShutdownActorListener(WindowListener* aListener) {
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(aListener->mActor);
+ aListener->mActor->Close();
+ aListener->mActor = nullptr;
+}
+
+already_AddRefed<WebSocketFrame> WebSocketEventService::CreateFrameIfNeeded(
+ bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3, uint8_t aOpCode,
+ bool aMaskBit, uint32_t aMask, const nsCString& aPayload) {
+ if (!HasListeners()) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<WebSocketFrame>(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3,
+ aOpCode, aMaskBit, aMask, aPayload);
+}
+
+already_AddRefed<WebSocketFrame> WebSocketEventService::CreateFrameIfNeeded(
+ bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3, uint8_t aOpCode,
+ bool aMaskBit, uint32_t aMask, uint8_t* aPayload, uint32_t aPayloadLength) {
+ if (!HasListeners()) {
+ return nullptr;
+ }
+
+ nsAutoCString payloadStr;
+ if (NS_WARN_IF(!(payloadStr.Assign((const char*)aPayload, aPayloadLength,
+ mozilla::fallible)))) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<WebSocketFrame>(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3,
+ aOpCode, aMaskBit, aMask, payloadStr);
+}
+
+already_AddRefed<WebSocketFrame> WebSocketEventService::CreateFrameIfNeeded(
+ bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3, uint8_t aOpCode,
+ bool aMaskBit, uint32_t aMask, uint8_t* aPayloadInHdr,
+ uint32_t aPayloadInHdrLength, uint8_t* aPayload, uint32_t aPayloadLength) {
+ if (!HasListeners()) {
+ return nullptr;
+ }
+
+ uint32_t payloadLength = aPayloadLength + aPayloadInHdrLength;
+
+ nsAutoCString payload;
+ if (NS_WARN_IF(!payload.SetLength(payloadLength, fallible))) {
+ return nullptr;
+ }
+
+ char* payloadPtr = payload.BeginWriting();
+ if (aPayloadInHdrLength) {
+ memcpy(payloadPtr, aPayloadInHdr, aPayloadInHdrLength);
+ }
+
+ memcpy(payloadPtr + aPayloadInHdrLength, aPayload, aPayloadLength);
+
+ return MakeAndAddRef<WebSocketFrame>(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3,
+ aOpCode, aMaskBit, aMask, payload);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketEventService.h b/netwerk/protocol/websocket/WebSocketEventService.h
new file mode 100644
index 0000000000..0f15e5058b
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventService.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketEventService_h
+#define mozilla_net_WebSocketEventService_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Atomics.h"
+#include "nsIWebSocketEventService.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsIObserver.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsTHashMap.h"
+
+class nsIWebSocketImpl;
+class nsIEventTarget;
+
+namespace mozilla {
+namespace net {
+
+class WebSocketFrame;
+class WebSocketEventListenerChild;
+
+class WebSocketEventService final : public nsIWebSocketEventService,
+ public nsIObserver {
+ friend class WebSocketBaseRunnable;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIWEBSOCKETEVENTSERVICE
+
+ static already_AddRefed<WebSocketEventService> Get();
+ static already_AddRefed<WebSocketEventService> GetOrCreate();
+
+ void WebSocketCreated(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ const nsAString& aURI, const nsACString& aProtocols,
+ nsIEventTarget* aTarget = nullptr);
+
+ void WebSocketOpened(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ const nsAString& aEffectiveURI,
+ const nsACString& aProtocols,
+ const nsACString& aExtensions, uint64_t aHttpChannelId,
+ nsIEventTarget* aTarget = nullptr);
+
+ void WebSocketMessageAvailable(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsACString& aData, uint16_t aMessageType,
+ nsIEventTarget* aTarget = nullptr);
+
+ void WebSocketClosed(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ bool aWasClean, uint16_t aCode, const nsAString& aReason,
+ nsIEventTarget* aTarget = nullptr);
+
+ void FrameReceived(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame,
+ nsIEventTarget* aTarget = nullptr);
+
+ void FrameSent(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame,
+ nsIEventTarget* aTarget = nullptr);
+
+ void AssociateWebSocketImplWithSerialID(nsIWebSocketImpl* aWebSocketImpl,
+ uint32_t aWebSocketSerialID);
+
+ already_AddRefed<WebSocketFrame> CreateFrameIfNeeded(
+ bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
+ const nsCString& aPayload);
+
+ already_AddRefed<WebSocketFrame> CreateFrameIfNeeded(
+ bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask, uint8_t* aPayload,
+ uint32_t aPayloadLength);
+
+ already_AddRefed<WebSocketFrame> CreateFrameIfNeeded(
+ bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask, uint8_t* aPayloadInHdr,
+ uint32_t aPayloadInHdrLength, uint8_t* aPayload, uint32_t aPayloadLength);
+
+ private:
+ WebSocketEventService();
+ ~WebSocketEventService();
+
+ bool HasListeners() const;
+ void Shutdown();
+
+ using WindowListeners = nsTArray<nsCOMPtr<nsIWebSocketEventListener>>;
+
+ nsTHashMap<nsUint32HashKey, nsWeakPtr> mWebSocketImplMap;
+
+ struct WindowListener {
+ WindowListeners mListeners;
+ RefPtr<WebSocketEventListenerChild> mActor;
+ };
+
+ void GetListeners(uint64_t aInnerWindowID, WindowListeners& aListeners) const;
+
+ void ShutdownActorListener(WindowListener* aListener);
+
+ // Used only on the main-thread.
+ nsClassHashtable<nsUint64HashKey, WindowListener> mWindows;
+
+ Atomic<uint64_t> mCountListeners;
+};
+
+} // namespace net
+} // namespace mozilla
+
+/**
+ * Casting WebSocketEventService to nsISupports is ambiguous.
+ * This method handles that.
+ */
+inline nsISupports* ToSupports(mozilla::net::WebSocketEventService* p) {
+ return NS_ISUPPORTS_CAST(nsIWebSocketEventService*, p);
+}
+
+#endif // mozilla_net_WebSocketEventService_h
diff --git a/netwerk/protocol/websocket/WebSocketFrame.cpp b/netwerk/protocol/websocket/WebSocketFrame.cpp
new file mode 100644
index 0000000000..745b598ba9
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrame.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketFrame.h"
+
+#include "ErrorList.h"
+#include "MainThreadUtils.h"
+#include "chrome/common/ipc_message_utils.h"
+#include "mozilla/Assertions.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsSocketTransportService2.h"
+#include "nscore.h"
+#include "prtime.h"
+
+namespace mozilla {
+namespace net {
+
+NS_INTERFACE_MAP_BEGIN(WebSocketFrame)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketFrame)
+ NS_INTERFACE_MAP_ENTRY(nsIWebSocketFrame)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(WebSocketFrame)
+NS_IMPL_RELEASE(WebSocketFrame)
+
+WebSocketFrame::WebSocketFrame(const WebSocketFrameData& aData)
+ : mData(aData) {}
+
+WebSocketFrame::WebSocketFrame(bool aFinBit, bool aRsvBit1, bool aRsvBit2,
+ bool aRsvBit3, uint8_t aOpCode, bool aMaskBit,
+ uint32_t aMask, const nsCString& aPayload)
+ : mData(PR_Now(), aFinBit, aRsvBit1, aRsvBit2, aRsvBit3, aOpCode, aMaskBit,
+ aMask, aPayload) {
+ // This could be called on the background thread when socket process is
+ // enabled.
+ mData.mTimeStamp = PR_Now();
+}
+
+#define WSF_GETTER(method, value, type) \
+ NS_IMETHODIMP \
+ WebSocketFrame::method(type* aValue) { \
+ MOZ_ASSERT(NS_IsMainThread()); \
+ if (!aValue) { \
+ return NS_ERROR_FAILURE; \
+ } \
+ *aValue = value; \
+ return NS_OK; \
+ }
+
+WSF_GETTER(GetTimeStamp, mData.mTimeStamp, DOMHighResTimeStamp);
+WSF_GETTER(GetFinBit, mData.mFinBit, bool);
+WSF_GETTER(GetRsvBit1, mData.mRsvBit1, bool);
+WSF_GETTER(GetRsvBit2, mData.mRsvBit2, bool);
+WSF_GETTER(GetRsvBit3, mData.mRsvBit3, bool);
+WSF_GETTER(GetOpCode, mData.mOpCode, uint16_t);
+WSF_GETTER(GetMaskBit, mData.mMaskBit, bool);
+WSF_GETTER(GetMask, mData.mMask, uint32_t);
+
+#undef WSF_GETTER
+
+NS_IMETHODIMP
+WebSocketFrame::GetPayload(nsACString& aValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+ aValue = mData.mPayload;
+ return NS_OK;
+}
+
+WebSocketFrameData::WebSocketFrameData()
+ : mFinBit(false),
+ mRsvBit1(false),
+ mRsvBit2(false),
+ mRsvBit3(false),
+ mMaskBit(false) {}
+
+WebSocketFrameData::WebSocketFrameData(DOMHighResTimeStamp aTimeStamp,
+ bool aFinBit, bool aRsvBit1,
+ bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit,
+ uint32_t aMask,
+ const nsCString& aPayload)
+ : mTimeStamp(aTimeStamp),
+ mFinBit(aFinBit),
+ mRsvBit1(aRsvBit1),
+ mRsvBit2(aRsvBit2),
+ mRsvBit3(aRsvBit3),
+ mMaskBit(aMaskBit),
+ mOpCode(aOpCode),
+ mMask(aMask),
+ mPayload(aPayload) {}
+
+void WebSocketFrameData::WriteIPCParams(IPC::MessageWriter* aWriter) const {
+ WriteParam(aWriter, mTimeStamp);
+ WriteParam(aWriter, mFinBit);
+ WriteParam(aWriter, mRsvBit1);
+ WriteParam(aWriter, mRsvBit2);
+ WriteParam(aWriter, mRsvBit3);
+ WriteParam(aWriter, mMaskBit);
+ WriteParam(aWriter, mOpCode);
+ WriteParam(aWriter, mMask);
+ WriteParam(aWriter, mPayload);
+}
+
+bool WebSocketFrameData::ReadIPCParams(IPC::MessageReader* aReader) {
+ if (!ReadParam(aReader, &mTimeStamp)) {
+ return false;
+ }
+
+#define ReadParamHelper(x) \
+ { \
+ bool bit; \
+ if (!ReadParam(aReader, &bit)) { \
+ return false; \
+ } \
+ (x) = bit; \
+ }
+
+ ReadParamHelper(mFinBit);
+ ReadParamHelper(mRsvBit1);
+ ReadParamHelper(mRsvBit2);
+ ReadParamHelper(mRsvBit3);
+ ReadParamHelper(mMaskBit);
+
+#undef ReadParamHelper
+
+ return ReadParam(aReader, &mOpCode) && ReadParam(aReader, &mMask) &&
+ ReadParam(aReader, &mPayload);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketFrame.h b/netwerk/protocol/websocket/WebSocketFrame.h
new file mode 100644
index 0000000000..8bcb672692
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrame.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketFrame_h
+#define mozilla_net_WebSocketFrame_h
+
+#include <cstdint>
+#include "nsISupports.h"
+#include "nsIWebSocketEventService.h"
+#include "nsString.h"
+
+class PickleIterator;
+
+// Avoid including nsDOMNavigationTiming.h here, where the canonical definition
+// of DOMHighResTimeStamp resides.
+using DOMHighResTimeStamp = double;
+
+namespace IPC {
+class Message;
+class MessageReader;
+class MessageWriter;
+template <class P>
+struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+namespace net {
+
+class WebSocketFrameData final {
+ public:
+ WebSocketFrameData();
+
+ explicit WebSocketFrameData(const WebSocketFrameData&) = default;
+ WebSocketFrameData(WebSocketFrameData&&) = default;
+ WebSocketFrameData& operator=(WebSocketFrameData&&) = default;
+ WebSocketFrameData& operator=(const WebSocketFrameData&) = default;
+
+ WebSocketFrameData(DOMHighResTimeStamp aTimeStamp, bool aFinBit,
+ bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
+ const nsCString& aPayload);
+
+ ~WebSocketFrameData() = default;
+
+ // For IPC serialization
+ void WriteIPCParams(IPC::MessageWriter* aWriter) const;
+ bool ReadIPCParams(IPC::MessageReader* aReader);
+
+ DOMHighResTimeStamp mTimeStamp{0};
+
+ bool mFinBit : 1;
+ bool mRsvBit1 : 1;
+ bool mRsvBit2 : 1;
+ bool mRsvBit3 : 1;
+ bool mMaskBit : 1;
+ uint8_t mOpCode{0};
+
+ uint32_t mMask{0};
+
+ nsCString mPayload;
+};
+
+class WebSocketFrame final : public nsIWebSocketFrame {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBSOCKETFRAME
+
+ explicit WebSocketFrame(const WebSocketFrameData& aData);
+
+ WebSocketFrame(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
+ const nsCString& aPayload);
+
+ const WebSocketFrameData& Data() const { return mData; }
+
+ private:
+ ~WebSocketFrame() = default;
+
+ WebSocketFrameData mData;
+};
+
+} // namespace net
+} // namespace mozilla
+
+namespace IPC {
+template <>
+struct ParamTraits<mozilla::net::WebSocketFrameData> {
+ using paramType = mozilla::net::WebSocketFrameData;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aParam.WriteIPCParams(aWriter);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return aResult->ReadIPCParams(aReader);
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_net_WebSocketFrame_h
diff --git a/netwerk/protocol/websocket/WebSocketLog.h b/netwerk/protocol/websocket/WebSocketLog.h
new file mode 100644
index 0000000000..80122249c2
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketLog.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebSocketLog_h
+#define WebSocketLog_h
+
+#include "base/basictypes.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla {
+namespace net {
+extern LazyLogModule webSocketLog;
+}
+} // namespace mozilla
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(mozilla::net::webSocketLog, mozilla::LogLevel::Debug, args)
+
+#endif
diff --git a/netwerk/protocol/websocket/moz.build b/netwerk/protocol/websocket/moz.build
new file mode 100644
index 0000000000..123d364b70
--- /dev/null
+++ b/netwerk/protocol/websocket/moz.build
@@ -0,0 +1,67 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: WebSockets")
+
+XPIDL_SOURCES += [
+ "nsITransportProvider.idl",
+ "nsIWebSocketChannel.idl",
+ "nsIWebSocketEventService.idl",
+ "nsIWebSocketImpl.idl",
+ "nsIWebSocketListener.idl",
+]
+
+XPIDL_MODULE = "necko_websocket"
+
+EXPORTS.mozilla.net += [
+ "BaseWebSocketChannel.h",
+ "IPCTransportProvider.h",
+ "WebSocketChannel.h",
+ "WebSocketChannelChild.h",
+ "WebSocketChannelParent.h",
+ "WebSocketConnectionBase.h",
+ "WebSocketConnectionChild.h",
+ "WebSocketConnectionListener.h",
+ "WebSocketConnectionParent.h",
+ "WebSocketEventListenerChild.h",
+ "WebSocketEventListenerParent.h",
+ "WebSocketEventService.h",
+ "WebSocketFrame.h",
+]
+
+UNIFIED_SOURCES += [
+ "BaseWebSocketChannel.cpp",
+ "IPCTransportProvider.cpp",
+ "WebSocketChannel.cpp",
+ "WebSocketChannelChild.cpp",
+ "WebSocketChannelParent.cpp",
+ "WebSocketConnection.cpp",
+ "WebSocketConnectionChild.cpp",
+ "WebSocketConnectionParent.cpp",
+ "WebSocketEventListenerChild.cpp",
+ "WebSocketEventListenerParent.cpp",
+ "WebSocketEventService.cpp",
+ "WebSocketFrame.cpp",
+]
+
+IPDL_SOURCES += [
+ "PTransportProvider.ipdl",
+ "PWebSocket.ipdl",
+ "PWebSocketConnection.ipdl",
+ "PWebSocketEventListener.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/netwerk/base",
+]
+
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/netwerk/protocol/websocket/nsITransportProvider.idl b/netwerk/protocol/websocket/nsITransportProvider.idl
new file mode 100644
index 0000000000..8b60eabd03
--- /dev/null
+++ b/netwerk/protocol/websocket/nsITransportProvider.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface nsIHttpUpgradeListener;
+
+#include "nsISupports.idl"
+
+%{C++
+namespace mozilla {
+namespace net {
+class PTransportProviderChild;
+}
+}
+%}
+
+[ptr] native PTransportProviderChild(mozilla::net::PTransportProviderChild);
+
+/**
+ * An interface which can be used to asynchronously request a nsITransport
+ * together with the input and output streams that go together with it.
+ */
+[scriptable, uuid(6fcec704-cfd2-46ef-a394-a64d5cb1475c)]
+interface nsITransportProvider : nsISupports
+{
+ // This must not be called in a child process since transport
+ // objects are not accessible there. Call getIPCChild instead.
+ [must_use] void setListener(in nsIHttpUpgradeListener listener);
+
+ // This must be implemented by nsITransportProvider objects running
+ // in the child process. It must return null when called in the parent
+ // process.
+ [noscript, must_use] PTransportProviderChild getIPCChild();
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketChannel.idl b/netwerk/protocol/websocket/nsIWebSocketChannel.idl
new file mode 100644
index 0000000000..7092c3c6b1
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketChannel.idl
@@ -0,0 +1,268 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface nsICookieJarSettings;
+interface nsIInputStream;
+interface nsIInterfaceRequestor;
+interface nsILoadGroup;
+interface nsILoadInfo;
+interface nsIPrincipal;
+interface nsITransportProvider;
+interface nsITransportSecurityInfo;
+interface nsIURI;
+interface nsIWebSocketListener;
+
+webidl Node;
+
+#include "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+
+
+
+[ref] native OriginAttributes(const mozilla::OriginAttributes);
+
+/**
+ * Low-level websocket API: handles network protocol.
+ *
+ * This is primarly intended for use by the higher-level nsIWebSocket.idl.
+ * We are also making it scriptable for now, but this may change once we have
+ * WebSockets for Workers.
+ */
+[scriptable, builtinclass, uuid(ce71d028-322a-4105-a947-a894689b52bf)]
+interface nsIWebSocketChannel : nsISupports
+{
+ /**
+ * The original URI used to construct the protocol connection. This is used
+ * in the case of a redirect or URI "resolution" (e.g. resolving a
+ * resource: URI to a file: URI) so that the original pre-redirect
+ * URI can still be obtained. This is never null.
+ */
+ [must_use] readonly attribute nsIURI originalURI;
+
+ /**
+ * The readonly URI corresponding to the protocol connection after any
+ * redirections are completed.
+ */
+ [must_use] readonly attribute nsIURI URI;
+
+ /**
+ * The notification callbacks for authorization, etc..
+ */
+ [must_use] attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Transport-level security information (if any)
+ */
+ [must_use] readonly attribute nsITransportSecurityInfo securityInfo;
+
+ /**
+ * The load group of of the websocket
+ */
+ [must_use] attribute nsILoadGroup loadGroup;
+
+ /**
+ * The load info of the websocket
+ */
+ [must_use] attribute nsILoadInfo loadInfo;
+
+ /**
+ * Sec-Websocket-Protocol value
+ */
+ [must_use] attribute ACString protocol;
+
+ /**
+ * Sec-Websocket-Extensions response header value
+ */
+ [must_use] readonly attribute ACString extensions;
+
+ /**
+ * The channelId of the underlying http channel.
+ * It's available only after nsIWebSocketListener::onStart
+ */
+ [must_use] readonly attribute uint64_t httpChannelId;
+
+ /**
+ * Init the WebSocketChannel with LoadInfo arguments.
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aCookieJarSettings
+ * @param aSecurityFlags
+ * @param aContentPolicyType
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * @return reference to the new nsIChannel object
+ *
+ * Keep in mind that URIs coming from a webpage should *never* use the
+ * systemPrincipal as the loadingPrincipal.
+ *
+ * Please note, if you provide both a loadingNode and a loadingPrincipal,
+ * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+ * But less error prone is to just supply a loadingNode.
+ */
+ [notxpcom] nsresult initLoadInfoNative(in Node aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in nsICookieJarSettings aCookieJarSettings,
+ in unsigned long aSecurityFlags,
+ in nsContentPolicyType aContentPolicyType,
+ in unsigned long aSandboxFlags);
+
+ /**
+ * Similar to the previous one but without nsICookieJarSettings.
+ * This method is used by JS code where nsICookieJarSettings is not exposed.
+ */
+ [must_use] void initLoadInfo(in Node aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in nsContentPolicyType aContentPolicyType);
+
+ /**
+ * Asynchronously open the websocket connection. Received messages are fed
+ * to the socket listener as they arrive. The socket listener's methods
+ * are called on the thread that calls asyncOpen and are not called until
+ * after asyncOpen returns. If asyncOpen returns successfully, the
+ * protocol implementation promises to call at least onStop on the listener.
+ *
+ * NOTE: Implementations should throw NS_ERROR_ALREADY_OPENED if the
+ * websocket connection is reopened.
+ *
+ * @param aURI the uri of the websocket protocol - may be redirected
+ * @param aOrigin the uri of the originating resource
+ * @param aOriginAttributes attributes of the originating resource.
+ * @param aInnerWindowID the inner window ID
+ * @param aListener the nsIWebSocketListener implementation
+ * @param aContext an opaque parameter forwarded to aListener's methods
+ */
+ [implicit_jscontext]
+ void asyncOpen(in nsIURI aURI,
+ in ACString aOrigin,
+ in jsval aOriginAttributes,
+ in unsigned long long aInnerWindowID,
+ in nsIWebSocketListener aListener,
+ in nsISupports aContext);
+
+ [must_use]
+ void asyncOpenNative(in nsIURI aURI,
+ in ACString aOrigin,
+ in OriginAttributes aOriginAttributes,
+ in unsigned long long aInnerWindowID,
+ in nsIWebSocketListener aListener,
+ in nsISupports aContext);
+
+ /*
+ * Close the websocket connection for writing - no more calls to sendMsg
+ * or sendBinaryMsg should be made after calling this. The listener object
+ * may receive more messages if a server close has not yet been received.
+ *
+ * @param aCode the websocket closing handshake close code. Set to 0 if
+ * you are not providing a code.
+ * @param aReason the websocket closing handshake close reason
+ */
+ [must_use] void close(in unsigned short aCode, in AUTF8String aReason);
+
+ // section 7.4.1 defines these close codes
+ const unsigned short CLOSE_NORMAL = 1000;
+ const unsigned short CLOSE_GOING_AWAY = 1001;
+ const unsigned short CLOSE_PROTOCOL_ERROR = 1002;
+ const unsigned short CLOSE_UNSUPPORTED_DATATYPE = 1003;
+ // code 1004 is reserved
+ const unsigned short CLOSE_NO_STATUS = 1005;
+ const unsigned short CLOSE_ABNORMAL = 1006;
+ const unsigned short CLOSE_INVALID_PAYLOAD = 1007;
+ const unsigned short CLOSE_POLICY_VIOLATION = 1008;
+ const unsigned short CLOSE_TOO_LARGE = 1009;
+ const unsigned short CLOSE_EXTENSION_MISSING = 1010;
+ // Initially used just for server-side internal errors: adopted later for
+ // client-side errors too (not clear if will make into spec: see
+ // http://www.ietf.org/mail-archive/web/hybi/current/msg09372.html
+ const unsigned short CLOSE_INTERNAL_ERROR = 1011;
+ // MUST NOT be set as a status code in Close control frame by an endpoint:
+ // To be used if TLS handshake failed (ex: server certificate unverifiable)
+ const unsigned short CLOSE_TLS_FAILED = 1015;
+
+ /**
+ * Use to send text message down the connection to WebSocket peer.
+ *
+ * @param aMsg the utf8 string to send
+ */
+ [must_use] void sendMsg(in AUTF8String aMsg);
+
+ /**
+ * Use to send binary message down the connection to WebSocket peer.
+ *
+ * @param aMsg the data to send
+ */
+ [must_use] void sendBinaryMsg(in ACString aMsg);
+
+ /**
+ * Use to send a binary stream (Blob) to Websocket peer.
+ *
+ * @param aStream The input stream to be sent.
+ */
+ [must_use] void sendBinaryStream(in nsIInputStream aStream,
+ in unsigned long length);
+
+ /**
+ * This value determines how often (in seconds) websocket keepalive
+ * pings are sent. If set to 0 (the default), no pings are ever sent.
+ *
+ * This value can currently only be set before asyncOpen is called, else
+ * NS_ERROR_IN_PROGRESS is thrown.
+ *
+ * Be careful using this setting: ping traffic can consume lots of power and
+ * bandwidth over time.
+ */
+ [must_use] attribute unsigned long pingInterval;
+
+ /**
+ * This value determines how long (in seconds) the websocket waits for
+ * the server to reply to a ping that has been sent before considering the
+ * connection broken.
+ *
+ * This value can currently only be set before asyncOpen is called, else
+ * NS_ERROR_IN_PROGRESS is thrown.
+ */
+ [must_use] attribute unsigned long pingTimeout;
+
+ /**
+ * Unique ID for this channel. It's not readonly because when the channel is
+ * created via IPC, the serial number is received from the child process.
+ */
+ [must_use] attribute unsigned long serial;
+
+ /**
+ * Set a nsITransportProvider and negotated extensions to be used by this
+ * channel. Calling this function also means that this channel will
+ * implement the server-side part of a websocket connection rather than the
+ * client-side part.
+ */
+ [must_use] void setServerParameters(in nsITransportProvider aProvider,
+ in ACString aNegotiatedExtensions);
+
+%{C++
+ inline uint32_t Serial()
+ {
+ uint32_t serial;
+ nsresult rv = GetSerial(&serial);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return 0;
+ }
+ return serial;
+ }
+
+ inline uint64_t HttpChannelId()
+ {
+ uint64_t httpChannelId;
+ nsresult rv = GetHttpChannelId(&httpChannelId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return 0;
+ }
+ return httpChannelId;
+ }
+%}
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketEventService.idl b/netwerk/protocol/websocket/nsIWebSocketEventService.idl
new file mode 100644
index 0000000000..9763850609
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketEventService.idl
@@ -0,0 +1,87 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "domstubs.idl"
+#include "nsISupports.idl"
+
+interface nsIWeakReference;
+
+[scriptable, builtinclass, uuid(6714a6be-2265-4f73-a988-d78a12416037)]
+interface nsIWebSocketFrame : nsISupports
+{
+ [must_use] readonly attribute DOMHighResTimeStamp timeStamp;
+
+ [must_use] readonly attribute boolean finBit;
+
+ [must_use] readonly attribute boolean rsvBit1;
+ [must_use] readonly attribute boolean rsvBit2;
+ [must_use] readonly attribute boolean rsvBit3;
+
+ [must_use] readonly attribute unsigned short opCode;
+
+ [must_use] readonly attribute boolean maskBit;
+
+ [must_use] readonly attribute unsigned long mask;
+
+ [must_use] readonly attribute ACString payload;
+
+ // Non-Control opCode values:
+ const unsigned short OPCODE_CONTINUATION = 0x0;
+ const unsigned short OPCODE_TEXT = 0x1;
+ const unsigned short OPCODE_BINARY = 0x2;
+
+ // Control opCode values:
+ const unsigned short OPCODE_CLOSE = 0x8;
+ const unsigned short OPCODE_PING = 0x9;
+ const unsigned short OPCODE_PONG = 0xA;
+};
+
+[scriptable, uuid(e7c005ab-e694-489b-b741-96db43ffb16f)]
+interface nsIWebSocketEventListener : nsISupports
+{
+ [must_use] void webSocketCreated(in unsigned long aWebSocketSerialID,
+ in AString aURI,
+ in ACString aProtocols);
+
+ [must_use] void webSocketOpened(in unsigned long aWebSocketSerialID,
+ in AString aEffectiveURI,
+ in ACString aProtocols,
+ in ACString aExtensions,
+ in uint64_t aHttpChannelId);
+
+ const unsigned short TYPE_STRING = 0x0;
+ const unsigned short TYPE_BLOB = 0x1;
+ const unsigned short TYPE_ARRAYBUFFER = 0x2;
+
+ [must_use] void webSocketMessageAvailable(in unsigned long aWebSocketSerialID,
+ in ACString aMessage,
+ in unsigned short aType);
+
+ [must_use] void webSocketClosed(in unsigned long aWebSocketSerialID,
+ in boolean aWasClean,
+ in unsigned short aCode,
+ in AString aReason);
+
+ [must_use] void frameReceived(in unsigned long aWebSocketSerialID,
+ in nsIWebSocketFrame aFrame);
+
+ [must_use] void frameSent(in unsigned long aWebSocketSerialID,
+ in nsIWebSocketFrame aFrame);
+};
+
+[scriptable, builtinclass, uuid(b89d1b90-2cf3-4d8f-ac21-5aedfb25c760)]
+interface nsIWebSocketEventService : nsISupports
+{
+ [must_use] void sendMessage(in unsigned long aWebSocketSerialID,
+ in AString aMessage);
+
+ [must_use] void addListener(in unsigned long long aInnerWindowID,
+ in nsIWebSocketEventListener aListener);
+
+ [must_use] void removeListener(in unsigned long long aInnerWindowID,
+ in nsIWebSocketEventListener aListener);
+
+ [must_use] bool hasListenerFor(in unsigned long long aInnerWindowID);
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketImpl.idl b/netwerk/protocol/websocket/nsIWebSocketImpl.idl
new file mode 100644
index 0000000000..ab4177a7e6
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketImpl.idl
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(db1f4e2b-3cff-4615-a03c-341fda66c53d)]
+interface nsIWebSocketImpl : nsISupports
+{
+ /**
+ * Called to send message of type string through web socket
+ *
+ * @param aMessage the message to send
+ */
+ [must_use] void sendMessage(in AString aMessage);
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketListener.idl b/netwerk/protocol/websocket/nsIWebSocketListener.idl
new file mode 100644
index 0000000000..31c588630b
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketListener.idl
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIWebSocketListener: passed to nsIWebSocketChannel::AsyncOpen. Receives
+ * websocket traffic events as they arrive.
+ */
+[scriptable, uuid(d74c96b2-65b3-4e39-9e39-c577de5d7a73)]
+interface nsIWebSocketListener : nsISupports
+{
+ /**
+ * Called to signify the establishment of the message stream.
+ *
+ * Unlike most other networking channels (which use nsIRequestObserver
+ * instead of this class), we do not guarantee that OnStart is always
+ * called: OnStop is called without calling this function if errors occur
+ * during connection setup. If the websocket connection is successful,
+ * OnStart will be called before any other calls to this API.
+ *
+ * @param aContext user defined context
+ */
+ [must_use] void onStart(in nsISupports aContext);
+
+ /**
+ * Called to signify the completion of the message stream.
+ * OnStop is the final notification the listener will receive and it
+ * completes the WebSocket connection: after it returns the
+ * nsIWebSocketChannel will release its reference to the listener.
+ *
+ * Note: this event can be received in error cases even if
+ * nsIWebSocketChannel::Close() has not been called.
+ *
+ * @param aContext user defined context
+ * @param aStatusCode reason for stopping (NS_OK if completed successfully)
+ */
+ [must_use] void onStop(in nsISupports aContext,
+ in nsresult aStatusCode);
+
+ /**
+ * Called to deliver text message.
+ *
+ * @param aContext user defined context
+ * @param aMsg the message data
+ */
+ [must_use] void onMessageAvailable(in nsISupports aContext,
+ in AUTF8String aMsg);
+
+ /**
+ * Called to deliver binary message.
+ *
+ * @param aContext user defined context
+ * @param aMsg the message data
+ */
+ [must_use] void onBinaryMessageAvailable(in nsISupports aContext,
+ in ACString aMsg);
+
+ /**
+ * Called to acknowledge message sent via sendMsg() or sendBinaryMsg.
+ *
+ * @param aContext user defined context
+ * @param aSize number of bytes placed in OS send buffer
+ */
+ [must_use] void onAcknowledge(in nsISupports aContext, in uint32_t aSize);
+
+ /**
+ * Called to inform receipt of WebSocket Close message from server.
+ * In the case of errors onStop() can be called without ever
+ * receiving server close.
+ *
+ * No additional messages through onMessageAvailable(),
+ * onBinaryMessageAvailable() or onAcknowledge() will be delievered
+ * to the listener after onServerClose(), though outgoing messages can still
+ * be sent through the nsIWebSocketChannel connection.
+ *
+ * @param aContext user defined context
+ * @param aCode the websocket closing handshake close code.
+ * @param aReason the websocket closing handshake close reason
+ */
+ [must_use] void onServerClose(in nsISupports aContext,
+ in unsigned short aCode,
+ in AUTF8String aReason);
+
+ /**
+ * Called to inform an error is happened. The connection will be closed
+ * when this is called.
+ */
+ [must_use] void OnError();
+
+};
diff --git a/netwerk/protocol/webtransport/WebTransportHash.cpp b/netwerk/protocol/webtransport/WebTransportHash.cpp
new file mode 100644
index 0000000000..f53fbacbcf
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportHash.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebTransportHash.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(WebTransportHash, nsIWebTransportHash);
+
+NS_IMETHODIMP
+WebTransportHash::GetValue(nsTArray<uint8_t>& aValue) {
+ aValue.Clear();
+ aValue.AppendElements(mValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportHash::GetAlgorithm(nsACString& algorithm) {
+ algorithm.Assign(mAlgorithm);
+ return NS_OK;
+}
+} // namespace mozilla::net
diff --git a/netwerk/protocol/webtransport/WebTransportHash.h b/netwerk/protocol/webtransport/WebTransportHash.h
new file mode 100644
index 0000000000..b3037f560a
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportHash.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebTransportHash_h
+#define mozilla_net_WebTransportHash_h
+
+#include "nsIWebTransport.h"
+
+namespace mozilla::net {
+
+class WebTransportHash final : public nsIWebTransportHash {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBTRANSPORTHASH
+
+ explicit WebTransportHash(const nsACString& algorithm,
+ const nsTArray<uint8_t>& value)
+ : mAlgorithm(algorithm), mValue(Span(value)) {}
+
+ private:
+ ~WebTransportHash() = default;
+ nsCString mAlgorithm;
+ nsTArray<uint8_t> mValue;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_WebTransportHash_h
diff --git a/netwerk/protocol/webtransport/WebTransportLog.h b/netwerk/protocol/webtransport/WebTransportLog.h
new file mode 100644
index 0000000000..99f211b893
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportLog.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebTransportLog_h
+#define WebTransportLog_h
+
+#include "mozilla/Logging.h"
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla::net {
+extern LazyLogModule webTransportLog;
+} // namespace mozilla::net
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(mozilla::net::webTransportLog, mozilla::LogLevel::Debug, args)
+
+#endif
diff --git a/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp b/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp
new file mode 100644
index 0000000000..e650a74d5b
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp
@@ -0,0 +1,1269 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebTransportLog.h"
+#include "Http3WebTransportSession.h"
+#include "Http3WebTransportStream.h"
+#include "ScopedNSSTypes.h"
+#include "WebTransportSessionProxy.h"
+#include "WebTransportStreamProxy.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIRequest.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIX509Cert.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransportService2.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+
+namespace mozilla::net {
+
+LazyLogModule webTransportLog("nsWebTransport");
+
+NS_IMPL_ISUPPORTS(WebTransportSessionProxy, WebTransportSessionEventListener,
+ nsIWebTransport, nsIRedirectResultListener, nsIStreamListener,
+ nsIChannelEventSink, nsIInterfaceRequestor);
+
+WebTransportSessionProxy::WebTransportSessionProxy()
+ : mMutex("WebTransportSessionProxy::mMutex"),
+ mTarget(GetMainThreadSerialEventTarget()) {
+ LOG(("WebTransportSessionProxy constructor"));
+}
+
+WebTransportSessionProxy::~WebTransportSessionProxy() {
+ if (OnSocketThread()) {
+ return;
+ }
+
+ MutexAutoLock lock(mMutex);
+ if ((mState != WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED) &&
+ (mState != WebTransportSessionProxyState::ACTIVE) &&
+ (mState != WebTransportSessionProxyState::SESSION_CLOSE_PENDING)) {
+ return;
+ }
+
+ MOZ_ASSERT(mState != WebTransportSessionProxyState::SESSION_CLOSE_PENDING,
+ "We can not be in the SESSION_CLOSE_PENDING state in destructor, "
+ "because should e an runnable that holds reference to this"
+ "object.");
+
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::ProxyHttp3WebTransportSessionRelease",
+ [self{std::move(mWebTransportSession)}]() {}));
+}
+
+//-----------------------------------------------------------------------------
+// WebTransportSessionProxy::nsIWebTransport
+//-----------------------------------------------------------------------------
+
+nsresult WebTransportSessionProxy::AsyncConnect(
+ nsIURI* aURI,
+ const nsTArray<RefPtr<nsIWebTransportHash>>& aServerCertHashes,
+ nsIPrincipal* aPrincipal, uint32_t aSecurityFlags,
+ WebTransportSessionEventListener* aListener) {
+ return AsyncConnectWithClient(aURI, std::move(aServerCertHashes), aPrincipal,
+ aSecurityFlags, aListener,
+ Maybe<dom::ClientInfo>());
+}
+
+nsresult WebTransportSessionProxy::AsyncConnectWithClient(
+ nsIURI* aURI,
+ const nsTArray<RefPtr<nsIWebTransportHash>>& aServerCertHashes,
+ nsIPrincipal* aPrincipal, uint32_t aSecurityFlags,
+ WebTransportSessionEventListener* aListener,
+ const Maybe<dom::ClientInfo>& aClientInfo) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("WebTransportSessionProxy::AsyncConnect"));
+ {
+ MutexAutoLock lock(mMutex);
+ mListener = aListener;
+ }
+ auto cleanup = MakeScopeExit([self = RefPtr<WebTransportSessionProxy>(this)] {
+ MutexAutoLock lock(self->mMutex);
+ self->mListener->OnSessionClosed(false, 0,
+ ""_ns); // TODO: find a better error.
+ self->mChannel = nullptr;
+ self->mListener = nullptr;
+ self->ChangeState(WebTransportSessionProxyState::DONE);
+ });
+
+ nsSecurityFlags flags = nsILoadInfo::SEC_COOKIES_OMIT | aSecurityFlags;
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
+ nsIRequest::LOAD_BYPASS_CACHE |
+ nsIRequest::INHIBIT_CACHING;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (aClientInfo.isSome()) {
+ rv = NS_NewChannel(getter_AddRefs(mChannel), aURI, aPrincipal,
+ aClientInfo.ref(), Maybe<dom::ServiceWorkerDescriptor>(),
+ flags, nsContentPolicyType::TYPE_WEB_TRANSPORT,
+ /* aCookieJarSettings */ nullptr,
+ /* aPerformanceStorage */ nullptr,
+ /* aLoadGroup */ nullptr,
+ /* aCallbacks */ this, loadFlags);
+ } else {
+ rv = NS_NewChannel(getter_AddRefs(mChannel), aURI, aPrincipal, flags,
+ nsContentPolicyType::TYPE_WEB_TRANSPORT,
+ /* aCookieJarSettings */ nullptr,
+ /* aPerformanceStorage */ nullptr,
+ /* aLoadGroup */ nullptr,
+ /* aCallbacks */ this, loadFlags);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // configure HTTP specific stuff
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (!httpChannel) {
+ mChannel = nullptr;
+ return NS_ERROR_ABORT;
+ }
+
+ if (!aServerCertHashes.IsEmpty()) {
+ mServerCertHashes.AppendElements(aServerCertHashes);
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ ChangeState(WebTransportSessionProxyState::NEGOTIATING);
+ }
+
+ // https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-04.html#section-6
+ rv = httpChannel->SetRequestHeader("Sec-Webtransport-Http3-Draft02"_ns,
+ "1"_ns, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // To establish a WebTransport session with an origin origin, follow
+ // [WEB-TRANSPORT-HTTP3] section 3.3, with using origin, serialized and
+ // isomorphic encoded, as the `Origin` header of the request.
+ // https://www.w3.org/TR/webtransport/#protocol-concepts
+ nsAutoCString serializedOrigin;
+ if (NS_FAILED(
+ aPrincipal->GetWebExposedOriginSerialization(serializedOrigin))) {
+ // origin/URI will be missing for system principals
+ // assign null origin
+ serializedOrigin = "null"_ns;
+ }
+
+ rv = httpChannel->SetRequestHeader("Origin"_ns, serializedOrigin, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel =
+ do_QueryInterface(mChannel);
+ if (!internalChannel) {
+ mChannel = nullptr;
+ return NS_ERROR_ABORT;
+ }
+ Unused << internalChannel->SetWebTransportSessionEventListener(this);
+
+ rv = mChannel->AsyncOpen(this);
+ if (NS_SUCCEEDED(rv)) {
+ cleanup.release();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::RetargetTo(nsIEventTarget* aTarget) {
+ if (!aTarget) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ LOG(("WebTransportSessionProxy::RetargetTo mState=%d", mState));
+ // RetargetTo should be only called after the session is ready.
+ if (mState != WebTransportSessionProxyState::ACTIVE) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mTarget = aTarget;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::GetStats() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+WebTransportSessionProxy::CloseSession(uint32_t status,
+ const nsACString& reason) {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+ mCloseStatus = status;
+ mReason = reason;
+ mListener = nullptr;
+ mPendingEvents.Clear();
+ mServerCertHashes.Clear();
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::DONE:
+ return NS_ERROR_NOT_INITIALIZED;
+ case WebTransportSessionProxyState::NEGOTIATING:
+ mChannel->Cancel(NS_ERROR_ABORT);
+ mChannel = nullptr;
+ ChangeState(WebTransportSessionProxyState::DONE);
+ break;
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ mChannel->Cancel(NS_ERROR_ABORT);
+ mChannel = nullptr;
+ ChangeState(WebTransportSessionProxyState::SESSION_CLOSE_PENDING);
+ CloseSessionInternal();
+ break;
+ case WebTransportSessionProxyState::ACTIVE:
+ ChangeState(WebTransportSessionProxyState::SESSION_CLOSE_PENDING);
+ CloseSessionInternal();
+ break;
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ ChangeState(WebTransportSessionProxyState::DONE);
+ break;
+ case SESSION_CLOSE_PENDING:
+ break;
+ }
+ return NS_OK;
+}
+
+void WebTransportSessionProxy::CloseSessionInternalLocked() {
+ MutexAutoLock lock(mMutex);
+ CloseSessionInternal();
+}
+
+void WebTransportSessionProxy::CloseSessionInternal() {
+ if (!OnSocketThread()) {
+ mMutex.AssertCurrentThreadOwns();
+ RefPtr<WebTransportSessionProxy> self(this);
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::CallCloseWebTransportSession",
+ [self{std::move(self)}]() { self->CloseSessionInternalLocked(); }));
+ return;
+ }
+
+ mMutex.AssertCurrentThreadOwns();
+
+ RefPtr<Http3WebTransportSession> wt;
+ uint32_t closeStatus = 0;
+ nsCString reason;
+
+ if (mState == WebTransportSessionProxyState::SESSION_CLOSE_PENDING) {
+ MOZ_ASSERT(mWebTransportSession);
+ wt = mWebTransportSession;
+ mWebTransportSession = nullptr;
+ closeStatus = mCloseStatus;
+ reason = mReason;
+ ChangeState(WebTransportSessionProxyState::DONE);
+ } else {
+ MOZ_ASSERT(mState == WebTransportSessionProxyState::DONE);
+ }
+
+ if (wt) {
+ MutexAutoUnlock unlock(mMutex);
+ wt->CloseSession(closeStatus, reason);
+ }
+}
+
+class WebTransportStreamCallbackWrapper final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebTransportStreamCallbackWrapper)
+
+ explicit WebTransportStreamCallbackWrapper(
+ nsIWebTransportStreamCallback* aCallback, bool aBidi)
+ : mCallback(aCallback),
+ mTarget(GetCurrentSerialEventTarget()),
+ mBidi(aBidi) {}
+
+ void CallOnError(nsresult aError) {
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<WebTransportStreamCallbackWrapper> self(this);
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamCallbackWrapper::CallOnError",
+ [self{std::move(self)}, error{aError}]() {
+ self->CallOnError(error);
+ }));
+ return;
+ }
+
+ LOG(("WebTransportStreamCallbackWrapper::OnError aError=0x%" PRIx32,
+ static_cast<uint32_t>(aError)));
+ Unused << mCallback->OnError(nsIWebTransport::INVALID_STATE_ERROR);
+ }
+
+ void CallOnStreamReady(WebTransportStreamProxy* aStream) {
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<WebTransportStreamCallbackWrapper> self(this);
+ RefPtr<WebTransportStreamProxy> stream = aStream;
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamCallbackWrapper::CallOnStreamReady",
+ [self{std::move(self)}, stream{std::move(stream)}]() {
+ self->CallOnStreamReady(stream);
+ }));
+ return;
+ }
+
+ if (mBidi) {
+ Unused << mCallback->OnBidirectionalStreamReady(aStream);
+ return;
+ }
+
+ Unused << mCallback->OnUnidirectionalStreamReady(aStream);
+ }
+
+ private:
+ virtual ~WebTransportStreamCallbackWrapper() {
+ NS_ProxyRelease(
+ "WebTransportStreamCallbackWrapper::~WebTransportStreamCallbackWrapper",
+ mTarget, mCallback.forget());
+ }
+
+ nsCOMPtr<nsIWebTransportStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ bool mBidi = false;
+};
+
+void WebTransportSessionProxy::CreateStreamInternal(
+ nsIWebTransportStreamCallback* callback, bool aBidi) {
+ mMutex.AssertCurrentThreadOwns();
+ LOG(
+ ("WebTransportSessionProxy::CreateStreamInternal %p "
+ "mState=%d, bidi=%d",
+ this, mState, aBidi));
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::NEGOTIATING:
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ case WebTransportSessionProxyState::ACTIVE: {
+ RefPtr<WebTransportStreamCallbackWrapper> wrapper =
+ new WebTransportStreamCallbackWrapper(callback, aBidi);
+ if (mState == WebTransportSessionProxyState::ACTIVE &&
+ mWebTransportSession) {
+ DoCreateStream(wrapper, mWebTransportSession, aBidi);
+ } else {
+ LOG(
+ ("WebTransportSessionProxy::CreateStreamInternal %p "
+ " queue create stream event",
+ this));
+ auto task = [self = RefPtr{this}, wrapper{std::move(wrapper)},
+ bidi(aBidi)](nsresult aStatus) {
+ if (NS_FAILED(aStatus)) {
+ wrapper->CallOnError(aStatus);
+ return;
+ }
+
+ self->DoCreateStream(wrapper, nullptr, bidi);
+ };
+ // TODO: we should do this properly in bug 1830362.
+ mPendingCreateStreamEvents.AppendElement(std::move(task));
+ }
+ } break;
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ case WebTransportSessionProxyState::DONE: {
+ nsCOMPtr<nsIWebTransportStreamCallback> cb(callback);
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::CreateStreamInternal",
+ [cb{std::move(cb)}]() {
+ cb->OnError(nsIWebTransport::INVALID_STATE_ERROR);
+ }));
+ } break;
+ }
+}
+
+void WebTransportSessionProxy::DoCreateStream(
+ WebTransportStreamCallbackWrapper* aCallback,
+ Http3WebTransportSession* aSession, bool aBidi) {
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportSessionProxy> self(this);
+ RefPtr<WebTransportStreamCallbackWrapper> wrapper(aCallback);
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::DoCreateStream",
+ [self{std::move(self)}, wrapper{std::move(wrapper)}, bidi(aBidi)]() {
+ self->DoCreateStream(wrapper, nullptr, bidi);
+ }));
+ return;
+ }
+
+ LOG(("WebTransportSessionProxy::DoCreateStream %p bidi=%d", this, aBidi));
+
+ RefPtr<Http3WebTransportSession> session = aSession;
+ // Having no session here means that this is called by dispatching tasks.
+ // The mState may be already changed, so we need to check it again.
+ if (!aSession) {
+ MutexAutoLock lock(mMutex);
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::NEGOTIATING:
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ MOZ_ASSERT(false, "DoCreateStream called with invalid state");
+ aCallback->CallOnError(NS_ERROR_UNEXPECTED);
+ return;
+ case WebTransportSessionProxyState::ACTIVE: {
+ session = mWebTransportSession;
+ } break;
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ case WebTransportSessionProxyState::DONE:
+ // Session is going to be closed.
+ aCallback->CallOnError(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+ }
+
+ if (!session) {
+ MOZ_ASSERT_UNREACHABLE("This should not happen");
+ aCallback->CallOnError(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ RefPtr<WebTransportStreamCallbackWrapper> wrapper(aCallback);
+ auto callback =
+ [wrapper{std::move(wrapper)}](
+ Result<RefPtr<Http3WebTransportStream>, nsresult>&& aResult) {
+ if (aResult.isErr()) {
+ wrapper->CallOnError(aResult.unwrapErr());
+ return;
+ }
+
+ RefPtr<Http3WebTransportStream> stream = aResult.unwrap();
+ RefPtr<WebTransportStreamProxy> streamProxy =
+ new WebTransportStreamProxy(stream);
+ wrapper->CallOnStreamReady(streamProxy);
+ };
+
+ if (aBidi) {
+ session->CreateOutgoingBidirectionalStream(std::move(callback));
+ } else {
+ session->CreateOutgoingUnidirectionalStream(std::move(callback));
+ }
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::CreateOutgoingUnidirectionalStream(
+ nsIWebTransportStreamCallback* callback) {
+ if (!callback) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ MutexAutoLock lock(mMutex);
+ CreateStreamInternal(callback, false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::CreateOutgoingBidirectionalStream(
+ nsIWebTransportStreamCallback* callback) {
+ if (!callback) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ MutexAutoLock lock(mMutex);
+ CreateStreamInternal(callback, true);
+ return NS_OK;
+}
+
+void WebTransportSessionProxy::SendDatagramInternal(
+ const RefPtr<Http3WebTransportSession>& aSession, nsTArray<uint8_t>&& aData,
+ uint64_t aTrackingId) {
+ MOZ_ASSERT(OnSocketThread());
+
+ aSession->SendDatagram(std::move(aData), aTrackingId);
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::SendDatagram(const nsTArray<uint8_t>& aData,
+ uint64_t aTrackingId) {
+ RefPtr<Http3WebTransportSession> session;
+ {
+ MutexAutoLock lock(mMutex);
+ if (mState != WebTransportSessionProxyState::ACTIVE ||
+ !mWebTransportSession) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ session = mWebTransportSession;
+ }
+
+ nsTArray<uint8_t> copied;
+ copied.Assign(aData);
+ if (!OnSocketThread()) {
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::SendDatagramInternal",
+ [self = RefPtr{this}, session{std::move(session)},
+ data{std::move(copied)}, trackingId(aTrackingId)]() mutable {
+ self->SendDatagramInternal(session, std::move(data), trackingId);
+ }));
+ }
+
+ SendDatagramInternal(session, std::move(copied), aTrackingId);
+ return NS_OK;
+}
+
+void WebTransportSessionProxy::GetMaxDatagramSizeInternal(
+ const RefPtr<Http3WebTransportSession>& aSession) {
+ MOZ_ASSERT(OnSocketThread());
+
+ aSession->GetMaxDatagramSize();
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::GetMaxDatagramSize() {
+ RefPtr<Http3WebTransportSession> session;
+ {
+ MutexAutoLock lock(mMutex);
+ if (mState != WebTransportSessionProxyState::ACTIVE ||
+ !mWebTransportSession) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ session = mWebTransportSession;
+ }
+
+ if (!OnSocketThread()) {
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::GetMaxDatagramSizeInternal",
+ [self = RefPtr{this}, session{std::move(session)}]() {
+ self->GetMaxDatagramSizeInternal(session);
+ }));
+ }
+
+ GetMaxDatagramSizeInternal(session);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WebTransportSessionProxy::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnStartRequest(nsIRequest* aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("WebTransportSessionProxy::OnStartRequest\n"));
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ nsAutoCString reason;
+ uint32_t closeStatus = 0;
+ {
+ MutexAutoLock lock(mMutex);
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::DONE:
+ case WebTransportSessionProxyState::ACTIVE:
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ MOZ_ASSERT(false, "OnStartRequest cannot be called in this state.");
+ break;
+ case WebTransportSessionProxyState::NEGOTIATING:
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ listener = mListener;
+ mListener = nullptr;
+ mChannel = nullptr;
+ reason = mReason;
+ closeStatus = mCloseStatus;
+ ChangeState(WebTransportSessionProxyState::DONE);
+ break;
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED: {
+ uint32_t status;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (!httpChannel ||
+ NS_FAILED(httpChannel->GetResponseStatus(&status)) ||
+ !(status >= 200 && status < 300) ||
+ !CheckServerCertificateIfNeeded()) {
+ listener = mListener;
+ mListener = nullptr;
+ mChannel = nullptr;
+ mReason = ""_ns;
+ reason = ""_ns;
+ mCloseStatus =
+ 0; // TODO: find a better error. Currently error code 0 is used
+ ChangeState(WebTransportSessionProxyState::SESSION_CLOSE_PENDING);
+ CloseSessionInternal(); // TODO: find a better error. Currently error
+ // code 0 is used.
+ }
+ // The success cases will be handled in OnStopRequest.
+ } break;
+ }
+ }
+ if (listener) {
+ listener->OnSessionClosed(false, closeStatus, reason);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(
+ false, "WebTransportSessionProxy::OnDataAvailable should not be called");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mChannel = nullptr;
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ nsAutoCString reason;
+ uint32_t closeStatus = 0;
+ uint64_t sessionId;
+ bool succeeded = false;
+ nsTArray<std::function<void()>> pendingEvents;
+ nsTArray<std::function<void(nsresult)>> pendingCreateStreamEvents;
+ {
+ MutexAutoLock lock(mMutex);
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::ACTIVE:
+ case WebTransportSessionProxyState::NEGOTIATING:
+ MOZ_ASSERT(false, "OnStopRequest cannot be called in this state.");
+ break;
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ reason = mReason;
+ closeStatus = mCloseStatus;
+ listener = mListener;
+ mListener = nullptr;
+ ChangeState(WebTransportSessionProxyState::DONE);
+ break;
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ if (NS_FAILED(aStatus)) {
+ listener = mListener;
+ mListener = nullptr;
+ mReason = ""_ns;
+ reason = ""_ns;
+ mCloseStatus = 0;
+ ChangeState(WebTransportSessionProxyState::SESSION_CLOSE_PENDING);
+ CloseSessionInternal(); // TODO: find a better error. Currently error
+ // code 0 is used.
+ } else {
+ succeeded = true;
+ sessionId = mSessionId;
+ listener = mListener;
+ ChangeState(WebTransportSessionProxyState::ACTIVE);
+ }
+ break;
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ case WebTransportSessionProxyState::DONE:
+ break;
+ }
+ pendingEvents = std::move(mPendingEvents);
+ pendingCreateStreamEvents = std::move(mPendingCreateStreamEvents);
+ if (!pendingCreateStreamEvents.IsEmpty()) {
+ if (NS_SUCCEEDED(aStatus) &&
+ (mState == WebTransportSessionProxyState::DONE ||
+ mState == WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING ||
+ mState == WebTransportSessionProxyState::SESSION_CLOSE_PENDING)) {
+ aStatus = NS_ERROR_FAILURE;
+ }
+ }
+
+ mStopRequestCalled = true;
+ }
+
+ if (!pendingCreateStreamEvents.IsEmpty()) {
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::DispatchPendingCreateStreamEvents",
+ [pendingCreateStreamEvents = std::move(pendingCreateStreamEvents),
+ status(aStatus)]() {
+ for (const auto& event : pendingCreateStreamEvents) {
+ event(status);
+ }
+ }));
+ } // otherwise let the CreateStreams just go away
+
+ if (listener) {
+ if (succeeded) {
+ listener->OnSessionReady(sessionId);
+ if (!pendingEvents.IsEmpty()) {
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::DispatchPendingEvents",
+ [pendingEvents = std::move(pendingEvents)]() {
+ for (const auto& event : pendingEvents) {
+ event();
+ }
+ }));
+ }
+ } else {
+ listener->OnSessionClosed(false, closeStatus,
+ reason); // TODO: find a better error.
+ // Currently error code 0 is used.
+ }
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WebTransportSessionProxy::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebTransportSessionProxy::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ // Currently implementation we do not reach this part of the code
+ // as location headers are not forwarded by the http3 stack to the applicaion.
+ // Hence, the channel is aborted due to the location header check in
+ // nsHttpChannel::AsyncProcessRedirection This comment must be removed after
+ // the following neqo bug is resolved
+ // https://github.com/mozilla/neqo/issues/1364
+ if (!StaticPrefs::network_webtransport_redirect_enabled()) {
+ LOG(("Channel Redirects are disabled for WebTransport sessions"));
+ return NS_ERROR_ABORT;
+ }
+
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aNewChannel->GetURI(getter_AddRefs(newURI));
+ if (NS_FAILED(rv)) {
+ callback->OnRedirectVerifyCallback(rv);
+ return NS_OK;
+ }
+
+ // abort the request if redirecting to insecure context
+ if (!newURI->SchemeIs("https")) {
+ callback->OnRedirectVerifyCallback(NS_ERROR_ABORT);
+ return NS_OK;
+ }
+
+ // Assign to mChannel after we get notification about success of the
+ // redirect in OnRedirectResult.
+ mRedirectChannel = aNewChannel;
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WebTransportSessionProxy::nsIRedirectResultListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnRedirectResult(nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus) && mRedirectChannel) {
+ mChannel = mRedirectChannel;
+ }
+
+ mRedirectChannel = nullptr;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WebTransportSessionProxy::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebTransportSessionProxy::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIChannelEventSink*>(this);
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIRedirectResultListener*>(this);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// WebTransportSessionProxy::WebTransportSessionEventListener
+//-----------------------------------------------------------------------------
+
+// This function is called when the Http3WebTransportSession is ready. After
+// this call WebTransportSessionProxy is responsible for the
+// Http3WebTransportSession, i.e. it is responsible for closing it.
+// The listener of the WebTransportSessionProxy will be informed during
+// OnStopRequest call.
+NS_IMETHODIMP
+WebTransportSessionProxy::OnSessionReadyInternal(
+ Http3WebTransportSession* aSession) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("WebTransportSessionProxy::OnSessionReadyInternal"));
+ MutexAutoLock lock(mMutex);
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ case WebTransportSessionProxyState::ACTIVE:
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ MOZ_ASSERT(false,
+ "OnSessionReadyInternal cannot be called in this state.");
+ return NS_ERROR_ABORT;
+ case WebTransportSessionProxyState::NEGOTIATING:
+ mWebTransportSession = aSession;
+ mSessionId = aSession->StreamId();
+ ChangeState(WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED);
+ break;
+ case WebTransportSessionProxyState::DONE:
+ // The session has been canceled. We do not need to set
+ // mWebTransportSession.
+ break;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnIncomingStreamAvailableInternal(
+ Http3WebTransportStream* aStream) {
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+
+ LOG(
+ ("WebTransportSessionProxy::OnIncomingStreamAvailableInternal %p "
+ "mState=%d "
+ "mStopRequestCalled=%d",
+ this, mState, mStopRequestCalled));
+ // Since OnSessionReady on the listener is called on the main thread,
+ // OnIncomingStreamAvailableInternal and OnSessionReady can be racy. If
+ // OnStopRequest is not called yet, OnIncomingStreamAvailableInternal needs
+ // to wait.
+ if (!mStopRequestCalled) {
+ mPendingEvents.AppendElement(
+ [self = RefPtr{this}, stream = RefPtr{aStream}]() {
+ self->OnIncomingStreamAvailableInternal(stream);
+ });
+ return NS_OK;
+ }
+
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<WebTransportSessionProxy> self(this);
+ RefPtr<Http3WebTransportStream> stream = aStream;
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::OnIncomingStreamAvailableInternal",
+ [self{std::move(self)}, stream{std::move(stream)}]() {
+ self->OnIncomingStreamAvailableInternal(stream);
+ }));
+ return NS_OK;
+ }
+
+ LOG(
+ ("WebTransportSessionProxy::OnIncomingStreamAvailableInternal %p "
+ "mState=%d mListener=%p",
+ this, mState, mListener.get()));
+ if (mState == WebTransportSessionProxyState::ACTIVE) {
+ listener = mListener;
+ }
+ }
+
+ if (!listener) {
+ // Session can be already closed.
+ return NS_OK;
+ }
+
+ RefPtr<WebTransportStreamProxy> streamProxy =
+ new WebTransportStreamProxy(aStream);
+ if (aStream->StreamType() == WebTransportStreamType::BiDi) {
+ Unused << listener->OnIncomingBidirectionalStreamAvailable(streamProxy);
+ } else {
+ Unused << listener->OnIncomingUnidirectionalStreamAvailable(streamProxy);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnIncomingBidirectionalStreamAvailable(
+ nsIWebTransportBidirectionalStream* aStream) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnIncomingUnidirectionalStreamAvailable(
+ nsIWebTransportReceiveStream* aStream) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnSessionReady(uint64_t ready) {
+ MOZ_ASSERT(false, "Should not b called");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnSessionClosed(bool aCleanly, uint32_t aStatus,
+ const nsACString& aReason) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MutexAutoLock lock(mMutex);
+ LOG(
+ ("WebTransportSessionProxy::OnSessionClosed %p mState=%d "
+ "mStopRequestCalled=%d",
+ this, mState, mStopRequestCalled));
+ // Since OnSessionReady on the listener is called on the main thread,
+ // OnSessionClosed and OnSessionReady can be racy. If OnStopRequest is not
+ // called yet, OnSessionClosed needs to wait.
+ if (!mStopRequestCalled) {
+ nsCString closeReason(aReason);
+ mPendingEvents.AppendElement([self = RefPtr{this}, status(aStatus),
+ closeReason(std::move(closeReason)),
+ cleanly(aCleanly)]() {
+ Unused << self->OnSessionClosed(cleanly, status, closeReason);
+ });
+ return NS_OK;
+ }
+
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::NEGOTIATING:
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ MOZ_ASSERT(false, "OnSessionClosed cannot be called in this state.");
+ return NS_ERROR_ABORT;
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ case WebTransportSessionProxyState::ACTIVE: {
+ mCleanly = aCleanly;
+ mCloseStatus = aStatus;
+ mReason = aReason;
+ mWebTransportSession = nullptr;
+ ChangeState(WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING);
+ CallOnSessionClosed();
+ } break;
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ ChangeState(WebTransportSessionProxyState::DONE);
+ break;
+ case WebTransportSessionProxyState::DONE:
+ // The session has been canceled. We do not need to set
+ // mWebTransportSession.
+ break;
+ }
+ return NS_OK;
+}
+
+void WebTransportSessionProxy::CallOnSessionClosedLocked() {
+ MutexAutoLock lock(mMutex);
+ CallOnSessionClosed();
+}
+
+void WebTransportSessionProxy::CallOnSessionClosed() {
+ mMutex.AssertCurrentThreadOwns();
+
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<WebTransportSessionProxy> self(this);
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::CallOnSessionClosed",
+ [self{std::move(self)}]() { self->CallOnSessionClosedLocked(); }));
+ return;
+ }
+
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ bool cleanly = false;
+ nsAutoCString reason;
+ uint32_t closeStatus = 0;
+
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::NEGOTIATING:
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ case WebTransportSessionProxyState::ACTIVE:
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ MOZ_ASSERT(false, "CallOnSessionClosed cannot be called in this state.");
+ break;
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ listener = mListener;
+ mListener = nullptr;
+ cleanly = mCleanly;
+ reason = mReason;
+ closeStatus = mCloseStatus;
+ ChangeState(WebTransportSessionProxyState::DONE);
+ break;
+ case WebTransportSessionProxyState::DONE:
+ break;
+ }
+
+ if (listener) {
+ // Don't invoke the callback under the lock.
+ MutexAutoUnlock unlock(mMutex);
+ listener->OnSessionClosed(cleanly, closeStatus, reason);
+ }
+}
+
+bool WebTransportSessionProxy::CheckServerCertificateIfNeeded() {
+ if (mServerCertHashes.IsEmpty()) {
+ return true;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ MOZ_ASSERT(httpChannel, "Not a http channel ?");
+ nsCOMPtr<nsITransportSecurityInfo> tsi;
+ httpChannel->GetSecurityInfo(getter_AddRefs(tsi));
+ MOZ_ASSERT(tsi,
+ "We shouln't reach this code before setting the security info.");
+ nsCOMPtr<nsIX509Cert> cert;
+ nsresult rv = tsi->GetServerCert(getter_AddRefs(cert));
+ if (!cert || NS_WARN_IF(NS_FAILED(rv))) return true;
+ nsTArray<uint8_t> certDER;
+ if (NS_FAILED(cert->GetRawDER(certDER))) {
+ return false;
+ }
+ // https://w3c.github.io/webtransport/#compute-a-certificate-hash
+ nsTArray<uint8_t> certHash;
+ if (NS_FAILED(Digest::DigestBuf(SEC_OID_SHA256, certDER.Elements(),
+ certDER.Length(), certHash)) ||
+ certHash.Length() != SHA256_LENGTH) {
+ return false;
+ }
+ auto verifyCertDer = [&certHash](const auto& hash) {
+ return certHash.Length() == hash.Length() &&
+ memcmp(certHash.Elements(), hash.Elements(), certHash.Length()) == 0;
+ };
+
+ // https://w3c.github.io/webtransport/#verify-a-certificate-hash
+ for (const auto& hash : mServerCertHashes) {
+ nsCString algorithm;
+ if (NS_FAILED(hash->GetAlgorithm(algorithm)) || algorithm != "sha-256") {
+ continue;
+ LOG(("Unexpected non-SHA-256 hash"));
+ }
+
+ nsTArray<uint8_t> value;
+ if (NS_FAILED(hash->GetValue(value))) {
+ continue;
+ LOG(("Unexpected corrupted hash"));
+ }
+
+ if (verifyCertDer(value)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void WebTransportSessionProxy::ChangeState(
+ WebTransportSessionProxyState newState) {
+ mMutex.AssertCurrentThreadOwns();
+ LOG(("WebTransportSessionProxy::ChangeState %d -> %d [this=%p]", mState,
+ newState, this));
+ switch (newState) {
+ case WebTransportSessionProxyState::INIT:
+ MOZ_ASSERT(false, "Cannot change into INIT sate.");
+ break;
+ case WebTransportSessionProxyState::NEGOTIATING:
+ MOZ_ASSERT(mState == WebTransportSessionProxyState::INIT,
+ "Only from INIT can be change into NEGOTIATING");
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mListener);
+ break;
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ MOZ_ASSERT(
+ mState == WebTransportSessionProxyState::NEGOTIATING,
+ "Only from NEGOTIATING can be change into NEGOTIATING_SUCCEEDED");
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mWebTransportSession);
+ MOZ_ASSERT(mListener);
+ break;
+ case WebTransportSessionProxyState::ACTIVE:
+ MOZ_ASSERT(mState == WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED,
+ "Only from NEGOTIATING_SUCCEEDED can be change into ACTIVE");
+ MOZ_ASSERT(!mChannel);
+ MOZ_ASSERT(mWebTransportSession);
+ MOZ_ASSERT(mListener);
+ break;
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ MOZ_ASSERT(
+ (mState == WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED) ||
+ (mState == WebTransportSessionProxyState::ACTIVE),
+ "Only from NEGOTIATING_SUCCEEDED and ACTIVE can be change into"
+ " SESSION_CLOSE_PENDING");
+ MOZ_ASSERT(!mChannel);
+ MOZ_ASSERT(mWebTransportSession);
+ MOZ_ASSERT(!mListener);
+ break;
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ MOZ_ASSERT(
+ (mState == WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED) ||
+ (mState == WebTransportSessionProxyState::ACTIVE),
+ "Only from NEGOTIATING_SUCCEEDED and ACTIVE can be change into"
+ " CLOSE_CALLBACK_PENDING");
+ MOZ_ASSERT(!mWebTransportSession);
+ MOZ_ASSERT(mListener);
+ break;
+ case WebTransportSessionProxyState::DONE:
+ MOZ_ASSERT(
+ (mState == WebTransportSessionProxyState::NEGOTIATING) ||
+ (mState ==
+ WebTransportSessionProxyState::SESSION_CLOSE_PENDING) ||
+ (mState == WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING),
+ "Only from NEGOTIATING, SESSION_CLOSE_PENDING and "
+ "CLOSE_CALLBACK_PENDING can be change into DONE");
+ MOZ_ASSERT(!mChannel);
+ MOZ_ASSERT(!mWebTransportSession);
+ MOZ_ASSERT(!mListener);
+ break;
+ }
+ mState = newState;
+}
+
+void WebTransportSessionProxy::NotifyDatagramReceived(
+ nsTArray<uint8_t>&& aData) {
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+
+ if (!mStopRequestCalled) {
+ CopyableTArray<uint8_t> copied(aData);
+ mPendingEvents.AppendElement(
+ [self = RefPtr{this}, data = std::move(copied)]() mutable {
+ self->NotifyDatagramReceived(std::move(data));
+ });
+ return;
+ }
+
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return;
+ }
+ listener = mListener;
+ }
+
+ listener->OnDatagramReceived(aData);
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnDatagramReceivedInternal(
+ nsTArray<uint8_t>&& aData) {
+ MOZ_ASSERT(OnSocketThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mTarget->IsOnCurrentThread()) {
+ return mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::OnDatagramReceived",
+ [self = RefPtr{this}, data{std::move(aData)}]() mutable {
+ self->NotifyDatagramReceived(std::move(data));
+ }));
+ }
+ }
+
+ NotifyDatagramReceived(std::move(aData));
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnDatagramReceived(
+ const nsTArray<uint8_t>& aData) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void WebTransportSessionProxy::OnMaxDatagramSizeInternal(uint64_t aSize) {
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+
+ if (!mStopRequestCalled) {
+ mPendingEvents.AppendElement([self = RefPtr{this}, size(aSize)]() {
+ self->OnMaxDatagramSizeInternal(size);
+ });
+ return;
+ }
+
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return;
+ }
+ listener = mListener;
+ }
+
+ listener->OnMaxDatagramSize(aSize);
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnMaxDatagramSize(uint64_t aSize) {
+ MOZ_ASSERT(OnSocketThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mTarget->IsOnCurrentThread()) {
+ return mTarget->Dispatch(
+ NS_NewRunnableFunction("WebTransportSessionProxy::OnMaxDatagramSize",
+ [self = RefPtr{this}, size(aSize)] {
+ self->OnMaxDatagramSizeInternal(size);
+ }));
+ }
+ }
+
+ OnMaxDatagramSizeInternal(aSize);
+ return NS_OK;
+}
+
+void WebTransportSessionProxy::OnOutgoingDatagramOutComeInternal(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome) {
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return;
+ }
+ listener = mListener;
+ }
+
+ listener->OnOutgoingDatagramOutCome(aId, aOutCome);
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnOutgoingDatagramOutCome(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome) {
+ MOZ_ASSERT(OnSocketThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mTarget->IsOnCurrentThread()) {
+ return mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::OnOutgoingDatagramOutCome",
+ [self = RefPtr{this}, id(aId), outcome(aOutCome)] {
+ self->OnOutgoingDatagramOutComeInternal(id, outcome);
+ }));
+ }
+ }
+
+ OnOutgoingDatagramOutComeInternal(aId, aOutCome);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnStopSending(uint64_t aStreamId,
+ nsresult aError) {
+ MOZ_ASSERT(OnSocketThread());
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return NS_OK;
+ }
+ listener = mListener;
+ }
+
+ listener->OnStopSending(aStreamId, aError);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnResetReceived(uint64_t aStreamId,
+ nsresult aError) {
+ MOZ_ASSERT(OnSocketThread());
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return NS_OK;
+ }
+ listener = mListener;
+ }
+
+ listener->OnResetReceived(aStreamId, aError);
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/webtransport/WebTransportSessionProxy.h b/netwerk/protocol/webtransport/WebTransportSessionProxy.h
new file mode 100644
index 0000000000..aebb446e21
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportSessionProxy.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebTransportProxy_h
+#define mozilla_net_WebTransportProxy_h
+
+#include <functional>
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIRedirectResultListener.h"
+#include "nsIStreamListener.h"
+#include "nsIWebTransport.h"
+
+/*
+ * WebTransportSessionProxy is introduced to enable the creation of a
+ * Http3WebTransportSession and coordination of actions that are performed on
+ * the main thread and on the socket thread.
+ *
+ * mChannel, mRedirectChannel, and mListener are used only on the main thread.
+ *
+ * mWebTransportSession is used only on the socket thread.
+ *
+ * mState and mSessionId are used on both threads, socket and main thread and it
+ * is only used with a lock.
+ *
+ *
+ * WebTransportSessionProxyState:
+ * - INIT: before AsyncConnect is called.
+ *
+ * - NEGOTIATING: It is set during AsyncConnect. During this state HttpChannel
+ * is open but OnStartRequest has not been called yet. This state can
+ * transfer into:
+ * - NEGOTIATING_SUCCEEDED: when a Http3WebTransportSession has been
+ * negotiated.
+ * - DONE: when a WebTransport session has been canceled.
+ *
+ * - NEGOTIATING_SUCCEEDED: It is set during parsing of
+ * Http3WebTransportSession response when the response has been successful.
+ * mWebTransport is set to the Http3WebTransportSession at the same time the
+ * session changes to this state. This state can transfer into:
+ * - ACTIVE: during the OnStopRequest call if the WebTransport has not been
+ * canceled or failed for other reason, e.g. a browser shutdown or content
+ * blocking policies.
+ * - SESSION_CLOSE_PENDING: if the WebTransport has been canceled via an API
+ * call or content blocking policies. (the main thread initiated close).
+ * - CLOSE_CALLBACK_PENDING: if Http3WebTransportSession has been canceled
+ * due to a shutdown or a server closing a session. (the socket thread
+ * initiated close).
+ *
+ * - ACTIVE: In this state the session is negotiated and ready to use. This
+ * state can transfer into:
+ * - SESSION_CLOSE_PENDING: if the WebTransport has been canceled via an API
+ * call(nsIWebTransport::closeSession) or content blocking policies. (the
+ * main thread initiated close).
+ * - CLOSE_CALLBACK_PENDING: if Http3WebTransportSession has been canceled
+ * due to a shutdown or a server closing a session. (the socket thread
+ * initiated close).
+ *
+ * - CLOSE_CALLBACK_PENDING: This is the socket thread initiated close. In this
+ * state, the Http3WebTransportSession has been closed and a
+ * CallOnSessionClosed call is dispatched to the main thread to call the
+ * appropriate listener.
+ *
+ * - SESSION_CLOSE_PENDING: This is the main thread initiated close. In this
+ * state, the WebTransport has been closed via an API call
+ * (nsIWebTransport::closeSession) and a CloseSessionInternal call is
+ * dispatched to the socket thread to close the appropriate
+ * Http3WebTransportSession.
+ *
+ * - DONE: everything has been cleaned up on both threads.
+ *
+ *
+ * AsyncConnect creates mChannel on the main thread. Redirect callbacks are also
+ * performed on the main thread (mRedirectChannel set and access only on the
+ * main thread). Before this point, there are no activities on the socket thread
+ * and Http3WebTransportSession is nullptr. mChannel is going to create a
+ * nsHttpTransaction. The transaction will be dispatched on a nsAHttpConnection,
+ * i.e. currently only the HTTP/3 version is implemented, therefore this will be
+ * a HttpConnectionUDP and a Http3Session. The Http3Session creates a
+ * Http3WebTransportSession. Until a response is received
+ * Http3WebTransportSession is only accessed by Http3Session. During parsing of
+ * a successful received from a server on the socket thread,
+ * WebTransportSessionProxy::mWebTransportSession will take a reference to
+ * Http3WebTransportSession and mState will be set to NEGOTIATING_SUCCEEDED.
+ * From now on WebTransportSessionProxy is responsible for closing
+ * Http3WebTransportSession if the closing of the session is initiated on the
+ * main thread. OnStartRequest and OnStopRequest will be called on the main
+ * thread. The session negotiation can have 2 outcomes:
+ * - If both calls, i.e. OnStartRequest an OnStopRequest, indicate that the
+ * request has succeeded and mState is NEGOTIATING_SUCCEEDED, the
+ * mListener->OnSessionReady will be called during OnStopRequest.
+ * - Otherwise, mListener->OnSessionClosed will be called, the state transferred
+ * into SESSION_CLOSE_PENDING, and CloseSessionInternal will be dispatched to
+ * the socket thread.
+ *
+ * CloseSession is called on the main thread. If the session is already closed
+ * it returns an error. If the session is in state NEGOTIATING or
+ * NEGOTIATING_SUCCEEDED mChannel will be canceled. If the session is in state
+ * NEGOTIATING_SUCCEEDED or ACTIVE the state transferred into
+ * SESSION_CLOSE_PENDING, and CloseSessionInternal will be dispatched to the
+ * socket thread
+ *
+ * OnSessionReadyInternal is called on the socket thread. If mState is
+ * NEGOTIATING the state will be set to NEGOTIATING_SUCCEEDED and mWebTransport
+ * will be set to the newly negotiated Http3WebTransportSession. If mState is
+ * DONE, the Http3WebTransportSession will be close.
+ *
+ * OnSessionClosed is called on the socket thread. mState will be set to
+ * CLOSE_CALLBACK_PENDING and CallOnSessionClosed will be dispatched to the main
+ * thread.
+ *
+ * mWebTransport is set during states NEGOTIATING_SUCCEEDED, ACTIVE and
+ * SESSION_CLOSE_PENDING.
+ */
+
+namespace mozilla::net {
+
+class WebTransportStreamCallbackWrapper;
+
+class WebTransportSessionProxy final : public nsIWebTransport,
+ public WebTransportSessionEventListener,
+ public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIRedirectResultListener,
+ public nsIInterfaceRequestor {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBTRANSPORT
+ NS_DECL_WEBTRANSPORTSESSIONEVENTLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIREDIRECTRESULTLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ WebTransportSessionProxy();
+
+ private:
+ ~WebTransportSessionProxy();
+
+ void CloseSessionInternal();
+ void CloseSessionInternalLocked();
+ void CallOnSessionClosed();
+ void CallOnSessionClosedLocked();
+
+ enum WebTransportSessionProxyState {
+ INIT,
+ NEGOTIATING,
+ NEGOTIATING_SUCCEEDED,
+ ACTIVE,
+ CLOSE_CALLBACK_PENDING,
+ SESSION_CLOSE_PENDING,
+ DONE,
+ };
+ mozilla::Mutex mMutex;
+ WebTransportSessionProxyState mState MOZ_GUARDED_BY(mMutex) =
+ WebTransportSessionProxyState::INIT;
+ void ChangeState(WebTransportSessionProxyState newState);
+ void CreateStreamInternal(nsIWebTransportStreamCallback* callback,
+ bool aBidi);
+ void DoCreateStream(WebTransportStreamCallbackWrapper* aCallback,
+ Http3WebTransportSession* aSession, bool aBidi);
+ void SendDatagramInternal(const RefPtr<Http3WebTransportSession>& aSession,
+ nsTArray<uint8_t>&& aData, uint64_t aTrackingId);
+ void NotifyDatagramReceived(nsTArray<uint8_t>&& aData);
+ void GetMaxDatagramSizeInternal(
+ const RefPtr<Http3WebTransportSession>& aSession);
+ void OnMaxDatagramSizeInternal(uint64_t aSize);
+ void OnOutgoingDatagramOutComeInternal(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome);
+ bool CheckServerCertificateIfNeeded();
+
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsTArray<RefPtr<nsIWebTransportHash>> mServerCertHashes;
+ nsCOMPtr<WebTransportSessionEventListener> mListener MOZ_GUARDED_BY(mMutex);
+ RefPtr<Http3WebTransportSession> mWebTransportSession MOZ_GUARDED_BY(mMutex);
+ uint64_t mSessionId MOZ_GUARDED_BY(mMutex) = UINT64_MAX;
+ uint32_t mCloseStatus MOZ_GUARDED_BY(mMutex) = 0;
+ nsCString mReason MOZ_GUARDED_BY(mMutex);
+ bool mCleanly MOZ_GUARDED_BY(mMutex) = false;
+ bool mStopRequestCalled MOZ_GUARDED_BY(mMutex) = false;
+ // This is used to store events happened before OnSessionReady.
+ // Note that these events will be dispatched to the socket thread.
+ nsTArray<std::function<void()>> mPendingEvents MOZ_GUARDED_BY(mMutex);
+ nsTArray<std::function<void(nsresult)>> mPendingCreateStreamEvents
+ MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsIEventTarget> mTarget MOZ_GUARDED_BY(mMutex);
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_WebTransportProxy_h
diff --git a/netwerk/protocol/webtransport/WebTransportStreamProxy.cpp b/netwerk/protocol/webtransport/WebTransportStreamProxy.cpp
new file mode 100644
index 0000000000..6192bda0f7
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportStreamProxy.cpp
@@ -0,0 +1,386 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebTransportStreamProxy.h"
+
+#include "WebTransportLog.h"
+#include "Http3WebTransportStream.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransportService2.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ADDREF(WebTransportStreamProxy)
+NS_IMPL_RELEASE(WebTransportStreamProxy)
+
+NS_INTERFACE_MAP_BEGIN(WebTransportStreamProxy)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebTransportReceiveStream)
+ NS_INTERFACE_MAP_ENTRY(nsIWebTransportReceiveStream)
+ NS_INTERFACE_MAP_ENTRY(nsIWebTransportSendStream)
+ NS_INTERFACE_MAP_ENTRY(nsIWebTransportBidirectionalStream)
+NS_INTERFACE_MAP_END
+
+WebTransportStreamProxy::WebTransportStreamProxy(
+ Http3WebTransportStream* aStream)
+ : mWebTransportStream(aStream) {
+ nsCOMPtr<nsIAsyncInputStream> inputStream;
+ nsCOMPtr<nsIAsyncOutputStream> outputStream;
+ mWebTransportStream->GetWriterAndReader(getter_AddRefs(outputStream),
+ getter_AddRefs(inputStream));
+ if (outputStream) {
+ mWriter = new AsyncOutputStreamWrapper(outputStream);
+ }
+ if (inputStream) {
+ mReader = new AsyncInputStreamWrapper(inputStream, mWebTransportStream);
+ }
+}
+
+WebTransportStreamProxy::~WebTransportStreamProxy() {
+ // mWebTransportStream needs to be destroyed on the socket thread.
+ NS_ProxyRelease("WebTransportStreamProxy::~WebTransportStreamProxy",
+ gSocketTransportService, mWebTransportStream.forget());
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::SendStopSending(uint8_t aError) {
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ return gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction("WebTransportStreamProxy::SendStopSending",
+ [self{std::move(self)}, error(aError)]() {
+ self->SendStopSending(error);
+ }));
+ }
+
+ mWebTransportStream->SendStopSending(aError);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::SendFin(void) {
+ if (!mWriter) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mWriter->Close();
+
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamProxy::SendFin",
+ [self{std::move(self)}]() { self->mWebTransportStream->SendFin(); }));
+ }
+
+ mWebTransportStream->SendFin();
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::Reset(uint8_t aErrorCode) {
+ if (!mWriter) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mWriter->Close();
+
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ return gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction("WebTransportStreamProxy::Reset",
+ [self{std::move(self)}, error(aErrorCode)]() {
+ self->mWebTransportStream->Reset(error);
+ }));
+ }
+
+ mWebTransportStream->Reset(aErrorCode);
+ return NS_OK;
+}
+
+namespace {
+
+class StatsCallbackWrapper : public nsIWebTransportStreamStatsCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit StatsCallbackWrapper(nsIWebTransportStreamStatsCallback* aCallback)
+ : mCallback(aCallback), mTarget(GetCurrentSerialEventTarget()) {}
+
+ NS_IMETHOD OnSendStatsAvailable(
+ nsIWebTransportSendStreamStats* aStats) override {
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<StatsCallbackWrapper> self(this);
+ nsCOMPtr<nsIWebTransportSendStreamStats> stats = aStats;
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "StatsCallbackWrapper::OnSendStatsAvailable",
+ [self{std::move(self)}, stats{std::move(stats)}]() {
+ self->OnSendStatsAvailable(stats);
+ }));
+ return NS_OK;
+ }
+
+ mCallback->OnSendStatsAvailable(aStats);
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnReceiveStatsAvailable(
+ nsIWebTransportReceiveStreamStats* aStats) override {
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<StatsCallbackWrapper> self(this);
+ nsCOMPtr<nsIWebTransportReceiveStreamStats> stats = aStats;
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "StatsCallbackWrapper::OnReceiveStatsAvailable",
+ [self{std::move(self)}, stats{std::move(stats)}]() {
+ self->OnReceiveStatsAvailable(stats);
+ }));
+ return NS_OK;
+ }
+
+ mCallback->OnReceiveStatsAvailable(aStats);
+ return NS_OK;
+ }
+
+ private:
+ virtual ~StatsCallbackWrapper() {
+ NS_ProxyRelease("StatsCallbackWrapper::~StatsCallbackWrapper", mTarget,
+ mCallback.forget());
+ }
+
+ nsCOMPtr<nsIWebTransportStreamStatsCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+NS_IMPL_ISUPPORTS(StatsCallbackWrapper, nsIWebTransportStreamStatsCallback)
+
+} // namespace
+
+NS_IMETHODIMP WebTransportStreamProxy::GetSendStreamStats(
+ nsIWebTransportStreamStatsCallback* aCallback) {
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ nsCOMPtr<nsIWebTransportStreamStatsCallback> callback =
+ new StatsCallbackWrapper(aCallback);
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamProxy::GetSendStreamStats",
+ [self{std::move(self)}, callback{std::move(callback)}]() {
+ self->GetSendStreamStats(callback);
+ }));
+ }
+
+ nsCOMPtr<nsIWebTransportSendStreamStats> stats =
+ mWebTransportStream->GetSendStreamStats();
+ aCallback->OnSendStatsAvailable(stats);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetReceiveStreamStats(
+ nsIWebTransportStreamStatsCallback* aCallback) {
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ nsCOMPtr<nsIWebTransportStreamStatsCallback> callback =
+ new StatsCallbackWrapper(aCallback);
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamProxy::GetReceiveStreamStats",
+ [self{std::move(self)}, callback{std::move(callback)}]() {
+ self->GetReceiveStreamStats(callback);
+ }));
+ }
+
+ nsCOMPtr<nsIWebTransportReceiveStreamStats> stats =
+ mWebTransportStream->GetReceiveStreamStats();
+ aCallback->OnReceiveStatsAvailable(stats);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetHasReceivedFIN(
+ bool* aHasReceivedFIN) {
+ *aHasReceivedFIN = mWebTransportStream->RecvDone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetInputStream(
+ nsIAsyncInputStream** aOut) {
+ if (!mReader) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<AsyncInputStreamWrapper> stream = mReader;
+ stream.forget(aOut);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetOutputStream(
+ nsIAsyncOutputStream** aOut) {
+ if (!mWriter) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<AsyncOutputStreamWrapper> stream = mWriter;
+ stream.forget(aOut);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetStreamId(uint64_t* aId) {
+ *aId = mWebTransportStream->StreamId();
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::SetSendOrder(Maybe<int64_t> aSendOrder) {
+ if (!OnSocketThread()) {
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "SetSendOrder", [stream = mWebTransportStream, aSendOrder]() {
+ stream->SetSendOrder(aSendOrder);
+ }));
+ }
+ mWebTransportStream->SetSendOrder(aSendOrder);
+ return NS_OK;
+}
+
+//------------------------------------------------------------------------------
+// WebTransportStreamProxy::AsyncInputStreamWrapper
+//------------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(WebTransportStreamProxy::AsyncInputStreamWrapper,
+ nsIInputStream, nsIAsyncInputStream)
+
+WebTransportStreamProxy::AsyncInputStreamWrapper::AsyncInputStreamWrapper(
+ nsIAsyncInputStream* aStream, Http3WebTransportStream* aWebTransportStream)
+ : mStream(aStream), mWebTransportStream(aWebTransportStream) {}
+
+WebTransportStreamProxy::AsyncInputStreamWrapper::~AsyncInputStreamWrapper() =
+ default;
+
+void WebTransportStreamProxy::AsyncInputStreamWrapper::MaybeCloseStream() {
+ if (!mWebTransportStream->RecvDone()) {
+ return;
+ }
+
+ uint64_t available = 0;
+ Unused << Available(&available);
+ if (available) {
+ // Don't close the InputStream if there's unread data available, since it
+ // would be lost. We exit above unless we know no more data will be received
+ // for the stream.
+ return;
+ }
+
+ LOG(
+ ("AsyncInputStreamWrapper::MaybeCloseStream close stream due to FIN "
+ "stream=%p",
+ mWebTransportStream.get()));
+ Close();
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::Close() {
+ return mStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::Available(
+ uint64_t* aAvailable) {
+ return mStream->Available(aAvailable);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::StreamStatus() {
+ return mStream->StreamStatus();
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::Read(
+ char* aBuf, uint32_t aCount, uint32_t* aResult) {
+ LOG(("WebTransportStreamProxy::AsyncInputStreamWrapper::Read %p", this));
+ nsresult rv = mStream->Read(aBuf, aCount, aResult);
+ MaybeCloseStream();
+ return rv;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::ReadSegments(
+ nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
+ uint32_t* aResult) {
+ LOG(("WebTransportStreamProxy::AsyncInputStreamWrapper::ReadSegments %p",
+ this));
+ nsresult rv = mStream->ReadSegments(aWriter, aClosure, aCount, aResult);
+ if (*aResult > 0) {
+ LOG((" Read %u bytes", *aResult));
+ }
+ MaybeCloseStream();
+ return rv;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::IsNonBlocking(
+ bool* aResult) {
+ return mStream->IsNonBlocking(aResult);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::CloseWithStatus(
+ nsresult aStatus) {
+ return mStream->CloseWithStatus(aStatus);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::AsyncWait(
+ nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aEventTarget) {
+ return mStream->AsyncWait(aCallback, aFlags, aRequestedCount, aEventTarget);
+}
+
+//------------------------------------------------------------------------------
+// WebTransportStreamProxy::AsyncOutputStreamWrapper
+//------------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(WebTransportStreamProxy::AsyncOutputStreamWrapper,
+ nsIOutputStream, nsIAsyncOutputStream)
+
+WebTransportStreamProxy::AsyncOutputStreamWrapper::AsyncOutputStreamWrapper(
+ nsIAsyncOutputStream* aStream)
+ : mStream(aStream) {}
+
+WebTransportStreamProxy::AsyncOutputStreamWrapper::~AsyncOutputStreamWrapper() =
+ default;
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::Flush() {
+ return mStream->Flush();
+}
+
+NS_IMETHODIMP
+WebTransportStreamProxy::AsyncOutputStreamWrapper::StreamStatus() {
+ return mStream->StreamStatus();
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::Write(
+ const char* aBuf, uint32_t aCount, uint32_t* aResult) {
+ LOG(
+ ("WebTransportStreamProxy::AsyncOutputStreamWrapper::Write %p %u bytes, "
+ "first byte %c",
+ this, aCount, aBuf[0]));
+ return mStream->Write(aBuf, aCount, aResult);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::WriteFrom(
+ nsIInputStream* aFromStream, uint32_t aCount, uint32_t* aResult) {
+ return mStream->WriteFrom(aFromStream, aCount, aResult);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::WriteSegments(
+ nsReadSegmentFun aReader, void* aClosure, uint32_t aCount,
+ uint32_t* aResult) {
+ return mStream->WriteSegments(aReader, aClosure, aCount, aResult);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::AsyncWait(
+ nsIOutputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aEventTarget) {
+ return mStream->AsyncWait(aCallback, aFlags, aRequestedCount, aEventTarget);
+}
+
+NS_IMETHODIMP
+WebTransportStreamProxy::AsyncOutputStreamWrapper::CloseWithStatus(
+ nsresult aStatus) {
+ return mStream->CloseWithStatus(aStatus);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::Close() {
+ return mStream->Close();
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::IsNonBlocking(
+ bool* aResult) {
+ return mStream->IsNonBlocking(aResult);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/webtransport/WebTransportStreamProxy.h b/netwerk/protocol/webtransport/WebTransportStreamProxy.h
new file mode 100644
index 0000000000..e6ccc573fa
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportStreamProxy.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebTransportStreamProxy_h
+#define mozilla_net_WebTransportStreamProxy_h
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIWebTransportStream.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla::net {
+
+class Http3WebTransportStream;
+
+class WebTransportStreamProxy final
+ : public nsIWebTransportReceiveStream,
+ public nsIWebTransportSendStream,
+ public nsIWebTransportBidirectionalStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit WebTransportStreamProxy(Http3WebTransportStream* aStream);
+
+ NS_IMETHOD SendStopSending(uint8_t aError) override;
+ NS_IMETHOD SendFin() override;
+ NS_IMETHOD Reset(uint8_t aErrorCode) override;
+ NS_IMETHOD GetSendStreamStats(
+ nsIWebTransportStreamStatsCallback* aCallback) override;
+ NS_IMETHOD GetReceiveStreamStats(
+ nsIWebTransportStreamStatsCallback* aCallback) override;
+
+ NS_IMETHOD GetHasReceivedFIN(bool* aHasReceivedFIN) override;
+
+ NS_IMETHOD GetInputStream(nsIAsyncInputStream** aOut) override;
+ NS_IMETHOD GetOutputStream(nsIAsyncOutputStream** aOut) override;
+
+ NS_IMETHOD GetStreamId(uint64_t* aId) override;
+ NS_IMETHOD SetSendOrder(Maybe<int64_t> aSendOrder) override;
+
+ private:
+ virtual ~WebTransportStreamProxy();
+
+ class AsyncInputStreamWrapper : public nsIAsyncInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ AsyncInputStreamWrapper(nsIAsyncInputStream* aStream,
+ Http3WebTransportStream* aWebTransportStream);
+
+ private:
+ virtual ~AsyncInputStreamWrapper();
+ void MaybeCloseStream();
+
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ RefPtr<Http3WebTransportStream> mWebTransportStream;
+ };
+
+ class AsyncOutputStreamWrapper : public nsIAsyncOutputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ explicit AsyncOutputStreamWrapper(nsIAsyncOutputStream* aStream);
+
+ private:
+ virtual ~AsyncOutputStreamWrapper();
+
+ nsCOMPtr<nsIAsyncOutputStream> mStream;
+ };
+
+ RefPtr<Http3WebTransportStream> mWebTransportStream;
+ RefPtr<AsyncOutputStreamWrapper> mWriter;
+ RefPtr<AsyncInputStreamWrapper> mReader;
+};
+
+} // namespace mozilla::net
+
+#endif
diff --git a/netwerk/protocol/webtransport/moz.build b/netwerk/protocol/webtransport/moz.build
new file mode 100644
index 0000000000..2dd99978ac
--- /dev/null
+++ b/netwerk/protocol/webtransport/moz.build
@@ -0,0 +1,36 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: WebTransport")
+
+XPIDL_SOURCES += [
+ "nsIWebTransport.idl",
+ "nsIWebTransportStream.idl",
+]
+
+XPIDL_MODULE = "necko_webtransport"
+
+EXPORTS.mozilla.net += [
+ "WebTransportHash.h",
+ "WebTransportSessionProxy.h",
+ "WebTransportStreamProxy.h",
+]
+
+UNIFIED_SOURCES += [
+ "WebTransportHash.cpp",
+ "WebTransportSessionProxy.cpp",
+ "WebTransportStreamProxy.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/netwerk/protocol/http",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/netwerk/protocol/webtransport/nsIWebTransport.idl b/netwerk/protocol/webtransport/nsIWebTransport.idl
new file mode 100644
index 0000000000..faf99e61b6
--- /dev/null
+++ b/netwerk/protocol/webtransport/nsIWebTransport.idl
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIURI.idl"
+#include "nsIPrincipal.idl"
+
+interface WebTransportSessionEventListener;
+interface nsIWebTransportStreamCallback;
+interface nsIWebTransportBidirectionalStream;
+interface nsIWebTransportSendStream;
+interface nsIWebTransportReceiveStream;
+interface nsIWebTransportHash;
+
+%{C++
+namespace mozilla::dom {
+class ClientInfo;
+}
+namespace mozilla::net {
+class Http3WebTransportSession;
+class Http3WebTransportStream;
+}
+%}
+
+[ptr] native Http3WebTransportSessionPtr(mozilla::net::Http3WebTransportSession);
+[ptr] native Http3WebTransportStreamPtr(mozilla::net::Http3WebTransportStream);
+native Datagram(nsTArray<uint8_t>&&);
+[ref] native const_MaybeClientInfoRef(const mozilla::Maybe<mozilla::dom::ClientInfo>);
+
+[builtinclass, scriptable, uuid(c20d6e77-8cb1-4838-a88d-fff826080aa3)]
+interface nsIWebTransport : nsISupports {
+ cenum WebTransportError : 16 {
+ UNKNOWN_ERROR,
+ INVALID_STATE_ERROR,
+ };
+
+ // When called, perform steps in "Initialization WebTransport over HTTP".
+ void asyncConnect(in nsIURI aURI,
+ in Array<nsIWebTransportHash> aServerCertHashes,
+ in nsIPrincipal aLoadingPrincipal,
+ in unsigned long aSecurityFlags,
+ in WebTransportSessionEventListener aListener);
+
+ void asyncConnectWithClient(in nsIURI aURI,
+ in Array<nsIWebTransportHash> aServerCertHashes,
+ in nsIPrincipal aLoadingPrincipal,
+ in unsigned long aSecurityFlags,
+ in WebTransportSessionEventListener aListener,
+ in const_MaybeClientInfoRef aClientInfo);
+
+ // Asynchronously get stats.
+ void getStats();
+
+ // Close the session.
+ void closeSession(in uint32_t aErrorCode,
+ in ACString aReason);
+
+ // Create and open a new WebTransport stream.
+ void createOutgoingBidirectionalStream(in nsIWebTransportStreamCallback aListener);
+ void createOutgoingUnidirectionalStream(in nsIWebTransportStreamCallback aListener);
+
+ void sendDatagram(in Array<uint8_t> aData, in uint64_t aTrackingId);
+
+ void getMaxDatagramSize();
+
+ // This can be only called after onSessionReady().
+ // After this point, we can retarget the underlying WebTransportSessionProxy
+ // object off main thread.
+ [noscript] void retargetTo(in nsIEventTarget aTarget);
+};
+
+// Events related to a WebTransport session.
+[scriptable, uuid(0e3cb269-f318-43c8-959e-897f57894b71)]
+interface WebTransportSessionEventListener : nsISupports {
+ // This is used to let the consumer of nsIWebTransport know that the
+ // underlying WebTransportSession object is ready to use.
+ void onSessionReady(in uint64_t aSessionId);
+ // This is used internally to pass the reference of WebTransportSession
+ // object to WebTransportSessionProxy.
+ void onSessionReadyInternal(in Http3WebTransportSessionPtr aSession);
+ void onSessionClosed(in bool aCleanly,
+ in uint32_t aErrorCode,
+ in ACString aReason);
+
+ // When a new stream has been received.
+ void onIncomingBidirectionalStreamAvailable(in nsIWebTransportBidirectionalStream aStream);
+ void onIncomingUnidirectionalStreamAvailable(in nsIWebTransportReceiveStream aStream);
+
+ // This is used internally to pass the reference of Http3WebTransportStream
+ // object to WebTransportSessionProxy.
+ void onIncomingStreamAvailableInternal(in Http3WebTransportStreamPtr aStream);
+
+ void onStopSending(in uint64_t aStreamId, in nsresult aError);
+ void onResetReceived(in uint64_t aStreamId, in nsresult aError);
+
+ // When a new datagram has been received.
+ void onDatagramReceived(in Array<uint8_t> aData);
+
+ // This is used internally to pass the datagram to WebTransportSessionProxy.
+ void onDatagramReceivedInternal(in Datagram aData);
+
+ void onMaxDatagramSize(in uint64_t aSize);
+
+ cenum DatagramOutcome: 32 {
+ UNKNOWN = 0,
+ DROPPED_TOO_MUCH_DATA = 1,
+ SENT = 2,
+ };
+
+ void onOutgoingDatagramOutCome(
+ in uint64_t aId,
+ in WebTransportSessionEventListener_DatagramOutcome aOutCome);
+
+ // void onStatsAvailable(in WebTransportStats aStats);
+};
+
+// This interface is used as a callback when creating an outgoing
+// unidirectional or bidirectional stream.
+[scriptable, uuid(c6eeff1d-599b-40a8-9157-c7a40c3d51a2)]
+interface nsIWebTransportStreamCallback : nsISupports {
+ void onBidirectionalStreamReady(in nsIWebTransportBidirectionalStream aStream);
+ void onUnidirectionalStreamReady(in nsIWebTransportSendStream aStream);
+ void onError(in uint8_t aError);
+};
+
+[scriptable, uuid(2523a26e-94be-4de6-8c27-9b4ffff742f0)]
+interface nsIWebTransportHash : nsISupports {
+ readonly attribute ACString algorithm;
+ readonly attribute Array<uint8_t> value;
+};
diff --git a/netwerk/protocol/webtransport/nsIWebTransportStream.idl b/netwerk/protocol/webtransport/nsIWebTransportStream.idl
new file mode 100644
index 0000000000..9283f4aa30
--- /dev/null
+++ b/netwerk/protocol/webtransport/nsIWebTransportStream.idl
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAsyncInputStream;
+interface nsIAsyncOutputStream;
+interface nsIInputStreamCallback;
+interface nsIEventTarget;
+
+%{C++
+#include "mozilla/Maybe.h"
+
+namespace mozilla {
+class TimeStamp;
+}
+%}
+
+native TimeStamp(mozilla::TimeStamp);
+native MaybeInt64(mozilla::Maybe<int64_t>);
+
+[builtinclass, scriptable, uuid(ccc3e685-8411-48f0-8b3e-ff6d1fae4809)]
+interface nsIWebTransportSendStreamStats : nsISupports {
+ [noscript] readonly attribute TimeStamp timestamp;
+ readonly attribute unsigned long long bytesSent;
+ readonly attribute unsigned long long bytesAcknowledged;
+};
+
+[builtinclass, scriptable, uuid(43ce1145-30ef-41a7-b97d-fa797f7f7d18)]
+interface nsIWebTransportReceiveStreamStats : nsISupports {
+ [noscript] readonly attribute TimeStamp timestamp;
+ readonly attribute unsigned long long bytesReceived;
+};
+
+[scriptable, uuid(9c1df3f5-bf04-46b6-9977-eb6389076db8)]
+interface nsIWebTransportStreamStatsCallback : nsISupports {
+ void onSendStatsAvailable(in nsIWebTransportSendStreamStats aStats);
+ void onReceiveStatsAvailable(in nsIWebTransportReceiveStreamStats aStats);
+};
+
+[builtinclass, scriptable, uuid(d461b235-6291-4817-adcc-a2a3b3dfc10b)]
+interface nsIWebTransportReceiveStream : nsISupports {
+ // Sends the STOP_SENDING on the stream.
+ void sendStopSending(in uint8_t aError);
+
+ void getReceiveStreamStats(
+ in nsIWebTransportStreamStatsCallback aCallback);
+
+ // When true, this indicates that FIN had been received.
+ readonly attribute boolean hasReceivedFIN;
+ readonly attribute nsIAsyncInputStream inputStream;
+ readonly attribute uint64_t streamId;
+};
+
+[builtinclass, scriptable, uuid(804f245c-52ea-403c-8a78-f751533bdd70)]
+interface nsIWebTransportSendStream : nsISupports {
+ // Sends the FIN on the stream.
+ void sendFin();
+
+ // Reset the stream with the specified error code.
+ void reset(in uint8_t aErrorCode);
+
+ void getSendStreamStats(in nsIWebTransportStreamStatsCallback aCallback);
+ readonly attribute nsIAsyncOutputStream outputStream;
+ readonly attribute uint64_t streamId;
+ [noscript] void setSendOrder(in MaybeInt64 aSendOrder);
+};
+
+[builtinclass, scriptable, uuid(f9ecb509-36db-4689-97d6-137639a08750)]
+interface nsIWebTransportBidirectionalStream : nsISupports {
+ // Sends the STOP_SENDING on the stream.
+ void sendStopSending(in uint8_t aError);
+
+ // Sends the FIN on the stream.
+ void sendFin();
+
+ // Reset the stream with the specified error code.
+ void reset(in uint8_t aErrorCode);
+
+ // When true, this indicates that FIN had been received.
+ readonly attribute boolean hasReceivedFIN;
+
+ readonly attribute nsIAsyncInputStream inputStream;
+ readonly attribute nsIAsyncOutputStream outputStream;
+ readonly attribute uint64_t streamId;
+ [noscript] void setSendOrder(in MaybeInt64 aSendOrder);
+};