Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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,
#include "TCPServerSocket.h"
#include "TCPSocket.h"
#include "TCPSocketChild.h"
#include "TCPSocketParent.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/TCPSocketBinding.h"
#include "mozilla/dom/TCPSocketErrorEvent.h"
#include "mozilla/dom/TCPSocketErrorEventBinding.h"
#include "mozilla/dom/TCPSocketEvent.h"
#include "mozilla/dom/TCPSocketEventBinding.h"
#include "mozilla/dom/ToJSValue.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsIArrayBufferInputStream.h"
#include "nsIAsyncInputStream.h"
#include "nsIAsyncStreamCopier.h"
#include "nsIBinaryInputStream.h"
#include "nsICancelable.h"
#include "nsIChannel.h"
#include "nsIInputStream.h"
#include "nsIInputStreamPump.h"
#include "nsIMultiplexInputStream.h"
#include "nsINSSErrorsService.h"
#include "nsIObserverService.h"
#include "nsIOutputStream.h"
#include "nsIProtocolProxyService.h"
#include "nsIScriptableInputStream.h"
#include "nsISocketTransport.h"
#include "nsISocketTransportService.h"
#include "nsISupportsPrimitives.h"
#include "nsITLSSocketControl.h"
#include "nsITransport.h"
#include "nsIURIMutator.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsStringStream.h"
#include "secerr.h"
#include "sslerr.h"
using namespace mozilla::dom;
NS_IMPL_CYCLE_COLLECTION(LegacyMozTCPSocket, mGlobal)
NS_IMPL_CYCLE_COLLECTING_ADDREF(LegacyMozTCPSocket)
NS_IMPL_CYCLE_COLLECTING_RELEASE(LegacyMozTCPSocket)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LegacyMozTCPSocket)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
LegacyMozTCPSocket::LegacyMozTCPSocket(nsPIDOMWindowInner* aWindow)
: mGlobal(do_QueryInterface(aWindow)) {}
LegacyMozTCPSocket::~LegacyMozTCPSocket() = default;
already_AddRefed<TCPSocket> LegacyMozTCPSocket::Open(
const nsAString& aHost, uint16_t aPort, const SocketOptions& aOptions,
mozilla::ErrorResult& aRv) {
AutoJSAPI api;
if (NS_WARN_IF(!api.Init(mGlobal))) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
GlobalObject globalObj(api.cx(), mGlobal->GetGlobalJSObject());
return TCPSocket::Constructor(globalObj, aHost, aPort, aOptions, aRv);
}
already_AddRefed<TCPServerSocket> LegacyMozTCPSocket::Listen(
uint16_t aPort, const ServerSocketOptions& aOptions, uint16_t aBacklog,
mozilla::ErrorResult& aRv) {
AutoJSAPI api;
if (NS_WARN_IF(!api.Init(mGlobal))) {
return nullptr;
}
GlobalObject globalObj(api.cx(), mGlobal->GetGlobalJSObject());
return TCPServerSocket::Constructor(globalObj, aPort, aOptions, aBacklog,
aRv);
}
bool LegacyMozTCPSocket::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aReflector) {
return LegacyMozTCPSocket_Binding::Wrap(aCx, this, aGivenProto, aReflector);
}
NS_IMPL_CYCLE_COLLECTION_CLASS(TCPSocket)
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(TCPSocket, DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TCPSocket,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransport)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketInputStream)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketOutputStream)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamPump)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamScriptable)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamBinary)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingDataAfterStartTLS)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketBridgeChild)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketBridgeParent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TCPSocket, DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransport)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketInputStream)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketOutputStream)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamPump)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamScriptable)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamBinary)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingDataAfterStartTLS)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketBridgeChild)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketBridgeParent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_ADDREF_INHERITED(TCPSocket, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(TCPSocket, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocket)
NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsITCPSocketCallback)
NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
TCPSocket::TCPSocket(nsIGlobalObject* aGlobal, const nsAString& aHost,
uint16_t aPort, bool aSsl, bool aUseArrayBuffers)
: DOMEventTargetHelper(aGlobal),
mReadyState(TCPReadyState::Closed),
mUseArrayBuffers(aUseArrayBuffers),
mHost(aHost),
mPort(aPort),
mSsl(aSsl),
mAsyncCopierActive(false),
mWaitingForDrain(false),
mInnerWindowID(0),
mBufferedAmount(0),
mSuspendCount(0),
mTrackingNumber(0),
mWaitingForStartTLS(false),
mObserversActive(false) {
if (aGlobal) {
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
if (window) {
mInnerWindowID = window->WindowID();
}
}
}
TCPSocket::~TCPSocket() {
if (mObserversActive) {
nsCOMPtr<nsIObserverService> obs =
do_GetService("@mozilla.org/observer-service;1");
if (obs) {
obs->RemoveObserver(this, "inner-window-destroyed");
obs->RemoveObserver(this, "profile-change-net-teardown");
}
}
}
nsresult TCPSocket::CreateStream() {
nsresult rv =
mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(mSocketInputStream));
NS_ENSURE_SUCCESS(rv, rv);
rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
getter_AddRefs(mSocketOutputStream));
NS_ENSURE_SUCCESS(rv, rv);
// If the other side is not listening, we will
// get an onInputStreamReady callback where available
// raises to indicate the connection was refused.
nsCOMPtr<nsIAsyncInputStream> asyncStream =
do_QueryInterface(mSocketInputStream);
NS_ENSURE_TRUE(asyncStream, NS_ERROR_NOT_AVAILABLE);
nsCOMPtr<nsISerialEventTarget> mainTarget = GetMainThreadSerialEventTarget();
rv = asyncStream->AsyncWait(this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0,
mainTarget);
NS_ENSURE_SUCCESS(rv, rv);
if (mUseArrayBuffers) {
mInputStreamBinary =
do_CreateInstance("@mozilla.org/binaryinputstream;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mInputStreamBinary->SetInputStream(mSocketInputStream);
NS_ENSURE_SUCCESS(rv, rv);
} else {
mInputStreamScriptable =
do_CreateInstance("@mozilla.org/scriptableinputstream;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mInputStreamScriptable->Init(mSocketInputStream);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult TCPSocket::InitWithUnconnectedTransport(
nsISocketTransport* aTransport) {
mReadyState = TCPReadyState::Connecting;
mTransport = aTransport;
MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Content);
nsCOMPtr<nsISerialEventTarget> mainTarget = GetMainThreadSerialEventTarget();
mTransport->SetEventSink(this, mainTarget);
nsresult rv = CreateStream();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult TCPSocket::Init(nsIProxyInfo* aProxyInfo) {
nsCOMPtr<nsIObserverService> obs =
do_GetService("@mozilla.org/observer-service;1");
if (obs) {
mObserversActive = true;
obs->AddObserver(this, "inner-window-destroyed", true); // weak reference
obs->AddObserver(this, "profile-change-net-teardown", true); // weak ref
}
if (XRE_IsContentProcess()) {
mReadyState = TCPReadyState::Connecting;
nsCOMPtr<nsISerialEventTarget> target;
if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
target = global->SerialEventTarget();
}
mSocketBridgeChild = new TCPSocketChild(mHost, mPort, target);
mSocketBridgeChild->SendOpen(this, mSsl, mUseArrayBuffers);
return NS_OK;
}
nsCOMPtr<nsISocketTransportService> sts =
do_GetService("@mozilla.org/network/socket-transport-service;1");
AutoTArray<nsCString, 1> socketTypes;
if (mSsl) {
socketTypes.AppendElement("ssl"_ns);
} else {
socketTypes.AppendElement("starttls"_ns);
}
nsCOMPtr<nsISocketTransport> transport;
nsresult rv =
sts->CreateTransport(socketTypes, NS_ConvertUTF16toUTF8(mHost), mPort,
aProxyInfo, nullptr, getter_AddRefs(transport));
NS_ENSURE_SUCCESS(rv, rv);
return InitWithUnconnectedTransport(transport);
}
void TCPSocket::InitWithSocketChild(TCPSocketChild* aSocketBridge) {
mSocketBridgeChild = aSocketBridge;
mReadyState = TCPReadyState::Open;
mSocketBridgeChild->SetSocket(this);
mSocketBridgeChild->GetHost(mHost);
mSocketBridgeChild->GetPort(&mPort);
}
nsresult TCPSocket::InitWithTransport(nsISocketTransport* aTransport) {
mTransport = aTransport;
nsresult rv = CreateStream();
NS_ENSURE_SUCCESS(rv, rv);
mReadyState = TCPReadyState::Open;
rv = CreateInputStreamPump();
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString host;
mTransport->GetHost(host);
CopyUTF8toUTF16(host, mHost);
int32_t port;
mTransport->GetPort(&port);
mPort = port;
return NS_OK;
}
void TCPSocket::UpgradeToSecure(mozilla::ErrorResult& aRv) {
if (mReadyState != TCPReadyState::Open) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
if (mSsl) {
return;
}
mSsl = true;
if (mSocketBridgeChild) {
mSocketBridgeChild->SendStartTLS();
return;
}
if (!mAsyncCopierActive) {
ActivateTLS();
} else {
mWaitingForStartTLS = true;
}
}
namespace {
class CopierCallbacks final : public nsIRequestObserver {
RefPtr<TCPSocket> mOwner;
public:
explicit CopierCallbacks(TCPSocket* aSocket) : mOwner(aSocket) {}
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
private:
~CopierCallbacks() = default;
};
NS_IMPL_ISUPPORTS(CopierCallbacks, nsIRequestObserver)
NS_IMETHODIMP
CopierCallbacks::OnStartRequest(nsIRequest* aRequest) { return NS_OK; }
NS_IMETHODIMP
CopierCallbacks::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
mOwner->NotifyCopyComplete(aStatus);
mOwner = nullptr;
return NS_OK;
}
} // unnamed namespace
void TCPSocket::CalculateBufferedAmount() {
// Let's update the buffered amount of data.
uint64_t bufferedAmount = 0;
for (uint32_t i = 0, len = mPendingData.Length(); i < len; ++i) {
nsCOMPtr<nsIInputStream> stream = mPendingData[i];
uint64_t available = 0;
if (NS_SUCCEEDED(stream->Available(&available))) {
bufferedAmount += available;
}
}
mBufferedAmount = bufferedAmount;
}
nsresult TCPSocket::EnsureCopying() {
if (mAsyncCopierActive) {
return NS_OK;
}
mAsyncCopierActive = true;
nsresult rv;
nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> stream = do_QueryInterface(multiplexStream);
while (!mPendingData.IsEmpty()) {
nsCOMPtr<nsIInputStream> stream = mPendingData[0];
multiplexStream->AppendStream(stream);
mPendingData.RemoveElementAt(0);
}
nsCOMPtr<nsIAsyncStreamCopier> copier =
do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISocketTransportService> sts =
do_GetService("@mozilla.org/network/socket-transport-service;1");
nsCOMPtr<nsISerialEventTarget> target = do_QueryInterface(sts);
rv = copier->Init(stream, mSocketOutputStream, target,
true, /* source buffered */
false, /* sink buffered */
BUFFER_SIZE, false, /* close source */
false); /* close sink */
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<CopierCallbacks> callbacks = new CopierCallbacks(this);
rv = copier->AsyncCopy(callbacks, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
void TCPSocket::NotifyCopyComplete(nsresult aStatus) {
mAsyncCopierActive = false;
CalculateBufferedAmount();
if (mSocketBridgeParent && mSocketBridgeParent->IPCOpen()) {
mozilla::Unused << mSocketBridgeParent->SendUpdateBufferedAmount(
BufferedAmount(), mTrackingNumber);
}
if (NS_FAILED(aStatus)) {
MaybeReportErrorAndCloseIfOpen(aStatus);
return;
}
if (BufferedAmount() != 0) {
EnsureCopying();
return;
}
// Maybe we have some empty stream. We want to have an empty queue now.
mPendingData.Clear();
// If we are waiting for initiating starttls, we can begin to
// activate tls now.
if (mWaitingForStartTLS && mReadyState == TCPReadyState::Open) {
ActivateTLS();
mWaitingForStartTLS = false;
// If we have pending data, we should send them, or fire
// a drain event if we are waiting for it.
if (!mPendingDataAfterStartTLS.IsEmpty()) {
mPendingData = std::move(mPendingDataAfterStartTLS);
EnsureCopying();
return;
}
}
// If we have a connected child, we let the child decide whether
// ondrain should be dispatched.
if (mWaitingForDrain && !mSocketBridgeParent) {
mWaitingForDrain = false;
FireEvent(u"drain"_ns);
}
if (mReadyState == TCPReadyState::Closing) {
if (mSocketOutputStream) {
mSocketOutputStream->Close();
mSocketOutputStream = nullptr;
}
mReadyState = TCPReadyState::Closed;
FireEvent(u"close"_ns);
}
}
void TCPSocket::ActivateTLS() {
nsresult rv;
nsCOMPtr<nsIEventTarget> socketThread =
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
return;
}
bool alreadyOnSTST = false;
if (NS_FAILED(socketThread->IsOnCurrentThread(&alreadyOnSTST))) {
return;
}
if (alreadyOnSTST) {
ActivateTLSHelper();
return;
}
auto CallActivateTLS = [sock = RefPtr{this}]() mutable {
sock->ActivateTLSHelper();
};
mozilla::SyncRunnable::DispatchToThread(
socketThread,
NS_NewRunnableFunction("TCPSocket::UpgradeToSecure->ActivateTLSHelper",
CallActivateTLS));
}
void TCPSocket::ActivateTLSHelper() {
nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
mTransport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl));
if (tlsSocketControl) {
tlsSocketControl->StartTLS();
}
}
NS_IMETHODIMP
TCPSocket::FireErrorEvent(const nsAString& aName, const nsAString& aType,
nsresult aErrorCode) {
if (mSocketBridgeParent) {
mSocketBridgeParent->FireErrorEvent(aName, aType, aErrorCode, mReadyState);
return NS_OK;
}
TCPSocketErrorEventInit init;
init.mBubbles = false;
init.mCancelable = false;
init.mName = aName;
init.mMessage = aType;
static_assert(std::is_same_v<std::underlying_type_t<nsresult>, uint32_t>);
init.mErrorCode = uint32_t(aErrorCode);
RefPtr<TCPSocketErrorEvent> event =
TCPSocketErrorEvent::