summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /netwerk/protocol
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'netwerk/protocol')
-rw-r--r--netwerk/protocol/about/moz.build34
-rw-r--r--netwerk/protocol/about/nsAboutBlank.cpp59
-rw-r--r--netwerk/protocol/about/nsAboutBlank.h35
-rw-r--r--netwerk/protocol/about/nsAboutCache.cpp585
-rw-r--r--netwerk/protocol/about/nsAboutCache.h136
-rw-r--r--netwerk/protocol/about/nsAboutCacheEntry.cpp603
-rw-r--r--netwerk/protocol/about/nsAboutCacheEntry.h98
-rw-r--r--netwerk/protocol/about/nsAboutProtocolHandler.cpp439
-rw-r--r--netwerk/protocol/about/nsAboutProtocolHandler.h90
-rw-r--r--netwerk/protocol/about/nsAboutProtocolUtils.h71
-rw-r--r--netwerk/protocol/about/nsIAboutModule.idl88
-rw-r--r--netwerk/protocol/data/DataChannelChild.cpp77
-rw-r--r--netwerk/protocol/data/DataChannelChild.h43
-rw-r--r--netwerk/protocol/data/DataChannelParent.cpp89
-rw-r--r--netwerk/protocol/data/DataChannelParent.h41
-rw-r--r--netwerk/protocol/data/moz.build24
-rw-r--r--netwerk/protocol/data/nsDataChannel.cpp91
-rw-r--r--netwerk/protocol/data/nsDataChannel.h28
-rw-r--r--netwerk/protocol/data/nsDataHandler.cpp250
-rw-r--r--netwerk/protocol/data/nsDataHandler.h41
-rw-r--r--netwerk/protocol/data/nsDataModule.cpp21
-rw-r--r--netwerk/protocol/device/AndroidCaptureProvider.cpp301
-rw-r--r--netwerk/protocol/device/AndroidCaptureProvider.h68
-rw-r--r--netwerk/protocol/device/CameraStreamImpl.cpp114
-rw-r--r--netwerk/protocol/device/CameraStreamImpl.h71
-rw-r--r--netwerk/protocol/device/RawStructs.h60
-rw-r--r--netwerk/protocol/device/moz.build27
-rw-r--r--netwerk/protocol/device/nsDeviceCaptureProvider.h31
-rw-r--r--netwerk/protocol/device/nsDeviceChannel.cpp154
-rw-r--r--netwerk/protocol/device/nsDeviceChannel.h26
-rw-r--r--netwerk/protocol/device/nsDeviceProtocolHandler.cpp93
-rw-r--r--netwerk/protocol/device/nsDeviceProtocolHandler.h34
-rw-r--r--netwerk/protocol/file/moz.build30
-rw-r--r--netwerk/protocol/file/nsFileChannel.cpp486
-rw-r--r--netwerk/protocol/file/nsFileChannel.h45
-rw-r--r--netwerk/protocol/file/nsFileProtocolHandler.cpp274
-rw-r--r--netwerk/protocol/file/nsFileProtocolHandler.h27
-rw-r--r--netwerk/protocol/file/nsIFileChannel.idl17
-rw-r--r--netwerk/protocol/file/nsIFileProtocolHandler.idl65
-rw-r--r--netwerk/protocol/ftp/FTPChannelChild.cpp932
-rw-r--r--netwerk/protocol/ftp/FTPChannelChild.h164
-rw-r--r--netwerk/protocol/ftp/FTPChannelParent.cpp896
-rw-r--r--netwerk/protocol/ftp/FTPChannelParent.h146
-rw-r--r--netwerk/protocol/ftp/PFTPChannel.ipdl74
-rw-r--r--netwerk/protocol/ftp/doc/rfc959.txt3933
-rw-r--r--netwerk/protocol/ftp/doc/testdoc4
-rw-r--r--netwerk/protocol/ftp/ftpCore.h15
-rw-r--r--netwerk/protocol/ftp/moz.build45
-rw-r--r--netwerk/protocol/ftp/nsFTPChannel.cpp294
-rw-r--r--netwerk/protocol/ftp/nsFTPChannel.h122
-rw-r--r--netwerk/protocol/ftp/nsFtpConnectionThread.cpp2256
-rw-r--r--netwerk/protocol/ftp/nsFtpConnectionThread.h231
-rw-r--r--netwerk/protocol/ftp/nsFtpControlConnection.cpp189
-rw-r--r--netwerk/protocol/ftp/nsFtpControlConnection.h85
-rw-r--r--netwerk/protocol/ftp/nsFtpProtocolHandler.cpp426
-rw-r--r--netwerk/protocol/ftp/nsFtpProtocolHandler.h87
-rw-r--r--netwerk/protocol/ftp/nsIFTPChannel.idl29
-rw-r--r--netwerk/protocol/ftp/nsIFTPChannelParentInternal.idl15
-rw-r--r--netwerk/protocol/ftp/test/frametest/contents.html5
-rw-r--r--netwerk/protocol/ftp/test/frametest/index.html13
-rw-r--r--netwerk/protocol/ftp/test/frametest/menu.html373
-rw-r--r--netwerk/protocol/http/ASpdySession.cpp127
-rw-r--r--netwerk/protocol/http/ASpdySession.h120
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamChild.cpp151
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamChild.h46
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamParent.cpp71
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamParent.h52
-rw-r--r--netwerk/protocol/http/AlternateServices.cpp1075
-rw-r--r--netwerk/protocol/http/AlternateServices.h191
-rw-r--r--netwerk/protocol/http/CacheControlParser.cpp123
-rw-r--r--netwerk/protocol/http/CacheControlParser.h44
-rw-r--r--netwerk/protocol/http/ConnectionDiagnostics.cpp211
-rw-r--r--netwerk/protocol/http/HSTSPrimerListener.cpp273
-rw-r--r--netwerk/protocol/http/HSTSPrimerListener.h108
-rw-r--r--netwerk/protocol/http/Http2Compression.cpp1487
-rw-r--r--netwerk/protocol/http/Http2Compression.h202
-rw-r--r--netwerk/protocol/http/Http2HuffmanIncoming.h4054
-rw-r--r--netwerk/protocol/http/Http2HuffmanOutgoing.h278
-rw-r--r--netwerk/protocol/http/Http2Push.cpp510
-rw-r--r--netwerk/protocol/http/Http2Push.h129
-rw-r--r--netwerk/protocol/http/Http2Session.cpp3884
-rw-r--r--netwerk/protocol/http/Http2Session.h508
-rw-r--r--netwerk/protocol/http/Http2Stream.cpp1472
-rw-r--r--netwerk/protocol/http/Http2Stream.h346
-rw-r--r--netwerk/protocol/http/HttpBaseChannel.cpp3715
-rw-r--r--netwerk/protocol/http/HttpBaseChannel.h660
-rw-r--r--netwerk/protocol/http/HttpChannelChild.cpp2849
-rw-r--r--netwerk/protocol/http/HttpChannelChild.h390
-rw-r--r--netwerk/protocol/http/HttpChannelParent.cpp1821
-rw-r--r--netwerk/protocol/http/HttpChannelParent.h269
-rw-r--r--netwerk/protocol/http/HttpChannelParentListener.cpp407
-rw-r--r--netwerk/protocol/http/HttpChannelParentListener.h89
-rw-r--r--netwerk/protocol/http/HttpInfo.cpp18
-rw-r--r--netwerk/protocol/http/HttpInfo.h25
-rw-r--r--netwerk/protocol/http/HttpLog.h58
-rw-r--r--netwerk/protocol/http/InterceptedChannel.cpp540
-rw-r--r--netwerk/protocol/http/InterceptedChannel.h134
-rw-r--r--netwerk/protocol/http/NullHttpChannel.cpp772
-rw-r--r--netwerk/protocol/http/NullHttpChannel.h66
-rw-r--r--netwerk/protocol/http/NullHttpTransaction.cpp333
-rw-r--r--netwerk/protocol/http/NullHttpTransaction.h84
-rw-r--r--netwerk/protocol/http/PAltDataOutputStream.ipdl32
-rw-r--r--netwerk/protocol/http/PHttpChannel.ipdl178
-rw-r--r--netwerk/protocol/http/PHttpChannelParams.h222
-rw-r--r--netwerk/protocol/http/PSpdyPush.h56
-rw-r--r--netwerk/protocol/http/README119
-rw-r--r--netwerk/protocol/http/TimingStruct.h41
-rw-r--r--netwerk/protocol/http/TunnelUtils.cpp1678
-rw-r--r--netwerk/protocol/http/TunnelUtils.h250
-rw-r--r--netwerk/protocol/http/UserAgentOverrides.jsm182
-rw-r--r--netwerk/protocol/http/UserAgentUpdates.jsm285
-rw-r--r--netwerk/protocol/http/WellKnownOpportunisticUtils.js43
-rw-r--r--netwerk/protocol/http/WellKnownOpportunisticUtils.manifest3
-rw-r--r--netwerk/protocol/http/http2_huffman_table.txt257
-rw-r--r--netwerk/protocol/http/make_incoming_tables.py194
-rw-r--r--netwerk/protocol/http/make_outgoing_tables.py55
-rw-r--r--netwerk/protocol/http/moz.build121
-rw-r--r--netwerk/protocol/http/nsAHttpConnection.h263
-rw-r--r--netwerk/protocol/http/nsAHttpTransaction.h301
-rw-r--r--netwerk/protocol/http/nsCORSListenerProxy.cpp1526
-rw-r--r--netwerk/protocol/http/nsCORSListenerProxy.h112
-rw-r--r--netwerk/protocol/http/nsHttp.cpp481
-rw-r--r--netwerk/protocol/http/nsHttp.h276
-rw-r--r--netwerk/protocol/http/nsHttpActivityDistributor.cpp135
-rw-r--r--netwerk/protocol/http/nsHttpActivityDistributor.h35
-rw-r--r--netwerk/protocol/http/nsHttpAtomList.h97
-rw-r--r--netwerk/protocol/http/nsHttpAuthCache.cpp607
-rw-r--r--netwerk/protocol/http/nsHttpAuthCache.h259
-rw-r--r--netwerk/protocol/http/nsHttpAuthManager.cpp151
-rw-r--r--netwerk/protocol/http/nsHttpAuthManager.h35
-rw-r--r--netwerk/protocol/http/nsHttpBasicAuth.cpp116
-rw-r--r--netwerk/protocol/http/nsHttpBasicAuth.h32
-rw-r--r--netwerk/protocol/http/nsHttpChannel.cpp8563
-rw-r--r--netwerk/protocol/http/nsHttpChannel.h620
-rw-r--r--netwerk/protocol/http/nsHttpChannelAuthProvider.cpp1682
-rw-r--r--netwerk/protocol/http/nsHttpChannelAuthProvider.h192
-rw-r--r--netwerk/protocol/http/nsHttpChunkedDecoder.cpp168
-rw-r--r--netwerk/protocol/http/nsHttpChunkedDecoder.h56
-rw-r--r--netwerk/protocol/http/nsHttpConnection.cpp2319
-rw-r--r--netwerk/protocol/http/nsHttpConnection.h378
-rw-r--r--netwerk/protocol/http/nsHttpConnectionInfo.cpp339
-rw-r--r--netwerk/protocol/http/nsHttpConnectionInfo.h186
-rw-r--r--netwerk/protocol/http/nsHttpConnectionMgr.cpp4006
-rw-r--r--netwerk/protocol/http/nsHttpConnectionMgr.h636
-rw-r--r--netwerk/protocol/http/nsHttpDigestAuth.cpp722
-rw-r--r--netwerk/protocol/http/nsHttpDigestAuth.h95
-rw-r--r--netwerk/protocol/http/nsHttpHandler.cpp2493
-rw-r--r--netwerk/protocol/http/nsHttpHandler.h683
-rw-r--r--netwerk/protocol/http/nsHttpHeaderArray.cpp441
-rw-r--r--netwerk/protocol/http/nsHttpHeaderArray.h287
-rw-r--r--netwerk/protocol/http/nsHttpNTLMAuth.cpp533
-rw-r--r--netwerk/protocol/http/nsHttpNTLMAuth.h31
-rw-r--r--netwerk/protocol/http/nsHttpPipeline.cpp911
-rw-r--r--netwerk/protocol/http/nsHttpPipeline.h105
-rw-r--r--netwerk/protocol/http/nsHttpRequestHead.cpp369
-rw-r--r--netwerk/protocol/http/nsHttpRequestHead.h128
-rw-r--r--netwerk/protocol/http/nsHttpResponseHead.cpp1221
-rw-r--r--netwerk/protocol/http/nsHttpResponseHead.h195
-rw-r--r--netwerk/protocol/http/nsHttpTransaction.cpp2461
-rw-r--r--netwerk/protocol/http/nsHttpTransaction.h487
-rw-r--r--netwerk/protocol/http/nsICorsPreflightCallback.h28
-rw-r--r--netwerk/protocol/http/nsIHstsPrimingCallback.idl50
-rw-r--r--netwerk/protocol/http/nsIHttpActivityObserver.idl127
-rw-r--r--netwerk/protocol/http/nsIHttpAuthManager.idl115
-rw-r--r--netwerk/protocol/http/nsIHttpAuthenticableChannel.idl122
-rw-r--r--netwerk/protocol/http/nsIHttpAuthenticator.idl223
-rw-r--r--netwerk/protocol/http/nsIHttpChannel.idl472
-rw-r--r--netwerk/protocol/http/nsIHttpChannelAuthProvider.idl79
-rw-r--r--netwerk/protocol/http/nsIHttpChannelChild.idl34
-rw-r--r--netwerk/protocol/http/nsIHttpChannelInternal.idl314
-rw-r--r--netwerk/protocol/http/nsIHttpEventSink.idl37
-rw-r--r--netwerk/protocol/http/nsIHttpHeaderVisitor.idl26
-rw-r--r--netwerk/protocol/http/nsIHttpProtocolHandler.idl126
-rw-r--r--netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl26
-rw-r--r--netwerk/protocol/moz.build7
-rw-r--r--netwerk/protocol/res/ExtensionProtocolHandler.cpp193
-rw-r--r--netwerk/protocol/res/ExtensionProtocolHandler.h42
-rw-r--r--netwerk/protocol/res/SubstitutingProtocolHandler.cpp404
-rw-r--r--netwerk/protocol/res/SubstitutingProtocolHandler.h107
-rw-r--r--netwerk/protocol/res/moz.build26
-rw-r--r--netwerk/protocol/res/nsIResProtocolHandler.idl14
-rw-r--r--netwerk/protocol/res/nsISubstitutingProtocolHandler.idl46
-rw-r--r--netwerk/protocol/res/nsResProtocolHandler.cpp100
-rw-r--r--netwerk/protocol/res/nsResProtocolHandler.h65
-rw-r--r--netwerk/protocol/viewsource/moz.build21
-rw-r--r--netwerk/protocol/viewsource/nsIViewSourceChannel.idl38
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceChannel.cpp1018
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceChannel.h78
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceHandler.cpp175
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceHandler.h45
-rw-r--r--netwerk/protocol/websocket/BaseWebSocketChannel.cpp381
-rw-r--r--netwerk/protocol/websocket/BaseWebSocketChannel.h114
-rw-r--r--netwerk/protocol/websocket/IPCTransportProvider.cpp104
-rw-r--r--netwerk/protocol/websocket/IPCTransportProvider.h92
-rw-r--r--netwerk/protocol/websocket/PTransportProvider.ipdl27
-rw-r--r--netwerk/protocol/websocket/PWebSocket.ipdl70
-rw-r--r--netwerk/protocol/websocket/PWebSocketEventListener.ipdl51
-rw-r--r--netwerk/protocol/websocket/WebSocketChannel.cpp4107
-rw-r--r--netwerk/protocol/websocket/WebSocketChannel.h337
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelChild.cpp723
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelChild.h97
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelParent.cpp306
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelParent.h72
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerChild.cpp117
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerChild.h62
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerParent.cpp129
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerParent.h43
-rw-r--r--netwerk/protocol/websocket/WebSocketEventService.cpp578
-rw-r--r--netwerk/protocol/websocket/WebSocketEventService.h124
-rw-r--r--netwerk/protocol/websocket/WebSocketFrame.cpp169
-rw-r--r--netwerk/protocol/websocket/WebSocketFrame.h79
-rw-r--r--netwerk/protocol/websocket/WebSocketLog.h23
-rw-r--r--netwerk/protocol/websocket/moz.build56
-rw-r--r--netwerk/protocol/websocket/nsITransportProvider.idl36
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketChannel.idl220
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketEventService.idl79
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketListener.idl90
-rw-r--r--netwerk/protocol/wyciwyg/PWyciwygChannel.ipdl62
-rw-r--r--netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp764
-rw-r--r--netwerk/protocol/wyciwyg/WyciwygChannelChild.h124
-rw-r--r--netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp373
-rw-r--r--netwerk/protocol/wyciwyg/WyciwygChannelParent.h71
-rw-r--r--netwerk/protocol/wyciwyg/moz.build36
-rw-r--r--netwerk/protocol/wyciwyg/nsIWyciwygChannel.idl45
-rw-r--r--netwerk/protocol/wyciwyg/nsWyciwyg.cpp10
-rw-r--r--netwerk/protocol/wyciwyg/nsWyciwyg.h43
-rw-r--r--netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp808
-rw-r--r--netwerk/protocol/wyciwyg/nsWyciwygChannel.h115
-rw-r--r--netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.cpp158
-rw-r--r--netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.h23
230 files changed, 96417 insertions, 0 deletions
diff --git a/netwerk/protocol/about/moz.build b/netwerk/protocol/about/moz.build
new file mode 100644
index 000000000..60a10c9cc
--- /dev/null
+++ b/netwerk/protocol/about/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIAboutModule.idl',
+]
+
+XPIDL_MODULE = 'necko_about'
+
+EXPORTS += [
+ 'nsAboutProtocolUtils.h',
+]
+
+UNIFIED_SOURCES += [
+ 'nsAboutBlank.cpp',
+ 'nsAboutCache.cpp',
+ 'nsAboutCacheEntry.cpp',
+ 'nsAboutProtocolHandler.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+ '/netwerk/cache2',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/protocol/about/nsAboutBlank.cpp b/netwerk/protocol/about/nsAboutBlank.cpp
new file mode 100644
index 000000000..be10be9ac
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutBlank.cpp
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAboutBlank.h"
+#include "nsStringStream.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+
+NS_IMPL_ISUPPORTS(nsAboutBlank, nsIAboutModule)
+
+NS_IMETHODIMP
+nsAboutBlank::NewChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIInputStream> in;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(in), EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel),
+ aURI,
+ in,
+ NS_LITERAL_CSTRING("text/html"),
+ NS_LITERAL_CSTRING("utf-8"),
+ aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ channel.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAboutBlank::GetURIFlags(nsIURI *aURI, uint32_t *result)
+{
+ *result = nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
+ nsIAboutModule::MAKE_LINKABLE |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT;
+ return NS_OK;
+}
+
+nsresult
+nsAboutBlank::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ nsAboutBlank* about = new nsAboutBlank();
+ if (about == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(about);
+ nsresult rv = about->QueryInterface(aIID, aResult);
+ NS_RELEASE(about);
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/protocol/about/nsAboutBlank.h b/netwerk/protocol/about/nsAboutBlank.h
new file mode 100644
index 000000000..aae6e072d
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutBlank.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAboutBlank_h__
+#define nsAboutBlank_h__
+
+#include "nsIAboutModule.h"
+
+class nsAboutBlank : public nsIAboutModule
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIABOUTMODULE
+
+ nsAboutBlank() {}
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+private:
+ virtual ~nsAboutBlank() {}
+};
+
+#define NS_ABOUT_BLANK_MODULE_CID \
+{ /* 3decd6c8-30ef-11d3-8cd0-0060b0fc14a3 */ \
+ 0x3decd6c8, \
+ 0x30ef, \
+ 0x11d3, \
+ {0x8c, 0xd0, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3} \
+}
+
+#endif // nsAboutBlank_h__
diff --git a/netwerk/protocol/about/nsAboutCache.cpp b/netwerk/protocol/about/nsAboutCache.cpp
new file mode 100644
index 000000000..2eb5e3b42
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCache.cpp
@@ -0,0 +1,585 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAboutCache.h"
+#include "nsIInputStream.h"
+#include "nsIStorageStream.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsNetUtil.h"
+#include "nsIPipe.h"
+#include "nsContentUtils.h"
+#include "nsEscape.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsPrintfCString.h"
+
+#include "nsICacheStorageService.h"
+#include "nsICacheStorage.h"
+#include "CacheFileUtils.h"
+#include "CacheObserver.h"
+
+#include "nsThreadUtils.h"
+
+using namespace mozilla::net;
+
+NS_IMPL_ISUPPORTS(nsAboutCache, nsIAboutModule)
+NS_IMPL_ISUPPORTS(nsAboutCache::Channel, nsIChannel, nsIRequest, nsICacheStorageVisitor)
+
+NS_IMETHODIMP
+nsAboutCache::NewChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ RefPtr<Channel> channel = new Channel();
+ rv = channel->Init(aURI, aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ channel.forget(result);
+
+ return NS_OK;
+}
+
+nsresult
+nsAboutCache::Channel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo)
+{
+ nsresult rv;
+
+ mCancel = false;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(mStream),
+ 16384, (uint32_t)-1,
+ true, // non-blocking input
+ false // blocking output
+ );
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString storageName;
+ rv = ParseURI(aURI, storageName);
+ if (NS_FAILED(rv)) return rv;
+
+ mOverview = storageName.IsEmpty();
+ if (mOverview) {
+ // ...and visit all we can
+ mStorageList.AppendElement(NS_LITERAL_CSTRING("memory"));
+ mStorageList.AppendElement(NS_LITERAL_CSTRING("disk"));
+ mStorageList.AppendElement(NS_LITERAL_CSTRING("appcache"));
+ } else {
+ // ...and visit just the specified storage, entries will output too
+ mStorageList.AppendElement(storageName);
+ }
+
+ // The entries header is added on encounter of the first entry
+ mEntriesHeaderAdded = false;
+
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel),
+ aURI,
+ inputStream,
+ NS_LITERAL_CSTRING("text/html"),
+ NS_LITERAL_CSTRING("utf-8"),
+ aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ mBuffer.AssignLiteral(
+ "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ " <title>Network Cache Storage Information</title>\n"
+ " <meta charset=\"utf-8\">\n"
+ " <link rel=\"stylesheet\" href=\"chrome://global/skin/about.css\"/>\n"
+ " <link rel=\"stylesheet\" href=\"chrome://global/skin/aboutCache.css\"/>\n"
+ " <script src=\"chrome://global/content/aboutCache.js\"></script>"
+ "</head>\n"
+ "<body class=\"aboutPageWideContainer\">\n"
+ "<h1>Information about the Network Cache Storage Service</h1>\n");
+
+ // Add the context switch controls
+ mBuffer.AppendLiteral(
+ "<label><input id='priv' type='checkbox'/> Private</label>\n"
+ "<label><input id='anon' type='checkbox'/> Anonymous</label>\n"
+ );
+
+ if (CacheObserver::UseNewCache()) {
+ // Visit scoping by browser and appid is not implemented for
+ // the old cache, simply don't add these controls.
+ // The appid/inbrowser entries are already mixed in the default
+ // view anyway.
+ mBuffer.AppendLiteral(
+ "<label><input id='appid' type='text' size='6'/> AppID</label>\n"
+ "<label><input id='inbrowser' type='checkbox'/> In Browser Element</label>\n"
+ );
+ }
+
+ mBuffer.AppendLiteral(
+ "<label><input id='submit' type='button' value='Update' onclick='navigate()'/></label>\n"
+ );
+
+ if (!mOverview) {
+ mBuffer.AppendLiteral("<a href=\"about:cache?storage=&amp;context=");
+ char* escapedContext = nsEscapeHTML(mContextString.get());
+ mBuffer.Append(escapedContext);
+ free(escapedContext);
+ mBuffer.AppendLiteral("\">Back to overview</a>");
+ }
+
+ rv = FlushBuffer();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to flush buffer");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAboutCache::Channel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext)
+{
+ nsresult rv;
+
+ if (!mChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Kick the walk loop.
+ rv = VisitNextStorage();
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_ASSERT(!aContext, "asyncOpen2() does not take a context argument");
+ rv = NS_MaybeOpenChannelUsingAsyncOpen2(mChannel, aListener);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAboutCache::Channel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ return AsyncOpen(aListener, nullptr);
+}
+
+NS_IMETHODIMP nsAboutCache::Channel::Open(nsIInputStream * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAboutCache::Channel::Open2(nsIInputStream * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsAboutCache::Channel::ParseURI(nsIURI * uri, nsACString & storage)
+{
+ //
+ // about:cache[?storage=<storage-name>[&context=<context-key>]]
+ //
+ nsresult rv;
+
+ nsAutoCString path;
+ rv = uri->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ mContextString.Truncate();
+ mLoadInfo = CacheFileUtils::ParseKey(NS_LITERAL_CSTRING(""));
+ storage.Truncate();
+
+ nsACString::const_iterator start, valueStart, end;
+ path.BeginReading(start);
+ path.EndReading(end);
+
+ valueStart = end;
+ if (!FindInReadable(NS_LITERAL_CSTRING("?storage="), start, valueStart)) {
+ return NS_OK;
+ }
+
+ nsACString::const_iterator storageNameBegin = valueStart;
+
+ start = valueStart;
+ valueStart = end;
+ if (!FindInReadable(NS_LITERAL_CSTRING("&context="), start, valueStart))
+ start = end;
+
+ nsACString::const_iterator storageNameEnd = start;
+
+ mContextString = Substring(valueStart, end);
+ mLoadInfo = CacheFileUtils::ParseKey(mContextString);
+ storage.Assign(Substring(storageNameBegin, storageNameEnd));
+
+ return NS_OK;
+}
+
+nsresult
+nsAboutCache::Channel::VisitNextStorage()
+{
+ if (!mStorageList.Length())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ mStorageName = mStorageList[0];
+ mStorageList.RemoveElementAt(0);
+
+ // Must re-dispatch since we cannot start another visit cycle
+ // from visitor callback. The cache v1 service doesn't like it.
+ // TODO - mayhemer, bug 913828, remove this dispatch and call
+ // directly.
+ return NS_DispatchToMainThread(mozilla::NewRunnableMethod(this, &nsAboutCache::Channel::FireVisitStorage));
+}
+
+void
+nsAboutCache::Channel::FireVisitStorage()
+{
+ nsresult rv;
+
+ rv = VisitStorage(mStorageName);
+ if (NS_FAILED(rv)) {
+ if (mLoadInfo) {
+ char* escaped = nsEscapeHTML(mStorageName.get());
+ mBuffer.Append(
+ nsPrintfCString("<p>Unrecognized storage name '%s' in about:cache URL</p>",
+ escaped));
+ free(escaped);
+ } else {
+ char* escaped = nsEscapeHTML(mContextString.get());
+ mBuffer.Append(
+ nsPrintfCString("<p>Unrecognized context key '%s' in about:cache URL</p>",
+ escaped));
+ free(escaped);
+ }
+
+ rv = FlushBuffer();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to flush buffer");
+ }
+
+ // Simulate finish of a visit cycle, this tries the next storage
+ // or closes the output stream (i.e. the UI loader will stop spinning)
+ OnCacheEntryVisitCompleted();
+ }
+}
+
+nsresult
+nsAboutCache::Channel::VisitStorage(nsACString const & storageName)
+{
+ nsresult rv;
+
+ rv = GetStorage(storageName, mLoadInfo, getter_AddRefs(mStorage));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mStorage->AsyncVisitStorage(this, !mOverview);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+//static
+nsresult
+nsAboutCache::GetStorage(nsACString const & storageName,
+ nsILoadContextInfo* loadInfo,
+ nsICacheStorage **storage)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICacheStorageService> cacheService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ if (storageName == "disk") {
+ rv = cacheService->DiskCacheStorage(
+ loadInfo, false, getter_AddRefs(cacheStorage));
+ } else if (storageName == "memory") {
+ rv = cacheService->MemoryCacheStorage(
+ loadInfo, getter_AddRefs(cacheStorage));
+ } else if (storageName == "appcache") {
+ rv = cacheService->AppCacheStorage(
+ loadInfo, nullptr, getter_AddRefs(cacheStorage));
+ } else {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ cacheStorage.forget(storage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCache::Channel::OnCacheStorageInfo(uint32_t aEntryCount, uint64_t aConsumption,
+ uint64_t aCapacity, nsIFile * aDirectory)
+{
+ // We need mStream for this
+ if (!mStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mBuffer.AssignLiteral("<h2>");
+ mBuffer.Append(mStorageName);
+ mBuffer.AppendLiteral("</h2>\n"
+ "<table id=\"");
+ mBuffer.AppendLiteral("\">\n");
+
+ // Write out cache info
+ // Number of entries
+ mBuffer.AppendLiteral(" <tr>\n"
+ " <th>Number of entries:</th>\n"
+ " <td>");
+ mBuffer.AppendInt(aEntryCount);
+ mBuffer.AppendLiteral("</td>\n"
+ " </tr>\n");
+
+ // Maximum storage size
+ mBuffer.AppendLiteral(" <tr>\n"
+ " <th>Maximum storage size:</th>\n"
+ " <td>");
+ mBuffer.AppendInt(aCapacity / 1024);
+ mBuffer.AppendLiteral(" KiB</td>\n"
+ " </tr>\n");
+
+ // Storage in use
+ mBuffer.AppendLiteral(" <tr>\n"
+ " <th>Storage in use:</th>\n"
+ " <td>");
+ mBuffer.AppendInt(aConsumption / 1024);
+ mBuffer.AppendLiteral(" KiB</td>\n"
+ " </tr>\n");
+
+ // Storage disk location
+ mBuffer.AppendLiteral(" <tr>\n"
+ " <th>Storage disk location:</th>\n"
+ " <td>");
+ if (aDirectory) {
+ nsAutoString path;
+ aDirectory->GetPath(path);
+ mBuffer.Append(NS_ConvertUTF16toUTF8(path));
+ } else {
+ mBuffer.AppendLiteral("none, only stored in memory");
+ }
+ mBuffer.AppendLiteral(" </td>\n"
+ " </tr>\n");
+
+ if (mOverview) { // The about:cache case
+ if (aEntryCount != 0) { // Add the "List Cache Entries" link
+ mBuffer.AppendLiteral(" <tr>\n"
+ " <th><a href=\"about:cache?storage=");
+ mBuffer.Append(mStorageName);
+ mBuffer.AppendLiteral("&amp;context=");
+ char* escapedContext = nsEscapeHTML(mContextString.get());
+ mBuffer.Append(escapedContext);
+ free(escapedContext);
+ mBuffer.AppendLiteral("\">List Cache Entries</a></th>\n"
+ " </tr>\n");
+ }
+ }
+
+ mBuffer.AppendLiteral("</table>\n");
+
+ // The entries header is added on encounter of the first entry
+ mEntriesHeaderAdded = false;
+
+ nsresult rv = FlushBuffer();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to flush buffer");
+ }
+
+ if (mOverview) {
+ // OnCacheEntryVisitCompleted() is not called when we do not iterate
+ // cache entries. Since this moves forward to the next storage in
+ // the list we want to visit, artificially call it here.
+ OnCacheEntryVisitCompleted();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCache::Channel::OnCacheEntryInfo(nsIURI *aURI, const nsACString & aIdEnhance,
+ int64_t aDataSize, int32_t aFetchCount,
+ uint32_t aLastModified, uint32_t aExpirationTime,
+ bool aPinned)
+{
+ // We need mStream for this
+ if (!mStream || mCancel) {
+ // Returning a failure from this callback stops the iteration
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mEntriesHeaderAdded) {
+ mBuffer.AppendLiteral("<hr/>\n"
+ "<table id=\"entries\">\n"
+ " <colgroup>\n"
+ " <col id=\"col-key\">\n"
+ " <col id=\"col-dataSize\">\n"
+ " <col id=\"col-fetchCount\">\n"
+ " <col id=\"col-lastModified\">\n"
+ " <col id=\"col-expires\">\n"
+ " <col id=\"col-pinned\">\n"
+ " </colgroup>\n"
+ " <thead>\n"
+ " <tr>\n"
+ " <th>Key</th>\n"
+ " <th>Data size</th>\n"
+ " <th>Fetch count</th>\n"
+ " <th>Last Modifed</th>\n"
+ " <th>Expires</th>\n"
+ " <th>Pinning</th>\n"
+ " </tr>\n"
+ " </thead>\n");
+ mEntriesHeaderAdded = true;
+ }
+
+ // Generate a about:cache-entry URL for this entry...
+
+ nsAutoCString url;
+ url.AssignLiteral("about:cache-entry?storage=");
+ url.Append(mStorageName);
+
+ url.AppendLiteral("&amp;context=");
+ char* escapedContext = nsEscapeHTML(mContextString.get());
+ url += escapedContext;
+ free(escapedContext);
+
+ url.AppendLiteral("&amp;eid=");
+ char* escapedEID = nsEscapeHTML(aIdEnhance.BeginReading());
+ url += escapedEID;
+ free(escapedEID);
+
+ nsAutoCString cacheUriSpec;
+ aURI->GetAsciiSpec(cacheUriSpec);
+ char* escapedCacheURI = nsEscapeHTML(cacheUriSpec.get());
+ url.AppendLiteral("&amp;uri=");
+ url += escapedCacheURI;
+
+ // Entry start...
+ mBuffer.AppendLiteral(" <tr>\n");
+
+ // URI
+ mBuffer.AppendLiteral(" <td><a href=\"");
+ mBuffer.Append(url);
+ mBuffer.AppendLiteral("\">");
+ if (!aIdEnhance.IsEmpty()) {
+ mBuffer.Append(aIdEnhance);
+ mBuffer.Append(':');
+ }
+ mBuffer.Append(escapedCacheURI);
+ mBuffer.AppendLiteral("</a></td>\n");
+
+ free(escapedCacheURI);
+
+ // Content length
+ mBuffer.AppendLiteral(" <td>");
+ mBuffer.AppendInt(aDataSize);
+ mBuffer.AppendLiteral(" bytes</td>\n");
+
+ // Number of accesses
+ mBuffer.AppendLiteral(" <td>");
+ mBuffer.AppendInt(aFetchCount);
+ mBuffer.AppendLiteral("</td>\n");
+
+ // vars for reporting time
+ char buf[255];
+
+ // Last modified time
+ mBuffer.AppendLiteral(" <td>");
+ if (aLastModified) {
+ PrintTimeString(buf, sizeof(buf), aLastModified);
+ mBuffer.Append(buf);
+ } else {
+ mBuffer.AppendLiteral("No last modified time");
+ }
+ mBuffer.AppendLiteral("</td>\n");
+
+ // Expires time
+ mBuffer.AppendLiteral(" <td>");
+ if (aExpirationTime < 0xFFFFFFFF) {
+ PrintTimeString(buf, sizeof(buf), aExpirationTime);
+ mBuffer.Append(buf);
+ } else {
+ mBuffer.AppendLiteral("No expiration time");
+ }
+ mBuffer.AppendLiteral("</td>\n");
+
+ // Pinning
+ mBuffer.AppendLiteral(" <td>");
+ if (aPinned) {
+ mBuffer.Append(NS_LITERAL_CSTRING("Pinned"));
+ } else {
+ mBuffer.Append(NS_LITERAL_CSTRING("&nbsp;"));
+ }
+ mBuffer.AppendLiteral("</td>\n");
+
+ // Entry is done...
+ mBuffer.AppendLiteral(" </tr>\n");
+
+ return FlushBuffer();
+}
+
+NS_IMETHODIMP
+nsAboutCache::Channel::OnCacheEntryVisitCompleted()
+{
+ if (!mStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mEntriesHeaderAdded) {
+ mBuffer.AppendLiteral("</table>\n");
+ }
+
+ // Kick another storage visiting (from a storage that allows us.)
+ while (mStorageList.Length()) {
+ nsresult rv = VisitNextStorage();
+ if (NS_SUCCEEDED(rv)) {
+ // Expecting new round of OnCache* calls.
+ return NS_OK;
+ }
+ }
+
+ // We are done!
+ mBuffer.AppendLiteral("</body>\n"
+ "</html>\n");
+ nsresult rv = FlushBuffer();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to flush buffer");
+ }
+ mStream->Close();
+
+ return NS_OK;
+}
+
+nsresult
+nsAboutCache::Channel::FlushBuffer()
+{
+ nsresult rv;
+
+ uint32_t bytesWritten;
+ rv = mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
+ mBuffer.Truncate();
+
+ if (NS_FAILED(rv)) {
+ mCancel = true;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAboutCache::GetURIFlags(nsIURI *aURI, uint32_t *result)
+{
+ *result = nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT;
+ return NS_OK;
+}
+
+// static
+nsresult
+nsAboutCache::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ nsAboutCache* about = new nsAboutCache();
+ if (about == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(about);
+ nsresult rv = about->QueryInterface(aIID, aResult);
+ NS_RELEASE(about);
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/protocol/about/nsAboutCache.h b/netwerk/protocol/about/nsAboutCache.h
new file mode 100644
index 000000000..c2d1af850
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCache.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAboutCache_h__
+#define nsAboutCache_h__
+
+#include "nsIAboutModule.h"
+#include "nsICacheStorageVisitor.h"
+#include "nsICacheStorage.h"
+
+#include "nsString.h"
+#include "nsIOutputStream.h"
+#include "nsILoadContextInfo.h"
+
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+#define NS_FORWARD_SAFE_NSICHANNEL_SUBSET(_to) \
+ NS_IMETHOD GetOriginalURI(nsIURI * *aOriginalURI) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetOriginalURI(aOriginalURI); } \
+ NS_IMETHOD SetOriginalURI(nsIURI *aOriginalURI) override { return !_to ? NS_ERROR_NULL_POINTER : _to->SetOriginalURI(aOriginalURI); } \
+ NS_IMETHOD GetURI(nsIURI * *aURI) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetURI(aURI); } \
+ NS_IMETHOD GetOwner(nsISupports * *aOwner) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetOwner(aOwner); } \
+ NS_IMETHOD SetOwner(nsISupports *aOwner) override { return !_to ? NS_ERROR_NULL_POINTER : _to->SetOwner(aOwner); } \
+ NS_IMETHOD GetNotificationCallbacks(nsIInterfaceRequestor * *aNotificationCallbacks) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetNotificationCallbacks(aNotificationCallbacks); } \
+ NS_IMETHOD SetNotificationCallbacks(nsIInterfaceRequestor *aNotificationCallbacks) override { return !_to ? NS_ERROR_NULL_POINTER : _to->SetNotificationCallbacks(aNotificationCallbacks); } \
+ NS_IMETHOD GetSecurityInfo(nsISupports * *aSecurityInfo) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetSecurityInfo(aSecurityInfo); } \
+ NS_IMETHOD GetContentType(nsACString & aContentType) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetContentType(aContentType); } \
+ NS_IMETHOD SetContentType(const nsACString & aContentType) override { return !_to ? NS_ERROR_NULL_POINTER : _to->SetContentType(aContentType); } \
+ NS_IMETHOD GetContentCharset(nsACString & aContentCharset) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetContentCharset(aContentCharset); } \
+ NS_IMETHOD SetContentCharset(const nsACString & aContentCharset) override { return !_to ? NS_ERROR_NULL_POINTER : _to->SetContentCharset(aContentCharset); } \
+ NS_IMETHOD GetContentLength(int64_t *aContentLength) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetContentLength(aContentLength); } \
+ NS_IMETHOD SetContentLength(int64_t aContentLength) override { return !_to ? NS_ERROR_NULL_POINTER : _to->SetContentLength(aContentLength); } \
+ NS_IMETHOD GetContentDisposition(uint32_t *aContentDisposition) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetContentDisposition(aContentDisposition); } \
+ NS_IMETHOD SetContentDisposition(uint32_t aContentDisposition) override { return !_to ? NS_ERROR_NULL_POINTER : _to->SetContentDisposition(aContentDisposition); } \
+ NS_IMETHOD GetContentDispositionFilename(nsAString & aContentDispositionFilename) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetContentDispositionFilename(aContentDispositionFilename); } \
+ NS_IMETHOD SetContentDispositionFilename(const nsAString & aContentDispositionFilename) override { return !_to ? NS_ERROR_NULL_POINTER : _to->SetContentDispositionFilename(aContentDispositionFilename); } \
+ NS_IMETHOD GetContentDispositionHeader(nsACString & aContentDispositionHeader) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetContentDispositionHeader(aContentDispositionHeader); } \
+ NS_IMETHOD GetLoadInfo(nsILoadInfo * *aLoadInfo) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetLoadInfo(aLoadInfo); } \
+ NS_IMETHOD SetLoadInfo(nsILoadInfo *aLoadInfo) override { return !_to ? NS_ERROR_NULL_POINTER : _to->SetLoadInfo(aLoadInfo); } \
+
+class nsAboutCache final : public nsIAboutModule
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABOUTMODULE
+
+ nsAboutCache() {}
+
+ static MOZ_MUST_USE nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+ static MOZ_MUST_USE nsresult
+ GetStorage(nsACString const & storageName, nsILoadContextInfo* loadInfo,
+ nsICacheStorage **storage);
+
+protected:
+ virtual ~nsAboutCache() {}
+
+ class Channel final : public nsIChannel
+ , public nsICacheStorageVisitor
+ {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHESTORAGEVISITOR
+ NS_FORWARD_SAFE_NSIREQUEST(mChannel)
+ NS_FORWARD_SAFE_NSICHANNEL_SUBSET(mChannel)
+ NS_IMETHOD AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext) override;
+ NS_IMETHOD AsyncOpen2(nsIStreamListener *aListener) override;
+ NS_IMETHOD Open(nsIInputStream * *_retval) override;
+ NS_IMETHOD Open2(nsIInputStream * *_retval) override;
+
+ private:
+ virtual ~Channel() {}
+
+ public:
+ MOZ_MUST_USE nsresult Init(nsIURI* aURI, nsILoadInfo* aLoadInfo);
+ MOZ_MUST_USE nsresult ParseURI(nsIURI * uri, nsACString & storage);
+
+ // Finds a next storage we wish to visit (we use this method
+ // even there is a specified storage name, which is the only
+ // one in the list then.) Posts FireVisitStorage() when found.
+ MOZ_MUST_USE nsresult VisitNextStorage();
+ // Helper method that calls VisitStorage() for the current storage.
+ // When it fails, OnCacheEntryVisitCompleted is simulated to close
+ // the output stream and thus the about:cache channel.
+ void FireVisitStorage();
+ // Kiks the visit cycle for the given storage, names can be:
+ // "disk", "memory", "appcache"
+ // Note: any newly added storage type has to be manually handled here.
+ MOZ_MUST_USE nsresult VisitStorage(nsACString const & storageName);
+
+ // Writes content of mBuffer to mStream and truncates
+ // the buffer. It may fail when the input stream is closed by canceling
+ // the input stream channel. It can be used to stop the cache iteration
+ // process.
+ MOZ_MUST_USE nsresult FlushBuffer();
+
+ // Whether we are showing overview status of all available
+ // storages.
+ bool mOverview;
+
+ // Flag initially false, that indicates the entries header has
+ // been added to the output HTML.
+ bool mEntriesHeaderAdded;
+
+ // Cancelation flag
+ bool mCancel;
+
+ // The context we are working with.
+ nsCOMPtr<nsILoadContextInfo> mLoadInfo;
+ nsCString mContextString;
+
+ // The list of all storage names we want to visit
+ nsTArray<nsCString> mStorageList;
+ nsCString mStorageName;
+ nsCOMPtr<nsICacheStorage> mStorage;
+
+ // Output data buffering and streaming output
+ nsCString mBuffer;
+ nsCOMPtr<nsIOutputStream> mStream;
+
+ // The input stream channel, the one that actually does the job
+ nsCOMPtr<nsIChannel> mChannel;
+ };
+};
+
+#define NS_ABOUT_CACHE_MODULE_CID \
+{ /* 9158c470-86e4-11d4-9be2-00e09872a416 */ \
+ 0x9158c470, \
+ 0x86e4, \
+ 0x11d4, \
+ {0x9b, 0xe2, 0x00, 0xe0, 0x98, 0x72, 0xa4, 0x16} \
+}
+
+#endif // nsAboutCache_h__
diff --git a/netwerk/protocol/about/nsAboutCacheEntry.cpp b/netwerk/protocol/about/nsAboutCacheEntry.cpp
new file mode 100644
index 000000000..183395976
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCacheEntry.cpp
@@ -0,0 +1,603 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAboutCacheEntry.h"
+
+#include "mozilla/Sprintf.h"
+
+#include "nsAboutCache.h"
+#include "nsICacheStorage.h"
+#include "CacheObserver.h"
+#include "nsNetUtil.h"
+#include "nsEscape.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsContentUtils.h"
+#include "nsInputStreamPump.h"
+#include "CacheFileUtils.h"
+#include <algorithm>
+#include "nsIPipe.h"
+
+using namespace mozilla::net;
+
+#define HEXDUMP_MAX_ROWS 16
+
+static void
+HexDump(uint32_t *state, const char *buf, int32_t n, nsCString &result)
+{
+ char temp[16];
+
+ const unsigned char *p;
+ while (n) {
+ SprintfLiteral(temp, "%08x: ", *state);
+ result.Append(temp);
+ *state += HEXDUMP_MAX_ROWS;
+
+ p = (const unsigned char *) buf;
+
+ int32_t i, row_max = std::min(HEXDUMP_MAX_ROWS, n);
+
+ // print hex codes:
+ for (i = 0; i < row_max; ++i) {
+ SprintfLiteral(temp, "%02x ", *p++);
+ result.Append(temp);
+ }
+ for (i = row_max; i < HEXDUMP_MAX_ROWS; ++i) {
+ result.AppendLiteral(" ");
+ }
+
+ // print ASCII glyphs if possible:
+ p = (const unsigned char *) buf;
+ for (i = 0; i < row_max; ++i, ++p) {
+ switch (*p) {
+ case '<':
+ result.AppendLiteral("&lt;");
+ break;
+ case '>':
+ result.AppendLiteral("&gt;");
+ break;
+ case '&':
+ result.AppendLiteral("&amp;");
+ break;
+ default:
+ if (*p < 0x7F && *p > 0x1F) {
+ result.Append(*p);
+ } else {
+ result.Append('.');
+ }
+ }
+ }
+
+ result.Append('\n');
+
+ buf += row_max;
+ n -= row_max;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsAboutCacheEntry::nsISupports
+
+NS_IMPL_ISUPPORTS(nsAboutCacheEntry,
+ nsIAboutModule)
+NS_IMPL_ISUPPORTS(nsAboutCacheEntry::Channel,
+ nsICacheEntryOpenCallback,
+ nsICacheEntryMetaDataVisitor,
+ nsIStreamListener,
+ nsIChannel)
+
+//-----------------------------------------------------------------------------
+// nsAboutCacheEntry::nsIAboutModule
+
+NS_IMETHODIMP
+nsAboutCacheEntry::NewChannel(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ nsresult rv;
+
+ RefPtr<Channel> channel = new Channel();
+ rv = channel->Init(uri, aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ channel.forget(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::GetURIFlags(nsIURI *aURI, uint32_t *result)
+{
+ *result = nsIAboutModule::HIDE_FROM_ABOUTABOUT |
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsAboutCacheEntry::Channel
+
+nsresult
+nsAboutCacheEntry::Channel::Init(nsIURI* uri, nsILoadInfo* aLoadInfo)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = GetContentStream(uri, getter_AddRefs(stream));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel),
+ uri,
+ stream,
+ NS_LITERAL_CSTRING("text/html"),
+ NS_LITERAL_CSTRING("utf-8"),
+ aLoadInfo);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+nsresult
+nsAboutCacheEntry::Channel::GetContentStream(nsIURI *uri, nsIInputStream **result)
+{
+ nsresult rv;
+
+ // Init: (block size, maximum length)
+ nsCOMPtr<nsIAsyncInputStream> inputStream;
+ rv = NS_NewPipe2(getter_AddRefs(inputStream),
+ getter_AddRefs(mOutputStream),
+ true, false,
+ 256, UINT32_MAX);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_NAMED_LITERAL_CSTRING(
+ buffer,
+ "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ " <title>Cache entry information</title>\n"
+ " <link rel=\"stylesheet\" "
+ "href=\"chrome://global/skin/about.css\" type=\"text/css\"/>\n"
+ " <link rel=\"stylesheet\" "
+ "href=\"chrome://global/skin/aboutCacheEntry.css\" type=\"text/css\"/>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>Cache entry information</h1>\n");
+ uint32_t n;
+ rv = mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+ if (NS_FAILED(rv)) return rv;
+ if (n != buffer.Length()) return NS_ERROR_UNEXPECTED;
+
+ rv = OpenCacheEntry(uri);
+ if (NS_FAILED(rv)) return rv;
+
+ inputStream.forget(result);
+ return NS_OK;
+}
+
+nsresult
+nsAboutCacheEntry::Channel::OpenCacheEntry(nsIURI *uri)
+{
+ nsresult rv;
+
+ rv = ParseURI(uri, mStorageName, getter_AddRefs(mLoadInfo),
+ mEnhanceId, getter_AddRefs(mCacheURI));
+ if (NS_FAILED(rv)) return rv;
+
+ if (!CacheObserver::UseNewCache() &&
+ mLoadInfo->IsPrivate() &&
+ mStorageName.EqualsLiteral("disk")) {
+ // The cache v1 is storing all private entries in the memory-only
+ // cache, so it would not be found in the v1 disk cache.
+ mStorageName = NS_LITERAL_CSTRING("memory");
+ }
+
+ return OpenCacheEntry();
+}
+
+nsresult
+nsAboutCacheEntry::Channel::OpenCacheEntry()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICacheStorage> storage;
+ rv = nsAboutCache::GetStorage(mStorageName, mLoadInfo, getter_AddRefs(storage));
+ if (NS_FAILED(rv)) return rv;
+
+ // Invokes OnCacheEntryAvailable()
+ rv = storage->AsyncOpenURI(mCacheURI, mEnhanceId,
+ nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY,
+ this);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+nsresult
+nsAboutCacheEntry::Channel::ParseURI(nsIURI *uri,
+ nsACString &storageName,
+ nsILoadContextInfo **loadInfo,
+ nsCString &enahnceID,
+ nsIURI **cacheUri)
+{
+ //
+ // about:cache-entry?storage=[string]&contenxt=[string]&eid=[string]&uri=[string]
+ //
+ nsresult rv;
+
+ nsAutoCString path;
+ rv = uri->GetPath(path);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsACString::const_iterator keyBegin, keyEnd, valBegin, begin, end;
+ path.BeginReading(begin);
+ path.EndReading(end);
+
+ keyBegin = begin; keyEnd = end;
+ if (!FindInReadable(NS_LITERAL_CSTRING("?storage="), keyBegin, keyEnd))
+ return NS_ERROR_FAILURE;
+
+ valBegin = keyEnd; // the value of the storage key starts after the key
+
+ keyBegin = keyEnd; keyEnd = end;
+ if (!FindInReadable(NS_LITERAL_CSTRING("&context="), keyBegin, keyEnd))
+ return NS_ERROR_FAILURE;
+
+ storageName.Assign(Substring(valBegin, keyBegin));
+ valBegin = keyEnd; // the value of the context key starts after the key
+
+ keyBegin = keyEnd; keyEnd = end;
+ if (!FindInReadable(NS_LITERAL_CSTRING("&eid="), keyBegin, keyEnd))
+ return NS_ERROR_FAILURE;
+
+ nsAutoCString contextKey(Substring(valBegin, keyBegin));
+ valBegin = keyEnd; // the value of the eid key starts after the key
+
+ keyBegin = keyEnd; keyEnd = end;
+ if (!FindInReadable(NS_LITERAL_CSTRING("&uri="), keyBegin, keyEnd))
+ return NS_ERROR_FAILURE;
+
+ enahnceID.Assign(Substring(valBegin, keyBegin));
+
+ valBegin = keyEnd; // the value of the uri key starts after the key
+ nsAutoCString uriSpec(Substring(valBegin, end)); // uri is the last one
+
+ // Uf... parsing done, now get some objects from it...
+
+ nsCOMPtr<nsILoadContextInfo> info =
+ CacheFileUtils::ParseKey(contextKey);
+ if (!info)
+ return NS_ERROR_FAILURE;
+ info.forget(loadInfo);
+
+ rv = NS_NewURI(cacheUri, uriSpec);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsICacheEntryOpenCallback implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnCacheEntryCheck(nsICacheEntry *aEntry,
+ nsIApplicationCache *aApplicationCache,
+ uint32_t *result)
+{
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnCacheEntryAvailable(nsICacheEntry *entry,
+ bool isNew,
+ nsIApplicationCache *aApplicationCache,
+ nsresult status)
+{
+ nsresult rv;
+
+ mWaitingForData = false;
+ if (entry) {
+ rv = WriteCacheEntryDescription(entry);
+ } else if (!CacheObserver::UseNewCache() &&
+ !mLoadInfo->IsPrivate() &&
+ mStorageName.EqualsLiteral("memory")) {
+ // If we were not able to find the entry in the memory storage
+ // try again in the disk storage.
+ // This is a workaround for cache v1: when an originally disk
+ // cache entry is recreated as memory-only, it's clientID doesn't
+ // change and we cannot find it in "HTTP-memory-only" session.
+ // "Disk" cache storage looks at "HTTP".
+ mStorageName = NS_LITERAL_CSTRING("disk");
+ rv = OpenCacheEntry();
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ } else {
+ rv = WriteCacheEntryUnavailable();
+ }
+ if (NS_FAILED(rv)) return rv;
+
+
+ if (!mWaitingForData) {
+ // Data is not expected, close the output of content now.
+ CloseContent();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Print-out helper methods
+//-----------------------------------------------------------------------------
+
+#define APPEND_ROW(label, value) \
+ PR_BEGIN_MACRO \
+ buffer.AppendLiteral(" <tr>\n" \
+ " <th>"); \
+ buffer.AppendLiteral(label); \
+ buffer.AppendLiteral(":</th>\n" \
+ " <td>"); \
+ buffer.Append(value); \
+ buffer.AppendLiteral("</td>\n" \
+ " </tr>\n"); \
+ PR_END_MACRO
+
+nsresult
+nsAboutCacheEntry::Channel::WriteCacheEntryDescription(nsICacheEntry *entry)
+{
+ nsresult rv;
+ nsCString buffer;
+ uint32_t n;
+
+ nsAutoCString str;
+
+ rv = entry->GetKey(str);
+ if (NS_FAILED(rv)) return rv;
+
+ buffer.SetCapacity(4096);
+ buffer.AssignLiteral("<table>\n"
+ " <tr>\n"
+ " <th>key:</th>\n"
+ " <td id=\"td-key\">");
+
+ // Test if the key is actually a URI
+ nsCOMPtr<nsIURI> uri;
+ bool isJS = false;
+ bool isData = false;
+
+ rv = NS_NewURI(getter_AddRefs(uri), str);
+ // javascript: and data: URLs should not be linkified
+ // since clicking them can cause scripts to run - bug 162584
+ if (NS_SUCCEEDED(rv)) {
+ uri->SchemeIs("javascript", &isJS);
+ uri->SchemeIs("data", &isData);
+ }
+ char* escapedStr = nsEscapeHTML(str.get());
+ if (NS_SUCCEEDED(rv) && !(isJS || isData)) {
+ buffer.AppendLiteral("<a href=\"");
+ buffer.Append(escapedStr);
+ buffer.AppendLiteral("\">");
+ buffer.Append(escapedStr);
+ buffer.AppendLiteral("</a>");
+ uri = nullptr;
+ } else {
+ buffer.Append(escapedStr);
+ }
+ free(escapedStr);
+ buffer.AppendLiteral("</td>\n"
+ " </tr>\n");
+
+ // temp vars for reporting
+ char timeBuf[255];
+ uint32_t u = 0;
+ int32_t i = 0;
+ nsAutoCString s;
+
+ // Fetch Count
+ s.Truncate();
+ entry->GetFetchCount(&i);
+ s.AppendInt(i);
+ APPEND_ROW("fetch count", s);
+
+ // Last Fetched
+ entry->GetLastFetched(&u);
+ if (u) {
+ PrintTimeString(timeBuf, sizeof(timeBuf), u);
+ APPEND_ROW("last fetched", timeBuf);
+ } else {
+ APPEND_ROW("last fetched", "No last fetch time (bug 1000338)");
+ }
+
+ // Last Modified
+ entry->GetLastModified(&u);
+ if (u) {
+ PrintTimeString(timeBuf, sizeof(timeBuf), u);
+ APPEND_ROW("last modified", timeBuf);
+ } else {
+ APPEND_ROW("last modified", "No last modified time (bug 1000338)");
+ }
+
+ // Expiration Time
+ entry->GetExpirationTime(&u);
+ if (u < 0xFFFFFFFF) {
+ PrintTimeString(timeBuf, sizeof(timeBuf), u);
+ APPEND_ROW("expires", timeBuf);
+ } else {
+ APPEND_ROW("expires", "No expiration time");
+ }
+
+ // Data Size
+ s.Truncate();
+ uint32_t dataSize;
+ if (NS_FAILED(entry->GetStorageDataSize(&dataSize)))
+ dataSize = 0;
+ s.AppendInt((int32_t)dataSize); // XXX nsICacheEntryInfo interfaces should be fixed.
+ s.AppendLiteral(" B");
+ APPEND_ROW("Data size", s);
+
+ // TODO - mayhemer
+ // Here used to be a link to the disk file (in the old cache for entries that
+ // did not fit any of the block files, in the new cache every time).
+ // I'd rather have a small set of buttons here to action on the entry:
+ // 1. save the content
+ // 2. save as a complete HTTP response (response head, headers, content)
+ // 3. doom the entry
+ // A new bug(s) should be filed here.
+
+ // Security Info
+ nsCOMPtr<nsISupports> securityInfo;
+ entry->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (securityInfo) {
+ APPEND_ROW("Security", "This is a secure document.");
+ } else {
+ APPEND_ROW("Security",
+ "This document does not have any security info associated with it.");
+ }
+
+ buffer.AppendLiteral("</table>\n"
+ "<hr/>\n"
+ "<table>\n");
+
+ mBuffer = &buffer; // make it available for OnMetaDataElement().
+ entry->VisitMetaData(this);
+ mBuffer = nullptr;
+
+ buffer.AppendLiteral("</table>\n");
+ mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+ buffer.Truncate();
+
+ // Provide a hexdump of the data
+ if (!dataSize) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ entry->OpenInputStream(0, getter_AddRefs(stream));
+ if (!stream) {
+ return NS_OK;
+ }
+
+ RefPtr<nsInputStreamPump> pump;
+ rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream);
+ if (NS_FAILED(rv)) {
+ return NS_OK; // just ignore
+ }
+
+ rv = pump->AsyncRead(this, nullptr);
+ if (NS_FAILED(rv)) {
+ return NS_OK; // just ignore
+ }
+
+ mWaitingForData = true;
+ return NS_OK;
+}
+
+nsresult
+nsAboutCacheEntry::Channel::WriteCacheEntryUnavailable()
+{
+ uint32_t n;
+ NS_NAMED_LITERAL_CSTRING(buffer,
+ "The cache entry you selected is not available.");
+ mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsICacheEntryMetaDataVisitor implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnMetaDataElement(char const * key, char const * value)
+{
+ mBuffer->AppendLiteral(" <tr>\n"
+ " <th>");
+ mBuffer->Append(key);
+ mBuffer->AppendLiteral(":</th>\n"
+ " <td>");
+ char* escapedValue = nsEscapeHTML(value);
+ mBuffer->Append(escapedValue);
+ free(escapedValue);
+ mBuffer->AppendLiteral("</td>\n"
+ " </tr>\n");
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIStreamListener implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnStartRequest(nsIRequest *request, nsISupports *ctx)
+{
+ mHexDumpState = 0;
+
+ NS_NAMED_LITERAL_CSTRING(buffer, "<hr/>\n<pre>");
+ uint32_t n;
+ return mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnDataAvailable(nsIRequest *request, nsISupports *ctx,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ uint32_t n;
+ return aInputStream->ReadSegments(
+ &nsAboutCacheEntry::Channel::PrintCacheData, this, aCount, &n);
+}
+
+/* static */ nsresult
+nsAboutCacheEntry::Channel::PrintCacheData(nsIInputStream *aInStream,
+ void *aClosure,
+ const char *aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t *aWriteCount)
+{
+ nsAboutCacheEntry::Channel *a =
+ static_cast<nsAboutCacheEntry::Channel*>(aClosure);
+
+ nsCString buffer;
+ HexDump(&a->mHexDumpState, aFromSegment, aCount, buffer);
+
+ uint32_t n;
+ a->mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+
+ *aWriteCount = aCount;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnStopRequest(nsIRequest *request, nsISupports *ctx,
+ nsresult result)
+{
+ NS_NAMED_LITERAL_CSTRING(buffer, "</pre>\n");
+ uint32_t n;
+ mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+
+ CloseContent();
+
+ return NS_OK;
+}
+
+void
+nsAboutCacheEntry::Channel::CloseContent()
+{
+ NS_NAMED_LITERAL_CSTRING(buffer, "</body>\n</html>\n");
+ uint32_t n;
+ mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+
+ mOutputStream->Close();
+ mOutputStream = nullptr;
+}
diff --git a/netwerk/protocol/about/nsAboutCacheEntry.h b/netwerk/protocol/about/nsAboutCacheEntry.h
new file mode 100644
index 000000000..44a78760b
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCacheEntry.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAboutCacheEntry_h__
+#define nsAboutCacheEntry_h__
+
+#include "nsIAboutModule.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheEntry.h"
+#include "nsIStreamListener.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+class nsIAsyncOutputStream;
+class nsIInputStream;
+class nsILoadContextInfo;
+class nsIURI;
+class nsCString;
+
+class nsAboutCacheEntry final : public nsIAboutModule
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABOUTMODULE
+
+private:
+ virtual ~nsAboutCacheEntry() {}
+
+ class Channel final : public nsICacheEntryOpenCallback
+ , public nsICacheEntryMetaDataVisitor
+ , public nsIStreamListener
+ , public nsIChannel
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_FORWARD_SAFE_NSICHANNEL(mChannel)
+ NS_FORWARD_SAFE_NSIREQUEST(mChannel)
+
+ Channel()
+ : mBuffer(nullptr)
+ , mWaitingForData(false)
+ , mHexDumpState(0)
+ {}
+
+ private:
+ virtual ~Channel() {}
+
+ public:
+ MOZ_MUST_USE nsresult Init(nsIURI* uri, nsILoadInfo* aLoadInfo);
+
+ MOZ_MUST_USE nsresult GetContentStream(nsIURI *, nsIInputStream **);
+ MOZ_MUST_USE nsresult OpenCacheEntry(nsIURI *);
+ MOZ_MUST_USE nsresult OpenCacheEntry();
+ MOZ_MUST_USE nsresult WriteCacheEntryDescription(nsICacheEntry *);
+ MOZ_MUST_USE nsresult WriteCacheEntryUnavailable();
+ MOZ_MUST_USE nsresult ParseURI(nsIURI *uri, nsACString &storageName,
+ nsILoadContextInfo **loadInfo,
+ nsCString &enahnceID,
+ nsIURI **cacheUri);
+ void CloseContent();
+
+ static MOZ_MUST_USE nsresult
+ PrintCacheData(nsIInputStream *aInStream,
+ void *aClosure,
+ const char *aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t *aWriteCount);
+
+ private:
+ nsCString mStorageName, mEnhanceId;
+ nsCOMPtr<nsILoadContextInfo> mLoadInfo;
+ nsCOMPtr<nsIURI> mCacheURI;
+
+ nsCString *mBuffer;
+ nsCOMPtr<nsIAsyncOutputStream> mOutputStream;
+ bool mWaitingForData;
+ uint32_t mHexDumpState;
+
+ nsCOMPtr<nsIChannel> mChannel;
+ };
+};
+
+#define NS_ABOUT_CACHE_ENTRY_MODULE_CID \
+{ /* 7fa5237d-b0eb-438f-9e50-ca0166e63788 */ \
+ 0x7fa5237d, \
+ 0xb0eb, \
+ 0x438f, \
+ {0x9e, 0x50, 0xca, 0x01, 0x66, 0xe6, 0x37, 0x88} \
+}
+
+#endif // nsAboutCacheEntry_h__
diff --git a/netwerk/protocol/about/nsAboutProtocolHandler.cpp b/netwerk/protocol/about/nsAboutProtocolHandler.cpp
new file mode 100644
index 000000000..998fc71f9
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolHandler.cpp
@@ -0,0 +1,439 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+#include "mozilla/ArrayUtils.h"
+
+#include "nsAboutProtocolHandler.h"
+#include "nsIURI.h"
+#include "nsIAboutModule.h"
+#include "nsString.h"
+#include "nsNetCID.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsError.h"
+#include "nsNetUtil.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsAutoPtr.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsIChannel.h"
+#include "nsIScriptError.h"
+
+namespace mozilla {
+namespace net {
+
+static NS_DEFINE_CID(kSimpleURICID, NS_SIMPLEURI_CID);
+static NS_DEFINE_CID(kNestedAboutURICID, NS_NESTEDABOUTURI_CID);
+
+static bool IsSafeForUntrustedContent(nsIAboutModule *aModule, nsIURI *aURI) {
+ uint32_t flags;
+ nsresult rv = aModule->GetURIFlags(aURI, &flags);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return (flags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT) != 0;
+}
+
+static bool IsSafeToLinkForUntrustedContent(nsIAboutModule *aModule, nsIURI *aURI) {
+ uint32_t flags;
+ nsresult rv = aModule->GetURIFlags(aURI, &flags);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return (flags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT) && (flags & nsIAboutModule::MAKE_LINKABLE);
+}
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsAboutProtocolHandler, nsIProtocolHandler,
+ nsIProtocolHandlerWithDynamicFlags, nsISupportsWeakReference)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::GetScheme(nsACString &result)
+{
+ result.AssignLiteral("about");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::GetDefaultPort(int32_t *result)
+{
+ *result = -1; // no port for about: URLs
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ *result = URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD | URI_SCHEME_NOT_SELF_LINKABLE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* aFlags)
+{
+ // First use the default (which is "unsafe for content"):
+ GetProtocolFlags(aFlags);
+
+ // Now try to see if this URI overrides the default:
+ nsCOMPtr<nsIAboutModule> aboutMod;
+ nsresult rv = NS_GetAboutModule(aURI, getter_AddRefs(aboutMod));
+ if (NS_FAILED(rv)) {
+ // Swallow this and just tell the consumer the default:
+ return NS_OK;
+ }
+ uint32_t aboutModuleFlags = 0;
+ rv = aboutMod->GetURIFlags(aURI, &aboutModuleFlags);
+ // This should never happen, so pass back the error:
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Secure (https) pages can load safe about pages without becoming
+ // mixed content.
+ if (aboutModuleFlags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT) {
+ *aFlags |= URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT;
+ // about: pages can only be loaded by unprivileged principals
+ // if they are marked as LINKABLE
+ if (aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) {
+ // Replace URI_DANGEROUS_TO_LOAD with URI_LOADABLE_BY_ANYONE.
+ *aFlags &= ~URI_DANGEROUS_TO_LOAD;
+ *aFlags |= URI_LOADABLE_BY_ANYONE;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset, // ignore charset info
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+ *result = nullptr;
+ nsresult rv;
+
+ // Use a simple URI to parse out some stuff first
+ nsCOMPtr<nsIURI> url = do_CreateInstance(kSimpleURICID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = url->SetSpec(aSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Unfortunately, people create random about: URIs that don't correspond to
+ // about: modules... Since those URIs will never open a channel, might as
+ // well consider them unsafe for better perf, and just in case.
+ bool isSafe = false;
+
+ nsCOMPtr<nsIAboutModule> aboutMod;
+ rv = NS_GetAboutModule(url, getter_AddRefs(aboutMod));
+ if (NS_SUCCEEDED(rv)) {
+ isSafe = IsSafeToLinkForUntrustedContent(aboutMod, url);
+ }
+
+ if (isSafe) {
+ // We need to indicate that this baby is safe. Use an inner URI that
+ // no one but the security manager will see. Make sure to preserve our
+ // path, in case someone decides to hardcode checks for particular
+ // about: URIs somewhere.
+ nsAutoCString spec;
+ rv = url->GetPath(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ spec.Insert("moz-safe-about:", 0);
+
+ nsCOMPtr<nsIURI> inner;
+ rv = NS_NewURI(getter_AddRefs(inner), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsSimpleNestedURI* outer = new nsNestedAboutURI(inner, aBaseURI);
+ NS_ENSURE_TRUE(outer, NS_ERROR_OUT_OF_MEMORY);
+
+ // Take a ref to it in the COMPtr we plan to return
+ url = outer;
+
+ rv = outer->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We don't want to allow mutation, since it would allow safe and
+ // unsafe URIs to change into each other...
+ NS_TryToSetImmutable(url);
+ url.swap(*result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+
+ // about:what you ask?
+ nsCOMPtr<nsIAboutModule> aboutMod;
+ nsresult rv = NS_GetAboutModule(uri, getter_AddRefs(aboutMod));
+
+ nsAutoCString path;
+ nsresult rv2 = NS_GetAboutModuleName(uri, path);
+ if (NS_SUCCEEDED(rv2) && path.EqualsLiteral("srcdoc")) {
+ // about:srcdoc is meant to be unresolvable, yet is included in the
+ // about lookup tables so that it can pass security checks when used in
+ // a srcdoc iframe. To ensure that it stays unresolvable, we pretend
+ // that it doesn't exist.
+ rv = NS_ERROR_FACTORY_NOT_REGISTERED;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // The standard return case:
+ rv = aboutMod->NewChannel(uri, aLoadInfo, result);
+ if (NS_SUCCEEDED(rv)) {
+ // Not all implementations of nsIAboutModule::NewChannel()
+ // set the LoadInfo on the newly created channel yet, as
+ // an interim solution we set the LoadInfo here if not
+ // available on the channel. Bug 1087720
+ nsCOMPtr<nsILoadInfo> loadInfo = (*result)->GetLoadInfo();
+ if (aLoadInfo != loadInfo) {
+ if (loadInfo) {
+ NS_ASSERTION(false,
+ "nsIAboutModule->newChannel(aURI, aLoadInfo) needs to set LoadInfo");
+ const char16_t* params[] = {
+ u"nsIAboutModule->newChannel(aURI)",
+ u"nsIAboutModule->newChannel(aURI, aLoadInfo)"
+ };
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Security by Default"),
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES,
+ "APIDeprecationWarning",
+ params, mozilla::ArrayLength(params));
+ }
+ (*result)->SetLoadInfo(aLoadInfo);
+ }
+
+ // If this URI is safe for untrusted content, enforce that its
+ // principal be based on the channel's originalURI by setting the
+ // owner to null.
+ // Note: this relies on aboutMod's newChannel implementation
+ // having set the proper originalURI, which probably isn't ideal.
+ if (IsSafeForUntrustedContent(aboutMod, uri)) {
+ (*result)->SetOwner(nullptr);
+ }
+
+ RefPtr<nsNestedAboutURI> aboutURI;
+ nsresult rv2 = uri->QueryInterface(kNestedAboutURICID,
+ getter_AddRefs(aboutURI));
+ if (NS_SUCCEEDED(rv2) && aboutURI->GetBaseURI()) {
+ nsCOMPtr<nsIWritablePropertyBag2> writableBag =
+ do_QueryInterface(*result);
+ if (writableBag) {
+ writableBag->
+ SetPropertyAsInterface(NS_LITERAL_STRING("baseURI"),
+ aboutURI->GetBaseURI());
+ }
+ }
+ }
+ return rv;
+ }
+
+ // mumble...
+
+ if (rv == NS_ERROR_FACTORY_NOT_REGISTERED) {
+ // This looks like an about: we don't know about. Convert
+ // this to an invalid URI error.
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Safe about protocol handler impl
+
+NS_IMPL_ISUPPORTS(nsSafeAboutProtocolHandler, nsIProtocolHandler, nsISupportsWeakReference)
+
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::GetScheme(nsACString &result)
+{
+ result.AssignLiteral("moz-safe-about");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::GetDefaultPort(int32_t *result)
+{
+ *result = -1; // no port for moz-safe-about: URLs
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ *result = URI_NORELATIVE | URI_NOAUTH | URI_LOADABLE_BY_ANYONE | URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset, // ignore charset info
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> url = do_CreateInstance(kSimpleURICID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = url->SetSpec(aSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ NS_TryToSetImmutable(url);
+
+ *result = nullptr;
+ url.swap(*result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ *result = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
+{
+ *result = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////
+// nsNestedAboutURI implementation
+NS_INTERFACE_MAP_BEGIN(nsNestedAboutURI)
+ if (aIID.Equals(kNestedAboutURICID))
+ foundInterface = static_cast<nsIURI*>(this);
+ else
+NS_INTERFACE_MAP_END_INHERITING(nsSimpleNestedURI)
+
+// nsISerializable
+NS_IMETHODIMP
+nsNestedAboutURI::Read(nsIObjectInputStream* aStream)
+{
+ nsresult rv = nsSimpleNestedURI::Read(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ bool haveBase;
+ rv = aStream->ReadBoolean(&haveBase);
+ if (NS_FAILED(rv)) return rv;
+
+ if (haveBase) {
+ nsCOMPtr<nsISupports> supports;
+ rv = aStream->ReadObject(true, getter_AddRefs(supports));
+ if (NS_FAILED(rv)) return rv;
+
+ mBaseURI = do_QueryInterface(supports, &rv);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNestedAboutURI::Write(nsIObjectOutputStream* aStream)
+{
+ nsresult rv = nsSimpleNestedURI::Write(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteBoolean(mBaseURI != nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mBaseURI) {
+ // A previous iteration of this code wrote out mBaseURI as nsISupports
+ // and then read it in as nsIURI, which is non-kosher when mBaseURI
+ // implements more than just a single line of interfaces and the
+ // canonical nsISupports* isn't the one a static_cast<> of mBaseURI
+ // would produce. For backwards compatibility with existing
+ // serializations we continue to write mBaseURI as nsISupports but
+ // switch to reading it as nsISupports, with a post-read QI to get to
+ // nsIURI.
+ rv = aStream->WriteCompoundObject(mBaseURI, NS_GET_IID(nsISupports),
+ true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+// nsSimpleURI
+/* virtual */ nsSimpleURI*
+nsNestedAboutURI::StartClone(nsSimpleURI::RefHandlingEnum aRefHandlingMode,
+ const nsACString& aNewRef)
+{
+ // Sadly, we can't make use of nsSimpleNestedURI::StartClone here.
+ // However, this function is expected to exactly match that function,
+ // aside from the "new ns***URI()" call.
+ NS_ENSURE_TRUE(mInnerURI, nullptr);
+
+ nsCOMPtr<nsIURI> innerClone;
+ nsresult rv;
+ if (aRefHandlingMode == eHonorRef) {
+ rv = mInnerURI->Clone(getter_AddRefs(innerClone));
+ } else if (aRefHandlingMode == eReplaceRef) {
+ rv = mInnerURI->CloneWithNewRef(aNewRef, getter_AddRefs(innerClone));
+ } else {
+ rv = mInnerURI->CloneIgnoringRef(getter_AddRefs(innerClone));
+ }
+
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsNestedAboutURI* url = new nsNestedAboutURI(innerClone, mBaseURI);
+ SetRefOnClone(url, aRefHandlingMode, aNewRef);
+ url->SetMutable(false);
+
+ return url;
+}
+
+// nsIClassInfo
+NS_IMETHODIMP
+nsNestedAboutURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ *aClassIDNoAlloc = kNestedAboutURICID;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/about/nsAboutProtocolHandler.h b/netwerk/protocol/about/nsAboutProtocolHandler.h
new file mode 100644
index 000000000..72c7e8e66
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolHandler.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAboutProtocolHandler_h___
+#define nsAboutProtocolHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsSimpleNestedURI.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class nsAboutProtocolHandler : public nsIProtocolHandlerWithDynamicFlags
+ , public nsIProtocolHandler
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIPROTOCOLHANDLERWITHDYNAMICFLAGS
+
+ // nsAboutProtocolHandler methods:
+ nsAboutProtocolHandler() {}
+
+private:
+ virtual ~nsAboutProtocolHandler() {}
+};
+
+class nsSafeAboutProtocolHandler final : public nsIProtocolHandler
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ // nsSafeAboutProtocolHandler methods:
+ nsSafeAboutProtocolHandler() {}
+
+private:
+ ~nsSafeAboutProtocolHandler() {}
+};
+
+
+// Class to allow us to propagate the base URI to about:blank correctly
+class nsNestedAboutURI : public nsSimpleNestedURI {
+public:
+ nsNestedAboutURI(nsIURI* aInnerURI, nsIURI* aBaseURI)
+ : nsSimpleNestedURI(aInnerURI)
+ , mBaseURI(aBaseURI)
+ {}
+
+ // For use only from deserialization
+ nsNestedAboutURI() : nsSimpleNestedURI() {}
+
+ virtual ~nsNestedAboutURI() {}
+
+ // Override QI so we can QI to our CID as needed
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr);
+
+ // Override StartClone(), the nsISerializable methods, and
+ // GetClassIDNoAlloc; this last is needed to make our nsISerializable impl
+ // work right.
+ virtual nsSimpleURI* StartClone(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef);
+ NS_IMETHOD Read(nsIObjectInputStream* aStream);
+ NS_IMETHOD Write(nsIObjectOutputStream* aStream);
+ NS_IMETHOD GetClassIDNoAlloc(nsCID *aClassIDNoAlloc);
+
+ nsIURI* GetBaseURI() const {
+ return mBaseURI;
+ }
+
+protected:
+ nsCOMPtr<nsIURI> mBaseURI;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* nsAboutProtocolHandler_h___ */
diff --git a/netwerk/protocol/about/nsAboutProtocolUtils.h b/netwerk/protocol/about/nsAboutProtocolUtils.h
new file mode 100644
index 000000000..c5946412b
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolUtils.h
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAboutProtocolUtils_h
+#define nsAboutProtocolUtils_h
+
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIAboutModule.h"
+#include "nsServiceManagerUtils.h"
+#include "prtime.h"
+
+inline MOZ_MUST_USE nsresult
+NS_GetAboutModuleName(nsIURI *aAboutURI, nsCString& aModule)
+{
+#ifdef DEBUG
+ {
+ bool isAbout;
+ NS_ASSERTION(NS_SUCCEEDED(aAboutURI->SchemeIs("about", &isAbout)) &&
+ isAbout,
+ "should be used only on about: URIs");
+ }
+#endif
+
+ nsresult rv = aAboutURI->GetPath(aModule);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t f = aModule.FindCharInSet(NS_LITERAL_CSTRING("#?"));
+ if (f != kNotFound) {
+ aModule.Truncate(f);
+ }
+
+ // convert to lowercase, as all about: modules are lowercase
+ ToLowerCase(aModule);
+ return NS_OK;
+}
+
+inline nsresult
+NS_GetAboutModule(nsIURI *aAboutURI, nsIAboutModule** aModule)
+{
+ NS_PRECONDITION(aAboutURI, "Must have URI");
+
+ nsAutoCString contractID;
+ nsresult rv = NS_GetAboutModuleName(aAboutURI, contractID);
+ if (NS_FAILED(rv)) return rv;
+
+ // look up a handler to deal with "what"
+ contractID.Insert(NS_LITERAL_CSTRING(NS_ABOUT_MODULE_CONTRACTID_PREFIX), 0);
+
+ return CallGetService(contractID.get(), aModule);
+}
+
+inline PRTime SecondsToPRTime(uint32_t t_sec)
+{
+ PRTime t_usec, usec_per_sec;
+ t_usec = t_sec;
+ usec_per_sec = PR_USEC_PER_SEC;
+ return t_usec *= usec_per_sec;
+}
+inline void PrintTimeString(char *buf, uint32_t bufsize, uint32_t t_sec)
+{
+ PRExplodedTime et;
+ PRTime t_usec = SecondsToPRTime(t_sec);
+ PR_ExplodeTime(t_usec, PR_LocalTimeParameters, &et);
+ PR_FormatTime(buf, bufsize, "%Y-%m-%d %H:%M:%S", &et);
+}
+
+
+#endif
diff --git a/netwerk/protocol/about/nsIAboutModule.idl b/netwerk/protocol/about/nsIAboutModule.idl
new file mode 100644
index 000000000..230cd6c05
--- /dev/null
+++ b/netwerk/protocol/about/nsIAboutModule.idl
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIChannel;
+interface nsILoadInfo;
+
+[scriptable, uuid(c0c19db9-1b5a-4ac5-b656-ed6f8149fa48)]
+interface nsIAboutModule : nsISupports
+{
+
+ /**
+ * Constructs a new channel for the about protocol module.
+ *
+ * @param aURI the uri of the new channel
+ * @param aLoadInfo the loadinfo of the new channel
+ */
+ nsIChannel newChannel(in nsIURI aURI,
+ in nsILoadInfo aLoadInfo);
+
+ /**
+ * A flag that indicates whether a URI should be run with content
+ * privileges. If it is, the about: protocol handler will enforce that
+ * the principal of channels created for it be based on their
+ * originalURI or URI (depending on the channel flags), by setting
+ * their "owner" to null.
+ * If content needs to be able to link to this URI, specify
+ * URI_CONTENT_LINKABLE as well.
+ */
+ const unsigned long URI_SAFE_FOR_UNTRUSTED_CONTENT = (1 << 0);
+
+ /**
+ * A flag that indicates whether script should be enabled for the
+ * given about: URI even if it's disabled in general.
+ */
+ const unsigned long ALLOW_SCRIPT = (1 << 1);
+
+ /**
+ * A flag that indicates whether this about: URI doesn't want to be listed
+ * in about:about, especially if it's not useful without a query string.
+ */
+ const unsigned long HIDE_FROM_ABOUTABOUT = (1 << 2);
+
+ /**
+ * A flag that indicates whether this about: URI wants Indexed DB enabled.
+ */
+ const unsigned long ENABLE_INDEXED_DB = (1 << 3);
+
+ /**
+ * A flag that indicates that this URI can be loaded in a child process
+ */
+ const unsigned long URI_CAN_LOAD_IN_CHILD = (1 << 4);
+
+ /**
+ * A flag that indicates that this URI must be loaded in a child process
+ */
+ const unsigned long URI_MUST_LOAD_IN_CHILD = (1 << 5);
+
+ /**
+ * Obsolete. This flag no longer has any effect and will be removed in future.
+ */
+ const unsigned long MAKE_UNLINKABLE = (1 << 6);
+
+ /**
+ * A flag that indicates that this URI should be linkable from content.
+ * Ignored unless URI_SAFE_FOR_UNTRUSTED_CONTENT is also specified.
+ */
+ const unsigned long MAKE_LINKABLE = (1 << 7);
+
+ /**
+ * A method to get the flags that apply to a given about: URI. The URI
+ * passed in is guaranteed to be one of the URIs that this module
+ * registered to deal with.
+ */
+ unsigned long getURIFlags(in nsIURI aURI);
+};
+
+%{C++
+
+#define NS_ABOUT_MODULE_CONTRACTID "@mozilla.org/network/protocol/about;1"
+#define NS_ABOUT_MODULE_CONTRACTID_PREFIX NS_ABOUT_MODULE_CONTRACTID "?what="
+#define NS_ABOUT_MODULE_CONTRACTID_LENGTH 49 // strlen(NS_ABOUT_MODULE_CONTRACTID_PREFIX)
+
+%}
diff --git a/netwerk/protocol/data/DataChannelChild.cpp b/netwerk/protocol/data/DataChannelChild.cpp
new file mode 100644
index 000000000..137eb74b6
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelChild.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 sw=4 sts=4 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DataChannelChild.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED(DataChannelChild, nsDataChannel, nsIChildChannel)
+
+DataChannelChild::DataChannelChild(nsIURI* aURI)
+ : nsDataChannel(aURI)
+ , mIPCOpen(false)
+{
+}
+
+DataChannelChild::~DataChannelChild()
+{
+}
+
+NS_IMETHODIMP
+DataChannelChild::ConnectParent(uint32_t aId)
+{
+ if (!gNeckoChild->SendPDataChannelConstructor(this, aId)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // IPC now has a ref to us.
+ AddIPDLReference();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelChild::CompleteRedirectSetup(nsIStreamListener *aListener,
+ nsISupports *aContext)
+{
+ nsresult rv;
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!aContext, "aContext should be null!");
+ rv = AsyncOpen2(aListener);
+ }
+ else {
+ rv = AsyncOpen(aListener, aContext);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mIPCOpen) {
+ Unused << Send__delete__(this);
+ }
+ return NS_OK;
+}
+
+void
+DataChannelChild::AddIPDLReference()
+{
+ AddRef();
+ mIPCOpen = true;
+}
+
+void
+DataChannelChild::ActorDestroy(ActorDestroyReason why)
+{
+ MOZ_ASSERT(mIPCOpen);
+ mIPCOpen = false;
+ Release();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/data/DataChannelChild.h b/netwerk/protocol/data/DataChannelChild.h
new file mode 100644
index 000000000..8fa42177a
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelChild.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 sw=4 sts=4 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_DATACHANNELCHILD_H
+#define NS_DATACHANNELCHILD_H
+
+#include "nsDataChannel.h"
+#include "nsIChildChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PDataChannelChild.h"
+
+namespace mozilla {
+namespace net {
+
+class DataChannelChild : public nsDataChannel
+ , public nsIChildChannel
+ , public PDataChannelChild
+{
+public:
+ explicit DataChannelChild(nsIURI *uri);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICHILDCHANNEL
+
+protected:
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+private:
+ ~DataChannelChild();
+
+ void AddIPDLReference();
+
+ bool mIPCOpen;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NS_DATACHANNELCHILD_H */
diff --git a/netwerk/protocol/data/DataChannelParent.cpp b/netwerk/protocol/data/DataChannelParent.cpp
new file mode 100644
index 000000000..e1db0ab36
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelParent.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 sw=4 sts=4 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DataChannelParent.h"
+#include "mozilla/Assertions.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(DataChannelParent, nsIParentChannel, nsIStreamListener)
+
+DataChannelParent::~DataChannelParent()
+{
+}
+
+bool
+DataChannelParent::Init(const uint32_t &channelId)
+{
+ nsCOMPtr<nsIChannel> channel;
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_LinkRedirectChannels(channelId, this, getter_AddRefs(channel)));
+
+ return true;
+}
+
+NS_IMETHODIMP
+DataChannelParent::SetParentListener(HttpChannelParentListener* aListener)
+{
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::NotifyTrackingProtectionDisabled()
+{
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::Delete()
+{
+ // Nothing to do.
+ return NS_OK;
+}
+
+void
+DataChannelParent::ActorDestroy(ActorDestroyReason why)
+{
+}
+
+NS_IMETHODIMP
+DataChannelParent::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ // We don't have a way to prevent nsBaseChannel from calling AsyncOpen on
+ // the created nsDataChannel. We don't have anywhere to send the data in the
+ // parent, so abort the binding.
+ return NS_BINDING_ABORTED;
+}
+
+NS_IMETHODIMP
+DataChannelParent::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ // See above.
+ MOZ_ASSERT(NS_FAILED(aStatusCode));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DataChannelParent::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ // See above.
+ MOZ_CRASH("Should never be called");
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/data/DataChannelParent.h b/netwerk/protocol/data/DataChannelParent.h
new file mode 100644
index 000000000..415672a44
--- /dev/null
+++ b/netwerk/protocol/data/DataChannelParent.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 sw=4 sts=4 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_DATACHANNELPARENT_H
+#define NS_DATACHANNELPARENT_H
+
+#include "nsIParentChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PDataChannelParent.h"
+
+namespace mozilla {
+namespace net {
+
+// In order to support HTTP redirects to data:, we need to implement the HTTP
+// redirection API, which requires a class that implements nsIParentChannel
+// and which calls NS_LinkRedirectChannels.
+class DataChannelParent : public nsIParentChannel
+ , public PDataChannelParent
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ MOZ_MUST_USE bool Init(const uint32_t& aArgs);
+
+private:
+ ~DataChannelParent();
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NS_DATACHANNELPARENT_H */
diff --git a/netwerk/protocol/data/moz.build b/netwerk/protocol/data/moz.build
new file mode 100644
index 000000000..0958118fa
--- /dev/null
+++ b/netwerk/protocol/data/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.net += [
+ 'DataChannelParent.h',
+]
+
+UNIFIED_SOURCES += [
+ 'DataChannelChild.cpp',
+ 'DataChannelParent.cpp',
+ 'nsDataChannel.cpp',
+ 'nsDataHandler.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
+
diff --git a/netwerk/protocol/data/nsDataChannel.cpp b/netwerk/protocol/data/nsDataChannel.cpp
new file mode 100644
index 000000000..608a6c6e0
--- /dev/null
+++ b/netwerk/protocol/data/nsDataChannel.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// data implementation
+
+#include "nsDataChannel.h"
+
+#include "mozilla/Base64.h"
+#include "nsIOService.h"
+#include "nsDataHandler.h"
+#include "nsIPipe.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsEscape.h"
+
+using namespace mozilla;
+
+nsresult
+nsDataChannel::OpenContentStream(bool async, nsIInputStream **result,
+ nsIChannel** channel)
+{
+ NS_ENSURE_TRUE(URI(), NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv;
+
+ nsAutoCString spec;
+ rv = URI()->GetAsciiSpec(spec);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString contentType, contentCharset, dataBuffer;
+ bool lBase64;
+ rv = nsDataHandler::ParseURI(spec, contentType, &contentCharset,
+ lBase64, &dataBuffer);
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_UnescapeURL(dataBuffer);
+
+ if (lBase64) {
+ // Don't allow spaces in base64-encoded content. This is only
+ // relevant for escaped spaces; other spaces are stripped in
+ // NewURI.
+ dataBuffer.StripWhitespace();
+ }
+
+ nsCOMPtr<nsIInputStream> bufInStream;
+ nsCOMPtr<nsIOutputStream> bufOutStream;
+
+ // create an unbounded pipe.
+ rv = NS_NewPipe(getter_AddRefs(bufInStream),
+ getter_AddRefs(bufOutStream),
+ nsIOService::gDefaultSegmentSize,
+ UINT32_MAX,
+ async, true);
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t contentLen;
+ if (lBase64) {
+ const uint32_t dataLen = dataBuffer.Length();
+ int32_t resultLen = 0;
+ if (dataLen >= 1 && dataBuffer[dataLen-1] == '=') {
+ if (dataLen >= 2 && dataBuffer[dataLen-2] == '=')
+ resultLen = dataLen-2;
+ else
+ resultLen = dataLen-1;
+ } else {
+ resultLen = dataLen;
+ }
+ resultLen = ((resultLen * 3) / 4);
+
+ nsAutoCString decodedData;
+ rv = Base64Decode(dataBuffer, decodedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bufOutStream->Write(decodedData.get(), resultLen, &contentLen);
+ } else {
+ rv = bufOutStream->Write(dataBuffer.get(), dataBuffer.Length(), &contentLen);
+ }
+ if (NS_FAILED(rv))
+ return rv;
+
+ SetContentType(contentType);
+ SetContentCharset(contentCharset);
+ mContentLength = contentLen;
+
+ bufInStream.forget(result);
+
+ return NS_OK;
+}
diff --git a/netwerk/protocol/data/nsDataChannel.h b/netwerk/protocol/data/nsDataChannel.h
new file mode 100644
index 000000000..c986fba1e
--- /dev/null
+++ b/netwerk/protocol/data/nsDataChannel.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// data implementation header
+
+#ifndef nsDataChannel_h___
+#define nsDataChannel_h___
+
+#include "nsBaseChannel.h"
+
+class nsIInputStream;
+
+class nsDataChannel : public nsBaseChannel
+{
+public:
+ explicit nsDataChannel(nsIURI *uri) {
+ SetURI(uri);
+ }
+
+protected:
+ virtual MOZ_MUST_USE nsresult OpenContentStream(bool async,
+ nsIInputStream **result,
+ nsIChannel** channel);
+};
+
+#endif /* nsDataChannel_h___ */
diff --git a/netwerk/protocol/data/nsDataHandler.cpp b/netwerk/protocol/data/nsDataHandler.cpp
new file mode 100644
index 000000000..b84b50d5b
--- /dev/null
+++ b/netwerk/protocol/data/nsDataHandler.cpp
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDataChannel.h"
+#include "nsDataHandler.h"
+#include "nsNetCID.h"
+#include "nsError.h"
+#include "DataChannelChild.h"
+#include "plstr.h"
+
+static NS_DEFINE_CID(kSimpleURICID, NS_SIMPLEURI_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsDataHandler::nsDataHandler() {
+}
+
+nsDataHandler::~nsDataHandler() {
+}
+
+NS_IMPL_ISUPPORTS(nsDataHandler, nsIProtocolHandler, nsISupportsWeakReference)
+
+nsresult
+nsDataHandler::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult) {
+
+ nsDataHandler* ph = new nsDataHandler();
+ if (ph == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(ph);
+ nsresult rv = ph->QueryInterface(aIID, aResult);
+ NS_RELEASE(ph);
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsDataHandler::GetScheme(nsACString &result) {
+ result.AssignLiteral("data");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDataHandler::GetDefaultPort(int32_t *result) {
+ // no ports for data protocol
+ *result = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDataHandler::GetProtocolFlags(uint32_t *result) {
+ *result = URI_NORELATIVE | URI_NOAUTH | URI_INHERITS_SECURITY_CONTEXT |
+ URI_LOADABLE_BY_ANYONE | URI_NON_PERSISTABLE | URI_IS_LOCAL_RESOURCE |
+ URI_SYNC_LOAD_IS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDataHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset, // ignore charset info
+ nsIURI *aBaseURI,
+ nsIURI **result) {
+ nsresult rv;
+ RefPtr<nsIURI> uri;
+
+ nsCString spec(aSpec);
+
+ if (aBaseURI && !spec.IsEmpty() && spec[0] == '#') {
+ // Looks like a reference instead of a fully-specified URI.
+ // --> initialize |uri| as a clone of |aBaseURI|, with ref appended.
+ rv = aBaseURI->Clone(getter_AddRefs(uri));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = uri->SetRef(spec);
+ } else {
+ // Otherwise, we'll assume |spec| is a fully-specified data URI
+ nsAutoCString contentType;
+ bool base64;
+ rv = ParseURI(spec, contentType, /* contentCharset = */ nullptr,
+ base64, /* dataBuffer = */ nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Strip whitespace unless this is text, where whitespace is important
+ // Don't strip escaped whitespace though (bug 391951)
+ if (base64 || (strncmp(contentType.get(),"text/",5) != 0 &&
+ contentType.Find("xml") == kNotFound)) {
+ // it's ascii encoded binary, don't let any spaces in
+ if (!spec.StripWhitespace(mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ uri = do_CreateInstance(kSimpleURICID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = uri->SetSpec(spec);
+ }
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ uri.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDataHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ nsDataChannel* channel;
+ if (XRE_IsParentProcess()) {
+ channel = new nsDataChannel(uri);
+ } else {
+ channel = new mozilla::net::DataChannelChild(uri);
+ }
+ NS_ADDREF(channel);
+
+ nsresult rv = channel->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(channel);
+ return rv;
+ }
+
+ // set the loadInfo on the new channel
+ rv = channel->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(channel);
+ return rv;
+ }
+
+ *result = channel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDataHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+NS_IMETHODIMP
+nsDataHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+#define BASE64_EXTENSION ";base64"
+
+nsresult
+nsDataHandler::ParseURI(nsCString& spec,
+ nsCString& contentType,
+ nsCString* contentCharset,
+ bool& isBase64,
+ nsCString* dataBuffer)
+{
+ isBase64 = false;
+
+ // move past "data:"
+ const char* roBuffer = (const char*) PL_strcasestr(spec.get(), "data:");
+ if (!roBuffer) {
+ // malformed uri
+ return NS_ERROR_MALFORMED_URI;
+ }
+ roBuffer += sizeof("data:") - 1;
+
+ // First, find the start of the data
+ const char* roComma = strchr(roBuffer, ',');
+ const char* roHash = strchr(roBuffer, '#');
+ if (!roComma || (roHash && roHash < roComma)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (roComma == roBuffer) {
+ // nothing but data
+ contentType.AssignLiteral("text/plain");
+ if (contentCharset) {
+ contentCharset->AssignLiteral("US-ASCII");
+ }
+ } else {
+ // Make a copy of the non-data part so we can null out parts of it as
+ // we go. This copy will be a small number of chars, in contrast to the
+ // data which may be large.
+ char* buffer = PL_strndup(roBuffer, roComma - roBuffer);
+
+ // determine if the data is base64 encoded.
+ char* base64 = PL_strcasestr(buffer, BASE64_EXTENSION);
+ if (base64) {
+ char *beyond = base64 + sizeof(BASE64_EXTENSION) - 1;
+ // Per the RFC 2397 grammar, "base64" MUST be at the end of the
+ // non-data part.
+ //
+ // But we also allow it in between parameters so a subsequent ";"
+ // is ok as well (this deals with *broken* data URIs, see bug
+ // 781693 for an example). Anything after "base64" in the non-data
+ // part will be discarded in this case, however.
+ if (*beyond == '\0' || *beyond == ';') {
+ isBase64 = true;
+ *base64 = '\0';
+ }
+ }
+
+ // everything else is content type
+ char *semiColon = (char *) strchr(buffer, ';');
+ if (semiColon)
+ *semiColon = '\0';
+
+ if (semiColon == buffer || base64 == buffer) {
+ // there is no content type, but there are other parameters
+ contentType.AssignLiteral("text/plain");
+ } else {
+ contentType.Assign(buffer);
+ ToLowerCase(contentType);
+ if (!contentType.StripWhitespace(mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ if (semiColon && contentCharset) {
+ char *charset = PL_strcasestr(semiColon + 1, "charset=");
+ if (charset) {
+ contentCharset->Assign(charset + sizeof("charset=") - 1);
+ if (!contentCharset->StripWhitespace(mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+
+ free(buffer);
+ }
+
+ if (dataBuffer) {
+ // Split encoded data from terminal "#ref" (if present)
+ const char* roData = roComma + 1;
+ bool ok = !roHash
+ ? dataBuffer->Assign(roData, mozilla::fallible)
+ : dataBuffer->Assign(roData, roHash - roData, mozilla::fallible);
+ if (!ok) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/netwerk/protocol/data/nsDataHandler.h b/netwerk/protocol/data/nsDataHandler.h
new file mode 100644
index 000000000..75f873e17
--- /dev/null
+++ b/netwerk/protocol/data/nsDataHandler.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDataHandler_h___
+#define nsDataHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsWeakReference.h"
+
+class nsDataHandler : public nsIProtocolHandler
+ , public nsSupportsWeakReference
+{
+ virtual ~nsDataHandler();
+
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ // nsDataHandler methods:
+ nsDataHandler();
+
+ // Define a Create method to be used with a factory:
+ static MOZ_MUST_USE nsresult
+ Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult);
+
+ // Parse a data: URI and return the individual parts
+ // (the given spec will temporarily be modified but will be returned
+ // to the original before returning)
+ // contentCharset and dataBuffer can be nullptr if they are not needed.
+ static MOZ_MUST_USE nsresult ParseURI(nsCString& spec,
+ nsCString& contentType,
+ nsCString* contentCharset,
+ bool& isBase64,
+ nsCString* dataBuffer);
+};
+
+#endif /* nsDataHandler_h___ */
diff --git a/netwerk/protocol/data/nsDataModule.cpp b/netwerk/protocol/data/nsDataModule.cpp
new file mode 100644
index 000000000..f96db8c2e
--- /dev/null
+++ b/netwerk/protocol/data/nsDataModule.cpp
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIModule.h"
+#include "nsIGenericFactory.h"
+#include "nsDataHandler.h"
+
+// The list of components we register
+static const nsModuleComponentInfo components[] = {
+ { "Data Protocol Handler",
+ NS_DATAHANDLER_CID,
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "data",
+ nsDataHandler::Create},
+};
+
+NS_IMPL_NSGETMODULE(nsDataProtocolModule, components)
+
+
+
diff --git a/netwerk/protocol/device/AndroidCaptureProvider.cpp b/netwerk/protocol/device/AndroidCaptureProvider.cpp
new file mode 100644
index 000000000..e69766085
--- /dev/null
+++ b/netwerk/protocol/device/AndroidCaptureProvider.cpp
@@ -0,0 +1,301 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+#include "AndroidCaptureProvider.h"
+#include "nsXULAppAPI.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsMemory.h"
+#include "RawStructs.h"
+
+// The maximum number of frames we keep in our queue. Don't live in the past.
+#define MAX_FRAMES_QUEUED 10
+
+using namespace mozilla::net;
+
+NS_IMPL_ISUPPORTS(AndroidCameraInputStream, nsIInputStream, nsIAsyncInputStream)
+
+AndroidCameraInputStream::AndroidCameraInputStream() :
+ mWidth(0), mHeight(0), mCamera(0), mHeaderSent(false), mClosed(true), mFrameSize(0),
+ mMonitor("AndroidCamera.Monitor")
+{
+ mAvailable = sizeof(RawVideoHeader);
+ mFrameQueue = new nsDeque();
+}
+
+AndroidCameraInputStream::~AndroidCameraInputStream() {
+ // clear the frame queue
+ while (mFrameQueue->GetSize() > 0) {
+ free(mFrameQueue->PopFront());
+ }
+ delete mFrameQueue;
+}
+
+NS_IMETHODIMP
+AndroidCameraInputStream::Init(nsACString& aContentType, nsCaptureParams* aParams)
+{
+ if (!XRE_IsParentProcess())
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ mContentType = aContentType;
+ mWidth = aParams->width;
+ mHeight = aParams->height;
+ mCamera = aParams->camera;
+
+ CameraStreamImpl *impl = CameraStreamImpl::GetInstance(0);
+ if (!impl)
+ return NS_ERROR_OUT_OF_MEMORY;
+ if (impl->Init(mContentType, mCamera, mWidth, mHeight, this)) {
+ mWidth = impl->GetWidth();
+ mHeight = impl->GetHeight();
+ mClosed = false;
+ }
+ return NS_OK;
+}
+
+void AndroidCameraInputStream::ReceiveFrame(char* frame, uint32_t length) {
+ {
+ mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor);
+ if (mFrameQueue->GetSize() > MAX_FRAMES_QUEUED) {
+ free(mFrameQueue->PopFront());
+ mAvailable -= mFrameSize;
+ }
+ }
+
+ mFrameSize = sizeof(RawPacketHeader) + length;
+
+ char* fullFrame = (char*)moz_xmalloc(mFrameSize);
+
+ if (!fullFrame)
+ return;
+
+ RawPacketHeader* header = (RawPacketHeader*) fullFrame;
+ header->packetID = 0xFF;
+ header->codecID = 0x595556; // "YUV"
+
+ // we copy the Y plane, and de-interlace the CrCb
+
+ uint32_t yFrameSize = mWidth * mHeight;
+ uint32_t uvFrameSize = yFrameSize / 4;
+
+ memcpy(fullFrame + sizeof(RawPacketHeader), frame, yFrameSize);
+
+ char* uFrame = fullFrame + yFrameSize;
+ char* vFrame = fullFrame + yFrameSize + uvFrameSize;
+ char* yFrame = frame + yFrameSize;
+ for (uint32_t i = 0; i < uvFrameSize; i++) {
+ uFrame[i] = yFrame[2 * i + 1];
+ vFrame[i] = yFrame[2 * i];
+ }
+
+ {
+ mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor);
+ mAvailable += mFrameSize;
+ mFrameQueue->Push((void*)fullFrame);
+ }
+
+ NotifyListeners();
+}
+
+NS_IMETHODIMP
+AndroidCameraInputStream::Available(uint64_t *aAvailable)
+{
+ mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor);
+
+ *aAvailable = mAvailable;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP AndroidCameraInputStream::IsNonBlocking(bool *aNonBlock) {
+ *aNonBlock = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP AndroidCameraInputStream::Read(char *aBuffer, uint32_t aCount, uint32_t *aRead) {
+ return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aRead);
+}
+
+NS_IMETHODIMP AndroidCameraInputStream::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, uint32_t aCount, uint32_t *aRead) {
+ *aRead = 0;
+
+ nsresult rv;
+
+ if (mAvailable == 0)
+ return NS_BASE_STREAM_WOULD_BLOCK;
+
+ if (aCount > mAvailable)
+ aCount = mAvailable;
+
+ if (!mHeaderSent) {
+ CameraStreamImpl *impl = CameraStreamImpl::GetInstance(0);
+ RawVideoHeader header;
+ header.headerPacketID = 0;
+ header.codecID = 0x595556; // "YUV"
+ header.majorVersion = 0;
+ header.minorVersion = 1;
+ header.options = 1 | 1 << 1; // color, 4:2:2
+
+ header.alphaChannelBpp = 0;
+ header.lumaChannelBpp = 8;
+ header.chromaChannelBpp = 4;
+ header.colorspace = 1;
+
+ header.frameWidth = mWidth;
+ header.frameHeight = mHeight;
+ header.aspectNumerator = 1;
+ header.aspectDenominator = 1;
+
+ header.framerateNumerator = impl->GetFps();
+ header.framerateDenominator = 1;
+
+ rv = aWriter(this, aClosure, (const char*)&header, 0, sizeof(RawVideoHeader), aRead);
+
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ mHeaderSent = true;
+ aCount -= sizeof(RawVideoHeader);
+ mAvailable -= sizeof(RawVideoHeader);
+ }
+
+ {
+ mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor);
+ while ((mAvailable > 0) && (aCount >= mFrameSize)) {
+ uint32_t readThisTime = 0;
+
+ char* frame = (char*)mFrameQueue->PopFront();
+ rv = aWriter(this, aClosure, (const char*)frame, *aRead, mFrameSize, &readThisTime);
+
+ if (readThisTime != mFrameSize) {
+ mFrameQueue->PushFront((void*)frame);
+ return NS_OK;
+ }
+
+ // RawReader does a copy when calling VideoData::Create()
+ free(frame);
+
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ aCount -= readThisTime;
+ mAvailable -= readThisTime;
+ *aRead += readThisTime;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP AndroidCameraInputStream::Close() {
+ return CloseWithStatus(NS_OK);
+}
+
+
+/**
+ * must be called on the main (java) thread
+ */
+void AndroidCameraInputStream::doClose() {
+ NS_ASSERTION(!mClosed, "Camera is already closed");
+
+ CameraStreamImpl *impl = CameraStreamImpl::GetInstance(0);
+ impl->Close();
+ mClosed = true;
+}
+
+
+void AndroidCameraInputStream::NotifyListeners() {
+ mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor);
+
+ if (mCallback && (mAvailable > sizeof(RawVideoHeader))) {
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ if (mCallbackTarget) {
+ callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget);
+ } else {
+ callback = mCallback;
+ }
+
+ NS_ASSERTION(callback, "Shouldn't fail to make the callback!");
+
+ // Null the callback first because OnInputStreamReady may reenter AsyncWait
+ mCallback = nullptr;
+ mCallbackTarget = nullptr;
+
+ callback->OnInputStreamReady(this);
+ }
+}
+
+NS_IMETHODIMP AndroidCameraInputStream::AsyncWait(nsIInputStreamCallback *aCallback, uint32_t aFlags, uint32_t aRequestedCount, nsIEventTarget *aTarget)
+{
+ if (aFlags != 0)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (mCallback || mCallbackTarget)
+ return NS_ERROR_UNEXPECTED;
+
+ mCallbackTarget = aTarget;
+ mCallback = aCallback;
+
+ // What we are being asked for may be present already
+ NotifyListeners();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP AndroidCameraInputStream::CloseWithStatus(nsresult status)
+{
+ AndroidCameraInputStream::doClose();
+ return NS_OK;
+}
+
+/**
+ * AndroidCaptureProvider implementation
+ */
+
+NS_IMPL_ISUPPORTS0(AndroidCaptureProvider)
+
+AndroidCaptureProvider* AndroidCaptureProvider::sInstance = nullptr;
+
+AndroidCaptureProvider::AndroidCaptureProvider() {
+}
+
+AndroidCaptureProvider::~AndroidCaptureProvider() {
+ AndroidCaptureProvider::sInstance = nullptr;
+}
+
+nsresult AndroidCaptureProvider::Init(nsACString& aContentType,
+ nsCaptureParams* aParams,
+ nsIInputStream** aStream) {
+
+ NS_ENSURE_ARG_POINTER(aParams);
+
+ NS_ASSERTION(aParams->frameLimit == 0 || aParams->timeLimit == 0,
+ "Cannot set both a frame limit and a time limit!");
+
+ RefPtr<AndroidCameraInputStream> stream;
+
+ if (aContentType.EqualsLiteral("video/x-raw-yuv")) {
+ stream = new AndroidCameraInputStream();
+ if (stream) {
+ nsresult rv = stream->Init(aContentType, aParams);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else
+ return NS_ERROR_OUT_OF_MEMORY;
+ } else {
+ NS_NOTREACHED("Should not have asked Android for this type!");
+ }
+ stream.forget(aStream);
+ return NS_OK;
+}
+
+already_AddRefed<AndroidCaptureProvider> GetAndroidCaptureProvider() {
+ if (!AndroidCaptureProvider::sInstance) {
+ AndroidCaptureProvider::sInstance = new AndroidCaptureProvider();
+ }
+ RefPtr<AndroidCaptureProvider> ret = AndroidCaptureProvider::sInstance;
+ return ret.forget();
+}
diff --git a/netwerk/protocol/device/AndroidCaptureProvider.h b/netwerk/protocol/device/AndroidCaptureProvider.h
new file mode 100644
index 000000000..dd99ea541
--- /dev/null
+++ b/netwerk/protocol/device/AndroidCaptureProvider.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AndroidDeviceCaptureProvide_h_
+#define AndroidDeviceCaptureProvide_h_
+
+#include "nsDeviceCaptureProvider.h"
+#include "nsIAsyncInputStream.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "mozilla/net/CameraStreamImpl.h"
+#include "nsIEventTarget.h"
+#include "nsDeque.h"
+#include "mozilla/ReentrantMonitor.h"
+
+class AndroidCaptureProvider final : public nsDeviceCaptureProvider {
+ private:
+ ~AndroidCaptureProvider();
+
+ public:
+ AndroidCaptureProvider();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ MOZ_MUST_USE nsresult Init(nsACString& aContentType, nsCaptureParams* aParams, nsIInputStream** aStream) override;
+ static AndroidCaptureProvider* sInstance;
+};
+
+class AndroidCameraInputStream final : public nsIAsyncInputStream, mozilla::net::CameraStreamImpl::FrameCallback {
+ private:
+ ~AndroidCameraInputStream();
+
+ public:
+ AndroidCameraInputStream();
+
+ NS_IMETHODIMP Init(nsACString& aContentType, nsCaptureParams* aParams);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ void ReceiveFrame(char* frame, uint32_t length) override;
+
+ protected:
+ void NotifyListeners();
+ void doClose();
+
+ uint32_t mAvailable;
+ nsCString mContentType;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mCamera;
+ bool mHeaderSent;
+ bool mClosed;
+ nsDeque *mFrameQueue;
+ uint32_t mFrameSize;
+ mozilla::ReentrantMonitor mMonitor;
+
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+};
+
+already_AddRefed<AndroidCaptureProvider> GetAndroidCaptureProvider();
+
+#endif
diff --git a/netwerk/protocol/device/CameraStreamImpl.cpp b/netwerk/protocol/device/CameraStreamImpl.cpp
new file mode 100644
index 000000000..f4a2cf4a4
--- /dev/null
+++ b/netwerk/protocol/device/CameraStreamImpl.cpp
@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CameraStreamImpl.h"
+#include "GeneratedJNINatives.h"
+#include "nsCRTGlue.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Monitor.h"
+
+using namespace mozilla;
+
+namespace mozilla {
+namespace net {
+
+static CameraStreamImpl* mCamera0 = nullptr;
+static CameraStreamImpl* mCamera1 = nullptr;
+
+class CameraStreamImpl::Callback
+ : public java::GeckoAppShell::CameraCallback::Natives<Callback>
+{
+public:
+ static void OnFrameData(int32_t aCamera, jni::ByteArray::Param aData)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ CameraStreamImpl* impl = GetInstance(uint32_t(aCamera));
+ if (impl) {
+ impl->TransmitFrame(aData);
+ }
+ }
+};
+
+/**
+ * CameraStreamImpl
+ */
+
+void CameraStreamImpl::TransmitFrame(jni::ByteArray::Param aData) {
+ if (!mCallback) {
+ return;
+ }
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ const size_t length = size_t(env->GetArrayLength(aData.Get()));
+
+ if (!length) {
+ return;
+ }
+
+ jbyte* const data = env->GetByteArrayElements(aData.Get(), nullptr);
+ mCallback->ReceiveFrame(reinterpret_cast<char*>(data), length);
+ env->ReleaseByteArrayElements(aData.Get(), data, JNI_ABORT);
+}
+
+CameraStreamImpl* CameraStreamImpl::GetInstance(uint32_t aCamera) {
+ CameraStreamImpl* res = nullptr;
+ switch(aCamera) {
+ case 0:
+ if (mCamera0)
+ res = mCamera0;
+ else
+ res = mCamera0 = new CameraStreamImpl(aCamera);
+ break;
+ case 1:
+ if (mCamera1)
+ res = mCamera1;
+ else
+ res = mCamera1 = new CameraStreamImpl(aCamera);
+ break;
+ }
+ return res;
+}
+
+
+CameraStreamImpl::CameraStreamImpl(uint32_t aCamera) :
+ mInit(false), mCamera(aCamera)
+{
+ NS_WARNING("CameraStreamImpl::CameraStreamImpl()");
+ mWidth = 0;
+ mHeight = 0;
+ mFps = 0;
+}
+
+CameraStreamImpl::~CameraStreamImpl()
+{
+ NS_WARNING("CameraStreamImpl::~CameraStreamImpl()");
+}
+
+bool CameraStreamImpl::Init(const nsCString& contentType, const uint32_t& camera, const uint32_t& width, const uint32_t& height, FrameCallback* aCallback)
+{
+ mCallback = aCallback;
+ mWidth = width;
+ mHeight = height;
+
+ Callback::Init();
+ jni::IntArray::LocalRef retArray = java::GeckoAppShell::InitCamera(
+ contentType, int32_t(camera), int32_t(width), int32_t(height));
+ nsTArray<int32_t> ret = retArray->GetElements();
+
+ mWidth = uint32_t(ret[1]);
+ mHeight = uint32_t(ret[2]);
+ mFps = uint32_t(ret[3]);
+
+ return !!ret[0];
+}
+
+void CameraStreamImpl::Close() {
+ java::GeckoAppShell::CloseCamera();
+ mCallback = nullptr;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/device/CameraStreamImpl.h b/netwerk/protocol/device/CameraStreamImpl.h
new file mode 100644
index 000000000..93037caf6
--- /dev/null
+++ b/netwerk/protocol/device/CameraStreamImpl.h
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __CAMERASTREAMIMPL_H__
+#define __CAMERASTREAMIMPL_H__
+
+#include "mozilla/jni/Refs.h"
+
+#include "nsString.h"
+
+/**
+ * This singleton class handles communication with the Android camera
+ * through JNI. It is used by the IPDL parent or directly from the chrome process
+ */
+
+namespace mozilla {
+namespace net {
+
+class CameraStreamImpl {
+public:
+ class FrameCallback {
+ public:
+ virtual void ReceiveFrame(char* frame, uint32_t length) = 0;
+ };
+
+ /**
+ * instance bound to a given camera
+ */
+ static CameraStreamImpl* GetInstance(uint32_t aCamera);
+
+ bool initNeeded() {
+ return !mInit;
+ }
+
+ FrameCallback* GetFrameCallback() {
+ return mCallback;
+ }
+
+ MOZ_MUST_USE bool Init(const nsCString& contentType, const uint32_t& camera, const uint32_t& width, const uint32_t& height, FrameCallback* callback);
+ void Close();
+
+ uint32_t GetWidth() { return mWidth; }
+ uint32_t GetHeight() { return mHeight; }
+ uint32_t GetFps() { return mFps; }
+
+ void takePicture(const nsAString& aFileName);
+
+private:
+ class Callback;
+
+ CameraStreamImpl(uint32_t aCamera);
+ CameraStreamImpl(const CameraStreamImpl&);
+ CameraStreamImpl& operator=(const CameraStreamImpl&);
+
+ ~CameraStreamImpl();
+
+ void TransmitFrame(jni::ByteArray::Param aData);
+
+ bool mInit;
+ uint32_t mCamera;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mFps;
+ FrameCallback* mCallback;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/device/RawStructs.h b/netwerk/protocol/device/RawStructs.h
new file mode 100644
index 000000000..61777877e
--- /dev/null
+++ b/netwerk/protocol/device/RawStructs.h
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(RawStructs_h_)
+#define RawStructs_h_
+
+static const uint32_t RAW_ID = 0x595556;
+
+struct nsRawVideo_PRUint24 {
+ operator uint32_t() const { return value[2] << 16 | value[1] << 8 | value[0]; }
+ nsRawVideo_PRUint24& operator= (const uint32_t& rhs)
+ { value[2] = (rhs & 0x00FF0000) >> 16;
+ value[1] = (rhs & 0x0000FF00) >> 8;
+ value[0] = (rhs & 0x000000FF);
+ return *this; }
+private:
+ uint8_t value[3];
+};
+
+struct RawPacketHeader {
+ typedef nsRawVideo_PRUint24 PRUint24;
+ uint8_t packetID;
+ PRUint24 codecID;
+};
+
+// This is Arc's draft from wiki.xiph.org/OggYUV
+struct RawVideoHeader {
+ typedef nsRawVideo_PRUint24 PRUint24;
+ uint8_t headerPacketID; // Header Packet ID (always 0)
+ PRUint24 codecID; // Codec identifier (always "YUV")
+ uint8_t majorVersion; // Version Major (breaks backwards compat)
+ uint8_t minorVersion; // Version Minor (preserves backwards compat)
+ uint16_t options; // Bit 1: Color (false = B/W)
+ // Bits 2-4: Chroma Pixel Shape
+ // Bit 5: 50% horizontal offset for Cr samples
+ // Bit 6: 50% vertical ...
+ // Bits 7-8: Chroma Blending
+ // Bit 9: Packed (false = Planar)
+ // Bit 10: Cr Staggered Horizontally
+ // Bit 11: Cr Staggered Vertically
+ // Bit 12: Unused (format is always little endian)
+ // Bit 13: Interlaced (false = Progressive)
+ // Bits 14-16: Interlace options (undefined)
+
+ uint8_t alphaChannelBpp;
+ uint8_t lumaChannelBpp;
+ uint8_t chromaChannelBpp;
+ uint8_t colorspace;
+
+ PRUint24 frameWidth;
+ PRUint24 frameHeight;
+ PRUint24 aspectNumerator;
+ PRUint24 aspectDenominator;
+
+ uint32_t framerateNumerator;
+ uint32_t framerateDenominator;
+};
+
+#endif // RawStructs_h_
diff --git a/netwerk/protocol/device/moz.build b/netwerk/protocol/device/moz.build
new file mode 100644
index 000000000..a18672220
--- /dev/null
+++ b/netwerk/protocol/device/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+ EXPORTS.mozilla.net += [
+ 'CameraStreamImpl.h',
+ ]
+ UNIFIED_SOURCES += [
+ 'AndroidCaptureProvider.cpp',
+ 'CameraStreamImpl.cpp',
+ ]
+
+UNIFIED_SOURCES += [
+ 'nsDeviceChannel.cpp',
+ 'nsDeviceProtocolHandler.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base/',
+]
diff --git a/netwerk/protocol/device/nsDeviceCaptureProvider.h b/netwerk/protocol/device/nsDeviceCaptureProvider.h
new file mode 100644
index 000000000..024f6689d
--- /dev/null
+++ b/netwerk/protocol/device/nsDeviceCaptureProvider.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDeviceCaptureProvider_h_
+#define nsDeviceCaptureProvider_h_
+
+#include "nsIInputStream.h"
+
+struct nsCaptureParams {
+ bool captureAudio;
+ bool captureVideo;
+ uint32_t frameRate;
+ uint32_t frameLimit;
+ uint32_t timeLimit;
+ uint32_t width;
+ uint32_t height;
+ uint32_t bpp;
+ uint32_t camera;
+};
+
+class nsDeviceCaptureProvider : public nsISupports
+{
+public:
+ virtual MOZ_MUST_USE nsresult Init(nsACString& aContentType,
+ nsCaptureParams* aParams,
+ nsIInputStream** aStream) = 0;
+};
+
+#endif
diff --git a/netwerk/protocol/device/nsDeviceChannel.cpp b/netwerk/protocol/device/nsDeviceChannel.cpp
new file mode 100644
index 000000000..6c5788a56
--- /dev/null
+++ b/netwerk/protocol/device/nsDeviceChannel.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "plstr.h"
+#include "nsDeviceChannel.h"
+#include "nsDeviceCaptureProvider.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+#include "mozilla/Preferences.h"
+#include "AndroidCaptureProvider.h"
+#endif
+
+using namespace mozilla;
+
+// Copied from image/decoders/icon/nsIconURI.cpp
+// takes a string like ?size=32&contentType=text/html and returns a new string
+// containing just the attribute values. i.e you could pass in this string with
+// an attribute name of "size=", this will return 32
+// Assumption: attribute pairs are separated by &
+void extractAttributeValue(const char* searchString, const char* attributeName, nsCString& result)
+{
+ result.Truncate();
+
+ if (!searchString || !attributeName)
+ return;
+
+ uint32_t attributeNameSize = strlen(attributeName);
+ const char *startOfAttribute = PL_strcasestr(searchString, attributeName);
+ if (!startOfAttribute ||
+ !( *(startOfAttribute-1) == '?' || *(startOfAttribute-1) == '&') )
+ return;
+
+ startOfAttribute += attributeNameSize; // Skip the attributeName
+ if (!*startOfAttribute)
+ return;
+
+ const char *endOfAttribute = strchr(startOfAttribute, '&');
+ if (endOfAttribute) {
+ result.Assign(Substring(startOfAttribute, endOfAttribute));
+ } else {
+ result.Assign(startOfAttribute);
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsDeviceChannel,
+ nsBaseChannel,
+ nsIChannel)
+
+// nsDeviceChannel methods
+nsDeviceChannel::nsDeviceChannel()
+{
+ SetContentType(NS_LITERAL_CSTRING("image/png"));
+}
+
+nsDeviceChannel::~nsDeviceChannel()
+{
+}
+
+nsresult
+nsDeviceChannel::Init(nsIURI* aUri)
+{
+ nsBaseChannel::Init();
+ nsBaseChannel::SetURI(aUri);
+ return NS_OK;
+}
+
+nsresult
+nsDeviceChannel::OpenContentStream(bool aAsync,
+ nsIInputStream** aStream,
+ nsIChannel** aChannel)
+{
+ if (!aAsync)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsCOMPtr<nsIURI> uri = nsBaseChannel::URI();
+ *aStream = nullptr;
+ *aChannel = nullptr;
+ NS_NAMED_LITERAL_CSTRING(width, "width=");
+ NS_NAMED_LITERAL_CSTRING(height, "height=");
+
+ nsAutoCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString type;
+
+ RefPtr<nsDeviceCaptureProvider> capture;
+ nsCaptureParams captureParams;
+ captureParams.camera = 0;
+ if (kNotFound != spec.Find(NS_LITERAL_CSTRING("type=image/png"),
+ true,
+ 0,
+ -1)) {
+ type.AssignLiteral("image/png");
+ SetContentType(type);
+ captureParams.captureAudio = false;
+ captureParams.captureVideo = true;
+ captureParams.timeLimit = 0;
+ captureParams.frameLimit = 1;
+ nsAutoCString buffer;
+ extractAttributeValue(spec.get(), "width=", buffer);
+ nsresult err;
+ captureParams.width = buffer.ToInteger(&err);
+ if (!captureParams.width)
+ captureParams.width = 640;
+ extractAttributeValue(spec.get(), "height=", buffer);
+ captureParams.height = buffer.ToInteger(&err);
+ if (!captureParams.height)
+ captureParams.height = 480;
+ extractAttributeValue(spec.get(), "camera=", buffer);
+ captureParams.camera = buffer.ToInteger(&err);
+ captureParams.bpp = 32;
+#ifdef MOZ_WIDGET_ANDROID
+ capture = GetAndroidCaptureProvider();
+#endif
+ } else if (kNotFound != spec.Find(NS_LITERAL_CSTRING("type=video/x-raw-yuv"),
+ true,
+ 0,
+ -1)) {
+ type.AssignLiteral("video/x-raw-yuv");
+ SetContentType(type);
+ captureParams.captureAudio = false;
+ captureParams.captureVideo = true;
+ nsAutoCString buffer;
+ extractAttributeValue(spec.get(), "width=", buffer);
+ nsresult err;
+ captureParams.width = buffer.ToInteger(&err);
+ if (!captureParams.width)
+ captureParams.width = 640;
+ extractAttributeValue(spec.get(), "height=", buffer);
+ captureParams.height = buffer.ToInteger(&err);
+ if (!captureParams.height)
+ captureParams.height = 480;
+ extractAttributeValue(spec.get(), "camera=", buffer);
+ captureParams.camera = buffer.ToInteger(&err);
+ captureParams.bpp = 32;
+ captureParams.timeLimit = 0;
+ captureParams.frameLimit = 60000;
+#ifdef MOZ_WIDGET_ANDROID
+ // only enable if "device.camera.enabled" is true.
+ if (Preferences::GetBool("device.camera.enabled", false) == true)
+ capture = GetAndroidCaptureProvider();
+#endif
+ } else {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (!capture)
+ return NS_ERROR_FAILURE;
+
+ return capture->Init(type, &captureParams, aStream);
+}
diff --git a/netwerk/protocol/device/nsDeviceChannel.h b/netwerk/protocol/device/nsDeviceChannel.h
new file mode 100644
index 000000000..8c3e44793
--- /dev/null
+++ b/netwerk/protocol/device/nsDeviceChannel.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDeviceChannel_h_
+#define nsDeviceChannel_h_
+
+#include "nsBaseChannel.h"
+
+class nsDeviceChannel : public nsBaseChannel
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsDeviceChannel();
+
+ MOZ_MUST_USE nsresult Init(nsIURI* uri);
+ MOZ_MUST_USE nsresult OpenContentStream(bool aAsync,
+ nsIInputStream **aStream,
+ nsIChannel **aChannel) override;
+
+protected:
+ ~nsDeviceChannel();
+};
+#endif
diff --git a/netwerk/protocol/device/nsDeviceProtocolHandler.cpp b/netwerk/protocol/device/nsDeviceProtocolHandler.cpp
new file mode 100644
index 000000000..26c5f33df
--- /dev/null
+++ b/netwerk/protocol/device/nsDeviceProtocolHandler.cpp
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDeviceProtocolHandler.h"
+#include "nsDeviceChannel.h"
+#include "nsAutoPtr.h"
+#include "nsSimpleURI.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+NS_IMPL_ISUPPORTS(nsDeviceProtocolHandler,
+ nsIProtocolHandler)
+
+nsresult
+nsDeviceProtocolHandler::Init(){
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::GetScheme(nsACString &aResult)
+{
+ aResult.AssignLiteral("moz-device");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::GetDefaultPort(int32_t *aResult)
+{
+ *aResult = -1; // no port for moz_device: URLs
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::GetProtocolFlags(uint32_t *aResult)
+{
+ *aResult = URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::NewURI(const nsACString &spec,
+ const char *originCharset,
+ nsIURI *baseURI,
+ nsIURI **result)
+{
+ RefPtr<nsSimpleURI> uri = new nsSimpleURI();
+
+ nsresult rv = uri->SetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uri.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::NewChannel2(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aResult)
+{
+ RefPtr<nsDeviceChannel> channel = new nsDeviceChannel();
+ nsresult rv = channel->Init(aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // set the loadInfo on the new channel
+ rv = channel->SetLoadInfo(aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::NewChannel(nsIURI* aURI, nsIChannel **aResult)
+{
+ return NewChannel2(aURI, nullptr, aResult);
+}
+
+NS_IMETHODIMP
+nsDeviceProtocolHandler::AllowPort(int32_t port,
+ const char *scheme,
+ bool *aResult)
+{
+ // don't override anything.
+ *aResult = false;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/device/nsDeviceProtocolHandler.h b/netwerk/protocol/device/nsDeviceProtocolHandler.h
new file mode 100644
index 000000000..dee9b4f8f
--- /dev/null
+++ b/netwerk/protocol/device/nsDeviceProtocolHandler.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDeviceProtocolHandler_h_
+#define nsDeviceProtocolHandler_h_
+
+#include "nsIProtocolHandler.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace net {
+
+// {6b0ffe9e-d114-486b-aeb7-da62e7273ed5}
+#define NS_DEVICEPROTOCOLHANDLER_CID \
+{ 0x60ffe9e, 0xd114, 0x486b, \
+ {0xae, 0xb7, 0xda, 0x62, 0xe7, 0x27, 0x3e, 0xd5} }
+
+class nsDeviceProtocolHandler final : public nsIProtocolHandler {
+ ~nsDeviceProtocolHandler() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ nsDeviceProtocolHandler() {}
+
+ MOZ_MUST_USE nsresult Init();
+};
+
+} // namespace net
+} // namespace mozilla
+#endif
diff --git a/netwerk/protocol/file/moz.build b/netwerk/protocol/file/moz.build
new file mode 100644
index 000000000..223ff2f2b
--- /dev/null
+++ b/netwerk/protocol/file/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.net += [
+ 'nsFileProtocolHandler.h',
+]
+
+XPIDL_SOURCES += [
+ 'nsIFileChannel.idl',
+ 'nsIFileProtocolHandler.idl',
+]
+
+XPIDL_MODULE = 'necko_file'
+
+UNIFIED_SOURCES += [
+ 'nsFileChannel.cpp',
+ 'nsFileProtocolHandler.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/protocol/file/nsFileChannel.cpp b/netwerk/protocol/file/nsFileChannel.cpp
new file mode 100644
index 000000000..0400aaec0
--- /dev/null
+++ b/netwerk/protocol/file/nsFileChannel.cpp
@@ -0,0 +1,486 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOService.h"
+#include "nsFileChannel.h"
+#include "nsBaseContentStream.h"
+#include "nsDirectoryIndexStream.h"
+#include "nsThreadUtils.h"
+#include "nsTransportUtils.h"
+#include "nsStreamUtils.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsIOutputStream.h"
+#include "nsIFileStreams.h"
+#include "nsFileProtocolHandler.h"
+#include "nsProxyRelease.h"
+#include "nsAutoPtr.h"
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+
+#include "nsIFileURL.h"
+#include "nsIFile.h"
+#include "nsIMIMEService.h"
+#include "prio.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+//-----------------------------------------------------------------------------
+
+class nsFileCopyEvent : public Runnable {
+public:
+ nsFileCopyEvent(nsIOutputStream *dest, nsIInputStream *source, int64_t len)
+ : mDest(dest)
+ , mSource(source)
+ , mLen(len)
+ , mStatus(NS_OK)
+ , mInterruptStatus(NS_OK) {
+ }
+
+ // Read the current status of the file copy operation.
+ nsresult Status() { return mStatus; }
+
+ // Call this method to perform the file copy synchronously.
+ void DoCopy();
+
+ // Call this method to perform the file copy on a background thread. The
+ // callback is dispatched when the file copy completes.
+ nsresult Dispatch(nsIRunnable *callback,
+ nsITransportEventSink *sink,
+ nsIEventTarget *target);
+
+ // Call this method to interrupt a file copy operation that is occuring on
+ // a background thread. The status parameter passed to this function must
+ // be a failure code and is set as the status of this file copy operation.
+ void Interrupt(nsresult status) {
+ NS_ASSERTION(NS_FAILED(status), "must be a failure code");
+ mInterruptStatus = status;
+ }
+
+ NS_IMETHOD Run() override {
+ DoCopy();
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+ nsCOMPtr<nsIRunnable> mCallback;
+ nsCOMPtr<nsITransportEventSink> mSink;
+ nsCOMPtr<nsIOutputStream> mDest;
+ nsCOMPtr<nsIInputStream> mSource;
+ int64_t mLen;
+ nsresult mStatus; // modified on i/o thread only
+ nsresult mInterruptStatus; // modified on main thread only
+};
+
+void
+nsFileCopyEvent::DoCopy()
+{
+ // We'll copy in chunks this large by default. This size affects how
+ // frequently we'll check for interrupts.
+ const int32_t chunk = nsIOService::gDefaultSegmentSize * nsIOService::gDefaultSegmentCount;
+
+ nsresult rv = NS_OK;
+
+ int64_t len = mLen, progress = 0;
+ while (len) {
+ // If we've been interrupted, then stop copying.
+ rv = mInterruptStatus;
+ if (NS_FAILED(rv))
+ break;
+
+ int32_t num = std::min((int32_t) len, chunk);
+
+ uint32_t result;
+ rv = mSource->ReadSegments(NS_CopySegmentToStream, mDest, num, &result);
+ if (NS_FAILED(rv))
+ break;
+ if (result != (uint32_t) num) {
+ rv = NS_ERROR_FILE_DISK_FULL; // stopped prematurely (out of disk space)
+ break;
+ }
+
+ // Dispatch progress notification
+ if (mSink) {
+ progress += num;
+ mSink->OnTransportStatus(nullptr, NS_NET_STATUS_WRITING, progress,
+ mLen);
+ }
+
+ len -= num;
+ }
+
+ if (NS_FAILED(rv))
+ mStatus = rv;
+
+ // Close the output stream before notifying our callback so that others may
+ // freely "play" with the file.
+ mDest->Close();
+
+ // Notify completion
+ if (mCallback) {
+ mCallbackTarget->Dispatch(mCallback, NS_DISPATCH_NORMAL);
+
+ // Release the callback on the target thread to avoid destroying stuff on
+ // the wrong thread.
+ NS_ProxyRelease(mCallbackTarget, mCallback.forget());
+ }
+}
+
+nsresult
+nsFileCopyEvent::Dispatch(nsIRunnable *callback,
+ nsITransportEventSink *sink,
+ nsIEventTarget *target)
+{
+ // Use the supplied event target for all asynchronous operations.
+
+ mCallback = callback;
+ mCallbackTarget = target;
+
+ // Build a coalescing proxy for progress events
+ nsresult rv = net_NewTransportEventSinkProxy(getter_AddRefs(mSink), sink, target);
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Dispatch ourselves to I/O thread pool...
+ nsCOMPtr<nsIEventTarget> pool =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return pool->Dispatch(this, NS_DISPATCH_NORMAL);
+}
+
+//-----------------------------------------------------------------------------
+
+// This is a dummy input stream that when read, performs the file copy. The
+// copy happens on a background thread via mCopyEvent.
+
+class nsFileUploadContentStream : public nsBaseContentStream {
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsFileUploadContentStream(bool nonBlocking,
+ nsIOutputStream *dest,
+ nsIInputStream *source,
+ int64_t len,
+ nsITransportEventSink *sink)
+ : nsBaseContentStream(nonBlocking)
+ , mCopyEvent(new nsFileCopyEvent(dest, source, len))
+ , mSink(sink) {
+ }
+
+ bool IsInitialized() {
+ return mCopyEvent != nullptr;
+ }
+
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun fun, void *closure,
+ uint32_t count, uint32_t *result) override;
+ NS_IMETHOD AsyncWait(nsIInputStreamCallback *callback, uint32_t flags,
+ uint32_t count, nsIEventTarget *target) override;
+
+private:
+ virtual ~nsFileUploadContentStream() {}
+
+ void OnCopyComplete();
+
+ RefPtr<nsFileCopyEvent> mCopyEvent;
+ nsCOMPtr<nsITransportEventSink> mSink;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsFileUploadContentStream,
+ nsBaseContentStream)
+
+NS_IMETHODIMP
+nsFileUploadContentStream::ReadSegments(nsWriteSegmentFun fun, void *closure,
+ uint32_t count, uint32_t *result)
+{
+ *result = 0; // nothing is ever actually read from this stream
+
+ if (IsClosed())
+ return NS_OK;
+
+ if (IsNonBlocking()) {
+ // Inform the caller that they will have to wait for the copy operation to
+ // complete asynchronously. We'll kick of the copy operation once they
+ // call AsyncWait.
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // Perform copy synchronously, and then close out the stream.
+ mCopyEvent->DoCopy();
+ nsresult status = mCopyEvent->Status();
+ CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED);
+ return status;
+}
+
+NS_IMETHODIMP
+nsFileUploadContentStream::AsyncWait(nsIInputStreamCallback *callback,
+ uint32_t flags, uint32_t count,
+ nsIEventTarget *target)
+{
+ nsresult rv = nsBaseContentStream::AsyncWait(callback, flags, count, target);
+ if (NS_FAILED(rv) || IsClosed())
+ return rv;
+
+ if (IsNonBlocking()) {
+ nsCOMPtr<nsIRunnable> callback =
+ NewRunnableMethod(this, &nsFileUploadContentStream::OnCopyComplete);
+ mCopyEvent->Dispatch(callback, mSink, target);
+ }
+
+ return NS_OK;
+}
+
+void
+nsFileUploadContentStream::OnCopyComplete()
+{
+ // This method is being called to indicate that we are done copying.
+ nsresult status = mCopyEvent->Status();
+
+ CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED);
+}
+
+//-----------------------------------------------------------------------------
+
+nsFileChannel::nsFileChannel(nsIURI *uri)
+{
+ // If we have a link file, we should resolve its target right away.
+ // This is to protect against a same origin attack where the same link file
+ // can point to different resources right after the first resource is loaded.
+ nsCOMPtr<nsIFile> file;
+ nsCOMPtr <nsIURI> targetURI;
+ nsAutoCString fileTarget;
+ nsCOMPtr<nsIFile> resolvedFile;
+ bool symLink;
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri);
+ if (fileURL &&
+ NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) &&
+ NS_SUCCEEDED(file->IsSymlink(&symLink)) &&
+ symLink &&
+ NS_SUCCEEDED(file->GetNativeTarget(fileTarget)) &&
+ NS_SUCCEEDED(NS_NewNativeLocalFile(fileTarget, PR_TRUE,
+ getter_AddRefs(resolvedFile))) &&
+ NS_SUCCEEDED(NS_NewFileURI(getter_AddRefs(targetURI),
+ resolvedFile, nullptr))) {
+ // Make an effort to match up the query strings.
+ nsCOMPtr<nsIURL> origURL = do_QueryInterface(uri);
+ nsCOMPtr<nsIURL> targetURL = do_QueryInterface(targetURI);
+ nsAutoCString queryString;
+ if (origURL && targetURL && NS_SUCCEEDED(origURL->GetQuery(queryString))) {
+ targetURL->SetQuery(queryString);
+ }
+
+ SetURI(targetURI);
+ SetOriginalURI(uri);
+ nsLoadFlags loadFlags = 0;
+ GetLoadFlags(&loadFlags);
+ SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE);
+ } else {
+ SetURI(uri);
+ }
+}
+
+nsFileChannel::~nsFileChannel()
+{
+}
+
+nsresult
+nsFileChannel::MakeFileInputStream(nsIFile *file,
+ nsCOMPtr<nsIInputStream> &stream,
+ nsCString &contentType,
+ bool async)
+{
+ // we accept that this might result in a disk hit to stat the file
+ bool isDir;
+ nsresult rv = file->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) {
+ // canonicalize error message
+ if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+ rv = NS_ERROR_FILE_NOT_FOUND;
+
+ if (async && (NS_ERROR_FILE_NOT_FOUND == rv)) {
+ // We don't return "Not Found" errors here. Since we could not find
+ // the file, it's not a directory anyway.
+ isDir = false;
+ } else {
+ return rv;
+ }
+ }
+
+ if (isDir) {
+ rv = nsDirectoryIndexStream::Create(file, getter_AddRefs(stream));
+ if (NS_SUCCEEDED(rv) && !HasContentTypeHint())
+ contentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT);
+ } else {
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, -1, -1,
+ async? nsIFileInputStream::DEFER_OPEN : 0);
+ if (NS_SUCCEEDED(rv) && !HasContentTypeHint()) {
+ // Use file extension to infer content type
+ nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mime->GetTypeFromFile(file, contentType);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsFileChannel::OpenContentStream(bool async, nsIInputStream **result,
+ nsIChannel** channel)
+{
+ // NOTE: the resulting file is a clone, so it is safe to pass it to the
+ // file input stream which will be read on a background thread.
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIURI> newURI;
+ rv = fileHandler->ReadURLFile(file, getter_AddRefs(newURI));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = NS_NewChannel(getter_AddRefs(newChannel),
+ newURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ *result = nullptr;
+ newChannel.forget(channel);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+
+ if (mUploadStream) {
+ // Pass back a nsFileUploadContentStream instance that knows how to perform
+ // the file copy when "read" (the resulting stream in this case does not
+ // actually return any data).
+
+ nsCOMPtr<nsIOutputStream> fileStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileStream), file,
+ PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
+ PR_IRUSR | PR_IWUSR);
+ if (NS_FAILED(rv))
+ return rv;
+
+ RefPtr<nsFileUploadContentStream> uploadStream =
+ new nsFileUploadContentStream(async, fileStream, mUploadStream,
+ mUploadLength, this);
+ if (!uploadStream || !uploadStream->IsInitialized()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ stream = uploadStream.forget();
+
+ mContentLength = 0;
+
+ // Since there isn't any content to speak of we just set the content-type
+ // to something other than "unknown" to avoid triggering the content-type
+ // sniffer code in nsBaseChannel.
+ // However, don't override explicitly set types.
+ if (!HasContentTypeHint())
+ SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
+ } else {
+ nsAutoCString contentType;
+ rv = MakeFileInputStream(file, stream, contentType, async);
+ if (NS_FAILED(rv))
+ return rv;
+
+ EnableSynthesizedProgressEvents(true);
+
+ // fixup content length and type
+ if (mContentLength < 0) {
+ int64_t size;
+ rv = file->GetFileSize(&size);
+ if (NS_FAILED(rv)) {
+ if (async &&
+ (NS_ERROR_FILE_NOT_FOUND == rv ||
+ NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv)) {
+ size = 0;
+ } else {
+ return rv;
+ }
+ }
+ mContentLength = size;
+ }
+ if (!contentType.IsEmpty())
+ SetContentType(contentType);
+ }
+
+ *result = nullptr;
+ stream.swap(*result);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileChannel,
+ nsBaseChannel,
+ nsIUploadChannel,
+ nsIFileChannel)
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsIFileChannel
+
+NS_IMETHODIMP
+nsFileChannel::GetFile(nsIFile **file)
+{
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(URI());
+ NS_ENSURE_STATE(fileURL);
+
+ // This returns a cloned nsIFile
+ return fileURL->GetFile(file);
+}
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsIUploadChannel
+
+NS_IMETHODIMP
+nsFileChannel::SetUploadStream(nsIInputStream *stream,
+ const nsACString &contentType,
+ int64_t contentLength)
+{
+ NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
+
+ if ((mUploadStream = stream)) {
+ mUploadLength = contentLength;
+ if (mUploadLength < 0) {
+ // Make sure we know how much data we are uploading.
+ uint64_t avail;
+ nsresult rv = mUploadStream->Available(&avail);
+ if (NS_FAILED(rv))
+ return rv;
+ // if this doesn't fit in the javascript MAX_SAFE_INTEGER
+ // pretend we don't know the size
+ mUploadLength = InScriptableRange(avail) ? avail : -1;
+ }
+ } else {
+ mUploadLength = -1;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileChannel::GetUploadStream(nsIInputStream **result)
+{
+ NS_IF_ADDREF(*result = mUploadStream);
+ return NS_OK;
+}
diff --git a/netwerk/protocol/file/nsFileChannel.h b/netwerk/protocol/file/nsFileChannel.h
new file mode 100644
index 000000000..2f1f71ba3
--- /dev/null
+++ b/netwerk/protocol/file/nsFileChannel.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFileChannel_h__
+#define nsFileChannel_h__
+
+#include "nsBaseChannel.h"
+#include "nsIFileChannel.h"
+#include "nsIUploadChannel.h"
+
+class nsFileChannel : public nsBaseChannel
+ , public nsIFileChannel
+ , public nsIUploadChannel
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILECHANNEL
+ NS_DECL_NSIUPLOADCHANNEL
+
+ explicit nsFileChannel(nsIURI *uri);
+
+protected:
+ ~nsFileChannel();
+
+ // Called to construct a blocking file input stream for the given file. This
+ // method also returns a best guess at the content-type for the data stream.
+ // NOTE: If the channel has a type hint set, contentType will be left
+ // untouched. The caller should not use it in that case.
+ MOZ_MUST_USE nsresult MakeFileInputStream(nsIFile *file,
+ nsCOMPtr<nsIInputStream> &stream,
+ nsCString &contentType, bool async);
+
+ virtual MOZ_MUST_USE nsresult OpenContentStream(bool async,
+ nsIInputStream **result,
+ nsIChannel** channel) override;
+
+private:
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ int64_t mUploadLength;
+};
+
+#endif // !nsFileChannel_h__
diff --git a/netwerk/protocol/file/nsFileProtocolHandler.cpp b/netwerk/protocol/file/nsFileProtocolHandler.cpp
new file mode 100644
index 000000000..e55cb9d47
--- /dev/null
+++ b/netwerk/protocol/file/nsFileProtocolHandler.cpp
@@ -0,0 +1,274 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// vim:ts=4 sw=4 sts=4 et cin:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIFile.h"
+#include "nsFileProtocolHandler.h"
+#include "nsFileChannel.h"
+#include "nsStandardURL.h"
+#include "nsURLHelper.h"
+
+#include "nsNetUtil.h"
+
+// URL file handling, copied and modified from xpfe/components/bookmarks/src/nsBookmarksService.cpp
+#ifdef XP_WIN
+#include <shlobj.h>
+#include <intshcut.h>
+#include "nsIFileURL.h"
+#ifdef CompareString
+#undef CompareString
+#endif
+#endif
+
+// URL file handling for freedesktop.org
+#ifdef XP_UNIX
+#include "nsINIParser.h"
+#define DESKTOP_ENTRY_SECTION "Desktop Entry"
+#endif
+
+//-----------------------------------------------------------------------------
+
+nsFileProtocolHandler::nsFileProtocolHandler()
+{
+}
+
+nsresult
+nsFileProtocolHandler::Init()
+{
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsFileProtocolHandler,
+ nsIFileProtocolHandler,
+ nsIProtocolHandler,
+ nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsIProtocolHandler methods:
+
+#if defined(XP_WIN)
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadURLFile(nsIFile* aFile, nsIURI** aURI)
+{
+ nsAutoString path;
+ nsresult rv = aFile->GetPath(path);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (path.Length() < 4)
+ return NS_ERROR_NOT_AVAILABLE;
+ if (!StringTail(path, 4).LowerCaseEqualsLiteral(".url"))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ HRESULT result;
+
+ rv = NS_ERROR_NOT_AVAILABLE;
+
+ IUniformResourceLocatorW* urlLink = nullptr;
+ result = ::CoCreateInstance(CLSID_InternetShortcut, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IUniformResourceLocatorW, (void**)&urlLink);
+ if (SUCCEEDED(result) && urlLink) {
+ IPersistFile* urlFile = nullptr;
+ result = urlLink->QueryInterface(IID_IPersistFile, (void**)&urlFile);
+ if (SUCCEEDED(result) && urlFile) {
+ result = urlFile->Load(path.get(), STGM_READ);
+ if (SUCCEEDED(result) ) {
+ LPWSTR lpTemp = nullptr;
+
+ // The URL this method will give us back seems to be already
+ // escaped. Hence, do not do escaping of our own.
+ result = urlLink->GetURL(&lpTemp);
+ if (SUCCEEDED(result) && lpTemp) {
+ rv = NS_NewURI(aURI, nsDependentString(lpTemp));
+ // free the string that GetURL alloc'd
+ CoTaskMemFree(lpTemp);
+ }
+ }
+ urlFile->Release();
+ }
+ urlLink->Release();
+ }
+ return rv;
+}
+
+#elif defined(XP_UNIX)
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadURLFile(nsIFile* aFile, nsIURI** aURI)
+{
+ // We only support desktop files that end in ".desktop" like the spec says:
+ // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s02.html
+ nsAutoCString leafName;
+ nsresult rv = aFile->GetNativeLeafName(leafName);
+ if (NS_FAILED(rv) ||
+ !StringEndsWith(leafName, NS_LITERAL_CSTRING(".desktop")))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ bool isFile = false;
+ rv = aFile->IsFile(&isFile);
+ if (NS_FAILED(rv) || !isFile) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsINIParser parser;
+ rv = parser.Init(aFile);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString type;
+ parser.GetString(DESKTOP_ENTRY_SECTION, "Type", type);
+ if (!type.EqualsLiteral("Link"))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoCString url;
+ rv = parser.GetString(DESKTOP_ENTRY_SECTION, "URL", url);
+ if (NS_FAILED(rv) || url.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_NewURI(aURI, url);
+}
+
+#else // other platforms
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadURLFile(nsIFile* aFile, nsIURI** aURI)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+#endif // ReadURLFile()
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetScheme(nsACString &result)
+{
+ result.AssignLiteral("file");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetDefaultPort(int32_t *result)
+{
+ *result = -1; // no port for file: URLs
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ *result = URI_NOAUTH | URI_IS_LOCAL_FILE | URI_IS_LOCAL_RESOURCE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewURI(const nsACString &spec,
+ const char *charset,
+ nsIURI *baseURI,
+ nsIURI **result)
+{
+ nsCOMPtr<nsIStandardURL> url = new nsStandardURL(true);
+ if (!url)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ const nsACString *specPtr = &spec;
+
+#if defined(XP_WIN)
+ nsAutoCString buf;
+ if (net_NormalizeFileURL(spec, buf))
+ specPtr = &buf;
+#endif
+
+ nsresult rv = url->Init(nsIStandardURL::URLTYPE_NO_AUTHORITY, -1,
+ *specPtr, charset, baseURI);
+ if (NS_FAILED(rv)) return rv;
+
+ return CallQueryInterface(url, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ nsFileChannel *chan = new nsFileChannel(uri);
+ if (!chan)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(chan);
+
+ nsresult rv = chan->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(chan);
+ return rv;
+ }
+
+ // set the loadInfo on the new channel
+ rv = chan->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(chan);
+ return rv;
+ }
+
+ *result = chan;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewChannel(nsIURI *uri, nsIChannel **result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *result)
+{
+ // don't override anything.
+ *result = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIFileProtocolHandler methods:
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewFileURI(nsIFile *file, nsIURI **result)
+{
+ NS_ENSURE_ARG_POINTER(file);
+ nsresult rv;
+
+ nsCOMPtr<nsIFileURL> url = new nsStandardURL(true);
+ if (!url)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // NOTE: the origin charset is assigned the value of the platform
+ // charset by the SetFile method.
+ rv = url->SetFile(file);
+ if (NS_FAILED(rv)) return rv;
+
+ return CallQueryInterface(url, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetURLSpecFromFile(nsIFile *file, nsACString &result)
+{
+ NS_ENSURE_ARG_POINTER(file);
+ return net_GetURLSpecFromFile(file, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetURLSpecFromActualFile(nsIFile *file,
+ nsACString &result)
+{
+ NS_ENSURE_ARG_POINTER(file);
+ return net_GetURLSpecFromActualFile(file, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetURLSpecFromDir(nsIFile *file, nsACString &result)
+{
+ NS_ENSURE_ARG_POINTER(file);
+ return net_GetURLSpecFromDir(file, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetFileFromURLSpec(const nsACString &spec, nsIFile **result)
+{
+ return net_GetFileFromURLSpec(spec, result);
+}
diff --git a/netwerk/protocol/file/nsFileProtocolHandler.h b/netwerk/protocol/file/nsFileProtocolHandler.h
new file mode 100644
index 000000000..211eb2873
--- /dev/null
+++ b/netwerk/protocol/file/nsFileProtocolHandler.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFileProtocolHandler_h__
+#define nsFileProtocolHandler_h__
+
+#include "nsIFileProtocolHandler.h"
+#include "nsWeakReference.h"
+
+class nsFileProtocolHandler : public nsIFileProtocolHandler
+ , public nsSupportsWeakReference
+{
+ virtual ~nsFileProtocolHandler() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIFILEPROTOCOLHANDLER
+
+ nsFileProtocolHandler();
+
+ MOZ_MUST_USE nsresult Init();
+};
+
+#endif // !nsFileProtocolHandler_h__
diff --git a/netwerk/protocol/file/nsIFileChannel.idl b/netwerk/protocol/file/nsIFileChannel.idl
new file mode 100644
index 000000000..e5fcdecb4
--- /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 000000000..c39d54f51
--- /dev/null
+++ b/netwerk/protocol/file/nsIFileProtocolHandler.idl
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+interface nsIFile;
+
+[scriptable, uuid(1fb25bd5-4354-4dcd-8d97-621b7b3ed2e4)]
+interface nsIFileProtocolHandler : nsIProtocolHandler
+{
+ /**
+ * This method constructs a new file URI
+ *
+ * @param aFile nsIFile
+ * @return reference to a new nsIURI object
+ */
+ nsIURI newFileURI(in nsIFile aFile);
+
+ /**
+ * Converts the nsIFile to the corresponding URL string. NOTE: under
+ * some platforms this is a lossy conversion (e.g., Mac Carbon build).
+ * If the nsIFile is a local file, then the result will be a file://
+ * URL string.
+ *
+ * The resulting string may contain URL-escaped characters.
+ * NOTE: Callers should use getURLSpecFromActualFile or
+ * getURLSpecFromDirFile if possible, for performance reasons.
+ */
+ AUTF8String getURLSpecFromFile(in nsIFile file);
+
+ /**
+ * Converts the nsIFile to the corresponding URL string. Should
+ * only be called on files which are not directories. Otherwise
+ * identical to getURLSpecFromFile, but is usually more efficient.
+ * WARNING: This restriction may not be enforced at runtime!
+ */
+ AUTF8String getURLSpecFromActualFile(in nsIFile file);
+
+ /**
+ * Converts the nsIFile to the corresponding URL string. Should
+ * only be called on files which are directories. Otherwise
+ * identical to getURLSpecFromFile, but is usually more efficient.
+ * WARNING: This restriction may not be enforced at runtime!
+ */
+ AUTF8String getURLSpecFromDir(in nsIFile file);
+
+ /**
+ * Converts the URL string into the corresponding nsIFile if possible.
+ * A local file will be created if the URL string begins with file://.
+ */
+ nsIFile getFileFromURLSpec(in AUTF8String url);
+
+ /**
+ * Takes a local file and tries to interpret it as an internet shortcut
+ * (e.g. .url files on windows).
+ * @param file The local file to read
+ * @return The URI the file refers to
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if the OS does not support such files.
+ * @throw NS_ERROR_NOT_AVAILABLE if this file is not an internet shortcut.
+ */
+ nsIURI readURLFile(in nsIFile file);
+};
diff --git a/netwerk/protocol/ftp/FTPChannelChild.cpp b/netwerk/protocol/ftp/FTPChannelChild.cpp
new file mode 100644
index 000000000..f8284aae3
--- /dev/null
+++ b/netwerk/protocol/ftp/FTPChannelChild.cpp
@@ -0,0 +1,932 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/ChannelDiverterChild.h"
+#include "mozilla/net/FTPChannelChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "nsFtpProtocolHandler.h"
+#include "nsITabChild.h"
+#include "nsStringStream.h"
+#include "nsNetUtil.h"
+#include "base/compiler_specific.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "nsIPrompt.h"
+
+using namespace mozilla::ipc;
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
+
+namespace mozilla {
+namespace net {
+
+FTPChannelChild::FTPChannelChild(nsIURI* uri)
+: mIPCOpen(false)
+, mUnknownDecoderInvolved(false)
+, mCanceled(false)
+, mSuspendCount(0)
+, mIsPending(false)
+, mLastModifiedTime(0)
+, mStartPos(0)
+, mDivertingToParent(false)
+, mFlushedForDiversion(false)
+, mSuspendSent(false)
+{
+ LOG(("Creating FTPChannelChild @%x\n", this));
+ // grab a reference to the handler to ensure that it doesn't go away.
+ NS_ADDREF(gFtpHandler);
+ SetURI(uri);
+ mEventQ = new ChannelEventQueue(static_cast<nsIFTPChannel*>(this));
+
+ // We could support thread retargeting, but as long as we're being driven by
+ // IPDL on the main thread it doesn't buy us anything.
+ DisallowThreadRetargeting();
+}
+
+FTPChannelChild::~FTPChannelChild()
+{
+ LOG(("Destroying FTPChannelChild @%x\n", this));
+ gFtpHandler->Release();
+}
+
+void
+FTPChannelChild::AddIPDLReference()
+{
+ MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void
+FTPChannelChild::ReleaseIPDLReference()
+{
+ MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference");
+ mIPCOpen = false;
+ Release();
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelChild::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS_INHERITED(FTPChannelChild,
+ nsBaseChannel,
+ nsIFTPChannel,
+ nsIUploadChannel,
+ nsIResumableChannel,
+ nsIProxiedChannel,
+ nsIChildChannel,
+ nsIDivertableChannel)
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelChild::GetLastModifiedTime(PRTime* lastModifiedTime)
+{
+ *lastModifiedTime = mLastModifiedTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::SetLastModifiedTime(PRTime lastModifiedTime)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::ResumeAt(uint64_t aStartPos, const nsACString& aEntityID)
+{
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ mStartPos = aStartPos;
+ mEntityID = aEntityID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::GetEntityID(nsACString& entityID)
+{
+ entityID = mEntityID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::GetProxyInfo(nsIProxyInfo** aProxyInfo)
+{
+ DROP_DEAD();
+}
+
+NS_IMETHODIMP
+FTPChannelChild::SetUploadStream(nsIInputStream* stream,
+ const nsACString& contentType,
+ int64_t contentLength)
+{
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ mUploadStream = stream;
+ // NOTE: contentLength is intentionally ignored here.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::GetUploadStream(nsIInputStream** stream)
+{
+ NS_ENSURE_ARG_POINTER(stream);
+ *stream = mUploadStream;
+ NS_IF_ADDREF(*stream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::AsyncOpen(::nsIStreamListener* listener, nsISupports* aContext)
+{
+ LOG(("FTPChannelChild::AsyncOpen [this=%p]\n", this));
+
+ NS_ENSURE_TRUE((gNeckoChild), NS_ERROR_FAILURE);
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ // Port checked in parent, but duplicate here so we can return with error
+ // immediately, as we've done since before e10s.
+ nsresult rv;
+ rv = NS_CheckPortSafety(nsBaseChannel::URI()); // Need to disambiguate,
+ // because in the child ipdl,
+ // a typedef URI is defined...
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozilla::dom::TabChild* tabChild = nullptr;
+ nsCOMPtr<nsITabChild> iTabChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsITabChild),
+ getter_AddRefs(iTabChild));
+ GetCallback(iTabChild);
+ if (iTabChild) {
+ tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
+ }
+ if (MissingRequiredTabChild(tabChild, "ftp")) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mListener = listener;
+ mListenerContext = aContext;
+
+ // add ourselves to the load group.
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ OptionalInputStreamParams uploadStream;
+ nsTArray<mozilla::ipc::FileDescriptor> fds;
+ SerializeInputStream(mUploadStream, uploadStream, fds);
+
+ MOZ_ASSERT(fds.IsEmpty());
+
+ FTPChannelOpenArgs openArgs;
+ SerializeURI(nsBaseChannel::URI(), openArgs.uri());
+ openArgs.startPos() = mStartPos;
+ openArgs.entityID() = mEntityID;
+ openArgs.uploadStream() = uploadStream;
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ GetLoadInfo(getter_AddRefs(loadInfo));
+ rv = mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &openArgs.loadInfo());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ gNeckoChild->
+ SendPFTPChannelConstructor(this, tabChild, IPC::SerializedLoadContext(this),
+ openArgs);
+
+ // The socket transport layer in the chrome process now has a logical ref to
+ // us until OnStopRequest is called.
+ AddIPDLReference();
+
+ mIsPending = true;
+ mWasOpened = true;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::IsPending(bool* result)
+{
+ *result = mIsPending;
+ return NS_OK;
+}
+
+nsresult
+FTPChannelChild::OpenContentStream(bool async,
+ nsIInputStream** stream,
+ nsIChannel** channel)
+{
+ NS_RUNTIMEABORT("FTPChannel*Child* should never have OpenContentStream called!");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelChild::PFTPChannelChild
+//-----------------------------------------------------------------------------
+
+class FTPStartRequestEvent : public ChannelEvent
+{
+public:
+ FTPStartRequestEvent(FTPChannelChild* aChild,
+ const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsCString& aContentType,
+ const PRTime& aLastModified,
+ const nsCString& aEntityID,
+ const URIParams& aURI)
+ : mChild(aChild)
+ , mChannelStatus(aChannelStatus)
+ , mContentLength(aContentLength)
+ , mContentType(aContentType)
+ , mLastModified(aLastModified)
+ , mEntityID(aEntityID)
+ , mURI(aURI)
+ {
+ }
+ void Run()
+ {
+ mChild->DoOnStartRequest(mChannelStatus, mContentLength, mContentType,
+ mLastModified, mEntityID, mURI);
+ }
+
+private:
+ FTPChannelChild* mChild;
+ nsresult mChannelStatus;
+ int64_t mContentLength;
+ nsCString mContentType;
+ PRTime mLastModified;
+ nsCString mEntityID;
+ URIParams mURI;
+};
+
+bool
+FTPChannelChild::RecvOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsCString& aContentType,
+ const PRTime& aLastModified,
+ const nsCString& aEntityID,
+ const URIParams& aURI)
+{
+ // mFlushedForDiversion and mDivertingToParent should NEVER be set at this
+ // stage, as they are set in the listener's OnStartRequest.
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "mFlushedForDiversion should be unset before OnStartRequest!");
+ MOZ_RELEASE_ASSERT(!mDivertingToParent,
+ "mDivertingToParent should be unset before OnStartRequest!");
+
+ LOG(("FTPChannelChild::RecvOnStartRequest [this=%p]\n", this));
+
+ mEventQ->RunOrEnqueue(new FTPStartRequestEvent(this, aChannelStatus,
+ aContentLength, aContentType,
+ aLastModified, aEntityID,
+ aURI));
+ return true;
+}
+
+void
+FTPChannelChild::DoOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsCString& aContentType,
+ const PRTime& aLastModified,
+ const nsCString& aEntityID,
+ const URIParams& aURI)
+{
+ LOG(("FTPChannelChild::DoOnStartRequest [this=%p]\n", this));
+
+ // mFlushedForDiversion and mDivertingToParent should NEVER be set at this
+ // stage, as they are set in the listener's OnStartRequest.
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "mFlushedForDiversion should be unset before OnStartRequest!");
+ MOZ_RELEASE_ASSERT(!mDivertingToParent,
+ "mDivertingToParent should be unset before OnStartRequest!");
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aChannelStatus;
+ }
+
+ mContentLength = aContentLength;
+ SetContentType(aContentType);
+ mLastModifiedTime = aLastModified;
+ mEntityID = aEntityID;
+
+ nsCString spec;
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+ nsresult rv = uri->GetSpec(spec);
+ if (NS_SUCCEEDED(rv)) {
+ rv = nsBaseChannel::URI()->SetSpec(spec);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ } else {
+ Cancel(rv);
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ rv = mListener->OnStartRequest(this, mListenerContext);
+ if (NS_FAILED(rv))
+ Cancel(rv);
+
+ if (mDivertingToParent) {
+ mListener = nullptr;
+ mListenerContext = nullptr;
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ }
+ }
+}
+
+class FTPDataAvailableEvent : public ChannelEvent
+{
+public:
+ FTPDataAvailableEvent(FTPChannelChild* aChild,
+ const nsresult& aChannelStatus,
+ const nsCString& aData,
+ const uint64_t& aOffset,
+ const uint32_t& aCount)
+ : mChild(aChild)
+ , mChannelStatus(aChannelStatus)
+ , mData(aData)
+ , mOffset(aOffset)
+ , mCount(aCount)
+ {
+ }
+ void Run()
+ {
+ mChild->DoOnDataAvailable(mChannelStatus, mData, mOffset, mCount);
+ }
+
+private:
+ FTPChannelChild* mChild;
+ nsresult mChannelStatus;
+ nsCString mData;
+ uint64_t mOffset;
+ uint32_t mCount;
+};
+
+bool
+FTPChannelChild::RecvOnDataAvailable(const nsresult& channelStatus,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be receiving any more callbacks from parent!");
+
+ LOG(("FTPChannelChild::RecvOnDataAvailable [this=%p]\n", this));
+
+ mEventQ->RunOrEnqueue(new FTPDataAvailableEvent(this, channelStatus, data,
+ offset, count),
+ mDivertingToParent);
+
+ return true;
+}
+
+class MaybeDivertOnDataFTPEvent : public ChannelEvent
+{
+ public:
+ MaybeDivertOnDataFTPEvent(FTPChannelChild* child,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+ : mChild(child)
+ , mData(data)
+ , mOffset(offset)
+ , mCount(count) {}
+
+ void Run()
+ {
+ mChild->MaybeDivertOnData(mData, mOffset, mCount);
+ }
+
+ private:
+ FTPChannelChild* mChild;
+ nsCString mData;
+ uint64_t mOffset;
+ uint32_t mCount;
+};
+
+void
+FTPChannelChild::MaybeDivertOnData(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ if (mDivertingToParent) {
+ SendDivertOnDataAvailable(data, offset, count);
+ }
+}
+
+void
+FTPChannelChild::DoOnDataAvailable(const nsresult& channelStatus,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ LOG(("FTPChannelChild::DoOnDataAvailable [this=%p]\n", this));
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = channelStatus;
+ }
+
+ if (mDivertingToParent) {
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be processing any more callbacks from parent!");
+
+ SendDivertOnDataAvailable(data, offset, count);
+ return;
+ }
+
+ if (mCanceled)
+ return;
+
+ if (mUnknownDecoderInvolved) {
+ mUnknownDecoderEventQ.AppendElement(
+ MakeUnique<MaybeDivertOnDataFTPEvent>(this, data, offset, count));
+ }
+
+ // NOTE: the OnDataAvailable contract requires the client to read all the data
+ // in the inputstream. This code relies on that ('data' will go away after
+ // this function). Apparently the previous, non-e10s behavior was to actually
+ // support only reading part of the data, allowing later calls to read the
+ // rest.
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream),
+ data.get(),
+ count,
+ NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ rv = mListener->OnDataAvailable(this, mListenerContext,
+ stringStream, offset, count);
+ if (NS_FAILED(rv))
+ Cancel(rv);
+ stringStream->Close();
+}
+
+class FTPStopRequestEvent : public ChannelEvent
+{
+public:
+ FTPStopRequestEvent(FTPChannelChild* aChild,
+ const nsresult& aChannelStatus,
+ const nsCString &aErrorMsg,
+ bool aUseUTF8)
+ : mChild(aChild)
+ , mChannelStatus(aChannelStatus)
+ , mErrorMsg(aErrorMsg)
+ , mUseUTF8(aUseUTF8)
+ {
+ }
+ void Run()
+ {
+ mChild->DoOnStopRequest(mChannelStatus, mErrorMsg, mUseUTF8);
+ }
+
+private:
+ FTPChannelChild* mChild;
+ nsresult mChannelStatus;
+ nsCString mErrorMsg;
+ bool mUseUTF8;
+};
+
+bool
+FTPChannelChild::RecvOnStopRequest(const nsresult& aChannelStatus,
+ const nsCString &aErrorMsg,
+ const bool &aUseUTF8)
+{
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be receiving any more callbacks from parent!");
+
+ LOG(("FTPChannelChild::RecvOnStopRequest [this=%p status=%x]\n",
+ this, aChannelStatus));
+
+ mEventQ->RunOrEnqueue(new FTPStopRequestEvent(this, aChannelStatus, aErrorMsg,
+ aUseUTF8));
+ return true;
+}
+
+class nsFtpChildAsyncAlert : public Runnable
+{
+public:
+ nsFtpChildAsyncAlert(nsIPrompt *aPrompter, nsString aResponseMsg)
+ : mPrompter(aPrompter)
+ , mResponseMsg(aResponseMsg)
+ {
+ MOZ_COUNT_CTOR(nsFtpChildAsyncAlert);
+ }
+protected:
+ virtual ~nsFtpChildAsyncAlert()
+ {
+ MOZ_COUNT_DTOR(nsFtpChildAsyncAlert);
+ }
+public:
+ NS_IMETHOD Run() override
+ {
+ if (mPrompter) {
+ mPrompter->Alert(nullptr, mResponseMsg.get());
+ }
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIPrompt> mPrompter;
+ nsString mResponseMsg;
+};
+
+class MaybeDivertOnStopFTPEvent : public ChannelEvent
+{
+ public:
+ MaybeDivertOnStopFTPEvent(FTPChannelChild* child,
+ const nsresult& aChannelStatus)
+ : mChild(child)
+ , mChannelStatus(aChannelStatus) {}
+
+ void Run()
+ {
+ mChild->MaybeDivertOnStop(mChannelStatus);
+ }
+
+ private:
+ FTPChannelChild* mChild;
+ nsresult mChannelStatus;
+};
+
+void
+FTPChannelChild::MaybeDivertOnStop(const nsresult& aChannelStatus)
+{
+ if (mDivertingToParent) {
+ SendDivertOnStopRequest(aChannelStatus);
+ }
+}
+
+void
+FTPChannelChild::DoOnStopRequest(const nsresult& aChannelStatus,
+ const nsCString &aErrorMsg,
+ bool aUseUTF8)
+{
+ LOG(("FTPChannelChild::DoOnStopRequest [this=%p status=%x]\n",
+ this, aChannelStatus));
+
+ if (mDivertingToParent) {
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be processing any more callbacks from parent!");
+
+ SendDivertOnStopRequest(aChannelStatus);
+ return;
+ }
+
+ if (!mCanceled)
+ mStatus = aChannelStatus;
+
+ if (mUnknownDecoderInvolved) {
+ mUnknownDecoderEventQ.AppendElement(
+ MakeUnique<MaybeDivertOnStopFTPEvent>(this, aChannelStatus));
+ }
+
+ { // Ensure that all queued ipdl events are dispatched before
+ // we initiate protocol deletion below.
+ mIsPending = false;
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ (void)mListener->OnStopRequest(this, mListenerContext, aChannelStatus);
+
+ if (NS_FAILED(aChannelStatus) && !aErrorMsg.IsEmpty()) {
+ nsCOMPtr<nsIPrompt> prompter;
+ GetCallback(prompter);
+ if (prompter) {
+ nsCOMPtr<nsIRunnable> alertEvent;
+ if (aUseUTF8) {
+ alertEvent = new nsFtpChildAsyncAlert(prompter,
+ NS_ConvertUTF8toUTF16(aErrorMsg));
+ } else {
+ alertEvent = new nsFtpChildAsyncAlert(prompter,
+ NS_ConvertASCIItoUTF16(aErrorMsg));
+ }
+ NS_DispatchToMainThread(alertEvent);
+ }
+ }
+
+ mListener = nullptr;
+ mListenerContext = nullptr;
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, aChannelStatus);
+ }
+
+ // This calls NeckoChild::DeallocPFTPChannelChild(), which deletes |this| if IPDL
+ // holds the last reference. Don't rely on |this| existing after here!
+ Send__delete__(this);
+}
+
+class FTPFailedAsyncOpenEvent : public ChannelEvent
+{
+ public:
+ FTPFailedAsyncOpenEvent(FTPChannelChild* aChild, nsresult aStatus)
+ : mChild(aChild), mStatus(aStatus) {}
+ void Run() { mChild->DoFailedAsyncOpen(mStatus); }
+ private:
+ FTPChannelChild* mChild;
+ nsresult mStatus;
+};
+
+bool
+FTPChannelChild::RecvFailedAsyncOpen(const nsresult& statusCode)
+{
+ LOG(("FTPChannelChild::RecvFailedAsyncOpen [this=%p status=%x]\n",
+ this, statusCode));
+ mEventQ->RunOrEnqueue(new FTPFailedAsyncOpenEvent(this, statusCode));
+ return true;
+}
+
+void
+FTPChannelChild::DoFailedAsyncOpen(const nsresult& statusCode)
+{
+ LOG(("FTPChannelChild::DoFailedAsyncOpen [this=%p status=%x]\n",
+ this, statusCode));
+ mStatus = statusCode;
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, statusCode);
+
+ if (mListener) {
+ mListener->OnStartRequest(this, mListenerContext);
+ mIsPending = false;
+ mListener->OnStopRequest(this, mListenerContext, statusCode);
+ } else {
+ mIsPending = false;
+ }
+
+ mListener = nullptr;
+ mListenerContext = nullptr;
+
+ if (mIPCOpen)
+ Send__delete__(this);
+}
+
+class FTPFlushedForDiversionEvent : public ChannelEvent
+{
+ public:
+ explicit FTPFlushedForDiversionEvent(FTPChannelChild* aChild)
+ : mChild(aChild)
+ {
+ MOZ_RELEASE_ASSERT(aChild);
+ }
+
+ void Run()
+ {
+ mChild->FlushedForDiversion();
+ }
+ private:
+ FTPChannelChild* mChild;
+};
+
+bool
+FTPChannelChild::RecvFlushedForDiversion()
+{
+ LOG(("FTPChannelChild::RecvFlushedForDiversion [this=%p]\n", this));
+ MOZ_ASSERT(mDivertingToParent);
+
+ mEventQ->RunOrEnqueue(new FTPFlushedForDiversionEvent(this), true);
+ return true;
+}
+
+void
+FTPChannelChild::FlushedForDiversion()
+{
+ LOG(("FTPChannelChild::FlushedForDiversion [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(mDivertingToParent);
+
+ // Once this is set, it should not be unset before FTPChannelChild is taken
+ // down. After it is set, no OnStart/OnData/OnStop callbacks should be
+ // received from the parent channel, nor dequeued from the ChannelEventQueue.
+ mFlushedForDiversion = true;
+
+ SendDivertComplete();
+}
+
+bool
+FTPChannelChild::RecvDivertMessages()
+{
+ LOG(("FTPChannelChild::RecvDivertMessages [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(mDivertingToParent);
+ MOZ_RELEASE_ASSERT(mSuspendCount > 0);
+
+ // DivertTo() has been called on parent, so we can now start sending queued
+ // IPDL messages back to parent listener.
+ if (NS_WARN_IF(NS_FAILED(Resume()))) {
+ return false;
+ }
+ return true;
+}
+
+class FTPDeleteSelfEvent : public ChannelEvent
+{
+ public:
+ explicit FTPDeleteSelfEvent(FTPChannelChild* aChild)
+ : mChild(aChild) {}
+ void Run() { mChild->DoDeleteSelf(); }
+ private:
+ FTPChannelChild* mChild;
+};
+
+bool
+FTPChannelChild::RecvDeleteSelf()
+{
+ mEventQ->RunOrEnqueue(new FTPDeleteSelfEvent(this));
+ return true;
+}
+
+void
+FTPChannelChild::DoDeleteSelf()
+{
+ if (mIPCOpen)
+ Send__delete__(this);
+}
+
+NS_IMETHODIMP
+FTPChannelChild::Cancel(nsresult status)
+{
+ LOG(("FTPChannelChild::Cancel [this=%p]\n", this));
+ if (mCanceled)
+ return NS_OK;
+
+ mCanceled = true;
+ mStatus = status;
+ if (mIPCOpen)
+ SendCancel(status);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::Suspend()
+{
+ NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
+
+ LOG(("FTPChannelChild::Suspend [this=%p]\n", this));
+
+ // SendSuspend only once, when suspend goes from 0 to 1.
+ // Don't SendSuspend at all if we're diverting callbacks to the parent;
+ // suspend will be called at the correct time in the parent itself.
+ if (!mSuspendCount++ && !mDivertingToParent) {
+ SendSuspend();
+ mSuspendSent = true;
+ }
+ mEventQ->Suspend();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::Resume()
+{
+ NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
+
+ LOG(("FTPChannelChild::Resume [this=%p]\n", this));
+
+ // SendResume only once, when suspend count drops to 0.
+ // Don't SendResume at all if we're diverting callbacks to the parent (unless
+ // suspend was sent earlier); otherwise, resume will be called at the correct
+ // time in the parent itself.
+ if (!--mSuspendCount && (!mDivertingToParent || mSuspendSent)) {
+ SendResume();
+ }
+ mEventQ->Resume();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelChild::nsIChildChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelChild::ConnectParent(uint32_t id)
+{
+ LOG(("FTPChannelChild::ConnectParent [this=%p]\n", this));
+
+ mozilla::dom::TabChild* tabChild = nullptr;
+ nsCOMPtr<nsITabChild> iTabChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsITabChild),
+ getter_AddRefs(iTabChild));
+ GetCallback(iTabChild);
+ if (iTabChild) {
+ tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
+ }
+
+ // The socket transport in the chrome process now holds a logical ref to us
+ // until OnStopRequest, or we do a redirect, or we hit an IPDL error.
+ AddIPDLReference();
+
+ FTPChannelConnectArgs connectArgs(id);
+
+ if (!gNeckoChild->SendPFTPChannelConstructor(this, tabChild,
+ IPC::SerializedLoadContext(this),
+ connectArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::CompleteRedirectSetup(nsIStreamListener *listener,
+ nsISupports *aContext)
+{
+ LOG(("FTPChannelChild::CompleteRedirectSetup [this=%p]\n", this));
+
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ mIsPending = true;
+ mWasOpened = true;
+ mListener = listener;
+ mListenerContext = aContext;
+
+ // add ourselves to the load group.
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ // We already have an open IPDL connection to the parent. If on-modify-request
+ // listeners or load group observers canceled us, let the parent handle it
+ // and send it back to us naturally.
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelChild::nsIDivertableChannel
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+FTPChannelChild::DivertToParent(ChannelDiverterChild **aChild)
+{
+ MOZ_RELEASE_ASSERT(aChild);
+ MOZ_RELEASE_ASSERT(gNeckoChild);
+ MOZ_RELEASE_ASSERT(!mDivertingToParent);
+
+ LOG(("FTPChannelChild::DivertToParent [this=%p]\n", this));
+
+ // We must fail DivertToParent() if there's no parent end of the channel (and
+ // won't be!) due to early failure.
+ if (NS_FAILED(mStatus) && !mIPCOpen) {
+ return mStatus;
+ }
+
+ nsresult rv = Suspend();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Once this is set, it should not be unset before the child is taken down.
+ mDivertingToParent = true;
+
+ PChannelDiverterChild* diverter =
+ gNeckoChild->SendPChannelDiverterConstructor(this);
+ MOZ_RELEASE_ASSERT(diverter);
+
+ *aChild = static_cast<ChannelDiverterChild*>(diverter);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::UnknownDecoderInvolvedKeepData()
+{
+ mUnknownDecoderInvolved = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::UnknownDecoderInvolvedOnStartRequestCalled()
+{
+ mUnknownDecoderInvolved = false;
+
+ nsresult rv = NS_OK;
+
+ if (mDivertingToParent) {
+ rv = mEventQ->PrependEvents(mUnknownDecoderEventQ);
+ }
+ mUnknownDecoderEventQ.Clear();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::GetDivertingToParent(bool* aDiverting)
+{
+ NS_ENSURE_ARG_POINTER(aDiverting);
+ *aDiverting = mDivertingToParent;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/protocol/ftp/FTPChannelChild.h b/netwerk/protocol/ftp/FTPChannelChild.h
new file mode 100644
index 000000000..f68b85a72
--- /dev/null
+++ b/netwerk/protocol/ftp/FTPChannelChild.h
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_FTPChannelChild_h
+#define mozilla_net_FTPChannelChild_h
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/net/PFTPChannelChild.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "nsBaseChannel.h"
+#include "nsIFTPChannel.h"
+#include "nsIUploadChannel.h"
+#include "nsIProxiedChannel.h"
+#include "nsIResumableChannel.h"
+#include "nsIChildChannel.h"
+#include "nsIDivertableChannel.h"
+
+#include "nsIStreamListener.h"
+#include "PrivateBrowsingChannel.h"
+
+namespace mozilla {
+namespace net {
+
+// This class inherits logic from nsBaseChannel that is not needed for an
+// e10s child channel, but it works. At some point we could slice up
+// nsBaseChannel and have a new class that has only the common logic for
+// nsFTPChannel/FTPChannelChild.
+
+class FTPChannelChild final : public PFTPChannelChild
+ , public nsBaseChannel
+ , public nsIFTPChannel
+ , public nsIUploadChannel
+ , public nsIResumableChannel
+ , public nsIProxiedChannel
+ , public nsIChildChannel
+ , public nsIDivertableChannel
+{
+public:
+ typedef ::nsIStreamListener nsIStreamListener;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFTPCHANNEL
+ NS_DECL_NSIUPLOADCHANNEL
+ NS_DECL_NSIRESUMABLECHANNEL
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSICHILDCHANNEL
+ NS_DECL_NSIDIVERTABLECHANNEL
+
+ NS_IMETHOD Cancel(nsresult status) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+
+ explicit FTPChannelChild(nsIURI* uri);
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ NS_IMETHOD AsyncOpen(nsIStreamListener* listener, nsISupports* aContext) override;
+
+ // Note that we handle this ourselves, overriding the nsBaseChannel
+ // default behavior, in order to be e10s-friendly.
+ NS_IMETHOD IsPending(bool* result) override;
+
+ nsresult OpenContentStream(bool async,
+ nsIInputStream** stream,
+ nsIChannel** channel) override;
+
+ bool IsSuspended();
+
+ void FlushedForDiversion();
+
+protected:
+ virtual ~FTPChannelChild();
+
+ bool RecvOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsCString& aContentType,
+ const PRTime& aLastModified,
+ const nsCString& aEntityID,
+ const URIParams& aURI) override;
+ bool RecvOnDataAvailable(const nsresult& channelStatus,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count) override;
+ bool RecvOnStopRequest(const nsresult& channelStatus,
+ const nsCString &aErrorMsg,
+ const bool &aUseUTF8) override;
+ bool RecvFailedAsyncOpen(const nsresult& statusCode) override;
+ bool RecvFlushedForDiversion() override;
+ bool RecvDivertMessages() override;
+ bool RecvDeleteSelf() override;
+
+ void DoOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsCString& aContentType,
+ const PRTime& aLastModified,
+ const nsCString& aEntityID,
+ const URIParams& aURI);
+ void DoOnDataAvailable(const nsresult& channelStatus,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count);
+ void MaybeDivertOnData(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count);
+ void MaybeDivertOnStop(const nsresult& statusCode);
+ void DoOnStopRequest(const nsresult& statusCode,
+ const nsCString &aErrorMsg,
+ bool aUseUTF8);
+ void DoFailedAsyncOpen(const nsresult& statusCode);
+ void DoDeleteSelf();
+
+ friend class FTPStartRequestEvent;
+ friend class FTPDataAvailableEvent;
+ friend class MaybeDivertOnDataFTPEvent;
+ friend class FTPStopRequestEvent;
+ friend class MaybeDivertOnStopFTPEvent;
+ friend class FTPFailedAsyncOpenEvent;
+ friend class FTPDeleteSelfEvent;
+
+private:
+ nsCOMPtr<nsIInputStream> mUploadStream;
+
+ bool mIPCOpen;
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ // If nsUnknownDecoder is involved we queue onDataAvailable (and possibly
+ // OnStopRequest) so that we can divert them if needed when the listener's
+ // OnStartRequest is finally called
+ nsTArray<UniquePtr<ChannelEvent>> mUnknownDecoderEventQ;
+ bool mUnknownDecoderInvolved;
+
+ bool mCanceled;
+ uint32_t mSuspendCount;
+ bool mIsPending;
+
+ PRTime mLastModifiedTime;
+ uint64_t mStartPos;
+ nsCString mEntityID;
+
+ // Once set, OnData and possibly OnStop will be diverted to the parent.
+ bool mDivertingToParent;
+ // Once set, no OnStart/OnData/OnStop callbacks should be received from the
+ // parent channel, nor dequeued from the ChannelEventQueue.
+ bool mFlushedForDiversion;
+ // Set if SendSuspend is called. Determines if SendResume is needed when
+ // diverting callbacks to parent.
+ bool mSuspendSent;
+};
+
+inline bool
+FTPChannelChild::IsSuspended()
+{
+ return mSuspendCount != 0;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_FTPChannelChild_h
diff --git a/netwerk/protocol/ftp/FTPChannelParent.cpp b/netwerk/protocol/ftp/FTPChannelParent.cpp
new file mode 100644
index 000000000..cdd334971
--- /dev/null
+++ b/netwerk/protocol/ftp/FTPChannelParent.cpp
@@ -0,0 +1,896 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/FTPChannelParent.h"
+#include "nsStringStream.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "mozilla/dom/TabParent.h"
+#include "nsFTPChannel.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsFtpProtocolHandler.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIEncodedChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIForcePendingChannel.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/Unused.h"
+#include "SerializedLoadContext.h"
+#include "nsIContentPolicy.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/LoadInfo.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
+
+namespace mozilla {
+namespace net {
+
+FTPChannelParent::FTPChannelParent(const PBrowserOrId& aIframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus)
+ : mIPCClosed(false)
+ , mLoadContext(aLoadContext)
+ , mPBOverride(aOverrideStatus)
+ , mStatus(NS_OK)
+ , mDivertingFromChild(false)
+ , mDivertedOnStartRequest(false)
+ , mSuspendedForDiversion(false)
+ , mUseUTF8(false)
+{
+ nsIProtocolHandler* handler;
+ CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &handler);
+ MOZ_ASSERT(handler, "no ftp handler");
+
+ if (aIframeEmbedding.type() == PBrowserOrId::TPBrowserParent) {
+ mTabParent = static_cast<dom::TabParent*>(aIframeEmbedding.get_PBrowserParent());
+ }
+
+ mEventQ = new ChannelEventQueue(static_cast<nsIParentChannel*>(this));
+}
+
+FTPChannelParent::~FTPChannelParent()
+{
+ gFtpHandler->Release();
+}
+
+void
+FTPChannelParent::ActorDestroy(ActorDestroyReason why)
+{
+ // We may still have refcount>0 if the channel hasn't called OnStopRequest
+ // yet, but we must not send any more msgs to child.
+ mIPCClosed = true;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(FTPChannelParent,
+ nsIStreamListener,
+ nsIParentChannel,
+ nsIInterfaceRequestor,
+ nsIRequestObserver,
+ nsIChannelEventSink,
+ nsIFTPChannelParentInternal)
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::PFTPChannelParent
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent methods
+//-----------------------------------------------------------------------------
+
+bool
+FTPChannelParent::Init(const FTPChannelCreationArgs& aArgs)
+{
+ switch (aArgs.type()) {
+ case FTPChannelCreationArgs::TFTPChannelOpenArgs:
+ {
+ const FTPChannelOpenArgs& a = aArgs.get_FTPChannelOpenArgs();
+ return DoAsyncOpen(a.uri(), a.startPos(), a.entityID(), a.uploadStream(),
+ a.loadInfo());
+ }
+ case FTPChannelCreationArgs::TFTPChannelConnectArgs:
+ {
+ const FTPChannelConnectArgs& cArgs = aArgs.get_FTPChannelConnectArgs();
+ return ConnectChannel(cArgs.channelId());
+ }
+ default:
+ NS_NOTREACHED("unknown open type");
+ return false;
+ }
+}
+
+bool
+FTPChannelParent::DoAsyncOpen(const URIParams& aURI,
+ const uint64_t& aStartPos,
+ const nsCString& aEntityID,
+ const OptionalInputStreamParams& aUploadStream,
+ const OptionalLoadInfoArgs& aLoadInfoArgs)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+ if (!uri)
+ return false;
+
+#ifdef DEBUG
+ LOG(("FTPChannelParent DoAsyncOpen [this=%p uri=%s]\n",
+ this, uri->GetSpecOrDefault().get()));
+#endif
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs,
+ getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ NeckoOriginAttributes attrs;
+ rv = loadInfo->GetOriginAttributes(&attrs);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIChannel> chan;
+ rv = NS_NewChannelInternal(getter_AddRefs(chan), uri, loadInfo,
+ nullptr, nullptr,
+ nsIRequest::LOAD_NORMAL, ios);
+
+ if (NS_FAILED(rv))
+ return SendFailedAsyncOpen(rv);
+
+ mChannel = chan;
+
+ // later on mChannel may become an HTTP channel (we'll be redirected to one
+ // if we're using a proxy), but for now this is safe
+ nsFtpChannel* ftpChan = static_cast<nsFtpChannel*>(mChannel.get());
+
+ if (mPBOverride != kPBOverride_Unset) {
+ ftpChan->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
+ }
+ rv = ftpChan->SetNotificationCallbacks(this);
+ if (NS_FAILED(rv))
+ return SendFailedAsyncOpen(rv);
+
+ nsTArray<mozilla::ipc::FileDescriptor> fds;
+ nsCOMPtr<nsIInputStream> upload = DeserializeInputStream(aUploadStream, fds);
+ if (upload) {
+ // contentType and contentLength are ignored
+ rv = ftpChan->SetUploadStream(upload, EmptyCString(), 0);
+ if (NS_FAILED(rv))
+ return SendFailedAsyncOpen(rv);
+ }
+
+ rv = ftpChan->ResumeAt(aStartPos, aEntityID);
+ if (NS_FAILED(rv))
+ return SendFailedAsyncOpen(rv);
+
+ if (loadInfo && loadInfo->GetEnforceSecurity()) {
+ rv = ftpChan->AsyncOpen2(this);
+ }
+ else {
+ rv = ftpChan->AsyncOpen(this, nullptr);
+ }
+
+ if (NS_FAILED(rv))
+ return SendFailedAsyncOpen(rv);
+
+ return true;
+}
+
+bool
+FTPChannelParent::ConnectChannel(const uint32_t& channelId)
+{
+ nsresult rv;
+
+ LOG(("Looking for a registered channel [this=%p, id=%d]", this, channelId));
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_LinkRedirectChannels(channelId, this, getter_AddRefs(channel));
+ if (NS_SUCCEEDED(rv))
+ mChannel = channel;
+
+ LOG((" found channel %p, rv=%08x", mChannel.get(), rv));
+
+ return true;
+}
+
+bool
+FTPChannelParent::RecvCancel(const nsresult& status)
+{
+ if (mChannel)
+ mChannel->Cancel(status);
+
+ return true;
+}
+
+bool
+FTPChannelParent::RecvSuspend()
+{
+ if (mChannel) {
+ SuspendChannel();
+ }
+ return true;
+}
+
+bool
+FTPChannelParent::RecvResume()
+{
+ if (mChannel) {
+ ResumeChannel();
+ }
+ return true;
+}
+
+class FTPDivertDataAvailableEvent : public ChannelEvent
+{
+public:
+ FTPDivertDataAvailableEvent(FTPChannelParent* aParent,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+ : mParent(aParent)
+ , mData(data)
+ , mOffset(offset)
+ , mCount(count)
+ {
+ }
+
+ void Run()
+ {
+ mParent->DivertOnDataAvailable(mData, mOffset, mCount);
+ }
+
+private:
+ FTPChannelParent* mParent;
+ nsCString mData;
+ uint64_t mOffset;
+ uint32_t mCount;
+};
+
+bool
+FTPChannelParent::RecvDivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot RecvDivertOnDataAvailable if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ // Drop OnDataAvailables if the parent was canceled already.
+ if (NS_FAILED(mStatus)) {
+ return true;
+ }
+
+ mEventQ->RunOrEnqueue(new FTPDivertDataAvailableEvent(this, data, offset,
+ count));
+ return true;
+}
+
+void
+FTPChannelParent::DivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ LOG(("FTPChannelParent::DivertOnDataAvailable [this=%p]\n", this));
+
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertOnDataAvailable if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Drop OnDataAvailables if the parent was canceled already.
+ if (NS_FAILED(mStatus)) {
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(),
+ count, NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ if (mChannel) {
+ mChannel->Cancel(rv);
+ }
+ mStatus = rv;
+ return;
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ rv = OnDataAvailable(mChannel, nullptr, stringStream, offset, count);
+
+ stringStream->Close();
+ if (NS_FAILED(rv)) {
+ if (mChannel) {
+ mChannel->Cancel(rv);
+ }
+ mStatus = rv;
+ }
+}
+
+class FTPDivertStopRequestEvent : public ChannelEvent
+{
+public:
+ FTPDivertStopRequestEvent(FTPChannelParent* aParent,
+ const nsresult& statusCode)
+ : mParent(aParent)
+ , mStatusCode(statusCode)
+ {
+ }
+
+ void Run() {
+ mParent->DivertOnStopRequest(mStatusCode);
+ }
+
+private:
+ FTPChannelParent* mParent;
+ nsresult mStatusCode;
+};
+
+bool
+FTPChannelParent::RecvDivertOnStopRequest(const nsresult& statusCode)
+{
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot RecvDivertOnStopRequest if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ mEventQ->RunOrEnqueue(new FTPDivertStopRequestEvent(this, statusCode));
+ return true;
+}
+
+void
+FTPChannelParent::DivertOnStopRequest(const nsresult& statusCode)
+{
+ LOG(("FTPChannelParent::DivertOnStopRequest [this=%p]\n", this));
+
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertOnStopRequest if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Honor the channel's status even if the underlying transaction completed.
+ nsresult status = NS_FAILED(mStatus) ? mStatus : statusCode;
+
+ // Reset fake pending status in case OnStopRequest has already been called.
+ if (mChannel) {
+ nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel);
+ if (forcePendingIChan) {
+ forcePendingIChan->ForcePending(false);
+ }
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ OnStopRequest(mChannel, nullptr, status);
+}
+
+class FTPDivertCompleteEvent : public ChannelEvent
+{
+public:
+ explicit FTPDivertCompleteEvent(FTPChannelParent* aParent)
+ : mParent(aParent)
+ {
+ }
+
+ void Run() {
+ mParent->DivertComplete();
+ }
+
+private:
+ FTPChannelParent* mParent;
+};
+
+bool
+FTPChannelParent::RecvDivertComplete()
+{
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot RecvDivertComplete if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ mEventQ->RunOrEnqueue(new FTPDivertCompleteEvent(this));
+ return true;
+}
+
+void
+FTPChannelParent::DivertComplete()
+{
+ LOG(("FTPChannelParent::DivertComplete [this=%p]\n", this));
+
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertComplete if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ nsresult rv = ResumeForDiversion();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelParent::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+ LOG(("FTPChannelParent::OnStartRequest [this=%p]\n", this));
+
+ if (mDivertingFromChild) {
+ MOZ_RELEASE_ASSERT(mDivertToListener,
+ "Cannot divert if listener is unset!");
+ return mDivertToListener->OnStartRequest(aRequest, aContext);
+ }
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(aRequest);
+ MOZ_ASSERT(chan);
+ NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
+
+ int64_t contentLength;
+ chan->GetContentLength(&contentLength);
+ nsCString contentType;
+ chan->GetContentType(contentType);
+
+ nsCString entityID;
+ nsCOMPtr<nsIResumableChannel> resChan = do_QueryInterface(aRequest);
+ MOZ_ASSERT(resChan); // both FTP and HTTP should implement nsIResumableChannel
+ if (resChan) {
+ resChan->GetEntityID(entityID);
+ }
+
+ PRTime lastModified = 0;
+ nsCOMPtr<nsIFTPChannel> ftpChan = do_QueryInterface(aRequest);
+ if (ftpChan) {
+ ftpChan->GetLastModifiedTime(&lastModified);
+ }
+ nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(aRequest);
+ if (httpChan) {
+ httpChan->GetLastModifiedTime(&lastModified);
+ }
+
+ URIParams uriparam;
+ nsCOMPtr<nsIURI> uri;
+ chan->GetURI(getter_AddRefs(uri));
+ SerializeURI(uri, uriparam);
+
+ if (mIPCClosed || !SendOnStartRequest(mStatus, contentLength, contentType,
+ lastModified, entityID, uriparam)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatusCode)
+{
+ LOG(("FTPChannelParent::OnStopRequest: [this=%p status=%ul]\n",
+ this, aStatusCode));
+
+ if (mDivertingFromChild) {
+ MOZ_RELEASE_ASSERT(mDivertToListener,
+ "Cannot divert if listener is unset!");
+ return mDivertToListener->OnStopRequest(aRequest, aContext, aStatusCode);
+ }
+
+ if (mIPCClosed || !SendOnStopRequest(aStatusCode, mErrorMsg, mUseUTF8)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ LOG(("FTPChannelParent::OnDataAvailable [this=%p]\n", this));
+
+ if (mDivertingFromChild) {
+ MOZ_RELEASE_ASSERT(mDivertToListener,
+ "Cannot divert if listener is unset!");
+ return mDivertToListener->OnDataAvailable(aRequest, aContext, aInputStream,
+ aOffset, aCount);
+ }
+
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mIPCClosed || !SendOnDataAvailable(mStatus, data, aOffset, aCount))
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsIParentChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelParent::SetParentListener(HttpChannelParentListener* aListener)
+{
+ // Do not need ptr to HttpChannelParentListener.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::NotifyTrackingProtectionDisabled()
+{
+ // One day, this should probably be filled in.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::Delete()
+{
+ if (mIPCClosed || !SendDeleteSelf())
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelParent::GetInterface(const nsIID& uuid, void** result)
+{
+ if (uuid.Equals(NS_GET_IID(nsIAuthPromptProvider)) ||
+ uuid.Equals(NS_GET_IID(nsISecureBrowserUI))) {
+ if (mTabParent) {
+ return mTabParent->QueryInterface(uuid, result);
+ }
+ } else if (uuid.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ uuid.Equals(NS_GET_IID(nsIAuthPrompt2))) {
+ nsCOMPtr<nsIAuthPromptProvider> provider(do_QueryObject(mTabParent));
+ if (provider) {
+ return provider->GetAuthPrompt(nsIAuthPromptProvider::PROMPT_NORMAL,
+ uuid,
+ result);
+ }
+ }
+
+ // Only support nsILoadContext if child channel's callbacks did too
+ if (uuid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(result);
+ return NS_OK;
+ }
+
+ return QueryInterface(uuid, result);
+}
+
+nsresult
+FTPChannelParent::SuspendChannel()
+{
+ nsCOMPtr<nsIChannelWithDivertableParentListener> chan =
+ do_QueryInterface(mChannel);
+ if (chan) {
+ return chan->SuspendInternal();
+ } else {
+ return mChannel->Suspend();
+ }
+}
+
+nsresult
+FTPChannelParent::ResumeChannel()
+{
+ nsCOMPtr<nsIChannelWithDivertableParentListener> chan =
+ do_QueryInterface(mChannel);
+ if (chan) {
+ return chan->ResumeInternal();
+ } else {
+ return mChannel->Resume();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::ADivertableParentChannel
+//-----------------------------------------------------------------------------
+nsresult
+FTPChannelParent::SuspendForDiversion()
+{
+ MOZ_ASSERT(mChannel);
+ if (NS_WARN_IF(mDivertingFromChild)) {
+ MOZ_ASSERT(!mDivertingFromChild, "Already suspended for diversion!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Try suspending the channel. Allow it to fail, since OnStopRequest may have
+ // been called and thus the channel may not be pending.
+ nsresult rv = SuspendChannel();
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE);
+ mSuspendedForDiversion = NS_SUCCEEDED(rv);
+
+ // Once this is set, no more OnStart/OnData/OnStop callbacks should be sent
+ // to the child.
+ mDivertingFromChild = true;
+
+ nsCOMPtr<nsIChannelWithDivertableParentListener> chan =
+ do_QueryInterface(mChannel);
+ if (chan) {
+ chan->MessageDiversionStarted(this);
+ }
+
+ return NS_OK;
+}
+
+/* private, supporting function for ADivertableParentChannel */
+nsresult
+FTPChannelParent::ResumeForDiversion()
+{
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mDivertToListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot ResumeForDiversion if not diverting!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIChannelWithDivertableParentListener> chan =
+ do_QueryInterface(mChannel);
+ if (chan) {
+ chan->MessageDiversionStop();
+ }
+
+ if (mSuspendedForDiversion) {
+ nsresult rv = ResumeChannel();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailDiversion(NS_ERROR_UNEXPECTED, true);
+ return rv;
+ }
+ mSuspendedForDiversion = false;
+ }
+
+ // Delete() will tear down IPDL, but ref from underlying nsFTPChannel will
+ // keep us alive if there's more data to be delivered to listener.
+ if (NS_WARN_IF(NS_FAILED(Delete()))) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult
+FTPChannelParent::SuspendMessageDiversion()
+{
+ // This only need to suspend message queue.
+ mEventQ->Suspend();
+ return NS_OK;
+}
+
+nsresult
+FTPChannelParent::ResumeMessageDiversion()
+{
+ // This only need to resumes message queue.
+ mEventQ->Resume();
+ return NS_OK;
+}
+
+void
+FTPChannelParent::DivertTo(nsIStreamListener *aListener)
+{
+ MOZ_ASSERT(aListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertTo new listener if diverting is not set!");
+ return;
+ }
+
+ if (NS_WARN_IF(mIPCClosed || !SendFlushedForDiversion())) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ mDivertToListener = aListener;
+
+ // Call OnStartRequest and SendDivertMessages asynchronously to avoid
+ // reentering client context.
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod(this, &FTPChannelParent::StartDiversion));
+ return;
+}
+
+void
+FTPChannelParent::StartDiversion()
+{
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot StartDiversion if diverting is not set!");
+ return;
+ }
+
+ // Fake pending status in case OnStopRequest has already been called.
+ if (mChannel) {
+ nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel);
+ if (forcePendingIChan) {
+ forcePendingIChan->ForcePending(true);
+ }
+ }
+
+ {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ // Call OnStartRequest for the "DivertTo" listener.
+ nsresult rv = OnStartRequest(mChannel, nullptr);
+ if (NS_FAILED(rv)) {
+ if (mChannel) {
+ mChannel->Cancel(rv);
+ }
+ mStatus = rv;
+ return;
+ }
+ }
+
+ // After OnStartRequest has been called, tell FTPChannelChild to divert the
+ // OnDataAvailables and OnStopRequest to this FTPChannelParent.
+ if (NS_WARN_IF(mIPCClosed || !SendDivertMessages())) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+}
+
+class FTPFailDiversionEvent : public Runnable
+{
+public:
+ FTPFailDiversionEvent(FTPChannelParent *aChannelParent,
+ nsresult aErrorCode,
+ bool aSkipResume)
+ : mChannelParent(aChannelParent)
+ , mErrorCode(aErrorCode)
+ , mSkipResume(aSkipResume)
+ {
+ MOZ_RELEASE_ASSERT(aChannelParent);
+ MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+ }
+ NS_IMETHOD Run() override
+ {
+ mChannelParent->NotifyDiversionFailed(mErrorCode, mSkipResume);
+ return NS_OK;
+ }
+private:
+ RefPtr<FTPChannelParent> mChannelParent;
+ nsresult mErrorCode;
+ bool mSkipResume;
+};
+
+void
+FTPChannelParent::FailDiversion(nsresult aErrorCode,
+ bool aSkipResume)
+{
+ MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+ MOZ_RELEASE_ASSERT(mDivertingFromChild);
+ MOZ_RELEASE_ASSERT(mDivertToListener);
+ MOZ_RELEASE_ASSERT(mChannel);
+
+ NS_DispatchToCurrentThread(
+ new FTPFailDiversionEvent(this, aErrorCode, aSkipResume));
+}
+
+void
+FTPChannelParent::NotifyDiversionFailed(nsresult aErrorCode,
+ bool aSkipResume)
+{
+ MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+ MOZ_RELEASE_ASSERT(mDivertingFromChild);
+ MOZ_RELEASE_ASSERT(mDivertToListener);
+ MOZ_RELEASE_ASSERT(mChannel);
+
+ mChannel->Cancel(aErrorCode);
+ nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel);
+ if (forcePendingIChan) {
+ forcePendingIChan->ForcePending(false);
+ }
+
+ bool isPending = false;
+ nsresult rv = mChannel->IsPending(&isPending);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+
+ // Resume only we suspended earlier.
+ if (mSuspendedForDiversion) {
+ ResumeChannel();
+ }
+ // Channel has already sent OnStartRequest to the child, so ensure that we
+ // call it here if it hasn't already been called.
+ if (!mDivertedOnStartRequest) {
+ nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel);
+ if (forcePendingIChan) {
+ forcePendingIChan->ForcePending(true);
+ }
+ mDivertToListener->OnStartRequest(mChannel, nullptr);
+
+ if (forcePendingIChan) {
+ forcePendingIChan->ForcePending(false);
+ }
+ }
+ // If the channel is pending, it will call OnStopRequest itself; otherwise, do
+ // it here.
+ if (!isPending) {
+ mDivertToListener->OnStopRequest(mChannel, nullptr, aErrorCode);
+ }
+ mDivertToListener = nullptr;
+ mChannel = nullptr;
+
+ if (!mIPCClosed) {
+ Unused << SendDeleteSelf();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelParent::AsyncOnChannelRedirect(
+ nsIChannel *oldChannel,
+ nsIChannel *newChannel,
+ uint32_t redirectFlags,
+ nsIAsyncVerifyRedirectCallback* callback)
+{
+ nsCOMPtr<nsIFTPChannel> ftpChan = do_QueryInterface(newChannel);
+ if (!ftpChan) {
+ // when FTP is set to use HTTP proxying, we wind up getting redirected to an HTTP channel.
+ nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(newChannel);
+ if (!httpChan)
+ return NS_ERROR_UNEXPECTED;
+ }
+ mChannel = newChannel;
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::SetErrorMsg(const char *aMsg, bool aUseUTF8)
+{
+ mErrorMsg = aMsg;
+ mUseUTF8 = aUseUTF8;
+ return NS_OK;
+}
+
+//---------------------
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/protocol/ftp/FTPChannelParent.h b/netwerk/protocol/ftp/FTPChannelParent.h
new file mode 100644
index 000000000..210c99618
--- /dev/null
+++ b/netwerk/protocol/ftp/FTPChannelParent.h
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_FTPChannelParent_h
+#define mozilla_net_FTPChannelParent_h
+
+#include "ADivertableParentChannel.h"
+#include "mozilla/net/PFTPChannelParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsIParentChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIChannelEventSink.h"
+#include "nsIFTPChannelParentInternal.h"
+
+class nsILoadContext;
+
+namespace mozilla {
+
+namespace dom {
+class TabParent;
+class PBrowserOrId;
+} // namespace dom
+
+namespace net {
+class ChannelEventQueue;
+
+class FTPChannelParent final : public PFTPChannelParent
+ , public nsIParentChannel
+ , public nsIInterfaceRequestor
+ , public ADivertableParentChannel
+ , public nsIChannelEventSink
+ , public nsIFTPChannelParentInternal
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+
+ FTPChannelParent(const dom::PBrowserOrId& aIframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus);
+
+ bool Init(const FTPChannelCreationArgs& aOpenArgs);
+
+ // ADivertableParentChannel functions.
+ void DivertTo(nsIStreamListener *aListener) override;
+ nsresult SuspendForDiversion() override;
+ nsresult SuspendMessageDiversion() override;
+ nsresult ResumeMessageDiversion() override;
+
+ // Calls OnStartRequest for "DivertTo" listener, then notifies child channel
+ // that it should divert OnDataAvailable and OnStopRequest calls to this
+ // parent channel.
+ void StartDiversion();
+
+ // Handles calling OnStart/Stop if there are errors during diversion.
+ // Called asynchronously from FailDiversion.
+ void NotifyDiversionFailed(nsresult aErrorCode, bool aSkipResume = true);
+
+ NS_IMETHOD SetErrorMsg(const char *aMsg, bool aUseUTF8) override;
+
+protected:
+ virtual ~FTPChannelParent();
+
+ // private, supporting function for ADivertableParentChannel.
+ nsresult ResumeForDiversion();
+
+ // Asynchronously calls NotifyDiversionFailed.
+ void FailDiversion(nsresult aErrorCode, bool aSkipResume = true);
+
+ bool DoAsyncOpen(const URIParams& aURI, const uint64_t& aStartPos,
+ const nsCString& aEntityID,
+ const OptionalInputStreamParams& aUploadStream,
+ const OptionalLoadInfoArgs& aLoadInfoArgs);
+
+ // used to connect redirected-to channel in parent with just created
+ // ChildChannel. Used during HTTP->FTP redirects.
+ bool ConnectChannel(const uint32_t& channelId);
+
+ void DivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count);
+ void DivertOnStopRequest(const nsresult& statusCode);
+ void DivertComplete();
+
+ friend class FTPDivertDataAvailableEvent;
+ friend class FTPDivertStopRequestEvent;
+ friend class FTPDivertCompleteEvent;
+
+ virtual bool RecvCancel(const nsresult& status) override;
+ virtual bool RecvSuspend() override;
+ virtual bool RecvResume() override;
+ virtual bool RecvDivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count) override;
+ virtual bool RecvDivertOnStopRequest(const nsresult& statusCode) override;
+ virtual bool RecvDivertComplete() override;
+
+ nsresult SuspendChannel();
+ nsresult ResumeChannel();
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ // if configured to use HTTP proxy for FTP, this can an an HTTP channel.
+ nsCOMPtr<nsIChannel> mChannel;
+
+ bool mIPCClosed;
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+
+ PBOverrideStatus mPBOverride;
+
+ // If OnStart/OnData/OnStop have been diverted from the child, forward them to
+ // this listener.
+ nsCOMPtr<nsIStreamListener> mDivertToListener;
+ // Set to the canceled status value if the main channel was canceled.
+ nsresult mStatus;
+ // Once set, no OnStart/OnData/OnStop calls should be accepted; conversely, it
+ // must be set when RecvDivertOnData/~DivertOnStop/~DivertComplete are
+ // received from the child channel.
+ bool mDivertingFromChild;
+ // Set if OnStart|StopRequest was called during a diversion from the child.
+ bool mDivertedOnStartRequest;
+
+ // Set if we successfully suspended the nsHttpChannel for diversion. Unset
+ // when we call ResumeForDiversion.
+ bool mSuspendedForDiversion;
+ RefPtr<mozilla::dom::TabParent> mTabParent;
+
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ nsCString mErrorMsg;
+ bool mUseUTF8;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_FTPChannelParent_h
diff --git a/netwerk/protocol/ftp/PFTPChannel.ipdl b/netwerk/protocol/ftp/PFTPChannel.ipdl
new file mode 100644
index 000000000..55721bdbe
--- /dev/null
+++ b/netwerk/protocol/ftp/PFTPChannel.ipdl
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include InputStreamParams;
+include URIParams;
+
+//FIXME: bug #792908 (NeckoChannelParams already included by PNecko)
+include NeckoChannelParams;
+include protocol PBlob; //FIXME: bug #792908
+
+using PRTime from "prtime.h";
+
+namespace mozilla {
+namespace net {
+
+async protocol PFTPChannel
+{
+ manager PNecko;
+
+parent:
+ // Note: channels are opened during construction, so no open method here:
+ // see PNecko.ipdl
+
+ async __delete__();
+
+ async Cancel(nsresult status);
+ async Suspend();
+ async Resume();
+
+ // Divert OnDataAvailable to the parent.
+ async DivertOnDataAvailable(nsCString data,
+ uint64_t offset,
+ uint32_t count);
+
+ // Divert OnStopRequest to the parent.
+ async DivertOnStopRequest(nsresult statusCode);
+
+ // Child has no more events/messages to divert to the parent.
+ async DivertComplete();
+
+child:
+ async OnStartRequest(nsresult aChannelStatus,
+ int64_t aContentLength,
+ nsCString aContentType,
+ PRTime aLastModified,
+ nsCString aEntityID,
+ URIParams aURI);
+ async OnDataAvailable(nsresult channelStatus,
+ nsCString data,
+ uint64_t offset,
+ uint32_t count);
+ async OnStopRequest(nsresult channelStatus,
+ nsCString aErrorMsg,
+ bool aUseUTF8);
+ async FailedAsyncOpen(nsresult statusCode);
+
+ // Parent has been suspended for diversion; no more events to be enqueued.
+ async FlushedForDiversion();
+
+ // Child should resume processing the ChannelEventQueue, i.e. diverting any
+ // OnDataAvailable and OnStopRequest messages in the queue back to the parent.
+ async DivertMessages();
+
+ async DeleteSelf();
+};
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/protocol/ftp/doc/rfc959.txt b/netwerk/protocol/ftp/doc/rfc959.txt
new file mode 100644
index 000000000..5c9f11af5
--- /dev/null
+++ b/netwerk/protocol/ftp/doc/rfc959.txt
@@ -0,0 +1,3933 @@
+
+
+Network Working Group J. Postel
+Request for Comments: 959 J. Reynolds
+ ISI
+Obsoletes RFC: 765 (IEN 149) October 1985
+
+ FILE TRANSFER PROTOCOL (FTP)
+
+
+Status of this Memo
+
+ This memo is the official specification of the File Transfer
+ Protocol (FTP). Distribution of this memo is unlimited.
+
+ The following new optional commands are included in this edition of
+ the specification:
+
+ CDUP (Change to Parent Directory), SMNT (Structure Mount), STOU
+ (Store Unique), RMD (Remove Directory), MKD (Make Directory), PWD
+ (Print Directory), and SYST (System).
+
+ Note that this specification is compatible with the previous edition.
+
+1. INTRODUCTION
+
+ The objectives of FTP are 1) to promote sharing of files (computer
+ programs and/or data), 2) to encourage indirect or implicit (via
+ programs) use of remote computers, 3) to shield a user from
+ variations in file storage systems among hosts, and 4) to transfer
+ data reliably and efficiently. FTP, though usable directly by a user
+ at a terminal, is designed mainly for use by programs.
+
+ The attempt in this specification is to satisfy the diverse needs of
+ users of maxi-hosts, mini-hosts, personal workstations, and TACs,
+ with a simple, and easily implemented protocol design.
+
+ This paper assumes knowledge of the Transmission Control Protocol
+ (TCP) [2] and the Telnet Protocol [3]. These documents are contained
+ in the ARPA-Internet protocol handbook [1].
+
+2. OVERVIEW
+
+ In this section, the history, the terminology, and the FTP model are
+ discussed. The terms defined in this section are only those that
+ have special significance in FTP. Some of the terminology is very
+ specific to the FTP model; some readers may wish to turn to the
+ section on the FTP model while reviewing the terminology.
+
+
+
+
+
+
+
+Postel & Reynolds [Page 1]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 2.1. HISTORY
+
+ FTP has had a long evolution over the years. Appendix III is a
+ chronological compilation of Request for Comments documents
+ relating to FTP. These include the first proposed file transfer
+ mechanisms in 1971 that were developed for implementation on hosts
+ at M.I.T. (RFC 114), plus comments and discussion in RFC 141.
+
+ RFC 172 provided a user-level oriented protocol for file transfer
+ between host computers (including terminal IMPs). A revision of
+ this as RFC 265, restated FTP for additional review, while RFC 281
+ suggested further changes. The use of a "Set Data Type"
+ transaction was proposed in RFC 294 in January 1982.
+
+ RFC 354 obsoleted RFCs 264 and 265. The File Transfer Protocol
+ was now defined as a protocol for file transfer between HOSTs on
+ the ARPANET, with the primary function of FTP defined as
+ transfering files efficiently and reliably among hosts and
+ allowing the convenient use of remote file storage capabilities.
+ RFC 385 further commented on errors, emphasis points, and
+ additions to the protocol, while RFC 414 provided a status report
+ on the working server and user FTPs. RFC 430, issued in 1973,
+ (among other RFCs too numerous to mention) presented further
+ comments on FTP. Finally, an "official" FTP document was
+ published as RFC 454.
+
+ By July 1973, considerable changes from the last versions of FTP
+ were made, but the general structure remained the same. RFC 542
+ was published as a new "official" specification to reflect these
+ changes. However, many implementations based on the older
+ specification were not updated.
+
+ In 1974, RFCs 607 and 614 continued comments on FTP. RFC 624
+ proposed further design changes and minor modifications. In 1975,
+ RFC 686 entitled, "Leaving Well Enough Alone", discussed the
+ differences between all of the early and later versions of FTP.
+ RFC 691 presented a minor revision of RFC 686, regarding the
+ subject of print files.
+
+ Motivated by the transition from the NCP to the TCP as the
+ underlying protocol, a phoenix was born out of all of the above
+ efforts in RFC 765 as the specification of FTP for use on TCP.
+
+ This current edition of the FTP specification is intended to
+ correct some minor documentation errors, to improve the
+ explanation of some protocol features, and to add some new
+ optional commands.
+
+
+Postel & Reynolds [Page 2]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ In particular, the following new optional commands are included in
+ this edition of the specification:
+
+ CDUP - Change to Parent Directory
+
+ SMNT - Structure Mount
+
+ STOU - Store Unique
+
+ RMD - Remove Directory
+
+ MKD - Make Directory
+
+ PWD - Print Directory
+
+ SYST - System
+
+ This specification is compatible with the previous edition. A
+ program implemented in conformance to the previous specification
+ should automatically be in conformance to this specification.
+
+ 2.2. TERMINOLOGY
+
+ ASCII
+
+ The ASCII character set is as defined in the ARPA-Internet
+ Protocol Handbook. In FTP, ASCII characters are defined to be
+ the lower half of an eight-bit code set (i.e., the most
+ significant bit is zero).
+
+ access controls
+
+ Access controls define users' access privileges to the use of a
+ system, and to the files in that system. Access controls are
+ necessary to prevent unauthorized or accidental use of files.
+ It is the prerogative of a server-FTP process to invoke access
+ controls.
+
+ byte size
+
+ There are two byte sizes of interest in FTP: the logical byte
+ size of the file, and the transfer byte size used for the
+ transmission of the data. The transfer byte size is always 8
+ bits. The transfer byte size is not necessarily the byte size
+ in which data is to be stored in a system, nor the logical byte
+ size for interpretation of the structure of the data.
+
+
+
+Postel & Reynolds [Page 3]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ control connection
+
+ The communication path between the USER-PI and SERVER-PI for
+ the exchange of commands and replies. This connection follows
+ the Telnet Protocol.
+
+ data connection
+
+ A full duplex connection over which data is transferred, in a
+ specified mode and type. The data transferred may be a part of
+ a file, an entire file or a number of files. The path may be
+ between a server-DTP and a user-DTP, or between two
+ server-DTPs.
+
+ data port
+
+ The passive data transfer process "listens" on the data port
+ for a connection from the active transfer process in order to
+ open the data connection.
+
+ DTP
+
+ The data transfer process establishes and manages the data
+ connection. The DTP can be passive or active.
+
+ End-of-Line
+
+ The end-of-line sequence defines the separation of printing
+ lines. The sequence is Carriage Return, followed by Line Feed.
+
+ EOF
+
+ The end-of-file condition that defines the end of a file being
+ transferred.
+
+ EOR
+
+ The end-of-record condition that defines the end of a record
+ being transferred.
+
+ error recovery
+
+ A procedure that allows a user to recover from certain errors
+ such as failure of either host system or transfer process. In
+ FTP, error recovery may involve restarting a file transfer at a
+ given checkpoint.
+
+
+
+Postel & Reynolds [Page 4]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ FTP commands
+
+ A set of commands that comprise the control information flowing
+ from the user-FTP to the server-FTP process.
+
+ file
+
+ An ordered set of computer data (including programs), of
+ arbitrary length, uniquely identified by a pathname.
+
+ mode
+
+ The mode in which data is to be transferred via the data
+ connection. The mode defines the data format during transfer
+ including EOR and EOF. The transfer modes defined in FTP are
+ described in the Section on Transmission Modes.
+
+ NVT
+
+ The Network Virtual Terminal as defined in the Telnet Protocol.
+
+ NVFS
+
+ The Network Virtual File System. A concept which defines a
+ standard network file system with standard commands and
+ pathname conventions.
+
+ page
+
+ A file may be structured as a set of independent parts called
+ pages. FTP supports the transmission of discontinuous files as
+ independent indexed pages.
+
+ pathname
+
+ Pathname is defined to be the character string which must be
+ input to a file system by a user in order to identify a file.
+ Pathname normally contains device and/or directory names, and
+ file name specification. FTP does not yet specify a standard
+ pathname convention. Each user must follow the file naming
+ conventions of the file systems involved in the transfer.
+
+ PI
+
+ The protocol interpreter. The user and server sides of the
+ protocol have distinct roles implemented in a user-PI and a
+ server-PI.
+
+
+Postel & Reynolds [Page 5]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ record
+
+ A sequential file may be structured as a number of contiguous
+ parts called records. Record structures are supported by FTP
+ but a file need not have record structure.
+
+ reply
+
+ A reply is an acknowledgment (positive or negative) sent from
+ server to user via the control connection in response to FTP
+ commands. The general form of a reply is a completion code
+ (including error codes) followed by a text string. The codes
+ are for use by programs and the text is usually intended for
+ human users.
+
+ server-DTP
+
+ The data transfer process, in its normal "active" state,
+ establishes the data connection with the "listening" data port.
+ It sets up parameters for transfer and storage, and transfers
+ data on command from its PI. The DTP can be placed in a
+ "passive" state to listen for, rather than initiate a
+ connection on the data port.
+
+ server-FTP process
+
+ A process or set of processes which perform the function of
+ file transfer in cooperation with a user-FTP process and,
+ possibly, another server. The functions consist of a protocol
+ interpreter (PI) and a data transfer process (DTP).
+
+ server-PI
+
+ The server protocol interpreter "listens" on Port L for a
+ connection from a user-PI and establishes a control
+ communication connection. It receives standard FTP commands
+ from the user-PI, sends replies, and governs the server-DTP.
+
+ type
+
+ The data representation type used for data transfer and
+ storage. Type implies certain transformations between the time
+ of data storage and data transfer. The representation types
+ defined in FTP are described in the Section on Establishing
+ Data Connections.
+
+
+
+
+Postel & Reynolds [Page 6]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ user
+
+ A person or a process on behalf of a person wishing to obtain
+ file transfer service. The human user may interact directly
+ with a server-FTP process, but use of a user-FTP process is
+ preferred since the protocol design is weighted towards
+ automata.
+
+ user-DTP
+
+ The data transfer process "listens" on the data port for a
+ connection from a server-FTP process. If two servers are
+ transferring data between them, the user-DTP is inactive.
+
+ user-FTP process
+
+ A set of functions including a protocol interpreter, a data
+ transfer process and a user interface which together perform
+ the function of file transfer in cooperation with one or more
+ server-FTP processes. The user interface allows a local
+ language to be used in the command-reply dialogue with the
+ user.
+
+ user-PI
+
+ The user protocol interpreter initiates the control connection
+ from its port U to the server-FTP process, initiates FTP
+ commands, and governs the user-DTP if that process is part of
+ the file transfer.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 7]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 2.3. THE FTP MODEL
+
+ With the above definitions in mind, the following model (shown in
+ Figure 1) may be diagrammed for an FTP service.
+
+ -------------
+ |/---------\|
+ || User || --------
+ ||Interface|<--->| User |
+ |\----^----/| --------
+ ---------- | | |
+ |/------\| FTP Commands |/----V----\|
+ ||Server|<---------------->| User ||
+ || PI || FTP Replies || PI ||
+ |\--^---/| |\----^----/|
+ | | | | | |
+ -------- |/--V---\| Data |/----V----\| --------
+ | File |<--->|Server|<---------------->| User |<--->| File |
+ |System| || DTP || Connection || DTP || |System|
+ -------- |\------/| |\---------/| --------
+ ---------- -------------
+
+ Server-FTP USER-FTP
+
+ NOTES: 1. The data connection may be used in either direction.
+ 2. The data connection need not exist all of the time.
+
+ Figure 1 Model for FTP Use
+
+ In the model described in Figure 1, the user-protocol interpreter
+ initiates the control connection. The control connection follows
+ the Telnet protocol. At the initiation of the user, standard FTP
+ commands are generated by the user-PI and transmitted to the
+ server process via the control connection. (The user may
+ establish a direct control connection to the server-FTP, from a
+ TAC terminal for example, and generate standard FTP commands
+ independently, bypassing the user-FTP process.) Standard replies
+ are sent from the server-PI to the user-PI over the control
+ connection in response to the commands.
+
+ The FTP commands specify the parameters for the data connection
+ (data port, transfer mode, representation type, and structure) and
+ the nature of file system operation (store, retrieve, append,
+ delete, etc.). The user-DTP or its designate should "listen" on
+ the specified data port, and the server initiate the data
+ connection and data transfer in accordance with the specified
+ parameters. It should be noted that the data port need not be in
+
+
+Postel & Reynolds [Page 8]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ the same host that initiates the FTP commands via the control
+ connection, but the user or the user-FTP process must ensure a
+ "listen" on the specified data port. It ought to also be noted
+ that the data connection may be used for simultaneous sending and
+ receiving.
+
+ In another situation a user might wish to transfer files between
+ two hosts, neither of which is a local host. The user sets up
+ control connections to the two servers and then arranges for a
+ data connection between them. In this manner, control information
+ is passed to the user-PI but data is transferred between the
+ server data transfer processes. Following is a model of this
+ server-server interaction.
+
+
+ Control ------------ Control
+ ---------->| User-FTP |<-----------
+ | | User-PI | |
+ | | "C" | |
+ V ------------ V
+ -------------- --------------
+ | Server-FTP | Data Connection | Server-FTP |
+ | "A" |<---------------------->| "B" |
+ -------------- Port (A) Port (B) --------------
+
+
+ Figure 2
+
+ The protocol requires that the control connections be open while
+ data transfer is in progress. It is the responsibility of the
+ user to request the closing of the control connections when
+ finished using the FTP service, while it is the server who takes
+ the action. The server may abort data transfer if the control
+ connections are closed without command.
+
+ The Relationship between FTP and Telnet:
+
+ The FTP uses the Telnet protocol on the control connection.
+ This can be achieved in two ways: first, the user-PI or the
+ server-PI may implement the rules of the Telnet Protocol
+ directly in their own procedures; or, second, the user-PI or
+ the server-PI may make use of the existing Telnet module in the
+ system.
+
+ Ease of implementaion, sharing code, and modular programming
+ argue for the second approach. Efficiency and independence
+
+
+
+Postel & Reynolds [Page 9]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ argue for the first approach. In practice, FTP relies on very
+ little of the Telnet Protocol, so the first approach does not
+ necessarily involve a large amount of code.
+
+3. DATA TRANSFER FUNCTIONS
+
+ Files are transferred only via the data connection. The control
+ connection is used for the transfer of commands, which describe the
+ functions to be performed, and the replies to these commands (see the
+ Section on FTP Replies). Several commands are concerned with the
+ transfer of data between hosts. These data transfer commands include
+ the MODE command which specify how the bits of the data are to be
+ transmitted, and the STRUcture and TYPE commands, which are used to
+ define the way in which the data are to be represented. The
+ transmission and representation are basically independent but the
+ "Stream" transmission mode is dependent on the file structure
+ attribute and if "Compressed" transmission mode is used, the nature
+ of the filler byte depends on the representation type.
+
+ 3.1. DATA REPRESENTATION AND STORAGE
+
+ Data is transferred from a storage device in the sending host to a
+ storage device in the receiving host. Often it is necessary to
+ perform certain transformations on the data because data storage
+ representations in the two systems are different. For example,
+ NVT-ASCII has different data storage representations in different
+ systems. DEC TOPS-20s's generally store NVT-ASCII as five 7-bit
+ ASCII characters, left-justified in a 36-bit word. IBM Mainframe's
+ store NVT-ASCII as 8-bit EBCDIC codes. Multics stores NVT-ASCII
+ as four 9-bit characters in a 36-bit word. It is desirable to
+ convert characters into the standard NVT-ASCII representation when
+ transmitting text between dissimilar systems. The sending and
+ receiving sites would have to perform the necessary
+ transformations between the standard representation and their
+ internal representations.
+
+ A different problem in representation arises when transmitting
+ binary data (not character codes) between host systems with
+ different word lengths. It is not always clear how the sender
+ should send data, and the receiver store it. For example, when
+ transmitting 32-bit bytes from a 32-bit word-length system to a
+ 36-bit word-length system, it may be desirable (for reasons of
+ efficiency and usefulness) to store the 32-bit bytes
+ right-justified in a 36-bit word in the latter system. In any
+ case, the user should have the option of specifying data
+ representation and transformation functions. It should be noted
+
+
+
+Postel & Reynolds [Page 10]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ that FTP provides for very limited data type representations.
+ Transformations desired beyond this limited capability should be
+ performed by the user directly.
+
+ 3.1.1. DATA TYPES
+
+ Data representations are handled in FTP by a user specifying a
+ representation type. This type may implicitly (as in ASCII or
+ EBCDIC) or explicitly (as in Local byte) define a byte size for
+ interpretation which is referred to as the "logical byte size."
+ Note that this has nothing to do with the byte size used for
+ transmission over the data connection, called the "transfer
+ byte size", and the two should not be confused. For example,
+ NVT-ASCII has a logical byte size of 8 bits. If the type is
+ Local byte, then the TYPE command has an obligatory second
+ parameter specifying the logical byte size. The transfer byte
+ size is always 8 bits.
+
+ 3.1.1.1. ASCII TYPE
+
+ This is the default type and must be accepted by all FTP
+ implementations. It is intended primarily for the transfer
+ of text files, except when both hosts would find the EBCDIC
+ type more convenient.
+
+ The sender converts the data from an internal character
+ representation to the standard 8-bit NVT-ASCII
+ representation (see the Telnet specification). The receiver
+ will convert the data from the standard form to his own
+ internal form.
+
+ In accordance with the NVT standard, the <CRLF> sequence
+ should be used where necessary to denote the end of a line
+ of text. (See the discussion of file structure at the end
+ of the Section on Data Representation and Storage.)
+
+ Using the standard NVT-ASCII representation means that data
+ must be interpreted as 8-bit bytes.
+
+ The Format parameter for ASCII and EBCDIC types is discussed
+ below.
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 11]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 3.1.1.2. EBCDIC TYPE
+
+ This type is intended for efficient transfer between hosts
+ which use EBCDIC for their internal character
+ representation.
+
+ For transmission, the data are represented as 8-bit EBCDIC
+ characters. The character code is the only difference
+ between the functional specifications of EBCDIC and ASCII
+ types.
+
+ End-of-line (as opposed to end-of-record--see the discussion
+ of structure) will probably be rarely used with EBCDIC type
+ for purposes of denoting structure, but where it is
+ necessary the <NL> character should be used.
+
+ 3.1.1.3. IMAGE TYPE
+
+ The data are sent as contiguous bits which, for transfer,
+ are packed into the 8-bit transfer bytes. The receiving
+ site must store the data as contiguous bits. The structure
+ of the storage system might necessitate the padding of the
+ file (or of each record, for a record-structured file) to
+ some convenient boundary (byte, word or block). This
+ padding, which must be all zeros, may occur only at the end
+ of the file (or at the end of each record) and there must be
+ a way of identifying the padding bits so that they may be
+ stripped off if the file is retrieved. The padding
+ transformation should be well publicized to enable a user to
+ process a file at the storage site.
+
+ Image type is intended for the efficient storage and
+ retrieval of files and for the transfer of binary data. It
+ is recommended that this type be accepted by all FTP
+ implementations.
+
+ 3.1.1.4. LOCAL TYPE
+
+ The data is transferred in logical bytes of the size
+ specified by the obligatory second parameter, Byte size.
+ The value of Byte size must be a decimal integer; there is
+ no default value. The logical byte size is not necessarily
+ the same as the transfer byte size. If there is a
+ difference in byte sizes, then the logical bytes should be
+ packed contiguously, disregarding transfer byte boundaries
+ and with any necessary padding at the end.
+
+
+
+Postel & Reynolds [Page 12]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ When the data reaches the receiving host, it will be
+ transformed in a manner dependent on the logical byte size
+ and the particular host. This transformation must be
+ invertible (i.e., an identical file can be retrieved if the
+ same parameters are used) and should be well publicized by
+ the FTP implementors.
+
+ For example, a user sending 36-bit floating-point numbers to
+ a host with a 32-bit word could send that data as Local byte
+ with a logical byte size of 36. The receiving host would
+ then be expected to store the logical bytes so that they
+ could be easily manipulated; in this example putting the
+ 36-bit logical bytes into 64-bit double words should
+ suffice.
+
+ In another example, a pair of hosts with a 36-bit word size
+ may send data to one another in words by using TYPE L 36.
+ The data would be sent in the 8-bit transmission bytes
+ packed so that 9 transmission bytes carried two host words.
+
+ 3.1.1.5. FORMAT CONTROL
+
+ The types ASCII and EBCDIC also take a second (optional)
+ parameter; this is to indicate what kind of vertical format
+ control, if any, is associated with a file. The following
+ data representation types are defined in FTP:
+
+ A character file may be transferred to a host for one of
+ three purposes: for printing, for storage and later
+ retrieval, or for processing. If a file is sent for
+ printing, the receiving host must know how the vertical
+ format control is represented. In the second case, it must
+ be possible to store a file at a host and then retrieve it
+ later in exactly the same form. Finally, it should be
+ possible to move a file from one host to another and process
+ the file at the second host without undue trouble. A single
+ ASCII or EBCDIC format does not satisfy all these
+ conditions. Therefore, these types have a second parameter
+ specifying one of the following three formats:
+
+ 3.1.1.5.1. NON PRINT
+
+ This is the default format to be used if the second
+ (format) parameter is omitted. Non-print format must be
+ accepted by all FTP implementations.
+
+
+
+
+Postel & Reynolds [Page 13]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The file need contain no vertical format information. If
+ it is passed to a printer process, this process may
+ assume standard values for spacing and margins.
+
+ Normally, this format will be used with files destined
+ for processing or just storage.
+
+ 3.1.1.5.2. TELNET FORMAT CONTROLS
+
+ The file contains ASCII/EBCDIC vertical format controls
+ (i.e., <CR>, <LF>, <NL>, <VT>, <FF>) which the printer
+ process will interpret appropriately. <CRLF>, in exactly
+ this sequence, also denotes end-of-line.
+
+ 3.1.1.5.2. CARRIAGE CONTROL (ASA)
+
+ The file contains ASA (FORTRAN) vertical format control
+ characters. (See RFC 740 Appendix C; and Communications
+ of the ACM, Vol. 7, No. 10, p. 606, October 1964.) In a
+ line or a record formatted according to the ASA Standard,
+ the first character is not to be printed. Instead, it
+ should be used to determine the vertical movement of the
+ paper which should take place before the rest of the
+ record is printed.
+
+ The ASA Standard specifies the following control
+ characters:
+
+ Character Vertical Spacing
+
+ blank Move paper up one line
+ 0 Move paper up two lines
+ 1 Move paper to top of next page
+ + No movement, i.e., overprint
+
+ Clearly there must be some way for a printer process to
+ distinguish the end of the structural entity. If a file
+ has record structure (see below) this is no problem;
+ records will be explicitly marked during transfer and
+ storage. If the file has no record structure, the <CRLF>
+ end-of-line sequence is used to separate printing lines,
+ but these format effectors are overridden by the ASA
+ controls.
+
+
+
+
+
+
+Postel & Reynolds [Page 14]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 3.1.2. DATA STRUCTURES
+
+ In addition to different representation types, FTP allows the
+ structure of a file to be specified. Three file structures are
+ defined in FTP:
+
+ file-structure, where there is no internal structure and
+ the file is considered to be a
+ continuous sequence of data bytes,
+
+ record-structure, where the file is made up of sequential
+ records,
+
+ and page-structure, where the file is made up of independent
+ indexed pages.
+
+ File-structure is the default to be assumed if the STRUcture
+ command has not been used but both file and record structures
+ must be accepted for "text" files (i.e., files with TYPE ASCII
+ or EBCDIC) by all FTP implementations. The structure of a file
+ will affect both the transfer mode of a file (see the Section
+ on Transmission Modes) and the interpretation and storage of
+ the file.
+
+ The "natural" structure of a file will depend on which host
+ stores the file. A source-code file will usually be stored on
+ an IBM Mainframe in fixed length records but on a DEC TOPS-20
+ as a stream of characters partitioned into lines, for example
+ by <CRLF>. If the transfer of files between such disparate
+ sites is to be useful, there must be some way for one site to
+ recognize the other's assumptions about the file.
+
+ With some sites being naturally file-oriented and others
+ naturally record-oriented there may be problems if a file with
+ one structure is sent to a host oriented to the other. If a
+ text file is sent with record-structure to a host which is file
+ oriented, then that host should apply an internal
+ transformation to the file based on the record structure.
+ Obviously, this transformation should be useful, but it must
+ also be invertible so that an identical file may be retrieved
+ using record structure.
+
+ In the case of a file being sent with file-structure to a
+ record-oriented host, there exists the question of what
+ criteria the host should use to divide the file into records
+ which can be processed locally. If this division is necessary,
+ the FTP implementation should use the end-of-line sequence,
+
+
+Postel & Reynolds [Page 15]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ <CRLF> for ASCII, or <NL> for EBCDIC text files, as the
+ delimiter. If an FTP implementation adopts this technique, it
+ must be prepared to reverse the transformation if the file is
+ retrieved with file-structure.
+
+ 3.1.2.1. FILE STRUCTURE
+
+ File structure is the default to be assumed if the STRUcture
+ command has not been used.
+
+ In file-structure there is no internal structure and the
+ file is considered to be a continuous sequence of data
+ bytes.
+
+ 3.1.2.2. RECORD STRUCTURE
+
+ Record structures must be accepted for "text" files (i.e.,
+ files with TYPE ASCII or EBCDIC) by all FTP implementations.
+
+ In record-structure the file is made up of sequential
+ records.
+
+ 3.1.2.3. PAGE STRUCTURE
+
+ To transmit files that are discontinuous, FTP defines a page
+ structure. Files of this type are sometimes known as
+ "random access files" or even as "holey files". In these
+ files there is sometimes other information associated with
+ the file as a whole (e.g., a file descriptor), or with a
+ section of the file (e.g., page access controls), or both.
+ In FTP, the sections of the file are called pages.
+
+ To provide for various page sizes and associated
+ information, each page is sent with a page header. The page
+ header has the following defined fields:
+
+ Header Length
+
+ The number of logical bytes in the page header
+ including this byte. The minimum header length is 4.
+
+ Page Index
+
+ The logical page number of this section of the file.
+ This is not the transmission sequence number of this
+ page, but the index used to identify this page of the
+ file.
+
+
+Postel & Reynolds [Page 16]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ Data Length
+
+ The number of logical bytes in the page data. The
+ minimum data length is 0.
+
+ Page Type
+
+ The type of page this is. The following page types
+ are defined:
+
+ 0 = Last Page
+
+ This is used to indicate the end of a paged
+ structured transmission. The header length must
+ be 4, and the data length must be 0.
+
+ 1 = Simple Page
+
+ This is the normal type for simple paged files
+ with no page level associated control
+ information. The header length must be 4.
+
+ 2 = Descriptor Page
+
+ This type is used to transmit the descriptive
+ information for the file as a whole.
+
+ 3 = Access Controlled Page
+
+ This type includes an additional header field
+ for paged files with page level access control
+ information. The header length must be 5.
+
+ Optional Fields
+
+ Further header fields may be used to supply per page
+ control information, for example, per page access
+ control.
+
+ All fields are one logical byte in length. The logical byte
+ size is specified by the TYPE command. See Appendix I for
+ further details and a specific case at the page structure.
+
+ A note of caution about parameters: a file must be stored and
+ retrieved with the same parameters if the retrieved version is to
+
+
+
+
+Postel & Reynolds [Page 17]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ be identical to the version originally transmitted. Conversely,
+ FTP implementations must return a file identical to the original
+ if the parameters used to store and retrieve a file are the same.
+
+ 3.2. ESTABLISHING DATA CONNECTIONS
+
+ The mechanics of transferring data consists of setting up the data
+ connection to the appropriate ports and choosing the parameters
+ for transfer. Both the user and the server-DTPs have a default
+ data port. The user-process default data port is the same as the
+ control connection port (i.e., U). The server-process default
+ data port is the port adjacent to the control connection port
+ (i.e., L-1).
+
+ The transfer byte size is 8-bit bytes. This byte size is relevant
+ only for the actual transfer of the data; it has no bearing on
+ representation of the data within a host's file system.
+
+ The passive data transfer process (this may be a user-DTP or a
+ second server-DTP) shall "listen" on the data port prior to
+ sending a transfer request command. The FTP request command
+ determines the direction of the data transfer. The server, upon
+ receiving the transfer request, will initiate the data connection
+ to the port. When the connection is established, the data
+ transfer begins between DTP's, and the server-PI sends a
+ confirming reply to the user-PI.
+
+ Every FTP implementation must support the use of the default data
+ ports, and only the USER-PI can initiate a change to non-default
+ ports.
+
+ It is possible for the user to specify an alternate data port by
+ use of the PORT command. The user may want a file dumped on a TAC
+ line printer or retrieved from a third party host. In the latter
+ case, the user-PI sets up control connections with both
+ server-PI's. One server is then told (by an FTP command) to
+ "listen" for a connection which the other will initiate. The
+ user-PI sends one server-PI a PORT command indicating the data
+ port of the other. Finally, both are sent the appropriate
+ transfer commands. The exact sequence of commands and replies
+ sent between the user-controller and the servers is defined in the
+ Section on FTP Replies.
+
+ In general, it is the server's responsibility to maintain the data
+ connection--to initiate it and to close it. The exception to this
+
+
+
+
+Postel & Reynolds [Page 18]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ is when the user-DTP is sending the data in a transfer mode that
+ requires the connection to be closed to indicate EOF. The server
+ MUST close the data connection under the following conditions:
+
+ 1. The server has completed sending data in a transfer mode
+ that requires a close to indicate EOF.
+
+ 2. The server receives an ABORT command from the user.
+
+ 3. The port specification is changed by a command from the
+ user.
+
+ 4. The control connection is closed legally or otherwise.
+
+ 5. An irrecoverable error condition occurs.
+
+ Otherwise the close is a server option, the exercise of which the
+ server must indicate to the user-process by either a 250 or 226
+ reply only.
+
+ 3.3. DATA CONNECTION MANAGEMENT
+
+ Default Data Connection Ports: All FTP implementations must
+ support use of the default data connection ports, and only the
+ User-PI may initiate the use of non-default ports.
+
+ Negotiating Non-Default Data Ports: The User-PI may specify a
+ non-default user side data port with the PORT command. The
+ User-PI may request the server side to identify a non-default
+ server side data port with the PASV command. Since a connection
+ is defined by the pair of addresses, either of these actions is
+ enough to get a different data connection, still it is permitted
+ to do both commands to use new ports on both ends of the data
+ connection.
+
+ Reuse of the Data Connection: When using the stream mode of data
+ transfer the end of the file must be indicated by closing the
+ connection. This causes a problem if multiple files are to be
+ transfered in the session, due to need for TCP to hold the
+ connection record for a time out period to guarantee the reliable
+ communication. Thus the connection can not be reopened at once.
+
+ There are two solutions to this problem. The first is to
+ negotiate a non-default port. The second is to use another
+ transfer mode.
+
+ A comment on transfer modes. The stream transfer mode is
+
+
+Postel & Reynolds [Page 19]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ inherently unreliable, since one can not determine if the
+ connection closed prematurely or not. The other transfer modes
+ (Block, Compressed) do not close the connection to indicate the
+ end of file. They have enough FTP encoding that the data
+ connection can be parsed to determine the end of the file.
+ Thus using these modes one can leave the data connection open
+ for multiple file transfers.
+
+ 3.4. TRANSMISSION MODES
+
+ The next consideration in transferring data is choosing the
+ appropriate transmission mode. There are three modes: one which
+ formats the data and allows for restart procedures; one which also
+ compresses the data for efficient transfer; and one which passes
+ the data with little or no processing. In this last case the mode
+ interacts with the structure attribute to determine the type of
+ processing. In the compressed mode, the representation type
+ determines the filler byte.
+
+ All data transfers must be completed with an end-of-file (EOF)
+ which may be explicitly stated or implied by the closing of the
+ data connection. For files with record structure, all the
+ end-of-record markers (EOR) are explicit, including the final one.
+ For files transmitted in page structure a "last-page" page type is
+ used.
+
+ NOTE: In the rest of this section, byte means "transfer byte"
+ except where explicitly stated otherwise.
+
+ For the purpose of standardized transfer, the sending host will
+ translate its internal end of line or end of record denotation
+ into the representation prescribed by the transfer mode and file
+ structure, and the receiving host will perform the inverse
+ translation to its internal denotation. An IBM Mainframe record
+ count field may not be recognized at another host, so the
+ end-of-record information may be transferred as a two byte control
+ code in Stream mode or as a flagged bit in a Block or Compressed
+ mode descriptor. End-of-line in an ASCII or EBCDIC file with no
+ record structure should be indicated by <CRLF> or <NL>,
+ respectively. Since these transformations imply extra work for
+ some systems, identical systems transferring non-record structured
+ text files might wish to use a binary representation and stream
+ mode for the transfer.
+
+
+
+
+
+
+Postel & Reynolds [Page 20]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The following transmission modes are defined in FTP:
+
+ 3.4.1. STREAM MODE
+
+ The data is transmitted as a stream of bytes. There is no
+ restriction on the representation type used; record structures
+ are allowed.
+
+ In a record structured file EOR and EOF will each be indicated
+ by a two-byte control code. The first byte of the control code
+ will be all ones, the escape character. The second byte will
+ have the low order bit on and zeros elsewhere for EOR and the
+ second low order bit on for EOF; that is, the byte will have
+ value 1 for EOR and value 2 for EOF. EOR and EOF may be
+ indicated together on the last byte transmitted by turning both
+ low order bits on (i.e., the value 3). If a byte of all ones
+ was intended to be sent as data, it should be repeated in the
+ second byte of the control code.
+
+ If the structure is a file structure, the EOF is indicated by
+ the sending host closing the data connection and all bytes are
+ data bytes.
+
+ 3.4.2. BLOCK MODE
+
+ The file is transmitted as a series of data blocks preceded by
+ one or more header bytes. The header bytes contain a count
+ field, and descriptor code. The count field indicates the
+ total length of the data block in bytes, thus marking the
+ beginning of the next data block (there are no filler bits).
+ The descriptor code defines: last block in the file (EOF) last
+ block in the record (EOR), restart marker (see the Section on
+ Error Recovery and Restart) or suspect data (i.e., the data
+ being transferred is suspected of errors and is not reliable).
+ This last code is NOT intended for error control within FTP.
+ It is motivated by the desire of sites exchanging certain types
+ of data (e.g., seismic or weather data) to send and receive all
+ the data despite local errors (such as "magnetic tape read
+ errors"), but to indicate in the transmission that certain
+ portions are suspect). Record structures are allowed in this
+ mode, and any representation type may be used.
+
+ The header consists of the three bytes. Of the 24 bits of
+ header information, the 16 low order bits shall represent byte
+ count, and the 8 high order bits shall represent descriptor
+ codes as shown below.
+
+
+
+Postel & Reynolds [Page 21]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ Block Header
+
+ +----------------+----------------+----------------+
+ | Descriptor | Byte Count |
+ | 8 bits | 16 bits |
+ +----------------+----------------+----------------+
+
+
+ The descriptor codes are indicated by bit flags in the
+ descriptor byte. Four codes have been assigned, where each
+ code number is the decimal value of the corresponding bit in
+ the byte.
+
+ Code Meaning
+
+ 128 End of data block is EOR
+ 64 End of data block is EOF
+ 32 Suspected errors in data block
+ 16 Data block is a restart marker
+
+ With this encoding, more than one descriptor coded condition
+ may exist for a particular block. As many bits as necessary
+ may be flagged.
+
+ The restart marker is embedded in the data stream as an
+ integral number of 8-bit bytes representing printable
+ characters in the language being used over the control
+ connection (e.g., default--NVT-ASCII). <SP> (Space, in the
+ appropriate language) must not be used WITHIN a restart marker.
+
+ For example, to transmit a six-character marker, the following
+ would be sent:
+
+ +--------+--------+--------+
+ |Descrptr| Byte count |
+ |code= 16| = 6 |
+ +--------+--------+--------+
+
+ +--------+--------+--------+
+ | Marker | Marker | Marker |
+ | 8 bits | 8 bits | 8 bits |
+ +--------+--------+--------+
+
+ +--------+--------+--------+
+ | Marker | Marker | Marker |
+ | 8 bits | 8 bits | 8 bits |
+ +--------+--------+--------+
+
+
+Postel & Reynolds [Page 22]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 3.4.3. COMPRESSED MODE
+
+ There are three kinds of information to be sent: regular data,
+ sent in a byte string; compressed data, consisting of
+ replications or filler; and control information, sent in a
+ two-byte escape sequence. If n>0 bytes (up to 127) of regular
+ data are sent, these n bytes are preceded by a byte with the
+ left-most bit set to 0 and the right-most 7 bits containing the
+ number n.
+
+ Byte string:
+
+ 1 7 8 8
+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
+ |0| n | | d(1) | ... | d(n) |
+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
+ ^ ^
+ |---n bytes---|
+ of data
+
+ String of n data bytes d(1),..., d(n)
+ Count n must be positive.
+
+ To compress a string of n replications of the data byte d, the
+ following 2 bytes are sent:
+
+ Replicated Byte:
+
+ 2 6 8
+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
+ |1 0| n | | d |
+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
+
+ A string of n filler bytes can be compressed into a single
+ byte, where the filler byte varies with the representation
+ type. If the type is ASCII or EBCDIC the filler byte is <SP>
+ (Space, ASCII code 32, EBCDIC code 64). If the type is Image
+ or Local byte the filler is a zero byte.
+
+ Filler String:
+
+ 2 6
+ +-+-+-+-+-+-+-+-+
+ |1 1| n |
+ +-+-+-+-+-+-+-+-+
+
+ The escape sequence is a double byte, the first of which is the
+
+
+Postel & Reynolds [Page 23]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ escape byte (all zeros) and the second of which contains
+ descriptor codes as defined in Block mode. The descriptor
+ codes have the same meaning as in Block mode and apply to the
+ succeeding string of bytes.
+
+ Compressed mode is useful for obtaining increased bandwidth on
+ very large network transmissions at a little extra CPU cost.
+ It can be most effectively used to reduce the size of printer
+ files such as those generated by RJE hosts.
+
+ 3.5. ERROR RECOVERY AND RESTART
+
+ There is no provision for detecting bits lost or scrambled in data
+ transfer; this level of error control is handled by the TCP.
+ However, a restart procedure is provided to protect users from
+ gross system failures (including failures of a host, an
+ FTP-process, or the underlying network).
+
+ The restart procedure is defined only for the block and compressed
+ modes of data transfer. It requires the sender of data to insert
+ a special marker code in the data stream with some marker
+ information. The marker information has meaning only to the
+ sender, but must consist of printable characters in the default or
+ negotiated language of the control connection (ASCII or EBCDIC).
+ The marker could represent a bit-count, a record-count, or any
+ other information by which a system may identify a data
+ checkpoint. The receiver of data, if it implements the restart
+ procedure, would then mark the corresponding position of this
+ marker in the receiving system, and return this information to the
+ user.
+
+ In the event of a system failure, the user can restart the data
+ transfer by identifying the marker point with the FTP restart
+ procedure. The following example illustrates the use of the
+ restart procedure.
+
+ The sender of the data inserts an appropriate marker block in the
+ data stream at a convenient point. The receiving host marks the
+ corresponding data point in its file system and conveys the last
+ known sender and receiver marker information to the user, either
+ directly or over the control connection in a 110 reply (depending
+ on who is the sender). In the event of a system failure, the user
+ or controller process restarts the server at the last server
+ marker by sending a restart command with server's marker code as
+ its argument. The restart command is transmitted over the control
+
+
+
+
+Postel & Reynolds [Page 24]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ connection and is immediately followed by the command (such as
+ RETR, STOR or LIST) which was being executed when the system
+ failure occurred.
+
+4. FILE TRANSFER FUNCTIONS
+
+ The communication channel from the user-PI to the server-PI is
+ established as a TCP connection from the user to the standard server
+ port. The user protocol interpreter is responsible for sending FTP
+ commands and interpreting the replies received; the server-PI
+ interprets commands, sends replies and directs its DTP to set up the
+ data connection and transfer the data. If the second party to the
+ data transfer (the passive transfer process) is the user-DTP, then it
+ is governed through the internal protocol of the user-FTP host; if it
+ is a second server-DTP, then it is governed by its PI on command from
+ the user-PI. The FTP replies are discussed in the next section. In
+ the description of a few of the commands in this section, it is
+ helpful to be explicit about the possible replies.
+
+ 4.1. FTP COMMANDS
+
+ 4.1.1. ACCESS CONTROL COMMANDS
+
+ The following commands specify access control identifiers
+ (command codes are shown in parentheses).
+
+ USER NAME (USER)
+
+ The argument field is a Telnet string identifying the user.
+ The user identification is that which is required by the
+ server for access to its file system. This command will
+ normally be the first command transmitted by the user after
+ the control connections are made (some servers may require
+ this). Additional identification information in the form of
+ a password and/or an account command may also be required by
+ some servers. Servers may allow a new USER command to be
+ entered at any point in order to change the access control
+ and/or accounting information. This has the effect of
+ flushing any user, password, and account information already
+ supplied and beginning the login sequence again. All
+ transfer parameters are unchanged and any file transfer in
+ progress is completed under the old access control
+ parameters.
+
+
+
+
+
+
+Postel & Reynolds [Page 25]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ PASSWORD (PASS)
+
+ The argument field is a Telnet string specifying the user's
+ password. This command must be immediately preceded by the
+ user name command, and, for some sites, completes the user's
+ identification for access control. Since password
+ information is quite sensitive, it is desirable in general
+ to "mask" it or suppress typeout. It appears that the
+ server has no foolproof way to achieve this. It is
+ therefore the responsibility of the user-FTP process to hide
+ the sensitive password information.
+
+ ACCOUNT (ACCT)
+
+ The argument field is a Telnet string identifying the user's
+ account. The command is not necessarily related to the USER
+ command, as some sites may require an account for login and
+ others only for specific access, such as storing files. In
+ the latter case the command may arrive at any time.
+
+ There are reply codes to differentiate these cases for the
+ automation: when account information is required for login,
+ the response to a successful PASSword command is reply code
+ 332. On the other hand, if account information is NOT
+ required for login, the reply to a successful PASSword
+ command is 230; and if the account information is needed for
+ a command issued later in the dialogue, the server should
+ return a 332 or 532 reply depending on whether it stores
+ (pending receipt of the ACCounT command) or discards the
+ command, respectively.
+
+ CHANGE WORKING DIRECTORY (CWD)
+
+ This command allows the user to work with a different
+ directory or dataset for file storage or retrieval without
+ altering his login or accounting information. Transfer
+ parameters are similarly unchanged. The argument is a
+ pathname specifying a directory or other system dependent
+ file group designator.
+
+ CHANGE TO PARENT DIRECTORY (CDUP)
+
+ This command is a special case of CWD, and is included to
+ simplify the implementation of programs for transferring
+ directory trees between operating systems having different
+
+
+
+
+Postel & Reynolds [Page 26]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ syntaxes for naming the parent directory. The reply codes
+ shall be identical to the reply codes of CWD. See
+ Appendix II for further details.
+
+ STRUCTURE MOUNT (SMNT)
+
+ This command allows the user to mount a different file
+ system data structure without altering his login or
+ accounting information. Transfer parameters are similarly
+ unchanged. The argument is a pathname specifying a
+ directory or other system dependent file group designator.
+
+ REINITIALIZE (REIN)
+
+ This command terminates a USER, flushing all I/O and account
+ information, except to allow any transfer in progress to be
+ completed. All parameters are reset to the default settings
+ and the control connection is left open. This is identical
+ to the state in which a user finds himself immediately after
+ the control connection is opened. A USER command may be
+ expected to follow.
+
+ LOGOUT (QUIT)
+
+ This command terminates a USER and if file transfer is not
+ in progress, the server closes the control connection. If
+ file transfer is in progress, the connection will remain
+ open for result response and the server will then close it.
+ If the user-process is transferring files for several USERs
+ but does not wish to close and then reopen connections for
+ each, then the REIN command should be used instead of QUIT.
+
+ An unexpected close on the control connection will cause the
+ server to take the effective action of an abort (ABOR) and a
+ logout (QUIT).
+
+ 4.1.2. TRANSFER PARAMETER COMMANDS
+
+ All data transfer parameters have default values, and the
+ commands specifying data transfer parameters are required only
+ if the default parameter values are to be changed. The default
+ value is the last specified value, or if no value has been
+ specified, the standard default value is as stated here. This
+ implies that the server must "remember" the applicable default
+ values. The commands may be in any order except that they must
+ precede the FTP service request. The following commands
+ specify data transfer parameters:
+
+
+Postel & Reynolds [Page 27]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ DATA PORT (PORT)
+
+ The argument is a HOST-PORT specification for the data port
+ to be used in data connection. There are defaults for both
+ the user and server data ports, and under normal
+ circumstances this command and its reply are not needed. If
+ this command is used, the argument is the concatenation of a
+ 32-bit internet host address and a 16-bit TCP port address.
+ This address information is broken into 8-bit fields and the
+ value of each field is transmitted as a decimal number (in
+ character string representation). The fields are separated
+ by commas. A port command would be:
+
+ PORT h1,h2,h3,h4,p1,p2
+
+ where h1 is the high order 8 bits of the internet host
+ address.
+
+ PASSIVE (PASV)
+
+ This command requests the server-DTP to "listen" on a data
+ port (which is not its default data port) and to wait for a
+ connection rather than initiate one upon receipt of a
+ transfer command. The response to this command includes the
+ host and port address this server is listening on.
+
+ REPRESENTATION TYPE (TYPE)
+
+ The argument specifies the representation type as described
+ in the Section on Data Representation and Storage. Several
+ types take a second parameter. The first parameter is
+ denoted by a single Telnet character, as is the second
+ Format parameter for ASCII and EBCDIC; the second parameter
+ for local byte is a decimal integer to indicate Bytesize.
+ The parameters are separated by a <SP> (Space, ASCII code
+ 32).
+
+ The following codes are assigned for type:
+
+ \ /
+ A - ASCII | | N - Non-print
+ |-><-| T - Telnet format effectors
+ E - EBCDIC| | C - Carriage Control (ASA)
+ / \
+ I - Image
+
+ L <byte size> - Local byte Byte size
+
+
+Postel & Reynolds [Page 28]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The default representation type is ASCII Non-print. If the
+ Format parameter is changed, and later just the first
+ argument is changed, Format then returns to the Non-print
+ default.
+
+ FILE STRUCTURE (STRU)
+
+ The argument is a single Telnet character code specifying
+ file structure described in the Section on Data
+ Representation and Storage.
+
+ The following codes are assigned for structure:
+
+ F - File (no record structure)
+ R - Record structure
+ P - Page structure
+
+ The default structure is File.
+
+ TRANSFER MODE (MODE)
+
+ The argument is a single Telnet character code specifying
+ the data transfer modes described in the Section on
+ Transmission Modes.
+
+ The following codes are assigned for transfer modes:
+
+ S - Stream
+ B - Block
+ C - Compressed
+
+ The default transfer mode is Stream.
+
+ 4.1.3. FTP SERVICE COMMANDS
+
+ The FTP service commands define the file transfer or the file
+ system function requested by the user. The argument of an FTP
+ service command will normally be a pathname. The syntax of
+ pathnames must conform to server site conventions (with
+ standard defaults applicable), and the language conventions of
+ the control connection. The suggested default handling is to
+ use the last specified device, directory or file name, or the
+ standard default defined for local users. The commands may be
+ in any order except that a "rename from" command must be
+ followed by a "rename to" command and the restart command must
+ be followed by the interrupted service command (e.g., STOR or
+ RETR). The data, when transferred in response to FTP service
+
+
+Postel & Reynolds [Page 29]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ commands, shall always be sent over the data connection, except
+ for certain informative replies. The following commands
+ specify FTP service requests:
+
+ RETRIEVE (RETR)
+
+ This command causes the server-DTP to transfer a copy of the
+ file, specified in the pathname, to the server- or user-DTP
+ at the other end of the data connection. The status and
+ contents of the file at the server site shall be unaffected.
+
+ STORE (STOR)
+
+ This command causes the server-DTP to accept the data
+ transferred via the data connection and to store the data as
+ a file at the server site. If the file specified in the
+ pathname exists at the server site, then its contents shall
+ be replaced by the data being transferred. A new file is
+ created at the server site if the file specified in the
+ pathname does not already exist.
+
+ STORE UNIQUE (STOU)
+
+ This command behaves like STOR except that the resultant
+ file is to be created in the current directory under a name
+ unique to that directory. The 250 Transfer Started response
+ must include the name generated.
+
+ APPEND (with create) (APPE)
+
+ This command causes the server-DTP to accept the data
+ transferred via the data connection and to store the data in
+ a file at the server site. If the file specified in the
+ pathname exists at the server site, then the data shall be
+ appended to that file; otherwise the file specified in the
+ pathname shall be created at the server site.
+
+ ALLOCATE (ALLO)
+
+ This command may be required by some servers to reserve
+ sufficient storage to accommodate the new file to be
+ transferred. The argument shall be a decimal integer
+ representing the number of bytes (using the logical byte
+ size) of storage to be reserved for the file. For files
+ sent with record or page structure a maximum record or page
+ size (in logical bytes) might also be necessary; this is
+ indicated by a decimal integer in a second argument field of
+
+
+Postel & Reynolds [Page 30]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ the command. This second argument is optional, but when
+ present should be separated from the first by the three
+ Telnet characters <SP> R <SP>. This command shall be
+ followed by a STORe or APPEnd command. The ALLO command
+ should be treated as a NOOP (no operation) by those servers
+ which do not require that the maximum size of the file be
+ declared beforehand, and those servers interested in only
+ the maximum record or page size should accept a dummy value
+ in the first argument and ignore it.
+
+ RESTART (REST)
+
+ The argument field represents the server marker at which
+ file transfer is to be restarted. This command does not
+ cause file transfer but skips over the file to the specified
+ data checkpoint. This command shall be immediately followed
+ by the appropriate FTP service command which shall cause
+ file transfer to resume.
+
+ RENAME FROM (RNFR)
+
+ This command specifies the old pathname of the file which is
+ to be renamed. This command must be immediately followed by
+ a "rename to" command specifying the new file pathname.
+
+ RENAME TO (RNTO)
+
+ This command specifies the new pathname of the file
+ specified in the immediately preceding "rename from"
+ command. Together the two commands cause a file to be
+ renamed.
+
+ ABORT (ABOR)
+
+ This command tells the server to abort the previous FTP
+ service command and any associated transfer of data. The
+ abort command may require "special action", as discussed in
+ the Section on FTP Commands, to force recognition by the
+ server. No action is to be taken if the previous command
+ has been completed (including data transfer). The control
+ connection is not to be closed by the server, but the data
+ connection must be closed.
+
+ There are two cases for the server upon receipt of this
+ command: (1) the FTP service command was already completed,
+ or (2) the FTP service command is still in progress.
+
+
+
+Postel & Reynolds [Page 31]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ In the first case, the server closes the data connection
+ (if it is open) and responds with a 226 reply, indicating
+ that the abort command was successfully processed.
+
+ In the second case, the server aborts the FTP service in
+ progress and closes the data connection, returning a 426
+ reply to indicate that the service request terminated
+ abnormally. The server then sends a 226 reply,
+ indicating that the abort command was successfully
+ processed.
+
+ DELETE (DELE)
+
+ This command causes the file specified in the pathname to be
+ deleted at the server site. If an extra level of protection
+ is desired (such as the query, "Do you really wish to
+ delete?"), it should be provided by the user-FTP process.
+
+ REMOVE DIRECTORY (RMD)
+
+ This command causes the directory specified in the pathname
+ to be removed as a directory (if the pathname is absolute)
+ or as a subdirectory of the current working directory (if
+ the pathname is relative). See Appendix II.
+
+ MAKE DIRECTORY (MKD)
+
+ This command causes the directory specified in the pathname
+ to be created as a directory (if the pathname is absolute)
+ or as a subdirectory of the current working directory (if
+ the pathname is relative). See Appendix II.
+
+ PRINT WORKING DIRECTORY (PWD)
+
+ This command causes the name of the current working
+ directory to be returned in the reply. See Appendix II.
+
+ LIST (LIST)
+
+ This command causes a list to be sent from the server to the
+ passive DTP. If the pathname specifies a directory or other
+ group of files, the server should transfer a list of files
+ in the specified directory. If the pathname specifies a
+ file then the server should send current information on the
+ file. A null argument implies the user's current working or
+ default directory. The data transfer is over the data
+ connection in type ASCII or type EBCDIC. (The user must
+
+
+Postel & Reynolds [Page 32]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ ensure that the TYPE is appropriately ASCII or EBCDIC).
+ Since the information on a file may vary widely from system
+ to system, this information may be hard to use automatically
+ in a program, but may be quite useful to a human user.
+
+ NAME LIST (NLST)
+
+ This command causes a directory listing to be sent from
+ server to user site. The pathname should specify a
+ directory or other system-specific file group descriptor; a
+ null argument implies the current directory. The server
+ will return a stream of names of files and no other
+ information. The data will be transferred in ASCII or
+ EBCDIC type over the data connection as valid pathname
+ strings separated by <CRLF> or <NL>. (Again the user must
+ ensure that the TYPE is correct.) This command is intended
+ to return information that can be used by a program to
+ further process the files automatically. For example, in
+ the implementation of a "multiple get" function.
+
+ SITE PARAMETERS (SITE)
+
+ This command is used by the server to provide services
+ specific to his system that are essential to file transfer
+ but not sufficiently universal to be included as commands in
+ the protocol. The nature of these services and the
+ specification of their syntax can be stated in a reply to
+ the HELP SITE command.
+
+ SYSTEM (SYST)
+
+ This command is used to find out the type of operating
+ system at the server. The reply shall have as its first
+ word one of the system names listed in the current version
+ of the Assigned Numbers document [4].
+
+ STATUS (STAT)
+
+ This command shall cause a status response to be sent over
+ the control connection in the form of a reply. The command
+ may be sent during a file transfer (along with the Telnet IP
+ and Synch signals--see the Section on FTP Commands) in which
+ case the server will respond with the status of the
+ operation in progress, or it may be sent between file
+ transfers. In the latter case, the command may have an
+ argument field. If the argument is a pathname, the command
+ is analogous to the "list" command except that data shall be
+
+
+Postel & Reynolds [Page 33]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ transferred over the control connection. If a partial
+ pathname is given, the server may respond with a list of
+ file names or attributes associated with that specification.
+ If no argument is given, the server should return general
+ status information about the server FTP process. This
+ should include current values of all transfer parameters and
+ the status of connections.
+
+ HELP (HELP)
+
+ This command shall cause the server to send helpful
+ information regarding its implementation status over the
+ control connection to the user. The command may take an
+ argument (e.g., any command name) and return more specific
+ information as a response. The reply is type 211 or 214.
+ It is suggested that HELP be allowed before entering a USER
+ command. The server may use this reply to specify
+ site-dependent parameters, e.g., in response to HELP SITE.
+
+ NOOP (NOOP)
+
+ This command does not affect any parameters or previously
+ entered commands. It specifies no action other than that the
+ server send an OK reply.
+
+ The File Transfer Protocol follows the specifications of the Telnet
+ protocol for all communications over the control connection. Since
+ the language used for Telnet communication may be a negotiated
+ option, all references in the next two sections will be to the
+ "Telnet language" and the corresponding "Telnet end-of-line code".
+ Currently, one may take these to mean NVT-ASCII and <CRLF>. No other
+ specifications of the Telnet protocol will be cited.
+
+ FTP commands are "Telnet strings" terminated by the "Telnet end of
+ line code". The command codes themselves are alphabetic characters
+ terminated by the character <SP> (Space) if parameters follow and
+ Telnet-EOL otherwise. The command codes and the semantics of
+ commands are described in this section; the detailed syntax of
+ commands is specified in the Section on Commands, the reply sequences
+ are discussed in the Section on Sequencing of Commands and Replies,
+ and scenarios illustrating the use of commands are provided in the
+ Section on Typical FTP Scenarios.
+
+ FTP commands may be partitioned as those specifying access-control
+ identifiers, data transfer parameters, or FTP service requests.
+ Certain commands (such as ABOR, STAT, QUIT) may be sent over the
+ control connection while a data transfer is in progress. Some
+
+
+Postel & Reynolds [Page 34]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ servers may not be able to monitor the control and data connections
+ simultaneously, in which case some special action will be necessary
+ to get the server's attention. The following ordered format is
+ tentatively recommended:
+
+ 1. User system inserts the Telnet "Interrupt Process" (IP) signal
+ in the Telnet stream.
+
+ 2. User system sends the Telnet "Synch" signal.
+
+ 3. User system inserts the command (e.g., ABOR) in the Telnet
+ stream.
+
+ 4. Server PI, after receiving "IP", scans the Telnet stream for
+ EXACTLY ONE FTP command.
+
+ (For other servers this may not be necessary but the actions listed
+ above should have no unusual effect.)
+
+ 4.2. FTP REPLIES
+
+ Replies to File Transfer Protocol commands are devised to ensure
+ the synchronization of requests and actions in the process of file
+ transfer, and to guarantee that the user process always knows the
+ state of the Server. Every command must generate at least one
+ reply, although there may be more than one; in the latter case,
+ the multiple replies must be easily distinguished. In addition,
+ some commands occur in sequential groups, such as USER, PASS and
+ ACCT, or RNFR and RNTO. The replies show the existence of an
+ intermediate state if all preceding commands have been successful.
+ A failure at any point in the sequence necessitates the repetition
+ of the entire sequence from the beginning.
+
+ The details of the command-reply sequence are made explicit in
+ a set of state diagrams below.
+
+ An FTP reply consists of a three digit number (transmitted as
+ three alphanumeric characters) followed by some text. The number
+ is intended for use by automata to determine what state to enter
+ next; the text is intended for the human user. It is intended
+ that the three digits contain enough encoded information that the
+ user-process (the User-PI) will not need to examine the text and
+ may either discard it or pass it on to the user, as appropriate.
+ In particular, the text may be server-dependent, so there are
+ likely to be varying texts for each reply code.
+
+ A reply is defined to contain the 3-digit code, followed by Space
+
+
+Postel & Reynolds [Page 35]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ <SP>, followed by one line of text (where some maximum line length
+ has been specified), and terminated by the Telnet end-of-line
+ code. There will be cases however, where the text is longer than
+ a single line. In these cases the complete text must be bracketed
+ so the User-process knows when it may stop reading the reply (i.e.
+ stop processing input on the control connection) and go do other
+ things. This requires a special format on the first line to
+ indicate that more than one line is coming, and another on the
+ last line to designate it as the last. At least one of these must
+ contain the appropriate reply code to indicate the state of the
+ transaction. To satisfy all factions, it was decided that both
+ the first and last line codes should be the same.
+
+ Thus the format for multi-line replies is that the first line
+ will begin with the exact required reply code, followed
+ immediately by a Hyphen, "-" (also known as Minus), followed by
+ text. The last line will begin with the same code, followed
+ immediately by Space <SP>, optionally some text, and the Telnet
+ end-of-line code.
+
+ For example:
+ 123-First line
+ Second line
+ 234 A line beginning with numbers
+ 123 The last line
+
+ The user-process then simply needs to search for the second
+ occurrence of the same reply code, followed by <SP> (Space), at
+ the beginning of a line, and ignore all intermediary lines. If
+ an intermediary line begins with a 3-digit number, the Server
+ must pad the front to avoid confusion.
+
+ This scheme allows standard system routines to be used for
+ reply information (such as for the STAT reply), with
+ "artificial" first and last lines tacked on. In rare cases
+ where these routines are able to generate three digits and a
+ Space at the beginning of any line, the beginning of each
+ text line should be offset by some neutral text, like Space.
+
+ This scheme assumes that multi-line replies may not be nested.
+
+ The three digits of the reply each have a special significance.
+ This is intended to allow a range of very simple to very
+ sophisticated responses by the user-process. The first digit
+ denotes whether the response is good, bad or incomplete.
+ (Referring to the state diagram), an unsophisticated user-process
+ will be able to determine its next action (proceed as planned,
+
+
+Postel & Reynolds [Page 36]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ redo, retrench, etc.) by simply examining this first digit. A
+ user-process that wants to know approximately what kind of error
+ occurred (e.g. file system error, command syntax error) may
+ examine the second digit, reserving the third digit for the finest
+ gradation of information (e.g., RNTO command without a preceding
+ RNFR).
+
+ There are five values for the first digit of the reply code:
+
+ 1yz Positive Preliminary reply
+
+ The requested action is being initiated; expect another
+ reply before proceeding with a new command. (The
+ user-process sending another command before the
+ completion reply would be in violation of protocol; but
+ server-FTP processes should queue any commands that
+ arrive while a preceding command is in progress.) This
+ type of reply can be used to indicate that the command
+ was accepted and the user-process may now pay attention
+ to the data connections, for implementations where
+ simultaneous monitoring is difficult. The server-FTP
+ process may send at most, one 1yz reply per command.
+
+ 2yz Positive Completion reply
+
+ The requested action has been successfully completed. A
+ new request may be initiated.
+
+ 3yz Positive Intermediate reply
+
+ The command has been accepted, but the requested action
+ is being held in abeyance, pending receipt of further
+ information. The user should send another command
+ specifying this information. This reply is used in
+ command sequence groups.
+
+ 4yz Transient Negative Completion reply
+
+ The command was not accepted and the requested action did
+ not take place, but the error condition is temporary and
+ the action may be requested again. The user should
+ return to the beginning of the command sequence, if any.
+ It is difficult to assign a meaning to "transient",
+ particularly when two distinct sites (Server- and
+ User-processes) have to agree on the interpretation.
+ Each reply in the 4yz category might have a slightly
+ different time value, but the intent is that the
+
+
+Postel & Reynolds [Page 37]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ user-process is encouraged to try again. A rule of thumb
+ in determining if a reply fits into the 4yz or the 5yz
+ (Permanent Negative) category is that replies are 4yz if
+ the commands can be repeated without any change in
+ command form or in properties of the User or Server
+ (e.g., the command is spelled the same with the same
+ arguments used; the user does not change his file access
+ or user name; the server does not put up a new
+ implementation.)
+
+ 5yz Permanent Negative Completion reply
+
+ The command was not accepted and the requested action did
+ not take place. The User-process is discouraged from
+ repeating the exact request (in the same sequence). Even
+ some "permanent" error conditions can be corrected, so
+ the human user may want to direct his User-process to
+ reinitiate the command sequence by direct action at some
+ point in the future (e.g., after the spelling has been
+ changed, or the user has altered his directory status.)
+
+ The following function groupings are encoded in the second
+ digit:
+
+ x0z Syntax - These replies refer to syntax errors,
+ syntactically correct commands that don't fit any
+ functional category, unimplemented or superfluous
+ commands.
+
+ x1z Information - These are replies to requests for
+ information, such as status or help.
+
+ x2z Connections - Replies referring to the control and
+ data connections.
+
+ x3z Authentication and accounting - Replies for the login
+ process and accounting procedures.
+
+ x4z Unspecified as yet.
+
+ x5z File system - These replies indicate the status of the
+ Server file system vis-a-vis the requested transfer or
+ other file system action.
+
+ The third digit gives a finer gradation of meaning in each of
+ the function categories, specified by the second digit. The
+ list of replies below will illustrate this. Note that the text
+
+
+Postel & Reynolds [Page 38]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ associated with each reply is recommended, rather than
+ mandatory, and may even change according to the command with
+ which it is associated. The reply codes, on the other hand,
+ must strictly follow the specifications in the last section;
+ that is, Server implementations should not invent new codes for
+ situations that are only slightly different from the ones
+ described here, but rather should adapt codes already defined.
+
+ A command such as TYPE or ALLO whose successful execution
+ does not offer the user-process any new information will
+ cause a 200 reply to be returned. If the command is not
+ implemented by a particular Server-FTP process because it
+ has no relevance to that computer system, for example ALLO
+ at a TOPS20 site, a Positive Completion reply is still
+ desired so that the simple User-process knows it can proceed
+ with its course of action. A 202 reply is used in this case
+ with, for example, the reply text: "No storage allocation
+ necessary." If, on the other hand, the command requests a
+ non-site-specific action and is unimplemented, the response
+ is 502. A refinement of that is the 504 reply for a command
+ that is implemented, but that requests an unimplemented
+ parameter.
+
+ 4.2.1 Reply Codes by Function Groups
+
+ 200 Command okay.
+ 500 Syntax error, command unrecognized.
+ This may include errors such as command line too long.
+ 501 Syntax error in parameters or arguments.
+ 202 Command not implemented, superfluous at this site.
+ 502 Command not implemented.
+ 503 Bad sequence of commands.
+ 504 Command not implemented for that parameter.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 39]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 110 Restart marker reply.
+ In this case, the text is exact and not left to the
+ particular implementation; it must read:
+ MARK yyyy = mmmm
+ Where yyyy is User-process data stream marker, and mmmm
+ server's equivalent marker (note the spaces between markers
+ and "=").
+ 211 System status, or system help reply.
+ 212 Directory status.
+ 213 File status.
+ 214 Help message.
+ On how to use the server or the meaning of a particular
+ non-standard command. This reply is useful only to the
+ human user.
+ 215 NAME system type.
+ Where NAME is an official system name from the list in the
+ Assigned Numbers document.
+
+ 120 Service ready in nnn minutes.
+ 220 Service ready for new user.
+ 221 Service closing control connection.
+ Logged out if appropriate.
+ 421 Service not available, closing control connection.
+ This may be a reply to any command if the service knows it
+ must shut down.
+ 125 Data connection already open; transfer starting.
+ 225 Data connection open; no transfer in progress.
+ 425 Can't open data connection.
+ 226 Closing data connection.
+ Requested file action successful (for example, file
+ transfer or file abort).
+ 426 Connection closed; transfer aborted.
+ 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
+
+ 230 User logged in, proceed.
+ 530 Not logged in.
+ 331 User name okay, need password.
+ 332 Need account for login.
+ 532 Need account for storing files.
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 40]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 150 File status okay; about to open data connection.
+ 250 Requested file action okay, completed.
+ 257 "PATHNAME" created.
+ 350 Requested file action pending further information.
+ 450 Requested file action not taken.
+ File unavailable (e.g., file busy).
+ 550 Requested action not taken.
+ File unavailable (e.g., file not found, no access).
+ 451 Requested action aborted. Local error in processing.
+ 551 Requested action aborted. Page type unknown.
+ 452 Requested action not taken.
+ Insufficient storage space in system.
+ 552 Requested file action aborted.
+ Exceeded storage allocation (for current directory or
+ dataset).
+ 553 Requested action not taken.
+ File name not allowed.
+
+
+ 4.2.2 Numeric Order List of Reply Codes
+
+ 110 Restart marker reply.
+ In this case, the text is exact and not left to the
+ particular implementation; it must read:
+ MARK yyyy = mmmm
+ Where yyyy is User-process data stream marker, and mmmm
+ server's equivalent marker (note the spaces between markers
+ and "=").
+ 120 Service ready in nnn minutes.
+ 125 Data connection already open; transfer starting.
+ 150 File status okay; about to open data connection.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 41]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 200 Command okay.
+ 202 Command not implemented, superfluous at this site.
+ 211 System status, or system help reply.
+ 212 Directory status.
+ 213 File status.
+ 214 Help message.
+ On how to use the server or the meaning of a particular
+ non-standard command. This reply is useful only to the
+ human user.
+ 215 NAME system type.
+ Where NAME is an official system name from the list in the
+ Assigned Numbers document.
+ 220 Service ready for new user.
+ 221 Service closing control connection.
+ Logged out if appropriate.
+ 225 Data connection open; no transfer in progress.
+ 226 Closing data connection.
+ Requested file action successful (for example, file
+ transfer or file abort).
+ 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
+ 230 User logged in, proceed.
+ 250 Requested file action okay, completed.
+ 257 "PATHNAME" created.
+
+ 331 User name okay, need password.
+ 332 Need account for login.
+ 350 Requested file action pending further information.
+
+ 421 Service not available, closing control connection.
+ This may be a reply to any command if the service knows it
+ must shut down.
+ 425 Can't open data connection.
+ 426 Connection closed; transfer aborted.
+ 450 Requested file action not taken.
+ File unavailable (e.g., file busy).
+ 451 Requested action aborted: local error in processing.
+ 452 Requested action not taken.
+ Insufficient storage space in system.
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 42]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 500 Syntax error, command unrecognized.
+ This may include errors such as command line too long.
+ 501 Syntax error in parameters or arguments.
+ 502 Command not implemented.
+ 503 Bad sequence of commands.
+ 504 Command not implemented for that parameter.
+ 530 Not logged in.
+ 532 Need account for storing files.
+ 550 Requested action not taken.
+ File unavailable (e.g., file not found, no access).
+ 551 Requested action aborted: page type unknown.
+ 552 Requested file action aborted.
+ Exceeded storage allocation (for current directory or
+ dataset).
+ 553 Requested action not taken.
+ File name not allowed.
+
+
+5. DECLARATIVE SPECIFICATIONS
+
+ 5.1. MINIMUM IMPLEMENTATION
+
+ In order to make FTP workable without needless error messages, the
+ following minimum implementation is required for all servers:
+
+ TYPE - ASCII Non-print
+ MODE - Stream
+ STRUCTURE - File, Record
+ COMMANDS - USER, QUIT, PORT,
+ TYPE, MODE, STRU,
+ for the default values
+ RETR, STOR,
+ NOOP.
+
+ The default values for transfer parameters are:
+
+ TYPE - ASCII Non-print
+ MODE - Stream
+ STRU - File
+
+ All hosts must accept the above as the standard defaults.
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 43]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 5.2. CONNECTIONS
+
+ The server protocol interpreter shall "listen" on Port L. The
+ user or user protocol interpreter shall initiate the full-duplex
+ control connection. Server- and user- processes should follow the
+ conventions of the Telnet protocol as specified in the
+ ARPA-Internet Protocol Handbook [1]. Servers are under no
+ obligation to provide for editing of command lines and may require
+ that it be done in the user host. The control connection shall be
+ closed by the server at the user's request after all transfers and
+ replies are completed.
+
+ The user-DTP must "listen" on the specified data port; this may be
+ the default user port (U) or a port specified in the PORT command.
+ The server shall initiate the data connection from his own default
+ data port (L-1) using the specified user data port. The direction
+ of the transfer and the port used will be determined by the FTP
+ service command.
+
+ Note that all FTP implementation must support data transfer using
+ the default port, and that only the USER-PI may initiate the use
+ of non-default ports.
+
+ When data is to be transferred between two servers, A and B (refer
+ to Figure 2), the user-PI, C, sets up control connections with
+ both server-PI's. One of the servers, say A, is then sent a PASV
+ command telling him to "listen" on his data port rather than
+ initiate a connection when he receives a transfer service command.
+ When the user-PI receives an acknowledgment to the PASV command,
+ which includes the identity of the host and port being listened
+ on, the user-PI then sends A's port, a, to B in a PORT command; a
+ reply is returned. The user-PI may then send the corresponding
+ service commands to A and B. Server B initiates the connection
+ and the transfer proceeds. The command-reply sequence is listed
+ below where the messages are vertically synchronous but
+ horizontally asynchronous:
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 44]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ User-PI - Server A User-PI - Server B
+ ------------------ ------------------
+
+ C->A : Connect C->B : Connect
+ C->A : PASV
+ A->C : 227 Entering Passive Mode. A1,A2,A3,A4,a1,a2
+ C->B : PORT A1,A2,A3,A4,a1,a2
+ B->C : 200 Okay
+ C->A : STOR C->B : RETR
+ B->A : Connect to HOST-A, PORT-a
+
+ Figure 3
+
+ The data connection shall be closed by the server under the
+ conditions described in the Section on Establishing Data
+ Connections. If the data connection is to be closed following a
+ data transfer where closing the connection is not required to
+ indicate the end-of-file, the server must do so immediately.
+ Waiting until after a new transfer command is not permitted
+ because the user-process will have already tested the data
+ connection to see if it needs to do a "listen"; (remember that the
+ user must "listen" on a closed data port BEFORE sending the
+ transfer request). To prevent a race condition here, the server
+ sends a reply (226) after closing the data connection (or if the
+ connection is left open, a "file transfer completed" reply (250)
+ and the user-PI should wait for one of these replies before
+ issuing a new transfer command).
+
+ Any time either the user or server see that the connection is
+ being closed by the other side, it should promptly read any
+ remaining data queued on the connection and issue the close on its
+ own side.
+
+ 5.3. COMMANDS
+
+ The commands are Telnet character strings transmitted over the
+ control connections as described in the Section on FTP Commands.
+ The command functions and semantics are described in the Section
+ on Access Control Commands, Transfer Parameter Commands, FTP
+ Service Commands, and Miscellaneous Commands. The command syntax
+ is specified here.
+
+ The commands begin with a command code followed by an argument
+ field. The command codes are four or fewer alphabetic characters.
+ Upper and lower case alphabetic characters are to be treated
+ identically. Thus, any of the following may represent the
+ retrieve command:
+
+
+Postel & Reynolds [Page 45]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ RETR Retr retr ReTr rETr
+
+ This also applies to any symbols representing parameter values,
+ such as A or a for ASCII TYPE. The command codes and the argument
+ fields are separated by one or more spaces.
+
+ The argument field consists of a variable length character string
+ ending with the character sequence <CRLF> (Carriage Return, Line
+ Feed) for NVT-ASCII representation; for other negotiated languages
+ a different end of line character might be used. It should be
+ noted that the server is to take no action until the end of line
+ code is received.
+
+ The syntax is specified below in NVT-ASCII. All characters in the
+ argument field are ASCII characters including any ASCII
+ represented decimal integers. Square brackets denote an optional
+ argument field. If the option is not taken, the appropriate
+ default is implied.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 46]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 5.3.1. FTP COMMANDS
+
+ The following are the FTP commands:
+
+ USER <SP> <username> <CRLF>
+ PASS <SP> <password> <CRLF>
+ ACCT <SP> <account-information> <CRLF>
+ CWD <SP> <pathname> <CRLF>
+ CDUP <CRLF>
+ SMNT <SP> <pathname> <CRLF>
+ QUIT <CRLF>
+ REIN <CRLF>
+ PORT <SP> <host-port> <CRLF>
+ PASV <CRLF>
+ TYPE <SP> <type-code> <CRLF>
+ STRU <SP> <structure-code> <CRLF>
+ MODE <SP> <mode-code> <CRLF>
+ RETR <SP> <pathname> <CRLF>
+ STOR <SP> <pathname> <CRLF>
+ STOU <CRLF>
+ APPE <SP> <pathname> <CRLF>
+ ALLO <SP> <decimal-integer>
+ [<SP> R <SP> <decimal-integer>] <CRLF>
+ REST <SP> <marker> <CRLF>
+ RNFR <SP> <pathname> <CRLF>
+ RNTO <SP> <pathname> <CRLF>
+ ABOR <CRLF>
+ DELE <SP> <pathname> <CRLF>
+ RMD <SP> <pathname> <CRLF>
+ MKD <SP> <pathname> <CRLF>
+ PWD <CRLF>
+ LIST [<SP> <pathname>] <CRLF>
+ NLST [<SP> <pathname>] <CRLF>
+ SITE <SP> <string> <CRLF>
+ SYST <CRLF>
+ STAT [<SP> <pathname>] <CRLF>
+ HELP [<SP> <string>] <CRLF>
+ NOOP <CRLF>
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 47]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 5.3.2. FTP COMMAND ARGUMENTS
+
+ The syntax of the above argument fields (using BNF notation
+ where applicable) is:
+
+ <username> ::= <string>
+ <password> ::= <string>
+ <account-information> ::= <string>
+ <string> ::= <char> | <char><string>
+ <char> ::= any of the 128 ASCII characters except <CR> and
+ <LF>
+ <marker> ::= <pr-string>
+ <pr-string> ::= <pr-char> | <pr-char><pr-string>
+ <pr-char> ::= printable characters, any
+ ASCII code 33 through 126
+ <byte-size> ::= <number>
+ <host-port> ::= <host-number>,<port-number>
+ <host-number> ::= <number>,<number>,<number>,<number>
+ <port-number> ::= <number>,<number>
+ <number> ::= any decimal integer 1 through 255
+ <form-code> ::= N | T | C
+ <type-code> ::= A [<sp> <form-code>]
+ | E [<sp> <form-code>]
+ | I
+ | L <sp> <byte-size>
+ <structure-code> ::= F | R | P
+ <mode-code> ::= S | B | C
+ <pathname> ::= <string>
+ <decimal-integer> ::= any decimal integer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 48]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ 5.4. SEQUENCING OF COMMANDS AND REPLIES
+
+ The communication between the user and server is intended to be an
+ alternating dialogue. As such, the user issues an FTP command and
+ the server responds with a prompt primary reply. The user should
+ wait for this initial primary success or failure response before
+ sending further commands.
+
+ Certain commands require a second reply for which the user should
+ also wait. These replies may, for example, report on the progress
+ or completion of file transfer or the closing of the data
+ connection. They are secondary replies to file transfer commands.
+
+ One important group of informational replies is the connection
+ greetings. Under normal circumstances, a server will send a 220
+ reply, "awaiting input", when the connection is completed. The
+ user should wait for this greeting message before sending any
+ commands. If the server is unable to accept input right away, a
+ 120 "expected delay" reply should be sent immediately and a 220
+ reply when ready. The user will then know not to hang up if there
+ is a delay.
+
+ Spontaneous Replies
+
+ Sometimes "the system" spontaneously has a message to be sent
+ to a user (usually all users). For example, "System going down
+ in 15 minutes". There is no provision in FTP for such
+ spontaneous information to be sent from the server to the user.
+ It is recommended that such information be queued in the
+ server-PI and delivered to the user-PI in the next reply
+ (possibly making it a multi-line reply).
+
+ The table below lists alternative success and failure replies for
+ each command. These must be strictly adhered to; a server may
+ substitute text in the replies, but the meaning and action implied
+ by the code numbers and by the specific command reply sequence
+ cannot be altered.
+
+ Command-Reply Sequences
+
+ In this section, the command-reply sequence is presented. Each
+ command is listed with its possible replies; command groups are
+ listed together. Preliminary replies are listed first (with
+ their succeeding replies indented and under them), then
+ positive and negative completion, and finally intermediary
+
+
+
+
+Postel & Reynolds [Page 49]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ replies with the remaining commands from the sequence
+ following. This listing forms the basis for the state
+ diagrams, which will be presented separately.
+
+ Connection Establishment
+ 120
+ 220
+ 220
+ 421
+ Login
+ USER
+ 230
+ 530
+ 500, 501, 421
+ 331, 332
+ PASS
+ 230
+ 202
+ 530
+ 500, 501, 503, 421
+ 332
+ ACCT
+ 230
+ 202
+ 530
+ 500, 501, 503, 421
+ CWD
+ 250
+ 500, 501, 502, 421, 530, 550
+ CDUP
+ 200
+ 500, 501, 502, 421, 530, 550
+ SMNT
+ 202, 250
+ 500, 501, 502, 421, 530, 550
+ Logout
+ REIN
+ 120
+ 220
+ 220
+ 421
+ 500, 502
+ QUIT
+ 221
+ 500
+
+
+
+
+Postel & Reynolds [Page 50]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ Transfer parameters
+ PORT
+ 200
+ 500, 501, 421, 530
+ PASV
+ 227
+ 500, 501, 502, 421, 530
+ MODE
+ 200
+ 500, 501, 504, 421, 530
+ TYPE
+ 200
+ 500, 501, 504, 421, 530
+ STRU
+ 200
+ 500, 501, 504, 421, 530
+ File action commands
+ ALLO
+ 200
+ 202
+ 500, 501, 504, 421, 530
+ REST
+ 500, 501, 502, 421, 530
+ 350
+ STOR
+ 125, 150
+ (110)
+ 226, 250
+ 425, 426, 451, 551, 552
+ 532, 450, 452, 553
+ 500, 501, 421, 530
+ STOU
+ 125, 150
+ (110)
+ 226, 250
+ 425, 426, 451, 551, 552
+ 532, 450, 452, 553
+ 500, 501, 421, 530
+ RETR
+ 125, 150
+ (110)
+ 226, 250
+ 425, 426, 451
+ 450, 550
+ 500, 501, 421, 530
+
+
+
+
+Postel & Reynolds [Page 51]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ LIST
+ 125, 150
+ 226, 250
+ 425, 426, 451
+ 450
+ 500, 501, 502, 421, 530
+ NLST
+ 125, 150
+ 226, 250
+ 425, 426, 451
+ 450
+ 500, 501, 502, 421, 530
+ APPE
+ 125, 150
+ (110)
+ 226, 250
+ 425, 426, 451, 551, 552
+ 532, 450, 550, 452, 553
+ 500, 501, 502, 421, 530
+ RNFR
+ 450, 550
+ 500, 501, 502, 421, 530
+ 350
+ RNTO
+ 250
+ 532, 553
+ 500, 501, 502, 503, 421, 530
+ DELE
+ 250
+ 450, 550
+ 500, 501, 502, 421, 530
+ RMD
+ 250
+ 500, 501, 502, 421, 530, 550
+ MKD
+ 257
+ 500, 501, 502, 421, 530, 550
+ PWD
+ 257
+ 500, 501, 502, 421, 550
+ ABOR
+ 225, 226
+ 500, 501, 502, 421
+
+
+
+
+
+
+Postel & Reynolds [Page 52]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ Informational commands
+ SYST
+ 215
+ 500, 501, 502, 421
+ STAT
+ 211, 212, 213
+ 450
+ 500, 501, 502, 421, 530
+ HELP
+ 211, 214
+ 500, 501, 502, 421
+ Miscellaneous commands
+ SITE
+ 200
+ 202
+ 500, 501, 530
+ NOOP
+ 200
+ 500 421
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 53]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+6. STATE DIAGRAMS
+
+ Here we present state diagrams for a very simple minded FTP
+ implementation. Only the first digit of the reply codes is used.
+ There is one state diagram for each group of FTP commands or command
+ sequences.
+
+ The command groupings were determined by constructing a model for
+ each command then collecting together the commands with structurally
+ identical models.
+
+ For each command or command sequence there are three possible
+ outcomes: success (S), failure (F), and error (E). In the state
+ diagrams below we use the symbol B for "begin", and the symbol W for
+ "wait for reply".
+
+ We first present the diagram that represents the largest group of FTP
+ commands:
+
+
+ 1,3 +---+
+ ----------->| E |
+ | +---+
+ |
+ +---+ cmd +---+ 2 +---+
+ | B |---------->| W |---------->| S |
+ +---+ +---+ +---+
+ |
+ | 4,5 +---+
+ ----------->| F |
+ +---+
+
+
+ This diagram models the commands:
+
+ ABOR, ALLO, DELE, CWD, CDUP, SMNT, HELP, MODE, NOOP, PASV,
+ QUIT, SITE, PORT, SYST, STAT, RMD, MKD, PWD, STRU, and TYPE.
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 54]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The other large group of commands is represented by a very similar
+ diagram:
+
+
+ 3 +---+
+ ----------->| E |
+ | +---+
+ |
+ +---+ cmd +---+ 2 +---+
+ | B |---------->| W |---------->| S |
+ +---+ --->+---+ +---+
+ | | |
+ | | | 4,5 +---+
+ | 1 | ----------->| F |
+ ----- +---+
+
+
+ This diagram models the commands:
+
+ APPE, LIST, NLST, REIN, RETR, STOR, and STOU.
+
+ Note that this second model could also be used to represent the first
+ group of commands, the only difference being that in the first group
+ the 100 series replies are unexpected and therefore treated as error,
+ while the second group expects (some may require) 100 series replies.
+ Remember that at most, one 100 series reply is allowed per command.
+
+ The remaining diagrams model command sequences, perhaps the simplest
+ of these is the rename sequence:
+
+
+ +---+ RNFR +---+ 1,2 +---+
+ | B |---------->| W |---------->| E |
+ +---+ +---+ -->+---+
+ | | |
+ 3 | | 4,5 |
+ -------------- ------ |
+ | | | +---+
+ | ------------->| S |
+ | | 1,3 | | +---+
+ | 2| --------
+ | | | |
+ V | | |
+ +---+ RNTO +---+ 4,5 ----->+---+
+ | |---------->| W |---------->| F |
+ +---+ +---+ +---+
+
+
+
+Postel & Reynolds [Page 55]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The next diagram is a simple model of the Restart command:
+
+
+ +---+ REST +---+ 1,2 +---+
+ | B |---------->| W |---------->| E |
+ +---+ +---+ -->+---+
+ | | |
+ 3 | | 4,5 |
+ -------------- ------ |
+ | | | +---+
+ | ------------->| S |
+ | | 3 | | +---+
+ | 2| --------
+ | | | |
+ V | | |
+ +---+ cmd +---+ 4,5 ----->+---+
+ | |---------->| W |---------->| F |
+ +---+ -->+---+ +---+
+ | |
+ | 1 |
+ ------
+
+
+ Where "cmd" is APPE, STOR, or RETR.
+
+ We note that the above three models are similar. The Restart differs
+ from the Rename two only in the treatment of 100 series replies at
+ the second stage, while the second group expects (some may require)
+ 100 series replies. Remember that at most, one 100 series reply is
+ allowed per command.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 56]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The most complicated diagram is for the Login sequence:
+
+
+ 1
+ +---+ USER +---+------------->+---+
+ | B |---------->| W | 2 ---->| E |
+ +---+ +---+------ | -->+---+
+ | | | | |
+ 3 | | 4,5 | | |
+ -------------- ----- | | |
+ | | | | |
+ | | | | |
+ | --------- |
+ | 1| | | |
+ V | | | |
+ +---+ PASS +---+ 2 | ------>+---+
+ | |---------->| W |------------->| S |
+ +---+ +---+ ---------->+---+
+ | | | | |
+ 3 | |4,5| | |
+ -------------- -------- |
+ | | | | |
+ | | | | |
+ | -----------
+ | 1,3| | | |
+ V | 2| | |
+ +---+ ACCT +---+-- | ----->+---+
+ | |---------->| W | 4,5 -------->| F |
+ +---+ +---+------------->+---+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 57]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ Finally, we present a generalized diagram that could be used to model
+ the command and reply interchange:
+
+
+ ------------------------------------
+ | |
+ Begin | |
+ | V |
+ | +---+ cmd +---+ 2 +---+ |
+ -->| |------->| |---------->| | |
+ | | | W | | S |-----|
+ -->| | -->| |----- | | |
+ | +---+ | +---+ 4,5 | +---+ |
+ | | | | | | |
+ | | | 1| |3 | +---+ |
+ | | | | | | | | |
+ | | ---- | ---->| F |-----
+ | | | | |
+ | | | +---+
+ -------------------
+ |
+ |
+ V
+ End
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 58]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+7. TYPICAL FTP SCENARIO
+
+ User at host U wanting to transfer files to/from host S:
+
+ In general, the user will communicate to the server via a mediating
+ user-FTP process. The following may be a typical scenario. The
+ user-FTP prompts are shown in parentheses, '---->' represents
+ commands from host U to host S, and '<----' represents replies from
+ host S to host U.
+
+ LOCAL COMMANDS BY USER ACTION INVOLVED
+
+ ftp (host) multics<CR> Connect to host S, port L,
+ establishing control connections.
+ <---- 220 Service ready <CRLF>.
+ username Doe <CR> USER Doe<CRLF>---->
+ <---- 331 User name ok,
+ need password<CRLF>.
+ password mumble <CR> PASS mumble<CRLF>---->
+ <---- 230 User logged in<CRLF>.
+ retrieve (local type) ASCII<CR>
+ (local pathname) test 1 <CR> User-FTP opens local file in ASCII.
+ (for. pathname) test.pl1<CR> RETR test.pl1<CRLF> ---->
+ <---- 150 File status okay;
+ about to open data
+ connection<CRLF>.
+ Server makes data connection
+ to port U.
+
+ <---- 226 Closing data connection,
+ file transfer successful<CRLF>.
+ type Image<CR> TYPE I<CRLF> ---->
+ <---- 200 Command OK<CRLF>
+ store (local type) image<CR>
+ (local pathname) file dump<CR> User-FTP opens local file in Image.
+ (for.pathname) >udd>cn>fd<CR> STOR >udd>cn>fd<CRLF> ---->
+ <---- 550 Access denied<CRLF>
+ terminate QUIT <CRLF> ---->
+ Server closes all
+ connections.
+
+8. CONNECTION ESTABLISHMENT
+
+ The FTP control connection is established via TCP between the user
+ process port U and the server process port L. This protocol is
+ assigned the service port 21 (25 octal), that is L=21.
+
+
+
+Postel & Reynolds [Page 59]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+APPENDIX I - PAGE STRUCTURE
+
+ The need for FTP to support page structure derives principally from
+ the need to support efficient transmission of files between TOPS-20
+ systems, particularly the files used by NLS.
+
+ The file system of TOPS-20 is based on the concept of pages. The
+ operating system is most efficient at manipulating files as pages.
+ The operating system provides an interface to the file system so that
+ many applications view files as sequential streams of characters.
+ However, a few applications use the underlying page structures
+ directly, and some of these create holey files.
+
+ A TOPS-20 disk file consists of four things: a pathname, a page
+ table, a (possibly empty) set of pages, and a set of attributes.
+
+ The pathname is specified in the RETR or STOR command. It includes
+ the directory name, file name, file name extension, and generation
+ number.
+
+ The page table contains up to 2**18 entries. Each entry may be
+ EMPTY, or may point to a page. If it is not empty, there are also
+ some page-specific access bits; not all pages of a file need have the
+ same access protection.
+
+ A page is a contiguous set of 512 words of 36 bits each.
+
+ The attributes of the file, in the File Descriptor Block (FDB),
+ contain such things as creation time, write time, read time, writer's
+ byte-size, end-of-file pointer, count of reads and writes, backup
+ system tape numbers, etc.
+
+ Note that there is NO requirement that entries in the page table be
+ contiguous. There may be empty page table slots between occupied
+ ones. Also, the end of file pointer is simply a number. There is no
+ requirement that it in fact point at the "last" datum in the file.
+ Ordinary sequential I/O calls in TOPS-20 will cause the end of file
+ pointer to be left after the last datum written, but other operations
+ may cause it not to be so, if a particular programming system so
+ requires.
+
+ In fact, in both of these special cases, "holey" files and
+ end-of-file pointers NOT at the end of the file, occur with NLS data
+ files.
+
+
+
+
+
+Postel & Reynolds [Page 60]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The TOPS-20 paged files can be sent with the FTP transfer parameters:
+ TYPE L 36, STRU P, and MODE S (in fact, any mode could be used).
+
+ Each page of information has a header. Each header field, which is a
+ logical byte, is a TOPS-20 word, since the TYPE is L 36.
+
+ The header fields are:
+
+ Word 0: Header Length.
+
+ The header length is 5.
+
+ Word 1: Page Index.
+
+ If the data is a disk file page, this is the number of that
+ page in the file's page map. Empty pages (holes) in the file
+ are simply not sent. Note that a hole is NOT the same as a
+ page of zeros.
+
+ Word 2: Data Length.
+
+ The number of data words in this page, following the header.
+ Thus, the total length of the transmission unit is the Header
+ Length plus the Data Length.
+
+ Word 3: Page Type.
+
+ A code for what type of chunk this is. A data page is type 3,
+ the FDB page is type 2.
+
+ Word 4: Page Access Control.
+
+ The access bits associated with the page in the file's page
+ map. (This full word quantity is put into AC2 of an SPACS by
+ the program reading from net to disk.)
+
+ After the header are Data Length data words. Data Length is
+ currently either 512 for a data page or 31 for an FDB. Trailing
+ zeros in a disk file page may be discarded, making Data Length less
+ than 512 in that case.
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 61]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+APPENDIX II - DIRECTORY COMMANDS
+
+ Since UNIX has a tree-like directory structure in which directories
+ are as easy to manipulate as ordinary files, it is useful to expand
+ the FTP servers on these machines to include commands which deal with
+ the creation of directories. Since there are other hosts on the
+ ARPA-Internet which have tree-like directories (including TOPS-20 and
+ Multics), these commands are as general as possible.
+
+ Four directory commands have been added to FTP:
+
+ MKD pathname
+
+ Make a directory with the name "pathname".
+
+ RMD pathname
+
+ Remove the directory with the name "pathname".
+
+ PWD
+
+ Print the current working directory name.
+
+ CDUP
+
+ Change to the parent of the current working directory.
+
+ The "pathname" argument should be created (removed) as a
+ subdirectory of the current working directory, unless the "pathname"
+ string contains sufficient information to specify otherwise to the
+ server, e.g., "pathname" is an absolute pathname (in UNIX and
+ Multics), or pathname is something like "<abso.lute.path>" to
+ TOPS-20.
+
+ REPLY CODES
+
+ The CDUP command is a special case of CWD, and is included to
+ simplify the implementation of programs for transferring directory
+ trees between operating systems having different syntaxes for
+ naming the parent directory. The reply codes for CDUP be
+ identical to the reply codes of CWD.
+
+ The reply codes for RMD be identical to the reply codes for its
+ file analogue, DELE.
+
+ The reply codes for MKD, however, are a bit more complicated. A
+ freshly created directory will probably be the object of a future
+
+
+Postel & Reynolds [Page 62]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ CWD command. Unfortunately, the argument to MKD may not always be
+ a suitable argument for CWD. This is the case, for example, when
+ a TOPS-20 subdirectory is created by giving just the subdirectory
+ name. That is, with a TOPS-20 server FTP, the command sequence
+
+ MKD MYDIR
+ CWD MYDIR
+
+ will fail. The new directory may only be referred to by its
+ "absolute" name; e.g., if the MKD command above were issued while
+ connected to the directory <DFRANKLIN>, the new subdirectory
+ could only be referred to by the name <DFRANKLIN.MYDIR>.
+
+ Even on UNIX and Multics, however, the argument given to MKD may
+ not be suitable. If it is a "relative" pathname (i.e., a pathname
+ which is interpreted relative to the current directory), the user
+ would need to be in the same current directory in order to reach
+ the subdirectory. Depending on the application, this may be
+ inconvenient. It is not very robust in any case.
+
+ To solve these problems, upon successful completion of an MKD
+ command, the server should return a line of the form:
+
+ 257<space>"<directory-name>"<space><commentary>
+
+ That is, the server will tell the user what string to use when
+ referring to the created directory. The directory name can
+ contain any character; embedded double-quotes should be escaped by
+ double-quotes (the "quote-doubling" convention).
+
+ For example, a user connects to the directory /usr/dm, and creates
+ a subdirectory, named pathname:
+
+ CWD /usr/dm
+ 200 directory changed to /usr/dm
+ MKD pathname
+ 257 "/usr/dm/pathname" directory created
+
+ An example with an embedded double quote:
+
+ MKD foo"bar
+ 257 "/usr/dm/foo""bar" directory created
+ CWD /usr/dm/foo"bar
+ 200 directory changed to /usr/dm/foo"bar
+
+
+
+
+
+Postel & Reynolds [Page 63]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ The prior existence of a subdirectory with the same name is an
+ error, and the server must return an "access denied" error reply
+ in that case.
+
+ CWD /usr/dm
+ 200 directory changed to /usr/dm
+ MKD pathname
+ 521-"/usr/dm/pathname" directory already exists;
+ 521 taking no action.
+
+ The failure replies for MKD are analogous to its file creating
+ cousin, STOR. Also, an "access denied" return is given if a file
+ name with the same name as the subdirectory will conflict with the
+ creation of the subdirectory (this is a problem on UNIX, but
+ shouldn't be one on TOPS-20).
+
+ Essentially because the PWD command returns the same type of
+ information as the successful MKD command, the successful PWD
+ command uses the 257 reply code as well.
+
+ SUBTLETIES
+
+ Because these commands will be most useful in transferring
+ subtrees from one machine to another, carefully observe that the
+ argument to MKD is to be interpreted as a sub-directory of the
+ current working directory, unless it contains enough information
+ for the destination host to tell otherwise. A hypothetical
+ example of its use in the TOPS-20 world:
+
+ CWD <some.where>
+ 200 Working directory changed
+ MKD overrainbow
+ 257 "<some.where.overrainbow>" directory created
+ CWD overrainbow
+ 431 No such directory
+ CWD <some.where.overrainbow>
+ 200 Working directory changed
+
+ CWD <some.where>
+ 200 Working directory changed to <some.where>
+ MKD <unambiguous>
+ 257 "<unambiguous>" directory created
+ CWD <unambiguous>
+
+ Note that the first example results in a subdirectory of the
+ connected directory. In contrast, the argument in the second
+ example contains enough information for TOPS-20 to tell that the
+
+
+Postel & Reynolds [Page 64]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ <unambiguous> directory is a top-level directory. Note also that
+ in the first example the user "violated" the protocol by
+ attempting to access the freshly created directory with a name
+ other than the one returned by TOPS-20. Problems could have
+ resulted in this case had there been an <overrainbow> directory;
+ this is an ambiguity inherent in some TOPS-20 implementations.
+ Similar considerations apply to the RMD command. The point is
+ this: except where to do so would violate a host's conventions for
+ denoting relative versus absolute pathnames, the host should treat
+ the operands of the MKD and RMD commands as subdirectories. The
+ 257 reply to the MKD command must always contain the absolute
+ pathname of the created directory.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 65]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+APPENDIX III - RFCs on FTP
+
+ Bhushan, Abhay, "A File Transfer Protocol", RFC 114 (NIC 5823),
+ MIT-Project MAC, 16 April 1971.
+
+ Harslem, Eric, and John Heafner, "Comments on RFC 114 (A File
+ Transfer Protocol)", RFC 141 (NIC 6726), RAND, 29 April 1971.
+
+ Bhushan, Abhay, et al, "The File Transfer Protocol", RFC 172
+ (NIC 6794), MIT-Project MAC, 23 June 1971.
+
+ Braden, Bob, "Comments on DTP and FTP Proposals", RFC 238 (NIC 7663),
+ UCLA/CCN, 29 September 1971.
+
+ Bhushan, Abhay, et al, "The File Transfer Protocol", RFC 265
+ (NIC 7813), MIT-Project MAC, 17 November 1971.
+
+ McKenzie, Alex, "A Suggested Addition to File Transfer Protocol",
+ RFC 281 (NIC 8163), BBN, 8 December 1971.
+
+ Bhushan, Abhay, "The Use of "Set Data Type" Transaction in File
+ Transfer Protocol", RFC 294 (NIC 8304), MIT-Project MAC,
+ 25 January 1972.
+
+ Bhushan, Abhay, "The File Transfer Protocol", RFC 354 (NIC 10596),
+ MIT-Project MAC, 8 July 1972.
+
+ Bhushan, Abhay, "Comments on the File Transfer Protocol (RFC 354)",
+ RFC 385 (NIC 11357), MIT-Project MAC, 18 August 1972.
+
+ Hicks, Greg, "User FTP Documentation", RFC 412 (NIC 12404), Utah,
+ 27 November 1972.
+
+ Bhushan, Abhay, "File Transfer Protocol (FTP) Status and Further
+ Comments", RFC 414 (NIC 12406), MIT-Project MAC, 20 November 1972.
+
+ Braden, Bob, "Comments on File Transfer Protocol", RFC 430
+ (NIC 13299), UCLA/CCN, 7 February 1973.
+
+ Thomas, Bob, and Bob Clements, "FTP Server-Server Interaction",
+ RFC 438 (NIC 13770), BBN, 15 January 1973.
+
+ Braden, Bob, "Print Files in FTP", RFC 448 (NIC 13299), UCLA/CCN,
+ 27 February 1973.
+
+ McKenzie, Alex, "File Transfer Protocol", RFC 454 (NIC 14333), BBN,
+ 16 February 1973.
+
+
+Postel & Reynolds [Page 66]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ Bressler, Bob, and Bob Thomas, "Mail Retrieval via FTP", RFC 458
+ (NIC 14378), BBN-NET and BBN-TENEX, 20 February 1973.
+
+ Neigus, Nancy, "File Transfer Protocol", RFC 542 (NIC 17759), BBN,
+ 12 July 1973.
+
+ Krilanovich, Mark, and George Gregg, "Comments on the File Transfer
+ Protocol", RFC 607 (NIC 21255), UCSB, 7 January 1974.
+
+ Pogran, Ken, and Nancy Neigus, "Response to RFC 607 - Comments on the
+ File Transfer Protocol", RFC 614 (NIC 21530), BBN, 28 January 1974.
+
+ Krilanovich, Mark, George Gregg, Wayne Hathaway, and Jim White,
+ "Comments on the File Transfer Protocol", RFC 624 (NIC 22054), UCSB,
+ Ames Research Center, SRI-ARC, 28 February 1974.
+
+ Bhushan, Abhay, "FTP Comments and Response to RFC 430", RFC 463
+ (NIC 14573), MIT-DMCG, 21 February 1973.
+
+ Braden, Bob, "FTP Data Compression", RFC 468 (NIC 14742), UCLA/CCN,
+ 8 March 1973.
+
+ Bhushan, Abhay, "FTP and Network Mail System", RFC 475 (NIC 14919),
+ MIT-DMCG, 6 March 1973.
+
+ Bressler, Bob, and Bob Thomas "FTP Server-Server Interaction - II",
+ RFC 478 (NIC 14947), BBN-NET and BBN-TENEX, 26 March 1973.
+
+ White, Jim, "Use of FTP by the NIC Journal", RFC 479 (NIC 14948),
+ SRI-ARC, 8 March 1973.
+
+ White, Jim, "Host-Dependent FTP Parameters", RFC 480 (NIC 14949),
+ SRI-ARC, 8 March 1973.
+
+ Padlipsky, Mike, "An FTP Command-Naming Problem", RFC 506
+ (NIC 16157), MIT-Multics, 26 June 1973.
+
+ Day, John, "Memo to FTP Group (Proposal for File Access Protocol)",
+ RFC 520 (NIC 16819), Illinois, 25 June 1973.
+
+ Merryman, Robert, "The UCSD-CC Server-FTP Facility", RFC 532
+ (NIC 17451), UCSD-CC, 22 June 1973.
+
+ Braden, Bob, "TENEX FTP Problem", RFC 571 (NIC 18974), UCLA/CCN,
+ 15 November 1973.
+
+
+
+
+Postel & Reynolds [Page 67]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+ McKenzie, Alex, and Jon Postel, "Telnet and FTP Implementation -
+ Schedule Change", RFC 593 (NIC 20615), BBN and MITRE,
+ 29 November 1973.
+
+ Sussman, Julie, "FTP Error Code Usage for More Reliable Mail
+ Service", RFC 630 (NIC 30237), BBN, 10 April 1974.
+
+ Postel, Jon, "Revised FTP Reply Codes", RFC 640 (NIC 30843),
+ UCLA/NMC, 5 June 1974.
+
+ Harvey, Brian, "Leaving Well Enough Alone", RFC 686 (NIC 32481),
+ SU-AI, 10 May 1975.
+
+ Harvey, Brian, "One More Try on the FTP", RFC 691 (NIC 32700), SU-AI,
+ 28 May 1975.
+
+ Lieb, J., "CWD Command of FTP", RFC 697 (NIC 32963), 14 July 1975.
+
+ Harrenstien, Ken, "FTP Extension: XSEN", RFC 737 (NIC 42217), SRI-KL,
+ 31 October 1977.
+
+ Harrenstien, Ken, "FTP Extension: XRSQ/XRCP", RFC 743 (NIC 42758),
+ SRI-KL, 30 December 1977.
+
+ Lebling, P. David, "Survey of FTP Mail and MLFL", RFC 751, MIT,
+ 10 December 1978.
+
+ Postel, Jon, "File Transfer Protocol Specification", RFC 765, ISI,
+ June 1980.
+
+ Mankins, David, Dan Franklin, and Buzz Owen, "Directory Oriented FTP
+ Commands", RFC 776, BBN, December 1980.
+
+ Padlipsky, Michael, "FTP Unique-Named Store Command", RFC 949, MITRE,
+ July 1985.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 68]
+
+
+
+RFC 959 October 1985
+File Transfer Protocol
+
+
+REFERENCES
+
+ [1] Feinler, Elizabeth, "Internet Protocol Transition Workbook",
+ Network Information Center, SRI International, March 1982.
+
+ [2] Postel, Jon, "Transmission Control Protocol - DARPA Internet
+ Program Protocol Specification", RFC 793, DARPA, September 1981.
+
+ [3] Postel, Jon, and Joyce Reynolds, "Telnet Protocol
+ Specification", RFC 854, ISI, May 1983.
+
+ [4] Reynolds, Joyce, and Jon Postel, "Assigned Numbers", RFC 943,
+ ISI, April 1985.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Postel & Reynolds [Page 69]
+
diff --git a/netwerk/protocol/ftp/doc/testdoc b/netwerk/protocol/ftp/doc/testdoc
new file mode 100644
index 000000000..61fda16fc
--- /dev/null
+++ b/netwerk/protocol/ftp/doc/testdoc
@@ -0,0 +1,4 @@
+Test
+here
+there
+everywhere
diff --git a/netwerk/protocol/ftp/ftpCore.h b/netwerk/protocol/ftp/ftpCore.h
new file mode 100644
index 000000000..3f708952a
--- /dev/null
+++ b/netwerk/protocol/ftp/ftpCore.h
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __ftpCore_h___
+#define __ftpCore_h___
+
+#include "nsError.h"
+
+/**
+ * Status nsresult codes
+ */
+
+#endif // __ftpCore_h___
diff --git a/netwerk/protocol/ftp/moz.build b/netwerk/protocol/ftp/moz.build
new file mode 100644
index 000000000..060fb7575
--- /dev/null
+++ b/netwerk/protocol/ftp/moz.build
@@ -0,0 +1,45 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIFTPChannel.idl',
+ 'nsIFTPChannelParentInternal.idl',
+]
+
+XPIDL_MODULE = 'necko_ftp'
+
+EXPORTS += [
+ 'ftpCore.h',
+]
+
+EXPORTS.mozilla.net += [
+ 'FTPChannelChild.h',
+ 'FTPChannelParent.h',
+]
+
+UNIFIED_SOURCES += [
+ 'FTPChannelChild.cpp',
+ 'FTPChannelParent.cpp',
+ 'nsFTPChannel.cpp',
+ 'nsFtpConnectionThread.cpp',
+ 'nsFtpControlConnection.cpp',
+ 'nsFtpProtocolHandler.cpp',
+]
+
+IPDL_SOURCES += [
+ 'PFTPChannel.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/protocol/ftp/nsFTPChannel.cpp b/netwerk/protocol/ftp/nsFTPChannel.cpp
new file mode 100644
index 000000000..2a0f04915
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFTPChannel.cpp
@@ -0,0 +1,294 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sts=4 sw=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsFTPChannel.h"
+#include "nsFtpConnectionThread.h" // defines nsFtpState
+
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+extern LazyLogModule gFTPLog;
+
+// There are two transport connections established for an
+// ftp connection. One is used for the command channel , and
+// the other for the data channel. The command channel is the first
+// connection made and is used to negotiate the second, data, channel.
+// The data channel is driven by the command channel and is either
+// initiated by the server (PORT command) or by the client (PASV command).
+// Client initiation is the most common case and is attempted first.
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFtpChannel,
+ nsBaseChannel,
+ nsIUploadChannel,
+ nsIResumableChannel,
+ nsIFTPChannel,
+ nsIProxiedChannel,
+ nsIForcePendingChannel,
+ nsIChannelWithDivertableParentListener)
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsFtpChannel::SetUploadStream(nsIInputStream *stream,
+ const nsACString &contentType,
+ int64_t contentLength)
+{
+ NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
+
+ mUploadStream = stream;
+
+ // NOTE: contentLength is intentionally ignored here.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::GetUploadStream(nsIInputStream **stream)
+{
+ NS_ENSURE_ARG_POINTER(stream);
+ *stream = mUploadStream;
+ NS_IF_ADDREF(*stream);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsFtpChannel::ResumeAt(uint64_t aStartPos, const nsACString& aEntityID)
+{
+ NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
+ mEntityID = aEntityID;
+ mStartPos = aStartPos;
+ mResumeRequested = (mStartPos || !mEntityID.IsEmpty());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::GetEntityID(nsACString& entityID)
+{
+ if (mEntityID.IsEmpty())
+ return NS_ERROR_NOT_RESUMABLE;
+
+ entityID = mEntityID;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsFtpChannel::GetProxyInfo(nsIProxyInfo** aProxyInfo)
+{
+ *aProxyInfo = ProxyInfo();
+ NS_IF_ADDREF(*aProxyInfo);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult
+nsFtpChannel::OpenContentStream(bool async, nsIInputStream **result,
+ nsIChannel** channel)
+{
+ if (!async)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsFtpState *state = new nsFtpState();
+ if (!state)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(state);
+
+ nsresult rv = state->Init(this);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(state);
+ return rv;
+ }
+
+ *result = state;
+ return NS_OK;
+}
+
+bool
+nsFtpChannel::GetStatusArg(nsresult status, nsString &statusArg)
+{
+ nsAutoCString host;
+ URI()->GetHost(host);
+ CopyUTF8toUTF16(host, statusArg);
+ return true;
+}
+
+void
+nsFtpChannel::OnCallbacksChanged()
+{
+ mFTPEventSink = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+class FTPEventSinkProxy final : public nsIFTPEventSink
+{
+ ~FTPEventSinkProxy() {}
+
+public:
+ explicit FTPEventSinkProxy(nsIFTPEventSink* aTarget)
+ : mTarget(aTarget)
+ , mTargetThread(do_GetCurrentThread())
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIFTPEVENTSINK
+
+ class OnFTPControlLogRunnable : public Runnable
+ {
+ public:
+ OnFTPControlLogRunnable(nsIFTPEventSink* aTarget,
+ bool aServer,
+ const char* aMessage)
+ : mTarget(aTarget)
+ , mServer(aServer)
+ , mMessage(aMessage)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsIFTPEventSink> mTarget;
+ bool mServer;
+ nsCString mMessage;
+ };
+
+private:
+ nsCOMPtr<nsIFTPEventSink> mTarget;
+ nsCOMPtr<nsIThread> mTargetThread;
+};
+
+NS_IMPL_ISUPPORTS(FTPEventSinkProxy, nsIFTPEventSink)
+
+NS_IMETHODIMP
+FTPEventSinkProxy::OnFTPControlLog(bool aServer, const char* aMsg)
+{
+ RefPtr<OnFTPControlLogRunnable> r =
+ new OnFTPControlLogRunnable(mTarget, aServer, aMsg);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+FTPEventSinkProxy::OnFTPControlLogRunnable::Run()
+{
+ mTarget->OnFTPControlLog(mServer, mMessage.get());
+ return NS_OK;
+}
+
+} // namespace
+
+void
+nsFtpChannel::GetFTPEventSink(nsCOMPtr<nsIFTPEventSink> &aResult)
+{
+ if (!mFTPEventSink) {
+ nsCOMPtr<nsIFTPEventSink> ftpSink;
+ GetCallback(ftpSink);
+ if (ftpSink) {
+ mFTPEventSink = new FTPEventSinkProxy(ftpSink);
+ }
+ }
+ aResult = mFTPEventSink;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::ForcePending(bool aForcePending)
+{
+ // Set true here so IsPending will return true.
+ // Required for callback diversion from child back to parent. In such cases
+ // OnStopRequest can be called in the parent before callbacks are diverted
+ // back from the child to the listener in the parent.
+ mForcePending = aForcePending;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::IsPending(bool *result)
+{
+ *result = Pending();
+ return NS_OK;
+}
+
+bool
+nsFtpChannel::Pending() const
+{
+ return nsBaseChannel::Pending() || mForcePending;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::Suspend()
+{
+ LOG(("nsFtpChannel::Suspend [this=%p]\n", this));
+
+ nsresult rv = nsBaseChannel::Suspend();
+
+ nsresult rvParentChannel = NS_OK;
+ if (mParentChannel) {
+ rvParentChannel = mParentChannel->SuspendMessageDiversion();
+ }
+
+ return NS_FAILED(rv) ? rv : rvParentChannel;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::Resume()
+{
+ LOG(("nsFtpChannel::Resume [this=%p]\n", this));
+
+ nsresult rv = nsBaseChannel::Resume();
+
+ nsresult rvParentChannel = NS_OK;
+ if (mParentChannel) {
+ rvParentChannel = mParentChannel->ResumeMessageDiversion();
+ }
+
+ return NS_FAILED(rv) ? rv : rvParentChannel;
+}
+
+//-----------------------------------------------------------------------------
+// AChannelHasDivertableParentChannelAsListener internal functions
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsFtpChannel::MessageDiversionStarted(ADivertableParentChannel *aParentChannel)
+{
+ MOZ_ASSERT(!mParentChannel);
+ mParentChannel = aParentChannel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::MessageDiversionStop()
+{
+ LOG(("nsFtpChannel::MessageDiversionStop [this=%p]", this));
+ MOZ_ASSERT(mParentChannel);
+ mParentChannel = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::SuspendInternal()
+{
+ LOG(("nsFtpChannel::SuspendInternal [this=%p]\n", this));
+
+ return nsBaseChannel::Suspend();
+}
+
+NS_IMETHODIMP
+nsFtpChannel::ResumeInternal()
+{
+ LOG(("nsFtpChannel::ResumeInternal [this=%p]\n", this));
+
+ return nsBaseChannel::Resume();
+}
diff --git a/netwerk/protocol/ftp/nsFTPChannel.h b/netwerk/protocol/ftp/nsFTPChannel.h
new file mode 100644
index 000000000..549e577b3
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFTPChannel.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFTPChannel_h___
+#define nsFTPChannel_h___
+
+#include "nsBaseChannel.h"
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIChannelWithDivertableParentListener.h"
+#include "nsIFTPChannel.h"
+#include "nsIForcePendingChannel.h"
+#include "nsIUploadChannel.h"
+#include "nsIProxyInfo.h"
+#include "nsIProxiedChannel.h"
+#include "nsIResumableChannel.h"
+
+class nsIURI;
+using mozilla::net::ADivertableParentChannel;
+
+class nsFtpChannel final : public nsBaseChannel,
+ public nsIFTPChannel,
+ public nsIUploadChannel,
+ public nsIResumableChannel,
+ public nsIProxiedChannel,
+ public nsIForcePendingChannel,
+ public nsIChannelWithDivertableParentListener
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIUPLOADCHANNEL
+ NS_DECL_NSIRESUMABLECHANNEL
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSICHANNELWITHDIVERTABLEPARENTLISTENER
+
+ nsFtpChannel(nsIURI *uri, nsIProxyInfo *pi)
+ : mProxyInfo(pi)
+ , mStartPos(0)
+ , mResumeRequested(false)
+ , mLastModifiedTime(0)
+ , mForcePending(false)
+ {
+ SetURI(uri);
+ }
+
+ nsIProxyInfo *ProxyInfo() {
+ return mProxyInfo;
+ }
+
+ void SetProxyInfo(nsIProxyInfo *pi)
+ {
+ mProxyInfo = pi;
+ }
+
+ NS_IMETHOD IsPending(bool *result) override;
+
+ // This is a short-cut to calling nsIRequest::IsPending().
+ // Overrides Pending in nsBaseChannel.
+ bool Pending() const override;
+
+ // Were we asked to resume a download?
+ bool ResumeRequested() { return mResumeRequested; }
+
+ // Download from this byte offset
+ uint64_t StartPos() { return mStartPos; }
+
+ // ID of the entity to resume downloading
+ const nsCString &EntityID() {
+ return mEntityID;
+ }
+ void SetEntityID(const nsCSubstring &entityID) {
+ mEntityID = entityID;
+ }
+
+ NS_IMETHOD GetLastModifiedTime(PRTime* lastModifiedTime) override {
+ *lastModifiedTime = mLastModifiedTime;
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetLastModifiedTime(PRTime lastModifiedTime) override {
+ mLastModifiedTime = lastModifiedTime;
+ return NS_OK;
+ }
+
+ // Data stream to upload
+ nsIInputStream *UploadStream() {
+ return mUploadStream;
+ }
+
+ // Helper function for getting the nsIFTPEventSink.
+ void GetFTPEventSink(nsCOMPtr<nsIFTPEventSink> &aResult);
+
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+
+public:
+ NS_IMETHOD ForcePending(bool aForcePending) override;
+
+protected:
+ virtual ~nsFtpChannel() {}
+ virtual nsresult OpenContentStream(bool async, nsIInputStream **result,
+ nsIChannel** channel) override;
+ virtual bool GetStatusArg(nsresult status, nsString &statusArg) override;
+ virtual void OnCallbacksChanged() override;
+
+private:
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+ nsCOMPtr<nsIFTPEventSink> mFTPEventSink;
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ uint64_t mStartPos;
+ nsCString mEntityID;
+ bool mResumeRequested;
+ PRTime mLastModifiedTime;
+ bool mForcePending;
+ RefPtr<ADivertableParentChannel> mParentChannel;
+};
+
+#endif /* nsFTPChannel_h___ */
diff --git a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp
new file mode 100644
index 000000000..d428b093c
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp
@@ -0,0 +1,2256 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set tw=80 ts=4 sts=4 sw=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <ctype.h>
+
+#include "prprf.h"
+#include "mozilla/Logging.h"
+#include "prtime.h"
+
+#include "nsIOService.h"
+#include "nsFTPChannel.h"
+#include "nsFtpConnectionThread.h"
+#include "nsFtpControlConnection.h"
+#include "nsFtpProtocolHandler.h"
+#include "netCore.h"
+#include "nsCRT.h"
+#include "nsEscape.h"
+#include "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIAsyncStreamCopier.h"
+#include "nsThreadUtils.h"
+#include "nsStreamUtils.h"
+#include "nsIURL.h"
+#include "nsISocketTransport.h"
+#include "nsIStreamListenerTee.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIStringBundle.h"
+#include "nsAuthInformationHolder.h"
+#include "nsIProtocolProxyService.h"
+#include "nsICancelable.h"
+#include "nsIOutputStream.h"
+#include "nsIPrompt.h"
+#include "nsIProtocolHandler.h"
+#include "nsIProxyInfo.h"
+#include "nsIRunnable.h"
+#include "nsISocketTransportService.h"
+#include "nsIURI.h"
+#include "nsILoadInfo.h"
+#include "nsNullPrincipal.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIFTPChannelParentInternal.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "NetStatistics.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+extern LazyLogModule gFTPLog;
+#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
+#define LOG_INFO(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Info, args)
+
+// remove FTP parameters (starting with ";") from the path
+static void
+removeParamsFromPath(nsCString& path)
+{
+ int32_t index = path.FindChar(';');
+ if (index >= 0) {
+ path.SetLength(index);
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFtpState,
+ nsBaseContentStream,
+ nsIInputStreamCallback,
+ nsITransportEventSink,
+ nsIRequestObserver,
+ nsIProtocolProxyCallback)
+
+nsFtpState::nsFtpState()
+ : nsBaseContentStream(true)
+ , mState(FTP_INIT)
+ , mNextState(FTP_S_USER)
+ , mKeepRunning(true)
+ , mReceivedControlData(false)
+ , mTryingCachedControl(false)
+ , mRETRFailed(false)
+ , mFileSize(kJS_MAX_SAFE_UINTEGER)
+ , mServerType(FTP_GENERIC_TYPE)
+ , mAction(GET)
+ , mAnonymous(true)
+ , mRetryPass(false)
+ , mStorReplyReceived(false)
+ , mInternalError(NS_OK)
+ , mReconnectAndLoginAgain(false)
+ , mCacheConnection(true)
+ , mPort(21)
+ , mAddressChecked(false)
+ , mServerIsIPv6(false)
+ , mUseUTF8(false)
+ , mControlStatus(NS_OK)
+ , mDeferredCallbackPending(false)
+{
+ LOG_INFO(("FTP:(%x) nsFtpState created", this));
+
+ // make sure handler stays around
+ NS_ADDREF(gFtpHandler);
+}
+
+nsFtpState::~nsFtpState()
+{
+ LOG_INFO(("FTP:(%x) nsFtpState destroyed", this));
+
+ if (mProxyRequest)
+ mProxyRequest->Cancel(NS_ERROR_FAILURE);
+
+ // release reference to handler
+ nsFtpProtocolHandler *handler = gFtpHandler;
+ NS_RELEASE(handler);
+}
+
+// nsIInputStreamCallback implementation
+NS_IMETHODIMP
+nsFtpState::OnInputStreamReady(nsIAsyncInputStream *aInStream)
+{
+ LOG(("FTP:(%p) data stream ready\n", this));
+
+ // We are receiving a notification from our data stream, so just forward it
+ // on to our stream callback.
+ if (HasPendingCallback())
+ DispatchCallbackSync();
+
+ return NS_OK;
+}
+
+void
+nsFtpState::OnControlDataAvailable(const char *aData, uint32_t aDataLen)
+{
+ LOG(("FTP:(%p) control data available [%u]\n", this, aDataLen));
+ mControlConnection->WaitData(this); // queue up another call
+
+ if (!mReceivedControlData) {
+ // parameter can be null cause the channel fills them in.
+ OnTransportStatus(nullptr, NS_NET_STATUS_BEGIN_FTP_TRANSACTION, 0, 0);
+ mReceivedControlData = true;
+ }
+
+ // Sometimes we can get two responses in the same packet, eg from LIST.
+ // So we need to parse the response line by line
+
+ nsCString buffer = mControlReadCarryOverBuf;
+
+ // Clear the carryover buf - if we still don't have a line, then it will
+ // be reappended below
+ mControlReadCarryOverBuf.Truncate();
+
+ buffer.Append(aData, aDataLen);
+
+ const char* currLine = buffer.get();
+ while (*currLine && mKeepRunning) {
+ int32_t eolLength = strcspn(currLine, CRLF);
+ int32_t currLineLength = strlen(currLine);
+
+ // if currLine is empty or only contains CR or LF, then bail. we can
+ // sometimes get an ODA event with the full response line + CR without
+ // the trailing LF. the trailing LF might come in the next ODA event.
+ // because we are happy enough to process a response line ending only
+ // in CR, we need to take care to discard the extra LF (bug 191220).
+ if (eolLength == 0 && currLineLength <= 1)
+ break;
+
+ if (eolLength == currLineLength) {
+ mControlReadCarryOverBuf.Assign(currLine);
+ break;
+ }
+
+ // Append the current segment, including the LF
+ nsAutoCString line;
+ int32_t crlfLength = 0;
+
+ if ((currLineLength > eolLength) &&
+ (currLine[eolLength] == nsCRT::CR) &&
+ (currLine[eolLength+1] == nsCRT::LF)) {
+ crlfLength = 2; // CR +LF
+ } else {
+ crlfLength = 1; // + LF or CR
+ }
+
+ line.Assign(currLine, eolLength + crlfLength);
+
+ // Does this start with a response code?
+ bool startNum = (line.Length() >= 3 &&
+ isdigit(line[0]) &&
+ isdigit(line[1]) &&
+ isdigit(line[2]));
+
+ if (mResponseMsg.IsEmpty()) {
+ // If we get here, then we know that we have a complete line, and
+ // that it is the first one
+
+ NS_ASSERTION(line.Length() > 4 && startNum,
+ "Read buffer doesn't include response code");
+
+ mResponseCode = atoi(PromiseFlatCString(Substring(line,0,3)).get());
+ }
+
+ mResponseMsg.Append(line);
+
+ // This is the last line if its 3 numbers followed by a space
+ if (startNum && line[3] == ' ') {
+ // yup. last line, let's move on.
+ if (mState == mNextState) {
+ NS_ERROR("ftp read state mixup");
+ mInternalError = NS_ERROR_FAILURE;
+ mState = FTP_ERROR;
+ } else {
+ mState = mNextState;
+ }
+
+ nsCOMPtr<nsIFTPEventSink> ftpSink;
+ mChannel->GetFTPEventSink(ftpSink);
+ if (ftpSink)
+ ftpSink->OnFTPControlLog(true, mResponseMsg.get());
+
+ nsresult rv = Process();
+ mResponseMsg.Truncate();
+ if (NS_FAILED(rv)) {
+ CloseWithStatus(rv);
+ return;
+ }
+ }
+
+ currLine = currLine + eolLength + crlfLength;
+ }
+}
+
+void
+nsFtpState::OnControlError(nsresult status)
+{
+ NS_ASSERTION(NS_FAILED(status), "expecting error condition");
+
+ LOG(("FTP:(%p) CC(%p) error [%x was-cached=%u]\n",
+ this, mControlConnection.get(), status, mTryingCachedControl));
+
+ mControlStatus = status;
+ if (mReconnectAndLoginAgain && NS_SUCCEEDED(mInternalError)) {
+ mReconnectAndLoginAgain = false;
+ mAnonymous = false;
+ mControlStatus = NS_OK;
+ Connect();
+ } else if (mTryingCachedControl && NS_SUCCEEDED(mInternalError)) {
+ mTryingCachedControl = false;
+ Connect();
+ } else {
+ CloseWithStatus(status);
+ }
+}
+
+nsresult
+nsFtpState::EstablishControlConnection()
+{
+ NS_ASSERTION(!mControlConnection, "we already have a control connection");
+
+ nsresult rv;
+
+ LOG(("FTP:(%x) trying cached control\n", this));
+
+ // Look to see if we can use a cached control connection:
+ RefPtr<nsFtpControlConnection> connection;
+ // Don't use cached control if anonymous (bug #473371)
+ if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
+ gFtpHandler->RemoveConnection(mChannel->URI(), getter_AddRefs(connection));
+
+ if (connection) {
+ mControlConnection.swap(connection);
+ if (mControlConnection->IsAlive())
+ {
+ // set stream listener of the control connection to be us.
+ mControlConnection->WaitData(this);
+
+ // read cached variables into us.
+ mServerType = mControlConnection->mServerType;
+ mPassword = mControlConnection->mPassword;
+ mPwd = mControlConnection->mPwd;
+ mUseUTF8 = mControlConnection->mUseUTF8;
+ mTryingCachedControl = true;
+
+ // we have to set charset to connection if server supports utf-8
+ if (mUseUTF8)
+ mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
+
+ // we're already connected to this server, skip login.
+ mState = FTP_S_PASV;
+ mResponseCode = 530; // assume the control connection was dropped.
+ mControlStatus = NS_OK;
+ mReceivedControlData = false; // For this request, we have not.
+
+ // if we succeed, return. Otherwise, we need to create a transport
+ rv = mControlConnection->Connect(mChannel->ProxyInfo(), this);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ }
+ LOG(("FTP:(%p) cached CC(%p) is unusable\n", this,
+ mControlConnection.get()));
+
+ mControlConnection->WaitData(nullptr);
+ mControlConnection = nullptr;
+ }
+
+ LOG(("FTP:(%p) creating CC\n", this));
+
+ mState = FTP_READ_BUF;
+ mNextState = FTP_S_USER;
+
+ nsAutoCString host;
+ rv = mChannel->URI()->GetAsciiHost(host);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mControlConnection = new nsFtpControlConnection(host, mPort);
+ if (!mControlConnection)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = mControlConnection->Connect(mChannel->ProxyInfo(), this);
+ if (NS_FAILED(rv)) {
+ LOG(("FTP:(%p) CC(%p) failed to connect [rv=%x]\n", this,
+ mControlConnection.get(), rv));
+ mControlConnection = nullptr;
+ return rv;
+ }
+
+ return mControlConnection->WaitData(this);
+}
+
+void
+nsFtpState::MoveToNextState(FTP_STATE nextState)
+{
+ if (NS_FAILED(mInternalError)) {
+ mState = FTP_ERROR;
+ LOG(("FTP:(%x) FAILED (%x)\n", this, mInternalError));
+ } else {
+ mState = FTP_READ_BUF;
+ mNextState = nextState;
+ }
+}
+
+nsresult
+nsFtpState::Process()
+{
+ nsresult rv = NS_OK;
+ bool processingRead = true;
+
+ while (mKeepRunning && processingRead) {
+ switch (mState) {
+ case FTP_COMMAND_CONNECT:
+ KillControlConnection();
+ LOG(("FTP:(%p) establishing CC", this));
+ mInternalError = EstablishControlConnection(); // sets mState
+ if (NS_FAILED(mInternalError)) {
+ mState = FTP_ERROR;
+ LOG(("FTP:(%p) FAILED\n", this));
+ } else {
+ LOG(("FTP:(%p) SUCCEEDED\n", this));
+ }
+ break;
+
+ case FTP_READ_BUF:
+ LOG(("FTP:(%p) Waiting for CC(%p)\n", this,
+ mControlConnection.get()));
+ processingRead = false;
+ break;
+
+ case FTP_ERROR: // xx needs more work to handle dropped control connection cases
+ if ((mTryingCachedControl && mResponseCode == 530 &&
+ mInternalError == NS_ERROR_FTP_PASV) ||
+ (mResponseCode == 425 &&
+ mInternalError == NS_ERROR_FTP_PASV)) {
+ // The user was logged out during an pasv operation
+ // we want to restart this request with a new control
+ // channel.
+ mState = FTP_COMMAND_CONNECT;
+ } else if (mResponseCode == 421 &&
+ mInternalError != NS_ERROR_FTP_LOGIN) {
+ // The command channel dropped for some reason.
+ // Fire it back up, unless we were trying to login
+ // in which case the server might just be telling us
+ // that the max number of users has been reached...
+ mState = FTP_COMMAND_CONNECT;
+ } else if (mAnonymous &&
+ mInternalError == NS_ERROR_FTP_LOGIN) {
+ // If the login was anonymous, and it failed, try again with a username
+ // Don't reuse old control connection, see #386167
+ mAnonymous = false;
+ mState = FTP_COMMAND_CONNECT;
+ } else {
+ LOG(("FTP:(%x) FTP_ERROR - calling StopProcessing\n", this));
+ rv = StopProcessing();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
+ processingRead = false;
+ }
+ break;
+
+ case FTP_COMPLETE:
+ LOG(("FTP:(%x) COMPLETE\n", this));
+ rv = StopProcessing();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
+ processingRead = false;
+ break;
+
+// USER
+ case FTP_S_USER:
+ rv = S_user();
+
+ if (NS_FAILED(rv))
+ mInternalError = NS_ERROR_FTP_LOGIN;
+
+ MoveToNextState(FTP_R_USER);
+ break;
+
+ case FTP_R_USER:
+ mState = R_user();
+
+ if (FTP_ERROR == mState)
+ mInternalError = NS_ERROR_FTP_LOGIN;
+
+ break;
+// PASS
+ case FTP_S_PASS:
+ rv = S_pass();
+
+ if (NS_FAILED(rv))
+ mInternalError = NS_ERROR_FTP_LOGIN;
+
+ MoveToNextState(FTP_R_PASS);
+ break;
+
+ case FTP_R_PASS:
+ mState = R_pass();
+
+ if (FTP_ERROR == mState)
+ mInternalError = NS_ERROR_FTP_LOGIN;
+
+ break;
+// ACCT
+ case FTP_S_ACCT:
+ rv = S_acct();
+
+ if (NS_FAILED(rv))
+ mInternalError = NS_ERROR_FTP_LOGIN;
+
+ MoveToNextState(FTP_R_ACCT);
+ break;
+
+ case FTP_R_ACCT:
+ mState = R_acct();
+
+ if (FTP_ERROR == mState)
+ mInternalError = NS_ERROR_FTP_LOGIN;
+
+ break;
+
+// SYST
+ case FTP_S_SYST:
+ rv = S_syst();
+
+ if (NS_FAILED(rv))
+ mInternalError = NS_ERROR_FTP_LOGIN;
+
+ MoveToNextState(FTP_R_SYST);
+ break;
+
+ case FTP_R_SYST:
+ mState = R_syst();
+
+ if (FTP_ERROR == mState)
+ mInternalError = NS_ERROR_FTP_LOGIN;
+
+ break;
+
+// TYPE
+ case FTP_S_TYPE:
+ rv = S_type();
+
+ if (NS_FAILED(rv))
+ mInternalError = rv;
+
+ MoveToNextState(FTP_R_TYPE);
+ break;
+
+ case FTP_R_TYPE:
+ mState = R_type();
+
+ if (FTP_ERROR == mState)
+ mInternalError = NS_ERROR_FAILURE;
+
+ break;
+// CWD
+ case FTP_S_CWD:
+ rv = S_cwd();
+
+ if (NS_FAILED(rv))
+ mInternalError = NS_ERROR_FTP_CWD;
+
+ MoveToNextState(FTP_R_CWD);
+ break;
+
+ case FTP_R_CWD:
+ mState = R_cwd();
+
+ if (FTP_ERROR == mState)
+ mInternalError = NS_ERROR_FTP_CWD;
+ break;
+
+// LIST
+ case FTP_S_LIST:
+ rv = S_list();
+
+ if (rv == NS_ERROR_NOT_RESUMABLE) {
+ mInternalError = rv;
+ } else if (NS_FAILED(rv)) {
+ mInternalError = NS_ERROR_FTP_CWD;
+ }
+
+ MoveToNextState(FTP_R_LIST);
+ break;
+
+ case FTP_R_LIST:
+ mState = R_list();
+
+ if (FTP_ERROR == mState)
+ mInternalError = NS_ERROR_FAILURE;
+
+ break;
+
+// SIZE
+ case FTP_S_SIZE:
+ rv = S_size();
+
+ if (NS_FAILED(rv))
+ mInternalError = rv;
+
+ MoveToNextState(FTP_R_SIZE);
+ break;
+
+ case FTP_R_SIZE:
+ mState = R_size();
+
+ if (FTP_ERROR == mState)
+ mInternalError = NS_ERROR_FAILURE;
+
+ break;
+
+// REST
+ case FTP_S_REST:
+ rv = S_rest();
+
+ if (NS_FAILED(rv))
+ mInternalError = rv;
+
+ MoveToNextState(FTP_R_REST);
+ break;
+
+ case FTP_R_REST:
+ mState = R_rest();
+
+ if (FTP_ERROR == mState)
+ mInternalError = NS_ERROR_FAILURE;
+
+ break;
+
+// MDTM
+ case FTP_S_MDTM:
+ rv = S_mdtm();
+ if (NS_FAILED(rv))
+ mInternalError = rv;
+ MoveToNextState(FTP_R_MDTM);
+ break;
+
+ case FTP_R_MDTM:
+ mState = R_mdtm();
+
+ // Don't want to overwrite a more explicit status code
+ if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
+ mInternalError = NS_ERROR_FAILURE;
+
+ break;
+
+// RETR
+ case FTP_S_RETR:
+ rv = S_retr();
+
+ if (NS_FAILED(rv))
+ mInternalError = rv;
+
+ MoveToNextState(FTP_R_RETR);
+ break;
+
+ case FTP_R_RETR:
+
+ mState = R_retr();
+
+ if (FTP_ERROR == mState)
+ mInternalError = NS_ERROR_FAILURE;
+
+ break;
+
+// STOR
+ case FTP_S_STOR:
+ rv = S_stor();
+
+ if (NS_FAILED(rv))
+ mInternalError = rv;
+
+ MoveToNextState(FTP_R_STOR);
+ break;
+
+ case FTP_R_STOR:
+ mState = R_stor();
+
+ if (FTP_ERROR == mState)
+ mInternalError = NS_ERROR_FAILURE;
+
+ break;
+
+// PASV
+ case FTP_S_PASV:
+ rv = S_pasv();
+
+ if (NS_FAILED(rv))
+ mInternalError = NS_ERROR_FTP_PASV;
+
+ MoveToNextState(FTP_R_PASV);
+ break;
+
+ case FTP_R_PASV:
+ mState = R_pasv();
+
+ if (FTP_ERROR == mState)
+ mInternalError = NS_ERROR_FTP_PASV;
+
+ break;
+
+// PWD
+ case FTP_S_PWD:
+ rv = S_pwd();
+
+ if (NS_FAILED(rv))
+ mInternalError = NS_ERROR_FTP_PWD;
+
+ MoveToNextState(FTP_R_PWD);
+ break;
+
+ case FTP_R_PWD:
+ mState = R_pwd();
+
+ if (FTP_ERROR == mState)
+ mInternalError = NS_ERROR_FTP_PWD;
+
+ break;
+
+// FEAT for RFC2640 support
+ case FTP_S_FEAT:
+ rv = S_feat();
+
+ if (NS_FAILED(rv))
+ mInternalError = rv;
+
+ MoveToNextState(FTP_R_FEAT);
+ break;
+
+ case FTP_R_FEAT:
+ mState = R_feat();
+
+ // Don't want to overwrite a more explicit status code
+ if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
+ mInternalError = NS_ERROR_FAILURE;
+ break;
+
+// OPTS for some non-RFC2640-compliant servers support
+ case FTP_S_OPTS:
+ rv = S_opts();
+
+ if (NS_FAILED(rv))
+ mInternalError = rv;
+
+ MoveToNextState(FTP_R_OPTS);
+ break;
+
+ case FTP_R_OPTS:
+ mState = R_opts();
+
+ // Don't want to overwrite a more explicit status code
+ if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
+ mInternalError = NS_ERROR_FAILURE;
+ break;
+
+ default:
+ ;
+
+ }
+ }
+
+ return rv;
+}
+
+///////////////////////////////////
+// STATE METHODS
+///////////////////////////////////
+nsresult
+nsFtpState::S_user() {
+ // some servers on connect send us a 421 or 521. (84525) (141784)
+ if ((mResponseCode == 421) || (mResponseCode == 521))
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsAutoCString usernameStr("USER ");
+
+ mResponseMsg = "";
+
+ if (mAnonymous) {
+ mReconnectAndLoginAgain = true;
+ usernameStr.AppendLiteral("anonymous");
+ } else {
+ mReconnectAndLoginAgain = false;
+ if (mUsername.IsEmpty()) {
+
+ // No prompt for anonymous requests (bug #473371)
+ if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAuthPrompt2> prompter;
+ NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel),
+ getter_AddRefs(prompter));
+ if (!prompter)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ RefPtr<nsAuthInformationHolder> info =
+ new nsAuthInformationHolder(nsIAuthInformation::AUTH_HOST,
+ EmptyString(),
+ EmptyCString());
+
+ bool retval;
+ rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE,
+ info, &retval);
+
+ // if the user canceled or didn't supply a username we want to fail
+ if (NS_FAILED(rv) || !retval || info->User().IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ mUsername = info->User();
+ mPassword = info->Password();
+ }
+ // XXX Is UTF-8 the best choice?
+ AppendUTF16toUTF8(mUsername, usernameStr);
+ }
+ usernameStr.Append(CRLF);
+
+ return SendFTPCommand(usernameStr);
+}
+
+FTP_STATE
+nsFtpState::R_user() {
+ mReconnectAndLoginAgain = false;
+ if (mResponseCode/100 == 3) {
+ // send off the password
+ return FTP_S_PASS;
+ }
+ if (mResponseCode/100 == 2) {
+ // no password required, we're already logged in
+ return FTP_S_SYST;
+ }
+ if (mResponseCode/100 == 5) {
+ // problem logging in. typically this means the server
+ // has reached it's user limit.
+ return FTP_ERROR;
+ }
+ // LOGIN FAILED
+ return FTP_ERROR;
+}
+
+
+nsresult
+nsFtpState::S_pass() {
+ nsresult rv;
+ nsAutoCString passwordStr("PASS ");
+
+ mResponseMsg = "";
+
+ if (mAnonymous) {
+ if (!mPassword.IsEmpty()) {
+ // XXX Is UTF-8 the best choice?
+ AppendUTF16toUTF8(mPassword, passwordStr);
+ } else {
+ nsXPIDLCString anonPassword;
+ bool useRealEmail = false;
+ nsCOMPtr<nsIPrefBranch> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ rv = prefs->GetBoolPref("advanced.mailftp", &useRealEmail);
+ if (NS_SUCCEEDED(rv) && useRealEmail) {
+ prefs->GetCharPref("network.ftp.anonymous_password",
+ getter_Copies(anonPassword));
+ }
+ }
+ if (!anonPassword.IsEmpty()) {
+ passwordStr.AppendASCII(anonPassword);
+ } else {
+ // We need to default to a valid email address - bug 101027
+ // example.com is reserved (rfc2606), so use that
+ passwordStr.AppendLiteral("mozilla@example.com");
+ }
+ }
+ } else {
+ if (mPassword.IsEmpty() || mRetryPass) {
+
+ // No prompt for anonymous requests (bug #473371)
+ if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAuthPrompt2> prompter;
+ NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel),
+ getter_AddRefs(prompter));
+ if (!prompter)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ RefPtr<nsAuthInformationHolder> info =
+ new nsAuthInformationHolder(nsIAuthInformation::AUTH_HOST |
+ nsIAuthInformation::ONLY_PASSWORD,
+ EmptyString(),
+ EmptyCString());
+
+ info->SetUserInternal(mUsername);
+
+ bool retval;
+ rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE,
+ info, &retval);
+
+ // we want to fail if the user canceled. Note here that if they want
+ // a blank password, we will pass it along.
+ if (NS_FAILED(rv) || !retval)
+ return NS_ERROR_FAILURE;
+
+ mPassword = info->Password();
+ }
+ // XXX Is UTF-8 the best choice?
+ AppendUTF16toUTF8(mPassword, passwordStr);
+ }
+ passwordStr.Append(CRLF);
+
+ return SendFTPCommand(passwordStr);
+}
+
+FTP_STATE
+nsFtpState::R_pass() {
+ if (mResponseCode/100 == 3) {
+ // send account info
+ return FTP_S_ACCT;
+ }
+ if (mResponseCode/100 == 2) {
+ // logged in
+ return FTP_S_SYST;
+ }
+ if (mResponseCode == 503) {
+ // start over w/ the user command.
+ // note: the password was successful, and it's stored in mPassword
+ mRetryPass = false;
+ return FTP_S_USER;
+ }
+ if (mResponseCode/100 == 5 || mResponseCode==421) {
+ // There is no difference between a too-many-users error,
+ // a wrong-password error, or any other sort of error
+
+ if (!mAnonymous)
+ mRetryPass = true;
+
+ return FTP_ERROR;
+ }
+ // unexpected response code
+ return FTP_ERROR;
+}
+
+nsresult
+nsFtpState::S_pwd() {
+ return SendFTPCommand(NS_LITERAL_CSTRING("PWD" CRLF));
+}
+
+FTP_STATE
+nsFtpState::R_pwd() {
+ // Error response to PWD command isn't fatal, but don't cache the connection
+ // if CWD command is sent since correct mPwd is needed for further requests.
+ if (mResponseCode/100 != 2)
+ return FTP_S_TYPE;
+
+ nsAutoCString respStr(mResponseMsg);
+ int32_t pos = respStr.FindChar('"');
+ if (pos > -1) {
+ respStr.Cut(0, pos+1);
+ pos = respStr.FindChar('"');
+ if (pos > -1) {
+ respStr.Truncate(pos);
+ if (mServerType == FTP_VMS_TYPE)
+ ConvertDirspecFromVMS(respStr);
+ if (respStr.IsEmpty() || respStr.Last() != '/')
+ respStr.Append('/');
+ mPwd = respStr;
+ }
+ }
+ return FTP_S_TYPE;
+}
+
+nsresult
+nsFtpState::S_syst() {
+ return SendFTPCommand(NS_LITERAL_CSTRING("SYST" CRLF));
+}
+
+FTP_STATE
+nsFtpState::R_syst() {
+ if (mResponseCode/100 == 2) {
+ if (( mResponseMsg.Find("L8") > -1) ||
+ ( mResponseMsg.Find("UNIX") > -1) ||
+ ( mResponseMsg.Find("BSD") > -1) ||
+ ( mResponseMsg.Find("MACOS Peter's Server") > -1) ||
+ ( mResponseMsg.Find("MACOS WebSTAR FTP") > -1) ||
+ ( mResponseMsg.Find("MVS") > -1) ||
+ ( mResponseMsg.Find("OS/390") > -1) ||
+ ( mResponseMsg.Find("OS/400") > -1)) {
+ mServerType = FTP_UNIX_TYPE;
+ } else if (( mResponseMsg.Find("WIN32", true) > -1) ||
+ ( mResponseMsg.Find("windows", true) > -1)) {
+ mServerType = FTP_NT_TYPE;
+ } else if (mResponseMsg.Find("OS/2", true) > -1) {
+ mServerType = FTP_OS2_TYPE;
+ } else if (mResponseMsg.Find("VMS", true) > -1) {
+ mServerType = FTP_VMS_TYPE;
+ } else {
+ NS_ERROR("Server type list format unrecognized.");
+ // Guessing causes crashes.
+ // (Of course, the parsing code should be more robust...)
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ if (!bundleService)
+ return FTP_ERROR;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = bundleService->CreateBundle(NECKO_MSGS_URL,
+ getter_AddRefs(bundle));
+ if (NS_FAILED(rv))
+ return FTP_ERROR;
+
+ char16_t* ucs2Response = ToNewUnicode(mResponseMsg);
+ const char16_t *formatStrings[1] = { ucs2Response };
+ NS_NAMED_LITERAL_STRING(name, "UnsupportedFTPServer");
+
+ nsXPIDLString formattedString;
+ rv = bundle->FormatStringFromName(name.get(), formatStrings, 1,
+ getter_Copies(formattedString));
+ free(ucs2Response);
+ if (NS_FAILED(rv))
+ return FTP_ERROR;
+
+ // TODO(darin): this code should not be dictating UI like this!
+ nsCOMPtr<nsIPrompt> prompter;
+ mChannel->GetCallback(prompter);
+ if (prompter)
+ prompter->Alert(nullptr, formattedString.get());
+
+ // since we just alerted the user, clear mResponseMsg,
+ // which is displayed to the user.
+ mResponseMsg = "";
+ return FTP_ERROR;
+ }
+
+ return FTP_S_FEAT;
+ }
+
+ if (mResponseCode/100 == 5) {
+ // server didn't like the SYST command. Probably (500, 501, 502)
+ // No clue. We will just hope it is UNIX type server.
+ mServerType = FTP_UNIX_TYPE;
+
+ return FTP_S_FEAT;
+ }
+ return FTP_ERROR;
+}
+
+nsresult
+nsFtpState::S_acct() {
+ return SendFTPCommand(NS_LITERAL_CSTRING("ACCT noaccount" CRLF));
+}
+
+FTP_STATE
+nsFtpState::R_acct() {
+ if (mResponseCode/100 == 2)
+ return FTP_S_SYST;
+
+ return FTP_ERROR;
+}
+
+nsresult
+nsFtpState::S_type() {
+ return SendFTPCommand(NS_LITERAL_CSTRING("TYPE I" CRLF));
+}
+
+FTP_STATE
+nsFtpState::R_type() {
+ if (mResponseCode/100 != 2)
+ return FTP_ERROR;
+
+ return FTP_S_PASV;
+}
+
+nsresult
+nsFtpState::S_cwd() {
+ // Don't cache the connection if PWD command failed
+ if (mPwd.IsEmpty())
+ mCacheConnection = false;
+
+ nsAutoCString cwdStr;
+ if (mAction != PUT)
+ cwdStr = mPath;
+ if (cwdStr.IsEmpty() || cwdStr.First() != '/')
+ cwdStr.Insert(mPwd,0);
+ if (mServerType == FTP_VMS_TYPE)
+ ConvertDirspecToVMS(cwdStr);
+ cwdStr.Insert("CWD ",0);
+ cwdStr.Append(CRLF);
+
+ return SendFTPCommand(cwdStr);
+}
+
+FTP_STATE
+nsFtpState::R_cwd() {
+ if (mResponseCode/100 == 2) {
+ if (mAction == PUT)
+ return FTP_S_STOR;
+
+ return FTP_S_LIST;
+ }
+
+ return FTP_ERROR;
+}
+
+nsresult
+nsFtpState::S_size() {
+ nsAutoCString sizeBuf(mPath);
+ if (sizeBuf.IsEmpty() || sizeBuf.First() != '/')
+ sizeBuf.Insert(mPwd,0);
+ if (mServerType == FTP_VMS_TYPE)
+ ConvertFilespecToVMS(sizeBuf);
+ sizeBuf.Insert("SIZE ",0);
+ sizeBuf.Append(CRLF);
+
+ return SendFTPCommand(sizeBuf);
+}
+
+FTP_STATE
+nsFtpState::R_size() {
+ if (mResponseCode/100 == 2) {
+ PR_sscanf(mResponseMsg.get() + 4, "%llu", &mFileSize);
+ mChannel->SetContentLength(mFileSize);
+ }
+
+ // We may want to be able to resume this
+ return FTP_S_MDTM;
+}
+
+nsresult
+nsFtpState::S_mdtm() {
+ nsAutoCString mdtmBuf(mPath);
+ if (mdtmBuf.IsEmpty() || mdtmBuf.First() != '/')
+ mdtmBuf.Insert(mPwd,0);
+ if (mServerType == FTP_VMS_TYPE)
+ ConvertFilespecToVMS(mdtmBuf);
+ mdtmBuf.Insert("MDTM ",0);
+ mdtmBuf.Append(CRLF);
+
+ return SendFTPCommand(mdtmBuf);
+}
+
+FTP_STATE
+nsFtpState::R_mdtm() {
+ if (mResponseCode == 213) {
+ mResponseMsg.Cut(0,4);
+ mResponseMsg.Trim(" \t\r\n");
+ // yyyymmddhhmmss
+ if (mResponseMsg.Length() != 14) {
+ NS_ASSERTION(mResponseMsg.Length() == 14, "Unknown MDTM response");
+ } else {
+ mModTime = mResponseMsg;
+
+ // Save lastModified time for downloaded files.
+ nsAutoCString timeString;
+ nsresult error;
+ PRExplodedTime exTime;
+
+ mResponseMsg.Mid(timeString, 0, 4);
+ exTime.tm_year = timeString.ToInteger(&error);
+ mResponseMsg.Mid(timeString, 4, 2);
+ exTime.tm_month = timeString.ToInteger(&error) - 1; //january = 0
+ mResponseMsg.Mid(timeString, 6, 2);
+ exTime.tm_mday = timeString.ToInteger(&error);
+ mResponseMsg.Mid(timeString, 8, 2);
+ exTime.tm_hour = timeString.ToInteger(&error);
+ mResponseMsg.Mid(timeString, 10, 2);
+ exTime.tm_min = timeString.ToInteger(&error);
+ mResponseMsg.Mid(timeString, 12, 2);
+ exTime.tm_sec = timeString.ToInteger(&error);
+ exTime.tm_usec = 0;
+
+ exTime.tm_params.tp_gmt_offset = 0;
+ exTime.tm_params.tp_dst_offset = 0;
+
+ PR_NormalizeTime(&exTime, PR_GMTParameters);
+ exTime.tm_params = PR_LocalTimeParameters(&exTime);
+
+ PRTime time = PR_ImplodeTime(&exTime);
+ (void)mChannel->SetLastModifiedTime(time);
+ }
+ }
+
+ nsCString entityID;
+ entityID.Truncate();
+ entityID.AppendInt(int64_t(mFileSize));
+ entityID.Append('/');
+ entityID.Append(mModTime);
+ mChannel->SetEntityID(entityID);
+
+ // We weren't asked to resume
+ if (!mChannel->ResumeRequested())
+ return FTP_S_RETR;
+
+ //if (our entityID == supplied one (if any))
+ if (mSuppliedEntityID.IsEmpty() || entityID.Equals(mSuppliedEntityID))
+ return FTP_S_REST;
+
+ mInternalError = NS_ERROR_ENTITY_CHANGED;
+ mResponseMsg.Truncate();
+ return FTP_ERROR;
+}
+
+nsresult
+nsFtpState::SetContentType()
+{
+ // FTP directory URLs don't always end in a slash. Make sure they do.
+ // This check needs to be here rather than a more obvious place
+ // (e.g. LIST command processing) so that it ensures the terminating
+ // slash is appended for the new request case.
+
+ if (!mPath.IsEmpty() && mPath.Last() != '/') {
+ nsCOMPtr<nsIURL> url = (do_QueryInterface(mChannel->URI()));
+ nsAutoCString filePath;
+ if(NS_SUCCEEDED(url->GetFilePath(filePath))) {
+ filePath.Append('/');
+ url->SetFilePath(filePath);
+ }
+ }
+ return mChannel->SetContentType(
+ NS_LITERAL_CSTRING(APPLICATION_HTTP_INDEX_FORMAT));
+}
+
+nsresult
+nsFtpState::S_list() {
+ nsresult rv = SetContentType();
+ if (NS_FAILED(rv))
+ // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has
+ // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109)
+ return (nsresult)FTP_ERROR;
+
+ rv = mChannel->PushStreamConverter("text/ftp-dir",
+ APPLICATION_HTTP_INDEX_FORMAT);
+ if (NS_FAILED(rv)) {
+ // clear mResponseMsg which is displayed to the user.
+ // TODO: we should probably set this to something meaningful.
+ mResponseMsg = "";
+ return rv;
+ }
+
+ // dir listings aren't resumable
+ NS_ENSURE_TRUE(!mChannel->ResumeRequested(), NS_ERROR_NOT_RESUMABLE);
+
+ mChannel->SetEntityID(EmptyCString());
+
+ const char *listString;
+ if (mServerType == FTP_VMS_TYPE) {
+ listString = "LIST *.*;0" CRLF;
+ } else {
+ listString = "LIST" CRLF;
+ }
+
+ return SendFTPCommand(nsDependentCString(listString));
+}
+
+FTP_STATE
+nsFtpState::R_list() {
+ if (mResponseCode/100 == 1) {
+ // OK, time to start reading from the data connection.
+ if (mDataStream && HasPendingCallback())
+ mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
+ return FTP_READ_BUF;
+ }
+
+ if (mResponseCode/100 == 2) {
+ //(DONE)
+ mNextState = FTP_COMPLETE;
+ return FTP_COMPLETE;
+ }
+ return FTP_ERROR;
+}
+
+nsresult
+nsFtpState::S_retr() {
+ nsAutoCString retrStr(mPath);
+ if (retrStr.IsEmpty() || retrStr.First() != '/')
+ retrStr.Insert(mPwd,0);
+ if (mServerType == FTP_VMS_TYPE)
+ ConvertFilespecToVMS(retrStr);
+ retrStr.Insert("RETR ",0);
+ retrStr.Append(CRLF);
+ return SendFTPCommand(retrStr);
+}
+
+FTP_STATE
+nsFtpState::R_retr() {
+ if (mResponseCode/100 == 2) {
+ //(DONE)
+ mNextState = FTP_COMPLETE;
+ return FTP_COMPLETE;
+ }
+
+ if (mResponseCode/100 == 1) {
+ if (mDataStream && HasPendingCallback())
+ mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
+ return FTP_READ_BUF;
+ }
+
+ // These error codes are related to problems with the connection.
+ // If we encounter any at this point, do not try CWD and abort.
+ if (mResponseCode == 421 || mResponseCode == 425 || mResponseCode == 426)
+ return FTP_ERROR;
+
+ if (mResponseCode/100 == 5) {
+ mRETRFailed = true;
+ return FTP_S_PASV;
+ }
+
+ return FTP_S_CWD;
+}
+
+
+nsresult
+nsFtpState::S_rest() {
+
+ nsAutoCString restString("REST ");
+ // The int64_t cast is needed to avoid ambiguity
+ restString.AppendInt(int64_t(mChannel->StartPos()), 10);
+ restString.Append(CRLF);
+
+ return SendFTPCommand(restString);
+}
+
+FTP_STATE
+nsFtpState::R_rest() {
+ if (mResponseCode/100 == 4) {
+ // If REST fails, then we can't resume
+ mChannel->SetEntityID(EmptyCString());
+
+ mInternalError = NS_ERROR_NOT_RESUMABLE;
+ mResponseMsg.Truncate();
+
+ return FTP_ERROR;
+ }
+
+ return FTP_S_RETR;
+}
+
+nsresult
+nsFtpState::S_stor() {
+ NS_ENSURE_STATE(mChannel->UploadStream());
+
+ NS_ASSERTION(mAction == PUT, "Wrong state to be here");
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI());
+ NS_ASSERTION(url, "I thought you were a nsStandardURL");
+
+ nsAutoCString storStr;
+ url->GetFilePath(storStr);
+ NS_ASSERTION(!storStr.IsEmpty(), "What does it mean to store a empty path");
+
+ // kill the first slash since we want to be relative to CWD.
+ if (storStr.First() == '/')
+ storStr.Cut(0,1);
+
+ if (mServerType == FTP_VMS_TYPE)
+ ConvertFilespecToVMS(storStr);
+
+ NS_UnescapeURL(storStr);
+ storStr.Insert("STOR ",0);
+ storStr.Append(CRLF);
+
+ return SendFTPCommand(storStr);
+}
+
+FTP_STATE
+nsFtpState::R_stor() {
+ if (mResponseCode/100 == 2) {
+ //(DONE)
+ mNextState = FTP_COMPLETE;
+ mStorReplyReceived = true;
+
+ // Call Close() if it was not called in nsFtpState::OnStoprequest()
+ if (!mUploadRequest && !IsClosed())
+ Close();
+
+ return FTP_COMPLETE;
+ }
+
+ if (mResponseCode/100 == 1) {
+ LOG(("FTP:(%x) writing on DT\n", this));
+ return FTP_READ_BUF;
+ }
+
+ mStorReplyReceived = true;
+ return FTP_ERROR;
+}
+
+
+nsresult
+nsFtpState::S_pasv() {
+ if (!mAddressChecked) {
+ // Find socket address
+ mAddressChecked = true;
+ mServerAddress.raw.family = AF_INET;
+ mServerAddress.inet.ip = htonl(INADDR_ANY);
+ mServerAddress.inet.port = htons(0);
+
+ nsITransport *controlSocket = mControlConnection->Transport();
+ if (!controlSocket)
+ // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has
+ // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109)
+ return (nsresult)FTP_ERROR;
+
+ nsCOMPtr<nsISocketTransport> sTrans = do_QueryInterface(controlSocket);
+ if (sTrans) {
+ nsresult rv = sTrans->GetPeerAddr(&mServerAddress);
+ if (NS_SUCCEEDED(rv)) {
+ if (!IsIPAddrAny(&mServerAddress))
+ mServerIsIPv6 = (mServerAddress.raw.family == AF_INET6) &&
+ !IsIPAddrV4Mapped(&mServerAddress);
+ else {
+ /*
+ * In case of SOCKS5 remote DNS resolution, we do
+ * not know the remote IP address. Still, if it is
+ * an IPV6 host, then the external address of the
+ * socks server should also be IPv6, and this is the
+ * self address of the transport.
+ */
+ NetAddr selfAddress;
+ rv = sTrans->GetSelfAddr(&selfAddress);
+ if (NS_SUCCEEDED(rv))
+ mServerIsIPv6 = (selfAddress.raw.family == AF_INET6) &&
+ !IsIPAddrV4Mapped(&selfAddress);
+ }
+ }
+ }
+ }
+
+ const char *string;
+ if (mServerIsIPv6) {
+ string = "EPSV" CRLF;
+ } else {
+ string = "PASV" CRLF;
+ }
+
+ return SendFTPCommand(nsDependentCString(string));
+
+}
+
+FTP_STATE
+nsFtpState::R_pasv() {
+ if (mResponseCode/100 != 2)
+ return FTP_ERROR;
+
+ nsresult rv;
+ int32_t port;
+
+ nsAutoCString responseCopy(mResponseMsg);
+ char *response = responseCopy.BeginWriting();
+
+ char *ptr = response;
+
+ // Make sure to ignore the address in the PASV response (bug 370559)
+
+ if (mServerIsIPv6) {
+ // The returned string is of the form
+ // text (|||ppp|)
+ // Where '|' can be any single character
+ char delim;
+ while (*ptr && *ptr != '(')
+ ptr++;
+ if (*ptr++ != '(')
+ return FTP_ERROR;
+ delim = *ptr++;
+ if (!delim || *ptr++ != delim ||
+ *ptr++ != delim ||
+ *ptr < '0' || *ptr > '9')
+ return FTP_ERROR;
+ port = 0;
+ do {
+ port = port * 10 + *ptr++ - '0';
+ } while (*ptr >= '0' && *ptr <= '9');
+ if (*ptr++ != delim || *ptr != ')')
+ return FTP_ERROR;
+ } else {
+ // The returned address string can be of the form
+ // (xxx,xxx,xxx,xxx,ppp,ppp) or
+ // xxx,xxx,xxx,xxx,ppp,ppp (without parens)
+ int32_t h0, h1, h2, h3, p0, p1;
+
+ int32_t fields = 0;
+ // First try with parens
+ while (*ptr && *ptr != '(')
+ ++ptr;
+ if (*ptr) {
+ ++ptr;
+ fields = PR_sscanf(ptr,
+ "%ld,%ld,%ld,%ld,%ld,%ld",
+ &h0, &h1, &h2, &h3, &p0, &p1);
+ }
+ if (!*ptr || fields < 6) {
+ // OK, lets try w/o parens
+ ptr = response;
+ while (*ptr && *ptr != ',')
+ ++ptr;
+ if (*ptr) {
+ // backup to the start of the digits
+ do {
+ ptr--;
+ } while ((ptr >=response) && (*ptr >= '0') && (*ptr <= '9'));
+ ptr++; // get back onto the numbers
+ fields = PR_sscanf(ptr,
+ "%ld,%ld,%ld,%ld,%ld,%ld",
+ &h0, &h1, &h2, &h3, &p0, &p1);
+ }
+ }
+
+ NS_ASSERTION(fields == 6, "Can't parse PASV response");
+ if (fields < 6)
+ return FTP_ERROR;
+
+ port = ((int32_t) (p0<<8)) + p1;
+ }
+
+ bool newDataConn = true;
+ if (mDataTransport) {
+ // Reuse this connection only if its still alive, and the port
+ // is the same
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(mDataTransport);
+ if (strans) {
+ int32_t oldPort;
+ nsresult rv = strans->GetPort(&oldPort);
+ if (NS_SUCCEEDED(rv)) {
+ if (oldPort == port) {
+ bool isAlive;
+ if (NS_SUCCEEDED(strans->IsAlive(&isAlive)) && isAlive)
+ newDataConn = false;
+ }
+ }
+ }
+
+ if (newDataConn) {
+ mDataTransport->Close(NS_ERROR_ABORT);
+ mDataTransport = nullptr;
+ mDataStream = nullptr;
+ }
+ }
+
+ if (newDataConn) {
+ // now we know where to connect our data channel
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ if (!sts)
+ return FTP_ERROR;
+
+ nsCOMPtr<nsISocketTransport> strans;
+
+ nsAutoCString host;
+ if (!IsIPAddrAny(&mServerAddress)) {
+ char buf[kIPv6CStrBufSize];
+ NetAddrToString(&mServerAddress, buf, sizeof(buf));
+ host.Assign(buf);
+ } else {
+ /*
+ * In case of SOCKS5 remote DNS resolving, the peer address
+ * fetched previously will be invalid (0.0.0.0): it is unknown
+ * to us. But we can pass on the original hostname to the
+ * connect for the data connection.
+ */
+ rv = mChannel->URI()->GetAsciiHost(host);
+ if (NS_FAILED(rv))
+ return FTP_ERROR;
+ }
+
+ rv = sts->CreateTransport(nullptr, 0, host,
+ port, mChannel->ProxyInfo(),
+ getter_AddRefs(strans)); // the data socket
+ if (NS_FAILED(rv))
+ return FTP_ERROR;
+ mDataTransport = strans;
+
+ strans->SetQoSBits(gFtpHandler->GetDataQoSBits());
+
+ LOG(("FTP:(%x) created DT (%s:%x)\n", this, host.get(), port));
+
+ // hook ourself up as a proxy for status notifications
+ rv = mDataTransport->SetEventSink(this, NS_GetCurrentThread());
+ NS_ENSURE_SUCCESS(rv, FTP_ERROR);
+
+ if (mAction == PUT) {
+ NS_ASSERTION(!mRETRFailed, "Failed before uploading");
+
+ // nsIUploadChannel requires the upload stream to support ReadSegments.
+ // therefore, we can open an unbuffered socket output stream.
+ nsCOMPtr<nsIOutputStream> output;
+ rv = mDataTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED,
+ 0, 0, getter_AddRefs(output));
+ if (NS_FAILED(rv))
+ return FTP_ERROR;
+
+ // perform the data copy on the socket transport thread. we do this
+ // because "output" is a socket output stream, so the result is that
+ // all work will be done on the socket transport thread.
+ nsCOMPtr<nsIEventTarget> stEventTarget =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ if (!stEventTarget)
+ return FTP_ERROR;
+
+ nsCOMPtr<nsIAsyncStreamCopier> copier;
+ rv = NS_NewAsyncStreamCopier(getter_AddRefs(copier),
+ mChannel->UploadStream(),
+ output,
+ stEventTarget,
+ true, // upload stream is buffered
+ false); // output is NOT buffered
+ if (NS_FAILED(rv))
+ return FTP_ERROR;
+
+ rv = copier->AsyncCopy(this, nullptr);
+ if (NS_FAILED(rv))
+ return FTP_ERROR;
+
+ // hold a reference to the copier so we can cancel it if necessary.
+ mUploadRequest = copier;
+
+ // update the current working directory before sending the STOR
+ // command. this is needed since we might be reusing a control
+ // connection.
+ return FTP_S_CWD;
+ }
+
+ //
+ // else, we are reading from the data connection...
+ //
+
+ // open a buffered, asynchronous socket input stream
+ nsCOMPtr<nsIInputStream> input;
+ rv = mDataTransport->OpenInputStream(0,
+ nsIOService::gDefaultSegmentSize,
+ nsIOService::gDefaultSegmentCount,
+ getter_AddRefs(input));
+ NS_ENSURE_SUCCESS(rv, FTP_ERROR);
+ mDataStream = do_QueryInterface(input);
+ }
+
+ if (mRETRFailed || mPath.IsEmpty() || mPath.Last() == '/')
+ return FTP_S_CWD;
+ return FTP_S_SIZE;
+}
+
+nsresult
+nsFtpState::S_feat() {
+ return SendFTPCommand(NS_LITERAL_CSTRING("FEAT" CRLF));
+}
+
+FTP_STATE
+nsFtpState::R_feat() {
+ if (mResponseCode/100 == 2) {
+ if (mResponseMsg.Find(NS_LITERAL_CSTRING(CRLF " UTF8" CRLF), true) > -1) {
+ // This FTP server supports UTF-8 encoding
+ mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
+ mUseUTF8 = true;
+ return FTP_S_OPTS;
+ }
+ }
+
+ mUseUTF8 = false;
+ return FTP_S_PWD;
+}
+
+nsresult
+nsFtpState::S_opts() {
+ // This command is for compatibility of old FTP spec (IETF Draft)
+ return SendFTPCommand(NS_LITERAL_CSTRING("OPTS UTF8 ON" CRLF));
+}
+
+FTP_STATE
+nsFtpState::R_opts() {
+ // Ignore error code because "OPTS UTF8 ON" is for compatibility of
+ // FTP server using IETF draft
+ return FTP_S_PWD;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+
+nsresult
+nsFtpState::Init(nsFtpChannel *channel)
+{
+ // parameter validation
+ NS_ASSERTION(channel, "FTP: needs a channel");
+
+ mChannel = channel; // a straight ref ptr to the channel
+
+ // initialize counter for network metering
+ mCountRecv = 0;
+
+#ifdef MOZ_WIDGET_GONK
+ nsCOMPtr<nsINetworkInfo> activeNetworkInfo;
+ GetActiveNetworkInfo(activeNetworkInfo);
+ mActiveNetworkInfo =
+ new nsMainThreadPtrHolder<nsINetworkInfo>(activeNetworkInfo);
+#endif
+
+ mKeepRunning = true;
+ mSuppliedEntityID = channel->EntityID();
+
+ if (channel->UploadStream())
+ mAction = PUT;
+
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI());
+
+ nsAutoCString host;
+ if (url) {
+ rv = url->GetAsciiHost(host);
+ } else {
+ rv = mChannel->URI()->GetAsciiHost(host);
+ }
+ if (NS_FAILED(rv) || host.IsEmpty()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsAutoCString path;
+ if (url) {
+ rv = url->GetFilePath(path);
+ } else {
+ rv = mChannel->URI()->GetPath(path);
+ }
+ if (NS_FAILED(rv))
+ return rv;
+
+ removeParamsFromPath(path);
+
+ // FTP parameters such as type=i are ignored
+ if (url) {
+ url->SetFilePath(path);
+ } else {
+ mChannel->URI()->SetPath(path);
+ }
+
+ // Skip leading slash
+ char *fwdPtr = path.BeginWriting();
+ if (!fwdPtr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ if (*fwdPtr == '/')
+ fwdPtr++;
+ if (*fwdPtr != '\0') {
+ // now unescape it... %xx reduced inline to resulting character
+ int32_t len = NS_UnescapeURL(fwdPtr);
+ mPath.Assign(fwdPtr, len);
+
+#ifdef DEBUG
+ if (mPath.FindCharInSet(CRLF) >= 0)
+ NS_ERROR("NewURI() should've prevented this!!!");
+#endif
+ }
+
+ // pull any username and/or password out of the uri
+ nsAutoCString uname;
+ rv = mChannel->URI()->GetUsername(uname);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!uname.IsEmpty() && !uname.EqualsLiteral("anonymous")) {
+ mAnonymous = false;
+ CopyUTF8toUTF16(NS_UnescapeURL(uname), mUsername);
+
+ // return an error if we find a CR or LF in the username
+ if (uname.FindCharInSet(CRLF) >= 0)
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsAutoCString password;
+ rv = mChannel->URI()->GetPassword(password);
+ if (NS_FAILED(rv))
+ return rv;
+
+ CopyUTF8toUTF16(NS_UnescapeURL(password), mPassword);
+
+ // return an error if we find a CR or LF in the password
+ if (mPassword.FindCharInSet(CRLF) >= 0)
+ return NS_ERROR_MALFORMED_URI;
+
+ int32_t port;
+ rv = mChannel->URI()->GetPort(&port);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (port > 0)
+ mPort = port;
+
+ // Lookup Proxy information asynchronously if it isn't already set
+ // on the channel and if we aren't configured explicitly to go directly
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
+
+ if (pps && !mChannel->ProxyInfo()) {
+ pps->AsyncResolve(static_cast<nsIChannel*>(mChannel), 0, this,
+ getter_AddRefs(mProxyRequest));
+ }
+
+ return NS_OK;
+}
+
+void
+nsFtpState::Connect()
+{
+ mState = FTP_COMMAND_CONNECT;
+ mNextState = FTP_S_USER;
+
+ nsresult rv = Process();
+
+ // check for errors.
+ if (NS_FAILED(rv)) {
+ LOG(("FTP:Process() failed: %x\n", rv));
+ mInternalError = NS_ERROR_FAILURE;
+ mState = FTP_ERROR;
+ CloseWithStatus(mInternalError);
+ }
+}
+
+void
+nsFtpState::KillControlConnection()
+{
+ mControlReadCarryOverBuf.Truncate(0);
+
+ mAddressChecked = false;
+ mServerIsIPv6 = false;
+
+ // if everything went okay, save the connection.
+ // FIX: need a better way to determine if we can cache the connections.
+ // there are some errors which do not mean that we need to kill the connection
+ // e.g. fnf.
+
+ if (!mControlConnection)
+ return;
+
+ // kill the reference to ourselves in the control connection.
+ mControlConnection->WaitData(nullptr);
+
+ if (NS_SUCCEEDED(mInternalError) &&
+ NS_SUCCEEDED(mControlStatus) &&
+ mControlConnection->IsAlive() &&
+ mCacheConnection) {
+
+ LOG_INFO(("FTP:(%p) caching CC(%p)", this, mControlConnection.get()));
+
+ // Store connection persistent data
+ mControlConnection->mServerType = mServerType;
+ mControlConnection->mPassword = mPassword;
+ mControlConnection->mPwd = mPwd;
+ mControlConnection->mUseUTF8 = mUseUTF8;
+
+ nsresult rv = NS_OK;
+ // Don't cache controlconnection if anonymous (bug #473371)
+ if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
+ rv = gFtpHandler->InsertConnection(mChannel->URI(),
+ mControlConnection);
+ // Can't cache it? Kill it then.
+ mControlConnection->Disconnect(rv);
+ } else {
+ mControlConnection->Disconnect(NS_BINDING_ABORTED);
+ }
+
+ mControlConnection = nullptr;
+}
+
+class nsFtpAsyncAlert : public Runnable
+{
+public:
+ nsFtpAsyncAlert(nsIPrompt *aPrompter, nsString aResponseMsg)
+ : mPrompter(aPrompter)
+ , mResponseMsg(aResponseMsg)
+ {
+ MOZ_COUNT_CTOR(nsFtpAsyncAlert);
+ }
+protected:
+ virtual ~nsFtpAsyncAlert()
+ {
+ MOZ_COUNT_DTOR(nsFtpAsyncAlert);
+ }
+public:
+ NS_IMETHOD Run() override
+ {
+ if (mPrompter) {
+ mPrompter->Alert(nullptr, mResponseMsg.get());
+ }
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIPrompt> mPrompter;
+ nsString mResponseMsg;
+};
+
+
+nsresult
+nsFtpState::StopProcessing()
+{
+ // Only do this function once.
+ if (!mKeepRunning)
+ return NS_OK;
+ mKeepRunning = false;
+
+ LOG_INFO(("FTP:(%x) nsFtpState stopping", this));
+
+ if (NS_FAILED(mInternalError) && !mResponseMsg.IsEmpty()) {
+ // check to see if the control status is bad.
+ // web shell wont throw an alert. we better:
+
+ // XXX(darin): this code should not be dictating UI like this!
+ nsCOMPtr<nsIPrompt> prompter;
+ mChannel->GetCallback(prompter);
+ if (prompter) {
+ nsCOMPtr<nsIRunnable> alertEvent;
+ if (mUseUTF8) {
+ alertEvent = new nsFtpAsyncAlert(prompter,
+ NS_ConvertUTF8toUTF16(mResponseMsg));
+ } else {
+ alertEvent = new nsFtpAsyncAlert(prompter,
+ NS_ConvertASCIItoUTF16(mResponseMsg));
+ }
+ NS_DispatchToMainThread(alertEvent);
+ }
+ nsCOMPtr<nsIFTPChannelParentInternal> ftpChanP;
+ mChannel->GetCallback(ftpChanP);
+ if (ftpChanP) {
+ ftpChanP->SetErrorMsg(mResponseMsg.get(), mUseUTF8);
+ }
+ }
+
+ nsresult broadcastErrorCode = mControlStatus;
+ if (NS_SUCCEEDED(broadcastErrorCode))
+ broadcastErrorCode = mInternalError;
+
+ mInternalError = broadcastErrorCode;
+
+ KillControlConnection();
+
+ // XXX This can fire before we are done loading data. Is that a problem?
+ OnTransportStatus(nullptr, NS_NET_STATUS_END_FTP_TRANSACTION, 0, 0);
+
+ if (NS_FAILED(broadcastErrorCode))
+ CloseWithStatus(broadcastErrorCode);
+
+ return NS_OK;
+}
+
+nsresult
+nsFtpState::SendFTPCommand(const nsCSubstring& command)
+{
+ NS_ASSERTION(mControlConnection, "null control connection");
+
+ // we don't want to log the password:
+ nsAutoCString logcmd(command);
+ if (StringBeginsWith(command, NS_LITERAL_CSTRING("PASS ")))
+ logcmd = "PASS xxxxx";
+
+ LOG(("FTP:(%x) writing \"%s\"\n", this, logcmd.get()));
+
+ nsCOMPtr<nsIFTPEventSink> ftpSink;
+ mChannel->GetFTPEventSink(ftpSink);
+ if (ftpSink)
+ ftpSink->OnFTPControlLog(false, logcmd.get());
+
+ if (mControlConnection)
+ return mControlConnection->Write(command);
+
+ return NS_ERROR_FAILURE;
+}
+
+// Convert a unix-style filespec to VMS format
+// /foo/fred/barney/file.txt -> foo:[fred.barney]file.txt
+// /foo/file.txt -> foo:[000000]file.txt
+void
+nsFtpState::ConvertFilespecToVMS(nsCString& fileString)
+{
+ int ntok=1;
+ char *t, *nextToken;
+ nsAutoCString fileStringCopy;
+
+ // Get a writeable copy we can strtok with.
+ fileStringCopy = fileString;
+ t = nsCRT::strtok(fileStringCopy.BeginWriting(), "/", &nextToken);
+ if (t)
+ while (nsCRT::strtok(nextToken, "/", &nextToken))
+ ntok++; // count number of terms (tokens)
+ LOG(("FTP:(%x) ConvertFilespecToVMS ntok: %d\n", this, ntok));
+ LOG(("FTP:(%x) ConvertFilespecToVMS from: \"%s\"\n", this, fileString.get()));
+
+ if (fileString.First() == '/') {
+ // absolute filespec
+ // / -> []
+ // /a -> a (doesn't really make much sense)
+ // /a/b -> a:[000000]b
+ // /a/b/c -> a:[b]c
+ // /a/b/c/d -> a:[b.c]d
+ if (ntok == 1) {
+ if (fileString.Length() == 1) {
+ // Just a slash
+ fileString.Truncate();
+ fileString.AppendLiteral("[]");
+ } else {
+ // just copy the name part (drop the leading slash)
+ fileStringCopy = fileString;
+ fileString = Substring(fileStringCopy, 1,
+ fileStringCopy.Length()-1);
+ }
+ } else {
+ // Get another copy since the last one was written to.
+ fileStringCopy = fileString;
+ fileString.Truncate();
+ fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(),
+ "/", &nextToken));
+ fileString.AppendLiteral(":[");
+ if (ntok > 2) {
+ for (int i=2; i<ntok; i++) {
+ if (i > 2) fileString.Append('.');
+ fileString.Append(nsCRT::strtok(nextToken,
+ "/", &nextToken));
+ }
+ } else {
+ fileString.AppendLiteral("000000");
+ }
+ fileString.Append(']');
+ fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
+ }
+ } else {
+ // relative filespec
+ // a -> a
+ // a/b -> [.a]b
+ // a/b/c -> [.a.b]c
+ if (ntok == 1) {
+ // no slashes, just use the name as is
+ } else {
+ // Get another copy since the last one was written to.
+ fileStringCopy = fileString;
+ fileString.Truncate();
+ fileString.AppendLiteral("[.");
+ fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(),
+ "/", &nextToken));
+ if (ntok > 2) {
+ for (int i=2; i<ntok; i++) {
+ fileString.Append('.');
+ fileString.Append(nsCRT::strtok(nextToken,
+ "/", &nextToken));
+ }
+ }
+ fileString.Append(']');
+ fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
+ }
+ }
+ LOG(("FTP:(%x) ConvertFilespecToVMS to: \"%s\"\n", this, fileString.get()));
+}
+
+// Convert a unix-style dirspec to VMS format
+// /foo/fred/barney/rubble -> foo:[fred.barney.rubble]
+// /foo/fred -> foo:[fred]
+// /foo -> foo:[000000]
+// (null) -> (null)
+void
+nsFtpState::ConvertDirspecToVMS(nsCString& dirSpec)
+{
+ LOG(("FTP:(%x) ConvertDirspecToVMS from: \"%s\"\n", this, dirSpec.get()));
+ if (!dirSpec.IsEmpty()) {
+ if (dirSpec.Last() != '/')
+ dirSpec.Append('/');
+ // we can use the filespec routine if we make it look like a file name
+ dirSpec.Append('x');
+ ConvertFilespecToVMS(dirSpec);
+ dirSpec.Truncate(dirSpec.Length()-1);
+ }
+ LOG(("FTP:(%x) ConvertDirspecToVMS to: \"%s\"\n", this, dirSpec.get()));
+}
+
+// Convert an absolute VMS style dirspec to UNIX format
+void
+nsFtpState::ConvertDirspecFromVMS(nsCString& dirSpec)
+{
+ LOG(("FTP:(%x) ConvertDirspecFromVMS from: \"%s\"\n", this, dirSpec.get()));
+ if (dirSpec.IsEmpty()) {
+ dirSpec.Insert('.', 0);
+ } else {
+ dirSpec.Insert('/', 0);
+ dirSpec.ReplaceSubstring(":[", "/");
+ dirSpec.ReplaceChar('.', '/');
+ dirSpec.ReplaceChar(']', '/');
+ }
+ LOG(("FTP:(%x) ConvertDirspecFromVMS to: \"%s\"\n", this, dirSpec.get()));
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsFtpState::OnTransportStatus(nsITransport *transport, nsresult status,
+ int64_t progress, int64_t progressMax)
+{
+ // Mix signals from both the control and data connections.
+
+ // Ignore data transfer events on the control connection.
+ if (mControlConnection && transport == mControlConnection->Transport()) {
+ switch (status) {
+ case NS_NET_STATUS_RESOLVING_HOST:
+ case NS_NET_STATUS_RESOLVED_HOST:
+ case NS_NET_STATUS_CONNECTING_TO:
+ case NS_NET_STATUS_CONNECTED_TO:
+ break;
+ default:
+ return NS_OK;
+ }
+ }
+
+ // Ignore the progressMax value from the socket. We know the true size of
+ // the file based on the response from our SIZE request. Additionally, only
+ // report the max progress based on where we started/resumed.
+ mChannel->OnTransportStatus(nullptr, status, progress,
+ mFileSize - mChannel->StartPos());
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsFtpState::OnStartRequest(nsIRequest *request, nsISupports *context)
+{
+ mStorReplyReceived = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpState::OnStopRequest(nsIRequest *request, nsISupports *context,
+ nsresult status)
+{
+ mUploadRequest = nullptr;
+
+ // Close() will be called when reply to STOR command is received
+ // see bug #389394
+ if (!mStorReplyReceived)
+ return NS_OK;
+
+ // We're done uploading. Let our consumer know that we're done.
+ Close();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsFtpState::Available(uint64_t *result)
+{
+ if (mDataStream)
+ return mDataStream->Available(result);
+
+ return nsBaseContentStream::Available(result);
+}
+
+NS_IMETHODIMP
+nsFtpState::ReadSegments(nsWriteSegmentFun writer, void *closure,
+ uint32_t count, uint32_t *result)
+{
+ // Insert a thunk here so that the input stream passed to the writer is this
+ // input stream instead of mDataStream.
+
+ if (mDataStream) {
+ nsWriteSegmentThunk thunk = { this, writer, closure };
+ nsresult rv;
+ rv = mDataStream->ReadSegments(NS_WriteSegmentThunk, &thunk, count,
+ result);
+ if (NS_SUCCEEDED(rv)) {
+ CountRecvBytes(*result);
+ }
+ return rv;
+ }
+
+ return nsBaseContentStream::ReadSegments(writer, closure, count, result);
+}
+
+nsresult
+nsFtpState::SaveNetworkStats(bool enforce)
+{
+#ifdef MOZ_WIDGET_GONK
+ // Obtain app id
+ uint32_t appId;
+ bool isInBrowser;
+ NS_GetAppInfo(mChannel, &appId, &isInBrowser);
+
+ // Check if active network and appid are valid.
+ if (!mActiveNetworkInfo || appId == NECKO_NO_APP_ID) {
+ return NS_OK;
+ }
+
+ if (mCountRecv <= 0) {
+ // There is no traffic, no need to save.
+ return NS_OK;
+ }
+
+ // If |enforce| is false, the traffic amount is saved
+ // only when the total amount exceeds the predefined
+ // threshold.
+ if (!enforce && mCountRecv < NETWORK_STATS_THRESHOLD) {
+ return NS_OK;
+ }
+
+ // Create the event to save the network statistics.
+ // the event is then dispathed to the main thread.
+ RefPtr<Runnable> event =
+ new SaveNetworkStatsEvent(appId, isInBrowser, mActiveNetworkInfo,
+ mCountRecv, 0, false);
+ NS_DispatchToMainThread(event);
+
+ // Reset the counters after saving.
+ mCountRecv = 0;
+
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsFtpState::CloseWithStatus(nsresult status)
+{
+ LOG(("FTP:(%p) close [%x]\n", this, status));
+
+ // Shutdown the control connection processing if we are being closed with an
+ // error. Note: This method may be called several times.
+ if (!IsClosed() && status != NS_BASE_STREAM_CLOSED && NS_FAILED(status)) {
+ if (NS_SUCCEEDED(mInternalError))
+ mInternalError = status;
+ StopProcessing();
+ }
+
+ if (mUploadRequest) {
+ mUploadRequest->Cancel(NS_ERROR_ABORT);
+ mUploadRequest = nullptr;
+ }
+
+ if (mDataTransport) {
+ // Save the network stats before data transport is closing.
+ SaveNetworkStats(true);
+
+ // Shutdown the data transport.
+ mDataTransport->Close(NS_ERROR_ABORT);
+ mDataTransport = nullptr;
+ }
+
+ mDataStream = nullptr;
+
+ return nsBaseContentStream::CloseWithStatus(status);
+}
+
+static nsresult
+CreateHTTPProxiedChannel(nsIChannel *channel, nsIProxyInfo *pi, nsIChannel **newChannel)
+{
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ioService->GetProtocolHandler("http", getter_AddRefs(handler));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ channel->GetLoadInfo(getter_AddRefs(loadInfo));
+
+ return pph->NewProxiedChannel2(uri, pi, 0, nullptr, loadInfo, newChannel);
+}
+
+NS_IMETHODIMP
+nsFtpState::OnProxyAvailable(nsICancelable *request, nsIChannel *channel,
+ nsIProxyInfo *pi, nsresult status)
+{
+ mProxyRequest = nullptr;
+
+ // failed status code just implies DIRECT processing
+
+ if (NS_SUCCEEDED(status)) {
+ nsAutoCString type;
+ if (pi && NS_SUCCEEDED(pi->GetType(type)) && type.EqualsLiteral("http")) {
+ // Proxy the FTP url via HTTP
+ // This would have been easier to just return a HTTP channel directly
+ // from nsIIOService::NewChannelFromURI(), but the proxy type cannot
+ // be reliabliy determined synchronously without jank due to pac, etc..
+ LOG(("FTP:(%p) Configured to use a HTTP proxy channel\n", this));
+
+ nsCOMPtr<nsIChannel> newChannel;
+ if (NS_SUCCEEDED(CreateHTTPProxiedChannel(channel, pi,
+ getter_AddRefs(newChannel))) &&
+ NS_SUCCEEDED(mChannel->Redirect(newChannel,
+ nsIChannelEventSink::REDIRECT_INTERNAL,
+ true))) {
+ LOG(("FTP:(%p) Redirected to use a HTTP proxy channel\n", this));
+ return NS_OK;
+ }
+ }
+ else if (pi) {
+ // Proxy using the FTP protocol routed through a socks proxy
+ LOG(("FTP:(%p) Configured to use a SOCKS proxy channel\n", this));
+ mChannel->SetProxyInfo(pi);
+ }
+ }
+
+ if (mDeferredCallbackPending) {
+ mDeferredCallbackPending = false;
+ OnCallbackPending();
+ }
+ return NS_OK;
+}
+
+void
+nsFtpState::OnCallbackPending()
+{
+ if (mState == FTP_INIT) {
+ if (mProxyRequest) {
+ mDeferredCallbackPending = true;
+ return;
+ }
+ Connect();
+ } else if (mDataStream) {
+ mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
+ }
+}
+
diff --git a/netwerk/protocol/ftp/nsFtpConnectionThread.h b/netwerk/protocol/ftp/nsFtpConnectionThread.h
new file mode 100644
index 000000000..dd48da562
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpConnectionThread.h
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsFtpConnectionThread__h_
+#define __nsFtpConnectionThread__h_
+
+#include "nsBaseContentStream.h"
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsAutoPtr.h"
+#include "nsITransport.h"
+#include "mozilla/net/DNS.h"
+#include "nsFtpControlConnection.h"
+#include "nsIProtocolProxyCallback.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkInterface.h"
+#include "nsProxyRelease.h"
+#endif
+
+// ftp server types
+#define FTP_GENERIC_TYPE 0
+#define FTP_UNIX_TYPE 1
+#define FTP_VMS_TYPE 8
+#define FTP_NT_TYPE 9
+#define FTP_OS2_TYPE 11
+
+// ftp states
+typedef enum _FTP_STATE {
+///////////////////////
+//// Internal states
+ FTP_INIT,
+ FTP_COMMAND_CONNECT,
+ FTP_READ_BUF,
+ FTP_ERROR,
+ FTP_COMPLETE,
+
+///////////////////////
+//// Command channel connection setup states
+ FTP_S_USER, FTP_R_USER,
+ FTP_S_PASS, FTP_R_PASS,
+ FTP_S_SYST, FTP_R_SYST,
+ FTP_S_ACCT, FTP_R_ACCT,
+ FTP_S_TYPE, FTP_R_TYPE,
+ FTP_S_CWD, FTP_R_CWD,
+ FTP_S_SIZE, FTP_R_SIZE,
+ FTP_S_MDTM, FTP_R_MDTM,
+ FTP_S_REST, FTP_R_REST,
+ FTP_S_RETR, FTP_R_RETR,
+ FTP_S_STOR, FTP_R_STOR,
+ FTP_S_LIST, FTP_R_LIST,
+ FTP_S_PASV, FTP_R_PASV,
+ FTP_S_PWD, FTP_R_PWD,
+ FTP_S_FEAT, FTP_R_FEAT,
+ FTP_S_OPTS, FTP_R_OPTS
+} FTP_STATE;
+
+// higher level ftp actions
+typedef enum _FTP_ACTION {GET, PUT} FTP_ACTION;
+
+class nsFtpChannel;
+class nsICancelable;
+class nsIProxyInfo;
+class nsIStreamListener;
+
+// The nsFtpState object is the content stream for the channel. It implements
+// nsIInputStreamCallback, so it can read data from the control connection. It
+// implements nsITransportEventSink so it can mix status events from both the
+// control connection and the data connection.
+
+class nsFtpState final : public nsBaseContentStream,
+ public nsIInputStreamCallback,
+ public nsITransportEventSink,
+ public nsIRequestObserver,
+ public nsFtpControlConnectionListener,
+ public nsIProtocolProxyCallback
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+
+ // Override input stream methods:
+ NS_IMETHOD CloseWithStatus(nsresult status) override;
+ NS_IMETHOD Available(uint64_t *result) override;
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun fun, void *closure,
+ uint32_t count, uint32_t *result) override;
+
+ // nsFtpControlConnectionListener methods:
+ virtual void OnControlDataAvailable(const char *data, uint32_t dataLen) override;
+ virtual void OnControlError(nsresult status) override;
+
+ nsFtpState();
+ nsresult Init(nsFtpChannel *channel);
+
+protected:
+ // Notification from nsBaseContentStream::AsyncWait
+ virtual void OnCallbackPending() override;
+
+private:
+ virtual ~nsFtpState();
+
+ ///////////////////////////////////
+ // BEGIN: STATE METHODS
+ nsresult S_user(); FTP_STATE R_user();
+ nsresult S_pass(); FTP_STATE R_pass();
+ nsresult S_syst(); FTP_STATE R_syst();
+ nsresult S_acct(); FTP_STATE R_acct();
+
+ nsresult S_type(); FTP_STATE R_type();
+ nsresult S_cwd(); FTP_STATE R_cwd();
+
+ nsresult S_size(); FTP_STATE R_size();
+ nsresult S_mdtm(); FTP_STATE R_mdtm();
+ nsresult S_list(); FTP_STATE R_list();
+
+ nsresult S_rest(); FTP_STATE R_rest();
+ nsresult S_retr(); FTP_STATE R_retr();
+ nsresult S_stor(); FTP_STATE R_stor();
+ nsresult S_pasv(); FTP_STATE R_pasv();
+ nsresult S_pwd(); FTP_STATE R_pwd();
+ nsresult S_feat(); FTP_STATE R_feat();
+ nsresult S_opts(); FTP_STATE R_opts();
+ // END: STATE METHODS
+ ///////////////////////////////////
+
+ // internal methods
+ void MoveToNextState(FTP_STATE nextState);
+ nsresult Process();
+
+ void KillControlConnection();
+ nsresult StopProcessing();
+ nsresult EstablishControlConnection();
+ nsresult SendFTPCommand(const nsCSubstring& command);
+ void ConvertFilespecToVMS(nsCString& fileSpec);
+ void ConvertDirspecToVMS(nsCString& fileSpec);
+ void ConvertDirspecFromVMS(nsCString& fileSpec);
+ nsresult BuildStreamConverter(nsIStreamListener** convertStreamListener);
+ nsresult SetContentType();
+
+ /**
+ * This method is called to kick-off the FTP state machine. mState is
+ * reset to FTP_COMMAND_CONNECT, and the FTP state machine progresses from
+ * there. This method is initially called (indirectly) from the channel's
+ * AsyncOpen implementation.
+ */
+ void Connect();
+
+ ///////////////////////////////////
+ // Private members
+
+ // ****** state machine vars
+ FTP_STATE mState; // the current state
+ FTP_STATE mNextState; // the next state
+ bool mKeepRunning; // thread event loop boolean
+ int32_t mResponseCode; // the last command response code
+ nsCString mResponseMsg; // the last command response text
+
+ // ****** channel/transport/stream vars
+ RefPtr<nsFtpControlConnection> mControlConnection; // cacheable control connection (owns mCPipe)
+ bool mReceivedControlData;
+ bool mTryingCachedControl; // retrying the password
+ bool mRETRFailed; // Did we already try a RETR and it failed?
+ uint64_t mFileSize;
+ nsCString mModTime;
+
+ // ****** consumer vars
+ RefPtr<nsFtpChannel> mChannel; // our owning FTP channel we pass through our events
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+
+ // ****** connection cache vars
+ int32_t mServerType; // What kind of server are we talking to
+
+ // ****** protocol interpretation related state vars
+ nsString mUsername; // username
+ nsString mPassword; // password
+ FTP_ACTION mAction; // the higher level action (GET/PUT)
+ bool mAnonymous; // try connecting anonymous (default)
+ bool mRetryPass; // retrying the password
+ bool mStorReplyReceived; // FALSE if waiting for STOR
+ // completion status from server
+ nsresult mInternalError; // represents internal state errors
+ bool mReconnectAndLoginAgain;
+ bool mCacheConnection;
+
+ // ****** URI vars
+ int32_t mPort; // the port to connect to
+ nsString mFilename; // url filename (if any)
+ nsCString mPath; // the url's path
+ nsCString mPwd; // login Path
+
+ // ****** other vars
+ nsCOMPtr<nsITransport> mDataTransport;
+ nsCOMPtr<nsIAsyncInputStream> mDataStream;
+ nsCOMPtr<nsIRequest> mUploadRequest;
+ bool mAddressChecked;
+ bool mServerIsIPv6;
+ bool mUseUTF8;
+
+ mozilla::net::NetAddr mServerAddress;
+
+ // ***** control read gvars
+ nsresult mControlStatus;
+ nsCString mControlReadCarryOverBuf;
+
+ nsCString mSuppliedEntityID;
+
+ nsCOMPtr<nsICancelable> mProxyRequest;
+ bool mDeferredCallbackPending;
+
+// These members are used for network per-app metering (bug 855948)
+// Currently, they are only available on gonk.
+ uint64_t mCountRecv;
+#ifdef MOZ_WIDGET_GONK
+ nsMainThreadPtrHandle<nsINetworkInfo> mActiveNetworkInfo;
+#endif
+ nsresult SaveNetworkStats(bool);
+ void CountRecvBytes(uint64_t recvBytes)
+ {
+ mCountRecv += recvBytes;
+ SaveNetworkStats(false);
+ }
+};
+
+#endif //__nsFtpConnectionThread__h_
diff --git a/netwerk/protocol/ftp/nsFtpControlConnection.cpp b/netwerk/protocol/ftp/nsFtpControlConnection.cpp
new file mode 100644
index 000000000..ab55cd4f6
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpControlConnection.cpp
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOService.h"
+#include "nsFtpControlConnection.h"
+#include "nsFtpProtocolHandler.h"
+#include "mozilla/Logging.h"
+#include "nsIInputStream.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsThreadUtils.h"
+#include "nsIOutputStream.h"
+#include "nsNetCID.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+extern LazyLogModule gFTPLog;
+#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
+#define LOG_INFO(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Info, args)
+
+//
+// nsFtpControlConnection implementation ...
+//
+
+NS_IMPL_ISUPPORTS(nsFtpControlConnection, nsIInputStreamCallback)
+
+NS_IMETHODIMP
+nsFtpControlConnection::OnInputStreamReady(nsIAsyncInputStream *stream)
+{
+ char data[4096];
+
+ // Consume data whether we have a listener or not.
+ uint64_t avail64;
+ uint32_t avail = 0;
+ nsresult rv = stream->Available(&avail64);
+ if (NS_SUCCEEDED(rv)) {
+ avail = (uint32_t)std::min(avail64, (uint64_t)sizeof(data));
+
+ uint32_t n;
+ rv = stream->Read(data, avail, &n);
+ if (NS_SUCCEEDED(rv))
+ avail = n;
+ }
+
+ // It's important that we null out mListener before calling one of its
+ // methods as it may call WaitData, which would queue up another read.
+
+ RefPtr<nsFtpControlConnectionListener> listener;
+ listener.swap(mListener);
+
+ if (!listener)
+ return NS_OK;
+
+ if (NS_FAILED(rv)) {
+ listener->OnControlError(rv);
+ } else {
+ listener->OnControlDataAvailable(data, avail);
+ }
+
+ return NS_OK;
+}
+
+nsFtpControlConnection::nsFtpControlConnection(const nsCSubstring& host,
+ uint32_t port)
+ : mServerType(0), mSessionId(gFtpHandler->GetSessionId())
+ , mUseUTF8(false), mHost(host), mPort(port)
+{
+ LOG_INFO(("FTP:CC created @%p", this));
+}
+
+nsFtpControlConnection::~nsFtpControlConnection()
+{
+ LOG_INFO(("FTP:CC destroyed @%p", this));
+}
+
+bool
+nsFtpControlConnection::IsAlive()
+{
+ if (!mSocket)
+ return false;
+
+ bool isAlive = false;
+ mSocket->IsAlive(&isAlive);
+ return isAlive;
+}
+nsresult
+nsFtpControlConnection::Connect(nsIProxyInfo* proxyInfo,
+ nsITransportEventSink* eventSink)
+{
+ if (mSocket)
+ return NS_OK;
+
+ // build our own
+ nsresult rv;
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = sts->CreateTransport(nullptr, 0, mHost, mPort, proxyInfo,
+ getter_AddRefs(mSocket)); // the command transport
+ if (NS_FAILED(rv))
+ return rv;
+
+ mSocket->SetQoSBits(gFtpHandler->GetControlQoSBits());
+
+ // proxy transport events back to current thread
+ if (eventSink)
+ mSocket->SetEventSink(eventSink, NS_GetCurrentThread());
+
+ // open buffered, blocking output stream to socket. so long as commands
+ // do not exceed 1024 bytes in length, the writing thread (the main thread)
+ // will not block. this should be OK.
+ rv = mSocket->OpenOutputStream(nsITransport::OPEN_BLOCKING, 1024, 1,
+ getter_AddRefs(mSocketOutput));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // open buffered, non-blocking/asynchronous input stream to socket.
+ nsCOMPtr<nsIInputStream> inStream;
+ rv = mSocket->OpenInputStream(0,
+ nsIOService::gDefaultSegmentSize,
+ nsIOService::gDefaultSegmentCount,
+ getter_AddRefs(inStream));
+ if (NS_SUCCEEDED(rv))
+ mSocketInput = do_QueryInterface(inStream);
+
+ return rv;
+}
+
+nsresult
+nsFtpControlConnection::WaitData(nsFtpControlConnectionListener *listener)
+{
+ LOG(("FTP:(%p) wait data [listener=%p]\n", this, listener));
+
+ // If listener is null, then simply disconnect the listener. Otherwise,
+ // ensure that we are listening.
+ if (!listener) {
+ mListener = nullptr;
+ return NS_OK;
+ }
+
+ NS_ENSURE_STATE(mSocketInput);
+
+ mListener = listener;
+ return mSocketInput->AsyncWait(this, 0, 0, NS_GetCurrentThread());
+}
+
+nsresult
+nsFtpControlConnection::Disconnect(nsresult status)
+{
+ if (!mSocket)
+ return NS_OK; // already disconnected
+
+ LOG_INFO(("FTP:(%p) CC disconnecting (%x)", this, status));
+
+ if (NS_FAILED(status)) {
+ // break cyclic reference!
+ mSocket->Close(status);
+ mSocket = nullptr;
+ mSocketInput->AsyncWait(nullptr, 0, 0, nullptr); // clear any observer
+ mSocketInput = nullptr;
+ mSocketOutput = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsFtpControlConnection::Write(const nsCSubstring& command)
+{
+ NS_ENSURE_STATE(mSocketOutput);
+
+ uint32_t len = command.Length();
+ uint32_t cnt;
+ nsresult rv = mSocketOutput->Write(command.Data(), len, &cnt);
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (len != cnt)
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
diff --git a/netwerk/protocol/ftp/nsFtpControlConnection.h b/netwerk/protocol/ftp/nsFtpControlConnection.h
new file mode 100644
index 000000000..5301bb9ce
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpControlConnection.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et ts=4 sts=4 sw=4 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFtpControlConnection_h___
+#define nsFtpControlConnection_h___
+
+#include "nsCOMPtr.h"
+
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+class nsIOutputStream;
+class nsIProxyInfo;
+class nsITransportEventSink;
+
+class nsFtpControlConnectionListener : public nsISupports {
+public:
+ /**
+ * Called when a chunk of data arrives on the control connection.
+ * @param data
+ * The new data or null if an error occurred.
+ * @param dataLen
+ * The data length in bytes.
+ */
+ virtual void OnControlDataAvailable(const char *data, uint32_t dataLen) = 0;
+
+ /**
+ * Called when an error occurs on the control connection.
+ * @param status
+ * A failure code providing more info about the error.
+ */
+ virtual void OnControlError(nsresult status) = 0;
+};
+
+class nsFtpControlConnection final : public nsIInputStreamCallback
+{
+ ~nsFtpControlConnection();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+
+ nsFtpControlConnection(const nsCSubstring& host, uint32_t port);
+
+ nsresult Connect(nsIProxyInfo* proxyInfo, nsITransportEventSink* eventSink);
+ nsresult Disconnect(nsresult status);
+ nsresult Write(const nsCSubstring& command);
+
+ bool IsAlive();
+
+ nsITransport *Transport() { return mSocket; }
+
+ /**
+ * Call this function to be notified asynchronously when there is data
+ * available for the socket. The listener passed to this method replaces
+ * any existing listener, and the listener can be null to disconnect the
+ * previous listener.
+ */
+ nsresult WaitData(nsFtpControlConnectionListener *listener);
+
+ uint32_t mServerType; // what kind of server is it.
+ nsString mPassword;
+ int32_t mSuspendedWrite;
+ nsCString mPwd;
+ uint32_t mSessionId;
+ bool mUseUTF8;
+
+private:
+ nsCString mHost;
+ uint32_t mPort;
+
+ nsCOMPtr<nsISocketTransport> mSocket;
+ nsCOMPtr<nsIOutputStream> mSocketOutput;
+ nsCOMPtr<nsIAsyncInputStream> mSocketInput;
+
+ RefPtr<nsFtpControlConnectionListener> mListener;
+};
+
+#endif
diff --git a/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp b/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp
new file mode 100644
index 000000000..46b12767a
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp
@@ -0,0 +1,426 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
+ * use in OS2
+ */
+
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/FTPChannelChild.h"
+using namespace mozilla;
+using namespace mozilla::net;
+
+#include "nsFtpProtocolHandler.h"
+#include "nsFTPChannel.h"
+#include "nsIStandardURL.h"
+#include "mozilla/Logging.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIObserverService.h"
+#include "nsEscape.h"
+#include "nsAlgorithm.h"
+
+//-----------------------------------------------------------------------------
+
+//
+// Log module for FTP Protocol logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=nsFtp:5
+// set MOZ_LOG_FILE=ftp.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file ftp.log.
+//
+LazyLogModule gFTPLog("nsFtp");
+#undef LOG
+#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+
+#define IDLE_TIMEOUT_PREF "network.ftp.idleConnectionTimeout"
+#define IDLE_CONNECTION_LIMIT 8 /* TODO pref me */
+
+#define QOS_DATA_PREF "network.ftp.data.qos"
+#define QOS_CONTROL_PREF "network.ftp.control.qos"
+
+nsFtpProtocolHandler *gFtpHandler = nullptr;
+
+//-----------------------------------------------------------------------------
+
+nsFtpProtocolHandler::nsFtpProtocolHandler()
+ : mIdleTimeout(-1)
+ , mSessionId(0)
+ , mControlQoSBits(0x00)
+ , mDataQoSBits(0x00)
+{
+ LOG(("FTP:creating handler @%x\n", this));
+
+ gFtpHandler = this;
+}
+
+nsFtpProtocolHandler::~nsFtpProtocolHandler()
+{
+ LOG(("FTP:destroying handler @%x\n", this));
+
+ NS_ASSERTION(mRootConnectionList.Length() == 0, "why wasn't Observe called?");
+
+ gFtpHandler = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsFtpProtocolHandler,
+ nsIProtocolHandler,
+ nsIProxiedProtocolHandler,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+nsresult
+nsFtpProtocolHandler::Init()
+{
+ if (IsNeckoChild())
+ NeckoChild::InitNeckoChild();
+
+ if (mIdleTimeout == -1) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = branch->GetIntPref(IDLE_TIMEOUT_PREF, &mIdleTimeout);
+ if (NS_FAILED(rv))
+ mIdleTimeout = 5*60; // 5 minute default
+
+ rv = branch->AddObserver(IDLE_TIMEOUT_PREF, this, true);
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t val;
+ rv = branch->GetIntPref(QOS_DATA_PREF, &val);
+ if (NS_SUCCEEDED(rv))
+ mDataQoSBits = (uint8_t) clamped(val, 0, 0xff);
+
+ rv = branch->AddObserver(QOS_DATA_PREF, this, true);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = branch->GetIntPref(QOS_CONTROL_PREF, &val);
+ if (NS_SUCCEEDED(rv))
+ mControlQoSBits = (uint8_t) clamped(val, 0, 0xff);
+
+ rv = branch->AddObserver(QOS_CONTROL_PREF, this, true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this,
+ "network:offline-about-to-go-offline",
+ true);
+
+ observerService->AddObserver(this,
+ "net:clear-active-logins",
+ true);
+ }
+
+ return NS_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::GetScheme(nsACString &result)
+{
+ result.AssignLiteral("ftp");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::GetDefaultPort(int32_t *result)
+{
+ *result = 21;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ *result = URI_STD | ALLOWS_PROXY | ALLOWS_PROXY_HTTP |
+ URI_LOADABLE_BY_ANYONE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+ nsAutoCString spec(aSpec);
+ spec.Trim(" \t\n\r"); // Match NS_IsAsciiWhitespace instead of HTML5
+
+ char *fwdPtr = spec.BeginWriting();
+
+ // now unescape it... %xx reduced inline to resulting character
+
+ int32_t len = NS_UnescapeURL(fwdPtr);
+
+ // NS_UnescapeURL() modified spec's buffer, truncate to ensure
+ // spec knows its new length.
+ spec.Truncate(len);
+
+ // return an error if we find a NUL, CR, or LF in the path
+ if (spec.FindCharInSet(CRLF) >= 0 || spec.FindChar('\0') >= 0)
+ return NS_ERROR_MALFORMED_URI;
+
+ nsresult rv;
+ nsCOMPtr<nsIStandardURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY, 21, aSpec, aCharset, aBaseURI);
+ if (NS_FAILED(rv)) return rv;
+
+ return CallQueryInterface(url, result);
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::NewChannel2(nsIURI* url,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ return NewProxiedChannel2(url, nullptr, 0, nullptr, aLoadInfo, result);
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::NewChannel(nsIURI* url, nsIChannel* *result)
+{
+ return NewChannel2(url, nullptr, result);
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::NewProxiedChannel2(nsIURI* uri, nsIProxyInfo* proxyInfo,
+ uint32_t proxyResolveFlags,
+ nsIURI *proxyURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel* *result)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ RefPtr<nsBaseChannel> channel;
+ if (IsNeckoChild())
+ channel = new FTPChannelChild(uri);
+ else
+ channel = new nsFtpChannel(uri, proxyInfo);
+
+ nsresult rv = channel->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // set the loadInfo on the new channel
+ rv = channel->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ channel.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::NewProxiedChannel(nsIURI* uri, nsIProxyInfo* proxyInfo,
+ uint32_t proxyResolveFlags,
+ nsIURI *proxyURI,
+ nsIChannel* *result)
+{
+ return NewProxiedChannel2(uri, proxyInfo, proxyResolveFlags,
+ proxyURI, nullptr /*loadinfo*/,
+ result);
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ *_retval = (port == 21 || port == 22);
+ return NS_OK;
+}
+
+// connection cache methods
+
+void
+nsFtpProtocolHandler::Timeout(nsITimer *aTimer, void *aClosure)
+{
+ LOG(("FTP:timeout reached for %p\n", aClosure));
+
+ bool found = gFtpHandler->mRootConnectionList.RemoveElement(aClosure);
+ if (!found) {
+ NS_ERROR("timerStruct not found");
+ return;
+ }
+
+ timerStruct* s = (timerStruct*)aClosure;
+ delete s;
+}
+
+nsresult
+nsFtpProtocolHandler::RemoveConnection(nsIURI *aKey, nsFtpControlConnection* *_retval)
+{
+ NS_ASSERTION(_retval, "null pointer");
+ NS_ASSERTION(aKey, "null pointer");
+
+ *_retval = nullptr;
+
+ nsAutoCString spec;
+ aKey->GetPrePath(spec);
+
+ LOG(("FTP:removing connection for %s\n", spec.get()));
+
+ timerStruct* ts = nullptr;
+ uint32_t i;
+ bool found = false;
+
+ for (i=0;i<mRootConnectionList.Length();++i) {
+ ts = mRootConnectionList[i];
+ if (strcmp(spec.get(), ts->key) == 0) {
+ found = true;
+ mRootConnectionList.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ if (!found)
+ return NS_ERROR_FAILURE;
+
+ // swap connection ownership
+ ts->conn.forget(_retval);
+ delete ts;
+
+ return NS_OK;
+}
+
+nsresult
+nsFtpProtocolHandler::InsertConnection(nsIURI *aKey, nsFtpControlConnection *aConn)
+{
+ NS_ASSERTION(aConn, "null pointer");
+ NS_ASSERTION(aKey, "null pointer");
+
+ if (aConn->mSessionId != mSessionId)
+ return NS_ERROR_FAILURE;
+
+ nsAutoCString spec;
+ aKey->GetPrePath(spec);
+
+ LOG(("FTP:inserting connection for %s\n", spec.get()));
+
+ nsresult rv;
+ nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ timerStruct* ts = new timerStruct();
+ if (!ts)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = timer->InitWithFuncCallback(nsFtpProtocolHandler::Timeout,
+ ts,
+ mIdleTimeout*1000,
+ nsITimer::TYPE_REPEATING_SLACK);
+ if (NS_FAILED(rv)) {
+ delete ts;
+ return rv;
+ }
+
+ ts->key = ToNewCString(spec);
+ if (!ts->key) {
+ delete ts;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // ts->conn is a RefPtr
+ ts->conn = aConn;
+ ts->timer = timer;
+
+ //
+ // limit number of idle connections. if limit is reached, then prune
+ // eldest connection with matching key. if none matching, then prune
+ // eldest connection.
+ //
+ if (mRootConnectionList.Length() == IDLE_CONNECTION_LIMIT) {
+ uint32_t i;
+ for (i=0;i<mRootConnectionList.Length();++i) {
+ timerStruct *candidate = mRootConnectionList[i];
+ if (strcmp(candidate->key, ts->key) == 0) {
+ mRootConnectionList.RemoveElementAt(i);
+ delete candidate;
+ break;
+ }
+ }
+ if (mRootConnectionList.Length() == IDLE_CONNECTION_LIMIT) {
+ timerStruct *eldest = mRootConnectionList[0];
+ mRootConnectionList.RemoveElementAt(0);
+ delete eldest;
+ }
+ }
+
+ mRootConnectionList.AppendElement(ts);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIObserver
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ LOG(("FTP:observing [%s]\n", aTopic));
+
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(aSubject);
+ if (!branch) {
+ NS_ERROR("no prefbranch");
+ return NS_ERROR_UNEXPECTED;
+ }
+ int32_t val;
+ nsresult rv = branch->GetIntPref(IDLE_TIMEOUT_PREF, &val);
+ if (NS_SUCCEEDED(rv))
+ mIdleTimeout = val;
+
+ rv = branch->GetIntPref(QOS_DATA_PREF, &val);
+ if (NS_SUCCEEDED(rv))
+ mDataQoSBits = (uint8_t) clamped(val, 0, 0xff);
+
+ rv = branch->GetIntPref(QOS_CONTROL_PREF, &val);
+ if (NS_SUCCEEDED(rv))
+ mControlQoSBits = (uint8_t) clamped(val, 0, 0xff);
+ } else if (!strcmp(aTopic, "network:offline-about-to-go-offline")) {
+ ClearAllConnections();
+ } else if (!strcmp(aTopic, "net:clear-active-logins")) {
+ ClearAllConnections();
+ mSessionId++;
+ } else {
+ NS_NOTREACHED("unexpected topic");
+ }
+
+ return NS_OK;
+}
+
+void
+nsFtpProtocolHandler::ClearAllConnections()
+{
+ uint32_t i;
+ for (i=0;i<mRootConnectionList.Length();++i)
+ delete mRootConnectionList[i];
+ mRootConnectionList.Clear();
+}
diff --git a/netwerk/protocol/ftp/nsFtpProtocolHandler.h b/netwerk/protocol/ftp/nsFtpProtocolHandler.h
new file mode 100644
index 000000000..0bc3b0dfd
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpProtocolHandler.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFtpProtocolHandler_h__
+#define nsFtpProtocolHandler_h__
+
+#include "nsFtpControlConnection.h"
+#include "nsIProxiedProtocolHandler.h"
+#include "nsTArray.h"
+#include "nsITimer.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+
+//-----------------------------------------------------------------------------
+
+class nsFtpProtocolHandler final : public nsIProxiedProtocolHandler
+ , public nsIObserver
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIPROXIEDPROTOCOLHANDLER
+ NS_DECL_NSIOBSERVER
+
+ nsFtpProtocolHandler();
+
+ nsresult Init();
+
+ // FTP Connection list access
+ nsresult InsertConnection(nsIURI *aKey, nsFtpControlConnection *aConn);
+ nsresult RemoveConnection(nsIURI *aKey, nsFtpControlConnection **aConn);
+ uint32_t GetSessionId() { return mSessionId; }
+
+ uint8_t GetDataQoSBits() { return mDataQoSBits; }
+ uint8_t GetControlQoSBits() { return mControlQoSBits; }
+
+private:
+ virtual ~nsFtpProtocolHandler();
+
+ // Stuff for the timer callback function
+ struct timerStruct {
+ nsCOMPtr<nsITimer> timer;
+ RefPtr<nsFtpControlConnection> conn;
+ char *key;
+
+ timerStruct() : key(nullptr) {}
+
+ ~timerStruct() {
+ if (timer)
+ timer->Cancel();
+ if (key)
+ free(key);
+ if (conn) {
+ conn->Disconnect(NS_ERROR_ABORT);
+ }
+ }
+ };
+
+ static void Timeout(nsITimer *aTimer, void *aClosure);
+ void ClearAllConnections();
+
+ nsTArray<timerStruct*> mRootConnectionList;
+
+ int32_t mIdleTimeout;
+
+ // When "clear active logins" is performed, all idle connection are dropped
+ // and mSessionId is incremented. When nsFtpState wants to insert idle
+ // connection we refuse to cache if its mSessionId is different (i.e.
+ // control connection had been created before last "clear active logins" was
+ // performed.
+ uint32_t mSessionId;
+
+ uint8_t mControlQoSBits;
+ uint8_t mDataQoSBits;
+};
+
+//-----------------------------------------------------------------------------
+
+extern nsFtpProtocolHandler *gFtpHandler;
+
+#include "mozilla/Logging.h"
+extern mozilla::LazyLogModule gFTPLog;
+
+#endif // !nsFtpProtocolHandler_h__
diff --git a/netwerk/protocol/ftp/nsIFTPChannel.idl b/netwerk/protocol/ftp/nsIFTPChannel.idl
new file mode 100644
index 000000000..de8298384
--- /dev/null
+++ b/netwerk/protocol/ftp/nsIFTPChannel.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface may be used to determine if a channel is a FTP channel.
+ */
+[scriptable, uuid(07f0d5cd-1fd5-4aa3-b6fc-665bdc5dbf9f)]
+interface nsIFTPChannel : nsISupports
+{
+ attribute PRTime lastModifiedTime;
+};
+
+/**
+ * This interface may be defined as a notification callback on the FTP
+ * channel. It allows a consumer to receive a log of the FTP control
+ * connection conversation.
+ */
+[scriptable, uuid(455d4234-0330-43d2-bbfb-99afbecbfeb0)]
+interface nsIFTPEventSink : nsISupports
+{
+ /**
+ * XXX document this method! (see bug 328915)
+ */
+ void OnFTPControlLog(in boolean server, in string msg);
+};
diff --git a/netwerk/protocol/ftp/nsIFTPChannelParentInternal.idl b/netwerk/protocol/ftp/nsIFTPChannelParentInternal.idl
new file mode 100644
index 000000000..2642c804b
--- /dev/null
+++ b/netwerk/protocol/ftp/nsIFTPChannelParentInternal.idl
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This is an internal interface for FTP parent channel.
+ */
+[builtinclass, uuid(87b58410-83cb-42a7-b57b-27c07ef828d7)]
+interface nsIFTPChannelParentInternal : nsISupports
+{
+ void setErrorMsg(in string msg, in boolean useUTF8);
+};
diff --git a/netwerk/protocol/ftp/test/frametest/contents.html b/netwerk/protocol/ftp/test/frametest/contents.html
new file mode 100644
index 000000000..077b8d8f7
--- /dev/null
+++ b/netwerk/protocol/ftp/test/frametest/contents.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<h2>Click a link to the left</h2>
+</body>
+</html>
diff --git a/netwerk/protocol/ftp/test/frametest/index.html b/netwerk/protocol/ftp/test/frametest/index.html
new file mode 100644
index 000000000..78c0dad58
--- /dev/null
+++ b/netwerk/protocol/ftp/test/frametest/index.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>FTP Frameset Test</title>
+
+</head>
+
+<frameset cols="30%,70%" name="ftp_main_frame">
+ <frame src="menu.html" name="ftp_menu" scrolling="yes" marginwidth="0" marginheight="0" noresize>
+ <frame src="contents.html" name="ftp_content" scrolling="YES" marginwidth="0" marginheight="0" noresize>
+</frameset>
+
+</html>
+
diff --git a/netwerk/protocol/ftp/test/frametest/menu.html b/netwerk/protocol/ftp/test/frametest/menu.html
new file mode 100644
index 000000000..afe3afcf2
--- /dev/null
+++ b/netwerk/protocol/ftp/test/frametest/menu.html
@@ -0,0 +1,373 @@
+<html>
+
+<script language="javascript">
+
+<!--
+
+function add_location(url)
+{
+ // faster to do this in one assignment, but who really cares.
+
+ var string = '<LI> <a href=\"javascript: ftp_open(\'';
+ string += url;
+ string += '\')\"';
+ string += 'onmouseover=\"window.status=\'ftp://';
+ string += url;
+ string += '\'; return true; \">';
+ string += url;
+ string += '</a>';
+ document.writeln(string);
+}
+
+function ftp_open(url)
+{
+ url = 'ftp://' + url;
+ parent.ftp_content.location=url;
+}
+
+// I like this format.
+document.writeln('<pre>');
+
+document.writeln('<br><OL><b>Company sites</b>');
+ add_location('ftp.mozilla.org');
+ add_location('ftp.sun.com');
+ add_location('ftp.iplanet.com');
+ add_location('ftp.netscape.com');
+ add_location('ftp.apple.com');
+ add_location('ftp.microsoft.com');
+ add_location('ftp.netmanage.com');
+
+document.writeln('</OL><br><OL><b>Misc sites</b>');
+ add_location('cal044202.student.utwente.nl');
+ add_location('download.intel.com');
+ add_location('fddisunsite.oit.unc.edu');
+ add_location('ftp.abas.de');
+ add_location('ftp.acm.org');
+ add_location('ftp.acomp.hu');
+ add_location('ftp.acri.fr');
+ add_location('ftp.alaska.edu');
+ add_location('ftp.altera.com');
+ add_location('ftp.amsat.org');
+ add_location('ftp.amtp.cam.ac.uk');
+ add_location('ftp.ar.freebsd.org');
+ add_location('ftp.ari.net');
+ add_location('ftp.arl.mil');
+ add_location('ftp.astro.ulg.ac.be');
+ add_location('ftp.avery-zweckform.com');
+ add_location('ftp.awi-bremerhaven.de');
+ add_location('ftp.axime-is.fr');
+ add_location('ftp.ba.cnr.it');
+ add_location('ftp.bath.ac.uk');
+ add_location('ftp.bic.mni.mcgill.ca');
+ add_location('ftp.biomed.ruhr-uni-bochum.de');
+ add_location('ftp.boerde.de');
+ add_location('ftp.bond.edu.au');
+ add_location('ftp.boulder.ibm.com');
+ add_location('ftp.brics.dk');
+ add_location('ftp.bris.ac.uk');
+ add_location('ftp.cablelabs.com');
+ add_location('ftp.cac.psu.edu');
+ add_location('ftp.cadpoint.se');
+ add_location('ftp.cas.cz');
+ add_location('ftp.cciw.ca');
+ add_location('ftp.ccs.queensu.ca');
+ add_location('ftp.ccsi.com');
+ add_location('ftp.cdrom.com');
+ add_location('ftp.cea.fr');
+ add_location('ftp.celestial.com');
+ add_location('ftp.cert.fr');
+ add_location('ftp.cgd.ucar.edu');
+ add_location('ftp.chiba-u.ac.jp');
+ add_location('ftp.cis.ksu.edu');
+ add_location('ftp.citi2.fr');
+ add_location('ftp.cityline.net');
+ add_location('ftp.cnam.fr');
+ add_location('ftp.cohesive.com');
+ add_location('ftp.contrib.net');
+ add_location('ftp.create.ucsb.edu');
+ add_location('ftp.cronyx.ru');
+ add_location('ftp.cs.arizona.edu');
+ add_location('ftp.cs.colorado.edu');
+ add_location('ftp.cs.concordia.ca');
+ add_location('ftp.cs.helsinki.fi');
+ add_location('ftp.cs.jhu.edu');
+ add_location('ftp.cs.monash.edu.au');
+ add_location('ftp.cs.ohiou.edu');
+ add_location('ftp.cs.rug.nl');
+ add_location('ftp.cs.toronto.edu');
+ add_location('ftp.cs.umanitoba.ca');
+ add_location('ftp.cs.uni-dortmund.de');
+ add_location('ftp.cs.vu.nl');
+ add_location('ftp.cse.cuhk.edu.hk');
+ add_location('ftp.cse.unsw.edu.au');
+ add_location('ftp.csse.monash.edu.au');
+ add_location('ftp.csus.edu');
+ add_location('ftp.cullasaja.com');
+ add_location('ftp.daimi.au.dk');
+ add_location('ftp.dcs.qmw.ac.uk');
+ add_location('ftp.delorie.com');
+ add_location('ftp.dementia.org');
+ add_location('ftp.dfki.uni-kl.de');
+ add_location('ftp.dgs.monash.edu.au');
+ add_location('ftp.dis.strath.ac.uk');
+ add_location('ftp.dosis.uni-dortmund.de');
+ add_location('ftp.duke.edu');
+ add_location('ftp.duplexx.com');
+ add_location('ftp.ece.ucdavis.edu');
+ add_location('ftp.ee.lbl.gov');
+ add_location('ftp.ee.rochester.edu');
+ add_location('ftp.ee.uts.edu.au');
+ add_location('ftp.efrei.fr');
+ add_location('ftp.elet.polimi.it');
+ add_location('ftp.elite.net');
+ add_location('ftp.embl-hamburg.de');
+ add_location('ftp.eng.buffalo.edu');
+ add_location('ftp.engr.uark.edu');
+ add_location('ftp.eni.co.jp');
+ add_location('ftp.enst-bretagne.fr');
+ add_location('ftp.epix.net');
+ add_location('ftp.eskimo.com');
+ add_location('ftp.essential.org');
+ add_location('ftp.eunet.fi');
+ add_location('ftp.eurexpo.com');
+ add_location('ftp.ex.ac.uk');
+ add_location('ftp.faximum.com');
+ add_location('ftp.fernuni-hagen.de');
+ add_location('ftp.fh-dortmund.de');
+ add_location('ftp.fit.qut.edu.au');
+ add_location('ftp.forum.swarthmore.edu');
+ add_location('ftp.fsu.edu');
+ add_location('ftp.ftp.epson.com');
+ add_location('ftp.fu-berlin.de');
+ add_location('ftp.fujixerox.co.jp');
+ add_location('ftp.game.org');
+ add_location('ftp.ge.ucl.ac.uk');
+ add_location('ftp.genetics.wisc.edu');
+ add_location('ftp.geo.uu.nl');
+ add_location('ftp.geom.umn.edu');
+ add_location('ftp.gfdl.gov');
+ add_location('ftp.gigo.com');
+ add_location('ftp.giss.nasa.gov');
+ add_location('ftp.globalnet.co.uk');
+ add_location('ftp.gnu.org');
+ add_location('ftp.gnu.vbs.at');
+ add_location('ftp.gps.caltech.edu');
+ add_location('ftp.grau-wzs.de');
+ add_location('ftp.gsoc.dlr.de');
+ add_location('ftp.gutenberg.org');
+ add_location('ftp.hawaii.edu');
+ add_location('ftp.hep.net');
+ add_location('ftp.hgc.edu');
+ add_location('ftp.hgmp.mrc.ac.uk');
+ add_location('ftp.hugin.dk');
+ add_location('ftp.ic.tsu.ru');
+ add_location('ftp.icce.rug.nl');
+ add_location('ftp.icon-stl.net');
+ add_location('ftp.icor.fr');
+ add_location('ftp.ics.uci.edu');
+ add_location('ftp.idsia.ch');
+ add_location('ftp.ifm.liu.se');
+ add_location('ftp.ifm.uni-kiel.de');
+ add_location('ftp.iglou.com');
+ add_location('ftp.ign.fr');
+ add_location('ftp.imag.fr');
+ add_location('ftp.inel.gov');
+ add_location('ftp.inf.ethz.ch');
+ add_location('ftp.inf.puc-rio.br');
+ add_location('ftp.infoflex.se');
+ add_location('ftp.informatik.rwth-aachen.de');
+ add_location('ftp.informatik.uni-bremen.de');
+ add_location('ftp.informatik.uni-hannover.de');
+ add_location('ftp.infoscandic.se');
+ add_location('ftp.intel.com');
+ add_location('ftp.intergraph.com');
+ add_location('ftp.ionet.net');
+ add_location('ftp.ipc.chiba-u.ac.jp');
+ add_location('ftp.ips.cs.tu-bs.de');
+ add_location('ftp.iram.rwth-aachen.de');
+ add_location('ftp.is.co.za');
+ add_location('ftp.isoc.org');
+ add_location('ftp.iteso.mx');
+ add_location('ftp.ivd.uni-stuttgart.de');
+ add_location('ftp.iway.fr');
+ add_location('ftp.jcu.edu.au');
+ add_location('ftp.jhuapl.edu');
+ add_location('ftp.jpix.ad.jp');
+ add_location('ftp.karlin.mff.cuni.cz');
+ add_location('ftp.kfu.com');
+ add_location('ftp.kfunigraz.ac.at');
+ add_location('ftp.khm.de');
+ add_location('ftp.ki.se');
+ add_location('ftp.komkon.org');
+ add_location('ftp.laas.fr');
+ add_location('ftp.lanl.gov');
+ add_location('ftp.lantronix.com');
+ add_location('ftp.lava.net');
+ add_location('ftp.lcs.mit.edu');
+ add_location('ftp.legend.co.uk');
+ add_location('ftp.leidenuniv.nl');
+ add_location('ftp.let.rug.nl');
+ add_location('ftp.linux.co.uk');
+ add_location('ftp.linux.unife.it');
+ add_location('ftp.liv.ac.uk');
+ add_location('ftp.livingston.com');
+ add_location('ftp.lnt.e-technik.tu-muenchen.de');
+ add_location('ftp.lsu.edu');
+ add_location('ftp.lth.se');
+ add_location('ftp.lysator.liu.se');
+ add_location('ftp.mailbase.ac.uk');
+ add_location('ftp.mainstream.net');
+ add_location('ftp.maricopa.edu');
+ add_location('ftp.math.fu-berlin.de');
+ add_location('ftp.math.hr');
+ add_location('ftp.math.utah.edu');
+ add_location('ftp.mathematik.uni-marburg.de');
+ add_location('ftp.maths.tcd.ie');
+ add_location('ftp.maths.usyd.edu.au');
+ add_location('ftp.mathworks.com');
+ add_location('ftp.mbb.ki.se');
+ add_location('ftp.mbt.ru');
+ add_location('ftp.mcs.net');
+ add_location('ftp.mcs.vuw.ac.nz');
+ add_location('ftp.media.mit.edu');
+ add_location('ftp.meme.com');
+ add_location('ftp.merl.com');
+ add_location('ftp.microport.com');
+ add_location('ftp.mms.de');
+ add_location('ftp.mpce.mq.edu.au');
+ add_location('ftp.mpgn.com');
+ add_location('ftp.mpipf-muenchen.mpg.de');
+ add_location('ftp.mscf.uky.edu');
+ add_location('ftp.natinst.com');
+ add_location('ftp.ncsa.uiuc.edu');
+ add_location('ftp.net-tel.co.uk');
+ add_location('ftp.net.cmu.edu');
+ add_location('ftp.netsw.org');
+ add_location('ftp.new-york.net');
+ add_location('ftp.nis.net');
+ add_location('ftp.nlm.nih.gov');
+ add_location('ftp.nmt.edu');
+ add_location('ftp.noao.edu');
+ add_location('ftp.ntnu.no');
+ add_location('ftp.nwu.edu');
+ add_location('ftp.nysaes.cornell.edu');
+ add_location('ftp.observ.u-bordeaux.fr');
+ add_location('ftp.oit.unc.edu');
+ add_location('ftp.oldenbourg.de');
+ add_location('ftp.omg.unb.ca');
+ add_location('ftp.onecall.net');
+ add_location('ftp.ornl.gov');
+ add_location('ftp.ozone.fmi.fi');
+ add_location('ftp.pacific.net.hk');
+ add_location('ftp.panix.com');
+ add_location('ftp.pcnet.com');
+ add_location('ftp.phred.org');
+ add_location('ftp.pnl.gov');
+ add_location('ftp.prairienet.org');
+ add_location('ftp.proxad.net');
+ add_location('ftp.proximity.com.au');
+ add_location('ftp.psg.com');
+ add_location('ftp.psy.uq.edu.au');
+ add_location('ftp.psychologie.uni-freiburg.de');
+ add_location('ftp.pwr.wroc.pl');
+ add_location('ftp.python.org');
+ add_location('ftp.quantum.de');
+ add_location('ftp.ra.phy.cam.ac.uk');
+ add_location('ftp.rasip.fer.hr');
+ add_location('ftp.rbgkew.org.uk');
+ add_location('ftp.rcsb.org');
+ add_location('ftp.realtime.net');
+ add_location('ftp.red-bean.com');
+ add_location('ftp.redac.co.uk');
+ add_location('ftp.redac.fr');
+ add_location('ftp.rediris.es');
+ add_location('ftp.rgn.it');
+ add_location('ftp.rice.edu');
+ add_location('ftp.rkk.hu');
+ add_location('ftp.robelle.com');
+ add_location('ftp.rose.hp.com');
+ add_location('ftp.rt66.com');
+ add_location('ftp.ruhr-uni-bochum.de');
+ add_location('ftp.rz.uni-frankfurt.de');
+ add_location('ftp.sat.dundee.ac.uk');
+ add_location('ftp.saugus.net');
+ add_location('ftp.schrodinger.com');
+ add_location('ftp.science-computing.de');
+ add_location('ftp.science.unitn.it');
+ add_location('ftp.sco.com');
+ add_location('ftp.scs.leeds.ac.uk');
+ add_location('ftp.scsr.nevada.edu');
+ add_location('ftp.sd.monash.edu.au');
+ add_location('ftp.sdv.fr');
+ add_location('ftp.selapo.vwl.uni-muenchen.de');
+ add_location('ftp.serv.net');
+ add_location('ftp.sgi.leeds.ac.uk');
+ add_location('ftp.shore.net');
+ add_location('ftp.socsci.auc.dk');
+ add_location('ftp.space.net');
+ add_location('ftp.spec.org');
+ add_location('ftp.stallion.com');
+ add_location('ftp.starnet.de');
+ add_location('ftp.stat.math.ethz.ch');
+ add_location('ftp.stat.umn.edu');
+ add_location('ftp.std.com');
+ add_location('ftp.structchem.uni-essen.de');
+ add_location('ftp.sunsite.org.uk');
+ add_location('ftp.syd.dms.csiro.au');
+ add_location('ftp.tapr.org');
+ add_location('ftp.teco.uni-karlsruhe.de');
+ add_location('ftp.tenon.com');
+ add_location('ftp.tierzucht.uni-kiel.de');
+ add_location('ftp.tnt.uni-hannover.de');
+ add_location('ftp.tu-clausthal.de');
+ add_location('ftp.uci.edu');
+ add_location('ftp.ucsd.edu');
+ add_location('ftp.udel.edu');
+ add_location('ftp.uec.ac.jp');
+ add_location('ftp.uibk.ac.at');
+ add_location('ftp.uit.co.uk');
+ add_location('ftp.uji.es');
+ add_location('ftp.uke.uni-hamburg.de');
+ add_location('ftp.ulcc.ac.uk');
+ add_location('ftp.um.es');
+ add_location('ftp.umi.cs.tu-bs.de');
+ add_location('ftp.uni-augsburg.de');
+ add_location('ftp.uni-dortmund.de');
+ add_location('ftp.uni-hannover.de');
+ add_location('ftp.uni-magdeburg.de');
+ add_location('ftp.unidata.ucar.edu');
+ add_location('ftp.unige.ch');
+ add_location('ftp.univ-aix.fr');
+ add_location('ftp.upc.es');
+ add_location('ftp.uradio.ku.dk');
+ add_location('ftp.uralexpress.ru');
+ add_location('ftp.urc.ac.ru');
+ add_location('ftp.ut.ee');
+ add_location('ftp.uunet.ca');
+ add_location('ftp.uwo.ca');
+ add_location('ftp.vaxxine.com');
+ add_location('ftp.visi.com');
+ add_location('ftp.vub.ac.be');
+ add_location('ftp.wfu.edu');
+ add_location('ftp.win.tue.nl');
+ add_location('ftp.wolfe.net');
+ add_location('sunsite.cnlab-switch.ch');
+ add_location('sunsite.sut.ac.jp');
+ add_location('ftp.cuhk.edu.hk');
+ add_location('ftp.cetis.hvu.nl');
+ add_location('ftp.clinet.fi');
+ add_location('ftp.gamma.ru');
+ add_location('ftp.itv.se');
+ add_location('ftp.cs.rpi.edu');
+ add_location('ftp.carrier.kiev.ua');
+ add_location('ftp.rosnet.ru');
+ add_location('ftp.nsk.su');
+ add_location('ftp.southcom.com.au');
+
+// -->
+
+</script>
+<body>
+<br><br><br>
+</body>
+</html>
diff --git a/netwerk/protocol/http/ASpdySession.cpp b/netwerk/protocol/http/ASpdySession.cpp
new file mode 100644
index 000000000..6bd54c7c0
--- /dev/null
+++ b/netwerk/protocol/http/ASpdySession.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+/*
+ Currently supported is h2
+*/
+
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+
+#include "ASpdySession.h"
+#include "PSpdyPush.h"
+#include "Http2Push.h"
+#include "Http2Session.h"
+
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace net {
+
+ASpdySession::ASpdySession()
+{
+}
+
+ASpdySession::~ASpdySession() = default;
+
+ASpdySession *
+ASpdySession::NewSpdySession(uint32_t version,
+ nsISocketTransport *aTransport)
+{
+ // This is a necko only interface, so we can enforce version
+ // requests as a precondition
+ MOZ_ASSERT(version == HTTP_VERSION_2,
+ "Unsupported spdy version");
+
+ // Don't do a runtime check of IsSpdyV?Enabled() here because pref value
+ // may have changed since starting negotiation. The selected protocol comes
+ // from a list provided in the SERVER HELLO filtered by our acceptable
+ // versions, so there is no risk of the server ignoring our prefs.
+
+ Telemetry::Accumulate(Telemetry::SPDY_VERSION2, version);
+
+ return new Http2Session(aTransport, version);
+}
+
+SpdyInformation::SpdyInformation()
+{
+ // highest index of enabled protocols is the
+ // most preferred for ALPN negotiaton
+ Version[0] = HTTP_VERSION_2;
+ VersionString[0] = NS_LITERAL_CSTRING("h2");
+ ALPNCallbacks[0] = Http2Session::ALPNCallback;
+}
+
+bool
+SpdyInformation::ProtocolEnabled(uint32_t index) const
+{
+ MOZ_ASSERT(index < kCount, "index out of range");
+
+ return gHttpHandler->IsHttp2Enabled();
+}
+
+nsresult
+SpdyInformation::GetNPNIndex(const nsACString &npnString,
+ uint32_t *result) const
+{
+ if (npnString.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ for (uint32_t index = 0; index < kCount; ++index) {
+ if (npnString.Equals(VersionString[index])) {
+ *result = index;
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+//////////////////////////////////////////
+// SpdyPushCache
+//////////////////////////////////////////
+
+SpdyPushCache::SpdyPushCache()
+{
+}
+
+SpdyPushCache::~SpdyPushCache()
+{
+ mHashHttp2.Clear();
+}
+
+bool
+SpdyPushCache::RegisterPushedStreamHttp2(nsCString key,
+ Http2PushedStream *stream)
+{
+ LOG3(("SpdyPushCache::RegisterPushedStreamHttp2 %s 0x%X\n",
+ key.get(), stream->StreamID()));
+ if(mHashHttp2.Get(key)) {
+ LOG3(("SpdyPushCache::RegisterPushedStreamHttp2 %s 0x%X duplicate key\n",
+ key.get(), stream->StreamID()));
+ return false;
+ }
+ mHashHttp2.Put(key, stream);
+ return true;
+}
+
+Http2PushedStream *
+SpdyPushCache::RemovePushedStreamHttp2(nsCString key)
+{
+ Http2PushedStream *rv = mHashHttp2.Get(key);
+ LOG3(("SpdyPushCache::RemovePushedStreamHttp2 %s 0x%X\n",
+ key.get(), rv ? rv->StreamID() : 0));
+ if (rv)
+ mHashHttp2.Remove(key);
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/protocol/http/ASpdySession.h b/netwerk/protocol/http/ASpdySession.h
new file mode 100644
index 000000000..e116d423b
--- /dev/null
+++ b/netwerk/protocol/http/ASpdySession.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ASpdySession_h
+#define mozilla_net_ASpdySession_h
+
+#include "nsAHttpTransaction.h"
+#include "prinrval.h"
+#include "nsString.h"
+
+class nsISocketTransport;
+
+namespace mozilla { namespace net {
+
+class ASpdySession : public nsAHttpTransaction
+{
+public:
+ ASpdySession();
+ virtual ~ASpdySession();
+
+ virtual bool AddStream(nsAHttpTransaction *, int32_t,
+ bool, nsIInterfaceRequestor *) = 0;
+ virtual bool CanReuse() = 0;
+ virtual bool RoomForMoreStreams() = 0;
+ virtual PRIntervalTime IdleTime() = 0;
+ virtual uint32_t ReadTimeoutTick(PRIntervalTime now) = 0;
+ virtual void DontReuse() = 0;
+
+ static ASpdySession *NewSpdySession(uint32_t version, nsISocketTransport *);
+
+ // MaybeReTunnel() is called by the connection manager when it cannot
+ // dispatch a tunneled transaction. That might be because the tunnels it
+ // expects to see are dead (and we may or may not be able to make more),
+ // or it might just need to wait longer for one of them to become free.
+ //
+ // return true if the session takes back ownership of the transaction from
+ // the connection manager.
+ virtual bool MaybeReTunnel(nsAHttpTransaction *) = 0;
+
+ virtual void PrintDiagnostics (nsCString &log) = 0;
+
+ bool ResponseTimeoutEnabled() const override final {
+ return true;
+ }
+
+ virtual void SendPing() = 0;
+
+ const static uint32_t kSendingChunkSize = 4095;
+ const static uint32_t kTCPSendBufferSize = 131072;
+
+ // This is roughly the amount of data a suspended channel will have to
+ // buffer before h2 flow control kicks in.
+ const static uint32_t kInitialRwin = 12 * 1024 * 1024; // 12MB
+
+ const static uint32_t kDefaultMaxConcurrent = 100;
+
+ // soft errors are errors that terminate a stream without terminating the
+ // connection. In general non-network errors are stream errors as well
+ // as network specific items like cancels.
+ bool SoftStreamError(nsresult code)
+ {
+ if (NS_SUCCEEDED(code) || code == NS_BASE_STREAM_WOULD_BLOCK) {
+ return false;
+ }
+
+ // this could go either way, but because there are network instances of
+ // it being a hard error we should consider it hard.
+ if (code == NS_ERROR_FAILURE || code == NS_ERROR_OUT_OF_MEMORY) {
+ return false;
+ }
+
+ if (NS_ERROR_GET_MODULE(code) != NS_ERROR_MODULE_NETWORK) {
+ return true;
+ }
+
+ // these are network specific soft errors
+ return (code == NS_BASE_STREAM_CLOSED || code == NS_BINDING_FAILED ||
+ code == NS_BINDING_ABORTED || code == NS_BINDING_REDIRECTED ||
+ code == NS_ERROR_INVALID_CONTENT_ENCODING ||
+ code == NS_BINDING_RETARGETED || code == NS_ERROR_CORRUPTED_CONTENT);
+ }
+};
+
+typedef bool (*ALPNCallback) (nsISupports *); // nsISSLSocketControl is typical
+
+// this is essentially a single instantiation as a member of nsHttpHandler.
+// It could be all static except using static ctors of XPCOM objects is a
+// bad idea.
+class SpdyInformation
+{
+public:
+ SpdyInformation();
+ ~SpdyInformation() {}
+
+ static const uint32_t kCount = 1;
+
+ // determine the index (0..kCount-1) of the spdy information that
+ // correlates to the npn string. NS_FAILED() if no match is found.
+ nsresult GetNPNIndex(const nsACString &npnString, uint32_t *result) const;
+
+ // determine if a version of the protocol is enabled for index < kCount
+ bool ProtocolEnabled(uint32_t index) const;
+
+ uint8_t Version[kCount]; // telemetry enum e.g. SPDY_VERSION_31
+ nsCString VersionString[kCount]; // npn string e.g. "spdy/3.1"
+
+ // the ALPNCallback function allows the protocol stack to decide whether or
+ // not to offer a particular protocol based on the known TLS information
+ // that we will offer in the client hello (such as version). There has
+ // not been a Server Hello received yet, so not much else can be considered.
+ ALPNCallback ALPNCallbacks[kCount];
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ASpdySession_h
diff --git a/netwerk/protocol/http/AltDataOutputStreamChild.cpp b/netwerk/protocol/http/AltDataOutputStreamChild.cpp
new file mode 100644
index 000000000..b24514685
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamChild.cpp
@@ -0,0 +1,151 @@
+#include "mozilla/net/AltDataOutputStreamChild.h"
+#include "mozilla/Unused.h"
+#include "nsIInputStream.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(AltDataOutputStreamChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) AltDataOutputStreamChild::Release()
+{
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "AltDataOutputStreamChild");
+
+ if (mRefCnt == 1 && mIPCOpen) {
+ // Send_delete calls NeckoChild::PAltDataOutputStreamChild, which will release
+ // again to refcount == 0
+ PAltDataOutputStreamChild::Send__delete__(this);
+ return 0;
+ }
+
+ if (mRefCnt == 0) {
+ mRefCnt = 1; /* stabilize */
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+}
+
+NS_INTERFACE_MAP_BEGIN(AltDataOutputStreamChild)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+AltDataOutputStreamChild::AltDataOutputStreamChild()
+ : mIPCOpen(false)
+ , mError(NS_OK)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+}
+
+AltDataOutputStreamChild::~AltDataOutputStreamChild()
+{
+}
+
+void
+AltDataOutputStreamChild::AddIPDLReference()
+{
+ MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void
+AltDataOutputStreamChild::ReleaseIPDLReference()
+{
+ MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference");
+ mIPCOpen = false;
+ Release();
+}
+
+bool
+AltDataOutputStreamChild::WriteDataInChunks(const nsCString& data)
+{
+ const uint32_t kChunkSize = 128*1024;
+ uint32_t next = std::min(data.Length(), kChunkSize);
+ for (uint32_t i = 0; i < data.Length();
+ i = next, next = std::min(data.Length(), next + kChunkSize)) {
+ nsCString chunk(Substring(data, i, kChunkSize));
+ if (mIPCOpen && !SendWriteData(chunk)) {
+ mIPCOpen = false;
+ return false;
+ }
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::Close()
+{
+ if (!mIPCOpen) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+ Unused << SendClose();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::Flush()
+{
+ if (!mIPCOpen) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+
+ // This is a no-op
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::Write(const char * aBuf, uint32_t aCount, uint32_t *_retval)
+{
+ if (!mIPCOpen) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_FAILED(mError)) {
+ return mError;
+ }
+ if (WriteDataInChunks(nsCString(aBuf, aCount))) {
+ *_retval = aCount;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::WriteFrom(nsIInputStream *aFromStream, uint32_t aCount, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::WriteSegments(nsReadSegmentFun aReader, void *aClosure, uint32_t aCount, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::IsNonBlocking(bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+bool
+AltDataOutputStreamChild::RecvError(const nsresult& err)
+{
+ mError = err;
+ return true;
+}
+
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltDataOutputStreamChild.h b/netwerk/protocol/http/AltDataOutputStreamChild.h
new file mode 100644
index 000000000..76b4b82ba
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamChild.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_AltDataOutputStreamChild_h
+#define mozilla_net_AltDataOutputStreamChild_h
+
+#include "mozilla/net/PAltDataOutputStreamChild.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+class AltDataOutputStreamChild
+ : public PAltDataOutputStreamChild
+ , public nsIOutputStream
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ explicit AltDataOutputStreamChild();
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+ // Saves an error code which will be reported to the writer on the next call.
+ virtual bool RecvError(const nsresult& err) override;
+
+private:
+ virtual ~AltDataOutputStreamChild();
+ // Sends data to the parent process in 256k chunks.
+ bool WriteDataInChunks(const nsCString& data);
+
+ bool mIPCOpen;
+ // If there was an error opening the output stream or writing to it on the
+ // parent side, this will be set to the error code. We check it before we
+ // write so we can report an error to the consumer.
+ nsresult mError;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AltDataOutputStreamChild_h
diff --git a/netwerk/protocol/http/AltDataOutputStreamParent.cpp b/netwerk/protocol/http/AltDataOutputStreamParent.cpp
new file mode 100644
index 000000000..1181209dc
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamParent.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/AltDataOutputStreamParent.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS0(AltDataOutputStreamParent)
+
+AltDataOutputStreamParent::AltDataOutputStreamParent(nsIOutputStream* aStream)
+ : mOutputStream(aStream)
+ , mStatus(NS_OK)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+}
+
+AltDataOutputStreamParent::~AltDataOutputStreamParent()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+}
+
+bool
+AltDataOutputStreamParent::RecvWriteData(const nsCString& data)
+{
+ if (NS_FAILED(mStatus)) {
+ Unused << SendError(mStatus);
+ return true;
+ }
+ nsresult rv;
+ uint32_t n;
+ if (mOutputStream) {
+ rv = mOutputStream->Write(data.BeginReading(), data.Length(), &n);
+ MOZ_ASSERT(n == data.Length());
+ if (NS_FAILED(rv)) {
+ Unused << SendError(rv);
+ }
+ }
+ return true;
+}
+
+bool
+AltDataOutputStreamParent::RecvClose()
+{
+ if (NS_FAILED(mStatus)) {
+ Unused << SendError(mStatus);
+ return true;
+ }
+ nsresult rv;
+ if (mOutputStream) {
+ rv = mOutputStream->Close();
+ if (NS_FAILED(rv)) {
+ Unused << SendError(rv);
+ }
+ mOutputStream = nullptr;
+ }
+ return true;
+}
+
+void
+AltDataOutputStreamParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AltDataOutputStreamParent.h b/netwerk/protocol/http/AltDataOutputStreamParent.h
new file mode 100644
index 000000000..208339efb
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamParent.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_AltDataOutputStreamParent_h
+#define mozilla_net_AltDataOutputStreamParent_h
+
+#include "mozilla/net/PAltDataOutputStreamParent.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+// Forwards data received from the content process to an output stream.
+class AltDataOutputStreamParent
+ : public PAltDataOutputStreamParent
+ , public nsISupports
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // Called from NeckoParent::AllocPAltDataOutputStreamParent which also opens
+ // the output stream.
+ // aStream may be null
+ explicit AltDataOutputStreamParent(nsIOutputStream* aStream);
+
+ // Called when data is received from the content process.
+ // We proceed to write that data to the output stream.
+ virtual bool RecvWriteData(const nsCString& data) override;
+ // Called when AltDataOutputStreamChild::Close() is
+ // Closes and nulls the output stream.
+ virtual bool RecvClose() override;
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ // Sets an error that will be reported to the content process.
+ void SetError(nsresult status) { mStatus = status; }
+
+private:
+ virtual ~AltDataOutputStreamParent();
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ // In case any error occurs mStatus will be != NS_OK, and this status code will
+ // be sent to the content process asynchronously.
+ nsresult mStatus;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AltDataOutputStreamParent_h
diff --git a/netwerk/protocol/http/AlternateServices.cpp b/netwerk/protocol/http/AlternateServices.cpp
new file mode 100644
index 000000000..b3e6babe3
--- /dev/null
+++ b/netwerk/protocol/http/AlternateServices.cpp
@@ -0,0 +1,1075 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HttpLog.h"
+
+#include "AlternateServices.h"
+#include "LoadInfo.h"
+#include "nsEscape.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpChannel.h"
+#include "nsHttpHandler.h"
+#include "nsThreadUtils.h"
+#include "nsHttpTransaction.h"
+#include "NullHttpTransaction.h"
+#include "nsISSLStatusProvider.h"
+#include "nsISSLStatus.h"
+#include "nsISSLSocketControl.h"
+#include "nsIWellKnownOpportunisticUtils.h"
+
+/* RFC 7838 Alternative Services
+ http://httpwg.org/http-extensions/opsec.html
+ note that connections currently do not do mixed-scheme (the I attribute
+ in the ConnectionInfo prevents it) but could, do not honor tls-commit and should
+ not, and always require authentication
+*/
+
+namespace mozilla {
+namespace net {
+
+// function places true in outIsHTTPS if scheme is https, false if
+// http, and returns an error if neither. originScheme passed into
+// alternate service should already be normalized to those lower case
+// strings by the URI parser (and so there is an assert)- this is an extra check.
+static nsresult
+SchemeIsHTTPS(const nsACString &originScheme, bool &outIsHTTPS)
+{
+ outIsHTTPS = originScheme.Equals(NS_LITERAL_CSTRING("https"));
+
+ if (!outIsHTTPS && !originScheme.Equals(NS_LITERAL_CSTRING("http"))) {
+ MOZ_ASSERT(false, "unexpected scheme");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+void
+AltSvcMapping::ProcessHeader(const nsCString &buf, const nsCString &originScheme,
+ const nsCString &originHost, int32_t originPort,
+ const nsACString &username, bool privateBrowsing,
+ nsIInterfaceRequestor *callbacks, nsProxyInfo *proxyInfo,
+ uint32_t caps, const NeckoOriginAttributes &originAttributes)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("AltSvcMapping::ProcessHeader: %s\n", buf.get()));
+ if (!callbacks) {
+ return;
+ }
+
+ if (proxyInfo && !proxyInfo->IsDirect()) {
+ LOG(("AltSvcMapping::ProcessHeader ignoring due to proxy\n"));
+ return;
+ }
+
+ bool isHTTPS;
+ if (NS_FAILED(SchemeIsHTTPS(originScheme, isHTTPS))) {
+ return;
+ }
+ if (!isHTTPS && !gHttpHandler->AllowAltSvcOE()) {
+ LOG(("Alt-Svc Response Header for http:// origin but OE disabled\n"));
+ return;
+ }
+
+ LOG(("Alt-Svc Response Header %s\n", buf.get()));
+ ParsedHeaderValueListList parsedAltSvc(buf);
+
+ for (uint32_t index = 0; index < parsedAltSvc.mValues.Length(); ++index) {
+ uint32_t maxage = 86400; // default
+ nsAutoCString hostname;
+ nsAutoCString npnToken;
+ int32_t portno = originPort;
+ bool clearEntry = false;
+
+ for (uint32_t pairIndex = 0;
+ pairIndex < parsedAltSvc.mValues[index].mValues.Length();
+ ++pairIndex) {
+ nsDependentCSubstring &currentName =
+ parsedAltSvc.mValues[index].mValues[pairIndex].mName;
+ nsDependentCSubstring &currentValue =
+ parsedAltSvc.mValues[index].mValues[pairIndex].mValue;
+
+ if (!pairIndex) {
+ if (currentName.Equals(NS_LITERAL_CSTRING("clear"))) {
+ clearEntry = true;
+ break;
+ }
+
+ // h2=[hostname]:443
+ npnToken = currentName;
+ int32_t colonIndex = currentValue.FindChar(':');
+ if (colonIndex >= 0) {
+ portno =
+ atoi(PromiseFlatCString(currentValue).get() + colonIndex + 1);
+ } else {
+ colonIndex = 0;
+ }
+ hostname.Assign(currentValue.BeginReading(), colonIndex);
+ } else if (currentName.Equals(NS_LITERAL_CSTRING("ma"))) {
+ maxage = atoi(PromiseFlatCString(currentValue).get());
+ break;
+ } else {
+ LOG(("Alt Svc ignoring parameter %s", currentName.BeginReading()));
+ }
+ }
+
+ if (clearEntry) {
+ LOG(("Alt Svc clearing mapping for %s:%d", originHost.get(), originPort));
+ gHttpHandler->ConnMgr()->ClearHostMapping(originHost, originPort);
+ continue;
+ }
+
+ // unescape modifies a c string in place, so afterwards
+ // update nsCString length
+ nsUnescape(npnToken.BeginWriting());
+ npnToken.SetLength(strlen(npnToken.BeginReading()));
+
+ uint32_t spdyIndex;
+ SpdyInformation *spdyInfo = gHttpHandler->SpdyInfo();
+ if (!(NS_SUCCEEDED(spdyInfo->GetNPNIndex(npnToken, &spdyIndex)) &&
+ spdyInfo->ProtocolEnabled(spdyIndex))) {
+ LOG(("Alt Svc unknown protocol %s, ignoring", npnToken.get()));
+ continue;
+ }
+
+ RefPtr<AltSvcMapping> mapping = new AltSvcMapping(gHttpHandler->ConnMgr()->GetStoragePtr(),
+ gHttpHandler->ConnMgr()->StorageEpoch(),
+ originScheme,
+ originHost, originPort,
+ username, privateBrowsing,
+ NowInSeconds() + maxage,
+ hostname, portno, npnToken);
+ if (mapping->TTL() <= 0) {
+ LOG(("Alt Svc invalid map"));
+ mapping = nullptr;
+ // since this isn't a parse error, let's clear any existing mapping
+ // as that would have happened if we had accepted the parameters.
+ gHttpHandler->ConnMgr()->ClearHostMapping(originHost, originPort);
+ } else {
+ gHttpHandler->UpdateAltServiceMapping(mapping, proxyInfo, callbacks, caps,
+ originAttributes);
+ }
+ }
+}
+
+AltSvcMapping::AltSvcMapping(DataStorage *storage, int32_t epoch,
+ const nsACString &originScheme,
+ const nsACString &originHost,
+ int32_t originPort,
+ const nsACString &username,
+ bool privateBrowsing,
+ uint32_t expiresAt,
+ const nsACString &alternateHost,
+ int32_t alternatePort,
+ const nsACString &npnToken)
+ : mStorage(storage)
+ , mStorageEpoch(epoch)
+ , mAlternateHost(alternateHost)
+ , mAlternatePort(alternatePort)
+ , mOriginHost(originHost)
+ , mOriginPort(originPort)
+ , mUsername(username)
+ , mPrivate(privateBrowsing)
+ , mExpiresAt(expiresAt)
+ , mValidated(false)
+ , mMixedScheme(false)
+ , mNPNToken(npnToken)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(SchemeIsHTTPS(originScheme, mHttps))) {
+ LOG(("AltSvcMapping ctor %p invalid scheme\n", this));
+ mExpiresAt = 0; // invalid
+ }
+
+ if (mAlternatePort == -1) {
+ mAlternatePort = mHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+ }
+ if (mOriginPort == -1) {
+ mOriginPort = mHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+ }
+
+ LOG(("AltSvcMapping ctor %p %s://%s:%d to %s:%d\n", this,
+ nsCString(originScheme).get(), mOriginHost.get(), mOriginPort,
+ mAlternateHost.get(), mAlternatePort));
+
+ if (mAlternateHost.IsEmpty()) {
+ mAlternateHost = mOriginHost;
+ }
+
+ if ((mAlternatePort == mOriginPort) &&
+ mAlternateHost.EqualsIgnoreCase(mOriginHost.get())) {
+ LOG(("Alt Svc is also origin Svc - ignoring\n"));
+ mExpiresAt = 0; // invalid
+ }
+
+ if (mExpiresAt) {
+ MakeHashKey(mHashKey, originScheme, mOriginHost, mOriginPort, mPrivate);
+ }
+}
+
+void
+AltSvcMapping::MakeHashKey(nsCString &outKey,
+ const nsACString &originScheme,
+ const nsACString &originHost,
+ int32_t originPort,
+ bool privateBrowsing)
+{
+ outKey.Truncate();
+
+ if (originPort == -1) {
+ bool isHttps = originScheme.Equals("https");
+ originPort = isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+ }
+
+ outKey.Append(originScheme);
+ outKey.Append(':');
+ outKey.Append(originHost);
+ outKey.Append(':');
+ outKey.AppendInt(originPort);
+ outKey.Append(':');
+ outKey.Append(privateBrowsing ? 'P' : '.');
+}
+
+int32_t
+AltSvcMapping::TTL()
+{
+ return mExpiresAt - NowInSeconds();
+}
+
+void
+AltSvcMapping::SyncString(nsCString str)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mStorage->Put(HashKey(), str,
+ mPrivate ? DataStorage_Private : DataStorage_Persistent);
+}
+
+void
+AltSvcMapping::Sync()
+{
+ if (!mStorage) {
+ return;
+ }
+ nsCString value;
+ Serialize(value);
+
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> r;
+ r = NewRunnableMethod<nsCString>(this,
+ &AltSvcMapping::SyncString,
+ value);
+ NS_DispatchToMainThread(r, NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ mStorage->Put(HashKey(), value,
+ mPrivate ? DataStorage_Private : DataStorage_Persistent);
+}
+
+void
+AltSvcMapping::SetValidated(bool val)
+{
+ mValidated = val;
+ Sync();
+}
+
+void
+AltSvcMapping::SetMixedScheme(bool val)
+{
+ mMixedScheme = val;
+ Sync();
+}
+
+void
+AltSvcMapping::SetExpiresAt(int32_t val)
+{
+ mExpiresAt = val;
+ Sync();
+}
+
+void
+AltSvcMapping::SetExpired()
+{
+ LOG(("AltSvcMapping SetExpired %p origin %s alternate %s\n", this,
+ mOriginHost.get(), mAlternateHost.get()));
+ mExpiresAt = NowInSeconds() - 1;
+ Sync();
+}
+
+bool
+AltSvcMapping::RouteEquals(AltSvcMapping *map)
+{
+ MOZ_ASSERT(map->mHashKey.Equals(mHashKey));
+ return mAlternateHost.Equals(map->mAlternateHost) &&
+ (mAlternatePort == map->mAlternatePort) &&
+ mNPNToken.Equals(map->mNPNToken);
+}
+
+void
+AltSvcMapping::GetConnectionInfo(nsHttpConnectionInfo **outCI,
+ nsProxyInfo *pi,
+ const NeckoOriginAttributes &originAttributes)
+{
+ RefPtr<nsHttpConnectionInfo> ci =
+ new nsHttpConnectionInfo(mOriginHost, mOriginPort, mNPNToken,
+ mUsername, pi, originAttributes,
+ mAlternateHost, mAlternatePort);
+
+ // http:// without the mixed-scheme attribute needs to be segmented in the
+ // connection manager connection information hash with this attribute
+ if (!mHttps && !mMixedScheme) {
+ ci->SetInsecureScheme(true);
+ }
+ ci->SetPrivate(mPrivate);
+ ci.forget(outCI);
+}
+
+void
+AltSvcMapping::Serialize(nsCString &out)
+{
+ out = mHttps ? NS_LITERAL_CSTRING("https:") : NS_LITERAL_CSTRING("http:");
+ out.Append(mOriginHost);
+ out.Append(':');
+ out.AppendInt(mOriginPort);
+ out.Append(':');
+ out.Append(mAlternateHost);
+ out.Append(':');
+ out.AppendInt(mAlternatePort);
+ out.Append(':');
+ out.Append(mUsername);
+ out.Append(':');
+ out.Append(mPrivate ? 'y' : 'n');
+ out.Append(':');
+ out.AppendInt(mExpiresAt);
+ out.Append(':');
+ out.Append(mNPNToken);
+ out.Append(':');
+ out.Append(mValidated ? 'y' : 'n');
+ out.Append(':');
+ out.AppendInt(mStorageEpoch);
+ out.Append(':');
+ out.Append(mMixedScheme ? 'y' : 'n');
+ out.Append(':');
+}
+
+AltSvcMapping::AltSvcMapping(DataStorage *storage, int32_t epoch, const nsCString &str)
+ : mStorage(storage)
+ , mStorageEpoch(epoch)
+{
+ mValidated = false;
+ nsresult code;
+
+ // The the do {} while(0) loop acts like try/catch(e){} with the break in _NS_NEXT_TOKEN
+ do {
+#ifdef _NS_NEXT_TOKEN
+COMPILER ERROR
+#endif
+ #define _NS_NEXT_TOKEN start = idx + 1; idx = str.FindChar(':', start); if (idx < 0) break;
+ int32_t start = 0;
+ int32_t idx;
+ idx = str.FindChar(':', start); if (idx < 0) break;
+ mHttps = Substring(str, start, idx - start).Equals(NS_LITERAL_CSTRING("https"));
+ _NS_NEXT_TOKEN;
+ mOriginHost = Substring(str, start, idx - start);
+ _NS_NEXT_TOKEN;
+ mOriginPort = nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+ _NS_NEXT_TOKEN;
+ mAlternateHost = Substring(str, start, idx - start);
+ _NS_NEXT_TOKEN;
+ mAlternatePort = nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+ _NS_NEXT_TOKEN;
+ mUsername = Substring(str, start, idx - start);
+ _NS_NEXT_TOKEN;
+ mPrivate = Substring(str, start, idx - start).Equals(NS_LITERAL_CSTRING("y"));
+ _NS_NEXT_TOKEN;
+ mExpiresAt = nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+ _NS_NEXT_TOKEN;
+ mNPNToken = Substring(str, start, idx - start);
+ _NS_NEXT_TOKEN;
+ mValidated = Substring(str, start, idx - start).Equals(NS_LITERAL_CSTRING("y"));
+ _NS_NEXT_TOKEN;
+ mStorageEpoch = nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+ _NS_NEXT_TOKEN;
+ mMixedScheme = Substring(str, start, idx - start).Equals(NS_LITERAL_CSTRING("y"));
+ #undef _NS_NEXT_TOKEN
+
+ MakeHashKey(mHashKey, mHttps ? NS_LITERAL_CSTRING("https") : NS_LITERAL_CSTRING("http"),
+ mOriginHost, mOriginPort, mPrivate);
+ } while (false);
+}
+
+// This is the asynchronous null transaction used to validate
+// an alt-svc advertisement only for https://
+class AltSvcTransaction final : public NullHttpTransaction
+{
+public:
+ AltSvcTransaction(AltSvcMapping *map,
+ nsHttpConnectionInfo *ci,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps)
+ : NullHttpTransaction(ci, callbacks, caps & ~NS_HTTP_ALLOW_KEEPALIVE)
+ , mMapping(map)
+ , mRunning(true)
+ , mTriedToValidate(false)
+ , mTriedToWrite(false)
+ {
+ LOG(("AltSvcTransaction ctor %p map %p [%s -> %s]",
+ this, map, map->OriginHost().get(), map->AlternateHost().get()));
+ MOZ_ASSERT(mMapping);
+ MOZ_ASSERT(mMapping->HTTPS());
+ }
+
+ ~AltSvcTransaction() override
+ {
+ LOG(("AltSvcTransaction dtor %p map %p running %d",
+ this, mMapping.get(), mRunning));
+
+ if (mRunning) {
+ MaybeValidate(NS_OK);
+ }
+ if (!mMapping->Validated()) {
+ // try again later
+ mMapping->SetExpiresAt(NowInSeconds() + 2);
+ }
+ LOG(("AltSvcTransaction dtor %p map %p validated %d [%s]",
+ this, mMapping.get(), mMapping->Validated(),
+ mMapping->HashKey().get()));
+ }
+
+private:
+ // check on alternate route.
+ // also evaluate 'reasonable assurances' for opportunistic security
+ void MaybeValidate(nsresult reason)
+ {
+ MOZ_ASSERT(mMapping->HTTPS()); // http:// uses the .wk path
+
+ if (mTriedToValidate) {
+ return;
+ }
+ mTriedToValidate = true;
+
+ LOG(("AltSvcTransaction::MaybeValidate() %p reason=%x running=%d conn=%p write=%d",
+ this, reason, mRunning, mConnection.get(), mTriedToWrite));
+
+ if (mTriedToWrite && reason == NS_BASE_STREAM_CLOSED) {
+ // The normal course of events is to cause the transaction to fail with CLOSED
+ // on a write - so that's a success that means the HTTP/2 session is setup.
+ reason = NS_OK;
+ }
+
+ if (NS_FAILED(reason) || !mRunning || !mConnection) {
+ LOG(("AltSvcTransaction::MaybeValidate %p Failed due to precondition", this));
+ return;
+ }
+
+ // insist on >= http/2
+ uint32_t version = mConnection->Version();
+ LOG(("AltSvcTransaction::MaybeValidate() %p version %d\n", this, version));
+ if (version != HTTP_VERSION_2) {
+ LOG(("AltSvcTransaction::MaybeValidate %p Failed due to protocol version", this));
+ return;
+ }
+
+ nsCOMPtr<nsISupports> secInfo;
+ mConnection->GetSecurityInfo(getter_AddRefs(secInfo));
+ nsCOMPtr<nsISSLSocketControl> socketControl = do_QueryInterface(secInfo);
+
+ LOG(("AltSvcTransaction::MaybeValidate() %p socketControl=%p\n",
+ this, socketControl.get()));
+
+ if (socketControl->GetFailedVerification()) {
+ LOG(("AltSvcTransaction::MaybeValidate() %p "
+ "not validated due to auth error", this));
+ return;
+ }
+
+ LOG(("AltSvcTransaction::MaybeValidate() %p "
+ "validating alternate service with successful auth check", this));
+ mMapping->SetValidated(true);
+ }
+
+public:
+ void Close(nsresult reason) override
+ {
+ LOG(("AltSvcTransaction::Close() %p reason=%x running %d",
+ this, reason, mRunning));
+
+ MaybeValidate(reason);
+ if (!mMapping->Validated() && mConnection) {
+ mConnection->DontReuse();
+ }
+ NullHttpTransaction::Close(reason);
+ }
+
+ nsresult ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead) override
+ {
+ LOG(("AltSvcTransaction::ReadSegements() %p\n"));
+ mTriedToWrite = true;
+ return NullHttpTransaction::ReadSegments(reader, count, countRead);
+ }
+
+private:
+ RefPtr<AltSvcMapping> mMapping;
+ uint32_t mRunning : 1;
+ uint32_t mTriedToValidate : 1;
+ uint32_t mTriedToWrite : 1;
+};
+
+class WellKnownChecker
+{
+public:
+ WellKnownChecker(nsIURI *uri, const nsCString &origin, uint32_t caps, nsHttpConnectionInfo *ci, AltSvcMapping *mapping)
+ : mWaiting(2) // waiting for 2 channels (default and alternate) to complete
+ , mOrigin(origin)
+ , mAlternatePort(ci->RoutedPort())
+ , mMapping(mapping)
+ , mCI(ci)
+ , mURI(uri)
+ , mCaps(caps)
+ {
+ LOG(("WellKnownChecker ctor %p\n", this));
+ MOZ_ASSERT(!mMapping->HTTPS());
+ }
+
+ nsresult Start()
+ {
+ LOG(("WellKnownChecker::Start %p\n", this));
+ nsCOMPtr<nsILoadInfo> loadInfo = new LoadInfo(nsContentUtils::GetSystemPrincipal(),
+ nullptr, nullptr,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ loadInfo->SetOriginAttributes(mCI->GetOriginAttributes());
+
+ RefPtr<nsHttpChannel> chan = new nsHttpChannel();
+ nsresult rv;
+
+ mTransactionAlternate = new TransactionObserver(chan, this);
+ RefPtr<nsHttpConnectionInfo> newCI = mCI->Clone();
+ rv = MakeChannel(chan, mTransactionAlternate, newCI, mURI, mCaps, loadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ chan = new nsHttpChannel();
+ mTransactionOrigin = new TransactionObserver(chan, this);
+ newCI = nullptr;
+ return MakeChannel(chan, mTransactionOrigin, newCI, mURI, mCaps, loadInfo);
+ }
+
+ void Done(TransactionObserver *finished)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("WellKnownChecker::Done %p waiting for %d\n", this, mWaiting));
+
+ mWaiting--; // another channel is complete
+ if (!mWaiting) { // there are all complete!
+ nsAutoCString mAlternateCT, mOriginCT;
+ mTransactionOrigin->mChannel->GetContentType(mOriginCT);
+ mTransactionAlternate->mChannel->GetContentType(mAlternateCT);
+ nsCOMPtr<nsIWellKnownOpportunisticUtils> uu = do_CreateInstance(NS_WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID);
+ bool accepted = false;
+
+ if (!mTransactionOrigin->mStatusOK) {
+ LOG(("WellKnownChecker::Done %p origin was not 200 response code\n", this));
+ } else if (!mTransactionAlternate->mAuthOK) {
+ LOG(("WellKnownChecker::Done %p alternate was not TLS authenticated\n", this));
+ } else if (!mTransactionAlternate->mStatusOK) {
+ LOG(("WellKnownChecker::Done %p alternate was not 200 response code\n", this));
+ } else if (!mTransactionAlternate->mVersionOK) {
+ LOG(("WellKnownChecker::Done %p alternate was not at least h2\n", this));
+ } else if (!mTransactionAlternate->mWKResponse.Equals(mTransactionOrigin->mWKResponse)) {
+ LOG(("WellKnownChecker::Done %p alternate and origin "
+ ".wk representations don't match\norigin: %s\alternate:%s\n", this,
+ mTransactionOrigin->mWKResponse.get(),
+ mTransactionAlternate->mWKResponse.get()));
+ } else if (!mAlternateCT.Equals(mOriginCT)) {
+ LOG(("WellKnownChecker::Done %p alternate and origin content types dont match\n", this));
+ } else if (!mAlternateCT.Equals(NS_LITERAL_CSTRING("application/json"))) {
+ LOG(("WellKnownChecker::Done %p .wk content type is %s\n", this, mAlternateCT.get()));
+ } else if (!uu) {
+ LOG(("WellKnownChecker::Done %p json parser service unavailable\n", this));
+ } else {
+ accepted = true;
+ }
+
+ if (accepted) {
+ MOZ_ASSERT(!mMapping->HTTPS()); // https:// does not use .wk
+
+ nsresult rv = uu->Verify(mTransactionAlternate->mWKResponse, mOrigin, mAlternatePort);
+ if (NS_SUCCEEDED(rv)) {
+ bool validWK = false;
+ bool mixedScheme = false;
+ int32_t lifetime = 0;
+ uu->GetValid(&validWK);
+ uu->GetLifetime(&lifetime);
+ uu->GetMixed(&mixedScheme);
+ if (!validWK) {
+ LOG(("WellKnownChecker::Done %p json parser declares invalid\n%s\n", this, mTransactionAlternate->mWKResponse.get()));
+ accepted = false;
+ }
+ if (accepted && (lifetime > 0)) {
+ if (mMapping->TTL() > lifetime) {
+ LOG(("WellKnownChecker::Done %p atl-svc lifetime reduced by .wk\n", this));
+ mMapping->SetExpiresAt(NowInSeconds() + lifetime);
+ } else {
+ LOG(("WellKnownChecker::Done %p .wk lifetime exceeded alt-svc ma so ignored\n", this));
+ }
+ }
+ if (accepted && mixedScheme) {
+ mMapping->SetMixedScheme(true);
+ LOG(("WellKnownChecker::Done %p atl-svc .wk allows mixed scheme\n", this));
+ }
+ } else {
+ LOG(("WellKnownChecker::Done %p .wk jason eval failed to run\n", this));
+ accepted = false;
+ }
+ }
+
+ MOZ_ASSERT(!mMapping->Validated());
+ if (accepted) {
+ LOG(("WellKnownChecker::Done %p Alternate for %s ACCEPTED\n", this, mOrigin.get()));
+ mMapping->SetValidated(true);
+ } else {
+ LOG(("WellKnownChecker::Done %p Alternate for %s FAILED\n", this, mOrigin.get()));
+ // try again soon
+ mMapping->SetExpiresAt(NowInSeconds() + 2);
+ }
+
+ delete this;
+ }
+ }
+
+ ~WellKnownChecker()
+ {
+ LOG(("WellKnownChecker dtor %p\n", this));
+ }
+
+private:
+ nsresult
+ MakeChannel(nsHttpChannel *chan, TransactionObserver *obs, nsHttpConnectionInfo *ci,
+ nsIURI *uri, uint32_t caps, nsILoadInfo *loadInfo)
+ {
+ nsID channelId;
+ nsLoadFlags flags;
+ if (NS_FAILED(gHttpHandler->NewChannelId(&channelId)) ||
+ NS_FAILED(chan->Init(uri, caps, nullptr, 0, nullptr, channelId)) ||
+ NS_FAILED(chan->SetAllowAltSvc(false)) ||
+ NS_FAILED(chan->SetRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_ERROR)) ||
+ NS_FAILED(chan->SetLoadInfo(loadInfo)) ||
+ NS_FAILED(chan->GetLoadFlags(&flags))) {
+ return NS_ERROR_FAILURE;
+ }
+ flags |= HttpBaseChannel::LOAD_BYPASS_CACHE;
+ if (NS_FAILED(chan->SetLoadFlags(flags))) {
+ return NS_ERROR_FAILURE;
+ }
+ chan->SetTransactionObserver(obs);
+ chan->SetConnectionInfo(ci);
+ return chan->AsyncOpen2(obs);
+ }
+
+ RefPtr<TransactionObserver> mTransactionAlternate;
+ RefPtr<TransactionObserver> mTransactionOrigin;
+ uint32_t mWaiting; // semaphore
+ nsCString mOrigin;
+ int32_t mAlternatePort;
+ RefPtr<AltSvcMapping> mMapping;
+ RefPtr<nsHttpConnectionInfo> mCI;
+ nsCOMPtr<nsIURI> mURI;
+ uint32_t mCaps;
+};
+
+NS_IMPL_ISUPPORTS(TransactionObserver, nsIStreamListener)
+
+TransactionObserver::TransactionObserver(nsHttpChannel *channel, WellKnownChecker *checker)
+ : mChannel(channel)
+ , mChecker(checker)
+ , mRanOnce(false)
+ , mAuthOK(false)
+ , mVersionOK(false)
+ , mStatusOK(false)
+{
+ LOG(("TransactionObserver ctor %p channel %p checker %p\n", this, channel, checker));
+ mChannelRef = do_QueryInterface((nsIHttpChannel *)channel);
+}
+
+void
+TransactionObserver::Complete(nsHttpTransaction *aTrans, nsresult reason)
+{
+ // socket thread
+ MOZ_ASSERT(!NS_IsMainThread());
+ if (mRanOnce) {
+ return;
+ }
+ mRanOnce = true;
+
+ RefPtr<nsAHttpConnection> conn = aTrans->GetConnectionReference();
+ LOG(("TransactionObserver::Complete %p aTrans %p reason %x conn %p\n",
+ this, aTrans, reason, conn.get()));
+ if (!conn) {
+ return;
+ }
+ uint32_t version = conn->Version();
+ mVersionOK = (((reason == NS_BASE_STREAM_CLOSED) || (reason == NS_OK)) &&
+ conn->Version() == HTTP_VERSION_2);
+
+ nsCOMPtr<nsISupports> secInfo;
+ conn->GetSecurityInfo(getter_AddRefs(secInfo));
+ nsCOMPtr<nsISSLSocketControl> socketControl = do_QueryInterface(secInfo);
+ LOG(("TransactionObserver::Complete version %u socketControl %p\n",
+ version, socketControl.get()));
+ if (!socketControl) {
+ return;
+ }
+
+ mAuthOK = !socketControl->GetFailedVerification();
+ LOG(("TransactionObserve::Complete %p trans %p authOK %d versionOK %d\n",
+ this, aTrans, mAuthOK, mVersionOK));
+}
+
+#define MAX_WK 32768
+
+NS_IMETHODIMP
+TransactionObserver::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // only consider the first 32KB.. because really.
+ mWKResponse.SetCapacity(MAX_WK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransactionObserver::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext,
+ nsIInputStream *aStream, uint64_t aOffset, uint32_t aCount)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ uint64_t newLen = aCount + mWKResponse.Length();
+ if (newLen < MAX_WK) {
+ char *startByte = reinterpret_cast<char *>(mWKResponse.BeginWriting()) + mWKResponse.Length();
+ uint32_t amtRead;
+ if (NS_SUCCEEDED(aStream->Read(startByte, aCount, &amtRead))) {
+ MOZ_ASSERT(mWKResponse.Length() + amtRead < MAX_WK);
+ mWKResponse.SetLength(mWKResponse.Length() + amtRead);
+ LOG(("TransactionObserver onDataAvailable %p read %d of .wk [%d]\n",
+ this, amtRead, mWKResponse.Length()));
+ } else {
+ LOG(("TransactionObserver onDataAvailable %p read error\n", this));
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransactionObserver::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext, nsresult code)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("TransactionObserver onStopRequest %p code %x\n", this, code));
+ if (NS_SUCCEEDED(code)) {
+ nsHttpResponseHead *hdrs = mChannel->GetResponseHead();
+ LOG(("TransactionObserver onStopRequest %p http resp %d\n",
+ this, hdrs ? hdrs->Status() : -1));
+ mStatusOK = hdrs && (hdrs->Status() == 200);
+ }
+ if (mChecker) {
+ mChecker->Done(this);
+ }
+ return NS_OK;
+}
+
+already_AddRefed<AltSvcMapping>
+AltSvcCache::LookupMapping(const nsCString &key, bool privateBrowsing)
+{
+ LOG(("AltSvcCache::LookupMapping %p %s\n", this, key.get()));
+ if (!mStorage) {
+ LOG(("AltSvcCache::LookupMapping %p no backing store\n", this));
+ return nullptr;
+ }
+ nsCString val(mStorage->Get(key,
+ privateBrowsing ? DataStorage_Private : DataStorage_Persistent));
+ if (val.IsEmpty()) {
+ LOG(("AltSvcCache::LookupMapping %p MISS\n", this));
+ return nullptr;
+ }
+ RefPtr<AltSvcMapping> rv = new AltSvcMapping(mStorage, mStorageEpoch, val);
+ if (!rv->Validated() && (rv->StorageEpoch() != mStorageEpoch)) {
+ // this was an in progress validation abandoned in a different session
+ // rare edge case will not detect session change - that's ok as only impact
+ // will be loss of alt-svc to this origin for this session.
+ LOG(("AltSvcCache::LookupMapping %p invalid hit - MISS\n", this));
+ mStorage->Remove(key,
+ rv->Private() ? DataStorage_Private : DataStorage_Persistent);
+ return nullptr;
+ }
+
+ if (rv->TTL() <= 0) {
+ LOG(("AltSvcCache::LookupMapping %p expired hit - MISS\n", this));
+ mStorage->Remove(key,
+ rv->Private() ? DataStorage_Private : DataStorage_Persistent);
+ return nullptr;
+ }
+
+ MOZ_ASSERT(rv->Private() == privateBrowsing);
+ LOG(("AltSvcCache::LookupMapping %p HIT %p\n", this, rv.get()));
+ return rv.forget();
+}
+
+void
+AltSvcCache::UpdateAltServiceMapping(AltSvcMapping *map, nsProxyInfo *pi,
+ nsIInterfaceRequestor *aCallbacks,
+ uint32_t caps,
+ const NeckoOriginAttributes &originAttributes)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mStorage) {
+ return;
+ }
+ RefPtr<AltSvcMapping> existing = LookupMapping(map->HashKey(), map->Private());
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p existing %p %s validated=%d",
+ this, map, existing.get(), map->AlternateHost().get(),
+ existing ? existing->Validated() : 0));
+
+ if (existing && existing->Validated()) {
+ if (existing->RouteEquals(map)){
+ // update expires in storage
+ // if this is http:// then a ttl can only be extended via .wk, so ignore this
+ // header path unless it is making things shorter
+ if (existing->HTTPS()) {
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p updates ttl of %p\n",
+ this, map, existing.get()));
+ existing->SetExpiresAt(map->GetExpiresAt());
+ } else {
+ if (map->GetExpiresAt() < existing->GetExpiresAt()) {
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p reduces ttl of %p\n",
+ this, map, existing.get()));
+ existing->SetExpiresAt(map->GetExpiresAt());
+ } else {
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p tries to extend %p but"
+ " cannot as without .wk\n",
+ this, map, existing.get()));
+ }
+ }
+ return;
+ }
+
+ // new alternate. remove old entry and start new validation
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p overwrites %p\n",
+ this, map, existing.get()));
+ existing = nullptr;
+ mStorage->Remove(map->HashKey(),
+ map->Private() ? DataStorage_Private : DataStorage_Persistent);
+ }
+
+ if (existing && !existing->Validated()) {
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p ignored because %p "
+ "still in progress\n", this, map, existing.get()));
+ return;
+ }
+
+ // start new validation
+ MOZ_ASSERT(!map->Validated());
+ map->Sync();
+
+ RefPtr<nsHttpConnectionInfo> ci;
+ map->GetConnectionInfo(getter_AddRefs(ci), pi, originAttributes);
+ caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
+ caps |= NS_HTTP_ERROR_SOFTLY;
+
+ if (map->HTTPS()) {
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p validation via "
+ "speculative connect started\n", this));
+ // for https resources we only establish a connection
+ nsCOMPtr<nsIInterfaceRequestor> callbacks = new AltSvcOverride(aCallbacks);
+ RefPtr<AltSvcTransaction> nullTransaction =
+ new AltSvcTransaction(map, ci, aCallbacks, caps);
+ gHttpHandler->ConnMgr()->SpeculativeConnect(ci, callbacks, caps, nullTransaction);
+ } else {
+ // for http:// resources we fetch .well-known too
+ nsAutoCString origin (NS_LITERAL_CSTRING("http://") + map->OriginHost());
+ if (map->OriginPort() != NS_HTTP_DEFAULT_PORT) {
+ origin.Append(':');
+ origin.AppendInt(map->OriginPort());
+ }
+
+ nsCOMPtr<nsIURI> wellKnown;
+ nsAutoCString uri(origin);
+ uri.Append(NS_LITERAL_CSTRING("/.well-known/http-opportunistic"));
+ NS_NewURI(getter_AddRefs(wellKnown), uri);
+
+ auto *checker = new WellKnownChecker(wellKnown, origin, caps, ci, map);
+ if (NS_FAILED(checker->Start())) {
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p .wk checker failed to start\n", this));
+ map->SetExpired();
+ delete checker;
+ checker = nullptr;
+ } else {
+ // object deletes itself when done if started
+ LOG(("AltSvcCache::UpdateAltServiceMapping %p .wk checker started %p\n", this, checker));
+ }
+ }
+}
+
+already_AddRefed<AltSvcMapping>
+AltSvcCache::GetAltServiceMapping(const nsACString &scheme, const nsACString &host,
+ int32_t port, bool privateBrowsing)
+{
+ bool isHTTPS;
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mStorage) {
+ // DataStorage gives synchronous access to a memory based hash table
+ // that is backed by disk where those writes are done asynchronously
+ // on another thread
+ mStorage = DataStorage::Get(NS_LITERAL_STRING("AlternateServices.txt"));
+ if (mStorage) {
+ bool storageWillPersist = false;
+ if (NS_FAILED(mStorage->Init(storageWillPersist))) {
+ mStorage = nullptr;
+ }
+ }
+ if (!mStorage) {
+ LOG(("AltSvcCache::GetAltServiceMapping WARN NO STORAGE\n"));
+ }
+ mStorageEpoch = NowInSeconds();
+ }
+
+ if (NS_FAILED(SchemeIsHTTPS(scheme, isHTTPS))) {
+ return nullptr;
+ }
+ if (!gHttpHandler->AllowAltSvc()) {
+ return nullptr;
+ }
+ if (!gHttpHandler->AllowAltSvcOE() && !isHTTPS) {
+ return nullptr;
+ }
+
+ nsAutoCString key;
+ AltSvcMapping::MakeHashKey(key, scheme, host, port, privateBrowsing);
+ RefPtr<AltSvcMapping> existing = LookupMapping(key, privateBrowsing);
+ LOG(("AltSvcCache::GetAltServiceMapping %p key=%s "
+ "existing=%p validated=%d ttl=%d",
+ this, key.get(), existing.get(), existing ? existing->Validated() : 0,
+ existing ? existing->TTL() : 0));
+ if (existing && !existing->Validated()) {
+ existing = nullptr;
+ }
+ return existing.forget();
+}
+
+class ProxyClearHostMapping : public Runnable {
+public:
+ explicit ProxyClearHostMapping(const nsACString &host, int32_t port)
+ : mHost(host)
+ , mPort(port)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ gHttpHandler->ConnMgr()->ClearHostMapping(mHost, mPort);
+ return NS_OK;
+ }
+private:
+ nsCString mHost;
+ int32_t mPort;
+};
+
+void
+AltSvcCache::ClearHostMapping(const nsACString &host, int32_t port)
+{
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> event = new ProxyClearHostMapping(host, port);
+ if (event) {
+ NS_DispatchToMainThread(event);
+ }
+ return;
+ }
+ nsAutoCString key;
+ AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("http"), host, port, true);
+ RefPtr<AltSvcMapping> existing = LookupMapping(key, true);
+ if (existing) {
+ existing->SetExpired();
+ }
+
+ AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("https"), host, port, true);
+ existing = LookupMapping(key, true);
+ if (existing) {
+ existing->SetExpired();
+ }
+
+ AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("http"), host, port, false);
+ existing = LookupMapping(key, false);
+ if (existing) {
+ existing->SetExpired();
+ }
+
+ AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("https"), host, port, false);
+ existing = LookupMapping(key, false);
+ if (existing) {
+ existing->SetExpired();
+ }
+}
+
+void
+AltSvcCache::ClearHostMapping(nsHttpConnectionInfo *ci)
+{
+ if (!ci->GetOrigin().IsEmpty()) {
+ ClearHostMapping(ci->GetOrigin(), ci->OriginPort());
+ }
+}
+
+void
+AltSvcCache::ClearAltServiceMappings()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mStorage) {
+ mStorage->Clear();
+ }
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetInterface(const nsIID &iid, void **result)
+{
+ if (NS_SUCCEEDED(QueryInterface(iid, result)) && *result) {
+ return NS_OK;
+ }
+ return mCallbacks->GetInterface(iid, result);
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetIgnoreIdle(bool *ignoreIdle)
+{
+ *ignoreIdle = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetParallelSpeculativeConnectLimit(
+ uint32_t *parallelSpeculativeConnectLimit)
+{
+ *parallelSpeculativeConnectLimit = 32;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetIsFromPredictor(bool *isFromPredictor)
+{
+ *isFromPredictor = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetAllow1918(bool *allow)
+{
+ // normally we don't do speculative connects to 1918.. and we use
+ // speculative connects for the mapping validation, so override
+ // that default here for alt-svc
+ *allow = true;
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(AltSvcOverride, nsIInterfaceRequestor, nsISpeculativeConnectionOverrider)
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/AlternateServices.h b/netwerk/protocol/http/AlternateServices.h
new file mode 100644
index 000000000..13403cd36
--- /dev/null
+++ b/netwerk/protocol/http/AlternateServices.h
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+Alt-Svc allows separation of transport routing from the origin host without
+using a proxy. See https://httpwg.github.io/http-extensions/alt-svc.html and
+https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06
+
+ Nice To Have Future Enhancements::
+ * flush on network change event when we have an indicator
+ * use established https channel for http instead separate of conninfo hash
+ * pin via http-tls header
+ * clear based on origin when a random fail happens not just 421
+ * upon establishment of channel, cancel and retry trans that have not yet written anything
+ * persistent storage (including private browsing filter)
+ * memory reporter for cache, but this is rather tiny
+*/
+
+#ifndef mozilla_net_AlternateServices_h
+#define mozilla_net_AlternateServices_h
+
+#include "mozilla/DataStorage.h"
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsISpeculativeConnect.h"
+#include "mozilla/BasePrincipal.h"
+
+class nsILoadInfo;
+
+namespace mozilla { namespace net {
+
+class nsProxyInfo;
+class nsHttpConnectionInfo;
+class nsHttpTransaction;
+class nsHttpChannel;
+class WellKnownChecker;
+
+class AltSvcMapping
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AltSvcMapping)
+
+private: // ctor from ProcessHeader
+ AltSvcMapping(DataStorage *storage,
+ int32_t storageEpoch,
+ const nsACString &originScheme,
+ const nsACString &originHost,
+ int32_t originPort,
+ const nsACString &username,
+ bool privateBrowsing,
+ uint32_t expiresAt,
+ const nsACString &alternateHost,
+ int32_t alternatePort,
+ const nsACString &npnToken);
+public:
+ AltSvcMapping(DataStorage *storage, int32_t storageEpoch, const nsCString &serialized);
+
+ static void ProcessHeader(const nsCString &buf, const nsCString &originScheme,
+ const nsCString &originHost, int32_t originPort,
+ const nsACString &username, bool privateBrowsing,
+ nsIInterfaceRequestor *callbacks, nsProxyInfo *proxyInfo,
+ uint32_t caps, const NeckoOriginAttributes &originAttributes);
+
+ const nsCString &AlternateHost() const { return mAlternateHost; }
+ const nsCString &OriginHost() const { return mOriginHost; }
+ uint32_t OriginPort() const { return mOriginPort; }
+ const nsCString &HashKey() const { return mHashKey; }
+ uint32_t AlternatePort() const { return mAlternatePort; }
+ bool Validated() { return mValidated; }
+ int32_t GetExpiresAt() { return mExpiresAt; }
+ bool RouteEquals(AltSvcMapping *map);
+ bool HTTPS() { return mHttps; }
+
+ void GetConnectionInfo(nsHttpConnectionInfo **outCI, nsProxyInfo *pi,
+ const NeckoOriginAttributes &originAttributes);
+
+ int32_t TTL();
+ int32_t StorageEpoch() { return mStorageEpoch; }
+ bool Private() { return mPrivate; }
+
+ void SetValidated(bool val);
+ void SetMixedScheme(bool val);
+ void SetExpiresAt(int32_t val);
+ void SetExpired();
+ void Sync();
+
+ static void MakeHashKey(nsCString &outKey,
+ const nsACString &originScheme,
+ const nsACString &originHost,
+ int32_t originPort,
+ bool privateBrowsing);
+
+private:
+ virtual ~AltSvcMapping() {};
+ void SyncString(nsCString val);
+ RefPtr<DataStorage> mStorage;
+ int32_t mStorageEpoch;
+ void Serialize (nsCString &out);
+
+ nsCString mHashKey;
+
+ // If you change any of these members, update Serialize()
+ nsCString mAlternateHost;
+ MOZ_INIT_OUTSIDE_CTOR int32_t mAlternatePort;
+
+ nsCString mOriginHost;
+ MOZ_INIT_OUTSIDE_CTOR int32_t mOriginPort;
+
+ nsCString mUsername;
+ MOZ_INIT_OUTSIDE_CTOR bool mPrivate;
+
+ MOZ_INIT_OUTSIDE_CTOR uint32_t mExpiresAt; // alt-svc mappping
+
+ MOZ_INIT_OUTSIDE_CTOR bool mValidated;
+ MOZ_INIT_OUTSIDE_CTOR bool mHttps; // origin is https://
+ MOZ_INIT_OUTSIDE_CTOR bool mMixedScheme; // .wk allows http and https on same con
+
+ nsCString mNPNToken;
+};
+
+class AltSvcOverride : public nsIInterfaceRequestor
+ , public nsISpeculativeConnectionOverrider
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ explicit AltSvcOverride(nsIInterfaceRequestor *aRequestor)
+ : mCallbacks(aRequestor) {}
+
+private:
+ virtual ~AltSvcOverride() {}
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+};
+
+class TransactionObserver : public nsIStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ TransactionObserver(nsHttpChannel *channel, WellKnownChecker *checker);
+ void Complete(nsHttpTransaction *, nsresult);
+private:
+ friend class WellKnownChecker;
+ virtual ~TransactionObserver() {}
+
+ nsCOMPtr<nsISupports> mChannelRef;
+ nsHttpChannel *mChannel;
+ WellKnownChecker *mChecker;
+ nsCString mWKResponse;
+
+ bool mRanOnce;
+ bool mAuthOK; // confirmed no TLS failure
+ bool mVersionOK; // connection h2
+ bool mStatusOK; // HTTP Status 200
+};
+
+class AltSvcCache
+{
+public:
+ AltSvcCache() : mStorageEpoch(0) {}
+ virtual ~AltSvcCache () {};
+ void UpdateAltServiceMapping(AltSvcMapping *map, nsProxyInfo *pi,
+ nsIInterfaceRequestor *, uint32_t caps,
+ const NeckoOriginAttributes &originAttributes); // main thread
+ already_AddRefed<AltSvcMapping> GetAltServiceMapping(const nsACString &scheme,
+ const nsACString &host,
+ int32_t port, bool pb);
+ void ClearAltServiceMappings();
+ void ClearHostMapping(const nsACString &host, int32_t port);
+ void ClearHostMapping(nsHttpConnectionInfo *ci);
+ DataStorage *GetStoragePtr() { return mStorage.get(); }
+ int32_t StorageEpoch() { return mStorageEpoch; }
+
+private:
+ already_AddRefed<AltSvcMapping> LookupMapping(const nsCString &key, bool privateBrowsing);
+ RefPtr<DataStorage> mStorage;
+ int32_t mStorageEpoch;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // include guard
diff --git a/netwerk/protocol/http/CacheControlParser.cpp b/netwerk/protocol/http/CacheControlParser.cpp
new file mode 100644
index 000000000..68c637706
--- /dev/null
+++ b/netwerk/protocol/http/CacheControlParser.cpp
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheControlParser.h"
+
+namespace mozilla {
+namespace net {
+
+CacheControlParser::CacheControlParser(nsACString const &aHeader)
+ : Tokenizer(aHeader, nullptr, "-_")
+ , mMaxAgeSet(false)
+ , mMaxAge(0)
+ , mMaxStaleSet(false)
+ , mMaxStale(0)
+ , mMinFreshSet(false)
+ , mMinFresh(0)
+ , mNoCache(false)
+ , mNoStore(false)
+{
+ SkipWhites();
+ if (!CheckEOF()) {
+ Directive();
+ }
+}
+
+void CacheControlParser::Directive()
+{
+ if (CheckWord("no-cache")) {
+ mNoCache = true;
+ IgnoreDirective(); // ignore any optionally added values
+ } else if (CheckWord("no-store")) {
+ mNoStore = true;
+ } else if (CheckWord("max-age")) {
+ mMaxAgeSet = SecondsValue(&mMaxAge);
+ } else if (CheckWord("max-stale")) {
+ mMaxStaleSet = SecondsValue(&mMaxStale, PR_UINT32_MAX);
+ } else if (CheckWord("min-fresh")) {
+ mMinFreshSet = SecondsValue(&mMinFresh);
+ } else {
+ IgnoreDirective();
+ }
+
+ SkipWhites();
+ if (CheckEOF()) {
+ return;
+ }
+ if (CheckChar(',')) {
+ SkipWhites();
+ Directive();
+ return;
+ }
+
+ NS_WARNING("Unexpected input in Cache-control header value");
+}
+
+bool CacheControlParser::SecondsValue(uint32_t *seconds, uint32_t defaultVal)
+{
+ SkipWhites();
+ if (!CheckChar('=')) {
+ *seconds = defaultVal;
+ return !!defaultVal;
+ }
+
+ SkipWhites();
+ if (!ReadInteger(seconds)) {
+ NS_WARNING("Unexpected value in Cache-control header value");
+ return false;
+ }
+
+ return true;
+}
+
+void CacheControlParser::IgnoreDirective()
+{
+ Token t;
+ while (Next(t)) {
+ if (t.Equals(Token::Char(',')) || t.Equals(Token::EndOfFile())) {
+ Rollback();
+ break;
+ }
+ if (t.Equals(Token::Char('"'))) {
+ SkipUntil(Token::Char('"'));
+ if (!CheckChar('"')) {
+ NS_WARNING("Missing quoted string expansion in Cache-control header value");
+ break;
+ }
+ }
+ }
+}
+
+bool CacheControlParser::MaxAge(uint32_t *seconds)
+{
+ *seconds = mMaxAge;
+ return mMaxAgeSet;
+}
+
+bool CacheControlParser::MaxStale(uint32_t *seconds)
+{
+ *seconds = mMaxStale;
+ return mMaxStaleSet;
+}
+
+bool CacheControlParser::MinFresh(uint32_t *seconds)
+{
+ *seconds = mMinFresh;
+ return mMinFreshSet;
+}
+
+bool CacheControlParser::NoCache()
+{
+ return mNoCache;
+}
+
+bool CacheControlParser::NoStore()
+{
+ return mNoStore;
+}
+
+} // net
+} // mozilla
diff --git a/netwerk/protocol/http/CacheControlParser.h b/netwerk/protocol/http/CacheControlParser.h
new file mode 100644
index 000000000..5f1b44213
--- /dev/null
+++ b/netwerk/protocol/http/CacheControlParser.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheControlParser_h__
+#define CacheControlParser_h__
+
+#include "mozilla/Tokenizer.h"
+
+namespace mozilla {
+namespace net {
+
+class CacheControlParser final : Tokenizer
+{
+public:
+ explicit CacheControlParser(nsACString const &header);
+
+ bool MaxAge(uint32_t *seconds);
+ bool MaxStale(uint32_t *seconds);
+ bool MinFresh(uint32_t *seconds);
+ bool NoCache();
+ bool NoStore();
+
+private:
+ void Directive();
+ void IgnoreDirective();
+ bool SecondsValue(uint32_t *seconds, uint32_t defaultVal = 0);
+
+ bool mMaxAgeSet;
+ uint32_t mMaxAge;
+ bool mMaxStaleSet;
+ uint32_t mMaxStale;
+ bool mMinFreshSet;
+ uint32_t mMinFresh;
+ bool mNoCache;
+ bool mNoStore;
+};
+
+} // net
+} // mozilla
+
+#endif
diff --git a/netwerk/protocol/http/ConnectionDiagnostics.cpp b/netwerk/protocol/http/ConnectionDiagnostics.cpp
new file mode 100644
index 000000000..9ddc8e362
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionDiagnostics.cpp
@@ -0,0 +1,211 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpConnectionMgr.h"
+#include "nsHttpConnection.h"
+#include "Http2Session.h"
+#include "nsHttpHandler.h"
+#include "nsIConsoleService.h"
+#include "nsHttpRequestHead.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransportService2.h"
+
+namespace mozilla {
+namespace net {
+
+void
+nsHttpConnectionMgr::PrintDiagnostics()
+{
+ PostEvent(&nsHttpConnectionMgr::OnMsgPrintDiagnostics, 0, nullptr);
+}
+
+void
+nsHttpConnectionMgr::OnMsgPrintDiagnostics(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (!consoleService)
+ return;
+
+ mLogData.AppendPrintf("HTTP Connection Diagnostics\n---------------------\n");
+ mLogData.AppendPrintf("IsSpdyEnabled() = %d\n", gHttpHandler->IsSpdyEnabled());
+ mLogData.AppendPrintf("MaxSocketCount() = %d\n", gHttpHandler->MaxSocketCount());
+ mLogData.AppendPrintf("mNumActiveConns = %d\n", mNumActiveConns);
+ mLogData.AppendPrintf("mNumIdleConns = %d\n", mNumIdleConns);
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ mLogData.AppendPrintf(" ent host = %s hashkey = %s\n",
+ ent->mConnInfo->Origin(), ent->mConnInfo->HashKey().get());
+ mLogData.AppendPrintf(" AtActiveConnectionLimit = %d\n",
+ AtActiveConnectionLimit(ent, NS_HTTP_ALLOW_KEEPALIVE));
+ mLogData.AppendPrintf(" RestrictConnections = %d\n",
+ RestrictConnections(ent));
+ mLogData.AppendPrintf(" Pending Q Length = %u\n",
+ ent->mPendingQ.Length());
+ mLogData.AppendPrintf(" Active Conns Length = %u\n",
+ ent->mActiveConns.Length());
+ mLogData.AppendPrintf(" Idle Conns Length = %u\n",
+ ent->mIdleConns.Length());
+ mLogData.AppendPrintf(" Half Opens Length = %u\n",
+ ent->mHalfOpens.Length());
+ mLogData.AppendPrintf(" Coalescing Keys Length = %u\n",
+ ent->mCoalescingKeys.Length());
+ mLogData.AppendPrintf(" Spdy using = %d, preferred = %d\n",
+ ent->mUsingSpdy, ent->mInPreferredHash);
+ mLogData.AppendPrintf(" pipelinestate = %d penalty = %d\n",
+ ent->mPipelineState, ent->mPipeliningPenalty);
+
+ uint32_t i;
+ for (i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) {
+ mLogData.AppendPrintf(" pipeline per class penalty 0x%x %d\n",
+ i, ent->mPipeliningClassPenalty[i]);
+ }
+ for (i = 0; i < ent->mActiveConns.Length(); ++i) {
+ mLogData.AppendPrintf(" :: Active Connection #%u\n", i);
+ ent->mActiveConns[i]->PrintDiagnostics(mLogData);
+ }
+ for (i = 0; i < ent->mIdleConns.Length(); ++i) {
+ mLogData.AppendPrintf(" :: Idle Connection #%u\n", i);
+ ent->mIdleConns[i]->PrintDiagnostics(mLogData);
+ }
+ for (i = 0; i < ent->mHalfOpens.Length(); ++i) {
+ mLogData.AppendPrintf(" :: Half Open #%u\n", i);
+ ent->mHalfOpens[i]->PrintDiagnostics(mLogData);
+ }
+ for (i = 0; i < ent->mPendingQ.Length(); ++i) {
+ mLogData.AppendPrintf(" :: Pending Transaction #%u\n", i);
+ ent->mPendingQ[i]->PrintDiagnostics(mLogData);
+ }
+ for (i = 0; i < ent->mCoalescingKeys.Length(); ++i) {
+ mLogData.AppendPrintf(" :: Coalescing Key #%u %s\n",
+ i, ent->mCoalescingKeys[i].get());
+ }
+ }
+
+ consoleService->LogStringMessage(NS_ConvertUTF8toUTF16(mLogData).Data());
+ mLogData.Truncate();
+}
+
+void
+nsHttpConnectionMgr::nsHalfOpenSocket::PrintDiagnostics(nsCString &log)
+{
+ log.AppendPrintf(" has connected = %d, isSpeculative = %d\n",
+ HasConnected(), IsSpeculative());
+
+ TimeStamp now = TimeStamp::Now();
+
+ if (mPrimarySynStarted.IsNull())
+ log.AppendPrintf(" primary not started\n");
+ else
+ log.AppendPrintf(" primary started %.2fms ago\n",
+ (now - mPrimarySynStarted).ToMilliseconds());
+
+ if (mBackupSynStarted.IsNull())
+ log.AppendPrintf(" backup not started\n");
+ else
+ log.AppendPrintf(" backup started %.2f ago\n",
+ (now - mBackupSynStarted).ToMilliseconds());
+
+ log.AppendPrintf(" primary transport %d, backup transport %d\n",
+ !!mSocketTransport.get(), !!mBackupTransport.get());
+}
+
+void
+nsHttpConnection::PrintDiagnostics(nsCString &log)
+{
+ log.AppendPrintf(" CanDirectlyActivate = %d\n", CanDirectlyActivate());
+
+ log.AppendPrintf(" npncomplete = %d setupSSLCalled = %d\n",
+ mNPNComplete, mSetupSSLCalled);
+
+ log.AppendPrintf(" spdyVersion = %d reportedSpdy = %d everspdy = %d\n",
+ mUsingSpdyVersion, mReportedSpdy, mEverUsedSpdy);
+
+ log.AppendPrintf(" iskeepalive = %d dontReuse = %d isReused = %d\n",
+ IsKeepAlive(), mDontReuse, mIsReused);
+
+ log.AppendPrintf(" mTransaction = %d mSpdySession = %d\n",
+ !!mTransaction.get(), !!mSpdySession.get());
+
+ PRIntervalTime now = PR_IntervalNow();
+ log.AppendPrintf(" time since last read = %ums\n",
+ PR_IntervalToMilliseconds(now - mLastReadTime));
+
+ log.AppendPrintf(" max-read/read/written %lld/%lld/%lld\n",
+ mMaxBytesRead, mTotalBytesRead, mTotalBytesWritten);
+
+ log.AppendPrintf(" rtt = %ums\n", PR_IntervalToMilliseconds(mRtt));
+
+ log.AppendPrintf(" idlemonitoring = %d transactionCount=%d\n",
+ mIdleMonitoring, mHttp1xTransactionCount);
+
+ log.AppendPrintf(" supports pipeline = %d classification = 0x%x\n",
+ mSupportsPipelining, mClassification);
+
+ if (mSpdySession)
+ mSpdySession->PrintDiagnostics(log);
+}
+
+void
+Http2Session::PrintDiagnostics(nsCString &log)
+{
+ log.AppendPrintf(" ::: HTTP2\n");
+ log.AppendPrintf(" shouldgoaway = %d mClosed = %d CanReuse = %d nextID=0x%X\n",
+ mShouldGoAway, mClosed, CanReuse(), mNextStreamID);
+
+ log.AppendPrintf(" concurrent = %d maxconcurrent = %d\n",
+ mConcurrent, mMaxConcurrent);
+
+ log.AppendPrintf(" roomformorestreams = %d roomformoreconcurrent = %d\n",
+ RoomForMoreStreams(), RoomForMoreConcurrent());
+
+ log.AppendPrintf(" transactionHashCount = %d streamIDHashCount = %d\n",
+ mStreamTransactionHash.Count(),
+ mStreamIDHash.Count());
+
+ log.AppendPrintf(" Queued Stream Size = %d\n", mQueuedStreams.GetSize());
+
+ PRIntervalTime now = PR_IntervalNow();
+ log.AppendPrintf(" Ping Threshold = %ums\n",
+ PR_IntervalToMilliseconds(mPingThreshold));
+ log.AppendPrintf(" Ping Timeout = %ums\n",
+ PR_IntervalToMilliseconds(gHttpHandler->SpdyPingTimeout()));
+ log.AppendPrintf(" Idle for Any Activity (ping) = %ums\n",
+ PR_IntervalToMilliseconds(now - mLastReadEpoch));
+ log.AppendPrintf(" Idle for Data Activity = %ums\n",
+ PR_IntervalToMilliseconds(now - mLastDataReadEpoch));
+ if (mPingSentEpoch)
+ log.AppendPrintf(" Ping Outstanding (ping) = %ums, expired = %d\n",
+ PR_IntervalToMilliseconds(now - mPingSentEpoch),
+ now - mPingSentEpoch >= gHttpHandler->SpdyPingTimeout());
+ else
+ log.AppendPrintf(" No Ping Outstanding\n");
+}
+
+void
+nsHttpTransaction::PrintDiagnostics(nsCString &log)
+{
+ if (!mRequestHead)
+ return;
+
+ nsAutoCString requestURI;
+ mRequestHead->RequestURI(requestURI);
+ log.AppendPrintf(" ::: uri = %s\n", requestURI.get());
+ log.AppendPrintf(" caps = 0x%x\n", mCaps);
+ log.AppendPrintf(" priority = %d\n", mPriority);
+ log.AppendPrintf(" restart count = %u\n", mRestartCount);
+ log.AppendPrintf(" classification = 0x%x\n", mClassification);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HSTSPrimerListener.cpp b/netwerk/protocol/http/HSTSPrimerListener.cpp
new file mode 100644
index 000000000..8c9d28d36
--- /dev/null
+++ b/netwerk/protocol/http/HSTSPrimerListener.cpp
@@ -0,0 +1,273 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsHttp.h"
+
+#include "HSTSPrimerListener.h"
+#include "nsIHstsPrimingCallback.h"
+#include "nsIPrincipal.h"
+#include "nsSecurityHeaderParser.h"
+#include "nsISiteSecurityService.h"
+#include "nsISocketProvider.h"
+#include "nsISSLStatus.h"
+#include "nsISSLStatusProvider.h"
+#include "nsStreamUtils.h"
+#include "nsHttpChannel.h"
+#include "LoadInfo.h"
+
+namespace mozilla {
+namespace net {
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(HSTSPrimingListener, nsIStreamListener,
+ nsIRequestObserver, nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+HSTSPrimingListener::GetInterface(const nsIID & aIID, void **aResult)
+{
+ return QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+HSTSPrimingListener::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ nsresult primingResult = CheckHSTSPrimingRequestStatus(aRequest);
+ nsCOMPtr<nsIHstsPrimingCallback> callback(mCallback);
+ mCallback = nullptr;
+
+ nsCOMPtr<nsITimedChannel> timingChannel =
+ do_QueryInterface(callback);
+ if (timingChannel) {
+ TimeStamp channelCreationTime;
+ nsresult rv = timingChannel->GetChannelCreation(&channelCreationTime);
+ if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) {
+ PRUint32 interval =
+ (PRUint32) (TimeStamp::Now() - channelCreationTime).ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::HSTS_PRIMING_REQUEST_DURATION,
+ (NS_SUCCEEDED(primingResult)) ? NS_LITERAL_CSTRING("success")
+ : NS_LITERAL_CSTRING("failure"),
+ interval);
+ }
+ }
+
+ if (NS_FAILED(primingResult)) {
+ LOG(("HSTS Priming Failed (request was not approved)"));
+ return callback->OnHSTSPrimingFailed(primingResult, false);
+ }
+
+ LOG(("HSTS Priming Succeeded (request was approved)"));
+ return callback->OnHSTSPrimingSucceeded(false);
+}
+
+NS_IMETHODIMP
+HSTSPrimingListener::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ return NS_OK;
+}
+
+nsresult
+HSTSPrimingListener::CheckHSTSPrimingRequestStatus(nsIRequest* aRequest)
+{
+ nsresult status;
+ nsresult rv = aRequest->GetStatus(&status);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (NS_FAILED(status)) {
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+
+ // Test that things worked on a HTTP level
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ NS_ENSURE_STATE(httpChannel);
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
+ NS_ENSURE_STATE(internal);
+
+ bool succeedded;
+ rv = httpChannel->GetRequestSucceeded(&succeedded);
+ if (NS_FAILED(rv) || !succeedded) {
+ // If the request did not return a 2XX response, don't process it
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+
+ bool synthesized = false;
+ nsHttpChannel* rawHttpChannel = static_cast<nsHttpChannel*>(httpChannel.get());
+ rv = rawHttpChannel->GetResponseSynthesized(&synthesized);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (synthesized) {
+ // Don't consider synthesized responses
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+
+ // check to see if the HSTS cache was updated
+ nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = httpChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(uri, NS_ERROR_CONTENT_BLOCKED);
+
+ bool hsts;
+ rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0, nullptr, &hsts);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (hsts) {
+ // An HSTS upgrade was found
+ return NS_OK;
+ }
+
+ // There is no HSTS upgrade available
+ return NS_ERROR_CONTENT_BLOCKED;
+}
+
+/** nsIStreamListener methods **/
+
+NS_IMETHODIMP
+HSTSPrimingListener::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset,
+ uint32_t count)
+{
+ uint32_t totalRead;
+ return inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &totalRead);
+}
+
+// static
+nsresult
+HSTSPrimingListener::StartHSTSPriming(nsIChannel* aRequestChannel,
+ nsIHstsPrimingCallback* aCallback)
+{
+
+ nsCOMPtr<nsIURI> finalChannelURI;
+ nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(finalChannelURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_GetSecureUpgradedURI(finalChannelURI, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // check the HSTS cache
+ bool hsts;
+ bool cached;
+ nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0, &cached, &hsts);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (hsts) {
+ // already saw this host and will upgrade if allowed by preferences
+ return aCallback->OnHSTSPrimingSucceeded(true);
+ }
+
+ if (cached) {
+ // there is a non-expired entry in the cache that doesn't allow us to
+ // upgrade, so go ahead and fail early.
+ return aCallback->OnHSTSPrimingFailed(NS_ERROR_CONTENT_BLOCKED, true);
+ }
+
+ // Either it wasn't cached or the cached result has expired. Build a
+ // channel for the HEAD request.
+
+ nsCOMPtr<nsILoadInfo> originalLoadInfo = aRequestChannel->GetLoadInfo();
+ MOZ_ASSERT(originalLoadInfo, "can not perform HSTS priming without a loadInfo");
+ if (!originalLoadInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = static_cast<mozilla::LoadInfo*>
+ (originalLoadInfo.get())->CloneForNewRequest();
+
+ // the LoadInfo must have a security flag set in order to pass through priming
+ // if none of these security flags are set, go ahead and fail now instead of
+ // crashing in nsContentSecurityManager::ValidateSecurityFlags
+ nsSecurityFlags securityMode = loadInfo->GetSecurityMode();
+ if (securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS &&
+ securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED &&
+ securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS &&
+ securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ securityMode != nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
+ return aCallback->OnHSTSPrimingFailed(NS_ERROR_CONTENT_BLOCKED, true);
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsLoadFlags loadFlags;
+ rv = aRequestChannel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ loadFlags &= HttpBaseChannel::INHIBIT_CACHING |
+ HttpBaseChannel::INHIBIT_PERSISTENT_CACHING |
+ HttpBaseChannel::LOAD_BYPASS_CACHE |
+ HttpBaseChannel::LOAD_FROM_CACHE |
+ HttpBaseChannel::VALIDATE_ALWAYS;
+ // Priming requests should never be intercepted by service workers and
+ // are always anonymous.
+ loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER |
+ nsIRequest::LOAD_ANONYMOUS;
+
+ // Create a new channel to send the priming request
+ nsCOMPtr<nsIChannel> primingChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(primingChannel),
+ uri,
+ loadInfo,
+ loadGroup,
+ nullptr, // aCallbacks are set later
+ loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set method and headers
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(primingChannel);
+ if (!httpChannel) {
+ NS_ERROR("HSTSPrimingListener: Failed to QI to nsIHttpChannel!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Currently using HEAD per the draft, but under discussion to change to GET
+ // with credentials so if the upgrade is approved the result is already cached.
+ rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("HEAD"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = httpChannel->
+ SetRequestHeader(NS_LITERAL_CSTRING("Upgrade-Insecure-Requests"),
+ NS_LITERAL_CSTRING("1"), false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // attempt to set the class of service flags on the new channel
+ nsCOMPtr<nsIClassOfService> requestClass = do_QueryInterface(aRequestChannel);
+ if (!requestClass) {
+ NS_ERROR("HSTSPrimingListener: aRequestChannel is not an nsIClassOfService");
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIClassOfService> primingClass = do_QueryInterface(httpChannel);
+ if (!primingClass) {
+ NS_ERROR("HSTSPrimingListener: aRequestChannel is not an nsIClassOfService");
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t classFlags = 0;
+ rv = requestClass ->GetClassFlags(&classFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = primingClass->SetClassFlags(classFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set up listener which will start the original channel
+ nsCOMPtr<nsIStreamListener> primingListener(new HSTSPrimingListener(aCallback));
+
+ // Start priming
+ rv = primingChannel->AsyncOpen2(primingListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HSTSPrimerListener.h b/netwerk/protocol/http/HSTSPrimerListener.h
new file mode 100644
index 000000000..05089911b
--- /dev/null
+++ b/netwerk/protocol/http/HSTSPrimerListener.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HSTSPrimingListener_h__
+#define HSTSPrimingListener_h__
+
+#include "nsCOMPtr.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
+
+#include "mozilla/Attributes.h"
+
+class nsIPrincipal;
+class nsINetworkInterceptController;
+class nsIHstsPrimingCallback;
+
+namespace mozilla {
+namespace net {
+
+class HttpChannelParent;
+class nsHttpChannel;
+
+/*
+ * How often do we get back an HSTS priming result which upgrades the connection to HTTPS?
+ */
+enum HSTSPrimingResult {
+ // This site has been seen before and won't be upgraded
+ eHSTS_PRIMING_CACHED_NO_UPGRADE = 0,
+ // This site has been seen before and will be upgraded
+ eHSTS_PRIMING_CACHED_DO_UPGRADE = 1,
+ // This site has been seen before and will be blocked
+ eHSTS_PRIMING_CACHED_BLOCK = 2,
+ // The request was already upgraded, probably through
+ // upgrade-insecure-requests
+ eHSTS_PRIMING_ALREADY_UPGRADED = 3,
+ // HSTS priming is successful and the connection will be upgraded to HTTPS
+ eHSTS_PRIMING_SUCCEEDED = 4,
+ // When priming succeeds, but preferences require preservation of the order
+ // of mixed-content and hsts, and mixed-content blocks the load
+ eHSTS_PRIMING_SUCCEEDED_BLOCK = 5,
+ // When priming succeeds, but preferences require preservation of the order
+ // of mixed-content and hsts, and mixed-content allows the load over http
+ eHSTS_PRIMING_SUCCEEDED_HTTP = 6,
+ // HSTS priming failed, and the load is blocked by mixed-content
+ eHSTS_PRIMING_FAILED_BLOCK = 7,
+ // HSTS priming failed, and the load is allowed by mixed-content
+ eHSTS_PRIMING_FAILED_ACCEPT = 8
+};
+
+//////////////////////////////////////////////////////////////////////////
+// Class used as streamlistener and notification callback when
+// doing the HEAD request for an HSTS Priming check. Needs to be an
+// nsIStreamListener in order to receive events from AsyncOpen2
+class HSTSPrimingListener final : public nsIStreamListener,
+ public nsIInterfaceRequestor
+{
+public:
+ explicit HSTSPrimingListener(nsIHstsPrimingCallback* aCallback)
+ : mCallback(aCallback)
+ {
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+private:
+ ~HSTSPrimingListener() {}
+
+ // Only nsHttpChannel can invoke HSTS priming
+ friend class mozilla::net::nsHttpChannel;
+
+ /**
+ * Start the HSTS priming request. This will send an anonymous HEAD request to
+ * the URI aRequestChannel is attempting to load. On success, the new HSTS
+ * priming channel is allocated in aHSTSPrimingChannel.
+ *
+ * @param aRequestChannel the reference channel used to initialze the HSTS
+ * priming channel
+ * @param aCallback the callback stored to handle the results of HSTS priming.
+ * @param aHSTSPrimingChannel if the new HSTS priming channel is allocated
+ * successfully, it will be placed here.
+ */
+ static nsresult StartHSTSPriming(nsIChannel* aRequestChannel,
+ nsIHstsPrimingCallback* aCallback);
+
+ /**
+ * Given a request, return NS_OK if it has resulted in a cached HSTS update.
+ * We don't need to check for the header as that has already been done for us.
+ */
+ nsresult CheckHSTSPrimingRequestStatus(nsIRequest* aRequest);
+
+ /**
+ * the nsIHttpChannel to notify with the result of HSTS priming.
+ */
+ nsCOMPtr<nsIHstsPrimingCallback> mCallback;
+};
+
+
+}} // mozilla::net
+
+#endif // HSTSPrimingListener_h__
diff --git a/netwerk/protocol/http/Http2Compression.cpp b/netwerk/protocol/http/Http2Compression.cpp
new file mode 100644
index 000000000..1b4603e1a
--- /dev/null
+++ b/netwerk/protocol/http/Http2Compression.cpp
@@ -0,0 +1,1487 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "Http2Compression.h"
+#include "Http2HuffmanIncoming.h"
+#include "Http2HuffmanOutgoing.h"
+#include "mozilla/StaticPtr.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+static nsDeque *gStaticHeaders = nullptr;
+
+class HpackStaticTableReporter final : public nsIMemoryReporter
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ HpackStaticTableReporter() {}
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override
+ {
+ MOZ_COLLECT_REPORT(
+ "explicit/network/hpack/static-table", KIND_HEAP, UNITS_BYTES,
+ gStaticHeaders->SizeOfIncludingThis(MallocSizeOf),
+ "Memory usage of HPACK static table.");
+
+ return NS_OK;
+ }
+
+private:
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ ~HpackStaticTableReporter() {}
+};
+
+NS_IMPL_ISUPPORTS(HpackStaticTableReporter, nsIMemoryReporter)
+
+class HpackDynamicTableReporter final : public nsIMemoryReporter
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit HpackDynamicTableReporter(Http2BaseCompressor* aCompressor)
+ : mCompressor(aCompressor)
+ {}
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override
+ {
+ if (mCompressor) {
+ MOZ_COLLECT_REPORT(
+ "explicit/network/hpack/dynamic-tables", KIND_HEAP, UNITS_BYTES,
+ mCompressor->SizeOfExcludingThis(MallocSizeOf),
+ "Aggregate memory usage of HPACK dynamic tables.");
+ }
+ return NS_OK;
+ }
+
+private:
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ ~HpackDynamicTableReporter() {}
+
+ Http2BaseCompressor* mCompressor;
+
+ friend class Http2BaseCompressor;
+};
+
+NS_IMPL_ISUPPORTS(HpackDynamicTableReporter, nsIMemoryReporter)
+
+StaticRefPtr<HpackStaticTableReporter> gStaticReporter;
+
+void
+Http2CompressionCleanup()
+{
+ // this happens after the socket thread has been destroyed
+ delete gStaticHeaders;
+ gStaticHeaders = nullptr;
+ UnregisterStrongMemoryReporter(gStaticReporter);
+ gStaticReporter = nullptr;
+}
+
+static void
+AddStaticElement(const nsCString &name, const nsCString &value)
+{
+ nvPair *pair = new nvPair(name, value);
+ gStaticHeaders->Push(pair);
+}
+
+static void
+AddStaticElement(const nsCString &name)
+{
+ AddStaticElement(name, EmptyCString());
+}
+
+static void
+InitializeStaticHeaders()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (!gStaticHeaders) {
+ gStaticHeaders = new nsDeque();
+ gStaticReporter = new HpackStaticTableReporter();
+ RegisterStrongMemoryReporter(gStaticReporter);
+ AddStaticElement(NS_LITERAL_CSTRING(":authority"));
+ AddStaticElement(NS_LITERAL_CSTRING(":method"), NS_LITERAL_CSTRING("GET"));
+ AddStaticElement(NS_LITERAL_CSTRING(":method"), NS_LITERAL_CSTRING("POST"));
+ AddStaticElement(NS_LITERAL_CSTRING(":path"), NS_LITERAL_CSTRING("/"));
+ AddStaticElement(NS_LITERAL_CSTRING(":path"), NS_LITERAL_CSTRING("/index.html"));
+ AddStaticElement(NS_LITERAL_CSTRING(":scheme"), NS_LITERAL_CSTRING("http"));
+ AddStaticElement(NS_LITERAL_CSTRING(":scheme"), NS_LITERAL_CSTRING("https"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("200"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("204"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("206"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("304"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("400"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("404"));
+ AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("500"));
+ AddStaticElement(NS_LITERAL_CSTRING("accept-charset"));
+ AddStaticElement(NS_LITERAL_CSTRING("accept-encoding"), NS_LITERAL_CSTRING("gzip, deflate"));
+ AddStaticElement(NS_LITERAL_CSTRING("accept-language"));
+ AddStaticElement(NS_LITERAL_CSTRING("accept-ranges"));
+ AddStaticElement(NS_LITERAL_CSTRING("accept"));
+ AddStaticElement(NS_LITERAL_CSTRING("access-control-allow-origin"));
+ AddStaticElement(NS_LITERAL_CSTRING("age"));
+ AddStaticElement(NS_LITERAL_CSTRING("allow"));
+ AddStaticElement(NS_LITERAL_CSTRING("authorization"));
+ AddStaticElement(NS_LITERAL_CSTRING("cache-control"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-disposition"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-encoding"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-language"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-length"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-location"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-range"));
+ AddStaticElement(NS_LITERAL_CSTRING("content-type"));
+ AddStaticElement(NS_LITERAL_CSTRING("cookie"));
+ AddStaticElement(NS_LITERAL_CSTRING("date"));
+ AddStaticElement(NS_LITERAL_CSTRING("etag"));
+ AddStaticElement(NS_LITERAL_CSTRING("expect"));
+ AddStaticElement(NS_LITERAL_CSTRING("expires"));
+ AddStaticElement(NS_LITERAL_CSTRING("from"));
+ AddStaticElement(NS_LITERAL_CSTRING("host"));
+ AddStaticElement(NS_LITERAL_CSTRING("if-match"));
+ AddStaticElement(NS_LITERAL_CSTRING("if-modified-since"));
+ AddStaticElement(NS_LITERAL_CSTRING("if-none-match"));
+ AddStaticElement(NS_LITERAL_CSTRING("if-range"));
+ AddStaticElement(NS_LITERAL_CSTRING("if-unmodified-since"));
+ AddStaticElement(NS_LITERAL_CSTRING("last-modified"));
+ AddStaticElement(NS_LITERAL_CSTRING("link"));
+ AddStaticElement(NS_LITERAL_CSTRING("location"));
+ AddStaticElement(NS_LITERAL_CSTRING("max-forwards"));
+ AddStaticElement(NS_LITERAL_CSTRING("proxy-authenticate"));
+ AddStaticElement(NS_LITERAL_CSTRING("proxy-authorization"));
+ AddStaticElement(NS_LITERAL_CSTRING("range"));
+ AddStaticElement(NS_LITERAL_CSTRING("referer"));
+ AddStaticElement(NS_LITERAL_CSTRING("refresh"));
+ AddStaticElement(NS_LITERAL_CSTRING("retry-after"));
+ AddStaticElement(NS_LITERAL_CSTRING("server"));
+ AddStaticElement(NS_LITERAL_CSTRING("set-cookie"));
+ AddStaticElement(NS_LITERAL_CSTRING("strict-transport-security"));
+ AddStaticElement(NS_LITERAL_CSTRING("transfer-encoding"));
+ AddStaticElement(NS_LITERAL_CSTRING("user-agent"));
+ AddStaticElement(NS_LITERAL_CSTRING("vary"));
+ AddStaticElement(NS_LITERAL_CSTRING("via"));
+ AddStaticElement(NS_LITERAL_CSTRING("www-authenticate"));
+ }
+}
+
+size_t nvPair::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+}
+
+size_t nvPair::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+nvFIFO::nvFIFO()
+ : mByteCount(0)
+ , mTable()
+{
+ InitializeStaticHeaders();
+}
+
+nvFIFO::~nvFIFO()
+{
+ Clear();
+}
+
+void
+nvFIFO::AddElement(const nsCString &name, const nsCString &value)
+{
+ mByteCount += name.Length() + value.Length() + 32;
+ nvPair *pair = new nvPair(name, value);
+ mTable.PushFront(pair);
+}
+
+void
+nvFIFO::AddElement(const nsCString &name)
+{
+ AddElement(name, EmptyCString());
+}
+
+void
+nvFIFO::RemoveElement()
+{
+ nvPair *pair = static_cast<nvPair *>(mTable.Pop());
+ if (pair) {
+ mByteCount -= pair->Size();
+ delete pair;
+ }
+}
+
+uint32_t
+nvFIFO::ByteCount() const
+{
+ return mByteCount;
+}
+
+uint32_t
+nvFIFO::Length() const
+{
+ return mTable.GetSize() + gStaticHeaders->GetSize();
+}
+
+uint32_t
+nvFIFO::VariableLength() const
+{
+ return mTable.GetSize();
+}
+
+size_t
+nvFIFO::StaticLength() const
+{
+ return gStaticHeaders->GetSize();
+}
+
+void
+nvFIFO::Clear()
+{
+ mByteCount = 0;
+ while (mTable.GetSize())
+ delete static_cast<nvPair *>(mTable.Pop());
+}
+
+const nvPair *
+nvFIFO::operator[] (size_t index) const
+{
+ // NWGH - ensure index > 0
+ // NWGH - subtract 1 from index here
+ if (index >= (mTable.GetSize() + gStaticHeaders->GetSize())) {
+ MOZ_ASSERT(false);
+ NS_WARNING("nvFIFO Table Out of Range");
+ return nullptr;
+ }
+ if (index >= gStaticHeaders->GetSize()) {
+ return static_cast<nvPair *>(mTable.ObjectAt(index - gStaticHeaders->GetSize()));
+ }
+ return static_cast<nvPair *>(gStaticHeaders->ObjectAt(index));
+}
+
+Http2BaseCompressor::Http2BaseCompressor()
+ : mOutput(nullptr)
+ , mMaxBuffer(kDefaultMaxBuffer)
+ , mMaxBufferSetting(kDefaultMaxBuffer)
+ , mSetInitialMaxBufferSizeAllowed(true)
+ , mPeakSize(0)
+ , mPeakCount(0)
+{
+ mDynamicReporter = new HpackDynamicTableReporter(this);
+ RegisterStrongMemoryReporter(mDynamicReporter);
+}
+
+Http2BaseCompressor::~Http2BaseCompressor()
+{
+ if (mPeakSize) {
+ Telemetry::Accumulate(mPeakSizeID, mPeakSize);
+ }
+ if (mPeakCount) {
+ Telemetry::Accumulate(mPeakCountID, mPeakCount);
+ }
+ UnregisterStrongMemoryReporter(mDynamicReporter);
+ mDynamicReporter->mCompressor = nullptr;
+ mDynamicReporter = nullptr;
+}
+
+void
+Http2BaseCompressor::ClearHeaderTable()
+{
+ mHeaderTable.Clear();
+}
+
+size_t
+Http2BaseCompressor::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t size = 0;
+ for (uint32_t i = mHeaderTable.StaticLength(); i < mHeaderTable.Length(); ++i) {
+ size += mHeaderTable[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return size;
+}
+
+void
+Http2BaseCompressor::MakeRoom(uint32_t amount, const char *direction)
+{
+ uint32_t countEvicted = 0;
+ uint32_t bytesEvicted = 0;
+
+ // make room in the header table
+ while (mHeaderTable.VariableLength() && ((mHeaderTable.ByteCount() + amount) > mMaxBuffer)) {
+ // NWGH - remove the "- 1" here
+ uint32_t index = mHeaderTable.Length() - 1;
+ LOG(("HTTP %s header table index %u %s %s removed for size.\n",
+ direction, index, mHeaderTable[index]->mName.get(),
+ mHeaderTable[index]->mValue.get()));
+ ++countEvicted;
+ bytesEvicted += mHeaderTable[index]->Size();
+ mHeaderTable.RemoveElement();
+ }
+
+ if (!strcmp(direction, "decompressor")) {
+ Telemetry::Accumulate(Telemetry::HPACK_ELEMENTS_EVICTED_DECOMPRESSOR, countEvicted);
+ Telemetry::Accumulate(Telemetry::HPACK_BYTES_EVICTED_DECOMPRESSOR, bytesEvicted);
+ Telemetry::Accumulate(Telemetry::HPACK_BYTES_EVICTED_RATIO_DECOMPRESSOR, (uint32_t)((100.0 * (double)bytesEvicted) / (double)amount));
+ } else {
+ Telemetry::Accumulate(Telemetry::HPACK_ELEMENTS_EVICTED_COMPRESSOR, countEvicted);
+ Telemetry::Accumulate(Telemetry::HPACK_BYTES_EVICTED_COMPRESSOR, bytesEvicted);
+ Telemetry::Accumulate(Telemetry::HPACK_BYTES_EVICTED_RATIO_COMPRESSOR, (uint32_t)((100.0 * (double)bytesEvicted) / (double)amount));
+ }
+}
+
+void
+Http2BaseCompressor::DumpState()
+{
+ if (!LOG_ENABLED()) {
+ return;
+ }
+
+ LOG(("Header Table"));
+ uint32_t i;
+ uint32_t length = mHeaderTable.Length();
+ uint32_t staticLength = mHeaderTable.StaticLength();
+ // NWGH - make i = 1; i <= length; ++i
+ for (i = 0; i < length; ++i) {
+ const nvPair *pair = mHeaderTable[i];
+ // NWGH - make this <= staticLength
+ LOG(("%sindex %u: %s %s", i < staticLength ? "static " : "", i,
+ pair->mName.get(), pair->mValue.get()));
+ }
+}
+
+void
+Http2BaseCompressor::SetMaxBufferSizeInternal(uint32_t maxBufferSize)
+{
+ MOZ_ASSERT(maxBufferSize <= mMaxBufferSetting);
+
+ uint32_t removedCount = 0;
+
+ LOG(("Http2BaseCompressor::SetMaxBufferSizeInternal %u called", maxBufferSize));
+
+ while (mHeaderTable.VariableLength() && (mHeaderTable.ByteCount() > maxBufferSize)) {
+ mHeaderTable.RemoveElement();
+ ++removedCount;
+ }
+
+ mMaxBuffer = maxBufferSize;
+}
+
+nsresult
+Http2BaseCompressor::SetInitialMaxBufferSize(uint32_t maxBufferSize)
+{
+ MOZ_ASSERT(mSetInitialMaxBufferSizeAllowed);
+
+ if (mSetInitialMaxBufferSizeAllowed) {
+ mMaxBufferSetting = maxBufferSize;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+Http2Decompressor::DecodeHeaderBlock(const uint8_t *data, uint32_t datalen,
+ nsACString &output, bool isPush)
+{
+ mSetInitialMaxBufferSizeAllowed = false;
+ mOffset = 0;
+ mData = data;
+ mDataLen = datalen;
+ mOutput = &output;
+ mOutput->Truncate();
+ mHeaderStatus.Truncate();
+ mHeaderHost.Truncate();
+ mHeaderScheme.Truncate();
+ mHeaderPath.Truncate();
+ mHeaderMethod.Truncate();
+ mSeenNonColonHeader = false;
+ mIsPush = isPush;
+
+ nsresult rv = NS_OK;
+ nsresult softfail_rv = NS_OK;
+ while (NS_SUCCEEDED(rv) && (mOffset < datalen)) {
+ bool modifiesTable = true;
+ if (mData[mOffset] & 0x80) {
+ rv = DoIndexed();
+ LOG(("Decompressor state after indexed"));
+ } else if (mData[mOffset] & 0x40) {
+ rv = DoLiteralWithIncremental();
+ LOG(("Decompressor state after literal with incremental"));
+ } else if (mData[mOffset] & 0x20) {
+ rv = DoContextUpdate();
+ LOG(("Decompressor state after context update"));
+ } else if (mData[mOffset] & 0x10) {
+ modifiesTable = false;
+ rv = DoLiteralNeverIndexed();
+ LOG(("Decompressor state after literal never index"));
+ } else {
+ modifiesTable = false;
+ rv = DoLiteralWithoutIndex();
+ LOG(("Decompressor state after literal without index"));
+ }
+ DumpState();
+ if (rv == NS_ERROR_ILLEGAL_VALUE) {
+ if (modifiesTable) {
+ // Unfortunately, we can't count on our peer now having the same state
+ // as us, so let's terminate the session and we can try again later.
+ return NS_ERROR_FAILURE;
+ }
+
+ // This is an http-level error that we can handle by resetting the stream
+ // in the upper layers. Let's note that we saw this, then continue
+ // decompressing until we either hit the end of the header block or find a
+ // hard failure. That way we won't get an inconsistent compression state
+ // with the server.
+ softfail_rv = rv;
+ rv = NS_OK;
+ } else if (rv == NS_ERROR_NET_RESET) {
+ // This happens when we detect connection-based auth being requested in
+ // the response headers. We'll paper over it for now, and the session will
+ // handle this as if it received RST_STREAM with HTTP_1_1_REQUIRED.
+ softfail_rv = rv;
+ rv = NS_OK;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return softfail_rv;
+}
+
+nsresult
+Http2Decompressor::DecodeInteger(uint32_t prefixLen, uint32_t &accum)
+{
+ accum = 0;
+
+ if (prefixLen) {
+ uint32_t mask = (1 << prefixLen) - 1;
+
+ accum = mData[mOffset] & mask;
+ ++mOffset;
+
+ if (accum != mask) {
+ // the simple case for small values
+ return NS_OK;
+ }
+ }
+
+ uint32_t factor = 1; // 128 ^ 0
+
+ // we need a series of bytes. The high bit signifies if we need another one.
+ // The first one is a a factor of 128 ^ 0, the next 128 ^1, the next 128 ^2, ..
+
+ if (mOffset >= mDataLen) {
+ NS_WARNING("Ran out of data to decode integer");
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+ bool chainBit = mData[mOffset] & 0x80;
+ accum += (mData[mOffset] & 0x7f) * factor;
+
+ ++mOffset;
+ factor = factor * 128;
+
+ while (chainBit) {
+ // really big offsets are just trawling for overflows
+ if (accum >= 0x800000) {
+ NS_WARNING("Decoding integer >= 0x800000");
+ // This is not strictly fatal to the session, but given the fact that
+ // the value is way to large to be reasonable, let's just tell our peer
+ // to go away.
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mOffset >= mDataLen) {
+ NS_WARNING("Ran out of data to decode integer");
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+ chainBit = mData[mOffset] & 0x80;
+ accum += (mData[mOffset] & 0x7f) * factor;
+ ++mOffset;
+ factor = factor * 128;
+ }
+ return NS_OK;
+}
+
+static bool
+HasConnectionBasedAuth(const nsACString& headerValue)
+{
+ nsCCharSeparatedTokenizer t(headerValue, '\n');
+ while (t.hasMoreTokens()) {
+ const nsDependentCSubstring& authMethod = t.nextToken();
+ if (authMethod.LowerCaseEqualsLiteral("ntlm")) {
+ return true;
+ }
+ if (authMethod.LowerCaseEqualsLiteral("negotiate")) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult
+Http2Decompressor::OutputHeader(const nsACString &name, const nsACString &value)
+{
+ // exclusions
+ if (!mIsPush &&
+ (name.EqualsLiteral("connection") ||
+ name.EqualsLiteral("host") ||
+ name.EqualsLiteral("keep-alive") ||
+ name.EqualsLiteral("proxy-connection") ||
+ name.EqualsLiteral("te") ||
+ name.EqualsLiteral("transfer-encoding") ||
+ name.EqualsLiteral("upgrade") ||
+ name.Equals(("accept-encoding")))) {
+ nsCString toLog(name);
+ LOG(("HTTP Decompressor illegal response header found, not gatewaying: %s",
+ toLog.get()));
+ return NS_OK;
+ }
+
+ // Look for upper case characters in the name.
+ for (const char *cPtr = name.BeginReading();
+ cPtr && cPtr < name.EndReading();
+ ++cPtr) {
+ if (*cPtr <= 'Z' && *cPtr >= 'A') {
+ nsCString toLog(name);
+ LOG(("HTTP Decompressor upper case response header found. [%s]\n",
+ toLog.get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+
+ // Look for CR OR LF in value - could be smuggling Sec 10.3
+ // can map to space safely
+ for (const char *cPtr = value.BeginReading();
+ cPtr && cPtr < value.EndReading();
+ ++cPtr) {
+ if (*cPtr == '\r' || *cPtr== '\n') {
+ char *wPtr = const_cast<char *>(cPtr);
+ *wPtr = ' ';
+ }
+ }
+
+ // Status comes first
+ if (name.EqualsLiteral(":status")) {
+ nsAutoCString status(NS_LITERAL_CSTRING("HTTP/2.0 "));
+ status.Append(value);
+ status.AppendLiteral("\r\n");
+ mOutput->Insert(status, 0);
+ mHeaderStatus = value;
+ } else if (name.EqualsLiteral(":authority")) {
+ mHeaderHost = value;
+ } else if (name.EqualsLiteral(":scheme")) {
+ mHeaderScheme = value;
+ } else if (name.EqualsLiteral(":path")) {
+ mHeaderPath = value;
+ } else if (name.EqualsLiteral(":method")) {
+ mHeaderMethod = value;
+ }
+
+ // http/2 transport level headers shouldn't be gatewayed into http/1
+ bool isColonHeader = false;
+ for (const char *cPtr = name.BeginReading();
+ cPtr && cPtr < name.EndReading();
+ ++cPtr) {
+ if (*cPtr == ':') {
+ isColonHeader = true;
+ break;
+ } else if (*cPtr != ' ' && *cPtr != '\t') {
+ isColonHeader = false;
+ break;
+ }
+ }
+
+ if (isColonHeader) {
+ // :status is the only pseudo-header field allowed in received HEADERS frames, PUSH_PROMISE allows the other pseudo-header fields
+ if (!name.EqualsLiteral(":status") && !mIsPush) {
+ LOG(("HTTP Decompressor found illegal response pseudo-header %s", name.BeginReading()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (mSeenNonColonHeader) {
+ LOG(("HTTP Decompressor found illegal : header %s", name.BeginReading()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ LOG(("HTTP Decompressor not gatewaying %s into http/1",
+ name.BeginReading()));
+ return NS_OK;
+ }
+
+ LOG(("Http2Decompressor::OutputHeader %s %s", name.BeginReading(),
+ value.BeginReading()));
+ mSeenNonColonHeader = true;
+ mOutput->Append(name);
+ mOutput->AppendLiteral(": ");
+ mOutput->Append(value);
+ mOutput->AppendLiteral("\r\n");
+
+ // Need to check if the server is going to try to speak connection-based auth
+ // with us. If so, we need to kill this via h2, and dial back with http/1.1.
+ // Technically speaking, the server should've just reset or goaway'd us with
+ // HTTP_1_1_REQUIRED, but there are some busted servers out there, so we need
+ // to check on our own to work around them.
+ if (name.EqualsLiteral("www-authenticate") ||
+ name.EqualsLiteral("proxy-authenticate")) {
+ if (HasConnectionBasedAuth(value)) {
+ LOG3(("Http2Decompressor %p connection-based auth found in %s", this,
+ name.BeginReading()));
+ return NS_ERROR_NET_RESET;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+Http2Decompressor::OutputHeader(uint32_t index)
+{
+ // NWGH - make this < index
+ // bounds check
+ if (mHeaderTable.Length() <= index) {
+ LOG(("Http2Decompressor::OutputHeader index too large %u", index));
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+
+ return OutputHeader(mHeaderTable[index]->mName,
+ mHeaderTable[index]->mValue);
+}
+
+nsresult
+Http2Decompressor::CopyHeaderString(uint32_t index, nsACString &name)
+{
+ // NWGH - make this < index
+ // bounds check
+ if (mHeaderTable.Length() <= index) {
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+
+ name = mHeaderTable[index]->mName;
+ return NS_OK;
+}
+
+nsresult
+Http2Decompressor::CopyStringFromInput(uint32_t bytes, nsACString &val)
+{
+ if (mOffset + bytes > mDataLen) {
+ // This is session-fatal.
+ return NS_ERROR_FAILURE;
+ }
+
+ val.Assign(reinterpret_cast<const char *>(mData) + mOffset, bytes);
+ mOffset += bytes;
+ return NS_OK;
+}
+
+nsresult
+Http2Decompressor::DecodeFinalHuffmanCharacter(const HuffmanIncomingTable *table,
+ uint8_t &c, uint8_t &bitsLeft)
+{
+ uint8_t mask = (1 << bitsLeft) - 1;
+ uint8_t idx = mData[mOffset - 1] & mask;
+ idx <<= (8 - bitsLeft);
+ // Don't update bitsLeft yet, because we need to check that value against the
+ // number of bits used by our encoding later on. We'll update when we are sure
+ // how many bits we've actually used.
+
+ if (table->IndexHasANextTable(idx)) {
+ // Can't chain to another table when we're all out of bits in the encoding
+ LOG(("DecodeFinalHuffmanCharacter trying to chain when we're out of bits"));
+ return NS_ERROR_FAILURE;
+ }
+
+ const HuffmanIncomingEntry *entry = table->Entry(idx);
+
+ if (bitsLeft < entry->mPrefixLen) {
+ // We don't have enough bits to actually make a match, this is some sort of
+ // invalid coding
+ LOG(("DecodeFinalHuffmanCharacter does't have enough bits to match"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // This is a character!
+ if (entry->mValue == 256) {
+ // EOS
+ LOG(("DecodeFinalHuffmanCharacter actually decoded an EOS"));
+ return NS_ERROR_FAILURE;
+ }
+ c = static_cast<uint8_t>(entry->mValue & 0xFF);
+ bitsLeft -= entry->mPrefixLen;
+
+ return NS_OK;
+}
+
+uint8_t
+Http2Decompressor::ExtractByte(uint8_t bitsLeft, uint32_t &bytesConsumed)
+{
+ uint8_t rv;
+
+ if (bitsLeft) {
+ // Need to extract bitsLeft bits from the previous byte, and 8 - bitsLeft
+ // bits from the current byte
+ uint8_t mask = (1 << bitsLeft) - 1;
+ rv = (mData[mOffset - 1] & mask) << (8 - bitsLeft);
+ rv |= (mData[mOffset] & ~mask) >> bitsLeft;
+ } else {
+ rv = mData[mOffset];
+ }
+
+ // We always update these here, under the assumption that all 8 bits we got
+ // here will be used. These may be re-adjusted later in the case that we don't
+ // use up all 8 bits of the byte.
+ ++mOffset;
+ ++bytesConsumed;
+
+ return rv;
+}
+
+nsresult
+Http2Decompressor::DecodeHuffmanCharacter(const HuffmanIncomingTable *table,
+ uint8_t &c, uint32_t &bytesConsumed,
+ uint8_t &bitsLeft)
+{
+ uint8_t idx = ExtractByte(bitsLeft, bytesConsumed);
+
+ if (table->IndexHasANextTable(idx)) {
+ if (bytesConsumed >= mDataLen) {
+ if (!bitsLeft || (bytesConsumed > mDataLen)) {
+ // TODO - does this get me into trouble in the new world?
+ // No info left in input to try to consume, we're done
+ LOG(("DecodeHuffmanCharacter all out of bits to consume, can't chain"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // We might get lucky here!
+ return DecodeFinalHuffmanCharacter(table->NextTable(idx), c, bitsLeft);
+ }
+
+ // We're sorry, Mario, but your princess is in another castle
+ return DecodeHuffmanCharacter(table->NextTable(idx), c, bytesConsumed, bitsLeft);
+ }
+
+ const HuffmanIncomingEntry *entry = table->Entry(idx);
+ if (entry->mValue == 256) {
+ LOG(("DecodeHuffmanCharacter found an actual EOS"));
+ return NS_ERROR_FAILURE;
+ }
+ c = static_cast<uint8_t>(entry->mValue & 0xFF);
+
+ // Need to adjust bitsLeft (and possibly other values) because we may not have
+ // consumed all of the bits of the byte we extracted.
+ if (entry->mPrefixLen <= bitsLeft) {
+ bitsLeft -= entry->mPrefixLen;
+ --mOffset;
+ --bytesConsumed;
+ } else {
+ bitsLeft = 8 - (entry->mPrefixLen - bitsLeft);
+ }
+ MOZ_ASSERT(bitsLeft < 8);
+
+ return NS_OK;
+}
+
+nsresult
+Http2Decompressor::CopyHuffmanStringFromInput(uint32_t bytes, nsACString &val)
+{
+ if (mOffset + bytes > mDataLen) {
+ LOG(("CopyHuffmanStringFromInput not enough data"));
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t bytesRead = 0;
+ uint8_t bitsLeft = 0;
+ nsAutoCString buf;
+ nsresult rv;
+ uint8_t c;
+
+ while (bytesRead < bytes) {
+ uint32_t bytesConsumed = 0;
+ rv = DecodeHuffmanCharacter(&HuffmanIncomingRoot, c, bytesConsumed,
+ bitsLeft);
+ if (NS_FAILED(rv)) {
+ LOG(("CopyHuffmanStringFromInput failed to decode a character"));
+ return rv;
+ }
+
+ bytesRead += bytesConsumed;
+ buf.Append(c);
+ }
+
+ if (bytesRead > bytes) {
+ LOG(("CopyHuffmanStringFromInput read more bytes than was allowed!"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (bitsLeft) {
+ // The shortest valid code is 4 bits, so we know there can be at most one
+ // character left that our loop didn't decode. Check to see if that's the
+ // case, and if so, add it to our output.
+ rv = DecodeFinalHuffmanCharacter(&HuffmanIncomingRoot, c, bitsLeft);
+ if (NS_SUCCEEDED(rv)) {
+ buf.Append(c);
+ }
+ }
+
+ if (bitsLeft > 7) {
+ LOG(("CopyHuffmanStringFromInput more than 7 bits of padding"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (bitsLeft) {
+ // Any bits left at this point must belong to the EOS symbol, so make sure
+ // they make sense (ie, are all ones)
+ uint8_t mask = (1 << bitsLeft) - 1;
+ uint8_t bits = mData[mOffset - 1] & mask;
+ if (bits != mask) {
+ LOG(("CopyHuffmanStringFromInput ran out of data but found possible "
+ "non-EOS symbol"));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ val = buf;
+ LOG(("CopyHuffmanStringFromInput decoded a full string!"));
+ return NS_OK;
+}
+
+nsresult
+Http2Decompressor::DoIndexed()
+{
+ // this starts with a 1 bit pattern
+ MOZ_ASSERT(mData[mOffset] & 0x80);
+
+ // This is a 7 bit prefix
+
+ uint32_t index;
+ nsresult rv = DecodeInteger(7, index);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ LOG(("HTTP decompressor indexed entry %u\n", index));
+
+ if (index == 0) {
+ return NS_ERROR_FAILURE;
+ }
+ // NWGH - remove this line, since we'll keep everything 1-indexed
+ index--; // Internally, we 0-index everything, since this is, y'know, C++
+
+ return OutputHeader(index);
+}
+
+nsresult
+Http2Decompressor::DoLiteralInternal(nsACString &name, nsACString &value,
+ uint32_t namePrefixLen)
+{
+ // guts of doliteralwithoutindex and doliteralwithincremental
+ MOZ_ASSERT(((mData[mOffset] & 0xF0) == 0x00) || // withoutindex
+ ((mData[mOffset] & 0xF0) == 0x10) || // neverindexed
+ ((mData[mOffset] & 0xC0) == 0x40)); // withincremental
+
+ // first let's get the name
+ uint32_t index;
+ nsresult rv = DecodeInteger(namePrefixLen, index);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool isHuffmanEncoded;
+
+ if (!index) {
+ // name is embedded as a literal
+ uint32_t nameLen;
+ isHuffmanEncoded = mData[mOffset] & (1 << 7);
+ rv = DecodeInteger(7, nameLen);
+ if (NS_SUCCEEDED(rv)) {
+ if (isHuffmanEncoded) {
+ rv = CopyHuffmanStringFromInput(nameLen, name);
+ } else {
+ rv = CopyStringFromInput(nameLen, name);
+ }
+ }
+ LOG(("Http2Decompressor::DoLiteralInternal literal name %s",
+ name.BeginReading()));
+ } else {
+ // NWGH - make this index, not index - 1
+ // name is from headertable
+ rv = CopyHeaderString(index - 1, name);
+ LOG(("Http2Decompressor::DoLiteralInternal indexed name %d %s",
+ index, name.BeginReading()));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // now the value
+ uint32_t valueLen;
+ isHuffmanEncoded = mData[mOffset] & (1 << 7);
+ rv = DecodeInteger(7, valueLen);
+ if (NS_SUCCEEDED(rv)) {
+ if (isHuffmanEncoded) {
+ rv = CopyHuffmanStringFromInput(valueLen, value);
+ } else {
+ rv = CopyStringFromInput(valueLen, value);
+ }
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ int32_t newline = 0;
+ while ((newline = value.FindChar('\n', newline)) != -1) {
+ if (value[newline + 1] == ' ' || value[newline + 1] == '\t') {
+ LOG(("Http2Decompressor::Disallowing folded header value %s",
+ value.BeginReading()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ // Increment this to avoid always finding the same newline and looping
+ // forever
+ ++newline;
+ }
+
+ LOG(("Http2Decompressor::DoLiteralInternal value %s", value.BeginReading()));
+ return NS_OK;
+}
+
+nsresult
+Http2Decompressor::DoLiteralWithoutIndex()
+{
+ // this starts with 0000 bit pattern
+ MOZ_ASSERT((mData[mOffset] & 0xF0) == 0x00);
+
+ nsAutoCString name, value;
+ nsresult rv = DoLiteralInternal(name, value, 4);
+
+ LOG(("HTTP decompressor literal without index %s %s\n",
+ name.get(), value.get()));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = OutputHeader(name, value);
+ }
+ return rv;
+}
+
+nsresult
+Http2Decompressor::DoLiteralWithIncremental()
+{
+ // this starts with 01 bit pattern
+ MOZ_ASSERT((mData[mOffset] & 0xC0) == 0x40);
+
+ nsAutoCString name, value;
+ nsresult rv = DoLiteralInternal(name, value, 6);
+ if (NS_SUCCEEDED(rv)) {
+ rv = OutputHeader(name, value);
+ }
+ // Let NET_RESET continue on so that we don't get out of sync, as it is just
+ // used to kill the stream, not the session.
+ if (NS_FAILED(rv) && rv != NS_ERROR_NET_RESET) {
+ return rv;
+ }
+
+ uint32_t room = nvPair(name, value).Size();
+ if (room > mMaxBuffer) {
+ ClearHeaderTable();
+ LOG(("HTTP decompressor literal with index not inserted due to size %u %s %s\n",
+ room, name.get(), value.get()));
+ LOG(("Decompressor state after ClearHeaderTable"));
+ DumpState();
+ return rv;
+ }
+
+ MakeRoom(room, "decompressor");
+
+ // Incremental Indexing implicitly adds a row to the header table.
+ mHeaderTable.AddElement(name, value);
+
+ uint32_t currentSize = mHeaderTable.ByteCount();
+ if (currentSize > mPeakSize) {
+ mPeakSize = currentSize;
+ }
+
+ uint32_t currentCount = mHeaderTable.VariableLength();
+ if (currentCount > mPeakCount) {
+ mPeakCount = currentCount;
+ }
+
+ LOG(("HTTP decompressor literal with index 0 %s %s\n",
+ name.get(), value.get()));
+
+ return rv;
+}
+
+nsresult
+Http2Decompressor::DoLiteralNeverIndexed()
+{
+ // This starts with 0001 bit pattern
+ MOZ_ASSERT((mData[mOffset] & 0xF0) == 0x10);
+
+ nsAutoCString name, value;
+ nsresult rv = DoLiteralInternal(name, value, 4);
+
+ LOG(("HTTP decompressor literal never indexed %s %s\n",
+ name.get(), value.get()));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = OutputHeader(name, value);
+ }
+ return rv;
+}
+
+nsresult
+Http2Decompressor::DoContextUpdate()
+{
+ // This starts with 001 bit pattern
+ MOZ_ASSERT((mData[mOffset] & 0xE0) == 0x20);
+
+ // Getting here means we have to adjust the max table size, because the
+ // compressor on the other end has signaled to us through HPACK (not H2)
+ // that it's using a size different from the currently-negotiated size.
+ // This change could either come about because we've sent a
+ // SETTINGS_HEADER_TABLE_SIZE, or because the encoder has decided that
+ // the current negotiated size doesn't fit its needs (for whatever reason)
+ // and so it needs to change it (either up to the max allowed by our SETTING,
+ // or down to some value below that)
+ uint32_t newMaxSize;
+ nsresult rv = DecodeInteger(5, newMaxSize);
+ LOG(("Http2Decompressor::DoContextUpdate new maximum size %u", newMaxSize));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (newMaxSize > mMaxBufferSetting) {
+ // This is fatal to the session - peer is trying to use a table larger
+ // than we have made available.
+ return NS_ERROR_FAILURE;
+ }
+
+ SetMaxBufferSizeInternal(newMaxSize);
+
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////
+
+nsresult
+Http2Compressor::EncodeHeaderBlock(const nsCString &nvInput,
+ const nsACString &method, const nsACString &path,
+ const nsACString &host, const nsACString &scheme,
+ bool connectForm, nsACString &output)
+{
+ mSetInitialMaxBufferSizeAllowed = false;
+ mOutput = &output;
+ output.SetCapacity(1024);
+ output.Truncate();
+ mParsedContentLength = -1;
+
+ // first thing's first - context size updates (if necessary)
+ if (mBufferSizeChangeWaiting) {
+ if (mLowestBufferSizeWaiting < mMaxBufferSetting) {
+ EncodeTableSizeChange(mLowestBufferSizeWaiting);
+ }
+ EncodeTableSizeChange(mMaxBufferSetting);
+ mBufferSizeChangeWaiting = false;
+ }
+
+ // colon headers first
+ if (!connectForm) {
+ ProcessHeader(nvPair(NS_LITERAL_CSTRING(":method"), method), false, false);
+ ProcessHeader(nvPair(NS_LITERAL_CSTRING(":path"), path), true, false);
+ ProcessHeader(nvPair(NS_LITERAL_CSTRING(":authority"), host), false, false);
+ ProcessHeader(nvPair(NS_LITERAL_CSTRING(":scheme"), scheme), false, false);
+ } else {
+ ProcessHeader(nvPair(NS_LITERAL_CSTRING(":method"), method), false, false);
+ ProcessHeader(nvPair(NS_LITERAL_CSTRING(":authority"), host), false, false);
+ }
+
+ // now the non colon headers
+ const char *beginBuffer = nvInput.BeginReading();
+
+ // This strips off the HTTP/1 method+path+version
+ int32_t crlfIndex = nvInput.Find("\r\n");
+ while (true) {
+ int32_t startIndex = crlfIndex + 2;
+
+ crlfIndex = nvInput.Find("\r\n", false, startIndex);
+ if (crlfIndex == -1) {
+ break;
+ }
+
+ int32_t colonIndex = nvInput.Find(":", false, startIndex,
+ crlfIndex - startIndex);
+ if (colonIndex == -1) {
+ break;
+ }
+
+ nsDependentCSubstring name = Substring(beginBuffer + startIndex,
+ beginBuffer + colonIndex);
+ // all header names are lower case in http/2
+ ToLowerCase(name);
+
+ // exclusions
+ if (name.EqualsLiteral("connection") ||
+ name.EqualsLiteral("host") ||
+ name.EqualsLiteral("keep-alive") ||
+ name.EqualsLiteral("proxy-connection") ||
+ name.EqualsLiteral("te") ||
+ name.EqualsLiteral("transfer-encoding") ||
+ name.EqualsLiteral("upgrade")) {
+ continue;
+ }
+
+ // colon headers are for http/2 and this is http/1 input, so that
+ // is probably a smuggling attack of some kind
+ bool isColonHeader = false;
+ for (const char *cPtr = name.BeginReading();
+ cPtr && cPtr < name.EndReading();
+ ++cPtr) {
+ if (*cPtr == ':') {
+ isColonHeader = true;
+ break;
+ } else if (*cPtr != ' ' && *cPtr != '\t') {
+ isColonHeader = false;
+ break;
+ }
+ }
+ if(isColonHeader) {
+ continue;
+ }
+
+ int32_t valueIndex = colonIndex + 1;
+
+ while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ')
+ ++valueIndex;
+
+ nsDependentCSubstring value = Substring(beginBuffer + valueIndex,
+ beginBuffer + crlfIndex);
+
+ if (name.EqualsLiteral("content-length")) {
+ int64_t len;
+ nsCString tmp(value);
+ if (nsHttp::ParseInt64(tmp.get(), nullptr, &len)) {
+ mParsedContentLength = len;
+ }
+ }
+
+ if (name.EqualsLiteral("cookie")) {
+ // cookie crumbling
+ bool haveMoreCookies = true;
+ int32_t nextCookie = valueIndex;
+ while (haveMoreCookies) {
+ int32_t semiSpaceIndex = nvInput.Find("; ", false, nextCookie,
+ crlfIndex - nextCookie);
+ if (semiSpaceIndex == -1) {
+ haveMoreCookies = false;
+ semiSpaceIndex = crlfIndex;
+ }
+ nsDependentCSubstring cookie = Substring(beginBuffer + nextCookie,
+ beginBuffer + semiSpaceIndex);
+ // cookies less than 20 bytes are not indexed
+ ProcessHeader(nvPair(name, cookie), false, cookie.Length() < 20);
+ nextCookie = semiSpaceIndex + 2;
+ }
+ } else {
+ // allow indexing of every non-cookie except authorization
+ ProcessHeader(nvPair(name, value), false,
+ name.EqualsLiteral("authorization"));
+ }
+ }
+
+ mOutput = nullptr;
+ LOG(("Compressor state after EncodeHeaderBlock"));
+ DumpState();
+ return NS_OK;
+}
+
+void
+Http2Compressor::DoOutput(Http2Compressor::outputCode code,
+ const class nvPair *pair, uint32_t index)
+{
+ // start Byte needs to be calculated from the offset after
+ // the opcode has been written out in case the output stream
+ // buffer gets resized/relocated
+ uint32_t offset = mOutput->Length();
+ uint8_t *startByte;
+
+ switch (code) {
+ case kNeverIndexedLiteral:
+ LOG(("HTTP compressor %p neverindex literal with name reference %u %s %s\n",
+ this, index, pair->mName.get(), pair->mValue.get()));
+
+ // In this case, the index will have already been adjusted to be 1-based
+ // instead of 0-based.
+ EncodeInteger(4, index); // 0001 4 bit prefix
+ startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
+ *startByte = (*startByte & 0x0f) | 0x10;
+
+ if (!index) {
+ HuffmanAppend(pair->mName);
+ }
+
+ HuffmanAppend(pair->mValue);
+ break;
+
+ case kPlainLiteral:
+ LOG(("HTTP compressor %p noindex literal with name reference %u %s %s\n",
+ this, index, pair->mName.get(), pair->mValue.get()));
+
+ // In this case, the index will have already been adjusted to be 1-based
+ // instead of 0-based.
+ EncodeInteger(4, index); // 0000 4 bit prefix
+ startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
+ *startByte = *startByte & 0x0f;
+
+ if (!index) {
+ HuffmanAppend(pair->mName);
+ }
+
+ HuffmanAppend(pair->mValue);
+ break;
+
+ case kIndexedLiteral:
+ LOG(("HTTP compressor %p literal with name reference %u %s %s\n",
+ this, index, pair->mName.get(), pair->mValue.get()));
+
+ // In this case, the index will have already been adjusted to be 1-based
+ // instead of 0-based.
+ EncodeInteger(6, index); // 01 2 bit prefix
+ startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
+ *startByte = (*startByte & 0x3f) | 0x40;
+
+ if (!index) {
+ HuffmanAppend(pair->mName);
+ }
+
+ HuffmanAppend(pair->mValue);
+ break;
+
+ case kIndex:
+ LOG(("HTTP compressor %p index %u %s %s\n",
+ this, index, pair->mName.get(), pair->mValue.get()));
+ // NWGH - make this plain old index instead of index + 1
+ // In this case, we are passed the raw 0-based C index, and need to
+ // increment to make it 1-based and comply with the spec
+ EncodeInteger(7, index + 1);
+ startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
+ *startByte = *startByte | 0x80; // 1 1 bit prefix
+ break;
+
+ }
+}
+
+// writes the encoded integer onto the output
+void
+Http2Compressor::EncodeInteger(uint32_t prefixLen, uint32_t val)
+{
+ uint32_t mask = (1 << prefixLen) - 1;
+ uint8_t tmp;
+
+ if (val < mask) {
+ // 1 byte encoding!
+ tmp = val;
+ mOutput->Append(reinterpret_cast<char *>(&tmp), 1);
+ return;
+ }
+
+ if (mask) {
+ val -= mask;
+ tmp = mask;
+ mOutput->Append(reinterpret_cast<char *>(&tmp), 1);
+ }
+
+ uint32_t q, r;
+ do {
+ q = val / 128;
+ r = val % 128;
+ tmp = r;
+ if (q) {
+ tmp |= 0x80; // chain bit
+ }
+ val = q;
+ mOutput->Append(reinterpret_cast<char *>(&tmp), 1);
+ } while (q);
+}
+
+void
+Http2Compressor::HuffmanAppend(const nsCString &value)
+{
+ nsAutoCString buf;
+ uint8_t bitsLeft = 8;
+ uint32_t length = value.Length();
+ uint32_t offset;
+ uint8_t *startByte;
+
+ for (uint32_t i = 0; i < length; ++i) {
+ uint8_t idx = static_cast<uint8_t>(value[i]);
+ uint8_t huffLength = HuffmanOutgoing[idx].mLength;
+ uint32_t huffValue = HuffmanOutgoing[idx].mValue;
+
+ if (bitsLeft < 8) {
+ // Fill in the least significant <bitsLeft> bits of the previous byte
+ // first
+ uint32_t val;
+ if (huffLength >= bitsLeft) {
+ val = huffValue & ~((1 << (huffLength - bitsLeft)) - 1);
+ val >>= (huffLength - bitsLeft);
+ } else {
+ val = huffValue << (bitsLeft - huffLength);
+ }
+ val &= ((1 << bitsLeft) - 1);
+ offset = buf.Length() - 1;
+ startByte = reinterpret_cast<unsigned char *>(buf.BeginWriting()) + offset;
+ *startByte = *startByte | static_cast<uint8_t>(val & 0xFF);
+ if (huffLength >= bitsLeft) {
+ huffLength -= bitsLeft;
+ bitsLeft = 8;
+ } else {
+ bitsLeft -= huffLength;
+ huffLength = 0;
+ }
+ }
+
+ while (huffLength >= 8) {
+ uint32_t mask = ~((1 << (huffLength - 8)) - 1);
+ uint8_t val = ((huffValue & mask) >> (huffLength - 8)) & 0xFF;
+ buf.Append(reinterpret_cast<char *>(&val), 1);
+ huffLength -= 8;
+ }
+
+ if (huffLength) {
+ // Fill in the most significant <huffLength> bits of the next byte
+ bitsLeft = 8 - huffLength;
+ uint8_t val = (huffValue & ((1 << huffLength) - 1)) << bitsLeft;
+ buf.Append(reinterpret_cast<char *>(&val), 1);
+ }
+ }
+
+ if (bitsLeft != 8) {
+ // Pad the last <bitsLeft> bits with ones, which corresponds to the EOS
+ // encoding
+ uint8_t val = (1 << bitsLeft) - 1;
+ offset = buf.Length() - 1;
+ startByte = reinterpret_cast<unsigned char *>(buf.BeginWriting()) + offset;
+ *startByte = *startByte | val;
+ }
+
+ // Now we know how long our encoded string is, we can fill in our length
+ uint32_t bufLength = buf.Length();
+ offset = mOutput->Length();
+ EncodeInteger(7, bufLength);
+ startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
+ *startByte = *startByte | 0x80;
+
+ // Finally, we can add our REAL data!
+ mOutput->Append(buf);
+ LOG(("Http2Compressor::HuffmanAppend %p encoded %d byte original on %d "
+ "bytes.\n", this, length, bufLength));
+}
+
+void
+Http2Compressor::ProcessHeader(const nvPair inputPair, bool noLocalIndex,
+ bool neverIndex)
+{
+ uint32_t newSize = inputPair.Size();
+ uint32_t headerTableSize = mHeaderTable.Length();
+ uint32_t matchedIndex = 0u;
+ uint32_t nameReference = 0u;
+ bool match = false;
+
+ LOG(("Http2Compressor::ProcessHeader %s %s", inputPair.mName.get(),
+ inputPair.mValue.get()));
+
+ // NWGH - make this index = 1; index <= headerTableSize; ++index
+ for (uint32_t index = 0; index < headerTableSize; ++index) {
+ if (mHeaderTable[index]->mName.Equals(inputPair.mName)) {
+ // NWGH - make this nameReference = index
+ nameReference = index + 1;
+ if (mHeaderTable[index]->mValue.Equals(inputPair.mValue)) {
+ match = true;
+ matchedIndex = index;
+ break;
+ }
+ }
+ }
+
+ // We need to emit a new literal
+ if (!match || noLocalIndex || neverIndex) {
+ if (neverIndex) {
+ DoOutput(kNeverIndexedLiteral, &inputPair, nameReference);
+ LOG(("Compressor state after literal never index"));
+ DumpState();
+ return;
+ }
+
+ if (noLocalIndex || (newSize > (mMaxBuffer / 2)) || (mMaxBuffer < 128)) {
+ DoOutput(kPlainLiteral, &inputPair, nameReference);
+ LOG(("Compressor state after literal without index"));
+ DumpState();
+ return;
+ }
+
+ // make sure to makeroom() first so that any implied items
+ // get preserved.
+ MakeRoom(newSize, "compressor");
+ DoOutput(kIndexedLiteral, &inputPair, nameReference);
+
+ mHeaderTable.AddElement(inputPair.mName, inputPair.mValue);
+ LOG(("HTTP compressor %p new literal placed at index 0\n",
+ this));
+ LOG(("Compressor state after literal with index"));
+ DumpState();
+ return;
+ }
+
+ // emit an index
+ DoOutput(kIndex, &inputPair, matchedIndex);
+
+ LOG(("Compressor state after index"));
+ DumpState();
+ return;
+}
+
+void
+Http2Compressor::EncodeTableSizeChange(uint32_t newMaxSize)
+{
+ uint32_t offset = mOutput->Length();
+ EncodeInteger(5, newMaxSize);
+ uint8_t *startByte = reinterpret_cast<uint8_t *>(mOutput->BeginWriting()) + offset;
+ *startByte = *startByte | 0x20;
+}
+
+void
+Http2Compressor::SetMaxBufferSize(uint32_t maxBufferSize)
+{
+ mMaxBufferSetting = maxBufferSize;
+ SetMaxBufferSizeInternal(maxBufferSize);
+ if (!mBufferSizeChangeWaiting) {
+ mBufferSizeChangeWaiting = true;
+ mLowestBufferSizeWaiting = maxBufferSize;
+ } else if (maxBufferSize < mLowestBufferSizeWaiting) {
+ mLowestBufferSizeWaiting = maxBufferSize;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Compression.h b/netwerk/protocol/http/Http2Compression.h
new file mode 100644
index 000000000..0fb391bf7
--- /dev/null
+++ b/netwerk/protocol/http/Http2Compression.h
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http2Compression_Internal_h
+#define mozilla_net_Http2Compression_Internal_h
+
+// HPACK - RFC 7541
+// https://www.rfc-editor.org/rfc/rfc7541.txt
+
+#include "mozilla/Attributes.h"
+#include "nsDeque.h"
+#include "nsString.h"
+#include "nsIMemoryReporter.h"
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanIncomingTable;
+
+void Http2CompressionCleanup();
+
+class nvPair
+{
+public:
+nvPair(const nsACString &name, const nsACString &value)
+ : mName(name)
+ , mValue(value)
+ { }
+
+ uint32_t Size() const { return mName.Length() + mValue.Length() + 32; }
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ nsCString mName;
+ nsCString mValue;
+};
+
+class nvFIFO
+{
+public:
+ nvFIFO();
+ ~nvFIFO();
+ void AddElement(const nsCString &name, const nsCString &value);
+ void AddElement(const nsCString &name);
+ void RemoveElement();
+ uint32_t ByteCount() const;
+ uint32_t Length() const;
+ uint32_t VariableLength() const;
+ size_t StaticLength() const;
+ void Clear();
+ const nvPair *operator[] (size_t index) const;
+
+private:
+ uint32_t mByteCount;
+ nsDeque mTable;
+};
+
+class HpackDynamicTableReporter;
+
+class Http2BaseCompressor
+{
+public:
+ Http2BaseCompressor();
+ virtual ~Http2BaseCompressor();
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ nsresult SetInitialMaxBufferSize(uint32_t maxBufferSize);
+
+protected:
+ const static uint32_t kDefaultMaxBuffer = 4096;
+
+ virtual void ClearHeaderTable();
+ virtual void MakeRoom(uint32_t amount, const char *direction);
+ virtual void DumpState();
+ virtual void SetMaxBufferSizeInternal(uint32_t maxBufferSize);
+
+ nsACString *mOutput;
+ nvFIFO mHeaderTable;
+
+ uint32_t mMaxBuffer;
+ uint32_t mMaxBufferSetting;
+ bool mSetInitialMaxBufferSizeAllowed;
+
+ uint32_t mPeakSize;
+ uint32_t mPeakCount;
+ MOZ_INIT_OUTSIDE_CTOR
+ Telemetry::ID mPeakSizeID;
+ MOZ_INIT_OUTSIDE_CTOR
+ Telemetry::ID mPeakCountID;
+
+private:
+ RefPtr<HpackDynamicTableReporter> mDynamicReporter;
+};
+
+class Http2Compressor;
+
+class Http2Decompressor final : public Http2BaseCompressor
+{
+public:
+ Http2Decompressor()
+ {
+ mPeakSizeID = Telemetry::HPACK_PEAK_SIZE_DECOMPRESSOR;
+ mPeakCountID = Telemetry::HPACK_PEAK_COUNT_DECOMPRESSOR;
+ };
+ virtual ~Http2Decompressor() { } ;
+
+ // NS_OK: Produces the working set of HTTP/1 formatted headers
+ nsresult DecodeHeaderBlock(const uint8_t *data, uint32_t datalen,
+ nsACString &output, bool isPush);
+
+ void GetStatus(nsACString &hdr) { hdr = mHeaderStatus; }
+ void GetHost(nsACString &hdr) { hdr = mHeaderHost; }
+ void GetScheme(nsACString &hdr) { hdr = mHeaderScheme; }
+ void GetPath(nsACString &hdr) { hdr = mHeaderPath; }
+ void GetMethod(nsACString &hdr) { hdr = mHeaderMethod; }
+
+private:
+ nsresult DoIndexed();
+ nsresult DoLiteralWithoutIndex();
+ nsresult DoLiteralWithIncremental();
+ nsresult DoLiteralInternal(nsACString &, nsACString &, uint32_t);
+ nsresult DoLiteralNeverIndexed();
+ nsresult DoContextUpdate();
+
+ nsresult DecodeInteger(uint32_t prefixLen, uint32_t &result);
+ nsresult OutputHeader(uint32_t index);
+ nsresult OutputHeader(const nsACString &name, const nsACString &value);
+
+ nsresult CopyHeaderString(uint32_t index, nsACString &name);
+ nsresult CopyStringFromInput(uint32_t index, nsACString &val);
+ uint8_t ExtractByte(uint8_t bitsLeft, uint32_t &bytesConsumed);
+ nsresult CopyHuffmanStringFromInput(uint32_t index, nsACString &val);
+ nsresult DecodeHuffmanCharacter(const HuffmanIncomingTable *table, uint8_t &c,
+ uint32_t &bytesConsumed, uint8_t &bitsLeft);
+ nsresult DecodeFinalHuffmanCharacter(const HuffmanIncomingTable *table,
+ uint8_t &c, uint8_t &bitsLeft);
+
+ nsCString mHeaderStatus;
+ nsCString mHeaderHost;
+ nsCString mHeaderScheme;
+ nsCString mHeaderPath;
+ nsCString mHeaderMethod;
+
+ // state variables when DecodeBlock() is on the stack
+ uint32_t mOffset;
+ const uint8_t *mData;
+ uint32_t mDataLen;
+ bool mSeenNonColonHeader;
+ bool mIsPush;
+};
+
+
+class Http2Compressor final : public Http2BaseCompressor
+{
+public:
+ Http2Compressor() : mParsedContentLength(-1),
+ mBufferSizeChangeWaiting(false),
+ mLowestBufferSizeWaiting(0)
+ {
+ mPeakSizeID = Telemetry::HPACK_PEAK_SIZE_COMPRESSOR;
+ mPeakCountID = Telemetry::HPACK_PEAK_COUNT_COMPRESSOR;
+ };
+ virtual ~Http2Compressor() { }
+
+ // HTTP/1 formatted header block as input - HTTP/2 formatted
+ // header block as output
+ nsresult EncodeHeaderBlock(const nsCString &nvInput,
+ const nsACString &method, const nsACString &path,
+ const nsACString &host, const nsACString &scheme,
+ bool connectForm, nsACString &output);
+
+ int64_t GetParsedContentLength() { return mParsedContentLength; } // -1 on not found
+
+ void SetMaxBufferSize(uint32_t maxBufferSize);
+
+private:
+ enum outputCode {
+ kNeverIndexedLiteral,
+ kPlainLiteral,
+ kIndexedLiteral,
+ kIndex
+ };
+
+ void DoOutput(Http2Compressor::outputCode code,
+ const class nvPair *pair, uint32_t index);
+ void EncodeInteger(uint32_t prefixLen, uint32_t val);
+ void ProcessHeader(const nvPair inputPair, bool noLocalIndex,
+ bool neverIndex);
+ void HuffmanAppend(const nsCString &value);
+ void EncodeTableSizeChange(uint32_t newMaxSize);
+
+ int64_t mParsedContentLength;
+ bool mBufferSizeChangeWaiting;
+ uint32_t mLowestBufferSizeWaiting;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http2Compression_Internal_h
diff --git a/netwerk/protocol/http/Http2HuffmanIncoming.h b/netwerk/protocol/http/Http2HuffmanIncoming.h
new file mode 100644
index 000000000..cf035cd90
--- /dev/null
+++ b/netwerk/protocol/http/Http2HuffmanIncoming.h
@@ -0,0 +1,4054 @@
+/*
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT!
+ */
+#ifndef mozilla__net__Http2HuffmanIncoming_h
+#define mozilla__net__Http2HuffmanIncoming_h
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanIncomingTable;
+
+struct HuffmanIncomingEntry {
+ const uint16_t mValue:9; // 9 bits so it can hold 0..256
+ const uint16_t mPrefixLen:7; // only holds 1..8
+};
+
+// The data members are public only so they can be statically constructed. All
+// accesses should be done through the functions.
+struct HuffmanIncomingTable {
+ // The normal entries, for indices in the range 0..(mNumEntries-1).
+ const HuffmanIncomingEntry* const mEntries;
+
+ // The next tables, for indices in the range mNumEntries..255. Must be
+ // |nullptr| if mIndexOfFirstNextTable is 256.
+ const HuffmanIncomingTable** const mNextTables;
+
+ // The index of the first next table (equal to the number of entries in
+ // mEntries). This cannot be a uint8_t because it can have the value 256,
+ // in which case there are no next tables and mNextTables must be |nullptr|.
+ const uint16_t mIndexOfFirstNextTable;
+
+ const uint8_t mPrefixLen;
+
+ bool IndexHasANextTable(uint8_t aIndex) const
+ {
+ return aIndex >= mIndexOfFirstNextTable;
+ }
+
+ const HuffmanIncomingEntry* Entry(uint8_t aIndex) const
+ {
+ MOZ_ASSERT(aIndex < mIndexOfFirstNextTable);
+ return &mEntries[aIndex];
+ }
+
+ const HuffmanIncomingTable* NextTable(uint8_t aIndex) const
+ {
+ MOZ_ASSERT(aIndex >= mIndexOfFirstNextTable);
+ return mNextTables[aIndex - mIndexOfFirstNextTable];
+ }
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_254[] = {
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 33, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 34, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 40, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+ { 41, 2 },
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_254 = {
+ HuffmanIncomingEntries_254,
+ nullptr,
+ 256,
+ 2
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_254[] = {
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 92, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 195, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 208, 3 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 128, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 130, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 131, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 162, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 184, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 194, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 224, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 226, 4 },
+ { 153, 5 },
+ { 153, 5 },
+ { 153, 5 },
+ { 153, 5 },
+ { 153, 5 },
+ { 153, 5 },
+ { 153, 5 },
+ { 153, 5 },
+ { 161, 5 },
+ { 161, 5 },
+ { 161, 5 },
+ { 161, 5 },
+ { 161, 5 },
+ { 161, 5 },
+ { 161, 5 },
+ { 161, 5 },
+ { 167, 5 },
+ { 167, 5 },
+ { 167, 5 },
+ { 167, 5 },
+ { 167, 5 },
+ { 167, 5 },
+ { 167, 5 },
+ { 167, 5 },
+ { 172, 5 },
+ { 172, 5 },
+ { 172, 5 },
+ { 172, 5 },
+ { 172, 5 },
+ { 172, 5 },
+ { 172, 5 },
+ { 172, 5 },
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_254 = {
+ HuffmanIncomingEntries_255_254,
+ nullptr,
+ 256,
+ 5
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_246[] = {
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 199, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+ { 207, 1 },
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_246 = {
+ HuffmanIncomingEntries_255_255_246,
+ nullptr,
+ 256,
+ 1
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_247[] = {
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 234, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+ { 235, 1 },
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_247 = {
+ HuffmanIncomingEntries_255_255_247,
+ nullptr,
+ 256,
+ 1
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_248[] = {
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 192, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 193, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 200, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+ { 201, 2 },
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_248 = {
+ HuffmanIncomingEntries_255_255_248,
+ nullptr,
+ 256,
+ 2
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_249[] = {
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 202, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 205, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 210, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+ { 213, 2 },
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_249 = {
+ HuffmanIncomingEntries_255_255_249,
+ nullptr,
+ 256,
+ 2
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_250[] = {
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 218, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 219, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 238, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+ { 240, 2 },
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_250 = {
+ HuffmanIncomingEntries_255_255_250,
+ nullptr,
+ 256,
+ 2
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_251[] = {
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 242, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 243, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 255, 2 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 203, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+ { 204, 3 },
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_251 = {
+ HuffmanIncomingEntries_255_255_251,
+ nullptr,
+ 256,
+ 3
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_252[] = {
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 211, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 212, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 214, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 221, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 222, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 223, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 241, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+ { 244, 3 },
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_252 = {
+ HuffmanIncomingEntries_255_255_252,
+ nullptr,
+ 256,
+ 3
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_253[] = {
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 245, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 246, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 247, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 248, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 250, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 251, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 252, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+ { 253, 3 },
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_253 = {
+ HuffmanIncomingEntries_255_255_253,
+ nullptr,
+ 256,
+ 3
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_254[] = {
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 254, 3 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 2, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 3, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 4, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 5, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 6, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 7, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 8, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 11, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 12, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 14, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 15, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 16, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 17, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+ { 18, 4 },
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_254 = {
+ HuffmanIncomingEntries_255_255_254,
+ nullptr,
+ 256,
+ 4
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255_255[] = {
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 19, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 20, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 21, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 23, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 24, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 25, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 26, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 27, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 28, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 29, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 30, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 31, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 127, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 220, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 249, 4 },
+ { 10, 6 },
+ { 10, 6 },
+ { 10, 6 },
+ { 10, 6 },
+ { 13, 6 },
+ { 13, 6 },
+ { 13, 6 },
+ { 13, 6 },
+ { 22, 6 },
+ { 22, 6 },
+ { 22, 6 },
+ { 22, 6 },
+ { 256, 6 },
+ { 256, 6 },
+ { 256, 6 },
+ { 256, 6 },
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255_255 = {
+ HuffmanIncomingEntries_255_255_255,
+ nullptr,
+ 256,
+ 6
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255_255[] = {
+ { 176, 5 },
+ { 176, 5 },
+ { 176, 5 },
+ { 176, 5 },
+ { 176, 5 },
+ { 176, 5 },
+ { 176, 5 },
+ { 176, 5 },
+ { 177, 5 },
+ { 177, 5 },
+ { 177, 5 },
+ { 177, 5 },
+ { 177, 5 },
+ { 177, 5 },
+ { 177, 5 },
+ { 177, 5 },
+ { 179, 5 },
+ { 179, 5 },
+ { 179, 5 },
+ { 179, 5 },
+ { 179, 5 },
+ { 179, 5 },
+ { 179, 5 },
+ { 179, 5 },
+ { 209, 5 },
+ { 209, 5 },
+ { 209, 5 },
+ { 209, 5 },
+ { 209, 5 },
+ { 209, 5 },
+ { 209, 5 },
+ { 209, 5 },
+ { 216, 5 },
+ { 216, 5 },
+ { 216, 5 },
+ { 216, 5 },
+ { 216, 5 },
+ { 216, 5 },
+ { 216, 5 },
+ { 216, 5 },
+ { 217, 5 },
+ { 217, 5 },
+ { 217, 5 },
+ { 217, 5 },
+ { 217, 5 },
+ { 217, 5 },
+ { 217, 5 },
+ { 217, 5 },
+ { 227, 5 },
+ { 227, 5 },
+ { 227, 5 },
+ { 227, 5 },
+ { 227, 5 },
+ { 227, 5 },
+ { 227, 5 },
+ { 227, 5 },
+ { 229, 5 },
+ { 229, 5 },
+ { 229, 5 },
+ { 229, 5 },
+ { 229, 5 },
+ { 229, 5 },
+ { 229, 5 },
+ { 229, 5 },
+ { 230, 5 },
+ { 230, 5 },
+ { 230, 5 },
+ { 230, 5 },
+ { 230, 5 },
+ { 230, 5 },
+ { 230, 5 },
+ { 230, 5 },
+ { 129, 6 },
+ { 129, 6 },
+ { 129, 6 },
+ { 129, 6 },
+ { 132, 6 },
+ { 132, 6 },
+ { 132, 6 },
+ { 132, 6 },
+ { 133, 6 },
+ { 133, 6 },
+ { 133, 6 },
+ { 133, 6 },
+ { 134, 6 },
+ { 134, 6 },
+ { 134, 6 },
+ { 134, 6 },
+ { 136, 6 },
+ { 136, 6 },
+ { 136, 6 },
+ { 136, 6 },
+ { 146, 6 },
+ { 146, 6 },
+ { 146, 6 },
+ { 146, 6 },
+ { 154, 6 },
+ { 154, 6 },
+ { 154, 6 },
+ { 154, 6 },
+ { 156, 6 },
+ { 156, 6 },
+ { 156, 6 },
+ { 156, 6 },
+ { 160, 6 },
+ { 160, 6 },
+ { 160, 6 },
+ { 160, 6 },
+ { 163, 6 },
+ { 163, 6 },
+ { 163, 6 },
+ { 163, 6 },
+ { 164, 6 },
+ { 164, 6 },
+ { 164, 6 },
+ { 164, 6 },
+ { 169, 6 },
+ { 169, 6 },
+ { 169, 6 },
+ { 169, 6 },
+ { 170, 6 },
+ { 170, 6 },
+ { 170, 6 },
+ { 170, 6 },
+ { 173, 6 },
+ { 173, 6 },
+ { 173, 6 },
+ { 173, 6 },
+ { 178, 6 },
+ { 178, 6 },
+ { 178, 6 },
+ { 178, 6 },
+ { 181, 6 },
+ { 181, 6 },
+ { 181, 6 },
+ { 181, 6 },
+ { 185, 6 },
+ { 185, 6 },
+ { 185, 6 },
+ { 185, 6 },
+ { 186, 6 },
+ { 186, 6 },
+ { 186, 6 },
+ { 186, 6 },
+ { 187, 6 },
+ { 187, 6 },
+ { 187, 6 },
+ { 187, 6 },
+ { 189, 6 },
+ { 189, 6 },
+ { 189, 6 },
+ { 189, 6 },
+ { 190, 6 },
+ { 190, 6 },
+ { 190, 6 },
+ { 190, 6 },
+ { 196, 6 },
+ { 196, 6 },
+ { 196, 6 },
+ { 196, 6 },
+ { 198, 6 },
+ { 198, 6 },
+ { 198, 6 },
+ { 198, 6 },
+ { 228, 6 },
+ { 228, 6 },
+ { 228, 6 },
+ { 228, 6 },
+ { 232, 6 },
+ { 232, 6 },
+ { 232, 6 },
+ { 232, 6 },
+ { 233, 6 },
+ { 233, 6 },
+ { 233, 6 },
+ { 233, 6 },
+ { 1, 7 },
+ { 1, 7 },
+ { 135, 7 },
+ { 135, 7 },
+ { 137, 7 },
+ { 137, 7 },
+ { 138, 7 },
+ { 138, 7 },
+ { 139, 7 },
+ { 139, 7 },
+ { 140, 7 },
+ { 140, 7 },
+ { 141, 7 },
+ { 141, 7 },
+ { 143, 7 },
+ { 143, 7 },
+ { 147, 7 },
+ { 147, 7 },
+ { 149, 7 },
+ { 149, 7 },
+ { 150, 7 },
+ { 150, 7 },
+ { 151, 7 },
+ { 151, 7 },
+ { 152, 7 },
+ { 152, 7 },
+ { 155, 7 },
+ { 155, 7 },
+ { 157, 7 },
+ { 157, 7 },
+ { 158, 7 },
+ { 158, 7 },
+ { 165, 7 },
+ { 165, 7 },
+ { 166, 7 },
+ { 166, 7 },
+ { 168, 7 },
+ { 168, 7 },
+ { 174, 7 },
+ { 174, 7 },
+ { 175, 7 },
+ { 175, 7 },
+ { 180, 7 },
+ { 180, 7 },
+ { 182, 7 },
+ { 182, 7 },
+ { 183, 7 },
+ { 183, 7 },
+ { 188, 7 },
+ { 188, 7 },
+ { 191, 7 },
+ { 191, 7 },
+ { 197, 7 },
+ { 197, 7 },
+ { 231, 7 },
+ { 231, 7 },
+ { 239, 7 },
+ { 239, 7 },
+ { 9, 8 },
+ { 142, 8 },
+ { 144, 8 },
+ { 145, 8 },
+ { 148, 8 },
+ { 159, 8 },
+ { 171, 8 },
+ { 206, 8 },
+ { 215, 8 },
+ { 225, 8 },
+ { 236, 8 },
+ { 237, 8 },
+};
+
+static const HuffmanIncomingTable* HuffmanIncomingNextTables_255_255[] = {
+ &HuffmanIncoming_255_255_246,
+ &HuffmanIncoming_255_255_247,
+ &HuffmanIncoming_255_255_248,
+ &HuffmanIncoming_255_255_249,
+ &HuffmanIncoming_255_255_250,
+ &HuffmanIncoming_255_255_251,
+ &HuffmanIncoming_255_255_252,
+ &HuffmanIncoming_255_255_253,
+ &HuffmanIncoming_255_255_254,
+ &HuffmanIncoming_255_255_255,
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255_255 = {
+ HuffmanIncomingEntries_255_255,
+ HuffmanIncomingNextTables_255_255,
+ 246,
+ 8
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntries_255[] = {
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 63, 2 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 39, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 43, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 124, 3 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 35, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 62, 4 },
+ { 0, 5 },
+ { 0, 5 },
+ { 0, 5 },
+ { 0, 5 },
+ { 0, 5 },
+ { 0, 5 },
+ { 0, 5 },
+ { 0, 5 },
+ { 36, 5 },
+ { 36, 5 },
+ { 36, 5 },
+ { 36, 5 },
+ { 36, 5 },
+ { 36, 5 },
+ { 36, 5 },
+ { 36, 5 },
+ { 64, 5 },
+ { 64, 5 },
+ { 64, 5 },
+ { 64, 5 },
+ { 64, 5 },
+ { 64, 5 },
+ { 64, 5 },
+ { 64, 5 },
+ { 91, 5 },
+ { 91, 5 },
+ { 91, 5 },
+ { 91, 5 },
+ { 91, 5 },
+ { 91, 5 },
+ { 91, 5 },
+ { 91, 5 },
+ { 93, 5 },
+ { 93, 5 },
+ { 93, 5 },
+ { 93, 5 },
+ { 93, 5 },
+ { 93, 5 },
+ { 93, 5 },
+ { 93, 5 },
+ { 126, 5 },
+ { 126, 5 },
+ { 126, 5 },
+ { 126, 5 },
+ { 126, 5 },
+ { 126, 5 },
+ { 126, 5 },
+ { 126, 5 },
+ { 94, 6 },
+ { 94, 6 },
+ { 94, 6 },
+ { 94, 6 },
+ { 125, 6 },
+ { 125, 6 },
+ { 125, 6 },
+ { 125, 6 },
+ { 60, 7 },
+ { 60, 7 },
+ { 96, 7 },
+ { 96, 7 },
+ { 123, 7 },
+ { 123, 7 },
+};
+
+static const HuffmanIncomingTable* HuffmanIncomingNextTables_255[] = {
+ &HuffmanIncoming_255_254,
+ &HuffmanIncoming_255_255,
+};
+
+static const HuffmanIncomingTable HuffmanIncoming_255 = {
+ HuffmanIncomingEntries_255,
+ HuffmanIncomingNextTables_255,
+ 254,
+ 7
+};
+
+static const HuffmanIncomingEntry HuffmanIncomingEntriesRoot[] = {
+ { 48, 5 },
+ { 48, 5 },
+ { 48, 5 },
+ { 48, 5 },
+ { 48, 5 },
+ { 48, 5 },
+ { 48, 5 },
+ { 48, 5 },
+ { 49, 5 },
+ { 49, 5 },
+ { 49, 5 },
+ { 49, 5 },
+ { 49, 5 },
+ { 49, 5 },
+ { 49, 5 },
+ { 49, 5 },
+ { 50, 5 },
+ { 50, 5 },
+ { 50, 5 },
+ { 50, 5 },
+ { 50, 5 },
+ { 50, 5 },
+ { 50, 5 },
+ { 50, 5 },
+ { 97, 5 },
+ { 97, 5 },
+ { 97, 5 },
+ { 97, 5 },
+ { 97, 5 },
+ { 97, 5 },
+ { 97, 5 },
+ { 97, 5 },
+ { 99, 5 },
+ { 99, 5 },
+ { 99, 5 },
+ { 99, 5 },
+ { 99, 5 },
+ { 99, 5 },
+ { 99, 5 },
+ { 99, 5 },
+ { 101, 5 },
+ { 101, 5 },
+ { 101, 5 },
+ { 101, 5 },
+ { 101, 5 },
+ { 101, 5 },
+ { 101, 5 },
+ { 101, 5 },
+ { 105, 5 },
+ { 105, 5 },
+ { 105, 5 },
+ { 105, 5 },
+ { 105, 5 },
+ { 105, 5 },
+ { 105, 5 },
+ { 105, 5 },
+ { 111, 5 },
+ { 111, 5 },
+ { 111, 5 },
+ { 111, 5 },
+ { 111, 5 },
+ { 111, 5 },
+ { 111, 5 },
+ { 111, 5 },
+ { 115, 5 },
+ { 115, 5 },
+ { 115, 5 },
+ { 115, 5 },
+ { 115, 5 },
+ { 115, 5 },
+ { 115, 5 },
+ { 115, 5 },
+ { 116, 5 },
+ { 116, 5 },
+ { 116, 5 },
+ { 116, 5 },
+ { 116, 5 },
+ { 116, 5 },
+ { 116, 5 },
+ { 116, 5 },
+ { 32, 6 },
+ { 32, 6 },
+ { 32, 6 },
+ { 32, 6 },
+ { 37, 6 },
+ { 37, 6 },
+ { 37, 6 },
+ { 37, 6 },
+ { 45, 6 },
+ { 45, 6 },
+ { 45, 6 },
+ { 45, 6 },
+ { 46, 6 },
+ { 46, 6 },
+ { 46, 6 },
+ { 46, 6 },
+ { 47, 6 },
+ { 47, 6 },
+ { 47, 6 },
+ { 47, 6 },
+ { 51, 6 },
+ { 51, 6 },
+ { 51, 6 },
+ { 51, 6 },
+ { 52, 6 },
+ { 52, 6 },
+ { 52, 6 },
+ { 52, 6 },
+ { 53, 6 },
+ { 53, 6 },
+ { 53, 6 },
+ { 53, 6 },
+ { 54, 6 },
+ { 54, 6 },
+ { 54, 6 },
+ { 54, 6 },
+ { 55, 6 },
+ { 55, 6 },
+ { 55, 6 },
+ { 55, 6 },
+ { 56, 6 },
+ { 56, 6 },
+ { 56, 6 },
+ { 56, 6 },
+ { 57, 6 },
+ { 57, 6 },
+ { 57, 6 },
+ { 57, 6 },
+ { 61, 6 },
+ { 61, 6 },
+ { 61, 6 },
+ { 61, 6 },
+ { 65, 6 },
+ { 65, 6 },
+ { 65, 6 },
+ { 65, 6 },
+ { 95, 6 },
+ { 95, 6 },
+ { 95, 6 },
+ { 95, 6 },
+ { 98, 6 },
+ { 98, 6 },
+ { 98, 6 },
+ { 98, 6 },
+ { 100, 6 },
+ { 100, 6 },
+ { 100, 6 },
+ { 100, 6 },
+ { 102, 6 },
+ { 102, 6 },
+ { 102, 6 },
+ { 102, 6 },
+ { 103, 6 },
+ { 103, 6 },
+ { 103, 6 },
+ { 103, 6 },
+ { 104, 6 },
+ { 104, 6 },
+ { 104, 6 },
+ { 104, 6 },
+ { 108, 6 },
+ { 108, 6 },
+ { 108, 6 },
+ { 108, 6 },
+ { 109, 6 },
+ { 109, 6 },
+ { 109, 6 },
+ { 109, 6 },
+ { 110, 6 },
+ { 110, 6 },
+ { 110, 6 },
+ { 110, 6 },
+ { 112, 6 },
+ { 112, 6 },
+ { 112, 6 },
+ { 112, 6 },
+ { 114, 6 },
+ { 114, 6 },
+ { 114, 6 },
+ { 114, 6 },
+ { 117, 6 },
+ { 117, 6 },
+ { 117, 6 },
+ { 117, 6 },
+ { 58, 7 },
+ { 58, 7 },
+ { 66, 7 },
+ { 66, 7 },
+ { 67, 7 },
+ { 67, 7 },
+ { 68, 7 },
+ { 68, 7 },
+ { 69, 7 },
+ { 69, 7 },
+ { 70, 7 },
+ { 70, 7 },
+ { 71, 7 },
+ { 71, 7 },
+ { 72, 7 },
+ { 72, 7 },
+ { 73, 7 },
+ { 73, 7 },
+ { 74, 7 },
+ { 74, 7 },
+ { 75, 7 },
+ { 75, 7 },
+ { 76, 7 },
+ { 76, 7 },
+ { 77, 7 },
+ { 77, 7 },
+ { 78, 7 },
+ { 78, 7 },
+ { 79, 7 },
+ { 79, 7 },
+ { 80, 7 },
+ { 80, 7 },
+ { 81, 7 },
+ { 81, 7 },
+ { 82, 7 },
+ { 82, 7 },
+ { 83, 7 },
+ { 83, 7 },
+ { 84, 7 },
+ { 84, 7 },
+ { 85, 7 },
+ { 85, 7 },
+ { 86, 7 },
+ { 86, 7 },
+ { 87, 7 },
+ { 87, 7 },
+ { 89, 7 },
+ { 89, 7 },
+ { 106, 7 },
+ { 106, 7 },
+ { 107, 7 },
+ { 107, 7 },
+ { 113, 7 },
+ { 113, 7 },
+ { 118, 7 },
+ { 118, 7 },
+ { 119, 7 },
+ { 119, 7 },
+ { 120, 7 },
+ { 120, 7 },
+ { 121, 7 },
+ { 121, 7 },
+ { 122, 7 },
+ { 122, 7 },
+ { 38, 8 },
+ { 42, 8 },
+ { 44, 8 },
+ { 59, 8 },
+ { 88, 8 },
+ { 90, 8 },
+};
+
+static const HuffmanIncomingTable* HuffmanIncomingNextTablesRoot[] = {
+ &HuffmanIncoming_254,
+ &HuffmanIncoming_255,
+};
+
+static const HuffmanIncomingTable HuffmanIncomingRoot = {
+ HuffmanIncomingEntriesRoot,
+ HuffmanIncomingNextTablesRoot,
+ 254,
+ 8
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__Http2HuffmanIncoming_h
diff --git a/netwerk/protocol/http/Http2HuffmanOutgoing.h b/netwerk/protocol/http/Http2HuffmanOutgoing.h
new file mode 100644
index 000000000..ba59e6bd5
--- /dev/null
+++ b/netwerk/protocol/http/Http2HuffmanOutgoing.h
@@ -0,0 +1,278 @@
+/*
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT!
+ */
+#ifndef mozilla__net__Http2HuffmanOutgoing_h
+#define mozilla__net__Http2HuffmanOutgoing_h
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanOutgoingEntry {
+ uint32_t mValue;
+ uint8_t mLength;
+};
+
+static HuffmanOutgoingEntry HuffmanOutgoing[] = {
+ { 0x00001ff8, 13 },
+ { 0x007fffd8, 23 },
+ { 0x0fffffe2, 28 },
+ { 0x0fffffe3, 28 },
+ { 0x0fffffe4, 28 },
+ { 0x0fffffe5, 28 },
+ { 0x0fffffe6, 28 },
+ { 0x0fffffe7, 28 },
+ { 0x0fffffe8, 28 },
+ { 0x00ffffea, 24 },
+ { 0x3ffffffc, 30 },
+ { 0x0fffffe9, 28 },
+ { 0x0fffffea, 28 },
+ { 0x3ffffffd, 30 },
+ { 0x0fffffeb, 28 },
+ { 0x0fffffec, 28 },
+ { 0x0fffffed, 28 },
+ { 0x0fffffee, 28 },
+ { 0x0fffffef, 28 },
+ { 0x0ffffff0, 28 },
+ { 0x0ffffff1, 28 },
+ { 0x0ffffff2, 28 },
+ { 0x3ffffffe, 30 },
+ { 0x0ffffff3, 28 },
+ { 0x0ffffff4, 28 },
+ { 0x0ffffff5, 28 },
+ { 0x0ffffff6, 28 },
+ { 0x0ffffff7, 28 },
+ { 0x0ffffff8, 28 },
+ { 0x0ffffff9, 28 },
+ { 0x0ffffffa, 28 },
+ { 0x0ffffffb, 28 },
+ { 0x00000014, 6 },
+ { 0x000003f8, 10 },
+ { 0x000003f9, 10 },
+ { 0x00000ffa, 12 },
+ { 0x00001ff9, 13 },
+ { 0x00000015, 6 },
+ { 0x000000f8, 8 },
+ { 0x000007fa, 11 },
+ { 0x000003fa, 10 },
+ { 0x000003fb, 10 },
+ { 0x000000f9, 8 },
+ { 0x000007fb, 11 },
+ { 0x000000fa, 8 },
+ { 0x00000016, 6 },
+ { 0x00000017, 6 },
+ { 0x00000018, 6 },
+ { 0x00000000, 5 },
+ { 0x00000001, 5 },
+ { 0x00000002, 5 },
+ { 0x00000019, 6 },
+ { 0x0000001a, 6 },
+ { 0x0000001b, 6 },
+ { 0x0000001c, 6 },
+ { 0x0000001d, 6 },
+ { 0x0000001e, 6 },
+ { 0x0000001f, 6 },
+ { 0x0000005c, 7 },
+ { 0x000000fb, 8 },
+ { 0x00007ffc, 15 },
+ { 0x00000020, 6 },
+ { 0x00000ffb, 12 },
+ { 0x000003fc, 10 },
+ { 0x00001ffa, 13 },
+ { 0x00000021, 6 },
+ { 0x0000005d, 7 },
+ { 0x0000005e, 7 },
+ { 0x0000005f, 7 },
+ { 0x00000060, 7 },
+ { 0x00000061, 7 },
+ { 0x00000062, 7 },
+ { 0x00000063, 7 },
+ { 0x00000064, 7 },
+ { 0x00000065, 7 },
+ { 0x00000066, 7 },
+ { 0x00000067, 7 },
+ { 0x00000068, 7 },
+ { 0x00000069, 7 },
+ { 0x0000006a, 7 },
+ { 0x0000006b, 7 },
+ { 0x0000006c, 7 },
+ { 0x0000006d, 7 },
+ { 0x0000006e, 7 },
+ { 0x0000006f, 7 },
+ { 0x00000070, 7 },
+ { 0x00000071, 7 },
+ { 0x00000072, 7 },
+ { 0x000000fc, 8 },
+ { 0x00000073, 7 },
+ { 0x000000fd, 8 },
+ { 0x00001ffb, 13 },
+ { 0x0007fff0, 19 },
+ { 0x00001ffc, 13 },
+ { 0x00003ffc, 14 },
+ { 0x00000022, 6 },
+ { 0x00007ffd, 15 },
+ { 0x00000003, 5 },
+ { 0x00000023, 6 },
+ { 0x00000004, 5 },
+ { 0x00000024, 6 },
+ { 0x00000005, 5 },
+ { 0x00000025, 6 },
+ { 0x00000026, 6 },
+ { 0x00000027, 6 },
+ { 0x00000006, 5 },
+ { 0x00000074, 7 },
+ { 0x00000075, 7 },
+ { 0x00000028, 6 },
+ { 0x00000029, 6 },
+ { 0x0000002a, 6 },
+ { 0x00000007, 5 },
+ { 0x0000002b, 6 },
+ { 0x00000076, 7 },
+ { 0x0000002c, 6 },
+ { 0x00000008, 5 },
+ { 0x00000009, 5 },
+ { 0x0000002d, 6 },
+ { 0x00000077, 7 },
+ { 0x00000078, 7 },
+ { 0x00000079, 7 },
+ { 0x0000007a, 7 },
+ { 0x0000007b, 7 },
+ { 0x00007ffe, 15 },
+ { 0x000007fc, 11 },
+ { 0x00003ffd, 14 },
+ { 0x00001ffd, 13 },
+ { 0x0ffffffc, 28 },
+ { 0x000fffe6, 20 },
+ { 0x003fffd2, 22 },
+ { 0x000fffe7, 20 },
+ { 0x000fffe8, 20 },
+ { 0x003fffd3, 22 },
+ { 0x003fffd4, 22 },
+ { 0x003fffd5, 22 },
+ { 0x007fffd9, 23 },
+ { 0x003fffd6, 22 },
+ { 0x007fffda, 23 },
+ { 0x007fffdb, 23 },
+ { 0x007fffdc, 23 },
+ { 0x007fffdd, 23 },
+ { 0x007fffde, 23 },
+ { 0x00ffffeb, 24 },
+ { 0x007fffdf, 23 },
+ { 0x00ffffec, 24 },
+ { 0x00ffffed, 24 },
+ { 0x003fffd7, 22 },
+ { 0x007fffe0, 23 },
+ { 0x00ffffee, 24 },
+ { 0x007fffe1, 23 },
+ { 0x007fffe2, 23 },
+ { 0x007fffe3, 23 },
+ { 0x007fffe4, 23 },
+ { 0x001fffdc, 21 },
+ { 0x003fffd8, 22 },
+ { 0x007fffe5, 23 },
+ { 0x003fffd9, 22 },
+ { 0x007fffe6, 23 },
+ { 0x007fffe7, 23 },
+ { 0x00ffffef, 24 },
+ { 0x003fffda, 22 },
+ { 0x001fffdd, 21 },
+ { 0x000fffe9, 20 },
+ { 0x003fffdb, 22 },
+ { 0x003fffdc, 22 },
+ { 0x007fffe8, 23 },
+ { 0x007fffe9, 23 },
+ { 0x001fffde, 21 },
+ { 0x007fffea, 23 },
+ { 0x003fffdd, 22 },
+ { 0x003fffde, 22 },
+ { 0x00fffff0, 24 },
+ { 0x001fffdf, 21 },
+ { 0x003fffdf, 22 },
+ { 0x007fffeb, 23 },
+ { 0x007fffec, 23 },
+ { 0x001fffe0, 21 },
+ { 0x001fffe1, 21 },
+ { 0x003fffe0, 22 },
+ { 0x001fffe2, 21 },
+ { 0x007fffed, 23 },
+ { 0x003fffe1, 22 },
+ { 0x007fffee, 23 },
+ { 0x007fffef, 23 },
+ { 0x000fffea, 20 },
+ { 0x003fffe2, 22 },
+ { 0x003fffe3, 22 },
+ { 0x003fffe4, 22 },
+ { 0x007ffff0, 23 },
+ { 0x003fffe5, 22 },
+ { 0x003fffe6, 22 },
+ { 0x007ffff1, 23 },
+ { 0x03ffffe0, 26 },
+ { 0x03ffffe1, 26 },
+ { 0x000fffeb, 20 },
+ { 0x0007fff1, 19 },
+ { 0x003fffe7, 22 },
+ { 0x007ffff2, 23 },
+ { 0x003fffe8, 22 },
+ { 0x01ffffec, 25 },
+ { 0x03ffffe2, 26 },
+ { 0x03ffffe3, 26 },
+ { 0x03ffffe4, 26 },
+ { 0x07ffffde, 27 },
+ { 0x07ffffdf, 27 },
+ { 0x03ffffe5, 26 },
+ { 0x00fffff1, 24 },
+ { 0x01ffffed, 25 },
+ { 0x0007fff2, 19 },
+ { 0x001fffe3, 21 },
+ { 0x03ffffe6, 26 },
+ { 0x07ffffe0, 27 },
+ { 0x07ffffe1, 27 },
+ { 0x03ffffe7, 26 },
+ { 0x07ffffe2, 27 },
+ { 0x00fffff2, 24 },
+ { 0x001fffe4, 21 },
+ { 0x001fffe5, 21 },
+ { 0x03ffffe8, 26 },
+ { 0x03ffffe9, 26 },
+ { 0x0ffffffd, 28 },
+ { 0x07ffffe3, 27 },
+ { 0x07ffffe4, 27 },
+ { 0x07ffffe5, 27 },
+ { 0x000fffec, 20 },
+ { 0x00fffff3, 24 },
+ { 0x000fffed, 20 },
+ { 0x001fffe6, 21 },
+ { 0x003fffe9, 22 },
+ { 0x001fffe7, 21 },
+ { 0x001fffe8, 21 },
+ { 0x007ffff3, 23 },
+ { 0x003fffea, 22 },
+ { 0x003fffeb, 22 },
+ { 0x01ffffee, 25 },
+ { 0x01ffffef, 25 },
+ { 0x00fffff4, 24 },
+ { 0x00fffff5, 24 },
+ { 0x03ffffea, 26 },
+ { 0x007ffff4, 23 },
+ { 0x03ffffeb, 26 },
+ { 0x07ffffe6, 27 },
+ { 0x03ffffec, 26 },
+ { 0x03ffffed, 26 },
+ { 0x07ffffe7, 27 },
+ { 0x07ffffe8, 27 },
+ { 0x07ffffe9, 27 },
+ { 0x07ffffea, 27 },
+ { 0x07ffffeb, 27 },
+ { 0x0ffffffe, 28 },
+ { 0x07ffffec, 27 },
+ { 0x07ffffed, 27 },
+ { 0x07ffffee, 27 },
+ { 0x07ffffef, 27 },
+ { 0x07fffff0, 27 },
+ { 0x03ffffee, 26 },
+ { 0x3fffffff, 30 }
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__Http2HuffmanOutgoing_h
diff --git a/netwerk/protocol/http/Http2Push.cpp b/netwerk/protocol/http/Http2Push.cpp
new file mode 100644
index 000000000..b6fc485e2
--- /dev/null
+++ b/netwerk/protocol/http/Http2Push.cpp
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include <algorithm>
+
+#include "Http2Push.h"
+#include "nsHttpChannel.h"
+#include "nsIHttpPushListener.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+class CallChannelOnPush final : public Runnable {
+ public:
+ CallChannelOnPush(nsIHttpChannelInternal *associatedChannel,
+ const nsACString &pushedURI,
+ Http2PushedStream *pushStream)
+ : mAssociatedChannel(associatedChannel)
+ , mPushedURI(pushedURI)
+ , mPushedStream(pushStream)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<nsHttpChannel> channel;
+ CallQueryInterface(mAssociatedChannel, channel.StartAssignment());
+ MOZ_ASSERT(channel);
+ if (channel && NS_SUCCEEDED(channel->OnPush(mPushedURI, mPushedStream))) {
+ return NS_OK;
+ }
+
+ LOG3(("Http2PushedStream Orphan %p failed OnPush\n", this));
+ mPushedStream->OnPushFailed();
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIHttpChannelInternal> mAssociatedChannel;
+ const nsCString mPushedURI;
+ Http2PushedStream *mPushedStream;
+};
+
+//////////////////////////////////////////
+// Http2PushedStream
+//////////////////////////////////////////
+
+Http2PushedStream::Http2PushedStream(Http2PushTransactionBuffer *aTransaction,
+ Http2Session *aSession,
+ Http2Stream *aAssociatedStream,
+ uint32_t aID)
+ :Http2Stream(aTransaction, aSession, 0)
+ , mConsumerStream(nullptr)
+ , mAssociatedTransaction(aAssociatedStream->Transaction())
+ , mBufferedPush(aTransaction)
+ , mStatus(NS_OK)
+ , mPushCompleted(false)
+ , mDeferCleanupOnSuccess(true)
+ , mDeferCleanupOnPush(false)
+ , mOnPushFailed(false)
+{
+ LOG3(("Http2PushedStream ctor this=%p 0x%X\n", this, aID));
+ mStreamID = aID;
+ MOZ_ASSERT(!(aID & 1)); // must be even to be a pushed stream
+ mBufferedPush->SetPushStream(this);
+ mRequestContext = aAssociatedStream->RequestContext();
+ mLastRead = TimeStamp::Now();
+ SetPriority(aAssociatedStream->Priority() + 1);
+}
+
+bool
+Http2PushedStream::GetPushComplete()
+{
+ return mPushCompleted;
+}
+
+nsresult
+Http2PushedStream::WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count, uint32_t *countWritten)
+{
+ nsresult rv = Http2Stream::WriteSegments(writer, count, countWritten);
+ if (NS_SUCCEEDED(rv) && *countWritten) {
+ mLastRead = TimeStamp::Now();
+ }
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ mPushCompleted = true;
+ rv = NS_OK; // this is what a normal HTTP transaction would do
+ }
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv))
+ mStatus = rv;
+ return rv;
+}
+
+bool
+Http2PushedStream::DeferCleanup(nsresult status)
+{
+ LOG3(("Http2PushedStream::DeferCleanup Query %p %x\n", this, status));
+
+ if (NS_SUCCEEDED(status) && mDeferCleanupOnSuccess) {
+ LOG3(("Http2PushedStream::DeferCleanup %p %x defer on success\n", this, status));
+ return true;
+ }
+ if (mDeferCleanupOnPush) {
+ LOG3(("Http2PushedStream::DeferCleanup %p %x defer onPush ref\n", this, status));
+ return true;
+ }
+ if (mConsumerStream) {
+ LOG3(("Http2PushedStream::DeferCleanup %p %x defer active consumer\n", this, status));
+ return true;
+ }
+ LOG3(("Http2PushedStream::DeferCleanup Query %p %x not deferred\n", this, status));
+ return false;
+}
+
+// return true if channel implements nsIHttpPushListener
+bool
+Http2PushedStream::TryOnPush()
+{
+ nsHttpTransaction *trans = mAssociatedTransaction->QueryHttpTransaction();
+ if (!trans) {
+ return false;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> associatedChannel = do_QueryInterface(trans->HttpChannel());
+ if (!associatedChannel) {
+ return false;
+ }
+
+ if (!(trans->Caps() & NS_HTTP_ONPUSH_LISTENER)) {
+ return false;
+ }
+
+ mDeferCleanupOnPush = true;
+ nsCString uri = Origin() + Path();
+ NS_DispatchToMainThread(new CallChannelOnPush(associatedChannel, uri, this));
+ return true;
+}
+
+// side effect free static method to determine if Http2Stream implements nsIHttpPushListener
+bool
+Http2PushedStream::TestOnPush(Http2Stream *stream)
+{
+ if (!stream) {
+ return false;
+ }
+ nsAHttpTransaction *abstractTransaction = stream->Transaction();
+ if (!abstractTransaction) {
+ return false;
+ }
+ nsHttpTransaction *trans = abstractTransaction->QueryHttpTransaction();
+ if (!trans) {
+ return false;
+ }
+ nsCOMPtr<nsIHttpChannelInternal> associatedChannel = do_QueryInterface(trans->HttpChannel());
+ if (!associatedChannel) {
+ return false;
+ }
+ return (trans->Caps() & NS_HTTP_ONPUSH_LISTENER);
+}
+
+nsresult
+Http2PushedStream::ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t, uint32_t *count)
+{
+ nsresult rv = NS_OK;
+ *count = 0;
+
+ switch (mUpstreamState) {
+ case GENERATING_HEADERS:
+ // The request headers for this has been processed, so we need to verify
+ // that :authority, :scheme, and :path MUST be present. :method MUST NOT be
+ // present
+ CreatePushHashKey(mHeaderScheme, mHeaderHost,
+ mSession->Serial(), mHeaderPath,
+ mOrigin, mHashKey);
+
+ LOG3(("Http2PushStream 0x%X hash key %s\n", mStreamID, mHashKey.get()));
+
+ // the write side of a pushed transaction just involves manipulating a little state
+ SetSentFin(true);
+ Http2Stream::mRequestHeadersDone = 1;
+ Http2Stream::mOpenGenerated = 1;
+ Http2Stream::ChangeState(UPSTREAM_COMPLETE);
+ break;
+
+ case UPSTREAM_COMPLETE:
+ // Let's just clear the stream's transmit buffer by pushing it into
+ // the session. This is probably a window adjustment.
+ LOG3(("Http2Push::ReadSegments 0x%X \n", mStreamID));
+ mSegmentReader = reader;
+ rv = TransmitFrame(nullptr, nullptr, true);
+ mSegmentReader = nullptr;
+ break;
+
+ case GENERATING_BODY:
+ case SENDING_BODY:
+ case SENDING_FIN_STREAM:
+ default:
+ break;
+ }
+
+ return rv;
+}
+
+void
+Http2PushedStream::AdjustInitialWindow()
+{
+ LOG3(("Http2PushStream %p 0x%X AdjustInitialWindow", this, mStreamID));
+ if (mConsumerStream) {
+ LOG3(("Http2PushStream::AdjustInitialWindow %p 0x%X "
+ "calling super consumer %p 0x%X\n", this,
+ mStreamID, mConsumerStream, mConsumerStream->StreamID()));
+ Http2Stream::AdjustInitialWindow();
+ // Http2PushedStream::ReadSegments is needed to call TransmitFrame()
+ // and actually get this information into the session bytestream
+ mSession->TransactionHasDataToWrite(this);
+ }
+ // Otherwise, when we get hooked up, the initial window will get bumped
+ // anyway, so we're good to go.
+}
+
+void
+Http2PushedStream::SetConsumerStream(Http2Stream *consumer)
+{
+ mConsumerStream = consumer;
+ mDeferCleanupOnPush = false;
+}
+
+bool
+Http2PushedStream::GetHashKey(nsCString &key)
+{
+ if (mHashKey.IsEmpty())
+ return false;
+
+ key = mHashKey;
+ return true;
+}
+
+void
+Http2PushedStream::ConnectPushedStream(Http2Stream *stream)
+{
+ mSession->ConnectPushedStream(stream);
+}
+
+bool
+Http2PushedStream::IsOrphaned(TimeStamp now)
+{
+ MOZ_ASSERT(!now.IsNull());
+
+ // if session is not transmitting, and is also not connected to a consumer
+ // stream, and its been like that for too long then it is oprhaned
+
+ if (mConsumerStream || mDeferCleanupOnPush) {
+ return false;
+ }
+
+ if (mOnPushFailed) {
+ return true;
+ }
+
+ bool rv = ((now - mLastRead).ToSeconds() > 30.0);
+ if (rv) {
+ LOG3(("Http2PushedStream:IsOrphaned 0x%X IsOrphaned %3.2f\n",
+ mStreamID, (now - mLastRead).ToSeconds()));
+ }
+ return rv;
+}
+
+nsresult
+Http2PushedStream::GetBufferedData(char *buf,
+ uint32_t count, uint32_t *countWritten)
+{
+ if (NS_FAILED(mStatus))
+ return mStatus;
+
+ nsresult rv = mBufferedPush->GetBufferedData(buf, count, countWritten);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!*countWritten)
+ rv = GetPushComplete() ? NS_BASE_STREAM_CLOSED : NS_BASE_STREAM_WOULD_BLOCK;
+
+ return rv;
+}
+
+//////////////////////////////////////////
+// Http2PushTransactionBuffer
+// This is the nsAHttpTransction owned by the stream when the pushed
+// stream has not yet been matched with a pull request
+//////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS0(Http2PushTransactionBuffer)
+
+Http2PushTransactionBuffer::Http2PushTransactionBuffer()
+ : mStatus(NS_OK)
+ , mRequestHead(nullptr)
+ , mPushStream(nullptr)
+ , mIsDone(false)
+ , mBufferedHTTP1Size(kDefaultBufferSize)
+ , mBufferedHTTP1Used(0)
+ , mBufferedHTTP1Consumed(0)
+{
+ mBufferedHTTP1 = MakeUnique<char[]>(mBufferedHTTP1Size);
+}
+
+Http2PushTransactionBuffer::~Http2PushTransactionBuffer()
+{
+ delete mRequestHead;
+}
+
+void
+Http2PushTransactionBuffer::SetConnection(nsAHttpConnection *conn)
+{
+}
+
+nsAHttpConnection *
+Http2PushTransactionBuffer::Connection()
+{
+ return nullptr;
+}
+
+void
+Http2PushTransactionBuffer::GetSecurityCallbacks(nsIInterfaceRequestor **outCB)
+{
+ *outCB = nullptr;
+}
+
+void
+Http2PushTransactionBuffer::OnTransportStatus(nsITransport* transport,
+ nsresult status, int64_t progress)
+{
+}
+
+nsHttpConnectionInfo *
+Http2PushTransactionBuffer::ConnectionInfo()
+{
+ if (!mPushStream) {
+ return nullptr;
+ }
+ if (!mPushStream->Transaction()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(mPushStream->Transaction() != this);
+ return mPushStream->Transaction()->ConnectionInfo();
+}
+
+bool
+Http2PushTransactionBuffer::IsDone()
+{
+ return mIsDone;
+}
+
+nsresult
+Http2PushTransactionBuffer::Status()
+{
+ return mStatus;
+}
+
+uint32_t
+Http2PushTransactionBuffer::Caps()
+{
+ return 0;
+}
+
+void
+Http2PushTransactionBuffer::SetDNSWasRefreshed()
+{
+}
+
+uint64_t
+Http2PushTransactionBuffer::Available()
+{
+ return mBufferedHTTP1Used - mBufferedHTTP1Consumed;
+}
+
+nsresult
+Http2PushTransactionBuffer::ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead)
+{
+ *countRead = 0;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+Http2PushTransactionBuffer::WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count, uint32_t *countWritten)
+{
+ if ((mBufferedHTTP1Size - mBufferedHTTP1Used) < 20480) {
+ EnsureBuffer(mBufferedHTTP1,mBufferedHTTP1Size + kDefaultBufferSize,
+ mBufferedHTTP1Used, mBufferedHTTP1Size);
+ }
+
+ count = std::min(count, mBufferedHTTP1Size - mBufferedHTTP1Used);
+ nsresult rv = writer->OnWriteSegment(&mBufferedHTTP1[mBufferedHTTP1Used],
+ count, countWritten);
+ if (NS_SUCCEEDED(rv)) {
+ mBufferedHTTP1Used += *countWritten;
+ }
+ else if (rv == NS_BASE_STREAM_CLOSED) {
+ mIsDone = true;
+ }
+
+ if (Available() || mIsDone) {
+ Http2Stream *consumer = mPushStream->GetConsumerStream();
+
+ if (consumer) {
+ LOG3(("Http2PushTransactionBuffer::WriteSegments notifying connection "
+ "consumer data available 0x%X [%u] done=%d\n",
+ mPushStream->StreamID(), Available(), mIsDone));
+ mPushStream->ConnectPushedStream(consumer);
+ }
+ }
+
+ return rv;
+}
+
+uint32_t
+Http2PushTransactionBuffer::Http1xTransactionCount()
+{
+ return 0;
+}
+
+nsHttpRequestHead *
+Http2PushTransactionBuffer::RequestHead()
+{
+ if (!mRequestHead)
+ mRequestHead = new nsHttpRequestHead();
+ return mRequestHead;
+}
+
+nsresult
+Http2PushTransactionBuffer::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void
+Http2PushTransactionBuffer::SetProxyConnectFailed()
+{
+}
+
+void
+Http2PushTransactionBuffer::Close(nsresult reason)
+{
+ mStatus = reason;
+ mIsDone = true;
+}
+
+nsresult
+Http2PushTransactionBuffer::AddTransaction(nsAHttpTransaction *trans)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+Http2PushTransactionBuffer::PipelineDepth()
+{
+ return 0;
+}
+
+nsresult
+Http2PushTransactionBuffer::SetPipelinePosition(int32_t position)
+{
+ return NS_OK;
+}
+
+int32_t
+Http2PushTransactionBuffer::PipelinePosition()
+{
+ return 1;
+}
+
+nsresult
+Http2PushTransactionBuffer::GetBufferedData(char *buf,
+ uint32_t count,
+ uint32_t *countWritten)
+{
+ *countWritten = std::min(count, static_cast<uint32_t>(Available()));
+ if (*countWritten) {
+ memcpy(buf, &mBufferedHTTP1[mBufferedHTTP1Consumed], *countWritten);
+ mBufferedHTTP1Consumed += *countWritten;
+ }
+
+ // If all the data has been consumed then reset the buffer
+ if (mBufferedHTTP1Consumed == mBufferedHTTP1Used) {
+ mBufferedHTTP1Consumed = 0;
+ mBufferedHTTP1Used = 0;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Push.h b/netwerk/protocol/http/Http2Push.h
new file mode 100644
index 000000000..fd39eb2c7
--- /dev/null
+++ b/netwerk/protocol/http/Http2Push.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http2Push_Internal_h
+#define mozilla_net_Http2Push_Internal_h
+
+// HTTP/2 - RFC 7540
+// https://www.rfc-editor.org/rfc/rfc7540.txt
+
+#include "Http2Session.h"
+#include "Http2Stream.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "nsHttpRequestHead.h"
+#include "nsILoadGroup.h"
+#include "nsIRequestContext.h"
+#include "nsString.h"
+#include "PSpdyPush.h"
+
+namespace mozilla {
+namespace net {
+
+class Http2PushTransactionBuffer;
+
+class Http2PushedStream final : public Http2Stream
+{
+public:
+ Http2PushedStream(Http2PushTransactionBuffer *aTransaction,
+ Http2Session *aSession,
+ Http2Stream *aAssociatedStream,
+ uint32_t aID);
+ virtual ~Http2PushedStream() {}
+
+ bool GetPushComplete();
+
+ // The consumer stream is the synthetic pull stream hooked up to this push
+ virtual Http2Stream *GetConsumerStream() override { return mConsumerStream; };
+
+ void SetConsumerStream(Http2Stream *aStream);
+ bool GetHashKey(nsCString &key);
+
+ // override of Http2Stream
+ nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *) override;
+ nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *) override;
+ void AdjustInitialWindow() override;
+
+ nsIRequestContext *RequestContext() override { return mRequestContext; };
+ void ConnectPushedStream(Http2Stream *consumer);
+
+ bool TryOnPush();
+ static bool TestOnPush(Http2Stream *consumer);
+
+ virtual bool DeferCleanup(nsresult status) override;
+ void SetDeferCleanupOnSuccess(bool val) { mDeferCleanupOnSuccess = val; }
+
+ bool IsOrphaned(TimeStamp now);
+ void OnPushFailed() { mDeferCleanupOnPush = false; mOnPushFailed = true; }
+
+ nsresult GetBufferedData(char *buf, uint32_t count, uint32_t *countWritten);
+
+ // overload of Http2Stream
+ virtual bool HasSink() override { return !!mConsumerStream; }
+
+ nsCString &GetRequestString() { return mRequestString; }
+
+private:
+
+ Http2Stream *mConsumerStream; // paired request stream that consumes from
+ // real http/2 one.. null until a match is made.
+
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+
+ nsAHttpTransaction *mAssociatedTransaction;
+
+ Http2PushTransactionBuffer *mBufferedPush;
+ mozilla::TimeStamp mLastRead;
+
+ nsCString mHashKey;
+ nsresult mStatus;
+ bool mPushCompleted; // server push FIN received
+ bool mDeferCleanupOnSuccess;
+
+ // mDeferCleanupOnPush prevents Http2Session::CleanupStream() from
+ // destroying the push stream on an error code during the period between
+ // when we need to do OnPush() on another thread and the time it takes
+ // for that event to create a synthetic pull stream attached to this
+ // object. That synthetic pull will become mConsuemerStream.
+ // Ths is essentially a delete protecting reference.
+ bool mDeferCleanupOnPush;
+ bool mOnPushFailed;
+ nsCString mRequestString;
+
+};
+
+class Http2PushTransactionBuffer final : public nsAHttpTransaction
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+
+ Http2PushTransactionBuffer();
+
+ nsresult GetBufferedData(char *buf, uint32_t count, uint32_t *countWritten);
+ void SetPushStream(Http2PushedStream *stream) { mPushStream = stream; }
+
+private:
+ virtual ~Http2PushTransactionBuffer();
+
+ const static uint32_t kDefaultBufferSize = 4096;
+
+ nsresult mStatus;
+ nsHttpRequestHead *mRequestHead;
+ Http2PushedStream *mPushStream;
+ bool mIsDone;
+
+ UniquePtr<char[]> mBufferedHTTP1;
+ uint32_t mBufferedHTTP1Size;
+ uint32_t mBufferedHTTP1Used;
+ uint32_t mBufferedHTTP1Consumed;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http2Push_Internal_h
diff --git a/netwerk/protocol/http/Http2Session.cpp b/netwerk/protocol/http/Http2Session.cpp
new file mode 100644
index 000000000..a2721017d
--- /dev/null
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -0,0 +1,3884 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include <algorithm>
+
+#include "Http2Session.h"
+#include "Http2Stream.h"
+#include "Http2Push.h"
+
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Preferences.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpConnection.h"
+#include "nsIRequestContext.h"
+#include "nsISSLSocketControl.h"
+#include "nsISSLStatus.h"
+#include "nsISSLStatusProvider.h"
+#include "nsISupportsPriority.h"
+#include "nsStandardURL.h"
+#include "nsURLHelper.h"
+#include "prnetdb.h"
+#include "sslt.h"
+#include "mozilla/Sprintf.h"
+#include "nsSocketTransportService2.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+// Http2Session has multiple inheritance of things that implement
+// nsISupports, so this magic is taken from nsHttpPipeline that
+// implements some of the same abstract classes.
+NS_IMPL_ADDREF(Http2Session)
+NS_IMPL_RELEASE(Http2Session)
+NS_INTERFACE_MAP_BEGIN(Http2Session)
+NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
+NS_INTERFACE_MAP_END
+
+// "magic" refers to the string that preceeds HTTP/2 on the wire
+// to help find any intermediaries speaking an older version of HTTP
+const uint8_t Http2Session::kMagicHello[] = {
+ 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
+ 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
+ 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
+};
+
+#define RETURN_SESSION_ERROR(o,x) \
+do { \
+ (o)->mGoAwayReason = (x); \
+ return NS_ERROR_ILLEGAL_VALUE; \
+ } while (0)
+
+Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t version)
+ : mSocketTransport(aSocketTransport)
+ , mSegmentReader(nullptr)
+ , mSegmentWriter(nullptr)
+ , mNextStreamID(3) // 1 is reserved for Updgrade handshakes
+ , mLastPushedID(0)
+ , mConcurrentHighWater(0)
+ , mDownstreamState(BUFFERING_OPENING_SETTINGS)
+ , mInputFrameBufferSize(kDefaultBufferSize)
+ , mInputFrameBufferUsed(0)
+ , mInputFrameDataSize(0)
+ , mInputFrameDataRead(0)
+ , mInputFrameFinal(false)
+ , mInputFrameType(0)
+ , mInputFrameFlags(0)
+ , mInputFrameID(0)
+ , mPaddingLength(0)
+ , mInputFrameDataStream(nullptr)
+ , mNeedsCleanup(nullptr)
+ , mDownstreamRstReason(NO_HTTP_ERROR)
+ , mExpectedHeaderID(0)
+ , mExpectedPushPromiseID(0)
+ , mContinuedPromiseStream(0)
+ , mFlatHTTPResponseHeadersOut(0)
+ , mShouldGoAway(false)
+ , mClosed(false)
+ , mCleanShutdown(false)
+ , mTLSProfileConfirmed(false)
+ , mGoAwayReason(NO_HTTP_ERROR)
+ , mClientGoAwayReason(UNASSIGNED)
+ , mPeerGoAwayReason(UNASSIGNED)
+ , mGoAwayID(0)
+ , mOutgoingGoAwayID(0)
+ , mConcurrent(0)
+ , mServerPushedResources(0)
+ , mServerInitialStreamWindow(kDefaultRwin)
+ , mLocalSessionWindow(kDefaultRwin)
+ , mServerSessionWindow(kDefaultRwin)
+ , mInitialRwin(ASpdySession::kInitialRwin)
+ , mOutputQueueSize(kDefaultQueueSize)
+ , mOutputQueueUsed(0)
+ , mOutputQueueSent(0)
+ , mLastReadEpoch(PR_IntervalNow())
+ , mPingSentEpoch(0)
+ , mPreviousUsed(false)
+ , mWaitingForSettingsAck(false)
+ , mGoAwayOnPush(false)
+ , mUseH2Deps(false)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ static uint64_t sSerial;
+ mSerial = ++sSerial;
+
+ LOG3(("Http2Session::Http2Session %p serial=0x%X\n", this, mSerial));
+
+ mInputFrameBuffer = MakeUnique<char[]>(mInputFrameBufferSize);
+ mOutputQueueBuffer = MakeUnique<char[]>(mOutputQueueSize);
+ mDecompressBuffer.SetCapacity(kDefaultBufferSize);
+
+ mPushAllowance = gHttpHandler->SpdyPushAllowance();
+ mInitialRwin = std::max(gHttpHandler->SpdyPullAllowance(), mPushAllowance);
+ mMaxConcurrent = gHttpHandler->DefaultSpdyConcurrent();
+ mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
+ SendHello();
+
+ mLastDataReadEpoch = mLastReadEpoch;
+
+ mPingThreshold = gHttpHandler->SpdyPingThreshold();
+ mPreviousPingThreshold = mPingThreshold;
+}
+
+void
+Http2Session::Shutdown()
+{
+ for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<Http2Stream> &stream = iter.Data();
+
+ // On a clean server hangup the server sets the GoAwayID to be the ID of
+ // the last transaction it processed. If the ID of stream in the
+ // local stream is greater than that it can safely be restarted because the
+ // server guarantees it was not partially processed. Streams that have not
+ // registered an ID haven't actually been sent yet so they can always be
+ // restarted.
+ if (mCleanShutdown &&
+ (stream->StreamID() > mGoAwayID || !stream->HasRegisteredID())) {
+ CloseStream(stream, NS_ERROR_NET_RESET); // can be restarted
+ } else if (stream->RecvdData()) {
+ CloseStream(stream, NS_ERROR_NET_PARTIAL_TRANSFER);
+ } else if (mGoAwayReason == INADEQUATE_SECURITY) {
+ CloseStream(stream, NS_ERROR_NET_INADEQUATE_SECURITY);
+ } else {
+ CloseStream(stream, NS_ERROR_ABORT);
+ }
+ }
+}
+
+Http2Session::~Http2Session()
+{
+ LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X",
+ this, mDownstreamState));
+
+ Shutdown();
+
+ Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
+ Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN, (mNextStreamID - 1) / 2);
+ Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
+ mServerPushedResources);
+ Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_LOCAL, mClientGoAwayReason);
+ Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_PEER, mPeerGoAwayReason);
+}
+
+void
+Http2Session::LogIO(Http2Session *self, Http2Stream *stream,
+ const char *label,
+ const char *data, uint32_t datalen)
+{
+ if (!LOG5_ENABLED())
+ return;
+
+ LOG5(("Http2Session::LogIO %p stream=%p id=0x%X [%s]",
+ self, stream, stream ? stream->StreamID() : 0, label));
+
+ // Max line is (16 * 3) + 10(prefix) + newline + null
+ char linebuf[128];
+ uint32_t index;
+ char *line = linebuf;
+
+ linebuf[127] = 0;
+
+ for (index = 0; index < datalen; ++index) {
+ if (!(index % 16)) {
+ if (index) {
+ *line = 0;
+ LOG5(("%s", linebuf));
+ }
+ line = linebuf;
+ snprintf(line, 128, "%08X: ", index);
+ line += 10;
+ }
+ snprintf(line, 128 - (line - linebuf), "%02X ", (reinterpret_cast<const uint8_t *>(data))[index]);
+ line += 3;
+ }
+ if (index) {
+ *line = 0;
+ LOG5(("%s", linebuf));
+ }
+}
+
+typedef nsresult (*Http2ControlFx) (Http2Session *self);
+static Http2ControlFx sControlFunctions[] = {
+ nullptr, // type 0 data is not a control function
+ Http2Session::RecvHeaders,
+ Http2Session::RecvPriority,
+ Http2Session::RecvRstStream,
+ Http2Session::RecvSettings,
+ Http2Session::RecvPushPromise,
+ Http2Session::RecvPing,
+ Http2Session::RecvGoAway,
+ Http2Session::RecvWindowUpdate,
+ Http2Session::RecvContinuation,
+ Http2Session::RecvAltSvc // extension for type 0x0A
+};
+
+bool
+Http2Session::RoomForMoreConcurrent()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ return (mConcurrent < mMaxConcurrent);
+}
+
+bool
+Http2Session::RoomForMoreStreams()
+{
+ if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID)
+ return false;
+
+ return !mShouldGoAway;
+}
+
+PRIntervalTime
+Http2Session::IdleTime()
+{
+ return PR_IntervalNow() - mLastDataReadEpoch;
+}
+
+uint32_t
+Http2Session::ReadTimeoutTick(PRIntervalTime now)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n",
+ this, PR_IntervalToSeconds(now - mLastReadEpoch)));
+
+ if (!mPingThreshold)
+ return UINT32_MAX;
+
+ if ((now - mLastReadEpoch) < mPingThreshold) {
+ // recent activity means ping is not an issue
+ if (mPingSentEpoch) {
+ mPingSentEpoch = 0;
+ if (mPreviousUsed) {
+ // restore the former value
+ mPingThreshold = mPreviousPingThreshold;
+ mPreviousUsed = false;
+ }
+ }
+
+ return PR_IntervalToSeconds(mPingThreshold) -
+ PR_IntervalToSeconds(now - mLastReadEpoch);
+ }
+
+ if (mPingSentEpoch) {
+ LOG3(("Http2Session::ReadTimeoutTick %p handle outstanding ping\n"));
+ if ((now - mPingSentEpoch) >= gHttpHandler->SpdyPingTimeout()) {
+ LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n", this));
+ mPingSentEpoch = 0;
+ Close(NS_ERROR_NET_TIMEOUT);
+ return UINT32_MAX;
+ }
+ return 1; // run the tick aggressively while ping is outstanding
+ }
+
+ LOG3(("Http2Session::ReadTimeoutTick %p generating ping\n", this));
+
+ mPingSentEpoch = PR_IntervalNow();
+ if (!mPingSentEpoch) {
+ mPingSentEpoch = 1; // avoid the 0 sentinel value
+ }
+ GeneratePing(false);
+ ResumeRecv(); // read the ping reply
+
+ // Check for orphaned push streams. This looks expensive, but generally the
+ // list is empty.
+ Http2PushedStream *deleteMe;
+ TimeStamp timestampNow;
+ do {
+ deleteMe = nullptr;
+
+ for (uint32_t index = mPushedStreams.Length();
+ index > 0 ; --index) {
+ Http2PushedStream *pushedStream = mPushedStreams[index - 1];
+
+ if (timestampNow.IsNull())
+ timestampNow = TimeStamp::Now(); // lazy initializer
+
+ // if stream finished, but is not connected, and its been like that for
+ // long then cleanup the stream.
+ if (pushedStream->IsOrphaned(timestampNow))
+ {
+ LOG3(("Http2Session Timeout Pushed Stream %p 0x%X\n",
+ this, pushedStream->StreamID()));
+ deleteMe = pushedStream;
+ break; // don't CleanupStream() while iterating this vector
+ }
+ }
+ if (deleteMe)
+ CleanupStream(deleteMe, NS_ERROR_ABORT, CANCEL_ERROR);
+
+ } while (deleteMe);
+
+ return 1; // run the tick aggressively while ping is outstanding
+}
+
+uint32_t
+Http2Session::RegisterStreamID(Http2Stream *stream, uint32_t aNewID)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mNextStreamID < 0xfffffff0,
+ "should have stopped admitting streams");
+ MOZ_ASSERT(!(aNewID & 1),
+ "0 for autoassign pull, otherwise explicit even push assignment");
+
+ if (!aNewID) {
+ // auto generate a new pull stream ID
+ aNewID = mNextStreamID;
+ MOZ_ASSERT(aNewID & 1, "pull ID must be odd.");
+ mNextStreamID += 2;
+ }
+
+ LOG3(("Http2Session::RegisterStreamID session=%p stream=%p id=0x%X "
+ "concurrent=%d",this, stream, aNewID, mConcurrent));
+
+ // We've used up plenty of ID's on this session. Start
+ // moving to a new one before there is a crunch involving
+ // server push streams or concurrent non-registered submits
+ if (aNewID >= kMaxStreamID)
+ mShouldGoAway = true;
+
+ // integrity check
+ if (mStreamIDHash.Get(aNewID)) {
+ LOG3((" New ID already present\n"));
+ MOZ_ASSERT(false, "New ID already present in mStreamIDHash");
+ mShouldGoAway = true;
+ return kDeadStreamID;
+ }
+
+ mStreamIDHash.Put(aNewID, stream);
+ return aNewID;
+}
+
+bool
+Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction,
+ int32_t aPriority,
+ bool aUseTunnel,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // integrity check
+ if (mStreamTransactionHash.Get(aHttpTransaction)) {
+ LOG3((" New transaction already present\n"));
+ MOZ_ASSERT(false, "AddStream duplicate transaction pointer");
+ return false;
+ }
+
+ if (!mConnection) {
+ mConnection = aHttpTransaction->Connection();
+ }
+
+ if (mClosed || mShouldGoAway) {
+ nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
+ if (trans && !trans->GetPushedStream()) {
+ LOG3(("Http2Session::AddStream %p atrans=%p trans=%p session unusable - resched.\n",
+ this, aHttpTransaction, trans));
+ aHttpTransaction->SetConnection(nullptr);
+ gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ return true;
+ }
+ }
+
+ aHttpTransaction->SetConnection(this);
+
+ if (aUseTunnel) {
+ LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel",
+ this, aHttpTransaction));
+ DispatchOnTunnel(aHttpTransaction, aCallbacks);
+ return true;
+ }
+
+ Http2Stream *stream = new Http2Stream(aHttpTransaction, this, aPriority);
+
+ LOG3(("Http2Session::AddStream session=%p stream=%p serial=%u "
+ "NextID=0x%X (tentative)", this, stream, mSerial, mNextStreamID));
+
+ mStreamTransactionHash.Put(aHttpTransaction, stream);
+
+ mReadyForWrite.Push(stream);
+ SetWriteCallbacks();
+
+ // Kick off the SYN transmit without waiting for the poll loop
+ // This won't work for the first stream because there is no segment reader
+ // yet.
+ if (mSegmentReader) {
+ uint32_t countRead;
+ ReadSegments(nullptr, kDefaultBufferSize, &countRead);
+ }
+
+ if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) &&
+ !aHttpTransaction->IsNullTransaction()) {
+ LOG3(("Http2Session::AddStream %p transaction %p forces keep-alive off.\n",
+ this, aHttpTransaction));
+ DontReuse();
+ }
+
+ return true;
+}
+
+void
+Http2Session::QueueStream(Http2Stream *stream)
+{
+ // will be removed via processpending or a shutdown path
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(!stream->CountAsActive());
+ MOZ_ASSERT(!stream->Queued());
+
+ LOG3(("Http2Session::QueueStream %p stream %p queued.", this, stream));
+
+#ifdef DEBUG
+ int32_t qsize = mQueuedStreams.GetSize();
+ for (int32_t i = 0; i < qsize; i++) {
+ Http2Stream *qStream = static_cast<Http2Stream *>(mQueuedStreams.ObjectAt(i));
+ MOZ_ASSERT(qStream != stream);
+ MOZ_ASSERT(qStream->Queued());
+ }
+#endif
+
+ stream->SetQueued(true);
+ mQueuedStreams.Push(stream);
+}
+
+void
+Http2Session::ProcessPending()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ Http2Stream*stream;
+ while (RoomForMoreConcurrent() &&
+ (stream = static_cast<Http2Stream *>(mQueuedStreams.PopFront()))) {
+
+ LOG3(("Http2Session::ProcessPending %p stream %p woken from queue.",
+ this, stream));
+ MOZ_ASSERT(!stream->CountAsActive());
+ MOZ_ASSERT(stream->Queued());
+ stream->SetQueued(false);
+ mReadyForWrite.Push(stream);
+ SetWriteCallbacks();
+ }
+}
+
+nsresult
+Http2Session::NetworkRead(nsAHttpSegmentWriter *writer, char *buf,
+ uint32_t count, uint32_t *countWritten)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (!count) {
+ *countWritten = 0;
+ return NS_OK;
+ }
+
+ nsresult rv = writer->OnWriteSegment(buf, count, countWritten);
+ if (NS_SUCCEEDED(rv) && *countWritten > 0)
+ mLastReadEpoch = PR_IntervalNow();
+ return rv;
+}
+
+void
+Http2Session::SetWriteCallbacks()
+{
+ if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed))
+ mConnection->ResumeSend();
+}
+
+void
+Http2Session::RealignOutputQueue()
+{
+ mOutputQueueUsed -= mOutputQueueSent;
+ memmove(mOutputQueueBuffer.get(),
+ mOutputQueueBuffer.get() + mOutputQueueSent,
+ mOutputQueueUsed);
+ mOutputQueueSent = 0;
+}
+
+void
+Http2Session::FlushOutputQueue()
+{
+ if (!mSegmentReader || !mOutputQueueUsed)
+ return;
+
+ nsresult rv;
+ uint32_t countRead;
+ uint32_t avail = mOutputQueueUsed - mOutputQueueSent;
+
+ rv = mSegmentReader->
+ OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail,
+ &countRead);
+ LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%x actual=%d",
+ this, avail, rv, countRead));
+
+ // Dont worry about errors on write, we will pick this up as a read error too
+ if (NS_FAILED(rv))
+ return;
+
+ if (countRead == avail) {
+ mOutputQueueUsed = 0;
+ mOutputQueueSent = 0;
+ return;
+ }
+
+ mOutputQueueSent += countRead;
+
+ // If the output queue is close to filling up and we have sent out a good
+ // chunk of data from the beginning then realign it.
+
+ if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
+ ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
+ RealignOutputQueue();
+ }
+}
+
+void
+Http2Session::DontReuse()
+{
+ LOG3(("Http2Session::DontReuse %p\n", this));
+ mShouldGoAway = true;
+ if (!mStreamTransactionHash.Count())
+ Close(NS_OK);
+}
+
+uint32_t
+Http2Session::GetWriteQueueSize()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ return mReadyForWrite.GetSize();
+}
+
+void
+Http2Session::ChangeDownstreamState(enum internalStateType newState)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ LOG3(("Http2Session::ChangeDownstreamState() %p from %X to %X",
+ this, mDownstreamState, newState));
+ mDownstreamState = newState;
+}
+
+void
+Http2Session::ResetDownstreamState()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ LOG3(("Http2Session::ResetDownstreamState() %p", this));
+ ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+
+ if (mInputFrameFinal && mInputFrameDataStream) {
+ mInputFrameFinal = false;
+ LOG3((" SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID()));
+ mInputFrameDataStream->SetRecvdFin(true);
+ MaybeDecrementConcurrent(mInputFrameDataStream);
+ }
+ mInputFrameFinal = false;
+ mInputFrameBufferUsed = 0;
+ mInputFrameDataStream = nullptr;
+}
+
+// return true if activated (and counted against max)
+// otherwise return false and queue
+bool
+Http2Session::TryToActivate(Http2Stream *aStream)
+{
+ if (aStream->Queued()) {
+ LOG3(("Http2Session::TryToActivate %p stream=%p already queued.\n", this, aStream));
+ return false;
+ }
+
+ if (!RoomForMoreConcurrent()) {
+ LOG3(("Http2Session::TryToActivate %p stream=%p no room for more concurrent "
+ "streams %d\n", this, aStream));
+ QueueStream(aStream);
+ return false;
+ }
+
+ LOG3(("Http2Session::TryToActivate %p stream=%p\n", this, aStream));
+ IncrementConcurrent(aStream);
+ return true;
+}
+
+void
+Http2Session::IncrementConcurrent(Http2Stream *stream)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1),
+ "Do not activate pushed streams");
+
+ nsAHttpTransaction *trans = stream->Transaction();
+ if (!trans || !trans->IsNullTransaction() || trans->QuerySpdyConnectTransaction()) {
+
+ MOZ_ASSERT(!stream->CountAsActive());
+ stream->SetCountAsActive(true);
+ ++mConcurrent;
+
+ if (mConcurrent > mConcurrentHighWater) {
+ mConcurrentHighWater = mConcurrent;
+ }
+ LOG3(("Http2Session::IncrementCounter %p counting stream %p Currently %d "
+ "streams in session, high water mark is %d\n",
+ this, stream, mConcurrent, mConcurrentHighWater));
+ }
+}
+
+// call with data length (i.e. 0 for 0 data bytes - ignore 9 byte header)
+// dest must have 9 bytes of allocated space
+template<typename charType> void
+Http2Session::CreateFrameHeader(charType dest, uint16_t frameLength,
+ uint8_t frameType, uint8_t frameFlags,
+ uint32_t streamID)
+{
+ MOZ_ASSERT(frameLength <= kMaxFrameData, "framelength too large");
+ MOZ_ASSERT(!(streamID & 0x80000000));
+ MOZ_ASSERT(!frameFlags ||
+ (frameType != FRAME_TYPE_PRIORITY &&
+ frameType != FRAME_TYPE_RST_STREAM &&
+ frameType != FRAME_TYPE_GOAWAY &&
+ frameType != FRAME_TYPE_WINDOW_UPDATE));
+
+ dest[0] = 0x00;
+ NetworkEndian::writeUint16(dest + 1, frameLength);
+ dest[3] = frameType;
+ dest[4] = frameFlags;
+ NetworkEndian::writeUint32(dest + 5, streamID);
+}
+
+char *
+Http2Session::EnsureOutputBuffer(uint32_t spaceNeeded)
+{
+ // this is an infallible allocation (if an allocation is
+ // needed, which is probably isn't)
+ EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + spaceNeeded,
+ mOutputQueueUsed, mOutputQueueSize);
+ return mOutputQueueBuffer.get() + mOutputQueueUsed;
+}
+
+template void
+Http2Session::CreateFrameHeader(char *dest, uint16_t frameLength,
+ uint8_t frameType, uint8_t frameFlags,
+ uint32_t streamID);
+
+template void
+Http2Session::CreateFrameHeader(uint8_t *dest, uint16_t frameLength,
+ uint8_t frameType, uint8_t frameFlags,
+ uint32_t streamID);
+
+void
+Http2Session::MaybeDecrementConcurrent(Http2Stream *aStream)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("MaybeDecrementConcurrent %p id=0x%X concurrent=%d active=%d\n",
+ this, aStream->StreamID(), mConcurrent, aStream->CountAsActive()));
+
+ if (!aStream->CountAsActive())
+ return;
+
+ MOZ_ASSERT(mConcurrent);
+ aStream->SetCountAsActive(false);
+ --mConcurrent;
+ ProcessPending();
+}
+
+// Need to decompress some data in order to keep the compression
+// context correct, but we really don't care what the result is
+nsresult
+Http2Session::UncompressAndDiscard(bool isPush)
+{
+ nsresult rv;
+ nsAutoCString trash;
+
+ rv = mDecompressor.DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(mDecompressBuffer.BeginReading()),
+ mDecompressBuffer.Length(), trash, isPush);
+ mDecompressBuffer.Truncate();
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::UncompressAndDiscard %p Compression Error\n",
+ this));
+ mGoAwayReason = COMPRESSION_ERROR;
+ return rv;
+ }
+ return NS_OK;
+}
+
+void
+Http2Session::GeneratePing(bool isAck)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::GeneratePing %p isAck=%d\n", this, isAck));
+
+ char *packet = EnsureOutputBuffer(kFrameHeaderBytes + 8);
+ mOutputQueueUsed += kFrameHeaderBytes + 8;
+
+ if (isAck) {
+ CreateFrameHeader(packet, 8, FRAME_TYPE_PING, kFlag_ACK, 0);
+ memcpy(packet + kFrameHeaderBytes,
+ mInputFrameBuffer.get() + kFrameHeaderBytes, 8);
+ } else {
+ CreateFrameHeader(packet, 8, FRAME_TYPE_PING, 0, 0);
+ memset(packet + kFrameHeaderBytes, 0, 8);
+ }
+
+ LogIO(this, nullptr, "Generate Ping", packet, kFrameHeaderBytes + 8);
+ FlushOutputQueue();
+}
+
+void
+Http2Session::GenerateSettingsAck()
+{
+ // need to generate ack of this settings frame
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::GenerateSettingsAck %p\n", this));
+
+ char *packet = EnsureOutputBuffer(kFrameHeaderBytes);
+ mOutputQueueUsed += kFrameHeaderBytes;
+ CreateFrameHeader(packet, 0, FRAME_TYPE_SETTINGS, kFlag_ACK, 0);
+ LogIO(this, nullptr, "Generate Settings ACK", packet, kFrameHeaderBytes);
+ FlushOutputQueue();
+}
+
+void
+Http2Session::GeneratePriority(uint32_t aID, uint8_t aPriorityWeight)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::GeneratePriority %p %X %X\n",
+ this, aID, aPriorityWeight));
+
+ uint32_t frameSize = kFrameHeaderBytes + 5;
+ char *packet = EnsureOutputBuffer(frameSize);
+ mOutputQueueUsed += frameSize;
+
+ CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, aID);
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, 0);
+ memcpy(packet + frameSize - 1, &aPriorityWeight, 1);
+ LogIO(this, nullptr, "Generate Priority", packet, frameSize);
+ FlushOutputQueue();
+}
+
+void
+Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // make sure we don't do this twice for the same stream (at least if we
+ // have a stream entry for it)
+ Http2Stream *stream = mStreamIDHash.Get(aID);
+ if (stream) {
+ if (stream->SentReset())
+ return;
+ stream->SetSentReset(true);
+ }
+
+ LOG3(("Http2Session::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode));
+
+ uint32_t frameSize = kFrameHeaderBytes + 4;
+ char *packet = EnsureOutputBuffer(frameSize);
+ mOutputQueueUsed += frameSize;
+ CreateFrameHeader(packet, 4, FRAME_TYPE_RST_STREAM, 0, aID);
+
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, aStatusCode);
+
+ LogIO(this, nullptr, "Generate Reset", packet, frameSize);
+ FlushOutputQueue();
+}
+
+void
+Http2Session::GenerateGoAway(uint32_t aStatusCode)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::GenerateGoAway %p code=%X\n", this, aStatusCode));
+
+ mClientGoAwayReason = aStatusCode;
+ uint32_t frameSize = kFrameHeaderBytes + 8;
+ char *packet = EnsureOutputBuffer(frameSize);
+ mOutputQueueUsed += frameSize;
+
+ CreateFrameHeader(packet, 8, FRAME_TYPE_GOAWAY, 0, 0);
+
+ // last-good-stream-id are bytes 9-12 reflecting pushes
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, mOutgoingGoAwayID);
+
+ // bytes 13-16 are the status code.
+ NetworkEndian::writeUint32(packet + frameSize - 4, aStatusCode);
+
+ LogIO(this, nullptr, "Generate GoAway", packet, frameSize);
+ FlushOutputQueue();
+}
+
+// The Hello is comprised of
+// 1] 24 octets of magic, which are designed to
+// flush out silent but broken intermediaries
+// 2] a settings frame which sets a small flow control window for pushes
+// 3] a window update frame which creates a large session flow control window
+// 4] 5 priority frames for streams which will never be opened with headers
+// these streams (3, 5, 7, 9, b) build a dependency tree that all other
+// streams will be direct leaves of.
+void
+Http2Session::SendHello()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::SendHello %p\n", this));
+
+ // sized for magic + 5 settings and a session window update and 5 priority frames
+ // 24 magic, 33 for settings (9 header + 4 settings @6), 13 for window update,
+ // 5 priority frames at 14 (9 + 5) each
+ static const uint32_t maxSettings = 5;
+ static const uint32_t prioritySize = 5 * (kFrameHeaderBytes + 5);
+ static const uint32_t maxDataLen = 24 + kFrameHeaderBytes + maxSettings * 6 + 13 + prioritySize;
+ char *packet = EnsureOutputBuffer(maxDataLen);
+ memcpy(packet, kMagicHello, 24);
+ mOutputQueueUsed += 24;
+ LogIO(this, nullptr, "Magic Connection Header", packet, 24);
+
+ packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ memset(packet, 0, maxDataLen - 24);
+
+ // frame header will be filled in after we know how long the frame is
+ uint8_t numberOfEntries = 0;
+
+ // entries need to be listed in order by ID
+ // 1st entry is bytes 9 to 14
+ // 2nd entry is bytes 15 to 20
+ // 3rd entry is bytes 21 to 26
+ // 4th entry is bytes 27 to 32
+ // 5th entry is bytes 33 to 38
+
+ // Let the other endpoint know about our default HPACK decompress table size
+ uint32_t maxHpackBufferSize = gHttpHandler->DefaultHpackBuffer();
+ mDecompressor.SetInitialMaxBufferSize(maxHpackBufferSize);
+ NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_HEADER_TABLE_SIZE);
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, maxHpackBufferSize);
+ numberOfEntries++;
+
+ if (!gHttpHandler->AllowPush()) {
+ // If we don't support push then set MAX_CONCURRENT to 0 and also
+ // set ENABLE_PUSH to 0
+ NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_ENABLE_PUSH);
+ // The value portion of the setting pair is already initialized to 0
+ numberOfEntries++;
+
+ NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_MAX_CONCURRENT);
+ // The value portion of the setting pair is already initialized to 0
+ numberOfEntries++;
+
+ mWaitingForSettingsAck = true;
+ }
+
+ // Advertise the Push RWIN for the session, and on each new pull stream
+ // send a window update
+ NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_INITIAL_WINDOW);
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, mPushAllowance);
+ numberOfEntries++;
+
+ // Make sure the other endpoint knows that we're sticking to the default max
+ // frame size
+ NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_MAX_FRAME_SIZE);
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, kMaxFrameData);
+ numberOfEntries++;
+
+ MOZ_ASSERT(numberOfEntries <= maxSettings);
+ uint32_t dataLen = 6 * numberOfEntries;
+ CreateFrameHeader(packet, dataLen, FRAME_TYPE_SETTINGS, 0, 0);
+ mOutputQueueUsed += kFrameHeaderBytes + dataLen;
+
+ LogIO(this, nullptr, "Generate Settings", packet, kFrameHeaderBytes + dataLen);
+
+ // now bump the local session window from 64KB
+ uint32_t sessionWindowBump = mInitialRwin - kDefaultRwin;
+ if (kDefaultRwin < mInitialRwin) {
+ // send a window update for the session (Stream 0) for something large
+ mLocalSessionWindow = mInitialRwin;
+
+ packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
+ mOutputQueueUsed += kFrameHeaderBytes + 4;
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, sessionWindowBump);
+
+ LOG3(("Session Window increase at start of session %p %u\n",
+ this, sessionWindowBump));
+ LogIO(this, nullptr, "Session Window Bump ", packet, kFrameHeaderBytes + 4);
+ }
+
+ if (gHttpHandler->UseH2Deps() && gHttpHandler->CriticalRequestPrioritization()) {
+ mUseH2Deps = true;
+ MOZ_ASSERT(mNextStreamID == kLeaderGroupID);
+ CreatePriorityNode(kLeaderGroupID, 0, 200, "leader");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kOtherGroupID);
+ CreatePriorityNode(kOtherGroupID, 0, 100, "other");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kBackgroundGroupID);
+ CreatePriorityNode(kBackgroundGroupID, 0, 0, "background");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kSpeculativeGroupID);
+ CreatePriorityNode(kSpeculativeGroupID, kBackgroundGroupID, 0, "speculative");
+ mNextStreamID += 2;
+ MOZ_ASSERT(mNextStreamID == kFollowerGroupID);
+ CreatePriorityNode(kFollowerGroupID, kLeaderGroupID, 0, "follower");
+ mNextStreamID += 2;
+ }
+
+ FlushOutputQueue();
+}
+
+void
+Http2Session::CreatePriorityNode(uint32_t streamID, uint32_t dependsOn, uint8_t weight,
+ const char *label)
+{
+ char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, streamID);
+ mOutputQueueUsed += kFrameHeaderBytes + 5;
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, dependsOn); // depends on
+ packet[kFrameHeaderBytes + 4] = weight; // weight
+
+ LOG3(("Http2Session %p generate Priority Frame 0x%X depends on 0x%X "
+ "weight %d for %s class\n", this, streamID, dependsOn, weight, label));
+ LogIO(this, nullptr, "Priority dep node", packet, kFrameHeaderBytes + 5);
+}
+
+// perform a bunch of integrity checks on the stream.
+// returns true if passed, false (plus LOG and ABORT) if failed.
+bool
+Http2Session::VerifyStream(Http2Stream *aStream, uint32_t aOptionalID = 0)
+{
+ // This is annoying, but at least it is O(1)
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+#ifndef DEBUG
+ // Only do the real verification in debug builds
+ return true;
+#endif
+
+ if (!aStream)
+ return true;
+
+ uint32_t test = 0;
+
+ do {
+ if (aStream->StreamID() == kDeadStreamID)
+ break;
+
+ nsAHttpTransaction *trans = aStream->Transaction();
+
+ test++;
+ if (!trans)
+ break;
+
+ test++;
+ if (mStreamTransactionHash.Get(trans) != aStream)
+ break;
+
+ if (aStream->StreamID()) {
+ Http2Stream *idStream = mStreamIDHash.Get(aStream->StreamID());
+
+ test++;
+ if (idStream != aStream)
+ break;
+
+ if (aOptionalID) {
+ test++;
+ if (idStream->StreamID() != aOptionalID)
+ break;
+ }
+ }
+
+ // tests passed
+ return true;
+ } while (0);
+
+ LOG3(("Http2Session %p VerifyStream Failure %p stream->id=0x%X "
+ "optionalID=0x%X trans=%p test=%d\n",
+ this, aStream, aStream->StreamID(),
+ aOptionalID, aStream->Transaction(), test));
+
+ MOZ_ASSERT(false, "VerifyStream");
+ return false;
+}
+
+void
+Http2Session::CleanupStream(Http2Stream *aStream, nsresult aResult,
+ errorType aResetCode)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::CleanupStream %p %p 0x%X %X\n",
+ this, aStream, aStream ? aStream->StreamID() : 0, aResult));
+ if (!aStream) {
+ return;
+ }
+
+ if (aStream->DeferCleanup(aResult)) {
+ LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID()));
+ return;
+ }
+
+ if (!VerifyStream(aStream)) {
+ LOG3(("Http2Session::CleanupStream failed to verify stream\n"));
+ return;
+ }
+
+ Http2PushedStream *pushSource = aStream->PushSource();
+ if (pushSource) {
+ // aStream is a synthetic attached to an even push
+ MOZ_ASSERT(pushSource->GetConsumerStream() == aStream);
+ MOZ_ASSERT(!aStream->StreamID());
+ MOZ_ASSERT(!(pushSource->StreamID() & 0x1));
+ pushSource->SetConsumerStream(nullptr);
+ }
+
+ // don't reset a stream that has recevied a fin or rst
+ if (!aStream->RecvdFin() && !aStream->RecvdReset() && aStream->StreamID() &&
+ !(mInputFrameFinal && (aStream == mInputFrameDataStream))) { // !(recvdfin with mark pending)
+ LOG3(("Stream 0x%X had not processed recv FIN, sending RST code %X\n", aStream->StreamID(), aResetCode));
+ GenerateRstStream(aResetCode, aStream->StreamID());
+ }
+
+ CloseStream(aStream, aResult);
+
+ // Remove the stream from the ID hash table and, if an even id, the pushed
+ // table too.
+ uint32_t id = aStream->StreamID();
+ if (id > 0) {
+ mStreamIDHash.Remove(id);
+ if (!(id & 1)) {
+ mPushedStreams.RemoveElement(aStream);
+ Http2PushedStream *pushStream = static_cast<Http2PushedStream *>(aStream);
+ nsAutoCString hashKey;
+ pushStream->GetHashKey(hashKey);
+ nsIRequestContext *requestContext = aStream->RequestContext();
+ if (requestContext) {
+ SpdyPushCache *cache = nullptr;
+ requestContext->GetSpdyPushCache(&cache);
+ if (cache) {
+ Http2PushedStream *trash = cache->RemovePushedStreamHttp2(hashKey);
+ LOG3(("Http2Session::CleanupStream %p aStream=%p pushStream=%p trash=%p",
+ this, aStream, pushStream, trash));
+ }
+ }
+ }
+ }
+
+ RemoveStreamFromQueues(aStream);
+
+ // removing from the stream transaction hash will
+ // delete the Http2Stream and drop the reference to
+ // its transaction
+ mStreamTransactionHash.Remove(aStream->Transaction());
+
+ if (mShouldGoAway && !mStreamTransactionHash.Count())
+ Close(NS_OK);
+
+ if (pushSource) {
+ pushSource->SetDeferCleanupOnSuccess(false);
+ CleanupStream(pushSource, aResult, aResetCode);
+ }
+}
+
+void
+Http2Session::CleanupStream(uint32_t aID, nsresult aResult, errorType aResetCode)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ Http2Stream *stream = mStreamIDHash.Get(aID);
+ LOG3(("Http2Session::CleanupStream %p by ID 0x%X to stream %p\n",
+ this, aID, stream));
+ if (!stream) {
+ return;
+ }
+ CleanupStream(stream, aResult, aResetCode);
+}
+
+static void RemoveStreamFromQueue(Http2Stream *aStream, nsDeque &queue)
+{
+ size_t size = queue.GetSize();
+ for (size_t count = 0; count < size; ++count) {
+ Http2Stream *stream = static_cast<Http2Stream *>(queue.PopFront());
+ if (stream != aStream)
+ queue.Push(stream);
+ }
+}
+
+void
+Http2Session::RemoveStreamFromQueues(Http2Stream *aStream)
+{
+ RemoveStreamFromQueue(aStream, mReadyForWrite);
+ RemoveStreamFromQueue(aStream, mQueuedStreams);
+ RemoveStreamFromQueue(aStream, mPushesReadyForRead);
+ RemoveStreamFromQueue(aStream, mSlowConsumersReadyForRead);
+}
+
+void
+Http2Session::CloseStream(Http2Stream *aStream, nsresult aResult)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::CloseStream %p %p 0x%x %X\n",
+ this, aStream, aStream->StreamID(), aResult));
+
+ MaybeDecrementConcurrent(aStream);
+
+ // Check if partial frame reader
+ if (aStream == mInputFrameDataStream) {
+ LOG3(("Stream had active partial read frame on close"));
+ ChangeDownstreamState(DISCARDING_DATA_FRAME);
+ mInputFrameDataStream = nullptr;
+ }
+
+ RemoveStreamFromQueues(aStream);
+
+ if (aStream->IsTunnel()) {
+ UnRegisterTunnel(aStream);
+ }
+
+ // Send the stream the close() indication
+ aStream->Close(aResult);
+}
+
+nsresult
+Http2Session::SetInputFrameDataStream(uint32_t streamID)
+{
+ mInputFrameDataStream = mStreamIDHash.Get(streamID);
+ if (VerifyStream(mInputFrameDataStream, streamID))
+ return NS_OK;
+
+ LOG3(("Http2Session::SetInputFrameDataStream failed to verify 0x%X\n",
+ streamID));
+ mInputFrameDataStream = nullptr;
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+Http2Session::ParsePadding(uint8_t &paddingControlBytes, uint16_t &paddingLength)
+{
+ if (mInputFrameFlags & kFlag_PADDED) {
+ paddingLength = *reinterpret_cast<uint8_t *>(&mInputFrameBuffer[kFrameHeaderBytes]);
+ paddingControlBytes = 1;
+ } else {
+ paddingLength = 0;
+ paddingControlBytes = 0;
+ }
+
+ if (static_cast<uint32_t>(paddingLength + paddingControlBytes) > mInputFrameDataSize) {
+ // This is fatal to the session
+ LOG3(("Http2Session::ParsePadding %p stream 0x%x PROTOCOL_ERROR "
+ "paddingLength %d > frame size %d\n",
+ this, mInputFrameID, paddingLength, mInputFrameDataSize));
+ RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Http2Session::RecvHeaders(Http2Session *self)
+{
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_HEADERS ||
+ self->mInputFrameType == FRAME_TYPE_CONTINUATION);
+
+ bool isContinuation = self->mExpectedHeaderID != 0;
+
+ // If this doesn't have END_HEADERS set on it then require the next
+ // frame to be HEADERS of the same ID
+ bool endHeadersFlag = self->mInputFrameFlags & kFlag_END_HEADERS;
+
+ if (endHeadersFlag)
+ self->mExpectedHeaderID = 0;
+ else
+ self->mExpectedHeaderID = self->mInputFrameID;
+
+ uint32_t priorityLen = 0;
+ if (self->mInputFrameFlags & kFlag_PRIORITY) {
+ priorityLen = 5;
+ }
+ self->SetInputFrameDataStream(self->mInputFrameID);
+
+ // Find out how much padding this frame has, so we can only extract the real
+ // header data from the frame.
+ uint16_t paddingLength = 0;
+ uint8_t paddingControlBytes = 0;
+ nsresult rv;
+
+ if (!isContinuation) {
+ self->mDecompressBuffer.Truncate();
+ rv = self->ParsePadding(paddingControlBytes, paddingLength);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ LOG3(("Http2Session::RecvHeaders %p stream 0x%X priorityLen=%d stream=%p "
+ "end_stream=%d end_headers=%d priority_group=%d "
+ "paddingLength=%d padded=%d\n",
+ self, self->mInputFrameID, priorityLen, self->mInputFrameDataStream,
+ self->mInputFrameFlags & kFlag_END_STREAM,
+ self->mInputFrameFlags & kFlag_END_HEADERS,
+ self->mInputFrameFlags & kFlag_PRIORITY,
+ paddingLength,
+ self->mInputFrameFlags & kFlag_PADDED));
+
+ if ((paddingControlBytes + priorityLen + paddingLength) > self->mInputFrameDataSize) {
+ // This is fatal to the session
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ if (!self->mInputFrameDataStream) {
+ // Cannot find stream. We can continue the session, but we need to
+ // uncompress the header block to maintain the correct compression context
+
+ LOG3(("Http2Session::RecvHeaders %p lookup mInputFrameID stream "
+ "0x%X failed. NextStreamID = 0x%X\n",
+ self, self->mInputFrameID, self->mNextStreamID));
+
+ if (self->mInputFrameID >= self->mNextStreamID)
+ self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
+
+ self->mDecompressBuffer.Append(&self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + priorityLen],
+ self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength);
+
+ if (self->mInputFrameFlags & kFlag_END_HEADERS) {
+ rv = self->UncompressAndDiscard(false);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::RecvHeaders uncompress failed\n"));
+ // this is fatal to the session
+ self->mGoAwayReason = COMPRESSION_ERROR;
+ return rv;
+ }
+ }
+
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ // make sure this is either the first headers or a trailer
+ if (self->mInputFrameDataStream->AllHeadersReceived() &&
+ !(self->mInputFrameFlags & kFlag_END_STREAM)) {
+ // Any header block after the first that does *not* end the stream is
+ // illegal.
+ LOG3(("Http2Session::Illegal Extra HeaderBlock %p 0x%X\n", self, self->mInputFrameID));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ // queue up any compression bytes
+ self->mDecompressBuffer.Append(&self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + priorityLen],
+ self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength);
+
+ self->mInputFrameDataStream->UpdateTransportReadEvents(self->mInputFrameDataSize);
+ self->mLastDataReadEpoch = self->mLastReadEpoch;
+
+ if (!endHeadersFlag) { // more are coming - don't process yet
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ rv = self->ResponseHeadersComplete();
+ if (rv == NS_ERROR_ILLEGAL_VALUE) {
+ LOG3(("Http2Session::RecvHeaders %p PROTOCOL_ERROR detected stream 0x%X\n",
+ self, self->mInputFrameID));
+ self->CleanupStream(self->mInputFrameDataStream, rv, PROTOCOL_ERROR);
+ self->ResetDownstreamState();
+ rv = NS_OK;
+ } else if (NS_FAILED(rv)) {
+ // This is fatal to the session.
+ self->mGoAwayReason = COMPRESSION_ERROR;
+ }
+ return rv;
+}
+
+// ResponseHeadersComplete() returns NS_ERROR_ILLEGAL_VALUE when the stream
+// should be reset with a PROTOCOL_ERROR, NS_OK when the response headers were
+// fine, and any other error is fatal to the session.
+nsresult
+Http2Session::ResponseHeadersComplete()
+{
+ LOG3(("Http2Session::ResponseHeadersComplete %p for 0x%X fin=%d",
+ this, mInputFrameDataStream->StreamID(), mInputFrameFinal));
+
+ // only interpret headers once, afterwards ignore as trailers
+ if (mInputFrameDataStream->AllHeadersReceived()) {
+ LOG3(("Http2Session::ResponseHeadersComplete extra headers"));
+ MOZ_ASSERT(mInputFrameFlags & kFlag_END_STREAM);
+ nsresult rv = UncompressAndDiscard(false);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::ResponseHeadersComplete extra uncompress failed\n"));
+ return rv;
+ }
+ mFlatHTTPResponseHeadersOut = 0;
+ mFlatHTTPResponseHeaders.Truncate();
+ if (mInputFrameFinal) {
+ // need to process the fin
+ ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
+ } else {
+ ResetDownstreamState();
+ }
+
+ return NS_OK;
+ }
+
+ // if this turns out to be a 1xx response code we have to
+ // undo the headers received bit that we are setting here.
+ bool didFirstSetAllRecvd = !mInputFrameDataStream->AllHeadersReceived();
+ mInputFrameDataStream->SetAllHeadersReceived();
+
+ // The stream needs to see flattened http headers
+ // Uncompressed http/2 format headers currently live in
+ // Http2Stream::mDecompressBuffer - convert that to HTTP format in
+ // mFlatHTTPResponseHeaders via ConvertHeaders()
+
+ nsresult rv;
+ int32_t httpResponseCode; // out param to ConvertResponseHeaders
+ mFlatHTTPResponseHeadersOut = 0;
+ rv = mInputFrameDataStream->ConvertResponseHeaders(&mDecompressor,
+ mDecompressBuffer,
+ mFlatHTTPResponseHeaders,
+ httpResponseCode);
+ if (rv == NS_ERROR_NET_RESET) {
+ LOG(("Http2Session::ResponseHeadersComplete %p ConvertResponseHeaders reset\n", this));
+ // This means the stream found connection-oriented auth. Treat this like we
+ // got a reset with HTTP_1_1_REQUIRED.
+ mInputFrameDataStream->Transaction()->DisableSpdy();
+ CleanupStream(mInputFrameDataStream, NS_ERROR_NET_RESET, CANCEL_ERROR);
+ ResetDownstreamState();
+ return NS_OK;
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // allow more headers in the case of 1xx
+ if (((httpResponseCode / 100) == 1) && didFirstSetAllRecvd) {
+ mInputFrameDataStream->UnsetAllHeadersReceived();
+ }
+
+ ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
+ return NS_OK;
+}
+
+nsresult
+Http2Session::RecvPriority(Http2Session *self)
+{
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PRIORITY);
+
+ if (self->mInputFrameDataSize != 5) {
+ LOG3(("Http2Session::RecvPriority %p wrong length data=%d\n",
+ self, self->mInputFrameDataSize));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ if (!self->mInputFrameID) {
+ LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t newPriorityDependency = NetworkEndian::readUint32(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes);
+ bool exclusive = !!(newPriorityDependency & 0x80000000);
+ newPriorityDependency &= 0x7fffffff;
+ uint8_t newPriorityWeight = *(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);
+ if (self->mInputFrameDataStream) {
+ self->mInputFrameDataStream->SetPriorityDependency(newPriorityDependency,
+ newPriorityWeight,
+ exclusive);
+ }
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult
+Http2Session::RecvRstStream(Http2Session *self)
+{
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_RST_STREAM);
+
+ if (self->mInputFrameDataSize != 4) {
+ LOG3(("Http2Session::RecvRstStream %p RST_STREAM wrong length data=%d",
+ self, self->mInputFrameDataSize));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ if (!self->mInputFrameID) {
+ LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ self->mDownstreamRstReason = NetworkEndian::readUint32(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes);
+
+ LOG3(("Http2Session::RecvRstStream %p RST_STREAM Reason Code %u ID %x\n",
+ self, self->mDownstreamRstReason, self->mInputFrameID));
+
+ self->SetInputFrameDataStream(self->mInputFrameID);
+ if (!self->mInputFrameDataStream) {
+ // if we can't find the stream just ignore it (4.2 closed)
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ self->mInputFrameDataStream->SetRecvdReset(true);
+ self->MaybeDecrementConcurrent(self->mInputFrameDataStream);
+ self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM);
+ return NS_OK;
+}
+
+nsresult
+Http2Session::RecvSettings(Http2Session *self)
+{
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_SETTINGS);
+
+ if (self->mInputFrameID) {
+ LOG3(("Http2Session::RecvSettings %p needs stream ID of 0. 0x%X\n",
+ self, self->mInputFrameID));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ if (self->mInputFrameDataSize % 6) {
+ // Number of Settings is determined by dividing by each 6 byte setting
+ // entry. So the payload must be a multiple of 6.
+ LOG3(("Http2Session::RecvSettings %p SETTINGS wrong length data=%d",
+ self, self->mInputFrameDataSize));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ uint32_t numEntries = self->mInputFrameDataSize / 6;
+ LOG3(("Http2Session::RecvSettings %p SETTINGS Control Frame "
+ "with %d entries ack=%X", self, numEntries,
+ self->mInputFrameFlags & kFlag_ACK));
+
+ if ((self->mInputFrameFlags & kFlag_ACK) && self->mInputFrameDataSize) {
+ LOG3(("Http2Session::RecvSettings %p ACK with non zero payload is err\n"));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ for (uint32_t index = 0; index < numEntries; ++index) {
+ uint8_t *setting = reinterpret_cast<uint8_t *>
+ (self->mInputFrameBuffer.get()) + kFrameHeaderBytes + index * 6;
+
+ uint16_t id = NetworkEndian::readUint16(setting);
+ uint32_t value = NetworkEndian::readUint32(setting + 2);
+ LOG3(("Settings ID %u, Value %u", id, value));
+
+ switch (id)
+ {
+ case SETTINGS_TYPE_HEADER_TABLE_SIZE:
+ LOG3(("Compression header table setting received: %d\n", value));
+ self->mCompressor.SetMaxBufferSize(value);
+ break;
+
+ case SETTINGS_TYPE_ENABLE_PUSH:
+ LOG3(("Client received an ENABLE Push SETTING. Odd.\n"));
+ // nop
+ break;
+
+ case SETTINGS_TYPE_MAX_CONCURRENT:
+ self->mMaxConcurrent = value;
+ Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value);
+ self->ProcessPending();
+ break;
+
+ case SETTINGS_TYPE_INITIAL_WINDOW:
+ {
+ Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10);
+ int32_t delta = value - self->mServerInitialStreamWindow;
+ self->mServerInitialStreamWindow = value;
+
+ // SETTINGS only adjusts stream windows. Leave the session window alone.
+ // We need to add the delta to all open streams (delta can be negative)
+ for (auto iter = self->mStreamTransactionHash.Iter();
+ !iter.Done();
+ iter.Next()) {
+ iter.Data()->UpdateServerReceiveWindow(delta);
+ }
+ }
+ break;
+
+ case SETTINGS_TYPE_MAX_FRAME_SIZE:
+ {
+ if ((value < kMaxFrameData) || (value >= 0x01000000)) {
+ LOG3(("Received invalid max frame size 0x%X", value));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+ // We stick to the default for simplicity's sake, so nothing to change
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ self->ResetDownstreamState();
+
+ if (!(self->mInputFrameFlags & kFlag_ACK)) {
+ self->GenerateSettingsAck();
+ } else if (self->mWaitingForSettingsAck) {
+ self->mGoAwayOnPush = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Http2Session::RecvPushPromise(Http2Session *self)
+{
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PUSH_PROMISE ||
+ self->mInputFrameType == FRAME_TYPE_CONTINUATION);
+
+ // Find out how much padding this frame has, so we can only extract the real
+ // header data from the frame.
+ uint16_t paddingLength = 0;
+ uint8_t paddingControlBytes = 0;
+
+ // If this doesn't have END_PUSH_PROMISE set on it then require the next
+ // frame to be PUSH_PROMISE of the same ID
+ uint32_t promiseLen;
+ uint32_t promisedID;
+
+ if (self->mExpectedPushPromiseID) {
+ promiseLen = 0; // really a continuation frame
+ promisedID = self->mContinuedPromiseStream;
+ } else {
+ self->mDecompressBuffer.Truncate();
+ nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ promiseLen = 4;
+ promisedID = NetworkEndian::readUint32(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes + paddingControlBytes);
+ promisedID &= 0x7fffffff;
+ if (promisedID <= self->mLastPushedID) {
+ LOG3(("Http2Session::RecvPushPromise %p ID too low %u expected > %u.\n",
+ self, promisedID, self->mLastPushedID));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+ self->mLastPushedID = promisedID;
+ }
+
+ uint32_t associatedID = self->mInputFrameID;
+
+ if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
+ self->mExpectedPushPromiseID = 0;
+ self->mContinuedPromiseStream = 0;
+ } else {
+ self->mExpectedPushPromiseID = self->mInputFrameID;
+ self->mContinuedPromiseStream = promisedID;
+ }
+
+ if ((paddingControlBytes + promiseLen + paddingLength) > self->mInputFrameDataSize) {
+ // This is fatal to the session
+ LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
+ "PROTOCOL_ERROR extra %d > frame size %d\n",
+ self, promisedID, associatedID, (paddingControlBytes + promiseLen + paddingLength),
+ self->mInputFrameDataSize));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
+ "paddingLength %d padded %d\n",
+ self, promisedID, associatedID, paddingLength,
+ self->mInputFrameFlags & kFlag_PADDED));
+
+ if (!associatedID || !promisedID || (promisedID & 1)) {
+ LOG3(("Http2Session::RecvPushPromise %p ID invalid.\n", self));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ // confirm associated-to
+ nsresult rv = self->SetInputFrameDataStream(associatedID);
+ if (NS_FAILED(rv))
+ return rv;
+
+ Http2Stream *associatedStream = self->mInputFrameDataStream;
+ ++(self->mServerPushedResources);
+
+ // Anytime we start using the high bit of stream ID (either client or server)
+ // begin to migrate to a new session.
+ if (promisedID >= kMaxStreamID)
+ self->mShouldGoAway = true;
+
+ bool resetStream = true;
+ SpdyPushCache *cache = nullptr;
+
+ if (self->mShouldGoAway && !Http2PushedStream::TestOnPush(associatedStream)) {
+ LOG3(("Http2Session::RecvPushPromise %p cache push while in GoAway "
+ "mode refused.\n", self));
+ self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
+ } else if (!gHttpHandler->AllowPush()) {
+ // ENABLE_PUSH and MAX_CONCURRENT_STREAMS of 0 in settings disabled push
+ LOG3(("Http2Session::RecvPushPromise Push Recevied when Disabled\n"));
+ if (self->mGoAwayOnPush) {
+ LOG3(("Http2Session::RecvPushPromise sending GOAWAY"));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+ self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
+ } else if (!(associatedID & 1)) {
+ LOG3(("Http2Session::RecvPushPromise %p assocated=0x%X on pushed (even) stream not allowed\n",
+ self, associatedID));
+ self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
+ } else if (!associatedStream) {
+ LOG3(("Http2Session::RecvPushPromise %p lookup associated ID failed.\n", self));
+ self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
+ } else {
+ nsIRequestContext *requestContext = associatedStream->RequestContext();
+ if (requestContext) {
+ requestContext->GetSpdyPushCache(&cache);
+ if (!cache) {
+ cache = new SpdyPushCache();
+ if (!cache || NS_FAILED(requestContext->SetSpdyPushCache(cache))) {
+ delete cache;
+ cache = nullptr;
+ }
+ }
+ }
+ if (!cache) {
+ // this is unexpected, but we can handle it just by refusing the push
+ LOG3(("Http2Session::RecvPushPromise Push Recevied without push cache\n"));
+ self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
+ } else {
+ resetStream = false;
+ }
+ }
+
+ if (resetStream) {
+ // Need to decompress the headers even though we aren't using them yet in
+ // order to keep the compression context consistent for other frames
+ self->mDecompressBuffer.Append(&self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + promiseLen],
+ self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength);
+ if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
+ rv = self->UncompressAndDiscard(true);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::RecvPushPromise uncompress failed\n"));
+ self->mGoAwayReason = COMPRESSION_ERROR;
+ return rv;
+ }
+ }
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ self->mDecompressBuffer.Append(&self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + promiseLen],
+ self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength);
+
+ if (!(self->mInputFrameFlags & kFlag_END_PUSH_PROMISE)) {
+ LOG3(("Http2Session::RecvPushPromise not finishing processing for multi-frame push\n"));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ // Create the buffering transaction and push stream
+ RefPtr<Http2PushTransactionBuffer> transactionBuffer =
+ new Http2PushTransactionBuffer();
+ transactionBuffer->SetConnection(self);
+ Http2PushedStream *pushedStream =
+ new Http2PushedStream(transactionBuffer, self, associatedStream, promisedID);
+
+ rv = pushedStream->ConvertPushHeaders(&self->mDecompressor,
+ self->mDecompressBuffer,
+ pushedStream->GetRequestString());
+
+ if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ LOG3(("Http2Session::PushPromise Semantics not Implemented\n"));
+ self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
+ delete pushedStream;
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (rv == NS_ERROR_ILLEGAL_VALUE) {
+ // This means the decompression completed ok, but there was a problem with
+ // the decoded headers. Reset the stream and go away.
+ self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
+ delete pushedStream;
+ self->ResetDownstreamState();
+ return NS_OK;
+ } else if (NS_FAILED(rv)) {
+ // This is fatal to the session.
+ self->mGoAwayReason = COMPRESSION_ERROR;
+ return rv;
+ }
+
+ // Ownership of the pushed stream is by the transaction hash, just as it
+ // is for a client initiated stream. Errors that aren't fatal to the
+ // whole session must call cleanupStream() after this point in order
+ // to remove the stream from that hash.
+ self->mStreamTransactionHash.Put(transactionBuffer, pushedStream);
+ self->mPushedStreams.AppendElement(pushedStream);
+
+ if (self->RegisterStreamID(pushedStream, promisedID) == kDeadStreamID) {
+ LOG3(("Http2Session::RecvPushPromise registerstreamid failed\n"));
+ self->mGoAwayReason = INTERNAL_ERROR;
+ return NS_ERROR_FAILURE;
+ }
+
+ if (promisedID > self->mOutgoingGoAwayID)
+ self->mOutgoingGoAwayID = promisedID;
+
+ // Fake the request side of the pushed HTTP transaction. Sets up hash
+ // key and origin
+ uint32_t notUsed;
+ pushedStream->ReadSegments(nullptr, 1, &notUsed);
+
+ nsAutoCString key;
+ if (!pushedStream->GetHashKey(key)) {
+ LOG3(("Http2Session::RecvPushPromise one of :authority :scheme :path missing from push\n"));
+ self->CleanupStream(pushedStream, NS_ERROR_FAILURE, PROTOCOL_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ RefPtr<nsStandardURL> associatedURL, pushedURL;
+ rv = Http2Stream::MakeOriginURL(associatedStream->Origin(), associatedURL);
+ if (NS_SUCCEEDED(rv)) {
+ rv = Http2Stream::MakeOriginURL(pushedStream->Origin(), pushedURL);
+ }
+ LOG3(("Http2Session::RecvPushPromise %p checking %s == %s", self,
+ associatedStream->Origin().get(), pushedStream->Origin().get()));
+ bool match = false;
+ if (NS_SUCCEEDED(rv)) {
+ rv = associatedURL->Equals(pushedURL, &match);
+ }
+ if (NS_FAILED(rv)) {
+ // Fallback to string equality of origins. This won't be guaranteed to be as
+ // liberal as we want it to be, but it will at least be safe
+ match = associatedStream->Origin().Equals(pushedStream->Origin());
+ }
+ if (!match) {
+ LOG3(("Http2Session::RecvPushPromise %p pushed stream mismatched origin "
+ "associated origin %s .. pushed origin %s\n", self,
+ associatedStream->Origin().get(), pushedStream->Origin().get()));
+ self->CleanupStream(pushedStream, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (pushedStream->TryOnPush()) {
+ LOG3(("Http2Session::RecvPushPromise %p channel implements nsIHttpPushListener "
+ "stream %p will not be placed into session cache.\n", self, pushedStream));
+ } else {
+ LOG3(("Http2Session::RecvPushPromise %p place stream into session cache\n", self));
+ if (!cache->RegisterPushedStreamHttp2(key, pushedStream)) {
+ LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n"));
+ self->CleanupStream(pushedStream, NS_ERROR_FAILURE, INTERNAL_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+ }
+
+ pushedStream->SetHTTPState(Http2Stream::RESERVED_BY_REMOTE);
+ static_assert(Http2Stream::kWorstPriority >= 0,
+ "kWorstPriority out of range");
+ uint8_t priorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) -
+ (Http2Stream::kWorstPriority - Http2Stream::kNormalPriority);
+ pushedStream->SetPriority(Http2Stream::kWorstPriority);
+ self->GeneratePriority(promisedID, priorityWeight);
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult
+Http2Session::RecvPing(Http2Session *self)
+{
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PING);
+
+ LOG3(("Http2Session::RecvPing %p PING Flags 0x%X.", self,
+ self->mInputFrameFlags));
+
+ if (self->mInputFrameDataSize != 8) {
+ LOG3(("Http2Session::RecvPing %p PING had wrong amount of data %d",
+ self, self->mInputFrameDataSize));
+ RETURN_SESSION_ERROR(self, FRAME_SIZE_ERROR);
+ }
+
+ if (self->mInputFrameID) {
+ LOG3(("Http2Session::RecvPing %p PING needs stream ID of 0. 0x%X\n",
+ self, self->mInputFrameID));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ if (self->mInputFrameFlags & kFlag_ACK) {
+ // presumably a reply to our timeout ping.. don't reply to it
+ self->mPingSentEpoch = 0;
+ } else {
+ // reply with a ack'd ping
+ self->GeneratePing(true);
+ }
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult
+Http2Session::RecvGoAway(Http2Session *self)
+{
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_GOAWAY);
+
+ if (self->mInputFrameDataSize < 8) {
+ // data > 8 is an opaque token that we can't interpret. NSPR Logs will
+ // have the hex of all packets so there is no point in separately logging.
+ LOG3(("Http2Session::RecvGoAway %p GOAWAY had wrong amount of data %d",
+ self, self->mInputFrameDataSize));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ if (self->mInputFrameID) {
+ LOG3(("Http2Session::RecvGoAway %p GOAWAY had non zero stream ID 0x%X\n",
+ self, self->mInputFrameID));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ self->mShouldGoAway = true;
+ self->mGoAwayID = NetworkEndian::readUint32(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes);
+ self->mGoAwayID &= 0x7fffffff;
+ self->mCleanShutdown = true;
+ self->mPeerGoAwayReason = NetworkEndian::readUint32(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);
+
+ // Find streams greater than the last-good ID and mark them for deletion
+ // in the mGoAwayStreamsToRestart queue. The underlying transaction can be
+ // restarted.
+ for (auto iter = self->mStreamTransactionHash.Iter();
+ !iter.Done();
+ iter.Next()) {
+ // these streams were not processed by the server and can be restarted.
+ // Do that after the enumerator completes to avoid the risk of
+ // a restart event re-entrantly modifying this hash. Be sure not to restart
+ // a pushed (even numbered) stream
+ nsAutoPtr<Http2Stream>& stream = iter.Data();
+ if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) ||
+ !stream->HasRegisteredID()) {
+ self->mGoAwayStreamsToRestart.Push(stream);
+ }
+ }
+
+ // Process the streams marked for deletion and restart.
+ size_t size = self->mGoAwayStreamsToRestart.GetSize();
+ for (size_t count = 0; count < size; ++count) {
+ Http2Stream *stream =
+ static_cast<Http2Stream *>(self->mGoAwayStreamsToRestart.PopFront());
+
+ if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
+ stream->Transaction()->DisableSpdy();
+ }
+ self->CloseStream(stream, NS_ERROR_NET_RESET);
+ if (stream->HasRegisteredID())
+ self->mStreamIDHash.Remove(stream->StreamID());
+ self->mStreamTransactionHash.Remove(stream->Transaction());
+ }
+
+ // Queued streams can also be deleted from this session and restarted
+ // in another one. (they were never sent on the network so they implicitly
+ // are not covered by the last-good id.
+ size = self->mQueuedStreams.GetSize();
+ for (size_t count = 0; count < size; ++count) {
+ Http2Stream *stream =
+ static_cast<Http2Stream *>(self->mQueuedStreams.PopFront());
+ MOZ_ASSERT(stream->Queued());
+ stream->SetQueued(false);
+ if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
+ stream->Transaction()->DisableSpdy();
+ }
+ self->CloseStream(stream, NS_ERROR_NET_RESET);
+ self->mStreamTransactionHash.Remove(stream->Transaction());
+ }
+
+ LOG3(("Http2Session::RecvGoAway %p GOAWAY Last-Good-ID 0x%X status 0x%X "
+ "live streams=%d\n", self, self->mGoAwayID, self->mPeerGoAwayReason,
+ self->mStreamTransactionHash.Count()));
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult
+Http2Session::RecvWindowUpdate(Http2Session *self)
+{
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_WINDOW_UPDATE);
+
+ if (self->mInputFrameDataSize != 4) {
+ LOG3(("Http2Session::RecvWindowUpdate %p Window Update wrong length %d\n",
+ self, self->mInputFrameDataSize));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ uint32_t delta = NetworkEndian::readUint32(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes);
+ delta &= 0x7fffffff;
+
+ LOG3(("Http2Session::RecvWindowUpdate %p len=%d Stream 0x%X.\n",
+ self, delta, self->mInputFrameID));
+
+ if (self->mInputFrameID) { // stream window
+ nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!self->mInputFrameDataStream) {
+ LOG3(("Http2Session::RecvWindowUpdate %p lookup streamID 0x%X failed.\n",
+ self, self->mInputFrameID));
+ // only resest the session if the ID is one we haven't ever opened
+ if (self->mInputFrameID >= self->mNextStreamID)
+ self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (delta == 0) {
+ LOG3(("Http2Session::RecvWindowUpdate %p received 0 stream window update",
+ self));
+ self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
+ PROTOCOL_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ int64_t oldRemoteWindow = self->mInputFrameDataStream->ServerReceiveWindow();
+ self->mInputFrameDataStream->UpdateServerReceiveWindow(delta);
+ if (self->mInputFrameDataStream->ServerReceiveWindow() >= 0x80000000) {
+ // a window cannot reach 2^31 and be in compliance. Our calculations
+ // are 64 bit safe though.
+ LOG3(("Http2Session::RecvWindowUpdate %p stream window "
+ "exceeds 2^31 - 1\n", self));
+ self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
+ FLOW_CONTROL_ERROR);
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ LOG3(("Http2Session::RecvWindowUpdate %p stream 0x%X window "
+ "%d increased by %d now %d.\n", self, self->mInputFrameID,
+ oldRemoteWindow, delta, oldRemoteWindow + delta));
+
+ } else { // session window update
+ if (delta == 0) {
+ LOG3(("Http2Session::RecvWindowUpdate %p received 0 session window update",
+ self));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ int64_t oldRemoteWindow = self->mServerSessionWindow;
+ self->mServerSessionWindow += delta;
+
+ if (self->mServerSessionWindow >= 0x80000000) {
+ // a window cannot reach 2^31 and be in compliance. Our calculations
+ // are 64 bit safe though.
+ LOG3(("Http2Session::RecvWindowUpdate %p session window "
+ "exceeds 2^31 - 1\n", self));
+ RETURN_SESSION_ERROR(self, FLOW_CONTROL_ERROR);
+ }
+
+ if ((oldRemoteWindow <= 0) && (self->mServerSessionWindow > 0)) {
+ LOG3(("Http2Session::RecvWindowUpdate %p restart session window\n",
+ self));
+ for (auto iter = self->mStreamTransactionHash.Iter();
+ !iter.Done();
+ iter.Next()) {
+ MOZ_ASSERT(self->mServerSessionWindow > 0);
+
+ nsAutoPtr<Http2Stream>& stream = iter.Data();
+ if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0) {
+ continue;
+ }
+
+ self->mReadyForWrite.Push(stream);
+ self->SetWriteCallbacks();
+ }
+ }
+ LOG3(("Http2Session::RecvWindowUpdate %p session window "
+ "%d increased by %d now %d.\n", self,
+ oldRemoteWindow, delta, oldRemoteWindow + delta));
+ }
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult
+Http2Session::RecvContinuation(Http2Session *self)
+{
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_CONTINUATION);
+ MOZ_ASSERT(self->mInputFrameID);
+ MOZ_ASSERT(self->mExpectedPushPromiseID || self->mExpectedHeaderID);
+ MOZ_ASSERT(!(self->mExpectedPushPromiseID && self->mExpectedHeaderID));
+
+ LOG3(("Http2Session::RecvContinuation %p Flags 0x%X id 0x%X "
+ "promise id 0x%X header id 0x%X\n",
+ self, self->mInputFrameFlags, self->mInputFrameID,
+ self->mExpectedPushPromiseID, self->mExpectedHeaderID));
+
+ self->SetInputFrameDataStream(self->mInputFrameID);
+
+ if (!self->mInputFrameDataStream) {
+ LOG3(("Http2Session::RecvContination stream ID 0x%X not found.",
+ self->mInputFrameID));
+ RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
+ }
+
+ // continued headers
+ if (self->mExpectedHeaderID) {
+ self->mInputFrameFlags &= ~kFlag_PRIORITY;
+ return RecvHeaders(self);
+ }
+
+ // continued push promise
+ if (self->mInputFrameFlags & kFlag_END_HEADERS) {
+ self->mInputFrameFlags &= ~kFlag_END_HEADERS;
+ self->mInputFrameFlags |= kFlag_END_PUSH_PROMISE;
+ }
+ return RecvPushPromise(self);
+}
+
+class UpdateAltSvcEvent : public Runnable
+{
+public:
+UpdateAltSvcEvent(const nsCString &header,
+ const nsCString &aOrigin,
+ nsHttpConnectionInfo *aCI,
+ nsIInterfaceRequestor *callbacks)
+ : mHeader(header)
+ , mOrigin(aOrigin)
+ , mCI(aCI)
+ , mCallbacks(callbacks)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCString originScheme;
+ nsCString originHost;
+ int32_t originPort = -1;
+
+ nsCOMPtr<nsIURI> uri;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), mOrigin))) {
+ LOG(("UpdateAltSvcEvent origin does not parse %s\n",
+ mOrigin.get()));
+ return NS_OK;
+ }
+ uri->GetScheme(originScheme);
+ uri->GetHost(originHost);
+ uri->GetPort(&originPort);
+
+ AltSvcMapping::ProcessHeader(mHeader, originScheme, originHost, originPort,
+ mCI->GetUsername(), mCI->GetPrivate(), mCallbacks,
+ mCI->ProxyInfo(), 0, mCI->GetOriginAttributes());
+ return NS_OK;
+ }
+
+private:
+ nsCString mHeader;
+ nsCString mOrigin;
+ RefPtr<nsHttpConnectionInfo> mCI;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+};
+
+// defined as an http2 extension - alt-svc
+// defines receipt of frame type 0x0A.. See AlternateSevices.h at least draft -06 sec 4
+// as this is an extension, never generate protocol error - just ignore problems
+nsresult
+Http2Session::RecvAltSvc(Http2Session *self)
+{
+ MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ALTSVC);
+ LOG3(("Http2Session::RecvAltSvc %p Flags 0x%X id 0x%X\n", self,
+ self->mInputFrameFlags, self->mInputFrameID));
+
+ if (self->mInputFrameDataSize < 2) {
+ LOG3(("Http2Session::RecvAltSvc %p frame too small", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ uint16_t originLen = NetworkEndian::readUint16(
+ self->mInputFrameBuffer.get() + kFrameHeaderBytes);
+ if (originLen + 2U > self->mInputFrameDataSize) {
+ LOG3(("Http2Session::RecvAltSvc %p origin len too big for frame", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (!gHttpHandler->AllowAltSvc()) {
+ LOG3(("Http2Session::RecvAltSvc %p frame alt service pref'd off", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ uint16_t altSvcFieldValueLen = static_cast<uint16_t>(self->mInputFrameDataSize) - 2U - originLen;
+ LOG3(("Http2Session::RecvAltSvc %p frame originLen=%u altSvcFieldValueLen=%u\n",
+ self, originLen, altSvcFieldValueLen));
+
+ if (self->mInputFrameDataSize > 2000) {
+ LOG3(("Http2Session::RecvAltSvc %p frame too large to parse sensibly", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ nsAutoCString origin;
+ bool impliedOrigin = true;
+ if (originLen) {
+ origin.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2, originLen);
+ impliedOrigin = false;
+ }
+
+ nsAutoCString altSvcFieldValue;
+ if (altSvcFieldValueLen) {
+ altSvcFieldValue.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2 + originLen,
+ altSvcFieldValueLen);
+ }
+
+ if (altSvcFieldValue.IsEmpty() || !nsHttp::IsReasonableHeaderValue(altSvcFieldValue)) {
+ LOG(("Http2Session %p Alt-Svc Response Header seems unreasonable - skipping\n", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (self->mInputFrameID & 1) {
+ // pulled streams apply to the origin of the pulled stream.
+ // If the origin field is filled in the frame, the frame should be ignored
+ if (!origin.IsEmpty()) {
+ LOG(("Http2Session %p Alt-Svc pulled stream has non empty origin\n", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (NS_FAILED(self->SetInputFrameDataStream(self->mInputFrameID)) ||
+ !self->mInputFrameDataStream->Transaction() ||
+ !self->mInputFrameDataStream->Transaction()->RequestHead()) {
+ LOG3(("Http2Session::RecvAltSvc %p got frame w/o origin on invalid stream", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ self->mInputFrameDataStream->Transaction()->RequestHead()->Origin(origin);
+ } else if (!self->mInputFrameID) {
+ // ID 0 streams must supply their own origin
+ if (origin.IsEmpty()) {
+ LOG(("Http2Session %p Alt-Svc Stream 0 has empty origin\n", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+ } else {
+ // handling of push streams is not defined. Let's ignore it
+ LOG(("Http2Session %p Alt-Svc received on pushed stream - ignoring\n", self));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ RefPtr<nsHttpConnectionInfo> ci(self->ConnectionInfo());
+ if (!self->mConnection || !ci) {
+ LOG3(("Http2Session::RecvAltSvc %p no connection or conninfo for %d", self,
+ self->mInputFrameID));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+
+ if (!impliedOrigin) {
+ bool okToReroute = true;
+ nsCOMPtr<nsISupports> securityInfo;
+ self->mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
+ nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
+ if (!ssl) {
+ okToReroute = false;
+ }
+
+ // a little off main thread origin parser. This is a non critical function because
+ // any alternate route created has to be verified anyhow
+ nsAutoCString specifiedOriginHost;
+ if (origin.EqualsIgnoreCase("https://", 8)) {
+ specifiedOriginHost.Assign(origin.get() + 8, origin.Length() - 8);
+ } else if (origin.EqualsIgnoreCase("http://", 7)) {
+ specifiedOriginHost.Assign(origin.get() + 7, origin.Length() - 7);
+ }
+
+ int32_t colonOffset = specifiedOriginHost.FindCharInSet(":", 0);
+ if (colonOffset != kNotFound) {
+ specifiedOriginHost.Truncate(colonOffset);
+ }
+
+ if (okToReroute) {
+ ssl->IsAcceptableForHost(specifiedOriginHost, &okToReroute);
+ }
+
+ if (!okToReroute) {
+ LOG3(("Http2Session::RecvAltSvc %p can't reroute non-authoritative origin %s",
+ self, origin.BeginReading()));
+ self->ResetDownstreamState();
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<nsISupports> callbacks;
+ self->mConnection->GetSecurityInfo(getter_AddRefs(callbacks));
+ nsCOMPtr<nsIInterfaceRequestor> irCallbacks = do_QueryInterface(callbacks);
+
+ RefPtr<UpdateAltSvcEvent> event =
+ new UpdateAltSvcEvent(altSvcFieldValue, origin, ci, irCallbacks);
+ NS_DispatchToMainThread(event);
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpTransaction. It is expected that nsHttpConnection is the caller
+// of these methods
+//-----------------------------------------------------------------------------
+
+void
+Http2Session::OnTransportStatus(nsITransport* aTransport,
+ nsresult aStatus, int64_t aProgress)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ switch (aStatus) {
+ // These should appear only once, deliver to the first
+ // transaction on the session.
+ case NS_NET_STATUS_RESOLVING_HOST:
+ case NS_NET_STATUS_RESOLVED_HOST:
+ case NS_NET_STATUS_CONNECTING_TO:
+ case NS_NET_STATUS_CONNECTED_TO:
+ {
+ Http2Stream *target = mStreamIDHash.Get(1);
+ nsAHttpTransaction *transaction = target ? target->Transaction() : nullptr;
+ if (transaction)
+ transaction->OnTransportStatus(aTransport, aStatus, aProgress);
+ break;
+ }
+
+ default:
+ // The other transport events are ignored here because there is no good
+ // way to map them to the right transaction in http/2. Instead, the events
+ // are generated again from the http/2 code and passed directly to the
+ // correct transaction.
+
+ // NS_NET_STATUS_SENDING_TO:
+ // This is generated by the socket transport when (part) of
+ // a transaction is written out
+ //
+ // There is no good way to map it to the right transaction in http/2,
+ // so it is ignored here and generated separately when the request
+ // is sent from Http2Stream::TransmitFrame
+
+ // NS_NET_STATUS_WAITING_FOR:
+ // Created by nsHttpConnection when the request has been totally sent.
+ // There is no good way to map it to the right transaction in http/2,
+ // so it is ignored here and generated separately when the same
+ // condition is complete in Http2Stream when there is no more
+ // request body left to be transmitted.
+
+ // NS_NET_STATUS_RECEIVING_FROM
+ // Generated in session whenever we read a data frame or a HEADERS
+ // that can be attributed to a particular stream/transaction
+
+ break;
+ }
+}
+
+// ReadSegments() is used to write data to the network. Generally, HTTP
+// request data is pulled from the approriate transaction and
+// converted to http/2 data. Sometimes control data like window-update are
+// generated instead.
+
+nsresult
+Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead, bool *again)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ MOZ_ASSERT(!mSegmentReader || !reader || (mSegmentReader == reader),
+ "Inconsistent Write Function Callback");
+
+ nsresult rv = ConfirmTLSProfile();
+ if (NS_FAILED(rv)) {
+ if (mGoAwayReason == INADEQUATE_SECURITY) {
+ LOG3(("Http2Session::ReadSegments %p returning INADEQUATE_SECURITY %x",
+ this, NS_ERROR_NET_INADEQUATE_SECURITY));
+ rv = NS_ERROR_NET_INADEQUATE_SECURITY;
+ }
+ return rv;
+ }
+
+ if (reader)
+ mSegmentReader = reader;
+
+ *countRead = 0;
+
+ LOG3(("Http2Session::ReadSegments %p", this));
+
+ Http2Stream *stream = static_cast<Http2Stream *>(mReadyForWrite.PopFront());
+ if (!stream) {
+ LOG3(("Http2Session %p could not identify a stream to write; suspending.",
+ this));
+ FlushOutputQueue();
+ SetWriteCallbacks();
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ LOG3(("Http2Session %p will write from Http2Stream %p 0x%X "
+ "block-input=%d block-output=%d\n", this, stream, stream->StreamID(),
+ stream->RequestBlockedOnRead(), stream->BlockedOnRwin()));
+
+ rv = stream->ReadSegments(this, count, countRead);
+
+ // Not every permutation of stream->ReadSegents produces data (and therefore
+ // tries to flush the output queue) - SENDING_FIN_STREAM can be an example
+ // of that. But we might still have old data buffered that would be good
+ // to flush.
+ FlushOutputQueue();
+
+ // Allow new server reads - that might be data or control information
+ // (e.g. window updates or http replies) that are responses to these writes
+ ResumeRecv();
+
+ if (stream->RequestBlockedOnRead()) {
+
+ // We are blocked waiting for input - either more http headers or
+ // any request body data. When more data from the request stream
+ // becomes available the httptransaction will call conn->ResumeSend().
+
+ LOG3(("Http2Session::ReadSegments %p dealing with block on read", this));
+
+ // call readsegments again if there are other streams ready
+ // to run in this session
+ if (GetWriteQueueSize()) {
+ rv = NS_OK;
+ } else {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ SetWriteCallbacks();
+ return rv;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::ReadSegments %p may return FAIL code %X",
+ this, rv));
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ return rv;
+ }
+
+ CleanupStream(stream, rv, CANCEL_ERROR);
+ if (SoftStreamError(rv)) {
+ LOG3(("Http2Session::ReadSegments %p soft error override\n", this));
+ *again = false;
+ SetWriteCallbacks();
+ rv = NS_OK;
+ }
+ return rv;
+ }
+
+ if (*countRead > 0) {
+ LOG3(("Http2Session::ReadSegments %p stream=%p countread=%d",
+ this, stream, *countRead));
+ mReadyForWrite.Push(stream);
+ SetWriteCallbacks();
+ return rv;
+ }
+
+ if (stream->BlockedOnRwin()) {
+ LOG3(("Http2Session %p will stream %p 0x%X suspended for flow control\n",
+ this, stream, stream->StreamID()));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ LOG3(("Http2Session::ReadSegments %p stream=%p stream send complete",
+ this, stream));
+
+ // call readsegments again if there are other streams ready
+ // to go in this session
+ SetWriteCallbacks();
+
+ return rv;
+}
+
+nsresult
+Http2Session::ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead)
+{
+ bool again = false;
+ return ReadSegmentsAgain(reader, count, countRead, &again);
+}
+
+nsresult
+Http2Session::ReadyToProcessDataFrame(enum internalStateType newState)
+{
+ MOZ_ASSERT(newState == PROCESSING_DATA_FRAME ||
+ newState == DISCARDING_DATA_FRAME_PADDING);
+ ChangeDownstreamState(newState);
+
+ Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD,
+ mInputFrameDataSize >> 10);
+ mLastDataReadEpoch = mLastReadEpoch;
+
+ if (!mInputFrameID) {
+ LOG3(("Http2Session::ReadyToProcessDataFrame %p data frame stream 0\n",
+ this));
+ RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
+ }
+
+ nsresult rv = SetInputFrameDataStream(mInputFrameID);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
+ "failed. probably due to verification.\n", this, mInputFrameID));
+ return rv;
+ }
+ if (!mInputFrameDataStream) {
+ LOG3(("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
+ "failed. Next = 0x%X", this, mInputFrameID, mNextStreamID));
+ if (mInputFrameID >= mNextStreamID)
+ GenerateRstStream(PROTOCOL_ERROR, mInputFrameID);
+ ChangeDownstreamState(DISCARDING_DATA_FRAME);
+ } else if (mInputFrameDataStream->RecvdFin() ||
+ mInputFrameDataStream->RecvdReset() ||
+ mInputFrameDataStream->SentReset()) {
+ LOG3(("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
+ "Data arrived for already server closed stream.\n",
+ this, mInputFrameID));
+ if (mInputFrameDataStream->RecvdFin() || mInputFrameDataStream->RecvdReset())
+ GenerateRstStream(STREAM_CLOSED_ERROR, mInputFrameID);
+ ChangeDownstreamState(DISCARDING_DATA_FRAME);
+ } else if (mInputFrameDataSize == 0 && !mInputFrameFinal) {
+ // Only if non-final because the stream properly handles final frames of any
+ // size, and we want the stream to be able to notice its own end flag.
+ LOG3(("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
+ "Ignoring 0-length non-terminal data frame.", this, mInputFrameID));
+ ChangeDownstreamState(DISCARDING_DATA_FRAME);
+ }
+
+ LOG3(("Start Processing Data Frame. "
+ "Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d",
+ this, mInputFrameID, mInputFrameDataStream, mInputFrameFinal,
+ mInputFrameDataSize));
+ UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize);
+
+ if (mInputFrameDataStream) {
+ mInputFrameDataStream->SetRecvdData(true);
+ }
+
+ return NS_OK;
+}
+
+// WriteSegments() is used to read data off the socket. Generally this is
+// just the http2 frame header and from there the appropriate *Stream
+// is identified from the Stream-ID. The http transaction associated with
+// that read then pulls in the data directly, which it will feed to
+// OnWriteSegment(). That function will gateway it into http and feed
+// it to the appropriate transaction.
+
+// we call writer->OnWriteSegment via NetworkRead() to get a http2 header..
+// and decide if it is data or control.. if it is control, just deal with it.
+// if it is data, identify the stream
+// call stream->WriteSegments which can call this::OnWriteSegment to get the
+// data. It always gets full frames if they are part of the stream
+
+nsresult
+Http2Session::WriteSegmentsAgain(nsAHttpSegmentWriter *writer,
+ uint32_t count, uint32_t *countWritten,
+ bool *again)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ LOG3(("Http2Session::WriteSegments %p InternalState %X\n",
+ this, mDownstreamState));
+
+ *countWritten = 0;
+
+ if (mClosed)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = ConfirmTLSProfile();
+ if (NS_FAILED(rv))
+ return rv;
+
+ SetWriteCallbacks();
+
+ // If there are http transactions attached to a push stream with filled buffers
+ // trigger that data pump here. This only reads from buffers (not the network)
+ // so mDownstreamState doesn't matter.
+ Http2Stream *pushConnectedStream =
+ static_cast<Http2Stream *>(mPushesReadyForRead.PopFront());
+ if (pushConnectedStream) {
+ return ProcessConnectedPush(pushConnectedStream, writer, count, countWritten);
+ }
+
+ // feed gecko channels that previously stopped consuming data
+ // only take data from stored buffers
+ Http2Stream *slowConsumer =
+ static_cast<Http2Stream *>(mSlowConsumersReadyForRead.PopFront());
+ if (slowConsumer) {
+ internalStateType savedState = mDownstreamState;
+ mDownstreamState = NOT_USING_NETWORK;
+ rv = ProcessSlowConsumer(slowConsumer, writer, count, countWritten);
+ mDownstreamState = savedState;
+ return rv;
+ }
+
+ // The BUFFERING_OPENING_SETTINGS state is just like any BUFFERING_FRAME_HEADER
+ // except the only frame type it will allow is SETTINGS
+
+ // The session layer buffers the leading 8 byte header of every frame.
+ // Non-Data frames are then buffered for their full length, but data
+ // frames (type 0) are passed through to the http stack unprocessed
+
+ if (mDownstreamState == BUFFERING_OPENING_SETTINGS ||
+ mDownstreamState == BUFFERING_FRAME_HEADER) {
+ // The first 9 bytes of every frame is header information that
+ // we are going to want to strip before passing to http. That is
+ // true of both control and data packets.
+
+ MOZ_ASSERT(mInputFrameBufferUsed < kFrameHeaderBytes,
+ "Frame Buffer Used Too Large for State");
+
+ rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
+ kFrameHeaderBytes - mInputFrameBufferUsed, countWritten);
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p buffering frame header read failure %x\n",
+ this, rv));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+ rv = NS_OK;
+ return rv;
+ }
+
+ LogIO(this, nullptr, "Reading Frame Header",
+ &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);
+
+ mInputFrameBufferUsed += *countWritten;
+
+ if (mInputFrameBufferUsed < kFrameHeaderBytes)
+ {
+ LOG3(("Http2Session::WriteSegments %p "
+ "BUFFERING FRAME HEADER incomplete size=%d",
+ this, mInputFrameBufferUsed));
+ return rv;
+ }
+
+ // 3 bytes of length, 1 type byte, 1 flag byte, 1 unused bit, 31 bits of ID
+ uint8_t totallyWastedByte = mInputFrameBuffer.get()[0];
+ mInputFrameDataSize = NetworkEndian::readUint16(
+ mInputFrameBuffer.get() + 1);
+ if (totallyWastedByte || (mInputFrameDataSize > kMaxFrameData)) {
+ LOG3(("Got frame too large 0x%02X%04X", totallyWastedByte, mInputFrameDataSize));
+ RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
+ }
+ mInputFrameType = *reinterpret_cast<uint8_t *>(mInputFrameBuffer.get() + kFrameLengthBytes);
+ mInputFrameFlags = *reinterpret_cast<uint8_t *>(mInputFrameBuffer.get() + kFrameLengthBytes + kFrameTypeBytes);
+ mInputFrameID = NetworkEndian::readUint32(
+ mInputFrameBuffer.get() + kFrameLengthBytes + kFrameTypeBytes + kFrameFlagBytes);
+ mInputFrameID &= 0x7fffffff;
+ mInputFrameDataRead = 0;
+
+ if (mInputFrameType == FRAME_TYPE_DATA || mInputFrameType == FRAME_TYPE_HEADERS) {
+ mInputFrameFinal = mInputFrameFlags & kFlag_END_STREAM;
+ } else {
+ mInputFrameFinal = 0;
+ }
+
+ mPaddingLength = 0;
+
+ LOG3(("Http2Session::WriteSegments[%p::%x] Frame Header Read "
+ "type %X data len %u flags %x id 0x%X",
+ this, mSerial, mInputFrameType, mInputFrameDataSize, mInputFrameFlags,
+ mInputFrameID));
+
+ // if mExpectedHeaderID is non 0, it means this frame must be a CONTINUATION of
+ // a HEADERS frame with a matching ID (section 6.2)
+ if (mExpectedHeaderID &&
+ ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
+ (mExpectedHeaderID != mInputFrameID))) {
+ LOG3(("Expected CONINUATION OF HEADERS for ID 0x%X\n", mExpectedHeaderID));
+ RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
+ }
+
+ // if mExpectedPushPromiseID is non 0, it means this frame must be a
+ // CONTINUATION of a PUSH_PROMISE with a matching ID (section 6.2)
+ if (mExpectedPushPromiseID &&
+ ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
+ (mExpectedPushPromiseID != mInputFrameID))) {
+ LOG3(("Expected CONTINUATION of PUSH PROMISE for ID 0x%X\n",
+ mExpectedPushPromiseID));
+ RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
+ }
+
+ if (mDownstreamState == BUFFERING_OPENING_SETTINGS &&
+ mInputFrameType != FRAME_TYPE_SETTINGS) {
+ LOG3(("First Frame Type Must Be Settings\n"));
+ RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
+ }
+
+ if (mInputFrameType != FRAME_TYPE_DATA) { // control frame
+ EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + kFrameHeaderBytes,
+ kFrameHeaderBytes, mInputFrameBufferSize);
+ ChangeDownstreamState(BUFFERING_CONTROL_FRAME);
+ } else if (mInputFrameFlags & kFlag_PADDED) {
+ ChangeDownstreamState(PROCESSING_DATA_FRAME_PADDING_CONTROL);
+ } else {
+ rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ if (mDownstreamState == PROCESSING_DATA_FRAME_PADDING_CONTROL) {
+ MOZ_ASSERT(mInputFrameFlags & kFlag_PADDED,
+ "Processing padding control on unpadded frame");
+
+ MOZ_ASSERT(mInputFrameBufferUsed < (kFrameHeaderBytes + 1),
+ "Frame buffer used too large for state");
+
+ rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
+ (kFrameHeaderBytes + 1) - mInputFrameBufferUsed,
+ countWritten);
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p buffering data frame padding control read failure %x\n",
+ this, rv));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+ rv = NS_OK;
+ return rv;
+ }
+
+ LogIO(this, nullptr, "Reading Data Frame Padding Control",
+ &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);
+
+ mInputFrameBufferUsed += *countWritten;
+
+ if (mInputFrameBufferUsed - kFrameHeaderBytes < 1) {
+ LOG3(("Http2Session::WriteSegments %p "
+ "BUFFERING DATA FRAME CONTROL PADDING incomplete size=%d",
+ this, mInputFrameBufferUsed - 8));
+ return rv;
+ }
+
+ ++mInputFrameDataRead;
+
+ char *control = &mInputFrameBuffer[kFrameHeaderBytes];
+ mPaddingLength = static_cast<uint8_t>(*control);
+
+ LOG3(("Http2Session::WriteSegments %p stream 0x%X mPaddingLength=%d", this,
+ mInputFrameID, mPaddingLength));
+
+ if (1U + mPaddingLength > mInputFrameDataSize) {
+ LOG3(("Http2Session::WriteSegments %p stream 0x%X padding too large for "
+ "frame", this, mInputFrameID));
+ RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
+ } else if (1U + mPaddingLength == mInputFrameDataSize) {
+ // This frame consists entirely of padding, we can just discard it
+ LOG3(("Http2Session::WriteSegments %p stream 0x%X frame with only padding",
+ this, mInputFrameID));
+ rv = ReadyToProcessDataFrame(DISCARDING_DATA_FRAME_PADDING);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ LOG3(("Http2Session::WriteSegments %p stream 0x%X ready to read HTTP data",
+ this, mInputFrameID));
+ rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) {
+ nsresult streamCleanupCode;
+
+ // There is no bounds checking on the error code.. we provide special
+ // handling for a couple of cases and all others (including unknown) are
+ // equivalent to cancel.
+ if (mDownstreamRstReason == REFUSED_STREAM_ERROR) {
+ streamCleanupCode = NS_ERROR_NET_RESET; // can retry this 100% safely
+ mInputFrameDataStream->Transaction()->ReuseConnectionOnRestartOK(true);
+ } else if (mDownstreamRstReason == HTTP_1_1_REQUIRED) {
+ streamCleanupCode = NS_ERROR_NET_RESET;
+ mInputFrameDataStream->Transaction()->ReuseConnectionOnRestartOK(true);
+ mInputFrameDataStream->Transaction()->DisableSpdy();
+ } else {
+ streamCleanupCode = mInputFrameDataStream->RecvdData() ?
+ NS_ERROR_NET_PARTIAL_TRANSFER :
+ NS_ERROR_NET_INTERRUPT;
+ }
+
+ if (mDownstreamRstReason == COMPRESSION_ERROR) {
+ mShouldGoAway = true;
+ }
+
+ // mInputFrameDataStream is reset by ChangeDownstreamState
+ Http2Stream *stream = mInputFrameDataStream;
+ ResetDownstreamState();
+ LOG3(("Http2Session::WriteSegments cleanup stream on recv of rst "
+ "session=%p stream=%p 0x%X\n", this, stream,
+ stream ? stream->StreamID() : 0));
+ CleanupStream(stream, streamCleanupCode, CANCEL_ERROR);
+ return NS_OK;
+ }
+
+ if (mDownstreamState == PROCESSING_DATA_FRAME ||
+ mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
+
+ // The cleanup stream should only be set while stream->WriteSegments is
+ // on the stack and then cleaned up in this code block afterwards.
+ MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly");
+ mNeedsCleanup = nullptr; /* just in case */
+
+ uint32_t streamID = mInputFrameDataStream->StreamID();
+ mSegmentWriter = writer;
+ rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
+ mSegmentWriter = nullptr;
+
+ mLastDataReadEpoch = mLastReadEpoch;
+
+ if (SoftStreamError(rv)) {
+ // This will happen when the transaction figures out it is EOF, generally
+ // due to a content-length match being made. Return OK from this function
+ // otherwise the whole session would be torn down.
+
+ // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state
+ // back to PROCESSING_DATA_FRAME where we came from
+ mDownstreamState = PROCESSING_DATA_FRAME;
+
+ if (mInputFrameDataRead == mInputFrameDataSize)
+ ResetDownstreamState();
+ LOG3(("Http2Session::WriteSegments session=%p id 0x%X "
+ "needscleanup=%p. cleanup stream based on "
+ "stream->writeSegments returning code %x\n",
+ this, streamID, mNeedsCleanup, rv));
+ MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup->StreamID() == streamID);
+ CleanupStream(streamID, NS_OK, CANCEL_ERROR);
+ mNeedsCleanup = nullptr;
+ *again = false;
+ ResumeRecv();
+ return NS_OK;
+ }
+
+ if (mNeedsCleanup) {
+ LOG3(("Http2Session::WriteSegments session=%p stream=%p 0x%X "
+ "cleanup stream based on mNeedsCleanup.\n",
+ this, mNeedsCleanup, mNeedsCleanup ? mNeedsCleanup->StreamID() : 0));
+ CleanupStream(mNeedsCleanup, NS_OK, CANCEL_ERROR);
+ mNeedsCleanup = nullptr;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p data frame read failure %x\n", this, rv));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+ rv = NS_OK;
+ }
+
+ return rv;
+ }
+
+ if (mDownstreamState == DISCARDING_DATA_FRAME ||
+ mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
+ char trash[4096];
+ uint32_t discardCount = std::min(mInputFrameDataSize - mInputFrameDataRead,
+ 4096U);
+ LOG3(("Http2Session::WriteSegments %p trying to discard %d bytes of data",
+ this, discardCount));
+
+ if (!discardCount && mDownstreamState == DISCARDING_DATA_FRAME) {
+ // Only do this short-cirtuit if we're not discarding a pure padding
+ // frame, as we need to potentially handle the stream FIN in those cases.
+ // See bug 1381016 comment 36 for more details.
+ ResetDownstreamState();
+ ResumeRecv();
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = NetworkRead(writer, trash, discardCount, countWritten);
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p discard frame read failure %x\n", this, rv));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+ rv = NS_OK;
+ return rv;
+ }
+
+ LogIO(this, nullptr, "Discarding Frame", trash, *countWritten);
+
+ mInputFrameDataRead += *countWritten;
+
+ if (mInputFrameDataRead == mInputFrameDataSize) {
+ Http2Stream *streamToCleanup = nullptr;
+ if (mInputFrameFinal) {
+ streamToCleanup = mInputFrameDataStream;
+ }
+
+ ResetDownstreamState();
+
+ if (streamToCleanup) {
+ CleanupStream(streamToCleanup, NS_OK, CANCEL_ERROR);
+ }
+ }
+ return rv;
+ }
+
+ if (mDownstreamState != BUFFERING_CONTROL_FRAME) {
+ MOZ_ASSERT(false); // this cannot happen
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mInputFrameBufferUsed == kFrameHeaderBytes, "Frame Buffer Header Not Present");
+ MOZ_ASSERT(mInputFrameDataSize + kFrameHeaderBytes <= mInputFrameBufferSize,
+ "allocation for control frame insufficient");
+
+ rv = NetworkRead(writer, &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead],
+ mInputFrameDataSize - mInputFrameDataRead, countWritten);
+
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Session %p buffering control frame read failure %x\n",
+ this, rv));
+ // maybe just blocked reading from network
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+ rv = NS_OK;
+ return rv;
+ }
+
+ LogIO(this, nullptr, "Reading Control Frame",
+ &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead], *countWritten);
+
+ mInputFrameDataRead += *countWritten;
+
+ if (mInputFrameDataRead != mInputFrameDataSize)
+ return NS_OK;
+
+ MOZ_ASSERT(mInputFrameType != FRAME_TYPE_DATA);
+ if (mInputFrameType < FRAME_TYPE_LAST) {
+ rv = sControlFunctions[mInputFrameType](this);
+ } else {
+ // Section 4.1 requires this to be ignored; though protocol_error would
+ // be better
+ LOG3(("Http2Session %p unknown frame type %x ignored\n",
+ this, mInputFrameType));
+ ResetDownstreamState();
+ rv = NS_OK;
+ }
+
+ MOZ_ASSERT(NS_FAILED(rv) ||
+ mDownstreamState != BUFFERING_CONTROL_FRAME,
+ "Control Handler returned OK but did not change state");
+
+ if (mShouldGoAway && !mStreamTransactionHash.Count())
+ Close(NS_OK);
+ return rv;
+}
+
+nsresult
+Http2Session::WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count, uint32_t *countWritten)
+{
+ bool again = false;
+ return WriteSegmentsAgain(writer, count, countWritten, &again);
+}
+
+nsresult
+Http2Session::ProcessConnectedPush(Http2Stream *pushConnectedStream,
+ nsAHttpSegmentWriter * writer,
+ uint32_t count, uint32_t *countWritten)
+{
+ LOG3(("Http2Session::ProcessConnectedPush %p 0x%X\n",
+ this, pushConnectedStream->StreamID()));
+ mSegmentWriter = writer;
+ nsresult rv = pushConnectedStream->WriteSegments(this, count, countWritten);
+ mSegmentWriter = nullptr;
+
+ // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK
+ // so we need this check to determine the truth.
+ if (NS_SUCCEEDED(rv) && !*countWritten &&
+ pushConnectedStream->PushSource() &&
+ pushConnectedStream->PushSource()->GetPushComplete()) {
+ rv = NS_BASE_STREAM_CLOSED;
+ }
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ CleanupStream(pushConnectedStream, NS_OK, CANCEL_ERROR);
+ rv = NS_OK;
+ }
+
+ // if we return OK to nsHttpConnection it will use mSocketInCondition
+ // to determine whether to schedule more reads, incorrectly
+ // assuming that nsHttpConnection::OnSocketWrite() was called.
+ if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ ResumeRecv();
+ }
+ return rv;
+}
+
+nsresult
+Http2Session::ProcessSlowConsumer(Http2Stream *slowConsumer,
+ nsAHttpSegmentWriter * writer,
+ uint32_t count, uint32_t *countWritten)
+{
+ LOG3(("Http2Session::ProcessSlowConsumer %p 0x%X\n",
+ this, slowConsumer->StreamID()));
+ mSegmentWriter = writer;
+ nsresult rv = slowConsumer->WriteSegments(this, count, countWritten);
+ mSegmentWriter = nullptr;
+ LOG3(("Http2Session::ProcessSlowConsumer Writesegments %p 0x%X rv %X %d\n",
+ this, slowConsumer->StreamID(), rv, *countWritten));
+ if (NS_SUCCEEDED(rv) && !*countWritten && slowConsumer->RecvdFin()) {
+ rv = NS_BASE_STREAM_CLOSED;
+ }
+
+ if (NS_SUCCEEDED(rv) && (*countWritten > 0)) {
+ // There have been buffered bytes successfully fed into the
+ // formerly blocked consumer. Repeat until buffer empty or
+ // consumer is blocked again.
+ UpdateLocalRwin(slowConsumer, 0);
+ ConnectSlowConsumer(slowConsumer);
+ }
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ CleanupStream(slowConsumer, NS_OK, CANCEL_ERROR);
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+void
+Http2Session::UpdateLocalStreamWindow(Http2Stream *stream, uint32_t bytes)
+{
+ if (!stream) // this is ok - it means there was a data frame for a rst stream
+ return;
+
+ // If this data packet was not for a valid or live stream then there
+ // is no reason to mess with the flow control
+ if (!stream || stream->RecvdFin() || stream->RecvdReset() ||
+ mInputFrameFinal) {
+ return;
+ }
+
+ stream->DecrementClientReceiveWindow(bytes);
+
+ // Don't necessarily ack every data packet. Only do it
+ // after a significant amount of data.
+ uint64_t unacked = stream->LocalUnAcked();
+ int64_t localWindow = stream->ClientReceiveWindow();
+
+ LOG3(("Http2Session::UpdateLocalStreamWindow this=%p id=0x%X newbytes=%u "
+ "unacked=%llu localWindow=%lld\n",
+ this, stream->StreamID(), bytes, unacked, localWindow));
+
+ if (!unacked)
+ return;
+
+ if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold))
+ return;
+
+ if (!stream->HasSink()) {
+ LOG3(("Http2Session::UpdateLocalStreamWindow %p 0x%X Pushed Stream Has No Sink\n",
+ this, stream->StreamID()));
+ return;
+ }
+
+ // Generate window updates directly out of session instead of the stream
+ // in order to avoid queue delays in getting the 'ACK' out.
+ uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU;
+
+ LOG3(("Http2Session::UpdateLocalStreamWindow Ack this=%p id=0x%X acksize=%d\n",
+ this, stream->StreamID(), toack));
+ stream->IncrementClientReceiveWindow(toack);
+ if (toack == 0) {
+ // Ensure we never send an illegal 0 window update
+ return;
+ }
+
+ // room for this packet needs to be ensured before calling this function
+ char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ mOutputQueueUsed += kFrameHeaderBytes + 4;
+ MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
+
+ CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, stream->StreamID());
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);
+
+ LogIO(this, stream, "Stream Window Update", packet, kFrameHeaderBytes + 4);
+ // dont flush here, this write can commonly be coalesced with a
+ // session window update to immediately follow.
+}
+
+void
+Http2Session::UpdateLocalSessionWindow(uint32_t bytes)
+{
+ if (!bytes)
+ return;
+
+ mLocalSessionWindow -= bytes;
+
+ LOG3(("Http2Session::UpdateLocalSessionWindow this=%p newbytes=%u "
+ "localWindow=%lld\n", this, bytes, mLocalSessionWindow));
+
+ // Don't necessarily ack every data packet. Only do it
+ // after a significant amount of data.
+ if ((mLocalSessionWindow > (mInitialRwin - kMinimumToAck)) &&
+ (mLocalSessionWindow > kEmergencyWindowThreshold))
+ return;
+
+ // Only send max bits of window updates at a time.
+ uint64_t toack64 = mInitialRwin - mLocalSessionWindow;
+ uint32_t toack = (toack64 <= 0x7fffffffU) ? toack64 : 0x7fffffffU;
+
+ LOG3(("Http2Session::UpdateLocalSessionWindow Ack this=%p acksize=%u\n",
+ this, toack));
+ mLocalSessionWindow += toack;
+
+ if (toack == 0) {
+ // Ensure we never send an illegal 0 window update
+ return;
+ }
+
+ // room for this packet needs to be ensured before calling this function
+ char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+ mOutputQueueUsed += kFrameHeaderBytes + 4;
+ MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
+
+ CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
+ NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);
+
+ LogIO(this, nullptr, "Session Window Update", packet, kFrameHeaderBytes + 4);
+ // dont flush here, this write can commonly be coalesced with others
+}
+
+void
+Http2Session::UpdateLocalRwin(Http2Stream *stream, uint32_t bytes)
+{
+ // make sure there is room for 2 window updates even though
+ // we may not generate any.
+ EnsureOutputBuffer(2 * (kFrameHeaderBytes + 4));
+
+ UpdateLocalStreamWindow(stream, bytes);
+ UpdateLocalSessionWindow(bytes);
+ FlushOutputQueue();
+}
+
+void
+Http2Session::Close(nsresult aReason)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mClosed)
+ return;
+
+ LOG3(("Http2Session::Close %p %X", this, aReason));
+
+ mClosed = true;
+
+ Shutdown();
+
+ mStreamIDHash.Clear();
+ mStreamTransactionHash.Clear();
+
+ uint32_t goAwayReason;
+ if (mGoAwayReason != NO_HTTP_ERROR) {
+ goAwayReason = mGoAwayReason;
+ } else if (NS_SUCCEEDED(aReason)) {
+ goAwayReason = NO_HTTP_ERROR;
+ } else if (aReason == NS_ERROR_ILLEGAL_VALUE) {
+ goAwayReason = PROTOCOL_ERROR;
+ } else {
+ goAwayReason = INTERNAL_ERROR;
+ }
+ GenerateGoAway(goAwayReason);
+ mConnection = nullptr;
+ mSegmentReader = nullptr;
+ mSegmentWriter = nullptr;
+}
+
+nsHttpConnectionInfo *
+Http2Session::ConnectionInfo()
+{
+ RefPtr<nsHttpConnectionInfo> ci;
+ GetConnectionInfo(getter_AddRefs(ci));
+ return ci.get();
+}
+
+void
+Http2Session::CloseTransaction(nsAHttpTransaction *aTransaction,
+ nsresult aResult)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::CloseTransaction %p %p %x", this, aTransaction, aResult));
+
+ // Generally this arrives as a cancel event from the connection manager.
+
+ // need to find the stream and call CleanupStream() on it.
+ Http2Stream *stream = mStreamTransactionHash.Get(aTransaction);
+ if (!stream) {
+ LOG3(("Http2Session::CloseTransaction %p %p %x - not found.",
+ this, aTransaction, aResult));
+ return;
+ }
+ LOG3(("Http2Session::CloseTransaction probably a cancel. "
+ "this=%p, trans=%p, result=%x, streamID=0x%X stream=%p",
+ this, aTransaction, aResult, stream->StreamID(), stream));
+ CleanupStream(stream, aResult, CANCEL_ERROR);
+ ResumeRecv();
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+nsresult
+Http2Session::OnReadSegment(const char *buf,
+ uint32_t count, uint32_t *countRead)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsresult rv;
+
+ // If we can release old queued data then we can try and write the new
+ // data directly to the network without using the output queue at all
+ if (mOutputQueueUsed)
+ FlushOutputQueue();
+
+ if (!mOutputQueueUsed && mSegmentReader) {
+ // try and write directly without output queue
+ rv = mSegmentReader->OnReadSegment(buf, count, countRead);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ *countRead = 0;
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (*countRead < count) {
+ uint32_t required = count - *countRead;
+ // assuming a commitment() happened, this ensurebuffer is a nop
+ // but just in case the queuesize is too small for the required data
+ // call ensurebuffer().
+ EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize);
+ memcpy(mOutputQueueBuffer.get(), buf + *countRead, required);
+ mOutputQueueUsed = required;
+ }
+
+ *countRead = count;
+ return NS_OK;
+ }
+
+ // At this point we are going to buffer the new data in the output
+ // queue if it fits. By coalescing multiple small submissions into one larger
+ // buffer we can get larger writes out to the network later on.
+
+ // This routine should not be allowed to fill up the output queue
+ // all on its own - at least kQueueReserved bytes are always left
+ // for other routines to use - but this is an all-or-nothing function,
+ // so if it will not all fit just return WOULD_BLOCK
+
+ if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved))
+ return NS_BASE_STREAM_WOULD_BLOCK;
+
+ memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count);
+ mOutputQueueUsed += count;
+ *countRead = count;
+
+ FlushOutputQueue();
+
+ return NS_OK;
+}
+
+nsresult
+Http2Session::CommitToSegmentSize(uint32_t count, bool forceCommitment)
+{
+ if (mOutputQueueUsed)
+ FlushOutputQueue();
+
+ // would there be enough room to buffer this if needed?
+ if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
+ return NS_OK;
+
+ // if we are using part of our buffers already, try again later unless
+ // forceCommitment is set.
+ if (mOutputQueueUsed && !forceCommitment)
+ return NS_BASE_STREAM_WOULD_BLOCK;
+
+ if (mOutputQueueUsed) {
+ // normally we avoid the memmove of RealignOutputQueue, but we'll try
+ // it if forceCommitment is set before growing the buffer.
+ RealignOutputQueue();
+
+ // is there enough room now?
+ if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
+ return NS_OK;
+ }
+
+ // resize the buffers as needed
+ EnsureOutputBuffer(count + kQueueReserved);
+
+ MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved),
+ "buffer not as large as expected");
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+nsresult
+Http2Session::OnWriteSegment(char *buf,
+ uint32_t count, uint32_t *countWritten)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsresult rv;
+
+ if (!mSegmentWriter) {
+ // the only way this could happen would be if Close() were called on the
+ // stack with WriteSegments()
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mDownstreamState == NOT_USING_NETWORK ||
+ mDownstreamState == BUFFERING_FRAME_HEADER ||
+ mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ if (mDownstreamState == PROCESSING_DATA_FRAME) {
+
+ if (mInputFrameFinal &&
+ mInputFrameDataRead == mInputFrameDataSize) {
+ *countWritten = 0;
+ SetNeedsCleanup();
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ count = std::min(count, mInputFrameDataSize - mInputFrameDataRead);
+ rv = NetworkRead(mSegmentWriter, buf, count, countWritten);
+ if (NS_FAILED(rv))
+ return rv;
+
+ LogIO(this, mInputFrameDataStream, "Reading Data Frame",
+ buf, *countWritten);
+
+ mInputFrameDataRead += *countWritten;
+ if (mPaddingLength && (mInputFrameDataSize - mInputFrameDataRead <= mPaddingLength)) {
+ // We are crossing from real HTTP data into the realm of padding. If
+ // we've actually crossed the line, we need to munge countWritten for the
+ // sake of goodness and sanity. No matter what, any future calls to
+ // WriteSegments need to just discard data until we reach the end of this
+ // frame.
+ if (mInputFrameDataSize != mInputFrameDataRead) {
+ // Only change state if we still have padding to read. If we don't do
+ // this, we can end up hanging on frames that combine real data,
+ // padding, and END_STREAM (see bug 1019921)
+ ChangeDownstreamState(DISCARDING_DATA_FRAME_PADDING);
+ }
+ uint32_t paddingRead = mPaddingLength - (mInputFrameDataSize - mInputFrameDataRead);
+ LOG3(("Http2Session::OnWriteSegment %p stream 0x%X len=%d read=%d "
+ "crossed from HTTP data into padding (%d of %d) countWritten=%d",
+ this, mInputFrameID, mInputFrameDataSize, mInputFrameDataRead,
+ paddingRead, mPaddingLength, *countWritten));
+ *countWritten -= paddingRead;
+ LOG3(("Http2Session::OnWriteSegment %p stream 0x%X new countWritten=%d",
+ this, mInputFrameID, *countWritten));
+ }
+
+ mInputFrameDataStream->UpdateTransportReadEvents(*countWritten);
+ if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameFinal)
+ ResetDownstreamState();
+
+ return rv;
+ }
+
+ if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
+
+ if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
+ mInputFrameFinal) {
+ *countWritten = 0;
+ SetNeedsCleanup();
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ count = std::min(count,
+ mFlatHTTPResponseHeaders.Length() -
+ mFlatHTTPResponseHeadersOut);
+ memcpy(buf,
+ mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut,
+ count);
+ mFlatHTTPResponseHeadersOut += count;
+ *countWritten = count;
+
+ if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) {
+ if (!mInputFrameFinal) {
+ // If more frames are expected in this stream, then reset the state so they can be
+ // handled. Otherwise (e.g. a 0 length response with the fin on the incoming headers)
+ // stay in PROCESSING_COMPLETE_HEADERS state so the SetNeedsCleanup() code above can
+ // cleanup the stream.
+ ResetDownstreamState();
+ }
+ }
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(false);
+ return NS_ERROR_UNEXPECTED;
+}
+
+void
+Http2Session::SetNeedsCleanup()
+{
+ LOG3(("Http2Session::SetNeedsCleanup %p - recorded downstream fin of "
+ "stream %p 0x%X", this, mInputFrameDataStream,
+ mInputFrameDataStream->StreamID()));
+
+ // This will result in Close() being called
+ MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set");
+ mInputFrameDataStream->SetResponseIsComplete();
+ mNeedsCleanup = mInputFrameDataStream;
+ ResetDownstreamState();
+}
+
+void
+Http2Session::ConnectPushedStream(Http2Stream *stream)
+{
+ mPushesReadyForRead.Push(stream);
+ ForceRecv();
+}
+
+void
+Http2Session::ConnectSlowConsumer(Http2Stream *stream)
+{
+ LOG3(("Http2Session::ConnectSlowConsumer %p 0x%X\n",
+ this, stream->StreamID()));
+ mSlowConsumersReadyForRead.Push(stream);
+ ForceRecv();
+}
+
+uint32_t
+Http2Session::FindTunnelCount(nsHttpConnectionInfo *aConnInfo)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ uint32_t rv = 0;
+ mTunnelHash.Get(aConnInfo->HashKey(), &rv);
+ return rv;
+}
+
+void
+Http2Session::RegisterTunnel(Http2Stream *aTunnel)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
+ uint32_t newcount = FindTunnelCount(ci) + 1;
+ mTunnelHash.Remove(ci->HashKey());
+ mTunnelHash.Put(ci->HashKey(), newcount);
+ LOG3(("Http2Stream::RegisterTunnel %p stream=%p tunnels=%d [%s]",
+ this, aTunnel, newcount, ci->HashKey().get()));
+}
+
+void
+Http2Session::UnRegisterTunnel(Http2Stream *aTunnel)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
+ MOZ_ASSERT(FindTunnelCount(ci));
+ uint32_t newcount = FindTunnelCount(ci) - 1;
+ mTunnelHash.Remove(ci->HashKey());
+ if (newcount) {
+ mTunnelHash.Put(ci->HashKey(), newcount);
+ }
+ LOG3(("Http2Session::UnRegisterTunnel %p stream=%p tunnels=%d [%s]",
+ this, aTunnel, newcount, ci->HashKey().get()));
+}
+
+void
+Http2Session::CreateTunnel(nsHttpTransaction *trans,
+ nsHttpConnectionInfo *ci,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ LOG(("Http2Session::CreateTunnel %p %p make new tunnel\n", this, trans));
+ // The connect transaction will hold onto the underlying http
+ // transaction so that an auth created by the connect can be mappped
+ // to the correct security callbacks
+
+ RefPtr<SpdyConnectTransaction> connectTrans =
+ new SpdyConnectTransaction(ci, aCallbacks, trans->Caps(), trans, this);
+ AddStream(connectTrans, nsISupportsPriority::PRIORITY_NORMAL, false, nullptr);
+ Http2Stream *tunnel = mStreamTransactionHash.Get(connectTrans);
+ MOZ_ASSERT(tunnel);
+ RegisterTunnel(tunnel);
+}
+
+void
+Http2Session::DispatchOnTunnel(nsAHttpTransaction *aHttpTransaction,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
+ nsHttpConnectionInfo *ci = aHttpTransaction->ConnectionInfo();
+ MOZ_ASSERT(trans);
+
+ LOG3(("Http2Session::DispatchOnTunnel %p trans=%p", this, trans));
+
+ aHttpTransaction->SetConnection(nullptr);
+
+ // this transaction has done its work of setting up a tunnel, let
+ // the connection manager queue it if necessary
+ trans->SetTunnelProvider(this);
+ trans->EnableKeepAlive();
+
+ if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) {
+ LOG3(("Http2Session::DispatchOnTunnel %p create on new tunnel %s",
+ this, ci->HashKey().get()));
+ CreateTunnel(trans, ci, aCallbacks);
+ } else {
+ // requeue it. The connection manager is responsible for actually putting
+ // this on the tunnel connection with the specific ci. If that can't
+ // happen the cmgr checks with us via MaybeReTunnel() to see if it should
+ // make a new tunnel or just wait longer.
+ LOG3(("Http2Session::DispatchOnTunnel %p trans=%p queue in connection manager",
+ this, trans));
+ gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ }
+}
+
+// From ASpdySession
+bool
+Http2Session::MaybeReTunnel(nsAHttpTransaction *aHttpTransaction)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
+ LOG(("Http2Session::MaybeReTunnel %p trans=%p\n", this, trans));
+ if (!trans || trans->TunnelProvider() != this) {
+ // this isn't really one of our transactions.
+ return false;
+ }
+
+ if (mClosed || mShouldGoAway) {
+ LOG(("Http2Session::MaybeReTunnel %p %p session closed - requeue\n", this, trans));
+ trans->SetTunnelProvider(nullptr);
+ gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ return true;
+ }
+
+ nsHttpConnectionInfo *ci = aHttpTransaction->ConnectionInfo();
+ LOG(("Http2Session:MaybeReTunnel %p %p count=%d limit %d\n",
+ this, trans, FindTunnelCount(ci), gHttpHandler->MaxConnectionsPerOrigin()));
+ if (FindTunnelCount(ci) >= gHttpHandler->MaxConnectionsPerOrigin()) {
+ // patience - a tunnel will open up.
+ return false;
+ }
+
+ LOG(("Http2Session::MaybeReTunnel %p %p make new tunnel\n", this, trans));
+ CreateTunnel(trans, ci, trans->SecurityCallbacks());
+ return true;
+}
+
+nsresult
+Http2Session::BufferOutput(const char *buf,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ nsAHttpSegmentReader *old = mSegmentReader;
+ mSegmentReader = nullptr;
+ nsresult rv = OnReadSegment(buf, count, countRead);
+ mSegmentReader = old;
+ return rv;
+}
+
+bool // static
+Http2Session::ALPNCallback(nsISupports *securityInfo)
+{
+ if (!gHttpHandler->IsH2MandatorySuiteEnabled()) {
+ LOG3(("Http2Session::ALPNCallback Mandatory Cipher Suite Unavailable\n"));
+ return false;
+ }
+
+ nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
+ LOG3(("Http2Session::ALPNCallback sslsocketcontrol=%p\n", ssl.get()));
+ if (ssl) {
+ int16_t version = ssl->GetSSLVersionOffered();
+ LOG3(("Http2Session::ALPNCallback version=%x\n", version));
+ if (version >= nsISSLSocketControl::TLS_VERSION_1_2) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsresult
+Http2Session::ConfirmTLSProfile()
+{
+ if (mTLSProfileConfirmed)
+ return NS_OK;
+
+ LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n",
+ this, mConnection.get()));
+
+ if (!gHttpHandler->EnforceHttp2TlsProfile()) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p passed due to configuration bypass\n", this));
+ mTLSProfileConfirmed = true;
+ return NS_OK;
+ }
+
+ if (!mConnection)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISupports> securityInfo;
+ mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
+ nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
+ LOG3(("Http2Session::ConfirmTLSProfile %p sslsocketcontrol=%p\n", this, ssl.get()));
+ if (!ssl)
+ return NS_ERROR_FAILURE;
+
+ int16_t version = ssl->GetSSLVersionUsed();
+ LOG3(("Http2Session::ConfirmTLSProfile %p version=%x\n", this, version));
+ if (version < nsISSLSocketControl::TLS_VERSION_1_2) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of TLS1.2\n", this));
+ RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
+ }
+
+ uint16_t kea = ssl->GetKEAUsed();
+ if (kea != ssl_kea_dh && kea != ssl_kea_ecdh) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to invalid KEA %d\n",
+ this, kea));
+ RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
+ }
+
+ uint32_t keybits = ssl->GetKEAKeyBits();
+ if (kea == ssl_kea_dh && keybits < 2048) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to DH %d < 2048\n",
+ this, keybits));
+ RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
+ } else if (kea == ssl_kea_ecdh && keybits < 224) { // see rfc7540 9.2.1.
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to ECDH %d < 224\n",
+ this, keybits));
+ RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
+ }
+
+ int16_t macAlgorithm = ssl->GetMACAlgorithmUsed();
+ LOG3(("Http2Session::ConfirmTLSProfile %p MAC Algortihm (aead==6) %d\n",
+ this, macAlgorithm));
+ if (macAlgorithm != nsISSLSocketControl::SSL_MAC_AEAD) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of AEAD\n", this));
+ RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
+ }
+
+ /* We are required to send SNI. We do that already, so no check is done
+ * here to make sure we did. */
+
+ /* We really should check to ensure TLS compression isn't enabled on
+ * this connection. However, we never enable TLS compression on our end,
+ * anyway, so it'll never be on. All the same, see https://bugzil.la/965881
+ * for the possibility for an interface to ensure it never gets turned on. */
+
+ mTLSProfileConfirmed = true;
+ return NS_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// Modified methods of nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+void
+Http2Session::TransactionHasDataToWrite(nsAHttpTransaction *caller)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::TransactionHasDataToWrite %p trans=%p", this, caller));
+
+ // a trapped signal from the http transaction to the connection that
+ // it is no longer blocked on read.
+
+ Http2Stream *stream = mStreamTransactionHash.Get(caller);
+ if (!stream || !VerifyStream(stream)) {
+ LOG3(("Http2Session::TransactionHasDataToWrite %p caller %p not found",
+ this, caller));
+ return;
+ }
+
+ LOG3(("Http2Session::TransactionHasDataToWrite %p ID is 0x%X\n",
+ this, stream->StreamID()));
+
+ if (!mClosed) {
+ mReadyForWrite.Push(stream);
+ SetWriteCallbacks();
+ } else {
+ LOG3(("Http2Session::TransactionHasDataToWrite %p closed so not setting Ready4Write\n",
+ this));
+ }
+
+ // NSPR poll will not poll the network if there are non system PR_FileDesc's
+ // that are ready - so we can get into a deadlock waiting for the system IO
+ // to come back here if we don't force the send loop manually.
+ ForceSend();
+}
+
+void
+Http2Session::TransactionHasDataToRecv(nsAHttpTransaction *caller)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::TransactionHasDataToRecv %p trans=%p", this, caller));
+
+ // a signal from the http transaction to the connection that it will consume more
+ Http2Stream *stream = mStreamTransactionHash.Get(caller);
+ if (!stream || !VerifyStream(stream)) {
+ LOG3(("Http2Session::TransactionHasDataToRecv %p caller %p not found",
+ this, caller));
+ return;
+ }
+
+ LOG3(("Http2Session::TransactionHasDataToRecv %p ID is 0x%X\n",
+ this, stream->StreamID()));
+ ConnectSlowConsumer(stream);
+}
+
+void
+Http2Session::TransactionHasDataToWrite(Http2Stream *stream)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x",
+ this, stream, stream->StreamID()));
+
+ mReadyForWrite.Push(stream);
+ SetWriteCallbacks();
+ ForceSend();
+}
+
+bool
+Http2Session::IsPersistent()
+{
+ return true;
+}
+
+nsresult
+Http2Session::TakeTransport(nsISocketTransport **,
+ nsIAsyncInputStream **, nsIAsyncOutputStream **)
+{
+ MOZ_ASSERT(false, "TakeTransport of Http2Session");
+ return NS_ERROR_UNEXPECTED;
+}
+
+already_AddRefed<nsHttpConnection>
+Http2Session::TakeHttpConnection()
+{
+ MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
+ return nullptr;
+}
+
+uint32_t
+Http2Session::CancelPipeline(nsresult reason)
+{
+ // we don't pipeline inside http/2, so this isn't an issue
+ return 0;
+}
+
+nsAHttpTransaction::Classifier
+Http2Session::Classification()
+{
+ if (!mConnection)
+ return nsAHttpTransaction::CLASS_GENERAL;
+ return mConnection->Classification();
+}
+
+void
+Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor **aOut)
+{
+ *aOut = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// unused methods of nsAHttpTransaction
+// We can be sure of this because Http2Session is only constructed in
+// nsHttpConnection and is never passed out of that object or a TLSFilterTransaction
+// TLS tunnel
+//-----------------------------------------------------------------------------
+
+void
+Http2Session::SetConnection(nsAHttpConnection *)
+{
+ // This is unexpected
+ MOZ_ASSERT(false, "Http2Session::SetConnection()");
+}
+
+void
+Http2Session::SetProxyConnectFailed()
+{
+ MOZ_ASSERT(false, "Http2Session::SetProxyConnectFailed()");
+}
+
+bool
+Http2Session::IsDone()
+{
+ return !mStreamTransactionHash.Count();
+}
+
+nsresult
+Http2Session::Status()
+{
+ MOZ_ASSERT(false, "Http2Session::Status()");
+ return NS_ERROR_UNEXPECTED;
+}
+
+uint32_t
+Http2Session::Caps()
+{
+ MOZ_ASSERT(false, "Http2Session::Caps()");
+ return 0;
+}
+
+void
+Http2Session::SetDNSWasRefreshed()
+{
+ MOZ_ASSERT(false, "Http2Session::SetDNSWasRefreshed()");
+}
+
+uint64_t
+Http2Session::Available()
+{
+ MOZ_ASSERT(false, "Http2Session::Available()");
+ return 0;
+}
+
+nsHttpRequestHead *
+Http2Session::RequestHead()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(false,
+ "Http2Session::RequestHead() "
+ "should not be called after http/2 is setup");
+ return NULL;
+}
+
+uint32_t
+Http2Session::Http1xTransactionCount()
+{
+ return 0;
+}
+
+nsresult
+Http2Session::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions)
+{
+ // Generally this cannot be done with http/2 as transactions are
+ // started right away.
+
+ LOG3(("Http2Session::TakeSubTransactions %p\n", this));
+
+ if (mConcurrentHighWater > 0)
+ return NS_ERROR_ALREADY_OPENED;
+
+ LOG3((" taking %d\n", mStreamTransactionHash.Count()));
+
+ for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
+ outTransactions.AppendElement(iter.Key());
+
+ // Removing the stream from the hash will delete the stream and drop the
+ // transaction reference the hash held.
+ iter.Remove();
+ }
+ return NS_OK;
+}
+
+nsresult
+Http2Session::AddTransaction(nsAHttpTransaction *)
+{
+ // This API is meant for pipelining, Http2Session's should be
+ // extended with AddStream()
+
+ MOZ_ASSERT(false,
+ "Http2Session::AddTransaction() should not be called");
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+Http2Session::PipelineDepth()
+{
+ return IsDone() ? 0 : 1;
+}
+
+nsresult
+Http2Session::SetPipelinePosition(int32_t position)
+{
+ // This API is meant for pipelining, Http2Session's should be
+ // extended with AddStream()
+
+ MOZ_ASSERT(false,
+ "Http2Session::SetPipelinePosition() should not be called");
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+int32_t
+Http2Session::PipelinePosition()
+{
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Pass through methods of nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+nsAHttpConnection *
+Http2Session::Connection()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ return mConnection;
+}
+
+nsresult
+Http2Session::OnHeadersAvailable(nsAHttpTransaction *transaction,
+ nsHttpRequestHead *requestHead,
+ nsHttpResponseHead *responseHead, bool *reset)
+{
+ return mConnection->OnHeadersAvailable(transaction,
+ requestHead,
+ responseHead,
+ reset);
+}
+
+bool
+Http2Session::IsReused()
+{
+ return mConnection->IsReused();
+}
+
+nsresult
+Http2Session::PushBack(const char *buf, uint32_t len)
+{
+ return mConnection->PushBack(buf, len);
+}
+
+void
+Http2Session::SendPing()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mPreviousUsed) {
+ // alredy in progress, get out
+ return;
+ }
+
+ mPingSentEpoch = PR_IntervalNow();
+ if (!mPingSentEpoch) {
+ mPingSentEpoch = 1; // avoid the 0 sentinel value
+ }
+ if (!mPingThreshold ||
+ (mPingThreshold > gHttpHandler->NetworkChangedTimeout())) {
+ mPreviousPingThreshold = mPingThreshold;
+ mPreviousUsed = true;
+ mPingThreshold = gHttpHandler->NetworkChangedTimeout();
+ }
+ GeneratePing(false);
+ ResumeRecv();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Session.h b/netwerk/protocol/http/Http2Session.h
new file mode 100644
index 000000000..60986381b
--- /dev/null
+++ b/netwerk/protocol/http/Http2Session.h
@@ -0,0 +1,508 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http2Session_h
+#define mozilla_net_Http2Session_h
+
+// HTTP/2 - RFC 7540
+// https://www.rfc-editor.org/rfc/rfc7540.txt
+
+#include "ASpdySession.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsAHttpConnection.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsDeque.h"
+#include "nsHashKeys.h"
+
+#include "Http2Compression.h"
+
+class nsISocketTransport;
+
+namespace mozilla {
+namespace net {
+
+class Http2PushedStream;
+class Http2Stream;
+class nsHttpTransaction;
+
+class Http2Session final : public ASpdySession
+ , public nsAHttpConnection
+ , public nsAHttpSegmentReader
+ , public nsAHttpSegmentWriter
+{
+ ~Http2Session();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_NSAHTTPCONNECTION(mConnection)
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+
+ Http2Session(nsISocketTransport *, uint32_t version);
+
+ bool AddStream(nsAHttpTransaction *, int32_t,
+ bool, nsIInterfaceRequestor *) override;
+ bool CanReuse() override { return !mShouldGoAway && !mClosed; }
+ bool RoomForMoreStreams() override;
+
+ // When the connection is active this is called up to once every 1 second
+ // return the interval (in seconds) that the connection next wants to
+ // have this invoked. It might happen sooner depending on the needs of
+ // other connections.
+ uint32_t ReadTimeoutTick(PRIntervalTime now) override;
+
+ // Idle time represents time since "goodput".. e.g. a data or header frame
+ PRIntervalTime IdleTime() override;
+
+ // Registering with a newID of 0 means pick the next available odd ID
+ uint32_t RegisterStreamID(Http2Stream *, uint32_t aNewID = 0);
+
+/*
+ HTTP/2 framing
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Length (16) | Type (8) | Flags (8) |
+ +-+-------------+---------------+-------------------------------+
+ |R| Stream Identifier (31) |
+ +-+-------------------------------------------------------------+
+ | Frame Data (0...) ...
+ +---------------------------------------------------------------+
+*/
+
+ enum FrameType {
+ FRAME_TYPE_DATA = 0x0,
+ FRAME_TYPE_HEADERS = 0x1,
+ FRAME_TYPE_PRIORITY = 0x2,
+ FRAME_TYPE_RST_STREAM = 0x3,
+ FRAME_TYPE_SETTINGS = 0x4,
+ FRAME_TYPE_PUSH_PROMISE = 0x5,
+ FRAME_TYPE_PING = 0x6,
+ FRAME_TYPE_GOAWAY = 0x7,
+ FRAME_TYPE_WINDOW_UPDATE = 0x8,
+ FRAME_TYPE_CONTINUATION = 0x9,
+ FRAME_TYPE_ALTSVC = 0xA,
+ FRAME_TYPE_LAST = 0xB
+ };
+
+ // NO_ERROR is a macro defined on windows, so we'll name the HTTP2 goaway
+ // code NO_ERROR to be NO_HTTP_ERROR
+ enum errorType {
+ NO_HTTP_ERROR = 0,
+ PROTOCOL_ERROR = 1,
+ INTERNAL_ERROR = 2,
+ FLOW_CONTROL_ERROR = 3,
+ SETTINGS_TIMEOUT_ERROR = 4,
+ STREAM_CLOSED_ERROR = 5,
+ FRAME_SIZE_ERROR = 6,
+ REFUSED_STREAM_ERROR = 7,
+ CANCEL_ERROR = 8,
+ COMPRESSION_ERROR = 9,
+ CONNECT_ERROR = 10,
+ ENHANCE_YOUR_CALM = 11,
+ INADEQUATE_SECURITY = 12,
+ HTTP_1_1_REQUIRED = 13,
+ UNASSIGNED = 31
+ };
+
+ // These are frame flags. If they, or other undefined flags, are
+ // used on frames other than the comments indicate they MUST be ignored.
+ const static uint8_t kFlag_END_STREAM = 0x01; // data, headers
+ const static uint8_t kFlag_END_HEADERS = 0x04; // headers, continuation
+ const static uint8_t kFlag_END_PUSH_PROMISE = 0x04; // push promise
+ const static uint8_t kFlag_ACK = 0x01; // ping and settings
+ const static uint8_t kFlag_PADDED = 0x08; // data, headers, push promise, continuation
+ const static uint8_t kFlag_PRIORITY = 0x20; // headers
+
+ enum {
+ SETTINGS_TYPE_HEADER_TABLE_SIZE = 1, // compression table size
+ SETTINGS_TYPE_ENABLE_PUSH = 2, // can be used to disable push
+ SETTINGS_TYPE_MAX_CONCURRENT = 3, // streams recvr allowed to initiate
+ SETTINGS_TYPE_INITIAL_WINDOW = 4, // bytes for flow control default
+ SETTINGS_TYPE_MAX_FRAME_SIZE = 5 // max frame size settings sender allows receipt of
+ };
+
+ // This should be big enough to hold all of your control packets,
+ // but if it needs to grow for huge headers it can do so dynamically.
+ const static uint32_t kDefaultBufferSize = 2048;
+
+ // kDefaultQueueSize must be >= other queue size constants
+ const static uint32_t kDefaultQueueSize = 32768;
+ const static uint32_t kQueueMinimumCleanup = 24576;
+ const static uint32_t kQueueTailRoom = 4096;
+ const static uint32_t kQueueReserved = 1024;
+
+ const static uint32_t kMaxStreamID = 0x7800000;
+
+ // This is a sentinel for a deleted stream. It is not a valid
+ // 31 bit stream ID.
+ const static uint32_t kDeadStreamID = 0xffffdead;
+
+ // below the emergency threshold of local window we ack every received
+ // byte. Above that we coalesce bytes into the MinimumToAck size.
+ const static int32_t kEmergencyWindowThreshold = 256 * 1024;
+ const static uint32_t kMinimumToAck = 4 * 1024 * 1024;
+
+ // The default rwin is 64KB - 1 unless updated by a settings frame
+ const static uint32_t kDefaultRwin = 65535;
+
+ // We limit frames to 2^14 bytes of length in order to preserve responsiveness
+ // This is the smallest allowed value for SETTINGS_MAX_FRAME_SIZE
+ const static uint32_t kMaxFrameData = 0x4000;
+
+ const static uint8_t kFrameLengthBytes = 3;
+ const static uint8_t kFrameStreamIDBytes = 4;
+ const static uint8_t kFrameFlagBytes = 1;
+ const static uint8_t kFrameTypeBytes = 1;
+ const static uint8_t kFrameHeaderBytes = kFrameLengthBytes + kFrameFlagBytes +
+ kFrameTypeBytes + kFrameStreamIDBytes;
+
+ enum {
+ kLeaderGroupID = 0x3,
+ kOtherGroupID = 0x5,
+ kBackgroundGroupID = 0x7,
+ kSpeculativeGroupID = 0x9,
+ kFollowerGroupID = 0xB
+ };
+
+ static nsresult RecvHeaders(Http2Session *);
+ static nsresult RecvPriority(Http2Session *);
+ static nsresult RecvRstStream(Http2Session *);
+ static nsresult RecvSettings(Http2Session *);
+ static nsresult RecvPushPromise(Http2Session *);
+ static nsresult RecvPing(Http2Session *);
+ static nsresult RecvGoAway(Http2Session *);
+ static nsresult RecvWindowUpdate(Http2Session *);
+ static nsresult RecvContinuation(Http2Session *);
+ static nsresult RecvAltSvc(Http2Session *);
+
+ char *EnsureOutputBuffer(uint32_t needed);
+
+ template<typename charType>
+ void CreateFrameHeader(charType dest, uint16_t frameLength,
+ uint8_t frameType, uint8_t frameFlags,
+ uint32_t streamID);
+
+ // For writing the data stream to LOG4
+ static void LogIO(Http2Session *, Http2Stream *, const char *,
+ const char *, uint32_t);
+
+ // overload of nsAHttpConnection
+ void TransactionHasDataToWrite(nsAHttpTransaction *) override;
+ void TransactionHasDataToRecv(nsAHttpTransaction *) override;
+
+ // a similar version for Http2Stream
+ void TransactionHasDataToWrite(Http2Stream *);
+
+ // an overload of nsAHttpSegementReader
+ virtual nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment) override;
+ nsresult BufferOutput(const char *, uint32_t, uint32_t *);
+ void FlushOutputQueue();
+ uint32_t AmountOfOutputBuffered() { return mOutputQueueUsed - mOutputQueueSent; }
+
+ uint32_t GetServerInitialStreamWindow() { return mServerInitialStreamWindow; }
+
+ bool TryToActivate(Http2Stream *stream);
+ void ConnectPushedStream(Http2Stream *stream);
+ void ConnectSlowConsumer(Http2Stream *stream);
+
+ nsresult ConfirmTLSProfile();
+ static bool ALPNCallback(nsISupports *securityInfo);
+
+ uint64_t Serial() { return mSerial; }
+
+ void PrintDiagnostics (nsCString &log) override;
+
+ // Streams need access to these
+ uint32_t SendingChunkSize() { return mSendingChunkSize; }
+ uint32_t PushAllowance() { return mPushAllowance; }
+ Http2Compressor *Compressor() { return &mCompressor; }
+ nsISocketTransport *SocketTransport() { return mSocketTransport; }
+ int64_t ServerSessionWindow() { return mServerSessionWindow; }
+ void DecrementServerSessionWindow (uint32_t bytes) { mServerSessionWindow -= bytes; }
+ uint32_t InitialRwin() { return mInitialRwin; }
+
+ void SendPing() override;
+ bool MaybeReTunnel(nsAHttpTransaction *) override;
+ bool UseH2Deps() { return mUseH2Deps; }
+
+ // overload of nsAHttpTransaction
+ nsresult ReadSegmentsAgain(nsAHttpSegmentReader *, uint32_t, uint32_t *, bool *) override final;
+ nsresult WriteSegmentsAgain(nsAHttpSegmentWriter *, uint32_t , uint32_t *, bool *) override final;
+
+private:
+
+ // These internal states do not correspond to the states of the HTTP/2 specification
+ enum internalStateType {
+ BUFFERING_OPENING_SETTINGS,
+ BUFFERING_FRAME_HEADER,
+ BUFFERING_CONTROL_FRAME,
+ PROCESSING_DATA_FRAME_PADDING_CONTROL,
+ PROCESSING_DATA_FRAME,
+ DISCARDING_DATA_FRAME_PADDING,
+ DISCARDING_DATA_FRAME,
+ PROCESSING_COMPLETE_HEADERS,
+ PROCESSING_CONTROL_RST_STREAM,
+ NOT_USING_NETWORK
+ };
+
+ static const uint8_t kMagicHello[24];
+
+ nsresult ResponseHeadersComplete();
+ uint32_t GetWriteQueueSize();
+ void ChangeDownstreamState(enum internalStateType);
+ void ResetDownstreamState();
+ nsresult ReadyToProcessDataFrame(enum internalStateType);
+ nsresult UncompressAndDiscard(bool);
+ void GeneratePing(bool);
+ void GenerateSettingsAck();
+ void GeneratePriority(uint32_t, uint8_t);
+ void GenerateRstStream(uint32_t, uint32_t);
+ void GenerateGoAway(uint32_t);
+ void CleanupStream(Http2Stream *, nsresult, errorType);
+ void CleanupStream(uint32_t, nsresult, errorType);
+ void CloseStream(Http2Stream *, nsresult);
+ void SendHello();
+ void RemoveStreamFromQueues(Http2Stream *);
+ nsresult ParsePadding(uint8_t &, uint16_t &);
+
+ void SetWriteCallbacks();
+ void RealignOutputQueue();
+
+ void ProcessPending();
+ nsresult ProcessConnectedPush(Http2Stream *, nsAHttpSegmentWriter *,
+ uint32_t, uint32_t *);
+ nsresult ProcessSlowConsumer(Http2Stream *, nsAHttpSegmentWriter *,
+ uint32_t, uint32_t *);
+
+ nsresult SetInputFrameDataStream(uint32_t);
+ void CreatePriorityNode(uint32_t, uint32_t, uint8_t, const char *);
+ bool VerifyStream(Http2Stream *, uint32_t);
+ void SetNeedsCleanup();
+
+ void UpdateLocalRwin(Http2Stream *stream, uint32_t bytes);
+ void UpdateLocalStreamWindow(Http2Stream *stream, uint32_t bytes);
+ void UpdateLocalSessionWindow(uint32_t bytes);
+
+ void MaybeDecrementConcurrent(Http2Stream *stream);
+ bool RoomForMoreConcurrent();
+ void IncrementConcurrent(Http2Stream *stream);
+ void QueueStream(Http2Stream *stream);
+
+ // a wrapper for all calls to the nshttpconnection level segment writer. Used
+ // to track network I/O for timeout purposes
+ nsresult NetworkRead(nsAHttpSegmentWriter *, char *, uint32_t, uint32_t *);
+
+ void Shutdown();
+
+ // This is intended to be nsHttpConnectionMgr:nsConnectionHandle taken
+ // from the first transaction on this session. That object contains the
+ // pointer to the real network-level nsHttpConnection object.
+ RefPtr<nsAHttpConnection> mConnection;
+
+ // The underlying socket transport object is needed to propogate some events
+ nsISocketTransport *mSocketTransport;
+
+ // These are temporary state variables to hold the argument to
+ // Read/WriteSegments so it can be accessed by On(read/write)segment
+ // further up the stack.
+ nsAHttpSegmentReader *mSegmentReader;
+ nsAHttpSegmentWriter *mSegmentWriter;
+
+ uint32_t mSendingChunkSize; /* the transmission chunk size */
+ uint32_t mNextStreamID; /* 24 bits */
+ uint32_t mLastPushedID;
+ uint32_t mConcurrentHighWater; /* max parallelism on session */
+ uint32_t mPushAllowance; /* rwin for unmatched pushes */
+
+ internalStateType mDownstreamState; /* in frame, between frames, etc.. */
+
+ // Maintain 2 indexes - one by stream ID, one by transaction pointer.
+ // There are also several lists of streams: ready to write, queued due to
+ // max parallelism, streams that need to force a read for push, and the full
+ // set of pushed streams.
+ // The objects are not ref counted - they get destroyed
+ // by the nsClassHashtable implementation when they are removed from
+ // the transaction hash.
+ nsDataHashtable<nsUint32HashKey, Http2Stream *> mStreamIDHash;
+ nsClassHashtable<nsPtrHashKey<nsAHttpTransaction>,
+ Http2Stream> mStreamTransactionHash;
+
+ nsDeque mReadyForWrite;
+ nsDeque mQueuedStreams;
+ nsDeque mPushesReadyForRead;
+ nsDeque mSlowConsumersReadyForRead;
+ nsTArray<Http2PushedStream *> mPushedStreams;
+
+ // Compression contexts for header transport.
+ // HTTP/2 compresses only HTTP headers and does not reset the context in between
+ // frames. Even data that is not associated with a stream (e.g invalid
+ // stream ID) is passed through these contexts to keep the compression
+ // context correct.
+ Http2Compressor mCompressor;
+ Http2Decompressor mDecompressor;
+ nsCString mDecompressBuffer;
+
+ // mInputFrameBuffer is used to store received control packets and the 8 bytes
+ // of header on data packets
+ uint32_t mInputFrameBufferSize; // buffer allocation
+ uint32_t mInputFrameBufferUsed; // amt of allocation used
+ UniquePtr<char[]> mInputFrameBuffer;
+
+ // mInputFrameDataSize/Read are used for tracking the amount of data consumed
+ // in a frame after the 8 byte header. Control frames are always fully buffered
+ // and the fixed 8 byte leading header is at mInputFrameBuffer + 0, the first
+ // data byte (i.e. the first settings/goaway/etc.. specific byte) is at
+ // mInputFrameBuffer + 8
+ // The frame size is mInputFrameDataSize + the constant 8 byte header
+ uint32_t mInputFrameDataSize;
+ uint32_t mInputFrameDataRead;
+ bool mInputFrameFinal; // This frame was marked FIN
+ uint8_t mInputFrameType;
+ uint8_t mInputFrameFlags;
+ uint32_t mInputFrameID;
+ uint16_t mPaddingLength;
+
+ // When a frame has been received that is addressed to a particular stream
+ // (e.g. a data frame after the stream-id has been decoded), this points
+ // to the stream.
+ Http2Stream *mInputFrameDataStream;
+
+ // mNeedsCleanup is a state variable to defer cleanup of a closed stream
+ // If needed, It is set in session::OnWriteSegments() and acted on and
+ // cleared when the stack returns to session::WriteSegments(). The stream
+ // cannot be destroyed directly out of OnWriteSegments because
+ // stream::writeSegments() is on the stack at that time.
+ Http2Stream *mNeedsCleanup;
+
+ // This reason code in the last processed RESET frame
+ uint32_t mDownstreamRstReason;
+
+ // When HEADERS/PROMISE are chained together, this is the expected ID of the next
+ // recvd frame which must be the same type
+ uint32_t mExpectedHeaderID;
+ uint32_t mExpectedPushPromiseID;
+ uint32_t mContinuedPromiseStream;
+
+ // for the conversion of downstream http headers into http/2 formatted headers
+ // The data here does not persist between frames
+ nsCString mFlatHTTPResponseHeaders;
+ uint32_t mFlatHTTPResponseHeadersOut;
+
+ // when set, the session will go away when it reaches 0 streams. This flag
+ // is set when: the stream IDs are running out (at either the client or the
+ // server), when DontReuse() is called, a RST that is not specific to a
+ // particular stream is received, a GOAWAY frame has been received from
+ // the server.
+ bool mShouldGoAway;
+
+ // the session has received a nsAHttpTransaction::Close() call
+ bool mClosed;
+
+ // the session received a GoAway frame with a valid GoAwayID
+ bool mCleanShutdown;
+
+ // The TLS comlpiance checks are not done in the ctor beacuse of bad
+ // exception handling - so we do them at IO time and cache the result
+ bool mTLSProfileConfirmed;
+
+ // A specifc reason code for the eventual GoAway frame. If set to NO_HTTP_ERROR
+ // only NO_HTTP_ERROR, PROTOCOL_ERROR, or INTERNAL_ERROR will be sent.
+ errorType mGoAwayReason;
+
+ // The error code sent/received on the session goaway frame. UNASSIGNED/31
+ // if not transmitted.
+ int32_t mClientGoAwayReason;
+ int32_t mPeerGoAwayReason;
+
+ // If a GoAway message was received this is the ID of the last valid
+ // stream. 0 otherwise. (0 is never a valid stream id.)
+ uint32_t mGoAwayID;
+
+ // The last stream processed ID we will send in our GoAway frame.
+ uint32_t mOutgoingGoAwayID;
+
+ // The limit on number of concurrent streams for this session. Normally it
+ // is basically unlimited, but the SETTINGS control message from the
+ // server might bring it down.
+ uint32_t mMaxConcurrent;
+
+ // The actual number of concurrent streams at this moment. Generally below
+ // mMaxConcurrent, but the max can be lowered in real time to a value
+ // below the current value
+ uint32_t mConcurrent;
+
+ // The number of server initiated promises, tracked for telemetry
+ uint32_t mServerPushedResources;
+
+ // The server rwin for new streams as determined from a SETTINGS frame
+ uint32_t mServerInitialStreamWindow;
+
+ // The Local Session window is how much data the server is allowed to send
+ // (across all streams) without getting a window update to stream 0. It is
+ // signed because asynchronous changes via SETTINGS can drive it negative.
+ int64_t mLocalSessionWindow;
+
+ // The Remote Session Window is how much data the client is allowed to send
+ // (across all streams) without receiving a window update to stream 0. It is
+ // signed because asynchronous changes via SETTINGS can drive it negative.
+ int64_t mServerSessionWindow;
+
+ // The initial value of the local stream and session window
+ uint32_t mInitialRwin;
+
+ // This is a output queue of bytes ready to be written to the SSL stream.
+ // When that streams returns WOULD_BLOCK on direct write the bytes get
+ // coalesced together here. This results in larger writes to the SSL layer.
+ // The buffer is not dynamically grown to accomodate stream writes, but
+ // does expand to accept infallible session wide frames like GoAway and RST.
+ uint32_t mOutputQueueSize;
+ uint32_t mOutputQueueUsed;
+ uint32_t mOutputQueueSent;
+ UniquePtr<char[]> mOutputQueueBuffer;
+
+ PRIntervalTime mPingThreshold;
+ PRIntervalTime mLastReadEpoch; // used for ping timeouts
+ PRIntervalTime mLastDataReadEpoch; // used for IdleTime()
+ PRIntervalTime mPingSentEpoch;
+
+ PRIntervalTime mPreviousPingThreshold; // backup for the former value
+ bool mPreviousUsed; // true when backup is used
+
+ // used as a temporary buffer while enumerating the stream hash during GoAway
+ nsDeque mGoAwayStreamsToRestart;
+
+ // Each session gets a unique serial number because the push cache is correlated
+ // by the load group and the serial number can be used as part of the cache key
+ // to make sure streams aren't shared across sessions.
+ uint64_t mSerial;
+
+ // If push is disabled, we want to be able to send PROTOCOL_ERRORs if we
+ // receive a PUSH_PROMISE, but we have to wait for the SETTINGS ACK before
+ // we can actually tell the other end to go away. These help us keep track
+ // of that state so we can behave appropriately.
+ bool mWaitingForSettingsAck;
+ bool mGoAwayOnPush;
+
+ bool mUseH2Deps;
+
+private:
+/// connect tunnels
+ void DispatchOnTunnel(nsAHttpTransaction *, nsIInterfaceRequestor *);
+ void CreateTunnel(nsHttpTransaction *, nsHttpConnectionInfo *, nsIInterfaceRequestor *);
+ void RegisterTunnel(Http2Stream *);
+ void UnRegisterTunnel(Http2Stream *);
+ uint32_t FindTunnelCount(nsHttpConnectionInfo *);
+ nsDataHashtable<nsCStringHashKey, uint32_t> mTunnelHash;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http2Session_h
diff --git a/netwerk/protocol/http/Http2Stream.cpp b/netwerk/protocol/http/Http2Stream.cpp
new file mode 100644
index 000000000..5c562557c
--- /dev/null
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -0,0 +1,1472 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include <algorithm>
+
+#include "Http2Compression.h"
+#include "Http2Session.h"
+#include "Http2Stream.h"
+#include "Http2Push.h"
+#include "TunnelUtils.h"
+
+#include "mozilla/Telemetry.h"
+#include "nsAlgorithm.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsIClassOfService.h"
+#include "nsIPipe.h"
+#include "nsISocketTransport.h"
+#include "nsStandardURL.h"
+#include "prnetdb.h"
+
+namespace mozilla {
+namespace net {
+
+Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction,
+ Http2Session *session,
+ int32_t priority)
+ : mStreamID(0)
+ , mSession(session)
+ , mSegmentReader(nullptr)
+ , mSegmentWriter(nullptr)
+ , mUpstreamState(GENERATING_HEADERS)
+ , mState(IDLE)
+ , mRequestHeadersDone(0)
+ , mOpenGenerated(0)
+ , mAllHeadersReceived(0)
+ , mQueued(0)
+ , mTransaction(httpTransaction)
+ , mSocketTransport(session->SocketTransport())
+ , mChunkSize(session->SendingChunkSize())
+ , mRequestBlockedOnRead(0)
+ , mRecvdFin(0)
+ , mReceivedData(0)
+ , mRecvdReset(0)
+ , mSentReset(0)
+ , mCountAsActive(0)
+ , mSentFin(0)
+ , mSentWaitingFor(0)
+ , mSetTCPSocketBuffer(0)
+ , mBypassInputBuffer(0)
+ , mTxInlineFrameSize(Http2Session::kDefaultBufferSize)
+ , mTxInlineFrameUsed(0)
+ , mTxStreamFrameSize(0)
+ , mRequestBodyLenRemaining(0)
+ , mLocalUnacked(0)
+ , mBlockedOnRwin(false)
+ , mTotalSent(0)
+ , mTotalRead(0)
+ , mPushSource(nullptr)
+ , mIsTunnel(false)
+ , mPlainTextTunnel(false)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ LOG3(("Http2Stream::Http2Stream %p", this));
+
+ mServerReceiveWindow = session->GetServerInitialStreamWindow();
+ mClientReceiveWindow = session->PushAllowance();
+
+ mTxInlineFrame = MakeUnique<uint8_t[]>(mTxInlineFrameSize);
+
+ static_assert(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority,
+ "Lowest Priority should be less than kNormalPriority");
+
+ // values of priority closer to 0 are higher priority for the priority
+ // argument. This value is used as a group, which maps to a
+ // weight that is related to the nsISupportsPriority that we are given.
+ int32_t httpPriority;
+ if (priority >= nsISupportsPriority::PRIORITY_LOWEST) {
+ httpPriority = kWorstPriority;
+ } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) {
+ httpPriority = kBestPriority;
+ } else {
+ httpPriority = kNormalPriority + priority;
+ }
+ MOZ_ASSERT(httpPriority >= 0);
+ SetPriority(static_cast<uint32_t>(httpPriority));
+}
+
+Http2Stream::~Http2Stream()
+{
+ ClearTransactionsBlockedOnTunnel();
+ mStreamID = Http2Session::kDeadStreamID;
+}
+
+// ReadSegments() is used to write data down the socket. Generally, HTTP
+// request data is pulled from the approriate transaction and
+// converted to HTTP/2 data. Sometimes control data like a window-update is
+// generated instead.
+
+nsresult
+Http2Stream::ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ LOG3(("Http2Stream %p ReadSegments reader=%p count=%d state=%x",
+ this, reader, count, mUpstreamState));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ mRequestBlockedOnRead = 0;
+
+ if (mRecvdFin || mRecvdReset) {
+ // Don't transmit any request frames if the peer cannot respond
+ LOG3(("Http2Stream %p ReadSegments request stream aborted due to"
+ " response side closure\n", this));
+ return NS_ERROR_ABORT;
+ }
+
+ // avoid runt chunks if possible by anticipating
+ // full data frames
+ if (count > (mChunkSize + 8)) {
+ uint32_t numchunks = count / (mChunkSize + 8);
+ count = numchunks * (mChunkSize + 8);
+ }
+
+ switch (mUpstreamState) {
+ case GENERATING_HEADERS:
+ case GENERATING_BODY:
+ case SENDING_BODY:
+ // Call into the HTTP Transaction to generate the HTTP request
+ // stream. That stream will show up in OnReadSegment().
+ mSegmentReader = reader;
+ rv = mTransaction->ReadSegments(this, count, countRead);
+ mSegmentReader = nullptr;
+
+ LOG3(("Http2Stream::ReadSegments %p trans readsegments rv %x read=%d\n",
+ this, rv, *countRead));
+
+ // Check to see if the transaction's request could be written out now.
+ // If not, mark the stream for callback when writing can proceed.
+ if (NS_SUCCEEDED(rv) &&
+ mUpstreamState == GENERATING_HEADERS &&
+ !mRequestHeadersDone)
+ mSession->TransactionHasDataToWrite(this);
+
+ // mTxinlineFrameUsed represents any queued un-sent frame. It might
+ // be 0 if there is no such frame, which is not a gurantee that we
+ // don't have more request body to send - just that any data that was
+ // sent comprised a complete HTTP/2 frame. Likewise, a non 0 value is
+ // a queued, but complete, http/2 frame length.
+
+ // Mark that we are blocked on read if the http transaction needs to
+ // provide more of the request message body and there is nothing queued
+ // for writing
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed)
+ mRequestBlockedOnRead = 1;
+
+ // A transaction that had already generated its headers before it was
+ // queued at the session level (due to concurrency concerns) may not call
+ // onReadSegment off the ReadSegments() stack above.
+ if (mUpstreamState == GENERATING_HEADERS && NS_SUCCEEDED(rv)) {
+ LOG3(("Http2Stream %p ReadSegments forcing OnReadSegment call\n", this));
+ uint32_t wasted = 0;
+ mSegmentReader = reader;
+ OnReadSegment("", 0, &wasted);
+ mSegmentReader = nullptr;
+ }
+
+ // If the sending flow control window is open (!mBlockedOnRwin) then
+ // continue sending the request
+ if (!mBlockedOnRwin && mOpenGenerated &&
+ !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) {
+ MOZ_ASSERT(!mQueued);
+ MOZ_ASSERT(mRequestHeadersDone);
+ LOG3(("Http2Stream::ReadSegments %p 0x%X: Sending request data complete, "
+ "mUpstreamState=%x\n",this, mStreamID, mUpstreamState));
+ if (mSentFin) {
+ ChangeState(UPSTREAM_COMPLETE);
+ } else {
+ GenerateDataFrameHeader(0, true);
+ ChangeState(SENDING_FIN_STREAM);
+ mSession->TransactionHasDataToWrite(this);
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ }
+ break;
+
+ case SENDING_FIN_STREAM:
+ // We were trying to send the FIN-STREAM but were blocked from
+ // sending it out - try again.
+ if (!mSentFin) {
+ mSegmentReader = reader;
+ rv = TransmitFrame(nullptr, nullptr, false);
+ mSegmentReader = nullptr;
+ MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
+ "Transmit Frame should be all or nothing");
+ if (NS_SUCCEEDED(rv))
+ ChangeState(UPSTREAM_COMPLETE);
+ } else {
+ rv = NS_OK;
+ mTxInlineFrameUsed = 0; // cancel fin data packet
+ ChangeState(UPSTREAM_COMPLETE);
+ }
+
+ *countRead = 0;
+
+ // don't change OK to WOULD BLOCK. we are really done sending if OK
+ break;
+
+ case UPSTREAM_COMPLETE:
+ *countRead = 0;
+ rv = NS_OK;
+ break;
+
+ default:
+ MOZ_ASSERT(false, "Http2Stream::ReadSegments unknown state");
+ break;
+ }
+
+ return rv;
+}
+
+uint64_t
+Http2Stream::LocalUnAcked()
+{
+ // reduce unacked by the amount of undelivered data
+ // to help assert flow control
+ uint64_t undelivered = mSimpleBuffer.Available();
+
+ if (undelivered > mLocalUnacked) {
+ return 0;
+ }
+ return mLocalUnacked - undelivered;
+}
+
+nsresult
+Http2Stream::BufferInput(uint32_t count, uint32_t *countWritten)
+{
+ char buf[SimpleBufferPage::kSimpleBufferPageSize];
+ if (SimpleBufferPage::kSimpleBufferPageSize < count) {
+ count = SimpleBufferPage::kSimpleBufferPageSize;
+ }
+
+ mBypassInputBuffer = 1;
+ nsresult rv = mSegmentWriter->OnWriteSegment(buf, count, countWritten);
+ mBypassInputBuffer = 0;
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = mSimpleBuffer.Write(buf, *countWritten);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return rv;
+}
+
+bool
+Http2Stream::DeferCleanup(nsresult status)
+{
+ // do not cleanup a stream that has data buffered for the transaction
+ return (NS_SUCCEEDED(status) && mSimpleBuffer.Available());
+}
+
+// WriteSegments() is used to read data off the socket. Generally this is
+// just a call through to the associated nsHttpTransaction for this stream
+// for the remaining data bytes indicated by the current DATA frame.
+
+nsresult
+Http2Stream::WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count,
+ uint32_t *countWritten)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");
+
+ LOG3(("Http2Stream::WriteSegments %p count=%d state=%x",
+ this, count, mUpstreamState));
+
+ mSegmentWriter = writer;
+ nsresult rv = mTransaction->WriteSegments(this, count, countWritten);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ // consuming transaction won't take data. but we need to read it into a buffer so that it
+ // won't block other streams. but we should not advance the flow control window
+ // so that we'll eventually push back on the sender.
+
+ // with tunnels you need to make sure that this is an underlying connction established
+ // that can be meaningfully giving this signal
+ bool doBuffer = true;
+ if (mIsTunnel) {
+ RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
+ if (qiTrans) {
+ doBuffer = qiTrans->ConnectedReadyForInput();
+ }
+ }
+ // stash this data
+ if (doBuffer) {
+ rv = BufferInput(count, countWritten);
+ LOG3(("Http2Stream::WriteSegments %p Buffered %X %d\n", this, rv, *countWritten));
+ }
+ }
+ mSegmentWriter = nullptr;
+ return rv;
+}
+
+nsresult
+Http2Stream::MakeOriginURL(const nsACString &origin, RefPtr<nsStandardURL> &url)
+{
+ nsAutoCString scheme;
+ nsresult rv = net_ExtractURLScheme(origin, scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return MakeOriginURL(scheme, origin, url);
+}
+
+nsresult
+Http2Stream::MakeOriginURL(const nsACString &scheme, const nsACString &origin,
+ RefPtr<nsStandardURL> &url)
+{
+ url = new nsStandardURL();
+ nsresult rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY,
+ scheme.EqualsLiteral("http") ?
+ NS_HTTP_DEFAULT_PORT :
+ NS_HTTPS_DEFAULT_PORT,
+ origin, nullptr, nullptr);
+ return rv;
+}
+
+void
+Http2Stream::CreatePushHashKey(const nsCString &scheme,
+ const nsCString &hostHeader,
+ uint64_t serial,
+ const nsCSubstring &pathInfo,
+ nsCString &outOrigin,
+ nsCString &outKey)
+{
+ nsCString fullOrigin = scheme;
+ fullOrigin.AppendLiteral("://");
+ fullOrigin.Append(hostHeader);
+
+ RefPtr<nsStandardURL> origin;
+ nsresult rv = Http2Stream::MakeOriginURL(scheme, fullOrigin, origin);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = origin->GetAsciiSpec(outOrigin);
+ outOrigin.Trim("/", false, true, false);
+ }
+
+ if (NS_FAILED(rv)) {
+ // Fallback to plain text copy - this may end up behaving poorly
+ outOrigin = fullOrigin;
+ }
+
+ outKey = outOrigin;
+ outKey.AppendLiteral("/[http2.");
+ outKey.AppendInt(serial);
+ outKey.Append(']');
+ outKey.Append(pathInfo);
+}
+
+nsresult
+Http2Stream::ParseHttpRequestHeaders(const char *buf,
+ uint32_t avail,
+ uint32_t *countUsed)
+{
+ // Returns NS_OK even if the headers are incomplete
+ // set mRequestHeadersDone flag if they are complete
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS);
+ MOZ_ASSERT(!mRequestHeadersDone);
+
+ LOG3(("Http2Stream::ParseHttpRequestHeaders %p avail=%d state=%x",
+ this, avail, mUpstreamState));
+
+ mFlatHttpRequestHeaders.Append(buf, avail);
+ nsHttpRequestHead *head = mTransaction->RequestHead();
+
+ // We can use the simple double crlf because firefox is the
+ // only client we are parsing
+ int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
+
+ if (endHeader == kNotFound) {
+ // We don't have all the headers yet
+ LOG3(("Http2Stream::ParseHttpRequestHeaders %p "
+ "Need more header bytes. Len = %d",
+ this, mFlatHttpRequestHeaders.Length()));
+ *countUsed = avail;
+ return NS_OK;
+ }
+
+ // We have recvd all the headers, trim the local
+ // buffer of the final empty line, and set countUsed to reflect
+ // the whole header has been consumed.
+ uint32_t oldLen = mFlatHttpRequestHeaders.Length();
+ mFlatHttpRequestHeaders.SetLength(endHeader + 2);
+ *countUsed = avail - (oldLen - endHeader) + 4;
+ mRequestHeadersDone = 1;
+
+ nsAutoCString authorityHeader;
+ nsAutoCString hashkey;
+ head->GetHeader(nsHttp::Host, authorityHeader);
+
+ nsAutoCString requestURI;
+ head->RequestURI(requestURI);
+ CreatePushHashKey(nsDependentCString(head->IsHTTPS() ? "https" : "http"),
+ authorityHeader, mSession->Serial(),
+ requestURI,
+ mOrigin, hashkey);
+
+ // check the push cache for GET
+ if (head->IsGet()) {
+ // from :scheme, :authority, :path
+ nsIRequestContext *requestContext = mTransaction->RequestContext();
+ SpdyPushCache *cache = nullptr;
+ if (requestContext) {
+ requestContext->GetSpdyPushCache(&cache);
+ }
+
+ Http2PushedStream *pushedStream = nullptr;
+
+ // If a push stream is attached to the transaction via onPush, match only with that
+ // one. This occurs when a push was made with in conjunction with a nsIHttpPushListener
+ nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
+ if (trans && (pushedStream = trans->TakePushedStream())) {
+ if (pushedStream->mSession == mSession) {
+ LOG3(("Pushed Stream match based on OnPush correlation %p", pushedStream));
+ } else {
+ LOG3(("Pushed Stream match failed due to stream mismatch %p %d %d\n", pushedStream,
+ pushedStream->mSession->Serial(), mSession->Serial()));
+ pushedStream->OnPushFailed();
+ pushedStream = nullptr;
+ }
+ }
+
+ // we remove the pushedstream from the push cache so that
+ // it will not be used for another GET. This does not destroy the
+ // stream itself - that is done when the transactionhash is done with it.
+ if (cache && !pushedStream){
+ pushedStream = cache->RemovePushedStreamHttp2(hashkey);
+ }
+
+ LOG3(("Pushed Stream Lookup "
+ "session=%p key=%s requestcontext=%p cache=%p hit=%p\n",
+ mSession, hashkey.get(), requestContext, cache, pushedStream));
+
+ if (pushedStream) {
+ LOG3(("Pushed Stream Match located %p id=0x%X key=%s\n",
+ pushedStream, pushedStream->StreamID(), hashkey.get()));
+ pushedStream->SetConsumerStream(this);
+ mPushSource = pushedStream;
+ SetSentFin(true);
+ AdjustPushedPriority();
+
+ // There is probably pushed data buffered so trigger a read manually
+ // as we can't rely on future network events to do it
+ mSession->ConnectPushedStream(this);
+ mOpenGenerated = 1;
+ return NS_OK;
+ }
+ }
+ return NS_OK;
+}
+
+// This is really a headers frame, but open is pretty clear from a workflow pov
+nsresult
+Http2Stream::GenerateOpen()
+{
+ // It is now OK to assign a streamID that we are assured will
+ // be monotonically increasing amongst new streams on this
+ // session
+ mStreamID = mSession->RegisterStreamID(this);
+ MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd");
+ MOZ_ASSERT(!mOpenGenerated);
+
+ mOpenGenerated = 1;
+
+ nsHttpRequestHead *head = mTransaction->RequestHead();
+ nsAutoCString requestURI;
+ head->RequestURI(requestURI);
+ LOG3(("Http2Stream %p Stream ID 0x%X [session=%p] for URI %s\n",
+ this, mStreamID, mSession, requestURI.get()));
+
+ if (mStreamID >= 0x80000000) {
+ // streamID must fit in 31 bits. Evading This is theoretically possible
+ // because stream ID assignment is asynchronous to stream creation
+ // because of the protocol requirement that the new stream ID
+ // be monotonically increasing. In reality this is really not possible
+ // because new streams stop being added to a session with millions of
+ // IDs still available and no race condition is going to bridge that gap;
+ // so we can be comfortable on just erroring out for correctness in that
+ // case.
+ LOG3(("Stream assigned out of range ID: 0x%X", mStreamID));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Now we need to convert the flat http headers into a set
+ // of HTTP/2 headers by writing to mTxInlineFrame{sz}
+
+ nsCString compressedData;
+ nsAutoCString authorityHeader;
+ head->GetHeader(nsHttp::Host, authorityHeader);
+
+ nsDependentCString scheme(head->IsHTTPS() ? "https" : "http");
+ if (head->IsConnect()) {
+ MOZ_ASSERT(mTransaction->QuerySpdyConnectTransaction());
+ mIsTunnel = true;
+ mRequestBodyLenRemaining = 0x0fffffffffffffffULL;
+
+ // Our normal authority has an implicit port, best to use an
+ // explicit one with a tunnel
+ nsHttpConnectionInfo *ci = mTransaction->ConnectionInfo();
+ if (!ci) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ authorityHeader = ci->GetOrigin();
+ authorityHeader.Append(':');
+ authorityHeader.AppendInt(ci->OriginPort());
+ }
+
+ nsAutoCString method;
+ nsAutoCString path;
+ head->Method(method);
+ head->Path(path);
+ mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders,
+ method,
+ path,
+ authorityHeader,
+ scheme,
+ head->IsConnect(),
+ compressedData);
+
+ int64_t clVal = mSession->Compressor()->GetParsedContentLength();
+ if (clVal != -1) {
+ mRequestBodyLenRemaining = clVal;
+ }
+
+ // Determine whether to put the fin bit on the header frame or whether
+ // to wait for a data packet to put it on.
+ uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY;
+
+ if (head->IsGet() ||
+ head->IsHead()) {
+ // for GET and HEAD place the fin bit right on the
+ // header packet
+
+ SetSentFin(true);
+ firstFrameFlags |= Http2Session::kFlag_END_STREAM;
+ } else if (head->IsPost() ||
+ head->IsPut() ||
+ head->IsConnect()) {
+ // place fin in a data frame even for 0 length messages for iterop
+ } else if (!mRequestBodyLenRemaining) {
+ // for other HTTP extension methods, rely on the content-length
+ // to determine whether or not to put fin on headers
+ SetSentFin(true);
+ firstFrameFlags |= Http2Session::kFlag_END_STREAM;
+ }
+
+ // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it exceeds the
+ // 2^14-1 limit for 1 frame. Do it by inserting header size gaps in the existing
+ // frame for the new headers and for the first one a priority field. There is
+ // no question this is ugly, but a 16KB HEADERS frame should be a long
+ // tail event, so this is really just for correctness and a nop in the base case.
+ //
+
+ MOZ_ASSERT(!mTxInlineFrameUsed);
+
+ uint32_t dataLength = compressedData.Length();
+ uint32_t maxFrameData = Http2Session::kMaxFrameData - 5; // 5 bytes for priority
+ uint32_t numFrames = 1;
+
+ if (dataLength > maxFrameData) {
+ numFrames += ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) /
+ Http2Session::kMaxFrameData;
+ MOZ_ASSERT (numFrames > 1);
+ }
+
+ // note that we could still have 1 frame for 0 bytes of data. that's ok.
+
+ uint32_t messageSize = dataLength;
+ messageSize += Http2Session::kFrameHeaderBytes + 5; // frame header + priority overhead in HEADERS frame
+ messageSize += (numFrames - 1) * Http2Session::kFrameHeaderBytes; // frame header overhead in CONTINUATION frames
+
+ EnsureBuffer(mTxInlineFrame, messageSize,
+ mTxInlineFrameUsed, mTxInlineFrameSize);
+
+ mTxInlineFrameUsed += messageSize;
+ UpdatePriorityDependency();
+ LOG3(("Http2Stream %p Generating %d bytes of HEADERS for stream 0x%X with "
+ "priority weight %u dep 0x%X frames %u uri=%s\n",
+ this, mTxInlineFrameUsed, mStreamID, mPriorityWeight,
+ mPriorityDependency, numFrames, requestURI.get()));
+
+ uint32_t outputOffset = 0;
+ uint32_t compressedDataOffset = 0;
+ for (uint32_t idx = 0; idx < numFrames; ++idx) {
+ uint32_t flags, frameLen;
+ bool lastFrame = (idx == numFrames - 1);
+
+ flags = 0;
+ frameLen = maxFrameData;
+ if (!idx) {
+ flags |= firstFrameFlags;
+ // Only the first frame needs the 4-byte offset
+ maxFrameData = Http2Session::kMaxFrameData;
+ }
+ if (lastFrame) {
+ frameLen = dataLength;
+ flags |= Http2Session::kFlag_END_HEADERS;
+ }
+ dataLength -= frameLen;
+
+ mSession->CreateFrameHeader(
+ mTxInlineFrame.get() + outputOffset,
+ frameLen + (idx ? 0 : 5),
+ (idx) ? Http2Session::FRAME_TYPE_CONTINUATION : Http2Session::FRAME_TYPE_HEADERS,
+ flags, mStreamID);
+ outputOffset += Http2Session::kFrameHeaderBytes;
+
+ if (!idx) {
+ uint32_t wireDep = PR_htonl(mPriorityDependency);
+ memcpy(mTxInlineFrame.get() + outputOffset, &wireDep, 4);
+ memcpy(mTxInlineFrame.get() + outputOffset + 4, &mPriorityWeight, 1);
+ outputOffset += 5;
+ }
+
+ memcpy(mTxInlineFrame.get() + outputOffset,
+ compressedData.BeginReading() + compressedDataOffset, frameLen);
+ compressedDataOffset += frameLen;
+ outputOffset += frameLen;
+ }
+
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, compressedData.Length());
+
+ // The size of the input headers is approximate
+ uint32_t ratio =
+ compressedData.Length() * 100 /
+ (11 + requestURI.Length() +
+ mFlatHttpRequestHeaders.Length());
+
+ mFlatHttpRequestHeaders.Truncate();
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
+ return NS_OK;
+}
+
+void
+Http2Stream::AdjustInitialWindow()
+{
+ // The default initial_window is sized for pushed streams. When we
+ // generate a client pulled stream we want to disable flow control for
+ // the stream with a window update. Do the same for pushed streams
+ // when they connect to a pull.
+
+ // >0 even numbered IDs are pushed streams.
+ // odd numbered IDs are pulled streams.
+ // 0 is the sink for a pushed stream.
+ Http2Stream *stream = this;
+ if (!mStreamID) {
+ MOZ_ASSERT(mPushSource);
+ if (!mPushSource)
+ return;
+ stream = mPushSource;
+ MOZ_ASSERT(stream->mStreamID);
+ MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream
+
+ // If the pushed stream has recvd a FIN, there is no reason to update
+ // the window
+ if (stream->RecvdFin() || stream->RecvdReset())
+ return;
+ }
+
+ if (stream->mState == RESERVED_BY_REMOTE) {
+ // h2-14 prevents sending a window update in this state
+ return;
+ }
+
+ // right now mClientReceiveWindow is the lower push limit
+ // bump it up to the pull limit set by the channel or session
+ // don't allow windows less than push
+ uint32_t bump = 0;
+ nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
+ if (trans && trans->InitialRwin()) {
+ bump = (trans->InitialRwin() > mClientReceiveWindow) ?
+ (trans->InitialRwin() - mClientReceiveWindow) : 0;
+ } else {
+ MOZ_ASSERT(mSession->InitialRwin() >= mClientReceiveWindow);
+ bump = mSession->InitialRwin() - mClientReceiveWindow;
+ }
+
+ LOG3(("AdjustInitialwindow increased flow control window %p 0x%X %u\n",
+ this, stream->mStreamID, bump));
+ if (!bump) { // nothing to do
+ return;
+ }
+
+ EnsureBuffer(mTxInlineFrame, mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 4,
+ mTxInlineFrameUsed, mTxInlineFrameSize);
+ uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
+ mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 4;
+
+ mSession->CreateFrameHeader(packet, 4,
+ Http2Session::FRAME_TYPE_WINDOW_UPDATE,
+ 0, stream->mStreamID);
+
+ mClientReceiveWindow += bump;
+ bump = PR_htonl(bump);
+ memcpy(packet + Http2Session::kFrameHeaderBytes, &bump, 4);
+}
+
+void
+Http2Stream::AdjustPushedPriority()
+{
+ // >0 even numbered IDs are pushed streams. odd numbered IDs are pulled streams.
+ // 0 is the sink for a pushed stream.
+
+ if (mStreamID || !mPushSource)
+ return;
+
+ MOZ_ASSERT(mPushSource->mStreamID && !(mPushSource->mStreamID & 1));
+
+ // If the pushed stream has recvd a FIN, there is no reason to update
+ // the window
+ if (mPushSource->RecvdFin() || mPushSource->RecvdReset())
+ return;
+
+ EnsureBuffer(mTxInlineFrame, mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 5,
+ mTxInlineFrameUsed, mTxInlineFrameSize);
+ uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
+ mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 5;
+
+ mSession->CreateFrameHeader(packet, 5,
+ Http2Session::FRAME_TYPE_PRIORITY, 0,
+ mPushSource->mStreamID);
+
+ mPushSource->SetPriority(mPriority);
+ memset(packet + Http2Session::kFrameHeaderBytes, 0, 4);
+ memcpy(packet + Http2Session::kFrameHeaderBytes + 4, &mPriorityWeight, 1);
+
+ LOG3(("AdjustPushedPriority %p id 0x%X to weight %X\n", this, mPushSource->mStreamID,
+ mPriorityWeight));
+}
+
+void
+Http2Stream::UpdateTransportReadEvents(uint32_t count)
+{
+ mTotalRead += count;
+ if (!mSocketTransport) {
+ return;
+ }
+
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_RECEIVING_FROM,
+ mTotalRead);
+}
+
+void
+Http2Stream::UpdateTransportSendEvents(uint32_t count)
+{
+ mTotalSent += count;
+
+ // normally on non-windows platform we use TCP autotuning for
+ // the socket buffers, and this works well (managing enough
+ // buffers for BDP while conserving memory) for HTTP even when
+ // it creates really deep queues. However this 'buffer bloat' is
+ // a problem for http/2 because it ruins the low latency properties
+ // necessary for PING and cancel to work meaningfully.
+ //
+ // If this stream represents a large upload, disable autotuning for
+ // the session and cap the send buffers by default at 128KB.
+ // (10Mbit/sec @ 100ms)
+ //
+ uint32_t bufferSize = gHttpHandler->SpdySendBufferSize();
+ if ((mTotalSent > bufferSize) && !mSetTCPSocketBuffer) {
+ mSetTCPSocketBuffer = 1;
+ mSocketTransport->SetSendBufferSize(bufferSize);
+ }
+
+ if (mUpstreamState != SENDING_FIN_STREAM)
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_SENDING_TO,
+ mTotalSent);
+
+ if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
+ mSentWaitingFor = 1;
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_WAITING_FOR,
+ 0);
+ }
+}
+
+nsresult
+Http2Stream::TransmitFrame(const char *buf,
+ uint32_t *countUsed,
+ bool forceCommitment)
+{
+ // If TransmitFrame returns SUCCESS than all the data is sent (or at least
+ // buffered at the session level), if it returns WOULD_BLOCK then none of
+ // the data is sent.
+
+ // You can call this function with no data and no out parameter in order to
+ // flush internal buffers that were previously blocked on writing. You can
+ // of course feed new data to it as well.
+
+ LOG3(("Http2Stream::TransmitFrame %p inline=%d stream=%d",
+ this, mTxInlineFrameUsed, mTxStreamFrameSize));
+ if (countUsed)
+ *countUsed = 0;
+
+ if (!mTxInlineFrameUsed) {
+ MOZ_ASSERT(!buf);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit");
+ MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader");
+ MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed),
+ "TransmitFrame arguments inconsistent");
+
+ uint32_t transmittedCount;
+ nsresult rv;
+
+ // In the (relatively common) event that we have a small amount of data
+ // split between the inlineframe and the streamframe, then move the stream
+ // data into the inlineframe via copy in order to coalesce into one write.
+ // Given the interaction with ssl this is worth the small copy cost.
+ if (mTxStreamFrameSize && mTxInlineFrameUsed &&
+ mTxStreamFrameSize < Http2Session::kDefaultBufferSize &&
+ mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) {
+ LOG3(("Coalesce Transmit"));
+ memcpy (&mTxInlineFrame[mTxInlineFrameUsed], buf, mTxStreamFrameSize);
+ if (countUsed)
+ *countUsed += mTxStreamFrameSize;
+ mTxInlineFrameUsed += mTxStreamFrameSize;
+ mTxStreamFrameSize = 0;
+ }
+
+ rv =
+ mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed,
+ forceCommitment);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK");
+ mSession->TransactionHasDataToWrite(this);
+ }
+ if (NS_FAILED(rv)) // this will include WOULD_BLOCK
+ return rv;
+
+ // This function calls mSegmentReader->OnReadSegment to report the actual http/2
+ // bytes through to the session object and then the HttpConnection which calls
+ // the socket write function. It will accept all of the inline and stream
+ // data because of the above 'commitment' even if it has to buffer
+
+ rv = mSession->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()),
+ mTxInlineFrameUsed,
+ &transmittedCount);
+ LOG3(("Http2Stream::TransmitFrame for inline BufferOutput session=%p "
+ "stream=%p result %x len=%d",
+ mSession, this, rv, transmittedCount));
+
+ MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
+ "inconsistent inline commitment result");
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed,
+ "inconsistent inline commitment count");
+
+ Http2Session::LogIO(mSession, this, "Writing from Inline Buffer",
+ reinterpret_cast<char*>(mTxInlineFrame.get()),
+ transmittedCount);
+
+ if (mTxStreamFrameSize) {
+ if (!buf) {
+ // this cannot happen
+ MOZ_ASSERT(false, "Stream transmit with null buf argument to "
+ "TransmitFrame()");
+ LOG3(("Stream transmit with null buf argument to TransmitFrame()\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // If there is already data buffered, just add to that to form
+ // a single TLS Application Data Record - otherwise skip the memcpy
+ if (mSession->AmountOfOutputBuffered()) {
+ rv = mSession->BufferOutput(buf, mTxStreamFrameSize,
+ &transmittedCount);
+ } else {
+ rv = mSession->OnReadSegment(buf, mTxStreamFrameSize,
+ &transmittedCount);
+ }
+
+ LOG3(("Http2Stream::TransmitFrame for regular session=%p "
+ "stream=%p result %x len=%d",
+ mSession, this, rv, transmittedCount));
+
+ MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
+ "inconsistent stream commitment result");
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ MOZ_ASSERT(transmittedCount == mTxStreamFrameSize,
+ "inconsistent stream commitment count");
+
+ Http2Session::LogIO(mSession, this, "Writing from Transaction Buffer",
+ buf, transmittedCount);
+
+ *countUsed += mTxStreamFrameSize;
+ }
+
+ mSession->FlushOutputQueue();
+
+ // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
+ UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
+
+ mTxInlineFrameUsed = 0;
+ mTxStreamFrameSize = 0;
+
+ return NS_OK;
+}
+
+void
+Http2Stream::ChangeState(enum upstreamStateType newState)
+{
+ LOG3(("Http2Stream::ChangeState() %p from %X to %X",
+ this, mUpstreamState, newState));
+ mUpstreamState = newState;
+}
+
+void
+Http2Stream::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame)
+{
+ LOG3(("Http2Stream::GenerateDataFrameHeader %p len=%d last=%d",
+ this, dataLength, lastFrame));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty");
+ MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty");
+
+ uint8_t frameFlags = 0;
+ if (lastFrame) {
+ frameFlags |= Http2Session::kFlag_END_STREAM;
+ if (dataLength)
+ SetSentFin(true);
+ }
+
+ mSession->CreateFrameHeader(mTxInlineFrame.get(),
+ dataLength,
+ Http2Session::FRAME_TYPE_DATA,
+ frameFlags, mStreamID);
+
+ mTxInlineFrameUsed = Http2Session::kFrameHeaderBytes;
+ mTxStreamFrameSize = dataLength;
+}
+
+// ConvertResponseHeaders is used to convert the response headers
+// into HTTP/1 format and report some telemetry
+nsresult
+Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor,
+ nsACString &aHeadersIn,
+ nsACString &aHeadersOut,
+ int32_t &httpResponseCode)
+{
+ aHeadersOut.Truncate();
+ aHeadersOut.SetCapacity(aHeadersIn.Length() + 512);
+
+ nsresult rv =
+ decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()),
+ aHeadersIn.Length(),
+ aHeadersOut, false);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Stream::ConvertResponseHeaders %p decode Error\n", this));
+ return rv;
+ }
+
+ nsAutoCString statusString;
+ decompressor->GetStatus(statusString);
+ if (statusString.IsEmpty()) {
+ LOG3(("Http2Stream::ConvertResponseHeaders %p Error - no status\n", this));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult errcode;
+ httpResponseCode = statusString.ToInteger(&errcode);
+ if (mIsTunnel) {
+ LOG3(("Http2Stream %p Tunnel Response code %d", this, httpResponseCode));
+ if ((httpResponseCode / 100) != 2) {
+ MapStreamToPlainText();
+ }
+ }
+
+ if (httpResponseCode == 101) {
+ // 8.1.1 of h2 disallows 101.. throw PROTOCOL_ERROR on stream
+ LOG3(("Http2Stream::ConvertResponseHeaders %p Error - status == 101\n", this));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (aHeadersIn.Length() && aHeadersOut.Length()) {
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length());
+ uint32_t ratio =
+ aHeadersIn.Length() * 100 / aHeadersOut.Length();
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
+ }
+
+ // The decoding went ok. Now we can customize and clean up.
+
+ aHeadersIn.Truncate();
+ aHeadersOut.Append("X-Firefox-Spdy: h2");
+ aHeadersOut.Append("\r\n\r\n");
+ LOG (("decoded response headers are:\n%s", aHeadersOut.BeginReading()));
+ if (mIsTunnel && !mPlainTextTunnel) {
+ aHeadersOut.Truncate();
+ LOG(("Http2Stream::ConvertHeaders %p 0x%X headers removed for tunnel\n",
+ this, mStreamID));
+ }
+ return NS_OK;
+}
+
+// ConvertPushHeaders is used to convert the pushed request headers
+// into HTTP/1 format and report some telemetry
+nsresult
+Http2Stream::ConvertPushHeaders(Http2Decompressor *decompressor,
+ nsACString &aHeadersIn,
+ nsACString &aHeadersOut)
+{
+ aHeadersOut.Truncate();
+ aHeadersOut.SetCapacity(aHeadersIn.Length() + 512);
+ nsresult rv =
+ decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()),
+ aHeadersIn.Length(),
+ aHeadersOut, true);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http2Stream::ConvertPushHeaders %p Error\n", this));
+ return rv;
+ }
+
+ nsCString method;
+ decompressor->GetHost(mHeaderHost);
+ decompressor->GetScheme(mHeaderScheme);
+ decompressor->GetPath(mHeaderPath);
+
+ if (mHeaderHost.IsEmpty() || mHeaderScheme.IsEmpty() || mHeaderPath.IsEmpty()) {
+ LOG3(("Http2Stream::ConvertPushHeaders %p Error - missing required "
+ "host=%s scheme=%s path=%s\n", this, mHeaderHost.get(), mHeaderScheme.get(),
+ mHeaderPath.get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ decompressor->GetMethod(method);
+ if (!method.EqualsLiteral("GET")) {
+ LOG3(("Http2Stream::ConvertPushHeaders %p Error - method not supported: %s\n",
+ this, method.get()));
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ aHeadersIn.Truncate();
+ LOG (("id 0x%X decoded push headers %s %s %s are:\n%s", mStreamID,
+ mHeaderScheme.get(), mHeaderHost.get(), mHeaderPath.get(),
+ aHeadersOut.BeginReading()));
+ return NS_OK;
+}
+
+void
+Http2Stream::Close(nsresult reason)
+{
+ mTransaction->Close(reason);
+}
+
+void
+Http2Stream::SetResponseIsComplete()
+{
+ nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
+ if (trans) {
+ trans->SetResponseIsComplete();
+ }
+}
+
+void
+Http2Stream::SetAllHeadersReceived()
+{
+ if (mAllHeadersReceived) {
+ return;
+ }
+
+ if (mState == RESERVED_BY_REMOTE) {
+ // pushed streams needs to wait until headers have
+ // arrived to open up their window
+ LOG3(("Http2Stream::SetAllHeadersReceived %p state OPEN from reserved\n", this));
+ mState = OPEN;
+ AdjustInitialWindow();
+ }
+
+ mAllHeadersReceived = 1;
+ if (mIsTunnel) {
+ MapStreamToHttpConnection();
+ ClearTransactionsBlockedOnTunnel();
+ }
+ return;
+}
+
+bool
+Http2Stream::AllowFlowControlledWrite()
+{
+ return (mSession->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0);
+}
+
+void
+Http2Stream::UpdateServerReceiveWindow(int32_t delta)
+{
+ mServerReceiveWindow += delta;
+
+ if (mBlockedOnRwin && AllowFlowControlledWrite()) {
+ LOG3(("Http2Stream::UpdateServerReceived UnPause %p 0x%X "
+ "Open stream window\n", this, mStreamID));
+ mSession->TransactionHasDataToWrite(this); }
+}
+
+void
+Http2Stream::SetPriority(uint32_t newPriority)
+{
+ int32_t httpPriority = static_cast<int32_t>(newPriority);
+ if (httpPriority > kWorstPriority) {
+ httpPriority = kWorstPriority;
+ } else if (httpPriority < kBestPriority) {
+ httpPriority = kBestPriority;
+ }
+ mPriority = static_cast<uint32_t>(httpPriority);
+ mPriorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) -
+ (httpPriority - kNormalPriority);
+
+ mPriorityDependency = 0; // maybe adjusted later
+}
+
+void
+Http2Stream::SetPriorityDependency(uint32_t newDependency, uint8_t newWeight,
+ bool exclusive)
+{
+ // undefined what it means when the server sends a priority frame. ignore it.
+ LOG3(("Http2Stream::SetPriorityDependency %p 0x%X received dependency=0x%X "
+ "weight=%u exclusive=%d", this, mStreamID, newDependency, newWeight,
+ exclusive));
+}
+
+void
+Http2Stream::UpdatePriorityDependency()
+{
+ if (!mSession->UseH2Deps()) {
+ return;
+ }
+
+ nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
+ if (!trans) {
+ return;
+ }
+
+ // we create 5 fake dependency streams per session,
+ // these streams are never opened with HEADERS. our first opened stream is 0xd
+ // 3 depends 0, weight 200, leader class (kLeaderGroupID)
+ // 5 depends 0, weight 100, other (kOtherGroupID)
+ // 7 depends 0, weight 0, background (kBackgroundGroupID)
+ // 9 depends 7, weight 0, speculative (kSpeculativeGroupID)
+ // b depends 3, weight 0, follower class (kFollowerGroupID)
+ //
+ // streams for leaders (html, js, css) depend on 3
+ // streams for folowers (images) depend on b
+ // default streams (xhr, async js) depend on 5
+ // explicit bg streams (beacon, etc..) depend on 7
+ // spculative bg streams depend on 9
+
+ uint32_t classFlags = trans->ClassOfService();
+
+ if (classFlags & nsIClassOfService::Leader) {
+ mPriorityDependency = Http2Session::kLeaderGroupID;
+ } else if (classFlags & nsIClassOfService::Follower) {
+ mPriorityDependency = Http2Session::kFollowerGroupID;
+ } else if (classFlags & nsIClassOfService::Speculative) {
+ mPriorityDependency = Http2Session::kSpeculativeGroupID;
+ } else if (classFlags & nsIClassOfService::Background) {
+ mPriorityDependency = Http2Session::kBackgroundGroupID;
+ } else if (classFlags & nsIClassOfService::Unblocked) {
+ mPriorityDependency = Http2Session::kOtherGroupID;
+ } else {
+ mPriorityDependency = Http2Session::kFollowerGroupID; // unmarked followers
+ }
+
+ LOG3(("Http2Stream::UpdatePriorityDependency %p "
+ "classFlags %X depends on stream 0x%X\n",
+ this, classFlags, mPriorityDependency));
+}
+
+void
+Http2Stream::SetRecvdFin(bool aStatus)
+{
+ mRecvdFin = aStatus ? 1 : 0;
+ if (!aStatus)
+ return;
+
+ if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
+ mState = CLOSED_BY_REMOTE;
+ } else if (mState == CLOSED_BY_LOCAL) {
+ mState = CLOSED;
+ }
+}
+
+void
+Http2Stream::SetSentFin(bool aStatus)
+{
+ mSentFin = aStatus ? 1 : 0;
+ if (!aStatus)
+ return;
+
+ if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
+ mState = CLOSED_BY_LOCAL;
+ } else if (mState == CLOSED_BY_REMOTE) {
+ mState = CLOSED;
+ }
+}
+
+void
+Http2Stream::SetRecvdReset(bool aStatus)
+{
+ mRecvdReset = aStatus ? 1 : 0;
+ if (!aStatus)
+ return;
+ mState = CLOSED;
+}
+
+void
+Http2Stream::SetSentReset(bool aStatus)
+{
+ mSentReset = aStatus ? 1 : 0;
+ if (!aStatus)
+ return;
+ mState = CLOSED;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+nsresult
+Http2Stream::OnReadSegment(const char *buf,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ LOG3(("Http2Stream::OnReadSegment %p count=%d state=%x",
+ this, count, mUpstreamState));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mSegmentReader, "OnReadSegment with null mSegmentReader");
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ uint32_t dataLength;
+
+ switch (mUpstreamState) {
+ case GENERATING_HEADERS:
+ // The buffer is the HTTP request stream, including at least part of the
+ // HTTP request header. This state's job is to build a HEADERS frame
+ // from the header information. count is the number of http bytes available
+ // (which may include more than the header), and in countRead we return
+ // the number of those bytes that we consume (i.e. the portion that are
+ // header bytes)
+
+ if (!mRequestHeadersDone) {
+ if (NS_FAILED(rv = ParseHttpRequestHeaders(buf, count, countRead))) {
+ return rv;
+ }
+ }
+
+ if (mRequestHeadersDone && !mOpenGenerated) {
+ if (!mSession->TryToActivate(this)) {
+ LOG3(("Http2Stream::OnReadSegment %p cannot activate now. queued.\n", this));
+ return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ if (NS_FAILED(rv = GenerateOpen())) {
+ return rv;
+ }
+ }
+
+ LOG3(("ParseHttpRequestHeaders %p used %d of %d. "
+ "requestheadersdone = %d mOpenGenerated = %d\n",
+ this, *countRead, count, mRequestHeadersDone, mOpenGenerated));
+ if (mOpenGenerated) {
+ SetHTTPState(OPEN);
+ AdjustInitialWindow();
+ // This version of TransmitFrame cannot block
+ rv = TransmitFrame(nullptr, nullptr, true);
+ ChangeState(GENERATING_BODY);
+ break;
+ }
+ MOZ_ASSERT(*countRead == count, "Header parsing not complete but unused data");
+ break;
+
+ case GENERATING_BODY:
+ // if there is session flow control and either the stream window is active and
+ // exhaused or the session window is exhausted then suspend
+ if (!AllowFlowControlledWrite()) {
+ *countRead = 0;
+ LOG3(("Http2Stream this=%p, id 0x%X request body suspended because "
+ "remote window is stream=%ld session=%ld.\n", this, mStreamID,
+ mServerReceiveWindow, mSession->ServerSessionWindow()));
+ mBlockedOnRwin = true;
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ mBlockedOnRwin = false;
+
+ // The chunk is the smallest of: availableData, configured chunkSize,
+ // stream window, session window, or 14 bit framing limit.
+ // Its amazing we send anything at all.
+ dataLength = std::min(count, mChunkSize);
+
+ if (dataLength > Http2Session::kMaxFrameData)
+ dataLength = Http2Session::kMaxFrameData;
+
+ if (dataLength > mSession->ServerSessionWindow())
+ dataLength = static_cast<uint32_t>(mSession->ServerSessionWindow());
+
+ if (dataLength > mServerReceiveWindow)
+ dataLength = static_cast<uint32_t>(mServerReceiveWindow);
+
+ LOG3(("Http2Stream this=%p id 0x%X send calculation "
+ "avail=%d chunksize=%d stream window=%" PRId64 " session window=%" PRId64 " "
+ "max frame=%d USING=%u\n", this, mStreamID,
+ count, mChunkSize, mServerReceiveWindow, mSession->ServerSessionWindow(),
+ Http2Session::kMaxFrameData, dataLength));
+
+ mSession->DecrementServerSessionWindow(dataLength);
+ mServerReceiveWindow -= dataLength;
+
+ LOG3(("Http2Stream %p id 0x%x request len remaining %" PRId64 ", "
+ "count avail %u, chunk used %u",
+ this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
+ if (!dataLength && mRequestBodyLenRemaining) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ if (dataLength > mRequestBodyLenRemaining) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mRequestBodyLenRemaining -= dataLength;
+ GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
+ ChangeState(SENDING_BODY);
+ MOZ_FALLTHROUGH;
+
+ case SENDING_BODY:
+ MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
+ rv = TransmitFrame(buf, countRead, false);
+ MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
+ "Transmit Frame should be all or nothing");
+
+ LOG3(("TransmitFrame() rv=%x returning %d data bytes. "
+ "Header is %d Body is %d.",
+ rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize));
+
+ // normalize a partial write with a WOULD_BLOCK into just a partial write
+ // as some code will take WOULD_BLOCK to mean an error with nothing
+ // written (e.g. nsHttpTransaction::ReadRequestSegment()
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
+ rv = NS_OK;
+
+ // If that frame was all sent, look for another one
+ if (!mTxInlineFrameUsed)
+ ChangeState(GENERATING_BODY);
+ break;
+
+ case SENDING_FIN_STREAM:
+ MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment");
+ break;
+
+ case UPSTREAM_COMPLETE:
+ MOZ_ASSERT(mPushSource);
+ rv = TransmitFrame(nullptr, nullptr, true);
+ break;
+
+ default:
+ MOZ_ASSERT(false, "Http2Stream::OnReadSegment non-write state");
+ break;
+ }
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+nsresult
+Http2Stream::OnWriteSegment(char *buf,
+ uint32_t count,
+ uint32_t *countWritten)
+{
+ LOG3(("Http2Stream::OnWriteSegment %p count=%d state=%x 0x%X\n",
+ this, count, mUpstreamState, mStreamID));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mSegmentWriter);
+
+ if (mPushSource) {
+ nsresult rv;
+ rv = mPushSource->GetBufferedData(buf, count, countWritten);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mSession->ConnectPushedStream(this);
+ return NS_OK;
+ }
+
+ // sometimes we have read data from the network and stored it in a pipe
+ // so that other streams can proceed when the gecko caller is not processing
+ // data events fast enough and flow control hasn't caught up yet. This
+ // gets the stored data out of that pipe
+ if (!mBypassInputBuffer && mSimpleBuffer.Available()) {
+ *countWritten = mSimpleBuffer.Read(buf, count);
+ MOZ_ASSERT(*countWritten);
+ LOG3(("Http2Stream::OnWriteSegment read from flow control buffer %p %x %d\n",
+ this, mStreamID, *countWritten));
+ return NS_OK;
+ }
+
+ // read from the network
+ return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
+}
+
+/// connect tunnels
+
+void
+Http2Stream::ClearTransactionsBlockedOnTunnel()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (!mIsTunnel) {
+ return;
+ }
+ gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo());
+}
+
+void
+Http2Stream::MapStreamToPlainText()
+{
+ RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
+ MOZ_ASSERT(qiTrans);
+ mPlainTextTunnel = true;
+ qiTrans->ForcePlainText();
+}
+
+void
+Http2Stream::MapStreamToHttpConnection()
+{
+ RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
+ MOZ_ASSERT(qiTrans);
+ qiTrans->MapStreamToHttpConnection(mSocketTransport,
+ mTransaction->ConnectionInfo());
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Stream.h b/netwerk/protocol/http/Http2Stream.h
new file mode 100644
index 000000000..452db5fe0
--- /dev/null
+++ b/netwerk/protocol/http/Http2Stream.h
@@ -0,0 +1,346 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Http2Stream_h
+#define mozilla_net_Http2Stream_h
+
+// HTTP/2 - RFC7540
+// https://www.rfc-editor.org/rfc/rfc7540.txt
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsAHttpTransaction.h"
+#include "nsISupportsPriority.h"
+#include "SimpleBuffer.h"
+
+class nsIInputStream;
+class nsIOutputStream;
+
+namespace mozilla {
+namespace net {
+
+class nsStandardURL;
+class Http2Session;
+class Http2Decompressor;
+
+class Http2Stream
+ : public nsAHttpSegmentReader
+ , public nsAHttpSegmentWriter
+{
+public:
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+
+ enum stateType {
+ IDLE,
+ RESERVED_BY_REMOTE,
+ OPEN,
+ CLOSED_BY_LOCAL,
+ CLOSED_BY_REMOTE,
+ CLOSED
+ };
+
+ const static int32_t kNormalPriority = 0x1000;
+ const static int32_t kWorstPriority = kNormalPriority + nsISupportsPriority::PRIORITY_LOWEST;
+ const static int32_t kBestPriority = kNormalPriority + nsISupportsPriority::PRIORITY_HIGHEST;
+
+ Http2Stream(nsAHttpTransaction *, Http2Session *, int32_t);
+
+ uint32_t StreamID() { return mStreamID; }
+ Http2PushedStream *PushSource() { return mPushSource; }
+
+ stateType HTTPState() { return mState; }
+ void SetHTTPState(stateType val) { mState = val; }
+
+ virtual nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *);
+ virtual nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *);
+ virtual bool DeferCleanup(nsresult status);
+
+ // The consumer stream is the synthetic pull stream hooked up to this stream
+ // http2PushedStream overrides it
+ virtual Http2Stream *GetConsumerStream() { return nullptr; };
+
+ const nsAFlatCString &Origin() const { return mOrigin; }
+ const nsAFlatCString &Host() const { return mHeaderHost; }
+ const nsAFlatCString &Path() const { return mHeaderPath; }
+
+ bool RequestBlockedOnRead()
+ {
+ return static_cast<bool>(mRequestBlockedOnRead);
+ }
+
+ bool HasRegisteredID() { return mStreamID != 0; }
+
+ nsAHttpTransaction *Transaction() { return mTransaction; }
+ virtual nsIRequestContext *RequestContext()
+ {
+ return mTransaction ? mTransaction->RequestContext() : nullptr;
+ }
+
+ void Close(nsresult reason);
+ void SetResponseIsComplete();
+
+ void SetRecvdFin(bool aStatus);
+ bool RecvdFin() { return mRecvdFin; }
+
+ void SetRecvdData(bool aStatus) { mReceivedData = aStatus ? 1 : 0; }
+ bool RecvdData() { return mReceivedData; }
+
+ void SetSentFin(bool aStatus);
+ bool SentFin() { return mSentFin; }
+
+ void SetRecvdReset(bool aStatus);
+ bool RecvdReset() { return mRecvdReset; }
+
+ void SetSentReset(bool aStatus);
+ bool SentReset() { return mSentReset; }
+
+ void SetQueued(bool aStatus) { mQueued = aStatus ? 1 : 0; }
+ bool Queued() { return mQueued; }
+
+ void SetCountAsActive(bool aStatus) { mCountAsActive = aStatus ? 1 : 0; }
+ bool CountAsActive() { return mCountAsActive; }
+
+ void SetAllHeadersReceived();
+ void UnsetAllHeadersReceived() { mAllHeadersReceived = 0; }
+ bool AllHeadersReceived() { return mAllHeadersReceived; }
+
+ void UpdateTransportSendEvents(uint32_t count);
+ void UpdateTransportReadEvents(uint32_t count);
+
+ // NS_ERROR_ABORT terminates stream, other failure terminates session
+ nsresult ConvertResponseHeaders(Http2Decompressor *, nsACString &,
+ nsACString &, int32_t &);
+ nsresult ConvertPushHeaders(Http2Decompressor *, nsACString &, nsACString &);
+
+ bool AllowFlowControlledWrite();
+ void UpdateServerReceiveWindow(int32_t delta);
+ int64_t ServerReceiveWindow() { return mServerReceiveWindow; }
+
+ void DecrementClientReceiveWindow(uint32_t delta) {
+ mClientReceiveWindow -= delta;
+ mLocalUnacked += delta;
+ }
+
+ void IncrementClientReceiveWindow(uint32_t delta) {
+ mClientReceiveWindow += delta;
+ mLocalUnacked -= delta;
+ }
+
+ uint64_t LocalUnAcked();
+ int64_t ClientReceiveWindow() { return mClientReceiveWindow; }
+
+ bool BlockedOnRwin() { return mBlockedOnRwin; }
+
+ uint32_t Priority() { return mPriority; }
+ void SetPriority(uint32_t);
+ void SetPriorityDependency(uint32_t, uint8_t, bool);
+ void UpdatePriorityDependency();
+
+ // A pull stream has an implicit sink, a pushed stream has a sink
+ // once it is matched to a pull stream.
+ virtual bool HasSink() { return true; }
+
+ virtual ~Http2Stream();
+
+ Http2Session *Session() { return mSession; }
+
+ static nsresult MakeOriginURL(const nsACString &origin,
+ RefPtr<nsStandardURL> &url);
+
+ static nsresult MakeOriginURL(const nsACString &scheme,
+ const nsACString &origin,
+ RefPtr<nsStandardURL> &url);
+
+protected:
+ static void CreatePushHashKey(const nsCString &scheme,
+ const nsCString &hostHeader,
+ uint64_t serial,
+ const nsCSubstring &pathInfo,
+ nsCString &outOrigin,
+ nsCString &outKey);
+
+ // These internal states track request generation
+ enum upstreamStateType {
+ GENERATING_HEADERS,
+ GENERATING_BODY,
+ SENDING_BODY,
+ SENDING_FIN_STREAM,
+ UPSTREAM_COMPLETE
+ };
+
+ uint32_t mStreamID;
+
+ // The session that this stream is a subset of
+ Http2Session *mSession;
+
+ // These are temporary state variables to hold the argument to
+ // Read/WriteSegments so it can be accessed by On(read/write)segment
+ // further up the stack.
+ nsAHttpSegmentReader *mSegmentReader;
+ nsAHttpSegmentWriter *mSegmentWriter;
+
+ nsCString mOrigin;
+ nsCString mHeaderHost;
+ nsCString mHeaderScheme;
+ nsCString mHeaderPath;
+
+ // Each stream goes from generating_headers to upstream_complete, perhaps
+ // looping on multiple instances of generating_body and
+ // sending_body for each frame in the upload.
+ enum upstreamStateType mUpstreamState;
+
+ // The HTTP/2 state for the stream from section 5.1
+ enum stateType mState;
+
+ // Flag is set when all http request headers have been read ID is not stable
+ uint32_t mRequestHeadersDone : 1;
+
+ // Flag is set when ID is stable and concurrency limits are met
+ uint32_t mOpenGenerated : 1;
+
+ // Flag is set when all http response headers have been read
+ uint32_t mAllHeadersReceived : 1;
+
+ // Flag is set when stream is queued inside the session due to
+ // concurrency limits being exceeded
+ uint32_t mQueued : 1;
+
+ void ChangeState(enum upstreamStateType);
+
+ virtual void AdjustInitialWindow();
+ nsresult TransmitFrame(const char *, uint32_t *, bool forceCommitment);
+
+private:
+ friend class nsAutoPtr<Http2Stream>;
+
+ nsresult ParseHttpRequestHeaders(const char *, uint32_t, uint32_t *);
+ nsresult GenerateOpen();
+
+ void AdjustPushedPriority();
+ void GenerateDataFrameHeader(uint32_t, bool);
+
+ nsresult BufferInput(uint32_t , uint32_t *);
+
+ // The underlying HTTP transaction. This pointer is used as the key
+ // in the Http2Session mStreamTransactionHash so it is important to
+ // keep a reference to it as long as this stream is a member of that hash.
+ // (i.e. don't change it or release it after it is set in the ctor).
+ RefPtr<nsAHttpTransaction> mTransaction;
+
+ // The underlying socket transport object is needed to propogate some events
+ nsISocketTransport *mSocketTransport;
+
+ // The quanta upstream data frames are chopped into
+ uint32_t mChunkSize;
+
+ // Flag is set when the HTTP processor has more data to send
+ // but has blocked in doing so.
+ uint32_t mRequestBlockedOnRead : 1;
+
+ // Flag is set after the response frame bearing the fin bit has
+ // been processed. (i.e. after the server has closed).
+ uint32_t mRecvdFin : 1;
+
+ // Flag is set after 1st DATA frame has been passed to stream
+ uint32_t mReceivedData : 1;
+
+ // Flag is set after RST_STREAM has been received for this stream
+ uint32_t mRecvdReset : 1;
+
+ // Flag is set after RST_STREAM has been generated for this stream
+ uint32_t mSentReset : 1;
+
+ // Flag is set when stream is counted towards MAX_CONCURRENT streams in session
+ uint32_t mCountAsActive : 1;
+
+ // Flag is set when a FIN has been placed on a data or header frame
+ // (i.e after the client has closed)
+ uint32_t mSentFin : 1;
+
+ // Flag is set after the WAITING_FOR Transport event has been generated
+ uint32_t mSentWaitingFor : 1;
+
+ // Flag is set after TCP send autotuning has been disabled
+ uint32_t mSetTCPSocketBuffer : 1;
+
+ // Flag is set when OnWriteSegment is being called directly from stream instead
+ // of transaction
+ uint32_t mBypassInputBuffer : 1;
+
+ // The InlineFrame and associated data is used for composing control
+ // frames and data frame headers.
+ UniquePtr<uint8_t[]> mTxInlineFrame;
+ uint32_t mTxInlineFrameSize;
+ uint32_t mTxInlineFrameUsed;
+
+ // mTxStreamFrameSize tracks the progress of
+ // transmitting a request body data frame. The data frame itself
+ // is never copied into the spdy layer.
+ uint32_t mTxStreamFrameSize;
+
+ // Buffer for request header compression.
+ nsCString mFlatHttpRequestHeaders;
+
+ // Track the content-length of a request body so that we can
+ // place the fin flag on the last data packet instead of waiting
+ // for a stream closed indication. Relying on stream close results
+ // in an extra 0-length runt packet and seems to have some interop
+ // problems with the google servers. Connect does rely on stream
+ // close by setting this to the max value.
+ int64_t mRequestBodyLenRemaining;
+
+ uint32_t mPriority; // geckoish weight
+ uint32_t mPriorityDependency; // h2 stream id 3 - 0xb
+ uint8_t mPriorityWeight; // h2 weight
+
+ // mClientReceiveWindow, mServerReceiveWindow, and mLocalUnacked are for flow control.
+ // *window are signed because the race conditions in asynchronous SETTINGS
+ // messages can force them temporarily negative.
+
+ // mClientReceiveWindow is how much data the server will send without getting a
+ // window update
+ int64_t mClientReceiveWindow;
+
+ // mServerReceiveWindow is how much data the client is allowed to send without
+ // getting a window update
+ int64_t mServerReceiveWindow;
+
+ // LocalUnacked is the number of bytes received by the client but not
+ // yet reflected in a window update. Sending that update will increment
+ // ClientReceiveWindow
+ uint64_t mLocalUnacked;
+
+ // True when sending is suspended becuase the server receive window is
+ // <= 0
+ bool mBlockedOnRwin;
+
+ // For Progress Events
+ uint64_t mTotalSent;
+ uint64_t mTotalRead;
+
+ // For Http2Push
+ Http2PushedStream *mPushSource;
+
+ // Used to store stream data when the transaction channel cannot keep up
+ // and flow control has not yet kicked in.
+ SimpleBuffer mSimpleBuffer;
+
+/// connect tunnels
+public:
+ bool IsTunnel() { return mIsTunnel; }
+private:
+ void ClearTransactionsBlockedOnTunnel();
+ void MapStreamToPlainText();
+ void MapStreamToHttpConnection();
+
+ bool mIsTunnel;
+ bool mPlainTextTunnel;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http2Stream_h
diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp
new file mode 100644
index 000000000..66252b82f
--- /dev/null
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -0,0 +1,3715 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/net/HttpBaseChannel.h"
+
+#include "nsHttpHandler.h"
+#include "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+
+#include "nsICachingChannel.h"
+#include "nsIDOMDocument.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptError.h"
+#include "nsISeekableStream.h"
+#include "nsIStorageStream.h"
+#include "nsITimedChannel.h"
+#include "nsIEncodedChannel.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIMutableArray.h"
+#include "nsEscape.h"
+#include "nsStreamListenerWrapper.h"
+#include "nsISecurityConsoleMessage.h"
+#include "nsURLHelper.h"
+#include "nsICookieService.h"
+#include "nsIStreamConverterService.h"
+#include "nsCRT.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIObserverService.h"
+#include "nsProxyRelease.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsINetworkInterceptController.h"
+#include "mozilla/dom/Performance.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsStreamUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsIChannelEventSink.h"
+#include "nsILoadGroupChild.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "LoadInfo.h"
+#include "nsNullPrincipal.h"
+#include "nsISSLSocketControl.h"
+#include "mozilla/Telemetry.h"
+#include "nsIURL.h"
+#include "nsIConsoleService.h"
+#include "mozilla/BinarySearch.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIXULRuntime.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIDOMWindowUtils.h"
+
+#include <algorithm>
+#include "HttpBaseChannel.h"
+
+namespace mozilla {
+namespace net {
+
+HttpBaseChannel::HttpBaseChannel()
+ : mStartPos(UINT64_MAX)
+ , mStatus(NS_OK)
+ , mLoadFlags(LOAD_NORMAL)
+ , mCaps(0)
+ , mClassOfService(0)
+ , mPriority(PRIORITY_NORMAL)
+ , mRedirectionLimit(gHttpHandler->RedirectionLimit())
+ , mApplyConversion(true)
+ , mCanceled(false)
+ , mIsPending(false)
+ , mWasOpened(false)
+ , mRequestObserversCalled(false)
+ , mResponseHeadersModified(false)
+ , mAllowPipelining(true)
+ , mAllowSTS(true)
+ , mThirdPartyFlags(0)
+ , mUploadStreamHasHeaders(false)
+ , mInheritApplicationCache(true)
+ , mChooseApplicationCache(false)
+ , mLoadedFromApplicationCache(false)
+ , mChannelIsForDownload(false)
+ , mTracingEnabled(true)
+ , mTimingEnabled(false)
+ , mAllowSpdy(true)
+ , mAllowAltSvc(true)
+ , mBeConservative(false)
+ , mResponseTimeoutEnabled(true)
+ , mAllRedirectsSameOrigin(true)
+ , mAllRedirectsPassTimingAllowCheck(true)
+ , mResponseCouldBeSynthesized(false)
+ , mBlockAuthPrompt(false)
+ , mAllowStaleCacheContent(false)
+ , mSuspendCount(0)
+ , mInitialRwin(0)
+ , mProxyResolveFlags(0)
+ , mProxyURI(nullptr)
+ , mContentDispositionHint(UINT32_MAX)
+ , mHttpHandler(gHttpHandler)
+ , mReferrerPolicy(REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE)
+ , mRedirectCount(0)
+ , mForcePending(false)
+ , mCorsIncludeCredentials(false)
+ , mCorsMode(nsIHttpChannelInternal::CORS_MODE_NO_CORS)
+ , mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW)
+ , mFetchCacheMode(nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT)
+ , mOnStartRequestCalled(false)
+ , mOnStopRequestCalled(false)
+ , mAfterOnStartRequestBegun(false)
+ , mTransferSize(0)
+ , mDecodedBodySize(0)
+ , mEncodedBodySize(0)
+ , mContentWindowId(0)
+ , mRequireCORSPreflight(false)
+ , mReportCollector(new ConsoleReportCollector())
+ , mForceMainDocumentChannel(false)
+{
+ LOG(("Creating HttpBaseChannel @%x\n", this));
+
+ // Subfields of unions cannot be targeted in an initializer list.
+#ifdef MOZ_VALGRIND
+ // Zero the entire unions so that Valgrind doesn't complain when we send them
+ // to another process.
+ memset(&mSelfAddr, 0, sizeof(NetAddr));
+ memset(&mPeerAddr, 0, sizeof(NetAddr));
+#endif
+ mSelfAddr.raw.family = PR_AF_UNSPEC;
+ mPeerAddr.raw.family = PR_AF_UNSPEC;
+ mRequestContextID.Clear();
+}
+
+HttpBaseChannel::~HttpBaseChannel()
+{
+ LOG(("Destroying HttpBaseChannel @%x\n", this));
+
+ NS_ReleaseOnMainThread(mLoadInfo.forget());
+
+ // Make sure we don't leak
+ CleanRedirectCacheChainIfNecessary();
+}
+
+nsresult
+HttpBaseChannel::Init(nsIURI *aURI,
+ uint32_t aCaps,
+ nsProxyInfo *aProxyInfo,
+ uint32_t aProxyResolveFlags,
+ nsIURI *aProxyURI,
+ const nsID& aChannelId)
+{
+ LOG(("HttpBaseChannel::Init [this=%p]\n", this));
+
+ NS_PRECONDITION(aURI, "null uri");
+
+ mURI = aURI;
+ mOriginalURI = aURI;
+ mDocumentURI = nullptr;
+ mCaps = aCaps;
+ mProxyResolveFlags = aProxyResolveFlags;
+ mProxyURI = aProxyURI;
+ mChannelId = aChannelId;
+
+ // Construct connection info object
+ nsAutoCString host;
+ int32_t port = -1;
+ bool isHTTPS = false;
+
+ nsresult rv = mURI->SchemeIs("https", &isHTTPS);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mURI->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ // Reject the URL if it doesn't specify a host
+ if (host.IsEmpty())
+ return NS_ERROR_MALFORMED_URI;
+
+ rv = mURI->GetPort(&port);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG(("host=%s port=%d\n", host.get(), port));
+
+ rv = mURI->GetAsciiSpec(mSpec);
+ if (NS_FAILED(rv)) return rv;
+ LOG(("uri=%s\n", mSpec.get()));
+
+ // Assert default request method
+ MOZ_ASSERT(mRequestHead.EqualsMethod(nsHttpRequestHead::kMethod_Get));
+
+ // Set request headers
+ nsAutoCString hostLine;
+ rv = nsHttpHandler::GenerateHostPort(host, port, hostLine);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mRequestHead.SetHeader(nsHttp::Host, hostLine);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = gHttpHandler->AddStandardRequestHeaders(&mRequestHead, isHTTPS);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString type;
+ if (aProxyInfo && NS_SUCCEEDED(aProxyInfo->GetType(type)) &&
+ !type.EqualsLiteral("unknown"))
+ mProxyInfo = aProxyInfo;
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpBaseChannel)
+NS_IMPL_RELEASE(HttpBaseChannel)
+
+NS_INTERFACE_MAP_BEGIN(HttpBaseChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY(nsIForcePendingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsITimedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIConsoleReportCollector)
+ NS_INTERFACE_MAP_ENTRY(nsIThrottledInputChannel)
+NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetName(nsACString& aName)
+{
+ aName = mSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsPending(bool *aIsPending)
+{
+ NS_ENSURE_ARG_POINTER(aIsPending);
+ *aIsPending = mIsPending || mForcePending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetStatus(nsresult *aStatus)
+{
+ NS_ENSURE_ARG_POINTER(aStatus);
+ *aStatus = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ NS_ENSURE_ARG_POINTER(aLoadGroup);
+ *aLoadGroup = mLoadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+
+ if (!CanSetLoadGroup(aLoadGroup)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoadGroup = aLoadGroup;
+ mProgressSink = nullptr;
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ NS_ENSURE_ARG_POINTER(aLoadFlags);
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ bool synthesized = false;
+ nsresult rv = GetResponseSynthesized(&synthesized);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If this channel is marked as awaiting a synthesized response,
+ // modifying certain load flags can interfere with the implementation
+ // of the network interception logic. This takes care of a couple
+ // known cases that attempt to mark channels as anonymous due
+ // to cross-origin redirects; since the response is entirely synthesized
+ // this is an unnecessary precaution.
+ // This should be removed when bug 1201683 is fixed.
+ if (synthesized && aLoadFlags != mLoadFlags) {
+ aLoadFlags &= ~LOAD_ANONYMOUS;
+ }
+
+ mLoadFlags = aLoadFlags;
+ mForceMainDocumentChannel = (aLoadFlags & LOAD_DOCUMENT_URI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDocshellUserAgentOverride()
+{
+ // This sets the docshell specific user agent override, it will be overwritten
+ // by UserAgentOverrides.jsm if site-specific user agent overrides are set.
+ nsresult rv;
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ if (!loadContext) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
+ if (!domWindow) {
+ return NS_OK;
+ }
+
+ auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow);
+ nsIDocShell* docshell = pDomWindow->GetDocShell();
+ if (!docshell) {
+ return NS_OK;
+ }
+
+ nsString customUserAgent;
+ docshell->GetCustomUserAgent(customUserAgent);
+ if (customUserAgent.IsEmpty()) {
+ return NS_OK;
+ }
+
+ NS_ConvertUTF16toUTF8 utf8CustomUserAgent(customUserAgent);
+ rv = SetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), utf8CustomUserAgent, false);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOriginalURI(nsIURI **aOriginalURI)
+{
+ NS_ENSURE_ARG_POINTER(aOriginalURI);
+ *aOriginalURI = mOriginalURI;
+ NS_ADDREF(*aOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetOriginalURI(nsIURI *aOriginalURI)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ NS_ENSURE_ARG_POINTER(aOriginalURI);
+ mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetURI(nsIURI **aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ *aURI = mURI;
+ NS_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOwner(nsISupports **aOwner)
+{
+ NS_ENSURE_ARG_POINTER(aOwner);
+ *aOwner = mOwner;
+ NS_IF_ADDREF(*aOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetOwner(nsISupports *aOwner)
+{
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLoadInfo(nsILoadInfo *aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLoadInfo(nsILoadInfo **aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
+{
+ *aCallbacks = mCallbacks;
+ NS_IF_ADDREF(*aCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+
+ if (!CanSetCallbacks(aCallbacks)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCallbacks = aCallbacks;
+ mProgressSink = nullptr;
+
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentType(nsACString& aContentType)
+{
+ if (!mResponseHead) {
+ aContentType.Truncate();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mResponseHead->ContentType(aContentType);
+ if (!aContentType.IsEmpty()) {
+ return NS_OK;
+ }
+
+ aContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentType(const nsACString& aContentType)
+{
+ if (mListener || mWasOpened) {
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoCString contentTypeBuf, charsetBuf;
+ bool hadCharset;
+ net_ParseContentType(aContentType, contentTypeBuf, charsetBuf, &hadCharset);
+
+ mResponseHead->SetContentType(contentTypeBuf);
+
+ // take care not to stomp on an existing charset
+ if (hadCharset)
+ mResponseHead->SetContentCharset(charsetBuf);
+
+ } else {
+ // We are being given a content-type hint.
+ bool dummy;
+ net_ParseContentType(aContentType, mContentTypeHint, mContentCharsetHint,
+ &dummy);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentCharset(nsACString& aContentCharset)
+{
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ mResponseHead->ContentCharset(aContentCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentCharset(const nsACString& aContentCharset)
+{
+ if (mListener) {
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ mResponseHead->SetContentCharset(aContentCharset);
+ } else {
+ // Charset hint
+ mContentCharsetHint = aContentCharset;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ nsresult rv;
+ nsCString header;
+
+ rv = GetContentDispositionHeader(header);
+ if (NS_FAILED(rv)) {
+ if (mContentDispositionHint == UINT32_MAX)
+ return rv;
+
+ *aContentDisposition = mContentDispositionHint;
+ return NS_OK;
+ }
+
+ *aContentDisposition = NS_GetContentDispositionFromHeader(header, this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ mContentDispositionHint = aContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDispositionFilename(nsAString& aContentDispositionFilename)
+{
+ aContentDispositionFilename.Truncate();
+ nsresult rv;
+ nsCString header;
+
+ rv = GetContentDispositionHeader(header);
+ if (NS_FAILED(rv)) {
+ if (!mContentDispositionFilename)
+ return rv;
+
+ aContentDispositionFilename = *mContentDispositionFilename;
+ return NS_OK;
+ }
+
+ return NS_GetFilenameFromDisposition(aContentDispositionFilename,
+ header, mURI);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentDispositionFilename(const nsAString& aContentDispositionFilename)
+{
+ mContentDispositionFilename = new nsString(aContentDispositionFilename);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDispositionHeader(nsACString& aContentDispositionHeader)
+{
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = mResponseHead->GetHeader(nsHttp::Content_Disposition,
+ aContentDispositionHeader);
+ if (NS_FAILED(rv) || aContentDispositionHeader.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentLength(int64_t *aContentLength)
+{
+ NS_ENSURE_ARG_POINTER(aContentLength);
+
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *aContentLength = mResponseHead->ContentLength();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentLength(int64_t value)
+{
+ NS_NOTYETIMPLEMENTED("HttpBaseChannel::SetContentLength");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::Open(nsIInputStream **aResult)
+{
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS);
+
+ if (!gHttpHandler->Active()) {
+ LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_ImplementChannelOpen(this, aResult);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::Open2(nsIInputStream** aStream)
+{
+ if (!gHttpHandler->Active()) {
+ LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIUploadChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetUploadStream(nsIInputStream **stream)
+{
+ NS_ENSURE_ARG_POINTER(stream);
+ *stream = mUploadStream;
+ NS_IF_ADDREF(*stream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetUploadStream(nsIInputStream *stream,
+ const nsACString &contentTypeArg,
+ int64_t contentLength)
+{
+ // NOTE: for backwards compatibility and for compatibility with old style
+ // plugins, |stream| may include headers, specifically Content-Type and
+ // Content-Length headers. in this case, |contentType| and |contentLength|
+ // would be unspecified. this is traditionally the case of a POST request,
+ // and so we select POST as the request method if contentType and
+ // contentLength are unspecified.
+
+ if (stream) {
+ nsAutoCString method;
+ bool hasHeaders;
+
+ // This method and ExplicitSetUploadStream mean different things by "empty
+ // content type string". This method means "no header", but
+ // ExplicitSetUploadStream means "header with empty value". So we have to
+ // massage the contentType argument into the form ExplicitSetUploadStream
+ // expects.
+ nsAutoCString contentType;
+ if (contentTypeArg.IsEmpty()) {
+ method = NS_LITERAL_CSTRING("POST");
+ hasHeaders = true;
+ contentType.SetIsVoid(true);
+ } else {
+ method = NS_LITERAL_CSTRING("PUT");
+ hasHeaders = false;
+ contentType = contentTypeArg;
+ }
+ return ExplicitSetUploadStream(stream, contentType, contentLength,
+ method, hasHeaders);
+ }
+
+ // if stream is null, ExplicitSetUploadStream returns error.
+ // So we need special case for GET method.
+ mUploadStreamHasHeaders = false;
+ mRequestHead.SetMethod(NS_LITERAL_CSTRING("GET")); // revert to GET request
+ mUploadStream = stream;
+ return NS_OK;
+}
+
+namespace {
+
+void
+CopyComplete(void* aClosure, nsresult aStatus) {
+ // Called on the STS thread by NS_AsyncCopy
+ auto channel = static_cast<HttpBaseChannel*>(aClosure);
+ nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<nsresult>(
+ channel, &HttpBaseChannel::EnsureUploadStreamIsCloneableComplete, aStatus);
+ NS_DispatchToMainThread(runnable.forget());
+}
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+HttpBaseChannel::EnsureUploadStreamIsCloneable(nsIRunnable* aCallback)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+ NS_ENSURE_ARG_POINTER(aCallback);
+
+ // We could in theory allow multiple callers to use this method,
+ // but the complexity does not seem worth it yet. Just fail if
+ // this is called more than once simultaneously.
+ NS_ENSURE_FALSE(mUploadCloneableCallback, NS_ERROR_UNEXPECTED);
+
+ // If the CloneUploadStream() will succeed, then synchronously invoke
+ // the callback to indicate we're already cloneable.
+ if (!mUploadStream || NS_InputStreamIsCloneable(mUploadStream)) {
+ aCallback->Run();
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIStorageStream> storageStream;
+ nsresult rv = NS_NewStorageStream(4096, UINT32_MAX,
+ getter_AddRefs(storageStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> newUploadStream;
+ rv = storageStream->NewInputStream(0, getter_AddRefs(newUploadStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> sink;
+ rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> source;
+ if (NS_InputStreamIsBuffered(mUploadStream)) {
+ source = mUploadStream;
+ } else {
+ rv = NS_NewBufferedInputStream(getter_AddRefs(source), mUploadStream, 4096);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+
+ mUploadCloneableCallback = aCallback;
+
+ rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS,
+ 4096, // copy segment size
+ CopyComplete, this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mUploadCloneableCallback = nullptr;
+ return rv;
+ }
+
+ // Since we're consuming the old stream, replace it with the new
+ // stream immediately.
+ mUploadStream = newUploadStream;
+
+ // Explicity hold the stream alive until copying is complete. This will
+ // be released in EnsureUploadStreamIsCloneableComplete().
+ AddRef();
+
+ return NS_OK;
+}
+
+void
+HttpBaseChannel::EnsureUploadStreamIsCloneableComplete(nsresult aStatus)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+ MOZ_ASSERT(mUploadCloneableCallback);
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ mUploadCloneableCallback->Run();
+ mUploadCloneableCallback = nullptr;
+
+ // Release the reference we grabbed in EnsureUploadStreamIsCloneable() now
+ // that the copying is complete.
+ Release();
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::CloneUploadStream(nsIInputStream** aClonedStream)
+{
+ NS_ENSURE_ARG_POINTER(aClonedStream);
+ *aClonedStream = nullptr;
+
+ if (!mUploadStream) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> clonedStream;
+ nsresult rv = NS_CloneInputStream(mUploadStream, getter_AddRefs(clonedStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ clonedStream.forget(aClonedStream);
+
+ return NS_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIUploadChannel2
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream *aStream,
+ const nsACString &aContentType,
+ int64_t aContentLength,
+ const nsACString &aMethod,
+ bool aStreamHasHeaders)
+{
+ // Ensure stream is set and method is valid
+ NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE);
+
+ if (aContentLength < 0 && !aStreamHasHeaders) {
+ nsresult rv = aStream->Available(reinterpret_cast<uint64_t*>(&aContentLength));
+ if (NS_FAILED(rv) || aContentLength < 0) {
+ NS_ERROR("unable to determine content length");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ nsresult rv = SetRequestMethod(aMethod);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aStreamHasHeaders) {
+ // SetRequestHeader propagates headers to chrome if HttpChannelChild
+ nsAutoCString contentLengthStr;
+ contentLengthStr.AppendInt(aContentLength);
+ SetRequestHeader(NS_LITERAL_CSTRING("Content-Length"), contentLengthStr,
+ false);
+ if (!aContentType.IsVoid()) {
+ if (aContentType.IsEmpty()) {
+ SetEmptyRequestHeader(NS_LITERAL_CSTRING("Content-Type"));
+ } else {
+ SetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), aContentType,
+ false);
+ }
+ }
+ }
+
+ mUploadStreamHasHeaders = aStreamHasHeaders;
+ mUploadStream = aStream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetUploadStreamHasHeaders(bool *hasHeaders)
+{
+ NS_ENSURE_ARG(hasHeaders);
+
+ *hasHeaders = mUploadStreamHasHeaders;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIEncodedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetApplyConversion(bool *value)
+{
+ *value = mApplyConversion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetApplyConversion(bool value)
+{
+ LOG(("HttpBaseChannel::SetApplyConversion [this=%p value=%d]\n", this, value));
+ mApplyConversion = value;
+ return NS_OK;
+}
+
+nsresult
+HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
+ nsIStreamListener** aNewNextListener)
+{
+ return DoApplyContentConversions(aNextListener,
+ aNewNextListener,
+ mListenerContext);
+}
+
+// create a listener chain that looks like this
+// http-channel -> decompressor (n times) -> InterceptFailedOnSTop -> channel-creator-listener
+//
+// we need to do this because not every decompressor has fully streamed output so
+// may need a call to OnStopRequest to identify its completion state.. and if it
+// creates an error there the channel status code needs to be updated before calling
+// the terminal listener. Having the decompress do it via cancel() means channels cannot
+// effectively be used in two contexts (specifically this one and a peek context for
+// sniffing)
+//
+class InterceptFailedOnStop : public nsIStreamListener
+{
+ virtual ~InterceptFailedOnStop() {}
+ nsCOMPtr<nsIStreamListener> mNext;
+ HttpBaseChannel *mChannel;
+
+public:
+ InterceptFailedOnStop(nsIStreamListener *arg, HttpBaseChannel *chan)
+ : mNext(arg)
+ , mChannel(chan) {}
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) override
+ {
+ return mNext->OnStartRequest(aRequest, aContext);
+ }
+
+ NS_IMETHOD OnStopRequest(nsIRequest *aRequest, nsISupports *aContext, nsresult aStatusCode) override
+ {
+ if (NS_FAILED(aStatusCode) && NS_SUCCEEDED(mChannel->mStatus)) {
+ LOG(("HttpBaseChannel::InterceptFailedOnStop %p seting status %x", mChannel, aStatusCode));
+ mChannel->mStatus = aStatusCode;
+ }
+ return mNext->OnStopRequest(aRequest, aContext, aStatusCode);
+ }
+
+ NS_IMETHOD OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext,
+ nsIInputStream *aInputStream, uint64_t aOffset,
+ uint32_t aCount) override
+ {
+ return mNext->OnDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
+ }
+};
+
+NS_IMPL_ISUPPORTS(InterceptFailedOnStop, nsIStreamListener, nsIRequestObserver)
+
+NS_IMETHODIMP
+HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
+ nsIStreamListener** aNewNextListener,
+ nsISupports *aCtxt)
+{
+ *aNewNextListener = nullptr;
+ if (!mResponseHead || ! aNextListener) {
+ return NS_OK;
+ }
+
+ LOG(("HttpBaseChannel::DoApplyContentConversions [this=%p]\n", this));
+
+ if (!mApplyConversion) {
+ LOG(("not applying conversion per mApplyConversion\n"));
+ return NS_OK;
+ }
+
+ if (!mAvailableCachedAltDataType.IsEmpty()) {
+ LOG(("not applying conversion because delivering alt-data\n"));
+ return NS_OK;
+ }
+
+ nsAutoCString contentEncoding;
+ nsresult rv = mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+ if (NS_FAILED(rv) || contentEncoding.IsEmpty())
+ return NS_OK;
+
+ nsCOMPtr<nsIStreamListener> nextListener = new InterceptFailedOnStop(aNextListener, this);
+
+ // The encodings are listed in the order they were applied
+ // (see rfc 2616 section 14.11), so they need to removed in reverse
+ // order. This is accomplished because the converter chain ends up
+ // being a stack with the last converter created being the first one
+ // to accept the raw network data.
+
+ char* cePtr = contentEncoding.BeginWriting();
+ uint32_t count = 0;
+ while (char* val = nsCRT::strtok(cePtr, HTTP_LWS ",", &cePtr)) {
+ if (++count > 16) {
+ // That's ridiculous. We only understand 2 different ones :)
+ // but for compatibility with old code, we will just carry on without
+ // removing the encodings
+ LOG(("Too many Content-Encodings. Ignoring remainder.\n"));
+ break;
+ }
+
+ bool isHTTPS = false;
+ mURI->SchemeIs("https", &isHTTPS);
+ if (gHttpHandler->IsAcceptableEncoding(val, isHTTPS)) {
+ nsCOMPtr<nsIStreamConverterService> serv;
+ rv = gHttpHandler->GetStreamConverterService(getter_AddRefs(serv));
+
+ // we won't fail to load the page just because we couldn't load the
+ // stream converter service.. carry on..
+ if (NS_FAILED(rv)) {
+ if (val)
+ LOG(("Unknown content encoding '%s', ignoring\n", val));
+ continue;
+ }
+
+ nsCOMPtr<nsIStreamListener> converter;
+ nsAutoCString from(val);
+ ToLowerCase(from);
+ rv = serv->AsyncConvertData(from.get(),
+ "uncompressed",
+ nextListener,
+ aCtxt,
+ getter_AddRefs(converter));
+ if (NS_FAILED(rv)) {
+ LOG(("Unexpected failure of AsyncConvertData %s\n", val));
+ return rv;
+ }
+
+ LOG(("converter removed '%s' content-encoding\n", val));
+ if (gHttpHandler->IsTelemetryEnabled()) {
+ int mode = 0;
+ if (from.Equals("gzip") || from.Equals("x-gzip")) {
+ mode = 1;
+ } else if (from.Equals("deflate") || from.Equals("x-deflate")) {
+ mode = 2;
+ } else if (from.Equals("br")) {
+ mode = 3;
+ }
+ Telemetry::Accumulate(Telemetry::HTTP_CONTENT_ENCODING, mode);
+ }
+ nextListener = converter;
+ }
+ else {
+ if (val)
+ LOG(("Unknown content encoding '%s', ignoring\n", val));
+ }
+ }
+ *aNewNextListener = nextListener;
+ NS_IF_ADDREF(*aNewNextListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentEncodings(nsIUTF8StringEnumerator** aEncodings)
+{
+ if (!mResponseHead) {
+ *aEncodings = nullptr;
+ return NS_OK;
+ }
+
+ nsAutoCString encoding;
+ mResponseHead->GetHeader(nsHttp::Content_Encoding, encoding);
+ if (encoding.IsEmpty()) {
+ *aEncodings = nullptr;
+ return NS_OK;
+ }
+ nsContentEncodings* enumerator = new nsContentEncodings(this,
+ encoding.get());
+ NS_ADDREF(*aEncodings = enumerator);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings <public>
+//-----------------------------------------------------------------------------
+
+HttpBaseChannel::nsContentEncodings::nsContentEncodings(nsIHttpChannel* aChannel,
+ const char* aEncodingHeader)
+ : mEncodingHeader(aEncodingHeader)
+ , mChannel(aChannel)
+ , mReady(false)
+{
+ mCurEnd = aEncodingHeader + strlen(aEncodingHeader);
+ mCurStart = mCurEnd;
+}
+
+HttpBaseChannel::nsContentEncodings::~nsContentEncodings()
+{
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings::nsISimpleEnumerator
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::nsContentEncodings::HasMore(bool* aMoreEncodings)
+{
+ if (mReady) {
+ *aMoreEncodings = true;
+ return NS_OK;
+ }
+
+ nsresult rv = PrepareForNext();
+ *aMoreEncodings = NS_SUCCEEDED(rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::nsContentEncodings::GetNext(nsACString& aNextEncoding)
+{
+ aNextEncoding.Truncate();
+ if (!mReady) {
+ nsresult rv = PrepareForNext();
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ const nsACString & encoding = Substring(mCurStart, mCurEnd);
+
+ nsACString::const_iterator start, end;
+ encoding.BeginReading(start);
+ encoding.EndReading(end);
+
+ bool haveType = false;
+ if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("gzip"), start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_GZIP);
+ haveType = true;
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("compress"), start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_COMPRESS);
+ haveType = true;
+ }
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("deflate"), start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_ZIP);
+ haveType = true;
+ }
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("br"), start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_BROTLI);
+ haveType = true;
+ }
+ }
+
+ // Prepare to fetch the next encoding
+ mCurEnd = mCurStart;
+ mReady = false;
+
+ if (haveType)
+ return NS_OK;
+
+ NS_WARNING("Unknown encoding type");
+ return NS_ERROR_FAILURE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(HttpBaseChannel::nsContentEncodings, nsIUTF8StringEnumerator)
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings <private>
+//-----------------------------------------------------------------------------
+
+nsresult
+HttpBaseChannel::nsContentEncodings::PrepareForNext(void)
+{
+ MOZ_ASSERT(mCurStart == mCurEnd, "Indeterminate state");
+
+ // At this point both mCurStart and mCurEnd point to somewhere
+ // past the end of the next thing we want to return
+
+ while (mCurEnd != mEncodingHeader) {
+ --mCurEnd;
+ if (*mCurEnd != ',' && !nsCRT::IsAsciiSpace(*mCurEnd))
+ break;
+ }
+ if (mCurEnd == mEncodingHeader)
+ return NS_ERROR_NOT_AVAILABLE; // no more encodings
+ ++mCurEnd;
+
+ // At this point mCurEnd points to the first char _after_ the
+ // header we want. Furthermore, mCurEnd - 1 != mEncodingHeader
+
+ mCurStart = mCurEnd - 1;
+ while (mCurStart != mEncodingHeader &&
+ *mCurStart != ',' && !nsCRT::IsAsciiSpace(*mCurStart))
+ --mCurStart;
+ if (*mCurStart == ',' || nsCRT::IsAsciiSpace(*mCurStart))
+ ++mCurStart; // we stopped because of a weird char, so move up one
+
+ // At this point mCurStart and mCurEnd bracket the encoding string
+ // we want. Check that it's not "identity"
+ if (Substring(mCurStart, mCurEnd).Equals("identity",
+ nsCaseInsensitiveCStringComparator())) {
+ mCurEnd = mCurStart;
+ return PrepareForNext();
+ }
+
+ mReady = true;
+ return NS_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetChannelId(nsACString& aChannelId)
+{
+ char id[NSID_LENGTH];
+ mChannelId.ToProvidedString(id);
+ aChannelId.AssignASCII(id);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetChannelId(const nsACString& aChannelId)
+{
+ nsID newId;
+ nsAutoCString idStr(aChannelId);
+ if (newId.Parse(idStr.get())) {
+ mChannelId = newId;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
+{
+ if (!mContentWindowId) {
+ nsCOMPtr<nsILoadContext> loadContext;
+ GetCallback(loadContext);
+ if (loadContext) {
+ nsCOMPtr<mozIDOMWindowProxy> topWindow;
+ loadContext->GetTopWindow(getter_AddRefs(topWindow));
+ nsCOMPtr<nsIDOMWindowUtils> windowUtils = do_GetInterface(topWindow);
+ if (windowUtils) {
+ windowUtils->GetCurrentInnerWindowID(&mContentWindowId);
+ }
+ }
+ }
+ *aWindowId = mContentWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
+{
+ mContentWindowId = aWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTransferSize(uint64_t *aTransferSize)
+{
+ *aTransferSize = mTransferSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDecodedBodySize(uint64_t *aDecodedBodySize)
+{
+ *aDecodedBodySize = mDecodedBodySize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEncodedBodySize(uint64_t *aEncodedBodySize)
+{
+ *aEncodedBodySize = mEncodedBodySize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestMethod(nsACString& aMethod)
+{
+ mRequestHead.Method(aMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestMethod(const nsACString& aMethod)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ const nsCString& flatMethod = PromiseFlatCString(aMethod);
+
+ // Method names are restricted to valid HTTP tokens.
+ if (!nsHttp::IsValidToken(flatMethod))
+ return NS_ERROR_INVALID_ARG;
+
+ mRequestHead.SetMethod(flatMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetNetworkInterfaceId(nsACString& aNetworkInterfaceId)
+{
+ aNetworkInterfaceId = mNetworkInterfaceId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNetworkInterfaceId(const nsACString& aNetworkInterfaceId)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+ mNetworkInterfaceId = aNetworkInterfaceId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetReferrer(nsIURI **referrer)
+{
+ NS_ENSURE_ARG_POINTER(referrer);
+ *referrer = mReferrer;
+ NS_IF_ADDREF(*referrer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetReferrer(nsIURI *referrer)
+{
+ return SetReferrerWithPolicy(referrer, REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetReferrerPolicy(uint32_t *referrerPolicy)
+{
+ NS_ENSURE_ARG_POINTER(referrerPolicy);
+ *referrerPolicy = mReferrerPolicy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetReferrerWithPolicy(nsIURI *referrer,
+ uint32_t referrerPolicy)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ // clear existing referrer, if any
+ mReferrer = nullptr;
+ nsresult rv = mRequestHead.ClearHeader(nsHttp::Referer);
+ if(NS_FAILED(rv)) {
+ return rv;
+ }
+ mReferrerPolicy = REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE;
+
+ if (!referrer) {
+ return NS_OK;
+ }
+
+ // Don't send referrer at all when the meta referrer setting is "no-referrer"
+ if (referrerPolicy == REFERRER_POLICY_NO_REFERRER) {
+ mReferrerPolicy = REFERRER_POLICY_NO_REFERRER;
+ return NS_OK;
+ }
+
+ // 0: never send referer
+ // 1: send referer for direct user action
+ // 2: always send referer
+ uint32_t userReferrerLevel = gHttpHandler->ReferrerLevel();
+
+ // false: use real referrer
+ // true: spoof with URI of the current request
+ bool userSpoofReferrerSource = gHttpHandler->SpoofReferrerSource();
+
+ // 0: full URI
+ // 1: scheme+host+port+path
+ // 2: scheme+host+port
+ int userReferrerTrimmingPolicy = gHttpHandler->ReferrerTrimmingPolicy();
+
+ // 0: send referer no matter what
+ // 1: send referer ONLY when base domains match
+ // 2: send referer ONLY when hosts match
+ int userReferrerXOriginPolicy = gHttpHandler->ReferrerXOriginPolicy();
+
+ // check referrer blocking pref
+ uint32_t referrerLevel;
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+ referrerLevel = 1; // user action
+ } else {
+ referrerLevel = 2; // inline content
+ }
+ if (userReferrerLevel < referrerLevel) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> referrerGrip;
+ bool match;
+
+ //
+ // Strip off "wyciwyg://123/" from wyciwyg referrers.
+ //
+ // XXX this really belongs elsewhere since wyciwyg URLs aren't part of necko.
+ // perhaps some sort of generic nsINestedURI could be used. then, if an URI
+ // fails the whitelist test, then we could check for an inner URI and try
+ // that instead. though, that might be too automatic.
+ //
+ rv = referrer->SchemeIs("wyciwyg", &match);
+ if (NS_FAILED(rv)) return rv;
+ if (match) {
+ nsAutoCString path;
+ rv = referrer->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t pathLength = path.Length();
+ if (pathLength <= 2) return NS_ERROR_FAILURE;
+
+ // Path is of the form "//123/http://foo/bar", with a variable number of
+ // digits. To figure out where the "real" URL starts, search path for a
+ // '/', starting at the third character.
+ int32_t slashIndex = path.FindChar('/', 2);
+ if (slashIndex == kNotFound) return NS_ERROR_FAILURE;
+
+ // Get charset of the original URI so we can pass it to our fixed up URI.
+ nsAutoCString charset;
+ referrer->GetOriginCharset(charset);
+
+ // Replace |referrer| with a URI without wyciwyg://123/.
+ rv = NS_NewURI(getter_AddRefs(referrerGrip),
+ Substring(path, slashIndex + 1, pathLength - slashIndex - 1),
+ charset.get());
+ if (NS_FAILED(rv)) return rv;
+
+ referrer = referrerGrip.get();
+ }
+
+ //
+ // block referrer if not on our white list...
+ //
+ static const char *const referrerWhiteList[] = {
+ "http",
+ "https",
+ "ftp",
+ nullptr
+ };
+ match = false;
+ const char *const *scheme = referrerWhiteList;
+ for (; *scheme && !match; ++scheme) {
+ rv = referrer->SchemeIs(*scheme, &match);
+ if (NS_FAILED(rv)) return rv;
+ }
+ if (!match) return NS_OK; // kick out....
+
+ //
+ // Handle secure referrals.
+ //
+ // Support referrals from a secure server if this is a secure site
+ // and (optionally) if the host names are the same.
+ //
+ rv = referrer->SchemeIs("https", &match);
+ if (NS_FAILED(rv)) return rv;
+
+ if (match) {
+ rv = mURI->SchemeIs("https", &match);
+ if (NS_FAILED(rv)) return rv;
+
+ // It's ok to send referrer for https-to-http scenarios if the referrer
+ // policy is "unsafe-url", "origin", or "origin-when-cross-origin".
+ if (referrerPolicy != REFERRER_POLICY_UNSAFE_URL &&
+ referrerPolicy != REFERRER_POLICY_ORIGIN_WHEN_XORIGIN &&
+ referrerPolicy != REFERRER_POLICY_ORIGIN) {
+
+ // in other referrer policies, https->http is not allowed...
+ if (!match) return NS_OK;
+ }
+ }
+
+ // for cross-origin-based referrer changes (not just host-based), figure out
+ // if the referrer is being sent cross-origin.
+ nsCOMPtr<nsIURI> triggeringURI;
+ bool isCrossOrigin = true;
+ if (mLoadInfo) {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = mLoadInfo->TriggeringPrincipal();
+ if (triggeringPrincipal) {
+ triggeringPrincipal->GetURI(getter_AddRefs(triggeringURI));
+ }
+ }
+ if (triggeringURI) {
+ if (LOG_ENABLED()) {
+ nsAutoCString triggeringURISpec;
+ rv = triggeringURI->GetAsciiSpec(triggeringURISpec);
+ if (!NS_FAILED(rv)) {
+ LOG(("triggeringURI=%s\n", triggeringURISpec.get()));
+ }
+ }
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ rv = ssm->CheckSameOriginURI(triggeringURI, mURI, false);
+ isCrossOrigin = NS_FAILED(rv);
+ } else {
+ LOG(("no triggering principal available via loadInfo, assuming load is cross-origin"));
+ }
+
+ // Don't send referrer when the request is cross-origin and policy is "same-origin".
+ if (isCrossOrigin && referrerPolicy == REFERRER_POLICY_SAME_ORIGIN) {
+ mReferrerPolicy = REFERRER_POLICY_SAME_ORIGIN;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> clone;
+ //
+ // we need to clone the referrer, so we can:
+ // (1) modify it
+ // (2) keep a reference to it after returning from this function
+ //
+ // Use CloneIgnoringRef to strip away any fragment per RFC 2616 section 14.36
+ // and Referrer Policy section 6.3.5.
+ rv = referrer->CloneIgnoringRef(getter_AddRefs(clone));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString currentHost;
+ nsAutoCString referrerHost;
+
+ rv = mURI->GetAsciiHost(currentHost);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = clone->GetAsciiHost(referrerHost);
+ if (NS_FAILED(rv)) return rv;
+
+ // check policy for sending ref only when hosts match
+ if (userReferrerXOriginPolicy == 2 && !currentHost.Equals(referrerHost))
+ return NS_OK;
+
+ if (userReferrerXOriginPolicy == 1) {
+ nsAutoCString currentDomain = currentHost;
+ nsAutoCString referrerDomain = referrerHost;
+ uint32_t extraDomains = 0;
+ nsCOMPtr<nsIEffectiveTLDService> eTLDService = do_GetService(
+ NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (eTLDService) {
+ rv = eTLDService->GetBaseDomain(mURI, extraDomains, currentDomain);
+ if (NS_FAILED(rv)) return rv;
+ rv = eTLDService->GetBaseDomain(clone, extraDomains, referrerDomain);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // check policy for sending only when effective top level domain matches.
+ // this falls back on using host if eTLDService does not work
+ if (!currentDomain.Equals(referrerDomain))
+ return NS_OK;
+ }
+
+ // send spoofed referrer if desired
+ if (userSpoofReferrerSource) {
+ nsCOMPtr<nsIURI> mURIclone;
+ rv = mURI->CloneIgnoringRef(getter_AddRefs(mURIclone));
+ if (NS_FAILED(rv)) return rv;
+ clone = mURIclone;
+ currentHost = referrerHost;
+ }
+
+ // strip away any userpass; we don't want to be giving out passwords ;-)
+ // This is required by Referrer Policy stripping algorithm.
+ rv = clone->SetUserPass(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString spec;
+
+ // Apply the user cross-origin trimming policy if it's more
+ // restrictive than the general one.
+ if (isCrossOrigin) {
+ int userReferrerXOriginTrimmingPolicy =
+ gHttpHandler->ReferrerXOriginTrimmingPolicy();
+ userReferrerTrimmingPolicy =
+ std::max(userReferrerTrimmingPolicy, userReferrerXOriginTrimmingPolicy);
+ }
+
+ // site-specified referrer trimming may affect the trim level
+ // "unsafe-url" behaves like "origin" (send referrer in the same situations) but
+ // "unsafe-url" sends the whole referrer and origin removes the path.
+ // "origin-when-cross-origin" trims the referrer only when the request is
+ // cross-origin.
+ // "Strict" request from https->http case was bailed out, so here:
+ // "strict-origin" behaves the same as "origin".
+ // "strict-origin-when-cross-origin" behaves the same as "origin-when-cross-origin"
+ if (referrerPolicy == REFERRER_POLICY_ORIGIN ||
+ referrerPolicy == REFERRER_POLICY_STRICT_ORIGIN ||
+ (isCrossOrigin && (referrerPolicy == REFERRER_POLICY_ORIGIN_WHEN_XORIGIN ||
+ referrerPolicy == REFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN))) {
+ // We can override the user trimming preference because "origin"
+ // (network.http.referer.trimmingPolicy = 2) is the strictest
+ // trimming policy that users can specify.
+ userReferrerTrimmingPolicy = 2;
+ }
+
+ // check how much referer to send
+ if (userReferrerTrimmingPolicy) {
+ // All output strings start with: scheme+host+port
+ // We want the IDN-normalized PrePath. That's not something currently
+ // available and there doesn't yet seem to be justification for adding it to
+ // the interfaces, so just build it up ourselves from scheme+AsciiHostPort
+ nsAutoCString scheme, asciiHostPort;
+ rv = clone->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+ spec = scheme;
+ spec.AppendLiteral("://");
+ // Note we explicitly cleared UserPass above, so do not need to build it.
+ rv = clone->GetAsciiHostPort(asciiHostPort);
+ if (NS_FAILED(rv)) return rv;
+ spec.Append(asciiHostPort);
+
+ switch (userReferrerTrimmingPolicy) {
+ case 1: { // scheme+host+port+path
+ nsCOMPtr<nsIURL> url(do_QueryInterface(clone));
+ if (url) {
+ nsAutoCString path;
+ rv = url->GetFilePath(path);
+ if (NS_FAILED(rv)) return rv;
+ spec.Append(path);
+ rv = url->SetQuery(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+ rv = url->SetRef(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+ break;
+ }
+ // No URL, so fall through to truncating the path and any query/ref off
+ // as well.
+ }
+ MOZ_FALLTHROUGH;
+ default: // (Pref limited to [0,2] enforced by clamp, MOZ_CRASH overkill.)
+ case 2: // scheme+host+port+/
+ spec.AppendLiteral("/");
+ // This nukes any query/ref present as well in the case of nsStandardURL
+ rv = clone->SetPath(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+ break;
+ }
+ } else {
+ // use the full URI
+ rv = clone->GetAsciiSpec(spec);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // finally, remember the referrer URI and set the Referer header.
+ rv = SetRequestHeader(NS_LITERAL_CSTRING("Referer"), spec, false);
+ if (NS_FAILED(rv)) return rv;
+
+ mReferrer = clone;
+ mReferrerPolicy = referrerPolicy;
+ return NS_OK;
+}
+
+// Return the channel's proxy URI, or if it doesn't exist, the
+// channel's main URI.
+NS_IMETHODIMP
+HttpBaseChannel::GetProxyURI(nsIURI **aOut)
+{
+ NS_ENSURE_ARG_POINTER(aOut);
+ nsCOMPtr<nsIURI> result(mProxyURI);
+ result.forget(aOut);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestHeader(const nsACString& aHeader,
+ nsACString& aValue)
+{
+ aValue.Truncate();
+
+ // XXX might be better to search the header list directly instead of
+ // hitting the http atom hash table.
+ nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+ if (!atom)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return mRequestHead.GetHeader(atom, aValue);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue,
+ bool aMerge)
+{
+ const nsCString &flatHeader = PromiseFlatCString(aHeader);
+ const nsCString &flatValue = PromiseFlatCString(aValue);
+
+ LOG(("HttpBaseChannel::SetRequestHeader [this=%p header=\"%s\" value=\"%s\" merge=%u]\n",
+ this, flatHeader.get(), flatValue.get(), aMerge));
+
+ // Verify header names are valid HTTP tokens and header values are reasonably
+ // close to whats allowed in RFC 2616.
+ if (!nsHttp::IsValidToken(flatHeader) ||
+ !nsHttp::IsReasonableHeaderValue(flatValue)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(flatHeader.get());
+ if (!atom) {
+ NS_WARNING("failed to resolve atom");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mRequestHead.SetHeader(atom, flatValue, aMerge);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetEmptyRequestHeader(const nsACString& aHeader)
+{
+ const nsCString &flatHeader = PromiseFlatCString(aHeader);
+
+ LOG(("HttpBaseChannel::SetEmptyRequestHeader [this=%p header=\"%s\"]\n",
+ this, flatHeader.get()));
+
+ // Verify header names are valid HTTP tokens and header values are reasonably
+ // close to whats allowed in RFC 2616.
+ if (!nsHttp::IsValidToken(flatHeader)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(flatHeader.get());
+ if (!atom) {
+ NS_WARNING("failed to resolve atom");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mRequestHead.SetEmptyHeader(atom);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitRequestHeaders(nsIHttpHeaderVisitor *visitor)
+{
+ return mRequestHead.VisitHeaders(visitor);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor *visitor)
+{
+ return mRequestHead.VisitHeaders(visitor,
+ nsHttpHeaderArray::eFilterSkipDefault);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseHeader(const nsACString &header, nsACString &value)
+{
+ value.Truncate();
+
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(header);
+ if (!atom)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return mResponseHead->GetHeader(atom, value);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetResponseHeader(const nsACString& header,
+ const nsACString& value,
+ bool merge)
+{
+ LOG(("HttpBaseChannel::SetResponseHeader [this=%p header=\"%s\" value=\"%s\" merge=%u]\n",
+ this, PromiseFlatCString(header).get(), PromiseFlatCString(value).get(), merge));
+
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(header);
+ if (!atom)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // these response headers must not be changed
+ if (atom == nsHttp::Content_Type ||
+ atom == nsHttp::Content_Length ||
+ atom == nsHttp::Content_Encoding ||
+ atom == nsHttp::Trailer ||
+ atom == nsHttp::Transfer_Encoding)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ mResponseHeadersModified = true;
+
+ return mResponseHead->SetHeader(atom, value, merge);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *visitor)
+{
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mResponseHead->VisitHeaders(visitor,
+ nsHttpHeaderArray::eFilterResponse);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOriginalResponseHeader(const nsACString& aHeader,
+ nsIHttpHeaderVisitor *aVisitor)
+{
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+ if (!atom) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mResponseHead->GetOriginalHeader(atom, aVisitor);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor *aVisitor)
+{
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mResponseHead->VisitHeaders(aVisitor,
+ nsHttpHeaderArray::eFilterResponseOriginal);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowPipelining(bool *value)
+{
+ NS_ENSURE_ARG_POINTER(value);
+ *value = mAllowPipelining;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowPipelining(bool value)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mAllowPipelining = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowSTS(bool *value)
+{
+ NS_ENSURE_ARG_POINTER(value);
+ *value = mAllowSTS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowSTS(bool value)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mAllowSTS = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectionLimit(uint32_t *value)
+{
+ NS_ENSURE_ARG_POINTER(value);
+ *value = mRedirectionLimit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectionLimit(uint32_t value)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mRedirectionLimit = std::min<uint32_t>(value, 0xff);
+ return NS_OK;
+}
+
+nsresult
+HttpBaseChannel::OverrideSecurityInfo(nsISupports* aSecurityInfo)
+{
+ MOZ_ASSERT(!mSecurityInfo,
+ "This can only be called when we don't have a security info object already");
+ MOZ_RELEASE_ASSERT(aSecurityInfo,
+ "This can only be called with a valid security info object");
+ MOZ_ASSERT(!BypassServiceWorker(),
+ "This can only be called on channels that are not bypassing interception");
+ MOZ_ASSERT(mResponseCouldBeSynthesized,
+ "This can only be called on channels that can be intercepted");
+ if (mSecurityInfo) {
+ LOG(("HttpBaseChannel::OverrideSecurityInfo mSecurityInfo is null! "
+ "[this=%p]\n", this));
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (!mResponseCouldBeSynthesized) {
+ LOG(("HttpBaseChannel::OverrideSecurityInfo channel cannot be intercepted! "
+ "[this=%p]\n", this));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mSecurityInfo = aSecurityInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsNoStoreResponse(bool *value)
+{
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+ *value = mResponseHead->NoStore();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsNoCacheResponse(bool *value)
+{
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+ *value = mResponseHead->NoCache();
+ if (!*value)
+ *value = mResponseHead->ExpiresInPast();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsPrivateResponse(bool *value)
+{
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+ *value = mResponseHead->Private();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseStatus(uint32_t *aValue)
+{
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+ *aValue = mResponseHead->Status();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseStatusText(nsACString& aValue)
+{
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+ mResponseHead->StatusText(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestSucceeded(bool *aValue)
+{
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+ uint32_t status = mResponseHead->Status();
+ *aValue = (status / 100 == 2);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::RedirectTo(nsIURI *targetURI)
+{
+ // We cannot redirect after OnStartRequest of the listener
+ // has been called, since to redirect we have to switch channels
+ // and the dance with OnStartRequest et al has to start over.
+ // This would break the nsIStreamListener contract.
+ NS_ENSURE_FALSE(mOnStartRequestCalled, NS_ERROR_NOT_AVAILABLE);
+
+ mAPIRedirectToURI = targetURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestContextID(nsID *aRCID)
+{
+ NS_ENSURE_ARG_POINTER(aRCID);
+ *aRCID = mRequestContextID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestContextID(const nsID aRCID)
+{
+ mRequestContextID = aRCID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsMainDocumentChannel(bool* aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+ *aValue = mForceMainDocumentChannel || (mLoadFlags & LOAD_DOCUMENT_URI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIsMainDocumentChannel(bool aValue)
+{
+ mForceMainDocumentChannel = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetProtocolVersion(nsACString& aProtocolVersion)
+{
+ nsresult rv;
+ nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(mSecurityInfo, &rv);
+ nsAutoCString protocol;
+ if (NS_SUCCEEDED(rv) && ssl &&
+ NS_SUCCEEDED(ssl->GetNegotiatedNPN(protocol)) &&
+ !protocol.IsEmpty()) {
+ // The negotiated protocol was not empty so we can use it.
+ aProtocolVersion = protocol;
+ return NS_OK;
+ }
+
+ if (mResponseHead) {
+ uint32_t version = mResponseHead->Version();
+ aProtocolVersion.Assign(nsHttp::GetProtocolVersion(version));
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTopWindowURI(nsIURI **aTopWindowURI)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<mozIThirdPartyUtil> util;
+ // Only compute the top window URI once. In e10s, this must be computed in the
+ // child. The parent gets the top window URI through HttpChannelOpenArgs.
+ if (!mTopWindowURI) {
+ util = do_GetService(THIRDPARTYUTIL_CONTRACTID);
+ if (!util) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ rv = util->GetTopWindowForChannel(this, getter_AddRefs(win));
+ if (NS_SUCCEEDED(rv)) {
+ rv = util->GetURIFromWindow(win, getter_AddRefs(mTopWindowURI));
+#if DEBUG
+ if (mTopWindowURI) {
+ nsCString spec;
+ if (NS_SUCCEEDED(mTopWindowURI->GetSpec(spec))) {
+ LOG(("HttpChannelBase::Setting topwindow URI spec %s [this=%p]\n",
+ spec.get(), this));
+ }
+ }
+#endif
+ }
+ }
+ NS_IF_ADDREF(*aTopWindowURI = mTopWindowURI);
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDocumentURI(nsIURI **aDocumentURI)
+{
+ NS_ENSURE_ARG_POINTER(aDocumentURI);
+ *aDocumentURI = mDocumentURI;
+ NS_IF_ADDREF(*aDocumentURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDocumentURI(nsIURI *aDocumentURI)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mDocumentURI = aDocumentURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestVersion(uint32_t *major, uint32_t *minor)
+{
+ nsHttpVersion version = mRequestHead.Version();
+
+ if (major) { *major = version / 10; }
+ if (minor) { *minor = version % 10; }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseVersion(uint32_t *major, uint32_t *minor)
+{
+ if (!mResponseHead)
+ {
+ *major = *minor = 0; // we should at least be kind about it
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsHttpVersion version = mResponseHead->Version();
+
+ if (major) { *major = version / 10; }
+ if (minor) { *minor = version % 10; }
+
+ return NS_OK;
+}
+
+void
+HttpBaseChannel::NotifySetCookie(char const *aCookie)
+{
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ nsAutoString cookie;
+ CopyASCIItoUTF16(aCookie, cookie);
+ obs->NotifyObservers(static_cast<nsIChannel*>(this),
+ "http-on-response-set-cookie",
+ cookie.get());
+ }
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCookie(const char *aCookieHeader)
+{
+ if (mLoadFlags & LOAD_ANONYMOUS)
+ return NS_OK;
+
+ // empty header isn't an error
+ if (!(aCookieHeader && *aCookieHeader))
+ return NS_OK;
+
+ nsICookieService *cs = gHttpHandler->GetCookieService();
+ NS_ENSURE_TRUE(cs, NS_ERROR_FAILURE);
+
+ nsAutoCString date;
+ mResponseHead->GetHeader(nsHttp::Date, date);
+ nsresult rv =
+ cs->SetCookieStringFromHttp(mURI, nullptr, nullptr, aCookieHeader,
+ date.get(), this);
+ if (NS_SUCCEEDED(rv)) {
+ NotifySetCookie(aCookieHeader);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetThirdPartyFlags(uint32_t *aFlags)
+{
+ *aFlags = mThirdPartyFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetThirdPartyFlags(uint32_t aFlags)
+{
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ mThirdPartyFlags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetForceAllowThirdPartyCookie(bool *aForce)
+{
+ *aForce = !!(mThirdPartyFlags & nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetForceAllowThirdPartyCookie(bool aForce)
+{
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ if (aForce)
+ mThirdPartyFlags |= nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW;
+ else
+ mThirdPartyFlags &= ~nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCanceled(bool *aCanceled)
+{
+ *aCanceled = mCanceled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetChannelIsForDownload(bool *aChannelIsForDownload)
+{
+ *aChannelIsForDownload = mChannelIsForDownload;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetChannelIsForDownload(bool aChannelIsForDownload)
+{
+ mChannelIsForDownload = aChannelIsForDownload;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCacheKeysRedirectChain(nsTArray<nsCString> *cacheKeys)
+{
+ mRedirectedCachekeys = cacheKeys;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLocalAddress(nsACString& addr)
+{
+ if (mSelfAddr.raw.family == PR_AF_UNSPEC)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ addr.SetCapacity(kIPv6CStrBufSize);
+ NetAddrToString(&mSelfAddr, addr.BeginWriting(), kIPv6CStrBufSize);
+ addr.SetLength(strlen(addr.BeginReading()));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::TakeAllSecurityMessages(
+ nsCOMArray<nsISecurityConsoleMessage> &aMessages)
+{
+ aMessages.Clear();
+ aMessages.SwapElements(mSecurityConsoleMessages);
+ return NS_OK;
+}
+
+/* Please use this method with care. This can cause the message
+ * queue to grow large and cause the channel to take up a lot
+ * of memory. Use only static string messages and do not add
+ * server side data to the queue, as that can be large.
+ * Add only a limited number of messages to the queue to keep
+ * the channel size down and do so only in rare erroneous situations.
+ * More information can be found here:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=846918
+ */
+nsresult
+HttpBaseChannel::AddSecurityMessage(const nsAString &aMessageTag,
+ const nsAString &aMessageCategory)
+{
+ nsresult rv;
+ nsCOMPtr<nsISecurityConsoleMessage> message =
+ do_CreateInstance(NS_SECURITY_CONSOLE_MESSAGE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ message->SetTag(aMessageTag);
+ message->SetCategory(aMessageCategory);
+ mSecurityConsoleMessages.AppendElement(message);
+
+ nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ if (!console) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ GetLoadInfo(getter_AddRefs(loadInfo));
+ if (!loadInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t innerWindowID = loadInfo->GetInnerWindowID();
+
+ nsXPIDLString errorText;
+ rv = nsContentUtils::GetLocalizedString(
+ nsContentUtils::eSECURITY_PROPERTIES,
+ NS_ConvertUTF16toUTF8(aMessageTag).get(),
+ errorText);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString spec;
+ if (mURI) {
+ spec = mURI->GetSpecOrDefault();
+ }
+
+ nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ error->InitWithWindowID(errorText, NS_ConvertUTF8toUTF16(spec),
+ EmptyString(), 0, 0, nsIScriptError::warningFlag,
+ NS_ConvertUTF16toUTF8(aMessageCategory),
+ innerWindowID);
+ console->LogMessage(error);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLocalPort(int32_t* port)
+{
+ NS_ENSURE_ARG_POINTER(port);
+
+ if (mSelfAddr.raw.family == PR_AF_INET) {
+ *port = (int32_t)ntohs(mSelfAddr.inet.port);
+ }
+ else if (mSelfAddr.raw.family == PR_AF_INET6) {
+ *port = (int32_t)ntohs(mSelfAddr.inet6.port);
+ }
+ else
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRemoteAddress(nsACString& addr)
+{
+ if (mPeerAddr.raw.family == PR_AF_UNSPEC)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ addr.SetCapacity(kIPv6CStrBufSize);
+ NetAddrToString(&mPeerAddr, addr.BeginWriting(), kIPv6CStrBufSize);
+ addr.SetLength(strlen(addr.BeginReading()));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRemotePort(int32_t* port)
+{
+ NS_ENSURE_ARG_POINTER(port);
+
+ if (mPeerAddr.raw.family == PR_AF_INET) {
+ *port = (int32_t)ntohs(mPeerAddr.inet.port);
+ }
+ else if (mPeerAddr.raw.family == PR_AF_INET6) {
+ *port = (int32_t)ntohs(mPeerAddr.inet6.port);
+ }
+ else
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::HTTPUpgrade(const nsACString &aProtocolName,
+ nsIHttpUpgradeListener *aListener)
+{
+ NS_ENSURE_ARG(!aProtocolName.IsEmpty());
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mUpgradeProtocol = aProtocolName;
+ mUpgradeProtocolCallback = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowSpdy(bool *aAllowSpdy)
+{
+ NS_ENSURE_ARG_POINTER(aAllowSpdy);
+
+ *aAllowSpdy = mAllowSpdy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowSpdy(bool aAllowSpdy)
+{
+ mAllowSpdy = aAllowSpdy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowAltSvc(bool *aAllowAltSvc)
+{
+ NS_ENSURE_ARG_POINTER(aAllowAltSvc);
+
+ *aAllowAltSvc = mAllowAltSvc;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowAltSvc(bool aAllowAltSvc)
+{
+ mAllowAltSvc = aAllowAltSvc;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetBeConservative(bool *aBeConservative)
+{
+ NS_ENSURE_ARG_POINTER(aBeConservative);
+
+ *aBeConservative = mBeConservative;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetBeConservative(bool aBeConservative)
+{
+ mBeConservative = aBeConservative;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetApiRedirectToURI(nsIURI ** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_IF_ADDREF(*aResult = mAPIRedirectToURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseTimeoutEnabled(bool *aEnable)
+{
+ if (NS_WARN_IF(!aEnable)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aEnable = mResponseTimeoutEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetResponseTimeoutEnabled(bool aEnable)
+{
+ mResponseTimeoutEnabled = aEnable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetInitialRwin(uint32_t *aRwin)
+{
+ if (NS_WARN_IF(!aRwin)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aRwin = mInitialRwin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetInitialRwin(uint32_t aRwin)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+ mInitialRwin = aRwin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::ForcePending(bool aForcePending)
+{
+ mForcePending = aForcePending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLastModifiedTime(PRTime* lastModifiedTime)
+{
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+ uint32_t lastMod;
+ mResponseHead->GetLastModifiedValue(&lastMod);
+ *lastModifiedTime = lastMod;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCorsIncludeCredentials(bool* aInclude)
+{
+ *aInclude = mCorsIncludeCredentials;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCorsIncludeCredentials(bool aInclude)
+{
+ mCorsIncludeCredentials = aInclude;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCorsMode(uint32_t* aMode)
+{
+ *aMode = mCorsMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCorsMode(uint32_t aMode)
+{
+ mCorsMode = aMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectMode(uint32_t* aMode)
+{
+ *aMode = mRedirectMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectMode(uint32_t aMode)
+{
+ mRedirectMode = aMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetFetchCacheMode(uint32_t* aFetchCacheMode)
+{
+ NS_ENSURE_ARG_POINTER(aFetchCacheMode);
+
+ // If the fetch cache mode is overriden, then use it directly.
+ if (mFetchCacheMode != nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT) {
+ *aFetchCacheMode = mFetchCacheMode;
+ return NS_OK;
+ }
+
+ // Otherwise try to guess an appropriate cache mode from the load flags.
+ if (mLoadFlags & (INHIBIT_CACHING | LOAD_BYPASS_CACHE)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE;
+ } else if (mLoadFlags & LOAD_BYPASS_CACHE) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD;
+ } else if (mLoadFlags & VALIDATE_ALWAYS) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE;
+ } else if (mLoadFlags & (LOAD_FROM_CACHE | nsICachingChannel::LOAD_ONLY_FROM_CACHE)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED;
+ } else if (mLoadFlags & LOAD_FROM_CACHE) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE;
+ } else {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetFetchCacheMode(uint32_t aFetchCacheMode)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+ MOZ_ASSERT(mFetchCacheMode == nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT,
+ "SetFetchCacheMode() should only be called once per channel");
+
+ mFetchCacheMode = aFetchCacheMode;
+
+ // Now, set the load flags that implement each cache mode.
+ switch (mFetchCacheMode) {
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE:
+ // no-store means don't consult the cache on the way to the network, and
+ // don't store the response in the cache even if it's cacheable.
+ mLoadFlags |= INHIBIT_CACHING | LOAD_BYPASS_CACHE;
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD:
+ // reload means don't consult the cache on the way to the network, but
+ // do store the response in the cache if possible.
+ mLoadFlags |= LOAD_BYPASS_CACHE;
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE:
+ // no-cache means always validate what's in the cache.
+ mLoadFlags |= VALIDATE_ALWAYS;
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE:
+ // force-cache means don't validate unless if the response would vary.
+ mLoadFlags |= LOAD_FROM_CACHE;
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED:
+ // only-if-cached means only from cache, no network, no validation, generate
+ // a network error if the document was't in the cache.
+ // The privacy implications of these flags (making it fast/easy to check if
+ // the user has things in their cache without any network traffic side
+ // effects) are addressed in the Request constructor which enforces/requires
+ // same-origin request mode.
+ mLoadFlags |= LOAD_FROM_CACHE | nsICachingChannel::LOAD_ONLY_FROM_CACHE;
+ break;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIntegrityMetadata(const nsAString& aIntegrityMetadata)
+{
+ mIntegrityMetadata = aIntegrityMetadata;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIntegrityMetadata(nsAString& aIntegrityMetadata)
+{
+ aIntegrityMetadata = mIntegrityMetadata;
+ return NS_OK;
+}
+
+mozilla::net::nsHttpChannel*
+HttpBaseChannel::QueryHttpChannelImpl(void)
+{
+ return nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsISupportsPriority
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetPriority(int32_t *value)
+{
+ *value = mPriority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::AdjustPriority(int32_t delta)
+{
+ return SetPriority(mPriority + delta);
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIResumableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEntityID(nsACString& aEntityID)
+{
+ // Don't return an entity ID for Non-GET requests which require
+ // additional data
+ if (!mRequestHead.IsGet()) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ uint64_t size = UINT64_MAX;
+ nsAutoCString etag, lastmod;
+ if (mResponseHead) {
+ // Don't return an entity if the server sent the following header:
+ // Accept-Ranges: none
+ // Not sending the Accept-Ranges header means we can still try
+ // sending range requests.
+ nsAutoCString acceptRanges;
+ mResponseHead->GetHeader(nsHttp::Accept_Ranges, acceptRanges);
+ if (!acceptRanges.IsEmpty() &&
+ !nsHttp::FindToken(acceptRanges.get(), "bytes", HTTP_HEADER_VALUE_SEPS)) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ size = mResponseHead->TotalEntitySize();
+ mResponseHead->GetHeader(nsHttp::Last_Modified, lastmod);
+ mResponseHead->GetHeader(nsHttp::ETag, etag);
+ }
+ nsCString entityID;
+ NS_EscapeURL(etag.BeginReading(), etag.Length(), esc_AlwaysCopy |
+ esc_FileBaseName | esc_Forced, entityID);
+ entityID.Append('/');
+ entityID.AppendInt(int64_t(size));
+ entityID.Append('/');
+ entityID.Append(lastmod);
+ // NOTE: Appending lastmod as the last part avoids having to escape it
+
+ aEntityID = entityID;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIConsoleReportCollector
+//-----------------------------------------------------------------------------
+
+void
+HttpBaseChannel::AddConsoleReport(uint32_t aErrorFlags,
+ const nsACString& aCategory,
+ nsContentUtils::PropertiesFile aPropertiesFile,
+ const nsACString& aSourceFileURI,
+ uint32_t aLineNumber, uint32_t aColumnNumber,
+ const nsACString& aMessageName,
+ const nsTArray<nsString>& aStringParams)
+{
+ mReportCollector->AddConsoleReport(aErrorFlags, aCategory, aPropertiesFile,
+ aSourceFileURI, aLineNumber,
+ aColumnNumber, aMessageName,
+ aStringParams);
+}
+
+void
+HttpBaseChannel::FlushConsoleReports(nsIDocument* aDocument,
+ ReportAction aAction)
+{
+ mReportCollector->FlushConsoleReports(aDocument, aAction);
+}
+
+void
+HttpBaseChannel::FlushConsoleReports(nsIConsoleReportCollector* aCollector)
+{
+ mReportCollector->FlushConsoleReports(aCollector);
+}
+
+void
+HttpBaseChannel::FlushReportsByWindowId(uint64_t aWindowId,
+ ReportAction aAction)
+{
+ mReportCollector->FlushReportsByWindowId(aWindowId, aAction);
+}
+
+void
+HttpBaseChannel::ClearConsoleReports()
+{
+ mReportCollector->ClearConsoleReports();
+}
+
+nsIPrincipal *
+HttpBaseChannel::GetURIPrincipal()
+{
+ if (mPrincipal) {
+ return mPrincipal;
+ }
+
+ nsIScriptSecurityManager *securityManager =
+ nsContentUtils::GetSecurityManager();
+
+ if (!securityManager) {
+ LOG(("HttpBaseChannel::GetURIPrincipal: No security manager [this=%p]",
+ this));
+ return nullptr;
+ }
+
+ securityManager->GetChannelURIPrincipal(this, getter_AddRefs(mPrincipal));
+ if (!mPrincipal) {
+ LOG(("HttpBaseChannel::GetURIPrincipal: No channel principal [this=%p]",
+ this));
+ return nullptr;
+ }
+
+ return mPrincipal;
+}
+
+bool
+HttpBaseChannel::IsNavigation()
+{
+ return mForceMainDocumentChannel;
+}
+
+bool
+HttpBaseChannel::BypassServiceWorker() const
+{
+ return mLoadFlags & LOAD_BYPASS_SERVICE_WORKER;
+}
+
+bool
+HttpBaseChannel::ShouldIntercept(nsIURI* aURI)
+{
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+ bool shouldIntercept = false;
+ if (controller && !BypassServiceWorker() && mLoadInfo) {
+ nsresult rv = controller->ShouldPrepareForIntercept(aURI ? aURI : mURI.get(),
+ nsContentUtils::IsNonSubresourceRequest(this),
+ &shouldIntercept);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ }
+ return shouldIntercept;
+}
+
+#ifdef DEBUG
+void HttpBaseChannel::AssertPrivateBrowsingId()
+{
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ // For addons it's possible that mLoadInfo is null.
+ if (!mLoadInfo) {
+ return;
+ }
+
+ if (!loadContext) {
+ return;
+ }
+
+ // We skip testing of favicon loading here since it could be triggered by XUL image
+ // which uses SystemPrincipal. The SystemPrincpal doesn't have mPrivateBrowsingId.
+ if (nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal()) &&
+ mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
+ return;
+ }
+
+ DocShellOriginAttributes docShellAttrs;
+ loadContext->GetOriginAttributes(docShellAttrs);
+ MOZ_ASSERT(mLoadInfo->GetOriginAttributes().mPrivateBrowsingId == docShellAttrs.mPrivateBrowsingId,
+ "PrivateBrowsingId values are not the same between LoadInfo and LoadContext.");
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsITraceableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNewListener(nsIStreamListener *aListener, nsIStreamListener **_retval)
+{
+ LOG(("HttpBaseChannel::SetNewListener [this=%p, mListener=%p, newListener=%p]",
+ this, mListener.get(), aListener));
+
+ if (!mTracingEnabled)
+ return NS_ERROR_FAILURE;
+
+ NS_ENSURE_STATE(mListener);
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ nsCOMPtr<nsIStreamListener> wrapper = new nsStreamListenerWrapper(mListener);
+
+ wrapper.forget(_retval);
+ mListener = aListener;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel helpers
+//-----------------------------------------------------------------------------
+
+void
+HttpBaseChannel::ReleaseListeners()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+
+ mListener = nullptr;
+ mListenerContext = nullptr;
+ mCallbacks = nullptr;
+ mProgressSink = nullptr;
+ mCompressListener = nullptr;
+}
+
+void
+HttpBaseChannel::DoNotifyListener()
+{
+ if (mListener) {
+ MOZ_ASSERT(!mOnStartRequestCalled,
+ "We should not call OnStartRequest twice");
+
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ listener->OnStartRequest(this, mListenerContext);
+
+ mOnStartRequestCalled = true;
+ }
+
+ // Make sure mIsPending is set to false. At this moment we are done from
+ // the point of view of our consumer and we have to report our self
+ // as not-pending.
+ mIsPending = false;
+
+ if (mListener) {
+ MOZ_ASSERT(!mOnStopRequestCalled,
+ "We should not call OnStopRequest twice");
+
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ listener->OnStopRequest(this, mListenerContext, mStatus);
+
+ mOnStopRequestCalled = true;
+ }
+
+ // We have to make sure to drop the references to listeners and callbacks
+ // no longer needed
+ ReleaseListeners();
+
+ DoNotifyListenerCleanup();
+
+ // If this is a navigation, then we must let the docshell flush the reports
+ // to the console later. The LoadDocument() is pointing at the detached
+ // document that started the navigation. We want to show the reports on the
+ // new document. Otherwise the console is wiped and the user never sees
+ // the information.
+ if (!IsNavigation() && mLoadInfo) {
+ nsCOMPtr<nsIDOMDocument> dommyDoc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(dommyDoc));
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(dommyDoc);
+ FlushConsoleReports(doc);
+ }
+}
+
+void
+HttpBaseChannel::AddCookiesToRequest()
+{
+ if (mLoadFlags & LOAD_ANONYMOUS) {
+ return;
+ }
+
+ bool useCookieService =
+ (XRE_IsParentProcess());
+ nsXPIDLCString cookie;
+ if (useCookieService) {
+ nsICookieService *cs = gHttpHandler->GetCookieService();
+ if (cs) {
+ cs->GetCookieStringFromHttp(mURI,
+ nullptr,
+ this, getter_Copies(cookie));
+ }
+
+ if (cookie.IsEmpty()) {
+ cookie = mUserSetCookieHeader;
+ }
+ else if (!mUserSetCookieHeader.IsEmpty()) {
+ cookie.AppendLiteral("; ");
+ cookie.Append(mUserSetCookieHeader);
+ }
+ }
+ else {
+ cookie = mUserSetCookieHeader;
+ }
+
+ // If we are in the child process, we want the parent seeing any
+ // cookie headers that might have been set by SetRequestHeader()
+ SetRequestHeader(nsDependentCString(nsHttp::Cookie), cookie, false);
+}
+
+bool
+HttpBaseChannel::ShouldRewriteRedirectToGET(uint32_t httpStatus,
+ nsHttpRequestHead::ParsedMethodType method)
+{
+ // for 301 and 302, only rewrite POST
+ if (httpStatus == 301 || httpStatus == 302)
+ return method == nsHttpRequestHead::kMethod_Post;
+
+ // rewrite for 303 unless it was HEAD
+ if (httpStatus == 303)
+ return method != nsHttpRequestHead::kMethod_Head;
+
+ // otherwise, such as for 307, do not rewrite
+ return false;
+}
+
+static
+bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader)
+{
+ // IMPORTANT: keep this list ASCII-code sorted
+ static nsHttpAtom const* blackList[] = {
+ &nsHttp::Accept,
+ &nsHttp::Accept_Encoding,
+ &nsHttp::Accept_Language,
+ &nsHttp::Authentication,
+ &nsHttp::Authorization,
+ &nsHttp::Connection,
+ &nsHttp::Content_Length,
+ &nsHttp::Cookie,
+ &nsHttp::Host,
+ &nsHttp::If,
+ &nsHttp::If_Match,
+ &nsHttp::If_Modified_Since,
+ &nsHttp::If_None_Match,
+ &nsHttp::If_None_Match_Any,
+ &nsHttp::If_Range,
+ &nsHttp::If_Unmodified_Since,
+ &nsHttp::Proxy_Authenticate,
+ &nsHttp::Proxy_Authorization,
+ &nsHttp::Range,
+ &nsHttp::TE,
+ &nsHttp::Transfer_Encoding,
+ &nsHttp::Upgrade,
+ &nsHttp::User_Agent,
+ &nsHttp::WWW_Authenticate
+ };
+
+ class HttpAtomComparator
+ {
+ nsHttpAtom const& mTarget;
+ public:
+ explicit HttpAtomComparator(nsHttpAtom const& aTarget)
+ : mTarget(aTarget) {}
+ int operator()(nsHttpAtom const* aVal) const {
+ if (mTarget == *aVal) {
+ return 0;
+ }
+ return strcmp(mTarget._val, aVal->_val);
+ }
+ };
+
+ size_t unused;
+ return BinarySearchIf(blackList, 0, ArrayLength(blackList),
+ HttpAtomComparator(aHeader), &unused);
+}
+
+class SetupReplacementChannelHeaderVisitor final : public nsIHttpHeaderVisitor
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit SetupReplacementChannelHeaderVisitor(nsIHttpChannel *aChannel)
+ : mChannel(aChannel)
+ {
+ }
+
+ NS_IMETHOD VisitHeader(const nsACString& aHeader,
+ const nsACString& aValue) override
+ {
+ nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+ if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
+ mChannel->SetRequestHeader(aHeader, aValue, false);
+ }
+ return NS_OK;
+ }
+private:
+ ~SetupReplacementChannelHeaderVisitor()
+ {
+ }
+
+ nsCOMPtr<nsIHttpChannel> mChannel;
+};
+
+NS_IMPL_ISUPPORTS(SetupReplacementChannelHeaderVisitor, nsIHttpHeaderVisitor)
+
+nsresult
+HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI,
+ nsIChannel *newChannel,
+ bool preserveMethod,
+ uint32_t redirectFlags)
+{
+ LOG(("HttpBaseChannel::SetupReplacementChannel "
+ "[this=%p newChannel=%p preserveMethod=%d]",
+ this, newChannel, preserveMethod));
+
+ uint32_t newLoadFlags = mLoadFlags | LOAD_REPLACE;
+ // if the original channel was using SSL and this channel is not using
+ // SSL, then no need to inhibit persistent caching. however, if the
+ // original channel was not using SSL and has INHIBIT_PERSISTENT_CACHING
+ // set, then allow the flag to apply to the redirected channel as well.
+ // since we force set INHIBIT_PERSISTENT_CACHING on all HTTPS channels,
+ // we only need to check if the original channel was using SSL.
+ bool usingSSL = false;
+ nsresult rv = mURI->SchemeIs("https", &usingSSL);
+ if (NS_SUCCEEDED(rv) && usingSSL)
+ newLoadFlags &= ~INHIBIT_PERSISTENT_CACHING;
+
+ // Do not pass along LOAD_CHECK_OFFLINE_CACHE
+ newLoadFlags &= ~nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE;
+
+ newChannel->SetLoadGroup(mLoadGroup);
+ newChannel->SetNotificationCallbacks(mCallbacks);
+ newChannel->SetLoadFlags(newLoadFlags);
+
+ // Try to preserve the privacy bit if it has been overridden
+ if (mPrivateBrowsingOverriden) {
+ nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
+ do_QueryInterface(newChannel);
+ if (newPBChannel) {
+ newPBChannel->SetPrivate(mPrivateBrowsing);
+ }
+ }
+
+ // make a copy of the loadinfo, append to the redirectchain
+ // and set it on the new channel
+ if (mLoadInfo) {
+ nsCOMPtr<nsILoadInfo> newLoadInfo =
+ static_cast<mozilla::LoadInfo*>(mLoadInfo.get())->Clone();
+
+ nsContentPolicyType contentPolicyType = mLoadInfo->GetExternalContentPolicyType();
+ if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
+ contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
+ nsCOMPtr<nsIPrincipal> nullPrincipalToInherit = nsNullPrincipal::Create();
+ newLoadInfo->SetPrincipalToInherit(nullPrincipalToInherit);
+ }
+
+ // re-compute the origin attributes of the loadInfo if it's top-level load.
+ bool isTopLevelDoc =
+ newLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT;
+
+ if (isTopLevelDoc) {
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ DocShellOriginAttributes docShellAttrs;
+ if (loadContext) {
+ loadContext->GetOriginAttributes(docShellAttrs);
+ }
+ MOZ_ASSERT(docShellAttrs.mFirstPartyDomain.IsEmpty(),
+ "top-level docshell shouldn't have firstPartyDomain attribute.");
+
+ NeckoOriginAttributes attrs = newLoadInfo->GetOriginAttributes();
+
+ MOZ_ASSERT(docShellAttrs.mAppId == attrs.mAppId,
+ "docshell and necko should have the same appId attribute.");
+ MOZ_ASSERT(docShellAttrs.mUserContextId == attrs.mUserContextId,
+ "docshell and necko should have the same userContextId attribute.");
+ MOZ_ASSERT(docShellAttrs.mInIsolatedMozBrowser == attrs.mInIsolatedMozBrowser,
+ "docshell and necko should have the same inIsolatedMozBrowser attribute.");
+ MOZ_ASSERT(docShellAttrs.mPrivateBrowsingId == attrs.mPrivateBrowsingId,
+ "docshell and necko should have the same privateBrowsingId attribute.");
+
+ attrs.InheritFromDocShellToNecko(docShellAttrs, true, newURI);
+ newLoadInfo->SetOriginAttributes(attrs);
+ }
+
+ bool isInternalRedirect =
+ (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+ newLoadInfo->AppendRedirectedPrincipal(GetURIPrincipal(), isInternalRedirect);
+ newChannel->SetLoadInfo(newLoadInfo);
+ }
+ else {
+ // the newChannel was created with a dummy loadInfo, we should clear
+ // it in case the original channel does not have a loadInfo
+ newChannel->SetLoadInfo(nullptr);
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+ if (!httpChannel)
+ return NS_OK; // no other options to set
+
+ // Preserve the CORS preflight information.
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel);
+ if (mRequireCORSPreflight && httpInternal) {
+ httpInternal->SetCorsPreflightParameters(mUnsafeHeaders);
+ }
+
+ if (preserveMethod) {
+ nsCOMPtr<nsIUploadChannel> uploadChannel =
+ do_QueryInterface(httpChannel);
+ nsCOMPtr<nsIUploadChannel2> uploadChannel2 =
+ do_QueryInterface(httpChannel);
+ if (mUploadStream && (uploadChannel2 || uploadChannel)) {
+ // rewind upload stream
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
+ if (seekable)
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+
+ // replicate original call to SetUploadStream...
+ if (uploadChannel2) {
+ nsAutoCString ctype;
+ // If header is not present mRequestHead.HasHeaderValue will truncated
+ // it. But we want to end up with a void string, not an empty string,
+ // because ExplicitSetUploadStream treats the former as "no header" and
+ // the latter as "header with empty string value".
+ nsresult ctypeOK = mRequestHead.GetHeader(nsHttp::Content_Type, ctype);
+ if (NS_FAILED(ctypeOK)) {
+ ctype.SetIsVoid(true);
+ }
+ nsAutoCString clen;
+ mRequestHead.GetHeader(nsHttp::Content_Length, clen);
+ nsAutoCString method;
+ mRequestHead.Method(method);
+ int64_t len = clen.IsEmpty() ? -1 : nsCRT::atoll(clen.get());
+ uploadChannel2->ExplicitSetUploadStream(
+ mUploadStream, ctype, len,
+ method,
+ mUploadStreamHasHeaders);
+ } else {
+ if (mUploadStreamHasHeaders) {
+ uploadChannel->SetUploadStream(mUploadStream, EmptyCString(),
+ -1);
+ } else {
+ nsAutoCString ctype;
+ if (NS_FAILED(mRequestHead.GetHeader(nsHttp::Content_Type, ctype))) {
+ ctype = NS_LITERAL_CSTRING("application/octet-stream");
+ }
+ nsAutoCString clen;
+ if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Content_Length, clen))
+ &&
+ !clen.IsEmpty()) {
+ uploadChannel->SetUploadStream(mUploadStream,
+ ctype,
+ nsCRT::atoll(clen.get()));
+ }
+ }
+ }
+ }
+ // since preserveMethod is true, we need to ensure that the appropriate
+ // request method gets set on the channel, regardless of whether or not
+ // we set the upload stream above. This means SetRequestMethod() will
+ // be called twice if ExplicitSetUploadStream() gets called above.
+
+ nsAutoCString method;
+ mRequestHead.Method(method);
+ httpChannel->SetRequestMethod(method);
+ }
+ // convey the referrer if one was used for this channel to the next one
+ if (mReferrer)
+ httpChannel->SetReferrerWithPolicy(mReferrer, mReferrerPolicy);
+ // convey the mAllowPipelining and mAllowSTS flags
+ httpChannel->SetAllowPipelining(mAllowPipelining);
+ httpChannel->SetAllowSTS(mAllowSTS);
+ // convey the new redirection limit
+ // make sure we don't underflow
+ uint32_t redirectionLimit = mRedirectionLimit
+ ? mRedirectionLimit - 1
+ : 0;
+ httpChannel->SetRedirectionLimit(redirectionLimit);
+
+ // convey the Accept header value
+ {
+ nsAutoCString oldAcceptValue;
+ nsresult hasHeader = mRequestHead.GetHeader(nsHttp::Accept, oldAcceptValue);
+ if (NS_SUCCEEDED(hasHeader)) {
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+ oldAcceptValue,
+ false);
+ }
+ }
+
+ // share the request context - see bug 1236650
+ httpChannel->SetRequestContextID(mRequestContextID);
+
+ if (httpInternal) {
+ // Convey third party cookie, conservative, and spdy flags.
+ httpInternal->SetThirdPartyFlags(mThirdPartyFlags);
+ httpInternal->SetAllowSpdy(mAllowSpdy);
+ httpInternal->SetAllowAltSvc(mAllowAltSvc);
+ httpInternal->SetBeConservative(mBeConservative);
+
+ RefPtr<nsHttpChannel> realChannel;
+ CallQueryInterface(newChannel, realChannel.StartAssignment());
+ if (realChannel) {
+ realChannel->SetTopWindowURI(mTopWindowURI);
+ }
+
+ // update the DocumentURI indicator since we are being redirected.
+ // if this was a top-level document channel, then the new channel
+ // should have its mDocumentURI point to newURI; otherwise, we
+ // just need to pass along our mDocumentURI to the new channel.
+ if (newURI && (mURI == mDocumentURI))
+ httpInternal->SetDocumentURI(newURI);
+ else
+ httpInternal->SetDocumentURI(mDocumentURI);
+
+ // if there is a chain of keys for redirect-responses we transfer it to
+ // the new channel (see bug #561276)
+ if (mRedirectedCachekeys) {
+ LOG(("HttpBaseChannel::SetupReplacementChannel "
+ "[this=%p] transferring chain of redirect cache-keys", this));
+ httpInternal->SetCacheKeysRedirectChain(mRedirectedCachekeys.forget());
+ }
+
+ // Preserve CORS mode flag.
+ httpInternal->SetCorsMode(mCorsMode);
+
+ // Preserve Redirect mode flag.
+ httpInternal->SetRedirectMode(mRedirectMode);
+
+ // Preserve Cache mode flag.
+ httpInternal->SetFetchCacheMode(mFetchCacheMode);
+
+ // Preserve Integrity metadata.
+ httpInternal->SetIntegrityMetadata(mIntegrityMetadata);
+ }
+
+ // transfer application cache information
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(newChannel);
+ if (appCacheChannel) {
+ appCacheChannel->SetApplicationCache(mApplicationCache);
+ appCacheChannel->SetInheritApplicationCache(mInheritApplicationCache);
+ // We purposely avoid transfering mChooseApplicationCache.
+ }
+
+ // transfer any properties
+ nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
+ if (bag) {
+ for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) {
+ bag->SetProperty(iter.Key(), iter.UserData());
+ }
+ }
+
+ // Transfer the timing data (if we are dealing with an nsITimedChannel).
+ nsCOMPtr<nsITimedChannel> newTimedChannel(do_QueryInterface(newChannel));
+ nsCOMPtr<nsITimedChannel> oldTimedChannel(
+ do_QueryInterface(static_cast<nsIHttpChannel*>(this)));
+ if (oldTimedChannel && newTimedChannel) {
+ newTimedChannel->SetTimingEnabled(mTimingEnabled);
+ newTimedChannel->SetRedirectCount(mRedirectCount + 1);
+
+ // If the RedirectStart is null, we will use the AsyncOpen value of the
+ // previous channel (this is the first redirect in the redirects chain).
+ if (mRedirectStartTimeStamp.IsNull()) {
+ TimeStamp asyncOpen;
+ oldTimedChannel->GetAsyncOpen(&asyncOpen);
+ newTimedChannel->SetRedirectStart(asyncOpen);
+ }
+ else {
+ newTimedChannel->SetRedirectStart(mRedirectStartTimeStamp);
+ }
+
+ // The RedirectEnd timestamp is equal to the previous channel response end.
+ TimeStamp prevResponseEnd;
+ oldTimedChannel->GetResponseEnd(&prevResponseEnd);
+ newTimedChannel->SetRedirectEnd(prevResponseEnd);
+
+ nsAutoString initiatorType;
+ oldTimedChannel->GetInitiatorType(initiatorType);
+ newTimedChannel->SetInitiatorType(initiatorType);
+
+ // Check whether or not this was a cross-domain redirect.
+ newTimedChannel->SetAllRedirectsSameOrigin(
+ mAllRedirectsSameOrigin && SameOriginWithOriginalUri(newURI));
+
+ // Execute the timing allow check to determine whether
+ // to report the redirect timing info
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ GetLoadInfo(getter_AddRefs(loadInfo));
+ // TYPE_DOCUMENT loads don't have a loadingPrincipal, so we can't set
+ // AllRedirectsPassTimingAllowCheck on them.
+ if (loadInfo && loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT) {
+ nsCOMPtr<nsIPrincipal> principal = loadInfo->LoadingPrincipal();
+ newTimedChannel->SetAllRedirectsPassTimingAllowCheck(
+ mAllRedirectsPassTimingAllowCheck &&
+ oldTimedChannel->TimingAllowCheck(principal));
+ }
+ }
+
+ // Pass the preferred alt-data type on to the new channel.
+ nsCOMPtr<nsICacheInfoChannel> cacheInfoChan(do_QueryInterface(newChannel));
+ if (cacheInfoChan) {
+ cacheInfoChan->PreferAlternativeDataType(mPreferredCachedAltDataType);
+ }
+
+ if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
+ // Copy non-origin related headers to the new channel.
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new SetupReplacementChannelHeaderVisitor(httpChannel);
+ mRequestHead.VisitHeaders(visitor);
+ }
+
+ // This channel has been redirected. Don't report timing info.
+ mTimingEnabled = false;
+ return NS_OK;
+}
+
+// Redirect Tracking
+bool
+HttpBaseChannel::SameOriginWithOriginalUri(nsIURI *aURI)
+{
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ nsresult rv = ssm->CheckSameOriginURI(aURI, mOriginalURI, false);
+ return (NS_SUCCEEDED(rv));
+}
+
+
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsITimedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTimingEnabled(bool enabled) {
+ mTimingEnabled = enabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTimingEnabled(bool* _retval) {
+ *_retval = mTimingEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetChannelCreation(TimeStamp* _retval) {
+ *_retval = mChannelCreationTimestamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAsyncOpen(TimeStamp* _retval) {
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+/**
+ * @return the number of redirects. There is no check for cross-domain
+ * redirects. This check must be done by the consumers.
+ */
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectCount(uint16_t *aRedirectCount)
+{
+ *aRedirectCount = mRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectCount(uint16_t aRedirectCount)
+{
+ mRedirectCount = aRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectStart(TimeStamp* _retval)
+{
+ *_retval = mRedirectStartTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectStart(TimeStamp aRedirectStart)
+{
+ mRedirectStartTimeStamp = aRedirectStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectEnd(TimeStamp* _retval)
+{
+ *_retval = mRedirectEndTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectEnd(TimeStamp aRedirectEnd)
+{
+ mRedirectEndTimeStamp = aRedirectEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllRedirectsSameOrigin(bool *aAllRedirectsSameOrigin)
+{
+ *aAllRedirectsSameOrigin = mAllRedirectsSameOrigin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllRedirectsSameOrigin(bool aAllRedirectsSameOrigin)
+{
+ mAllRedirectsSameOrigin = aAllRedirectsSameOrigin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllRedirectsPassTimingAllowCheck(bool *aPassesCheck)
+{
+ *aPassesCheck = mAllRedirectsPassTimingAllowCheck;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllRedirectsPassTimingAllowCheck(bool aPassesCheck)
+{
+ mAllRedirectsPassTimingAllowCheck = aPassesCheck;
+ return NS_OK;
+}
+
+// http://www.w3.org/TR/resource-timing/#timing-allow-check
+NS_IMETHODIMP
+HttpBaseChannel::TimingAllowCheck(nsIPrincipal *aOrigin, bool *_retval)
+{
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ nsCOMPtr<nsIPrincipal> resourcePrincipal;
+ nsresult rv = ssm->GetChannelURIPrincipal(this, getter_AddRefs(resourcePrincipal));
+ if (NS_FAILED(rv) || !resourcePrincipal || !aOrigin) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ bool sameOrigin = false;
+ rv = resourcePrincipal->Equals(aOrigin, &sameOrigin);
+ if (NS_SUCCEEDED(rv) && sameOrigin) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ nsAutoCString headerValue;
+ rv = GetResponseHeader(NS_LITERAL_CSTRING("Timing-Allow-Origin"), headerValue);
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ if (headerValue == "*") {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ nsAutoCString origin;
+ nsContentUtils::GetASCIIOrigin(aOrigin, origin);
+
+ if (headerValue == origin) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDomainLookupStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.domainLookupStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDomainLookupEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.domainLookupEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetConnectStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.connectStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetConnectEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.connectEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.requestStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.responseStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.responseEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCacheReadStart(TimeStamp* _retval) {
+ *_retval = mCacheReadStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCacheReadEnd(TimeStamp* _retval) {
+ *_retval = mCacheReadEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetInitiatorType(nsAString & aInitiatorType)
+{
+ aInitiatorType = mInitiatorType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetInitiatorType(const nsAString & aInitiatorType)
+{
+ mInitiatorType = aInitiatorType;
+ return NS_OK;
+}
+
+#define IMPL_TIMING_ATTR(name) \
+NS_IMETHODIMP \
+HttpBaseChannel::Get##name##Time(PRTime* _retval) { \
+ TimeStamp stamp; \
+ Get##name(&stamp); \
+ if (stamp.IsNull()) { \
+ *_retval = 0; \
+ return NS_OK; \
+ } \
+ *_retval = mChannelCreationTime + \
+ (PRTime) ((stamp - mChannelCreationTimestamp).ToSeconds() * 1e6); \
+ return NS_OK; \
+}
+
+IMPL_TIMING_ATTR(ChannelCreation)
+IMPL_TIMING_ATTR(AsyncOpen)
+IMPL_TIMING_ATTR(DomainLookupStart)
+IMPL_TIMING_ATTR(DomainLookupEnd)
+IMPL_TIMING_ATTR(ConnectStart)
+IMPL_TIMING_ATTR(ConnectEnd)
+IMPL_TIMING_ATTR(RequestStart)
+IMPL_TIMING_ATTR(ResponseStart)
+IMPL_TIMING_ATTR(ResponseEnd)
+IMPL_TIMING_ATTR(CacheReadStart)
+IMPL_TIMING_ATTR(CacheReadEnd)
+IMPL_TIMING_ATTR(RedirectStart)
+IMPL_TIMING_ATTR(RedirectEnd)
+
+#undef IMPL_TIMING_ATTR
+
+mozilla::dom::Performance*
+HttpBaseChannel::GetPerformance()
+{
+ // If performance timing is disabled, there is no need for the Performance
+ // object anymore.
+ if (!mTimingEnabled) {
+ return nullptr;
+ }
+
+ // There is no point in continuing, since the performance object in the parent
+ // isn't the same as the one in the child which will be reporting resource performance.
+ if (XRE_IsParentProcess() && BrowserTabsRemoteAutostart()) {
+ return nullptr;
+ }
+
+ if (!mLoadInfo) {
+ return nullptr;
+ }
+
+ // We don't need to report the resource timing entry for a TYPE_DOCUMENT load.
+ if (mLoadInfo->GetExternalContentPolicyType() == nsIContentPolicyBase::TYPE_DOCUMENT) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(domDocument));
+ if (!domDocument) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocument> loadingDocument = do_QueryInterface(domDocument);
+ if (!loadingDocument) {
+ return nullptr;
+ }
+
+ // We only add to the document's performance object if it has the same
+ // principal as the one triggering the load. This is to prevent navigations
+ // triggered _by_ the iframe from showing up in the parent document's
+ // performance entries if they have different origins.
+ if (!mLoadInfo->TriggeringPrincipal()->Equals(loadingDocument->NodePrincipal())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow = loadingDocument->GetInnerWindow();
+ if (!innerWindow) {
+ return nullptr;
+ }
+
+ mozilla::dom::Performance* docPerformance = innerWindow->GetPerformance();
+ if (!docPerformance) {
+ return nullptr;
+ }
+
+ return docPerformance;
+}
+
+nsIURI*
+HttpBaseChannel::GetReferringPage()
+{
+ nsCOMPtr<nsPIDOMWindowInner> pDomWindow = GetInnerDOMWindow();
+ if (!pDomWindow) {
+ return nullptr;
+ }
+ return pDomWindow->GetDocumentURI();
+}
+
+nsPIDOMWindowInner*
+HttpBaseChannel::GetInnerDOMWindow()
+{
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ if (!loadContext) {
+ return nullptr;
+ }
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
+ if (!domWindow) {
+ return nullptr;
+ }
+ auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow);
+ if (!pDomWindow) {
+ return nullptr;
+ }
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow = pDomWindow->GetCurrentInnerWindow();
+ if (!innerWindow) {
+ return nullptr;
+ }
+
+ return innerWindow;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIThrottledInputChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetThrottleQueue(nsIInputChannelThrottleQueue* aQueue)
+{
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mThrottleQueue = aQueue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetThrottleQueue(nsIInputChannelThrottleQueue** aQueue)
+{
+ *aQueue = mThrottleQueue;
+ return NS_OK;
+}
+
+//------------------------------------------------------------------------------
+
+bool
+HttpBaseChannel::EnsureRequestContextID()
+{
+ nsID nullID;
+ nullID.Clear();
+ if (!mRequestContextID.Equals(nullID)) {
+ // Already have a request context ID, no need to do the rest of this work
+ return true;
+ }
+
+ // Find the loadgroup at the end of the chain in order
+ // to make sure all channels derived from the load group
+ // use the same connection scope.
+ nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(mLoadGroup);
+ if (!childLoadGroup) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadGroup> rootLoadGroup;
+ childLoadGroup->GetRootLoadGroup(getter_AddRefs(rootLoadGroup));
+ if (!rootLoadGroup) {
+ return false;
+ }
+
+ // Set the load group connection scope on the transaction
+ rootLoadGroup->GetRequestContextID(&mRequestContextID);
+ return true;
+}
+
+void
+HttpBaseChannel::SetCorsPreflightParameters(const nsTArray<nsCString>& aUnsafeHeaders)
+{
+ MOZ_RELEASE_ASSERT(!mRequestObserversCalled);
+
+ mRequireCORSPreflight = true;
+ mUnsafeHeaders = aUnsafeHeaders;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetBlockAuthPrompt(bool* aValue)
+{
+ if (!aValue) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aValue = mBlockAuthPrompt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetBlockAuthPrompt(bool aValue)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mBlockAuthPrompt = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetConnectionInfoHashKey(nsACString& aConnectionInfoHashKey)
+{
+ if (!mConnectionInfo) {
+ return NS_ERROR_FAILURE;
+ }
+ aConnectionInfoHashKey.Assign(mConnectionInfo->HashKey());
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h
new file mode 100644
index 000000000..c8184a601
--- /dev/null
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -0,0 +1,660 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_HttpBaseChannel_h
+#define mozilla_net_HttpBaseChannel_h
+
+#include "nsHttp.h"
+#include "nsAutoPtr.h"
+#include "nsHashPropertyBag.h"
+#include "nsProxyInfo.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIEncodedChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIForcePendingChannel.h"
+#include "nsIFormPOSTActionChannel.h"
+#include "nsIUploadChannel2.h"
+#include "nsIProgressEventSink.h"
+#include "nsIURI.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIStringEnumerator.h"
+#include "nsISupportsPriority.h"
+#include "nsIClassOfService.h"
+#include "nsIApplicationCache.h"
+#include "nsIResumableChannel.h"
+#include "nsITraceableChannel.h"
+#include "nsILoadContext.h"
+#include "nsILoadInfo.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "nsThreadUtils.h"
+#include "PrivateBrowsingChannel.h"
+#include "mozilla/net/DNS.h"
+#include "nsITimedChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsISecurityConsoleMessage.h"
+#include "nsCOMArray.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "nsIThrottledInputChannel.h"
+
+class nsISecurityConsoleMessage;
+class nsIPrincipal;
+
+namespace mozilla {
+
+namespace dom {
+class Performance;
+}
+
+class LogCollector;
+
+namespace net {
+extern mozilla::LazyLogModule gHttpLog;
+
+/*
+ * This class is a partial implementation of nsIHttpChannel. It contains code
+ * shared by nsHttpChannel and HttpChannelChild.
+ * - Note that this class has nothing to do with nsBaseChannel, which is an
+ * earlier effort at a base class for channels that somehow never made it all
+ * the way to the HTTP channel.
+ */
+class HttpBaseChannel : public nsHashPropertyBag
+ , public nsIEncodedChannel
+ , public nsIHttpChannel
+ , public nsIHttpChannelInternal
+ , public nsIFormPOSTActionChannel
+ , public nsIUploadChannel2
+ , public nsISupportsPriority
+ , public nsIClassOfService
+ , public nsIResumableChannel
+ , public nsITraceableChannel
+ , public PrivateBrowsingChannel<HttpBaseChannel>
+ , public nsITimedChannel
+ , public nsIForcePendingChannel
+ , public nsIConsoleReportCollector
+ , public nsIThrottledInputChannel
+{
+protected:
+ virtual ~HttpBaseChannel();
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIUPLOADCHANNEL
+ NS_DECL_NSIFORMPOSTACTIONCHANNEL
+ NS_DECL_NSIUPLOADCHANNEL2
+ NS_DECL_NSITRACEABLECHANNEL
+ NS_DECL_NSITIMEDCHANNEL
+ NS_DECL_NSITHROTTLEDINPUTCHANNEL
+
+ HttpBaseChannel();
+
+ virtual nsresult Init(nsIURI *aURI, uint32_t aCaps, nsProxyInfo *aProxyInfo,
+ uint32_t aProxyResolveFlags,
+ nsIURI *aProxyURI,
+ const nsID& aChannelId);
+
+ // nsIRequest
+ NS_IMETHOD GetName(nsACString& aName) override;
+ NS_IMETHOD IsPending(bool *aIsPending) override;
+ NS_IMETHOD GetStatus(nsresult *aStatus) override;
+ NS_IMETHOD GetLoadGroup(nsILoadGroup **aLoadGroup) override;
+ NS_IMETHOD SetLoadGroup(nsILoadGroup *aLoadGroup) override;
+ NS_IMETHOD GetLoadFlags(nsLoadFlags *aLoadFlags) override;
+ NS_IMETHOD SetLoadFlags(nsLoadFlags aLoadFlags) override;
+ NS_IMETHOD SetDocshellUserAgentOverride();
+
+ // nsIChannel
+ NS_IMETHOD GetOriginalURI(nsIURI **aOriginalURI) override;
+ NS_IMETHOD SetOriginalURI(nsIURI *aOriginalURI) override;
+ NS_IMETHOD GetURI(nsIURI **aURI) override;
+ NS_IMETHOD GetOwner(nsISupports **aOwner) override;
+ NS_IMETHOD SetOwner(nsISupports *aOwner) override;
+ NS_IMETHOD GetLoadInfo(nsILoadInfo **aLoadInfo) override;
+ NS_IMETHOD SetLoadInfo(nsILoadInfo *aLoadInfo) override;
+ NS_IMETHOD GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks) override;
+ NS_IMETHOD SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) override;
+ NS_IMETHOD GetContentType(nsACString& aContentType) override;
+ NS_IMETHOD SetContentType(const nsACString& aContentType) override;
+ NS_IMETHOD GetContentCharset(nsACString& aContentCharset) override;
+ NS_IMETHOD SetContentCharset(const nsACString& aContentCharset) override;
+ NS_IMETHOD GetContentDisposition(uint32_t *aContentDisposition) override;
+ NS_IMETHOD SetContentDisposition(uint32_t aContentDisposition) override;
+ NS_IMETHOD GetContentDispositionFilename(nsAString& aContentDispositionFilename) override;
+ NS_IMETHOD SetContentDispositionFilename(const nsAString& aContentDispositionFilename) override;
+ NS_IMETHOD GetContentDispositionHeader(nsACString& aContentDispositionHeader) override;
+ NS_IMETHOD GetContentLength(int64_t *aContentLength) override;
+ NS_IMETHOD SetContentLength(int64_t aContentLength) override;
+ NS_IMETHOD Open(nsIInputStream **aResult) override;
+ NS_IMETHOD Open2(nsIInputStream **aResult) override;
+ NS_IMETHOD GetBlockAuthPrompt(bool* aValue) override;
+ NS_IMETHOD SetBlockAuthPrompt(bool aValue) override;
+
+ // nsIEncodedChannel
+ NS_IMETHOD GetApplyConversion(bool *value) override;
+ NS_IMETHOD SetApplyConversion(bool value) override;
+ NS_IMETHOD GetContentEncodings(nsIUTF8StringEnumerator** aEncodings) override;
+ NS_IMETHOD DoApplyContentConversions(nsIStreamListener *aNextListener,
+ nsIStreamListener **aNewNextListener,
+ nsISupports *aCtxt) override;
+
+ // HttpBaseChannel::nsIHttpChannel
+ NS_IMETHOD GetRequestMethod(nsACString& aMethod) override;
+ NS_IMETHOD SetRequestMethod(const nsACString& aMethod) override;
+ NS_IMETHOD GetReferrer(nsIURI **referrer) override;
+ NS_IMETHOD SetReferrer(nsIURI *referrer) override;
+ NS_IMETHOD GetReferrerPolicy(uint32_t *referrerPolicy) override;
+ NS_IMETHOD SetReferrerWithPolicy(nsIURI *referrer, uint32_t referrerPolicy) override;
+ NS_IMETHOD GetRequestHeader(const nsACString& aHeader, nsACString& aValue) override;
+ NS_IMETHOD SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) override;
+ NS_IMETHOD SetEmptyRequestHeader(const nsACString& aHeader) override;
+ NS_IMETHOD VisitRequestHeaders(nsIHttpHeaderVisitor *visitor) override;
+ NS_IMETHOD VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor *visitor) override;
+ NS_IMETHOD GetResponseHeader(const nsACString &header, nsACString &value) override;
+ NS_IMETHOD SetResponseHeader(const nsACString& header,
+ const nsACString& value, bool merge) override;
+ NS_IMETHOD VisitResponseHeaders(nsIHttpHeaderVisitor *visitor) override;
+ NS_IMETHOD GetOriginalResponseHeader(const nsACString &aHeader,
+ nsIHttpHeaderVisitor *aVisitor) override;
+ NS_IMETHOD VisitOriginalResponseHeaders(nsIHttpHeaderVisitor *aVisitor) override;
+ NS_IMETHOD GetAllowPipelining(bool *value) override;
+ NS_IMETHOD SetAllowPipelining(bool value) override;
+ NS_IMETHOD GetAllowSTS(bool *value) override;
+ NS_IMETHOD SetAllowSTS(bool value) override;
+ NS_IMETHOD GetRedirectionLimit(uint32_t *value) override;
+ NS_IMETHOD SetRedirectionLimit(uint32_t value) override;
+ NS_IMETHOD IsNoStoreResponse(bool *value) override;
+ NS_IMETHOD IsNoCacheResponse(bool *value) override;
+ NS_IMETHOD IsPrivateResponse(bool *value) override;
+ NS_IMETHOD GetResponseStatus(uint32_t *aValue) override;
+ NS_IMETHOD GetResponseStatusText(nsACString& aValue) override;
+ NS_IMETHOD GetRequestSucceeded(bool *aValue) override;
+ NS_IMETHOD RedirectTo(nsIURI *newURI) override;
+ NS_IMETHOD GetRequestContextID(nsID *aRCID) override;
+ NS_IMETHOD GetTransferSize(uint64_t *aTransferSize) override;
+ NS_IMETHOD GetDecodedBodySize(uint64_t *aDecodedBodySize) override;
+ NS_IMETHOD GetEncodedBodySize(uint64_t *aEncodedBodySize) override;
+ NS_IMETHOD SetRequestContextID(const nsID aRCID) override;
+ NS_IMETHOD GetIsMainDocumentChannel(bool* aValue) override;
+ NS_IMETHOD SetIsMainDocumentChannel(bool aValue) override;
+ NS_IMETHOD GetProtocolVersion(nsACString & aProtocolVersion) override;
+ NS_IMETHOD GetChannelId(nsACString& aChannelId) override;
+ NS_IMETHOD SetChannelId(const nsACString& aChannelId) override;
+ NS_IMETHOD GetTopLevelContentWindowId(uint64_t *aContentWindowId) override;
+ NS_IMETHOD SetTopLevelContentWindowId(uint64_t aContentWindowId) override;
+
+ // nsIHttpChannelInternal
+ NS_IMETHOD GetDocumentURI(nsIURI **aDocumentURI) override;
+ NS_IMETHOD SetDocumentURI(nsIURI *aDocumentURI) override;
+ NS_IMETHOD GetRequestVersion(uint32_t *major, uint32_t *minor) override;
+ NS_IMETHOD GetResponseVersion(uint32_t *major, uint32_t *minor) override;
+ NS_IMETHOD SetCookie(const char *aCookieHeader) override;
+ NS_IMETHOD GetThirdPartyFlags(uint32_t *aForce) override;
+ NS_IMETHOD SetThirdPartyFlags(uint32_t aForce) override;
+ NS_IMETHOD GetForceAllowThirdPartyCookie(bool *aForce) override;
+ NS_IMETHOD SetForceAllowThirdPartyCookie(bool aForce) override;
+ NS_IMETHOD GetCanceled(bool *aCanceled) override;
+ NS_IMETHOD GetChannelIsForDownload(bool *aChannelIsForDownload) override;
+ NS_IMETHOD SetChannelIsForDownload(bool aChannelIsForDownload) override;
+ NS_IMETHOD SetCacheKeysRedirectChain(nsTArray<nsCString> *cacheKeys) override;
+ NS_IMETHOD GetLocalAddress(nsACString& addr) override;
+ NS_IMETHOD GetLocalPort(int32_t* port) override;
+ NS_IMETHOD GetRemoteAddress(nsACString& addr) override;
+ NS_IMETHOD GetRemotePort(int32_t* port) override;
+ NS_IMETHOD GetAllowSpdy(bool *aAllowSpdy) override;
+ NS_IMETHOD SetAllowSpdy(bool aAllowSpdy) override;
+ NS_IMETHOD GetAllowAltSvc(bool *aAllowAltSvc) override;
+ NS_IMETHOD SetAllowAltSvc(bool aAllowAltSvc) override;
+ NS_IMETHOD GetBeConservative(bool *aBeConservative) override;
+ NS_IMETHOD SetBeConservative(bool aBeConservative) override;
+ NS_IMETHOD GetApiRedirectToURI(nsIURI * *aApiRedirectToURI) override;
+ virtual nsresult AddSecurityMessage(const nsAString &aMessageTag, const nsAString &aMessageCategory);
+ NS_IMETHOD TakeAllSecurityMessages(nsCOMArray<nsISecurityConsoleMessage> &aMessages) override;
+ NS_IMETHOD GetResponseTimeoutEnabled(bool *aEnable) override;
+ NS_IMETHOD SetResponseTimeoutEnabled(bool aEnable) override;
+ NS_IMETHOD GetInitialRwin(uint32_t* aRwin) override;
+ NS_IMETHOD SetInitialRwin(uint32_t aRwin) override;
+ NS_IMETHOD GetNetworkInterfaceId(nsACString& aNetworkInterfaceId) override;
+ NS_IMETHOD SetNetworkInterfaceId(const nsACString& aNetworkInterfaceId) override;
+ NS_IMETHOD ForcePending(bool aForcePending) override;
+ NS_IMETHOD GetLastModifiedTime(PRTime* lastModifiedTime) override;
+ NS_IMETHOD GetCorsIncludeCredentials(bool* aInclude) override;
+ NS_IMETHOD SetCorsIncludeCredentials(bool aInclude) override;
+ NS_IMETHOD GetCorsMode(uint32_t* aCorsMode) override;
+ NS_IMETHOD SetCorsMode(uint32_t aCorsMode) override;
+ NS_IMETHOD GetRedirectMode(uint32_t* aRedirectMode) override;
+ NS_IMETHOD SetRedirectMode(uint32_t aRedirectMode) override;
+ NS_IMETHOD GetFetchCacheMode(uint32_t* aFetchCacheMode) override;
+ NS_IMETHOD SetFetchCacheMode(uint32_t aFetchCacheMode) override;
+ NS_IMETHOD GetTopWindowURI(nsIURI **aTopWindowURI) override;
+ NS_IMETHOD GetProxyURI(nsIURI **proxyURI) override;
+ virtual void SetCorsPreflightParameters(const nsTArray<nsCString>& unsafeHeaders) override;
+ NS_IMETHOD GetConnectionInfoHashKey(nsACString& aConnectionInfoHashKey) override;
+ NS_IMETHOD GetIntegrityMetadata(nsAString& aIntegrityMetadata) override;
+ NS_IMETHOD SetIntegrityMetadata(const nsAString& aIntegrityMetadata) override;
+ virtual mozilla::net::nsHttpChannel * QueryHttpChannelImpl(void) override;
+
+ inline void CleanRedirectCacheChainIfNecessary()
+ {
+ mRedirectedCachekeys = nullptr;
+ }
+ NS_IMETHOD HTTPUpgrade(const nsACString & aProtocolName,
+ nsIHttpUpgradeListener *aListener) override;
+
+ // nsISupportsPriority
+ NS_IMETHOD GetPriority(int32_t *value) override;
+ NS_IMETHOD AdjustPriority(int32_t delta) override;
+
+ // nsIClassOfService
+ NS_IMETHOD GetClassFlags(uint32_t *outFlags) override { *outFlags = mClassOfService; return NS_OK; }
+
+ // nsIResumableChannel
+ NS_IMETHOD GetEntityID(nsACString& aEntityID) override;
+
+
+ // nsIConsoleReportCollector
+ void
+ AddConsoleReport(uint32_t aErrorFlags, const nsACString& aCategory,
+ nsContentUtils::PropertiesFile aPropertiesFile,
+ const nsACString& aSourceFileURI,
+ uint32_t aLineNumber, uint32_t aColumnNumber,
+ const nsACString& aMessageName,
+ const nsTArray<nsString>& aStringParams) override;
+
+ void
+ FlushConsoleReports(nsIDocument* aDocument,
+ ReportAction aAction = ReportAction::Forget) override;
+
+ void
+ FlushConsoleReports(nsIConsoleReportCollector* aCollector) override;
+
+ void
+ FlushReportsByWindowId(uint64_t aWindowId,
+ ReportAction aAction = ReportAction::Forget) override;
+
+ void
+ ClearConsoleReports() override;
+
+ class nsContentEncodings : public nsIUTF8StringEnumerator
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+
+ nsContentEncodings(nsIHttpChannel* aChannel, const char* aEncodingHeader);
+
+ private:
+ virtual ~nsContentEncodings();
+
+ nsresult PrepareForNext(void);
+
+ // We do not own the buffer. The channel owns it.
+ const char* mEncodingHeader;
+ const char* mCurStart; // points to start of current header
+ const char* mCurEnd; // points to end of current header
+
+ // Hold a ref to our channel so that it can't go away and take the
+ // header with it.
+ nsCOMPtr<nsIHttpChannel> mChannel;
+
+ bool mReady;
+ };
+
+ nsHttpResponseHead * GetResponseHead() const { return mResponseHead; }
+ nsHttpRequestHead * GetRequestHead() { return &mRequestHead; }
+
+ const NetAddr& GetSelfAddr() { return mSelfAddr; }
+ const NetAddr& GetPeerAddr() { return mPeerAddr; }
+
+ nsresult OverrideSecurityInfo(nsISupports* aSecurityInfo);
+
+public: /* Necko internal use only... */
+ bool IsNavigation();
+
+ // Return whether upon a redirect code of httpStatus for method, the
+ // request method should be rewritten to GET.
+ static bool ShouldRewriteRedirectToGET(uint32_t httpStatus,
+ nsHttpRequestHead::ParsedMethodType method);
+
+ // Like nsIEncodedChannel::DoApplyConversions except context is set to
+ // mListenerContext.
+ nsresult DoApplyContentConversions(nsIStreamListener *aNextListener,
+ nsIStreamListener **aNewNextListener);
+
+ // Callback on main thread when NS_AsyncCopy() is finished populating
+ // the new mUploadStream.
+ void EnsureUploadStreamIsCloneableComplete(nsresult aStatus);
+
+protected:
+ nsCOMArray<nsISecurityConsoleMessage> mSecurityConsoleMessages;
+
+ // Handle notifying listener, removing from loadgroup if request failed.
+ void DoNotifyListener();
+ virtual void DoNotifyListenerCleanup() = 0;
+
+ // drop reference to listener, its callbacks, and the progress sink
+ void ReleaseListeners();
+
+ // This is fired only when a cookie is created due to the presence of
+ // Set-Cookie header in the response header of any network request.
+ // This notification will come only after the "http-on-examine-response"
+ // was fired.
+ void NotifySetCookie(char const *aCookie);
+
+ mozilla::dom::Performance* GetPerformance();
+ nsIURI* GetReferringPage();
+ nsPIDOMWindowInner* GetInnerDOMWindow();
+
+ void AddCookiesToRequest();
+ virtual nsresult SetupReplacementChannel(nsIURI *,
+ nsIChannel *,
+ bool preserveMethod,
+ uint32_t redirectFlags);
+
+ // bundle calling OMR observers and marking flag into one function
+ inline void CallOnModifyRequestObservers() {
+ gHttpHandler->OnModifyRequest(this);
+ mRequestObserversCalled = true;
+ }
+
+ // Helper function to simplify getting notification callbacks.
+ template <class T>
+ void GetCallback(nsCOMPtr<T> &aResult)
+ {
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_TEMPLATE_IID(T),
+ getter_AddRefs(aResult));
+ }
+
+ // Redirect tracking
+ // Checks whether or not aURI and mOriginalURI share the same domain.
+ bool SameOriginWithOriginalUri(nsIURI *aURI);
+
+ // GetPrincipal Returns the channel's URI principal.
+ nsIPrincipal *GetURIPrincipal();
+
+ bool BypassServiceWorker() const;
+
+ // Returns true if this channel should intercept the network request and prepare
+ // for a possible synthesized response instead.
+ bool ShouldIntercept(nsIURI* aURI = nullptr);
+
+#ifdef DEBUG
+ // Check if mPrivateBrowsingId matches between LoadInfo and LoadContext.
+ void AssertPrivateBrowsingId();
+#endif
+
+ friend class PrivateBrowsingChannel<HttpBaseChannel>;
+ friend class InterceptFailedOnStop;
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mListenerContext;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIURI> mReferrer;
+ nsCOMPtr<nsIApplicationCache> mApplicationCache;
+
+ // An instance of nsHTTPCompressConv
+ nsCOMPtr<nsIStreamListener> mCompressListener;
+
+ nsHttpRequestHead mRequestHead;
+ // Upload throttling.
+ nsCOMPtr<nsIInputChannelThrottleQueue> mThrottleQueue;
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ nsCOMPtr<nsIRunnable> mUploadCloneableCallback;
+ nsAutoPtr<nsHttpResponseHead> mResponseHead;
+ RefPtr<nsHttpConnectionInfo> mConnectionInfo;
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+
+ nsCString mSpec; // ASCII encoded URL spec
+ nsCString mContentTypeHint;
+ nsCString mContentCharsetHint;
+ nsCString mUserSetCookieHeader;
+
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+
+ // HTTP Upgrade Data
+ nsCString mUpgradeProtocol;
+ nsCOMPtr<nsIHttpUpgradeListener> mUpgradeProtocolCallback;
+
+ // Resumable channel specific data
+ nsCString mEntityID;
+ uint64_t mStartPos;
+
+ nsresult mStatus;
+ uint32_t mLoadFlags;
+ uint32_t mCaps;
+ uint32_t mClassOfService;
+ int16_t mPriority;
+ uint8_t mRedirectionLimit;
+
+ uint32_t mApplyConversion : 1;
+ uint32_t mCanceled : 1;
+ uint32_t mIsPending : 1;
+ uint32_t mWasOpened : 1;
+ // if 1 all "http-on-{opening|modify|etc}-request" observers have been called
+ uint32_t mRequestObserversCalled : 1;
+ uint32_t mResponseHeadersModified : 1;
+ uint32_t mAllowPipelining : 1;
+ uint32_t mAllowSTS : 1;
+ uint32_t mThirdPartyFlags : 3;
+ uint32_t mUploadStreamHasHeaders : 1;
+ uint32_t mInheritApplicationCache : 1;
+ uint32_t mChooseApplicationCache : 1;
+ uint32_t mLoadedFromApplicationCache : 1;
+ uint32_t mChannelIsForDownload : 1;
+ uint32_t mTracingEnabled : 1;
+ // True if timing collection is enabled
+ uint32_t mTimingEnabled : 1;
+ uint32_t mAllowSpdy : 1;
+ uint32_t mAllowAltSvc : 1;
+ uint32_t mBeConservative : 1;
+ uint32_t mResponseTimeoutEnabled : 1;
+ // A flag that should be false only if a cross-domain redirect occurred
+ uint32_t mAllRedirectsSameOrigin : 1;
+
+ // Is 1 if no redirects have occured or if all redirects
+ // pass the Resource Timing timing-allow-check
+ uint32_t mAllRedirectsPassTimingAllowCheck : 1;
+
+ // True if this channel was intercepted and could receive a synthesized response.
+ uint32_t mResponseCouldBeSynthesized : 1;
+
+ uint32_t mBlockAuthPrompt : 1;
+
+ // If true, we behave as if the LOAD_FROM_CACHE flag has been set.
+ // Used to enforce that flag's behavior but not expose it externally.
+ uint32_t mAllowStaleCacheContent : 1;
+
+ // Current suspension depth for this channel object
+ uint32_t mSuspendCount;
+
+ // Per channel transport window override (0 means no override)
+ uint32_t mInitialRwin;
+
+ nsCOMPtr<nsIURI> mAPIRedirectToURI;
+ nsAutoPtr<nsTArray<nsCString> > mRedirectedCachekeys;
+
+ uint32_t mProxyResolveFlags;
+ nsCOMPtr<nsIURI> mProxyURI;
+
+ uint32_t mContentDispositionHint;
+ nsAutoPtr<nsString> mContentDispositionFilename;
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+
+ uint32_t mReferrerPolicy;
+
+ // Performance tracking
+ // The initiator type (for this resource) - how was the resource referenced in
+ // the HTML file.
+ nsString mInitiatorType;
+ // Number of redirects that has occurred.
+ int16_t mRedirectCount;
+ // A time value equal to the starting time of the fetch that initiates the
+ // redirect.
+ mozilla::TimeStamp mRedirectStartTimeStamp;
+ // A time value equal to the time immediately after receiving the last byte of
+ // the response of the last redirect.
+ mozilla::TimeStamp mRedirectEndTimeStamp;
+
+ PRTime mChannelCreationTime;
+ TimeStamp mChannelCreationTimestamp;
+ TimeStamp mAsyncOpenTime;
+ TimeStamp mCacheReadStart;
+ TimeStamp mCacheReadEnd;
+ // copied from the transaction before we null out mTransaction
+ // so that the timing can still be queried from OnStopRequest
+ TimingStruct mTransactionTimings;
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+
+ bool mForcePending;
+ nsCOMPtr<nsIURI> mTopWindowURI;
+
+ bool mCorsIncludeCredentials;
+ uint32_t mCorsMode;
+ uint32_t mRedirectMode;
+ uint32_t mFetchCacheMode;
+
+ // These parameters are used to ensure that we do not call OnStartRequest and
+ // OnStopRequest more than once.
+ bool mOnStartRequestCalled;
+ bool mOnStopRequestCalled;
+
+ // Defaults to false. Is set to true at the begining of OnStartRequest.
+ // Used to ensure methods can't be called before OnStartRequest.
+ bool mAfterOnStartRequestBegun;
+
+ uint64_t mTransferSize;
+ uint64_t mDecodedBodySize;
+ uint64_t mEncodedBodySize;
+
+ // The network interface id that's associated with this channel.
+ nsCString mNetworkInterfaceId;
+
+ nsID mRequestContextID;
+ bool EnsureRequestContextID();
+
+ // ID of the top-level document's inner window this channel is being
+ // originated from.
+ uint64_t mContentWindowId;
+
+ bool mRequireCORSPreflight;
+ nsTArray<nsCString> mUnsafeHeaders;
+
+ nsCOMPtr<nsIConsoleReportCollector> mReportCollector;
+
+ // Holds the name of the preferred alt-data type.
+ nsCString mPreferredCachedAltDataType;
+ // Holds the name of the alternative data type the channel returned.
+ nsCString mAvailableCachedAltDataType;
+
+ bool mForceMainDocumentChannel;
+
+ nsID mChannelId;
+
+ nsString mIntegrityMetadata;
+};
+
+// Share some code while working around C++'s absurd inability to handle casting
+// of member functions between base/derived types.
+// - We want to store member function pointer to call at resume time, but one
+// such function--HandleAsyncAbort--we want to share between the
+// nsHttpChannel/HttpChannelChild. Can't define it in base class, because
+// then we'd have to cast member function ptr between base/derived class
+// types. Sigh...
+template <class T>
+class HttpAsyncAborter
+{
+public:
+ explicit HttpAsyncAborter(T *derived) : mThis(derived), mCallOnResume(0) {}
+
+ // Aborts channel: calls OnStart/Stop with provided status, removes channel
+ // from loadGroup.
+ nsresult AsyncAbort(nsresult status);
+
+ // Does most the actual work.
+ void HandleAsyncAbort();
+
+ // AsyncCall calls a member function asynchronously (via an event).
+ // retval isn't refcounted and is set only when event was successfully
+ // posted, the event is returned for the purpose of cancelling when needed
+ nsresult AsyncCall(void (T::*funcPtr)(),
+ nsRunnableMethod<T> **retval = nullptr);
+private:
+ T *mThis;
+
+protected:
+ // Function to be called at resume time
+ void (T::* mCallOnResume)(void);
+};
+
+template <class T>
+nsresult HttpAsyncAborter<T>::AsyncAbort(nsresult status)
+{
+ MOZ_LOG(gHttpLog, LogLevel::Debug,
+ ("HttpAsyncAborter::AsyncAbort [this=%p status=%x]\n", mThis, status));
+
+ mThis->mStatus = status;
+
+ // if this fails? Callers ignore our return value anyway....
+ return AsyncCall(&T::HandleAsyncAbort);
+}
+
+// Each subclass needs to define its own version of this (which just calls this
+// base version), else we wind up casting base/derived member function ptrs
+template <class T>
+inline void HttpAsyncAborter<T>::HandleAsyncAbort()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+
+ if (mThis->mSuspendCount) {
+ MOZ_LOG(gHttpLog, LogLevel::Debug,
+ ("Waiting until resume to do async notification [this=%p]\n", mThis));
+ mCallOnResume = &T::HandleAsyncAbort;
+ return;
+ }
+
+ mThis->DoNotifyListener();
+
+ // finally remove ourselves from the load group.
+ if (mThis->mLoadGroup)
+ mThis->mLoadGroup->RemoveRequest(mThis, nullptr, mThis->mStatus);
+}
+
+template <class T>
+nsresult HttpAsyncAborter<T>::AsyncCall(void (T::*funcPtr)(),
+ nsRunnableMethod<T> **retval)
+{
+ nsresult rv;
+
+ RefPtr<nsRunnableMethod<T>> event = NewRunnableMethod(mThis, funcPtr);
+ rv = NS_DispatchToCurrentThread(event);
+ if (NS_SUCCEEDED(rv) && retval) {
+ *retval = event;
+ }
+
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpBaseChannel_h
diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp
new file mode 100644
index 000000000..0de6095e1
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -0,0 +1,2849 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttp.h"
+#include "nsICacheEntry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/ipc/FileDescriptorSetChild.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/HttpChannelChild.h"
+
+#include "nsISupportsPrimitives.h"
+#include "nsChannelClassifier.h"
+#include "nsStringStream.h"
+#include "nsHttpHandler.h"
+#include "nsNetUtil.h"
+#include "nsSerializationHelper.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/net/ChannelDiverterChild.h"
+#include "mozilla/net/DNS.h"
+#include "SerializedLoadContext.h"
+#include "nsInputStreamPump.h"
+#include "InterceptedChannel.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsContentSecurityManager.h"
+#include "nsIDeprecationWarner.h"
+#include "nsICompressConvStats.h"
+#include "nsIDocument.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsStreamUtils.h"
+
+#ifdef OS_POSIX
+#include "chrome/common/file_descriptor_set_posix.h"
+#endif
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+extern bool
+WillRedirect(nsHttpResponseHead * response);
+
+namespace {
+
+const uint32_t kMaxFileDescriptorsPerMessage = 250;
+
+#ifdef OS_POSIX
+// Keep this in sync with other platforms.
+static_assert(FileDescriptorSet::MAX_DESCRIPTORS_PER_MESSAGE == 250,
+ "MAX_DESCRIPTORS_PER_MESSAGE mismatch!");
+#endif
+
+} // namespace
+
+
+NS_IMPL_ISUPPORTS(InterceptStreamListener,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIProgressEventSink)
+
+NS_IMETHODIMP
+InterceptStreamListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+ if (mOwner) {
+ mOwner->DoOnStartRequest(mOwner, mContext);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptStreamListener::OnStatus(nsIRequest* aRequest, nsISupports* aContext,
+ nsresult status, const char16_t* aStatusArg)
+{
+ if (mOwner) {
+ mOwner->DoOnStatus(mOwner, status);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptStreamListener::OnProgress(nsIRequest* aRequest, nsISupports* aContext,
+ int64_t aProgress, int64_t aProgressMax)
+{
+ if (mOwner) {
+ mOwner->DoOnProgress(mOwner, aProgress, aProgressMax);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptStreamListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
+ nsIInputStream* aInputStream, uint64_t aOffset,
+ uint32_t aCount)
+{
+ if (!mOwner) {
+ return NS_OK;
+ }
+
+ uint32_t loadFlags;
+ mOwner->GetLoadFlags(&loadFlags);
+
+ if (!(loadFlags & HttpBaseChannel::LOAD_BACKGROUND)) {
+ nsCOMPtr<nsIURI> uri;
+ mOwner->GetURI(getter_AddRefs(uri));
+
+ nsAutoCString host;
+ uri->GetHost(host);
+
+ OnStatus(mOwner, aContext, NS_NET_STATUS_READING, NS_ConvertUTF8toUTF16(host).get());
+
+ int64_t progress = aOffset + aCount;
+ OnProgress(mOwner, aContext, progress, mOwner->mSynthesizedStreamLength);
+ }
+
+ mOwner->DoOnDataAvailable(mOwner, mContext, aInputStream, aOffset, aCount);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptStreamListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatusCode)
+{
+ if (mOwner) {
+ mOwner->DoPreOnStopRequest(aStatusCode);
+ mOwner->DoOnStopRequest(mOwner, aStatusCode, mContext);
+ }
+ Cleanup();
+ return NS_OK;
+}
+
+void
+InterceptStreamListener::Cleanup()
+{
+ mOwner = nullptr;
+ mContext = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild
+//-----------------------------------------------------------------------------
+
+HttpChannelChild::HttpChannelChild()
+ : HttpAsyncAborter<HttpChannelChild>(this)
+ , mSynthesizedStreamLength(0)
+ , mIsFromCache(false)
+ , mCacheEntryAvailable(false)
+ , mCacheExpirationTime(nsICacheEntry::NO_EXPIRATION_TIME)
+ , mSendResumeAt(false)
+ , mIPCOpen(false)
+ , mKeptAlive(false)
+ , mUnknownDecoderInvolved(false)
+ , mDivertingToParent(false)
+ , mFlushedForDiversion(false)
+ , mSuspendSent(false)
+ , mSynthesizedResponse(false)
+ , mShouldInterceptSubsequentRedirect(false)
+ , mRedirectingForSubsequentSynthesizedResponse(false)
+ , mPostRedirectChannelShouldIntercept(false)
+ , mPostRedirectChannelShouldUpgrade(false)
+ , mShouldParentIntercept(false)
+ , mSuspendParentAfterSynthesizeResponse(false)
+{
+ LOG(("Creating HttpChannelChild @%x\n", this));
+
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+ mAsyncOpenTime = TimeStamp::Now();
+ mEventQ = new ChannelEventQueue(static_cast<nsIHttpChannel*>(this));
+}
+
+HttpChannelChild::~HttpChannelChild()
+{
+ LOG(("Destroying HttpChannelChild @%x\n", this));
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsISupports
+//-----------------------------------------------------------------------------
+
+// Override nsHashPropertyBag's AddRef: we don't need thread-safe refcnt
+NS_IMPL_ADDREF(HttpChannelChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) HttpChannelChild::Release()
+{
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ NS_ASSERT_OWNINGTHREAD(HttpChannelChild);
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "HttpChannelChild");
+
+ // Normally we Send_delete in OnStopRequest, but when we need to retain the
+ // remote channel for security info IPDL itself holds 1 reference, so we
+ // Send_delete when refCnt==1. But if !mIPCOpen, then there's nobody to send
+ // to, so we fall through.
+ if (mKeptAlive && mRefCnt == 1 && mIPCOpen) {
+ mKeptAlive = false;
+ // We send a message to the parent, which calls SendDelete, and then the
+ // child calling Send__delete__() to finally drop the refcount to 0.
+ SendDeletingChannel();
+ return 1;
+ }
+
+ if (mRefCnt == 0) {
+ mRefCnt = 1; /* stabilize */
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+}
+
+NS_INTERFACE_MAP_BEGIN(HttpChannelChild)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
+ NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
+ NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIChildChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelChild)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAssociatedContentSecurity, GetAssociatedContentSecurity())
+ NS_INTERFACE_MAP_ENTRY(nsIDivertableChannel)
+NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::PHttpChannelChild
+//-----------------------------------------------------------------------------
+
+void
+HttpChannelChild::AddIPDLReference()
+{
+ MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void
+HttpChannelChild::ReleaseIPDLReference()
+{
+ MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference");
+ mIPCOpen = false;
+ Release();
+}
+
+class AssociateApplicationCacheEvent : public ChannelEvent
+{
+ public:
+ AssociateApplicationCacheEvent(HttpChannelChild* aChild,
+ const nsCString &aGroupID,
+ const nsCString &aClientID)
+ : mChild(aChild)
+ , groupID(aGroupID)
+ , clientID(aClientID) {}
+
+ void Run() { mChild->AssociateApplicationCache(groupID, clientID); }
+ private:
+ HttpChannelChild* mChild;
+ nsCString groupID;
+ nsCString clientID;
+};
+
+bool
+HttpChannelChild::RecvAssociateApplicationCache(const nsCString &groupID,
+ const nsCString &clientID)
+{
+ LOG(("HttpChannelChild::RecvAssociateApplicationCache [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new AssociateApplicationCacheEvent(this, groupID,
+ clientID));
+ return true;
+}
+
+void
+HttpChannelChild::AssociateApplicationCache(const nsCString &groupID,
+ const nsCString &clientID)
+{
+ LOG(("HttpChannelChild::AssociateApplicationCache [this=%p]\n", this));
+ nsresult rv;
+ mApplicationCache = do_CreateInstance(NS_APPLICATIONCACHE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return;
+
+ mLoadedFromApplicationCache = true;
+ mApplicationCache->InitAsHandle(groupID, clientID);
+}
+
+class StartRequestEvent : public ChannelEvent
+{
+ public:
+ StartRequestEvent(HttpChannelChild* aChild,
+ const nsresult& aChannelStatus,
+ const nsHttpResponseHead& aResponseHead,
+ const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const bool& aIsFromCache,
+ const bool& aCacheEntryAvailable,
+ const uint32_t& aCacheExpirationTime,
+ const nsCString& aCachedCharset,
+ const nsCString& aSecurityInfoSerialization,
+ const NetAddr& aSelfAddr,
+ const NetAddr& aPeerAddr,
+ const uint32_t& aCacheKey,
+ const nsCString& altDataType)
+ : mChild(aChild)
+ , mChannelStatus(aChannelStatus)
+ , mResponseHead(aResponseHead)
+ , mRequestHeaders(aRequestHeaders)
+ , mUseResponseHead(aUseResponseHead)
+ , mIsFromCache(aIsFromCache)
+ , mCacheEntryAvailable(aCacheEntryAvailable)
+ , mCacheExpirationTime(aCacheExpirationTime)
+ , mCachedCharset(aCachedCharset)
+ , mSecurityInfoSerialization(aSecurityInfoSerialization)
+ , mSelfAddr(aSelfAddr)
+ , mPeerAddr(aPeerAddr)
+ , mCacheKey(aCacheKey)
+ , mAltDataType(altDataType)
+ {}
+
+ void Run()
+ {
+ LOG(("StartRequestEvent [this=%p]\n", mChild));
+ mChild->OnStartRequest(mChannelStatus, mResponseHead, mUseResponseHead,
+ mRequestHeaders, mIsFromCache, mCacheEntryAvailable,
+ mCacheExpirationTime, mCachedCharset,
+ mSecurityInfoSerialization, mSelfAddr, mPeerAddr,
+ mCacheKey, mAltDataType);
+ }
+ private:
+ HttpChannelChild* mChild;
+ nsresult mChannelStatus;
+ nsHttpResponseHead mResponseHead;
+ nsHttpHeaderArray mRequestHeaders;
+ bool mUseResponseHead;
+ bool mIsFromCache;
+ bool mCacheEntryAvailable;
+ uint32_t mCacheExpirationTime;
+ nsCString mCachedCharset;
+ nsCString mSecurityInfoSerialization;
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+ uint32_t mCacheKey;
+ nsCString mAltDataType;
+};
+
+bool
+HttpChannelChild::RecvOnStartRequest(const nsresult& channelStatus,
+ const nsHttpResponseHead& responseHead,
+ const bool& useResponseHead,
+ const nsHttpHeaderArray& requestHeaders,
+ const bool& isFromCache,
+ const bool& cacheEntryAvailable,
+ const uint32_t& cacheExpirationTime,
+ const nsCString& cachedCharset,
+ const nsCString& securityInfoSerialization,
+ const NetAddr& selfAddr,
+ const NetAddr& peerAddr,
+ const int16_t& redirectCount,
+ const uint32_t& cacheKey,
+ const nsCString& altDataType)
+{
+ LOG(("HttpChannelChild::RecvOnStartRequest [this=%p]\n", this));
+ // mFlushedForDiversion and mDivertingToParent should NEVER be set at this
+ // stage, as they are set in the listener's OnStartRequest.
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "mFlushedForDiversion should be unset before OnStartRequest!");
+ MOZ_RELEASE_ASSERT(!mDivertingToParent,
+ "mDivertingToParent should be unset before OnStartRequest!");
+
+
+ mRedirectCount = redirectCount;
+
+ mEventQ->RunOrEnqueue(new StartRequestEvent(this, channelStatus, responseHead,
+ useResponseHead, requestHeaders,
+ isFromCache, cacheEntryAvailable,
+ cacheExpirationTime,
+ cachedCharset,
+ securityInfoSerialization,
+ selfAddr, peerAddr, cacheKey,
+ altDataType));
+ return true;
+}
+
+void
+HttpChannelChild::OnStartRequest(const nsresult& channelStatus,
+ const nsHttpResponseHead& responseHead,
+ const bool& useResponseHead,
+ const nsHttpHeaderArray& requestHeaders,
+ const bool& isFromCache,
+ const bool& cacheEntryAvailable,
+ const uint32_t& cacheExpirationTime,
+ const nsCString& cachedCharset,
+ const nsCString& securityInfoSerialization,
+ const NetAddr& selfAddr,
+ const NetAddr& peerAddr,
+ const uint32_t& cacheKey,
+ const nsCString& altDataType)
+{
+ LOG(("HttpChannelChild::OnStartRequest [this=%p]\n", this));
+
+ // mFlushedForDiversion and mDivertingToParent should NEVER be set at this
+ // stage, as they are set in the listener's OnStartRequest.
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "mFlushedForDiversion should be unset before OnStartRequest!");
+ MOZ_RELEASE_ASSERT(!mDivertingToParent,
+ "mDivertingToParent should be unset before OnStartRequest!");
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = channelStatus;
+ }
+
+ if (useResponseHead && !mCanceled)
+ mResponseHead = new nsHttpResponseHead(responseHead);
+
+ if (!securityInfoSerialization.IsEmpty()) {
+ NS_DeserializeObject(securityInfoSerialization,
+ getter_AddRefs(mSecurityInfo));
+ }
+
+ mIsFromCache = isFromCache;
+ mCacheEntryAvailable = cacheEntryAvailable;
+ mCacheExpirationTime = cacheExpirationTime;
+ mCachedCharset = cachedCharset;
+ mSelfAddr = selfAddr;
+ mPeerAddr = peerAddr;
+
+ mAvailableCachedAltDataType = altDataType;
+
+ mAfterOnStartRequestBegun = true;
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ nsresult rv;
+ nsCOMPtr<nsISupportsPRUint32> container =
+ do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+
+ rv = container->SetData(cacheKey);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+ mCacheKey = container;
+
+ // replace our request headers with what actually got sent in the parent
+ mRequestHead.SetHeaders(requestHeaders);
+
+ // Note: this is where we would notify "http-on-examine-response" observers.
+ // We have deliberately disabled this for child processes (see bug 806753)
+ //
+ // gHttpHandler->OnExamineResponse(this);
+
+ mTracingEnabled = false;
+
+ DoOnStartRequest(this, mListenerContext);
+}
+
+namespace {
+
+class SyntheticDiversionListener final : public nsIStreamListener
+{
+ RefPtr<HttpChannelChild> mChannel;
+
+ ~SyntheticDiversionListener()
+ {
+ }
+
+public:
+ explicit SyntheticDiversionListener(HttpChannelChild* aChannel)
+ : mChannel(aChannel)
+ {
+ MOZ_ASSERT(mChannel);
+ }
+
+ NS_IMETHOD
+ OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) override
+ {
+ MOZ_ASSERT_UNREACHABLE("SyntheticDiversionListener should never see OnStartRequest");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
+ nsresult aStatus) override
+ {
+ mChannel->SendDivertOnStopRequest(aStatus);
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
+ nsIInputStream* aInputStream, uint64_t aOffset,
+ uint32_t aCount) override
+ {
+ nsAutoCString data;
+ nsresult rv = NS_ConsumeStream(aInputStream, aCount, data);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->Cancel(rv);
+ return rv;
+ }
+
+ mChannel->SendDivertOnDataAvailable(data, aOffset, aCount);
+ return NS_OK;
+ }
+
+ NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(SyntheticDiversionListener, nsIStreamListener);
+
+} // anonymous namespace
+
+void
+HttpChannelChild::DoOnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+ LOG(("HttpChannelChild::DoOnStartRequest [this=%p]\n", this));
+
+ // In theory mListener should not be null, but in practice sometimes it is.
+ MOZ_ASSERT(mListener);
+ if (!mListener) {
+ Cancel(NS_ERROR_FAILURE);
+ return;
+ }
+ nsresult rv = mListener->OnStartRequest(aRequest, aContext);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+
+ if (mDivertingToParent) {
+ mListener = nullptr;
+ mListenerContext = nullptr;
+ mCompressListener = nullptr;
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ }
+
+ // If the response has been synthesized in the child, then we are going
+ // be getting OnDataAvailable and OnStopRequest from the synthetic
+ // stream pump. We need to forward these back to the parent diversion
+ // listener.
+ if (mSynthesizedResponse) {
+ mListener = new SyntheticDiversionListener(this);
+ }
+
+ return;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = DoApplyContentConversions(mListener, getter_AddRefs(listener),
+ mListenerContext);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ } else if (listener) {
+ mListener = listener;
+ mCompressListener = listener;
+ }
+}
+
+class TransportAndDataEvent : public ChannelEvent
+{
+ public:
+ TransportAndDataEvent(HttpChannelChild* child,
+ const nsresult& channelStatus,
+ const nsresult& transportStatus,
+ const uint64_t& progress,
+ const uint64_t& progressMax,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+ : mChild(child)
+ , mChannelStatus(channelStatus)
+ , mTransportStatus(transportStatus)
+ , mProgress(progress)
+ , mProgressMax(progressMax)
+ , mData(data)
+ , mOffset(offset)
+ , mCount(count) {}
+
+ void Run()
+ {
+ mChild->OnTransportAndData(mChannelStatus, mTransportStatus, mProgress,
+ mProgressMax, mOffset, mCount, mData);
+ }
+ private:
+ HttpChannelChild* mChild;
+ nsresult mChannelStatus;
+ nsresult mTransportStatus;
+ uint64_t mProgress;
+ uint64_t mProgressMax;
+ nsCString mData;
+ uint64_t mOffset;
+ uint32_t mCount;
+};
+
+bool
+HttpChannelChild::RecvOnTransportAndData(const nsresult& channelStatus,
+ const nsresult& transportStatus,
+ const uint64_t& progress,
+ const uint64_t& progressMax,
+ const uint64_t& offset,
+ const uint32_t& count,
+ const nsCString& data)
+{
+ LOG(("HttpChannelChild::RecvOnTransportAndData [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be receiving any more callbacks from parent!");
+
+ mEventQ->RunOrEnqueue(new TransportAndDataEvent(this, channelStatus,
+ transportStatus, progress,
+ progressMax, data, offset,
+ count),
+ mDivertingToParent);
+ return true;
+}
+
+class MaybeDivertOnDataHttpEvent : public ChannelEvent
+{
+ public:
+ MaybeDivertOnDataHttpEvent(HttpChannelChild* child,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+ : mChild(child)
+ , mData(data)
+ , mOffset(offset)
+ , mCount(count) {}
+
+ void Run()
+ {
+ mChild->MaybeDivertOnData(mData, mOffset, mCount);
+ }
+
+ private:
+ HttpChannelChild* mChild;
+ nsCString mData;
+ uint64_t mOffset;
+ uint32_t mCount;
+};
+
+void
+HttpChannelChild::MaybeDivertOnData(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ LOG(("HttpChannelChild::MaybeDivertOnData [this=%p]", this));
+
+ if (mDivertingToParent) {
+ SendDivertOnDataAvailable(data, offset, count);
+ }
+}
+
+void
+HttpChannelChild::OnTransportAndData(const nsresult& channelStatus,
+ const nsresult& transportStatus,
+ const uint64_t progress,
+ const uint64_t& progressMax,
+ const uint64_t& offset,
+ const uint32_t& count,
+ const nsCString& data)
+{
+ LOG(("HttpChannelChild::OnTransportAndData [this=%p]\n", this));
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = channelStatus;
+ }
+
+ // For diversion to parent, just SendDivertOnDataAvailable.
+ if (mDivertingToParent) {
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be processing any more callbacks from parent!");
+
+ SendDivertOnDataAvailable(data, offset, count);
+ return;
+ }
+
+ if (mCanceled)
+ return;
+
+ if (mUnknownDecoderInvolved) {
+ LOG(("UnknownDecoder is involved queue OnDataAvailable call. [this=%p]",
+ this));
+ mUnknownDecoderEventQ.AppendElement(
+ MakeUnique<MaybeDivertOnDataHttpEvent>(this, data, offset, count));
+ }
+
+ // Hold queue lock throughout all three calls, else we might process a later
+ // necko msg in between them.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ DoOnStatus(this, transportStatus);
+ DoOnProgress(this, progress, progressMax);
+
+ // OnDataAvailable
+ //
+ // NOTE: the OnDataAvailable contract requires the client to read all the data
+ // in the inputstream. This code relies on that ('data' will go away after
+ // this function). Apparently the previous, non-e10s behavior was to actually
+ // support only reading part of the data, allowing later calls to read the
+ // rest.
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(),
+ count, NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+
+ DoOnDataAvailable(this, mListenerContext, stringStream, offset, count);
+ stringStream->Close();
+}
+
+void
+HttpChannelChild::DoOnStatus(nsIRequest* aRequest, nsresult status)
+{
+ LOG(("HttpChannelChild::DoOnStatus [this=%p]\n", this));
+ if (mCanceled)
+ return;
+
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink)
+ GetCallback(mProgressSink);
+
+ // Temporary fix for bug 1116124
+ // See 1124971 - Child removes LOAD_BACKGROUND flag from channel
+ if (status == NS_OK)
+ return;
+
+ // block status/progress after Cancel or OnStopRequest has been called,
+ // or if channel has LOAD_BACKGROUND set.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending &&
+ !(mLoadFlags & LOAD_BACKGROUND))
+ {
+ // OnStatus
+ //
+ MOZ_ASSERT(status == NS_NET_STATUS_RECEIVING_FROM ||
+ status == NS_NET_STATUS_READING);
+
+ nsAutoCString host;
+ mURI->GetHost(host);
+ mProgressSink->OnStatus(aRequest, nullptr, status,
+ NS_ConvertUTF8toUTF16(host).get());
+ }
+}
+
+void
+HttpChannelChild::DoOnProgress(nsIRequest* aRequest, int64_t progress, int64_t progressMax)
+{
+ LOG(("HttpChannelChild::DoOnProgress [this=%p]\n", this));
+ if (mCanceled)
+ return;
+
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink)
+ GetCallback(mProgressSink);
+
+ // block status/progress after Cancel or OnStopRequest has been called,
+ // or if channel has LOAD_BACKGROUND set.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending &&
+ !(mLoadFlags & LOAD_BACKGROUND))
+ {
+ // OnProgress
+ //
+ if (progress > 0) {
+ mProgressSink->OnProgress(aRequest, nullptr, progress, progressMax);
+ }
+ }
+}
+
+void
+HttpChannelChild::DoOnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t offset, uint32_t count)
+{
+ LOG(("HttpChannelChild::DoOnDataAvailable [this=%p]\n", this));
+ if (mCanceled)
+ return;
+
+ nsresult rv = mListener->OnDataAvailable(aRequest, aContext, aStream, offset, count);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+}
+
+class StopRequestEvent : public ChannelEvent
+{
+ public:
+ StopRequestEvent(HttpChannelChild* child,
+ const nsresult& channelStatus,
+ const ResourceTimingStruct& timing)
+ : mChild(child)
+ , mChannelStatus(channelStatus)
+ , mTiming(timing) {}
+
+ void Run() { mChild->OnStopRequest(mChannelStatus, mTiming); }
+ private:
+ HttpChannelChild* mChild;
+ nsresult mChannelStatus;
+ ResourceTimingStruct mTiming;
+};
+
+bool
+HttpChannelChild::RecvOnStopRequest(const nsresult& channelStatus,
+ const ResourceTimingStruct& timing)
+{
+ LOG(("HttpChannelChild::RecvOnStopRequest [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be receiving any more callbacks from parent!");
+
+ mEventQ->RunOrEnqueue(new StopRequestEvent(this, channelStatus, timing),
+ mDivertingToParent);
+ return true;
+}
+
+class MaybeDivertOnStopHttpEvent : public ChannelEvent
+{
+ public:
+ MaybeDivertOnStopHttpEvent(HttpChannelChild* child,
+ const nsresult& channelStatus)
+ : mChild(child)
+ , mChannelStatus(channelStatus)
+ {}
+
+ void Run()
+ {
+ mChild->MaybeDivertOnStop(mChannelStatus);
+ }
+
+ private:
+ HttpChannelChild* mChild;
+ nsresult mChannelStatus;
+};
+
+void
+HttpChannelChild::MaybeDivertOnStop(const nsresult& aChannelStatus)
+{
+ LOG(("HttpChannelChild::MaybeDivertOnStop [this=%p, "
+ "mDivertingToParent=%d status=%x]", this, mDivertingToParent,
+ aChannelStatus));
+ if (mDivertingToParent) {
+ SendDivertOnStopRequest(aChannelStatus);
+ }
+}
+
+void
+HttpChannelChild::OnStopRequest(const nsresult& channelStatus,
+ const ResourceTimingStruct& timing)
+{
+ LOG(("HttpChannelChild::OnStopRequest [this=%p status=%x]\n",
+ this, channelStatus));
+
+ if (mDivertingToParent) {
+ MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+ "Should not be processing any more callbacks from parent!");
+
+ SendDivertOnStopRequest(channelStatus);
+ return;
+ }
+
+ if (mUnknownDecoderInvolved) {
+ LOG(("UnknownDecoder is involved queue OnStopRequest call. [this=%p]",
+ this));
+ mUnknownDecoderEventQ.AppendElement(
+ MakeUnique<MaybeDivertOnStopHttpEvent>(this, channelStatus));
+ }
+
+ nsCOMPtr<nsICompressConvStats> conv = do_QueryInterface(mCompressListener);
+ if (conv) {
+ conv->GetDecodedDataLength(&mDecodedBodySize);
+ }
+
+ mTransactionTimings.domainLookupStart = timing.domainLookupStart;
+ mTransactionTimings.domainLookupEnd = timing.domainLookupEnd;
+ mTransactionTimings.connectStart = timing.connectStart;
+ mTransactionTimings.connectEnd = timing.connectEnd;
+ mTransactionTimings.requestStart = timing.requestStart;
+ mTransactionTimings.responseStart = timing.responseStart;
+ mTransactionTimings.responseEnd = timing.responseEnd;
+ mAsyncOpenTime = timing.fetchStart;
+ mRedirectStartTimeStamp = timing.redirectStart;
+ mRedirectEndTimeStamp = timing.redirectEnd;
+ mTransferSize = timing.transferSize;
+ mEncodedBodySize = timing.encodedBodySize;
+ mProtocolVersion = timing.protocolVersion;
+
+ mCacheReadStart = timing.cacheReadStart;
+ mCacheReadEnd = timing.cacheReadEnd;
+
+ Performance* documentPerformance = GetPerformance();
+ if (documentPerformance) {
+ documentPerformance->AddEntry(this, this);
+ }
+
+ DoPreOnStopRequest(channelStatus);
+
+ { // We must flush the queue before we Send__delete__
+ // (although we really shouldn't receive any msgs after OnStop),
+ // so make sure this goes out of scope before then.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ DoOnStopRequest(this, channelStatus, mListenerContext);
+ }
+
+ ReleaseListeners();
+
+ // DocumentChannelCleanup actually nulls out mCacheEntry in the parent, which
+ // we might need later to open the Alt-Data output stream, so just return here
+ if (!mPreferredCachedAltDataType.IsEmpty()) {
+ mKeptAlive = true;
+ return;
+ }
+
+ if (mLoadFlags & LOAD_DOCUMENT_URI) {
+ // Keep IPDL channel open, but only for updating security info.
+ mKeptAlive = true;
+ SendDocumentChannelCleanup();
+ } else {
+ // The parent process will respond by sending a DeleteSelf message and
+ // making sure not to send any more messages after that.
+ SendDeletingChannel();
+ }
+}
+
+void
+HttpChannelChild::DoPreOnStopRequest(nsresult aStatus)
+{
+ LOG(("HttpChannelChild::DoPreOnStopRequest [this=%p status=%x]\n",
+ this, aStatus));
+ mIsPending = false;
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+}
+
+void
+HttpChannelChild::DoOnStopRequest(nsIRequest* aRequest, nsresult aChannelStatus, nsISupports* aContext)
+{
+ LOG(("HttpChannelChild::DoOnStopRequest [this=%p]\n", this));
+ MOZ_ASSERT(!mIsPending);
+
+ // NB: We use aChannelStatus here instead of mStatus because if there was an
+ // nsCORSListenerProxy on this request, it will override the tracking
+ // protection's return value.
+ if (aChannelStatus == NS_ERROR_TRACKING_URI) {
+ nsChannelClassifier::SetBlockedTrackingContent(this);
+ }
+
+ MOZ_ASSERT(!mOnStopRequestCalled,
+ "We should not call OnStopRequest twice");
+
+ // In theory mListener should not be null, but in practice sometimes it is.
+ MOZ_ASSERT(mListener);
+ if (mListener) {
+ mListener->OnStopRequest(aRequest, aContext, mStatus);
+ }
+ mOnStopRequestCalled = true;
+
+ mListener = nullptr;
+ mListenerContext = nullptr;
+ mCacheEntryAvailable = false;
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+}
+
+class ProgressEvent : public ChannelEvent
+{
+ public:
+ ProgressEvent(HttpChannelChild* child,
+ const int64_t& progress,
+ const int64_t& progressMax)
+ : mChild(child)
+ , mProgress(progress)
+ , mProgressMax(progressMax) {}
+
+ void Run() { mChild->OnProgress(mProgress, mProgressMax); }
+ private:
+ HttpChannelChild* mChild;
+ int64_t mProgress, mProgressMax;
+};
+
+bool
+HttpChannelChild::RecvOnProgress(const int64_t& progress,
+ const int64_t& progressMax)
+{
+ mEventQ->RunOrEnqueue(new ProgressEvent(this, progress, progressMax));
+ return true;
+}
+
+void
+HttpChannelChild::OnProgress(const int64_t& progress,
+ const int64_t& progressMax)
+{
+ LOG(("HttpChannelChild::OnProgress [this=%p progress=%lld/%lld]\n",
+ this, progress, progressMax));
+
+ if (mCanceled)
+ return;
+
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink) {
+ GetCallback(mProgressSink);
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ // Block socket status event after Cancel or OnStopRequest has been called.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending)
+ {
+ if (progress > 0) {
+ mProgressSink->OnProgress(this, nullptr, progress, progressMax);
+ }
+ }
+}
+
+class StatusEvent : public ChannelEvent
+{
+ public:
+ StatusEvent(HttpChannelChild* child,
+ const nsresult& status)
+ : mChild(child)
+ , mStatus(status) {}
+
+ void Run() { mChild->OnStatus(mStatus); }
+ private:
+ HttpChannelChild* mChild;
+ nsresult mStatus;
+};
+
+bool
+HttpChannelChild::RecvOnStatus(const nsresult& status)
+{
+ mEventQ->RunOrEnqueue(new StatusEvent(this, status));
+ return true;
+}
+
+void
+HttpChannelChild::OnStatus(const nsresult& status)
+{
+ LOG(("HttpChannelChild::OnStatus [this=%p status=%x]\n", this, status));
+
+ if (mCanceled)
+ return;
+
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink)
+ GetCallback(mProgressSink);
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ // block socket status event after Cancel or OnStopRequest has been called,
+ // or if channel has LOAD_BACKGROUND set
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending &&
+ !(mLoadFlags & LOAD_BACKGROUND))
+ {
+ nsAutoCString host;
+ mURI->GetHost(host);
+ mProgressSink->OnStatus(this, nullptr, status,
+ NS_ConvertUTF8toUTF16(host).get());
+ }
+}
+
+class FailedAsyncOpenEvent : public ChannelEvent
+{
+ public:
+ FailedAsyncOpenEvent(HttpChannelChild* child, const nsresult& status)
+ : mChild(child)
+ , mStatus(status) {}
+
+ void Run() { mChild->FailedAsyncOpen(mStatus); }
+ private:
+ HttpChannelChild* mChild;
+ nsresult mStatus;
+};
+
+bool
+HttpChannelChild::RecvFailedAsyncOpen(const nsresult& status)
+{
+ LOG(("HttpChannelChild::RecvFailedAsyncOpen [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new FailedAsyncOpenEvent(this, status));
+ return true;
+}
+
+// We need to have an implementation of this function just so that we can keep
+// all references to mCallOnResume of type HttpChannelChild: it's not OK in C++
+// to set a member function ptr to a base class function.
+void
+HttpChannelChild::HandleAsyncAbort()
+{
+ HttpAsyncAborter<HttpChannelChild>::HandleAsyncAbort();
+}
+
+void
+HttpChannelChild::FailedAsyncOpen(const nsresult& status)
+{
+ LOG(("HttpChannelChild::FailedAsyncOpen [this=%p status=%x]\n", this, status));
+
+ mStatus = status;
+
+ // We're already being called from IPDL, therefore already "async"
+ HandleAsyncAbort();
+
+ if (mIPCOpen) {
+ SendDeletingChannel();
+ }
+}
+
+void
+HttpChannelChild::DoNotifyListenerCleanup()
+{
+ LOG(("HttpChannelChild::DoNotifyListenerCleanup [this=%p]\n", this));
+
+ if (mInterceptListener) {
+ mInterceptListener->Cleanup();
+ mInterceptListener = nullptr;
+ }
+}
+
+class DeleteSelfEvent : public ChannelEvent
+{
+ public:
+ explicit DeleteSelfEvent(HttpChannelChild* child) : mChild(child) {}
+ void Run() { mChild->DeleteSelf(); }
+ private:
+ HttpChannelChild* mChild;
+};
+
+bool
+HttpChannelChild::RecvDeleteSelf()
+{
+ LOG(("HttpChannelChild::RecvDeleteSelf [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new DeleteSelfEvent(this));
+ return true;
+}
+
+HttpChannelChild::OverrideRunnable::OverrideRunnable(HttpChannelChild* aChannel,
+ HttpChannelChild* aNewChannel,
+ InterceptStreamListener* aListener,
+ nsIInputStream* aInput,
+ nsAutoPtr<nsHttpResponseHead>& aHead)
+{
+ mChannel = aChannel;
+ mNewChannel = aNewChannel;
+ mListener = aListener;
+ mInput = aInput;
+ mHead = aHead;
+}
+
+void
+HttpChannelChild::OverrideRunnable::OverrideWithSynthesizedResponse()
+{
+ if (mNewChannel) {
+ mNewChannel->OverrideWithSynthesizedResponse(mHead, mInput, mListener);
+ }
+}
+
+NS_IMETHODIMP
+HttpChannelChild::OverrideRunnable::Run()
+{
+ bool ret = mChannel->Redirect3Complete(this);
+
+ // If the method returns false, it means the IPDL connection is being
+ // asyncly torn down and reopened, and OverrideWithSynthesizedResponse
+ // will be called later from FinishInterceptedRedirect. This object will
+ // be assigned to HttpChannelChild::mOverrideRunnable in order to do so.
+ // If it is true, we can call the method right now.
+ if (ret) {
+ OverrideWithSynthesizedResponse();
+ }
+
+ return NS_OK;
+}
+
+bool
+HttpChannelChild::RecvFinishInterceptedRedirect()
+{
+ // Hold a ref to this to keep it from being deleted by Send__delete__()
+ RefPtr<HttpChannelChild> self(this);
+ Send__delete__(this);
+
+ // The IPDL connection was torn down by a interception logic in
+ // CompleteRedirectSetup, and we need to call FinishInterceptedRedirect.
+ NS_DispatchToMainThread(NewRunnableMethod(this, &HttpChannelChild::FinishInterceptedRedirect));
+
+ return true;
+}
+
+void
+HttpChannelChild::DeleteSelf()
+{
+ Send__delete__(this);
+}
+
+void HttpChannelChild::FinishInterceptedRedirect()
+{
+ nsresult rv;
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!mInterceptedRedirectContext, "the context should be null!");
+ rv = AsyncOpen2(mInterceptedRedirectListener);
+ } else {
+ rv = AsyncOpen(mInterceptedRedirectListener, mInterceptedRedirectContext);
+ }
+ mInterceptedRedirectListener = nullptr;
+ mInterceptedRedirectContext = nullptr;
+
+ if (mInterceptingChannel) {
+ mInterceptingChannel->CleanupRedirectingChannel(rv);
+ mInterceptingChannel = nullptr;
+ }
+
+ if (mOverrideRunnable) {
+ mOverrideRunnable->OverrideWithSynthesizedResponse();
+ mOverrideRunnable = nullptr;
+ }
+}
+
+bool
+HttpChannelChild::RecvReportSecurityMessage(const nsString& messageTag,
+ const nsString& messageCategory)
+{
+ AddSecurityMessage(messageTag, messageCategory);
+ return true;
+}
+
+class Redirect1Event : public ChannelEvent
+{
+ public:
+ Redirect1Event(HttpChannelChild* child,
+ const uint32_t& registrarId,
+ const URIParams& newURI,
+ const uint32_t& redirectFlags,
+ const nsHttpResponseHead& responseHead,
+ const nsACString& securityInfoSerialization,
+ const nsACString& channelId)
+ : mChild(child)
+ , mRegistrarId(registrarId)
+ , mNewURI(newURI)
+ , mRedirectFlags(redirectFlags)
+ , mResponseHead(responseHead)
+ , mSecurityInfoSerialization(securityInfoSerialization)
+ , mChannelId(channelId) {}
+
+ void Run()
+ {
+ mChild->Redirect1Begin(mRegistrarId, mNewURI, mRedirectFlags,
+ mResponseHead, mSecurityInfoSerialization,
+ mChannelId);
+ }
+ private:
+ HttpChannelChild* mChild;
+ uint32_t mRegistrarId;
+ URIParams mNewURI;
+ uint32_t mRedirectFlags;
+ nsHttpResponseHead mResponseHead;
+ nsCString mSecurityInfoSerialization;
+ nsCString mChannelId;
+};
+
+bool
+HttpChannelChild::RecvRedirect1Begin(const uint32_t& registrarId,
+ const URIParams& newUri,
+ const uint32_t& redirectFlags,
+ const nsHttpResponseHead& responseHead,
+ const nsCString& securityInfoSerialization,
+ const nsCString& channelId)
+{
+ // TODO: handle security info
+ LOG(("HttpChannelChild::RecvRedirect1Begin [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new Redirect1Event(this, registrarId, newUri,
+ redirectFlags, responseHead,
+ securityInfoSerialization,
+ channelId));
+ return true;
+}
+
+nsresult
+HttpChannelChild::SetupRedirect(nsIURI* uri,
+ const nsHttpResponseHead* responseHead,
+ const uint32_t& redirectFlags,
+ nsIChannel** outChannel)
+{
+ LOG(("HttpChannelChild::SetupRedirect [this=%p]\n", this));
+
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel),
+ uri,
+ mLoadInfo,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL,
+ ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We won't get OnStartRequest, set cookies here.
+ mResponseHead = new nsHttpResponseHead(*responseHead);
+
+ bool rewriteToGET = HttpBaseChannel::ShouldRewriteRedirectToGET(mResponseHead->Status(),
+ mRequestHead.ParsedMethod());
+
+ rv = SetupReplacementChannel(uri, newChannel, !rewriteToGET, redirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannelChild> httpChannelChild = do_QueryInterface(newChannel);
+ if (httpChannelChild) {
+ bool shouldUpgrade = false;
+ auto channelChild = static_cast<HttpChannelChild*>(httpChannelChild.get());
+ if (mShouldInterceptSubsequentRedirect) {
+ // In the case where there was a synthesized response that caused a redirection,
+ // we must force the new channel to intercept the request in the parent before a
+ // network transaction is initiated.
+ httpChannelChild->ForceIntercepted(false, false);
+ } else if (mRedirectMode == nsIHttpChannelInternal::REDIRECT_MODE_MANUAL &&
+ ((redirectFlags & (nsIChannelEventSink::REDIRECT_TEMPORARY |
+ nsIChannelEventSink::REDIRECT_PERMANENT)) != 0) &&
+ channelChild->ShouldInterceptURI(uri, shouldUpgrade)) {
+ // In the case where the redirect mode is manual, we need to check whether
+ // the post-redirect channel needs to be intercepted. If that is the
+ // case, force the new channel to intercept the request in the parent
+ // similar to the case above, but also remember that ShouldInterceptURI()
+ // returned true to avoid calling it a second time.
+ httpChannelChild->ForceIntercepted(true, shouldUpgrade);
+ }
+ }
+
+ mRedirectChannelChild = do_QueryInterface(newChannel);
+ newChannel.forget(outChannel);
+
+ return NS_OK;
+}
+
+void
+HttpChannelChild::Redirect1Begin(const uint32_t& registrarId,
+ const URIParams& newUri,
+ const uint32_t& redirectFlags,
+ const nsHttpResponseHead& responseHead,
+ const nsACString& securityInfoSerialization,
+ const nsACString& channelId)
+{
+ LOG(("HttpChannelChild::Redirect1Begin [this=%p]\n", this));
+
+ nsCOMPtr<nsIURI> uri = DeserializeURI(newUri);
+
+ if (!securityInfoSerialization.IsEmpty()) {
+ NS_DeserializeObject(securityInfoSerialization,
+ getter_AddRefs(mSecurityInfo));
+ }
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsresult rv = SetupRedirect(uri,
+ &responseHead,
+ redirectFlags,
+ getter_AddRefs(newChannel));
+
+ if (NS_SUCCEEDED(rv)) {
+ if (mRedirectChannelChild) {
+ // Set the channelId allocated in parent to the child instance
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mRedirectChannelChild);
+ if (httpChannel) {
+ httpChannel->SetChannelId(channelId);
+ }
+ mRedirectChannelChild->ConnectParent(registrarId);
+ }
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
+ }
+
+ if (NS_FAILED(rv))
+ OnRedirectVerifyCallback(rv);
+}
+
+void
+HttpChannelChild::BeginNonIPCRedirect(nsIURI* responseURI,
+ const nsHttpResponseHead* responseHead)
+{
+ LOG(("HttpChannelChild::BeginNonIPCRedirect [this=%p]\n", this));
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsresult rv = SetupRedirect(responseURI,
+ responseHead,
+ nsIChannelEventSink::REDIRECT_INTERNAL,
+ getter_AddRefs(newChannel));
+
+ if (NS_SUCCEEDED(rv)) {
+ // Ensure that the new channel shares the original channel's security information,
+ // since it won't be provided via IPC. In particular, if the target of this redirect
+ // is a synthesized response that has its own security info, the pre-redirect channel
+ // has already received it and it must be propagated to the post-redirect channel.
+ nsCOMPtr<nsIHttpChannelChild> channelChild = do_QueryInterface(newChannel);
+ if (mSecurityInfo && channelChild) {
+ HttpChannelChild* httpChannelChild = static_cast<HttpChannelChild*>(channelChild.get());
+ httpChannelChild->OverrideSecurityInfoForNonIPCRedirect(mSecurityInfo);
+ }
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this,
+ newChannel,
+ nsIChannelEventSink::REDIRECT_INTERNAL);
+ }
+
+ if (NS_FAILED(rv))
+ OnRedirectVerifyCallback(rv);
+}
+
+void
+HttpChannelChild::OverrideSecurityInfoForNonIPCRedirect(nsISupports* securityInfo)
+{
+ mResponseCouldBeSynthesized = true;
+ OverrideSecurityInfo(securityInfo);
+}
+
+class Redirect3Event : public ChannelEvent
+{
+ public:
+ explicit Redirect3Event(HttpChannelChild* child) : mChild(child) {}
+ void Run() { mChild->Redirect3Complete(nullptr); }
+ private:
+ HttpChannelChild* mChild;
+};
+
+bool
+HttpChannelChild::RecvRedirect3Complete()
+{
+ LOG(("HttpChannelChild::RecvRedirect3Complete [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new Redirect3Event(this));
+ return true;
+}
+
+class HttpFlushedForDiversionEvent : public ChannelEvent
+{
+ public:
+ explicit HttpFlushedForDiversionEvent(HttpChannelChild* aChild)
+ : mChild(aChild)
+ {
+ MOZ_RELEASE_ASSERT(aChild);
+ }
+
+ void Run()
+ {
+ mChild->FlushedForDiversion();
+ }
+ private:
+ HttpChannelChild* mChild;
+};
+
+bool
+HttpChannelChild::RecvFlushedForDiversion()
+{
+ LOG(("HttpChannelChild::RecvFlushedForDiversion [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(mDivertingToParent);
+
+ mEventQ->RunOrEnqueue(new HttpFlushedForDiversionEvent(this), true);
+
+ return true;
+}
+
+bool
+HttpChannelChild::RecvNotifyTrackingProtectionDisabled()
+{
+ nsChannelClassifier::NotifyTrackingProtectionDisabled(this);
+ return true;
+}
+
+void
+HttpChannelChild::FlushedForDiversion()
+{
+ LOG(("HttpChannelChild::FlushedForDiversion [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(mDivertingToParent);
+
+ // Once this is set, it should not be unset before HttpChannelChild is taken
+ // down. After it is set, no OnStart/OnData/OnStop callbacks should be
+ // received from the parent channel, nor dequeued from the ChannelEventQueue.
+ mFlushedForDiversion = true;
+
+ SendDivertComplete();
+}
+
+bool
+HttpChannelChild::RecvDivertMessages()
+{
+ LOG(("HttpChannelChild::RecvDivertMessages [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(mDivertingToParent);
+ MOZ_RELEASE_ASSERT(mSuspendCount > 0);
+
+ // DivertTo() has been called on parent, so we can now start sending queued
+ // IPDL messages back to parent listener.
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(Resume()));
+
+ return true;
+}
+
+// Returns true if has actually completed the redirect and cleaned up the
+// channel, or false the interception logic kicked in and we need to asyncly
+// call FinishInterceptedRedirect and CleanupRedirectingChannel.
+// The argument is an optional OverrideRunnable that we pass to the redirected
+// channel.
+bool
+HttpChannelChild::Redirect3Complete(OverrideRunnable* aRunnable)
+{
+ LOG(("HttpChannelChild::Redirect3Complete [this=%p]\n", this));
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIHttpChannelChild> chan = do_QueryInterface(mRedirectChannelChild);
+ RefPtr<HttpChannelChild> httpChannelChild = static_cast<HttpChannelChild*>(chan.get());
+ // Chrome channel has been AsyncOpen'd. Reflect this in child.
+ if (mRedirectChannelChild) {
+ if (httpChannelChild) {
+ httpChannelChild->mOverrideRunnable = aRunnable;
+ httpChannelChild->mInterceptingChannel = this;
+ }
+ rv = mRedirectChannelChild->CompleteRedirectSetup(mListener,
+ mListenerContext);
+ }
+
+ if (!httpChannelChild || !httpChannelChild->mShouldParentIntercept) {
+ // The redirect channel either isn't a HttpChannelChild, or the interception
+ // logic wasn't triggered, so we can clean it up right here.
+ CleanupRedirectingChannel(rv);
+ if (httpChannelChild) {
+ httpChannelChild->mOverrideRunnable = nullptr;
+ httpChannelChild->mInterceptingChannel = nullptr;
+ }
+ return true;
+ }
+ return false;
+}
+
+void
+HttpChannelChild::CleanupRedirectingChannel(nsresult rv)
+{
+ // Redirecting to new channel: shut this down and init new channel
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_ABORTED);
+
+ if (NS_SUCCEEDED(rv)) {
+ if (mLoadInfo) {
+ mLoadInfo->AppendRedirectedPrincipal(GetURIPrincipal(), false);
+ }
+ }
+ else {
+ NS_WARNING("CompleteRedirectSetup failed, HttpChannelChild already open?");
+ }
+
+ // Release ref to new channel.
+ mRedirectChannelChild = nullptr;
+
+ if (mInterceptListener) {
+ mInterceptListener->Cleanup();
+ mInterceptListener = nullptr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIChildChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::ConnectParent(uint32_t registrarId)
+{
+ LOG(("HttpChannelChild::ConnectParent [this=%p]\n", this));
+ mozilla::dom::TabChild* tabChild = nullptr;
+ nsCOMPtr<nsITabChild> iTabChild;
+ GetCallback(iTabChild);
+ if (iTabChild) {
+ tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
+ }
+ if (MissingRequiredTabChild(tabChild, "http")) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (tabChild && !tabChild->IPCOpen()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ // The socket transport in the chrome process now holds a logical ref to us
+ // until OnStopRequest, or we do a redirect, or we hit an IPDL error.
+ AddIPDLReference();
+
+ HttpChannelConnectArgs connectArgs(registrarId, mShouldParentIntercept);
+ PBrowserOrId browser = static_cast<ContentChild*>(gNeckoChild->Manager())
+ ->GetBrowserOrId(tabChild);
+ if (!gNeckoChild->
+ SendPHttpChannelConstructor(this, browser,
+ IPC::SerializedLoadContext(this),
+ connectArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::CompleteRedirectSetup(nsIStreamListener *listener,
+ nsISupports *aContext)
+{
+ LOG(("HttpChannelChild::FinishRedirectSetup [this=%p]\n", this));
+
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ if (mShouldParentIntercept) {
+ // This is a redirected channel, and the corresponding parent channel has started
+ // AsyncOpen but was intercepted and suspended. We must tear it down and start
+ // fresh - we will intercept the child channel this time, before creating a new
+ // parent channel unnecessarily.
+
+ // Since this method is called from RecvRedirect3Complete which itself is
+ // called from either OnRedirectVerifyCallback via OverrideRunnable, or from
+ // RecvRedirect3Complete. The order of events must always be:
+ // 1. Teardown the IPDL connection
+ // 2. AsyncOpen the connection again
+ // 3. Cleanup the redirecting channel (the one calling Redirect3Complete)
+ // 4. [optional] Call OverrideWithSynthesizedResponse on the redirected
+ // channel if the call came from OverrideRunnable.
+ mInterceptedRedirectListener = listener;
+ mInterceptedRedirectContext = aContext;
+
+ // This will send a message to the parent notifying it that we are closing
+ // down. After closing the IPC channel, we will proceed to execute
+ // FinishInterceptedRedirect() which AsyncOpen's the channel again.
+ SendFinishInterceptedRedirect();
+
+ // XXX valentin: The interception logic should be rewritten to avoid
+ // calling AsyncOpen on the channel _after_ we call Send__delete__()
+ return NS_OK;
+ }
+
+ /*
+ * No need to check for cancel: we don't get here if nsHttpChannel canceled
+ * before AsyncOpen(); if it's canceled after that, OnStart/Stop will just
+ * get called with error code as usual. So just setup mListener and make the
+ * channel reflect AsyncOpen'ed state.
+ */
+
+ mIsPending = true;
+ mWasOpened = true;
+ mListener = listener;
+ mListenerContext = aContext;
+
+ // add ourselves to the load group.
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ // We already have an open IPDL connection to the parent. If on-modify-request
+ // listeners or load group observers canceled us, let the parent handle it
+ // and send it back to us naturally.
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIAsyncVerifyRedirectCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::OnRedirectVerifyCallback(nsresult result)
+{
+ LOG(("HttpChannelChild::OnRedirectVerifyCallback [this=%p]\n", this));
+ nsresult rv;
+ OptionalURIParams redirectURI;
+ nsCOMPtr<nsIHttpChannel> newHttpChannel =
+ do_QueryInterface(mRedirectChannelChild);
+
+ if (NS_SUCCEEDED(result) && !mRedirectChannelChild) {
+ // mRedirectChannelChild doesn't exist means we're redirecting to a protocol
+ // that doesn't implement nsIChildChannel. The redirect result should be set
+ // as failed by veto listeners and shouldn't enter this condition. As the
+ // last resort, we synthesize the error result as NS_ERROR_DOM_BAD_URI here
+ // to let nsHttpChannel::ContinueProcessResponse2 know it's redirecting to
+ // another protocol and throw an error.
+ LOG((" redirecting to a protocol that doesn't implement nsIChildChannel"));
+ result = NS_ERROR_DOM_BAD_URI;
+ }
+
+ bool forceHSTSPriming = false;
+ bool mixedContentWouldBlock = false;
+ if (newHttpChannel) {
+ // Must not be called until after redirect observers called.
+ newHttpChannel->SetOriginalURI(mOriginalURI);
+
+ nsCOMPtr<nsILoadInfo> newLoadInfo;
+ rv = newHttpChannel->GetLoadInfo(getter_AddRefs(newLoadInfo));
+ if (NS_SUCCEEDED(rv) && newLoadInfo) {
+ forceHSTSPriming = newLoadInfo->GetForceHSTSPriming();
+ mixedContentWouldBlock = newLoadInfo->GetMixedContentWouldBlock();
+ }
+ }
+
+ if (mRedirectingForSubsequentSynthesizedResponse) {
+ nsCOMPtr<nsIHttpChannelChild> httpChannelChild = do_QueryInterface(mRedirectChannelChild);
+ RefPtr<HttpChannelChild> redirectedChannel =
+ static_cast<HttpChannelChild*>(httpChannelChild.get());
+ // redirectChannel will be NULL if mRedirectChannelChild isn't a
+ // nsIHttpChannelChild (it could be a DataChannelChild).
+
+ RefPtr<InterceptStreamListener> streamListener =
+ new InterceptStreamListener(redirectedChannel, mListenerContext);
+
+ NS_DispatchToMainThread(new OverrideRunnable(this, redirectedChannel,
+ streamListener, mSynthesizedInput,
+ mResponseHead));
+ return NS_OK;
+ }
+
+ RequestHeaderTuples emptyHeaders;
+ RequestHeaderTuples* headerTuples = &emptyHeaders;
+ nsLoadFlags loadFlags = 0;
+ OptionalCorsPreflightArgs corsPreflightArgs = mozilla::void_t();
+
+ nsCOMPtr<nsIHttpChannelChild> newHttpChannelChild =
+ do_QueryInterface(mRedirectChannelChild);
+ if (newHttpChannelChild && NS_SUCCEEDED(result)) {
+ newHttpChannelChild->AddCookiesToRequest();
+ newHttpChannelChild->GetClientSetRequestHeaders(&headerTuples);
+ newHttpChannelChild->GetClientSetCorsPreflightParameters(corsPreflightArgs);
+ }
+
+ /* If the redirect was canceled, bypass OMR and send an empty API
+ * redirect URI */
+ SerializeURI(nullptr, redirectURI);
+
+ if (NS_SUCCEEDED(result)) {
+ // Note: this is where we would notify "http-on-modify-response" observers.
+ // We have deliberately disabled this for child processes (see bug 806753)
+ //
+ // After we verify redirect, nsHttpChannel may hit the network: must give
+ // "http-on-modify-request" observers the chance to cancel before that.
+ //base->CallOnModifyRequestObservers();
+
+ nsCOMPtr<nsIHttpChannelInternal> newHttpChannelInternal =
+ do_QueryInterface(mRedirectChannelChild);
+ if (newHttpChannelInternal) {
+ nsCOMPtr<nsIURI> apiRedirectURI;
+ nsresult rv = newHttpChannelInternal->GetApiRedirectToURI(
+ getter_AddRefs(apiRedirectURI));
+ if (NS_SUCCEEDED(rv) && apiRedirectURI) {
+ /* If there was an API redirect of this channel, we need to send it
+ * up here, since it can't be sent via SendAsyncOpen. */
+ SerializeURI(apiRedirectURI, redirectURI);
+ }
+ }
+
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mRedirectChannelChild);
+ if (request) {
+ request->GetLoadFlags(&loadFlags);
+ }
+ }
+
+ bool chooseAppcache = false;
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(newHttpChannel);
+ if (appCacheChannel) {
+ appCacheChannel->GetChooseApplicationCache(&chooseAppcache);
+ }
+
+ if (mIPCOpen)
+ SendRedirect2Verify(result, *headerTuples, loadFlags, redirectURI,
+ corsPreflightArgs, forceHSTSPriming,
+ mixedContentWouldBlock, chooseAppcache);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::Cancel(nsresult status)
+{
+ LOG(("HttpChannelChild::Cancel [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mCanceled) {
+ // If this cancel occurs before nsHttpChannel has been set up, AsyncOpen
+ // is responsible for cleaning up.
+ mCanceled = true;
+ mStatus = status;
+ if (RemoteChannelExists())
+ SendCancel(status);
+ if (mSynthesizedResponsePump) {
+ mSynthesizedResponsePump->Cancel(status);
+ }
+ mInterceptListener = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Suspend()
+{
+ LOG(("HttpChannelChild::Suspend [this=%p, mSuspendCount=%lu, "
+ "mDivertingToParent=%d]\n", this, mSuspendCount+1, mDivertingToParent));
+ NS_ENSURE_TRUE(RemoteChannelExists() || mInterceptListener,
+ NS_ERROR_NOT_AVAILABLE);
+
+ // SendSuspend only once, when suspend goes from 0 to 1.
+ // Don't SendSuspend at all if we're diverting callbacks to the parent;
+ // suspend will be called at the correct time in the parent itself.
+ if (!mSuspendCount++ && !mDivertingToParent) {
+ if (RemoteChannelExists()) {
+ SendSuspend();
+ mSuspendSent = true;
+ }
+ }
+ if (mSynthesizedResponsePump) {
+ mSynthesizedResponsePump->Suspend();
+ }
+ mEventQ->Suspend();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Resume()
+{
+ LOG(("HttpChannelChild::Resume [this=%p, mSuspendCount=%lu, "
+ "mDivertingToParent=%d]\n", this, mSuspendCount-1, mDivertingToParent));
+ NS_ENSURE_TRUE(RemoteChannelExists() || mInterceptListener,
+ NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = NS_OK;
+
+ // SendResume only once, when suspend count drops to 0.
+ // Don't SendResume at all if we're diverting callbacks to the parent (unless
+ // suspend was sent earlier); otherwise, resume will be called at the correct
+ // time in the parent itself.
+ if (!--mSuspendCount && (!mDivertingToParent || mSuspendSent)) {
+ if (RemoteChannelExists()) {
+ SendResume();
+ }
+ if (mCallOnResume) {
+ AsyncCall(mCallOnResume);
+ mCallOnResume = nullptr;
+ }
+ }
+ if (mSynthesizedResponsePump) {
+ mSynthesizedResponsePump->Resume();
+ }
+ mEventQ->Resume();
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetSecurityInfo(nsISupports **aSecurityInfo)
+{
+ NS_ENSURE_ARG_POINTER(aSecurityInfo);
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ LOG(("HttpChannelChild::AsyncOpen [this=%p uri=%s]\n", this, mSpec.get()));
+
+#ifdef DEBUG
+ AssertPrivateBrowsingId();
+#endif
+
+ if (mCanceled)
+ return mStatus;
+
+ NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ mAsyncOpenTime = TimeStamp::Now();
+
+ // Port checked in parent, but duplicate here so we can return with error
+ // immediately
+ nsresult rv;
+ rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString cookie;
+ if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookie))) {
+ mUserSetCookieHeader = cookie;
+ }
+
+ AddCookiesToRequest();
+
+ //
+ // NOTE: From now on we must return NS_OK; all errors must be handled via
+ // OnStart/OnStopRequest
+ //
+
+ // We notify "http-on-opening-request" observers in the child
+ // process so that devtools can capture a stack trace at the
+ // appropriate spot. See bug 806753 for some information about why
+ // other http-* notifications are disabled in child processes.
+ gHttpHandler->OnOpeningRequest(this);
+
+ mIsPending = true;
+ mWasOpened = true;
+ mListener = listener;
+ mListenerContext = aContext;
+
+ // add ourselves to the load group.
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ if (mCanceled) {
+ // We may have been canceled already, either by on-modify-request
+ // listeners or by load group observers; in that case, don't create IPDL
+ // connection. See nsHttpChannel::AsyncOpen().
+ AsyncAbort(mStatus);
+ return NS_OK;
+ }
+
+ // Set user agent override from docshell
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ MOZ_ASSERT_IF(mPostRedirectChannelShouldUpgrade,
+ mPostRedirectChannelShouldIntercept);
+ bool shouldUpgrade = mPostRedirectChannelShouldUpgrade;
+ if (mPostRedirectChannelShouldIntercept ||
+ ShouldInterceptURI(mURI, shouldUpgrade)) {
+ mResponseCouldBeSynthesized = true;
+
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+
+ mInterceptListener = new InterceptStreamListener(this, mListenerContext);
+
+ RefPtr<InterceptedChannelContent> intercepted =
+ new InterceptedChannelContent(this, controller,
+ mInterceptListener, shouldUpgrade);
+ intercepted->NotifyController();
+ return NS_OK;
+ }
+
+ return ContinueAsyncOpen();
+}
+
+NS_IMETHODIMP
+HttpChannelChild::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+nsresult
+HttpChannelChild::ContinueAsyncOpen()
+{
+ nsCString appCacheClientId;
+ if (mInheritApplicationCache) {
+ // Pick up an application cache from the notification
+ // callbacks if available
+ nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
+ GetCallback(appCacheContainer);
+
+ if (appCacheContainer) {
+ nsCOMPtr<nsIApplicationCache> appCache;
+ nsresult rv = appCacheContainer->GetApplicationCache(getter_AddRefs(appCache));
+ if (NS_SUCCEEDED(rv) && appCache) {
+ appCache->GetClientID(appCacheClientId);
+ }
+ }
+ }
+
+ //
+ // Send request to the chrome process...
+ //
+
+ mozilla::dom::TabChild* tabChild = nullptr;
+ nsCOMPtr<nsITabChild> iTabChild;
+ GetCallback(iTabChild);
+ if (iTabChild) {
+ tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
+ }
+ if (MissingRequiredTabChild(tabChild, "http")) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // This id identifies the inner window's top-level document,
+ // which changes on every new load or navigation.
+ uint64_t contentWindowId = 0;
+ if (tabChild) {
+ MOZ_ASSERT(tabChild->WebNavigation());
+ nsCOMPtr<nsIDocument> document = tabChild->GetDocument();
+ if (document) {
+ contentWindowId = document->InnerWindowID();
+ }
+ }
+ SetTopLevelContentWindowId(contentWindowId);
+
+ HttpChannelOpenArgs openArgs;
+ // No access to HttpChannelOpenArgs members, but they each have a
+ // function with the struct name that returns a ref.
+ SerializeURI(mURI, openArgs.uri());
+ SerializeURI(mOriginalURI, openArgs.original());
+ SerializeURI(mDocumentURI, openArgs.doc());
+ SerializeURI(mReferrer, openArgs.referrer());
+ openArgs.referrerPolicy() = mReferrerPolicy;
+ SerializeURI(mAPIRedirectToURI, openArgs.apiRedirectTo());
+ openArgs.loadFlags() = mLoadFlags;
+ openArgs.requestHeaders() = mClientSetRequestHeaders;
+ mRequestHead.Method(openArgs.requestMethod());
+ openArgs.preferredAlternativeType() = mPreferredCachedAltDataType;
+
+ AutoIPCStream autoStream(openArgs.uploadStream());
+ if (mUploadStream) {
+ autoStream.Serialize(mUploadStream, ContentChild::GetSingleton());
+ autoStream.TakeOptionalValue();
+ }
+
+ if (mResponseHead) {
+ openArgs.synthesizedResponseHead() = *mResponseHead;
+ openArgs.suspendAfterSynthesizeResponse() =
+ mSuspendParentAfterSynthesizeResponse;
+ } else {
+ openArgs.synthesizedResponseHead() = mozilla::void_t();
+ openArgs.suspendAfterSynthesizeResponse() = false;
+ }
+
+ nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(mSecurityInfo);
+ if (secInfoSer) {
+ NS_SerializeToString(secInfoSer, openArgs.synthesizedSecurityInfoSerialization());
+ }
+
+ OptionalCorsPreflightArgs optionalCorsPreflightArgs;
+ GetClientSetCorsPreflightParameters(optionalCorsPreflightArgs);
+
+ // NB: This call forces us to cache mTopWindowURI if we haven't already.
+ nsCOMPtr<nsIURI> uri;
+ GetTopWindowURI(getter_AddRefs(uri));
+
+ SerializeURI(mTopWindowURI, openArgs.topWindowURI());
+
+ openArgs.preflightArgs() = optionalCorsPreflightArgs;
+
+ openArgs.uploadStreamHasHeaders() = mUploadStreamHasHeaders;
+ openArgs.priority() = mPriority;
+ openArgs.classOfService() = mClassOfService;
+ openArgs.redirectionLimit() = mRedirectionLimit;
+ openArgs.allowPipelining() = mAllowPipelining;
+ openArgs.allowSTS() = mAllowSTS;
+ openArgs.thirdPartyFlags() = mThirdPartyFlags;
+ openArgs.resumeAt() = mSendResumeAt;
+ openArgs.startPos() = mStartPos;
+ openArgs.entityID() = mEntityID;
+ openArgs.chooseApplicationCache() = mChooseApplicationCache;
+ openArgs.appCacheClientID() = appCacheClientId;
+ openArgs.allowSpdy() = mAllowSpdy;
+ openArgs.allowAltSvc() = mAllowAltSvc;
+ openArgs.beConservative() = mBeConservative;
+ openArgs.initialRwin() = mInitialRwin;
+
+ uint32_t cacheKey = 0;
+ if (mCacheKey) {
+ nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(mCacheKey);
+ if (!container) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult rv = container->GetData(&cacheKey);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ openArgs.cacheKey() = cacheKey;
+
+ openArgs.blockAuthPrompt() = mBlockAuthPrompt;
+
+ openArgs.allowStaleCacheContent() = mAllowStaleCacheContent;
+
+ openArgs.contentTypeHint() = mContentTypeHint;
+
+ nsresult rv = mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &openArgs.loadInfo());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ EnsureRequestContextID();
+ char rcid[NSID_LENGTH];
+ mRequestContextID.ToProvidedString(rcid);
+ openArgs.requestContextID().AssignASCII(rcid);
+
+ char chid[NSID_LENGTH];
+ mChannelId.ToProvidedString(chid);
+ openArgs.channelId().AssignASCII(chid);
+
+ openArgs.contentWindowId() = contentWindowId;
+
+ if (tabChild && !tabChild->IPCOpen()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // The socket transport in the chrome process now holds a logical ref to us
+ // until OnStopRequest, or we do a redirect, or we hit an IPDL error.
+ AddIPDLReference();
+
+ PBrowserOrId browser = cc->GetBrowserOrId(tabChild);
+ if (!gNeckoChild->SendPHttpChannelConstructor(this, browser,
+ IPC::SerializedLoadContext(this),
+ openArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue,
+ bool aMerge)
+{
+ LOG(("HttpChannelChild::SetRequestHeader [this=%p]\n", this));
+ nsresult rv = HttpBaseChannel::SetRequestHeader(aHeader, aValue, aMerge);
+ if (NS_FAILED(rv))
+ return rv;
+
+ RequestHeaderTuple* tuple = mClientSetRequestHeaders.AppendElement();
+ if (!tuple)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ tuple->mHeader = aHeader;
+ tuple->mValue = aValue;
+ tuple->mMerge = aMerge;
+ tuple->mEmpty = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetEmptyRequestHeader(const nsACString& aHeader)
+{
+ LOG(("HttpChannelChild::SetEmptyRequestHeader [this=%p]\n", this));
+ nsresult rv = HttpBaseChannel::SetEmptyRequestHeader(aHeader);
+ if (NS_FAILED(rv))
+ return rv;
+
+ RequestHeaderTuple* tuple = mClientSetRequestHeaders.AppendElement();
+ if (!tuple)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ tuple->mHeader = aHeader;
+ tuple->mMerge = false;
+ tuple->mEmpty = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::RedirectTo(nsIURI *newURI)
+{
+ // disabled until/unless addons run in child or something else needs this
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetProtocolVersion(nsACString& aProtocolVersion)
+{
+ aProtocolVersion = mProtocolVersion;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::SetupFallbackChannel(const char *aFallbackKey)
+{
+ DROP_DEAD();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsICacheInfoChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheTokenExpirationTime(uint32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ if (!mCacheEntryAvailable)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *_retval = mCacheExpirationTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheTokenCachedCharset(nsACString &_retval)
+{
+ if (!mCacheEntryAvailable)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ _retval = mCachedCharset;
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetCacheTokenCachedCharset(const nsACString &aCharset)
+{
+ if (!mCacheEntryAvailable || !RemoteChannelExists())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ mCachedCharset = aCharset;
+ if (!SendSetCacheTokenCachedCharset(PromiseFlatCString(aCharset))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::IsFromCache(bool *value)
+{
+ if (!mIsPending)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *value = mIsFromCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheKey(nsISupports **cacheKey)
+{
+ NS_IF_ADDREF(*cacheKey = mCacheKey);
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetCacheKey(nsISupports *cacheKey)
+{
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ mCacheKey = cacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetAllowStaleCacheContent(bool aAllowStaleCacheContent)
+{
+ mAllowStaleCacheContent = aAllowStaleCacheContent;
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::GetAllowStaleCacheContent(bool *aAllowStaleCacheContent)
+{
+ NS_ENSURE_ARG(aAllowStaleCacheContent);
+ *aAllowStaleCacheContent = mAllowStaleCacheContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::PreferAlternativeDataType(const nsACString & aType)
+{
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+ mPreferredCachedAltDataType = aType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetAlternativeDataType(nsACString & aType)
+{
+ // Must be called during or after OnStartRequest
+ if (!mAfterOnStartRequestBegun) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aType = mAvailableCachedAltDataType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::OpenAlternativeOutputStream(const nsACString & aType, nsIOutputStream * *_retval)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+ RefPtr<AltDataOutputStreamChild> stream =
+ static_cast<AltDataOutputStreamChild*>(gNeckoChild->SendPAltDataOutputStreamConstructor(nsCString(aType), this));
+ stream.forget(_retval);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIResumableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::ResumeAt(uint64_t startPos, const nsACString& entityID)
+{
+ LOG(("HttpChannelChild::ResumeAt [this=%p]\n", this));
+ ENSURE_CALLED_BEFORE_CONNECT();
+ mStartPos = startPos;
+ mEntityID = entityID;
+ mSendResumeAt = true;
+ return NS_OK;
+}
+
+// GetEntityID is shared in HttpBaseChannel
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsISupportsPriority
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::SetPriority(int32_t aPriority)
+{
+ int16_t newValue = clamped<int32_t>(aPriority, INT16_MIN, INT16_MAX);
+ if (mPriority == newValue)
+ return NS_OK;
+ mPriority = newValue;
+ if (RemoteChannelExists())
+ SendSetPriority(mPriority);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIClassOfService
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+HttpChannelChild::SetClassFlags(uint32_t inFlags)
+{
+ if (mClassOfService == inFlags) {
+ return NS_OK;
+ }
+
+ mClassOfService = inFlags;
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::AddClassFlags(uint32_t inFlags)
+{
+ mClassOfService |= inFlags;
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::ClearClassFlags(uint32_t inFlags)
+{
+ mClassOfService &= ~inFlags;
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIProxiedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetProxyInfo(nsIProxyInfo **aProxyInfo)
+{
+ DROP_DEAD();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIApplicationCacheContainer
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetApplicationCache(nsIApplicationCache **aApplicationCache)
+{
+ NS_IF_ADDREF(*aApplicationCache = mApplicationCache);
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetApplicationCache(nsIApplicationCache *aApplicationCache)
+{
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ mApplicationCache = aApplicationCache;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIApplicationCacheChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetApplicationCacheForWrite(nsIApplicationCache **aApplicationCache)
+{
+ *aApplicationCache = nullptr;
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetApplicationCacheForWrite(nsIApplicationCache *aApplicationCache)
+{
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ // Child channels are not intended to be used for cache writes
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetLoadedFromApplicationCache(bool *aLoadedFromApplicationCache)
+{
+ *aLoadedFromApplicationCache = mLoadedFromApplicationCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetInheritApplicationCache(bool *aInherit)
+{
+ *aInherit = mInheritApplicationCache;
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetInheritApplicationCache(bool aInherit)
+{
+ mInheritApplicationCache = aInherit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetChooseApplicationCache(bool *aChoose)
+{
+ *aChoose = mChooseApplicationCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetChooseApplicationCache(bool aChoose)
+{
+ mChooseApplicationCache = aChoose;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::MarkOfflineCacheEntryAsForeign()
+{
+ SendMarkOfflineCacheEntryAsForeign();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIAssociatedContentSecurity
+//-----------------------------------------------------------------------------
+
+bool
+HttpChannelChild::GetAssociatedContentSecurity(
+ nsIAssociatedContentSecurity** _result)
+{
+ if (!mSecurityInfo)
+ return false;
+
+ nsCOMPtr<nsIAssociatedContentSecurity> assoc =
+ do_QueryInterface(mSecurityInfo);
+ if (!assoc)
+ return false;
+
+ if (_result)
+ assoc.forget(_result);
+ return true;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCountSubRequestsBrokenSecurity(
+ int32_t *aSubRequestsBrokenSecurity)
+{
+ nsCOMPtr<nsIAssociatedContentSecurity> assoc;
+ if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
+ return NS_OK;
+
+ return assoc->GetCountSubRequestsBrokenSecurity(aSubRequestsBrokenSecurity);
+}
+NS_IMETHODIMP
+HttpChannelChild::SetCountSubRequestsBrokenSecurity(
+ int32_t aSubRequestsBrokenSecurity)
+{
+ nsCOMPtr<nsIAssociatedContentSecurity> assoc;
+ if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
+ return NS_OK;
+
+ return assoc->SetCountSubRequestsBrokenSecurity(aSubRequestsBrokenSecurity);
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCountSubRequestsNoSecurity(int32_t *aSubRequestsNoSecurity)
+{
+ nsCOMPtr<nsIAssociatedContentSecurity> assoc;
+ if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
+ return NS_OK;
+
+ return assoc->GetCountSubRequestsNoSecurity(aSubRequestsNoSecurity);
+}
+NS_IMETHODIMP
+HttpChannelChild::SetCountSubRequestsNoSecurity(int32_t aSubRequestsNoSecurity)
+{
+ nsCOMPtr<nsIAssociatedContentSecurity> assoc;
+ if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
+ return NS_OK;
+
+ return assoc->SetCountSubRequestsNoSecurity(aSubRequestsNoSecurity);
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Flush()
+{
+ nsCOMPtr<nsIAssociatedContentSecurity> assoc;
+ if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
+ return NS_OK;
+
+ nsresult rv;
+ int32_t broken, no;
+
+ rv = assoc->GetCountSubRequestsBrokenSecurity(&broken);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = assoc->GetCountSubRequestsNoSecurity(&no);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mIPCOpen)
+ SendUpdateAssociatedContentSecurity(broken, no);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIHttpChannelChild
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP HttpChannelChild::AddCookiesToRequest()
+{
+ HttpBaseChannel::AddCookiesToRequest();
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpChannelChild::GetClientSetRequestHeaders(RequestHeaderTuples **aRequestHeaders)
+{
+ *aRequestHeaders = &mClientSetRequestHeaders;
+ return NS_OK;
+}
+
+void
+HttpChannelChild::GetClientSetCorsPreflightParameters(OptionalCorsPreflightArgs& aArgs)
+{
+ if (mRequireCORSPreflight) {
+ CorsPreflightArgs args;
+ args.unsafeHeaders() = mUnsafeHeaders;
+ aArgs = args;
+ } else {
+ aArgs = mozilla::void_t();
+ }
+}
+
+NS_IMETHODIMP
+HttpChannelChild::RemoveCorsPreflightCacheEntry(nsIURI* aURI,
+ nsIPrincipal* aPrincipal)
+{
+ URIParams uri;
+ SerializeURI(aURI, uri);
+ PrincipalInfo principalInfo;
+ nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ bool result = false;
+ // Be careful to not attempt to send a message to the parent after the
+ // actor has been destroyed.
+ if (mIPCOpen) {
+ result = SendRemoveCorsPreflightCacheEntry(uri, principalInfo);
+ }
+ return result ? NS_OK : NS_ERROR_FAILURE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIDivertableChannel
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+HttpChannelChild::DivertToParent(ChannelDiverterChild **aChild)
+{
+ LOG(("HttpChannelChild::DivertToParent [this=%p]\n", this));
+ MOZ_RELEASE_ASSERT(aChild);
+ MOZ_RELEASE_ASSERT(gNeckoChild);
+ MOZ_RELEASE_ASSERT(!mDivertingToParent);
+
+ nsresult rv = NS_OK;
+
+ // If the channel was intercepted, then we likely do not have an IPC actor
+ // yet. We need one, though, in order to have a parent side to divert to.
+ // Therefore, create the actor just in time for us to suspend and divert it.
+ if (mSynthesizedResponse && !RemoteChannelExists()) {
+ mSuspendParentAfterSynthesizeResponse = true;
+ rv = ContinueAsyncOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We must fail DivertToParent() if there's no parent end of the channel (and
+ // won't be!) due to early failure.
+ if (NS_FAILED(mStatus) && !RemoteChannelExists()) {
+ return mStatus;
+ }
+
+ // Once this is set, it should not be unset before the child is taken down.
+ mDivertingToParent = true;
+
+ rv = Suspend();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ HttpChannelDiverterArgs args;
+ args.mChannelChild() = this;
+ args.mApplyConversion() = mApplyConversion;
+
+ PChannelDiverterChild* diverter =
+ gNeckoChild->SendPChannelDiverterConstructor(args);
+ MOZ_RELEASE_ASSERT(diverter);
+
+ *aChild = static_cast<ChannelDiverterChild*>(diverter);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::UnknownDecoderInvolvedKeepData()
+{
+ LOG(("HttpChannelChild::UnknownDecoderInvolvedKeepData [this=%p]",
+ this));
+ mUnknownDecoderInvolved = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::UnknownDecoderInvolvedOnStartRequestCalled()
+{
+ LOG(("HttpChannelChild::UnknownDecoderInvolvedOnStartRequestCalled "
+ "[this=%p, mDivertingToParent=%d]", this, mDivertingToParent));
+ mUnknownDecoderInvolved = false;
+
+ nsresult rv = NS_OK;
+
+ if (mDivertingToParent) {
+ rv = mEventQ->PrependEvents(mUnknownDecoderEventQ);
+ }
+ mUnknownDecoderEventQ.Clear();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetDivertingToParent(bool* aDiverting)
+{
+ NS_ENSURE_ARG_POINTER(aDiverting);
+ *aDiverting = mDivertingToParent;
+ return NS_OK;
+}
+
+
+void
+HttpChannelChild::ResetInterception()
+{
+ NS_ENSURE_TRUE_VOID(gNeckoChild != nullptr);
+
+ if (mInterceptListener) {
+ mInterceptListener->Cleanup();
+ }
+ mInterceptListener = nullptr;
+
+ // The chance to intercept any further requests associated with this channel
+ // (such as redirects) has passed.
+ mLoadFlags |= LOAD_BYPASS_SERVICE_WORKER;
+
+ // Continue with the original cross-process request
+ nsresult rv = ContinueAsyncOpen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ AsyncAbort(rv);
+ }
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetResponseSynthesized(bool* aSynthesized)
+{
+ NS_ENSURE_ARG_POINTER(aSynthesized);
+ *aSynthesized = mSynthesizedResponse;
+ return NS_OK;
+}
+
+void
+HttpChannelChild::OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>& aResponseHead,
+ nsIInputStream* aSynthesizedInput,
+ InterceptStreamListener* aStreamListener)
+{
+ mInterceptListener = aStreamListener;
+
+ // Intercepted responses should already be decoded. If its a redirect,
+ // however, we want to respect the encoding of the final result instead.
+ if (!WillRedirect(aResponseHead)) {
+ SetApplyConversion(false);
+ }
+
+ mResponseHead = aResponseHead;
+ mSynthesizedResponse = true;
+
+ if (WillRedirect(mResponseHead)) {
+ mShouldInterceptSubsequentRedirect = true;
+ // Continue with the original cross-process request
+ nsresult rv = ContinueAsyncOpen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ AsyncAbort(rv);
+ }
+ return;
+ }
+
+ // In our current implementation, the FetchEvent handler will copy the
+ // response stream completely into the pipe backing the input stream so we
+ // can treat the available as the length of the stream.
+ uint64_t available;
+ nsresult rv = aSynthesizedInput->Available(&available);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mSynthesizedStreamLength = -1;
+ } else {
+ mSynthesizedStreamLength = int64_t(available);
+ }
+
+ rv = nsInputStreamPump::Create(getter_AddRefs(mSynthesizedResponsePump),
+ aSynthesizedInput,
+ int64_t(-1), int64_t(-1), 0, 0, true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aSynthesizedInput->Close();
+ return;
+ }
+
+ rv = mSynthesizedResponsePump->AsyncRead(aStreamListener, nullptr);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // if this channel has been suspended previously, the pump needs to be
+ // correspondingly suspended now that it exists.
+ for (uint32_t i = 0; i < mSuspendCount; i++) {
+ rv = mSynthesizedResponsePump->Suspend();
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+
+ if (mCanceled) {
+ mSynthesizedResponsePump->Cancel(mStatus);
+ }
+}
+
+NS_IMETHODIMP
+HttpChannelChild::ForceIntercepted(bool aPostRedirectChannelShouldIntercept,
+ bool aPostRedirectChannelShouldUpgrade)
+{
+ mShouldParentIntercept = true;
+ mPostRedirectChannelShouldIntercept = aPostRedirectChannelShouldIntercept;
+ mPostRedirectChannelShouldUpgrade = aPostRedirectChannelShouldUpgrade;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::ForceIntercepted(uint64_t aInterceptionID)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void
+HttpChannelChild::ForceIntercepted(nsIInputStream* aSynthesizedInput)
+{
+ mSynthesizedInput = aSynthesizedInput;
+ mSynthesizedResponse = true;
+ mRedirectingForSubsequentSynthesizedResponse = true;
+}
+
+bool
+HttpChannelChild::RecvIssueDeprecationWarning(const uint32_t& warning,
+ const bool& asError)
+{
+ nsCOMPtr<nsIDeprecationWarner> warner;
+ GetCallback(warner);
+ if (warner) {
+ warner->IssueWarning(warning, asError);
+ }
+ return true;
+}
+
+bool
+HttpChannelChild::ShouldInterceptURI(nsIURI* aURI,
+ bool& aShouldUpgrade)
+{
+ bool isHttps = false;
+ nsresult rv = aURI->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ if (!isHttps && mLoadInfo) {
+ nsContentUtils::GetSecurityManager()->
+ GetChannelResultPrincipal(this, getter_AddRefs(resultPrincipal));
+ }
+ rv = NS_ShouldSecureUpgrade(aURI,
+ mLoadInfo,
+ resultPrincipal,
+ mPrivateBrowsing,
+ mAllowSTS,
+ aShouldUpgrade);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIURI> upgradedURI;
+ if (aShouldUpgrade) {
+ rv = NS_GetSecureUpgradedURI(aURI, getter_AddRefs(upgradedURI));
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ return ShouldIntercept(upgradedURI ? upgradedURI.get() : aURI);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpChannelChild.h b/netwerk/protocol/http/HttpChannelChild.h
new file mode 100644
index 000000000..edd209a9f
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -0,0 +1,390 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_HttpChannelChild_h
+#define mozilla_net_HttpChannelChild_h
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/net/PHttpChannelChild.h"
+#include "mozilla/net/ChannelEventQueue.h"
+
+#include "nsIStreamListener.h"
+#include "nsILoadGroup.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIProgressEventSink.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIUploadChannel2.h"
+#include "nsIResumableChannel.h"
+#include "nsIProxiedChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIAssociatedContentSecurity.h"
+#include "nsIChildChannel.h"
+#include "nsIHttpChannelChild.h"
+#include "nsIDivertableChannel.h"
+#include "mozilla/net/DNS.h"
+
+class nsInputStreamPump;
+
+namespace mozilla {
+namespace net {
+
+class InterceptedChannelContent;
+class InterceptStreamListener;
+
+class HttpChannelChild final : public PHttpChannelChild
+ , public HttpBaseChannel
+ , public HttpAsyncAborter<HttpChannelChild>
+ , public nsICacheInfoChannel
+ , public nsIProxiedChannel
+ , public nsIApplicationCacheChannel
+ , public nsIAsyncVerifyRedirectCallback
+ , public nsIAssociatedContentSecurity
+ , public nsIChildChannel
+ , public nsIHttpChannelChild
+ , public nsIDivertableChannel
+{
+ virtual ~HttpChannelChild();
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICACHEINFOCHANNEL
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSIAPPLICATIONCACHECONTAINER
+ NS_DECL_NSIAPPLICATIONCACHECHANNEL
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSIASSOCIATEDCONTENTSECURITY
+ NS_DECL_NSICHILDCHANNEL
+ NS_DECL_NSIHTTPCHANNELCHILD
+ NS_DECL_NSIDIVERTABLECHANNEL
+
+ HttpChannelChild();
+
+ // Methods HttpBaseChannel didn't implement for us or that we override.
+ //
+ // nsIRequest
+ NS_IMETHOD Cancel(nsresult status) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+ // nsIChannel
+ NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo) override;
+ NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *aContext) override;
+ NS_IMETHOD AsyncOpen2(nsIStreamListener *aListener) override;
+
+ // HttpBaseChannel::nsIHttpChannel
+ NS_IMETHOD SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue,
+ bool aMerge) override;
+ NS_IMETHOD SetEmptyRequestHeader(const nsACString& aHeader) override;
+ NS_IMETHOD RedirectTo(nsIURI *newURI) override;
+ NS_IMETHOD GetProtocolVersion(nsACString& aProtocolVersion) override;
+ // nsIHttpChannelInternal
+ NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey) override;
+ NS_IMETHOD ForceIntercepted(uint64_t aInterceptionID) override;
+ // nsISupportsPriority
+ NS_IMETHOD SetPriority(int32_t value) override;
+ // nsIClassOfService
+ NS_IMETHOD SetClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD AddClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD ClearClassFlags(uint32_t inFlags) override;
+ // nsIResumableChannel
+ NS_IMETHOD ResumeAt(uint64_t startPos, const nsACString& entityID) override;
+
+ // IPDL holds a reference while the PHttpChannel protocol is live (starting at
+ // AsyncOpen, and ending at either OnStopRequest or any IPDL error, either of
+ // which call NeckoChild::DeallocPHttpChannelChild()).
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ bool IsSuspended();
+
+ bool RecvNotifyTrackingProtectionDisabled() override;
+ void FlushedForDiversion();
+
+protected:
+ bool RecvOnStartRequest(const nsresult& channelStatus,
+ const nsHttpResponseHead& responseHead,
+ const bool& useResponseHead,
+ const nsHttpHeaderArray& requestHeaders,
+ const bool& isFromCache,
+ const bool& cacheEntryAvailable,
+ const uint32_t& cacheExpirationTime,
+ const nsCString& cachedCharset,
+ const nsCString& securityInfoSerialization,
+ const NetAddr& selfAddr,
+ const NetAddr& peerAddr,
+ const int16_t& redirectCount,
+ const uint32_t& cacheKey,
+ const nsCString& altDataType) override;
+ bool RecvOnTransportAndData(const nsresult& channelStatus,
+ const nsresult& status,
+ const uint64_t& progress,
+ const uint64_t& progressMax,
+ const uint64_t& offset,
+ const uint32_t& count,
+ const nsCString& data) override;
+ bool RecvOnStopRequest(const nsresult& statusCode, const ResourceTimingStruct& timing) override;
+ bool RecvOnProgress(const int64_t& progress, const int64_t& progressMax) override;
+ bool RecvOnStatus(const nsresult& status) override;
+ bool RecvFailedAsyncOpen(const nsresult& status) override;
+ bool RecvRedirect1Begin(const uint32_t& registrarId,
+ const URIParams& newURI,
+ const uint32_t& redirectFlags,
+ const nsHttpResponseHead& responseHead,
+ const nsCString& securityInfoSerialization,
+ const nsCString& channelId) override;
+ bool RecvRedirect3Complete() override;
+ bool RecvAssociateApplicationCache(const nsCString& groupID,
+ const nsCString& clientID) override;
+ bool RecvFlushedForDiversion() override;
+ bool RecvDivertMessages() override;
+ bool RecvDeleteSelf() override;
+ bool RecvFinishInterceptedRedirect() override;
+
+ bool RecvReportSecurityMessage(const nsString& messageTag,
+ const nsString& messageCategory) override;
+
+ bool RecvIssueDeprecationWarning(const uint32_t& warning,
+ const bool& asError) override;
+
+ bool GetAssociatedContentSecurity(nsIAssociatedContentSecurity** res = nullptr);
+ virtual void DoNotifyListenerCleanup() override;
+
+ NS_IMETHOD GetResponseSynthesized(bool* aSynthesized) override;
+
+private:
+
+ class OverrideRunnable : public Runnable {
+ public:
+ OverrideRunnable(HttpChannelChild* aChannel,
+ HttpChannelChild* aNewChannel,
+ InterceptStreamListener* aListener,
+ nsIInputStream* aInput,
+ nsAutoPtr<nsHttpResponseHead>& aHead);
+
+ NS_IMETHOD Run() override;
+ void OverrideWithSynthesizedResponse();
+ private:
+ RefPtr<HttpChannelChild> mChannel;
+ RefPtr<HttpChannelChild> mNewChannel;
+ RefPtr<InterceptStreamListener> mListener;
+ nsCOMPtr<nsIInputStream> mInput;
+ nsAutoPtr<nsHttpResponseHead> mHead;
+ };
+
+ nsresult ContinueAsyncOpen();
+
+ void DoOnStartRequest(nsIRequest* aRequest, nsISupports* aContext);
+ void DoOnStatus(nsIRequest* aRequest, nsresult status);
+ void DoOnProgress(nsIRequest* aRequest, int64_t progress, int64_t progressMax);
+ void DoOnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, nsIInputStream* aStream,
+ uint64_t offset, uint32_t count);
+ void DoPreOnStopRequest(nsresult aStatus);
+ void DoOnStopRequest(nsIRequest* aRequest, nsresult aChannelStatus, nsISupports* aContext);
+
+ bool ShouldInterceptURI(nsIURI* aURI, bool& aShouldUpgrade);
+
+ // Discard the prior interception and continue with the original network request.
+ void ResetInterception();
+
+ // Override this channel's pending response with a synthesized one. The content will be
+ // asynchronously read from the pump.
+ void OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>& aResponseHead,
+ nsIInputStream* aSynthesizedInput,
+ InterceptStreamListener* aStreamListener);
+
+ void ForceIntercepted(nsIInputStream* aSynthesizedInput);
+
+ RequestHeaderTuples mClientSetRequestHeaders;
+ nsCOMPtr<nsIChildChannel> mRedirectChannelChild;
+ RefPtr<InterceptStreamListener> mInterceptListener;
+ RefPtr<nsInputStreamPump> mSynthesizedResponsePump;
+ nsCOMPtr<nsIInputStream> mSynthesizedInput;
+ int64_t mSynthesizedStreamLength;
+
+ bool mIsFromCache;
+ bool mCacheEntryAvailable;
+ uint32_t mCacheExpirationTime;
+ nsCString mCachedCharset;
+ nsCOMPtr<nsISupports> mCacheKey;
+
+ nsCString mProtocolVersion;
+
+ // If ResumeAt is called before AsyncOpen, we need to send extra data upstream
+ bool mSendResumeAt;
+
+ bool mIPCOpen;
+ bool mKeptAlive; // IPC kept open, but only for security info
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ // If nsUnknownDecoder is involved OnStartRequest call will be delayed and
+ // this queue keeps OnDataAvailable data until OnStartRequest is finally
+ // called.
+ nsTArray<UniquePtr<ChannelEvent>> mUnknownDecoderEventQ;
+ bool mUnknownDecoderInvolved;
+
+ // Once set, OnData and possibly OnStop will be diverted to the parent.
+ bool mDivertingToParent;
+ // Once set, no OnStart/OnData/OnStop callbacks should be received from the
+ // parent channel, nor dequeued from the ChannelEventQueue.
+ bool mFlushedForDiversion;
+ // Set if SendSuspend is called. Determines if SendResume is needed when
+ // diverting callbacks to parent.
+ bool mSuspendSent;
+
+ // Set if a response was synthesized, indicating that any forthcoming redirects
+ // should be intercepted.
+ bool mSynthesizedResponse;
+
+ // Set if a synthesized response should cause us to explictly allows intercepting
+ // an expected forthcoming redirect.
+ bool mShouldInterceptSubsequentRedirect;
+ // Set if a redirection is being initiated to facilitate providing a synthesized
+ // response to a channel using a different principal than the current one.
+ bool mRedirectingForSubsequentSynthesizedResponse;
+
+ // Set if a manual redirect mode channel needs to be intercepted in the
+ // parent.
+ bool mPostRedirectChannelShouldIntercept;
+ // Set if a manual redirect mode channel needs to be upgraded to a secure URI
+ // when it's being considered for interception. Can only be true if
+ // mPostRedirectChannelShouldIntercept is true.
+ bool mPostRedirectChannelShouldUpgrade;
+
+ // Set if the corresponding parent channel should force an interception to occur
+ // before the network transaction is initiated.
+ bool mShouldParentIntercept;
+
+ // Set if the corresponding parent channel should suspend after a response
+ // is synthesized.
+ bool mSuspendParentAfterSynthesizeResponse;
+
+ // Needed to call AsyncOpen in FinishInterceptedRedirect
+ nsCOMPtr<nsIStreamListener> mInterceptedRedirectListener;
+ nsCOMPtr<nsISupports> mInterceptedRedirectContext;
+ // Needed to call CleanupRedirectingChannel in FinishInterceptedRedirect
+ RefPtr<HttpChannelChild> mInterceptingChannel;
+ // Used to call OverrideWithSynthesizedResponse in FinishInterceptedRedirect
+ RefPtr<OverrideRunnable> mOverrideRunnable;
+
+ void FinishInterceptedRedirect();
+ void CleanupRedirectingChannel(nsresult rv);
+
+ // true after successful AsyncOpen until OnStopRequest completes.
+ bool RemoteChannelExists() { return mIPCOpen && !mKeptAlive; }
+
+ void AssociateApplicationCache(const nsCString &groupID,
+ const nsCString &clientID);
+ void OnStartRequest(const nsresult& channelStatus,
+ const nsHttpResponseHead& responseHead,
+ const bool& useResponseHead,
+ const nsHttpHeaderArray& requestHeaders,
+ const bool& isFromCache,
+ const bool& cacheEntryAvailable,
+ const uint32_t& cacheExpirationTime,
+ const nsCString& cachedCharset,
+ const nsCString& securityInfoSerialization,
+ const NetAddr& selfAddr,
+ const NetAddr& peerAddr,
+ const uint32_t& cacheKey,
+ const nsCString& altDataType);
+ void MaybeDivertOnData(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count);
+ void OnTransportAndData(const nsresult& channelStatus,
+ const nsresult& status,
+ const uint64_t progress,
+ const uint64_t& progressMax,
+ const uint64_t& offset,
+ const uint32_t& count,
+ const nsCString& data);
+ void OnStopRequest(const nsresult& channelStatus, const ResourceTimingStruct& timing);
+ void MaybeDivertOnStop(const nsresult& aChannelStatus);
+ void OnProgress(const int64_t& progress, const int64_t& progressMax);
+ void OnStatus(const nsresult& status);
+ void FailedAsyncOpen(const nsresult& status);
+ void HandleAsyncAbort();
+ void Redirect1Begin(const uint32_t& registrarId,
+ const URIParams& newUri,
+ const uint32_t& redirectFlags,
+ const nsHttpResponseHead& responseHead,
+ const nsACString& securityInfoSerialization,
+ const nsACString& channelId);
+ bool Redirect3Complete(OverrideRunnable* aRunnable);
+ void DeleteSelf();
+
+ // Create a a new channel to be used in a redirection, based on the provided
+ // response headers.
+ nsresult SetupRedirect(nsIURI* uri,
+ const nsHttpResponseHead* responseHead,
+ const uint32_t& redirectFlags,
+ nsIChannel** outChannel);
+
+ // Perform a redirection without communicating with the parent process at all.
+ void BeginNonIPCRedirect(nsIURI* responseURI,
+ const nsHttpResponseHead* responseHead);
+
+ // Override the default security info pointer during a non-IPC redirection.
+ void OverrideSecurityInfoForNonIPCRedirect(nsISupports* securityInfo);
+
+ friend class AssociateApplicationCacheEvent;
+ friend class StartRequestEvent;
+ friend class StopRequestEvent;
+ friend class TransportAndDataEvent;
+ friend class MaybeDivertOnDataHttpEvent;
+ friend class MaybeDivertOnStopHttpEvent;
+ friend class ProgressEvent;
+ friend class StatusEvent;
+ friend class FailedAsyncOpenEvent;
+ friend class Redirect1Event;
+ friend class Redirect3Event;
+ friend class DeleteSelfEvent;
+ friend class HttpAsyncAborter<HttpChannelChild>;
+ friend class InterceptStreamListener;
+ friend class InterceptedChannelContent;
+};
+
+// A stream listener interposed between the nsInputStreamPump used for intercepted channels
+// and this channel's original listener. This is only used to ensure the original listener
+// sees the channel as the request object, and to synthesize OnStatus and OnProgress notifications.
+class InterceptStreamListener : public nsIStreamListener
+ , public nsIProgressEventSink
+{
+ RefPtr<HttpChannelChild> mOwner;
+ nsCOMPtr<nsISupports> mContext;
+ virtual ~InterceptStreamListener() {}
+ public:
+ InterceptStreamListener(HttpChannelChild* aOwner, nsISupports* aContext)
+ : mOwner(aOwner)
+ , mContext(aContext)
+ {
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIPROGRESSEVENTSINK
+
+ void Cleanup();
+};
+
+//-----------------------------------------------------------------------------
+// inline functions
+//-----------------------------------------------------------------------------
+
+inline bool
+HttpChannelChild::IsSuspended()
+{
+ return mSuspendCount != 0;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpChannelChild_h
diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp
new file mode 100644
index 000000000..51da1ec8c
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -0,0 +1,1821 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/ipc/FileDescriptorSetParent.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "HttpChannelParentListener.h"
+#include "nsHttpHandler.h"
+#include "nsNetUtil.h"
+#include "nsISupportsPriority.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsSerializationHelper.h"
+#include "nsISerializable.h"
+#include "nsIAssociatedContentSecurity.h"
+#include "nsIApplicationCacheService.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "nsIAuthInformation.h"
+#include "nsIAuthPromptCallback.h"
+#include "nsIContentPolicy.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "nsICachingChannel.h"
+#include "mozilla/LoadInfo.h"
+#include "nsQueryObject.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsCORSListenerProxy.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsIPrompt.h"
+#include "nsIWindowWatcher.h"
+#include "nsIDocument.h"
+#include "nsStringStream.h"
+
+using mozilla::BasePrincipal;
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+HttpChannelParent::HttpChannelParent(const PBrowserOrId& iframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus)
+ : mIPCClosed(false)
+ , mStoredStatus(NS_OK)
+ , mStoredProgress(0)
+ , mStoredProgressMax(0)
+ , mSentRedirect1Begin(false)
+ , mSentRedirect1BeginFailed(false)
+ , mReceivedRedirect2Verify(false)
+ , mPBOverride(aOverrideStatus)
+ , mLoadContext(aLoadContext)
+ , mStatus(NS_OK)
+ , mPendingDiversion(false)
+ , mDivertingFromChild(false)
+ , mDivertedOnStartRequest(false)
+ , mSuspendedForDiversion(false)
+ , mSuspendAfterSynthesizeResponse(false)
+ , mWillSynthesizeResponse(false)
+ , mNestedFrameId(0)
+{
+ LOG(("Creating HttpChannelParent [this=%p]\n", this));
+
+ // Ensure gHttpHandler is initialized: we need the atom table up and running.
+ nsCOMPtr<nsIHttpProtocolHandler> dummyInitializer =
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http");
+
+ MOZ_ASSERT(gHttpHandler);
+ mHttpHandler = gHttpHandler;
+
+ if (iframeEmbedding.type() == PBrowserOrId::TPBrowserParent) {
+ mTabParent = static_cast<dom::TabParent*>(iframeEmbedding.get_PBrowserParent());
+ } else {
+ mNestedFrameId = iframeEmbedding.get_TabId();
+ }
+
+ mEventQ = new ChannelEventQueue(static_cast<nsIParentRedirectingChannel*>(this));
+}
+
+HttpChannelParent::~HttpChannelParent()
+{
+ LOG(("Destroying HttpChannelParent [this=%p]\n", this));
+}
+
+void
+HttpChannelParent::ActorDestroy(ActorDestroyReason why)
+{
+ // We may still have refcount>0 if nsHttpChannel hasn't called OnStopRequest
+ // yet, but child process has crashed. We must not try to send any more msgs
+ // to child, or IPDL will kill chrome process, too.
+ mIPCClosed = true;
+
+ // If this is an intercepted channel, we need to make sure that any resources are
+ // cleaned up to avoid leaks.
+ if (mParentListener) {
+ mParentListener->ClearInterceptedChannel();
+ }
+}
+
+bool
+HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs)
+{
+ LOG(("HttpChannelParent::Init [this=%p]\n", this));
+ switch (aArgs.type()) {
+ case HttpChannelCreationArgs::THttpChannelOpenArgs:
+ {
+ const HttpChannelOpenArgs& a = aArgs.get_HttpChannelOpenArgs();
+ return DoAsyncOpen(a.uri(), a.original(), a.doc(), a.referrer(),
+ a.referrerPolicy(), a.apiRedirectTo(), a.topWindowURI(),
+ a.loadFlags(), a.requestHeaders(),
+ a.requestMethod(), a.uploadStream(),
+ a.uploadStreamHasHeaders(), a.priority(), a.classOfService(),
+ a.redirectionLimit(), a.allowPipelining(), a.allowSTS(),
+ a.thirdPartyFlags(), a.resumeAt(), a.startPos(),
+ a.entityID(), a.chooseApplicationCache(),
+ a.appCacheClientID(), a.allowSpdy(), a.allowAltSvc(), a.beConservative(),
+ a.loadInfo(), a.synthesizedResponseHead(),
+ a.synthesizedSecurityInfoSerialization(),
+ a.cacheKey(), a.requestContextID(), a.preflightArgs(),
+ a.initialRwin(), a.blockAuthPrompt(),
+ a.suspendAfterSynthesizeResponse(),
+ a.allowStaleCacheContent(), a.contentTypeHint(),
+ a.channelId(), a.contentWindowId(), a.preferredAlternativeType());
+ }
+ case HttpChannelCreationArgs::THttpChannelConnectArgs:
+ {
+ const HttpChannelConnectArgs& cArgs = aArgs.get_HttpChannelConnectArgs();
+ return ConnectChannel(cArgs.registrarId(), cArgs.shouldIntercept());
+ }
+ default:
+ NS_NOTREACHED("unknown open type");
+ return false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpChannelParent)
+NS_IMPL_RELEASE(HttpChannelParent)
+NS_INTERFACE_MAP_BEGIN(HttpChannelParent)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider)
+ NS_INTERFACE_MAP_ENTRY(nsIParentRedirectingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIDeprecationWarner)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIParentRedirectingChannel)
+ if (aIID.Equals(NS_GET_IID(HttpChannelParent))) {
+ foundInterface = static_cast<nsIInterfaceRequestor*>(this);
+ } else
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::GetInterface(const nsIID& aIID, void **result)
+{
+ if (aIID.Equals(NS_GET_IID(nsIAuthPromptProvider)) ||
+ aIID.Equals(NS_GET_IID(nsISecureBrowserUI))) {
+ if (mTabParent) {
+ return mTabParent->QueryInterface(aIID, result);
+ }
+ }
+
+ // Only support nsIAuthPromptProvider in Content process
+ if (XRE_IsParentProcess() &&
+ aIID.Equals(NS_GET_IID(nsIAuthPromptProvider))) {
+ *result = nullptr;
+ return NS_OK;
+ }
+
+ // Only support nsILoadContext if child channel's callbacks did too
+ if (aIID.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(result);
+ return NS_OK;
+ }
+
+ if (mTabParent && aIID.Equals(NS_GET_IID(nsIPrompt))) {
+ nsCOMPtr<Element> frameElement = mTabParent->GetOwnerElement();
+ if (frameElement) {
+ nsCOMPtr<nsPIDOMWindowOuter> win =frameElement->OwnerDoc()->GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+
+ if (NS_WARN_IF(!NS_SUCCEEDED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIPrompt> prompt;
+ rv = wwatch->GetNewPrompter(win, getter_AddRefs(prompt));
+ if (NS_WARN_IF(!NS_SUCCEEDED(rv))) {
+ return rv;
+ }
+
+ prompt.forget(result);
+ return NS_OK;
+ }
+ }
+
+ return QueryInterface(aIID, result);
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::PHttpChannelParent
+//-----------------------------------------------------------------------------
+
+void
+HttpChannelParent::InvokeAsyncOpen(nsresult rv)
+{
+ if (NS_FAILED(rv)) {
+ Unused << SendFailedAsyncOpen(rv);
+ return;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = mChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ Unused << SendFailedAsyncOpen(rv);
+ return;
+ }
+ if (loadInfo && loadInfo->GetEnforceSecurity()) {
+ rv = mChannel->AsyncOpen2(mParentListener);
+ }
+ else {
+ rv = mChannel->AsyncOpen(mParentListener, nullptr);
+ }
+ if (NS_FAILED(rv)) {
+ Unused << SendFailedAsyncOpen(rv);
+ }
+}
+
+namespace {
+class InvokeAsyncOpen : public Runnable
+{
+ nsMainThreadPtrHandle<nsIInterfaceRequestor> mChannel;
+ nsresult mStatus;
+public:
+ InvokeAsyncOpen(const nsMainThreadPtrHandle<nsIInterfaceRequestor>& aChannel,
+ nsresult aStatus)
+ : mChannel(aChannel)
+ , mStatus(aStatus)
+ {
+ }
+
+ NS_IMETHOD Run()
+ {
+ RefPtr<HttpChannelParent> channel = do_QueryObject(mChannel.get());
+ channel->InvokeAsyncOpen(mStatus);
+ return NS_OK;
+ }
+};
+
+struct UploadStreamClosure {
+ nsMainThreadPtrHandle<nsIInterfaceRequestor> mChannel;
+
+ explicit UploadStreamClosure(const nsMainThreadPtrHandle<nsIInterfaceRequestor>& aChannel)
+ : mChannel(aChannel)
+ {
+ }
+};
+
+void
+UploadCopyComplete(void* aClosure, nsresult aStatus) {
+ // Called on the Stream Transport Service thread by NS_AsyncCopy
+ MOZ_ASSERT(!NS_IsMainThread());
+ UniquePtr<UploadStreamClosure> closure(static_cast<UploadStreamClosure*>(aClosure));
+ nsCOMPtr<nsIRunnable> event = new InvokeAsyncOpen(closure->mChannel, aStatus);
+ NS_DispatchToMainThread(event);
+}
+} // anonymous namespace
+
+bool
+HttpChannelParent::DoAsyncOpen( const URIParams& aURI,
+ const OptionalURIParams& aOriginalURI,
+ const OptionalURIParams& aDocURI,
+ const OptionalURIParams& aReferrerURI,
+ const uint32_t& aReferrerPolicy,
+ const OptionalURIParams& aAPIRedirectToURI,
+ const OptionalURIParams& aTopWindowURI,
+ const uint32_t& aLoadFlags,
+ const RequestHeaderTuples& requestHeaders,
+ const nsCString& requestMethod,
+ const OptionalIPCStream& uploadStream,
+ const bool& uploadStreamHasHeaders,
+ const uint16_t& priority,
+ const uint32_t& classOfService,
+ const uint8_t& redirectionLimit,
+ const bool& allowPipelining,
+ const bool& allowSTS,
+ const uint32_t& thirdPartyFlags,
+ const bool& doResumeAt,
+ const uint64_t& startPos,
+ const nsCString& entityID,
+ const bool& chooseApplicationCache,
+ const nsCString& appCacheClientID,
+ const bool& allowSpdy,
+ const bool& allowAltSvc,
+ const bool& beConservative,
+ const OptionalLoadInfoArgs& aLoadInfoArgs,
+ const OptionalHttpResponseHead& aSynthesizedResponseHead,
+ const nsCString& aSecurityInfoSerialization,
+ const uint32_t& aCacheKey,
+ const nsCString& aRequestContextID,
+ const OptionalCorsPreflightArgs& aCorsPreflightArgs,
+ const uint32_t& aInitialRwin,
+ const bool& aBlockAuthPrompt,
+ const bool& aSuspendAfterSynthesizeResponse,
+ const bool& aAllowStaleCacheContent,
+ const nsCString& aContentTypeHint,
+ const nsCString& aChannelId,
+ const uint64_t& aContentWindowId,
+ const nsCString& aPreferredAlternativeType)
+{
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+ if (!uri) {
+ // URIParams does MOZ_ASSERT if null, but we need to protect opt builds from
+ // null deref here.
+ return false;
+ }
+ nsCOMPtr<nsIURI> originalUri = DeserializeURI(aOriginalURI);
+ nsCOMPtr<nsIURI> docUri = DeserializeURI(aDocURI);
+ nsCOMPtr<nsIURI> referrerUri = DeserializeURI(aReferrerURI);
+ nsCOMPtr<nsIURI> apiRedirectToUri = DeserializeURI(aAPIRedirectToURI);
+ nsCOMPtr<nsIURI> topWindowUri = DeserializeURI(aTopWindowURI);
+
+ LOG(("HttpChannelParent RecvAsyncOpen [this=%p uri=%s]\n",
+ this, uri->GetSpecOrDefault().get()));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
+ if (NS_FAILED(rv))
+ return SendFailedAsyncOpen(rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs,
+ getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ NeckoOriginAttributes attrs;
+ rv = loadInfo->GetOriginAttributes(&attrs);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannelInternal(getter_AddRefs(channel), uri, loadInfo,
+ nullptr, nullptr, aLoadFlags, ios);
+
+ if (NS_FAILED(rv))
+ return SendFailedAsyncOpen(rv);
+
+ // This cast is safe since this is AsyncOpen specific to http. channel
+ // is ensured to be nsHttpChannel.
+ mChannel = static_cast<nsHttpChannel *>(channel.get());
+
+ // Set the channelId allocated in child to the parent instance
+ mChannel->SetChannelId(aChannelId);
+ mChannel->SetTopLevelContentWindowId(aContentWindowId);
+
+ mChannel->SetWarningReporter(this);
+ mChannel->SetTimingEnabled(true);
+ if (mPBOverride != kPBOverride_Unset) {
+ mChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
+ }
+
+ if (doResumeAt)
+ mChannel->ResumeAt(startPos, entityID);
+
+ if (originalUri)
+ mChannel->SetOriginalURI(originalUri);
+ if (docUri)
+ mChannel->SetDocumentURI(docUri);
+ if (referrerUri)
+ mChannel->SetReferrerWithPolicyInternal(referrerUri, aReferrerPolicy);
+ if (apiRedirectToUri)
+ mChannel->RedirectTo(apiRedirectToUri);
+ if (topWindowUri)
+ mChannel->SetTopWindowURI(topWindowUri);
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL)
+ mChannel->SetLoadFlags(aLoadFlags);
+
+ for (uint32_t i = 0; i < requestHeaders.Length(); i++) {
+ if (requestHeaders[i].mEmpty) {
+ mChannel->SetEmptyRequestHeader(requestHeaders[i].mHeader);
+ } else {
+ mChannel->SetRequestHeader(requestHeaders[i].mHeader,
+ requestHeaders[i].mValue,
+ requestHeaders[i].mMerge);
+ }
+ }
+
+ mParentListener = new HttpChannelParentListener(this);
+
+ mChannel->SetNotificationCallbacks(mParentListener);
+
+ mChannel->SetRequestMethod(nsDependentCString(requestMethod.get()));
+
+ if (aCorsPreflightArgs.type() == OptionalCorsPreflightArgs::TCorsPreflightArgs) {
+ const CorsPreflightArgs& args = aCorsPreflightArgs.get_CorsPreflightArgs();
+ mChannel->SetCorsPreflightParameters(args.unsafeHeaders());
+ }
+
+ bool delayAsyncOpen = false;
+ nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(uploadStream);
+ if (stream) {
+ // FIXME: The fast path of using the existing stream currently only applies to streams
+ // that have had their entire contents serialized from the child at this point.
+ // Once bug 1294446 and bug 1294450 are fixed it is worth revisiting this heuristic.
+ nsCOMPtr<nsIIPCSerializableInputStream> completeStream = do_QueryInterface(stream);
+ if (!completeStream) {
+ delayAsyncOpen = true;
+
+ // buffer size matches PSendStream transfer size.
+ const uint32_t kBufferSize = 32768;
+
+ nsCOMPtr<nsIStorageStream> storageStream;
+ nsresult rv = NS_NewStorageStream(kBufferSize, UINT32_MAX,
+ getter_AddRefs(storageStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIInputStream> newUploadStream;
+ rv = storageStream->NewInputStream(0, getter_AddRefs(newUploadStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIOutputStream> sink;
+ rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv) || !target) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> iir = static_cast<nsIInterfaceRequestor*>(this);
+ nsMainThreadPtrHandle<nsIInterfaceRequestor> handle =
+ nsMainThreadPtrHandle<nsIInterfaceRequestor>(
+ new nsMainThreadPtrHolder<nsIInterfaceRequestor>(iir));
+ UniquePtr<UploadStreamClosure> closure(new UploadStreamClosure(handle));
+
+ // Accumulate the stream contents as the child sends it. We will continue with
+ // the AsyncOpen process once the full stream has been received.
+ rv = NS_AsyncCopy(stream, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS,
+ kBufferSize, // copy segment size
+ UploadCopyComplete, closure.release());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ mChannel->InternalSetUploadStream(newUploadStream);
+ } else {
+ mChannel->InternalSetUploadStream(stream);
+ }
+ mChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
+ }
+
+ if (aSynthesizedResponseHead.type() == OptionalHttpResponseHead::TnsHttpResponseHead) {
+ mParentListener->SetupInterception(aSynthesizedResponseHead.get_nsHttpResponseHead());
+ mWillSynthesizeResponse = true;
+ mChannel->SetCouldBeSynthesized();
+
+ if (!aSecurityInfoSerialization.IsEmpty()) {
+ nsCOMPtr<nsISupports> secInfo;
+ NS_DeserializeObject(aSecurityInfoSerialization, getter_AddRefs(secInfo));
+ mChannel->OverrideSecurityInfo(secInfo);
+ }
+ } else {
+ nsLoadFlags newLoadFlags;
+ mChannel->GetLoadFlags(&newLoadFlags);
+ newLoadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+ mChannel->SetLoadFlags(newLoadFlags);
+ }
+
+ nsCOMPtr<nsISupportsPRUint32> cacheKey =
+ do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ rv = cacheKey->SetData(aCacheKey);
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ mChannel->SetCacheKey(cacheKey);
+ mChannel->PreferAlternativeDataType(aPreferredAlternativeType);
+
+ mChannel->SetAllowStaleCacheContent(aAllowStaleCacheContent);
+
+ mChannel->SetContentType(aContentTypeHint);
+
+ if (priority != nsISupportsPriority::PRIORITY_NORMAL) {
+ mChannel->SetPriority(priority);
+ }
+ if (classOfService) {
+ mChannel->SetClassFlags(classOfService);
+ }
+ mChannel->SetRedirectionLimit(redirectionLimit);
+ mChannel->SetAllowPipelining(allowPipelining);
+ mChannel->SetAllowSTS(allowSTS);
+ mChannel->SetThirdPartyFlags(thirdPartyFlags);
+ mChannel->SetAllowSpdy(allowSpdy);
+ mChannel->SetAllowAltSvc(allowAltSvc);
+ mChannel->SetBeConservative(beConservative);
+ mChannel->SetInitialRwin(aInitialRwin);
+ mChannel->SetBlockAuthPrompt(aBlockAuthPrompt);
+
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChan =
+ do_QueryObject(mChannel);
+ nsCOMPtr<nsIApplicationCacheService> appCacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
+
+ bool setChooseApplicationCache = chooseApplicationCache;
+ if (appCacheChan && appCacheService) {
+ // We might potentially want to drop this flag (that is TRUE by default)
+ // after we successfully associate the channel with an application cache
+ // reported by the channel child. Dropping it here may be too early.
+ appCacheChan->SetInheritApplicationCache(false);
+ if (!appCacheClientID.IsEmpty()) {
+ nsCOMPtr<nsIApplicationCache> appCache;
+ rv = appCacheService->GetApplicationCache(appCacheClientID,
+ getter_AddRefs(appCache));
+ if (NS_SUCCEEDED(rv)) {
+ appCacheChan->SetApplicationCache(appCache);
+ setChooseApplicationCache = false;
+ }
+ }
+
+ if (setChooseApplicationCache) {
+ NeckoOriginAttributes neckoAttrs;
+ NS_GetOriginAttributes(mChannel, neckoAttrs);
+
+ PrincipalOriginAttributes attrs;
+ attrs.InheritFromNecko(neckoAttrs);
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateCodebasePrincipal(uri, attrs);
+
+ bool chooseAppCache = false;
+ // This works because we've already called SetNotificationCallbacks and
+ // done mPBOverride logic by this point.
+ chooseAppCache = NS_ShouldCheckAppCache(principal, NS_UsePrivateBrowsing(mChannel));
+
+ appCacheChan->SetChooseApplicationCache(chooseAppCache);
+ }
+ }
+
+ nsID requestContextID;
+ requestContextID.Parse(aRequestContextID.BeginReading());
+ mChannel->SetRequestContextID(requestContextID);
+
+ mSuspendAfterSynthesizeResponse = aSuspendAfterSynthesizeResponse;
+
+ if (!delayAsyncOpen) {
+ InvokeAsyncOpen(NS_OK);
+ }
+
+ return true;
+}
+
+bool
+HttpChannelParent::ConnectChannel(const uint32_t& registrarId, const bool& shouldIntercept)
+{
+ nsresult rv;
+
+ LOG(("HttpChannelParent::ConnectChannel: Looking for a registered channel "
+ "[this=%p, id=%lu]\n", this, registrarId));
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_LinkRedirectChannels(registrarId, this, getter_AddRefs(channel));
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Could not find the http channel to connect its IPC parent");
+ // This makes the channel delete itself safely. It's the only thing
+ // we can do now, since this parent channel cannot be used and there is
+ // no other way to tell the child side there were something wrong.
+ Delete();
+ return true;
+ }
+
+ // It's safe to cast here since the found parent-side real channel is ensured
+ // to be http (nsHttpChannel). ConnectChannel called from HttpChannelParent::Init
+ // can only be called for http channels. It's bound by ipdl.
+ mChannel = static_cast<nsHttpChannel*>(channel.get());
+ LOG((" found channel %p, rv=%08x", mChannel.get(), rv));
+
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ NS_QueryNotificationCallbacks(channel, controller);
+ RefPtr<HttpChannelParentListener> parentListener = do_QueryObject(controller);
+ MOZ_ASSERT(parentListener);
+ parentListener->SetupInterceptionAfterRedirect(shouldIntercept);
+
+ if (mPBOverride != kPBOverride_Unset) {
+ // redirected-to channel may not support PB
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryObject(mChannel);
+ if (pbChannel) {
+ pbChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
+ }
+ }
+
+ return true;
+}
+
+bool
+HttpChannelParent::RecvSetPriority(const uint16_t& priority)
+{
+ LOG(("HttpChannelParent::RecvSetPriority [this=%p, priority=%u]\n",
+ this, priority));
+
+ if (mChannel) {
+ mChannel->SetPriority(priority);
+ }
+
+ nsCOMPtr<nsISupportsPriority> priorityRedirectChannel =
+ do_QueryInterface(mRedirectChannel);
+ if (priorityRedirectChannel)
+ priorityRedirectChannel->SetPriority(priority);
+
+ return true;
+}
+
+bool
+HttpChannelParent::RecvSetClassOfService(const uint32_t& cos)
+{
+ if (mChannel) {
+ mChannel->SetClassFlags(cos);
+ }
+ return true;
+}
+
+bool
+HttpChannelParent::RecvSuspend()
+{
+ LOG(("HttpChannelParent::RecvSuspend [this=%p]\n", this));
+
+ if (mChannel) {
+ mChannel->Suspend();
+ }
+ return true;
+}
+
+bool
+HttpChannelParent::RecvResume()
+{
+ LOG(("HttpChannelParent::RecvResume [this=%p]\n", this));
+
+ if (mChannel) {
+ mChannel->Resume();
+ }
+ return true;
+}
+
+bool
+HttpChannelParent::RecvCancel(const nsresult& status)
+{
+ LOG(("HttpChannelParent::RecvCancel [this=%p]\n", this));
+
+ // May receive cancel before channel has been constructed!
+ if (mChannel) {
+ mChannel->Cancel(status);
+ }
+ return true;
+}
+
+
+bool
+HttpChannelParent::RecvSetCacheTokenCachedCharset(const nsCString& charset)
+{
+ if (mCacheEntry)
+ mCacheEntry->SetMetaDataElement("charset", charset.get());
+ return true;
+}
+
+bool
+HttpChannelParent::RecvUpdateAssociatedContentSecurity(const int32_t& broken,
+ const int32_t& no)
+{
+ if (mAssociatedContentSecurity) {
+ mAssociatedContentSecurity->SetCountSubRequestsBrokenSecurity(broken);
+ mAssociatedContentSecurity->SetCountSubRequestsNoSecurity(no);
+ }
+ return true;
+}
+
+bool
+HttpChannelParent::RecvRedirect2Verify(const nsresult& result,
+ const RequestHeaderTuples& changedHeaders,
+ const uint32_t& loadFlags,
+ const OptionalURIParams& aAPIRedirectURI,
+ const OptionalCorsPreflightArgs& aCorsPreflightArgs,
+ const bool& aForceHSTSPriming,
+ const bool& aMixedContentWouldBlock,
+ const bool& aChooseAppcache)
+{
+ LOG(("HttpChannelParent::RecvRedirect2Verify [this=%p result=%x]\n",
+ this, result));
+ nsresult rv;
+ if (NS_SUCCEEDED(result)) {
+ nsCOMPtr<nsIHttpChannel> newHttpChannel =
+ do_QueryInterface(mRedirectChannel);
+
+ if (newHttpChannel) {
+ nsCOMPtr<nsIURI> apiRedirectUri = DeserializeURI(aAPIRedirectURI);
+
+ if (apiRedirectUri)
+ newHttpChannel->RedirectTo(apiRedirectUri);
+
+ for (uint32_t i = 0; i < changedHeaders.Length(); i++) {
+ if (changedHeaders[i].mEmpty) {
+ newHttpChannel->SetEmptyRequestHeader(changedHeaders[i].mHeader);
+ } else {
+ newHttpChannel->SetRequestHeader(changedHeaders[i].mHeader,
+ changedHeaders[i].mValue,
+ changedHeaders[i].mMerge);
+ }
+ }
+
+ // A successfully redirected channel must have the LOAD_REPLACE flag.
+ MOZ_ASSERT(loadFlags & nsIChannel::LOAD_REPLACE);
+ if (loadFlags & nsIChannel::LOAD_REPLACE) {
+ newHttpChannel->SetLoadFlags(loadFlags);
+ }
+
+ if (aCorsPreflightArgs.type() == OptionalCorsPreflightArgs::TCorsPreflightArgs) {
+ nsCOMPtr<nsIHttpChannelInternal> newInternalChannel =
+ do_QueryInterface(newHttpChannel);
+ MOZ_RELEASE_ASSERT(newInternalChannel);
+ const CorsPreflightArgs& args = aCorsPreflightArgs.get_CorsPreflightArgs();
+ newInternalChannel->SetCorsPreflightParameters(args.unsafeHeaders());
+ }
+
+ if (aForceHSTSPriming) {
+ nsCOMPtr<nsILoadInfo> newLoadInfo;
+ rv = newHttpChannel->GetLoadInfo(getter_AddRefs(newLoadInfo));
+ if (NS_SUCCEEDED(rv) && newLoadInfo) {
+ newLoadInfo->SetHSTSPriming(aMixedContentWouldBlock);
+ }
+ }
+
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(newHttpChannel);
+ if (appCacheChannel) {
+ appCacheChannel->SetChooseApplicationCache(aChooseAppcache);
+ }
+ }
+ }
+
+ if (!mRedirectCallback) {
+ // This should according the logic never happen, log the situation.
+ if (mReceivedRedirect2Verify)
+ LOG(("RecvRedirect2Verify[%p]: Duplicate fire", this));
+ if (mSentRedirect1BeginFailed)
+ LOG(("RecvRedirect2Verify[%p]: Send to child failed", this));
+ if (mSentRedirect1Begin && NS_FAILED(result))
+ LOG(("RecvRedirect2Verify[%p]: Redirect failed", this));
+ if (mSentRedirect1Begin && NS_SUCCEEDED(result))
+ LOG(("RecvRedirect2Verify[%p]: Redirect succeeded", this));
+ if (!mRedirectChannel)
+ LOG(("RecvRedirect2Verify[%p]: Missing redirect channel", this));
+
+ NS_ERROR("Unexpcted call to HttpChannelParent::RecvRedirect2Verify, "
+ "mRedirectCallback null");
+ }
+
+ mReceivedRedirect2Verify = true;
+
+ if (mRedirectCallback) {
+ LOG(("HttpChannelParent::RecvRedirect2Verify call OnRedirectVerifyCallback"
+ " [this=%p result=%x, mRedirectCallback=%p]\n",
+ this, result, mRedirectCallback.get()));
+ mRedirectCallback->OnRedirectVerifyCallback(result);
+ mRedirectCallback = nullptr;
+ }
+
+ return true;
+}
+
+bool
+HttpChannelParent::RecvDocumentChannelCleanup()
+{
+ // From now on only using mAssociatedContentSecurity. Free everything else.
+ mChannel = nullptr; // Reclaim some memory sooner.
+ mCacheEntry = nullptr; // Else we'll block other channels reading same URI
+ return true;
+}
+
+bool
+HttpChannelParent::RecvMarkOfflineCacheEntryAsForeign()
+{
+ if (mOfflineForeignMarker) {
+ mOfflineForeignMarker->MarkAsForeign();
+ mOfflineForeignMarker = 0;
+ }
+
+ return true;
+}
+
+class DivertDataAvailableEvent : public ChannelEvent
+{
+public:
+ DivertDataAvailableEvent(HttpChannelParent* aParent,
+ const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+ : mParent(aParent)
+ , mData(data)
+ , mOffset(offset)
+ , mCount(count)
+ {
+ }
+
+ void Run()
+ {
+ mParent->DivertOnDataAvailable(mData, mOffset, mCount);
+ }
+
+private:
+ HttpChannelParent* mParent;
+ nsCString mData;
+ uint64_t mOffset;
+ uint32_t mCount;
+};
+
+bool
+HttpChannelParent::RecvDivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ LOG(("HttpChannelParent::RecvDivertOnDataAvailable [this=%p]\n", this));
+
+ MOZ_ASSERT(mParentListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot RecvDivertOnDataAvailable if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ // Drop OnDataAvailables if the parent was canceled already.
+ if (NS_FAILED(mStatus)) {
+ return true;
+ }
+
+ mEventQ->RunOrEnqueue(new DivertDataAvailableEvent(this, data, offset,
+ count));
+ return true;
+}
+
+void
+HttpChannelParent::DivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ LOG(("HttpChannelParent::DivertOnDataAvailable [this=%p]\n", this));
+
+ MOZ_ASSERT(mParentListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertOnDataAvailable if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Drop OnDataAvailables if the parent was canceled already.
+ if (NS_FAILED(mStatus)) {
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(),
+ count, NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ if (mChannel) {
+ mChannel->Cancel(rv);
+ }
+ mStatus = rv;
+ return;
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ rv = mParentListener->OnDataAvailable(mChannel, nullptr, stringStream,
+ offset, count);
+ stringStream->Close();
+ if (NS_FAILED(rv)) {
+ if (mChannel) {
+ mChannel->Cancel(rv);
+ }
+ mStatus = rv;
+ }
+}
+
+class DivertStopRequestEvent : public ChannelEvent
+{
+public:
+ DivertStopRequestEvent(HttpChannelParent* aParent,
+ const nsresult& statusCode)
+ : mParent(aParent)
+ , mStatusCode(statusCode)
+ {
+ }
+
+ void Run() {
+ mParent->DivertOnStopRequest(mStatusCode);
+ }
+
+private:
+ HttpChannelParent* mParent;
+ nsresult mStatusCode;
+};
+
+bool
+HttpChannelParent::RecvDivertOnStopRequest(const nsresult& statusCode)
+{
+ LOG(("HttpChannelParent::RecvDivertOnStopRequest [this=%p]\n", this));
+
+ MOZ_ASSERT(mParentListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot RecvDivertOnStopRequest if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ mEventQ->RunOrEnqueue(new DivertStopRequestEvent(this, statusCode));
+ return true;
+}
+
+void
+HttpChannelParent::DivertOnStopRequest(const nsresult& statusCode)
+{
+ LOG(("HttpChannelParent::DivertOnStopRequest [this=%p]\n", this));
+
+ MOZ_ASSERT(mParentListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertOnStopRequest if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Honor the channel's status even if the underlying transaction completed.
+ nsresult status = NS_FAILED(mStatus) ? mStatus : statusCode;
+
+ // Reset fake pending status in case OnStopRequest has already been called.
+ if (mChannel) {
+ mChannel->ForcePending(false);
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mParentListener->OnStopRequest(mChannel, nullptr, status);
+}
+
+class DivertCompleteEvent : public ChannelEvent
+{
+public:
+ explicit DivertCompleteEvent(HttpChannelParent* aParent)
+ : mParent(aParent)
+ {
+ }
+
+ void Run() {
+ mParent->DivertComplete();
+ }
+
+private:
+ HttpChannelParent* mParent;
+};
+
+bool
+HttpChannelParent::RecvDivertComplete()
+{
+ LOG(("HttpChannelParent::RecvDivertComplete [this=%p]\n", this));
+
+ MOZ_ASSERT(mParentListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot RecvDivertComplete if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ mEventQ->RunOrEnqueue(new DivertCompleteEvent(this));
+ return true;
+}
+
+void
+HttpChannelParent::DivertComplete()
+{
+ LOG(("HttpChannelParent::DivertComplete [this=%p]\n", this));
+
+ MOZ_ASSERT(mParentListener);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertComplete if diverting is not set!");
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ nsresult rv = ResumeForDiversion();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ mParentListener = nullptr;
+}
+
+void
+HttpChannelParent::MaybeFlushPendingDiversion()
+{
+ if (!mPendingDiversion) {
+ return;
+ }
+
+ mPendingDiversion = false;
+
+ nsresult rv = SuspendForDiversion();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (mDivertListener) {
+ DivertTo(mDivertListener);
+ }
+
+ return;
+}
+
+void
+HttpChannelParent::ResponseSynthesized()
+{
+ // Suspend now even though the FinishSynthesizeResponse runnable has
+ // not executed. We want to suspend after we get far enough to trigger
+ // the synthesis, but not actually allow the nsHttpChannel to trigger
+ // any OnStartRequests().
+ if (mSuspendAfterSynthesizeResponse) {
+ mChannel->Suspend();
+ }
+
+ mWillSynthesizeResponse = false;
+
+ MaybeFlushPendingDiversion();
+}
+
+bool
+HttpChannelParent::RecvRemoveCorsPreflightCacheEntry(const URIParams& uri,
+ const mozilla::ipc::PrincipalInfo& requestingPrincipal)
+{
+ nsCOMPtr<nsIURI> deserializedURI = DeserializeURI(uri);
+ if (!deserializedURI) {
+ return false;
+ }
+ nsCOMPtr<nsIPrincipal> principal =
+ PrincipalInfoToPrincipal(requestingPrincipal);
+ if (!principal) {
+ return false;
+ }
+ nsCORSListenerProxy::RemoveFromCorsPreflightCache(deserializedURI,
+ principal);
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ LOG(("HttpChannelParent::OnStartRequest [this=%p, aRequest=%p]\n",
+ this, aRequest));
+
+ MOZ_RELEASE_ASSERT(!mDivertingFromChild,
+ "Cannot call OnStartRequest if diverting is set!");
+
+ // We can't cast here since the new channel can be a redirect to a different
+ // schema. We must query the channel implementation through a special method.
+ nsHttpChannel *chan = nullptr;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal(do_QueryInterface(aRequest));
+ if (httpChannelInternal) {
+ chan = httpChannelInternal->QueryHttpChannelImpl();
+ }
+
+ if (!chan) {
+ LOG((" aRequest is not nsHttpChannel"));
+ NS_ERROR("Expecting only nsHttpChannel as aRequest in HttpChannelParent::OnStartRequest");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mChannel == chan,
+ "HttpChannelParent getting OnStartRequest from a different nsHttpChannel instance");
+
+ nsHttpResponseHead *responseHead = chan->GetResponseHead();
+ nsHttpRequestHead *requestHead = chan->GetRequestHead();
+ bool isFromCache = false;
+ chan->IsFromCache(&isFromCache);
+ uint32_t expirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
+ chan->GetCacheTokenExpirationTime(&expirationTime);
+ nsCString cachedCharset;
+ chan->GetCacheTokenCachedCharset(cachedCharset);
+
+ bool loadedFromApplicationCache;
+ chan->GetLoadedFromApplicationCache(&loadedFromApplicationCache);
+ if (loadedFromApplicationCache) {
+ mOfflineForeignMarker = chan->GetOfflineCacheEntryAsForeignMarker();
+ nsCOMPtr<nsIApplicationCache> appCache;
+ chan->GetApplicationCache(getter_AddRefs(appCache));
+ nsCString appCacheGroupId;
+ nsCString appCacheClientId;
+ appCache->GetGroupID(appCacheGroupId);
+ appCache->GetClientID(appCacheClientId);
+ if (mIPCClosed ||
+ !SendAssociateApplicationCache(appCacheGroupId, appCacheClientId))
+ {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(aRequest);
+ if (encodedChannel)
+ encodedChannel->SetApplyConversion(false);
+
+ // Keep the cache entry for future use in RecvSetCacheTokenCachedCharset().
+ // It could be already released by nsHttpChannel at that time.
+ nsCOMPtr<nsISupports> cacheEntry;
+ chan->GetCacheToken(getter_AddRefs(cacheEntry));
+ mCacheEntry = do_QueryInterface(cacheEntry);
+
+ nsresult channelStatus = NS_OK;
+ chan->GetStatus(&channelStatus);
+
+ nsCString secInfoSerialization;
+ UpdateAndSerializeSecurityInfo(secInfoSerialization);
+
+ uint16_t redirectCount = 0;
+ chan->GetRedirectCount(&redirectCount);
+
+ nsCOMPtr<nsISupports> cacheKey;
+ chan->GetCacheKey(getter_AddRefs(cacheKey));
+ uint32_t cacheKeyValue = 0;
+ if (cacheKey) {
+ nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(cacheKey);
+ if (!container) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult rv = container->GetData(&cacheKeyValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ nsAutoCString altDataType;
+ chan->GetAlternativeDataType(altDataType);
+
+ // !!! We need to lock headers and please don't forget to unlock them !!!
+ requestHead->Enter();
+ nsresult rv = NS_OK;
+ if (mIPCClosed ||
+ !SendOnStartRequest(channelStatus,
+ responseHead ? *responseHead : nsHttpResponseHead(),
+ !!responseHead,
+ requestHead->Headers(),
+ isFromCache,
+ mCacheEntry ? true : false,
+ expirationTime, cachedCharset, secInfoSerialization,
+ chan->GetSelfAddr(), chan->GetPeerAddr(),
+ redirectCount,
+ cacheKeyValue,
+ altDataType))
+ {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ requestHead->Exit();
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ LOG(("HttpChannelParent::OnStopRequest: [this=%p aRequest=%p status=%x]\n",
+ this, aRequest, aStatusCode));
+
+ MOZ_RELEASE_ASSERT(!mDivertingFromChild,
+ "Cannot call OnStopRequest if diverting is set!");
+ ResourceTimingStruct timing;
+ mChannel->GetDomainLookupStart(&timing.domainLookupStart);
+ mChannel->GetDomainLookupEnd(&timing.domainLookupEnd);
+ mChannel->GetConnectStart(&timing.connectStart);
+ mChannel->GetConnectEnd(&timing.connectEnd);
+ mChannel->GetRequestStart(&timing.requestStart);
+ mChannel->GetResponseStart(&timing.responseStart);
+ mChannel->GetResponseEnd(&timing.responseEnd);
+ mChannel->GetAsyncOpen(&timing.fetchStart);
+ mChannel->GetRedirectStart(&timing.redirectStart);
+ mChannel->GetRedirectEnd(&timing.redirectEnd);
+ mChannel->GetTransferSize(&timing.transferSize);
+ mChannel->GetEncodedBodySize(&timing.encodedBodySize);
+ // decodedBodySize can be computed in the child process so it doesn't need
+ // to be passed down.
+ mChannel->GetProtocolVersion(timing.protocolVersion);
+
+ mChannel->GetCacheReadStart(&timing.cacheReadStart);
+ mChannel->GetCacheReadEnd(&timing.cacheReadEnd);
+
+ if (mIPCClosed || !SendOnStopRequest(aStatusCode, timing))
+ return NS_ERROR_UNEXPECTED;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ LOG(("HttpChannelParent::OnDataAvailable [this=%p aRequest=%p]\n",
+ this, aRequest));
+
+ MOZ_RELEASE_ASSERT(!mDivertingFromChild,
+ "Cannot call OnDataAvailable if diverting is set!");
+
+ nsresult channelStatus = NS_OK;
+ mChannel->GetStatus(&channelStatus);
+
+ static uint32_t const kCopyChunkSize = 128 * 1024;
+ uint32_t toRead = std::min<uint32_t>(aCount, kCopyChunkSize);
+
+ nsCString data;
+ if (!data.SetCapacity(toRead, fallible)) {
+ LOG((" out of memory!"));
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ while (aCount) {
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, toRead);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // OnDataAvailable is always preceded by OnStatus/OnProgress calls that set
+ // mStoredStatus/mStoredProgress(Max) to appropriate values, unless
+ // LOAD_BACKGROUND set. In that case, they'll have garbage values, but
+ // child doesn't use them.
+ if (mIPCClosed || !SendOnTransportAndData(channelStatus, mStoredStatus,
+ mStoredProgress, mStoredProgressMax,
+ aOffset, toRead, data)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ aOffset += toRead;
+ aCount -= toRead;
+ toRead = std::min<uint32_t>(aCount, kCopyChunkSize);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIProgressEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnProgress(nsIRequest *aRequest,
+ nsISupports *aContext,
+ int64_t aProgress,
+ int64_t aProgressMax)
+{
+ // OnStatus has always just set mStoredStatus. If it indicates this precedes
+ // OnDataAvailable, store and ODA will send to child.
+ if (mStoredStatus == NS_NET_STATUS_RECEIVING_FROM ||
+ mStoredStatus == NS_NET_STATUS_READING)
+ {
+ mStoredProgress = aProgress;
+ mStoredProgressMax = aProgressMax;
+ } else {
+ // Send OnProgress events to the child for data upload progress notifications
+ // (i.e. status == NS_NET_STATUS_SENDING_TO) or if the channel has
+ // LOAD_BACKGROUND set.
+ if (mIPCClosed || !SendOnProgress(aProgress, aProgressMax))
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::OnStatus(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus,
+ const char16_t *aStatusArg)
+{
+ // If this precedes OnDataAvailable, store and ODA will send to child.
+ if (aStatus == NS_NET_STATUS_RECEIVING_FROM ||
+ aStatus == NS_NET_STATUS_READING)
+ {
+ mStoredStatus = aStatus;
+ return NS_OK;
+ }
+ // Otherwise, send to child now
+ if (mIPCClosed || !SendOnStatus(aStatus))
+ return NS_ERROR_UNEXPECTED;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIParentChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::SetParentListener(HttpChannelParentListener* aListener)
+{
+ LOG(("HttpChannelParent::SetParentListener [this=%p aListener=%p]\n",
+ this, aListener));
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(!mParentListener, "SetParentListener should only be called for "
+ "new HttpChannelParents after a redirect, when "
+ "mParentListener is null.");
+ mParentListener = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::NotifyTrackingProtectionDisabled()
+{
+ if (!mIPCClosed)
+ Unused << SendNotifyTrackingProtectionDisabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::Delete()
+{
+ if (!mIPCClosed)
+ Unused << DoSendDeleteSelf();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::nsIParentRedirectingChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::StartRedirect(uint32_t registrarId,
+ nsIChannel* newChannel,
+ uint32_t redirectFlags,
+ nsIAsyncVerifyRedirectCallback* callback)
+{
+ LOG(("HttpChannelParent::StartRedirect [this=%p, registrarId=%lu "
+ "newChannel=%p callback=%p]\n", this, registrarId, newChannel,
+ callback));
+
+ if (mIPCClosed)
+ return NS_BINDING_ABORTED;
+
+ nsCOMPtr<nsIURI> newURI;
+ newChannel->GetURI(getter_AddRefs(newURI));
+
+ URIParams uriParams;
+ SerializeURI(newURI, uriParams);
+
+ nsCString secInfoSerialization;
+ UpdateAndSerializeSecurityInfo(secInfoSerialization);
+
+ // If the channel is a HTTP channel, we also want to inform the child
+ // about the parent's channelId attribute, so that both parent and child
+ // share the same ID. Useful for monitoring channel activity in devtools.
+ nsAutoCString channelId;
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+ if (httpChannel) {
+ nsresult rv = httpChannel->GetChannelId(channelId);
+ NS_ENSURE_SUCCESS(rv, NS_BINDING_ABORTED);
+ }
+
+ nsHttpResponseHead *responseHead = mChannel->GetResponseHead();
+ bool result = false;
+ if (!mIPCClosed) {
+ result = SendRedirect1Begin(registrarId, uriParams, redirectFlags,
+ responseHead ? *responseHead
+ : nsHttpResponseHead(),
+ secInfoSerialization,
+ channelId);
+ }
+ if (!result) {
+ // Bug 621446 investigation
+ mSentRedirect1BeginFailed = true;
+ return NS_BINDING_ABORTED;
+ }
+
+ // Bug 621446 investigation
+ mSentRedirect1Begin = true;
+
+ // Result is handled in RecvRedirect2Verify above
+
+ mRedirectChannel = newChannel;
+ mRedirectCallback = callback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::CompleteRedirect(bool succeeded)
+{
+ LOG(("HttpChannelParent::CompleteRedirect [this=%p succeeded=%d]\n",
+ this, succeeded));
+
+ if (succeeded && !mIPCClosed) {
+ // TODO: check return value: assume child dead if failed
+ Unused << SendRedirect3Complete();
+ }
+
+ mRedirectChannel = nullptr;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParent::ADivertableParentChannel
+//-----------------------------------------------------------------------------
+nsresult
+HttpChannelParent::SuspendForDiversion()
+{
+ LOG(("HttpChannelParent::SuspendForDiversion [this=%p]\n", this));
+ MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mParentListener);
+
+ // If we're in the process of opening a synthesized response, we must wait
+ // to perform the diversion. Some of our diversion listeners clear callbacks
+ // which breaks the synthesis process.
+ if (mWillSynthesizeResponse) {
+ mPendingDiversion = true;
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(mDivertingFromChild)) {
+ MOZ_ASSERT(!mDivertingFromChild, "Already suspended for diversion!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // MessageDiversionStarted call will suspend mEventQ as many times as the
+ // channel has been suspended, so that channel and this queue are in sync.
+ mChannel->MessageDiversionStarted(this);
+
+ nsresult rv = NS_OK;
+
+ // Try suspending the channel. Allow it to fail, since OnStopRequest may have
+ // been called and thus the channel may not be pending. If we've already
+ // automatically suspended after synthesizing the response, then we don't
+ // need to suspend again here.
+ if (!mSuspendAfterSynthesizeResponse) {
+ // We need to suspend only nsHttpChannel (i.e. we should not suspend
+ // mEventQ). Therefore we call mChannel->SuspendInternal() and not
+ // mChannel->Suspend().
+ // We are suspending only nsHttpChannel here because we want to stop
+ // OnDataAvailable until diversion is over. At the same time we should
+ // send the diverted OnDataAvailable-s to the listeners and not queue them
+ // in mEventQ.
+ rv = mChannel->SuspendInternal();
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE);
+ mSuspendedForDiversion = NS_SUCCEEDED(rv);
+ } else {
+ // Take ownership of the automatic suspend that occurred after synthesizing
+ // the response.
+ mSuspendedForDiversion = true;
+
+ // If mSuspendAfterSynthesizeResponse is true channel has been already
+ // suspended. From comment above mSuspendedForDiversion takes the ownership
+ // of this suspend, therefore mEventQ should not be suspened so we need to
+ // resume it once.
+ mEventQ->Resume();
+ }
+
+ rv = mParentListener->SuspendForDiversion();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Once this is set, no more OnStart/OnData/OnStop callbacks should be sent
+ // to the child.
+ mDivertingFromChild = true;
+
+ return NS_OK;
+}
+
+nsresult
+HttpChannelParent::SuspendMessageDiversion()
+{
+ LOG(("HttpChannelParent::SuspendMessageDiversion [this=%p]", this));
+ // This only needs to suspend message queue.
+ mEventQ->Suspend();
+ return NS_OK;
+}
+
+nsresult
+HttpChannelParent::ResumeMessageDiversion()
+{
+ LOG(("HttpChannelParent::SuspendMessageDiversion [this=%p]", this));
+ // This only needs to resumes message queue.
+ mEventQ->Resume();
+ return NS_OK;
+}
+
+/* private, supporting function for ADivertableParentChannel */
+nsresult
+HttpChannelParent::ResumeForDiversion()
+{
+ LOG(("HttpChannelParent::ResumeForDiversion [this=%p]\n", this));
+ MOZ_ASSERT(mChannel);
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot ResumeForDiversion if not diverting!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mChannel->MessageDiversionStop();
+
+ if (mSuspendedForDiversion) {
+ // The nsHttpChannel will deliver remaining OnData/OnStop for the transfer.
+ nsresult rv = mChannel->ResumeInternal();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailDiversion(NS_ERROR_UNEXPECTED, true);
+ return rv;
+ }
+ mSuspendedForDiversion = false;
+ }
+
+ if (NS_WARN_IF(mIPCClosed || !DoSendDeleteSelf())) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+void
+HttpChannelParent::DivertTo(nsIStreamListener *aListener)
+{
+ LOG(("HttpChannelParent::DivertTo [this=%p aListener=%p]\n",
+ this, aListener));
+ MOZ_ASSERT(mParentListener);
+
+ // If we're in the process of opening a synthesized response, we must wait
+ // to perform the diversion. Some of our diversion listeners clear callbacks
+ // which breaks the synthesis process.
+ if (mWillSynthesizeResponse) {
+ // We should already have started pending the diversion when
+ // SuspendForDiversion() was called.
+ MOZ_ASSERT(mPendingDiversion);
+ mDivertListener = aListener;
+ return;
+ }
+
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot DivertTo new listener if diverting is not set!");
+ return;
+ }
+
+ mDivertListener = aListener;
+
+ // Call OnStartRequest and SendDivertMessages asynchronously to avoid
+ // reentering client context.
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod(this, &HttpChannelParent::StartDiversion));
+ return;
+}
+
+void
+HttpChannelParent::StartDiversion()
+{
+ LOG(("HttpChannelParent::StartDiversion [this=%p]\n", this));
+ if (NS_WARN_IF(!mDivertingFromChild)) {
+ MOZ_ASSERT(mDivertingFromChild,
+ "Cannot StartDiversion if diverting is not set!");
+ return;
+ }
+
+ // Fake pending status in case OnStopRequest has already been called.
+ if (mChannel) {
+ mChannel->ForcePending(true);
+ }
+
+ {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ // Call OnStartRequest for the "DivertTo" listener.
+ nsresult rv = mDivertListener->OnStartRequest(mChannel, nullptr);
+ if (NS_FAILED(rv)) {
+ if (mChannel) {
+ mChannel->Cancel(rv);
+ }
+ mStatus = rv;
+ }
+ }
+ mDivertedOnStartRequest = true;
+
+ // After OnStartRequest has been called, setup content decoders if needed.
+ //
+ // Create a content conversion chain based on mDivertListener and update
+ // mDivertListener.
+ nsCOMPtr<nsIStreamListener> converterListener;
+ mChannel->DoApplyContentConversions(mDivertListener,
+ getter_AddRefs(converterListener));
+ if (converterListener) {
+ mDivertListener = converterListener.forget();
+ }
+
+ // Now mParentListener can be diverted to mDivertListener.
+ DebugOnly<nsresult> rvdbg = mParentListener->DivertTo(mDivertListener);
+ MOZ_ASSERT(NS_SUCCEEDED(rvdbg));
+ mDivertListener = nullptr;
+
+ if (NS_WARN_IF(mIPCClosed || !SendFlushedForDiversion())) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // The listener chain should now be setup; tell HttpChannelChild to divert
+ // the OnDataAvailables and OnStopRequest to this HttpChannelParent.
+ if (NS_WARN_IF(mIPCClosed || !SendDivertMessages())) {
+ FailDiversion(NS_ERROR_UNEXPECTED);
+ return;
+ }
+}
+
+class HTTPFailDiversionEvent : public Runnable
+{
+public:
+ HTTPFailDiversionEvent(HttpChannelParent *aChannelParent,
+ nsresult aErrorCode,
+ bool aSkipResume)
+ : mChannelParent(aChannelParent)
+ , mErrorCode(aErrorCode)
+ , mSkipResume(aSkipResume)
+ {
+ MOZ_RELEASE_ASSERT(aChannelParent);
+ MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+ }
+ NS_IMETHOD Run() override
+ {
+ mChannelParent->NotifyDiversionFailed(mErrorCode, mSkipResume);
+ return NS_OK;
+ }
+private:
+ RefPtr<HttpChannelParent> mChannelParent;
+ nsresult mErrorCode;
+ bool mSkipResume;
+};
+
+void
+HttpChannelParent::FailDiversion(nsresult aErrorCode,
+ bool aSkipResume)
+{
+ MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+ MOZ_RELEASE_ASSERT(mDivertingFromChild);
+ MOZ_RELEASE_ASSERT(mParentListener);
+ MOZ_RELEASE_ASSERT(mChannel);
+
+ NS_DispatchToCurrentThread(
+ new HTTPFailDiversionEvent(this, aErrorCode, aSkipResume));
+}
+
+void
+HttpChannelParent::NotifyDiversionFailed(nsresult aErrorCode,
+ bool aSkipResume)
+{
+ LOG(("HttpChannelParent::NotifyDiversionFailed [this=%p aErrorCode=%x]\n",
+ this, aErrorCode));
+ MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+ MOZ_RELEASE_ASSERT(mDivertingFromChild);
+ MOZ_RELEASE_ASSERT(mParentListener);
+ MOZ_RELEASE_ASSERT(mChannel);
+
+ mChannel->Cancel(aErrorCode);
+
+ mChannel->ForcePending(false);
+
+ bool isPending = false;
+ nsresult rv = mChannel->IsPending(&isPending);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+
+ // Resume only if we suspended earlier.
+ if (mSuspendedForDiversion) {
+ mChannel->ResumeInternal();
+ }
+ // Channel has already sent OnStartRequest to the child, so ensure that we
+ // call it here if it hasn't already been called.
+ if (!mDivertedOnStartRequest) {
+ mChannel->ForcePending(true);
+ mParentListener->OnStartRequest(mChannel, nullptr);
+ mChannel->ForcePending(false);
+ }
+ // If the channel is pending, it will call OnStopRequest itself; otherwise, do
+ // it here.
+ if (!isPending) {
+ mParentListener->OnStopRequest(mChannel, nullptr, aErrorCode);
+ }
+ mParentListener = nullptr;
+ mChannel = nullptr;
+
+ if (!mIPCClosed) {
+ Unused << DoSendDeleteSelf();
+ }
+}
+
+nsresult
+HttpChannelParent::OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval)
+{
+ // We need to make sure the child does not call SendDocumentChannelCleanup()
+ // before opening the altOutputStream, because that clears mCacheEntry.
+ if (!mCacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mCacheEntry->OpenAlternativeOutputStream(type, _retval);
+}
+
+NS_IMETHODIMP
+HttpChannelParent::GetAuthPrompt(uint32_t aPromptReason, const nsIID& iid,
+ void** aResult)
+{
+ nsCOMPtr<nsIAuthPrompt2> prompt =
+ new NeckoParent::NestedFrameAuthPrompt(Manager(), mNestedFrameId);
+ prompt.forget(aResult);
+ return NS_OK;
+}
+
+void
+HttpChannelParent::UpdateAndSerializeSecurityInfo(nsACString& aSerializedSecurityInfoOut)
+{
+ nsCOMPtr<nsISupports> secInfoSupp;
+ mChannel->GetSecurityInfo(getter_AddRefs(secInfoSupp));
+ if (secInfoSupp) {
+ mAssociatedContentSecurity = do_QueryInterface(secInfoSupp);
+ nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(secInfoSupp);
+ if (secInfoSer) {
+ NS_SerializeToString(secInfoSer, aSerializedSecurityInfoOut);
+ }
+ }
+}
+
+bool
+HttpChannelParent::DoSendDeleteSelf()
+{
+ bool rv = SendDeleteSelf();
+ mIPCClosed = true;
+ return rv;
+}
+
+bool
+HttpChannelParent::RecvDeletingChannel()
+{
+ // We need to ensure that the parent channel will not be sending any more IPC
+ // messages after this, as the child is going away. DoSendDeleteSelf will
+ // set mIPCClosed = true;
+ return DoSendDeleteSelf();
+}
+
+bool
+HttpChannelParent::RecvFinishInterceptedRedirect()
+{
+ // We make sure not to send any more messages until the IPC channel is torn
+ // down by the child.
+ mIPCClosed = true;
+ return SendFinishInterceptedRedirect();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelSecurityWarningReporter
+//-----------------------------------------------------------------------------
+
+nsresult
+HttpChannelParent::ReportSecurityMessage(const nsAString& aMessageTag,
+ const nsAString& aMessageCategory)
+{
+ if (mIPCClosed ||
+ NS_WARN_IF(!SendReportSecurityMessage(nsString(aMessageTag),
+ nsString(aMessageCategory)))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::IssueWarning(uint32_t aWarning, bool aAsError)
+{
+ Unused << SendIssueDeprecationWarning(aWarning, aAsError);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h
new file mode 100644
index 000000000..51fae5a82
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -0,0 +1,269 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_HttpChannelParent_h
+#define mozilla_net_HttpChannelParent_h
+
+#include "ADivertableParentChannel.h"
+#include "nsHttp.h"
+#include "mozilla/net/PHttpChannelParent.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsIObserver.h"
+#include "nsIParentRedirectingChannel.h"
+#include "nsIProgressEventSink.h"
+#include "nsHttpChannel.h"
+#include "nsIAuthPromptProvider.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "nsIDeprecationWarner.h"
+
+class nsICacheEntry;
+class nsIAssociatedContentSecurity;
+
+#define HTTP_CHANNEL_PARENT_IID \
+ { 0x982b2372, 0x7aa5, 0x4e8a, \
+ { 0xbd, 0x9f, 0x89, 0x74, 0xd7, 0xf0, 0x58, 0xeb } }
+
+namespace mozilla {
+
+namespace dom{
+class TabParent;
+class PBrowserOrId;
+} // namespace dom
+
+namespace net {
+
+class HttpChannelParentListener;
+class ChannelEventQueue;
+
+// Note: nsIInterfaceRequestor must be the first base so that do_QueryObject()
+// works correctly on this object, as it's needed to compute a void* pointing to
+// the beginning of this object.
+
+class HttpChannelParent final : public nsIInterfaceRequestor
+ , public PHttpChannelParent
+ , public nsIParentRedirectingChannel
+ , public nsIProgressEventSink
+ , public ADivertableParentChannel
+ , public nsIAuthPromptProvider
+ , public nsIDeprecationWarner
+ , public HttpChannelSecurityWarningReporter
+{
+ virtual ~HttpChannelParent();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIPARENTREDIRECTINGCHANNEL
+ NS_DECL_NSIPROGRESSEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIAUTHPROMPTPROVIDER
+ NS_DECL_NSIDEPRECATIONWARNER
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_CHANNEL_PARENT_IID)
+
+ HttpChannelParent(const dom::PBrowserOrId& iframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aStatus);
+
+ bool Init(const HttpChannelCreationArgs& aOpenArgs);
+
+ // ADivertableParentChannel functions.
+ void DivertTo(nsIStreamListener *aListener) override;
+ nsresult SuspendForDiversion() override;
+ nsresult SuspendMessageDiversion() override;
+ nsresult ResumeMessageDiversion() override;
+
+ // Calls OnStartRequest for "DivertTo" listener, then notifies child channel
+ // that it should divert OnDataAvailable and OnStopRequest calls to this
+ // parent channel.
+ void StartDiversion();
+
+ // Handles calling OnStart/Stop if there are errors during diversion.
+ // Called asynchronously from FailDiversion.
+ void NotifyDiversionFailed(nsresult aErrorCode, bool aSkipResume = true);
+
+ // Forwarded to nsHttpChannel::SetApplyConversion.
+ void SetApplyConversion(bool aApplyConversion) {
+ if (mChannel) {
+ mChannel->SetApplyConversion(aApplyConversion);
+ }
+ }
+
+ nsresult OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval);
+
+ void InvokeAsyncOpen(nsresult rv);
+protected:
+ // used to connect redirected-to channel in parent with just created
+ // ChildChannel. Used during redirects.
+ bool ConnectChannel(const uint32_t& channelId, const bool& shouldIntercept);
+
+ bool DoAsyncOpen(const URIParams& uri,
+ const OptionalURIParams& originalUri,
+ const OptionalURIParams& docUri,
+ const OptionalURIParams& referrerUri,
+ const uint32_t& referrerPolicy,
+ const OptionalURIParams& internalRedirectUri,
+ const OptionalURIParams& topWindowUri,
+ const uint32_t& loadFlags,
+ const RequestHeaderTuples& requestHeaders,
+ const nsCString& requestMethod,
+ const OptionalIPCStream& uploadStream,
+ const bool& uploadStreamHasHeaders,
+ const uint16_t& priority,
+ const uint32_t& classOfService,
+ const uint8_t& redirectionLimit,
+ const bool& allowPipelining,
+ const bool& allowSTS,
+ const uint32_t& thirdPartyFlags,
+ const bool& doResumeAt,
+ const uint64_t& startPos,
+ const nsCString& entityID,
+ const bool& chooseApplicationCache,
+ const nsCString& appCacheClientID,
+ const bool& allowSpdy,
+ const bool& allowAltSvc,
+ const bool& beConservative,
+ const OptionalLoadInfoArgs& aLoadInfoArgs,
+ const OptionalHttpResponseHead& aSynthesizedResponseHead,
+ const nsCString& aSecurityInfoSerialization,
+ const uint32_t& aCacheKey,
+ const nsCString& aRequestContextID,
+ const OptionalCorsPreflightArgs& aCorsPreflightArgs,
+ const uint32_t& aInitialRwin,
+ const bool& aBlockAuthPrompt,
+ const bool& aSuspendAfterSynthesizeResponse,
+ const bool& aAllowStaleCacheContent,
+ const nsCString& aContentTypeHint,
+ const nsCString& aChannelId,
+ const uint64_t& aContentWindowId,
+ const nsCString& aPreferredAlternativeType);
+
+ virtual bool RecvSetPriority(const uint16_t& priority) override;
+ virtual bool RecvSetClassOfService(const uint32_t& cos) override;
+ virtual bool RecvSetCacheTokenCachedCharset(const nsCString& charset) override;
+ virtual bool RecvSuspend() override;
+ virtual bool RecvResume() override;
+ virtual bool RecvCancel(const nsresult& status) override;
+ virtual bool RecvRedirect2Verify(const nsresult& result,
+ const RequestHeaderTuples& changedHeaders,
+ const uint32_t& loadFlags,
+ const OptionalURIParams& apiRedirectUri,
+ const OptionalCorsPreflightArgs& aCorsPreflightArgs,
+ const bool& aForceHSTSPriming,
+ const bool& aMixedContentWouldBlock,
+ const bool& aChooseAppcache) override;
+ virtual bool RecvUpdateAssociatedContentSecurity(const int32_t& broken,
+ const int32_t& no) override;
+ virtual bool RecvDocumentChannelCleanup() override;
+ virtual bool RecvMarkOfflineCacheEntryAsForeign() override;
+ virtual bool RecvDivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count) override;
+ virtual bool RecvDivertOnStopRequest(const nsresult& statusCode) override;
+ virtual bool RecvDivertComplete() override;
+ virtual bool RecvRemoveCorsPreflightCacheEntry(const URIParams& uri,
+ const mozilla::ipc::PrincipalInfo& requestingPrincipal) override;
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ // Supporting function for ADivertableParentChannel.
+ nsresult ResumeForDiversion();
+
+ // Asynchronously calls NotifyDiversionFailed.
+ void FailDiversion(nsresult aErrorCode, bool aSkipResume = true);
+
+ friend class HttpChannelParentListener;
+ RefPtr<mozilla::dom::TabParent> mTabParent;
+
+ nsresult ReportSecurityMessage(const nsAString& aMessageTag,
+ const nsAString& aMessageCategory) override;
+
+ // Calls SendDeleteSelf and sets mIPCClosed to true because we should not
+ // send any more messages after that. Bug 1274886
+ bool DoSendDeleteSelf();
+ // Called to notify the parent channel to not send any more IPC messages.
+ virtual bool RecvDeletingChannel() override;
+ virtual bool RecvFinishInterceptedRedirect() override;
+
+private:
+ void UpdateAndSerializeSecurityInfo(nsACString& aSerializedSecurityInfoOut);
+
+ void DivertOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count);
+ void DivertOnStopRequest(const nsresult& statusCode);
+ void DivertComplete();
+ void MaybeFlushPendingDiversion();
+ void ResponseSynthesized();
+
+ friend class DivertDataAvailableEvent;
+ friend class DivertStopRequestEvent;
+ friend class DivertCompleteEvent;
+
+ RefPtr<nsHttpChannel> mChannel;
+ nsCOMPtr<nsICacheEntry> mCacheEntry;
+ nsCOMPtr<nsIAssociatedContentSecurity> mAssociatedContentSecurity;
+ bool mIPCClosed; // PHttpChannel actor has been Closed()
+
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+
+ nsAutoPtr<class nsHttpChannel::OfflineCacheEntryAsForeignMarker> mOfflineForeignMarker;
+
+ // state for combining OnStatus/OnProgress with OnDataAvailable
+ // into one IPDL call to child.
+ nsresult mStoredStatus;
+ int64_t mStoredProgress;
+ int64_t mStoredProgressMax;
+
+ bool mSentRedirect1Begin : 1;
+ bool mSentRedirect1BeginFailed : 1;
+ bool mReceivedRedirect2Verify : 1;
+
+ PBOverrideStatus mPBOverride;
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ RefPtr<nsHttpHandler> mHttpHandler;
+
+ RefPtr<HttpChannelParentListener> mParentListener;
+ // The listener we are diverting to or will divert to if mPendingDiversion
+ // is set.
+ nsCOMPtr<nsIStreamListener> mDivertListener;
+ // Set to the canceled status value if the main channel was canceled.
+ nsresult mStatus;
+ // Indicates that diversion has been requested, but we could not start it
+ // yet because the channel is still being opened with a synthesized response.
+ bool mPendingDiversion;
+ // Once set, no OnStart/OnData/OnStop calls should be accepted; conversely, it
+ // must be set when RecvDivertOnData/~DivertOnStop/~DivertComplete are
+ // received from the child channel.
+ bool mDivertingFromChild;
+
+ // Set if OnStart|StopRequest was called during a diversion from the child.
+ bool mDivertedOnStartRequest;
+
+ bool mSuspendedForDiversion;
+
+ // Set if this channel should be suspended after synthesizing a response.
+ bool mSuspendAfterSynthesizeResponse;
+ // Set if this channel will synthesize its response.
+ bool mWillSynthesizeResponse;
+
+ dom::TabId mNestedFrameId;
+
+ RefPtr<ChannelEventQueue> mEventQ;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpChannelParent,
+ HTTP_CHANNEL_PARENT_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpChannelParent_h
diff --git a/netwerk/protocol/http/HttpChannelParentListener.cpp b/netwerk/protocol/http/HttpChannelParentListener.cpp
new file mode 100644
index 000000000..59030cf99
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParentListener.cpp
@@ -0,0 +1,407 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "HttpChannelParentListener.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/Unused.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIHttpEventSink.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIRedirectChannelRegistrar.h"
+#include "nsIPromptFactory.h"
+#include "nsQueryObject.h"
+
+using mozilla::Unused;
+
+namespace mozilla {
+namespace net {
+
+HttpChannelParentListener::HttpChannelParentListener(HttpChannelParent* aInitialChannel)
+ : mNextListener(aInitialChannel)
+ , mRedirectChannelId(0)
+ , mSuspendedForDiversion(false)
+ , mShouldIntercept(false)
+ , mShouldSuspendIntercept(false)
+{
+}
+
+HttpChannelParentListener::~HttpChannelParentListener()
+{
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpChannelParentListener)
+NS_IMPL_RELEASE(HttpChannelParentListener)
+NS_INTERFACE_MAP_BEGIN(HttpChannelParentListener)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIRedirectResultListener)
+ NS_INTERFACE_MAP_ENTRY(nsINetworkInterceptController)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
+ if (aIID.Equals(NS_GET_IID(HttpChannelParentListener))) {
+ foundInterface = static_cast<nsIInterfaceRequestor*>(this);
+ } else
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParentListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
+ "Cannot call OnStartRequest if suspended for diversion!");
+
+ if (!mNextListener)
+ return NS_ERROR_UNEXPECTED;
+
+ LOG(("HttpChannelParentListener::OnStartRequest [this=%p]\n", this));
+ return mNextListener->OnStartRequest(aRequest, aContext);
+}
+
+NS_IMETHODIMP
+HttpChannelParentListener::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
+ "Cannot call OnStopRequest if suspended for diversion!");
+
+ if (!mNextListener)
+ return NS_ERROR_UNEXPECTED;
+
+ LOG(("HttpChannelParentListener::OnStopRequest: [this=%p status=%ul]\n",
+ this, aStatusCode));
+ nsresult rv = mNextListener->OnStopRequest(aRequest, aContext, aStatusCode);
+
+ mNextListener = nullptr;
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParentListener::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
+ "Cannot call OnDataAvailable if suspended for diversion!");
+
+ if (!mNextListener)
+ return NS_ERROR_UNEXPECTED;
+
+ LOG(("HttpChannelParentListener::OnDataAvailable [this=%p]\n", this));
+ return mNextListener->OnDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParentListener::GetInterface(const nsIID& aIID, void **result)
+{
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)) ||
+ aIID.Equals(NS_GET_IID(nsIHttpEventSink)) ||
+ aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) ||
+ aIID.Equals(NS_GET_IID(nsIRedirectResultListener)))
+ {
+ return QueryInterface(aIID, result);
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> ir;
+ if (mNextListener &&
+ NS_SUCCEEDED(CallQueryInterface(mNextListener.get(),
+ getter_AddRefs(ir))))
+ {
+ return ir->GetInterface(aIID, result);
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return wwatch->GetPrompt(nullptr, aIID,
+ reinterpret_cast<void**>(result));
+ }
+
+ return NS_NOINTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParentListener::AsyncOnChannelRedirect(
+ nsIChannel *oldChannel,
+ nsIChannel *newChannel,
+ uint32_t redirectFlags,
+ nsIAsyncVerifyRedirectCallback* callback)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIParentRedirectingChannel> activeRedirectingChannel =
+ do_QueryInterface(mNextListener);
+ if (!activeRedirectingChannel) {
+ NS_ERROR("Channel got a redirect response, but doesn't implement "
+ "nsIParentRedirectingChannel to handle it.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Register the new channel and obtain id for it
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = registrar->RegisterChannel(newChannel, &mRedirectChannelId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("Registered %p channel under id=%d", newChannel, mRedirectChannelId));
+
+ return activeRedirectingChannel->StartRedirect(mRedirectChannelId,
+ newChannel,
+ redirectFlags,
+ callback);
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsIRedirectResultListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParentListener::OnRedirectResult(bool succeeded)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIParentChannel> redirectChannel;
+ if (mRedirectChannelId) {
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = registrar->GetParentChannel(mRedirectChannelId,
+ getter_AddRefs(redirectChannel));
+ if (NS_FAILED(rv) || !redirectChannel) {
+ // Redirect might get canceled before we got AsyncOnChannelRedirect
+ LOG(("Registered parent channel not found under id=%d", mRedirectChannelId));
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = registrar->GetRegisteredChannel(mRedirectChannelId,
+ getter_AddRefs(newChannel));
+ MOZ_ASSERT(newChannel, "Already registered channel not found");
+
+ if (NS_SUCCEEDED(rv))
+ newChannel->Cancel(NS_BINDING_ABORTED);
+ }
+
+ // Release all previously registered channels, they are no longer need to be
+ // kept in the registrar from this moment.
+ registrar->DeregisterChannels(mRedirectChannelId);
+
+ mRedirectChannelId = 0;
+ }
+
+ if (!redirectChannel) {
+ succeeded = false;
+ }
+
+ nsCOMPtr<nsIParentRedirectingChannel> activeRedirectingChannel =
+ do_QueryInterface(mNextListener);
+ MOZ_ASSERT(activeRedirectingChannel,
+ "Channel finished a redirect response, but doesn't implement "
+ "nsIParentRedirectingChannel to complete it.");
+
+ if (activeRedirectingChannel) {
+ activeRedirectingChannel->CompleteRedirect(succeeded);
+ } else {
+ succeeded = false;
+ }
+
+ if (succeeded) {
+ // Switch to redirect channel and delete the old one.
+ nsCOMPtr<nsIParentChannel> parent;
+ parent = do_QueryInterface(mNextListener);
+ MOZ_ASSERT(parent);
+ parent->Delete();
+ mNextListener = do_QueryInterface(redirectChannel);
+ MOZ_ASSERT(mNextListener);
+ redirectChannel->SetParentListener(this);
+ } else if (redirectChannel) {
+ // Delete the redirect target channel: continue using old channel
+ redirectChannel->Delete();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsINetworkInterceptController
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParentListener::ShouldPrepareForIntercept(nsIURI* aURI,
+ bool aIsNonSubresourceRequest,
+ bool* aShouldIntercept)
+{
+ *aShouldIntercept = mShouldIntercept;
+ return NS_OK;
+}
+
+class HeaderVisitor final : public nsIHttpHeaderVisitor
+{
+ nsCOMPtr<nsIInterceptedChannel> mChannel;
+ ~HeaderVisitor()
+ {
+ }
+public:
+ explicit HeaderVisitor(nsIInterceptedChannel* aChannel) : mChannel(aChannel)
+ {
+ }
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD VisitHeader(const nsACString& aHeader, const nsACString& aValue) override
+ {
+ mChannel->SynthesizeHeader(aHeader, aValue);
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(HeaderVisitor, nsIHttpHeaderVisitor)
+
+class FinishSynthesizedResponse : public Runnable
+{
+ nsCOMPtr<nsIInterceptedChannel> mChannel;
+public:
+ explicit FinishSynthesizedResponse(nsIInterceptedChannel* aChannel)
+ : mChannel(aChannel)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ // The URL passed as an argument here doesn't matter, since the child will
+ // receive a redirection notification as a result of this synthesized response.
+ mChannel->FinishSynthesizedResponse(EmptyCString());
+ return NS_OK;
+ }
+};
+
+NS_IMETHODIMP
+HttpChannelParentListener::ChannelIntercepted(nsIInterceptedChannel* aChannel)
+{
+ if (mShouldSuspendIntercept) {
+ mInterceptedChannel = aChannel;
+ return NS_OK;
+ }
+
+ nsAutoCString statusText;
+ mSynthesizedResponseHead->StatusText(statusText);
+ aChannel->SynthesizeStatus(mSynthesizedResponseHead->Status(), statusText);
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor = new HeaderVisitor(aChannel);
+ mSynthesizedResponseHead->VisitHeaders(visitor,
+ nsHttpHeaderArray::eFilterResponse);
+
+ nsCOMPtr<nsIRunnable> event = new FinishSynthesizedResponse(aChannel);
+ NS_DispatchToCurrentThread(event);
+
+ mSynthesizedResponseHead = nullptr;
+
+ MOZ_ASSERT(mNextListener);
+ RefPtr<HttpChannelParent> channel = do_QueryObject(mNextListener);
+ MOZ_ASSERT(channel);
+ channel->ResponseSynthesized();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult
+HttpChannelParentListener::SuspendForDiversion()
+{
+ if (NS_WARN_IF(mSuspendedForDiversion)) {
+ MOZ_ASSERT(!mSuspendedForDiversion, "Cannot SuspendForDiversion twice!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // While this is set, no OnStart/OnData/OnStop callbacks should be forwarded
+ // to mNextListener.
+ mSuspendedForDiversion = true;
+
+ return NS_OK;
+}
+
+nsresult
+HttpChannelParentListener::ResumeForDiversion()
+{
+ MOZ_RELEASE_ASSERT(mSuspendedForDiversion, "Must already be suspended!");
+
+ // Allow OnStart/OnData/OnStop callbacks to be forwarded to mNextListener.
+ mSuspendedForDiversion = false;
+
+ return NS_OK;
+}
+
+nsresult
+HttpChannelParentListener::DivertTo(nsIStreamListener* aListener)
+{
+ MOZ_ASSERT(aListener);
+ MOZ_RELEASE_ASSERT(mSuspendedForDiversion, "Must already be suspended!");
+
+ mNextListener = aListener;
+
+ return ResumeForDiversion();
+}
+
+void
+HttpChannelParentListener::SetupInterception(const nsHttpResponseHead& aResponseHead)
+{
+ mSynthesizedResponseHead = new nsHttpResponseHead(aResponseHead);
+ mShouldIntercept = true;
+}
+
+void
+HttpChannelParentListener::SetupInterceptionAfterRedirect(bool aShouldIntercept)
+{
+ mShouldIntercept = aShouldIntercept;
+ if (mShouldIntercept) {
+ // When an interception occurs, this channel should suspend all further activity.
+ // It will be torn down and recreated if necessary.
+ mShouldSuspendIntercept = true;
+ }
+}
+
+void
+HttpChannelParentListener::ClearInterceptedChannel()
+{
+ if (mInterceptedChannel) {
+ mInterceptedChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED);
+ mInterceptedChannel = nullptr;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpChannelParentListener.h b/netwerk/protocol/http/HttpChannelParentListener.h
new file mode 100644
index 000000000..de90f4c74
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParentListener.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_HttpChannelCallbackWrapper_h
+#define mozilla_net_HttpChannelCallbackWrapper_h
+
+#include "nsIInterfaceRequestor.h"
+#include "nsIChannelEventSink.h"
+#include "nsIRedirectResultListener.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+class HttpChannelParent;
+
+#define HTTP_CHANNEL_PARENT_LISTENER_IID \
+ { 0xe409da52, 0xda76, 0x4eb7, \
+ { 0xa7, 0xf4, 0x03, 0x3d, 0x88, 0xac, 0x87, 0x6d } }
+
+// Note: nsIInterfaceRequestor must be the first base so that do_QueryObject()
+// works correctly on this object, as it's needed to compute a void* pointing to
+// the beginning of this object.
+
+class HttpChannelParentListener final : public nsIInterfaceRequestor
+ , public nsIChannelEventSink
+ , public nsIRedirectResultListener
+ , public nsIStreamListener
+ , public nsINetworkInterceptController
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIREDIRECTRESULTLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSINETWORKINTERCEPTCONTROLLER
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_CHANNEL_PARENT_LISTENER_IID)
+
+ explicit HttpChannelParentListener(HttpChannelParent* aInitialChannel);
+
+ // For channel diversion from child to parent.
+ nsresult DivertTo(nsIStreamListener *aListener);
+ nsresult SuspendForDiversion();
+
+ void SetupInterception(const nsHttpResponseHead& aResponseHead);
+ void SetupInterceptionAfterRedirect(bool aShouldIntercept);
+ void ClearInterceptedChannel();
+
+private:
+ virtual ~HttpChannelParentListener();
+
+ // Private partner function to SuspendForDiversion.
+ nsresult ResumeForDiversion();
+
+ // Can be the original HttpChannelParent that created this object (normal
+ // case), a different {HTTP|FTP}ChannelParent that we've been redirected to,
+ // or some other listener that we have been diverted to via
+ // nsIDivertableChannel.
+ nsCOMPtr<nsIStreamListener> mNextListener;
+ uint32_t mRedirectChannelId;
+ // When set, no OnStart/OnData/OnStop calls should be received.
+ bool mSuspendedForDiversion;
+
+ // Set if this channel should be intercepted before it sets up the HTTP transaction.
+ bool mShouldIntercept;
+ // Set if this channel should suspend on interception.
+ bool mShouldSuspendIntercept;
+
+ nsAutoPtr<nsHttpResponseHead> mSynthesizedResponseHead;
+
+ // Handle to the channel wrapper if this channel has been intercepted.
+ nsCOMPtr<nsIInterceptedChannel> mInterceptedChannel;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpChannelParentListener,
+ HTTP_CHANNEL_PARENT_LISTENER_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_HttpChannelParent_h
diff --git a/netwerk/protocol/http/HttpInfo.cpp b/netwerk/protocol/http/HttpInfo.cpp
new file mode 100644
index 000000000..827f2c6ec
--- /dev/null
+++ b/netwerk/protocol/http/HttpInfo.cpp
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http:mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpHandler.h"
+#include "HttpInfo.h"
+
+
+void
+mozilla::net::HttpInfo::
+GetHttpConnectionData(nsTArray<HttpRetParams>* args)
+{
+ if (gHttpHandler)
+ gHttpHandler->ConnMgr()->GetConnectionData(args);
+}
diff --git a/netwerk/protocol/http/HttpInfo.h b/netwerk/protocol/http/HttpInfo.h
new file mode 100644
index 000000000..e13ffff16
--- /dev/null
+++ b/netwerk/protocol/http/HttpInfo.h
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http:mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpInfo__
+#define nsHttpInfo__
+
+#include "nsTArrayForwardDeclare.h"
+
+namespace mozilla {
+namespace net {
+
+struct HttpRetParams;
+
+class HttpInfo
+{
+public:
+ /* Calls getConnectionData method in nsHttpConnectionMgr. */
+ static void GetHttpConnectionData(nsTArray<HttpRetParams> *);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpInfo__
diff --git a/netwerk/protocol/http/HttpLog.h b/netwerk/protocol/http/HttpLog.h
new file mode 100644
index 000000000..21f29cdcd
--- /dev/null
+++ b/netwerk/protocol/http/HttpLog.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpLog_h__
+#define HttpLog_h__
+
+
+/*******************************************************************************
+ * This file should ONLY be #included by source (.cpp) files in the /http
+ * directory, not headers (.h). If you need to use LOG() in a .h file, call
+ * PR_LOG directly.
+ *
+ * This file should also be the first #include in your file.
+ *
+ * Yes, this is kludgy.
+ *******************************************************************************/
+
+#include "mozilla/net/NeckoChild.h"
+
+// Get rid of Chromium's LOG definition
+#undef LOG
+
+//
+// Log module for HTTP Protocol logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=nsHttp:5
+// set MOZ_LOG_FILE=http.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file http.log.
+//
+namespace mozilla {
+namespace net {
+extern LazyLogModule gHttpLog;
+}
+}
+
+// http logging
+#define LOG1(args) MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Error, args)
+#define LOG2(args) MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Warning, args)
+#define LOG3(args) MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Info, args)
+#define LOG4(args) MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Debug, args)
+#define LOG5(args) MOZ_LOG(mozilla::net::gHttpLog, mozilla::LogLevel::Verbose, args)
+#define LOG(args) LOG4(args)
+
+#define LOG1_ENABLED() MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Error)
+#define LOG2_ENABLED() MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Warning)
+#define LOG3_ENABLED() MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Info)
+#define LOG4_ENABLED() MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Debug)
+#define LOG5_ENABLED() MOZ_LOG_TEST(mozilla::net::gHttpLog, mozilla::LogLevel::Verbose)
+#define LOG_ENABLED() LOG4_ENABLED()
+
+#endif // HttpLog_h__
diff --git a/netwerk/protocol/http/InterceptedChannel.cpp b/netwerk/protocol/http/InterceptedChannel.cpp
new file mode 100644
index 000000000..9e38e2734
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedChannel.cpp
@@ -0,0 +1,540 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: -*- */
+/* vim:set expandtab ts=2 sw=2 sts=2 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HttpLog.h"
+
+#include "InterceptedChannel.h"
+#include "nsInputStreamPump.h"
+#include "nsIPipe.h"
+#include "nsIStreamListener.h"
+#include "nsHttpChannel.h"
+#include "HttpChannelChild.h"
+#include "nsHttpResponseHead.h"
+#include "nsNetUtil.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/dom/ChannelInfo.h"
+#include "nsIChannelEventSink.h"
+
+namespace mozilla {
+namespace net {
+
+extern bool
+WillRedirect(const nsHttpResponseHead * response);
+
+extern nsresult
+DoUpdateExpirationTime(nsHttpChannel* aSelf,
+ nsICacheEntry* aCacheEntry,
+ nsHttpResponseHead* aResponseHead,
+ uint32_t& aExpirationTime);
+extern nsresult
+DoAddCacheEntryHeaders(nsHttpChannel *self,
+ nsICacheEntry *entry,
+ nsHttpRequestHead *requestHead,
+ nsHttpResponseHead *responseHead,
+ nsISupports *securityInfo);
+
+NS_IMPL_ISUPPORTS(InterceptedChannelBase, nsIInterceptedChannel)
+
+InterceptedChannelBase::InterceptedChannelBase(nsINetworkInterceptController* aController)
+: mController(aController)
+, mReportCollector(new ConsoleReportCollector())
+, mClosed(false)
+{
+}
+
+InterceptedChannelBase::~InterceptedChannelBase()
+{
+}
+
+NS_IMETHODIMP
+InterceptedChannelBase::GetResponseBody(nsIOutputStream** aStream)
+{
+ NS_IF_ADDREF(*aStream = mResponseBody);
+ return NS_OK;
+}
+
+void
+InterceptedChannelBase::EnsureSynthesizedResponse()
+{
+ if (mSynthesizedResponseHead.isNothing()) {
+ mSynthesizedResponseHead.emplace(new nsHttpResponseHead());
+ }
+}
+
+void
+InterceptedChannelBase::DoNotifyController()
+{
+ nsresult rv = NS_OK;
+
+ if (NS_WARN_IF(!mController)) {
+ rv = ResetInterception();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to resume intercepted network request");
+ return;
+ }
+
+ rv = mController->ChannelIntercepted(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ rv = ResetInterception();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to resume intercepted network request");
+ }
+ mController = nullptr;
+}
+
+nsresult
+InterceptedChannelBase::DoSynthesizeStatus(uint16_t aStatus, const nsACString& aReason)
+{
+ EnsureSynthesizedResponse();
+
+ // Always assume HTTP 1.1 for synthesized responses.
+ nsAutoCString statusLine;
+ statusLine.AppendLiteral("HTTP/1.1 ");
+ statusLine.AppendInt(aStatus);
+ statusLine.AppendLiteral(" ");
+ statusLine.Append(aReason);
+
+ (*mSynthesizedResponseHead)->ParseStatusLine(statusLine);
+ return NS_OK;
+}
+
+nsresult
+InterceptedChannelBase::DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue)
+{
+ EnsureSynthesizedResponse();
+
+ nsAutoCString header = aName + NS_LITERAL_CSTRING(": ") + aValue;
+ // Overwrite any existing header.
+ nsresult rv = (*mSynthesizedResponseHead)->ParseHeaderLine(header);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelBase::GetConsoleReportCollector(nsIConsoleReportCollector** aCollectorOut)
+{
+ MOZ_ASSERT(aCollectorOut);
+ nsCOMPtr<nsIConsoleReportCollector> ref = mReportCollector;
+ ref.forget(aCollectorOut);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelBase::SetReleaseHandle(nsISupports* aHandle)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mReleaseHandle);
+ MOZ_ASSERT(aHandle);
+
+ // We need to keep it and mChannel alive until destructor clear it up.
+ mReleaseHandle = aHandle;
+ return NS_OK;
+}
+
+/* static */
+already_AddRefed<nsIURI>
+InterceptedChannelBase::SecureUpgradeChannelURI(nsIChannel* aChannel)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIURI> upgradedURI;
+ rv = NS_GetSecureUpgradedURI(uri, getter_AddRefs(upgradedURI));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return upgradedURI.forget();
+}
+
+InterceptedChannelChrome::InterceptedChannelChrome(nsHttpChannel* aChannel,
+ nsINetworkInterceptController* aController,
+ nsICacheEntry* aEntry)
+: InterceptedChannelBase(aController)
+, mChannel(aChannel)
+, mSynthesizedCacheEntry(aEntry)
+{
+ nsresult rv = mChannel->GetApplyConversion(&mOldApplyConversion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mOldApplyConversion = false;
+ }
+}
+
+void
+InterceptedChannelChrome::NotifyController()
+{
+ // Intercepted responses should already be decoded.
+ mChannel->SetApplyConversion(false);
+
+ nsresult rv = mSynthesizedCacheEntry->OpenOutputStream(0, getter_AddRefs(mResponseBody));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ DoNotifyController();
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::GetChannel(nsIChannel** aChannel)
+{
+ NS_IF_ADDREF(*aChannel = mChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::ResetInterception()
+{
+ if (mClosed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ mSynthesizedCacheEntry->AsyncDoom(nullptr);
+ mSynthesizedCacheEntry = nullptr;
+
+ mChannel->SetApplyConversion(mOldApplyConversion);
+
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+
+ nsresult rv = mChannel->StartRedirectChannelToURI(uri, nsIChannelEventSink::REDIRECT_INTERNAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mResponseBody->Close();
+ mResponseBody = nullptr;
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::SynthesizeStatus(uint16_t aStatus, const nsACString& aReason)
+{
+ if (!mSynthesizedCacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return DoSynthesizeStatus(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::SynthesizeHeader(const nsACString& aName, const nsACString& aValue)
+{
+ if (!mSynthesizedCacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return DoSynthesizeHeader(aName, aValue);
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::FinishSynthesizedResponse(const nsACString& aFinalURLSpec)
+{
+ if (mClosed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Make sure the cache entry's output stream is always closed. If the
+ // channel was intercepted with a null-body response then its possible
+ // the synthesis completed without a stream copy operation.
+ mResponseBody->Close();
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ EnsureSynthesizedResponse();
+
+ // If the synthesized response is a redirect, then we want to respect
+ // the encoding of whatever is loaded as a result.
+ if (WillRedirect(mSynthesizedResponseHead.ref())) {
+ nsresult rv = mChannel->SetApplyConversion(mOldApplyConversion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mChannel->MarkIntercepted();
+
+ // First we ensure the appropriate metadata is set on the synthesized cache entry
+ // (i.e. the flattened response head)
+
+ nsCOMPtr<nsISupports> securityInfo;
+ nsresult rv = mChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t expirationTime = 0;
+ rv = DoUpdateExpirationTime(mChannel, mSynthesizedCacheEntry,
+ mSynthesizedResponseHead.ref(),
+ expirationTime);
+
+ rv = DoAddCacheEntryHeaders(mChannel, mSynthesizedCacheEntry,
+ mChannel->GetRequestHead(),
+ mSynthesizedResponseHead.ref(), securityInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> originalURI;
+ mChannel->GetURI(getter_AddRefs(originalURI));
+
+ nsCOMPtr<nsIURI> responseURI;
+ if (!aFinalURLSpec.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ responseURI = originalURI;
+ }
+
+ bool equal = false;
+ originalURI->Equals(responseURI, &equal);
+ if (!equal) {
+ rv =
+ mChannel->StartRedirectChannelToURI(responseURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ bool usingSSL = false;
+ responseURI->SchemeIs("https", &usingSSL);
+
+ // Then we open a real cache entry to read the synthesized response from.
+ rv = mChannel->OpenCacheEntry(usingSSL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSynthesizedCacheEntry = nullptr;
+
+ if (!mChannel->AwaitingCacheCallbacks()) {
+ rv = mChannel->ContinueConnect();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::Cancel(nsresult aStatus)
+{
+ MOZ_ASSERT(NS_FAILED(aStatus));
+
+ if (mClosed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ // we need to use AsyncAbort instead of Cancel since there's no active pump
+ // to cancel which will provide OnStart/OnStopRequest to the channel.
+ nsresult rv = mChannel->AsyncAbort(aStatus);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::SetChannelInfo(dom::ChannelInfo* aChannelInfo)
+{
+ if (mClosed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return aChannelInfo->ResurrectInfoOnChannel(mChannel);
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::GetInternalContentPolicyType(nsContentPolicyType* aPolicyType)
+{
+ NS_ENSURE_ARG(aPolicyType);
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsresult rv = mChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aPolicyType = loadInfo->InternalContentPolicyType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::GetSecureUpgradedChannelURI(nsIURI** aURI)
+{
+ return mChannel->GetURI(aURI);
+}
+
+InterceptedChannelContent::InterceptedChannelContent(HttpChannelChild* aChannel,
+ nsINetworkInterceptController* aController,
+ InterceptStreamListener* aListener,
+ bool aSecureUpgrade)
+: InterceptedChannelBase(aController)
+, mChannel(aChannel)
+, mStreamListener(aListener)
+, mSecureUpgrade(aSecureUpgrade)
+{
+}
+
+void
+InterceptedChannelContent::NotifyController()
+{
+ nsresult rv = NS_NewPipe(getter_AddRefs(mSynthesizedInput),
+ getter_AddRefs(mResponseBody),
+ 0, UINT32_MAX, true, true);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ DoNotifyController();
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::GetChannel(nsIChannel** aChannel)
+{
+ NS_IF_ADDREF(*aChannel = mChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::ResetInterception()
+{
+ if (mClosed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ mResponseBody->Close();
+ mResponseBody = nullptr;
+ mSynthesizedInput = nullptr;
+
+ mChannel->ResetInterception();
+
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::SynthesizeStatus(uint16_t aStatus, const nsACString& aReason)
+{
+ if (!mResponseBody) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return DoSynthesizeStatus(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::SynthesizeHeader(const nsACString& aName, const nsACString& aValue)
+{
+ if (!mResponseBody) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return DoSynthesizeHeader(aName, aValue);
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::FinishSynthesizedResponse(const nsACString& aFinalURLSpec)
+{
+ if (NS_WARN_IF(mClosed)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Make sure the body output stream is always closed. If the channel was
+ // intercepted with a null-body response then its possible the synthesis
+ // completed without a stream copy operation.
+ mResponseBody->Close();
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ EnsureSynthesizedResponse();
+
+ nsCOMPtr<nsIURI> originalURI;
+ mChannel->GetURI(getter_AddRefs(originalURI));
+
+ nsCOMPtr<nsIURI> responseURI;
+ if (!aFinalURLSpec.IsEmpty()) {
+ nsresult rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (mSecureUpgrade) {
+ nsresult rv = NS_GetSecureUpgradedURI(originalURI,
+ getter_AddRefs(responseURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ responseURI = originalURI;
+ }
+
+ bool equal = false;
+ originalURI->Equals(responseURI, &equal);
+ if (!equal) {
+ mChannel->ForceIntercepted(mSynthesizedInput);
+ mChannel->BeginNonIPCRedirect(responseURI, *mSynthesizedResponseHead.ptr());
+ } else {
+ mChannel->OverrideWithSynthesizedResponse(mSynthesizedResponseHead.ref(),
+ mSynthesizedInput,
+ mStreamListener);
+ }
+
+ mResponseBody = nullptr;
+ mStreamListener = nullptr;
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::Cancel(nsresult aStatus)
+{
+ MOZ_ASSERT(NS_FAILED(aStatus));
+
+ if (mClosed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ // we need to use AsyncAbort instead of Cancel since there's no active pump
+ // to cancel which will provide OnStart/OnStopRequest to the channel.
+ nsresult rv = mChannel->AsyncAbort(aStatus);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mStreamListener = nullptr;
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::SetChannelInfo(dom::ChannelInfo* aChannelInfo)
+{
+ if (mClosed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return aChannelInfo->ResurrectInfoOnChannel(mChannel);
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::GetInternalContentPolicyType(nsContentPolicyType* aPolicyType)
+{
+ NS_ENSURE_ARG(aPolicyType);
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsresult rv = mChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aPolicyType = loadInfo->InternalContentPolicyType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::GetSecureUpgradedChannelURI(nsIURI** aURI)
+{
+ nsCOMPtr<nsIURI> uri;
+ if (mSecureUpgrade) {
+ uri = SecureUpgradeChannelURI(mChannel);
+ } else {
+ nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (uri) {
+ uri.forget(aURI);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/InterceptedChannel.h b/netwerk/protocol/http/InterceptedChannel.h
new file mode 100644
index 000000000..688f211de
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedChannel.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=2 sw=2 sts=2 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef InterceptedChannel_h
+#define InterceptedChannel_h
+
+#include "nsINetworkInterceptController.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Maybe.h"
+
+class nsICacheEntry;
+class nsInputStreamPump;
+class nsIStreamListener;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpChannel;
+class HttpChannelChild;
+class nsHttpResponseHead;
+class InterceptStreamListener;
+
+// An object representing a channel that has been intercepted. This avoids complicating
+// the actual channel implementation with the details of synthesizing responses.
+class InterceptedChannelBase : public nsIInterceptedChannel {
+protected:
+ // The interception controller to notify about the successful channel interception
+ nsCOMPtr<nsINetworkInterceptController> mController;
+
+ // The stream to write the body of the synthesized response
+ nsCOMPtr<nsIOutputStream> mResponseBody;
+
+ // Response head for use when synthesizing
+ Maybe<nsAutoPtr<nsHttpResponseHead>> mSynthesizedResponseHead;
+
+ nsCOMPtr<nsIConsoleReportCollector> mReportCollector;
+ nsCOMPtr<nsISupports> mReleaseHandle;
+
+ bool mClosed;
+
+ void EnsureSynthesizedResponse();
+ void DoNotifyController();
+ nsresult DoSynthesizeStatus(uint16_t aStatus, const nsACString& aReason);
+ nsresult DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue);
+
+ virtual ~InterceptedChannelBase();
+public:
+ explicit InterceptedChannelBase(nsINetworkInterceptController* aController);
+
+ // Notify the interception controller that the channel has been intercepted
+ // and prepare the response body output stream.
+ virtual void NotifyController() = 0;
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD GetResponseBody(nsIOutputStream** aOutput) override;
+ NS_IMETHOD GetConsoleReportCollector(nsIConsoleReportCollector** aCollectorOut) override;
+ NS_IMETHOD SetReleaseHandle(nsISupports* aHandle) override;
+
+ static already_AddRefed<nsIURI>
+ SecureUpgradeChannelURI(nsIChannel* aChannel);
+};
+
+class InterceptedChannelChrome : public InterceptedChannelBase
+{
+ // The actual channel being intercepted.
+ RefPtr<nsHttpChannel> mChannel;
+
+ // Writeable cache entry for use when synthesizing a response in a parent process
+ nsCOMPtr<nsICacheEntry> mSynthesizedCacheEntry;
+
+ // When a channel is intercepted, content decoding is disabled since the
+ // ServiceWorker will have already extracted the decoded data. For parent
+ // process channels we need to preserve the earlier value in case
+ // ResetInterception is called.
+ bool mOldApplyConversion;
+public:
+ InterceptedChannelChrome(nsHttpChannel* aChannel,
+ nsINetworkInterceptController* aController,
+ nsICacheEntry* aEntry);
+
+ NS_IMETHOD ResetInterception() override;
+ NS_IMETHOD FinishSynthesizedResponse(const nsACString& aFinalURLSpec) override;
+ NS_IMETHOD GetChannel(nsIChannel** aChannel) override;
+ NS_IMETHOD GetSecureUpgradedChannelURI(nsIURI** aURI) override;
+ NS_IMETHOD SynthesizeStatus(uint16_t aStatus, const nsACString& aReason) override;
+ NS_IMETHOD SynthesizeHeader(const nsACString& aName, const nsACString& aValue) override;
+ NS_IMETHOD Cancel(nsresult aStatus) override;
+ NS_IMETHOD SetChannelInfo(mozilla::dom::ChannelInfo* aChannelInfo) override;
+ NS_IMETHOD GetInternalContentPolicyType(nsContentPolicyType *aInternalContentPolicyType) override;
+
+ virtual void NotifyController() override;
+};
+
+class InterceptedChannelContent : public InterceptedChannelBase
+{
+ // The actual channel being intercepted.
+ RefPtr<HttpChannelChild> mChannel;
+
+ // Reader-side of the response body when synthesizing in a child proces
+ nsCOMPtr<nsIInputStream> mSynthesizedInput;
+
+ // Listener for the synthesized response to fix up the notifications before they reach
+ // the actual channel.
+ RefPtr<InterceptStreamListener> mStreamListener;
+
+ // Set for intercepted channels that have gone through a secure upgrade.
+ bool mSecureUpgrade;
+public:
+ InterceptedChannelContent(HttpChannelChild* aChannel,
+ nsINetworkInterceptController* aController,
+ InterceptStreamListener* aListener,
+ bool aSecureUpgrade);
+
+ NS_IMETHOD ResetInterception() override;
+ NS_IMETHOD FinishSynthesizedResponse(const nsACString& aFinalURLSpec) override;
+ NS_IMETHOD GetChannel(nsIChannel** aChannel) override;
+ NS_IMETHOD GetSecureUpgradedChannelURI(nsIURI** aURI) override;
+ NS_IMETHOD SynthesizeStatus(uint16_t aStatus, const nsACString& aReason) override;
+ NS_IMETHOD SynthesizeHeader(const nsACString& aName, const nsACString& aValue) override;
+ NS_IMETHOD Cancel(nsresult aStatus) override;
+ NS_IMETHOD SetChannelInfo(mozilla::dom::ChannelInfo* aChannelInfo) override;
+ NS_IMETHOD GetInternalContentPolicyType(nsContentPolicyType *aInternalContentPolicyType) override;
+
+ virtual void NotifyController() override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // InterceptedChannel_h
diff --git a/netwerk/protocol/http/NullHttpChannel.cpp b/netwerk/protocol/http/NullHttpChannel.cpp
new file mode 100644
index 000000000..8c048a6b5
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpChannel.cpp
@@ -0,0 +1,772 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NullHttpChannel.h"
+#include "nsContentUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(NullHttpChannel, nsINullChannel,
+ nsIHttpChannel, nsITimedChannel)
+
+NullHttpChannel::NullHttpChannel()
+{
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+ mAsyncOpenTime = TimeStamp::Now();
+}
+
+NullHttpChannel::NullHttpChannel(nsIHttpChannel * chan)
+{
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ ssm->GetChannelURIPrincipal(chan, getter_AddRefs(mResourcePrincipal));
+
+ chan->GetResponseHeader(NS_LITERAL_CSTRING("Timing-Allow-Origin"),
+ mTimingAllowOriginHeader);
+ chan->GetURI(getter_AddRefs(mURI));
+ chan->GetOriginalURI(getter_AddRefs(mOriginalURI));
+
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+ mAsyncOpenTime = TimeStamp::Now();
+
+ nsCOMPtr<nsITimedChannel> timedChanel(do_QueryInterface(chan));
+ if (timedChanel) {
+ timedChanel->GetInitiatorType(mInitiatorType);
+ }
+}
+
+nsresult
+NullHttpChannel::Init(nsIURI *aURI,
+ uint32_t aCaps,
+ nsProxyInfo *aProxyInfo,
+ uint32_t aProxyResolveFlags,
+ nsIURI *aProxyURI)
+{
+ mURI = aURI;
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsIHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetChannelId(nsACString& aChannelId)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetChannelId(const nsACString& aChannelId)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetTransferSize(uint64_t *aTransferSize)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDecodedBodySize(uint64_t *aDecodedBodySize)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestMethod(nsACString & aRequestMethod)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRequestMethod(const nsACString & aRequestMethod)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetReferrer(nsIURI * *aReferrer)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetReferrer(nsIURI *aReferrer)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetReferrerPolicy(uint32_t *aReferrerPolicy)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetReferrerWithPolicy(nsIURI *referrer, uint32_t referrerPolicy)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestHeader(const nsACString & aHeader, nsACString & _retval)
+{
+ _retval.Truncate();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRequestHeader(const nsACString & aHeader, const nsACString & aValue, bool aMerge)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetEmptyRequestHeader(const nsACString & aHeader)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::VisitRequestHeaders(nsIHttpHeaderVisitor *aVisitor)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor *aVisitor)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAllowPipelining(bool *aAllowPipelining)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetAllowPipelining(bool aAllowPipelining)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAllowSTS(bool *aAllowSTS)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetAllowSTS(bool aAllowSTS)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRedirectionLimit(uint32_t *aRedirectionLimit)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRedirectionLimit(uint32_t aRedirectionLimit)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseStatus(uint32_t *aResponseStatus)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseStatusText(nsACString & aResponseStatusText)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestSucceeded(bool *aRequestSucceeded)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseHeader(const nsACString & header, nsACString & _retval)
+{
+ _retval.Truncate();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetResponseHeader(const nsACString & header, const nsACString & value, bool merge)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *aVisitor)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetOriginalResponseHeader(const nsACString & header,
+ nsIHttpHeaderVisitor *aVisitor)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor *aVisitor)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::IsNoStoreResponse(bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::IsNoCacheResponse(bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::IsPrivateResponse(bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::RedirectTo(nsIURI *aNewURI)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestContextID(nsID *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRequestContextID(const nsID rcID)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetProtocolVersion(nsACString& aProtocolVersion)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetEncodedBodySize(uint64_t *aEncodedBodySize)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetOriginalURI(nsIURI * *aOriginalURI)
+{
+ NS_IF_ADDREF(*aOriginalURI = mOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetOriginalURI(nsIURI *aOriginalURI)
+{
+ mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetURI(nsIURI * *aURI)
+{
+ NS_IF_ADDREF(*aURI = mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetOwner(nsISupports * *aOwner)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetOwner(nsISupports *aOwner)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor * *aNotificationCallbacks)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aNotificationCallbacks)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentType(nsACString & aContentType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentType(const nsACString & aContentType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentCharset(nsACString & aContentCharset)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentCharset(const nsACString & aContentCharset)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentLength(int64_t *aContentLength)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentLength(int64_t aContentLength)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::Open(nsIInputStream * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP
+NullHttpChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentDispositionFilename(nsAString & aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetContentDispositionFilename(const nsAString & aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetContentDispositionHeader(nsACString & aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetLoadInfo(nsILoadInfo * *aLoadInfo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLoadInfo(nsILoadInfo *aLoadInfo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetName(nsACString & aName)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::IsPending(bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetStatus(nsresult *aStatus)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::Cancel(nsresult aStatus)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::Suspend()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::Resume()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetLoadGroup(nsILoadGroup * *aLoadGroup)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsITimedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetTimingEnabled(bool *aTimingEnabled)
+{
+ *aTimingEnabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetTimingEnabled(bool aTimingEnabled)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRedirectCount(uint16_t *aRedirectCount)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRedirectCount(uint16_t aRedirectCount)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetChannelCreation(mozilla::TimeStamp *aChannelCreation)
+{
+ *aChannelCreation = mChannelCreationTimestamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAsyncOpen(mozilla::TimeStamp *aAsyncOpen)
+{
+ *aAsyncOpen = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDomainLookupStart(mozilla::TimeStamp *aDomainLookupStart)
+{
+ *aDomainLookupStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDomainLookupEnd(mozilla::TimeStamp *aDomainLookupEnd)
+{
+ *aDomainLookupEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetConnectStart(mozilla::TimeStamp *aConnectStart)
+{
+ *aConnectStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetConnectEnd(mozilla::TimeStamp *aConnectEnd)
+{
+ *aConnectEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRequestStart(mozilla::TimeStamp *aRequestStart)
+{
+ *aRequestStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseStart(mozilla::TimeStamp *aResponseStart)
+{
+ *aResponseStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetResponseEnd(mozilla::TimeStamp *aResponseEnd)
+{
+ *aResponseEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRedirectStart(mozilla::TimeStamp *aRedirectStart)
+{
+ *aRedirectStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRedirectStart(mozilla::TimeStamp aRedirectStart)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetRedirectEnd(mozilla::TimeStamp *aRedirectEnd)
+{
+ *aRedirectEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetRedirectEnd(mozilla::TimeStamp aRedirectEnd)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetInitiatorType(nsAString & aInitiatorType)
+{
+ aInitiatorType = mInitiatorType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetInitiatorType(const nsAString & aInitiatorType)
+{
+ mInitiatorType = aInitiatorType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAllRedirectsSameOrigin(bool *aAllRedirectsSameOrigin)
+{
+ *aAllRedirectsSameOrigin = mAllRedirectsSameOrigin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetAllRedirectsSameOrigin(bool aAllRedirectsSameOrigin)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetAllRedirectsPassTimingAllowCheck(bool *aAllRedirectsPassTimingAllowCheck)
+{
+ *aAllRedirectsPassTimingAllowCheck = mAllRedirectsPassTimingAllowCheck;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetAllRedirectsPassTimingAllowCheck(bool aAllRedirectsPassTimingAllowCheck)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::TimingAllowCheck(nsIPrincipal *aOrigin, bool *_retval)
+{
+ if (!mResourcePrincipal || !aOrigin) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ bool sameOrigin = false;
+ nsresult rv = mResourcePrincipal->Equals(aOrigin, &sameOrigin);
+ if (NS_SUCCEEDED(rv) && sameOrigin) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ if (mTimingAllowOriginHeader == "*") {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ nsAutoCString origin;
+ nsContentUtils::GetASCIIOrigin(aOrigin, origin);
+
+ if (mTimingAllowOriginHeader == origin) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetCacheReadStart(mozilla::TimeStamp *aCacheReadStart)
+{
+ *aCacheReadStart = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetCacheReadEnd(mozilla::TimeStamp *aCacheReadEnd)
+{
+ *aCacheReadEnd = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetIsMainDocumentChannel(bool* aValue)
+{
+ *aValue = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetIsMainDocumentChannel(bool aValue)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#define IMPL_TIMING_ATTR(name) \
+NS_IMETHODIMP \
+NullHttpChannel::Get##name##Time(PRTime* _retval) { \
+ TimeStamp stamp; \
+ Get##name(&stamp); \
+ if (stamp.IsNull()) { \
+ *_retval = 0; \
+ return NS_OK; \
+ } \
+ *_retval = mChannelCreationTime + \
+ (PRTime) ((stamp - mChannelCreationTimestamp).ToSeconds() * 1e6); \
+ return NS_OK; \
+}
+
+IMPL_TIMING_ATTR(ChannelCreation)
+IMPL_TIMING_ATTR(AsyncOpen)
+IMPL_TIMING_ATTR(DomainLookupStart)
+IMPL_TIMING_ATTR(DomainLookupEnd)
+IMPL_TIMING_ATTR(ConnectStart)
+IMPL_TIMING_ATTR(ConnectEnd)
+IMPL_TIMING_ATTR(RequestStart)
+IMPL_TIMING_ATTR(ResponseStart)
+IMPL_TIMING_ATTR(ResponseEnd)
+IMPL_TIMING_ATTR(CacheReadStart)
+IMPL_TIMING_ATTR(CacheReadEnd)
+IMPL_TIMING_ATTR(RedirectStart)
+IMPL_TIMING_ATTR(RedirectEnd)
+
+#undef IMPL_TIMING_ATTR
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/NullHttpChannel.h b/netwerk/protocol/http/NullHttpChannel.h
new file mode 100644
index 000000000..69b28d992
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpChannel.h
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_NullHttpChannel_h
+#define mozilla_net_NullHttpChannel_h
+
+#include "nsINullChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsITimedChannel.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsString.h"
+#include "prtime.h"
+
+namespace mozilla {
+namespace net {
+
+class nsProxyInfo;
+
+class NullHttpChannel final
+ : public nsINullChannel
+ , public nsIHttpChannel
+ , public nsITimedChannel
+{
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINULLCHANNEL
+ NS_DECL_NSIHTTPCHANNEL
+ NS_DECL_NSITIMEDCHANNEL
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+
+ NullHttpChannel();
+
+ // Copies the URI, Principal and Timing-Allow-Origin headers from the
+ // passed channel to this object, to be used for resource timing checks
+ explicit NullHttpChannel(nsIHttpChannel * chan);
+
+ // Same signature as nsHttpChannel::Init
+ nsresult Init(nsIURI *aURI, uint32_t aCaps, nsProxyInfo *aProxyInfo,
+ uint32_t aProxyResolveFlags,
+ nsIURI *aProxyURI);
+private:
+ ~NullHttpChannel() { }
+
+protected:
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+
+ nsString mInitiatorType;
+ PRTime mChannelCreationTime;
+ TimeStamp mAsyncOpenTime;
+ TimeStamp mChannelCreationTimestamp;
+ nsCOMPtr<nsIPrincipal> mResourcePrincipal;
+ nsCString mTimingAllowOriginHeader;
+ bool mAllRedirectsSameOrigin;
+ bool mAllRedirectsPassTimingAllowCheck;
+};
+
+} // namespace net
+} // namespace mozilla
+
+
+
+#endif // mozilla_net_NullHttpChannel_h
diff --git a/netwerk/protocol/http/NullHttpTransaction.cpp b/netwerk/protocol/http/NullHttpTransaction.cpp
new file mode 100644
index 000000000..965ffcc2c
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpTransaction.cpp
@@ -0,0 +1,333 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttp.h"
+#include "NullHttpTransaction.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsIHttpActivityObserver.h"
+#include "NullHttpChannel.h"
+#include "nsQueryObject.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+class CallObserveActivity final : public nsIRunnable
+{
+ ~CallObserveActivity()
+ {
+ }
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ CallObserveActivity(nsIHttpActivityObserver *aActivityDistributor,
+ const nsCString &aHost,
+ int32_t aPort,
+ bool aEndToEndSSL,
+ uint32_t aActivityType,
+ uint32_t aActivitySubtype,
+ PRTime aTimestamp,
+ uint64_t aExtraSizeData,
+ const nsACString &aExtraStringData)
+ : mActivityDistributor(aActivityDistributor)
+ , mHost(aHost)
+ , mPort(aPort)
+ , mEndToEndSSL(aEndToEndSSL)
+ , mActivityType(aActivityType)
+ , mActivitySubtype(aActivitySubtype)
+ , mTimestamp(aTimestamp)
+ , mExtraSizeData(aExtraSizeData)
+ , mExtraStringData(aExtraStringData)
+ {
+ }
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString port(NS_LITERAL_CSTRING(""));
+ if (mPort != -1 && ((mEndToEndSSL && mPort != 443) ||
+ (!mEndToEndSSL && mPort != 80))) {
+ port.AppendInt(mPort);
+ }
+
+ nsresult rv = NS_NewURI(getter_AddRefs(uri),
+ (mEndToEndSSL ? NS_LITERAL_CSTRING("https://")
+ : NS_LITERAL_CSTRING("http://") ) + mHost + port);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ RefPtr<NullHttpChannel> channel = new NullHttpChannel();
+ channel->Init(uri, 0, nullptr, 0, nullptr);
+ mActivityDistributor->ObserveActivity(
+ nsCOMPtr<nsISupports>(do_QueryObject(channel)),
+ mActivityType,
+ mActivitySubtype,
+ mTimestamp,
+ mExtraSizeData,
+ mExtraStringData);
+
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIHttpActivityObserver> mActivityDistributor;
+ nsCString mHost;
+ int32_t mPort;
+ bool mEndToEndSSL;
+ uint32_t mActivityType;
+ uint32_t mActivitySubtype;
+ PRTime mTimestamp;
+ uint64_t mExtraSizeData;
+ nsCString mExtraStringData;
+};
+
+NS_IMPL_ISUPPORTS(CallObserveActivity, nsIRunnable)
+
+NS_IMPL_ISUPPORTS(NullHttpTransaction, NullHttpTransaction, nsISupportsWeakReference)
+
+NullHttpTransaction::NullHttpTransaction(nsHttpConnectionInfo *ci,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps)
+ : mStatus(NS_OK)
+ , mCaps(caps | NS_HTTP_ALLOW_KEEPALIVE)
+ , mRequestHead(nullptr)
+ , mCapsToClear(0)
+ , mIsDone(false)
+ , mClaimed(false)
+ , mCallbacks(callbacks)
+ , mConnectionInfo(ci)
+{
+ nsresult rv;
+ mActivityDistributor = do_GetService(NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID,
+ &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ bool activityDistributorActive;
+ rv = mActivityDistributor->GetIsActive(&activityDistributorActive);
+ if (NS_SUCCEEDED(rv) && activityDistributorActive) {
+ // There are some observers registered at activity distributor.
+ LOG(("NulHttpTransaction::NullHttpTransaction() "
+ "mActivityDistributor is active "
+ "[this=%p, %s]", this, ci->GetOrigin().get()));
+ } else {
+ // There is no observer, so don't use it.
+ mActivityDistributor = nullptr;
+ }
+}
+
+NullHttpTransaction::~NullHttpTransaction()
+{
+ mCallbacks = nullptr;
+ delete mRequestHead;
+}
+
+bool
+NullHttpTransaction::Claim()
+{
+ if (mClaimed) {
+ return false;
+ }
+ mClaimed = true;
+ return true;
+}
+
+void
+NullHttpTransaction::SetConnection(nsAHttpConnection *conn)
+{
+ mConnection = conn;
+}
+
+nsAHttpConnection *
+NullHttpTransaction::Connection()
+{
+ return mConnection.get();
+}
+
+void
+NullHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **outCB)
+{
+ nsCOMPtr<nsIInterfaceRequestor> copyCB(mCallbacks);
+ *outCB = copyCB.forget().take();
+}
+
+void
+NullHttpTransaction::OnTransportStatus(nsITransport* transport,
+ nsresult status, int64_t progress)
+{
+ if (mActivityDistributor) {
+ NS_DispatchToMainThread(new CallObserveActivity(mActivityDistributor,
+ mConnectionInfo->GetOrigin(),
+ mConnectionInfo->OriginPort(),
+ mConnectionInfo->EndToEndSSL(),
+ NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
+ static_cast<uint32_t>(status),
+ PR_Now(),
+ progress,
+ EmptyCString()));
+ }
+}
+
+bool
+NullHttpTransaction::IsDone()
+{
+ return mIsDone;
+}
+
+nsresult
+NullHttpTransaction::Status()
+{
+ return mStatus;
+}
+
+uint32_t
+NullHttpTransaction::Caps()
+{
+ return mCaps & ~mCapsToClear;
+}
+
+void
+NullHttpTransaction::SetDNSWasRefreshed()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "SetDNSWasRefreshed on main thread only!");
+ mCapsToClear |= NS_HTTP_REFRESH_DNS;
+}
+
+uint64_t
+NullHttpTransaction::Available()
+{
+ return 0;
+}
+
+nsresult
+NullHttpTransaction::ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead)
+{
+ *countRead = 0;
+ mIsDone = true;
+ return NS_BASE_STREAM_CLOSED;
+}
+
+nsresult
+NullHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count, uint32_t *countWritten)
+{
+ *countWritten = 0;
+ return NS_BASE_STREAM_CLOSED;
+}
+
+uint32_t
+NullHttpTransaction::Http1xTransactionCount()
+{
+ return 0;
+}
+
+nsHttpRequestHead *
+NullHttpTransaction::RequestHead()
+{
+ // We suport a requesthead at all so that a CONNECT tunnel transaction
+ // can obtain a Host header from it, but we lazy-popualate that header.
+
+ if (!mRequestHead) {
+ mRequestHead = new nsHttpRequestHead();
+
+ nsAutoCString hostHeader;
+ nsCString host(mConnectionInfo->GetOrigin());
+ nsresult rv = nsHttpHandler::GenerateHostPort(host,
+ mConnectionInfo->OriginPort(),
+ hostHeader);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestHead->SetHeader(nsHttp::Host, hostHeader);
+ if (mActivityDistributor) {
+ // Report request headers.
+ nsCString reqHeaderBuf;
+ mRequestHead->Flatten(reqHeaderBuf, false);
+ NS_DispatchToMainThread(new CallObserveActivity(mActivityDistributor,
+ mConnectionInfo->GetOrigin(),
+ mConnectionInfo->OriginPort(),
+ mConnectionInfo->EndToEndSSL(),
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER,
+ PR_Now(), 0, reqHeaderBuf));
+ }
+ }
+
+ // CONNECT tunnels may also want Proxy-Authorization but that is a lot
+ // harder to determine, so for now we will let those connections fail in
+ // the NullHttpTransaction and let them be retried from the pending queue
+ // with a bound transaction
+ }
+
+ return mRequestHead;
+}
+
+nsresult
+NullHttpTransaction::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void
+NullHttpTransaction::SetProxyConnectFailed()
+{
+}
+
+void
+NullHttpTransaction::Close(nsresult reason)
+{
+ mStatus = reason;
+ mConnection = nullptr;
+ mIsDone = true;
+ if (mActivityDistributor) {
+ // Report that this transaction is closing.
+ NS_DispatchToMainThread(new CallObserveActivity(mActivityDistributor,
+ mConnectionInfo->GetOrigin(),
+ mConnectionInfo->OriginPort(),
+ mConnectionInfo->EndToEndSSL(),
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE,
+ PR_Now(), 0, EmptyCString()));
+ }
+}
+
+nsHttpConnectionInfo *
+NullHttpTransaction::ConnectionInfo()
+{
+ return mConnectionInfo;
+}
+
+nsresult
+NullHttpTransaction::AddTransaction(nsAHttpTransaction *trans)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+NullHttpTransaction::PipelineDepth()
+{
+ return 0;
+}
+
+nsresult
+NullHttpTransaction::SetPipelinePosition(int32_t position)
+{
+ return NS_OK;
+}
+
+int32_t
+NullHttpTransaction::PipelinePosition()
+{
+ return 1;
+}
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/protocol/http/NullHttpTransaction.h b/netwerk/protocol/http/NullHttpTransaction.h
new file mode 100644
index 000000000..04f80a9b3
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpTransaction.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_NullHttpTransaction_h
+#define mozilla_net_NullHttpTransaction_h
+
+#include "nsAHttpTransaction.h"
+#include "mozilla/Attributes.h"
+
+// This is the minimal nsAHttpTransaction implementation. A NullHttpTransaction
+// can be used to drive connection level semantics (such as SSL handshakes
+// tunnels) so that a nsHttpConnection becomes fully established in
+// anticipation of a real transaction needing to use it soon.
+
+class nsIHttpActivityObserver;
+
+namespace mozilla { namespace net {
+
+class nsAHttpConnection;
+class nsHttpConnectionInfo;
+class nsHttpRequestHead;
+
+// 6c445340-3b82-4345-8efa-4902c3b8805a
+#define NS_NULLHTTPTRANSACTION_IID \
+{ 0x6c445340, 0x3b82, 0x4345, {0x8e, 0xfa, 0x49, 0x02, 0xc3, 0xb8, 0x80, 0x5a }}
+
+class NullHttpTransaction : public nsAHttpTransaction
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_NULLHTTPTRANSACTION_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+
+ NullHttpTransaction(nsHttpConnectionInfo *ci,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps);
+
+ bool Claim();
+
+ // Overload of nsAHttpTransaction methods
+ bool IsNullTransaction() override final { return true; }
+ NullHttpTransaction *QueryNullTransaction() override final { return this; }
+ bool ResponseTimeoutEnabled() const override final {return true; }
+ PRIntervalTime ResponseTimeout() override final
+ {
+ return PR_SecondsToInterval(15);
+ }
+
+protected:
+ virtual ~NullHttpTransaction();
+
+private:
+ nsresult mStatus;
+protected:
+ uint32_t mCaps;
+ nsHttpRequestHead *mRequestHead;
+private:
+ // mCapsToClear holds flags that should be cleared in mCaps, e.g. unset
+ // NS_HTTP_REFRESH_DNS when DNS refresh request has completed to avoid
+ // redundant requests on the network. The member itself is atomic, but
+ // access to it from the networking thread may happen either before or
+ // after the main thread modifies it. To deal with raciness, only unsetting
+ // bitfields should be allowed: 'lost races' will thus err on the
+ // conservative side, e.g. by going ahead with a 2nd DNS refresh.
+ Atomic<uint32_t> mCapsToClear;
+ bool mIsDone;
+ bool mClaimed;
+
+protected:
+ RefPtr<nsAHttpConnection> mConnection;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ RefPtr<nsHttpConnectionInfo> mConnectionInfo;
+ nsCOMPtr<nsIHttpActivityObserver> mActivityDistributor;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(NullHttpTransaction, NS_NULLHTTPTRANSACTION_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_NullHttpTransaction_h
diff --git a/netwerk/protocol/http/PAltDataOutputStream.ipdl b/netwerk/protocol/http/PAltDataOutputStream.ipdl
new file mode 100644
index 000000000..824137f8e
--- /dev/null
+++ b/netwerk/protocol/http/PAltDataOutputStream.ipdl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+namespace mozilla {
+namespace net {
+
+protocol PAltDataOutputStream
+{
+ manager PNecko;
+
+parent:
+ // Sends data from the child to the parent that will be written to the cache.
+ async WriteData(nsCString data);
+ // Signals that writing to the output stream is done.
+ async Close();
+ async __delete__();
+
+child:
+ // The parent calls this method to signal that an error has ocurred.
+ // This may mean that opening the output stream has failed or that writing to
+ // the stream has returned an error.
+ async Error(nsresult err);
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PHttpChannel.ipdl b/netwerk/protocol/http/PHttpChannel.ipdl
new file mode 100644
index 000000000..1eb25a403
--- /dev/null
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include InputStreamParams;
+include URIParams;
+include PBackgroundSharedTypes;
+include NeckoChannelParams;
+
+include protocol PBlob; //FIXME: bug #792908
+
+include "mozilla/net/NeckoMessageUtils.h";
+
+using class nsHttpHeaderArray from "nsHttpHeaderArray.h";
+using mozilla::net::NetAddr from "mozilla/net/DNS.h";
+using struct mozilla::net::ResourceTimingStruct from "mozilla/net/TimingStruct.h";
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+protocol PHttpChannel
+{
+ manager PNecko;
+
+parent:
+ // Note: channels are opened during construction, so no open method here:
+ // see PNecko.ipdl
+
+ async SetPriority(uint16_t priority);
+ async SetClassOfService(uint32_t cos);
+
+ async SetCacheTokenCachedCharset(nsCString charset);
+
+ async UpdateAssociatedContentSecurity(int32_t broken,
+ int32_t no);
+ async Suspend();
+ async Resume();
+
+ async Cancel(nsresult status);
+
+ // Reports approval/veto of redirect by child process redirect observers
+ async Redirect2Verify(nsresult result, RequestHeaderTuples changedHeaders,
+ uint32_t loadFlags, OptionalURIParams apiRedirectTo,
+ OptionalCorsPreflightArgs corsPreflightArgs,
+ bool forceHSTSPriming, bool mixedContentWouldBlock,
+ bool chooseAppcache);
+
+ // For document loads we keep this protocol open after child's
+ // OnStopRequest, and send this msg (instead of __delete__) to allow
+ // partial cleanup on parent.
+ async DocumentChannelCleanup();
+
+ // This might have to be sync. If this fails we must fail the document load
+ // to avoid endless loop.
+ //
+ // Explanation: the document loaded was loaded from the offline cache. But
+ // the cache group id (the manifest URL) of the cache group it was loaded
+ // from is different then the manifest the document refers to in the html
+ // tag. If we detect this during the cache selection algorithm, we must not
+ // load this document from the offline cache group it was just loaded from.
+ // Marking the cache entry as foreign in its cache group will prevent
+ // the document to load from the bad offline cache group. After it is marked,
+ // we reload the document to take the effect. If we fail to mark the entry
+ // as foreign, we will end up in the same situation and reload again and
+ // again, indefinitely.
+ async MarkOfflineCacheEntryAsForeign();
+
+ // Divert OnDataAvailable to the parent.
+ async DivertOnDataAvailable(nsCString data,
+ uint64_t offset,
+ uint32_t count);
+
+ // Divert OnStopRequest to the parent.
+ async DivertOnStopRequest(nsresult statusCode);
+
+ // Child has no more events/messages to divert to the parent.
+ async DivertComplete();
+
+ // Child has detected a CORS check failure, so needs to tell the parent
+ // to remove any matching entry from the CORS preflight cache.
+ async RemoveCorsPreflightCacheEntry(URIParams uri,
+ PrincipalInfo requestingPrincipal);
+
+ // After receiving this message, the parent calls SendDeleteSelf, and makes
+ // sure not to send any more messages after that.
+ async DeletingChannel();
+
+ async __delete__();
+
+child:
+ async OnStartRequest(nsresult channelStatus,
+ nsHttpResponseHead responseHead,
+ bool useResponseHead,
+ nsHttpHeaderArray requestHeaders,
+ bool isFromCache,
+ bool cacheEntryAvailable,
+ uint32_t cacheExpirationTime,
+ nsCString cachedCharset,
+ nsCString securityInfoSerialization,
+ NetAddr selfAddr,
+ NetAddr peerAddr,
+ int16_t redirectCount,
+ uint32_t cacheKey,
+ nsCString altDataType);
+
+ // Combines a single OnDataAvailable and its associated OnProgress &
+ // OnStatus calls into one IPDL message
+ async OnTransportAndData(nsresult channelStatus,
+ nsresult transportStatus,
+ uint64_t progress,
+ uint64_t progressMax,
+ uint64_t offset,
+ uint32_t count,
+ nsCString data);
+
+ async OnStopRequest(nsresult channelStatus, ResourceTimingStruct timing);
+
+ async OnProgress(int64_t progress, int64_t progressMax);
+
+ async OnStatus(nsresult status);
+
+ // Used to cancel child channel if we hit errors during creating and
+ // AsyncOpen of nsHttpChannel on the parent.
+ async FailedAsyncOpen(nsresult status);
+
+ // Called to initiate content channel redirect, starts talking to sinks
+ // on the content process and reports result via Redirect2Verify above
+ async Redirect1Begin(uint32_t registrarId,
+ URIParams newUri,
+ uint32_t redirectFlags,
+ nsHttpResponseHead responseHead,
+ nsCString securityInfoSerialization,
+ nsCString channelId);
+
+ // Called if redirect successful so that child can complete setup.
+ async Redirect3Complete();
+
+ // Associate the child with an application ids
+ async AssociateApplicationCache(nsCString groupID,
+ nsCString clientID);
+
+ // Tell the child that tracking protection was disabled for this load.
+ async NotifyTrackingProtectionDisabled();
+
+ // Parent has been suspended for diversion; no more events to be enqueued.
+ async FlushedForDiversion();
+
+ // Child should resume processing the ChannelEventQueue, i.e. diverting any
+ // OnDataAvailable and OnStopRequest messages in the queue back to the parent.
+ async DivertMessages();
+
+ // Report a security message to the console associated with this
+ // channel.
+ async ReportSecurityMessage(nsString messageTag, nsString messageCategory);
+
+ // Tell child to delete channel (all IPDL deletes must be done from child to
+ // avoid races: see bug 591708).
+ async DeleteSelf();
+
+ // Tell the child to issue a deprecation warning.
+ async IssueDeprecationWarning(uint32_t warning, bool asError);
+
+both:
+ // After receiving this message, the parent also calls
+ // SendFinishInterceptedRedirect, and makes sure not to send any more messages
+ // after that. When receiving this message, the child will call
+ // Send__delete__() and complete the steps required to finish the redirect.
+ async FinishInterceptedRedirect();
+};
+
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PHttpChannelParams.h b/netwerk/protocol/http/PHttpChannelParams.h
new file mode 100644
index 000000000..4df5c7832
--- /dev/null
+++ b/netwerk/protocol/http/PHttpChannelParams.h
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_PHttpChannelParams_h
+#define mozilla_net_PHttpChannelParams_h
+
+#define ALLOW_LATE_NSHTTP_H_INCLUDE 1
+#include "base/basictypes.h"
+
+#include "ipc/IPCMessageUtils.h"
+#include "nsHttp.h"
+#include "nsHttpHeaderArray.h"
+#include "nsHttpResponseHead.h"
+
+#include "nsIClassInfo.h"
+
+namespace mozilla {
+namespace net {
+
+struct RequestHeaderTuple {
+ nsCString mHeader;
+ nsCString mValue;
+ bool mMerge;
+ bool mEmpty;
+
+ bool operator ==(const RequestHeaderTuple &other) const {
+ return mHeader.Equals(other.mHeader) &&
+ mValue.Equals(other.mValue) &&
+ mMerge == other.mMerge &&
+ mEmpty == other.mEmpty;
+ }
+};
+
+typedef nsTArray<RequestHeaderTuple> RequestHeaderTuples;
+
+} // namespace net
+} // namespace mozilla
+
+namespace IPC {
+
+template<>
+struct ParamTraits<mozilla::net::RequestHeaderTuple>
+{
+ typedef mozilla::net::RequestHeaderTuple paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.mHeader);
+ WriteParam(aMsg, aParam.mValue);
+ WriteParam(aMsg, aParam.mMerge);
+ WriteParam(aMsg, aParam.mEmpty);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ if (!ReadParam(aMsg, aIter, &aResult->mHeader) ||
+ !ReadParam(aMsg, aIter, &aResult->mValue) ||
+ !ReadParam(aMsg, aIter, &aResult->mMerge) ||
+ !ReadParam(aMsg, aIter, &aResult->mEmpty))
+ return false;
+
+ return true;
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::net::nsHttpAtom>
+{
+ typedef mozilla::net::nsHttpAtom paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ // aParam.get() cannot be null.
+ MOZ_ASSERT(aParam.get(), "null nsHTTPAtom value");
+ nsAutoCString value(aParam.get());
+ WriteParam(aMsg, value);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ nsAutoCString value;
+ if (!ReadParam(aMsg, aIter, &value))
+ return false;
+
+ *aResult = mozilla::net::nsHttp::ResolveAtom(value.get());
+ MOZ_ASSERT(aResult->get(), "atom table not initialized");
+ return true;
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::net::nsHttpHeaderArray::nsEntry>
+{
+ typedef mozilla::net::nsHttpHeaderArray::nsEntry paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.header);
+ WriteParam(aMsg, aParam.value);
+ switch (aParam.variety) {
+ case mozilla::net::nsHttpHeaderArray::eVarietyUnknown:
+ WriteParam(aMsg, (uint8_t)0);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyRequestOverride:
+ WriteParam(aMsg, (uint8_t)1);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyRequestDefault:
+ WriteParam(aMsg, (uint8_t)2);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginalAndResponse:
+ WriteParam(aMsg, (uint8_t)3);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginal:
+ WriteParam(aMsg, (uint8_t)4);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyResponse:
+ WriteParam(aMsg, (uint8_t)5);
+ }
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ uint8_t variety;
+ if (!ReadParam(aMsg, aIter, &aResult->header) ||
+ !ReadParam(aMsg, aIter, &aResult->value) ||
+ !ReadParam(aMsg, aIter, &variety))
+ return false;
+
+ switch (variety) {
+ case 0:
+ aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyUnknown;
+ break;
+ case 1:
+ aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyRequestOverride;
+ break;
+ case 2:
+ aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyRequestDefault;
+ break;
+ case 3:
+ aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginalAndResponse;
+ break;
+ case 4:
+ aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginal;
+ break;
+ case 5:
+ aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyResponse;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+ }
+};
+
+
+template<>
+struct ParamTraits<mozilla::net::nsHttpHeaderArray>
+{
+ typedef mozilla::net::nsHttpHeaderArray paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ paramType& p = const_cast<paramType&>(aParam);
+
+ WriteParam(aMsg, p.mHeaders);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ if (!ReadParam(aMsg, aIter, &aResult->mHeaders))
+ return false;
+
+ return true;
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::net::nsHttpResponseHead>
+{
+ typedef mozilla::net::nsHttpResponseHead paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.mHeaders);
+ WriteParam(aMsg, aParam.mVersion);
+ WriteParam(aMsg, aParam.mStatus);
+ WriteParam(aMsg, aParam.mStatusText);
+ WriteParam(aMsg, aParam.mContentLength);
+ WriteParam(aMsg, aParam.mContentType);
+ WriteParam(aMsg, aParam.mContentCharset);
+ WriteParam(aMsg, aParam.mCacheControlPrivate);
+ WriteParam(aMsg, aParam.mCacheControlNoStore);
+ WriteParam(aMsg, aParam.mCacheControlNoCache);
+ WriteParam(aMsg, aParam.mPragmaNoCache);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ if (!ReadParam(aMsg, aIter, &aResult->mHeaders) ||
+ !ReadParam(aMsg, aIter, &aResult->mVersion) ||
+ !ReadParam(aMsg, aIter, &aResult->mStatus) ||
+ !ReadParam(aMsg, aIter, &aResult->mStatusText) ||
+ !ReadParam(aMsg, aIter, &aResult->mContentLength) ||
+ !ReadParam(aMsg, aIter, &aResult->mContentType) ||
+ !ReadParam(aMsg, aIter, &aResult->mContentCharset) ||
+ !ReadParam(aMsg, aIter, &aResult->mCacheControlPrivate) ||
+ !ReadParam(aMsg, aIter, &aResult->mCacheControlNoStore) ||
+ !ReadParam(aMsg, aIter, &aResult->mCacheControlNoCache) ||
+ !ReadParam(aMsg, aIter, &aResult->mPragmaNoCache))
+ return false;
+
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_net_PHttpChannelParams_h
diff --git a/netwerk/protocol/http/PSpdyPush.h b/netwerk/protocol/http/PSpdyPush.h
new file mode 100644
index 000000000..aa489912a
--- /dev/null
+++ b/netwerk/protocol/http/PSpdyPush.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// SPDY Server Push
+
+/*
+ A pushed stream is put into a memory buffer (The SpdyPushTransactionBuffer)
+ and spooled there until a GET is made that can be matched up with it. At
+ that time we have two spdy streams - the GET (aka the sink) and the PUSH
+ (aka the source). Data is copied between those two streams for the lifetime
+ of the transaction. This is true even if the transaction buffer is empty,
+ partly complete, or totally loaded at the time the GET correspondence is made.
+
+ correspondence is done through a hash table of the full url, the spdy session,
+ and the load group. The load group is implicit because that's where the
+ hash is stored, the other items comprise the hash key.
+
+ Pushed streams are subject to aggressive flow control before they are matched
+ with a GET at which point flow control is effectively disabled to match the
+ client pull behavior.
+*/
+
+#ifndef mozilla_net_SpdyPush_Public_h
+#define mozilla_net_SpdyPush_Public_h
+
+#include "nsAutoPtr.h"
+#include "nsDataHashtable.h"
+#include "nsISupports.h"
+
+class nsCString;
+
+namespace mozilla {
+namespace net {
+
+class Http2PushedStream;
+
+// One cache per load group
+class SpdyPushCache
+{
+public:
+ // The cache holds only weak pointers - no references
+ SpdyPushCache();
+ virtual ~SpdyPushCache();
+ bool RegisterPushedStreamHttp2(nsCString key,
+ Http2PushedStream *stream);
+ Http2PushedStream *RemovePushedStreamHttp2(nsCString key);
+private:
+ nsDataHashtable<nsCStringHashKey, Http2PushedStream *> mHashHttp2;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_SpdyPush_Public_h
diff --git a/netwerk/protocol/http/README b/netwerk/protocol/http/README
new file mode 100644
index 000000000..621e9e950
--- /dev/null
+++ b/netwerk/protocol/http/README
@@ -0,0 +1,119 @@
+ Darin Fisher
+ darin@netscape.com
+ 8/8/2001
+
+ HTTP DESIGN NOTES
+
+
+CLASS BREAKDOWN
+
+ nsHttpHandler
+ - implements nsIProtocolHandler
+ - manages preferences
+ - owns the authentication cache
+ - holds references to frequently used services
+
+ nsHttpChannel
+ - implements nsIHttpChannel
+ - talks to the cache
+ - initiates http transactions
+ - processes http response codes
+ - intercepts progress notifications
+
+ nsHttpConnection
+ - implements nsIStreamListener & nsIStreamProvider
+ - talks to the socket transport service
+ - feeds data to its transaction object
+ - routes progress notifications
+
+ nsHttpConnectionInfo
+ - identifies a connection
+
+ nsHttpTransaction
+ - implements nsIRequest
+ - encapsulates a http request and response
+ - parses incoming data
+
+ nsHttpChunkedDecoder
+ - owned by a transaction
+ - removes chunked decoding
+
+ nsHttpRequestHead
+ - owns a nsHttpHeaderArray
+ - knows how to fill a request buffer
+
+ nsHttpResponseHead
+ - owns a nsHttpHeaderArray
+ - knows how to parse response lines
+ - performs common header manipulations/calculations
+
+ nsHttpHeaderArray
+ - stores http "<header>:<value>" pairs
+
+ nsHttpAuthCache
+ - stores authentication credentials for http auth domains
+
+ nsHttpBasicAuth
+ - implements nsIHttpAuthenticator
+ - generates BASIC auth credentials from user:pass
+
+
+ATOMS
+
+ nsHttp:: (header namespace)
+
+ eg. nsHttp::Content_Length
+
+
+TRANSACTION MODEL
+
+ InitiateTransaction -> ActivateConnection -> AsyncWrite, AsyncRead
+
+ The channel creates transactions, and passes them to the handler via
+ InitiateTransaction along with a nsHttpConnectionInfo object
+ identifying the requested connection. The handler either dispatches
+ the transaction immediately or queues it up to be dispatched later,
+ depending on whether or not the limit on the number of connections
+ to the requested server has been reached. Once the transaction can
+ be run, the handler looks for an idle connection or creates a new
+ connection, and then (re)activates the connection, assigning it the
+ new transaction.
+
+ Once activated the connection ensures that it has a socket transport,
+ and then calls AsyncWrite and AsyncRead on the socket transport. This
+ begins the process of talking to the server. To minimize buffering,
+ socket transport thread-proxying is completely disabled (using the flags
+ DONT_PROXY_LISTENER | DONT_PROXY_PROVIDER | DONT_PROXY_OBSERVER with
+ both AsyncWrite and AsyncRead). This means that the nsHttpConnection's
+ OnStartRequest, OnDataAvailable, OnDataWritable, and OnStopRequest
+ methods will execute on the socket transport thread.
+
+ The transaction defines (non-virtual) OnDataReadable, OnDataWritable, and
+ OnStopTransaction methods, which the connection calls in response to
+ its OnDataAvailable, OnDataWritable, and OnStopRequest methods, respectively.
+ The transaction owns a nsStreamListenerProxy created by the channel, which
+ it uses to transfer data from the socket thread over to the client's thread.
+ To mimize buffering, the transaction implements nsIInputStream, and passes
+ itself to the stream listener proxy's OnDataAvailable. In this way, we
+ have effectively wedged the response parsing between the socket and the
+ thread proxy's buffer. When read, the transaction turns around and reads
+ from the socket using the buffer passed to it. The transaction scans the
+ buffer for headers, removes them as they are detected, and copies the headers
+ into its nsHttpResponseHead object. The rest of the data remains in the
+ buffer, and is proxied over to the client's thread to be handled first by the
+ http channel and eventually by the client.
+
+ There are several other major design factors, including:
+
+ - transaction cancelation
+ - progress notification
+ - SSL tunneling
+ - chunked decoding
+ - thread safety
+ - premature EOF detection and transaction restarting
+ - pipelining (not yet implemented)
+
+
+CACHING
+
+<EOF>
diff --git a/netwerk/protocol/http/TimingStruct.h b/netwerk/protocol/http/TimingStruct.h
new file mode 100644
index 000000000..b177eee8e
--- /dev/null
+++ b/netwerk/protocol/http/TimingStruct.h
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef TimingStruct_h_
+#define TimingStruct_h_
+
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla { namespace net {
+
+struct TimingStruct {
+ TimeStamp domainLookupStart;
+ TimeStamp domainLookupEnd;
+ TimeStamp connectStart;
+ TimeStamp connectEnd;
+ TimeStamp requestStart;
+ TimeStamp responseStart;
+ TimeStamp responseEnd;
+};
+
+struct ResourceTimingStruct : TimingStruct {
+ TimeStamp fetchStart;
+ TimeStamp redirectStart;
+ TimeStamp redirectEnd;
+ uint64_t transferSize;
+ uint64_t encodedBodySize;
+ nsCString protocolVersion;
+
+ // Not actually part of resource timing, but not part of the transaction
+ // timings either. These need to be passed to HttpChannelChild along with
+ // the rest of the timings so the timing information in the child is complete.
+ TimeStamp cacheReadStart;
+ TimeStamp cacheReadEnd;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/TunnelUtils.cpp b/netwerk/protocol/http/TunnelUtils.cpp
new file mode 100644
index 000000000..4cc24a07f
--- /dev/null
+++ b/netwerk/protocol/http/TunnelUtils.cpp
@@ -0,0 +1,1678 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "Http2Session.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsISocketProvider.h"
+#include "nsISocketProviderService.h"
+#include "nsISSLSocketControl.h"
+#include "nsISocketTransport.h"
+#include "nsISupportsPriority.h"
+#include "nsNetAddr.h"
+#include "prerror.h"
+#include "prio.h"
+#include "TunnelUtils.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+
+namespace mozilla {
+namespace net {
+
+static PRDescIdentity sLayerIdentity;
+static PRIOMethods sLayerMethods;
+static PRIOMethods *sLayerMethodsPtr = nullptr;
+
+TLSFilterTransaction::TLSFilterTransaction(nsAHttpTransaction *aWrapped,
+ const char *aTLSHost,
+ int32_t aTLSPort,
+ nsAHttpSegmentReader *aReader,
+ nsAHttpSegmentWriter *aWriter)
+ : mTransaction(aWrapped)
+ , mEncryptedTextUsed(0)
+ , mEncryptedTextSize(0)
+ , mSegmentReader(aReader)
+ , mSegmentWriter(aWriter)
+ , mForce(false)
+ , mNudgeCounter(0)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("TLSFilterTransaction ctor %p\n", this));
+
+ nsCOMPtr<nsISocketProvider> provider;
+ nsCOMPtr<nsISocketProviderService> spserv =
+ do_GetService(NS_SOCKETPROVIDERSERVICE_CONTRACTID);
+
+ if (spserv) {
+ spserv->GetSocketProvider("ssl", getter_AddRefs(provider));
+ }
+
+ // Install an NSPR layer to handle getpeername() with a failure. This is kind
+ // of silly, but the default one used by the pipe asserts when called and the
+ // nss code calls it to see if we are connected to a real socket or not.
+ if (!sLayerMethodsPtr) {
+ // one time initialization
+ sLayerIdentity = PR_GetUniqueIdentity("TLSFilterTransaction Layer");
+ sLayerMethods = *PR_GetDefaultIOMethods();
+ sLayerMethods.getpeername = GetPeerName;
+ sLayerMethods.getsocketoption = GetSocketOption;
+ sLayerMethods.setsocketoption = SetSocketOption;
+ sLayerMethods.read = FilterRead;
+ sLayerMethods.write = FilterWrite;
+ sLayerMethods.send = FilterSend;
+ sLayerMethods.recv = FilterRecv;
+ sLayerMethods.close = FilterClose;
+ sLayerMethodsPtr = &sLayerMethods;
+ }
+
+ mFD = PR_CreateIOLayerStub(sLayerIdentity, &sLayerMethods);
+
+ if (provider && mFD) {
+ mFD->secret = reinterpret_cast<PRFilePrivate *>(this);
+ provider->AddToSocket(PR_AF_INET, aTLSHost, aTLSPort, nullptr,
+ NeckoOriginAttributes(), 0, mFD,
+ getter_AddRefs(mSecInfo));
+ }
+
+ if (mTransaction) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(mSecInfo));
+ if (secCtrl) {
+ secCtrl->SetNotificationCallbacks(callbacks);
+ }
+ }
+}
+
+TLSFilterTransaction::~TLSFilterTransaction()
+{
+ LOG(("TLSFilterTransaction dtor %p\n", this));
+ Cleanup();
+}
+
+void
+TLSFilterTransaction::Cleanup()
+{
+ if (mTransaction) {
+ mTransaction->Close(NS_ERROR_ABORT);
+ mTransaction = nullptr;
+ }
+
+ if (mFD) {
+ PR_Close(mFD);
+ mFD = nullptr;
+ }
+ mSecInfo = nullptr;
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+void
+TLSFilterTransaction::Close(nsresult aReason)
+{
+ if (!mTransaction) {
+ return;
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ mTransaction->Close(aReason);
+ mTransaction = nullptr;
+}
+
+nsresult
+TLSFilterTransaction::OnReadSegment(const char *aData,
+ uint32_t aCount,
+ uint32_t *outCountRead)
+{
+ LOG(("TLSFilterTransaction %p OnReadSegment %d (buffered %d)\n",
+ this, aCount, mEncryptedTextUsed));
+
+ mReadSegmentBlocked = false;
+ MOZ_ASSERT(mSegmentReader);
+ if (!mSecInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ *outCountRead = 0;
+
+ // get rid of buffer first
+ if (mEncryptedTextUsed) {
+ rv = mSegmentReader->CommitToSegmentSize(mEncryptedTextUsed, mForce);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ return rv;
+ }
+
+ uint32_t amt;
+ rv = mSegmentReader->OnReadSegment(mEncryptedText.get(), mEncryptedTextUsed, &amt);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mEncryptedTextUsed -= amt;
+ if (mEncryptedTextUsed) {
+ memmove(mEncryptedText.get(), &mEncryptedText[amt], mEncryptedTextUsed);
+ return NS_OK;
+ }
+ }
+
+ // encrypt for network write
+ // write aData down the SSL layer into the FilterWrite() method where it will
+ // be queued into mEncryptedText. We need to copy it like this in order to
+ // guarantee atomic writes
+
+ EnsureBuffer(mEncryptedText, aCount + 4096,
+ 0, mEncryptedTextSize);
+
+ while (aCount > 0) {
+ int32_t written = PR_Write(mFD, aData, aCount);
+ LOG(("TLSFilterTransaction %p OnReadSegment PRWrite(%d) = %d %d\n",
+ this, aCount, written,
+ PR_GetError() == PR_WOULD_BLOCK_ERROR));
+
+ if (written < 1) {
+ if (*outCountRead) {
+ return NS_OK;
+ }
+ // mTransaction ReadSegments actually obscures this code, so
+ // keep it in a member var for this::ReadSegments to insepct. Similar
+ // to nsHttpConnection::mSocketOutCondition
+ mReadSegmentBlocked = (PR_GetError() == PR_WOULD_BLOCK_ERROR);
+ return mReadSegmentBlocked ? NS_BASE_STREAM_WOULD_BLOCK : NS_ERROR_FAILURE;
+ }
+ aCount -= written;
+ aData += written;
+ *outCountRead += written;
+ mNudgeCounter = 0;
+ }
+
+ LOG(("TLSFilterTransaction %p OnReadSegment2 (buffered %d)\n",
+ this, mEncryptedTextUsed));
+
+ uint32_t amt = 0;
+ if (mEncryptedTextUsed) {
+ // If we are tunneled on spdy CommitToSegmentSize will prevent partial
+ // writes that could interfere with multiplexing. H1 is fine with
+ // partial writes.
+ rv = mSegmentReader->CommitToSegmentSize(mEncryptedTextUsed, mForce);
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSegmentReader->OnReadSegment(mEncryptedText.get(), mEncryptedTextUsed, &amt);
+ }
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ // return OK because all the data was consumed and stored in this buffer
+ Connection()->TransactionHasDataToWrite(this);
+ return NS_OK;
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (amt == mEncryptedTextUsed) {
+ mEncryptedText = nullptr;
+ mEncryptedTextUsed = 0;
+ mEncryptedTextSize = 0;
+ } else {
+ memmove(mEncryptedText.get(), &mEncryptedText[amt], mEncryptedTextUsed - amt);
+ mEncryptedTextUsed -= amt;
+ }
+ return NS_OK;
+}
+
+int32_t
+TLSFilterTransaction::FilterOutput(const char *aBuf, int32_t aAmount)
+{
+ EnsureBuffer(mEncryptedText, mEncryptedTextUsed + aAmount,
+ mEncryptedTextUsed, mEncryptedTextSize);
+ memcpy(&mEncryptedText[mEncryptedTextUsed], aBuf, aAmount);
+ mEncryptedTextUsed += aAmount;
+ return aAmount;
+}
+
+nsresult
+TLSFilterTransaction::CommitToSegmentSize(uint32_t size, bool forceCommitment)
+{
+ if (!mSegmentReader) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // pad the commit by a little bit to leave room for encryption overhead
+ // this isn't foolproof and we may still have to buffer, but its a good start
+ mForce = forceCommitment;
+ return mSegmentReader->CommitToSegmentSize(size + 1024, forceCommitment);
+}
+
+nsresult
+TLSFilterTransaction::OnWriteSegment(char *aData,
+ uint32_t aCount,
+ uint32_t *outCountRead)
+{
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mSegmentWriter);
+ LOG(("TLSFilterTransaction::OnWriteSegment %p max=%d\n", this, aCount));
+ if (!mSecInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // this will call through to FilterInput to get data from the higher
+ // level connection before removing the local TLS layer
+ mFilterReadCode = NS_OK;
+ int32_t bytesRead = PR_Read(mFD, aData, aCount);
+ if (bytesRead == -1) {
+ if (PR_GetError() == PR_WOULD_BLOCK_ERROR) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+ *outCountRead = bytesRead;
+
+ if (NS_SUCCEEDED(mFilterReadCode) && !bytesRead) {
+ LOG(("TLSFilterTransaction::OnWriteSegment %p "
+ "Second layer of TLS stripping results in STREAM_CLOSED\n", this));
+ mFilterReadCode = NS_BASE_STREAM_CLOSED;
+ }
+
+ LOG(("TLSFilterTransaction::OnWriteSegment %p rv=%x didread=%d "
+ "2 layers of ssl stripped to plaintext\n", this, mFilterReadCode, bytesRead));
+ return mFilterReadCode;
+}
+
+int32_t
+TLSFilterTransaction::FilterInput(char *aBuf, int32_t aAmount)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mSegmentWriter);
+ LOG(("TLSFilterTransaction::FilterInput max=%d\n", aAmount));
+
+ uint32_t outCountRead = 0;
+ mFilterReadCode = mSegmentWriter->OnWriteSegment(aBuf, aAmount, &outCountRead);
+ if (NS_SUCCEEDED(mFilterReadCode) && outCountRead) {
+ LOG(("TLSFilterTransaction::FilterInput rv=%x read=%d input from net "
+ "1 layer stripped, 1 still on\n", mFilterReadCode, outCountRead));
+ if (mReadSegmentBlocked) {
+ mNudgeCounter = 0;
+ }
+ }
+ if (mFilterReadCode == NS_BASE_STREAM_WOULD_BLOCK) {
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ return -1;
+ }
+ return outCountRead;
+}
+
+nsresult
+TLSFilterTransaction::ReadSegments(nsAHttpSegmentReader *aReader,
+ uint32_t aCount, uint32_t *outCountRead)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("TLSFilterTransaction::ReadSegments %p max=%d\n", this, aCount));
+
+ if (!mTransaction) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mReadSegmentBlocked = false;
+ mSegmentReader = aReader;
+ nsresult rv = mTransaction->ReadSegments(this, aCount, outCountRead);
+ LOG(("TLSFilterTransaction %p called trans->ReadSegments rv=%x %d\n",
+ this, rv, *outCountRead));
+ if (NS_SUCCEEDED(rv) && mReadSegmentBlocked) {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ LOG(("TLSFilterTransaction %p read segment blocked found rv=%x\n",
+ this, rv));
+ Connection()->ForceSend();
+ }
+
+ return rv;
+}
+
+nsresult
+TLSFilterTransaction::WriteSegments(nsAHttpSegmentWriter *aWriter,
+ uint32_t aCount, uint32_t *outCountWritten)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("TLSFilterTransaction::WriteSegments %p max=%d\n", this, aCount));
+
+ if (!mTransaction) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mSegmentWriter = aWriter;
+ nsresult rv = mTransaction->WriteSegments(this, aCount, outCountWritten);
+ if (NS_SUCCEEDED(rv) && NS_FAILED(mFilterReadCode) && !(*outCountWritten)) {
+ // nsPipe turns failures into silent OK.. undo that!
+ rv = mFilterReadCode;
+ if (Connection() && (mFilterReadCode == NS_BASE_STREAM_WOULD_BLOCK)) {
+ Connection()->ResumeRecv();
+ }
+ }
+ LOG(("TLSFilterTransaction %p called trans->WriteSegments rv=%x %d\n",
+ this, rv, *outCountWritten));
+ return rv;
+}
+
+nsresult
+TLSFilterTransaction::GetTransactionSecurityInfo(nsISupports **outSecInfo)
+{
+ if (!mSecInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISupports> temp(mSecInfo);
+ temp.forget(outSecInfo);
+ return NS_OK;
+}
+
+nsresult
+TLSFilterTransaction::NudgeTunnel(NudgeTunnelCallback *aCallback)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("TLSFilterTransaction %p NudgeTunnel\n", this));
+ mNudgeCallback = nullptr;
+
+ if (!mSecInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t notUsed;
+ int32_t written = PR_Write(mFD, "", 0);
+ if ((written < 0) && (PR_GetError() != PR_WOULD_BLOCK_ERROR)) {
+ // fatal handshake failure
+ LOG(("TLSFilterTransaction %p Fatal Handshake Failure: %d\n", this, PR_GetError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ OnReadSegment("", 0, &notUsed);
+
+ // The SSL Layer does some unusual things with PR_Poll that makes it a bad
+ // match for multiplexed SSL sessions. We work around this by manually polling for
+ // the moment during the brief handshake phase or otherwise blocked on write.
+ // Thankfully this is a pretty unusual state. NSPR doesn't help us here -
+ // asserting when polling without the NSPR IO layer on the bottom of
+ // the stack. As a follow-on we can do some NSPR and maybe libssl changes
+ // to make this more event driven, but this is acceptable for getting started.
+
+ uint32_t counter = mNudgeCounter++;
+ uint32_t delay;
+
+ if (!counter) {
+ delay = 0;
+ } else if (counter < 8) { // up to 48ms at 6
+ delay = 6;
+ } else if (counter < 34) { // up to 499 ms at 17ms
+ delay = 17;
+ } else { // after that at 51ms (3 old windows ticks)
+ delay = 51;
+ }
+
+ if(!mTimer) {
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ }
+
+ mNudgeCallback = aCallback;
+ if (!mTimer ||
+ NS_FAILED(mTimer->InitWithCallback(this, delay, nsITimer::TYPE_ONE_SHOT))) {
+ return StartTimerCallback();
+ }
+
+ LOG(("TLSFilterTransaction %p NudgeTunnel timer started\n", this));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSFilterTransaction::Notify(nsITimer *timer)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("TLSFilterTransaction %p NudgeTunnel notify\n", this));
+
+ if (timer != mTimer) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ StartTimerCallback();
+ return NS_OK;
+}
+
+nsresult
+TLSFilterTransaction::StartTimerCallback()
+{
+ LOG(("TLSFilterTransaction %p NudgeTunnel StartTimerCallback %p\n",
+ this, mNudgeCallback.get()));
+
+ if (mNudgeCallback) {
+ // This class can be called re-entrantly, so cleanup m* before ->on()
+ RefPtr<NudgeTunnelCallback> cb(mNudgeCallback);
+ mNudgeCallback = nullptr;
+ cb->OnTunnelNudged(this);
+ }
+ return NS_OK;
+}
+
+PRStatus
+TLSFilterTransaction::GetPeerName(PRFileDesc *aFD, PRNetAddr*addr)
+{
+ NetAddr peeraddr;
+ TLSFilterTransaction *self = reinterpret_cast<TLSFilterTransaction *>(aFD->secret);
+
+ if (!self->mTransaction ||
+ NS_FAILED(self->mTransaction->Connection()->Transport()->GetPeerAddr(&peeraddr))) {
+ return PR_FAILURE;
+ }
+ NetAddrToPRNetAddr(&peeraddr, addr);
+ return PR_SUCCESS;
+}
+
+PRStatus
+TLSFilterTransaction::GetSocketOption(PRFileDesc *aFD, PRSocketOptionData *aOpt)
+{
+ if (aOpt->option == PR_SockOpt_Nonblocking) {
+ aOpt->value.non_blocking = PR_TRUE;
+ return PR_SUCCESS;
+ }
+ return PR_FAILURE;
+}
+
+PRStatus
+TLSFilterTransaction::SetSocketOption(PRFileDesc *aFD, const PRSocketOptionData *aOpt)
+{
+ return PR_FAILURE;
+}
+
+PRStatus
+TLSFilterTransaction::FilterClose(PRFileDesc *aFD)
+{
+ return PR_SUCCESS;
+}
+
+int32_t
+TLSFilterTransaction::FilterWrite(PRFileDesc *aFD, const void *aBuf, int32_t aAmount)
+{
+ TLSFilterTransaction *self = reinterpret_cast<TLSFilterTransaction *>(aFD->secret);
+ return self->FilterOutput(static_cast<const char *>(aBuf), aAmount);
+}
+
+int32_t
+TLSFilterTransaction::FilterSend(PRFileDesc *aFD, const void *aBuf, int32_t aAmount,
+ int , PRIntervalTime)
+{
+ return FilterWrite(aFD, aBuf, aAmount);
+}
+
+int32_t
+TLSFilterTransaction::FilterRead(PRFileDesc *aFD, void *aBuf, int32_t aAmount)
+{
+ TLSFilterTransaction *self = reinterpret_cast<TLSFilterTransaction *>(aFD->secret);
+ return self->FilterInput(static_cast<char *>(aBuf), aAmount);
+}
+
+int32_t
+TLSFilterTransaction::FilterRecv(PRFileDesc *aFD, void *aBuf, int32_t aAmount,
+ int , PRIntervalTime)
+{
+ return FilterRead(aFD, aBuf, aAmount);
+}
+
+/////
+// The other methods of TLSFilterTransaction just call mTransaction->method
+/////
+
+void
+TLSFilterTransaction::SetConnection(nsAHttpConnection *aConnection)
+{
+ if (!mTransaction) {
+ return;
+ }
+
+ mTransaction->SetConnection(aConnection);
+}
+
+nsAHttpConnection *
+TLSFilterTransaction::Connection()
+{
+ if (!mTransaction) {
+ return nullptr;
+ }
+ return mTransaction->Connection();
+}
+
+void
+TLSFilterTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **outCB)
+{
+ if (!mTransaction) {
+ return;
+ }
+ mTransaction->GetSecurityCallbacks(outCB);
+}
+
+void
+TLSFilterTransaction::OnTransportStatus(nsITransport* aTransport,
+ nsresult aStatus, int64_t aProgress)
+{
+ if (!mTransaction) {
+ return;
+ }
+ mTransaction->OnTransportStatus(aTransport, aStatus, aProgress);
+}
+
+nsHttpConnectionInfo *
+TLSFilterTransaction::ConnectionInfo()
+{
+ if (!mTransaction) {
+ return nullptr;
+ }
+ return mTransaction->ConnectionInfo();
+}
+
+bool
+TLSFilterTransaction::IsDone()
+{
+ if (!mTransaction) {
+ return true;
+ }
+ return mTransaction->IsDone();
+}
+
+nsresult
+TLSFilterTransaction::Status()
+{
+ if (!mTransaction) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return mTransaction->Status();
+}
+
+uint32_t
+TLSFilterTransaction::Caps()
+{
+ if (!mTransaction) {
+ return 0;
+ }
+
+ return mTransaction->Caps();
+}
+
+void
+TLSFilterTransaction::SetDNSWasRefreshed()
+{
+ if (!mTransaction) {
+ return;
+ }
+
+ mTransaction->SetDNSWasRefreshed();
+}
+
+uint64_t
+TLSFilterTransaction::Available()
+{
+ if (!mTransaction) {
+ return 0;
+ }
+
+ return mTransaction->Available();
+}
+
+void
+TLSFilterTransaction::SetProxyConnectFailed()
+{
+ if (!mTransaction) {
+ return;
+ }
+
+ mTransaction->SetProxyConnectFailed();
+}
+
+nsHttpRequestHead *
+TLSFilterTransaction::RequestHead()
+{
+ if (!mTransaction) {
+ return nullptr;
+ }
+
+ return mTransaction->RequestHead();
+}
+
+uint32_t
+TLSFilterTransaction::Http1xTransactionCount()
+{
+ if (!mTransaction) {
+ return 0;
+ }
+
+ return mTransaction->Http1xTransactionCount();
+}
+
+nsresult
+TLSFilterTransaction::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions)
+{
+ LOG(("TLSFilterTransaction::TakeSubTransactions [this=%p] mTransaction %p\n",
+ this, mTransaction.get()));
+
+ if (!mTransaction) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mTransaction->TakeSubTransactions(outTransactions) == NS_ERROR_NOT_IMPLEMENTED) {
+ outTransactions.AppendElement(mTransaction);
+ }
+ mTransaction = nullptr;
+
+ return NS_OK;
+}
+
+nsresult
+TLSFilterTransaction::SetProxiedTransaction(nsAHttpTransaction *aTrans)
+{
+ LOG(("TLSFilterTransaction::SetProxiedTransaction [this=%p] aTrans=%p\n",
+ this, aTrans));
+
+ mTransaction = aTrans;
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(mSecInfo));
+ if (secCtrl && callbacks) {
+ secCtrl->SetNotificationCallbacks(callbacks);
+ }
+
+ return NS_OK;
+}
+
+// AddTransaction is for adding pipelined subtransactions
+nsresult
+TLSFilterTransaction::AddTransaction(nsAHttpTransaction *aTrans)
+{
+ LOG(("TLSFilterTransaction::AddTransaction passing on subtransaction "
+ "[this=%p] aTrans=%p ,mTransaction=%p\n", this, aTrans, mTransaction.get()));
+
+ if (!mTransaction) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mTransaction->AddTransaction(aTrans);
+}
+
+uint32_t
+TLSFilterTransaction::PipelineDepth()
+{
+ if (!mTransaction) {
+ return 0;
+ }
+
+ return mTransaction->PipelineDepth();
+}
+
+nsresult
+TLSFilterTransaction::SetPipelinePosition(int32_t aPosition)
+{
+ if (!mTransaction) {
+ return NS_OK;
+ }
+
+ return mTransaction->SetPipelinePosition(aPosition);
+}
+
+int32_t
+TLSFilterTransaction::PipelinePosition()
+{
+ if (!mTransaction) {
+ return 1;
+ }
+
+ return mTransaction->PipelinePosition();
+}
+
+nsHttpPipeline *
+TLSFilterTransaction::QueryPipeline()
+{
+ if (!mTransaction) {
+ return nullptr;
+ }
+ return mTransaction->QueryPipeline();
+}
+
+bool
+TLSFilterTransaction::IsNullTransaction()
+{
+ if (!mTransaction) {
+ return false;
+ }
+ return mTransaction->IsNullTransaction();
+}
+
+NullHttpTransaction *
+TLSFilterTransaction::QueryNullTransaction()
+{
+ if (!mTransaction) {
+ return nullptr;
+ }
+ return mTransaction->QueryNullTransaction();
+}
+
+nsHttpTransaction *
+TLSFilterTransaction::QueryHttpTransaction()
+{
+ if (!mTransaction) {
+ return nullptr;
+ }
+ return mTransaction->QueryHttpTransaction();
+}
+
+
+class SocketInWrapper : public nsIAsyncInputStream
+ , public nsAHttpSegmentWriter
+{
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_NSIASYNCINPUTSTREAM(mStream->)
+
+ SocketInWrapper(nsIAsyncInputStream *aWrapped, TLSFilterTransaction *aFilter)
+ : mStream(aWrapped)
+ , mTLSFilter(aFilter)
+ { }
+
+ NS_IMETHOD Close() override
+ {
+ mTLSFilter = nullptr;
+ return mStream->Close();
+ }
+
+ NS_IMETHOD Available(uint64_t *_retval) override
+ {
+ return mStream->Available(_retval);
+ }
+
+ NS_IMETHOD IsNonBlocking(bool *_retval) override
+ {
+ return mStream->IsNonBlocking(_retval);
+ }
+
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, uint32_t aCount, uint32_t *_retval) override
+ {
+ return mStream->ReadSegments(aWriter, aClosure, aCount, _retval);
+ }
+
+ // finally, ones that don't get forwarded :)
+ NS_IMETHOD Read(char *aBuf, uint32_t aCount, uint32_t *_retval) override;
+ virtual nsresult OnWriteSegment(char *segment, uint32_t count, uint32_t *countWritten) override;
+
+private:
+ virtual ~SocketInWrapper() {};
+
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ RefPtr<TLSFilterTransaction> mTLSFilter;
+};
+
+nsresult
+SocketInWrapper::OnWriteSegment(char *segment, uint32_t count, uint32_t *countWritten)
+{
+ LOG(("SocketInWrapper OnWriteSegment %d %p filter=%p\n", count, this, mTLSFilter.get()));
+
+ nsresult rv = mStream->Read(segment, count, countWritten);
+ LOG(("SocketInWrapper OnWriteSegment %p wrapped read %x %d\n",
+ this, rv, *countWritten));
+ return rv;
+}
+
+NS_IMETHODIMP
+SocketInWrapper::Read(char *aBuf, uint32_t aCount, uint32_t *_retval)
+{
+ LOG(("SocketInWrapper Read %d %p filter=%p\n", aCount, this, mTLSFilter.get()));
+
+ if (!mTLSFilter) {
+ return NS_ERROR_UNEXPECTED; // protect potentially dangling mTLSFilter
+ }
+
+ // mTLSFilter->mSegmentWriter MUST be this at ctor time
+ return mTLSFilter->OnWriteSegment(aBuf, aCount, _retval);
+}
+
+class SocketOutWrapper : public nsIAsyncOutputStream
+ , public nsAHttpSegmentReader
+{
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_NSIASYNCOUTPUTSTREAM(mStream->)
+
+ SocketOutWrapper(nsIAsyncOutputStream *aWrapped, TLSFilterTransaction *aFilter)
+ : mStream(aWrapped)
+ , mTLSFilter(aFilter)
+ { }
+
+ NS_IMETHOD Close() override
+ {
+ mTLSFilter = nullptr;
+ return mStream->Close();
+ }
+
+ NS_IMETHOD Flush() override
+ {
+ return mStream->Flush();
+ }
+
+ NS_IMETHOD IsNonBlocking(bool *_retval) override
+ {
+ return mStream->IsNonBlocking(_retval);
+ }
+
+ NS_IMETHOD WriteSegments(nsReadSegmentFun aReader, void *aClosure, uint32_t aCount, uint32_t *_retval) override
+ {
+ return mStream->WriteSegments(aReader, aClosure, aCount, _retval);
+ }
+
+ NS_IMETHOD WriteFrom(nsIInputStream *aFromStream, uint32_t aCount, uint32_t *_retval) override
+ {
+ return mStream->WriteFrom(aFromStream, aCount, _retval);
+ }
+
+ // finally, ones that don't get forwarded :)
+ NS_IMETHOD Write(const char *aBuf, uint32_t aCount, uint32_t *_retval) override;
+ virtual nsresult OnReadSegment(const char *segment, uint32_t count, uint32_t *countRead) override;
+
+private:
+ virtual ~SocketOutWrapper() {};
+
+ nsCOMPtr<nsIAsyncOutputStream> mStream;
+ RefPtr<TLSFilterTransaction> mTLSFilter;
+};
+
+nsresult
+SocketOutWrapper::OnReadSegment(const char *segment, uint32_t count, uint32_t *countWritten)
+{
+ return mStream->Write(segment, count, countWritten);
+}
+
+NS_IMETHODIMP
+SocketOutWrapper::Write(const char *aBuf, uint32_t aCount, uint32_t *_retval)
+{
+ LOG(("SocketOutWrapper Write %d %p filter=%p\n", aCount, this, mTLSFilter.get()));
+
+ // mTLSFilter->mSegmentReader MUST be this at ctor time
+ if (!mTLSFilter) {
+ return NS_ERROR_UNEXPECTED; // protect potentially dangling mTLSFilter
+ }
+
+ return mTLSFilter->OnReadSegment(aBuf, aCount, _retval);
+}
+
+void
+TLSFilterTransaction::newIODriver(nsIAsyncInputStream *aSocketIn,
+ nsIAsyncOutputStream *aSocketOut,
+ nsIAsyncInputStream **outSocketIn,
+ nsIAsyncOutputStream **outSocketOut)
+{
+ SocketInWrapper *inputWrapper = new SocketInWrapper(aSocketIn, this);
+ mSegmentWriter = inputWrapper;
+ nsCOMPtr<nsIAsyncInputStream> newIn(inputWrapper);
+ newIn.forget(outSocketIn);
+
+ SocketOutWrapper *outputWrapper = new SocketOutWrapper(aSocketOut, this);
+ mSegmentReader = outputWrapper;
+ nsCOMPtr<nsIAsyncOutputStream> newOut(outputWrapper);
+ newOut.forget(outSocketOut);
+}
+
+SpdyConnectTransaction *
+TLSFilterTransaction::QuerySpdyConnectTransaction()
+{
+ if (!mTransaction) {
+ return nullptr;
+ }
+ return mTransaction->QuerySpdyConnectTransaction();
+}
+
+class SocketTransportShim : public nsISocketTransport
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSISOCKETTRANSPORT
+
+ explicit SocketTransportShim(nsISocketTransport *aWrapped)
+ : mWrapped(aWrapped)
+ {};
+
+private:
+ virtual ~SocketTransportShim() {};
+
+ nsCOMPtr<nsISocketTransport> mWrapped;
+};
+
+class OutputStreamShim : public nsIAsyncOutputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ friend class SpdyConnectTransaction;
+
+ explicit OutputStreamShim(SpdyConnectTransaction *aTrans)
+ : mCallback(nullptr)
+ , mStatus(NS_OK)
+ {
+ mWeakTrans = do_GetWeakReference(aTrans);
+ }
+
+private:
+ virtual ~OutputStreamShim() {};
+
+ nsWeakPtr mWeakTrans; // SpdyConnectTransaction *
+ nsIOutputStreamCallback *mCallback;
+ nsresult mStatus;
+};
+
+class InputStreamShim : public nsIAsyncInputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ friend class SpdyConnectTransaction;
+
+ explicit InputStreamShim(SpdyConnectTransaction *aTrans)
+ : mCallback(nullptr)
+ , mStatus(NS_OK)
+ {
+ mWeakTrans = do_GetWeakReference(aTrans);
+ }
+
+private:
+ virtual ~InputStreamShim() {};
+
+ nsWeakPtr mWeakTrans; // SpdyConnectTransaction *
+ nsIInputStreamCallback *mCallback;
+ nsresult mStatus;
+};
+
+SpdyConnectTransaction::SpdyConnectTransaction(nsHttpConnectionInfo *ci,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps,
+ nsHttpTransaction *trans,
+ nsAHttpConnection *session)
+ : NullHttpTransaction(ci, callbacks, caps | NS_HTTP_ALLOW_KEEPALIVE)
+ , mConnectStringOffset(0)
+ , mSession(session)
+ , mSegmentReader(nullptr)
+ , mInputDataSize(0)
+ , mInputDataUsed(0)
+ , mInputDataOffset(0)
+ , mOutputDataSize(0)
+ , mOutputDataUsed(0)
+ , mOutputDataOffset(0)
+ , mForcePlainText(false)
+{
+ LOG(("SpdyConnectTransaction ctor %p\n", this));
+
+ mTimestampSyn = TimeStamp::Now();
+ mRequestHead = new nsHttpRequestHead();
+ nsHttpConnection::MakeConnectString(trans, mRequestHead, mConnectString);
+ mDrivingTransaction = trans;
+}
+
+SpdyConnectTransaction::~SpdyConnectTransaction()
+{
+ LOG(("SpdyConnectTransaction dtor %p\n", this));
+
+ if (mDrivingTransaction) {
+ // requeue it I guess. This should be gone.
+ gHttpHandler->InitiateTransaction(mDrivingTransaction,
+ mDrivingTransaction->Priority());
+ }
+}
+
+void
+SpdyConnectTransaction::ForcePlainText()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(!mInputDataUsed && !mInputDataSize && !mInputDataOffset);
+ MOZ_ASSERT(!mForcePlainText);
+ MOZ_ASSERT(!mTunnelTransport, "call before mapstreamtohttpconnection");
+
+ mForcePlainText = true;
+ return;
+}
+
+void
+SpdyConnectTransaction::MapStreamToHttpConnection(nsISocketTransport *aTransport,
+ nsHttpConnectionInfo *aConnInfo)
+{
+ mConnInfo = aConnInfo;
+
+ mTunnelTransport = new SocketTransportShim(aTransport);
+ mTunnelStreamIn = new InputStreamShim(this);
+ mTunnelStreamOut = new OutputStreamShim(this);
+ mTunneledConn = new nsHttpConnection();
+
+ // this new http connection has a specific hashkey (i.e. to a particular
+ // host via the tunnel) and is associated with the tunnel streams
+ LOG(("SpdyConnectTransaction new httpconnection %p %s\n",
+ mTunneledConn.get(), aConnInfo->HashKey().get()));
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ GetSecurityCallbacks(getter_AddRefs(callbacks));
+ mTunneledConn->SetTransactionCaps(Caps());
+ MOZ_ASSERT(aConnInfo->UsingHttpsProxy());
+ TimeDuration rtt = TimeStamp::Now() - mTimestampSyn;
+ mTunneledConn->Init(aConnInfo,
+ gHttpHandler->ConnMgr()->MaxRequestDelay(),
+ mTunnelTransport, mTunnelStreamIn, mTunnelStreamOut,
+ true, callbacks,
+ PR_MillisecondsToInterval(
+ static_cast<uint32_t>(rtt.ToMilliseconds())));
+ if (mForcePlainText) {
+ mTunneledConn->ForcePlainText();
+ } else {
+ mTunneledConn->SetupSecondaryTLS();
+ mTunneledConn->SetInSpdyTunnel(true);
+ }
+
+ // make the originating transaction stick to the tunneled conn
+ RefPtr<nsAHttpConnection> wrappedConn =
+ gHttpHandler->ConnMgr()->MakeConnectionHandle(mTunneledConn);
+ mDrivingTransaction->SetConnection(wrappedConn);
+ mDrivingTransaction->MakeSticky();
+
+ // jump the priority and start the dispatcher
+ gHttpHandler->InitiateTransaction(
+ mDrivingTransaction, nsISupportsPriority::PRIORITY_HIGHEST - 60);
+ mDrivingTransaction = nullptr;
+}
+
+nsresult
+SpdyConnectTransaction::Flush(uint32_t count, uint32_t *countRead)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("SpdyConnectTransaction::Flush %p count %d avail %d\n",
+ this, count, mOutputDataUsed - mOutputDataOffset));
+
+ if (!mSegmentReader) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *countRead = 0;
+ count = std::min(count, (mOutputDataUsed - mOutputDataOffset));
+ if (count) {
+ nsresult rv;
+ rv = mSegmentReader->OnReadSegment(&mOutputData[mOutputDataOffset],
+ count, countRead);
+ if (NS_FAILED(rv) && (rv != NS_BASE_STREAM_WOULD_BLOCK)) {
+ LOG(("SpdyConnectTransaction::Flush %p Error %x\n", this, rv));
+ CreateShimError(rv);
+ return rv;
+ }
+ }
+
+ mOutputDataOffset += *countRead;
+ if (mOutputDataOffset == mOutputDataUsed) {
+ mOutputDataOffset = mOutputDataUsed = 0;
+ }
+ if (!(*countRead)) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ if (mOutputDataUsed != mOutputDataOffset) {
+ LOG(("SpdyConnectTransaction::Flush %p Incomplete %d\n",
+ this, mOutputDataUsed - mOutputDataOffset));
+ mSession->TransactionHasDataToWrite(this);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+SpdyConnectTransaction::ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("SpdyConnectTransaction::ReadSegments %p count %d conn %p\n",
+ this, count, mTunneledConn.get()));
+
+ mSegmentReader = reader;
+
+ // spdy stream carrying tunnel is not setup yet.
+ if (!mTunneledConn) {
+ uint32_t toWrite = mConnectString.Length() - mConnectStringOffset;
+ toWrite = std::min(toWrite, count);
+ *countRead = toWrite;
+ if (toWrite) {
+ nsresult rv = mSegmentReader->
+ OnReadSegment(mConnectString.BeginReading() + mConnectStringOffset,
+ toWrite, countRead);
+ if (NS_FAILED(rv) && (rv != NS_BASE_STREAM_WOULD_BLOCK)) {
+ LOG(("SpdyConnectTransaction::ReadSegments %p OnReadSegmentError %x\n",
+ this, rv));
+ CreateShimError(rv);
+ } else {
+ mConnectStringOffset += toWrite;
+ if (mConnectString.Length() == mConnectStringOffset) {
+ mConnectString.Truncate();
+ mConnectStringOffset = 0;
+ }
+ }
+ return rv;
+ }
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ if (mForcePlainText) {
+ // this path just ignores sending the request so that we can
+ // send a synthetic reply in writesegments()
+ LOG(("SpdyConnectTransaciton::ReadSegments %p dropping %d output bytes "
+ "due to synthetic reply\n", this, mOutputDataUsed - mOutputDataOffset));
+ *countRead = mOutputDataUsed - mOutputDataOffset;
+ mOutputDataOffset = mOutputDataUsed = 0;
+ mTunneledConn->DontReuse();
+ return NS_OK;
+ }
+
+ *countRead = 0;
+ Flush(count, countRead);
+ if (!mTunnelStreamOut->mCallback) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ nsresult rv =
+ mTunnelStreamOut->mCallback->OnOutputStreamReady(mTunnelStreamOut);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint32_t subtotal;
+ count -= *countRead;
+ rv = Flush(count, &subtotal);
+ *countRead += subtotal;
+ return rv;
+}
+
+void
+SpdyConnectTransaction::CreateShimError(nsresult code)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(NS_FAILED(code));
+
+ if (mTunnelStreamOut && NS_SUCCEEDED(mTunnelStreamOut->mStatus)) {
+ mTunnelStreamOut->mStatus = code;
+ }
+
+ if (mTunnelStreamIn && NS_SUCCEEDED(mTunnelStreamIn->mStatus)) {
+ mTunnelStreamIn->mStatus = code;
+ }
+
+ if (mTunnelStreamIn && mTunnelStreamIn->mCallback) {
+ mTunnelStreamIn->mCallback->OnInputStreamReady(mTunnelStreamIn);
+ }
+
+ if (mTunnelStreamOut && mTunnelStreamOut->mCallback) {
+ mTunnelStreamOut->mCallback->OnOutputStreamReady(mTunnelStreamOut);
+ }
+}
+
+nsresult
+SpdyConnectTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count,
+ uint32_t *countWritten)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("SpdyConnectTransaction::WriteSegments %p max=%d cb=%p\n",
+ this, count, mTunneledConn ? mTunnelStreamIn->mCallback : nullptr));
+
+ // first call into the tunnel stream to get the demux'd data out of the
+ // spdy session.
+ EnsureBuffer(mInputData, mInputDataUsed + count, mInputDataUsed, mInputDataSize);
+ nsresult rv = writer->OnWriteSegment(&mInputData[mInputDataUsed],
+ count, countWritten);
+ if (NS_FAILED(rv)) {
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG(("SpdyConnectTransaction::WriteSegments wrapped writer %p Error %x\n", this, rv));
+ CreateShimError(rv);
+ }
+ return rv;
+ }
+ mInputDataUsed += *countWritten;
+ LOG(("SpdyConnectTransaction %p %d new bytes [%d total] of ciphered data buffered\n",
+ this, *countWritten, mInputDataUsed - mInputDataOffset));
+
+ if (!mTunneledConn || !mTunnelStreamIn->mCallback) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = mTunnelStreamIn->mCallback->OnInputStreamReady(mTunnelStreamIn);
+ LOG(("SpdyConnectTransaction::WriteSegments %p "
+ "after InputStreamReady callback %d total of ciphered data buffered rv=%x\n",
+ this, mInputDataUsed - mInputDataOffset, rv));
+ LOG(("SpdyConnectTransaction::WriteSegments %p "
+ "goodput %p out %llu\n", this, mTunneledConn.get(),
+ mTunneledConn->ContentBytesWritten()));
+ if (NS_SUCCEEDED(rv) && !mTunneledConn->ContentBytesWritten()) {
+ mTunnelStreamOut->AsyncWait(mTunnelStreamOut->mCallback, 0, 0, nullptr);
+ }
+ return rv;
+}
+
+bool
+SpdyConnectTransaction::ConnectedReadyForInput()
+{
+ return mTunneledConn && mTunnelStreamIn->mCallback;
+}
+
+nsHttpRequestHead *
+SpdyConnectTransaction::RequestHead()
+{
+ return mRequestHead;
+}
+
+void
+SpdyConnectTransaction::Close(nsresult code)
+{
+ LOG(("SpdyConnectTransaction close %p %x\n", this, code));
+
+ NullHttpTransaction::Close(code);
+ if (NS_FAILED(code) && (code != NS_BASE_STREAM_WOULD_BLOCK)) {
+ CreateShimError(code);
+ } else {
+ CreateShimError(NS_BASE_STREAM_CLOSED);
+ }
+}
+
+NS_IMETHODIMP
+OutputStreamShim::AsyncWait(nsIOutputStreamCallback *callback,
+ unsigned int, unsigned int, nsIEventTarget *target)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ bool currentThread;
+
+ if (target &&
+ (NS_FAILED(target->IsOnCurrentThread(&currentThread)) || !currentThread)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG(("OutputStreamShim::AsyncWait %p callback %p\n", this, callback));
+ mCallback = callback;
+
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ trans->mSession->TransactionHasDataToWrite(trans);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::CloseWithStatus(nsresult reason)
+{
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ trans->mSession->CloseTransaction(trans, reason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::Close()
+{
+ return CloseWithStatus(NS_OK);
+}
+
+NS_IMETHODIMP
+OutputStreamShim::Flush()
+{
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ uint32_t count = trans->mOutputDataUsed - trans->mOutputDataOffset;
+ if (!count) {
+ return NS_OK;
+ }
+
+ uint32_t countRead;
+ nsresult rv = trans->Flush(count, &countRead);
+ LOG(("OutputStreamShim::Flush %p before %d after %d\n",
+ this, count, trans->mOutputDataUsed - trans->mOutputDataOffset));
+ return rv;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::Write(const char * aBuf, uint32_t aCount, uint32_t *_retval)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if ((trans->mOutputDataUsed + aCount) >= 512000) {
+ *_retval = 0;
+ // time for some flow control;
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ EnsureBuffer(trans->mOutputData, trans->mOutputDataUsed + aCount,
+ trans->mOutputDataUsed, trans->mOutputDataSize);
+ memcpy(&trans->mOutputData[trans->mOutputDataUsed], aBuf, aCount);
+ trans->mOutputDataUsed += aCount;
+ *_retval = aCount;
+ LOG(("OutputStreamShim::Write %p new %d total %d\n", this, aCount, trans->mOutputDataUsed));
+
+ trans->mSession->TransactionHasDataToWrite(trans);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::WriteFrom(nsIInputStream *aFromStream, uint32_t aCount, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::WriteSegments(nsReadSegmentFun aReader, void *aClosure, uint32_t aCount, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::IsNonBlocking(bool *_retval)
+{
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::AsyncWait(nsIInputStreamCallback *callback,
+ unsigned int, unsigned int, nsIEventTarget *target)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ bool currentThread;
+
+ if (target &&
+ (NS_FAILED(target->IsOnCurrentThread(&currentThread)) || !currentThread)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG(("InputStreamShim::AsyncWait %p callback %p\n", this, callback));
+ mCallback = callback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::CloseWithStatus(nsresult reason)
+{
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ trans->mSession->CloseTransaction(trans, reason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::Close()
+{
+ return CloseWithStatus(NS_OK);
+}
+
+NS_IMETHODIMP
+InputStreamShim::Available(uint64_t *_retval)
+{
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *_retval = trans->mInputDataUsed - trans->mInputDataOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::Read(char *aBuf, uint32_t aCount, uint32_t *_retval)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ uint32_t avail = trans->mInputDataUsed - trans->mInputDataOffset;
+ uint32_t tocopy = std::min(aCount, avail);
+ *_retval = tocopy;
+ memcpy(aBuf, &trans->mInputData[trans->mInputDataOffset], tocopy);
+ trans->mInputDataOffset += tocopy;
+ if (trans->mInputDataOffset == trans->mInputDataUsed) {
+ trans->mInputDataOffset = trans->mInputDataUsed = 0;
+ }
+
+ return tocopy ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure,
+ uint32_t aCount, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InputStreamShim::IsNonBlocking(bool *_retval)
+{
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetKeepaliveEnabled(bool aKeepaliveEnabled)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetKeepaliveVals(int32_t keepaliveIdleTime, int32_t keepaliveRetryInterval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetSecurityCallbacks(nsIInterfaceRequestor *aSecurityCallbacks)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount, nsIInputStream * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount, nsIOutputStream * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::Close(nsresult aReason)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetEventSink(nsITransportEventSink *aSink, nsIEventTarget *aEventTarget)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::Bind(NetAddr *aLocalAddr)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#define FWD_TS_PTR(fx, ts) NS_IMETHODIMP \
+SocketTransportShim::fx(ts *arg) { return mWrapped->fx(arg); }
+
+#define FWD_TS_ADDREF(fx, ts) NS_IMETHODIMP \
+SocketTransportShim::fx(ts **arg) { return mWrapped->fx(arg); }
+
+#define FWD_TS(fx, ts) NS_IMETHODIMP \
+SocketTransportShim::fx(ts arg) { return mWrapped->fx(arg); }
+
+FWD_TS_PTR(GetKeepaliveEnabled, bool);
+FWD_TS_PTR(GetSendBufferSize, uint32_t);
+FWD_TS(SetSendBufferSize, uint32_t);
+FWD_TS_PTR(GetPort, int32_t);
+FWD_TS_PTR(GetPeerAddr, mozilla::net::NetAddr);
+FWD_TS_PTR(GetSelfAddr, mozilla::net::NetAddr);
+FWD_TS_ADDREF(GetScriptablePeerAddr, nsINetAddr);
+FWD_TS_ADDREF(GetScriptableSelfAddr, nsINetAddr);
+FWD_TS_ADDREF(GetSecurityInfo, nsISupports);
+FWD_TS_ADDREF(GetSecurityCallbacks, nsIInterfaceRequestor);
+FWD_TS_PTR(IsAlive, bool);
+FWD_TS_PTR(GetConnectionFlags, uint32_t);
+FWD_TS(SetConnectionFlags, uint32_t);
+FWD_TS_PTR(GetRecvBufferSize, uint32_t);
+FWD_TS(SetRecvBufferSize, uint32_t);
+
+nsresult
+SocketTransportShim::GetOriginAttributes(mozilla::NeckoOriginAttributes* aOriginAttributes)
+{
+ return mWrapped->GetOriginAttributes(aOriginAttributes);
+}
+
+nsresult
+SocketTransportShim::SetOriginAttributes(const mozilla::NeckoOriginAttributes& aOriginAttributes)
+{
+ return mWrapped->SetOriginAttributes(aOriginAttributes);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetScriptableOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aOriginAttributes)
+{
+ return mWrapped->GetScriptableOriginAttributes(aCx, aOriginAttributes);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetScriptableOriginAttributes(JSContext* aCx,
+ JS::Handle<JS::Value> aOriginAttributes)
+{
+ return mWrapped->SetScriptableOriginAttributes(aCx, aOriginAttributes);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetHost(nsACString & aHost)
+{
+ return mWrapped->GetHost(aHost);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetTimeout(uint32_t aType, uint32_t *_retval)
+{
+ return mWrapped->GetTimeout(aType, _retval);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetNetworkInterfaceId(nsACString_internal &aNetworkInterfaceId)
+{
+ return mWrapped->GetNetworkInterfaceId(aNetworkInterfaceId);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetNetworkInterfaceId(const nsACString_internal &aNetworkInterfaceId)
+{
+ return mWrapped->SetNetworkInterfaceId(aNetworkInterfaceId);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetTimeout(uint32_t aType, uint32_t aValue)
+{
+ return mWrapped->SetTimeout(aType, aValue);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetQoSBits(uint8_t *aQoSBits)
+{
+ return mWrapped->GetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetQoSBits(uint8_t aQoSBits)
+{
+ return mWrapped->SetQoSBits(aQoSBits);
+}
+
+NS_IMPL_ISUPPORTS(TLSFilterTransaction, nsITimerCallback)
+NS_IMPL_ISUPPORTS(SocketTransportShim, nsISocketTransport, nsITransport)
+NS_IMPL_ISUPPORTS(InputStreamShim, nsIInputStream, nsIAsyncInputStream)
+NS_IMPL_ISUPPORTS(OutputStreamShim, nsIOutputStream, nsIAsyncOutputStream)
+NS_IMPL_ISUPPORTS(SocketInWrapper, nsIAsyncInputStream)
+NS_IMPL_ISUPPORTS(SocketOutWrapper, nsIAsyncOutputStream)
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/TunnelUtils.h b/netwerk/protocol/http/TunnelUtils.h
new file mode 100644
index 000000000..20cfaf7ee
--- /dev/null
+++ b/netwerk/protocol/http/TunnelUtils.h
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_TLSFilterTransaction_h
+#define mozilla_net_TLSFilterTransaction_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsAHttpTransaction.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsISocketTransport.h"
+#include "nsITimer.h"
+#include "NullHttpTransaction.h"
+#include "mozilla/TimeStamp.h"
+#include "prio.h"
+
+// a TLSFilterTransaction wraps another nsAHttpTransaction but
+// applies a encode/decode filter of TLS onto the ReadSegments
+// and WriteSegments data. It is not used for basic https://
+// but it is used for supplemental TLS tunnels - such as those
+// needed by CONNECT tunnels in HTTP/2 or even CONNECT tunnels when
+// the underlying proxy connection is already running TLS
+//
+// HTTP/2 CONNECT tunnels cannot use pushed IO layers because of
+// the multiplexing involved on the base stream. i.e. the base stream
+// once it is decrypted may have parts that are encrypted with a
+// variety of keys, or none at all
+
+/* ************************************************************************
+The input path of http over a spdy CONNECT tunnel once it is established as a stream
+
+note the "real http transaction" can be either a http/1 transaction or another spdy session
+inside the tunnel.
+
+ nsHttpConnection::OnInputStreamReady (real socket)
+ nsHttpConnection::OnSocketReadable()
+ SpdySession::WriteSegment()
+ SpdyStream::WriteSegment (tunnel stream)
+ SpdyConnectTransaction::WriteSegment
+ SpdyStream::OnWriteSegment(tunnel stream)
+ SpdySession::OnWriteSegment()
+ SpdySession::NetworkRead()
+ nsHttpConnection::OnWriteSegment (real socket)
+ realSocketIn->Read() return data from network
+
+now pop the stack back up to SpdyConnectTransaction::WriteSegment, the data
+that has been read is stored mInputData
+
+ SpdyConnectTransaction.mTunneledConn::OnInputStreamReady(mTunnelStreamIn)
+ SpdyConnectTransaction.mTunneledConn::OnSocketReadable()
+ TLSFilterTransaction::WriteSegment()
+ nsHttpTransaction::WriteSegment(real http transaction)
+ TLSFilterTransaction::OnWriteSegment() removes tls on way back up stack
+ SpdyConnectTransaction.mTunneledConn::OnWriteSegment()
+ SpdyConnectTransaction.mTunneledConn.mTunnelStreamIn->Read() // gets data from mInputData
+
+The output path works similarly:
+ nsHttpConnection::OnOutputStreamReady (real socket)
+ nsHttpConnection::OnSocketWritable()
+ SpdySession::ReadSegments (locates tunnel)
+ SpdyStream::ReadSegments (tunnel stream)
+ SpdyConnectTransaction::ReadSegments()
+ SpdyConnectTransaction.mTunneledConn::OnOutputStreamReady (tunnel connection)
+ SpdyConnectTransaction.mTunneledConn::OnSocketWritable (tunnel connection)
+ TLSFilterTransaction::ReadSegment()
+ nsHttpTransaction::ReadSegment (real http transaction generates plaintext on way down)
+ TLSFilterTransaction::OnReadSegment (BUF and LEN gets encrypted here on way down)
+ SpdyConnectTransaction.mTunneledConn::OnReadSegment (BUF and LEN) (tunnel connection)
+ SpdyConnectTransaction.mTunneledConn.mTunnelStreamOut->Write(BUF, LEN) .. get stored in mOutputData
+
+Now pop the stack back up to SpdyConnectTransaction::ReadSegment(), where it has
+the encrypted text available in mOutputData
+
+ SpdyStream->OnReadSegment(BUF,LEN) from mOutputData. Tunnel stream
+ SpdySession->OnReadSegment() // encrypted data gets put in a data frame
+ nsHttpConnection->OnReadSegment()
+ realSocketOut->write() writes data to network
+
+**************************************************************************/
+
+struct PRSocketOptionData;
+
+namespace mozilla { namespace net {
+
+class nsHttpRequestHead;
+class NullHttpTransaction;
+class TLSFilterTransaction;
+
+class NudgeTunnelCallback : public nsISupports
+{
+public:
+ virtual void OnTunnelNudged(TLSFilterTransaction *) = 0;
+};
+
+#define NS_DECL_NUDGETUNNELCALLBACK void OnTunnelNudged(TLSFilterTransaction *) override;
+
+class TLSFilterTransaction final
+ : public nsAHttpTransaction
+ , public nsAHttpSegmentReader
+ , public nsAHttpSegmentWriter
+ , public nsITimerCallback
+{
+ ~TLSFilterTransaction();
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ NS_DECL_NSITIMERCALLBACK
+
+ TLSFilterTransaction(nsAHttpTransaction *aWrappedTransaction,
+ const char *tlsHost, int32_t tlsPort,
+ nsAHttpSegmentReader *reader,
+ nsAHttpSegmentWriter *writer);
+
+ const nsAHttpTransaction *Transaction() const { return mTransaction.get(); }
+ nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment) override;
+ nsresult GetTransactionSecurityInfo(nsISupports **) override;
+ nsresult NudgeTunnel(NudgeTunnelCallback *callback);
+ nsresult SetProxiedTransaction(nsAHttpTransaction *aTrans);
+ void newIODriver(nsIAsyncInputStream *aSocketIn,
+ nsIAsyncOutputStream *aSocketOut,
+ nsIAsyncInputStream **outSocketIn,
+ nsIAsyncOutputStream **outSocketOut);
+
+ // nsAHttpTransaction overloads
+ nsHttpPipeline *QueryPipeline() override;
+ bool IsNullTransaction() override;
+ NullHttpTransaction *QueryNullTransaction() override;
+ nsHttpTransaction *QueryHttpTransaction() override;
+ SpdyConnectTransaction *QuerySpdyConnectTransaction() override;
+
+private:
+ nsresult StartTimerCallback();
+ void Cleanup();
+ int32_t FilterOutput(const char *aBuf, int32_t aAmount);
+ int32_t FilterInput(char *aBuf, int32_t aAmount);
+
+ static PRStatus GetPeerName(PRFileDesc *fd, PRNetAddr*addr);
+ static PRStatus GetSocketOption(PRFileDesc *fd, PRSocketOptionData *data);
+ static PRStatus SetSocketOption(PRFileDesc *fd, const PRSocketOptionData *data);
+ static int32_t FilterWrite(PRFileDesc *fd, const void *buf, int32_t amount);
+ static int32_t FilterRead(PRFileDesc *fd, void *buf, int32_t amount);
+ static int32_t FilterSend(PRFileDesc *fd, const void *buf, int32_t amount, int flags,
+ PRIntervalTime timeout);
+ static int32_t FilterRecv(PRFileDesc *fd, void *buf, int32_t amount, int flags,
+ PRIntervalTime timeout);
+ static PRStatus FilterClose(PRFileDesc *fd);
+
+private:
+ RefPtr<nsAHttpTransaction> mTransaction;
+ nsCOMPtr<nsISupports> mSecInfo;
+ nsCOMPtr<nsITimer> mTimer;
+ RefPtr<NudgeTunnelCallback> mNudgeCallback;
+
+ // buffered network output, after encryption
+ UniquePtr<char[]> mEncryptedText;
+ uint32_t mEncryptedTextUsed;
+ uint32_t mEncryptedTextSize;
+
+ PRFileDesc *mFD;
+ nsAHttpSegmentReader *mSegmentReader;
+ nsAHttpSegmentWriter *mSegmentWriter;
+
+ nsresult mFilterReadCode;
+ bool mForce;
+ bool mReadSegmentBlocked;
+ uint32_t mNudgeCounter;
+};
+
+class SocketTransportShim;
+class InputStreamShim;
+class OutputStreamShim;
+class nsHttpConnection;
+
+class SpdyConnectTransaction final : public NullHttpTransaction
+{
+public:
+ SpdyConnectTransaction(nsHttpConnectionInfo *ci,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps,
+ nsHttpTransaction *trans,
+ nsAHttpConnection *session);
+ ~SpdyConnectTransaction();
+
+ SpdyConnectTransaction *QuerySpdyConnectTransaction() override { return this; }
+
+ // A transaction is forced into plaintext when it is intended to be used as a CONNECT
+ // tunnel but the setup fails. The plaintext only carries the CONNECT error.
+ void ForcePlainText();
+ void MapStreamToHttpConnection(nsISocketTransport *aTransport,
+ nsHttpConnectionInfo *aConnInfo);
+
+ nsresult ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead) override final;
+ nsresult WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count, uint32_t *countWritten) override final;
+ nsHttpRequestHead *RequestHead() override final;
+ void Close(nsresult reason) override final;
+
+ // ConnectedReadyForInput() tests whether the spdy connect transaction is attached to
+ // an nsHttpConnection that can properly deal with flow control, etc..
+ bool ConnectedReadyForInput();
+
+private:
+ friend class InputStreamShim;
+ friend class OutputStreamShim;
+
+ nsresult Flush(uint32_t count, uint32_t *countRead);
+ void CreateShimError(nsresult code);
+
+ nsCString mConnectString;
+ uint32_t mConnectStringOffset;
+
+ nsAHttpConnection *mSession;
+ nsAHttpSegmentReader *mSegmentReader;
+
+ UniquePtr<char[]> mInputData;
+ uint32_t mInputDataSize;
+ uint32_t mInputDataUsed;
+ uint32_t mInputDataOffset;
+
+ UniquePtr<char[]> mOutputData;
+ uint32_t mOutputDataSize;
+ uint32_t mOutputDataUsed;
+ uint32_t mOutputDataOffset;
+
+ bool mForcePlainText;
+ TimeStamp mTimestampSyn;
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+
+ // mTunneledConn, mTunnelTransport, mTunnelStreamIn, mTunnelStreamOut
+ // are the connectors to the "real" http connection. They are created
+ // together when the tunnel setup is complete and a static reference is held
+ // for the lifetime of the tunnel.
+ RefPtr<nsHttpConnection> mTunneledConn;
+ RefPtr<SocketTransportShim> mTunnelTransport;
+ RefPtr<InputStreamShim> mTunnelStreamIn;
+ RefPtr<OutputStreamShim> mTunnelStreamOut;
+ RefPtr<nsHttpTransaction> mDrivingTransaction;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_TLSFilterTransaction_h
diff --git a/netwerk/protocol/http/UserAgentOverrides.jsm b/netwerk/protocol/http/UserAgentOverrides.jsm
new file mode 100644
index 000000000..22c676f06
--- /dev/null
+++ b/netwerk/protocol/http/UserAgentOverrides.jsm
@@ -0,0 +1,182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [ "UserAgentOverrides" ];
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/UserAgentUpdates.jsm");
+
+const OVERRIDE_MESSAGE = "Useragent:GetOverride";
+const PREF_OVERRIDES_ENABLED = "general.useragent.site_specific_overrides";
+const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"]
+ .getService(Ci.nsIHttpProtocolHandler)
+ .userAgent;
+const MAX_OVERRIDE_FOR_HOST_CACHE_SIZE = 250;
+
+XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
+ "@mozilla.org/parentprocessmessagemanager;1",
+ "nsIMessageListenerManager"); // Might have to make this broadcast?
+
+var gPrefBranch;
+var gOverrides = new Map;
+var gUpdatedOverrides;
+var gOverrideForHostCache = new Map;
+var gInitialized = false;
+var gOverrideFunctions = [
+ function (aHttpChannel) { return UserAgentOverrides.getOverrideForURI(aHttpChannel.URI); }
+];
+var gBuiltUAs = new Map;
+
+this.UserAgentOverrides = {
+ init: function uao_init() {
+ if (gInitialized)
+ return;
+
+ gPrefBranch = Services.prefs.getBranch("general.useragent.override.");
+ gPrefBranch.addObserver("", buildOverrides, false);
+
+ ppmm.addMessageListener(OVERRIDE_MESSAGE, this);
+ Services.prefs.addObserver(PREF_OVERRIDES_ENABLED, buildOverrides, false);
+
+ try {
+ Services.obs.addObserver(HTTP_on_useragent_request, "http-on-useragent-request", false);
+ } catch (x) {
+ // The http-on-useragent-request notification is disallowed in content processes.
+ }
+
+ UserAgentUpdates.init(function(overrides) {
+ gOverrideForHostCache.clear();
+ if (overrides) {
+ for (let domain in overrides) {
+ overrides[domain] = getUserAgentFromOverride(overrides[domain]);
+ }
+ overrides.get = function(key) { return this[key]; };
+ }
+ gUpdatedOverrides = overrides;
+ });
+
+ buildOverrides();
+ gInitialized = true;
+ },
+
+ addComplexOverride: function uao_addComplexOverride(callback) {
+ // Add to front of array so complex overrides have precedence
+ gOverrideFunctions.unshift(callback);
+ },
+
+ getOverrideForURI: function uao_getOverrideForURI(aURI) {
+ let host = aURI.asciiHost;
+ if (!gInitialized ||
+ (!gOverrides.size && !gUpdatedOverrides) ||
+ !(host)) {
+ return null;
+ }
+
+ let override = gOverrideForHostCache.get(host);
+ if (override !== undefined)
+ return override;
+
+ function findOverride(overrides) {
+ let searchHost = host;
+ let userAgent = overrides.get(searchHost);
+
+ while (!userAgent) {
+ let dot = searchHost.indexOf('.');
+ if (dot === -1) {
+ return null;
+ }
+ searchHost = searchHost.slice(dot + 1);
+ userAgent = overrides.get(searchHost);
+ }
+ return userAgent;
+ }
+
+ override = (gOverrides.size && findOverride(gOverrides))
+ || (gUpdatedOverrides && findOverride(gUpdatedOverrides));
+
+ if (gOverrideForHostCache.size >= MAX_OVERRIDE_FOR_HOST_CACHE_SIZE) {
+ gOverrideForHostCache.clear();
+ }
+ gOverrideForHostCache.set(host, override);
+
+ return override;
+ },
+
+ uninit: function uao_uninit() {
+ if (!gInitialized)
+ return;
+ gInitialized = false;
+
+ gPrefBranch.removeObserver("", buildOverrides);
+
+ Services.prefs.removeObserver(PREF_OVERRIDES_ENABLED, buildOverrides);
+
+ Services.obs.removeObserver(HTTP_on_useragent_request, "http-on-useragent-request");
+ },
+
+ receiveMessage: function(aMessage) {
+ let name = aMessage.name;
+ switch (name) {
+ case OVERRIDE_MESSAGE:
+ let uri = Services.io.newURI(aMessage.data.uri, null, null);
+ return this.getOverrideForURI(uri);
+ default:
+ throw("Wrong Message in UserAgentOverride: " + name);
+ }
+ }
+};
+
+function getUserAgentFromOverride(override)
+{
+ let userAgent = gBuiltUAs.get(override);
+ if (userAgent !== undefined) {
+ return userAgent;
+ }
+ let [search, replace] = override.split("#", 2);
+ if (search && replace) {
+ userAgent = DEFAULT_UA.replace(new RegExp(search, "g"), replace);
+ } else {
+ userAgent = override;
+ }
+ gBuiltUAs.set(override, userAgent);
+ return userAgent;
+}
+
+function buildOverrides() {
+ gOverrides.clear();
+ gOverrideForHostCache.clear();
+
+ if (!Services.prefs.getBoolPref(PREF_OVERRIDES_ENABLED))
+ return;
+
+ let builtUAs = new Map;
+ let domains = gPrefBranch.getChildList("");
+
+ for (let domain of domains) {
+ let override = gPrefBranch.getCharPref(domain);
+ let userAgent = getUserAgentFromOverride(override);
+
+ if (userAgent != DEFAULT_UA) {
+ gOverrides.set(domain, userAgent);
+ }
+ }
+}
+
+function HTTP_on_useragent_request(aSubject, aTopic, aData) {
+ let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+
+ for (let callback of gOverrideFunctions) {
+ let modifiedUA = callback(channel, DEFAULT_UA);
+ if (modifiedUA) {
+ channel.setRequestHeader("User-Agent", modifiedUA, false);
+ return;
+ }
+ }
+}
diff --git a/netwerk/protocol/http/UserAgentUpdates.jsm b/netwerk/protocol/http/UserAgentUpdates.jsm
new file mode 100644
index 000000000..602705ebe
--- /dev/null
+++ b/netwerk/protocol/http/UserAgentUpdates.jsm
@@ -0,0 +1,285 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["UserAgentUpdates"];
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(
+ this, "FileUtils", "resource://gre/modules/FileUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(
+ this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(
+ this, "OS", "resource://gre/modules/osfile.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(
+ this, "Promise", "resource://gre/modules/Promise.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(
+ this, "UpdateUtils", "resource://gre/modules/UpdateUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(
+ this, "gUpdateTimer", "@mozilla.org/updates/timer-manager;1", "nsIUpdateTimerManager");
+
+XPCOMUtils.defineLazyGetter(this, "gApp",
+ function() {
+ return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo)
+ .QueryInterface(Ci.nsIXULRuntime);
+ });
+
+XPCOMUtils.defineLazyGetter(this, "gDecoder",
+ function() { return new TextDecoder(); }
+);
+
+XPCOMUtils.defineLazyGetter(this, "gEncoder",
+ function() { return new TextEncoder(); }
+);
+
+const TIMER_ID = "user-agent-updates-timer";
+
+const PREF_UPDATES = "general.useragent.updates.";
+const PREF_UPDATES_ENABLED = PREF_UPDATES + "enabled";
+const PREF_UPDATES_URL = PREF_UPDATES + "url";
+const PREF_UPDATES_INTERVAL = PREF_UPDATES + "interval";
+const PREF_UPDATES_RETRY = PREF_UPDATES + "retry";
+const PREF_UPDATES_TIMEOUT = PREF_UPDATES + "timeout";
+const PREF_UPDATES_LASTUPDATED = PREF_UPDATES + "lastupdated";
+
+const KEY_PREFDIR = "PrefD";
+const KEY_APPDIR = "XCurProcD";
+const FILE_UPDATES = "ua-update.json";
+
+const PREF_APP_DISTRIBUTION = "distribution.id";
+const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
+
+var gInitialized = false;
+
+function readChannel(url) {
+ return new Promise((resolve, reject) => {
+ try {
+ let channel = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+ channel.contentType = "application/json";
+
+ NetUtil.asyncFetch(channel, (inputStream, status) => {
+ if (!Components.isSuccessCode(status)) {
+ reject();
+ return;
+ }
+
+ let data = JSON.parse(
+ NetUtil.readInputStreamToString(inputStream, inputStream.available())
+ );
+ resolve(data);
+ });
+ } catch (ex) {
+ reject(new Error("UserAgentUpdates: Could not fetch " + url + " " +
+ ex + "\n" + ex.stack));
+ }
+ });
+}
+
+this.UserAgentUpdates = {
+ init: function(callback) {
+ if (gInitialized) {
+ return;
+ }
+ gInitialized = true;
+
+ this._callback = callback;
+ this._lastUpdated = 0;
+ this._applySavedUpdate();
+
+ Services.prefs.addObserver(PREF_UPDATES, this, false);
+ },
+
+ uninit: function() {
+ if (!gInitialized) {
+ return;
+ }
+ gInitialized = false;
+ Services.prefs.removeObserver(PREF_UPDATES, this);
+ },
+
+ _applyUpdate: function(update) {
+ // Check pref again in case it has changed
+ if (update && this._getPref(PREF_UPDATES_ENABLED, false)) {
+ this._callback(update);
+ } else {
+ this._callback(null);
+ }
+ },
+
+ _applySavedUpdate: function() {
+ if (!this._getPref(PREF_UPDATES_ENABLED, false)) {
+ // remove previous overrides
+ this._applyUpdate(null);
+ return;
+ }
+ // try loading from profile dir, then from app dir
+ let dirs = [KEY_PREFDIR, KEY_APPDIR];
+
+ dirs.reduce((prevLoad, dir) => {
+ let file = FileUtils.getFile(dir, [FILE_UPDATES], true).path;
+ // tryNext returns promise to read file under dir and parse it
+ let tryNext = () => OS.File.read(file).then(
+ (bytes) => {
+ let update = JSON.parse(gDecoder.decode(bytes));
+ if (!update) {
+ throw new Error("invalid update");
+ }
+ return update;
+ }
+ );
+ // try to load next one if the previous load failed
+ return prevLoad ? prevLoad.then(null, tryNext) : tryNext();
+ }, null).then(null, (ex) => {
+ if (AppConstants.platform !== "android") {
+ // All previous (non-Android) load attempts have failed, so we bail.
+ throw new Error("UserAgentUpdates: Failed to load " + FILE_UPDATES +
+ ex + "\n" + ex.stack);
+ }
+ // Make one last attempt to read from the Fennec APK root.
+ return readChannel("resource://android/" + FILE_UPDATES);
+ }).then((update) => {
+ // Apply update if loading was successful
+ this._applyUpdate(update);
+ }).catch(Cu.reportError);
+ this._scheduleUpdate();
+ },
+
+ _saveToFile: function(update) {
+ let file = FileUtils.getFile(KEY_PREFDIR, [FILE_UPDATES], true);
+ let path = file.path;
+ let bytes = gEncoder.encode(JSON.stringify(update));
+ OS.File.writeAtomic(path, bytes, {tmpPath: path + ".tmp"}).then(
+ () => {
+ this._lastUpdated = Date.now();
+ Services.prefs.setCharPref(
+ PREF_UPDATES_LASTUPDATED, this._lastUpdated.toString());
+ },
+ Cu.reportError
+ );
+ },
+
+ _getPref: function(name, def) {
+ try {
+ switch (typeof def) {
+ case "number": return Services.prefs.getIntPref(name);
+ case "boolean": return Services.prefs.getBoolPref(name);
+ }
+ return Services.prefs.getCharPref(name);
+ } catch (e) {
+ return def;
+ }
+ },
+
+ _getParameters() {
+ return {
+ "%DATE%": function() { return Date.now().toString(); },
+ "%PRODUCT%": function() { return gApp.name; },
+ "%APP_ID%": function() { return gApp.ID; },
+ "%APP_VERSION%": function() { return gApp.version; },
+ "%BUILD_ID%": function() { return gApp.appBuildID; },
+ "%OS%": function() { return gApp.OS; },
+ "%CHANNEL%": function() { return UpdateUtils.UpdateChannel; },
+ "%DISTRIBUTION%": function() { return this._getPref(PREF_APP_DISTRIBUTION, ""); },
+ "%DISTRIBUTION_VERSION%": function() { return this._getPref(PREF_APP_DISTRIBUTION_VERSION, ""); },
+ };
+ },
+
+ _getUpdateURL: function() {
+ let url = this._getPref(PREF_UPDATES_URL, "");
+ let params = this._getParameters();
+ return url.replace(/%[A-Z_]+%/g, function(match) {
+ let param = params[match];
+ // preserve the %FOO% string (e.g. as an encoding) if it's not a valid parameter
+ return param ? encodeURIComponent(param()) : match;
+ });
+ },
+
+ _fetchUpdate: function(url, success, error) {
+ let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ request.mozBackgroundRequest = true;
+ request.timeout = this._getPref(PREF_UPDATES_TIMEOUT, 60000);
+ request.open("GET", url, true);
+ request.overrideMimeType("application/json");
+ request.responseType = "json";
+
+ request.addEventListener("load", function() {
+ let response = request.response;
+ response ? success(response) : error();
+ });
+ request.addEventListener("error", error);
+ request.send();
+ },
+
+ _update: function() {
+ let url = this._getUpdateURL();
+ url && this._fetchUpdate(url,
+ (function(response) { // success
+ // apply update and save overrides to profile
+ this._applyUpdate(response);
+ this._saveToFile(response);
+ this._scheduleUpdate(); // cancel any retries
+ }).bind(this),
+ (function(response) { // error
+ this._scheduleUpdate(true /* retry */);
+ }).bind(this));
+ },
+
+ _scheduleUpdate: function(retry) {
+ // only schedule updates in the main process
+ if (gApp.processType !== Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+ return;
+ }
+ let interval = this._getPref(PREF_UPDATES_INTERVAL, 604800 /* 1 week */);
+ if (retry) {
+ interval = this._getPref(PREF_UPDATES_RETRY, interval);
+ }
+ gUpdateTimer.registerTimer(TIMER_ID, this, Math.max(1, interval));
+ },
+
+ notify: function(timer) {
+ // timer notification
+ if (this._getPref(PREF_UPDATES_ENABLED, false)) {
+ this._update();
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "nsPref:changed":
+ if (data === PREF_UPDATES_ENABLED) {
+ this._applySavedUpdate();
+ } else if (data === PREF_UPDATES_INTERVAL) {
+ this._scheduleUpdate();
+ } else if (data === PREF_UPDATES_LASTUPDATED) {
+ // reload from file if there has been an update
+ let lastUpdated = parseInt(
+ this._getPref(PREF_UPDATES_LASTUPDATED, "0"), 0);
+ if (lastUpdated > this._lastUpdated) {
+ this._applySavedUpdate();
+ this._lastUpdated = lastUpdated;
+ }
+ }
+ break;
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver,
+ Ci.nsITimerCallback,
+ ]),
+};
diff --git a/netwerk/protocol/http/WellKnownOpportunisticUtils.js b/netwerk/protocol/http/WellKnownOpportunisticUtils.js
new file mode 100644
index 000000000..865f2a6b4
--- /dev/null
+++ b/netwerk/protocol/http/WellKnownOpportunisticUtils.js
@@ -0,0 +1,43 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID = "@mozilla.org/network/well-known-opportunistic-utils;1";
+const WELLKNOWNOPPORTUNISTICUTILS_CID = Components.ID("{b4f96c89-5238-450c-8bda-e12c26f1d150}");
+
+function WellKnownOpportunisticUtils() {
+ this.valid = false;
+ this.mixed = false;
+ this.lifetime = 0;
+}
+
+WellKnownOpportunisticUtils.prototype = {
+ classID: WELLKNOWNOPPORTUNISTICUTILS_CID,
+ contractID: WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID,
+ classDescription: "Well-Known Opportunistic Utils",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWellKnownOpportunisticUtils]),
+
+ verify: function(aJSON, aOrigin, aAlternatePort) {
+ try {
+ let obj = JSON.parse(aJSON.toLowerCase());
+ let ports = obj[aOrigin.toLowerCase()]['tls-ports'];
+ if (ports.indexOf(aAlternatePort) == -1) {
+ throw "invalid port";
+ }
+ this.lifetime = obj[aOrigin.toLowerCase()]['lifetime'];
+ this.mixed = obj[aOrigin.toLowerCase()]['mixed-scheme'];
+ } catch (e) {
+ return;
+ }
+ this.valid = true;
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WellKnownOpportunisticUtils]);
diff --git a/netwerk/protocol/http/WellKnownOpportunisticUtils.manifest b/netwerk/protocol/http/WellKnownOpportunisticUtils.manifest
new file mode 100644
index 000000000..645358e43
--- /dev/null
+++ b/netwerk/protocol/http/WellKnownOpportunisticUtils.manifest
@@ -0,0 +1,3 @@
+# WellKnownOpportunisticUtils.js
+component {b4f96c89-5238-450c-8bda-e12c26f1d150} WellKnownOpportunisticUtils.js
+contract @mozilla.org/network/well-known-opportunistic-utils;1 {b4f96c89-5238-450c-8bda-e12c26f1d150}
diff --git a/netwerk/protocol/http/http2_huffman_table.txt b/netwerk/protocol/http/http2_huffman_table.txt
new file mode 100644
index 000000000..bfde068cd
--- /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 000000000..35525660a
--- /dev/null
+++ b/netwerk/protocol/http/make_incoming_tables.py
@@ -0,0 +1,194 @@
+# This script exists to auto-generate Http2HuffmanIncoming.h from the table
+# contained in the HPACK spec. It's pretty simple to run:
+# python make_incoming_tables.py < http2_huffman_table.txt > Http2HuffmanIncoming.h
+# where huff_incoming.txt is copy/pasted text from the latest version of the
+# HPACK spec, with all non-relevant lines removed (the most recent version
+# of huff_incoming.txt also lives in this directory as an example).
+import sys
+
+def char_cmp(x, y):
+ rv = cmp(x['nbits'], y['nbits'])
+ if not rv:
+ rv = cmp(x['bpat'], y['bpat'])
+ if not rv:
+ rv = cmp(x['ascii'], y['ascii'])
+ return rv
+
+characters = []
+
+for line in sys.stdin:
+ line = line.rstrip()
+ obracket = line.rfind('[')
+ nbits = int(line[obracket + 1:-1])
+
+ oparen = line.find(' (')
+ ascii = int(line[oparen + 2:oparen + 5].strip())
+
+ bar = line.find('|', oparen)
+ space = line.find(' ', bar)
+ bpat = line[bar + 1:space].strip().rstrip('|')
+
+ characters.append({'ascii': ascii, 'nbits': nbits, 'bpat': bpat})
+
+characters.sort(cmp=char_cmp)
+raw_entries = []
+for c in characters:
+ raw_entries.append((c['ascii'], c['bpat']))
+
+class DefaultList(list):
+ def __init__(self, default=None):
+ self.__default = default
+
+ def __ensure_size(self, sz):
+ while sz > len(self):
+ self.append(self.__default)
+
+ def __getitem__(self, idx):
+ self.__ensure_size(idx + 1)
+ rv = super(DefaultList, self).__getitem__(idx)
+ return rv
+
+ def __setitem__(self, idx, val):
+ self.__ensure_size(idx + 1)
+ super(DefaultList, self).__setitem__(idx, val)
+
+def expand_to_8bit(bstr):
+ while len(bstr) < 8:
+ bstr += '0'
+ return int(bstr, 2)
+
+table = DefaultList()
+for r in raw_entries:
+ ascii, bpat = r
+ ascii = int(ascii)
+ bstrs = bpat.split('|')
+ curr_table = table
+ while len(bstrs) > 1:
+ idx = expand_to_8bit(bstrs[0])
+ if curr_table[idx] is None:
+ curr_table[idx] = DefaultList()
+ curr_table = curr_table[idx]
+ bstrs.pop(0)
+
+ idx = expand_to_8bit(bstrs[0])
+ curr_table[idx] = {'prefix_len': len(bstrs[0]),
+ 'mask': int(bstrs[0], 2),
+ 'value': ascii}
+
+
+def output_table(table, name_suffix=''):
+ max_prefix_len = 0
+ for i, t in enumerate(table):
+ if isinstance(t, dict):
+ if t['prefix_len'] > max_prefix_len:
+ max_prefix_len = t['prefix_len']
+ elif t is not None:
+ output_table(t, '%s_%s' % (name_suffix, i))
+
+ tablename = 'HuffmanIncoming%s' % (name_suffix if name_suffix else 'Root',)
+ entriestable = tablename.replace('HuffmanIncoming', 'HuffmanIncomingEntries')
+ nexttable = tablename.replace('HuffmanIncoming', 'HuffmanIncomingNextTables')
+ sys.stdout.write('static const HuffmanIncomingEntry %s[] = {\n' %
+ (entriestable,))
+ prefix_len = 0
+ value = 0
+ i = 0
+ while i < 256:
+ t = table[i]
+ if isinstance(t, dict):
+ value = t['value']
+ prefix_len = t['prefix_len']
+ elif t is not None:
+ break
+
+ sys.stdout.write(' { %s, %s }' %
+ (value, prefix_len))
+ sys.stdout.write(',\n')
+ i += 1
+
+ indexOfFirstNextTable = i
+ if i < 256:
+ sys.stdout.write('};\n')
+ sys.stdout.write('\n')
+ sys.stdout.write('static const HuffmanIncomingTable* %s[] = {\n' %
+ (nexttable,))
+ while i < 256:
+ subtable = '%s_%s' % (name_suffix, i)
+ ptr = '&HuffmanIncoming%s' % (subtable,)
+ sys.stdout.write(' %s' %
+ (ptr))
+ sys.stdout.write(',\n')
+ i += 1
+ else:
+ nexttable = 'nullptr'
+
+ sys.stdout.write('};\n')
+ sys.stdout.write('\n')
+ sys.stdout.write('static const HuffmanIncomingTable %s = {\n' % (tablename,))
+ sys.stdout.write(' %s,\n' % (entriestable,))
+ sys.stdout.write(' %s,\n' % (nexttable,))
+ sys.stdout.write(' %s,\n' % (indexOfFirstNextTable,))
+ sys.stdout.write(' %s\n' % (max_prefix_len,))
+ sys.stdout.write('};\n')
+ sys.stdout.write('\n')
+
+sys.stdout.write('''/*
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT!
+ */
+#ifndef mozilla__net__Http2HuffmanIncoming_h
+#define mozilla__net__Http2HuffmanIncoming_h
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanIncomingTable;
+
+struct HuffmanIncomingEntry {
+ const uint16_t mValue:9; // 9 bits so it can hold 0..256
+ const uint16_t mPrefixLen:7; // only holds 1..8
+};
+
+// The data members are public only so they can be statically constructed. All
+// accesses should be done through the functions.
+struct HuffmanIncomingTable {
+ // The normal entries, for indices in the range 0..(mNumEntries-1).
+ const HuffmanIncomingEntry* const mEntries;
+
+ // The next tables, for indices in the range mNumEntries..255. Must be
+ // |nullptr| if mIndexOfFirstNextTable is 256.
+ const HuffmanIncomingTable** const mNextTables;
+
+ // The index of the first next table (equal to the number of entries in
+ // mEntries). This cannot be a uint8_t because it can have the value 256,
+ // in which case there are no next tables and mNextTables must be |nullptr|.
+ const uint16_t mIndexOfFirstNextTable;
+
+ const uint8_t mPrefixLen;
+
+ bool IndexHasANextTable(uint8_t aIndex) const
+ {
+ return aIndex >= mIndexOfFirstNextTable;
+ }
+
+ const HuffmanIncomingEntry* Entry(uint8_t aIndex) const
+ {
+ MOZ_ASSERT(aIndex < mIndexOfFirstNextTable);
+ return &mEntries[aIndex];
+ }
+
+ const HuffmanIncomingTable* NextTable(uint8_t aIndex) const
+ {
+ MOZ_ASSERT(aIndex >= mIndexOfFirstNextTable);
+ return mNextTables[aIndex - mIndexOfFirstNextTable];
+ }
+};
+
+''')
+
+output_table(table)
+
+sys.stdout.write('''} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__Http2HuffmanIncoming_h
+''')
diff --git a/netwerk/protocol/http/make_outgoing_tables.py b/netwerk/protocol/http/make_outgoing_tables.py
new file mode 100644
index 000000000..abf559dad
--- /dev/null
+++ b/netwerk/protocol/http/make_outgoing_tables.py
@@ -0,0 +1,55 @@
+# This script exists to auto-generate Http2HuffmanOutgoing.h from the table
+# contained in the HPACK spec. It's pretty simple to run:
+# python make_outgoing_tables.py < http2_huffman_table.txt > Http2HuffmanOutgoing.h
+# where huff_outgoing.txt is copy/pasted text from the latest version of the
+# HPACK spec, with all non-relevant lines removed (the most recent version
+# of huff_outgoing.txt also lives in this directory as an example).
+import sys
+
+sys.stdout.write('''/*
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT!
+ */
+#ifndef mozilla__net__Http2HuffmanOutgoing_h
+#define mozilla__net__Http2HuffmanOutgoing_h
+
+namespace mozilla {
+namespace net {
+
+struct HuffmanOutgoingEntry {
+ uint32_t mValue;
+ uint8_t mLength;
+};
+
+static HuffmanOutgoingEntry HuffmanOutgoing[] = {
+''')
+
+entries = []
+for line in sys.stdin:
+ line = line.strip()
+ obracket = line.rfind('[')
+ nbits = int(line[obracket + 1:-1])
+
+ lastbar = line.rfind('|')
+ space = line.find(' ', lastbar)
+ encend = line.rfind(' ', 0, obracket)
+
+ enc = line[space:encend].strip()
+ val = int(enc, 16)
+
+ entries.append({'length': nbits, 'value': val})
+
+line = []
+for i, e in enumerate(entries):
+ sys.stdout.write(' { 0x%08x, %s }' %
+ (e['value'], e['length']))
+ if i < (len(entries) - 1):
+ sys.stdout.write(',')
+ sys.stdout.write('\n')
+
+sys.stdout.write('''};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__Http2HuffmanOutgoing_h
+''')
diff --git a/netwerk/protocol/http/moz.build b/netwerk/protocol/http/moz.build
new file mode 100644
index 000000000..e13101aa0
--- /dev/null
+++ b/netwerk/protocol/http/moz.build
@@ -0,0 +1,121 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIHstsPrimingCallback.idl',
+ 'nsIHttpActivityObserver.idl',
+ 'nsIHttpAuthenticableChannel.idl',
+ 'nsIHttpAuthenticator.idl',
+ 'nsIHttpAuthManager.idl',
+ 'nsIHttpChannel.idl',
+ 'nsIHttpChannelAuthProvider.idl',
+ 'nsIHttpChannelChild.idl',
+ 'nsIHttpChannelInternal.idl',
+ 'nsIHttpEventSink.idl',
+ 'nsIHttpHeaderVisitor.idl',
+ 'nsIHttpProtocolHandler.idl',
+ 'nsIWellKnownOpportunisticUtils.idl',
+]
+
+XPIDL_MODULE = 'necko_http'
+
+EXPORTS += [
+ 'nsCORSListenerProxy.h',
+ 'nsHttp.h',
+ 'nsHttpAtomList.h',
+ 'nsHttpHeaderArray.h',
+ 'nsHttpRequestHead.h',
+ 'nsHttpResponseHead.h',
+]
+
+EXPORTS.mozilla.net += [
+ 'AltDataOutputStreamChild.h',
+ 'AltDataOutputStreamParent.h',
+ 'HttpBaseChannel.h',
+ 'HttpChannelChild.h',
+ 'HttpChannelParent.h',
+ 'HttpInfo.h',
+ 'NullHttpChannel.h',
+ 'PHttpChannelParams.h',
+ 'PSpdyPush.h',
+ 'TimingStruct.h',
+]
+
+# ASpdySession.cpp and nsHttpAuthCache cannot be built in unified mode because
+# they use plarena.h.
+SOURCES += [
+ 'AlternateServices.cpp',
+ 'ASpdySession.cpp',
+ 'nsHttpAuthCache.cpp',
+ 'nsHttpChannelAuthProvider.cpp', # redefines GetAuthType
+]
+
+UNIFIED_SOURCES += [
+ 'AltDataOutputStreamChild.cpp',
+ 'AltDataOutputStreamParent.cpp',
+ 'CacheControlParser.cpp',
+ 'ConnectionDiagnostics.cpp',
+ 'HSTSPrimerListener.cpp',
+ 'Http2Compression.cpp',
+ 'Http2Push.cpp',
+ 'Http2Session.cpp',
+ 'Http2Stream.cpp',
+ 'HttpBaseChannel.cpp',
+ 'HttpChannelChild.cpp',
+ 'HttpChannelParent.cpp',
+ 'HttpChannelParentListener.cpp',
+ 'HttpInfo.cpp',
+ 'InterceptedChannel.cpp',
+ 'nsCORSListenerProxy.cpp',
+ 'nsHttp.cpp',
+ 'nsHttpActivityDistributor.cpp',
+ 'nsHttpAuthManager.cpp',
+ 'nsHttpBasicAuth.cpp',
+ 'nsHttpChannel.cpp',
+ 'nsHttpChunkedDecoder.cpp',
+ 'nsHttpConnection.cpp',
+ 'nsHttpConnectionInfo.cpp',
+ 'nsHttpConnectionMgr.cpp',
+ 'nsHttpDigestAuth.cpp',
+ 'nsHttpHeaderArray.cpp',
+ 'nsHttpNTLMAuth.cpp',
+ 'nsHttpPipeline.cpp',
+ 'nsHttpRequestHead.cpp',
+ 'nsHttpResponseHead.cpp',
+ 'nsHttpTransaction.cpp',
+ 'NullHttpChannel.cpp',
+ 'NullHttpTransaction.cpp',
+ 'TunnelUtils.cpp',
+]
+
+# These files cannot be built in unified mode because of OS X headers.
+SOURCES += [
+ 'nsHttpHandler.cpp',
+]
+
+IPDL_SOURCES += [
+ 'PAltDataOutputStream.ipdl',
+ 'PHttpChannel.ipdl',
+]
+
+EXTRA_JS_MODULES += [
+ 'UserAgentOverrides.jsm',
+ 'UserAgentUpdates.jsm',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/netwerk/base',
+]
+
+EXTRA_COMPONENTS += [
+ 'WellKnownOpportunisticUtils.js',
+ 'WellKnownOpportunisticUtils.manifest',
+]
diff --git a/netwerk/protocol/http/nsAHttpConnection.h b/netwerk/protocol/http/nsAHttpConnection.h
new file mode 100644
index 000000000..1775a2c68
--- /dev/null
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -0,0 +1,263 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAHttpConnection_h__
+#define nsAHttpConnection_h__
+
+#include "nsISupports.h"
+#include "nsAHttpTransaction.h"
+
+class nsISocketTransport;
+class nsIAsyncInputStream;
+class nsIAsyncOutputStream;
+
+namespace mozilla { namespace net {
+
+class nsHttpConnectionInfo;
+class nsHttpConnection;
+
+//-----------------------------------------------------------------------------
+// Abstract base class for a HTTP connection
+//-----------------------------------------------------------------------------
+
+// 5a66aed7-eede-468b-ac2b-e5fb431fcc5c
+#define NS_AHTTPCONNECTION_IID \
+{ 0x5a66aed7, 0xeede, 0x468b, {0xac, 0x2b, 0xe5, 0xfb, 0x43, 0x1f, 0xcc, 0x5c }}
+
+class nsAHttpConnection : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_AHTTPCONNECTION_IID)
+
+ //-------------------------------------------------------------------------
+ // NOTE: these methods may only be called on the socket thread.
+ //-------------------------------------------------------------------------
+
+ //
+ // called by a transaction when the response headers have all been read.
+ // the connection can force the transaction to reset it's response headers,
+ // and prepare for a new set of response headers, by setting |*reset=TRUE|.
+ //
+ // @return failure code to close the transaction.
+ //
+ virtual nsresult OnHeadersAvailable(nsAHttpTransaction *,
+ nsHttpRequestHead *,
+ nsHttpResponseHead *,
+ bool *reset) = 0;
+
+ //
+ // called by a transaction to resume either sending or receiving data
+ // after a transaction returned NS_BASE_STREAM_WOULD_BLOCK from its
+ // ReadSegments/WriteSegments methods.
+ //
+ virtual nsresult ResumeSend() = 0;
+ virtual nsresult ResumeRecv() = 0;
+
+ // called by a transaction to force a "send/recv from network" iteration
+ // even if not scheduled by socket associated with connection
+ virtual nsresult ForceSend() = 0;
+ virtual nsresult ForceRecv() = 0;
+
+ // After a connection has had ResumeSend() called by a transaction,
+ // and it is ready to write to the network it may need to know the
+ // transaction that has data to write. This is only an issue for
+ // multiplexed protocols like SPDY - plain HTTP or pipelined HTTP
+ // implicitly have this information in a 1:1 relationship with the
+ // transaction(s) they manage.
+ virtual void TransactionHasDataToWrite(nsAHttpTransaction *)
+ {
+ // by default do nothing - only multiplexed protocols need to overload
+ return;
+ }
+
+ // This is the companion to *HasDataToWrite() for the case
+ // when a gecko caller has called ResumeRecv() after being paused
+ virtual void TransactionHasDataToRecv(nsAHttpTransaction *)
+ {
+ // by default do nothing - only multiplexed protocols need to overload
+ return;
+ }
+
+ // called by the connection manager to close a transaction being processed
+ // by this connection.
+ //
+ // @param transaction
+ // the transaction being closed.
+ // @param reason
+ // the reason for closing the transaction. NS_BASE_STREAM_CLOSED
+ // is equivalent to NS_OK.
+ //
+ virtual void CloseTransaction(nsAHttpTransaction *transaction,
+ nsresult reason) = 0;
+
+ // get a reference to the connection's connection info object.
+ virtual void GetConnectionInfo(nsHttpConnectionInfo **) = 0;
+
+ // get the transport level information for this connection. This may fail
+ // if it is in use.
+ virtual nsresult TakeTransport(nsISocketTransport **,
+ nsIAsyncInputStream **,
+ nsIAsyncOutputStream **) = 0;
+
+ // called by a transaction to get the security info from the socket.
+ virtual void GetSecurityInfo(nsISupports **) = 0;
+
+ // called by a transaction to determine whether or not the connection is
+ // persistent... important in determining the end of a response.
+ virtual bool IsPersistent() = 0;
+
+ // called to determine or set if a connection has been reused.
+ virtual bool IsReused() = 0;
+ virtual void DontReuse() = 0;
+
+ // called by a transaction when the transaction reads more from the socket
+ // than it should have (eg. containing part of the next pipelined response).
+ virtual nsresult PushBack(const char *data, uint32_t length) = 0;
+
+ // Used to determine if the connection wants read events even though
+ // it has not written out a transaction. Used when a connection has issued
+ // a preamble such as a proxy ssl CONNECT sequence.
+ virtual bool IsProxyConnectInProgress() = 0;
+
+ // Used by a transaction to manage the state of previous response bodies on
+ // the same connection and work around buggy servers.
+ virtual bool LastTransactionExpectedNoContent() = 0;
+ virtual void SetLastTransactionExpectedNoContent(bool) = 0;
+
+ // Transfer the base http connection object along with a
+ // reference to it to the caller.
+ virtual already_AddRefed<nsHttpConnection> TakeHttpConnection() = 0;
+
+ // Get the nsISocketTransport used by the connection without changing
+ // references or ownership.
+ virtual nsISocketTransport *Transport() = 0;
+
+ // Cancel and reschedule transactions deeper than the current response.
+ // Returns the number of canceled transactions.
+ virtual uint32_t CancelPipeline(nsresult originalReason) = 0;
+
+ // Read and write class of transaction that is carried on this connection
+ virtual nsAHttpTransaction::Classifier Classification() = 0;
+ virtual void Classify(nsAHttpTransaction::Classifier newclass) = 0;
+
+ // The number of transaction bytes written out on this HTTP Connection, does
+ // not count CONNECT tunnel setup
+ virtual int64_t BytesWritten() = 0;
+
+ // Update the callbacks used to provide security info. May be called on
+ // any thread.
+ virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) = 0;
+
+ // nsHttp.h version
+ virtual uint32_t Version() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpConnection, NS_AHTTPCONNECTION_IID)
+
+#define NS_DECL_NSAHTTPCONNECTION(fwdObject) \
+ nsresult OnHeadersAvailable(nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *, bool *reset) override; \
+ void CloseTransaction(nsAHttpTransaction *, nsresult) override; \
+ nsresult TakeTransport(nsISocketTransport **, \
+ nsIAsyncInputStream **, \
+ nsIAsyncOutputStream **) override; \
+ bool IsPersistent() override; \
+ bool IsReused() override; \
+ void DontReuse() override; \
+ nsresult PushBack(const char *, uint32_t) override; \
+ already_AddRefed<nsHttpConnection> TakeHttpConnection() override; \
+ uint32_t CancelPipeline(nsresult originalReason) override; \
+ nsAHttpTransaction::Classifier Classification() override; \
+ /* \
+ Thes methods below have automatic definitions that just forward the \
+ function to a lower level connection object \
+ */ \
+ void GetConnectionInfo(nsHttpConnectionInfo **result) \
+ override \
+ { \
+ if (!(fwdObject)) { \
+ *result = nullptr; \
+ return; \
+ } \
+ return (fwdObject)->GetConnectionInfo(result); \
+ } \
+ void GetSecurityInfo(nsISupports **result) override \
+ { \
+ if (!(fwdObject)) { \
+ *result = nullptr; \
+ return; \
+ } \
+ return (fwdObject)->GetSecurityInfo(result); \
+ } \
+ nsresult ResumeSend() override \
+ { \
+ if (!(fwdObject)) \
+ return NS_ERROR_FAILURE; \
+ return (fwdObject)->ResumeSend(); \
+ } \
+ nsresult ResumeRecv() override \
+ { \
+ if (!(fwdObject)) \
+ return NS_ERROR_FAILURE; \
+ return (fwdObject)->ResumeRecv(); \
+ } \
+ nsresult ForceSend() override \
+ { \
+ if (!(fwdObject)) \
+ return NS_ERROR_FAILURE; \
+ return (fwdObject)->ForceSend(); \
+ } \
+ nsresult ForceRecv() override \
+ { \
+ if (!(fwdObject)) \
+ return NS_ERROR_FAILURE; \
+ return (fwdObject)->ForceRecv(); \
+ } \
+ nsISocketTransport *Transport() \
+ override \
+ { \
+ if (!(fwdObject)) \
+ return nullptr; \
+ return (fwdObject)->Transport(); \
+ } \
+ uint32_t Version() override \
+ { \
+ return (fwdObject) ? \
+ (fwdObject)->Version() : \
+ NS_HTTP_VERSION_UNKNOWN; \
+ } \
+ bool IsProxyConnectInProgress() override \
+ { \
+ return (!fwdObject) ? false : \
+ (fwdObject)->IsProxyConnectInProgress(); \
+ } \
+ bool LastTransactionExpectedNoContent() override \
+ { \
+ return (!fwdObject) ? false : \
+ (fwdObject)->LastTransactionExpectedNoContent(); \
+ } \
+ void SetLastTransactionExpectedNoContent(bool val) \
+ override \
+ { \
+ if (fwdObject) \
+ (fwdObject)->SetLastTransactionExpectedNoContent(val); \
+ } \
+ void Classify(nsAHttpTransaction::Classifier newclass) \
+ override \
+ { \
+ if (fwdObject) \
+ (fwdObject)->Classify(newclass); \
+ } \
+ int64_t BytesWritten() override \
+ { return fwdObject ? (fwdObject)->BytesWritten() : 0; } \
+ void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) \
+ override \
+ { \
+ if (fwdObject) \
+ (fwdObject)->SetSecurityCallbacks(aCallbacks); \
+ }
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsAHttpConnection_h__
diff --git a/netwerk/protocol/http/nsAHttpTransaction.h b/netwerk/protocol/http/nsAHttpTransaction.h
new file mode 100644
index 000000000..7e42d191a
--- /dev/null
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -0,0 +1,301 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAHttpTransaction_h__
+#define nsAHttpTransaction_h__
+
+#include "nsISupports.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+
+class nsIInterfaceRequestor;
+class nsITransport;
+class nsIRequestContext;
+
+namespace mozilla { namespace net {
+
+class nsAHttpConnection;
+class nsAHttpSegmentReader;
+class nsAHttpSegmentWriter;
+class nsHttpTransaction;
+class nsHttpPipeline;
+class nsHttpRequestHead;
+class nsHttpConnectionInfo;
+class NullHttpTransaction;
+class SpdyConnectTransaction;
+
+//----------------------------------------------------------------------------
+// Abstract base class for a HTTP transaction:
+//
+// A transaction is a "sink" for the response data. The connection pushes
+// data to the transaction by writing to it. The transaction supports
+// WriteSegments and may refuse to accept data if its buffers are full (its
+// write function returns NS_BASE_STREAM_WOULD_BLOCK in this case).
+//----------------------------------------------------------------------------
+
+// 2af6d634-13e3-494c-8903-c9dce5c22fc0
+#define NS_AHTTPTRANSACTION_IID \
+{ 0x2af6d634, 0x13e3, 0x494c, {0x89, 0x03, 0xc9, 0xdc, 0xe5, 0xc2, 0x2f, 0xc0 }}
+
+class nsAHttpTransaction : public nsSupportsWeakReference
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_AHTTPTRANSACTION_IID)
+
+ // called by the connection when it takes ownership of the transaction.
+ virtual void SetConnection(nsAHttpConnection *) = 0;
+
+ // used to obtain the connection associated with this transaction
+ virtual nsAHttpConnection *Connection() = 0;
+
+ // called by the connection to get security callbacks to set on the
+ // socket transport.
+ virtual void GetSecurityCallbacks(nsIInterfaceRequestor **) = 0;
+
+ // called to report socket status (see nsITransportEventSink)
+ virtual void OnTransportStatus(nsITransport* transport,
+ nsresult status, int64_t progress) = 0;
+
+ // called to check the transaction status.
+ virtual bool IsDone() = 0;
+ virtual nsresult Status() = 0;
+ virtual uint32_t Caps() = 0;
+
+ // called to notify that a requested DNS cache entry was refreshed.
+ virtual void SetDNSWasRefreshed() = 0;
+
+ // called to find out how much request data is available for writing.
+ virtual uint64_t Available() = 0;
+
+ // called to read request data from the transaction.
+ virtual nsresult ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead) = 0;
+
+ // called to write response data to the transaction.
+ virtual nsresult WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count, uint32_t *countWritten) = 0;
+
+ // These versions of the functions allow the overloader to specify whether or
+ // not it is safe to call *Segments() in a loop while they return OK.
+ // The callee should turn again to false if it is not, otherwise leave untouched
+ virtual nsresult ReadSegmentsAgain(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead, bool *again)
+ {
+ return ReadSegments(reader, count, countRead);
+ }
+ virtual nsresult WriteSegmentsAgain(nsAHttpSegmentWriter *writer,
+ uint32_t count, uint32_t *countWritten, bool *again)
+ {
+ return WriteSegments(writer, count, countWritten);
+ }
+
+ // called to close the transaction
+ virtual void Close(nsresult reason) = 0;
+
+ // called to indicate a failure with proxy CONNECT
+ virtual void SetProxyConnectFailed() = 0;
+
+ // called to retrieve the request headers of the transaction
+ virtual nsHttpRequestHead *RequestHead() = 0;
+
+ // determine the number of real http/1.x transactions on this
+ // abstract object. Pipelines may have multiple, SPDY has 0,
+ // normal http transactions have 1.
+ virtual uint32_t Http1xTransactionCount() = 0;
+
+ // called to remove the unused sub transactions from an object that can
+ // handle multiple transactions simultaneously (i.e. pipelines or spdy).
+ //
+ // Returns NS_ERROR_NOT_IMPLEMENTED if the object does not implement
+ // sub-transactions.
+ //
+ // Returns NS_ERROR_ALREADY_OPENED if the subtransactions have been
+ // at least partially written and cannot be moved.
+ //
+ virtual nsresult TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) = 0;
+
+ // called to add a sub-transaction in the case of pipelined transactions
+ // classes that do not implement sub transactions
+ // return NS_ERROR_NOT_IMPLEMENTED
+ virtual nsresult AddTransaction(nsAHttpTransaction *transaction) = 0;
+
+ // The total length of the outstanding pipeline comprised of transacations
+ // and sub-transactions.
+ virtual uint32_t PipelineDepth() = 0;
+
+ // Used to inform the connection that it is being used in a pipelined
+ // context. That may influence the handling of some errors.
+ // The value is the pipeline position (> 1).
+ virtual nsresult SetPipelinePosition(int32_t) = 0;
+ virtual int32_t PipelinePosition() = 0;
+
+ // Occasionally the abstract interface has to give way to base implementations
+ // to respect differences between spdy, pipelines, etc..
+ // These Query* (and IsNullTransaction()) functions provide a way to do
+ // that without using xpcom or rtti. Any calling code that can't deal with
+ // a null response from one of them probably shouldn't be using nsAHttpTransaction
+
+ // If we used rtti this would be the result of doing
+ // dynamic_cast<nsHttpPipeline *>(this).. i.e. it can be nullptr for
+ // non pipeline implementations of nsAHttpTransaction
+ virtual nsHttpPipeline *QueryPipeline() { return nullptr; }
+
+ // equivalent to !!dynamic_cast<NullHttpTransaction *>(this)
+ // A null transaction is expected to return BASE_STREAM_CLOSED on all of
+ // its IO functions all the time.
+ virtual bool IsNullTransaction() { return false; }
+ virtual NullHttpTransaction *QueryNullTransaction() { return nullptr; }
+
+ // If we used rtti this would be the result of doing
+ // dynamic_cast<nsHttpTransaction *>(this).. i.e. it can be nullptr for
+ // non nsHttpTransaction implementations of nsAHttpTransaction
+ virtual nsHttpTransaction *QueryHttpTransaction() { return nullptr; }
+
+ // If we used rtti this would be the result of doing
+ // dynamic_cast<SpdyConnectTransaction *>(this).. i.e. it can be nullptr for
+ // other types
+ virtual SpdyConnectTransaction *QuerySpdyConnectTransaction() { return nullptr; }
+
+ // return the request context associated with the transaction
+ virtual nsIRequestContext *RequestContext() { return nullptr; }
+
+ // return the connection information associated with the transaction
+ virtual nsHttpConnectionInfo *ConnectionInfo() = 0;
+
+ // The base definition of these is done in nsHttpTransaction.cpp
+ virtual bool ResponseTimeoutEnabled() const;
+ virtual PRIntervalTime ResponseTimeout();
+
+ // Every transaction is classified into one of the types below. When using
+ // HTTP pipelines, only transactions with the same type appear on the same
+ // pipeline.
+ enum Classifier {
+ // Transactions that expect a short 304 (no-content) response
+ CLASS_REVALIDATION,
+
+ // Transactions for content expected to be CSS or JS
+ CLASS_SCRIPT,
+
+ // Transactions for content expected to be an image
+ CLASS_IMAGE,
+
+ // Transactions that cannot involve a pipeline
+ CLASS_SOLO,
+
+ // Transactions that do not fit any of the other categories. HTML
+ // is normally GENERAL.
+ CLASS_GENERAL,
+
+ CLASS_MAX
+ };
+
+ // conceptually the security info is part of the connection, but sometimes
+ // in the case of TLS tunneled within TLS the transaction might present
+ // a more specific security info that cannot be represented as a layer in
+ // the connection due to multiplexing. This interface represents such an
+ // overload. If it returns NS_FAILURE the connection should be considered
+ // authoritative.
+ virtual nsresult GetTransactionSecurityInfo(nsISupports **)
+ {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ virtual void DisableSpdy() { }
+ virtual void ReuseConnectionOnRestartOK(bool) { }
+
+ // Returns true if early-data is possible.
+ virtual bool Do0RTT() {
+ return false;
+ }
+ // This function will be called when a tls handshake has been finished and
+ // we know whether early-data that was sent has been accepted or not, e.g.
+ // do we need to restart a transaction. This will be called only if Do0RTT
+ // returns true.
+ // If aRestart parameter is true we need to restart the transaction,
+ // otherwise the erly-data has been accepted and we can continue the
+ // transaction.
+ // The function will return success or failure of the transaction restart.
+ virtual nsresult Finish0RTT(bool aRestart) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpTransaction, NS_AHTTPTRANSACTION_IID)
+
+#define NS_DECL_NSAHTTPTRANSACTION \
+ void SetConnection(nsAHttpConnection *) override; \
+ nsAHttpConnection *Connection() override; \
+ void GetSecurityCallbacks(nsIInterfaceRequestor **) override; \
+ void OnTransportStatus(nsITransport* transport, \
+ nsresult status, int64_t progress) override; \
+ bool IsDone() override; \
+ nsresult Status() override; \
+ uint32_t Caps() override; \
+ void SetDNSWasRefreshed() override; \
+ uint64_t Available() override; \
+ virtual nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *) override; \
+ virtual nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *) override; \
+ virtual void Close(nsresult reason) override; \
+ nsHttpConnectionInfo *ConnectionInfo() override; \
+ void SetProxyConnectFailed() override; \
+ virtual nsHttpRequestHead *RequestHead() override; \
+ uint32_t Http1xTransactionCount() override; \
+ nsresult TakeSubTransactions(nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) override; \
+ nsresult AddTransaction(nsAHttpTransaction *) override; \
+ uint32_t PipelineDepth() override; \
+ nsresult SetPipelinePosition(int32_t) override; \
+ int32_t PipelinePosition() override;
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+class nsAHttpSegmentReader
+{
+public:
+ // any returned failure code stops segment iteration
+ virtual nsresult OnReadSegment(const char *segment,
+ uint32_t count,
+ uint32_t *countRead) = 0;
+
+ // Ask the segment reader to commit to accepting size bytes of
+ // data from subsequent OnReadSegment() calls or throw hard
+ // (i.e. not wouldblock) exceptions. Implementations
+ // can return NS_ERROR_FAILURE if they never make commitments of that size
+ // (the default), NS_OK if they make the commitment, or
+ // NS_BASE_STREAM_WOULD_BLOCK if they cannot make the
+ // commitment now but might in the future and forceCommitment is not true .
+ // (forceCommitment requires a hard failure or OK at this moment.)
+ //
+ // SpdySession uses this to make sure frames are atomic.
+ virtual nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment)
+ {
+ return NS_ERROR_FAILURE;
+ }
+};
+
+#define NS_DECL_NSAHTTPSEGMENTREADER \
+ nsresult OnReadSegment(const char *, uint32_t, uint32_t *) override;
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+class nsAHttpSegmentWriter
+{
+public:
+ // any returned failure code stops segment iteration
+ virtual nsresult OnWriteSegment(char *segment,
+ uint32_t count,
+ uint32_t *countWritten) = 0;
+};
+
+#define NS_DECL_NSAHTTPSEGMENTWRITER \
+ nsresult OnWriteSegment(char *, uint32_t, uint32_t *) override;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsAHttpTransaction_h__
diff --git a/netwerk/protocol/http/nsCORSListenerProxy.cpp b/netwerk/protocol/http/nsCORSListenerProxy.cpp
new file mode 100644
index 000000000..c2a624330
--- /dev/null
+++ b/netwerk/protocol/http/nsCORSListenerProxy.cpp
@@ -0,0 +1,1526 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/LinkedList.h"
+
+#include "nsCORSListenerProxy.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "HttpChannelChild.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsError.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsNetUtil.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMimeTypes.h"
+#include "nsIStreamConverterService.h"
+#include "nsStringStream.h"
+#include "nsGkAtoms.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsStreamUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsIScriptError.h"
+#include "nsILoadGroup.h"
+#include "nsILoadContext.h"
+#include "nsIConsoleService.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIDOMWindow.h"
+#include "nsINetworkInterceptController.h"
+#include "nsNullPrincipal.h"
+#include "nsICorsPreflightCallback.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/LoadInfo.h"
+#include "nsIHttpHeaderVisitor.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+#define PREFLIGHT_CACHE_SIZE 100
+
+static bool gDisableCORS = false;
+static bool gDisableCORSPrivateData = false;
+
+static void
+LogBlockedRequest(nsIRequest* aRequest,
+ const char* aProperty,
+ const char16_t* aParam)
+{
+ nsresult rv = NS_OK;
+
+ // Build the error object and log it to the console
+ nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to log blocked cross-site request (no console)");
+ return;
+ }
+
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to log blocked cross-site request (no scriptError)");
+ return;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ nsCOMPtr<nsIURI> aUri;
+ channel->GetURI(getter_AddRefs(aUri));
+ nsAutoCString spec;
+ if (aUri) {
+ spec = aUri->GetSpecOrDefault();
+ }
+
+ // Generate the error message
+ nsXPIDLString blockedMessage;
+ NS_ConvertUTF8toUTF16 specUTF16(spec);
+ const char16_t* params[] = { specUTF16.get(), aParam };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES,
+ aProperty,
+ params,
+ blockedMessage);
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to log blocked cross-site request (no formalizedStr");
+ return;
+ }
+
+ nsAutoString msg(blockedMessage.get());
+
+ // query innerWindowID and log to web console, otherwise log to
+ // the error to the browser console.
+ uint64_t innerWindowID = nsContentUtils::GetInnerWindowID(aRequest);
+
+ if (innerWindowID > 0) {
+ rv = scriptError->InitWithWindowID(msg,
+ EmptyString(), // sourceName
+ EmptyString(), // sourceLine
+ 0, // lineNumber
+ 0, // columnNumber
+ nsIScriptError::warningFlag,
+ "CORS",
+ innerWindowID);
+ }
+ else {
+ rv = scriptError->Init(msg,
+ EmptyString(), // sourceName
+ EmptyString(), // sourceLine
+ 0, // lineNumber
+ 0, // columnNumber
+ nsIScriptError::warningFlag,
+ "CORS");
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to log blocked cross-site request (scriptError init failed)");
+ return;
+ }
+ console->LogMessage(scriptError);
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Preflight cache
+
+class nsPreflightCache
+{
+public:
+ struct TokenTime
+ {
+ nsCString token;
+ TimeStamp expirationTime;
+ };
+
+ struct CacheEntry : public LinkedListElement<CacheEntry>
+ {
+ explicit CacheEntry(nsCString& aKey)
+ : mKey(aKey)
+ {
+ MOZ_COUNT_CTOR(nsPreflightCache::CacheEntry);
+ }
+
+ ~CacheEntry()
+ {
+ MOZ_COUNT_DTOR(nsPreflightCache::CacheEntry);
+ }
+
+ void PurgeExpired(TimeStamp now);
+ bool CheckRequest(const nsCString& aMethod,
+ const nsTArray<nsCString>& aCustomHeaders);
+
+ nsCString mKey;
+ nsTArray<TokenTime> mMethods;
+ nsTArray<TokenTime> mHeaders;
+ };
+
+ nsPreflightCache()
+ {
+ MOZ_COUNT_CTOR(nsPreflightCache);
+ }
+
+ ~nsPreflightCache()
+ {
+ Clear();
+ MOZ_COUNT_DTOR(nsPreflightCache);
+ }
+
+ bool Initialize()
+ {
+ return true;
+ }
+
+ CacheEntry* GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ bool aWithCredentials, bool aCreate);
+ void RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal);
+
+ void Clear();
+
+private:
+ static bool GetCacheKey(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ bool aWithCredentials, nsACString& _retval);
+
+ nsClassHashtable<nsCStringHashKey, CacheEntry> mTable;
+ LinkedList<CacheEntry> mList;
+};
+
+// Will be initialized in EnsurePreflightCache.
+static nsPreflightCache* sPreflightCache = nullptr;
+
+static bool EnsurePreflightCache()
+{
+ if (sPreflightCache)
+ return true;
+
+ nsAutoPtr<nsPreflightCache> newCache(new nsPreflightCache());
+
+ if (newCache->Initialize()) {
+ sPreflightCache = newCache.forget();
+ return true;
+ }
+
+ return false;
+}
+
+void
+nsPreflightCache::CacheEntry::PurgeExpired(TimeStamp now)
+{
+ uint32_t i;
+ for (i = 0; i < mMethods.Length(); ++i) {
+ if (now >= mMethods[i].expirationTime) {
+ mMethods.RemoveElementAt(i--);
+ }
+ }
+ for (i = 0; i < mHeaders.Length(); ++i) {
+ if (now >= mHeaders[i].expirationTime) {
+ mHeaders.RemoveElementAt(i--);
+ }
+ }
+}
+
+bool
+nsPreflightCache::CacheEntry::CheckRequest(const nsCString& aMethod,
+ const nsTArray<nsCString>& aHeaders)
+{
+ PurgeExpired(TimeStamp::NowLoRes());
+
+ if (!aMethod.EqualsLiteral("GET") && !aMethod.EqualsLiteral("POST")) {
+ uint32_t i;
+ for (i = 0; i < mMethods.Length(); ++i) {
+ if (aMethod.Equals(mMethods[i].token))
+ break;
+ }
+ if (i == mMethods.Length()) {
+ return false;
+ }
+ }
+
+ for (uint32_t i = 0; i < aHeaders.Length(); ++i) {
+ uint32_t j;
+ for (j = 0; j < mHeaders.Length(); ++j) {
+ if (aHeaders[i].Equals(mHeaders[j].token,
+ nsCaseInsensitiveCStringComparator())) {
+ break;
+ }
+ }
+ if (j == mHeaders.Length()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsPreflightCache::CacheEntry*
+nsPreflightCache::GetEntry(nsIURI* aURI,
+ nsIPrincipal* aPrincipal,
+ bool aWithCredentials,
+ bool aCreate)
+{
+ nsCString key;
+ if (!GetCacheKey(aURI, aPrincipal, aWithCredentials, key)) {
+ NS_WARNING("Invalid cache key!");
+ return nullptr;
+ }
+
+ CacheEntry* existingEntry = nullptr;
+
+ if (mTable.Get(key, &existingEntry)) {
+ // Entry already existed so just return it. Also update the LRU list.
+
+ // Move to the head of the list.
+ existingEntry->removeFrom(mList);
+ mList.insertFront(existingEntry);
+
+ return existingEntry;
+ }
+
+ if (!aCreate) {
+ return nullptr;
+ }
+
+ // This is a new entry, allocate and insert into the table now so that any
+ // failures don't cause items to be removed from a full cache.
+ CacheEntry* newEntry = new CacheEntry(key);
+ if (!newEntry) {
+ NS_WARNING("Failed to allocate new cache entry!");
+ return nullptr;
+ }
+
+ NS_ASSERTION(mTable.Count() <= PREFLIGHT_CACHE_SIZE,
+ "Something is borked, too many entries in the cache!");
+
+ // Now enforce the max count.
+ if (mTable.Count() == PREFLIGHT_CACHE_SIZE) {
+ // Try to kick out all the expired entries.
+ TimeStamp now = TimeStamp::NowLoRes();
+ for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<CacheEntry>& entry = iter.Data();
+ entry->PurgeExpired(now);
+
+ if (entry->mHeaders.IsEmpty() &&
+ entry->mMethods.IsEmpty()) {
+ // Expired, remove from the list as well as the hash table.
+ entry->removeFrom(sPreflightCache->mList);
+ iter.Remove();
+ }
+ }
+
+ // If that didn't remove anything then kick out the least recently used
+ // entry.
+ if (mTable.Count() == PREFLIGHT_CACHE_SIZE) {
+ CacheEntry* lruEntry = static_cast<CacheEntry*>(mList.popLast());
+ MOZ_ASSERT(lruEntry);
+
+ // This will delete 'lruEntry'.
+ mTable.Remove(lruEntry->mKey);
+
+ NS_ASSERTION(mTable.Count() == PREFLIGHT_CACHE_SIZE - 1,
+ "Somehow tried to remove an entry that was never added!");
+ }
+ }
+
+ mTable.Put(key, newEntry);
+ mList.insertFront(newEntry);
+
+ return newEntry;
+}
+
+void
+nsPreflightCache::RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal)
+{
+ CacheEntry* entry;
+ nsCString key;
+ if (GetCacheKey(aURI, aPrincipal, true, key) &&
+ mTable.Get(key, &entry)) {
+ entry->removeFrom(mList);
+ mTable.Remove(key);
+ }
+
+ if (GetCacheKey(aURI, aPrincipal, false, key) &&
+ mTable.Get(key, &entry)) {
+ entry->removeFrom(mList);
+ mTable.Remove(key);
+ }
+}
+
+void
+nsPreflightCache::Clear()
+{
+ mList.clear();
+ mTable.Clear();
+}
+
+/* static */ bool
+nsPreflightCache::GetCacheKey(nsIURI* aURI,
+ nsIPrincipal* aPrincipal,
+ bool aWithCredentials,
+ nsACString& _retval)
+{
+ NS_ASSERTION(aURI, "Null uri!");
+ NS_ASSERTION(aPrincipal, "Null principal!");
+
+ NS_NAMED_LITERAL_CSTRING(space, " ");
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoCString scheme, host, port;
+ if (uri) {
+ uri->GetScheme(scheme);
+ uri->GetHost(host);
+ port.AppendInt(NS_GetRealPort(uri));
+ }
+
+ if (aWithCredentials) {
+ _retval.AssignLiteral("cred");
+ }
+ else {
+ _retval.AssignLiteral("nocred");
+ }
+
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ _retval.Append(space + scheme + space + host + space + port + space +
+ spec);
+
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////
+// nsCORSListenerProxy
+
+NS_IMPL_ISUPPORTS(nsCORSListenerProxy, nsIStreamListener,
+ nsIRequestObserver, nsIChannelEventSink,
+ nsIInterfaceRequestor, nsIThreadRetargetableStreamListener)
+
+/* static */
+void
+nsCORSListenerProxy::Startup()
+{
+ Preferences::AddBoolVarCache(&gDisableCORS,
+ "content.cors.disable");
+ Preferences::AddBoolVarCache(&gDisableCORSPrivateData,
+ "content.cors.no_private_data");
+}
+
+/* static */
+void
+nsCORSListenerProxy::Shutdown()
+{
+ delete sPreflightCache;
+ sPreflightCache = nullptr;
+}
+
+nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter,
+ nsIPrincipal* aRequestingPrincipal,
+ bool aWithCredentials)
+ : mOuterListener(aOuter),
+ mRequestingPrincipal(aRequestingPrincipal),
+ mOriginHeaderPrincipal(aRequestingPrincipal),
+ mWithCredentials(aWithCredentials && !gDisableCORSPrivateData),
+ mRequestApproved(false),
+ mHasBeenCrossSite(false)
+{
+}
+
+nsCORSListenerProxy::~nsCORSListenerProxy()
+{
+}
+
+nsresult
+nsCORSListenerProxy::Init(nsIChannel* aChannel, DataURIHandling aAllowDataURI)
+{
+ aChannel->GetNotificationCallbacks(getter_AddRefs(mOuterNotificationCallbacks));
+ aChannel->SetNotificationCallbacks(this);
+
+ nsresult rv = UpdateChannel(aChannel, aAllowDataURI, UpdateType::Default);
+ if (NS_FAILED(rv)) {
+ mOuterListener = nullptr;
+ mRequestingPrincipal = nullptr;
+ mOriginHeaderPrincipal = nullptr;
+ mOuterNotificationCallbacks = nullptr;
+ }
+#ifdef DEBUG
+ mInited = true;
+#endif
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
+ nsresult rv = CheckRequestApproved(aRequest);
+ mRequestApproved = NS_SUCCEEDED(rv);
+ if (!mRequestApproved) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
+ if (uri) {
+ if (sPreflightCache) {
+ // OK to use mRequestingPrincipal since preflights never get
+ // redirected.
+ sPreflightCache->RemoveEntries(uri, mRequestingPrincipal);
+ } else {
+ nsCOMPtr<nsIHttpChannelChild> httpChannelChild =
+ do_QueryInterface(channel);
+ if (httpChannelChild) {
+ rv = httpChannelChild->RemoveCorsPreflightCacheEntry(uri, mRequestingPrincipal);
+ if (NS_FAILED(rv)) {
+ // Only warn here to ensure we fall through the request Cancel()
+ // and outer listener OnStartRequest() calls.
+ NS_WARNING("Failed to remove CORS preflight cache entry!");
+ }
+ }
+ }
+ }
+ }
+
+ aRequest->Cancel(NS_ERROR_DOM_BAD_URI);
+ mOuterListener->OnStartRequest(aRequest, aContext);
+
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ return mOuterListener->OnStartRequest(aRequest, aContext);
+}
+
+namespace {
+class CheckOriginHeader final : public nsIHttpHeaderVisitor {
+
+public:
+ NS_DECL_ISUPPORTS
+
+ CheckOriginHeader()
+ : mHeaderCount(0)
+ {}
+
+ NS_IMETHOD
+ VisitHeader(const nsACString & aHeader, const nsACString & aValue) override
+ {
+ if (aHeader.EqualsLiteral("Access-Control-Allow-Origin")) {
+ mHeaderCount++;
+ }
+
+ if (mHeaderCount > 1) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ return NS_OK;
+ }
+
+private:
+ uint32_t mHeaderCount;
+
+ ~CheckOriginHeader()
+ {}
+
+};
+
+NS_IMPL_ISUPPORTS(CheckOriginHeader, nsIHttpHeaderVisitor)
+}
+
+nsresult
+nsCORSListenerProxy::CheckRequestApproved(nsIRequest* aRequest)
+{
+ // Check if this was actually a cross domain request
+ if (!mHasBeenCrossSite) {
+ return NS_OK;
+ }
+
+ if (gDisableCORS) {
+ LogBlockedRequest(aRequest, "CORSDisabled", nullptr);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // Check if the request failed
+ nsresult status;
+ nsresult rv = aRequest->GetStatus(&status);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(status, status);
+
+ // Test that things worked on a HTTP level
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
+ if (!http) {
+ LogBlockedRequest(aRequest, "CORSRequestNotHttp", nullptr);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
+ NS_ENSURE_STATE(internal);
+ bool responseSynthesized = false;
+ if (NS_SUCCEEDED(internal->GetResponseSynthesized(&responseSynthesized)) &&
+ responseSynthesized) {
+ // For synthesized responses, we don't need to perform any checks.
+ // Note: This would be unsafe if we ever changed our behavior to allow
+ // service workers to intercept CORS preflights.
+ return NS_OK;
+ }
+
+ // Check the Access-Control-Allow-Origin header
+ RefPtr<CheckOriginHeader> visitor = new CheckOriginHeader();
+ nsAutoCString allowedOriginHeader;
+
+ // check for duplicate headers
+ rv = http->VisitOriginalResponseHeaders(visitor);
+ if (NS_FAILED(rv)) {
+ LogBlockedRequest(aRequest, "CORSAllowOriginNotMatchingOrigin", nullptr);
+ return rv;
+ }
+
+ rv = http->GetResponseHeader(
+ NS_LITERAL_CSTRING("Access-Control-Allow-Origin"), allowedOriginHeader);
+ if (NS_FAILED(rv)) {
+ LogBlockedRequest(aRequest, "CORSMissingAllowOrigin", nullptr);
+ return rv;
+ }
+
+ // Bug 1210985 - Explicitly point out the error that the credential is
+ // not supported if the allowing origin is '*'. Note that this check
+ // has to be done before the condition
+ //
+ // >> if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*"))
+ //
+ // below since "if (A && B)" is included in "if (A || !B)".
+ //
+ if (mWithCredentials && allowedOriginHeader.EqualsLiteral("*")) {
+ LogBlockedRequest(aRequest, "CORSNotSupportingCredentials", nullptr);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*")) {
+ MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(mOriginHeaderPrincipal));
+ nsAutoCString origin;
+ nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin);
+
+ if (!allowedOriginHeader.Equals(origin)) {
+ LogBlockedRequest(aRequest, "CORSAllowOriginNotMatchingOrigin",
+ NS_ConvertUTF8toUTF16(allowedOriginHeader).get());
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+
+ // Check Access-Control-Allow-Credentials header
+ if (mWithCredentials) {
+ nsAutoCString allowCredentialsHeader;
+ rv = http->GetResponseHeader(
+ NS_LITERAL_CSTRING("Access-Control-Allow-Credentials"), allowCredentialsHeader);
+
+ if (!allowCredentialsHeader.EqualsLiteral("true")) {
+ LogBlockedRequest(aRequest, "CORSMissingAllowCredentials", nullptr);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatusCode)
+{
+ MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
+ nsresult rv = mOuterListener->OnStopRequest(aRequest, aContext, aStatusCode);
+ mOuterListener = nullptr;
+ mOuterNotificationCallbacks = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ // NB: This can be called on any thread! But we're guaranteed that it is
+ // called between OnStartRequest and OnStopRequest, so we don't need to worry
+ // about races.
+
+ MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
+ if (!mRequestApproved) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ return mOuterListener->OnDataAvailable(aRequest, aContext, aInputStream,
+ aOffset, aCount);
+}
+
+void
+nsCORSListenerProxy::SetInterceptController(nsINetworkInterceptController* aInterceptController)
+{
+ mInterceptController = aInterceptController;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::GetInterface(const nsIID & aIID, void **aResult)
+{
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ *aResult = static_cast<nsIChannelEventSink*>(this);
+ NS_ADDREF_THIS();
+
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) &&
+ mInterceptController) {
+ nsCOMPtr<nsINetworkInterceptController> copy(mInterceptController);
+ *aResult = copy.forget().take();
+
+ return NS_OK;
+ }
+
+ return mOuterNotificationCallbacks ?
+ mOuterNotificationCallbacks->GetInterface(aIID, aResult) :
+ NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback *aCb)
+{
+ nsresult rv;
+ if (NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags) ||
+ NS_IsHSTSUpgradeRedirect(aOldChannel, aNewChannel, aFlags)) {
+ // Internal redirects still need to be updated in order to maintain
+ // the correct headers. We use DataURIHandling::Allow, since unallowed
+ // data URIs should have been blocked before we got to the internal
+ // redirect.
+ rv = UpdateChannel(aNewChannel, DataURIHandling::Allow,
+ UpdateType::InternalOrHSTSRedirect);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("nsCORSListenerProxy::AsyncOnChannelRedirect: "
+ "internal redirect UpdateChannel() returned failure");
+ aOldChannel->Cancel(rv);
+ return rv;
+ }
+ } else {
+ // A real, external redirect. Perform CORS checking on new URL.
+ rv = CheckRequestApproved(aOldChannel);
+ if (NS_FAILED(rv)) {
+ nsCOMPtr<nsIURI> oldURI;
+ NS_GetFinalChannelURI(aOldChannel, getter_AddRefs(oldURI));
+ if (oldURI) {
+ if (sPreflightCache) {
+ // OK to use mRequestingPrincipal since preflights never get
+ // redirected.
+ sPreflightCache->RemoveEntries(oldURI, mRequestingPrincipal);
+ } else {
+ nsCOMPtr<nsIHttpChannelChild> httpChannelChild =
+ do_QueryInterface(aOldChannel);
+ if (httpChannelChild) {
+ rv = httpChannelChild->RemoveCorsPreflightCacheEntry(oldURI, mRequestingPrincipal);
+ if (NS_FAILED(rv)) {
+ // Only warn here to ensure we call the channel Cancel() below
+ NS_WARNING("Failed to remove CORS preflight cache entry!");
+ }
+ }
+ }
+ }
+ aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ if (mHasBeenCrossSite) {
+ // Once we've been cross-site, cross-origin redirects reset our source
+ // origin. Note that we need to call GetChannelURIPrincipal() because
+ // we are looking for the principal that is actually being loaded and not
+ // the principal that initiated the load.
+ nsCOMPtr<nsIPrincipal> oldChannelPrincipal;
+ nsContentUtils::GetSecurityManager()->
+ GetChannelURIPrincipal(aOldChannel, getter_AddRefs(oldChannelPrincipal));
+ nsCOMPtr<nsIPrincipal> newChannelPrincipal;
+ nsContentUtils::GetSecurityManager()->
+ GetChannelURIPrincipal(aNewChannel, getter_AddRefs(newChannelPrincipal));
+ if (!oldChannelPrincipal || !newChannelPrincipal) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ bool equal;
+ rv = oldChannelPrincipal->Equals(newChannelPrincipal, &equal);
+ if (NS_SUCCEEDED(rv) && !equal) {
+ // Spec says to set our source origin to a unique origin.
+ mOriginHeaderPrincipal =
+ nsNullPrincipal::CreateWithInheritedAttributes(oldChannelPrincipal);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ aOldChannel->Cancel(rv);
+ return rv;
+ }
+ }
+
+ rv = UpdateChannel(aNewChannel, DataURIHandling::Disallow,
+ UpdateType::Default);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("nsCORSListenerProxy::AsyncOnChannelRedirect: "
+ "UpdateChannel() returned failure");
+ aOldChannel->Cancel(rv);
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIChannelEventSink> outer =
+ do_GetInterface(mOuterNotificationCallbacks);
+ if (outer) {
+ return outer->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCb);
+ }
+
+ aCb->OnRedirectVerifyCallback(NS_OK);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCORSListenerProxy::CheckListenerChain()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mOuterListener)) {
+ return retargetableListener->CheckListenerChain();
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+// Please note that the CSP directive 'upgrade-insecure-requests' relies
+// on the promise that channels get updated from http: to https: before
+// the channel fetches any data from the netwerk. Such channels should
+// not be blocked by CORS and marked as cross origin requests. E.g.:
+// toplevel page: https://www.example.com loads
+// xhr: http://www.example.com/foo which gets updated to
+// https://www.example.com/foo
+// In such a case we should bail out of CORS and rely on the promise that
+// nsHttpChannel::Connect() upgrades the request from http to https.
+bool
+CheckUpgradeInsecureRequestsPreventsCORS(nsIPrincipal* aRequestingPrincipal,
+ nsIChannel* aChannel)
+{
+ nsCOMPtr<nsIURI> channelURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
+ NS_ENSURE_SUCCESS(rv, false);
+ bool isHttpScheme = false;
+ rv = channelURI->SchemeIs("http", &isHttpScheme);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // upgrade insecure requests is only applicable to http requests
+ if (!isHttpScheme) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> principalURI;
+ rv = aRequestingPrincipal->GetURI(getter_AddRefs(principalURI));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // if the requestingPrincipal does not have a uri, there is nothing to do
+ if (!principalURI) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI>originalURI;
+ rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoCString principalHost, channelHost, origChannelHost;
+
+ // if we can not query a host from the uri, there is nothing to do
+ if (NS_FAILED(principalURI->GetAsciiHost(principalHost)) ||
+ NS_FAILED(channelURI->GetAsciiHost(channelHost)) ||
+ NS_FAILED(originalURI->GetAsciiHost(origChannelHost))) {
+ return false;
+ }
+
+ // if the hosts do not match, there is nothing to do
+ if (!principalHost.EqualsIgnoreCase(channelHost.get())) {
+ return false;
+ }
+
+ // also check that uri matches the one of the originalURI
+ if (!channelHost.EqualsIgnoreCase(origChannelHost.get())) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // lets see if the loadInfo indicates that the request will
+ // be upgraded before fetching any data from the netwerk.
+ return loadInfo->GetUpgradeInsecureRequests();
+}
+
+
+nsresult
+nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel,
+ DataURIHandling aAllowDataURI,
+ UpdateType aUpdateType)
+{
+ nsCOMPtr<nsIURI> uri, originalURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+
+ // exempt data URIs from the same origin check.
+ if (aAllowDataURI == DataURIHandling::Allow && originalURI == uri) {
+ bool dataScheme = false;
+ rv = uri->SchemeIs("data", &dataScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (dataScheme) {
+ return NS_OK;
+ }
+ if (loadInfo && loadInfo->GetAboutBlankInherits() &&
+ NS_IsAboutBlank(uri)) {
+ return NS_OK;
+ }
+ }
+
+ // Set CORS attributes on channel so that intercepted requests get correct
+ // values. We have to do this here because the CheckMayLoad checks may lead
+ // to early return. We can't be sure this is an http channel though, so we
+ // can't return early on failure.
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aChannel);
+ if (internal) {
+ rv = internal->SetCorsMode(nsIHttpChannelInternal::CORS_MODE_CORS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = internal->SetCorsIncludeCredentials(mWithCredentials);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Check that the uri is ok to load
+ rv = nsContentUtils::GetSecurityManager()->
+ CheckLoadURIWithPrincipal(mRequestingPrincipal, uri,
+ nsIScriptSecurityManager::STANDARD);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (originalURI != uri) {
+ rv = nsContentUtils::GetSecurityManager()->
+ CheckLoadURIWithPrincipal(mRequestingPrincipal, originalURI,
+ nsIScriptSecurityManager::STANDARD);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!mHasBeenCrossSite &&
+ NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(uri, false, false)) &&
+ (originalURI == uri ||
+ NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(originalURI,
+ false, false)))) {
+ return NS_OK;
+ }
+
+ // if the CSP directive 'upgrade-insecure-requests' is used then we should
+ // not incorrectly require CORS if the only difference of a subresource
+ // request and the main page is the scheme.
+ // e.g. toplevel page: https://www.example.com loads
+ // xhr: http://www.example.com/somefoo,
+ // then the xhr request will be upgraded to https before it fetches any data
+ // from the netwerk, hence we shouldn't require CORS in that specific case.
+ if (CheckUpgradeInsecureRequestsPreventsCORS(mRequestingPrincipal, aChannel)) {
+ return NS_OK;
+ }
+
+ // Check if we need to do a preflight, and if so set one up. This must be
+ // called once we know that the request is going, or has gone, cross-origin.
+ rv = CheckPreflightNeeded(aChannel, aUpdateType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // It's a cross site load
+ mHasBeenCrossSite = true;
+
+ nsCString userpass;
+ uri->GetUserPass(userpass);
+ NS_ENSURE_TRUE(userpass.IsEmpty(), NS_ERROR_DOM_BAD_URI);
+
+ // If we have an expanded principal here, we'll reject the CORS request,
+ // because we can't send a useful Origin header which is required for CORS.
+ if (nsContentUtils::IsExpandedPrincipal(mOriginHeaderPrincipal)) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // Add the Origin header
+ nsAutoCString origin;
+ rv = nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
+ NS_ENSURE_TRUE(http, NS_ERROR_FAILURE);
+
+ rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), origin, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make cookie-less if needed. We don't need to do anything here if the
+ // channel was opened with AsyncOpen2, since then AsyncOpen2 will take
+ // care of the cookie policy for us.
+ if (!mWithCredentials &&
+ (!loadInfo || !loadInfo->GetEnforceSecurity())) {
+ nsLoadFlags flags;
+ rv = http->GetLoadFlags(&flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ flags |= nsIRequest::LOAD_ANONYMOUS;
+ rv = http->SetLoadFlags(flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsCORSListenerProxy::CheckPreflightNeeded(nsIChannel* aChannel, UpdateType aUpdateType)
+{
+ // If this caller isn't using AsyncOpen2, or if this *is* a preflight channel,
+ // then we shouldn't initiate preflight for this channel.
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (!loadInfo ||
+ loadInfo->GetSecurityMode() !=
+ nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS ||
+ loadInfo->GetIsPreflight()) {
+ return NS_OK;
+ }
+
+ bool doPreflight = loadInfo->GetForcePreflight();
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
+ NS_ENSURE_TRUE(http, NS_ERROR_DOM_BAD_URI);
+ nsAutoCString method;
+ http->GetRequestMethod(method);
+ if (!method.LowerCaseEqualsLiteral("get") &&
+ !method.LowerCaseEqualsLiteral("post") &&
+ !method.LowerCaseEqualsLiteral("head")) {
+ doPreflight = true;
+ }
+
+ // Avoid copying the array here
+ const nsTArray<nsCString>& loadInfoHeaders = loadInfo->CorsUnsafeHeaders();
+ if (!loadInfoHeaders.IsEmpty()) {
+ doPreflight = true;
+ }
+
+ // Add Content-Type header if needed
+ nsTArray<nsCString> headers;
+ nsAutoCString contentTypeHeader;
+ nsresult rv = http->GetRequestHeader(NS_LITERAL_CSTRING("Content-Type"),
+ contentTypeHeader);
+ // GetRequestHeader return an error if the header is not set. Don't add
+ // "content-type" to the list if that's the case.
+ if (NS_SUCCEEDED(rv) &&
+ !nsContentUtils::IsAllowedNonCorsContentType(contentTypeHeader) &&
+ !loadInfoHeaders.Contains(NS_LITERAL_CSTRING("content-type"),
+ nsCaseInsensitiveCStringArrayComparator())) {
+ headers.AppendElements(loadInfoHeaders);
+ headers.AppendElement(NS_LITERAL_CSTRING("content-type"));
+ doPreflight = true;
+ }
+
+ if (!doPreflight) {
+ return NS_OK;
+ }
+
+ // A preflight is needed. But if we've already been cross-site, then
+ // we already did a preflight when that happened, and so we're not allowed
+ // to do another preflight again.
+ if (aUpdateType != UpdateType::InternalOrHSTSRedirect) {
+ NS_ENSURE_FALSE(mHasBeenCrossSite, NS_ERROR_DOM_BAD_URI);
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(http);
+ NS_ENSURE_TRUE(internal, NS_ERROR_DOM_BAD_URI);
+
+ internal->SetCorsPreflightParameters(
+ headers.IsEmpty() ? loadInfoHeaders : headers);
+
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Preflight proxy
+
+// Class used as streamlistener and notification callback when
+// doing the initial OPTIONS request for a CORS check
+class nsCORSPreflightListener final : public nsIStreamListener,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink
+{
+public:
+ nsCORSPreflightListener(nsIPrincipal* aReferrerPrincipal,
+ nsICorsPreflightCallback* aCallback,
+ nsILoadContext* aLoadContext,
+ bool aWithCredentials,
+ const nsCString& aPreflightMethod,
+ const nsTArray<nsCString>& aPreflightHeaders)
+ : mPreflightMethod(aPreflightMethod),
+ mPreflightHeaders(aPreflightHeaders),
+ mReferrerPrincipal(aReferrerPrincipal),
+ mCallback(aCallback),
+ mLoadContext(aLoadContext),
+ mWithCredentials(aWithCredentials)
+ {
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+
+ nsresult CheckPreflightRequestApproved(nsIRequest* aRequest);
+
+private:
+ ~nsCORSPreflightListener() {}
+
+ void AddResultToCache(nsIRequest* aRequest);
+
+ nsCString mPreflightMethod;
+ nsTArray<nsCString> mPreflightHeaders;
+ nsCOMPtr<nsIPrincipal> mReferrerPrincipal;
+ nsCOMPtr<nsICorsPreflightCallback> mCallback;
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ bool mWithCredentials;
+};
+
+NS_IMPL_ISUPPORTS(nsCORSPreflightListener, nsIStreamListener,
+ nsIRequestObserver, nsIInterfaceRequestor,
+ nsIChannelEventSink)
+
+void
+nsCORSPreflightListener::AddResultToCache(nsIRequest *aRequest)
+{
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
+ NS_ASSERTION(http, "Request was not http");
+
+ // The "Access-Control-Max-Age" header should return an age in seconds.
+ nsAutoCString headerVal;
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Max-Age"),
+ headerVal);
+ if (headerVal.IsEmpty()) {
+ return;
+ }
+
+ // Sanitize the string. We only allow 'delta-seconds' as specified by
+ // http://dev.w3.org/2006/waf/access-control (digits 0-9 with no leading or
+ // trailing non-whitespace characters).
+ uint32_t age = 0;
+ nsCSubstring::const_char_iterator iter, end;
+ headerVal.BeginReading(iter);
+ headerVal.EndReading(end);
+ while (iter != end) {
+ if (*iter < '0' || *iter > '9') {
+ return;
+ }
+ age = age * 10 + (*iter - '0');
+ // Cap at 24 hours. This also avoids overflow
+ age = std::min(age, 86400U);
+ ++iter;
+ }
+
+ if (!age || !EnsurePreflightCache()) {
+ return;
+ }
+
+
+ // String seems fine, go ahead and cache.
+ // Note that we have already checked that these headers follow the correct
+ // syntax.
+
+ nsCOMPtr<nsIURI> uri;
+ NS_GetFinalChannelURI(http, getter_AddRefs(uri));
+
+ TimeStamp expirationTime = TimeStamp::NowLoRes() + TimeDuration::FromSeconds(age);
+
+ nsPreflightCache::CacheEntry* entry =
+ sPreflightCache->GetEntry(uri, mReferrerPrincipal, mWithCredentials,
+ true);
+ if (!entry) {
+ return;
+ }
+
+ // The "Access-Control-Allow-Methods" header contains a comma separated
+ // list of method names.
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"),
+ headerVal);
+
+ nsCCharSeparatedTokenizer methods(headerVal, ',');
+ while(methods.hasMoreTokens()) {
+ const nsDependentCSubstring& method = methods.nextToken();
+ if (method.IsEmpty()) {
+ continue;
+ }
+ uint32_t i;
+ for (i = 0; i < entry->mMethods.Length(); ++i) {
+ if (entry->mMethods[i].token.Equals(method)) {
+ entry->mMethods[i].expirationTime = expirationTime;
+ break;
+ }
+ }
+ if (i == entry->mMethods.Length()) {
+ nsPreflightCache::TokenTime* newMethod =
+ entry->mMethods.AppendElement();
+ if (!newMethod) {
+ return;
+ }
+
+ newMethod->token = method;
+ newMethod->expirationTime = expirationTime;
+ }
+ }
+
+ // The "Access-Control-Allow-Headers" header contains a comma separated
+ // list of method names.
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"),
+ headerVal);
+
+ nsCCharSeparatedTokenizer headers(headerVal, ',');
+ while(headers.hasMoreTokens()) {
+ const nsDependentCSubstring& header = headers.nextToken();
+ if (header.IsEmpty()) {
+ continue;
+ }
+ uint32_t i;
+ for (i = 0; i < entry->mHeaders.Length(); ++i) {
+ if (entry->mHeaders[i].token.Equals(header)) {
+ entry->mHeaders[i].expirationTime = expirationTime;
+ break;
+ }
+ }
+ if (i == entry->mHeaders.Length()) {
+ nsPreflightCache::TokenTime* newHeader =
+ entry->mHeaders.AppendElement();
+ if (!newHeader) {
+ return;
+ }
+
+ newHeader->token = header;
+ newHeader->expirationTime = expirationTime;
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsCORSPreflightListener::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
+ bool responseSynthesized = false;
+ if (internal &&
+ NS_SUCCEEDED(internal->GetResponseSynthesized(&responseSynthesized))) {
+ // For synthesized responses, we don't need to perform any checks.
+ // This would be unsafe if we ever changed our behavior to allow
+ // service workers to intercept CORS preflights.
+ MOZ_ASSERT(!responseSynthesized);
+ }
+ }
+#endif
+
+ nsresult rv = CheckPreflightRequestApproved(aRequest);
+
+ if (NS_SUCCEEDED(rv)) {
+ // Everything worked, try to cache and then fire off the actual request.
+ AddResultToCache(aRequest);
+
+ mCallback->OnPreflightSucceeded();
+ } else {
+ mCallback->OnPreflightFailed(rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCORSPreflightListener::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ mCallback = nullptr;
+ return NS_OK;
+}
+
+/** nsIStreamListener methods **/
+
+NS_IMETHODIMP
+nsCORSPreflightListener::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset,
+ uint32_t count)
+{
+ uint32_t totalRead;
+ return inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &totalRead);
+}
+
+NS_IMETHODIMP
+nsCORSPreflightListener::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback *callback)
+{
+ // Only internal redirects allowed for now.
+ if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags) &&
+ !NS_IsHSTSUpgradeRedirect(aOldChannel, aNewChannel, aFlags))
+ return NS_ERROR_DOM_BAD_URI;
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+nsresult
+nsCORSPreflightListener::CheckPreflightRequestApproved(nsIRequest* aRequest)
+{
+ nsresult status;
+ nsresult rv = aRequest->GetStatus(&status);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(status, status);
+
+ // Test that things worked on a HTTP level
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
+ nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
+ NS_ENSURE_STATE(internal);
+
+ bool succeedded;
+ rv = http->GetRequestSucceeded(&succeedded);
+ if (NS_FAILED(rv) || !succeedded) {
+ LogBlockedRequest(aRequest, "CORSPreflightDidNotSucceed", nullptr);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsAutoCString headerVal;
+ // The "Access-Control-Allow-Methods" header contains a comma separated
+ // list of method names.
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"),
+ headerVal);
+ bool foundMethod = mPreflightMethod.EqualsLiteral("GET") ||
+ mPreflightMethod.EqualsLiteral("HEAD") ||
+ mPreflightMethod.EqualsLiteral("POST");
+ nsCCharSeparatedTokenizer methodTokens(headerVal, ',');
+ while(methodTokens.hasMoreTokens()) {
+ const nsDependentCSubstring& method = methodTokens.nextToken();
+ if (method.IsEmpty()) {
+ continue;
+ }
+ if (!NS_IsValidHTTPToken(method)) {
+ LogBlockedRequest(aRequest, "CORSInvalidAllowMethod",
+ NS_ConvertUTF8toUTF16(method).get());
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ foundMethod |= mPreflightMethod.Equals(method);
+ }
+ if (!foundMethod) {
+ LogBlockedRequest(aRequest, "CORSMethodNotFound", nullptr);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // The "Access-Control-Allow-Headers" header contains a comma separated
+ // list of header names.
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"),
+ headerVal);
+ nsTArray<nsCString> headers;
+ nsCCharSeparatedTokenizer headerTokens(headerVal, ',');
+ while(headerTokens.hasMoreTokens()) {
+ const nsDependentCSubstring& header = headerTokens.nextToken();
+ if (header.IsEmpty()) {
+ continue;
+ }
+ if (!NS_IsValidHTTPToken(header)) {
+ LogBlockedRequest(aRequest, "CORSInvalidAllowHeader",
+ NS_ConvertUTF8toUTF16(header).get());
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ headers.AppendElement(header);
+ }
+ for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
+ if (!headers.Contains(mPreflightHeaders[i],
+ nsCaseInsensitiveCStringArrayComparator())) {
+ LogBlockedRequest(aRequest, "CORSMissingAllowHeaderFromPreflight",
+ NS_ConvertUTF8toUTF16(mPreflightHeaders[i]).get());
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCORSPreflightListener::GetInterface(const nsIID & aIID, void **aResult)
+{
+ if (aIID.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(aResult);
+ return NS_OK;
+ }
+
+ return QueryInterface(aIID, aResult);
+}
+
+void
+nsCORSListenerProxy::RemoveFromCorsPreflightCache(nsIURI* aURI,
+ nsIPrincipal* aRequestingPrincipal)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (sPreflightCache) {
+ sPreflightCache->RemoveEntries(aURI, aRequestingPrincipal);
+ }
+}
+
+nsresult
+nsCORSListenerProxy::StartCORSPreflight(nsIChannel* aRequestChannel,
+ nsICorsPreflightCallback* aCallback,
+ nsTArray<nsCString>& aUnsafeHeaders,
+ nsIChannel** aPreflightChannel)
+{
+ *aPreflightChannel = nullptr;
+
+ if (gDisableCORS) {
+ LogBlockedRequest(aRequestChannel, "CORSDisabled", nullptr);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsAutoCString method;
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequestChannel));
+ NS_ENSURE_TRUE(httpChannel, NS_ERROR_UNEXPECTED);
+ httpChannel->GetRequestMethod(method);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadInfo> originalLoadInfo = aRequestChannel->GetLoadInfo();
+ MOZ_ASSERT(originalLoadInfo, "can not perform CORS preflight without a loadInfo");
+ if (!originalLoadInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(originalLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS,
+ "how did we end up here?");
+
+ nsCOMPtr<nsIPrincipal> principal = originalLoadInfo->LoadingPrincipal();
+ MOZ_ASSERT(principal &&
+ originalLoadInfo->GetExternalContentPolicyType() !=
+ nsIContentPolicy::TYPE_DOCUMENT,
+ "Should not do CORS loads for top-level loads, so a loadingPrincipal should always exist.");
+ bool withCredentials = originalLoadInfo->GetCookiePolicy() ==
+ nsILoadInfo::SEC_COOKIES_INCLUDE;
+
+ nsPreflightCache::CacheEntry* entry =
+ sPreflightCache ?
+ sPreflightCache->GetEntry(uri, principal, withCredentials, false) :
+ nullptr;
+
+ if (entry && entry->CheckRequest(method, aUnsafeHeaders)) {
+ aCallback->OnPreflightSucceeded();
+ return NS_OK;
+ }
+
+ // Either it wasn't cached or the cached result has expired. Build a
+ // channel for the OPTIONS request.
+
+ nsCOMPtr<nsILoadInfo> loadInfo = static_cast<mozilla::LoadInfo*>
+ (originalLoadInfo.get())->CloneForNewRequest();
+ static_cast<mozilla::LoadInfo*>(loadInfo.get())->SetIsPreflight();
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We want to give the preflight channel's notification callbacks the same
+ // load context as the original channel's notification callbacks had. We
+ // don't worry about a load context provided via the loadgroup here, since
+ // they have the same loadgroup.
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ rv = aRequestChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
+
+ nsLoadFlags loadFlags;
+ rv = aRequestChannel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Preflight requests should never be intercepted by service workers and
+ // are always anonymous.
+ // NOTE: We ignore CORS checks on synthesized responses (see the CORS
+ // preflights, then we need to extend the GetResponseSynthesized() check in
+ // nsCORSListenerProxy::CheckRequestApproved()). If we change our behavior
+ // here and allow service workers to intercept CORS preflights, then that
+ // check won't be safe any more.
+ loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER |
+ nsIRequest::LOAD_ANONYMOUS;
+
+ nsCOMPtr<nsIChannel> preflightChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(preflightChannel),
+ uri,
+ loadInfo,
+ loadGroup,
+ nullptr, // aCallbacks
+ loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set method and headers
+ nsCOMPtr<nsIHttpChannel> preHttp = do_QueryInterface(preflightChannel);
+ NS_ASSERTION(preHttp, "Failed to QI to nsIHttpChannel!");
+
+ rv = preHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = preHttp->
+ SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Method"),
+ method, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsCString> preflightHeaders;
+ if (!aUnsafeHeaders.IsEmpty()) {
+ for (uint32_t i = 0; i < aUnsafeHeaders.Length(); ++i) {
+ preflightHeaders.AppendElement();
+ ToLowerCase(aUnsafeHeaders[i], preflightHeaders[i]);
+ }
+ preflightHeaders.Sort();
+ nsAutoCString headers;
+ for (uint32_t i = 0; i < preflightHeaders.Length(); ++i) {
+ if (i != 0) {
+ headers += ',';
+ }
+ headers += preflightHeaders[i];
+ }
+ rv = preHttp->
+ SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Headers"),
+ headers, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Set up listener which will start the original channel
+ RefPtr<nsCORSPreflightListener> preflightListener =
+ new nsCORSPreflightListener(principal, aCallback, loadContext,
+ withCredentials, method, preflightHeaders);
+
+ rv = preflightChannel->SetNotificationCallbacks(preflightListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Start preflight
+ rv = preflightChannel->AsyncOpen2(preflightListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Return newly created preflight channel
+ preflightChannel.forget(aPreflightChannel);
+
+ return NS_OK;
+}
diff --git a/netwerk/protocol/http/nsCORSListenerProxy.h b/netwerk/protocol/http/nsCORSListenerProxy.h
new file mode 100644
index 000000000..75918fc46
--- /dev/null
+++ b/netwerk/protocol/http/nsCORSListenerProxy.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCORSListenerProxy_h__
+#define nsCORSListenerProxy_h__
+
+#include "nsIStreamListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIURI.h"
+#include "nsTArray.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "mozilla/Attributes.h"
+
+class nsIURI;
+class nsIPrincipal;
+class nsINetworkInterceptController;
+class nsICorsPreflightCallback;
+
+namespace mozilla {
+namespace net {
+class HttpChannelParent;
+class nsHttpChannel;
+}
+}
+
+enum class DataURIHandling
+{
+ Allow,
+ Disallow
+};
+
+enum class UpdateType
+{
+ Default,
+ InternalOrHSTSRedirect
+};
+
+class nsCORSListenerProxy final : public nsIStreamListener,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public nsIThreadRetargetableStreamListener
+{
+public:
+ nsCORSListenerProxy(nsIStreamListener* aOuter,
+ nsIPrincipal* aRequestingPrincipal,
+ bool aWithCredentials);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ // Must be called at startup.
+ static void Startup();
+
+ static void Shutdown();
+
+ nsresult Init(nsIChannel* aChannel, DataURIHandling aAllowDataURI);
+
+ void SetInterceptController(nsINetworkInterceptController* aInterceptController);
+
+private:
+ // Only HttpChannelParent can call RemoveFromCorsPreflightCache
+ friend class mozilla::net::HttpChannelParent;
+ // Only nsHttpChannel can invoke CORS preflights
+ friend class mozilla::net::nsHttpChannel;
+
+ static void RemoveFromCorsPreflightCache(nsIURI* aURI,
+ nsIPrincipal* aRequestingPrincipal);
+ static nsresult StartCORSPreflight(nsIChannel* aRequestChannel,
+ nsICorsPreflightCallback* aCallback,
+ nsTArray<nsCString>& aACUnsafeHeaders,
+ nsIChannel** aPreflightChannel);
+
+ ~nsCORSListenerProxy();
+
+ nsresult UpdateChannel(nsIChannel* aChannel, DataURIHandling aAllowDataURI,
+ UpdateType aUpdateType);
+ nsresult CheckRequestApproved(nsIRequest* aRequest);
+ nsresult CheckPreflightNeeded(nsIChannel* aChannel, UpdateType aUpdateType);
+
+ nsCOMPtr<nsIStreamListener> mOuterListener;
+ // The principal that originally kicked off the request
+ nsCOMPtr<nsIPrincipal> mRequestingPrincipal;
+ // The principal to use for our Origin header ("source origin" in spec terms).
+ // This can get changed during redirects, unlike mRequestingPrincipal.
+ nsCOMPtr<nsIPrincipal> mOriginHeaderPrincipal;
+ nsCOMPtr<nsIInterfaceRequestor> mOuterNotificationCallbacks;
+ nsCOMPtr<nsINetworkInterceptController> mInterceptController;
+ bool mWithCredentials;
+ bool mRequestApproved;
+ // Please note that the member variable mHasBeenCrossSite may rely on the
+ // promise that the CSP directive 'upgrade-insecure-requests' upgrades
+ // an http: request to https: in nsHttpChannel::Connect() and hence
+ // a request might not be marked as cross site request based on that promise.
+ bool mHasBeenCrossSite;
+#ifdef DEBUG
+ bool mInited;
+#endif
+};
+
+#endif
diff --git a/netwerk/protocol/http/nsHttp.cpp b/netwerk/protocol/http/nsHttp.cpp
new file mode 100644
index 000000000..faf9e21ff
--- /dev/null
+++ b/netwerk/protocol/http/nsHttp.cpp
@@ -0,0 +1,481 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttp.h"
+#include "PLDHashTable.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/HashFunctions.h"
+#include "nsCRT.h"
+#include <errno.h>
+
+namespace mozilla {
+namespace net {
+
+// define storage for all atoms
+#define HTTP_ATOM(_name, _value) nsHttpAtom nsHttp::_name = { _value };
+#include "nsHttpAtomList.h"
+#undef HTTP_ATOM
+
+// find out how many atoms we have
+#define HTTP_ATOM(_name, _value) Unused_ ## _name,
+enum {
+#include "nsHttpAtomList.h"
+ NUM_HTTP_ATOMS
+};
+#undef HTTP_ATOM
+
+// we keep a linked list of atoms allocated on the heap for easy clean up when
+// the atom table is destroyed. The structure and value string are allocated
+// as one contiguous block.
+
+struct HttpHeapAtom {
+ struct HttpHeapAtom *next;
+ char value[1];
+};
+
+static PLDHashTable *sAtomTable;
+static struct HttpHeapAtom *sHeapAtoms = nullptr;
+static Mutex *sLock = nullptr;
+
+HttpHeapAtom *
+NewHeapAtom(const char *value) {
+ int len = strlen(value);
+
+ HttpHeapAtom *a =
+ reinterpret_cast<HttpHeapAtom *>(malloc(sizeof(*a) + len));
+ if (!a)
+ return nullptr;
+ memcpy(a->value, value, len + 1);
+
+ // add this heap atom to the list of all heap atoms
+ a->next = sHeapAtoms;
+ sHeapAtoms = a;
+
+ return a;
+}
+
+// Hash string ignore case, based on PL_HashString
+static PLDHashNumber
+StringHash(const void *key)
+{
+ PLDHashNumber h = 0;
+ for (const char *s = reinterpret_cast<const char*>(key); *s; ++s)
+ h = AddToHash(h, nsCRT::ToLower(*s));
+ return h;
+}
+
+static bool
+StringCompare(const PLDHashEntryHdr *entry, const void *testKey)
+{
+ const void *entryKey =
+ reinterpret_cast<const PLDHashEntryStub *>(entry)->key;
+
+ return PL_strcasecmp(reinterpret_cast<const char *>(entryKey),
+ reinterpret_cast<const char *>(testKey)) == 0;
+}
+
+static const PLDHashTableOps ops = {
+ StringHash,
+ StringCompare,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr
+};
+
+// We put the atoms in a hash table for speedy lookup.. see ResolveAtom.
+nsresult
+nsHttp::CreateAtomTable()
+{
+ MOZ_ASSERT(!sAtomTable, "atom table already initialized");
+
+ if (!sLock) {
+ sLock = new Mutex("nsHttp.sLock");
+ }
+
+ // The initial length for this table is a value greater than the number of
+ // known atoms (NUM_HTTP_ATOMS) because we expect to encounter a few random
+ // headers right off the bat.
+ sAtomTable = new PLDHashTable(&ops, sizeof(PLDHashEntryStub),
+ NUM_HTTP_ATOMS + 10);
+
+ // fill the table with our known atoms
+ const char *const atoms[] = {
+#define HTTP_ATOM(_name, _value) nsHttp::_name._val,
+#include "nsHttpAtomList.h"
+#undef HTTP_ATOM
+ nullptr
+ };
+
+ for (int i = 0; atoms[i]; ++i) {
+ auto stub = static_cast<PLDHashEntryStub*>
+ (sAtomTable->Add(atoms[i], fallible));
+ if (!stub)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ MOZ_ASSERT(!stub->key, "duplicate static atom");
+ stub->key = atoms[i];
+ }
+
+ return NS_OK;
+}
+
+void
+nsHttp::DestroyAtomTable()
+{
+ delete sAtomTable;
+ sAtomTable = nullptr;
+
+ while (sHeapAtoms) {
+ HttpHeapAtom *next = sHeapAtoms->next;
+ free(sHeapAtoms);
+ sHeapAtoms = next;
+ }
+
+ delete sLock;
+ sLock = nullptr;
+}
+
+Mutex *
+nsHttp::GetLock()
+{
+ return sLock;
+}
+
+// this function may be called from multiple threads
+nsHttpAtom
+nsHttp::ResolveAtom(const char *str)
+{
+ nsHttpAtom atom = { nullptr };
+
+ if (!str || !sAtomTable)
+ return atom;
+
+ MutexAutoLock lock(*sLock);
+
+ auto stub = static_cast<PLDHashEntryStub*>(sAtomTable->Add(str, fallible));
+ if (!stub)
+ return atom; // out of memory
+
+ if (stub->key) {
+ atom._val = reinterpret_cast<const char *>(stub->key);
+ return atom;
+ }
+
+ // if the atom could not be found in the atom table, then we'll go
+ // and allocate a new atom on the heap.
+ HttpHeapAtom *heapAtom = NewHeapAtom(str);
+ if (!heapAtom)
+ return atom; // out of memory
+
+ stub->key = atom._val = heapAtom->value;
+ return atom;
+}
+
+//
+// From section 2.2 of RFC 2616, a token is defined as:
+//
+// token = 1*<any CHAR except CTLs or separators>
+// CHAR = <any US-ASCII character (octets 0 - 127)>
+// separators = "(" | ")" | "<" | ">" | "@"
+// | "," | ";" | ":" | "\" | <">
+// | "/" | "[" | "]" | "?" | "="
+// | "{" | "}" | SP | HT
+// CTL = <any US-ASCII control character
+// (octets 0 - 31) and DEL (127)>
+// SP = <US-ASCII SP, space (32)>
+// HT = <US-ASCII HT, horizontal-tab (9)>
+//
+static const char kValidTokenMap[128] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24
+
+ 0, 1, 0, 1, 1, 1, 1, 1, // 32
+ 0, 0, 1, 1, 0, 1, 1, 0, // 40
+ 1, 1, 1, 1, 1, 1, 1, 1, // 48
+ 1, 1, 0, 0, 0, 0, 0, 0, // 56
+
+ 0, 1, 1, 1, 1, 1, 1, 1, // 64
+ 1, 1, 1, 1, 1, 1, 1, 1, // 72
+ 1, 1, 1, 1, 1, 1, 1, 1, // 80
+ 1, 1, 1, 0, 0, 0, 1, 1, // 88
+
+ 1, 1, 1, 1, 1, 1, 1, 1, // 96
+ 1, 1, 1, 1, 1, 1, 1, 1, // 104
+ 1, 1, 1, 1, 1, 1, 1, 1, // 112
+ 1, 1, 1, 0, 1, 0, 1, 0 // 120
+};
+bool
+nsHttp::IsValidToken(const char *start, const char *end)
+{
+ if (start == end)
+ return false;
+
+ for (; start != end; ++start) {
+ const unsigned char idx = *start;
+ if (idx > 127 || !kValidTokenMap[idx])
+ return false;
+ }
+
+ return true;
+}
+
+const char*
+nsHttp::GetProtocolVersion(uint32_t pv)
+{
+ switch (pv) {
+ case HTTP_VERSION_2:
+ case NS_HTTP_VERSION_2_0:
+ return "h2";
+ case NS_HTTP_VERSION_1_0:
+ return "http/1.0";
+ case NS_HTTP_VERSION_1_1:
+ return "http/1.1";
+ default:
+ NS_WARNING(nsPrintfCString("Unkown protocol version: 0x%X. "
+ "Please file a bug", pv).get());
+ return "http/1.1";
+ }
+}
+
+// static
+bool
+nsHttp::IsReasonableHeaderValue(const nsACString &s)
+{
+ // Header values MUST NOT contain line-breaks. RFC 2616 technically
+ // permits CTL characters, including CR and LF, in header values provided
+ // they are quoted. However, this can lead to problems if servers do not
+ // interpret quoted strings properly. Disallowing CR and LF here seems
+ // reasonable and keeps things simple. We also disallow a null byte.
+ const nsACString::char_type* end = s.EndReading();
+ for (const nsACString::char_type* i = s.BeginReading(); i != end; ++i) {
+ if (*i == '\r' || *i == '\n' || *i == '\0') {
+ return false;
+ }
+ }
+ return true;
+}
+
+const char *
+nsHttp::FindToken(const char *input, const char *token, const char *seps)
+{
+ if (!input)
+ return nullptr;
+
+ int inputLen = strlen(input);
+ int tokenLen = strlen(token);
+
+ if (inputLen < tokenLen)
+ return nullptr;
+
+ const char *inputTop = input;
+ const char *inputEnd = input + inputLen - tokenLen;
+ for (; input <= inputEnd; ++input) {
+ if (PL_strncasecmp(input, token, tokenLen) == 0) {
+ if (input > inputTop && !strchr(seps, *(input - 1)))
+ continue;
+ if (input < inputEnd && !strchr(seps, *(input + tokenLen)))
+ continue;
+ return input;
+ }
+ }
+
+ return nullptr;
+}
+
+bool
+nsHttp::ParseInt64(const char *input, const char **next, int64_t *r)
+{
+ MOZ_ASSERT(input);
+ MOZ_ASSERT(r);
+
+ char *end = nullptr;
+ errno = 0; // Clear errno to make sure its value is set by strtoll
+ int64_t value = strtoll(input, &end, /* base */ 10);
+
+ // Fail if: - the parsed number overflows.
+ // - the end points to the start of the input string.
+ // - we parsed a negative value. Consumers don't expect that.
+ if (errno != 0 || end == input || value < 0) {
+ LOG(("nsHttp::ParseInt64 value=%ld errno=%d", value, errno));
+ return false;
+ }
+
+ if (next) {
+ *next = end;
+ }
+ *r = value;
+ return true;
+}
+
+bool
+nsHttp::IsPermanentRedirect(uint32_t httpStatus)
+{
+ return httpStatus == 301 || httpStatus == 308;
+}
+
+
+template<typename T> void
+localEnsureBuffer(UniquePtr<T[]> &buf, uint32_t newSize,
+ uint32_t preserve, uint32_t &objSize)
+{
+ if (objSize >= newSize)
+ return;
+
+ // Leave a little slop on the new allocation - add 2KB to
+ // what we need and then round the result up to a 4KB (page)
+ // boundary.
+
+ objSize = (newSize + 2048 + 4095) & ~4095;
+
+ static_assert(sizeof(T) == 1, "sizeof(T) must be 1");
+ auto tmp = MakeUnique<T[]>(objSize);
+ if (preserve) {
+ memcpy(tmp.get(), buf.get(), preserve);
+ }
+ buf = Move(tmp);
+}
+
+void EnsureBuffer(UniquePtr<char[]> &buf, uint32_t newSize,
+ uint32_t preserve, uint32_t &objSize)
+{
+ localEnsureBuffer<char> (buf, newSize, preserve, objSize);
+}
+
+void EnsureBuffer(UniquePtr<uint8_t[]> &buf, uint32_t newSize,
+ uint32_t preserve, uint32_t &objSize)
+{
+ localEnsureBuffer<uint8_t> (buf, newSize, preserve, objSize);
+}
+///
+
+void
+ParsedHeaderValueList::Tokenize(char *input, uint32_t inputLen, char **token,
+ uint32_t *tokenLen, bool *foundEquals, char **next)
+{
+ if (foundEquals) {
+ *foundEquals = false;
+ }
+ if (next) {
+ *next = nullptr;
+ }
+ if (inputLen < 1 || !input || !token) {
+ return;
+ }
+
+ bool foundFirst = false;
+ bool inQuote = false;
+ bool foundToken = false;
+ *token = input;
+ *tokenLen = inputLen;
+
+ for (uint32_t index = 0; !foundToken && index < inputLen; ++index) {
+ // strip leading cruft
+ if (!foundFirst &&
+ (input[index] == ' ' || input[index] == '"' || input[index] == '\t')) {
+ (*token)++;
+ } else {
+ foundFirst = true;
+ }
+
+ if (input[index] == '"') {
+ inQuote = !inQuote;
+ continue;
+ }
+
+ if (inQuote) {
+ continue;
+ }
+
+ if (input[index] == '=' || input[index] == ';') {
+ *tokenLen = (input + index) - *token;
+ if (next && ((index + 1) < inputLen)) {
+ *next = input + index + 1;
+ }
+ foundToken = true;
+ if (foundEquals && input[index] == '=') {
+ *foundEquals = true;
+ }
+ break;
+ }
+ }
+
+ if (!foundToken) {
+ *tokenLen = (input + inputLen) - *token;
+ }
+
+ // strip trailing cruft
+ for (char *index = *token + *tokenLen - 1; index >= *token; --index) {
+ if (*index != ' ' && *index != '\t' && *index != '"') {
+ break;
+ }
+ --(*tokenLen);
+ if (*index == '"') {
+ break;
+ }
+ }
+}
+
+ParsedHeaderValueList::ParsedHeaderValueList(char *t, uint32_t len)
+{
+ char *name = nullptr;
+ uint32_t nameLen = 0;
+ char *value = nullptr;
+ uint32_t valueLen = 0;
+ char *next = nullptr;
+ bool foundEquals;
+
+ while (t) {
+ Tokenize(t, len, &name, &nameLen, &foundEquals, &next);
+ if (next) {
+ len -= next - t;
+ }
+ t = next;
+ if (foundEquals && t) {
+ Tokenize(t, len, &value, &valueLen, nullptr, &next);
+ if (next) {
+ len -= next - t;
+ }
+ t = next;
+ }
+ mValues.AppendElement(ParsedHeaderPair(name, nameLen, value, valueLen));
+ value = name = nullptr;
+ valueLen = nameLen = 0;
+ next = nullptr;
+ }
+}
+
+ParsedHeaderValueListList::ParsedHeaderValueListList(const nsCString &fullHeader)
+ : mFull(fullHeader)
+{
+ char *t = mFull.BeginWriting();
+ uint32_t len = mFull.Length();
+ char *last = t;
+ bool inQuote = false;
+ for (uint32_t index = 0; index < len; ++index) {
+ if (t[index] == '"') {
+ inQuote = !inQuote;
+ continue;
+ }
+ if (inQuote) {
+ continue;
+ }
+ if (t[index] == ',') {
+ mValues.AppendElement(ParsedHeaderValueList(last, (t + index) - last));
+ last = t + index + 1;
+ }
+ }
+ if (!inQuote) {
+ mValues.AppendElement(ParsedHeaderValueList(last, (t + len) - last));
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h
new file mode 100644
index 000000000..a20637808
--- /dev/null
+++ b/netwerk/protocol/http/nsHttp.h
@@ -0,0 +1,276 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttp_h__
+#define nsHttp_h__
+
+#include <stdint.h>
+#include "prtime.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "nsError.h"
+#include "nsTArray.h"
+#include "mozilla/UniquePtr.h"
+
+// http version codes
+#define NS_HTTP_VERSION_UNKNOWN 0
+#define NS_HTTP_VERSION_0_9 9
+#define NS_HTTP_VERSION_1_0 10
+#define NS_HTTP_VERSION_1_1 11
+#define NS_HTTP_VERSION_2_0 20
+
+namespace mozilla {
+
+class Mutex;
+
+namespace net {
+ enum {
+ // SPDY_VERSION_2 = 2, REMOVED
+ // SPDY_VERSION_3 = 3, REMOVED
+ // SPDY_VERSION_31 = 4, REMOVED
+ HTTP_VERSION_2 = 5
+
+ // leave room for official versions. telem goes to 48
+ // 24 was a internal spdy/3.1
+ // 25 was spdy/4a2
+ // 26 was http/2-draft08 and http/2-draft07 (they were the same)
+ // 27 was http/2-draft09, h2-10, and h2-11
+ // 28 was http/2-draft12
+ // 29 was http/2-draft13
+ // 30 was h2-14 and h2-15
+ // 31 was h2-16
+ };
+
+typedef uint8_t nsHttpVersion;
+
+//-----------------------------------------------------------------------------
+// http connection capabilities
+//-----------------------------------------------------------------------------
+
+#define NS_HTTP_ALLOW_KEEPALIVE (1<<0)
+#define NS_HTTP_ALLOW_PIPELINING (1<<1)
+
+// a transaction with this caps flag will continue to own the connection,
+// preventing it from being reclaimed, even after the transaction completes.
+#define NS_HTTP_STICKY_CONNECTION (1<<2)
+
+// a transaction with this caps flag will, upon opening a new connection,
+// bypass the local DNS cache
+#define NS_HTTP_REFRESH_DNS (1<<3)
+
+// a transaction with this caps flag will not pass SSL client-certificates
+// to the server (see bug #466080), but is may also be used for other things
+#define NS_HTTP_LOAD_ANONYMOUS (1<<4)
+
+// a transaction with this caps flag keeps timing information
+#define NS_HTTP_TIMING_ENABLED (1<<5)
+
+// a transaction with this flag blocks the initiation of other transactons
+// in the same load group until it is complete
+#define NS_HTTP_LOAD_AS_BLOCKING (1<<6)
+
+// Disallow the use of the SPDY protocol. This is meant for the contexts
+// such as HTTP upgrade which are nonsensical for SPDY, it is not the
+// SPDY configuration variable.
+#define NS_HTTP_DISALLOW_SPDY (1<<7)
+
+// a transaction with this flag loads without respect to whether the load
+// group is currently blocking on some resources
+#define NS_HTTP_LOAD_UNBLOCKED (1<<8)
+
+// This flag indicates the transaction should accept associated pushes
+#define NS_HTTP_ONPUSH_LISTENER (1<<9)
+
+// Transactions with this flag should react to errors without side effects
+// First user is to prevent clearing of alt-svc cache on failed probe
+#define NS_HTTP_ERROR_SOFTLY (1<<10)
+
+// This corresponds to nsIHttpChannelInternal.beConservative
+// it disables any cutting edge features that we are worried might result in
+// interop problems with critical infrastructure
+#define NS_HTTP_BE_CONSERVATIVE (1<<11)
+
+// (1<<12) is used for NS_HTTP_URGENT_START on a newer branch
+
+// A sticky connection of the transaction is explicitly allowed to be restarted
+// on ERROR_NET_RESET.
+#define NS_HTTP_CONNECTION_RESTARTABLE (1<<13)
+
+//-----------------------------------------------------------------------------
+// some default values
+//-----------------------------------------------------------------------------
+
+#define NS_HTTP_DEFAULT_PORT 80
+#define NS_HTTPS_DEFAULT_PORT 443
+
+#define NS_HTTP_HEADER_SEPS ", \t"
+
+//-----------------------------------------------------------------------------
+// http atoms...
+//-----------------------------------------------------------------------------
+
+struct nsHttpAtom
+{
+ operator const char *() const { return _val; }
+ const char *get() const { return _val; }
+
+ void operator=(const char *v) { _val = v; }
+ void operator=(const nsHttpAtom &a) { _val = a._val; }
+
+ // private
+ const char *_val;
+};
+
+struct nsHttp
+{
+ static nsresult CreateAtomTable();
+ static void DestroyAtomTable();
+
+ // The mutex is valid any time the Atom Table is valid
+ // This mutex is used in the unusual case that the network thread and
+ // main thread might access the same data
+ static Mutex *GetLock();
+
+ // will dynamically add atoms to the table if they don't already exist
+ static nsHttpAtom ResolveAtom(const char *);
+ static nsHttpAtom ResolveAtom(const nsACString &s)
+ {
+ return ResolveAtom(PromiseFlatCString(s).get());
+ }
+
+ // returns true if the specified token [start,end) is valid per RFC 2616
+ // section 2.2
+ static bool IsValidToken(const char *start, const char *end);
+
+ static inline bool IsValidToken(const nsACString &s) {
+ return IsValidToken(s.BeginReading(), s.EndReading());
+ }
+
+ // Returns true if the specified value is reasonable given the defintion
+ // in RFC 2616 section 4.2. Full strict validation is not performed
+ // currently as it would require full parsing of the value.
+ static bool IsReasonableHeaderValue(const nsACString &s);
+
+ // find the first instance (case-insensitive comparison) of the given
+ // |token| in the |input| string. the |token| is bounded by elements of
+ // |separators| and may appear at the beginning or end of the |input|
+ // string. null is returned if the |token| is not found. |input| may be
+ // null, in which case null is returned.
+ static const char *FindToken(const char *input, const char *token,
+ const char *separators);
+
+ // This function parses a string containing a decimal-valued, non-negative
+ // 64-bit integer. If the value would exceed INT64_MAX, then false is
+ // returned. Otherwise, this function returns true and stores the
+ // parsed value in |result|. The next unparsed character in |input| is
+ // optionally returned via |next| if |next| is non-null.
+ //
+ // TODO(darin): Replace this with something generic.
+ //
+ static bool ParseInt64(const char *input, const char **next,
+ int64_t *result);
+
+ // Variant on ParseInt64 that expects the input string to contain nothing
+ // more than the value being parsed.
+ static inline bool ParseInt64(const char *input, int64_t *result) {
+ const char *next;
+ return ParseInt64(input, &next, result) && *next == '\0';
+ }
+
+ // Return whether the HTTP status code represents a permanent redirect
+ static bool IsPermanentRedirect(uint32_t httpStatus);
+
+ // Returns the APLN token which represents the used protocol version.
+ static const char* GetProtocolVersion(uint32_t pv);
+
+ // Declare all atoms
+ //
+ // The atom names and values are stored in nsHttpAtomList.h and are brought
+ // to you by the magic of C preprocessing. Add new atoms to nsHttpAtomList
+ // and all support logic will be auto-generated.
+ //
+#define HTTP_ATOM(_name, _value) static nsHttpAtom _name;
+#include "nsHttpAtomList.h"
+#undef HTTP_ATOM
+};
+
+//-----------------------------------------------------------------------------
+// utilities...
+//-----------------------------------------------------------------------------
+
+static inline uint32_t
+PRTimeToSeconds(PRTime t_usec)
+{
+ return uint32_t( t_usec / PR_USEC_PER_SEC );
+}
+
+#define NowInSeconds() PRTimeToSeconds(PR_Now())
+
+// Round q-value to 2 decimal places; return 2 most significant digits as uint.
+#define QVAL_TO_UINT(q) ((unsigned int) ((q + 0.005) * 100.0))
+
+#define HTTP_LWS " \t"
+#define HTTP_HEADER_VALUE_SEPS HTTP_LWS ","
+
+void EnsureBuffer(UniquePtr<char[]> &buf, uint32_t newSize,
+ uint32_t preserve, uint32_t &objSize);
+void EnsureBuffer(UniquePtr<uint8_t[]> &buf, uint32_t newSize,
+ uint32_t preserve, uint32_t &objSize);
+
+// h2=":443"; ma=60; single
+// results in 3 mValues = {{h2, :443}, {ma, 60}, {single}}
+
+class ParsedHeaderPair
+{
+public:
+ ParsedHeaderPair(const char *name, int32_t nameLen,
+ const char *val, int32_t valLen)
+ {
+ if (nameLen > 0) {
+ mName.Rebind(name, name + nameLen);
+ }
+ if (valLen > 0) {
+ mValue.Rebind(val, val + valLen);
+ }
+ }
+
+ ParsedHeaderPair(ParsedHeaderPair const &copy)
+ : mName(copy.mName)
+ , mValue(copy.mValue)
+ {
+ }
+
+ nsDependentCSubstring mName;
+ nsDependentCSubstring mValue;
+};
+
+class ParsedHeaderValueList
+{
+public:
+ ParsedHeaderValueList(char *t, uint32_t len);
+ nsTArray<ParsedHeaderPair> mValues;
+
+private:
+ void ParsePair(char *t, uint32_t len);
+ void Tokenize(char *input, uint32_t inputLen, char **token,
+ uint32_t *tokenLen, bool *foundEquals, char **next);
+};
+
+class ParsedHeaderValueListList
+{
+public:
+ explicit ParsedHeaderValueListList(const nsCString &txt);
+ nsTArray<ParsedHeaderValueList> mValues;
+
+private:
+ nsCString mFull;
+};
+
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttp_h__
diff --git a/netwerk/protocol/http/nsHttpActivityDistributor.cpp b/netwerk/protocol/http/nsHttpActivityDistributor.cpp
new file mode 100644
index 000000000..4b332a53d
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpActivityDistributor.cpp
@@ -0,0 +1,135 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpActivityDistributor.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+typedef nsMainThreadPtrHolder<nsIHttpActivityObserver> ObserverHolder;
+typedef nsMainThreadPtrHandle<nsIHttpActivityObserver> ObserverHandle;
+typedef nsTArray<ObserverHandle> ObserverArray;
+
+class nsHttpActivityEvent : public Runnable
+{
+public:
+ nsHttpActivityEvent(nsISupports *aHttpChannel,
+ uint32_t aActivityType,
+ uint32_t aActivitySubtype,
+ PRTime aTimestamp,
+ uint64_t aExtraSizeData,
+ const nsACString & aExtraStringData,
+ ObserverArray *aObservers)
+ : mHttpChannel(aHttpChannel)
+ , mActivityType(aActivityType)
+ , mActivitySubtype(aActivitySubtype)
+ , mTimestamp(aTimestamp)
+ , mExtraSizeData(aExtraSizeData)
+ , mExtraStringData(aExtraStringData)
+ , mObservers(*aObservers)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ for (size_t i = 0 ; i < mObservers.Length() ; i++)
+ mObservers[i]->ObserveActivity(mHttpChannel, mActivityType,
+ mActivitySubtype, mTimestamp,
+ mExtraSizeData, mExtraStringData);
+ return NS_OK;
+ }
+
+private:
+ virtual ~nsHttpActivityEvent()
+ {
+ }
+
+ nsCOMPtr<nsISupports> mHttpChannel;
+ uint32_t mActivityType;
+ uint32_t mActivitySubtype;
+ PRTime mTimestamp;
+ uint64_t mExtraSizeData;
+ nsCString mExtraStringData;
+
+ ObserverArray mObservers;
+};
+
+NS_IMPL_ISUPPORTS(nsHttpActivityDistributor,
+ nsIHttpActivityDistributor,
+ nsIHttpActivityObserver)
+
+nsHttpActivityDistributor::nsHttpActivityDistributor()
+ : mLock("nsHttpActivityDistributor.mLock")
+{
+}
+
+nsHttpActivityDistributor::~nsHttpActivityDistributor()
+{
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::ObserveActivity(nsISupports *aHttpChannel,
+ uint32_t aActivityType,
+ uint32_t aActivitySubtype,
+ PRTime aTimestamp,
+ uint64_t aExtraSizeData,
+ const nsACString & aExtraStringData)
+{
+ nsCOMPtr<nsIRunnable> event;
+ {
+ MutexAutoLock lock(mLock);
+
+ if (!mObservers.Length())
+ return NS_OK;
+
+ event = new nsHttpActivityEvent(aHttpChannel, aActivityType,
+ aActivitySubtype, aTimestamp,
+ aExtraSizeData, aExtraStringData,
+ &mObservers);
+ }
+ NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
+ return NS_DispatchToMainThread(event);
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::GetIsActive(bool *isActive)
+{
+ NS_ENSURE_ARG_POINTER(isActive);
+ MutexAutoLock lock(mLock);
+ *isActive = !!mObservers.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::AddObserver(nsIHttpActivityObserver *aObserver)
+{
+ MutexAutoLock lock(mLock);
+
+ ObserverHandle observer(new ObserverHolder(aObserver));
+ if (!mObservers.AppendElement(observer))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::RemoveObserver(nsIHttpActivityObserver *aObserver)
+{
+ MutexAutoLock lock(mLock);
+
+ ObserverHandle observer(new ObserverHolder(aObserver));
+ if (!mObservers.RemoveElement(observer))
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpActivityDistributor.h b/netwerk/protocol/http/nsHttpActivityDistributor.h
new file mode 100644
index 000000000..4da59de7a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpActivityDistributor.h
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpActivityDistributor_h__
+#define nsHttpActivityDistributor_h__
+
+#include "nsIHttpActivityObserver.h"
+#include "nsTArray.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla { namespace net {
+
+class nsHttpActivityDistributor : public nsIHttpActivityDistributor
+{
+public:
+ typedef nsTArray<nsMainThreadPtrHandle<nsIHttpActivityObserver> > ObserverArray;
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIHTTPACTIVITYOBSERVER
+ NS_DECL_NSIHTTPACTIVITYDISTRIBUTOR
+
+ nsHttpActivityDistributor();
+
+protected:
+ virtual ~nsHttpActivityDistributor();
+
+ ObserverArray mObservers;
+ Mutex mLock;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpActivityDistributor_h__
diff --git a/netwerk/protocol/http/nsHttpAtomList.h b/netwerk/protocol/http/nsHttpAtomList.h
new file mode 100644
index 000000000..5db985613
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAtomList.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/******
+ This file contains the list of all HTTP atoms
+ See nsHttp.h for access to the atoms.
+
+ It is designed to be used as inline input to nsHttp.cpp *only*
+ through the magic of C preprocessing.
+
+ All entries must be enclosed in the macro HTTP_ATOM which will have cruel
+ and unusual things done to it.
+
+ The first argument to HTTP_ATOM is the C++ name of the atom.
+ The second argument to HTTP_ATOM is the string value of the atom.
+ ******/
+
+HTTP_ATOM(Accept, "Accept")
+HTTP_ATOM(Accept_Encoding, "Accept-Encoding")
+HTTP_ATOM(Accept_Language, "Accept-Language")
+HTTP_ATOM(Accept_Ranges, "Accept-Ranges")
+HTTP_ATOM(Access_Control_Allow_Origin,"Access-Control-Allow-Origin")
+HTTP_ATOM(Age, "Age")
+HTTP_ATOM(Allow, "Allow")
+HTTP_ATOM(Alternate_Service, "Alt-Svc")
+HTTP_ATOM(Alternate_Service_Used, "Alt-Used")
+HTTP_ATOM(Alternate_Protocol, "Alternate-Protocol")
+HTTP_ATOM(Assoc_Req, "Assoc-Req")
+HTTP_ATOM(Authentication, "Authentication")
+HTTP_ATOM(Authorization, "Authorization")
+HTTP_ATOM(Cache_Control, "Cache-Control")
+HTTP_ATOM(Connection, "Connection")
+HTTP_ATOM(Content_Disposition, "Content-Disposition")
+HTTP_ATOM(Content_Encoding, "Content-Encoding")
+HTTP_ATOM(Content_Language, "Content-Language")
+HTTP_ATOM(Content_Length, "Content-Length")
+HTTP_ATOM(Content_Location, "Content-Location")
+HTTP_ATOM(Content_MD5, "Content-MD5")
+HTTP_ATOM(Content_Range, "Content-Range")
+HTTP_ATOM(Content_Type, "Content-Type")
+HTTP_ATOM(Cookie, "Cookie")
+HTTP_ATOM(Date, "Date")
+HTTP_ATOM(DAV, "DAV")
+HTTP_ATOM(Depth, "Depth")
+HTTP_ATOM(Destination, "Destination")
+HTTP_ATOM(DoNotTrack, "DNT")
+HTTP_ATOM(ETag, "Etag")
+HTTP_ATOM(Expect, "Expect")
+HTTP_ATOM(Expires, "Expires")
+HTTP_ATOM(From, "From")
+HTTP_ATOM(Host, "Host")
+HTTP_ATOM(If, "If")
+HTTP_ATOM(If_Match, "If-Match")
+HTTP_ATOM(If_Modified_Since, "If-Modified-Since")
+HTTP_ATOM(If_None_Match, "If-None-Match")
+HTTP_ATOM(If_None_Match_Any, "If-None-Match-Any")
+HTTP_ATOM(If_Range, "If-Range")
+HTTP_ATOM(If_Unmodified_Since, "If-Unmodified-Since")
+HTTP_ATOM(Keep_Alive, "Keep-Alive")
+HTTP_ATOM(Last_Modified, "Last-Modified")
+HTTP_ATOM(Lock_Token, "Lock-Token")
+HTTP_ATOM(Link, "Link")
+HTTP_ATOM(Location, "Location")
+HTTP_ATOM(Max_Forwards, "Max-Forwards")
+HTTP_ATOM(Overwrite, "Overwrite")
+HTTP_ATOM(Pragma, "Pragma")
+HTTP_ATOM(Prefer, "Prefer")
+HTTP_ATOM(Proxy_Authenticate, "Proxy-Authenticate")
+HTTP_ATOM(Proxy_Authorization, "Proxy-Authorization")
+HTTP_ATOM(Proxy_Connection, "Proxy-Connection")
+HTTP_ATOM(Range, "Range")
+HTTP_ATOM(Referer, "Referer")
+HTTP_ATOM(Retry_After, "Retry-After")
+HTTP_ATOM(Server, "Server")
+HTTP_ATOM(Service_Worker_Allowed, "Service-Worker-Allowed")
+HTTP_ATOM(Set_Cookie, "Set-Cookie")
+HTTP_ATOM(Set_Cookie2, "Set-Cookie2")
+HTTP_ATOM(Status_URI, "Status-URI")
+HTTP_ATOM(TE, "TE")
+HTTP_ATOM(Title, "Title")
+HTTP_ATOM(Timeout, "Timeout")
+HTTP_ATOM(Trailer, "Trailer")
+HTTP_ATOM(Transfer_Encoding, "Transfer-Encoding")
+HTTP_ATOM(URI, "URI")
+HTTP_ATOM(Upgrade, "Upgrade")
+HTTP_ATOM(User_Agent, "User-Agent")
+HTTP_ATOM(Vary, "Vary")
+HTTP_ATOM(Version, "Version")
+HTTP_ATOM(WWW_Authenticate, "WWW-Authenticate")
+HTTP_ATOM(Warning, "Warning")
+HTTP_ATOM(X_Content_Type_Options, "X-Content-Type-Options")
+HTTP_ATOM(X_Firefox_Spdy, "X-Firefox-Spdy")
+HTTP_ATOM(X_Firefox_Spdy_Proxy, "X-Firefox-Spdy-Proxy")
+
+// methods are case sensitive and do not use atom table
diff --git a/netwerk/protocol/http/nsHttpAuthCache.cpp b/netwerk/protocol/http/nsHttpAuthCache.cpp
new file mode 100644
index 000000000..be5cd17a7
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthCache.cpp
@@ -0,0 +1,607 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpAuthCache.h"
+
+#include <stdlib.h>
+
+#include "mozilla/Attributes.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "mozIApplicationClearPrivateDataParams.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/DebugOnly.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+static inline void
+GetAuthKey(const char *scheme, const char *host, int32_t port, nsACString const &originSuffix, nsCString &key)
+{
+ key.Truncate();
+ key.Append(originSuffix);
+ key.Append(':');
+ key.Append(scheme);
+ key.AppendLiteral("://");
+ key.Append(host);
+ key.Append(':');
+ key.AppendInt(port);
+}
+
+// return true if the two strings are equal or both empty. an empty string
+// is either null or zero length.
+static bool
+StrEquivalent(const char16_t *a, const char16_t *b)
+{
+ static const char16_t emptyStr[] = {0};
+
+ if (!a)
+ a = emptyStr;
+ if (!b)
+ b = emptyStr;
+
+ return nsCRT::strcmp(a, b) == 0;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthCache <public>
+//-----------------------------------------------------------------------------
+
+nsHttpAuthCache::nsHttpAuthCache()
+ : mDB(nullptr)
+ , mObserver(new OriginClearObserver(this))
+{
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->AddObserver(mObserver, "clear-origin-attributes-data", false);
+ }
+}
+
+nsHttpAuthCache::~nsHttpAuthCache()
+{
+ if (mDB)
+ ClearAll();
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(mObserver, "clear-origin-attributes-data");
+ mObserver->mOwner = nullptr;
+ }
+}
+
+nsresult
+nsHttpAuthCache::Init()
+{
+ NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED);
+
+ LOG(("nsHttpAuthCache::Init\n"));
+
+ mDB = PL_NewHashTable(128, (PLHashFunction) PL_HashString,
+ (PLHashComparator) PL_CompareStrings,
+ (PLHashComparator) 0, &gHashAllocOps, this);
+ if (!mDB)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpAuthCache::GetAuthEntryForPath(const char *scheme,
+ const char *host,
+ int32_t port,
+ const char *path,
+ nsACString const &originSuffix,
+ nsHttpAuthEntry **entry)
+{
+ LOG(("nsHttpAuthCache::GetAuthEntryForPath [key=%s://%s:%d path=%s]\n",
+ scheme, host, port, path));
+
+ nsAutoCString key;
+ nsHttpAuthNode *node = LookupAuthNode(scheme, host, port, originSuffix, key);
+ if (!node)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *entry = node->LookupEntryByPath(path);
+ return *entry ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult
+nsHttpAuthCache::GetAuthEntryForDomain(const char *scheme,
+ const char *host,
+ int32_t port,
+ const char *realm,
+ nsACString const &originSuffix,
+ nsHttpAuthEntry **entry)
+
+{
+ LOG(("nsHttpAuthCache::GetAuthEntryForDomain [key=%s://%s:%d realm=%s]\n",
+ scheme, host, port, realm));
+
+ nsAutoCString key;
+ nsHttpAuthNode *node = LookupAuthNode(scheme, host, port, originSuffix, key);
+ if (!node)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *entry = node->LookupEntryByRealm(realm);
+ return *entry ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult
+nsHttpAuthCache::SetAuthEntry(const char *scheme,
+ const char *host,
+ int32_t port,
+ const char *path,
+ const char *realm,
+ const char *creds,
+ const char *challenge,
+ nsACString const &originSuffix,
+ const nsHttpAuthIdentity *ident,
+ nsISupports *metadata)
+{
+ nsresult rv;
+
+ LOG(("nsHttpAuthCache::SetAuthEntry [key=%s://%s:%d realm=%s path=%s metadata=%x]\n",
+ scheme, host, port, realm, path, metadata));
+
+ if (!mDB) {
+ rv = Init();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsAutoCString key;
+ nsHttpAuthNode *node = LookupAuthNode(scheme, host, port, originSuffix, key);
+
+ if (!node) {
+ // create a new entry node and set the given entry
+ node = new nsHttpAuthNode();
+ if (!node)
+ return NS_ERROR_OUT_OF_MEMORY;
+ rv = node->SetAuthEntry(path, realm, creds, challenge, ident, metadata);
+ if (NS_FAILED(rv))
+ delete node;
+ else
+ PL_HashTableAdd(mDB, strdup(key.get()), node);
+ return rv;
+ }
+
+ return node->SetAuthEntry(path, realm, creds, challenge, ident, metadata);
+}
+
+void
+nsHttpAuthCache::ClearAuthEntry(const char *scheme,
+ const char *host,
+ int32_t port,
+ const char *realm,
+ nsACString const &originSuffix)
+{
+ if (!mDB)
+ return;
+
+ nsAutoCString key;
+ GetAuthKey(scheme, host, port, originSuffix, key);
+ PL_HashTableRemove(mDB, key.get());
+}
+
+nsresult
+nsHttpAuthCache::ClearAll()
+{
+ LOG(("nsHttpAuthCache::ClearAll\n"));
+
+ if (mDB) {
+ PL_HashTableDestroy(mDB);
+ mDB = 0;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthCache <private>
+//-----------------------------------------------------------------------------
+
+nsHttpAuthNode *
+nsHttpAuthCache::LookupAuthNode(const char *scheme,
+ const char *host,
+ int32_t port,
+ nsACString const &originSuffix,
+ nsCString &key)
+{
+ if (!mDB)
+ return nullptr;
+
+ GetAuthKey(scheme, host, port, originSuffix, key);
+
+ return (nsHttpAuthNode *) PL_HashTableLookup(mDB, key.get());
+}
+
+void *
+nsHttpAuthCache::AllocTable(void *self, size_t size)
+{
+ return malloc(size);
+}
+
+void
+nsHttpAuthCache::FreeTable(void *self, void *item)
+{
+ free(item);
+}
+
+PLHashEntry *
+nsHttpAuthCache::AllocEntry(void *self, const void *key)
+{
+ return (PLHashEntry *) malloc(sizeof(PLHashEntry));
+}
+
+void
+nsHttpAuthCache::FreeEntry(void *self, PLHashEntry *he, unsigned flag)
+{
+ if (flag == HT_FREE_VALUE) {
+ // this would only happen if PL_HashTableAdd were to replace an
+ // existing entry in the hash table, but we _always_ do a lookup
+ // before adding a new entry to avoid this case.
+ NS_NOTREACHED("should never happen");
+ }
+ else if (flag == HT_FREE_ENTRY) {
+ // three wonderful flavors of freeing memory ;-)
+ delete (nsHttpAuthNode *) he->value;
+ free((char *) he->key);
+ free(he);
+ }
+}
+
+PLHashAllocOps nsHttpAuthCache::gHashAllocOps =
+{
+ nsHttpAuthCache::AllocTable,
+ nsHttpAuthCache::FreeTable,
+ nsHttpAuthCache::AllocEntry,
+ nsHttpAuthCache::FreeEntry
+};
+
+NS_IMPL_ISUPPORTS(nsHttpAuthCache::OriginClearObserver, nsIObserver)
+
+NS_IMETHODIMP
+nsHttpAuthCache::OriginClearObserver::Observe(nsISupports *subject,
+ const char * topic,
+ const char16_t * data_unicode)
+{
+ NS_ENSURE_TRUE(mOwner, NS_ERROR_NOT_AVAILABLE);
+
+ OriginAttributesPattern pattern;
+ if (!pattern.Init(nsDependentString(data_unicode))) {
+ NS_ERROR("Cannot parse origin attributes pattern");
+ return NS_ERROR_FAILURE;
+ }
+
+ mOwner->ClearOriginData(pattern);
+ return NS_OK;
+}
+
+static int
+RemoveEntriesForPattern(PLHashEntry *entry, int32_t number, void *arg)
+{
+ nsDependentCString key(static_cast<const char*>(entry->key));
+
+ // Extract the origin attributes suffix from the key.
+ int32_t colon = key.Find(NS_LITERAL_CSTRING(":"));
+ MOZ_ASSERT(colon != kNotFound);
+ nsDependentCSubstring oaSuffix;
+ oaSuffix.Rebind(key.BeginReading(), colon);
+
+ // Build the NeckoOriginAttributes object of it...
+ NeckoOriginAttributes oa;
+ DebugOnly<bool> rv = oa.PopulateFromSuffix(oaSuffix);
+ MOZ_ASSERT(rv);
+
+ // ...and match it against the given pattern.
+ OriginAttributesPattern const *pattern = static_cast<OriginAttributesPattern const*>(arg);
+ if (pattern->Matches(oa)) {
+ return HT_ENUMERATE_NEXT | HT_ENUMERATE_REMOVE;
+ }
+ return HT_ENUMERATE_NEXT;
+}
+
+void
+nsHttpAuthCache::ClearOriginData(OriginAttributesPattern const &pattern)
+{
+ if (!mDB) {
+ return;
+ }
+ PL_HashTableEnumerateEntries(mDB, RemoveEntriesForPattern, (void*)&pattern);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthIdentity
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpAuthIdentity::Set(const char16_t *domain,
+ const char16_t *user,
+ const char16_t *pass)
+{
+ char16_t *newUser, *newPass, *newDomain;
+
+ int domainLen = domain ? NS_strlen(domain) : 0;
+ int userLen = user ? NS_strlen(user) : 0;
+ int passLen = pass ? NS_strlen(pass) : 0;
+
+ int len = userLen + 1 + passLen + 1 + domainLen + 1;
+ newUser = (char16_t *) malloc(len * sizeof(char16_t));
+ if (!newUser)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (user)
+ memcpy(newUser, user, userLen * sizeof(char16_t));
+ newUser[userLen] = 0;
+
+ newPass = &newUser[userLen + 1];
+ if (pass)
+ memcpy(newPass, pass, passLen * sizeof(char16_t));
+ newPass[passLen] = 0;
+
+ newDomain = &newPass[passLen + 1];
+ if (domain)
+ memcpy(newDomain, domain, domainLen * sizeof(char16_t));
+ newDomain[domainLen] = 0;
+
+ // wait until the end to clear member vars in case input params
+ // reference our members!
+ if (mUser)
+ free(mUser);
+ mUser = newUser;
+ mPass = newPass;
+ mDomain = newDomain;
+ return NS_OK;
+}
+
+void
+nsHttpAuthIdentity::Clear()
+{
+ if (mUser) {
+ free(mUser);
+ mUser = nullptr;
+ mPass = nullptr;
+ mDomain = nullptr;
+ }
+}
+
+bool
+nsHttpAuthIdentity::Equals(const nsHttpAuthIdentity &ident) const
+{
+ // we could probably optimize this with a single loop, but why bother?
+ return StrEquivalent(mUser, ident.mUser) &&
+ StrEquivalent(mPass, ident.mPass) &&
+ StrEquivalent(mDomain, ident.mDomain);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthEntry
+//-----------------------------------------------------------------------------
+
+nsHttpAuthEntry::~nsHttpAuthEntry()
+{
+ if (mRealm)
+ free(mRealm);
+
+ while (mRoot) {
+ nsHttpAuthPath *ap = mRoot;
+ mRoot = mRoot->mNext;
+ free(ap);
+ }
+}
+
+nsresult
+nsHttpAuthEntry::AddPath(const char *aPath)
+{
+ // null path matches empty path
+ if (!aPath)
+ aPath = "";
+
+ nsHttpAuthPath *tempPtr = mRoot;
+ while (tempPtr) {
+ const char *curpath = tempPtr->mPath;
+ if (strncmp(aPath, curpath, strlen(curpath)) == 0)
+ return NS_OK; // subpath already exists in the list
+
+ tempPtr = tempPtr->mNext;
+
+ }
+
+ //Append the aPath
+ nsHttpAuthPath *newAuthPath;
+ int newpathLen = strlen(aPath);
+ newAuthPath = (nsHttpAuthPath *) malloc(sizeof(nsHttpAuthPath) + newpathLen);
+ if (!newAuthPath)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ memcpy(newAuthPath->mPath, aPath, newpathLen+1);
+ newAuthPath->mNext = nullptr;
+
+ if (!mRoot)
+ mRoot = newAuthPath; //first entry
+ else
+ mTail->mNext = newAuthPath; // Append newAuthPath
+
+ //update the tail pointer.
+ mTail = newAuthPath;
+ return NS_OK;
+}
+
+nsresult
+nsHttpAuthEntry::Set(const char *path,
+ const char *realm,
+ const char *creds,
+ const char *chall,
+ const nsHttpAuthIdentity *ident,
+ nsISupports *metadata)
+{
+ char *newRealm, *newCreds, *newChall;
+
+ int realmLen = realm ? strlen(realm) : 0;
+ int credsLen = creds ? strlen(creds) : 0;
+ int challLen = chall ? strlen(chall) : 0;
+
+ int len = realmLen + 1 + credsLen + 1 + challLen + 1;
+ newRealm = (char *) malloc(len);
+ if (!newRealm)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (realm)
+ memcpy(newRealm, realm, realmLen);
+ newRealm[realmLen] = 0;
+
+ newCreds = &newRealm[realmLen + 1];
+ if (creds)
+ memcpy(newCreds, creds, credsLen);
+ newCreds[credsLen] = 0;
+
+ newChall = &newCreds[credsLen + 1];
+ if (chall)
+ memcpy(newChall, chall, challLen);
+ newChall[challLen] = 0;
+
+ nsresult rv = NS_OK;
+ if (ident) {
+ rv = mIdent.Set(*ident);
+ }
+ else if (mIdent.IsEmpty()) {
+ // If we are not given an identity and our cached identity has not been
+ // initialized yet (so is currently empty), initialize it now by
+ // filling it with nulls. We need to do that because consumers expect
+ // that mIdent is initialized after this function returns.
+ rv = mIdent.Set(nullptr, nullptr, nullptr);
+ }
+ if (NS_FAILED(rv)) {
+ free(newRealm);
+ return rv;
+ }
+
+ rv = AddPath(path);
+ if (NS_FAILED(rv)) {
+ free(newRealm);
+ return rv;
+ }
+
+ // wait until the end to clear member vars in case input params
+ // reference our members!
+ if (mRealm)
+ free(mRealm);
+
+ mRealm = newRealm;
+ mCreds = newCreds;
+ mChallenge = newChall;
+ mMetaData = metadata;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthNode
+//-----------------------------------------------------------------------------
+
+nsHttpAuthNode::nsHttpAuthNode()
+{
+ LOG(("Creating nsHttpAuthNode @%x\n", this));
+}
+
+nsHttpAuthNode::~nsHttpAuthNode()
+{
+ LOG(("Destroying nsHttpAuthNode @%x\n", this));
+
+ mList.Clear();
+}
+
+nsHttpAuthEntry *
+nsHttpAuthNode::LookupEntryByPath(const char *path)
+{
+ nsHttpAuthEntry *entry;
+
+ // null path matches empty path
+ if (!path)
+ path = "";
+
+ // look for an entry that either matches or contains this directory.
+ // ie. we'll give out credentials if the given directory is a sub-
+ // directory of an existing entry.
+ for (uint32_t i=0; i<mList.Length(); ++i) {
+ entry = mList[i];
+ nsHttpAuthPath *authPath = entry->RootPath();
+ while (authPath) {
+ const char *entryPath = authPath->mPath;
+ // proxy auth entries have no path, so require exact match on
+ // empty path string.
+ if (entryPath[0] == '\0') {
+ if (path[0] == '\0')
+ return entry;
+ }
+ else if (strncmp(path, entryPath, strlen(entryPath)) == 0)
+ return entry;
+
+ authPath = authPath->mNext;
+ }
+ }
+ return nullptr;
+}
+
+nsHttpAuthEntry *
+nsHttpAuthNode::LookupEntryByRealm(const char *realm)
+{
+ nsHttpAuthEntry *entry;
+
+ // null realm matches empty realm
+ if (!realm)
+ realm = "";
+
+ // look for an entry that matches this realm
+ uint32_t i;
+ for (i=0; i<mList.Length(); ++i) {
+ entry = mList[i];
+ if (strcmp(realm, entry->Realm()) == 0)
+ return entry;
+ }
+ return nullptr;
+}
+
+nsresult
+nsHttpAuthNode::SetAuthEntry(const char *path,
+ const char *realm,
+ const char *creds,
+ const char *challenge,
+ const nsHttpAuthIdentity *ident,
+ nsISupports *metadata)
+{
+ // look for an entry with a matching realm
+ nsHttpAuthEntry *entry = LookupEntryByRealm(realm);
+ if (!entry) {
+ entry = new nsHttpAuthEntry(path, realm, creds, challenge, ident, metadata);
+ if (!entry)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // We want the latest identity be at the begining of the list so that
+ // the newest working credentials are sent first on new requests.
+ // Changing a realm is sometimes used to "timeout" authrozization.
+ mList.InsertElementAt(0, entry);
+ }
+ else {
+ // update the entry...
+ entry->Set(path, realm, creds, challenge, ident, metadata);
+ }
+
+ return NS_OK;
+}
+
+void
+nsHttpAuthNode::ClearAuthEntry(const char *realm)
+{
+ nsHttpAuthEntry *entry = LookupEntryByRealm(realm);
+ if (entry) {
+ mList.RemoveElement(entry); // double search OK
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpAuthCache.h b/netwerk/protocol/http/nsHttpAuthCache.h
new file mode 100644
index 000000000..f96623d84
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthCache.h
@@ -0,0 +1,259 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpAuthCache_h__
+#define nsHttpAuthCache_h__
+
+#include "nsError.h"
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "plhash.h"
+#include "nsIObserver.h"
+
+class nsCString;
+
+namespace mozilla {
+
+class OriginAttributesPattern;
+
+namespace net {
+
+struct nsHttpAuthPath {
+ struct nsHttpAuthPath *mNext;
+ char mPath[1];
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthIdentity
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthIdentity
+{
+public:
+ nsHttpAuthIdentity()
+ : mUser(nullptr)
+ , mPass(nullptr)
+ , mDomain(nullptr)
+ {
+ }
+ nsHttpAuthIdentity(const char16_t *domain,
+ const char16_t *user,
+ const char16_t *password)
+ : mUser(nullptr)
+ {
+ Set(domain, user, password);
+ }
+ ~nsHttpAuthIdentity()
+ {
+ Clear();
+ }
+
+ const char16_t *Domain() const { return mDomain; }
+ const char16_t *User() const { return mUser; }
+ const char16_t *Password() const { return mPass; }
+
+ nsresult Set(const char16_t *domain,
+ const char16_t *user,
+ const char16_t *password);
+ nsresult Set(const nsHttpAuthIdentity &other) { return Set(other.mDomain, other.mUser, other.mPass); }
+ void Clear();
+
+ bool Equals(const nsHttpAuthIdentity &other) const;
+ bool IsEmpty() const { return !mUser; }
+
+private:
+ // allocated as one contiguous blob, starting at mUser.
+ char16_t *mUser;
+ char16_t *mPass;
+ char16_t *mDomain;
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthEntry
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthEntry
+{
+public:
+ const char *Realm() const { return mRealm; }
+ const char *Creds() const { return mCreds; }
+ const char *Challenge() const { return mChallenge; }
+ const char16_t *Domain() const { return mIdent.Domain(); }
+ const char16_t *User() const { return mIdent.User(); }
+ const char16_t *Pass() const { return mIdent.Password(); }
+ nsHttpAuthPath *RootPath() { return mRoot; }
+
+ const nsHttpAuthIdentity &Identity() const { return mIdent; }
+
+ nsresult AddPath(const char *aPath);
+
+ nsCOMPtr<nsISupports> mMetaData;
+
+private:
+ nsHttpAuthEntry(const char *path,
+ const char *realm,
+ const char *creds,
+ const char *challenge,
+ const nsHttpAuthIdentity *ident,
+ nsISupports *metadata)
+ : mRoot(nullptr)
+ , mTail(nullptr)
+ , mRealm(nullptr)
+ {
+ Set(path, realm, creds, challenge, ident, metadata);
+ }
+ ~nsHttpAuthEntry();
+
+ nsresult Set(const char *path,
+ const char *realm,
+ const char *creds,
+ const char *challenge,
+ const nsHttpAuthIdentity *ident,
+ nsISupports *metadata);
+
+ nsHttpAuthIdentity mIdent;
+
+ nsHttpAuthPath *mRoot; //root pointer
+ nsHttpAuthPath *mTail; //tail pointer
+
+ // allocated together in one blob, starting with mRealm.
+ char *mRealm;
+ char *mCreds;
+ char *mChallenge;
+
+ friend class nsHttpAuthNode;
+ friend class nsHttpAuthCache;
+ friend class nsAutoPtr<nsHttpAuthEntry>; // needs to call the destructor
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthNode
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthNode
+{
+private:
+ nsHttpAuthNode();
+ ~nsHttpAuthNode();
+
+ // path can be null, in which case we'll search for an entry
+ // with a null path.
+ nsHttpAuthEntry *LookupEntryByPath(const char *path);
+
+ // realm must not be null
+ nsHttpAuthEntry *LookupEntryByRealm(const char *realm);
+
+ // if a matching entry is found, then credentials will be changed.
+ nsresult SetAuthEntry(const char *path,
+ const char *realm,
+ const char *credentials,
+ const char *challenge,
+ const nsHttpAuthIdentity *ident,
+ nsISupports *metadata);
+
+ void ClearAuthEntry(const char *realm);
+
+ uint32_t EntryCount() { return mList.Length(); }
+
+private:
+ nsTArray<nsAutoPtr<nsHttpAuthEntry> > mList;
+
+ friend class nsHttpAuthCache;
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthCache
+// (holds a hash table from host:port to nsHttpAuthNode)
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthCache
+{
+public:
+ nsHttpAuthCache();
+ ~nsHttpAuthCache();
+
+ nsresult Init();
+
+ // |scheme|, |host|, and |port| are required
+ // |path| can be null
+ // |entry| is either null or a weak reference
+ nsresult GetAuthEntryForPath(const char *scheme,
+ const char *host,
+ int32_t port,
+ const char *path,
+ nsACString const &originSuffix,
+ nsHttpAuthEntry **entry);
+
+ // |scheme|, |host|, and |port| are required
+ // |realm| must not be null
+ // |entry| is either null or a weak reference
+ nsresult GetAuthEntryForDomain(const char *scheme,
+ const char *host,
+ int32_t port,
+ const char *realm,
+ nsACString const &originSuffix,
+ nsHttpAuthEntry **entry);
+
+ // |scheme|, |host|, and |port| are required
+ // |path| can be null
+ // |realm| must not be null
+ // if |credentials|, |user|, |pass|, and |challenge| are each
+ // null, then the entry is deleted.
+ nsresult SetAuthEntry(const char *scheme,
+ const char *host,
+ int32_t port,
+ const char *directory,
+ const char *realm,
+ const char *credentials,
+ const char *challenge,
+ nsACString const &originSuffix,
+ const nsHttpAuthIdentity *ident,
+ nsISupports *metadata);
+
+ void ClearAuthEntry(const char *scheme,
+ const char *host,
+ int32_t port,
+ const char *realm,
+ nsACString const &originSuffix);
+
+ // expire all existing auth list entries including proxy auths.
+ nsresult ClearAll();
+
+private:
+ nsHttpAuthNode *LookupAuthNode(const char *scheme,
+ const char *host,
+ int32_t port,
+ nsACString const &originSuffix,
+ nsCString &key);
+
+ // hash table allocation functions
+ static void* AllocTable(void *, size_t size);
+ static void FreeTable(void *, void *item);
+ static PLHashEntry* AllocEntry(void *, const void *key);
+ static void FreeEntry(void *, PLHashEntry *he, unsigned flag);
+
+ static PLHashAllocOps gHashAllocOps;
+
+ class OriginClearObserver : public nsIObserver {
+ virtual ~OriginClearObserver() {}
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ explicit OriginClearObserver(nsHttpAuthCache* aOwner) : mOwner(aOwner) {}
+ nsHttpAuthCache* mOwner;
+ };
+
+ void ClearOriginData(OriginAttributesPattern const &pattern);
+
+private:
+ PLHashTable *mDB; // "host:port" --> nsHttpAuthNode
+ RefPtr<OriginClearObserver> mObserver;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpAuthCache_h__
diff --git a/netwerk/protocol/http/nsHttpAuthManager.cpp b/netwerk/protocol/http/nsHttpAuthManager.cpp
new file mode 100644
index 000000000..a2f1b7c5e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthManager.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpHandler.h"
+#include "nsHttpAuthManager.h"
+#include "nsNetUtil.h"
+#include "nsIPrincipal.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsHttpAuthManager, nsIHttpAuthManager)
+
+nsHttpAuthManager::nsHttpAuthManager()
+{
+}
+
+nsresult nsHttpAuthManager::Init()
+{
+ // get reference to the auth cache. we assume that we will live
+ // as long as gHttpHandler. instantiate it if necessary.
+
+ if (!gHttpHandler) {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ios->GetProtocolHandler("http", getter_AddRefs(handler));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // maybe someone is overriding our HTTP handler implementation?
+ NS_ENSURE_TRUE(gHttpHandler, NS_ERROR_UNEXPECTED);
+ }
+
+ mAuthCache = gHttpHandler->AuthCache(false);
+ mPrivateAuthCache = gHttpHandler->AuthCache(true);
+ NS_ENSURE_TRUE(mAuthCache, NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(mPrivateAuthCache, NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+nsHttpAuthManager::~nsHttpAuthManager()
+{
+}
+
+NS_IMETHODIMP
+nsHttpAuthManager::GetAuthIdentity(const nsACString & aScheme,
+ const nsACString & aHost,
+ int32_t aPort,
+ const nsACString & aAuthType,
+ const nsACString & aRealm,
+ const nsACString & aPath,
+ nsAString & aUserDomain,
+ nsAString & aUserName,
+ nsAString & aUserPassword,
+ bool aIsPrivate,
+ nsIPrincipal* aPrincipal)
+{
+ nsHttpAuthCache* auth_cache = aIsPrivate ? mPrivateAuthCache : mAuthCache;
+ nsHttpAuthEntry * entry = nullptr;
+ nsresult rv;
+
+ nsAutoCString originSuffix;
+ if (aPrincipal) {
+ BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(originSuffix);
+ }
+
+ if (!aPath.IsEmpty())
+ rv = auth_cache->GetAuthEntryForPath(PromiseFlatCString(aScheme).get(),
+ PromiseFlatCString(aHost).get(),
+ aPort,
+ PromiseFlatCString(aPath).get(),
+ originSuffix,
+ &entry);
+ else
+ rv = auth_cache->GetAuthEntryForDomain(PromiseFlatCString(aScheme).get(),
+ PromiseFlatCString(aHost).get(),
+ aPort,
+ PromiseFlatCString(aRealm).get(),
+ originSuffix,
+ &entry);
+
+ if (NS_FAILED(rv))
+ return rv;
+ if (!entry)
+ return NS_ERROR_UNEXPECTED;
+
+ aUserDomain.Assign(entry->Domain());
+ aUserName.Assign(entry->User());
+ aUserPassword.Assign(entry->Pass());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpAuthManager::SetAuthIdentity(const nsACString & aScheme,
+ const nsACString & aHost,
+ int32_t aPort,
+ const nsACString & aAuthType,
+ const nsACString & aRealm,
+ const nsACString & aPath,
+ const nsAString & aUserDomain,
+ const nsAString & aUserName,
+ const nsAString & aUserPassword,
+ bool aIsPrivate,
+ nsIPrincipal* aPrincipal)
+{
+ nsHttpAuthIdentity ident(PromiseFlatString(aUserDomain).get(),
+ PromiseFlatString(aUserName).get(),
+ PromiseFlatString(aUserPassword).get());
+
+ nsAutoCString originSuffix;
+ if (aPrincipal) {
+ BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(originSuffix);
+ }
+
+
+ nsHttpAuthCache* auth_cache = aIsPrivate ? mPrivateAuthCache : mAuthCache;
+ return auth_cache->SetAuthEntry(PromiseFlatCString(aScheme).get(),
+ PromiseFlatCString(aHost).get(),
+ aPort,
+ PromiseFlatCString(aPath).get(),
+ PromiseFlatCString(aRealm).get(),
+ nullptr, // credentials
+ nullptr, // challenge
+ originSuffix,
+ &ident,
+ nullptr); // metadata
+}
+
+NS_IMETHODIMP
+nsHttpAuthManager::ClearAll()
+{
+ nsresult rv = mAuthCache->ClearAll();
+ nsresult rv2 = mPrivateAuthCache->ClearAll();
+ if (NS_FAILED(rv))
+ return rv;
+ if (NS_FAILED(rv2))
+ return rv2;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpAuthManager.h b/netwerk/protocol/http/nsHttpAuthManager.h
new file mode 100644
index 000000000..261417c1d
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthManager.h
@@ -0,0 +1,35 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpAuthManager_h__
+#define nsHttpAuthManager_h__
+
+#include "nsIHttpAuthManager.h"
+
+namespace mozilla {
+namespace net {
+
+class nsHttpAuthCache;
+
+class nsHttpAuthManager : public nsIHttpAuthManager
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHMANAGER
+
+ nsHttpAuthManager();
+ nsresult Init();
+
+protected:
+ virtual ~nsHttpAuthManager();
+
+ nsHttpAuthCache *mAuthCache;
+ nsHttpAuthCache *mPrivateAuthCache;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpAuthManager_h__
diff --git a/netwerk/protocol/http/nsHttpBasicAuth.cpp b/netwerk/protocol/http/nsHttpBasicAuth.cpp
new file mode 100644
index 000000000..7e1232fd8
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpBasicAuth.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpBasicAuth.h"
+#include "plbase64.h"
+#include "plstr.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpBasicAuth <public>
+//-----------------------------------------------------------------------------
+
+nsHttpBasicAuth::nsHttpBasicAuth()
+{
+}
+
+nsHttpBasicAuth::~nsHttpBasicAuth()
+{
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpBasicAuth::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpBasicAuth, nsIHttpAuthenticator)
+
+//-----------------------------------------------------------------------------
+// nsHttpBasicAuth::nsIHttpAuthenticator
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpBasicAuth::ChallengeReceived(nsIHttpAuthenticableChannel *authChannel,
+ const char *challenge,
+ bool isProxyAuth,
+ nsISupports **sessionState,
+ nsISupports **continuationState,
+ bool *identityInvalid)
+{
+ // if challenged, then the username:password that was sent must
+ // have been wrong.
+ *identityInvalid = true;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsHttpBasicAuth::GenerateCredentialsAsync(nsIHttpAuthenticableChannel *authChannel,
+ nsIHttpAuthenticatorCallback* aCallback,
+ const char *challenge,
+ bool isProxyAuth,
+ const char16_t *domain,
+ const char16_t *username,
+ const char16_t *password,
+ nsISupports *sessionState,
+ nsISupports *continuationState,
+ nsICancelable **aCancellable)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpBasicAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel,
+ const char *challenge,
+ bool isProxyAuth,
+ const char16_t *domain,
+ const char16_t *user,
+ const char16_t *password,
+ nsISupports **sessionState,
+ nsISupports **continuationState,
+ uint32_t *aFlags,
+ char **creds)
+
+{
+ LOG(("nsHttpBasicAuth::GenerateCredentials [challenge=%s]\n", challenge));
+
+ NS_ENSURE_ARG_POINTER(creds);
+
+ *aFlags = 0;
+
+ // we only know how to deal with Basic auth for http.
+ bool isBasicAuth = !PL_strncasecmp(challenge, "basic", 5);
+ NS_ENSURE_TRUE(isBasicAuth, NS_ERROR_UNEXPECTED);
+
+ // we work with ASCII around here
+ nsAutoCString userpass;
+ LossyCopyUTF16toASCII(user, userpass);
+ userpass.Append(':'); // always send a ':' (see bug 129565)
+ if (password)
+ LossyAppendUTF16toASCII(password, userpass);
+
+ // plbase64.h provides this worst-case output buffer size calculation.
+ // use calloc, since PL_Base64Encode does not null terminate.
+ *creds = (char *) calloc(6 + ((userpass.Length() + 2)/3)*4 + 1, 1);
+ if (!*creds)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ memcpy(*creds, "Basic ", 6);
+ PL_Base64Encode(userpass.get(), userpass.Length(), *creds + 6);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpBasicAuth::GetAuthFlags(uint32_t *flags)
+{
+ *flags = REQUEST_BASED | REUSABLE_CREDENTIALS | REUSABLE_CHALLENGE;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpBasicAuth.h b/netwerk/protocol/http/nsHttpBasicAuth.h
new file mode 100644
index 000000000..b824b1d5a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpBasicAuth.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBasicAuth_h__
+#define nsBasicAuth_h__
+
+#include "nsIHttpAuthenticator.h"
+
+namespace mozilla { namespace net {
+
+//-----------------------------------------------------------------------------
+// The nsHttpBasicAuth class produces HTTP Basic-auth responses for a username/
+// (optional)password pair, BASE64("user:pass").
+//-----------------------------------------------------------------------------
+
+class nsHttpBasicAuth : public nsIHttpAuthenticator
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHENTICATOR
+
+ nsHttpBasicAuth();
+private:
+ virtual ~nsHttpBasicAuth();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsHttpBasicAuth_h__
diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp
new file mode 100644
index 000000000..0e570e8cb
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -0,0 +1,8563 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab ts=4 sw=4 sts=4 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include <inttypes.h>
+
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/Sprintf.h"
+
+#include "nsHttp.h"
+#include "nsHttpChannel.h"
+#include "nsHttpHandler.h"
+#include "nsIApplicationCacheService.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsICacheStorageService.h"
+#include "nsICacheStorage.h"
+#include "nsICacheEntry.h"
+#include "nsICaptivePortalService.h"
+#include "nsICryptoHash.h"
+#include "nsINetworkInterceptController.h"
+#include "nsINSSErrorsService.h"
+#include "nsISecurityReporter.h"
+#include "nsIStringBundle.h"
+#include "nsIStreamListenerTee.h"
+#include "nsISeekableStream.h"
+#include "nsILoadGroupChild.h"
+#include "nsIProtocolProxyService2.h"
+#include "nsIURIClassifier.h"
+#include "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIURL.h"
+#include "nsIStreamTransportService.h"
+#include "prnetdb.h"
+#include "nsEscape.h"
+#include "nsStreamUtils.h"
+#include "nsIOService.h"
+#include "nsDNSPrefetch.h"
+#include "nsChannelClassifier.h"
+#include "nsIRedirectResultListener.h"
+#include "mozilla/dom/ContentVerifier.h"
+#include "mozilla/TimeStamp.h"
+#include "nsError.h"
+#include "nsPrintfCString.h"
+#include "nsAlgorithm.h"
+#include "nsQueryObject.h"
+#include "GeckoProfiler.h"
+#include "nsIConsoleService.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Preferences.h"
+#include "nsISSLSocketControl.h"
+#include "sslt.h"
+#include "nsContentUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsIClassOfService.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISSLStatus.h"
+#include "nsISSLStatusProvider.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIWebProgressListener.h"
+#include "LoadContextInfo.h"
+#include "netCore.h"
+#include "nsHttpTransaction.h"
+#include "nsICacheEntryDescriptor.h"
+#include "nsICancelable.h"
+#include "nsIHttpChannelAuthProvider.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIHttpEventSink.h"
+#include "nsIPrompt.h"
+#include "nsInputStreamPump.h"
+#include "nsURLHelper.h"
+#include "nsISocketTransport.h"
+#include "nsIStreamConverterService.h"
+#include "nsISiteSecurityService.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "CacheObserver.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/Telemetry.h"
+#include "AlternateServices.h"
+#include "InterceptedChannel.h"
+#include "nsIHttpPushListener.h"
+#include "nsIX509Cert.h"
+#include "ScopedNSSTypes.h"
+#include "nsNullPrincipal.h"
+#include "nsIDeprecationWarner.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsICompressConvStats.h"
+#include "nsCORSListenerProxy.h"
+#include "nsISocketProvider.h"
+#include "mozilla/net/Predictor.h"
+#include "CacheControlParser.h"
+#include "nsMixedContentBlocker.h"
+#include "HSTSPrimerListener.h"
+#include "CacheStorageService.h"
+
+namespace mozilla { namespace net {
+
+namespace {
+
+// Monotonically increasing ID for generating unique cache entries per
+// intercepted channel.
+static uint64_t gNumIntercepted = 0;
+
+// True if the local cache should be bypassed when processing a request.
+#define BYPASS_LOCAL_CACHE(loadFlags) \
+ (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \
+ nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE))
+
+#define RECOVER_FROM_CACHE_FILE_ERROR(result) \
+ ((result) == NS_ERROR_FILE_NOT_FOUND || \
+ (result) == NS_ERROR_FILE_CORRUPTED || \
+ (result) == NS_ERROR_OUT_OF_MEMORY)
+
+static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
+static NS_DEFINE_CID(kStreamTransportServiceCID,
+ NS_STREAMTRANSPORTSERVICE_CID);
+
+enum CacheDisposition {
+ kCacheHit = 1,
+ kCacheHitViaReval = 2,
+ kCacheMissedViaReval = 3,
+ kCacheMissed = 4
+};
+
+void
+AccumulateCacheHitTelemetry(CacheDisposition hitOrMiss)
+{
+ if (!CacheObserver::UseNewCache()) {
+ Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2, hitOrMiss);
+ }
+ else {
+ Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2_V2, hitOrMiss);
+
+ int32_t experiment = CacheObserver::HalfLifeExperiment();
+ if (experiment > 0 && hitOrMiss == kCacheMissed) {
+ Telemetry::Accumulate(Telemetry::HTTP_CACHE_MISS_HALFLIFE_EXPERIMENT_2,
+ experiment - 1);
+ }
+ }
+}
+
+// Computes and returns a SHA1 hash of the input buffer. The input buffer
+// must be a null-terminated string.
+nsresult
+Hash(const char *buf, nsACString &hash)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICryptoHash> hasher
+ = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Init(nsICryptoHash::SHA1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Update(reinterpret_cast<unsigned const char*>(buf),
+ strlen(buf));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Finish(true, hash);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+bool
+IsInSubpathOfAppCacheManifest(nsIApplicationCache *cache, nsACString const& uriSpec)
+{
+ MOZ_ASSERT(cache);
+
+ static bool sForbid = true;
+ static nsresult once = Preferences::AddBoolVarCache(&sForbid, "network.appcache.forbid-fallback-outside-manifest-path", true);
+ Unused << once;
+
+ if (!sForbid) {
+ return true;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri, &rv));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoCString directory;
+ rv = url->GetDirectory(directory);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> manifestURI;
+ rv = cache->GetManifestURI(getter_AddRefs(manifestURI));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURL> manifestURL(do_QueryInterface(manifestURI, &rv));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoCString manifestDirectory;
+ rv = manifestURL->GetDirectory(manifestDirectory);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return StringBeginsWith(directory, manifestDirectory);
+}
+
+} // unnamed namespace
+
+// We only treat 3xx responses as redirects if they have a Location header and
+// the status code is in a whitelist.
+bool
+WillRedirect(nsHttpResponseHead * response)
+{
+ return nsHttpChannel::IsRedirectStatus(response->Status()) &&
+ response->HasHeader(nsHttp::Location);
+}
+
+nsresult
+StoreAuthorizationMetaData(nsICacheEntry *entry, nsHttpRequestHead *requestHead);
+
+class AutoRedirectVetoNotifier
+{
+public:
+ explicit AutoRedirectVetoNotifier(nsHttpChannel* channel) : mChannel(channel)
+ {
+ if (mChannel->mHasAutoRedirectVetoNotifier) {
+ MOZ_CRASH("Nested AutoRedirectVetoNotifier on the stack");
+ mChannel = nullptr;
+ return;
+ }
+
+ mChannel->mHasAutoRedirectVetoNotifier = true;
+ }
+ ~AutoRedirectVetoNotifier() {ReportRedirectResult(false);}
+ void RedirectSucceeded() {ReportRedirectResult(true);}
+
+private:
+ nsHttpChannel* mChannel;
+ void ReportRedirectResult(bool succeeded);
+};
+
+void
+AutoRedirectVetoNotifier::ReportRedirectResult(bool succeeded)
+{
+ if (!mChannel)
+ return;
+
+ mChannel->mRedirectChannel = nullptr;
+
+ nsCOMPtr<nsIRedirectResultListener> vetoHook;
+ NS_QueryNotificationCallbacks(mChannel,
+ NS_GET_IID(nsIRedirectResultListener),
+ getter_AddRefs(vetoHook));
+
+ nsHttpChannel* channel = mChannel;
+ mChannel = nullptr;
+
+ if (vetoHook)
+ vetoHook->OnRedirectResult(succeeded);
+
+ // Drop after the notification
+ channel->mHasAutoRedirectVetoNotifier = false;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <public>
+//-----------------------------------------------------------------------------
+
+nsHttpChannel::nsHttpChannel()
+ : HttpAsyncAborter<nsHttpChannel>(this)
+ , mLogicalOffset(0)
+ , mPostID(0)
+ , mRequestTime(0)
+ , mOfflineCacheLastModifiedTime(0)
+ , mInterceptCache(DO_NOT_INTERCEPT)
+ , mInterceptionID(gNumIntercepted++)
+ , mCacheOpenWithPriority(false)
+ , mCacheQueueSizeWhenOpen(0)
+ , mCachedContentIsValid(false)
+ , mCachedContentIsPartial(false)
+ , mCacheOnlyMetadata(false)
+ , mTransactionReplaced(false)
+ , mAuthRetryPending(false)
+ , mProxyAuthPending(false)
+ , mCustomAuthHeader(false)
+ , mResuming(false)
+ , mInitedCacheEntry(false)
+ , mFallbackChannel(false)
+ , mCustomConditionalRequest(false)
+ , mFallingBack(false)
+ , mWaitingForRedirectCallback(false)
+ , mRequestTimeInitialized(false)
+ , mCacheEntryIsReadOnly(false)
+ , mCacheEntryIsWriteOnly(false)
+ , mCacheEntriesToWaitFor(0)
+ , mHasQueryString(0)
+ , mConcurrentCacheAccess(0)
+ , mIsPartialRequest(0)
+ , mHasAutoRedirectVetoNotifier(0)
+ , mPinCacheContent(0)
+ , mIsCorsPreflightDone(0)
+ , mStronglyFramed(false)
+ , mUsedNetwork(0)
+ , mAuthConnectionRestartable(0)
+ , mPushedStream(nullptr)
+ , mLocalBlocklist(false)
+ , mWarningReporter(nullptr)
+ , mDidReval(false)
+{
+ LOG(("Creating nsHttpChannel [this=%p]\n", this));
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+}
+
+nsHttpChannel::~nsHttpChannel()
+{
+ LOG(("Destroying nsHttpChannel [this=%p]\n", this));
+
+ if (mAuthProvider)
+ mAuthProvider->Disconnect(NS_ERROR_ABORT);
+}
+
+nsresult
+nsHttpChannel::Init(nsIURI *uri,
+ uint32_t caps,
+ nsProxyInfo *proxyInfo,
+ uint32_t proxyResolveFlags,
+ nsIURI *proxyURI,
+ const nsID& channelId)
+{
+ nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo,
+ proxyResolveFlags, proxyURI, channelId);
+ if (NS_FAILED(rv))
+ return rv;
+
+ LOG(("nsHttpChannel::Init [this=%p]\n", this));
+
+ return rv;
+}
+
+nsresult
+nsHttpChannel::AddSecurityMessage(const nsAString& aMessageTag,
+ const nsAString& aMessageCategory)
+{
+ if (mWarningReporter) {
+ return mWarningReporter->ReportSecurityMessage(aMessageTag,
+ aMessageCategory);
+ }
+ return HttpBaseChannel::AddSecurityMessage(aMessageTag,
+ aMessageCategory);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <private>
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpChannel::Connect()
+{
+ nsresult rv;
+
+ LOG(("nsHttpChannel::Connect [this=%p]\n", this));
+
+ // Note that we are only setting the "Upgrade-Insecure-Requests" request
+ // header for *all* navigational requests instead of all requests as
+ // defined in the spec, see:
+ // https://www.w3.org/TR/upgrade-insecure-requests/#preference
+ nsContentPolicyType type = mLoadInfo ?
+ mLoadInfo->GetExternalContentPolicyType() :
+ nsIContentPolicy::TYPE_OTHER;
+
+ if (type == nsIContentPolicy::TYPE_DOCUMENT ||
+ type == nsIContentPolicy::TYPE_SUBDOCUMENT) {
+ rv = SetRequestHeader(NS_LITERAL_CSTRING("Upgrade-Insecure-Requests"),
+ NS_LITERAL_CSTRING("1"), false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ bool isHttps = false;
+ rv = mURI->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ if (!isHttps && mLoadInfo) {
+ nsContentUtils::GetSecurityManager()->
+ GetChannelResultPrincipal(this, getter_AddRefs(resultPrincipal));
+ }
+ bool shouldUpgrade = false;
+ rv = NS_ShouldSecureUpgrade(mURI,
+ mLoadInfo,
+ resultPrincipal,
+ mPrivateBrowsing,
+ mAllowSTS,
+ shouldUpgrade);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (shouldUpgrade) {
+ return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
+ }
+
+ // ensure that we are using a valid hostname
+ if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Origin())))
+ return NS_ERROR_UNKNOWN_HOST;
+
+ if (mUpgradeProtocolCallback) {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+
+ // Finalize ConnectionInfo flags before SpeculativeConnect
+ mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
+ mConnectionInfo->SetPrivate(mPrivateBrowsing);
+ mConnectionInfo->SetNoSpdy(mCaps & NS_HTTP_DISALLOW_SPDY);
+ mConnectionInfo->SetBeConservative((mCaps & NS_HTTP_BE_CONSERVATIVE) || mBeConservative);
+
+ // Consider opening a TCP connection right away.
+ SpeculativeConnect();
+
+ // Don't allow resuming when cache must be used
+ if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
+ LOG(("Resuming from cache is not supported yet"));
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+
+ // open a cache entry for this channel...
+ rv = OpenCacheEntry(isHttps);
+
+ // do not continue if asyncOpenCacheEntry is in progress
+ if (AwaitingCacheCallbacks()) {
+ LOG(("nsHttpChannel::Connect %p AwaitingCacheCallbacks forces async\n", this));
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Unexpected state");
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("OpenCacheEntry failed [rv=%x]\n", rv));
+ // if this channel is only allowed to pull from the cache, then
+ // we must fail if we were unable to open a cache entry.
+ if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+ // If we have a fallback URI (and we're not already
+ // falling back), process the fallback asynchronously.
+ if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
+ return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
+ }
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+ // otherwise, let's just proceed without using the cache.
+ }
+
+ return TryHSTSPriming();
+}
+
+nsresult
+nsHttpChannel::TryHSTSPriming()
+{
+ if (mLoadInfo) {
+ // HSTS priming requires the LoadInfo provided with AsyncOpen2
+ bool requireHSTSPriming =
+ mLoadInfo->GetForceHSTSPriming();
+
+ if (requireHSTSPriming &&
+ nsMixedContentBlocker::sSendHSTSPriming &&
+ mInterceptCache == DO_NOT_INTERCEPT) {
+ bool isHttpsScheme;
+ nsresult rv = mURI->SchemeIs("https", &isHttpsScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isHttpsScheme) {
+ rv = HSTSPrimingListener::StartHSTSPriming(this, this);
+
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ // The request was already upgraded, for example by
+ // upgrade-insecure-requests or a prior successful priming request
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+ HSTSPrimingResult::eHSTS_PRIMING_ALREADY_UPGRADED);
+ mLoadInfo->ClearHSTSPriming();
+ }
+ }
+
+ return ContinueConnect();
+}
+
+nsresult
+nsHttpChannel::ContinueConnect()
+{
+ // If we have had HSTS priming, we need to reevaluate whether we need
+ // a CORS preflight. Bug: 1272440
+ // If we need to start a CORS preflight, do it now!
+ // Note that it is important to do this before the early returns below.
+ if (!mIsCorsPreflightDone && mRequireCORSPreflight &&
+ mInterceptCache != INTERCEPTED) {
+ MOZ_ASSERT(!mPreflightChannel);
+ nsresult rv =
+ nsCORSListenerProxy::StartCORSPreflight(this, this,
+ mUnsafeHeaders,
+ getter_AddRefs(mPreflightChannel));
+ return rv;
+ }
+
+ MOZ_RELEASE_ASSERT(!(mRequireCORSPreflight &&
+ mInterceptCache != INTERCEPTED) ||
+ mIsCorsPreflightDone,
+ "CORS preflight must have been finished by the time we "
+ "do the rest of ContinueConnect");
+
+ // we may or may not have a cache entry at this point
+ if (mCacheEntry) {
+ // read straight from the cache if possible...
+ if (mCachedContentIsValid) {
+ nsRunnableMethod<nsHttpChannel> *event = nullptr;
+ if (!mCachedContentIsPartial) {
+ AsyncCall(&nsHttpChannel::AsyncOnExamineCachedResponse, &event);
+ }
+ nsresult rv = ReadFromCache(true);
+ if (NS_FAILED(rv) && event) {
+ event->Revoke();
+ }
+
+ // Don't accumulate the cache hit telemetry for intercepted channels.
+ if (mInterceptCache != INTERCEPTED) {
+ AccumulateCacheHitTelemetry(kCacheHit);
+ }
+
+ return rv;
+ }
+ else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+ // the cache contains the requested resource, but it must be
+ // validated before we can reuse it. since we are not allowed
+ // to hit the net, there's nothing more to do. the document
+ // is effectively not in the cache.
+ LOG((" !mCachedContentIsValid && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+ }
+ else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+ // If we have a fallback URI (and we're not already
+ // falling back), process the fallback asynchronously.
+ if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
+ return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
+ }
+ LOG((" !mCacheEntry && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+
+ if (mLoadFlags & LOAD_NO_NETWORK_IO) {
+ LOG((" mLoadFlags & LOAD_NO_NETWORK_IO"));
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+
+ // hit the net...
+ nsresult rv = SetupTransaction();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mTransactionPump->AsyncRead(this, nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t suspendCount = mSuspendCount;
+ while (suspendCount--)
+ mTransactionPump->Suspend();
+
+ return NS_OK;
+}
+
+void
+nsHttpChannel::SpeculativeConnect()
+{
+ // Before we take the latency hit of dealing with the cache, try and
+ // get the TCP (and SSL) handshakes going so they can overlap.
+
+ // don't speculate if we are on a local blocklist, on uses of the offline
+ // application cache, if we are offline, when doing http upgrade (i.e.
+ // websockets bootstrap), or if we can't do keep-alive (because then we
+ // couldn't reuse the speculative connection anyhow).
+ if (mLocalBlocklist || mApplicationCache || gIOService->IsOffline() ||
+ mUpgradeProtocolCallback || !(mCaps & NS_HTTP_ALLOW_KEEPALIVE))
+ return;
+
+ // LOAD_ONLY_FROM_CACHE and LOAD_NO_NETWORK_IO must not hit network.
+ // LOAD_FROM_CACHE and LOAD_CHECK_OFFLINE_CACHE are unlikely to hit network,
+ // so skip preconnects for them.
+ if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE |
+ LOAD_NO_NETWORK_IO | LOAD_CHECK_OFFLINE_CACHE))
+ return;
+
+ if (mAllowStaleCacheContent) {
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+ if (!callbacks)
+ return;
+
+ gHttpHandler->SpeculativeConnect(
+ mConnectionInfo, callbacks, mCaps & NS_HTTP_DISALLOW_SPDY);
+}
+
+void
+nsHttpChannel::DoNotifyListenerCleanup()
+{
+ // We don't need this info anymore
+ CleanRedirectCacheChainIfNecessary();
+}
+
+void
+nsHttpChannel::HandleAsyncRedirect()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async redirect [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::HandleAsyncRedirect;
+ return;
+ }
+
+ nsresult rv = NS_OK;
+
+ LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this));
+
+ // since this event is handled asynchronously, it is possible that this
+ // channel could have been canceled, in which case there would be no point
+ // in processing the redirect.
+ if (NS_SUCCEEDED(mStatus)) {
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
+ rv = AsyncProcessRedirection(mResponseHead->Status());
+ if (NS_FAILED(rv)) {
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
+ // TODO: if !DoNotRender3xxBody(), render redirect body instead.
+ // But first we need to cache 3xx bodies (bug 748510)
+ ContinueHandleAsyncRedirect(rv);
+ }
+ }
+ else {
+ ContinueHandleAsyncRedirect(mStatus);
+ }
+}
+
+nsresult
+nsHttpChannel::ContinueHandleAsyncRedirect(nsresult rv)
+{
+ if (NS_FAILED(rv)) {
+ // If AsyncProcessRedirection fails, then we have to send out the
+ // OnStart/OnStop notifications.
+ LOG(("ContinueHandleAsyncRedirect got failure result [rv=%x]\n", rv));
+
+ bool redirectsEnabled =
+ !mLoadInfo || !mLoadInfo->GetDontFollowRedirects();
+
+ if (redirectsEnabled) {
+ // TODO: stop failing original channel if redirect vetoed?
+ mStatus = rv;
+
+ DoNotifyListener();
+
+ // Blow away cache entry if we couldn't process the redirect
+ // for some reason (the cache entry might be corrupt).
+ if (mCacheEntry) {
+ mCacheEntry->AsyncDoom(nullptr);
+ }
+ }
+ else {
+ DoNotifyListener();
+ }
+ }
+
+ CloseCacheEntry(true);
+
+ mIsPending = false;
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ return NS_OK;
+}
+
+void
+nsHttpChannel::HandleAsyncNotModified()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async not-modified [this=%p]\n",
+ this));
+ mCallOnResume = &nsHttpChannel::HandleAsyncNotModified;
+ return;
+ }
+
+ LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
+
+ DoNotifyListener();
+
+ CloseCacheEntry(false);
+
+ mIsPending = false;
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+}
+
+void
+nsHttpChannel::HandleAsyncFallback()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::HandleAsyncFallback;
+ return;
+ }
+
+ nsresult rv = NS_OK;
+
+ LOG(("nsHttpChannel::HandleAsyncFallback [this=%p]\n", this));
+
+ // since this event is handled asynchronously, it is possible that this
+ // channel could have been canceled, in which case there would be no point
+ // in processing the fallback.
+ if (!mCanceled) {
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
+ bool waitingForRedirectCallback;
+ rv = ProcessFallback(&waitingForRedirectCallback);
+ if (waitingForRedirectCallback)
+ return;
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
+ }
+
+ ContinueHandleAsyncFallback(rv);
+}
+
+nsresult
+nsHttpChannel::ContinueHandleAsyncFallback(nsresult rv)
+{
+ if (!mCanceled && (NS_FAILED(rv) || !mFallingBack)) {
+ // If ProcessFallback fails, then we have to send out the
+ // OnStart/OnStop notifications.
+ LOG(("ProcessFallback failed [rv=%x, %d]\n", rv, mFallingBack));
+ mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
+ DoNotifyListener();
+ }
+
+ mIsPending = false;
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ return rv;
+}
+
+void
+nsHttpChannel::SetupTransactionRequestContext()
+{
+ if (!EnsureRequestContextID()) {
+ return;
+ }
+
+ nsIRequestContextService *rcsvc =
+ gHttpHandler->GetRequestContextService();
+ if (!rcsvc) {
+ return;
+ }
+
+ nsCOMPtr<nsIRequestContext> rc;
+ nsresult rv = rcsvc->GetRequestContext(mRequestContextID,
+ getter_AddRefs(rc));
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ mTransaction->SetRequestContext(rc);
+}
+
+static bool
+SafeForPipelining(nsHttpRequestHead::ParsedMethodType method,
+ const nsCString &methodString)
+{
+ if (method == nsHttpRequestHead::kMethod_Get ||
+ method == nsHttpRequestHead::kMethod_Head ||
+ method == nsHttpRequestHead::kMethod_Options) {
+ return true;
+ }
+
+ if (method != nsHttpRequestHead::kMethod_Custom) {
+ return false;
+ }
+
+ return (!strcmp(methodString.get(), "PROPFIND") ||
+ !strcmp(methodString.get(), "PROPPATCH"));
+}
+
+nsresult
+nsHttpChannel::SetupTransaction()
+{
+ LOG(("nsHttpChannel::SetupTransaction [this=%p]\n", this));
+
+ NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
+
+ nsresult rv;
+
+ mUsedNetwork = 1;
+ if (mCaps & NS_HTTP_ALLOW_PIPELINING) {
+ //
+ // disable pipelining if:
+ // (1) pipelining has been disabled by config
+ // (2) pipelining has been disabled by connection mgr info
+ // (3) request corresponds to a top-level document load (link click)
+ // (4) request method is non-idempotent
+ // (5) request is marked slow (e.g XHR)
+ //
+ nsAutoCString method;
+ mRequestHead.Method(method);
+ if (!mAllowPipelining ||
+ (mLoadFlags & (LOAD_INITIAL_DOCUMENT_URI | INHIBIT_PIPELINE)) ||
+ !SafeForPipelining(mRequestHead.ParsedMethod(), method)) {
+ LOG((" pipelining disallowed\n"));
+ mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
+ }
+ }
+
+ if (!mAllowSpdy) {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+ if (mBeConservative) {
+ mCaps |= NS_HTTP_BE_CONSERVATIVE;
+ }
+
+ // Use the URI path if not proxying (transparent proxying such as proxy
+ // CONNECT does not count here). Also figure out what HTTP version to use.
+ nsAutoCString buf, path;
+ nsCString* requestURI;
+
+ // This is the normal e2e H1 path syntax "/index.html"
+ rv = mURI->GetPath(path);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // path may contain UTF-8 characters, so ensure that they're escaped.
+ if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII, buf)) {
+ requestURI = &buf;
+ } else {
+ requestURI = &path;
+ }
+
+ // trim off the #ref portion if any...
+ int32_t ref1 = requestURI->FindChar('#');
+ if (ref1 != kNotFound) {
+ requestURI->SetLength(ref1);
+ }
+
+ if (mConnectionInfo->UsingConnect() || !mConnectionInfo->UsingHttpProxy()) {
+ mRequestHead.SetVersion(gHttpHandler->HttpVersion());
+ }
+ else {
+ mRequestHead.SetPath(*requestURI);
+
+ // RequestURI should be the absolute uri H1 proxy syntax "http://foo/index.html"
+ // so we will overwrite the relative version in requestURI
+ rv = mURI->GetUserPass(buf);
+ if (NS_FAILED(rv)) return rv;
+ if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
+ strncmp(mSpec.get(), "https:", 6) == 0)) {
+ nsCOMPtr<nsIURI> tempURI;
+ rv = mURI->Clone(getter_AddRefs(tempURI));
+ if (NS_FAILED(rv)) return rv;
+ rv = tempURI->SetUserPass(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+ rv = tempURI->GetAsciiSpec(path);
+ if (NS_FAILED(rv)) return rv;
+ requestURI = &path;
+ } else {
+ requestURI = &mSpec;
+ }
+
+ // trim off the #ref portion if any...
+ int32_t ref2 = requestURI->FindChar('#');
+ if (ref2 != kNotFound) {
+ requestURI->SetLength(ref2);
+ }
+
+ mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
+ }
+
+ mRequestHead.SetRequestURI(*requestURI);
+
+ // set the request time for cache expiration calculations
+ mRequestTime = NowInSeconds();
+ mRequestTimeInitialized = true;
+
+ // if doing a reload, force end-to-end
+ if (mLoadFlags & LOAD_BYPASS_CACHE) {
+ // We need to send 'Pragma:no-cache' to inhibit proxy caching even if
+ // no proxy is configured since we might be talking with a transparent
+ // proxy, i.e. one that operates at the network level. See bug #14772.
+ mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
+ // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
+ // no-cache'
+ if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
+ mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "no-cache", true);
+ }
+ else if ((mLoadFlags & VALIDATE_ALWAYS) && !mCacheEntryIsWriteOnly) {
+ // We need to send 'Cache-Control: max-age=0' to force each cache along
+ // the path to the origin server to revalidate its own entry, if any,
+ // with the next cache or server. See bug #84847.
+ //
+ // If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache'
+ if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
+ mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "max-age=0", true);
+ else
+ mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
+ }
+
+ if (mResuming) {
+ char byteRange[32];
+ SprintfLiteral(byteRange, "bytes=%" PRIu64 "-", mStartPos);
+ mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
+
+ if (!mEntityID.IsEmpty()) {
+ // Also, we want an error if this resource changed in the meantime
+ // Format of the entity id is: escaped_etag/size/lastmod
+ nsCString::const_iterator start, end, slash;
+ mEntityID.BeginReading(start);
+ mEntityID.EndReading(end);
+ mEntityID.BeginReading(slash);
+
+ if (FindCharInReadable('/', slash, end)) {
+ nsAutoCString ifMatch;
+ mRequestHead.SetHeader(nsHttp::If_Match,
+ NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
+
+ ++slash; // Incrementing, so that searching for '/' won't find
+ // the same slash again
+ }
+
+ if (FindCharInReadable('/', slash, end)) {
+ mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
+ Substring(++slash, end));
+ }
+ }
+ }
+
+ // create wrapper for this channel's notification callbacks
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+
+ // create the transaction object
+ mTransaction = new nsHttpTransaction();
+ LOG(("nsHttpChannel %p created nsHttpTransaction %p\n", this, mTransaction.get()));
+ mTransaction->SetTransactionObserver(mTransactionObserver);
+ mTransactionObserver = nullptr;
+
+ // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
+ if (mLoadFlags & LOAD_ANONYMOUS)
+ mCaps |= NS_HTTP_LOAD_ANONYMOUS;
+
+ if (mTimingEnabled)
+ mCaps |= NS_HTTP_TIMING_ENABLED;
+
+ if (mUpgradeProtocolCallback) {
+ mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false);
+ mRequestHead.SetHeaderOnce(nsHttp::Connection,
+ nsHttp::Upgrade.get(),
+ true);
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
+ mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
+ }
+
+ if (mPushedStream) {
+ mTransaction->SetPushedStream(mPushedStream);
+ mPushedStream = nullptr;
+ }
+
+ nsCOMPtr<nsIHttpPushListener> pushListener;
+ NS_QueryNotificationCallbacks(mCallbacks,
+ mLoadGroup,
+ NS_GET_IID(nsIHttpPushListener),
+ getter_AddRefs(pushListener));
+ if (pushListener) {
+ mCaps |= NS_HTTP_ONPUSH_LISTENER;
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> responseStream;
+ rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead,
+ mUploadStream, mUploadStreamHasHeaders,
+ NS_GetCurrentThread(), callbacks, this,
+ getter_AddRefs(responseStream));
+ if (NS_FAILED(rv)) {
+ mTransaction = nullptr;
+ return rv;
+ }
+
+ mTransaction->SetClassOfService(mClassOfService);
+ SetupTransactionRequestContext();
+
+ rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
+ responseStream);
+ return rv;
+}
+
+// NOTE: This function duplicates code from nsBaseChannel. This will go away
+// once HTTP uses nsBaseChannel (part of bug 312760)
+static void
+CallTypeSniffers(void *aClosure, const uint8_t *aData, uint32_t aCount)
+{
+ nsIChannel *chan = static_cast<nsIChannel*>(aClosure);
+
+ nsAutoCString newType;
+ NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, chan, aData, aCount, newType);
+ if (!newType.IsEmpty()) {
+ chan->SetContentType(newType);
+ }
+}
+
+// Helper Function to report messages to the console when loading
+// a resource was blocked due to a MIME type mismatch.
+void
+ReportTypeBlocking(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ const char* aMessageName)
+{
+ NS_ConvertUTF8toUTF16 specUTF16(aURI->GetSpecOrDefault());
+ const char16_t* params[] = { specUTF16.get() };
+ nsCOMPtr<nsIDocument> doc;
+ if (aLoadInfo) {
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ aLoadInfo->GetLoadingDocument(getter_AddRefs(domDoc));
+ if (domDoc) {
+ doc = do_QueryInterface(domDoc);
+ }
+ }
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("MIMEMISMATCH"),
+ doc,
+ nsContentUtils::eSECURITY_PROPERTIES,
+ aMessageName,
+ params, ArrayLength(params));
+}
+
+// Check and potentially enforce X-Content-Type-Options: nosniff
+nsresult
+ProcessXCTO(nsIURI* aURI, nsHttpResponseHead* aResponseHead, nsILoadInfo* aLoadInfo)
+{
+ if (!aURI || !aResponseHead || !aLoadInfo) {
+ // if there is no uri, no response head or no loadInfo, then there is nothing to do
+ return NS_OK;
+ }
+
+ // 1) Query the XCTO header and check if 'nosniff' is the first value.
+ nsAutoCString contentTypeOptionsHeader;
+ aResponseHead->GetHeader(nsHttp::X_Content_Type_Options, contentTypeOptionsHeader);
+ if (contentTypeOptionsHeader.IsEmpty()) {
+ // if there is no XCTO header, then there is nothing to do.
+ return NS_OK;
+ }
+ // XCTO header might contain multiple values which are comma separated, so:
+ // a) let's skip all subsequent values
+ // e.g. " NoSniFF , foo " will be " NoSniFF "
+ int32_t idx = contentTypeOptionsHeader.Find(",");
+ if (idx > 0) {
+ contentTypeOptionsHeader = Substring(contentTypeOptionsHeader, 0, idx);
+ }
+ // b) let's trim all surrounding whitespace
+ // e.g. " NoSniFF " -> "NoSniFF"
+ contentTypeOptionsHeader.StripWhitespace();
+ // c) let's compare the header (ignoring case)
+ // e.g. "NoSniFF" -> "nosniff"
+ // if it's not 'nosniff' then there is nothing to do here
+ if (!contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) {
+ // since we are getting here, the XCTO header was sent;
+ // a non matching value most likely means a mistake happenend;
+ // e.g. sending 'nosnif' instead of 'nosniff', let's log a warning.
+ NS_ConvertUTF8toUTF16 char16_header(contentTypeOptionsHeader);
+ const char16_t* params[] = { char16_header.get() };
+ nsCOMPtr<nsIDocument> doc;
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ aLoadInfo->GetLoadingDocument(getter_AddRefs(domDoc));
+ if (domDoc) {
+ doc = do_QueryInterface(domDoc);
+ }
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("XCTO"),
+ doc,
+ nsContentUtils::eSECURITY_PROPERTIES,
+ "XCTOHeaderValueMissing",
+ params, ArrayLength(params));
+ return NS_OK;
+ }
+
+ // 2) Query the content type from the channel
+ nsAutoCString contentType;
+ aResponseHead->ContentType(contentType);
+
+ // 3) Compare the expected MIME type with the actual type
+ if (aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_STYLESHEET) {
+ if (contentType.EqualsLiteral(TEXT_CSS)) {
+ return NS_OK;
+ }
+ ReportTypeBlocking(aURI, aLoadInfo, "MimeTypeMismatch");
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_IMAGE) {
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) {
+ Accumulate(Telemetry::XCTO_NOSNIFF_BLOCK_IMAGE, 0);
+ return NS_OK;
+ }
+ Accumulate(Telemetry::XCTO_NOSNIFF_BLOCK_IMAGE, 1);
+ // Instead of consulting Preferences::GetBool() all the time we
+ // can cache the result to speed things up.
+ static bool sXCTONosniffBlockImages = false;
+ static bool sIsInited = false;
+ if (!sIsInited) {
+ sIsInited = true;
+ Preferences::AddBoolVarCache(&sXCTONosniffBlockImages,
+ "security.xcto_nosniff_block_images");
+ }
+ if (!sXCTONosniffBlockImages) {
+ return NS_OK;
+ }
+ ReportTypeBlocking(aURI, aLoadInfo, "MimeTypeMismatch");
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_SCRIPT) {
+ if (nsContentUtils::IsScriptType(contentType)) {
+ return NS_OK;
+ }
+ ReportTypeBlocking(aURI, aLoadInfo, "MimeTypeMismatch");
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ return NS_OK;
+}
+
+// Ensure that a load of type script has correct MIME type
+nsresult
+EnsureMIMEOfScript(nsIURI* aURI, nsHttpResponseHead* aResponseHead, nsILoadInfo* aLoadInfo)
+{
+ if (!aURI || !aResponseHead || !aLoadInfo) {
+ // if there is no uri, no response head or no loadInfo, then there is nothing to do
+ return NS_OK;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_SCRIPT) {
+ // if this is not a script load, then there is nothing to do
+ return NS_OK;
+ }
+
+ nsAutoCString contentType;
+ aResponseHead->ContentType(contentType);
+ NS_ConvertUTF8toUTF16 typeString(contentType);
+
+ if (nsContentUtils::IsJavascriptMIMEType(typeString)) {
+ // script load has type script
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 1);
+ return NS_OK;
+ }
+
+ bool block = false;
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) {
+ // script load has type image
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 2);
+ block = true;
+ } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("audio/"))) {
+ // script load has type audio
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 3);
+ block = true;
+ } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("video/"))) {
+ // script load has type video
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 4);
+ block = true;
+ } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/csv"))) {
+ // script load has type text/csv
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 6);
+ block = true;
+ }
+
+ if (block) {
+ // Instead of consulting Preferences::GetBool() all the time we
+ // can cache the result to speed things up.
+ static bool sCachedBlockScriptWithWrongMime = false;
+ static bool sIsInited = false;
+ if (!sIsInited) {
+ sIsInited = true;
+ Preferences::AddBoolVarCache(&sCachedBlockScriptWithWrongMime,
+ "security.block_script_with_wrong_mime");
+ }
+
+ // Do not block the load if the feature is not enabled.
+ if (!sCachedBlockScriptWithWrongMime) {
+ return NS_OK;
+ }
+
+ ReportTypeBlocking(aURI, aLoadInfo, "BlockScriptWithWrongMimeType");
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/plain"))) {
+ // script load has type text/plain
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 5);
+ return NS_OK;
+ }
+
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/xml"))) {
+ // script load has type text/xml
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 7);
+ return NS_OK;
+ }
+
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("application/octet-stream"))) {
+ // script load has type application/octet-stream
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 8);
+ return NS_OK;
+ }
+
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("application/xml"))) {
+ // script load has type application/xml
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 9);
+ return NS_OK;
+ }
+
+ if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/html"))) {
+ // script load has type text/html
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 10);
+ return NS_OK;
+ }
+
+ if (contentType.IsEmpty()) {
+ // script load has no type
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 11);
+ return NS_OK;
+ }
+
+ // script load has unknown type
+ Telemetry::Accumulate(Telemetry::SCRIPT_BLOCK_INCORRECT_MIME, 0);
+ return NS_OK;
+}
+
+
+nsresult
+nsHttpChannel::CallOnStartRequest()
+{
+ MOZ_RELEASE_ASSERT(!(mRequireCORSPreflight &&
+ mInterceptCache != INTERCEPTED) ||
+ mIsCorsPreflightDone,
+ "CORS preflight must have been finished by the time we "
+ "call OnStartRequest");
+
+ nsresult rv = EnsureMIMEOfScript(mURI, mResponseHead, mLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ProcessXCTO(mURI, mResponseHead, mLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mOnStartRequestCalled) {
+ // This can only happen when a range request loading rest of the data
+ // after interrupted concurrent cache read asynchronously failed, e.g.
+ // the response range bytes are not as expected or this channel has
+ // been externally canceled.
+ //
+ // It's legal to bypass CallOnStartRequest for that case since we've
+ // already called OnStartRequest on our listener and also added all
+ // content converters before.
+ MOZ_ASSERT(mConcurrentCacheAccess);
+ LOG(("CallOnStartRequest already invoked before"));
+ return mStatus;
+ }
+
+ mTracingEnabled = false;
+
+ // Allow consumers to override our content type
+ if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
+ // NOTE: We can have both a txn pump and a cache pump when the cache
+ // content is partial. In that case, we need to read from the cache,
+ // because that's the one that has the initial contents. If that fails
+ // then give the transaction pump a shot.
+
+ nsIChannel* thisChannel = static_cast<nsIChannel*>(this);
+
+ bool typeSniffersCalled = false;
+ if (mCachePump) {
+ typeSniffersCalled =
+ NS_SUCCEEDED(mCachePump->PeekStream(CallTypeSniffers, thisChannel));
+ }
+
+ if (!typeSniffersCalled && mTransactionPump) {
+ mTransactionPump->PeekStream(CallTypeSniffers, thisChannel);
+ }
+ }
+
+ bool unknownDecoderStarted = false;
+ if (mResponseHead && !mResponseHead->HasContentType()) {
+ MOZ_ASSERT(mConnectionInfo, "Should have connection info here");
+ if (!mContentTypeHint.IsEmpty())
+ mResponseHead->SetContentType(mContentTypeHint);
+ else if (mResponseHead->Version() == NS_HTTP_VERSION_0_9 &&
+ mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort())
+ mResponseHead->SetContentType(NS_LITERAL_CSTRING(TEXT_PLAIN));
+ else {
+ // Uh-oh. We had better find out what type we are!
+ nsCOMPtr<nsIStreamConverterService> serv;
+ rv = gHttpHandler->
+ GetStreamConverterService(getter_AddRefs(serv));
+ // If we failed, we just fall through to the "normal" case
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIStreamListener> converter;
+ rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE,
+ "*/*",
+ mListener,
+ mListenerContext,
+ getter_AddRefs(converter));
+ if (NS_SUCCEEDED(rv)) {
+ mListener = converter;
+ unknownDecoderStarted = true;
+ }
+ }
+ }
+ }
+
+ if (mResponseHead && !mResponseHead->HasContentCharset())
+ mResponseHead->SetContentCharset(mContentCharsetHint);
+
+ if (mResponseHead && mCacheEntry) {
+ // If we have a cache entry, set its predicted size to TotalEntitySize to
+ // avoid caching an entry that will exceed the max size limit.
+ rv = mCacheEntry->SetPredictedDataSize(
+ mResponseHead->TotalEntitySize());
+ if (NS_ERROR_FILE_TOO_BIG == rv) {
+ // Don't throw the entry away, we will need it later.
+ LOG((" entry too big"));
+ } else {
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ LOG((" calling mListener->OnStartRequest\n"));
+ if (mListener) {
+ MOZ_ASSERT(!mOnStartRequestCalled,
+ "We should not call OsStartRequest twice");
+ nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
+ rv = deleteProtector->OnStartRequest(this, mListenerContext);
+ mOnStartRequestCalled = true;
+ if (NS_FAILED(rv))
+ return rv;
+ } else {
+ NS_WARNING("OnStartRequest skipped because of null listener");
+ mOnStartRequestCalled = true;
+ }
+
+ // Install stream converter if required.
+ // If we use unknownDecoder, stream converters will be installed later (in
+ // nsUnknownDecoder) after OnStartRequest is called for the real listener.
+ if (!unknownDecoderStarted) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsISupports *ctxt = mListenerContext;
+ rv = DoApplyContentConversions(mListener, getter_AddRefs(listener), ctxt);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (listener) {
+ mListener = listener;
+ mCompressListener = listener;
+ }
+ }
+
+ rv = EnsureAssocReq();
+ if (NS_FAILED(rv))
+ return rv;
+
+ // if this channel is for a download, close off access to the cache.
+ if (mCacheEntry && mChannelIsForDownload) {
+ mCacheEntry->AsyncDoom(nullptr);
+
+ // We must keep the cache entry in case of partial request.
+ // Concurrent access is the same, we need the entry in
+ // OnStopRequest.
+ if (!mCachedContentIsPartial && !mConcurrentCacheAccess)
+ CloseCacheEntry(false);
+ }
+
+ if (!mCanceled) {
+ // create offline cache entry if offline caching was requested
+ if (ShouldUpdateOfflineCacheEntry()) {
+ LOG(("writing to the offline cache"));
+ rv = InitOfflineCacheEntry();
+ if (NS_FAILED(rv)) return rv;
+
+ // InitOfflineCacheEntry may have closed mOfflineCacheEntry
+ if (mOfflineCacheEntry) {
+ rv = InstallOfflineCacheListener();
+ if (NS_FAILED(rv)) return rv;
+ }
+ } else if (mApplicationCacheForWrite) {
+ LOG(("offline cache is up to date, not updating"));
+ CloseOfflineCacheEntry();
+ }
+ }
+
+ // Check for a Content-Signature header and inject mediator if the header is
+ // requested and available.
+ // If requested (mLoadInfo->GetVerifySignedContent), but not present, or
+ // present but not valid, fail this channel and return
+ // NS_ERROR_INVALID_SIGNATURE to indicate a signature error and trigger a
+ // fallback load in nsDocShell.
+ // Note that OnStartRequest has already been called on the target stream
+ // listener at this point. We have to add the listener here that late to
+ // ensure that it's the last listener and can thus block the load in
+ // OnStopRequest.
+ if (!mCanceled) {
+ rv = ProcessContentSignatureHeader(mResponseHead);
+ if (NS_FAILED(rv)) {
+ LOG(("Content-signature verification failed.\n"));
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus)
+{
+ // Failure to set up a proxy tunnel via CONNECT means one of the following:
+ // 1) Proxy wants authorization, or forbids.
+ // 2) DNS at proxy couldn't resolve target URL.
+ // 3) Proxy connection to target failed or timed out.
+ // 4) Eve intercepted our CONNECT, and is replying with malicious HTML.
+ //
+ // Our current architecture would parse the proxy's response content with
+ // the permission of the target URL. Given #4, we must avoid rendering the
+ // body of the reply, and instead give the user a (hopefully helpful)
+ // boilerplate error page, based on just the HTTP status of the reply.
+
+ MOZ_ASSERT(mConnectionInfo->UsingConnect(),
+ "proxy connect failed but not using CONNECT?");
+ nsresult rv;
+ switch (httpStatus)
+ {
+ case 300: case 301: case 302: case 303: case 307: case 308:
+ // Bad redirect: not top-level, or it's a POST, bad/missing Location,
+ // or ProcessRedirect() failed for some other reason. Legal
+ // redirects that fail because site not available, etc., are handled
+ // elsewhere, in the regular codepath.
+ rv = NS_ERROR_CONNECTION_REFUSED;
+ break;
+ case 403: // HTTP/1.1: "Forbidden"
+ case 407: // ProcessAuthentication() failed
+ case 501: // HTTP/1.1: "Not Implemented"
+ // user sees boilerplate Mozilla "Proxy Refused Connection" page.
+ rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
+ break;
+ // Squid sends 404 if DNS fails (regular 404 from target is tunneled)
+ case 404: // HTTP/1.1: "Not Found"
+ // RFC 2616: "some deployed proxies are known to return 400 or 500 when
+ // DNS lookups time out." (Squid uses 500 if it runs out of sockets: so
+ // we have a conflict here).
+ case 400: // HTTP/1.1 "Bad Request"
+ case 500: // HTTP/1.1: "Internal Server Error"
+ /* User sees: "Address Not Found: Firefox can't find the server at
+ * www.foo.com."
+ */
+ rv = NS_ERROR_UNKNOWN_HOST;
+ break;
+ case 502: // HTTP/1.1: "Bad Gateway" (invalid resp from target server)
+ // Squid returns 503 if target request fails for anything but DNS.
+ case 503: // HTTP/1.1: "Service Unavailable"
+ /* User sees: "Failed to Connect:
+ * Firefox can't establish a connection to the server at
+ * www.foo.com. Though the site seems valid, the browser
+ * was unable to establish a connection."
+ */
+ rv = NS_ERROR_CONNECTION_REFUSED;
+ break;
+ // RFC 2616 uses 504 for both DNS and target timeout, so not clear what to
+ // do here: picking target timeout, as DNS covered by 400/404/500
+ case 504: // HTTP/1.1: "Gateway Timeout"
+ // user sees: "Network Timeout: The server at www.foo.com
+ // is taking too long to respond."
+ rv = NS_ERROR_NET_TIMEOUT;
+ break;
+ // Confused proxy server or malicious response
+ default:
+ rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
+ break;
+ }
+ LOG(("Cancelling failed proxy CONNECT [this=%p httpStatus=%u]\n",
+ this, httpStatus));
+ Cancel(rv);
+ CallOnStartRequest();
+ return rv;
+}
+
+static void
+GetSTSConsoleErrorTag(uint32_t failureResult, nsAString& consoleErrorTag)
+{
+ switch (failureResult) {
+ case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
+ consoleErrorTag = NS_LITERAL_STRING("STSUntrustworthyConnection");
+ break;
+ case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
+ consoleErrorTag = NS_LITERAL_STRING("STSCouldNotParseHeader");
+ break;
+ case nsISiteSecurityService::ERROR_NO_MAX_AGE:
+ consoleErrorTag = NS_LITERAL_STRING("STSNoMaxAge");
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
+ consoleErrorTag = NS_LITERAL_STRING("STSMultipleMaxAges");
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
+ consoleErrorTag = NS_LITERAL_STRING("STSInvalidMaxAge");
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
+ consoleErrorTag = NS_LITERAL_STRING("STSMultipleIncludeSubdomains");
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
+ consoleErrorTag = NS_LITERAL_STRING("STSInvalidIncludeSubdomains");
+ break;
+ case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
+ consoleErrorTag = NS_LITERAL_STRING("STSCouldNotSaveState");
+ break;
+ default:
+ consoleErrorTag = NS_LITERAL_STRING("STSUnknownError");
+ break;
+ }
+}
+
+static void
+GetPKPConsoleErrorTag(uint32_t failureResult, nsAString& consoleErrorTag)
+{
+ switch (failureResult) {
+ case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
+ consoleErrorTag = NS_LITERAL_STRING("PKPUntrustworthyConnection");
+ break;
+ case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
+ consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotParseHeader");
+ break;
+ case nsISiteSecurityService::ERROR_NO_MAX_AGE:
+ consoleErrorTag = NS_LITERAL_STRING("PKPNoMaxAge");
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
+ consoleErrorTag = NS_LITERAL_STRING("PKPMultipleMaxAges");
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
+ consoleErrorTag = NS_LITERAL_STRING("PKPInvalidMaxAge");
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
+ consoleErrorTag = NS_LITERAL_STRING("PKPMultipleIncludeSubdomains");
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
+ consoleErrorTag = NS_LITERAL_STRING("PKPInvalidIncludeSubdomains");
+ break;
+ case nsISiteSecurityService::ERROR_INVALID_PIN:
+ consoleErrorTag = NS_LITERAL_STRING("PKPInvalidPin");
+ break;
+ case nsISiteSecurityService::ERROR_MULTIPLE_REPORT_URIS:
+ consoleErrorTag = NS_LITERAL_STRING("PKPMultipleReportURIs");
+ break;
+ case nsISiteSecurityService::ERROR_PINSET_DOES_NOT_MATCH_CHAIN:
+ consoleErrorTag = NS_LITERAL_STRING("PKPPinsetDoesNotMatch");
+ break;
+ case nsISiteSecurityService::ERROR_NO_BACKUP_PIN:
+ consoleErrorTag = NS_LITERAL_STRING("PKPNoBackupPin");
+ break;
+ case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
+ consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotSaveState");
+ break;
+ case nsISiteSecurityService::ERROR_ROOT_NOT_BUILT_IN:
+ consoleErrorTag = NS_LITERAL_STRING("PKPRootNotBuiltIn");
+ break;
+ default:
+ consoleErrorTag = NS_LITERAL_STRING("PKPUnknownError");
+ break;
+ }
+}
+
+/**
+ * Process a single security header. Only two types are supported: HSTS and HPKP.
+ */
+nsresult
+nsHttpChannel::ProcessSingleSecurityHeader(uint32_t aType,
+ nsISSLStatus *aSSLStatus,
+ uint32_t aFlags)
+{
+ nsHttpAtom atom;
+ switch (aType) {
+ case nsISiteSecurityService::HEADER_HSTS:
+ atom = nsHttp::ResolveAtom("Strict-Transport-Security");
+ break;
+ case nsISiteSecurityService::HEADER_HPKP:
+ atom = nsHttp::ResolveAtom("Public-Key-Pins");
+ break;
+ default:
+ NS_NOTREACHED("Invalid security header type");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString securityHeader;
+ nsresult rv = mResponseHead->GetHeader(atom, securityHeader);
+ if (NS_SUCCEEDED(rv)) {
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+ // Process header will now discard the headers itself if the channel
+ // wasn't secure (whereas before it had to be checked manually)
+ uint32_t failureResult;
+ rv = sss->ProcessHeader(aType, mURI, securityHeader.get(), aSSLStatus,
+ aFlags, nullptr, nullptr, &failureResult);
+ if (NS_FAILED(rv)) {
+ nsAutoString consoleErrorCategory;
+ nsAutoString consoleErrorTag;
+ switch (aType) {
+ case nsISiteSecurityService::HEADER_HSTS:
+ GetSTSConsoleErrorTag(failureResult, consoleErrorTag);
+ consoleErrorCategory = NS_LITERAL_STRING("Invalid HSTS Headers");
+ break;
+ case nsISiteSecurityService::HEADER_HPKP:
+ GetPKPConsoleErrorTag(failureResult, consoleErrorTag);
+ consoleErrorCategory = NS_LITERAL_STRING("Invalid HPKP Headers");
+ break;
+ default:
+ return NS_ERROR_FAILURE;
+ }
+ AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
+ LOG(("nsHttpChannel: Failed to parse %s header, continuing load.\n",
+ atom.get()));
+ }
+ } else {
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ // All other errors are fatal
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ LOG(("nsHttpChannel: No %s header, continuing load.\n",
+ atom.get()));
+ }
+ return NS_OK;
+}
+
+/**
+ * Decide whether or not to remember Strict-Transport-Security, and whether
+ * or not to enforce channel integrity.
+ *
+ * @return NS_ERROR_FAILURE if there's security information missing even though
+ * it's an HTTPS connection.
+ */
+nsresult
+nsHttpChannel::ProcessSecurityHeaders()
+{
+ nsresult rv;
+ bool isHttps = false;
+ rv = mURI->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If this channel is not loading securely, STS or PKP doesn't do anything.
+ // In the case of HSTS, the upgrade to HTTPS takes place earlier in the
+ // channel load process.
+ if (!isHttps)
+ return NS_OK;
+
+ nsAutoCString asciiHost;
+ rv = mURI->GetAsciiHost(asciiHost);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ // If the channel is not a hostname, but rather an IP, do not process STS
+ // or PKP headers
+ PRNetAddr hostAddr;
+ if (PR_SUCCESS == PR_StringToNetAddr(asciiHost.get(), &hostAddr))
+ return NS_OK;
+
+ // mSecurityInfo may not always be present, and if it's not then it is okay
+ // to just disregard any security headers since we know nothing about the
+ // security of the connection.
+ NS_ENSURE_TRUE(mSecurityInfo, NS_OK);
+
+ uint32_t flags =
+ NS_UsePrivateBrowsing(this) ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
+
+ // Get the SSLStatus
+ nsCOMPtr<nsISSLStatusProvider> sslprov = do_QueryInterface(mSecurityInfo);
+ NS_ENSURE_TRUE(sslprov, NS_ERROR_FAILURE);
+ nsCOMPtr<nsISSLStatus> sslStatus;
+ rv = sslprov->GetSSLStatus(getter_AddRefs(sslStatus));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(sslStatus, NS_ERROR_FAILURE);
+
+ rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HSTS,
+ sslStatus, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HPKP,
+ sslStatus, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::ProcessContentSignatureHeader(nsHttpResponseHead *aResponseHead)
+{
+ nsresult rv = NS_OK;
+
+ // we only do this if we require it in loadInfo
+ if (!mLoadInfo || !mLoadInfo->GetVerifySignedContent()) {
+ return NS_OK;
+ }
+
+ // check if we verify content signatures on this newtab channel
+ if (gHttpHandler->NewTabContentSignaturesDisabled()) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(aResponseHead, NS_ERROR_ABORT);
+ nsAutoCString contentSignatureHeader;
+ nsHttpAtom atom = nsHttp::ResolveAtom("Content-Signature");
+ rv = aResponseHead->GetHeader(atom, contentSignatureHeader);
+ if (NS_FAILED(rv)) {
+ LOG(("Content-Signature header is missing but expected."));
+ DoInvalidateCacheEntry(mURI);
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ // if we require a signature but it is empty, fail
+ if (contentSignatureHeader.IsEmpty()) {
+ DoInvalidateCacheEntry(mURI);
+ LOG(("An expected content-signature header is missing.\n"));
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ // we ensure a content type here to avoid running into problems with
+ // content sniffing, which might sniff parts of the content before we can
+ // verify the signature
+ if (!aResponseHead->HasContentType()) {
+ NS_WARNING("Empty content type can get us in trouble when verifying "
+ "content signatures");
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+ // create a new listener that meadiates the content
+ RefPtr<ContentVerifier> contentVerifyingMediator =
+ new ContentVerifier(mListener, mListenerContext);
+ rv = contentVerifyingMediator->Init(contentSignatureHeader, this,
+ mListenerContext);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
+ mListener = contentVerifyingMediator;
+
+ return NS_OK;
+}
+
+/**
+ * Decide whether or not to send a security report and, if so, give the
+ * SecurityReporter the information required to send such a report.
+ */
+void
+nsHttpChannel::ProcessSecurityReport(nsresult status) {
+ uint32_t errorClass;
+ nsCOMPtr<nsINSSErrorsService> errSvc =
+ do_GetService("@mozilla.org/nss_errors_service;1");
+ // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
+ // not in the set of errors covered by the NSS errors service.
+ nsresult rv = errSvc->GetErrorClass(status, &errorClass);
+ if (!NS_SUCCEEDED(rv)) {
+ return;
+ }
+
+ // if the content was not loaded succesfully and we have security info,
+ // send a TLS error report - we must do this early as other parts of
+ // OnStopRequest can return early
+ bool reportingEnabled =
+ Preferences::GetBool("security.ssl.errorReporting.enabled");
+ bool reportingAutomatic =
+ Preferences::GetBool("security.ssl.errorReporting.automatic");
+ if (!mSecurityInfo || !reportingEnabled || !reportingAutomatic) {
+ return;
+ }
+
+ nsCOMPtr<nsITransportSecurityInfo> secInfo =
+ do_QueryInterface(mSecurityInfo);
+ nsCOMPtr<nsISecurityReporter> errorReporter =
+ do_GetService("@mozilla.org/securityreporter;1");
+
+ if (!secInfo || !mURI) {
+ return;
+ }
+
+ nsAutoCString hostStr;
+ int32_t port;
+ rv = mURI->GetHost(hostStr);
+ if (!NS_SUCCEEDED(rv)) {
+ return;
+ }
+
+ rv = mURI->GetPort(&port);
+
+ if (NS_SUCCEEDED(rv)) {
+ errorReporter->ReportTLSError(secInfo, hostStr, port);
+ }
+}
+
+bool
+nsHttpChannel::IsHTTPS()
+{
+ bool isHttps;
+ if (NS_FAILED(mURI->SchemeIs("https", &isHttps)) || !isHttps)
+ return false;
+ return true;
+}
+
+void
+nsHttpChannel::ProcessSSLInformation()
+{
+ // If this is HTTPS, record any use of RSA so that Key Exchange Algorithm
+ // can be whitelisted for TLS False Start in future sessions. We could
+ // do the same for DH but its rarity doesn't justify the lookup.
+
+ if (mCanceled || NS_FAILED(mStatus) || !mSecurityInfo ||
+ !IsHTTPS() || mPrivateBrowsing)
+ return;
+
+ nsCOMPtr<nsISSLStatusProvider> statusProvider =
+ do_QueryInterface(mSecurityInfo);
+ if (!statusProvider)
+ return;
+ nsCOMPtr<nsISSLStatus> sslstat;
+ statusProvider->GetSSLStatus(getter_AddRefs(sslstat));
+ if (!sslstat)
+ return;
+
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo =
+ do_QueryInterface(mSecurityInfo);
+ uint32_t state;
+ if (securityInfo &&
+ NS_SUCCEEDED(securityInfo->GetSecurityState(&state)) &&
+ (state & nsIWebProgressListener::STATE_IS_BROKEN)) {
+ // Send weak crypto warnings to the web console
+ if (state & nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) {
+ nsString consoleErrorTag = NS_LITERAL_STRING("WeakCipherSuiteWarning");
+ nsString consoleErrorCategory = NS_LITERAL_STRING("SSL");
+ AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
+ }
+ }
+
+ // Send (SHA-1) signature algorithm errors to the web console
+ nsCOMPtr<nsIX509Cert> cert;
+ sslstat->GetServerCert(getter_AddRefs(cert));
+ if (cert) {
+ UniqueCERTCertificate nssCert(cert->GetCert());
+ if (nssCert) {
+ SECOidTag tag = SECOID_GetAlgorithmTag(&nssCert->signature);
+ LOG(("Checking certificate signature: The OID tag is %i [this=%p]\n", tag, this));
+ // Check to see if the signature is sha-1 based.
+ // Not including checks for SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE
+ // from http://tools.ietf.org/html/rfc2437#section-8 since I
+ // can't see reference to it outside this spec
+ if (tag == SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION ||
+ tag == SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST ||
+ tag == SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE) {
+ nsString consoleErrorTag = NS_LITERAL_STRING("SHA1Sig");
+ nsString consoleErrorMessage
+ = NS_LITERAL_STRING("SHA-1 Signature");
+ AddSecurityMessage(consoleErrorTag, consoleErrorMessage);
+ }
+ }
+ }
+}
+
+void
+nsHttpChannel::ProcessAltService()
+{
+ // e.g. Alt-Svc: h2=":443"; ma=60
+ // e.g. Alt-Svc: h2="otherhost:443"
+ // Alt-Svc = 1#( alternative *( OWS ";" OWS parameter ) )
+ // alternative = protocol-id "=" alt-authority
+ // protocol-id = token ; percent-encoded ALPN protocol identifier
+ // alt-authority = quoted-string ; containing [ uri-host ] ":" port
+
+ if (!mAllowAltSvc) { // per channel opt out
+ return;
+ }
+
+ if (!gHttpHandler->AllowAltSvc() || (mCaps & NS_HTTP_DISALLOW_SPDY)) {
+ return;
+ }
+
+ nsAutoCString scheme;
+ mURI->GetScheme(scheme);
+ bool isHttp = scheme.Equals(NS_LITERAL_CSTRING("http"));
+ if (!isHttp && !scheme.Equals(NS_LITERAL_CSTRING("https"))) {
+ return;
+ }
+
+ nsAutoCString altSvc;
+ mResponseHead->GetHeader(nsHttp::Alternate_Service, altSvc);
+ if (altSvc.IsEmpty()) {
+ return;
+ }
+
+ if (!nsHttp::IsReasonableHeaderValue(altSvc)) {
+ LOG(("Alt-Svc Response Header seems unreasonable - skipping\n"));
+ return;
+ }
+
+ nsAutoCString originHost;
+ int32_t originPort = 80;
+ mURI->GetPort(&originPort);
+ if (NS_FAILED(mURI->GetHost(originHost))) {
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(callbacks));
+ if (mProxyInfo) {
+ proxyInfo = do_QueryInterface(mProxyInfo);
+ }
+
+ NeckoOriginAttributes originAttributes;
+ NS_GetOriginAttributes(this, originAttributes);
+
+ AltSvcMapping::ProcessHeader(altSvc, scheme, originHost, originPort,
+ mUsername, mPrivateBrowsing, callbacks, proxyInfo,
+ mCaps & NS_HTTP_DISALLOW_SPDY,
+ originAttributes);
+}
+
+nsresult
+nsHttpChannel::ProcessResponse()
+{
+ uint32_t httpStatus = mResponseHead->Status();
+
+ LOG(("nsHttpChannel::ProcessResponse [this=%p httpStatus=%u]\n",
+ this, httpStatus));
+
+ // do some telemetry
+ if (gHttpHandler->IsTelemetryEnabled()) {
+ // Gather data on whether the transaction and page (if this is
+ // the initial page load) is being loaded with SSL.
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_IS_SSL,
+ mConnectionInfo->EndToEndSSL());
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+ Telemetry::Accumulate(Telemetry::HTTP_PAGELOAD_IS_SSL,
+ mConnectionInfo->EndToEndSSL());
+ }
+
+ // how often do we see something like Alternate-Protocol: "443:quic,p=1"
+ nsAutoCString alt_protocol;
+ mResponseHead->GetHeader(nsHttp::Alternate_Protocol, alt_protocol);
+ bool saw_quic = (!alt_protocol.IsEmpty() &&
+ PL_strstr(alt_protocol.get(), "quic")) ? 1 : 0;
+ Telemetry::Accumulate(Telemetry::HTTP_SAW_QUIC_ALT_PROTOCOL, saw_quic);
+
+ // Gather data on how many URLS get redirected
+ switch (httpStatus) {
+ case 200:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 0);
+ break;
+ case 301:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 1);
+ break;
+ case 302:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 2);
+ break;
+ case 304:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 3);
+ break;
+ case 307:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 4);
+ break;
+ case 308:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 5);
+ break;
+ case 400:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 6);
+ break;
+ case 401:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 7);
+ break;
+ case 403:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 8);
+ break;
+ case 404:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 9);
+ break;
+ case 500:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 10);
+ break;
+ default:
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 11);
+ break;
+ }
+ }
+
+ // Let the predictor know whether this was a cacheable response or not so
+ // that it knows whether or not to possibly prefetch this resource in the
+ // future.
+ // We use GetReferringPage because mReferrer may not be set at all, or may
+ // not be a full URI (HttpBaseChannel::SetReferrer has the gorey details).
+ // If that's null, though, we'll fall back to mReferrer just in case (this
+ // is especially useful in xpcshell tests, where we don't have an actual
+ // pageload to get a referrer from).
+ nsCOMPtr<nsIURI> referrer = GetReferringPage();
+ if (!referrer) {
+ referrer = mReferrer;
+ }
+ if (referrer) {
+ nsCOMPtr<nsILoadContextInfo> lci = GetLoadContextInfo(this);
+ mozilla::net::Predictor::UpdateCacheability(referrer, mURI, httpStatus,
+ mRequestHead, mResponseHead,
+ lci);
+ }
+
+ if (mTransaction->ProxyConnectFailed()) {
+ // Only allow 407 (authentication required) to continue
+ if (httpStatus != 407)
+ return ProcessFailedProxyConnect(httpStatus);
+ // If proxy CONNECT response needs to complete, wait to process connection
+ // for Strict-Transport-Security.
+ } else {
+ // Given a successful connection, process any STS or PKP data that's
+ // relevant.
+ DebugOnly<nsresult> rv = ProcessSecurityHeaders();
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load.");
+ }
+
+ MOZ_ASSERT(!mCachedContentIsValid);
+
+ ProcessSSLInformation();
+
+ // notify "http-on-examine-response" observers
+ gHttpHandler->OnExamineResponse(this);
+
+ return ContinueProcessResponse1();
+}
+
+void
+nsHttpChannel::AsyncContinueProcessResponse()
+{
+ nsresult rv;
+ rv = ContinueProcessResponse1();
+ if (NS_FAILED(rv)) {
+ // A synchronous failure here would normally be passed as the return
+ // value from OnStartRequest, which would in turn cancel the request.
+ // If we're continuing asynchronously, we need to cancel the request
+ // ourselves.
+ Unused << Cancel(rv);
+ }
+}
+
+nsresult
+nsHttpChannel::ContinueProcessResponse1()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+ nsresult rv;
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to finish processing response [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::AsyncContinueProcessResponse;
+ return NS_OK;
+ }
+
+ uint32_t httpStatus = mResponseHead->Status();
+
+ // Cookies and Alt-Service should not be handled on proxy failure either.
+ // This would be consolidated with ProcessSecurityHeaders but it should
+ // happen after OnExamineResponse.
+ if (!mTransaction->ProxyConnectFailed() && (httpStatus != 407)) {
+ nsAutoCString cookie;
+ if (NS_SUCCEEDED(mResponseHead->GetHeader(nsHttp::Set_Cookie, cookie))) {
+ SetCookie(cookie.get());
+ }
+ if ((httpStatus < 500) && (httpStatus != 421)) {
+ ProcessAltService();
+ }
+ }
+
+ if (mConcurrentCacheAccess && mCachedContentIsPartial && httpStatus != 206) {
+ LOG((" only expecting 206 when doing partial request during "
+ "interrupted cache concurrent read"));
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ // handle unused username and password in url (see bug 232567)
+ if (httpStatus != 401 && httpStatus != 407) {
+ if (!mAuthRetryPending)
+ mAuthProvider->CheckForSuperfluousAuth();
+ if (mCanceled)
+ return CallOnStartRequest();
+
+ // reset the authentication's current continuation state because our
+ // last authentication attempt has been completed successfully
+ mAuthProvider->Disconnect(NS_ERROR_ABORT);
+ mAuthProvider = nullptr;
+ LOG((" continuation state has been reset"));
+ }
+
+ if (mAPIRedirectToURI && !mCanceled) {
+ MOZ_ASSERT(!mOnStartRequestCalled);
+ nsCOMPtr<nsIURI> redirectTo;
+ mAPIRedirectToURI.swap(redirectTo);
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse2);
+ rv = StartRedirectChannelToURI(redirectTo, nsIChannelEventSink::REDIRECT_TEMPORARY);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse2);
+ }
+
+ // Hack: ContinueProcessResponse2 uses NS_OK to detect successful
+ // redirects, so we distinguish this codepath (a non-redirect that's
+ // processing normally) by passing in a bogus error code.
+ return ContinueProcessResponse2(NS_BINDING_FAILED);
+}
+
+nsresult
+nsHttpChannel::ContinueProcessResponse2(nsresult rv)
+{
+ if (NS_SUCCEEDED(rv)) {
+ // redirectTo() has passed through, we don't want to go on with
+ // this channel. It will now be canceled by the redirect handling
+ // code that called this function.
+ return NS_OK;
+ }
+
+ rv = NS_OK;
+
+ uint32_t httpStatus = mResponseHead->Status();
+
+ bool successfulReval = false;
+
+ // handle different server response categories. Note that we handle
+ // caching or not caching of error pages in
+ // nsHttpResponseHead::MustValidate; if you change this switch, update that
+ // one
+ switch (httpStatus) {
+ case 200:
+ case 203:
+ // Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
+ // So if a server does that and sends 200 instead of 206 that we
+ // expect, notify our caller.
+ // However, if we wanted to start from the beginning, let it go through
+ if (mResuming && mStartPos != 0) {
+ LOG(("Server ignored our Range header, cancelling [this=%p]\n", this));
+ Cancel(NS_ERROR_NOT_RESUMABLE);
+ rv = CallOnStartRequest();
+ break;
+ }
+ // these can normally be cached
+ rv = ProcessNormal();
+ MaybeInvalidateCacheEntryForSubsequentGet();
+ break;
+ case 206:
+ if (mCachedContentIsPartial) // an internal byte range request...
+ rv = ProcessPartialContent();
+ else {
+ mCacheInputStream.CloseAndRelease();
+ rv = ProcessNormal();
+ }
+ break;
+ case 300:
+ case 301:
+ case 302:
+ case 307:
+ case 308:
+ case 303:
+#if 0
+ case 305: // disabled as a security measure (see bug 187996).
+#endif
+ // don't store the response body for redirects
+ MaybeInvalidateCacheEntryForSubsequentGet();
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
+ rv = AsyncProcessRedirection(httpStatus);
+ if (NS_FAILED(rv)) {
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
+ LOG(("AsyncProcessRedirection failed [rv=%x]\n", rv));
+ // don't cache failed redirect responses.
+ if (mCacheEntry)
+ mCacheEntry->AsyncDoom(nullptr);
+ if (DoNotRender3xxBody(rv)) {
+ mStatus = rv;
+ DoNotifyListener();
+ } else {
+ rv = ContinueProcessResponse3(rv);
+ }
+ }
+ break;
+ case 304:
+ if (!ShouldBypassProcessNotModified()) {
+ rv = ProcessNotModified();
+ if (NS_SUCCEEDED(rv)) {
+ successfulReval = true;
+ break;
+ }
+
+ LOG(("ProcessNotModified failed [rv=%x]\n", rv));
+
+ // We cannot read from the cache entry, it might be in an
+ // incosistent state. Doom it and redirect the channel
+ // to the same URI to reload from the network.
+ mCacheInputStream.CloseAndRelease();
+ if (mCacheEntry) {
+ mCacheEntry->AsyncDoom(nullptr);
+ mCacheEntry = nullptr;
+ }
+
+ rv = StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ }
+
+ if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
+ rv = ProcessNormal();
+ }
+ break;
+ case 401:
+ case 407:
+ if (MOZ_UNLIKELY(mCustomAuthHeader) && httpStatus == 401) {
+ // When a custom auth header fails, we don't want to try
+ // any cached credentials, nor we want to ask the user.
+ // It's up to the consumer to re-try w/o setting a custom
+ // auth header if cached credentials should be attempted.
+ rv = NS_ERROR_FAILURE;
+ } else {
+ rv = mAuthProvider->ProcessAuthentication(
+ httpStatus,
+ mConnectionInfo->EndToEndSSL() && mTransaction->ProxyConnectFailed());
+ }
+ if (rv == NS_ERROR_IN_PROGRESS) {
+ // authentication prompt has been invoked and result
+ // is expected asynchronously
+ mAuthRetryPending = true;
+ if (httpStatus == 407 || mTransaction->ProxyConnectFailed())
+ mProxyAuthPending = true;
+
+ // suspend the transaction pump to stop receiving the
+ // unauthenticated content data. We will throw that data
+ // away when user provides credentials or resume the pump
+ // when user refuses to authenticate.
+ LOG(("Suspending the transaction, asynchronously prompting for credentials"));
+ mTransactionPump->Suspend();
+ rv = NS_OK;
+ } else if (NS_FAILED(rv)) {
+ LOG(("ProcessAuthentication failed [rv=%x]\n", rv));
+ if (mTransaction->ProxyConnectFailed())
+ return ProcessFailedProxyConnect(httpStatus);
+ if (!mAuthRetryPending)
+ mAuthProvider->CheckForSuperfluousAuth();
+ rv = ProcessNormal();
+ } else {
+ mAuthRetryPending = true; // see DoAuthRetry
+ }
+ break;
+ default:
+ rv = ProcessNormal();
+ MaybeInvalidateCacheEntryForSubsequentGet();
+ break;
+ }
+
+ if (gHttpHandler->IsTelemetryEnabled()) {
+ CacheDisposition cacheDisposition;
+ if (!mDidReval) {
+ cacheDisposition = kCacheMissed;
+ } else if (successfulReval) {
+ cacheDisposition = kCacheHitViaReval;
+ } else {
+ cacheDisposition = kCacheMissedViaReval;
+ }
+ AccumulateCacheHitTelemetry(cacheDisposition);
+
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_VERSION,
+ mResponseHead->Version());
+
+ if (mResponseHead->Version() == NS_HTTP_VERSION_0_9) {
+ // DefaultPortTopLevel = 0, DefaultPortSubResource = 1,
+ // NonDefaultPortTopLevel = 2, NonDefaultPortSubResource = 3
+ uint32_t v09Info = 0;
+ if (!(mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)) {
+ v09Info += 1;
+ }
+ if (mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort()) {
+ v09Info += 2;
+ }
+ Telemetry::Accumulate(Telemetry::HTTP_09_INFO, v09Info);
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsHttpChannel::ContinueProcessResponse3(nsresult rv)
+{
+ bool doNotRender = DoNotRender3xxBody(rv);
+
+ if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) {
+ bool isHTTP = false;
+ if (NS_FAILED(mRedirectURI->SchemeIs("http", &isHTTP)))
+ isHTTP = false;
+ if (!isHTTP && NS_FAILED(mRedirectURI->SchemeIs("https", &isHTTP)))
+ isHTTP = false;
+
+ if (!isHTTP) {
+ // This was a blocked attempt to redirect and subvert the system by
+ // redirecting to another protocol (perhaps javascript:)
+ // In that case we want to throw an error instead of displaying the
+ // non-redirected response body.
+ LOG(("ContinueProcessResponse3 detected rejected Non-HTTP Redirection"));
+ doNotRender = true;
+ rv = NS_ERROR_CORRUPTED_CONTENT;
+ }
+ }
+
+ if (doNotRender) {
+ Cancel(rv);
+ DoNotifyListener();
+ return rv;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ UpdateInhibitPersistentCachingFlag();
+
+ InitCacheEntry();
+ CloseCacheEntry(false);
+
+ if (mApplicationCacheForWrite) {
+ // Store response in the offline cache
+ InitOfflineCacheEntry();
+ CloseOfflineCacheEntry();
+ }
+ return NS_OK;
+ }
+
+ LOG(("ContinueProcessResponse3 got failure result [rv=%x]\n", rv));
+ if (mTransaction && mTransaction->ProxyConnectFailed()) {
+ return ProcessFailedProxyConnect(mRedirectType);
+ }
+ return ProcessNormal();
+}
+
+nsresult
+nsHttpChannel::ProcessNormal()
+{
+ nsresult rv;
+
+ LOG(("nsHttpChannel::ProcessNormal [this=%p]\n", this));
+
+ bool succeeded;
+ rv = GetRequestSucceeded(&succeeded);
+ if (NS_SUCCEEDED(rv) && !succeeded) {
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
+ bool waitingForRedirectCallback;
+ (void)ProcessFallback(&waitingForRedirectCallback);
+ if (waitingForRedirectCallback) {
+ // The transaction has been suspended by ProcessFallback.
+ return NS_OK;
+ }
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
+ }
+
+ return ContinueProcessNormal(NS_OK);
+}
+
+nsresult
+nsHttpChannel::ContinueProcessNormal(nsresult rv)
+{
+ if (NS_FAILED(rv)) {
+ // Fill the failure status here, we have failed to fall back, thus we
+ // have to report our status as failed.
+ mStatus = rv;
+ DoNotifyListener();
+ return rv;
+ }
+
+ if (mFallingBack) {
+ // Do not continue with normal processing, fallback is in
+ // progress now.
+ return NS_OK;
+ }
+
+ // if we're here, then any byte-range requests failed to result in a partial
+ // response. we must clear this flag to prevent BufferPartialContent from
+ // being called inside our OnDataAvailable (see bug 136678).
+ mCachedContentIsPartial = false;
+
+ ClearBogusContentEncodingIfNeeded();
+
+ UpdateInhibitPersistentCachingFlag();
+
+ // this must be called before firing OnStartRequest, since http clients,
+ // such as imagelib, expect our cache entry to already have the correct
+ // expiration time (bug 87710).
+ if (mCacheEntry) {
+ rv = InitCacheEntry();
+ if (NS_FAILED(rv))
+ CloseCacheEntry(true);
+ }
+
+ // Check that the server sent us what we were asking for
+ if (mResuming) {
+ // Create an entity id from the response
+ nsAutoCString id;
+ rv = GetEntityID(id);
+ if (NS_FAILED(rv)) {
+ // If creating an entity id is not possible -> error
+ Cancel(NS_ERROR_NOT_RESUMABLE);
+ }
+ else if (mResponseHead->Status() != 206 &&
+ mResponseHead->Status() != 200) {
+ // Probably 404 Not Found, 412 Precondition Failed or
+ // 416 Invalid Range -> error
+ LOG(("Unexpected response status while resuming, aborting [this=%p]\n",
+ this));
+ Cancel(NS_ERROR_ENTITY_CHANGED);
+ }
+ // If we were passed an entity id, verify it's equal to the server's
+ else if (!mEntityID.IsEmpty()) {
+ if (!mEntityID.Equals(id)) {
+ LOG(("Entity mismatch, expected '%s', got '%s', aborting [this=%p]",
+ mEntityID.get(), id.get(), this));
+ Cancel(NS_ERROR_ENTITY_CHANGED);
+ }
+ }
+ }
+
+ rv = CallOnStartRequest();
+ if (NS_FAILED(rv)) return rv;
+
+ // install cache listener if we still have a cache entry open
+ if (mCacheEntry && !mCacheEntryIsReadOnly) {
+ rv = InstallCacheListener();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::PromptTempRedirect()
+{
+ if (!gHttpHandler->PromptTempRedirect()) {
+ return NS_OK;
+ }
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ rv = bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle));
+ if (NS_FAILED(rv)) return rv;
+
+ nsXPIDLString messageString;
+ rv = stringBundle->GetStringFromName(u"RepostFormData", getter_Copies(messageString));
+ // GetStringFromName can return NS_OK and nullptr messageString.
+ if (NS_SUCCEEDED(rv) && messageString) {
+ bool repost = false;
+
+ nsCOMPtr<nsIPrompt> prompt;
+ GetCallback(prompt);
+ if (!prompt)
+ return NS_ERROR_NO_INTERFACE;
+
+ prompt->Confirm(nullptr, messageString, &repost);
+ if (!repost)
+ return NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+nsresult
+nsHttpChannel::ProxyFailover()
+{
+ LOG(("nsHttpChannel::ProxyFailover [this=%p]\n", this));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus,
+ getter_AddRefs(pi));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // XXXbz so where does this codepath remove us from the loadgroup,
+ // exactly?
+ return AsyncDoReplaceWithProxy(pi);
+}
+
+void
+nsHttpChannel::HandleAsyncRedirectChannelToHttps()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async redirect to https [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::HandleAsyncRedirectChannelToHttps;
+ return;
+ }
+
+ nsresult rv = StartRedirectChannelToHttps();
+ if (NS_FAILED(rv))
+ ContinueAsyncRedirectChannelToURI(rv);
+}
+
+nsresult
+nsHttpChannel::StartRedirectChannelToHttps()
+{
+ LOG(("nsHttpChannel::HandleAsyncRedirectChannelToHttps() [STS]\n"));
+
+ nsCOMPtr<nsIURI> upgradedURI;
+ nsresult rv = NS_GetSecureUpgradedURI(mURI, getter_AddRefs(upgradedURI));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return StartRedirectChannelToURI(upgradedURI,
+ nsIChannelEventSink::REDIRECT_PERMANENT |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE);
+}
+
+void
+nsHttpChannel::HandleAsyncAPIRedirect()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+ NS_PRECONDITION(mAPIRedirectToURI, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async API redirect [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::HandleAsyncAPIRedirect;
+ return;
+ }
+
+ nsresult rv = StartRedirectChannelToURI(mAPIRedirectToURI,
+ nsIChannelEventSink::REDIRECT_PERMANENT);
+ if (NS_FAILED(rv))
+ ContinueAsyncRedirectChannelToURI(rv);
+
+ return;
+}
+
+nsresult
+nsHttpChannel::StartRedirectChannelToURI(nsIURI *upgradedURI, uint32_t flags)
+{
+ nsresult rv = NS_OK;
+ LOG(("nsHttpChannel::StartRedirectChannelToURI()\n"));
+
+ nsCOMPtr<nsIChannel> newChannel;
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel),
+ upgradedURI,
+ mLoadInfo,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL,
+ ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupReplacementChannel(upgradedURI, newChannel, true, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Inform consumers about this fake redirect
+ mRedirectChannel = newChannel;
+
+ if (!(flags & nsIChannelEventSink::REDIRECT_STS_UPGRADE) &&
+ mInterceptCache == INTERCEPTED) {
+ // Mark the channel as intercepted in order to propagate the response URL.
+ nsCOMPtr<nsIHttpChannelInternal> httpRedirect = do_QueryInterface(mRedirectChannel);
+ if (httpRedirect) {
+ httpRedirect->ForceIntercepted(mInterceptionID);
+ }
+ }
+
+ PushRedirectAsyncFunc(
+ &nsHttpChannel::ContinueAsyncRedirectChannelToURI);
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
+
+ if (NS_SUCCEEDED(rv))
+ rv = WaitForRedirectCallback();
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this);
+
+ /* Remove the async call to ContinueAsyncRedirectChannelToURI().
+ * It is called directly by our callers upon return (to clean up
+ * the failed redirect). */
+ PopRedirectAsyncFunc(
+ &nsHttpChannel::ContinueAsyncRedirectChannelToURI);
+ }
+
+ return rv;
+}
+
+nsresult
+nsHttpChannel::ContinueAsyncRedirectChannelToURI(nsresult rv)
+{
+ // Since we handle mAPIRedirectToURI also after on-examine-response handler
+ // rather drop it here to avoid any redirect loops, even just hypothetical.
+ mAPIRedirectToURI = nullptr;
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = OpenRedirectChannel(rv);
+ }
+
+ if (NS_FAILED(rv)) {
+ // Fill the failure status here, the update to https had been vetoed
+ // but from the security reasons we have to discard the whole channel
+ // load.
+ mStatus = rv;
+ }
+
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ }
+
+ if (NS_FAILED(rv)) {
+ // We have to manually notify the listener because there is not any pump
+ // that would call our OnStart/StopRequest after resume from waiting for
+ // the redirect callback.
+ DoNotifyListener();
+ }
+
+ return rv;
+}
+
+nsresult
+nsHttpChannel::OpenRedirectChannel(nsresult rv)
+{
+ AutoRedirectVetoNotifier notifier(this);
+
+ // Make sure to do this after we received redirect veto answer,
+ // i.e. after all sinks had been notified
+ mRedirectChannel->SetOriginalURI(mOriginalURI);
+
+ // And now, notify observers the deprecated way
+ nsCOMPtr<nsIHttpEventSink> httpEventSink;
+ GetCallback(httpEventSink);
+ if (httpEventSink) {
+ // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
+ // versions.
+ rv = httpEventSink->OnRedirect(this, mRedirectChannel);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ // open new channel
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
+ rv = mRedirectChannel->AsyncOpen2(mListener);
+ }
+ else {
+ rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = NS_BINDING_REDIRECTED;
+
+ notifier.RedirectSucceeded();
+
+ ReleaseListeners();
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo* pi)
+{
+ LOG(("nsHttpChannel::AsyncDoReplaceWithProxy [this=%p pi=%p]", this, pi));
+ nsresult rv;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = gHttpHandler->NewProxiedChannel2(mURI, pi, mProxyResolveFlags,
+ mProxyURI, mLoadInfo,
+ getter_AddRefs(newChannel));
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;
+
+ rv = SetupReplacementChannel(mURI, newChannel, true, flags);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Inform consumers about this fake redirect
+ mRedirectChannel = newChannel;
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
+
+ if (NS_SUCCEEDED(rv))
+ rv = WaitForRedirectCallback();
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this);
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
+ }
+
+ return rv;
+}
+
+nsresult
+nsHttpChannel::ContinueDoReplaceWithProxy(nsresult rv)
+{
+ AutoRedirectVetoNotifier notifier(this);
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
+
+ // Make sure to do this after we received redirect veto answer,
+ // i.e. after all sinks had been notified
+ mRedirectChannel->SetOriginalURI(mOriginalURI);
+
+ // open new channel
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
+ rv = mRedirectChannel->AsyncOpen2(mListener);
+ }
+ else {
+ rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = NS_BINDING_REDIRECTED;
+
+ notifier.RedirectSucceeded();
+
+ ReleaseListeners();
+
+ return rv;
+}
+
+nsresult
+nsHttpChannel::ResolveProxy()
+{
+ LOG(("nsHttpChannel::ResolveProxy [this=%p]\n", this));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // using the nsIProtocolProxyService2 allows a minor performance
+ // optimization, but if an add-on has only provided the original interface
+ // then it is ok to use that version.
+ nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
+ if (pps2) {
+ rv = pps2->AsyncResolve2(this, mProxyResolveFlags,
+ this, getter_AddRefs(mProxyRequest));
+ } else {
+ rv = pps->AsyncResolve(static_cast<nsIChannel*>(this), mProxyResolveFlags,
+ this, getter_AddRefs(mProxyRequest));
+ }
+
+ return rv;
+}
+
+bool
+nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry)
+{
+ nsresult rv;
+ nsAutoCString buf, metaKey;
+ mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
+ if (!buf.IsEmpty()) {
+ NS_NAMED_LITERAL_CSTRING(prefix, "request-");
+
+ // enumerate the elements of the Vary header...
+ char *val = buf.BeginWriting(); // going to munge buf
+ char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
+ while (token) {
+ LOG(("nsHttpChannel::ResponseWouldVary [channel=%p] " \
+ "processing %s\n",
+ this, token));
+ //
+ // if "*", then assume response would vary. technically speaking,
+ // "Vary: header, *" is not permitted, but we allow it anyways.
+ //
+ // We hash values of cookie-headers for the following reasons:
+ //
+ // 1- cookies can be very large in size
+ //
+ // 2- cookies may contain sensitive information. (for parity with
+ // out policy of not storing Set-cookie headers in the cache
+ // meta data, we likewise do not want to store cookie headers
+ // here.)
+ //
+ if (*token == '*')
+ return true; // if we encounter this, just get out of here
+
+ // build cache meta data key...
+ metaKey = prefix + nsDependentCString(token);
+
+ // check the last value of the given request header to see if it has
+ // since changed. if so, then indeed the cached response is invalid.
+ nsXPIDLCString lastVal;
+ entry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
+ LOG(("nsHttpChannel::ResponseWouldVary [channel=%p] "
+ "stored value = \"%s\"\n",
+ this, lastVal.get()));
+
+ // Look for value of "Cookie" in the request headers
+ nsHttpAtom atom = nsHttp::ResolveAtom(token);
+ nsAutoCString newVal;
+ bool hasHeader = NS_SUCCEEDED(mRequestHead.GetHeader(atom,
+ newVal));
+ if (!lastVal.IsEmpty()) {
+ // value for this header in cache, but no value in request
+ if (!hasHeader) {
+ return true; // yes - response would vary
+ }
+
+ // If this is a cookie-header, stored metadata is not
+ // the value itself but the hash. So we also hash the
+ // outgoing value here in order to compare the hashes
+ nsAutoCString hash;
+ if (atom == nsHttp::Cookie) {
+ rv = Hash(newVal.get(), hash);
+ // If hash failed, be conservative (the cached hash
+ // exists at this point) and claim response would vary
+ if (NS_FAILED(rv))
+ return true;
+ newVal = hash;
+
+ LOG(("nsHttpChannel::ResponseWouldVary [this=%p] " \
+ "set-cookie value hashed to %s\n",
+ this, newVal.get()));
+ }
+
+ if (!newVal.Equals(lastVal)) {
+ return true; // yes, response would vary
+ }
+
+ } else if (hasHeader) { // old value is empty, but newVal is set
+ return true;
+ }
+
+ // next token...
+ token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
+ }
+ }
+ return false;
+}
+
+// We need to have an implementation of this function just so that we can keep
+// all references to mCallOnResume of type nsHttpChannel: it's not OK in C++
+// to set a member function ptr to a base class function.
+void
+nsHttpChannel::HandleAsyncAbort()
+{
+ HttpAsyncAborter<nsHttpChannel>::HandleAsyncAbort();
+}
+
+
+nsresult
+nsHttpChannel::EnsureAssocReq()
+{
+ // Confirm Assoc-Req response header on pipelined transactions
+ // per draft-nottingham-http-pipeline-01.txt
+ // of the form: GET http://blah.com/foo/bar?qv
+ // return NS_OK as long as we don't find a violation
+ // (i.e. no header is ok, as are malformed headers, as are
+ // transactions that have not been pipelined (unless those have been
+ // opted in via pragma))
+
+ if (!mResponseHead)
+ return NS_OK;
+
+ nsAutoCString assoc_val;
+ if (NS_FAILED(mResponseHead->GetHeader(nsHttp::Assoc_Req, assoc_val))) {
+ return NS_OK;
+ }
+
+ if (!mTransaction || !mURI)
+ return NS_OK;
+
+ if (!mTransaction->PipelinePosition()) {
+ // "Pragma: X-Verify-Assoc-Req" can be used to verify even non pipelined
+ // transactions. It is used by test harness.
+
+ nsAutoCString pragma_val;
+ mResponseHead->GetHeader(nsHttp::Pragma, pragma_val);
+ if (pragma_val.IsEmpty() ||
+ !nsHttp::FindToken(pragma_val.get(), "X-Verify-Assoc-Req",
+ HTTP_HEADER_VALUE_SEPS))
+ return NS_OK;
+ }
+
+ char *method = net_FindCharNotInSet(assoc_val.get(), HTTP_LWS);
+ if (!method)
+ return NS_OK;
+
+ bool equals;
+ char *endofmethod;
+
+ char * assoc_valChar = nullptr;
+ endofmethod = net_FindCharInSet(method, HTTP_LWS);
+ if (endofmethod)
+ assoc_valChar = net_FindCharNotInSet(endofmethod, HTTP_LWS);
+ if (!assoc_valChar)
+ return NS_OK;
+
+ // check the method
+ nsAutoCString methodHead;
+ mRequestHead.Method(methodHead);
+ if ((((int32_t)methodHead.Length()) != (endofmethod - method)) ||
+ PL_strncmp(method,
+ methodHead.get(),
+ endofmethod - method)) {
+ LOG((" Assoc-Req failure Method %s", method));
+ if (mConnectionInfo)
+ gHttpHandler->ConnMgr()->
+ PipelineFeedbackInfo(mConnectionInfo,
+ nsHttpConnectionMgr::RedCorruptedContent,
+ nullptr, 0);
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ nsAutoString message
+ (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
+ nsAutoCString assoc_req;
+ mResponseHead->GetHeader(nsHttp::Assoc_Req, assoc_req);
+ AppendASCIItoUTF16(assoc_req, message);
+ message += NS_LITERAL_STRING(" expected method ");
+ AppendASCIItoUTF16(methodHead, message);
+ consoleService->LogStringMessage(message.get());
+ }
+
+ if (gHttpHandler->EnforceAssocReq())
+ return NS_ERROR_CORRUPTED_CONTENT;
+ return NS_OK;
+ }
+
+ // check the URL
+ nsCOMPtr<nsIURI> assoc_url;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(assoc_url), assoc_valChar)) ||
+ !assoc_url)
+ return NS_OK;
+
+ mURI->Equals(assoc_url, &equals);
+ if (!equals) {
+ LOG((" Assoc-Req failure URL %s", assoc_valChar));
+ if (mConnectionInfo)
+ gHttpHandler->ConnMgr()->
+ PipelineFeedbackInfo(mConnectionInfo,
+ nsHttpConnectionMgr::RedCorruptedContent,
+ nullptr, 0);
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ nsAutoString message
+ (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
+ nsAutoCString assoc_req;
+ mResponseHead->GetHeader(nsHttp::Assoc_Req, assoc_req);
+ AppendASCIItoUTF16(assoc_req, message);
+ message += NS_LITERAL_STRING(" expected URL ");
+ AppendASCIItoUTF16(mSpec.get(), message);
+ consoleService->LogStringMessage(message.get());
+ }
+
+ if (gHttpHandler->EnforceAssocReq())
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <byte-range>
+//-----------------------------------------------------------------------------
+
+bool
+nsHttpChannel::IsResumable(int64_t partialLen, int64_t contentLength,
+ bool ignoreMissingPartialLen) const
+{
+ bool hasContentEncoding =
+ mCachedResponseHead->HasHeader(nsHttp::Content_Encoding);
+
+ nsAutoCString etag;
+ mCachedResponseHead->GetHeader(nsHttp::ETag, etag);
+ bool hasWeakEtag = !etag.IsEmpty() &&
+ StringBeginsWith(etag, NS_LITERAL_CSTRING("W/"));
+
+ return (partialLen < contentLength) &&
+ (partialLen > 0 || ignoreMissingPartialLen) &&
+ !hasContentEncoding && !hasWeakEtag &&
+ mCachedResponseHead->IsResumable() &&
+ !mCustomConditionalRequest &&
+ !mCachedResponseHead->NoStore();
+}
+
+nsresult
+nsHttpChannel::MaybeSetupByteRangeRequest(int64_t partialLen, int64_t contentLength,
+ bool ignoreMissingPartialLen)
+{
+ // Be pesimistic
+ mIsPartialRequest = false;
+
+ if (!IsResumable(partialLen, contentLength, ignoreMissingPartialLen))
+ return NS_ERROR_NOT_RESUMABLE;
+
+ // looks like a partial entry we can reuse; add If-Range
+ // and Range headers.
+ nsresult rv = SetupByteRangeRequest(partialLen);
+ if (NS_FAILED(rv)) {
+ // Make the request unconditional again.
+ UntieByteRangeRequest();
+ }
+
+ return rv;
+}
+
+nsresult
+nsHttpChannel::SetupByteRangeRequest(int64_t partialLen)
+{
+ // cached content has been found to be partial, add necessary request
+ // headers to complete cache entry.
+
+ // use strongest validator available...
+ nsAutoCString val;
+ mCachedResponseHead->GetHeader(nsHttp::ETag, val);
+ if (val.IsEmpty())
+ mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
+ if (val.IsEmpty()) {
+ // if we hit this code it means mCachedResponseHead->IsResumable() is
+ // either broken or not being called.
+ NS_NOTREACHED("no cache validator");
+ mIsPartialRequest = false;
+ return NS_ERROR_FAILURE;
+ }
+
+ char buf[64];
+ SprintfLiteral(buf, "bytes=%" PRId64 "-", partialLen);
+
+ mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
+ mRequestHead.SetHeader(nsHttp::If_Range, val);
+ mIsPartialRequest = true;
+
+ return NS_OK;
+}
+
+void
+nsHttpChannel::UntieByteRangeRequest()
+{
+ mRequestHead.ClearHeader(nsHttp::Range);
+ mRequestHead.ClearHeader(nsHttp::If_Range);
+}
+
+nsresult
+nsHttpChannel::ProcessPartialContent()
+{
+ // ok, we've just received a 206
+ //
+ // we need to stream whatever data is in the cache out first, and then
+ // pick up whatever data is on the wire, writing it into the cache.
+
+ LOG(("nsHttpChannel::ProcessPartialContent [this=%p]\n", this));
+
+ NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
+
+ // Make sure to clear bogus content-encodings before looking at the header
+ ClearBogusContentEncodingIfNeeded();
+
+ // Check if the content-encoding we now got is different from the one we
+ // got before
+ nsAutoCString contentEncoding, cachedContentEncoding;
+ mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+ mCachedResponseHead->GetHeader(nsHttp::Content_Encoding,
+ cachedContentEncoding);
+ if (PL_strcasecmp(contentEncoding.get(), cachedContentEncoding.get())
+ != 0) {
+ Cancel(NS_ERROR_INVALID_CONTENT_ENCODING);
+ return CallOnStartRequest();
+ }
+
+ nsresult rv;
+
+ int64_t cachedContentLength = mCachedResponseHead->ContentLength();
+ int64_t entitySize = mResponseHead->TotalEntitySize();
+
+ nsAutoCString contentRange;
+ mResponseHead->GetHeader(nsHttp::Content_Range, contentRange);
+ LOG(("nsHttpChannel::ProcessPartialContent [this=%p trans=%p] "
+ "original content-length %lld, entity-size %lld, content-range %s\n",
+ this, mTransaction.get(), cachedContentLength, entitySize,
+ contentRange.get()));
+
+ if ((entitySize >= 0) && (cachedContentLength >= 0) &&
+ (entitySize != cachedContentLength)) {
+ LOG(("nsHttpChannel::ProcessPartialContent [this=%p] "
+ "206 has different total entity size than the content length "
+ "of the original partially cached entity.\n", this));
+
+ mCacheEntry->AsyncDoom(nullptr);
+ Cancel(NS_ERROR_CORRUPTED_CONTENT);
+ return CallOnStartRequest();
+ }
+
+ if (mConcurrentCacheAccess) {
+ // We started to read cached data sooner than its write has been done.
+ // But the concurrent write has not finished completely, so we had to
+ // do a range request. Now let the content coming from the network
+ // be presented to consumers and also stored to the cache entry.
+
+ rv = InstallCacheListener(mLogicalOffset);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mOfflineCacheEntry) {
+ rv = InstallOfflineCacheListener(mLogicalOffset);
+ if (NS_FAILED(rv)) return rv;
+ }
+ } else {
+ // suspend the current transaction
+ rv = mTransactionPump->Suspend();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // merge any new headers with the cached response headers
+ rv = mCachedResponseHead->UpdateHeaders(mResponseHead);
+ if (NS_FAILED(rv)) return rv;
+
+ // update the cached response head
+ nsAutoCString head;
+ mCachedResponseHead->Flatten(head, true);
+ rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // make the cached response be the current response
+ mResponseHead = Move(mCachedResponseHead);
+
+ UpdateInhibitPersistentCachingFlag();
+
+ rv = UpdateExpirationTime();
+ if (NS_FAILED(rv)) return rv;
+
+ // notify observers interested in looking at a response that has been
+ // merged with any cached headers (http-on-examine-merged-response).
+ gHttpHandler->OnExamineMergedResponse(this);
+
+ if (mConcurrentCacheAccess) {
+ mCachedContentIsPartial = false;
+ // Leave the mConcurrentCacheAccess flag set, we want to use it
+ // to prevent duplicate OnStartRequest call on the target listener
+ // in case this channel is canceled before it gets its OnStartRequest
+ // from the http transaction.
+
+ // Now we continue reading the network response.
+ } else {
+ // the cached content is valid, although incomplete.
+ mCachedContentIsValid = true;
+ rv = ReadFromCache(false);
+ }
+
+ return rv;
+}
+
+nsresult
+nsHttpChannel::OnDoneReadingPartialCacheEntry(bool *streamDone)
+{
+ nsresult rv;
+
+ LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%p]", this));
+
+ // by default, assume we would have streamed all data or failed...
+ *streamDone = true;
+
+ // setup cache listener to append to cache entry
+ int64_t size;
+ rv = mCacheEntry->GetDataSize(&size);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = InstallCacheListener(size);
+ if (NS_FAILED(rv)) return rv;
+
+ // Entry is valid, do it now, after the output stream has been opened,
+ // otherwise when done earlier, pending readers would consider the cache
+ // entry still as partial (CacheEntry::GetDataSize would return the partial
+ // data size) and consumers would do the conditional request again.
+ rv = mCacheEntry->SetValid();
+ if (NS_FAILED(rv)) return rv;
+
+ // need to track the logical offset of the data being sent to our listener
+ mLogicalOffset = size;
+
+ // we're now completing the cached content, so we can clear this flag.
+ // this puts us in the state of a regular download.
+ mCachedContentIsPartial = false;
+ // The cache input stream pump is finished, we do not need it any more.
+ // (see bug 1313923)
+ mCachePump = nullptr;
+
+ // resume the transaction if it exists, otherwise the pipe contained the
+ // remaining part of the document and we've now streamed all of the data.
+ if (mTransactionPump) {
+ rv = mTransactionPump->Resume();
+ if (NS_SUCCEEDED(rv))
+ *streamDone = false;
+ }
+ else
+ NS_NOTREACHED("no transaction");
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <cache>
+//-----------------------------------------------------------------------------
+
+bool
+nsHttpChannel::ShouldBypassProcessNotModified()
+{
+ if (mCustomConditionalRequest) {
+ LOG(("Bypassing ProcessNotModified due to custom conditional headers"));
+ return true;
+ }
+
+ if (!mDidReval) {
+ LOG(("Server returned a 304 response even though we did not send a "
+ "conditional request"));
+ return true;
+ }
+
+ return false;
+}
+
+nsresult
+nsHttpChannel::ProcessNotModified()
+{
+ nsresult rv;
+
+ LOG(("nsHttpChannel::ProcessNotModified [this=%p]\n", this));
+
+ // Assert ShouldBypassProcessNotModified() has been checked before call to
+ // ProcessNotModified().
+ MOZ_ASSERT(!ShouldBypassProcessNotModified());
+
+ MOZ_ASSERT(mCachedResponseHead);
+ MOZ_ASSERT(mCacheEntry);
+ NS_ENSURE_TRUE(mCachedResponseHead && mCacheEntry, NS_ERROR_UNEXPECTED);
+
+ // If the 304 response contains a Last-Modified different than the
+ // one in our cache that is pretty suspicious and is, in at least the
+ // case of bug 716840, a sign of the server having previously corrupted
+ // our cache with a bad response. Take the minor step here of just dooming
+ // that cache entry so there is a fighting chance of getting things on the
+ // right track as well as disabling pipelining for that host.
+
+ nsAutoCString lastModifiedCached;
+ nsAutoCString lastModified304;
+
+ rv = mCachedResponseHead->GetHeader(nsHttp::Last_Modified,
+ lastModifiedCached);
+ if (NS_SUCCEEDED(rv)) {
+ rv = mResponseHead->GetHeader(nsHttp::Last_Modified,
+ lastModified304);
+ }
+
+ if (NS_SUCCEEDED(rv) && !lastModified304.Equals(lastModifiedCached)) {
+ LOG(("Cache Entry and 304 Last-Modified Headers Do Not Match "
+ "[%s] and [%s]\n",
+ lastModifiedCached.get(), lastModified304.get()));
+
+ mCacheEntry->AsyncDoom(nullptr);
+ if (mConnectionInfo)
+ gHttpHandler->ConnMgr()->
+ PipelineFeedbackInfo(mConnectionInfo,
+ nsHttpConnectionMgr::RedCorruptedContent,
+ nullptr, 0);
+ Telemetry::Accumulate(Telemetry::CACHE_LM_INCONSISTENT, true);
+ }
+
+ // merge any new headers with the cached response headers
+ rv = mCachedResponseHead->UpdateHeaders(mResponseHead);
+ if (NS_FAILED(rv)) return rv;
+
+ // update the cached response head
+ nsAutoCString head;
+ mCachedResponseHead->Flatten(head, true);
+ rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // make the cached response be the current response
+ mResponseHead = Move(mCachedResponseHead);
+
+ UpdateInhibitPersistentCachingFlag();
+
+ rv = UpdateExpirationTime();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = AddCacheEntryHeaders(mCacheEntry);
+ if (NS_FAILED(rv)) return rv;
+
+ // notify observers interested in looking at a reponse that has been
+ // merged with any cached headers
+ gHttpHandler->OnExamineMergedResponse(this);
+
+ mCachedContentIsValid = true;
+
+ // Tell other consumers the entry is OK to use
+ rv = mCacheEntry->SetValid();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadFromCache(false);
+ if (NS_FAILED(rv)) return rv;
+
+ mTransactionReplaced = true;
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::ProcessFallback(bool *waitingForRedirectCallback)
+{
+ LOG(("nsHttpChannel::ProcessFallback [this=%p]\n", this));
+ nsresult rv;
+
+ *waitingForRedirectCallback = false;
+ mFallingBack = false;
+
+ // At this point a load has failed (either due to network problems
+ // or an error returned on the server). Perform an application
+ // cache fallback if we have a URI to fall back to.
+ if (!mApplicationCache || mFallbackKey.IsEmpty() || mFallbackChannel) {
+ LOG((" choosing not to fallback [%p,%s,%d]",
+ mApplicationCache.get(), mFallbackKey.get(), mFallbackChannel));
+ return NS_OK;
+ }
+
+ // Make sure the fallback entry hasn't been marked as a foreign
+ // entry.
+ uint32_t fallbackEntryType;
+ rv = mApplicationCache->GetTypes(mFallbackKey, &fallbackEntryType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) {
+ // This cache points to a fallback that refers to a different
+ // manifest. Refuse to fall back.
+ return NS_OK;
+ }
+
+ if (!IsInSubpathOfAppCacheManifest(mApplicationCache, mFallbackKey)) {
+ // Refuse to fallback if the fallback key is not contained in the same
+ // path as the cache manifest.
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK,
+ "Fallback entry not marked correctly!");
+
+ // Kill any offline cache entry, and disable offline caching for the
+ // fallback.
+ if (mOfflineCacheEntry) {
+ mOfflineCacheEntry->AsyncDoom(nullptr);
+ mOfflineCacheEntry = nullptr;
+ }
+
+ mApplicationCacheForWrite = nullptr;
+ mOfflineCacheEntry = nullptr;
+
+ // Close the current cache entry.
+ CloseCacheEntry(true);
+
+ // Create a new channel to load the fallback entry.
+ RefPtr<nsIChannel> newChannel;
+ rv = gHttpHandler->NewChannel2(mURI,
+ mLoadInfo,
+ getter_AddRefs(newChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
+ rv = SetupReplacementChannel(mURI, newChannel, true, redirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make sure the new channel loads from the fallback key.
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal =
+ do_QueryInterface(newChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = httpInternal->SetupFallbackChannel(mFallbackKey.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // ... and fallbacks should only load from the cache.
+ uint32_t newLoadFlags = mLoadFlags | LOAD_REPLACE | LOAD_ONLY_FROM_CACHE;
+ rv = newChannel->SetLoadFlags(newLoadFlags);
+
+ // Inform consumers about this fake redirect
+ mRedirectChannel = newChannel;
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
+
+ if (NS_SUCCEEDED(rv))
+ rv = WaitForRedirectCallback();
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this);
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
+ return rv;
+ }
+
+ // Indicate we are now waiting for the asynchronous redirect callback
+ // if all went OK.
+ *waitingForRedirectCallback = true;
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::ContinueProcessFallback(nsresult rv)
+{
+ AutoRedirectVetoNotifier notifier(this);
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
+
+ // Make sure to do this after we received redirect veto answer,
+ // i.e. after all sinks had been notified
+ mRedirectChannel->SetOriginalURI(mOriginalURI);
+
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
+ rv = mRedirectChannel->AsyncOpen2(mListener);
+ }
+ else {
+ rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+ MaybeWarnAboutAppCache();
+ }
+
+ // close down this channel
+ Cancel(NS_BINDING_REDIRECTED);
+
+ notifier.RedirectSucceeded();
+
+ ReleaseListeners();
+
+ mFallingBack = true;
+
+ return NS_OK;
+}
+
+// Determines if a request is a byte range request for a subrange,
+// i.e. is a byte range request, but not a 0- byte range request.
+static bool
+IsSubRangeRequest(nsHttpRequestHead &aRequestHead)
+{
+ nsAutoCString byteRange;
+ if (NS_FAILED(aRequestHead.GetHeader(nsHttp::Range, byteRange))) {
+ return false;
+ }
+ return !byteRange.EqualsLiteral("bytes=0-");
+}
+
+nsresult
+nsHttpChannel::OpenCacheEntry(bool isHttps)
+{
+ // Handle correctly mCacheEntriesToWaitFor
+ AutoCacheWaitFlags waitFlags(this);
+
+ // Drop this flag here
+ mConcurrentCacheAccess = 0;
+
+ nsresult rv;
+
+ mLoadedFromApplicationCache = false;
+ mHasQueryString = HasQueryString(mRequestHead.ParsedMethod(), mURI);
+
+ LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));
+
+ // make sure we're not abusing this function
+ NS_PRECONDITION(!mCacheEntry, "cache entry already open");
+
+ nsAutoCString cacheKey;
+ nsAutoCString extension;
+
+ if (mRequestHead.IsPost()) {
+ // If the post id is already set then this is an attempt to replay
+ // a post transaction via the cache. Otherwise, we need a unique
+ // post id for this transaction.
+ if (mPostID == 0)
+ mPostID = gHttpHandler->GenerateUniqueID();
+ }
+ else if (!PossiblyIntercepted() && !mRequestHead.IsGet() && !mRequestHead.IsHead()) {
+ // don't use the cache for other types of requests
+ return NS_OK;
+ }
+
+ if (mResuming) {
+ // We don't support caching for requests initiated
+ // via nsIResumableChannel.
+ return NS_OK;
+ }
+
+ // Don't cache byte range requests which are subranges, only cache 0-
+ // byte range requests.
+ if (IsSubRangeRequest(mRequestHead))
+ return NS_OK;
+
+ // Pick up an application cache from the notification
+ // callbacks if available and if we are not an intercepted channel.
+ if (!PossiblyIntercepted() && !mApplicationCache &&
+ mInheritApplicationCache) {
+ nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
+ GetCallback(appCacheContainer);
+
+ if (appCacheContainer) {
+ appCacheContainer->GetApplicationCache(getter_AddRefs(mApplicationCache));
+ }
+ }
+
+ nsCOMPtr<nsICacheStorageService> cacheStorageService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ nsCOMPtr<nsIURI> openURI;
+ if (!mFallbackKey.IsEmpty() && mFallbackChannel) {
+ // This is a fallback channel, open fallback URI instead
+ rv = NS_NewURI(getter_AddRefs(openURI), mFallbackKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ // In the case of intercepted channels, we need to construct the cache
+ // entry key based on the original URI, so that in case the intercepted
+ // channel is redirected, the cache entry key before and after the
+ // redirect is the same.
+ if (PossiblyIntercepted()) {
+ openURI = mOriginalURI;
+ } else {
+ openURI = mURI;
+ }
+ }
+
+ RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
+ if (!info) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t cacheEntryOpenFlags;
+ bool offline = gIOService->IsOffline();
+
+ nsAutoCString cacheControlRequestHeader;
+ mRequestHead.GetHeader(nsHttp::Cache_Control, cacheControlRequestHeader);
+ CacheControlParser cacheControlRequest(cacheControlRequestHeader);
+ if (cacheControlRequest.NoStore() && !PossiblyIntercepted()) {
+ goto bypassCacheEntryOpen;
+ }
+
+ if (offline || (mLoadFlags & INHIBIT_CACHING)) {
+ if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline && !PossiblyIntercepted()) {
+ goto bypassCacheEntryOpen;
+ }
+ cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY;
+ mCacheEntryIsReadOnly = true;
+ }
+ else if (BYPASS_LOCAL_CACHE(mLoadFlags) && !mApplicationCache) {
+ cacheEntryOpenFlags = nsICacheStorage::OPEN_TRUNCATE;
+ }
+ else {
+ cacheEntryOpenFlags = nsICacheStorage::OPEN_NORMALLY
+ | nsICacheStorage::CHECK_MULTITHREADED;
+ }
+
+ if (!mPostID && mApplicationCache) {
+ rv = cacheStorageService->AppCacheStorage(info,
+ mApplicationCache,
+ getter_AddRefs(cacheStorage));
+ } else if (PossiblyIntercepted()) {
+ // The synthesized cache has less restrictions on file size and so on.
+ rv = cacheStorageService->SynthesizedCacheStorage(info,
+ getter_AddRefs(cacheStorage));
+ } else if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
+ rv = cacheStorageService->MemoryCacheStorage(info, // ? choose app cache as well...
+ getter_AddRefs(cacheStorage));
+ }
+ else if (mPinCacheContent) {
+ rv = cacheStorageService->PinningCacheStorage(info,
+ getter_AddRefs(cacheStorage));
+ }
+ else {
+ rv = cacheStorageService->DiskCacheStorage(info,
+ !mPostID && (mChooseApplicationCache || (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE)),
+ getter_AddRefs(cacheStorage));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if ((mClassOfService & nsIClassOfService::Leader) ||
+ (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI))
+ cacheEntryOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
+
+ // Only for backward compatibility with the old cache back end.
+ // When removed, remove the flags and related code snippets.
+ if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY)
+ cacheEntryOpenFlags |= nsICacheStorage::OPEN_BYPASS_IF_BUSY;
+
+ if (PossiblyIntercepted()) {
+ extension.Append(nsPrintfCString("u%lld", mInterceptionID));
+ } else if (mPostID) {
+ extension.Append(nsPrintfCString("%d", mPostID));
+ }
+
+ // If this channel should be intercepted, we do not open a cache entry for this channel
+ // until the interception process is complete and the consumer decides what to do with it.
+ if (mInterceptCache == MAYBE_INTERCEPT) {
+ DebugOnly<bool> exists;
+ MOZ_ASSERT(NS_FAILED(cacheStorage->Exists(openURI, extension, &exists)) || !exists,
+ "The entry must not exist in the cache before we create it here");
+
+ nsCOMPtr<nsICacheEntry> entry;
+ rv = cacheStorage->OpenTruncate(openURI, extension, getter_AddRefs(entry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+
+ RefPtr<InterceptedChannelChrome> intercepted =
+ new InterceptedChannelChrome(this, controller, entry);
+ intercepted->NotifyController();
+ } else {
+ if (mInterceptCache == INTERCEPTED) {
+ cacheEntryOpenFlags |= nsICacheStorage::OPEN_INTERCEPTED;
+ // Clear OPEN_TRUNCATE for the fake cache entry, since otherwise
+ // cache storage will close the current entry which breaks the
+ // response synthesis.
+ cacheEntryOpenFlags &= ~nsICacheStorage::OPEN_TRUNCATE;
+ DebugOnly<bool> exists;
+ MOZ_ASSERT(NS_SUCCEEDED(cacheStorage->Exists(openURI, extension, &exists)) && exists,
+ "The entry must exist in the cache after we create it here");
+ }
+
+ mCacheOpenWithPriority = cacheEntryOpenFlags & nsICacheStorage::OPEN_PRIORITY;
+ mCacheQueueSizeWhenOpen = CacheStorageService::CacheQueueSize(mCacheOpenWithPriority);
+
+ rv = cacheStorage->AsyncOpenURI(openURI, extension, cacheEntryOpenFlags, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ waitFlags.Keep(WAIT_FOR_CACHE_ENTRY);
+
+bypassCacheEntryOpen:
+ if (!mApplicationCacheForWrite)
+ return NS_OK;
+
+ // If there is an app cache to write to, open the entry right now in parallel.
+
+ // make sure we're not abusing this function
+ NS_PRECONDITION(!mOfflineCacheEntry, "cache entry already open");
+
+ if (offline) {
+ // only put things in the offline cache while online
+ return NS_OK;
+ }
+
+ if (mLoadFlags & INHIBIT_CACHING) {
+ // respect demand not to cache
+ return NS_OK;
+ }
+
+ if (!mRequestHead.IsGet()) {
+ // only cache complete documents offline
+ return NS_OK;
+ }
+
+ rv = cacheStorageService->AppCacheStorage(info, mApplicationCacheForWrite,
+ getter_AddRefs(cacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheStorage->AsyncOpenURI(
+ mURI, EmptyCString(), nsICacheStorage::OPEN_TRUNCATE, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ waitFlags.Keep(WAIT_FOR_OFFLINE_CACHE_ENTRY);
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::CheckPartial(nsICacheEntry* aEntry, int64_t *aSize, int64_t *aContentLength)
+{
+ nsresult rv;
+
+ rv = aEntry->GetDataSize(aSize);
+
+ if (NS_ERROR_IN_PROGRESS == rv) {
+ *aSize = -1;
+ rv = NS_OK;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsHttpResponseHead* responseHead = mCachedResponseHead
+ ? mCachedResponseHead
+ : mResponseHead;
+
+ if (!responseHead)
+ return NS_ERROR_UNEXPECTED;
+
+ *aContentLength = responseHead->ContentLength();
+
+ return NS_OK;
+}
+
+void
+nsHttpChannel::UntieValidationRequest()
+{
+ // Make the request unconditional again.
+ mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
+ mRequestHead.ClearHeader(nsHttp::If_None_Match);
+ mRequestHead.ClearHeader(nsHttp::ETag);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appCache,
+ uint32_t* aResult)
+{
+ nsresult rv = NS_OK;
+
+ LOG(("nsHttpChannel::OnCacheEntryCheck enter [channel=%p entry=%p]",
+ this, entry));
+
+ nsAutoCString cacheControlRequestHeader;
+ mRequestHead.GetHeader(nsHttp::Cache_Control, cacheControlRequestHeader);
+ CacheControlParser cacheControlRequest(cacheControlRequestHeader);
+
+ if (cacheControlRequest.NoStore()) {
+ LOG(("Not using cached response based on no-store request cache directive\n"));
+ *aResult = ENTRY_NOT_WANTED;
+ return NS_OK;
+ }
+
+ // Remember the request is a custom conditional request so that we can
+ // process any 304 response correctly.
+ mCustomConditionalRequest =
+ mRequestHead.HasHeader(nsHttp::If_Modified_Since) ||
+ mRequestHead.HasHeader(nsHttp::If_None_Match) ||
+ mRequestHead.HasHeader(nsHttp::If_Unmodified_Since) ||
+ mRequestHead.HasHeader(nsHttp::If_Match) ||
+ mRequestHead.HasHeader(nsHttp::If_Range);
+
+ // Be pessimistic: assume the cache entry has no useful data.
+ *aResult = ENTRY_WANTED;
+ mCachedContentIsValid = false;
+
+ nsXPIDLCString buf;
+
+ // Get the method that was used to generate the cached response
+ rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool methodWasHead = buf.EqualsLiteral("HEAD");
+ bool methodWasGet = buf.EqualsLiteral("GET");
+
+ if (methodWasHead) {
+ // The cached response does not contain an entity. We can only reuse
+ // the response if the current request is also HEAD.
+ if (!mRequestHead.IsHead()) {
+ return NS_OK;
+ }
+ }
+ buf.Adopt(0);
+
+ // We'll need this value in later computations...
+ uint32_t lastModifiedTime;
+ rv = entry->GetLastModified(&lastModifiedTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Determine if this is the first time that this cache entry
+ // has been accessed during this session.
+ bool fromPreviousSession =
+ (gHttpHandler->SessionStartTime() > lastModifiedTime);
+
+ // Get the cached HTTP response headers
+ mCachedResponseHead = new nsHttpResponseHead();
+
+ // A "original-response-headers" metadata element holds network original headers,
+ // i.e. the headers in the form as they arrieved from the network.
+ // We need to get the network original headers first, because we need to keep them
+ // in order.
+ rv = entry->GetMetaDataElement("original-response-headers", getter_Copies(buf));
+ if (NS_SUCCEEDED(rv)) {
+ mCachedResponseHead->ParseCachedOriginalHeaders((char *) buf.get());
+ }
+
+ buf.Adopt(0);
+ // A "response-head" metadata element holds response head, e.g. response status
+ // line and headers in the form Firefox uses them internally (no dupicate
+ // headers, etc.).
+ rv = entry->GetMetaDataElement("response-head", getter_Copies(buf));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Parse string stored in a "response-head" metadata element.
+ // These response headers will be merged with the orignal headers (i.e. the
+ // headers stored in a "original-response-headers" metadata element).
+ rv = mCachedResponseHead->ParseCachedHead(buf.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ buf.Adopt(0);
+
+ bool isCachedRedirect = WillRedirect(mCachedResponseHead);
+
+ // Do not return 304 responses from the cache, and also do not return
+ // any other non-redirect 3xx responses from the cache (see bug 759043).
+ NS_ENSURE_TRUE((mCachedResponseHead->Status() / 100 != 3) ||
+ isCachedRedirect, NS_ERROR_ABORT);
+
+ if (mCachedResponseHead->NoStore() && mCacheEntryIsReadOnly) {
+ // This prevents loading no-store responses when navigating back
+ // while the browser is set to work offline.
+ LOG((" entry loading as read-only but is no-store, set INHIBIT_CACHING"));
+ mLoadFlags |= nsIRequest::INHIBIT_CACHING;
+ }
+
+ // Don't bother to validate items that are read-only,
+ // unless they are read-only because of INHIBIT_CACHING or because
+ // we're updating the offline cache.
+ // Don't bother to validate if this is a fallback entry.
+ if (!mApplicationCacheForWrite &&
+ (appCache ||
+ (mCacheEntryIsReadOnly && !(mLoadFlags & nsIRequest::INHIBIT_CACHING)) ||
+ mFallbackChannel)) {
+ rv = OpenCacheInputStream(entry, true, !!appCache);
+ if (NS_SUCCEEDED(rv)) {
+ mCachedContentIsValid = true;
+ entry->MaybeMarkValid();
+ }
+ return rv;
+ }
+
+ bool wantCompleteEntry = false;
+
+ if (!methodWasHead && !isCachedRedirect) {
+ // If the cached content-length is set and it does not match the data
+ // size of the cached content, then the cached response is partial...
+ // either we need to issue a byte range request or we need to refetch
+ // the entire document.
+ //
+ // We exclude redirects from this check because we (usually) strip the
+ // entity when we store the cache entry, and even if we didn't, we
+ // always ignore a cached redirect's entity anyway. See bug 759043.
+ int64_t size, contentLength;
+ rv = CheckPartial(entry, &size, &contentLength);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (size == int64_t(-1)) {
+ LOG((" write is in progress"));
+ if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
+ LOG((" not interested in the entry, "
+ "LOAD_BYPASS_LOCAL_CACHE_IF_BUSY specified"));
+
+ *aResult = ENTRY_NOT_WANTED;
+ return NS_OK;
+ }
+
+ // Ignore !(size > 0) from the resumability condition
+ if (!IsResumable(size, contentLength, true)) {
+ LOG((" wait for entry completion, "
+ "response is not resumable"));
+
+ wantCompleteEntry = true;
+ }
+ else {
+ mConcurrentCacheAccess = 1;
+ }
+ }
+ else if (contentLength != int64_t(-1) && contentLength != size) {
+ LOG(("Cached data size does not match the Content-Length header "
+ "[content-length=%lld size=%lld]\n", contentLength, size));
+
+ rv = MaybeSetupByteRangeRequest(size, contentLength);
+ mCachedContentIsPartial = NS_SUCCEEDED(rv) && mIsPartialRequest;
+ if (mCachedContentIsPartial) {
+ rv = OpenCacheInputStream(entry, false, !!appCache);
+ if (NS_FAILED(rv)) {
+ UntieByteRangeRequest();
+ return rv;
+ }
+
+ *aResult = ENTRY_NEEDS_REVALIDATION;
+ return NS_OK;
+ }
+
+ if (size == 0 && mCacheOnlyMetadata) {
+ // Don't break cache entry load when the entry's data size
+ // is 0 and mCacheOnlyMetadata flag is set. In that case we
+ // want to proceed since the LOAD_ONLY_IF_MODIFIED flag is
+ // also set.
+ MOZ_ASSERT(mLoadFlags & LOAD_ONLY_IF_MODIFIED);
+ } else if (mInterceptCache != INTERCEPTED) {
+ return rv;
+ }
+ }
+ }
+
+ bool isHttps = false;
+ rv = mURI->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool doValidation = false;
+ bool canAddImsHeader = true;
+
+ bool isForcedValid = false;
+ entry->GetIsForcedValid(&isForcedValid);
+
+ nsXPIDLCString framedBuf;
+ rv = entry->GetMetaDataElement("strongly-framed", getter_Copies(framedBuf));
+ // describe this in terms of explicitly weakly framed so as to be backwards
+ // compatible with old cache contents which dont have strongly-framed makers
+ bool weaklyFramed = NS_SUCCEEDED(rv) && framedBuf.EqualsLiteral("0");
+ bool isImmutable = !weaklyFramed && isHttps && mCachedResponseHead->Immutable();
+
+ // Cached entry is not the entity we request (see bug #633743)
+ if (ResponseWouldVary(entry)) {
+ LOG(("Validating based on Vary headers returning TRUE\n"));
+ canAddImsHeader = false;
+ doValidation = true;
+ }
+ // Check isForcedValid to see if it is possible to skip validation.
+ // Don't skip validation if we have serious reason to believe that this
+ // content is invalid (it's expired).
+ // See netwerk/cache2/nsICacheEntry.idl for details
+ else if (isForcedValid &&
+ (!mCachedResponseHead->ExpiresInPast() ||
+ !mCachedResponseHead->MustValidateIfExpired())) {
+ LOG(("NOT validating based on isForcedValid being true.\n"));
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREFETCHES_USED> used;
+ ++used;
+ doValidation = false;
+ }
+ // If the LOAD_FROM_CACHE flag is set, any cached data can simply be used
+ else if (mLoadFlags & nsIRequest::LOAD_FROM_CACHE || mAllowStaleCacheContent) {
+ LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
+ doValidation = false;
+ }
+ // If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
+ // it's revalidated with the server.
+ else if ((mLoadFlags & nsIRequest::VALIDATE_ALWAYS) && !isImmutable) {
+ LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
+ doValidation = true;
+ }
+ // Even if the VALIDATE_NEVER flag is set, there are still some cases in
+ // which we must validate the cached response with the server.
+ else if (mLoadFlags & nsIRequest::VALIDATE_NEVER) {
+ LOG(("VALIDATE_NEVER set\n"));
+ // if no-store validate cached response (see bug 112564)
+ if (mCachedResponseHead->NoStore()) {
+ LOG(("Validating based on no-store logic\n"));
+ doValidation = true;
+ }
+ else {
+ LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
+ doValidation = false;
+ }
+ }
+ // check if validation is strictly required...
+ else if (mCachedResponseHead->MustValidate()) {
+ LOG(("Validating based on MustValidate() returning TRUE\n"));
+ doValidation = true;
+ } else {
+ // previously we also checked for a query-url w/out expiration
+ // and didn't do heuristic on it. but defacto that is allowed now.
+ //
+ // Check if the cache entry has expired...
+
+ uint32_t now = NowInSeconds();
+
+ uint32_t age = 0;
+ rv = mCachedResponseHead->ComputeCurrentAge(now, now, &age);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t freshness = 0;
+ rv = mCachedResponseHead->ComputeFreshnessLifetime(&freshness);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t expiration = 0;
+ rv = entry->GetExpirationTime(&expiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t maxAgeRequest, maxStaleRequest, minFreshRequest;
+
+ LOG((" NowInSeconds()=%u, expiration time=%u, freshness lifetime=%u, age=%u",
+ now, expiration, freshness, age));
+
+ if (cacheControlRequest.NoCache()) {
+ LOG((" validating, no-cache request"));
+ doValidation = true;
+ } else if (cacheControlRequest.MaxStale(&maxStaleRequest)) {
+ uint32_t staleTime = age > freshness ? age - freshness : 0;
+ doValidation = staleTime > maxStaleRequest;
+ LOG((" validating=%d, max-stale=%u requested", doValidation, maxStaleRequest));
+ } else if (cacheControlRequest.MaxAge(&maxAgeRequest)) {
+ doValidation = age > maxAgeRequest;
+ LOG((" validating=%d, max-age=%u requested", doValidation, maxAgeRequest));
+ } else if (cacheControlRequest.MinFresh(&minFreshRequest)) {
+ uint32_t freshTime = freshness > age ? freshness - age : 0;
+ doValidation = freshTime < minFreshRequest;
+ LOG((" validating=%d, min-fresh=%u requested", doValidation, minFreshRequest));
+ } else if (now <= expiration) {
+ doValidation = false;
+ LOG((" not validating, expire time not in the past"));
+ } else if (mCachedResponseHead->MustValidateIfExpired()) {
+ doValidation = true;
+ } else if (mLoadFlags & nsIRequest::VALIDATE_ONCE_PER_SESSION) {
+ // If the cached response does not include expiration infor-
+ // mation, then we must validate the response, despite whether
+ // or not this is the first access this session. This behavior
+ // is consistent with existing browsers and is generally expected
+ // by web authors.
+ if (freshness == 0)
+ doValidation = true;
+ else
+ doValidation = fromPreviousSession;
+ }
+ else
+ doValidation = true;
+
+ LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
+ }
+
+
+ // If a content signature is expected to be valid in this load,
+ // set doValidation to force a signature check.
+ if (!doValidation &&
+ mLoadInfo && mLoadInfo->GetVerifySignedContent()) {
+ doValidation = true;
+ }
+
+ nsAutoCString requestedETag;
+ if (!doValidation &&
+ NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag)) &&
+ (methodWasGet || methodWasHead)) {
+ nsAutoCString cachedETag;
+ mCachedResponseHead->GetHeader(nsHttp::ETag, cachedETag);
+ if (!cachedETag.IsEmpty() &&
+ (StringBeginsWith(cachedETag, NS_LITERAL_CSTRING("W/")) ||
+ !requestedETag.Equals(cachedETag))) {
+ // User has defined If-Match header, if the cached entry is not
+ // matching the provided header value or the cached ETag is weak,
+ // force validation.
+ doValidation = true;
+ }
+ }
+
+ if (!doValidation) {
+ //
+ // Check the authorization headers used to generate the cache entry.
+ // We must validate the cache entry if:
+ //
+ // 1) the cache entry was generated prior to this session w/
+ // credentials (see bug 103402).
+ // 2) the cache entry was generated w/o credentials, but would now
+ // require credentials (see bug 96705).
+ //
+ // NOTE: this does not apply to proxy authentication.
+ //
+ entry->GetMetaDataElement("auth", getter_Copies(buf));
+ doValidation =
+ (fromPreviousSession && !buf.IsEmpty()) ||
+ (buf.IsEmpty() && mRequestHead.HasHeader(nsHttp::Authorization));
+ }
+
+ // Bug #561276: We maintain a chain of cache-keys which returns cached
+ // 3xx-responses (redirects) in order to detect cycles. If a cycle is
+ // found, ignore the cached response and hit the net. Otherwise, use
+ // the cached response and add the cache-key to the chain. Note that
+ // a limited number of redirects (cached or not) is allowed and is
+ // enforced independently of this mechanism
+ if (!doValidation && isCachedRedirect) {
+ nsAutoCString cacheKey;
+ GenerateCacheKey(mPostID, cacheKey);
+
+ if (!mRedirectedCachekeys)
+ mRedirectedCachekeys = new nsTArray<nsCString>();
+ else if (mRedirectedCachekeys->Contains(cacheKey))
+ doValidation = true;
+
+ LOG(("Redirection-chain %s key %s\n",
+ doValidation ? "contains" : "does not contain", cacheKey.get()));
+
+ // Append cacheKey if not in the chain already
+ if (!doValidation)
+ mRedirectedCachekeys->AppendElement(cacheKey);
+ }
+
+ if (doValidation && mInterceptCache == INTERCEPTED) {
+ doValidation = false;
+ }
+
+ mCachedContentIsValid = !doValidation;
+
+ if (doValidation) {
+ //
+ // now, we are definitely going to issue a HTTP request to the server.
+ // make it conditional if possible.
+ //
+ // do not attempt to validate no-store content, since servers will not
+ // expect it to be cached. (we only keep it in our cache for the
+ // purposes of back/forward, etc.)
+ //
+ // the request method MUST be either GET or HEAD (see bug 175641) and
+ // the cached response code must be < 400
+ //
+ // the cached content must not be weakly framed or marked immutable
+ //
+ // do not override conditional headers when consumer has defined its own
+ if (!mCachedResponseHead->NoStore() &&
+ (mRequestHead.IsGet() || mRequestHead.IsHead()) &&
+ !mCustomConditionalRequest && !weaklyFramed && !isImmutable &&
+ (mCachedResponseHead->Status() < 400)) {
+
+ if (mConcurrentCacheAccess) {
+ // In case of concurrent read and also validation request we
+ // must wait for the current writer to close the output stream
+ // first. Otherwise, when the writer's job would have been interrupted
+ // before all the data were downloaded, we'd have to do a range request
+ // which would be a second request in line during this channel's
+ // life-time. nsHttpChannel is not designed to do that, so rather
+ // turn off concurrent read and wait for entry's completion.
+ // Then only re-validation or range-re-validation request will go out.
+ mConcurrentCacheAccess = 0;
+ // This will cause that OnCacheEntryCheck is called again with the same
+ // entry after the writer is done.
+ wantCompleteEntry = true;
+ } else {
+ nsAutoCString val;
+ // Add If-Modified-Since header if a Last-Modified was given
+ // and we are allowed to do this (see bugs 510359 and 269303)
+ if (canAddImsHeader) {
+ mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
+ if (!val.IsEmpty())
+ mRequestHead.SetHeader(nsHttp::If_Modified_Since, val);
+ }
+ // Add If-None-Match header if an ETag was given in the response
+ mCachedResponseHead->GetHeader(nsHttp::ETag, val);
+ if (!val.IsEmpty())
+ mRequestHead.SetHeader(nsHttp::If_None_Match, val);
+ mDidReval = true;
+ }
+ }
+ }
+
+ if (mCachedContentIsValid || mDidReval) {
+ rv = OpenCacheInputStream(entry, mCachedContentIsValid, !!appCache);
+ if (NS_FAILED(rv)) {
+ // If we can't get the entity then we have to act as though we
+ // don't have the cache entry.
+ if (mDidReval) {
+ UntieValidationRequest();
+ mDidReval = false;
+ }
+ mCachedContentIsValid = false;
+ }
+ }
+
+ if (mDidReval)
+ *aResult = ENTRY_NEEDS_REVALIDATION;
+ else if (wantCompleteEntry)
+ *aResult = RECHECK_AFTER_WRITE_FINISHED;
+ else
+ *aResult = ENTRY_WANTED;
+
+ if (mCachedContentIsValid) {
+ entry->MaybeMarkValid();
+ }
+
+ LOG(("nsHTTPChannel::OnCacheEntryCheck exit [this=%p doValidation=%d result=%d]\n",
+ this, doValidation, *aResult));
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnCacheEntryAvailable(nsICacheEntry *entry,
+ bool aNew,
+ nsIApplicationCache* aAppCache,
+ nsresult status)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ LOG(("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p "
+ "new=%d appcache=%p status=%x mAppCache=%p mAppCacheForWrite=%p]\n",
+ this, entry, aNew, aAppCache, status,
+ mApplicationCache.get(), mApplicationCacheForWrite.get()));
+
+ // if the channel's already fired onStopRequest, then we should ignore
+ // this event.
+ if (!mIsPending) {
+ mCacheInputStream.CloseAndRelease();
+ return NS_OK;
+ }
+
+ rv = OnCacheEntryAvailableInternal(entry, aNew, aAppCache, status);
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::OnCacheEntryAvailableInternal(nsICacheEntry *entry,
+ bool aNew,
+ nsIApplicationCache* aAppCache,
+ nsresult status)
+{
+ nsresult rv;
+
+ if (mCanceled) {
+ LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus));
+ return mStatus;
+ }
+
+ if (aAppCache) {
+ if (mApplicationCache == aAppCache && !mCacheEntry) {
+ rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
+ }
+ else if (mApplicationCacheForWrite == aAppCache && aNew && !mOfflineCacheEntry) {
+ rv = OnOfflineCacheEntryForWritingAvailable(entry, aAppCache, status);
+ }
+ else {
+ rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
+ }
+ }
+ else {
+ rv = OnNormalCacheEntryAvailable(entry, aNew, status);
+ }
+
+ if (NS_FAILED(rv) && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
+ // If we have a fallback URI (and we're not already
+ // falling back), process the fallback asynchronously.
+ if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
+ return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
+ }
+
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // We may be waiting for more callbacks...
+ if (AwaitingCacheCallbacks()) {
+ return NS_OK;
+ }
+
+ return TryHSTSPriming();
+}
+
+nsresult
+nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry *aEntry,
+ bool aNew,
+ nsresult aEntryStatus)
+{
+ mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;
+
+ if (NS_FAILED(aEntryStatus) || aNew) {
+ // Make sure this flag is dropped. It may happen the entry is doomed
+ // between OnCacheEntryCheck and OnCacheEntryAvailable.
+ mCachedContentIsValid = false;
+
+ // From the same reason remove any conditional headers added
+ // in OnCacheEntryCheck.
+ if (mDidReval) {
+ LOG((" Removing conditional request headers"));
+ UntieValidationRequest();
+ mDidReval = false;
+ }
+
+ if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+ // if this channel is only allowed to pull from the cache, then
+ // we must fail if we were unable to open a cache entry for read.
+ return NS_ERROR_DOCUMENT_NOT_CACHED;
+ }
+ }
+
+ if (NS_SUCCEEDED(aEntryStatus)) {
+ mCacheEntry = aEntry;
+ mCacheEntryIsWriteOnly = aNew;
+
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+ Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD,
+ false);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::OnOfflineCacheEntryAvailable(nsICacheEntry *aEntry,
+ bool aNew,
+ nsIApplicationCache* aAppCache,
+ nsresult aEntryStatus)
+{
+ MOZ_ASSERT(!mApplicationCache || aAppCache == mApplicationCache);
+ MOZ_ASSERT(!aNew || !aEntry || mApplicationCacheForWrite);
+
+ mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;
+
+ nsresult rv;
+
+ if (NS_SUCCEEDED(aEntryStatus)) {
+ if (!mApplicationCache) {
+ mApplicationCache = aAppCache;
+ }
+
+ // We successfully opened an offline cache session and the entry,
+ // so indicate we will load from the offline cache.
+ mLoadedFromApplicationCache = true;
+ mCacheEntryIsReadOnly = true;
+ mCacheEntry = aEntry;
+ mCacheEntryIsWriteOnly = false;
+
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI && !mApplicationCacheForWrite) {
+ MaybeWarnAboutAppCache();
+ }
+
+ return NS_OK;
+ }
+
+ if (!mApplicationCacheForWrite && !mFallbackChannel) {
+ if (!mApplicationCache) {
+ mApplicationCache = aAppCache;
+ }
+
+ // Check for namespace match.
+ nsCOMPtr<nsIApplicationCacheNamespace> namespaceEntry;
+ rv = mApplicationCache->GetMatchingNamespace(mSpec,
+ getter_AddRefs(namespaceEntry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t namespaceType = 0;
+ if (!namespaceEntry ||
+ NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) ||
+ (namespaceType &
+ (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
+ nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) == 0) {
+ // When loading from an application cache, only items
+ // on the whitelist or matching a
+ // fallback namespace should hit the network...
+ mLoadFlags |= LOAD_ONLY_FROM_CACHE;
+
+ // ... and if there were an application cache entry,
+ // we would have found it earlier.
+ return NS_ERROR_CACHE_KEY_NOT_FOUND;
+ }
+
+ if (namespaceType &
+ nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
+
+ nsAutoCString namespaceSpec;
+ rv = namespaceEntry->GetNamespaceSpec(namespaceSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This prevents fallback attacks injected by an insecure subdirectory
+ // for the whole origin (or a parent directory).
+ if (!IsInSubpathOfAppCacheManifest(mApplicationCache, namespaceSpec)) {
+ return NS_OK;
+ }
+
+ rv = namespaceEntry->GetData(mFallbackKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::OnOfflineCacheEntryForWritingAvailable(nsICacheEntry *aEntry,
+ nsIApplicationCache* aAppCache,
+ nsresult aEntryStatus)
+{
+ MOZ_ASSERT(mApplicationCacheForWrite && aAppCache == mApplicationCacheForWrite);
+
+ mCacheEntriesToWaitFor &= ~WAIT_FOR_OFFLINE_CACHE_ENTRY;
+
+ if (NS_SUCCEEDED(aEntryStatus)) {
+ mOfflineCacheEntry = aEntry;
+ if (NS_FAILED(aEntry->GetLastModified(&mOfflineCacheLastModifiedTime))) {
+ mOfflineCacheLastModifiedTime = 0;
+ }
+ }
+
+ return aEntryStatus;
+}
+
+// Generates the proper cache-key for this instance of nsHttpChannel
+nsresult
+nsHttpChannel::GenerateCacheKey(uint32_t postID, nsACString &cacheKey)
+{
+ AssembleCacheKey(mFallbackChannel ? mFallbackKey.get() : mSpec.get(),
+ postID, cacheKey);
+ return NS_OK;
+}
+
+// Assembles a cache-key from the given pieces of information and |mLoadFlags|
+void
+nsHttpChannel::AssembleCacheKey(const char *spec, uint32_t postID,
+ nsACString &cacheKey)
+{
+ cacheKey.Truncate();
+
+ if (mLoadFlags & LOAD_ANONYMOUS) {
+ cacheKey.AssignLiteral("anon&");
+ }
+
+ if (postID) {
+ char buf[32];
+ SprintfLiteral(buf, "id=%x&", postID);
+ cacheKey.Append(buf);
+ }
+
+ if (!cacheKey.IsEmpty()) {
+ cacheKey.AppendLiteral("uri=");
+ }
+
+ // Strip any trailing #ref from the URL before using it as the key
+ const char *p = strchr(spec, '#');
+ if (p)
+ cacheKey.Append(spec, p - spec);
+ else
+ cacheKey.Append(spec);
+}
+
+nsresult
+DoUpdateExpirationTime(nsHttpChannel* aSelf,
+ nsICacheEntry* aCacheEntry,
+ nsHttpResponseHead* aResponseHead,
+ uint32_t& aExpirationTime)
+{
+ MOZ_ASSERT(aExpirationTime == 0);
+ NS_ENSURE_TRUE(aResponseHead, NS_ERROR_FAILURE);
+
+ nsresult rv;
+
+ if (!aResponseHead->MustValidate()) {
+ uint32_t freshnessLifetime = 0;
+
+ rv = aResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
+ if (NS_FAILED(rv)) return rv;
+
+ if (freshnessLifetime > 0) {
+ uint32_t now = NowInSeconds(), currentAge = 0;
+
+ rv = aResponseHead->ComputeCurrentAge(now, aSelf->GetRequestTime(), &currentAge);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG(("freshnessLifetime = %u, currentAge = %u\n",
+ freshnessLifetime, currentAge));
+
+ if (freshnessLifetime > currentAge) {
+ uint32_t timeRemaining = freshnessLifetime - currentAge;
+ // be careful... now + timeRemaining may overflow
+ if (now + timeRemaining < now)
+ aExpirationTime = uint32_t(-1);
+ else
+ aExpirationTime = now + timeRemaining;
+ }
+ else
+ aExpirationTime = now;
+ }
+ }
+
+ rv = aCacheEntry->SetExpirationTime(aExpirationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+// UpdateExpirationTime is called when a new response comes in from the server.
+// It updates the stored response-time and sets the expiration time on the
+// cache entry.
+//
+// From section 13.2.4 of RFC2616, we compute expiration time as follows:
+//
+// timeRemaining = freshnessLifetime - currentAge
+// expirationTime = now + timeRemaining
+//
+nsresult
+nsHttpChannel::UpdateExpirationTime()
+{
+ uint32_t expirationTime = 0;
+ nsresult rv = DoUpdateExpirationTime(this, mCacheEntry, mResponseHead, expirationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mOfflineCacheEntry) {
+ rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+/*static*/ inline bool
+nsHttpChannel::HasQueryString(nsHttpRequestHead::ParsedMethodType method, nsIURI * uri)
+{
+ // Must be called on the main thread because nsIURI does not implement
+ // thread-safe QueryInterface.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (method != nsHttpRequestHead::kMethod_Get &&
+ method != nsHttpRequestHead::kMethod_Head)
+ return false;
+
+ nsAutoCString query;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
+ nsresult rv = url->GetQuery(query);
+ return NS_SUCCEEDED(rv) && !query.IsEmpty();
+}
+
+bool
+nsHttpChannel::ShouldUpdateOfflineCacheEntry()
+{
+ if (!mApplicationCacheForWrite || !mOfflineCacheEntry) {
+ return false;
+ }
+
+ // if we're updating the cache entry, update the offline cache entry too
+ if (mCacheEntry && mCacheEntryIsWriteOnly) {
+ return true;
+ }
+
+ // if there's nothing in the offline cache, add it
+ if (mOfflineCacheEntry) {
+ return true;
+ }
+
+ // if the document is newer than the offline entry, update it
+ uint32_t docLastModifiedTime;
+ nsresult rv = mResponseHead->GetLastModifiedValue(&docLastModifiedTime);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ if (mOfflineCacheLastModifiedTime == 0) {
+ return false;
+ }
+
+ if (docLastModifiedTime > mOfflineCacheLastModifiedTime) {
+ return true;
+ }
+
+ return false;
+}
+
+nsresult
+nsHttpChannel::OpenCacheInputStream(nsICacheEntry* cacheEntry, bool startBuffering,
+ bool checkingAppCacheEntry)
+{
+ nsresult rv;
+
+ bool isHttps = false;
+ rv = mURI->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (isHttps) {
+ rv = cacheEntry->GetSecurityInfo(
+ getter_AddRefs(mCachedSecurityInfo));
+ if (NS_FAILED(rv)) {
+ LOG(("failed to parse security-info [channel=%p, entry=%p]",
+ this, cacheEntry));
+ NS_WARNING("failed to parse security-info");
+ cacheEntry->AsyncDoom(nullptr);
+ return rv;
+ }
+
+ // XXX: We should not be skilling this check in the offline cache
+ // case, but we have to do so now to work around bug 794507.
+ bool mustHaveSecurityInfo = !mLoadedFromApplicationCache && !checkingAppCacheEntry;
+ MOZ_ASSERT(mCachedSecurityInfo || !mustHaveSecurityInfo);
+ if (!mCachedSecurityInfo && mustHaveSecurityInfo) {
+ LOG(("mCacheEntry->GetSecurityInfo returned success but did not "
+ "return the security info [channel=%p, entry=%p]",
+ this, cacheEntry));
+ cacheEntry->AsyncDoom(nullptr);
+ return NS_ERROR_UNEXPECTED; // XXX error code
+ }
+ }
+
+ // Keep the conditions below in sync with the conditions in ReadFromCache.
+
+ rv = NS_OK;
+
+ if (WillRedirect(mCachedResponseHead)) {
+ // Do not even try to read the entity for a redirect because we do not
+ // return an entity to the application when we process redirects.
+ LOG(("Will skip read of cached redirect entity\n"));
+ return NS_OK;
+ }
+
+ if ((mLoadFlags & nsICachingChannel::LOAD_ONLY_IF_MODIFIED) &&
+ !mCachedContentIsPartial) {
+ // For LOAD_ONLY_IF_MODIFIED, we usually don't have to deal with the
+ // cached entity.
+ if (!mApplicationCacheForWrite) {
+ LOG(("Will skip read from cache based on LOAD_ONLY_IF_MODIFIED "
+ "load flag\n"));
+ return NS_OK;
+ }
+
+ // If offline caching has been requested and the offline cache needs
+ // updating, we must complete the call even if the main cache entry
+ // is up to date. We don't know yet for sure whether the offline
+ // cache needs updating because at this point we haven't opened it
+ // for writing yet, so we have to start reading the cached entity now
+ // just in case.
+ LOG(("May skip read from cache based on LOAD_ONLY_IF_MODIFIED "
+ "load flag\n"));
+ }
+
+ // Open an input stream for the entity, so that the call to OpenInputStream
+ // happens off the main thread.
+ nsCOMPtr<nsIInputStream> stream;
+
+ // If an alternate representation was requested, try to open the alt
+ // input stream.
+ if (!mPreferredCachedAltDataType.IsEmpty()) {
+ rv = cacheEntry->OpenAlternativeInputStream(mPreferredCachedAltDataType,
+ getter_AddRefs(stream));
+ if (NS_SUCCEEDED(rv)) {
+ // We have succeeded.
+ mAvailableCachedAltDataType = mPreferredCachedAltDataType;
+ // Clear the header.
+ mCachedResponseHead->SetContentLength(-1);
+ // Set the correct data size on the channel.
+ int64_t altDataSize;
+ if (NS_SUCCEEDED(cacheEntry->GetAltDataSize(&altDataSize))) {
+ mCachedResponseHead->SetContentLength(altDataSize);
+ }
+ }
+ }
+
+ if (!stream) {
+ rv = cacheEntry->OpenInputStream(0, getter_AddRefs(stream));
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("Failed to open cache input stream [channel=%p, "
+ "mCacheEntry=%p]", this, cacheEntry));
+ return rv;
+ }
+
+ if (startBuffering) {
+ bool nonBlocking;
+ rv = stream->IsNonBlocking(&nonBlocking);
+ if (NS_SUCCEEDED(rv) && nonBlocking)
+ startBuffering = false;
+ }
+
+ if (!startBuffering) {
+ // Bypass wrapping the input stream for the new cache back-end since
+ // nsIStreamTransportService expects a blocking stream. Preloading of
+ // the data must be done on the level of the cache backend, internally.
+ //
+ // We do not connect the stream to the stream transport service if we
+ // have to validate the entry with the server. If we did, we would get
+ // into a race condition between the stream transport service reading
+ // the existing contents and the opening of the cache entry's output
+ // stream to write the new contents in the case where we get a non-304
+ // response.
+ LOG(("Opened cache input stream without buffering [channel=%p, "
+ "mCacheEntry=%p, stream=%p]", this,
+ cacheEntry, stream.get()));
+ mCacheInputStream.takeOver(stream);
+ return rv;
+ }
+
+ // Have the stream transport service start reading the entity on one of its
+ // background threads.
+
+ nsCOMPtr<nsITransport> transport;
+ nsCOMPtr<nsIInputStream> wrapper;
+
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(kStreamTransportServiceCID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = sts->CreateInputTransport(stream, int64_t(-1), int64_t(-1),
+ true, getter_AddRefs(transport));
+ }
+ if (NS_SUCCEEDED(rv)) {
+ rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(wrapper));
+ }
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("Opened cache input stream [channel=%p, wrapper=%p, "
+ "transport=%p, stream=%p]", this, wrapper.get(),
+ transport.get(), stream.get()));
+ } else {
+ LOG(("Failed to open cache input stream [channel=%p, "
+ "wrapper=%p, transport=%p, stream=%p]", this,
+ wrapper.get(), transport.get(), stream.get()));
+
+ stream->Close();
+ return rv;
+ }
+
+ mCacheInputStream.takeOver(wrapper);
+
+ return NS_OK;
+}
+
+// Actually process the cached response that we started to handle in CheckCache
+// and/or StartBufferingCachedEntity.
+nsresult
+nsHttpChannel::ReadFromCache(bool alreadyMarkedValid)
+{
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
+
+ LOG(("nsHttpChannel::ReadFromCache [this=%p] "
+ "Using cached copy of: %s\n", this, mSpec.get()));
+
+ if (mCachedResponseHead)
+ mResponseHead = Move(mCachedResponseHead);
+
+ UpdateInhibitPersistentCachingFlag();
+
+ // if we don't already have security info, try to get it from the cache
+ // entry. there are two cases to consider here: 1) we are just reading
+ // from the cache, or 2) this may be due to a 304 not modified response,
+ // in which case we could have security info from a socket transport.
+ if (!mSecurityInfo)
+ mSecurityInfo = mCachedSecurityInfo;
+
+ if (!alreadyMarkedValid && !mCachedContentIsPartial) {
+ // We validated the entry, and we have write access to the cache, so
+ // mark the cache entry as valid in order to allow others access to
+ // this cache entry.
+ //
+ // TODO: This should be done asynchronously so we don't take the cache
+ // service lock on the main thread.
+ mCacheEntry->MaybeMarkValid();
+ }
+
+ nsresult rv;
+
+ // Keep the conditions below in sync with the conditions in
+ // StartBufferingCachedEntity.
+
+ if (WillRedirect(mResponseHead)) {
+ // TODO: Bug 759040 - We should call HandleAsyncRedirect directly here,
+ // to avoid event dispatching latency.
+ MOZ_ASSERT(!mCacheInputStream);
+ LOG(("Skipping skip read of cached redirect entity\n"));
+ return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
+ }
+
+ if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) {
+ if (!mApplicationCacheForWrite) {
+ LOG(("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
+ "load flag\n"));
+ MOZ_ASSERT(!mCacheInputStream);
+ // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
+ // here, to avoid event dispatching latency.
+ return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
+ }
+
+ if (!ShouldUpdateOfflineCacheEntry()) {
+ LOG(("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
+ "load flag (mApplicationCacheForWrite not null case)\n"));
+ mCacheInputStream.CloseAndRelease();
+ // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
+ // here, to avoid event dispatching latency.
+ return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
+ }
+ }
+
+ MOZ_ASSERT(mCacheInputStream);
+ if (!mCacheInputStream) {
+ NS_ERROR("mCacheInputStream is null but we're expecting to "
+ "be able to read from it.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+
+ nsCOMPtr<nsIInputStream> inputStream = mCacheInputStream.forget();
+
+ rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), inputStream,
+ int64_t(-1), int64_t(-1), 0, 0, true);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ rv = mCachePump->AsyncRead(this, mListenerContext);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mTimingEnabled)
+ mCacheReadStart = TimeStamp::Now();
+
+ uint32_t suspendCount = mSuspendCount;
+ while (suspendCount--)
+ mCachePump->Suspend();
+
+ return NS_OK;
+}
+
+void
+nsHttpChannel::CloseCacheEntry(bool doomOnFailure)
+{
+ mCacheInputStream.CloseAndRelease();
+
+ if (!mCacheEntry)
+ return;
+
+ LOG(("nsHttpChannel::CloseCacheEntry [this=%p] mStatus=%x mCacheEntryIsWriteOnly=%x",
+ this, mStatus, mCacheEntryIsWriteOnly));
+
+ // If we have begun to create or replace a cache entry, and that cache
+ // entry is not complete and not resumable, then it needs to be doomed.
+ // Otherwise, CheckCache will make the mistake of thinking that the
+ // partial cache entry is complete.
+
+ bool doom = false;
+ if (mInitedCacheEntry) {
+ MOZ_ASSERT(mResponseHead, "oops");
+ if (NS_FAILED(mStatus) && doomOnFailure &&
+ mCacheEntryIsWriteOnly && !mResponseHead->IsResumable())
+ doom = true;
+ }
+ else if (mCacheEntryIsWriteOnly)
+ doom = true;
+
+ if (doom) {
+ LOG((" dooming cache entry!!"));
+ mCacheEntry->AsyncDoom(nullptr);
+ } else {
+ // Store updated security info, makes cached EV status race less likely
+ // (see bug 1040086)
+ if (mSecurityInfo)
+ mCacheEntry->SetSecurityInfo(mSecurityInfo);
+ }
+
+ mCachedResponseHead = nullptr;
+
+ mCachePump = nullptr;
+ mCacheEntry = nullptr;
+ mCacheEntryIsWriteOnly = false;
+ mInitedCacheEntry = false;
+}
+
+
+void
+nsHttpChannel::CloseOfflineCacheEntry()
+{
+ if (!mOfflineCacheEntry)
+ return;
+
+ LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%p]", this));
+
+ if (NS_FAILED(mStatus)) {
+ mOfflineCacheEntry->AsyncDoom(nullptr);
+ }
+ else {
+ bool succeeded;
+ if (NS_SUCCEEDED(GetRequestSucceeded(&succeeded)) && !succeeded)
+ mOfflineCacheEntry->AsyncDoom(nullptr);
+ }
+
+ mOfflineCacheEntry = nullptr;
+}
+
+
+// Initialize the cache entry for writing.
+// - finalize storage policy
+// - store security info
+// - update expiration time
+// - store headers and other meta data
+nsresult
+nsHttpChannel::InitCacheEntry()
+{
+ nsresult rv;
+
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
+ // if only reading, nothing to be done here.
+ if (mCacheEntryIsReadOnly)
+ return NS_OK;
+
+ // Don't cache the response again if already cached...
+ if (mCachedContentIsValid)
+ return NS_OK;
+
+ LOG(("nsHttpChannel::InitCacheEntry [this=%p entry=%p]\n",
+ this, mCacheEntry.get()));
+
+ bool recreate = !mCacheEntryIsWriteOnly;
+ bool dontPersist = mLoadFlags & INHIBIT_PERSISTENT_CACHING;
+
+ if (!recreate && dontPersist) {
+ // If the current entry is persistent but we inhibit peristence
+ // then force recreation of the entry as memory/only.
+ rv = mCacheEntry->GetPersistent(&recreate);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ if (recreate) {
+ LOG((" we have a ready entry, but reading it again from the server -> recreating cache entry\n"));
+ nsCOMPtr<nsICacheEntry> currentEntry;
+ currentEntry.swap(mCacheEntry);
+ rv = currentEntry->Recreate(dontPersist, getter_AddRefs(mCacheEntry));
+ if (NS_FAILED(rv)) {
+ LOG((" recreation failed, the response will not be cached"));
+ return NS_OK;
+ }
+
+ mCacheEntryIsWriteOnly = true;
+ }
+
+ // Set the expiration time for this cache entry
+ rv = UpdateExpirationTime();
+ if (NS_FAILED(rv)) return rv;
+
+ // mark this weakly framed until a response body is seen
+ mCacheEntry->SetMetaDataElement("strongly-framed", "0");
+
+ rv = AddCacheEntryHeaders(mCacheEntry);
+ if (NS_FAILED(rv)) return rv;
+
+ mInitedCacheEntry = true;
+
+ // Don't perform the check when writing (doesn't make sense)
+ mConcurrentCacheAccess = 0;
+
+ return NS_OK;
+}
+
+void
+nsHttpChannel::UpdateInhibitPersistentCachingFlag()
+{
+ // The no-store directive within the 'Cache-Control:' header indicates
+ // that we must not store the response in a persistent cache.
+ if (mResponseHead->NoStore())
+ mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
+
+ // Only cache SSL content on disk if the pref is set
+ bool isHttps;
+ if (!gHttpHandler->IsPersistentHttpsCachingEnabled() &&
+ NS_SUCCEEDED(mURI->SchemeIs("https", &isHttps)) && isHttps) {
+ mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
+ }
+}
+
+nsresult
+nsHttpChannel::InitOfflineCacheEntry()
+{
+ // This function can be called even when we fail to connect (bug 551990)
+
+ if (!mOfflineCacheEntry) {
+ return NS_OK;
+ }
+
+ if (!mResponseHead || mResponseHead->NoStore()) {
+ if (mResponseHead && mResponseHead->NoStore()) {
+ mOfflineCacheEntry->AsyncDoom(nullptr);
+ }
+
+ CloseOfflineCacheEntry();
+
+ if (mResponseHead && mResponseHead->NoStore()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+ }
+
+ // This entry's expiration time should match the main entry's expiration
+ // time. UpdateExpirationTime() will keep it in sync once the offline
+ // cache entry has been created.
+ if (mCacheEntry) {
+ uint32_t expirationTime;
+ nsresult rv = mCacheEntry->GetExpirationTime(&expirationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOfflineCacheEntry->SetExpirationTime(expirationTime);
+ }
+
+ return AddCacheEntryHeaders(mOfflineCacheEntry);
+}
+
+
+nsresult
+DoAddCacheEntryHeaders(nsHttpChannel *self,
+ nsICacheEntry *entry,
+ nsHttpRequestHead *requestHead,
+ nsHttpResponseHead *responseHead,
+ nsISupports *securityInfo)
+{
+ nsresult rv;
+
+ LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] begin", self));
+ // Store secure data in memory only
+ if (securityInfo)
+ entry->SetSecurityInfo(securityInfo);
+
+ // Store the HTTP request method with the cache entry so we can distinguish
+ // for example GET and HEAD responses.
+ nsAutoCString method;
+ requestHead->Method(method);
+ rv = entry->SetMetaDataElement("request-method", method.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // Store the HTTP authorization scheme used if any...
+ rv = StoreAuthorizationMetaData(entry, requestHead);
+ if (NS_FAILED(rv)) return rv;
+
+ // Iterate over the headers listed in the Vary response header, and
+ // store the value of the corresponding request header so we can verify
+ // that it has not varied when we try to re-use the cached response at
+ // a later time. Take care to store "Cookie" headers only as hashes
+ // due to security considerations and the fact that they can be pretty
+ // large (bug 468426). We take care of "Vary: cookie" in ResponseWouldVary.
+ //
+ // NOTE: if "Vary: accept, cookie", then we will store the "accept" header
+ // in the cache. we could try to avoid needlessly storing the "accept"
+ // header in this case, but it doesn't seem worth the extra code to perform
+ // the check.
+ {
+ nsAutoCString buf, metaKey;
+ responseHead->GetHeader(nsHttp::Vary, buf);
+ if (!buf.IsEmpty()) {
+ NS_NAMED_LITERAL_CSTRING(prefix, "request-");
+
+ char *bufData = buf.BeginWriting(); // going to munge buf
+ char *token = nsCRT::strtok(bufData, NS_HTTP_HEADER_SEPS, &bufData);
+ while (token) {
+ LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \
+ "processing %s", self, token));
+ if (*token != '*') {
+ nsHttpAtom atom = nsHttp::ResolveAtom(token);
+ nsAutoCString val;
+ nsAutoCString hash;
+ if (NS_SUCCEEDED(requestHead->GetHeader(atom, val))) {
+ // If cookie-header, store a hash of the value
+ if (atom == nsHttp::Cookie) {
+ LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \
+ "cookie-value %s", self, val.get()));
+ rv = Hash(val.get(), hash);
+ // If hash failed, store a string not very likely
+ // to be the result of subsequent hashes
+ if (NS_FAILED(rv)) {
+ val = NS_LITERAL_CSTRING("<hash failed>");
+ } else {
+ val = hash;
+ }
+
+ LOG((" hashed to %s\n", val.get()));
+ }
+
+ // build cache meta data key and set meta data element...
+ metaKey = prefix + nsDependentCString(token);
+ entry->SetMetaDataElement(metaKey.get(), val.get());
+ } else {
+ LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \
+ "clearing metadata for %s", self, token));
+ metaKey = prefix + nsDependentCString(token);
+ entry->SetMetaDataElement(metaKey.get(), nullptr);
+ }
+ }
+ token = nsCRT::strtok(bufData, NS_HTTP_HEADER_SEPS, &bufData);
+ }
+ }
+ }
+
+ // Store the received HTTP head with the cache entry as an element of
+ // the meta data.
+ nsAutoCString head;
+ responseHead->Flatten(head, true);
+ rv = entry->SetMetaDataElement("response-head", head.get());
+ if (NS_FAILED(rv)) return rv;
+ head.Truncate();
+ responseHead->FlattenNetworkOriginalHeaders(head);
+ rv = entry->SetMetaDataElement("original-response-headers", head.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // Indicate we have successfully finished setting metadata on the cache entry.
+ rv = entry->MetaDataReady();
+
+ return rv;
+}
+
+nsresult
+nsHttpChannel::AddCacheEntryHeaders(nsICacheEntry *entry)
+{
+ return DoAddCacheEntryHeaders(this, entry, &mRequestHead, mResponseHead, mSecurityInfo);
+}
+
+inline void
+GetAuthType(const char *challenge, nsCString &authType)
+{
+ const char *p;
+
+ // get the challenge type
+ if ((p = strchr(challenge, ' ')) != nullptr)
+ authType.Assign(challenge, p - challenge);
+ else
+ authType.Assign(challenge);
+}
+
+nsresult
+StoreAuthorizationMetaData(nsICacheEntry *entry, nsHttpRequestHead *requestHead)
+{
+ // Not applicable to proxy authorization...
+ nsAutoCString val;
+ if (NS_FAILED(requestHead->GetHeader(nsHttp::Authorization, val))) {
+ return NS_OK;
+ }
+
+ // eg. [Basic realm="wally world"]
+ nsAutoCString buf;
+ GetAuthType(val.get(), buf);
+ return entry->SetMetaDataElement("auth", buf.get());
+}
+
+// Finalize the cache entry
+// - may need to rewrite response headers if any headers changed
+// - may need to recalculate the expiration time if any headers changed
+// - called only for freshly written cache entries
+nsresult
+nsHttpChannel::FinalizeCacheEntry()
+{
+ LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p]\n", this));
+
+ // Don't update this meta-data on 304
+ if (mStronglyFramed && !mCachedContentIsValid && mCacheEntry) {
+ LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p] Is Strongly Framed\n", this));
+ mCacheEntry->SetMetaDataElement("strongly-framed", "1");
+ }
+
+ if (mResponseHead && mResponseHeadersModified) {
+ // Set the expiration time for this cache entry
+ nsresult rv = UpdateExpirationTime();
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+// Open an output stream to the cache entry and insert a listener tee into
+// the chain of response listeners.
+nsresult
+nsHttpChannel::InstallCacheListener(int64_t offset)
+{
+ nsresult rv;
+
+ LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));
+
+ MOZ_ASSERT(mCacheEntry);
+ MOZ_ASSERT(mCacheEntryIsWriteOnly || mCachedContentIsPartial);
+ MOZ_ASSERT(mListener);
+
+ nsAutoCString contentEncoding, contentType;
+ mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+ mResponseHead->ContentType(contentType);
+ // If the content is compressible and the server has not compressed it,
+ // mark the cache entry for compression.
+ if (contentEncoding.IsEmpty() &&
+ (contentType.EqualsLiteral(TEXT_HTML) ||
+ contentType.EqualsLiteral(TEXT_PLAIN) ||
+ contentType.EqualsLiteral(TEXT_CSS) ||
+ contentType.EqualsLiteral(TEXT_JAVASCRIPT) ||
+ contentType.EqualsLiteral(TEXT_ECMASCRIPT) ||
+ contentType.EqualsLiteral(TEXT_XML) ||
+ contentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
+ contentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
+ contentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
+ contentType.EqualsLiteral(APPLICATION_XHTML_XML))) {
+ rv = mCacheEntry->SetMetaDataElement("uncompressed-len", "0");
+ if (NS_FAILED(rv)) {
+ LOG(("unable to mark cache entry for compression"));
+ }
+ }
+
+ LOG(("Trading cache input stream for output stream [channel=%p]", this));
+
+ // We must close the input stream first because cache entries do not
+ // correctly handle having an output stream and input streams open at
+ // the same time.
+ mCacheInputStream.CloseAndRelease();
+
+ nsCOMPtr<nsIOutputStream> out;
+ rv = mCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG((" entry doomed, not writing it [channel=%p]", this));
+ // Entry is already doomed.
+ // This may happen when expiration time is set to past and the entry
+ // has been removed by the background eviction logic.
+ return NS_OK;
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ if (mCacheOnlyMetadata) {
+ LOG(("Not storing content, cacheOnlyMetadata set"));
+ // We must open and then close the output stream of the cache entry.
+ // This way we indicate the content has been written (despite with zero
+ // length) and the entry is now in the ready state with "having data".
+
+ out->Close();
+ return NS_OK;
+ }
+
+ // XXX disk cache does not support overlapped i/o yet
+#if 0
+ // Mark entry valid inorder to allow simultaneous reading...
+ rv = mCacheEntry->MarkValid();
+ if (NS_FAILED(rv)) return rv;
+#endif
+
+ nsCOMPtr<nsIStreamListenerTee> tee =
+ do_CreateInstance(kStreamListenerTeeCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIEventTarget> cacheIOTarget;
+ if (!CacheObserver::UseNewCache()) {
+ nsCOMPtr<nsICacheStorageService> serv =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ serv->GetIoTarget(getter_AddRefs(cacheIOTarget));
+ }
+
+ if (!cacheIOTarget) {
+ LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%x "
+ "cacheIOTarget=%p", tee.get(), rv, cacheIOTarget.get()));
+ rv = tee->Init(mListener, out, nullptr);
+ } else {
+ LOG(("nsHttpChannel::InstallCacheListener async tee %p", tee.get()));
+ rv = tee->InitAsync(mListener, cacheIOTarget, out, nullptr);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+ mListener = tee;
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::InstallOfflineCacheListener(int64_t offset)
+{
+ nsresult rv;
+
+ LOG(("Preparing to write data into the offline cache [uri=%s]\n",
+ mSpec.get()));
+
+ MOZ_ASSERT(mOfflineCacheEntry);
+ MOZ_ASSERT(mListener);
+
+ nsCOMPtr<nsIOutputStream> out;
+ rv = mOfflineCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStreamListenerTee> tee =
+ do_CreateInstance(kStreamListenerTeeCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = tee->Init(mListener, out, nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ mListener = tee;
+
+ return NS_OK;
+}
+
+void
+nsHttpChannel::ClearBogusContentEncodingIfNeeded()
+{
+ // For .gz files, apache sends both a Content-Type: application/x-gzip
+ // as well as Content-Encoding: gzip, which is completely wrong. In
+ // this case, we choose to ignore the rogue Content-Encoding header. We
+ // must do this early on so as to prevent it from being seen up stream.
+ // The same problem exists for Content-Encoding: compress in default
+ // Apache installs.
+ nsAutoCString contentType;
+ mResponseHead->ContentType(contentType);
+ if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "gzip") && (
+ contentType.EqualsLiteral(APPLICATION_GZIP) ||
+ contentType.EqualsLiteral(APPLICATION_GZIP2) ||
+ contentType.EqualsLiteral(APPLICATION_GZIP3))) {
+ // clear the Content-Encoding header
+ mResponseHead->ClearHeader(nsHttp::Content_Encoding);
+ }
+ else if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "compress") && (
+ contentType.EqualsLiteral(APPLICATION_COMPRESS) ||
+ contentType.EqualsLiteral(APPLICATION_COMPRESS2))) {
+ // clear the Content-Encoding header
+ mResponseHead->ClearHeader(nsHttp::Content_Encoding);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <redirect>
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpChannel::SetupReplacementChannel(nsIURI *newURI,
+ nsIChannel *newChannel,
+ bool preserveMethod,
+ uint32_t redirectFlags)
+{
+ LOG(("nsHttpChannel::SetupReplacementChannel "
+ "[this=%p newChannel=%p preserveMethod=%d]",
+ this, newChannel, preserveMethod));
+
+ nsresult rv =
+ HttpBaseChannel::SetupReplacementChannel(newURI, newChannel,
+ preserveMethod, redirectFlags);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+ if (!httpChannel)
+ return NS_OK; // no other options to set
+
+ // convey the mApplyConversion flag (bug 91862)
+ nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
+ if (encodedChannel)
+ encodedChannel->SetApplyConversion(mApplyConversion);
+
+ // transfer the resume information
+ if (mResuming) {
+ nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(newChannel));
+ if (!resumableChannel) {
+ NS_WARNING("Got asked to resume, but redirected to non-resumable channel!");
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+ resumableChannel->ResumeAt(mStartPos, mEntityID);
+ }
+
+ if (!(redirectFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE) &&
+ mInterceptCache != INTERCEPTED) {
+ // Ensure that internally-redirected channels, or loads with manual
+ // redirect mode cannot be intercepted, which would look like two
+ // separate requests to the nsINetworkInterceptController.
+ if (mRedirectMode != nsIHttpChannelInternal::REDIRECT_MODE_MANUAL ||
+ (redirectFlags & (nsIChannelEventSink::REDIRECT_TEMPORARY |
+ nsIChannelEventSink::REDIRECT_PERMANENT)) == 0) {
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
+ rv = newChannel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+ rv = newChannel->SetLoadFlags(loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::AsyncProcessRedirection(uint32_t redirectType)
+{
+ LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n",
+ this, redirectType));
+
+ nsAutoCString location;
+
+ // if a location header was not given, then we can't perform the redirect,
+ // so just carry on as though this were a normal response.
+ if (NS_FAILED(mResponseHead->GetHeader(nsHttp::Location, location)))
+ return NS_ERROR_FAILURE;
+
+ // make sure non-ASCII characters in the location header are escaped.
+ nsAutoCString locationBuf;
+ if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII, locationBuf))
+ location = locationBuf;
+
+ if (mRedirectionLimit == 0) {
+ LOG(("redirection limit reached!\n"));
+ return NS_ERROR_REDIRECT_LOOP;
+ }
+
+ mRedirectType = redirectType;
+
+ LOG(("redirecting to: %s [redirection-limit=%u]\n",
+ location.get(), uint32_t(mRedirectionLimit)));
+
+ nsresult rv = CreateNewURI(location.get(), getter_AddRefs(mRedirectURI));
+
+ if (NS_FAILED(rv)) {
+ LOG(("Invalid URI for redirect: Location: %s\n", location.get()));
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (mApplicationCache) {
+ // if we are redirected to a different origin check if there is a fallback
+ // cache entry to fall back to. we don't care about file strict
+ // checking, at least mURI is not a file URI.
+ if (!NS_SecurityCompareURIs(mURI, mRedirectURI, false)) {
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
+ bool waitingForRedirectCallback;
+ (void)ProcessFallback(&waitingForRedirectCallback);
+ if (waitingForRedirectCallback)
+ return NS_OK;
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
+ }
+ }
+
+ return ContinueProcessRedirectionAfterFallback(NS_OK);
+}
+
+nsresult
+nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv)
+{
+ if (NS_SUCCEEDED(rv) && mFallingBack) {
+ // do not continue with redirect processing, fallback is in
+ // progress now.
+ return NS_OK;
+ }
+
+ // Kill the current cache entry if we are redirecting
+ // back to ourself.
+ bool redirectingBackToSameURI = false;
+ if (mCacheEntry && mCacheEntryIsWriteOnly &&
+ NS_SUCCEEDED(mURI->Equals(mRedirectURI, &redirectingBackToSameURI)) &&
+ redirectingBackToSameURI)
+ mCacheEntry->AsyncDoom(nullptr);
+
+ bool hasRef = false;
+ rv = mRedirectURI->GetHasRef(&hasRef);
+
+ // move the reference of the old location to the new one if the new
+ // one has none.
+ if (NS_SUCCEEDED(rv) && !hasRef) {
+ nsAutoCString ref;
+ mURI->GetRef(ref);
+ if (!ref.IsEmpty()) {
+ // NOTE: SetRef will fail if mRedirectURI is immutable
+ // (e.g. an about: URI)... Oh well.
+ mRedirectURI->SetRef(ref);
+ }
+ }
+
+ bool rewriteToGET = ShouldRewriteRedirectToGET(mRedirectType,
+ mRequestHead.ParsedMethod());
+
+ // prompt if the method is not safe (such as POST, PUT, DELETE, ...)
+ if (!rewriteToGET && !mRequestHead.IsSafeMethod()) {
+ rv = PromptTempRedirect();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel),
+ mRedirectURI,
+ mLoadInfo,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL,
+ ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t redirectFlags;
+ if (nsHttp::IsPermanentRedirect(mRedirectType))
+ redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
+ else
+ redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
+
+ rv = SetupReplacementChannel(mRedirectURI, newChannel,
+ !rewriteToGET, redirectFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // verify that this is a legal redirect
+ mRedirectChannel = newChannel;
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
+
+ if (NS_SUCCEEDED(rv))
+ rv = WaitForRedirectCallback();
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this);
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
+ }
+
+ return rv;
+}
+
+nsresult
+nsHttpChannel::ContinueProcessRedirection(nsresult rv)
+{
+ AutoRedirectVetoNotifier notifier(this);
+
+ LOG(("nsHttpChannel::ContinueProcessRedirection [rv=%x,this=%p]\n", rv,
+ this));
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
+
+ // Make sure to do this after we received redirect veto answer,
+ // i.e. after all sinks had been notified
+ mRedirectChannel->SetOriginalURI(mOriginalURI);
+
+ // And now, the deprecated way
+ nsCOMPtr<nsIHttpEventSink> httpEventSink;
+ GetCallback(httpEventSink);
+ if (httpEventSink) {
+ // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
+ // versions.
+ rv = httpEventSink->OnRedirect(this, mRedirectChannel);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ // XXX we used to talk directly with the script security manager, but that
+ // should really be handled by the event sink implementation.
+
+ // begin loading the new channel
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
+ rv = mRedirectChannel->AsyncOpen2(mListener);
+ }
+ else {
+ rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // close down this channel
+ Cancel(NS_BINDING_REDIRECTED);
+
+ notifier.RedirectSucceeded();
+
+ ReleaseListeners();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <auth>
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP nsHttpChannel::OnAuthAvailable()
+{
+ LOG(("nsHttpChannel::OnAuthAvailable [this=%p]", this));
+
+ // setting mAuthRetryPending flag and resuming the transaction
+ // triggers process of throwing away the unauthenticated data already
+ // coming from the network
+ mAuthRetryPending = true;
+ mProxyAuthPending = false;
+ LOG(("Resuming the transaction, we got credentials from user"));
+ mTransactionPump->Resume();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(bool userCancel)
+{
+ LOG(("nsHttpChannel::OnAuthCancelled [this=%p]", this));
+
+ if (mTransactionPump) {
+ // If the channel is trying to authenticate to a proxy and
+ // that was canceled we cannot show the http response body
+ // from the 40x as that might mislead the user into thinking
+ // it was a end host response instead of a proxy reponse.
+ // This must check explicitly whether a proxy auth was being done
+ // because we do want to show the content if this is an error from
+ // the origin server.
+ if (mProxyAuthPending)
+ Cancel(NS_ERROR_PROXY_CONNECTION_REFUSED);
+
+ // ensure call of OnStartRequest of the current listener here,
+ // it would not be called otherwise at all
+ nsresult rv = CallOnStartRequest();
+
+ // drop mAuthRetryPending flag and resume the transaction
+ // this resumes load of the unauthenticated content data (which
+ // may have been canceled if we don't want to show it)
+ mAuthRetryPending = false;
+ LOG(("Resuming the transaction, user cancelled the auth dialog"));
+ mTransactionPump->Resume();
+
+ if (NS_FAILED(rv))
+ mTransactionPump->Cancel(rv);
+ }
+
+ mProxyAuthPending = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::CloseStickyConnection()
+{
+ LOG(("nsHttpChannel::CloseStickyConnection this=%p", this));
+
+ // Require we are between OnStartRequest and OnStopRequest, because
+ // what we do here takes effect in OnStopRequest (not reusing the
+ // connection for next authentication round).
+ if (!mIsPending) {
+ LOG((" channel not pending"));
+ NS_ERROR("CloseStickyConnection not called before OnStopRequest, won't have any effect");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mTransaction);
+ if (!mTransaction) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!(mCaps & NS_HTTP_STICKY_CONNECTION ||
+ mTransaction->Caps() & NS_HTTP_STICKY_CONNECTION)) {
+ LOG((" not sticky"));
+ return NS_OK;
+ }
+
+ RefPtr<nsAHttpConnection> conn = mTransaction->GetConnectionReference();
+ if (!conn) {
+ LOG((" no connection"));
+ return NS_OK;
+ }
+
+ // This turns the IsPersistent() indicator on the connection to false,
+ // and makes us throw it away in OnStopRequest.
+ conn->DontReuse();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::ConnectionRestartable(bool aRestartable)
+{
+ LOG(("nsHttpChannel::ConnectionRestartable this=%p, restartable=%d",
+ this, aRestartable));
+ mAuthConnectionRestartable = aRestartable;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF_INHERITED(nsHttpChannel, HttpBaseChannel)
+NS_IMPL_RELEASE_INHERITED(nsHttpChannel, HttpBaseChannel)
+
+NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel)
+ NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
+ NS_INTERFACE_MAP_ENTRY(nsICacheEntryOpenCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpAuthenticableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
+ NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsICorsPreflightCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIHstsPrimingCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelWithDivertableParentListener)
+ // we have no macro that covers this case.
+ if (aIID.Equals(NS_GET_IID(nsHttpChannel)) ) {
+ AddRef();
+ *aInstancePtr = this;
+ return NS_OK;
+ } else
+NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::Cancel(nsresult status)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // We should never have a pump open while a CORS preflight is in progress.
+ MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
+
+ LOG(("nsHttpChannel::Cancel [this=%p status=%x]\n", this, status));
+ if (mCanceled) {
+ LOG((" ignoring; already canceled\n"));
+ return NS_OK;
+ }
+ if (mWaitingForRedirectCallback) {
+ LOG(("channel canceled during wait for redirect callback"));
+ }
+ mCanceled = true;
+ mStatus = status;
+ if (mProxyRequest)
+ mProxyRequest->Cancel(status);
+ if (mTransaction)
+ gHttpHandler->CancelTransaction(mTransaction, status);
+ if (mTransactionPump)
+ mTransactionPump->Cancel(status);
+ mCacheInputStream.CloseAndRelease();
+ if (mCachePump)
+ mCachePump->Cancel(status);
+ if (mAuthProvider)
+ mAuthProvider->Cancel(status);
+ if (mPreflightChannel)
+ mPreflightChannel->Cancel(status);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Suspend()
+{
+ nsresult rv = SuspendInternal();
+
+ nsresult rvParentChannel = NS_OK;
+ if (mParentChannel) {
+ rvParentChannel = mParentChannel->SuspendMessageDiversion();
+ }
+
+ return NS_FAILED(rv) ? rv : rvParentChannel;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Resume()
+{
+ nsresult rv = ResumeInternal();
+
+ nsresult rvParentChannel = NS_OK;
+ if (mParentChannel) {
+ rvParentChannel = mParentChannel->ResumeMessageDiversion();
+ }
+
+ return NS_FAILED(rv) ? rv : rvParentChannel;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetSecurityInfo(nsISupports **securityInfo)
+{
+ NS_ENSURE_ARG_POINTER(securityInfo);
+ *securityInfo = mSecurityInfo;
+ NS_IF_ADDREF(*securityInfo);
+ return NS_OK;
+}
+
+// If any of the functions that AsyncOpen calls returns immediately an error
+// AsyncAbort(which calls onStart/onStopRequest) does not need to be call.
+// To be sure that they are not call ReleaseListeners() is called.
+// If AsyncOpen returns NS_OK, after that point AsyncAbort must be called on
+// any error.
+NS_IMETHODIMP
+nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ LOG(("nsHttpChannel::AsyncOpen [this=%p]\n", this));
+
+ NS_CompareLoadInfoAndLoadContext(this);
+
+#ifdef DEBUG
+ AssertPrivateBrowsingId();
+#endif
+
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ nsresult rv;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gHttpHandler->Active()) {
+ LOG((" after HTTP shutdown..."));
+ ReleaseListeners();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv)) {
+ ReleaseListeners();
+ return rv;
+ }
+
+ if (mInterceptCache != INTERCEPTED && ShouldIntercept()) {
+ mInterceptCache = MAYBE_INTERCEPT;
+ SetCouldBeSynthesized();
+ }
+
+ // Remember the cookie header that was set, if any
+ nsAutoCString cookieHeader;
+ if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookieHeader))) {
+ mUserSetCookieHeader = cookieHeader;
+ }
+
+ AddCookiesToRequest();
+
+ // After we notify any observers (on-opening-request, loadGroup, etc) we
+ // must return NS_OK and return any errors asynchronously via
+ // OnStart/OnStopRequest. Observers may add a reference to the channel
+ // and expect to get OnStopRequest so they know when to drop the reference,
+ // etc.
+
+ // notify "http-on-opening-request" observers, but not if this is a redirect
+ if (!(mLoadFlags & LOAD_REPLACE)) {
+ gHttpHandler->OnOpeningRequest(this);
+ }
+
+ // Set user agent override
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ mIsPending = true;
+ mWasOpened = true;
+
+ mListener = listener;
+ mListenerContext = context;
+
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ // record asyncopen time unconditionally and clear it if we
+ // don't want it after OnModifyRequest() weighs in. But waiting for
+ // that to complete would mean we don't include proxy resolution in the
+ // timing.
+ mAsyncOpenTime = TimeStamp::Now();
+
+ // Remember we have Authorization header set here. We need to check on it
+ // just once and early, AsyncOpen is the best place.
+ mCustomAuthHeader = mRequestHead.HasHeader(nsHttp::Authorization);
+
+ // The common case for HTTP channels is to begin proxy resolution and return
+ // at this point. The only time we know mProxyInfo already is if we're
+ // proxying a non-http protocol like ftp.
+ if (!mProxyInfo && NS_SUCCEEDED(ResolveProxy())) {
+ return NS_OK;
+ }
+
+ rv = BeginConnect();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ReleaseListeners();
+ return rv;
+ }
+ return AsyncOpen(listener, nullptr);
+}
+
+// BeginConnect() SHOULD NOT call AsyncAbort(). AsyncAbort will be called by
+// functions that called BeginConnect if needed. Only AsyncOpen and
+// OnProxyAvailable ever call BeginConnect.
+nsresult
+nsHttpChannel::BeginConnect()
+{
+ LOG(("nsHttpChannel::BeginConnect [this=%p]\n", this));
+ nsresult rv;
+
+ // Construct connection info object
+ nsAutoCString host;
+ nsAutoCString scheme;
+ int32_t port = -1;
+ bool isHttps = false;
+
+ rv = mURI->GetScheme(scheme);
+ if (NS_SUCCEEDED(rv))
+ rv = mURI->SchemeIs("https", &isHttps);
+ if (NS_SUCCEEDED(rv))
+ rv = mURI->GetAsciiHost(host);
+ if (NS_SUCCEEDED(rv))
+ rv = mURI->GetPort(&port);
+ if (NS_SUCCEEDED(rv))
+ mURI->GetUsername(mUsername);
+ if (NS_SUCCEEDED(rv))
+ rv = mURI->GetAsciiSpec(mSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Reject the URL if it doesn't specify a host
+ if (host.IsEmpty()) {
+ rv = NS_ERROR_MALFORMED_URI;
+ return rv;
+ }
+ LOG(("host=%s port=%d\n", host.get(), port));
+ LOG(("uri=%s\n", mSpec.get()));
+
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (mProxyInfo)
+ proxyInfo = do_QueryInterface(mProxyInfo);
+
+ mRequestHead.SetHTTPS(isHttps);
+ mRequestHead.SetOrigin(scheme, host, port);
+
+ SetDoNotTrack();
+
+ NeckoOriginAttributes originAttributes;
+ NS_GetOriginAttributes(this, originAttributes);
+
+ RefPtr<AltSvcMapping> mapping;
+ if (!mConnectionInfo && mAllowAltSvc && // per channel
+ !(mLoadFlags & LOAD_FRESH_CONNECTION) &&
+ (scheme.Equals(NS_LITERAL_CSTRING("http")) ||
+ scheme.Equals(NS_LITERAL_CSTRING("https"))) &&
+ (!proxyInfo || proxyInfo->IsDirect()) &&
+ (mapping = gHttpHandler->GetAltServiceMapping(scheme,
+ host, port,
+ mPrivateBrowsing))) {
+ LOG(("nsHttpChannel %p Alt Service Mapping Found %s://%s:%d [%s]\n",
+ this, scheme.get(), mapping->AlternateHost().get(),
+ mapping->AlternatePort(), mapping->HashKey().get()));
+
+ if (!(mLoadFlags & LOAD_ANONYMOUS) && !mPrivateBrowsing) {
+ nsAutoCString altUsedLine(mapping->AlternateHost());
+ bool defaultPort = mapping->AlternatePort() ==
+ (isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT);
+ if (!defaultPort) {
+ altUsedLine.AppendLiteral(":");
+ altUsedLine.AppendInt(mapping->AlternatePort());
+ }
+ mRequestHead.SetHeader(nsHttp::Alternate_Service_Used, altUsedLine);
+ }
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ nsAutoString message(NS_LITERAL_STRING("Alternate Service Mapping found: "));
+ AppendASCIItoUTF16(scheme.get(), message);
+ message.Append(NS_LITERAL_STRING("://"));
+ AppendASCIItoUTF16(host.get(), message);
+ message.Append(NS_LITERAL_STRING(":"));
+ message.AppendInt(port);
+ message.Append(NS_LITERAL_STRING(" to "));
+ AppendASCIItoUTF16(scheme.get(), message);
+ message.Append(NS_LITERAL_STRING("://"));
+ AppendASCIItoUTF16(mapping->AlternateHost().get(), message);
+ message.Append(NS_LITERAL_STRING(":"));
+ message.AppendInt(mapping->AlternatePort());
+ consoleService->LogStringMessage(message.get());
+ }
+
+ LOG(("nsHttpChannel %p Using connection info from altsvc mapping", this));
+ mapping->GetConnectionInfo(getter_AddRefs(mConnectionInfo), proxyInfo, originAttributes);
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, true);
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC_OE, !isHttps);
+ } else if (mConnectionInfo) {
+ LOG(("nsHttpChannel %p Using channel supplied connection info", this));
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
+ } else {
+ LOG(("nsHttpChannel %p Using default connection info", this));
+
+ mConnectionInfo = new nsHttpConnectionInfo(host, port, EmptyCString(), mUsername, proxyInfo,
+ originAttributes, isHttps);
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
+ }
+
+ // Set network interface id only when it's not empty to avoid
+ // rebuilding hash key.
+ if (!mNetworkInterfaceId.IsEmpty()) {
+ mConnectionInfo->SetNetworkInterfaceId(mNetworkInterfaceId);
+ }
+
+ mAuthProvider =
+ do_CreateInstance("@mozilla.org/network/http-channel-auth-provider;1",
+ &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = mAuthProvider->Init(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // check to see if authorization headers should be included
+ // mCustomAuthHeader is set in AsyncOpen if we find Authorization header
+ mAuthProvider->AddAuthorizationHeaders(mCustomAuthHeader);
+
+ // notify "http-on-modify-request" observers
+ CallOnModifyRequestObservers();
+
+ SetLoadGroupUserAgentOverride();
+
+ // Check if request was cancelled during on-modify-request or on-useragent.
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume BeginConnect [this=%p]\n", this));
+ MOZ_ASSERT(!mCallOnResume);
+ mCallOnResume = &nsHttpChannel::HandleBeginConnectContinue;
+ return NS_OK;
+ }
+
+ return BeginConnectContinue();
+}
+
+void
+nsHttpChannel::HandleBeginConnectContinue()
+{
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+ nsresult rv;
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume BeginConnect [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::HandleBeginConnectContinue;
+ return;
+ }
+
+ LOG(("nsHttpChannel::HandleBeginConnectContinue [this=%p]\n", this));
+ rv = BeginConnectContinue();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(rv);
+ }
+}
+
+nsresult
+nsHttpChannel::BeginConnectContinue()
+{
+ nsresult rv;
+
+ // Check if request was cancelled during suspend AFTER on-modify-request or
+ // on-useragent.
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ // Check to see if we should redirect this channel elsewhere by
+ // nsIHttpChannel.redirectTo API request
+ if (mAPIRedirectToURI) {
+ return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
+ }
+ // Check to see if this principal exists on local blocklists.
+ RefPtr<nsChannelClassifier> channelClassifier = new nsChannelClassifier();
+ if (mLoadFlags & LOAD_CLASSIFY_URI) {
+ nsCOMPtr<nsIURIClassifier> classifier = do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID);
+ bool tpEnabled = false;
+ channelClassifier->ShouldEnableTrackingProtection(this, &tpEnabled);
+ if (classifier && tpEnabled) {
+ // We skip speculative connections by setting mLocalBlocklist only
+ // when tracking protection is enabled. Though we could do this for
+ // both phishing and malware, it is not necessary for correctness,
+ // since no network events will be received while the
+ // nsChannelClassifier is in progress. See bug 1122691.
+ nsCOMPtr<nsIURI> uri;
+ rv = GetURI(getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv) && uri) {
+ nsAutoCString tables;
+ Preferences::GetCString("urlclassifier.trackingTable", &tables);
+ nsAutoCString results;
+ rv = classifier->ClassifyLocalWithTables(uri, tables, results);
+ if (NS_SUCCEEDED(rv) && !results.IsEmpty()) {
+ LOG(("nsHttpChannel::ClassifyLocalWithTables found "
+ "uri on local tracking blocklist [this=%p]",
+ this));
+ mLocalBlocklist = true;
+ } else {
+ LOG(("nsHttpChannel::ClassifyLocalWithTables no result "
+ "found [this=%p]", this));
+ }
+ }
+ }
+ }
+
+ // If mTimingEnabled flag is not set after OnModifyRequest() then
+ // clear the already recorded AsyncOpen value for consistency.
+ if (!mTimingEnabled)
+ mAsyncOpenTime = TimeStamp();
+
+ // when proxying only use the pipeline bit if ProxyPipelining() allows it.
+ if (!mConnectionInfo->UsingConnect() && mConnectionInfo->UsingHttpProxy()) {
+ mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
+ if (gHttpHandler->ProxyPipelining())
+ mCaps |= NS_HTTP_ALLOW_PIPELINING;
+ }
+
+ // if this somehow fails we can go on without it
+ gHttpHandler->AddConnectionHeader(&mRequestHead, mCaps);
+
+ if (mLoadFlags & VALIDATE_ALWAYS || BYPASS_LOCAL_CACHE(mLoadFlags))
+ mCaps |= NS_HTTP_REFRESH_DNS;
+
+ if (!mLocalBlocklist && !mConnectionInfo->UsingHttpProxy() &&
+ !(mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE))) {
+ // Start a DNS lookup very early in case the real open is queued the DNS can
+ // happen in parallel. Do not do so in the presence of an HTTP proxy as
+ // all lookups other than for the proxy itself are done by the proxy.
+ // Also we don't do a lookup if the LOAD_NO_NETWORK_IO or
+ // LOAD_ONLY_FROM_CACHE flags are set.
+ //
+ // We keep the DNS prefetch object around so that we can retrieve
+ // timing information from it. There is no guarantee that we actually
+ // use the DNS prefetch data for the real connection, but as we keep
+ // this data around for 3 minutes by default, this should almost always
+ // be correct, and even when it isn't, the timing still represents _a_
+ // valid DNS lookup timing for the site, even if it is not _the_
+ // timing we used.
+ LOG(("nsHttpChannel::BeginConnect [this=%p] prefetching%s\n",
+ this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : ""));
+ mDNSPrefetch = new nsDNSPrefetch(mURI, this, mTimingEnabled);
+ mDNSPrefetch->PrefetchHigh(mCaps & NS_HTTP_REFRESH_DNS);
+ }
+
+ // Adjust mCaps according to our request headers:
+ // - If "Connection: close" is set as a request header, then do not bother
+ // trying to establish a keep-alive connection.
+ if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close"))
+ mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
+
+ if (gHttpHandler->CriticalRequestPrioritization()) {
+ if (mClassOfService & nsIClassOfService::Leader) {
+ mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
+ }
+ if (mClassOfService & nsIClassOfService::Unblocked) {
+ mCaps |= NS_HTTP_LOAD_UNBLOCKED;
+ }
+ }
+
+ // Force-Reload should reset the persistent connection pool for this host
+ if (mLoadFlags & LOAD_FRESH_CONNECTION) {
+ // just the initial document resets the whole pool
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+ gHttpHandler->ConnMgr()->ClearAltServiceMappings();
+ gHttpHandler->ConnMgr()->DoShiftReloadConnectionCleanup(mConnectionInfo);
+ }
+ mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
+ }
+
+ // We may have been cancelled already, either by on-modify-request
+ // listeners or load group observers; in that case, we should not send the
+ // request to the server
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (!(mLoadFlags & LOAD_CLASSIFY_URI)) {
+ return ContinueBeginConnectWithResult();
+ }
+
+ // mLocalBlocklist is true only if tracking protection is enabled and the
+ // URI is a tracking domain, it makes no guarantees about phishing or
+ // malware, so if LOAD_CLASSIFY_URI is true we must call
+ // nsChannelClassifier to catch phishing and malware URIs.
+ bool callContinueBeginConnect = true;
+ if (!mLocalBlocklist) {
+ // Here we call ContinueBeginConnectWithResult and not
+ // ContinueBeginConnect so that in the case of an error we do not start
+ // channelClassifier.
+ rv = ContinueBeginConnectWithResult();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ callContinueBeginConnect = false;
+ }
+ // nsChannelClassifier calls ContinueBeginConnect if it has not already
+ // been called, after optionally cancelling the channel once we have a
+ // remote verdict. We call a concrete class instead of an nsI* that might
+ // be overridden.
+ LOG(("nsHttpChannel::Starting nsChannelClassifier %p [this=%p]",
+ channelClassifier.get(), this));
+ channelClassifier->Start(this);
+ if (callContinueBeginConnect) {
+ return ContinueBeginConnectWithResult();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetEncodedBodySize(uint64_t *aEncodedBodySize)
+{
+ if (mCacheEntry && !mCacheEntryIsWriteOnly) {
+ int64_t dataSize = 0;
+ mCacheEntry->GetDataSize(&dataSize);
+ *aEncodedBodySize = dataSize;
+ } else {
+ *aEncodedBodySize = mLogicalOffset;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::SetupFallbackChannel(const char *aFallbackKey)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ LOG(("nsHttpChannel::SetupFallbackChannel [this=%p, key=%s]\n",
+ this, aFallbackKey));
+ mFallbackChannel = true;
+ mFallbackKey = aFallbackKey;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::ForceIntercepted(uint64_t aInterceptionID)
+{
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ if (NS_WARN_IF(mLoadFlags & LOAD_BYPASS_SERVICE_WORKER)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MarkIntercepted();
+ mResponseCouldBeSynthesized = true;
+ mInterceptionID = aInterceptionID;
+ return NS_OK;
+}
+
+mozilla::net::nsHttpChannel*
+nsHttpChannel::QueryHttpChannelImpl(void)
+{
+ return this;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsISupportsPriority
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::SetPriority(int32_t value)
+{
+ int16_t newValue = clamped<int32_t>(value, INT16_MIN, INT16_MAX);
+ if (mPriority == newValue)
+ return NS_OK;
+ mPriority = newValue;
+ if (mTransaction)
+ gHttpHandler->RescheduleTransaction(mTransaction, mPriority);
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::ContinueBeginConnectWithResult()
+{
+ LOG(("nsHttpChannel::ContinueBeginConnectWithResult [this=%p]", this));
+ NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+
+ nsresult rv;
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async connect [this=%p]\n", this));
+ mCallOnResume = &nsHttpChannel::ContinueBeginConnect;
+ rv = NS_OK;
+ } else if (mCanceled) {
+ // We may have been cancelled already, by nsChannelClassifier in that
+ // case, we should not send the request to the server
+ rv = mStatus;
+ } else {
+ rv = Connect();
+ }
+
+ LOG(("nsHttpChannel::ContinueBeginConnectWithResult result [this=%p rv=%x "
+ "mCanceled=%i]\n", this, rv, mCanceled));
+ return rv;
+}
+
+void
+nsHttpChannel::ContinueBeginConnect()
+{
+ nsresult rv = ContinueBeginConnectWithResult();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ AsyncAbort(rv);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannel::nsIClassOfService
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsHttpChannel::SetClassFlags(uint32_t inFlags)
+{
+ mClassOfService = inFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::AddClassFlags(uint32_t inFlags)
+{
+ mClassOfService |= inFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::ClearClassFlags(uint32_t inFlags)
+{
+ mClassOfService &= ~inFlags;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIProtocolProxyCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::OnProxyAvailable(nsICancelable *request, nsIChannel *channel,
+ nsIProxyInfo *pi, nsresult status)
+{
+ LOG(("nsHttpChannel::OnProxyAvailable [this=%p pi=%p status=%x mStatus=%x]\n",
+ this, pi, status, mStatus));
+ mProxyRequest = nullptr;
+
+ nsresult rv;
+
+ // If status is a failure code, then it means that we failed to resolve
+ // proxy info. That is a non-fatal error assuming it wasn't because the
+ // request was canceled. We just failover to DIRECT when proxy resolution
+ // fails (failure can mean that the PAC URL could not be loaded).
+
+ if (NS_SUCCEEDED(status))
+ mProxyInfo = pi;
+
+ if (!gHttpHandler->Active()) {
+ LOG(("nsHttpChannel::OnProxyAvailable [this=%p] "
+ "Handler no longer active.\n", this));
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ else {
+ rv = BeginConnect();
+ }
+
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ AsyncAbort(rv);
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIProxiedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetProxyInfo(nsIProxyInfo **result)
+{
+ if (!mConnectionInfo)
+ *result = mProxyInfo;
+ else
+ *result = mConnectionInfo->ProxyInfo();
+ NS_IF_ADDREF(*result);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsITimedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetDomainLookupStart(TimeStamp* _retval) {
+ if (mTransaction)
+ *_retval = mTransaction->GetDomainLookupStart();
+ else
+ *_retval = mTransactionTimings.domainLookupStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetDomainLookupEnd(TimeStamp* _retval) {
+ if (mTransaction)
+ *_retval = mTransaction->GetDomainLookupEnd();
+ else
+ *_retval = mTransactionTimings.domainLookupEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetConnectStart(TimeStamp* _retval) {
+ if (mTransaction)
+ *_retval = mTransaction->GetConnectStart();
+ else
+ *_retval = mTransactionTimings.connectStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetConnectEnd(TimeStamp* _retval) {
+ if (mTransaction)
+ *_retval = mTransaction->GetConnectEnd();
+ else
+ *_retval = mTransactionTimings.connectEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetRequestStart(TimeStamp* _retval) {
+ if (mTransaction)
+ *_retval = mTransaction->GetRequestStart();
+ else
+ *_retval = mTransactionTimings.requestStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetResponseStart(TimeStamp* _retval) {
+ if (mTransaction)
+ *_retval = mTransaction->GetResponseStart();
+ else
+ *_retval = mTransactionTimings.responseStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetResponseEnd(TimeStamp* _retval) {
+ if (mTransaction)
+ *_retval = mTransaction->GetResponseEnd();
+ else
+ *_retval = mTransactionTimings.responseEnd;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIHttpAuthenticableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetIsSSL(bool *aIsSSL)
+{
+ // this attribute is really misnamed - it wants to know if
+ // https:// is being used. SSL might be used to cover http://
+ // in some circumstances (proxies, http/2, etc..)
+ return mURI->SchemeIs("https", aIsSSL);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetProxyMethodIsConnect(bool *aProxyMethodIsConnect)
+{
+ *aProxyMethodIsConnect = mConnectionInfo->UsingConnect();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetServerResponseHeader(nsACString &value)
+{
+ if (!mResponseHead)
+ return NS_ERROR_NOT_AVAILABLE;
+ return mResponseHead->GetHeader(nsHttp::Server, value);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetProxyChallenges(nsACString &value)
+{
+ if (!mResponseHead)
+ return NS_ERROR_UNEXPECTED;
+ return mResponseHead->GetHeader(nsHttp::Proxy_Authenticate, value);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetWWWChallenges(nsACString &value)
+{
+ if (!mResponseHead)
+ return NS_ERROR_UNEXPECTED;
+ return mResponseHead->GetHeader(nsHttp::WWW_Authenticate, value);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetProxyCredentials(const nsACString &value)
+{
+ return mRequestHead.SetHeader(nsHttp::Proxy_Authorization, value);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetWWWCredentials(const nsACString &value)
+{
+ return mRequestHead.SetHeader(nsHttp::Authorization, value);
+}
+
+//-----------------------------------------------------------------------------
+// Methods that nsIHttpAuthenticableChannel dupes from other IDLs, which we
+// get from HttpBaseChannel, must be explicitly forwarded, because C++ sucks.
+//
+
+NS_IMETHODIMP
+nsHttpChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ return HttpBaseChannel::GetLoadFlags(aLoadFlags);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetURI(nsIURI **aURI)
+{
+ return HttpBaseChannel::GetURI(aURI);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
+{
+ return HttpBaseChannel::GetNotificationCallbacks(aCallbacks);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ return HttpBaseChannel::GetLoadGroup(aLoadGroup);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetRequestMethod(nsACString& aMethod)
+{
+ return HttpBaseChannel::GetRequestMethod(aMethod);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ nsresult rv;
+
+ PROFILER_LABEL("nsHttpChannel", "OnStartRequest",
+ js::ProfileEntry::Category::NETWORK);
+
+ if (!(mCanceled || NS_FAILED(mStatus))) {
+ // capture the request's status, so our consumers will know ASAP of any
+ // connection failures, etc - bug 93581
+ request->GetStatus(&mStatus);
+ }
+
+ LOG(("nsHttpChannel::OnStartRequest [this=%p request=%p status=%x]\n",
+ this, request, mStatus));
+
+ // Make sure things are what we expect them to be...
+ MOZ_ASSERT(request == mCachePump || request == mTransactionPump,
+ "Unexpected request");
+ MOZ_ASSERT(!(mTransactionPump && mCachePump) || mCachedContentIsPartial,
+ "If we have both pumps, the cache content must be partial");
+
+ mAfterOnStartRequestBegun = true;
+ mOnStartRequestTimestamp = TimeStamp::Now();
+
+ if (!mSecurityInfo && !mCachePump && mTransaction) {
+ // grab the security info from the connection object; the transaction
+ // is guaranteed to own a reference to the connection.
+ mSecurityInfo = mTransaction->SecurityInfo();
+ }
+
+ // don't enter this block if we're reading from the cache...
+ if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
+ // mTransactionPump doesn't hit OnInputStreamReady and call this until
+ // all of the response headers have been acquired, so we can take ownership
+ // of them from the transaction.
+ mResponseHead = mTransaction->TakeResponseHead();
+ // the response head may be null if the transaction was cancelled. in
+ // which case we just need to call OnStartRequest/OnStopRequest.
+ if (mResponseHead)
+ return ProcessResponse();
+
+ NS_WARNING("No response head in OnStartRequest");
+ }
+
+ // cache file could be deleted on our behalf, it could contain errors or
+ // it failed to allocate memory, reload from network here.
+ if (mCacheEntry && mCachePump && RECOVER_FROM_CACHE_FILE_ERROR(mStatus)) {
+ LOG((" cache file error, reloading from server"));
+ mCacheEntry->AsyncDoom(nullptr);
+ rv = StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+ if (NS_SUCCEEDED(rv))
+ return NS_OK;
+ }
+
+ // avoid crashing if mListener happens to be null...
+ if (!mListener) {
+ NS_NOTREACHED("mListener is null");
+ return NS_OK;
+ }
+
+ // before we start any content load, check for redirectTo being called
+ // this code is executed mainly before we start load from the cache
+ if (mAPIRedirectToURI && !mCanceled) {
+ nsAutoCString redirectToSpec;
+ mAPIRedirectToURI->GetAsciiSpec(redirectToSpec);
+ LOG((" redirectTo called with uri=%s", redirectToSpec.BeginReading()));
+
+ MOZ_ASSERT(!mOnStartRequestCalled);
+
+ nsCOMPtr<nsIURI> redirectTo;
+ mAPIRedirectToURI.swap(redirectTo);
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
+ rv = StartRedirectChannelToURI(redirectTo, nsIChannelEventSink::REDIRECT_TEMPORARY);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
+ }
+
+ // Hack: ContinueOnStartRequest1 uses NS_OK to detect successful redirects,
+ // so we distinguish this codepath (a non-redirect that's processing
+ // normally) by passing in a bogus error code.
+ return ContinueOnStartRequest1(NS_BINDING_FAILED);
+}
+
+nsresult
+nsHttpChannel::ContinueOnStartRequest1(nsresult result)
+{
+ if (NS_SUCCEEDED(result)) {
+ // Redirect has passed through, we don't want to go on with this
+ // channel. It will now be canceled by the redirect handling code
+ // that called this function.
+ return NS_OK;
+ }
+
+ // on proxy errors, try to failover
+ if (mConnectionInfo->ProxyInfo() &&
+ (mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
+ mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
+ mStatus == NS_ERROR_NET_TIMEOUT)) {
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest2);
+ if (NS_SUCCEEDED(ProxyFailover()))
+ return NS_OK;
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest2);
+ }
+
+ // Hack: ContinueOnStartRequest2 uses NS_OK to detect successful redirects,
+ // so we distinguish this codepath (a non-redirect that's processing
+ // normally) by passing in a bogus error code.
+ return ContinueOnStartRequest2(NS_BINDING_FAILED);
+}
+
+nsresult
+nsHttpChannel::ContinueOnStartRequest2(nsresult result)
+{
+ if (NS_SUCCEEDED(result)) {
+ // Redirect has passed through, we don't want to go on with this
+ // channel. It will now be canceled by the redirect handling code
+ // that called this function.
+ return NS_OK;
+ }
+
+ // on other request errors, try to fall back
+ if (NS_FAILED(mStatus)) {
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
+ bool waitingForRedirectCallback;
+ ProcessFallback(&waitingForRedirectCallback);
+ if (waitingForRedirectCallback)
+ return NS_OK;
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
+ }
+
+ return ContinueOnStartRequest3(NS_OK);
+}
+
+nsresult
+nsHttpChannel::ContinueOnStartRequest3(nsresult result)
+{
+ if (mFallingBack)
+ return NS_OK;
+
+ return CallOnStartRequest();
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
+{
+ PROFILER_LABEL("nsHttpChannel", "OnStopRequest",
+ js::ProfileEntry::Category::NETWORK);
+
+ LOG(("nsHttpChannel::OnStopRequest [this=%p request=%p status=%x]\n",
+ this, request, status));
+
+ MOZ_ASSERT(NS_IsMainThread(),
+ "OnStopRequest should only be called from the main thread");
+
+ if (NS_FAILED(status)) {
+ ProcessSecurityReport(status);
+ }
+
+ // If this load failed because of a security error, it may be because we
+ // are in a captive portal - trigger an async check to make sure.
+ int32_t nsprError = -1 * NS_ERROR_GET_CODE(status);
+ if (mozilla::psm::IsNSSErrorCode(nsprError)) {
+ gIOService->RecheckCaptivePortal();
+ }
+
+ if (mTimingEnabled && request == mCachePump) {
+ mCacheReadEnd = TimeStamp::Now();
+
+ ReportNetVSCacheTelemetry();
+ }
+
+ // allow content to be cached if it was loaded successfully (bug #482935)
+ bool contentComplete = NS_SUCCEEDED(status);
+
+ // honor the cancelation status even if the underlying transaction completed.
+ if (mCanceled || NS_FAILED(mStatus))
+ status = mStatus;
+
+ if (mCachedContentIsPartial) {
+ if (NS_SUCCEEDED(status)) {
+ // mTransactionPump should be suspended
+ MOZ_ASSERT(request != mTransactionPump,
+ "byte-range transaction finished prematurely");
+
+ if (request == mCachePump) {
+ bool streamDone;
+ status = OnDoneReadingPartialCacheEntry(&streamDone);
+ if (NS_SUCCEEDED(status) && !streamDone)
+ return status;
+ // otherwise, fall through and fire OnStopRequest...
+ }
+ else if (request == mTransactionPump) {
+ MOZ_ASSERT(mConcurrentCacheAccess);
+ }
+ else
+ NS_NOTREACHED("unexpected request");
+ }
+ // Do not to leave the transaction in a suspended state in error cases.
+ if (NS_FAILED(status) && mTransaction)
+ gHttpHandler->CancelTransaction(mTransaction, status);
+ }
+
+ nsCOMPtr<nsICompressConvStats> conv = do_QueryInterface(mCompressListener);
+ if (conv) {
+ conv->GetDecodedDataLength(&mDecodedBodySize);
+ }
+
+ if (mTransaction) {
+ // determine if we should call DoAuthRetry
+ bool authRetry = mAuthRetryPending && NS_SUCCEEDED(status);
+ mStronglyFramed = mTransaction->ResponseIsComplete();
+ LOG(("nsHttpChannel %p has a strongly framed transaction: %d",
+ this, mStronglyFramed));
+
+ //
+ // grab references to connection in case we need to retry an
+ // authentication request over it or use it for an upgrade
+ // to another protocol.
+ //
+ // this code relies on the code in nsHttpTransaction::Close, which
+ // tests for NS_HTTP_STICKY_CONNECTION to determine whether or not to
+ // keep the connection around after the transaction is finished.
+ //
+ RefPtr<nsAHttpConnection> conn;
+ LOG((" authRetry=%d, sticky conn cap=%d", authRetry, mCaps & NS_HTTP_STICKY_CONNECTION));
+ // We must check caps for stickinness also on the transaction because it
+ // might have been updated by the transaction itself during inspection of
+ // the reposnse headers yet on the socket thread (found connection based
+ // auth schema).
+ if (authRetry && (mCaps & NS_HTTP_STICKY_CONNECTION ||
+ mTransaction->Caps() & NS_HTTP_STICKY_CONNECTION)) {
+ conn = mTransaction->GetConnectionReference();
+ LOG((" transaction %p provides connection %p", mTransaction.get(), conn.get()));
+ // This is so far a workaround to fix leak when reusing unpersistent
+ // connection for authentication retry. See bug 459620 comment 4
+ // for details.
+ if (conn && !conn->IsPersistent()) {
+ LOG((" connection is not persistent, not reusing it"));
+ conn = nullptr;
+ }
+ // We do not use a sticky connection in case of a nsHttpPipeline as
+ // well (see bug 1337826). This is a quick fix, because
+ // nsHttpPipeline is turned off by default.
+ RefPtr<nsAHttpTransaction> tranConn = do_QueryObject(conn);
+ if (tranConn && tranConn->QueryPipeline()) {
+ LOG(("Do not use this connection, it is a nsHttpPipeline."));
+ conn = nullptr;
+ }
+ }
+
+ RefPtr<nsAHttpConnection> stickyConn;
+ if (mCaps & NS_HTTP_STICKY_CONNECTION) {
+ stickyConn = mTransaction->GetConnectionReference();
+ }
+
+ mTransferSize = mTransaction->GetTransferSize();
+
+ // If we are using the transaction to serve content, we also save the
+ // time since async open in the cache entry so we can compare telemetry
+ // between cache and net response.
+ if (request == mTransactionPump && mCacheEntry &&
+ !mAsyncOpenTime.IsNull() && !mOnStartRequestTimestamp.IsNull()) {
+ nsAutoCString onStartTime;
+ onStartTime.AppendInt( (uint64_t) (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds());
+ mCacheEntry->SetMetaDataElement("net-response-time-onstart", onStartTime.get());
+
+ nsAutoCString responseTime;
+ responseTime.AppendInt( (uint64_t) (TimeStamp::Now() - mAsyncOpenTime).ToMilliseconds());
+ mCacheEntry->SetMetaDataElement("net-response-time-onstop", responseTime.get());
+ }
+
+ // at this point, we're done with the transaction
+ mTransactionTimings = mTransaction->Timings();
+ mTransaction = nullptr;
+ mTransactionPump = nullptr;
+
+ // We no longer need the dns prefetch object
+ if (mDNSPrefetch && mDNSPrefetch->TimingsValid()
+ && !mTransactionTimings.requestStart.IsNull()
+ && !mTransactionTimings.connectStart.IsNull()
+ && mDNSPrefetch->EndTimestamp() <= mTransactionTimings.connectStart) {
+ // We only need the domainLookup timestamps when not using a
+ // persistent connection, meaning if the endTimestamp < connectStart
+ mTransactionTimings.domainLookupStart =
+ mDNSPrefetch->StartTimestamp();
+ mTransactionTimings.domainLookupEnd =
+ mDNSPrefetch->EndTimestamp();
+ }
+ mDNSPrefetch = nullptr;
+
+ // handle auth retry...
+ if (authRetry) {
+ mAuthRetryPending = false;
+ status = DoAuthRetry(conn);
+ if (NS_SUCCEEDED(status))
+ return NS_OK;
+ }
+
+ // If DoAuthRetry failed, or if we have been cancelled since showing
+ // the auth. dialog, then we need to send OnStartRequest now
+ if (authRetry || (mAuthRetryPending && NS_FAILED(status))) {
+ MOZ_ASSERT(NS_FAILED(status), "should have a failure code here");
+ // NOTE: since we have a failure status, we can ignore the return
+ // value from onStartRequest.
+ if (mListener) {
+ MOZ_ASSERT(!mOnStartRequestCalled,
+ "We should not call OnStartRequest twice.");
+ mListener->OnStartRequest(this, mListenerContext);
+ mOnStartRequestCalled = true;
+ } else {
+ NS_WARNING("OnStartRequest skipped because of null listener");
+ }
+ }
+
+ // if this transaction has been replaced, then bail.
+ if (mTransactionReplaced)
+ return NS_OK;
+
+ if (mUpgradeProtocolCallback && stickyConn &&
+ mResponseHead && mResponseHead->Status() == 101) {
+ gHttpHandler->ConnMgr()->CompleteUpgrade(stickyConn,
+ mUpgradeProtocolCallback);
+ }
+ }
+
+ // HTTP_CHANNEL_DISPOSITION TELEMETRY
+ enum ChannelDisposition
+ {
+ kHttpCanceled = 0,
+ kHttpDisk = 1,
+ kHttpNetOK = 2,
+ kHttpNetEarlyFail = 3,
+ kHttpNetLateFail = 4,
+ kHttpsCanceled = 8,
+ kHttpsDisk = 9,
+ kHttpsNetOK = 10,
+ kHttpsNetEarlyFail = 11,
+ kHttpsNetLateFail = 12
+ } chanDisposition = kHttpCanceled;
+
+ // HTTP 0.9 is more likely to be an error than really 0.9, so count it that way
+ if (mCanceled) {
+ chanDisposition = kHttpCanceled;
+ } else if (!mUsedNetwork) {
+ chanDisposition = kHttpDisk;
+ } else if (NS_SUCCEEDED(status) &&
+ mResponseHead &&
+ mResponseHead->Version() != NS_HTTP_VERSION_0_9) {
+ chanDisposition = kHttpNetOK;
+ } else if (!mTransferSize) {
+ chanDisposition = kHttpNetEarlyFail;
+ } else {
+ chanDisposition = kHttpNetLateFail;
+ }
+ if (IsHTTPS()) {
+ // shift http to https disposition enums
+ chanDisposition = static_cast<ChannelDisposition>(chanDisposition + kHttpsCanceled);
+ }
+ LOG((" nsHttpChannel::OnStopRequest ChannelDisposition %d\n", chanDisposition));
+ Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_DISPOSITION, chanDisposition);
+
+ // if needed, check cache entry has all data we expect
+ if (mCacheEntry && mCachePump &&
+ mConcurrentCacheAccess && contentComplete) {
+ int64_t size, contentLength;
+ nsresult rv = CheckPartial(mCacheEntry, &size, &contentLength);
+ if (NS_SUCCEEDED(rv)) {
+ if (size == int64_t(-1)) {
+ // mayhemer TODO - we have to restart read from cache here at the size offset
+ MOZ_ASSERT(false);
+ LOG((" cache entry write is still in progress, but we just "
+ "finished reading the cache entry"));
+ }
+ else if (contentLength != int64_t(-1) && contentLength != size) {
+ LOG((" concurrent cache entry write has been interrupted"));
+ mCachedResponseHead = Move(mResponseHead);
+ // Ignore zero partial length because we also want to resume when
+ // no data at all has been read from the cache.
+ rv = MaybeSetupByteRangeRequest(size, contentLength, true);
+ if (NS_SUCCEEDED(rv) && mIsPartialRequest) {
+ // Prevent read from cache again
+ mCachedContentIsValid = 0;
+ mCachedContentIsPartial = 1;
+
+ // Perform the range request
+ rv = ContinueConnect();
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" performing range request"));
+ mCachePump = nullptr;
+ return NS_OK;
+ } else {
+ LOG((" but range request perform failed 0x%08x", rv));
+ status = NS_ERROR_NET_INTERRUPT;
+ }
+ }
+ else {
+ LOG((" but range request setup failed rv=0x%08x, failing load", rv));
+ }
+ }
+ }
+ }
+
+ mIsPending = false;
+ mStatus = status;
+
+ // perform any final cache operations before we close the cache entry.
+ if (mCacheEntry && mRequestTimeInitialized) {
+ bool writeAccess;
+ // New implementation just returns value of the !mCacheEntryIsReadOnly flag passed in.
+ // Old implementation checks on nsICache::ACCESS_WRITE flag.
+ mCacheEntry->HasWriteAccess(!mCacheEntryIsReadOnly, &writeAccess);
+ if (writeAccess) {
+ FinalizeCacheEntry();
+ }
+ }
+
+ // Register entry to the Performance resource timing
+ mozilla::dom::Performance* documentPerformance = GetPerformance();
+ if (documentPerformance) {
+ documentPerformance->AddEntry(this, this);
+ }
+
+ if (mListener) {
+ LOG((" calling OnStopRequest\n"));
+ MOZ_ASSERT(!mOnStopRequestCalled,
+ "We should not call OnStopRequest twice");
+ mListener->OnStopRequest(this, mListenerContext, status);
+ mOnStopRequestCalled = true;
+ }
+
+ // If a preferred alt-data type was set, this signals the consumer is
+ // interested in reading and/or writing the alt-data representation.
+ // We need to hold a reference to the cache entry in case the listener calls
+ // openAlternativeOutputStream() after CloseCacheEntry() clears mCacheEntry.
+ if (!mPreferredCachedAltDataType.IsEmpty()) {
+ mAltDataCacheEntry = mCacheEntry;
+ }
+
+ CloseCacheEntry(!contentComplete);
+
+ if (mOfflineCacheEntry)
+ CloseOfflineCacheEntry();
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, status);
+
+ // We don't need this info anymore
+ CleanRedirectCacheChainIfNecessary();
+
+ ReleaseListeners();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+class OnTransportStatusAsyncEvent : public Runnable
+{
+public:
+ OnTransportStatusAsyncEvent(nsITransportEventSink* aEventSink,
+ nsresult aTransportStatus,
+ int64_t aProgress,
+ int64_t aProgressMax)
+ : mEventSink(aEventSink)
+ , mTransportStatus(aTransportStatus)
+ , mProgress(aProgress)
+ , mProgressMax(aProgressMax)
+ {
+ MOZ_ASSERT(!NS_IsMainThread(), "Shouldn't be created on main thread");
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "Should run on main thread");
+ if (mEventSink) {
+ mEventSink->OnTransportStatus(nullptr, mTransportStatus,
+ mProgress, mProgressMax);
+ }
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsresult mTransportStatus;
+ int64_t mProgress;
+ int64_t mProgressMax;
+};
+
+NS_IMETHODIMP
+nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
+ nsIInputStream *input,
+ uint64_t offset, uint32_t count)
+{
+ PROFILER_LABEL("nsHttpChannel", "OnDataAvailable",
+ js::ProfileEntry::Category::NETWORK);
+
+ LOG(("nsHttpChannel::OnDataAvailable [this=%p request=%p offset=%llu count=%u]\n",
+ this, request, offset, count));
+
+ // don't send out OnDataAvailable notifications if we've been canceled.
+ if (mCanceled)
+ return mStatus;
+
+ MOZ_ASSERT(mResponseHead, "No response head in ODA!!");
+
+ MOZ_ASSERT(!(mCachedContentIsPartial && (request == mTransactionPump)),
+ "transaction pump not suspended");
+
+ if (mAuthRetryPending || (request == mTransactionPump && mTransactionReplaced)) {
+ uint32_t n;
+ return input->ReadSegments(NS_DiscardSegment, nullptr, count, &n);
+ }
+
+ if (mListener) {
+ //
+ // synthesize transport progress event. we do this here since we want
+ // to delay OnProgress events until we start streaming data. this is
+ // crucially important since it impacts the lock icon (see bug 240053).
+ //
+ nsresult transportStatus;
+ if (request == mCachePump)
+ transportStatus = NS_NET_STATUS_READING;
+ else
+ transportStatus = NS_NET_STATUS_RECEIVING_FROM;
+
+ // mResponseHead may reference new or cached headers, but either way it
+ // holds our best estimate of the total content length. Even in the case
+ // of a byte range request, the content length stored in the cached
+ // response headers is what we want to use here.
+
+ int64_t progressMax(mResponseHead->ContentLength());
+ int64_t progress = mLogicalOffset + count;
+
+ if ((progress > progressMax) && (progressMax != -1)) {
+ NS_WARNING("unexpected progress values - "
+ "is server exceeding content length?");
+ }
+
+ // make sure params are in range for js
+ if (!InScriptableRange(progressMax)) {
+ progressMax = -1;
+ }
+
+ if (!InScriptableRange(progress)) {
+ progress = -1;
+ }
+
+ if (NS_IsMainThread()) {
+ OnTransportStatus(nullptr, transportStatus, progress, progressMax);
+ } else {
+ nsresult rv = NS_DispatchToMainThread(
+ new OnTransportStatusAsyncEvent(this, transportStatus,
+ progress, progressMax));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ //
+ // we have to manually keep the logical offset of the stream up-to-date.
+ // we cannot depend solely on the offset provided, since we may have
+ // already streamed some data from another source (see, for example,
+ // OnDoneReadingPartialCacheEntry).
+ //
+ int64_t offsetBefore = 0;
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(input);
+ if (seekable && NS_FAILED(seekable->Tell(&offsetBefore))) {
+ seekable = nullptr;
+ }
+
+ nsresult rv = mListener->OnDataAvailable(this,
+ mListenerContext,
+ input,
+ mLogicalOffset,
+ count);
+ if (NS_SUCCEEDED(rv)) {
+ // by contract mListener must read all of "count" bytes, but
+ // nsInputStreamPump is tolerant to seekable streams that violate that
+ // and it will redeliver incompletely read data. So we need to do
+ // the same thing when updating the progress counter to stay in sync.
+ int64_t offsetAfter, delta;
+ if (seekable && NS_SUCCEEDED(seekable->Tell(&offsetAfter))) {
+ delta = offsetAfter - offsetBefore;
+ if (delta != count) {
+ count = delta;
+
+ NS_WARNING("Listener OnDataAvailable contract violation");
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ nsAutoString message
+ (NS_LITERAL_STRING(
+ "http channel Listener OnDataAvailable contract violation"));
+ if (consoleService) {
+ consoleService->LogStringMessage(message.get());
+ }
+ }
+ }
+ mLogicalOffset += count;
+ }
+
+ return rv;
+ }
+
+ return NS_ERROR_ABORT;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::RetargetDeliveryTo(nsIEventTarget* aNewTarget)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
+
+ NS_ENSURE_ARG(aNewTarget);
+ if (aNewTarget == NS_GetCurrentThread()) {
+ NS_WARNING("Retargeting delivery to same thread");
+ return NS_OK;
+ }
+ if (!mTransactionPump && !mCachePump) {
+ LOG(("nsHttpChannel::RetargetDeliveryTo %p %p no pump available\n",
+ this, aNewTarget));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = NS_OK;
+ // If both cache pump and transaction pump exist, we're probably dealing
+ // with partially cached content. So, we must be able to retarget both.
+ nsCOMPtr<nsIThreadRetargetableRequest> retargetableCachePump;
+ nsCOMPtr<nsIThreadRetargetableRequest> retargetableTransactionPump;
+ if (mCachePump) {
+ retargetableCachePump = do_QueryObject(mCachePump);
+ // nsInputStreamPump should implement this interface.
+ MOZ_ASSERT(retargetableCachePump);
+ rv = retargetableCachePump->RetargetDeliveryTo(aNewTarget);
+ }
+ if (NS_SUCCEEDED(rv) && mTransactionPump) {
+ retargetableTransactionPump = do_QueryObject(mTransactionPump);
+ // nsInputStreamPump should implement this interface.
+ MOZ_ASSERT(retargetableTransactionPump);
+ rv = retargetableTransactionPump->RetargetDeliveryTo(aNewTarget);
+
+ // If retarget fails for transaction pump, we must restore mCachePump.
+ if (NS_FAILED(rv) && retargetableCachePump) {
+ nsCOMPtr<nsIThread> mainThread;
+ rv = NS_GetMainThread(getter_AddRefs(mainThread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = retargetableCachePump->RetargetDeliveryTo(mainThread);
+ }
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsThreadRetargetableStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::CheckListenerChain()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsITransportEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::OnTransportStatus(nsITransport *trans, nsresult status,
+ int64_t progress, int64_t progressMax)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread only");
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink)
+ GetCallback(mProgressSink);
+
+ if (status == NS_NET_STATUS_CONNECTED_TO ||
+ status == NS_NET_STATUS_WAITING_FOR) {
+ if (mTransaction) {
+ mTransaction->GetNetworkAddresses(mSelfAddr, mPeerAddr);
+ } else {
+ nsCOMPtr<nsISocketTransport> socketTransport =
+ do_QueryInterface(trans);
+ if (socketTransport) {
+ socketTransport->GetSelfAddr(&mSelfAddr);
+ socketTransport->GetPeerAddr(&mPeerAddr);
+ }
+ }
+ }
+
+ // block socket status event after Cancel or OnStopRequest has been called.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending) {
+ LOG(("sending progress%s notification [this=%p status=%x"
+ " progress=%lld/%lld]\n",
+ (mLoadFlags & LOAD_BACKGROUND)? "" : " and status",
+ this, status, progress, progressMax));
+
+ if (!(mLoadFlags & LOAD_BACKGROUND)) {
+ nsAutoCString host;
+ mURI->GetHost(host);
+ mProgressSink->OnStatus(this, nullptr, status,
+ NS_ConvertUTF8toUTF16(host).get());
+ }
+
+ if (progress > 0) {
+ if ((progress > progressMax) && (progressMax != -1)) {
+ NS_WARNING("unexpected progress values");
+ }
+
+ // Try to get mProgressSink if it was nulled out during OnStatus.
+ if (!mProgressSink) {
+ GetCallback(mProgressSink);
+ }
+ if (mProgressSink) {
+ mProgressSink->OnProgress(this, nullptr, progress, progressMax);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsICacheInfoChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::IsFromCache(bool *value)
+{
+ if (!mIsPending)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // return false if reading a partial cache entry; the data isn't entirely
+ // from the cache!
+
+ *value = (mCachePump || (mLoadFlags & LOAD_ONLY_IF_MODIFIED)) &&
+ mCachedContentIsValid && !mCachedContentIsPartial;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheTokenExpirationTime(uint32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ if (!mCacheEntry)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return mCacheEntry->GetExpirationTime(_retval);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheTokenCachedCharset(nsACString &_retval)
+{
+ nsresult rv;
+
+ if (!mCacheEntry)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsXPIDLCString cachedCharset;
+ rv = mCacheEntry->GetMetaDataElement("charset",
+ getter_Copies(cachedCharset));
+ if (NS_SUCCEEDED(rv))
+ _retval = cachedCharset;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetCacheTokenCachedCharset(const nsACString &aCharset)
+{
+ if (!mCacheEntry)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return mCacheEntry->SetMetaDataElement("charset",
+ PromiseFlatCString(aCharset).get());
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetAllowStaleCacheContent(bool aAllowStaleCacheContent)
+{
+ LOG(("nsHttpChannel::SetAllowStaleCacheContent [this=%p, allow=%d]",
+ this, aAllowStaleCacheContent));
+ mAllowStaleCacheContent = aAllowStaleCacheContent;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsHttpChannel::GetAllowStaleCacheContent(bool *aAllowStaleCacheContent)
+{
+ NS_ENSURE_ARG(aAllowStaleCacheContent);
+ *aAllowStaleCacheContent = mAllowStaleCacheContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::PreferAlternativeDataType(const nsACString & aType)
+{
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+ mPreferredCachedAltDataType = aType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetAlternativeDataType(nsACString & aType)
+{
+ // must be called during or after OnStartRequest
+ if (!mAfterOnStartRequestBegun) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aType = mAvailableCachedAltDataType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval)
+{
+ // OnStopRequest will clear mCacheEntry, but we may use mAltDataCacheEntry
+ // if the consumer called PreferAlternativeDataType()
+ nsCOMPtr<nsICacheEntry> cacheEntry = mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
+ if (!cacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return cacheEntry->OpenAlternativeOutputStream(type, _retval);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsICachingChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheToken(nsISupports **token)
+{
+ NS_ENSURE_ARG_POINTER(token);
+ if (!mCacheEntry)
+ return NS_ERROR_NOT_AVAILABLE;
+ return CallQueryInterface(mCacheEntry, token);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetCacheToken(nsISupports *token)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetOfflineCacheToken(nsISupports **token)
+{
+ NS_ENSURE_ARG_POINTER(token);
+ if (!mOfflineCacheEntry)
+ return NS_ERROR_NOT_AVAILABLE;
+ return CallQueryInterface(mOfflineCacheEntry, token);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetOfflineCacheToken(nsISupports *token)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheKey(nsISupports **key)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(key);
+
+ LOG(("nsHttpChannel::GetCacheKey [this=%p]\n", this));
+
+ *key = nullptr;
+
+ nsCOMPtr<nsISupportsPRUint32> container =
+ do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
+
+ if (!container)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = container->SetData(mPostID);
+ if (NS_FAILED(rv)) return rv;
+
+ return CallQueryInterface(container.get(), key);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetCacheKey(nsISupports *key)
+{
+ nsresult rv;
+
+ LOG(("nsHttpChannel::SetCacheKey [this=%p key=%p]\n", this, key));
+
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ if (!key)
+ mPostID = 0;
+ else {
+ // extract the post id
+ nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(key, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = container->GetData(&mPostID);
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetCacheOnlyMetadata(bool *aOnlyMetadata)
+{
+ NS_ENSURE_ARG(aOnlyMetadata);
+ *aOnlyMetadata = mCacheOnlyMetadata;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetCacheOnlyMetadata(bool aOnlyMetadata)
+{
+ LOG(("nsHttpChannel::SetCacheOnlyMetadata [this=%p only-metadata=%d]\n",
+ this, aOnlyMetadata));
+
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ mCacheOnlyMetadata = aOnlyMetadata;
+ if (aOnlyMetadata) {
+ mLoadFlags |= LOAD_ONLY_IF_MODIFIED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetPin(bool *aPin)
+{
+ NS_ENSURE_ARG(aPin);
+ *aPin = mPinCacheContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetPin(bool aPin)
+{
+ LOG(("nsHttpChannel::SetPin [this=%p pin=%d]\n",
+ this, aPin));
+
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mPinCacheContent = aPin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::ForceCacheEntryValidFor(uint32_t aSecondsToTheFuture)
+{
+ if (!mCacheEntry) {
+ LOG(("nsHttpChannel::ForceCacheEntryValidFor found no cache entry "
+ "for this channel [this=%p].", this));
+ } else {
+ mCacheEntry->ForceValidFor(aSecondsToTheFuture);
+
+ nsAutoCString key;
+ mCacheEntry->GetKey(key);
+
+ LOG(("nsHttpChannel::ForceCacheEntryValidFor successfully forced valid "
+ "entry with key %s for %d seconds. [this=%p]", key.get(),
+ aSecondsToTheFuture, this));
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIResumableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::ResumeAt(uint64_t aStartPos,
+ const nsACString& aEntityID)
+{
+ LOG(("nsHttpChannel::ResumeAt [this=%p startPos=%llu id='%s']\n",
+ this, aStartPos, PromiseFlatCString(aEntityID).get()));
+ mEntityID = aEntityID;
+ mStartPos = aStartPos;
+ mResuming = true;
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn)
+{
+ LOG(("nsHttpChannel::DoAuthRetry [this=%p]\n", this));
+
+ MOZ_ASSERT(!mTransaction, "should not have a transaction");
+ nsresult rv;
+
+ // toggle mIsPending to allow nsIObserver implementations to modify
+ // the request headers (bug 95044).
+ mIsPending = false;
+
+ // fetch cookies, and add them to the request header.
+ // the server response could have included cookies that must be sent with
+ // this authentication attempt (bug 84794).
+ // TODO: save cookies from auth response and send them here (bug 572151).
+ AddCookiesToRequest();
+
+ // notify "http-on-modify-request" observers
+ CallOnModifyRequestObservers();
+
+ mIsPending = true;
+
+ // get rid of the old response headers
+ mResponseHead = nullptr;
+
+ // rewind the upload stream
+ if (mUploadStream) {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
+ if (seekable)
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ }
+
+ // always set sticky connection flag
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ // and when needed, allow restart regardless the sticky flag
+ if (mAuthConnectionRestartable) {
+ LOG((" connection made restartable"));
+ mCaps |= NS_HTTP_CONNECTION_RESTARTABLE;
+ mAuthConnectionRestartable = false;
+ } else {
+ LOG((" connection made non-restartable"));
+ mCaps &= ~NS_HTTP_CONNECTION_RESTARTABLE;
+ }
+
+ // and create a new one...
+ rv = SetupTransaction();
+ if (NS_FAILED(rv)) return rv;
+
+ // transfer ownership of connection to transaction
+ if (conn)
+ mTransaction->SetConnection(conn);
+
+ rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mTransactionPump->AsyncRead(this, nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t suspendCount = mSuspendCount;
+ while (suspendCount--)
+ mTransactionPump->Suspend();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIApplicationCacheChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetApplicationCache(nsIApplicationCache **out)
+{
+ NS_IF_ADDREF(*out = mApplicationCache);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetApplicationCache(nsIApplicationCache *appCache)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mApplicationCache = appCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetApplicationCacheForWrite(nsIApplicationCache **out)
+{
+ NS_IF_ADDREF(*out = mApplicationCacheForWrite);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetApplicationCacheForWrite(nsIApplicationCache *appCache)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mApplicationCacheForWrite = appCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetLoadedFromApplicationCache(bool *aLoadedFromApplicationCache)
+{
+ *aLoadedFromApplicationCache = mLoadedFromApplicationCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetInheritApplicationCache(bool *aInherit)
+{
+ *aInherit = mInheritApplicationCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetInheritApplicationCache(bool aInherit)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mInheritApplicationCache = aInherit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetChooseApplicationCache(bool *aChoose)
+{
+ *aChoose = mChooseApplicationCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetChooseApplicationCache(bool aChoose)
+{
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mChooseApplicationCache = aChoose;
+ return NS_OK;
+}
+
+nsHttpChannel::OfflineCacheEntryAsForeignMarker*
+nsHttpChannel::GetOfflineCacheEntryAsForeignMarker()
+{
+ if (!mApplicationCache)
+ return nullptr;
+
+ return new OfflineCacheEntryAsForeignMarker(mApplicationCache, mURI);
+}
+
+nsresult
+nsHttpChannel::OfflineCacheEntryAsForeignMarker::MarkAsForeign()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> noRefURI;
+ rv = mCacheURI->CloneIgnoringRef(getter_AddRefs(noRefURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString spec;
+ rv = noRefURI->GetAsciiSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mApplicationCache->MarkEntry(spec,
+ nsIApplicationCache::ITEM_FOREIGN);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::MarkOfflineCacheEntryAsForeign()
+{
+ nsresult rv;
+
+ nsAutoPtr<OfflineCacheEntryAsForeignMarker> marker(
+ GetOfflineCacheEntryAsForeignMarker());
+
+ if (!marker)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ rv = marker->MarkAsForeign();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIAsyncVerifyRedirectCallback
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpChannel::WaitForRedirectCallback()
+{
+ nsresult rv;
+ LOG(("nsHttpChannel::WaitForRedirectCallback [this=%p]\n", this));
+
+ if (mTransactionPump) {
+ rv = mTransactionPump->Suspend();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (mCachePump) {
+ rv = mCachePump->Suspend();
+ if (NS_FAILED(rv) && mTransactionPump) {
+#ifdef DEBUG
+ nsresult resume =
+#endif
+ mTransactionPump->Resume();
+ MOZ_ASSERT(NS_SUCCEEDED(resume),
+ "Failed to resume transaction pump");
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mWaitingForRedirectCallback = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnRedirectVerifyCallback(nsresult result)
+{
+ LOG(("nsHttpChannel::OnRedirectVerifyCallback [this=%p] "
+ "result=%x stack=%d mWaitingForRedirectCallback=%u\n",
+ this, result, mRedirectFuncStack.Length(), mWaitingForRedirectCallback));
+ MOZ_ASSERT(mWaitingForRedirectCallback,
+ "Someone forgot to call WaitForRedirectCallback() ?!");
+ mWaitingForRedirectCallback = false;
+
+ if (mCanceled && NS_SUCCEEDED(result))
+ result = NS_BINDING_ABORTED;
+
+ for (uint32_t i = mRedirectFuncStack.Length(); i > 0;) {
+ --i;
+ // Pop the last function pushed to the stack
+ nsContinueRedirectionFunc func = mRedirectFuncStack[i];
+ mRedirectFuncStack.RemoveElementAt(mRedirectFuncStack.Length() - 1);
+
+ // Call it with the result we got from the callback or the deeper
+ // function call.
+ result = (this->*func)(result);
+
+ // If a new function has been pushed to the stack and placed us in the
+ // waiting state, we need to break the chain and wait for the callback
+ // again.
+ if (mWaitingForRedirectCallback)
+ break;
+ }
+
+ if (NS_FAILED(result) && !mCanceled) {
+ // First, cancel this channel if we are in failure state to set mStatus
+ // and let it be propagated to pumps.
+ Cancel(result);
+ }
+
+ if (!mWaitingForRedirectCallback) {
+ // We are not waiting for the callback. At this moment we must release
+ // reference to the redirect target channel, otherwise we may leak.
+ mRedirectChannel = nullptr;
+ }
+
+ // We always resume the pumps here. If all functions on stack have been
+ // called we need OnStopRequest to be triggered, and if we broke out of the
+ // loop above (and are thus waiting for a new callback) the suspension
+ // count must be balanced in the pumps.
+ if (mTransactionPump)
+ mTransactionPump->Resume();
+ if (mCachePump)
+ mCachePump->Resume();
+
+ return result;
+}
+
+void
+nsHttpChannel::PushRedirectAsyncFunc(nsContinueRedirectionFunc func)
+{
+ mRedirectFuncStack.AppendElement(func);
+}
+
+void
+nsHttpChannel::PopRedirectAsyncFunc(nsContinueRedirectionFunc func)
+{
+ MOZ_ASSERT(func == mRedirectFuncStack[mRedirectFuncStack.Length() - 1],
+ "Trying to pop wrong method from redirect async stack!");
+
+ mRedirectFuncStack.TruncateLength(mRedirectFuncStack.Length() - 1);
+}
+
+//-----------------------------------------------------------------------------
+// nsIDNSListener functions
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
+
+ LOG(("nsHttpChannel::OnLookupComplete [this=%p] prefetch complete%s: "
+ "%s status[0x%x]\n",
+ this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : "",
+ NS_SUCCEEDED(status) ? "success" : "failure", status));
+
+ // We no longer need the dns prefetch object. Note: mDNSPrefetch could be
+ // validly null if OnStopRequest has already been called.
+ // We only need the domainLookup timestamps when not loading from cache
+ if (mDNSPrefetch && mDNSPrefetch->TimingsValid() && mTransaction) {
+ TimeStamp connectStart = mTransaction->GetConnectStart();
+ TimeStamp requestStart = mTransaction->GetRequestStart();
+ // We only set the domainLookup timestamps if we're not using a
+ // persistent connection.
+ if (requestStart.IsNull() && connectStart.IsNull()) {
+ mTransaction->SetDomainLookupStart(mDNSPrefetch->StartTimestamp());
+ mTransaction->SetDomainLookupEnd(mDNSPrefetch->EndTimestamp());
+ }
+ }
+ mDNSPrefetch = nullptr;
+
+ // Unset DNS cache refresh if it was requested,
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ mCaps &= ~NS_HTTP_REFRESH_DNS;
+ if (mTransaction) {
+ mTransaction->SetDNSWasRefreshed();
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel internal functions
+//-----------------------------------------------------------------------------
+
+// Creates an URI to the given location using current URI for base and charset
+nsresult
+nsHttpChannel::CreateNewURI(const char *loc, nsIURI **newURI)
+{
+ nsCOMPtr<nsIIOService> ioService;
+ nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ if (NS_FAILED(rv)) return rv;
+
+ // the new uri should inherit the origin charset of the current uri
+ nsAutoCString originCharset;
+ rv = mURI->GetOriginCharset(originCharset);
+ if (NS_FAILED(rv))
+ originCharset.Truncate();
+
+ return ioService->NewURI(nsDependentCString(loc),
+ originCharset.get(),
+ mURI,
+ newURI);
+}
+
+void
+nsHttpChannel::MaybeInvalidateCacheEntryForSubsequentGet()
+{
+ // See RFC 2616 section 5.1.1. These are considered valid
+ // methods which DO NOT invalidate cache-entries for the
+ // referred resource. POST, PUT and DELETE as well as any
+ // other method not listed here will potentially invalidate
+ // any cached copy of the resource
+ if (mRequestHead.IsGet() || mRequestHead.IsOptions() ||
+ mRequestHead.IsHead() || mRequestHead.IsTrace() ||
+ mRequestHead.IsConnect()) {
+ return;
+ }
+
+ // Invalidate the request-uri.
+ if (LOG_ENABLED()) {
+ nsAutoCString key;
+ mURI->GetAsciiSpec(key);
+ LOG(("MaybeInvalidateCacheEntryForSubsequentGet [this=%p uri=%s]\n",
+ this, key.get()));
+ }
+
+ DoInvalidateCacheEntry(mURI);
+
+ // Invalidate Location-header if set
+ nsAutoCString location;
+ mResponseHead->GetHeader(nsHttp::Location, location);
+ if (!location.IsEmpty()) {
+ LOG((" Location-header=%s\n", location.get()));
+ InvalidateCacheEntryForLocation(location.get());
+ }
+
+ // Invalidate Content-Location-header if set
+ mResponseHead->GetHeader(nsHttp::Content_Location, location);
+ if (!location.IsEmpty()) {
+ LOG((" Content-Location-header=%s\n", location.get()));
+ InvalidateCacheEntryForLocation(location.get());
+ }
+}
+
+void
+nsHttpChannel::InvalidateCacheEntryForLocation(const char *location)
+{
+ nsAutoCString tmpCacheKey, tmpSpec;
+ nsCOMPtr<nsIURI> resultingURI;
+ nsresult rv = CreateNewURI(location, getter_AddRefs(resultingURI));
+ if (NS_SUCCEEDED(rv) && HostPartIsTheSame(resultingURI)) {
+ DoInvalidateCacheEntry(resultingURI);
+ } else {
+ LOG((" hosts not matching\n"));
+ }
+}
+
+void
+nsHttpChannel::DoInvalidateCacheEntry(nsIURI* aURI)
+{
+ // NOTE:
+ // Following comments 24,32 and 33 in bug #327765, we only care about
+ // the cache in the protocol-handler, not the application cache.
+ // The logic below deviates from the original logic in OpenCacheEntry on
+ // one point by using only READ_ONLY access-policy. I think this is safe.
+
+ nsresult rv;
+
+ nsAutoCString key;
+ if (LOG_ENABLED()) {
+ aURI->GetAsciiSpec(key);
+ }
+
+ LOG(("DoInvalidateCacheEntry [channel=%p key=%s]", this, key.get()));
+
+ nsCOMPtr<nsICacheStorageService> cacheStorageService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
+ rv = cacheStorageService->DiskCacheStorage(info, false, getter_AddRefs(cacheStorage));
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = cacheStorage->AsyncDoomURI(aURI, EmptyCString(), nullptr);
+ }
+
+ LOG(("DoInvalidateCacheEntry [channel=%p key=%s rv=%d]", this, key.get(), int(rv)));
+}
+
+void
+nsHttpChannel::AsyncOnExamineCachedResponse()
+{
+ gHttpHandler->OnExamineCachedResponse(this);
+
+}
+
+void
+nsHttpChannel::UpdateAggregateCallbacks()
+{
+ if (!mTransaction) {
+ return;
+ }
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ NS_GetCurrentThread(),
+ getter_AddRefs(callbacks));
+ mTransaction->SetSecurityCallbacks(callbacks);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
+
+ nsresult rv = HttpBaseChannel::SetLoadGroup(aLoadGroup);
+ if (NS_SUCCEEDED(rv)) {
+ UpdateAggregateCallbacks();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
+
+ nsresult rv = HttpBaseChannel::SetNotificationCallbacks(aCallbacks);
+ if (NS_SUCCEEDED(rv)) {
+ UpdateAggregateCallbacks();
+ }
+ return rv;
+}
+
+void
+nsHttpChannel::MarkIntercepted()
+{
+ mInterceptCache = INTERCEPTED;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetResponseSynthesized(bool* aSynthesized)
+{
+ NS_ENSURE_ARG_POINTER(aSynthesized);
+ *aSynthesized = (mInterceptCache == INTERCEPTED);
+ return NS_OK;
+}
+
+bool
+nsHttpChannel::AwaitingCacheCallbacks()
+{
+ return mCacheEntriesToWaitFor != 0;
+}
+
+void
+nsHttpChannel::SetPushedStream(Http2PushedStream *stream)
+{
+ MOZ_ASSERT(stream);
+ MOZ_ASSERT(!mPushedStream);
+ mPushedStream = stream;
+}
+
+nsresult
+nsHttpChannel::OnPush(const nsACString &url, Http2PushedStream *pushedStream)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("nsHttpChannel::OnPush [this=%p]\n", this));
+
+ MOZ_ASSERT(mCaps & NS_HTTP_ONPUSH_LISTENER);
+ nsCOMPtr<nsIHttpPushListener> pushListener;
+ NS_QueryNotificationCallbacks(mCallbacks,
+ mLoadGroup,
+ NS_GET_IID(nsIHttpPushListener),
+ getter_AddRefs(pushListener));
+
+ MOZ_ASSERT(pushListener);
+ if (!pushListener) {
+ LOG(("nsHttpChannel::OnPush [this=%p] notification callbacks do not "
+ "implement nsIHttpPushListener\n", this));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIURI> pushResource;
+ nsresult rv;
+
+ // Create a Channel for the Push Resource
+ rv = NS_NewURI(getter_AddRefs(pushResource), url);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> pushChannel;
+ rv = NS_NewChannelInternal(getter_AddRefs(pushChannel),
+ pushResource,
+ mLoadInfo,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL,
+ ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> pushHttpChannel = do_QueryInterface(pushChannel);
+ MOZ_ASSERT(pushHttpChannel);
+ if (!pushHttpChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<nsHttpChannel> channel;
+ CallQueryInterface(pushHttpChannel, channel.StartAssignment());
+ MOZ_ASSERT(channel);
+ if (!channel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // new channel needs mrqeuesthead and headers from pushedStream
+ channel->mRequestHead.ParseHeaderSet(
+ pushedStream->GetRequestString().BeginWriting());
+
+ channel->mLoadGroup = mLoadGroup;
+ channel->mLoadInfo = mLoadInfo;
+ channel->mCallbacks = mCallbacks;
+
+ // Link the pushed stream with the new channel and call listener
+ channel->SetPushedStream(pushedStream);
+ rv = pushListener->OnPush(this, pushHttpChannel);
+ return rv;
+}
+
+// static
+bool nsHttpChannel::IsRedirectStatus(uint32_t status)
+{
+ // 305 disabled as a security measure (see bug 187996).
+ return status == 300 || status == 301 || status == 302 || status == 303 ||
+ status == 307 || status == 308;
+}
+
+void
+nsHttpChannel::SetCouldBeSynthesized()
+{
+ MOZ_ASSERT(!BypassServiceWorker());
+ mResponseCouldBeSynthesized = true;
+}
+
+void
+nsHttpChannel::SetConnectionInfo(nsHttpConnectionInfo *aCI)
+{
+ mConnectionInfo = aCI ? aCI->Clone() : nullptr;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnPreflightSucceeded()
+{
+ MOZ_ASSERT(mRequireCORSPreflight, "Why did a preflight happen?");
+ mIsCorsPreflightDone = 1;
+ mPreflightChannel = nullptr;
+
+ return ContinueConnect();
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OnPreflightFailed(nsresult aError)
+{
+ MOZ_ASSERT(mRequireCORSPreflight, "Why did a preflight happen?");
+ mIsCorsPreflightDone = 1;
+ mPreflightChannel = nullptr;
+
+ CloseCacheEntry(false);
+ AsyncAbort(aError);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIHstsPrimingCallback functions
+//-----------------------------------------------------------------------------
+
+/*
+ * May be invoked synchronously if HSTS priming has already been performed
+ * for the host.
+ */
+nsresult
+nsHttpChannel::OnHSTSPrimingSucceeded(bool aCached)
+{
+ if (nsMixedContentBlocker::sUseHSTS) {
+ // redirect the channel to HTTPS if the pref
+ // "security.mixed_content.use_hsts" is true
+ LOG(("HSTS Priming succeeded, redirecting to HTTPS [this=%p]", this));
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+ (aCached) ? HSTSPrimingResult::eHSTS_PRIMING_CACHED_DO_UPGRADE :
+ HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED);
+ return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
+ }
+
+ // If "security.mixed_content.use_hsts" is false, record the result of
+ // HSTS priming and block or proceed with the load as required by
+ // mixed-content blocking
+ bool wouldBlock = mLoadInfo->GetMixedContentWouldBlock();
+
+ // preserve the mixed-content-before-hsts order and block if required
+ if (wouldBlock) {
+ LOG(("HSTS Priming succeeded, blocking for mixed-content [this=%p]",
+ this));
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+ HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED_BLOCK);
+ CloseCacheEntry(false);
+ return AsyncAbort(NS_ERROR_CONTENT_BLOCKED);
+ }
+
+ LOG(("HSTS Priming succeeded, loading insecure: [this=%p]", this));
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+ HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED_HTTP);
+
+ nsresult rv = ContinueConnect();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ return AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+/*
+ * May be invoked synchronously if HSTS priming has already been performed
+ * for the host.
+ */
+nsresult
+nsHttpChannel::OnHSTSPrimingFailed(nsresult aError, bool aCached)
+{
+ bool wouldBlock = mLoadInfo->GetMixedContentWouldBlock();
+
+ LOG(("HSTS Priming Failed [this=%p], %s the load", this,
+ (wouldBlock) ? "blocking" : "allowing"));
+ if (aCached) {
+ // Between the time we marked for priming and started the priming request,
+ // the host was found to not allow the upgrade, probably from another
+ // priming request.
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+ (wouldBlock) ? HSTSPrimingResult::eHSTS_PRIMING_CACHED_BLOCK :
+ HSTSPrimingResult::eHSTS_PRIMING_CACHED_NO_UPGRADE);
+ } else {
+ // A priming request was sent, and no HSTS header was found that allows
+ // the upgrade.
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+ (wouldBlock) ? HSTSPrimingResult::eHSTS_PRIMING_FAILED_BLOCK :
+ HSTSPrimingResult::eHSTS_PRIMING_FAILED_ACCEPT);
+ }
+
+ // Don't visit again for at least
+ // security.mixed_content.hsts_priming_cache_timeout seconds.
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = sss->CacheNegativeHSTSResult(mURI,
+ nsMixedContentBlocker::sHSTSPrimingCacheTimeout);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsISiteSecurityService::CacheNegativeHSTSResult failed");
+ }
+
+ // If we would block, go ahead and abort with the error provided
+ if (wouldBlock) {
+ CloseCacheEntry(false);
+ return AsyncAbort(aError);
+ }
+
+ // we can continue the load and the UI has been updated as mixed content
+ rv = ContinueConnect();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ return AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// AChannelHasDivertableParentChannelAsListener internal functions
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::MessageDiversionStarted(ADivertableParentChannel *aParentChannel)
+{
+ LOG(("nsHttpChannel::MessageDiversionStarted [this=%p]", this));
+ MOZ_ASSERT(!mParentChannel);
+ mParentChannel = aParentChannel;
+ // If the channel is suspended, propagate that info to the parent's mEventQ.
+ uint32_t suspendCount = mSuspendCount;
+ while (suspendCount--) {
+ mParentChannel->SuspendMessageDiversion();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::MessageDiversionStop()
+{
+ LOG(("nsHttpChannel::MessageDiversionStop [this=%p]", this));
+ MOZ_ASSERT(mParentChannel);
+ mParentChannel = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SuspendInternal()
+{
+ NS_ENSURE_TRUE(mIsPending, NS_ERROR_NOT_AVAILABLE);
+
+ LOG(("nsHttpChannel::SuspendInternal [this=%p]\n", this));
+
+ ++mSuspendCount;
+
+ nsresult rvTransaction = NS_OK;
+ if (mTransactionPump) {
+ rvTransaction = mTransactionPump->Suspend();
+ }
+ nsresult rvCache = NS_OK;
+ if (mCachePump) {
+ rvCache = mCachePump->Suspend();
+ }
+
+ return NS_FAILED(rvTransaction) ? rvTransaction : rvCache;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::ResumeInternal()
+{
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+
+ LOG(("nsHttpChannel::ResumeInternal [this=%p]\n", this));
+
+ if (--mSuspendCount == 0 && mCallOnResume) {
+ nsresult rv = AsyncCall(mCallOnResume);
+ mCallOnResume = nullptr;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsresult rvTransaction = NS_OK;
+ if (mTransactionPump) {
+ rvTransaction = mTransactionPump->Resume();
+ }
+
+ nsresult rvCache = NS_OK;
+ if (mCachePump) {
+ rvCache = mCachePump->Resume();
+ }
+
+ return NS_FAILED(rvTransaction) ? rvTransaction : rvCache;
+}
+
+void
+nsHttpChannel::MaybeWarnAboutAppCache()
+{
+ // First, accumulate a telemetry ping about appcache usage.
+ Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD,
+ true);
+
+ // Then, issue a deprecation warning.
+ nsCOMPtr<nsIDeprecationWarner> warner;
+ GetCallback(warner);
+ if (warner) {
+ warner->IssueWarning(nsIDocument::eAppCache, false);
+ }
+}
+
+void
+nsHttpChannel::SetLoadGroupUserAgentOverride()
+{
+ nsCOMPtr<nsIURI> uri;
+ GetURI(getter_AddRefs(uri));
+ nsAutoCString uriScheme;
+ if (uri) {
+ uri->GetScheme(uriScheme);
+ }
+
+ // We don't need a UA for file: protocols.
+ if (uriScheme.EqualsLiteral("file")) {
+ gHttpHandler->OnUserAgentRequest(this);
+ return;
+ }
+
+ nsIRequestContextService* rcsvc = gHttpHandler->GetRequestContextService();
+ nsCOMPtr<nsIRequestContext> rc;
+ if (rcsvc) {
+ rcsvc->GetRequestContext(mRequestContextID,
+ getter_AddRefs(rc));
+ }
+
+ nsAutoCString ua;
+ if (nsContentUtils::IsNonSubresourceRequest(this)) {
+ gHttpHandler->OnUserAgentRequest(this);
+ if (rc) {
+ GetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua);
+ rc->SetUserAgentOverride(ua);
+ }
+ } else {
+ GetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua);
+ // Don't overwrite the UA if it is already set (eg by an XHR with explicit UA).
+ if (ua.IsEmpty()) {
+ if (rc) {
+ rc->GetUserAgentOverride(ua);
+ SetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua, false);
+ } else {
+ gHttpHandler->OnUserAgentRequest(this);
+ }
+ }
+ }
+}
+
+void
+nsHttpChannel::SetDoNotTrack()
+{
+ /**
+ * 'DoNotTrack' header should be added if 'privacy.donottrackheader.enabled'
+ * is true or tracking protection is enabled. See bug 1258033.
+ */
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+
+ if ((loadContext && loadContext->UseTrackingProtection()) ||
+ nsContentUtils::DoNotTrackEnabled()) {
+ mRequestHead.SetHeader(nsHttp::DoNotTrack,
+ NS_LITERAL_CSTRING("1"),
+ false);
+ }
+}
+
+
+void
+nsHttpChannel::ReportNetVSCacheTelemetry()
+{
+ nsresult rv;
+ if (!mCacheEntry) {
+ return;
+ }
+
+ // We only report telemetry if the entry is persistent (on disk)
+ bool persistent;
+ rv = mCacheEntry->GetPersistent(&persistent);
+ if (NS_FAILED(rv) || !persistent) {
+ return;
+ }
+
+ nsXPIDLCString tmpStr;
+ rv = mCacheEntry->GetMetaDataElement("net-response-time-onstart",
+ getter_Copies(tmpStr));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ uint64_t onStartNetTime = tmpStr.ToInteger64(&rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ tmpStr.Truncate();
+ rv = mCacheEntry->GetMetaDataElement("net-response-time-onstop",
+ getter_Copies(tmpStr));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ uint64_t onStopNetTime = tmpStr.ToInteger64(&rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ uint64_t onStartCacheTime = (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds();
+ int64_t onStartDiff = onStartNetTime - onStartCacheTime;
+ onStartDiff += 500; // We offset the difference by 500 ms to report positive values in telemetry
+
+ uint64_t onStopCacheTime = (mCacheReadEnd - mAsyncOpenTime).ToMilliseconds();
+ int64_t onStopDiff = onStopNetTime - onStopCacheTime;
+ onStopDiff += 500; // We offset the difference by 500 ms
+
+ if (mDidReval) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_REVALIDATED, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_REVALIDATED, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_NOTREVALIDATED, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_NOTREVALIDATED, onStopDiff);
+ }
+
+ if (mDidReval) {
+ // We don't report revalidated probes as the data would be skewed.
+ return;
+ }
+
+ uint32_t diskStorageSizeK = 0;
+ rv = mCacheEntry->GetDiskStorageSizeInKB(&diskStorageSizeK);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsAutoCString contentType;
+ if (mResponseHead && mResponseHead->HasContentType()) {
+ mResponseHead->ContentType(contentType);
+ }
+ bool isImage = StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"));
+ if (isImage) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_ISIMG, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_ISIMG, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_NOTIMG, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_NOTIMG, onStopDiff);
+ }
+
+ if (mCacheOpenWithPriority) {
+ if (mCacheQueueSizeWhenOpen < 5) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_QSMALL_HIGHPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QSMALL_HIGHPRI, onStopDiff);
+ } else if (mCacheQueueSizeWhenOpen < 10) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_QMED_HIGHPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QMED_HIGHPRI, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_QBIG_HIGHPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QBIG_HIGHPRI, onStopDiff);
+ }
+ } else { // The limits are higher for normal priority cache queues
+ if (mCacheQueueSizeWhenOpen < 10) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_QSMALL_NORMALPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QSMALL_NORMALPRI, onStopDiff);
+ } else if (mCacheQueueSizeWhenOpen < 50) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_QMED_NORMALPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QMED_NORMALPRI, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_QBIG_NORMALPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QBIG_NORMALPRI, onStopDiff);
+ }
+ }
+
+ if (diskStorageSizeK < 32) {
+ if (mCacheOpenWithPriority) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_SMALL_HIGHPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_SMALL_HIGHPRI, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_SMALL_NORMALPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_SMALL_NORMALPRI, onStopDiff);
+ }
+ } else if (diskStorageSizeK < 256) {
+ if (mCacheOpenWithPriority) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_MED_HIGHPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_MED_HIGHPRI, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_MED_NORMALPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_MED_NORMALPRI, onStopDiff);
+ }
+ } else {
+ if (mCacheOpenWithPriority) {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_LARGE_HIGHPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_LARGE_HIGHPRI, onStopDiff);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_LARGE_NORMALPRI, onStartDiff);
+ Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_LARGE_NORMALPRI, onStopDiff);
+ }
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h
new file mode 100644
index 000000000..ad8156ec0
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -0,0 +1,620 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et cin ts=4 sw=4 sts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpChannel_h__
+#define nsHttpChannel_h__
+
+#include "HttpBaseChannel.h"
+#include "nsTArray.h"
+#include "nsICachingChannel.h"
+#include "nsICacheEntry.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsIDNSListener.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIChannelWithDivertableParentListener.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsWeakReference.h"
+#include "TimingStruct.h"
+#include "ADivertableParentChannel.h"
+#include "AutoClose.h"
+#include "nsIStreamListener.h"
+#include "nsISupportsPrimitives.h"
+#include "nsICorsPreflightCallback.h"
+#include "AlternateServices.h"
+#include "nsIHstsPrimingCallback.h"
+
+class nsDNSPrefetch;
+class nsICancelable;
+class nsIHttpChannelAuthProvider;
+class nsInputStreamPump;
+class nsISSLStatus;
+
+namespace mozilla { namespace net {
+
+class Http2PushedStream;
+
+class HttpChannelSecurityWarningReporter
+{
+public:
+ virtual nsresult ReportSecurityMessage(const nsAString& aMessageTag,
+ const nsAString& aMessageCategory) = 0;
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel
+//-----------------------------------------------------------------------------
+
+// Use to support QI nsIChannel to nsHttpChannel
+#define NS_HTTPCHANNEL_IID \
+{ \
+ 0x301bf95b, \
+ 0x7bb3, \
+ 0x4ae1, \
+ {0xa9, 0x71, 0x40, 0xbc, 0xfa, 0x81, 0xde, 0x12} \
+}
+
+class nsHttpChannel final : public HttpBaseChannel
+ , public HttpAsyncAborter<nsHttpChannel>
+ , public nsIStreamListener
+ , public nsICachingChannel
+ , public nsICacheEntryOpenCallback
+ , public nsITransportEventSink
+ , public nsIProtocolProxyCallback
+ , public nsIHttpAuthenticableChannel
+ , public nsIApplicationCacheChannel
+ , public nsIAsyncVerifyRedirectCallback
+ , public nsIThreadRetargetableRequest
+ , public nsIThreadRetargetableStreamListener
+ , public nsIDNSListener
+ , public nsSupportsWeakReference
+ , public nsICorsPreflightCallback
+ , public nsIChannelWithDivertableParentListener
+ , public nsIHstsPrimingCallback
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+ NS_DECL_NSICACHEINFOCHANNEL
+ NS_DECL_NSICACHINGCHANNEL
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSIAPPLICATIONCACHECONTAINER
+ NS_DECL_NSIAPPLICATIONCACHECHANNEL
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSIHSTSPRIMINGCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSICHANNELWITHDIVERTABLEPARENTLISTENER
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTPCHANNEL_IID)
+
+ // nsIHttpAuthenticableChannel. We can't use
+ // NS_DECL_NSIHTTPAUTHENTICABLECHANNEL because it duplicates cancel() and
+ // others.
+ NS_IMETHOD GetIsSSL(bool *aIsSSL) override;
+ NS_IMETHOD GetProxyMethodIsConnect(bool *aProxyMethodIsConnect) override;
+ NS_IMETHOD GetServerResponseHeader(nsACString & aServerResponseHeader) override;
+ NS_IMETHOD GetProxyChallenges(nsACString & aChallenges) override;
+ NS_IMETHOD GetWWWChallenges(nsACString & aChallenges) override;
+ NS_IMETHOD SetProxyCredentials(const nsACString & aCredentials) override;
+ NS_IMETHOD SetWWWCredentials(const nsACString & aCredentials) override;
+ NS_IMETHOD OnAuthAvailable() override;
+ NS_IMETHOD OnAuthCancelled(bool userCancel) override;
+ NS_IMETHOD CloseStickyConnection() override;
+ NS_IMETHOD ConnectionRestartable(bool) override;
+ // Functions we implement from nsIHttpAuthenticableChannel but are
+ // declared in HttpBaseChannel must be implemented in this class. We
+ // just call the HttpBaseChannel:: impls.
+ NS_IMETHOD GetLoadFlags(nsLoadFlags *aLoadFlags) override;
+ NS_IMETHOD GetURI(nsIURI **aURI) override;
+ NS_IMETHOD GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks) override;
+ NS_IMETHOD GetLoadGroup(nsILoadGroup **aLoadGroup) override;
+ NS_IMETHOD GetRequestMethod(nsACString& aMethod) override;
+
+ nsHttpChannel();
+
+ virtual nsresult Init(nsIURI *aURI, uint32_t aCaps, nsProxyInfo *aProxyInfo,
+ uint32_t aProxyResolveFlags,
+ nsIURI *aProxyURI,
+ const nsID& aChannelId) override;
+
+ nsresult OnPush(const nsACString &uri, Http2PushedStream *pushedStream);
+
+ static bool IsRedirectStatus(uint32_t status);
+
+
+ // Methods HttpBaseChannel didn't implement for us or that we override.
+ //
+ // nsIRequest
+ NS_IMETHOD Cancel(nsresult status) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+ // nsIChannel
+ NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo) override;
+ NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *aContext) override;
+ NS_IMETHOD AsyncOpen2(nsIStreamListener *aListener) override;
+ // nsIHttpChannel
+ NS_IMETHOD GetEncodedBodySize(uint64_t *aEncodedBodySize) override;
+ // nsIHttpChannelInternal
+ NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey) override;
+ NS_IMETHOD ForceIntercepted(uint64_t aInterceptionID) override;
+ virtual mozilla::net::nsHttpChannel * QueryHttpChannelImpl(void) override;
+ // nsISupportsPriority
+ NS_IMETHOD SetPriority(int32_t value) override;
+ // nsIClassOfService
+ NS_IMETHOD SetClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD AddClassFlags(uint32_t inFlags) override;
+ NS_IMETHOD ClearClassFlags(uint32_t inFlags) override;
+
+ // nsIResumableChannel
+ NS_IMETHOD ResumeAt(uint64_t startPos, const nsACString& entityID) override;
+
+ NS_IMETHOD SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) override;
+ NS_IMETHOD SetLoadGroup(nsILoadGroup *aLoadGroup) override;
+ // nsITimedChannel
+ NS_IMETHOD GetDomainLookupStart(mozilla::TimeStamp *aDomainLookupStart) override;
+ NS_IMETHOD GetDomainLookupEnd(mozilla::TimeStamp *aDomainLookupEnd) override;
+ NS_IMETHOD GetConnectStart(mozilla::TimeStamp *aConnectStart) override;
+ NS_IMETHOD GetConnectEnd(mozilla::TimeStamp *aConnectEnd) override;
+ NS_IMETHOD GetRequestStart(mozilla::TimeStamp *aRequestStart) override;
+ NS_IMETHOD GetResponseStart(mozilla::TimeStamp *aResponseStart) override;
+ NS_IMETHOD GetResponseEnd(mozilla::TimeStamp *aResponseEnd) override;
+ // nsICorsPreflightCallback
+ NS_IMETHOD OnPreflightSucceeded() override;
+ NS_IMETHOD OnPreflightFailed(nsresult aError) override;
+
+ nsresult AddSecurityMessage(const nsAString& aMessageTag,
+ const nsAString& aMessageCategory) override;
+
+ void SetWarningReporter(HttpChannelSecurityWarningReporter* aReporter)
+ { mWarningReporter = aReporter; }
+
+public: /* internal necko use only */
+
+ void InternalSetUploadStream(nsIInputStream *uploadStream)
+ { mUploadStream = uploadStream; }
+ void SetUploadStreamHasHeaders(bool hasHeaders)
+ { mUploadStreamHasHeaders = hasHeaders; }
+
+ nsresult SetReferrerWithPolicyInternal(nsIURI *referrer,
+ uint32_t referrerPolicy) {
+ nsAutoCString spec;
+ nsresult rv = referrer->GetAsciiSpec(spec);
+ if (NS_FAILED(rv)) return rv;
+ mReferrer = referrer;
+ mReferrerPolicy = referrerPolicy;
+ mRequestHead.SetHeader(nsHttp::Referer, spec);
+ return NS_OK;
+ }
+
+ nsresult SetTopWindowURI(nsIURI* aTopWindowURI) {
+ mTopWindowURI = aTopWindowURI;
+ return NS_OK;
+ }
+
+ uint32_t GetRequestTime() const
+ {
+ return mRequestTime;
+ }
+
+ nsresult OpenCacheEntry(bool usingSSL);
+ nsresult ContinueConnect();
+
+ // If the load is mixed-content, build and send an HSTS priming request.
+ nsresult TryHSTSPriming();
+
+ nsresult StartRedirectChannelToURI(nsIURI *, uint32_t);
+
+ // This allows cache entry to be marked as foreign even after channel itself
+ // is gone. Needed for e10s (see HttpChannelParent::RecvDocumentChannelCleanup)
+ class OfflineCacheEntryAsForeignMarker {
+ nsCOMPtr<nsIApplicationCache> mApplicationCache;
+ nsCOMPtr<nsIURI> mCacheURI;
+ public:
+ OfflineCacheEntryAsForeignMarker(nsIApplicationCache* appCache,
+ nsIURI* aURI)
+ : mApplicationCache(appCache)
+ , mCacheURI(aURI)
+ {}
+
+ nsresult MarkAsForeign();
+ };
+
+ OfflineCacheEntryAsForeignMarker* GetOfflineCacheEntryAsForeignMarker();
+
+ // Helper to keep cache callbacks wait flags consistent
+ class AutoCacheWaitFlags
+ {
+ public:
+ explicit AutoCacheWaitFlags(nsHttpChannel* channel)
+ : mChannel(channel)
+ , mKeep(0)
+ {
+ // Flags must be set before entering any AsyncOpenCacheEntry call.
+ mChannel->mCacheEntriesToWaitFor =
+ nsHttpChannel::WAIT_FOR_CACHE_ENTRY |
+ nsHttpChannel::WAIT_FOR_OFFLINE_CACHE_ENTRY;
+ }
+
+ void Keep(uint32_t flags)
+ {
+ // Called after successful call to appropriate AsyncOpenCacheEntry call.
+ mKeep |= flags;
+ }
+
+ ~AutoCacheWaitFlags()
+ {
+ // Keep only flags those are left to be wait for.
+ mChannel->mCacheEntriesToWaitFor &= mKeep;
+ }
+
+ private:
+ nsHttpChannel* mChannel;
+ uint32_t mKeep : 2;
+ };
+
+ void MarkIntercepted();
+ NS_IMETHOD GetResponseSynthesized(bool* aSynthesized) override;
+ bool AwaitingCacheCallbacks();
+ void SetCouldBeSynthesized();
+
+private: // used for alternate service validation
+ RefPtr<TransactionObserver> mTransactionObserver;
+public:
+ void SetConnectionInfo(nsHttpConnectionInfo *); // clones the argument
+ void SetTransactionObserver(TransactionObserver *arg) { mTransactionObserver = arg; }
+ TransactionObserver *GetTransactionObserver() { return mTransactionObserver; }
+
+protected:
+ virtual ~nsHttpChannel();
+
+private:
+ typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
+
+ bool RequestIsConditional();
+ nsresult BeginConnect();
+ void HandleBeginConnectContinue();
+ MOZ_MUST_USE nsresult BeginConnectContinue();
+ nsresult ContinueBeginConnectWithResult();
+ void ContinueBeginConnect();
+ nsresult Connect();
+ void SpeculativeConnect();
+ nsresult SetupTransaction();
+ void SetupTransactionRequestContext();
+ nsresult CallOnStartRequest();
+ nsresult ProcessResponse();
+ void AsyncContinueProcessResponse();
+ nsresult ContinueProcessResponse1();
+ nsresult ContinueProcessResponse2(nsresult);
+ nsresult ContinueProcessResponse3(nsresult);
+ nsresult ProcessNormal();
+ nsresult ContinueProcessNormal(nsresult);
+ void ProcessAltService();
+ bool ShouldBypassProcessNotModified();
+ nsresult ProcessNotModified();
+ nsresult AsyncProcessRedirection(uint32_t httpStatus);
+ nsresult ContinueProcessRedirection(nsresult);
+ nsresult ContinueProcessRedirectionAfterFallback(nsresult);
+ nsresult ProcessFailedProxyConnect(uint32_t httpStatus);
+ nsresult ProcessFallback(bool *waitingForRedirectCallback);
+ nsresult ContinueProcessFallback(nsresult);
+ void HandleAsyncAbort();
+ nsresult EnsureAssocReq();
+ void ProcessSSLInformation();
+ bool IsHTTPS();
+
+ nsresult ContinueOnStartRequest1(nsresult);
+ nsresult ContinueOnStartRequest2(nsresult);
+ nsresult ContinueOnStartRequest3(nsresult);
+
+ // redirection specific methods
+ void HandleAsyncRedirect();
+ void HandleAsyncAPIRedirect();
+ nsresult ContinueHandleAsyncRedirect(nsresult);
+ void HandleAsyncNotModified();
+ void HandleAsyncFallback();
+ nsresult ContinueHandleAsyncFallback(nsresult);
+ nsresult PromptTempRedirect();
+ virtual nsresult SetupReplacementChannel(nsIURI *, nsIChannel *,
+ bool preserveMethod,
+ uint32_t redirectFlags) override;
+
+ // proxy specific methods
+ nsresult ProxyFailover();
+ nsresult AsyncDoReplaceWithProxy(nsIProxyInfo *);
+ nsresult ContinueDoReplaceWithProxy(nsresult);
+ nsresult ResolveProxy();
+
+ // cache specific methods
+ nsresult OnOfflineCacheEntryAvailable(nsICacheEntry *aEntry,
+ bool aNew,
+ nsIApplicationCache* aAppCache,
+ nsresult aResult);
+ nsresult OnNormalCacheEntryAvailable(nsICacheEntry *aEntry,
+ bool aNew,
+ nsresult aResult);
+ nsresult OpenOfflineCacheEntryForWriting();
+ nsresult OnOfflineCacheEntryForWritingAvailable(nsICacheEntry *aEntry,
+ nsIApplicationCache* aAppCache,
+ nsresult aResult);
+ nsresult OnCacheEntryAvailableInternal(nsICacheEntry *entry,
+ bool aNew,
+ nsIApplicationCache* aAppCache,
+ nsresult status);
+ nsresult GenerateCacheKey(uint32_t postID, nsACString &key);
+ nsresult UpdateExpirationTime();
+ nsresult CheckPartial(nsICacheEntry* aEntry, int64_t *aSize, int64_t *aContentLength);
+ bool ShouldUpdateOfflineCacheEntry();
+ nsresult ReadFromCache(bool alreadyMarkedValid);
+ void CloseCacheEntry(bool doomOnFailure);
+ void CloseOfflineCacheEntry();
+ nsresult InitCacheEntry();
+ void UpdateInhibitPersistentCachingFlag();
+ nsresult InitOfflineCacheEntry();
+ nsresult AddCacheEntryHeaders(nsICacheEntry *entry);
+ nsresult FinalizeCacheEntry();
+ nsresult InstallCacheListener(int64_t offset = 0);
+ nsresult InstallOfflineCacheListener(int64_t offset = 0);
+ void MaybeInvalidateCacheEntryForSubsequentGet();
+ void AsyncOnExamineCachedResponse();
+
+ // Handle the bogus Content-Encoding Apache sometimes sends
+ void ClearBogusContentEncodingIfNeeded();
+
+ // byte range request specific methods
+ nsresult ProcessPartialContent();
+ nsresult OnDoneReadingPartialCacheEntry(bool *streamDone);
+
+ nsresult DoAuthRetry(nsAHttpConnection *);
+
+ void HandleAsyncRedirectChannelToHttps();
+ nsresult StartRedirectChannelToHttps();
+ nsresult ContinueAsyncRedirectChannelToURI(nsresult rv);
+ nsresult OpenRedirectChannel(nsresult rv);
+
+ /**
+ * A function that takes care of reading STS and PKP headers and enforcing
+ * STS and PKP load rules. After a secure channel is erected, STS and PKP
+ * requires the channel to be trusted or any STS or PKP header data on
+ * the channel is ignored. This is called from ProcessResponse.
+ */
+ nsresult ProcessSecurityHeaders();
+
+ /**
+ * Taking care of the Content-Signature header and fail the channel if
+ * the signature verification fails or is required but the header is not
+ * present.
+ * This sets mListener to ContentVerifier, which buffers the entire response
+ * before verifying the Content-Signature header. If the verification is
+ * successful, the load proceeds as usual. If the verification fails, a
+ * NS_ERROR_INVALID_SIGNATURE is thrown and a fallback loaded in nsDocShell
+ */
+ nsresult ProcessContentSignatureHeader(nsHttpResponseHead *aResponseHead);
+
+ /**
+ * A function that will, if the feature is enabled, send security reports.
+ */
+ void ProcessSecurityReport(nsresult status);
+
+ /**
+ * A function to process a single security header (STS or PKP), assumes
+ * some basic sanity checks have been applied to the channel. Called
+ * from ProcessSecurityHeaders.
+ */
+ nsresult ProcessSingleSecurityHeader(uint32_t aType,
+ nsISSLStatus *aSSLStatus,
+ uint32_t aFlags);
+
+ void InvalidateCacheEntryForLocation(const char *location);
+ void AssembleCacheKey(const char *spec, uint32_t postID, nsACString &key);
+ nsresult CreateNewURI(const char *loc, nsIURI **newURI);
+ void DoInvalidateCacheEntry(nsIURI* aURI);
+
+ // Ref RFC2616 13.10: "invalidation... MUST only be performed if
+ // the host part is the same as in the Request-URI"
+ inline bool HostPartIsTheSame(nsIURI *uri) {
+ nsAutoCString tmpHost1, tmpHost2;
+ return (NS_SUCCEEDED(mURI->GetAsciiHost(tmpHost1)) &&
+ NS_SUCCEEDED(uri->GetAsciiHost(tmpHost2)) &&
+ (tmpHost1 == tmpHost2));
+ }
+
+ inline static bool DoNotRender3xxBody(nsresult rv) {
+ return rv == NS_ERROR_REDIRECT_LOOP ||
+ rv == NS_ERROR_CORRUPTED_CONTENT ||
+ rv == NS_ERROR_UNKNOWN_PROTOCOL ||
+ rv == NS_ERROR_MALFORMED_URI;
+ }
+
+ // Report net vs cache time telemetry
+ void ReportNetVSCacheTelemetry();
+
+ // Create a aggregate set of the current notification callbacks
+ // and ensure the transaction is updated to use it.
+ void UpdateAggregateCallbacks();
+
+ static bool HasQueryString(nsHttpRequestHead::ParsedMethodType method, nsIURI * uri);
+ bool ResponseWouldVary(nsICacheEntry* entry);
+ bool IsResumable(int64_t partialLen, int64_t contentLength,
+ bool ignoreMissingPartialLen = false) const;
+ nsresult MaybeSetupByteRangeRequest(int64_t partialLen, int64_t contentLength,
+ bool ignoreMissingPartialLen = false);
+ nsresult SetupByteRangeRequest(int64_t partialLen);
+ void UntieByteRangeRequest();
+ void UntieValidationRequest();
+ nsresult OpenCacheInputStream(nsICacheEntry* cacheEntry, bool startBuffering,
+ bool checkingAppCacheEntry);
+
+ void SetPushedStream(Http2PushedStream *stream);
+
+ void MaybeWarnAboutAppCache();
+
+ void SetLoadGroupUserAgentOverride();
+
+ void SetDoNotTrack();
+
+private:
+ nsCOMPtr<nsICancelable> mProxyRequest;
+
+ RefPtr<nsInputStreamPump> mTransactionPump;
+ RefPtr<nsHttpTransaction> mTransaction;
+
+ uint64_t mLogicalOffset;
+
+ // cache specific data
+ nsCOMPtr<nsICacheEntry> mCacheEntry;
+ // This will be set during OnStopRequest() before calling CloseCacheEntry(),
+ // but only if the listener wants to use alt-data (signaled by
+ // HttpBaseChannel::mPreferredCachedAltDataType being not empty)
+ // Needed because calling openAlternativeOutputStream needs a reference
+ // to the cache entry.
+ nsCOMPtr<nsICacheEntry> mAltDataCacheEntry;
+ // We must close mCacheInputStream explicitly to avoid leaks.
+ AutoClose<nsIInputStream> mCacheInputStream;
+ RefPtr<nsInputStreamPump> mCachePump;
+ nsAutoPtr<nsHttpResponseHead> mCachedResponseHead;
+ nsCOMPtr<nsISupports> mCachedSecurityInfo;
+ uint32_t mPostID;
+ uint32_t mRequestTime;
+
+ nsCOMPtr<nsICacheEntry> mOfflineCacheEntry;
+ uint32_t mOfflineCacheLastModifiedTime;
+ nsCOMPtr<nsIApplicationCache> mApplicationCacheForWrite;
+
+ // auth specific data
+ nsCOMPtr<nsIHttpChannelAuthProvider> mAuthProvider;
+
+ mozilla::TimeStamp mOnStartRequestTimestamp;
+
+ // States of channel interception
+ enum {
+ DO_NOT_INTERCEPT, // no interception will occur
+ MAYBE_INTERCEPT, // interception in progress, but can be cancelled
+ INTERCEPTED, // a synthesized response has been provided
+ } mInterceptCache;
+ // ID of this channel for the interception purposes. Unique unless this
+ // channel is replacing an intercepted one via an redirection.
+ uint64_t mInterceptionID;
+
+ bool PossiblyIntercepted() {
+ return mInterceptCache != DO_NOT_INTERCEPT;
+ }
+
+ // If the channel is associated with a cache, and the URI matched
+ // a fallback namespace, this will hold the key for the fallback
+ // cache entry.
+ nsCString mFallbackKey;
+
+ friend class AutoRedirectVetoNotifier;
+ friend class HttpAsyncAborter<nsHttpChannel>;
+
+ nsCOMPtr<nsIURI> mRedirectURI;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ uint32_t mRedirectType;
+
+ static const uint32_t WAIT_FOR_CACHE_ENTRY = 1;
+ static const uint32_t WAIT_FOR_OFFLINE_CACHE_ENTRY = 2;
+
+ bool mCacheOpenWithPriority;
+ uint32_t mCacheQueueSizeWhenOpen;
+
+ // state flags
+ uint32_t mCachedContentIsValid : 1;
+ uint32_t mCachedContentIsPartial : 1;
+ uint32_t mCacheOnlyMetadata : 1;
+ uint32_t mTransactionReplaced : 1;
+ uint32_t mAuthRetryPending : 1;
+ uint32_t mProxyAuthPending : 1;
+ // Set if before the first authentication attempt a custom authorization
+ // header has been set on the channel. This will make that custom header
+ // go to the server instead of any cached credentials.
+ uint32_t mCustomAuthHeader : 1;
+ uint32_t mResuming : 1;
+ uint32_t mInitedCacheEntry : 1;
+ // True if we are loading a fallback cache entry from the
+ // application cache.
+ uint32_t mFallbackChannel : 1;
+ // True if consumer added its own If-None-Match or If-Modified-Since
+ // headers. In such a case we must not override them in the cache code
+ // and also we want to pass possible 304 code response through.
+ uint32_t mCustomConditionalRequest : 1;
+ uint32_t mFallingBack : 1;
+ uint32_t mWaitingForRedirectCallback : 1;
+ // True if mRequestTime has been set. In such a case it is safe to update
+ // the cache entry's expiration time. Otherwise, it is not(see bug 567360).
+ uint32_t mRequestTimeInitialized : 1;
+ uint32_t mCacheEntryIsReadOnly : 1;
+ uint32_t mCacheEntryIsWriteOnly : 1;
+ // see WAIT_FOR_* constants above
+ uint32_t mCacheEntriesToWaitFor : 2;
+ uint32_t mHasQueryString : 1;
+ // whether cache entry data write was in progress during cache entry check
+ // when true, after we finish read from cache we must check all data
+ // had been loaded from cache. If not, then an error has to be propagated
+ // to the consumer.
+ uint32_t mConcurrentCacheAccess : 1;
+ // whether the request is setup be byte-range
+ uint32_t mIsPartialRequest : 1;
+ // true iff there is AutoRedirectVetoNotifier on the stack
+ uint32_t mHasAutoRedirectVetoNotifier : 1;
+ // consumers set this to true to use cache pinning, this has effect
+ // only when the channel is in an app context (load context has an appid)
+ uint32_t mPinCacheContent : 1;
+ // True if CORS preflight has been performed
+ uint32_t mIsCorsPreflightDone : 1;
+
+ // if the http transaction was performed (i.e. not cached) and
+ // the result in OnStopRequest was known to be correctly delimited
+ // by chunking, content-length, or h2 end-stream framing
+ uint32_t mStronglyFramed : 1;
+
+ // true if an HTTP transaction is created for the socket thread
+ uint32_t mUsedNetwork : 1;
+
+ // the next authentication request can be sent on a whole new connection
+ uint32_t mAuthConnectionRestartable : 1;
+
+ nsCOMPtr<nsIChannel> mPreflightChannel;
+
+ nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack;
+
+ // Needed for accurate DNS timing
+ RefPtr<nsDNSPrefetch> mDNSPrefetch;
+
+ Http2PushedStream *mPushedStream;
+ // True if the channel's principal was found on a phishing, malware, or
+ // tracking (if tracking protection is enabled) blocklist
+ bool mLocalBlocklist;
+
+ nsresult WaitForRedirectCallback();
+ void PushRedirectAsyncFunc(nsContinueRedirectionFunc func);
+ void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
+
+ nsCString mUsername;
+
+ // If non-null, warnings should be reported to this object.
+ HttpChannelSecurityWarningReporter* mWarningReporter;
+
+ RefPtr<ADivertableParentChannel> mParentChannel;
+protected:
+ virtual void DoNotifyListenerCleanup() override;
+
+private: // cache telemetry
+ bool mDidReval;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpChannel, NS_HTTPCHANNEL_IID)
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpChannel_h__
diff --git a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
new file mode 100644
index 000000000..9a2275287
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
@@ -0,0 +1,1682 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab ts=4 sw=4 sts=4 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/Preferences.h"
+#include "nsHttpChannelAuthProvider.h"
+#include "nsNetUtil.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpAuthenticator.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsEscape.h"
+#include "nsAuthInformationHolder.h"
+#include "nsIStringBundle.h"
+#include "nsIPrompt.h"
+#include "netCore.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIURI.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsILoadContext.h"
+#include "nsIURL.h"
+#include "mozilla/Telemetry.h"
+#include "nsIProxiedChannel.h"
+#include "nsIProxyInfo.h"
+
+namespace mozilla {
+namespace net {
+
+#define SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL 0
+#define SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN 1
+#define SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL 2
+
+#define HTTP_AUTH_DIALOG_TOP_LEVEL_DOC 0
+#define HTTP_AUTH_DIALOG_SAME_ORIGIN_SUBRESOURCE 1
+#define HTTP_AUTH_DIALOG_CROSS_ORIGIN_SUBRESOURCE 2
+#define HTTP_AUTH_DIALOG_XHR 3
+
+#define HTTP_AUTH_BASIC_INSECURE 0
+#define HTTP_AUTH_BASIC_SECURE 1
+#define HTTP_AUTH_DIGEST_INSECURE 2
+#define HTTP_AUTH_DIGEST_SECURE 3
+#define HTTP_AUTH_NTLM_INSECURE 4
+#define HTTP_AUTH_NTLM_SECURE 5
+#define HTTP_AUTH_NEGOTIATE_INSECURE 6
+#define HTTP_AUTH_NEGOTIATE_SECURE 7
+
+static void
+GetOriginAttributesSuffix(nsIChannel* aChan, nsACString &aSuffix)
+{
+ NeckoOriginAttributes oa;
+
+ // Deliberately ignoring the result and going with defaults
+ if (aChan) {
+ NS_GetOriginAttributes(aChan, oa);
+ }
+
+ oa.CreateSuffix(aSuffix);
+}
+
+nsHttpChannelAuthProvider::nsHttpChannelAuthProvider()
+ : mAuthChannel(nullptr)
+ , mPort(-1)
+ , mUsingSSL(false)
+ , mProxyUsingSSL(false)
+ , mIsPrivate(false)
+ , mProxyAuthContinuationState(nullptr)
+ , mAuthContinuationState(nullptr)
+ , mProxyAuth(false)
+ , mTriedProxyAuth(false)
+ , mTriedHostAuth(false)
+ , mSuppressDefensiveAuth(false)
+ , mCrossOrigin(false)
+ , mConnectionBased(false)
+ , mHttpHandler(gHttpHandler)
+{
+}
+
+nsHttpChannelAuthProvider::~nsHttpChannelAuthProvider()
+{
+ MOZ_ASSERT(!mAuthChannel, "Disconnect wasn't called");
+}
+
+uint32_t nsHttpChannelAuthProvider::sAuthAllowPref =
+ SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL;
+
+void
+nsHttpChannelAuthProvider::InitializePrefs()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::Preferences::AddUintVarCache(&sAuthAllowPref,
+ "network.auth.subresource-http-auth-allow",
+ SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL);
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::Init(nsIHttpAuthenticableChannel *channel)
+{
+ MOZ_ASSERT(channel, "channel expected!");
+
+ mAuthChannel = channel;
+
+ nsresult rv = mAuthChannel->GetURI(getter_AddRefs(mURI));
+ if (NS_FAILED(rv)) return rv;
+
+ mAuthChannel->GetIsSSL(&mUsingSSL);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProxiedChannel> proxied(do_QueryInterface(channel));
+ if (proxied) {
+ nsCOMPtr<nsIProxyInfo> pi;
+ rv = proxied->GetProxyInfo(getter_AddRefs(pi));
+ if (NS_FAILED(rv)) return rv;
+
+ if (pi) {
+ nsAutoCString proxyType;
+ rv = pi->GetType(proxyType);
+ if (NS_FAILED(rv)) return rv;
+
+ mProxyUsingSSL = proxyType.EqualsLiteral("https");
+ }
+ }
+
+ rv = mURI->GetAsciiHost(mHost);
+ if (NS_FAILED(rv)) return rv;
+
+ // reject the URL if it doesn't specify a host
+ if (mHost.IsEmpty())
+ return NS_ERROR_MALFORMED_URI;
+
+ rv = mURI->GetPort(&mPort);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(channel);
+ mIsPrivate = NS_UsePrivateBrowsing(bareChannel);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::ProcessAuthentication(uint32_t httpStatus,
+ bool SSLConnectFailed)
+{
+ LOG(("nsHttpChannelAuthProvider::ProcessAuthentication "
+ "[this=%p channel=%p code=%u SSLConnectFailed=%d]\n",
+ this, mAuthChannel, httpStatus, SSLConnectFailed));
+
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv)) return rv;
+ if (proxyInfo) {
+ mProxyInfo = do_QueryInterface(proxyInfo);
+ if (!mProxyInfo) return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsAutoCString challenges;
+ mProxyAuth = (httpStatus == 407);
+
+ rv = PrepareForAuthentication(mProxyAuth);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mProxyAuth) {
+ // only allow a proxy challenge if we have a proxy server configured.
+ // otherwise, we could inadvertently expose the user's proxy
+ // credentials to an origin server. We could attempt to proceed as
+ // if we had received a 401 from the server, but why risk flirting
+ // with trouble? IE similarly rejects 407s when a proxy server is
+ // not configured, so there's no reason not to do the same.
+ if (!UsingHttpProxy()) {
+ LOG(("rejecting 407 when proxy server not configured!\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (UsingSSL() && !SSLConnectFailed) {
+ // we need to verify that this challenge came from the proxy
+ // server itself, and not some server on the other side of the
+ // SSL tunnel.
+ LOG(("rejecting 407 from origin server!\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ rv = mAuthChannel->GetProxyChallenges(challenges);
+ }
+ else
+ rv = mAuthChannel->GetWWWChallenges(challenges);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString creds;
+ rv = GetCredentials(challenges.get(), mProxyAuth, creds);
+ if (rv == NS_ERROR_IN_PROGRESS)
+ return rv;
+ if (NS_FAILED(rv))
+ LOG(("unable to authenticate\n"));
+ else {
+ // set the authentication credentials
+ if (mProxyAuth)
+ rv = mAuthChannel->SetProxyCredentials(creds);
+ else
+ rv = mAuthChannel->SetWWWCredentials(creds);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::AddAuthorizationHeaders(bool aDontUseCachedWWWCreds)
+{
+ LOG(("nsHttpChannelAuthProvider::AddAuthorizationHeaders? "
+ "[this=%p channel=%p]\n", this, mAuthChannel));
+
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv)) return rv;
+ if (proxyInfo) {
+ mProxyInfo = do_QueryInterface(proxyInfo);
+ if (!mProxyInfo) return NS_ERROR_NO_INTERFACE;
+ }
+
+ uint32_t loadFlags;
+ rv = mAuthChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // this getter never fails
+ nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate);
+
+ // check if proxy credentials should be sent
+ const char *proxyHost = ProxyHost();
+ if (proxyHost && UsingHttpProxy()) {
+ SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization,
+ "http", proxyHost, ProxyPort(),
+ nullptr, // proxy has no path
+ mProxyIdent);
+ }
+
+ if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
+ LOG(("Skipping Authorization header for anonymous load\n"));
+ return NS_OK;
+ }
+
+ if (aDontUseCachedWWWCreds) {
+ LOG(("Authorization header already present:"
+ " skipping adding auth header from cache\n"));
+ return NS_OK;
+ }
+
+ // check if server credentials should be sent
+ nsAutoCString path, scheme;
+ if (NS_SUCCEEDED(GetCurrentPath(path)) &&
+ NS_SUCCEEDED(mURI->GetScheme(scheme))) {
+ SetAuthorizationHeader(authCache, nsHttp::Authorization,
+ scheme.get(),
+ Host(),
+ Port(),
+ path.get(),
+ mIdent);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::CheckForSuperfluousAuth()
+{
+ LOG(("nsHttpChannelAuthProvider::CheckForSuperfluousAuth? "
+ "[this=%p channel=%p]\n", this, mAuthChannel));
+
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ // we've been called because it has been determined that this channel is
+ // getting loaded without taking the userpass from the URL. if the URL
+ // contained a userpass, then (provided some other conditions are true),
+ // we'll give the user an opportunity to abort the channel as this might be
+ // an attempt to spoof a different site (see bug 232567).
+ if (!ConfirmAuth(NS_LITERAL_STRING("SuperfluousAuth"), true)) {
+ // calling cancel here sets our mStatus and aborts the HTTP
+ // transaction, which prevents OnDataAvailable events.
+ mAuthChannel->Cancel(NS_ERROR_ABORT);
+ return NS_ERROR_ABORT;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::Cancel(nsresult status)
+{
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ if (mAsyncPromptAuthCancelable) {
+ mAsyncPromptAuthCancelable->Cancel(status);
+ mAsyncPromptAuthCancelable = nullptr;
+ }
+
+ if (mGenerateCredentialsCancelable) {
+ mGenerateCredentialsCancelable->Cancel(status);
+ mGenerateCredentialsCancelable = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::Disconnect(nsresult status)
+{
+ mAuthChannel = nullptr;
+
+ if (mAsyncPromptAuthCancelable) {
+ mAsyncPromptAuthCancelable->Cancel(status);
+ mAsyncPromptAuthCancelable = nullptr;
+ }
+
+ if (mGenerateCredentialsCancelable) {
+ mGenerateCredentialsCancelable->Cancel(status);
+ mGenerateCredentialsCancelable = nullptr;
+ }
+
+ NS_IF_RELEASE(mProxyAuthContinuationState);
+ NS_IF_RELEASE(mAuthContinuationState);
+
+ return NS_OK;
+}
+
+// buf contains "domain\user"
+static void
+ParseUserDomain(char16_t *buf,
+ const char16_t **user,
+ const char16_t **domain)
+{
+ char16_t *p = buf;
+ while (*p && *p != '\\') ++p;
+ if (!*p)
+ return;
+ *p = '\0';
+ *domain = buf;
+ *user = p + 1;
+}
+
+// helper function for setting identity from raw user:pass
+static void
+SetIdent(nsHttpAuthIdentity &ident,
+ uint32_t authFlags,
+ char16_t *userBuf,
+ char16_t *passBuf)
+{
+ const char16_t *user = userBuf;
+ const char16_t *domain = nullptr;
+
+ if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN)
+ ParseUserDomain(userBuf, &user, &domain);
+
+ ident.Set(domain, user, passBuf);
+}
+
+// helper function for getting an auth prompt from an interface requestor
+static void
+GetAuthPrompt(nsIInterfaceRequestor *ifreq, bool proxyAuth,
+ nsIAuthPrompt2 **result)
+{
+ if (!ifreq)
+ return;
+
+ uint32_t promptReason;
+ if (proxyAuth)
+ promptReason = nsIAuthPromptProvider::PROMPT_PROXY;
+ else
+ promptReason = nsIAuthPromptProvider::PROMPT_NORMAL;
+
+ nsCOMPtr<nsIAuthPromptProvider> promptProvider = do_GetInterface(ifreq);
+ if (promptProvider)
+ promptProvider->GetAuthPrompt(promptReason,
+ NS_GET_IID(nsIAuthPrompt2),
+ reinterpret_cast<void**>(result));
+ else
+ NS_QueryAuthPrompt2(ifreq, result);
+}
+
+// generate credentials for the given challenge, and update the auth cache.
+nsresult
+nsHttpChannelAuthProvider::GenCredsAndSetEntry(nsIHttpAuthenticator *auth,
+ bool proxyAuth,
+ const char *scheme,
+ const char *host,
+ int32_t port,
+ const char *directory,
+ const char *realm,
+ const char *challenge,
+ const nsHttpAuthIdentity &ident,
+ nsCOMPtr<nsISupports> &sessionState,
+ char **result)
+{
+ nsresult rv;
+ nsISupports *ss = sessionState;
+
+ // set informations that depend on whether
+ // we're authenticating against a proxy
+ // or a webserver
+ nsISupports **continuationState;
+
+ if (proxyAuth) {
+ continuationState = &mProxyAuthContinuationState;
+ } else {
+ continuationState = &mAuthContinuationState;
+ }
+
+ rv = auth->GenerateCredentialsAsync(mAuthChannel,
+ this,
+ challenge,
+ proxyAuth,
+ ident.Domain(),
+ ident.User(),
+ ident.Password(),
+ ss,
+ *continuationState,
+ getter_AddRefs(mGenerateCredentialsCancelable));
+ if (NS_SUCCEEDED(rv)) {
+ // Calling generate credentials async, results will be dispatched to the
+ // main thread by calling OnCredsGenerated method
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ uint32_t generateFlags;
+ rv = auth->GenerateCredentials(mAuthChannel,
+ challenge,
+ proxyAuth,
+ ident.Domain(),
+ ident.User(),
+ ident.Password(),
+ &ss,
+ &*continuationState,
+ &generateFlags,
+ result);
+
+ sessionState.swap(ss);
+ if (NS_FAILED(rv)) return rv;
+
+ // don't log this in release build since it could contain sensitive info.
+#ifdef DEBUG
+ LOG(("generated creds: %s\n", *result));
+#endif
+
+ return UpdateCache(auth, scheme, host, port, directory, realm,
+ challenge, ident, *result, generateFlags, sessionState);
+}
+
+nsresult
+nsHttpChannelAuthProvider::UpdateCache(nsIHttpAuthenticator *auth,
+ const char *scheme,
+ const char *host,
+ int32_t port,
+ const char *directory,
+ const char *realm,
+ const char *challenge,
+ const nsHttpAuthIdentity &ident,
+ const char *creds,
+ uint32_t generateFlags,
+ nsISupports *sessionState)
+{
+ nsresult rv;
+
+ uint32_t authFlags;
+ rv = auth->GetAuthFlags(&authFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // find out if this authenticator allows reuse of credentials and/or
+ // challenge.
+ bool saveCreds =
+ 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS);
+ bool saveChallenge =
+ 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE);
+
+ bool saveIdentity =
+ 0 == (generateFlags & nsIHttpAuthenticator::USING_INTERNAL_IDENTITY);
+
+ // this getter never fails
+ nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate);
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsAutoCString suffix;
+ GetOriginAttributesSuffix(chan, suffix);
+
+
+ // create a cache entry. we do this even though we don't yet know that
+ // these credentials are valid b/c we need to avoid prompting the user
+ // more than once in case the credentials are valid.
+ //
+ // if the credentials are not reusable, then we don't bother sticking
+ // them in the auth cache.
+ rv = authCache->SetAuthEntry(scheme, host, port, directory, realm,
+ saveCreds ? creds : nullptr,
+ saveChallenge ? challenge : nullptr,
+ suffix,
+ saveIdentity ? &ident : nullptr,
+ sessionState);
+ return rv;
+
+}
+
+nsresult
+nsHttpChannelAuthProvider::PrepareForAuthentication(bool proxyAuth)
+{
+ LOG(("nsHttpChannelAuthProvider::PrepareForAuthentication "
+ "[this=%p channel=%p]\n", this, mAuthChannel));
+
+ if (!proxyAuth) {
+ // reset the current proxy continuation state because our last
+ // authentication attempt was completed successfully.
+ NS_IF_RELEASE(mProxyAuthContinuationState);
+ LOG((" proxy continuation state has been reset"));
+ }
+
+ if (!UsingHttpProxy() || mProxyAuthType.IsEmpty())
+ return NS_OK;
+
+ // We need to remove any Proxy_Authorization header left over from a
+ // non-request based authentication handshake (e.g., for NTLM auth).
+
+ nsAutoCString contractId;
+ contractId.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
+ contractId.Append(mProxyAuthType);
+
+ nsresult rv;
+ nsCOMPtr<nsIHttpAuthenticator> precedingAuth =
+ do_GetService(contractId.get(), &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t precedingAuthFlags;
+ rv = precedingAuth->GetAuthFlags(&precedingAuthFlags);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!(precedingAuthFlags & nsIHttpAuthenticator::REQUEST_BASED)) {
+ nsAutoCString challenges;
+ rv = mAuthChannel->GetProxyChallenges(challenges);
+ if (NS_FAILED(rv)) {
+ // delete the proxy authorization header because we weren't
+ // asked to authenticate
+ rv = mAuthChannel->SetProxyCredentials(EmptyCString());
+ if (NS_FAILED(rv)) return rv;
+ LOG((" cleared proxy authorization header"));
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannelAuthProvider::GetCredentials(const char *challenges,
+ bool proxyAuth,
+ nsAFlatCString &creds)
+{
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsAutoCString challenge;
+
+ nsCString authType; // force heap allocation to enable string sharing since
+ // we'll be assigning this value into mAuthType.
+
+ // set informations that depend on whether we're authenticating against a
+ // proxy or a webserver
+ nsISupports **currentContinuationState;
+ nsCString *currentAuthType;
+
+ if (proxyAuth) {
+ currentContinuationState = &mProxyAuthContinuationState;
+ currentAuthType = &mProxyAuthType;
+ } else {
+ currentContinuationState = &mAuthContinuationState;
+ currentAuthType = &mAuthType;
+ }
+
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ bool gotCreds = false;
+
+ // figure out which challenge we can handle and which authenticator to use.
+ for (const char *eol = challenges - 1; eol; ) {
+ const char *p = eol + 1;
+
+ // get the challenge string (LF separated -- see nsHttpHeaderArray)
+ if ((eol = strchr(p, '\n')) != nullptr)
+ challenge.Assign(p, eol - p);
+ else
+ challenge.Assign(p);
+
+ rv = GetAuthenticator(challenge.get(), authType, getter_AddRefs(auth));
+ if (NS_SUCCEEDED(rv)) {
+ //
+ // if we've already selected an auth type from a previous challenge
+ // received while processing this channel, then skip others until
+ // we find a challenge corresponding to the previously tried auth
+ // type.
+ //
+ if (!currentAuthType->IsEmpty() && authType != *currentAuthType)
+ continue;
+
+ //
+ // we allow the routines to run all the way through before we
+ // decide if they are valid.
+ //
+ // we don't worry about the auth cache being altered because that
+ // would have been the last step, and if the error is from updating
+ // the authcache it wasn't really altered anyway. -CTN
+ //
+ // at this point the code is really only useful for client side
+ // errors (it will not automatically fail over to do a different
+ // auth type if the server keeps rejecting what is being sent, even
+ // if a particular auth method only knows 1 thing, like a
+ // non-identity based authentication method)
+ //
+ rv = GetCredentialsForChallenge(challenge.get(), authType.get(),
+ proxyAuth, auth, creds);
+ if (NS_SUCCEEDED(rv)) {
+ gotCreds = true;
+ *currentAuthType = authType;
+
+ break;
+ }
+ else if (rv == NS_ERROR_IN_PROGRESS) {
+ // authentication prompt has been invoked and result is
+ // expected asynchronously, save current challenge being
+ // processed and all remaining challenges to use later in
+ // OnAuthAvailable and now immediately return
+ mCurrentChallenge = challenge;
+ mRemainingChallenges = eol ? eol+1 : nullptr;
+ return rv;
+ }
+
+ // reset the auth type and continuation state
+ NS_IF_RELEASE(*currentContinuationState);
+ currentAuthType->Truncate();
+ }
+ }
+
+ if (!gotCreds && !currentAuthType->IsEmpty()) {
+ // looks like we never found the auth type we were looking for.
+ // reset the auth type and continuation state, and try again.
+ currentAuthType->Truncate();
+ NS_IF_RELEASE(*currentContinuationState);
+
+ rv = GetCredentials(challenges, proxyAuth, creds);
+ }
+
+ return rv;
+}
+
+nsresult
+nsHttpChannelAuthProvider::GetAuthorizationMembers(bool proxyAuth,
+ nsCSubstring& scheme,
+ const char*& host,
+ int32_t& port,
+ nsCSubstring& path,
+ nsHttpAuthIdentity*& ident,
+ nsISupports**& continuationState)
+{
+ if (proxyAuth) {
+ MOZ_ASSERT (UsingHttpProxy(),
+ "proxyAuth is true, but no HTTP proxy is configured!");
+
+ host = ProxyHost();
+ port = ProxyPort();
+ ident = &mProxyIdent;
+ scheme.AssignLiteral("http");
+
+ continuationState = &mProxyAuthContinuationState;
+ }
+ else {
+ host = Host();
+ port = Port();
+ ident = &mIdent;
+
+ nsresult rv;
+ rv = GetCurrentPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ continuationState = &mAuthContinuationState;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannelAuthProvider::GetCredentialsForChallenge(const char *challenge,
+ const char *authType,
+ bool proxyAuth,
+ nsIHttpAuthenticator *auth,
+ nsAFlatCString &creds)
+{
+ LOG(("nsHttpChannelAuthProvider::GetCredentialsForChallenge "
+ "[this=%p channel=%p proxyAuth=%d challenges=%s]\n",
+ this, mAuthChannel, proxyAuth, challenge));
+
+ // this getter never fails
+ nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate);
+
+ uint32_t authFlags;
+ nsresult rv = auth->GetAuthFlags(&authFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString realm;
+ ParseRealm(challenge, realm);
+
+ // if no realm, then use the auth type as the realm. ToUpperCase so the
+ // ficticious realm stands out a bit more.
+ // XXX this will cause some single signon misses!
+ // XXX this was meant to be used with NTLM, which supplies no realm.
+ /*
+ if (realm.IsEmpty()) {
+ realm = authType;
+ ToUpperCase(realm);
+ }
+ */
+
+ // set informations that depend on whether
+ // we're authenticating against a proxy
+ // or a webserver
+ const char *host;
+ int32_t port;
+ nsHttpAuthIdentity *ident;
+ nsAutoCString path, scheme;
+ bool identFromURI = false;
+ nsISupports **continuationState;
+
+ rv = GetAuthorizationMembers(proxyAuth, scheme, host, port,
+ path, ident, continuationState);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t loadFlags;
+ rv = mAuthChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!proxyAuth) {
+ // if this is the first challenge, then try using the identity
+ // specified in the URL.
+ if (mIdent.IsEmpty()) {
+ GetIdentityFromURI(authFlags, mIdent);
+ identFromURI = !mIdent.IsEmpty();
+ }
+
+ if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !identFromURI) {
+ LOG(("Skipping authentication for anonymous non-proxy request\n"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Let explicit URL credentials pass
+ // regardless of the LOAD_ANONYMOUS flag
+ }
+ else if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !UsingHttpProxy()) {
+ LOG(("Skipping authentication for anonymous non-proxy request\n"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsAutoCString suffix;
+ GetOriginAttributesSuffix(chan, suffix);
+
+ //
+ // if we already tried some credentials for this transaction, then
+ // we need to possibly clear them from the cache, unless the credentials
+ // in the cache have changed, in which case we'd want to give them a
+ // try instead.
+ //
+ nsHttpAuthEntry *entry = nullptr;
+ authCache->GetAuthEntryForDomain(scheme.get(), host, port,
+ realm.get(), suffix, &entry);
+
+ // hold reference to the auth session state (in case we clear our
+ // reference to the entry).
+ nsCOMPtr<nsISupports> sessionStateGrip;
+ if (entry)
+ sessionStateGrip = entry->mMetaData;
+
+ // remember if we already had the continuation state. it means we are in
+ // the middle of the authentication exchange and the connection must be
+ // kept sticky then (and only then).
+ bool authAtProgress = !!*continuationState;
+
+ // for digest auth, maybe our cached nonce value simply timed out...
+ bool identityInvalid;
+ nsISupports *sessionState = sessionStateGrip;
+ rv = auth->ChallengeReceived(mAuthChannel,
+ challenge,
+ proxyAuth,
+ &sessionState,
+ &*continuationState,
+ &identityInvalid);
+ sessionStateGrip.swap(sessionState);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG((" identity invalid = %d\n", identityInvalid));
+
+ if (mConnectionBased && identityInvalid) {
+ // If the flag is set and identity is invalid, it means we received the first
+ // challange for a new negotiation round after negotiating a connection based
+ // auth failed (invalid password).
+ // The mConnectionBased flag is set later for the newly received challenge,
+ // so here it reflects the previous 401/7 response schema.
+ mAuthChannel->CloseStickyConnection();
+ }
+
+ mConnectionBased = !!(authFlags & nsIHttpAuthenticator::CONNECTION_BASED);
+
+ // It's legal if the peer closes the connection after the first 401/7.
+ // Making the connection sticky will prevent its restart giving the user
+ // a 'network reset' error every time. Hence, we mark the connection
+ // as restartable.
+ mAuthChannel->ConnectionRestartable(mConnectionBased && !authAtProgress);
+
+ if (identityInvalid) {
+ if (entry) {
+ if (ident->Equals(entry->Identity())) {
+ if (!identFromURI) {
+ LOG((" clearing bad auth cache entry\n"));
+ // ok, we've already tried this user identity, so clear the
+ // corresponding entry from the auth cache.
+ authCache->ClearAuthEntry(scheme.get(), host,
+ port, realm.get(),
+ suffix);
+ entry = nullptr;
+ ident->Clear();
+ }
+ }
+ else if (!identFromURI ||
+ (nsCRT::strcmp(ident->User(),
+ entry->Identity().User()) == 0 &&
+ !(loadFlags &
+ (nsIChannel::LOAD_ANONYMOUS |
+ nsIChannel::LOAD_EXPLICIT_CREDENTIALS)))) {
+ LOG((" taking identity from auth cache\n"));
+ // the password from the auth cache is more likely to be
+ // correct than the one in the URL. at least, we know that it
+ // works with the given username. it is possible for a server
+ // to distinguish logons based on the supplied password alone,
+ // but that would be quite unusual... and i don't think we need
+ // to worry about such unorthodox cases.
+ ident->Set(entry->Identity());
+ identFromURI = false;
+ if (entry->Creds()[0] != '\0') {
+ LOG((" using cached credentials!\n"));
+ creds.Assign(entry->Creds());
+ return entry->AddPath(path.get());
+ }
+ }
+ }
+ else if (!identFromURI) {
+ // hmm... identity invalid, but no auth entry! the realm probably
+ // changed (see bug 201986).
+ ident->Clear();
+ }
+
+ if (!entry && ident->IsEmpty()) {
+ uint32_t level = nsIAuthPrompt2::LEVEL_NONE;
+ if ((!proxyAuth && mUsingSSL) || (proxyAuth && mProxyUsingSSL))
+ level = nsIAuthPrompt2::LEVEL_SECURE;
+ else if (authFlags & nsIHttpAuthenticator::IDENTITY_ENCRYPTED)
+ level = nsIAuthPrompt2::LEVEL_PW_ENCRYPTED;
+
+ // Collect statistics on how frequently the various types of HTTP
+ // authentication are used over SSL and non-SSL connections.
+ if (gHttpHandler->IsTelemetryEnabled()) {
+ if (NS_LITERAL_CSTRING("basic").LowerCaseEqualsASCII(authType)) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_BASIC_SECURE : HTTP_AUTH_BASIC_INSECURE);
+ } else if (NS_LITERAL_CSTRING("digest").LowerCaseEqualsASCII(authType)) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_DIGEST_SECURE : HTTP_AUTH_DIGEST_INSECURE);
+ } else if (NS_LITERAL_CSTRING("ntlm").LowerCaseEqualsASCII(authType)) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_NTLM_SECURE : HTTP_AUTH_NTLM_INSECURE);
+ } else if (NS_LITERAL_CSTRING("negotiate").LowerCaseEqualsASCII(authType)) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_NEGOTIATE_SECURE : HTTP_AUTH_NEGOTIATE_INSECURE);
+ }
+ }
+
+ // Depending on the pref setting, the authentication dialog may be
+ // blocked for all sub-resources, blocked for cross-origin
+ // sub-resources, or always allowed for sub-resources.
+ // For more details look at the bug 647010.
+ // BlockPrompt will set mCrossOrigin parameter as well.
+ if (BlockPrompt()) {
+ LOG(("nsHttpChannelAuthProvider::GetCredentialsForChallenge: "
+ "Prompt is blocked [this=%p pref=%d]\n",
+ this, sAuthAllowPref));
+ return NS_ERROR_ABORT;
+ }
+
+ // at this point we are forced to interact with the user to get
+ // their username and password for this domain.
+ rv = PromptForIdentity(level, proxyAuth, realm.get(),
+ authType, authFlags, *ident);
+ if (NS_FAILED(rv)) return rv;
+ identFromURI = false;
+ }
+ }
+
+ if (identFromURI) {
+ // Warn the user before automatically using the identity from the URL
+ // to automatically log them into a site (see bug 232567).
+ if (!ConfirmAuth(NS_LITERAL_STRING("AutomaticAuth"), false)) {
+ // calling cancel here sets our mStatus and aborts the HTTP
+ // transaction, which prevents OnDataAvailable events.
+ mAuthChannel->Cancel(NS_ERROR_ABORT);
+ // this return code alone is not equivalent to Cancel, since
+ // it only instructs our caller that authentication failed.
+ // without an explicit call to Cancel, our caller would just
+ // load the page that accompanies the HTTP auth challenge.
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ //
+ // get credentials for the given user:pass
+ //
+ // always store the credentials we're trying now so that they will be used
+ // on subsequent links. This will potentially remove good credentials from
+ // the cache. This is ok as we don't want to use cached credentials if the
+ // user specified something on the URI or in another manner. This is so
+ // that we don't transparently authenticate as someone they're not
+ // expecting to authenticate as.
+ //
+ nsXPIDLCString result;
+ rv = GenCredsAndSetEntry(auth, proxyAuth, scheme.get(), host, port,
+ path.get(), realm.get(), challenge, *ident,
+ sessionStateGrip, getter_Copies(result));
+ if (NS_SUCCEEDED(rv))
+ creds = result;
+ return rv;
+}
+
+bool
+nsHttpChannelAuthProvider::BlockPrompt()
+{
+ // Verify that it's ok to prompt for credentials here, per spec
+ // http://xhr.spec.whatwg.org/#the-send%28%29-method
+
+ nsCOMPtr<nsIHttpChannelInternal> chanInternal = do_QueryInterface(mAuthChannel);
+ MOZ_ASSERT(chanInternal);
+
+ if (chanInternal->GetBlockAuthPrompt()) {
+ LOG(("nsHttpChannelAuthProvider::BlockPrompt: Prompt is blocked "
+ "[this=%p channel=%p]\n", this, mAuthChannel));
+ return true;
+ }
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ chan->GetLoadInfo(getter_AddRefs(loadInfo));
+
+ // We will treat loads w/o loadInfo as a top level document.
+ bool topDoc = true;
+ bool xhr = false;
+
+ if (loadInfo) {
+ if (loadInfo->GetExternalContentPolicyType() !=
+ nsIContentPolicy::TYPE_DOCUMENT) {
+ topDoc = false;
+ }
+ if (loadInfo->GetExternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_XMLHTTPREQUEST) {
+ xhr = true;
+ }
+
+ if (!topDoc && !xhr) {
+ nsCOMPtr<nsIURI> topURI;
+ chanInternal->GetTopWindowURI(getter_AddRefs(topURI));
+
+ if (!topURI) {
+ // If we do not have topURI try the loadingPrincipal.
+ nsCOMPtr<nsIPrincipal> loadingPrinc = loadInfo->LoadingPrincipal();
+ if (loadingPrinc) {
+ loadingPrinc->GetURI(getter_AddRefs(topURI));
+ }
+ }
+
+ if (!NS_SecurityCompareURIs(topURI, mURI, true)) {
+ mCrossOrigin = true;
+ }
+ }
+ }
+
+ if (gHttpHandler->IsTelemetryEnabled()) {
+ if (topDoc) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS,
+ HTTP_AUTH_DIALOG_TOP_LEVEL_DOC);
+ } else if (xhr) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS,
+ HTTP_AUTH_DIALOG_XHR);
+ } else if (!mCrossOrigin) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS,
+ HTTP_AUTH_DIALOG_SAME_ORIGIN_SUBRESOURCE);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS,
+ HTTP_AUTH_DIALOG_CROSS_ORIGIN_SUBRESOURCE);
+ }
+ }
+
+ switch (sAuthAllowPref) {
+ case SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL:
+ // Do not open the http-authentication credentials dialog for
+ // the sub-resources.
+ return !topDoc && !xhr;
+ case SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN:
+ // Open the http-authentication credentials dialog for
+ // the sub-resources only if they are not cross-origin.
+ return !topDoc && !xhr && mCrossOrigin;
+ case SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL:
+ // Allow the http-authentication dialog.
+ return false;
+ default:
+ // This is an invalid value.
+ MOZ_ASSERT(false, "A non valid value!");
+ }
+ return false;
+}
+
+inline void
+GetAuthType(const char *challenge, nsCString &authType)
+{
+ const char *p;
+
+ // get the challenge type
+ if ((p = strchr(challenge, ' ')) != nullptr)
+ authType.Assign(challenge, p - challenge);
+ else
+ authType.Assign(challenge);
+}
+
+nsresult
+nsHttpChannelAuthProvider::GetAuthenticator(const char *challenge,
+ nsCString &authType,
+ nsIHttpAuthenticator **auth)
+{
+ LOG(("nsHttpChannelAuthProvider::GetAuthenticator [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ GetAuthType(challenge, authType);
+
+ // normalize to lowercase
+ ToLowerCase(authType);
+
+ nsAutoCString contractid;
+ contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
+ contractid.Append(authType);
+
+ return CallGetService(contractid.get(), auth);
+}
+
+void
+nsHttpChannelAuthProvider::GetIdentityFromURI(uint32_t authFlags,
+ nsHttpAuthIdentity &ident)
+{
+ LOG(("nsHttpChannelAuthProvider::GetIdentityFromURI [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ nsAutoString userBuf;
+ nsAutoString passBuf;
+
+ // XXX i18n
+ nsAutoCString buf;
+ mURI->GetUsername(buf);
+ if (!buf.IsEmpty()) {
+ NS_UnescapeURL(buf);
+ CopyASCIItoUTF16(buf, userBuf);
+ mURI->GetPassword(buf);
+ if (!buf.IsEmpty()) {
+ NS_UnescapeURL(buf);
+ CopyASCIItoUTF16(buf, passBuf);
+ }
+ }
+
+ if (!userBuf.IsEmpty()) {
+ SetIdent(ident, authFlags, (char16_t *) userBuf.get(),
+ (char16_t *) passBuf.get());
+ }
+}
+
+void
+nsHttpChannelAuthProvider::ParseRealm(const char *challenge,
+ nsACString &realm)
+{
+ //
+ // From RFC2617 section 1.2, the realm value is defined as such:
+ //
+ // realm = "realm" "=" realm-value
+ // realm-value = quoted-string
+ //
+ // but, we'll accept anything after the the "=" up to the first space, or
+ // end-of-line, if the string is not quoted.
+ //
+
+ const char *p = PL_strcasestr(challenge, "realm=");
+ if (p) {
+ bool has_quote = false;
+ p += 6;
+ if (*p == '"') {
+ has_quote = true;
+ p++;
+ }
+
+ const char *end;
+ if (has_quote) {
+ end = p;
+ while (*end) {
+ if (*end == '\\') {
+ // escaped character, store that one instead if not zero
+ if (!*++end)
+ break;
+ }
+ else if (*end == '\"')
+ // end of string
+ break;
+
+ realm.Append(*end);
+ ++end;
+ }
+ }
+ else {
+ // realm given without quotes
+ end = strchr(p, ' ');
+ if (end)
+ realm.Assign(p, end - p);
+ else
+ realm.Assign(p);
+ }
+ }
+}
+
+
+class nsHTTPAuthInformation : public nsAuthInformationHolder {
+public:
+ nsHTTPAuthInformation(uint32_t aFlags, const nsString& aRealm,
+ const nsCString& aAuthType)
+ : nsAuthInformationHolder(aFlags, aRealm, aAuthType) {}
+
+ void SetToHttpAuthIdentity(uint32_t authFlags,
+ nsHttpAuthIdentity& identity);
+};
+
+void
+nsHTTPAuthInformation::SetToHttpAuthIdentity(uint32_t authFlags,
+ nsHttpAuthIdentity& identity)
+{
+ identity.Set(Domain().get(), User().get(), Password().get());
+}
+
+nsresult
+nsHttpChannelAuthProvider::PromptForIdentity(uint32_t level,
+ bool proxyAuth,
+ const char *realm,
+ const char *authType,
+ uint32_t authFlags,
+ nsHttpAuthIdentity &ident)
+{
+ LOG(("nsHttpChannelAuthProvider::PromptForIdentity [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIAuthPrompt2> authPrompt;
+ GetAuthPrompt(callbacks, proxyAuth, getter_AddRefs(authPrompt));
+ if (!authPrompt && loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ GetAuthPrompt(cbs, proxyAuth, getter_AddRefs(authPrompt));
+ }
+ if (!authPrompt)
+ return NS_ERROR_NO_INTERFACE;
+
+ // XXX i18n: need to support non-ASCII realm strings (see bug 41489)
+ NS_ConvertASCIItoUTF16 realmU(realm);
+
+ // prompt the user...
+ uint32_t promptFlags = 0;
+ if (proxyAuth)
+ {
+ promptFlags |= nsIAuthInformation::AUTH_PROXY;
+ if (mTriedProxyAuth)
+ promptFlags |= nsIAuthInformation::PREVIOUS_FAILED;
+ mTriedProxyAuth = true;
+ }
+ else {
+ promptFlags |= nsIAuthInformation::AUTH_HOST;
+ if (mTriedHostAuth)
+ promptFlags |= nsIAuthInformation::PREVIOUS_FAILED;
+ mTriedHostAuth = true;
+ }
+
+ if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN)
+ promptFlags |= nsIAuthInformation::NEED_DOMAIN;
+
+ if (mCrossOrigin) {
+ promptFlags |= nsIAuthInformation::CROSS_ORIGIN_SUB_RESOURCE;
+ }
+
+ RefPtr<nsHTTPAuthInformation> holder =
+ new nsHTTPAuthInformation(promptFlags, realmU,
+ nsDependentCString(authType));
+ if (!holder)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(mAuthChannel, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ rv =
+ authPrompt->AsyncPromptAuth(channel, this, nullptr, level, holder,
+ getter_AddRefs(mAsyncPromptAuthCancelable));
+
+ if (NS_SUCCEEDED(rv)) {
+ // indicate using this error code that authentication prompt
+ // result is expected asynchronously
+ rv = NS_ERROR_IN_PROGRESS;
+ }
+ else {
+ // Fall back to synchronous prompt
+ bool retval = false;
+ rv = authPrompt->PromptAuth(channel, level, holder, &retval);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!retval)
+ rv = NS_ERROR_ABORT;
+ else
+ holder->SetToHttpAuthIdentity(authFlags, ident);
+ }
+
+ // remember that we successfully showed the user an auth dialog
+ if (!proxyAuth)
+ mSuppressDefensiveAuth = true;
+
+ if (mConnectionBased) {
+ // Connection can be reset by the server in the meantime user is entering
+ // the credentials. Result would be just a "Connection was reset" error.
+ // Hence, we drop the current regardless if the user would make it on time
+ // to provide credentials.
+ // It's OK to send the NTLM type 1 message (response to the plain "NTLM"
+ // challenge) on a new connection.
+ mAuthChannel->CloseStickyConnection();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthAvailable(nsISupports *aContext,
+ nsIAuthInformation *aAuthInfo)
+{
+ LOG(("nsHttpChannelAuthProvider::OnAuthAvailable [this=%p channel=%p]",
+ this, mAuthChannel));
+
+ mAsyncPromptAuthCancelable = nullptr;
+ if (!mAuthChannel)
+ return NS_OK;
+
+ nsresult rv;
+
+ const char *host;
+ int32_t port;
+ nsHttpAuthIdentity *ident;
+ nsAutoCString path, scheme;
+ nsISupports **continuationState;
+ rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port,
+ path, ident, continuationState);
+ if (NS_FAILED(rv))
+ OnAuthCancelled(aContext, false);
+
+ nsAutoCString realm;
+ ParseRealm(mCurrentChallenge.get(), realm);
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsAutoCString suffix;
+ GetOriginAttributesSuffix(chan, suffix);
+
+ nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate);
+ nsHttpAuthEntry *entry = nullptr;
+ authCache->GetAuthEntryForDomain(scheme.get(), host, port,
+ realm.get(), suffix,
+ &entry);
+
+ nsCOMPtr<nsISupports> sessionStateGrip;
+ if (entry)
+ sessionStateGrip = entry->mMetaData;
+
+ nsAuthInformationHolder* holder =
+ static_cast<nsAuthInformationHolder*>(aAuthInfo);
+ ident->Set(holder->Domain().get(),
+ holder->User().get(),
+ holder->Password().get());
+
+ nsAutoCString unused;
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ rv = GetAuthenticator(mCurrentChallenge.get(), unused,
+ getter_AddRefs(auth));
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false, "GetAuthenticator failed");
+ OnAuthCancelled(aContext, true);
+ return NS_OK;
+ }
+
+ nsXPIDLCString creds;
+ rv = GenCredsAndSetEntry(auth, mProxyAuth,
+ scheme.get(), host, port, path.get(),
+ realm.get(), mCurrentChallenge.get(), *ident,
+ sessionStateGrip, getter_Copies(creds));
+
+ mCurrentChallenge.Truncate();
+ if (NS_FAILED(rv)) {
+ OnAuthCancelled(aContext, true);
+ return NS_OK;
+ }
+
+ return ContinueOnAuthAvailable(creds);
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthCancelled(nsISupports *aContext,
+ bool userCancel)
+{
+ LOG(("nsHttpChannelAuthProvider::OnAuthCancelled [this=%p channel=%p]",
+ this, mAuthChannel));
+
+ mAsyncPromptAuthCancelable = nullptr;
+ if (!mAuthChannel)
+ return NS_OK;
+
+ // When user cancels or auth fails we want to close the connection for
+ // connection based schemes like NTLM. Some servers don't like re-negotiation
+ // on the same connection.
+ if (mConnectionBased) {
+ mAuthChannel->CloseStickyConnection();
+ mConnectionBased = false;
+ }
+
+ if (userCancel) {
+ if (!mRemainingChallenges.IsEmpty()) {
+ // there are still some challenges to process, do so
+ nsresult rv;
+
+ // Get rid of current continuationState to avoid reusing it in
+ // next challenges since it is no longer relevant.
+ if (mProxyAuth) {
+ NS_IF_RELEASE(mProxyAuthContinuationState);
+ } else {
+ NS_IF_RELEASE(mAuthContinuationState);
+ }
+ nsAutoCString creds;
+ rv = GetCredentials(mRemainingChallenges.get(), mProxyAuth, creds);
+ if (NS_SUCCEEDED(rv)) {
+ // GetCredentials loaded the credentials from the cache or
+ // some other way in a synchronous manner, process those
+ // credentials now
+ mRemainingChallenges.Truncate();
+ return ContinueOnAuthAvailable(creds);
+ }
+ else if (rv == NS_ERROR_IN_PROGRESS) {
+ // GetCredentials successfully queued another authprompt for
+ // a challenge from the list, we are now waiting for the user
+ // to provide the credentials
+ return NS_OK;
+ }
+
+ // otherwise, we failed...
+ }
+
+ mRemainingChallenges.Truncate();
+ }
+
+ mAuthChannel->OnAuthCancelled(userCancel);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::OnCredsGenerated(const char *aGeneratedCreds,
+ uint32_t aFlags,
+ nsresult aResult,
+ nsISupports* aSessionState,
+ nsISupports* aContinuationState)
+{
+ nsresult rv;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // When channel is closed, do not proceed
+ if (!mAuthChannel) {
+ return NS_OK;
+ }
+
+ mGenerateCredentialsCancelable = nullptr;
+
+ if (NS_FAILED(aResult)) {
+ return OnAuthCancelled(nullptr, true);
+ }
+
+ // We want to update m(Proxy)AuthContinuationState in case it was changed by
+ // nsHttpNegotiateAuth::GenerateCredentials
+ nsCOMPtr<nsISupports> contState(aContinuationState);
+ if (mProxyAuth) {
+ contState.swap(mProxyAuthContinuationState);
+ } else {
+ contState.swap(mAuthContinuationState);
+ }
+
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsAutoCString unused;
+ rv = GetAuthenticator(mCurrentChallenge.get(), unused, getter_AddRefs(auth));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char *host;
+ int32_t port;
+ nsHttpAuthIdentity *ident;
+ nsAutoCString directory, scheme;
+ nsISupports **unusedContinuationState;
+
+ // Get realm from challenge
+ nsAutoCString realm;
+ ParseRealm(mCurrentChallenge.get(), realm);
+
+ rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port,
+ directory, ident, unusedContinuationState);
+ if (NS_FAILED(rv)) return rv;
+
+ UpdateCache(auth, scheme.get(), host, port, directory.get(), realm.get(),
+ mCurrentChallenge.get(), *ident, aGeneratedCreds, aFlags, aSessionState);
+ mCurrentChallenge.Truncate();
+
+ ContinueOnAuthAvailable(nsDependentCString(aGeneratedCreds));
+ return NS_OK;
+}
+
+nsresult
+nsHttpChannelAuthProvider::ContinueOnAuthAvailable(const nsCSubstring& creds)
+{
+ nsresult rv;
+ if (mProxyAuth)
+ rv = mAuthChannel->SetProxyCredentials(creds);
+ else
+ rv = mAuthChannel->SetWWWCredentials(creds);
+ if (NS_FAILED(rv)) return rv;
+
+ // drop our remaining list of challenges. We don't need them, because we
+ // have now authenticated against a challenge and will be sending that
+ // information to the server (or proxy). If it doesn't accept our
+ // authentication it'll respond with failure and resend the challenge list
+ mRemainingChallenges.Truncate();
+
+ mAuthChannel->OnAuthAvailable();
+
+ return NS_OK;
+}
+
+bool
+nsHttpChannelAuthProvider::ConfirmAuth(const nsString &bundleKey,
+ bool doYesNoPrompt)
+{
+ // skip prompting the user if
+ // 1) we've already prompted the user
+ // 2) we're not a toplevel channel
+ // 3) the userpass length is less than the "phishy" threshold
+
+ uint32_t loadFlags;
+ nsresult rv = mAuthChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv))
+ return true;
+
+ if (mSuppressDefensiveAuth ||
+ !(loadFlags & nsIChannel::LOAD_INITIAL_DOCUMENT_URI))
+ return true;
+
+ nsAutoCString userPass;
+ rv = mURI->GetUserPass(userPass);
+ if (NS_FAILED(rv) ||
+ (userPass.Length() < gHttpHandler->PhishyUserPassLength()))
+ return true;
+
+ // we try to confirm by prompting the user. if we cannot do so, then
+ // assume the user said ok. this is done to keep things working in
+ // embedded builds, where the string bundle might not be present, etc.
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ if (!bundleService)
+ return true;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
+ if (!bundle)
+ return true;
+
+ nsAutoCString host;
+ rv = mURI->GetHost(host);
+ if (NS_FAILED(rv))
+ return true;
+
+ nsAutoCString user;
+ rv = mURI->GetUsername(user);
+ if (NS_FAILED(rv))
+ return true;
+
+ NS_ConvertUTF8toUTF16 ucsHost(host), ucsUser(user);
+ const char16_t *strs[2] = { ucsHost.get(), ucsUser.get() };
+
+ nsXPIDLString msg;
+ bundle->FormatStringFromName(bundleKey.get(), strs, 2, getter_Copies(msg));
+ if (!msg)
+ return true;
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (NS_FAILED(rv))
+ return true;
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (NS_FAILED(rv))
+ return true;
+
+ nsCOMPtr<nsIPrompt> prompt;
+ NS_QueryNotificationCallbacks(callbacks, loadGroup, NS_GET_IID(nsIPrompt),
+ getter_AddRefs(prompt));
+ if (!prompt)
+ return true;
+
+ // do not prompt again
+ mSuppressDefensiveAuth = true;
+
+ bool confirmed;
+ if (doYesNoPrompt) {
+ int32_t choice;
+ bool checkState = false;
+ rv = prompt->ConfirmEx(nullptr, msg,
+ nsIPrompt::BUTTON_POS_1_DEFAULT +
+ nsIPrompt::STD_YES_NO_BUTTONS,
+ nullptr, nullptr, nullptr, nullptr,
+ &checkState, &choice);
+ if (NS_FAILED(rv))
+ return true;
+
+ confirmed = choice == 0;
+ }
+ else {
+ rv = prompt->Confirm(nullptr, msg, &confirmed);
+ if (NS_FAILED(rv))
+ return true;
+ }
+
+ return confirmed;
+}
+
+void
+nsHttpChannelAuthProvider::SetAuthorizationHeader(nsHttpAuthCache *authCache,
+ nsHttpAtom header,
+ const char *scheme,
+ const char *host,
+ int32_t port,
+ const char *path,
+ nsHttpAuthIdentity &ident)
+{
+ nsHttpAuthEntry *entry = nullptr;
+ nsresult rv;
+
+ // set informations that depend on whether
+ // we're authenticating against a proxy
+ // or a webserver
+ nsISupports **continuationState;
+
+ if (header == nsHttp::Proxy_Authorization) {
+ continuationState = &mProxyAuthContinuationState;
+ } else {
+ continuationState = &mAuthContinuationState;
+ }
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsAutoCString suffix;
+ GetOriginAttributesSuffix(chan, suffix);
+
+ rv = authCache->GetAuthEntryForPath(scheme, host, port, path,
+ suffix, &entry);
+ if (NS_SUCCEEDED(rv)) {
+ // if we are trying to add a header for origin server auth and if the
+ // URL contains an explicit username, then try the given username first.
+ // we only want to do this, however, if we know the URL requires auth
+ // based on the presence of an auth cache entry for this URL (which is
+ // true since we are here). but, if the username from the URL matches
+ // the username from the cache, then we should prefer the password
+ // stored in the cache since that is most likely to be valid.
+ if (header == nsHttp::Authorization && entry->Domain()[0] == '\0') {
+ GetIdentityFromURI(0, ident);
+ // if the usernames match, then clear the ident so we will pick
+ // up the one from the auth cache instead.
+ // when this is undesired, specify LOAD_EXPLICIT_CREDENTIALS load
+ // flag.
+ if (nsCRT::strcmp(ident.User(), entry->User()) == 0) {
+ uint32_t loadFlags;
+ if (NS_SUCCEEDED(mAuthChannel->GetLoadFlags(&loadFlags)) &&
+ !(loadFlags & nsIChannel::LOAD_EXPLICIT_CREDENTIALS)) {
+ ident.Clear();
+ }
+ }
+ }
+ bool identFromURI;
+ if (ident.IsEmpty()) {
+ ident.Set(entry->Identity());
+ identFromURI = false;
+ }
+ else
+ identFromURI = true;
+
+ nsXPIDLCString temp;
+ const char *creds = entry->Creds();
+ const char *challenge = entry->Challenge();
+ // we can only send a preemptive Authorization header if we have either
+ // stored credentials or a stored challenge from which to derive
+ // credentials. if the identity is from the URI, then we cannot use
+ // the stored credentials.
+ if ((!creds[0] || identFromURI) && challenge[0]) {
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsAutoCString unused;
+ rv = GetAuthenticator(challenge, unused, getter_AddRefs(auth));
+ if (NS_SUCCEEDED(rv)) {
+ bool proxyAuth = (header == nsHttp::Proxy_Authorization);
+ rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port,
+ path, entry->Realm(), challenge, ident,
+ entry->mMetaData, getter_Copies(temp));
+ if (NS_SUCCEEDED(rv))
+ creds = temp.get();
+
+ // make sure the continuation state is null since we do not
+ // support mixing preemptive and 'multirequest' authentication.
+ NS_IF_RELEASE(*continuationState);
+ }
+ }
+ if (creds[0]) {
+ LOG((" adding \"%s\" request header\n", header.get()));
+ if (header == nsHttp::Proxy_Authorization)
+ mAuthChannel->SetProxyCredentials(nsDependentCString(creds));
+ else
+ mAuthChannel->SetWWWCredentials(nsDependentCString(creds));
+
+ // suppress defensive auth prompting for this channel since we know
+ // that we already prompted at least once this session. we only do
+ // this for non-proxy auth since the URL's userpass is not used for
+ // proxy auth.
+ if (header == nsHttp::Authorization)
+ mSuppressDefensiveAuth = true;
+ }
+ else
+ ident.Clear(); // don't remember the identity
+ }
+}
+
+nsresult
+nsHttpChannelAuthProvider::GetCurrentPath(nsACString &path)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
+ if (url)
+ rv = url->GetDirectory(path);
+ else
+ rv = mURI->GetPath(path);
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsHttpChannelAuthProvider, nsICancelable,
+ nsIHttpChannelAuthProvider, nsIAuthPromptCallback, nsIHttpAuthenticatorCallback)
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpChannelAuthProvider.h b/netwerk/protocol/http/nsHttpChannelAuthProvider.h
new file mode 100644
index 000000000..44d79b22b
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.h
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set et cin ts=4 sw=4 sts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpChannelAuthProvider_h__
+#define nsHttpChannelAuthProvider_h__
+
+#include "nsIHttpChannelAuthProvider.h"
+#include "nsIAuthPromptCallback.h"
+#include "nsIHttpAuthenticatorCallback.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsHttpAuthCache.h"
+#include "nsProxyInfo.h"
+#include "nsCRT.h"
+#include "nsICancelableRunnable.h"
+
+class nsIHttpAuthenticableChannel;
+class nsIHttpAuthenticator;
+class nsIURI;
+
+namespace mozilla { namespace net {
+
+class nsHttpHandler;
+
+class nsHttpChannelAuthProvider : public nsIHttpChannelAuthProvider
+ , public nsIAuthPromptCallback
+ , public nsIHttpAuthenticatorCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+ NS_DECL_NSIHTTPCHANNELAUTHPROVIDER
+ NS_DECL_NSIAUTHPROMPTCALLBACK
+ NS_DECL_NSIHTTPAUTHENTICATORCALLBACK
+
+ nsHttpChannelAuthProvider();
+ static void InitializePrefs();
+private:
+ virtual ~nsHttpChannelAuthProvider();
+
+ const char *ProxyHost() const
+ { return mProxyInfo ? mProxyInfo->Host().get() : nullptr; }
+
+ int32_t ProxyPort() const
+ { return mProxyInfo ? mProxyInfo->Port() : -1; }
+
+ const char *Host() const { return mHost.get(); }
+ int32_t Port() const { return mPort; }
+ bool UsingSSL() const { return mUsingSSL; }
+
+ bool UsingHttpProxy() const
+ { return mProxyInfo && (mProxyInfo->IsHTTP() || mProxyInfo->IsHTTPS()); }
+
+ nsresult PrepareForAuthentication(bool proxyAuth);
+ nsresult GenCredsAndSetEntry(nsIHttpAuthenticator *, bool proxyAuth,
+ const char *scheme, const char *host,
+ int32_t port, const char *dir,
+ const char *realm, const char *challenge,
+ const nsHttpAuthIdentity &ident,
+ nsCOMPtr<nsISupports> &session, char **result);
+ nsresult GetAuthenticator(const char *challenge, nsCString &scheme,
+ nsIHttpAuthenticator **auth);
+ void ParseRealm(const char *challenge, nsACString &realm);
+ void GetIdentityFromURI(uint32_t authFlags, nsHttpAuthIdentity&);
+
+ /**
+ * Following three methods return NS_ERROR_IN_PROGRESS when
+ * nsIAuthPrompt2.asyncPromptAuth method is called. This result indicates
+ * the user's decision will be gathered in a callback and is not an actual
+ * error.
+ */
+ nsresult GetCredentials(const char *challenges, bool proxyAuth,
+ nsAFlatCString &creds);
+ nsresult GetCredentialsForChallenge(const char *challenge,
+ const char *scheme, bool proxyAuth,
+ nsIHttpAuthenticator *auth,
+ nsAFlatCString &creds);
+ nsresult PromptForIdentity(uint32_t level, bool proxyAuth,
+ const char *realm, const char *authType,
+ uint32_t authFlags, nsHttpAuthIdentity &);
+
+ bool ConfirmAuth(const nsString &bundleKey, bool doYesNoPrompt);
+ void SetAuthorizationHeader(nsHttpAuthCache *, nsHttpAtom header,
+ const char *scheme, const char *host,
+ int32_t port, const char *path,
+ nsHttpAuthIdentity &ident);
+ nsresult GetCurrentPath(nsACString &);
+ /**
+ * Return all information needed to build authorization information,
+ * all parameters except proxyAuth are out parameters. proxyAuth specifies
+ * with what authorization we work (WWW or proxy).
+ */
+ nsresult GetAuthorizationMembers(bool proxyAuth, nsCSubstring& scheme,
+ const char*& host, int32_t& port,
+ nsCSubstring& path,
+ nsHttpAuthIdentity*& ident,
+ nsISupports**& continuationState);
+ /**
+ * Method called to resume suspended transaction after we got credentials
+ * from the user. Called from OnAuthAvailable callback or OnAuthCancelled
+ * when credentials for next challenge were obtained synchronously.
+ */
+ nsresult ContinueOnAuthAvailable(const nsCSubstring& creds);
+
+ nsresult DoRedirectChannelToHttps();
+
+ /**
+ * A function that takes care of reading STS headers and enforcing STS
+ * load rules. After a secure channel is erected, STS requires the channel
+ * to be trusted or any STS header data on the channel is ignored.
+ * This is called from ProcessResponse.
+ */
+ nsresult ProcessSTSHeader();
+
+ // Depending on the pref setting, the authentication dialog may be blocked
+ // for all sub-resources, blocked for cross-origin sub-resources, or
+ // always allowed for sub-resources.
+ // For more details look at the bug 647010.
+ bool BlockPrompt();
+
+ // Store credentials to the cache when appropriate aFlags are set.
+ nsresult UpdateCache(nsIHttpAuthenticator *aAuth,
+ const char *aScheme,
+ const char *aHost,
+ int32_t aPort,
+ const char *aDirectory,
+ const char *aRealm,
+ const char *aChallenge,
+ const nsHttpAuthIdentity &aIdent,
+ const char *aCreds,
+ uint32_t aGenerateFlags,
+ nsISupports *aSessionState);
+
+private:
+ nsIHttpAuthenticableChannel *mAuthChannel; // weak ref
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsProxyInfo> mProxyInfo;
+ nsCString mHost;
+ int32_t mPort;
+ bool mUsingSSL;
+ bool mProxyUsingSSL;
+ bool mIsPrivate;
+
+ nsISupports *mProxyAuthContinuationState;
+ nsCString mProxyAuthType;
+ nsISupports *mAuthContinuationState;
+ nsCString mAuthType;
+ nsHttpAuthIdentity mIdent;
+ nsHttpAuthIdentity mProxyIdent;
+
+ // Reference to the prompt waiting in prompt queue. The channel is
+ // responsible to call its cancel method when user in any way cancels
+ // this request.
+ nsCOMPtr<nsICancelable> mAsyncPromptAuthCancelable;
+ // Saved in GetCredentials when prompt is asynchronous, the first challenge
+ // we obtained from the server with 401/407 response, will be processed in
+ // OnAuthAvailable callback.
+ nsCString mCurrentChallenge;
+ // Saved in GetCredentials when prompt is asynchronous, remaning challenges
+ // we have to process when user cancels the auth dialog for the current
+ // challenge.
+ nsCString mRemainingChallenges;
+
+ // True when we need to authenticate to proxy, i.e. when we get 407
+ // response. Used in OnAuthAvailable and OnAuthCancelled callbacks.
+ uint32_t mProxyAuth : 1;
+ uint32_t mTriedProxyAuth : 1;
+ uint32_t mTriedHostAuth : 1;
+ uint32_t mSuppressDefensiveAuth : 1;
+
+ // If a cross-origin sub-resource is being loaded, this flag will be set.
+ // In that case, the prompt text will be different to warn users.
+ uint32_t mCrossOrigin : 1;
+ uint32_t mConnectionBased : 1;
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+
+ // A variable holding the preference settings to whether to open HTTP
+ // authentication credentials dialogs for sub-resources and cross-origin
+ // sub-resources.
+ static uint32_t sAuthAllowPref;
+ nsCOMPtr<nsICancelable> mGenerateCredentialsCancelable;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpChannelAuthProvider_h__
diff --git a/netwerk/protocol/http/nsHttpChunkedDecoder.cpp b/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
new file mode 100644
index 000000000..a532c137d
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+#include <errno.h>
+#include "nsHttpChunkedDecoder.h"
+#include <algorithm>
+#include "plstr.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpChunkedDecoder <public>
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpChunkedDecoder::HandleChunkedContent(char *buf,
+ uint32_t count,
+ uint32_t *contentRead,
+ uint32_t *contentRemaining)
+{
+ LOG(("nsHttpChunkedDecoder::HandleChunkedContent [count=%u]\n", count));
+
+ *contentRead = 0;
+
+ // from RFC2617 section 3.6.1, the chunked transfer coding is defined as:
+ //
+ // Chunked-Body = *chunk
+ // last-chunk
+ // trailer
+ // CRLF
+ // chunk = chunk-size [ chunk-extension ] CRLF
+ // chunk-data CRLF
+ // chunk-size = 1*HEX
+ // last-chunk = 1*("0") [ chunk-extension ] CRLF
+ //
+ // chunk-extension = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
+ // chunk-ext-name = token
+ // chunk-ext-val = token | quoted-string
+ // chunk-data = chunk-size(OCTET)
+ // trailer = *(entity-header CRLF)
+ //
+ // the chunk-size field is a string of hex digits indicating the size of the
+ // chunk. the chunked encoding is ended by any chunk whose size is zero,
+ // followed by the trailer, which is terminated by an empty line.
+
+ while (count) {
+ if (mChunkRemaining) {
+ uint32_t amt = std::min(mChunkRemaining, count);
+
+ count -= amt;
+ mChunkRemaining -= amt;
+
+ *contentRead += amt;
+ buf += amt;
+ }
+ else if (mReachedEOF)
+ break; // done
+ else {
+ uint32_t bytesConsumed = 0;
+
+ nsresult rv = ParseChunkRemaining(buf, count, &bytesConsumed);
+ if (NS_FAILED(rv)) return rv;
+
+ count -= bytesConsumed;
+
+ if (count) {
+ // shift buf by bytesConsumed
+ memmove(buf, buf + bytesConsumed, count);
+ }
+ }
+ }
+
+ *contentRemaining = count;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChunkedDecoder <private>
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpChunkedDecoder::ParseChunkRemaining(char *buf,
+ uint32_t count,
+ uint32_t *bytesConsumed)
+{
+ NS_PRECONDITION(mChunkRemaining == 0, "chunk remaining should be zero");
+ NS_PRECONDITION(count, "unexpected");
+
+ *bytesConsumed = 0;
+
+ char *p = static_cast<char *>(memchr(buf, '\n', count));
+ if (p) {
+ *p = 0;
+ count = p - buf; // new length
+ *bytesConsumed = count + 1; // length + newline
+ if ((p > buf) && (*(p-1) == '\r')) { // eliminate a preceding CR
+ *(p-1) = 0;
+ count--;
+ }
+
+ // make buf point to the full line buffer to parse
+ if (!mLineBuf.IsEmpty()) {
+ mLineBuf.Append(buf, count);
+ buf = (char *) mLineBuf.get();
+ count = mLineBuf.Length();
+ }
+
+ if (mWaitEOF) {
+ if (*buf) {
+ LOG(("got trailer: %s\n", buf));
+ // allocate a header array for the trailers on demand
+ if (!mTrailers) {
+ mTrailers = new nsHttpHeaderArray();
+ }
+ mTrailers->ParseHeaderLine(nsDependentCSubstring(buf, count));
+ }
+ else {
+ mWaitEOF = false;
+ mReachedEOF = true;
+ LOG(("reached end of chunked-body\n"));
+ }
+ }
+ else if (*buf) {
+ char *endptr;
+ unsigned long parsedval; // could be 64 bit, could be 32
+
+ // ignore any chunk-extensions
+ if ((p = PL_strchr(buf, ';')) != nullptr)
+ *p = 0;
+
+ // mChunkRemaining is an uint32_t!
+ parsedval = strtoul(buf, &endptr, 16);
+ mChunkRemaining = (uint32_t) parsedval;
+
+ if ((endptr == buf) ||
+ ((errno == ERANGE) && (parsedval == ULONG_MAX)) ||
+ (parsedval != mChunkRemaining) ) {
+ LOG(("failed parsing hex on string [%s]\n", buf));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // we've discovered the last chunk
+ if (mChunkRemaining == 0)
+ mWaitEOF = true;
+ }
+
+ // ensure that the line buffer is clear
+ mLineBuf.Truncate();
+ }
+ else {
+ // save the partial line; wait for more data
+ *bytesConsumed = count;
+ // ignore a trailing CR
+ if (buf[count-1] == '\r')
+ count--;
+ mLineBuf.Append(buf, count);
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpChunkedDecoder.h b/netwerk/protocol/http/nsHttpChunkedDecoder.h
new file mode 100644
index 000000000..64c8fbf64
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpChunkedDecoder_h__
+#define nsHttpChunkedDecoder_h__
+
+#include "nsError.h"
+#include "nsString.h"
+#include "nsHttpHeaderArray.h"
+
+namespace mozilla { namespace net {
+
+class nsHttpChunkedDecoder
+{
+public:
+ nsHttpChunkedDecoder() : mTrailers(nullptr)
+ , mChunkRemaining(0)
+ , mReachedEOF(false)
+ , mWaitEOF(false) {}
+ ~nsHttpChunkedDecoder() { delete mTrailers; }
+
+ bool ReachedEOF() { return mReachedEOF; }
+
+ // called by the transaction to handle chunked content.
+ nsresult HandleChunkedContent(char *buf,
+ uint32_t count,
+ uint32_t *contentRead,
+ uint32_t *contentRemaining);
+
+ nsHttpHeaderArray *Trailers() { return mTrailers; }
+
+ nsHttpHeaderArray *TakeTrailers() { nsHttpHeaderArray *h = mTrailers;
+ mTrailers = nullptr;
+ return h; }
+
+ uint32_t GetChunkRemaining() { return mChunkRemaining; }
+
+private:
+ nsresult ParseChunkRemaining(char *buf,
+ uint32_t count,
+ uint32_t *countRead);
+
+private:
+ nsHttpHeaderArray *mTrailers;
+ uint32_t mChunkRemaining;
+ nsCString mLineBuf; // may hold a partial line
+ bool mReachedEOF;
+ bool mWaitEOF;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp
new file mode 100644
index 000000000..916d1249c
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -0,0 +1,2319 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#define TLS_EARLY_DATA_NOT_AVAILABLE 0
+#define TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED 1
+#define TLS_EARLY_DATA_AVAILABLE_AND_USED 2
+
+#include "ASpdySession.h"
+#include "mozilla/ChaosMode.h"
+#include "mozilla/Telemetry.h"
+#include "nsHttpConnection.h"
+#include "nsHttpHandler.h"
+#include "nsHttpPipeline.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+#include "nsIOService.h"
+#include "nsISocketTransport.h"
+#include "nsSocketTransportService2.h"
+#include "nsISSLSocketControl.h"
+#include "nsISupportsPriority.h"
+#include "nsPreloadedStream.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransport2.h"
+#include "nsStringStream.h"
+#include "sslt.h"
+#include "TunnelUtils.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection <public>
+//-----------------------------------------------------------------------------
+
+nsHttpConnection::nsHttpConnection()
+ : mTransaction(nullptr)
+ , mHttpHandler(gHttpHandler)
+ , mCallbacksLock("nsHttpConnection::mCallbacksLock")
+ , mConsiderReusedAfterInterval(0)
+ , mConsiderReusedAfterEpoch(0)
+ , mCurrentBytesRead(0)
+ , mMaxBytesRead(0)
+ , mTotalBytesRead(0)
+ , mTotalBytesWritten(0)
+ , mContentBytesWritten(0)
+ , mConnectedTransport(false)
+ , mKeepAlive(true) // assume to keep-alive by default
+ , mKeepAliveMask(true)
+ , mDontReuse(false)
+ , mSupportsPipelining(false) // assume low-grade server
+ , mIsReused(false)
+ , mCompletedProxyConnect(false)
+ , mLastTransactionExpectedNoContent(false)
+ , mIdleMonitoring(false)
+ , mProxyConnectInProgress(false)
+ , mExperienced(false)
+ , mInSpdyTunnel(false)
+ , mForcePlainText(false)
+ , mTrafficStamp(false)
+ , mHttp1xTransactionCount(0)
+ , mRemainingConnectionUses(0xffffffff)
+ , mClassification(nsAHttpTransaction::CLASS_GENERAL)
+ , mNPNComplete(false)
+ , mSetupSSLCalled(false)
+ , mUsingSpdyVersion(0)
+ , mPriority(nsISupportsPriority::PRIORITY_NORMAL)
+ , mReportedSpdy(false)
+ , mEverUsedSpdy(false)
+ , mLastHttpResponseVersion(NS_HTTP_VERSION_1_1)
+ , mTransactionCaps(0)
+ , mResponseTimeoutEnabled(false)
+ , mTCPKeepaliveConfig(kTCPKeepaliveDisabled)
+ , mForceSendPending(false)
+ , m0RTTChecked(false)
+ , mWaitingFor0RTTResponse(false)
+ , mContentBytesWritten0RTT(0)
+ , mEarlyDataNegotiated(false)
+{
+ LOG(("Creating nsHttpConnection @%p\n", this));
+
+ // the default timeout is for when this connection has not yet processed a
+ // transaction
+ static const PRIntervalTime k5Sec = PR_SecondsToInterval(5);
+ mIdleTimeout =
+ (k5Sec < gHttpHandler->IdleTimeout()) ? k5Sec : gHttpHandler->IdleTimeout();
+}
+
+nsHttpConnection::~nsHttpConnection()
+{
+ LOG(("Destroying nsHttpConnection @%p\n", this));
+
+ if (!mEverUsedSpdy) {
+ LOG(("nsHttpConnection %p performed %d HTTP/1.x transactions\n",
+ this, mHttp1xTransactionCount));
+ Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_CONN,
+ mHttp1xTransactionCount);
+ }
+
+ if (mTotalBytesRead) {
+ uint32_t totalKBRead = static_cast<uint32_t>(mTotalBytesRead >> 10);
+ LOG(("nsHttpConnection %p read %dkb on connection spdy=%d\n",
+ this, totalKBRead, mEverUsedSpdy));
+ Telemetry::Accumulate(mEverUsedSpdy ?
+ Telemetry::SPDY_KBREAD_PER_CONN :
+ Telemetry::HTTP_KBREAD_PER_CONN,
+ totalKBRead);
+ }
+ if (mForceSendTimer) {
+ mForceSendTimer->Cancel();
+ mForceSendTimer = nullptr;
+ }
+}
+
+nsresult
+nsHttpConnection::Init(nsHttpConnectionInfo *info,
+ uint16_t maxHangTime,
+ nsISocketTransport *transport,
+ nsIAsyncInputStream *instream,
+ nsIAsyncOutputStream *outstream,
+ bool connectedTransport,
+ nsIInterfaceRequestor *callbacks,
+ PRIntervalTime rtt)
+{
+ LOG(("nsHttpConnection::Init this=%p", this));
+ NS_ENSURE_ARG_POINTER(info);
+ NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
+
+ mConnectedTransport = connectedTransport;
+ mConnInfo = info;
+ mLastWriteTime = mLastReadTime = PR_IntervalNow();
+ mSupportsPipelining =
+ gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo);
+ mRtt = rtt;
+ mMaxHangTime = PR_SecondsToInterval(maxHangTime);
+
+ mSocketTransport = transport;
+ mSocketIn = instream;
+ mSocketOut = outstream;
+
+ // See explanation for non-strictness of this operation in SetSecurityCallbacks.
+ mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(callbacks, false);
+
+ mSocketTransport->SetEventSink(this, nullptr);
+ mSocketTransport->SetSecurityCallbacks(this);
+
+ return NS_OK;
+}
+
+void
+nsHttpConnection::StartSpdy(uint8_t spdyVersion)
+{
+ LOG(("nsHttpConnection::StartSpdy [this=%p]\n", this));
+
+ MOZ_ASSERT(!mSpdySession);
+
+ mUsingSpdyVersion = spdyVersion;
+ mEverUsedSpdy = true;
+ mSpdySession = ASpdySession::NewSpdySession(spdyVersion, mSocketTransport);
+
+ if (!mReportedSpdy) {
+ mReportedSpdy = true;
+ gHttpHandler->ConnMgr()->ReportSpdyConnection(this, true);
+ }
+
+ // Setting the connection as reused allows some transactions that fail
+ // with NS_ERROR_NET_RESET to be restarted and SPDY uses that code
+ // to handle clean rejections (such as those that arrived after
+ // a server goaway was generated).
+ mIsReused = true;
+
+ // If mTransaction is a pipeline object it might represent
+ // several requests. If so, we need to unpack that and
+ // pack them all into a new spdy session.
+
+ nsTArray<RefPtr<nsAHttpTransaction> > list;
+ nsresult rv = mTransaction->TakeSubTransactions(list);
+
+ if (rv == NS_ERROR_ALREADY_OPENED) {
+ // Has the interface for TakeSubTransactions() changed?
+ LOG(("TakeSubTransactions somehow called after "
+ "nsAHttpTransaction began processing\n"));
+ MOZ_ASSERT(false,
+ "TakeSubTransactions somehow called after "
+ "nsAHttpTransaction began processing");
+ mTransaction->Close(NS_ERROR_ABORT);
+ return;
+ }
+
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
+ // Has the interface for TakeSubTransactions() changed?
+ LOG(("unexpected rv from nnsAHttpTransaction::TakeSubTransactions()"));
+ MOZ_ASSERT(false,
+ "unexpected result from "
+ "nsAHttpTransaction::TakeSubTransactions()");
+ mTransaction->Close(NS_ERROR_ABORT);
+ return;
+ }
+
+ if (NeedSpdyTunnel()) {
+ LOG3(("nsHttpConnection::StartSpdy %p Connecting To a HTTP/2 "
+ "Proxy and Need Connect", this));
+ MOZ_ASSERT(mProxyConnectStream);
+
+ mProxyConnectStream = nullptr;
+ mCompletedProxyConnect = true;
+ mProxyConnectInProgress = false;
+ }
+
+ bool spdyProxy = mConnInfo->UsingHttpsProxy() && !mTLSFilter;
+ if (spdyProxy) {
+ RefPtr<nsHttpConnectionInfo> wildCardProxyCi;
+ mConnInfo->CreateWildCard(getter_AddRefs(wildCardProxyCi));
+ gHttpHandler->ConnMgr()->MoveToWildCardConnEntry(mConnInfo,
+ wildCardProxyCi, this);
+ mConnInfo = wildCardProxyCi;
+ }
+
+ if (NS_FAILED(rv)) { // includes NS_ERROR_NOT_IMPLEMENTED
+ MOZ_ASSERT(list.IsEmpty(), "sub transaction list not empty");
+
+ // This is ok - treat mTransaction as a single real request.
+ // Wrap the old http transaction into the new spdy session
+ // as the first stream.
+ LOG(("nsHttpConnection::StartSpdy moves single transaction %p "
+ "into SpdySession %p\n", mTransaction.get(), mSpdySession.get()));
+ rv = AddTransaction(mTransaction, mPriority);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ } else {
+ int32_t count = list.Length();
+
+ LOG(("nsHttpConnection::StartSpdy moving transaction list len=%d "
+ "into SpdySession %p\n", count, mSpdySession.get()));
+
+ if (!count) {
+ mTransaction->Close(NS_ERROR_ABORT);
+ return;
+ }
+
+ for (int32_t index = 0; index < count; ++index) {
+ rv = AddTransaction(list[index], mPriority);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+ }
+
+ // Disable TCP Keepalives - use SPDY ping instead.
+ rv = DisableTCPKeepalives();
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpConnection::StartSpdy [%p] DisableTCPKeepalives failed "
+ "rv[0x%x]", this, rv));
+ }
+
+ mSupportsPipelining = false; // don't use http/1 pipelines with spdy
+ mIdleTimeout = gHttpHandler->SpdyTimeout();
+
+ if (!mTLSFilter) {
+ mTransaction = mSpdySession;
+ } else {
+ mTLSFilter->SetProxiedTransaction(mSpdySession);
+ }
+ if (mDontReuse) {
+ mSpdySession->DontReuse();
+ }
+}
+
+bool
+nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
+ uint32_t &aOut0RTTBytesWritten)
+{
+ // If for some reason the components to check on NPN aren't available,
+ // this function will just return true to continue on and disable SPDY
+
+ aOut0RTTWriteHandshakeValue = NS_OK;
+ aOut0RTTBytesWritten = 0;
+
+ MOZ_ASSERT(mSocketTransport);
+ if (!mSocketTransport) {
+ // this cannot happen
+ mNPNComplete = true;
+ return true;
+ }
+
+ if (mNPNComplete) {
+ return true;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> securityInfo;
+ nsCOMPtr<nsISSLSocketControl> ssl;
+ nsAutoCString negotiatedNPN;
+
+ GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (!securityInfo) {
+ goto npnComplete;
+ }
+
+ ssl = do_QueryInterface(securityInfo, &rv);
+ if (NS_FAILED(rv))
+ goto npnComplete;
+
+ rv = ssl->GetNegotiatedNPN(negotiatedNPN);
+ if (!m0RTTChecked && (rv == NS_ERROR_NOT_CONNECTED) &&
+ !mConnInfo->UsingProxy()) {
+ // There is no ALPN info (yet!). We need to consider doing 0RTT. We
+ // will do so if there is ALPN information from a previous session
+ // (AlpnEarlySelection), we are using HTTP/1, and the request data can
+ // be safely retried.
+ m0RTTChecked = true;
+ nsAutoCString earlyNegotiatedNPN;
+ nsresult rvEarlyAlpn = ssl->GetAlpnEarlySelection(earlyNegotiatedNPN);
+ if (NS_FAILED(rvEarlyAlpn)) {
+ // if ssl->DriveHandshake() has never been called the value
+ // for AlpnEarlySelection is still not set. So call it here and
+ // check again.
+ LOG(("nsHttpConnection::EnsureNPNComplete %p - "
+ "early selected alpn not available, we will try one more time.",
+ this));
+ // Let's do DriveHandshake again.
+ rv = ssl->DriveHandshake();
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ goto npnComplete;
+ }
+
+ // Check NegotiatedNPN first.
+ rv = ssl->GetNegotiatedNPN(negotiatedNPN);
+ if (rv == NS_ERROR_NOT_CONNECTED) {
+ rvEarlyAlpn = ssl->GetAlpnEarlySelection(earlyNegotiatedNPN);
+ }
+ }
+
+ if (NS_FAILED(rvEarlyAlpn)) {
+ LOG(("nsHttpConnection::EnsureNPNComplete %p - "
+ "early selected alpn not available", this));
+ mEarlyDataNegotiated = false;
+ } else {
+ LOG(("nsHttpConnection::EnsureNPNComplete %p -"
+ "early selected alpn: %s", this, earlyNegotiatedNPN.get()));
+ uint32_t infoIndex;
+ const SpdyInformation *info = gHttpHandler->SpdyInfo();
+ // We are doing 0RTT only with Http/1 right now!
+ if (NS_FAILED(info->GetNPNIndex(earlyNegotiatedNPN, &infoIndex))) {
+ // Check if early-data is allowed for this transaction.
+ if (mTransaction->Do0RTT()) {
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - We "
+ "can do 0RTT!", this));
+ mWaitingFor0RTTResponse = true;
+ }
+ mEarlyDataNegotiated = true;
+ }
+ }
+ }
+
+ if (rv == NS_ERROR_NOT_CONNECTED) {
+ if (mWaitingFor0RTTResponse) {
+ aOut0RTTWriteHandshakeValue = mTransaction->ReadSegments(this,
+ nsIOService::gDefaultSegmentSize, &aOut0RTTBytesWritten);
+ if (NS_FAILED(aOut0RTTWriteHandshakeValue) &&
+ aOut0RTTWriteHandshakeValue != NS_BASE_STREAM_WOULD_BLOCK) {
+ goto npnComplete;
+ }
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - written %d "
+ "bytes during 0RTT", this, aOut0RTTBytesWritten));
+ mContentBytesWritten0RTT += aOut0RTTBytesWritten;
+ }
+
+ rv = ssl->DriveHandshake();
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ goto npnComplete;
+ }
+
+ return false;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("nsHttpConnection::EnsureNPNComplete %p [%s] negotiated to '%s'%s\n",
+ this, mConnInfo->HashKey().get(), negotiatedNPN.get(),
+ mTLSFilter ? " [Double Tunnel]" : ""));
+
+ bool ealyDataAccepted = false;
+ if (mWaitingFor0RTTResponse) {
+ // Check if early data has been accepted.
+ rv = ssl->GetEarlyDataAccepted(&ealyDataAccepted);
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - early data "
+ "that was sent during 0RTT %s been accepted.",
+ this, ealyDataAccepted ? "has" : "has not"));
+
+ if (NS_FAILED(rv) ||
+ NS_FAILED(mTransaction->Finish0RTT(!ealyDataAccepted))) {
+ mTransaction->Close(NS_ERROR_NET_RESET);
+ goto npnComplete;
+ }
+ }
+
+ int16_t tlsVersion;
+ ssl->GetSSLVersionUsed(&tlsVersion);
+ // Send the 0RTT telemetry only for tls1.3
+ if (tlsVersion > nsISSLSocketControl::TLS_VERSION_1_2) {
+ Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_NEGOTIATED,
+ (!mEarlyDataNegotiated) ? TLS_EARLY_DATA_NOT_AVAILABLE
+ : ((mWaitingFor0RTTResponse) ? TLS_EARLY_DATA_AVAILABLE_AND_USED
+ : TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED));
+ if (mWaitingFor0RTTResponse) {
+ Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_ACCEPTED,
+ ealyDataAccepted);
+ }
+ if (ealyDataAccepted) {
+ Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_BYTES_WRITTEN,
+ mContentBytesWritten0RTT);
+ }
+ }
+ mWaitingFor0RTTResponse = false;
+
+ if (!ealyDataAccepted) {
+ uint32_t infoIndex;
+ const SpdyInformation *info = gHttpHandler->SpdyInfo();
+ if (NS_SUCCEEDED(info->GetNPNIndex(negotiatedNPN, &infoIndex))) {
+ StartSpdy(info->Version[infoIndex]);
+ }
+ } else {
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - %d bytes "
+ "has been sent during 0RTT.", this, mContentBytesWritten0RTT));
+ mContentBytesWritten = mContentBytesWritten0RTT;
+ }
+
+ Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy());
+ }
+
+npnComplete:
+ LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true"));
+ mNPNComplete = true;
+ if (mWaitingFor0RTTResponse) {
+ mWaitingFor0RTTResponse = false;
+ if (NS_FAILED(mTransaction->Finish0RTT(true))) {
+ mTransaction->Close(NS_ERROR_NET_RESET);
+ }
+ mContentBytesWritten0RTT = 0;
+ }
+ return true;
+}
+
+void
+nsHttpConnection::OnTunnelNudged(TLSFilterTransaction *trans)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnection::OnTunnelNudged %p\n", this));
+ if (trans != mTLSFilter) {
+ return;
+ }
+ LOG(("nsHttpConnection::OnTunnelNudged %p Calling OnSocketWritable\n", this));
+ OnSocketWritable();
+}
+
+// called on the socket thread
+nsresult
+nsHttpConnection::Activate(nsAHttpTransaction *trans, uint32_t caps, int32_t pri)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnection::Activate [this=%p trans=%p caps=%x]\n",
+ this, trans, caps));
+
+ if (!trans->IsNullTransaction())
+ mExperienced = true;
+
+ mTransactionCaps = caps;
+ mPriority = pri;
+ if (mTransaction && mUsingSpdyVersion) {
+ return AddTransaction(trans, pri);
+ }
+
+ NS_ENSURE_ARG_POINTER(trans);
+ NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS);
+
+ // reset the read timers to wash away any idle time
+ mLastWriteTime = mLastReadTime = PR_IntervalNow();
+
+ // Connection failures are Activated() just like regular transacions.
+ // If we don't have a confirmation of a connected socket then test it
+ // with a write() to get relevant error code.
+ if (!mConnectedTransport) {
+ uint32_t count;
+ mSocketOutCondition = NS_ERROR_FAILURE;
+ if (mSocketOut) {
+ mSocketOutCondition = mSocketOut->Write("", 0, &count);
+ }
+ if (NS_FAILED(mSocketOutCondition) &&
+ mSocketOutCondition != NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG(("nsHttpConnection::Activate [this=%p] Bad Socket %x\n",
+ this, mSocketOutCondition));
+ mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mTransaction = trans;
+ CloseTransaction(mTransaction, mSocketOutCondition);
+ return mSocketOutCondition;
+ }
+ }
+
+ // Update security callbacks
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ trans->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ SetSecurityCallbacks(callbacks);
+ SetupSSL();
+
+ // take ownership of the transaction
+ mTransaction = trans;
+
+ MOZ_ASSERT(!mIdleMonitoring, "Activating a connection with an Idle Monitor");
+ mIdleMonitoring = false;
+
+ // set mKeepAlive according to what will be requested
+ mKeepAliveMask = mKeepAlive = (caps & NS_HTTP_ALLOW_KEEPALIVE);
+
+ // need to handle HTTP CONNECT tunnels if this is the first time if
+ // we are tunneling through a proxy
+ nsresult rv = NS_OK;
+ if (mTransaction->ConnectionInfo()->UsingConnect() && !mCompletedProxyConnect) {
+ rv = SetupProxyConnect();
+ if (NS_FAILED(rv))
+ goto failed_activation;
+ mProxyConnectInProgress = true;
+ }
+
+ // Clear the per activation counter
+ mCurrentBytesRead = 0;
+
+ // The overflow state is not needed between activations
+ mInputOverflow = nullptr;
+
+ mResponseTimeoutEnabled = gHttpHandler->ResponseTimeoutEnabled() &&
+ mTransaction->ResponseTimeout() > 0 &&
+ mTransaction->ResponseTimeoutEnabled();
+
+ rv = StartShortLivedTCPKeepalives();
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpConnection::Activate [%p] "
+ "StartShortLivedTCPKeepalives failed rv[0x%x]",
+ this, rv));
+ }
+
+ if (mTLSFilter) {
+ mTLSFilter->SetProxiedTransaction(trans);
+ mTransaction = mTLSFilter;
+ }
+
+ rv = OnOutputStreamReady(mSocketOut);
+
+failed_activation:
+ if (NS_FAILED(rv)) {
+ mTransaction = nullptr;
+ }
+
+ return rv;
+}
+
+void
+nsHttpConnection::SetupSSL()
+{
+ LOG(("nsHttpConnection::SetupSSL %p caps=0x%X %s\n",
+ this, mTransactionCaps, mConnInfo->HashKey().get()));
+
+ if (mSetupSSLCalled) // do only once
+ return;
+ mSetupSSLCalled = true;
+
+ if (mNPNComplete)
+ return;
+
+ // we flip this back to false if SetNPNList succeeds at the end
+ // of this function
+ mNPNComplete = true;
+
+ if (!mConnInfo->FirstHopSSL() || mForcePlainText) {
+ return;
+ }
+
+ // if we are connected to the proxy with TLS, start the TLS
+ // flow immediately without waiting for a CONNECT sequence.
+ if (mInSpdyTunnel) {
+ InitSSLParams(false, true);
+ } else {
+ bool usingHttpsProxy = mConnInfo->UsingHttpsProxy();
+ InitSSLParams(usingHttpsProxy, usingHttpsProxy);
+ }
+}
+
+// The naming of NPN is historical - this function creates the basic
+// offer list for both NPN and ALPN. ALPN validation callbacks are made
+// now before the handshake is complete, and NPN validation callbacks
+// are made during the handshake.
+nsresult
+nsHttpConnection::SetupNPNList(nsISSLSocketControl *ssl, uint32_t caps)
+{
+ nsTArray<nsCString> protocolArray;
+
+ nsCString npnToken = mConnInfo->GetNPNToken();
+ if (npnToken.IsEmpty()) {
+ // The first protocol is used as the fallback if none of the
+ // protocols supported overlap with the server's list.
+ // When using ALPN the advertised preferences are protocolArray indicies
+ // {1, .., N, 0} in decreasing order.
+ // For NPN, In the case of overlap, matching priority is driven by
+ // the order of the server's advertisement - with index 0 used when
+ // there is no match.
+ protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1"));
+
+ if (gHttpHandler->IsSpdyEnabled() &&
+ !(caps & NS_HTTP_DISALLOW_SPDY)) {
+ LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
+ const SpdyInformation *info = gHttpHandler->SpdyInfo();
+ for (uint32_t index = SpdyInformation::kCount; index > 0; --index) {
+ if (info->ProtocolEnabled(index - 1) &&
+ info->ALPNCallbacks[index - 1](ssl)) {
+ protocolArray.AppendElement(info->VersionString[index - 1]);
+ }
+ }
+ }
+ } else {
+ LOG(("nsHttpConnection::SetupSSL limiting NPN selection to %s",
+ npnToken.get()));
+ protocolArray.AppendElement(npnToken);
+ }
+
+ nsresult rv = ssl->SetNPNList(protocolArray);
+ LOG(("nsHttpConnection::SetupNPNList %p %x\n",this, rv));
+ return rv;
+}
+
+nsresult
+nsHttpConnection::AddTransaction(nsAHttpTransaction *httpTransaction,
+ int32_t priority)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mSpdySession && mUsingSpdyVersion,
+ "AddTransaction to live http connection without spdy");
+
+ // If this is a wild card nshttpconnection (i.e. a spdy proxy) then
+ // it is important to start the stream using the specific connection
+ // info of the transaction to ensure it is routed on the right tunnel
+
+ nsHttpConnectionInfo *transCI = httpTransaction->ConnectionInfo();
+
+ bool needTunnel = transCI->UsingHttpsProxy();
+ needTunnel = needTunnel && !mTLSFilter;
+ needTunnel = needTunnel && transCI->UsingConnect();
+ needTunnel = needTunnel && httpTransaction->QueryHttpTransaction();
+
+ LOG(("nsHttpConnection::AddTransaction for SPDY%s",
+ needTunnel ? " over tunnel" : ""));
+
+ if (!mSpdySession->AddStream(httpTransaction, priority,
+ needTunnel, mCallbacks)) {
+ MOZ_ASSERT(false); // this cannot happen!
+ httpTransaction->Close(NS_ERROR_ABORT);
+ return NS_ERROR_FAILURE;
+ }
+
+ ResumeSend();
+ return NS_OK;
+}
+
+void
+nsHttpConnection::Close(nsresult reason, bool aIsShutdown)
+{
+ LOG(("nsHttpConnection::Close [this=%p reason=%x]\n", this, reason));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // Ensure TCP keepalive timer is stopped.
+ if (mTCPKeepaliveTransitionTimer) {
+ mTCPKeepaliveTransitionTimer->Cancel();
+ mTCPKeepaliveTransitionTimer = nullptr;
+ }
+ if (mForceSendTimer) {
+ mForceSendTimer->Cancel();
+ mForceSendTimer = nullptr;
+ }
+
+ if (NS_FAILED(reason)) {
+ if (mIdleMonitoring)
+ EndIdleMonitoring();
+
+ mTLSFilter = nullptr;
+
+ // The connection and security errors clear out alt-svc mappings
+ // in case any previously validated ones are now invalid
+ if (((reason == NS_ERROR_NET_RESET) ||
+ (NS_ERROR_GET_MODULE(reason) == NS_ERROR_MODULE_SECURITY))
+ && mConnInfo && !(mTransactionCaps & NS_HTTP_ERROR_SOFTLY)) {
+ gHttpHandler->ConnMgr()->ClearHostMapping(mConnInfo);
+ }
+
+ if (mSocketTransport) {
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+
+ // If there are bytes sitting in the input queue then read them
+ // into a junk buffer to avoid generating a tcp rst by closing a
+ // socket with data pending. TLS is a classic case of this where
+ // a Alert record might be superfulous to a clean HTTP/SPDY shutdown.
+ // Never block to do this and limit it to a small amount of data.
+ // During shutdown just be fast!
+ if (mSocketIn && !aIsShutdown) {
+ char buffer[4000];
+ uint32_t count, total = 0;
+ nsresult rv;
+ do {
+ rv = mSocketIn->Read(buffer, 4000, &count);
+ if (NS_SUCCEEDED(rv))
+ total += count;
+ }
+ while (NS_SUCCEEDED(rv) && count > 0 && total < 64000);
+ LOG(("nsHttpConnection::Close drained %d bytes\n", total));
+ }
+
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport->Close(reason);
+ if (mSocketOut)
+ mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+ mKeepAlive = false;
+ }
+}
+
+// called on the socket thread
+nsresult
+nsHttpConnection::InitSSLParams(bool connectingToProxy, bool proxyStartSSL)
+{
+ LOG(("nsHttpConnection::InitSSLParams [this=%p] connectingToProxy=%d\n",
+ this, connectingToProxy));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> securityInfo;
+ GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (!securityInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo, &rv);
+ if (NS_FAILED(rv)){
+ return rv;
+ }
+
+ if (proxyStartSSL) {
+ rv = ssl->ProxyStartSSL();
+ if (NS_FAILED(rv)){
+ return rv;
+ }
+ }
+
+ if (NS_SUCCEEDED(SetupNPNList(ssl, mTransactionCaps))) {
+ LOG(("InitSSLParams Setting up SPDY Negotiation OK"));
+ mNPNComplete = false;
+ }
+
+ return NS_OK;
+}
+
+void
+nsHttpConnection::DontReuse()
+{
+ LOG(("nsHttpConnection::DontReuse %p spdysession=%p\n", this, mSpdySession.get()));
+ mKeepAliveMask = false;
+ mKeepAlive = false;
+ mDontReuse = true;
+ mIdleTimeout = 0;
+ if (mSpdySession)
+ mSpdySession->DontReuse();
+}
+
+// Checked by the Connection Manager before scheduling a pipelined transaction
+bool
+nsHttpConnection::SupportsPipelining()
+{
+ if (mTransaction &&
+ mTransaction->PipelineDepth() >= mRemainingConnectionUses) {
+ LOG(("nsHttpConnection::SupportsPipelining this=%p deny pipeline "
+ "because current depth %d exceeds max remaining uses %d\n",
+ this, mTransaction->PipelineDepth(), mRemainingConnectionUses));
+ return false;
+ }
+ return mSupportsPipelining && IsKeepAlive() && !mDontReuse;
+}
+
+bool
+nsHttpConnection::CanReuse()
+{
+ if (mDontReuse)
+ return false;
+
+ if ((mTransaction ? mTransaction->PipelineDepth() : 0) >=
+ mRemainingConnectionUses) {
+ return false;
+ }
+
+ bool canReuse;
+
+ if (mSpdySession)
+ canReuse = mSpdySession->CanReuse();
+ else
+ canReuse = IsKeepAlive();
+
+ canReuse = canReuse && (IdleTime() < mIdleTimeout) && IsAlive();
+
+ // An idle persistent connection should not have data waiting to be read
+ // before a request is sent. Data here is likely a 408 timeout response
+ // which we would deal with later on through the restart logic, but that
+ // path is more expensive than just closing the socket now.
+
+ uint64_t dataSize;
+ if (canReuse && mSocketIn && !mUsingSpdyVersion && mHttp1xTransactionCount &&
+ NS_SUCCEEDED(mSocketIn->Available(&dataSize)) && dataSize) {
+ LOG(("nsHttpConnection::CanReuse %p %s"
+ "Socket not reusable because read data pending (%llu) on it.\n",
+ this, mConnInfo->Origin(), dataSize));
+ canReuse = false;
+ }
+ return canReuse;
+}
+
+bool
+nsHttpConnection::CanDirectlyActivate()
+{
+ // return true if a new transaction can be addded to ths connection at any
+ // time through Activate(). In practice this means this is a healthy SPDY
+ // connection with room for more concurrent streams.
+
+ return UsingSpdy() && CanReuse() &&
+ mSpdySession && mSpdySession->RoomForMoreStreams();
+}
+
+PRIntervalTime
+nsHttpConnection::IdleTime()
+{
+ return mSpdySession ?
+ mSpdySession->IdleTime() : (PR_IntervalNow() - mLastReadTime);
+}
+
+// returns the number of seconds left before the allowable idle period
+// expires, or 0 if the period has already expied.
+uint32_t
+nsHttpConnection::TimeToLive()
+{
+ if (IdleTime() >= mIdleTimeout)
+ return 0;
+ uint32_t timeToLive = PR_IntervalToSeconds(mIdleTimeout - IdleTime());
+
+ // a positive amount of time can be rounded to 0. Because 0 is used
+ // as the expiration signal, round all values from 0 to 1 up to 1.
+ if (!timeToLive)
+ timeToLive = 1;
+ return timeToLive;
+}
+
+bool
+nsHttpConnection::IsAlive()
+{
+ if (!mSocketTransport || !mConnectedTransport)
+ return false;
+
+ // SocketTransport::IsAlive can run the SSL state machine, so make sure
+ // the NPN options are set before that happens.
+ SetupSSL();
+
+ bool alive;
+ nsresult rv = mSocketTransport->IsAlive(&alive);
+ if (NS_FAILED(rv))
+ alive = false;
+
+//#define TEST_RESTART_LOGIC
+#ifdef TEST_RESTART_LOGIC
+ if (!alive) {
+ LOG(("pretending socket is still alive to test restart logic\n"));
+ alive = true;
+ }
+#endif
+
+ return alive;
+}
+
+bool
+nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead)
+{
+ // SPDY supports infinite parallelism, so no need to pipeline.
+ if (mUsingSpdyVersion)
+ return false;
+
+ // assuming connection is HTTP/1.1 with keep-alive enabled
+ if (mConnInfo->UsingHttpProxy() && !mConnInfo->UsingConnect()) {
+ // XXX check for bad proxy servers...
+ return true;
+ }
+
+ // check for bad origin servers
+ nsAutoCString val;
+ responseHead->GetHeader(nsHttp::Server, val);
+
+ // If there is no server header we will assume it should not be banned
+ // as facebook and some other prominent sites do this
+ if (val.IsEmpty())
+ return true;
+
+ // The blacklist is indexed by the first character. All of these servers are
+ // known to return their identifier as the first thing in the server string,
+ // so we can do a leading match.
+
+ static const char *bad_servers[26][6] = {
+ { nullptr }, { nullptr }, { nullptr }, { nullptr }, // a - d
+ { "EFAServer/", nullptr }, // e
+ { nullptr }, { nullptr }, { nullptr }, { nullptr }, // f - i
+ { nullptr }, { nullptr }, { nullptr }, // j - l
+ { "Microsoft-IIS/4.", "Microsoft-IIS/5.", nullptr }, // m
+ { "Netscape-Enterprise/3.", "Netscape-Enterprise/4.",
+ "Netscape-Enterprise/5.", "Netscape-Enterprise/6.", nullptr }, // n
+ { nullptr }, { nullptr }, { nullptr }, { nullptr }, // o - r
+ { nullptr }, { nullptr }, { nullptr }, { nullptr }, // s - v
+ { "WebLogic 3.", "WebLogic 4.","WebLogic 5.", "WebLogic 6.",
+ "Winstone Servlet Engine v0.", nullptr }, // w
+ { nullptr }, { nullptr }, { nullptr } // x - z
+ };
+
+ int index = val.get()[0] - 'A'; // the whole table begins with capital letters
+ if ((index >= 0) && (index <= 25))
+ {
+ for (int i = 0; bad_servers[index][i] != nullptr; i++) {
+ if (val.Equals(bad_servers[index][i])) {
+ LOG(("looks like this server does not support pipelining"));
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::RedBannedServer, this , 0);
+ return false;
+ }
+ }
+ }
+
+ // ok, let's allow pipelining to this server
+ return true;
+}
+
+//----------------------------------------------------------------------------
+// nsHttpConnection::nsAHttpConnection compatible methods
+//----------------------------------------------------------------------------
+
+nsresult
+nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans,
+ nsHttpRequestHead *requestHead,
+ nsHttpResponseHead *responseHead,
+ bool *reset)
+{
+ LOG(("nsHttpConnection::OnHeadersAvailable [this=%p trans=%p response-head=%p]\n",
+ this, trans, responseHead));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ NS_ENSURE_ARG_POINTER(trans);
+ MOZ_ASSERT(responseHead, "No response head?");
+
+ if (mInSpdyTunnel) {
+ responseHead->SetHeader(nsHttp::X_Firefox_Spdy_Proxy,
+ NS_LITERAL_CSTRING("true"));
+ }
+
+ // we won't change our keep-alive policy unless the server has explicitly
+ // told us to do so.
+
+ // inspect the connection headers for keep-alive info provided the
+ // transaction completed successfully. In the case of a non-sensical close
+ // and keep-alive favor the close out of conservatism.
+
+ bool explicitKeepAlive = false;
+ bool explicitClose = responseHead->HasHeaderValue(nsHttp::Connection, "close") ||
+ responseHead->HasHeaderValue(nsHttp::Proxy_Connection, "close");
+ if (!explicitClose)
+ explicitKeepAlive = responseHead->HasHeaderValue(nsHttp::Connection, "keep-alive") ||
+ responseHead->HasHeaderValue(nsHttp::Proxy_Connection, "keep-alive");
+
+ // deal with 408 Server Timeouts
+ uint16_t responseStatus = responseHead->Status();
+ static const PRIntervalTime k1000ms = PR_MillisecondsToInterval(1000);
+ if (responseStatus == 408) {
+ // If this error could be due to a persistent connection reuse then
+ // we pass an error code of NS_ERROR_NET_RESET to
+ // trigger the transaction 'restart' mechanism. We tell it to reset its
+ // response headers so that it will be ready to receive the new response.
+ if (mIsReused && ((PR_IntervalNow() - mLastWriteTime) < k1000ms)) {
+ Close(NS_ERROR_NET_RESET);
+ *reset = true;
+ return NS_OK;
+ }
+
+ // timeouts that are not caused by persistent connection reuse should
+ // not be retried for browser compatibility reasons. bug 907800. The
+ // server driven close is implicit in the 408.
+ explicitClose = true;
+ explicitKeepAlive = false;
+ }
+
+ // reset to default (the server may have changed since we last checked)
+ mSupportsPipelining = false;
+
+ if ((responseHead->Version() < NS_HTTP_VERSION_1_1) ||
+ (requestHead->Version() < NS_HTTP_VERSION_1_1)) {
+ // HTTP/1.0 connections are by default NOT persistent
+ if (explicitKeepAlive)
+ mKeepAlive = true;
+ else
+ mKeepAlive = false;
+
+ // We need at least version 1.1 to use pipelines
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::RedVersionTooLow, this, 0);
+ }
+ else {
+ // HTTP/1.1 connections are by default persistent
+ if (explicitClose) {
+ mKeepAlive = false;
+
+ // persistent connections are required for pipelining to work - if
+ // this close was not pre-announced then generate the negative
+ // BadExplicitClose feedback
+ if (mRemainingConnectionUses > 1)
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::BadExplicitClose, this, 0);
+ }
+ else {
+ mKeepAlive = true;
+
+ // Do not support pipelining when we are establishing
+ // an SSL tunnel though an HTTP proxy. Pipelining support
+ // determination must be based on comunication with the
+ // target server in this case. See bug 422016 for futher
+ // details.
+ if (!mProxyConnectStream)
+ mSupportsPipelining = SupportsPipelining(responseHead);
+ }
+ }
+ mKeepAliveMask = mKeepAlive;
+
+ // Update the pipelining status in the connection info object
+ // and also read it back. It is possible the ci status is
+ // locked to false if pipelining has been banned on this ci due to
+ // some kind of observed flaky behavior
+ if (mSupportsPipelining) {
+ // report the pipelining-compatible header to the connection manager
+ // as positive feedback. This will undo 1 penalty point the host
+ // may have accumulated in the past.
+
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::NeutralExpectedOK, this, 0);
+
+ mSupportsPipelining =
+ gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo);
+ }
+
+ // If this connection is reserved for revalidations and we are
+ // receiving a document that failed revalidation then switch the
+ // classification to general to avoid pipelining more revalidations behind
+ // it.
+ if (mClassification == nsAHttpTransaction::CLASS_REVALIDATION &&
+ responseStatus != 304) {
+ mClassification = nsAHttpTransaction::CLASS_GENERAL;
+ }
+
+ // if this connection is persistent, then the server may send a "Keep-Alive"
+ // header specifying the maximum number of times the connection can be
+ // reused as well as the maximum amount of time the connection can be idle
+ // before the server will close it. we ignore the max reuse count, because
+ // a "keep-alive" connection is by definition capable of being reused, and
+ // we only care about being able to reuse it once. if a timeout is not
+ // specified then we use our advertized timeout value.
+ bool foundKeepAliveMax = false;
+ if (mKeepAlive) {
+ nsAutoCString keepAlive;
+ responseHead->GetHeader(nsHttp::Keep_Alive, keepAlive);
+
+ if (!mUsingSpdyVersion) {
+ const char *cp = PL_strcasestr(keepAlive.get(), "timeout=");
+ if (cp)
+ mIdleTimeout = PR_SecondsToInterval((uint32_t) atoi(cp + 8));
+ else
+ mIdleTimeout = gHttpHandler->IdleTimeout();
+
+ cp = PL_strcasestr(keepAlive.get(), "max=");
+ if (cp) {
+ int maxUses = atoi(cp + 4);
+ if (maxUses > 0) {
+ foundKeepAliveMax = true;
+ mRemainingConnectionUses = static_cast<uint32_t>(maxUses);
+ }
+ }
+ }
+ else {
+ mIdleTimeout = gHttpHandler->SpdyTimeout();
+ }
+
+ LOG(("Connection can be reused [this=%p idle-timeout=%usec]\n",
+ this, PR_IntervalToSeconds(mIdleTimeout)));
+ }
+
+ if (!foundKeepAliveMax && mRemainingConnectionUses && !mUsingSpdyVersion)
+ --mRemainingConnectionUses;
+
+ // If we're doing a proxy connect, we need to check whether or not
+ // it was successful. If so, we have to reset the transaction and step-up
+ // the socket connection if using SSL. Finally, we have to wake up the
+ // socket write request.
+ if (mProxyConnectStream) {
+ MOZ_ASSERT(!mUsingSpdyVersion,
+ "SPDY NPN Complete while using proxy connect stream");
+ mProxyConnectStream = nullptr;
+ bool isHttps =
+ mTransaction ? mTransaction->ConnectionInfo()->EndToEndSSL() :
+ mConnInfo->EndToEndSSL();
+
+ if (responseStatus == 200) {
+ LOG(("proxy CONNECT succeeded! endtoendssl=%d\n", isHttps));
+ *reset = true;
+ nsresult rv;
+ if (isHttps) {
+ if (mConnInfo->UsingHttpsProxy()) {
+ LOG(("%p new TLSFilterTransaction %s %d\n",
+ this, mConnInfo->Origin(), mConnInfo->OriginPort()));
+ SetupSecondaryTLS();
+ }
+
+ rv = InitSSLParams(false, true);
+ LOG(("InitSSLParams [rv=%x]\n", rv));
+ }
+ mCompletedProxyConnect = true;
+ mProxyConnectInProgress = false;
+ rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ // XXX what if this fails -- need to handle this error
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "mSocketOut->AsyncWait failed");
+ }
+ else {
+ LOG(("proxy CONNECT failed! endtoendssl=%d\n", isHttps));
+ mTransaction->SetProxyConnectFailed();
+ }
+ }
+
+ nsAutoCString upgradeReq;
+ bool hasUpgradeReq = NS_SUCCEEDED(requestHead->GetHeader(nsHttp::Upgrade,
+ upgradeReq));
+ // Don't use persistent connection for Upgrade unless there's an auth failure:
+ // some proxies expect to see auth response on persistent connection.
+ if (hasUpgradeReq && responseStatus != 401 && responseStatus != 407) {
+ LOG(("HTTP Upgrade in play - disable keepalive\n"));
+ DontReuse();
+ }
+
+ if (responseStatus == 101) {
+ nsAutoCString upgradeResp;
+ bool hasUpgradeResp = NS_SUCCEEDED(responseHead->GetHeader(
+ nsHttp::Upgrade,
+ upgradeResp));
+ if (!hasUpgradeReq || !hasUpgradeResp ||
+ !nsHttp::FindToken(upgradeResp.get(), upgradeReq.get(),
+ HTTP_HEADER_VALUE_SEPS)) {
+ LOG(("HTTP 101 Upgrade header mismatch req = %s, resp = %s\n",
+ upgradeReq.get(),
+ !upgradeResp.IsEmpty() ? upgradeResp.get() :
+ "RESPONSE's nsHttp::Upgrade is empty"));
+ Close(NS_ERROR_ABORT);
+ }
+ else {
+ LOG(("HTTP Upgrade Response to %s\n", upgradeResp.get()));
+ }
+ }
+
+ mLastHttpResponseVersion = responseHead->Version();
+
+ return NS_OK;
+}
+
+bool
+nsHttpConnection::IsReused()
+{
+ if (mIsReused)
+ return true;
+ if (!mConsiderReusedAfterInterval)
+ return false;
+
+ // ReusedAfter allows a socket to be consider reused only after a certain
+ // interval of time has passed
+ return (PR_IntervalNow() - mConsiderReusedAfterEpoch) >=
+ mConsiderReusedAfterInterval;
+}
+
+void
+nsHttpConnection::SetIsReusedAfter(uint32_t afterMilliseconds)
+{
+ mConsiderReusedAfterEpoch = PR_IntervalNow();
+ mConsiderReusedAfterInterval = PR_MillisecondsToInterval(afterMilliseconds);
+}
+
+nsresult
+nsHttpConnection::TakeTransport(nsISocketTransport **aTransport,
+ nsIAsyncInputStream **aInputStream,
+ nsIAsyncOutputStream **aOutputStream)
+{
+ if (mUsingSpdyVersion)
+ return NS_ERROR_FAILURE;
+ if (mTransaction && !mTransaction->IsDone())
+ return NS_ERROR_IN_PROGRESS;
+ if (!(mSocketTransport && mSocketIn && mSocketOut))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (mInputOverflow)
+ mSocketIn = mInputOverflow.forget();
+
+ // Change TCP Keepalive frequency to long-lived if currently short-lived.
+ if (mTCPKeepaliveConfig == kTCPKeepaliveShortLivedConfig) {
+ if (mTCPKeepaliveTransitionTimer) {
+ mTCPKeepaliveTransitionTimer->Cancel();
+ mTCPKeepaliveTransitionTimer = nullptr;
+ }
+ nsresult rv = StartLongLivedTCPKeepalives();
+ LOG(("nsHttpConnection::TakeTransport [%p] calling "
+ "StartLongLivedTCPKeepalives", this));
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpConnection::TakeTransport [%p] "
+ "StartLongLivedTCPKeepalives failed rv[0x%x]", this, rv));
+ }
+ }
+
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+
+ // The nsHttpConnection will go away soon, so if there is a TLS Filter
+ // being used (e.g. for wss CONNECT tunnel from a proxy connected to
+ // via https) that filter needs to take direct control of the
+ // streams
+ if (mTLSFilter) {
+ nsCOMPtr<nsIAsyncInputStream> ref1(mSocketIn);
+ nsCOMPtr<nsIAsyncOutputStream> ref2(mSocketOut);
+ mTLSFilter->newIODriver(ref1, ref2,
+ getter_AddRefs(mSocketIn),
+ getter_AddRefs(mSocketOut));
+ mTLSFilter = nullptr;
+ }
+
+ mSocketTransport.forget(aTransport);
+ mSocketIn.forget(aInputStream);
+ mSocketOut.forget(aOutputStream);
+
+ return NS_OK;
+}
+
+uint32_t
+nsHttpConnection::ReadTimeoutTick(PRIntervalTime now)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // make sure timer didn't tick before Activate()
+ if (!mTransaction)
+ return UINT32_MAX;
+
+ // Spdy implements some timeout handling using the SPDY ping frame.
+ if (mSpdySession) {
+ return mSpdySession->ReadTimeoutTick(now);
+ }
+
+ uint32_t nextTickAfter = UINT32_MAX;
+ // Timeout if the response is taking too long to arrive.
+ if (mResponseTimeoutEnabled) {
+ NS_WARNING_ASSERTION(
+ gHttpHandler->ResponseTimeoutEnabled(),
+ "Timing out a response, but response timeout is disabled!");
+
+ PRIntervalTime initialResponseDelta = now - mLastWriteTime;
+
+ if (initialResponseDelta > mTransaction->ResponseTimeout()) {
+ LOG(("canceling transaction: no response for %ums: timeout is %dms\n",
+ PR_IntervalToMilliseconds(initialResponseDelta),
+ PR_IntervalToMilliseconds(mTransaction->ResponseTimeout())));
+
+ mResponseTimeoutEnabled = false;
+
+ // This will also close the connection
+ CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT);
+ return UINT32_MAX;
+ }
+ nextTickAfter = PR_IntervalToSeconds(mTransaction->ResponseTimeout()) -
+ PR_IntervalToSeconds(initialResponseDelta);
+ nextTickAfter = std::max(nextTickAfter, 1U);
+ }
+
+ if (!gHttpHandler->GetPipelineRescheduleOnTimeout())
+ return nextTickAfter;
+
+ PRIntervalTime delta = now - mLastReadTime;
+
+ // we replicate some of the checks both here and in OnSocketReadable() as
+ // they will be discovered under different conditions. The ones here
+ // will generally be discovered if we are totally hung and OSR does
+ // not get called at all, however OSR discovers them with lower latency
+ // if the issue is just very slow (but not stalled) reading.
+ //
+ // Right now we only take action if pipelining is involved, but this would
+ // be the place to add general read timeout handling if it is desired.
+
+ uint32_t pipelineDepth = mTransaction->PipelineDepth();
+ if (pipelineDepth > 1) {
+ // if we have pipelines outstanding (not just an idle connection)
+ // then get a fairly quick tick
+ nextTickAfter = 1;
+ }
+
+ if (delta >= gHttpHandler->GetPipelineRescheduleTimeout() &&
+ pipelineDepth > 1) {
+
+ // this just reschedules blocked transactions. no transaction
+ // is aborted completely.
+ LOG(("cancelling pipeline due to a %ums stall - depth %d\n",
+ PR_IntervalToMilliseconds(delta), pipelineDepth));
+
+ nsHttpPipeline *pipeline = mTransaction->QueryPipeline();
+ MOZ_ASSERT(pipeline, "pipelinedepth > 1 without pipeline");
+ // code this defensively for the moment and check for null in opt build
+ // This will reschedule blocked members of the pipeline, but the
+ // blocking transaction (i.e. response 0) will not be changed.
+ if (pipeline) {
+ pipeline->CancelPipeline(NS_ERROR_NET_TIMEOUT);
+ LOG(("Rescheduling the head of line blocked members of a pipeline "
+ "because reschedule-timeout idle interval exceeded"));
+ }
+ }
+
+ if (delta < gHttpHandler->GetPipelineTimeout())
+ return nextTickAfter;
+
+ if (pipelineDepth <= 1 && !mTransaction->PipelinePosition())
+ return nextTickAfter;
+
+ // nothing has transpired on this pipelined socket for many
+ // seconds. Call that a total stall and close the transaction.
+ // There is a chance the transaction will be restarted again
+ // depending on its state.. that will come back araound
+ // without pipelining on, so this won't loop.
+
+ LOG(("canceling transaction stalled for %ums on a pipeline "
+ "of depth %d and scheduled originally at pos %d\n",
+ PR_IntervalToMilliseconds(delta),
+ pipelineDepth, mTransaction->PipelinePosition()));
+
+ // This will also close the connection
+ CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT);
+ return UINT32_MAX;
+}
+
+void
+nsHttpConnection::UpdateTCPKeepalive(nsITimer *aTimer, void *aClosure)
+{
+ MOZ_ASSERT(aTimer);
+ MOZ_ASSERT(aClosure);
+
+ nsHttpConnection *self = static_cast<nsHttpConnection*>(aClosure);
+
+ if (NS_WARN_IF(self->mUsingSpdyVersion)) {
+ return;
+ }
+
+ // Do not reduce keepalive probe frequency for idle connections.
+ if (self->mIdleMonitoring) {
+ return;
+ }
+
+ nsresult rv = self->StartLongLivedTCPKeepalives();
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpConnection::UpdateTCPKeepalive [%p] "
+ "StartLongLivedTCPKeepalives failed rv[0x%x]",
+ self, rv));
+ }
+}
+
+void
+nsHttpConnection::GetSecurityInfo(nsISupports **secinfo)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnection::GetSecurityInfo trans=%p tlsfilter=%p socket=%p\n",
+ mTransaction.get(), mTLSFilter.get(), mSocketTransport.get()));
+
+ if (mTransaction &&
+ NS_SUCCEEDED(mTransaction->GetTransactionSecurityInfo(secinfo))) {
+ return;
+ }
+
+ if (mTLSFilter &&
+ NS_SUCCEEDED(mTLSFilter->GetTransactionSecurityInfo(secinfo))) {
+ return;
+ }
+
+ if (mSocketTransport &&
+ NS_SUCCEEDED(mSocketTransport->GetSecurityInfo(secinfo))) {
+ return;
+ }
+
+ *secinfo = nullptr;
+}
+
+void
+nsHttpConnection::SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks)
+{
+ MutexAutoLock lock(mCallbacksLock);
+ // This is called both on and off the main thread. For JS-implemented
+ // callbacks, we requires that the call happen on the main thread, but
+ // for C++-implemented callbacks we don't care. Use a pointer holder with
+ // strict checking disabled.
+ mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(aCallbacks, false);
+}
+
+nsresult
+nsHttpConnection::PushBack(const char *data, uint32_t length)
+{
+ LOG(("nsHttpConnection::PushBack [this=%p, length=%d]\n", this, length));
+
+ if (mInputOverflow) {
+ NS_ERROR("nsHttpConnection::PushBack only one buffer supported");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mInputOverflow = new nsPreloadedStream(mSocketIn, data, length);
+ return NS_OK;
+}
+
+nsresult
+nsHttpConnection::ResumeSend()
+{
+ LOG(("nsHttpConnection::ResumeSend [this=%p]\n", this));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mSocketOut)
+ return mSocketOut->AsyncWait(this, 0, 0, nullptr);
+
+ NS_NOTREACHED("no socket output stream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+nsHttpConnection::ResumeRecv()
+{
+ LOG(("nsHttpConnection::ResumeRecv [this=%p]\n", this));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // the mLastReadTime timestamp is used for finding slowish readers
+ // and can be pretty sensitive. For that reason we actually reset it
+ // when we ask to read (resume recv()) so that when we get called back
+ // with actual read data in OnSocketReadable() we are only measuring
+ // the latency between those two acts and not all the processing that
+ // may get done before the ResumeRecv() call
+ mLastReadTime = PR_IntervalNow();
+
+ if (mSocketIn)
+ return mSocketIn->AsyncWait(this, 0, 0, nullptr);
+
+ NS_NOTREACHED("no socket input stream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+
+class HttpConnectionForceIO : public Runnable
+{
+public:
+ HttpConnectionForceIO(nsHttpConnection *aConn, bool doRecv)
+ : mConn(aConn)
+ , mDoRecv(doRecv)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mDoRecv) {
+ if (!mConn->mSocketIn)
+ return NS_OK;
+ return mConn->OnInputStreamReady(mConn->mSocketIn);
+ }
+
+ MOZ_ASSERT(mConn->mForceSendPending);
+ mConn->mForceSendPending = false;
+ if (!mConn->mSocketOut) {
+ return NS_OK;
+ }
+ return mConn->OnOutputStreamReady(mConn->mSocketOut);
+ }
+private:
+ RefPtr<nsHttpConnection> mConn;
+ bool mDoRecv;
+};
+
+void
+nsHttpConnection::ForceSendIO(nsITimer *aTimer, void *aClosure)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsHttpConnection *self = static_cast<nsHttpConnection *>(aClosure);
+ MOZ_ASSERT(aTimer == self->mForceSendTimer);
+ self->mForceSendTimer = nullptr;
+ NS_DispatchToCurrentThread(new HttpConnectionForceIO(self, false));
+}
+
+nsresult
+nsHttpConnection::MaybeForceSendIO()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ // due to bug 1213084 sometimes real I/O events do not get serviced when
+ // NSPR derived I/O events are ready and this can cause a deadlock with
+ // https over https proxying. Normally we would expect the write callback to
+ // be invoked before this timer goes off, but set it at the old windows
+ // tick interval (kForceDelay) as a backup for those circumstances.
+ static const uint32_t kForceDelay = 17; //ms
+
+ if (mForceSendPending) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(!mForceSendTimer);
+ mForceSendPending = true;
+ mForceSendTimer = do_CreateInstance("@mozilla.org/timer;1");
+ return mForceSendTimer->InitWithFuncCallback(
+ nsHttpConnection::ForceSendIO, this, kForceDelay, nsITimer::TYPE_ONE_SHOT);
+}
+
+// trigger an asynchronous read
+nsresult
+nsHttpConnection::ForceRecv()
+{
+ LOG(("nsHttpConnection::ForceRecv [this=%p]\n", this));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ return NS_DispatchToCurrentThread(new HttpConnectionForceIO(this, true));
+}
+
+// trigger an asynchronous write
+nsresult
+nsHttpConnection::ForceSend()
+{
+ LOG(("nsHttpConnection::ForceSend [this=%p]\n", this));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mTLSFilter) {
+ return mTLSFilter->NudgeTunnel(this);
+ }
+ return MaybeForceSendIO();
+}
+
+void
+nsHttpConnection::BeginIdleMonitoring()
+{
+ LOG(("nsHttpConnection::BeginIdleMonitoring [this=%p]\n", this));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(!mTransaction, "BeginIdleMonitoring() while active");
+ MOZ_ASSERT(!mUsingSpdyVersion, "Idle monitoring of spdy not allowed");
+
+ LOG(("Entering Idle Monitoring Mode [this=%p]", this));
+ mIdleMonitoring = true;
+ if (mSocketIn)
+ mSocketIn->AsyncWait(this, 0, 0, nullptr);
+}
+
+void
+nsHttpConnection::EndIdleMonitoring()
+{
+ LOG(("nsHttpConnection::EndIdleMonitoring [this=%p]\n", this));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(!mTransaction, "EndIdleMonitoring() while active");
+
+ if (mIdleMonitoring) {
+ LOG(("Leaving Idle Monitoring Mode [this=%p]", this));
+ mIdleMonitoring = false;
+ if (mSocketIn)
+ mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+}
+
+uint32_t
+nsHttpConnection::Version()
+{
+ return mUsingSpdyVersion ? mUsingSpdyVersion : mLastHttpResponseVersion;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection <private>
+//-----------------------------------------------------------------------------
+
+void
+nsHttpConnection::CloseTransaction(nsAHttpTransaction *trans, nsresult reason,
+ bool aIsShutdown)
+{
+ LOG(("nsHttpConnection::CloseTransaction[this=%p trans=%p reason=%x]\n",
+ this, trans, reason));
+
+ MOZ_ASSERT((trans == mTransaction) ||
+ (mTLSFilter && mTLSFilter->Transaction() == trans));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mCurrentBytesRead > mMaxBytesRead)
+ mMaxBytesRead = mCurrentBytesRead;
+
+ // mask this error code because its not a real error.
+ if (reason == NS_BASE_STREAM_CLOSED)
+ reason = NS_OK;
+
+ if (mUsingSpdyVersion) {
+ DontReuse();
+ // if !mSpdySession then mUsingSpdyVersion must be false for canreuse()
+ mUsingSpdyVersion = 0;
+ mSpdySession = nullptr;
+ }
+
+ if (mTransaction) {
+ mHttp1xTransactionCount += mTransaction->Http1xTransactionCount();
+
+ mTransaction->Close(reason);
+ mTransaction = nullptr;
+ }
+
+ {
+ MutexAutoLock lock(mCallbacksLock);
+ mCallbacks = nullptr;
+ }
+
+ if (NS_FAILED(reason) && (reason != NS_BINDING_RETARGETED)) {
+ Close(reason, aIsShutdown);
+ }
+
+ // flag the connection as reused here for convenience sake. certainly
+ // it might be going away instead ;-)
+ mIsReused = true;
+}
+
+nsresult
+nsHttpConnection::ReadFromStream(nsIInputStream *input,
+ void *closure,
+ const char *buf,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ // thunk for nsIInputStream instance
+ nsHttpConnection *conn = (nsHttpConnection *) closure;
+ return conn->OnReadSegment(buf, count, countRead);
+}
+
+nsresult
+nsHttpConnection::OnReadSegment(const char *buf,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ if (count == 0) {
+ // some ReadSegments implementations will erroneously call the writer
+ // to consume 0 bytes worth of data. we must protect against this case
+ // or else we'd end up closing the socket prematurely.
+ NS_ERROR("bad ReadSegments implementation");
+ return NS_ERROR_FAILURE; // stop iterating
+ }
+
+ nsresult rv = mSocketOut->Write(buf, count, countRead);
+ if (NS_FAILED(rv))
+ mSocketOutCondition = rv;
+ else if (*countRead == 0)
+ mSocketOutCondition = NS_BASE_STREAM_CLOSED;
+ else {
+ mLastWriteTime = PR_IntervalNow();
+ mSocketOutCondition = NS_OK; // reset condition
+ if (!mProxyConnectInProgress)
+ mTotalBytesWritten += *countRead;
+ }
+
+ return mSocketOutCondition;
+}
+
+nsresult
+nsHttpConnection::OnSocketWritable()
+{
+ LOG(("nsHttpConnection::OnSocketWritable [this=%p] host=%s\n",
+ this, mConnInfo->Origin()));
+
+ nsresult rv;
+ uint32_t transactionBytes;
+ bool again = true;
+
+ do {
+ rv = mSocketOutCondition = NS_OK;
+ transactionBytes = 0;
+
+ // The SSL handshake must be completed before the transaction->readsegments()
+ // processing can proceed because we need to know how to format the
+ // request differently for http/1, http/2, spdy, etc.. and that is
+ // negotiated with NPN/ALPN in the SSL handshake.
+
+ if (mConnInfo->UsingHttpsProxy() &&
+ !EnsureNPNComplete(rv, transactionBytes)) {
+ MOZ_ASSERT(!transactionBytes);
+ mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
+ } else if (mProxyConnectStream) {
+ // If we're need an HTTP/1 CONNECT tunnel through a proxy
+ // send it before doing the SSL handshake
+ LOG((" writing CONNECT request stream\n"));
+ rv = mProxyConnectStream->ReadSegments(ReadFromStream, this,
+ nsIOService::gDefaultSegmentSize,
+ &transactionBytes);
+ } else if (!EnsureNPNComplete(rv, transactionBytes)) {
+ if (NS_SUCCEEDED(rv) && !transactionBytes &&
+ NS_SUCCEEDED(mSocketOutCondition)) {
+ mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ } else if (!mTransaction) {
+ rv = NS_ERROR_FAILURE;
+ LOG((" No Transaction In OnSocketWritable\n"));
+ } else {
+
+ // for non spdy sessions let the connection manager know
+ if (!mReportedSpdy) {
+ mReportedSpdy = true;
+ MOZ_ASSERT(!mEverUsedSpdy);
+ gHttpHandler->ConnMgr()->ReportSpdyConnection(this, false);
+ }
+
+ LOG((" writing transaction request stream\n"));
+ mProxyConnectInProgress = false;
+ rv = mTransaction->ReadSegmentsAgain(this, nsIOService::gDefaultSegmentSize,
+ &transactionBytes, &again);
+ mContentBytesWritten += transactionBytes;
+ }
+
+ LOG(("nsHttpConnection::OnSocketWritable %p "
+ "ReadSegments returned [rv=%x read=%u sock-cond=%x]\n",
+ this, rv, transactionBytes, mSocketOutCondition));
+
+ // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
+ if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) {
+ rv = NS_OK;
+ transactionBytes = 0;
+ }
+
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to write any more data, then
+ // wait for the transaction to call ResumeSend.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+ rv = NS_OK;
+ again = false;
+ } else if (NS_FAILED(mSocketOutCondition)) {
+ if (mSocketOutCondition == NS_BASE_STREAM_WOULD_BLOCK) {
+ if (mTLSFilter) {
+ LOG((" blocked tunnel (handshake?)\n"));
+ rv = mTLSFilter->NudgeTunnel(this);
+ } else {
+ rv = mSocketOut->AsyncWait(this, 0, 0, nullptr); // continue writing
+ }
+ } else {
+ rv = mSocketOutCondition;
+ }
+ again = false;
+ } else if (!transactionBytes) {
+ rv = NS_OK;
+
+ if (mTransaction && !mWaitingFor0RTTResponse) { // in case the ReadSegments stack called CloseTransaction()
+ //
+ // at this point we've written out the entire transaction, and now we
+ // must wait for the server's response. we manufacture a status message
+ // here to reflect the fact that we are waiting. this message will be
+ // trumped (overwritten) if the server responds quickly.
+ //
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_WAITING_FOR,
+ 0);
+
+ rv = ResumeRecv(); // start reading
+ }
+ again = false;
+ }
+ // write more to the socket until error or end-of-request...
+ } while (again && gHttpHandler->Active());
+
+ return rv;
+}
+
+nsresult
+nsHttpConnection::OnWriteSegment(char *buf,
+ uint32_t count,
+ uint32_t *countWritten)
+{
+ if (count == 0) {
+ // some WriteSegments implementations will erroneously call the reader
+ // to provide 0 bytes worth of data. we must protect against this case
+ // or else we'd end up closing the socket prematurely.
+ NS_ERROR("bad WriteSegments implementation");
+ return NS_ERROR_FAILURE; // stop iterating
+ }
+
+ if (ChaosMode::isActive(ChaosFeature::IOAmounts) &&
+ ChaosMode::randomUint32LessThan(2)) {
+ // read 1...count bytes
+ count = ChaosMode::randomUint32LessThan(count) + 1;
+ }
+
+ nsresult rv = mSocketIn->Read(buf, count, countWritten);
+ if (NS_FAILED(rv))
+ mSocketInCondition = rv;
+ else if (*countWritten == 0)
+ mSocketInCondition = NS_BASE_STREAM_CLOSED;
+ else
+ mSocketInCondition = NS_OK; // reset condition
+
+ return mSocketInCondition;
+}
+
+nsresult
+nsHttpConnection::OnSocketReadable()
+{
+ LOG(("nsHttpConnection::OnSocketReadable [this=%p]\n", this));
+
+ PRIntervalTime now = PR_IntervalNow();
+ PRIntervalTime delta = now - mLastReadTime;
+
+ // Reset mResponseTimeoutEnabled to stop response timeout checks.
+ mResponseTimeoutEnabled = false;
+
+ if (mKeepAliveMask && (delta >= mMaxHangTime)) {
+ LOG(("max hang time exceeded!\n"));
+ // give the handler a chance to create a new persistent connection to
+ // this host if we've been busy for too long.
+ mKeepAliveMask = false;
+ gHttpHandler->ProcessPendingQ(mConnInfo);
+ }
+
+ // Look for data being sent in bursts with large pauses. If the pauses
+ // are caused by server bottlenecks such as think-time, disk i/o, or
+ // cpu exhaustion (as opposed to network latency) then we generate negative
+ // pipelining feedback to prevent head of line problems
+
+ // Reduce the estimate of the time since last read by up to 1 RTT to
+ // accommodate exhausted sender TCP congestion windows or minor I/O delays.
+
+ if (delta > mRtt)
+ delta -= mRtt;
+ else
+ delta = 0;
+
+ static const PRIntervalTime k400ms = PR_MillisecondsToInterval(400);
+
+ if (delta >= (mRtt + gHttpHandler->GetPipelineRescheduleTimeout())) {
+ LOG(("Read delta ms of %u causing slow read major "
+ "event and pipeline cancellation",
+ PR_IntervalToMilliseconds(delta)));
+
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::BadSlowReadMajor, this, 0);
+
+ if (gHttpHandler->GetPipelineRescheduleOnTimeout() &&
+ mTransaction->PipelineDepth() > 1) {
+ nsHttpPipeline *pipeline = mTransaction->QueryPipeline();
+ MOZ_ASSERT(pipeline, "pipelinedepth > 1 without pipeline");
+ // code this defensively for the moment and check for null
+ // This will reschedule blocked members of the pipeline, but the
+ // blocking transaction (i.e. response 0) will not be changed.
+ if (pipeline) {
+ pipeline->CancelPipeline(NS_ERROR_NET_TIMEOUT);
+ LOG(("Rescheduling the head of line blocked members of a "
+ "pipeline because reschedule-timeout idle interval "
+ "exceeded"));
+ }
+ }
+ }
+ else if (delta > k400ms) {
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::BadSlowReadMinor, this, 0);
+ }
+
+ mLastReadTime = now;
+
+ nsresult rv;
+ uint32_t n;
+ bool again = true;
+
+ do {
+ if (!mProxyConnectInProgress && !mNPNComplete) {
+ // Unless we are setting up a tunnel via CONNECT, prevent reading
+ // from the socket until the results of NPN
+ // negotiation are known (which is determined from the write path).
+ // If the server speaks SPDY it is likely the readable data here is
+ // a spdy settings frame and without NPN it would be misinterpreted
+ // as HTTP/*
+
+ LOG(("nsHttpConnection::OnSocketReadable %p return due to inactive "
+ "tunnel setup but incomplete NPN state\n", this));
+ rv = NS_OK;
+ break;
+ }
+
+ mSocketInCondition = NS_OK;
+ rv = mTransaction->
+ WriteSegmentsAgain(this, nsIOService::gDefaultSegmentSize, &n, &again);
+ LOG(("nsHttpConnection::OnSocketReadable %p trans->ws rv=%x n=%d socketin=%x\n",
+ this, rv, n, mSocketInCondition));
+ if (NS_FAILED(rv)) {
+ // if the transaction didn't want to take any more data, then
+ // wait for the transaction to call ResumeRecv.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+ again = false;
+ } else {
+ mCurrentBytesRead += n;
+ mTotalBytesRead += n;
+ if (NS_FAILED(mSocketInCondition)) {
+ // continue waiting for the socket if necessary...
+ if (mSocketInCondition == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = ResumeRecv();
+ } else {
+ rv = mSocketInCondition;
+ }
+ again = false;
+ }
+ }
+ // read more from the socket until error...
+ } while (again && gHttpHandler->Active());
+
+ return rv;
+}
+
+void
+nsHttpConnection::SetupSecondaryTLS()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(!mTLSFilter);
+ LOG(("nsHttpConnection %p SetupSecondaryTLS %s %d\n",
+ this, mConnInfo->Origin(), mConnInfo->OriginPort()));
+
+ nsHttpConnectionInfo *ci = nullptr;
+ if (mTransaction) {
+ ci = mTransaction->ConnectionInfo();
+ }
+ if (!ci) {
+ ci = mConnInfo;
+ }
+ MOZ_ASSERT(ci);
+
+ mTLSFilter = new TLSFilterTransaction(mTransaction,
+ ci->Origin(), ci->OriginPort(), this, this);
+
+ if (mTransaction) {
+ mTransaction = mTLSFilter;
+ }
+}
+
+void
+nsHttpConnection::SetInSpdyTunnel(bool arg)
+{
+ MOZ_ASSERT(mTLSFilter);
+ mInSpdyTunnel = arg;
+
+ // don't setup another tunnel :)
+ mProxyConnectStream = nullptr;
+ mCompletedProxyConnect = true;
+ mProxyConnectInProgress = false;
+}
+
+nsresult
+nsHttpConnection::MakeConnectString(nsAHttpTransaction *trans,
+ nsHttpRequestHead *request,
+ nsACString &result)
+{
+ result.Truncate();
+ if (!trans->ConnectionInfo()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsHttpHandler::GenerateHostPort(
+ nsDependentCString(trans->ConnectionInfo()->Origin()),
+ trans->ConnectionInfo()->OriginPort(), result);
+
+ // CONNECT host:port HTTP/1.1
+ request->SetMethod(NS_LITERAL_CSTRING("CONNECT"));
+ request->SetVersion(gHttpHandler->HttpVersion());
+ request->SetRequestURI(result);
+ request->SetHeader(nsHttp::User_Agent, gHttpHandler->UserAgent());
+
+ // a CONNECT is always persistent
+ request->SetHeader(nsHttp::Proxy_Connection, NS_LITERAL_CSTRING("keep-alive"));
+ request->SetHeader(nsHttp::Connection, NS_LITERAL_CSTRING("keep-alive"));
+
+ // all HTTP/1.1 requests must include a Host header (even though it
+ // may seem redundant in this case; see bug 82388).
+ request->SetHeader(nsHttp::Host, result);
+
+ nsAutoCString val;
+ if (NS_SUCCEEDED(trans->RequestHead()->GetHeader(
+ nsHttp::Proxy_Authorization,
+ val))) {
+ // we don't know for sure if this authorization is intended for the
+ // SSL proxy, so we add it just in case.
+ request->SetHeader(nsHttp::Proxy_Authorization, val);
+ }
+
+ result.Truncate();
+ request->Flatten(result, false);
+ result.AppendLiteral("\r\n");
+ return NS_OK;
+}
+
+nsresult
+nsHttpConnection::SetupProxyConnect()
+{
+ LOG(("nsHttpConnection::SetupProxyConnect [this=%p]\n", this));
+ NS_ENSURE_TRUE(!mProxyConnectStream, NS_ERROR_ALREADY_INITIALIZED);
+ MOZ_ASSERT(!mUsingSpdyVersion,
+ "SPDY NPN Complete while using proxy connect stream");
+
+ nsAutoCString buf;
+ nsHttpRequestHead request;
+ nsresult rv = MakeConnectString(mTransaction, &request, buf);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_NewCStringInputStream(getter_AddRefs(mProxyConnectStream), buf);
+}
+
+nsresult
+nsHttpConnection::StartShortLivedTCPKeepalives()
+{
+ if (mUsingSpdyVersion) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(mSocketTransport);
+ if (!mSocketTransport) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv = NS_OK;
+ int32_t idleTimeS = -1;
+ int32_t retryIntervalS = -1;
+ if (gHttpHandler->TCPKeepaliveEnabledForShortLivedConns()) {
+ // Set the idle time.
+ idleTimeS = gHttpHandler->GetTCPKeepaliveShortLivedIdleTime();
+ LOG(("nsHttpConnection::StartShortLivedTCPKeepalives[%p] "
+ "idle time[%ds].", this, idleTimeS));
+
+ retryIntervalS =
+ std::max<int32_t>((int32_t)PR_IntervalToSeconds(mRtt), 1);
+ rv = mSocketTransport->SetKeepaliveVals(idleTimeS, retryIntervalS);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = mSocketTransport->SetKeepaliveEnabled(true);
+ mTCPKeepaliveConfig = kTCPKeepaliveShortLivedConfig;
+ } else {
+ rv = mSocketTransport->SetKeepaliveEnabled(false);
+ mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Start a timer to move to long-lived keepalive config.
+ if(!mTCPKeepaliveTransitionTimer) {
+ mTCPKeepaliveTransitionTimer =
+ do_CreateInstance("@mozilla.org/timer;1");
+ }
+
+ if (mTCPKeepaliveTransitionTimer) {
+ int32_t time = gHttpHandler->GetTCPKeepaliveShortLivedTime();
+
+ // Adjust |time| to ensure a full set of keepalive probes can be sent
+ // at the end of the short-lived phase.
+ if (gHttpHandler->TCPKeepaliveEnabledForShortLivedConns()) {
+ if (NS_WARN_IF(!gSocketTransportService)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ int32_t probeCount = -1;
+ rv = gSocketTransportService->GetKeepaliveProbeCount(&probeCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (NS_WARN_IF(probeCount <= 0)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ // Add time for final keepalive probes, and 2 seconds for a buffer.
+ time += ((probeCount) * retryIntervalS) - (time % idleTimeS) + 2;
+ }
+ mTCPKeepaliveTransitionTimer->InitWithFuncCallback(
+ nsHttpConnection::UpdateTCPKeepalive,
+ this,
+ (uint32_t)time*1000,
+ nsITimer::TYPE_ONE_SHOT);
+ } else {
+ NS_WARNING("nsHttpConnection::StartShortLivedTCPKeepalives failed to "
+ "create timer.");
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpConnection::StartLongLivedTCPKeepalives()
+{
+ MOZ_ASSERT(!mUsingSpdyVersion, "Don't use TCP Keepalive with SPDY!");
+ if (NS_WARN_IF(mUsingSpdyVersion)) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(mSocketTransport);
+ if (!mSocketTransport) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv = NS_OK;
+ if (gHttpHandler->TCPKeepaliveEnabledForLongLivedConns()) {
+ // Increase the idle time.
+ int32_t idleTimeS = gHttpHandler->GetTCPKeepaliveLongLivedIdleTime();
+ LOG(("nsHttpConnection::StartLongLivedTCPKeepalives[%p] idle time[%ds]",
+ this, idleTimeS));
+
+ int32_t retryIntervalS =
+ std::max<int32_t>((int32_t)PR_IntervalToSeconds(mRtt), 1);
+ rv = mSocketTransport->SetKeepaliveVals(idleTimeS, retryIntervalS);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Ensure keepalive is enabled, if current status is disabled.
+ if (mTCPKeepaliveConfig == kTCPKeepaliveDisabled) {
+ rv = mSocketTransport->SetKeepaliveEnabled(true);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ mTCPKeepaliveConfig = kTCPKeepaliveLongLivedConfig;
+ } else {
+ rv = mSocketTransport->SetKeepaliveEnabled(false);
+ mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsHttpConnection::DisableTCPKeepalives()
+{
+ MOZ_ASSERT(mSocketTransport);
+ if (!mSocketTransport) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ LOG(("nsHttpConnection::DisableTCPKeepalives [%p]", this));
+ if (mTCPKeepaliveConfig != kTCPKeepaliveDisabled) {
+ nsresult rv = mSocketTransport->SetKeepaliveEnabled(false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
+ }
+ if (mTCPKeepaliveTransitionTimer) {
+ mTCPKeepaliveTransitionTimer->Cancel();
+ mTCPKeepaliveTransitionTimer = nullptr;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpConnection,
+ nsIInputStreamCallback,
+ nsIOutputStreamCallback,
+ nsITransportEventSink,
+ nsIInterfaceRequestor)
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsIInputStreamCallback
+//-----------------------------------------------------------------------------
+
+// called on the socket transport thread
+NS_IMETHODIMP
+nsHttpConnection::OnInputStreamReady(nsIAsyncInputStream *in)
+{
+ MOZ_ASSERT(in == mSocketIn, "unexpected stream");
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mIdleMonitoring) {
+ MOZ_ASSERT(!mTransaction, "Idle Input Event While Active");
+
+ // The only read event that is protocol compliant for an idle connection
+ // is an EOF, which we check for with CanReuse(). If the data is
+ // something else then just ignore it and suspend checking for EOF -
+ // our normal timers or protocol stack are the place to deal with
+ // any exception logic.
+
+ if (!CanReuse()) {
+ LOG(("Server initiated close of idle conn %p\n", this));
+ gHttpHandler->ConnMgr()->CloseIdleConnection(this);
+ return NS_OK;
+ }
+
+ LOG(("Input data on idle conn %p, but not closing yet\n", this));
+ return NS_OK;
+ }
+
+ // if the transaction was dropped...
+ if (!mTransaction) {
+ LOG((" no transaction; ignoring event\n"));
+ return NS_OK;
+ }
+
+ nsresult rv = OnSocketReadable();
+ if (NS_FAILED(rv))
+ CloseTransaction(mTransaction, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsIOutputStreamCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpConnection::OnOutputStreamReady(nsIAsyncOutputStream *out)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(out == mSocketOut, "unexpected socket");
+ // if the transaction was dropped...
+ if (!mTransaction) {
+ LOG((" no transaction; ignoring event\n"));
+ return NS_OK;
+ }
+
+ nsresult rv = OnSocketWritable();
+ if (NS_FAILED(rv))
+ CloseTransaction(mTransaction, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsITransportEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpConnection::OnTransportStatus(nsITransport *trans,
+ nsresult status,
+ int64_t progress,
+ int64_t progressMax)
+{
+ if (mTransaction)
+ mTransaction->OnTransportStatus(trans, status, progress);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+// not called on the socket transport thread
+NS_IMETHODIMP
+nsHttpConnection::GetInterface(const nsIID &iid, void **result)
+{
+ // NOTE: This function is only called on the UI thread via sync proxy from
+ // the socket transport thread. If that weren't the case, then we'd
+ // have to worry about the possibility of mTransaction going away
+ // part-way through this function call. See CloseTransaction.
+
+ // NOTE - there is a bug here, the call to getinterface is proxied off the
+ // nss thread, not the ui thread as the above comment says. So there is
+ // indeed a chance of mTransaction going away. bug 615342
+
+ MOZ_ASSERT(PR_GetCurrentThread() != gSocketThread);
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ {
+ MutexAutoLock lock(mCallbacksLock);
+ callbacks = mCallbacks;
+ }
+ if (callbacks)
+ return callbacks->GetInterface(iid, result);
+ return NS_ERROR_NO_INTERFACE;
+}
+
+void
+nsHttpConnection::CheckForTraffic(bool check)
+{
+ if (check) {
+ LOG((" CheckForTraffic conn %p\n", this));
+ if (mSpdySession) {
+ if (PR_IntervalToMilliseconds(IdleTime()) >= 500) {
+ // Send a ping to verify it is still alive if it has been idle
+ // more than half a second, the network changed events are
+ // rate-limited to one per 1000 ms.
+ LOG((" SendPing\n"));
+ mSpdySession->SendPing();
+ } else {
+ LOG((" SendPing skipped due to network activity\n"));
+ }
+ } else {
+ // If not SPDY, Store snapshot amount of data right now
+ mTrafficCount = mTotalBytesWritten + mTotalBytesRead;
+ mTrafficStamp = true;
+ }
+ } else {
+ // mark it as not checked
+ mTrafficStamp = false;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h
new file mode 100644
index 000000000..783b080b3
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -0,0 +1,378 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpConnection_h__
+#define nsHttpConnection_h__
+
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpResponseHead.h"
+#include "nsAHttpTransaction.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsProxyRelease.h"
+#include "prinrval.h"
+#include "TunnelUtils.h"
+#include "mozilla/Mutex.h"
+#include "ARefBase.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsITimer.h"
+
+class nsISocketTransport;
+class nsISSLSocketControl;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpHandler;
+class ASpdySession;
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection - represents a connection to a HTTP server (or proxy)
+//
+// NOTE: this objects lives on the socket thread only. it should not be
+// accessed from any other thread.
+//-----------------------------------------------------------------------------
+
+class nsHttpConnection final : public nsAHttpSegmentReader
+ , public nsAHttpSegmentWriter
+ , public nsIInputStreamCallback
+ , public nsIOutputStreamCallback
+ , public nsITransportEventSink
+ , public nsIInterfaceRequestor
+ , public NudgeTunnelCallback
+ , public ARefBase
+{
+ virtual ~nsHttpConnection();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NUDGETUNNELCALLBACK
+
+ nsHttpConnection();
+
+ // Initialize the connection:
+ // info - specifies the connection parameters.
+ // maxHangTime - limits the amount of time this connection can spend on a
+ // single transaction before it should no longer be kept
+ // alive. a value of 0xffff indicates no limit.
+ nsresult Init(nsHttpConnectionInfo *info, uint16_t maxHangTime,
+ nsISocketTransport *, nsIAsyncInputStream *,
+ nsIAsyncOutputStream *, bool connectedTransport,
+ nsIInterfaceRequestor *, PRIntervalTime);
+
+ // Activate causes the given transaction to be processed on this
+ // connection. It fails if there is already an existing transaction unless
+ // a multiplexing protocol such as SPDY is being used
+ nsresult Activate(nsAHttpTransaction *, uint32_t caps, int32_t pri);
+
+ // Close the underlying socket transport.
+ void Close(nsresult reason, bool aIsShutdown = false);
+
+ //-------------------------------------------------------------------------
+ // XXX document when these are ok to call
+
+ bool SupportsPipelining();
+ bool IsKeepAlive()
+ {
+ return mUsingSpdyVersion || (mKeepAliveMask && mKeepAlive);
+ }
+ bool CanReuse(); // can this connection be reused?
+ bool CanDirectlyActivate();
+
+ // Returns time in seconds for how long connection can be reused.
+ uint32_t TimeToLive();
+
+ void DontReuse();
+
+ bool IsProxyConnectInProgress()
+ {
+ return mProxyConnectInProgress;
+ }
+
+ bool LastTransactionExpectedNoContent()
+ {
+ return mLastTransactionExpectedNoContent;
+ }
+
+ void SetLastTransactionExpectedNoContent(bool val)
+ {
+ mLastTransactionExpectedNoContent = val;
+ }
+
+ bool NeedSpdyTunnel()
+ {
+ return mConnInfo->UsingHttpsProxy() && !mTLSFilter && mConnInfo->UsingConnect();
+ }
+
+ // A connection is forced into plaintext when it is intended to be used as a CONNECT
+ // tunnel but the setup fails. The plaintext only carries the CONNECT error.
+ void ForcePlainText()
+ {
+ mForcePlainText = true;
+ }
+
+ nsISocketTransport *Transport() { return mSocketTransport; }
+ nsAHttpTransaction *Transaction() { return mTransaction; }
+ nsHttpConnectionInfo *ConnectionInfo() { return mConnInfo; }
+
+ // nsAHttpConnection compatible methods (non-virtual):
+ nsresult OnHeadersAvailable(nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *, bool *reset);
+ void CloseTransaction(nsAHttpTransaction *, nsresult reason, bool aIsShutdown = false);
+ void GetConnectionInfo(nsHttpConnectionInfo **ci) { NS_IF_ADDREF(*ci = mConnInfo); }
+ nsresult TakeTransport(nsISocketTransport **,
+ nsIAsyncInputStream **,
+ nsIAsyncOutputStream **);
+ void GetSecurityInfo(nsISupports **);
+ bool IsPersistent() { return IsKeepAlive() && !mDontReuse; }
+ bool IsReused();
+ void SetIsReusedAfter(uint32_t afterMilliseconds);
+ nsresult PushBack(const char *data, uint32_t length);
+ nsresult ResumeSend();
+ nsresult ResumeRecv();
+ int64_t MaxBytesRead() {return mMaxBytesRead;}
+ uint8_t GetLastHttpResponseVersion() { return mLastHttpResponseVersion; }
+
+ friend class HttpConnectionForceIO;
+ nsresult ForceSend();
+ nsresult ForceRecv();
+
+ static nsresult ReadFromStream(nsIInputStream *, void *, const char *,
+ uint32_t, uint32_t, uint32_t *);
+
+ // When a persistent connection is in the connection manager idle
+ // connection pool, the nsHttpConnection still reads errors and hangups
+ // on the socket so that it can be proactively released if the server
+ // initiates a termination. Only call on socket thread.
+ void BeginIdleMonitoring();
+ void EndIdleMonitoring();
+
+ bool UsingSpdy() { return !!mUsingSpdyVersion; }
+ uint8_t GetSpdyVersion() { return mUsingSpdyVersion; }
+ bool EverUsedSpdy() { return mEverUsedSpdy; }
+ PRIntervalTime Rtt() { return mRtt; }
+
+ // true when connection SSL NPN phase is complete and we know
+ // authoritatively whether UsingSpdy() or not.
+ bool ReportedNPN() { return mReportedSpdy; }
+
+ // When the connection is active this is called up to once every 1 second
+ // return the interval (in seconds) that the connection next wants to
+ // have this invoked. It might happen sooner depending on the needs of
+ // other connections.
+ uint32_t ReadTimeoutTick(PRIntervalTime now);
+
+ // For Active and Idle connections, this will be called when
+ // mTCPKeepaliveTransitionTimer fires, to check if the TCP keepalive config
+ // should move from short-lived (fast-detect) to long-lived.
+ static void UpdateTCPKeepalive(nsITimer *aTimer, void *aClosure);
+
+ nsAHttpTransaction::Classifier Classification() { return mClassification; }
+ void Classify(nsAHttpTransaction::Classifier newclass)
+ {
+ mClassification = newclass;
+ }
+
+ // When the connection is active this is called every second
+ void ReadTimeoutTick();
+
+ int64_t BytesWritten() { return mTotalBytesWritten; } // includes TLS
+ int64_t ContentBytesWritten() { return mContentBytesWritten; }
+
+ void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks);
+ void PrintDiagnostics(nsCString &log);
+
+ void SetTransactionCaps(uint32_t aCaps) { mTransactionCaps = aCaps; }
+
+ // IsExperienced() returns true when the connection has started at least one
+ // non null HTTP transaction of any version.
+ bool IsExperienced() { return mExperienced; }
+
+ static nsresult MakeConnectString(nsAHttpTransaction *trans,
+ nsHttpRequestHead *request,
+ nsACString &result);
+ void SetupSecondaryTLS();
+ void SetInSpdyTunnel(bool arg);
+
+ // Check active connections for traffic (or not). SPDY connections send a
+ // ping, ordinary HTTP connections get some time to get traffic to be
+ // considered alive.
+ void CheckForTraffic(bool check);
+
+ // NoTraffic() returns true if there's been no traffic on the (non-spdy)
+ // connection since CheckForTraffic() was called.
+ bool NoTraffic() {
+ return mTrafficStamp &&
+ (mTrafficCount == (mTotalBytesWritten + mTotalBytesRead));
+ }
+ // override of nsAHttpConnection
+ virtual uint32_t Version();
+
+private:
+ // Value (set in mTCPKeepaliveConfig) indicates which set of prefs to use.
+ enum TCPKeepaliveConfig {
+ kTCPKeepaliveDisabled = 0,
+ kTCPKeepaliveShortLivedConfig,
+ kTCPKeepaliveLongLivedConfig
+ };
+
+ // called to cause the underlying socket to start speaking SSL
+ nsresult InitSSLParams(bool connectingToProxy, bool ProxyStartSSL);
+ nsresult SetupNPNList(nsISSLSocketControl *ssl, uint32_t caps);
+
+ nsresult OnTransactionDone(nsresult reason);
+ nsresult OnSocketWritable();
+ nsresult OnSocketReadable();
+
+ nsresult SetupProxyConnect();
+
+ PRIntervalTime IdleTime();
+ bool IsAlive();
+ bool SupportsPipelining(nsHttpResponseHead *);
+
+ // Makes certain the SSL handshake is complete and NPN negotiation
+ // has had a chance to happen
+ bool EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
+ uint32_t &aOut0RTTBytesWritten);
+ void SetupSSL();
+
+ // Start the Spdy transaction handler when NPN indicates spdy/*
+ void StartSpdy(uint8_t versionLevel);
+
+ // Directly Add a transaction to an active connection for SPDY
+ nsresult AddTransaction(nsAHttpTransaction *, int32_t);
+
+ // Used to set TCP keepalives for fast detection of dead connections during
+ // an initial period, and slower detection for long-lived connections.
+ nsresult StartShortLivedTCPKeepalives();
+ nsresult StartLongLivedTCPKeepalives();
+ nsresult DisableTCPKeepalives();
+
+private:
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+
+ nsresult mSocketInCondition;
+ nsresult mSocketOutCondition;
+
+ nsCOMPtr<nsIInputStream> mProxyConnectStream;
+ nsCOMPtr<nsIInputStream> mRequestStream;
+
+ // mTransaction only points to the HTTP Transaction callbacks if the
+ // transaction is open, otherwise it is null.
+ RefPtr<nsAHttpTransaction> mTransaction;
+ RefPtr<TLSFilterTransaction> mTLSFilter;
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+
+ Mutex mCallbacksLock;
+ nsMainThreadPtrHandle<nsIInterfaceRequestor> mCallbacks;
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+
+ PRIntervalTime mLastReadTime;
+ PRIntervalTime mLastWriteTime;
+ PRIntervalTime mMaxHangTime; // max download time before dropping keep-alive status
+ PRIntervalTime mIdleTimeout; // value of keep-alive: timeout=
+ PRIntervalTime mConsiderReusedAfterInterval;
+ PRIntervalTime mConsiderReusedAfterEpoch;
+ int64_t mCurrentBytesRead; // data read per activation
+ int64_t mMaxBytesRead; // max read in 1 activation
+ int64_t mTotalBytesRead; // total data read
+ int64_t mTotalBytesWritten; // does not include CONNECT tunnel
+ int64_t mContentBytesWritten; // does not include CONNECT tunnel or TLS
+
+ RefPtr<nsIAsyncInputStream> mInputOverflow;
+
+ PRIntervalTime mRtt;
+
+ bool mConnectedTransport;
+ bool mKeepAlive;
+ bool mKeepAliveMask;
+ bool mDontReuse;
+ bool mSupportsPipelining;
+ bool mIsReused;
+ bool mCompletedProxyConnect;
+ bool mLastTransactionExpectedNoContent;
+ bool mIdleMonitoring;
+ bool mProxyConnectInProgress;
+ bool mExperienced;
+ bool mInSpdyTunnel;
+ bool mForcePlainText;
+
+ // A snapshot of current number of transfered bytes
+ int64_t mTrafficCount;
+ bool mTrafficStamp; // true then the above is set
+
+ // The number of <= HTTP/1.1 transactions performed on this connection. This
+ // excludes spdy transactions.
+ uint32_t mHttp1xTransactionCount;
+
+ // Keep-Alive: max="mRemainingConnectionUses" provides the number of future
+ // transactions (including the current one) that the server expects to allow
+ // on this persistent connection.
+ uint32_t mRemainingConnectionUses;
+
+ nsAHttpTransaction::Classifier mClassification;
+
+ // SPDY related
+ bool mNPNComplete;
+ bool mSetupSSLCalled;
+
+ // version level in use, 0 if unused
+ uint8_t mUsingSpdyVersion;
+
+ RefPtr<ASpdySession> mSpdySession;
+ int32_t mPriority;
+ bool mReportedSpdy;
+
+ // mUsingSpdyVersion is cleared when mSpdySession is freed, this is permanent
+ bool mEverUsedSpdy;
+
+ // mLastHttpResponseVersion stores the last response's http version seen.
+ uint8_t mLastHttpResponseVersion;
+
+ // The capabailities associated with the most recent transaction
+ uint32_t mTransactionCaps;
+
+ bool mResponseTimeoutEnabled;
+
+ // Flag to indicate connection is in inital keepalive period (fast detect).
+ uint32_t mTCPKeepaliveConfig;
+ nsCOMPtr<nsITimer> mTCPKeepaliveTransitionTimer;
+
+private:
+ // For ForceSend()
+ static void ForceSendIO(nsITimer *aTimer, void *aClosure);
+ nsresult MaybeForceSendIO();
+ bool mForceSendPending;
+ nsCOMPtr<nsITimer> mForceSendTimer;
+
+ // Helper variable for 0RTT handshake;
+ bool m0RTTChecked; // Possible 0RTT has been
+ // checked.
+ bool mWaitingFor0RTTResponse; // We have are
+ // sending 0RTT
+ // data and we
+ // are waiting
+ // for the end of
+ // the handsake.
+ int64_t mContentBytesWritten0RTT;
+ bool mEarlyDataNegotiated; //Only used for telemetry
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpConnection_h__
diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.cpp b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
new file mode 100644
index 000000000..e965fd1cc
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
@@ -0,0 +1,339 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "nsHttpConnectionInfo.h"
+#include "mozilla/net/DNS.h"
+#include "prnetdb.h"
+#include "nsICryptoHash.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIProtocolProxyService.h"
+
+static nsresult
+SHA256(const char* aPlainText, nsAutoCString& aResult)
+{
+ static nsICryptoHash* hasher = nullptr;
+ nsresult rv;
+ if (!hasher) {
+ rv = CallCreateInstance("@mozilla.org/security/hash;1", &hasher);
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpDigestAuth: no crypto hash!\n"));
+ return rv;
+ }
+ }
+
+ rv = hasher->Init(nsICryptoHash::SHA256);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Update((unsigned char*) aPlainText, strlen(aPlainText));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Finish(false, aResult);
+ return rv;
+}
+
+namespace mozilla {
+namespace net {
+
+nsHttpConnectionInfo::nsHttpConnectionInfo(const nsACString &originHost,
+ int32_t originPort,
+ const nsACString &npnToken,
+ const nsACString &username,
+ nsProxyInfo *proxyInfo,
+ const NeckoOriginAttributes &originAttributes,
+ bool endToEndSSL)
+ : mRoutedPort(443)
+{
+ Init(originHost, originPort, npnToken, username, proxyInfo, originAttributes, endToEndSSL);
+}
+
+nsHttpConnectionInfo::nsHttpConnectionInfo(const nsACString &originHost,
+ int32_t originPort,
+ const nsACString &npnToken,
+ const nsACString &username,
+ nsProxyInfo *proxyInfo,
+ const NeckoOriginAttributes &originAttributes,
+ const nsACString &routedHost,
+ int32_t routedPort)
+{
+ mEndToEndSSL = true; // so DefaultPort() works
+ mRoutedPort = routedPort == -1 ? DefaultPort() : routedPort;
+
+ if (!originHost.Equals(routedHost) || (originPort != routedPort)) {
+ mRoutedHost = routedHost;
+ }
+ Init(originHost, originPort, npnToken, username, proxyInfo, originAttributes, true);
+}
+
+void
+nsHttpConnectionInfo::Init(const nsACString &host, int32_t port,
+ const nsACString &npnToken,
+ const nsACString &username,
+ nsProxyInfo* proxyInfo,
+ const NeckoOriginAttributes &originAttributes,
+ bool e2eSSL)
+{
+ LOG(("Init nsHttpConnectionInfo @%p\n", this));
+
+ mUsername = username;
+ mProxyInfo = proxyInfo;
+ mEndToEndSSL = e2eSSL;
+ mUsingConnect = false;
+ mNPNToken = npnToken;
+ mOriginAttributes = originAttributes;
+
+ mUsingHttpsProxy = (proxyInfo && proxyInfo->IsHTTPS());
+ mUsingHttpProxy = mUsingHttpsProxy || (proxyInfo && proxyInfo->IsHTTP());
+
+ if (mUsingHttpProxy) {
+ mUsingConnect = mEndToEndSSL; // SSL always uses CONNECT
+ uint32_t resolveFlags = 0;
+ if (NS_SUCCEEDED(mProxyInfo->GetResolveFlags(&resolveFlags)) &&
+ resolveFlags & nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL) {
+ mUsingConnect = true;
+ }
+ }
+
+ SetOriginServer(host, port);
+}
+
+void
+nsHttpConnectionInfo::SetNetworkInterfaceId(const nsACString& aNetworkInterfaceId)
+{
+ mNetworkInterfaceId = aNetworkInterfaceId;
+ BuildHashKey();
+}
+
+void nsHttpConnectionInfo::BuildHashKey()
+{
+ //
+ // build hash key:
+ //
+ // the hash key uniquely identifies the connection type. two connections
+ // are "equal" if they end up talking the same protocol to the same server
+ // and are both used for anonymous or non-anonymous connection only;
+ // anonymity of the connection is setup later from nsHttpChannel::AsyncOpen
+ // where we know we use anonymous connection (LOAD_ANONYMOUS load flag)
+ //
+
+ const char *keyHost;
+ int32_t keyPort;
+
+ if (mUsingHttpProxy && !mUsingConnect) {
+ keyHost = ProxyHost();
+ keyPort = ProxyPort();
+ } else {
+ keyHost = Origin();
+ keyPort = OriginPort();
+ }
+
+ // The hashkey has 4 fields followed by host connection info
+ // byte 0 is P/T/. {P,T} for Plaintext/TLS Proxy over HTTP
+ // byte 1 is S/. S is for end to end ssl such as https:// uris
+ // byte 2 is A/. A is for an anonymous channel (no cookies, etc..)
+ // byte 3 is P/. P is for a private browising channel
+ // byte 4 is I/. I is for insecure scheme on TLS for http:// uris
+ // byte 5 is X/. X is for disallow_spdy flag
+ // byte 6 is C/. C is for be Conservative
+
+ mHashKey.AssignLiteral(".......");
+ mHashKey.Append(keyHost);
+ if (!mNetworkInterfaceId.IsEmpty()) {
+ mHashKey.Append('(');
+ mHashKey.Append(mNetworkInterfaceId);
+ mHashKey.Append(')');
+ }
+ mHashKey.Append(':');
+ mHashKey.AppendInt(keyPort);
+ if (!mUsername.IsEmpty()) {
+ mHashKey.Append('[');
+ mHashKey.Append(mUsername);
+ mHashKey.Append(']');
+ }
+
+ if (mUsingHttpsProxy) {
+ mHashKey.SetCharAt('T', 0);
+ } else if (mUsingHttpProxy) {
+ mHashKey.SetCharAt('P', 0);
+ }
+ if (mEndToEndSSL) {
+ mHashKey.SetCharAt('S', 1);
+ }
+
+ // NOTE: for transparent proxies (e.g., SOCKS) we need to encode the proxy
+ // info in the hash key (this ensures that we will continue to speak the
+ // right protocol even if our proxy preferences change).
+ //
+ // NOTE: for SSL tunnels add the proxy information to the cache key.
+ // We cannot use the proxy as the host parameter (as we do for non SSL)
+ // because this is a single host tunnel, but we need to include the proxy
+ // information so that a change in proxy config will mean this connection
+ // is not reused
+
+ // NOTE: Adding the username and the password provides a means to isolate
+ // keep-alive to the URL bar domain as well: If the username is the URL bar
+ // domain, keep-alive connections are not reused by resources bound to
+ // different URL bar domains as the respective hash keys are not matching.
+
+ if ((!mUsingHttpProxy && ProxyHost()) ||
+ (mUsingHttpProxy && mUsingConnect)) {
+ mHashKey.AppendLiteral(" (");
+ mHashKey.Append(ProxyType());
+ mHashKey.Append(':');
+ mHashKey.Append(ProxyHost());
+ mHashKey.Append(':');
+ mHashKey.AppendInt(ProxyPort());
+ mHashKey.Append(')');
+ mHashKey.Append('[');
+ mHashKey.Append(ProxyUsername());
+ mHashKey.Append(':');
+ const char* password = ProxyPassword();
+ if (strlen(password) > 0) {
+ nsAutoCString digestedPassword;
+ nsresult rv = SHA256(password, digestedPassword);
+ if (rv == NS_OK) {
+ mHashKey.Append(digestedPassword);
+ }
+ }
+ mHashKey.Append(']');
+ }
+
+ if(!mRoutedHost.IsEmpty()) {
+ mHashKey.AppendLiteral(" <ROUTE-via ");
+ mHashKey.Append(mRoutedHost);
+ mHashKey.Append(':');
+ mHashKey.AppendInt(mRoutedPort);
+ mHashKey.Append('>');
+ }
+
+ if (!mNPNToken.IsEmpty()) {
+ mHashKey.AppendLiteral(" {NPN-TOKEN ");
+ mHashKey.Append(mNPNToken);
+ mHashKey.AppendLiteral("}");
+ }
+
+ nsAutoCString originAttributes;
+ mOriginAttributes.CreateSuffix(originAttributes);
+ mHashKey.Append(originAttributes);
+}
+
+void
+nsHttpConnectionInfo::SetOriginServer(const nsACString &host, int32_t port)
+{
+ mOrigin = host;
+ mOriginPort = port == -1 ? DefaultPort() : port;
+ BuildHashKey();
+}
+
+nsHttpConnectionInfo*
+nsHttpConnectionInfo::Clone() const
+{
+ nsHttpConnectionInfo *clone;
+ if (mRoutedHost.IsEmpty()) {
+ clone = new nsHttpConnectionInfo(mOrigin, mOriginPort, mNPNToken, mUsername, mProxyInfo,
+ mOriginAttributes, mEndToEndSSL);
+ } else {
+ MOZ_ASSERT(mEndToEndSSL);
+ clone = new nsHttpConnectionInfo(mOrigin, mOriginPort, mNPNToken, mUsername, mProxyInfo,
+ mOriginAttributes, mRoutedHost, mRoutedPort);
+ }
+
+ if (!mNetworkInterfaceId.IsEmpty()) {
+ clone->SetNetworkInterfaceId(mNetworkInterfaceId);
+ }
+
+ // Make sure the anonymous, insecure-scheme, and private flags are transferred
+ clone->SetAnonymous(GetAnonymous());
+ clone->SetPrivate(GetPrivate());
+ clone->SetInsecureScheme(GetInsecureScheme());
+ clone->SetNoSpdy(GetNoSpdy());
+ clone->SetBeConservative(GetBeConservative());
+ MOZ_ASSERT(clone->Equals(this));
+
+ return clone;
+}
+
+void
+nsHttpConnectionInfo::CloneAsDirectRoute(nsHttpConnectionInfo **outCI)
+{
+ if (mRoutedHost.IsEmpty()) {
+ *outCI = Clone();
+ return;
+ }
+
+ RefPtr<nsHttpConnectionInfo> clone =
+ new nsHttpConnectionInfo(mOrigin, mOriginPort,
+ EmptyCString(), mUsername, mProxyInfo,
+ mOriginAttributes, mEndToEndSSL);
+ // Make sure the anonymous, insecure-scheme, and private flags are transferred
+ clone->SetAnonymous(GetAnonymous());
+ clone->SetPrivate(GetPrivate());
+ clone->SetInsecureScheme(GetInsecureScheme());
+ clone->SetNoSpdy(GetNoSpdy());
+ clone->SetBeConservative(GetBeConservative());
+ if (!mNetworkInterfaceId.IsEmpty()) {
+ clone->SetNetworkInterfaceId(mNetworkInterfaceId);
+ }
+ clone.forget(outCI);
+}
+
+nsresult
+nsHttpConnectionInfo::CreateWildCard(nsHttpConnectionInfo **outParam)
+{
+ // T???mozilla.org:443 (https:proxy.ducksong.com:3128) [specifc form]
+ // TS??*:0 (https:proxy.ducksong.com:3128) [wildcard form]
+
+ if (!mUsingHttpsProxy) {
+ MOZ_ASSERT(false);
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ RefPtr<nsHttpConnectionInfo> clone;
+ clone = new nsHttpConnectionInfo(NS_LITERAL_CSTRING("*"), 0,
+ mNPNToken, mUsername, mProxyInfo,
+ mOriginAttributes, true);
+ // Make sure the anonymous and private flags are transferred!
+ clone->SetAnonymous(GetAnonymous());
+ clone->SetPrivate(GetPrivate());
+ clone.forget(outParam);
+ return NS_OK;
+}
+
+bool
+nsHttpConnectionInfo::UsingProxy()
+{
+ if (!mProxyInfo)
+ return false;
+ return !mProxyInfo->IsDirect();
+}
+
+bool
+nsHttpConnectionInfo::HostIsLocalIPLiteral() const
+{
+ PRNetAddr prAddr;
+ // If the host/proxy host is not an IP address literal, return false.
+ if (ProxyHost()) {
+ if (PR_StringToNetAddr(ProxyHost(), &prAddr) != PR_SUCCESS) {
+ return false;
+ }
+ } else if (PR_StringToNetAddr(Origin(), &prAddr) != PR_SUCCESS) {
+ return false;
+ }
+ NetAddr netAddr;
+ PRNetAddrToNetAddr(&prAddr, &netAddr);
+ return IsIPAddrLocal(&netAddr);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.h b/netwerk/protocol/http/nsHttpConnectionInfo.h
new file mode 100644
index 000000000..9c5d29d72
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpConnectionInfo_h__
+#define nsHttpConnectionInfo_h__
+
+#include "nsHttp.h"
+#include "nsProxyInfo.h"
+#include "nsCOMPtr.h"
+#include "nsStringFwd.h"
+#include "mozilla/Logging.h"
+#include "mozilla/BasePrincipal.h"
+#include "ARefBase.h"
+
+//-----------------------------------------------------------------------------
+// nsHttpConnectionInfo - holds the properties of a connection
+//-----------------------------------------------------------------------------
+
+// http:// uris through a proxy will all share the same CI, because they can
+// all use the same connection. (modulo pb and anonymous flags). They just use
+// the proxy as the origin host name.
+// however, https:// uris tunnel through the proxy so they will have different
+// CIs - the CI reflects both the proxy and the origin.
+// however, proxy conenctions made with http/2 (or spdy) can tunnel to the origin
+// and multiplex non tunneled transactions at the same time, so they have a
+// special wildcard CI that accepts all origins through that proxy.
+
+namespace mozilla { namespace net {
+
+extern LazyLogModule gHttpLog;
+
+class nsHttpConnectionInfo: public ARefBase
+{
+public:
+ nsHttpConnectionInfo(const nsACString &originHost,
+ int32_t originPort,
+ const nsACString &npnToken,
+ const nsACString &username,
+ nsProxyInfo *proxyInfo,
+ const NeckoOriginAttributes &originAttributes,
+ bool endToEndSSL = false);
+
+ // this version must use TLS and you may supply separate
+ // connection (aka routing) information than the authenticated
+ // origin information
+ nsHttpConnectionInfo(const nsACString &originHost,
+ int32_t originPort,
+ const nsACString &npnToken,
+ const nsACString &username,
+ nsProxyInfo *proxyInfo,
+ const NeckoOriginAttributes &originAttributes,
+ const nsACString &routedHost,
+ int32_t routedPort);
+
+private:
+ virtual ~nsHttpConnectionInfo()
+ {
+ MOZ_LOG(gHttpLog, LogLevel::Debug, ("Destroying nsHttpConnectionInfo @%x\n", this));
+ }
+
+ void BuildHashKey();
+
+public:
+ const nsAFlatCString &HashKey() const { return mHashKey; }
+
+ const nsCString &GetOrigin() const { return mOrigin; }
+ const char *Origin() const { return mOrigin.get(); }
+ int32_t OriginPort() const { return mOriginPort; }
+
+ const nsCString &GetRoutedHost() const { return mRoutedHost; }
+ const char *RoutedHost() const { return mRoutedHost.get(); }
+ int32_t RoutedPort() const { return mRoutedPort; }
+
+ // With overhead rebuilding the hash key. The initial
+ // network interface is empty. So you can reduce one call
+ // if there's no explicit route after ctor.
+ void SetNetworkInterfaceId(const nsACString& aNetworkInterfaceId);
+
+ // OK to treat these as an infalible allocation
+ nsHttpConnectionInfo* Clone() const;
+ void CloneAsDirectRoute(nsHttpConnectionInfo **outParam);
+ nsresult CreateWildCard(nsHttpConnectionInfo **outParam);
+
+ const char *ProxyHost() const { return mProxyInfo ? mProxyInfo->Host().get() : nullptr; }
+ int32_t ProxyPort() const { return mProxyInfo ? mProxyInfo->Port() : -1; }
+ const char *ProxyType() const { return mProxyInfo ? mProxyInfo->Type() : nullptr; }
+ const char *ProxyUsername() const { return mProxyInfo ? mProxyInfo->Username().get() : nullptr; }
+ const char *ProxyPassword() const { return mProxyInfo ? mProxyInfo->Password().get() : nullptr; }
+
+ // Compare this connection info to another...
+ // Two connections are 'equal' if they end up talking the same
+ // protocol to the same server. This is needed to properly manage
+ // persistent connections to proxies
+ // Note that we don't care about transparent proxies -
+ // it doesn't matter if we're talking via socks or not, since
+ // a request will end up at the same host.
+ bool Equals(const nsHttpConnectionInfo *info)
+ {
+ return mHashKey.Equals(info->HashKey());
+ }
+
+ const char *Username() const { return mUsername.get(); }
+ nsProxyInfo *ProxyInfo() const { return mProxyInfo; }
+ int32_t DefaultPort() const { return mEndToEndSSL ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT; }
+ void SetAnonymous(bool anon)
+ { mHashKey.SetCharAt(anon ? 'A' : '.', 2); }
+ bool GetAnonymous() const { return mHashKey.CharAt(2) == 'A'; }
+ void SetPrivate(bool priv) { mHashKey.SetCharAt(priv ? 'P' : '.', 3); }
+ bool GetPrivate() const { return mHashKey.CharAt(3) == 'P'; }
+ void SetInsecureScheme(bool insecureScheme)
+ { mHashKey.SetCharAt(insecureScheme ? 'I' : '.', 4); }
+ bool GetInsecureScheme() const { return mHashKey.CharAt(4) == 'I'; }
+
+ void SetNoSpdy(bool aNoSpdy)
+ { mHashKey.SetCharAt(aNoSpdy ? 'X' : '.', 5); }
+ bool GetNoSpdy() const { return mHashKey.CharAt(5) == 'X'; }
+
+ void SetBeConservative(bool aBeConservative)
+ { mHashKey.SetCharAt(aBeConservative ? 'C' : '.', 6); }
+ bool GetBeConservative() const { return mHashKey.CharAt(6) == 'C'; }
+
+ const nsCString &GetNetworkInterfaceId() const { return mNetworkInterfaceId; }
+
+ const nsCString &GetNPNToken() { return mNPNToken; }
+ const nsCString &GetUsername() { return mUsername; }
+
+ const NeckoOriginAttributes &GetOriginAttributes() { return mOriginAttributes; }
+
+ // Returns true for any kind of proxy (http, socks, https, etc..)
+ bool UsingProxy();
+
+ // Returns true when proxying over HTTP or HTTPS
+ bool UsingHttpProxy() const { return mUsingHttpProxy || mUsingHttpsProxy; }
+
+ // Returns true when proxying over HTTPS
+ bool UsingHttpsProxy() const { return mUsingHttpsProxy; }
+
+ // Returns true when a resource is in SSL end to end (e.g. https:// uri)
+ bool EndToEndSSL() const { return mEndToEndSSL; }
+
+ // Returns true when at least first hop is SSL (e.g. proxy over https or https uri)
+ bool FirstHopSSL() const { return mEndToEndSSL || mUsingHttpsProxy; }
+
+ // Returns true when CONNECT is used to tunnel through the proxy (e.g. https:// or ws://)
+ bool UsingConnect() const { return mUsingConnect; }
+
+ // Returns true when origin/proxy is an RFC1918 literal.
+ bool HostIsLocalIPLiteral() const;
+
+private:
+ void Init(const nsACString &host,
+ int32_t port,
+ const nsACString &npnToken,
+ const nsACString &username,
+ nsProxyInfo* proxyInfo,
+ const NeckoOriginAttributes &originAttributes,
+ bool EndToEndSSL);
+ void SetOriginServer(const nsACString &host, int32_t port);
+
+ nsCString mOrigin;
+ int32_t mOriginPort;
+ nsCString mRoutedHost;
+ int32_t mRoutedPort;
+
+ nsCString mHashKey;
+ nsCString mNetworkInterfaceId;
+ nsCString mUsername;
+ nsCOMPtr<nsProxyInfo> mProxyInfo;
+ bool mUsingHttpProxy;
+ bool mUsingHttpsProxy;
+ bool mEndToEndSSL;
+ bool mUsingConnect; // if will use CONNECT with http proxy
+ nsCString mNPNToken;
+ NeckoOriginAttributes mOriginAttributes;
+
+// for RefPtr
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpConnectionInfo)
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpConnectionInfo_h__
diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
new file mode 100644
index 000000000..abae51e2f
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -0,0 +1,4006 @@
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+// Log on level :5, instead of default :4.
+#undef LOG
+#define LOG(args) LOG5(args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() LOG5_ENABLED()
+
+#include "nsHttpConnectionMgr.h"
+#include "nsHttpConnection.h"
+#include "nsHttpPipeline.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsNetCID.h"
+#include "nsCOMPtr.h"
+#include "nsNetUtil.h"
+#include "mozilla/net/DNS.h"
+#include "nsISocketTransport.h"
+#include "nsISSLSocketControl.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "NullHttpTransaction.h"
+#include "nsIDNSRecord.h"
+#include "nsITransport.h"
+#include "nsInterfaceRequestorAgg.h"
+#include "nsIRequestContext.h"
+#include "nsISocketTransportService.h"
+#include <algorithm>
+#include "mozilla/ChaosMode.h"
+#include "mozilla/Unused.h"
+#include "nsIURI.h"
+
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpConnectionMgr, nsIObserver)
+
+static void
+InsertTransactionSorted(nsTArray<RefPtr<nsHttpTransaction> > &pendingQ, nsHttpTransaction *trans)
+{
+ // insert into queue with smallest valued number first. search in reverse
+ // order under the assumption that many of the existing transactions will
+ // have the same priority (usually 0).
+
+ for (int32_t i = pendingQ.Length() - 1; i >= 0; --i) {
+ nsHttpTransaction *t = pendingQ[i];
+ if (trans->Priority() >= t->Priority()) {
+ if (ChaosMode::isActive(ChaosFeature::NetworkScheduling)) {
+ int32_t samePriorityCount;
+ for (samePriorityCount = 0; i - samePriorityCount >= 0; ++samePriorityCount) {
+ if (pendingQ[i - samePriorityCount]->Priority() != trans->Priority()) {
+ break;
+ }
+ }
+ // skip over 0...all of the elements with the same priority.
+ i -= ChaosMode::randomUint32LessThan(samePriorityCount + 1);
+ }
+ pendingQ.InsertElementAt(i+1, trans);
+ return;
+ }
+ }
+ pendingQ.InsertElementAt(0, trans);
+}
+
+//-----------------------------------------------------------------------------
+
+nsHttpConnectionMgr::nsHttpConnectionMgr()
+ : mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor")
+ , mMaxConns(0)
+ , mMaxPersistConnsPerHost(0)
+ , mMaxPersistConnsPerProxy(0)
+ , mIsShuttingDown(false)
+ , mNumActiveConns(0)
+ , mNumIdleConns(0)
+ , mNumSpdyActiveConns(0)
+ , mNumHalfOpenConns(0)
+ , mTimeOfNextWakeUp(UINT64_MAX)
+ , mPruningNoTraffic(false)
+ , mTimeoutTickArmed(false)
+ , mTimeoutTickNext(1)
+{
+ LOG(("Creating nsHttpConnectionMgr @%p\n", this));
+}
+
+nsHttpConnectionMgr::~nsHttpConnectionMgr()
+{
+ LOG(("Destroying nsHttpConnectionMgr @%p\n", this));
+ if (mTimeoutTick)
+ mTimeoutTick->Cancel();
+}
+
+nsresult
+nsHttpConnectionMgr::EnsureSocketThreadTarget()
+{
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_SUCCEEDED(rv))
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // do nothing if already initialized or if we've shut down
+ if (mSocketThreadTarget || mIsShuttingDown)
+ return NS_OK;
+
+ mSocketThreadTarget = sts;
+
+ return rv;
+}
+
+nsresult
+nsHttpConnectionMgr::Init(uint16_t maxConns,
+ uint16_t maxPersistConnsPerHost,
+ uint16_t maxPersistConnsPerProxy,
+ uint16_t maxRequestDelay,
+ uint16_t maxPipelinedRequests,
+ uint16_t maxOptimisticPipelinedRequests)
+{
+ LOG(("nsHttpConnectionMgr::Init\n"));
+
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ mMaxConns = maxConns;
+ mMaxPersistConnsPerHost = maxPersistConnsPerHost;
+ mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
+ mMaxRequestDelay = maxRequestDelay;
+ mMaxPipelinedRequests = maxPipelinedRequests;
+ mMaxOptimisticPipelinedRequests = maxOptimisticPipelinedRequests;
+
+ mIsShuttingDown = false;
+ }
+
+ return EnsureSocketThreadTarget();
+}
+
+class BoolWrapper : public ARefBase
+{
+public:
+ BoolWrapper() : mBool(false) {}
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BoolWrapper)
+
+public: // intentional!
+ bool mBool;
+
+private:
+ virtual ~BoolWrapper() {}
+};
+
+nsresult
+nsHttpConnectionMgr::Shutdown()
+{
+ LOG(("nsHttpConnectionMgr::Shutdown\n"));
+
+ RefPtr<BoolWrapper> shutdownWrapper = new BoolWrapper();
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // do nothing if already shutdown
+ if (!mSocketThreadTarget)
+ return NS_OK;
+
+ nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgShutdown,
+ 0, shutdownWrapper);
+
+ // release our reference to the STS to prevent further events
+ // from being posted. this is how we indicate that we are
+ // shutting down.
+ mIsShuttingDown = true;
+ mSocketThreadTarget = nullptr;
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to post SHUTDOWN message");
+ return rv;
+ }
+ }
+
+ // wait for shutdown event to complete
+ while (!shutdownWrapper->mBool) {
+ NS_ProcessNextEvent(NS_GetCurrentThread());
+ }
+
+ return NS_OK;
+}
+
+class ConnEvent : public Runnable
+{
+public:
+ ConnEvent(nsHttpConnectionMgr *mgr,
+ nsConnEventHandler handler, int32_t iparam, ARefBase *vparam)
+ : mMgr(mgr)
+ , mHandler(handler)
+ , mIParam(iparam)
+ , mVParam(vparam) {}
+
+ NS_IMETHOD Run() override
+ {
+ (mMgr->*mHandler)(mIParam, mVParam);
+ return NS_OK;
+ }
+
+private:
+ virtual ~ConnEvent() {}
+
+ RefPtr<nsHttpConnectionMgr> mMgr;
+ nsConnEventHandler mHandler;
+ int32_t mIParam;
+ RefPtr<ARefBase> mVParam;
+};
+
+nsresult
+nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler,
+ int32_t iparam, ARefBase *vparam)
+{
+ EnsureSocketThreadTarget();
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ nsresult rv;
+ if (!mSocketThreadTarget) {
+ NS_WARNING("cannot post event if not initialized");
+ rv = NS_ERROR_NOT_INITIALIZED;
+ }
+ else {
+ nsCOMPtr<nsIRunnable> event = new ConnEvent(this, handler, iparam, vparam);
+ rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ return rv;
+}
+
+void
+nsHttpConnectionMgr::PruneDeadConnectionsAfter(uint32_t timeInSeconds)
+{
+ LOG(("nsHttpConnectionMgr::PruneDeadConnectionsAfter\n"));
+
+ if(!mTimer)
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+
+ // failure to create a timer is not a fatal error, but idle connections
+ // will not be cleaned up until we try to use them.
+ if (mTimer) {
+ mTimeOfNextWakeUp = timeInSeconds + NowInSeconds();
+ mTimer->Init(this, timeInSeconds*1000, nsITimer::TYPE_ONE_SHOT);
+ } else {
+ NS_WARNING("failed to create: timer for pruning the dead connections!");
+ }
+}
+
+void
+nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer()
+{
+ // Leave the timer in place if there are connections that potentially
+ // need management
+ if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled()))
+ return;
+
+ LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n"));
+
+ // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
+ mTimeOfNextWakeUp = UINT64_MAX;
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+void
+nsHttpConnectionMgr::ConditionallyStopTimeoutTick()
+{
+ LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick "
+ "armed=%d active=%d\n", mTimeoutTickArmed, mNumActiveConns));
+
+ if (!mTimeoutTickArmed)
+ return;
+
+ if (mNumActiveConns)
+ return;
+
+ LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick stop==true\n"));
+
+ mTimeoutTick->Cancel();
+ mTimeoutTickArmed = false;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnectionMgr::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpConnectionMgr::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic));
+
+ if (0 == strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) {
+ nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
+ if (timer == mTimer) {
+ PruneDeadConnections();
+ }
+ else if (timer == mTimeoutTick) {
+ TimeoutTick();
+ } else if (timer == mTrafficTimer) {
+ PruneNoTraffic();
+ }
+ else {
+ MOZ_ASSERT(false, "unexpected timer-callback");
+ LOG(("Unexpected timer object\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpConnectionMgr::AddTransaction(nsHttpTransaction *trans, int32_t priority)
+{
+ LOG(("nsHttpConnectionMgr::AddTransaction [trans=%p %d]\n", trans, priority));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority, trans);
+}
+
+nsresult
+nsHttpConnectionMgr::RescheduleTransaction(nsHttpTransaction *trans, int32_t priority)
+{
+ LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans, priority));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority, trans);
+}
+
+nsresult
+nsHttpConnectionMgr::CancelTransaction(nsHttpTransaction *trans, nsresult reason)
+{
+ LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%x]\n", trans, reason));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
+ static_cast<int32_t>(reason), trans);
+}
+
+nsresult
+nsHttpConnectionMgr::PruneDeadConnections()
+{
+ return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections);
+}
+
+//
+// Called after a timeout. Check for active connections that have had no
+// traffic since they were "marked" and nuke them.
+nsresult
+nsHttpConnectionMgr::PruneNoTraffic()
+{
+ LOG(("nsHttpConnectionMgr::PruneNoTraffic\n"));
+ mPruningNoTraffic = true;
+ return PostEvent(&nsHttpConnectionMgr::OnMsgPruneNoTraffic);
+}
+
+nsresult
+nsHttpConnectionMgr::VerifyTraffic()
+{
+ LOG(("nsHttpConnectionMgr::VerifyTraffic\n"));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgVerifyTraffic);
+}
+
+nsresult
+nsHttpConnectionMgr::DoShiftReloadConnectionCleanup(nsHttpConnectionInfo *aCI)
+{
+ return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup,
+ 0, aCI);
+}
+
+class SpeculativeConnectArgs : public ARefBase
+{
+public:
+ SpeculativeConnectArgs() { mOverridesOK = false; }
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs)
+
+public: // intentional!
+ RefPtr<NullHttpTransaction> mTrans;
+
+ bool mOverridesOK;
+ uint32_t mParallelSpeculativeConnectLimit;
+ bool mIgnoreIdle;
+ bool mIsFromPredictor;
+ bool mAllow1918;
+
+private:
+ virtual ~SpeculativeConnectArgs() {}
+ NS_DECL_OWNINGTHREAD
+};
+
+nsresult
+nsHttpConnectionMgr::SpeculativeConnect(nsHttpConnectionInfo *ci,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps,
+ NullHttpTransaction *nullTransaction)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "nsHttpConnectionMgr::SpeculativeConnect called off main thread!");
+
+ if (!IsNeckoChild()) {
+ // HACK: make sure PSM gets initialized on the main thread.
+ net_EnsurePSMInit();
+ }
+
+ LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n",
+ ci->HashKey().get()));
+
+ nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
+ do_GetInterface(callbacks);
+
+ bool allow1918 = overrider ? overrider->GetAllow1918() : false;
+
+ // Hosts that are Local IP Literals should not be speculatively
+ // connected - Bug 853423.
+ if ((!allow1918) && ci && ci->HostIsLocalIPLiteral()) {
+ LOG(("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 "
+ "address [%s]", ci->Origin()));
+ return NS_OK;
+ }
+
+ RefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs();
+
+ // Wrap up the callbacks and the target to ensure they're released on the target
+ // thread properly.
+ nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks;
+ NS_NewInterfaceRequestorAggregation(callbacks, nullptr, getter_AddRefs(wrappedCallbacks));
+
+ caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
+ caps |= NS_HTTP_ERROR_SOFTLY;
+ args->mTrans =
+ nullTransaction ? nullTransaction : new NullHttpTransaction(ci, wrappedCallbacks, caps);
+
+ if (overrider) {
+ args->mOverridesOK = true;
+ args->mParallelSpeculativeConnectLimit =
+ overrider->GetParallelSpeculativeConnectLimit();
+ args->mIgnoreIdle = overrider->GetIgnoreIdle();
+ args->mIsFromPredictor = overrider->GetIsFromPredictor();
+ args->mAllow1918 = overrider->GetAllow1918();
+ }
+
+ return PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
+}
+
+nsresult
+nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target)
+{
+ EnsureSocketThreadTarget();
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ nsCOMPtr<nsIEventTarget> temp(mSocketThreadTarget);
+ temp.forget(target);
+ return NS_OK;
+}
+
+nsresult
+nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn)
+{
+ LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%p]\n", conn));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgReclaimConnection, 0, conn);
+}
+
+// A structure used to marshall 2 pointers across the various necessary
+// threads to complete an HTTP upgrade.
+class nsCompleteUpgradeData : public ARefBase
+{
+public:
+ nsCompleteUpgradeData(nsAHttpConnection *aConn,
+ nsIHttpUpgradeListener *aListener)
+ : mConn(aConn)
+ , mUpgradeListener(aListener) { }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCompleteUpgradeData)
+
+ RefPtr<nsAHttpConnection> mConn;
+ nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener;
+private:
+ virtual ~nsCompleteUpgradeData() { }
+};
+
+nsresult
+nsHttpConnectionMgr::CompleteUpgrade(nsAHttpConnection *aConn,
+ nsIHttpUpgradeListener *aUpgradeListener)
+{
+ RefPtr<nsCompleteUpgradeData> data =
+ new nsCompleteUpgradeData(aConn, aUpgradeListener);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data);
+}
+
+nsresult
+nsHttpConnectionMgr::UpdateParam(nsParamName name, uint16_t value)
+{
+ uint32_t param = (uint32_t(name) << 16) | uint32_t(value);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam,
+ static_cast<int32_t>(param), nullptr);
+}
+
+nsresult
+nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo *ci)
+{
+ LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", ci->HashKey().get()));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci);
+}
+
+nsresult
+nsHttpConnectionMgr::ProcessPendingQ()
+{
+ LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n"));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr);
+}
+
+void
+nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t, ARefBase *param)
+{
+ EventTokenBucket *tokenBucket = static_cast<EventTokenBucket *>(param);
+ gHttpHandler->SetRequestTokenBucket(tokenBucket);
+}
+
+nsresult
+nsHttpConnectionMgr::UpdateRequestTokenBucket(EventTokenBucket *aBucket)
+{
+ // Call From main thread when a new EventTokenBucket has been made in order
+ // to post the new value to the socket thread.
+ return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket,
+ 0, aBucket);
+}
+
+nsresult
+nsHttpConnectionMgr::ClearConnectionHistory()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+ if (ent->mIdleConns.Length() == 0 &&
+ ent->mActiveConns.Length() == 0 &&
+ ent->mHalfOpens.Length() == 0 &&
+ ent->mPendingQ.Length() == 0) {
+ iter.Remove();
+ }
+ }
+
+ return NS_OK;
+}
+
+
+nsHttpConnectionMgr::nsConnectionEntry *
+nsHttpConnectionMgr::LookupPreferredHash(nsHttpConnectionMgr::nsConnectionEntry *ent)
+{
+ nsConnectionEntry *preferred = nullptr;
+ uint32_t len = ent->mCoalescingKeys.Length();
+ for (uint32_t i = 0; !preferred && (i < len); ++i) {
+ preferred = mSpdyPreferredHash.Get(ent->mCoalescingKeys[i]);
+ }
+ return preferred;
+}
+
+void
+nsHttpConnectionMgr::StorePreferredHash(nsHttpConnectionMgr::nsConnectionEntry *ent)
+{
+ if (ent->mCoalescingKeys.IsEmpty()) {
+ return;
+ }
+
+ ent->mInPreferredHash = true;
+ uint32_t len = ent->mCoalescingKeys.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ mSpdyPreferredHash.Put(ent->mCoalescingKeys[i], ent);
+ }
+}
+
+void
+nsHttpConnectionMgr::RemovePreferredHash(nsHttpConnectionMgr::nsConnectionEntry *ent)
+{
+ if (!ent->mInPreferredHash || ent->mCoalescingKeys.IsEmpty()) {
+ return;
+ }
+
+ ent->mInPreferredHash = false;
+ uint32_t len = ent->mCoalescingKeys.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ mSpdyPreferredHash.Remove(ent->mCoalescingKeys[i]);
+ }
+}
+
+// Given a nsHttpConnectionInfo find the connection entry object that
+// contains either the nshttpconnection or nshttptransaction parameter.
+// Normally this is done by the hashkey lookup of connectioninfo,
+// but if spdy coalescing is in play it might be found in a redirected
+// entry
+nsHttpConnectionMgr::nsConnectionEntry *
+nsHttpConnectionMgr::LookupConnectionEntry(nsHttpConnectionInfo *ci,
+ nsHttpConnection *conn,
+ nsHttpTransaction *trans)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (!ci)
+ return nullptr;
+
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+
+ // If there is no sign of coalescing (or it is disabled) then just
+ // return the primary hash lookup
+ if (!ent || !ent->mUsingSpdy || ent->mCoalescingKeys.IsEmpty())
+ return ent;
+
+ // If there is no preferred coalescing entry for this host (or the
+ // preferred entry is the one that matched the mCT hash lookup) then
+ // there is only option
+ nsConnectionEntry *preferred = LookupPreferredHash(ent);
+ if (!preferred || (preferred == ent))
+ return ent;
+
+ if (conn) {
+ // The connection could be either in preferred or ent. It is most
+ // likely the only active connection in preferred - so start with that.
+ if (preferred->mActiveConns.Contains(conn))
+ return preferred;
+ if (preferred->mIdleConns.Contains(conn))
+ return preferred;
+ }
+
+ if (trans && preferred->mPendingQ.Contains(trans))
+ return preferred;
+
+ // Neither conn nor trans found in preferred, use the default entry
+ return ent;
+}
+
+nsresult
+nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection *conn)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p",
+ this, conn));
+
+ if (!conn->ConnectionInfo())
+ return NS_ERROR_UNEXPECTED;
+
+ nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
+ conn, nullptr);
+
+ RefPtr<nsHttpConnection> deleteProtector(conn);
+ if (!ent || !ent->mIdleConns.RemoveElement(conn))
+ return NS_ERROR_UNEXPECTED;
+
+ conn->Close(NS_ERROR_ABORT);
+ mNumIdleConns--;
+ ConditionallyStopPruneDeadConnectionsTimer();
+ return NS_OK;
+}
+
+// This function lets a connection, after completing the NPN phase,
+// report whether or not it is using spdy through the usingSpdy
+// argument. It would not be necessary if NPN were driven out of
+// the connection manager. The connection entry associated with the
+// connection is then updated to indicate whether or not we want to use
+// spdy with that host and update the preliminary preferred host
+// entries used for de-sharding hostsnames.
+void
+nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn,
+ bool usingSpdy)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
+ conn, nullptr);
+
+ if (!ent)
+ return;
+
+ if (!usingSpdy)
+ return;
+
+ ent->mUsingSpdy = true;
+ mNumSpdyActiveConns++;
+
+ uint32_t ttl = conn->TimeToLive();
+ uint64_t timeOfExpire = NowInSeconds() + ttl;
+ if (!mTimer || timeOfExpire < mTimeOfNextWakeUp)
+ PruneDeadConnectionsAfter(ttl);
+
+ // Lookup preferred directly from the hash instead of using
+ // GetSpdyPreferredEnt() because we want to avoid the cert compatibility
+ // check at this point because the cert is never part of the hash
+ // lookup. Filtering on that has to be done at the time of use
+ // rather than the time of registration (i.e. now).
+ nsConnectionEntry *joinedConnection;
+ nsConnectionEntry *preferred = LookupPreferredHash(ent);
+
+ LOG(("ReportSpdyConnection %p,%s conn %p prefers %p,%s\n",
+ ent, ent->mConnInfo->Origin(), conn, preferred,
+ preferred ? preferred->mConnInfo->Origin() : ""));
+
+ if (!preferred) {
+ // this becomes the preferred entry
+ StorePreferredHash(ent);
+ preferred = ent;
+ } else if ((preferred != ent) &&
+ (joinedConnection = GetSpdyPreferredEnt(ent)) &&
+ (joinedConnection != ent)) {
+ //
+ // A connection entry (e.g. made with a different hostname) with
+ // the same IP address is preferred for future transactions over this
+ // connection entry. Gracefully close down the connection to help
+ // new transactions migrate over.
+
+ LOG(("ReportSpdyConnection graceful close of conn=%p ent=%p to "
+ "migrate to preferred (desharding)\n", conn, ent));
+ conn->DontReuse();
+ } else if (preferred != ent) {
+ LOG (("ReportSpdyConnection preferred host may be in false start or "
+ "may have insufficient cert. Leave mapping in place but do not "
+ "abandon this connection yet."));
+ }
+
+ if ((preferred == ent) && conn->CanDirectlyActivate()) {
+ // this is a new spdy connection to the preferred entry
+
+ // Cancel any other pending connections - their associated transactions
+ // are in the pending queue and will be dispatched onto this connection
+ for (int32_t index = ent->mHalfOpens.Length() - 1;
+ index >= 0; --index) {
+ LOG(("ReportSpdyConnection forcing halfopen abandon %p\n",
+ ent->mHalfOpens[index]));
+ ent->mHalfOpens[index]->Abandon();
+ }
+
+ if (ent->mActiveConns.Length() > 1) {
+ // this is a new connection to an established preferred spdy host.
+ // if there is more than 1 live and established spdy connection (e.g.
+ // some could still be handshaking, shutting down, etc..) then close
+ // this one down after any transactions that are on it are complete.
+ // This probably happened due to the parallel connection algorithm
+ // that is used only before the host is known to speak spdy.
+ for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
+ nsHttpConnection *otherConn = ent->mActiveConns[index];
+ if (otherConn != conn) {
+ LOG(("ReportSpdyConnection shutting down connection (%p) because new "
+ "spdy connection (%p) takes precedence\n", otherConn, conn));
+ otherConn->DontReuse();
+ }
+ }
+ }
+ }
+
+ ProcessPendingQ(ent->mConnInfo);
+ PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
+}
+
+nsHttpConnectionMgr::nsConnectionEntry *
+nsHttpConnectionMgr::GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry)
+{
+ if (!gHttpHandler->IsSpdyEnabled() ||
+ !gHttpHandler->CoalesceSpdy() ||
+ aOriginalEntry->mConnInfo->GetNoSpdy() ||
+ aOriginalEntry->mCoalescingKeys.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsConnectionEntry *preferred = LookupPreferredHash(aOriginalEntry);
+
+ // if there is no redirection no cert validation is required
+ if (preferred == aOriginalEntry)
+ return aOriginalEntry;
+
+ // if there is no preferred host or it is no longer using spdy
+ // then skip pooling
+ if (!preferred || !preferred->mUsingSpdy)
+ return nullptr;
+
+ // if there is not an active spdy session in this entry then
+ // we cannot pool because the cert upon activation may not
+ // be the same as the old one. Active sessions are prohibited
+ // from changing certs.
+
+ nsHttpConnection *activeSpdy = nullptr;
+
+ for (uint32_t index = 0; index < preferred->mActiveConns.Length(); ++index) {
+ if (preferred->mActiveConns[index]->CanDirectlyActivate()) {
+ activeSpdy = preferred->mActiveConns[index];
+ break;
+ }
+ }
+
+ if (!activeSpdy) {
+ // remove the preferred status of this entry if it cannot be
+ // used for pooling.
+ RemovePreferredHash(preferred);
+ LOG(("nsHttpConnectionMgr::GetSpdyPreferredEnt "
+ "preferred host mapping %s to %s removed due to inactivity.\n",
+ aOriginalEntry->mConnInfo->Origin(),
+ preferred->mConnInfo->Origin()));
+
+ return nullptr;
+ }
+
+ // Check that the server cert supports redirection
+ nsresult rv;
+ bool isJoined = false;
+
+ nsCOMPtr<nsISupports> securityInfo;
+ nsCOMPtr<nsISSLSocketControl> sslSocketControl;
+ nsAutoCString negotiatedNPN;
+
+ activeSpdy->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (!securityInfo) {
+ NS_WARNING("cannot obtain spdy security info");
+ return nullptr;
+ }
+
+ sslSocketControl = do_QueryInterface(securityInfo, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("sslSocketControl QI Failed");
+ return nullptr;
+ }
+
+ // try all the spdy versions we support.
+ const SpdyInformation *info = gHttpHandler->SpdyInfo();
+ for (uint32_t index = SpdyInformation::kCount;
+ NS_SUCCEEDED(rv) && index > 0; --index) {
+ if (info->ProtocolEnabled(index - 1)) {
+ rv = sslSocketControl->JoinConnection(info->VersionString[index - 1],
+ aOriginalEntry->mConnInfo->GetOrigin(),
+ aOriginalEntry->mConnInfo->OriginPort(),
+ &isJoined);
+ if (NS_SUCCEEDED(rv) && isJoined) {
+ break;
+ }
+ }
+ }
+
+ if (NS_FAILED(rv) || !isJoined) {
+ LOG(("nsHttpConnectionMgr::GetSpdyPreferredEnt "
+ "Host %s cannot be confirmed to be joined "
+ "with %s connections. rv=%x isJoined=%d",
+ preferred->mConnInfo->Origin(), aOriginalEntry->mConnInfo->Origin(),
+ rv, isJoined));
+ Telemetry::Accumulate(Telemetry::SPDY_NPN_JOIN, false);
+ return nullptr;
+ }
+
+ // IP pooling confirmed
+ LOG(("nsHttpConnectionMgr::GetSpdyPreferredEnt "
+ "Host %s has cert valid for %s connections, "
+ "so %s will be coalesced with %s",
+ preferred->mConnInfo->Origin(), aOriginalEntry->mConnInfo->Origin(),
+ aOriginalEntry->mConnInfo->Origin(), preferred->mConnInfo->Origin()));
+ Telemetry::Accumulate(Telemetry::SPDY_NPN_JOIN, true);
+ return preferred;
+}
+
+//-----------------------------------------------------------------------------
+
+bool
+nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool considerAll)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry "
+ "[ci=%s ent=%p active=%d idle=%d queued=%d]\n",
+ ent->mConnInfo->HashKey().get(), ent, ent->mActiveConns.Length(),
+ ent->mIdleConns.Length(), ent->mPendingQ.Length()));
+
+ ProcessSpdyPendingQ(ent);
+
+ nsHttpTransaction *trans;
+ nsresult rv;
+ bool dispatchedSuccessfully = false;
+
+ // if !considerAll iterate the pending list until one is dispatched successfully.
+ // Keep iterating afterwards only until a transaction fails to dispatch.
+ // if considerAll == true then try and dispatch all items.
+ for (uint32_t i = 0; i < ent->mPendingQ.Length(); ) {
+ trans = ent->mPendingQ[i];
+
+ // When this transaction has already established a half-open
+ // connection, we want to prevent any duplicate half-open
+ // connections from being established and bound to this
+ // transaction. Allow only use of an idle persistent connection
+ // (if found) for transactions referred by a half-open connection.
+ bool alreadyHalfOpen = false;
+ for (int32_t j = 0; j < ((int32_t) ent->mHalfOpens.Length()); ++j) {
+ if (ent->mHalfOpens[j]->Transaction() == trans) {
+ alreadyHalfOpen = true;
+ break;
+ }
+ }
+
+ rv = TryDispatchTransaction(ent,
+ alreadyHalfOpen || !!trans->TunnelProvider(),
+ trans);
+ if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE)) {
+ if (NS_SUCCEEDED(rv))
+ LOG((" dispatching pending transaction...\n"));
+ else
+ LOG((" removing pending transaction based on "
+ "TryDispatchTransaction returning hard error %x\n", rv));
+
+ if (ent->mPendingQ.RemoveElement(trans)) {
+ // trans is now potentially destroyed
+ dispatchedSuccessfully = true;
+ continue; // dont ++i as we just made the array shorter
+ }
+
+ LOG((" transaction not found in pending queue\n"));
+ }
+
+ if (dispatchedSuccessfully && !considerAll)
+ break;
+
+ ++i;
+ }
+ return dispatchedSuccessfully;
+}
+
+bool
+nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo *ci)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ if (ent)
+ return ProcessPendingQForEntry(ent, false);
+ return false;
+}
+
+bool
+nsHttpConnectionMgr::SupportsPipelining(nsHttpConnectionInfo *ci)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ if (ent)
+ return ent->SupportsPipelining();
+ return false;
+}
+
+// nsHttpPipelineFeedback used to hold references across events
+
+class nsHttpPipelineFeedback : public ARefBase
+{
+public:
+ nsHttpPipelineFeedback(nsHttpConnectionInfo *ci,
+ nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+ nsHttpConnection *conn, uint32_t data)
+ : mConnInfo(ci)
+ , mConn(conn)
+ , mInfo(info)
+ , mData(data)
+ {
+ }
+
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ RefPtr<nsHttpConnection> mConn;
+ nsHttpConnectionMgr::PipelineFeedbackInfoType mInfo;
+ uint32_t mData;
+private:
+ ~nsHttpPipelineFeedback() {}
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpPipelineFeedback)
+};
+
+void
+nsHttpConnectionMgr::PipelineFeedbackInfo(nsHttpConnectionInfo *ci,
+ PipelineFeedbackInfoType info,
+ nsHttpConnection *conn,
+ uint32_t data)
+{
+ if (!ci)
+ return;
+
+ // Post this to the socket thread if we are not running there already
+ if (PR_GetCurrentThread() != gSocketThread) {
+ RefPtr<nsHttpPipelineFeedback> fb =
+ new nsHttpPipelineFeedback(ci, info, conn, data);
+ PostEvent(&nsHttpConnectionMgr::OnMsgProcessFeedback, 0, fb);
+ return;
+ }
+
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ if (ent)
+ ent->OnPipelineFeedbackInfo(info, conn, data);
+}
+
+void
+nsHttpConnectionMgr::ReportFailedToProcess(nsIURI *uri)
+{
+ MOZ_ASSERT(uri);
+
+ nsAutoCString host;
+ int32_t port = -1;
+ nsAutoCString username;
+ bool usingSSL = false;
+ bool isHttp = false;
+
+ nsresult rv = uri->SchemeIs("https", &usingSSL);
+ if (NS_SUCCEEDED(rv) && usingSSL)
+ isHttp = true;
+ if (NS_SUCCEEDED(rv) && !isHttp)
+ rv = uri->SchemeIs("http", &isHttp);
+ if (NS_SUCCEEDED(rv))
+ rv = uri->GetAsciiHost(host);
+ if (NS_SUCCEEDED(rv))
+ rv = uri->GetPort(&port);
+ if (NS_SUCCEEDED(rv))
+ uri->GetUsername(username);
+ if (NS_FAILED(rv) || !isHttp || host.IsEmpty())
+ return;
+
+ // report the event for all the permutations of anonymous and
+ // private versions of this host
+ RefPtr<nsHttpConnectionInfo> ci =
+ new nsHttpConnectionInfo(host, port, EmptyCString(), username, nullptr,
+ NeckoOriginAttributes(), usingSSL);
+ ci->SetAnonymous(false);
+ ci->SetPrivate(false);
+ PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+
+ ci = ci->Clone();
+ ci->SetAnonymous(false);
+ ci->SetPrivate(true);
+ PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+
+ ci = ci->Clone();
+ ci->SetAnonymous(true);
+ ci->SetPrivate(false);
+ PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+
+ ci = ci->Clone();
+ ci->SetAnonymous(true);
+ ci->SetPrivate(true);
+ PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+}
+
+// we're at the active connection limit if any one of the following conditions is true:
+// (1) at max-connections
+// (2) keep-alive enabled and at max-persistent-connections-per-server/proxy
+// (3) keep-alive disabled and at max-connections-per-server
+bool
+nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, uint32_t caps)
+{
+ nsHttpConnectionInfo *ci = ent->mConnInfo;
+
+ LOG(("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x]\n",
+ ci->HashKey().get(), caps));
+
+ // update maxconns if potentially limited by the max socket count
+ // this requires a dynamic reduction in the max socket count to a point
+ // lower than the max-connections pref.
+ uint32_t maxSocketCount = gHttpHandler->MaxSocketCount();
+ if (mMaxConns > maxSocketCount) {
+ mMaxConns = maxSocketCount;
+ LOG(("nsHttpConnectionMgr %p mMaxConns dynamically reduced to %u",
+ this, mMaxConns));
+ }
+
+ // If there are more active connections than the global limit, then we're
+ // done. Purging idle connections won't get us below it.
+ if (mNumActiveConns >= mMaxConns) {
+ LOG((" num active conns == max conns\n"));
+ return true;
+ }
+
+ // Add in the in-progress tcp connections, we will assume they are
+ // keepalive enabled.
+ // Exclude half-open's that has already created a usable connection.
+ // This prevents the limit being stuck on ipv6 connections that
+ // eventually time out after typical 21 seconds of no ACK+SYN reply.
+ uint32_t totalCount =
+ ent->mActiveConns.Length() + ent->UnconnectedHalfOpens();
+
+ uint16_t maxPersistConns;
+
+ if (ci->UsingHttpProxy() && !ci->UsingConnect())
+ maxPersistConns = mMaxPersistConnsPerProxy;
+ else
+ maxPersistConns = mMaxPersistConnsPerHost;
+
+ LOG((" connection count = %d, limit %d\n", totalCount, maxPersistConns));
+
+ // use >= just to be safe
+ bool result = (totalCount >= maxPersistConns);
+ LOG((" result: %s", result ? "true" : "false"));
+ return result;
+}
+
+void
+nsHttpConnectionMgr::ClosePersistentConnections(nsConnectionEntry *ent)
+{
+ LOG(("nsHttpConnectionMgr::ClosePersistentConnections [ci=%s]\n",
+ ent->mConnInfo->HashKey().get()));
+ while (ent->mIdleConns.Length()) {
+ RefPtr<nsHttpConnection> conn(ent->mIdleConns[0]);
+ ent->mIdleConns.RemoveElementAt(0);
+ mNumIdleConns--;
+ conn->Close(NS_ERROR_ABORT);
+ }
+
+ int32_t activeCount = ent->mActiveConns.Length();
+ for (int32_t i=0; i < activeCount; i++)
+ ent->mActiveConns[i]->DontReuse();
+}
+
+bool
+nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry *ent)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // If this host is trying to negotiate a SPDY session right now,
+ // don't create any new ssl connections until the result of the
+ // negotiation is known.
+
+ bool doRestrict =
+ ent->mConnInfo->FirstHopSSL() && gHttpHandler->IsSpdyEnabled() &&
+ ent->mUsingSpdy && (ent->mHalfOpens.Length() || ent->mActiveConns.Length());
+
+ // If there are no restrictions, we are done
+ if (!doRestrict)
+ return false;
+
+ // If the restriction is based on a tcp handshake in progress
+ // let that connect and then see if it was SPDY or not
+ if (ent->UnconnectedHalfOpens()) {
+ return true;
+ }
+
+ // There is a concern that a host is using a mix of HTTP/1 and SPDY.
+ // In that case we don't want to restrict connections just because
+ // there is a single active HTTP/1 session in use.
+ if (ent->mUsingSpdy && ent->mActiveConns.Length()) {
+ bool confirmedRestrict = false;
+ for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
+ nsHttpConnection *conn = ent->mActiveConns[index];
+ if (!conn->ReportedNPN() || conn->CanDirectlyActivate()) {
+ confirmedRestrict = true;
+ break;
+ }
+ }
+ doRestrict = confirmedRestrict;
+ if (!confirmedRestrict) {
+ LOG(("nsHttpConnectionMgr spdy connection restriction to "
+ "%s bypassed.\n", ent->mConnInfo->Origin()));
+ }
+ }
+ return doRestrict;
+}
+
+// returns NS_OK if a connection was started
+// return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to
+// ephemeral limits
+// returns other NS_ERROR on hard failure conditions
+nsresult
+nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent,
+ nsHttpTransaction *trans)
+{
+ LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p",
+ this, ent, trans));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ uint32_t halfOpenLength = ent->mHalfOpens.Length();
+ for (uint32_t i = 0; i < halfOpenLength; i++) {
+ if (ent->mHalfOpens[i]->IsSpeculative()) {
+ // We've found a speculative connection in the half
+ // open list. Remove the speculative bit from it and that
+ // connection can later be used for this transaction
+ // (or another one in the pending queue) - we don't
+ // need to open a new connection here.
+ LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s]\n"
+ "Found a speculative half open connection\n",
+ ent->mConnInfo->HashKey().get()));
+
+ uint32_t flags;
+ ent->mHalfOpens[i]->SetSpeculative(false);
+ nsISocketTransport *transport = ent->mHalfOpens[i]->SocketTransport();
+ if (transport && NS_SUCCEEDED(transport->GetConnectionFlags(&flags))) {
+ flags &= ~nsISocketTransport::DISABLE_RFC1918;
+ transport->SetConnectionFlags(flags);
+ }
+
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_USED_SPECULATIVE_CONN> usedSpeculativeConn;
+ ++usedSpeculativeConn;
+
+ if (ent->mHalfOpens[i]->IsFromPredictor()) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_USED> totalPreconnectsUsed;
+ ++totalPreconnectsUsed;
+ }
+
+ // return OK because we have essentially opened a new connection
+ // by converting a speculative half-open to general use
+ return NS_OK;
+ }
+ }
+
+ // consider null transactions that are being used to drive the ssl handshake if
+ // the transaction creating this connection can re-use persistent connections
+ if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) {
+ uint32_t activeLength = ent->mActiveConns.Length();
+ for (uint32_t i = 0; i < activeLength; i++) {
+ nsAHttpTransaction *activeTrans = ent->mActiveConns[i]->Transaction();
+ NullHttpTransaction *nullTrans = activeTrans ? activeTrans->QueryNullTransaction() : nullptr;
+ if (nullTrans && nullTrans->Claim()) {
+ LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s] "
+ "Claiming a null transaction for later use\n",
+ ent->mConnInfo->HashKey().get()));
+ return NS_OK;
+ }
+ }
+ }
+
+ // If this host is trying to negotiate a SPDY session right now,
+ // don't create any new connections until the result of the
+ // negotiation is known.
+ if (!(trans->Caps() & NS_HTTP_DISALLOW_SPDY) &&
+ (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) &&
+ RestrictConnections(ent)) {
+ LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s] "
+ "Not Available Due to RestrictConnections()\n",
+ ent->mConnInfo->HashKey().get()));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We need to make a new connection. If that is going to exceed the
+ // global connection limit then try and free up some room by closing
+ // an idle connection to another host. We know it won't select "ent"
+ // because we have already determined there are no idle connections
+ // to our destination
+
+ if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns) {
+ // If the global number of connections is preventing the opening of new
+ // connections to a host without idle connections, then close them
+ // regardless of their TTL.
+ auto iter = mCT.Iter();
+ while (mNumIdleConns + mNumActiveConns + 1 >= mMaxConns &&
+ !iter.Done()) {
+ nsAutoPtr<nsConnectionEntry> &entry = iter.Data();
+ if (!entry->mIdleConns.Length()) {
+ iter.Next();
+ continue;
+ }
+ RefPtr<nsHttpConnection> conn(entry->mIdleConns[0]);
+ entry->mIdleConns.RemoveElementAt(0);
+ conn->Close(NS_ERROR_ABORT);
+ mNumIdleConns--;
+ ConditionallyStopPruneDeadConnectionsTimer();
+ }
+ }
+
+ if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) &&
+ mNumActiveConns && gHttpHandler->IsSpdyEnabled())
+ {
+ // If the global number of connections is preventing the opening of new
+ // connections to a host without idle connections, then close any spdy
+ // ASAP.
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry> &entry = iter.Data();
+ if (!entry->mUsingSpdy) {
+ continue;
+ }
+
+ for (uint32_t index = 0;
+ index < entry->mActiveConns.Length();
+ ++index) {
+ nsHttpConnection *conn = entry->mActiveConns[index];
+ if (conn->UsingSpdy() && conn->CanReuse()) {
+ conn->DontReuse();
+ // Stop on <= (particularly =) because this dontreuse
+ // causes async close.
+ if (mNumIdleConns + mNumActiveConns + 1 <= mMaxConns) {
+ goto outerLoopEnd;
+ }
+ }
+ }
+ }
+ outerLoopEnd:
+ ;
+ }
+
+ if (AtActiveConnectionLimit(ent, trans->Caps()))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = CreateTransport(ent, trans, trans->Caps(), false, false, true);
+ if (NS_FAILED(rv)) {
+ /* hard failure */
+ LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] "
+ "CreateTransport() hard failure.\n",
+ ent->mConnInfo->HashKey().get(), trans));
+ trans->Close(rv);
+ if (rv == NS_ERROR_NOT_AVAILABLE)
+ rv = NS_ERROR_FAILURE;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+bool
+nsHttpConnectionMgr::AddToShortestPipeline(nsConnectionEntry *ent,
+ nsHttpTransaction *trans,
+ nsHttpTransaction::Classifier classification,
+ uint16_t depthLimit)
+{
+ if (classification == nsAHttpTransaction::CLASS_SOLO)
+ return false;
+
+ uint32_t maxdepth = ent->MaxPipelineDepth(classification);
+ if (maxdepth == 0) {
+ ent->CreditPenalty();
+ maxdepth = ent->MaxPipelineDepth(classification);
+ }
+
+ if (ent->PipelineState() == PS_RED)
+ return false;
+
+ if (ent->PipelineState() == PS_YELLOW && ent->mYellowConnection)
+ return false;
+
+ // The maximum depth of a pipeline in yellow is 1 pipeline of
+ // depth 2 for entire CI. When that transaction completes successfully
+ // we transition to green and that expands the allowed depth
+ // to any number of pipelines of up to depth 4. When a transaction
+ // queued at position 3 or deeper succeeds we open it all the way
+ // up to depths limited only by configuration. The staggered start
+ // in green is simply because a successful yellow test of depth=2
+ // might really just be a race condition (i.e. depth=1 from the
+ // server's point of view), while depth=3 is a stronger indicator -
+ // keeping the pipelines to a modest depth during that period limits
+ // the damage if something is going to go wrong.
+
+ maxdepth = std::min<uint32_t>(maxdepth, depthLimit);
+
+ if (maxdepth < 2)
+ return false;
+
+ nsAHttpTransaction *activeTrans;
+
+ nsHttpConnection *bestConn = nullptr;
+ uint32_t activeCount = ent->mActiveConns.Length();
+ uint32_t bestConnLength = 0;
+ uint32_t connLength;
+
+ for (uint32_t i = 0; i < activeCount; ++i) {
+ nsHttpConnection *conn = ent->mActiveConns[i];
+ if (!conn->SupportsPipelining())
+ continue;
+
+ if (conn->Classification() != classification)
+ continue;
+
+ activeTrans = conn->Transaction();
+ if (!activeTrans ||
+ activeTrans->IsDone() ||
+ NS_FAILED(activeTrans->Status()))
+ continue;
+
+ connLength = activeTrans->PipelineDepth();
+
+ if (maxdepth <= connLength)
+ continue;
+
+ if (!bestConn || (connLength < bestConnLength)) {
+ bestConn = conn;
+ bestConnLength = connLength;
+ }
+ }
+
+ if (!bestConn)
+ return false;
+
+ activeTrans = bestConn->Transaction();
+ nsresult rv = activeTrans->AddTransaction(trans);
+ if (NS_FAILED(rv))
+ return false;
+
+ LOG((" scheduling trans %p on pipeline at position %d\n",
+ trans, trans->PipelinePosition()));
+
+ if ((ent->PipelineState() == PS_YELLOW) && (trans->PipelinePosition() > 1))
+ ent->SetYellowConnection(bestConn);
+
+ if (!trans->GetPendingTime().IsNull()) {
+ if (trans->UsesPipelining())
+ AccumulateTimeDelta(
+ Telemetry::TRANSACTION_WAIT_TIME_HTTP_PIPELINES,
+ trans->GetPendingTime(), TimeStamp::Now());
+ else
+ AccumulateTimeDelta(
+ Telemetry::TRANSACTION_WAIT_TIME_HTTP,
+ trans->GetPendingTime(), TimeStamp::Now());
+ trans->SetPendingTime(false);
+ }
+ return true;
+}
+
+bool
+nsHttpConnectionMgr::IsUnderPressure(nsConnectionEntry *ent,
+ nsHttpTransaction::Classifier classification)
+{
+ // A connection entry is declared to be "under pressure" if most of the
+ // allowed parallel connections are already used up. In that case we want to
+ // favor existing pipelines over more parallelism so as to reserve any
+ // unused parallel connections for types that don't have existing pipelines.
+ //
+ // The definition of connection pressure is a pretty liberal one here - that
+ // is why we are using the more restrictive maxPersist* counters.
+ //
+ // Pipelines are also favored when the requested classification is already
+ // using 3 or more of the connections. Failure to do this could result in
+ // one class (e.g. images) establishing self replenishing queues on all the
+ // connections that would starve the other transaction types.
+
+ int32_t currentConns = ent->mActiveConns.Length();
+ int32_t maxConns =
+ (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingConnect()) ?
+ mMaxPersistConnsPerProxy : mMaxPersistConnsPerHost;
+
+ // Leave room for at least 3 distinct types to operate concurrently,
+ // this satisfies the typical {html, js/css, img} page.
+ if (currentConns >= (maxConns - 2))
+ return true; /* prefer pipeline */
+
+ int32_t sameClass = 0;
+ for (int32_t i = 0; i < currentConns; ++i)
+ if (classification == ent->mActiveConns[i]->Classification())
+ if (++sameClass == 3)
+ return true; /* prefer pipeline */
+
+ return false; /* normal behavior */
+}
+
+// returns OK if a connection is found for the transaction
+// and the transaction is started.
+// returns ERROR_NOT_AVAILABLE if no connection can be found and it
+// should be queued until circumstances change
+// returns other ERROR when transaction has a hard failure and should
+// not remain in the pending queue
+nsresult
+nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent,
+ bool onlyReusedConnection,
+ nsHttpTransaction *trans)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::TryDispatchTransaction without conn "
+ "[trans=%p ci=%p ci=%s caps=%x tunnelprovider=%p onlyreused=%d "
+ "active=%d idle=%d]\n", trans,
+ ent->mConnInfo.get(), ent->mConnInfo->HashKey().get(),
+ uint32_t(trans->Caps()), trans->TunnelProvider(),
+ onlyReusedConnection, ent->mActiveConns.Length(),
+ ent->mIdleConns.Length()));
+
+ nsHttpTransaction::Classifier classification = trans->Classification();
+ uint32_t caps = trans->Caps();
+
+ // no keep-alive means no pipelines either
+ if (!(caps & NS_HTTP_ALLOW_KEEPALIVE))
+ caps = caps & ~NS_HTTP_ALLOW_PIPELINING;
+
+ // 0 - If this should use spdy then dispatch it post haste.
+ // 1 - If there is connection pressure then see if we can pipeline this on
+ // a connection of a matching type instead of using a new conn
+ // 2 - If there is an idle connection, use it!
+ // 3 - if class == reval or script and there is an open conn of that type
+ // then pipeline onto shortest pipeline of that class if limits allow
+ // 4 - If we aren't up against our connection limit,
+ // then open a new one
+ // 5 - Try a pipeline if we haven't already - this will be unusual because
+ // it implies a low connection pressure situation where
+ // MakeNewConnection() failed.. that is possible, but unlikely, due to
+ // global limits
+ // 6 - no connection is available - queue it
+
+ bool attemptedOptimisticPipeline = !(caps & NS_HTTP_ALLOW_PIPELINING);
+ RefPtr<nsHttpConnection> unusedSpdyPersistentConnection;
+
+ // step 0
+ // look for existing spdy connection - that's always best because it is
+ // essentially pipelining without head of line blocking
+
+ if (!(caps & NS_HTTP_DISALLOW_SPDY) && gHttpHandler->IsSpdyEnabled()) {
+ RefPtr<nsHttpConnection> conn = GetSpdyPreferredConn(ent);
+ if (conn) {
+ if ((caps & NS_HTTP_ALLOW_KEEPALIVE) || !conn->IsExperienced()) {
+ LOG((" dispatch to spdy: [conn=%p]\n", conn.get()));
+ trans->RemoveDispatchedAsBlocking(); /* just in case */
+ DispatchTransaction(ent, trans, conn);
+ return NS_OK;
+ }
+ unusedSpdyPersistentConnection = conn;
+ }
+ }
+
+ // If this is not a blocking transaction and the request context for it is
+ // currently processing one or more blocking transactions then we
+ // need to just leave it in the queue until those are complete unless it is
+ // explicitly marked as unblocked.
+ if (!(caps & NS_HTTP_LOAD_AS_BLOCKING)) {
+ if (!(caps & NS_HTTP_LOAD_UNBLOCKED)) {
+ nsIRequestContext *requestContext = trans->RequestContext();
+ if (requestContext) {
+ uint32_t blockers = 0;
+ if (NS_SUCCEEDED(requestContext->GetBlockingTransactionCount(&blockers)) &&
+ blockers) {
+ // need to wait for blockers to clear
+ LOG((" blocked by request context: [rc=%p trans=%p blockers=%d]\n",
+ requestContext, trans, blockers));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ }
+ } else {
+ // Mark the transaction and its load group as blocking right now to prevent
+ // other transactions from being reordered in the queue due to slow syns.
+ trans->DispatchedAsBlocking();
+ }
+
+ // step 1
+ // If connection pressure, then we want to favor pipelining of any kind
+ if (IsUnderPressure(ent, classification) && !attemptedOptimisticPipeline) {
+ attemptedOptimisticPipeline = true;
+ if (AddToShortestPipeline(ent, trans,
+ classification,
+ mMaxOptimisticPipelinedRequests)) {
+ LOG((" dispatched step 1 trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // Subject most transactions at high parallelism to rate pacing.
+ // It will only be actually submitted to the
+ // token bucket once, and if possible it is granted admission synchronously.
+ // It is important to leave a transaction in the pending queue when blocked by
+ // pacing so it can be found on cancel if necessary.
+ // Transactions that cause blocking or bypass it (e.g. js/css) are not rate
+ // limited.
+ if (gHttpHandler->UseRequestTokenBucket()) {
+ // submit even whitelisted transactions to the token bucket though they will
+ // not be slowed by it
+ bool runNow = trans->TryToRunPacedRequest();
+ if (!runNow) {
+ if ((mNumActiveConns - mNumSpdyActiveConns) <=
+ gHttpHandler->RequestTokenBucketMinParallelism()) {
+ runNow = true; // white list it
+ } else if (caps & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) {
+ runNow = true; // white list it
+ }
+ }
+ if (!runNow) {
+ LOG((" blocked due to rate pacing trans=%p\n", trans));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ // step 2
+ // consider an idle persistent connection
+ if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
+ RefPtr<nsHttpConnection> conn;
+ while (!conn && (ent->mIdleConns.Length() > 0)) {
+ conn = ent->mIdleConns[0];
+ ent->mIdleConns.RemoveElementAt(0);
+ mNumIdleConns--;
+
+ // we check if the connection can be reused before even checking if
+ // it is a "matching" connection.
+ if (!conn->CanReuse()) {
+ LOG((" dropping stale connection: [conn=%p]\n", conn.get()));
+ conn->Close(NS_ERROR_ABORT);
+ conn = nullptr;
+ }
+ else {
+ LOG((" reusing connection [conn=%p]\n", conn.get()));
+ conn->EndIdleMonitoring();
+ }
+
+ // If there are no idle connections left at all, we need to make
+ // sure that we are not pruning dead connections anymore.
+ ConditionallyStopPruneDeadConnectionsTimer();
+ }
+ if (conn) {
+ // This will update the class of the connection to be the class of
+ // the transaction dispatched on it.
+ AddActiveConn(conn, ent);
+ DispatchTransaction(ent, trans, conn);
+ LOG((" dispatched step 2 (idle) trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // step 3
+ // consider pipelining scripts and revalidations
+ if (!attemptedOptimisticPipeline &&
+ (classification == nsHttpTransaction::CLASS_REVALIDATION ||
+ classification == nsHttpTransaction::CLASS_SCRIPT)) {
+ // Assignation kept here for documentation purpose; Never read after
+ attemptedOptimisticPipeline = true;
+ if (AddToShortestPipeline(ent, trans,
+ classification,
+ mMaxOptimisticPipelinedRequests)) {
+ LOG((" dispatched step 3 (pipeline) trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // step 4
+ if (!onlyReusedConnection) {
+ nsresult rv = MakeNewConnection(ent, trans);
+ if (NS_SUCCEEDED(rv)) {
+ // this function returns NOT_AVAILABLE for asynchronous connects
+ LOG((" dispatched step 4 (async new conn) trans=%p\n", trans));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ // not available return codes should try next step as they are
+ // not hard errors. Other codes should stop now
+ LOG((" failed step 4 (%x) trans=%p\n", rv, trans));
+ return rv;
+ }
+ } else if (trans->TunnelProvider() && trans->TunnelProvider()->MaybeReTunnel(trans)) {
+ LOG((" sort of dispatched step 4a tunnel requeue trans=%p\n", trans));
+ // the tunnel provider took responsibility for making a new tunnel
+ return NS_OK;
+ }
+
+ // step 5
+ if (caps & NS_HTTP_ALLOW_PIPELINING) {
+ if (AddToShortestPipeline(ent, trans,
+ classification,
+ mMaxPipelinedRequests)) {
+ LOG((" dispatched step 5 trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // step 6
+ if (unusedSpdyPersistentConnection) {
+ // to avoid deadlocks, we need to throw away this perfectly valid SPDY
+ // connection to make room for a new one that can service a no KEEPALIVE
+ // request
+ unusedSpdyPersistentConnection->DontReuse();
+ }
+
+ LOG((" not dispatched (queued) trans=%p\n", trans));
+ return NS_ERROR_NOT_AVAILABLE; /* queue it */
+}
+
+nsresult
+nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent,
+ nsHttpTransaction *trans,
+ nsHttpConnection *conn)
+{
+ uint32_t caps = trans->Caps();
+ int32_t priority = trans->Priority();
+ nsresult rv;
+
+ LOG(("nsHttpConnectionMgr::DispatchTransaction "
+ "[ent-ci=%s %p trans=%p caps=%x conn=%p priority=%d]\n",
+ ent->mConnInfo->HashKey().get(), ent, trans, caps, conn, priority));
+
+ // It is possible for a rate-paced transaction to be dispatched independent
+ // of the token bucket when the amount of parallelization has changed or
+ // when a muxed connection (e.g. spdy or pipelines) becomes available.
+ trans->CancelPacing(NS_OK);
+
+ if (conn->UsingSpdy()) {
+ LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s, "
+ "Connection host = %s\n",
+ trans->ConnectionInfo()->Origin(),
+ conn->ConnectionInfo()->Origin()));
+ rv = conn->Activate(trans, caps, priority);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch");
+ if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_SPDY,
+ trans->GetPendingTime(), TimeStamp::Now());
+ trans->SetPendingTime(false);
+ }
+ return rv;
+ }
+
+ MOZ_ASSERT(conn && !conn->Transaction(),
+ "DispatchTranaction() on non spdy active connection");
+
+ if (!(caps & NS_HTTP_ALLOW_PIPELINING))
+ conn->Classify(nsAHttpTransaction::CLASS_SOLO);
+ else
+ conn->Classify(trans->Classification());
+
+ rv = DispatchAbstractTransaction(ent, trans, caps, conn, priority);
+
+ if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
+ if (trans->UsesPipelining())
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP_PIPELINES,
+ trans->GetPendingTime(), TimeStamp::Now());
+ else
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP,
+ trans->GetPendingTime(), TimeStamp::Now());
+ trans->SetPendingTime(false);
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// ConnectionHandle
+//
+// thin wrapper around a real connection, used to keep track of references
+// to the connection to determine when the connection may be reused. the
+// transaction (or pipeline) owns a reference to this handle. this extra
+// layer of indirection greatly simplifies consumer code, avoiding the
+// need for consumer code to know when to give the connection back to the
+// connection manager.
+//
+class ConnectionHandle : public nsAHttpConnection
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPCONNECTION(mConn)
+
+ explicit ConnectionHandle(nsHttpConnection *conn) : mConn(conn) { }
+ void Reset() { mConn = nullptr; }
+private:
+ virtual ~ConnectionHandle();
+ RefPtr<nsHttpConnection> mConn;
+};
+
+nsAHttpConnection *
+nsHttpConnectionMgr::MakeConnectionHandle(nsHttpConnection *aWrapped)
+{
+ return new ConnectionHandle(aWrapped);
+}
+
+ConnectionHandle::~ConnectionHandle()
+{
+ if (mConn) {
+ gHttpHandler->ReclaimConnection(mConn);
+ }
+}
+
+NS_IMPL_ISUPPORTS0(ConnectionHandle)
+
+// Use this method for dispatching nsAHttpTransction's. It can only safely be
+// used upon first use of a connection when NPN has not negotiated SPDY vs
+// HTTP/1 yet as multiplexing onto an existing SPDY session requires a
+// concrete nsHttpTransaction
+nsresult
+nsHttpConnectionMgr::DispatchAbstractTransaction(nsConnectionEntry *ent,
+ nsAHttpTransaction *aTrans,
+ uint32_t caps,
+ nsHttpConnection *conn,
+ int32_t priority)
+{
+ MOZ_ASSERT(!conn->UsingSpdy(),
+ "Spdy Must Not Use DispatchAbstractTransaction");
+ LOG(("nsHttpConnectionMgr::DispatchAbstractTransaction "
+ "[ci=%s trans=%p caps=%x conn=%p]\n",
+ ent->mConnInfo->HashKey().get(), aTrans, caps, conn));
+
+ /* Use pipeline datastructure even if connection does not currently qualify
+ to pipeline this transaction because a different pipeline-eligible
+ transaction might be placed on the active connection. Make an exception
+ for CLASS_SOLO as that connection will never pipeline until it goes
+ quiescent */
+
+ RefPtr<nsAHttpTransaction> transaction;
+ nsresult rv;
+ if (conn->Classification() != nsAHttpTransaction::CLASS_SOLO) {
+ LOG((" using pipeline datastructure.\n"));
+ RefPtr<nsHttpPipeline> pipeline;
+ rv = BuildPipeline(ent, aTrans, getter_AddRefs(pipeline));
+ if (!NS_SUCCEEDED(rv))
+ return rv;
+ transaction = pipeline;
+ }
+ else {
+ LOG((" not using pipeline datastructure due to class solo.\n"));
+ transaction = aTrans;
+ }
+
+ RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
+
+ // give the transaction the indirect reference to the connection.
+ transaction->SetConnection(handle);
+
+ rv = conn->Activate(transaction, caps, priority);
+ if (NS_FAILED(rv)) {
+ LOG((" conn->Activate failed [rv=%x]\n", rv));
+ ent->mActiveConns.RemoveElement(conn);
+ if (conn == ent->mYellowConnection)
+ ent->OnYellowComplete();
+ DecrementActiveConnCount(conn);
+ ConditionallyStopTimeoutTick();
+
+ // sever back references to connection, and do so without triggering
+ // a call to ReclaimConnection ;-)
+ transaction->SetConnection(nullptr);
+ handle->Reset(); // destroy the connection
+ }
+
+ // As transaction goes out of scope it will drop the last refernece to the
+ // pipeline if activation failed, in which case this will destroy
+ // the pipeline, which will cause each the transactions owned by the
+ // pipeline to be restarted.
+
+ return rv;
+}
+
+nsresult
+nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent,
+ nsAHttpTransaction *firstTrans,
+ nsHttpPipeline **result)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ /* form a pipeline here even if nothing is pending so that we
+ can stream-feed it as new transactions arrive */
+
+ /* the first transaction can go in unconditionally - 1 transaction
+ on a nsHttpPipeline object is not a real HTTP pipeline */
+
+ RefPtr<nsHttpPipeline> pipeline = new nsHttpPipeline();
+ pipeline->AddTransaction(firstTrans);
+ pipeline.forget(result);
+ return NS_OK;
+}
+
+void
+nsHttpConnectionMgr::ReportProxyTelemetry(nsConnectionEntry *ent)
+{
+ enum { PROXY_NONE = 1, PROXY_HTTP = 2, PROXY_SOCKS = 3, PROXY_HTTPS = 4 };
+
+ if (!ent->mConnInfo->UsingProxy())
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_NONE);
+ else if (ent->mConnInfo->UsingHttpsProxy())
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTPS);
+ else if (ent->mConnInfo->UsingHttpProxy())
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTP);
+ else
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_SOCKS);
+}
+
+nsresult
+nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // since "adds" and "cancels" are processed asynchronously and because
+ // various events might trigger an "add" directly on the socket thread,
+ // we must take care to avoid dispatching a transaction that has already
+ // been canceled (see bug 190001).
+ if (NS_FAILED(trans->Status())) {
+ LOG((" transaction was canceled... dropping event!\n"));
+ return NS_OK;
+ }
+
+ trans->SetPendingTime();
+
+ Http2PushedStream *pushedStream = trans->GetPushedStream();
+ if (pushedStream) {
+ LOG((" ProcessNewTransaction %p tied to h2 session push %p\n",
+ trans, pushedStream->Session()));
+ return pushedStream->Session()->
+ AddStream(trans, trans->Priority(), false, nullptr) ?
+ NS_OK : NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+ nsHttpConnectionInfo *ci = trans->ConnectionInfo();
+ MOZ_ASSERT(ci);
+
+ nsConnectionEntry *ent =
+ GetOrCreateConnectionEntry(ci, !!trans->TunnelProvider());
+
+ // SPDY coalescing of hostnames means we might redirect from this
+ // connection entry onto the preferred one.
+ nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
+ if (preferredEntry && (preferredEntry != ent)) {
+ LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+ "redirected via coalescing from %s to %s\n", trans,
+ ent->mConnInfo->Origin(), preferredEntry->mConnInfo->Origin()));
+
+ ent = preferredEntry;
+ }
+
+ ReportProxyTelemetry(ent);
+
+ // Check if the transaction already has a sticky reference to a connection.
+ // If so, then we can just use it directly by transferring its reference
+ // to the new connection variable instead of searching for a new one
+
+ nsAHttpConnection *wrappedConnection = trans->Connection();
+ RefPtr<nsHttpConnection> conn;
+ if (wrappedConnection)
+ conn = wrappedConnection->TakeHttpConnection();
+
+ if (conn) {
+ MOZ_ASSERT(trans->Caps() & NS_HTTP_STICKY_CONNECTION);
+ LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+ "sticky connection=%p\n", trans, conn.get()));
+
+ if (static_cast<int32_t>(ent->mActiveConns.IndexOf(conn)) == -1) {
+ LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+ "sticky connection=%p needs to go on the active list\n", trans, conn.get()));
+
+ // make sure it isn't on the idle list - we expect this to be an
+ // unknown fresh connection
+ MOZ_ASSERT(static_cast<int32_t>(ent->mIdleConns.IndexOf(conn)) == -1);
+ MOZ_ASSERT(!conn->IsExperienced());
+
+ AddActiveConn(conn, ent); // make it active
+ }
+
+ trans->SetConnection(nullptr);
+ rv = DispatchTransaction(ent, trans, conn);
+ } else {
+ rv = TryDispatchTransaction(ent, !!trans->TunnelProvider(), trans);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" ProcessNewTransaction Dispatch Immediately trans=%p\n", trans));
+ return rv;
+ }
+
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG((" adding transaction to pending queue "
+ "[trans=%p pending-count=%u]\n",
+ trans, ent->mPendingQ.Length()+1));
+ // put this transaction on the pending queue...
+ InsertTransactionSorted(ent->mPendingQ, trans);
+ return NS_OK;
+ }
+
+ LOG((" ProcessNewTransaction Hard Error trans=%p rv=%x\n", trans, rv));
+ return rv;
+}
+
+
+void
+nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn,
+ nsConnectionEntry *ent)
+{
+ ent->mActiveConns.AppendElement(conn);
+ mNumActiveConns++;
+ ActivateTimeoutTick();
+}
+
+void
+nsHttpConnectionMgr::DecrementActiveConnCount(nsHttpConnection *conn)
+{
+ mNumActiveConns--;
+ if (conn->EverUsedSpdy())
+ mNumSpdyActiveConns--;
+}
+
+void
+nsHttpConnectionMgr::StartedConnect()
+{
+ mNumActiveConns++;
+ ActivateTimeoutTick(); // likely disabled by RecvdConnect()
+}
+
+void
+nsHttpConnectionMgr::RecvdConnect()
+{
+ mNumActiveConns--;
+ ConditionallyStopTimeoutTick();
+}
+
+nsresult
+nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent,
+ nsAHttpTransaction *trans,
+ uint32_t caps,
+ bool speculative,
+ bool isFromPredictor,
+ bool allow1918)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ RefPtr<nsHalfOpenSocket> sock = new nsHalfOpenSocket(ent, trans, caps);
+ if (speculative) {
+ sock->SetSpeculative(true);
+ sock->SetAllow1918(allow1918);
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_TOTAL_SPECULATIVE_CONN> totalSpeculativeConn;
+ ++totalSpeculativeConn;
+
+ if (isFromPredictor) {
+ sock->SetIsFromPredictor(true);
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_CREATED> totalPreconnectsCreated;
+ ++totalPreconnectsCreated;
+ }
+ }
+
+ // The socket stream holds the reference to the half open
+ // socket - so if the stream fails to init the half open
+ // will go away.
+ nsresult rv = sock->SetupPrimaryStreams();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ent->mHalfOpens.AppendElement(sock);
+ mNumHalfOpenConns++;
+ return NS_OK;
+}
+
+// This function tries to dispatch the pending spdy transactions on
+// the connection entry sent in as an argument. It will do so on the
+// active spdy connection either in that same entry or in the
+// redirected 'preferred' entry for the same coalescing hash key if
+// coalescing is enabled.
+
+void
+nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent)
+{
+ nsHttpConnection *conn = GetSpdyPreferredConn(ent);
+ if (!conn || !conn->CanDirectlyActivate())
+ return;
+
+ nsTArray<RefPtr<nsHttpTransaction> > leftovers;
+ uint32_t index;
+
+ // Dispatch all the transactions we can
+ for (index = 0;
+ index < ent->mPendingQ.Length() && conn->CanDirectlyActivate();
+ ++index) {
+ nsHttpTransaction *trans = ent->mPendingQ[index];
+
+ if (!(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) ||
+ trans->Caps() & NS_HTTP_DISALLOW_SPDY) {
+ leftovers.AppendElement(trans);
+ continue;
+ }
+
+ nsresult rv = DispatchTransaction(ent, trans, conn);
+ if (NS_FAILED(rv)) {
+ // this cannot happen, but if due to some bug it does then
+ // close the transaction
+ MOZ_ASSERT(false, "Dispatch SPDY Transaction");
+ LOG(("ProcessSpdyPendingQ Dispatch Transaction failed trans=%p\n",
+ trans));
+ trans->Close(rv);
+ }
+ }
+
+ // Slurp up the rest of the pending queue into our leftovers bucket (we
+ // might have some left if conn->CanDirectlyActivate returned false)
+ for (; index < ent->mPendingQ.Length(); ++index) {
+ nsHttpTransaction *trans = ent->mPendingQ[index];
+ leftovers.AppendElement(trans);
+ }
+
+ // Put the leftovers back in the pending queue and get rid of the
+ // transactions we dispatched
+ leftovers.SwapElements(ent->mPendingQ);
+ leftovers.Clear();
+}
+
+void
+nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ ProcessSpdyPendingQ(iter.Data());
+ }
+}
+
+nsHttpConnection *
+nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(ent);
+
+ nsConnectionEntry *preferred = GetSpdyPreferredEnt(ent);
+ // this entry is spdy-enabled if it is involved in a redirect
+ if (preferred) {
+ // all new connections for this entry will use spdy too
+ ent->mUsingSpdy = true;
+ } else {
+ preferred = ent;
+ }
+
+ if (!preferred->mUsingSpdy) {
+ return nullptr;
+ }
+
+ nsHttpConnection *rv = nullptr;
+ uint32_t activeLen = preferred->mActiveConns.Length();
+ uint32_t index;
+
+ // activeLen should generally be 1.. this is a setup race being resolved
+ // take a conn who can activate and is experienced
+ for (index = 0; index < activeLen; ++index) {
+ nsHttpConnection *tmp = preferred->mActiveConns[index];
+ if (tmp->CanDirectlyActivate() && tmp->IsExperienced()) {
+ rv = tmp;
+ break;
+ }
+ }
+
+ // if that worked, cleanup anything else
+ if (rv) {
+ for (index = 0; index < activeLen; ++index) {
+ nsHttpConnection *tmp = preferred->mActiveConns[index];
+ // in the case where there is a functional h2 session, drop the others
+ if (tmp != rv) {
+ tmp->DontReuse();
+ }
+ }
+ return rv;
+ }
+
+ // take a conn who can activate and leave the rest alone
+ for (index = 0; index < activeLen; ++index) {
+ nsHttpConnection *tmp = preferred->mActiveConns[index];
+ if (tmp->CanDirectlyActivate()) {
+ rv = tmp;
+ break;
+ }
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+
+void
+nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
+
+ gHttpHandler->StopRequestTokenBucket();
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ // Close all active connections.
+ while (ent->mActiveConns.Length()) {
+ RefPtr<nsHttpConnection> conn(ent->mActiveConns[0]);
+ ent->mActiveConns.RemoveElementAt(0);
+ DecrementActiveConnCount(conn);
+ // Since nsHttpConnection::Close doesn't break the bond with
+ // the connection's transaction, we must explicitely tell it
+ // to close its transaction and not just self.
+ conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
+ }
+
+ // Close all idle connections.
+ while (ent->mIdleConns.Length()) {
+ RefPtr<nsHttpConnection> conn(ent->mIdleConns[0]);
+
+ ent->mIdleConns.RemoveElementAt(0);
+ mNumIdleConns--;
+
+ conn->Close(NS_ERROR_ABORT);
+ }
+
+ // If all idle connections are removed we can stop pruning dead
+ // connections.
+ ConditionallyStopPruneDeadConnectionsTimer();
+
+ // Close all pending transactions.
+ while (ent->mPendingQ.Length()) {
+ nsHttpTransaction *trans = ent->mPendingQ[0];
+ trans->Close(NS_ERROR_ABORT);
+ ent->mPendingQ.RemoveElementAt(0);
+ }
+
+ // Close all half open tcp connections.
+ for (int32_t i = int32_t(ent->mHalfOpens.Length()) - 1; i >= 0; i--) {
+ ent->mHalfOpens[i]->Abandon();
+ }
+
+ iter.Remove();
+ }
+
+ if (mTimeoutTick) {
+ mTimeoutTick->Cancel();
+ mTimeoutTick = nullptr;
+ mTimeoutTickArmed = false;
+ }
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (mTrafficTimer) {
+ mTrafficTimer->Cancel();
+ mTrafficTimer = nullptr;
+ }
+
+ // signal shutdown complete
+ nsCOMPtr<nsIRunnable> runnable =
+ new ConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm,
+ 0, param);
+ NS_DispatchToMainThread(runnable);
+}
+
+void
+nsHttpConnectionMgr::OnMsgShutdownConfirm(int32_t priority, ARefBase *param)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("nsHttpConnectionMgr::OnMsgShutdownConfirm\n"));
+
+ BoolWrapper *shutdown = static_cast<BoolWrapper *>(param);
+ shutdown->mBool = true;
+}
+
+void
+nsHttpConnectionMgr::OnMsgNewTransaction(int32_t priority, ARefBase *param)
+{
+ LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param));
+
+ nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);
+ trans->SetPriority(priority);
+ nsresult rv = ProcessNewTransaction(trans);
+ if (NS_FAILED(rv))
+ trans->Close(rv); // for whatever its worth
+}
+
+void
+nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param));
+
+ RefPtr<nsHttpTransaction> trans = static_cast<nsHttpTransaction *>(param);
+ trans->SetPriority(priority);
+
+ nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(),
+ nullptr, trans);
+
+ if (ent) {
+ int32_t index = ent->mPendingQ.IndexOf(trans);
+ if (index >= 0) {
+ ent->mPendingQ.RemoveElementAt(index);
+ InsertTransactionSorted(ent->mPendingQ, trans);
+ }
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
+
+ nsresult closeCode = static_cast<nsresult>(reason);
+
+ // caller holds a ref to param/trans on stack
+ nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);
+
+ //
+ // if the transaction owns a connection and the transaction is not done,
+ // then ask the connection to close the transaction. otherwise, close the
+ // transaction directly (removing it from the pending queue first).
+ //
+ RefPtr<nsAHttpConnection> conn(trans->Connection());
+ if (conn && !trans->IsDone()) {
+ conn->CloseTransaction(trans, closeCode);
+ } else {
+ nsConnectionEntry *ent =
+ LookupConnectionEntry(trans->ConnectionInfo(), nullptr, trans);
+
+ if (ent) {
+ int32_t transIndex = ent->mPendingQ.IndexOf(trans);
+ if (transIndex >= 0) {
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
+ " found in pending queue\n", trans));
+ ent->mPendingQ.RemoveElementAt(transIndex);
+ }
+
+ // Abandon all half-open sockets belonging to the given transaction.
+ for (uint32_t index = 0;
+ index < ent->mHalfOpens.Length();
+ ++index) {
+ nsHalfOpenSocket *half = ent->mHalfOpens[index];
+ if (trans == half->Transaction()) {
+ half->Abandon();
+ // there is only one, and now mHalfOpens[] has been changed.
+ break;
+ }
+ }
+ }
+
+ trans->Close(closeCode);
+
+ // Cancel is a pretty strong signal that things might be hanging
+ // so we want to cancel any null transactions related to this connection
+ // entry. They are just optimizations, but they aren't hooked up to
+ // anything that might get canceled from the rest of gecko, so best
+ // to assume that's what was meant by the cancel we did receive if
+ // it only applied to something in the queue.
+ for (uint32_t index = 0;
+ ent && (index < ent->mActiveConns.Length());
+ ++index) {
+ nsHttpConnection *activeConn = ent->mActiveConns[index];
+ nsAHttpTransaction *liveTransaction = activeConn->Transaction();
+ if (liveTransaction && liveTransaction->IsNullTransaction()) {
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p] "
+ "also canceling Null Transaction %p on conn %p\n",
+ trans, liveTransaction, activeConn));
+ activeConn->CloseTransaction(liveTransaction, closeCode);
+ }
+ }
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
+
+ if (!ci) {
+ LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n"));
+ // Try and dispatch everything
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ ProcessPendingQForEntry(iter.Data(), true);
+ }
+ return;
+ }
+
+ LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n",
+ ci->HashKey().get()));
+
+ // start by processing the queue identified by the given connection info.
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ if (!(ent && ProcessPendingQForEntry(ent, false))) {
+ // if we reach here, it means that we couldn't dispatch a transaction
+ // for the specified connection info. walk the connection table...
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ if (ProcessPendingQForEntry(iter.Data(), false)) {
+ break;
+ }
+ }
+ }
+}
+
+nsresult
+nsHttpConnectionMgr::CancelTransactions(nsHttpConnectionInfo *ci, nsresult code)
+{
+ LOG(("nsHttpConnectionMgr::CancelTransactions %s\n",ci->HashKey().get()));
+
+ int32_t intReason = static_cast<int32_t>(code);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransactions, intReason, ci);
+}
+
+void
+nsHttpConnectionMgr::OnMsgCancelTransactions(int32_t code, ARefBase *param)
+{
+ nsresult reason = static_cast<nsresult>(code);
+ nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p\n",
+ ci->HashKey().get(), ent));
+ if (!ent) {
+ return;
+ }
+
+ for (int32_t i = ent->mPendingQ.Length() - 1; i >= 0; --i) {
+ nsHttpTransaction *trans = ent->mPendingQ[i];
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p %p\n",
+ ci->HashKey().get(), ent, trans));
+ trans->Close(reason);
+ ent->mPendingQ.RemoveElementAt(i);
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
+
+ // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
+ mTimeOfNextWakeUp = UINT64_MAX;
+
+ // check canreuse() for all idle connections plus any active connections on
+ // connection entries that are using spdy.
+ if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled())) {
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get()));
+
+ // Find out how long it will take for next idle connection to not
+ // be reusable anymore.
+ uint32_t timeToNextExpire = UINT32_MAX;
+ int32_t count = ent->mIdleConns.Length();
+ if (count > 0) {
+ for (int32_t i = count - 1; i >= 0; --i) {
+ RefPtr<nsHttpConnection> conn(ent->mIdleConns[i]);
+ if (!conn->CanReuse()) {
+ ent->mIdleConns.RemoveElementAt(i);
+ conn->Close(NS_ERROR_ABORT);
+ mNumIdleConns--;
+ } else {
+ timeToNextExpire =
+ std::min(timeToNextExpire, conn->TimeToLive());
+ }
+ }
+ }
+
+ if (ent->mUsingSpdy) {
+ for (uint32_t i = 0; i < ent->mActiveConns.Length(); ++i) {
+ nsHttpConnection* conn = ent->mActiveConns[i];
+ if (conn->UsingSpdy()) {
+ if (!conn->CanReuse()) {
+ // Marking it don't-reuse will create an active
+ // tear down if the spdy session is idle.
+ conn->DontReuse();
+ } else {
+ timeToNextExpire =
+ std::min(timeToNextExpire, conn->TimeToLive());
+ }
+ }
+ }
+ }
+
+ // If time to next expire found is shorter than time to next
+ // wake-up, we need to change the time for next wake-up.
+ if (timeToNextExpire != UINT32_MAX) {
+ uint32_t now = NowInSeconds();
+ uint64_t timeOfNextExpire = now + timeToNextExpire;
+ // If pruning of dead connections is not already scheduled to
+ // happen or time found for next connection to expire is is
+ // before mTimeOfNextWakeUp, we need to schedule the pruning to
+ // happen after timeToNextExpire.
+ if (!mTimer || timeOfNextExpire < mTimeOfNextWakeUp) {
+ PruneDeadConnectionsAfter(timeToNextExpire);
+ }
+ } else {
+ ConditionallyStopPruneDeadConnectionsTimer();
+ }
+
+ // If this entry is empty, we have too many entries, and this
+ // doesn't represent some painfully determined red condition, then
+ // we can clean it up and restart from yellow.
+ if (ent->PipelineState() != PS_RED &&
+ mCT.Count() > 125 &&
+ ent->mIdleConns.Length() == 0 &&
+ ent->mActiveConns.Length() == 0 &&
+ ent->mHalfOpens.Length() == 0 &&
+ ent->mPendingQ.Length() == 0 &&
+ (!ent->mUsingSpdy || mCT.Count() > 300)) {
+ LOG((" removing empty connection entry\n"));
+ iter.Remove();
+ continue;
+ }
+
+ // Otherwise use this opportunity to compact our arrays...
+ ent->mIdleConns.Compact();
+ ent->mActiveConns.Compact();
+ ent->mPendingQ.Compact();
+ }
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgPruneNoTraffic(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgPruneNoTraffic\n"));
+
+ // Prune connections without traffic
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+
+ // Close the connections with no registered traffic.
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ LOG((" pruning no traffic [ci=%s]\n",
+ ent->mConnInfo->HashKey().get()));
+
+ uint32_t numConns = ent->mActiveConns.Length();
+ if (numConns) {
+ // Walk the list backwards to allow us to remove entries easily.
+ for (int index = numConns - 1; index >= 0; index--) {
+ if (ent->mActiveConns[index]->NoTraffic()) {
+ RefPtr<nsHttpConnection> conn = ent->mActiveConns[index];
+ ent->mActiveConns.RemoveElementAt(index);
+ DecrementActiveConnCount(conn);
+ conn->Close(NS_ERROR_ABORT);
+ LOG((" closed active connection due to no traffic "
+ "[conn=%p]\n", conn.get()));
+ }
+ }
+ }
+ }
+
+ mPruningNoTraffic = false; // not pruning anymore
+}
+
+void
+nsHttpConnectionMgr::OnMsgVerifyTraffic(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgVerifyTraffic\n"));
+
+ if (mPruningNoTraffic) {
+ // Called in the time gap when the timeout to prune notraffic
+ // connections has triggered but the pruning hasn't happened yet.
+ return;
+ }
+
+ // Mark connections for traffic verification
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ // Iterate over all active connections and check them.
+ for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
+ ent->mActiveConns[index]->CheckForTraffic(true);
+ }
+ // Iterate the idle connections and unmark them for traffic checks.
+ for (uint32_t index = 0; index < ent->mIdleConns.Length(); ++index) {
+ ent->mIdleConns[index]->CheckForTraffic(false);
+ }
+ }
+
+ // If the timer is already there. we just re-init it
+ if(!mTrafficTimer) {
+ mTrafficTimer = do_CreateInstance("@mozilla.org/timer;1");
+ }
+
+ // failure to create a timer is not a fatal error, but dead
+ // connections will not be cleaned up as nicely
+ if (mTrafficTimer) {
+ // Give active connections time to get more traffic before killing
+ // them off. Default: 5000 milliseconds
+ mTrafficTimer->Init(this, gHttpHandler->NetworkChangedTimeout(),
+ nsITimer::TYPE_ONE_SHOT);
+ } else {
+ NS_WARNING("failed to create timer for VerifyTraffic!");
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup(int32_t, ARefBase *param)
+{
+ LOG(("nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup\n"));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ ClosePersistentConnections(iter.Data());
+ }
+
+ if (ci)
+ ResetIPFamilyPreference(ci);
+}
+
+void
+nsHttpConnectionMgr::OnMsgReclaimConnection(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [conn=%p]\n", param));
+
+ nsHttpConnection *conn = static_cast<nsHttpConnection *>(param);
+
+ //
+ // 1) remove the connection from the active list
+ // 2) if keep-alive, add connection to idle list
+ // 3) post event to process the pending transaction queue
+ //
+
+ nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
+ conn, nullptr);
+ if (!ent) {
+ // this can happen if the connection is made outside of the
+ // connection manager and is being "reclaimed" for use with
+ // future transactions. HTTP/2 tunnels work like this.
+ ent = GetOrCreateConnectionEntry(conn->ConnectionInfo(), true);
+ LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection conn %p "
+ "forced new hash entry %s\n",
+ conn, conn->ConnectionInfo()->HashKey().get()));
+ }
+
+ MOZ_ASSERT(ent);
+ RefPtr<nsHttpConnectionInfo> ci(ent->mConnInfo);
+
+ // If the connection is in the active list, remove that entry
+ // and the reference held by the mActiveConns list.
+ // This is never the final reference on conn as the event context
+ // is also holding one that is released at the end of this function.
+
+ if (conn->EverUsedSpdy()) {
+ // Spdy connections aren't reused in the traditional HTTP way in
+ // the idleconns list, they are actively multplexed as active
+ // conns. Even when they have 0 transactions on them they are
+ // considered active connections. So when one is reclaimed it
+ // is really complete and is meant to be shut down and not
+ // reused.
+ conn->DontReuse();
+ }
+
+ // a connection that still holds a reference to a transaction was
+ // not closed naturally (i.e. it was reset or aborted) and is
+ // therefore not something that should be reused.
+ if (conn->Transaction()) {
+ conn->DontReuse();
+ }
+
+ if (ent->mActiveConns.RemoveElement(conn)) {
+ if (conn == ent->mYellowConnection) {
+ ent->OnYellowComplete();
+ }
+ DecrementActiveConnCount(conn);
+ ConditionallyStopTimeoutTick();
+ }
+
+ if (conn->CanReuse()) {
+ LOG((" adding connection to idle list\n"));
+ // Keep The idle connection list sorted with the connections that
+ // have moved the largest data pipelines at the front because these
+ // connections have the largest cwnds on the server.
+
+ // The linear search is ok here because the number of idleconns
+ // in a single entry is generally limited to a small number (i.e. 6)
+
+ uint32_t idx;
+ for (idx = 0; idx < ent->mIdleConns.Length(); idx++) {
+ nsHttpConnection *idleConn = ent->mIdleConns[idx];
+ if (idleConn->MaxBytesRead() < conn->MaxBytesRead())
+ break;
+ }
+
+ ent->mIdleConns.InsertElementAt(idx, conn);
+ mNumIdleConns++;
+ conn->BeginIdleMonitoring();
+
+ // If the added connection was first idle connection or has shortest
+ // time to live among the watched connections, pruning dead
+ // connections needs to be done when it can't be reused anymore.
+ uint32_t timeToLive = conn->TimeToLive();
+ if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp)
+ PruneDeadConnectionsAfter(timeToLive);
+ } else {
+ LOG((" connection cannot be reused; closing connection\n"));
+ conn->Close(NS_ERROR_ABORT);
+ }
+
+ OnMsgProcessPendingQ(0, ci);
+}
+
+void
+nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsCompleteUpgradeData *data = static_cast<nsCompleteUpgradeData *>(param);
+ LOG(("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
+ "this=%p conn=%p listener=%p\n", this, data->mConn.get(),
+ data->mUpgradeListener.get()));
+
+ nsCOMPtr<nsISocketTransport> socketTransport;
+ nsCOMPtr<nsIAsyncInputStream> socketIn;
+ nsCOMPtr<nsIAsyncOutputStream> socketOut;
+
+ nsresult rv;
+ rv = data->mConn->TakeTransport(getter_AddRefs(socketTransport),
+ getter_AddRefs(socketIn),
+ getter_AddRefs(socketOut));
+
+ if (NS_SUCCEEDED(rv))
+ data->mUpgradeListener->OnTransportAvailable(socketTransport,
+ socketIn,
+ socketOut);
+}
+
+void
+nsHttpConnectionMgr::OnMsgUpdateParam(int32_t inParam, ARefBase *)
+{
+ uint32_t param = static_cast<uint32_t>(inParam);
+ uint16_t name = ((param) & 0xFFFF0000) >> 16;
+ uint16_t value = param & 0x0000FFFF;
+
+ switch (name) {
+ case MAX_CONNECTIONS:
+ mMaxConns = value;
+ break;
+ case MAX_PERSISTENT_CONNECTIONS_PER_HOST:
+ mMaxPersistConnsPerHost = value;
+ break;
+ case MAX_PERSISTENT_CONNECTIONS_PER_PROXY:
+ mMaxPersistConnsPerProxy = value;
+ break;
+ case MAX_REQUEST_DELAY:
+ mMaxRequestDelay = value;
+ break;
+ case MAX_PIPELINED_REQUESTS:
+ mMaxPipelinedRequests = value;
+ break;
+ case MAX_OPTIMISTIC_PIPELINED_REQUESTS:
+ mMaxOptimisticPipelinedRequests = value;
+ break;
+ default:
+ NS_NOTREACHED("unexpected parameter name");
+ }
+}
+
+// nsHttpConnectionMgr::nsConnectionEntry
+nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry()
+{
+ MOZ_COUNT_DTOR(nsConnectionEntry);
+ gHttpHandler->ConnMgr()->RemovePreferredHash(this);
+}
+
+void
+nsHttpConnectionMgr::OnMsgProcessFeedback(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsHttpPipelineFeedback *fb = static_cast<nsHttpPipelineFeedback *>(param);
+ PipelineFeedbackInfo(fb->mConnInfo, fb->mInfo, fb->mConn, fb->mData);
+}
+
+// Read Timeout Tick handlers
+
+void
+nsHttpConnectionMgr::ActivateTimeoutTick()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::ActivateTimeoutTick() "
+ "this=%p mTimeoutTick=%p\n", this, mTimeoutTick.get()));
+
+ // The timer tick should be enabled if it is not already pending.
+ // Upon running the tick will rearm itself if there are active
+ // connections available.
+
+ if (mTimeoutTick && mTimeoutTickArmed) {
+ // make sure we get one iteration on a quick tick
+ if (mTimeoutTickNext > 1) {
+ mTimeoutTickNext = 1;
+ mTimeoutTick->SetDelay(1000);
+ }
+ return;
+ }
+
+ if (!mTimeoutTick) {
+ mTimeoutTick = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (!mTimeoutTick) {
+ NS_WARNING("failed to create timer for http timeout management");
+ return;
+ }
+ mTimeoutTick->SetTarget(mSocketThreadTarget);
+ }
+
+ MOZ_ASSERT(!mTimeoutTickArmed, "timer tick armed");
+ mTimeoutTickArmed = true;
+ mTimeoutTick->Init(this, 1000, nsITimer::TYPE_REPEATING_SLACK);
+}
+
+void
+nsHttpConnectionMgr::TimeoutTick()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mTimeoutTick, "no readtimeout tick");
+
+ LOG(("nsHttpConnectionMgr::TimeoutTick active=%d\n", mNumActiveConns));
+ // The next tick will be between 1 second and 1 hr
+ // Set it to the max value here, and the TimeoutTick()s can
+ // reduce it to their local needs.
+ mTimeoutTickNext = 3600; // 1hr
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ LOG(("nsHttpConnectionMgr::TimeoutTick() this=%p host=%s "
+ "idle=%d active=%d half-len=%d pending=%d\n",
+ this, ent->mConnInfo->Origin(), ent->mIdleConns.Length(),
+ ent->mActiveConns.Length(), ent->mHalfOpens.Length(),
+ ent->mPendingQ.Length()));
+
+ // First call the tick handler for each active connection.
+ PRIntervalTime tickTime = PR_IntervalNow();
+ for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
+ uint32_t connNextTimeout =
+ ent->mActiveConns[index]->ReadTimeoutTick(tickTime);
+ mTimeoutTickNext = std::min(mTimeoutTickNext, connNextTimeout);
+ }
+
+ // Now check for any stalled half open sockets.
+ if (ent->mHalfOpens.Length()) {
+ TimeStamp currentTime = TimeStamp::Now();
+ double maxConnectTime_ms = gHttpHandler->ConnectTimeout();
+
+ for (uint32_t index = ent->mHalfOpens.Length(); index > 0; ) {
+ index--;
+
+ nsHalfOpenSocket *half = ent->mHalfOpens[index];
+ double delta = half->Duration(currentTime);
+ // If the socket has timed out, close it so the waiting
+ // transaction will get the proper signal.
+ if (delta > maxConnectTime_ms) {
+ LOG(("Force timeout of half open to %s after %.2fms.\n",
+ ent->mConnInfo->HashKey().get(), delta));
+ if (half->SocketTransport()) {
+ half->SocketTransport()->Close(NS_ERROR_NET_TIMEOUT);
+ }
+ if (half->BackupTransport()) {
+ half->BackupTransport()->Close(NS_ERROR_NET_TIMEOUT);
+ }
+ }
+
+ // If this half open hangs around for 5 seconds after we've
+ // closed() it then just abandon the socket.
+ if (delta > maxConnectTime_ms + 5000) {
+ LOG(("Abandon half open to %s after %.2fms.\n",
+ ent->mConnInfo->HashKey().get(), delta));
+ half->Abandon();
+ }
+ }
+ }
+ if (ent->mHalfOpens.Length()) {
+ mTimeoutTickNext = 1;
+ }
+ }
+
+ if (mTimeoutTick) {
+ mTimeoutTickNext = std::max(mTimeoutTickNext, 1U);
+ mTimeoutTick->SetDelay(mTimeoutTickNext * 1000);
+ }
+}
+
+// GetOrCreateConnectionEntry finds a ent for a particular CI for use in
+// dispatching a transaction according to these rules
+// 1] use an ent that matches the ci that can be dispatched immediately
+// 2] otherwise use an ent of wildcard(ci) than can be dispatched immediately
+// 3] otherwise create an ent that matches ci and make new conn on it
+
+nsHttpConnectionMgr::nsConnectionEntry *
+nsHttpConnectionMgr::GetOrCreateConnectionEntry(nsHttpConnectionInfo *specificCI,
+ bool prohibitWildCard)
+{
+ // step 1
+ nsConnectionEntry *specificEnt = mCT.Get(specificCI->HashKey());
+ if (specificEnt && specificEnt->AvailableForDispatchNow()) {
+ return specificEnt;
+ }
+
+ if (!specificCI->UsingHttpsProxy()) {
+ prohibitWildCard = true;
+ }
+
+ // step 2
+ if (!prohibitWildCard) {
+ RefPtr<nsHttpConnectionInfo> wildCardProxyCI;
+ specificCI->CreateWildCard(getter_AddRefs(wildCardProxyCI));
+ nsConnectionEntry *wildCardEnt = mCT.Get(wildCardProxyCI->HashKey());
+ if (wildCardEnt && wildCardEnt->AvailableForDispatchNow()) {
+ return wildCardEnt;
+ }
+ }
+
+ // step 3
+ if (!specificEnt) {
+ RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
+ specificEnt = new nsConnectionEntry(clone);
+ mCT.Put(clone->HashKey(), specificEnt);
+ }
+ return specificEnt;
+}
+
+nsresult
+ConnectionHandle::OnHeadersAvailable(nsAHttpTransaction *trans,
+ nsHttpRequestHead *req,
+ nsHttpResponseHead *resp,
+ bool *reset)
+{
+ return mConn->OnHeadersAvailable(trans, req, resp, reset);
+}
+
+void
+ConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
+{
+ mConn->CloseTransaction(trans, reason);
+}
+
+nsresult
+ConnectionHandle::TakeTransport(nsISocketTransport **aTransport,
+ nsIAsyncInputStream **aInputStream,
+ nsIAsyncOutputStream **aOutputStream)
+{
+ return mConn->TakeTransport(aTransport, aInputStream, aOutputStream);
+}
+
+void
+nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ SpeculativeConnectArgs *args = static_cast<SpeculativeConnectArgs *>(param);
+
+ LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n",
+ args->mTrans->ConnectionInfo()->HashKey().get()));
+
+ nsConnectionEntry *ent =
+ GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo(), false);
+
+ // If spdy has previously made a preferred entry for this host via
+ // the ip pooling rules. If so, connect to the preferred host instead of
+ // the one directly passed in here.
+ nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
+ if (preferredEntry)
+ ent = preferredEntry;
+
+ uint32_t parallelSpeculativeConnectLimit =
+ gHttpHandler->ParallelSpeculativeConnectLimit();
+ bool ignoreIdle = false;
+ bool isFromPredictor = false;
+ bool allow1918 = false;
+
+ if (args->mOverridesOK) {
+ parallelSpeculativeConnectLimit = args->mParallelSpeculativeConnectLimit;
+ ignoreIdle = args->mIgnoreIdle;
+ isFromPredictor = args->mIsFromPredictor;
+ allow1918 = args->mAllow1918;
+ }
+
+ bool keepAlive = args->mTrans->Caps() & NS_HTTP_ALLOW_KEEPALIVE;
+ if (mNumHalfOpenConns < parallelSpeculativeConnectLimit &&
+ ((ignoreIdle && (ent->mIdleConns.Length() < parallelSpeculativeConnectLimit)) ||
+ !ent->mIdleConns.Length()) &&
+ !(keepAlive && RestrictConnections(ent)) &&
+ !AtActiveConnectionLimit(ent, args->mTrans->Caps())) {
+ CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true, isFromPredictor, allow1918);
+ } else {
+ LOG(("OnMsgSpeculativeConnect Transport "
+ "not created due to existing connection count\n"));
+ }
+}
+
+bool
+ConnectionHandle::IsPersistent()
+{
+ return mConn->IsPersistent();
+}
+
+bool
+ConnectionHandle::IsReused()
+{
+ return mConn->IsReused();
+}
+
+void
+ConnectionHandle::DontReuse()
+{
+ mConn->DontReuse();
+}
+
+nsresult
+ConnectionHandle::PushBack(const char *buf, uint32_t bufLen)
+{
+ return mConn->PushBack(buf, bufLen);
+}
+
+
+//////////////////////// nsHalfOpenSocket
+
+NS_IMPL_ISUPPORTS(nsHttpConnectionMgr::nsHalfOpenSocket,
+ nsIOutputStreamCallback,
+ nsITransportEventSink,
+ nsIInterfaceRequestor,
+ nsITimerCallback)
+
+nsHttpConnectionMgr::
+nsHalfOpenSocket::nsHalfOpenSocket(nsConnectionEntry *ent,
+ nsAHttpTransaction *trans,
+ uint32_t caps)
+ : mEnt(ent)
+ , mTransaction(trans)
+ , mDispatchedMTransaction(false)
+ , mCaps(caps)
+ , mSpeculative(false)
+ , mIsFromPredictor(false)
+ , mAllow1918(true)
+ , mHasConnected(false)
+ , mPrimaryConnectedOK(false)
+ , mBackupConnectedOK(false)
+{
+ MOZ_ASSERT(ent && trans, "constructor with null arguments");
+ LOG(("Creating nsHalfOpenSocket [this=%p trans=%p ent=%s key=%s]\n",
+ this, trans, ent->mConnInfo->Origin(), ent->mConnInfo->HashKey().get()));
+}
+
+nsHttpConnectionMgr::nsHalfOpenSocket::~nsHalfOpenSocket()
+{
+ MOZ_ASSERT(!mStreamOut);
+ MOZ_ASSERT(!mBackupStreamOut);
+ MOZ_ASSERT(!mSynTimer);
+ LOG(("Destroying nsHalfOpenSocket [this=%p]\n", this));
+
+ if (mEnt)
+ mEnt->RemoveHalfOpen(this);
+}
+
+nsresult
+nsHttpConnectionMgr::
+nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport,
+ nsIAsyncInputStream **instream,
+ nsIAsyncOutputStream **outstream,
+ bool isBackup)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsresult rv;
+ const char *socketTypes[1];
+ uint32_t typeCount = 0;
+ const nsHttpConnectionInfo *ci = mEnt->mConnInfo;
+ if (ci->FirstHopSSL()) {
+ socketTypes[typeCount++] = "ssl";
+ } else {
+ socketTypes[typeCount] = gHttpHandler->DefaultSocketType();
+ if (socketTypes[typeCount]) {
+ typeCount++;
+ }
+ }
+
+ nsCOMPtr<nsISocketTransport> socketTransport;
+ nsCOMPtr<nsISocketTransportService> sts;
+
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("nsHalfOpenSocket::SetupStreams [this=%p ent=%s] "
+ "setup routed transport to origin %s:%d via %s:%d\n",
+ this, ci->HashKey().get(),
+ ci->Origin(), ci->OriginPort(), ci->RoutedHost(), ci->RoutedPort()));
+
+ nsCOMPtr<nsIRoutedSocketTransportService> routedSTS(do_QueryInterface(sts));
+ if (routedSTS) {
+ rv = routedSTS->CreateRoutedTransport(
+ socketTypes, typeCount,
+ ci->GetOrigin(), ci->OriginPort(), ci->GetRoutedHost(), ci->RoutedPort(),
+ ci->ProxyInfo(), getter_AddRefs(socketTransport));
+ } else {
+ if (!ci->GetRoutedHost().IsEmpty()) {
+ // There is a route requested, but the legacy nsISocketTransportService
+ // can't handle it.
+ // Origin should be reachable on origin host name, so this should
+ // not be a problem - but log it.
+ LOG(("nsHalfOpenSocket this=%p using legacy nsISocketTransportService "
+ "means explicit route %s:%d will be ignored.\n", this,
+ ci->RoutedHost(), ci->RoutedPort()));
+ }
+
+ rv = sts->CreateTransport(socketTypes, typeCount,
+ ci->GetOrigin(), ci->OriginPort(),
+ ci->ProxyInfo(),
+ getter_AddRefs(socketTransport));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t tmpFlags = 0;
+ if (mCaps & NS_HTTP_REFRESH_DNS)
+ tmpFlags = nsISocketTransport::BYPASS_CACHE;
+
+ if (mCaps & NS_HTTP_LOAD_ANONYMOUS)
+ tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
+
+ if (ci->GetPrivate())
+ tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
+
+ if ((mCaps & NS_HTTP_BE_CONSERVATIVE) || ci->GetBeConservative()) {
+ LOG(("Setting Socket to BE_CONSERVATIVE"));
+ tmpFlags |= nsISocketTransport::BE_CONSERVATIVE;
+ }
+
+ // For backup connections, we disable IPv6. That's because some users have
+ // broken IPv6 connectivity (leading to very long timeouts), and disabling
+ // IPv6 on the backup connection gives them a much better user experience
+ // with dual-stack hosts, though they still pay the 250ms delay for each new
+ // connection. This strategy is also known as "happy eyeballs".
+ if (mEnt->mPreferIPv6) {
+ tmpFlags |= nsISocketTransport::DISABLE_IPV4;
+ }
+ else if (mEnt->mPreferIPv4 ||
+ (isBackup && gHttpHandler->FastFallbackToIPv4())) {
+ tmpFlags |= nsISocketTransport::DISABLE_IPV6;
+ }
+
+ if (!Allow1918()) {
+ tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
+ }
+
+ socketTransport->SetConnectionFlags(tmpFlags);
+
+ NeckoOriginAttributes originAttributes =
+ mEnt->mConnInfo->GetOriginAttributes();
+ if (originAttributes != NeckoOriginAttributes()) {
+ socketTransport->SetOriginAttributes(originAttributes);
+ }
+
+ socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
+
+ if (!ci->GetNetworkInterfaceId().IsEmpty()) {
+ socketTransport->SetNetworkInterfaceId(ci->GetNetworkInterfaceId());
+ }
+
+ rv = socketTransport->SetEventSink(this, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = socketTransport->SetSecurityCallbacks(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Telemetry::Accumulate(Telemetry::HTTP_CONNECTION_ENTRY_CACHE_HIT_1,
+ mEnt->mUsedForConnection);
+ mEnt->mUsedForConnection = true;
+
+ nsCOMPtr<nsIOutputStream> sout;
+ rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED,
+ 0, 0,
+ getter_AddRefs(sout));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> sin;
+ rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED,
+ 0, 0,
+ getter_AddRefs(sin));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ socketTransport.forget(transport);
+ CallQueryInterface(sin, instream);
+ CallQueryInterface(sout, outstream);
+
+ rv = (*outstream)->AsyncWait(this, 0, 0, nullptr);
+ if (NS_SUCCEEDED(rv))
+ gHttpHandler->ConnMgr()->StartedConnect();
+
+ return rv;
+}
+
+nsresult
+nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsresult rv;
+
+ mPrimarySynStarted = TimeStamp::Now();
+ rv = SetupStreams(getter_AddRefs(mSocketTransport),
+ getter_AddRefs(mStreamIn),
+ getter_AddRefs(mStreamOut),
+ false);
+ LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]",
+ this, mEnt->mConnInfo->Origin(), rv));
+ if (NS_FAILED(rv)) {
+ if (mStreamOut)
+ mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mStreamOut = nullptr;
+ mStreamIn = nullptr;
+ mSocketTransport = nullptr;
+ }
+ return rv;
+}
+
+nsresult
+nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams()
+{
+ MOZ_ASSERT(mTransaction);
+ MOZ_ASSERT(!mTransaction->IsNullTransaction(),
+ "null transactions dont have backup streams");
+
+ mBackupSynStarted = TimeStamp::Now();
+ nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
+ getter_AddRefs(mBackupStreamIn),
+ getter_AddRefs(mBackupStreamOut),
+ true);
+ LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%x]",
+ this, mEnt->mConnInfo->Origin(), rv));
+ if (NS_FAILED(rv)) {
+ if (mBackupStreamOut)
+ mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mBackupStreamOut = nullptr;
+ mBackupStreamIn = nullptr;
+ mBackupTransport = nullptr;
+ }
+ return rv;
+}
+
+void
+nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer()
+{
+ uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
+ MOZ_ASSERT(!mSynTimer, "timer already initd");
+ if (timeout && !mTransaction->IsDone() && !mTransaction->IsNullTransaction()) {
+ // Setup the timer that will establish a backup socket
+ // if we do not get a writable event on the main one.
+ // We do this because a lost SYN takes a very long time
+ // to repair at the TCP level.
+ //
+ // Failure to setup the timer is something we can live with,
+ // so don't return an error in that case.
+ nsresult rv;
+ mSynTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mSynTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
+ LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p]", this));
+ }
+ } else if (timeout) {
+ LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p], did not arm\n", this));
+ }
+}
+
+void
+nsHttpConnectionMgr::nsHalfOpenSocket::CancelBackupTimer()
+{
+ // If the syntimer is still armed, we can cancel it because no backup
+ // socket should be formed at this point
+ if (!mSynTimer)
+ return;
+
+ LOG(("nsHalfOpenSocket::CancelBackupTimer()"));
+ mSynTimer->Cancel();
+ mSynTimer = nullptr;
+}
+
+void
+nsHttpConnectionMgr::nsHalfOpenSocket::Abandon()
+{
+ LOG(("nsHalfOpenSocket::Abandon [this=%p ent=%s] %p %p %p %p",
+ this, mEnt->mConnInfo->Origin(),
+ mSocketTransport.get(), mBackupTransport.get(),
+ mStreamOut.get(), mBackupStreamOut.get()));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ RefPtr<nsHalfOpenSocket> deleteProtector(this);
+
+ // Tell socket (and backup socket) to forget the half open socket.
+ if (mSocketTransport) {
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport = nullptr;
+ }
+ if (mBackupTransport) {
+ mBackupTransport->SetEventSink(nullptr, nullptr);
+ mBackupTransport->SetSecurityCallbacks(nullptr);
+ mBackupTransport = nullptr;
+ }
+
+ // Tell output stream (and backup) to forget the half open socket.
+ if (mStreamOut) {
+ gHttpHandler->ConnMgr()->RecvdConnect();
+ mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mStreamOut = nullptr;
+ }
+ if (mBackupStreamOut) {
+ gHttpHandler->ConnMgr()->RecvdConnect();
+ mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mBackupStreamOut = nullptr;
+ }
+
+ // Lose references to input stream (and backup).
+ mStreamIn = mBackupStreamIn = nullptr;
+
+ // Stop the timer - we don't want any new backups.
+ CancelBackupTimer();
+
+ // Remove the half open from the connection entry.
+ if (mEnt)
+ mEnt->RemoveHalfOpen(this);
+ mEnt = nullptr;
+}
+
+double
+nsHttpConnectionMgr::nsHalfOpenSocket::Duration(TimeStamp epoch)
+{
+ if (mPrimarySynStarted.IsNull())
+ return 0;
+
+ return (epoch - mPrimarySynStarted).ToMilliseconds();
+}
+
+
+NS_IMETHODIMP // method for nsITimerCallback
+nsHttpConnectionMgr::nsHalfOpenSocket::Notify(nsITimer *timer)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(timer == mSynTimer, "wrong timer");
+ MOZ_ASSERT(mTransaction && !mTransaction->IsNullTransaction(),
+ "null transactions dont have backup streams");
+
+ SetupBackupStreams();
+
+ mSynTimer = nullptr;
+ return NS_OK;
+}
+
+// method for nsIAsyncOutputStreamCallback
+NS_IMETHODIMP
+nsHttpConnectionMgr::
+nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(out == mStreamOut || out == mBackupStreamOut,
+ "stream mismatch");
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady [this=%p ent=%s %s]\n",
+ this, mEnt->mConnInfo->Origin(),
+ out == mStreamOut ? "primary" : "backup"));
+ int32_t index;
+ nsresult rv;
+
+ gHttpHandler->ConnMgr()->RecvdConnect();
+
+ CancelBackupTimer();
+
+ // assign the new socket to the http connection
+ RefPtr<nsHttpConnection> conn = new nsHttpConnection();
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady "
+ "Created new nshttpconnection %p\n", conn.get()));
+
+ // Some capabilities are needed before a transaciton actually gets
+ // scheduled (e.g. how to negotiate false start)
+ conn->SetTransactionCaps(mTransaction->Caps());
+
+ NetAddr peeraddr;
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ if (out == mStreamOut) {
+ TimeDuration rtt = TimeStamp::Now() - mPrimarySynStarted;
+ rv = conn->Init(mEnt->mConnInfo,
+ gHttpHandler->ConnMgr()->mMaxRequestDelay,
+ mSocketTransport, mStreamIn, mStreamOut,
+ mPrimaryConnectedOK, callbacks,
+ PR_MillisecondsToInterval(
+ static_cast<uint32_t>(rtt.ToMilliseconds())));
+
+ if (NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr)))
+ mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
+
+ // The nsHttpConnection object now owns these streams and sockets
+ mStreamOut = nullptr;
+ mStreamIn = nullptr;
+ mSocketTransport = nullptr;
+ } else if (out == mBackupStreamOut) {
+ MOZ_ASSERT(!mTransaction->IsNullTransaction(),
+ "null transactions dont have backup streams");
+ TimeDuration rtt = TimeStamp::Now() - mBackupSynStarted;
+ rv = conn->Init(mEnt->mConnInfo,
+ gHttpHandler->ConnMgr()->mMaxRequestDelay,
+ mBackupTransport, mBackupStreamIn, mBackupStreamOut,
+ mBackupConnectedOK, callbacks,
+ PR_MillisecondsToInterval(
+ static_cast<uint32_t>(rtt.ToMilliseconds())));
+
+ if (NS_SUCCEEDED(mBackupTransport->GetPeerAddr(&peeraddr)))
+ mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
+
+ // The nsHttpConnection object now owns these streams and sockets
+ mBackupStreamOut = nullptr;
+ mBackupStreamIn = nullptr;
+ mBackupTransport = nullptr;
+ } else {
+ MOZ_ASSERT(false, "unexpected stream");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady "
+ "conn->init (%p) failed %x\n", conn.get(), rv));
+ return rv;
+ }
+
+ // This half-open socket has created a connection. This flag excludes it
+ // from counter of actual connections used for checking limits.
+ mHasConnected = true;
+
+ // if this is still in the pending list, remove it and dispatch it
+ index = mEnt->mPendingQ.IndexOf(mTransaction);
+ if (index != -1) {
+ MOZ_ASSERT(!mSpeculative,
+ "Speculative Half Open found mTransaction");
+ RefPtr<nsHttpTransaction> temp = mEnt->mPendingQ[index];
+ mEnt->mPendingQ.RemoveElementAt(index);
+ gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
+ rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, temp, conn);
+ } else {
+ // this transaction was dispatched off the pending q before all the
+ // sockets established themselves.
+
+ // After about 1 second allow for the possibility of restarting a
+ // transaction due to server close. Keep at sub 1 second as that is the
+ // minimum granularity we can expect a server to be timing out with.
+ conn->SetIsReusedAfter(950);
+
+ // if we are using ssl and no other transactions are waiting right now,
+ // then form a null transaction to drive the SSL handshake to
+ // completion. Afterwards the connection will be 100% ready for the next
+ // transaction to use it. Make an exception for SSL tunneled HTTP proxy as the
+ // NullHttpTransaction does not know how to drive Connect
+ if (mEnt->mConnInfo->FirstHopSSL() && !mEnt->mPendingQ.Length() &&
+ !mEnt->mConnInfo->UsingConnect()) {
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady null transaction will "
+ "be used to finish SSL handshake on conn %p\n", conn.get()));
+ RefPtr<nsAHttpTransaction> trans;
+ if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
+ // null transactions cannot be put in the entry queue, so that
+ // explains why it is not present.
+ mDispatchedMTransaction = true;
+ trans = mTransaction;
+ } else {
+ trans = new NullHttpTransaction(mEnt->mConnInfo,
+ callbacks,
+ mCaps & ~NS_HTTP_ALLOW_PIPELINING);
+ }
+
+ gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
+ conn->Classify(nsAHttpTransaction::CLASS_SOLO);
+ rv = gHttpHandler->ConnMgr()->
+ DispatchAbstractTransaction(mEnt, trans, mCaps, conn, 0);
+ } else {
+ // otherwise just put this in the persistent connection pool
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady no transaction match "
+ "returning conn %p to pool\n", conn.get()));
+ gHttpHandler->ConnMgr()->OnMsgReclaimConnection(0, conn);
+ }
+ }
+
+ return rv;
+}
+
+// method for nsITransportEventSink
+NS_IMETHODIMP
+nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans,
+ nsresult status,
+ int64_t progress,
+ int64_t progressMax)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mTransaction)
+ mTransaction->OnTransportStatus(trans, status, progress);
+
+ MOZ_ASSERT(trans == mSocketTransport || trans == mBackupTransport);
+ if (status == NS_NET_STATUS_CONNECTED_TO) {
+ if (trans == mSocketTransport) {
+ mPrimaryConnectedOK = true;
+ } else {
+ mBackupConnectedOK = true;
+ }
+ }
+
+ // The rest of this method only applies to the primary transport
+ if (trans != mSocketTransport) {
+ return NS_OK;
+ }
+
+ // if we are doing spdy coalescing and haven't recorded the ip address
+ // for this entry before then make the hash key if our dns lookup
+ // just completed. We can't do coalescing if using a proxy because the
+ // ip addresses are not available to the client.
+
+ if (status == NS_NET_STATUS_CONNECTING_TO &&
+ gHttpHandler->IsSpdyEnabled() &&
+ gHttpHandler->CoalesceSpdy() &&
+ mEnt && mEnt->mConnInfo && mEnt->mConnInfo->EndToEndSSL() &&
+ !mEnt->mConnInfo->UsingProxy() &&
+ mEnt->mCoalescingKeys.IsEmpty()) {
+
+ nsCOMPtr<nsIDNSRecord> dnsRecord(do_GetInterface(mSocketTransport));
+ nsTArray<NetAddr> addressSet;
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ if (dnsRecord) {
+ rv = dnsRecord->GetAddresses(addressSet);
+ }
+
+ if (NS_SUCCEEDED(rv) && !addressSet.IsEmpty()) {
+ for (uint32_t i = 0; i < addressSet.Length(); ++i) {
+ nsCString *newKey = mEnt->mCoalescingKeys.AppendElement(nsCString());
+ newKey->SetCapacity(kIPv6CStrBufSize + 26);
+ NetAddrToString(&addressSet[i], newKey->BeginWriting(), kIPv6CStrBufSize);
+ newKey->SetLength(strlen(newKey->BeginReading()));
+ if (mEnt->mConnInfo->GetAnonymous()) {
+ newKey->AppendLiteral("~A:");
+ } else {
+ newKey->AppendLiteral("~.:");
+ }
+ newKey->AppendInt(mEnt->mConnInfo->OriginPort());
+ LOG(("nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus "
+ "STATUS_CONNECTING_TO Established New Coalescing Key # %d for host "
+ "%s [%s]", i, mEnt->mConnInfo->Origin(), newKey->get()));
+ }
+ gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt);
+ }
+ }
+
+ switch (status) {
+ case NS_NET_STATUS_CONNECTING_TO:
+ // Passed DNS resolution, now trying to connect, start the backup timer
+ // only prevent creating another backup transport.
+ // We also check for mEnt presence to not instantiate the timer after
+ // this half open socket has already been abandoned. It may happen
+ // when we get this notification right between main-thread calls to
+ // nsHttpConnectionMgr::Shutdown and nsSocketTransportService::Shutdown
+ // where the first abandons all half open socket instances and only
+ // after that the second stops the socket thread.
+ if (mEnt && !mBackupTransport && !mSynTimer)
+ SetupBackupTimer();
+ break;
+
+ case NS_NET_STATUS_CONNECTED_TO:
+ // TCP connection's up, now transfer or SSL negotiantion starts,
+ // no need for backup socket
+ CancelBackupTimer();
+ break;
+
+ default:
+ break;
+ }
+
+ return NS_OK;
+}
+
+// method for nsIInterfaceRequestor
+NS_IMETHODIMP
+nsHttpConnectionMgr::nsHalfOpenSocket::GetInterface(const nsIID &iid,
+ void **result)
+{
+ if (mTransaction) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ if (callbacks)
+ return callbacks->GetInterface(iid, result);
+ }
+ return NS_ERROR_NO_INTERFACE;
+}
+
+
+already_AddRefed<nsHttpConnection>
+ConnectionHandle::TakeHttpConnection()
+{
+ // return our connection object to the caller and clear it internally
+ // do not drop our reference - the caller now owns it.
+ MOZ_ASSERT(mConn);
+ return mConn.forget();
+}
+
+uint32_t
+ConnectionHandle::CancelPipeline(nsresult reason)
+{
+ // no pipeline to cancel
+ return 0;
+}
+
+nsAHttpTransaction::Classifier
+ConnectionHandle::Classification()
+{
+ if (mConn)
+ return mConn->Classification();
+
+ LOG(("ConnectionHandle::Classification this=%p "
+ "has null mConn using CLASS_SOLO default", this));
+ return nsAHttpTransaction::CLASS_SOLO;
+}
+
+// nsConnectionEntry
+
+nsHttpConnectionMgr::
+nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci)
+ : mConnInfo(ci)
+ , mPipelineState(PS_YELLOW)
+ , mYellowGoodEvents(0)
+ , mYellowBadEvents(0)
+ , mYellowConnection(nullptr)
+ , mGreenDepth(kPipelineOpen)
+ , mPipeliningPenalty(0)
+ , mUsingSpdy(false)
+ , mInPreferredHash(false)
+ , mPreferIPv4(false)
+ , mPreferIPv6(false)
+ , mUsedForConnection(false)
+{
+ MOZ_COUNT_CTOR(nsConnectionEntry);
+ if (gHttpHandler->GetPipelineAggressive()) {
+ mGreenDepth = kPipelineUnlimited;
+ mPipelineState = PS_GREEN;
+ }
+ mInitialGreenDepth = mGreenDepth;
+ memset(mPipeliningClassPenalty, 0, sizeof(int16_t) * nsAHttpTransaction::CLASS_MAX);
+}
+
+bool
+nsHttpConnectionMgr::nsConnectionEntry::AvailableForDispatchNow()
+{
+ if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) {
+ return true;
+ }
+
+ return gHttpHandler->ConnMgr()->
+ GetSpdyPreferredConn(this) ? true : false;
+}
+
+bool
+nsHttpConnectionMgr::nsConnectionEntry::SupportsPipelining()
+{
+ return mPipelineState != nsHttpConnectionMgr::PS_RED;
+}
+
+nsHttpConnectionMgr::PipeliningState
+nsHttpConnectionMgr::nsConnectionEntry::PipelineState()
+{
+ return mPipelineState;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::OnPipelineFeedbackInfo(
+ nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+ nsHttpConnection *conn,
+ uint32_t data)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mPipelineState == PS_YELLOW) {
+ if (info & kPipelineInfoTypeBad)
+ mYellowBadEvents++;
+ else if (info & (kPipelineInfoTypeNeutral | kPipelineInfoTypeGood))
+ mYellowGoodEvents++;
+ }
+
+ if (mPipelineState == PS_GREEN && info == GoodCompletedOK) {
+ int32_t depth = data;
+ LOG(("Transaction completed at pipeline depth of %d. Host = %s\n",
+ depth, mConnInfo->Origin()));
+
+ if (depth >= 3)
+ mGreenDepth = kPipelineUnlimited;
+ }
+
+ nsAHttpTransaction::Classifier classification;
+ if (conn)
+ classification = conn->Classification();
+ else if (info == BadInsufficientFraming ||
+ info == BadUnexpectedLarge)
+ classification = (nsAHttpTransaction::Classifier) data;
+ else
+ classification = nsAHttpTransaction::CLASS_SOLO;
+
+ if (gHttpHandler->GetPipelineAggressive() &&
+ info & kPipelineInfoTypeBad &&
+ info != BadExplicitClose &&
+ info != RedVersionTooLow &&
+ info != RedBannedServer &&
+ info != RedCorruptedContent &&
+ info != BadInsufficientFraming) {
+ LOG(("minor negative feedback ignored "
+ "because of pipeline aggressive mode"));
+ }
+ else if (info & kPipelineInfoTypeBad) {
+ if ((info & kPipelineInfoTypeRed) && (mPipelineState != PS_RED)) {
+ LOG(("transition to red from %d. Host = %s.\n",
+ mPipelineState, mConnInfo->Origin()));
+ mPipelineState = PS_RED;
+ mPipeliningPenalty = 0;
+ }
+
+ if (mLastCreditTime.IsNull())
+ mLastCreditTime = TimeStamp::Now();
+
+ // Red* events impact the host globally via mPipeliningPenalty, while
+ // Bad* events impact the per class penalty.
+
+ // The individual penalties should be < 16bit-signed-maxint - 25000
+ // (approx 7500). Penalties are paid-off either when something promising
+ // happens (a successful transaction, or promising headers) or when
+ // time goes by at a rate of 1 penalty point every 16 seconds.
+
+ switch (info) {
+ case RedVersionTooLow:
+ mPipeliningPenalty += 1000;
+ break;
+ case RedBannedServer:
+ mPipeliningPenalty += 7000;
+ break;
+ case RedCorruptedContent:
+ mPipeliningPenalty += 7000;
+ break;
+ case RedCanceledPipeline:
+ mPipeliningPenalty += 60;
+ break;
+ case BadExplicitClose:
+ mPipeliningClassPenalty[classification] += 250;
+ break;
+ case BadSlowReadMinor:
+ mPipeliningClassPenalty[classification] += 5;
+ break;
+ case BadSlowReadMajor:
+ mPipeliningClassPenalty[classification] += 25;
+ break;
+ case BadInsufficientFraming:
+ mPipeliningClassPenalty[classification] += 7000;
+ break;
+ case BadUnexpectedLarge:
+ mPipeliningClassPenalty[classification] += 120;
+ break;
+
+ default:
+ MOZ_ASSERT(false, "Unknown Bad/Red Pipeline Feedback Event");
+ }
+
+ const int16_t kPenalty = 25000;
+ mPipeliningPenalty = std::min(mPipeliningPenalty, kPenalty);
+ mPipeliningClassPenalty[classification] =
+ std::min(mPipeliningClassPenalty[classification], kPenalty);
+
+ LOG(("Assessing red penalty to %s class %d for event %d. "
+ "Penalty now %d, throttle[%d] = %d\n", mConnInfo->Origin(),
+ classification, info, mPipeliningPenalty, classification,
+ mPipeliningClassPenalty[classification]));
+ }
+ else {
+ // hand out credits for neutral and good events such as
+ // "headers look ok" events
+
+ mPipeliningPenalty = std::max(mPipeliningPenalty - 1, 0);
+ mPipeliningClassPenalty[classification] = std::max(mPipeliningClassPenalty[classification] - 1, 0);
+ }
+
+ if (mPipelineState == PS_RED && !mPipeliningPenalty)
+ {
+ LOG(("transition %s to yellow\n", mConnInfo->Origin()));
+ mPipelineState = PS_YELLOW;
+ mYellowConnection = nullptr;
+ }
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::SetYellowConnection(nsHttpConnection *conn)
+{
+ MOZ_ASSERT(!mYellowConnection && mPipelineState == PS_YELLOW,
+ "yellow connection already set or state is not yellow");
+ mYellowConnection = conn;
+ mYellowGoodEvents = mYellowBadEvents = 0;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::OnYellowComplete()
+{
+ if (mPipelineState == PS_YELLOW) {
+ if (mYellowGoodEvents && !mYellowBadEvents) {
+ LOG(("transition %s to green\n", mConnInfo->Origin()));
+ mPipelineState = PS_GREEN;
+ mGreenDepth = mInitialGreenDepth;
+ }
+ else {
+ // The purpose of the yellow state is to witness at least
+ // one successful pipelined transaction without seeing any
+ // kind of negative feedback before opening the flood gates.
+ // If we haven't confirmed that, then transfer back to red.
+ LOG(("transition %s to red from yellow return\n",
+ mConnInfo->Origin()));
+ mPipelineState = PS_RED;
+ }
+ }
+
+ mYellowConnection = nullptr;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::CreditPenalty()
+{
+ if (mLastCreditTime.IsNull())
+ return;
+
+ // Decrease penalty values by 1 for every 16 seconds
+ // (i.e 3.7 per minute, or 1000 every 4h20m)
+
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration elapsedTime = now - mLastCreditTime;
+ uint32_t creditsEarned =
+ static_cast<uint32_t>(elapsedTime.ToSeconds()) >> 4;
+
+ bool failed = false;
+ if (creditsEarned > 0) {
+ mPipeliningPenalty =
+ std::max(int32_t(mPipeliningPenalty - creditsEarned), 0);
+ if (mPipeliningPenalty > 0)
+ failed = true;
+
+ for (int32_t i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) {
+ mPipeliningClassPenalty[i] =
+ std::max(int32_t(mPipeliningClassPenalty[i] - creditsEarned), 0);
+ failed = failed || (mPipeliningClassPenalty[i] > 0);
+ }
+
+ // update last credit mark to reflect elapsed time
+ mLastCreditTime += TimeDuration::FromSeconds(creditsEarned << 4);
+ }
+ else {
+ failed = true; /* just assume this */
+ }
+
+ // If we are no longer red then clear the credit counter - you only
+ // get credits for time spent in the red state
+ if (!failed)
+ mLastCreditTime = TimeStamp(); /* reset to null timestamp */
+
+ if (mPipelineState == PS_RED && !mPipeliningPenalty)
+ {
+ LOG(("transition %s to yellow based on time credit\n",
+ mConnInfo->Origin()));
+ mPipelineState = PS_YELLOW;
+ mYellowConnection = nullptr;
+ }
+}
+
+uint32_t
+nsHttpConnectionMgr::
+nsConnectionEntry::MaxPipelineDepth(nsAHttpTransaction::Classifier aClass)
+{
+ // Still subject to configuration limit no matter return value
+
+ if ((mPipelineState == PS_RED) || (mPipeliningClassPenalty[aClass] > 0))
+ return 0;
+
+ if (mPipelineState == PS_YELLOW)
+ return kPipelineRestricted;
+
+ return mGreenDepth;
+}
+
+bool
+nsHttpConnectionMgr::GetConnectionData(nsTArray<HttpRetParams> *aArg)
+{
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ if (ent->mConnInfo->GetPrivate()) {
+ continue;
+ }
+
+ HttpRetParams data;
+ data.host = ent->mConnInfo->Origin();
+ data.port = ent->mConnInfo->OriginPort();
+ for (uint32_t i = 0; i < ent->mActiveConns.Length(); i++) {
+ HttpConnInfo info;
+ info.ttl = ent->mActiveConns[i]->TimeToLive();
+ info.rtt = ent->mActiveConns[i]->Rtt();
+ if (ent->mActiveConns[i]->UsingSpdy()) {
+ info.SetHTTP2ProtocolVersion(
+ ent->mActiveConns[i]->GetSpdyVersion());
+ } else {
+ info.SetHTTP1ProtocolVersion(
+ ent->mActiveConns[i]->GetLastHttpResponseVersion());
+ }
+ data.active.AppendElement(info);
+ }
+ for (uint32_t i = 0; i < ent->mIdleConns.Length(); i++) {
+ HttpConnInfo info;
+ info.ttl = ent->mIdleConns[i]->TimeToLive();
+ info.rtt = ent->mIdleConns[i]->Rtt();
+ info.SetHTTP1ProtocolVersion(
+ ent->mIdleConns[i]->GetLastHttpResponseVersion());
+ data.idle.AppendElement(info);
+ }
+ for (uint32_t i = 0; i < ent->mHalfOpens.Length(); i++) {
+ HalfOpenSockets hSocket;
+ hSocket.speculative = ent->mHalfOpens[i]->IsSpeculative();
+ data.halfOpens.AppendElement(hSocket);
+ }
+ data.spdy = ent->mUsingSpdy;
+ data.ssl = ent->mConnInfo->EndToEndSSL();
+ aArg->AppendElement(data);
+ }
+
+ return true;
+}
+
+void
+nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo *ci)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsConnectionEntry *ent = LookupConnectionEntry(ci, nullptr, nullptr);
+ if (ent)
+ ent->ResetIPFamilyPreference();
+}
+
+uint32_t
+nsHttpConnectionMgr::
+nsConnectionEntry::UnconnectedHalfOpens()
+{
+ uint32_t unconnectedHalfOpens = 0;
+ for (uint32_t i = 0; i < mHalfOpens.Length(); ++i) {
+ if (!mHalfOpens[i]->HasConnected())
+ ++unconnectedHalfOpens;
+ }
+ return unconnectedHalfOpens;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::RemoveHalfOpen(nsHalfOpenSocket *halfOpen)
+{
+ // A failure to create the transport object at all
+ // will result in it not being present in the halfopen table. That's expected.
+ if (mHalfOpens.RemoveElement(halfOpen)) {
+
+ if (halfOpen->IsSpeculative()) {
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_UNUSED_SPECULATIVE_CONN> unusedSpeculativeConn;
+ ++unusedSpeculativeConn;
+
+ if (halfOpen->IsFromPredictor()) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_UNUSED> totalPreconnectsUnused;
+ ++totalPreconnectsUnused;
+ }
+ }
+
+ MOZ_ASSERT(gHttpHandler->ConnMgr()->mNumHalfOpenConns);
+ if (gHttpHandler->ConnMgr()->mNumHalfOpenConns) { // just in case
+ gHttpHandler->ConnMgr()->mNumHalfOpenConns--;
+ }
+ }
+
+ if (!UnconnectedHalfOpens())
+ // perhaps this reverted RestrictConnections()
+ // use the PostEvent version of processpendingq to avoid
+ // altering the pending q vector from an arbitrary stack
+ gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::RecordIPFamilyPreference(uint16_t family)
+{
+ if (family == PR_AF_INET && !mPreferIPv6)
+ mPreferIPv4 = true;
+
+ if (family == PR_AF_INET6 && !mPreferIPv4)
+ mPreferIPv6 = true;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::ResetIPFamilyPreference()
+{
+ mPreferIPv4 = false;
+ mPreferIPv6 = false;
+}
+
+void
+nsHttpConnectionMgr::MoveToWildCardConnEntry(nsHttpConnectionInfo *specificCI,
+ nsHttpConnectionInfo *wildCardCI,
+ nsHttpConnection *proxyConn)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(specificCI->UsingHttpsProxy());
+
+ LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p has requested to "
+ "change CI from %s to %s\n", proxyConn, specificCI->HashKey().get(),
+ wildCardCI->HashKey().get()));
+
+ nsConnectionEntry *ent = LookupConnectionEntry(specificCI, proxyConn, nullptr);
+ LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p using ent %p (spdy %d)\n",
+ proxyConn, ent, ent ? ent->mUsingSpdy : 0));
+
+ if (!ent || !ent->mUsingSpdy) {
+ return;
+ }
+
+ nsConnectionEntry *wcEnt = GetOrCreateConnectionEntry(wildCardCI, true);
+ if (wcEnt == ent) {
+ // nothing to do!
+ return;
+ }
+ wcEnt->mUsingSpdy = true;
+
+ LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard ent %p "
+ "idle=%d active=%d half=%d pending=%d\n", ent,
+ ent->mIdleConns.Length(), ent->mActiveConns.Length(),
+ ent->mHalfOpens.Length(), ent->mPendingQ.Length()));
+
+ LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard wc-ent %p "
+ "idle=%d active=%d half=%d pending=%d\n", wcEnt,
+ wcEnt->mIdleConns.Length(), wcEnt->mActiveConns.Length(),
+ wcEnt->mHalfOpens.Length(), wcEnt->mPendingQ.Length()));
+
+ int32_t count = ent->mActiveConns.Length();
+ RefPtr<nsHttpConnection> deleteProtector(proxyConn);
+ for (int32_t i = 0; i < count; ++i) {
+ if (ent->mActiveConns[i] == proxyConn) {
+ ent->mActiveConns.RemoveElementAt(i);
+ wcEnt->mActiveConns.InsertElementAt(0, proxyConn);
+ return;
+ }
+ }
+
+ count = ent->mIdleConns.Length();
+ for (int32_t i = 0; i < count; ++i) {
+ if (ent->mIdleConns[i] == proxyConn) {
+ ent->mIdleConns.RemoveElementAt(i);
+ wcEnt->mIdleConns.InsertElementAt(0, proxyConn);
+ return;
+ }
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h
new file mode 100644
index 000000000..7ca2a2b28
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -0,0 +1,636 @@
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpConnectionMgr_h__
+#define nsHttpConnectionMgr_h__
+
+#include "nsHttpConnection.h"
+#include "nsHttpTransaction.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsAutoPtr.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Attributes.h"
+#include "AlternateServices.h"
+#include "ARefBase.h"
+
+#include "nsIObserver.h"
+#include "nsITimer.h"
+
+class nsIHttpUpgradeListener;
+
+namespace mozilla {
+namespace net {
+class EventTokenBucket;
+class NullHttpTransaction;
+struct HttpRetParams;
+
+//-----------------------------------------------------------------------------
+
+// message handlers have this signature
+class nsHttpConnectionMgr;
+typedef void (nsHttpConnectionMgr:: *nsConnEventHandler)(int32_t, ARefBase *);
+
+class nsHttpConnectionMgr final : public nsIObserver
+ , public AltSvcCache
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ // parameter names
+ enum nsParamName {
+ MAX_CONNECTIONS,
+ MAX_PERSISTENT_CONNECTIONS_PER_HOST,
+ MAX_PERSISTENT_CONNECTIONS_PER_PROXY,
+ MAX_REQUEST_DELAY,
+ MAX_PIPELINED_REQUESTS,
+ MAX_OPTIMISTIC_PIPELINED_REQUESTS
+ };
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may only be called on the main thread.
+ //-------------------------------------------------------------------------
+
+ nsHttpConnectionMgr();
+
+ nsresult Init(uint16_t maxConnections,
+ uint16_t maxPersistentConnectionsPerHost,
+ uint16_t maxPersistentConnectionsPerProxy,
+ uint16_t maxRequestDelay,
+ uint16_t maxPipelinedRequests,
+ uint16_t maxOptimisticPipelinedRequests);
+ nsresult Shutdown();
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may be called on any thread.
+ //-------------------------------------------------------------------------
+
+ // Schedules next pruning of dead connection to happen after
+ // given time.
+ void PruneDeadConnectionsAfter(uint32_t time);
+
+ // Stops timer scheduled for next pruning of dead connections if
+ // there are no more idle connections or active spdy ones
+ void ConditionallyStopPruneDeadConnectionsTimer();
+
+ // Stops timer used for the read timeout tick if there are no currently
+ // active connections.
+ void ConditionallyStopTimeoutTick();
+
+ // adds a transaction to the list of managed transactions.
+ nsresult AddTransaction(nsHttpTransaction *, int32_t priority);
+
+ // called to reschedule the given transaction. it must already have been
+ // added to the connection manager via AddTransaction.
+ nsresult RescheduleTransaction(nsHttpTransaction *, int32_t priority);
+
+ // cancels a transaction w/ the given reason.
+ nsresult CancelTransaction(nsHttpTransaction *, nsresult reason);
+ nsresult CancelTransactions(nsHttpConnectionInfo *, nsresult reason);
+
+ // called to force the connection manager to prune its list of idle
+ // connections.
+ nsresult PruneDeadConnections();
+
+ // called to close active connections with no registered "traffic"
+ nsresult PruneNoTraffic();
+
+ // "VerifyTraffic" means marking connections now, and then check again in
+ // N seconds to see if there's been any traffic and if not, kill
+ // that connection.
+ nsresult VerifyTraffic();
+
+ // Close all idle persistent connections and prevent any active connections
+ // from being reused. Optional connection info resets CI specific
+ // information such as Happy Eyeballs history.
+ nsresult DoShiftReloadConnectionCleanup(nsHttpConnectionInfo *);
+
+ // called to get a reference to the socket transport service. the socket
+ // transport service is not available when the connection manager is down.
+ nsresult GetSocketThreadTarget(nsIEventTarget **);
+
+ // called to indicate a transaction for the connectionInfo is likely coming
+ // soon. The connection manager may use this information to start a TCP
+ // and/or SSL level handshake for that resource immediately so that it is
+ // ready when the transaction is submitted. No obligation is taken on by the
+ // connection manager, nor is the submitter obligated to actually submit a
+ // real transaction for this connectionInfo.
+ nsresult SpeculativeConnect(nsHttpConnectionInfo *,
+ nsIInterfaceRequestor *,
+ uint32_t caps = 0,
+ NullHttpTransaction * = nullptr);
+
+ // called when a connection is done processing a transaction. if the
+ // connection can be reused then it will be added to the idle list, else
+ // it will be closed.
+ nsresult ReclaimConnection(nsHttpConnection *conn);
+
+ // called by the main thread to execute the taketransport() logic on the
+ // socket thread after a 101 response has been received and the socket
+ // needs to be transferred to an expectant upgrade listener such as
+ // websockets.
+ nsresult CompleteUpgrade(nsAHttpConnection *aConn,
+ nsIHttpUpgradeListener *aUpgradeListener);
+
+ // called to update a parameter after the connection manager has already
+ // been initialized.
+ nsresult UpdateParam(nsParamName name, uint16_t value);
+
+ // called from main thread to post a new request token bucket
+ // to the socket thread
+ nsresult UpdateRequestTokenBucket(EventTokenBucket *aBucket);
+
+ // clears the connection history mCT
+ nsresult ClearConnectionHistory();
+
+ // Pipielining Interfaces and Datatypes
+
+ const static uint32_t kPipelineInfoTypeMask = 0xffff0000;
+ const static uint32_t kPipelineInfoIDMask = ~kPipelineInfoTypeMask;
+
+ const static uint32_t kPipelineInfoTypeRed = 0x00010000;
+ const static uint32_t kPipelineInfoTypeBad = 0x00020000;
+ const static uint32_t kPipelineInfoTypeNeutral = 0x00040000;
+ const static uint32_t kPipelineInfoTypeGood = 0x00080000;
+
+ enum PipelineFeedbackInfoType
+ {
+ // Used when an HTTP response less than 1.1 is received
+ RedVersionTooLow = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0001,
+
+ // Used when a HTTP Server response header that is on the banned from
+ // pipelining list is received
+ RedBannedServer = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0002,
+
+ // Used when a response is terminated early, when it fails an
+ // integrity check such as assoc-req or when a 304 contained a Last-Modified
+ // differnet than the entry being validated.
+ RedCorruptedContent = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0004,
+
+ // Used when a pipeline is only partly satisfied - for instance if the
+ // server closed the connection after responding to the first
+ // request but left some requests unprocessed.
+ RedCanceledPipeline = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0005,
+
+ // Used when a connection that we expected to stay persistently open
+ // was closed by the server. Not used when simply timed out.
+ BadExplicitClose = kPipelineInfoTypeBad | 0x0003,
+
+ // Used when there is a gap of around 400 - 1200ms in between data being
+ // read from the server
+ BadSlowReadMinor = kPipelineInfoTypeBad | 0x0006,
+
+ // Used when there is a gap of > 1200ms in between data being
+ // read from the server
+ BadSlowReadMajor = kPipelineInfoTypeBad | 0x0007,
+
+ // Used when a response is received that is not framed with either chunked
+ // encoding or a complete content length.
+ BadInsufficientFraming = kPipelineInfoTypeBad | 0x0008,
+
+ // Used when a very large response is recevied in a potential pipelining
+ // context. Large responses cause head of line blocking.
+ BadUnexpectedLarge = kPipelineInfoTypeBad | 0x000B,
+
+ // Used when a response is received that has headers that appear to support
+ // pipelining.
+ NeutralExpectedOK = kPipelineInfoTypeNeutral | 0x0009,
+
+ // Used when a response is received successfully to a pipelined request.
+ GoodCompletedOK = kPipelineInfoTypeGood | 0x000A
+ };
+
+ // called to provide information relevant to the pipelining manager
+ // may be called from any thread
+ void PipelineFeedbackInfo(nsHttpConnectionInfo *,
+ PipelineFeedbackInfoType info,
+ nsHttpConnection *,
+ uint32_t);
+
+ void ReportFailedToProcess(nsIURI *uri);
+
+ // Causes a large amount of connection diagnostic information to be
+ // printed to the javascript console
+ void PrintDiagnostics();
+
+ //-------------------------------------------------------------------------
+ // NOTE: functions below may be called only on the socket thread.
+ //-------------------------------------------------------------------------
+
+ // called to change the connection entry associated with conn from specific into
+ // a wildcard (i.e. http2 proxy friendy) mapping
+ void MoveToWildCardConnEntry(nsHttpConnectionInfo *specificCI,
+ nsHttpConnectionInfo *wildcardCI,
+ nsHttpConnection *conn);
+
+ // called to force the transaction queue to be processed once more, giving
+ // preference to the specified connection.
+ nsresult ProcessPendingQ(nsHttpConnectionInfo *);
+ bool ProcessPendingQForEntry(nsHttpConnectionInfo *);
+
+ // Try and process all pending transactions
+ nsresult ProcessPendingQ();
+
+ // This is used to force an idle connection to be closed and removed from
+ // the idle connection list. It is called when the idle connection detects
+ // that the network peer has closed the transport.
+ nsresult CloseIdleConnection(nsHttpConnection *);
+
+ // The connection manager needs to know when a normal HTTP connection has been
+ // upgraded to SPDY because the dispatch and idle semantics are a little
+ // bit different.
+ void ReportSpdyConnection(nsHttpConnection *, bool usingSpdy);
+
+ bool SupportsPipelining(nsHttpConnectionInfo *);
+
+ bool GetConnectionData(nsTArray<HttpRetParams> *);
+
+ void ResetIPFamilyPreference(nsHttpConnectionInfo *);
+
+ uint16_t MaxRequestDelay() { return mMaxRequestDelay; }
+
+ // public, so that the SPDY/http2 seesions can activate
+ void ActivateTimeoutTick();
+
+private:
+ virtual ~nsHttpConnectionMgr();
+
+ enum PipeliningState {
+ // Host has proven itself pipeline capable through past experience and
+ // large pipeline depths are allowed on multiple connections.
+ PS_GREEN,
+
+ // Not enough information is available yet with this host to be certain
+ // of pipeline capability. Small pipelines on a single connection are
+ // allowed in order to decide whether or not to proceed to green.
+ PS_YELLOW,
+
+ // One or more bad events has happened that indicate that pipelining
+ // to this host (or a particular type of transaction with this host)
+ // is a bad idea. Pipelining is not currently allowed, but time and
+ // other positive experiences will eventually allow it to try again.
+ PS_RED
+ };
+
+ class nsHalfOpenSocket;
+
+ // nsConnectionEntry
+ //
+ // mCT maps connection info hash key to nsConnectionEntry object, which
+ // contains list of active and idle connections as well as the list of
+ // pending transactions.
+ //
+ class nsConnectionEntry
+ {
+ public:
+ explicit nsConnectionEntry(nsHttpConnectionInfo *ci);
+ ~nsConnectionEntry();
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ nsTArray<RefPtr<nsHttpTransaction> > mPendingQ; // pending transaction queue
+ nsTArray<RefPtr<nsHttpConnection> > mActiveConns; // active connections
+ nsTArray<RefPtr<nsHttpConnection> > mIdleConns; // idle persistent connections
+ nsTArray<nsHalfOpenSocket*> mHalfOpens; // half open connections
+
+ bool AvailableForDispatchNow();
+
+ // calculate the number of half open sockets that have not had at least 1
+ // connection complete
+ uint32_t UnconnectedHalfOpens();
+
+ // Remove a particular half open socket from the mHalfOpens array
+ void RemoveHalfOpen(nsHalfOpenSocket *);
+
+ // Pipeline depths for various states
+ const static uint32_t kPipelineUnlimited = 1024; // fully open - extended green
+ const static uint32_t kPipelineOpen = 6; // 6 on each conn - normal green
+ const static uint32_t kPipelineRestricted = 2; // 2 on just 1 conn in yellow
+
+ nsHttpConnectionMgr::PipeliningState PipelineState();
+ void OnPipelineFeedbackInfo(
+ nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+ nsHttpConnection *, uint32_t);
+ bool SupportsPipelining();
+ uint32_t MaxPipelineDepth(nsAHttpTransaction::Classifier classification);
+ void CreditPenalty();
+
+ nsHttpConnectionMgr::PipeliningState mPipelineState;
+
+ void SetYellowConnection(nsHttpConnection *);
+ void OnYellowComplete();
+ uint32_t mYellowGoodEvents;
+ uint32_t mYellowBadEvents;
+ nsHttpConnection *mYellowConnection;
+
+ // initialGreenDepth is the max depth of a pipeline when you first
+ // transition to green. Normally this is kPipelineOpen, but it can
+ // be kPipelineUnlimited in aggressive mode.
+ uint32_t mInitialGreenDepth;
+
+ // greenDepth is the current max allowed depth of a pipeline when
+ // in the green state. Normally this starts as kPipelineOpen and
+ // grows to kPipelineUnlimited after a pipeline of depth 3 has been
+ // successfully transacted.
+ uint32_t mGreenDepth;
+
+ // pipeliningPenalty is the current amount of penalty points this host
+ // entry has earned for participating in events that are not conducive
+ // to good pipelines - such as head of line blocking, canceled pipelines,
+ // etc.. penalties are paid back either through elapsed time or simply
+ // healthy transactions. Having penalty points means that this host is
+ // not currently eligible for pipelines.
+ int16_t mPipeliningPenalty;
+
+ // some penalty points only apply to particular classifications of
+ // transactions - this allows a server that perhaps has head of line
+ // blocking problems on CGI queries to still serve JS pipelined.
+ int16_t mPipeliningClassPenalty[nsAHttpTransaction::CLASS_MAX];
+
+ // for calculating penalty repair credits
+ TimeStamp mLastCreditTime;
+
+ // Spdy sometimes resolves the address in the socket manager in order
+ // to re-coalesce sharded HTTP hosts. The dotted decimal address is
+ // combined with the Anonymous flag from the connection information
+ // to build the hash key for hosts in the same ip pool.
+ //
+ // When a set of hosts are coalesced together one of them is marked
+ // mSpdyPreferred. The mapping is maintained in the connection mananger
+ // mSpdyPreferred hash.
+ //
+ nsTArray<nsCString> mCoalescingKeys;
+
+ // To have the UsingSpdy flag means some host with the same connection
+ // entry has done NPN=spdy/* at some point. It does not mean every
+ // connection is currently using spdy.
+ bool mUsingSpdy : 1;
+
+ bool mInPreferredHash : 1;
+
+ // Flags to remember our happy-eyeballs decision.
+ // Reset only by Ctrl-F5 reload.
+ // True when we've first connected an IPv4 server for this host,
+ // initially false.
+ bool mPreferIPv4 : 1;
+ // True when we've first connected an IPv6 server for this host,
+ // initially false.
+ bool mPreferIPv6 : 1;
+
+ // True if this connection entry has initiated a socket
+ bool mUsedForConnection : 1;
+
+ // Set the IP family preference flags according the connected family
+ void RecordIPFamilyPreference(uint16_t family);
+ // Resets all flags to their default values
+ void ResetIPFamilyPreference();
+ };
+
+public:
+ static nsAHttpConnection *MakeConnectionHandle(nsHttpConnection *aWrapped);
+
+private:
+
+ // nsHalfOpenSocket is used to hold the state of an opening TCP socket
+ // while we wait for it to establish and bind it to a connection
+
+ class nsHalfOpenSocket final : public nsIOutputStreamCallback,
+ public nsITransportEventSink,
+ public nsIInterfaceRequestor,
+ public nsITimerCallback
+ {
+ ~nsHalfOpenSocket();
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSITIMERCALLBACK
+
+ nsHalfOpenSocket(nsConnectionEntry *ent,
+ nsAHttpTransaction *trans,
+ uint32_t caps);
+
+ nsresult SetupStreams(nsISocketTransport **,
+ nsIAsyncInputStream **,
+ nsIAsyncOutputStream **,
+ bool isBackup);
+ nsresult SetupPrimaryStreams();
+ nsresult SetupBackupStreams();
+ void SetupBackupTimer();
+ void CancelBackupTimer();
+ void Abandon();
+ double Duration(TimeStamp epoch);
+ nsISocketTransport *SocketTransport() { return mSocketTransport; }
+ nsISocketTransport *BackupTransport() { return mBackupTransport; }
+
+ nsAHttpTransaction *Transaction() { return mTransaction; }
+
+ bool IsSpeculative() { return mSpeculative; }
+ void SetSpeculative(bool val) { mSpeculative = val; }
+
+ bool IsFromPredictor() { return mIsFromPredictor; }
+ void SetIsFromPredictor(bool val) { mIsFromPredictor = val; }
+
+ bool Allow1918() { return mAllow1918; }
+ void SetAllow1918(bool val) { mAllow1918 = val; }
+
+ bool HasConnected() { return mHasConnected; }
+
+ void PrintDiagnostics(nsCString &log);
+ private:
+ nsConnectionEntry *mEnt;
+ RefPtr<nsAHttpTransaction> mTransaction;
+ bool mDispatchedMTransaction;
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+ nsCOMPtr<nsIAsyncOutputStream> mStreamOut;
+ nsCOMPtr<nsIAsyncInputStream> mStreamIn;
+ uint32_t mCaps;
+
+ // mSpeculative is set if the socket was created from
+ // SpeculativeConnect(). It is cleared when a transaction would normally
+ // start a new connection from scratch but instead finds this one in
+ // the half open list and claims it for its own use. (which due to
+ // the vagaries of scheduling from the pending queue might not actually
+ // match up - but it prevents a speculative connection from opening
+ // more connections that are needed.)
+ bool mSpeculative;
+
+ // mIsFromPredictor is set if the socket originated from the network
+ // Predictor. It is used to gather telemetry data on used speculative
+ // connections from the predictor.
+ bool mIsFromPredictor;
+
+ bool mAllow1918;
+
+ TimeStamp mPrimarySynStarted;
+ TimeStamp mBackupSynStarted;
+
+ // for syn retry
+ nsCOMPtr<nsITimer> mSynTimer;
+ nsCOMPtr<nsISocketTransport> mBackupTransport;
+ nsCOMPtr<nsIAsyncOutputStream> mBackupStreamOut;
+ nsCOMPtr<nsIAsyncInputStream> mBackupStreamIn;
+
+ // mHasConnected tracks whether one of the sockets has completed the
+ // connection process. It may have completed unsuccessfully.
+ bool mHasConnected;
+
+ bool mPrimaryConnectedOK;
+ bool mBackupConnectedOK;
+ };
+ friend class nsHalfOpenSocket;
+
+ //-------------------------------------------------------------------------
+ // NOTE: these members may be accessed from any thread (use mReentrantMonitor)
+ //-------------------------------------------------------------------------
+
+ ReentrantMonitor mReentrantMonitor;
+ nsCOMPtr<nsIEventTarget> mSocketThreadTarget;
+
+ // connection limits
+ uint16_t mMaxConns;
+ uint16_t mMaxPersistConnsPerHost;
+ uint16_t mMaxPersistConnsPerProxy;
+ uint16_t mMaxRequestDelay; // in seconds
+ uint16_t mMaxPipelinedRequests;
+ uint16_t mMaxOptimisticPipelinedRequests;
+ Atomic<bool, mozilla::Relaxed> mIsShuttingDown;
+
+ //-------------------------------------------------------------------------
+ // NOTE: these members are only accessed on the socket transport thread
+ //-------------------------------------------------------------------------
+
+ bool ProcessPendingQForEntry(nsConnectionEntry *, bool considerAll);
+ bool IsUnderPressure(nsConnectionEntry *ent,
+ nsHttpTransaction::Classifier classification);
+ bool AtActiveConnectionLimit(nsConnectionEntry *, uint32_t caps);
+ nsresult TryDispatchTransaction(nsConnectionEntry *ent,
+ bool onlyReusedConnection,
+ nsHttpTransaction *trans);
+ nsresult DispatchTransaction(nsConnectionEntry *,
+ nsHttpTransaction *,
+ nsHttpConnection *);
+ nsresult DispatchAbstractTransaction(nsConnectionEntry *,
+ nsAHttpTransaction *,
+ uint32_t,
+ nsHttpConnection *,
+ int32_t);
+ nsresult BuildPipeline(nsConnectionEntry *,
+ nsAHttpTransaction *,
+ nsHttpPipeline **);
+ bool RestrictConnections(nsConnectionEntry *);
+ nsresult ProcessNewTransaction(nsHttpTransaction *);
+ nsresult EnsureSocketThreadTarget();
+ void ClosePersistentConnections(nsConnectionEntry *ent);
+ void ReportProxyTelemetry(nsConnectionEntry *ent);
+ nsresult CreateTransport(nsConnectionEntry *, nsAHttpTransaction *,
+ uint32_t, bool, bool, bool);
+ void AddActiveConn(nsHttpConnection *, nsConnectionEntry *);
+ void DecrementActiveConnCount(nsHttpConnection *);
+ void StartedConnect();
+ void RecvdConnect();
+
+ nsConnectionEntry *GetOrCreateConnectionEntry(nsHttpConnectionInfo *,
+ bool allowWildCard);
+
+ nsresult MakeNewConnection(nsConnectionEntry *ent,
+ nsHttpTransaction *trans);
+ bool AddToShortestPipeline(nsConnectionEntry *ent,
+ nsHttpTransaction *trans,
+ nsHttpTransaction::Classifier classification,
+ uint16_t depthLimit);
+
+ // Manage the preferred spdy connection entry for this address
+ nsConnectionEntry *GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry);
+ nsConnectionEntry *LookupPreferredHash(nsConnectionEntry *ent);
+ void StorePreferredHash(nsConnectionEntry *ent);
+ void RemovePreferredHash(nsConnectionEntry *ent);
+ nsHttpConnection *GetSpdyPreferredConn(nsConnectionEntry *ent);
+ nsDataHashtable<nsCStringHashKey, nsConnectionEntry *> mSpdyPreferredHash;
+ nsConnectionEntry *LookupConnectionEntry(nsHttpConnectionInfo *ci,
+ nsHttpConnection *conn,
+ nsHttpTransaction *trans);
+
+ void ProcessSpdyPendingQ(nsConnectionEntry *ent);
+
+ // used to marshall events to the socket transport thread.
+ nsresult PostEvent(nsConnEventHandler handler,
+ int32_t iparam = 0,
+ ARefBase *vparam = nullptr);
+
+ // message handlers
+ void OnMsgShutdown (int32_t, ARefBase *);
+ void OnMsgShutdownConfirm (int32_t, ARefBase *);
+ void OnMsgNewTransaction (int32_t, ARefBase *);
+ void OnMsgReschedTransaction (int32_t, ARefBase *);
+ void OnMsgCancelTransaction (int32_t, ARefBase *);
+ void OnMsgCancelTransactions (int32_t, ARefBase *);
+ void OnMsgProcessPendingQ (int32_t, ARefBase *);
+ void OnMsgPruneDeadConnections (int32_t, ARefBase *);
+ void OnMsgSpeculativeConnect (int32_t, ARefBase *);
+ void OnMsgReclaimConnection (int32_t, ARefBase *);
+ void OnMsgCompleteUpgrade (int32_t, ARefBase *);
+ void OnMsgUpdateParam (int32_t, ARefBase *);
+ void OnMsgDoShiftReloadConnectionCleanup (int32_t, ARefBase *);
+ void OnMsgProcessFeedback (int32_t, ARefBase *);
+ void OnMsgProcessAllSpdyPendingQ (int32_t, ARefBase *);
+ void OnMsgUpdateRequestTokenBucket (int32_t, ARefBase *);
+ void OnMsgVerifyTraffic (int32_t, ARefBase *);
+ void OnMsgPruneNoTraffic (int32_t, ARefBase *);
+
+ // Total number of active connections in all of the ConnectionEntry objects
+ // that are accessed from mCT connection table.
+ uint16_t mNumActiveConns;
+ // Total number of idle connections in all of the ConnectionEntry objects
+ // that are accessed from mCT connection table.
+ uint16_t mNumIdleConns;
+ // Total number of spdy connections which are a subset of the active conns
+ uint16_t mNumSpdyActiveConns;
+ // Total number of connections in mHalfOpens ConnectionEntry objects
+ // that are accessed from mCT connection table
+ uint32_t mNumHalfOpenConns;
+
+ // Holds time in seconds for next wake-up to prune dead connections.
+ uint64_t mTimeOfNextWakeUp;
+ // Timer for next pruning of dead connections.
+ nsCOMPtr<nsITimer> mTimer;
+ // Timer for pruning stalled connections after changed network.
+ nsCOMPtr<nsITimer> mTrafficTimer;
+ bool mPruningNoTraffic;
+
+ // A 1s tick to call nsHttpConnection::ReadTimeoutTick on
+ // active http/1 connections and check for orphaned half opens.
+ // Disabled when there are no active or half open connections.
+ nsCOMPtr<nsITimer> mTimeoutTick;
+ bool mTimeoutTickArmed;
+ uint32_t mTimeoutTickNext;
+
+ //
+ // the connection table
+ //
+ // this table is indexed by connection key. each entry is a
+ // nsConnectionEntry object. It is unlocked and therefore must only
+ // be accessed from the socket thread.
+ //
+ nsClassHashtable<nsCStringHashKey, nsConnectionEntry> mCT;
+
+ // Read Timeout Tick handlers
+ void TimeoutTick();
+
+ // For diagnostics
+ void OnMsgPrintDiagnostics(int32_t, ARefBase *);
+
+ nsCString mLogData;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsHttpConnectionMgr_h__
diff --git a/netwerk/protocol/http/nsHttpDigestAuth.cpp b/netwerk/protocol/http/nsHttpDigestAuth.cpp
new file mode 100644
index 000000000..235f391bc
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpDigestAuth.cpp
@@ -0,0 +1,722 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/Sprintf.h"
+
+#include "nsHttp.h"
+#include "nsHttpDigestAuth.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsEscape.h"
+#include "nsNetCID.h"
+#include "nsCRT.h"
+#include "nsICryptoHash.h"
+#include "nsComponentManagerUtils.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth <public>
+//-----------------------------------------------------------------------------
+
+nsHttpDigestAuth::nsHttpDigestAuth()
+{}
+
+nsHttpDigestAuth::~nsHttpDigestAuth()
+{}
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpDigestAuth, nsIHttpAuthenticator)
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth <protected>
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpDigestAuth::MD5Hash(const char *buf, uint32_t len)
+{
+ nsresult rv;
+
+ // Cache a reference to the nsICryptoHash instance since we'll be calling
+ // this function frequently.
+ if (!mVerifier) {
+ mVerifier = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpDigestAuth: no crypto hash!\n"));
+ return rv;
+ }
+ }
+
+ rv = mVerifier->Init(nsICryptoHash::MD5);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mVerifier->Update((unsigned char*)buf, len);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString hashString;
+ rv = mVerifier->Finish(false, hashString);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ENSURE_STATE(hashString.Length() == sizeof(mHashBuf));
+ memcpy(mHashBuf, hashString.get(), hashString.Length());
+
+ return rv;
+}
+
+nsresult
+nsHttpDigestAuth::GetMethodAndPath(nsIHttpAuthenticableChannel *authChannel,
+ bool isProxyAuth,
+ nsCString &httpMethod,
+ nsCString &path)
+{
+ nsresult rv, rv2;
+ nsCOMPtr<nsIURI> uri;
+ rv = authChannel->GetURI(getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv)) {
+ bool proxyMethodIsConnect;
+ rv = authChannel->GetProxyMethodIsConnect(&proxyMethodIsConnect);
+ if (NS_SUCCEEDED(rv)) {
+ if (proxyMethodIsConnect && isProxyAuth) {
+ httpMethod.AssignLiteral("CONNECT");
+ //
+ // generate hostname:port string. (unfortunately uri->GetHostPort
+ // leaves out the port if it matches the default value, so we can't
+ // just call it.)
+ //
+ int32_t port;
+ rv = uri->GetAsciiHost(path);
+ rv2 = uri->GetPort(&port);
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2)) {
+ path.Append(':');
+ path.AppendInt(port < 0 ? NS_HTTPS_DEFAULT_PORT : port);
+ }
+ }
+ else {
+ rv = authChannel->GetRequestMethod(httpMethod);
+ rv2 = uri->GetPath(path);
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2)) {
+ //
+ // strip any fragment identifier from the URL path.
+ //
+ int32_t ref = path.RFindChar('#');
+ if (ref != kNotFound)
+ path.Truncate(ref);
+ //
+ // make sure we escape any UTF-8 characters in the URI path. the
+ // digest auth uri attribute needs to match the request-URI.
+ //
+ // XXX we should really ask the HTTP channel for this string
+ // instead of regenerating it here.
+ //
+ nsAutoCString buf;
+ rv = NS_EscapeURL(path, esc_OnlyNonASCII, buf, mozilla::fallible);
+ if (NS_SUCCEEDED(rv)) {
+ path = buf;
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth::nsIHttpAuthenticator
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpDigestAuth::ChallengeReceived(nsIHttpAuthenticableChannel *authChannel,
+ const char *challenge,
+ bool isProxyAuth,
+ nsISupports **sessionState,
+ nsISupports **continuationState,
+ bool *result)
+{
+ nsAutoCString realm, domain, nonce, opaque;
+ bool stale;
+ uint16_t algorithm, qop;
+
+ nsresult rv = ParseChallenge(challenge, realm, domain, nonce, opaque,
+ &stale, &algorithm, &qop);
+ if (NS_FAILED(rv)) return rv;
+
+ // if the challenge has the "stale" flag set, then the user identity is not
+ // necessarily invalid. by returning FALSE here we can suppress username
+ // and password prompting that usually accompanies a 401/407 challenge.
+ *result = !stale;
+
+ // clear any existing nonce_count since we have a new challenge.
+ NS_IF_RELEASE(*sessionState);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsHttpDigestAuth::GenerateCredentialsAsync(nsIHttpAuthenticableChannel *authChannel,
+ nsIHttpAuthenticatorCallback* aCallback,
+ const char *challenge,
+ bool isProxyAuth,
+ const char16_t *domain,
+ const char16_t *username,
+ const char16_t *password,
+ nsISupports *sessionState,
+ nsISupports *continuationState,
+ nsICancelable **aCancellable)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpDigestAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel,
+ const char *challenge,
+ bool isProxyAuth,
+ const char16_t *userdomain,
+ const char16_t *username,
+ const char16_t *password,
+ nsISupports **sessionState,
+ nsISupports **continuationState,
+ uint32_t *aFlags,
+ char **creds)
+
+{
+ LOG(("nsHttpDigestAuth::GenerateCredentials [challenge=%s]\n", challenge));
+
+ NS_ENSURE_ARG_POINTER(creds);
+
+ *aFlags = 0;
+
+ bool isDigestAuth = !PL_strncasecmp(challenge, "digest ", 7);
+ NS_ENSURE_TRUE(isDigestAuth, NS_ERROR_UNEXPECTED);
+
+ // IIS implementation requires extra quotes
+ bool requireExtraQuotes = false;
+ {
+ nsAutoCString serverVal;
+ authChannel->GetServerResponseHeader(serverVal);
+ if (!serverVal.IsEmpty()) {
+ requireExtraQuotes = !PL_strncasecmp(serverVal.get(), "Microsoft-IIS", 13);
+ }
+ }
+
+ nsresult rv;
+ nsAutoCString httpMethod;
+ nsAutoCString path;
+ rv = GetMethodAndPath(authChannel, isProxyAuth, httpMethod, path);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString realm, domain, nonce, opaque;
+ bool stale;
+ uint16_t algorithm, qop;
+
+ rv = ParseChallenge(challenge, realm, domain, nonce, opaque,
+ &stale, &algorithm, &qop);
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpDigestAuth::GenerateCredentials [ParseChallenge failed rv=%x]\n", rv));
+ return rv;
+ }
+
+ char ha1_digest[EXPANDED_DIGEST_LENGTH+1];
+ char ha2_digest[EXPANDED_DIGEST_LENGTH+1];
+ char response_digest[EXPANDED_DIGEST_LENGTH+1];
+ char upload_data_digest[EXPANDED_DIGEST_LENGTH+1];
+
+ if (qop & QOP_AUTH_INT) {
+ // we do not support auth-int "quality of protection" currently
+ qop &= ~QOP_AUTH_INT;
+
+ NS_WARNING("no support for Digest authentication with data integrity quality of protection");
+
+ /* TODO: to support auth-int, we need to get an MD5 digest of
+ * TODO: the data uploaded with this request.
+ * TODO: however, i am not sure how to read in the file in without
+ * TODO: disturbing the channel''s use of it. do i need to copy it
+ * TODO: somehow?
+ */
+#if 0
+ if (http_channel != nullptr)
+ {
+ nsIInputStream * upload;
+ nsCOMPtr<nsIUploadChannel> uc = do_QueryInterface(http_channel);
+ NS_ENSURE_TRUE(uc, NS_ERROR_UNEXPECTED);
+ uc->GetUploadStream(&upload);
+ if (upload) {
+ char * upload_buffer;
+ int upload_buffer_length = 0;
+ //TODO: read input stream into buffer
+ const char * digest = (const char*)
+ nsNetwerkMD5Digest(upload_buffer, upload_buffer_length);
+ ExpandToHex(digest, upload_data_digest);
+ NS_RELEASE(upload);
+ }
+ }
+#endif
+ }
+
+ if (!(algorithm & ALGO_MD5 || algorithm & ALGO_MD5_SESS)) {
+ // they asked only for algorithms that we do not support
+ NS_WARNING("unsupported algorithm requested by Digest authentication");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ //
+ // the following are for increasing security. see RFC 2617 for more
+ // information.
+ //
+ // nonce_count allows the server to keep track of auth challenges (to help
+ // prevent spoofing). we increase this count every time.
+ //
+ char nonce_count[NONCE_COUNT_LENGTH+1] = "00000001"; // in hex
+ if (*sessionState) {
+ nsCOMPtr<nsISupportsPRUint32> v(do_QueryInterface(*sessionState));
+ if (v) {
+ uint32_t nc;
+ v->GetData(&nc);
+ SprintfLiteral(nonce_count, "%08x", ++nc);
+ v->SetData(nc);
+ }
+ }
+ else {
+ nsCOMPtr<nsISupportsPRUint32> v(
+ do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID));
+ if (v) {
+ v->SetData(1);
+ v.forget(sessionState);
+ }
+ }
+ LOG((" nonce_count=%s\n", nonce_count));
+
+ //
+ // this lets the client verify the server response (via a server
+ // returned Authentication-Info header). also used for session info.
+ //
+ nsAutoCString cnonce;
+ static const char hexChar[] = "0123456789abcdef";
+ for (int i=0; i<16; ++i) {
+ cnonce.Append(hexChar[(int)(15.0 * rand()/(RAND_MAX + 1.0))]);
+ }
+ LOG((" cnonce=%s\n", cnonce.get()));
+
+ //
+ // calculate credentials
+ //
+
+ NS_ConvertUTF16toUTF8 cUser(username), cPass(password);
+ rv = CalculateHA1(cUser, cPass, realm, algorithm, nonce, cnonce, ha1_digest);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = CalculateHA2(httpMethod, path, qop, upload_data_digest, ha2_digest);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = CalculateResponse(ha1_digest, ha2_digest, nonce, qop, nonce_count,
+ cnonce, response_digest);
+ if (NS_FAILED(rv)) return rv;
+
+ //
+ // Values that need to match the quoted-string production from RFC 2616:
+ //
+ // username
+ // realm
+ // nonce
+ // opaque
+ // cnonce
+ //
+
+ nsAutoCString authString;
+
+ authString.AssignLiteral("Digest username=");
+ rv = AppendQuotedString(cUser, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ authString.AppendLiteral(", realm=");
+ rv = AppendQuotedString(realm, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ authString.AppendLiteral(", nonce=");
+ rv = AppendQuotedString(nonce, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ authString.AppendLiteral(", uri=\"");
+ authString += path;
+ if (algorithm & ALGO_SPECIFIED) {
+ authString.AppendLiteral("\", algorithm=");
+ if (algorithm & ALGO_MD5_SESS)
+ authString.AppendLiteral("MD5-sess");
+ else
+ authString.AppendLiteral("MD5");
+ } else {
+ authString += '\"';
+ }
+ authString.AppendLiteral(", response=\"");
+ authString += response_digest;
+ authString += '\"';
+
+ if (!opaque.IsEmpty()) {
+ authString.AppendLiteral(", opaque=");
+ rv = AppendQuotedString(opaque, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (qop) {
+ authString.AppendLiteral(", qop=");
+ if (requireExtraQuotes)
+ authString += '\"';
+ authString.AppendLiteral("auth");
+ if (qop & QOP_AUTH_INT)
+ authString.AppendLiteral("-int");
+ if (requireExtraQuotes)
+ authString += '\"';
+ authString.AppendLiteral(", nc=");
+ authString += nonce_count;
+
+ authString.AppendLiteral(", cnonce=");
+ rv = AppendQuotedString(cnonce, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+
+ *creds = ToNewCString(authString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpDigestAuth::GetAuthFlags(uint32_t *flags)
+{
+ *flags = REQUEST_BASED | REUSABLE_CHALLENGE | IDENTITY_ENCRYPTED;
+ //
+ // NOTE: digest auth credentials must be uniquely computed for each request,
+ // so we do not set the REUSABLE_CREDENTIALS flag.
+ //
+ return NS_OK;
+}
+
+nsresult
+nsHttpDigestAuth::CalculateResponse(const char * ha1_digest,
+ const char * ha2_digest,
+ const nsAFlatCString & nonce,
+ uint16_t qop,
+ const char * nonce_count,
+ const nsAFlatCString & cnonce,
+ char * result)
+{
+ uint32_t len = 2*EXPANDED_DIGEST_LENGTH + nonce.Length() + 2;
+
+ if (qop & QOP_AUTH || qop & QOP_AUTH_INT) {
+ len += cnonce.Length() + NONCE_COUNT_LENGTH + 3;
+ if (qop & QOP_AUTH_INT)
+ len += 8; // length of "auth-int"
+ else
+ len += 4; // length of "auth"
+ }
+
+ nsAutoCString contents;
+ contents.SetCapacity(len);
+
+ contents.Assign(ha1_digest, EXPANDED_DIGEST_LENGTH);
+ contents.Append(':');
+ contents.Append(nonce);
+ contents.Append(':');
+
+ if (qop & QOP_AUTH || qop & QOP_AUTH_INT) {
+ contents.Append(nonce_count, NONCE_COUNT_LENGTH);
+ contents.Append(':');
+ contents.Append(cnonce);
+ contents.Append(':');
+ if (qop & QOP_AUTH_INT)
+ contents.AppendLiteral("auth-int:");
+ else
+ contents.AppendLiteral("auth:");
+ }
+
+ contents.Append(ha2_digest, EXPANDED_DIGEST_LENGTH);
+
+ nsresult rv = MD5Hash(contents.get(), contents.Length());
+ if (NS_SUCCEEDED(rv))
+ rv = ExpandToHex(mHashBuf, result);
+ return rv;
+}
+
+nsresult
+nsHttpDigestAuth::ExpandToHex(const char * digest, char * result)
+{
+ int16_t index, value;
+
+ for (index = 0; index < DIGEST_LENGTH; index++) {
+ value = (digest[index] >> 4) & 0xf;
+ if (value < 10)
+ result[index*2] = value + '0';
+ else
+ result[index*2] = value - 10 + 'a';
+
+ value = digest[index] & 0xf;
+ if (value < 10)
+ result[(index*2)+1] = value + '0';
+ else
+ result[(index*2)+1] = value - 10 + 'a';
+ }
+
+ result[EXPANDED_DIGEST_LENGTH] = 0;
+ return NS_OK;
+}
+
+nsresult
+nsHttpDigestAuth::CalculateHA1(const nsAFlatCString & username,
+ const nsAFlatCString & password,
+ const nsAFlatCString & realm,
+ uint16_t algorithm,
+ const nsAFlatCString & nonce,
+ const nsAFlatCString & cnonce,
+ char * result)
+{
+ int16_t len = username.Length() + password.Length() + realm.Length() + 2;
+ if (algorithm & ALGO_MD5_SESS) {
+ int16_t exlen = EXPANDED_DIGEST_LENGTH + nonce.Length() + cnonce.Length() + 2;
+ if (exlen > len)
+ len = exlen;
+ }
+
+ nsAutoCString contents;
+ contents.SetCapacity(len + 1);
+
+ contents.Assign(username);
+ contents.Append(':');
+ contents.Append(realm);
+ contents.Append(':');
+ contents.Append(password);
+
+ nsresult rv;
+ rv = MD5Hash(contents.get(), contents.Length());
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (algorithm & ALGO_MD5_SESS) {
+ char part1[EXPANDED_DIGEST_LENGTH+1];
+ ExpandToHex(mHashBuf, part1);
+
+ contents.Assign(part1, EXPANDED_DIGEST_LENGTH);
+ contents.Append(':');
+ contents.Append(nonce);
+ contents.Append(':');
+ contents.Append(cnonce);
+
+ rv = MD5Hash(contents.get(), contents.Length());
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ return ExpandToHex(mHashBuf, result);
+}
+
+nsresult
+nsHttpDigestAuth::CalculateHA2(const nsAFlatCString & method,
+ const nsAFlatCString & path,
+ uint16_t qop,
+ const char * bodyDigest,
+ char * result)
+{
+ uint16_t methodLen = method.Length();
+ uint32_t pathLen = path.Length();
+ uint32_t len = methodLen + pathLen + 1;
+
+ if (qop & QOP_AUTH_INT) {
+ len += EXPANDED_DIGEST_LENGTH + 1;
+ }
+
+ nsAutoCString contents;
+ contents.SetCapacity(len);
+
+ contents.Assign(method);
+ contents.Append(':');
+ contents.Append(path);
+
+ if (qop & QOP_AUTH_INT) {
+ contents.Append(':');
+ contents.Append(bodyDigest, EXPANDED_DIGEST_LENGTH);
+ }
+
+ nsresult rv = MD5Hash(contents.get(), contents.Length());
+ if (NS_SUCCEEDED(rv))
+ rv = ExpandToHex(mHashBuf, result);
+ return rv;
+}
+
+nsresult
+nsHttpDigestAuth::ParseChallenge(const char * challenge,
+ nsACString & realm,
+ nsACString & domain,
+ nsACString & nonce,
+ nsACString & opaque,
+ bool * stale,
+ uint16_t * algorithm,
+ uint16_t * qop)
+{
+ // put an absurd, but maximum, length cap on the challenge so
+ // that calculations are 32 bit safe
+ if (strlen(challenge) > 16000000) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const char *p = challenge + 6; // first 6 characters are "Digest"
+
+ *stale = false;
+ *algorithm = ALGO_MD5; // default is MD5
+ *qop = 0;
+
+ for (;;) {
+ while (*p && (*p == ',' || nsCRT::IsAsciiSpace(*p)))
+ ++p;
+ if (!*p)
+ break;
+
+ // name
+ int32_t nameStart = (p - challenge);
+ while (*p && !nsCRT::IsAsciiSpace(*p) && *p != '=')
+ ++p;
+ if (!*p)
+ return NS_ERROR_INVALID_ARG;
+ int32_t nameLength = (p - challenge) - nameStart;
+
+ while (*p && (nsCRT::IsAsciiSpace(*p) || *p == '='))
+ ++p;
+ if (!*p)
+ return NS_ERROR_INVALID_ARG;
+
+ bool quoted = false;
+ if (*p == '"') {
+ ++p;
+ quoted = true;
+ }
+
+ // value
+ int32_t valueStart = (p - challenge);
+ int32_t valueLength = 0;
+ if (quoted) {
+ while (*p && *p != '"')
+ ++p;
+ if (*p != '"')
+ return NS_ERROR_INVALID_ARG;
+ valueLength = (p - challenge) - valueStart;
+ ++p;
+ } else {
+ while (*p && !nsCRT::IsAsciiSpace(*p) && *p != ',')
+ ++p;
+ valueLength = (p - challenge) - valueStart;
+ }
+
+ // extract information
+ if (nameLength == 5 &&
+ nsCRT::strncasecmp(challenge+nameStart, "realm", 5) == 0)
+ {
+ realm.Assign(challenge+valueStart, valueLength);
+ }
+ else if (nameLength == 6 &&
+ nsCRT::strncasecmp(challenge+nameStart, "domain", 6) == 0)
+ {
+ domain.Assign(challenge+valueStart, valueLength);
+ }
+ else if (nameLength == 5 &&
+ nsCRT::strncasecmp(challenge+nameStart, "nonce", 5) == 0)
+ {
+ nonce.Assign(challenge+valueStart, valueLength);
+ }
+ else if (nameLength == 6 &&
+ nsCRT::strncasecmp(challenge+nameStart, "opaque", 6) == 0)
+ {
+ opaque.Assign(challenge+valueStart, valueLength);
+ }
+ else if (nameLength == 5 &&
+ nsCRT::strncasecmp(challenge+nameStart, "stale", 5) == 0)
+ {
+ if (nsCRT::strncasecmp(challenge+valueStart, "true", 4) == 0)
+ *stale = true;
+ else
+ *stale = false;
+ }
+ else if (nameLength == 9 &&
+ nsCRT::strncasecmp(challenge+nameStart, "algorithm", 9) == 0)
+ {
+ // we want to clear the default, so we use = not |= here
+ *algorithm = ALGO_SPECIFIED;
+ if (valueLength == 3 &&
+ nsCRT::strncasecmp(challenge+valueStart, "MD5", 3) == 0)
+ *algorithm |= ALGO_MD5;
+ else if (valueLength == 8 &&
+ nsCRT::strncasecmp(challenge+valueStart, "MD5-sess", 8) == 0)
+ *algorithm |= ALGO_MD5_SESS;
+ }
+ else if (nameLength == 3 &&
+ nsCRT::strncasecmp(challenge+nameStart, "qop", 3) == 0)
+ {
+ int32_t ipos = valueStart;
+ while (ipos < valueStart+valueLength) {
+ while (ipos < valueStart+valueLength &&
+ (nsCRT::IsAsciiSpace(challenge[ipos]) ||
+ challenge[ipos] == ','))
+ ipos++;
+ int32_t algostart = ipos;
+ while (ipos < valueStart+valueLength &&
+ !nsCRT::IsAsciiSpace(challenge[ipos]) &&
+ challenge[ipos] != ',')
+ ipos++;
+ if ((ipos - algostart) == 4 &&
+ nsCRT::strncasecmp(challenge+algostart, "auth", 4) == 0)
+ *qop |= QOP_AUTH;
+ else if ((ipos - algostart) == 8 &&
+ nsCRT::strncasecmp(challenge+algostart, "auth-int", 8) == 0)
+ *qop |= QOP_AUTH_INT;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsHttpDigestAuth::AppendQuotedString(const nsACString & value,
+ nsACString & aHeaderLine)
+{
+ nsAutoCString quoted;
+ nsACString::const_iterator s, e;
+ value.BeginReading(s);
+ value.EndReading(e);
+
+ //
+ // Encode string according to RFC 2616 quoted-string production
+ //
+ quoted.Append('"');
+ for ( ; s != e; ++s) {
+ //
+ // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
+ //
+ if (*s <= 31 || *s == 127) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Escape two syntactically significant characters
+ if (*s == '"' || *s == '\\') {
+ quoted.Append('\\');
+ }
+
+ quoted.Append(*s);
+ }
+ // FIXME: bug 41489
+ // We should RFC2047-encode non-Latin-1 values according to spec
+ quoted.Append('"');
+ aHeaderLine.Append(quoted);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+// vim: ts=2 sw=2
diff --git a/netwerk/protocol/http/nsHttpDigestAuth.h b/netwerk/protocol/http/nsHttpDigestAuth.h
new file mode 100644
index 000000000..fa1516676
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpDigestAuth.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDigestAuth_h__
+#define nsDigestAuth_h__
+
+#include "nsIHttpAuthenticator.h"
+#include "nsStringFwd.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+class nsICryptoHash;
+
+namespace mozilla { namespace net {
+
+#define ALGO_SPECIFIED 0x01
+#define ALGO_MD5 0x02
+#define ALGO_MD5_SESS 0x04
+#define QOP_AUTH 0x01
+#define QOP_AUTH_INT 0x02
+
+#define DIGEST_LENGTH 16
+#define EXPANDED_DIGEST_LENGTH 32
+#define NONCE_COUNT_LENGTH 8
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth
+//-----------------------------------------------------------------------------
+
+class nsHttpDigestAuth final : public nsIHttpAuthenticator
+{
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHENTICATOR
+
+ nsHttpDigestAuth();
+
+ protected:
+ ~nsHttpDigestAuth();
+
+ nsresult ExpandToHex(const char * digest, char * result);
+
+ nsresult CalculateResponse(const char * ha1_digest,
+ const char * ha2_digest,
+ const nsAFlatCString & nonce,
+ uint16_t qop,
+ const char * nonce_count,
+ const nsAFlatCString & cnonce,
+ char * result);
+
+ nsresult CalculateHA1(const nsAFlatCString & username,
+ const nsAFlatCString & password,
+ const nsAFlatCString & realm,
+ uint16_t algorithm,
+ const nsAFlatCString & nonce,
+ const nsAFlatCString & cnonce,
+ char * result);
+
+ nsresult CalculateHA2(const nsAFlatCString & http_method,
+ const nsAFlatCString & http_uri_path,
+ uint16_t qop,
+ const char * body_digest,
+ char * result);
+
+ nsresult ParseChallenge(const char * challenge,
+ nsACString & realm,
+ nsACString & domain,
+ nsACString & nonce,
+ nsACString & opaque,
+ bool * stale,
+ uint16_t * algorithm,
+ uint16_t * qop);
+
+ // result is in mHashBuf
+ nsresult MD5Hash(const char *buf, uint32_t len);
+
+ nsresult GetMethodAndPath(nsIHttpAuthenticableChannel *,
+ bool, nsCString &, nsCString &);
+
+ // append the quoted version of value to aHeaderLine
+ nsresult AppendQuotedString(const nsACString & value,
+ nsACString & aHeaderLine);
+
+ protected:
+ nsCOMPtr<nsICryptoHash> mVerifier;
+ char mHashBuf[DIGEST_LENGTH];
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpDigestAuth_h__
diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp
new file mode 100644
index 000000000..1ddffabff
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -0,0 +1,2493 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpChannel.h"
+#include "nsHttpAuthCache.h"
+#include "nsStandardURL.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMNavigator.h"
+#include "nsIMozNavigatorNetwork.h"
+#include "nsINetworkProperties.h"
+#include "nsIHttpChannel.h"
+#include "nsIStandardURL.h"
+#include "LoadContextInfo.h"
+#include "nsCategoryManagerUtils.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsISocketProviderService.h"
+#include "nsISocketProvider.h"
+#include "nsPrintfCString.h"
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "prprf.h"
+#include "mozilla/Sprintf.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsSocketTransportService2.h"
+#include "nsAlgorithm.h"
+#include "ASpdySession.h"
+#include "mozIApplicationClearPrivateDataParams.h"
+#include "EventTokenBucket.h"
+#include "Tickler.h"
+#include "nsIXULAppInfo.h"
+#include "nsICookieService.h"
+#include "nsIObserverService.h"
+#include "nsISiteSecurityService.h"
+#include "nsIStreamConverterService.h"
+#include "nsITimer.h"
+#include "nsCRT.h"
+#include "nsIMemoryReporter.h"
+#include "nsIParentalControlsService.h"
+#include "nsPIDOMWindow.h"
+#include "nsINetworkLinkService.h"
+#include "nsHttpChannelAuthProvider.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsSocketTransportService2.h"
+#include "nsIOService.h"
+#include "nsIUUIDGenerator.h"
+
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/BasePrincipal.h"
+
+#include "mozilla/dom/ContentParent.h"
+
+#if defined(XP_UNIX)
+#include <sys/utsname.h>
+#endif
+
+#if defined(XP_WIN)
+#include <windows.h>
+#endif
+
+#if defined(XP_MACOSX)
+#include <CoreServices/CoreServices.h>
+#include "nsCocoaFeatures.h"
+#endif
+
+//-----------------------------------------------------------------------------
+#include "mozilla/net/HttpChannelChild.h"
+
+
+#define UA_PREF_PREFIX "general.useragent."
+#ifdef XP_WIN
+#define UA_SPARE_PLATFORM
+#endif
+
+#define HTTP_PREF_PREFIX "network.http."
+#define INTL_ACCEPT_LANGUAGES "intl.accept_languages"
+#define BROWSER_PREF_PREFIX "browser.cache."
+#define DONOTTRACK_HEADER_ENABLED "privacy.donottrackheader.enabled"
+#define H2MANDATORY_SUITE "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256"
+#define TELEMETRY_ENABLED "toolkit.telemetry.enabled"
+#define ALLOW_EXPERIMENTS "network.allow-experiments"
+#define SAFE_HINT_HEADER_VALUE "safeHint.enabled"
+#define SECURITY_PREFIX "security."
+#define NEW_TAB_REMOTE_MODE "browser.newtabpage.remote.mode"
+
+#define UA_PREF(_pref) UA_PREF_PREFIX _pref
+#define HTTP_PREF(_pref) HTTP_PREF_PREFIX _pref
+#define BROWSER_PREF(_pref) BROWSER_PREF_PREFIX _pref
+
+#define NS_HTTP_PROTOCOL_FLAGS (URI_STD | ALLOWS_PROXY | ALLOWS_PROXY_HTTP | URI_LOADABLE_BY_ANYONE)
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gHttpLog("nsHttp");
+
+static nsresult
+NewURI(const nsACString &aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ int32_t aDefaultPort,
+ nsIURI **aURI)
+{
+ RefPtr<nsStandardURL> url = new nsStandardURL();
+
+ nsresult rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY,
+ aDefaultPort, aSpec, aCharset, aBaseURI);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ url.forget(aURI);
+ return NS_OK;
+}
+
+#ifdef ANDROID
+static nsCString
+GetDeviceModelId() {
+ // Assumed to be running on the main thread
+ // We need the device property in either case
+ nsAutoCString deviceModelId;
+ nsCOMPtr<nsIPropertyBag2> infoService = do_GetService("@mozilla.org/system-info;1");
+ MOZ_ASSERT(infoService, "Could not find a system info service");
+ nsAutoString androidDevice;
+ nsresult rv = infoService->GetPropertyAsAString(NS_LITERAL_STRING("device"), androidDevice);
+ if (NS_SUCCEEDED(rv)) {
+ deviceModelId = NS_LossyConvertUTF16toASCII(androidDevice);
+ }
+ nsAutoCString deviceString;
+ rv = Preferences::GetCString(UA_PREF("device_string"), &deviceString);
+ if (NS_SUCCEEDED(rv)) {
+ deviceString.Trim(" ", true, true);
+ deviceString.ReplaceSubstring(NS_LITERAL_CSTRING("%DEVICEID%"), deviceModelId);
+ return deviceString;
+ }
+ return deviceModelId;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler <public>
+//-----------------------------------------------------------------------------
+
+nsHttpHandler *gHttpHandler = nullptr;
+
+nsHttpHandler::nsHttpHandler()
+ : mHttpVersion(NS_HTTP_VERSION_1_1)
+ , mProxyHttpVersion(NS_HTTP_VERSION_1_1)
+ , mCapabilities(NS_HTTP_ALLOW_KEEPALIVE)
+ , mReferrerLevel(0xff) // by default we always send a referrer
+ , mSpoofReferrerSource(false)
+ , mReferrerTrimmingPolicy(0)
+ , mReferrerXOriginTrimmingPolicy(0)
+ , mReferrerXOriginPolicy(0)
+ , mFastFallbackToIPv4(false)
+ , mProxyPipelining(true)
+ , mIdleTimeout(PR_SecondsToInterval(10))
+ , mSpdyTimeout(PR_SecondsToInterval(180))
+ , mResponseTimeout(PR_SecondsToInterval(300))
+ , mResponseTimeoutEnabled(false)
+ , mNetworkChangedTimeout(5000)
+ , mMaxRequestAttempts(6)
+ , mMaxRequestDelay(10)
+ , mIdleSynTimeout(250)
+ , mH2MandatorySuiteEnabled(false)
+ , mPipeliningEnabled(false)
+ , mMaxConnections(24)
+ , mMaxPersistentConnectionsPerServer(2)
+ , mMaxPersistentConnectionsPerProxy(4)
+ , mMaxPipelinedRequests(32)
+ , mMaxOptimisticPipelinedRequests(4)
+ , mPipelineAggressive(false)
+ , mMaxPipelineObjectSize(300000)
+ , mPipelineRescheduleOnTimeout(true)
+ , mPipelineRescheduleTimeout(PR_MillisecondsToInterval(1500))
+ , mPipelineReadTimeout(PR_MillisecondsToInterval(30000))
+ , mRedirectionLimit(10)
+ , mPhishyUserPassLength(1)
+ , mQoSBits(0x00)
+ , mPipeliningOverSSL(false)
+ , mEnforceAssocReq(false)
+ , mLastUniqueID(NowInSeconds())
+ , mSessionStartTime(0)
+ , mLegacyAppName("Mozilla")
+ , mLegacyAppVersion("5.0")
+ , mProduct("Gecko")
+ , mCompatFirefoxEnabled(false)
+ , mUserAgentIsDirty(true)
+ , mPromptTempRedirect(true)
+ , mEnablePersistentHttpsCaching(false)
+ , mDoNotTrackEnabled(false)
+ , mSafeHintEnabled(false)
+ , mParentalControlEnabled(false)
+ , mHandlerActive(false)
+ , mTelemetryEnabled(false)
+ , mAllowExperiments(true)
+ , mDebugObservations(false)
+ , mEnableSpdy(false)
+ , mHttp2Enabled(true)
+ , mUseH2Deps(true)
+ , mEnforceHttp2TlsProfile(true)
+ , mCoalesceSpdy(true)
+ , mSpdyPersistentSettings(false)
+ , mAllowPush(true)
+ , mEnableAltSvc(false)
+ , mEnableAltSvcOE(false)
+ , mSpdySendingChunkSize(ASpdySession::kSendingChunkSize)
+ , mSpdySendBufferSize(ASpdySession::kTCPSendBufferSize)
+ , mSpdyPushAllowance(32768)
+ , mSpdyPullAllowance(ASpdySession::kInitialRwin)
+ , mDefaultSpdyConcurrent(ASpdySession::kDefaultMaxConcurrent)
+ , mSpdyPingThreshold(PR_SecondsToInterval(58))
+ , mSpdyPingTimeout(PR_SecondsToInterval(8))
+ , mConnectTimeout(90000)
+ , mParallelSpeculativeConnectLimit(6)
+ , mRequestTokenBucketEnabled(true)
+ , mRequestTokenBucketMinParallelism(6)
+ , mRequestTokenBucketHz(100)
+ , mRequestTokenBucketBurst(32)
+ , mCriticalRequestPrioritization(true)
+ , mTCPKeepaliveShortLivedEnabled(false)
+ , mTCPKeepaliveShortLivedTimeS(60)
+ , mTCPKeepaliveShortLivedIdleTimeS(10)
+ , mTCPKeepaliveLongLivedEnabled(false)
+ , mTCPKeepaliveLongLivedIdleTimeS(600)
+ , mEnforceH1Framing(FRAMECHECK_BARELY)
+ , mKeepEmptyResponseHeadersAsEmtpyString(false)
+ , mDefaultHpackBuffer(4096)
+ , mMaxHttpResponseHeaderSize(393216)
+{
+ LOG(("Creating nsHttpHandler [this=%p].\n", this));
+
+ MOZ_ASSERT(!gHttpHandler, "HTTP handler already created!");
+ gHttpHandler = this;
+}
+
+nsHttpHandler::~nsHttpHandler()
+{
+ LOG(("Deleting nsHttpHandler [this=%p]\n", this));
+
+ // make sure the connection manager is shutdown
+ if (mConnMgr) {
+ mConnMgr->Shutdown();
+ mConnMgr = nullptr;
+ }
+
+ // Note: don't call NeckoChild::DestroyNeckoChild() here, as it's too late
+ // and it'll segfault. NeckoChild will get cleaned up by process exit.
+
+ nsHttp::DestroyAtomTable();
+ if (mPipelineTestTimer) {
+ mPipelineTestTimer->Cancel();
+ mPipelineTestTimer = nullptr;
+ }
+
+ gHttpHandler = nullptr;
+}
+
+nsresult
+nsHttpHandler::Init()
+{
+ nsresult rv;
+
+ LOG(("nsHttpHandler::Init\n"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ rv = nsHttp::CreateAtomTable();
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIIOService> service = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to continue without io service");
+ return rv;
+ }
+ mIOService = new nsMainThreadPtrHolder<nsIIOService>(service);
+
+ if (IsNeckoChild())
+ NeckoChild::InitNeckoChild();
+
+ InitUserAgentComponents();
+
+ // monitor some preference changes
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefBranch) {
+ prefBranch->AddObserver(HTTP_PREF_PREFIX, this, true);
+ prefBranch->AddObserver(UA_PREF_PREFIX, this, true);
+ prefBranch->AddObserver(INTL_ACCEPT_LANGUAGES, this, true);
+ prefBranch->AddObserver(BROWSER_PREF("disk_cache_ssl"), this, true);
+ prefBranch->AddObserver(DONOTTRACK_HEADER_ENABLED, this, true);
+ prefBranch->AddObserver(TELEMETRY_ENABLED, this, true);
+ prefBranch->AddObserver(H2MANDATORY_SUITE, this, true);
+ prefBranch->AddObserver(HTTP_PREF("tcp_keepalive.short_lived_connections"), this, true);
+ prefBranch->AddObserver(HTTP_PREF("tcp_keepalive.long_lived_connections"), this, true);
+ prefBranch->AddObserver(SAFE_HINT_HEADER_VALUE, this, true);
+ prefBranch->AddObserver(SECURITY_PREFIX, this, true);
+ prefBranch->AddObserver(NEW_TAB_REMOTE_MODE, this, true);
+ PrefsChanged(prefBranch, nullptr);
+ }
+
+ nsHttpChannelAuthProvider::InitializePrefs();
+
+ mMisc.AssignLiteral("rv:" MOZILLA_UAVERSION);
+
+ mCompatFirefox.AssignLiteral("Firefox/" MOZILLA_UAVERSION);
+
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+
+ mAppName.AssignLiteral(MOZ_APP_UA_NAME);
+ if (mAppName.Length() == 0 && appInfo) {
+ // Try to get the UA name from appInfo, falling back to the name
+ appInfo->GetUAName(mAppName);
+ if (mAppName.Length() == 0) {
+ appInfo->GetName(mAppName);
+ }
+ appInfo->GetVersion(mAppVersion);
+ mAppName.StripChars(R"( ()<>@,;:\"/[]?={})");
+ } else {
+ mAppVersion.AssignLiteral(MOZ_APP_UA_VERSION);
+ }
+
+ mSessionStartTime = NowInSeconds();
+ mHandlerActive = true;
+
+ rv = mAuthCache.Init();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mPrivateAuthCache.Init();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = InitConnectionMgr();
+ if (NS_FAILED(rv)) return rv;
+
+ mRequestContextService =
+ do_GetService("@mozilla.org/network/request-context-service;1");
+
+#if defined(ANDROID) || defined(MOZ_MULET)
+ mProductSub.AssignLiteral(MOZILLA_UAVERSION);
+#else
+ mProductSub.AssignLiteral("20100101");
+#endif
+
+#if DEBUG
+ // dump user agent prefs
+ LOG(("> legacy-app-name = %s\n", mLegacyAppName.get()));
+ LOG(("> legacy-app-version = %s\n", mLegacyAppVersion.get()));
+ LOG(("> platform = %s\n", mPlatform.get()));
+ LOG(("> oscpu = %s\n", mOscpu.get()));
+ LOG(("> misc = %s\n", mMisc.get()));
+ LOG(("> product = %s\n", mProduct.get()));
+ LOG(("> product-sub = %s\n", mProductSub.get()));
+ LOG(("> app-name = %s\n", mAppName.get()));
+ LOG(("> app-version = %s\n", mAppVersion.get()));
+ LOG(("> compat-firefox = %s\n", mCompatFirefox.get()));
+ LOG(("> user-agent = %s\n", UserAgent().get()));
+#endif
+
+ // Startup the http category
+ // Bring alive the objects in the http-protocol-startup category
+ NS_CreateServicesFromCategory(NS_HTTP_STARTUP_CATEGORY,
+ static_cast<nsISupports*>(static_cast<void*>(this)),
+ NS_HTTP_STARTUP_TOPIC);
+
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ // register the handler object as a weak callback as we don't need to worry
+ // about shutdown ordering.
+ obsService->AddObserver(this, "profile-change-net-teardown", true);
+ obsService->AddObserver(this, "profile-change-net-restore", true);
+ obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ obsService->AddObserver(this, "net:clear-active-logins", true);
+ obsService->AddObserver(this, "net:prune-dead-connections", true);
+ // Sent by the TorButton add-on in the Tor Browser
+ obsService->AddObserver(this, "net:prune-all-connections", true);
+ obsService->AddObserver(this, "net:failed-to-process-uri-content", true);
+ obsService->AddObserver(this, "last-pb-context-exited", true);
+ obsService->AddObserver(this, "webapps-clear-data", true);
+ obsService->AddObserver(this, "browser:purge-session-history", true);
+ obsService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
+ obsService->AddObserver(this, "application-background", true);
+ }
+
+ MakeNewRequestTokenBucket();
+ mWifiTickler = new Tickler();
+ if (NS_FAILED(mWifiTickler->Init()))
+ mWifiTickler = nullptr;
+
+ nsCOMPtr<nsIParentalControlsService> pc = do_CreateInstance("@mozilla.org/parental-controls-service;1");
+ if (pc) {
+ pc->GetParentalControlsEnabled(&mParentalControlEnabled);
+ }
+ return NS_OK;
+}
+
+void
+nsHttpHandler::MakeNewRequestTokenBucket()
+{
+ LOG(("nsHttpHandler::MakeNewRequestTokenBucket this=%p child=%d\n",
+ this, IsNeckoChild()));
+ if (!mConnMgr || IsNeckoChild()) {
+ return;
+ }
+ RefPtr<EventTokenBucket> tokenBucket =
+ new EventTokenBucket(RequestTokenBucketHz(), RequestTokenBucketBurst());
+ mConnMgr->UpdateRequestTokenBucket(tokenBucket);
+}
+
+nsresult
+nsHttpHandler::InitConnectionMgr()
+{
+ // Init ConnectionManager only on parent!
+ if (IsNeckoChild()) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ if (!mConnMgr) {
+ mConnMgr = new nsHttpConnectionMgr();
+ }
+
+ rv = mConnMgr->Init(mMaxConnections,
+ mMaxPersistentConnectionsPerServer,
+ mMaxPersistentConnectionsPerProxy,
+ mMaxRequestDelay,
+ mMaxPipelinedRequests,
+ mMaxOptimisticPipelinedRequests);
+ return rv;
+}
+
+nsresult
+nsHttpHandler::AddStandardRequestHeaders(nsHttpRequestHead *request, bool isSecure)
+{
+ nsresult rv;
+
+ // Add the "User-Agent" header
+ rv = request->SetHeader(nsHttp::User_Agent, UserAgent(),
+ false, nsHttpHeaderArray::eVarietyRequestDefault);
+ if (NS_FAILED(rv)) return rv;
+
+ // MIME based content negotiation lives!
+ // Add the "Accept" header. Note, this is set as an override because the
+ // service worker expects to see it. The other "default" headers are
+ // hidden from service worker interception.
+ rv = request->SetHeader(nsHttp::Accept, mAccept,
+ false, nsHttpHeaderArray::eVarietyRequestOverride);
+ if (NS_FAILED(rv)) return rv;
+
+ // Add the "Accept-Language" header. This header is also exposed to the
+ // service worker.
+ if (!mAcceptLanguages.IsEmpty()) {
+ // Add the "Accept-Language" header
+ rv = request->SetHeader(nsHttp::Accept_Language, mAcceptLanguages,
+ false,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Add the "Accept-Encoding" header
+ if (isSecure) {
+ rv = request->SetHeader(nsHttp::Accept_Encoding, mHttpsAcceptEncodings,
+ false,
+ nsHttpHeaderArray::eVarietyRequestDefault);
+ } else {
+ rv = request->SetHeader(nsHttp::Accept_Encoding, mHttpAcceptEncodings,
+ false,
+ nsHttpHeaderArray::eVarietyRequestDefault);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ // add the "Send Hint" header
+ if (mSafeHintEnabled || mParentalControlEnabled) {
+ rv = request->SetHeader(nsHttp::Prefer, NS_LITERAL_CSTRING("safe"),
+ false,
+ nsHttpHeaderArray::eVarietyRequestDefault);
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsHttpHandler::AddConnectionHeader(nsHttpRequestHead *request,
+ uint32_t caps)
+{
+ // RFC2616 section 19.6.2 states that the "Connection: keep-alive"
+ // and "Keep-alive" request headers should not be sent by HTTP/1.1
+ // user-agents. But this is not a problem in practice, and the
+ // alternative proxy-connection is worse. see 570283
+
+ NS_NAMED_LITERAL_CSTRING(close, "close");
+ NS_NAMED_LITERAL_CSTRING(keepAlive, "keep-alive");
+
+ const nsACString *connectionType = &close;
+ if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
+ connectionType = &keepAlive;
+ }
+
+ return request->SetHeader(nsHttp::Connection, *connectionType);
+}
+
+bool
+nsHttpHandler::IsAcceptableEncoding(const char *enc, bool isSecure)
+{
+ if (!enc)
+ return false;
+
+ // we used to accept x-foo anytime foo was acceptable, but that's just
+ // continuing bad behavior.. so limit it to known x-* patterns
+ bool rv;
+ if (isSecure) {
+ rv = nsHttp::FindToken(mHttpsAcceptEncodings.get(), enc, HTTP_LWS ",") != nullptr;
+ } else {
+ rv = nsHttp::FindToken(mHttpAcceptEncodings.get(), enc, HTTP_LWS ",") != nullptr;
+ }
+ // gzip and deflate are inherently acceptable in modern HTTP - always
+ // process them if a stream converter can also be found.
+ if (!rv &&
+ (!PL_strcasecmp(enc, "gzip") || !PL_strcasecmp(enc, "deflate") ||
+ !PL_strcasecmp(enc, "x-gzip") || !PL_strcasecmp(enc, "x-deflate"))) {
+ rv = true;
+ }
+ LOG(("nsHttpHandler::IsAceptableEncoding %s https=%d %d\n",
+ enc, isSecure, rv));
+ return rv;
+}
+
+nsresult
+nsHttpHandler::GetStreamConverterService(nsIStreamConverterService **result)
+{
+ if (!mStreamConvSvc) {
+ nsresult rv;
+ nsCOMPtr<nsIStreamConverterService> service =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+ mStreamConvSvc = new nsMainThreadPtrHolder<nsIStreamConverterService>(service);
+ }
+ *result = mStreamConvSvc;
+ NS_ADDREF(*result);
+ return NS_OK;
+}
+
+nsISiteSecurityService*
+nsHttpHandler::GetSSService()
+{
+ if (!mSSService) {
+ nsCOMPtr<nsISiteSecurityService> service = do_GetService(NS_SSSERVICE_CONTRACTID);
+ mSSService = new nsMainThreadPtrHolder<nsISiteSecurityService>(service);
+ }
+ return mSSService;
+}
+
+nsICookieService *
+nsHttpHandler::GetCookieService()
+{
+ if (!mCookieService) {
+ nsCOMPtr<nsICookieService> service = do_GetService(NS_COOKIESERVICE_CONTRACTID);
+ mCookieService = new nsMainThreadPtrHolder<nsICookieService>(service);
+ }
+ return mCookieService;
+}
+
+nsresult
+nsHttpHandler::GetIOService(nsIIOService** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ NS_ADDREF(*result = mIOService);
+ return NS_OK;
+}
+
+uint32_t
+nsHttpHandler::Get32BitsOfPseudoRandom()
+{
+ // only confirm rand seeding on socket thread
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // rand() provides different amounts of PRNG on different platforms.
+ // 15 or 31 bits are common amounts.
+
+ static_assert(RAND_MAX >= 0xfff, "RAND_MAX should be >= 12 bits");
+
+#if RAND_MAX < 0xffffU
+ return ((uint16_t) rand() << 20) |
+ (((uint16_t) rand() & 0xfff) << 8) |
+ ((uint16_t) rand() & 0xff);
+#elif RAND_MAX < 0xffffffffU
+ return ((uint16_t) rand() << 16) | ((uint16_t) rand() & 0xffff);
+#else
+ return (uint32_t) rand();
+#endif
+}
+
+void
+nsHttpHandler::NotifyObservers(nsIHttpChannel *chan, const char *event)
+{
+ LOG(("nsHttpHandler::NotifyObservers [chan=%x event=\"%s\"]\n", chan, event));
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService)
+ obsService->NotifyObservers(chan, event, nullptr);
+}
+
+nsresult
+nsHttpHandler::AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
+ uint32_t flags)
+{
+ // TODO E10S This helper has to be initialized on the other process
+ RefPtr<nsAsyncRedirectVerifyHelper> redirectCallbackHelper =
+ new nsAsyncRedirectVerifyHelper();
+
+ return redirectCallbackHelper->Init(oldChan, newChan, flags);
+}
+
+/* static */ nsresult
+nsHttpHandler::GenerateHostPort(const nsCString& host, int32_t port,
+ nsACString& hostLine)
+{
+ return NS_GenerateHostPort(host, port, hostLine);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler <private>
+//-----------------------------------------------------------------------------
+
+const nsAFlatCString &
+nsHttpHandler::UserAgent()
+{
+ if (mUserAgentOverride) {
+ LOG(("using general.useragent.override : %s\n", mUserAgentOverride.get()));
+ return mUserAgentOverride;
+ }
+
+ if (mUserAgentIsDirty) {
+ BuildUserAgent();
+ mUserAgentIsDirty = false;
+ }
+
+ return mUserAgent;
+}
+
+void
+nsHttpHandler::BuildUserAgent()
+{
+ LOG(("nsHttpHandler::BuildUserAgent\n"));
+
+ MOZ_ASSERT(!mLegacyAppName.IsEmpty() &&
+ !mLegacyAppVersion.IsEmpty(),
+ "HTTP cannot send practical requests without this much");
+
+ // preallocate to worst-case size, which should always be better
+ // than if we didn't preallocate at all.
+ mUserAgent.SetCapacity(mLegacyAppName.Length() +
+ mLegacyAppVersion.Length() +
+#ifndef UA_SPARE_PLATFORM
+ mPlatform.Length() +
+#endif
+ mOscpu.Length() +
+ mMisc.Length() +
+ mProduct.Length() +
+ mProductSub.Length() +
+ mAppName.Length() +
+ mAppVersion.Length() +
+ mCompatFirefox.Length() +
+ mCompatDevice.Length() +
+ mDeviceModelId.Length() +
+ 13);
+
+ // Application portion
+ mUserAgent.Assign(mLegacyAppName);
+ mUserAgent += '/';
+ mUserAgent += mLegacyAppVersion;
+ mUserAgent += ' ';
+
+ // Application comment
+ mUserAgent += '(';
+#ifndef UA_SPARE_PLATFORM
+ if (!mPlatform.IsEmpty()) {
+ mUserAgent += mPlatform;
+ mUserAgent.AppendLiteral("; ");
+ }
+#endif
+ if (!mCompatDevice.IsEmpty()) {
+ mUserAgent += mCompatDevice;
+ mUserAgent.AppendLiteral("; ");
+ }
+ else if (!mOscpu.IsEmpty()) {
+ mUserAgent += mOscpu;
+ mUserAgent.AppendLiteral("; ");
+ }
+ if (!mDeviceModelId.IsEmpty()) {
+ mUserAgent += mDeviceModelId;
+ mUserAgent.AppendLiteral("; ");
+ }
+ mUserAgent += mMisc;
+ mUserAgent += ')';
+
+ // Product portion
+ mUserAgent += ' ';
+ mUserAgent += mProduct;
+ mUserAgent += '/';
+ mUserAgent += mProductSub;
+
+ bool isFirefox = mAppName.EqualsLiteral("Firefox");
+ if (isFirefox || mCompatFirefoxEnabled) {
+ // "Firefox/x.y" (compatibility) app token
+ mUserAgent += ' ';
+ mUserAgent += mCompatFirefox;
+ }
+ if (!isFirefox) {
+ // App portion
+ mUserAgent += ' ';
+ mUserAgent += mAppName;
+ mUserAgent += '/';
+ mUserAgent += mAppVersion;
+ }
+}
+
+#ifdef XP_WIN
+#define WNT_BASE "Windows NT %ld.%ld"
+#define W64_PREFIX "; Win64"
+#endif
+
+void
+nsHttpHandler::InitUserAgentComponents()
+{
+#ifndef MOZ_UA_OS_AGNOSTIC
+ // Gather platform.
+ mPlatform.AssignLiteral(
+#if defined(ANDROID)
+ "Android"
+#elif defined(XP_WIN)
+ "Windows"
+#elif defined(XP_MACOSX)
+ "Macintosh"
+#elif defined(XP_UNIX)
+ // We historically have always had X11 here,
+ // and there seems little a webpage can sensibly do
+ // based on it being something else, so use X11 for
+ // backwards compatibility in all cases.
+ "X11"
+#endif
+ );
+#endif
+
+
+#ifdef ANDROID
+ nsCOMPtr<nsIPropertyBag2> infoService = do_GetService("@mozilla.org/system-info;1");
+ MOZ_ASSERT(infoService, "Could not find a system info service");
+ nsresult rv;
+ // Add the Android version number to the Fennec platform identifier.
+#if defined MOZ_WIDGET_ANDROID
+#ifndef MOZ_UA_OS_AGNOSTIC // Don't add anything to mPlatform since it's empty.
+ nsAutoString androidVersion;
+ rv = infoService->GetPropertyAsAString(
+ NS_LITERAL_STRING("release_version"), androidVersion);
+ if (NS_SUCCEEDED(rv)) {
+ mPlatform += " ";
+ // If the 2nd character is a ".", we know the major version is a single
+ // digit. If we're running on a version below 4 we pretend to be on
+ // Android KitKat (4.4) to work around scripts sniffing for low versions.
+ if (androidVersion[1] == 46 && androidVersion[0] < 52) {
+ mPlatform += "4.4";
+ } else {
+ mPlatform += NS_LossyConvertUTF16toASCII(androidVersion);
+ }
+ }
+#endif
+#endif
+ // Add the `Mobile` or `Tablet` or `TV` token when running on device.
+ bool isTablet;
+ rv = infoService->GetPropertyAsBool(NS_LITERAL_STRING("tablet"), &isTablet);
+ if (NS_SUCCEEDED(rv) && isTablet) {
+ mCompatDevice.AssignLiteral("Tablet");
+ } else {
+ bool isTV;
+ rv = infoService->GetPropertyAsBool(NS_LITERAL_STRING("tv"), &isTV);
+ if (NS_SUCCEEDED(rv) && isTV) {
+ mCompatDevice.AssignLiteral("TV");
+ } else {
+ mCompatDevice.AssignLiteral("Mobile");
+ }
+ }
+
+ if (Preferences::GetBool(UA_PREF("use_device"), false)) {
+ mDeviceModelId = mozilla::net::GetDeviceModelId();
+ }
+#endif // ANDROID
+
+#ifdef MOZ_MULET
+ {
+ // Add the `Mobile` or `Tablet` or `TV` token when running in the b2g
+ // desktop simulator via preference.
+ nsCString deviceType;
+ nsresult rv = Preferences::GetCString("devtools.useragent.device_type", &deviceType);
+ if (NS_SUCCEEDED(rv)) {
+ mCompatDevice.Assign(deviceType);
+ } else {
+ mCompatDevice.AssignLiteral("Mobile");
+ }
+ }
+#endif // MOZ_MULET
+
+#if defined(MOZ_WIDGET_GONK)
+ // Device model identifier should be a simple token, which can be composed
+ // of letters, numbers, hyphen ("-") and dot (".").
+ // Any other characters means the identifier is invalid and ignored.
+ nsCString deviceId;
+ rv = Preferences::GetCString("general.useragent.device_id", &deviceId);
+ if (NS_SUCCEEDED(rv)) {
+ bool valid = true;
+ deviceId.Trim(" ", true, true);
+ for (size_t i = 0; i < deviceId.Length(); i++) {
+ char c = deviceId.CharAt(i);
+ if (!(isalnum(c) || c == '-' || c == '.')) {
+ valid = false;
+ break;
+ }
+ }
+ if (valid) {
+ mDeviceModelId = deviceId;
+ } else {
+ LOG(("nsHttpHandler: Ignore invalid device ID: [%s]\n",
+ deviceId.get()));
+ }
+ }
+#endif
+
+#ifndef MOZ_UA_OS_AGNOSTIC
+ // Gather OS/CPU.
+#if defined(XP_WIN)
+ OSVERSIONINFO info = { sizeof(OSVERSIONINFO) };
+#pragma warning(push)
+#pragma warning(disable:4996)
+ if (GetVersionEx(&info)) {
+#pragma warning(pop)
+ const char *format;
+#if defined _M_IA64
+ format = WNT_BASE W64_PREFIX "; IA64";
+#elif defined _M_X64 || defined _M_AMD64
+ format = WNT_BASE W64_PREFIX "; x64";
+#else
+ BOOL isWow64 = FALSE;
+ if (!IsWow64Process(GetCurrentProcess(), &isWow64)) {
+ isWow64 = FALSE;
+ }
+ format = isWow64
+ ? WNT_BASE "; WOW64"
+ : WNT_BASE;
+#endif
+ char *buf = PR_smprintf(format,
+ info.dwMajorVersion,
+ info.dwMinorVersion);
+ if (buf) {
+ mOscpu = buf;
+ PR_smprintf_free(buf);
+ }
+ }
+#elif defined (XP_MACOSX)
+#if defined(__ppc__)
+ mOscpu.AssignLiteral("PPC Mac OS X");
+#elif defined(__i386__) || defined(__x86_64__)
+ mOscpu.AssignLiteral("Intel Mac OS X");
+#endif
+ SInt32 majorVersion = nsCocoaFeatures::OSXVersionMajor();
+ SInt32 minorVersion = nsCocoaFeatures::OSXVersionMinor();
+ mOscpu += nsPrintfCString(" %d.%d", majorVersion, minorVersion);
+#elif defined (XP_UNIX)
+ struct utsname name;
+
+ int ret = uname(&name);
+ if (ret >= 0) {
+ nsAutoCString buf;
+ buf = (char*)name.sysname;
+
+ if (strcmp(name.machine, "x86_64") == 0 &&
+ sizeof(void *) == sizeof(int32_t)) {
+ // We're running 32-bit code on x86_64. Make this browser
+ // look like it's running on i686 hardware, but append "
+ // (x86_64)" to the end of the oscpu identifier to be able
+ // to differentiate this from someone running 64-bit code
+ // on x86_64..
+
+ buf += " i686 on x86_64";
+ } else {
+ buf += ' ';
+
+#ifdef AIX
+ // AIX uname returns machine specific info in the uname.machine
+ // field and does not return the cpu type like other platforms.
+ // We use the AIX version and release numbers instead.
+ buf += (char*)name.version;
+ buf += '.';
+ buf += (char*)name.release;
+#else
+ buf += (char*)name.machine;
+#endif
+ }
+
+ mOscpu.Assign(buf);
+ }
+#endif
+#endif
+
+ mUserAgentIsDirty = true;
+}
+
+uint32_t
+nsHttpHandler::MaxSocketCount()
+{
+ PR_CallOnce(&nsSocketTransportService::gMaxCountInitOnce,
+ nsSocketTransportService::DiscoverMaxCount);
+ // Don't use the full max count because sockets can be held in
+ // the persistent connection pool for a long time and that could
+ // starve other users.
+
+ uint32_t maxCount = nsSocketTransportService::gMaxCount;
+ if (maxCount <= 8)
+ maxCount = 1;
+ else
+ maxCount -= 8;
+
+ return maxCount;
+}
+
+void
+nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
+{
+ nsresult rv = NS_OK;
+ int32_t val;
+
+ LOG(("nsHttpHandler::PrefsChanged [pref=%s]\n", pref));
+
+#define PREF_CHANGED(p) ((pref == nullptr) || !PL_strcmp(pref, p))
+#define MULTI_PREF_CHANGED(p) \
+ ((pref == nullptr) || !PL_strncmp(pref, p, sizeof(p) - 1))
+
+ // If a security pref changed, lets clear our connection pool reuse
+ if (MULTI_PREF_CHANGED(SECURITY_PREFIX)) {
+ LOG(("nsHttpHandler::PrefsChanged Security Pref Changed %s\n", pref));
+ if (mConnMgr) {
+ mConnMgr->DoShiftReloadConnectionCleanup(nullptr);
+ mConnMgr->PruneDeadConnections();
+ }
+ }
+
+ //
+ // UA components
+ //
+
+ bool cVar = false;
+
+ if (PREF_CHANGED(UA_PREF("compatMode.firefox"))) {
+ rv = prefs->GetBoolPref(UA_PREF("compatMode.firefox"), &cVar);
+ mCompatFirefoxEnabled = (NS_SUCCEEDED(rv) && cVar);
+ mUserAgentIsDirty = true;
+ }
+
+ // general.useragent.override
+ if (PREF_CHANGED(UA_PREF("override"))) {
+ prefs->GetCharPref(UA_PREF("override"),
+ getter_Copies(mUserAgentOverride));
+ mUserAgentIsDirty = true;
+ }
+
+#ifdef ANDROID
+ // general.useragent.use_device
+ if (PREF_CHANGED(UA_PREF("use_device"))) {
+ if (Preferences::GetBool(UA_PREF("use_device"), false)) {
+ mDeviceModelId = mozilla::net::GetDeviceModelId();
+ } else {
+ mDeviceModelId = EmptyCString();
+ }
+ mUserAgentIsDirty = true;
+ }
+#endif
+
+ //
+ // HTTP options
+ //
+
+ if (PREF_CHANGED(HTTP_PREF("keep-alive.timeout"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("keep-alive.timeout"), &val);
+ if (NS_SUCCEEDED(rv))
+ mIdleTimeout = PR_SecondsToInterval(clamped(val, 1, 0xffff));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("request.max-attempts"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("request.max-attempts"), &val);
+ if (NS_SUCCEEDED(rv))
+ mMaxRequestAttempts = (uint16_t) clamped(val, 1, 0xffff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("request.max-start-delay"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("request.max-start-delay"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxRequestDelay = (uint16_t) clamped(val, 0, 0xffff);
+ if (mConnMgr)
+ mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_REQUEST_DELAY,
+ mMaxRequestDelay);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("response.timeout"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("response.timeout"), &val);
+ if (NS_SUCCEEDED(rv))
+ mResponseTimeout = PR_SecondsToInterval(clamped(val, 0, 0xffff));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("network-changed.timeout"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("network-changed.timeout"), &val);
+ if (NS_SUCCEEDED(rv))
+ mNetworkChangedTimeout = clamped(val, 1, 600) * 1000;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max-connections"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("max-connections"), &val);
+ if (NS_SUCCEEDED(rv)) {
+
+ mMaxConnections = (uint16_t) clamped((uint32_t)val,
+ (uint32_t)1, MaxSocketCount());
+
+ if (mConnMgr)
+ mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_CONNECTIONS,
+ mMaxConnections);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max-persistent-connections-per-server"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("max-persistent-connections-per-server"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxPersistentConnectionsPerServer = (uint8_t) clamped(val, 1, 0xff);
+ if (mConnMgr)
+ mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_PERSISTENT_CONNECTIONS_PER_HOST,
+ mMaxPersistentConnectionsPerServer);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max-persistent-connections-per-proxy"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("max-persistent-connections-per-proxy"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxPersistentConnectionsPerProxy = (uint8_t) clamped(val, 1, 0xff);
+ if (mConnMgr)
+ mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_PERSISTENT_CONNECTIONS_PER_PROXY,
+ mMaxPersistentConnectionsPerProxy);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("sendRefererHeader"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("sendRefererHeader"), &val);
+ if (NS_SUCCEEDED(rv))
+ mReferrerLevel = (uint8_t) clamped(val, 0, 0xff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("referer.spoofSource"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("referer.spoofSource"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mSpoofReferrerSource = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("referer.trimmingPolicy"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("referer.trimmingPolicy"), &val);
+ if (NS_SUCCEEDED(rv))
+ mReferrerTrimmingPolicy = (uint8_t) clamped(val, 0, 2);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("referer.XOriginTrimmingPolicy"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("referer.XOriginTrimmingPolicy"), &val);
+ if (NS_SUCCEEDED(rv))
+ mReferrerXOriginTrimmingPolicy = (uint8_t) clamped(val, 0, 2);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("referer.XOriginPolicy"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("referer.XOriginPolicy"), &val);
+ if (NS_SUCCEEDED(rv))
+ mReferrerXOriginPolicy = (uint8_t) clamped(val, 0, 0xff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("redirection-limit"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("redirection-limit"), &val);
+ if (NS_SUCCEEDED(rv))
+ mRedirectionLimit = (uint8_t) clamped(val, 0, 0xff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("connection-retry-timeout"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("connection-retry-timeout"), &val);
+ if (NS_SUCCEEDED(rv))
+ mIdleSynTimeout = (uint16_t) clamped(val, 0, 3000);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("fast-fallback-to-IPv4"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("fast-fallback-to-IPv4"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mFastFallbackToIPv4 = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("version"))) {
+ nsXPIDLCString httpVersion;
+ prefs->GetCharPref(HTTP_PREF("version"), getter_Copies(httpVersion));
+ if (httpVersion) {
+ if (!PL_strcmp(httpVersion, "1.1"))
+ mHttpVersion = NS_HTTP_VERSION_1_1;
+ else if (!PL_strcmp(httpVersion, "0.9"))
+ mHttpVersion = NS_HTTP_VERSION_0_9;
+ else
+ mHttpVersion = NS_HTTP_VERSION_1_0;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("proxy.version"))) {
+ nsXPIDLCString httpVersion;
+ prefs->GetCharPref(HTTP_PREF("proxy.version"), getter_Copies(httpVersion));
+ if (httpVersion) {
+ if (!PL_strcmp(httpVersion, "1.1"))
+ mProxyHttpVersion = NS_HTTP_VERSION_1_1;
+ else
+ mProxyHttpVersion = NS_HTTP_VERSION_1_0;
+ // it does not make sense to issue a HTTP/0.9 request to a proxy server
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pipelining"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("pipelining"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ if (cVar)
+ mCapabilities |= NS_HTTP_ALLOW_PIPELINING;
+ else
+ mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING;
+ mPipeliningEnabled = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pipelining.maxrequests"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxrequests"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxPipelinedRequests = clamped(val, 1, 0xffff);
+ if (mConnMgr)
+ mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_PIPELINED_REQUESTS,
+ mMaxPipelinedRequests);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pipelining.max-optimistic-requests"))) {
+ rv = prefs->
+ GetIntPref(HTTP_PREF("pipelining.max-optimistic-requests"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxOptimisticPipelinedRequests = clamped(val, 1, 0xffff);
+ if (mConnMgr)
+ mConnMgr->UpdateParam
+ (nsHttpConnectionMgr::MAX_OPTIMISTIC_PIPELINED_REQUESTS,
+ mMaxOptimisticPipelinedRequests);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pipelining.aggressive"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("pipelining.aggressive"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mPipelineAggressive = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pipelining.maxsize"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxsize"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxPipelineObjectSize =
+ static_cast<int64_t>(clamped(val, 1000, 100000000));
+ }
+ }
+
+ // Determines whether or not to actually reschedule after the
+ // reschedule-timeout has expired
+ if (PREF_CHANGED(HTTP_PREF("pipelining.reschedule-on-timeout"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("pipelining.reschedule-on-timeout"),
+ &cVar);
+ if (NS_SUCCEEDED(rv))
+ mPipelineRescheduleOnTimeout = cVar;
+ }
+
+ // The amount of time head of line blocking is allowed (in ms)
+ // before the blocked transactions are moved to another pipeline
+ if (PREF_CHANGED(HTTP_PREF("pipelining.reschedule-timeout"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("pipelining.reschedule-timeout"),
+ &val);
+ if (NS_SUCCEEDED(rv)) {
+ mPipelineRescheduleTimeout =
+ PR_MillisecondsToInterval((uint16_t) clamped(val, 500, 0xffff));
+ }
+ }
+
+ // The amount of time a pipelined transaction is allowed to wait before
+ // being canceled and retried in a non-pipeline connection
+ if (PREF_CHANGED(HTTP_PREF("pipelining.read-timeout"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("pipelining.read-timeout"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mPipelineReadTimeout =
+ PR_MillisecondsToInterval((uint16_t) clamped(val, 5000,
+ 0xffff));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pipelining.ssl"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("pipelining.ssl"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mPipeliningOverSSL = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("proxy.pipelining"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("proxy.pipelining"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mProxyPipelining = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("qos"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("qos"), &val);
+ if (NS_SUCCEEDED(rv))
+ mQoSBits = (uint8_t) clamped(val, 0, 0xff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("accept.default"))) {
+ nsXPIDLCString accept;
+ rv = prefs->GetCharPref(HTTP_PREF("accept.default"),
+ getter_Copies(accept));
+ if (NS_SUCCEEDED(rv))
+ SetAccept(accept);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("accept-encoding"))) {
+ nsXPIDLCString acceptEncodings;
+ rv = prefs->GetCharPref(HTTP_PREF("accept-encoding"),
+ getter_Copies(acceptEncodings));
+ if (NS_SUCCEEDED(rv)) {
+ SetAcceptEncodings(acceptEncodings, false);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("accept-encoding.secure"))) {
+ nsXPIDLCString acceptEncodings;
+ rv = prefs->GetCharPref(HTTP_PREF("accept-encoding.secure"),
+ getter_Copies(acceptEncodings));
+ if (NS_SUCCEEDED(rv)) {
+ SetAcceptEncodings(acceptEncodings, true);
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("default-socket-type"))) {
+ nsXPIDLCString sval;
+ rv = prefs->GetCharPref(HTTP_PREF("default-socket-type"),
+ getter_Copies(sval));
+ if (NS_SUCCEEDED(rv)) {
+ if (sval.IsEmpty())
+ mDefaultSocketType.Adopt(0);
+ else {
+ // verify that this socket type is actually valid
+ nsCOMPtr<nsISocketProviderService> sps(
+ do_GetService(NS_SOCKETPROVIDERSERVICE_CONTRACTID));
+ if (sps) {
+ nsCOMPtr<nsISocketProvider> sp;
+ rv = sps->GetSocketProvider(sval, getter_AddRefs(sp));
+ if (NS_SUCCEEDED(rv)) {
+ // OK, this looks like a valid socket provider.
+ mDefaultSocketType.Assign(sval);
+ }
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("prompt-temp-redirect"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("prompt-temp-redirect"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mPromptTempRedirect = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("assoc-req.enforce"))) {
+ cVar = false;
+ rv = prefs->GetBoolPref(HTTP_PREF("assoc-req.enforce"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mEnforceAssocReq = cVar;
+ }
+
+ // enable Persistent caching for HTTPS - bug#205921
+ if (PREF_CHANGED(BROWSER_PREF("disk_cache_ssl"))) {
+ cVar = false;
+ rv = prefs->GetBoolPref(BROWSER_PREF("disk_cache_ssl"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mEnablePersistentHttpsCaching = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("phishy-userpass-length"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("phishy-userpass-length"), &val);
+ if (NS_SUCCEEDED(rv))
+ mPhishyUserPassLength = (uint8_t) clamped(val, 0, 0xff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.enabled"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mEnableSpdy = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.enabled.http2"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled.http2"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mHttp2Enabled = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.enabled.deps"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled.deps"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mUseH2Deps = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.enforce-tls-profile"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("spdy.enforce-tls-profile"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mEnforceHttp2TlsProfile = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.coalesce-hostnames"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("spdy.coalesce-hostnames"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mCoalesceSpdy = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.persistent-settings"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("spdy.persistent-settings"),
+ &cVar);
+ if (NS_SUCCEEDED(rv))
+ mSpdyPersistentSettings = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.timeout"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("spdy.timeout"), &val);
+ if (NS_SUCCEEDED(rv))
+ mSpdyTimeout = PR_SecondsToInterval(clamped(val, 1, 0xffff));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.chunk-size"))) {
+ // keep this within http/2 ranges of 1 to 2^14-1
+ rv = prefs->GetIntPref(HTTP_PREF("spdy.chunk-size"), &val);
+ if (NS_SUCCEEDED(rv))
+ mSpdySendingChunkSize = (uint32_t) clamped(val, 1, 0x3fff);
+ }
+
+ // The amount of idle seconds on a spdy connection before initiating a
+ // server ping. 0 will disable.
+ if (PREF_CHANGED(HTTP_PREF("spdy.ping-threshold"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("spdy.ping-threshold"), &val);
+ if (NS_SUCCEEDED(rv))
+ mSpdyPingThreshold =
+ PR_SecondsToInterval((uint16_t) clamped(val, 0, 0x7fffffff));
+ }
+
+ // The amount of seconds to wait for a spdy ping response before
+ // closing the session.
+ if (PREF_CHANGED(HTTP_PREF("spdy.ping-timeout"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("spdy.ping-timeout"), &val);
+ if (NS_SUCCEEDED(rv))
+ mSpdyPingTimeout =
+ PR_SecondsToInterval((uint16_t) clamped(val, 0, 0x7fffffff));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.allow-push"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("spdy.allow-push"),
+ &cVar);
+ if (NS_SUCCEEDED(rv))
+ mAllowPush = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("altsvc.enabled"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("altsvc.enabled"),
+ &cVar);
+ if (NS_SUCCEEDED(rv))
+ mEnableAltSvc = cVar;
+ }
+
+
+ if (PREF_CHANGED(HTTP_PREF("altsvc.oe"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("altsvc.oe"),
+ &cVar);
+ if (NS_SUCCEEDED(rv))
+ mEnableAltSvcOE = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.push-allowance"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("spdy.push-allowance"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mSpdyPushAllowance =
+ static_cast<uint32_t>
+ (clamped(val, 1024, static_cast<int32_t>(ASpdySession::kInitialRwin)));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.pull-allowance"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("spdy.pull-allowance"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mSpdyPullAllowance =
+ static_cast<uint32_t>(clamped(val, 1024, 0x7fffffff));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.default-concurrent"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("spdy.default-concurrent"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mDefaultSpdyConcurrent =
+ static_cast<uint32_t>(std::max<int32_t>(std::min<int32_t>(val, 9999), 1));
+ }
+ }
+
+ // The amount of seconds to wait for a spdy ping response before
+ // closing the session.
+ if (PREF_CHANGED(HTTP_PREF("spdy.send-buffer-size"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("spdy.send-buffer-size"), &val);
+ if (NS_SUCCEEDED(rv))
+ mSpdySendBufferSize = (uint32_t) clamped(val, 1500, 0x7fffffff);
+ }
+
+ // The maximum amount of time to wait for socket transport to be
+ // established
+ if (PREF_CHANGED(HTTP_PREF("connection-timeout"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("connection-timeout"), &val);
+ if (NS_SUCCEEDED(rv))
+ // the pref is in seconds, but the variable is in milliseconds
+ mConnectTimeout = clamped(val, 1, 0xffff) * PR_MSEC_PER_SEC;
+ }
+
+ // The maximum number of current global half open sockets allowable
+ // for starting a new speculative connection.
+ if (PREF_CHANGED(HTTP_PREF("speculative-parallel-limit"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("speculative-parallel-limit"), &val);
+ if (NS_SUCCEEDED(rv))
+ mParallelSpeculativeConnectLimit = (uint32_t) clamped(val, 0, 1024);
+ }
+
+ // Whether or not to block requests for non head js/css items (e.g. media)
+ // while those elements load.
+ if (PREF_CHANGED(HTTP_PREF("rendering-critical-requests-prioritization"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("rendering-critical-requests-prioritization"), &cVar);
+ if (NS_SUCCEEDED(rv))
+ mCriticalRequestPrioritization = cVar;
+ }
+
+ // on transition of network.http.diagnostics to true print
+ // a bunch of information to the console
+ if (pref && PREF_CHANGED(HTTP_PREF("diagnostics"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("diagnostics"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ if (mConnMgr)
+ mConnMgr->PrintDiagnostics();
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("max_response_header_size"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("max_response_header_size"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxHttpResponseHeaderSize = val;
+ }
+ }
+ //
+ // INTL options
+ //
+
+ if (PREF_CHANGED(INTL_ACCEPT_LANGUAGES)) {
+ nsCOMPtr<nsIPrefLocalizedString> pls;
+ prefs->GetComplexValue(INTL_ACCEPT_LANGUAGES,
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(pls));
+ if (pls) {
+ nsXPIDLString uval;
+ pls->ToString(getter_Copies(uval));
+ if (uval)
+ SetAcceptLanguages(NS_ConvertUTF16toUTF8(uval).get());
+ }
+ }
+
+ //
+ // Tracking options
+ //
+
+ if (PREF_CHANGED(DONOTTRACK_HEADER_ENABLED)) {
+ cVar = false;
+ rv = prefs->GetBoolPref(DONOTTRACK_HEADER_ENABLED, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mDoNotTrackEnabled = cVar;
+ }
+ }
+ // Hint option
+ if (PREF_CHANGED(SAFE_HINT_HEADER_VALUE)) {
+ cVar = false;
+ rv = prefs->GetBoolPref(SAFE_HINT_HEADER_VALUE, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mSafeHintEnabled = cVar;
+ }
+ }
+
+ // toggle to true anytime a token bucket related pref is changed.. that
+ // includes telemetry and allow-experiments because of the abtest profile
+ bool requestTokenBucketUpdated = false;
+
+ //
+ // Telemetry
+ //
+
+ if (PREF_CHANGED(TELEMETRY_ENABLED)) {
+ cVar = false;
+ requestTokenBucketUpdated = true;
+ rv = prefs->GetBoolPref(TELEMETRY_ENABLED, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mTelemetryEnabled = cVar;
+ }
+ }
+
+ // "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256" is the required h2 interop
+ // suite.
+
+ if (PREF_CHANGED(H2MANDATORY_SUITE)) {
+ cVar = false;
+ rv = prefs->GetBoolPref(H2MANDATORY_SUITE, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mH2MandatorySuiteEnabled = cVar;
+ }
+ }
+
+ //
+ // network.allow-experiments
+ //
+ if (PREF_CHANGED(ALLOW_EXPERIMENTS)) {
+ cVar = true;
+ requestTokenBucketUpdated = true;
+ rv = prefs->GetBoolPref(ALLOW_EXPERIMENTS, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mAllowExperiments = cVar;
+ }
+ }
+
+ // network.http.debug-observations
+ if (PREF_CHANGED("network.http.debug-observations")) {
+ cVar = false;
+ rv = prefs->GetBoolPref("network.http.debug-observations", &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mDebugObservations = cVar;
+ }
+ }
+
+ //
+ // Test HTTP Pipelining (bug796192)
+ // If experiments are allowed and pipelining is Off,
+ // turn it On for just 10 minutes
+ //
+ if (mAllowExperiments && !mPipeliningEnabled &&
+ PREF_CHANGED(HTTP_PREF("pipelining.abtest"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("pipelining.abtest"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ // If option is enabled, only test for ~1% of sessions
+ if (cVar && !(rand() % 128)) {
+ mCapabilities |= NS_HTTP_ALLOW_PIPELINING;
+ if (mPipelineTestTimer)
+ mPipelineTestTimer->Cancel();
+ mPipelineTestTimer =
+ do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = mPipelineTestTimer->InitWithFuncCallback(
+ TimerCallback, this, 10*60*1000, // 10 minutes
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ } else {
+ mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING;
+ if (mPipelineTestTimer) {
+ mPipelineTestTimer->Cancel();
+ mPipelineTestTimer = nullptr;
+ }
+ }
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.enabled"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("pacing.requests.enabled"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestTokenBucketEnabled = cVar;
+ requestTokenBucketUpdated = true;
+ }
+ }
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.min-parallelism"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("pacing.requests.min-parallelism"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestTokenBucketMinParallelism = static_cast<uint16_t>(clamped(val, 1, 1024));
+ requestTokenBucketUpdated = true;
+ }
+ }
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.hz"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("pacing.requests.hz"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestTokenBucketHz = static_cast<uint32_t>(clamped(val, 1, 10000));
+ requestTokenBucketUpdated = true;
+ }
+ }
+ if (PREF_CHANGED(HTTP_PREF("pacing.requests.burst"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("pacing.requests.burst"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mRequestTokenBucketBurst = val ? val : 1;
+ requestTokenBucketUpdated = true;
+ }
+ }
+ if (requestTokenBucketUpdated) {
+ MakeNewRequestTokenBucket();
+ }
+
+ // Keepalive values for initial and idle connections.
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.short_lived_connections"))) {
+ rv = prefs->GetBoolPref(
+ HTTP_PREF("tcp_keepalive.short_lived_connections"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar != mTCPKeepaliveShortLivedEnabled) {
+ mTCPKeepaliveShortLivedEnabled = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.short_lived_time"))) {
+ rv = prefs->GetIntPref(
+ HTTP_PREF("tcp_keepalive.short_lived_time"), &val);
+ if (NS_SUCCEEDED(rv) && val > 0)
+ mTCPKeepaliveShortLivedTimeS = clamped(val, 1, 300); // Max 5 mins.
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.short_lived_idle_time"))) {
+ rv = prefs->GetIntPref(
+ HTTP_PREF("tcp_keepalive.short_lived_idle_time"), &val);
+ if (NS_SUCCEEDED(rv) && val > 0)
+ mTCPKeepaliveShortLivedIdleTimeS = clamped(val,
+ 1, kMaxTCPKeepIdle);
+ }
+
+ // Keepalive values for Long-lived Connections.
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.long_lived_connections"))) {
+ rv = prefs->GetBoolPref(
+ HTTP_PREF("tcp_keepalive.long_lived_connections"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar != mTCPKeepaliveLongLivedEnabled) {
+ mTCPKeepaliveLongLivedEnabled = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.long_lived_idle_time"))) {
+ rv = prefs->GetIntPref(
+ HTTP_PREF("tcp_keepalive.long_lived_idle_time"), &val);
+ if (NS_SUCCEEDED(rv) && val > 0)
+ mTCPKeepaliveLongLivedIdleTimeS = clamped(val,
+ 1, kMaxTCPKeepIdle);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("enforce-framing.http1")) ||
+ PREF_CHANGED(HTTP_PREF("enforce-framing.soft")) ) {
+ rv = prefs->GetBoolPref(HTTP_PREF("enforce-framing.http1"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ mEnforceH1Framing = FRAMECHECK_STRICT;
+ } else {
+ rv = prefs->GetBoolPref(HTTP_PREF("enforce-framing.soft"), &cVar);
+ if (NS_SUCCEEDED(rv) && cVar) {
+ mEnforceH1Framing = FRAMECHECK_BARELY;
+ } else {
+ mEnforceH1Framing = FRAMECHECK_LAX;
+ }
+ }
+ }
+
+ // remote content-signature testing option
+ if (PREF_CHANGED(NEW_TAB_REMOTE_MODE)) {
+ nsAutoCString channel;
+ prefs->GetCharPref(NEW_TAB_REMOTE_MODE, getter_Copies(channel));
+ if (channel.EqualsLiteral("test") ||
+ channel.EqualsLiteral("test2") ||
+ channel.EqualsLiteral("dev")) {
+ mNewTabContentSignaturesDisabled = true;
+ } else {
+ mNewTabContentSignaturesDisabled = false;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("keep_empty_response_headers_as_empty_string"))) {
+ rv = prefs->GetBoolPref(HTTP_PREF("keep_empty_response_headers_as_empty_string"),
+ &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mKeepEmptyResponseHeadersAsEmtpyString = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.hpack-default-buffer"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("spdy.default-hpack-buffer"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mDefaultHpackBuffer = val;
+ }
+ }
+
+ // Enable HTTP response timeout if TCP Keepalives are disabled.
+ mResponseTimeoutEnabled = !mTCPKeepaliveShortLivedEnabled &&
+ !mTCPKeepaliveLongLivedEnabled;
+
+#undef PREF_CHANGED
+#undef MULTI_PREF_CHANGED
+}
+
+
+/**
+ * Static method called by mPipelineTestTimer when it expires.
+ */
+void
+nsHttpHandler::TimerCallback(nsITimer * aTimer, void * aClosure)
+{
+ RefPtr<nsHttpHandler> thisObject = static_cast<nsHttpHandler*>(aClosure);
+ if (!thisObject->mPipeliningEnabled)
+ thisObject->mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING;
+}
+
+/**
+ * Currently, only regularizes the case of subtags.
+ */
+static void
+CanonicalizeLanguageTag(char *languageTag)
+{
+ char *s = languageTag;
+ while (*s != '\0') {
+ *s = nsCRT::ToLower(*s);
+ s++;
+ }
+
+ s = languageTag;
+ bool isFirst = true;
+ bool seenSingleton = false;
+ while (*s != '\0') {
+ char *subTagEnd = strchr(s, '-');
+ if (subTagEnd == nullptr) {
+ subTagEnd = strchr(s, '\0');
+ }
+
+ if (isFirst) {
+ isFirst = false;
+ } else if (seenSingleton) {
+ // Do nothing
+ } else {
+ size_t subTagLength = subTagEnd - s;
+ if (subTagLength == 1) {
+ seenSingleton = true;
+ } else if (subTagLength == 2) {
+ *s = nsCRT::ToUpper(*s);
+ *(s + 1) = nsCRT::ToUpper(*(s + 1));
+ } else if (subTagLength == 4) {
+ *s = nsCRT::ToUpper(*s);
+ }
+ }
+
+ s = subTagEnd;
+ if (*s != '\0') {
+ s++;
+ }
+ }
+}
+
+/**
+ * Allocates a C string into that contains a ISO 639 language list
+ * notated with HTTP "q" values for output with a HTTP Accept-Language
+ * header. Previous q values will be stripped because the order of
+ * the langs imply the q value. The q values are calculated by dividing
+ * 1.0 amongst the number of languages present.
+ *
+ * Ex: passing: "en, ja"
+ * returns: "en,ja;q=0.5"
+ *
+ * passing: "en, ja, fr_CA"
+ * returns: "en,ja;q=0.7,fr_CA;q=0.3"
+ */
+static nsresult
+PrepareAcceptLanguages(const char *i_AcceptLanguages, nsACString &o_AcceptLanguages)
+{
+ if (!i_AcceptLanguages)
+ return NS_OK;
+
+ uint32_t n, count_n, size, wrote;
+ double q, dec;
+ char *p, *p2, *token, *q_Accept, *o_Accept;
+ const char *comma;
+ int32_t available;
+
+ o_Accept = strdup(i_AcceptLanguages);
+ if (!o_Accept)
+ return NS_ERROR_OUT_OF_MEMORY;
+ for (p = o_Accept, n = size = 0; '\0' != *p; p++) {
+ if (*p == ',') n++;
+ size++;
+ }
+
+ available = size + ++n * 11 + 1;
+ q_Accept = new char[available];
+ if (!q_Accept) {
+ free(o_Accept);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ *q_Accept = '\0';
+ q = 1.0;
+ dec = q / (double) n;
+ count_n = 0;
+ p2 = q_Accept;
+ for (token = nsCRT::strtok(o_Accept, ",", &p);
+ token != (char *) 0;
+ token = nsCRT::strtok(p, ",", &p))
+ {
+ token = net_FindCharNotInSet(token, HTTP_LWS);
+ char* trim;
+ trim = net_FindCharInSet(token, ";" HTTP_LWS);
+ if (trim != (char*)0) // remove "; q=..." if present
+ *trim = '\0';
+
+ if (*token != '\0') {
+ CanonicalizeLanguageTag(token);
+
+ comma = count_n++ != 0 ? "," : ""; // delimiter if not first item
+ uint32_t u = QVAL_TO_UINT(q);
+
+ // Only display q-value if less than 1.00.
+ if (u < 100) {
+ const char *qval_str;
+
+ // With a small number of languages, one decimal place is enough to prevent duplicate q-values.
+ // Also, trailing zeroes do not add any information, so they can be removed.
+ if ((n < 10) || ((u % 10) == 0)) {
+ u = (u + 5) / 10;
+ qval_str = "%s%s;q=0.%u";
+ } else {
+ // Values below 10 require zero padding.
+ qval_str = "%s%s;q=0.%02u";
+ }
+
+ wrote = snprintf(p2, available, qval_str, comma, token, u);
+ } else {
+ wrote = snprintf(p2, available, "%s%s", comma, token);
+ }
+
+ q -= dec;
+ p2 += wrote;
+ available -= wrote;
+ MOZ_ASSERT(available > 0, "allocated string not long enough");
+ }
+ }
+ free(o_Accept);
+
+ o_AcceptLanguages.Assign((const char *) q_Accept);
+ delete [] q_Accept;
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpHandler::SetAcceptLanguages(const char *aAcceptLanguages)
+{
+ nsAutoCString buf;
+ nsresult rv = PrepareAcceptLanguages(aAcceptLanguages, buf);
+ if (NS_SUCCEEDED(rv))
+ mAcceptLanguages.Assign(buf);
+ return rv;
+}
+
+nsresult
+nsHttpHandler::SetAccept(const char *aAccept)
+{
+ mAccept = aAccept;
+ return NS_OK;
+}
+
+nsresult
+nsHttpHandler::SetAcceptEncodings(const char *aAcceptEncodings, bool isSecure)
+{
+ if (isSecure) {
+ mHttpsAcceptEncodings = aAcceptEncodings;
+ } else {
+ // use legacy list if a secure override is not specified
+ mHttpAcceptEncodings = aAcceptEncodings;
+ if (mHttpsAcceptEncodings.IsEmpty()) {
+ mHttpsAcceptEncodings = aAcceptEncodings;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpHandler,
+ nsIHttpProtocolHandler,
+ nsIProxiedProtocolHandler,
+ nsIProtocolHandler,
+ nsIObserver,
+ nsISupportsWeakReference,
+ nsISpeculativeConnect)
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsIProtocolHandler
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpHandler::GetScheme(nsACString &aScheme)
+{
+ aScheme.AssignLiteral("http");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetDefaultPort(int32_t *result)
+{
+ *result = NS_HTTP_DEFAULT_PORT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetProtocolFlags(uint32_t *result)
+{
+ *result = NS_HTTP_PROTOCOL_FLAGS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ nsIURI **aURI)
+{
+ return mozilla::net::NewURI(aSpec, aCharset, aBaseURI, NS_HTTP_DEFAULT_PORT, aURI);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ LOG(("nsHttpHandler::NewChannel\n"));
+
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(result);
+
+ bool isHttp = false, isHttps = false;
+
+ // Verify that we have been given a valid scheme
+ nsresult rv = uri->SchemeIs("http", &isHttp);
+ if (NS_FAILED(rv)) return rv;
+ if (!isHttp) {
+ rv = uri->SchemeIs("https", &isHttps);
+ if (NS_FAILED(rv)) return rv;
+ if (!isHttps) {
+ NS_WARNING("Invalid URI scheme");
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NewProxiedChannel2(uri, nullptr, 0, nullptr, aLoadInfo, result);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::NewChannel(nsIURI *uri, nsIChannel **result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsIProxiedProtocolHandler
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpHandler::NewProxiedChannel2(nsIURI *uri,
+ nsIProxyInfo* givenProxyInfo,
+ uint32_t proxyResolveFlags,
+ nsIURI *proxyURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ RefPtr<HttpBaseChannel> httpChannel;
+
+ LOG(("nsHttpHandler::NewProxiedChannel [proxyInfo=%p]\n",
+ givenProxyInfo));
+
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (givenProxyInfo) {
+ proxyInfo = do_QueryInterface(givenProxyInfo);
+ NS_ENSURE_ARG(proxyInfo);
+ }
+
+ bool https;
+ nsresult rv = uri->SchemeIs("https", &https);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (IsNeckoChild()) {
+ httpChannel = new HttpChannelChild();
+ } else {
+ httpChannel = new nsHttpChannel();
+ }
+
+ uint32_t caps = mCapabilities;
+
+ if (https) {
+ // enable pipelining over SSL if requested
+ if (mPipeliningOverSSL)
+ caps |= NS_HTTP_ALLOW_PIPELINING;
+ }
+
+ if (!IsNeckoChild()) {
+ // HACK: make sure PSM gets initialized on the main thread.
+ net_EnsurePSMInit();
+ }
+
+ nsID channelId;
+ rv = NewChannelId(&channelId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = httpChannel->Init(uri, caps, proxyInfo, proxyResolveFlags, proxyURI, channelId);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // set the loadInfo on the new channel
+ rv = httpChannel->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ httpChannel.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::NewProxiedChannel(nsIURI *uri,
+ nsIProxyInfo* givenProxyInfo,
+ uint32_t proxyResolveFlags,
+ nsIURI *proxyURI,
+ nsIChannel **result)
+{
+ return NewProxiedChannel2(uri, givenProxyInfo,
+ proxyResolveFlags, proxyURI,
+ nullptr, result);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsIHttpProtocolHandler
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpHandler::GetUserAgent(nsACString &value)
+{
+ value = UserAgent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetAppName(nsACString &value)
+{
+ value = mLegacyAppName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetAppVersion(nsACString &value)
+{
+ value = mLegacyAppVersion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetPlatform(nsACString &value)
+{
+ value = mPlatform;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetOscpu(nsACString &value)
+{
+ value = mOscpu;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetMisc(nsACString &value)
+{
+ value = mMisc;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpHandler::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("nsHttpHandler::Observe [topic=\"%s\"]\n", topic));
+
+ if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(subject);
+ if (prefBranch)
+ PrefsChanged(prefBranch, NS_ConvertUTF16toUTF8(data).get());
+ } else if (!strcmp(topic, "profile-change-net-teardown") ||
+ !strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) ) {
+
+ mHandlerActive = false;
+
+ // clear cache of all authentication credentials.
+ mAuthCache.ClearAll();
+ mPrivateAuthCache.ClearAll();
+ if (mWifiTickler)
+ mWifiTickler->Cancel();
+
+ // Inform nsIOService that network is tearing down.
+ gIOService->SetHttpHandlerAlreadyShutingDown();
+
+ ShutdownConnectionManager();
+
+ // need to reset the session start time since cache validation may
+ // depend on this value.
+ mSessionStartTime = NowInSeconds();
+
+ if (!mDoNotTrackEnabled) {
+ Telemetry::Accumulate(Telemetry::DNT_USAGE, 2);
+ } else {
+ Telemetry::Accumulate(Telemetry::DNT_USAGE, 1);
+ }
+ } else if (!strcmp(topic, "profile-change-net-restore")) {
+ // initialize connection manager
+ InitConnectionMgr();
+ } else if (!strcmp(topic, "net:clear-active-logins")) {
+ mAuthCache.ClearAll();
+ mPrivateAuthCache.ClearAll();
+ } else if (!strcmp(topic, "net:prune-dead-connections")) {
+ if (mConnMgr) {
+ mConnMgr->PruneDeadConnections();
+ }
+ } else if (!strcmp(topic, "net:prune-all-connections")) {
+ if (mConnMgr) {
+ mConnMgr->DoShiftReloadConnectionCleanup(nullptr);
+ mConnMgr->PruneDeadConnections();
+ }
+ } else if (!strcmp(topic, "net:failed-to-process-uri-content")) {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(subject);
+ if (uri && mConnMgr) {
+ mConnMgr->ReportFailedToProcess(uri);
+ }
+ } else if (!strcmp(topic, "last-pb-context-exited")) {
+ mPrivateAuthCache.ClearAll();
+ if (mConnMgr) {
+ mConnMgr->ClearAltServiceMappings();
+ }
+ } else if (!strcmp(topic, "webapps-clear-data")) {
+ if (mConnMgr) {
+ mConnMgr->ClearAltServiceMappings();
+ }
+ } else if (!strcmp(topic, "browser:purge-session-history")) {
+ if (mConnMgr) {
+ if (gSocketTransportService) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(mConnMgr,
+ &nsHttpConnectionMgr::ClearConnectionHistory);
+ gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ mConnMgr->ClearAltServiceMappings();
+ }
+ } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ nsAutoCString converted = NS_ConvertUTF16toUTF8(data);
+ if (!strcmp(converted.get(), NS_NETWORK_LINK_DATA_CHANGED)) {
+ if (mConnMgr) {
+ mConnMgr->PruneDeadConnections();
+ mConnMgr->VerifyTraffic();
+ }
+ }
+ } else if (!strcmp(topic, "application-background")) {
+ // going to the background on android means we should close
+ // down idle connections for power conservation
+ if (mConnMgr) {
+ mConnMgr->DoShiftReloadConnectionCleanup(nullptr);
+ }
+ }
+
+ return NS_OK;
+}
+
+// nsISpeculativeConnect
+
+nsresult
+nsHttpHandler::SpeculativeConnectInternal(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks,
+ bool anonymous)
+{
+ if (IsNeckoChild()) {
+ ipc::URIParams params;
+ SerializeURI(aURI, params);
+ gNeckoChild->SendSpeculativeConnect(params,
+ IPC::Principal(aPrincipal),
+ anonymous);
+ return NS_OK;
+ }
+
+ if (!mHandlerActive)
+ return NS_OK;
+
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (mDebugObservations && obsService) {
+ // this is basically used for test coverage of an otherwise 'hintable'
+ // feature
+ obsService->NotifyObservers(nullptr, "speculative-connect-request",
+ nullptr);
+ if (!IsNeckoChild()) {
+ for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ Unused << neckoParent->SendSpeculativeConnectRequest();
+ }
+ }
+ }
+
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ bool isStsHost = false;
+ if (!sss)
+ return NS_OK;
+
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(aCallbacks);
+ uint32_t flags = 0;
+ if (loadContext && loadContext->UsePrivateBrowsing())
+ flags |= nsISocketProvider::NO_PERMANENT_STORAGE;
+ nsCOMPtr<nsIURI> clone;
+ if (NS_SUCCEEDED(sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS,
+ aURI, flags, nullptr, &isStsHost)) &&
+ isStsHost) {
+ if (NS_SUCCEEDED(NS_GetSecureUpgradedURI(aURI,
+ getter_AddRefs(clone)))) {
+ aURI = clone.get();
+ // (NOTE: We better make sure |clone| stays alive until the end
+ // of the function now, since our aURI arg now points to it!)
+ }
+ }
+
+ nsAutoCString scheme;
+ nsresult rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // If this is HTTPS, make sure PSM is initialized as the channel
+ // creation path may have been bypassed
+ if (scheme.EqualsLiteral("https")) {
+ if (!IsNeckoChild()) {
+ // make sure PSM gets initialized on the main thread.
+ net_EnsurePSMInit();
+ }
+ }
+ // Ensure that this is HTTP or HTTPS, otherwise we don't do preconnect here
+ else if (!scheme.EqualsLiteral("http"))
+ return NS_ERROR_UNEXPECTED;
+
+ // Construct connection info object
+ bool usingSSL = false;
+ rv = aURI->SchemeIs("https", &usingSSL);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString host;
+ rv = aURI->GetAsciiHost(host);
+ if (NS_FAILED(rv))
+ return rv;
+
+ int32_t port = -1;
+ rv = aURI->GetPort(&port);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString username;
+ aURI->GetUsername(username);
+
+ NeckoOriginAttributes neckoOriginAttributes;
+ // If the principal is given, we use the originAttributes from this
+ // principal. Otherwise, we use the originAttributes from the
+ // loadContext.
+ if (aPrincipal) {
+ neckoOriginAttributes.InheritFromDocToNecko(
+ BasePrincipal::Cast(aPrincipal)->OriginAttributesRef());
+ } else if (loadContext) {
+ DocShellOriginAttributes docshellOriginAttributes;
+ loadContext->GetOriginAttributes(docshellOriginAttributes);
+ neckoOriginAttributes.InheritFromDocShellToNecko(docshellOriginAttributes);
+ }
+
+ auto *ci =
+ new nsHttpConnectionInfo(host, port, EmptyCString(), username, nullptr,
+ neckoOriginAttributes, usingSSL);
+ ci->SetAnonymous(anonymous);
+
+ return SpeculativeConnect(ci, aCallbacks);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::SpeculativeConnect(nsIURI *aURI,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, false);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::SpeculativeConnect2(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, false);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::SpeculativeAnonymousConnect(nsIURI *aURI,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, true);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::SpeculativeAnonymousConnect2(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, true);
+}
+
+void
+nsHttpHandler::TickleWifi(nsIInterfaceRequestor *cb)
+{
+ if (!cb || !mWifiTickler)
+ return;
+
+ // If B2G requires a similar mechanism nsINetworkManager, currently only avail
+ // on B2G, contains the necessary information on wifi and gateway
+
+ nsCOMPtr<nsIDOMWindow> domWindow = do_GetInterface(cb);
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow = do_QueryInterface(domWindow);
+ if (!piWindow)
+ return;
+
+ nsCOMPtr<nsIDOMNavigator> domNavigator = piWindow->GetNavigator();
+ nsCOMPtr<nsIMozNavigatorNetwork> networkNavigator =
+ do_QueryInterface(domNavigator);
+ if (!networkNavigator)
+ return;
+
+ nsCOMPtr<nsINetworkProperties> networkProperties;
+ networkNavigator->GetProperties(getter_AddRefs(networkProperties));
+ if (!networkProperties)
+ return;
+
+ uint32_t gwAddress;
+ bool isWifi;
+ nsresult rv;
+
+ rv = networkProperties->GetDhcpGateway(&gwAddress);
+ if (NS_SUCCEEDED(rv))
+ rv = networkProperties->GetIsWifi(&isWifi);
+ if (NS_FAILED(rv))
+ return;
+
+ if (!gwAddress || !isWifi)
+ return;
+
+ mWifiTickler->SetIPV4Address(gwAddress);
+ mWifiTickler->Tickle();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpsHandler implementation
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpsHandler,
+ nsIHttpProtocolHandler,
+ nsIProxiedProtocolHandler,
+ nsIProtocolHandler,
+ nsISupportsWeakReference,
+ nsISpeculativeConnect)
+
+nsresult
+nsHttpsHandler::Init()
+{
+ nsCOMPtr<nsIProtocolHandler> httpHandler(
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http"));
+ MOZ_ASSERT(httpHandler.get() != nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::GetScheme(nsACString &aScheme)
+{
+ aScheme.AssignLiteral("https");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::GetDefaultPort(int32_t *aPort)
+{
+ *aPort = NS_HTTPS_DEFAULT_PORT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::GetProtocolFlags(uint32_t *aProtocolFlags)
+{
+ *aProtocolFlags = NS_HTTP_PROTOCOL_FLAGS | URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::NewURI(const nsACString &aSpec,
+ const char *aOriginCharset,
+ nsIURI *aBaseURI,
+ nsIURI **_retval)
+{
+ return mozilla::net::NewURI(aSpec, aOriginCharset, aBaseURI, NS_HTTPS_DEFAULT_PORT, _retval);
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::NewChannel2(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** _retval)
+{
+ MOZ_ASSERT(gHttpHandler);
+ if (!gHttpHandler)
+ return NS_ERROR_UNEXPECTED;
+ return gHttpHandler->NewChannel2(aURI, aLoadInfo, _retval);
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ return NewChannel2(aURI, nullptr, _retval);
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::AllowPort(int32_t aPort, const char *aScheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+void
+nsHttpHandler::ShutdownConnectionManager()
+{
+ // ensure connection manager is shutdown
+ if (mConnMgr) {
+ mConnMgr->Shutdown();
+ }
+}
+
+nsresult
+nsHttpHandler::NewChannelId(nsID *channelId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mUUIDGen) {
+ nsresult rv;
+ mUUIDGen = do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return mUUIDGen->GenerateUUIDInPlace(channelId);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h
new file mode 100644
index 000000000..13cc72e8e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -0,0 +1,683 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpHandler_h__
+#define nsHttpHandler_h__
+
+#include "nsHttp.h"
+#include "nsHttpAuthCache.h"
+#include "nsHttpConnectionMgr.h"
+#include "ASpdySession.h"
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+
+#include "nsIHttpProtocolHandler.h"
+#include "nsIObserver.h"
+#include "nsISpeculativeConnect.h"
+
+class nsIHttpChannel;
+class nsIPrefBranch;
+class nsICancelable;
+class nsICookieService;
+class nsIIOService;
+class nsIRequestContextService;
+class nsISiteSecurityService;
+class nsIStreamConverterService;
+class nsITimer;
+class nsIUUIDGenerator;
+
+
+namespace mozilla {
+namespace net {
+
+extern Atomic<PRThread*, Relaxed> gSocketThread;
+
+class ATokenBucketEvent;
+class EventTokenBucket;
+class Tickler;
+class nsHttpConnection;
+class nsHttpConnectionInfo;
+class nsHttpTransaction;
+class AltSvcMapping;
+
+enum FrameCheckLevel {
+ FRAMECHECK_LAX,
+ FRAMECHECK_BARELY,
+ FRAMECHECK_STRICT
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpHandler - protocol handler for HTTP and HTTPS
+//-----------------------------------------------------------------------------
+
+class nsHttpHandler final : public nsIHttpProtocolHandler
+ , public nsIObserver
+ , public nsSupportsWeakReference
+ , public nsISpeculativeConnect
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIPROXIEDPROTOCOLHANDLER
+ NS_DECL_NSIHTTPPROTOCOLHANDLER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSISPECULATIVECONNECT
+
+ nsHttpHandler();
+
+ nsresult Init();
+ nsresult AddStandardRequestHeaders(nsHttpRequestHead *, bool isSecure);
+ nsresult AddConnectionHeader(nsHttpRequestHead *,
+ uint32_t capabilities);
+ bool IsAcceptableEncoding(const char *encoding, bool isSecure);
+
+ const nsAFlatCString &UserAgent();
+
+ nsHttpVersion HttpVersion() { return mHttpVersion; }
+ nsHttpVersion ProxyHttpVersion() { return mProxyHttpVersion; }
+ uint8_t ReferrerLevel() { return mReferrerLevel; }
+ bool SpoofReferrerSource() { return mSpoofReferrerSource; }
+ uint8_t ReferrerTrimmingPolicy() { return mReferrerTrimmingPolicy; }
+ uint8_t ReferrerXOriginTrimmingPolicy() {
+ return mReferrerXOriginTrimmingPolicy;
+ }
+ uint8_t ReferrerXOriginPolicy() { return mReferrerXOriginPolicy; }
+ uint8_t RedirectionLimit() { return mRedirectionLimit; }
+ PRIntervalTime IdleTimeout() { return mIdleTimeout; }
+ PRIntervalTime SpdyTimeout() { return mSpdyTimeout; }
+ PRIntervalTime ResponseTimeout() {
+ return mResponseTimeoutEnabled ? mResponseTimeout : 0;
+ }
+ PRIntervalTime ResponseTimeoutEnabled() { return mResponseTimeoutEnabled; }
+ uint32_t NetworkChangedTimeout() { return mNetworkChangedTimeout; }
+ uint16_t MaxRequestAttempts() { return mMaxRequestAttempts; }
+ const char *DefaultSocketType() { return mDefaultSocketType.get(); /* ok to return null */ }
+ uint32_t PhishyUserPassLength() { return mPhishyUserPassLength; }
+ uint8_t GetQoSBits() { return mQoSBits; }
+ uint16_t GetIdleSynTimeout() { return mIdleSynTimeout; }
+ bool FastFallbackToIPv4() { return mFastFallbackToIPv4; }
+ bool ProxyPipelining() { return mProxyPipelining; }
+ uint32_t MaxSocketCount();
+ bool EnforceAssocReq() { return mEnforceAssocReq; }
+
+ bool IsPersistentHttpsCachingEnabled() { return mEnablePersistentHttpsCaching; }
+ bool IsTelemetryEnabled() { return mTelemetryEnabled; }
+ bool AllowExperiments() { return mTelemetryEnabled && mAllowExperiments; }
+
+ bool IsSpdyEnabled() { return mEnableSpdy; }
+ bool IsHttp2Enabled() { return mHttp2Enabled; }
+ bool EnforceHttp2TlsProfile() { return mEnforceHttp2TlsProfile; }
+ bool CoalesceSpdy() { return mCoalesceSpdy; }
+ bool UseSpdyPersistentSettings() { return mSpdyPersistentSettings; }
+ uint32_t SpdySendingChunkSize() { return mSpdySendingChunkSize; }
+ uint32_t SpdySendBufferSize() { return mSpdySendBufferSize; }
+ uint32_t SpdyPushAllowance() { return mSpdyPushAllowance; }
+ uint32_t SpdyPullAllowance() { return mSpdyPullAllowance; }
+ uint32_t DefaultSpdyConcurrent() { return mDefaultSpdyConcurrent; }
+ PRIntervalTime SpdyPingThreshold() { return mSpdyPingThreshold; }
+ PRIntervalTime SpdyPingTimeout() { return mSpdyPingTimeout; }
+ bool AllowPush() { return mAllowPush; }
+ bool AllowAltSvc() { return mEnableAltSvc; }
+ bool AllowAltSvcOE() { return mEnableAltSvcOE; }
+ uint32_t ConnectTimeout() { return mConnectTimeout; }
+ uint32_t ParallelSpeculativeConnectLimit() { return mParallelSpeculativeConnectLimit; }
+ bool CriticalRequestPrioritization() { return mCriticalRequestPrioritization; }
+ bool UseH2Deps() { return mUseH2Deps; }
+
+ uint32_t MaxConnectionsPerOrigin() { return mMaxPersistentConnectionsPerServer; }
+ bool UseRequestTokenBucket() { return mRequestTokenBucketEnabled; }
+ uint16_t RequestTokenBucketMinParallelism() { return mRequestTokenBucketMinParallelism; }
+ uint32_t RequestTokenBucketHz() { return mRequestTokenBucketHz; }
+ uint32_t RequestTokenBucketBurst() {return mRequestTokenBucketBurst; }
+
+ bool PromptTempRedirect() { return mPromptTempRedirect; }
+
+ // TCP Keepalive configuration values.
+
+ // Returns true if TCP keepalive should be enabled for short-lived conns.
+ bool TCPKeepaliveEnabledForShortLivedConns() {
+ return mTCPKeepaliveShortLivedEnabled;
+ }
+ // Return time (secs) that a connection is consider short lived (for TCP
+ // keepalive purposes). After this time, the connection is long-lived.
+ int32_t GetTCPKeepaliveShortLivedTime() {
+ return mTCPKeepaliveShortLivedTimeS;
+ }
+ // Returns time (secs) before first TCP keepalive probes should be sent;
+ // same time used between successful keepalive probes.
+ int32_t GetTCPKeepaliveShortLivedIdleTime() {
+ return mTCPKeepaliveShortLivedIdleTimeS;
+ }
+
+ // Returns true if TCP keepalive should be enabled for long-lived conns.
+ bool TCPKeepaliveEnabledForLongLivedConns() {
+ return mTCPKeepaliveLongLivedEnabled;
+ }
+ // Returns time (secs) before first TCP keepalive probes should be sent;
+ // same time used between successful keepalive probes.
+ int32_t GetTCPKeepaliveLongLivedIdleTime() {
+ return mTCPKeepaliveLongLivedIdleTimeS;
+ }
+
+ // returns the HTTP framing check level preference, as controlled with
+ // network.http.enforce-framing.http1 and network.http.enforce-framing.soft
+ FrameCheckLevel GetEnforceH1Framing() { return mEnforceH1Framing; }
+
+ nsHttpAuthCache *AuthCache(bool aPrivate) {
+ return aPrivate ? &mPrivateAuthCache : &mAuthCache;
+ }
+ nsHttpConnectionMgr *ConnMgr() { return mConnMgr; }
+
+ // cache support
+ uint32_t GenerateUniqueID() { return ++mLastUniqueID; }
+ uint32_t SessionStartTime() { return mSessionStartTime; }
+
+ //
+ // Connection management methods:
+ //
+ // - the handler only owns idle connections; it does not own active
+ // connections.
+ //
+ // - the handler keeps a count of active connections to enforce the
+ // steady-state max-connections pref.
+ //
+
+ // Called to kick-off a new transaction, by default the transaction
+ // will be put on the pending transaction queue if it cannot be
+ // initiated at this time. Callable from any thread.
+ nsresult InitiateTransaction(nsHttpTransaction *trans, int32_t priority)
+ {
+ return mConnMgr->AddTransaction(trans, priority);
+ }
+
+ // Called to change the priority of an existing transaction that has
+ // already been initiated.
+ nsresult RescheduleTransaction(nsHttpTransaction *trans, int32_t priority)
+ {
+ return mConnMgr->RescheduleTransaction(trans, priority);
+ }
+
+ // Called to cancel a transaction, which may or may not be assigned to
+ // a connection. Callable from any thread.
+ nsresult CancelTransaction(nsHttpTransaction *trans, nsresult reason)
+ {
+ return mConnMgr->CancelTransaction(trans, reason);
+ }
+
+ // Called when a connection is done processing a transaction. Callable
+ // from any thread.
+ nsresult ReclaimConnection(nsHttpConnection *conn)
+ {
+ return mConnMgr->ReclaimConnection(conn);
+ }
+
+ nsresult ProcessPendingQ(nsHttpConnectionInfo *cinfo)
+ {
+ return mConnMgr->ProcessPendingQ(cinfo);
+ }
+
+ nsresult ProcessPendingQ()
+ {
+ return mConnMgr->ProcessPendingQ();
+ }
+
+ nsresult GetSocketThreadTarget(nsIEventTarget **target)
+ {
+ return mConnMgr->GetSocketThreadTarget(target);
+ }
+
+ nsresult SpeculativeConnect(nsHttpConnectionInfo *ci,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps = 0)
+ {
+ TickleWifi(callbacks);
+ return mConnMgr->SpeculativeConnect(ci, callbacks, caps);
+ }
+
+ // Alternate Services Maps are main thread only
+ void UpdateAltServiceMapping(AltSvcMapping *map,
+ nsProxyInfo *proxyInfo,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps,
+ const NeckoOriginAttributes &originAttributes)
+ {
+ mConnMgr->UpdateAltServiceMapping(map, proxyInfo, callbacks, caps,
+ originAttributes);
+ }
+
+ already_AddRefed<AltSvcMapping> GetAltServiceMapping(const nsACString &scheme,
+ const nsACString &host,
+ int32_t port, bool pb)
+ {
+ return mConnMgr->GetAltServiceMapping(scheme, host, port, pb);
+ }
+
+ //
+ // The HTTP handler caches pointers to specific XPCOM services, and
+ // provides the following helper routines for accessing those services:
+ //
+ nsresult GetStreamConverterService(nsIStreamConverterService **);
+ nsresult GetIOService(nsIIOService** service);
+ nsICookieService * GetCookieService(); // not addrefed
+ nsISiteSecurityService * GetSSService();
+
+ // callable from socket thread only
+ uint32_t Get32BitsOfPseudoRandom();
+
+ // Called by the channel synchronously during asyncOpen
+ void OnOpeningRequest(nsIHttpChannel *chan)
+ {
+ NotifyObservers(chan, NS_HTTP_ON_OPENING_REQUEST_TOPIC);
+ }
+
+ // Called by the channel before writing a request
+ void OnModifyRequest(nsIHttpChannel *chan)
+ {
+ NotifyObservers(chan, NS_HTTP_ON_MODIFY_REQUEST_TOPIC);
+ }
+
+ // Called by the channel and cached in the loadGroup
+ void OnUserAgentRequest(nsIHttpChannel *chan)
+ {
+ NotifyObservers(chan, NS_HTTP_ON_USERAGENT_REQUEST_TOPIC);
+ }
+
+ // Called by the channel once headers are available
+ void OnExamineResponse(nsIHttpChannel *chan)
+ {
+ NotifyObservers(chan, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC);
+ }
+
+ // Called by the channel once headers have been merged with cached headers
+ void OnExamineMergedResponse(nsIHttpChannel *chan)
+ {
+ NotifyObservers(chan, NS_HTTP_ON_EXAMINE_MERGED_RESPONSE_TOPIC);
+ }
+
+ // Called by channels before a redirect happens. This notifies both the
+ // channel's and the global redirect observers.
+ nsresult AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
+ uint32_t flags);
+
+ // Called by the channel when the response is read from the cache without
+ // communicating with the server.
+ void OnExamineCachedResponse(nsIHttpChannel *chan)
+ {
+ NotifyObservers(chan, NS_HTTP_ON_EXAMINE_CACHED_RESPONSE_TOPIC);
+ }
+
+ // Generates the host:port string for use in the Host: header as well as the
+ // CONNECT line for proxies. This handles IPv6 literals correctly.
+ static nsresult GenerateHostPort(const nsCString& host, int32_t port,
+ nsACString& hostLine);
+
+ bool GetPipelineAggressive() { return mPipelineAggressive; }
+ void GetMaxPipelineObjectSize(int64_t *outVal)
+ {
+ *outVal = mMaxPipelineObjectSize;
+ }
+
+ bool GetPipelineEnabled()
+ {
+ return mCapabilities & NS_HTTP_ALLOW_PIPELINING;
+ }
+
+ bool GetPipelineRescheduleOnTimeout()
+ {
+ return mPipelineRescheduleOnTimeout;
+ }
+
+ PRIntervalTime GetPipelineRescheduleTimeout()
+ {
+ return mPipelineRescheduleTimeout;
+ }
+
+ PRIntervalTime GetPipelineTimeout() { return mPipelineReadTimeout; }
+
+ SpdyInformation *SpdyInfo() { return &mSpdyInfo; }
+ bool IsH2MandatorySuiteEnabled() { return mH2MandatorySuiteEnabled; }
+
+ // Returns true if content-signature test pref is set such that they are
+ // NOT enforced on remote newtabs.
+ bool NewTabContentSignaturesDisabled()
+ {
+ return mNewTabContentSignaturesDisabled;
+ }
+
+ // returns true in between Init and Shutdown states
+ bool Active() { return mHandlerActive; }
+
+ // When the disk cache is responding slowly its use is suppressed
+ // for 1 minute for most requests. Callable from main thread only.
+ TimeStamp GetCacheSkippedUntil() { return mCacheSkippedUntil; }
+ void SetCacheSkippedUntil(TimeStamp arg) { mCacheSkippedUntil = arg; }
+ void ClearCacheSkippedUntil() { mCacheSkippedUntil = TimeStamp(); }
+
+ nsIRequestContextService *GetRequestContextService()
+ {
+ return mRequestContextService.get();
+ }
+
+ void ShutdownConnectionManager();
+
+ bool KeepEmptyResponseHeadersAsEmtpyString() const
+ {
+ return mKeepEmptyResponseHeadersAsEmtpyString;
+ }
+
+ uint32_t DefaultHpackBuffer() const
+ {
+ return mDefaultHpackBuffer;
+ }
+
+ uint32_t MaxHttpResponseHeaderSize() const
+ {
+ return mMaxHttpResponseHeaderSize;
+ }
+
+private:
+ virtual ~nsHttpHandler();
+
+ //
+ // Useragent/prefs helper methods
+ //
+ void BuildUserAgent();
+ void InitUserAgentComponents();
+ void PrefsChanged(nsIPrefBranch *prefs, const char *pref);
+
+ nsresult SetAccept(const char *);
+ nsresult SetAcceptLanguages(const char *);
+ nsresult SetAcceptEncodings(const char *, bool mIsSecure);
+
+ nsresult InitConnectionMgr();
+
+ void NotifyObservers(nsIHttpChannel *chan, const char *event);
+
+ static void TimerCallback(nsITimer * aTimer, void * aClosure);
+private:
+
+ // cached services
+ nsMainThreadPtrHandle<nsIIOService> mIOService;
+ nsMainThreadPtrHandle<nsIStreamConverterService> mStreamConvSvc;
+ nsMainThreadPtrHandle<nsICookieService> mCookieService;
+ nsMainThreadPtrHandle<nsISiteSecurityService> mSSService;
+
+ // the authentication credentials cache
+ nsHttpAuthCache mAuthCache;
+ nsHttpAuthCache mPrivateAuthCache;
+
+ // the connection manager
+ RefPtr<nsHttpConnectionMgr> mConnMgr;
+
+ //
+ // prefs
+ //
+
+ uint8_t mHttpVersion;
+ uint8_t mProxyHttpVersion;
+ uint32_t mCapabilities;
+ uint8_t mReferrerLevel;
+ uint8_t mSpoofReferrerSource;
+ uint8_t mReferrerTrimmingPolicy;
+ uint8_t mReferrerXOriginTrimmingPolicy;
+ uint8_t mReferrerXOriginPolicy;
+
+ bool mFastFallbackToIPv4;
+ bool mProxyPipelining;
+ PRIntervalTime mIdleTimeout;
+ PRIntervalTime mSpdyTimeout;
+ PRIntervalTime mResponseTimeout;
+ bool mResponseTimeoutEnabled;
+ uint32_t mNetworkChangedTimeout; // milliseconds
+ uint16_t mMaxRequestAttempts;
+ uint16_t mMaxRequestDelay;
+ uint16_t mIdleSynTimeout;
+
+ bool mH2MandatorySuiteEnabled;
+ bool mPipeliningEnabled;
+ uint16_t mMaxConnections;
+ uint8_t mMaxPersistentConnectionsPerServer;
+ uint8_t mMaxPersistentConnectionsPerProxy;
+ uint16_t mMaxPipelinedRequests;
+ uint16_t mMaxOptimisticPipelinedRequests;
+ bool mPipelineAggressive;
+ int64_t mMaxPipelineObjectSize;
+ bool mPipelineRescheduleOnTimeout;
+ PRIntervalTime mPipelineRescheduleTimeout;
+ PRIntervalTime mPipelineReadTimeout;
+ nsCOMPtr<nsITimer> mPipelineTestTimer;
+
+ uint8_t mRedirectionLimit;
+
+ // we'll warn the user if we load an URL containing a userpass field
+ // unless its length is less than this threshold. this warning is
+ // intended to protect the user against spoofing attempts that use
+ // the userpass field of the URL to obscure the actual origin server.
+ uint8_t mPhishyUserPassLength;
+
+ uint8_t mQoSBits;
+
+ bool mPipeliningOverSSL;
+ bool mEnforceAssocReq;
+
+ nsCString mAccept;
+ nsCString mAcceptLanguages;
+ nsCString mHttpAcceptEncodings;
+ nsCString mHttpsAcceptEncodings;
+
+ nsXPIDLCString mDefaultSocketType;
+
+ // cache support
+ uint32_t mLastUniqueID;
+ uint32_t mSessionStartTime;
+
+ // useragent components
+ nsCString mLegacyAppName;
+ nsCString mLegacyAppVersion;
+ nsCString mPlatform;
+ nsCString mOscpu;
+ nsCString mMisc;
+ nsCString mProduct;
+ nsXPIDLCString mProductSub;
+ nsXPIDLCString mAppName;
+ nsXPIDLCString mAppVersion;
+ nsCString mCompatFirefox;
+ bool mCompatFirefoxEnabled;
+ nsXPIDLCString mCompatDevice;
+ nsCString mDeviceModelId;
+
+ nsCString mUserAgent;
+ nsXPIDLCString mUserAgentOverride;
+ bool mUserAgentIsDirty; // true if mUserAgent should be rebuilt
+
+
+ bool mPromptTempRedirect;
+
+ // Persistent HTTPS caching flag
+ bool mEnablePersistentHttpsCaching;
+
+ // For broadcasting tracking preference
+ bool mDoNotTrackEnabled;
+
+ // for broadcasting safe hint;
+ bool mSafeHintEnabled;
+ bool mParentalControlEnabled;
+
+ // true in between init and shutdown states
+ Atomic<bool, Relaxed> mHandlerActive;
+
+ // Whether telemetry is reported or not
+ uint32_t mTelemetryEnabled : 1;
+
+ // The value of network.allow-experiments
+ uint32_t mAllowExperiments : 1;
+
+ // The value of 'hidden' network.http.debug-observations : 1;
+ uint32_t mDebugObservations : 1;
+
+ uint32_t mEnableSpdy : 1;
+ uint32_t mHttp2Enabled : 1;
+ uint32_t mUseH2Deps : 1;
+ uint32_t mEnforceHttp2TlsProfile : 1;
+ uint32_t mCoalesceSpdy : 1;
+ uint32_t mSpdyPersistentSettings : 1;
+ uint32_t mAllowPush : 1;
+ uint32_t mEnableAltSvc : 1;
+ uint32_t mEnableAltSvcOE : 1;
+
+ // Try to use SPDY features instead of HTTP/1.1 over SSL
+ SpdyInformation mSpdyInfo;
+
+ uint32_t mSpdySendingChunkSize;
+ uint32_t mSpdySendBufferSize;
+ uint32_t mSpdyPushAllowance;
+ uint32_t mSpdyPullAllowance;
+ uint32_t mDefaultSpdyConcurrent;
+ PRIntervalTime mSpdyPingThreshold;
+ PRIntervalTime mSpdyPingTimeout;
+
+ // The maximum amount of time to wait for socket transport to be
+ // established. In milliseconds.
+ uint32_t mConnectTimeout;
+
+ // The maximum number of current global half open sockets allowable
+ // when starting a new speculative connection.
+ uint32_t mParallelSpeculativeConnectLimit;
+
+ // For Rate Pacing of HTTP/1 requests through a netwerk/base/EventTokenBucket
+ // Active requests <= *MinParallelism are not subject to the rate pacing
+ bool mRequestTokenBucketEnabled;
+ uint16_t mRequestTokenBucketMinParallelism;
+ uint32_t mRequestTokenBucketHz; // EventTokenBucket HZ
+ uint32_t mRequestTokenBucketBurst; // EventTokenBucket Burst
+
+ // Whether or not to block requests for non head js/css items (e.g. media)
+ // while those elements load.
+ bool mCriticalRequestPrioritization;
+
+ // When the disk cache is responding slowly its use is suppressed
+ // for 1 minute for most requests.
+ TimeStamp mCacheSkippedUntil;
+
+ // TCP Keepalive configuration values.
+
+ // True if TCP keepalive is enabled for short-lived conns.
+ bool mTCPKeepaliveShortLivedEnabled;
+ // Time (secs) indicating how long a conn is considered short-lived.
+ int32_t mTCPKeepaliveShortLivedTimeS;
+ // Time (secs) before first keepalive probe; between successful probes.
+ int32_t mTCPKeepaliveShortLivedIdleTimeS;
+
+ // True if TCP keepalive is enabled for long-lived conns.
+ bool mTCPKeepaliveLongLivedEnabled;
+ // Time (secs) before first keepalive probe; between successful probes.
+ int32_t mTCPKeepaliveLongLivedIdleTimeS;
+
+ // if true, generate NS_ERROR_PARTIAL_TRANSFER for h1 responses with
+ // incorrect content lengths or malformed chunked encodings
+ FrameCheckLevel mEnforceH1Framing;
+
+ nsCOMPtr<nsIRequestContextService> mRequestContextService;
+
+ // True if remote newtab content-signature disabled because of the channel.
+ bool mNewTabContentSignaturesDisabled;
+
+ // If it is set to false, headers with empty value will not appear in the
+ // header array - behavior as it used to be. If it is true: empty headers
+ // coming from the network will exits in header array as empty string.
+ // Call SetHeader with an empty value will still delete the header.
+ // (Bug 6699259)
+ bool mKeepEmptyResponseHeadersAsEmtpyString;
+
+ // The default size (in bytes) of the HPACK decompressor table.
+ uint32_t mDefaultHpackBuffer;
+
+ // The max size (in bytes) for received Http response header.
+ uint32_t mMaxHttpResponseHeaderSize;
+
+private:
+ // For Rate Pacing Certain Network Events. Only assign this pointer on
+ // socket thread.
+ void MakeNewRequestTokenBucket();
+ RefPtr<EventTokenBucket> mRequestTokenBucket;
+
+public:
+ // Socket thread only
+ nsresult SubmitPacedRequest(ATokenBucketEvent *event,
+ nsICancelable **cancel)
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (!mRequestTokenBucket) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mRequestTokenBucket->SubmitEvent(event, cancel);
+ }
+
+ // Socket thread only
+ void SetRequestTokenBucket(EventTokenBucket *aTokenBucket)
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ mRequestTokenBucket = aTokenBucket;
+ }
+
+ void StopRequestTokenBucket()
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (mRequestTokenBucket) {
+ mRequestTokenBucket->Stop();
+ mRequestTokenBucket = nullptr;
+ }
+ }
+
+private:
+ RefPtr<Tickler> mWifiTickler;
+ void TickleWifi(nsIInterfaceRequestor *cb);
+
+private:
+ nsresult SpeculativeConnectInternal(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks,
+ bool anonymous);
+
+ // UUID generator for channelIds
+ nsCOMPtr<nsIUUIDGenerator> mUUIDGen;
+
+public:
+ nsresult NewChannelId(nsID *channelId);
+};
+
+extern nsHttpHandler *gHttpHandler;
+
+//-----------------------------------------------------------------------------
+// nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the
+// HTTPS handler (even though they share the same impl).
+//-----------------------------------------------------------------------------
+
+class nsHttpsHandler : public nsIHttpProtocolHandler
+ , public nsSupportsWeakReference
+ , public nsISpeculativeConnect
+{
+ virtual ~nsHttpsHandler() { }
+public:
+ // we basically just want to override GetScheme and GetDefaultPort...
+ // all other methods should be forwarded to the nsHttpHandler instance.
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_FORWARD_NSIPROXIEDPROTOCOLHANDLER (gHttpHandler->)
+ NS_FORWARD_NSIHTTPPROTOCOLHANDLER (gHttpHandler->)
+ NS_FORWARD_NSISPECULATIVECONNECT (gHttpHandler->)
+
+ nsHttpsHandler() { }
+
+ nsresult Init();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpHandler_h__
diff --git a/netwerk/protocol/http/nsHttpHeaderArray.cpp b/netwerk/protocol/http/nsHttpHeaderArray.cpp
new file mode 100644
index 000000000..670300dea
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHeaderArray.cpp
@@ -0,0 +1,441 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 ci et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpHeaderArray.h"
+#include "nsURLHelper.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpHeaderArray <public>
+//-----------------------------------------------------------------------------
+nsresult
+nsHttpHeaderArray::SetHeader(nsHttpAtom header,
+ const nsACString &value,
+ bool merge,
+ nsHttpHeaderArray::HeaderVariety variety)
+{
+ MOZ_ASSERT((variety == eVarietyResponse) ||
+ (variety == eVarietyRequestDefault) ||
+ (variety == eVarietyRequestOverride),
+ "Net original headers can only be set using SetHeader_internal().");
+
+ nsEntry *entry = nullptr;
+ int32_t index;
+
+ index = LookupEntry(header, &entry);
+
+ // If an empty value is passed in, then delete the header entry...
+ // unless we are merging, in which case this function becomes a NOP.
+ if (value.IsEmpty()) {
+ if (!merge && entry) {
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ } else {
+ mHeaders.RemoveElementAt(index);
+ }
+ }
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!entry || variety != eVarietyRequestDefault,
+ "Cannot set default entry which overrides existing entry!");
+ if (!entry) {
+ return SetHeader_internal(header, value, variety);
+ } else if (merge && !IsSingletonHeader(header)) {
+ return MergeHeader(header, entry, value, variety);
+ } else {
+ // Replace the existing string with the new value
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ return SetHeader_internal(header, value, variety);
+ } else {
+ entry->value = value;
+ entry->variety = variety;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpHeaderArray::SetHeader_internal(nsHttpAtom header,
+ const nsACString &value,
+ nsHttpHeaderArray::HeaderVariety variety)
+{
+ nsEntry *entry = mHeaders.AppendElement();
+ if (!entry) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ entry->header = header;
+ entry->value = value;
+ entry->variety = variety;
+ return NS_OK;
+}
+
+nsresult
+nsHttpHeaderArray::SetEmptyHeader(nsHttpAtom header, HeaderVariety variety)
+{
+ MOZ_ASSERT((variety == eVarietyResponse) ||
+ (variety == eVarietyRequestDefault) ||
+ (variety == eVarietyRequestOverride),
+ "Original headers can only be set using SetHeader_internal().");
+ nsEntry *entry = nullptr;
+
+ LookupEntry(header, &entry);
+
+ if (entry &&
+ entry->variety != eVarietyResponseNetOriginalAndResponse) {
+ entry->value.Truncate();
+ return NS_OK;
+ } else if (entry) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ }
+
+ return SetHeader_internal(header, EmptyCString(), variety);
+}
+
+nsresult
+nsHttpHeaderArray::SetHeaderFromNet(nsHttpAtom header,
+ const nsACString &value,
+ bool response)
+{
+ // mHeader holds the consolidated (merged or updated) headers.
+ // mHeader for response header will keep the original heades as well.
+ nsEntry *entry = nullptr;
+
+ LookupEntry(header, &entry);
+
+ if (!entry) {
+ if (value.IsEmpty()) {
+ if (!gHttpHandler->KeepEmptyResponseHeadersAsEmtpyString() &&
+ !TrackEmptyHeader(header)) {
+ LOG(("Ignoring Empty Header: %s\n", header.get()));
+ if (response) {
+ // Set header as original but not as response header.
+ return SetHeader_internal(header, value,
+ eVarietyResponseNetOriginal);
+ }
+ return NS_OK; // ignore empty headers by default
+ }
+ }
+ HeaderVariety variety = eVarietyRequestOverride;
+ if (response) {
+ variety = eVarietyResponseNetOriginalAndResponse;
+ }
+ return SetHeader_internal(header, value, variety);
+
+ } else if (!IsSingletonHeader(header)) {
+ HeaderVariety variety = eVarietyRequestOverride;
+ if (response) {
+ variety = eVarietyResponse;
+ }
+ nsresult rv = MergeHeader(header, entry, value, variety);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (response) {
+ rv = SetHeader_internal(header, value,
+ eVarietyResponseNetOriginal);
+ }
+ return rv;
+ } else {
+ // Multiple instances of non-mergeable header received from network
+ // - ignore if same value
+ if (!entry->value.Equals(value)) {
+ if (IsSuspectDuplicateHeader(header)) {
+ // reply may be corrupt/hacked (ex: CLRF injection attacks)
+ return NS_ERROR_CORRUPTED_CONTENT;
+ } // else silently drop value: keep value from 1st header seen
+ LOG(("Header %s silently dropped as non mergeable header\n",
+ header.get()));
+
+ }
+ if (response) {
+ return SetHeader_internal(header, value,
+ eVarietyResponseNetOriginal);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpHeaderArray::SetResponseHeaderFromCache(nsHttpAtom header,
+ const nsACString &value,
+ nsHttpHeaderArray::HeaderVariety variety)
+{
+ MOZ_ASSERT((variety == eVarietyResponse) ||
+ (variety == eVarietyResponseNetOriginal),
+ "Headers from cache can only be eVarietyResponse and "
+ "eVarietyResponseNetOriginal");
+
+ if (variety == eVarietyResponseNetOriginal) {
+ return SetHeader_internal(header, value,
+ eVarietyResponseNetOriginal);
+ } else {
+ nsTArray<nsEntry>::index_type index = 0;
+ do {
+ index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
+ if (index != mHeaders.NoIndex) {
+ nsEntry &entry = mHeaders[index];
+ if (value.Equals(entry.value)) {
+ MOZ_ASSERT((entry.variety == eVarietyResponseNetOriginal) ||
+ (entry.variety == eVarietyResponseNetOriginalAndResponse),
+ "This array must contain only eVarietyResponseNetOriginal"
+ " and eVarietyResponseNetOriginalAndRespons headers!");
+ entry.variety = eVarietyResponseNetOriginalAndResponse;
+ return NS_OK;
+ }
+ index++;
+ }
+ } while (index != mHeaders.NoIndex);
+ // If we are here, we have not found an entry so add a new one.
+ return SetHeader_internal(header, value, eVarietyResponse);
+ }
+}
+
+void
+nsHttpHeaderArray::ClearHeader(nsHttpAtom header)
+{
+ nsEntry *entry = nullptr;
+ int32_t index = LookupEntry(header, &entry);
+ if (entry) {
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ entry->variety = eVarietyResponseNetOriginal;
+ } else {
+ mHeaders.RemoveElementAt(index);
+ }
+ }
+}
+
+const char *
+nsHttpHeaderArray::PeekHeader(nsHttpAtom header) const
+{
+ const nsEntry *entry = nullptr;
+ LookupEntry(header, &entry);
+ return entry ? entry->value.get() : nullptr;
+}
+
+nsresult
+nsHttpHeaderArray::GetHeader(nsHttpAtom header, nsACString &result) const
+{
+ const nsEntry *entry = nullptr;
+ LookupEntry(header, &entry);
+ if (!entry)
+ return NS_ERROR_NOT_AVAILABLE;
+ result = entry->value;
+ return NS_OK;
+}
+
+nsresult
+nsHttpHeaderArray::GetOriginalHeader(nsHttpAtom aHeader,
+ nsIHttpHeaderVisitor *aVisitor)
+{
+ NS_ENSURE_ARG_POINTER(aVisitor);
+ uint32_t index = 0;
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ while (true) {
+ index = mHeaders.IndexOf(aHeader, index, nsEntry::MatchHeader());
+ if (index != UINT32_MAX) {
+ const nsEntry &entry = mHeaders[index];
+
+ MOZ_ASSERT((entry.variety == eVarietyResponseNetOriginalAndResponse) ||
+ (entry.variety == eVarietyResponseNetOriginal) ||
+ (entry.variety == eVarietyResponse),
+ "This must be a response header.");
+ index++;
+ if (entry.variety == eVarietyResponse) {
+ continue;
+ }
+ rv = NS_OK;
+ if (NS_FAILED(aVisitor->VisitHeader(nsDependentCString(entry.header),
+ entry.value))) {
+ break;
+ }
+ } else {
+ // if there is no such a header, it will return
+ // NS_ERROR_NOT_AVAILABLE or NS_OK otherwise.
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+bool
+nsHttpHeaderArray::HasHeader(nsHttpAtom header) const
+{
+ const nsEntry *entry = nullptr;
+ LookupEntry(header, &entry);
+ return entry;
+}
+
+nsresult
+nsHttpHeaderArray::VisitHeaders(nsIHttpHeaderVisitor *visitor, nsHttpHeaderArray::VisitorFilter filter)
+{
+ NS_ENSURE_ARG_POINTER(visitor);
+ nsresult rv;
+
+ uint32_t i, count = mHeaders.Length();
+ for (i = 0; i < count; ++i) {
+ const nsEntry &entry = mHeaders[i];
+ if (filter == eFilterSkipDefault && entry.variety == eVarietyRequestDefault) {
+ continue;
+ } else if (filter == eFilterResponse && entry.variety == eVarietyResponseNetOriginal) {
+ continue;
+ } else if (filter == eFilterResponseOriginal && entry.variety == eVarietyResponse) {
+ continue;
+ }
+ rv = visitor->VisitHeader(
+ nsDependentCString(entry.header), entry.value);
+ if NS_FAILED(rv) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+/*static*/ nsresult
+nsHttpHeaderArray::ParseHeaderLine(const nsACString& line,
+ nsHttpAtom *hdr,
+ nsACString *val)
+{
+ //
+ // BNF from section 4.2 of RFC 2616:
+ //
+ // message-header = field-name ":" [ field-value ]
+ // field-name = token
+ // field-value = *( field-content | LWS )
+ // field-content = <the OCTETs making up the field-value
+ // and consisting of either *TEXT or combinations
+ // of token, separators, and quoted-string>
+ //
+
+ // We skip over mal-formed headers in the hope that we'll still be able to
+ // do something useful with the response.
+ int32_t split = line.FindChar(':');
+
+ if (split == kNotFound) {
+ LOG(("malformed header [%s]: no colon\n",
+ PromiseFlatCString(line).get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ const nsACString& sub = Substring(line, 0, split);
+ const nsACString& sub2 = Substring(
+ line, split + 1, line.Length() - split - 1);
+
+ // make sure we have a valid token for the field-name
+ if (!nsHttp::IsValidToken(sub)) {
+ LOG(("malformed header [%s]: field-name not a token\n",
+ PromiseFlatCString(line).get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(sub);
+ if (!atom) {
+ LOG(("failed to resolve atom [%s]\n", PromiseFlatCString(line).get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ // skip over whitespace
+ char *p = net_FindCharNotInSet(
+ sub2.BeginReading(), sub2.EndReading(), HTTP_LWS);
+
+ // trim trailing whitespace - bug 86608
+ char *p2 = net_RFindCharNotInSet(p, sub2.EndReading(), HTTP_LWS);
+
+ // assign return values
+ if (hdr) *hdr = atom;
+ if (val) val->Assign(p, p2 - p + 1);
+
+ return NS_OK;
+}
+
+void
+nsHttpHeaderArray::Flatten(nsACString &buf, bool pruneProxyHeaders,
+ bool pruneTransients)
+{
+ uint32_t i, count = mHeaders.Length();
+ for (i = 0; i < count; ++i) {
+ const nsEntry &entry = mHeaders[i];
+ // Skip original header.
+ if (entry.variety == eVarietyResponseNetOriginal) {
+ continue;
+ }
+ // prune proxy headers if requested
+ if (pruneProxyHeaders &&
+ ((entry.header == nsHttp::Proxy_Authorization) ||
+ (entry.header == nsHttp::Proxy_Connection))) {
+ continue;
+ }
+ if (pruneTransients &&
+ (entry.value.IsEmpty() ||
+ entry.header == nsHttp::Connection ||
+ entry.header == nsHttp::Proxy_Connection ||
+ entry.header == nsHttp::Keep_Alive ||
+ entry.header == nsHttp::WWW_Authenticate ||
+ entry.header == nsHttp::Proxy_Authenticate ||
+ entry.header == nsHttp::Trailer ||
+ entry.header == nsHttp::Transfer_Encoding ||
+ entry.header == nsHttp::Upgrade ||
+ // XXX this will cause problems when we start honoring
+ // Cache-Control: no-cache="set-cookie", what to do?
+ entry.header == nsHttp::Set_Cookie)) {
+ continue;
+ }
+
+ buf.Append(entry.header);
+ buf.AppendLiteral(": ");
+ buf.Append(entry.value);
+ buf.AppendLiteral("\r\n");
+ }
+}
+
+void
+nsHttpHeaderArray::FlattenOriginalHeader(nsACString &buf)
+{
+ uint32_t i, count = mHeaders.Length();
+ for (i = 0; i < count; ++i) {
+ const nsEntry &entry = mHeaders[i];
+ // Skip changed header.
+ if (entry.variety == eVarietyResponse) {
+ continue;
+ }
+
+ buf.Append(entry.header);
+ buf.AppendLiteral(": ");
+ buf.Append(entry.value);
+ buf.AppendLiteral("\r\n");
+ }
+}
+
+const char *
+nsHttpHeaderArray::PeekHeaderAt(uint32_t index, nsHttpAtom &header) const
+{
+ const nsEntry &entry = mHeaders[index];
+
+ header = entry.header;
+ return entry.value.get();
+}
+
+void
+nsHttpHeaderArray::Clear()
+{
+ mHeaders.Clear();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpHeaderArray.h b/netwerk/protocol/http/nsHttpHeaderArray.h
new file mode 100644
index 000000000..91b91f04a
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHeaderArray.h
@@ -0,0 +1,287 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpHeaderArray_h__
+#define nsHttpHeaderArray_h__
+
+#include "nsHttp.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+class nsIHttpHeaderVisitor;
+
+// This needs to be forward declared here so we can include only this header
+// without also including PHttpChannelParams.h
+namespace IPC {
+ template <typename> struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla { namespace net {
+
+class nsHttpHeaderArray
+{
+public:
+ const char *PeekHeader(nsHttpAtom header) const;
+
+ // For nsHttpResponseHead nsHttpHeaderArray will keep track of the original
+ // headers as they come from the network and the parse headers used in
+ // firefox.
+ // If the original and the firefox header are the same, we will keep just
+ // one copy and marked it as eVarietyResponseNetOriginalAndResponse.
+ // If firefox header representation changes a header coming from the
+ // network (e.g. merged it) or a eVarietyResponseNetOriginalAndResponse
+ // header has been changed by SetHeader method, we will keep the original
+ // header as eVarietyResponseNetOriginal and make a copy for the new header
+ // and mark it as eVarietyResponse.
+ enum HeaderVariety
+ {
+ eVarietyUnknown,
+ // Used only for request header.
+ eVarietyRequestOverride,
+ eVarietyRequestDefault,
+ // Used only for response header.
+ eVarietyResponseNetOriginalAndResponse,
+ eVarietyResponseNetOriginal,
+ eVarietyResponse
+ };
+
+ // Used by internal setters: to set header from network use SetHeaderFromNet
+ nsresult SetHeader(nsHttpAtom header, const nsACString &value,
+ bool merge, HeaderVariety variety);
+
+ // Used by internal setters to set an empty header
+ nsresult SetEmptyHeader(nsHttpAtom header, HeaderVariety variety);
+
+ // Merges supported headers. For other duplicate values, determines if error
+ // needs to be thrown or 1st value kept.
+ // For the response header we keep the original headers as well.
+ nsresult SetHeaderFromNet(nsHttpAtom header, const nsACString &value,
+ bool response);
+
+ nsresult SetResponseHeaderFromCache(nsHttpAtom header, const nsACString &value,
+ HeaderVariety variety);
+
+ nsresult GetHeader(nsHttpAtom header, nsACString &value) const;
+ nsresult GetOriginalHeader(nsHttpAtom aHeader,
+ nsIHttpHeaderVisitor *aVisitor);
+ void ClearHeader(nsHttpAtom h);
+
+ // Find the location of the given header value, or null if none exists.
+ const char *FindHeaderValue(nsHttpAtom header, const char *value) const
+ {
+ return nsHttp::FindToken(PeekHeader(header), value,
+ HTTP_HEADER_VALUE_SEPS);
+ }
+
+ // Determine if the given header value exists.
+ bool HasHeaderValue(nsHttpAtom header, const char *value) const
+ {
+ return FindHeaderValue(header, value) != nullptr;
+ }
+
+ bool HasHeader(nsHttpAtom header) const;
+
+ enum VisitorFilter
+ {
+ eFilterAll,
+ eFilterSkipDefault,
+ eFilterResponse,
+ eFilterResponseOriginal
+ };
+
+ nsresult VisitHeaders(nsIHttpHeaderVisitor *visitor, VisitorFilter filter = eFilterAll);
+
+ // parse a header line, return the header atom and a pointer to the
+ // header value (the substring of the header line -- do not free).
+ static nsresult ParseHeaderLine(const nsACString& line,
+ nsHttpAtom *header=nullptr,
+ nsACString* value=nullptr);
+
+ void Flatten(nsACString &, bool pruneProxyHeaders, bool pruneTransients);
+ void FlattenOriginalHeader(nsACString &);
+
+ uint32_t Count() const { return mHeaders.Length(); }
+
+ const char *PeekHeaderAt(uint32_t i, nsHttpAtom &header) const;
+
+ void Clear();
+
+ // Must be copy-constructable and assignable
+ struct nsEntry
+ {
+ nsHttpAtom header;
+ nsCString value;
+ HeaderVariety variety = eVarietyUnknown;
+
+ struct MatchHeader {
+ bool Equals(const nsEntry &aEntry, const nsHttpAtom &aHeader) const {
+ return aEntry.header == aHeader;
+ }
+ };
+
+ bool operator==(const nsEntry& aOther) const
+ {
+ return header == aOther.header && value == aOther.value;
+ }
+ };
+
+ bool operator==(const nsHttpHeaderArray& aOther) const
+ {
+ return mHeaders == aOther.mHeaders;
+ }
+
+private:
+ // LookupEntry function will never return eVarietyResponseNetOriginal.
+ // It will ignore original headers from the network.
+ int32_t LookupEntry(nsHttpAtom header, const nsEntry **) const;
+ int32_t LookupEntry(nsHttpAtom header, nsEntry **);
+ nsresult MergeHeader(nsHttpAtom header, nsEntry *entry,
+ const nsACString &value, HeaderVariety variety);
+ nsresult SetHeader_internal(nsHttpAtom header, const nsACString &value,
+ HeaderVariety variety);
+
+ // Header cannot be merged: only one value possible
+ bool IsSingletonHeader(nsHttpAtom header);
+ // For some headers we want to track empty values to prevent them being
+ // combined with non-empty ones as a CRLF attack vector
+ bool TrackEmptyHeader(nsHttpAtom header);
+
+ // Subset of singleton headers: should never see multiple, different
+ // instances of these, else something fishy may be going on (like CLRF
+ // injection)
+ bool IsSuspectDuplicateHeader(nsHttpAtom header);
+
+ // All members must be copy-constructable and assignable
+ nsTArray<nsEntry> mHeaders;
+
+ friend struct IPC::ParamTraits<nsHttpHeaderArray>;
+ friend class nsHttpRequestHead;
+};
+
+
+//-----------------------------------------------------------------------------
+// nsHttpHeaderArray <private>: inline functions
+//-----------------------------------------------------------------------------
+
+inline int32_t
+nsHttpHeaderArray::LookupEntry(nsHttpAtom header, const nsEntry **entry) const
+{
+ uint32_t index = 0;
+ while (index != UINT32_MAX) {
+ index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
+ if (index != UINT32_MAX) {
+ if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
+ *entry = &mHeaders[index];
+ return index;
+ }
+ index++;
+ }
+ }
+
+ return index;
+}
+
+inline int32_t
+nsHttpHeaderArray::LookupEntry(nsHttpAtom header, nsEntry **entry)
+{
+ uint32_t index = 0;
+ while (index != UINT32_MAX) {
+ index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
+ if (index != UINT32_MAX) {
+ if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
+ *entry = &mHeaders[index];
+ return index;
+ }
+ index++;
+ }
+ }
+ return index;
+}
+
+inline bool
+nsHttpHeaderArray::IsSingletonHeader(nsHttpAtom header)
+{
+ return header == nsHttp::Content_Type ||
+ header == nsHttp::Content_Disposition ||
+ header == nsHttp::Content_Length ||
+ header == nsHttp::User_Agent ||
+ header == nsHttp::Referer ||
+ header == nsHttp::Host ||
+ header == nsHttp::Authorization ||
+ header == nsHttp::Proxy_Authorization ||
+ header == nsHttp::If_Modified_Since ||
+ header == nsHttp::If_Unmodified_Since ||
+ header == nsHttp::From ||
+ header == nsHttp::Location ||
+ header == nsHttp::Max_Forwards;
+}
+
+inline bool
+nsHttpHeaderArray::TrackEmptyHeader(nsHttpAtom header)
+{
+ return header == nsHttp::Content_Length ||
+ header == nsHttp::Location ||
+ header == nsHttp::Access_Control_Allow_Origin;
+}
+
+inline nsresult
+nsHttpHeaderArray::MergeHeader(nsHttpAtom header,
+ nsEntry *entry,
+ const nsACString &value,
+ nsHttpHeaderArray::HeaderVariety variety)
+{
+ if (value.IsEmpty())
+ return NS_OK; // merge of empty header = no-op
+
+ nsCString newValue = entry->value;
+ if (!newValue.IsEmpty()) {
+ // Append the new value to the existing value
+ if (header == nsHttp::Set_Cookie ||
+ header == nsHttp::WWW_Authenticate ||
+ header == nsHttp::Proxy_Authenticate)
+ {
+ // Special case these headers and use a newline delimiter to
+ // delimit the values from one another as commas may appear
+ // in the values of these headers contrary to what the spec says.
+ newValue.Append('\n');
+ } else {
+ // Delimit each value from the others using a comma (per HTTP spec)
+ newValue.AppendLiteral(", ");
+ }
+ }
+
+ newValue.Append(value);
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ nsresult rv = SetHeader_internal(header, newValue, eVarietyResponse);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ entry->value = newValue;
+ entry->variety = variety;
+ }
+ return NS_OK;
+}
+
+inline bool
+nsHttpHeaderArray::IsSuspectDuplicateHeader(nsHttpAtom header)
+{
+ bool retval = header == nsHttp::Content_Length ||
+ header == nsHttp::Content_Disposition ||
+ header == nsHttp::Location;
+
+ MOZ_ASSERT(!retval || IsSingletonHeader(header),
+ "Only non-mergeable headers should be in this list\n");
+
+ return retval;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/http/nsHttpNTLMAuth.cpp b/netwerk/protocol/http/nsHttpNTLMAuth.cpp
new file mode 100644
index 000000000..aa5b1f8f7
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpNTLMAuth.cpp
@@ -0,0 +1,533 @@
+/* vim:set ts=4 sw=4 sts=4 et ci: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpNTLMAuth.h"
+#include "nsIAuthModule.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "plbase64.h"
+#include "plstr.h"
+#include "prnetdb.h"
+
+//-----------------------------------------------------------------------------
+
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIURI.h"
+#ifdef XP_WIN
+#include "nsIChannel.h"
+#include "nsIX509Cert.h"
+#include "nsISSLStatus.h"
+#include "nsISSLStatusProvider.h"
+#endif
+#include "mozilla/Attributes.h"
+#include "mozilla/Base64.h"
+#include "mozilla/CheckedInt.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+
+namespace mozilla {
+namespace net {
+
+static const char kAllowProxies[] = "network.automatic-ntlm-auth.allow-proxies";
+static const char kAllowNonFqdn[] = "network.automatic-ntlm-auth.allow-non-fqdn";
+static const char kTrustedURIs[] = "network.automatic-ntlm-auth.trusted-uris";
+static const char kForceGeneric[] = "network.auth.force-generic-ntlm";
+static const char kSSOinPBmode[] = "network.auth.private-browsing-sso";
+
+// XXX MatchesBaseURI and TestPref are duplicated in nsHttpNegotiateAuth.cpp,
+// but since that file lives in a separate library we cannot directly share it.
+// bug 236865 addresses this problem.
+
+static bool
+MatchesBaseURI(const nsCSubstring &matchScheme,
+ const nsCSubstring &matchHost,
+ int32_t matchPort,
+ const char *baseStart,
+ const char *baseEnd)
+{
+ // check if scheme://host:port matches baseURI
+
+ // parse the base URI
+ const char *hostStart, *schemeEnd = strstr(baseStart, "://");
+ if (schemeEnd) {
+ // the given scheme must match the parsed scheme exactly
+ if (!matchScheme.Equals(Substring(baseStart, schemeEnd)))
+ return false;
+ hostStart = schemeEnd + 3;
+ }
+ else
+ hostStart = baseStart;
+
+ // XXX this does not work for IPv6-literals
+ const char *hostEnd = strchr(hostStart, ':');
+ if (hostEnd && hostEnd < baseEnd) {
+ // the given port must match the parsed port exactly
+ int port = atoi(hostEnd + 1);
+ if (matchPort != (int32_t) port)
+ return false;
+ }
+ else
+ hostEnd = baseEnd;
+
+
+ // if we didn't parse out a host, then assume we got a match.
+ if (hostStart == hostEnd)
+ return true;
+
+ uint32_t hostLen = hostEnd - hostStart;
+
+ // matchHost must either equal host or be a subdomain of host
+ if (matchHost.Length() < hostLen)
+ return false;
+
+ const char *end = matchHost.EndReading();
+ if (PL_strncasecmp(end - hostLen, hostStart, hostLen) == 0) {
+ // if matchHost ends with host from the base URI, then make sure it is
+ // either an exact match, or prefixed with a dot. we don't want
+ // "foobar.com" to match "bar.com"
+ if (matchHost.Length() == hostLen ||
+ *(end - hostLen) == '.' ||
+ *(end - hostLen - 1) == '.')
+ return true;
+ }
+
+ return false;
+}
+
+static bool
+IsNonFqdn(nsIURI *uri)
+{
+ nsAutoCString host;
+ PRNetAddr addr;
+
+ if (NS_FAILED(uri->GetAsciiHost(host)))
+ return false;
+
+ // return true if host does not contain a dot and is not an ip address
+ return !host.IsEmpty() && !host.Contains('.') &&
+ PR_StringToNetAddr(host.BeginReading(), &addr) != PR_SUCCESS;
+}
+
+static bool
+TestPref(nsIURI *uri, const char *pref)
+{
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs)
+ return false;
+
+ nsAutoCString scheme, host;
+ int32_t port;
+
+ if (NS_FAILED(uri->GetScheme(scheme)))
+ return false;
+ if (NS_FAILED(uri->GetAsciiHost(host)))
+ return false;
+ if (NS_FAILED(uri->GetPort(&port)))
+ return false;
+
+ char *hostList;
+ if (NS_FAILED(prefs->GetCharPref(pref, &hostList)) || !hostList)
+ return false;
+
+ // pseudo-BNF
+ // ----------
+ //
+ // url-list base-url ( base-url "," LWS )*
+ // base-url ( scheme-part | host-part | scheme-part host-part )
+ // scheme-part scheme "://"
+ // host-part host [":" port]
+ //
+ // for example:
+ // "https://, http://office.foo.com"
+ //
+
+ char *start = hostList, *end;
+ for (;;) {
+ // skip past any whitespace
+ while (*start == ' ' || *start == '\t')
+ ++start;
+ end = strchr(start, ',');
+ if (!end)
+ end = start + strlen(start);
+ if (start == end)
+ break;
+ if (MatchesBaseURI(scheme, host, port, start, end))
+ return true;
+ if (*end == '\0')
+ break;
+ start = end + 1;
+ }
+
+ free(hostList);
+ return false;
+}
+
+// Check to see if we should use our generic (internal) NTLM auth module.
+static bool
+ForceGenericNTLM()
+{
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs)
+ return false;
+ bool flag = false;
+
+ if (NS_FAILED(prefs->GetBoolPref(kForceGeneric, &flag)))
+ flag = false;
+
+ LOG(("Force use of generic ntlm auth module: %d\n", flag));
+ return flag;
+}
+
+// Check to see if we should use default credentials for this host or proxy.
+static bool
+CanUseDefaultCredentials(nsIHttpAuthenticableChannel *channel,
+ bool isProxyAuth)
+{
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) {
+ return false;
+ }
+
+ // Proxy should go all the time, it's not considered a privacy leak
+ // to send default credentials to a proxy.
+ if (isProxyAuth) {
+ bool val;
+ if (NS_FAILED(prefs->GetBoolPref(kAllowProxies, &val)))
+ val = false;
+ LOG(("Default credentials allowed for proxy: %d\n", val));
+ return val;
+ }
+
+ // Prevent using default credentials for authentication when we are in the
+ // private browsing mode (but not in "never remember history" mode) and when
+ // not explicitely allowed. Otherwise, it would cause a privacy data leak.
+ nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(channel);
+ MOZ_ASSERT(bareChannel);
+
+ if (NS_UsePrivateBrowsing(bareChannel)) {
+ bool ssoInPb;
+ if (NS_SUCCEEDED(prefs->GetBoolPref(kSSOinPBmode, &ssoInPb)) &&
+ ssoInPb) {
+ return true;
+ }
+
+ bool dontRememberHistory;
+ if (NS_SUCCEEDED(prefs->GetBoolPref("browser.privatebrowsing.autostart",
+ &dontRememberHistory)) &&
+ !dontRememberHistory) {
+ return false;
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+
+ bool allowNonFqdn;
+ if (NS_FAILED(prefs->GetBoolPref(kAllowNonFqdn, &allowNonFqdn)))
+ allowNonFqdn = false;
+ if (allowNonFqdn && uri && IsNonFqdn(uri)) {
+ LOG(("Host is non-fqdn, default credentials are allowed\n"));
+ return true;
+ }
+
+ bool isTrustedHost = (uri && TestPref(uri, kTrustedURIs));
+ LOG(("Default credentials allowed for host: %d\n", isTrustedHost));
+ return isTrustedHost;
+}
+
+// Dummy class for session state object. This class doesn't hold any data.
+// Instead we use its existence as a flag. See ChallengeReceived.
+class nsNTLMSessionState final : public nsISupports
+{
+ ~nsNTLMSessionState() {}
+public:
+ NS_DECL_ISUPPORTS
+};
+NS_IMPL_ISUPPORTS0(nsNTLMSessionState)
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpNTLMAuth, nsIHttpAuthenticator)
+
+NS_IMETHODIMP
+nsHttpNTLMAuth::ChallengeReceived(nsIHttpAuthenticableChannel *channel,
+ const char *challenge,
+ bool isProxyAuth,
+ nsISupports **sessionState,
+ nsISupports **continuationState,
+ bool *identityInvalid)
+{
+ LOG(("nsHttpNTLMAuth::ChallengeReceived [ss=%p cs=%p]\n",
+ *sessionState, *continuationState));
+
+ // Use the native NTLM if available
+ mUseNative = true;
+
+ // NOTE: we don't define any session state, but we do use the pointer.
+
+ *identityInvalid = false;
+
+ // Start a new auth sequence if the challenge is exactly "NTLM".
+ // If native NTLM auth apis are available and enabled through prefs,
+ // try to use them.
+ if (PL_strcasecmp(challenge, "NTLM") == 0) {
+ nsCOMPtr<nsISupports> module;
+
+ // Check to see if we should default to our generic NTLM auth module
+ // through UseGenericNTLM. (We use native auth by default if the
+ // system provides it.) If *sessionState is non-null, we failed to
+ // instantiate a native NTLM module the last time, so skip trying again.
+ bool forceGeneric = ForceGenericNTLM();
+ if (!forceGeneric && !*sessionState) {
+ // Check for approved default credentials hosts and proxies. If
+ // *continuationState is non-null, the last authentication attempt
+ // failed so skip default credential use.
+ if (!*continuationState && CanUseDefaultCredentials(channel, isProxyAuth)) {
+ // Try logging in with the user's default credentials. If
+ // successful, |identityInvalid| is false, which will trigger
+ // a default credentials attempt once we return.
+ module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm");
+ }
+#ifdef XP_WIN
+ else {
+ // Try to use native NTLM and prompt the user for their domain,
+ // username, and password. (only supported by windows nsAuthSSPI module.)
+ // Note, for servers that use LMv1 a weak hash of the user's password
+ // will be sent. We rely on windows internal apis to decide whether
+ // we should support this older, less secure version of the protocol.
+ module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sys-ntlm");
+ *identityInvalid = true;
+ }
+#endif // XP_WIN
+ if (!module)
+ LOG(("Native sys-ntlm auth module not found.\n"));
+ }
+
+#ifdef XP_WIN
+ // On windows, never fall back unless the user has specifically requested so.
+ if (!forceGeneric && !module)
+ return NS_ERROR_UNEXPECTED;
+#endif
+
+ // If no native support was available. Fall back on our internal NTLM implementation.
+ if (!module) {
+ if (!*sessionState) {
+ // Remember the fact that we cannot use the "sys-ntlm" module,
+ // so we don't ever bother trying again for this auth domain.
+ *sessionState = new nsNTLMSessionState();
+ if (!*sessionState)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*sessionState);
+ }
+
+ // Use our internal NTLM implementation. Note, this is less secure,
+ // see bug 520607 for details.
+ LOG(("Trying to fall back on internal ntlm auth.\n"));
+ module = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "ntlm");
+
+ mUseNative = false;
+
+ // Prompt user for domain, username, and password.
+ *identityInvalid = true;
+ }
+
+ // If this fails, then it means that we cannot do NTLM auth.
+ if (!module) {
+ LOG(("No ntlm auth modules available.\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // A non-null continuation state implies that we failed to authenticate.
+ // Blow away the old authentication state, and use the new one.
+ module.swap(*continuationState);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpNTLMAuth::GenerateCredentialsAsync(nsIHttpAuthenticableChannel *authChannel,
+ nsIHttpAuthenticatorCallback* aCallback,
+ const char *challenge,
+ bool isProxyAuth,
+ const char16_t *domain,
+ const char16_t *username,
+ const char16_t *password,
+ nsISupports *sessionState,
+ nsISupports *continuationState,
+ nsICancelable **aCancellable)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpNTLMAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel,
+ const char *challenge,
+ bool isProxyAuth,
+ const char16_t *domain,
+ const char16_t *user,
+ const char16_t *pass,
+ nsISupports **sessionState,
+ nsISupports **continuationState,
+ uint32_t *aFlags,
+ char **creds)
+
+{
+ LOG(("nsHttpNTLMAuth::GenerateCredentials\n"));
+
+ *creds = nullptr;
+ *aFlags = 0;
+
+ // if user or password is empty, ChallengeReceived returned
+ // identityInvalid = false, that means we are using default user
+ // credentials; see nsAuthSSPI::Init method for explanation of this
+ // condition
+ if (!user || !pass)
+ *aFlags = USING_INTERNAL_IDENTITY;
+
+ nsresult rv;
+ nsCOMPtr<nsIAuthModule> module = do_QueryInterface(*continuationState, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ void *inBuf, *outBuf;
+ uint32_t inBufLen, outBufLen;
+
+ // initial challenge
+ if (PL_strcasecmp(challenge, "NTLM") == 0) {
+ // NTLM service name format is 'HTTP@host' for both http and https
+ nsCOMPtr<nsIURI> uri;
+ rv = authChannel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv))
+ return rv;
+ nsAutoCString serviceName, host;
+ rv = uri->GetAsciiHost(host);
+ if (NS_FAILED(rv))
+ return rv;
+ serviceName.AppendLiteral("HTTP@");
+ serviceName.Append(host);
+ // initialize auth module
+ uint32_t reqFlags = nsIAuthModule::REQ_DEFAULT;
+ if (isProxyAuth)
+ reqFlags |= nsIAuthModule::REQ_PROXY_AUTH;
+
+ rv = module->Init(serviceName.get(), reqFlags, domain, user, pass);
+ if (NS_FAILED(rv))
+ return rv;
+
+// This update enables updated Windows machines (Win7 or patched previous
+// versions) and Linux machines running Samba (updated for Channel
+// Binding), to perform Channel Binding when authenticating using NTLMv2
+// and an outer secure channel.
+//
+// Currently only implemented for Windows, linux support will be landing in
+// a separate patch, update this #ifdef accordingly then.
+#if defined (XP_WIN) /* || defined (LINUX) */
+ // We should retrieve the server certificate and compute the CBT,
+ // but only when we are using the native NTLM implementation and
+ // not the internal one.
+ // It is a valid case not having the security info object. This
+ // occures when we connect an https site through an ntlm proxy.
+ // After the ssl tunnel has been created, we get here the second
+ // time and now generate the CBT from now valid security info.
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(authChannel, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsISupports> security;
+ rv = channel->GetSecurityInfo(getter_AddRefs(security));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsISSLStatusProvider> statusProvider =
+ do_QueryInterface(security);
+
+ if (mUseNative && statusProvider) {
+ nsCOMPtr<nsISSLStatus> status;
+ rv = statusProvider->GetSSLStatus(getter_AddRefs(status));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIX509Cert> cert;
+ rv = status->GetServerCert(getter_AddRefs(cert));
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t length;
+ uint8_t* certArray;
+ cert->GetRawDER(&length, &certArray);
+
+ // If there is a server certificate, we pass it along the
+ // first time we call GetNextToken().
+ inBufLen = length;
+ inBuf = certArray;
+ } else {
+ // If there is no server certificate, we don't pass anything.
+ inBufLen = 0;
+ inBuf = nullptr;
+ }
+#else // Extended protection update is just for Linux and Windows machines.
+ inBufLen = 0;
+ inBuf = nullptr;
+#endif
+ }
+ else {
+ // decode challenge; skip past "NTLM " to the start of the base64
+ // encoded data.
+ int len = strlen(challenge);
+ if (len < 6)
+ return NS_ERROR_UNEXPECTED; // bogus challenge
+ challenge += 5;
+ len -= 5;
+
+ // strip off any padding (see bug 230351)
+ while (challenge[len - 1] == '=')
+ len--;
+
+ // decode into the input secbuffer
+ rv = Base64Decode(challenge, len, (char**)&inBuf, &inBufLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ rv = module->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen);
+ if (NS_SUCCEEDED(rv)) {
+ // base64 encode data in output buffer and prepend "NTLM "
+ CheckedUint32 credsLen = ((CheckedUint32(outBufLen) + 2) / 3) * 4;
+ credsLen += 5; // "NTLM "
+ credsLen += 1; // null terminate
+
+ if (!credsLen.isValid()) {
+ rv = NS_ERROR_FAILURE;
+ } else {
+ *creds = (char *) moz_xmalloc(credsLen.value());
+ memcpy(*creds, "NTLM ", 5);
+ PL_Base64Encode((char *) outBuf, outBufLen, *creds + 5);
+ (*creds)[credsLen.value() - 1] = '\0'; // null terminate
+ }
+
+ // OK, we are done with |outBuf|
+ free(outBuf);
+ }
+
+ if (inBuf)
+ free(inBuf);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpNTLMAuth::GetAuthFlags(uint32_t *flags)
+{
+ *flags = CONNECTION_BASED | IDENTITY_INCLUDES_DOMAIN | IDENTITY_ENCRYPTED;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpNTLMAuth.h b/netwerk/protocol/http/nsHttpNTLMAuth.h
new file mode 100644
index 000000000..9bc1ef6f2
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpNTLMAuth.h
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpNTLMAuth_h__
+#define nsHttpNTLMAuth_h__
+
+#include "nsIHttpAuthenticator.h"
+
+namespace mozilla { namespace net {
+
+class nsHttpNTLMAuth : public nsIHttpAuthenticator
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHENTICATOR
+
+ nsHttpNTLMAuth() {}
+
+private:
+ virtual ~nsHttpNTLMAuth() {}
+
+ // This flag indicates whether we are using the native NTLM implementation
+ // or the internal one.
+ bool mUseNative;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsHttpNTLMAuth_h__
diff --git a/netwerk/protocol/http/nsHttpPipeline.cpp b/netwerk/protocol/http/nsHttpPipeline.cpp
new file mode 100644
index 000000000..293de8e39
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpPipeline.cpp
@@ -0,0 +1,911 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpPipeline.h"
+#include "nsHttpHandler.h"
+#include "nsIOService.h"
+#include "nsISocketTransport.h"
+#include "nsIPipe.h"
+#include "nsCOMPtr.h"
+#include "nsSocketTransportService2.h"
+#include <algorithm>
+
+#ifdef DEBUG
+#include "prthread.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpPushBackWriter
+//-----------------------------------------------------------------------------
+
+class nsHttpPushBackWriter : public nsAHttpSegmentWriter
+{
+public:
+ nsHttpPushBackWriter(const char *buf, uint32_t bufLen)
+ : mBuf(buf)
+ , mBufLen(bufLen)
+ { }
+ virtual ~nsHttpPushBackWriter() {}
+
+ nsresult OnWriteSegment(char *buf, uint32_t count, uint32_t *countWritten)
+ {
+ if (mBufLen == 0)
+ return NS_BASE_STREAM_CLOSED;
+
+ if (count > mBufLen)
+ count = mBufLen;
+
+ memcpy(buf, mBuf, count);
+
+ mBuf += count;
+ mBufLen -= count;
+ *countWritten = count;
+ return NS_OK;
+ }
+
+private:
+ const char *mBuf;
+ uint32_t mBufLen;
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpPipeline <public>
+//-----------------------------------------------------------------------------
+
+nsHttpPipeline::nsHttpPipeline()
+ : mStatus(NS_OK)
+ , mRequestIsPartial(false)
+ , mResponseIsPartial(false)
+ , mClosed(false)
+ , mUtilizedPipeline(false)
+ , mPushBackBuf(nullptr)
+ , mPushBackLen(0)
+ , mPushBackMax(0)
+ , mHttp1xTransactionCount(0)
+ , mReceivingFromProgress(0)
+ , mSendingToProgress(0)
+ , mSuppressSendEvents(true)
+{
+}
+
+nsHttpPipeline::~nsHttpPipeline()
+{
+ // make sure we aren't still holding onto any transactions!
+ Close(NS_ERROR_ABORT);
+
+ if (mPushBackBuf)
+ free(mPushBackBuf);
+}
+
+nsresult
+nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans)
+{
+ LOG(("nsHttpPipeline::AddTransaction [this=%p trans=%p]\n", this, trans));
+
+ if (mRequestQ.Length() || mResponseQ.Length())
+ mUtilizedPipeline = true;
+
+ // A reference to the actual transaction is held by the pipeline transaction
+ // in either the request or response queue
+ mRequestQ.AppendElement(trans);
+ uint32_t qlen = PipelineDepth();
+
+ if (qlen != 1) {
+ trans->SetPipelinePosition(qlen);
+ }
+ else {
+ // do it for this case in case an idempotent cancellation
+ // is being repeated and an old value needs to be cleared
+ trans->SetPipelinePosition(0);
+ }
+
+ // trans->SetConnection() needs to be updated to point back at
+ // the pipeline object.
+ trans->SetConnection(this);
+
+ if (mConnection && !mClosed && mRequestQ.Length() == 1)
+ mConnection->ResumeSend();
+
+ return NS_OK;
+}
+
+uint32_t
+nsHttpPipeline::PipelineDepth()
+{
+ return mRequestQ.Length() + mResponseQ.Length();
+}
+
+nsresult
+nsHttpPipeline::SetPipelinePosition(int32_t position)
+{
+ nsAHttpTransaction *trans = Response(0);
+ if (trans)
+ return trans->SetPipelinePosition(position);
+ return NS_OK;
+}
+
+int32_t
+nsHttpPipeline::PipelinePosition()
+{
+ nsAHttpTransaction *trans = Response(0);
+ if (trans)
+ return trans->PipelinePosition();
+
+ // The response queue is empty, so return oldest request
+ if (mRequestQ.Length())
+ return Request(mRequestQ.Length() - 1)->PipelinePosition();
+
+ // No transactions in the pipeline
+ return 0;
+}
+
+nsHttpPipeline *
+nsHttpPipeline::QueryPipeline()
+{
+ return this;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpPipeline::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsHttpPipeline)
+NS_IMPL_RELEASE(nsHttpPipeline)
+
+// multiple inheritance fun :-)
+NS_INTERFACE_MAP_BEGIN(nsHttpPipeline)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
+NS_INTERFACE_MAP_END
+
+
+//-----------------------------------------------------------------------------
+// nsHttpPipeline::nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpPipeline::OnHeadersAvailable(nsAHttpTransaction *trans,
+ nsHttpRequestHead *requestHead,
+ nsHttpResponseHead *responseHead,
+ bool *reset)
+{
+ LOG(("nsHttpPipeline::OnHeadersAvailable [this=%p]\n", this));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mConnection, "no connection");
+
+ RefPtr<nsHttpConnectionInfo> ci;
+ GetConnectionInfo(getter_AddRefs(ci));
+ MOZ_ASSERT(ci);
+
+ if (!ci) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool pipeliningBefore = gHttpHandler->ConnMgr()->SupportsPipelining(ci);
+
+ // trans has now received its response headers; forward to the real connection
+ nsresult rv = mConnection->OnHeadersAvailable(trans,
+ requestHead,
+ responseHead,
+ reset);
+
+ if (!pipeliningBefore && gHttpHandler->ConnMgr()->SupportsPipelining(ci)) {
+ // The received headers have expanded the eligible
+ // pipeline depth for this connection
+ gHttpHandler->ConnMgr()->ProcessPendingQForEntry(ci);
+ }
+
+ return rv;
+}
+
+void
+nsHttpPipeline::CloseTransaction(nsAHttpTransaction *aTrans, nsresult reason)
+{
+ LOG(("nsHttpPipeline::CloseTransaction [this=%p trans=%p reason=%x]\n",
+ this, aTrans, reason));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(NS_FAILED(reason), "expecting failure code");
+
+ // the specified transaction is to be closed with the given "reason"
+ RefPtr<nsAHttpTransaction> trans(aTrans);
+ int32_t index;
+ bool killPipeline = false;
+
+ if ((index = mRequestQ.IndexOf(trans)) >= 0) {
+ if (index == 0 && mRequestIsPartial) {
+ // the transaction is in the request queue. check to see if any of
+ // its data has been written out yet.
+ killPipeline = true;
+ }
+ mRequestQ.RemoveElementAt(index);
+ } else if ((index = mResponseQ.IndexOf(trans)) >= 0) {
+ mResponseQ.RemoveElementAt(index);
+ // while we could avoid killing the pipeline if this transaction is the
+ // last transaction in the pipeline, there doesn't seem to be that much
+ // value in doing so. most likely if this transaction is going away,
+ // the others will be shortly as well.
+ killPipeline = true;
+ }
+
+ // Marking this connection as non-reusable prevents other items from being
+ // added to it and causes it to be torn down soon.
+ DontReuse();
+
+ trans->Close(reason);
+ trans = nullptr;
+
+ if (killPipeline) {
+ // reschedule anything from this pipeline onto a different connection
+ CancelPipeline(reason);
+ }
+
+ // If all the transactions have been removed then we can close the connection
+ // right away.
+ if (!mRequestQ.Length() && !mResponseQ.Length() && mConnection)
+ mConnection->CloseTransaction(this, reason);
+}
+
+nsresult
+nsHttpPipeline::TakeTransport(nsISocketTransport **aTransport,
+ nsIAsyncInputStream **aInputStream,
+ nsIAsyncOutputStream **aOutputStream)
+{
+ return mConnection->TakeTransport(aTransport, aInputStream, aOutputStream);
+}
+
+bool
+nsHttpPipeline::IsPersistent()
+{
+ return true; // pipelining requires this
+}
+
+bool
+nsHttpPipeline::IsReused()
+{
+ if (!mUtilizedPipeline && mConnection)
+ return mConnection->IsReused();
+ return true;
+}
+
+void
+nsHttpPipeline::DontReuse()
+{
+ if (mConnection)
+ mConnection->DontReuse();
+}
+
+nsresult
+nsHttpPipeline::PushBack(const char *data, uint32_t length)
+{
+ LOG(("nsHttpPipeline::PushBack [this=%p len=%u]\n", this, length));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mPushBackLen == 0, "push back buffer already has data!");
+
+ // If we have no chance for a pipeline (e.g. due to an Upgrade)
+ // then push this data down to original connection
+ if (!mConnection->IsPersistent())
+ return mConnection->PushBack(data, length);
+
+ // PushBack is called recursively from WriteSegments
+
+ // XXX we have a design decision to make here. either we buffer the data
+ // and process it when we return to WriteSegments, or we attempt to move
+ // onto the next transaction from here. doing so adds complexity with the
+ // benefit of eliminating the extra buffer copy. the buffer is at most
+ // 4096 bytes, so it is really unclear if there is any value in the added
+ // complexity. besides simplicity, buffering this data has the advantage
+ // that we'll call close on the transaction sooner, which will wake up
+ // the HTTP channel sooner to continue with its work.
+
+ if (!mPushBackBuf) {
+ mPushBackMax = length;
+ mPushBackBuf = (char *) malloc(mPushBackMax);
+ if (!mPushBackBuf)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ else if (length > mPushBackMax) {
+ // grow push back buffer as necessary.
+ MOZ_ASSERT(length <= nsIOService::gDefaultSegmentSize, "too big");
+ mPushBackMax = length;
+ mPushBackBuf = (char *) realloc(mPushBackBuf, mPushBackMax);
+ if (!mPushBackBuf)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ memcpy(mPushBackBuf, data, length);
+ mPushBackLen = length;
+
+ return NS_OK;
+}
+
+already_AddRefed<nsHttpConnection>
+nsHttpPipeline::TakeHttpConnection()
+{
+ if (mConnection)
+ return mConnection->TakeHttpConnection();
+ return nullptr;
+}
+
+nsAHttpTransaction::Classifier
+nsHttpPipeline::Classification()
+{
+ if (mConnection)
+ return mConnection->Classification();
+
+ LOG(("nsHttpPipeline::Classification this=%p "
+ "has null mConnection using CLASS_SOLO default", this));
+ return nsAHttpTransaction::CLASS_SOLO;
+}
+
+void
+nsHttpPipeline::SetProxyConnectFailed()
+{
+ nsAHttpTransaction *trans = Request(0);
+
+ if (trans)
+ trans->SetProxyConnectFailed();
+}
+
+nsHttpRequestHead *
+nsHttpPipeline::RequestHead()
+{
+ nsAHttpTransaction *trans = Request(0);
+
+ if (trans)
+ return trans->RequestHead();
+ return nullptr;
+}
+
+uint32_t
+nsHttpPipeline::Http1xTransactionCount()
+{
+ return mHttp1xTransactionCount;
+}
+
+nsresult
+nsHttpPipeline::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions)
+{
+ LOG(("nsHttpPipeline::TakeSubTransactions [this=%p]\n", this));
+
+ if (mResponseQ.Length() || mRequestIsPartial)
+ return NS_ERROR_ALREADY_OPENED;
+
+ int32_t i, count = mRequestQ.Length();
+ for (i = 0; i < count; ++i) {
+ nsAHttpTransaction *trans = Request(i);
+ // set the transaction connection object back to the underlying
+ // nsHttpConnectionHandle
+ trans->SetConnection(mConnection);
+ outTransactions.AppendElement(trans);
+ }
+ mRequestQ.Clear();
+
+ LOG((" took %d\n", count));
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpPipeline::nsAHttpTransaction
+//-----------------------------------------------------------------------------
+
+void
+nsHttpPipeline::SetConnection(nsAHttpConnection *conn)
+{
+ LOG(("nsHttpPipeline::SetConnection [this=%p conn=%p]\n", this, conn));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(!conn || !mConnection, "already have a connection");
+
+ mConnection = conn;
+}
+
+nsAHttpConnection *
+nsHttpPipeline::Connection()
+{
+ LOG(("nsHttpPipeline::Connection [this=%p conn=%p]\n", this, mConnection.get()));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ return mConnection;
+}
+
+void
+nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor **result)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // depending on timing this could be either the request or the response
+ // that is needed - but they both go to the same host. A request for these
+ // callbacks directly in nsHttpTransaction would not make a distinction
+ // over whether the the request had been transmitted yet.
+ nsAHttpTransaction *trans = Request(0);
+ if (!trans)
+ trans = Response(0);
+ if (trans)
+ trans->GetSecurityCallbacks(result);
+ else {
+ *result = nullptr;
+ }
+}
+
+void
+nsHttpPipeline::OnTransportStatus(nsITransport* transport,
+ nsresult status, int64_t progress)
+{
+ LOG(("nsHttpPipeline::OnStatus [this=%p status=%x progress=%lld]\n",
+ this, status, progress));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsAHttpTransaction *trans;
+ int32_t i, count;
+
+ switch (status) {
+
+ case NS_NET_STATUS_RESOLVING_HOST:
+ case NS_NET_STATUS_RESOLVED_HOST:
+ case NS_NET_STATUS_CONNECTING_TO:
+ case NS_NET_STATUS_CONNECTED_TO:
+ // These should only appear at most once per pipeline.
+ // Deliver to the first transaction.
+
+ trans = Request(0);
+ if (!trans)
+ trans = Response(0);
+ if (trans)
+ trans->OnTransportStatus(transport, status, progress);
+
+ break;
+
+ case NS_NET_STATUS_SENDING_TO:
+ // This is generated by the socket transport when (part) of
+ // a transaction is written out
+ //
+ // In pipelining this is generated out of FillSendBuf(), but it cannot do
+ // so until the connection is confirmed by CONNECTED_TO.
+ // See patch for bug 196827.
+ //
+
+ if (mSuppressSendEvents) {
+ mSuppressSendEvents = false;
+
+ // catch up by sending the event to all the transactions that have
+ // moved from request to response and any that have been partially
+ // sent. Also send WAITING_FOR to those that were completely sent
+ count = mResponseQ.Length();
+ for (i = 0; i < count; ++i) {
+ Response(i)->OnTransportStatus(transport,
+ NS_NET_STATUS_SENDING_TO,
+ progress);
+ Response(i)->OnTransportStatus(transport,
+ NS_NET_STATUS_WAITING_FOR,
+ progress);
+ }
+ if (mRequestIsPartial && Request(0))
+ Request(0)->OnTransportStatus(transport,
+ NS_NET_STATUS_SENDING_TO,
+ progress);
+ mSendingToProgress = progress;
+ }
+ // otherwise ignore it
+ break;
+
+ case NS_NET_STATUS_WAITING_FOR:
+ // Created by nsHttpConnection when request pipeline has been totally
+ // sent. Ignore it here because it is simulated in FillSendBuf() when
+ // a request is moved from request to response.
+
+ // ignore it
+ break;
+
+ case NS_NET_STATUS_RECEIVING_FROM:
+ // Forward this only to the transaction currently recieving data. It is
+ // normally generated by the socket transport, but can also
+ // be repeated by the pushbackwriter if necessary.
+ mReceivingFromProgress = progress;
+ if (Response(0))
+ Response(0)->OnTransportStatus(transport, status, progress);
+ break;
+
+ default:
+ // forward other notifications to all request transactions
+ count = mRequestQ.Length();
+ for (i = 0; i < count; ++i)
+ Request(i)->OnTransportStatus(transport, status, progress);
+ break;
+ }
+}
+
+nsHttpConnectionInfo *
+nsHttpPipeline::ConnectionInfo()
+{
+ nsAHttpTransaction *trans = Request(0) ? Request(0) : Response(0);
+ if (!trans) {
+ return nullptr;
+ }
+ return trans->ConnectionInfo();
+}
+
+bool
+nsHttpPipeline::IsDone()
+{
+ bool done = true;
+
+ uint32_t i, count = mRequestQ.Length();
+ for (i = 0; done && (i < count); i++)
+ done = Request(i)->IsDone();
+
+ count = mResponseQ.Length();
+ for (i = 0; done && (i < count); i++)
+ done = Response(i)->IsDone();
+
+ return done;
+}
+
+nsresult
+nsHttpPipeline::Status()
+{
+ return mStatus;
+}
+
+uint32_t
+nsHttpPipeline::Caps()
+{
+ nsAHttpTransaction *trans = Request(0);
+ if (!trans)
+ trans = Response(0);
+
+ return trans ? trans->Caps() : 0;
+}
+
+void
+nsHttpPipeline::SetDNSWasRefreshed()
+{
+ nsAHttpTransaction *trans = Request(0);
+ if (!trans)
+ trans = Response(0);
+
+ if (trans)
+ trans->SetDNSWasRefreshed();
+}
+
+uint64_t
+nsHttpPipeline::Available()
+{
+ uint64_t result = 0;
+
+ int32_t i, count = mRequestQ.Length();
+ for (i=0; i<count; ++i)
+ result += Request(i)->Available();
+ return result;
+}
+
+nsresult
+nsHttpPipeline::ReadFromPipe(nsIInputStream *stream,
+ void *closure,
+ const char *buf,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ nsHttpPipeline *self = (nsHttpPipeline *) closure;
+ return self->mReader->OnReadSegment(buf, count, countRead);
+}
+
+nsresult
+nsHttpPipeline::ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ LOG(("nsHttpPipeline::ReadSegments [this=%p count=%u]\n", this, count));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mClosed) {
+ *countRead = 0;
+ return mStatus;
+ }
+
+ nsresult rv;
+ uint64_t avail = 0;
+ if (mSendBufIn) {
+ rv = mSendBufIn->Available(&avail);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (avail == 0) {
+ rv = FillSendBuf();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mSendBufIn->Available(&avail);
+ if (NS_FAILED(rv)) return rv;
+
+ // return EOF if send buffer is empty
+ if (avail == 0) {
+ *countRead = 0;
+ return NS_OK;
+ }
+ }
+
+ // read no more than what was requested
+ if (avail > count)
+ avail = count;
+
+ mReader = reader;
+
+ // avail is under 4GB, so casting to uint32_t is safe
+ rv = mSendBufIn->ReadSegments(ReadFromPipe, this, (uint32_t)avail, countRead);
+
+ mReader = nullptr;
+ return rv;
+}
+
+nsresult
+nsHttpPipeline::WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count,
+ uint32_t *countWritten)
+{
+ LOG(("nsHttpPipeline::WriteSegments [this=%p count=%u]\n", this, count));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mClosed)
+ return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
+
+ nsAHttpTransaction *trans;
+ nsresult rv;
+
+ trans = Response(0);
+ // This code deals with the establishment of a CONNECT tunnel through
+ // an HTTP proxy. It allows the connection to do the CONNECT/200
+ // HTTP transaction to establish a tunnel as a precursor to the
+ // actual pipeline of regular HTTP transactions.
+ if (!trans && mRequestQ.Length() &&
+ mConnection->IsProxyConnectInProgress()) {
+ LOG(("nsHttpPipeline::WriteSegments [this=%p] Forced Delegation\n",
+ this));
+ trans = Request(0);
+ }
+
+ if (!trans) {
+ if (mRequestQ.Length() > 0)
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ else
+ rv = NS_BASE_STREAM_CLOSED;
+ } else {
+ //
+ // ask the transaction to consume data from the connection.
+ // PushBack may be called recursively.
+ //
+ rv = trans->WriteSegments(writer, count, countWritten);
+
+ if (rv == NS_BASE_STREAM_CLOSED || trans->IsDone()) {
+ trans->Close(NS_OK);
+
+ // Release the transaction if it is not IsProxyConnectInProgress()
+ if (trans == Response(0)) {
+ mResponseQ.RemoveElementAt(0);
+ mResponseIsPartial = false;
+ ++mHttp1xTransactionCount;
+ }
+
+ // ask the connection manager to add additional transactions
+ // to our pipeline.
+ RefPtr<nsHttpConnectionInfo> ci;
+ GetConnectionInfo(getter_AddRefs(ci));
+ if (ci)
+ gHttpHandler->ConnMgr()->ProcessPendingQForEntry(ci);
+ }
+ else
+ mResponseIsPartial = true;
+ }
+
+ if (mPushBackLen) {
+ nsHttpPushBackWriter pushBackWriter(mPushBackBuf, mPushBackLen);
+ uint32_t len = mPushBackLen, n;
+ mPushBackLen = 0;
+
+ // This progress notification has previously been sent from
+ // the socket transport code, but it was delivered to the
+ // previous transaction on the pipeline.
+ nsITransport *transport = Transport();
+ if (transport)
+ OnTransportStatus(transport, NS_NET_STATUS_RECEIVING_FROM,
+ mReceivingFromProgress);
+
+ // the push back buffer is never larger than NS_HTTP_SEGMENT_SIZE,
+ // so we are guaranteed that the next response will eat the entire
+ // push back buffer (even though it might again call PushBack).
+ rv = WriteSegments(&pushBackWriter, len, &n);
+ }
+
+ return rv;
+}
+
+uint32_t
+nsHttpPipeline::CancelPipeline(nsresult originalReason)
+{
+ uint32_t i, reqLen, respLen, total;
+ nsAHttpTransaction *trans;
+
+ reqLen = mRequestQ.Length();
+ respLen = mResponseQ.Length();
+ total = reqLen + respLen;
+
+ // don't count the first response, if presnet
+ if (respLen)
+ total--;
+
+ if (!total)
+ return 0;
+
+ // any pending requests can ignore this error and be restarted
+ // unless it is during a CONNECT tunnel request
+ for (i = 0; i < reqLen; ++i) {
+ trans = Request(i);
+ if (mConnection && mConnection->IsProxyConnectInProgress())
+ trans->Close(originalReason);
+ else
+ trans->Close(NS_ERROR_NET_RESET);
+ }
+ mRequestQ.Clear();
+
+ // any pending responses can be restarted except for the first one,
+ // that we might want to finish on this pipeline or cancel individually.
+ // Higher levels of callers ensure that we don't process non-idempotent
+ // tranasction with the NS_HTTP_ALLOW_PIPELINING bit set
+ for (i = 1; i < respLen; ++i) {
+ trans = Response(i);
+ trans->Close(NS_ERROR_NET_RESET);
+ }
+
+ if (respLen > 1)
+ mResponseQ.TruncateLength(1);
+
+ DontReuse();
+ Classify(nsAHttpTransaction::CLASS_SOLO);
+
+ return total;
+}
+
+void
+nsHttpPipeline::Close(nsresult reason)
+{
+ LOG(("nsHttpPipeline::Close [this=%p reason=%x]\n", this, reason));
+
+ if (mClosed) {
+ LOG((" already closed\n"));
+ return;
+ }
+
+ // the connection is going away!
+ mStatus = reason;
+ mClosed = true;
+
+ RefPtr<nsHttpConnectionInfo> ci;
+ GetConnectionInfo(getter_AddRefs(ci));
+ uint32_t numRescheduled = CancelPipeline(reason);
+
+ // numRescheduled can be 0 if there is just a single response in the
+ // pipeline object. That isn't really a meaningful pipeline that
+ // has been forced to be rescheduled so it does not need to generate
+ // negative feedback.
+ if (ci && numRescheduled)
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ ci, nsHttpConnectionMgr::RedCanceledPipeline, nullptr, 0);
+
+ nsAHttpTransaction *trans = Response(0);
+ if (!trans)
+ return;
+
+ // The current transaction can be restarted via reset
+ // if the response has not started to arrive and the reason
+ // for failure is innocuous (e.g. not an SSL error)
+ if (!mResponseIsPartial &&
+ (reason == NS_ERROR_NET_RESET ||
+ reason == NS_OK ||
+ reason == NS_ERROR_NET_TIMEOUT ||
+ reason == NS_BASE_STREAM_CLOSED)) {
+ trans->Close(NS_ERROR_NET_RESET);
+ }
+ else {
+ trans->Close(reason);
+ }
+
+ mResponseQ.Clear();
+}
+
+nsresult
+nsHttpPipeline::OnReadSegment(const char *segment,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ return mSendBufOut->Write(segment, count, countRead);
+}
+
+nsresult
+nsHttpPipeline::FillSendBuf()
+{
+ // reads from request queue, moving transactions to response queue
+ // when they have been completely read.
+
+ nsresult rv;
+
+ if (!mSendBufIn) {
+ // allocate a single-segment pipe
+ rv = NS_NewPipe(getter_AddRefs(mSendBufIn),
+ getter_AddRefs(mSendBufOut),
+ nsIOService::gDefaultSegmentSize, /* segment size */
+ nsIOService::gDefaultSegmentSize, /* max size */
+ true, true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ uint32_t n;
+ uint64_t avail;
+ RefPtr<nsAHttpTransaction> trans;
+ nsITransport *transport = Transport();
+
+ while ((trans = Request(0)) != nullptr) {
+ avail = trans->Available();
+ if (avail) {
+ // if there is already a response in the responseq then this
+ // new data comprises a pipeline. Update the transaction in the
+ // response queue to reflect that if necessary. We are now sending
+ // out a request while we haven't received all responses.
+ nsAHttpTransaction *response = Response(0);
+ if (response && !response->PipelinePosition())
+ response->SetPipelinePosition(1);
+ rv = trans->ReadSegments(this, (uint32_t)std::min(avail, (uint64_t)UINT32_MAX), &n);
+ if (NS_FAILED(rv)) return rv;
+
+ if (n == 0) {
+ LOG(("send pipe is full"));
+ break;
+ }
+
+ mSendingToProgress += n;
+ if (!mSuppressSendEvents && transport) {
+ // Simulate a SENDING_TO event
+ trans->OnTransportStatus(transport,
+ NS_NET_STATUS_SENDING_TO,
+ mSendingToProgress);
+ }
+ }
+
+ avail = trans->Available();
+ if (avail == 0) {
+ // move transaction from request queue to response queue
+ mRequestQ.RemoveElementAt(0);
+ mResponseQ.AppendElement(trans);
+ mRequestIsPartial = false;
+
+ if (!mSuppressSendEvents && transport) {
+ // Simulate a WAITING_FOR event
+ trans->OnTransportStatus(transport,
+ NS_NET_STATUS_WAITING_FOR,
+ mSendingToProgress);
+ }
+
+ // It would be good to re-enable data read handlers via ResumeRecv()
+ // except the read handler code can be synchronously dispatched on
+ // the stack.
+ }
+ else
+ mRequestIsPartial = true;
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpPipeline.h b/netwerk/protocol/http/nsHttpPipeline.h
new file mode 100644
index 000000000..6dc027f19
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpPipeline.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpPipeline_h__
+#define nsHttpPipeline_h__
+
+#include "nsAHttpConnection.h"
+#include "nsAHttpTransaction.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+
+class nsIInputStream;
+class nsIOutputStream;
+
+namespace mozilla { namespace net {
+
+class nsHttpPipeline final : public nsAHttpConnection
+ , public nsAHttpTransaction
+ , public nsAHttpSegmentReader
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPCONNECTION(mConnection)
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_NSAHTTPSEGMENTREADER
+
+ nsHttpPipeline();
+
+ bool ResponseTimeoutEnabled() const override final {
+ return true;
+ }
+
+private:
+ virtual ~nsHttpPipeline();
+
+ nsresult FillSendBuf();
+
+ static nsresult ReadFromPipe(nsIInputStream *, void *, const char *,
+ uint32_t, uint32_t, uint32_t *);
+
+ // convenience functions
+ nsAHttpTransaction *Request(int32_t i)
+ {
+ if (mRequestQ.Length() == 0)
+ return nullptr;
+
+ return mRequestQ[i];
+ }
+ nsAHttpTransaction *Response(int32_t i)
+ {
+ if (mResponseQ.Length() == 0)
+ return nullptr;
+
+ return mResponseQ[i];
+ }
+
+ // overload of nsAHttpTransaction::QueryPipeline()
+ nsHttpPipeline *QueryPipeline() override;
+
+ RefPtr<nsAHttpConnection> mConnection;
+ nsTArray<RefPtr<nsAHttpTransaction> > mRequestQ;
+ nsTArray<RefPtr<nsAHttpTransaction> > mResponseQ;
+ nsresult mStatus;
+
+ // these flags indicate whether or not the first request or response
+ // is partial. a partial request means that Request(0) has been
+ // partially written out to the socket. a partial response means
+ // that Response(0) has been partially read in from the socket.
+ bool mRequestIsPartial;
+ bool mResponseIsPartial;
+
+ // indicates whether or not the pipeline has been explicitly closed.
+ bool mClosed;
+
+ // indicates whether or not a true pipeline (more than 1 request without
+ // a synchronous response) has been formed.
+ bool mUtilizedPipeline;
+
+ // used when calling ReadSegments/WriteSegments on a transaction.
+ nsAHttpSegmentReader *mReader;
+
+ // send buffer
+ nsCOMPtr<nsIInputStream> mSendBufIn;
+ nsCOMPtr<nsIOutputStream> mSendBufOut;
+
+ // the push back buffer. not exceeding nsIOService::gDefaultSegmentSize bytes.
+ char *mPushBackBuf;
+ uint32_t mPushBackLen;
+ uint32_t mPushBackMax;
+
+ // The number of transactions completed on this pipeline.
+ uint32_t mHttp1xTransactionCount;
+
+ // For support of OnTransportStatus()
+ int64_t mReceivingFromProgress;
+ int64_t mSendingToProgress;
+ bool mSuppressSendEvents;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpPipeline_h__
diff --git a/netwerk/protocol/http/nsHttpRequestHead.cpp b/netwerk/protocol/http/nsHttpRequestHead.cpp
new file mode 100644
index 000000000..094a79457
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpRequestHead.cpp
@@ -0,0 +1,369 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpRequestHead.h"
+#include "nsIHttpHeaderVisitor.h"
+
+//-----------------------------------------------------------------------------
+// nsHttpRequestHead
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+nsHttpRequestHead::nsHttpRequestHead()
+ : mMethod(NS_LITERAL_CSTRING("GET"))
+ , mVersion(NS_HTTP_VERSION_1_1)
+ , mParsedMethod(kMethod_Get)
+ , mHTTPS(false)
+ , mReentrantMonitor("nsHttpRequestHead.mReentrantMonitor")
+ , mInVisitHeaders(false)
+{
+ MOZ_COUNT_CTOR(nsHttpRequestHead);
+}
+
+nsHttpRequestHead::~nsHttpRequestHead()
+{
+ MOZ_COUNT_DTOR(nsHttpRequestHead);
+}
+
+// Don't use this function. It is only used by HttpChannelParent to avoid
+// copying of request headers!!!
+const nsHttpHeaderArray &
+nsHttpRequestHead::Headers() const
+{
+ nsHttpRequestHead &curr = const_cast<nsHttpRequestHead&>(*this);
+ curr.mReentrantMonitor.AssertCurrentThreadIn();
+ return mHeaders;
+}
+
+void
+nsHttpRequestHead::SetHeaders(const nsHttpHeaderArray& aHeaders)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mHeaders = aHeaders;
+}
+
+void
+nsHttpRequestHead::SetVersion(nsHttpVersion version)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mVersion = version;
+}
+
+void
+nsHttpRequestHead::SetRequestURI(const nsCSubstring &s)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mRequestURI = s;
+}
+
+void
+nsHttpRequestHead::SetPath(const nsCSubstring &s)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mPath = s;
+}
+
+uint32_t
+nsHttpRequestHead::HeaderCount()
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mHeaders.Count();
+}
+
+nsresult
+nsHttpRequestHead::VisitHeaders(nsIHttpHeaderVisitor *visitor,
+ nsHttpHeaderArray::VisitorFilter filter /* = nsHttpHeaderArray::eFilterAll*/)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mInVisitHeaders = true;
+ nsresult rv = mHeaders.VisitHeaders(visitor, filter);
+ mInVisitHeaders = false;
+ return rv;
+}
+
+void
+nsHttpRequestHead::Method(nsACString &aMethod)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ aMethod = mMethod;
+}
+
+nsHttpVersion
+nsHttpRequestHead::Version()
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mVersion;
+}
+
+void
+nsHttpRequestHead::RequestURI(nsACString &aRequestURI)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ aRequestURI = mRequestURI;
+}
+
+void
+nsHttpRequestHead::Path(nsACString &aPath)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ aPath = mPath.IsEmpty() ? mRequestURI : mPath;
+}
+
+void
+nsHttpRequestHead::SetHTTPS(bool val)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mHTTPS = val;
+}
+
+void
+nsHttpRequestHead::Origin(nsACString &aOrigin)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ aOrigin = mOrigin;
+}
+
+nsresult
+nsHttpRequestHead::SetHeader(nsHttpAtom h, const nsACString &v,
+ bool m /*= false*/)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mHeaders.SetHeader(h, v, m,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+}
+
+nsresult
+nsHttpRequestHead::SetHeader(nsHttpAtom h, const nsACString &v, bool m,
+ nsHttpHeaderArray::HeaderVariety variety)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mHeaders.SetHeader(h, v, m, variety);
+}
+
+nsresult
+nsHttpRequestHead::SetEmptyHeader(nsHttpAtom h)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mHeaders.SetEmptyHeader(h,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+}
+
+nsresult
+nsHttpRequestHead::GetHeader(nsHttpAtom h, nsACString &v)
+{
+ v.Truncate();
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mHeaders.GetHeader(h, v);
+}
+
+nsresult
+nsHttpRequestHead::ClearHeader(nsHttpAtom h)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mHeaders.ClearHeader(h);
+ return NS_OK;
+}
+
+void
+nsHttpRequestHead::ClearHeaders()
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (mInVisitHeaders) {
+ return;
+ }
+
+ mHeaders.Clear();
+}
+
+bool
+nsHttpRequestHead::HasHeader(nsHttpAtom h)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mHeaders.HasHeader(h);
+}
+
+bool
+nsHttpRequestHead::HasHeaderValue(nsHttpAtom h, const char *v)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mHeaders.HasHeaderValue(h, v);
+}
+
+nsresult
+nsHttpRequestHead::SetHeaderOnce(nsHttpAtom h, const char *v,
+ bool merge /*= false */)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!merge || !mHeaders.HasHeaderValue(h, v)) {
+ return mHeaders.SetHeader(h, nsDependentCString(v), merge,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+ }
+ return NS_OK;
+}
+
+nsHttpRequestHead::ParsedMethodType
+nsHttpRequestHead::ParsedMethod()
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mParsedMethod;
+}
+
+bool
+nsHttpRequestHead::EqualsMethod(ParsedMethodType aType)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mParsedMethod == aType;
+}
+
+void
+nsHttpRequestHead::ParseHeaderSet(const char *buffer)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ nsHttpAtom hdr;
+ nsAutoCString val;
+ while (buffer) {
+ const char *eof = strchr(buffer, '\r');
+ if (!eof) {
+ break;
+ }
+ if (NS_SUCCEEDED(nsHttpHeaderArray::ParseHeaderLine(
+ nsDependentCSubstring(buffer, eof - buffer),
+ &hdr,
+ &val))) {
+
+ mHeaders.SetHeaderFromNet(hdr, val, false);
+ }
+ buffer = eof + 1;
+ if (*buffer == '\n') {
+ buffer++;
+ }
+ }
+}
+
+bool
+nsHttpRequestHead::IsHTTPS()
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mHTTPS;
+}
+
+void
+nsHttpRequestHead::SetMethod(const nsACString &method)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mParsedMethod = kMethod_Custom;
+ mMethod = method;
+ if (!strcmp(mMethod.get(), "GET")) {
+ mParsedMethod = kMethod_Get;
+ } else if (!strcmp(mMethod.get(), "POST")) {
+ mParsedMethod = kMethod_Post;
+ } else if (!strcmp(mMethod.get(), "OPTIONS")) {
+ mParsedMethod = kMethod_Options;
+ } else if (!strcmp(mMethod.get(), "CONNECT")) {
+ mParsedMethod = kMethod_Connect;
+ } else if (!strcmp(mMethod.get(), "HEAD")) {
+ mParsedMethod = kMethod_Head;
+ } else if (!strcmp(mMethod.get(), "PUT")) {
+ mParsedMethod = kMethod_Put;
+ } else if (!strcmp(mMethod.get(), "TRACE")) {
+ mParsedMethod = kMethod_Trace;
+ }
+}
+
+void
+nsHttpRequestHead::SetOrigin(const nsACString &scheme, const nsACString &host,
+ int32_t port)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mOrigin.Assign(scheme);
+ mOrigin.Append(NS_LITERAL_CSTRING("://"));
+ mOrigin.Append(host);
+ if (port >= 0) {
+ mOrigin.Append(NS_LITERAL_CSTRING(":"));
+ mOrigin.AppendInt(port);
+ }
+}
+
+bool
+nsHttpRequestHead::IsSafeMethod()
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ // This code will need to be extended for new safe methods, otherwise
+ // they'll default to "not safe".
+ if ((mParsedMethod == kMethod_Get) || (mParsedMethod == kMethod_Head) ||
+ (mParsedMethod == kMethod_Options) || (mParsedMethod == kMethod_Trace)
+ ) {
+ return true;
+ }
+
+ if (mParsedMethod != kMethod_Custom) {
+ return false;
+ }
+
+ return (!strcmp(mMethod.get(), "PROPFIND") ||
+ !strcmp(mMethod.get(), "REPORT") ||
+ !strcmp(mMethod.get(), "SEARCH"));
+}
+
+void
+nsHttpRequestHead::Flatten(nsACString &buf, bool pruneProxyHeaders)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ // note: the first append is intentional.
+
+ buf.Append(mMethod);
+ buf.Append(' ');
+ buf.Append(mRequestURI);
+ buf.AppendLiteral(" HTTP/");
+
+ switch (mVersion) {
+ case NS_HTTP_VERSION_1_1:
+ buf.AppendLiteral("1.1");
+ break;
+ case NS_HTTP_VERSION_0_9:
+ buf.AppendLiteral("0.9");
+ break;
+ default:
+ buf.AppendLiteral("1.0");
+ }
+
+ buf.AppendLiteral("\r\n");
+
+ mHeaders.Flatten(buf, pruneProxyHeaders, false);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpRequestHead.h b/netwerk/protocol/http/nsHttpRequestHead.h
new file mode 100644
index 000000000..415968083
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpRequestHead.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpRequestHead_h__
+#define nsHttpRequestHead_h__
+
+#include "nsHttp.h"
+#include "nsHttpHeaderArray.h"
+#include "nsString.h"
+#include "mozilla/ReentrantMonitor.h"
+
+class nsIHttpHeaderVisitor;
+
+namespace mozilla { namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpRequestHead represents the request line and headers from an HTTP
+// request.
+//-----------------------------------------------------------------------------
+
+class nsHttpRequestHead
+{
+public:
+ nsHttpRequestHead();
+ ~nsHttpRequestHead();
+
+ // The following function is only used in HttpChannelParent to avoid
+ // copying headers. If you use it be careful to do it only under
+ // nsHttpRequestHead lock!!!
+ const nsHttpHeaderArray &Headers() const;
+ void Enter() { mReentrantMonitor.Enter(); }
+ void Exit() { mReentrantMonitor.Exit(); }
+
+ void SetHeaders(const nsHttpHeaderArray& aHeaders);
+
+ void SetMethod(const nsACString &method);
+ void SetVersion(nsHttpVersion version);
+ void SetRequestURI(const nsCSubstring &s);
+ void SetPath(const nsCSubstring &s);
+ uint32_t HeaderCount();
+
+ // Using this function it is possible to itereate through all headers
+ // automatically under one lock.
+ nsresult VisitHeaders(nsIHttpHeaderVisitor *visitor,
+ nsHttpHeaderArray::VisitorFilter filter =
+ nsHttpHeaderArray::eFilterAll);
+ void Method(nsACString &aMethod);
+ nsHttpVersion Version();
+ void RequestURI(nsACString &RequestURI);
+ void Path(nsACString &aPath);
+ void SetHTTPS(bool val);
+ bool IsHTTPS();
+
+ void SetOrigin(const nsACString &scheme, const nsACString &host,
+ int32_t port);
+ void Origin(nsACString &aOrigin);
+
+ nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m=false);
+ nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m,
+ nsHttpHeaderArray::HeaderVariety variety);
+ nsresult SetEmptyHeader(nsHttpAtom h);
+ nsresult GetHeader(nsHttpAtom h, nsACString &v);
+
+ nsresult ClearHeader(nsHttpAtom h);
+ void ClearHeaders();
+
+ bool HasHeaderValue(nsHttpAtom h, const char *v);
+ // This function returns true if header is set even if it is an empty
+ // header.
+ bool HasHeader(nsHttpAtom h);
+ void Flatten(nsACString &, bool pruneProxyHeaders = false);
+
+ // Don't allow duplicate values
+ nsresult SetHeaderOnce(nsHttpAtom h, const char *v, bool merge = false);
+
+ bool IsSafeMethod();
+
+ enum ParsedMethodType
+ {
+ kMethod_Custom,
+ kMethod_Get,
+ kMethod_Post,
+ kMethod_Options,
+ kMethod_Connect,
+ kMethod_Head,
+ kMethod_Put,
+ kMethod_Trace
+ };
+
+ ParsedMethodType ParsedMethod();
+ bool EqualsMethod(ParsedMethodType aType);
+ bool IsGet() { return EqualsMethod(kMethod_Get); }
+ bool IsPost() { return EqualsMethod(kMethod_Post); }
+ bool IsOptions() { return EqualsMethod(kMethod_Options); }
+ bool IsConnect() { return EqualsMethod(kMethod_Connect); }
+ bool IsHead() { return EqualsMethod(kMethod_Head); }
+ bool IsPut() { return EqualsMethod(kMethod_Put); }
+ bool IsTrace() { return EqualsMethod(kMethod_Trace); }
+ void ParseHeaderSet(const char *buffer);
+private:
+ // All members must be copy-constructable and assignable
+ nsHttpHeaderArray mHeaders;
+ nsCString mMethod;
+ nsHttpVersion mVersion;
+
+ // mRequestURI and mPath are strings instead of an nsIURI
+ // because this is used off the main thread
+ nsCString mRequestURI;
+ nsCString mPath;
+
+ nsCString mOrigin;
+ ParsedMethodType mParsedMethod;
+ bool mHTTPS;
+
+ // We are using ReentrantMonitor instead of a Mutex because VisitHeader
+ // function calls nsIHttpHeaderVisitor::VisitHeader while under lock.
+ ReentrantMonitor mReentrantMonitor;
+
+ // During VisitHeader we sould not allow cal to SetHeader.
+ bool mInVisitHeaders;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpRequestHead_h__
diff --git a/netwerk/protocol/http/nsHttpResponseHead.cpp b/netwerk/protocol/http/nsHttpResponseHead.cpp
new file mode 100644
index 000000000..6d384c488
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpResponseHead.cpp
@@ -0,0 +1,1221 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpResponseHead.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsPrintfCString.h"
+#include "prtime.h"
+#include "plstr.h"
+#include "nsURLHelper.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpResponseHead <public>
+//-----------------------------------------------------------------------------
+
+nsHttpResponseHead::nsHttpResponseHead(const nsHttpResponseHead &aOther)
+ : mReentrantMonitor("nsHttpResponseHead.mReentrantMonitor")
+ , mInVisitHeaders(false)
+{
+ nsHttpResponseHead &other = const_cast<nsHttpResponseHead&>(aOther);
+ ReentrantMonitorAutoEnter monitor(other.mReentrantMonitor);
+
+ mHeaders = other.mHeaders;
+ mVersion = other.mVersion;
+ mStatus = other.mStatus;
+ mStatusText = other.mStatusText;
+ mContentLength = other.mContentLength;
+ mContentType = other.mContentType;
+ mContentCharset = other.mContentCharset;
+ mCacheControlPrivate = other.mCacheControlPrivate;
+ mCacheControlNoStore = other.mCacheControlNoStore;
+ mCacheControlNoCache = other.mCacheControlNoCache;
+ mCacheControlImmutable = other.mCacheControlImmutable;
+ mPragmaNoCache = other.mPragmaNoCache;
+}
+
+nsHttpResponseHead&
+nsHttpResponseHead::operator=(const nsHttpResponseHead &aOther)
+{
+ nsHttpResponseHead &other = const_cast<nsHttpResponseHead&>(aOther);
+ ReentrantMonitorAutoEnter monitorOther(other.mReentrantMonitor);
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+
+ mHeaders = other.mHeaders;
+ mVersion = other.mVersion;
+ mStatus = other.mStatus;
+ mStatusText = other.mStatusText;
+ mContentLength = other.mContentLength;
+ mContentType = other.mContentType;
+ mContentCharset = other.mContentCharset;
+ mCacheControlPrivate = other.mCacheControlPrivate;
+ mCacheControlNoStore = other.mCacheControlNoStore;
+ mCacheControlNoCache = other.mCacheControlNoCache;
+ mCacheControlImmutable = other.mCacheControlImmutable;
+ mPragmaNoCache = other.mPragmaNoCache;
+
+ return *this;
+}
+
+nsHttpVersion
+nsHttpResponseHead::Version()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mVersion;
+}
+
+uint16_t
+nsHttpResponseHead::Status()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mStatus;
+}
+
+void
+nsHttpResponseHead::StatusText(nsACString &aStatusText)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ aStatusText = mStatusText;
+}
+
+int64_t
+nsHttpResponseHead::ContentLength()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mContentLength;
+}
+
+void
+nsHttpResponseHead::ContentType(nsACString &aContentType)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ aContentType = mContentType;
+}
+
+void
+nsHttpResponseHead::ContentCharset(nsACString &aContentCharset)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ aContentCharset = mContentCharset;
+}
+
+bool
+nsHttpResponseHead::Private()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mCacheControlPrivate;
+}
+
+bool
+nsHttpResponseHead::NoStore()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mCacheControlNoStore;
+}
+
+bool
+nsHttpResponseHead::NoCache()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return (mCacheControlNoCache || mPragmaNoCache);
+}
+
+bool
+nsHttpResponseHead::Immutable()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mCacheControlImmutable;
+}
+
+nsresult
+nsHttpResponseHead::SetHeader(nsHttpAtom hdr,
+ const nsACString &val,
+ bool merge)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+
+ if (mInVisitHeaders) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return SetHeader_locked(hdr, val, merge);
+}
+
+nsresult
+nsHttpResponseHead::SetHeader_locked(nsHttpAtom hdr,
+ const nsACString &val,
+ bool merge)
+{
+ nsresult rv = mHeaders.SetHeader(hdr, val, merge,
+ nsHttpHeaderArray::eVarietyResponse);
+ if (NS_FAILED(rv)) return rv;
+
+ // respond to changes in these headers. we need to reparse the entire
+ // header since the change may have merged in additional values.
+ if (hdr == nsHttp::Cache_Control)
+ ParseCacheControl(mHeaders.PeekHeader(hdr));
+ else if (hdr == nsHttp::Pragma)
+ ParsePragma(mHeaders.PeekHeader(hdr));
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpResponseHead::GetHeader(nsHttpAtom h, nsACString &v)
+{
+ v.Truncate();
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mHeaders.GetHeader(h, v);
+}
+
+void
+nsHttpResponseHead::ClearHeader(nsHttpAtom h)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ mHeaders.ClearHeader(h);
+}
+
+void
+nsHttpResponseHead::ClearHeaders()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ mHeaders.Clear();
+}
+
+bool
+nsHttpResponseHead::HasHeaderValue(nsHttpAtom h, const char *v)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mHeaders.HasHeaderValue(h, v);
+}
+
+bool
+nsHttpResponseHead::HasHeader(nsHttpAtom h)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return mHeaders.HasHeader(h);
+}
+
+void
+nsHttpResponseHead::SetContentType(const nsACString &s)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ mContentType = s;
+}
+
+void
+nsHttpResponseHead::SetContentCharset(const nsACString &s)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ mContentCharset = s;
+}
+
+void
+nsHttpResponseHead::SetContentLength(int64_t len)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+
+ mContentLength = len;
+ if (len < 0)
+ mHeaders.ClearHeader(nsHttp::Content_Length);
+ else
+ mHeaders.SetHeader(nsHttp::Content_Length,
+ nsPrintfCString("%lld", len),
+ false,
+ nsHttpHeaderArray::eVarietyResponse);
+}
+
+void
+nsHttpResponseHead::Flatten(nsACString &buf, bool pruneTransients)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ if (mVersion == NS_HTTP_VERSION_0_9)
+ return;
+
+ buf.AppendLiteral("HTTP/");
+ if (mVersion == NS_HTTP_VERSION_2_0)
+ buf.AppendLiteral("2.0 ");
+ else if (mVersion == NS_HTTP_VERSION_1_1)
+ buf.AppendLiteral("1.1 ");
+ else
+ buf.AppendLiteral("1.0 ");
+
+ buf.Append(nsPrintfCString("%u", unsigned(mStatus)) +
+ NS_LITERAL_CSTRING(" ") +
+ mStatusText +
+ NS_LITERAL_CSTRING("\r\n"));
+
+
+ mHeaders.Flatten(buf, false, pruneTransients);
+}
+
+void
+nsHttpResponseHead::FlattenNetworkOriginalHeaders(nsACString &buf)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ if (mVersion == NS_HTTP_VERSION_0_9) {
+ return;
+ }
+
+ mHeaders.FlattenOriginalHeader(buf);
+}
+
+nsresult
+nsHttpResponseHead::ParseCachedHead(const char *block)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ LOG(("nsHttpResponseHead::ParseCachedHead [this=%p]\n", this));
+
+ // this command works on a buffer as prepared by Flatten, as such it is
+ // not very forgiving ;-)
+
+ char *p = PL_strstr(block, "\r\n");
+ if (!p)
+ return NS_ERROR_UNEXPECTED;
+
+ ParseStatusLine_locked(nsDependentCSubstring(block, p - block));
+
+ do {
+ block = p + 2;
+
+ if (*block == 0)
+ break;
+
+ p = PL_strstr(block, "\r\n");
+ if (!p)
+ return NS_ERROR_UNEXPECTED;
+
+ ParseHeaderLine_locked(nsDependentCSubstring(block, p - block), false);
+
+ } while (1);
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpResponseHead::ParseCachedOriginalHeaders(char *block)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ LOG(("nsHttpResponseHead::ParseCachedOriginalHeader [this=%p]\n", this));
+
+ // this command works on a buffer as prepared by FlattenOriginalHeader,
+ // as such it is not very forgiving ;-)
+
+ if (!block) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ char *p = block;
+ nsHttpAtom hdr = {0};
+ nsAutoCString val;
+ nsresult rv;
+
+ do {
+ block = p;
+
+ if (*block == 0)
+ break;
+
+ p = PL_strstr(block, "\r\n");
+ if (!p)
+ return NS_ERROR_UNEXPECTED;
+
+ *p = 0;
+ if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(
+ nsDependentCString(block, p - block), &hdr, &val))) {
+
+ return NS_OK;
+ }
+
+ rv = mHeaders.SetResponseHeaderFromCache(hdr,
+ val,
+ nsHttpHeaderArray::eVarietyResponseNetOriginal);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ p = p + 2;
+ } while (1);
+
+ return NS_OK;
+}
+
+void
+nsHttpResponseHead::AssignDefaultStatusText()
+{
+ LOG(("response status line needs default reason phrase\n"));
+
+ // if a http response doesn't contain a reason phrase, put one in based
+ // on the status code. The reason phrase is totally meaningless so its
+ // ok to have a default catch all here - but this makes debuggers and addons
+ // a little saner to use if we don't map things to "404 OK" or other nonsense.
+ // In particular, HTTP/2 does not use reason phrases at all so they need to
+ // always be injected.
+
+ switch (mStatus) {
+ // start with the most common
+ case 200:
+ mStatusText.AssignLiteral("OK");
+ break;
+ case 404:
+ mStatusText.AssignLiteral("Not Found");
+ break;
+ case 301:
+ mStatusText.AssignLiteral("Moved Permanently");
+ break;
+ case 304:
+ mStatusText.AssignLiteral("Not Modified");
+ break;
+ case 307:
+ mStatusText.AssignLiteral("Temporary Redirect");
+ break;
+ case 500:
+ mStatusText.AssignLiteral("Internal Server Error");
+ break;
+
+ // also well known
+ case 100:
+ mStatusText.AssignLiteral("Continue");
+ break;
+ case 101:
+ mStatusText.AssignLiteral("Switching Protocols");
+ break;
+ case 201:
+ mStatusText.AssignLiteral("Created");
+ break;
+ case 202:
+ mStatusText.AssignLiteral("Accepted");
+ break;
+ case 203:
+ mStatusText.AssignLiteral("Non Authoritative");
+ break;
+ case 204:
+ mStatusText.AssignLiteral("No Content");
+ break;
+ case 205:
+ mStatusText.AssignLiteral("Reset Content");
+ break;
+ case 206:
+ mStatusText.AssignLiteral("Partial Content");
+ break;
+ case 207:
+ mStatusText.AssignLiteral("Multi-Status");
+ break;
+ case 208:
+ mStatusText.AssignLiteral("Already Reported");
+ break;
+ case 300:
+ mStatusText.AssignLiteral("Multiple Choices");
+ break;
+ case 302:
+ mStatusText.AssignLiteral("Found");
+ break;
+ case 303:
+ mStatusText.AssignLiteral("See Other");
+ break;
+ case 305:
+ mStatusText.AssignLiteral("Use Proxy");
+ break;
+ case 308:
+ mStatusText.AssignLiteral("Permanent Redirect");
+ break;
+ case 400:
+ mStatusText.AssignLiteral("Bad Request");
+ break;
+ case 401:
+ mStatusText.AssignLiteral("Unauthorized");
+ break;
+ case 402:
+ mStatusText.AssignLiteral("Payment Required");
+ break;
+ case 403:
+ mStatusText.AssignLiteral("Forbidden");
+ break;
+ case 405:
+ mStatusText.AssignLiteral("Method Not Allowed");
+ break;
+ case 406:
+ mStatusText.AssignLiteral("Not Acceptable");
+ break;
+ case 407:
+ mStatusText.AssignLiteral("Proxy Authentication Required");
+ break;
+ case 408:
+ mStatusText.AssignLiteral("Request Timeout");
+ break;
+ case 409:
+ mStatusText.AssignLiteral("Conflict");
+ break;
+ case 410:
+ mStatusText.AssignLiteral("Gone");
+ break;
+ case 411:
+ mStatusText.AssignLiteral("Length Required");
+ break;
+ case 412:
+ mStatusText.AssignLiteral("Precondition Failed");
+ break;
+ case 413:
+ mStatusText.AssignLiteral("Request Entity Too Large");
+ break;
+ case 414:
+ mStatusText.AssignLiteral("Request URI Too Long");
+ break;
+ case 415:
+ mStatusText.AssignLiteral("Unsupported Media Type");
+ break;
+ case 416:
+ mStatusText.AssignLiteral("Requested Range Not Satisfiable");
+ break;
+ case 417:
+ mStatusText.AssignLiteral("Expectation Failed");
+ break;
+ case 421:
+ mStatusText.AssignLiteral("Misdirected Request");
+ break;
+ case 501:
+ mStatusText.AssignLiteral("Not Implemented");
+ break;
+ case 502:
+ mStatusText.AssignLiteral("Bad Gateway");
+ break;
+ case 503:
+ mStatusText.AssignLiteral("Service Unavailable");
+ break;
+ case 504:
+ mStatusText.AssignLiteral("Gateway Timeout");
+ break;
+ case 505:
+ mStatusText.AssignLiteral("HTTP Version Unsupported");
+ break;
+ default:
+ mStatusText.AssignLiteral("No Reason Phrase");
+ break;
+ }
+}
+
+void
+nsHttpResponseHead::ParseStatusLine(const nsACString &line)
+{
+
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ ParseStatusLine_locked(line);
+}
+
+void
+nsHttpResponseHead::ParseStatusLine_locked(const nsACString &line)
+{
+ //
+ // Parse Status-Line:: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
+ //
+
+ const char *start = line.BeginReading();
+ const char *end = line.EndReading();
+ const char *p = start;
+
+ // HTTP-Version
+ ParseVersion(start);
+
+ int32_t index = line.FindChar(' ');
+
+ if ((mVersion == NS_HTTP_VERSION_0_9) || (index == -1)) {
+ mStatus = 200;
+ AssignDefaultStatusText();
+ }
+ else {
+ // Status-Code
+ p += index + 1;
+ mStatus = (uint16_t) atoi(p);
+ if (mStatus == 0) {
+ LOG(("mal-formed response status; assuming status = 200\n"));
+ mStatus = 200;
+ }
+
+ // Reason-Phrase is whatever is remaining of the line
+ index = line.FindChar(' ', p - start);
+ if (index == -1) {
+ AssignDefaultStatusText();
+ }
+ else {
+ p = start + index + 1;
+ mStatusText = nsDependentCSubstring(p, end - p);
+ }
+ }
+
+ LOG(("Have status line [version=%u status=%u statusText=%s]\n",
+ unsigned(mVersion), unsigned(mStatus), mStatusText.get()));
+}
+
+nsresult
+nsHttpResponseHead::ParseHeaderLine(const nsACString &line)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return ParseHeaderLine_locked(line, true);
+}
+
+nsresult
+nsHttpResponseHead::ParseHeaderLine_locked(const nsACString &line, bool originalFromNetHeaders)
+{
+ nsHttpAtom hdr = {0};
+ nsAutoCString val;
+
+ if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(line, &hdr, &val))) {
+ return NS_OK;
+ }
+ nsresult rv;
+ if (originalFromNetHeaders) {
+ rv = mHeaders.SetHeaderFromNet(hdr,
+ val,
+ true);
+ } else {
+ rv = mHeaders.SetResponseHeaderFromCache(hdr,
+ val,
+ nsHttpHeaderArray::eVarietyResponse);
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // leading and trailing LWS has been removed from |val|
+
+ // handle some special case headers...
+ if (hdr == nsHttp::Content_Length) {
+ int64_t len;
+ const char *ignored;
+ // permit only a single value here.
+ if (nsHttp::ParseInt64(val.get(), &ignored, &len)) {
+ mContentLength = len;
+ }
+ else {
+ // If this is a negative content length then just ignore it
+ LOG(("invalid content-length! %s\n", val.get()));
+ }
+ }
+ else if (hdr == nsHttp::Content_Type) {
+ LOG(("ParseContentType [type=%s]\n", val.get()));
+ bool dummy;
+ net_ParseContentType(val,
+ mContentType, mContentCharset, &dummy);
+ }
+ else if (hdr == nsHttp::Cache_Control)
+ ParseCacheControl(val.get());
+ else if (hdr == nsHttp::Pragma)
+ ParsePragma(val.get());
+ return NS_OK;
+}
+
+// From section 13.2.3 of RFC2616, we compute the current age of a cached
+// response as follows:
+//
+// currentAge = max(max(0, responseTime - dateValue), ageValue)
+// + now - requestTime
+//
+// where responseTime == now
+//
+// This is typically a very small number.
+//
+nsresult
+nsHttpResponseHead::ComputeCurrentAge(uint32_t now,
+ uint32_t requestTime,
+ uint32_t *result)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ uint32_t dateValue;
+ uint32_t ageValue;
+
+ *result = 0;
+
+ if (requestTime > now) {
+ // for calculation purposes lets not allow the request to happen in the future
+ requestTime = now;
+ }
+
+ if (NS_FAILED(GetDateValue_locked(&dateValue))) {
+ LOG(("nsHttpResponseHead::ComputeCurrentAge [this=%p] "
+ "Date response header not set!\n", this));
+ // Assume we have a fast connection and that our clock
+ // is in sync with the server.
+ dateValue = now;
+ }
+
+ // Compute apparent age
+ if (now > dateValue)
+ *result = now - dateValue;
+
+ // Compute corrected received age
+ if (NS_SUCCEEDED(GetAgeValue_locked(&ageValue)))
+ *result = std::max(*result, ageValue);
+
+ // Compute current age
+ *result += (now - requestTime);
+ return NS_OK;
+}
+
+// From section 13.2.4 of RFC2616, we compute the freshness lifetime of a cached
+// response as follows:
+//
+// freshnessLifetime = max_age_value
+// <or>
+// freshnessLifetime = expires_value - date_value
+// <or>
+// freshnessLifetime = min(one-week,(date_value - last_modified_value) * 0.10)
+// <or>
+// freshnessLifetime = 0
+//
+nsresult
+nsHttpResponseHead::ComputeFreshnessLifetime(uint32_t *result)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ *result = 0;
+
+ // Try HTTP/1.1 style max-age directive...
+ if (NS_SUCCEEDED(GetMaxAgeValue_locked(result)))
+ return NS_OK;
+
+ *result = 0;
+
+ uint32_t date = 0, date2 = 0;
+ if (NS_FAILED(GetDateValue_locked(&date)))
+ date = NowInSeconds(); // synthesize a date header if none exists
+
+ // Try HTTP/1.0 style expires header...
+ if (NS_SUCCEEDED(GetExpiresValue_locked(&date2))) {
+ if (date2 > date)
+ *result = date2 - date;
+ // the Expires header can specify a date in the past.
+ return NS_OK;
+ }
+
+ // These responses can be cached indefinitely.
+ if ((mStatus == 300) || (mStatus == 410) || nsHttp::IsPermanentRedirect(mStatus)) {
+ LOG(("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
+ "Assign an infinite heuristic lifetime\n", this));
+ *result = uint32_t(-1);
+ return NS_OK;
+ }
+
+ if (mStatus >= 400) {
+ LOG(("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
+ "Do not calculate heuristic max-age for most responses >= 400\n", this));
+ return NS_OK;
+ }
+
+ // Fallback on heuristic using last modified header...
+ if (NS_SUCCEEDED(GetLastModifiedValue_locked(&date2))) {
+ LOG(("using last-modified to determine freshness-lifetime\n"));
+ LOG(("last-modified = %u, date = %u\n", date2, date));
+ if (date2 <= date) {
+ // this only makes sense if last-modified is actually in the past
+ *result = (date - date2) / 10;
+ const uint32_t kOneWeek = 60 * 60 * 24 * 7;
+ *result = std::min(kOneWeek, *result);
+ return NS_OK;
+ }
+ }
+
+ LOG(("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
+ "Insufficient information to compute a non-zero freshness "
+ "lifetime!\n", this));
+
+ return NS_OK;
+}
+
+bool
+nsHttpResponseHead::MustValidate()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ LOG(("nsHttpResponseHead::MustValidate ??\n"));
+
+ // Some response codes are cacheable, but the rest are not. This switch
+ // should stay in sync with the list in nsHttpChannel::ProcessResponse
+ switch (mStatus) {
+ // Success codes
+ case 200:
+ case 203:
+ case 206:
+ // Cacheable redirects
+ case 300:
+ case 301:
+ case 302:
+ case 304:
+ case 307:
+ case 308:
+ // Gone forever
+ case 410:
+ break;
+ // Uncacheable redirects
+ case 303:
+ case 305:
+ // Other known errors
+ case 401:
+ case 407:
+ case 412:
+ case 416:
+ default: // revalidate unknown error pages
+ LOG(("Must validate since response is an uncacheable error page\n"));
+ return true;
+ }
+
+ // The no-cache response header indicates that we must validate this
+ // cached response before reusing.
+ if (mCacheControlNoCache || mPragmaNoCache) {
+ LOG(("Must validate since response contains 'no-cache' header\n"));
+ return true;
+ }
+
+ // Likewise, if the response is no-store, then we must validate this
+ // cached response before reusing. NOTE: it may seem odd that a no-store
+ // response may be cached, but indeed all responses are cached in order
+ // to support File->SaveAs, View->PageSource, and other browser features.
+ if (mCacheControlNoStore) {
+ LOG(("Must validate since response contains 'no-store' header\n"));
+ return true;
+ }
+
+ // Compare the Expires header to the Date header. If the server sent an
+ // Expires header with a timestamp in the past, then we must validate this
+ // cached response before reusing.
+ if (ExpiresInPast_locked()) {
+ LOG(("Must validate since Expires < Date\n"));
+ return true;
+ }
+
+ LOG(("no mandatory validation requirement\n"));
+ return false;
+}
+
+bool
+nsHttpResponseHead::MustValidateIfExpired()
+{
+ // according to RFC2616, section 14.9.4:
+ //
+ // When the must-revalidate directive is present in a response received by a
+ // cache, that cache MUST NOT use the entry after it becomes stale to respond to
+ // a subsequent request without first revalidating it with the origin server.
+ //
+ return HasHeaderValue(nsHttp::Cache_Control, "must-revalidate");
+}
+
+bool
+nsHttpResponseHead::IsResumable()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ // even though some HTTP/1.0 servers may support byte range requests, we're not
+ // going to bother with them, since those servers wouldn't understand If-Range.
+ // Also, while in theory it may be possible to resume when the status code
+ // is not 200, it is unlikely to be worth the trouble, especially for
+ // non-2xx responses.
+ return mStatus == 200 &&
+ mVersion >= NS_HTTP_VERSION_1_1 &&
+ mHeaders.PeekHeader(nsHttp::Content_Length) &&
+ (mHeaders.PeekHeader(nsHttp::ETag) ||
+ mHeaders.PeekHeader(nsHttp::Last_Modified)) &&
+ mHeaders.HasHeaderValue(nsHttp::Accept_Ranges, "bytes");
+}
+
+bool
+nsHttpResponseHead::ExpiresInPast()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return ExpiresInPast_locked();
+}
+
+bool
+nsHttpResponseHead::ExpiresInPast_locked() const
+{
+ uint32_t maxAgeVal, expiresVal, dateVal;
+
+ // Bug #203271. Ensure max-age directive takes precedence over Expires
+ if (NS_SUCCEEDED(GetMaxAgeValue_locked(&maxAgeVal))) {
+ return false;
+ }
+
+ return NS_SUCCEEDED(GetExpiresValue_locked(&expiresVal)) &&
+ NS_SUCCEEDED(GetDateValue_locked(&dateVal)) &&
+ expiresVal < dateVal;
+}
+
+nsresult
+nsHttpResponseHead::UpdateHeaders(nsHttpResponseHead *aOther)
+{
+ LOG(("nsHttpResponseHead::UpdateHeaders [this=%p]\n", this));
+
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ ReentrantMonitorAutoEnter monitorOther(aOther->mReentrantMonitor);
+
+ uint32_t i, count = aOther->mHeaders.Count();
+ for (i=0; i<count; ++i) {
+ nsHttpAtom header;
+ const char *val = aOther->mHeaders.PeekHeaderAt(i, header);
+
+ if (!val) {
+ continue;
+ }
+
+ // Ignore any hop-by-hop headers...
+ if (header == nsHttp::Connection ||
+ header == nsHttp::Proxy_Connection ||
+ header == nsHttp::Keep_Alive ||
+ header == nsHttp::Proxy_Authenticate ||
+ header == nsHttp::Proxy_Authorization || // not a response header!
+ header == nsHttp::TE ||
+ header == nsHttp::Trailer ||
+ header == nsHttp::Transfer_Encoding ||
+ header == nsHttp::Upgrade ||
+ // Ignore any non-modifiable headers...
+ header == nsHttp::Content_Location ||
+ header == nsHttp::Content_MD5 ||
+ header == nsHttp::ETag ||
+ // Assume Cache-Control: "no-transform"
+ header == nsHttp::Content_Encoding ||
+ header == nsHttp::Content_Range ||
+ header == nsHttp::Content_Type ||
+ // Ignore wacky headers too...
+ // this one is for MS servers that send "Content-Length: 0"
+ // on 304 responses
+ header == nsHttp::Content_Length) {
+ LOG(("ignoring response header [%s: %s]\n", header.get(), val));
+ }
+ else {
+ LOG(("new response header [%s: %s]\n", header.get(), val));
+
+ // overwrite the current header value with the new value...
+ SetHeader_locked(header, nsDependentCString(val));
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsHttpResponseHead::Reset()
+{
+ LOG(("nsHttpResponseHead::Reset\n"));
+
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+
+ mHeaders.Clear();
+
+ mVersion = NS_HTTP_VERSION_1_1;
+ mStatus = 200;
+ mContentLength = -1;
+ mCacheControlPrivate = false;
+ mCacheControlNoStore = false;
+ mCacheControlNoCache = false;
+ mCacheControlImmutable = false;
+ mPragmaNoCache = false;
+ mStatusText.Truncate();
+ mContentType.Truncate();
+ mContentCharset.Truncate();
+}
+
+nsresult
+nsHttpResponseHead::ParseDateHeader(nsHttpAtom header, uint32_t *result) const
+{
+ const char *val = mHeaders.PeekHeader(header);
+ if (!val)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ PRTime time;
+ PRStatus st = PR_ParseTimeString(val, true, &time);
+ if (st != PR_SUCCESS)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *result = PRTimeToSeconds(time);
+ return NS_OK;
+}
+
+nsresult
+nsHttpResponseHead::GetAgeValue(uint32_t *result)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return GetAgeValue_locked(result);
+}
+
+nsresult
+nsHttpResponseHead::GetAgeValue_locked(uint32_t *result) const
+{
+ const char *val = mHeaders.PeekHeader(nsHttp::Age);
+ if (!val)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *result = (uint32_t) atoi(val);
+ return NS_OK;
+}
+
+// Return the value of the (HTTP 1.1) max-age directive, which itself is a
+// component of the Cache-Control response header
+nsresult
+nsHttpResponseHead::GetMaxAgeValue(uint32_t *result)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return GetMaxAgeValue_locked(result);
+}
+
+nsresult
+nsHttpResponseHead::GetMaxAgeValue_locked(uint32_t *result) const
+{
+ const char *val = mHeaders.PeekHeader(nsHttp::Cache_Control);
+ if (!val)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ const char *p = nsHttp::FindToken(val, "max-age", HTTP_HEADER_VALUE_SEPS "=");
+ if (!p)
+ return NS_ERROR_NOT_AVAILABLE;
+ p += 7;
+ while (*p == ' ' || *p == '\t')
+ ++p;
+ if (*p != '=')
+ return NS_ERROR_NOT_AVAILABLE;
+ ++p;
+ while (*p == ' ' || *p == '\t')
+ ++p;
+
+ int maxAgeValue = atoi(p);
+ if (maxAgeValue < 0)
+ maxAgeValue = 0;
+ *result = static_cast<uint32_t>(maxAgeValue);
+ return NS_OK;
+}
+
+nsresult
+nsHttpResponseHead::GetDateValue(uint32_t *result)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return GetDateValue_locked(result);
+}
+
+nsresult
+nsHttpResponseHead::GetExpiresValue(uint32_t *result)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return GetExpiresValue_locked(result);
+}
+
+nsresult
+nsHttpResponseHead::GetExpiresValue_locked(uint32_t *result) const
+{
+ const char *val = mHeaders.PeekHeader(nsHttp::Expires);
+ if (!val)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ PRTime time;
+ PRStatus st = PR_ParseTimeString(val, true, &time);
+ if (st != PR_SUCCESS) {
+ // parsing failed... RFC 2616 section 14.21 says we should treat this
+ // as an expiration time in the past.
+ *result = 0;
+ return NS_OK;
+ }
+
+ if (time < 0)
+ *result = 0;
+ else
+ *result = PRTimeToSeconds(time);
+ return NS_OK;
+}
+
+nsresult
+nsHttpResponseHead::GetLastModifiedValue(uint32_t *result)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return ParseDateHeader(nsHttp::Last_Modified, result);
+}
+
+bool
+nsHttpResponseHead::operator==(const nsHttpResponseHead& aOther) const
+{
+ nsHttpResponseHead &curr = const_cast<nsHttpResponseHead&>(*this);
+ nsHttpResponseHead &other = const_cast<nsHttpResponseHead&>(aOther);
+ ReentrantMonitorAutoEnter monitorOther(other.mReentrantMonitor);
+ ReentrantMonitorAutoEnter monitor(curr.mReentrantMonitor);
+
+ return mHeaders == aOther.mHeaders &&
+ mVersion == aOther.mVersion &&
+ mStatus == aOther.mStatus &&
+ mStatusText == aOther.mStatusText &&
+ mContentLength == aOther.mContentLength &&
+ mContentType == aOther.mContentType &&
+ mContentCharset == aOther.mContentCharset &&
+ mCacheControlPrivate == aOther.mCacheControlPrivate &&
+ mCacheControlNoCache == aOther.mCacheControlNoCache &&
+ mCacheControlNoStore == aOther.mCacheControlNoStore &&
+ mCacheControlImmutable == aOther.mCacheControlImmutable &&
+ mPragmaNoCache == aOther.mPragmaNoCache;
+}
+
+int64_t
+nsHttpResponseHead::TotalEntitySize()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ const char* contentRange = mHeaders.PeekHeader(nsHttp::Content_Range);
+ if (!contentRange)
+ return mContentLength;
+
+ // Total length is after a slash
+ const char* slash = strrchr(contentRange, '/');
+ if (!slash)
+ return -1; // No idea what the length is
+
+ slash++;
+ if (*slash == '*') // Server doesn't know the length
+ return -1;
+
+ int64_t size;
+ if (!nsHttp::ParseInt64(slash, &size))
+ size = UINT64_MAX;
+ return size;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpResponseHead <private>
+//-----------------------------------------------------------------------------
+
+void
+nsHttpResponseHead::ParseVersion(const char *str)
+{
+ // Parse HTTP-Version:: "HTTP" "/" 1*DIGIT "." 1*DIGIT
+
+ LOG(("nsHttpResponseHead::ParseVersion [version=%s]\n", str));
+
+ // make sure we have HTTP at the beginning
+ if (PL_strncasecmp(str, "HTTP", 4) != 0) {
+ if (PL_strncasecmp(str, "ICY ", 4) == 0) {
+ // ShoutCast ICY is HTTP/1.0-like. Assume it is HTTP/1.0.
+ LOG(("Treating ICY as HTTP 1.0\n"));
+ mVersion = NS_HTTP_VERSION_1_0;
+ return;
+ }
+ LOG(("looks like a HTTP/0.9 response\n"));
+ mVersion = NS_HTTP_VERSION_0_9;
+ return;
+ }
+ str += 4;
+
+ if (*str != '/') {
+ LOG(("server did not send a version number; assuming HTTP/1.0\n"));
+ // NCSA/1.5.2 has a bug in which it fails to send a version number
+ // if the request version is HTTP/1.1, so we fall back on HTTP/1.0
+ mVersion = NS_HTTP_VERSION_1_0;
+ return;
+ }
+
+ char *p = PL_strchr(str, '.');
+ if (p == nullptr) {
+ LOG(("mal-formed server version; assuming HTTP/1.0\n"));
+ mVersion = NS_HTTP_VERSION_1_0;
+ return;
+ }
+
+ ++p; // let b point to the minor version
+
+ int major = atoi(str + 1);
+ int minor = atoi(p);
+
+ if ((major > 2) || ((major == 2) && (minor >= 0)))
+ mVersion = NS_HTTP_VERSION_2_0;
+ else if ((major == 1) && (minor >= 1))
+ // at least HTTP/1.1
+ mVersion = NS_HTTP_VERSION_1_1;
+ else
+ // treat anything else as version 1.0
+ mVersion = NS_HTTP_VERSION_1_0;
+}
+
+void
+nsHttpResponseHead::ParseCacheControl(const char *val)
+{
+ if (!(val && *val)) {
+ // clear flags
+ mCacheControlPrivate = false;
+ mCacheControlNoCache = false;
+ mCacheControlNoStore = false;
+ mCacheControlImmutable = false;
+ return;
+ }
+
+ // search header value for occurrence of "private"
+ if (nsHttp::FindToken(val, "private", HTTP_HEADER_VALUE_SEPS))
+ mCacheControlPrivate = true;
+
+ // search header value for occurrence(s) of "no-cache" but ignore
+ // occurrence(s) of "no-cache=blah"
+ if (nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS))
+ mCacheControlNoCache = true;
+
+ // search header value for occurrence of "no-store"
+ if (nsHttp::FindToken(val, "no-store", HTTP_HEADER_VALUE_SEPS))
+ mCacheControlNoStore = true;
+
+ // search header value for occurrence of "immutable"
+ if (nsHttp::FindToken(val, "immutable", HTTP_HEADER_VALUE_SEPS)) {
+ mCacheControlImmutable = true;
+ }
+}
+
+void
+nsHttpResponseHead::ParsePragma(const char *val)
+{
+ LOG(("nsHttpResponseHead::ParsePragma [val=%s]\n", val));
+
+ if (!(val && *val)) {
+ // clear no-cache flag
+ mPragmaNoCache = false;
+ return;
+ }
+
+ // Although 'Pragma: no-cache' is not a standard HTTP response header (it's
+ // a request header), caching is inhibited when this header is present so
+ // as to match existing Navigator behavior.
+ if (nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS))
+ mPragmaNoCache = true;
+}
+
+nsresult
+nsHttpResponseHead::VisitHeaders(nsIHttpHeaderVisitor *visitor,
+ nsHttpHeaderArray::VisitorFilter filter)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ mInVisitHeaders = true;
+ nsresult rv = mHeaders.VisitHeaders(visitor, filter);
+ mInVisitHeaders = false;
+ return rv;
+}
+
+nsresult
+nsHttpResponseHead::GetOriginalHeader(nsHttpAtom aHeader,
+ nsIHttpHeaderVisitor *aVisitor)
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ mInVisitHeaders = true;
+ nsresult rv = mHeaders.GetOriginalHeader(aHeader, aVisitor);
+ mInVisitHeaders = false;
+ return rv;
+}
+
+bool
+nsHttpResponseHead::HasContentType()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return !mContentType.IsEmpty();
+}
+
+bool
+nsHttpResponseHead::HasContentCharset()
+{
+ ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
+ return !mContentCharset.IsEmpty();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpResponseHead.h b/netwerk/protocol/http/nsHttpResponseHead.h
new file mode 100644
index 000000000..0a912f4b4
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpResponseHead.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpResponseHead_h__
+#define nsHttpResponseHead_h__
+
+#include "nsHttpHeaderArray.h"
+#include "nsHttp.h"
+#include "nsString.h"
+#include "mozilla/ReentrantMonitor.h"
+
+class nsIHttpHeaderVisitor;
+
+// This needs to be forward declared here so we can include only this header
+// without also including PHttpChannelParams.h
+namespace IPC {
+ template <typename> struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla { namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpResponseHead represents the status line and headers from an HTTP
+// response.
+//-----------------------------------------------------------------------------
+
+class nsHttpResponseHead
+{
+public:
+ nsHttpResponseHead() : mVersion(NS_HTTP_VERSION_1_1)
+ , mStatus(200)
+ , mContentLength(-1)
+ , mCacheControlPrivate(false)
+ , mCacheControlNoStore(false)
+ , mCacheControlNoCache(false)
+ , mCacheControlImmutable(false)
+ , mPragmaNoCache(false)
+ , mReentrantMonitor("nsHttpResponseHead.mReentrantMonitor")
+ , mInVisitHeaders(false) {}
+
+ nsHttpResponseHead(const nsHttpResponseHead &aOther);
+ nsHttpResponseHead &operator=(const nsHttpResponseHead &aOther);
+
+ void Enter() { mReentrantMonitor.Enter(); }
+ void Exit() { mReentrantMonitor.Exit(); }
+
+ nsHttpVersion Version();
+// X11's Xlib.h #defines 'Status' to 'int' on some systems!
+#undef Status
+ uint16_t Status();
+ void StatusText(nsACString &aStatusText);
+ int64_t ContentLength();
+ void ContentType(nsACString &aContentType);
+ void ContentCharset(nsACString &aContentCharset);
+ bool Private();
+ bool NoStore();
+ bool NoCache();
+ bool Immutable();
+ /**
+ * Full length of the entity. For byte-range requests, this may be larger
+ * than ContentLength(), which will only represent the requested part of the
+ * entity.
+ */
+ int64_t TotalEntitySize();
+
+ nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m=false);
+ nsresult GetHeader(nsHttpAtom h, nsACString &v);
+ void ClearHeader(nsHttpAtom h);
+ void ClearHeaders();
+ bool HasHeaderValue(nsHttpAtom h, const char *v);
+ bool HasHeader(nsHttpAtom h);
+
+ void SetContentType(const nsACString &s);
+ void SetContentCharset(const nsACString &s);
+ void SetContentLength(int64_t);
+
+ // write out the response status line and headers as a single text block,
+ // optionally pruning out transient headers (ie. headers that only make
+ // sense the first time the response is handled).
+ // Both functions append to the string supplied string.
+ void Flatten(nsACString &, bool pruneTransients);
+ void FlattenNetworkOriginalHeaders(nsACString &buf);
+
+ // The next 2 functions parse flattened response head and original net headers.
+ // They are used when we are reading an entry from the cache.
+ //
+ // To keep proper order of the original headers we MUST call
+ // ParseCachedOriginalHeaders FIRST and then ParseCachedHead.
+ //
+ // block must be null terminated.
+ nsresult ParseCachedHead(const char *block);
+ nsresult ParseCachedOriginalHeaders(char *block);
+
+ // parse the status line.
+ void ParseStatusLine(const nsACString &line);
+
+ // parse a header line.
+ nsresult ParseHeaderLine(const nsACString &line);
+
+ // cache validation support methods
+ nsresult ComputeFreshnessLifetime(uint32_t *);
+ nsresult ComputeCurrentAge(uint32_t now, uint32_t requestTime,
+ uint32_t *result);
+ bool MustValidate();
+ bool MustValidateIfExpired();
+
+ // returns true if the server appears to support byte range requests.
+ bool IsResumable();
+
+ // returns true if the Expires header has a value in the past relative to the
+ // value of the Date header.
+ bool ExpiresInPast();
+
+ // update headers...
+ nsresult UpdateHeaders(nsHttpResponseHead *headers);
+
+ // reset the response head to it's initial state
+ void Reset();
+
+ nsresult GetAgeValue(uint32_t *result);
+ nsresult GetMaxAgeValue(uint32_t *result);
+ nsresult GetDateValue(uint32_t *result);
+ nsresult GetExpiresValue(uint32_t *result);
+ nsresult GetLastModifiedValue(uint32_t *result);
+
+ bool operator==(const nsHttpResponseHead& aOther) const;
+
+ // Using this function it is possible to itereate through all headers
+ // automatically under one lock.
+ nsresult VisitHeaders(nsIHttpHeaderVisitor *visitor,
+ nsHttpHeaderArray::VisitorFilter filter);
+ nsresult GetOriginalHeader(nsHttpAtom aHeader,
+ nsIHttpHeaderVisitor *aVisitor);
+
+ bool HasContentType();
+ bool HasContentCharset();
+private:
+ nsresult SetHeader_locked(nsHttpAtom h, const nsACString &v,
+ bool m=false);
+ void AssignDefaultStatusText();
+ void ParseVersion(const char *);
+ void ParseCacheControl(const char *);
+ void ParsePragma(const char *);
+
+ void ParseStatusLine_locked(const nsACString &line);
+ nsresult ParseHeaderLine_locked(const nsACString &line, bool originalFromNetHeaders);
+
+ // these return failure if the header does not exist.
+ nsresult ParseDateHeader(nsHttpAtom header, uint32_t *result) const;
+
+ bool ExpiresInPast_locked() const;
+ nsresult GetAgeValue_locked(uint32_t *result) const;
+ nsresult GetExpiresValue_locked(uint32_t *result) const;
+ nsresult GetMaxAgeValue_locked(uint32_t *result) const;
+
+ nsresult GetDateValue_locked(uint32_t *result) const
+ {
+ return ParseDateHeader(nsHttp::Date, result);
+ }
+
+ nsresult GetLastModifiedValue_locked(uint32_t *result) const
+ {
+ return ParseDateHeader(nsHttp::Last_Modified, result);
+ }
+
+private:
+ // All members must be copy-constructable and assignable
+ nsHttpHeaderArray mHeaders;
+ nsHttpVersion mVersion;
+ uint16_t mStatus;
+ nsCString mStatusText;
+ int64_t mContentLength;
+ nsCString mContentType;
+ nsCString mContentCharset;
+ bool mCacheControlPrivate;
+ bool mCacheControlNoStore;
+ bool mCacheControlNoCache;
+ bool mCacheControlImmutable;
+ bool mPragmaNoCache;
+
+ // We are using ReentrantMonitor instead of a Mutex because VisitHeader
+ // function calls nsIHttpHeaderVisitor::VisitHeader while under lock.
+ ReentrantMonitor mReentrantMonitor;
+ // During VisitHeader we sould not allow cal to SetHeader.
+ bool mInVisitHeaders;
+
+ friend struct IPC::ParamTraits<nsHttpResponseHead>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpResponseHead_h__
diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp
new file mode 100644
index 000000000..ee3a88489
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -0,0 +1,2461 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "base/basictypes.h"
+
+#include "nsHttpHandler.h"
+#include "nsHttpTransaction.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+#include "nsHttpChunkedDecoder.h"
+#include "nsTransportUtils.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsIPipe.h"
+#include "nsCRT.h"
+#include "mozilla/Tokenizer.h"
+
+#include "nsISeekableStream.h"
+#include "nsMultiplexInputStream.h"
+#include "nsStringStream.h"
+
+#include "nsComponentManagerUtils.h" // do_CreateInstance
+#include "nsServiceManagerUtils.h" // do_GetService
+#include "nsIHttpActivityObserver.h"
+#include "nsSocketTransportService2.h"
+#include "nsICancelable.h"
+#include "nsIEventTarget.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInputStream.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITransport.h"
+#include "nsIOService.h"
+#include "nsIRequestContext.h"
+#include "nsIHttpAuthenticator.h"
+#include <algorithm>
+
+#ifdef MOZ_WIDGET_GONK
+#include "NetStatistics.h"
+#endif
+
+//-----------------------------------------------------------------------------
+
+static NS_DEFINE_CID(kMultiplexInputStream, NS_MULTIPLEXINPUTSTREAM_CID);
+
+// Place a limit on how much non-compliant HTTP can be skipped while
+// looking for a response header
+#define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128)
+
+using namespace mozilla::net;
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// helpers
+//-----------------------------------------------------------------------------
+
+static void
+LogHeaders(const char *lineStart)
+{
+ nsAutoCString buf;
+ char *endOfLine;
+ while ((endOfLine = PL_strstr(lineStart, "\r\n"))) {
+ buf.Assign(lineStart, endOfLine - lineStart);
+ if (PL_strcasestr(buf.get(), "authorization: ") ||
+ PL_strcasestr(buf.get(), "proxy-authorization: ")) {
+ char *p = PL_strchr(PL_strchr(buf.get(), ' ') + 1, ' ');
+ while (p && *++p)
+ *p = '*';
+ }
+ LOG3((" %s\n", buf.get()));
+ lineStart = endOfLine + 2;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction <public>
+//-----------------------------------------------------------------------------
+
+nsHttpTransaction::nsHttpTransaction()
+ : mLock("transaction lock")
+ , mRequestSize(0)
+ , mRequestHead(nullptr)
+ , mResponseHead(nullptr)
+ , mReader(nullptr)
+ , mWriter(nullptr)
+ , mContentLength(-1)
+ , mContentRead(0)
+ , mTransferSize(0)
+ , mInvalidResponseBytesRead(0)
+ , mPushedStream(nullptr)
+ , mInitialRwin(0)
+ , mChunkedDecoder(nullptr)
+ , mStatus(NS_OK)
+ , mPriority(0)
+ , mRestartCount(0)
+ , mCaps(0)
+ , mClassification(CLASS_GENERAL)
+ , mPipelinePosition(0)
+ , mHttpVersion(NS_HTTP_VERSION_UNKNOWN)
+ , mHttpResponseCode(0)
+ , mCurrentHttpResponseHeaderSize(0)
+ , mCapsToClear(0)
+ , mResponseIsComplete(false)
+ , mClosed(false)
+ , mConnected(false)
+ , mHaveStatusLine(false)
+ , mHaveAllHeaders(false)
+ , mTransactionDone(false)
+ , mDidContentStart(false)
+ , mNoContent(false)
+ , mSentData(false)
+ , mReceivedData(false)
+ , mStatusEventPending(false)
+ , mHasRequestBody(false)
+ , mProxyConnectFailed(false)
+ , mHttpResponseMatched(false)
+ , mPreserveStream(false)
+ , mDispatchedAsBlocking(false)
+ , mResponseTimeoutEnabled(true)
+ , mForceRestart(false)
+ , mReuseOnRestart(false)
+ , mContentDecoding(false)
+ , mContentDecodingCheck(false)
+ , mDeferredSendProgress(false)
+ , mWaitingOnPipeOut(false)
+ , mReportedStart(false)
+ , mReportedResponseHeader(false)
+ , mForTakeResponseHead(nullptr)
+ , mResponseHeadTaken(false)
+ , mSubmittedRatePacing(false)
+ , mPassedRatePacing(false)
+ , mSynchronousRatePaceRequest(false)
+ , mCountRecv(0)
+ , mCountSent(0)
+ , mAppId(NECKO_NO_APP_ID)
+ , mIsInIsolatedMozBrowser(false)
+ , mClassOfService(0)
+ , m0RTTInProgress(false)
+{
+ LOG(("Creating nsHttpTransaction @%p\n", this));
+ gHttpHandler->GetMaxPipelineObjectSize(&mMaxPipelineObjectSize);
+
+#ifdef MOZ_VALGRIND
+ memset(&mSelfAddr, 0, sizeof(NetAddr));
+ memset(&mPeerAddr, 0, sizeof(NetAddr));
+#endif
+ mSelfAddr.raw.family = PR_AF_UNSPEC;
+ mPeerAddr.raw.family = PR_AF_UNSPEC;
+}
+
+nsHttpTransaction::~nsHttpTransaction()
+{
+ LOG(("Destroying nsHttpTransaction @%p\n", this));
+ if (mTransactionObserver) {
+ mTransactionObserver->Complete(this, NS_OK);
+ }
+ if (mPushedStream) {
+ mPushedStream->OnPushFailed();
+ mPushedStream = nullptr;
+ }
+
+ if (mTokenBucketCancel) {
+ mTokenBucketCancel->Cancel(NS_ERROR_ABORT);
+ mTokenBucketCancel = nullptr;
+ }
+
+ // Force the callbacks and connection to be released right now
+ mCallbacks = nullptr;
+ mConnection = nullptr;
+
+ delete mResponseHead;
+ delete mForTakeResponseHead;
+ delete mChunkedDecoder;
+ ReleaseBlockingTransaction();
+}
+
+nsHttpTransaction::Classifier
+nsHttpTransaction::Classify()
+{
+ if (!(mCaps & NS_HTTP_ALLOW_PIPELINING))
+ return (mClassification = CLASS_SOLO);
+
+ if (mRequestHead->HasHeader(nsHttp::If_Modified_Since) ||
+ mRequestHead->HasHeader(nsHttp::If_None_Match))
+ return (mClassification = CLASS_REVALIDATION);
+
+ nsAutoCString accept;
+ bool hasAccept = NS_SUCCEEDED(mRequestHead->GetHeader(nsHttp::Accept, accept));
+ if (hasAccept && StringBeginsWith(accept, NS_LITERAL_CSTRING("image/"))) {
+ return (mClassification = CLASS_IMAGE);
+ }
+
+ if (hasAccept && StringBeginsWith(accept, NS_LITERAL_CSTRING("text/css"))) {
+ return (mClassification = CLASS_SCRIPT);
+ }
+
+ mClassification = CLASS_GENERAL;
+
+ nsAutoCString requestURI;
+ mRequestHead->RequestURI(requestURI);
+ int32_t queryPos = requestURI.FindChar('?');
+ if (queryPos == kNotFound) {
+ if (StringEndsWith(requestURI,
+ NS_LITERAL_CSTRING(".js")))
+ mClassification = CLASS_SCRIPT;
+ }
+ else if (queryPos >= 3 &&
+ Substring(requestURI, queryPos - 3, 3).
+ EqualsLiteral(".js")) {
+ mClassification = CLASS_SCRIPT;
+ }
+
+ return mClassification;
+}
+
+nsresult
+nsHttpTransaction::Init(uint32_t caps,
+ nsHttpConnectionInfo *cinfo,
+ nsHttpRequestHead *requestHead,
+ nsIInputStream *requestBody,
+ bool requestBodyHasHeaders,
+ nsIEventTarget *target,
+ nsIInterfaceRequestor *callbacks,
+ nsITransportEventSink *eventsink,
+ nsIAsyncInputStream **responseBody)
+{
+ nsresult rv;
+
+ LOG(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps));
+
+ MOZ_ASSERT(cinfo);
+ MOZ_ASSERT(requestHead);
+ MOZ_ASSERT(target);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mActivityDistributor = do_GetService(NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ bool activityDistributorActive;
+ rv = mActivityDistributor->GetIsActive(&activityDistributorActive);
+ if (NS_SUCCEEDED(rv) && activityDistributorActive) {
+ // there are some observers registered at activity distributor, gather
+ // nsISupports for the channel that called Init()
+ LOG(("nsHttpTransaction::Init() " \
+ "mActivityDistributor is active " \
+ "this=%p", this));
+ } else {
+ // there is no observer, so don't use it
+ activityDistributorActive = false;
+ mActivityDistributor = nullptr;
+ }
+ mChannel = do_QueryInterface(eventsink);
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(eventsink);
+ if (channel) {
+ NS_GetAppInfo(channel, &mAppId, &mIsInIsolatedMozBrowser);
+ }
+
+#ifdef MOZ_WIDGET_GONK
+ if (mAppId != NECKO_NO_APP_ID) {
+ nsCOMPtr<nsINetworkInfo> activeNetworkInfo;
+ GetActiveNetworkInfo(activeNetworkInfo);
+ mActiveNetworkInfo =
+ new nsMainThreadPtrHolder<nsINetworkInfo>(activeNetworkInfo);
+ }
+#endif
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
+ do_QueryInterface(eventsink);
+ if (httpChannelInternal) {
+ rv = httpChannelInternal->GetResponseTimeoutEnabled(
+ &mResponseTimeoutEnabled);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ httpChannelInternal->GetInitialRwin(&mInitialRwin);
+ }
+
+ // create transport event sink proxy. it coalesces consecutive
+ // events of the same status type.
+ rv = net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink),
+ eventsink, target);
+
+ if (NS_FAILED(rv)) return rv;
+
+ mConnInfo = cinfo;
+ mCallbacks = callbacks;
+ mConsumerTarget = target;
+ mCaps = caps;
+
+ if (requestHead->IsHead()) {
+ mNoContent = true;
+ }
+
+ // Make sure that there is "Content-Length: 0" header in the requestHead
+ // in case of POST and PUT methods when there is no requestBody and
+ // requestHead doesn't contain "Transfer-Encoding" header.
+ //
+ // RFC1945 section 7.2.2:
+ // HTTP/1.0 requests containing an entity body must include a valid
+ // Content-Length header field.
+ //
+ // RFC2616 section 4.4:
+ // For compatibility with HTTP/1.0 applications, HTTP/1.1 requests
+ // containing a message-body MUST include a valid Content-Length header
+ // field unless the server is known to be HTTP/1.1 compliant.
+ if ((requestHead->IsPost() || requestHead->IsPut()) &&
+ !requestBody && !requestHead->HasHeader(nsHttp::Transfer_Encoding)) {
+ requestHead->SetHeader(nsHttp::Content_Length, NS_LITERAL_CSTRING("0"));
+ }
+
+ // grab a weak reference to the request head
+ mRequestHead = requestHead;
+
+ // make sure we eliminate any proxy specific headers from
+ // the request if we are using CONNECT
+ bool pruneProxyHeaders = cinfo->UsingConnect();
+
+ mReqHeaderBuf.Truncate();
+ requestHead->Flatten(mReqHeaderBuf, pruneProxyHeaders);
+
+ if (LOG3_ENABLED()) {
+ LOG3(("http request [\n"));
+ LogHeaders(mReqHeaderBuf.get());
+ LOG3(("]\n"));
+ }
+
+ // If the request body does not include headers or if there is no request
+ // body, then we must add the header/body separator manually.
+ if (!requestBodyHasHeaders || !requestBody)
+ mReqHeaderBuf.AppendLiteral("\r\n");
+
+ // report the request header
+ if (mActivityDistributor)
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER,
+ PR_Now(), 0,
+ mReqHeaderBuf);
+
+ // Create a string stream for the request header buf (the stream holds
+ // a non-owning reference to the request header data, so we MUST keep
+ // mReqHeaderBuf around).
+ nsCOMPtr<nsIInputStream> headers;
+ rv = NS_NewByteInputStream(getter_AddRefs(headers),
+ mReqHeaderBuf.get(),
+ mReqHeaderBuf.Length());
+ if (NS_FAILED(rv)) return rv;
+
+ mHasRequestBody = !!requestBody;
+ if (mHasRequestBody) {
+ // some non standard methods set a 0 byte content-length for
+ // clarity, we can avoid doing the mulitplexed request stream for them
+ uint64_t size;
+ if (NS_SUCCEEDED(requestBody->Available(&size)) && !size) {
+ mHasRequestBody = false;
+ }
+ }
+
+ if (mHasRequestBody) {
+ // wrap the headers and request body in a multiplexed input stream.
+ nsCOMPtr<nsIMultiplexInputStream> multi =
+ do_CreateInstance(kMultiplexInputStream, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = multi->AppendStream(headers);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = multi->AppendStream(requestBody);
+ if (NS_FAILED(rv)) return rv;
+
+ // wrap the multiplexed input stream with a buffered input stream, so
+ // that we write data in the largest chunks possible. this is actually
+ // necessary to workaround some common server bugs (see bug 137155).
+ rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream), multi,
+ nsIOService::gDefaultSegmentSize);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else
+ mRequestStream = headers;
+
+ nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(mChannel);
+ nsIInputChannelThrottleQueue* queue;
+ if (throttled) {
+ rv = throttled->GetThrottleQueue(&queue);
+ // In case of failure, just carry on without throttling.
+ if (NS_SUCCEEDED(rv) && queue) {
+ nsCOMPtr<nsIAsyncInputStream> wrappedStream;
+ rv = queue->WrapStream(mRequestStream, getter_AddRefs(wrappedStream));
+ // Failure to throttle isn't sufficient reason to fail
+ // initialization
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(wrappedStream != nullptr);
+ LOG(("nsHttpTransaction::Init %p wrapping input stream using throttle queue %p\n",
+ this, queue));
+ mRequestStream = do_QueryInterface(wrappedStream);
+ }
+ }
+ }
+
+ uint64_t size_u64;
+ rv = mRequestStream->Available(&size_u64);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // make sure it fits within js MAX_SAFE_INTEGER
+ mRequestSize = InScriptableRange(size_u64) ? static_cast<int64_t>(size_u64) : -1;
+
+ // create pipe for response stream
+ rv = NS_NewPipe2(getter_AddRefs(mPipeIn),
+ getter_AddRefs(mPipeOut),
+ true, true,
+ nsIOService::gDefaultSegmentSize,
+ nsIOService::gDefaultSegmentCount);
+ if (NS_FAILED(rv)) return rv;
+
+#ifdef WIN32 // bug 1153929
+ MOZ_DIAGNOSTIC_ASSERT(mPipeOut);
+ uint32_t * vtable = (uint32_t *) mPipeOut.get();
+ MOZ_DIAGNOSTIC_ASSERT(*vtable != 0);
+#endif // WIN32
+
+ Classify();
+
+ nsCOMPtr<nsIAsyncInputStream> tmp(mPipeIn);
+ tmp.forget(responseBody);
+ return NS_OK;
+}
+
+// This method should only be used on the socket thread
+nsAHttpConnection *
+nsHttpTransaction::Connection()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ return mConnection.get();
+}
+
+already_AddRefed<nsAHttpConnection>
+nsHttpTransaction::GetConnectionReference()
+{
+ MutexAutoLock lock(mLock);
+ RefPtr<nsAHttpConnection> connection(mConnection);
+ return connection.forget();
+}
+
+nsHttpResponseHead *
+nsHttpTransaction::TakeResponseHead()
+{
+ MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x");
+
+ // Lock RestartInProgress() and TakeResponseHead() against main thread
+ MutexAutoLock lock(*nsHttp::GetLock());
+
+ mResponseHeadTaken = true;
+
+ // Prefer mForTakeResponseHead over mResponseHead. It is always a complete
+ // set of headers.
+ nsHttpResponseHead *head;
+ if (mForTakeResponseHead) {
+ head = mForTakeResponseHead;
+ mForTakeResponseHead = nullptr;
+ return head;
+ }
+
+ // Even in OnStartRequest() the headers won't be available if we were
+ // canceled
+ if (!mHaveAllHeaders) {
+ NS_WARNING("response headers not available or incomplete");
+ return nullptr;
+ }
+
+ head = mResponseHead;
+ mResponseHead = nullptr;
+ return head;
+}
+
+void
+nsHttpTransaction::SetProxyConnectFailed()
+{
+ mProxyConnectFailed = true;
+}
+
+nsHttpRequestHead *
+nsHttpTransaction::RequestHead()
+{
+ return mRequestHead;
+}
+
+uint32_t
+nsHttpTransaction::Http1xTransactionCount()
+{
+ return 1;
+}
+
+nsresult
+nsHttpTransaction::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//----------------------------------------------------------------------------
+// nsHttpTransaction::nsAHttpTransaction
+//----------------------------------------------------------------------------
+
+void
+nsHttpTransaction::SetConnection(nsAHttpConnection *conn)
+{
+ {
+ MutexAutoLock lock(mLock);
+ mConnection = conn;
+ }
+}
+
+void
+nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb)
+{
+ MutexAutoLock lock(mLock);
+ nsCOMPtr<nsIInterfaceRequestor> tmp(mCallbacks);
+ tmp.forget(cb);
+}
+
+void
+nsHttpTransaction::SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks)
+{
+ {
+ MutexAutoLock lock(mLock);
+ mCallbacks = aCallbacks;
+ }
+
+ if (gSocketTransportService) {
+ RefPtr<UpdateSecurityCallbacks> event = new UpdateSecurityCallbacks(this, aCallbacks);
+ gSocketTransportService->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+ }
+}
+
+void
+nsHttpTransaction::OnTransportStatus(nsITransport* transport,
+ nsresult status, int64_t progress)
+{
+ LOG(("nsHttpTransaction::OnSocketStatus [this=%p status=%x progress=%lld]\n",
+ this, status, progress));
+
+ if (status == NS_NET_STATUS_CONNECTED_TO ||
+ status == NS_NET_STATUS_WAITING_FOR) {
+ nsISocketTransport *socketTransport =
+ mConnection ? mConnection->Transport() : nullptr;
+ if (socketTransport) {
+ MutexAutoLock lock(mLock);
+ socketTransport->GetSelfAddr(&mSelfAddr);
+ socketTransport->GetPeerAddr(&mPeerAddr);
+ }
+ }
+
+ // If the timing is enabled, and we are not using a persistent connection
+ // then the requestStart timestamp will be null, so we mark the timestamps
+ // for domainLookupStart/End and connectStart/End
+ // If we are using a persistent connection they will remain null,
+ // and the correct value will be returned in Performance.
+ if (TimingEnabled() && GetRequestStart().IsNull()) {
+ if (status == NS_NET_STATUS_RESOLVING_HOST) {
+ SetDomainLookupStart(TimeStamp::Now(), true);
+ } else if (status == NS_NET_STATUS_RESOLVED_HOST) {
+ SetDomainLookupEnd(TimeStamp::Now());
+ } else if (status == NS_NET_STATUS_CONNECTING_TO) {
+ SetConnectStart(TimeStamp::Now());
+ } else if (status == NS_NET_STATUS_CONNECTED_TO) {
+ SetConnectEnd(TimeStamp::Now());
+ }
+ }
+
+ if (!mTransportSink)
+ return;
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // Need to do this before the STATUS_RECEIVING_FROM check below, to make
+ // sure that the activity distributor gets told about all status events.
+ if (mActivityDistributor) {
+ // upon STATUS_WAITING_FOR; report request body sent
+ if ((mHasRequestBody) &&
+ (status == NS_NET_STATUS_WAITING_FOR))
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT,
+ PR_Now(), 0, EmptyCString());
+
+ // report the status and progress
+ if (!mRestartInProgressVerifier.IsDiscardingContent())
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
+ static_cast<uint32_t>(status),
+ PR_Now(),
+ progress,
+ EmptyCString());
+ }
+
+ // nsHttpChannel synthesizes progress events in OnDataAvailable
+ if (status == NS_NET_STATUS_RECEIVING_FROM)
+ return;
+
+ int64_t progressMax;
+
+ if (status == NS_NET_STATUS_SENDING_TO) {
+ // suppress progress when only writing request headers
+ if (!mHasRequestBody) {
+ LOG(("nsHttpTransaction::OnTransportStatus %p "
+ "SENDING_TO without request body\n", this));
+ return;
+ }
+
+ if (mReader) {
+ // A mRequestStream method is on the stack - wait.
+ LOG(("nsHttpTransaction::OnSocketStatus [this=%p] "
+ "Skipping Re-Entrant NS_NET_STATUS_SENDING_TO\n", this));
+ // its ok to coalesce several of these into one deferred event
+ mDeferredSendProgress = true;
+ return;
+ }
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
+ if (!seekable) {
+ LOG(("nsHttpTransaction::OnTransportStatus %p "
+ "SENDING_TO without seekable request stream\n", this));
+ progress = 0;
+ } else {
+ int64_t prog = 0;
+ seekable->Tell(&prog);
+ progress = prog;
+ }
+
+ // when uploading, we include the request headers in the progress
+ // notifications.
+ progressMax = mRequestSize;
+ }
+ else {
+ progress = 0;
+ progressMax = 0;
+ }
+
+ mTransportSink->OnTransportStatus(transport, status, progress, progressMax);
+}
+
+bool
+nsHttpTransaction::IsDone()
+{
+ return mTransactionDone;
+}
+
+nsresult
+nsHttpTransaction::Status()
+{
+ return mStatus;
+}
+
+uint32_t
+nsHttpTransaction::Caps()
+{
+ return mCaps & ~mCapsToClear;
+}
+
+void
+nsHttpTransaction::SetDNSWasRefreshed()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "SetDNSWasRefreshed on main thread only!");
+ mCapsToClear |= NS_HTTP_REFRESH_DNS;
+}
+
+uint64_t
+nsHttpTransaction::Available()
+{
+ uint64_t size;
+ if (NS_FAILED(mRequestStream->Available(&size)))
+ size = 0;
+ return size;
+}
+
+nsresult
+nsHttpTransaction::ReadRequestSegment(nsIInputStream *stream,
+ void *closure,
+ const char *buf,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ nsHttpTransaction *trans = (nsHttpTransaction *) closure;
+ nsresult rv = trans->mReader->OnReadSegment(buf, count, countRead);
+ if (NS_FAILED(rv)) return rv;
+
+ if (trans->TimingEnabled()) {
+ // Set the timestamp to Now(), only if it null
+ trans->SetRequestStart(TimeStamp::Now(), true);
+ }
+
+ trans->CountSentBytes(*countRead);
+ trans->mSentData = true;
+ return NS_OK;
+}
+
+nsresult
+nsHttpTransaction::ReadSegments(nsAHttpSegmentReader *reader,
+ uint32_t count, uint32_t *countRead)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mTransactionDone) {
+ *countRead = 0;
+ return mStatus;
+ }
+
+ if (!mConnected && !m0RTTInProgress) {
+ mConnected = true;
+ mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
+ }
+
+ mDeferredSendProgress = false;
+ mReader = reader;
+ nsresult rv = mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead);
+ mReader = nullptr;
+
+ if (mDeferredSendProgress && mConnection && mConnection->Transport()) {
+ // to avoid using mRequestStream concurrently, OnTransportStatus()
+ // did not report upload status off the ReadSegments() stack from nsSocketTransport
+ // do it now.
+ OnTransportStatus(mConnection->Transport(), NS_NET_STATUS_SENDING_TO, 0);
+ }
+ mDeferredSendProgress = false;
+
+ if (mForceRestart) {
+ // The forceRestart condition was dealt with on the stack, but it did not
+ // clear the flag because nsPipe in the readsegment stack clears out
+ // return codes, so we need to use the flag here as a cue to return ERETARGETED
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_BINDING_RETARGETED;
+ }
+ mForceRestart = false;
+ }
+
+ // if read would block then we need to AsyncWait on the request stream.
+ // have callback occur on socket thread so we stay synchronized.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ nsCOMPtr<nsIAsyncInputStream> asyncIn =
+ do_QueryInterface(mRequestStream);
+ if (asyncIn) {
+ nsCOMPtr<nsIEventTarget> target;
+ gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
+ if (target)
+ asyncIn->AsyncWait(this, 0, 0, target);
+ else {
+ NS_ERROR("no socket thread event target");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsHttpTransaction::WritePipeSegment(nsIOutputStream *stream,
+ void *closure,
+ char *buf,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t *countWritten)
+{
+ nsHttpTransaction *trans = (nsHttpTransaction *) closure;
+
+ if (trans->mTransactionDone)
+ return NS_BASE_STREAM_CLOSED; // stop iterating
+
+ if (trans->TimingEnabled()) {
+ // Set the timestamp to Now(), only if it null
+ trans->SetResponseStart(TimeStamp::Now(), true);
+ }
+
+ // Bug 1153929 - add checks to fix windows crash
+ MOZ_ASSERT(trans->mWriter);
+ if (!trans->mWriter) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+ //
+ // OK, now let the caller fill this segment with data.
+ //
+ rv = trans->mWriter->OnWriteSegment(buf, count, countWritten);
+ if (NS_FAILED(rv)) return rv; // caller didn't want to write anything
+
+ MOZ_ASSERT(*countWritten > 0, "bad writer");
+ trans->CountRecvBytes(*countWritten);
+ trans->mReceivedData = true;
+ trans->mTransferSize += *countWritten;
+
+ // Let the transaction "play" with the buffer. It is free to modify
+ // the contents of the buffer and/or modify countWritten.
+ // - Bytes in HTTP headers don't count towards countWritten, so the input
+ // side of pipe (aka nsHttpChannel's mTransactionPump) won't hit
+ // OnInputStreamReady until all headers have been parsed.
+ //
+ rv = trans->ProcessData(buf, *countWritten, countWritten);
+ if (NS_FAILED(rv))
+ trans->Close(rv);
+
+ return rv; // failure code only stops WriteSegments; it is not propagated.
+}
+
+nsresult
+nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
+ uint32_t count, uint32_t *countWritten)
+{
+ static bool reentrantFlag = false;
+ LOG(("nsHttpTransaction::WriteSegments %p reentrantFlag=%d",
+ this, reentrantFlag));
+ MOZ_DIAGNOSTIC_ASSERT(!reentrantFlag);
+ reentrantFlag = true;
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mTransactionDone) {
+ reentrantFlag = false;
+ return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
+ }
+
+ mWriter = writer;
+
+#ifdef WIN32 // bug 1153929
+ MOZ_DIAGNOSTIC_ASSERT(mPipeOut);
+ uint32_t * vtable = (uint32_t *) mPipeOut.get();
+ MOZ_DIAGNOSTIC_ASSERT(*vtable != 0);
+#endif // WIN32
+
+ if (!mPipeOut) {
+ reentrantFlag = false;
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten);
+
+ mWriter = nullptr;
+
+ if (mForceRestart) {
+ // The forceRestart condition was dealt with on the stack, but it did not
+ // clear the flag because nsPipe in the writesegment stack clears out
+ // return codes, so we need to use the flag here as a cue to return ERETARGETED
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_BINDING_RETARGETED;
+ }
+ mForceRestart = false;
+ }
+
+ // if pipe would block then we need to AsyncWait on it. have callback
+ // occur on socket thread so we stay synchronized.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ nsCOMPtr<nsIEventTarget> target;
+ gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
+ if (target) {
+ mPipeOut->AsyncWait(this, 0, 0, target);
+ mWaitingOnPipeOut = true;
+ } else {
+ NS_ERROR("no socket thread event target");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ reentrantFlag = false;
+ return rv;
+}
+
+nsresult
+nsHttpTransaction::SaveNetworkStats(bool enforce)
+{
+#ifdef MOZ_WIDGET_GONK
+ // Check if active network and appid are valid.
+ if (!mActiveNetworkInfo || mAppId == NECKO_NO_APP_ID) {
+ return NS_OK;
+ }
+
+ if (mCountRecv <= 0 && mCountSent <= 0) {
+ // There is no traffic, no need to save.
+ return NS_OK;
+ }
+
+ // If |enforce| is false, the traffic amount is saved
+ // only when the total amount exceeds the predefined
+ // threshold.
+ uint64_t totalBytes = mCountRecv + mCountSent;
+ if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) {
+ return NS_OK;
+ }
+
+ // Create the event to save the network statistics.
+ // the event is then dispatched to the main thread.
+ RefPtr<Runnable> event =
+ new SaveNetworkStatsEvent(mAppId, mIsInIsolatedMozBrowser, mActiveNetworkInfo,
+ mCountRecv, mCountSent, false);
+ NS_DispatchToMainThread(event);
+
+ // Reset the counters after saving.
+ mCountSent = 0;
+ mCountRecv = 0;
+
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+void
+nsHttpTransaction::Close(nsresult reason)
+{
+ LOG(("nsHttpTransaction::Close [this=%p reason=%x]\n", this, reason));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (reason == NS_BINDING_RETARGETED) {
+ LOG((" close %p skipped due to ERETARGETED\n", this));
+ return;
+ }
+
+ if (mClosed) {
+ LOG((" already closed\n"));
+ return;
+ }
+
+ if (mTransactionObserver) {
+ mTransactionObserver->Complete(this, reason);
+ mTransactionObserver = nullptr;
+ }
+
+ if (mTokenBucketCancel) {
+ mTokenBucketCancel->Cancel(reason);
+ mTokenBucketCancel = nullptr;
+ }
+
+ if (mActivityDistributor) {
+ // report the reponse is complete if not already reported
+ if (!mResponseIsComplete)
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
+ PR_Now(),
+ static_cast<uint64_t>(mContentRead),
+ EmptyCString());
+
+ // report that this transaction is closing
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE,
+ PR_Now(), 0, EmptyCString());
+ }
+
+ // we must no longer reference the connection! find out if the
+ // connection was being reused before letting it go.
+ bool connReused = false;
+ if (mConnection) {
+ connReused = mConnection->IsReused();
+ }
+ mConnected = false;
+ mTunnelProvider = nullptr;
+
+ //
+ // if the connection was reset or closed before we wrote any part of the
+ // request or if we wrote the request but didn't receive any part of the
+ // response and the connection was being reused, then we can (and really
+ // should) assume that we wrote to a stale connection and we must therefore
+ // repeat the request over a new connection.
+ //
+ // We have decided to retry not only in case of the reused connections, but
+ // all safe methods(bug 1236277).
+ //
+ // NOTE: the conditions under which we will automatically retry the HTTP
+ // request have to be carefully selected to avoid duplication of the
+ // request from the point-of-view of the server. such duplication could
+ // have dire consequences including repeated purchases, etc.
+ //
+ // NOTE: because of the way SSL proxy CONNECT is implemented, it is
+ // possible that the transaction may have received data without having
+ // sent any data. for this reason, mSendData == FALSE does not imply
+ // mReceivedData == FALSE. (see bug 203057 for more info.)
+ //
+ // Never restart transactions that are marked as sticky to their conenction.
+ // We use that capability to identify transactions bound to connection based
+ // authentication. Reissuing them on a different connections will break
+ // this bondage. Major issue may arise when there is an NTLM message auth
+ // header on the transaction and we send it to a different NTLM authenticated
+ // connection. It will break that connection and also confuse the channel's
+ // auth provider, beliving the cached credentials are wrong and asking for
+ // the password mistakenly again from the user.
+ if ((reason == NS_ERROR_NET_RESET || reason == NS_OK) &&
+ (!(mCaps & NS_HTTP_STICKY_CONNECTION) || (mCaps & NS_HTTP_CONNECTION_RESTARTABLE))) {
+
+ if (mForceRestart && NS_SUCCEEDED(Restart())) {
+ if (mResponseHead) {
+ mResponseHead->Reset();
+ }
+ mContentRead = 0;
+ mContentLength = -1;
+ delete mChunkedDecoder;
+ mChunkedDecoder = nullptr;
+ mHaveStatusLine = false;
+ mHaveAllHeaders = false;
+ mHttpResponseMatched = false;
+ mResponseIsComplete = false;
+ mDidContentStart = false;
+ mNoContent = false;
+ mSentData = false;
+ mReceivedData = false;
+ LOG(("transaction force restarted\n"));
+ return;
+ }
+
+ // reallySentData is meant to separate the instances where data has
+ // been sent by this transaction but buffered at a higher level while
+ // a TLS session (perhaps via a tunnel) is setup.
+ bool reallySentData =
+ mSentData && (!mConnection || mConnection->BytesWritten());
+
+ if (!mReceivedData &&
+ ((mRequestHead && mRequestHead->IsSafeMethod()) ||
+ !reallySentData || connReused)) {
+ // if restarting fails, then we must proceed to close the pipe,
+ // which will notify the channel that the transaction failed.
+
+ if (mPipelinePosition) {
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::RedCanceledPipeline,
+ nullptr, 0);
+ }
+ if (NS_SUCCEEDED(Restart()))
+ return;
+ }
+ else if (!mResponseIsComplete && mPipelinePosition &&
+ reason == NS_ERROR_NET_RESET) {
+ // due to unhandled rst on a pipeline - safe to
+ // restart as only idempotent is found there
+
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::RedCorruptedContent, nullptr, 0);
+ if (NS_SUCCEEDED(RestartInProgress()))
+ return;
+ }
+ }
+
+ if ((mChunkedDecoder || (mContentLength >= int64_t(0))) &&
+ (NS_SUCCEEDED(reason) && !mResponseIsComplete)) {
+
+ NS_WARNING("Partial transfer, incomplete HTTP response received");
+
+ if ((mHttpResponseCode / 100 == 2) &&
+ (mHttpVersion >= NS_HTTP_VERSION_1_1)) {
+ FrameCheckLevel clevel = gHttpHandler->GetEnforceH1Framing();
+ if (clevel >= FRAMECHECK_BARELY) {
+ if ((clevel == FRAMECHECK_STRICT) ||
+ (mChunkedDecoder && mChunkedDecoder->GetChunkRemaining()) ||
+ (!mChunkedDecoder && !mContentDecoding && mContentDecodingCheck) ) {
+ reason = NS_ERROR_NET_PARTIAL_TRANSFER;
+ LOG(("Partial transfer, incomplete HTTP response received: %s",
+ mChunkedDecoder ? "broken chunk" : "c-l underrun"));
+ }
+ }
+ }
+
+ if (mConnection) {
+ // whether or not we generate an error for the transaction
+ // bad framing means we don't want a pconn
+ mConnection->DontReuse();
+ }
+ }
+
+ bool relConn = true;
+ if (NS_SUCCEEDED(reason)) {
+ if (!mResponseIsComplete) {
+ // The response has not been delimited with a high-confidence
+ // algorithm like Content-Length or Chunked Encoding. We
+ // need to use a strong framing mechanism to pipeline.
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming,
+ nullptr, mClassification);
+ }
+ else if (mPipelinePosition) {
+ // report this success as feedback
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::GoodCompletedOK,
+ nullptr, mPipelinePosition);
+ }
+
+ // the server has not sent the final \r\n terminating the header
+ // section, and there may still be a header line unparsed. let's make
+ // sure we parse the remaining header line, and then hopefully, the
+ // response will be usable (see bug 88792).
+ if (!mHaveAllHeaders) {
+ char data = '\n';
+ uint32_t unused;
+ ParseHead(&data, 1, &unused);
+
+ if (mResponseHead->Version() == NS_HTTP_VERSION_0_9) {
+ // Reject 0 byte HTTP/0.9 Responses - bug 423506
+ LOG(("nsHttpTransaction::Close %p 0 Byte 0.9 Response", this));
+ reason = NS_ERROR_NET_RESET;
+ }
+ }
+
+ // honor the sticky connection flag...
+ if (mCaps & NS_HTTP_STICKY_CONNECTION)
+ relConn = false;
+ }
+
+ // mTimings.responseEnd is normally recorded based on the end of a
+ // HTTP delimiter such as chunked-encodings or content-length. However,
+ // EOF or an error still require an end time be recorded.
+ if (TimingEnabled()) {
+ const TimingStruct timings = Timings();
+ if (timings.responseEnd.IsNull() && !timings.responseStart.IsNull()) {
+ SetResponseEnd(TimeStamp::Now());
+ }
+ }
+
+ if (relConn && mConnection) {
+ MutexAutoLock lock(mLock);
+ mConnection = nullptr;
+ }
+
+ // save network statistics in the end of transaction
+ SaveNetworkStats(true);
+
+ mStatus = reason;
+ mTransactionDone = true; // forcibly flag the transaction as complete
+ mClosed = true;
+ ReleaseBlockingTransaction();
+
+ // release some resources that we no longer need
+ mRequestStream = nullptr;
+ mReqHeaderBuf.Truncate();
+ mLineBuf.Truncate();
+ if (mChunkedDecoder) {
+ delete mChunkedDecoder;
+ mChunkedDecoder = nullptr;
+ }
+
+ // closing this pipe triggers the channel's OnStopRequest method.
+ mPipeOut->CloseWithStatus(reason);
+
+#ifdef WIN32 // bug 1153929
+ MOZ_DIAGNOSTIC_ASSERT(mPipeOut);
+ uint32_t * vtable = (uint32_t *) mPipeOut.get();
+ MOZ_DIAGNOSTIC_ASSERT(*vtable != 0);
+ mPipeOut = nullptr; // just in case
+#endif // WIN32
+}
+
+nsHttpConnectionInfo *
+nsHttpTransaction::ConnectionInfo()
+{
+ return mConnInfo.get();
+}
+
+nsresult
+nsHttpTransaction::AddTransaction(nsAHttpTransaction *trans)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+nsHttpTransaction::PipelineDepth()
+{
+ return IsDone() ? 0 : 1;
+}
+
+nsresult
+nsHttpTransaction::SetPipelinePosition(int32_t position)
+{
+ mPipelinePosition = position;
+ return NS_OK;
+}
+
+int32_t
+nsHttpTransaction::PipelinePosition()
+{
+ return mPipelinePosition;
+}
+
+bool // NOTE BASE CLASS
+nsAHttpTransaction::ResponseTimeoutEnabled() const
+{
+ return false;
+}
+
+PRIntervalTime // NOTE BASE CLASS
+nsAHttpTransaction::ResponseTimeout()
+{
+ return gHttpHandler->ResponseTimeout();
+}
+
+bool
+nsHttpTransaction::ResponseTimeoutEnabled() const
+{
+ return mResponseTimeoutEnabled;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction <private>
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpTransaction::RestartInProgress()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if ((mRestartCount + 1) >= gHttpHandler->MaxRequestAttempts()) {
+ LOG(("nsHttpTransaction::RestartInProgress() "
+ "reached max request attempts, failing transaction %p\n", this));
+ return NS_ERROR_NET_RESET;
+ }
+
+ // Lock RestartInProgress() and TakeResponseHead() against main thread
+ MutexAutoLock lock(*nsHttp::GetLock());
+
+ // Don't try and RestartInProgress() things that haven't gotten a response
+ // header yet. Those should be handled under the normal restart() path if
+ // they are eligible.
+ if (!mHaveAllHeaders)
+ return NS_ERROR_NET_RESET;
+
+ if (mCaps & NS_HTTP_STICKY_CONNECTION) {
+ return NS_ERROR_NET_RESET;
+ }
+
+ // don't try and restart 0.9 or non 200/Get HTTP/1
+ if (!mRestartInProgressVerifier.IsSetup())
+ return NS_ERROR_NET_RESET;
+
+ LOG(("Will restart transaction %p and skip first %lld bytes, "
+ "old Content-Length %lld",
+ this, mContentRead, mContentLength));
+
+ mRestartInProgressVerifier.SetAlreadyProcessed(
+ std::max(mRestartInProgressVerifier.AlreadyProcessed(), mContentRead));
+
+ if (!mResponseHeadTaken && !mForTakeResponseHead) {
+ // TakeResponseHeader() has not been called yet and this
+ // is the first restart. Store the resp headers exclusively
+ // for TakeResponseHead() which is called from the main thread and
+ // could happen at any time - so we can't continue to modify those
+ // headers (which restarting will effectively do)
+ mForTakeResponseHead = mResponseHead;
+ mResponseHead = nullptr;
+ }
+
+ if (mResponseHead) {
+ mResponseHead->Reset();
+ }
+
+ mContentRead = 0;
+ mContentLength = -1;
+ delete mChunkedDecoder;
+ mChunkedDecoder = nullptr;
+ mHaveStatusLine = false;
+ mHaveAllHeaders = false;
+ mHttpResponseMatched = false;
+ mResponseIsComplete = false;
+ mDidContentStart = false;
+ mNoContent = false;
+ mSentData = false;
+ mReceivedData = false;
+
+ return Restart();
+}
+
+nsresult
+nsHttpTransaction::Restart()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // limit the number of restart attempts - bug 92224
+ if (++mRestartCount >= gHttpHandler->MaxRequestAttempts()) {
+ LOG(("reached max request attempts, failing transaction @%p\n", this));
+ return NS_ERROR_NET_RESET;
+ }
+
+ LOG(("restarting transaction @%p\n", this));
+ mTunnelProvider = nullptr;
+
+ // rewind streams in case we already wrote out the request
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
+ if (seekable)
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+
+ // clear old connection state...
+ mSecurityInfo = nullptr;
+ if (mConnection) {
+ if (!mReuseOnRestart) {
+ mConnection->DontReuse();
+ }
+ MutexAutoLock lock(mLock);
+ mConnection = nullptr;
+ }
+
+ // Reset this to our default state, since this may change from one restart
+ // to the next
+ mReuseOnRestart = false;
+
+ // disable pipelining for the next attempt in case pipelining caused the
+ // reset. this is being overly cautious since we don't know if pipelining
+ // was the problem here.
+ mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
+ SetPipelinePosition(0);
+
+ if (!mConnInfo->GetRoutedHost().IsEmpty()) {
+ MutexAutoLock lock(*nsHttp::GetLock());
+ RefPtr<nsHttpConnectionInfo> ci;
+ mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
+ mConnInfo = ci;
+ if (mRequestHead) {
+ mRequestHead->SetHeader(nsHttp::Alternate_Service_Used, NS_LITERAL_CSTRING("0"));
+ }
+ }
+
+ return gHttpHandler->InitiateTransaction(this, mPriority);
+}
+
+char *
+nsHttpTransaction::LocateHttpStart(char *buf, uint32_t len,
+ bool aAllowPartialMatch)
+{
+ MOZ_ASSERT(!aAllowPartialMatch || mLineBuf.IsEmpty());
+
+ static const char HTTPHeader[] = "HTTP/1.";
+ static const uint32_t HTTPHeaderLen = sizeof(HTTPHeader) - 1;
+ static const char HTTP2Header[] = "HTTP/2.0";
+ static const uint32_t HTTP2HeaderLen = sizeof(HTTP2Header) - 1;
+ // ShoutCast ICY is treated as HTTP/1.0
+ static const char ICYHeader[] = "ICY ";
+ static const uint32_t ICYHeaderLen = sizeof(ICYHeader) - 1;
+
+ if (aAllowPartialMatch && (len < HTTPHeaderLen))
+ return (PL_strncasecmp(buf, HTTPHeader, len) == 0) ? buf : nullptr;
+
+ // mLineBuf can contain partial match from previous search
+ if (!mLineBuf.IsEmpty()) {
+ MOZ_ASSERT(mLineBuf.Length() < HTTPHeaderLen);
+ int32_t checkChars = std::min(len, HTTPHeaderLen - mLineBuf.Length());
+ if (PL_strncasecmp(buf, HTTPHeader + mLineBuf.Length(),
+ checkChars) == 0) {
+ mLineBuf.Append(buf, checkChars);
+ if (mLineBuf.Length() == HTTPHeaderLen) {
+ // We've found whole HTTPHeader sequence. Return pointer at the
+ // end of matched sequence since it is stored in mLineBuf.
+ return (buf + checkChars);
+ }
+ // Response matches pattern but is still incomplete.
+ return 0;
+ }
+ // Previous partial match together with new data doesn't match the
+ // pattern. Start the search again.
+ mLineBuf.Truncate();
+ }
+
+ bool firstByte = true;
+ while (len > 0) {
+ if (PL_strncasecmp(buf, HTTPHeader, std::min<uint32_t>(len, HTTPHeaderLen)) == 0) {
+ if (len < HTTPHeaderLen) {
+ // partial HTTPHeader sequence found
+ // save partial match to mLineBuf
+ mLineBuf.Assign(buf, len);
+ return 0;
+ }
+
+ // whole HTTPHeader sequence found
+ return buf;
+ }
+
+ // At least "SmarterTools/2.0.3974.16813" generates nonsensical
+ // HTTP/2.0 responses to our HTTP/1 requests. Treat the minimal case of
+ // it as HTTP/1.1 to be compatible with old versions of ourselves and
+ // other browsers
+
+ if (firstByte && !mInvalidResponseBytesRead && len >= HTTP2HeaderLen &&
+ (PL_strncasecmp(buf, HTTP2Header, HTTP2HeaderLen) == 0)) {
+ LOG(("nsHttpTransaction:: Identified HTTP/2.0 treating as 1.x\n"));
+ return buf;
+ }
+
+ // Treat ICY (AOL/Nullsoft ShoutCast) non-standard header in same fashion
+ // as HTTP/2.0 is treated above. This will allow "ICY " to be interpretted
+ // as HTTP/1.0 in nsHttpResponseHead::ParseVersion
+
+ if (firstByte && !mInvalidResponseBytesRead && len >= ICYHeaderLen &&
+ (PL_strncasecmp(buf, ICYHeader, ICYHeaderLen) == 0)) {
+ LOG(("nsHttpTransaction:: Identified ICY treating as HTTP/1.0\n"));
+ return buf;
+ }
+
+ if (!nsCRT::IsAsciiSpace(*buf))
+ firstByte = false;
+ buf++;
+ len--;
+ }
+ return 0;
+}
+
+nsresult
+nsHttpTransaction::ParseLine(nsACString &line)
+{
+ LOG(("nsHttpTransaction::ParseLine [%s]\n", PromiseFlatCString(line).get()));
+ nsresult rv = NS_OK;
+
+ if (!mHaveStatusLine) {
+ mResponseHead->ParseStatusLine(line);
+ mHaveStatusLine = true;
+ // XXX this should probably never happen
+ if (mResponseHead->Version() == NS_HTTP_VERSION_0_9)
+ mHaveAllHeaders = true;
+ }
+ else {
+ rv = mResponseHead->ParseHeaderLine(line);
+ }
+ return rv;
+}
+
+nsresult
+nsHttpTransaction::ParseLineSegment(char *segment, uint32_t len)
+{
+ NS_PRECONDITION(!mHaveAllHeaders, "already have all headers");
+
+ if (!mLineBuf.IsEmpty() && mLineBuf.Last() == '\n') {
+ // trim off the new line char, and if this segment is
+ // not a continuation of the previous or if we haven't
+ // parsed the status line yet, then parse the contents
+ // of mLineBuf.
+ mLineBuf.Truncate(mLineBuf.Length() - 1);
+ if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) {
+ nsresult rv = ParseLine(mLineBuf);
+ mLineBuf.Truncate();
+ if (NS_FAILED(rv)) {
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::RedCorruptedContent,
+ nullptr, 0);
+ return rv;
+ }
+ }
+ }
+
+ // append segment to mLineBuf...
+ mLineBuf.Append(segment, len);
+
+ // a line buf with only a new line char signifies the end of headers.
+ if (mLineBuf.First() == '\n') {
+ mLineBuf.Truncate();
+ // discard this response if it is a 100 continue or other 1xx status.
+ uint16_t status = mResponseHead->Status();
+ if ((status != 101) && (status / 100 == 1)) {
+ LOG(("ignoring 1xx response\n"));
+ mHaveStatusLine = false;
+ mHttpResponseMatched = false;
+ mConnection->SetLastTransactionExpectedNoContent(true);
+ mResponseHead->Reset();
+ return NS_OK;
+ }
+ mHaveAllHeaders = true;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsHttpTransaction::ParseHead(char *buf,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ nsresult rv;
+ uint32_t len;
+ char *eol;
+
+ LOG(("nsHttpTransaction::ParseHead [count=%u]\n", count));
+
+ *countRead = 0;
+
+ NS_PRECONDITION(!mHaveAllHeaders, "oops");
+
+ // allocate the response head object if necessary
+ if (!mResponseHead) {
+ mResponseHead = new nsHttpResponseHead();
+ if (!mResponseHead)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // report that we have a least some of the response
+ if (mActivityDistributor && !mReportedStart) {
+ mReportedStart = true;
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START,
+ PR_Now(), 0, EmptyCString());
+ }
+ }
+
+ if (!mHttpResponseMatched) {
+ // Normally we insist on seeing HTTP/1.x in the first few bytes,
+ // but if we are on a persistent connection and the previous transaction
+ // was not supposed to have any content then we need to be prepared
+ // to skip over a response body that the server may have sent even
+ // though it wasn't allowed.
+ if (!mConnection || !mConnection->LastTransactionExpectedNoContent()) {
+ // tolerate only minor junk before the status line
+ mHttpResponseMatched = true;
+ char *p = LocateHttpStart(buf, std::min<uint32_t>(count, 11), true);
+ if (!p) {
+ // Treat any 0.9 style response of a put as a failure.
+ if (mRequestHead->IsPut())
+ return NS_ERROR_ABORT;
+
+ mResponseHead->ParseStatusLine(EmptyCString());
+ mHaveStatusLine = true;
+ mHaveAllHeaders = true;
+ return NS_OK;
+ }
+ if (p > buf) {
+ // skip over the junk
+ mInvalidResponseBytesRead += p - buf;
+ *countRead = p - buf;
+ buf = p;
+ }
+ }
+ else {
+ char *p = LocateHttpStart(buf, count, false);
+ if (p) {
+ mInvalidResponseBytesRead += p - buf;
+ *countRead = p - buf;
+ buf = p;
+ mHttpResponseMatched = true;
+ } else {
+ mInvalidResponseBytesRead += count;
+ *countRead = count;
+ if (mInvalidResponseBytesRead > MAX_INVALID_RESPONSE_BODY_SIZE) {
+ LOG(("nsHttpTransaction::ParseHead() "
+ "Cannot find Response Header\n"));
+ // cannot go back and call this 0.9 anymore as we
+ // have thrown away a lot of the leading junk
+ return NS_ERROR_ABORT;
+ }
+ return NS_OK;
+ }
+ }
+ }
+ // otherwise we can assume that we don't have a HTTP/0.9 response.
+
+ MOZ_ASSERT (mHttpResponseMatched);
+ while ((eol = static_cast<char *>(memchr(buf, '\n', count - *countRead))) != nullptr) {
+ // found line in range [buf:eol]
+ len = eol - buf + 1;
+
+ *countRead += len;
+
+ // actually, the line is in the range [buf:eol-1]
+ if ((eol > buf) && (*(eol-1) == '\r'))
+ len--;
+
+ buf[len-1] = '\n';
+ rv = ParseLineSegment(buf, len);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mHaveAllHeaders)
+ return NS_OK;
+
+ // skip over line
+ buf = eol + 1;
+
+ if (!mHttpResponseMatched) {
+ // a 100 class response has caused us to throw away that set of
+ // response headers and look for the next response
+ return NS_ERROR_NET_INTERRUPT;
+ }
+ }
+
+ // do something about a partial header line
+ if (!mHaveAllHeaders && (len = count - *countRead)) {
+ *countRead = count;
+ // ignore a trailing carriage return, and don't bother calling
+ // ParseLineSegment if buf only contains a carriage return.
+ if ((buf[len-1] == '\r') && (--len == 0))
+ return NS_OK;
+ rv = ParseLineSegment(buf, len);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsHttpTransaction::HandleContentStart()
+{
+ LOG(("nsHttpTransaction::HandleContentStart [this=%p]\n", this));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mResponseHead) {
+ if (LOG3_ENABLED()) {
+ LOG3(("http response [\n"));
+ nsAutoCString headers;
+ mResponseHead->Flatten(headers, false);
+ headers.AppendLiteral(" OriginalHeaders");
+ headers.AppendLiteral("\r\n");
+ mResponseHead->FlattenNetworkOriginalHeaders(headers);
+ LogHeaders(headers.get());
+ LOG3(("]\n"));
+ }
+
+ CheckForStickyAuthScheme();
+
+ // Save http version, mResponseHead isn't available anymore after
+ // TakeResponseHead() is called
+ mHttpVersion = mResponseHead->Version();
+ mHttpResponseCode = mResponseHead->Status();
+
+ // notify the connection, give it a chance to cause a reset.
+ bool reset = false;
+ if (!mRestartInProgressVerifier.IsSetup())
+ mConnection->OnHeadersAvailable(this, mRequestHead, mResponseHead, &reset);
+
+ // looks like we should ignore this response, resetting...
+ if (reset) {
+ LOG(("resetting transaction's response head\n"));
+ mHaveAllHeaders = false;
+ mHaveStatusLine = false;
+ mReceivedData = false;
+ mSentData = false;
+ mHttpResponseMatched = false;
+ mResponseHead->Reset();
+ // wait to be called again...
+ return NS_OK;
+ }
+
+ // check if this is a no-content response
+ switch (mResponseHead->Status()) {
+ case 101:
+ mPreserveStream = true;
+ MOZ_FALLTHROUGH; // to other no content cases:
+ case 204:
+ case 205:
+ case 304:
+ mNoContent = true;
+ LOG(("this response should not contain a body.\n"));
+ break;
+ case 421:
+ LOG(("Misdirected Request.\n"));
+ gHttpHandler->ConnMgr()->ClearHostMapping(mConnInfo);
+
+ // retry on a new connection - just in case
+ if (!mRestartCount) {
+ mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
+ mForceRestart = true; // force restart has built in loop protection
+ return NS_ERROR_NET_RESET;
+ }
+ break;
+ }
+
+ if (mResponseHead->Status() == 200 &&
+ mConnection->IsProxyConnectInProgress()) {
+ // successful CONNECTs do not have response bodies
+ mNoContent = true;
+ }
+ mConnection->SetLastTransactionExpectedNoContent(mNoContent);
+ if (mInvalidResponseBytesRead)
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming,
+ nullptr, mClassification);
+
+ if (mNoContent)
+ mContentLength = 0;
+ else {
+ // grab the content-length from the response headers
+ mContentLength = mResponseHead->ContentLength();
+
+ if ((mClassification != CLASS_SOLO) &&
+ (mContentLength > mMaxPipelineObjectSize))
+ CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge);
+
+ // handle chunked encoding here, so we'll know immediately when
+ // we're done with the socket. please note that _all_ other
+ // decoding is done when the channel receives the content data
+ // so as not to block the socket transport thread too much.
+ if (mResponseHead->Version() >= NS_HTTP_VERSION_1_0 &&
+ mResponseHead->HasHeaderValue(nsHttp::Transfer_Encoding, "chunked")) {
+ // we only support the "chunked" transfer encoding right now.
+ mChunkedDecoder = new nsHttpChunkedDecoder();
+ LOG(("nsHttpTransaction %p chunked decoder created\n", this));
+ // Ignore server specified Content-Length.
+ if (mContentLength != int64_t(-1)) {
+ LOG(("nsHttpTransaction %p chunked with C-L ignores C-L\n", this));
+ mContentLength = -1;
+ if (mConnection) {
+ mConnection->DontReuse();
+ }
+ }
+ }
+ else if (mContentLength == int64_t(-1))
+ LOG(("waiting for the server to close the connection.\n"));
+ }
+ if (mRestartInProgressVerifier.IsSetup() &&
+ !mRestartInProgressVerifier.Verify(mContentLength, mResponseHead)) {
+ LOG(("Restart in progress subsequent transaction failed to match"));
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ mDidContentStart = true;
+
+ // The verifier only initializes itself once (from the first iteration of
+ // a transaction that gets far enough to have response headers)
+ if (mRequestHead->IsGet())
+ mRestartInProgressVerifier.Set(mContentLength, mResponseHead);
+
+ return NS_OK;
+}
+
+// called on the socket thread
+nsresult
+nsHttpTransaction::HandleContent(char *buf,
+ uint32_t count,
+ uint32_t *contentRead,
+ uint32_t *contentRemaining)
+{
+ nsresult rv;
+
+ LOG(("nsHttpTransaction::HandleContent [this=%p count=%u]\n", this, count));
+
+ *contentRead = 0;
+ *contentRemaining = 0;
+
+ MOZ_ASSERT(mConnection);
+
+ if (!mDidContentStart) {
+ rv = HandleContentStart();
+ if (NS_FAILED(rv)) return rv;
+ // Do not write content to the pipe if we haven't started streaming yet
+ if (!mDidContentStart)
+ return NS_OK;
+ }
+
+ if (mChunkedDecoder) {
+ // give the buf over to the chunked decoder so it can reformat the
+ // data and tell us how much is really there.
+ rv = mChunkedDecoder->HandleChunkedContent(buf, count, contentRead, contentRemaining);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else if (mContentLength >= int64_t(0)) {
+ // HTTP/1.0 servers have been known to send erroneous Content-Length
+ // headers. So, unless the connection is persistent, we must make
+ // allowances for a possibly invalid Content-Length header. Thus, if
+ // NOT persistent, we simply accept everything in |buf|.
+ if (mConnection->IsPersistent() || mPreserveStream ||
+ mHttpVersion >= NS_HTTP_VERSION_1_1) {
+ int64_t remaining = mContentLength - mContentRead;
+ *contentRead = uint32_t(std::min<int64_t>(count, remaining));
+ *contentRemaining = count - *contentRead;
+ }
+ else {
+ *contentRead = count;
+ // mContentLength might need to be increased...
+ int64_t position = mContentRead + int64_t(count);
+ if (position > mContentLength) {
+ mContentLength = position;
+ //mResponseHead->SetContentLength(mContentLength);
+ }
+ }
+ }
+ else {
+ // when we are just waiting for the server to close the connection...
+ // (no explicit content-length given)
+ *contentRead = count;
+ }
+
+ int64_t toReadBeforeRestart =
+ mRestartInProgressVerifier.ToReadBeforeRestart();
+
+ if (toReadBeforeRestart && *contentRead) {
+ uint32_t ignore =
+ static_cast<uint32_t>(std::min<int64_t>(toReadBeforeRestart, UINT32_MAX));
+ ignore = std::min(*contentRead, ignore);
+ LOG(("Due To Restart ignoring %d of remaining %ld",
+ ignore, toReadBeforeRestart));
+ *contentRead -= ignore;
+ mContentRead += ignore;
+ mRestartInProgressVerifier.HaveReadBeforeRestart(ignore);
+ memmove(buf, buf + ignore, *contentRead + *contentRemaining);
+ }
+
+ if (*contentRead) {
+ // update count of content bytes read and report progress...
+ mContentRead += *contentRead;
+ }
+
+ LOG(("nsHttpTransaction::HandleContent [this=%p count=%u read=%u mContentRead=%lld mContentLength=%lld]\n",
+ this, count, *contentRead, mContentRead, mContentLength));
+
+ // Check the size of chunked responses. If we exceed the max pipeline size
+ // for this response reschedule the pipeline
+ if ((mClassification != CLASS_SOLO) &&
+ mChunkedDecoder &&
+ ((mContentRead + mChunkedDecoder->GetChunkRemaining()) >
+ mMaxPipelineObjectSize)) {
+ CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge);
+ }
+
+ // check for end-of-file
+ if ((mContentRead == mContentLength) ||
+ (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) {
+ // the transaction is done with a complete response.
+ mTransactionDone = true;
+ mResponseIsComplete = true;
+ ReleaseBlockingTransaction();
+
+ if (TimingEnabled()) {
+ SetResponseEnd(TimeStamp::Now());
+ }
+
+ // report the entire response has arrived
+ if (mActivityDistributor)
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
+ PR_Now(),
+ static_cast<uint64_t>(mContentRead),
+ EmptyCString());
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpTransaction::ProcessData(char *buf, uint32_t count, uint32_t *countRead)
+{
+ nsresult rv;
+
+ LOG(("nsHttpTransaction::ProcessData [this=%p count=%u]\n", this, count));
+
+ *countRead = 0;
+
+ // we may not have read all of the headers yet...
+ if (!mHaveAllHeaders) {
+ uint32_t bytesConsumed = 0;
+
+ do {
+ uint32_t localBytesConsumed = 0;
+ char *localBuf = buf + bytesConsumed;
+ uint32_t localCount = count - bytesConsumed;
+
+ rv = ParseHead(localBuf, localCount, &localBytesConsumed);
+ if (NS_FAILED(rv) && rv != NS_ERROR_NET_INTERRUPT)
+ return rv;
+ bytesConsumed += localBytesConsumed;
+ } while (rv == NS_ERROR_NET_INTERRUPT);
+
+ mCurrentHttpResponseHeaderSize += bytesConsumed;
+ if (mCurrentHttpResponseHeaderSize >
+ gHttpHandler->MaxHttpResponseHeaderSize()) {
+ LOG(("nsHttpTransaction %p The response header exceeds the limit.\n",
+ this));
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+ count -= bytesConsumed;
+
+ // if buf has some content in it, shift bytes to top of buf.
+ if (count && bytesConsumed)
+ memmove(buf, buf + bytesConsumed, count);
+
+ // report the completed response header
+ if (mActivityDistributor && mResponseHead && mHaveAllHeaders &&
+ !mReportedResponseHeader) {
+ mReportedResponseHeader = true;
+ nsAutoCString completeResponseHeaders;
+ mResponseHead->Flatten(completeResponseHeaders, false);
+ completeResponseHeaders.AppendLiteral("\r\n");
+ mActivityDistributor->ObserveActivity(
+ mChannel,
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER,
+ PR_Now(), 0,
+ completeResponseHeaders);
+ }
+ }
+
+ // even though count may be 0, we still want to call HandleContent
+ // so it can complete the transaction if this is a "no-content" response.
+ if (mHaveAllHeaders) {
+ uint32_t countRemaining = 0;
+ //
+ // buf layout:
+ //
+ // +--------------------------------------+----------------+-----+
+ // | countRead | countRemaining | |
+ // +--------------------------------------+----------------+-----+
+ //
+ // count : bytes read from the socket
+ // countRead : bytes corresponding to this transaction
+ // countRemaining : bytes corresponding to next pipelined transaction
+ //
+ // NOTE:
+ // count > countRead + countRemaining <==> chunked transfer encoding
+ //
+ rv = HandleContent(buf, count, countRead, &countRemaining);
+ if (NS_FAILED(rv)) return rv;
+ // we may have read more than our share, in which case we must give
+ // the excess bytes back to the connection
+ if (mResponseIsComplete && countRemaining) {
+ MOZ_ASSERT(mConnection);
+ mConnection->PushBack(buf + *countRead, countRemaining);
+ }
+
+ if (!mContentDecodingCheck && mResponseHead) {
+ mContentDecoding =
+ mResponseHead->HasHeader(nsHttp::Content_Encoding);
+ mContentDecodingCheck = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsHttpTransaction::CancelPipeline(uint32_t reason)
+{
+ // reason is casted through a uint to avoid compiler header deps
+ gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+ mConnInfo,
+ static_cast<nsHttpConnectionMgr::PipelineFeedbackInfoType>(reason),
+ nullptr, mClassification);
+
+ mConnection->CancelPipeline(NS_ERROR_ABORT);
+
+ // Avoid pipelining this transaction on restart by classifying it as solo.
+ // This also prevents BadUnexpectedLarge from being reported more
+ // than one time per transaction.
+ mClassification = CLASS_SOLO;
+}
+
+
+void
+nsHttpTransaction::SetRequestContext(nsIRequestContext *aRequestContext)
+{
+ LOG(("nsHttpTransaction %p SetRequestContext %p\n", this, aRequestContext));
+ mRequestContext = aRequestContext;
+}
+
+// Called when the transaction marked for blocking is associated with a connection
+// (i.e. added to a new h1 conn, an idle http connection, or placed into
+// a http pipeline). It is safe to call this multiple times with it only
+// having an effect once.
+void
+nsHttpTransaction::DispatchedAsBlocking()
+{
+ if (mDispatchedAsBlocking)
+ return;
+
+ LOG(("nsHttpTransaction %p dispatched as blocking\n", this));
+
+ if (!mRequestContext)
+ return;
+
+ LOG(("nsHttpTransaction adding blocking transaction %p from "
+ "request context %p\n", this, mRequestContext.get()));
+
+ mRequestContext->AddBlockingTransaction();
+ mDispatchedAsBlocking = true;
+}
+
+void
+nsHttpTransaction::RemoveDispatchedAsBlocking()
+{
+ if (!mRequestContext || !mDispatchedAsBlocking)
+ return;
+
+ uint32_t blockers = 0;
+ nsresult rv = mRequestContext->RemoveBlockingTransaction(&blockers);
+
+ LOG(("nsHttpTransaction removing blocking transaction %p from "
+ "request context %p. %d blockers remain.\n", this,
+ mRequestContext.get(), blockers));
+
+ if (NS_SUCCEEDED(rv) && !blockers) {
+ LOG(("nsHttpTransaction %p triggering release of blocked channels "
+ " with request context=%p\n", this, mRequestContext.get()));
+ gHttpHandler->ConnMgr()->ProcessPendingQ();
+ }
+
+ mDispatchedAsBlocking = false;
+}
+
+void
+nsHttpTransaction::ReleaseBlockingTransaction()
+{
+ RemoveDispatchedAsBlocking();
+ LOG(("nsHttpTransaction %p request context set to null "
+ "in ReleaseBlockingTransaction() - was %p\n", this, mRequestContext.get()));
+ mRequestContext = nullptr;
+}
+
+void
+nsHttpTransaction::DisableSpdy()
+{
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ if (mConnInfo) {
+ // This is our clone of the connection info, not the persistent one that
+ // is owned by the connection manager, so we're safe to change this here
+ mConnInfo->SetNoSpdy(true);
+ }
+}
+
+void
+nsHttpTransaction::CheckForStickyAuthScheme()
+{
+ LOG(("nsHttpTransaction::CheckForStickyAuthScheme this=%p"));
+
+ MOZ_ASSERT(mHaveAllHeaders);
+ MOZ_ASSERT(mResponseHead);
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ CheckForStickyAuthSchemeAt(nsHttp::WWW_Authenticate);
+ CheckForStickyAuthSchemeAt(nsHttp::Proxy_Authenticate);
+}
+
+void
+nsHttpTransaction::CheckForStickyAuthSchemeAt(nsHttpAtom const& header)
+{
+ if (mCaps & NS_HTTP_STICKY_CONNECTION) {
+ LOG((" already sticky"));
+ return;
+ }
+
+ nsAutoCString auth;
+ if (NS_FAILED(mResponseHead->GetHeader(header, auth))) {
+ return;
+ }
+
+ Tokenizer p(auth);
+ nsAutoCString schema;
+ while (p.ReadWord(schema)) {
+ ToLowerCase(schema);
+
+ nsAutoCString contractid;
+ contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
+ contractid.Append(schema);
+
+ // using a new instance because of thread safety of auth modules refcnt
+ nsCOMPtr<nsIHttpAuthenticator> authenticator(do_CreateInstance(contractid.get()));
+ if (authenticator) {
+ uint32_t flags;
+ authenticator->GetAuthFlags(&flags);
+ if (flags & nsIHttpAuthenticator::CONNECTION_BASED) {
+ LOG((" connection made sticky, found %s auth shema", schema.get()));
+ // This is enough to make this transaction keep it's current connection,
+ // prevents the connection from being released back to the pool.
+ mCaps |= NS_HTTP_STICKY_CONNECTION;
+ break;
+ }
+ }
+
+ // schemes are separated with LFs, nsHttpHeaderArray::MergeHeader
+ p.SkipUntil(Tokenizer::Token::NewLine());
+ p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE);
+ }
+}
+
+const TimingStruct
+nsHttpTransaction::Timings()
+{
+ mozilla::MutexAutoLock lock(mLock);
+ TimingStruct timings = mTimings;
+ return timings;
+}
+
+void
+nsHttpTransaction::SetDomainLookupStart(mozilla::TimeStamp timeStamp, bool onlyIfNull)
+{
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.domainLookupStart.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.domainLookupStart = timeStamp;
+}
+
+void
+nsHttpTransaction::SetDomainLookupEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull)
+{
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.domainLookupEnd.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.domainLookupEnd = timeStamp;
+}
+
+void
+nsHttpTransaction::SetConnectStart(mozilla::TimeStamp timeStamp, bool onlyIfNull)
+{
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.connectStart.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.connectStart = timeStamp;
+}
+
+void
+nsHttpTransaction::SetConnectEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull)
+{
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.connectEnd.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.connectEnd = timeStamp;
+}
+
+void
+nsHttpTransaction::SetRequestStart(mozilla::TimeStamp timeStamp, bool onlyIfNull)
+{
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.requestStart.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.requestStart = timeStamp;
+}
+
+void
+nsHttpTransaction::SetResponseStart(mozilla::TimeStamp timeStamp, bool onlyIfNull)
+{
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.responseStart.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.responseStart = timeStamp;
+}
+
+void
+nsHttpTransaction::SetResponseEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull)
+{
+ mozilla::MutexAutoLock lock(mLock);
+ if (onlyIfNull && !mTimings.responseEnd.IsNull()) {
+ return; // We only set the timestamp if it was previously null
+ }
+ mTimings.responseEnd = timeStamp;
+}
+
+mozilla::TimeStamp
+nsHttpTransaction::GetDomainLookupStart()
+{
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.domainLookupStart;
+}
+
+mozilla::TimeStamp
+nsHttpTransaction::GetDomainLookupEnd()
+{
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.domainLookupEnd;
+}
+
+mozilla::TimeStamp
+nsHttpTransaction::GetConnectStart()
+{
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.connectStart;
+}
+
+mozilla::TimeStamp
+nsHttpTransaction::GetConnectEnd()
+{
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.connectEnd;
+}
+
+mozilla::TimeStamp
+nsHttpTransaction::GetRequestStart()
+{
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.requestStart;
+}
+
+mozilla::TimeStamp
+nsHttpTransaction::GetResponseStart()
+{
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.responseStart;
+}
+
+mozilla::TimeStamp
+nsHttpTransaction::GetResponseEnd()
+{
+ mozilla::MutexAutoLock lock(mLock);
+ return mTimings.responseEnd;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction deletion event
+//-----------------------------------------------------------------------------
+
+class DeleteHttpTransaction : public Runnable {
+public:
+ explicit DeleteHttpTransaction(nsHttpTransaction *trans)
+ : mTrans(trans)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ delete mTrans;
+ return NS_OK;
+ }
+private:
+ nsHttpTransaction *mTrans;
+};
+
+void
+nsHttpTransaction::DeleteSelfOnConsumerThread()
+{
+ LOG(("nsHttpTransaction::DeleteSelfOnConsumerThread [this=%p]\n", this));
+
+ bool val;
+ if (!mConsumerTarget ||
+ (NS_SUCCEEDED(mConsumerTarget->IsOnCurrentThread(&val)) && val)) {
+ delete this;
+ } else {
+ LOG(("proxying delete to consumer thread...\n"));
+ nsCOMPtr<nsIRunnable> event = new DeleteHttpTransaction(this);
+ if (NS_FAILED(mConsumerTarget->Dispatch(event, NS_DISPATCH_NORMAL)))
+ NS_WARNING("failed to dispatch nsHttpDeleteTransaction event");
+ }
+}
+
+bool
+nsHttpTransaction::TryToRunPacedRequest()
+{
+ if (mSubmittedRatePacing)
+ return mPassedRatePacing;
+
+ mSubmittedRatePacing = true;
+ mSynchronousRatePaceRequest = true;
+ gHttpHandler->SubmitPacedRequest(this, getter_AddRefs(mTokenBucketCancel));
+ mSynchronousRatePaceRequest = false;
+ return mPassedRatePacing;
+}
+
+void
+nsHttpTransaction::OnTokenBucketAdmitted()
+{
+ mPassedRatePacing = true;
+ mTokenBucketCancel = nullptr;
+
+ if (!mSynchronousRatePaceRequest)
+ gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
+}
+
+void
+nsHttpTransaction::CancelPacing(nsresult reason)
+{
+ if (mTokenBucketCancel) {
+ mTokenBucketCancel->Cancel(reason);
+ mTokenBucketCancel = nullptr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsHttpTransaction)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsHttpTransaction::Release()
+{
+ nsrefcnt count;
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "nsHttpTransaction");
+ if (0 == count) {
+ mRefCnt = 1; /* stablize */
+ // it is essential that the transaction be destroyed on the consumer
+ // thread (we could be holding the last reference to our consumer).
+ DeleteSelfOnConsumerThread();
+ return 0;
+ }
+ return count;
+}
+
+NS_IMPL_QUERY_INTERFACE(nsHttpTransaction,
+ nsIInputStreamCallback,
+ nsIOutputStreamCallback)
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction::nsIInputStreamCallback
+//-----------------------------------------------------------------------------
+
+// called on the socket thread
+NS_IMETHODIMP
+nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream *out)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (mConnection) {
+ mConnection->TransactionHasDataToWrite(this);
+ nsresult rv = mConnection->ResumeSend();
+ if (NS_FAILED(rv))
+ NS_ERROR("ResumeSend failed");
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction::nsIOutputStreamCallback
+//-----------------------------------------------------------------------------
+
+// called on the socket thread
+NS_IMETHODIMP
+nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream *out)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ mWaitingOnPipeOut = false;
+ if (mConnection) {
+ mConnection->TransactionHasDataToRecv(this);
+ nsresult rv = mConnection->ResumeRecv();
+ if (NS_FAILED(rv))
+ NS_ERROR("ResumeRecv failed");
+ }
+ return NS_OK;
+}
+
+// nsHttpTransaction::RestartVerifier
+
+static bool
+matchOld(nsHttpResponseHead *newHead, nsCString &old,
+ nsHttpAtom headerAtom)
+{
+ nsAutoCString val;
+
+ newHead->GetHeader(headerAtom, val);
+ if (!val.IsEmpty() && old.IsEmpty())
+ return false;
+ if (val.IsEmpty() && !old.IsEmpty())
+ return false;
+ if (!val.IsEmpty() && !old.Equals(val))
+ return false;
+ return true;
+}
+
+bool
+nsHttpTransaction::RestartVerifier::Verify(int64_t contentLength,
+ nsHttpResponseHead *newHead)
+{
+ if (mContentLength != contentLength)
+ return false;
+
+ if (newHead->Status() != 200)
+ return false;
+
+ if (!matchOld(newHead, mContentRange, nsHttp::Content_Range))
+ return false;
+
+ if (!matchOld(newHead, mLastModified, nsHttp::Last_Modified))
+ return false;
+
+ if (!matchOld(newHead, mETag, nsHttp::ETag))
+ return false;
+
+ if (!matchOld(newHead, mContentEncoding, nsHttp::Content_Encoding))
+ return false;
+
+ if (!matchOld(newHead, mTransferEncoding, nsHttp::Transfer_Encoding))
+ return false;
+
+ return true;
+}
+
+void
+nsHttpTransaction::RestartVerifier::Set(int64_t contentLength,
+ nsHttpResponseHead *head)
+{
+ if (mSetup)
+ return;
+
+ // If mSetup does not transition to true RestartInPogress() is later
+ // forbidden
+
+ // Only RestartInProgress with 200 response code
+ if (!head || (head->Status() != 200)) {
+ return;
+ }
+
+ mContentLength = contentLength;
+
+ nsAutoCString val;
+ if (NS_SUCCEEDED(head->GetHeader(nsHttp::ETag, val))) {
+ mETag = val;
+ }
+ if (NS_SUCCEEDED(head->GetHeader(nsHttp::Last_Modified, val))) {
+ mLastModified = val;
+ }
+ if (NS_SUCCEEDED(head->GetHeader(nsHttp::Content_Range, val))) {
+ mContentRange = val;
+ }
+ if (NS_SUCCEEDED(head->GetHeader(nsHttp::Content_Encoding, val))) {
+ mContentEncoding = val;
+ }
+ if (NS_SUCCEEDED(head->GetHeader(nsHttp::Transfer_Encoding, val))) {
+ mTransferEncoding = val;
+ }
+
+ // We can only restart with any confidence if we have a stored etag or
+ // last-modified header
+ if (mETag.IsEmpty() && mLastModified.IsEmpty()) {
+ return;
+ }
+
+ mSetup = true;
+}
+
+void
+nsHttpTransaction::GetNetworkAddresses(NetAddr &self, NetAddr &peer)
+{
+ MutexAutoLock lock(mLock);
+ self = mSelfAddr;
+ peer = mPeerAddr;
+}
+
+bool
+nsHttpTransaction::Do0RTT()
+{
+ if (mRequestHead->IsSafeMethod() &&
+ !mConnection->IsProxyConnectInProgress()) {
+ m0RTTInProgress = true;
+ }
+ return m0RTTInProgress;
+}
+
+nsresult
+nsHttpTransaction::Finish0RTT(bool aRestart)
+{
+ MOZ_ASSERT(m0RTTInProgress);
+ m0RTTInProgress = false;
+ if (aRestart) {
+ // Reset request headers to be sent again.
+ nsCOMPtr<nsISeekableStream> seekable =
+ do_QueryInterface(mRequestStream);
+ if (seekable) {
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h
new file mode 100644
index 000000000..ab0b267a7
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -0,0 +1,487 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpTransaction_h__
+#define nsHttpTransaction_h__
+
+#include "nsHttp.h"
+#include "nsAHttpTransaction.h"
+#include "nsAHttpConnection.h"
+#include "EventTokenBucket.h"
+#include "nsCOMPtr.h"
+#include "nsThreadUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "TimingStruct.h"
+#include "Http2Push.h"
+#include "mozilla/net/DNS.h"
+#include "ARefBase.h"
+#include "AlternateServices.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkInterface.h"
+#include "nsProxyRelease.h"
+#endif
+
+//-----------------------------------------------------------------------------
+
+class nsIHttpActivityObserver;
+class nsIEventTarget;
+class nsIInputStream;
+class nsIOutputStream;
+class nsIRequestContext;
+
+namespace mozilla { namespace net {
+
+class nsHttpChunkedDecoder;
+class nsHttpRequestHead;
+class nsHttpResponseHead;
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction represents a single HTTP transaction. It is thread-safe,
+// intended to run on the socket thread.
+//-----------------------------------------------------------------------------
+
+class nsHttpTransaction final : public nsAHttpTransaction
+ , public ATokenBucketEvent
+ , public nsIInputStreamCallback
+ , public nsIOutputStreamCallback
+ , public ARefBase
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+
+ nsHttpTransaction();
+
+ //
+ // called to initialize the transaction
+ //
+ // @param caps
+ // the transaction capabilities (see nsHttp.h)
+ // @param connInfo
+ // the connection type for this transaction.
+ // @param reqHeaders
+ // the request header struct
+ // @param reqBody
+ // the request body (POST or PUT data stream)
+ // @param reqBodyIncludesHeaders
+ // fun stuff to support NPAPI plugins.
+ // @param target
+ // the dispatch target were notifications should be sent.
+ // @param callbacks
+ // the notification callbacks to be given to PSM.
+ // @param responseBody
+ // the input stream that will contain the response data. async
+ // wait on this input stream for data. on first notification,
+ // headers should be available (check transaction status).
+ //
+ nsresult Init(uint32_t caps,
+ nsHttpConnectionInfo *connInfo,
+ nsHttpRequestHead *reqHeaders,
+ nsIInputStream *reqBody,
+ bool reqBodyIncludesHeaders,
+ nsIEventTarget *consumerTarget,
+ nsIInterfaceRequestor *callbacks,
+ nsITransportEventSink *eventsink,
+ nsIAsyncInputStream **responseBody);
+
+ // attributes
+ nsHttpResponseHead *ResponseHead() { return mHaveAllHeaders ? mResponseHead : nullptr; }
+ nsISupports *SecurityInfo() { return mSecurityInfo; }
+
+ nsIEventTarget *ConsumerTarget() { return mConsumerTarget; }
+ nsISupports *HttpChannel() { return mChannel; }
+
+ void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks);
+
+ // Called to take ownership of the response headers; the transaction
+ // will drop any reference to the response headers after this call.
+ nsHttpResponseHead *TakeResponseHead();
+
+ // Provides a thread safe reference of the connection
+ // nsHttpTransaction::Connection should only be used on the socket thread
+ already_AddRefed<nsAHttpConnection> GetConnectionReference();
+
+ // Called to set/find out if the transaction generated a complete response.
+ bool ResponseIsComplete() { return mResponseIsComplete; }
+ void SetResponseIsComplete() { mResponseIsComplete = true; }
+
+ bool ProxyConnectFailed() { return mProxyConnectFailed; }
+
+ void EnableKeepAlive() { mCaps |= NS_HTTP_ALLOW_KEEPALIVE; }
+ void MakeSticky() { mCaps |= NS_HTTP_STICKY_CONNECTION; }
+
+ // SetPriority() may only be used by the connection manager.
+ void SetPriority(int32_t priority) { mPriority = priority; }
+ int32_t Priority() { return mPriority; }
+
+ enum Classifier Classification() { return mClassification; }
+
+ void PrintDiagnostics(nsCString &log);
+
+ // Sets mPendingTime to the current time stamp or to a null time stamp (if now is false)
+ void SetPendingTime(bool now = true) { mPendingTime = now ? TimeStamp::Now() : TimeStamp(); }
+ const TimeStamp GetPendingTime() { return mPendingTime; }
+ bool UsesPipelining() const { return mCaps & NS_HTTP_ALLOW_PIPELINING; }
+
+ // overload of nsAHttpTransaction::RequestContext()
+ nsIRequestContext *RequestContext() override { return mRequestContext.get(); }
+ void SetRequestContext(nsIRequestContext *aRequestContext);
+ void DispatchedAsBlocking();
+ void RemoveDispatchedAsBlocking();
+
+ nsHttpTransaction *QueryHttpTransaction() override { return this; }
+
+ Http2PushedStream *GetPushedStream() { return mPushedStream; }
+ Http2PushedStream *TakePushedStream()
+ {
+ Http2PushedStream *r = mPushedStream;
+ mPushedStream = nullptr;
+ return r;
+ }
+ void SetPushedStream(Http2PushedStream *push) { mPushedStream = push; }
+ uint32_t InitialRwin() const { return mInitialRwin; };
+ bool ChannelPipeFull() { return mWaitingOnPipeOut; }
+
+ // Locked methods to get and set timing info
+ const TimingStruct Timings();
+ void SetDomainLookupStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetDomainLookupEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetConnectStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetConnectEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetRequestStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetResponseStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+ void SetResponseEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
+
+ mozilla::TimeStamp GetDomainLookupStart();
+ mozilla::TimeStamp GetDomainLookupEnd();
+ mozilla::TimeStamp GetConnectStart();
+ mozilla::TimeStamp GetConnectEnd();
+ mozilla::TimeStamp GetRequestStart();
+ mozilla::TimeStamp GetResponseStart();
+ mozilla::TimeStamp GetResponseEnd();
+
+ int64_t GetTransferSize() { return mTransferSize; }
+
+ bool Do0RTT() override;
+ nsresult Finish0RTT(bool aRestart) override;
+private:
+ friend class DeleteHttpTransaction;
+ virtual ~nsHttpTransaction();
+
+ nsresult Restart();
+ nsresult RestartInProgress();
+ char *LocateHttpStart(char *buf, uint32_t len,
+ bool aAllowPartialMatch);
+ nsresult ParseLine(nsACString &line);
+ nsresult ParseLineSegment(char *seg, uint32_t len);
+ nsresult ParseHead(char *, uint32_t count, uint32_t *countRead);
+ nsresult HandleContentStart();
+ nsresult HandleContent(char *, uint32_t count, uint32_t *contentRead, uint32_t *contentRemaining);
+ nsresult ProcessData(char *, uint32_t, uint32_t *);
+ void DeleteSelfOnConsumerThread();
+ void ReleaseBlockingTransaction();
+
+ Classifier Classify();
+ void CancelPipeline(uint32_t reason);
+
+ static nsresult ReadRequestSegment(nsIInputStream *, void *, const char *,
+ uint32_t, uint32_t, uint32_t *);
+ static nsresult WritePipeSegment(nsIOutputStream *, void *, char *,
+ uint32_t, uint32_t, uint32_t *);
+
+ bool TimingEnabled() const { return mCaps & NS_HTTP_TIMING_ENABLED; }
+
+ bool ResponseTimeoutEnabled() const final;
+
+ void DisableSpdy() override;
+ void ReuseConnectionOnRestartOK(bool reuseOk) override { mReuseOnRestart = reuseOk; }
+
+ // Called right after we parsed the response head. Checks for connection based
+ // authentication schemes in reponse headers for WWW and Proxy authentication.
+ // If such is found in any of them, NS_HTTP_STICKY_CONNECTION is set in mCaps.
+ // We need the sticky flag be set early to keep the connection from very start
+ // of the authentication process.
+ void CheckForStickyAuthScheme();
+ void CheckForStickyAuthSchemeAt(nsHttpAtom const& header);
+
+private:
+ class UpdateSecurityCallbacks : public Runnable
+ {
+ public:
+ UpdateSecurityCallbacks(nsHttpTransaction* aTrans,
+ nsIInterfaceRequestor* aCallbacks)
+ : mTrans(aTrans), mCallbacks(aCallbacks) {}
+
+ NS_IMETHOD Run() override
+ {
+ if (mTrans->mConnection)
+ mTrans->mConnection->SetSecurityCallbacks(mCallbacks);
+ return NS_OK;
+ }
+ private:
+ RefPtr<nsHttpTransaction> mTrans;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ };
+
+ Mutex mLock;
+
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsITransportEventSink> mTransportSink;
+ nsCOMPtr<nsIEventTarget> mConsumerTarget;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+ nsCOMPtr<nsIAsyncInputStream> mPipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> mPipeOut;
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+
+ nsCOMPtr<nsISupports> mChannel;
+ nsCOMPtr<nsIHttpActivityObserver> mActivityDistributor;
+
+ nsCString mReqHeaderBuf; // flattened request headers
+ nsCOMPtr<nsIInputStream> mRequestStream;
+ int64_t mRequestSize;
+
+ RefPtr<nsAHttpConnection> mConnection;
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ nsHttpRequestHead *mRequestHead; // weak ref
+ nsHttpResponseHead *mResponseHead; // owning pointer
+
+ nsAHttpSegmentReader *mReader;
+ nsAHttpSegmentWriter *mWriter;
+
+ nsCString mLineBuf; // may contain a partial line
+
+ int64_t mContentLength; // equals -1 if unknown
+ int64_t mContentRead; // count of consumed content bytes
+ Atomic<int64_t, ReleaseAcquire> mTransferSize; // count of received bytes
+
+ // After a 304/204 or other "no-content" style response we will skip over
+ // up to MAX_INVALID_RESPONSE_BODY_SZ bytes when looking for the next
+ // response header to deal with servers that actually sent a response
+ // body where they should not have. This member tracks how many bytes have
+ // so far been skipped.
+ uint32_t mInvalidResponseBytesRead;
+
+ Http2PushedStream *mPushedStream;
+ uint32_t mInitialRwin;
+
+ nsHttpChunkedDecoder *mChunkedDecoder;
+
+ TimingStruct mTimings;
+
+ nsresult mStatus;
+
+ int16_t mPriority;
+
+ uint16_t mRestartCount; // the number of times this transaction has been restarted
+ uint32_t mCaps;
+ enum Classifier mClassification;
+ int32_t mPipelinePosition;
+ int64_t mMaxPipelineObjectSize;
+
+ nsHttpVersion mHttpVersion;
+ uint16_t mHttpResponseCode;
+
+ uint32_t mCurrentHttpResponseHeaderSize;
+
+ // mCapsToClear holds flags that should be cleared in mCaps, e.g. unset
+ // NS_HTTP_REFRESH_DNS when DNS refresh request has completed to avoid
+ // redundant requests on the network. The member itself is atomic, but
+ // access to it from the networking thread may happen either before or
+ // after the main thread modifies it. To deal with raciness, only unsetting
+ // bitfields should be allowed: 'lost races' will thus err on the
+ // conservative side, e.g. by going ahead with a 2nd DNS refresh.
+ Atomic<uint32_t> mCapsToClear;
+ Atomic<bool, ReleaseAcquire> mResponseIsComplete;
+
+ // state flags, all logically boolean, but not packed together into a
+ // bitfield so as to avoid bitfield-induced races. See bug 560579.
+ bool mClosed;
+ bool mConnected;
+ bool mHaveStatusLine;
+ bool mHaveAllHeaders;
+ bool mTransactionDone;
+ bool mDidContentStart;
+ bool mNoContent; // expecting an empty entity body
+ bool mSentData;
+ bool mReceivedData;
+ bool mStatusEventPending;
+ bool mHasRequestBody;
+ bool mProxyConnectFailed;
+ bool mHttpResponseMatched;
+ bool mPreserveStream;
+ bool mDispatchedAsBlocking;
+ bool mResponseTimeoutEnabled;
+ bool mForceRestart;
+ bool mReuseOnRestart;
+ bool mContentDecoding;
+ bool mContentDecodingCheck;
+ bool mDeferredSendProgress;
+ bool mWaitingOnPipeOut;
+
+ // mClosed := transaction has been explicitly closed
+ // mTransactionDone := transaction ran to completion or was interrupted
+ // mResponseComplete := transaction ran to completion
+
+ // For Restart-In-Progress Functionality
+ bool mReportedStart;
+ bool mReportedResponseHeader;
+
+ // protected by nsHttp::GetLock()
+ nsHttpResponseHead *mForTakeResponseHead;
+ bool mResponseHeadTaken;
+
+ // The time when the transaction was submitted to the Connection Manager
+ TimeStamp mPendingTime;
+
+ class RestartVerifier
+ {
+
+ // When a idemptotent transaction has received part of its response body
+ // and incurs an error it can be restarted. To do this we mark the place
+ // where we stopped feeding the body to the consumer and start the
+ // network call over again. If everything we track (headers, length, etc..)
+ // matches up to the place where we left off then the consumer starts being
+ // fed data again with the new information. This can be done N times up
+ // to the normal restart (i.e. with no response info) limit.
+
+ public:
+ RestartVerifier()
+ : mContentLength(-1)
+ , mAlreadyProcessed(0)
+ , mToReadBeforeRestart(0)
+ , mSetup(false)
+ {}
+ ~RestartVerifier() {}
+
+ void Set(int64_t contentLength, nsHttpResponseHead *head);
+ bool Verify(int64_t contentLength, nsHttpResponseHead *head);
+ bool IsDiscardingContent() { return mToReadBeforeRestart != 0; }
+ bool IsSetup() { return mSetup; }
+ int64_t AlreadyProcessed() { return mAlreadyProcessed; }
+ void SetAlreadyProcessed(int64_t val) {
+ mAlreadyProcessed = val;
+ mToReadBeforeRestart = val;
+ }
+ int64_t ToReadBeforeRestart() { return mToReadBeforeRestart; }
+ void HaveReadBeforeRestart(uint32_t amt)
+ {
+ MOZ_ASSERT(amt <= mToReadBeforeRestart,
+ "too large of a HaveReadBeforeRestart deduction");
+ mToReadBeforeRestart -= amt;
+ }
+
+ private:
+ // This is the data from the first complete response header
+ // used to make sure that all subsequent response headers match
+
+ int64_t mContentLength;
+ nsCString mETag;
+ nsCString mLastModified;
+ nsCString mContentRange;
+ nsCString mContentEncoding;
+ nsCString mTransferEncoding;
+
+ // This is the amount of data that has been passed to the channel
+ // from previous iterations of the transaction and must therefore
+ // be skipped in the new one.
+ int64_t mAlreadyProcessed;
+
+ // The amount of data that must be discarded in the current iteration
+ // (where iteration > 0) to reach the mAlreadyProcessed high water
+ // mark.
+ int64_t mToReadBeforeRestart;
+
+ // true when ::Set has been called with a response header
+ bool mSetup;
+ } mRestartInProgressVerifier;
+
+// For Rate Pacing via an EventTokenBucket
+public:
+ // called by the connection manager to run this transaction through the
+ // token bucket. If the token bucket admits the transaction immediately it
+ // returns true. The function is called repeatedly until it returns true.
+ bool TryToRunPacedRequest();
+
+ // ATokenBucketEvent pure virtual implementation. Called by the token bucket
+ // when the transaction is ready to run. If this happens asynchrounously to
+ // token bucket submission the transaction just posts an event that causes
+ // the pending transaction queue to be rerun (and TryToRunPacedRequest() to
+ // be run again.
+ void OnTokenBucketAdmitted() override; // ATokenBucketEvent
+
+ // CancelPacing() can be used to tell the token bucket to remove this
+ // transaction from the list of pending transactions. This is used when a
+ // transaction is believed to be HTTP/1 (and thus subject to rate pacing)
+ // but later can be dispatched via spdy (not subject to rate pacing).
+ void CancelPacing(nsresult reason);
+
+private:
+ bool mSubmittedRatePacing;
+ bool mPassedRatePacing;
+ bool mSynchronousRatePaceRequest;
+ nsCOMPtr<nsICancelable> mTokenBucketCancel;
+
+// These members are used for network per-app metering (bug 746073)
+// Currently, they are only available on gonk.
+ uint64_t mCountRecv;
+ uint64_t mCountSent;
+ uint32_t mAppId;
+ bool mIsInIsolatedMozBrowser;
+#ifdef MOZ_WIDGET_GONK
+ nsMainThreadPtrHandle<nsINetworkInfo> mActiveNetworkInfo;
+#endif
+ nsresult SaveNetworkStats(bool);
+ void CountRecvBytes(uint64_t recvBytes)
+ {
+ mCountRecv += recvBytes;
+ SaveNetworkStats(false);
+ }
+ void CountSentBytes(uint64_t sentBytes)
+ {
+ mCountSent += sentBytes;
+ SaveNetworkStats(false);
+ }
+public:
+ void SetClassOfService(uint32_t cos) { mClassOfService = cos; }
+ uint32_t ClassOfService() { return mClassOfService; }
+private:
+ uint32_t mClassOfService;
+
+public:
+ // setting TunnelProvider to non-null means the transaction should only
+ // be dispatched on a specific ConnectionInfo Hash Key (as opposed to a
+ // generic wild card one). That means in the specific case of carrying this
+ // transaction on an HTTP/2 tunnel it will only be dispatched onto an
+ // existing tunnel instead of triggering creation of a new one.
+ // The tunnel provider is used for ASpdySession::MaybeReTunnel() checks.
+
+ void SetTunnelProvider(ASpdySession *provider) { mTunnelProvider = provider; }
+ ASpdySession *TunnelProvider() { return mTunnelProvider; }
+ nsIInterfaceRequestor *SecurityCallbacks() { return mCallbacks; }
+
+private:
+ RefPtr<ASpdySession> mTunnelProvider;
+
+public:
+ void SetTransactionObserver(TransactionObserver *arg) { mTransactionObserver = arg; }
+private:
+ RefPtr<TransactionObserver> mTransactionObserver;
+public:
+ void GetNetworkAddresses(NetAddr &self, NetAddr &peer);
+
+private:
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+
+ bool m0RTTInProgress;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpTransaction_h__
diff --git a/netwerk/protocol/http/nsICorsPreflightCallback.h b/netwerk/protocol/http/nsICorsPreflightCallback.h
new file mode 100644
index 000000000..49ca392a8
--- /dev/null
+++ b/netwerk/protocol/http/nsICorsPreflightCallback.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsICorsPreflightCallback_h__
+#define nsICorsPreflightCallback_h__
+
+#include "nsISupports.h"
+#include "nsID.h"
+#include "nsError.h"
+
+#define NS_ICORSPREFLIGHTCALLBACK_IID \
+ { 0x3758cfbb, 0x259f, 0x4074, \
+ { 0xa8, 0xc0, 0x98, 0xe0, 0x4b, 0x3c, 0xc0, 0xe3 } }
+
+class nsICorsPreflightCallback : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(nsICorsPreflightCallback);
+ NS_IMETHOD OnPreflightSucceeded() = 0;
+ NS_IMETHOD OnPreflightFailed(nsresult aError) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsICorsPreflightCallback, NS_ICORSPREFLIGHTCALLBACK_IID);
+
+#endif
diff --git a/netwerk/protocol/http/nsIHstsPrimingCallback.idl b/netwerk/protocol/http/nsIHstsPrimingCallback.idl
new file mode 100644
index 000000000..01f53a5b2
--- /dev/null
+++ b/netwerk/protocol/http/nsIHstsPrimingCallback.idl
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * HSTS priming attempts to prevent mixed-content by looking for the
+ * Strict-Transport-Security header as a signal from the server that it is
+ * safe to upgrade HTTP to HTTPS.
+ *
+ * Since mixed-content blocking happens very early in the process in AsyncOpen2,
+ * the status of mixed-content blocking is stored in the LoadInfo and then used
+ * to determine whether to send a priming request or not.
+ *
+ * This interface is implemented by nsHttpChannel so that it can receive the
+ * result of HSTS priming.
+ */
+[builtinclass, uuid(eca6daca-3f2a-4a2a-b3bf-9f24f79bc999)]
+interface nsIHstsPrimingCallback : nsISupports
+{
+ /**
+ * HSTS priming has succeeded with an STS header, and the site asserts it is
+ * safe to upgrade the request from HTTP to HTTPS. The request may still be
+ * blocked based on the user's preferences.
+ *
+ * May be invoked synchronously if HSTS priming has already been performed
+ * for the host.
+ *
+ * @param aCached whether the result was already in the HSTS cache
+ */
+ [noscript, nostdcall]
+ void onHSTSPrimingSucceeded(in bool aCached);
+ /**
+ * HSTS priming has seen no STS header, the request itself has failed,
+ * or some other failure which does not constitute a positive signal that the
+ * site can be upgraded safely to HTTPS. The request may still be allowed
+ * based on the user's preferences.
+ *
+ * May be invoked synchronously if HSTS priming has already been performed
+ * for the host.
+ *
+ * @param aError The error which caused this failure, or NS_ERROR_CONTENT_BLOCKED
+ * @param aCached whether the result was already in the HSTS cache
+ */
+ [noscript, nostdcall]
+ void onHSTSPrimingFailed(in nsresult aError, in bool aCached);
+};
diff --git a/netwerk/protocol/http/nsIHttpActivityObserver.idl b/netwerk/protocol/http/nsIHttpActivityObserver.idl
new file mode 100644
index 000000000..72a6f28d2
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpActivityObserver.idl
@@ -0,0 +1,127 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIHttpActivityObserver
+ *
+ * This interface provides a way for http activities to be reported
+ * to observers.
+ */
+[scriptable, uuid(412880C8-6C36-48d8-BF8F-84F91F892503)]
+interface nsIHttpActivityObserver : nsISupports
+{
+ /**
+ * observe activity from the http transport
+ *
+ * @param aHttpChannel
+ * nsISupports interface for the the http channel that
+ * generated this activity
+ * @param aActivityType
+ * The value of this aActivityType will be one of
+ * ACTIVITY_TYPE_SOCKET_TRANSPORT or
+ * ACTIVITY_TYPE_HTTP_TRANSACTION
+ * @param aActivitySubtype
+ * The value of this aActivitySubtype, will be depend
+ * on the value of aActivityType. When aActivityType
+ * is ACTIVITY_TYPE_SOCKET_TRANSPORT
+ * aActivitySubtype will be one of the
+ * nsISocketTransport::STATUS_???? values defined in
+ * nsISocketTransport.idl
+ * OR when aActivityType
+ * is ACTIVITY_TYPE_HTTP_TRANSACTION
+ * aActivitySubtype will be one of the
+ * nsIHttpActivityObserver::ACTIVITY_SUBTYPE_???? values
+ * defined below
+ * @param aTimestamp
+ * microseconds past the epoch of Jan 1, 1970
+ * @param aExtraSizeData
+ * Any extra size data optionally available with
+ * this activity
+ * @param aExtraStringData
+ * Any extra string data optionally available with
+ * this activity
+ */
+ void observeActivity(in nsISupports aHttpChannel,
+ in uint32_t aActivityType,
+ in uint32_t aActivitySubtype,
+ in PRTime aTimestamp,
+ in uint64_t aExtraSizeData,
+ in ACString aExtraStringData);
+
+ /**
+ * This attribute is true when this interface is active and should
+ * observe http activities. When false, observeActivity() should not
+ * be called. It is present for compatibility reasons and should be
+ * implemented only by nsHttpActivityDistributor.
+ */
+ readonly attribute boolean isActive;
+
+ const unsigned long ACTIVITY_TYPE_SOCKET_TRANSPORT = 0x0001;
+ const unsigned long ACTIVITY_TYPE_HTTP_TRANSACTION = 0x0002;
+
+ const unsigned long ACTIVITY_SUBTYPE_REQUEST_HEADER = 0x5001;
+ const unsigned long ACTIVITY_SUBTYPE_REQUEST_BODY_SENT = 0x5002;
+ const unsigned long ACTIVITY_SUBTYPE_RESPONSE_START = 0x5003;
+ const unsigned long ACTIVITY_SUBTYPE_RESPONSE_HEADER = 0x5004;
+ const unsigned long ACTIVITY_SUBTYPE_RESPONSE_COMPLETE = 0x5005;
+ const unsigned long ACTIVITY_SUBTYPE_TRANSACTION_CLOSE = 0x5006;
+
+ /**
+ * When aActivityType is ACTIVITY_TYPE_SOCKET_TRANSPORT
+ * and aActivitySubtype is STATUS_SENDING_TO
+ * aExtraSizeData will contain the count of bytes sent
+ * There may be more than one of these activities reported
+ * for a single http transaction, each aExtraSizeData
+ * represents only that portion of the total bytes sent
+ *
+ * When aActivityType is ACTIVITY_TYPE_HTTP_TRANSACTION
+ * and aActivitySubtype is ACTIVITY_SUBTYPE_REQUEST_HEADER
+ * aExtraStringData will contain the text of the header
+ *
+ * When aActivityType is ACTIVITY_TYPE_HTTP_TRANSACTION
+ * and aActivitySubtype is ACTIVITY_SUBTYPE_RESPONSE_HEADER
+ * aExtraStringData will contain the text of the header
+ *
+ * When aActivityType is ACTIVITY_TYPE_HTTP_TRANSACTION
+ * and aActivitySubtype is ACTIVITY_SUBTYPE_RESPONSE_COMPLETE
+ * aExtraSizeData will contain the count of total bytes received
+ */
+};
+
+%{C++
+
+#define NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT \
+ nsIHttpActivityObserver::ACTIVITY_TYPE_SOCKET_TRANSPORT
+#define NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION \
+ nsIHttpActivityObserver::ACTIVITY_TYPE_HTTP_TRANSACTION
+
+#define NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_REQUEST_HEADER
+#define NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_REQUEST_BODY_SENT
+#define NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_RESPONSE_START
+#define NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_RESPONSE_HEADER
+#define NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_RESPONSE_COMPLETE
+#define NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE \
+ nsIHttpActivityObserver::ACTIVITY_SUBTYPE_TRANSACTION_CLOSE
+
+%}
+
+/**
+ * nsIHttpActivityDistributor
+ *
+ * This interface provides a way to register and unregister observers to the
+ * http activities.
+ */
+[scriptable, uuid(7C512CB8-582A-4625-B5B6-8639755271B5)]
+interface nsIHttpActivityDistributor : nsIHttpActivityObserver
+{
+ void addObserver(in nsIHttpActivityObserver aObserver);
+ void removeObserver(in nsIHttpActivityObserver aObserver);
+};
diff --git a/netwerk/protocol/http/nsIHttpAuthManager.idl b/netwerk/protocol/http/nsIHttpAuthManager.idl
new file mode 100644
index 000000000..9cfe1f6a7
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpAuthManager.idl
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+
+/**
+ * nsIHttpAuthManager
+ *
+ * This service provides access to cached HTTP authentication
+ * user credentials (domain, username, password) for sites
+ * visited during the current browser session.
+ *
+ * This interface exists to provide other HTTP stacks with the
+ * ability to share HTTP authentication credentials with Necko.
+ * This is currently used by the Java plugin (version 1.5 and
+ * higher) to avoid duplicate authentication prompts when the
+ * Java client fetches content from a HTTP site that the user
+ * has already logged into.
+ */
+[scriptable, uuid(54f90444-c52b-4d2d-8916-c59a2bb25938)]
+interface nsIHttpAuthManager : nsISupports
+{
+ /**
+ * Lookup auth identity.
+ *
+ * @param aScheme
+ * the URL scheme (e.g., "http"). NOTE: for proxy authentication,
+ * this should be "http" (this includes authentication for CONNECT
+ * tunneling).
+ * @param aHost
+ * the host of the server issuing a challenge (ASCII only).
+ * @param aPort
+ * the port of the server issuing a challenge.
+ * @param aAuthType
+ * optional string identifying auth type used (e.g., "basic")
+ * @param aRealm
+ * optional string identifying auth realm.
+ * @param aPath
+ * optional string identifying auth path. empty for proxy auth.
+ * @param aUserDomain
+ * return value containing user domain.
+ * @param aUserName
+ * return value containing user name.
+ * @param aUserPassword
+ * return value containing user password.
+ * @param aIsPrivate
+ * whether to look up a private or public identity (they are
+ * stored separately, for use by private browsing)
+ * @param aPrincipal
+ * the principal from which to derive information about which
+ * app/mozbrowser is in use for this request
+ */
+ void getAuthIdentity(in ACString aScheme,
+ in ACString aHost,
+ in int32_t aPort,
+ in ACString aAuthType,
+ in ACString aRealm,
+ in ACString aPath,
+ out AString aUserDomain,
+ out AString aUserName,
+ out AString aUserPassword,
+ [optional] in bool aIsPrivate,
+ [optional] in nsIPrincipal aPrincipal);
+
+ /**
+ * Store auth identity.
+ *
+ * @param aScheme
+ * the URL scheme (e.g., "http"). NOTE: for proxy authentication,
+ * this should be "http" (this includes authentication for CONNECT
+ * tunneling).
+ * @param aHost
+ * the host of the server issuing a challenge (ASCII only).
+ * @param aPort
+ * the port of the server issuing a challenge.
+ * @param aAuthType
+ * optional string identifying auth type used (e.g., "basic")
+ * @param aRealm
+ * optional string identifying auth realm.
+ * @param aPath
+ * optional string identifying auth path. empty for proxy auth.
+ * @param aUserDomain
+ * optional string containing user domain.
+ * @param aUserName
+ * optional string containing user name.
+ * @param aUserPassword
+ * optional string containing user password.
+ * @param aIsPrivate
+ * whether to store a private or public identity (they are
+ * stored separately, for use by private browsing)
+ * @param aPrincipal
+ * the principal from which to derive information about which
+ * app/mozbrowser is in use for this request
+ */
+ void setAuthIdentity(in ACString aScheme,
+ in ACString aHost,
+ in int32_t aPort,
+ in ACString aAuthType,
+ in ACString aRealm,
+ in ACString aPath,
+ in AString aUserDomain,
+ in AString aUserName,
+ in AString aUserPassword,
+ [optional] in boolean aIsPrivate,
+ [optional] in nsIPrincipal aPrincipal);
+
+ /**
+ * Clear all auth cache.
+ */
+ void clearAll();
+};
diff --git a/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl b/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl
new file mode 100644
index 000000000..f02e20b91
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProxiedChannel.idl"
+#include "nsIRequest.idl"
+
+interface nsILoadGroup;
+interface nsIURI;
+interface nsIInterfaceRequestor;
+
+[scriptable, uuid(701093ac-5c7f-429c-99e3-423b041fccb4)]
+interface nsIHttpAuthenticableChannel : nsIProxiedChannel
+{
+ /**
+ * If the channel being authenticated is using SSL.
+ */
+ readonly attribute boolean isSSL;
+
+ /**
+ * Returns if the proxy HTTP method used is CONNECT. If no proxy is being
+ * used it must return PR_FALSE.
+ */
+ readonly attribute boolean proxyMethodIsConnect;
+
+ /**
+ * Cancels the current request. See nsIRequest.
+ */
+ void cancel(in nsresult aStatus);
+
+ /**
+ * The load flags of this request. See nsIRequest.
+ */
+ readonly attribute nsLoadFlags loadFlags;
+
+ /**
+ * The URI corresponding to the channel. See nsIChannel.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * The load group of this request. It is here for querying its
+ * notificationCallbacks. See nsIRequest.
+ */
+ readonly attribute nsILoadGroup loadGroup;
+
+ /**
+ * The notification callbacks for the channel. See nsIChannel.
+ */
+ readonly attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * The HTTP request method. See nsIHttpChannel.
+ */
+ readonly attribute ACString requestMethod;
+
+ /**
+ * The "Server" response header.
+ * Return NS_ERROR_NOT_AVAILABLE if not available.
+ */
+ readonly attribute ACString serverResponseHeader;
+
+ /**
+ * The Proxy-Authenticate response header.
+ */
+ readonly attribute ACString proxyChallenges;
+
+ /**
+ * The WWW-Authenticate response header.
+ */
+ readonly attribute ACString WWWChallenges;
+
+ /**
+ * Sets the Proxy-Authorization request header. An empty string
+ * will clear it.
+ */
+ void setProxyCredentials(in ACString credentials);
+
+ /**
+ * Sets the Authorization request header. An empty string
+ * will clear it.
+ */
+ void setWWWCredentials(in ACString credentials);
+
+ /**
+ * Called when authentication information is ready and has been set on this
+ * object using setWWWCredentials/setProxyCredentials. Implementations can
+ * continue with the request and send the given information to the server.
+ *
+ * It is called asynchronously from
+ * nsIHttpChannelAuthProvider::processAuthentication if that method returns
+ * NS_ERROR_IN_PROGRESS.
+ *
+ * @note Any exceptions thrown from this method should be ignored.
+ */
+ void onAuthAvailable();
+
+ /**
+ * Notifies that the prompt was cancelled. It is called asynchronously
+ * from nsIHttpChannelAuthProvider::processAuthentication if that method
+ * returns NS_ERROR_IN_PROGRESS.
+ *
+ * @param userCancel
+ * If the user was cancelled has cancelled the authentication prompt.
+ */
+ void onAuthCancelled(in boolean userCancel);
+
+ /**
+ * Tells the channel to drop and close any sticky connection, since this
+ * connection oriented schema cannot be negotiated second time on
+ * the same connection.
+ */
+ void closeStickyConnection();
+
+ /**
+ * Tells the channel to mark the connection as allowed to restart on
+ * authentication retry. This is allowed when the request is a start
+ * of a new authentication round.
+ */
+ void connectionRestartable(in boolean restartable);
+};
diff --git a/netwerk/protocol/http/nsIHttpAuthenticator.idl b/netwerk/protocol/http/nsIHttpAuthenticator.idl
new file mode 100644
index 000000000..6777245b3
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpAuthenticator.idl
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIHttpAuthenticableChannel;
+interface nsIHttpAuthenticatorCallback;
+interface nsICancelable;
+
+/**
+ * nsIHttpAuthenticator
+ *
+ * Interface designed to allow for pluggable HTTP authentication modules.
+ * Implementations are registered under the ContractID:
+ *
+ * "@mozilla.org/network/http-authenticator;1?scheme=<auth-scheme>"
+ *
+ * where <auth-scheme> is the lower-cased value of the authentication scheme
+ * found in the server challenge per the rules of RFC 2617.
+ */
+[scriptable, uuid(fef7db8a-a4e2-49d1-9685-19ed7e309b7d)]
+interface nsIHttpAuthenticator : nsISupports
+{
+ /**
+ * Upon receipt of a server challenge, this function is called to determine
+ * whether or not the current user identity has been rejected. If true,
+ * then the user will be prompted by the channel to enter (or revise) their
+ * identity. Following this, generateCredentials will be called.
+ *
+ * If the IDENTITY_IGNORED auth flag is set, then the aInvalidateIdentity
+ * return value will be ignored, and user prompting will be suppressed.
+ *
+ * @param aChannel
+ * the http channel that received the challenge.
+ * @param aChallenge
+ * the challenge from the WWW-Authenticate/Proxy-Authenticate
+ * server response header. (possibly from the auth cache.)
+ * @param aProxyAuth
+ * flag indicating whether or not aChallenge is from a proxy.
+ * @param aSessionState
+ * see description below for generateCredentials.
+ * @param aContinuationState
+ * see description below for generateCredentials.
+ * @param aInvalidateIdentity
+ * return value indicating whether or not to prompt the user for a
+ * revised identity.
+ */
+ void challengeReceived(in nsIHttpAuthenticableChannel aChannel,
+ in string aChallenge,
+ in boolean aProxyAuth,
+ inout nsISupports aSessionState,
+ inout nsISupports aContinuationState,
+ out boolean aInvalidatesIdentity);
+
+ /**
+ * Called to generate the authentication credentials for a particular
+ * server/proxy challenge asynchronously. Credentials will be sent back
+ * to the server via an Authorization/Proxy-Authorization header.
+ *
+ * @param aChannel
+ * the http channel requesting credentials
+ * @param aCallback
+ * callback function to be called when credentials are available
+ * @param aChallenge
+ * the challenge from the WWW-Authenticate/Proxy-Authenticate
+ * server response header. (possibly from the auth cache.)
+ * @param aProxyAuth
+ * flag indicating whether or not aChallenge is from a proxy.
+ * @param aDomain
+ * string containing the domain name (if appropriate)
+ * @param aUser
+ * string containing the user name
+ * @param aPassword
+ * string containing the password
+ * @param aSessionState
+ * state stored along side the user's identity in the auth cache
+ * for the lifetime of the browser session. if a new auth cache
+ * entry is created for this challenge, then this parameter will
+ * be null. on return, the result will be stored in the new auth
+ * cache entry. this parameter is non-null when an auth cache entry
+ * is being reused. currently modification of session state is not
+ * communicated to caller, thus caching credentials obtained by
+ * asynchronous way is not supported.
+ * @param aContinuationState
+ * state held by the channel between consecutive calls to
+ * generateCredentials, assuming multiple calls are required
+ * to authenticate. this state is held for at most the lifetime of
+ * the channel.
+ * @pram aCancel
+ * returns cancellable runnable object which caller can use to cancel
+ * calling aCallback when finished.
+ */
+ void generateCredentialsAsync(in nsIHttpAuthenticableChannel aChannel,
+ in nsIHttpAuthenticatorCallback aCallback,
+ in string aChallenge,
+ in boolean aProxyAuth,
+ in wstring aDomain,
+ in wstring aUser,
+ in wstring aPassword,
+ in nsISupports aSessionState,
+ in nsISupports aContinuationState,
+ out nsICancelable aCancel);
+ /**
+ * Called to generate the authentication credentials for a particular
+ * server/proxy challenge. This is the value that will be sent back
+ * to the server via an Authorization/Proxy-Authorization header.
+ *
+ * This function may be called using a cached challenge provided the
+ * authenticator sets the REUSABLE_CHALLENGE flag.
+ *
+ * @param aChannel
+ * the http channel requesting credentials
+ * @param aChallenge
+ * the challenge from the WWW-Authenticate/Proxy-Authenticate
+ * server response header. (possibly from the auth cache.)
+ * @param aProxyAuth
+ * flag indicating whether or not aChallenge is from a proxy.
+ * @param aDomain
+ * string containing the domain name (if appropriate)
+ * @param aUser
+ * string containing the user name
+ * @param aPassword
+ * string containing the password
+ * @param aSessionState
+ * state stored along side the user's identity in the auth cache
+ * for the lifetime of the browser session. if a new auth cache
+ * entry is created for this challenge, then this parameter will
+ * be null. on return, the result will be stored in the new auth
+ * cache entry. this parameter is non-null when an auth cache entry
+ * is being reused.
+ * @param aContinuationState
+ * state held by the channel between consecutive calls to
+ * generateCredentials, assuming multiple calls are required
+ * to authenticate. this state is held for at most the lifetime of
+ * the channel.
+ * @param aFlags
+ * authenticator may return one of the generate flags bellow.
+ */
+ string generateCredentials(in nsIHttpAuthenticableChannel aChannel,
+ in string aChallenge,
+ in boolean aProxyAuth,
+ in wstring aDomain,
+ in wstring aUser,
+ in wstring aPassword,
+ inout nsISupports aSessionState,
+ inout nsISupports aContinuationState,
+ out unsigned long aFlags);
+
+ /**
+ * Generate flags
+ */
+
+ /**
+ * Indicates that the authenticator has used an out-of-band or internal
+ * source of identity and tells the consumer that it must not cache
+ * the returned identity because it might not be valid and would overwrite
+ * the cached identity. See bug 542318 comment 32.
+ */
+ const unsigned long USING_INTERNAL_IDENTITY = (1<<0);
+
+ /**
+ * Flags defining various properties of the authenticator.
+ */
+ readonly attribute unsigned long authFlags;
+
+ /**
+ * A request based authentication scheme only authenticates an individual
+ * request (or a set of requests under the same authentication domain as
+ * defined by RFC 2617). BASIC and DIGEST are request based authentication
+ * schemes.
+ */
+ const unsigned long REQUEST_BASED = (1<<0);
+
+ /**
+ * A connection based authentication scheme authenticates an individual
+ * connection. Multiple requests may be issued over the connection without
+ * repeating the authentication steps. Connection based authentication
+ * schemes can associate state with the connection being authenticated via
+ * the aContinuationState parameter (see generateCredentials).
+ */
+ const unsigned long CONNECTION_BASED = (1<<1);
+
+ /**
+ * The credentials returned from generateCredentials may be reused with any
+ * other URLs within "the protection space" as defined by RFC 2617 section
+ * 1.2. If this flag is not set, then generateCredentials must be called
+ * for each request within the protection space. REUSABLE_CREDENTIALS
+ * implies REUSABLE_CHALLENGE.
+ */
+ const unsigned long REUSABLE_CREDENTIALS = (1<<2);
+
+ /**
+ * A challenge may be reused to later generate credentials in anticipation
+ * of a duplicate server challenge for URLs within "the protection space"
+ * as defined by RFC 2617 section 1.2.
+ */
+ const unsigned long REUSABLE_CHALLENGE = (1<<3);
+
+ /**
+ * This flag indicates that the identity of the user is not required by
+ * this authentication scheme.
+ */
+ const unsigned long IDENTITY_IGNORED = (1<<10);
+
+ /**
+ * This flag indicates that the identity of the user includes a domain
+ * attribute that the user must supply.
+ */
+ const unsigned long IDENTITY_INCLUDES_DOMAIN = (1<<11);
+
+ /**
+ * This flag indicates that the identity will be sent encrypted. It does
+ * not make sense to combine this flag with IDENTITY_IGNORED.
+ */
+ const unsigned long IDENTITY_ENCRYPTED = (1<<12);
+};
+
+%{C++
+#define NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX \
+ "@mozilla.org/network/http-authenticator;1?scheme="
+%}
diff --git a/netwerk/protocol/http/nsIHttpChannel.idl b/netwerk/protocol/http/nsIHttpChannel.idl
new file mode 100644
index 000000000..75ec2c739
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -0,0 +1,472 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIChannel.idl"
+
+interface nsIHttpHeaderVisitor;
+
+/**
+ * nsIHttpChannel
+ *
+ * This interface allows for the modification of HTTP request parameters and
+ * the inspection of the resulting HTTP response status and headers when they
+ * become available.
+ */
+[builtinclass, scriptable, uuid(c5a4a073-4539-49c7-a3f2-cec3f0619c6c)]
+interface nsIHttpChannel : nsIChannel
+{
+ /**************************************************************************
+ * REQUEST CONFIGURATION
+ *
+ * Modifying request parameters after asyncOpen has been called is an error.
+ */
+
+ /**
+ * Set/get the HTTP request method (default is "GET"). Both setter and
+ * getter are case sensitive.
+ *
+ * This attribute may only be set before the channel is opened.
+ *
+ * NOTE: The data for a "POST" or "PUT" request can be configured via
+ * nsIUploadChannel; however, after setting the upload data, it may be
+ * necessary to set the request method explicitly. The documentation
+ * for nsIUploadChannel has further details.
+ *
+ * @throws NS_ERROR_IN_PROGRESS if set after the channel has been opened.
+ */
+ attribute ACString requestMethod;
+
+ /**
+ * Get/set the HTTP referrer URI. This is the address (URI) of the
+ * resource from which this channel's URI was obtained (see RFC2616 section
+ * 14.36).
+ *
+ * This attribute may only be set before the channel is opened.
+ *
+ * NOTE: The channel may silently refuse to set the Referer header if the
+ * URI does not pass certain security checks (e.g., a "https://" URL will
+ * never be sent as the referrer for a plaintext HTTP request). The
+ * implementation is not required to throw an exception when the referrer
+ * URI is rejected.
+ *
+ * @throws NS_ERROR_IN_PROGRESS if set after the channel has been opened.
+ * @throws NS_ERROR_FAILURE if used for setting referrer during
+ * visitRequestHeaders. Getting the value will not throw.
+ */
+ attribute nsIURI referrer;
+
+ /**
+ * Referrer policies. See ReferrerPolicy.h for more details.
+ */
+
+
+ /* The undefined state, usually used to check the need of
+ overruling document-wide referrer policy. */
+ const unsigned long REFERRER_POLICY_UNSET = 4294967295; // UINT32_MAX
+
+ /* default state, a shorter name for no-referrer-when-downgrade */
+ const unsigned long REFERRER_POLICY_DEFAULT = 0;
+ /* default state, doesn't send referrer from https->http */
+ const unsigned long REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE = 0;
+ /* sends no referrer */
+ const unsigned long REFERRER_POLICY_NO_REFERRER = 1;
+ /* only sends the origin of the referring URL */
+ const unsigned long REFERRER_POLICY_ORIGIN = 2;
+ /* same as default, but reduced to ORIGIN when cross-origin. */
+ const unsigned long REFERRER_POLICY_ORIGIN_WHEN_XORIGIN = 3;
+ /* always sends the referrer, even on downgrade. */
+ const unsigned long REFERRER_POLICY_UNSAFE_URL = 4;
+ /* send referrer when same-origin, no referrer when cross-origin */
+ const unsigned long REFERRER_POLICY_SAME_ORIGIN = 5;
+ /* send origin when request from https->https or http->http(s)
+ No referrer when request from https->http. */
+ const unsigned long REFERRER_POLICY_STRICT_ORIGIN = 6;
+ /* send referrer when same-origin,
+ Send origin when cross-origin from https->https or http->http(s)
+ No referrer when request from https->http. */
+ const unsigned long REFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN = 7;
+
+ /**
+ * Get the HTTP referrer policy. The policy is retrieved from the meta
+ * referrer tag, which can be one of many values (see ReferrerPolicy.h for
+ * more details).
+ */
+ readonly attribute unsigned long referrerPolicy;
+
+ /**
+ * Set the HTTP referrer URI with a referrer policy.
+ * @throws NS_ERROR_FAILURE if called during visitRequestHeaders.
+ */
+ void setReferrerWithPolicy(in nsIURI referrer, in unsigned long referrerPolicy);
+
+ /**
+ * Returns the network protocol used to fetch the resource as identified
+ * by the ALPN Protocol ID.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ readonly attribute ACString protocolVersion;
+
+ /**
+ * size consumed by the response header fields and the response payload body
+ */
+ readonly attribute uint64_t transferSize;
+
+ /**
+ * The size of the message body received by the client,
+ * after removing any applied content-codings
+ */
+ readonly attribute uint64_t decodedBodySize;
+
+ /**
+ * The size in octets of the payload body, prior to removing content-codings
+ */
+ readonly attribute uint64_t encodedBodySize;
+
+ /**
+ * Get the value of a particular request header.
+ *
+ * @param aHeader
+ * The case-insensitive name of the request header to query (e.g.,
+ * "Cache-Control").
+ *
+ * @return the value of the request header.
+ * @throws NS_ERROR_NOT_AVAILABLE if the header is not set.
+ */
+ ACString getRequestHeader(in ACString aHeader);
+
+ /**
+ * Set the value of a particular request header.
+ *
+ * This method allows, for example, the cookies module to add "Cookie"
+ * headers to the outgoing HTTP request.
+ *
+ * This method may only be called before the channel is opened.
+ *
+ * @param aHeader
+ * The case-insensitive name of the request header to set (e.g.,
+ * "Cookie").
+ * @param aValue
+ * The request header value to set (e.g., "X=1").
+ * @param aMerge
+ * If true, the new header value will be merged with any existing
+ * values for the specified header. This flag is ignored if the
+ * specified header does not support merging (e.g., the "Content-
+ * Type" header can only have one value). The list of headers for
+ * which this flag is ignored is an implementation detail. If this
+ * flag is false, then the header value will be replaced with the
+ * contents of |aValue|.
+ *
+ * If aValue is empty and aMerge is false, the header will be cleared.
+ *
+ * @throws NS_ERROR_IN_PROGRESS if called after the channel has been
+ * opened.
+ * @throws NS_ERROR_FAILURE if called during visitRequestHeaders.
+ */
+ void setRequestHeader(in ACString aHeader,
+ in ACString aValue,
+ in boolean aMerge);
+
+ /**
+ * Set a request header with empty value.
+ *
+ * This should be used with caution in the cases where the behavior of
+ * setRequestHeader ignoring empty header values is undesirable.
+ *
+ * This method may only be called before the channel is opened.
+ *
+ * @param aHeader
+ * The case-insensitive name of the request header to set (e.g.,
+ * "Cookie").
+ *
+ * @throws NS_ERROR_IN_PROGRESS if called after the channel has been
+ * opened.
+ * @throws NS_ERROR_FAILURE if called during visitRequestHeaders.
+ */
+ void setEmptyRequestHeader(in ACString aHeader);
+
+ /**
+ * Call this method to visit all request headers. Calling setRequestHeader
+ * while visiting request headers has undefined behavior. Don't do it!
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ */
+ void visitRequestHeaders(in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Call this method to visit all non-default (UA-provided) request headers.
+ * Calling setRequestHeader while visiting request headers has undefined
+ * behavior. Don't do it!
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ */
+ void visitNonDefaultRequestHeaders(in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * This attribute is a hint to the channel to indicate whether or not
+ * the underlying HTTP transaction should be allowed to be pipelined
+ * with other transactions. This should be set to FALSE, for example,
+ * if the application knows that the corresponding document is likely
+ * to be very large.
+ *
+ * This attribute is true by default, though other factors may prevent
+ * pipelining.
+ *
+ * This attribute may only be set before the channel is opened.
+ *
+ * @throws NS_ERROR_FAILURE if set after the channel has been opened.
+ */
+ attribute boolean allowPipelining;
+
+ /**
+ * This attribute of the channel indicates whether or not
+ * the underlying HTTP transaction should be honor stored Strict Transport
+ * Security directives for its principal. It defaults to true. Using
+ * OCSP to bootstrap the HTTPs is the likely use case for setting it to
+ * false.
+ *
+ * This attribute may only be set before the channel is opened.
+ *
+ * @throws NS_ERROR_IN_PROGRESS or NS_ERROR_ALREADY_OPENED
+ * if called after the channel has been opened.
+ */
+ attribute boolean allowSTS;
+
+ /**
+ * This attribute specifies the number of redirects this channel is allowed
+ * to make. If zero, the channel will fail to redirect and will generate
+ * a NS_ERROR_REDIRECT_LOOP failure status.
+ *
+ * NOTE: An HTTP redirect results in a new channel being created. If the
+ * new channel supports nsIHttpChannel, then it will be assigned a value
+ * to its |redirectionLimit| attribute one less than the value of the
+ * redirected channel's |redirectionLimit| attribute. The initial value
+ * for this attribute may be a configurable preference (depending on the
+ * implementation).
+ */
+ attribute unsigned long redirectionLimit;
+
+
+ /**************************************************************************
+ * RESPONSE INFO
+ *
+ * Accessing response info before the onStartRequest event is an error.
+ */
+
+ /**
+ * Get the HTTP response code (e.g., 200).
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ readonly attribute unsigned long responseStatus;
+
+ /**
+ * Get the HTTP response status text (e.g., "OK").
+ *
+ * NOTE: This returns the raw (possibly 8-bit) text from the server. There
+ * are no assumptions made about the charset of the returned text. You
+ * have been warned!
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ readonly attribute ACString responseStatusText;
+
+ /**
+ * Returns true if the HTTP response code indicates success. The value of
+ * nsIRequest::status will be NS_OK even when processing a 404 response
+ * because a 404 response may include a message body that (in some cases)
+ * should be shown to the user.
+ *
+ * Use this attribute to distinguish server error pages from normal pages,
+ * instead of comparing the response status manually against the set of
+ * valid response codes, if that is required by your application.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ readonly attribute boolean requestSucceeded;
+
+ /** Indicates whether channel should be treated as the main one for the
+ * current document. If manually set to true, will always remain true. Otherwise,
+ * will be true iff LOAD_DOCUMENT_URI is set in the channel's loadflags.
+ */
+ attribute boolean isMainDocumentChannel;
+
+ /**
+ * Get the value of a particular response header.
+ *
+ * @param aHeader
+ * The case-insensitive name of the response header to query (e.g.,
+ * "Set-Cookie").
+ *
+ * @return the value of the response header.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest) or if the header is
+ * not set in the response.
+ */
+ ACString getResponseHeader(in ACString header);
+
+ /**
+ * Set the value of a particular response header.
+ *
+ * This method allows, for example, the HTML content sink to inform the HTTP
+ * channel about HTTP-EQUIV headers found in HTML <META> tags.
+ *
+ * @param aHeader
+ * The case-insensitive name of the response header to set (e.g.,
+ * "Cache-control").
+ * @param aValue
+ * The response header value to set (e.g., "no-cache").
+ * @param aMerge
+ * If true, the new header value will be merged with any existing
+ * values for the specified header. This flag is ignored if the
+ * specified header does not support merging (e.g., the "Content-
+ * Type" header can only have one value). The list of headers for
+ * which this flag is ignored is an implementation detail. If this
+ * flag is false, then the header value will be replaced with the
+ * contents of |aValue|.
+ *
+ * If aValue is empty and aMerge is false, the header will be cleared.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ * @throws NS_ERROR_ILLEGAL_VALUE if changing the value of this response
+ * header is not allowed.
+ * @throws NS_ERROR_FAILURE if called during visitResponseHeaders,
+ * VisitOriginalResponseHeaders or getOriginalResponseHeader.
+ */
+ void setResponseHeader(in ACString header,
+ in ACString value,
+ in boolean merge);
+
+ /**
+ * Call this method to visit all response headers. Calling
+ * setResponseHeader while visiting response headers has undefined
+ * behavior. Don't do it!
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ void visitResponseHeaders(in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Get the value(s) of a particular response header in the form and order
+ * it has been received from the remote peer. There can be multiple headers
+ * with the same name.
+ *
+ * @param aHeader
+ * The case-insensitive name of the response header to query (e.g.,
+ * "Set-Cookie").
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest) or if the header is
+ * not set in the response.
+ */
+ void getOriginalResponseHeader(in ACString aHeader,
+ in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Call this method to visit all response headers in the form and order as
+ * they have been received from the remote peer.
+ * Calling setResponseHeader while visiting response headers has undefined
+ * behavior. Don't do it!
+ *
+ * @param aVisitor
+ * the header visitor instance.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ void visitOriginalResponseHeaders(in nsIHttpHeaderVisitor aVisitor);
+
+ /**
+ * Returns true if the server sent a "Cache-Control: no-store" response
+ * header.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ boolean isNoStoreResponse();
+
+ /**
+ * Returns true if the server sent the equivalent of a "Cache-control:
+ * no-cache" response header. Equivalent response headers include:
+ * "Pragma: no-cache", "Expires: 0", and "Expires" with a date value
+ * in the past relative to the value of the "Date" header.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ boolean isNoCacheResponse();
+
+ /**
+ * Returns true if the server sent a "Cache-Control: private" response
+ * header.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+ * has been received (before onStartRequest).
+ */
+ boolean isPrivateResponse();
+
+ /**
+ * Instructs the channel to immediately redirect to a new destination.
+ * Can only be called on channels that have not yet called their
+ * listener's OnStartRequest(). Generally that means the latest time
+ * this can be used is one of:
+ * "http-on-examine-response"
+ * "http-on-examine-merged-response"
+ * "http-on-examine-cached-response"
+ *
+ * When non-null URL is set before AsyncOpen:
+ * we attempt to redirect to the targetURI before we even start building
+ * and sending the request to the cache or the origin server.
+ * If the redirect is vetoed, we fail the channel.
+ *
+ * When set between AsyncOpen and first call to OnStartRequest being called:
+ * we attempt to redirect before we start delivery of network or cached
+ * response to the listener. If vetoed, we continue with delivery of
+ * the original content to the channel listener.
+ *
+ * When passed aTargetURI is null the channel behaves normally (can be
+ * rewritten).
+ *
+ * This method provides no explicit conflict resolution. The last
+ * caller to call it wins.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if called after the channel has already
+ * started to deliver the content to its listener.
+ */
+ void redirectTo(in nsIURI aTargetURI);
+
+ /**
+ * Identifies the request context for this load.
+ */
+ [noscript] attribute nsID requestContextID;
+
+ /**
+ * Unique ID of the channel, shared between parent and child. Needed if
+ * the channel activity needs to be monitored across process boundaries,
+ * like in devtools net monitor. See bug 1274556.
+ */
+ attribute ACString channelId;
+
+ /**
+ * ID of the top-level document's inner window. Identifies the content
+ * this channels is being load in.
+ */
+ attribute uint64_t topLevelContentWindowId;
+};
diff --git a/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl b/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl
new file mode 100644
index 000000000..eaece8c54
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICancelable.idl"
+
+interface nsIHttpChannel;
+interface nsIHttpAuthenticableChannel;
+
+/**
+ * nsIHttpChannelAuthProvider
+ *
+ * This interface is intended for providing authentication for http-style
+ * channels, like nsIHttpChannel and nsIWebSocket, which implement the
+ * nsIHttpAuthenticableChannel interface.
+ *
+ * When requesting pages AddAuthorizationHeaders MUST be called
+ * in order to get the http cached headers credentials. When the request is
+ * unsuccessful because of receiving either a 401 or 407 http response code
+ * ProcessAuthentication MUST be called and the page MUST be requested again
+ * with the new credentials that the user has provided. After a successful
+ * request, checkForSuperfluousAuth MAY be called, and disconnect MUST be
+ * called.
+ */
+
+[scriptable, uuid(788f331b-2e1f-436c-b405-4f88a31a105b)]
+interface nsIHttpChannelAuthProvider : nsICancelable
+{
+ /**
+ * Initializes the http authentication support for the channel.
+ * Implementations must hold a weak reference of the channel.
+ */
+ void init(in nsIHttpAuthenticableChannel channel);
+
+ /**
+ * Upon receipt of a server challenge, this function is called to determine
+ * the credentials to send.
+ *
+ * @param httpStatus
+ * the http status received.
+ * @param sslConnectFailed
+ * if the last ssl tunnel connection attempt was or not successful.
+ * @param callback
+ * the callback to be called when it returns NS_ERROR_IN_PROGRESS.
+ * The implementation must hold a weak reference.
+ *
+ * @returns NS_OK if the credentials were got and set successfully.
+ * NS_ERROR_IN_PROGRESS if the credentials are going to be asked to
+ * the user. The channel reference must be
+ * alive until the feedback from
+ * nsIHttpAuthenticableChannel's methods or
+ * until disconnect be called.
+ */
+ void processAuthentication(in unsigned long httpStatus,
+ in boolean sslConnectFailed);
+
+ /**
+ * Add credentials from the http auth cache.
+ *
+ * @param dontUseCachedWWWCreds
+ * When true, the method will not add any Authorization headers from
+ * the auth cache.
+ */
+ void addAuthorizationHeaders(in boolean dontUseCachedWWWCreds);
+
+ /**
+ * Check if an unnecessary(and maybe malicious) url authentication has been
+ * provided.
+ */
+ void checkForSuperfluousAuth();
+
+ /**
+ * Cancel pending user auth prompts and release the callback and channel
+ * weak references.
+ */
+ void disconnect(in nsresult status);
+};
diff --git a/netwerk/protocol/http/nsIHttpChannelChild.idl b/netwerk/protocol/http/nsIHttpChannelChild.idl
new file mode 100644
index 000000000..187a3342c
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannelChild.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[ptr] native RequestHeaderTuples(mozilla::net::RequestHeaderTuples);
+[ref] native OptionalCorsPreflightArgsRef(mozilla::OptionalCorsPreflightArgs);
+
+interface nsIPrincipal;
+interface nsIURI;
+
+[uuid(d02b96ed-2789-4f42-a25c-7abe63de7c18)]
+interface nsIHttpChannelChild : nsISupports
+{
+ void addCookiesToRequest();
+
+ // Mark this channel as requiring an interception; this will propagate
+ // to the corresponding parent channel when a redirect occurs.
+ void forceIntercepted(in boolean postRedirectChannelShouldIntercept,
+ in boolean postRedirectChannelShouldUpgrade);
+
+ // Headers that the channel client has set via SetRequestHeader.
+ readonly attribute RequestHeaderTuples clientSetRequestHeaders;
+
+ // Headers that the channel client has set via SetRequestHeader.
+ [notxpcom, nostdcall]
+ void GetClientSetCorsPreflightParameters(in OptionalCorsPreflightArgsRef args);
+
+ // This method is called by nsCORSListenerProxy if we need to remove
+ // an entry from the CORS preflight cache in the parent process.
+ void removeCorsPreflightCacheEntry(in nsIURI aURI, in nsIPrincipal aRequestingPrincipal);
+};
diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl
new file mode 100644
index 000000000..e0332286f
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include "nsTArrayForwardDeclare.h"
+template<class T> class nsCOMArray;
+class nsCString;
+namespace mozilla { namespace net { class nsHttpChannel; } }
+%}
+[ptr] native StringArray(nsTArray<nsCString>);
+[ref] native StringArrayRef(const nsTArray<nsCString>);
+[ref] native securityMessagesArray(nsCOMArray<nsISecurityConsoleMessage>);
+[ptr] native nsHttpChannelPtr(mozilla::net::nsHttpChannel);
+
+interface nsIAsyncInputStream;
+interface nsIAsyncOutputStream;
+interface nsIPrincipal;
+interface nsIProxyInfo;
+interface nsISecurityConsoleMessage;
+interface nsISocketTransport;
+interface nsIURI;
+
+/**
+ * The callback interface for nsIHttpChannelInternal::HTTPUpgrade()
+ */
+
+[scriptable, uuid(5b515449-ab64-4dba-b3cd-da8fc2f83064)]
+interface nsIHttpUpgradeListener : nsISupports
+{
+ void onTransportAvailable(in nsISocketTransport aTransport,
+ in nsIAsyncInputStream aSocketIn,
+ in nsIAsyncOutputStream aSocketOut);
+};
+
+/**
+ * Dumping ground for http. This interface will never be frozen. If you are
+ * using any feature exposed by this interface, be aware that this interface
+ * will change and you will be broken. You have been warned.
+ */
+[builtinclass, scriptable, uuid(4e28263d-1e03-46f4-aa5c-9512f91957f9)]
+interface nsIHttpChannelInternal : nsISupports
+{
+ /**
+ * An http channel can own a reference to the document URI
+ */
+ attribute nsIURI documentURI;
+
+ /**
+ * Get the major/minor version numbers for the request
+ */
+ void getRequestVersion(out unsigned long major, out unsigned long minor);
+
+ /**
+ * Get the major/minor version numbers for the response
+ */
+ void getResponseVersion(out unsigned long major, out unsigned long minor);
+
+ /*
+ * Retrieves all security messages from the security message queue
+ * and empties the queue after retrieval
+ */
+ [noscript] void takeAllSecurityMessages(in securityMessagesArray aMessages);
+
+ /**
+ * Helper method to set a cookie with a consumer-provided
+ * cookie header, _but_ using the channel's other information
+ * (URI's, prompters, date headers etc).
+ *
+ * @param aCookieHeader
+ * The cookie header to be parsed.
+ */
+ void setCookie(in string aCookieHeader);
+
+ /**
+ * Setup this channel as an application cache fallback channel.
+ */
+ void setupFallbackChannel(in string aFallbackKey);
+
+ /**
+ * This flag is set to force relevant cookies to be sent with this load
+ * even if normally they wouldn't be.
+ */
+ const unsigned long THIRD_PARTY_FORCE_ALLOW = 1 << 0;
+
+ /**
+ * When set, these flags modify the algorithm used to decide whether to
+ * send 3rd party cookies for a given channel.
+ */
+ attribute unsigned long thirdPartyFlags;
+
+ /**
+ * This attribute was added before the "flags" above and is retained here
+ * for compatibility. When set to true, has the same effect as
+ * THIRD_PARTY_FORCE_ALLOW, described above.
+ */
+ attribute boolean forceAllowThirdPartyCookie;
+
+ /**
+ * True iff the channel has been canceled.
+ */
+ readonly attribute boolean canceled;
+
+ /**
+ * External handlers may set this to true to notify the channel
+ * that it is open on behalf of a download.
+ */
+ attribute boolean channelIsForDownload;
+
+ /**
+ * The local IP address to which this channel is bound, in the
+ * format produced by PR_NetAddrToString. May be IPv4 or IPv6.
+ * Note: in the presence of NAT, this may not be the same as the
+ * address that the remote host thinks it's talking to.
+ *
+ * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+ * endpoints are not yet determined, or in any case when
+ * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+ */
+ readonly attribute AUTF8String localAddress;
+
+ /**
+ * The local port number to which this channel is bound.
+ *
+ * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+ * endpoints are not yet determined, or in any case when
+ * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+ */
+ readonly attribute int32_t localPort;
+
+ /**
+ * The IP address of the remote host that this channel is
+ * connected to, in the format produced by PR_NetAddrToString.
+ *
+ * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+ * endpoints are not yet determined, or in any case when
+ * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+ */
+ readonly attribute AUTF8String remoteAddress;
+
+ /**
+ * The remote port number that this channel is connected to.
+ *
+ * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+ * endpoints are not yet determined, or in any case when
+ * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+ */
+ readonly attribute int32_t remotePort;
+
+ /**
+ * Transfer chain of redirected cache-keys.
+ */
+ [noscript] void setCacheKeysRedirectChain(in StringArray cacheKeys);
+
+ /**
+ * HTTPUpgrade allows for the use of HTTP to bootstrap another protocol
+ * via the RFC 2616 Upgrade request header in conjunction with a 101 level
+ * response. The nsIHttpUpgradeListener will have its
+ * onTransportAvailable() method invoked if a matching 101 is processed.
+ * The arguments to onTransportAvailable provide the new protocol the low
+ * level tranport streams that are no longer used by HTTP.
+ *
+ * The onStartRequest and onStopRequest events are still delivered and the
+ * listener gets full control over the socket if and when onTransportAvailable
+ * is delievered.
+ *
+ * @param aProtocolName
+ * The value of the HTTP Upgrade request header
+ * @param aListener
+ * The callback object used to handle a successful upgrade
+ */
+ void HTTPUpgrade(in ACString aProtocolName,
+ in nsIHttpUpgradeListener aListener);
+
+ /**
+ * Enable/Disable Spdy negotiation on per channel basis.
+ * The network.http.spdy.enabled preference is still a pre-requisite
+ * for starting spdy.
+ */
+ attribute boolean allowSpdy;
+
+ /**
+ * This attribute en/disables the timeout for the first byte of an HTTP
+ * response. Enabled by default.
+ */
+ attribute boolean responseTimeoutEnabled;
+
+ /**
+ * If the underlying transport supports RWIN manipulation, this is the
+ * intiial window value for the channel. HTTP/2 implements this.
+ * 0 means no override from system default. Set before opening channel.
+ */
+ attribute unsigned long initialRwin;
+
+ /**
+ * Get value of the URI passed to nsIHttpChannel.redirectTo() if any.
+ * May return null when redirectTo() has not been called.
+ */
+ readonly attribute nsIURI apiRedirectToURI;
+
+ /**
+ * Enable/Disable use of Alternate Services with this channel.
+ * The network.http.altsvc.enabled preference is still a pre-requisite.
+ */
+ attribute boolean allowAltSvc;
+
+ /**
+ * If true, do not use newer protocol features that might have interop problems
+ * on the Internet. Intended only for use with critical infra like the updater.
+ * default is false.
+ */
+ attribute boolean beConservative;
+
+ readonly attribute PRTime lastModifiedTime;
+
+ /**
+ * Force a channel that has not been AsyncOpen'ed to skip any check for possible
+ * interception and proceed immediately to open a previously-synthesized cache
+ * entry using the provided ID.
+ */
+ void forceIntercepted(in uint64_t aInterceptionID);
+
+ readonly attribute boolean responseSynthesized;
+
+ /**
+ * Set by nsCORSListenerProxy if credentials should be included in
+ * cross-origin requests. false indicates "same-origin", users should still
+ * check flag LOAD_ANONYMOUS!
+ */
+ attribute boolean corsIncludeCredentials;
+
+ const unsigned long CORS_MODE_SAME_ORIGIN = 0;
+ const unsigned long CORS_MODE_NO_CORS = 1;
+ const unsigned long CORS_MODE_CORS = 2;
+ const unsigned long CORS_MODE_NAVIGATE = 3;
+ /**
+ * Set by nsCORSListenerProxy to indicate CORS load type. Defaults to CORS_MODE_NO_CORS.
+ */
+ attribute unsigned long corsMode;
+
+ const unsigned long REDIRECT_MODE_FOLLOW = 0;
+ const unsigned long REDIRECT_MODE_ERROR = 1;
+ const unsigned long REDIRECT_MODE_MANUAL = 2;
+ /**
+ * Set to indicate Request.redirect mode exposed during ServiceWorker
+ * interception. No policy enforcement is performed by the channel for this
+ * value.
+ */
+ attribute unsigned long redirectMode;
+
+ const unsigned long FETCH_CACHE_MODE_DEFAULT = 0;
+ const unsigned long FETCH_CACHE_MODE_NO_STORE = 1;
+ const unsigned long FETCH_CACHE_MODE_RELOAD = 2;
+ const unsigned long FETCH_CACHE_MODE_NO_CACHE = 3;
+ const unsigned long FETCH_CACHE_MODE_FORCE_CACHE = 4;
+ const unsigned long FETCH_CACHE_MODE_ONLY_IF_CACHED = 5;
+ /**
+ * Set to indicate Request.cache mode, which simulates the fetch API
+ * semantics, and is also used for exposing this value to the Web page
+ * during service worker interception.
+ */
+ attribute unsigned long fetchCacheMode;
+
+ /**
+ * The URI of the top-level window that's associated with this channel.
+ */
+ readonly attribute nsIURI topWindowURI;
+
+ /**
+ * The network interface id that's associated with this channel.
+ */
+ attribute ACString networkInterfaceId;
+
+ /**
+ * Read the proxy URI, which, if non-null, will be used to resolve
+ * proxies for this channel.
+ */
+ readonly attribute nsIURI proxyURI;
+
+ /**
+ * Make cross-origin CORS loads happen with a CORS preflight, and specify
+ * the CORS preflight parameters.
+ */
+ [noscript, notxpcom, nostdcall]
+ void setCorsPreflightParameters(in StringArrayRef unsafeHeaders);
+
+ /**
+ * When set to true, the channel will not pop any authentication prompts up
+ * to the user. When provided or cached credentials lead to an
+ * authentication failure, that failure will be propagated to the channel
+ * listener. Must be called before opening the channel, otherwise throws.
+ */
+ [infallible]
+ attribute boolean blockAuthPrompt;
+
+ /**
+ * Set to indicate Request.integrity.
+ */
+ attribute AString integrityMetadata;
+
+ /**
+ * The connection info's hash key. We use it to test connection separation.
+ */
+ readonly attribute ACString connectionInfoHashKey;
+
+ /**
+ * Returns nsHttpChannel (self) when this actually is implementing nsHttpChannel.
+ */
+ [noscript, notxpcom, nostdcall]
+ nsHttpChannelPtr queryHttpChannelImpl();
+};
diff --git a/netwerk/protocol/http/nsIHttpEventSink.idl b/netwerk/protocol/http/nsIHttpEventSink.idl
new file mode 100644
index 000000000..d003fbe15
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpEventSink.idl
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIHttpChannel;
+interface nsIURI;
+
+/**
+ * nsIHttpEventSink
+ *
+ * Implement this interface to receive control over various HTTP events. The
+ * HTTP channel will try to get this interface from its notificationCallbacks
+ * attribute, and if it doesn't find it there it will look for it from its
+ * loadGroup's notificationCallbacks attribute.
+ *
+ * These methods are called before onStartRequest, and should be handled
+ * SYNCHRONOUSLY.
+ *
+ * @deprecated Newly written code should use nsIChannelEventSink instead of this
+ * interface.
+ */
+[scriptable, uuid(9475a6af-6352-4251-90f9-d65b1cd2ea15)]
+interface nsIHttpEventSink : nsISupports
+{
+ /**
+ * Called when a redirect occurs due to a HTTP response like 302. The
+ * redirection may be to a non-http channel.
+ *
+ * @return failure cancels redirect
+ */
+ void onRedirect(in nsIHttpChannel httpChannel,
+ in nsIChannel newChannel);
+};
diff --git a/netwerk/protocol/http/nsIHttpHeaderVisitor.idl b/netwerk/protocol/http/nsIHttpHeaderVisitor.idl
new file mode 100644
index 000000000..809f7c4a4
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpHeaderVisitor.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Implement this interface to visit http headers.
+ */
+[scriptable, function, uuid(35412859-b9d9-423c-8866-2d4559fdd2be)]
+interface nsIHttpHeaderVisitor : nsISupports
+{
+ /**
+ * Called by the nsIHttpChannel implementation when visiting request and
+ * response headers.
+ *
+ * @param aHeader
+ * the header being visited.
+ * @param aValue
+ * the header value (possibly a comma delimited list).
+ *
+ * @throw any exception to terminate enumeration
+ */
+ void visitHeader(in ACString aHeader, in ACString aValue);
+};
diff --git a/netwerk/protocol/http/nsIHttpProtocolHandler.idl b/netwerk/protocol/http/nsIHttpProtocolHandler.idl
new file mode 100644
index 000000000..f333a557c
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpProtocolHandler.idl
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProxiedProtocolHandler.idl"
+
+[scriptable, uuid(c48126d9-2ddd-485b-a51a-378e917e75f8)]
+interface nsIHttpProtocolHandler : nsIProxiedProtocolHandler
+{
+ /**
+ * Get the HTTP advertised user agent string.
+ */
+ readonly attribute ACString userAgent;
+
+ /**
+ * Get the application name.
+ *
+ * @return The name of this application (eg. "Mozilla").
+ */
+ readonly attribute ACString appName;
+
+ /**
+ * Get the application version string.
+ *
+ * @return The complete version (major and minor) string. (eg. "5.0")
+ */
+ readonly attribute ACString appVersion;
+
+ /**
+ * Get the current platform.
+ *
+ * @return The platform this application is running on
+ * (eg. "Windows", "Macintosh", "X11")
+ */
+ readonly attribute ACString platform;
+
+ /**
+ * Get the current oscpu.
+ *
+ * @return The oscpu this application is running on
+ */
+ readonly attribute ACString oscpu;
+
+ /**
+ * Get the application comment misc portion.
+ */
+ readonly attribute ACString misc;
+
+};
+
+%{C++
+// ----------- Categories -----------
+/**
+ * At initialization time, the HTTP handler will initialize each service
+ * registered under this category:
+ */
+#define NS_HTTP_STARTUP_CATEGORY "http-startup-category"
+
+// ----------- Observer topics -----------
+/**
+ * nsIObserver notification corresponding to startup category. Services
+ * registered under the startup category will receive this observer topic at
+ * startup if they implement nsIObserver. The "subject" of the notification
+ * is the nsIHttpProtocolHandler instance.
+ */
+#define NS_HTTP_STARTUP_TOPIC "http-startup"
+
+/**
+ * This observer topic is notified when an HTTP channel is opened.
+ * It is similar to http-on-modify-request, except that
+ * 1) The notification is guaranteed to occur before on-modify-request, during
+ * the AsyncOpen call itself.
+ * 2) It only occurs for the initial open of a channel, not for internal
+ * asyncOpens that happen during redirects, etc.
+ * 3) Some information (most notably nsIProxiedChannel.proxyInfo) may not be set
+ * on the channel object yet.
+ *
+ * The "subject" of the notification is the nsIHttpChannel instance.
+ *
+ * Generally the 'http-on-modify-request' notification is preferred unless the
+ * synchronous, during-asyncOpen behavior that this notification provides is
+ * required.
+ */
+#define NS_HTTP_ON_OPENING_REQUEST_TOPIC "http-on-opening-request"
+
+/**
+ * Before an HTTP request is sent to the server, this observer topic is
+ * notified. The observer of this topic can then choose to set any additional
+ * headers for this request before the request is actually sent to the server.
+ * The "subject" of the notification is the nsIHttpChannel instance.
+ */
+#define NS_HTTP_ON_MODIFY_REQUEST_TOPIC "http-on-modify-request"
+
+/**
+ * After an HTTP server response is received, this observer topic is notified.
+ * The observer of this topic can interrogate the response. The "subject" of
+ * the notification is the nsIHttpChannel instance.
+ */
+#define NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC "http-on-examine-response"
+
+/**
+ * The observer of this topic is notified after the received HTTP response
+ * is merged with data from the browser cache. This means that, among other
+ * things, the Content-Type header will be set correctly.
+ */
+#define NS_HTTP_ON_EXAMINE_MERGED_RESPONSE_TOPIC "http-on-examine-merged-response"
+
+/**
+ * The observer of this topic is notified before data is read from the cache.
+ * The notification is sent if and only if there is no network communication
+ * at all.
+ */
+#define NS_HTTP_ON_EXAMINE_CACHED_RESPONSE_TOPIC "http-on-examine-cached-response"
+
+/**
+ * Before an HTTP request corresponding to a channel with the LOAD_DOCUMENT_URI
+ * flag is sent to the server, this observer topic is notified. The observer of
+ * this topic can then choose to modify the user agent for this request before
+ * the request is actually sent to the server. Additionally, the modified user
+ * agent will be propagated to sub-resource requests from the same load group.
+ */
+#define NS_HTTP_ON_USERAGENT_REQUEST_TOPIC "http-on-useragent-request"
+
+
+%}
diff --git a/netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl b/netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl
new file mode 100644
index 000000000..cf4437904
--- /dev/null
+++ b/netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ For parsing JSON from http://httpwg.org/http-extensions/opsec.html
+*/
+
+#include "nsISupports.idl"
+
+%{C++
+#define NS_WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID "@mozilla.org/network/well-known-opportunistic-utils;1"
+%}
+
+[scriptable, uuid(b4f96c89-5238-450c-8bda-e12c26f1d150)]
+interface nsIWellKnownOpportunisticUtils : nsISupports
+{
+ void verify(in ACString aJSON,
+ in ACString aOrigin,
+ in long aAlternatePort);
+
+ readonly attribute bool valid;
+ readonly attribute bool mixed; /* mixed-scheme */
+ readonly attribute long lifetime;
+};
diff --git a/netwerk/protocol/moz.build b/netwerk/protocol/moz.build
new file mode 100644
index 000000000..e47f73df4
--- /dev/null
+++ b/netwerk/protocol/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += sorted(CONFIG['NECKO_PROTOCOLS'])
diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.cpp b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
new file mode 100644
index 000000000..238bc344a
--- /dev/null
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ExtensionProtocolHandler.h"
+
+#include "nsIAddonPolicyService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsIStreamListener.h"
+#include "nsIRequestObserver.h"
+#include "nsIInputStreamChannel.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIStreamConverterService.h"
+#include "nsIPipe.h"
+#include "nsNetUtil.h"
+#include "LoadInfo.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_QUERY_INTERFACE(ExtensionProtocolHandler, nsISubstitutingProtocolHandler,
+ nsIProtocolHandler, nsIProtocolHandlerWithDynamicFlags,
+ nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
+
+nsresult
+ExtensionProtocolHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* aFlags)
+{
+ // In general a moz-extension URI is only loadable by chrome, but a whitelisted
+ // subset are web-accessible (and cross-origin fetchable). Check that whitelist.
+ nsCOMPtr<nsIAddonPolicyService> aps = do_GetService("@mozilla.org/addons/policy-service;1");
+ bool loadableByAnyone = false;
+ if (aps) {
+ nsresult rv = aps->ExtensionURILoadableByAnyone(aURI, &loadableByAnyone);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *aFlags = URI_STD | URI_IS_LOCAL_RESOURCE | (loadableByAnyone ? (URI_LOADABLE_BY_ANYONE | URI_FETCHABLE_BY_ANYONE) : URI_DANGEROUS_TO_LOAD);
+ return NS_OK;
+}
+
+class PipeCloser : public nsIRequestObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit PipeCloser(nsIOutputStream* aOutputStream) :
+ mOutputStream(aOutputStream)
+ {
+ }
+
+ NS_IMETHOD OnStartRequest(nsIRequest*, nsISupports*) override
+ {
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnStopRequest(nsIRequest*, nsISupports*, nsresult aStatusCode) override
+ {
+ NS_ENSURE_TRUE(mOutputStream, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = mOutputStream->Close();
+ mOutputStream = nullptr;
+ return rv;
+ }
+
+protected:
+ virtual ~PipeCloser() {}
+
+private:
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+};
+
+NS_IMPL_ISUPPORTS(PipeCloser, nsIRequestObserver)
+
+bool
+ExtensionProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult)
+{
+ // Create special moz-extension:-pages such as moz-extension://foo/_blank.html
+ // for all registered extensions. We can't just do this as a substitution
+ // because substitutions can only match on host.
+ if (!SubstitutingProtocolHandler::HasSubstitution(aHost)) {
+ return false;
+ }
+ if (aPathname.EqualsLiteral("/_blank.html")) {
+ aResult.AssignLiteral("about:blank");
+ return true;
+ }
+ if (aPathname.EqualsLiteral("/_generated_background_page.html")) {
+ nsCOMPtr<nsIAddonPolicyService> aps =
+ do_GetService("@mozilla.org/addons/policy-service;1");
+ if (!aps) {
+ return false;
+ }
+ nsresult rv = aps->GetGeneratedBackgroundPageUrl(aHost, aResult);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (!aResult.IsEmpty()) {
+ MOZ_RELEASE_ASSERT(Substring(aResult, 0, 5).Equals("data:"));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult
+ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString ext;
+ rv = url->GetFileExtension(ext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!ext.LowerCaseEqualsLiteral("css")) {
+ return NS_OK;
+ }
+
+ // Filter CSS files to replace locale message tokens with localized strings.
+
+ nsCOMPtr<nsIStreamConverterService> convService =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char* kFromType = "application/vnd.mozilla.webext.unlocalized";
+ const char* kToType = "text/css";
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ if (aLoadInfo &&
+ aLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
+ // If the channel needs to enforce CORS, we need to open the channel async.
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(outputStream),
+ 0, UINT32_MAX, true, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsIRequestObserver> observer = new PipeCloser(outputStream);
+ rv = NS_NewSimpleStreamListener(getter_AddRefs(listener), outputStream, observer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStreamListener> converter;
+ rv = convService->AsyncConvertData(kFromType, kToType, listener,
+ aURI, getter_AddRefs(converter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ static_cast<LoadInfo*>(aLoadInfo)->CloneForNewRequest();
+ (*result)->SetLoadInfo(loadInfo);
+
+ rv = (*result)->AsyncOpen2(converter);
+ } else {
+ // Stylesheet loads for extension content scripts require a sync channel.
+
+ nsCOMPtr<nsIInputStream> sourceStream;
+ if (aLoadInfo && aLoadInfo->GetEnforceSecurity()) {
+ rv = (*result)->Open2(getter_AddRefs(sourceStream));
+ } else {
+ rv = (*result)->Open(getter_AddRefs(sourceStream));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = convService->Convert(sourceStream, kFromType, kToType,
+ aURI, getter_AddRefs(inputStream));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aURI, inputStream,
+ NS_LITERAL_CSTRING("text/css"),
+ NS_LITERAL_CSTRING("utf-8"),
+ aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel.swap(*result);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.h b/netwerk/protocol/res/ExtensionProtocolHandler.h
new file mode 100644
index 000000000..081f8cd62
--- /dev/null
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ExtensionProtocolHandler_h___
+#define ExtensionProtocolHandler_h___
+
+#include "SubstitutingProtocolHandler.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace net {
+
+class ExtensionProtocolHandler final : public nsISubstitutingProtocolHandler,
+ public nsIProtocolHandlerWithDynamicFlags,
+ public SubstitutingProtocolHandler,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIPROTOCOLHANDLERWITHDYNAMICFLAGS
+ NS_FORWARD_NSIPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+ NS_FORWARD_NSISUBSTITUTINGPROTOCOLHANDLER(SubstitutingProtocolHandler::)
+
+ ExtensionProtocolHandler() : SubstitutingProtocolHandler("moz-extension") {}
+
+protected:
+ ~ExtensionProtocolHandler() {}
+
+ bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) override;
+
+ virtual nsresult SubstituteChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* ExtensionProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
new file mode 100644
index 000000000..99061e0f7
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -0,0 +1,404 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/Unused.h"
+
+#include "SubstitutingProtocolHandler.h"
+#include "nsIChannel.h"
+#include "nsIIOService.h"
+#include "nsIFile.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+
+using mozilla::dom::ContentParent;
+
+namespace mozilla {
+namespace net {
+
+// Log module for Substituting Protocol logging. We keep the pre-existing module
+// name of "nsResProtocol" to avoid disruption.
+static LazyLogModule gResLog("nsResProtocol");
+
+static NS_DEFINE_CID(kSubstitutingURLCID, NS_SUBSTITUTINGURL_CID);
+
+//---------------------------------------------------------------------------------
+// SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile resolution
+//---------------------------------------------------------------------------------
+
+nsresult
+SubstitutingURL::EnsureFile()
+{
+ nsAutoCString ourScheme;
+ nsresult rv = GetScheme(ourScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the handler associated with this scheme. It would be nice to just
+ // pass this in when constructing SubstitutingURLs, but we need a generic
+ // factory constructor.
+ nsCOMPtr<nsIIOService> io = do_GetIOService(&rv);
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = io->GetProtocolHandler(ourScheme.get(), getter_AddRefs(handler));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISubstitutingProtocolHandler> substHandler = do_QueryInterface(handler);
+ MOZ_ASSERT(substHandler);
+
+ nsAutoCString spec;
+ rv = substHandler->ResolveURI(this, spec);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString scheme;
+ rv = net_ExtractURLScheme(spec, scheme);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Bug 585869:
+ // In most cases, the scheme is jar if it's not file.
+ // Regardless, net_GetFileFromURLSpec should be avoided
+ // when the scheme isn't file.
+ if (!scheme.EqualsLiteral("file"))
+ return NS_ERROR_NO_INTERFACE;
+
+ return net_GetFileFromURLSpec(spec, getter_AddRefs(mFile));
+}
+
+/* virtual */ nsStandardURL*
+SubstitutingURL::StartClone()
+{
+ SubstitutingURL *clone = new SubstitutingURL();
+ return clone;
+}
+
+NS_IMETHODIMP
+SubstitutingURL::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ *aClassIDNoAlloc = kSubstitutingURLCID;
+ return NS_OK;
+}
+
+SubstitutingProtocolHandler::SubstitutingProtocolHandler(const char* aScheme, uint32_t aFlags,
+ bool aEnforceFileOrJar)
+ : mScheme(aScheme)
+ , mSubstitutions(16)
+ , mEnforceFileOrJar(aEnforceFileOrJar)
+{
+ mFlags.emplace(aFlags);
+ ConstructInternal();
+}
+
+SubstitutingProtocolHandler::SubstitutingProtocolHandler(const char* aScheme)
+ : mScheme(aScheme)
+ , mSubstitutions(16)
+ , mEnforceFileOrJar(true)
+{
+ ConstructInternal();
+}
+
+void
+SubstitutingProtocolHandler::ConstructInternal()
+{
+ nsresult rv;
+ mIOService = do_GetIOService(&rv);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOService);
+}
+
+//
+// IPC marshalling.
+//
+
+nsresult
+SubstitutingProtocolHandler::CollectSubstitutions(InfallibleTArray<SubstitutionMapping>& aMappings)
+{
+ for (auto iter = mSubstitutions.ConstIter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIURI> uri = iter.Data();
+ SerializedURI serialized;
+ if (uri) {
+ nsresult rv = uri->GetSpec(serialized.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uri->GetOriginCharset(serialized.charset);
+ }
+ SubstitutionMapping substitution = { mScheme, nsCString(iter.Key()), serialized };
+ aMappings.AppendElement(substitution);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+SubstitutingProtocolHandler::SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI)
+{
+ if (GeckoProcessType_Content == XRE_GetProcessType()) {
+ return NS_OK;
+ }
+
+ nsTArray<ContentParent*> parents;
+ ContentParent::GetAll(parents);
+ if (!parents.Length()) {
+ return NS_OK;
+ }
+
+ SubstitutionMapping mapping;
+ mapping.scheme = mScheme;
+ mapping.path = aRoot;
+ if (aBaseURI) {
+ nsresult rv = aBaseURI->GetSpec(mapping.resolvedURI.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aBaseURI->GetOriginCharset(mapping.resolvedURI.charset);
+ }
+
+ for (uint32_t i = 0; i < parents.Length(); i++) {
+ Unused << parents[i]->SendRegisterChromeItem(mapping);
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsIProtocolHandler
+//----------------------------------------------------------------------------
+
+nsresult
+SubstitutingProtocolHandler::GetScheme(nsACString &result)
+{
+ result = mScheme;
+ return NS_OK;
+}
+
+nsresult
+SubstitutingProtocolHandler::GetDefaultPort(int32_t *result)
+{
+ *result = -1;
+ return NS_OK;
+}
+
+nsresult
+SubstitutingProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ if (mFlags.isNothing()) {
+ NS_WARNING("Trying to get protocol flags the wrong way - use nsIProtocolHandlerWithDynamicFlags instead");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *result = mFlags.ref();
+ return NS_OK;
+}
+
+nsresult
+SubstitutingProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+ nsresult rv;
+
+ RefPtr<SubstitutingURL> url = new SubstitutingURL();
+ if (!url)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // unescape any %2f and %2e to make sure nsStandardURL coalesces them.
+ // Later net_GetFileFromURLSpec() will do a full unescape and we want to
+ // treat them the same way the file system will. (bugs 380994, 394075)
+ nsAutoCString spec;
+ const char *src = aSpec.BeginReading();
+ const char *end = aSpec.EndReading();
+ const char *last = src;
+
+ spec.SetCapacity(aSpec.Length()+1);
+ for ( ; src < end; ++src) {
+ if (*src == '%' && (src < end-2) && *(src+1) == '2') {
+ char ch = '\0';
+ if (*(src+2) == 'f' || *(src+2) == 'F') {
+ ch = '/';
+ } else if (*(src+2) == 'e' || *(src+2) == 'E') {
+ ch = '.';
+ }
+
+ if (ch) {
+ if (last < src) {
+ spec.Append(last, src-last);
+ }
+ spec.Append(ch);
+ src += 2;
+ last = src+1; // src will be incremented by the loop
+ }
+ }
+ }
+ if (last < src)
+ spec.Append(last, src-last);
+
+ rv = url->Init(nsIStandardURL::URLTYPE_STANDARD, -1, spec, aCharset, aBaseURI);
+ if (NS_SUCCEEDED(rv)) {
+ url.forget(result);
+ }
+ return rv;
+}
+
+nsresult
+SubstitutingProtocolHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ nsAutoCString spec;
+ nsresult rv = ResolveURI(uri, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> newURI;
+ rv = NS_NewURI(getter_AddRefs(newURI), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewChannelInternal(result, newURI, aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsLoadFlags loadFlags = 0;
+ (*result)->GetLoadFlags(&loadFlags);
+ (*result)->SetLoadFlags(loadFlags & ~nsIChannel::LOAD_REPLACE);
+ rv = (*result)->SetOriginalURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return SubstituteChannel(uri, aLoadInfo, result);
+}
+
+nsresult
+SubstitutingProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+nsresult
+SubstitutingProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsISubstitutingProtocolHandler
+//----------------------------------------------------------------------------
+
+nsresult
+SubstitutingProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *baseURI)
+{
+ if (!baseURI) {
+ mSubstitutions.Remove(root);
+ return SendSubstitution(root, baseURI);
+ }
+
+ // If baseURI isn't a same-scheme URI, we can set the substitution immediately.
+ nsAutoCString scheme;
+ nsresult rv = baseURI->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!scheme.Equals(mScheme)) {
+ if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")
+ && !scheme.EqualsLiteral("app")) {
+ NS_WARNING("Refusing to create substituting URI to non-file:// target");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mSubstitutions.Put(root, baseURI);
+ return SendSubstitution(root, baseURI);
+ }
+
+ // baseURI is a same-type substituting URI, let's resolve it first.
+ nsAutoCString newBase;
+ rv = ResolveURI(baseURI, newBase);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> newBaseURI;
+ rv = mIOService->NewURI(newBase, nullptr, nullptr, getter_AddRefs(newBaseURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSubstitutions.Put(root, newBaseURI);
+ return SendSubstitution(root, newBaseURI);
+}
+
+nsresult
+SubstitutingProtocolHandler::GetSubstitution(const nsACString& root, nsIURI **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ if (mSubstitutions.Get(root, result))
+ return NS_OK;
+
+ return GetSubstitutionInternal(root, result);
+}
+
+nsresult
+SubstitutingProtocolHandler::HasSubstitution(const nsACString& root, bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = HasSubstitution(root);
+ return NS_OK;
+}
+
+nsresult
+SubstitutingProtocolHandler::ResolveURI(nsIURI *uri, nsACString &result)
+{
+ nsresult rv;
+
+ nsAutoCString host;
+ nsAutoCString path;
+ nsAutoCString pathname;
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
+ if (!url) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ rv = uri->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = uri->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = url->GetFilePath(pathname);
+ if (NS_FAILED(rv)) return rv;
+
+ if (ResolveSpecialCases(host, path, pathname, result)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> baseURI;
+ rv = GetSubstitution(host, getter_AddRefs(baseURI));
+ if (NS_FAILED(rv)) return rv;
+
+ // Unescape the path so we can perform some checks on it.
+ NS_UnescapeURL(pathname);
+ if (pathname.FindChar('\\') != -1) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // Some code relies on an empty path resolving to a file rather than a
+ // directory.
+ NS_ASSERTION(path.CharAt(0) == '/', "Path must begin with '/'");
+ if (path.Length() == 1) {
+ rv = baseURI->GetSpec(result);
+ } else {
+ // Make sure we always resolve the path as file-relative to our target URI.
+ path.InsertLiteral(".", 0);
+
+ rv = baseURI->Resolve(path, result);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (MOZ_LOG_TEST(gResLog, LogLevel::Debug)) {
+ nsAutoCString spec;
+ uri->GetAsciiSpec(spec);
+ MOZ_LOG(gResLog, LogLevel::Debug, ("%s\n -> %s\n", spec.get(), PromiseFlatCString(result).get()));
+ }
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/res/SubstitutingProtocolHandler.h b/netwerk/protocol/res/SubstitutingProtocolHandler.h
new file mode 100644
index 000000000..a59c5595d
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SubstitutingProtocolHandler_h___
+#define SubstitutingProtocolHandler_h___
+
+#include "nsISubstitutingProtocolHandler.h"
+
+#include "nsInterfaceHashtable.h"
+#include "nsIOService.h"
+#include "nsStandardURL.h"
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/Maybe.h"
+
+class nsIIOService;
+
+namespace mozilla {
+namespace net {
+
+//
+// Base class for resource://-like substitution protocols.
+//
+// If you add a new protocol, make sure to change nsChromeRegistryChrome
+// to properly invoke CollectSubstitutions at the right time.
+class SubstitutingProtocolHandler
+{
+public:
+ SubstitutingProtocolHandler(const char* aScheme, uint32_t aFlags, bool aEnforceFileOrJar = true);
+ explicit SubstitutingProtocolHandler(const char* aScheme);
+
+ NS_INLINE_DECL_REFCOUNTING(SubstitutingProtocolHandler);
+ NS_DECL_NON_VIRTUAL_NSIPROTOCOLHANDLER;
+ NS_DECL_NON_VIRTUAL_NSISUBSTITUTINGPROTOCOLHANDLER;
+
+ bool HasSubstitution(const nsACString& aRoot) const { return mSubstitutions.Get(aRoot, nullptr); }
+
+ nsresult CollectSubstitutions(InfallibleTArray<SubstitutionMapping>& aResources);
+
+protected:
+ virtual ~SubstitutingProtocolHandler() {}
+ void ConstructInternal();
+
+ nsresult SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI);
+
+ // Override this in the subclass to try additional lookups after checking
+ // mSubstitutions.
+ virtual nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult)
+ {
+ *aResult = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Override this in the subclass to check for special case when resolving URIs
+ // _before_ checking substitutions.
+ virtual bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult)
+ {
+ return false;
+ }
+
+ // Override this in the subclass to check for special case when opening
+ // channels.
+ virtual nsresult SubstituteChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result)
+ {
+ return NS_OK;
+ }
+
+ nsIIOService* IOService() { return mIOService; }
+
+private:
+ nsCString mScheme;
+ Maybe<uint32_t> mFlags;
+ nsInterfaceHashtable<nsCStringHashKey,nsIURI> mSubstitutions;
+ nsCOMPtr<nsIIOService> mIOService;
+
+ // In general, we expect the principal of a document loaded from a
+ // substituting URI to be a codebase principal for that URI (rather than
+ // a principal for whatever is underneath). However, this only works if
+ // the protocol handler for the underlying URI doesn't set an explicit
+ // owner (which chrome:// does, for example). So we want to require that
+ // substituting URIs only map to other URIs of the same type, or to
+ // file:// and jar:// URIs.
+ //
+ // Enforcing this for ye olde resource:// URIs could carry compat risks, so
+ // we just try to enforce it on new protocols going forward.
+ bool mEnforceFileOrJar;
+};
+
+// SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile resolution
+class SubstitutingURL : public nsStandardURL
+{
+public:
+ SubstitutingURL() : nsStandardURL(true) {}
+ virtual nsStandardURL* StartClone();
+ virtual nsresult EnsureFile();
+ NS_IMETHOD GetClassIDNoAlloc(nsCID *aCID);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SubstitutingProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/moz.build b/netwerk/protocol/res/moz.build
new file mode 100644
index 000000000..37e2316b0
--- /dev/null
+++ b/netwerk/protocol/res/moz.build
@@ -0,0 +1,26 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIResProtocolHandler.idl',
+ 'nsISubstitutingProtocolHandler.idl',
+]
+
+XPIDL_MODULE = 'necko_res'
+
+UNIFIED_SOURCES += [
+ 'ExtensionProtocolHandler.cpp',
+ 'nsResProtocolHandler.cpp',
+ 'SubstitutingProtocolHandler.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
diff --git a/netwerk/protocol/res/nsIResProtocolHandler.idl b/netwerk/protocol/res/nsIResProtocolHandler.idl
new file mode 100644
index 000000000..56c597f4c
--- /dev/null
+++ b/netwerk/protocol/res/nsIResProtocolHandler.idl
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISubstitutingProtocolHandler.idl"
+
+/**
+ * Protocol handler interface for the resource:// protocol
+ */
+[scriptable, uuid(241d34ac-9ed5-46d7-910c-7a9d914aa0c5)]
+interface nsIResProtocolHandler : nsISubstitutingProtocolHandler
+{
+};
diff --git a/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
new file mode 100644
index 000000000..e2c816a85
--- /dev/null
+++ b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+/**
+ * Protocol handler superinterface for a protocol which performs substitutions
+ * from URIs of its scheme to URIs of another scheme.
+ */
+[scriptable, uuid(154c64fd-a69e-4105-89f8-bd7dfe621372)]
+interface nsISubstitutingProtocolHandler : nsIProtocolHandler
+{
+ /**
+ * Sets the substitution for the root key:
+ * resource://root/path ==> baseURI.resolve(path)
+ *
+ * A null baseURI removes the specified substitution.
+ *
+ * A root key should always be lowercase; however, this may not be
+ * enforced.
+ */
+ void setSubstitution(in ACString root, in nsIURI baseURI);
+
+ /**
+ * Gets the substitution for the root key.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if none exists.
+ */
+ nsIURI getSubstitution(in ACString root);
+
+ /**
+ * Returns TRUE if the substitution exists and FALSE otherwise.
+ */
+ boolean hasSubstitution(in ACString root);
+
+ /**
+ * Utility function to resolve a substituted URI. A resolved URI is not
+ * guaranteed to reference a resource that exists (ie. opening a channel to
+ * the resolved URI may fail).
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if resURI.host() is an unknown root key.
+ */
+ AUTF8String resolveURI(in nsIURI resURI);
+};
diff --git a/netwerk/protocol/res/nsResProtocolHandler.cpp b/netwerk/protocol/res/nsResProtocolHandler.cpp
new file mode 100644
index 000000000..265bab9ec
--- /dev/null
+++ b/netwerk/protocol/res/nsResProtocolHandler.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/Unused.h"
+
+#include "nsResProtocolHandler.h"
+#include "nsIIOService.h"
+#include "nsIFile.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+
+#include "mozilla/Omnijar.h"
+
+using mozilla::dom::ContentParent;
+using mozilla::LogLevel;
+using mozilla::Unused;
+
+#define kAPP "app"
+#define kGRE "gre"
+
+nsresult
+nsResProtocolHandler::Init()
+{
+ nsresult rv;
+ rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::APP, mAppURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::GRE, mGREURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // mozilla::Omnijar::GetURIString always returns a string ending with /,
+ // and we want to remove it.
+ mGREURI.Truncate(mGREURI.Length() - 1);
+ if (mAppURI.Length()) {
+ mAppURI.Truncate(mAppURI.Length() - 1);
+ } else {
+ mAppURI = mGREURI;
+ }
+
+ //XXXbsmedberg Neil wants a resource://pchrome/ for the profile chrome dir...
+ // but once I finish multiple chrome registration I'm not sure that it is needed
+
+ // XXX dveditz: resource://pchrome/ defeats profile directory salting
+ // if web content can load it. Tread carefully.
+
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+// nsResProtocolHandler::nsISupports
+//----------------------------------------------------------------------------
+
+NS_IMPL_QUERY_INTERFACE(nsResProtocolHandler, nsIResProtocolHandler,
+ nsISubstitutingProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference)
+NS_IMPL_ADDREF_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
+NS_IMPL_RELEASE_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
+
+nsresult
+nsResProtocolHandler::GetSubstitutionInternal(const nsACString& root, nsIURI **result)
+{
+ nsAutoCString uri;
+
+ if (!ResolveSpecialCases(root, NS_LITERAL_CSTRING("/"), NS_LITERAL_CSTRING("/"), uri)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_NewURI(result, uri);
+}
+
+bool
+nsResProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult)
+{
+ if (aHost.Equals("") || aHost.Equals(kAPP)) {
+ aResult.Assign(mAppURI);
+ } else if (aHost.Equals(kGRE)) {
+ aResult.Assign(mGREURI);
+ } else {
+ return false;
+ }
+ aResult.Append(aPath);
+ return true;
+}
+
+nsresult
+nsResProtocolHandler::SetSubstitution(const nsACString& aRoot, nsIURI* aBaseURI)
+{
+ MOZ_ASSERT(!aRoot.Equals(""));
+ MOZ_ASSERT(!aRoot.Equals(kAPP));
+ MOZ_ASSERT(!aRoot.Equals(kGRE));
+ return SubstitutingProtocolHandler::SetSubstitution(aRoot, aBaseURI);
+}
diff --git a/netwerk/protocol/res/nsResProtocolHandler.h b/netwerk/protocol/res/nsResProtocolHandler.h
new file mode 100644
index 000000000..00f8d1b13
--- /dev/null
+++ b/netwerk/protocol/res/nsResProtocolHandler.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsResProtocolHandler_h___
+#define nsResProtocolHandler_h___
+
+#include "SubstitutingProtocolHandler.h"
+
+#include "nsIResProtocolHandler.h"
+#include "nsInterfaceHashtable.h"
+#include "nsWeakReference.h"
+#include "nsStandardURL.h"
+
+struct SubstitutionMapping;
+class nsResProtocolHandler final : public nsIResProtocolHandler,
+ public mozilla::SubstitutingProtocolHandler,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRESPROTOCOLHANDLER
+
+ NS_FORWARD_NSIPROTOCOLHANDLER(mozilla::SubstitutingProtocolHandler::)
+
+ nsResProtocolHandler()
+ : SubstitutingProtocolHandler("resource", URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE,
+ /* aEnforceFileOrJar = */ false)
+ {}
+
+ nsresult Init();
+
+ NS_IMETHOD SetSubstitution(const nsACString& aRoot, nsIURI* aBaseURI) override;
+
+ NS_IMETHOD GetSubstitution(const nsACString& aRoot, nsIURI** aResult) override
+ {
+ return mozilla::SubstitutingProtocolHandler::GetSubstitution(aRoot, aResult);
+ }
+
+ NS_IMETHOD HasSubstitution(const nsACString& aRoot, bool* aResult) override
+ {
+ return mozilla::SubstitutingProtocolHandler::HasSubstitution(aRoot, aResult);
+ }
+
+ NS_IMETHOD ResolveURI(nsIURI *aResURI, nsACString& aResult) override
+ {
+ return mozilla::SubstitutingProtocolHandler::ResolveURI(aResURI, aResult);
+ }
+
+protected:
+ nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult) override;
+ virtual ~nsResProtocolHandler() {}
+
+ bool ResolveSpecialCases(const nsACString& aHost,
+ const nsACString& aPath,
+ const nsACString& aPathname,
+ nsACString& aResult) override;
+
+private:
+ nsCString mAppURI;
+ nsCString mGREURI;
+};
+
+#endif /* nsResProtocolHandler_h___ */
diff --git a/netwerk/protocol/viewsource/moz.build b/netwerk/protocol/viewsource/moz.build
new file mode 100644
index 000000000..4e3303c40
--- /dev/null
+++ b/netwerk/protocol/viewsource/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIViewSourceChannel.idl',
+]
+
+XPIDL_MODULE = 'necko_viewsource'
+
+UNIFIED_SOURCES += [
+ 'nsViewSourceChannel.cpp',
+ 'nsViewSourceHandler.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
diff --git a/netwerk/protocol/viewsource/nsIViewSourceChannel.idl b/netwerk/protocol/viewsource/nsIViewSourceChannel.idl
new file mode 100644
index 000000000..9ed01ef31
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsIViewSourceChannel.idl
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIChannel.idl"
+
+[uuid(3e9800f8-edb7-4c9a-9285-09b4f045b019)]
+interface nsIViewSourceChannel : nsIChannel
+{
+ /**
+ * The actual (MIME) content type of the data.
+ *
+ * nsIViewSourceChannel returns a content type of
+ * "application/x-view-source" if you ask it for the contentType
+ * attribute.
+ *
+ * However, callers interested in finding out or setting the
+ * actual content type can utilize this attribute.
+ */
+ attribute ACString originalContentType;
+
+ /**
+ * Whether the channel was created to view the source of a srcdoc document.
+ */
+ readonly attribute boolean isSrcdocChannel;
+
+ /**
+ * Set to indicate the base URI. If this channel is a srcdoc channel, it
+ * returns the base URI provided by the embedded channel. It is used to
+ * provide an indication of the base URI in circumstances where it isn't
+ * otherwise recoverable. Returns null when it isn't set and isn't a
+ * srcdoc channel.
+ */
+ attribute nsIURI baseURI;
+};
+
+
diff --git a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
new file mode 100644
index 000000000..9ed71c4ef
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -0,0 +1,1018 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsViewSourceChannel.h"
+#include "nsIIOService.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsContentSecurityManager.h"
+#include "nsNullPrincipal.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIInputStreamChannel.h"
+#include "mozilla/DebugOnly.h"
+
+NS_IMPL_ADDREF(nsViewSourceChannel)
+NS_IMPL_RELEASE(nsViewSourceChannel)
+/*
+ This QI uses NS_INTERFACE_MAP_ENTRY_CONDITIONAL to check for
+ non-nullness of mHttpChannel, mCachingChannel, and mUploadChannel.
+*/
+NS_INTERFACE_MAP_BEGIN(nsViewSourceChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIViewSourceChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIHttpChannel, mHttpChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIHttpChannelInternal, mHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICachingChannel, mCachingChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICacheInfoChannel, mCacheInfoChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIApplicationCacheChannel, mApplicationCacheChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUploadChannel, mUploadChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFormPOSTActionChannel, mPostChannel)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIRequest, nsIViewSourceChannel)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIChannel, nsIViewSourceChannel)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIViewSourceChannel)
+NS_INTERFACE_MAP_END
+
+nsresult
+nsViewSourceChannel::Init(nsIURI* uri)
+{
+ mOriginalURI = uri;
+
+ nsAutoCString path;
+ nsresult rv = uri->GetPath(path);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIIOService> pService(do_GetIOService(&rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString scheme;
+ rv = pService->ExtractScheme(path, scheme);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // prevent viewing source of javascript URIs (see bug 204779)
+ if (scheme.LowerCaseEqualsLiteral("javascript")) {
+ NS_WARNING("blocking view-source:javascript:");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // This function is called from within nsViewSourceHandler::NewChannel2
+ // and sets the right loadInfo right after returning from this function.
+ // Until then we follow the principal of least privilege and use
+ // nullPrincipal as the loadingPrincipal and the least permissive
+ // securityflag.
+ nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create();
+
+ rv = pService->NewChannel2(path,
+ nullptr, // aOriginCharset
+ nullptr, // aCharSet
+ nullptr, // aLoadingNode
+ nullPrincipal,
+ nullptr, // aTriggeringPrincipal
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_OTHER,
+ getter_AddRefs(mChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIsSrcdocChannel = false;
+
+ mChannel->SetOriginalURI(mOriginalURI);
+ mHttpChannel = do_QueryInterface(mChannel);
+ mHttpChannelInternal = do_QueryInterface(mChannel);
+ mCachingChannel = do_QueryInterface(mChannel);
+ mCacheInfoChannel = do_QueryInterface(mChannel);
+ mApplicationCacheChannel = do_QueryInterface(mChannel);
+ mUploadChannel = do_QueryInterface(mChannel);
+ mPostChannel = do_QueryInterface(mChannel);
+
+ return NS_OK;
+}
+
+nsresult
+nsViewSourceChannel::InitSrcdoc(nsIURI* aURI,
+ nsIURI* aBaseURI,
+ const nsAString &aSrcdoc,
+ nsILoadInfo* aLoadInfo)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> inStreamURI;
+ // Need to strip view-source: from the URI. Hardcoded to
+ // about:srcdoc as this is the only permissible URI for srcdoc
+ // loads
+ rv = NS_NewURI(getter_AddRefs(inStreamURI),
+ NS_LITERAL_STRING("about:srcdoc"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel),
+ inStreamURI,
+ aSrcdoc,
+ NS_LITERAL_CSTRING("text/html"),
+ aLoadInfo,
+ true);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ mOriginalURI = aURI;
+ mIsSrcdocChannel = true;
+
+ mChannel->SetOriginalURI(mOriginalURI);
+ mHttpChannel = do_QueryInterface(mChannel);
+ mHttpChannelInternal = do_QueryInterface(mChannel);
+ mCachingChannel = do_QueryInterface(mChannel);
+ mCacheInfoChannel = do_QueryInterface(mChannel);
+ mApplicationCacheChannel = do_QueryInterface(mChannel);
+ mUploadChannel = do_QueryInterface(mChannel);
+
+ nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(mChannel);
+ MOZ_ASSERT(isc);
+ isc->SetBaseURI(aBaseURI);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetName(nsACString &result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetTransferSize(uint64_t *aTransferSize)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetDecodedBodySize(uint64_t *aDecodedBodySize)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetEncodedBodySize(uint64_t *aEncodedBodySize)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::IsPending(bool *result)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->IsPending(result);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetStatus(nsresult *status)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetStatus(status);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Cancel(nsresult status)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->Cancel(status);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Suspend(void)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->Suspend();
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Resume(void)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->Resume();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIChannel methods:
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetOriginalURI(nsIURI* *aURI)
+{
+ NS_ASSERTION(aURI, "Null out param!");
+ *aURI = mOriginalURI;
+ NS_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetOriginalURI(nsIURI* aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetURI(nsIURI* *aURI)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // protect ourselves against broken channel implementations
+ if (!uri) {
+ NS_ERROR("inner channel returned NS_OK and a null URI");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ /* XXX Gross hack -- NS_NewURI goes into an infinite loop on
+ non-flat specs. See bug 136980 */
+ return NS_NewURI(aURI, nsAutoCString(NS_LITERAL_CSTRING("view-source:")+spec), nullptr);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Open(nsIInputStream **_retval)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ nsresult rv = NS_MaybeOpenChannelUsingOpen2(mChannel, _retval);
+ if (NS_SUCCEEDED(rv)) {
+ mOpened = true;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::Open2(nsIInputStream** aStream)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ if(!loadInfo) {
+ MOZ_ASSERT(loadInfo, "can not enforce security without loadInfo");
+ return NS_ERROR_UNEXPECTED;
+ }
+ // setting the flag on the loadInfo indicates that the underlying
+ // channel will be openend using Open2() and hence performs
+ // the necessary security checks.
+ loadInfo->SetEnforceSecurity(true);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *ctxt)
+{
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ MOZ_ASSERT(!loadInfo || loadInfo->GetSecurityMode() == 0 ||
+ loadInfo->GetEnforceSecurity(),
+ "security flags in loadInfo but asyncOpen2() not called");
+ }
+#endif
+
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ mListener = aListener;
+
+ /*
+ * We want to add ourselves to the loadgroup before opening
+ * mChannel, since we want to make sure we're in the loadgroup
+ * when mChannel finishes and fires OnStopRequest()
+ */
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup)
+ loadGroup->AddRequest(static_cast<nsIViewSourceChannel*>
+ (this), nullptr);
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ if (loadInfo && loadInfo->GetEnforceSecurity()) {
+ rv = mChannel->AsyncOpen2(this);
+ }
+ else {
+ rv = mChannel->AsyncOpen(this, ctxt);
+ }
+
+ if (NS_FAILED(rv) && loadGroup)
+ loadGroup->RemoveRequest(static_cast<nsIViewSourceChannel*>
+ (this),
+ nullptr, rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ mOpened = true;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ if(!loadInfo) {
+ MOZ_ASSERT(loadInfo, "can not enforce security without loadInfo");
+ return NS_ERROR_UNEXPECTED;
+ }
+ // setting the flag on the loadInfo indicates that the underlying
+ // channel will be openend using AsyncOpen2() and hence performs
+ // the necessary security checks.
+ loadInfo->SetEnforceSecurity(true);
+ return AsyncOpen(aListener, nullptr);
+}
+/*
+ * Both the view source channel and mChannel are added to the
+ * loadgroup. There should never be more than one request in the
+ * loadgroup that has LOAD_DOCUMENT_URI set. The one that has this
+ * flag set is the request whose URI is used to refetch the document,
+ * so it better be the viewsource channel.
+ *
+ * Therefore, we need to make sure that
+ * 1) The load flags on mChannel _never_ include LOAD_DOCUMENT_URI
+ * 2) The load flags on |this| include LOAD_DOCUMENT_URI when it was
+ * set via SetLoadFlags (mIsDocument keeps track of this flag).
+ */
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetLoadFlags(uint32_t *aLoadFlags)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ nsresult rv = mChannel->GetLoadFlags(aLoadFlags);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // This should actually be just LOAD_DOCUMENT_URI but the win32 compiler
+ // fails to deal due to amiguous inheritance. nsIChannel::LOAD_DOCUMENT_URI
+ // also fails; the Win32 compiler thinks that's supposed to be a method.
+ if (mIsDocument)
+ *aLoadFlags |= ::nsIChannel::LOAD_DOCUMENT_URI;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetLoadFlags(uint32_t aLoadFlags)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ // "View source" always wants the currently cached content.
+ // We also want to have _this_ channel, not mChannel to be the
+ // 'document' channel in the loadgroup.
+
+ // These should actually be just LOAD_FROM_CACHE and LOAD_DOCUMENT_URI but
+ // the win32 compiler fails to deal due to amiguous inheritance.
+ // nsIChannel::LOAD_DOCUMENT_URI/nsIRequest::LOAD_FROM_CACHE also fails; the
+ // Win32 compiler thinks that's supposed to be a method.
+ mIsDocument = (aLoadFlags & ::nsIChannel::LOAD_DOCUMENT_URI) ? true : false;
+
+ nsresult rv = mChannel->SetLoadFlags((aLoadFlags |
+ ::nsIRequest::LOAD_FROM_CACHE) &
+ ~::nsIChannel::LOAD_DOCUMENT_URI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mHttpChannel) {
+ rv = mHttpChannel->SetIsMainDocumentChannel(aLoadFlags & ::nsIChannel::LOAD_DOCUMENT_URI);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentType(nsACString &aContentType)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ aContentType.Truncate();
+
+ if (mContentType.IsEmpty())
+ {
+ // Get the current content type
+ nsresult rv;
+ nsAutoCString contentType;
+ rv = mChannel->GetContentType(contentType);
+ if (NS_FAILED(rv)) return rv;
+
+ // If we don't know our type, just say so. The unknown
+ // content decoder will then kick in automatically, and it
+ // will call our SetOriginalContentType method instead of our
+ // SetContentType method to set the type it determines.
+ if (!contentType.Equals(UNKNOWN_CONTENT_TYPE)) {
+ contentType = VIEWSOURCE_CONTENT_TYPE;
+ }
+
+ mContentType = contentType;
+ }
+
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentType(const nsACString &aContentType)
+{
+ // Our GetContentType() currently returns VIEWSOURCE_CONTENT_TYPE
+ //
+ // However, during the parsing phase the parser calls our
+ // channel's GetContentType(). Returning the string above trips up
+ // the parser. In order to avoid messy changes and not to have the
+ // parser depend on nsIViewSourceChannel Vidur proposed the
+ // following solution:
+ //
+ // The ViewSourceChannel initially returns a content type of
+ // VIEWSOURCE_CONTENT_TYPE. Based on this type decisions to
+ // create a viewer for doing a view source are made. After the
+ // viewer is created, nsLayoutDLF::CreateInstance() calls this
+ // SetContentType() with the original content type. When it's
+ // time for the parser to find out the content type it will call
+ // our channel's GetContentType() and it will get the original
+ // content type, such as, text/html and everything is kosher from
+ // then on.
+
+ if (!mOpened) {
+ // We do not take hints
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mContentType = aContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentCharset(nsACString &aContentCharset)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetContentCharset(aContentCharset);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentCharset(const nsACString &aContentCharset)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetContentCharset(aContentCharset);
+}
+
+// We don't forward these methods becacuse content-disposition isn't whitelisted
+// (see GetResponseHeader/VisitResponseHeaders).
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentLength(int64_t *aContentLength)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetContentLength(aContentLength);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetContentLength(int64_t aContentLength)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetContentLength(aContentLength);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetLoadGroup(aLoadGroup);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetLoadGroup(aLoadGroup);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetOwner(nsISupports* *aOwner)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetOwner(aOwner);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetOwner(nsISupports* aOwner)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetOwner(aOwner);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetLoadInfo(nsILoadInfo* *aLoadInfo)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetLoadInfo(aLoadInfo);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetLoadInfo(aLoadInfo);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aNotificationCallbacks)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetNotificationCallbacks(aNotificationCallbacks);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->SetNotificationCallbacks(aNotificationCallbacks);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetSecurityInfo(aSecurityInfo);
+}
+
+// nsIViewSourceChannel methods
+NS_IMETHODIMP
+nsViewSourceChannel::GetOriginalContentType(nsACString &aContentType)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ return mChannel->GetContentType(aContentType);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetOriginalContentType(const nsACString &aContentType)
+{
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+ // clear our cached content-type value
+ mContentType.Truncate();
+
+ return mChannel->SetContentType(aContentType);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetIsSrcdocChannel(bool* aIsSrcdocChannel)
+{
+ *aIsSrcdocChannel = mIsSrcdocChannel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetBaseURI(nsIURI** aBaseURI)
+{
+ if (mIsSrcdocChannel) {
+ nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(mChannel);
+ if (isc) {
+ return isc->GetBaseURI(aBaseURI);
+ }
+ }
+ *aBaseURI = mBaseURI;
+ NS_IF_ADDREF(*aBaseURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetBaseURI(nsIURI* aBaseURI)
+{
+ mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetProtocolVersion(nsACString& aProtocolVersion)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsIRequestObserver methods
+NS_IMETHODIMP
+nsViewSourceChannel::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ NS_ENSURE_TRUE(mListener, NS_ERROR_FAILURE);
+ // The channel may have gotten redirected... Time to update our info
+ mChannel = do_QueryInterface(aRequest);
+ mHttpChannel = do_QueryInterface(aRequest);
+ mCachingChannel = do_QueryInterface(aRequest);
+ mCacheInfoChannel = do_QueryInterface(mChannel);
+ mUploadChannel = do_QueryInterface(aRequest);
+
+ return mListener->OnStartRequest(static_cast<nsIViewSourceChannel*>
+ (this),
+ aContext);
+}
+
+
+NS_IMETHODIMP
+nsViewSourceChannel::OnStopRequest(nsIRequest *aRequest, nsISupports* aContext,
+ nsresult aStatus)
+{
+ NS_ENSURE_TRUE(mListener, NS_ERROR_FAILURE);
+ if (mChannel)
+ {
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup)
+ {
+ loadGroup->RemoveRequest(static_cast<nsIViewSourceChannel*>
+ (this),
+ nullptr, aStatus);
+ }
+ }
+ return mListener->OnStopRequest(static_cast<nsIViewSourceChannel*>
+ (this),
+ aContext, aStatus);
+}
+
+
+// nsIStreamListener methods
+NS_IMETHODIMP
+nsViewSourceChannel::OnDataAvailable(nsIRequest *aRequest, nsISupports* aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ NS_ENSURE_TRUE(mListener, NS_ERROR_FAILURE);
+ return mListener->OnDataAvailable(static_cast<nsIViewSourceChannel*>
+ (this),
+ aContext, aInputStream,
+ aSourceOffset, aLength);
+}
+
+
+// nsIHttpChannel methods
+
+// We want to forward most of nsIHttpChannel over to mHttpChannel, but we want
+// to override GetRequestHeader and VisitHeaders. The reason is that we don't
+// want various headers like Link: and Refresh: applying to view-source.
+NS_IMETHODIMP
+nsViewSourceChannel::GetChannelId(nsACString& aChannelId)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetChannelId(aChannelId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetChannelId(const nsACString& aChannelId)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetChannelId(aChannelId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetTopLevelContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetTopLevelContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestMethod(nsACString & aRequestMethod)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetRequestMethod(aRequestMethod);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetRequestMethod(const nsACString & aRequestMethod)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetRequestMethod(aRequestMethod);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetReferrer(nsIURI * *aReferrer)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetReferrer(aReferrer);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetReferrer(nsIURI * aReferrer)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetReferrer(aReferrer);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetReferrerPolicy(uint32_t *aReferrerPolicy)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetReferrerPolicy(aReferrerPolicy);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetReferrerWithPolicy(nsIURI * aReferrer,
+ uint32_t aReferrerPolicy)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetReferrerWithPolicy(aReferrer, aReferrerPolicy);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestHeader(const nsACString & aHeader,
+ nsACString & aValue)
+{
+ aValue.Truncate();
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetRequestHeader(aHeader, aValue);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetRequestHeader(const nsACString & aHeader,
+ const nsACString & aValue,
+ bool aMerge)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetRequestHeader(aHeader, aValue, aMerge);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetEmptyRequestHeader(const nsACString & aHeader)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetEmptyRequestHeader(aHeader);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::VisitRequestHeaders(nsIHttpHeaderVisitor *aVisitor)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->VisitRequestHeaders(aVisitor);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor *aVisitor)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->VisitNonDefaultRequestHeaders(aVisitor);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetAllowPipelining(bool *aAllowPipelining)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetAllowPipelining(aAllowPipelining);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetAllowPipelining(bool aAllowPipelining)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetAllowPipelining(aAllowPipelining);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetAllowSTS(bool *aAllowSTS)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetAllowSTS(aAllowSTS);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetAllowSTS(bool aAllowSTS)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetAllowSTS(aAllowSTS);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRedirectionLimit(uint32_t *aRedirectionLimit)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetRedirectionLimit(aRedirectionLimit);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetRedirectionLimit(uint32_t aRedirectionLimit)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetRedirectionLimit(aRedirectionLimit);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetResponseStatus(uint32_t *aResponseStatus)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetResponseStatus(aResponseStatus);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetResponseStatusText(nsACString & aResponseStatusText)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetResponseStatusText(aResponseStatusText);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestSucceeded(bool *aRequestSucceeded)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetRequestSucceeded(aRequestSucceeded);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetResponseHeader(const nsACString & aHeader,
+ nsACString & aValue)
+{
+ aValue.Truncate();
+ if (!mHttpChannel)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!aHeader.Equals(NS_LITERAL_CSTRING("Content-Type"),
+ nsCaseInsensitiveCStringComparator()) &&
+ !aHeader.Equals(NS_LITERAL_CSTRING("Content-Security-Policy"),
+ nsCaseInsensitiveCStringComparator()) &&
+ !aHeader.Equals(NS_LITERAL_CSTRING("Content-Security-Policy-Report-Only"),
+ nsCaseInsensitiveCStringComparator()) &&
+ !aHeader.Equals(NS_LITERAL_CSTRING("X-Frame-Options"),
+ nsCaseInsensitiveCStringComparator())) {
+ return NS_OK;
+ }
+
+ return mHttpChannel->GetResponseHeader(aHeader, aValue);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetResponseHeader(const nsACString & header,
+ const nsACString & value, bool merge)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetResponseHeader(header, value, merge);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *aVisitor)
+{
+ if (!mHttpChannel)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_NAMED_LITERAL_CSTRING(contentTypeStr, "Content-Type");
+ nsAutoCString contentType;
+ nsresult rv =
+ mHttpChannel->GetResponseHeader(contentTypeStr, contentType);
+ if (NS_SUCCEEDED(rv))
+ aVisitor->VisitHeader(contentTypeStr, contentType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetOriginalResponseHeader(const nsACString & aHeader,
+ nsIHttpHeaderVisitor *aVisitor)
+{
+ nsAutoCString value;
+ nsresult rv = GetResponseHeader(aHeader, value);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ aVisitor->VisitHeader(aHeader, value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor *aVisitor)
+{
+ return VisitResponseHeaders(aVisitor);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::IsNoStoreResponse(bool *_retval)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->IsNoStoreResponse(_retval);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::IsNoCacheResponse(bool *_retval)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->IsNoCacheResponse(_retval);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::IsPrivateResponse(bool *_retval)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->IsPrivateResponse(_retval);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::RedirectTo(nsIURI *uri)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->RedirectTo(uri);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetRequestContextID(nsID *_retval)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetRequestContextID(_retval);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetRequestContextID(const nsID rcid)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetRequestContextID(rcid);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetIsMainDocumentChannel(bool* aValue)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetIsMainDocumentChannel(aValue);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetIsMainDocumentChannel(bool aValue)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetIsMainDocumentChannel(aValue);
+}
+
+// Have to manually forward since these are [notxpcom]
+void
+nsViewSourceChannel::SetCorsPreflightParameters(const nsTArray<nsCString>& aUnsafeHeaders)
+{
+ mHttpChannelInternal->SetCorsPreflightParameters(aUnsafeHeaders);
+}
+
+mozilla::net::nsHttpChannel *
+nsViewSourceChannel::QueryHttpChannelImpl()
+{
+ return mHttpChannelInternal->QueryHttpChannelImpl();
+}
diff --git a/netwerk/protocol/viewsource/nsViewSourceChannel.h b/netwerk/protocol/viewsource/nsViewSourceChannel.h
new file mode 100644
index 000000000..45e561aa7
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsViewSourceChannel_h___
+#define nsViewSourceChannel_h___
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIViewSourceChannel.h"
+#include "nsIURI.h"
+#include "nsIStreamListener.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsICachingChannel.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIFormPOSTActionChannel.h"
+#include "mozilla/Attributes.h"
+
+class nsViewSourceChannel final : public nsIViewSourceChannel,
+ public nsIStreamListener,
+ public nsIHttpChannel,
+ public nsIHttpChannelInternal,
+ public nsICachingChannel,
+ public nsIApplicationCacheChannel,
+ public nsIFormPOSTActionChannel
+{
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIVIEWSOURCECHANNEL
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIHTTPCHANNEL
+ NS_FORWARD_SAFE_NSICACHEINFOCHANNEL(mCacheInfoChannel)
+ NS_FORWARD_SAFE_NSICACHINGCHANNEL(mCachingChannel)
+ NS_FORWARD_SAFE_NSIAPPLICATIONCACHECHANNEL(mApplicationCacheChannel)
+ NS_FORWARD_SAFE_NSIAPPLICATIONCACHECONTAINER(mApplicationCacheChannel)
+ NS_FORWARD_SAFE_NSIUPLOADCHANNEL(mUploadChannel)
+ NS_FORWARD_SAFE_NSIFORMPOSTACTIONCHANNEL(mPostChannel)
+ NS_FORWARD_SAFE_NSIHTTPCHANNELINTERNAL(mHttpChannelInternal)
+
+ // nsViewSourceChannel methods:
+ nsViewSourceChannel()
+ : mIsDocument(false)
+ , mOpened(false) {}
+
+ nsresult Init(nsIURI* uri);
+
+ nsresult InitSrcdoc(nsIURI* aURI,
+ nsIURI* aBaseURI,
+ const nsAString &aSrcdoc,
+ nsILoadInfo* aLoadInfo);
+
+protected:
+ ~nsViewSourceChannel() {}
+
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+ nsCOMPtr<nsIHttpChannelInternal> mHttpChannelInternal;
+ nsCOMPtr<nsICachingChannel> mCachingChannel;
+ nsCOMPtr<nsICacheInfoChannel> mCacheInfoChannel;
+ nsCOMPtr<nsIApplicationCacheChannel> mApplicationCacheChannel;
+ nsCOMPtr<nsIUploadChannel> mUploadChannel;
+ nsCOMPtr<nsIFormPOSTActionChannel> mPostChannel;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsCString mContentType;
+ bool mIsDocument; // keeps track of the LOAD_DOCUMENT_URI flag
+ bool mOpened;
+ bool mIsSrcdocChannel;
+};
+
+#endif /* nsViewSourceChannel_h___ */
diff --git a/netwerk/protocol/viewsource/nsViewSourceHandler.cpp b/netwerk/protocol/viewsource/nsViewSourceHandler.cpp
new file mode 100644
index 000000000..e8d4711d8
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceHandler.cpp
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsViewSourceHandler.h"
+#include "nsViewSourceChannel.h"
+#include "nsNetUtil.h"
+#include "nsSimpleNestedURI.h"
+
+#define VIEW_SOURCE "view-source"
+
+namespace mozilla {
+namespace net {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsViewSourceHandler, nsIProtocolHandler)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsViewSourceHandler::GetScheme(nsACString &result)
+{
+ result.AssignLiteral(VIEW_SOURCE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::GetDefaultPort(int32_t *result)
+{
+ *result = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::GetProtocolFlags(uint32_t *result)
+{
+ *result = URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD |
+ URI_NON_PERSISTABLE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ nsIURI **aResult)
+{
+ *aResult = nullptr;
+
+ // Extract inner URL and normalize to ASCII. This is done to properly
+ // support IDN in cases like "view-source:http://www.szalagavató.hu/"
+
+ int32_t colon = aSpec.FindChar(':');
+ if (colon == kNotFound)
+ return NS_ERROR_MALFORMED_URI;
+
+ nsCOMPtr<nsIURI> innerURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(innerURI),
+ Substring(aSpec, colon + 1), aCharset, aBaseURI);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString asciiSpec;
+ rv = innerURI->GetAsciiSpec(asciiSpec);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // put back our scheme and construct a simple-uri wrapper
+
+ asciiSpec.Insert(VIEW_SOURCE ":", 0);
+
+ // We can't swap() from an RefPtr<nsSimpleNestedURI> to an nsIURI**,
+ // sadly.
+ nsSimpleNestedURI* ourURI = new nsSimpleNestedURI(innerURI);
+ nsCOMPtr<nsIURI> uri = ourURI;
+ if (!uri)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = ourURI->SetSpec(asciiSpec);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Make the URI immutable so it's impossible to get it out of sync
+ // with its inner URI.
+ ourURI->SetMutable(false);
+
+ uri.swap(*aResult);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ nsViewSourceChannel *channel = new nsViewSourceChannel();
+ if (!channel)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(channel);
+
+ nsresult rv = channel->Init(uri);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(channel);
+ return rv;
+ }
+
+ // set the loadInfo on the new channel
+ rv = channel->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(channel);
+ return rv;
+ }
+
+ *result = static_cast<nsIViewSourceChannel*>(channel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+nsresult
+nsViewSourceHandler::NewSrcdocChannel(nsIURI *aURI,
+ nsIURI *aBaseURI,
+ const nsAString &aSrcdoc,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** outChannel)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ RefPtr<nsViewSourceChannel> channel = new nsViewSourceChannel();
+
+ nsresult rv = channel->InitSrcdoc(aURI, aBaseURI, aSrcdoc, aLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *outChannel = static_cast<nsIViewSourceChannel*>(channel.forget().take());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+nsViewSourceHandler::nsViewSourceHandler()
+{
+ gInstance = this;
+}
+
+nsViewSourceHandler::~nsViewSourceHandler()
+{
+ gInstance = nullptr;
+}
+
+nsViewSourceHandler* nsViewSourceHandler::gInstance = nullptr;
+
+nsViewSourceHandler*
+nsViewSourceHandler::GetInstance()
+{
+ return gInstance;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/viewsource/nsViewSourceHandler.h b/netwerk/protocol/viewsource/nsViewSourceHandler.h
new file mode 100644
index 000000000..76f7c8d01
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceHandler.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsViewSourceHandler_h___
+#define nsViewSourceHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsNetUtil.h"
+#include "mozilla/Attributes.h"
+
+class nsILoadInfo;
+
+namespace mozilla {
+namespace net {
+
+class nsViewSourceHandler final : public nsIProtocolHandler
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ nsViewSourceHandler();
+
+ // Creates a new nsViewSourceChannel to view the source of an about:srcdoc
+ // URI with contents specified by srcdoc.
+ nsresult NewSrcdocChannel(nsIURI *aURI,
+ nsIURI *aBaseURI,
+ const nsAString &aSrcdoc,
+ nsILoadInfo *aLoadInfo,
+ nsIChannel** outChannel);
+
+ static nsViewSourceHandler* GetInstance();
+
+private:
+ ~nsViewSourceHandler();
+
+ static nsViewSourceHandler* gInstance;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* !defined( nsViewSourceHandler_h___ ) */
diff --git a/netwerk/protocol/websocket/BaseWebSocketChannel.cpp b/netwerk/protocol/websocket/BaseWebSocketChannel.cpp
new file mode 100644
index 000000000..bf3dbf9f7
--- /dev/null
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.cpp
@@ -0,0 +1,381 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketLog.h"
+#include "BaseWebSocketChannel.h"
+#include "MainThreadUtils.h"
+#include "nsILoadGroup.h"
+#include "nsINode.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsAutoPtr.h"
+#include "nsProxyRelease.h"
+#include "nsStandardURL.h"
+#include "LoadInfo.h"
+#include "nsIDOMNode.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsITransportProvider.h"
+
+using mozilla::dom::ContentChild;
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule webSocketLog("nsWebSocket");
+static uint64_t gNextWebSocketID = 0;
+
+// We use only 53 bits for the WebSocket serial ID so that it can be converted
+// to and from a JS value without loss of precision. The upper bits of the
+// WebSocket serial ID hold the process ID. The lower bits identify the
+// WebSocket.
+static const uint64_t kWebSocketIDTotalBits = 53;
+static const uint64_t kWebSocketIDProcessBits = 22;
+static const uint64_t kWebSocketIDWebSocketBits = kWebSocketIDTotalBits - kWebSocketIDProcessBits;
+
+BaseWebSocketChannel::BaseWebSocketChannel()
+ : mWasOpened(0)
+ , mClientSetPingInterval(0)
+ , mClientSetPingTimeout(0)
+ , mEncrypted(0)
+ , mPingForced(0)
+ , mIsServerSide(false)
+ , mPingInterval(0)
+ , mPingResponseTimeout(10000)
+{
+ // Generation of a unique serial ID.
+ uint64_t processID = 0;
+ if (XRE_IsContentProcess()) {
+ ContentChild* cc = ContentChild::GetSingleton();
+ processID = cc->GetID();
+ }
+
+ uint64_t processBits = processID & ((uint64_t(1) << kWebSocketIDProcessBits) - 1);
+
+ // Make sure no actual webSocket ends up with mWebSocketID == 0 but less then
+ // what the kWebSocketIDProcessBits allows.
+ if (++gNextWebSocketID >= (uint64_t(1) << kWebSocketIDWebSocketBits)) {
+ gNextWebSocketID = 1;
+ }
+
+ uint64_t webSocketBits = gNextWebSocketID & ((uint64_t(1) << kWebSocketIDWebSocketBits) - 1);
+ mSerial = (processBits << kWebSocketIDWebSocketBits) | webSocketBits;
+}
+
+//-----------------------------------------------------------------------------
+// BaseWebSocketChannel::nsIWebSocketChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetOriginalURI(nsIURI **aOriginalURI)
+{
+ LOG(("BaseWebSocketChannel::GetOriginalURI() %p\n", this));
+
+ if (!mOriginalURI)
+ return NS_ERROR_NOT_INITIALIZED;
+ NS_ADDREF(*aOriginalURI = mOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetURI(nsIURI **aURI)
+{
+ LOG(("BaseWebSocketChannel::GetURI() %p\n", this));
+
+ if (!mOriginalURI)
+ return NS_ERROR_NOT_INITIALIZED;
+ if (mURI)
+ NS_ADDREF(*aURI = mURI);
+ else
+ NS_ADDREF(*aURI = mOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::
+GetNotificationCallbacks(nsIInterfaceRequestor **aNotificationCallbacks)
+{
+ LOG(("BaseWebSocketChannel::GetNotificationCallbacks() %p\n", this));
+ NS_IF_ADDREF(*aNotificationCallbacks = mCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::
+SetNotificationCallbacks(nsIInterfaceRequestor *aNotificationCallbacks)
+{
+ LOG(("BaseWebSocketChannel::SetNotificationCallbacks() %p\n", this));
+ mCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ LOG(("BaseWebSocketChannel::GetLoadGroup() %p\n", this));
+ NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ LOG(("BaseWebSocketChannel::SetLoadGroup() %p\n", this));
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetLoadInfo(nsILoadInfo** aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetExtensions(nsACString &aExtensions)
+{
+ LOG(("BaseWebSocketChannel::GetExtensions() %p\n", this));
+ aExtensions = mNegotiatedExtensions;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetProtocol(nsACString &aProtocol)
+{
+ LOG(("BaseWebSocketChannel::GetProtocol() %p\n", this));
+ aProtocol = mProtocol;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetProtocol(const nsACString &aProtocol)
+{
+ LOG(("BaseWebSocketChannel::SetProtocol() %p\n", this));
+ mProtocol = aProtocol; /* the sub protocol */
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetPingInterval(uint32_t *aSeconds)
+{
+ // stored in ms but should only have second resolution
+ MOZ_ASSERT(!(mPingInterval % 1000));
+
+ *aSeconds = mPingInterval / 1000;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetPingInterval(uint32_t aSeconds)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mWasOpened) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ mPingInterval = aSeconds * 1000;
+ mClientSetPingInterval = 1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetPingTimeout(uint32_t *aSeconds)
+{
+ // stored in ms but should only have second resolution
+ MOZ_ASSERT(!(mPingResponseTimeout % 1000));
+
+ *aSeconds = mPingResponseTimeout / 1000;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetPingTimeout(uint32_t aSeconds)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mWasOpened) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ mPingResponseTimeout = aSeconds * 1000;
+ mClientSetPingTimeout = 1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::InitLoadInfo(nsIDOMNode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ uint32_t aContentPolicyType)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aLoadingNode);
+ mLoadInfo = new LoadInfo(aLoadingPrincipal, aTriggeringPrincipal,
+ node, aSecurityFlags, aContentPolicyType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetSerial(uint32_t* aSerial)
+{
+ if (!aSerial) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aSerial = mSerial;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetSerial(uint32_t aSerial)
+{
+ mSerial = aSerial;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::SetServerParameters(nsITransportProvider* aProvider,
+ const nsACString& aNegotiatedExtensions)
+{
+ MOZ_ASSERT(aProvider);
+ mServerTransportProvider = aProvider;
+ mNegotiatedExtensions = aNegotiatedExtensions;
+ mIsServerSide = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// BaseWebSocketChannel::nsIProtocolHandler
+//-----------------------------------------------------------------------------
+
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetScheme(nsACString &aScheme)
+{
+ LOG(("BaseWebSocketChannel::GetScheme() %p\n", this));
+
+ if (mEncrypted)
+ aScheme.AssignLiteral("wss");
+ else
+ aScheme.AssignLiteral("ws");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetDefaultPort(int32_t *aDefaultPort)
+{
+ LOG(("BaseWebSocketChannel::GetDefaultPort() %p\n", this));
+
+ if (mEncrypted)
+ *aDefaultPort = kDefaultWSSPort;
+ else
+ *aDefaultPort = kDefaultWSPort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetProtocolFlags(uint32_t *aProtocolFlags)
+{
+ LOG(("BaseWebSocketChannel::GetProtocolFlags() %p\n", this));
+
+ *aProtocolFlags = URI_NORELATIVE | URI_NON_PERSISTABLE | ALLOWS_PROXY |
+ ALLOWS_PROXY_HTTP | URI_DOES_NOT_RETURN_DATA | URI_DANGEROUS_TO_LOAD;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::NewURI(const nsACString & aSpec, const char *aOriginCharset,
+ nsIURI *aBaseURI, nsIURI **_retval)
+{
+ LOG(("BaseWebSocketChannel::NewURI() %p\n", this));
+
+ int32_t port;
+ nsresult rv = GetDefaultPort(&port);
+ if (NS_FAILED(rv))
+ return rv;
+
+ RefPtr<nsStandardURL> url = new nsStandardURL();
+ rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY, port, aSpec,
+ aOriginCharset, aBaseURI);
+ if (NS_FAILED(rv))
+ return rv;
+ url.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::NewChannel2(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** outChannel)
+{
+ LOG(("BaseWebSocketChannel::NewChannel2() %p\n", this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ LOG(("BaseWebSocketChannel::NewChannel() %p\n", this));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::AllowPort(int32_t port, const char *scheme,
+ bool *_retval)
+{
+ LOG(("BaseWebSocketChannel::AllowPort() %p\n", this));
+
+ // do not override any blacklisted ports
+ *_retval = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// BaseWebSocketChannel::nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+BaseWebSocketChannel::RetargetDeliveryTo(nsIEventTarget* aTargetThread)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTargetThread);
+ MOZ_ASSERT(!mTargetThread, "Delivery target should be set once, before AsyncOpen");
+ MOZ_ASSERT(!mWasOpened, "Should not be called after AsyncOpen!");
+
+ mTargetThread = do_QueryInterface(aTargetThread);
+ MOZ_ASSERT(mTargetThread);
+ return NS_OK;
+}
+
+BaseWebSocketChannel::ListenerAndContextContainer::ListenerAndContextContainer(
+ nsIWebSocketListener* aListener,
+ nsISupports* aContext)
+ : mListener(aListener)
+ , mContext(aContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mListener);
+}
+
+BaseWebSocketChannel::ListenerAndContextContainer::~ListenerAndContextContainer()
+{
+ MOZ_ASSERT(mListener);
+
+ NS_ReleaseOnMainThread(mListener.forget());
+ NS_ReleaseOnMainThread(mContext.forget());
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/BaseWebSocketChannel.h b/netwerk/protocol/websocket/BaseWebSocketChannel.h
new file mode 100644
index 000000000..2cb622f7c
--- /dev/null
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_BaseWebSocketChannel_h
+#define mozilla_net_BaseWebSocketChannel_h
+
+#include "nsIWebSocketChannel.h"
+#include "nsIWebSocketListener.h"
+#include "nsIProtocolHandler.h"
+#include "nsIThread.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+const static int32_t kDefaultWSPort = 80;
+const static int32_t kDefaultWSSPort = 443;
+
+class BaseWebSocketChannel : public nsIWebSocketChannel,
+ public nsIProtocolHandler,
+ public nsIThreadRetargetableRequest
+{
+ public:
+ BaseWebSocketChannel();
+
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+
+ NS_IMETHOD QueryInterface(const nsIID & uuid, void **result) override = 0;
+ NS_IMETHOD_(MozExternalRefCountType ) AddRef(void) override = 0;
+ NS_IMETHOD_(MozExternalRefCountType ) Release(void) override = 0;
+
+ // Partial implementation of nsIWebSocketChannel
+ //
+ NS_IMETHOD GetOriginalURI(nsIURI **aOriginalURI) override;
+ NS_IMETHOD GetURI(nsIURI **aURI) override;
+ NS_IMETHOD GetNotificationCallbacks(nsIInterfaceRequestor **aNotificationCallbacks) override;
+ NS_IMETHOD SetNotificationCallbacks(nsIInterfaceRequestor *aNotificationCallbacks) override;
+ NS_IMETHOD GetLoadGroup(nsILoadGroup **aLoadGroup) override;
+ NS_IMETHOD SetLoadGroup(nsILoadGroup *aLoadGroup) override;
+ NS_IMETHOD SetLoadInfo(nsILoadInfo *aLoadInfo) override;
+ NS_IMETHOD GetLoadInfo(nsILoadInfo **aLoadInfo) override;
+ NS_IMETHOD GetExtensions(nsACString &aExtensions) override;
+ NS_IMETHOD GetProtocol(nsACString &aProtocol) override;
+ NS_IMETHOD SetProtocol(const nsACString &aProtocol) override;
+ NS_IMETHOD GetPingInterval(uint32_t *aSeconds) override;
+ NS_IMETHOD SetPingInterval(uint32_t aSeconds) override;
+ NS_IMETHOD GetPingTimeout(uint32_t *aSeconds) override;
+ NS_IMETHOD SetPingTimeout(uint32_t aSeconds) override;
+ NS_IMETHOD InitLoadInfo(nsIDOMNode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal, uint32_t aSecurityFlags,
+ uint32_t aContentPolicyType) override;
+ NS_IMETHOD GetSerial(uint32_t* aSerial) override;
+ NS_IMETHOD SetSerial(uint32_t aSerial) override;
+ NS_IMETHOD SetServerParameters(nsITransportProvider* aProvider,
+ const nsACString& aNegotiatedExtensions) override;
+
+ // Off main thread URI access.
+ virtual void GetEffectiveURL(nsAString& aEffectiveURL) const = 0;
+ virtual bool IsEncrypted() const = 0;
+
+ class ListenerAndContextContainer final
+ {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ListenerAndContextContainer)
+
+ ListenerAndContextContainer(nsIWebSocketListener* aListener,
+ nsISupports* aContext);
+
+ nsCOMPtr<nsIWebSocketListener> mListener;
+ nsCOMPtr<nsISupports> mContext;
+
+ private:
+ ~ListenerAndContextContainer();
+ };
+
+ protected:
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mURI;
+ RefPtr<ListenerAndContextContainer> mListenerMT;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+ nsCOMPtr<nsITransportProvider> mServerTransportProvider;
+
+ nsCString mProtocol;
+ nsCString mOrigin;
+
+ nsCString mNegotiatedExtensions;
+
+ uint32_t mWasOpened : 1;
+ uint32_t mClientSetPingInterval : 1;
+ uint32_t mClientSetPingTimeout : 1;
+
+ Atomic<bool> mEncrypted;
+ bool mPingForced;
+ bool mIsServerSide;
+
+ uint32_t mPingInterval; /* milliseconds */
+ uint32_t mPingResponseTimeout; /* milliseconds */
+
+ uint32_t mSerial;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_BaseWebSocketChannel_h
diff --git a/netwerk/protocol/websocket/IPCTransportProvider.cpp b/netwerk/protocol/websocket/IPCTransportProvider.cpp
new file mode 100644
index 000000000..356cf2d57
--- /dev/null
+++ b/netwerk/protocol/websocket/IPCTransportProvider.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/IPCTransportProvider.h"
+
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(TransportProviderParent,
+ nsITransportProvider,
+ nsIHttpUpgradeListener)
+
+TransportProviderParent::TransportProviderParent()
+{
+ MOZ_COUNT_CTOR(TransportProviderParent);
+}
+
+TransportProviderParent::~TransportProviderParent()
+{
+ MOZ_COUNT_DTOR(TransportProviderParent);
+}
+
+NS_IMETHODIMP
+TransportProviderParent::SetListener(nsIHttpUpgradeListener* aListener)
+{
+ MOZ_ASSERT(aListener);
+ mListener = aListener;
+
+ MaybeNotify();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransportProviderParent::GetIPCChild(mozilla::net::PTransportProviderChild** aChild)
+{
+ MOZ_CRASH("Don't call this in parent process");
+ *aChild = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransportProviderParent::OnTransportAvailable(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aSocketIn,
+ nsIAsyncOutputStream* aSocketOut)
+{
+ MOZ_ASSERT(aTransport && aSocketOut && aSocketOut);
+ mTransport = aTransport;
+ mSocketIn = aSocketIn;
+ mSocketOut = aSocketOut;
+
+ MaybeNotify();
+
+ return NS_OK;
+}
+
+void
+TransportProviderParent::MaybeNotify()
+{
+ if (!mListener || !mTransport) {
+ return;
+ }
+
+ mListener->OnTransportAvailable(mTransport, mSocketIn, mSocketOut);
+}
+
+
+NS_IMPL_ISUPPORTS(TransportProviderChild,
+ nsITransportProvider)
+
+TransportProviderChild::TransportProviderChild()
+{
+ MOZ_COUNT_CTOR(TransportProviderChild);
+}
+
+TransportProviderChild::~TransportProviderChild()
+{
+ MOZ_COUNT_DTOR(TransportProviderChild);
+ Send__delete__(this);
+}
+
+NS_IMETHODIMP
+TransportProviderChild::SetListener(nsIHttpUpgradeListener* aListener)
+{
+ MOZ_CRASH("Don't call this in child process");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TransportProviderChild::GetIPCChild(mozilla::net::PTransportProviderChild** aChild)
+{
+ *aChild = this;
+ return NS_OK;
+}
+
+} // net
+} // mozilla
diff --git a/netwerk/protocol/websocket/IPCTransportProvider.h b/netwerk/protocol/websocket/IPCTransportProvider.h
new file mode 100644
index 000000000..343d3c038
--- /dev/null
+++ b/netwerk/protocol/websocket/IPCTransportProvider.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_IPCTransportProvider_h
+#define mozilla_net_IPCTransportProvider_h
+
+#include "nsISupportsImpl.h"
+#include "mozilla/net/PTransportProviderParent.h"
+#include "mozilla/net/PTransportProviderChild.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsITransportProvider.h"
+
+/*
+ * No, the ownership model for TransportProvider is that the child object is
+ * refcounted "normally". I.e. ipdl code doesn't hold a strong reference to
+ * TransportProviderChild.
+ *
+ * When TransportProviderChild goes away, it sends a __delete__ message to the
+ * parent.
+ *
+ * On the parent side, ipdl holds a strong reference to TransportProviderParent.
+ * When the actor is deallocatde it releases the reference to the
+ * TransportProviderParent.
+ *
+ * So effectively the child holds a strong reference to the parent, and are
+ * otherwise normally refcounted and have their lifetime determined by that
+ * refcount.
+ *
+ * The only other caveat is that the creation happens from the parent.
+ * So to create a TransportProvider, a constructor is sent from the parent to
+ * the child. At this time the child gets its first addref.
+ *
+ * A reference to the TransportProvider is then sent as part of some other
+ * message from the parent to the child. For example in the
+ * PFlyWebPublishedServer.WebSocketRequest message.
+ *
+ * The receiver of that message can then grab the TransportProviderChild and
+ * without addreffing it, effectively using the refcount that the
+ * TransportProviderChild got on creation.
+ */
+
+class nsISocketTransport;
+class nsIAsyncInputStream;
+class nsIAsyncOutputStream;
+
+namespace mozilla {
+namespace net {
+
+class TransportProviderParent final : public PTransportProviderParent
+ , public nsITransportProvider
+ , public nsIHttpUpgradeListener
+{
+public:
+ TransportProviderParent();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITRANSPORTPROVIDER
+ NS_DECL_NSIHTTPUPGRADELISTENER
+
+ void ActorDestroy(ActorDestroyReason aWhy) override {};
+
+private:
+ ~TransportProviderParent();
+
+ void MaybeNotify();
+
+ nsCOMPtr<nsIHttpUpgradeListener> mListener;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+};
+
+class TransportProviderChild final : public PTransportProviderChild
+ , public nsITransportProvider
+{
+public:
+ TransportProviderChild();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITRANSPORTPROVIDER
+
+private:
+ ~TransportProviderChild();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/protocol/websocket/PTransportProvider.ipdl b/netwerk/protocol/websocket/PTransportProvider.ipdl
new file mode 100644
index 000000000..329a381b6
--- /dev/null
+++ b/netwerk/protocol/websocket/PTransportProvider.ipdl
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+namespace mozilla {
+namespace net {
+
+/*
+ * The only thing this protocol manages is used for is passing a
+ * PTransportProvider object from parent to child and then back to the parent
+ * again. Hence there's no need for any messages on the protocol itself.
+ */
+
+async protocol PTransportProvider
+{
+ manager PNecko;
+
+parent:
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/PWebSocket.ipdl b/netwerk/protocol/websocket/PWebSocket.ipdl
new file mode 100644
index 000000000..236798429
--- /dev/null
+++ b/netwerk/protocol/websocket/PWebSocket.ipdl
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include protocol PBrowser;
+include protocol PTransportProvider;
+include InputStreamParams;
+include URIParams;
+include NeckoChannelParams;
+
+include protocol PBlob; //FIXME: bug #792908
+
+using class IPC::SerializedLoadContext from "SerializedLoadContext.h";
+using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace net {
+
+union OptionalTransportProvider
+{
+ PTransportProvider;
+ void_t;
+};
+
+async protocol PWebSocket
+{
+ manager PNecko;
+
+parent:
+ // Forwarded methods corresponding to methods on nsIWebSocketChannel
+ async AsyncOpen(OptionalURIParams aURI,
+ nsCString aOrigin,
+ uint64_t aInnerWindowID,
+ nsCString aProtocol,
+ bool aSecure,
+ // ping values only meaningful if client set them
+ uint32_t aPingInterval,
+ bool aClientSetPingInterval,
+ uint32_t aPingTimeout,
+ bool aClientSetPingTimeout,
+ OptionalLoadInfoArgs aLoadInfoArgs,
+ OptionalTransportProvider aProvider,
+ nsCString aNegotiatedExtensions);
+ async Close(uint16_t code, nsCString reason);
+ async SendMsg(nsCString aMsg);
+ async SendBinaryMsg(nsCString aMsg);
+ async SendBinaryStream(InputStreamParams aStream, uint32_t aLength);
+
+ async DeleteSelf();
+
+child:
+ // Forwarded notifications corresponding to the nsIWebSocketListener interface
+ async OnStart(nsCString aProtocol, nsCString aExtensions,
+ nsString aEffectiveURL, bool aEncrypted);
+ async OnStop(nsresult aStatusCode);
+ async OnMessageAvailable(nsCString aMsg);
+ async OnBinaryMessageAvailable(nsCString aMsg);
+ async OnAcknowledge(uint32_t aSize);
+ async OnServerClose(uint16_t code, nsCString aReason);
+
+ async __delete__();
+
+};
+
+} //namespace net
+} //namespace mozilla
diff --git a/netwerk/protocol/websocket/PWebSocketEventListener.ipdl b/netwerk/protocol/websocket/PWebSocketEventListener.ipdl
new file mode 100644
index 000000000..35a107457
--- /dev/null
+++ b/netwerk/protocol/websocket/PWebSocketEventListener.ipdl
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+using mozilla::net::WebSocketFrameData from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace net {
+
+async protocol PWebSocketEventListener
+{
+ manager PNecko;
+
+child:
+ async WebSocketCreated(uint32_t awebSocketSerialID,
+ nsString aURI,
+ nsCString aProtocols);
+
+ async WebSocketOpened(uint32_t awebSocketSerialID,
+ nsString aEffectiveURI,
+ nsCString aProtocols,
+ nsCString aExtensions);
+
+ async WebSocketMessageAvailable(uint32_t awebSocketSerialID,
+ nsCString aData,
+ uint16_t aMessageType);
+
+ async WebSocketClosed(uint32_t awebSocketSerialID,
+ bool aWasClean,
+ uint16_t aCode,
+ nsString aReason);
+
+ async FrameReceived(uint32_t aWebSocketSerialID,
+ WebSocketFrameData aFrameData);
+
+ async FrameSent(uint32_t aWebSocketSerialID,
+ WebSocketFrameData aFrameData);
+
+ async __delete__();
+
+parent:
+ async Close();
+};
+
+} //namespace net
+} //namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketChannel.cpp b/netwerk/protocol/websocket/WebSocketChannel.cpp
new file mode 100644
index 000000000..a6254a088
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -0,0 +1,4107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketFrame.h"
+#include "WebSocketLog.h"
+#include "WebSocketChannel.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/net/WebSocketEventService.h"
+
+#include "nsIURI.h"
+#include "nsIChannel.h"
+#include "nsICryptoHash.h"
+#include "nsIRunnable.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsICancelable.h"
+#include "nsIClassOfService.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsIStreamConverterService.h"
+#include "nsIIOService2.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIProxyInfo.h"
+#include "nsIProxiedChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIDashboardEventNotifier.h"
+#include "nsIEventTarget.h"
+#include "nsIHttpChannel.h"
+#include "nsILoadGroup.h"
+#include "nsIProtocolHandler.h"
+#include "nsIRandomGenerator.h"
+#include "nsISocketTransport.h"
+#include "nsThreadUtils.h"
+#include "nsINetworkLinkService.h"
+#include "nsIObserverService.h"
+#include "nsITransportProvider.h"
+#include "nsCharSeparatedTokenizer.h"
+
+#include "nsAutoPtr.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCRT.h"
+#include "nsThreadUtils.h"
+#include "nsError.h"
+#include "nsStringStream.h"
+#include "nsAlgorithm.h"
+#include "nsProxyRelease.h"
+#include "nsNetUtil.h"
+#include "nsINode.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "nsSocketTransportService2.h"
+
+#include "plbase64.h"
+#include "prmem.h"
+#include "prnetdb.h"
+#include "zlib.h"
+#include <algorithm>
+
+#ifdef MOZ_WIDGET_GONK
+#include "NetStatistics.h"
+#endif
+
+// rather than slurp up all of nsIWebSocket.idl, which lives outside necko, just
+// dupe one constant we need from it
+#define CLOSE_GOING_AWAY 1001
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(WebSocketChannel,
+ nsIWebSocketChannel,
+ nsIHttpUpgradeListener,
+ nsIRequestObserver,
+ nsIStreamListener,
+ nsIProtocolHandler,
+ nsIInputStreamCallback,
+ nsIOutputStreamCallback,
+ nsITimerCallback,
+ nsIDNSListener,
+ nsIProtocolProxyCallback,
+ nsIInterfaceRequestor,
+ nsIChannelEventSink,
+ nsIThreadRetargetableRequest,
+ nsIObserver)
+
+// We implement RFC 6455, which uses Sec-WebSocket-Version: 13 on the wire.
+#define SEC_WEBSOCKET_VERSION "13"
+
+/*
+ * About SSL unsigned certificates
+ *
+ * wss will not work to a host using an unsigned certificate unless there
+ * is already an exception (i.e. it cannot popup a dialog asking for
+ * a security exception). This is similar to how an inlined img will
+ * fail without a dialog if fails for the same reason. This should not
+ * be a problem in practice as it is expected the websocket javascript
+ * is served from the same host as the websocket server (or of course,
+ * a valid cert could just be provided).
+ *
+ */
+
+// some helper classes
+
+//-----------------------------------------------------------------------------
+// FailDelayManager
+//
+// Stores entries (searchable by {host, port}) of connections that have recently
+// failed, so we can do delay of reconnects per RFC 6455 Section 7.2.3
+//-----------------------------------------------------------------------------
+
+
+// Initial reconnect delay is randomly chosen between 200-400 ms.
+// This is a gentler backoff than the 0-5 seconds the spec offhandedly suggests.
+const uint32_t kWSReconnectInitialBaseDelay = 200;
+const uint32_t kWSReconnectInitialRandomDelay = 200;
+
+// Base lifetime (in ms) of a FailDelay: kept longer if more failures occur
+const uint32_t kWSReconnectBaseLifeTime = 60 * 1000;
+// Maximum reconnect delay (in ms)
+const uint32_t kWSReconnectMaxDelay = 60 * 1000;
+
+// hold record of failed connections, and calculates needed delay for reconnects
+// to same host/port.
+class FailDelay
+{
+public:
+ FailDelay(nsCString address, int32_t port)
+ : mAddress(address), mPort(port)
+ {
+ mLastFailure = TimeStamp::Now();
+ mNextDelay = kWSReconnectInitialBaseDelay +
+ (rand() % kWSReconnectInitialRandomDelay);
+ }
+
+ // Called to update settings when connection fails again.
+ void FailedAgain()
+ {
+ mLastFailure = TimeStamp::Now();
+ // We use a truncated exponential backoff as suggested by RFC 6455,
+ // but multiply by 1.5 instead of 2 to be more gradual.
+ mNextDelay = static_cast<uint32_t>(
+ std::min<double>(kWSReconnectMaxDelay, mNextDelay * 1.5));
+ LOG(("WebSocket: FailedAgain: host=%s, port=%d: incremented delay to %lu",
+ mAddress.get(), mPort, mNextDelay));
+ }
+
+ // returns 0 if there is no need to delay (i.e. delay interval is over)
+ uint32_t RemainingDelay(TimeStamp rightNow)
+ {
+ TimeDuration dur = rightNow - mLastFailure;
+ uint32_t sinceFail = (uint32_t) dur.ToMilliseconds();
+ if (sinceFail > mNextDelay)
+ return 0;
+
+ return mNextDelay - sinceFail;
+ }
+
+ bool IsExpired(TimeStamp rightNow)
+ {
+ return (mLastFailure +
+ TimeDuration::FromMilliseconds(kWSReconnectBaseLifeTime + mNextDelay))
+ <= rightNow;
+ }
+
+ nsCString mAddress; // IP address (or hostname if using proxy)
+ int32_t mPort;
+
+private:
+ TimeStamp mLastFailure; // Time of last failed attempt
+ // mLastFailure + mNextDelay is the soonest we'll allow a reconnect
+ uint32_t mNextDelay; // milliseconds
+};
+
+class FailDelayManager
+{
+public:
+ FailDelayManager()
+ {
+ MOZ_COUNT_CTOR(FailDelayManager);
+
+ mDelaysDisabled = false;
+
+ nsCOMPtr<nsIPrefBranch> prefService =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefService) {
+ return;
+ }
+ bool boolpref = true;
+ nsresult rv;
+ rv = prefService->GetBoolPref("network.websocket.delay-failed-reconnects",
+ &boolpref);
+ if (NS_SUCCEEDED(rv) && !boolpref) {
+ mDelaysDisabled = true;
+ }
+ }
+
+ ~FailDelayManager()
+ {
+ MOZ_COUNT_DTOR(FailDelayManager);
+ for (uint32_t i = 0; i < mEntries.Length(); i++) {
+ delete mEntries[i];
+ }
+ }
+
+ void Add(nsCString &address, int32_t port)
+ {
+ if (mDelaysDisabled)
+ return;
+
+ FailDelay *record = new FailDelay(address, port);
+ mEntries.AppendElement(record);
+ }
+
+ // Element returned may not be valid after next main thread event: don't keep
+ // pointer to it around
+ FailDelay* Lookup(nsCString &address, int32_t port,
+ uint32_t *outIndex = nullptr)
+ {
+ if (mDelaysDisabled)
+ return nullptr;
+
+ FailDelay *result = nullptr;
+ TimeStamp rightNow = TimeStamp::Now();
+
+ // We also remove expired entries during search: iterate from end to make
+ // indexing simpler
+ for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
+ FailDelay *fail = mEntries[i];
+ if (fail->mAddress.Equals(address) && fail->mPort == port) {
+ if (outIndex)
+ *outIndex = i;
+ result = fail;
+ // break here: removing more entries would mess up *outIndex.
+ // Any remaining expired entries will be deleted next time Lookup
+ // finds nothing, which is the most common case anyway.
+ break;
+ } else if (fail->IsExpired(rightNow)) {
+ mEntries.RemoveElementAt(i);
+ delete fail;
+ }
+ }
+ return result;
+ }
+
+ // returns true if channel connects immediately, or false if it's delayed
+ void DelayOrBegin(WebSocketChannel *ws)
+ {
+ if (!mDelaysDisabled) {
+ uint32_t failIndex = 0;
+ FailDelay *fail = Lookup(ws->mAddress, ws->mPort, &failIndex);
+
+ if (fail) {
+ TimeStamp rightNow = TimeStamp::Now();
+
+ uint32_t remainingDelay = fail->RemainingDelay(rightNow);
+ if (remainingDelay) {
+ // reconnecting within delay interval: delay by remaining time
+ nsresult rv;
+ ws->mReconnectDelayTimer =
+ do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = ws->mReconnectDelayTimer->InitWithCallback(
+ ws, remainingDelay, nsITimer::TYPE_ONE_SHOT);
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("WebSocket: delaying websocket [this=%p] by %lu ms, changing"
+ " state to CONNECTING_DELAYED", ws,
+ (unsigned long)remainingDelay));
+ ws->mConnecting = CONNECTING_DELAYED;
+ return;
+ }
+ }
+ // if timer fails (which is very unlikely), drop down to BeginOpen call
+ } else if (fail->IsExpired(rightNow)) {
+ mEntries.RemoveElementAt(failIndex);
+ delete fail;
+ }
+ }
+ }
+
+ // Delays disabled, or no previous failure, or we're reconnecting after scheduled
+ // delay interval has passed: connect.
+ ws->BeginOpen(true);
+ }
+
+ // Remove() also deletes all expired entries as it iterates: better for
+ // battery life than using a periodic timer.
+ void Remove(nsCString &address, int32_t port)
+ {
+ TimeStamp rightNow = TimeStamp::Now();
+
+ // iterate from end, to make deletion indexing easier
+ for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
+ FailDelay *entry = mEntries[i];
+ if ((entry->mAddress.Equals(address) && entry->mPort == port) ||
+ entry->IsExpired(rightNow)) {
+ mEntries.RemoveElementAt(i);
+ delete entry;
+ }
+ }
+ }
+
+private:
+ nsTArray<FailDelay *> mEntries;
+ bool mDelaysDisabled;
+};
+
+//-----------------------------------------------------------------------------
+// nsWSAdmissionManager
+//
+// 1) Ensures that only one websocket at a time is CONNECTING to a given IP
+// address (or hostname, if using proxy), per RFC 6455 Section 4.1.
+// 2) Delays reconnects to IP/host after connection failure, per Section 7.2.3
+//-----------------------------------------------------------------------------
+
+class nsWSAdmissionManager
+{
+public:
+ static void Init()
+ {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ sManager = new nsWSAdmissionManager();
+ }
+ }
+
+ static void Shutdown()
+ {
+ StaticMutexAutoLock lock(sLock);
+ delete sManager;
+ sManager = nullptr;
+ }
+
+ // Determine if we will open connection immediately (returns true), or
+ // delay/queue the connection (returns false)
+ static void ConditionallyConnect(WebSocketChannel *ws)
+ {
+ LOG(("Websocket: ConditionallyConnect: [this=%p]", ws));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(ws->mConnecting == NOT_CONNECTING, "opening state");
+
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+
+ // If there is already another WS channel connecting to this IP address,
+ // defer BeginOpen and mark as waiting in queue.
+ bool found = (sManager->IndexOf(ws->mAddress) >= 0);
+
+ // Always add ourselves to queue, even if we'll connect immediately
+ nsOpenConn *newdata = new nsOpenConn(ws->mAddress, ws);
+ LOG(("Websocket: adding conn %p to the queue", newdata));
+ sManager->mQueue.AppendElement(newdata);
+
+ if (found) {
+ LOG(("Websocket: some other channel is connecting, changing state to "
+ "CONNECTING_QUEUED"));
+ ws->mConnecting = CONNECTING_QUEUED;
+ } else {
+ sManager->mFailures.DelayOrBegin(ws);
+ }
+ }
+
+ static void OnConnected(WebSocketChannel *aChannel)
+ {
+ LOG(("Websocket: OnConnected: [this=%p]", aChannel));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(aChannel->mConnecting == CONNECTING_IN_PROGRESS,
+ "Channel completed connect, but not connecting?");
+
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+
+ LOG(("Websocket: changing state to NOT_CONNECTING"));
+ aChannel->mConnecting = NOT_CONNECTING;
+
+ // Remove from queue
+ sManager->RemoveFromQueue(aChannel);
+
+ // Connection succeeded, so stop keeping track of any previous failures
+ sManager->mFailures.Remove(aChannel->mAddress, aChannel->mPort);
+
+ // Check for queued connections to same host.
+ // Note: still need to check for failures, since next websocket with same
+ // host may have different port
+ sManager->ConnectNext(aChannel->mAddress);
+ }
+
+ // Called every time a websocket channel ends its session (including going away
+ // w/o ever successfully creating a connection)
+ static void OnStopSession(WebSocketChannel *aChannel, nsresult aReason)
+ {
+ LOG(("Websocket: OnStopSession: [this=%p, reason=0x%08x]", aChannel,
+ aReason));
+
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+
+ if (NS_FAILED(aReason)) {
+ // Have we seen this failure before?
+ FailDelay *knownFailure = sManager->mFailures.Lookup(aChannel->mAddress,
+ aChannel->mPort);
+ if (knownFailure) {
+ if (aReason == NS_ERROR_NOT_CONNECTED) {
+ // Don't count close() before connection as a network error
+ LOG(("Websocket close() before connection to %s, %d completed"
+ " [this=%p]", aChannel->mAddress.get(), (int)aChannel->mPort,
+ aChannel));
+ } else {
+ // repeated failure to connect: increase delay for next connection
+ knownFailure->FailedAgain();
+ }
+ } else {
+ // new connection failure: record it.
+ LOG(("WebSocket: connection to %s, %d failed: [this=%p]",
+ aChannel->mAddress.get(), (int)aChannel->mPort, aChannel));
+ sManager->mFailures.Add(aChannel->mAddress, aChannel->mPort);
+ }
+ }
+
+ if (aChannel->mConnecting) {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ // Only way a connecting channel may get here w/o failing is if it was
+ // closed with GOING_AWAY (1001) because of navigation, tab close, etc.
+ MOZ_ASSERT(NS_FAILED(aReason) ||
+ aChannel->mScriptCloseCode == CLOSE_GOING_AWAY,
+ "websocket closed while connecting w/o failing?");
+
+ sManager->RemoveFromQueue(aChannel);
+
+ bool wasNotQueued = (aChannel->mConnecting != CONNECTING_QUEUED);
+ LOG(("Websocket: changing state to NOT_CONNECTING"));
+ aChannel->mConnecting = NOT_CONNECTING;
+ if (wasNotQueued) {
+ sManager->ConnectNext(aChannel->mAddress);
+ }
+ }
+ }
+
+ static void IncrementSessionCount()
+ {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+ sManager->mSessionCount++;
+ }
+
+ static void DecrementSessionCount()
+ {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+ sManager->mSessionCount--;
+ }
+
+ static void GetSessionCount(int32_t &aSessionCount)
+ {
+ StaticMutexAutoLock lock(sLock);
+ if (!sManager) {
+ return;
+ }
+ aSessionCount = sManager->mSessionCount;
+ }
+
+private:
+ nsWSAdmissionManager() : mSessionCount(0)
+ {
+ MOZ_COUNT_CTOR(nsWSAdmissionManager);
+ }
+
+ ~nsWSAdmissionManager()
+ {
+ MOZ_COUNT_DTOR(nsWSAdmissionManager);
+ for (uint32_t i = 0; i < mQueue.Length(); i++)
+ delete mQueue[i];
+ }
+
+ class nsOpenConn
+ {
+ public:
+ nsOpenConn(nsCString &addr, WebSocketChannel *channel)
+ : mAddress(addr), mChannel(channel) { MOZ_COUNT_CTOR(nsOpenConn); }
+ ~nsOpenConn() { MOZ_COUNT_DTOR(nsOpenConn); }
+
+ nsCString mAddress;
+ WebSocketChannel *mChannel;
+ };
+
+ void ConnectNext(nsCString &hostName)
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ int32_t index = IndexOf(hostName);
+ if (index >= 0) {
+ WebSocketChannel *chan = mQueue[index]->mChannel;
+
+ MOZ_ASSERT(chan->mConnecting == CONNECTING_QUEUED,
+ "transaction not queued but in queue");
+ LOG(("WebSocket: ConnectNext: found channel [this=%p] in queue", chan));
+
+ mFailures.DelayOrBegin(chan);
+ }
+ }
+
+ void RemoveFromQueue(WebSocketChannel *aChannel)
+ {
+ LOG(("Websocket: RemoveFromQueue: [this=%p]", aChannel));
+ int32_t index = IndexOf(aChannel);
+ MOZ_ASSERT(index >= 0, "connection to remove not in queue");
+ if (index >= 0) {
+ nsOpenConn *olddata = mQueue[index];
+ mQueue.RemoveElementAt(index);
+ LOG(("Websocket: removing conn %p from the queue", olddata));
+ delete olddata;
+ }
+ }
+
+ int32_t IndexOf(nsCString &aStr)
+ {
+ for (uint32_t i = 0; i < mQueue.Length(); i++)
+ if (aStr == (mQueue[i])->mAddress)
+ return i;
+ return -1;
+ }
+
+ int32_t IndexOf(WebSocketChannel *aChannel)
+ {
+ for (uint32_t i = 0; i < mQueue.Length(); i++)
+ if (aChannel == (mQueue[i])->mChannel)
+ return i;
+ return -1;
+ }
+
+ // SessionCount might be decremented from the main or the socket
+ // thread, so manage it with atomic counters
+ Atomic<int32_t> mSessionCount;
+
+ // Queue for websockets that have not completed connecting yet.
+ // The first nsOpenConn with a given address will be either be
+ // CONNECTING_IN_PROGRESS or CONNECTING_DELAYED. Later ones with the same
+ // hostname must be CONNECTING_QUEUED.
+ //
+ // We could hash hostnames instead of using a single big vector here, but the
+ // dataset is expected to be small.
+ nsTArray<nsOpenConn *> mQueue;
+
+ FailDelayManager mFailures;
+
+ static nsWSAdmissionManager *sManager;
+ static StaticMutex sLock;
+};
+
+nsWSAdmissionManager *nsWSAdmissionManager::sManager;
+StaticMutex nsWSAdmissionManager::sLock;
+
+//-----------------------------------------------------------------------------
+// CallOnMessageAvailable
+//-----------------------------------------------------------------------------
+
+class CallOnMessageAvailable final : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ CallOnMessageAvailable(WebSocketChannel* aChannel,
+ nsACString& aData,
+ int32_t aLen)
+ : mChannel(aChannel),
+ mListenerMT(aChannel->mListenerMT),
+ mData(aData),
+ mLen(aLen) {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ if (mListenerMT) {
+ if (mLen < 0) {
+ mListenerMT->mListener->OnMessageAvailable(mListenerMT->mContext,
+ mData);
+ } else {
+ mListenerMT->mListener->OnBinaryMessageAvailable(mListenerMT->mContext,
+ mData);
+ }
+ }
+
+ return NS_OK;
+ }
+
+private:
+ ~CallOnMessageAvailable() {}
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ nsCString mData;
+ int32_t mLen;
+};
+NS_IMPL_ISUPPORTS(CallOnMessageAvailable, nsIRunnable)
+
+//-----------------------------------------------------------------------------
+// CallOnStop
+//-----------------------------------------------------------------------------
+
+class CallOnStop final : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ CallOnStop(WebSocketChannel* aChannel,
+ nsresult aReason)
+ : mChannel(aChannel),
+ mListenerMT(mChannel->mListenerMT),
+ mReason(aReason)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ if (mListenerMT) {
+ mListenerMT->mListener->OnStop(mListenerMT->mContext, mReason);
+ mChannel->mListenerMT = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+private:
+ ~CallOnStop() {}
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ nsresult mReason;
+};
+NS_IMPL_ISUPPORTS(CallOnStop, nsIRunnable)
+
+//-----------------------------------------------------------------------------
+// CallOnServerClose
+//-----------------------------------------------------------------------------
+
+class CallOnServerClose final : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ CallOnServerClose(WebSocketChannel* aChannel,
+ uint16_t aCode,
+ nsACString& aReason)
+ : mChannel(aChannel),
+ mListenerMT(mChannel->mListenerMT),
+ mCode(aCode),
+ mReason(aReason) {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ if (mListenerMT) {
+ mListenerMT->mListener->OnServerClose(mListenerMT->mContext, mCode,
+ mReason);
+ }
+ return NS_OK;
+ }
+
+private:
+ ~CallOnServerClose() {}
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ uint16_t mCode;
+ nsCString mReason;
+};
+NS_IMPL_ISUPPORTS(CallOnServerClose, nsIRunnable)
+
+//-----------------------------------------------------------------------------
+// CallAcknowledge
+//-----------------------------------------------------------------------------
+
+class CallAcknowledge final : public CancelableRunnable
+{
+public:
+ CallAcknowledge(WebSocketChannel* aChannel,
+ uint32_t aSize)
+ : mChannel(aChannel),
+ mListenerMT(mChannel->mListenerMT),
+ mSize(aSize) {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mChannel->IsOnTargetThread());
+
+ LOG(("WebSocketChannel::CallAcknowledge: Size %u\n", mSize));
+ if (mListenerMT) {
+ mListenerMT->mListener->OnAcknowledge(mListenerMT->mContext, mSize);
+ }
+ return NS_OK;
+ }
+
+private:
+ ~CallAcknowledge() {}
+
+ RefPtr<WebSocketChannel> mChannel;
+ RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
+ uint32_t mSize;
+};
+
+//-----------------------------------------------------------------------------
+// CallOnTransportAvailable
+//-----------------------------------------------------------------------------
+
+class CallOnTransportAvailable final : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ CallOnTransportAvailable(WebSocketChannel *aChannel,
+ nsISocketTransport *aTransport,
+ nsIAsyncInputStream *aSocketIn,
+ nsIAsyncOutputStream *aSocketOut)
+ : mChannel(aChannel),
+ mTransport(aTransport),
+ mSocketIn(aSocketIn),
+ mSocketOut(aSocketOut) {}
+
+ NS_IMETHOD Run() override
+ {
+ LOG(("WebSocketChannel::CallOnTransportAvailable %p\n", this));
+ return mChannel->OnTransportAvailable(mTransport, mSocketIn, mSocketOut);
+ }
+
+private:
+ ~CallOnTransportAvailable() {}
+
+ RefPtr<WebSocketChannel> mChannel;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+};
+NS_IMPL_ISUPPORTS(CallOnTransportAvailable, nsIRunnable)
+
+//-----------------------------------------------------------------------------
+// PMCECompression
+//-----------------------------------------------------------------------------
+
+class PMCECompression
+{
+public:
+ PMCECompression(bool aNoContextTakeover,
+ int32_t aLocalMaxWindowBits,
+ int32_t aRemoteMaxWindowBits)
+ : mActive(false)
+ , mNoContextTakeover(aNoContextTakeover)
+ , mResetDeflater(false)
+ , mMessageDeflated(false)
+ {
+ MOZ_COUNT_CTOR(PMCECompression);
+
+ mDeflater.zalloc = mInflater.zalloc = Z_NULL;
+ mDeflater.zfree = mInflater.zfree = Z_NULL;
+ mDeflater.opaque = mInflater.opaque = Z_NULL;
+
+ if (deflateInit2(&mDeflater, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ -aLocalMaxWindowBits, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
+ if (inflateInit2(&mInflater, -aRemoteMaxWindowBits) == Z_OK) {
+ mActive = true;
+ } else {
+ deflateEnd(&mDeflater);
+ }
+ }
+ }
+
+ ~PMCECompression()
+ {
+ MOZ_COUNT_DTOR(PMCECompression);
+
+ if (mActive) {
+ inflateEnd(&mInflater);
+ deflateEnd(&mDeflater);
+ }
+ }
+
+ bool Active()
+ {
+ return mActive;
+ }
+
+ void SetMessageDeflated()
+ {
+ MOZ_ASSERT(!mMessageDeflated);
+ mMessageDeflated = true;
+ }
+ bool IsMessageDeflated()
+ {
+ return mMessageDeflated;
+ }
+
+ bool UsingContextTakeover()
+ {
+ return !mNoContextTakeover;
+ }
+
+ nsresult Deflate(uint8_t *data, uint32_t dataLen, nsACString &_retval)
+ {
+ if (mResetDeflater || mNoContextTakeover) {
+ if (deflateReset(&mDeflater) != Z_OK) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mResetDeflater = false;
+ }
+
+ mDeflater.avail_out = kBufferLen;
+ mDeflater.next_out = mBuffer;
+ mDeflater.avail_in = dataLen;
+ mDeflater.next_in = data;
+
+ while (true) {
+ int zerr = deflate(&mDeflater, Z_SYNC_FLUSH);
+
+ if (zerr != Z_OK) {
+ mResetDeflater = true;
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ uint32_t deflated = kBufferLen - mDeflater.avail_out;
+ if (deflated > 0) {
+ _retval.Append(reinterpret_cast<char *>(mBuffer), deflated);
+ }
+
+ mDeflater.avail_out = kBufferLen;
+ mDeflater.next_out = mBuffer;
+
+ if (mDeflater.avail_in > 0) {
+ continue; // There is still some data to deflate
+ }
+
+ if (deflated == kBufferLen) {
+ continue; // There was not enough space in the buffer
+ }
+
+ break;
+ }
+
+ if (_retval.Length() < 4) {
+ MOZ_ASSERT(false, "Expected trailing not found in deflated data!");
+ mResetDeflater = true;
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ _retval.Truncate(_retval.Length() - 4);
+
+ return NS_OK;
+ }
+
+ nsresult Inflate(uint8_t *data, uint32_t dataLen, nsACString &_retval)
+ {
+ mMessageDeflated = false;
+
+ Bytef trailingData[] = { 0x00, 0x00, 0xFF, 0xFF };
+ bool trailingDataUsed = false;
+
+ mInflater.avail_out = kBufferLen;
+ mInflater.next_out = mBuffer;
+ mInflater.avail_in = dataLen;
+ mInflater.next_in = data;
+
+ while (true) {
+ int zerr = inflate(&mInflater, Z_NO_FLUSH);
+
+ if (zerr == Z_STREAM_END) {
+ Bytef *saveNextIn = mInflater.next_in;
+ uint32_t saveAvailIn = mInflater.avail_in;
+ Bytef *saveNextOut = mInflater.next_out;
+ uint32_t saveAvailOut = mInflater.avail_out;
+
+ inflateReset(&mInflater);
+
+ mInflater.next_in = saveNextIn;
+ mInflater.avail_in = saveAvailIn;
+ mInflater.next_out = saveNextOut;
+ mInflater.avail_out = saveAvailOut;
+ } else if (zerr != Z_OK && zerr != Z_BUF_ERROR) {
+ return NS_ERROR_INVALID_CONTENT_ENCODING;
+ }
+
+ uint32_t inflated = kBufferLen - mInflater.avail_out;
+ if (inflated > 0) {
+ _retval.Append(reinterpret_cast<char *>(mBuffer), inflated);
+ }
+
+ mInflater.avail_out = kBufferLen;
+ mInflater.next_out = mBuffer;
+
+ if (mInflater.avail_in > 0) {
+ continue; // There is still some data to inflate
+ }
+
+ if (inflated == kBufferLen) {
+ continue; // There was not enough space in the buffer
+ }
+
+ if (!trailingDataUsed) {
+ trailingDataUsed = true;
+ mInflater.avail_in = sizeof(trailingData);
+ mInflater.next_in = trailingData;
+ continue;
+ }
+
+ return NS_OK;
+ }
+ }
+
+private:
+ bool mActive;
+ bool mNoContextTakeover;
+ bool mResetDeflater;
+ bool mMessageDeflated;
+ z_stream mDeflater;
+ z_stream mInflater;
+ const static uint32_t kBufferLen = 4096;
+ uint8_t mBuffer[kBufferLen];
+};
+
+//-----------------------------------------------------------------------------
+// OutboundMessage
+//-----------------------------------------------------------------------------
+
+enum WsMsgType {
+ kMsgTypeString = 0,
+ kMsgTypeBinaryString,
+ kMsgTypeStream,
+ kMsgTypePing,
+ kMsgTypePong,
+ kMsgTypeFin
+};
+
+static const char* msgNames[] = {
+ "text",
+ "binaryString",
+ "binaryStream",
+ "ping",
+ "pong",
+ "close"
+};
+
+class OutboundMessage
+{
+public:
+ OutboundMessage(WsMsgType type, nsCString *str)
+ : mMsgType(type), mDeflated(false), mOrigLength(0)
+ {
+ MOZ_COUNT_CTOR(OutboundMessage);
+ mMsg.pString.mValue = str;
+ mMsg.pString.mOrigValue = nullptr;
+ mLength = str ? str->Length() : 0;
+ }
+
+ OutboundMessage(nsIInputStream *stream, uint32_t length)
+ : mMsgType(kMsgTypeStream), mLength(length), mDeflated(false)
+ , mOrigLength(0)
+ {
+ MOZ_COUNT_CTOR(OutboundMessage);
+ mMsg.pStream = stream;
+ mMsg.pStream->AddRef();
+ }
+
+ ~OutboundMessage() {
+ MOZ_COUNT_DTOR(OutboundMessage);
+ switch (mMsgType) {
+ case kMsgTypeString:
+ case kMsgTypeBinaryString:
+ case kMsgTypePing:
+ case kMsgTypePong:
+ delete mMsg.pString.mValue;
+ if (mMsg.pString.mOrigValue)
+ delete mMsg.pString.mOrigValue;
+ break;
+ case kMsgTypeStream:
+ // for now this only gets hit if msg deleted w/o being sent
+ if (mMsg.pStream) {
+ mMsg.pStream->Close();
+ mMsg.pStream->Release();
+ }
+ break;
+ case kMsgTypeFin:
+ break; // do-nothing: avoid compiler warning
+ }
+ }
+
+ WsMsgType GetMsgType() const { return mMsgType; }
+ int32_t Length() const { return mLength; }
+ int32_t OrigLength() const { return mDeflated ? mOrigLength : mLength; }
+
+ uint8_t* BeginWriting() {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ return (uint8_t *)(mMsg.pString.mValue ? mMsg.pString.mValue->BeginWriting() : nullptr);
+ }
+
+ uint8_t* BeginReading() {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ return (uint8_t *)(mMsg.pString.mValue ? mMsg.pString.mValue->BeginReading() : nullptr);
+ }
+
+ uint8_t* BeginOrigReading() {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ if (!mDeflated)
+ return BeginReading();
+ return (uint8_t *)(mMsg.pString.mOrigValue ? mMsg.pString.mOrigValue->BeginReading() : nullptr);
+ }
+
+ nsresult ConvertStreamToString()
+ {
+ MOZ_ASSERT(mMsgType == kMsgTypeStream, "Not a stream!");
+
+#ifdef DEBUG
+ // Make sure we got correct length from Blob
+ uint64_t bytes;
+ mMsg.pStream->Available(&bytes);
+ NS_ASSERTION(bytes == mLength, "Stream length != blob length!");
+#endif
+
+ nsAutoPtr<nsCString> temp(new nsCString());
+ nsresult rv = NS_ReadInputStreamToString(mMsg.pStream, *temp, mLength);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mMsg.pStream->Close();
+ mMsg.pStream->Release();
+ mMsg.pString.mValue = temp.forget();
+ mMsg.pString.mOrigValue = nullptr;
+ mMsgType = kMsgTypeBinaryString;
+
+ return NS_OK;
+ }
+
+ bool DeflatePayload(PMCECompression *aCompressor)
+ {
+ MOZ_ASSERT(mMsgType != kMsgTypeStream,
+ "Stream should have been converted to string by now");
+ MOZ_ASSERT(!mDeflated);
+
+ nsresult rv;
+
+ if (mLength == 0) {
+ // Empty message
+ return false;
+ }
+
+ nsAutoPtr<nsCString> temp(new nsCString());
+ rv = aCompressor->Deflate(BeginReading(), mLength, *temp);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::OutboundMessage: Deflating payload failed "
+ "[rv=0x%08x]\n", rv));
+ return false;
+ }
+
+ if (!aCompressor->UsingContextTakeover() && temp->Length() > mLength) {
+ // When "<local>_no_context_takeover" was negotiated, do not send deflated
+ // payload if it's larger that the original one. OTOH, it makes sense
+ // to send the larger deflated payload when the sliding window is not
+ // reset between messages because if we would skip some deflated block
+ // we would need to empty the sliding window which could affect the
+ // compression of the subsequent messages.
+ LOG(("WebSocketChannel::OutboundMessage: Not deflating message since the "
+ "deflated payload is larger than the original one [deflated=%d, "
+ "original=%d]", temp->Length(), mLength));
+ return false;
+ }
+
+ mOrigLength = mLength;
+ mDeflated = true;
+ mLength = temp->Length();
+ mMsg.pString.mOrigValue = mMsg.pString.mValue;
+ mMsg.pString.mValue = temp.forget();
+ return true;
+ }
+
+private:
+ union {
+ struct {
+ nsCString *mValue;
+ nsCString *mOrigValue;
+ } pString;
+ nsIInputStream *pStream;
+ } mMsg;
+ WsMsgType mMsgType;
+ uint32_t mLength;
+ bool mDeflated;
+ uint32_t mOrigLength;
+};
+
+//-----------------------------------------------------------------------------
+// OutboundEnqueuer
+//-----------------------------------------------------------------------------
+
+class OutboundEnqueuer final : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ OutboundEnqueuer(WebSocketChannel *aChannel, OutboundMessage *aMsg)
+ : mChannel(aChannel), mMessage(aMsg) {}
+
+ NS_IMETHOD Run() override
+ {
+ mChannel->EnqueueOutgoingMessage(mChannel->mOutgoingMessages, mMessage);
+ return NS_OK;
+ }
+
+private:
+ ~OutboundEnqueuer() {}
+
+ RefPtr<WebSocketChannel> mChannel;
+ OutboundMessage *mMessage;
+};
+NS_IMPL_ISUPPORTS(OutboundEnqueuer, nsIRunnable)
+
+
+//-----------------------------------------------------------------------------
+// WebSocketChannel
+//-----------------------------------------------------------------------------
+
+WebSocketChannel::WebSocketChannel() :
+ mPort(0),
+ mCloseTimeout(20000),
+ mOpenTimeout(20000),
+ mConnecting(NOT_CONNECTING),
+ mMaxConcurrentConnections(200),
+ mGotUpgradeOK(0),
+ mRecvdHttpUpgradeTransport(0),
+ mAutoFollowRedirects(0),
+ mAllowPMCE(1),
+ mPingOutstanding(0),
+ mReleaseOnTransmit(0),
+ mDataStarted(0),
+ mRequestedClose(0),
+ mClientClosed(0),
+ mServerClosed(0),
+ mStopped(0),
+ mCalledOnStop(0),
+ mTCPClosed(0),
+ mOpenedHttpChannel(0),
+ mIncrementedSessionCount(0),
+ mDecrementedSessionCount(0),
+ mMaxMessageSize(INT32_MAX),
+ mStopOnClose(NS_OK),
+ mServerCloseCode(CLOSE_ABNORMAL),
+ mScriptCloseCode(0),
+ mFragmentOpcode(nsIWebSocketFrame::OPCODE_CONTINUATION),
+ mFragmentAccumulator(0),
+ mBuffered(0),
+ mBufferSize(kIncomingBufferInitialSize),
+ mCurrentOut(nullptr),
+ mCurrentOutSent(0),
+ mDynamicOutputSize(0),
+ mDynamicOutput(nullptr),
+ mPrivateBrowsing(false),
+ mConnectionLogService(nullptr),
+ mCountRecv(0),
+ mCountSent(0),
+ mAppId(NECKO_NO_APP_ID),
+ mIsInIsolatedMozBrowser(false)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ LOG(("WebSocketChannel::WebSocketChannel() %p\n", this));
+
+ nsWSAdmissionManager::Init();
+
+ mFramePtr = mBuffer = static_cast<uint8_t *>(moz_xmalloc(mBufferSize));
+
+ nsresult rv;
+ mConnectionLogService = do_GetService("@mozilla.org/network/dashboard;1",&rv);
+ if (NS_FAILED(rv))
+ LOG(("Failed to initiate dashboard service."));
+
+ mService = WebSocketEventService::GetOrCreate();
+}
+
+WebSocketChannel::~WebSocketChannel()
+{
+ LOG(("WebSocketChannel::~WebSocketChannel() %p\n", this));
+
+ if (mWasOpened) {
+ MOZ_ASSERT(mCalledOnStop, "WebSocket was opened but OnStop was not called");
+ MOZ_ASSERT(mStopped, "WebSocket was opened but never stopped");
+ }
+ MOZ_ASSERT(!mCancelable, "DNS/Proxy Request still alive at destruction");
+ MOZ_ASSERT(!mConnecting, "Should not be connecting in destructor");
+
+ free(mBuffer);
+ free(mDynamicOutput);
+ delete mCurrentOut;
+
+ while ((mCurrentOut = (OutboundMessage *) mOutgoingPingMessages.PopFront()))
+ delete mCurrentOut;
+ while ((mCurrentOut = (OutboundMessage *) mOutgoingPongMessages.PopFront()))
+ delete mCurrentOut;
+ while ((mCurrentOut = (OutboundMessage *) mOutgoingMessages.PopFront()))
+ delete mCurrentOut;
+
+ NS_ReleaseOnMainThread(mURI.forget());
+ NS_ReleaseOnMainThread(mOriginalURI.forget());
+
+ mListenerMT = nullptr;
+
+ NS_ReleaseOnMainThread(mLoadGroup.forget());
+ NS_ReleaseOnMainThread(mLoadInfo.forget());
+ NS_ReleaseOnMainThread(mService.forget());
+}
+
+NS_IMETHODIMP
+WebSocketChannel::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ LOG(("WebSocketChannel::Observe [topic=\"%s\"]\n", topic));
+
+ if (strcmp(topic, NS_NETWORK_LINK_TOPIC) == 0) {
+ nsCString converted = NS_ConvertUTF16toUTF8(data);
+ const char *state = converted.get();
+
+ if (strcmp(state, NS_NETWORK_LINK_DATA_CHANGED) == 0) {
+ LOG(("WebSocket: received network CHANGED event"));
+
+ if (!mSocketThread) {
+ // there has not been an asyncopen yet on the object and then we need
+ // no ping.
+ LOG(("WebSocket: early object, no ping needed"));
+ } else {
+ // Next we check mDataStarted, which we need to do on mTargetThread.
+ if (!IsOnTargetThread()) {
+ mTargetThread->Dispatch(
+ NewRunnableMethod(this, &WebSocketChannel::OnNetworkChanged),
+ NS_DISPATCH_NORMAL);
+ } else {
+ OnNetworkChanged();
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+WebSocketChannel::OnNetworkChanged()
+{
+ if (IsOnTargetThread()) {
+ LOG(("WebSocketChannel::OnNetworkChanged() - on target thread %p", this));
+
+ if (!mDataStarted) {
+ LOG(("WebSocket: data not started yet, no ping needed"));
+ return NS_OK;
+ }
+
+ return mSocketThread->Dispatch(
+ NewRunnableMethod(this, &WebSocketChannel::OnNetworkChanged),
+ NS_DISPATCH_NORMAL);
+ }
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
+
+ LOG(("WebSocketChannel::OnNetworkChanged() - on socket thread %p", this));
+
+ if (mPingOutstanding) {
+ // If there's an outstanding ping that's expected to get a pong back
+ // we let that do its thing.
+ LOG(("WebSocket: pong already pending"));
+ return NS_OK;
+ }
+
+ if (mPingForced) {
+ // avoid more than one
+ LOG(("WebSocket: forced ping timer already fired"));
+ return NS_OK;
+ }
+
+ LOG(("nsWebSocketChannel:: Generating Ping as network changed\n"));
+
+ if (!mPingTimer) {
+ // The ping timer is only conditionally running already. If it wasn't
+ // already created do it here.
+ nsresult rv;
+ mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocket: unable to create ping timer!"));
+ NS_WARNING("unable to create ping timer!");
+ return rv;
+ }
+ }
+ // Trigger the ping timeout asap to fire off a new ping. Wait just
+ // a little bit to better avoid multi-triggers.
+ mPingForced = 1;
+ mPingTimer->InitWithCallback(this, 200, nsITimer::TYPE_ONE_SHOT);
+
+ return NS_OK;
+}
+
+void
+WebSocketChannel::Shutdown()
+{
+ nsWSAdmissionManager::Shutdown();
+}
+
+bool
+WebSocketChannel::IsOnTargetThread()
+{
+ MOZ_ASSERT(mTargetThread);
+ bool isOnTargetThread = false;
+ nsresult rv = mTargetThread->IsOnCurrentThread(&isOnTargetThread);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_FAILED(rv) ? false : isOnTargetThread;
+}
+
+void
+WebSocketChannel::GetEffectiveURL(nsAString& aEffectiveURL) const
+{
+ aEffectiveURL = mEffectiveURL;
+}
+
+bool
+WebSocketChannel::IsEncrypted() const
+{
+ return mEncrypted;
+}
+
+void
+WebSocketChannel::BeginOpen(bool aCalledFromAdmissionManager)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ LOG(("WebSocketChannel::BeginOpen() %p\n", this));
+
+ // Important that we set CONNECTING_IN_PROGRESS before any call to
+ // AbortSession here: ensures that any remaining queued connection(s) are
+ // scheduled in OnStopSession
+ LOG(("Websocket: changing state to CONNECTING_IN_PROGRESS"));
+ mConnecting = CONNECTING_IN_PROGRESS;
+
+ if (aCalledFromAdmissionManager) {
+ // When called from nsWSAdmissionManager post an event to avoid potential
+ // re-entering of nsWSAdmissionManager and its lock.
+ NS_DispatchToMainThread(
+ NewRunnableMethod(this, &WebSocketChannel::BeginOpenInternal),
+ NS_DISPATCH_NORMAL);
+ } else {
+ BeginOpenInternal();
+ }
+}
+
+void
+WebSocketChannel::BeginOpenInternal()
+{
+ LOG(("WebSocketChannel::BeginOpenInternal() %p\n", this));
+
+ nsresult rv;
+
+ if (mRedirectCallback) {
+ LOG(("WebSocketChannel::BeginOpenInternal: Resuming Redirect\n"));
+ rv = mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
+ mRedirectCallback = nullptr;
+ return;
+ }
+
+ nsCOMPtr<nsIChannel> localChannel = do_QueryInterface(mChannel, &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::BeginOpenInternal: cannot async open\n"));
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ if (localChannel) {
+ NS_GetAppInfo(localChannel, &mAppId, &mIsInIsolatedMozBrowser);
+ }
+
+#ifdef MOZ_WIDGET_GONK
+ if (mAppId != NECKO_NO_APP_ID) {
+ nsCOMPtr<nsINetworkInfo> activeNetworkInfo;
+ GetActiveNetworkInfo(activeNetworkInfo);
+ mActiveNetworkInfo =
+ new nsMainThreadPtrHolder<nsINetworkInfo>(activeNetworkInfo);
+ }
+#endif
+
+ rv = NS_MaybeOpenChannelUsingAsyncOpen2(localChannel, this);
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::BeginOpenInternal: cannot async open\n"));
+ AbortSession(NS_ERROR_CONNECTION_REFUSED);
+ return;
+ }
+ mOpenedHttpChannel = 1;
+
+ mOpenTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::BeginOpenInternal: cannot create open timer\n"));
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ rv = mOpenTimer->InitWithCallback(this, mOpenTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::BeginOpenInternal: cannot initialize open "
+ "timer\n"));
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return;
+ }
+}
+
+bool
+WebSocketChannel::IsPersistentFramePtr()
+{
+ return (mFramePtr >= mBuffer && mFramePtr < mBuffer + mBufferSize);
+}
+
+// Extends the internal buffer by count and returns the total
+// amount of data available for read
+//
+// Accumulated fragment size is passed in instead of using the member
+// variable beacuse when transitioning from the stack to the persistent
+// read buffer we want to explicitly include them in the buffer instead
+// of as already existing data.
+bool
+WebSocketChannel::UpdateReadBuffer(uint8_t *buffer, uint32_t count,
+ uint32_t accumulatedFragments,
+ uint32_t *available)
+{
+ LOG(("WebSocketChannel::UpdateReadBuffer() %p [%p %u]\n",
+ this, buffer, count));
+
+ if (!mBuffered)
+ mFramePtr = mBuffer;
+
+ MOZ_ASSERT(IsPersistentFramePtr(), "update read buffer bad mFramePtr");
+ MOZ_ASSERT(mFramePtr - accumulatedFragments >= mBuffer,
+ "reserved FramePtr bad");
+
+ if (mBuffered + count <= mBufferSize) {
+ // append to existing buffer
+ LOG(("WebSocketChannel: update read buffer absorbed %u\n", count));
+ } else if (mBuffered + count -
+ (mFramePtr - accumulatedFragments - mBuffer) <= mBufferSize) {
+ // make room in existing buffer by shifting unused data to start
+ mBuffered -= (mFramePtr - mBuffer - accumulatedFragments);
+ LOG(("WebSocketChannel: update read buffer shifted %u\n", mBuffered));
+ ::memmove(mBuffer, mFramePtr - accumulatedFragments, mBuffered);
+ mFramePtr = mBuffer + accumulatedFragments;
+ } else {
+ // existing buffer is not sufficient, extend it
+ mBufferSize += count + 8192 + mBufferSize/3;
+ LOG(("WebSocketChannel: update read buffer extended to %u\n", mBufferSize));
+ uint8_t *old = mBuffer;
+ mBuffer = (uint8_t *)realloc(mBuffer, mBufferSize);
+ if (!mBuffer) {
+ mBuffer = old;
+ return false;
+ }
+ mFramePtr = mBuffer + (mFramePtr - old);
+ }
+
+ ::memcpy(mBuffer + mBuffered, buffer, count);
+ mBuffered += count;
+
+ if (available)
+ *available = mBuffered - (mFramePtr - mBuffer);
+
+ return true;
+}
+
+nsresult
+WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
+{
+ LOG(("WebSocketChannel::ProcessInput %p [%d %d]\n", this, count, mBuffered));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
+
+ nsresult rv;
+
+ // The purpose of ping/pong is to actively probe the peer so that an
+ // unreachable peer is not mistaken for a period of idleness. This
+ // implementation accepts any application level read activity as a sign of
+ // life, it does not necessarily have to be a pong.
+ ResetPingTimer();
+
+ uint32_t avail;
+
+ if (!mBuffered) {
+ // Most of the time we can process right off the stack buffer without
+ // having to accumulate anything
+ mFramePtr = buffer;
+ avail = count;
+ } else {
+ if (!UpdateReadBuffer(buffer, count, mFragmentAccumulator, &avail)) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+ }
+
+ uint8_t *payload;
+ uint32_t totalAvail = avail;
+
+ while (avail >= 2) {
+ int64_t payloadLength64 = mFramePtr[1] & kPayloadLengthBitsMask;
+ uint8_t finBit = mFramePtr[0] & kFinalFragBit;
+ uint8_t rsvBits = mFramePtr[0] & kRsvBitsMask;
+ uint8_t rsvBit1 = mFramePtr[0] & kRsv1Bit;
+ uint8_t rsvBit2 = mFramePtr[0] & kRsv2Bit;
+ uint8_t rsvBit3 = mFramePtr[0] & kRsv3Bit;
+ uint8_t opcode = mFramePtr[0] & kOpcodeBitsMask;
+ uint8_t maskBit = mFramePtr[1] & kMaskBit;
+ uint32_t mask = 0;
+
+ uint32_t framingLength = 2;
+ if (maskBit)
+ framingLength += 4;
+
+ if (payloadLength64 < 126) {
+ if (avail < framingLength)
+ break;
+ } else if (payloadLength64 == 126) {
+ // 16 bit length field
+ framingLength += 2;
+ if (avail < framingLength)
+ break;
+
+ payloadLength64 = mFramePtr[2] << 8 | mFramePtr[3];
+ } else {
+ // 64 bit length
+ framingLength += 8;
+ if (avail < framingLength)
+ break;
+
+ if (mFramePtr[2] & 0x80) {
+ // Section 4.2 says that the most significant bit MUST be
+ // 0. (i.e. this is really a 63 bit value)
+ LOG(("WebSocketChannel:: high bit of 64 bit length set"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // copy this in case it is unaligned
+ payloadLength64 = NetworkEndian::readInt64(mFramePtr + 2);
+ }
+
+ payload = mFramePtr + framingLength;
+ avail -= framingLength;
+
+ LOG(("WebSocketChannel::ProcessInput: payload %lld avail %lu\n",
+ payloadLength64, avail));
+
+ CheckedInt<int64_t> payloadLengthChecked(payloadLength64);
+ payloadLengthChecked += mFragmentAccumulator;
+ if (!payloadLengthChecked.isValid() || payloadLengthChecked.value() >
+ mMaxMessageSize) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ uint32_t payloadLength = static_cast<uint32_t>(payloadLength64);
+
+ if (avail < payloadLength)
+ break;
+
+ LOG(("WebSocketChannel::ProcessInput: Frame accumulated - opcode %d\n",
+ opcode));
+
+ if (!maskBit && mIsServerSide) {
+ LOG(("WebSocketChannel::ProcessInput: unmasked frame received "
+ "from client\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (maskBit) {
+ if (!mIsServerSide) {
+ // The server should not be allowed to send masked frames to clients.
+ // But we've been allowing it for some time, so this should be
+ // deprecated with care.
+ LOG(("WebSocketChannel:: Client RECEIVING masked frame."));
+ }
+
+ mask = NetworkEndian::readUint32(payload - 4);
+ }
+
+ if (mask) {
+ ApplyMask(mask, payload, payloadLength);
+ } else if (mIsServerSide) {
+ LOG(("WebSocketChannel::ProcessInput: masked frame with mask 0 received"
+ "from client\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+
+ // Control codes are required to have the fin bit set
+ if (!finBit && (opcode & kControlFrameMask)) {
+ LOG(("WebSocketChannel:: fragmented control frame code %d\n", opcode));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (rsvBits) {
+ // PMCE sets RSV1 bit in the first fragment when the non-control frame
+ // is deflated
+ if (mPMCECompressor && rsvBits == kRsv1Bit && mFragmentAccumulator == 0 &&
+ !(opcode & kControlFrameMask)) {
+ mPMCECompressor->SetMessageDeflated();
+ LOG(("WebSocketChannel::ProcessInput: received deflated frame\n"));
+ } else {
+ LOG(("WebSocketChannel::ProcessInput: unexpected reserved bits %x\n",
+ rsvBits));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+
+ if (!finBit || opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
+ // This is part of a fragment response
+
+ // Only the first frame has a non zero op code: Make sure we don't see a
+ // first frame while some old fragments are open
+ if ((mFragmentAccumulator != 0) &&
+ (opcode != nsIWebSocketFrame::OPCODE_CONTINUATION)) {
+ LOG(("WebSocketChannel:: nested fragments\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ LOG(("WebSocketChannel:: Accumulating Fragment %ld\n", payloadLength));
+
+ if (opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
+
+ // Make sure this continuation fragment isn't the first fragment
+ if (mFragmentOpcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
+ LOG(("WebSocketHeandler:: continuation code in first fragment\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // For frag > 1 move the data body back on top of the headers
+ // so we have contiguous stream of data
+ MOZ_ASSERT(mFramePtr + framingLength == payload,
+ "payload offset from frameptr wrong");
+ ::memmove(mFramePtr, payload, avail);
+ payload = mFramePtr;
+ if (mBuffered)
+ mBuffered -= framingLength;
+ } else {
+ mFragmentOpcode = opcode;
+ }
+
+ if (finBit) {
+ LOG(("WebSocketChannel:: Finalizing Fragment\n"));
+ payload -= mFragmentAccumulator;
+ payloadLength += mFragmentAccumulator;
+ avail += mFragmentAccumulator;
+ mFragmentAccumulator = 0;
+ opcode = mFragmentOpcode;
+ // reset to detect if next message illegally starts with continuation
+ mFragmentOpcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
+ } else {
+ opcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
+ mFragmentAccumulator += payloadLength;
+ }
+ } else if (mFragmentAccumulator != 0 && !(opcode & kControlFrameMask)) {
+ // This frame is not part of a fragment sequence but we
+ // have an open fragment.. it must be a control code or else
+ // we have a problem
+ LOG(("WebSocketChannel:: illegal fragment sequence\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (mServerClosed) {
+ LOG(("WebSocketChannel:: ignoring read frame code %d after close\n",
+ opcode));
+ // nop
+ } else if (mStopped) {
+ LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n",
+ opcode));
+ } else if (opcode == nsIWebSocketFrame::OPCODE_TEXT) {
+ bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated();
+ LOG(("WebSocketChannel:: %stext frame received\n",
+ isDeflated ? "deflated " : ""));
+
+ if (mListenerMT) {
+ nsCString utf8Data;
+
+ if (isDeflated) {
+ rv = mPMCECompressor->Inflate(payload, payloadLength, utf8Data);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ LOG(("WebSocketChannel:: message successfully inflated "
+ "[origLength=%d, newLength=%d]\n", payloadLength,
+ utf8Data.Length()));
+ } else {
+ if (!utf8Data.Assign((const char *)payload, payloadLength,
+ mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ // Section 8.1 says to fail connection if invalid utf-8 in text message
+ if (!IsUTF8(utf8Data, false)) {
+ LOG(("WebSocketChannel:: text frame invalid utf-8\n"));
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+
+ RefPtr<WebSocketFrame> frame =
+ mService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
+ opcode, maskBit, mask, utf8Data);
+
+ if (frame) {
+ mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
+ }
+
+ mTargetThread->Dispatch(new CallOnMessageAvailable(this, utf8Data, -1),
+ NS_DISPATCH_NORMAL);
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
+ LOG(("Added new msg received for %s", mHost.get()));
+ }
+ }
+ } else if (opcode & kControlFrameMask) {
+ // control frames
+ if (payloadLength > 125) {
+ LOG(("WebSocketChannel:: bad control frame code %d length %d\n",
+ opcode, payloadLength));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ RefPtr<WebSocketFrame> frame =
+ mService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
+ opcode, maskBit, mask, payload,
+ payloadLength);
+
+ if (opcode == nsIWebSocketFrame::OPCODE_CLOSE) {
+ LOG(("WebSocketChannel:: close received\n"));
+ mServerClosed = 1;
+
+ mServerCloseCode = CLOSE_NO_STATUS;
+ if (payloadLength >= 2) {
+ mServerCloseCode = NetworkEndian::readUint16(payload);
+ LOG(("WebSocketChannel:: close recvd code %u\n", mServerCloseCode));
+ uint16_t msglen = static_cast<uint16_t>(payloadLength - 2);
+ if (msglen > 0) {
+ mServerCloseReason.SetLength(msglen);
+ memcpy(mServerCloseReason.BeginWriting(),
+ (const char *)payload + 2, msglen);
+
+ // section 8.1 says to replace received non utf-8 sequences
+ // (which are non-conformant to send) with u+fffd,
+ // but secteam feels that silently rewriting messages is
+ // inappropriate - so we will fail the connection instead.
+ if (!IsUTF8(mServerCloseReason, false)) {
+ LOG(("WebSocketChannel:: close frame invalid utf-8\n"));
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+
+ LOG(("WebSocketChannel:: close msg %s\n",
+ mServerCloseReason.get()));
+ }
+ }
+
+ if (mCloseTimer) {
+ mCloseTimer->Cancel();
+ mCloseTimer = nullptr;
+ }
+
+ if (frame) {
+ // We send the frame immediately becuase we want to have it dispatched
+ // before the CallOnServerClose.
+ mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
+ frame = nullptr;
+ }
+
+ if (mListenerMT) {
+ mTargetThread->Dispatch(new CallOnServerClose(this, mServerCloseCode,
+ mServerCloseReason),
+ NS_DISPATCH_NORMAL);
+ }
+
+ if (mClientClosed)
+ ReleaseSession();
+ } else if (opcode == nsIWebSocketFrame::OPCODE_PING) {
+ LOG(("WebSocketChannel:: ping received\n"));
+ GeneratePong(payload, payloadLength);
+ } else if (opcode == nsIWebSocketFrame::OPCODE_PONG) {
+ // opcode OPCODE_PONG: the mere act of receiving the packet is all we
+ // need to do for the pong to trigger the activity timers
+ LOG(("WebSocketChannel:: pong received\n"));
+ } else {
+ /* unknown control frame opcode */
+ LOG(("WebSocketChannel:: unknown control op code %d\n", opcode));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (mFragmentAccumulator) {
+ // Remove the control frame from the stream so we have a contiguous
+ // data buffer of reassembled fragments
+ LOG(("WebSocketChannel:: Removing Control From Read buffer\n"));
+ MOZ_ASSERT(mFramePtr + framingLength == payload,
+ "payload offset from frameptr wrong");
+ ::memmove(mFramePtr, payload + payloadLength, avail - payloadLength);
+ payload = mFramePtr;
+ avail -= payloadLength;
+ if (mBuffered)
+ mBuffered -= framingLength + payloadLength;
+ payloadLength = 0;
+ }
+
+ if (frame) {
+ mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
+ }
+ } else if (opcode == nsIWebSocketFrame::OPCODE_BINARY) {
+ bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated();
+ LOG(("WebSocketChannel:: %sbinary frame received\n",
+ isDeflated ? "deflated " : ""));
+
+ if (mListenerMT) {
+ nsCString binaryData;
+
+ if (isDeflated) {
+ rv = mPMCECompressor->Inflate(payload, payloadLength, binaryData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ LOG(("WebSocketChannel:: message successfully inflated "
+ "[origLength=%d, newLength=%d]\n", payloadLength,
+ binaryData.Length()));
+ } else {
+ if (!binaryData.Assign((const char *)payload, payloadLength,
+ mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ RefPtr<WebSocketFrame> frame =
+ mService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
+ opcode, maskBit, mask, binaryData);
+ if (frame) {
+ mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
+ }
+
+ mTargetThread->Dispatch(
+ new CallOnMessageAvailable(this, binaryData, binaryData.Length()),
+ NS_DISPATCH_NORMAL);
+ // To add the header to 'Networking Dashboard' log
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
+ LOG(("Added new received msg for %s", mHost.get()));
+ }
+ }
+ } else if (opcode != nsIWebSocketFrame::OPCODE_CONTINUATION) {
+ /* unknown opcode */
+ LOG(("WebSocketChannel:: unknown op code %d\n", opcode));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mFramePtr = payload + payloadLength;
+ avail -= payloadLength;
+ totalAvail = avail;
+ }
+
+ // Adjust the stateful buffer. If we were operating off the stack and
+ // now have a partial message then transition to the buffer, or if
+ // we were working off the buffer but no longer have any active state
+ // then transition to the stack
+ if (!IsPersistentFramePtr()) {
+ mBuffered = 0;
+
+ if (mFragmentAccumulator) {
+ LOG(("WebSocketChannel:: Setup Buffer due to fragment"));
+
+ if (!UpdateReadBuffer(mFramePtr - mFragmentAccumulator,
+ totalAvail + mFragmentAccumulator, 0, nullptr)) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ // UpdateReadBuffer will reset the frameptr to the beginning
+ // of new saved state, so we need to skip past processed framgents
+ mFramePtr += mFragmentAccumulator;
+ } else if (totalAvail) {
+ LOG(("WebSocketChannel:: Setup Buffer due to partial frame"));
+ if (!UpdateReadBuffer(mFramePtr, totalAvail, 0, nullptr)) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+ }
+ } else if (!mFragmentAccumulator && !totalAvail) {
+ // If we were working off a saved buffer state and there is no partial
+ // frame or fragment in process, then revert to stack behavior
+ LOG(("WebSocketChannel:: Internal buffering not needed anymore"));
+ mBuffered = 0;
+
+ // release memory if we've been processing a large message
+ if (mBufferSize > kIncomingBufferStableSize) {
+ mBufferSize = kIncomingBufferStableSize;
+ free(mBuffer);
+ mBuffer = (uint8_t *)moz_xmalloc(mBufferSize);
+ }
+ }
+ return NS_OK;
+}
+
+/* static */ void
+WebSocketChannel::ApplyMask(uint32_t mask, uint8_t *data, uint64_t len)
+{
+ if (!data || len == 0)
+ return;
+
+ // Optimally we want to apply the mask 32 bits at a time,
+ // but the buffer might not be alligned. So we first deal with
+ // 0 to 3 bytes of preamble individually
+
+ while (len && (reinterpret_cast<uintptr_t>(data) & 3)) {
+ *data ^= mask >> 24;
+ mask = RotateLeft(mask, 8);
+ data++;
+ len--;
+ }
+
+ // perform mask on full words of data
+
+ uint32_t *iData = (uint32_t *) data;
+ uint32_t *end = iData + (len / 4);
+ NetworkEndian::writeUint32(&mask, mask);
+ for (; iData < end; iData++)
+ *iData ^= mask;
+ mask = NetworkEndian::readUint32(&mask);
+ data = (uint8_t *)iData;
+ len = len % 4;
+
+ // There maybe up to 3 trailing bytes that need to be dealt with
+ // individually
+
+ while (len) {
+ *data ^= mask >> 24;
+ mask = RotateLeft(mask, 8);
+ data++;
+ len--;
+ }
+}
+
+void
+WebSocketChannel::GeneratePing()
+{
+ nsCString *buf = new nsCString();
+ buf->AssignLiteral("PING");
+ EnqueueOutgoingMessage(mOutgoingPingMessages,
+ new OutboundMessage(kMsgTypePing, buf));
+}
+
+void
+WebSocketChannel::GeneratePong(uint8_t *payload, uint32_t len)
+{
+ nsCString *buf = new nsCString();
+ buf->SetLength(len);
+ if (buf->Length() < len) {
+ LOG(("WebSocketChannel::GeneratePong Allocation Failure\n"));
+ delete buf;
+ return;
+ }
+
+ memcpy(buf->BeginWriting(), payload, len);
+ EnqueueOutgoingMessage(mOutgoingPongMessages,
+ new OutboundMessage(kMsgTypePong, buf));
+}
+
+void
+WebSocketChannel::EnqueueOutgoingMessage(nsDeque &aQueue,
+ OutboundMessage *aMsg)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
+
+ LOG(("WebSocketChannel::EnqueueOutgoingMessage %p "
+ "queueing msg %p [type=%s len=%d]\n",
+ this, aMsg, msgNames[aMsg->GetMsgType()], aMsg->Length()));
+
+ aQueue.Push(aMsg);
+ OnOutputStreamReady(mSocketOut);
+}
+
+
+uint16_t
+WebSocketChannel::ResultToCloseCode(nsresult resultCode)
+{
+ if (NS_SUCCEEDED(resultCode))
+ return CLOSE_NORMAL;
+
+ switch (resultCode) {
+ case NS_ERROR_FILE_TOO_BIG:
+ case NS_ERROR_OUT_OF_MEMORY:
+ return CLOSE_TOO_LARGE;
+ case NS_ERROR_CANNOT_CONVERT_DATA:
+ return CLOSE_INVALID_PAYLOAD;
+ case NS_ERROR_UNEXPECTED:
+ return CLOSE_INTERNAL_ERROR;
+ default:
+ return CLOSE_PROTOCOL_ERROR;
+ }
+}
+
+void
+WebSocketChannel::PrimeNewOutgoingMessage()
+{
+ LOG(("WebSocketChannel::PrimeNewOutgoingMessage() %p\n", this));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
+ MOZ_ASSERT(!mCurrentOut, "Current message in progress");
+
+ nsresult rv = NS_OK;
+
+ mCurrentOut = (OutboundMessage *)mOutgoingPongMessages.PopFront();
+ if (mCurrentOut) {
+ MOZ_ASSERT(mCurrentOut->GetMsgType() == kMsgTypePong,
+ "Not pong message!");
+ } else {
+ mCurrentOut = (OutboundMessage *)mOutgoingPingMessages.PopFront();
+ if (mCurrentOut)
+ MOZ_ASSERT(mCurrentOut->GetMsgType() == kMsgTypePing,
+ "Not ping message!");
+ else
+ mCurrentOut = (OutboundMessage *)mOutgoingMessages.PopFront();
+ }
+
+ if (!mCurrentOut)
+ return;
+
+ WsMsgType msgType = mCurrentOut->GetMsgType();
+
+ LOG(("WebSocketChannel::PrimeNewOutgoingMessage "
+ "%p found queued msg %p [type=%s len=%d]\n",
+ this, mCurrentOut, msgNames[msgType], mCurrentOut->Length()));
+
+ mCurrentOutSent = 0;
+ mHdrOut = mOutHeader;
+
+ uint8_t maskBit = mIsServerSide ? 0 : kMaskBit;
+ uint8_t maskSize = mIsServerSide ? 0 : 4;
+
+ uint8_t *payload = nullptr;
+
+ if (msgType == kMsgTypeFin) {
+ // This is a demand to create a close message
+ if (mClientClosed) {
+ DeleteCurrentOutGoingMessage();
+ PrimeNewOutgoingMessage();
+ return;
+ }
+
+ mClientClosed = 1;
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_CLOSE;
+ mOutHeader[1] = maskBit;
+
+ // payload is offset 2 plus size of the mask
+ payload = mOutHeader + 2 + maskSize;
+
+ // The close reason code sits in the first 2 bytes of payload
+ // If the channel user provided a code and reason during Close()
+ // and there isn't an internal error, use that.
+ if (NS_SUCCEEDED(mStopOnClose)) {
+ if (mScriptCloseCode) {
+ NetworkEndian::writeUint16(payload, mScriptCloseCode);
+ mOutHeader[1] += 2;
+ mHdrOutToSend = 4 + maskSize;
+ if (!mScriptCloseReason.IsEmpty()) {
+ MOZ_ASSERT(mScriptCloseReason.Length() <= 123,
+ "Close Reason Too Long");
+ mOutHeader[1] += mScriptCloseReason.Length();
+ mHdrOutToSend += mScriptCloseReason.Length();
+ memcpy (payload + 2,
+ mScriptCloseReason.BeginReading(),
+ mScriptCloseReason.Length());
+ }
+ } else {
+ // No close code/reason, so payload length = 0. We must still send mask
+ // even though it's not used. Keep payload offset so we write mask
+ // below.
+ mHdrOutToSend = 2 + maskSize;
+ }
+ } else {
+ NetworkEndian::writeUint16(payload, ResultToCloseCode(mStopOnClose));
+ mOutHeader[1] += 2;
+ mHdrOutToSend = 4 + maskSize;
+ }
+
+ if (mServerClosed) {
+ /* bidi close complete */
+ mReleaseOnTransmit = 1;
+ } else if (NS_FAILED(mStopOnClose)) {
+ /* result of abort session - give up */
+ StopSession(mStopOnClose);
+ } else {
+ /* wait for reciprocal close from server */
+ mCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mCloseTimer->InitWithCallback(this, mCloseTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ } else {
+ StopSession(rv);
+ }
+ }
+ } else {
+ switch (msgType) {
+ case kMsgTypePong:
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PONG;
+ break;
+ case kMsgTypePing:
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PING;
+ break;
+ case kMsgTypeString:
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_TEXT;
+ break;
+ case kMsgTypeStream:
+ // HACK ALERT: read in entire stream into string.
+ // Will block socket transport thread if file is blocking.
+ // TODO: bug 704447: don't block socket thread!
+ rv = mCurrentOut->ConvertStreamToString();
+ if (NS_FAILED(rv)) {
+ AbortSession(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+ // Now we're a binary string
+ msgType = kMsgTypeBinaryString;
+
+ // no break: fall down into binary string case
+ MOZ_FALLTHROUGH;
+
+ case kMsgTypeBinaryString:
+ mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_BINARY;
+ break;
+ case kMsgTypeFin:
+ MOZ_ASSERT(false, "unreachable"); // avoid compiler warning
+ break;
+ }
+
+ // deflate the payload if PMCE is negotiated
+ if (mPMCECompressor &&
+ (msgType == kMsgTypeString || msgType == kMsgTypeBinaryString)) {
+ if (mCurrentOut->DeflatePayload(mPMCECompressor)) {
+ // The payload was deflated successfully, set RSV1 bit
+ mOutHeader[0] |= kRsv1Bit;
+
+ LOG(("WebSocketChannel::PrimeNewOutgoingMessage %p current msg %p was "
+ "deflated [origLength=%d, newLength=%d].\n", this, mCurrentOut,
+ mCurrentOut->OrigLength(), mCurrentOut->Length()));
+ }
+ }
+
+ if (mCurrentOut->Length() < 126) {
+ mOutHeader[1] = mCurrentOut->Length() | maskBit;
+ mHdrOutToSend = 2 + maskSize;
+ } else if (mCurrentOut->Length() <= 0xffff) {
+ mOutHeader[1] = 126 | maskBit;
+ NetworkEndian::writeUint16(mOutHeader + sizeof(uint16_t),
+ mCurrentOut->Length());
+ mHdrOutToSend = 4 + maskSize;
+ } else {
+ mOutHeader[1] = 127 | maskBit;
+ NetworkEndian::writeUint64(mOutHeader + 2, mCurrentOut->Length());
+ mHdrOutToSend = 10 + maskSize;
+ }
+ payload = mOutHeader + mHdrOutToSend;
+ }
+
+ MOZ_ASSERT(payload, "payload offset not found");
+
+ uint32_t mask = 0;
+ if (!mIsServerSide) {
+ // Perform the sending mask. Never use a zero mask
+ do {
+ uint8_t *buffer;
+ static_assert(4 == sizeof(mask), "Size of the mask should be equal to 4");
+ nsresult rv = mRandomGenerator->GenerateRandomBytes(sizeof(mask),
+ &buffer);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::PrimeNewOutgoingMessage(): "
+ "GenerateRandomBytes failure %x\n", rv));
+ StopSession(rv);
+ return;
+ }
+ memcpy(&mask, buffer, sizeof(mask));
+ free(buffer);
+ } while (!mask);
+ NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask);
+ }
+
+ LOG(("WebSocketChannel::PrimeNewOutgoingMessage() using mask %08x\n", mask));
+
+ // We don't mask the framing, but occasionally we stick a little payload
+ // data in the buffer used for the framing. Close frames are the current
+ // example. This data needs to be masked, but it is never more than a
+ // handful of bytes and might rotate the mask, so we can just do it locally.
+ // For real data frames we ship the bulk of the payload off to ApplyMask()
+
+ RefPtr<WebSocketFrame> frame =
+ mService->CreateFrameIfNeeded(
+ mOutHeader[0] & WebSocketChannel::kFinalFragBit,
+ mOutHeader[0] & WebSocketChannel::kRsv1Bit,
+ mOutHeader[0] & WebSocketChannel::kRsv2Bit,
+ mOutHeader[0] & WebSocketChannel::kRsv3Bit,
+ mOutHeader[0] & WebSocketChannel::kOpcodeBitsMask,
+ mOutHeader[1] & WebSocketChannel::kMaskBit,
+ mask,
+ payload, mHdrOutToSend - (payload - mOutHeader),
+ mCurrentOut->BeginOrigReading(),
+ mCurrentOut->OrigLength());
+
+ if (frame) {
+ mService->FrameSent(mSerial, mInnerWindowID, frame.forget());
+ }
+
+ if (mask) {
+ while (payload < (mOutHeader + mHdrOutToSend)) {
+ *payload ^= mask >> 24;
+ mask = RotateLeft(mask, 8);
+ payload++;
+ }
+
+ // Mask the real message payloads
+ ApplyMask(mask, mCurrentOut->BeginWriting(), mCurrentOut->Length());
+ }
+
+ int32_t len = mCurrentOut->Length();
+
+ // for small frames, copy it all together for a contiguous write
+ if (len && len <= kCopyBreak) {
+ memcpy(mOutHeader + mHdrOutToSend, mCurrentOut->BeginWriting(), len);
+ mHdrOutToSend += len;
+ mCurrentOutSent = len;
+ }
+
+ // Transmitting begins - mHdrOutToSend bytes from mOutHeader and
+ // mCurrentOut->Length() bytes from mCurrentOut. The latter may be
+ // coaleseced into the former for small messages or as the result of the
+ // compression process.
+}
+
+void
+WebSocketChannel::DeleteCurrentOutGoingMessage()
+{
+ delete mCurrentOut;
+ mCurrentOut = nullptr;
+ mCurrentOutSent = 0;
+}
+
+void
+WebSocketChannel::EnsureHdrOut(uint32_t size)
+{
+ LOG(("WebSocketChannel::EnsureHdrOut() %p [%d]\n", this, size));
+
+ if (mDynamicOutputSize < size) {
+ mDynamicOutputSize = size;
+ mDynamicOutput =
+ (uint8_t *) moz_xrealloc(mDynamicOutput, mDynamicOutputSize);
+ }
+
+ mHdrOut = mDynamicOutput;
+}
+
+namespace {
+
+class RemoveObserverRunnable : public Runnable
+{
+ RefPtr<WebSocketChannel> mChannel;
+
+public:
+ explicit RemoveObserverRunnable(WebSocketChannel* aChannel)
+ : mChannel(aChannel)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ NS_WARNING("failed to get observer service");
+ return NS_OK;
+ }
+
+ observerService->RemoveObserver(mChannel, NS_NETWORK_LINK_TOPIC);
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+void
+WebSocketChannel::CleanupConnection()
+{
+ LOG(("WebSocketChannel::CleanupConnection() %p", this));
+
+ if (mLingeringCloseTimer) {
+ mLingeringCloseTimer->Cancel();
+ mLingeringCloseTimer = nullptr;
+ }
+
+ if (mSocketIn) {
+ mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ mSocketIn = nullptr;
+ }
+
+ if (mSocketOut) {
+ mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mSocketOut = nullptr;
+ }
+
+ if (mTransport) {
+ mTransport->SetSecurityCallbacks(nullptr);
+ mTransport->SetEventSink(nullptr, nullptr);
+ mTransport->Close(NS_BASE_STREAM_CLOSED);
+ mTransport = nullptr;
+ }
+
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->RemoveHost(mHost, mSerial);
+ }
+
+ // This method can run in any thread, but the observer has to be removed on
+ // the main-thread.
+ NS_DispatchToMainThread(new RemoveObserverRunnable(this));
+
+ DecrementSessionCount();
+}
+
+void
+WebSocketChannel::StopSession(nsresult reason)
+{
+ LOG(("WebSocketChannel::StopSession() %p [%x]\n", this, reason));
+
+ // normally this should be called on socket thread, but it is ok to call it
+ // from OnStartRequest before the socket thread machine has gotten underway
+
+ mStopped = 1;
+
+ if (!mOpenedHttpChannel) {
+ // The HTTP channel information will never be used in this case
+ NS_ReleaseOnMainThread(mChannel.forget());
+ NS_ReleaseOnMainThread(mHttpChannel.forget());
+ NS_ReleaseOnMainThread(mLoadGroup.forget());
+ NS_ReleaseOnMainThread(mCallbacks.forget());
+ }
+
+ if (mCloseTimer) {
+ mCloseTimer->Cancel();
+ mCloseTimer = nullptr;
+ }
+
+ if (mOpenTimer) {
+ mOpenTimer->Cancel();
+ mOpenTimer = nullptr;
+ }
+
+ if (mReconnectDelayTimer) {
+ mReconnectDelayTimer->Cancel();
+ mReconnectDelayTimer = nullptr;
+ }
+
+ if (mPingTimer) {
+ mPingTimer->Cancel();
+ mPingTimer = nullptr;
+ }
+
+ if (mSocketIn && !mTCPClosed) {
+ // Drain, within reason, this socket. if we leave any data
+ // unconsumed (including the tcp fin) a RST will be generated
+ // The right thing to do here is shutdown(SHUT_WR) and then wait
+ // a little while to see if any data comes in.. but there is no
+ // reason to delay things for that when the websocket handshake
+ // is supposed to guarantee a quiet connection except for that fin.
+
+ char buffer[512];
+ uint32_t count = 0;
+ uint32_t total = 0;
+ nsresult rv;
+ do {
+ total += count;
+ rv = mSocketIn->Read(buffer, 512, &count);
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK &&
+ (NS_FAILED(rv) || count == 0))
+ mTCPClosed = true;
+ } while (NS_SUCCEEDED(rv) && count > 0 && total < 32000);
+ }
+
+ int32_t sessionCount = kLingeringCloseThreshold;
+ nsWSAdmissionManager::GetSessionCount(sessionCount);
+
+ if (!mTCPClosed && mTransport && sessionCount < kLingeringCloseThreshold) {
+
+ // 7.1.1 says that the client SHOULD wait for the server to close the TCP
+ // connection. This is so we can reuse port numbers before 2 MSL expires,
+ // which is not really as much of a concern for us as the amount of state
+ // that might be accrued by keeping this channel object around waiting for
+ // the server. We handle the SHOULD by waiting a short time in the common
+ // case, but not waiting in the case of high concurrency.
+ //
+ // Normally this will be taken care of in AbortSession() after mTCPClosed
+ // is set when the server close arrives without waiting for the timeout to
+ // expire.
+
+ LOG(("WebSocketChannel::StopSession: Wait for Server TCP close"));
+
+ nsresult rv;
+ mLingeringCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_SUCCEEDED(rv))
+ mLingeringCloseTimer->InitWithCallback(this, kLingeringCloseTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ else
+ CleanupConnection();
+ } else {
+ CleanupConnection();
+ }
+
+ if (mCancelable) {
+ mCancelable->Cancel(NS_ERROR_UNEXPECTED);
+ mCancelable = nullptr;
+ }
+
+ mPMCECompressor = nullptr;
+
+ if (!mCalledOnStop) {
+ mCalledOnStop = 1;
+
+ nsWSAdmissionManager::OnStopSession(this, reason);
+
+ RefPtr<CallOnStop> runnable = new CallOnStop(this, reason);
+ mTargetThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ }
+}
+
+void
+WebSocketChannel::AbortSession(nsresult reason)
+{
+ LOG(("WebSocketChannel::AbortSession() %p [reason %x] stopped = %d\n",
+ this, reason, !!mStopped));
+
+ // normally this should be called on socket thread, but it is ok to call it
+ // from the main thread before StartWebsocketData() has completed
+
+ // When we are failing we need to close the TCP connection immediately
+ // as per 7.1.1
+ mTCPClosed = true;
+
+ if (mLingeringCloseTimer) {
+ MOZ_ASSERT(mStopped, "Lingering without Stop");
+ LOG(("WebSocketChannel:: Cleanup connection based on TCP Close"));
+ CleanupConnection();
+ return;
+ }
+
+ if (mStopped)
+ return;
+ mStopped = 1;
+
+ if (mTransport && reason != NS_BASE_STREAM_CLOSED && !mRequestedClose &&
+ !mClientClosed && !mServerClosed && mConnecting == NOT_CONNECTING) {
+ mRequestedClose = 1;
+ mStopOnClose = reason;
+ mSocketThread->Dispatch(
+ new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)),
+ nsIEventTarget::DISPATCH_NORMAL);
+ } else {
+ StopSession(reason);
+ }
+}
+
+// ReleaseSession is called on orderly shutdown
+void
+WebSocketChannel::ReleaseSession()
+{
+ LOG(("WebSocketChannel::ReleaseSession() %p stopped = %d\n",
+ this, !!mStopped));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
+
+ if (mStopped)
+ return;
+ StopSession(NS_OK);
+}
+
+void
+WebSocketChannel::IncrementSessionCount()
+{
+ if (!mIncrementedSessionCount) {
+ nsWSAdmissionManager::IncrementSessionCount();
+ mIncrementedSessionCount = 1;
+ }
+}
+
+void
+WebSocketChannel::DecrementSessionCount()
+{
+ // Make sure we decrement session count only once, and only if we incremented it.
+ // This code is thread-safe: sWebSocketAdmissions->DecrementSessionCount is
+ // atomic, and mIncrementedSessionCount/mDecrementedSessionCount are set at
+ // times when they'll never be a race condition for checking/setting them.
+ if (mIncrementedSessionCount && !mDecrementedSessionCount) {
+ nsWSAdmissionManager::DecrementSessionCount();
+ mDecrementedSessionCount = 1;
+ }
+}
+
+namespace {
+enum ExtensionParseMode { eParseServerSide, eParseClientSide };
+}
+
+static nsresult
+ParseWebSocketExtension(const nsACString& aExtension,
+ ExtensionParseMode aMode,
+ bool& aClientNoContextTakeover,
+ bool& aServerNoContextTakeover,
+ int32_t& aClientMaxWindowBits,
+ int32_t& aServerMaxWindowBits)
+{
+ nsCCharSeparatedTokenizer tokens(aExtension, ';');
+
+ if (!tokens.hasMoreTokens() ||
+ !tokens.nextToken().Equals(NS_LITERAL_CSTRING("permessage-deflate"))) {
+ LOG(("WebSocketChannel::ParseWebSocketExtension: "
+ "HTTP Sec-WebSocket-Extensions negotiated unknown value %s\n",
+ PromiseFlatCString(aExtension).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ aClientNoContextTakeover = aServerNoContextTakeover = false;
+ aClientMaxWindowBits = aServerMaxWindowBits = -1;
+
+ while (tokens.hasMoreTokens()) {
+ auto token = tokens.nextToken();
+
+ int32_t nameEnd, valueStart;
+ int32_t delimPos = token.FindChar('=');
+ if (delimPos == kNotFound) {
+ nameEnd = token.Length();
+ valueStart = token.Length();
+ } else {
+ nameEnd = delimPos;
+ valueStart = delimPos + 1;
+ }
+
+ auto paramName = Substring(token, 0, nameEnd);
+ auto paramValue = Substring(token, valueStart);
+
+ if (paramName.EqualsLiteral("client_no_context_takeover")) {
+ if (!paramValue.IsEmpty()) {
+ LOG(("WebSocketChannel::ParseWebSocketExtension: parameter "
+ "client_no_context_takeover must not have value, found %s\n",
+ PromiseFlatCString(paramValue).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (aClientNoContextTakeover) {
+ LOG(("WebSocketChannel::ParseWebSocketExtension: found multiple "
+ "parameters client_no_context_takeover\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ aClientNoContextTakeover = true;
+ } else if (paramName.EqualsLiteral("server_no_context_takeover")) {
+ if (!paramValue.IsEmpty()) {
+ LOG(("WebSocketChannel::ParseWebSocketExtension: parameter "
+ "server_no_context_takeover must not have value, found %s\n",
+ PromiseFlatCString(paramValue).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (aServerNoContextTakeover) {
+ LOG(("WebSocketChannel::ParseWebSocketExtension: found multiple "
+ "parameters server_no_context_takeover\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ aServerNoContextTakeover = true;
+ } else if (paramName.EqualsLiteral("client_max_window_bits")) {
+ if (aClientMaxWindowBits != -1) {
+ LOG(("WebSocketChannel::ParseWebSocketExtension: found multiple "
+ "parameters client_max_window_bits\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (aMode == eParseServerSide && paramValue.IsEmpty()) {
+ // Use -2 to indicate that "client_max_window_bits" has been parsed,
+ // but had no value.
+ aClientMaxWindowBits = -2;
+ }
+ else {
+ nsresult errcode;
+ aClientMaxWindowBits =
+ PromiseFlatCString(paramValue).ToInteger(&errcode);
+ if (NS_FAILED(errcode) || aClientMaxWindowBits < 8 ||
+ aClientMaxWindowBits > 15) {
+ LOG(("WebSocketChannel::ParseWebSocketExtension: found invalid "
+ "parameter client_max_window_bits %s\n",
+ PromiseFlatCString(paramValue).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+ } else if (paramName.EqualsLiteral("server_max_window_bits")) {
+ if (aServerMaxWindowBits != -1) {
+ LOG(("WebSocketChannel::ParseWebSocketExtension: found multiple "
+ "parameters server_max_window_bits\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult errcode;
+ aServerMaxWindowBits =
+ PromiseFlatCString(paramValue).ToInteger(&errcode);
+ if (NS_FAILED(errcode) || aServerMaxWindowBits < 8 ||
+ aServerMaxWindowBits > 15) {
+ LOG(("WebSocketChannel::ParseWebSocketExtension: found invalid "
+ "parameter server_max_window_bits %s\n",
+ PromiseFlatCString(paramValue).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ } else {
+ LOG(("WebSocketChannel::ParseWebSocketExtension: found unknown "
+ "parameter %s\n", PromiseFlatCString(paramName).get()));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+
+ if (aClientMaxWindowBits == -2) {
+ aClientMaxWindowBits = -1;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+WebSocketChannel::HandleExtensions()
+{
+ LOG(("WebSocketChannel::HandleExtensions() %p\n", this));
+
+ nsresult rv;
+ nsAutoCString extensions;
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ rv = mHttpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions);
+ extensions.CompressWhitespace();
+ if (extensions.IsEmpty()) {
+ return NS_OK;
+ }
+
+ LOG(("WebSocketChannel::HandleExtensions: received "
+ "Sec-WebSocket-Extensions header: %s\n", extensions.get()));
+
+ bool clientNoContextTakeover;
+ bool serverNoContextTakeover;
+ int32_t clientMaxWindowBits;
+ int32_t serverMaxWindowBits;
+
+ rv = ParseWebSocketExtension(extensions,
+ eParseClientSide,
+ clientNoContextTakeover,
+ serverNoContextTakeover,
+ clientMaxWindowBits,
+ serverMaxWindowBits);
+ if (NS_FAILED(rv)) {
+ AbortSession(rv);
+ return rv;
+ }
+
+ if (!mAllowPMCE) {
+ LOG(("WebSocketChannel::HandleExtensions: "
+ "Recvd permessage-deflate which wasn't offered\n"));
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (clientMaxWindowBits == -1) {
+ clientMaxWindowBits = 15;
+ }
+ if (serverMaxWindowBits == -1) {
+ serverMaxWindowBits = 15;
+ }
+
+ mPMCECompressor = new PMCECompression(clientNoContextTakeover,
+ clientMaxWindowBits,
+ serverMaxWindowBits);
+ if (mPMCECompressor->Active()) {
+ LOG(("WebSocketChannel::HandleExtensions: PMCE negotiated, %susing "
+ "context takeover, clientMaxWindowBits=%d, "
+ "serverMaxWindowBits=%d\n",
+ clientNoContextTakeover ? "NOT " : "", clientMaxWindowBits,
+ serverMaxWindowBits));
+
+ mNegotiatedExtensions = "permessage-deflate";
+ } else {
+ LOG(("WebSocketChannel::HandleExtensions: Cannot init PMCE "
+ "compression object\n"));
+ mPMCECompressor = nullptr;
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+void
+ProcessServerWebSocketExtensions(const nsACString& aExtensions,
+ nsACString& aNegotiatedExtensions)
+{
+ aNegotiatedExtensions.Truncate();
+
+ nsCOMPtr<nsIPrefBranch> prefService =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefService) {
+ bool boolpref;
+ nsresult rv = prefService->
+ GetBoolPref("network.websocket.extensions.permessage-deflate", &boolpref);
+ if (NS_SUCCEEDED(rv) && !boolpref) {
+ return;
+ }
+ }
+
+ nsCCharSeparatedTokenizer extList(aExtensions, ',');
+ while (extList.hasMoreTokens()) {
+ bool clientNoContextTakeover;
+ bool serverNoContextTakeover;
+ int32_t clientMaxWindowBits;
+ int32_t serverMaxWindowBits;
+
+ nsresult rv = ParseWebSocketExtension(extList.nextToken(),
+ eParseServerSide,
+ clientNoContextTakeover,
+ serverNoContextTakeover,
+ clientMaxWindowBits,
+ serverMaxWindowBits);
+ if (NS_FAILED(rv)) {
+ // Ignore extensions that we can't parse
+ continue;
+ }
+
+ aNegotiatedExtensions.AssignLiteral("permessage-deflate");
+ if (clientNoContextTakeover) {
+ aNegotiatedExtensions.AppendLiteral(";client_no_context_takeover");
+ }
+ if (serverNoContextTakeover) {
+ aNegotiatedExtensions.AppendLiteral(";server_no_context_takeover");
+ }
+ if (clientMaxWindowBits != -1) {
+ aNegotiatedExtensions.AppendLiteral(";client_max_window_bits=");
+ aNegotiatedExtensions.AppendInt(clientMaxWindowBits);
+ }
+ if (serverMaxWindowBits != -1) {
+ aNegotiatedExtensions.AppendLiteral(";server_max_window_bits=");
+ aNegotiatedExtensions.AppendInt(serverMaxWindowBits);
+ }
+
+ return;
+ }
+}
+
+nsresult
+CalculateWebSocketHashedSecret(const nsACString& aKey, nsACString& aHash)
+{
+ nsresult rv;
+ nsCString key =
+ aKey + NS_LITERAL_CSTRING("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
+ nsCOMPtr<nsICryptoHash> hasher =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = hasher->Init(nsICryptoHash::SHA1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = hasher->Update((const uint8_t *)key.BeginWriting(), key.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ return hasher->Finish(true, aHash);
+}
+
+nsresult
+WebSocketChannel::SetupRequest()
+{
+ LOG(("WebSocketChannel::SetupRequest() %p\n", this));
+
+ nsresult rv;
+
+ if (mLoadGroup) {
+ rv = mHttpChannel->SetLoadGroup(mLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mHttpChannel->SetLoadFlags(nsIRequest::LOAD_BACKGROUND |
+ nsIRequest::INHIBIT_CACHING |
+ nsIRequest::LOAD_BYPASS_CACHE |
+ nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we never let websockets be blocked by head CSS/JS loads to avoid
+ // potential deadlock where server generation of CSS/JS requires
+ // an XHR signal.
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::Unblocked);
+ }
+
+ // draft-ietf-hybi-thewebsocketprotocol-07 illustrates Upgrade: websocket
+ // in lower case, so go with that. It is technically case insensitive.
+ rv = mChannel->HTTPUpgrade(NS_LITERAL_CSTRING("websocket"), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHttpChannel->SetRequestHeader(
+ NS_LITERAL_CSTRING("Sec-WebSocket-Version"),
+ NS_LITERAL_CSTRING(SEC_WEBSOCKET_VERSION), false);
+
+ if (!mOrigin.IsEmpty())
+ mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), mOrigin,
+ false);
+
+ if (!mProtocol.IsEmpty())
+ mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
+ mProtocol, true);
+
+ if (mAllowPMCE)
+ mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"),
+ NS_LITERAL_CSTRING("permessage-deflate"),
+ false);
+
+ uint8_t *secKey;
+ nsAutoCString secKeyString;
+
+ rv = mRandomGenerator->GenerateRandomBytes(16, &secKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ char* b64 = PL_Base64Encode((const char *)secKey, 16, nullptr);
+ free(secKey);
+ if (!b64)
+ return NS_ERROR_OUT_OF_MEMORY;
+ secKeyString.Assign(b64);
+ PR_Free(b64);
+ mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Key"),
+ secKeyString, false);
+ LOG(("WebSocketChannel::SetupRequest: client key %s\n", secKeyString.get()));
+
+ // prepare the value we expect to see in
+ // the sec-websocket-accept response header
+ rv = CalculateWebSocketHashedSecret(secKeyString, mHashedSecret);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LOG(("WebSocketChannel::SetupRequest: expected server key %s\n",
+ mHashedSecret.get()));
+
+ return NS_OK;
+}
+
+nsresult
+WebSocketChannel::DoAdmissionDNS()
+{
+ nsresult rv;
+
+ nsCString hostName;
+ rv = mURI->GetHost(hostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mAddress = hostName;
+ rv = mURI->GetPort(&mPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mPort == -1)
+ mPort = (mEncrypted ? kDefaultWSSPort : kDefaultWSPort);
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIThread> mainThread;
+ NS_GetMainThread(getter_AddRefs(mainThread));
+ MOZ_ASSERT(!mCancelable);
+ return dns->AsyncResolve(hostName, 0, this, mainThread, getter_AddRefs(mCancelable));
+}
+
+nsresult
+WebSocketChannel::ApplyForAdmission()
+{
+ LOG(("WebSocketChannel::ApplyForAdmission() %p\n", this));
+
+ // Websockets has a policy of 1 session at a time being allowed in the
+ // CONNECTING state per server IP address (not hostname)
+
+ // Check to see if a proxy is being used before making DNS call
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
+
+ if (!pps) {
+ // go straight to DNS
+ // expect the callback in ::OnLookupComplete
+ LOG(("WebSocketChannel::ApplyForAdmission: checking for concurrent open\n"));
+ return DoAdmissionDNS();
+ }
+
+ MOZ_ASSERT(!mCancelable);
+
+ nsresult rv;
+ rv = pps->AsyncResolve(mHttpChannel,
+ nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
+ this, getter_AddRefs(mCancelable));
+ NS_ASSERTION(NS_FAILED(rv) || mCancelable,
+ "nsIProtocolProxyService::AsyncResolve succeeded but didn't "
+ "return a cancelable object!");
+ return rv;
+}
+
+// Called after both OnStartRequest and OnTransportAvailable have
+// executed. This essentially ends the handshake and starts the websockets
+// protocol state machine.
+nsresult
+WebSocketChannel::StartWebsocketData()
+{
+ nsresult rv;
+
+ if (!IsOnTargetThread()) {
+ return mTargetThread->Dispatch(
+ NewRunnableMethod(this, &WebSocketChannel::StartWebsocketData),
+ NS_DISPATCH_NORMAL);
+ }
+
+ LOG(("WebSocketChannel::StartWebsocketData() %p", this));
+ MOZ_ASSERT(!mDataStarted, "StartWebsocketData twice");
+ mDataStarted = 1;
+
+ rv = mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::StartWebsocketData mSocketIn->AsyncWait() failed "
+ "with error 0x%08x", rv));
+ return mSocketThread->Dispatch(
+ NewRunnableMethod<nsresult>(this,
+ &WebSocketChannel::AbortSession,
+ rv),
+ NS_DISPATCH_NORMAL);
+ }
+
+ if (mPingInterval) {
+ rv = mSocketThread->Dispatch(
+ NewRunnableMethod(this, &WebSocketChannel::StartPinging),
+ NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::StartWebsocketData Could not start pinging, "
+ "rv=0x%08x", rv));
+ return rv;
+ }
+ }
+
+ LOG(("WebSocketChannel::StartWebsocketData Notifying Listener %p",
+ mListenerMT ? mListenerMT->mListener.get() : nullptr));
+
+ if (mListenerMT) {
+ mListenerMT->mListener->OnStart(mListenerMT->mContext);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+WebSocketChannel::StartPinging()
+{
+ LOG(("WebSocketChannel::StartPinging() %p", this));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
+ MOZ_ASSERT(mPingInterval);
+ MOZ_ASSERT(!mPingTimer);
+
+ nsresult rv;
+ mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to create ping timer. Carrying on.");
+ } else {
+ LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n",
+ mPingInterval));
+ mPingTimer->InitWithCallback(this, mPingInterval, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ return NS_OK;
+}
+
+
+void
+WebSocketChannel::ReportConnectionTelemetry()
+{
+ // 3 bits are used. high bit is for wss, middle bit for failed,
+ // and low bit for proxy..
+ // 0 - 7 : ws-ok-plain, ws-ok-proxy, ws-failed-plain, ws-failed-proxy,
+ // wss-ok-plain, wss-ok-proxy, wss-failed-plain, wss-failed-proxy
+
+ bool didProxy = false;
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ nsCOMPtr<nsIProxiedChannel> pc = do_QueryInterface(mChannel);
+ if (pc)
+ pc->GetProxyInfo(getter_AddRefs(pi));
+ if (pi) {
+ nsAutoCString proxyType;
+ pi->GetType(proxyType);
+ if (!proxyType.IsEmpty() &&
+ !proxyType.EqualsLiteral("direct"))
+ didProxy = true;
+ }
+
+ uint8_t value = (mEncrypted ? (1 << 2) : 0) |
+ (!mGotUpgradeOK ? (1 << 1) : 0) |
+ (didProxy ? (1 << 0) : 0);
+
+ LOG(("WebSocketChannel::ReportConnectionTelemetry() %p %d", this, value));
+ Telemetry::Accumulate(Telemetry::WEBSOCKETS_HANDSHAKE_TYPE, value);
+}
+
+// nsIDNSListener
+
+NS_IMETHODIMP
+WebSocketChannel::OnLookupComplete(nsICancelable *aRequest,
+ nsIDNSRecord *aRecord,
+ nsresult aStatus)
+{
+ LOG(("WebSocketChannel::OnLookupComplete() %p [%p %p %x]\n",
+ this, aRequest, aRecord, aStatus));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnLookupComplete: Request Already Stopped\n"));
+ mCancelable = nullptr;
+ return NS_OK;
+ }
+
+ mCancelable = nullptr;
+
+ // These failures are not fatal - we just use the hostname as the key
+ if (NS_FAILED(aStatus)) {
+ LOG(("WebSocketChannel::OnLookupComplete: No DNS Response\n"));
+
+ // set host in case we got here without calling DoAdmissionDNS()
+ mURI->GetHost(mAddress);
+ } else {
+ nsresult rv = aRecord->GetNextAddrAsString(mAddress);
+ if (NS_FAILED(rv))
+ LOG(("WebSocketChannel::OnLookupComplete: Failed GetNextAddr\n"));
+ }
+
+ LOG(("WebSocket OnLookupComplete: Proceeding to ConditionallyConnect\n"));
+ nsWSAdmissionManager::ConditionallyConnect(this);
+
+ return NS_OK;
+}
+
+// nsIProtocolProxyCallback
+NS_IMETHODIMP
+WebSocketChannel::OnProxyAvailable(nsICancelable *aRequest, nsIChannel *aChannel,
+ nsIProxyInfo *pi, nsresult status)
+{
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnProxyAvailable: [%p] Request Already Stopped\n", this));
+ mCancelable = nullptr;
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mCancelable || (aRequest == mCancelable));
+ mCancelable = nullptr;
+
+ nsAutoCString type;
+ if (NS_SUCCEEDED(status) && pi &&
+ NS_SUCCEEDED(pi->GetType(type)) &&
+ !type.EqualsLiteral("direct")) {
+ LOG(("WebSocket OnProxyAvailable [%p] Proxy found skip DNS lookup\n", this));
+ // call DNS callback directly without DNS resolver
+ OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE);
+ } else {
+ LOG(("WebSocketChannel::OnProxyAvailable[%p] checking DNS resolution\n", this));
+ nsresult rv = DoAdmissionDNS();
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocket OnProxyAvailable [%p] DNS lookup failed\n", this));
+ // call DNS callback directly without DNS resolver
+ OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE);
+ }
+ }
+
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+
+NS_IMETHODIMP
+WebSocketChannel::GetInterface(const nsIID & iid, void **result)
+{
+ LOG(("WebSocketChannel::GetInterface() %p\n", this));
+
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink)))
+ return QueryInterface(iid, result);
+
+ if (mCallbacks)
+ return mCallbacks->GetInterface(iid, result);
+
+ return NS_ERROR_FAILURE;
+}
+
+// nsIChannelEventSink
+
+NS_IMETHODIMP
+WebSocketChannel::AsyncOnChannelRedirect(
+ nsIChannel *oldChannel,
+ nsIChannel *newChannel,
+ uint32_t flags,
+ nsIAsyncVerifyRedirectCallback *callback)
+{
+ LOG(("WebSocketChannel::AsyncOnChannelRedirect() %p\n", this));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> newuri;
+ rv = newChannel->GetURI(getter_AddRefs(newuri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // newuri is expected to be http or https
+ bool newuriIsHttps = false;
+ rv = newuri->SchemeIs("https", &newuriIsHttps);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mAutoFollowRedirects) {
+ // Even if redirects configured off, still allow them for HTTP Strict
+ // Transport Security (from ws://FOO to https://FOO (mapped to wss://FOO)
+
+ if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE))) {
+ nsAutoCString newSpec;
+ rv = newuri->GetSpec(newSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("WebSocketChannel: Redirect to %s denied by configuration\n",
+ newSpec.get()));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (mEncrypted && !newuriIsHttps) {
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(newuri->GetSpec(spec)))
+ LOG(("WebSocketChannel: Redirect to %s violates encryption rule\n",
+ spec.get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel, &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Redirect could not QI to HTTP\n"));
+ return rv;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> newUpgradeChannel =
+ do_QueryInterface(newChannel, &rv);
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Redirect could not QI to HTTP Upgrade\n"));
+ return rv;
+ }
+
+ // The redirect is likely OK
+
+ newChannel->SetNotificationCallbacks(this);
+
+ mEncrypted = newuriIsHttps;
+ newuri->Clone(getter_AddRefs(mURI));
+ if (mEncrypted)
+ rv = mURI->SetScheme(NS_LITERAL_CSTRING("wss"));
+ else
+ rv = mURI->SetScheme(NS_LITERAL_CSTRING("ws"));
+
+ mHttpChannel = newHttpChannel;
+ mChannel = newUpgradeChannel;
+ rv = SetupRequest();
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Redirect could not SetupRequest()\n"));
+ return rv;
+ }
+
+ // Redirected-to URI may need to be delayed by 1-connecting-per-host and
+ // delay-after-fail algorithms. So hold off calling OnRedirectVerifyCallback
+ // until BeginOpen, when we know it's OK to proceed with new channel.
+ mRedirectCallback = callback;
+
+ // Mark old channel as successfully connected so we'll clear any FailDelay
+ // associated with the old URI. Note: no need to also call OnStopSession:
+ // it's a no-op for successful, already-connected channels.
+ nsWSAdmissionManager::OnConnected(this);
+
+ // ApplyForAdmission as if we were starting from fresh...
+ mAddress.Truncate();
+ mOpenedHttpChannel = 0;
+ rv = ApplyForAdmission();
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel: Redirect failed due to DNS failure\n"));
+ mRedirectCallback = nullptr;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// nsITimerCallback
+
+NS_IMETHODIMP
+WebSocketChannel::Notify(nsITimer *timer)
+{
+ LOG(("WebSocketChannel::Notify() %p [%p]\n", this, timer));
+
+ if (timer == mCloseTimer) {
+ MOZ_ASSERT(mClientClosed, "Close Timeout without local close");
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread,
+ "not socket thread");
+
+ mCloseTimer = nullptr;
+ if (mStopped || mServerClosed) /* no longer relevant */
+ return NS_OK;
+
+ LOG(("WebSocketChannel:: Expecting Server Close - Timed Out\n"));
+ AbortSession(NS_ERROR_NET_TIMEOUT);
+ } else if (timer == mOpenTimer) {
+ MOZ_ASSERT(!mGotUpgradeOK,
+ "Open Timer after open complete");
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ mOpenTimer = nullptr;
+ LOG(("WebSocketChannel:: Connection Timed Out\n"));
+ if (mStopped || mServerClosed) /* no longer relevant */
+ return NS_OK;
+
+ AbortSession(NS_ERROR_NET_TIMEOUT);
+ } else if (timer == mReconnectDelayTimer) {
+ MOZ_ASSERT(mConnecting == CONNECTING_DELAYED,
+ "woke up from delay w/o being delayed?");
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ mReconnectDelayTimer = nullptr;
+ LOG(("WebSocketChannel: connecting [this=%p] after reconnect delay", this));
+ BeginOpen(false);
+ } else if (timer == mPingTimer) {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread,
+ "not socket thread");
+
+ if (mClientClosed || mServerClosed || mRequestedClose) {
+ // no point in worrying about ping now
+ mPingTimer = nullptr;
+ return NS_OK;
+ }
+
+ if (!mPingOutstanding) {
+ // Ping interval must be non-null or PING was forced by OnNetworkChanged()
+ MOZ_ASSERT(mPingInterval || mPingForced);
+ LOG(("nsWebSocketChannel:: Generating Ping\n"));
+ mPingOutstanding = 1;
+ mPingForced = 0;
+ mPingTimer->InitWithCallback(this, mPingResponseTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ GeneratePing();
+ } else {
+ LOG(("nsWebSocketChannel:: Timed out Ping\n"));
+ mPingTimer = nullptr;
+ AbortSession(NS_ERROR_NET_TIMEOUT);
+ }
+ } else if (timer == mLingeringCloseTimer) {
+ LOG(("WebSocketChannel:: Lingering Close Timer"));
+ CleanupConnection();
+ } else {
+ MOZ_ASSERT(0, "Unknown Timer");
+ }
+
+ return NS_OK;
+}
+
+// nsIWebSocketChannel
+
+NS_IMETHODIMP
+WebSocketChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
+{
+ LOG(("WebSocketChannel::GetSecurityInfo() %p\n", this));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ if (mTransport) {
+ if (NS_FAILED(mTransport->GetSecurityInfo(aSecurityInfo)))
+ *aSecurityInfo = nullptr;
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+WebSocketChannel::AsyncOpen(nsIURI *aURI,
+ const nsACString &aOrigin,
+ uint64_t aInnerWindowID,
+ nsIWebSocketListener *aListener,
+ nsISupports *aContext)
+{
+ LOG(("WebSocketChannel::AsyncOpen() %p\n", this));
+
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(false, "not main thread");
+ LOG(("WebSocketChannel::AsyncOpen() called off the main thread"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if ((!aURI && !mIsServerSide) || !aListener) {
+ LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mListenerMT || mWasOpened)
+ return NS_ERROR_ALREADY_OPENED;
+
+ nsresult rv;
+
+ // Ensure target thread is set.
+ if (!mTargetThread) {
+ mTargetThread = do_GetMainThread();
+ }
+
+ mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to continue without socket transport service");
+ return rv;
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefService;
+ prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ if (prefService) {
+ int32_t intpref;
+ bool boolpref;
+ rv = prefService->GetIntPref("network.websocket.max-message-size",
+ &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxMessageSize = clamped(intpref, 1024, INT32_MAX);
+ }
+ rv = prefService->GetIntPref("network.websocket.timeout.close", &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ mCloseTimeout = clamped(intpref, 1, 1800) * 1000;
+ }
+ rv = prefService->GetIntPref("network.websocket.timeout.open", &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ mOpenTimeout = clamped(intpref, 1, 1800) * 1000;
+ }
+ rv = prefService->GetIntPref("network.websocket.timeout.ping.request",
+ &intpref);
+ if (NS_SUCCEEDED(rv) && !mClientSetPingInterval) {
+ mPingInterval = clamped(intpref, 0, 86400) * 1000;
+ }
+ rv = prefService->GetIntPref("network.websocket.timeout.ping.response",
+ &intpref);
+ if (NS_SUCCEEDED(rv) && !mClientSetPingTimeout) {
+ mPingResponseTimeout = clamped(intpref, 1, 3600) * 1000;
+ }
+ rv = prefService->GetBoolPref("network.websocket.extensions.permessage-deflate",
+ &boolpref);
+ if (NS_SUCCEEDED(rv)) {
+ mAllowPMCE = boolpref ? 1 : 0;
+ }
+ rv = prefService->GetBoolPref("network.websocket.auto-follow-http-redirects",
+ &boolpref);
+ if (NS_SUCCEEDED(rv)) {
+ mAutoFollowRedirects = boolpref ? 1 : 0;
+ }
+ rv = prefService->GetIntPref
+ ("network.websocket.max-connections", &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ mMaxConcurrentConnections = clamped(intpref, 1, 0xffff);
+ }
+ }
+
+ int32_t sessionCount = -1;
+ nsWSAdmissionManager::GetSessionCount(sessionCount);
+ if (sessionCount >= 0) {
+ LOG(("WebSocketChannel::AsyncOpen %p sessionCount=%d max=%d\n", this,
+ sessionCount, mMaxConcurrentConnections));
+ }
+
+ if (sessionCount >= mMaxConcurrentConnections) {
+ LOG(("WebSocketChannel: max concurrency %d exceeded (%d)",
+ mMaxConcurrentConnections,
+ sessionCount));
+
+ // WebSocket connections are expected to be long lived, so return
+ // an error here instead of queueing
+ return NS_ERROR_SOCKET_CREATE_FAILED;
+ }
+
+ mInnerWindowID = aInnerWindowID;
+ mOriginalURI = aURI;
+ mURI = mOriginalURI;
+ mOrigin = aOrigin;
+
+ if (mIsServerSide) {
+ //IncrementSessionCount();
+ mWasOpened = 1;
+ mListenerMT = new ListenerAndContextContainer(aListener, aContext);
+ mServerTransportProvider->SetListener(this);
+ mServerTransportProvider = nullptr;
+
+ return NS_OK;
+ }
+
+ mURI->GetHostPort(mHost);
+
+ mRandomGenerator =
+ do_GetService("@mozilla.org/security/random-generator;1", &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to continue without random number generator");
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> localURI;
+ nsCOMPtr<nsIChannel> localChannel;
+
+ mURI->Clone(getter_AddRefs(localURI));
+ if (mEncrypted)
+ rv = localURI->SetScheme(NS_LITERAL_CSTRING("https"));
+ else
+ rv = localURI->SetScheme(NS_LITERAL_CSTRING("http"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIIOService> ioService;
+ ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to continue without io service");
+ return rv;
+ }
+
+ nsCOMPtr<nsIIOService2> io2 = do_QueryInterface(ioService, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("WebSocketChannel: unable to continue without ioservice2");
+ return rv;
+ }
+
+ // Ideally we'd call newChannelFromURIWithLoadInfo here, but that doesn't
+ // allow setting proxy uri/flags
+ rv = io2->NewChannelFromURIWithProxyFlags2(
+ localURI,
+ mURI,
+ nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
+ mLoadInfo->LoadingNode() ?
+ mLoadInfo->LoadingNode()->AsDOMNode() : nullptr,
+ mLoadInfo->LoadingPrincipal(),
+ mLoadInfo->TriggeringPrincipal(),
+ mLoadInfo->GetSecurityFlags(),
+ mLoadInfo->InternalContentPolicyType(),
+ getter_AddRefs(localChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Please note that we still call SetLoadInfo on the channel because
+ // we want the same instance of the loadInfo to be set on the channel.
+ rv = localChannel->SetLoadInfo(mLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Pass most GetInterface() requests through to our instantiator, but handle
+ // nsIChannelEventSink in this object in order to deal with redirects
+ localChannel->SetNotificationCallbacks(this);
+
+ class MOZ_STACK_CLASS CleanUpOnFailure
+ {
+ public:
+ explicit CleanUpOnFailure(WebSocketChannel* aWebSocketChannel)
+ : mWebSocketChannel(aWebSocketChannel)
+ {}
+
+ ~CleanUpOnFailure()
+ {
+ if (!mWebSocketChannel->mWasOpened) {
+ mWebSocketChannel->mChannel = nullptr;
+ mWebSocketChannel->mHttpChannel = nullptr;
+ }
+ }
+
+ WebSocketChannel *mWebSocketChannel;
+ };
+
+ CleanUpOnFailure cuof(this);
+
+ mChannel = do_QueryInterface(localChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHttpChannel = do_QueryInterface(localChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupRequest();
+ if (NS_FAILED(rv))
+ return rv;
+
+ mPrivateBrowsing = NS_UsePrivateBrowsing(localChannel);
+
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->AddHost(mHost, mSerial,
+ BaseWebSocketChannel::mEncrypted);
+ }
+
+ rv = ApplyForAdmission();
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Register for prefs change notifications
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ NS_WARNING("failed to get observer service");
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Only set these if the open was successful:
+ //
+ mWasOpened = 1;
+ mListenerMT = new ListenerAndContextContainer(aListener, aContext);
+ IncrementSessionCount();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::Close(uint16_t code, const nsACString & reason)
+{
+ LOG(("WebSocketChannel::Close() %p\n", this));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ // save the networkstats (bug 855949)
+ SaveNetworkStats(true);
+
+ if (mRequestedClose) {
+ return NS_OK;
+ }
+
+ // The API requires the UTF-8 string to be 123 or less bytes
+ if (reason.Length() > 123)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ mRequestedClose = 1;
+ mScriptCloseReason = reason;
+ mScriptCloseCode = code;
+
+ if (!mTransport || mConnecting != NOT_CONNECTING) {
+ nsresult rv;
+ if (code == CLOSE_GOING_AWAY) {
+ // Not an error: for example, tab has closed or navigated away
+ LOG(("WebSocketChannel::Close() GOING_AWAY without transport."));
+ rv = NS_OK;
+ } else {
+ LOG(("WebSocketChannel::Close() without transport - error."));
+ rv = NS_ERROR_NOT_CONNECTED;
+ }
+ StopSession(rv);
+ return rv;
+ }
+
+ return mSocketThread->Dispatch(
+ new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)),
+ nsIEventTarget::DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+WebSocketChannel::SendMsg(const nsACString &aMsg)
+{
+ LOG(("WebSocketChannel::SendMsg() %p\n", this));
+
+ return SendMsgCommon(&aMsg, false, aMsg.Length());
+}
+
+NS_IMETHODIMP
+WebSocketChannel::SendBinaryMsg(const nsACString &aMsg)
+{
+ LOG(("WebSocketChannel::SendBinaryMsg() %p len=%d\n", this, aMsg.Length()));
+ return SendMsgCommon(&aMsg, true, aMsg.Length());
+}
+
+NS_IMETHODIMP
+WebSocketChannel::SendBinaryStream(nsIInputStream *aStream, uint32_t aLength)
+{
+ LOG(("WebSocketChannel::SendBinaryStream() %p\n", this));
+
+ return SendMsgCommon(nullptr, true, aLength, aStream);
+}
+
+nsresult
+WebSocketChannel::SendMsgCommon(const nsACString *aMsg, bool aIsBinary,
+ uint32_t aLength, nsIInputStream *aStream)
+{
+ MOZ_ASSERT(IsOnTargetThread(), "not target thread");
+
+ if (!mDataStarted) {
+ LOG(("WebSocketChannel:: Error: data not started yet\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mRequestedClose) {
+ LOG(("WebSocketChannel:: Error: send when closed\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mStopped) {
+ LOG(("WebSocketChannel:: Error: send when stopped\n"));
+ return NS_ERROR_NOT_CONNECTED;
+ }
+
+ MOZ_ASSERT(mMaxMessageSize >= 0, "max message size negative");
+ if (aLength > static_cast<uint32_t>(mMaxMessageSize)) {
+ LOG(("WebSocketChannel:: Error: message too big\n"));
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ if (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->NewMsgSent(mHost, mSerial, aLength);
+ LOG(("Added new msg sent for %s", mHost.get()));
+ }
+
+ return mSocketThread->Dispatch(
+ aStream ? new OutboundEnqueuer(this, new OutboundMessage(aStream, aLength))
+ : new OutboundEnqueuer(this,
+ new OutboundMessage(aIsBinary ? kMsgTypeBinaryString
+ : kMsgTypeString,
+ new nsCString(*aMsg))),
+ nsIEventTarget::DISPATCH_NORMAL);
+}
+
+// nsIHttpUpgradeListener
+
+NS_IMETHODIMP
+WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport,
+ nsIAsyncInputStream *aSocketIn,
+ nsIAsyncOutputStream *aSocketOut)
+{
+ if (!NS_IsMainThread()) {
+ return NS_DispatchToMainThread(new CallOnTransportAvailable(this,
+ aTransport,
+ aSocketIn,
+ aSocketOut));
+ }
+
+ LOG(("WebSocketChannel::OnTransportAvailable %p [%p %p %p] rcvdonstart=%d\n",
+ this, aTransport, aSocketIn, aSocketOut, mGotUpgradeOK));
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnTransportAvailable: Already stopped"));
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(!mRecvdHttpUpgradeTransport, "OTA duplicated");
+ MOZ_ASSERT(aSocketIn, "OTA with invalid socketIn");
+
+ mTransport = aTransport;
+ mSocketIn = aSocketIn;
+ mSocketOut = aSocketOut;
+
+ nsresult rv;
+ rv = mTransport->SetEventSink(nullptr, nullptr);
+ if (NS_FAILED(rv)) return rv;
+ rv = mTransport->SetSecurityCallbacks(this);
+ if (NS_FAILED(rv)) return rv;
+
+ mRecvdHttpUpgradeTransport = 1;
+ if (mGotUpgradeOK) {
+ // We're now done CONNECTING, which means we can now open another,
+ // perhaps parallel, connection to the same host if one
+ // is pending
+ nsWSAdmissionManager::OnConnected(this);
+
+ return StartWebsocketData();
+ }
+
+ if (mIsServerSide) {
+ if (!mNegotiatedExtensions.IsEmpty()) {
+ bool clientNoContextTakeover;
+ bool serverNoContextTakeover;
+ int32_t clientMaxWindowBits;
+ int32_t serverMaxWindowBits;
+
+ rv = ParseWebSocketExtension(mNegotiatedExtensions,
+ eParseServerSide,
+ clientNoContextTakeover,
+ serverNoContextTakeover,
+ clientMaxWindowBits,
+ serverMaxWindowBits);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "illegal value provided by server");
+
+ if (clientMaxWindowBits == -1) {
+ clientMaxWindowBits = 15;
+ }
+ if (serverMaxWindowBits == -1) {
+ serverMaxWindowBits = 15;
+ }
+
+ mPMCECompressor = new PMCECompression(serverNoContextTakeover,
+ serverMaxWindowBits,
+ clientMaxWindowBits);
+ if (mPMCECompressor->Active()) {
+ LOG(("WebSocketChannel::OnTransportAvailable: PMCE negotiated, %susing "
+ "context takeover, serverMaxWindowBits=%d, "
+ "clientMaxWindowBits=%d\n",
+ serverNoContextTakeover ? "NOT " : "", serverMaxWindowBits,
+ clientMaxWindowBits));
+
+ mNegotiatedExtensions = "permessage-deflate";
+ } else {
+ LOG(("WebSocketChannel::OnTransportAvailable: Cannot init PMCE "
+ "compression object\n"));
+ mPMCECompressor = nullptr;
+ AbortSession(NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return StartWebsocketData();
+ }
+
+ return NS_OK;
+}
+
+// nsIRequestObserver (from nsIStreamListener)
+
+NS_IMETHODIMP
+WebSocketChannel::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ LOG(("WebSocketChannel::OnStartRequest(): %p [%p %p] recvdhttpupgrade=%d\n",
+ this, aRequest, mHttpChannel.get(), mRecvdHttpUpgradeTransport));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT(!mGotUpgradeOK, "OTA duplicated");
+
+ if (mOpenTimer) {
+ mOpenTimer->Cancel();
+ mOpenTimer = nullptr;
+ }
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnStartRequest: Channel Already Done\n"));
+ AbortSession(NS_ERROR_CONNECTION_REFUSED);
+ return NS_ERROR_CONNECTION_REFUSED;
+ }
+
+ nsresult rv;
+ uint32_t status;
+ char *val, *token;
+
+ rv = mHttpChannel->GetResponseStatus(&status);
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::OnStartRequest: No HTTP Response\n"));
+ AbortSession(NS_ERROR_CONNECTION_REFUSED);
+ return NS_ERROR_CONNECTION_REFUSED;
+ }
+
+ LOG(("WebSocketChannel::OnStartRequest: HTTP status %d\n", status));
+ if (status != 101) {
+ AbortSession(NS_ERROR_CONNECTION_REFUSED);
+ return NS_ERROR_CONNECTION_REFUSED;
+ }
+
+ nsAutoCString respUpgrade;
+ rv = mHttpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("Upgrade"), respUpgrade);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_ERROR_ILLEGAL_VALUE;
+ if (!respUpgrade.IsEmpty()) {
+ val = respUpgrade.BeginWriting();
+ while ((token = nsCRT::strtok(val, ", \t", &val))) {
+ if (PL_strcasecmp(token, "Websocket") == 0) {
+ rv = NS_OK;
+ break;
+ }
+ }
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::OnStartRequest: "
+ "HTTP response header Upgrade: websocket not found\n"));
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return rv;
+ }
+
+ nsAutoCString respConnection;
+ rv = mHttpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("Connection"), respConnection);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_ERROR_ILLEGAL_VALUE;
+ if (!respConnection.IsEmpty()) {
+ val = respConnection.BeginWriting();
+ while ((token = nsCRT::strtok(val, ", \t", &val))) {
+ if (PL_strcasecmp(token, "Upgrade") == 0) {
+ rv = NS_OK;
+ break;
+ }
+ }
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::OnStartRequest: "
+ "HTTP response header 'Connection: Upgrade' not found\n"));
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return rv;
+ }
+
+ nsAutoCString respAccept;
+ rv = mHttpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("Sec-WebSocket-Accept"),
+ respAccept);
+
+ if (NS_FAILED(rv) ||
+ respAccept.IsEmpty() || !respAccept.Equals(mHashedSecret)) {
+ LOG(("WebSocketChannel::OnStartRequest: "
+ "HTTP response header Sec-WebSocket-Accept check failed\n"));
+ LOG(("WebSocketChannel::OnStartRequest: Expected %s received %s\n",
+ mHashedSecret.get(), respAccept.get()));
+ AbortSession(NS_ERROR_ILLEGAL_VALUE);
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // If we sent a sub protocol header, verify the response matches
+ // If it does not, set mProtocol to "" so the protocol attribute
+ // of the WebSocket JS object reflects that
+ if (!mProtocol.IsEmpty()) {
+ nsAutoCString respProtocol;
+ rv = mHttpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
+ respProtocol);
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_ERROR_ILLEGAL_VALUE;
+ val = mProtocol.BeginWriting();
+ while ((token = nsCRT::strtok(val, ", \t", &val))) {
+ if (PL_strcasecmp(token, respProtocol.get()) == 0) {
+ rv = NS_OK;
+ break;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ LOG(("WebsocketChannel::OnStartRequest: subprotocol %s confirmed",
+ respProtocol.get()));
+ mProtocol = respProtocol;
+ } else {
+ LOG(("WebsocketChannel::OnStartRequest: "
+ "subprotocol [%s] not found - %s returned",
+ mProtocol.get(), respProtocol.get()));
+ mProtocol.Truncate();
+ }
+ } else {
+ LOG(("WebsocketChannel::OnStartRequest "
+ "subprotocol [%s] not found - none returned",
+ mProtocol.get()));
+ mProtocol.Truncate();
+ }
+ }
+
+ rv = HandleExtensions();
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Update mEffectiveURL for off main thread URI access.
+ nsCOMPtr<nsIURI> uri = mURI ? mURI : mOriginalURI;
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ CopyUTF8toUTF16(spec, mEffectiveURL);
+
+ mGotUpgradeOK = 1;
+ if (mRecvdHttpUpgradeTransport) {
+ // We're now done CONNECTING, which means we can now open another,
+ // perhaps parallel, connection to the same host if one
+ // is pending
+ nsWSAdmissionManager::OnConnected(this);
+
+ return StartWebsocketData();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ LOG(("WebSocketChannel::OnStopRequest() %p [%p %p %x]\n",
+ this, aRequest, mHttpChannel.get(), aStatusCode));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ ReportConnectionTelemetry();
+
+ // This is the end of the HTTP upgrade transaction, the
+ // upgraded streams live on
+
+ mChannel = nullptr;
+ mHttpChannel = nullptr;
+ mLoadGroup = nullptr;
+ mCallbacks = nullptr;
+
+ return NS_OK;
+}
+
+// nsIInputStreamCallback
+
+NS_IMETHODIMP
+WebSocketChannel::OnInputStreamReady(nsIAsyncInputStream *aStream)
+{
+ LOG(("WebSocketChannel::OnInputStreamReady() %p\n", this));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
+
+ if (!mSocketIn) // did we we clean up the socket after scheduling InputReady?
+ return NS_OK;
+
+ // this is after the http upgrade - so we are speaking websockets
+ char buffer[2048];
+ uint32_t count;
+ nsresult rv;
+
+ do {
+ rv = mSocketIn->Read((char *)buffer, 2048, &count);
+ LOG(("WebSocketChannel::OnInputStreamReady: read %u rv %x\n", count, rv));
+
+ // accumulate received bytes
+ CountRecvBytes(count);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ mTCPClosed = true;
+ AbortSession(rv);
+ return rv;
+ }
+
+ if (count == 0) {
+ mTCPClosed = true;
+ AbortSession(NS_BASE_STREAM_CLOSED);
+ return NS_OK;
+ }
+
+ if (mStopped) {
+ continue;
+ }
+
+ rv = ProcessInput((uint8_t *)buffer, count);
+ if (NS_FAILED(rv)) {
+ AbortSession(rv);
+ return rv;
+ }
+ } while (NS_SUCCEEDED(rv) && mSocketIn);
+
+ return NS_OK;
+}
+
+
+// nsIOutputStreamCallback
+
+NS_IMETHODIMP
+WebSocketChannel::OnOutputStreamReady(nsIAsyncOutputStream *aStream)
+{
+ LOG(("WebSocketChannel::OnOutputStreamReady() %p\n", this));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
+ nsresult rv;
+
+ if (!mCurrentOut)
+ PrimeNewOutgoingMessage();
+
+ while (mCurrentOut && mSocketOut) {
+ const char *sndBuf;
+ uint32_t toSend;
+ uint32_t amtSent;
+
+ if (mHdrOut) {
+ sndBuf = (const char *)mHdrOut;
+ toSend = mHdrOutToSend;
+ LOG(("WebSocketChannel::OnOutputStreamReady: "
+ "Try to send %u of hdr/copybreak\n", toSend));
+ } else {
+ sndBuf = (char *) mCurrentOut->BeginReading() + mCurrentOutSent;
+ toSend = mCurrentOut->Length() - mCurrentOutSent;
+ if (toSend > 0) {
+ LOG(("WebSocketChannel::OnOutputStreamReady: "
+ "Try to send %u of data\n", toSend));
+ }
+ }
+
+ if (toSend == 0) {
+ amtSent = 0;
+ } else {
+ rv = mSocketOut->Write(sndBuf, toSend, &amtSent);
+ LOG(("WebSocketChannel::OnOutputStreamReady: write %u rv %x\n",
+ amtSent, rv));
+
+ // accumulate sent bytes
+ CountSentBytes(amtSent);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ AbortSession(rv);
+ return NS_OK;
+ }
+ }
+
+ if (mHdrOut) {
+ if (amtSent == toSend) {
+ mHdrOut = nullptr;
+ mHdrOutToSend = 0;
+ } else {
+ mHdrOut += amtSent;
+ mHdrOutToSend -= amtSent;
+ mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
+ }
+ } else {
+ if (amtSent == toSend) {
+ if (!mStopped) {
+ mTargetThread->Dispatch(
+ new CallAcknowledge(this, mCurrentOut->OrigLength()),
+ NS_DISPATCH_NORMAL);
+ }
+ DeleteCurrentOutGoingMessage();
+ PrimeNewOutgoingMessage();
+ } else {
+ mCurrentOutSent += amtSent;
+ mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
+ }
+ }
+ }
+
+ if (mReleaseOnTransmit)
+ ReleaseSession();
+ return NS_OK;
+}
+
+// nsIStreamListener
+
+NS_IMETHODIMP
+WebSocketChannel::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ LOG(("WebSocketChannel::OnDataAvailable() %p [%p %p %p %llu %u]\n",
+ this, aRequest, mHttpChannel.get(), aInputStream, aOffset, aCount));
+
+ // This is the HTTP OnDataAvailable Method, which means this is http data in
+ // response to the upgrade request and there should be no http response body
+ // if the upgrade succeeded. This generally should be caught by a non 101
+ // response code in OnStartRequest().. so we can ignore the data here
+
+ LOG(("WebSocketChannel::OnDataAvailable: HTTP data unexpected len>=%u\n",
+ aCount));
+
+ return NS_OK;
+}
+
+nsresult
+WebSocketChannel::SaveNetworkStats(bool enforce)
+{
+#ifdef MOZ_WIDGET_GONK
+ // Check if the active network and app id are valid.
+ if(!mActiveNetworkInfo || mAppId == NECKO_NO_APP_ID) {
+ return NS_OK;
+ }
+
+ uint64_t countRecv = 0;
+ uint64_t countSent = 0;
+
+ mCountRecv.exchange(countRecv);
+ mCountSent.exchange(countSent);
+
+ if (countRecv == 0 && countSent == 0) {
+ // There is no traffic, no need to save.
+ return NS_OK;
+ }
+
+ // If |enforce| is false, the traffic amount is saved
+ // only when the total amount exceeds the predefined
+ // threshold.
+ uint64_t totalBytes = countRecv + countSent;
+ if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) {
+ return NS_OK;
+ }
+
+ // Create the event to save the network statistics.
+ // the event is then dispatched to the main thread.
+ RefPtr<Runnable> event =
+ new SaveNetworkStatsEvent(mAppId, mIsInIsolatedMozBrowser, mActiveNetworkInfo,
+ countRecv, countSent, false);
+ NS_DispatchToMainThread(event);
+
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef CLOSE_GOING_AWAY
diff --git a/netwerk/protocol/websocket/WebSocketChannel.h b/netwerk/protocol/websocket/WebSocketChannel.h
new file mode 100644
index 000000000..e2f332dab
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannel.h
@@ -0,0 +1,337 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketChannel_h
+#define mozilla_net_WebSocketChannel_h
+
+#include "nsISupports.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsITimer.h"
+#include "nsIDNSListener.h"
+#include "nsIObserver.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIChannelEventSink.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIStringStream.h"
+#include "BaseWebSocketChannel.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkInterface.h"
+#include "nsProxyRelease.h"
+#endif
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsDeque.h"
+#include "mozilla/Atomics.h"
+
+class nsIAsyncVerifyRedirectCallback;
+class nsIDashboardEventNotifier;
+class nsIEventTarget;
+class nsIHttpChannel;
+class nsIRandomGenerator;
+class nsISocketTransport;
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class OutboundMessage;
+class OutboundEnqueuer;
+class nsWSAdmissionManager;
+class PMCECompression;
+class CallOnMessageAvailable;
+class CallOnStop;
+class CallOnServerClose;
+class CallAcknowledge;
+class WebSocketEventService;
+
+extern nsresult
+CalculateWebSocketHashedSecret(const nsACString& aKey, nsACString& aHash);
+extern void
+ProcessServerWebSocketExtensions(const nsACString& aExtensions,
+ nsACString& aNegotiatedExtensions);
+
+// Used to enforce "1 connecting websocket per host" rule, and reconnect delays
+enum wsConnectingState {
+ NOT_CONNECTING = 0, // Not yet (or no longer) trying to open connection
+ CONNECTING_QUEUED, // Waiting for other ws to same host to finish opening
+ CONNECTING_DELAYED, // Delayed by "reconnect after failure" algorithm
+ CONNECTING_IN_PROGRESS // Started connection: waiting for result
+};
+
+class WebSocketChannel : public BaseWebSocketChannel,
+ public nsIHttpUpgradeListener,
+ public nsIStreamListener,
+ public nsIInputStreamCallback,
+ public nsIOutputStreamCallback,
+ public nsITimerCallback,
+ public nsIDNSListener,
+ public nsIObserver,
+ public nsIProtocolProxyCallback,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink
+{
+ friend class WebSocketFrame;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIHTTPUPGRADELISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIOBSERVER
+
+ // nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us
+ //
+ NS_IMETHOD AsyncOpen(nsIURI *aURI,
+ const nsACString &aOrigin,
+ uint64_t aWindowID,
+ nsIWebSocketListener *aListener,
+ nsISupports *aContext) override;
+ NS_IMETHOD Close(uint16_t aCode, const nsACString & aReason) override;
+ NS_IMETHOD SendMsg(const nsACString &aMsg) override;
+ NS_IMETHOD SendBinaryMsg(const nsACString &aMsg) override;
+ NS_IMETHOD SendBinaryStream(nsIInputStream *aStream, uint32_t length) override;
+ NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo) override;
+
+ WebSocketChannel();
+ static void Shutdown();
+ bool IsOnTargetThread();
+
+ // Off main thread URI access.
+ void GetEffectiveURL(nsAString& aEffectiveURL) const override;
+ bool IsEncrypted() const override;
+
+ const static uint32_t kControlFrameMask = 0x8;
+
+ // First byte of the header
+ const static uint8_t kFinalFragBit = 0x80;
+ const static uint8_t kRsvBitsMask = 0x70;
+ const static uint8_t kRsv1Bit = 0x40;
+ const static uint8_t kRsv2Bit = 0x20;
+ const static uint8_t kRsv3Bit = 0x10;
+ const static uint8_t kOpcodeBitsMask = 0x0F;
+
+ // Second byte of the header
+ const static uint8_t kMaskBit = 0x80;
+ const static uint8_t kPayloadLengthBitsMask = 0x7F;
+
+protected:
+ virtual ~WebSocketChannel();
+
+private:
+ friend class OutboundEnqueuer;
+ friend class nsWSAdmissionManager;
+ friend class FailDelayManager;
+ friend class CallOnMessageAvailable;
+ friend class CallOnStop;
+ friend class CallOnServerClose;
+ friend class CallAcknowledge;
+
+ // Common send code for binary + text msgs
+ nsresult SendMsgCommon(const nsACString *aMsg, bool isBinary,
+ uint32_t length, nsIInputStream *aStream = nullptr);
+
+ void EnqueueOutgoingMessage(nsDeque &aQueue, OutboundMessage *aMsg);
+
+ void PrimeNewOutgoingMessage();
+ void DeleteCurrentOutGoingMessage();
+ void GeneratePong(uint8_t *payload, uint32_t len);
+ void GeneratePing();
+
+ nsresult OnNetworkChanged();
+ nsresult StartPinging();
+
+ void BeginOpen(bool aCalledFromAdmissionManager);
+ void BeginOpenInternal();
+ nsresult HandleExtensions();
+ nsresult SetupRequest();
+ nsresult ApplyForAdmission();
+ nsresult DoAdmissionDNS();
+ nsresult StartWebsocketData();
+ uint16_t ResultToCloseCode(nsresult resultCode);
+ void ReportConnectionTelemetry();
+
+ void StopSession(nsresult reason);
+ void AbortSession(nsresult reason);
+ void ReleaseSession();
+ void CleanupConnection();
+ void IncrementSessionCount();
+ void DecrementSessionCount();
+
+ void EnsureHdrOut(uint32_t size);
+
+ static void ApplyMask(uint32_t mask, uint8_t *data, uint64_t len);
+
+ bool IsPersistentFramePtr();
+ nsresult ProcessInput(uint8_t *buffer, uint32_t count);
+ bool UpdateReadBuffer(uint8_t *buffer, uint32_t count,
+ uint32_t accumulatedFragments,
+ uint32_t *available);
+
+ inline void ResetPingTimer()
+ {
+ mPingOutstanding = 0;
+ if (mPingTimer) {
+ if (!mPingInterval) {
+ // The timer was created by forced ping and regular pinging is disabled,
+ // so cancel and null out mPingTimer.
+ mPingTimer->Cancel();
+ mPingTimer = nullptr;
+ } else {
+ mPingTimer->SetDelay(mPingInterval);
+ }
+ }
+ }
+
+ nsCOMPtr<nsIEventTarget> mSocketThread;
+ nsCOMPtr<nsIHttpChannelInternal> mChannel;
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+ nsCOMPtr<nsICancelable> mCancelable;
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+ nsCOMPtr<nsIRandomGenerator> mRandomGenerator;
+
+ nsCString mHashedSecret;
+
+ // Used as key for connection managment: Initially set to hostname from URI,
+ // then to IP address (unless we're leaving DNS resolution to a proxy server)
+ nsCString mAddress;
+ int32_t mPort; // WS server port
+
+ // Used for off main thread access to the URI string.
+ nsCString mHost;
+ nsString mEffectiveURL;
+
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+
+ nsCOMPtr<nsITimer> mCloseTimer;
+ uint32_t mCloseTimeout; /* milliseconds */
+
+ nsCOMPtr<nsITimer> mOpenTimer;
+ uint32_t mOpenTimeout; /* milliseconds */
+ wsConnectingState mConnecting; /* 0 if not connecting */
+ nsCOMPtr<nsITimer> mReconnectDelayTimer;
+
+ nsCOMPtr<nsITimer> mPingTimer;
+
+ nsCOMPtr<nsITimer> mLingeringCloseTimer;
+ const static int32_t kLingeringCloseTimeout = 1000;
+ const static int32_t kLingeringCloseThreshold = 50;
+
+ RefPtr<WebSocketEventService> mService;
+
+ int32_t mMaxConcurrentConnections;
+
+ uint64_t mInnerWindowID;
+
+ // following members are accessed only on the main thread
+ uint32_t mGotUpgradeOK : 1;
+ uint32_t mRecvdHttpUpgradeTransport : 1;
+ uint32_t mAutoFollowRedirects : 1;
+ uint32_t mAllowPMCE : 1;
+ uint32_t : 0;
+
+ // following members are accessed only on the socket thread
+ uint32_t mPingOutstanding : 1;
+ uint32_t mReleaseOnTransmit : 1;
+ uint32_t : 0;
+
+ Atomic<bool> mDataStarted;
+ Atomic<bool> mRequestedClose;
+ Atomic<bool> mClientClosed;
+ Atomic<bool> mServerClosed;
+ Atomic<bool> mStopped;
+ Atomic<bool> mCalledOnStop;
+ Atomic<bool> mTCPClosed;
+ Atomic<bool> mOpenedHttpChannel;
+ Atomic<bool> mIncrementedSessionCount;
+ Atomic<bool> mDecrementedSessionCount;
+
+ int32_t mMaxMessageSize;
+ nsresult mStopOnClose;
+ uint16_t mServerCloseCode;
+ nsCString mServerCloseReason;
+ uint16_t mScriptCloseCode;
+ nsCString mScriptCloseReason;
+
+ // These are for the read buffers
+ const static uint32_t kIncomingBufferInitialSize = 16 * 1024;
+ // We're ok with keeping a buffer this size or smaller around for the life of
+ // the websocket. If a particular message needs bigger than this we'll
+ // increase the buffer temporarily, then drop back down to this size.
+ const static uint32_t kIncomingBufferStableSize = 128 * 1024;
+
+ uint8_t *mFramePtr;
+ uint8_t *mBuffer;
+ uint8_t mFragmentOpcode;
+ uint32_t mFragmentAccumulator;
+ uint32_t mBuffered;
+ uint32_t mBufferSize;
+
+ // These are for the send buffers
+ const static int32_t kCopyBreak = 1000;
+
+ OutboundMessage *mCurrentOut;
+ uint32_t mCurrentOutSent;
+ nsDeque mOutgoingMessages;
+ nsDeque mOutgoingPingMessages;
+ nsDeque mOutgoingPongMessages;
+ uint32_t mHdrOutToSend;
+ uint8_t *mHdrOut;
+ uint8_t mOutHeader[kCopyBreak + 16];
+ nsAutoPtr<PMCECompression> mPMCECompressor;
+ uint32_t mDynamicOutputSize;
+ uint8_t *mDynamicOutput;
+ bool mPrivateBrowsing;
+
+ nsCOMPtr<nsIDashboardEventNotifier> mConnectionLogService;
+
+// These members are used for network per-app metering (bug 855949)
+// Currently, they are only available on gonk.
+ Atomic<uint64_t, Relaxed> mCountRecv;
+ Atomic<uint64_t, Relaxed> mCountSent;
+ uint32_t mAppId;
+ bool mIsInIsolatedMozBrowser;
+#ifdef MOZ_WIDGET_GONK
+ nsMainThreadPtrHandle<nsINetworkInfo> mActiveNetworkInfo;
+#endif
+ nsresult SaveNetworkStats(bool);
+ void CountRecvBytes(uint64_t recvBytes)
+ {
+ mCountRecv += recvBytes;
+ SaveNetworkStats(false);
+ }
+ void CountSentBytes(uint64_t sentBytes)
+ {
+ mCountSent += sentBytes;
+ SaveNetworkStats(false);
+ }
+};
+
+class WebSocketSSLChannel : public WebSocketChannel
+{
+public:
+ WebSocketSSLChannel() { BaseWebSocketChannel::mEncrypted = true; }
+protected:
+ virtual ~WebSocketSSLChannel() {}
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketChannel_h
diff --git a/netwerk/protocol/websocket/WebSocketChannelChild.cpp b/netwerk/protocol/websocket/WebSocketChannelChild.cpp
new file mode 100644
index 000000000..2444af45e
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.cpp
@@ -0,0 +1,723 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketLog.h"
+#include "base/compiler_specific.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/net/NeckoChild.h"
+#include "WebSocketChannelChild.h"
+#include "nsITabChild.h"
+#include "nsNetUtil.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "SerializedLoadContext.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(WebSocketChannelChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) WebSocketChannelChild::Release()
+{
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "WebSocketChannelChild");
+
+ if (mRefCnt == 1) {
+ MaybeReleaseIPCObject();
+ return mRefCnt;
+ }
+
+ if (mRefCnt == 0) {
+ mRefCnt = 1; /* stabilize */
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+}
+
+NS_INTERFACE_MAP_BEGIN(WebSocketChannelChild)
+ NS_INTERFACE_MAP_ENTRY(nsIWebSocketChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolHandler)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+NS_INTERFACE_MAP_END
+
+WebSocketChannelChild::WebSocketChannelChild(bool aEncrypted)
+ : mIPCState(Closed)
+ , mMutex("WebSocketChannelChild::mMutex")
+{
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ LOG(("WebSocketChannelChild::WebSocketChannelChild() %p\n", this));
+ mEncrypted = aEncrypted;
+ mEventQ = new ChannelEventQueue(static_cast<nsIWebSocketChannel*>(this));
+}
+
+WebSocketChannelChild::~WebSocketChannelChild()
+{
+ LOG(("WebSocketChannelChild::~WebSocketChannelChild() %p\n", this));
+}
+
+void
+WebSocketChannelChild::AddIPDLReference()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mIPCState == Closed, "Attempt to retain more than one IPDL reference");
+ mIPCState = Opened;
+ }
+
+ AddRef();
+}
+
+void
+WebSocketChannelChild::ReleaseIPDLReference()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mIPCState != Closed, "Attempt to release nonexistent IPDL reference");
+ mIPCState = Closed;
+ }
+
+ Release();
+}
+
+void
+WebSocketChannelChild::MaybeReleaseIPCObject()
+{
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return;
+ }
+
+ mIPCState = Closing;
+ }
+
+ if (!NS_IsMainThread()) {
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_DispatchToMainThread(NewRunnableMethod(this,
+ &WebSocketChannelChild::MaybeReleaseIPCObject)));
+ return;
+ }
+
+ SendDeleteSelf();
+}
+
+void
+WebSocketChannelChild::GetEffectiveURL(nsAString& aEffectiveURL) const
+{
+ aEffectiveURL = mEffectiveURL;
+}
+
+bool
+WebSocketChannelChild::IsEncrypted() const
+{
+ return mEncrypted;
+}
+
+class WrappedChannelEvent : public Runnable
+{
+public:
+ explicit WrappedChannelEvent(ChannelEvent *aChannelEvent)
+ : mChannelEvent(aChannelEvent)
+ {
+ MOZ_RELEASE_ASSERT(aChannelEvent);
+ }
+ NS_IMETHOD Run() override
+ {
+ mChannelEvent->Run();
+ return NS_OK;
+ }
+private:
+ nsAutoPtr<ChannelEvent> mChannelEvent;
+};
+
+void
+WebSocketChannelChild::DispatchToTargetThread(ChannelEvent *aChannelEvent)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(mTargetThread);
+ MOZ_RELEASE_ASSERT(aChannelEvent);
+
+ mTargetThread->Dispatch(new WrappedChannelEvent(aChannelEvent),
+ NS_DISPATCH_NORMAL);
+}
+
+class EventTargetDispatcher : public ChannelEvent
+{
+public:
+ EventTargetDispatcher(ChannelEvent* aChannelEvent,
+ nsIEventTarget* aEventTarget)
+ : mChannelEvent(aChannelEvent)
+ , mEventTarget(aEventTarget)
+ {}
+
+ void Run()
+ {
+ if (mEventTarget) {
+ mEventTarget->Dispatch(new WrappedChannelEvent(mChannelEvent.forget()),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ mChannelEvent->Run();
+ }
+
+private:
+ nsAutoPtr<ChannelEvent> mChannelEvent;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+class StartEvent : public ChannelEvent
+{
+ public:
+ StartEvent(WebSocketChannelChild* aChild,
+ const nsCString& aProtocol,
+ const nsCString& aExtensions,
+ const nsString& aEffectiveURL,
+ bool aEncrypted)
+ : mChild(aChild)
+ , mProtocol(aProtocol)
+ , mExtensions(aExtensions)
+ , mEffectiveURL(aEffectiveURL)
+ , mEncrypted(aEncrypted)
+ {}
+
+ void Run()
+ {
+ mChild->OnStart(mProtocol, mExtensions, mEffectiveURL, mEncrypted);
+ }
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ nsCString mProtocol;
+ nsCString mExtensions;
+ nsString mEffectiveURL;
+ bool mEncrypted;
+};
+
+bool
+WebSocketChannelChild::RecvOnStart(const nsCString& aProtocol,
+ const nsCString& aExtensions,
+ const nsString& aEffectiveURL,
+ const bool& aEncrypted)
+{
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(new StartEvent(this, aProtocol, aExtensions,
+ aEffectiveURL, aEncrypted),
+ mTargetThread));
+
+ return true;
+}
+
+void
+WebSocketChannelChild::OnStart(const nsCString& aProtocol,
+ const nsCString& aExtensions,
+ const nsString& aEffectiveURL,
+ const bool& aEncrypted)
+{
+ LOG(("WebSocketChannelChild::RecvOnStart() %p\n", this));
+ SetProtocol(aProtocol);
+ mNegotiatedExtensions = aExtensions;
+ mEffectiveURL = aEffectiveURL;
+ mEncrypted = aEncrypted;
+
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mListenerMT->mListener->OnStart(mListenerMT->mContext);
+ }
+}
+
+class StopEvent : public ChannelEvent
+{
+ public:
+ StopEvent(WebSocketChannelChild* aChild,
+ const nsresult& aStatusCode)
+ : mChild(aChild)
+ , mStatusCode(aStatusCode)
+ {}
+
+ void Run()
+ {
+ mChild->OnStop(mStatusCode);
+ }
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ nsresult mStatusCode;
+};
+
+bool
+WebSocketChannelChild::RecvOnStop(const nsresult& aStatusCode)
+{
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(new StopEvent(this, aStatusCode),
+ mTargetThread));
+
+ return true;
+}
+
+void
+WebSocketChannelChild::OnStop(const nsresult& aStatusCode)
+{
+ LOG(("WebSocketChannelChild::RecvOnStop() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mListenerMT->mListener->OnStop(mListenerMT->mContext, aStatusCode);
+ }
+}
+
+class MessageEvent : public ChannelEvent
+{
+ public:
+ MessageEvent(WebSocketChannelChild* aChild,
+ const nsCString& aMessage,
+ bool aBinary)
+ : mChild(aChild)
+ , mMessage(aMessage)
+ , mBinary(aBinary)
+ {}
+
+ void Run()
+ {
+ if (!mBinary) {
+ mChild->OnMessageAvailable(mMessage);
+ } else {
+ mChild->OnBinaryMessageAvailable(mMessage);
+ }
+ }
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ nsCString mMessage;
+ bool mBinary;
+};
+
+bool
+WebSocketChannelChild::RecvOnMessageAvailable(const nsCString& aMsg)
+{
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(new MessageEvent(this, aMsg, false),
+ mTargetThread));
+
+ return true;
+}
+
+void
+WebSocketChannelChild::OnMessageAvailable(const nsCString& aMsg)
+{
+ LOG(("WebSocketChannelChild::RecvOnMessageAvailable() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mListenerMT->mListener->OnMessageAvailable(mListenerMT->mContext, aMsg);
+ }
+}
+
+bool
+WebSocketChannelChild::RecvOnBinaryMessageAvailable(const nsCString& aMsg)
+{
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(new MessageEvent(this, aMsg, true),
+ mTargetThread));
+
+ return true;
+}
+
+void
+WebSocketChannelChild::OnBinaryMessageAvailable(const nsCString& aMsg)
+{
+ LOG(("WebSocketChannelChild::RecvOnBinaryMessageAvailable() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mListenerMT->mListener->OnBinaryMessageAvailable(mListenerMT->mContext,
+ aMsg);
+ }
+}
+
+class AcknowledgeEvent : public ChannelEvent
+{
+ public:
+ AcknowledgeEvent(WebSocketChannelChild* aChild,
+ const uint32_t& aSize)
+ : mChild(aChild)
+ , mSize(aSize)
+ {}
+
+ void Run()
+ {
+ mChild->OnAcknowledge(mSize);
+ }
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ uint32_t mSize;
+};
+
+bool
+WebSocketChannelChild::RecvOnAcknowledge(const uint32_t& aSize)
+{
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(new AcknowledgeEvent(this, aSize),
+ mTargetThread));
+
+ return true;
+}
+
+void
+WebSocketChannelChild::OnAcknowledge(const uint32_t& aSize)
+{
+ LOG(("WebSocketChannelChild::RecvOnAcknowledge() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mListenerMT->mListener->OnAcknowledge(mListenerMT->mContext, aSize);
+ }
+}
+
+class ServerCloseEvent : public ChannelEvent
+{
+ public:
+ ServerCloseEvent(WebSocketChannelChild* aChild,
+ const uint16_t aCode,
+ const nsCString &aReason)
+ : mChild(aChild)
+ , mCode(aCode)
+ , mReason(aReason)
+ {}
+
+ void Run()
+ {
+ mChild->OnServerClose(mCode, mReason);
+ }
+ private:
+ RefPtr<WebSocketChannelChild> mChild;
+ uint16_t mCode;
+ nsCString mReason;
+};
+
+bool
+WebSocketChannelChild::RecvOnServerClose(const uint16_t& aCode,
+ const nsCString& aReason)
+{
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(new ServerCloseEvent(this, aCode, aReason),
+ mTargetThread));
+
+ return true;
+}
+
+void
+WebSocketChannelChild::OnServerClose(const uint16_t& aCode,
+ const nsCString& aReason)
+{
+ LOG(("WebSocketChannelChild::RecvOnServerClose() %p\n", this));
+ if (mListenerMT) {
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ mListenerMT->mListener->OnServerClose(mListenerMT->mContext, aCode,
+ aReason);
+ }
+}
+
+NS_IMETHODIMP
+WebSocketChannelChild::AsyncOpen(nsIURI *aURI,
+ const nsACString &aOrigin,
+ uint64_t aInnerWindowID,
+ nsIWebSocketListener *aListener,
+ nsISupports *aContext)
+{
+ LOG(("WebSocketChannelChild::AsyncOpen() %p\n", this));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ MOZ_ASSERT((aURI && !mIsServerSide) || (!aURI && mIsServerSide),
+ "Invalid aURI for WebSocketChannelChild::AsyncOpen");
+ MOZ_ASSERT(aListener && !mListenerMT,
+ "Invalid state for WebSocketChannelChild::AsyncOpen");
+
+ mozilla::dom::TabChild* tabChild = nullptr;
+ nsCOMPtr<nsITabChild> iTabChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsITabChild),
+ getter_AddRefs(iTabChild));
+ if (iTabChild) {
+ tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
+ }
+ if (MissingRequiredTabChild(tabChild, "websocket")) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Corresponding release in DeallocPWebSocket
+ AddIPDLReference();
+
+ OptionalURIParams uri;
+ OptionalLoadInfoArgs loadInfoArgs;
+ OptionalTransportProvider transportProvider;
+
+ if (!mIsServerSide) {
+ uri = URIParams();
+ SerializeURI(aURI, uri.get_URIParams());
+ nsresult rv = LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfoArgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transportProvider = void_t();
+ } else {
+ uri = void_t();
+ loadInfoArgs = void_t();
+
+ MOZ_ASSERT(mServerTransportProvider);
+ PTransportProviderChild *ipcChild;
+ nsresult rv = mServerTransportProvider->GetIPCChild(&ipcChild);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transportProvider = ipcChild;
+ }
+
+ gNeckoChild->SendPWebSocketConstructor(this, tabChild,
+ IPC::SerializedLoadContext(this),
+ mSerial);
+ if (!SendAsyncOpen(uri, nsCString(aOrigin), aInnerWindowID, mProtocol,
+ mEncrypted, mPingInterval, mClientSetPingInterval,
+ mPingResponseTimeout, mClientSetPingTimeout, loadInfoArgs,
+ transportProvider, mNegotiatedExtensions)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mIsServerSide) {
+ mServerTransportProvider = nullptr;
+ }
+
+ mOriginalURI = aURI;
+ mURI = mOriginalURI;
+ mListenerMT = new ListenerAndContextContainer(aListener, aContext);
+ mOrigin = aOrigin;
+ mWasOpened = 1;
+
+ return NS_OK;
+}
+
+class CloseEvent : public Runnable
+{
+public:
+ CloseEvent(WebSocketChannelChild *aChild,
+ uint16_t aCode,
+ const nsACString& aReason)
+ : mChild(aChild)
+ , mCode(aCode)
+ , mReason(aReason)
+ {
+ MOZ_RELEASE_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aChild);
+ }
+ NS_IMETHOD Run() override
+ {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ mChild->Close(mCode, mReason);
+ return NS_OK;
+ }
+private:
+ RefPtr<WebSocketChannelChild> mChild;
+ uint16_t mCode;
+ nsCString mReason;
+};
+
+NS_IMETHODIMP
+WebSocketChannelChild::Close(uint16_t code, const nsACString & reason)
+{
+ if (!NS_IsMainThread()) {
+ MOZ_RELEASE_ASSERT(NS_GetCurrentThread() == mTargetThread);
+ return NS_DispatchToMainThread(new CloseEvent(this, code, reason));
+ }
+ LOG(("WebSocketChannelChild::Close() %p\n", this));
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendClose(code, nsCString(reason))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+class MsgEvent : public Runnable
+{
+public:
+ MsgEvent(WebSocketChannelChild *aChild,
+ const nsACString &aMsg,
+ bool aBinaryMsg)
+ : mChild(aChild)
+ , mMsg(aMsg)
+ , mBinaryMsg(aBinaryMsg)
+ {
+ MOZ_RELEASE_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aChild);
+ }
+ NS_IMETHOD Run() override
+ {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (mBinaryMsg) {
+ mChild->SendBinaryMsg(mMsg);
+ } else {
+ mChild->SendMsg(mMsg);
+ }
+ return NS_OK;
+ }
+private:
+ RefPtr<WebSocketChannelChild> mChild;
+ nsCString mMsg;
+ bool mBinaryMsg;
+};
+
+NS_IMETHODIMP
+WebSocketChannelChild::SendMsg(const nsACString &aMsg)
+{
+ if (!NS_IsMainThread()) {
+ MOZ_RELEASE_ASSERT(IsOnTargetThread());
+ return NS_DispatchToMainThread(new MsgEvent(this, aMsg, false));
+ }
+ LOG(("WebSocketChannelChild::SendMsg() %p\n", this));
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendSendMsg(nsCString(aMsg))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelChild::SendBinaryMsg(const nsACString &aMsg)
+{
+ if (!NS_IsMainThread()) {
+ MOZ_RELEASE_ASSERT(IsOnTargetThread());
+ return NS_DispatchToMainThread(new MsgEvent(this, aMsg, true));
+ }
+ LOG(("WebSocketChannelChild::SendBinaryMsg() %p\n", this));
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendSendBinaryMsg(nsCString(aMsg))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+class BinaryStreamEvent : public Runnable
+{
+public:
+ BinaryStreamEvent(WebSocketChannelChild *aChild,
+ OptionalInputStreamParams *aStream,
+ uint32_t aLength)
+ : mChild(aChild)
+ , mStream(aStream)
+ , mLength(aLength)
+ {
+ MOZ_RELEASE_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aChild);
+ }
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mChild->SendBinaryStream(mStream, mLength);
+ return NS_OK;
+ }
+private:
+ RefPtr<WebSocketChannelChild> mChild;
+ nsAutoPtr<OptionalInputStreamParams> mStream;
+ uint32_t mLength;
+};
+
+NS_IMETHODIMP
+WebSocketChannelChild::SendBinaryStream(nsIInputStream *aStream,
+ uint32_t aLength)
+{
+ OptionalInputStreamParams *stream = new OptionalInputStreamParams();
+ nsTArray<mozilla::ipc::FileDescriptor> fds;
+ SerializeInputStream(aStream, *stream, fds);
+
+ MOZ_ASSERT(fds.IsEmpty());
+
+ if (!NS_IsMainThread()) {
+ MOZ_RELEASE_ASSERT(NS_GetCurrentThread() == mTargetThread);
+ return NS_DispatchToMainThread(new BinaryStreamEvent(this, stream, aLength));
+ }
+ return SendBinaryStream(stream, aLength);
+}
+
+nsresult
+WebSocketChannelChild::SendBinaryStream(OptionalInputStreamParams *aStream,
+ uint32_t aLength)
+{
+ LOG(("WebSocketChannelChild::SendBinaryStream() %p\n", this));
+
+ nsAutoPtr<OptionalInputStreamParams> stream(aStream);
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendSendBinaryStream(*stream, aLength)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelChild::GetSecurityInfo(nsISupports **aSecurityInfo)
+{
+ LOG(("WebSocketChannelChild::GetSecurityInfo() %p\n", this));
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketChannelChild::nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketChannelChild::RetargetDeliveryTo(nsIEventTarget* aTargetThread)
+{
+ nsresult rv = BaseWebSocketChannel::RetargetDeliveryTo(aTargetThread);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+
+ return mEventQ->RetargetDeliveryTo(aTargetThread);
+}
+
+bool
+WebSocketChannelChild::IsOnTargetThread()
+{
+ MOZ_ASSERT(mTargetThread);
+ bool isOnTargetThread = false;
+ nsresult rv = mTargetThread->IsOnCurrentThread(&isOnTargetThread);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_FAILED(rv) ? false : isOnTargetThread;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketChannelChild.h b/netwerk/protocol/websocket/WebSocketChannelChild.h
new file mode 100644
index 000000000..9fbb707ed
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketChannelChild_h
+#define mozilla_net_WebSocketChannelChild_h
+
+#include "mozilla/net/PWebSocketChild.h"
+#include "mozilla/net/BaseWebSocketChannel.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+class ChannelEvent;
+class ChannelEventQueue;
+
+class WebSocketChannelChild final : public BaseWebSocketChannel,
+ public PWebSocketChild
+{
+ public:
+ explicit WebSocketChannelChild(bool aSecure);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+
+ // nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us
+ //
+ NS_IMETHOD AsyncOpen(nsIURI *aURI, const nsACString &aOrigin,
+ uint64_t aInnerWindowID,
+ nsIWebSocketListener *aListener,
+ nsISupports *aContext) override;
+ NS_IMETHOD Close(uint16_t code, const nsACString & reason) override;
+ NS_IMETHOD SendMsg(const nsACString &aMsg) override;
+ NS_IMETHOD SendBinaryMsg(const nsACString &aMsg) override;
+ NS_IMETHOD SendBinaryStream(nsIInputStream *aStream, uint32_t aLength) override;
+ nsresult SendBinaryStream(OptionalInputStreamParams *aStream, uint32_t aLength);
+ NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo) override;
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ // Off main thread URI access.
+ void GetEffectiveURL(nsAString& aEffectiveURL) const override;
+ bool IsEncrypted() const override;
+
+ private:
+ ~WebSocketChannelChild();
+
+ bool RecvOnStart(const nsCString& aProtocol, const nsCString& aExtensions,
+ const nsString& aEffectiveURL, const bool& aSecure) override;
+ bool RecvOnStop(const nsresult& aStatusCode) override;
+ bool RecvOnMessageAvailable(const nsCString& aMsg) override;
+ bool RecvOnBinaryMessageAvailable(const nsCString& aMsg) override;
+ bool RecvOnAcknowledge(const uint32_t& aSize) override;
+ bool RecvOnServerClose(const uint16_t& aCode, const nsCString &aReason) override;
+
+ void OnStart(const nsCString& aProtocol, const nsCString& aExtensions,
+ const nsString& aEffectiveURL, const bool& aSecure);
+ void OnStop(const nsresult& aStatusCode);
+ void OnMessageAvailable(const nsCString& aMsg);
+ void OnBinaryMessageAvailable(const nsCString& aMsg);
+ void OnAcknowledge(const uint32_t& aSize);
+ void OnServerClose(const uint16_t& aCode, const nsCString& aReason);
+ void AsyncOpenFailed();
+
+ void DispatchToTargetThread(ChannelEvent *aChannelEvent);
+ bool IsOnTargetThread();
+
+ void MaybeReleaseIPCObject();
+
+ RefPtr<ChannelEventQueue> mEventQ;
+ nsString mEffectiveURL;
+
+ // This variable is protected by mutex.
+ enum {
+ Opened,
+ Closing,
+ Closed
+ } mIPCState;
+
+ mozilla::Mutex mMutex;
+
+ friend class StartEvent;
+ friend class StopEvent;
+ friend class MessageEvent;
+ friend class AcknowledgeEvent;
+ friend class ServerCloseEvent;
+ friend class AsyncOpenFailedEvent;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketChannelChild_h
diff --git a/netwerk/protocol/websocket/WebSocketChannelParent.cpp b/netwerk/protocol/websocket/WebSocketChannelParent.cpp
new file mode 100644
index 000000000..742f05b7d
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.cpp
@@ -0,0 +1,306 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketLog.h"
+#include "WebSocketChannelParent.h"
+#include "nsIAuthPromptProvider.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/WebSocketChannel.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(WebSocketChannelParent,
+ nsIWebSocketListener,
+ nsIInterfaceRequestor)
+
+WebSocketChannelParent::WebSocketChannelParent(nsIAuthPromptProvider* aAuthProvider,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus,
+ uint32_t aSerial)
+ : mAuthProvider(aAuthProvider)
+ , mLoadContext(aLoadContext)
+ , mIPCOpen(true)
+ , mSerial(aSerial)
+{
+ // Websocket channels can't have a private browsing override
+ MOZ_ASSERT_IF(!aLoadContext, aOverrideStatus == kPBOverride_Unset);
+}
+
+WebSocketChannelParent::~WebSocketChannelParent()
+{
+}
+//-----------------------------------------------------------------------------
+// WebSocketChannelParent::PWebSocketChannelParent
+//-----------------------------------------------------------------------------
+
+bool
+WebSocketChannelParent::RecvDeleteSelf()
+{
+ LOG(("WebSocketChannelParent::RecvDeleteSelf() %p\n", this));
+ mChannel = nullptr;
+ mAuthProvider = nullptr;
+ return mIPCOpen ? Send__delete__(this) : true;
+}
+
+bool
+WebSocketChannelParent::RecvAsyncOpen(const OptionalURIParams& aURI,
+ const nsCString& aOrigin,
+ const uint64_t& aInnerWindowID,
+ const nsCString& aProtocol,
+ const bool& aSecure,
+ const uint32_t& aPingInterval,
+ const bool& aClientSetPingInterval,
+ const uint32_t& aPingTimeout,
+ const bool& aClientSetPingTimeout,
+ const OptionalLoadInfoArgs& aLoadInfoArgs,
+ const OptionalTransportProvider& aTransportProvider,
+ const nsCString& aNegotiatedExtensions)
+{
+ LOG(("WebSocketChannelParent::RecvAsyncOpen() %p\n", this));
+
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsILoadInfo> loadInfo;
+
+ rv = LoadInfoArgsToLoadInfo(aLoadInfoArgs, getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ goto fail;
+ }
+
+ if (aSecure) {
+ mChannel =
+ do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
+ } else {
+ mChannel =
+ do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
+ }
+ if (NS_FAILED(rv))
+ goto fail;
+
+ rv = mChannel->SetSerial(mSerial);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ goto fail;
+ }
+
+ rv = mChannel->SetLoadInfo(loadInfo);
+ if (NS_FAILED(rv)) {
+ goto fail;
+ }
+
+ rv = mChannel->SetNotificationCallbacks(this);
+ if (NS_FAILED(rv))
+ goto fail;
+
+ rv = mChannel->SetProtocol(aProtocol);
+ if (NS_FAILED(rv))
+ goto fail;
+
+ if (aTransportProvider.type() != OptionalTransportProvider::Tvoid_t) {
+ RefPtr<TransportProviderParent> provider =
+ static_cast<TransportProviderParent*>(
+ aTransportProvider.get_PTransportProviderParent());
+ rv = mChannel->SetServerParameters(provider, aNegotiatedExtensions);
+ if (NS_FAILED(rv)) {
+ goto fail;
+ }
+ } else {
+ uri = DeserializeURI(aURI);
+ if (!uri) {
+ rv = NS_ERROR_FAILURE;
+ goto fail;
+ }
+ }
+
+ // only use ping values from child if they were overridden by client code.
+ if (aClientSetPingInterval) {
+ // IDL allows setting in seconds, so must be multiple of 1000 ms
+ MOZ_ASSERT(aPingInterval >= 1000 && !(aPingInterval % 1000));
+ mChannel->SetPingInterval(aPingInterval / 1000);
+ }
+ if (aClientSetPingTimeout) {
+ MOZ_ASSERT(aPingTimeout >= 1000 && !(aPingTimeout % 1000));
+ mChannel->SetPingTimeout(aPingTimeout / 1000);
+ }
+
+ rv = mChannel->AsyncOpen(uri, aOrigin, aInnerWindowID, this, nullptr);
+ if (NS_FAILED(rv))
+ goto fail;
+
+ return true;
+
+fail:
+ mChannel = nullptr;
+ return SendOnStop(rv);
+}
+
+bool
+WebSocketChannelParent::RecvClose(const uint16_t& code, const nsCString& reason)
+{
+ LOG(("WebSocketChannelParent::RecvClose() %p\n", this));
+ if (mChannel) {
+ nsresult rv = mChannel->Close(code, reason);
+ NS_ENSURE_SUCCESS(rv, true);
+ }
+
+ return true;
+}
+
+bool
+WebSocketChannelParent::RecvSendMsg(const nsCString& aMsg)
+{
+ LOG(("WebSocketChannelParent::RecvSendMsg() %p\n", this));
+ if (mChannel) {
+ nsresult rv = mChannel->SendMsg(aMsg);
+ NS_ENSURE_SUCCESS(rv, true);
+ }
+ return true;
+}
+
+bool
+WebSocketChannelParent::RecvSendBinaryMsg(const nsCString& aMsg)
+{
+ LOG(("WebSocketChannelParent::RecvSendBinaryMsg() %p\n", this));
+ if (mChannel) {
+ nsresult rv = mChannel->SendBinaryMsg(aMsg);
+ NS_ENSURE_SUCCESS(rv, true);
+ }
+ return true;
+}
+
+bool
+WebSocketChannelParent::RecvSendBinaryStream(const InputStreamParams& aStream,
+ const uint32_t& aLength)
+{
+ LOG(("WebSocketChannelParent::RecvSendBinaryStream() %p\n", this));
+ if (mChannel) {
+ nsTArray<mozilla::ipc::FileDescriptor> fds;
+ nsCOMPtr<nsIInputStream> stream = DeserializeInputStream(aStream, fds);
+ if (!stream) {
+ return false;
+ }
+ nsresult rv = mChannel->SendBinaryStream(stream, aLength);
+ NS_ENSURE_SUCCESS(rv, true);
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnStart(nsISupports *aContext)
+{
+ LOG(("WebSocketChannelParent::OnStart() %p\n", this));
+ nsAutoCString protocol, extensions;
+ nsString effectiveURL;
+ bool encrypted = false;
+ if (mChannel) {
+ mChannel->GetProtocol(protocol);
+ mChannel->GetExtensions(extensions);
+
+ RefPtr<WebSocketChannel> channel;
+ channel = static_cast<WebSocketChannel*>(mChannel.get());
+ MOZ_ASSERT(channel);
+
+ channel->GetEffectiveURL(effectiveURL);
+ encrypted = channel->IsEncrypted();
+ }
+ if (!mIPCOpen || !SendOnStart(protocol, extensions, effectiveURL, encrypted)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnStop(nsISupports *aContext, nsresult aStatusCode)
+{
+ LOG(("WebSocketChannelParent::OnStop() %p\n", this));
+ if (!mIPCOpen || !SendOnStop(aStatusCode)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnMessageAvailable(nsISupports *aContext, const nsACString& aMsg)
+{
+ LOG(("WebSocketChannelParent::OnMessageAvailable() %p\n", this));
+ if (!mIPCOpen || !SendOnMessageAvailable(nsCString(aMsg))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnBinaryMessageAvailable(nsISupports *aContext, const nsACString& aMsg)
+{
+ LOG(("WebSocketChannelParent::OnBinaryMessageAvailable() %p\n", this));
+ if (!mIPCOpen || !SendOnBinaryMessageAvailable(nsCString(aMsg))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnAcknowledge(nsISupports *aContext, uint32_t aSize)
+{
+ LOG(("WebSocketChannelParent::OnAcknowledge() %p\n", this));
+ if (!mIPCOpen || !SendOnAcknowledge(aSize)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelParent::OnServerClose(nsISupports *aContext,
+ uint16_t code, const nsACString & reason)
+{
+ LOG(("WebSocketChannelParent::OnServerClose() %p\n", this));
+ if (!mIPCOpen || !SendOnServerClose(code, nsCString(reason))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+void
+WebSocketChannelParent::ActorDestroy(ActorDestroyReason why)
+{
+ LOG(("WebSocketChannelParent::ActorDestroy() %p\n", this));
+ mIPCOpen = false;
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketChannelParent::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketChannelParent::GetInterface(const nsIID & iid, void **result)
+{
+ LOG(("WebSocketChannelParent::GetInterface() %p\n", this));
+ if (mAuthProvider && iid.Equals(NS_GET_IID(nsIAuthPromptProvider)))
+ return mAuthProvider->GetAuthPrompt(nsIAuthPromptProvider::PROMPT_NORMAL,
+ iid, result);
+
+ // Only support nsILoadContext if child channel's callbacks did too
+ if (iid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(result);
+ return NS_OK;
+ }
+
+ return QueryInterface(iid, result);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketChannelParent.h b/netwerk/protocol/websocket/WebSocketChannelParent.h
new file mode 100644
index 000000000..10c363ef2
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketChannelParent_h
+#define mozilla_net_WebSocketChannelParent_h
+
+#include "mozilla/net/PWebSocketParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIWebSocketListener.h"
+#include "nsIWebSocketChannel.h"
+#include "nsILoadContext.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+class nsIAuthPromptProvider;
+
+namespace mozilla {
+namespace net {
+
+class WebSocketChannelParent : public PWebSocketParent,
+ public nsIWebSocketListener,
+ public nsIInterfaceRequestor
+{
+ ~WebSocketChannelParent();
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBSOCKETLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ WebSocketChannelParent(nsIAuthPromptProvider* aAuthProvider,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus,
+ uint32_t aSerial);
+
+ private:
+ bool RecvAsyncOpen(const OptionalURIParams& aURI,
+ const nsCString& aOrigin,
+ const uint64_t& aInnerWindowID,
+ const nsCString& aProtocol,
+ const bool& aSecure,
+ const uint32_t& aPingInterval,
+ const bool& aClientSetPingInterval,
+ const uint32_t& aPingTimeout,
+ const bool& aClientSetPingTimeout,
+ const OptionalLoadInfoArgs& aLoadInfoArgs,
+ const OptionalTransportProvider& aTransportProvider,
+ const nsCString& aNegotiatedExtensions) override;
+ bool RecvClose(const uint16_t & code, const nsCString & reason) override;
+ bool RecvSendMsg(const nsCString& aMsg) override;
+ bool RecvSendBinaryMsg(const nsCString& aMsg) override;
+ bool RecvSendBinaryStream(const InputStreamParams& aStream,
+ const uint32_t& aLength) override;
+ bool RecvDeleteSelf() override;
+
+ void ActorDestroy(ActorDestroyReason why) override;
+
+ nsCOMPtr<nsIAuthPromptProvider> mAuthProvider;
+ nsCOMPtr<nsIWebSocketChannel> mChannel;
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ bool mIPCOpen;
+
+ uint32_t mSerial;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketChannelParent_h
diff --git a/netwerk/protocol/websocket/WebSocketEventListenerChild.cpp b/netwerk/protocol/websocket/WebSocketEventListenerChild.cpp
new file mode 100644
index 000000000..82553a49b
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerChild.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketEventListenerChild.h"
+
+#include "WebSocketEventService.h"
+#include "WebSocketFrame.h"
+
+namespace mozilla {
+namespace net {
+
+WebSocketEventListenerChild::WebSocketEventListenerChild(uint64_t aInnerWindowID)
+ : mService(WebSocketEventService::GetOrCreate())
+ , mInnerWindowID(aInnerWindowID)
+{}
+
+WebSocketEventListenerChild::~WebSocketEventListenerChild()
+{
+ MOZ_ASSERT(!mService);
+}
+
+bool
+WebSocketEventListenerChild::RecvWebSocketCreated(const uint32_t& aWebSocketSerialID,
+ const nsString& aURI,
+ const nsCString& aProtocols)
+{
+ if (mService) {
+ mService->WebSocketCreated(aWebSocketSerialID, mInnerWindowID, aURI,
+ aProtocols);
+ }
+
+ return true;
+}
+
+bool
+WebSocketEventListenerChild::RecvWebSocketOpened(const uint32_t& aWebSocketSerialID,
+ const nsString& aEffectiveURI,
+ const nsCString& aProtocols,
+ const nsCString& aExtensions)
+{
+ if (mService) {
+ mService->WebSocketOpened(aWebSocketSerialID, mInnerWindowID,
+ aEffectiveURI, aProtocols, aExtensions);
+ }
+
+ return true;
+}
+
+bool
+WebSocketEventListenerChild::RecvWebSocketMessageAvailable(const uint32_t& aWebSocketSerialID,
+ const nsCString& aData,
+ const uint16_t& aMessageType)
+{
+ if (mService) {
+ mService->WebSocketMessageAvailable(aWebSocketSerialID, mInnerWindowID,
+ aData, aMessageType);
+ }
+
+ return true;
+}
+
+bool
+WebSocketEventListenerChild::RecvWebSocketClosed(const uint32_t& aWebSocketSerialID,
+ const bool& aWasClean,
+ const uint16_t& aCode,
+ const nsString& aReason)
+{
+ if (mService) {
+ mService->WebSocketClosed(aWebSocketSerialID, mInnerWindowID,
+ aWasClean, aCode, aReason);
+ }
+
+ return true;
+}
+
+bool
+WebSocketEventListenerChild::RecvFrameReceived(const uint32_t& aWebSocketSerialID,
+ const WebSocketFrameData& aFrameData)
+{
+ if (mService) {
+ RefPtr<WebSocketFrame> frame = new WebSocketFrame(aFrameData);
+ mService->FrameReceived(aWebSocketSerialID, mInnerWindowID, frame.forget());
+ }
+
+ return true;
+}
+
+bool
+WebSocketEventListenerChild::RecvFrameSent(const uint32_t& aWebSocketSerialID,
+ const WebSocketFrameData& aFrameData)
+{
+ if (mService) {
+ RefPtr<WebSocketFrame> frame = new WebSocketFrame(aFrameData);
+ mService->FrameSent(aWebSocketSerialID, mInnerWindowID, frame.forget());
+ }
+
+ return true;
+}
+
+void
+WebSocketEventListenerChild::Close()
+{
+ mService = nullptr;
+ SendClose();
+}
+
+void
+WebSocketEventListenerChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+ mService = nullptr;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketEventListenerChild.h b/netwerk/protocol/websocket/WebSocketEventListenerChild.h
new file mode 100644
index 000000000..8a42abac2
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerChild.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketEventListenerChild_h
+#define mozilla_net_WebSocketEventListenerChild_h
+
+#include "mozilla/net/PWebSocketEventListenerChild.h"
+
+namespace mozilla {
+namespace net {
+
+class WebSocketEventService;
+
+class WebSocketEventListenerChild final : public PWebSocketEventListenerChild
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(WebSocketEventListenerChild)
+
+ explicit WebSocketEventListenerChild(uint64_t aInnerWindowID);
+
+ bool RecvWebSocketCreated(const uint32_t& aWebSocketSerialID,
+ const nsString& aURI,
+ const nsCString& aProtocols) override;
+
+ bool RecvWebSocketOpened(const uint32_t& aWebSocketSerialID,
+ const nsString& aEffectiveURI,
+ const nsCString& aProtocols,
+ const nsCString& aExtensions) override;
+
+ bool RecvWebSocketMessageAvailable(const uint32_t& aWebSocketSerialID,
+ const nsCString& aData,
+ const uint16_t& aMessageType) override;
+
+ bool RecvWebSocketClosed(const uint32_t& aWebSocketSerialID,
+ const bool& aWasClean,
+ const uint16_t& aCode,
+ const nsString& aReason) override;
+
+ bool RecvFrameReceived(const uint32_t& aWebSocketSerialID,
+ const WebSocketFrameData& aFrameData) override;
+
+ bool RecvFrameSent(const uint32_t& aWebSocketSerialID,
+ const WebSocketFrameData& aFrameData) override;
+
+ void Close();
+
+private:
+ ~WebSocketEventListenerChild();
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ RefPtr<WebSocketEventService> mService;
+ uint64_t mInnerWindowID;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketEventListenerChild_h
diff --git a/netwerk/protocol/websocket/WebSocketEventListenerParent.cpp b/netwerk/protocol/websocket/WebSocketEventListenerParent.cpp
new file mode 100644
index 000000000..1814ecd7d
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerParent.cpp
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketEventService.h"
+#include "WebSocketEventListenerParent.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+NS_INTERFACE_MAP_BEGIN(WebSocketEventListenerParent)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsIWebSocketEventListener)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(WebSocketEventListenerParent)
+NS_IMPL_RELEASE(WebSocketEventListenerParent)
+
+WebSocketEventListenerParent::WebSocketEventListenerParent(uint64_t aInnerWindowID)
+ : mService(WebSocketEventService::GetOrCreate())
+ , mInnerWindowID(aInnerWindowID)
+{
+ mService->AddListener(mInnerWindowID, this);
+}
+
+WebSocketEventListenerParent::~WebSocketEventListenerParent()
+{
+ MOZ_ASSERT(!mService);
+}
+
+bool
+WebSocketEventListenerParent::RecvClose()
+{
+ if (mService) {
+ UnregisterListener();
+ Unused << Send__delete__(this);
+ }
+
+ return true;
+}
+
+void
+WebSocketEventListenerParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ UnregisterListener();
+}
+
+void
+WebSocketEventListenerParent::UnregisterListener()
+{
+ if (mService) {
+ mService->RemoveListener(mInnerWindowID, this);
+ mService = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::WebSocketCreated(uint32_t aWebSocketSerialID,
+ const nsAString& aURI,
+ const nsACString& aProtocols)
+{
+ Unused << SendWebSocketCreated(aWebSocketSerialID, nsString(aURI),
+ nsCString(aProtocols));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::WebSocketOpened(uint32_t aWebSocketSerialID,
+ const nsAString& aEffectiveURI,
+ const nsACString& aProtocols,
+ const nsACString& aExtensions)
+{
+ Unused << SendWebSocketOpened(aWebSocketSerialID, nsString(aEffectiveURI),
+ nsCString(aProtocols), nsCString(aExtensions));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::WebSocketClosed(uint32_t aWebSocketSerialID,
+ bool aWasClean,
+ uint16_t aCode,
+ const nsAString& aReason)
+{
+ Unused << SendWebSocketClosed(aWebSocketSerialID, aWasClean, aCode,
+ nsString(aReason));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::WebSocketMessageAvailable(uint32_t aWebSocketSerialID,
+ const nsACString& aData,
+ uint16_t aMessageType)
+{
+ Unused << SendWebSocketMessageAvailable(aWebSocketSerialID, nsCString(aData),
+ aMessageType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::FrameReceived(uint32_t aWebSocketSerialID,
+ nsIWebSocketFrame* aFrame)
+{
+ if (!aFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WebSocketFrame* frame = static_cast<WebSocketFrame*>(aFrame);
+ Unused << SendFrameReceived(aWebSocketSerialID, frame->Data());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::FrameSent(uint32_t aWebSocketSerialID,
+ nsIWebSocketFrame* aFrame)
+{
+ if (!aFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WebSocketFrame* frame = static_cast<WebSocketFrame*>(aFrame);
+ Unused << SendFrameSent(aWebSocketSerialID, frame->Data());
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketEventListenerParent.h b/netwerk/protocol/websocket/WebSocketEventListenerParent.h
new file mode 100644
index 000000000..5b5b10d73
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerParent.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketEventListenerParent_h
+#define mozilla_net_WebSocketEventListenerParent_h
+
+#include "mozilla/net/PWebSocketEventListenerParent.h"
+#include "nsIWebSocketEventService.h"
+
+namespace mozilla {
+namespace net {
+
+class WebSocketEventService;
+
+class WebSocketEventListenerParent final : public PWebSocketEventListenerParent
+ , public nsIWebSocketEventListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBSOCKETEVENTLISTENER
+
+ explicit WebSocketEventListenerParent(uint64_t aInnerWindowID);
+
+private:
+ ~WebSocketEventListenerParent();
+
+ virtual bool RecvClose() override;
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ void UnregisterListener();
+
+ RefPtr<WebSocketEventService> mService;
+ uint64_t mInnerWindowID;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WebSocketEventListenerParent_h
diff --git a/netwerk/protocol/websocket/WebSocketEventService.cpp b/netwerk/protocol/websocket/WebSocketEventService.cpp
new file mode 100644
index 000000000..2f9486a70
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventService.cpp
@@ -0,0 +1,578 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketEventListenerChild.h"
+#include "WebSocketEventService.h"
+#include "WebSocketFrame.h"
+
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/StaticPtr.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIObserverService.h"
+#include "nsXULAppAPI.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+StaticRefPtr<WebSocketEventService> gWebSocketEventService;
+
+bool
+IsChildProcess()
+{
+ return XRE_GetProcessType() != GeckoProcessType_Default;
+}
+
+} // anonymous namespace
+
+class WebSocketBaseRunnable : public Runnable
+{
+public:
+ WebSocketBaseRunnable(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID)
+ : mWebSocketSerialID(aWebSocketSerialID)
+ , mInnerWindowID(aInnerWindowID)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<WebSocketEventService> service = WebSocketEventService::GetOrCreate();
+ MOZ_ASSERT(service);
+
+ WebSocketEventService::WindowListeners listeners;
+ service->GetListeners(mInnerWindowID, listeners);
+
+ for (uint32_t i = 0; i < listeners.Length(); ++i) {
+ DoWork(listeners[i]);
+ }
+
+ return NS_OK;
+ }
+
+protected:
+ ~WebSocketBaseRunnable()
+ {}
+
+ virtual void DoWork(nsIWebSocketEventListener* aListener) = 0;
+
+ uint32_t mWebSocketSerialID;
+ uint64_t mInnerWindowID;
+};
+
+class WebSocketFrameRunnable final : public WebSocketBaseRunnable
+{
+public:
+ WebSocketFrameRunnable(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame,
+ bool aFrameSent)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID)
+ , mFrame(Move(aFrame))
+ , mFrameSent(aFrameSent)
+ {}
+
+private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override
+ {
+ DebugOnly<nsresult> rv;
+ if (mFrameSent) {
+ rv = aListener->FrameSent(mWebSocketSerialID, mFrame);
+ } else {
+ rv = aListener->FrameReceived(mWebSocketSerialID, mFrame);
+ }
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Frame op failed");
+ }
+
+ RefPtr<WebSocketFrame> mFrame;
+ bool mFrameSent;
+};
+
+class WebSocketCreatedRunnable final : public WebSocketBaseRunnable
+{
+public:
+ WebSocketCreatedRunnable(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsAString& aURI,
+ const nsACString& aProtocols)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID)
+ , mURI(aURI)
+ , mProtocols(aProtocols)
+ {}
+
+private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override
+ {
+ DebugOnly<nsresult> rv =
+ aListener->WebSocketCreated(mWebSocketSerialID, mURI, mProtocols);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketCreated failed");
+ }
+
+ const nsString mURI;
+ const nsCString mProtocols;
+};
+
+class WebSocketOpenedRunnable final : public WebSocketBaseRunnable
+{
+public:
+ WebSocketOpenedRunnable(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsAString& aEffectiveURI,
+ const nsACString& aProtocols,
+ const nsACString& aExtensions)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID)
+ , mEffectiveURI(aEffectiveURI)
+ , mProtocols(aProtocols)
+ , mExtensions(aExtensions)
+ {}
+
+private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override
+ {
+ DebugOnly<nsresult> rv = aListener->WebSocketOpened(mWebSocketSerialID,
+ mEffectiveURI,
+ mProtocols,
+ mExtensions);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketOpened failed");
+ }
+
+ const nsString mEffectiveURI;
+ const nsCString mProtocols;
+ const nsCString mExtensions;
+};
+
+class WebSocketMessageAvailableRunnable final : public WebSocketBaseRunnable
+{
+public:
+ WebSocketMessageAvailableRunnable(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsACString& aData,
+ uint16_t aMessageType)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID)
+ , mData(aData)
+ , mMessageType(aMessageType)
+ {}
+
+private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override
+ {
+ DebugOnly<nsresult> rv =
+ aListener->WebSocketMessageAvailable(mWebSocketSerialID, mData,
+ mMessageType);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketMessageAvailable failed");
+ }
+
+ const nsCString mData;
+ uint16_t mMessageType;
+};
+
+class WebSocketClosedRunnable final : public WebSocketBaseRunnable
+{
+public:
+ WebSocketClosedRunnable(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ bool aWasClean,
+ uint16_t aCode,
+ const nsAString& aReason)
+ : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID)
+ , mWasClean(aWasClean)
+ , mCode(aCode)
+ , mReason(aReason)
+ {}
+
+private:
+ virtual void DoWork(nsIWebSocketEventListener* aListener) override
+ {
+ DebugOnly<nsresult> rv =
+ aListener->WebSocketClosed(mWebSocketSerialID, mWasClean, mCode, mReason);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketClosed failed");
+ }
+
+ bool mWasClean;
+ uint16_t mCode;
+ const nsString mReason;
+};
+
+/* static */ already_AddRefed<WebSocketEventService>
+WebSocketEventService::GetOrCreate()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gWebSocketEventService) {
+ gWebSocketEventService = new WebSocketEventService();
+ }
+
+ RefPtr<WebSocketEventService> service = gWebSocketEventService.get();
+ return service.forget();
+}
+
+NS_INTERFACE_MAP_BEGIN(WebSocketEventService)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketEventService)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIWebSocketEventService)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(WebSocketEventService)
+NS_IMPL_RELEASE(WebSocketEventService)
+
+WebSocketEventService::WebSocketEventService()
+ : mCountListeners(0)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "xpcom-shutdown", false);
+ obs->AddObserver(this, "inner-window-destroyed", false);
+ }
+}
+
+WebSocketEventService::~WebSocketEventService()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+void
+WebSocketEventService::WebSocketCreated(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsAString& aURI,
+ const nsACString& aProtocols)
+{
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketCreatedRunnable> runnable =
+ new WebSocketCreatedRunnable(aWebSocketSerialID, aInnerWindowID,
+ aURI, aProtocols);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void
+WebSocketEventService::WebSocketOpened(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsAString& aEffectiveURI,
+ const nsACString& aProtocols,
+ const nsACString& aExtensions)
+{
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketOpenedRunnable> runnable =
+ new WebSocketOpenedRunnable(aWebSocketSerialID, aInnerWindowID,
+ aEffectiveURI, aProtocols, aExtensions);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void
+WebSocketEventService::WebSocketMessageAvailable(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsACString& aData,
+ uint16_t aMessageType)
+{
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketMessageAvailableRunnable> runnable =
+ new WebSocketMessageAvailableRunnable(aWebSocketSerialID, aInnerWindowID,
+ aData, aMessageType);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void
+WebSocketEventService::WebSocketClosed(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ bool aWasClean,
+ uint16_t aCode,
+ const nsAString& aReason)
+{
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketClosedRunnable> runnable =
+ new WebSocketClosedRunnable(aWebSocketSerialID, aInnerWindowID,
+ aWasClean, aCode, aReason);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void
+WebSocketEventService::FrameReceived(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame)
+{
+ RefPtr<WebSocketFrame> frame(Move(aFrame));
+ MOZ_ASSERT(frame);
+
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketFrameRunnable> runnable =
+ new WebSocketFrameRunnable(aWebSocketSerialID, aInnerWindowID,
+ frame.forget(), false /* frameSent */);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+void
+WebSocketEventService::FrameSent(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame)
+{
+ RefPtr<WebSocketFrame> frame(Move(aFrame));
+ MOZ_ASSERT(frame);
+
+ // Let's continue only if we have some listeners.
+ if (!HasListeners()) {
+ return;
+ }
+
+ RefPtr<WebSocketFrameRunnable> runnable =
+ new WebSocketFrameRunnable(aWebSocketSerialID, aInnerWindowID,
+ frame.forget(), true /* frameSent */);
+
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+
+NS_IMETHODIMP
+WebSocketEventService::AddListener(uint64_t aInnerWindowID,
+ nsIWebSocketEventListener* aListener)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aListener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ++mCountListeners;
+
+ WindowListener* listener = mWindows.Get(aInnerWindowID);
+ if (!listener) {
+ listener = new WindowListener();
+
+ if (IsChildProcess()) {
+ PWebSocketEventListenerChild* actor =
+ gNeckoChild->SendPWebSocketEventListenerConstructor(aInnerWindowID);
+
+ listener->mActor = static_cast<WebSocketEventListenerChild*>(actor);
+ MOZ_ASSERT(listener->mActor);
+ }
+
+ mWindows.Put(aInnerWindowID, listener);
+ }
+
+ listener->mListeners.AppendElement(aListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventService::RemoveListener(uint64_t aInnerWindowID,
+ nsIWebSocketEventListener* aListener)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aListener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WindowListener* listener = mWindows.Get(aInnerWindowID);
+ if (!listener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!listener->mListeners.RemoveElement(aListener)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // The last listener for this window.
+ if (listener->mListeners.IsEmpty()) {
+ if (IsChildProcess()) {
+ ShutdownActorListener(listener);
+ }
+
+ mWindows.Remove(aInnerWindowID);
+ }
+
+ MOZ_ASSERT(mCountListeners);
+ --mCountListeners;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp(aTopic, "xpcom-shutdown")) {
+ Shutdown();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "inner-window-destroyed") && HasListeners()) {
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+ NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+
+ uint64_t innerID;
+ nsresult rv = wrapper->GetData(&innerID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ WindowListener* listener = mWindows.Get(innerID);
+ if (!listener) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mCountListeners >= listener->mListeners.Length());
+ mCountListeners -= listener->mListeners.Length();
+
+ if (IsChildProcess()) {
+ ShutdownActorListener(listener);
+ }
+
+ mWindows.Remove(innerID);
+ }
+
+ // This should not happen.
+ return NS_ERROR_FAILURE;
+}
+
+void
+WebSocketEventService::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (gWebSocketEventService) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(gWebSocketEventService, "xpcom-shutdown");
+ obs->RemoveObserver(gWebSocketEventService, "inner-window-destroyed");
+ }
+
+ mWindows.Clear();
+ gWebSocketEventService = nullptr;
+ }
+}
+
+bool
+WebSocketEventService::HasListeners() const
+{
+ return !!mCountListeners;
+}
+
+void
+WebSocketEventService::GetListeners(uint64_t aInnerWindowID,
+ WebSocketEventService::WindowListeners& aListeners) const
+{
+ aListeners.Clear();
+
+ WindowListener* listener = mWindows.Get(aInnerWindowID);
+ if (!listener) {
+ return;
+ }
+
+ aListeners.AppendElements(listener->mListeners);
+}
+
+void
+WebSocketEventService::ShutdownActorListener(WindowListener* aListener)
+{
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(aListener->mActor);
+ aListener->mActor->Close();
+ aListener->mActor = nullptr;
+}
+
+already_AddRefed<WebSocketFrame>
+WebSocketEventService::CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1,
+ bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit,
+ uint32_t aMask,
+ const nsCString& aPayload)
+{
+ if (!HasListeners()) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<WebSocketFrame>(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3,
+ aOpCode, aMaskBit, aMask, aPayload);
+}
+
+already_AddRefed<WebSocketFrame>
+WebSocketEventService::CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1,
+ bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit,
+ uint32_t aMask, uint8_t* aPayload,
+ uint32_t aPayloadLength)
+{
+ if (!HasListeners()) {
+ return nullptr;
+ }
+
+ nsAutoCString payloadStr;
+ if (NS_WARN_IF(!(payloadStr.Assign((const char*) aPayload, aPayloadLength,
+ mozilla::fallible)))) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<WebSocketFrame>(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3,
+ aOpCode, aMaskBit, aMask, payloadStr);
+}
+
+already_AddRefed<WebSocketFrame>
+WebSocketEventService::CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1,
+ bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit,
+ uint32_t aMask,
+ uint8_t* aPayloadInHdr,
+ uint32_t aPayloadInHdrLength,
+ uint8_t* aPayload,
+ uint32_t aPayloadLength)
+{
+ if (!HasListeners()) {
+ return nullptr;
+ }
+
+ uint32_t payloadLength = aPayloadLength + aPayloadInHdrLength;
+
+ nsAutoCString payload;
+ if (NS_WARN_IF(!payload.SetLength(payloadLength, fallible))) {
+ return nullptr;
+ }
+
+ char* payloadPtr = payload.BeginWriting();
+ if (aPayloadInHdrLength) {
+ memcpy(payloadPtr, aPayloadInHdr, aPayloadInHdrLength);
+ }
+
+ memcpy(payloadPtr + aPayloadInHdrLength, aPayload, aPayloadLength);
+
+ return MakeAndAddRef<WebSocketFrame>(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3,
+ aOpCode, aMaskBit, aMask, payload);
+}
+
+} // net namespace
+} // mozilla namespace
diff --git a/netwerk/protocol/websocket/WebSocketEventService.h b/netwerk/protocol/websocket/WebSocketEventService.h
new file mode 100644
index 000000000..f23267999
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventService.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketEventService_h
+#define mozilla_net_WebSocketEventService_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Atomics.h"
+#include "nsIWebSocketEventService.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsIObserver.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace net {
+
+class WebSocketFrame;
+class WebSocketEventListenerChild;
+
+class WebSocketEventService final : public nsIWebSocketEventService
+ , public nsIObserver
+{
+ friend class WebSocketBaseRunnable;
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIWEBSOCKETEVENTSERVICE
+
+ static already_AddRefed<WebSocketEventService> GetOrCreate();
+
+ void WebSocketCreated(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsAString& aURI,
+ const nsACString& aProtocols);
+
+ void WebSocketOpened(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsAString& aEffectiveURI,
+ const nsACString& aProtocols,
+ const nsACString& aExtensions);
+
+ void WebSocketMessageAvailable(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ const nsACString& aData,
+ uint16_t aMessageType);
+
+ void WebSocketClosed(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ bool aWasClean,
+ uint16_t aCode,
+ const nsAString& aReason);
+
+ void FrameReceived(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame);
+
+ void FrameSent(uint32_t aWebSocketSerialID,
+ uint64_t aInnerWindowID,
+ already_AddRefed<WebSocketFrame> aFrame);
+
+ already_AddRefed<WebSocketFrame>
+ CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
+ const nsCString& aPayload);
+
+ already_AddRefed<WebSocketFrame>
+ CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
+ uint8_t* aPayload, uint32_t aPayloadLength);
+
+ already_AddRefed<WebSocketFrame>
+ CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
+ uint8_t* aPayloadInHdr, uint32_t aPayloadInHdrLength,
+ uint8_t* aPayload, uint32_t aPayloadLength);
+
+private:
+ WebSocketEventService();
+ ~WebSocketEventService();
+
+ bool HasListeners() const;
+ void Shutdown();
+
+ typedef nsTArray<nsCOMPtr<nsIWebSocketEventListener>> WindowListeners;
+
+ struct WindowListener
+ {
+ WindowListeners mListeners;
+ RefPtr<WebSocketEventListenerChild> mActor;
+ };
+
+ void GetListeners(uint64_t aInnerWindowID,
+ WindowListeners& aListeners) const;
+
+ void ShutdownActorListener(WindowListener* aListener);
+
+ // Used only on the main-thread.
+ nsClassHashtable<nsUint64HashKey, WindowListener> mWindows;
+
+ Atomic<uint64_t> mCountListeners;
+};
+
+} // net namespace
+} // mozilla namespace
+
+/**
+ * Casting WebSocketEventService to nsISupports is ambiguous.
+ * This method handles that.
+ */
+inline nsISupports*
+ToSupports(mozilla::net::WebSocketEventService* p)
+{
+ return NS_ISUPPORTS_CAST(nsIWebSocketEventService*, p);
+}
+
+#endif // mozilla_net_WebSocketEventService_h
diff --git a/netwerk/protocol/websocket/WebSocketFrame.cpp b/netwerk/protocol/websocket/WebSocketFrame.cpp
new file mode 100644
index 000000000..b93729b3f
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrame.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebSocketFrame.h"
+
+#include "WebSocketChannel.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h" // for NS_IsMainThread
+#include "ipc/IPCMessageUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_INTERFACE_MAP_BEGIN(WebSocketFrame)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketFrame)
+ NS_INTERFACE_MAP_ENTRY(nsIWebSocketFrame)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(WebSocketFrame)
+NS_IMPL_RELEASE(WebSocketFrame)
+
+WebSocketFrame::WebSocketFrame(const WebSocketFrameData& aData)
+ : mData(aData)
+{}
+
+WebSocketFrame::WebSocketFrame(bool aFinBit, bool aRsvBit1, bool aRsvBit2,
+ bool aRsvBit3, uint8_t aOpCode, bool aMaskBit,
+ uint32_t aMask, const nsCString& aPayload)
+ : mData(PR_Now(), aFinBit, aRsvBit1, aRsvBit2, aRsvBit3, aOpCode, aMaskBit,
+ aMask, aPayload)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ mData.mTimeStamp = PR_Now();
+}
+
+WebSocketFrame::~WebSocketFrame()
+{}
+
+#define WSF_GETTER( method, value , type ) \
+NS_IMETHODIMP \
+WebSocketFrame::method(type* aValue) \
+{ \
+ MOZ_ASSERT(NS_IsMainThread()); \
+ if (!aValue) { \
+ return NS_ERROR_FAILURE; \
+ } \
+ *aValue = value; \
+ return NS_OK; \
+}
+
+WSF_GETTER(GetTimeStamp, mData.mTimeStamp, DOMHighResTimeStamp);
+WSF_GETTER(GetFinBit, mData.mFinBit, bool);
+WSF_GETTER(GetRsvBit1, mData.mRsvBit1, bool);
+WSF_GETTER(GetRsvBit2, mData.mRsvBit2, bool);
+WSF_GETTER(GetRsvBit3, mData.mRsvBit3, bool);
+WSF_GETTER(GetOpCode, mData.mOpCode, uint16_t);
+WSF_GETTER(GetMaskBit, mData.mMaskBit, bool);
+WSF_GETTER(GetMask, mData.mMask, uint32_t);
+
+#undef WSF_GETTER
+
+NS_IMETHODIMP
+WebSocketFrame::GetPayload(nsACString& aValue)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ aValue = mData.mPayload;
+ return NS_OK;
+}
+
+WebSocketFrameData::WebSocketFrameData()
+ : mTimeStamp(0)
+ , mFinBit(false)
+ , mRsvBit1(false)
+ , mRsvBit2(false)
+ , mRsvBit3(false)
+ , mMaskBit(false)
+ , mOpCode(0)
+ , mMask(0)
+{
+ MOZ_COUNT_CTOR(WebSocketFrameData);
+}
+
+WebSocketFrameData::WebSocketFrameData(DOMHighResTimeStamp aTimeStamp,
+ bool aFinBit, bool aRsvBit1,
+ bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit,
+ uint32_t aMask,
+ const nsCString& aPayload)
+ : mTimeStamp(aTimeStamp)
+ , mFinBit(aFinBit)
+ , mRsvBit1(aRsvBit1)
+ , mRsvBit2(aRsvBit2)
+ , mRsvBit3(aRsvBit3)
+ , mMaskBit(aMaskBit)
+ , mOpCode(aOpCode)
+ , mMask(aMask)
+ , mPayload(aPayload)
+{
+ MOZ_COUNT_CTOR(WebSocketFrameData);
+}
+
+WebSocketFrameData::WebSocketFrameData(const WebSocketFrameData& aData)
+ : mTimeStamp(aData.mTimeStamp)
+ , mFinBit(aData.mFinBit)
+ , mRsvBit1(aData.mRsvBit1)
+ , mRsvBit2(aData.mRsvBit2)
+ , mRsvBit3(aData.mRsvBit3)
+ , mMaskBit(aData.mMaskBit)
+ , mOpCode(aData.mOpCode)
+ , mMask(aData.mMask)
+ , mPayload(aData.mPayload)
+{
+ MOZ_COUNT_CTOR(WebSocketFrameData);
+}
+
+WebSocketFrameData::~WebSocketFrameData()
+{
+ MOZ_COUNT_DTOR(WebSocketFrameData);
+}
+
+void
+WebSocketFrameData::WriteIPCParams(IPC::Message* aMessage) const
+{
+ WriteParam(aMessage, mTimeStamp);
+ WriteParam(aMessage, mFinBit);
+ WriteParam(aMessage, mRsvBit1);
+ WriteParam(aMessage, mRsvBit2);
+ WriteParam(aMessage, mRsvBit3);
+ WriteParam(aMessage, mOpCode);
+ WriteParam(aMessage, mMaskBit);
+ WriteParam(aMessage, mMask);
+ WriteParam(aMessage, mPayload);
+}
+
+bool
+WebSocketFrameData::ReadIPCParams(const IPC::Message* aMessage,
+ PickleIterator* aIter)
+{
+ if (!ReadParam(aMessage, aIter, &mTimeStamp)) {
+ return false;
+ }
+
+#define ReadParamHelper(x) \
+ { \
+ bool bit; \
+ if (!ReadParam(aMessage, aIter, &bit)) { \
+ return false; \
+ } \
+ x = bit; \
+ }
+
+ ReadParamHelper(mFinBit);
+ ReadParamHelper(mRsvBit1);
+ ReadParamHelper(mRsvBit2);
+ ReadParamHelper(mRsvBit3);
+ ReadParamHelper(mMaskBit);
+
+#undef ReadParamHelper
+
+ return ReadParam(aMessage, aIter, &mOpCode) &&
+ ReadParam(aMessage, aIter, &mMask) &&
+ ReadParam(aMessage, aIter, &mPayload);
+}
+
+} // net namespace
+} // mozilla namespace
diff --git a/netwerk/protocol/websocket/WebSocketFrame.h b/netwerk/protocol/websocket/WebSocketFrame.h
new file mode 100644
index 000000000..28c98466e
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrame.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebSocketFrame_h
+#define mozilla_net_WebSocketFrame_h
+
+#include "nsAutoPtr.h"
+#include "nsIWebSocketEventService.h"
+#include "nsString.h"
+
+namespace IPC {
+class Message;
+}
+
+namespace mozilla {
+namespace net {
+
+class WebSocketFrameData final
+{
+public:
+ WebSocketFrameData();
+
+ explicit WebSocketFrameData(const WebSocketFrameData& aData);
+
+ WebSocketFrameData(DOMHighResTimeStamp aTimeStamp, bool aFinBit,
+ bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
+ const nsCString& aPayload);
+
+ ~WebSocketFrameData();
+
+ // For IPC serialization
+ void WriteIPCParams(IPC::Message* aMessage) const;
+ bool ReadIPCParams(const IPC::Message* aMessage, PickleIterator* aIter);
+
+ DOMHighResTimeStamp mTimeStamp;
+
+ bool mFinBit : 1;
+ bool mRsvBit1 : 1;
+ bool mRsvBit2 : 1;
+ bool mRsvBit3 : 1;
+ bool mMaskBit : 1;
+ uint8_t mOpCode;
+
+ uint32_t mMask;
+
+ nsCString mPayload;
+};
+
+class WebSocketFrame final : public nsIWebSocketFrame
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBSOCKETFRAME
+
+ explicit WebSocketFrame(const WebSocketFrameData& aData);
+
+ WebSocketFrame(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
+ const nsCString& aPayload);
+
+ const WebSocketFrameData& Data() const
+ {
+ return mData;
+ }
+
+private:
+ ~WebSocketFrame();
+
+ WebSocketFrameData mData;
+};
+
+} // net namespace
+} // mozilla namespace
+
+#endif // mozilla_net_WebSocketFrame_h
diff --git a/netwerk/protocol/websocket/WebSocketLog.h b/netwerk/protocol/websocket/WebSocketLog.h
new file mode 100644
index 000000000..6bfe911c4
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketLog.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebSocketLog_h
+#define WebSocketLog_h
+
+#include "base/basictypes.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla {
+namespace net {
+extern LazyLogModule webSocketLog;
+}
+}
+
+#undef LOG
+#define LOG(args) MOZ_LOG(mozilla::net::webSocketLog, mozilla::LogLevel::Debug, args)
+
+#endif
diff --git a/netwerk/protocol/websocket/moz.build b/netwerk/protocol/websocket/moz.build
new file mode 100644
index 000000000..49320fe31
--- /dev/null
+++ b/netwerk/protocol/websocket/moz.build
@@ -0,0 +1,56 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsITransportProvider.idl',
+ 'nsIWebSocketChannel.idl',
+ 'nsIWebSocketEventService.idl',
+ 'nsIWebSocketListener.idl',
+]
+
+XPIDL_MODULE = 'necko_websocket'
+
+EXPORTS.mozilla.net += [
+ 'BaseWebSocketChannel.h',
+ 'IPCTransportProvider.h',
+ 'WebSocketChannel.h',
+ 'WebSocketChannelChild.h',
+ 'WebSocketChannelParent.h',
+ 'WebSocketEventListenerChild.h',
+ 'WebSocketEventListenerParent.h',
+ 'WebSocketEventService.h',
+ 'WebSocketFrame.h',
+]
+
+UNIFIED_SOURCES += [
+ 'BaseWebSocketChannel.cpp',
+ 'IPCTransportProvider.cpp',
+ 'WebSocketChannel.cpp',
+ 'WebSocketChannelChild.cpp',
+ 'WebSocketChannelParent.cpp',
+ 'WebSocketEventListenerChild.cpp',
+ 'WebSocketEventListenerParent.cpp',
+ 'WebSocketEventService.cpp',
+ 'WebSocketFrame.cpp',
+]
+
+IPDL_SOURCES += [
+ 'PTransportProvider.ipdl',
+ 'PWebSocket.ipdl',
+ 'PWebSocketEventListener.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/netwerk/base',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/protocol/websocket/nsITransportProvider.idl b/netwerk/protocol/websocket/nsITransportProvider.idl
new file mode 100644
index 000000000..e1723da3b
--- /dev/null
+++ b/netwerk/protocol/websocket/nsITransportProvider.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface nsIHttpUpgradeListener;
+
+#include "nsISupports.idl"
+
+%{C++
+namespace mozilla {
+namespace net {
+class PTransportProviderChild;
+}
+}
+%}
+
+[ptr] native PTransportProviderChild(mozilla::net::PTransportProviderChild);
+
+/**
+ * An interface which can be used to asynchronously request a nsITransport
+ * together with the input and output streams that go together with it.
+ */
+[scriptable, uuid(6fcec704-cfd2-46ef-a394-a64d5cb1475c)]
+interface nsITransportProvider : nsISupports
+{
+ // This must not be called in a child process since transport
+ // objects are not accessible there. Call getIPCChild instead.
+ void setListener(in nsIHttpUpgradeListener listener);
+
+ // This must be implemented by nsITransportProvider objects running
+ // in the child process. It must return null when called in the parent
+ // process.
+ [noscript] PTransportProviderChild getIPCChild();
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketChannel.idl b/netwerk/protocol/websocket/nsIWebSocketChannel.idl
new file mode 100644
index 000000000..0ffd3f60b
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketChannel.idl
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface nsIURI;
+interface nsIInterfaceRequestor;
+interface nsILoadGroup;
+interface nsIWebSocketListener;
+interface nsIInputStream;
+interface nsILoadInfo;
+interface nsIDOMNode;
+interface nsIPrincipal;
+interface nsITransportProvider;
+
+#include "nsISupports.idl"
+
+/**
+ * Low-level websocket API: handles network protocol.
+ *
+ * This is primarly intended for use by the higher-level nsIWebSocket.idl.
+ * We are also making it scriptable for now, but this may change once we have
+ * WebSockets for Workers.
+ */
+[scriptable, uuid(ce71d028-322a-4105-a947-a894689b52bf)]
+interface nsIWebSocketChannel : nsISupports
+{
+ /**
+ * The original URI used to construct the protocol connection. This is used
+ * in the case of a redirect or URI "resolution" (e.g. resolving a
+ * resource: URI to a file: URI) so that the original pre-redirect
+ * URI can still be obtained. This is never null.
+ */
+ readonly attribute nsIURI originalURI;
+
+ /**
+ * The readonly URI corresponding to the protocol connection after any
+ * redirections are completed.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * The notification callbacks for authorization, etc..
+ */
+ attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Transport-level security information (if any)
+ */
+ readonly attribute nsISupports securityInfo;
+
+ /**
+ * The load group of of the websocket
+ */
+ attribute nsILoadGroup loadGroup;
+
+ /**
+ * The load info of the websocket
+ */
+ attribute nsILoadInfo loadInfo;
+
+ /**
+ * Sec-Websocket-Protocol value
+ */
+ attribute ACString protocol;
+
+ /**
+ * Sec-Websocket-Extensions response header value
+ */
+ readonly attribute ACString extensions;
+
+ /**
+ * Init the WebSocketChannel with LoadInfo arguments.
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aSecurityFlags
+ * @param aContentPolicyType
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * @return reference to the new nsIChannel object
+ *
+ * Keep in mind that URIs coming from a webpage should *never* use the
+ * systemPrincipal as the loadingPrincipal.
+ *
+ * Please note, if you provide both a loadingNode and a loadingPrincipal,
+ * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+ * But less error prone is to just supply a loadingNode.
+ */
+ void initLoadInfo(in nsIDOMNode aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in unsigned long aContentPolicyType);
+
+ /**
+ * Asynchronously open the websocket connection. Received messages are fed
+ * to the socket listener as they arrive. The socket listener's methods
+ * are called on the thread that calls asyncOpen and are not called until
+ * after asyncOpen returns. If asyncOpen returns successfully, the
+ * protocol implementation promises to call at least onStop on the listener.
+ *
+ * NOTE: Implementations should throw NS_ERROR_ALREADY_OPENED if the
+ * websocket connection is reopened.
+ *
+ * @param aURI the uri of the websocket protocol - may be redirected
+ * @param aOrigin the uri of the originating resource
+ * @param aInnerWindowID the inner window ID
+ * @param aListener the nsIWebSocketListener implementation
+ * @param aContext an opaque parameter forwarded to aListener's methods
+ */
+ void asyncOpen(in nsIURI aURI,
+ in ACString aOrigin,
+ in unsigned long long aInnerWindowID,
+ in nsIWebSocketListener aListener,
+ in nsISupports aContext);
+
+ /*
+ * Close the websocket connection for writing - no more calls to sendMsg
+ * or sendBinaryMsg should be made after calling this. The listener object
+ * may receive more messages if a server close has not yet been received.
+ *
+ * @param aCode the websocket closing handshake close code. Set to 0 if
+ * you are not providing a code.
+ * @param aReason the websocket closing handshake close reason
+ */
+ void close(in unsigned short aCode, in AUTF8String aReason);
+
+ // section 7.4.1 defines these close codes
+ const unsigned short CLOSE_NORMAL = 1000;
+ const unsigned short CLOSE_GOING_AWAY = 1001;
+ const unsigned short CLOSE_PROTOCOL_ERROR = 1002;
+ const unsigned short CLOSE_UNSUPPORTED_DATATYPE = 1003;
+ // code 1004 is reserved
+ const unsigned short CLOSE_NO_STATUS = 1005;
+ const unsigned short CLOSE_ABNORMAL = 1006;
+ const unsigned short CLOSE_INVALID_PAYLOAD = 1007;
+ const unsigned short CLOSE_POLICY_VIOLATION = 1008;
+ const unsigned short CLOSE_TOO_LARGE = 1009;
+ const unsigned short CLOSE_EXTENSION_MISSING = 1010;
+ // Initially used just for server-side internal errors: adopted later for
+ // client-side errors too (not clear if will make into spec: see
+ // http://www.ietf.org/mail-archive/web/hybi/current/msg09372.html
+ const unsigned short CLOSE_INTERNAL_ERROR = 1011;
+ // MUST NOT be set as a status code in Close control frame by an endpoint:
+ // To be used if TLS handshake failed (ex: server certificate unverifiable)
+ const unsigned short CLOSE_TLS_FAILED = 1015;
+
+ /**
+ * Use to send text message down the connection to WebSocket peer.
+ *
+ * @param aMsg the utf8 string to send
+ */
+ void sendMsg(in AUTF8String aMsg);
+
+ /**
+ * Use to send binary message down the connection to WebSocket peer.
+ *
+ * @param aMsg the data to send
+ */
+ void sendBinaryMsg(in ACString aMsg);
+
+ /**
+ * Use to send a binary stream (Blob) to Websocket peer.
+ *
+ * @param aStream The input stream to be sent.
+ */
+ void sendBinaryStream(in nsIInputStream aStream,
+ in unsigned long length);
+
+ /**
+ * This value determines how often (in seconds) websocket keepalive
+ * pings are sent. If set to 0 (the default), no pings are ever sent.
+ *
+ * This value can currently only be set before asyncOpen is called, else
+ * NS_ERROR_IN_PROGRESS is thrown.
+ *
+ * Be careful using this setting: ping traffic can consume lots of power and
+ * bandwidth over time.
+ */
+ attribute unsigned long pingInterval;
+
+ /**
+ * This value determines how long (in seconds) the websocket waits for
+ * the server to reply to a ping that has been sent before considering the
+ * connection broken.
+ *
+ * This value can currently only be set before asyncOpen is called, else
+ * NS_ERROR_IN_PROGRESS is thrown.
+ */
+ attribute unsigned long pingTimeout;
+
+ /**
+ * Unique ID for this channel. It's not readonly because when the channel is
+ * created via IPC, the serial number is received from the child process.
+ */
+ attribute unsigned long serial;
+
+ /**
+ * Set a nsITransportProvider and negotated extensions to be used by this
+ * channel. Calling this function also means that this channel will
+ * implement the server-side part of a websocket connection rather than the
+ * client-side part.
+ */
+ void setServerParameters(in nsITransportProvider aProvider,
+ in ACString aNegotiatedExtensions);
+
+%{C++
+ inline uint32_t Serial()
+ {
+ uint32_t serial;
+ nsresult rv = GetSerial(&serial);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return 0;
+ }
+ return serial;
+ }
+%}
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketEventService.idl b/netwerk/protocol/websocket/nsIWebSocketEventService.idl
new file mode 100644
index 000000000..c2986dc2f
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketEventService.idl
@@ -0,0 +1,79 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "domstubs.idl"
+#include "nsISupports.idl"
+
+[scriptable, builtinclass, uuid(6714a6be-2265-4f73-a988-d78a12416037)]
+interface nsIWebSocketFrame : nsISupports
+{
+ readonly attribute DOMHighResTimeStamp timeStamp;
+
+ readonly attribute boolean finBit;
+
+ readonly attribute boolean rsvBit1;
+ readonly attribute boolean rsvBit2;
+ readonly attribute boolean rsvBit3;
+
+ readonly attribute unsigned short opCode;
+
+ readonly attribute boolean maskBit;
+
+ readonly attribute unsigned long mask;
+
+ readonly attribute ACString payload;
+
+ // Non-Control opCode values:
+ const unsigned short OPCODE_CONTINUATION = 0x0;
+ const unsigned short OPCODE_TEXT = 0x1;
+ const unsigned short OPCODE_BINARY = 0x2;
+
+ // Control opCode values:
+ const unsigned short OPCODE_CLOSE = 0x8;
+ const unsigned short OPCODE_PING = 0x9;
+ const unsigned short OPCODE_PONG = 0xA;
+};
+
+[scriptable, uuid(e7c005ab-e694-489b-b741-96db43ffb16f)]
+interface nsIWebSocketEventListener : nsISupports
+{
+ void webSocketCreated(in unsigned long aWebSocketSerialID,
+ in AString aURI,
+ in ACString aProtocols);
+
+ void webSocketOpened(in unsigned long aWebSocketSerialID,
+ in AString aEffectiveURI,
+ in ACString aProtocols,
+ in ACString aExtensions);
+
+ const unsigned short TYPE_STRING = 0x0;
+ const unsigned short TYPE_BLOB = 0x1;
+ const unsigned short TYPE_ARRAYBUFFER = 0x2;
+
+ void webSocketMessageAvailable(in unsigned long aWebSocketSerialID,
+ in ACString aMessage,
+ in unsigned short aType);
+
+ void webSocketClosed(in unsigned long aWebSocketSerialID,
+ in boolean aWasClean,
+ in unsigned short aCode,
+ in AString aReason);
+
+ void frameReceived(in unsigned long aWebSocketSerialID,
+ in nsIWebSocketFrame aFrame);
+
+ void frameSent(in unsigned long aWebSocketSerialID,
+ in nsIWebSocketFrame aFrame);
+};
+
+[scriptable, builtinclass, uuid(b89d1b90-2cf3-4d8f-ac21-5aedfb25c760)]
+interface nsIWebSocketEventService : nsISupports
+{
+ void addListener(in unsigned long long aInnerWindowID,
+ in nsIWebSocketEventListener aListener);
+
+ void removeListener(in unsigned long long aInnerWindowID,
+ in nsIWebSocketEventListener aListener);
+};
diff --git a/netwerk/protocol/websocket/nsIWebSocketListener.idl b/netwerk/protocol/websocket/nsIWebSocketListener.idl
new file mode 100644
index 000000000..ac2d42f76
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketListener.idl
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIWebSocketListener: passed to nsIWebSocketChannel::AsyncOpen. Receives
+ * websocket traffic events as they arrive.
+ */
+[scriptable, uuid(d74c96b2-65b3-4e39-9e39-c577de5d7a73)]
+interface nsIWebSocketListener : nsISupports
+{
+ /**
+ * Called to signify the establishment of the message stream.
+ *
+ * Unlike most other networking channels (which use nsIRequestObserver
+ * instead of this class), we do not guarantee that OnStart is always
+ * called: OnStop is called without calling this function if errors occur
+ * during connection setup. If the websocket connection is successful,
+ * OnStart will be called before any other calls to this API.
+ *
+ * @param aContext user defined context
+ */
+ void onStart(in nsISupports aContext);
+
+ /**
+ * Called to signify the completion of the message stream.
+ * OnStop is the final notification the listener will receive and it
+ * completes the WebSocket connection: after it returns the
+ * nsIWebSocketChannel will release its reference to the listener.
+ *
+ * Note: this event can be received in error cases even if
+ * nsIWebSocketChannel::Close() has not been called.
+ *
+ * @param aContext user defined context
+ * @param aStatusCode reason for stopping (NS_OK if completed successfully)
+ */
+ void onStop(in nsISupports aContext,
+ in nsresult aStatusCode);
+
+ /**
+ * Called to deliver text message.
+ *
+ * @param aContext user defined context
+ * @param aMsg the message data
+ */
+ void onMessageAvailable(in nsISupports aContext,
+ in AUTF8String aMsg);
+
+ /**
+ * Called to deliver binary message.
+ *
+ * @param aContext user defined context
+ * @param aMsg the message data
+ */
+ void onBinaryMessageAvailable(in nsISupports aContext,
+ in ACString aMsg);
+
+ /**
+ * Called to acknowledge message sent via sendMsg() or sendBinaryMsg.
+ *
+ * @param aContext user defined context
+ * @param aSize number of bytes placed in OS send buffer
+ */
+ void onAcknowledge(in nsISupports aContext, in uint32_t aSize);
+
+ /**
+ * Called to inform receipt of WebSocket Close message from server.
+ * In the case of errors onStop() can be called without ever
+ * receiving server close.
+ *
+ * No additional messages through onMessageAvailable(),
+ * onBinaryMessageAvailable() or onAcknowledge() will be delievered
+ * to the listener after onServerClose(), though outgoing messages can still
+ * be sent through the nsIWebSocketChannel connection.
+ *
+ * @param aContext user defined context
+ * @param aCode the websocket closing handshake close code.
+ * @param aReason the websocket closing handshake close reason
+
+ */
+ void onServerClose(in nsISupports aContext, in unsigned short aCode,
+ in AUTF8String aReason);
+
+};
+
+
diff --git a/netwerk/protocol/wyciwyg/PWyciwygChannel.ipdl b/netwerk/protocol/wyciwyg/PWyciwygChannel.ipdl
new file mode 100644
index 000000000..16f5848fb
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/PWyciwygChannel.ipdl
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include protocol PBrowser;
+include URIParams;
+include PBackgroundSharedTypes;
+include PBrowserOrId;
+
+using class IPC::SerializedLoadContext from "SerializedLoadContext.h";
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+protocol PWyciwygChannel
+{
+ manager PNecko;
+
+parent:
+ async __delete__();
+
+ async Init(URIParams uri,
+ PrincipalInfo requestingPrincipalInfo,
+ PrincipalInfo triggeringPrincipalInfo,
+ PrincipalInfo principalToInheritInfo,
+ uint32_t securityFlags,
+ uint32_t contentPolicyType);
+ async AsyncOpen(URIParams originalURI,
+ uint32_t loadFlags,
+ SerializedLoadContext loadContext,
+ PBrowserOrId browser);
+ async AppData(SerializedLoadContext loadContext, PBrowserOrId browser);
+
+ // methods corresponding to those of nsIWyciwygChannel
+ async WriteToCacheEntry(nsString data);
+ async CloseCacheEntry(nsresult reason);
+ async SetCharsetAndSource(int32_t source, nsCString charset);
+ async SetSecurityInfo(nsCString securityInfo);
+ async Cancel(nsresult status);
+
+child:
+ async OnStartRequest(nsresult statusCode,
+ int64_t contentLength,
+ int32_t source,
+ nsCString charset,
+ nsCString securityInfo);
+
+ async OnDataAvailable(nsCString data,
+ uint64_t offset);
+
+ async OnStopRequest(nsresult statusCode);
+
+ async CancelEarly(nsresult statusCode);
+};
+
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp b/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
new file mode 100644
index 000000000..2a794df3b
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
@@ -0,0 +1,764 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWyciwyg.h"
+
+#include "base/compiler_specific.h"
+
+#include "mozilla/net/ChannelEventQueue.h"
+#include "WyciwygChannelChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/ContentChild.h"
+
+#include "nsCharsetSource.h"
+#include "nsContentUtils.h"
+#include "nsStringStream.h"
+#include "nsNetUtil.h"
+#include "nsISerializable.h"
+#include "nsSerializationHelper.h"
+#include "nsIProgressEventSink.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "nsProxyRelease.h"
+#include "nsContentSecurityManager.h"
+
+using namespace mozilla::ipc;
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(WyciwygChannelChild,
+ nsIRequest,
+ nsIChannel,
+ nsIWyciwygChannel,
+ nsIPrivateBrowsingChannel)
+
+
+WyciwygChannelChild::WyciwygChannelChild()
+ : mStatus(NS_OK)
+ , mIsPending(false)
+ , mCanceled(false)
+ , mLoadFlags(LOAD_NORMAL)
+ , mContentLength(-1)
+ , mCharsetSource(kCharsetUninitialized)
+ , mState(WCC_NEW)
+ , mIPCOpen(false)
+ , mSentAppData(false)
+{
+ LOG(("Creating WyciwygChannelChild @%x\n", this));
+ mEventQ = new ChannelEventQueue(NS_ISUPPORTS_CAST(nsIWyciwygChannel*, this));
+}
+
+WyciwygChannelChild::~WyciwygChannelChild()
+{
+ LOG(("Destroying WyciwygChannelChild @%x\n", this));
+ if (mLoadInfo) {
+ NS_ReleaseOnMainThread(mLoadInfo.forget());
+ }
+}
+
+void
+WyciwygChannelChild::AddIPDLReference()
+{
+ MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void
+WyciwygChannelChild::ReleaseIPDLReference()
+{
+ MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference");
+ mIPCOpen = false;
+ Release();
+}
+
+nsresult
+WyciwygChannelChild::Init(nsIURI* uri)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+
+ mState = WCC_INIT;
+
+ mURI = uri;
+ mOriginalURI = uri;
+
+ URIParams serializedUri;
+ SerializeURI(uri, serializedUri);
+
+ // propagate loadInfo
+ mozilla::ipc::PrincipalInfo requestingPrincipalInfo;
+ mozilla::ipc::PrincipalInfo triggeringPrincipalInfo;
+ mozilla::ipc::PrincipalInfo principalToInheritInfo;
+ uint32_t securityFlags;
+ uint32_t policyType;
+ if (mLoadInfo) {
+ mozilla::ipc::PrincipalToPrincipalInfo(mLoadInfo->LoadingPrincipal(),
+ &requestingPrincipalInfo);
+ mozilla::ipc::PrincipalToPrincipalInfo(mLoadInfo->TriggeringPrincipal(),
+ &triggeringPrincipalInfo);
+ mozilla::ipc::PrincipalToPrincipalInfo(mLoadInfo->PrincipalToInherit(),
+ &principalToInheritInfo);
+ securityFlags = mLoadInfo->GetSecurityFlags();
+ policyType = mLoadInfo->InternalContentPolicyType();
+ }
+ else {
+ // use default values if no loadInfo is provided
+ mozilla::ipc::PrincipalToPrincipalInfo(nsContentUtils::GetSystemPrincipal(),
+ &requestingPrincipalInfo);
+ mozilla::ipc::PrincipalToPrincipalInfo(nsContentUtils::GetSystemPrincipal(),
+ &triggeringPrincipalInfo);
+ mozilla::ipc::PrincipalToPrincipalInfo(nsContentUtils::GetSystemPrincipal(),
+ &principalToInheritInfo);
+ securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
+ policyType = nsIContentPolicy::TYPE_OTHER;
+ }
+
+ SendInit(serializedUri,
+ requestingPrincipalInfo,
+ triggeringPrincipalInfo,
+ principalToInheritInfo,
+ securityFlags,
+ policyType);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WyciwygChannelChild::PWyciwygChannelChild
+//-----------------------------------------------------------------------------
+
+class WyciwygStartRequestEvent : public ChannelEvent
+{
+public:
+ WyciwygStartRequestEvent(WyciwygChannelChild* child,
+ const nsresult& statusCode,
+ const int64_t& contentLength,
+ const int32_t& source,
+ const nsCString& charset,
+ const nsCString& securityInfo)
+ : mChild(child), mStatusCode(statusCode), mContentLength(contentLength),
+ mSource(source), mCharset(charset), mSecurityInfo(securityInfo) {}
+ void Run() { mChild->OnStartRequest(mStatusCode, mContentLength, mSource,
+ mCharset, mSecurityInfo); }
+private:
+ WyciwygChannelChild* mChild;
+ nsresult mStatusCode;
+ int64_t mContentLength;
+ int32_t mSource;
+ nsCString mCharset;
+ nsCString mSecurityInfo;
+};
+
+bool
+WyciwygChannelChild::RecvOnStartRequest(const nsresult& statusCode,
+ const int64_t& contentLength,
+ const int32_t& source,
+ const nsCString& charset,
+ const nsCString& securityInfo)
+{
+ mEventQ->RunOrEnqueue(new WyciwygStartRequestEvent(this, statusCode,
+ contentLength, source,
+ charset, securityInfo));
+ return true;
+}
+
+void
+WyciwygChannelChild::OnStartRequest(const nsresult& statusCode,
+ const int64_t& contentLength,
+ const int32_t& source,
+ const nsCString& charset,
+ const nsCString& securityInfo)
+{
+ LOG(("WyciwygChannelChild::RecvOnStartRequest [this=%p]\n", this));
+
+ mState = WCC_ONSTART;
+
+ mStatus = statusCode;
+ mContentLength = contentLength;
+ mCharsetSource = source;
+ mCharset = charset;
+
+ if (!securityInfo.IsEmpty()) {
+ NS_DeserializeObject(securityInfo, getter_AddRefs(mSecurityInfo));
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ nsresult rv = mListener->OnStartRequest(this, mListenerContext);
+ if (NS_FAILED(rv))
+ Cancel(rv);
+}
+
+class WyciwygDataAvailableEvent : public ChannelEvent
+{
+public:
+ WyciwygDataAvailableEvent(WyciwygChannelChild* child,
+ const nsCString& data,
+ const uint64_t& offset)
+ : mChild(child), mData(data), mOffset(offset) {}
+ void Run() { mChild->OnDataAvailable(mData, mOffset); }
+private:
+ WyciwygChannelChild* mChild;
+ nsCString mData;
+ uint64_t mOffset;
+};
+
+bool
+WyciwygChannelChild::RecvOnDataAvailable(const nsCString& data,
+ const uint64_t& offset)
+{
+ mEventQ->RunOrEnqueue(new WyciwygDataAvailableEvent(this, data, offset));
+ return true;
+}
+
+void
+WyciwygChannelChild::OnDataAvailable(const nsCString& data,
+ const uint64_t& offset)
+{
+ LOG(("WyciwygChannelChild::RecvOnDataAvailable [this=%p]\n", this));
+
+ if (mCanceled)
+ return;
+
+ mState = WCC_ONDATA;
+
+ // NOTE: the OnDataAvailable contract requires the client to read all the data
+ // in the inputstream. This code relies on that ('data' will go away after
+ // this function). Apparently the previous, non-e10s behavior was to actually
+ // support only reading part of the data, allowing later calls to read the
+ // rest.
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream),
+ data.get(),
+ data.Length(),
+ NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return;
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ rv = mListener->OnDataAvailable(this, mListenerContext,
+ stringStream, offset, data.Length());
+ if (NS_FAILED(rv))
+ Cancel(rv);
+
+ if (mProgressSink && NS_SUCCEEDED(rv)) {
+ mProgressSink->OnProgress(this, nullptr, offset + data.Length(),
+ mContentLength);
+ }
+}
+
+class WyciwygStopRequestEvent : public ChannelEvent
+{
+public:
+ WyciwygStopRequestEvent(WyciwygChannelChild* child,
+ const nsresult& statusCode)
+ : mChild(child), mStatusCode(statusCode) {}
+ void Run() { mChild->OnStopRequest(mStatusCode); }
+private:
+ WyciwygChannelChild* mChild;
+ nsresult mStatusCode;
+};
+
+bool
+WyciwygChannelChild::RecvOnStopRequest(const nsresult& statusCode)
+{
+ mEventQ->RunOrEnqueue(new WyciwygStopRequestEvent(this, statusCode));
+ return true;
+}
+
+void
+WyciwygChannelChild::OnStopRequest(const nsresult& statusCode)
+{
+ LOG(("WyciwygChannelChild::RecvOnStopRequest [this=%p status=%u]\n",
+ this, statusCode));
+
+ { // We need to ensure that all IPDL message dispatching occurs
+ // before we delete the protocol below
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ mState = WCC_ONSTOP;
+
+ mIsPending = false;
+
+ if (!mCanceled)
+ mStatus = statusCode;
+
+ mListener->OnStopRequest(this, mListenerContext, statusCode);
+
+ mListener = nullptr;
+ mListenerContext = nullptr;
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ mCallbacks = nullptr;
+ mProgressSink = nullptr;
+ }
+
+ if (mIPCOpen)
+ PWyciwygChannelChild::Send__delete__(this);
+}
+
+class WyciwygCancelEvent : public ChannelEvent
+{
+ public:
+ WyciwygCancelEvent(WyciwygChannelChild* child, const nsresult& status)
+ : mChild(child)
+ , mStatus(status) {}
+
+ void Run() { mChild->CancelEarly(mStatus); }
+ private:
+ WyciwygChannelChild* mChild;
+ nsresult mStatus;
+};
+
+bool
+WyciwygChannelChild::RecvCancelEarly(const nsresult& statusCode)
+{
+ mEventQ->RunOrEnqueue(new WyciwygCancelEvent(this, statusCode));
+ return true;
+}
+
+void WyciwygChannelChild::CancelEarly(const nsresult& statusCode)
+{
+ LOG(("WyciwygChannelChild::CancelEarly [this=%p]\n", this));
+
+ if (mCanceled)
+ return;
+
+ mCanceled = true;
+ mStatus = statusCode;
+
+ mIsPending = false;
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ if (mListener) {
+ mListener->OnStartRequest(this, mListenerContext);
+ mListener->OnStopRequest(this, mListenerContext, mStatus);
+ }
+ mListener = nullptr;
+ mListenerContext = nullptr;
+
+ if (mIPCOpen)
+ PWyciwygChannelChild::Send__delete__(this);
+}
+
+//-----------------------------------------------------------------------------
+// nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetName(nsACString & aName)
+{
+ return mURI->GetSpec(aName);
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::IsPending(bool *aIsPending)
+{
+ *aIsPending = mIsPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetStatus(nsresult *aStatus)
+{
+ *aStatus = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::Cancel(nsresult aStatus)
+{
+ if (mCanceled)
+ return NS_OK;
+
+ mCanceled = true;
+ mStatus = aStatus;
+ if (mIPCOpen)
+ SendCancel(aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::Suspend()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::Resume()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetLoadGroup(nsILoadGroup * *aLoadGroup)
+{
+ *aLoadGroup = mLoadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetLoadGroup(nsILoadGroup * aLoadGroup)
+{
+ if (!CanSetLoadGroup(aLoadGroup)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoadGroup = aLoadGroup;
+ NS_QueryNotificationCallbacks(mCallbacks,
+ mLoadGroup,
+ NS_GET_IID(nsIProgressEventSink),
+ getter_AddRefs(mProgressSink));
+
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetOriginalURI(nsIURI * *aOriginalURI)
+{
+ *aOriginalURI = mOriginalURI;
+ NS_ADDREF(*aOriginalURI);
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetOriginalURI(nsIURI * aOriginalURI)
+{
+ NS_ENSURE_TRUE(mState == WCC_INIT, NS_ERROR_UNEXPECTED);
+
+ NS_ENSURE_ARG_POINTER(aOriginalURI);
+ mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetURI(nsIURI * *aURI)
+{
+ *aURI = mURI;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetOwner(nsISupports * *aOwner)
+{
+ NS_IF_ADDREF(*aOwner = mOwner);
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetOwner(nsISupports * aOwner)
+{
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetLoadInfo(nsILoadInfo **aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetNotificationCallbacks(nsIInterfaceRequestor * *aCallbacks)
+{
+ *aCallbacks = mCallbacks;
+ NS_IF_ADDREF(*aCallbacks);
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetNotificationCallbacks(nsIInterfaceRequestor * aCallbacks)
+{
+ if (!CanSetCallbacks(aCallbacks)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCallbacks = aCallbacks;
+ NS_QueryNotificationCallbacks(mCallbacks,
+ mLoadGroup,
+ NS_GET_IID(nsIProgressEventSink),
+ getter_AddRefs(mProgressSink));
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentType(nsACString & aContentType)
+{
+ aContentType.AssignLiteral(WYCIWYG_TYPE);
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetContentType(const nsACString & aContentType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentCharset(nsACString & aContentCharset)
+{
+ aContentCharset.AssignLiteral("UTF-16");
+ return NS_OK;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetContentCharset(const nsACString & aContentCharset)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentLength(int64_t *aContentLength)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+WyciwygChannelChild::SetContentLength(int64_t aContentLength)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::Open(nsIInputStream **_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+static mozilla::dom::TabChild*
+GetTabChild(nsIChannel* aChannel)
+{
+ nsCOMPtr<nsITabChild> iTabChild;
+ NS_QueryNotificationCallbacks(aChannel, iTabChild);
+ return iTabChild ? static_cast<mozilla::dom::TabChild*>(iTabChild.get()) : nullptr;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ LOG(("WyciwygChannelChild::AsyncOpen [this=%p]\n", this));
+
+ // The only places creating wyciwyg: channels should be
+ // HTMLDocument::OpenCommon and session history. Both should be setting an
+ // owner or loadinfo.
+ NS_PRECONDITION(mOwner || mLoadInfo, "Must have a principal");
+ NS_ENSURE_STATE(mOwner || mLoadInfo);
+
+ NS_ENSURE_ARG_POINTER(aListener);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+
+ mListener = aListener;
+ mListenerContext = aContext;
+ mIsPending = true;
+
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ URIParams originalURI;
+ SerializeURI(mOriginalURI, originalURI);
+
+ mozilla::dom::TabChild* tabChild = GetTabChild(this);
+ if (MissingRequiredTabChild(tabChild, "wyciwyg")) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ PBrowserOrId browser = static_cast<ContentChild*>(Manager()->Manager())
+ ->GetBrowserOrId(tabChild);
+
+ SendAsyncOpen(originalURI, mLoadFlags, IPC::SerializedLoadContext(this), browser);
+
+ mSentAppData = true;
+ mState = WCC_OPENED;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+//-----------------------------------------------------------------------------
+// nsIWyciwygChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WyciwygChannelChild::WriteToCacheEntry(const nsAString & aData)
+{
+ NS_ENSURE_TRUE((mState == WCC_INIT) ||
+ (mState == WCC_ONWRITE), NS_ERROR_UNEXPECTED);
+
+ if (!mSentAppData) {
+ mozilla::dom::TabChild* tabChild = GetTabChild(this);
+
+ PBrowserOrId browser = static_cast<ContentChild*>(Manager()->Manager())
+ ->GetBrowserOrId(tabChild);
+
+ SendAppData(IPC::SerializedLoadContext(this), browser);
+ mSentAppData = true;
+ }
+
+ SendWriteToCacheEntry(PromiseFlatString(aData));
+ mState = WCC_ONWRITE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::CloseCacheEntry(nsresult reason)
+{
+ NS_ENSURE_TRUE(mState == WCC_ONWRITE, NS_ERROR_UNEXPECTED);
+
+ SendCloseCacheEntry(reason);
+ mState = WCC_ONCLOSED;
+
+ if (mIPCOpen)
+ PWyciwygChannelChild::Send__delete__(this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::SetSecurityInfo(nsISupports *aSecurityInfo)
+{
+ mSecurityInfo = aSecurityInfo;
+
+ if (mSecurityInfo) {
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(mSecurityInfo);
+ if (serializable) {
+ nsCString secInfoStr;
+ NS_SerializeToString(serializable, secInfoStr);
+ SendSetSecurityInfo(secInfoStr);
+ }
+ else {
+ NS_WARNING("Can't serialize security info");
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::SetCharsetAndSource(int32_t aSource, const nsACString & aCharset)
+{
+ // mState == WCC_ONSTART when reading from the channel
+ // mState == WCC_INIT when writing to the cache
+ NS_ENSURE_TRUE((mState == WCC_ONSTART) ||
+ (mState == WCC_INIT), NS_ERROR_UNEXPECTED);
+
+ mCharsetSource = aSource;
+ mCharset = aCharset;
+
+ // TODO ensure that nsWyciwygChannel in the parent has still the cache entry
+ SendSetCharsetAndSource(mCharsetSource, mCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetCharsetAndSource(int32_t *aSource, nsACString & _retval)
+{
+ NS_ENSURE_TRUE((mState == WCC_ONSTART) ||
+ (mState == WCC_ONDATA) ||
+ (mState == WCC_ONSTOP), NS_ERROR_NOT_AVAILABLE);
+
+ if (mCharsetSource == kCharsetUninitialized)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *aSource = mCharsetSource;
+ _retval = mCharset;
+ return NS_OK;
+}
+
+//------------------------------------------------------------------------------
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/wyciwyg/WyciwygChannelChild.h b/netwerk/protocol/wyciwyg/WyciwygChannelChild.h
new file mode 100644
index 000000000..a712c4692
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelChild.h
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WyciwygChannelChild_h
+#define mozilla_net_WyciwygChannelChild_h
+
+#include "mozilla/net/PWyciwygChannelChild.h"
+#include "nsIWyciwygChannel.h"
+#include "nsIChannel.h"
+#include "nsILoadInfo.h"
+#include "PrivateBrowsingChannel.h"
+
+class nsIProgressEventSink;
+
+namespace mozilla {
+namespace net {
+
+class ChannelEventQueue;
+
+// TODO: replace with IPDL states
+enum WyciwygChannelChildState {
+ WCC_NEW,
+ WCC_INIT,
+
+ // States when reading from the channel
+ WCC_OPENED,
+ WCC_ONSTART,
+ WCC_ONDATA,
+ WCC_ONSTOP,
+
+ // States when writing to the cache
+ WCC_ONWRITE,
+ WCC_ONCLOSED
+};
+
+
+// Header file contents
+class WyciwygChannelChild final : public PWyciwygChannelChild
+ , public nsIWyciwygChannel
+ , public PrivateBrowsingChannel<WyciwygChannelChild>
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIWYCIWYGCHANNEL
+
+ WyciwygChannelChild();
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ nsresult Init(nsIURI *uri);
+
+ bool IsSuspended();
+
+protected:
+ virtual ~WyciwygChannelChild();
+
+ bool RecvOnStartRequest(const nsresult& statusCode,
+ const int64_t& contentLength,
+ const int32_t& source,
+ const nsCString& charset,
+ const nsCString& securityInfo) override;
+ bool RecvOnDataAvailable(const nsCString& data,
+ const uint64_t& offset) override;
+ bool RecvOnStopRequest(const nsresult& statusCode) override;
+ bool RecvCancelEarly(const nsresult& statusCode) override;
+
+ void OnStartRequest(const nsresult& statusCode,
+ const int64_t& contentLength,
+ const int32_t& source,
+ const nsCString& charset,
+ const nsCString& securityInfo);
+ void OnDataAvailable(const nsCString& data,
+ const uint64_t& offset);
+ void OnStopRequest(const nsresult& statusCode);
+ void CancelEarly(const nsresult& statusCode);
+
+ friend class PrivateBrowsingChannel<WyciwygChannelChild>;
+
+private:
+ nsresult mStatus;
+ bool mIsPending;
+ bool mCanceled;
+ uint32_t mLoadFlags;
+ int64_t mContentLength;
+ int32_t mCharsetSource;
+ nsCString mCharset;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mListenerContext;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+
+ // FIXME: replace with IPDL states (bug 536319)
+ enum WyciwygChannelChildState mState;
+
+ bool mIPCOpen;
+ bool mSentAppData;
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ friend class WyciwygStartRequestEvent;
+ friend class WyciwygDataAvailableEvent;
+ friend class WyciwygStopRequestEvent;
+ friend class WyciwygCancelEvent;
+};
+
+inline bool
+WyciwygChannelChild::IsSuspended()
+{
+ return false;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WyciwygChannelChild_h
diff --git a/netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp b/netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp
new file mode 100644
index 000000000..be00c2d86
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp
@@ -0,0 +1,373 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWyciwyg.h"
+
+#include "mozilla/net/WyciwygChannelParent.h"
+#include "nsWyciwygChannel.h"
+#include "nsNetUtil.h"
+#include "nsCharsetSource.h"
+#include "nsISerializable.h"
+#include "nsSerializationHelper.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoParent.h"
+#include "SerializedLoadContext.h"
+#include "nsIContentPolicy.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+WyciwygChannelParent::WyciwygChannelParent()
+ : mIPCClosed(false)
+ , mReceivedAppData(false)
+{
+}
+
+WyciwygChannelParent::~WyciwygChannelParent()
+{
+}
+
+void
+WyciwygChannelParent::ActorDestroy(ActorDestroyReason why)
+{
+ // We may still have refcount>0 if the channel hasn't called OnStopRequest
+ // yet, but we must not send any more msgs to child.
+ mIPCClosed = true;
+
+ // We need to force the cycle to break here
+ if (mChannel) {
+ mChannel->SetNotificationCallbacks(nullptr);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// WyciwygChannelParent::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(WyciwygChannelParent,
+ nsIStreamListener,
+ nsIInterfaceRequestor,
+ nsIRequestObserver)
+
+//-----------------------------------------------------------------------------
+// WyciwygChannelParent::PWyciwygChannelParent
+//-----------------------------------------------------------------------------
+
+bool
+WyciwygChannelParent::RecvInit(const URIParams& aURI,
+ const ipc::PrincipalInfo& aRequestingPrincipalInfo,
+ const ipc::PrincipalInfo& aTriggeringPrincipalInfo,
+ const ipc::PrincipalInfo& aPrincipalToInheritInfo,
+ const uint32_t& aSecurityFlags,
+ const uint32_t& aContentPolicyType)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+ if (!uri)
+ return false;
+
+ LOG(("WyciwygChannelParent RecvInit [this=%p uri=%s]\n",
+ this, uri->GetSpecOrDefault().get()));
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ nsCOMPtr<nsIPrincipal> requestingPrincipal =
+ mozilla::ipc::PrincipalInfoToPrincipal(aRequestingPrincipalInfo, &rv);
+ if (NS_FAILED(rv)) {
+ return SendCancelEarly(rv);
+ }
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ mozilla::ipc::PrincipalInfoToPrincipal(aTriggeringPrincipalInfo, &rv);
+ if (NS_FAILED(rv)) {
+ return SendCancelEarly(rv);
+ }
+
+ nsCOMPtr<nsIPrincipal> principalToInherit =
+ mozilla::ipc::PrincipalInfoToPrincipal(aPrincipalToInheritInfo, &rv);
+ if (NS_FAILED(rv)) {
+ return SendCancelEarly(rv);
+ }
+
+ nsCOMPtr<nsIChannel> chan;
+ rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(chan),
+ uri,
+ requestingPrincipal,
+ triggeringPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ nullptr, // loadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL,
+ ios);
+
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->GetLoadInfo();
+ rv = loadInfo->SetPrincipalToInherit(principalToInherit);
+ if (NS_FAILED(rv)) {
+ return SendCancelEarly(rv);
+ }
+
+ mChannel = do_QueryInterface(chan, &rv);
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvAppData(const IPC::SerializedLoadContext& loadContext,
+ const PBrowserOrId &parent)
+{
+ LOG(("WyciwygChannelParent RecvAppData [this=%p]\n", this));
+
+ if (!SetupAppData(loadContext, parent))
+ return false;
+
+ mChannel->SetNotificationCallbacks(this);
+ return true;
+}
+
+bool
+WyciwygChannelParent::SetupAppData(const IPC::SerializedLoadContext& loadContext,
+ const PBrowserOrId &aParent)
+{
+ if (!mChannel)
+ return true;
+
+ const char* error = NeckoParent::CreateChannelLoadContext(aParent,
+ Manager()->Manager(),
+ loadContext,
+ nullptr,
+ mLoadContext);
+ if (error) {
+ printf_stderr("WyciwygChannelParent::SetupAppData: FATAL ERROR: %s\n",
+ error);
+ return false;
+ }
+
+ if (!mLoadContext && loadContext.IsPrivateBitValid()) {
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(mChannel);
+ if (pbChannel)
+ pbChannel->SetPrivate(loadContext.mOriginAttributes.mPrivateBrowsingId > 0);
+ }
+
+ mReceivedAppData = true;
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvAsyncOpen(const URIParams& aOriginal,
+ const uint32_t& aLoadFlags,
+ const IPC::SerializedLoadContext& loadContext,
+ const PBrowserOrId &aParent)
+{
+ nsCOMPtr<nsIURI> original = DeserializeURI(aOriginal);
+ if (!original)
+ return false;
+
+ LOG(("WyciwygChannelParent RecvAsyncOpen [this=%p]\n", this));
+
+ if (!mChannel)
+ return true;
+
+ nsresult rv;
+
+ rv = mChannel->SetOriginalURI(original);
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ rv = mChannel->SetLoadFlags(aLoadFlags);
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ if (!mReceivedAppData && !SetupAppData(loadContext, aParent)) {
+ return false;
+ }
+
+ rv = mChannel->SetNotificationCallbacks(this);
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ if (loadInfo && loadInfo->GetEnforceSecurity()) {
+ rv = mChannel->AsyncOpen2(this);
+ }
+ else {
+ rv = mChannel->AsyncOpen(this, nullptr);
+ }
+
+ if (NS_FAILED(rv))
+ return SendCancelEarly(rv);
+
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvWriteToCacheEntry(const nsString& data)
+{
+ if (!mReceivedAppData) {
+ printf_stderr("WyciwygChannelParent::RecvWriteToCacheEntry: FATAL ERROR: didn't receive app data\n");
+ return false;
+ }
+
+ if (mChannel)
+ mChannel->WriteToCacheEntry(data);
+
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvCloseCacheEntry(const nsresult& reason)
+{
+ if (mChannel) {
+ mChannel->CloseCacheEntry(reason);
+ }
+
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvSetCharsetAndSource(const int32_t& aCharsetSource,
+ const nsCString& aCharset)
+{
+ if (mChannel)
+ mChannel->SetCharsetAndSource(aCharsetSource, aCharset);
+
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvSetSecurityInfo(const nsCString& aSecurityInfo)
+{
+ if (mChannel) {
+ nsCOMPtr<nsISupports> securityInfo;
+ NS_DeserializeObject(aSecurityInfo, getter_AddRefs(securityInfo));
+ mChannel->SetSecurityInfo(securityInfo);
+ }
+
+ return true;
+}
+
+bool
+WyciwygChannelParent::RecvCancel(const nsresult& aStatusCode)
+{
+ if (mChannel)
+ mChannel->Cancel(aStatusCode);
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// WyciwygChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WyciwygChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ LOG(("WyciwygChannelParent::OnStartRequest [this=%p]\n", this));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIWyciwygChannel> chan = do_QueryInterface(aRequest, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsresult status;
+ chan->GetStatus(&status);
+
+ int64_t contentLength = -1;
+ chan->GetContentLength(&contentLength);
+
+ int32_t charsetSource = kCharsetUninitialized;
+ nsAutoCString charset;
+ chan->GetCharsetAndSource(&charsetSource, charset);
+
+ nsCOMPtr<nsISupports> securityInfo;
+ chan->GetSecurityInfo(getter_AddRefs(securityInfo));
+ nsCString secInfoStr;
+ if (securityInfo) {
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(securityInfo);
+ if (serializable)
+ NS_SerializeToString(serializable, secInfoStr);
+ else {
+ NS_ERROR("Can't serialize security info");
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (mIPCClosed ||
+ !SendOnStartRequest(status, contentLength, charsetSource, charset, secInfoStr)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WyciwygChannelParent::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ LOG(("WyciwygChannelParent::OnStopRequest: [this=%p status=%ul]\n",
+ this, aStatusCode));
+
+ if (mIPCClosed || !SendOnStopRequest(aStatusCode)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WyciwygChannelParent::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WyciwygChannelParent::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ LOG(("WyciwygChannelParent::OnDataAvailable [this=%p]\n", this));
+
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mIPCClosed || !SendOnDataAvailable(data, aOffset)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WyciwygChannelParent::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WyciwygChannelParent::GetInterface(const nsIID& uuid, void** result)
+{
+ // Only support nsILoadContext if child channel's callbacks did too
+ if (uuid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
+ nsCOMPtr<nsILoadContext> copy = mLoadContext;
+ copy.forget(result);
+ return NS_OK;
+ }
+
+ return QueryInterface(uuid, result);
+}
+
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/wyciwyg/WyciwygChannelParent.h b/netwerk/protocol/wyciwyg/WyciwygChannelParent.h
new file mode 100644
index 000000000..be009487b
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelParent.h
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WyciwygChannelParent_h
+#define mozilla_net_WyciwygChannelParent_h
+
+#include "mozilla/net/PWyciwygChannelParent.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "nsIStreamListener.h"
+
+#include "nsIWyciwygChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadContext.h"
+
+namespace mozilla {
+namespace dom {
+ class PBrowserParent;
+} // namespace dom
+
+namespace net {
+
+class WyciwygChannelParent : public PWyciwygChannelParent
+ , public nsIStreamListener
+ , public nsIInterfaceRequestor
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ WyciwygChannelParent();
+
+protected:
+ virtual ~WyciwygChannelParent();
+
+ virtual bool RecvInit(const URIParams& uri,
+ const ipc::PrincipalInfo& aRequestingPrincipalInfo,
+ const ipc::PrincipalInfo& aTriggeringPrincipalInfo,
+ const ipc::PrincipalInfo& aPrincipalToInheritInfo,
+ const uint32_t& aSecurityFlags,
+ const uint32_t& aContentPolicyType) override;
+ virtual bool RecvAsyncOpen(const URIParams& original,
+ const uint32_t& loadFlags,
+ const IPC::SerializedLoadContext& loadContext,
+ const PBrowserOrId &parent) override;
+ virtual bool RecvWriteToCacheEntry(const nsString& data) override;
+ virtual bool RecvCloseCacheEntry(const nsresult& reason) override;
+ virtual bool RecvSetCharsetAndSource(const int32_t& source,
+ const nsCString& charset) override;
+ virtual bool RecvSetSecurityInfo(const nsCString& securityInfo) override;
+ virtual bool RecvCancel(const nsresult& statusCode) override;
+ virtual bool RecvAppData(const IPC::SerializedLoadContext& loadContext,
+ const PBrowserOrId &parent) override;
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ bool SetupAppData(const IPC::SerializedLoadContext& loadContext,
+ const PBrowserOrId &aParent);
+
+ nsCOMPtr<nsIWyciwygChannel> mChannel;
+ bool mIPCClosed;
+ bool mReceivedAppData;
+ nsCOMPtr<nsILoadContext> mLoadContext;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_WyciwygChannelParent_h
diff --git a/netwerk/protocol/wyciwyg/moz.build b/netwerk/protocol/wyciwyg/moz.build
new file mode 100644
index 000000000..b043137f7
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/moz.build
@@ -0,0 +1,36 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIWyciwygChannel.idl',
+]
+
+XPIDL_MODULE = 'necko_wyciwyg'
+
+EXPORTS.mozilla.net += [
+ 'WyciwygChannelChild.h',
+ 'WyciwygChannelParent.h',
+]
+
+UNIFIED_SOURCES += [
+ 'nsWyciwyg.cpp',
+ 'nsWyciwygChannel.cpp',
+ 'nsWyciwygProtocolHandler.cpp',
+ 'WyciwygChannelChild.cpp',
+ 'WyciwygChannelParent.cpp',
+]
+
+IPDL_SOURCES += [
+ 'PWyciwygChannel.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
diff --git a/netwerk/protocol/wyciwyg/nsIWyciwygChannel.idl b/netwerk/protocol/wyciwyg/nsIWyciwygChannel.idl
new file mode 100644
index 000000000..29bcc4d77
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsIWyciwygChannel.idl
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIChannel.idl"
+
+/**
+ * A channel to manage all cache-related interactions for layout
+ * when it is dealing with dynamic pages created through
+ * document.write(). This interface provides methods that will
+ * help layout save dynamic pages in cache for future retrievals.
+ */
+
+[scriptable, uuid (8b8f3341-46da-40f5-a16f-41a91f5d25dd)]
+interface nsIWyciwygChannel : nsIChannel
+{
+ /**
+ * Append data to the cache entry; opens the cache entry if necessary.
+ */
+ void writeToCacheEntry(in AString aData);
+
+ /**
+ * Close the cache entry; subsequent writes have undefined behavior.
+ */
+ void closeCacheEntry(in nsresult reason);
+
+ /**
+ * Set the wyciwyg channels security info
+ */
+ void setSecurityInfo(in nsISupports aSecurityInfo);
+
+ /**
+ * Store and read a charset and charset source on the wyciwyg channel. These
+ * are opaque values to the channel; consumers who set them should know what
+ * they mean.
+ */
+ void setCharsetAndSource(in long aSource, in ACString aCharset);
+ /**
+ * The return value is the charset. Throws if either the charset or the
+ * source cannot be retrieved. This is guaranteed to return a nonzero source
+ * and a nonempty charset if it does not throw.
+ */
+ ACString getCharsetAndSource(out long aSource);
+};
diff --git a/netwerk/protocol/wyciwyg/nsWyciwyg.cpp b/netwerk/protocol/wyciwyg/nsWyciwyg.cpp
new file mode 100644
index 000000000..edc716afd
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsWyciwyg.cpp
@@ -0,0 +1,10 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWyciwyg.h"
+#include "nscore.h"
+
+mozilla::LazyLogModule gWyciwygLog("nsWyciwygChannel");
+
+
diff --git a/netwerk/protocol/wyciwyg/nsWyciwyg.h b/netwerk/protocol/wyciwyg/nsWyciwyg.h
new file mode 100644
index 000000000..48199a9b9
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsWyciwyg.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWyciwyg_h__
+#define nsWyciwyg_h__
+
+#include "mozilla/net/NeckoChild.h"
+
+// Get rid of chromium's LOG.
+#undef LOG
+
+#include "mozilla/Logging.h"
+
+//
+// Log module for HTTP Protocol logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=nsWyciwyg:5
+// set MOZ_LOG_FILE=wyciwyg.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file wyciwyg.log.
+//
+extern mozilla::LazyLogModule gWyciwygLog;
+
+// http logging
+#define LOG1(args) MOZ_LOG(gWyciwygLog, mozilla::LogLevel::Error, args)
+#define LOG2(args) MOZ_LOG(gWyciwygLog, mozilla::LogLevel::Warning, args)
+#define LOG3(args) MOZ_LOG(gWyciwygLog, mozilla::LogLevel::Info, args)
+#define LOG4(args) MOZ_LOG(gWyciwygLog, mozilla::LogLevel::Debug, args)
+#define LOG(args) LOG4(args)
+
+#define LOG1_ENABLED() MOZ_LOG_TEST(gWyciwygLog, mozilla::LogLevel::Error)
+#define LOG2_ENABLED() MOZ_LOG_TEST(gWyciwygLog, mozilla::LogLevel::Warning)
+#define LOG3_ENABLED() MOZ_LOG_TEST(gWyciwygLog, mozilla::LogLevel::Info)
+#define LOG4_ENABLED() MOZ_LOG_TEST(gWyciwygLog, mozilla::LogLevel::Debug)
+#define LOG_ENABLED() LOG4_ENABLED()
+
+#define WYCIWYG_TYPE "text/html"
+
+#endif // nsWyciwyg_h__
diff --git a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
new file mode 100644
index 000000000..52949b799
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
@@ -0,0 +1,808 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWyciwyg.h"
+#include "nsWyciwygChannel.h"
+#include "nsILoadGroup.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "LoadContextInfo.h"
+#include "nsICacheService.h" // only to initialize
+#include "nsICacheStorageService.h"
+#include "nsICacheStorage.h"
+#include "nsICacheEntry.h"
+#include "nsCharsetSource.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsIInputStream.h"
+#include "nsIInputStreamPump.h"
+#include "nsIOutputStream.h"
+#include "nsIProgressEventSink.h"
+#include "nsIURI.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Unused.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsProxyRelease.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+
+typedef mozilla::net::LoadContextInfo LoadContextInfo;
+
+// nsWyciwygChannel methods
+nsWyciwygChannel::nsWyciwygChannel()
+ : mMode(NONE),
+ mStatus(NS_OK),
+ mIsPending(false),
+ mNeedToWriteCharset(false),
+ mCharsetSource(kCharsetUninitialized),
+ mContentLength(-1),
+ mLoadFlags(LOAD_NORMAL),
+ mNeedToSetSecurityInfo(false)
+{
+}
+
+nsWyciwygChannel::~nsWyciwygChannel()
+{
+ if (mLoadInfo) {
+ NS_ReleaseOnMainThread(mLoadInfo.forget(), false);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsWyciwygChannel,
+ nsIChannel,
+ nsIRequest,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsICacheEntryOpenCallback,
+ nsIWyciwygChannel,
+ nsIPrivateBrowsingChannel)
+
+nsresult
+nsWyciwygChannel::Init(nsIURI* uri)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+
+ mURI = uri;
+ mOriginalURI = uri;
+
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+///////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetName(nsACString &aName)
+{
+ return mURI->GetSpec(aName);
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::IsPending(bool *aIsPending)
+{
+ *aIsPending = mIsPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetStatus(nsresult *aStatus)
+{
+ if (NS_SUCCEEDED(mStatus) && mPump)
+ mPump->GetStatus(aStatus);
+ else
+ *aStatus = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::Cancel(nsresult status)
+{
+ mStatus = status;
+ if (mPump)
+ mPump->Cancel(status);
+ // else we're waiting for OnCacheEntryAvailable
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::Suspend()
+{
+ if (mPump)
+ mPump->Suspend();
+ // XXX else, we'll ignore this ... and that's probably bad!
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::Resume()
+{
+ if (mPump)
+ mPump->Resume();
+ // XXX else, we'll ignore this ... and that's probably bad!
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup)
+{
+ *aLoadGroup = mLoadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
+{
+ if (!CanSetLoadGroup(aLoadGroup)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoadGroup = aLoadGroup;
+ NS_QueryNotificationCallbacks(mCallbacks,
+ mLoadGroup,
+ NS_GET_IID(nsIProgressEventSink),
+ getter_AddRefs(mProgressSink));
+ UpdatePrivateBrowsing();
+ NS_GetOriginAttributes(this, mOriginAttributes);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetLoadFlags(uint32_t aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetLoadFlags(uint32_t * aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIChannel methods:
+///////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetOriginalURI(nsIURI* *aURI)
+{
+ *aURI = mOriginalURI;
+ NS_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetOriginalURI(nsIURI* aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetURI(nsIURI* *aURI)
+{
+ *aURI = mURI;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetOwner(nsISupports **aOwner)
+{
+ NS_IF_ADDREF(*aOwner = mOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetOwner(nsISupports* aOwner)
+{
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetLoadInfo(nsILoadInfo **aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aCallbacks)
+{
+ *aCallbacks = mCallbacks.get();
+ NS_IF_ADDREF(*aCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
+{
+ if (!CanSetCallbacks(aNotificationCallbacks)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCallbacks = aNotificationCallbacks;
+ NS_QueryNotificationCallbacks(mCallbacks,
+ mLoadGroup,
+ NS_GET_IID(nsIProgressEventSink),
+ getter_AddRefs(mProgressSink));
+
+ UpdatePrivateBrowsing();
+ NS_GetOriginAttributes(this, mOriginAttributes);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentType(nsACString &aContentType)
+{
+ aContentType.AssignLiteral(WYCIWYG_TYPE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetContentType(const nsACString &aContentType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentCharset(nsACString &aContentCharset)
+{
+ aContentCharset.AssignLiteral("UTF-16");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetContentCharset(const nsACString &aContentCharset)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentLength(int64_t *aContentLength)
+{
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetContentLength(int64_t aContentLength)
+{
+ mContentLength = aContentLength;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::Open(nsIInputStream ** aReturn)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ LOG(("nsWyciwygChannel::AsyncOpen [this=%p]\n", this));
+ MOZ_ASSERT(mMode == NONE, "nsWyciwygChannel already open");
+
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(mMode == NONE, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_ARG_POINTER(listener);
+
+ mMode = READING;
+
+ // open a cache entry for this channel...
+ // mIsPending set to true since OnCacheEntryAvailable may be called
+ // synchronously and fails when mIsPending found false.
+ mIsPending = true;
+ nsresult rv = OpenCacheEntryForReading(mURI);
+ if (NS_FAILED(rv)) {
+ LOG(("nsWyciwygChannel::OpenCacheEntryForReading failed [rv=%x]\n", rv));
+ mIsPending = false;
+ return rv;
+ }
+
+ // There is no code path that would invoke the listener sooner than
+ // we get to this line in case OnCacheEntryAvailable is invoked
+ // synchronously.
+ mListener = listener;
+ mListenerContext = ctx;
+
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// nsIWyciwygChannel
+//////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsWyciwygChannel::WriteToCacheEntry(const nsAString &aData)
+{
+ LOG(("nsWyciwygChannel::WriteToCacheEntry [this=%p]", this));
+
+ nsresult rv;
+
+ if (mMode == READING) {
+ LOG(("nsWyciwygChannel::WriteToCacheEntry already open for reading"));
+ MOZ_ASSERT(false);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mMode = WRITING;
+
+ if (!mCacheEntry) {
+ nsresult rv = OpenCacheEntryForWriting(mURI);
+ if (NS_FAILED(rv) || !mCacheEntry) {
+ LOG((" could not synchronously open cache entry for write!"));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
+ rv = mCacheEntry->SetMetaDataElement("inhibit-persistent-caching", "1");
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (mNeedToSetSecurityInfo) {
+ mCacheEntry->SetSecurityInfo(mSecurityInfo);
+ mNeedToSetSecurityInfo = false;
+ }
+
+ if (mNeedToWriteCharset) {
+ WriteCharsetAndSourceToCache(mCharsetSource, mCharset);
+ mNeedToWriteCharset = false;
+ }
+
+ uint32_t out;
+ if (!mCacheOutputStream) {
+ // Get the outputstream from the cache entry.
+ rv = mCacheEntry->OpenOutputStream(0, getter_AddRefs(mCacheOutputStream));
+ if (NS_FAILED(rv)) return rv;
+
+ // Write out a Byte Order Mark, so that we'll know if the data is
+ // BE or LE when we go to read it.
+ char16_t bom = 0xFEFF;
+ rv = mCacheOutputStream->Write((char *)&bom, sizeof(bom), &out);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return mCacheOutputStream->Write((const char *)PromiseFlatString(aData).get(),
+ aData.Length() * sizeof(char16_t), &out);
+}
+
+
+NS_IMETHODIMP
+nsWyciwygChannel::CloseCacheEntry(nsresult reason)
+{
+ if (mCacheEntry) {
+ LOG(("nsWyciwygChannel::CloseCacheEntry [this=%p ]", this));
+ mCacheOutputStream = nullptr;
+ mCacheInputStream = nullptr;
+
+ if (NS_FAILED(reason)) {
+ mCacheEntry->AsyncDoom(nullptr);
+ }
+
+ mCacheEntry = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetSecurityInfo(nsISupports *aSecurityInfo)
+{
+ if (mMode == READING) {
+ MOZ_ASSERT(false);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mSecurityInfo = aSecurityInfo;
+
+ if (mCacheEntry) {
+ return mCacheEntry->SetSecurityInfo(mSecurityInfo);
+ }
+
+ mNeedToSetSecurityInfo = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::SetCharsetAndSource(int32_t aSource,
+ const nsACString& aCharset)
+{
+ NS_ENSURE_ARG(!aCharset.IsEmpty());
+
+ if (mCacheEntry) {
+ WriteCharsetAndSourceToCache(mCharsetSource, mCharset);
+ } else {
+ MOZ_ASSERT(mMode != WRITING, "We must have an entry!");
+ if (mMode == READING) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ mNeedToWriteCharset = true;
+ mCharsetSource = aSource;
+ mCharset = aCharset;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetCharsetAndSource(int32_t* aSource, nsACString& aCharset)
+{
+ MOZ_ASSERT(mMode == READING);
+
+ if (!mCacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsXPIDLCString data;
+ mCacheEntry->GetMetaDataElement("charset", getter_Copies(data));
+
+ if (data.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsXPIDLCString sourceStr;
+ mCacheEntry->GetMetaDataElement("charset-source", getter_Copies(sourceStr));
+
+ int32_t source;
+ nsresult err;
+ source = sourceStr.ToInteger(&err);
+ if (NS_FAILED(err) || source == 0) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aSource = source;
+ aCharset = data;
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// nsICacheEntryOpenCallback
+//////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsWyciwygChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appCache,
+ uint32_t* aResult)
+{
+ *aResult = ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::OnCacheEntryAvailable(nsICacheEntry *aCacheEntry,
+ bool aNew,
+ nsIApplicationCache* aAppCache,
+ nsresult aStatus)
+{
+ LOG(("nsWyciwygChannel::OnCacheEntryAvailable [this=%p entry=%p "
+ "new=%d status=%x]\n", this, aCacheEntry, aNew, aStatus));
+
+ MOZ_RELEASE_ASSERT(!aNew, "New entry must not be returned when flag "
+ "OPEN_READONLY is used!");
+
+ // if the channel's already fired onStopRequest,
+ // then we should ignore this event.
+ if (!mIsPending)
+ return NS_OK;
+
+ if (NS_SUCCEEDED(mStatus)) {
+ if (NS_SUCCEEDED(aStatus)) {
+ MOZ_ASSERT(aCacheEntry);
+ mCacheEntry = aCacheEntry;
+ nsresult rv = ReadFromCache();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ }
+ } else {
+ mStatus = aStatus;
+ }
+ }
+
+ if (NS_FAILED(mStatus)) {
+ LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus));
+ // Since OnCacheEntryAvailable can be called directly from AsyncOpen
+ // we must dispatch.
+ NS_DispatchToCurrentThread(mozilla::NewRunnableMethod(
+ this, &nsWyciwygChannel::NotifyListener));
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsWyciwygChannel::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsWyciwygChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctx,
+ nsIInputStream *input,
+ uint64_t offset, uint32_t count)
+{
+ LOG(("nsWyciwygChannel::OnDataAvailable [this=%p request=%x offset=%llu count=%u]\n",
+ this, request, offset, count));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ nsCOMPtr<nsISupports> listenerContext = mListenerContext;
+
+ if (listener) {
+ rv = listener->OnDataAvailable(this, listenerContext, input, offset, count);
+ } else {
+ MOZ_ASSERT(false, "We must have a listener!");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+
+ // XXX handle 64-bit stuff for real
+ if (mProgressSink && NS_SUCCEEDED(rv)) {
+ mProgressSink->OnProgress(this, nullptr, offset + count, mContentLength);
+ }
+
+ return rv; // let the pump cancel on failure
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// nsIRequestObserver
+//////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsWyciwygChannel::OnStartRequest(nsIRequest *request, nsISupports *ctx)
+{
+ LOG(("nsWyciwygChannel::OnStartRequest [this=%p request=%x\n",
+ this, request));
+
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ nsCOMPtr<nsISupports> listenerContext = mListenerContext;
+
+ if (listener) {
+ return listener->OnStartRequest(this, listenerContext);
+ }
+
+ MOZ_ASSERT(false, "We must have a listener!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+
+NS_IMETHODIMP
+nsWyciwygChannel::OnStopRequest(nsIRequest *request, nsISupports *ctx, nsresult status)
+{
+ LOG(("nsWyciwygChannel::OnStopRequest [this=%p request=%x status=%d\n",
+ this, request, status));
+
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = status;
+
+ mIsPending = false;
+
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsISupports> listenerContext;
+ listener.swap(mListener);
+ listenerContext.swap(mListenerContext);
+
+ if (listener) {
+ listener->OnStopRequest(this, listenerContext, mStatus);
+ } else {
+ MOZ_ASSERT(false, "We must have a listener!");
+ }
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ CloseCacheEntry(mStatus);
+ mPump = nullptr;
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ mProgressSink = nullptr;
+
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Helper functions
+//////////////////////////////////////////////////////////////////////////////
+
+nsresult
+nsWyciwygChannel::GetCacheStorage(nsICacheStorage **_retval)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICacheStorageService> cacheService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool anonymous = mLoadFlags & LOAD_ANONYMOUS;
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(mPrivateBrowsing);
+ RefPtr<LoadContextInfo> loadInfo = mozilla::net::GetLoadContextInfo(anonymous, mOriginAttributes);
+
+ if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
+ return cacheService->MemoryCacheStorage(loadInfo, _retval);
+ }
+
+ return cacheService->DiskCacheStorage(loadInfo, false, _retval);
+}
+
+nsresult
+nsWyciwygChannel::OpenCacheEntryForReading(nsIURI *aURI)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ rv = GetCacheStorage(getter_AddRefs(cacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return cacheStorage->AsyncOpenURI(aURI, EmptyCString(),
+ nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::CHECK_MULTITHREADED,
+ this);
+}
+
+nsresult
+nsWyciwygChannel::OpenCacheEntryForWriting(nsIURI *aURI)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ rv = GetCacheStorage(getter_AddRefs(cacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return cacheStorage->OpenTruncate(aURI, EmptyCString(),
+ getter_AddRefs(mCacheEntry));
+}
+
+nsresult
+nsWyciwygChannel::ReadFromCache()
+{
+ LOG(("nsWyciwygChannel::ReadFromCache [this=%p] ", this));
+
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
+ nsresult rv;
+
+ // Get the stored security info
+ mCacheEntry->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
+
+ nsAutoCString tmpStr;
+ rv = mCacheEntry->GetMetaDataElement("inhibit-persistent-caching",
+ getter_Copies(tmpStr));
+ if (NS_SUCCEEDED(rv) && tmpStr.EqualsLiteral("1"))
+ mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
+
+ // Get a transport to the cached data...
+ rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(mCacheInputStream));
+ if (NS_FAILED(rv))
+ return rv;
+ NS_ENSURE_TRUE(mCacheInputStream, NS_ERROR_UNEXPECTED);
+
+ rv = NS_NewInputStreamPump(getter_AddRefs(mPump), mCacheInputStream);
+ if (NS_FAILED(rv)) return rv;
+
+ // Pump the cache data downstream
+ return mPump->AsyncRead(this, nullptr);
+}
+
+void
+nsWyciwygChannel::WriteCharsetAndSourceToCache(int32_t aSource,
+ const nsCString& aCharset)
+{
+ NS_PRECONDITION(mCacheEntry, "Better have cache entry!");
+
+ mCacheEntry->SetMetaDataElement("charset", aCharset.get());
+
+ nsAutoCString source;
+ source.AppendInt(aSource);
+ mCacheEntry->SetMetaDataElement("charset-source", source.get());
+}
+
+void
+nsWyciwygChannel::NotifyListener()
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsISupports> listenerContext;
+
+ listener.swap(mListener);
+ listenerContext.swap(mListenerContext);
+
+ if (listener) {
+ listener->OnStartRequest(this, listenerContext);
+ mIsPending = false;
+ listener->OnStopRequest(this, listenerContext, mStatus);
+ } else {
+ MOZ_ASSERT(false, "We must have the listener!");
+ mIsPending = false;
+ }
+
+ CloseCacheEntry(mStatus);
+
+ // Remove ourselves from the load group.
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ }
+}
+
+// vim: ts=2 sw=2
diff --git a/netwerk/protocol/wyciwyg/nsWyciwygChannel.h b/netwerk/protocol/wyciwyg/nsWyciwygChannel.h
new file mode 100644
index 000000000..26326e2a4
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWyciwygChannel_h___
+#define nsWyciwygChannel_h___
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+#include "nsILoadInfo.h"
+#include "nsIWyciwygChannel.h"
+#include "nsIStreamListener.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "PrivateBrowsingChannel.h"
+#include "mozilla/BasePrincipal.h"
+
+class nsICacheEntry;
+class nsICacheStorage;
+class nsIInputStream;
+class nsIInputStreamPump;
+class nsILoadGroup;
+class nsIOutputStream;
+class nsIProgressEventSink;
+class nsIURI;
+
+extern mozilla::LazyLogModule gWyciwygLog;
+
+//-----------------------------------------------------------------------------
+
+class nsWyciwygChannel final: public nsIWyciwygChannel,
+ public nsIStreamListener,
+ public nsICacheEntryOpenCallback,
+ public mozilla::net::PrivateBrowsingChannel<nsWyciwygChannel>
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIWYCIWYGCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+
+ // nsWyciwygChannel methods:
+ nsWyciwygChannel();
+
+ nsresult Init(nsIURI *uri);
+
+protected:
+ virtual ~nsWyciwygChannel();
+
+ nsresult ReadFromCache();
+ nsresult EnsureWriteCacheEntry();
+ nsresult GetCacheStorage(nsICacheStorage **_retval);
+ nsresult OpenCacheEntryForReading(nsIURI *aURI);
+ nsresult OpenCacheEntryForWriting(nsIURI *aURI);
+
+ void WriteCharsetAndSourceToCache(int32_t aSource,
+ const nsCString& aCharset);
+
+ void NotifyListener();
+
+ friend class mozilla::net::PrivateBrowsingChannel<nsWyciwygChannel>;
+
+ enum EMode {
+ NONE,
+ WRITING,
+ READING
+ };
+
+ EMode mMode;
+ nsresult mStatus;
+ bool mIsPending;
+ bool mNeedToWriteCharset;
+ int32_t mCharsetSource;
+ nsCString mCharset;
+ int64_t mContentLength;
+ uint32_t mLoadFlags;
+ mozilla::NeckoOriginAttributes mOriginAttributes;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mListenerContext;
+
+ // reuse as much of this channel implementation as we can
+ nsCOMPtr<nsIInputStreamPump> mPump;
+
+ // Cache related stuff
+ nsCOMPtr<nsICacheEntry> mCacheEntry;
+ nsCOMPtr<nsIOutputStream> mCacheOutputStream;
+ nsCOMPtr<nsIInputStream> mCacheInputStream;
+
+ bool mNeedToSetSecurityInfo;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+};
+
+/**
+ * Casting nsWyciwygChannel to nsISupports is ambiguous.
+ * This method handles that.
+ */
+inline nsISupports*
+ToSupports(nsWyciwygChannel* p)
+{
+ return NS_ISUPPORTS_CAST(nsIStreamListener*, p);
+}
+
+#endif /* nsWyciwygChannel_h___ */
diff --git a/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.cpp b/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.cpp
new file mode 100644
index 000000000..4e1f7c22e
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.cpp
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWyciwyg.h"
+#include "nsWyciwygChannel.h"
+#include "nsWyciwygProtocolHandler.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "plstr.h"
+#include "nsIObserverService.h"
+#include "mozIApplicationClearPrivateDataParams.h"
+#include "nsIURI.h"
+
+#include "mozilla/net/NeckoChild.h"
+
+using namespace mozilla::net;
+#include "mozilla/net/WyciwygChannelChild.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsWyciwygProtocolHandler::nsWyciwygProtocolHandler()
+{
+ LOG(("Creating nsWyciwygProtocolHandler [this=%p].\n", this));
+}
+
+nsWyciwygProtocolHandler::~nsWyciwygProtocolHandler()
+{
+ LOG(("Deleting nsWyciwygProtocolHandler [this=%p]\n", this));
+}
+
+NS_IMPL_ISUPPORTS(nsWyciwygProtocolHandler,
+ nsIProtocolHandler)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::GetScheme(nsACString &result)
+{
+ result = "wyciwyg";
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::GetDefaultPort(int32_t *result)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset, // ignored
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> url = do_CreateInstance(NS_SIMPLEURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ url.forget(result);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::NewChannel2(nsIURI* url,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ if (mozilla::net::IsNeckoChild())
+ mozilla::net::NeckoChild::InitNeckoChild();
+
+ NS_ENSURE_ARG_POINTER(url);
+ nsresult rv;
+
+ nsCOMPtr<nsIWyciwygChannel> channel;
+ if (IsNeckoChild()) {
+ NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
+
+ WyciwygChannelChild *wcc = static_cast<WyciwygChannelChild *>(
+ gNeckoChild->SendPWyciwygChannelConstructor());
+ if (!wcc)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ channel = wcc;
+ rv = wcc->Init(url);
+ if (NS_FAILED(rv))
+ PWyciwygChannelChild::Send__delete__(wcc);
+ } else
+ {
+ // If original channel used https, make sure PSM is initialized
+ // (this may be first channel to load during a session restore)
+ nsAutoCString path;
+ rv = url->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t slashIndex = path.FindChar('/', 2);
+ if (slashIndex == kNotFound)
+ return NS_ERROR_FAILURE;
+ if (path.Length() < (uint32_t)slashIndex + 1 + 5)
+ return NS_ERROR_FAILURE;
+ if (!PL_strncasecmp(path.get() + slashIndex + 1, "https", 5))
+ net_EnsurePSMInit();
+
+ nsWyciwygChannel *wc = new nsWyciwygChannel();
+ channel = wc;
+ rv = wc->Init(url);
+ }
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ // set the loadInfo on the new channel
+ rv = channel->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ channel.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::NewChannel(nsIURI* url, nsIChannel* *result)
+{
+ return NewChannel2(url, nullptr, result);
+}
+
+NS_IMETHODIMP
+nsWyciwygProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ // Should this be an an nsINestedURI? We don't really want random webpages
+ // loading these URIs...
+
+ // Note that using URI_INHERITS_SECURITY_CONTEXT here is OK -- untrusted code
+ // is not allowed to link to wyciwyg URIs and users shouldn't be able to get
+ // at them, and nsDocShell::InternalLoad forbids non-history loads of these
+ // URIs. And when loading from history we end up using the principal from
+ // the history entry, which we put there ourselves, so all is ok.
+ *result = URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD |
+ URI_INHERITS_SECURITY_CONTEXT;
+ return NS_OK;
+}
diff --git a/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.h b/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.h
new file mode 100644
index 000000000..d3dbd5ead
--- /dev/null
+++ b/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWyciwygProtocolHandler_h___
+#define nsWyciwygProtocolHandler_h___
+
+#include "nsIProtocolHandler.h"
+
+class nsWyciwygProtocolHandler : public nsIProtocolHandler
+{
+ virtual ~nsWyciwygProtocolHandler();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ nsWyciwygProtocolHandler();
+};
+
+#endif /* nsWyciwygProtocolHandler_h___ */