/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "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" #define BUFFER_SIZE 65536 #define NETWORK_STATS_THRESHOLD 65536 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 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 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 aGivenProto, JS::MutableHandle 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 window = do_QueryInterface(aGlobal); if (window) { mInnerWindowID = window->WindowID(); } } } TCPSocket::~TCPSocket() { if (mObserversActive) { nsCOMPtr 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 asyncStream = do_QueryInterface(mSocketInputStream); NS_ENSURE_TRUE(asyncStream, NS_ERROR_NOT_AVAILABLE); nsCOMPtr 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 mainTarget = GetMainThreadSerialEventTarget(); mTransport->SetEventSink(this, mainTarget); nsresult rv = CreateStream(); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult TCPSocket::Init(nsIProxyInfo* aProxyInfo) { nsCOMPtr 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_GetProcessType() == GeckoProcessType_Content) { mReadyState = TCPReadyState::Connecting; nsCOMPtr target; if (nsCOMPtr global = GetOwnerGlobal()) { target = global->EventTargetFor(TaskCategory::Other); } mSocketBridgeChild = new TCPSocketChild(mHost, mPort, target); mSocketBridgeChild->SendOpen(this, mSsl, mUseArrayBuffers); return NS_OK; } nsCOMPtr sts = do_GetService("@mozilla.org/network/socket-transport-service;1"); AutoTArray socketTypes; if (mSsl) { socketTypes.AppendElement("ssl"_ns); } else { socketTypes.AppendElement("starttls"_ns); } nsCOMPtr 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 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 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 multiplexStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr stream = do_QueryInterface(multiplexStream); while (!mPendingData.IsEmpty()) { nsCOMPtr stream = mPendingData[0]; multiplexStream->AppendStream(stream); mPendingData.RemoveElementAt(0); } nsCOMPtr copier = do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr sts = do_GetService("@mozilla.org/network/socket-transport-service;1"); nsCOMPtr 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 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 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 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, uint32_t>); init.mErrorCode = uint32_t(aErrorCode); RefPtr event = TCPSocketErrorEvent::Constructor(this, u"error"_ns, init); MOZ_ASSERT(event); event->SetTrusted(true); DispatchEvent(*event); return NS_OK; } NS_IMETHODIMP TCPSocket::FireEvent(const nsAString& aType) { if (mSocketBridgeParent) { mSocketBridgeParent->FireEvent(aType, mReadyState); return NS_OK; } AutoJSAPI api; if (NS_WARN_IF(!api.Init(GetOwnerGlobal()))) { return NS_ERROR_FAILURE; } JS::Rooted val(api.cx()); return FireDataEvent(api.cx(), aType, val); } NS_IMETHODIMP TCPSocket::FireDataArrayEvent(const nsAString& aType, const nsTArray& buffer) { AutoJSAPI api; if (NS_WARN_IF(!api.Init(GetOwnerGlobal()))) { return NS_ERROR_FAILURE; } JSContext* cx = api.cx(); JS::Rooted val(cx); bool ok = IPC::DeserializeArrayBuffer(cx, buffer, &val); if (ok) { return FireDataEvent(cx, aType, val); } return NS_ERROR_FAILURE; } NS_IMETHODIMP TCPSocket::FireDataStringEvent(const nsAString& aType, const nsACString& aString) { AutoJSAPI api; if (NS_WARN_IF(!api.Init(GetOwnerGlobal()))) { return NS_ERROR_FAILURE; } JSContext* cx = api.cx(); JS::Rooted val(cx); bool ok = ToJSValue(cx, NS_ConvertASCIItoUTF16(aString), &val); if (ok) { return FireDataEvent(cx, aType, val); } return NS_ERROR_FAILURE; } nsresult TCPSocket::FireDataEvent(JSContext* aCx, const nsAString& aType, JS::Handle aData) { MOZ_ASSERT(!mSocketBridgeParent); RootedDictionary init(aCx); init.mBubbles = false; init.mCancelable = false; init.mData = aData; RefPtr event = TCPSocketEvent::Constructor(this, aType, init); event->SetTrusted(true); DispatchEvent(*event); return NS_OK; } JSObject* TCPSocket::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return TCPSocket_Binding::Wrap(aCx, this, aGivenProto); } void TCPSocket::GetHost(nsAString& aHost) { aHost.Assign(mHost); } uint32_t TCPSocket::Port() const { return mPort; } bool TCPSocket::Ssl() const { return mSsl; } void TCPSocket::Suspend() { if (mSocketBridgeChild) { mSocketBridgeChild->SendSuspend(); return; } if (mInputStreamPump) { mInputStreamPump->Suspend(); } mSuspendCount++; } void TCPSocket::Resume(mozilla::ErrorResult& aRv) { if (mSocketBridgeChild) { mSocketBridgeChild->SendResume(); return; } if (!mSuspendCount) { aRv.Throw(NS_ERROR_FAILURE); return; } if (mInputStreamPump) { mInputStreamPump->Resume(); } mSuspendCount--; } nsresult TCPSocket::MaybeReportErrorAndCloseIfOpen(nsresult status) { // If we're closed, we've already reported the error or just don't need to // report the error. if (mReadyState == TCPReadyState::Closed) { return NS_OK; } // go through ::Closing state and then mark ::Closed Close(); mReadyState = TCPReadyState::Closed; if (NS_FAILED(status)) { // Convert the status code to an appropriate error message. nsString errorType, errName; // security module? (and this is an error) if ((static_cast(status) & 0xFF0000) == 0x5a0000) { nsCOMPtr errSvc = do_GetService("@mozilla.org/nss_errors_service;1"); // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code // is somehow not in the set of covered errors. uint32_t errorClass; nsresult rv = errSvc->GetErrorClass(status, &errorClass); if (NS_FAILED(rv)) { errorType.AssignLiteral("SecurityProtocol"); } else { switch (errorClass) { case nsINSSErrorsService::ERROR_CLASS_BAD_CERT: errorType.AssignLiteral("SecurityCertificate"); break; default: errorType.AssignLiteral("SecurityProtocol"); break; } } // NSS_SEC errors (happen below the base value because of negative vals) if ((static_cast(status) & 0xFFFF) < abs(nsINSSErrorsService::NSS_SEC_ERROR_BASE)) { switch (static_cast(status)) { case SEC_ERROR_EXPIRED_CERTIFICATE: errName.AssignLiteral("SecurityExpiredCertificateError"); break; case SEC_ERROR_REVOKED_CERTIFICATE: errName.AssignLiteral("SecurityRevokedCertificateError"); break; // per bsmith, we will be unable to tell these errors apart very // soon, so it makes sense to just folder them all together already. case SEC_ERROR_UNKNOWN_ISSUER: case SEC_ERROR_UNTRUSTED_ISSUER: case SEC_ERROR_UNTRUSTED_CERT: case SEC_ERROR_CA_CERT_INVALID: errName.AssignLiteral("SecurityUntrustedCertificateIssuerError"); break; case SEC_ERROR_INADEQUATE_KEY_USAGE: errName.AssignLiteral("SecurityInadequateKeyUsageError"); break; case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED: errName.AssignLiteral( "SecurityCertificateSignatureAlgorithmDisabledError"); break; default: errName.AssignLiteral("SecurityError"); break; } } else { // NSS_SSL errors switch (static_cast(status)) { case SSL_ERROR_NO_CERTIFICATE: errName.AssignLiteral("SecurityNoCertificateError"); break; case SSL_ERROR_BAD_CERTIFICATE: errName.AssignLiteral("SecurityBadCertificateError"); break; case SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE: errName.AssignLiteral("SecurityUnsupportedCertificateTypeError"); break; case SSL_ERROR_UNSUPPORTED_VERSION: errName.AssignLiteral("SecurityUnsupportedTLSVersionError"); break; case SSL_ERROR_BAD_CERT_DOMAIN: errName.AssignLiteral("SecurityCertificateDomainMismatchError"); break; default: errName.AssignLiteral("SecurityError"); break; } } } else { // must be network errorType.AssignLiteral("Network"); switch (status) { // connect to host:port failed case NS_ERROR_CONNECTION_REFUSED: errName.AssignLiteral("ConnectionRefusedError"); break; // network timeout error case NS_ERROR_NET_TIMEOUT: errName.AssignLiteral("NetworkTimeoutError"); break; // hostname lookup failed case NS_ERROR_UNKNOWN_HOST: errName.AssignLiteral("DomainNotFoundError"); break; case NS_ERROR_NET_INTERRUPT: errName.AssignLiteral("NetworkInterruptError"); break; default: errName.AssignLiteral("NetworkError"); break; } } Unused << NS_WARN_IF(NS_FAILED(FireErrorEvent(errName, errorType, status))); } return FireEvent(u"close"_ns); } void TCPSocket::Close() { CloseHelper(true); } void TCPSocket::CloseImmediately() { CloseHelper(false); } void TCPSocket::CloseHelper(bool waitForUnsentData) { if (mReadyState == TCPReadyState::Closed || mReadyState == TCPReadyState::Closing) { return; } mReadyState = TCPReadyState::Closing; if (mProxyRequest) { mProxyRequest->Cancel(NS_BINDING_ABORTED); mProxyRequest = nullptr; } if (mSocketBridgeChild) { mSocketBridgeChild->SendClose(); return; } if (!mAsyncCopierActive || !waitForUnsentData) { mPendingData.Clear(); mPendingDataAfterStartTLS.Clear(); if (mSocketOutputStream) { mSocketOutputStream->Close(); mSocketOutputStream = nullptr; } } if (mSocketInputStream) { mSocketInputStream->Close(); mSocketInputStream = nullptr; } } bool TCPSocket::Send(const nsACString& aData, mozilla::ErrorResult& aRv) { if (mReadyState != TCPReadyState::Open) { aRv.Throw(NS_ERROR_FAILURE); return false; } uint64_t byteLength; nsCOMPtr stream; if (mSocketBridgeChild) { mSocketBridgeChild->SendSend(aData); byteLength = aData.Length(); } else { nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), aData); if (NS_FAILED(rv)) { aRv.Throw(rv); return false; } rv = stream->Available(&byteLength); if (NS_FAILED(rv)) { aRv.Throw(rv); return false; } } return Send(stream, byteLength); } bool TCPSocket::Send(const ArrayBuffer& aData, uint32_t aByteOffset, const Optional& aByteLength, mozilla::ErrorResult& aRv) { if (mReadyState != TCPReadyState::Open) { aRv.Throw(NS_ERROR_FAILURE); return false; } nsCOMPtr stream; aData.ComputeState(); uint32_t byteLength = aByteLength.WasPassed() ? aByteLength.Value() : aData.Length(); if (mSocketBridgeChild) { nsresult rv = mSocketBridgeChild->SendSend(aData, aByteOffset, byteLength); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return false; } } else { JS::Rooted value(RootingCx(), JS::ObjectValue(*aData.Obj())); stream = do_CreateInstance("@mozilla.org/io/arraybuffer-input-stream;1"); nsresult rv = stream->SetData(value, aByteOffset, byteLength); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return false; } } return Send(stream, byteLength); } bool TCPSocket::Send(nsIInputStream* aStream, uint32_t aByteLength) { uint64_t newBufferedAmount = BufferedAmount() + aByteLength; bool bufferFull = newBufferedAmount > BUFFER_SIZE; if (bufferFull) { // If we buffered more than some arbitrary amount of data, // (65535 right now) we should tell the caller so they can // wait until ondrain is called if they so desire. Once all the // buffered data has been written to the socket, ondrain is // called. mWaitingForDrain = true; } if (mSocketBridgeChild) { // In the child, we just add the buffer length to our bufferedAmount and let // the parent update our bufferedAmount when the data have been sent. mBufferedAmount = newBufferedAmount; return !bufferFull; } // This is used to track how many packets we have been told to send. Signaling // this back to the user of this API allows the user to know how many packets // are currently in flight over IPC. ++mTrackingNumber; if (mWaitingForStartTLS) { // When we are waiting for starttls, newStream is added to pendingData // and will be appended to multiplexStream after tls had been set up. mPendingDataAfterStartTLS.AppendElement(aStream); } else { mPendingData.AppendElement(aStream); } CalculateBufferedAmount(); EnsureCopying(); return !bufferFull; } TCPReadyState TCPSocket::ReadyState() { return mReadyState; } TCPSocketBinaryType TCPSocket::BinaryType() const { if (mUseArrayBuffers) { return TCPSocketBinaryType::Arraybuffer; } return TCPSocketBinaryType::String; } already_AddRefed TCPSocket::CreateAcceptedSocket( nsIGlobalObject* aGlobal, nsISocketTransport* aTransport, bool aUseArrayBuffers) { RefPtr socket = new TCPSocket(aGlobal, u""_ns, 0, false, aUseArrayBuffers); nsresult rv = socket->InitWithTransport(aTransport); NS_ENSURE_SUCCESS(rv, nullptr); return socket.forget(); } already_AddRefed TCPSocket::CreateAcceptedSocket( nsIGlobalObject* aGlobal, TCPSocketChild* aBridge, bool aUseArrayBuffers) { RefPtr socket = new TCPSocket(aGlobal, u""_ns, 0, false, aUseArrayBuffers); socket->InitWithSocketChild(aBridge); return socket.forget(); } already_AddRefed TCPSocket::Constructor( const GlobalObject& aGlobal, const nsAString& aHost, uint16_t aPort, const SocketOptions& aOptions, mozilla::ErrorResult& aRv) { nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); RefPtr socket = new TCPSocket(global, aHost, aPort, aOptions.mUseSecureTransport, aOptions.mBinaryType == TCPSocketBinaryType::Arraybuffer); socket->ResolveProxy(); return socket.forget(); } nsresult TCPSocket::ResolveProxy() { nsresult rv; nsCOMPtr pps = do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr uri; nsCString spec = mSsl ? "https://"_ns : "http://"_ns; bool maybeIPv6 = mHost.FindChar(':') != -1; if (maybeIPv6) spec.Append('['); if (!AppendUTF16toUTF8(mHost, spec, fallible)) { return NS_ERROR_OUT_OF_MEMORY; } if (maybeIPv6) spec.Append(']'); rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) .SetSpec(spec) .SetPort(mPort) .Finalize(uri); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr channel; rv = NS_NewChannel(getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, nsIContentPolicy::TYPE_OTHER); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = pps->AsyncResolve(channel, nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY, this, nullptr, getter_AddRefs(mProxyRequest)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } NS_IMETHODIMP TCPSocket::OnProxyAvailable(nsICancelable* aRequest, nsIChannel* aChannel, nsIProxyInfo* aProxyInfo, nsresult aResult) { mProxyRequest = nullptr; if (NS_SUCCEEDED(aResult) && aProxyInfo) { nsCString proxyType; nsresult rv = aProxyInfo->GetType(proxyType); if (NS_WARN_IF(NS_FAILED(rv))) { Close(); return rv; } // Only supports SOCKS proxy for now. if (proxyType == "socks" || proxyType == "socks4") { return Init(aProxyInfo); } } return Init(nullptr); } nsresult TCPSocket::CreateInputStreamPump() { if (!mSocketInputStream) { return NS_ERROR_NOT_AVAILABLE; } nsresult rv; mInputStreamPump = do_CreateInstance("@mozilla.org/network/input-stream-pump;1", &rv); NS_ENSURE_SUCCESS(rv, rv); rv = mInputStreamPump->Init(mSocketInputStream, 0, 0, false, nullptr); NS_ENSURE_SUCCESS(rv, rv); uint64_t suspendCount = mSuspendCount; while (suspendCount--) { mInputStreamPump->Suspend(); } rv = mInputStreamPump->AsyncRead(this); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP TCPSocket::OnTransportStatus(nsITransport* aTransport, nsresult aStatus, int64_t aProgress, int64_t aProgressMax) { if (static_cast(aStatus) != static_cast(nsISocketTransport::STATUS_CONNECTED_TO)) { return NS_OK; } mReadyState = TCPReadyState::Open; nsresult rv = CreateInputStreamPump(); NS_ENSURE_SUCCESS(rv, rv); FireEvent(u"open"_ns); return NS_OK; } NS_IMETHODIMP TCPSocket::OnInputStreamReady(nsIAsyncInputStream* aStream) { // Only used for detecting if the connection was refused. uint64_t dummy; nsresult rv = aStream->Available(&dummy); if (NS_FAILED(rv)) { MaybeReportErrorAndCloseIfOpen(NS_ERROR_CONNECTION_REFUSED); } return NS_OK; } NS_IMETHODIMP TCPSocket::OnStartRequest(nsIRequest* aRequest) { return NS_OK; } NS_IMETHODIMP TCPSocket::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream, uint64_t aOffset, uint32_t aCount) { if (mUseArrayBuffers) { nsTArray buffer; buffer.SetCapacity(aCount); uint32_t actual; nsresult rv = aStream->Read(reinterpret_cast(buffer.Elements()), aCount, &actual); NS_ENSURE_SUCCESS(rv, rv); MOZ_ASSERT(actual == aCount); buffer.SetLength(actual); if (mSocketBridgeParent) { mSocketBridgeParent->FireArrayBufferDataEvent(buffer, mReadyState); return NS_OK; } AutoJSAPI api; if (!api.Init(GetOwnerGlobal())) { return NS_ERROR_FAILURE; } JSContext* cx = api.cx(); JS::Rooted value(cx); if (!ToJSValue(cx, TypedArrayCreator(buffer), &value)) { return NS_ERROR_FAILURE; } FireDataEvent(cx, u"data"_ns, value); return NS_OK; } nsCString data; nsresult rv = mInputStreamScriptable->ReadBytes(aCount, data); NS_ENSURE_SUCCESS(rv, rv); if (mSocketBridgeParent) { mSocketBridgeParent->FireStringDataEvent(data, mReadyState); return NS_OK; } AutoJSAPI api; if (!api.Init(GetOwnerGlobal())) { return NS_ERROR_FAILURE; } JSContext* cx = api.cx(); JS::Rooted value(cx); if (!ToJSValue(cx, NS_ConvertASCIItoUTF16(data), &value)) { return NS_ERROR_FAILURE; } FireDataEvent(cx, u"data"_ns, value); return NS_OK; } NS_IMETHODIMP TCPSocket::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { mInputStreamPump = nullptr; if (mAsyncCopierActive && NS_SUCCEEDED(aStatus)) { // If we have some buffered output still, and status is not an // error, the other side has done a half-close, but we don't // want to be in the close state until we are done sending // everything that was buffered. We also don't want to call onclose // yet. return NS_OK; } // We call this even if there is no error. MaybeReportErrorAndCloseIfOpen(aStatus); return NS_OK; } void TCPSocket::SetSocketBridgeParent(TCPSocketParent* aBridgeParent) { MOZ_ASSERT(NS_IsMainThread()); mSocketBridgeParent = aBridgeParent; } NS_IMETHODIMP TCPSocket::UpdateReadyState(uint32_t aReadyState) { MOZ_ASSERT(mSocketBridgeChild); mReadyState = static_cast(aReadyState); return NS_OK; } NS_IMETHODIMP TCPSocket::UpdateBufferedAmount(uint32_t aBufferedAmount, uint32_t aTrackingNumber) { if (aTrackingNumber != mTrackingNumber) { return NS_OK; } mBufferedAmount = aBufferedAmount; if (!mBufferedAmount) { if (mWaitingForDrain) { mWaitingForDrain = false; return FireEvent(u"drain"_ns); } } return NS_OK; } NS_IMETHODIMP TCPSocket::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { if (!strcmp(aTopic, "inner-window-destroyed")) { nsCOMPtr wrapper = do_QueryInterface(aSubject); NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE); uint64_t innerID; nsresult rv = wrapper->GetData(&innerID); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (innerID == mInnerWindowID) { Close(); } } else if (!strcmp(aTopic, "profile-change-net-teardown")) { Close(); } return NS_OK; } /* static */ bool TCPSocket::ShouldTCPSocketExist(JSContext* aCx, JSObject* aGlobal) { JS::Rooted global(aCx, aGlobal); return nsContentUtils::ObjectPrincipal(global)->IsSystemPrincipal(); }