/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et tw=80 : */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // HttpLog.h should generally be included first #include "HttpLog.h" // Log on level :5, instead of default :4. #undef LOG #define LOG(args) LOG5(args) #undef LOG_ENABLED #define LOG_ENABLED() LOG5_ENABLED() #include "nsHttpHandler.h" #include "Http2StreamTunnel.h" #include "nsHttpConnectionInfo.h" #include "nsQueryObject.h" #include "nsProxyRelease.h" namespace mozilla::net { bool Http2StreamTunnel::DispatchRelease() { if (OnSocketThread()) { return false; } gSocketTransportService->Dispatch( NewNonOwningRunnableMethod("net::Http2StreamTunnel::Release", this, &Http2StreamTunnel::Release), NS_DISPATCH_NORMAL); return true; } NS_IMPL_ADDREF(Http2StreamTunnel) NS_IMETHODIMP_(MozExternalRefCountType) Http2StreamTunnel::Release() { nsrefcnt count = mRefCnt - 1; if (DispatchRelease()) { // Redispatched to the socket thread. return count; } MOZ_ASSERT(0 != mRefCnt, "dup release"); count = --mRefCnt; NS_LOG_RELEASE(this, count, "Http2StreamTunnel"); if (0 == count) { mRefCnt = 1; delete (this); return 0; } return count; } NS_INTERFACE_MAP_BEGIN(Http2StreamTunnel) NS_INTERFACE_MAP_ENTRY(nsITransport) NS_INTERFACE_MAP_ENTRY_CONCRETE(Http2StreamTunnel) NS_INTERFACE_MAP_ENTRY(nsITransport) NS_INTERFACE_MAP_ENTRY(nsISocketTransport) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_END_INHERITING(Http2StreamTunnel) Http2StreamTunnel::Http2StreamTunnel(Http2Session* session, int32_t priority, uint64_t bcId, nsHttpConnectionInfo* aConnectionInfo) : Http2StreamBase(0, session, priority, bcId), mConnectionInfo(aConnectionInfo) {} Http2StreamTunnel::~Http2StreamTunnel() { ClearTransactionsBlockedOnTunnel(); } void Http2StreamTunnel::HandleResponseHeaders(nsACString& aHeadersOut, int32_t httpResponseCode) {} // TODO We do not need this. Fix in bug 1772212. void Http2StreamTunnel::ClearTransactionsBlockedOnTunnel() { nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnectionInfo); if (NS_FAILED(rv)) { LOG3( ("Http2StreamTunnel::ClearTransactionsBlockedOnTunnel %p\n" " ProcessPendingQ failed: %08x\n", this, static_cast(rv))); } } NS_IMETHODIMP Http2StreamTunnel::SetKeepaliveEnabled(bool aKeepaliveEnabled) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP Http2StreamTunnel::SetKeepaliveVals(int32_t keepaliveIdleTime, int32_t keepaliveRetryInterval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP Http2StreamTunnel::GetSecurityCallbacks( nsIInterfaceRequestor** aSecurityCallbacks) { return mSocketTransport->GetSecurityCallbacks(aSecurityCallbacks); } NS_IMETHODIMP Http2StreamTunnel::SetSecurityCallbacks( nsIInterfaceRequestor* aSecurityCallbacks) { return NS_OK; } NS_IMETHODIMP Http2StreamTunnel::OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize, uint32_t aSegmentCount, nsIInputStream** _retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP Http2StreamTunnel::OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize, uint32_t aSegmentCount, nsIOutputStream** _retval) { return NS_ERROR_NOT_IMPLEMENTED; } void Http2StreamTunnel::CloseStream(nsresult aReason) { LOG(("Http2StreamTunnel::CloseStream this=%p", this)); RefPtr session = Session(); if (NS_SUCCEEDED(mCondition)) { mSession = nullptr; // Let the session pickup that the stream has been closed. mCondition = aReason; if (NS_SUCCEEDED(aReason)) { aReason = NS_BASE_STREAM_CLOSED; } mOutput->OnSocketReady(aReason); mInput->OnSocketReady(aReason); } } NS_IMETHODIMP Http2StreamTunnel::Close(nsresult aReason) { LOG(("Http2StreamTunnel::Close this=%p", this)); RefPtr session = Session(); if (NS_SUCCEEDED(mCondition)) { mSession = nullptr; if (NS_SUCCEEDED(aReason)) { aReason = NS_BASE_STREAM_CLOSED; } mOutput->CloseWithStatus(aReason); mInput->CloseWithStatus(aReason); // Let the session pickup that the stream has been closed. mCondition = aReason; } return NS_OK; } NS_IMETHODIMP Http2StreamTunnel::SetEventSink(nsITransportEventSink* aSink, nsIEventTarget* aEventTarget) { return NS_OK; } NS_IMETHODIMP Http2StreamTunnel::Bind(NetAddr* aLocalAddr) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP Http2StreamTunnel::GetEchConfigUsed(bool* aEchConfigUsed) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP Http2StreamTunnel::SetEchConfig(const nsACString& aEchConfig) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP Http2StreamTunnel::ResolvedByTRR(bool* aResolvedByTRR) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP Http2StreamTunnel::IsAlive(bool* aAlive) { RefPtr session = Session(); if (mSocketTransport && session) { return mSocketTransport->IsAlive(aAlive); } *aAlive = false; return NS_OK; } #define FWD_TS_T_PTR(fx, ts) \ NS_IMETHODIMP \ Http2StreamTunnel::fx(ts* arg) { return mSocketTransport->fx(arg); } #define FWD_TS_T_ADDREF(fx, ts) \ NS_IMETHODIMP \ Http2StreamTunnel::fx(ts** arg) { return mSocketTransport->fx(arg); } #define FWD_TS_T(fx, ts) \ NS_IMETHODIMP \ Http2StreamTunnel::fx(ts arg) { return mSocketTransport->fx(arg); } FWD_TS_T_PTR(GetKeepaliveEnabled, bool); FWD_TS_T_PTR(GetSendBufferSize, uint32_t); FWD_TS_T(SetSendBufferSize, uint32_t); FWD_TS_T_PTR(GetPort, int32_t); FWD_TS_T_PTR(GetPeerAddr, mozilla::net::NetAddr); FWD_TS_T_PTR(GetSelfAddr, mozilla::net::NetAddr); FWD_TS_T_ADDREF(GetScriptablePeerAddr, nsINetAddr); FWD_TS_T_ADDREF(GetScriptableSelfAddr, nsINetAddr); FWD_TS_T_ADDREF(GetTlsSocketControl, nsITLSSocketControl); FWD_TS_T_PTR(GetConnectionFlags, uint32_t); FWD_TS_T(SetConnectionFlags, uint32_t); FWD_TS_T(SetIsPrivate, bool); FWD_TS_T_PTR(GetTlsFlags, uint32_t); FWD_TS_T(SetTlsFlags, uint32_t); FWD_TS_T_PTR(GetRecvBufferSize, uint32_t); FWD_TS_T(SetRecvBufferSize, uint32_t); FWD_TS_T_PTR(GetResetIPFamilyPreference, bool); nsresult Http2StreamTunnel::GetOriginAttributes( mozilla::OriginAttributes* aOriginAttributes) { return mSocketTransport->GetOriginAttributes(aOriginAttributes); } nsresult Http2StreamTunnel::SetOriginAttributes( const mozilla::OriginAttributes& aOriginAttributes) { return mSocketTransport->SetOriginAttributes(aOriginAttributes); } NS_IMETHODIMP Http2StreamTunnel::GetScriptableOriginAttributes( JSContext* aCx, JS::MutableHandle aOriginAttributes) { return mSocketTransport->GetScriptableOriginAttributes(aCx, aOriginAttributes); } NS_IMETHODIMP Http2StreamTunnel::SetScriptableOriginAttributes( JSContext* aCx, JS::Handle aOriginAttributes) { return mSocketTransport->SetScriptableOriginAttributes(aCx, aOriginAttributes); } NS_IMETHODIMP Http2StreamTunnel::GetHost(nsACString& aHost) { return mSocketTransport->GetHost(aHost); } NS_IMETHODIMP Http2StreamTunnel::GetTimeout(uint32_t aType, uint32_t* _retval) { return mSocketTransport->GetTimeout(aType, _retval); } NS_IMETHODIMP Http2StreamTunnel::SetTimeout(uint32_t aType, uint32_t aValue) { return mSocketTransport->SetTimeout(aType, aValue); } NS_IMETHODIMP Http2StreamTunnel::SetReuseAddrPort(bool aReuseAddrPort) { return mSocketTransport->SetReuseAddrPort(aReuseAddrPort); } NS_IMETHODIMP Http2StreamTunnel::SetLinger(bool aPolarity, int16_t aTimeout) { return mSocketTransport->SetLinger(aPolarity, aTimeout); } NS_IMETHODIMP Http2StreamTunnel::GetQoSBits(uint8_t* aQoSBits) { return mSocketTransport->GetQoSBits(aQoSBits); } NS_IMETHODIMP Http2StreamTunnel::SetQoSBits(uint8_t aQoSBits) { return mSocketTransport->SetQoSBits(aQoSBits); } NS_IMETHODIMP Http2StreamTunnel::GetRetryDnsIfPossible(bool* aRetry) { return mSocketTransport->GetRetryDnsIfPossible(aRetry); } NS_IMETHODIMP Http2StreamTunnel::GetStatus(nsresult* aStatus) { return mSocketTransport->GetStatus(aStatus); } already_AddRefed Http2StreamTunnel::CreateHttpConnection( nsAHttpTransaction* httpTransaction, nsIInterfaceRequestor* aCallbacks, PRIntervalTime aRtt) { mInput = new InputStreamTunnel(this); mOutput = new OutputStreamTunnel(this); RefPtr conn = new nsHttpConnection(); conn->SetTransactionCaps(httpTransaction->Caps()); nsresult rv = conn->Init(httpTransaction->ConnectionInfo(), gHttpHandler->ConnMgr()->MaxRequestDelay(), this, mInput, mOutput, true, NS_OK, aCallbacks, aRtt, false); MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); mTransaction = httpTransaction; return conn.forget(); } nsresult Http2StreamTunnel::CallToReadData(uint32_t count, uint32_t* countRead) { LOG(("Http2StreamTunnel::CallToReadData this=%p", this)); return mOutput->OnSocketReady(NS_OK); } nsresult Http2StreamTunnel::CallToWriteData(uint32_t count, uint32_t* countWritten) { LOG(("Http2StreamTunnel::CallToWriteData this=%p", this)); if (!mInput->HasCallback()) { return NS_BASE_STREAM_WOULD_BLOCK; } return mInput->OnSocketReady(NS_OK); } nsresult Http2StreamTunnel::GenerateHeaders(nsCString& aCompressedData, uint8_t& firstFrameFlags) { nsAutoCString authorityHeader; authorityHeader = mConnectionInfo->GetOrigin(); authorityHeader.Append(':'); authorityHeader.AppendInt(mConnectionInfo->OriginPort()); RefPtr session = Session(); LOG3(("Http2StreamTunnel %p Stream ID 0x%X [session=%p] for %s\n", this, mStreamID, session.get(), authorityHeader.get())); mRequestBodyLenRemaining = 0x0fffffffffffffffULL; nsresult rv = session->Compressor()->EncodeHeaderBlock( mFlatHttpRequestHeaders, "CONNECT"_ns, EmptyCString(), authorityHeader, EmptyCString(), EmptyCString(), true, aCompressedData); NS_ENSURE_SUCCESS(rv, rv); // The size of the input headers is approximate uint32_t ratio = aCompressedData.Length() * 100 / (11 + authorityHeader.Length() + mFlatHttpRequestHeaders.Length()); Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio); return NS_OK; } OutputStreamTunnel::OutputStreamTunnel(Http2StreamTunnel* aStream) { mWeakStream = do_GetWeakReference(aStream); } OutputStreamTunnel::~OutputStreamTunnel() { NS_ProxyRelease("OutputStreamTunnel::~OutputStreamTunnel", gSocketTransportService, mWeakStream.forget()); } nsresult OutputStreamTunnel::OnSocketReady(nsresult condition) { LOG(("OutputStreamTunnel::OnSocketReady [this=%p cond=%" PRIx32 " callback=%p]\n", this, static_cast(condition), mCallback.get())); MOZ_ASSERT(OnSocketThread(), "not on socket thread"); nsCOMPtr callback; // update condition, but be careful not to erase an already // existing error condition. if (NS_SUCCEEDED(mCondition)) { mCondition = condition; } callback = std::move(mCallback); nsresult rv = NS_OK; if (callback) { rv = callback->OnOutputStreamReady(this); MaybeSetRequestDone(callback); } return rv; } void OutputStreamTunnel::MaybeSetRequestDone( nsIOutputStreamCallback* aCallback) { RefPtr conn = do_QueryObject(aCallback); if (!conn) { return; } RefPtr tunnel; nsresult rv = GetStream(getter_AddRefs(tunnel)); if (NS_FAILED(rv)) { return; } if (conn->RequestDone()) { tunnel->SetRequestDone(); } } NS_IMPL_ISUPPORTS(OutputStreamTunnel, nsIOutputStream, nsIAsyncOutputStream) NS_IMETHODIMP OutputStreamTunnel::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); } NS_IMETHODIMP OutputStreamTunnel::Flush() { return NS_OK; } NS_IMETHODIMP OutputStreamTunnel::Write(const char* buf, uint32_t count, uint32_t* countWritten) { LOG(("OutputStreamTunnel::Write [this=%p count=%u]\n", this, count)); *countWritten = 0; if (NS_FAILED(mCondition)) { return mCondition; } RefPtr tunnel; nsresult rv = GetStream(getter_AddRefs(tunnel)); if (NS_FAILED(rv)) { return rv; } return tunnel->OnReadSegment(buf, count, countWritten); } NS_IMETHODIMP OutputStreamTunnel::WriteSegments(nsReadSegmentFun reader, void* closure, uint32_t count, uint32_t* countRead) { // stream is unbuffered return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP OutputStreamTunnel::WriteFrom(nsIInputStream* stream, uint32_t count, uint32_t* countRead) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP OutputStreamTunnel::IsNonBlocking(bool* nonblocking) { *nonblocking = true; return NS_OK; } NS_IMETHODIMP OutputStreamTunnel::CloseWithStatus(nsresult reason) { LOG(("OutputStreamTunnel::CloseWithStatus [this=%p reason=%" PRIx32 "]\n", this, static_cast(reason))); mCondition = reason; RefPtr tunnel = do_QueryReferent(mWeakStream); mWeakStream = nullptr; if (!tunnel) { return NS_OK; } RefPtr session = tunnel->Session(); if (!session) { return NS_OK; } session->CleanupStream(tunnel, reason, Http2Session::CANCEL_ERROR); return NS_OK; } NS_IMETHODIMP OutputStreamTunnel::AsyncWait(nsIOutputStreamCallback* callback, uint32_t flags, uint32_t amount, nsIEventTarget* target) { LOG(("OutputStreamTunnel::AsyncWait [this=%p]\n", this)); // The following parametr are not used: MOZ_ASSERT(!flags); MOZ_ASSERT(!amount); Unused << target; RefPtr self(this); if (NS_FAILED(mCondition)) { Unused << NS_DispatchToCurrentThread(NS_NewRunnableFunction( "OutputStreamTunnel::CallOnSocketReady", [self{std::move(self)}]() { self->OnSocketReady(NS_OK); })); } else if (callback) { // Inform the proxy connection that the inner connetion wants to // read data. RefPtr tunnel; nsresult rv = GetStream(getter_AddRefs(tunnel)); if (NS_FAILED(rv)) { return rv; } RefPtr session; rv = GetSession(getter_AddRefs(session)); if (NS_FAILED(rv)) { return rv; } session->TransactionHasDataToWrite(tunnel); } mCallback = callback; return NS_OK; } InputStreamTunnel::InputStreamTunnel(Http2StreamTunnel* aStream) { mWeakStream = do_GetWeakReference(aStream); } InputStreamTunnel::~InputStreamTunnel() { NS_ProxyRelease("InputStreamTunnel::~InputStreamTunnel", gSocketTransportService, mWeakStream.forget()); } nsresult InputStreamTunnel::OnSocketReady(nsresult condition) { LOG(("InputStreamTunnel::OnSocketReady [this=%p cond=%" PRIx32 "]\n", this, static_cast(condition))); MOZ_ASSERT(OnSocketThread(), "not on socket thread"); nsCOMPtr callback; // update condition, but be careful not to erase an already // existing error condition. if (NS_SUCCEEDED(mCondition)) { mCondition = condition; } callback = std::move(mCallback); return callback ? callback->OnInputStreamReady(this) : NS_OK; } NS_IMPL_ISUPPORTS(InputStreamTunnel, nsIInputStream, nsIAsyncInputStream) NS_IMETHODIMP InputStreamTunnel::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); } NS_IMETHODIMP InputStreamTunnel::Available(uint64_t* avail) { LOG(("InputStreamTunnel::Available [this=%p]\n", this)); if (NS_FAILED(mCondition)) { return mCondition; } return NS_ERROR_FAILURE; } NS_IMETHODIMP InputStreamTunnel::Read(char* buf, uint32_t count, uint32_t* countRead) { LOG(("InputStreamTunnel::Read [this=%p count=%u]\n", this, count)); *countRead = 0; if (NS_FAILED(mCondition)) { return mCondition; } RefPtr tunnel; nsresult rv = GetStream(getter_AddRefs(tunnel)); if (NS_FAILED(rv)) { return rv; } return tunnel->OnWriteSegment(buf, count, countRead); } NS_IMETHODIMP InputStreamTunnel::ReadSegments(nsWriteSegmentFun writer, void* closure, uint32_t count, uint32_t* countRead) { // socket stream is unbuffered return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP InputStreamTunnel::IsNonBlocking(bool* nonblocking) { *nonblocking = true; return NS_OK; } NS_IMETHODIMP InputStreamTunnel::CloseWithStatus(nsresult reason) { LOG(("InputStreamTunnel::CloseWithStatus [this=%p reason=%" PRIx32 "]\n", this, static_cast(reason))); mCondition = reason; RefPtr tunnel = do_QueryReferent(mWeakStream); mWeakStream = nullptr; if (!tunnel) { return NS_OK; } RefPtr session = tunnel->Session(); if (!session) { return NS_OK; } session->CleanupStream(tunnel, reason, Http2Session::CANCEL_ERROR); return NS_OK; } NS_IMETHODIMP InputStreamTunnel::AsyncWait(nsIInputStreamCallback* callback, uint32_t flags, uint32_t amount, nsIEventTarget* target) { LOG(("InputStreamTunnel::AsyncWait [this=%p mCondition=%x]\n", this, static_cast(mCondition))); // The following parametr are not used: MOZ_ASSERT(!flags); MOZ_ASSERT(!amount); Unused << target; RefPtr self(this); if (NS_FAILED(mCondition)) { Unused << NS_DispatchToCurrentThread(NS_NewRunnableFunction( "InputStreamTunnel::CallOnSocketReady", [self{std::move(self)}]() { self->OnSocketReady(NS_OK); })); } else if (callback) { // Inform the proxy connection that the inner connetion wants to // read data. RefPtr tunnel; nsresult rv = GetStream(getter_AddRefs(tunnel)); if (NS_FAILED(rv)) { return rv; } RefPtr session; rv = GetSession(getter_AddRefs(session)); if (NS_FAILED(rv)) { return rv; } if (tunnel->DataBuffered()) { session->TransactionHasDataToRecv(tunnel); } } mCallback = callback; return NS_OK; } nsresult OutputStreamTunnel::GetStream(Http2StreamTunnel** aStream) { RefPtr tunnel = do_QueryReferent(mWeakStream); MOZ_ASSERT(tunnel); if (!tunnel) { return NS_ERROR_UNEXPECTED; } tunnel.forget(aStream); return NS_OK; } nsresult OutputStreamTunnel::GetSession(Http2Session** aSession) { RefPtr tunnel; nsresult rv = GetStream(getter_AddRefs(tunnel)); if (NS_FAILED(rv)) { return rv; } RefPtr session = tunnel->Session(); MOZ_ASSERT(session); if (!session) { return NS_ERROR_UNEXPECTED; } session.forget(aSession); return NS_OK; } nsresult InputStreamTunnel::GetStream(Http2StreamTunnel** aStream) { RefPtr tunnel = do_QueryReferent(mWeakStream); MOZ_ASSERT(tunnel); if (!tunnel) { return NS_ERROR_UNEXPECTED; } tunnel.forget(aStream); return NS_OK; } nsresult InputStreamTunnel::GetSession(Http2Session** aSession) { RefPtr tunnel; nsresult rv = GetStream(getter_AddRefs(tunnel)); if (NS_FAILED(rv)) { return rv; } RefPtr session = tunnel->Session(); if (!session) { return NS_ERROR_UNEXPECTED; } session.forget(aSession); return NS_OK; } Http2StreamWebSocket::Http2StreamWebSocket( Http2Session* session, int32_t priority, uint64_t bcId, nsHttpConnectionInfo* aConnectionInfo) : Http2StreamTunnel(session, priority, bcId, aConnectionInfo) { LOG(("Http2StreamWebSocket ctor:%p", this)); } Http2StreamWebSocket::~Http2StreamWebSocket() { LOG(("Http2StreamWebSocket dtor:%p", this)); } nsresult Http2StreamWebSocket::GenerateHeaders(nsCString& aCompressedData, uint8_t& firstFrameFlags) { nsHttpRequestHead* head = mTransaction->RequestHead(); nsAutoCString authorityHeader; nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader); NS_ENSURE_SUCCESS(rv, rv); RefPtr session = Session(); LOG3(("Http2StreamWebSocket %p Stream ID 0x%X [session=%p] for %s\n", this, mStreamID, session.get(), authorityHeader.get())); nsDependentCString scheme(head->IsHTTPS() ? "https" : "http"); nsAutoCString path; head->Path(path); rv = session->Compressor()->EncodeHeaderBlock( mFlatHttpRequestHeaders, "CONNECT"_ns, path, authorityHeader, scheme, "websocket"_ns, false, aCompressedData); NS_ENSURE_SUCCESS(rv, rv); mRequestBodyLenRemaining = 0x0fffffffffffffffULL; // The size of the input headers is approximate uint32_t ratio = aCompressedData.Length() * 100 / (11 + authorityHeader.Length() + mFlatHttpRequestHeaders.Length()); Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio); return NS_OK; } void Http2StreamWebSocket::CloseStream(nsresult aReason) { LOG(("Http2StreamWebSocket::CloseStream this=%p aReason=%x", this, static_cast(aReason))); if (mTransaction) { mTransaction->Close(aReason); mTransaction = nullptr; } Http2StreamTunnel::CloseStream(aReason); } } // namespace mozilla::net