summaryrefslogtreecommitdiffstats
path: root/dom/network/TCPSocket.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/network/TCPSocket.cpp')
-rw-r--r--dom/network/TCPSocket.cpp1157
1 files changed, 1157 insertions, 0 deletions
diff --git a/dom/network/TCPSocket.cpp b/dom/network/TCPSocket.cpp
new file mode 100644
index 0000000000..6dabd1a630
--- /dev/null
+++ b/dom/network/TCPSocket.cpp
@@ -0,0 +1,1157 @@
+/* -*- 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<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_GetProcessType() == GeckoProcessType_Content) {
+ mReadyState = TCPReadyState::Connecting;
+
+ nsCOMPtr<nsISerialEventTarget> target;
+ if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
+ target = global->EventTargetFor(TaskCategory::Other);
+ }
+ 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::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<JS::Value> val(api.cx());
+ return FireDataEvent(api.cx(), aType, val);
+}
+
+NS_IMETHODIMP
+TCPSocket::FireDataArrayEvent(const nsAString& aType,
+ const nsTArray<uint8_t>& buffer) {
+ AutoJSAPI api;
+ if (NS_WARN_IF(!api.Init(GetOwnerGlobal()))) {
+ return NS_ERROR_FAILURE;
+ }
+ JSContext* cx = api.cx();
+ JS::Rooted<JS::Value> 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<JS::Value> 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<JS::Value> aData) {
+ MOZ_ASSERT(!mSocketBridgeParent);
+
+ RootedDictionary<TCPSocketEventInit> init(aCx);
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mData = aData;
+
+ RefPtr<TCPSocketEvent> event = TCPSocketEvent::Constructor(this, aType, init);
+ event->SetTrusted(true);
+ DispatchEvent(*event);
+ return NS_OK;
+}
+
+JSObject* TCPSocket::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> 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<uint32_t>(status) & 0xFF0000) == 0x5a0000) {
+ nsCOMPtr<nsINSSErrorsService> 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<int32_t>(status) & 0xFFFF) <
+ abs(nsINSSErrorsService::NSS_SEC_ERROR_BASE)) {
+ switch (static_cast<SECErrorCodes>(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<SSLErrorCodes>(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<nsIInputStream> 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<uint32_t>& aByteLength,
+ mozilla::ErrorResult& aRv) {
+ if (mReadyState != TCPReadyState::Open) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ nsCOMPtr<nsIArrayBufferInputStream> 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<JS::Value> 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> TCPSocket::CreateAcceptedSocket(
+ nsIGlobalObject* aGlobal, nsISocketTransport* aTransport,
+ bool aUseArrayBuffers) {
+ RefPtr<TCPSocket> 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> TCPSocket::CreateAcceptedSocket(
+ nsIGlobalObject* aGlobal, TCPSocketChild* aBridge, bool aUseArrayBuffers) {
+ RefPtr<TCPSocket> socket =
+ new TCPSocket(aGlobal, u""_ns, 0, false, aUseArrayBuffers);
+ socket->InitWithSocketChild(aBridge);
+ return socket.forget();
+}
+
+already_AddRefed<TCPSocket> TCPSocket::Constructor(
+ const GlobalObject& aGlobal, const nsAString& aHost, uint16_t aPort,
+ const SocketOptions& aOptions, mozilla::ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ RefPtr<TCPSocket> socket =
+ new TCPSocket(global, aHost, aPort, aOptions.mUseSecureTransport,
+ aOptions.mBinaryType == TCPSocketBinaryType::Arraybuffer);
+ socket->ResolveProxy();
+
+ return socket.forget();
+}
+
+nsresult TCPSocket::ResolveProxy() {
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> 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<nsIChannel> 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<uint32_t>(aStatus) !=
+ static_cast<uint32_t>(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<uint8_t> buffer;
+ buffer.SetCapacity(aCount);
+ uint32_t actual;
+ nsresult rv = aStream->Read(reinterpret_cast<char*>(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<JS::Value> value(cx);
+ if (!ToJSValue(cx, TypedArrayCreator<ArrayBuffer>(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<JS::Value> 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<TCPReadyState>(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<nsISupportsPRUint64> 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<JSObject*> global(aCx, aGlobal);
+ return nsContentUtils::ObjectPrincipal(global)->IsSystemPrincipal();
+}