summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http
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/http
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/http')
-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
113 files changed, 67920 insertions, 0 deletions
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;
+};