diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /netwerk/protocol/http | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-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')
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 ¤tName = + parsedAltSvc.mValues[index].mValues[pairIndex].mName; + nsDependentCSubstring ¤tValue = + 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, ¬Used); + + 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, ¬Used); + + // 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(¤tThread)) || !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(¤tThread)) || !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 ©) + : 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(), ¤tAge); + 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; +}; |