summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/TunnelUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/protocol/http/TunnelUtils.cpp2172
1 files changed, 2172 insertions, 0 deletions
diff --git a/netwerk/protocol/http/TunnelUtils.cpp b/netwerk/protocol/http/TunnelUtils.cpp
new file mode 100644
index 0000000000..b62b1dd92c
--- /dev/null
+++ b/netwerk/protocol/http/TunnelUtils.cpp
@@ -0,0 +1,2172 @@
+/* -*- 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 "TCPFastOpen.h"
+#include "nsISocketProvider.h"
+#include "nsSocketProviderService.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"
+#include "nsSocketTransport2.h"
+#include "nsSocketTransportService2.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Mutex.h"
+
+#if defined(FUZZING)
+# include "FuzzySecurityInfo.h"
+# include "mozilla/StaticPrefs_fuzzing.h"
+#endif
+
+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),
+ mFilterReadCode(NS_ERROR_NOT_INITIALIZED),
+ mFilterReadAmount(0),
+ mInOnReadSegment(false),
+ mForce(false),
+ mReadSegmentReturnValue(NS_OK),
+ mCloseReason(NS_ERROR_UNEXPECTED),
+ mNudgeCounter(0) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("TLSFilterTransaction ctor %p\n", this));
+
+ nsCOMPtr<nsISocketProvider> provider;
+ nsCOMPtr<nsISocketProviderService> spserv =
+ nsSocketProviderService::GetOrCreate();
+
+ 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);
+
+ bool addTLSLayer = true;
+#ifdef FUZZING
+ addTLSLayer = !StaticPrefs::fuzzing_necko_enabled();
+ if (!addTLSLayer) {
+ SOCKET_LOG(("Skipping TLS layer in TLSFilterTransaction for fuzzing.\n"));
+
+ mSecInfo = static_cast<nsISupports*>(
+ static_cast<nsISSLSocketControl*>(new FuzzySecurityInfo()));
+ }
+#endif
+
+ if (provider && mFD) {
+ mFD->secret = reinterpret_cast<PRFilePrivate*>(this);
+
+ if (addTLSLayer) {
+ provider->AddToSocket(PR_AF_INET, aTLSHost, aTLSPort, nullptr,
+ OriginAttributes(), 0, 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));
+
+ // Prevent call to OnReadSegment from FilterOutput, our mSegmentReader is now
+ // an invalid pointer.
+ mInOnReadSegment = true;
+
+ Cleanup();
+}
+
+void TLSFilterTransaction::Cleanup() {
+ LOG(("TLSFilterTransaction::Cleanup %p", this));
+
+ 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) {
+ LOG(("TLSFilterTransaction::Close %p %" PRIx32, this,
+ static_cast<uint32_t>(aReason)));
+
+ if (!mTransaction) {
+ return;
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ mTransaction->Close(aReason);
+ mTransaction = nullptr;
+
+ if (gHttpHandler->Bug1563538()) {
+ if (NS_FAILED(aReason)) {
+ mCloseReason = aReason;
+ } else {
+ mCloseReason = NS_BASE_STREAM_CLOSED;
+ }
+ } else {
+ MOZ_ASSERT(NS_ERROR_UNEXPECTED == mCloseReason);
+ }
+}
+
+nsresult TLSFilterTransaction::OnReadSegment(const char* aData, uint32_t aCount,
+ uint32_t* outCountRead) {
+ LOG(("TLSFilterTransaction %p OnReadSegment %d (buffered %d)\n", this, aCount,
+ mEncryptedTextUsed));
+
+ mReadSegmentReturnValue = NS_OK;
+ 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);
+
+ // Prevents call to OnReadSegment from inside FilterOutput, as we handle it
+ // here.
+ AutoRestore<bool> inOnReadSegment(mInOnReadSegment);
+ mInOnReadSegment = true;
+
+ 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 inspect. Similar
+ // to nsHttpConnection::mSocketOutCondition
+ PRErrorCode code = PR_GetError();
+ mReadSegmentReturnValue = ErrorAccordingToNSPR(code);
+
+ return mReadSegmentReturnValue;
+ }
+ 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
+ // It is fine if the connection is null. We are likely a websocket and
+ // thus writing push is ensured by the caller.
+ if (Connection()) {
+ Connection()->TransactionHasDataToWrite(this);
+ }
+ return NS_OK;
+ }
+ 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;
+
+ LOG(("TLSFilterTransaction::FilterOutput %p %d buffered=%u mSegmentReader=%p",
+ this, aAmount, mEncryptedTextUsed, mSegmentReader));
+
+ if (!mInOnReadSegment) {
+ // When called externally, we must make sure any newly written data is
+ // actually sent to the higher level connection.
+ // This also covers the case when PR_Read() wrote a re-negotioation
+ // response.
+ uint32_t notUsed;
+ Unused << OnReadSegment("", 0, &notUsed);
+ }
+
+ 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(OnSocketThread(), "not on socket thread");
+ 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;
+ mFilterReadAmount = 0;
+ int32_t bytesRead = PR_Read(mFD, aData, aCount);
+ if (bytesRead == -1) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ LOG(
+ ("TLSFilterTransaction::OnWriteSegment %p PR_Read would block, "
+ "actual read: %d\n",
+ this, mFilterReadAmount));
+
+ if (mFilterReadAmount == 0 && NS_SUCCEEDED(mFilterReadCode)) {
+ // No reading happened, but also no error occured, hence there is no
+ // condition to break the `again` loop, propagate WOULD_BLOCK through
+ // mFilterReadCode to break it and poll the socket again for reading.
+ mFilterReadCode = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ // If reading from the socket succeeded (NS_SUCCEEDED(mFilterReadCode)),
+ // but the nss layer encountered an error remember the error.
+ if (NS_SUCCEEDED(mFilterReadCode)) {
+ mFilterReadCode = ErrorAccordingToNSPR(code);
+ LOG(("TLSFilterTransaction::OnWriteSegment %p nss error %" PRIx32 ".\n",
+ this, static_cast<uint32_t>(mFilterReadCode)));
+ }
+ return mFilterReadCode;
+ }
+ *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=%" PRIx32 " didread=%d "
+ "2 layers of ssl stripped to plaintext\n",
+ this, static_cast<uint32_t>(mFilterReadCode), bytesRead));
+ return mFilterReadCode;
+}
+
+int32_t TLSFilterTransaction::FilterInput(char* aBuf, int32_t aAmount) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ 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=%" PRIx32
+ " read=%d input from net "
+ "1 layer stripped, 1 still on\n",
+ static_cast<uint32_t>(mFilterReadCode), outCountRead));
+ if (mReadSegmentReturnValue == NS_BASE_STREAM_WOULD_BLOCK) {
+ mNudgeCounter = 0;
+ }
+
+ mFilterReadAmount += outCountRead;
+ }
+ 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(OnSocketThread(), "not on socket thread");
+ LOG(("TLSFilterTransaction::ReadSegments %p max=%d\n", this, aCount));
+
+ if (!mTransaction) {
+ return mCloseReason;
+ }
+
+ mReadSegmentReturnValue = NS_OK;
+ mSegmentReader = aReader;
+ nsresult rv = mTransaction->ReadSegments(this, aCount, outCountRead);
+
+ // mSegmentReader is left assigned (not nullified) because we want to be able
+ // to call OnReadSegment directly, it expects mSegmentReader be non-null.
+
+ LOG(("TLSFilterTransaction %p called trans->ReadSegments rv=%" PRIx32 " %d\n",
+ this, static_cast<uint32_t>(rv), *outCountRead));
+ if (NS_SUCCEEDED(rv) &&
+ (mReadSegmentReturnValue == NS_BASE_STREAM_WOULD_BLOCK)) {
+ LOG(("TLSFilterTransaction %p read segment blocked found rv=%" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv)));
+ if (Connection()) {
+ Unused << Connection()->ForceSend();
+ }
+ }
+
+ return NS_SUCCEEDED(rv) ? mReadSegmentReturnValue : rv;
+}
+
+nsresult TLSFilterTransaction::WriteSegmentsAgain(nsAHttpSegmentWriter* aWriter,
+ uint32_t aCount,
+ uint32_t* outCountWritten,
+ bool* again) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("TLSFilterTransaction::WriteSegmentsAgain %p max=%d\n", this, aCount));
+
+ if (!mTransaction) {
+ return mCloseReason;
+ }
+
+ mSegmentWriter = aWriter;
+
+ nsresult rv =
+ mTransaction->WriteSegmentsAgain(this, aCount, outCountWritten, again);
+
+ if (NS_SUCCEEDED(rv) && !(*outCountWritten) && NS_FAILED(mFilterReadCode)) {
+ // nsPipe turns failures into silent OK.. undo that!
+ rv = mFilterReadCode;
+ if (Connection() && (mFilterReadCode == NS_BASE_STREAM_WOULD_BLOCK)) {
+ Unused << Connection()->ResumeRecv();
+ }
+ }
+ LOG(("TLSFilterTransaction %p called trans->WriteSegments rv=%" PRIx32
+ " %d\n",
+ this, static_cast<uint32_t>(rv), *outCountWritten));
+ return rv;
+}
+
+nsresult TLSFilterTransaction::WriteSegments(nsAHttpSegmentWriter* aWriter,
+ uint32_t aCount,
+ uint32_t* outCountWritten) {
+ bool again = false;
+ return WriteSegmentsAgain(aWriter, aCount, outCountWritten, &again);
+}
+
+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(OnSocketThread(), "not on socket thread");
+ LOG(("TLSFilterTransaction %p NudgeTunnel\n", this));
+ mNudgeCallback = nullptr;
+
+ if (!mSecInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISSLSocketControl> ssl(do_QueryInterface(mSecInfo));
+ nsresult rv = ssl ? ssl->DriveHandshake() : NS_ERROR_FAILURE;
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ // fatal handshake failure
+ LOG(("TLSFilterTransaction %p Fatal Handshake Failure: %d\n", this,
+ PR_GetError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t notUsed;
+ Unused << OnReadSegment("", 0, &notUsed);
+
+ // The SSL Layer does some unusual things with PR_Poll that makes it a bad
+ // match for multiplexed SSL sessions. We work around this by manually polling
+ // for the moment during the brief handshake phase or otherwise blocked on
+ // write. Thankfully this is a pretty unusual state. NSPR doesn't help us here
+ // - asserting when polling without the NSPR IO layer on the bottom of the
+ // stack. As a follow-on we can do some NSPR and maybe libssl changes to make
+ // this more event driven, but this is acceptable for getting started.
+
+ uint32_t counter = mNudgeCounter++;
+ uint32_t delay;
+
+ if (!counter) {
+ delay = 0;
+ } else if (counter < 8) { // up to 48ms at 6
+ delay = 6;
+ } else if (counter < 34) { // up to 499 ms at 17ms
+ delay = 17;
+ } else { // after that at 51ms (3 old windows ticks)
+ delay = 51;
+ }
+
+ if (!mTimer) {
+ mTimer = NS_NewTimer();
+ }
+
+ 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(OnSocketThread(), "not on socket thread");
+ LOG(("TLSFilterTransaction %p NudgeTunnel notify\n", this));
+
+ if (timer != mTimer) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsresult rv = StartTimerCallback();
+ if (NS_FAILED(rv)) {
+ Close(rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSFilterTransaction::GetName(nsACString& aName) {
+ aName.AssignLiteral("TLSFilterTransaction");
+ 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;
+ return cb->OnTunnelNudged(this);
+ }
+ return NS_OK;
+}
+
+bool TLSFilterTransaction::HasDataToRecv() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!mFD) {
+ return false;
+ }
+ int32_t n = 0;
+ char c;
+ n = PR_Recv(mFD, &c, 1, PR_MSG_PEEK, 0);
+ return n > 0;
+}
+
+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::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, nsAHttpTransaction* aSpdyConnectTransaction) {
+ LOG(
+ ("TLSFilterTransaction::SetProxiedTransaction [this=%p] aTrans=%p, "
+ "aSpdyConnectTransaction=%p\n",
+ this, aTrans, aSpdyConnectTransaction));
+
+ mTransaction = aTrans;
+
+ // Reverting mCloseReason to the default value for consistency to indicate we
+ // are no longer in closed state.
+ mCloseReason = NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(mSecInfo));
+ if (secCtrl && callbacks) {
+ secCtrl->SetNotificationCallbacks(callbacks);
+ }
+
+ mWeakTrans = do_GetWeakReference(aSpdyConnectTransaction);
+
+ return NS_OK;
+}
+
+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() = default;
+ ;
+
+ 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 %" PRIx32 " %d\n", this,
+ static_cast<uint32_t>(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() = default;
+ ;
+
+ 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 WeakTransProxy final : public nsISupports {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit WeakTransProxy(SpdyConnectTransaction* aTrans) {
+ MOZ_ASSERT(OnSocketThread());
+ mWeakTrans = do_GetWeakReference(aTrans);
+ }
+
+ already_AddRefed<NullHttpTransaction> QueryTransaction() {
+ MOZ_ASSERT(OnSocketThread());
+ RefPtr<NullHttpTransaction> trans = do_QueryReferent(mWeakTrans);
+ return trans.forget();
+ }
+
+ private:
+ ~WeakTransProxy() { MOZ_ASSERT(OnSocketThread()); }
+
+ nsWeakPtr mWeakTrans;
+};
+
+NS_IMPL_ISUPPORTS(WeakTransProxy, nsISupports)
+
+class WeakTransFreeProxy final : public Runnable {
+ public:
+ explicit WeakTransFreeProxy(WeakTransProxy* proxy)
+ : Runnable("WeakTransFreeProxy"), mProxy(proxy) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(OnSocketThread());
+ mProxy = nullptr;
+ return NS_OK;
+ }
+
+ void Dispatch() {
+ MOZ_ASSERT(!OnSocketThread());
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ Unused << sts->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ private:
+ RefPtr<WeakTransProxy> mProxy;
+};
+
+class SocketTransportShim : public nsISocketTransport {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSISOCKETTRANSPORT
+
+ explicit SocketTransportShim(SpdyConnectTransaction* aTrans,
+ nsISocketTransport* aWrapped, bool aIsWebsocket)
+ : mWrapped(aWrapped), mIsWebsocket(aIsWebsocket) {
+ mWeakTrans = new WeakTransProxy(aTrans);
+ }
+
+ private:
+ virtual ~SocketTransportShim() {
+ if (!OnSocketThread()) {
+ RefPtr<WeakTransFreeProxy> p = new WeakTransFreeProxy(mWeakTrans);
+ mWeakTrans = nullptr;
+ p->Dispatch();
+ }
+ }
+
+ nsCOMPtr<nsISocketTransport> mWrapped;
+ bool mIsWebsocket;
+ nsCOMPtr<nsIInterfaceRequestor> mSecurityCallbacks;
+ RefPtr<WeakTransProxy> mWeakTrans; // SpdyConnectTransaction *
+};
+
+class OutputStreamShim : public nsIAsyncOutputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ friend class SpdyConnectTransaction;
+ friend class WebsocketHasDataToWrite;
+ friend class OutputCloseTransaction;
+
+ OutputStreamShim(SpdyConnectTransaction* aTrans, bool aIsWebsocket)
+ : mCallback(nullptr),
+ mStatus(NS_OK),
+ mMutex("OutputStreamShim"),
+ mIsWebsocket(aIsWebsocket) {
+ mWeakTrans = new WeakTransProxy(aTrans);
+ }
+
+ already_AddRefed<nsIOutputStreamCallback> TakeCallback();
+
+ private:
+ virtual ~OutputStreamShim() {
+ if (!OnSocketThread()) {
+ RefPtr<WeakTransFreeProxy> p = new WeakTransFreeProxy(mWeakTrans);
+ mWeakTrans = nullptr;
+ p->Dispatch();
+ }
+ }
+
+ RefPtr<WeakTransProxy> mWeakTrans; // SpdyConnectTransaction *
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ nsresult mStatus;
+ mozilla::Mutex mMutex;
+
+ // Websockets
+ bool mIsWebsocket;
+ nsresult CallTransactionHasDataToWrite();
+ nsresult CloseTransaction(nsresult reason);
+};
+
+class InputStreamShim : public nsIAsyncInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ friend class SpdyConnectTransaction;
+ friend class InputCloseTransaction;
+
+ InputStreamShim(SpdyConnectTransaction* aTrans, bool aIsWebsocket)
+ : mCallback(nullptr),
+ mStatus(NS_OK),
+ mMutex("InputStreamShim"),
+ mIsWebsocket(aIsWebsocket) {
+ mWeakTrans = new WeakTransProxy(aTrans);
+ }
+
+ already_AddRefed<nsIInputStreamCallback> TakeCallback();
+ bool HasCallback();
+
+ private:
+ virtual ~InputStreamShim() {
+ if (!OnSocketThread()) {
+ RefPtr<WeakTransFreeProxy> p = new WeakTransFreeProxy(mWeakTrans);
+ mWeakTrans = nullptr;
+ p->Dispatch();
+ }
+ }
+
+ RefPtr<WeakTransProxy> mWeakTrans; // SpdyConnectTransaction *
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsresult mStatus;
+ mozilla::Mutex mMutex;
+
+ // Websockets
+ bool mIsWebsocket;
+ nsresult CloseTransaction(nsresult reason);
+};
+
+SpdyConnectTransaction::SpdyConnectTransaction(
+ nsHttpConnectionInfo* ci, nsIInterfaceRequestor* callbacks, uint32_t caps,
+ nsHttpTransaction* trans, nsAHttpConnection* session, bool isWebsocket)
+ : 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),
+ mIsWebsocket(isWebsocket),
+ mConnRefTaken(false),
+ mCreateShimErrorCalled(false) {
+ LOG(("SpdyConnectTransaction ctor %p\n", this));
+
+ mTimestampSyn = TimeStamp::Now();
+ mRequestHead = new nsHttpRequestHead();
+ if (mIsWebsocket) {
+ // Ensure our request head has all the websocket headers duplicated from the
+ // original transaction before calling the boilerplate stuff to create the
+ // rest of the CONNECT headers.
+ trans->RequestHead()->Enter();
+ mRequestHead->SetHeaders(trans->RequestHead()->Headers());
+ trans->RequestHead()->Exit();
+ }
+ DebugOnly<nsresult> rv = nsHttpConnection::MakeConnectString(
+ trans, mRequestHead, mConnectString, mIsWebsocket);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mDrivingTransaction = trans;
+}
+
+SpdyConnectTransaction::~SpdyConnectTransaction() {
+ LOG(("SpdyConnectTransaction dtor %p\n", this));
+
+ MOZ_ASSERT(OnSocketThread());
+
+ if (mDrivingTransaction) {
+ // requeue it I guess. This should be gone.
+ mDrivingTransaction->SetH2WSTransaction(nullptr);
+ Unused << gHttpHandler->InitiateTransaction(
+ mDrivingTransaction, mDrivingTransaction->Priority());
+ }
+}
+
+void SpdyConnectTransaction::ForcePlainText() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mInputDataUsed && !mInputDataSize && !mInputDataOffset);
+ MOZ_ASSERT(!mForcePlainText);
+ MOZ_ASSERT(!mTunnelTransport, "call before mapstreamtohttpconnection");
+ MOZ_ASSERT(!mIsWebsocket);
+
+ mForcePlainText = true;
+}
+
+bool SpdyConnectTransaction::MapStreamToHttpConnection(
+ nsISocketTransport* aTransport, nsHttpConnectionInfo* aConnInfo,
+ const nsACString& aFlat407Headers, int32_t aHttpResponseCode) {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (aHttpResponseCode >= 100 && aHttpResponseCode < 200) {
+ LOG(
+ ("SpdyConnectTransaction::MapStreamToHttpConnection %p skip "
+ "pre-response with response code %d",
+ this, aHttpResponseCode));
+ return false;
+ }
+
+ mTunnelTransport = new SocketTransportShim(this, aTransport, mIsWebsocket);
+ mTunnelStreamIn = new InputStreamShim(this, mIsWebsocket);
+ mTunnelStreamOut = new OutputStreamShim(this, mIsWebsocket);
+ mTunneledConn = new nsHttpConnection();
+
+ // If aHttpResponseCode is -1, it means that proxy connect is not used. We
+ // should not call HttpProxyResponseToErrorCode(), since this will create a
+ // shim error.
+ if (aHttpResponseCode > 0 && aHttpResponseCode != 200) {
+ nsresult err = HttpProxyResponseToErrorCode(aHttpResponseCode);
+ if (NS_FAILED(err)) {
+ CreateShimError(err);
+ }
+ }
+
+ // 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 %p new httpconnection %p %s\n", this,
+ mTunneledConn.get(), aConnInfo->HashKey().get()));
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ GetSecurityCallbacks(getter_AddRefs(callbacks));
+ mTunneledConn->SetTransactionCaps(Caps());
+ MOZ_ASSERT(aConnInfo->UsingHttpsProxy() || mIsWebsocket);
+ TimeDuration rtt = TimeStamp::Now() - mTimestampSyn;
+ DebugOnly<nsresult> rv = mTunneledConn->Init(
+ aConnInfo, gHttpHandler->ConnMgr()->MaxRequestDelay(), mTunnelTransport,
+ mTunnelStreamIn, mTunnelStreamOut, true, callbacks,
+ PR_MillisecondsToInterval(static_cast<uint32_t>(rtt.ToMilliseconds())));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (mForcePlainText) {
+ mTunneledConn->ForcePlainText();
+ } else if (mIsWebsocket) {
+ LOG(("SpdyConnectTransaction::MapStreamToHttpConnection %p websocket",
+ this));
+ // Let the root transaction know about us, so it can pass our own conn
+ // to the websocket.
+ mDrivingTransaction->SetH2WSTransaction(this);
+ } else {
+ mTunneledConn->SetupSecondaryTLS(this);
+ mTunneledConn->SetInSpdyTunnel(true);
+ }
+
+ // make the originating transaction stick to the tunneled conn
+ RefPtr<nsAHttpConnection> wrappedConn =
+ gHttpHandler->ConnMgr()->MakeConnectionHandle(mTunneledConn);
+ mDrivingTransaction->SetConnection(wrappedConn);
+ mDrivingTransaction->MakeSticky();
+
+ if (!mIsWebsocket) {
+ mDrivingTransaction->OnProxyConnectComplete(aHttpResponseCode);
+
+ if (aHttpResponseCode == 407) {
+ mDrivingTransaction->SetFlat407Headers(aFlat407Headers);
+ mDrivingTransaction->SetProxyConnectFailed();
+ }
+
+ // jump the priority and start the dispatcher
+ Unused << gHttpHandler->InitiateTransaction(
+ mDrivingTransaction, nsISupportsPriority::PRIORITY_HIGHEST - 60);
+ mDrivingTransaction = nullptr;
+ }
+
+ return true;
+}
+
+nsresult SpdyConnectTransaction::Flush(uint32_t count, uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ 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 %" PRIx32 "\n", this,
+ static_cast<uint32_t>(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(OnSocketThread(), "not on socket thread");
+ 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 "
+ "%" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv)));
+ CreateShimError(rv);
+ } else {
+ mConnectStringOffset += toWrite;
+ if (mConnectString.Length() == mConnectStringOffset) {
+ mConnectString.Truncate();
+ mConnectStringOffset = 0;
+ }
+ }
+ return rv;
+ }
+
+ LOG(("SpdyConnectTransaciton::ReadSegments %p connect request consumed",
+ this));
+ 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;
+ nsresult rv = Flush(count, countRead);
+ if (!(*countRead)) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ nsCOMPtr<nsIOutputStreamCallback> cb = mTunnelStreamOut->TakeCallback();
+ if (!cb) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // See if there is any more data available
+ rv = cb->OnOutputStreamReady(mTunnelStreamOut);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Write out anything that may have come out of the stream just above
+ uint32_t subtotal;
+ count -= *countRead;
+ rv = Flush(count, &subtotal);
+ *countRead += subtotal;
+
+ return rv;
+}
+
+void SpdyConnectTransaction::CreateShimError(nsresult code) {
+ LOG(("SpdyConnectTransaction::CreateShimError %p 0x%08" PRIx32, this,
+ static_cast<uint32_t>(code)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(NS_FAILED(code));
+
+ MOZ_ASSERT(!mCreateShimErrorCalled);
+ if (mCreateShimErrorCalled) {
+ return;
+ }
+ mCreateShimErrorCalled = true;
+
+ if (mTunnelStreamOut && NS_SUCCEEDED(mTunnelStreamOut->mStatus)) {
+ mTunnelStreamOut->mStatus = code;
+ }
+
+ if (mTunnelStreamIn && NS_SUCCEEDED(mTunnelStreamIn->mStatus)) {
+ mTunnelStreamIn->mStatus = code;
+ }
+
+ if (mTunnelStreamIn) {
+ nsCOMPtr<nsIInputStreamCallback> cb = mTunnelStreamIn->TakeCallback();
+ if (cb) {
+ cb->OnInputStreamReady(mTunnelStreamIn);
+ }
+ }
+
+ if (mTunnelStreamOut) {
+ nsCOMPtr<nsIOutputStreamCallback> cb = mTunnelStreamOut->TakeCallback();
+ if (cb) {
+ cb->OnOutputStreamReady(mTunnelStreamOut);
+ }
+ }
+ mCreateShimErrorCalled = false;
+}
+
+nsresult SpdyConnectTransaction::WriteDataToBuffer(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ 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 "
+ "%" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv)));
+ CreateShimError(rv);
+ }
+ return rv;
+ }
+ mInputDataUsed += *countWritten;
+ LOG(
+ ("SpdyConnectTransaction %p %d new bytes [%d total] of ciphered data "
+ "buffered\n",
+ this, *countWritten, mInputDataUsed - mInputDataOffset));
+
+ return rv;
+}
+
+nsresult SpdyConnectTransaction::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("SpdyConnectTransaction::WriteSegments %p max=%d", this, count));
+
+ // For websockets, we need to forward the initial response through to the base
+ // transaction so the normal websocket plumbing can do all the things it needs
+ // to do.
+ if (mIsWebsocket) {
+ return WebsocketWriteSegments(writer, count, countWritten);
+ }
+
+ // first call into the tunnel stream to get the demux'd data out of the
+ // spdy session.
+ nsresult rv = WriteDataToBuffer(writer, count, countWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStreamCallback> cb =
+ mTunneledConn ? mTunnelStreamIn->TakeCallback() : nullptr;
+ LOG(("SpdyConnectTransaction::WriteSegments %p cb=%p", this, cb.get()));
+
+ if (!cb) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = cb->OnInputStreamReady(mTunnelStreamIn);
+ LOG(
+ ("SpdyConnectTransaction::WriteSegments %p "
+ "after InputStreamReady callback %d total of ciphered data buffered "
+ "rv=%" PRIx32 "\n",
+ this, mInputDataUsed - mInputDataOffset, static_cast<uint32_t>(rv)));
+ LOG(
+ ("SpdyConnectTransaction::WriteSegments %p "
+ "goodput %p out %" PRId64 "\n",
+ this, mTunneledConn.get(), mTunneledConn->ContentBytesWritten()));
+ if (NS_SUCCEEDED(rv) && !mTunneledConn->ContentBytesWritten()) {
+ nsCOMPtr<nsIOutputStreamCallback> ocb = mTunnelStreamOut->TakeCallback();
+ mTunnelStreamOut->AsyncWait(ocb, 0, 0, nullptr);
+ }
+ return rv;
+}
+
+nsresult SpdyConnectTransaction::WebsocketWriteSegments(
+ nsAHttpSegmentWriter* writer, uint32_t count, uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread());
+ MOZ_ASSERT(mIsWebsocket);
+ LOG(("SpdyConnectTransaction::WebsocketWriteSegments %p max=%d", this,
+ count));
+
+ if (mDrivingTransaction && !mDrivingTransaction->IsDone()) {
+ // Transaction hasn't received end of headers yet, so keep passing data to
+ // it until it has. Then we can take over.
+ nsresult rv =
+ mDrivingTransaction->WriteSegments(writer, count, countWritten);
+ if (NS_SUCCEEDED(rv) && mDrivingTransaction->IsDone() && !mConnRefTaken) {
+ mDrivingTransaction->Close(NS_OK);
+ }
+ }
+
+ if (!mConnRefTaken) {
+ // Force driving transaction to finish so the websocket channel can get its
+ // notifications correctly and start driving.
+ MOZ_ASSERT(mDrivingTransaction);
+ mDrivingTransaction->Close(NS_OK);
+ }
+
+ nsresult rv = WriteDataToBuffer(writer, count, countWritten);
+ if (NS_SUCCEEDED(rv)) {
+ if (!mTunneledConn) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ nsCOMPtr<nsIInputStreamCallback> cb = mTunnelStreamIn->TakeCallback();
+ if (!cb) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ rv = cb->OnInputStreamReady(mTunnelStreamIn);
+ }
+
+ return rv;
+}
+
+bool SpdyConnectTransaction::ConnectedReadyForInput() {
+ return mTunneledConn && mTunnelStreamIn->HasCallback();
+}
+
+nsHttpRequestHead* SpdyConnectTransaction::RequestHead() {
+ return mRequestHead;
+}
+
+void SpdyConnectTransaction::Close(nsresult code) {
+ LOG(("SpdyConnectTransaction close %p %" PRIx32 "\n", this,
+ static_cast<uint32_t>(code)));
+
+ MOZ_ASSERT(OnSocketThread());
+
+ if (mIsWebsocket && mDrivingTransaction) {
+ mDrivingTransaction->SetH2WSTransaction(nullptr);
+ if (!mConnRefTaken) {
+ // This indicates that the websocket failed to set up, so just close down
+ // the transaction as usual.
+ mDrivingTransaction->Close(code);
+ mDrivingTransaction = nullptr;
+ }
+ }
+ NullHttpTransaction::Close(code);
+ if (NS_FAILED(code) && (code != NS_BASE_STREAM_WOULD_BLOCK)) {
+ CreateShimError(code);
+ } else {
+ CreateShimError(NS_BASE_STREAM_CLOSED);
+ }
+}
+
+void SpdyConnectTransaction::SetConnRefTaken() {
+ MOZ_ASSERT(OnSocketThread());
+
+ mConnRefTaken = true;
+ mDrivingTransaction = nullptr; // Just in case
+}
+
+already_AddRefed<nsIOutputStreamCallback> OutputStreamShim::TakeCallback() {
+ mozilla::MutexAutoLock lock(mMutex);
+ return mCallback.forget();
+}
+
+class WebsocketHasDataToWrite final : public Runnable {
+ public:
+ explicit WebsocketHasDataToWrite(OutputStreamShim* shim)
+ : Runnable("WebsocketHasDataToWrite"), mShim(shim) {}
+
+ ~WebsocketHasDataToWrite() = default;
+
+ NS_IMETHOD Run() override { return mShim->CallTransactionHasDataToWrite(); }
+
+ [[nodiscard]] nsresult Dispatch() {
+ if (OnSocketThread()) {
+ return Run();
+ }
+
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ return sts->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ private:
+ RefPtr<OutputStreamShim> mShim;
+};
+
+NS_IMETHODIMP
+OutputStreamShim::AsyncWait(nsIOutputStreamCallback* callback,
+ unsigned int flags, unsigned int requestedCount,
+ nsIEventTarget* target) {
+ if (mIsWebsocket) {
+ // With websockets, AsyncWait may be called from the main thread, but the
+ // target is on the socket thread. That's all we really care about.
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT((!target && !callback) || (target == sts));
+ if (target && (target != sts)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ bool currentThread;
+
+ if (target && (NS_FAILED(target->IsOnCurrentThread(&currentThread)) ||
+ !currentThread)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ LOG(("OutputStreamShim::AsyncWait %p callback %p\n", this, callback));
+
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ mCallback = callback;
+ }
+
+ RefPtr<WebsocketHasDataToWrite> wsdw = new WebsocketHasDataToWrite(this);
+ Unused << wsdw->Dispatch();
+
+ return NS_OK;
+}
+
+class OutputCloseTransaction final : public Runnable {
+ public:
+ OutputCloseTransaction(OutputStreamShim* shim, nsresult reason)
+ : Runnable("OutputCloseTransaction"), mShim(shim), mReason(reason) {}
+
+ ~OutputCloseTransaction() = default;
+
+ NS_IMETHOD Run() override { return mShim->CloseTransaction(mReason); }
+
+ private:
+ RefPtr<OutputStreamShim> mShim;
+ nsresult mReason;
+};
+
+NS_IMETHODIMP
+OutputStreamShim::CloseWithStatus(nsresult reason) {
+ if (!OnSocketThread()) {
+ RefPtr<OutputCloseTransaction> oct =
+ new OutputCloseTransaction(this, reason);
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ return sts->Dispatch(oct, nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ return CloseTransaction(reason);
+}
+
+nsresult OutputStreamShim::CloseTransaction(nsresult reason) {
+ MOZ_ASSERT(OnSocketThread());
+ RefPtr<NullHttpTransaction> baseTrans = mWeakTrans->QueryTransaction();
+ 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() {
+ MOZ_ASSERT(OnSocketThread());
+ RefPtr<NullHttpTransaction> baseTrans = mWeakTrans->QueryTransaction();
+ 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;
+}
+
+nsresult OutputStreamShim::CallTransactionHasDataToWrite() {
+ MOZ_ASSERT(OnSocketThread());
+ RefPtr<NullHttpTransaction> baseTrans = mWeakTrans->QueryTransaction();
+ 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::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ RefPtr<NullHttpTransaction> baseTrans = mWeakTrans->QueryTransaction();
+ 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) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: OutputStreamShim::WriteFrom %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: OutputStreamShim::WriteSegments %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::IsNonBlocking(bool* _retval) {
+ *_retval = true;
+ return NS_OK;
+}
+
+already_AddRefed<nsIInputStreamCallback> InputStreamShim::TakeCallback() {
+ mozilla::MutexAutoLock lock(mMutex);
+ return mCallback.forget();
+}
+
+bool InputStreamShim::HasCallback() {
+ mozilla::MutexAutoLock lock(mMutex);
+ return mCallback != nullptr;
+}
+
+class CheckAvailData final : public Runnable {
+ public:
+ explicit CheckAvailData(InputStreamShim* shim)
+ : Runnable("CheckAvailData"), mShim(shim) {}
+
+ ~CheckAvailData() = default;
+
+ NS_IMETHOD Run() override {
+ uint64_t avail = 0;
+ if (NS_SUCCEEDED(mShim->Available(&avail)) && avail) {
+ nsCOMPtr<nsIInputStreamCallback> cb = mShim->TakeCallback();
+ if (cb) {
+ cb->OnInputStreamReady(mShim);
+ }
+ }
+ return NS_OK;
+ }
+
+ [[nodiscard]] nsresult Dispatch() {
+ // Dispatch the event even if we're on socket thread to avoid closing and
+ // destructing Http2Session in case this call is comming from
+ // Http2Session::ReadSegments() and the callback closes the transaction in
+ // OnInputStreamRead().
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ return sts->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ private:
+ RefPtr<InputStreamShim> mShim;
+};
+
+NS_IMETHODIMP
+InputStreamShim::AsyncWait(nsIInputStreamCallback* callback, unsigned int flags,
+ unsigned int requestedCount,
+ nsIEventTarget* target) {
+ if (mIsWebsocket) {
+ // With websockets, AsyncWait may be called from the main thread, but the
+ // target is on the socket thread. That's all we really care about.
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT((!target && !callback) || (target == sts));
+ if (target && (target != sts)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ bool currentThread;
+
+ if (target && (NS_FAILED(target->IsOnCurrentThread(&currentThread)) ||
+ !currentThread)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ LOG(("InputStreamShim::AsyncWait %p callback %p\n", this, callback));
+
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ mCallback = callback;
+ }
+
+ if (callback) {
+ RefPtr<CheckAvailData> cad = new CheckAvailData(this);
+ Unused << cad->Dispatch();
+ }
+
+ return NS_OK;
+}
+
+class InputCloseTransaction final : public Runnable {
+ public:
+ InputCloseTransaction(InputStreamShim* shim, nsresult reason)
+ : Runnable("InputCloseTransaction"), mShim(shim), mReason(reason) {}
+
+ ~InputCloseTransaction() = default;
+
+ NS_IMETHOD Run() override { return mShim->CloseTransaction(mReason); }
+
+ private:
+ RefPtr<InputStreamShim> mShim;
+ nsresult mReason;
+};
+
+NS_IMETHODIMP
+InputStreamShim::CloseWithStatus(nsresult reason) {
+ if (!OnSocketThread()) {
+ RefPtr<InputCloseTransaction> ict = new InputCloseTransaction(this, reason);
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ return sts->Dispatch(ict, nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ return CloseTransaction(reason);
+}
+
+nsresult InputStreamShim::CloseTransaction(nsresult reason) {
+ MOZ_ASSERT(OnSocketThread());
+ RefPtr<NullHttpTransaction> baseTrans = mWeakTrans->QueryTransaction();
+ 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 = mWeakTrans->QueryTransaction();
+ 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(OnSocketThread(), "not on socket thread");
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ RefPtr<NullHttpTransaction> baseTrans = mWeakTrans->QueryTransaction();
+ 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) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: InputStreamShim::ReadSegments %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InputStreamShim::IsNonBlocking(bool* _retval) {
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetKeepaliveEnabled(bool aKeepaliveEnabled) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::SetKeepaliveEnabled %p called", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetKeepaliveVals(int32_t keepaliveIdleTime,
+ int32_t keepaliveRetryInterval) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::SetKeepaliveVals %p called", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetSecurityCallbacks(
+ nsIInterfaceRequestor** aSecurityCallbacks) {
+ if (mIsWebsocket) {
+ nsCOMPtr<nsIInterfaceRequestor> out(mSecurityCallbacks);
+ *aSecurityCallbacks = out.forget().take();
+ return NS_OK;
+ }
+
+ return mWrapped->GetSecurityCallbacks(aSecurityCallbacks);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetSecurityCallbacks(
+ nsIInterfaceRequestor* aSecurityCallbacks) {
+ if (mIsWebsocket) {
+ mSecurityCallbacks = aSecurityCallbacks;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIInputStream** _retval) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::OpenInputStream %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIOutputStream** _retval) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::OpenOutputStream %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::Close(nsresult aReason) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::Close %p", this));
+ } else {
+ LOG(("SocketTransportShim::Close %p", this));
+ }
+
+ if (gHttpHandler->Bug1563538()) {
+ // Must always post, because mSession->CloseTransaction releases the
+ // Http2Stream which is still on stack.
+ RefPtr<SocketTransportShim> self(this);
+
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ Unused << sts->Dispatch(NS_NewRunnableFunction(
+ "SocketTransportShim::Close", [self = std::move(self), aReason]() {
+ RefPtr<NullHttpTransaction> baseTrans =
+ self->mWeakTrans->QueryTransaction();
+ if (!baseTrans) {
+ return;
+ }
+ SpdyConnectTransaction* trans =
+ baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return;
+ }
+
+ trans->mSession->CloseTransaction(trans, aReason);
+ }));
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetEventSink(nsITransportEventSink* aSink,
+ nsIEventTarget* aEventTarget) {
+ if (mIsWebsocket) {
+ // Need to pretend, since websockets expect this to work
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::Bind(NetAddr* aLocalAddr) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::Bind %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetFirstRetryError(nsresult* aFirstRetryError) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::GetFirstRetryError %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetEchConfigUsed(bool* aEchConfigUsed) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::GetEchConfigUsed %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetEchConfig(const nsACString& aEchConfig) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::SetEchConfig %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::ResolvedByTRR(bool* aResolvedByTRR) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::IsTRR %p", this));
+ }
+ 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_PTR(IsAlive, bool);
+FWD_TS_PTR(GetConnectionFlags, uint32_t);
+FWD_TS(SetConnectionFlags, uint32_t);
+FWD_TS(SetIsPrivate, bool);
+FWD_TS_PTR(GetTlsFlags, uint32_t);
+FWD_TS(SetTlsFlags, uint32_t);
+FWD_TS_PTR(GetRecvBufferSize, uint32_t);
+FWD_TS(SetRecvBufferSize, uint32_t);
+FWD_TS_PTR(GetResetIPFamilyPreference, bool);
+
+nsresult SocketTransportShim::GetOriginAttributes(
+ mozilla::OriginAttributes* aOriginAttributes) {
+ return mWrapped->GetOriginAttributes(aOriginAttributes);
+}
+
+nsresult SocketTransportShim::SetOriginAttributes(
+ const mozilla::OriginAttributes& 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::SetTimeout(uint32_t aType, uint32_t aValue) {
+ return mWrapped->SetTimeout(aType, aValue);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetReuseAddrPort(bool aReuseAddrPort) {
+ return mWrapped->SetReuseAddrPort(aReuseAddrPort);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetLinger(bool aPolarity, int16_t aTimeout) {
+ return mWrapped->SetLinger(aPolarity, aTimeout);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetQoSBits(uint8_t* aQoSBits) {
+ return mWrapped->GetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetQoSBits(uint8_t aQoSBits) {
+ return mWrapped->SetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetFastOpenCallback(TCPFastOpen* aFastOpen) {
+ return mWrapped->SetFastOpenCallback(aFastOpen);
+}
+
+NS_IMPL_ISUPPORTS(TLSFilterTransaction, nsITimerCallback, nsINamed)
+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