Bug 1941046 - Part 4: Send a callback request for impression and clicks of MARS Top...
[gecko.git] / dom / network / TCPSocket.cpp
blob577e630952df93e540555640c4862c425a97c037
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "TCPServerSocket.h"
8 #include "TCPSocket.h"
9 #include "TCPSocketChild.h"
10 #include "TCPSocketParent.h"
11 #include "mozilla/BasePrincipal.h"
12 #include "mozilla/ErrorResult.h"
13 #include "mozilla/SyncRunnable.h"
14 #include "mozilla/dom/RootedDictionary.h"
15 #include "mozilla/dom/ScriptSettings.h"
16 #include "mozilla/dom/TCPSocketBinding.h"
17 #include "mozilla/dom/TCPSocketErrorEvent.h"
18 #include "mozilla/dom/TCPSocketErrorEventBinding.h"
19 #include "mozilla/dom/TCPSocketEvent.h"
20 #include "mozilla/dom/TCPSocketEventBinding.h"
21 #include "mozilla/dom/ToJSValue.h"
22 #include "nsComponentManagerUtils.h"
23 #include "nsContentUtils.h"
24 #include "nsIArrayBufferInputStream.h"
25 #include "nsIAsyncInputStream.h"
26 #include "nsIAsyncStreamCopier.h"
27 #include "nsIBinaryInputStream.h"
28 #include "nsICancelable.h"
29 #include "nsIChannel.h"
30 #include "nsIInputStream.h"
31 #include "nsIInputStreamPump.h"
32 #include "nsIMultiplexInputStream.h"
33 #include "nsINSSErrorsService.h"
34 #include "nsIObserverService.h"
35 #include "nsIOutputStream.h"
36 #include "nsIProtocolProxyService.h"
37 #include "nsIScriptableInputStream.h"
38 #include "nsISocketTransport.h"
39 #include "nsISocketTransportService.h"
40 #include "nsISupportsPrimitives.h"
41 #include "nsITLSSocketControl.h"
42 #include "nsITransport.h"
43 #include "nsIURIMutator.h"
44 #include "nsNetCID.h"
45 #include "nsNetUtil.h"
46 #include "nsServiceManagerUtils.h"
47 #include "nsString.h"
48 #include "nsStringStream.h"
49 #include "secerr.h"
50 #include "sslerr.h"
52 using namespace mozilla::dom;
54 NS_IMPL_CYCLE_COLLECTION(LegacyMozTCPSocket, mGlobal)
56 NS_IMPL_CYCLE_COLLECTING_ADDREF(LegacyMozTCPSocket)
57 NS_IMPL_CYCLE_COLLECTING_RELEASE(LegacyMozTCPSocket)
58 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LegacyMozTCPSocket)
59 NS_INTERFACE_MAP_ENTRY(nsISupports)
60 NS_INTERFACE_MAP_END
62 LegacyMozTCPSocket::LegacyMozTCPSocket(nsPIDOMWindowInner* aWindow)
63 : mGlobal(do_QueryInterface(aWindow)) {}
65 LegacyMozTCPSocket::~LegacyMozTCPSocket() = default;
67 already_AddRefed<TCPSocket> LegacyMozTCPSocket::Open(
68 const nsAString& aHost, uint16_t aPort, const SocketOptions& aOptions,
69 mozilla::ErrorResult& aRv) {
70 AutoJSAPI api;
71 if (NS_WARN_IF(!api.Init(mGlobal))) {
72 aRv.Throw(NS_ERROR_FAILURE);
73 return nullptr;
75 GlobalObject globalObj(api.cx(), mGlobal->GetGlobalJSObject());
76 return TCPSocket::Constructor(globalObj, aHost, aPort, aOptions, aRv);
79 already_AddRefed<TCPServerSocket> LegacyMozTCPSocket::Listen(
80 uint16_t aPort, const ServerSocketOptions& aOptions, uint16_t aBacklog,
81 mozilla::ErrorResult& aRv) {
82 AutoJSAPI api;
83 if (NS_WARN_IF(!api.Init(mGlobal))) {
84 return nullptr;
86 GlobalObject globalObj(api.cx(), mGlobal->GetGlobalJSObject());
87 return TCPServerSocket::Constructor(globalObj, aPort, aOptions, aBacklog,
88 aRv);
91 bool LegacyMozTCPSocket::WrapObject(JSContext* aCx,
92 JS::Handle<JSObject*> aGivenProto,
93 JS::MutableHandle<JSObject*> aReflector) {
94 return LegacyMozTCPSocket_Binding::Wrap(aCx, this, aGivenProto, aReflector);
97 NS_IMPL_CYCLE_COLLECTION_CLASS(TCPSocket)
99 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(TCPSocket, DOMEventTargetHelper)
100 NS_IMPL_CYCLE_COLLECTION_TRACE_END
102 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TCPSocket,
103 DOMEventTargetHelper)
104 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransport)
105 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketInputStream)
106 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketOutputStream)
107 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamPump)
108 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamScriptable)
109 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamBinary)
110 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingDataAfterStartTLS)
111 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketBridgeChild)
112 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketBridgeParent)
113 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
115 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TCPSocket, DOMEventTargetHelper)
116 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransport)
117 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketInputStream)
118 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketOutputStream)
119 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamPump)
120 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamScriptable)
121 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamBinary)
122 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingDataAfterStartTLS)
123 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketBridgeChild)
124 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketBridgeParent)
125 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
126 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
128 NS_IMPL_ADDREF_INHERITED(TCPSocket, DOMEventTargetHelper)
129 NS_IMPL_RELEASE_INHERITED(TCPSocket, DOMEventTargetHelper)
131 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocket)
132 NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
133 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
134 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
135 NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
136 NS_INTERFACE_MAP_ENTRY(nsIObserver)
137 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
138 NS_INTERFACE_MAP_ENTRY(nsITCPSocketCallback)
139 NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
140 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
142 TCPSocket::TCPSocket(nsIGlobalObject* aGlobal, const nsAString& aHost,
143 uint16_t aPort, bool aSsl, bool aUseArrayBuffers)
144 : DOMEventTargetHelper(aGlobal),
145 mReadyState(TCPReadyState::Closed),
146 mUseArrayBuffers(aUseArrayBuffers),
147 mHost(aHost),
148 mPort(aPort),
149 mSsl(aSsl),
150 mAsyncCopierActive(false),
151 mWaitingForDrain(false),
152 mInnerWindowID(0),
153 mBufferedAmount(0),
154 mSuspendCount(0),
155 mTrackingNumber(0),
156 mWaitingForStartTLS(false),
157 mObserversActive(false) {
158 if (aGlobal) {
159 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
160 if (window) {
161 mInnerWindowID = window->WindowID();
166 TCPSocket::~TCPSocket() {
167 if (mObserversActive) {
168 nsCOMPtr<nsIObserverService> obs =
169 do_GetService("@mozilla.org/observer-service;1");
170 if (obs) {
171 obs->RemoveObserver(this, "inner-window-destroyed");
172 obs->RemoveObserver(this, "profile-change-net-teardown");
177 nsresult TCPSocket::CreateStream() {
178 nsresult rv =
179 mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(mSocketInputStream));
180 NS_ENSURE_SUCCESS(rv, rv);
181 rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
182 getter_AddRefs(mSocketOutputStream));
183 NS_ENSURE_SUCCESS(rv, rv);
185 // If the other side is not listening, we will
186 // get an onInputStreamReady callback where available
187 // raises to indicate the connection was refused.
188 nsCOMPtr<nsIAsyncInputStream> asyncStream =
189 do_QueryInterface(mSocketInputStream);
190 NS_ENSURE_TRUE(asyncStream, NS_ERROR_NOT_AVAILABLE);
192 nsCOMPtr<nsISerialEventTarget> mainTarget = GetMainThreadSerialEventTarget();
193 rv = asyncStream->AsyncWait(this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0,
194 mainTarget);
195 NS_ENSURE_SUCCESS(rv, rv);
197 if (mUseArrayBuffers) {
198 mInputStreamBinary =
199 do_CreateInstance("@mozilla.org/binaryinputstream;1", &rv);
200 NS_ENSURE_SUCCESS(rv, rv);
201 rv = mInputStreamBinary->SetInputStream(mSocketInputStream);
202 NS_ENSURE_SUCCESS(rv, rv);
203 } else {
204 mInputStreamScriptable =
205 do_CreateInstance("@mozilla.org/scriptableinputstream;1", &rv);
206 NS_ENSURE_SUCCESS(rv, rv);
207 rv = mInputStreamScriptable->Init(mSocketInputStream);
208 NS_ENSURE_SUCCESS(rv, rv);
211 return NS_OK;
214 nsresult TCPSocket::InitWithUnconnectedTransport(
215 nsISocketTransport* aTransport) {
216 mReadyState = TCPReadyState::Connecting;
217 mTransport = aTransport;
219 MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Content);
221 nsCOMPtr<nsISerialEventTarget> mainTarget = GetMainThreadSerialEventTarget();
222 mTransport->SetEventSink(this, mainTarget);
224 nsresult rv = CreateStream();
225 NS_ENSURE_SUCCESS(rv, rv);
227 return NS_OK;
230 nsresult TCPSocket::Init(nsIProxyInfo* aProxyInfo) {
231 nsCOMPtr<nsIObserverService> obs =
232 do_GetService("@mozilla.org/observer-service;1");
233 if (obs) {
234 mObserversActive = true;
235 obs->AddObserver(this, "inner-window-destroyed", true); // weak reference
236 obs->AddObserver(this, "profile-change-net-teardown", true); // weak ref
239 if (XRE_IsContentProcess()) {
240 mReadyState = TCPReadyState::Connecting;
242 nsCOMPtr<nsISerialEventTarget> target;
243 if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
244 target = global->SerialEventTarget();
246 mSocketBridgeChild = new TCPSocketChild(mHost, mPort, target);
247 mSocketBridgeChild->SendOpen(this, mSsl, mUseArrayBuffers);
248 return NS_OK;
251 nsCOMPtr<nsISocketTransportService> sts =
252 do_GetService("@mozilla.org/network/socket-transport-service;1");
254 AutoTArray<nsCString, 1> socketTypes;
255 if (mSsl) {
256 socketTypes.AppendElement("ssl"_ns);
257 } else {
258 socketTypes.AppendElement("starttls"_ns);
260 nsCOMPtr<nsISocketTransport> transport;
261 nsresult rv =
262 sts->CreateTransport(socketTypes, NS_ConvertUTF16toUTF8(mHost), mPort,
263 aProxyInfo, nullptr, getter_AddRefs(transport));
264 NS_ENSURE_SUCCESS(rv, rv);
266 return InitWithUnconnectedTransport(transport);
269 void TCPSocket::InitWithSocketChild(TCPSocketChild* aSocketBridge) {
270 mSocketBridgeChild = aSocketBridge;
271 mReadyState = TCPReadyState::Open;
272 mSocketBridgeChild->SetSocket(this);
273 mSocketBridgeChild->GetHost(mHost);
274 mSocketBridgeChild->GetPort(&mPort);
277 nsresult TCPSocket::InitWithTransport(nsISocketTransport* aTransport) {
278 mTransport = aTransport;
279 nsresult rv = CreateStream();
280 NS_ENSURE_SUCCESS(rv, rv);
282 mReadyState = TCPReadyState::Open;
283 rv = CreateInputStreamPump();
284 NS_ENSURE_SUCCESS(rv, rv);
286 nsAutoCString host;
287 mTransport->GetHost(host);
288 CopyUTF8toUTF16(host, mHost);
289 int32_t port;
290 mTransport->GetPort(&port);
291 mPort = port;
293 return NS_OK;
296 void TCPSocket::UpgradeToSecure(mozilla::ErrorResult& aRv) {
297 if (mReadyState != TCPReadyState::Open) {
298 aRv.Throw(NS_ERROR_FAILURE);
299 return;
302 if (mSsl) {
303 return;
306 mSsl = true;
308 if (mSocketBridgeChild) {
309 mSocketBridgeChild->SendStartTLS();
310 return;
313 if (!mAsyncCopierActive) {
314 ActivateTLS();
315 } else {
316 mWaitingForStartTLS = true;
320 namespace {
321 class CopierCallbacks final : public nsIRequestObserver {
322 RefPtr<TCPSocket> mOwner;
324 public:
325 explicit CopierCallbacks(TCPSocket* aSocket) : mOwner(aSocket) {}
327 NS_DECL_ISUPPORTS
328 NS_DECL_NSIREQUESTOBSERVER
329 private:
330 ~CopierCallbacks() = default;
333 NS_IMPL_ISUPPORTS(CopierCallbacks, nsIRequestObserver)
335 NS_IMETHODIMP
336 CopierCallbacks::OnStartRequest(nsIRequest* aRequest) { return NS_OK; }
338 NS_IMETHODIMP
339 CopierCallbacks::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
340 mOwner->NotifyCopyComplete(aStatus);
341 mOwner = nullptr;
342 return NS_OK;
344 } // unnamed namespace
346 void TCPSocket::CalculateBufferedAmount() {
347 // Let's update the buffered amount of data.
348 uint64_t bufferedAmount = 0;
349 for (uint32_t i = 0, len = mPendingData.Length(); i < len; ++i) {
350 nsCOMPtr<nsIInputStream> stream = mPendingData[i];
351 uint64_t available = 0;
352 if (NS_SUCCEEDED(stream->Available(&available))) {
353 bufferedAmount += available;
356 mBufferedAmount = bufferedAmount;
359 nsresult TCPSocket::EnsureCopying() {
360 if (mAsyncCopierActive) {
361 return NS_OK;
364 mAsyncCopierActive = true;
366 nsresult rv;
368 nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
369 do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
370 NS_ENSURE_SUCCESS(rv, rv);
372 nsCOMPtr<nsIInputStream> stream = do_QueryInterface(multiplexStream);
374 while (!mPendingData.IsEmpty()) {
375 nsCOMPtr<nsIInputStream> stream = mPendingData[0];
376 multiplexStream->AppendStream(stream);
377 mPendingData.RemoveElementAt(0);
380 nsCOMPtr<nsIAsyncStreamCopier> copier =
381 do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv);
382 NS_ENSURE_SUCCESS(rv, rv);
384 nsCOMPtr<nsISocketTransportService> sts =
385 do_GetService("@mozilla.org/network/socket-transport-service;1");
387 nsCOMPtr<nsISerialEventTarget> target = do_QueryInterface(sts);
388 rv = copier->Init(stream, mSocketOutputStream, target,
389 true, /* source buffered */
390 false, /* sink buffered */
391 BUFFER_SIZE, false, /* close source */
392 false); /* close sink */
393 NS_ENSURE_SUCCESS(rv, rv);
395 RefPtr<CopierCallbacks> callbacks = new CopierCallbacks(this);
396 rv = copier->AsyncCopy(callbacks, nullptr);
397 NS_ENSURE_SUCCESS(rv, rv);
399 return NS_OK;
402 void TCPSocket::NotifyCopyComplete(nsresult aStatus) {
403 mAsyncCopierActive = false;
404 CalculateBufferedAmount();
406 if (mSocketBridgeParent && mSocketBridgeParent->IPCOpen()) {
407 mozilla::Unused << mSocketBridgeParent->SendUpdateBufferedAmount(
408 BufferedAmount(), mTrackingNumber);
411 if (NS_FAILED(aStatus)) {
412 MaybeReportErrorAndCloseIfOpen(aStatus);
413 return;
416 if (BufferedAmount() != 0) {
417 EnsureCopying();
418 return;
421 // Maybe we have some empty stream. We want to have an empty queue now.
422 mPendingData.Clear();
424 // If we are waiting for initiating starttls, we can begin to
425 // activate tls now.
426 if (mWaitingForStartTLS && mReadyState == TCPReadyState::Open) {
427 ActivateTLS();
428 mWaitingForStartTLS = false;
429 // If we have pending data, we should send them, or fire
430 // a drain event if we are waiting for it.
431 if (!mPendingDataAfterStartTLS.IsEmpty()) {
432 mPendingData = std::move(mPendingDataAfterStartTLS);
433 EnsureCopying();
434 return;
438 // If we have a connected child, we let the child decide whether
439 // ondrain should be dispatched.
440 if (mWaitingForDrain && !mSocketBridgeParent) {
441 mWaitingForDrain = false;
442 FireEvent(u"drain"_ns);
445 if (mReadyState == TCPReadyState::Closing) {
446 if (mSocketOutputStream) {
447 mSocketOutputStream->Close();
448 mSocketOutputStream = nullptr;
450 mReadyState = TCPReadyState::Closed;
451 FireEvent(u"close"_ns);
455 void TCPSocket::ActivateTLS() {
456 nsresult rv;
457 nsCOMPtr<nsIEventTarget> socketThread =
458 do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
459 if (NS_FAILED(rv)) {
460 return;
463 bool alreadyOnSTST = false;
464 if (NS_FAILED(socketThread->IsOnCurrentThread(&alreadyOnSTST))) {
465 return;
468 if (alreadyOnSTST) {
469 ActivateTLSHelper();
470 return;
473 auto CallActivateTLS = [sock = RefPtr{this}]() mutable {
474 sock->ActivateTLSHelper();
476 mozilla::SyncRunnable::DispatchToThread(
477 socketThread,
478 NS_NewRunnableFunction("TCPSocket::UpgradeToSecure->ActivateTLSHelper",
479 CallActivateTLS));
482 void TCPSocket::ActivateTLSHelper() {
483 nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
484 mTransport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl));
485 if (tlsSocketControl) {
486 tlsSocketControl->StartTLS();
490 NS_IMETHODIMP
491 TCPSocket::FireErrorEvent(const nsAString& aName, const nsAString& aType,
492 nsresult aErrorCode) {
493 if (mSocketBridgeParent) {
494 mSocketBridgeParent->FireErrorEvent(aName, aType, aErrorCode, mReadyState);
495 return NS_OK;
498 TCPSocketErrorEventInit init;
499 init.mBubbles = false;
500 init.mCancelable = false;
501 init.mName = aName;
502 init.mMessage = aType;
503 static_assert(std::is_same_v<std::underlying_type_t<nsresult>, uint32_t>);
504 init.mErrorCode = uint32_t(aErrorCode);
506 RefPtr<TCPSocketErrorEvent> event =
507 TCPSocketErrorEvent::Constructor(this, u"error"_ns, init);
508 MOZ_ASSERT(event);
509 event->SetTrusted(true);
510 DispatchEvent(*event);
511 return NS_OK;
514 NS_IMETHODIMP
515 TCPSocket::FireEvent(const nsAString& aType) {
516 if (mSocketBridgeParent) {
517 mSocketBridgeParent->FireEvent(aType, mReadyState);
518 return NS_OK;
521 AutoJSAPI api;
522 if (NS_WARN_IF(!api.Init(GetOwnerGlobal()))) {
523 return NS_ERROR_FAILURE;
525 JS::Rooted<JS::Value> val(api.cx());
526 return FireDataEvent(api.cx(), aType, val);
529 NS_IMETHODIMP
530 TCPSocket::FireDataArrayEvent(const nsAString& aType,
531 const nsTArray<uint8_t>& buffer) {
532 AutoJSAPI api;
533 if (NS_WARN_IF(!api.Init(GetOwnerGlobal()))) {
534 return NS_ERROR_FAILURE;
536 JSContext* cx = api.cx();
537 JS::Rooted<JS::Value> val(cx);
539 bool ok = IPC::DeserializeArrayBuffer(cx, buffer, &val);
540 if (ok) {
541 return FireDataEvent(cx, aType, val);
543 return NS_ERROR_FAILURE;
546 NS_IMETHODIMP
547 TCPSocket::FireDataStringEvent(const nsAString& aType,
548 const nsACString& aString) {
549 AutoJSAPI api;
550 if (NS_WARN_IF(!api.Init(GetOwnerGlobal()))) {
551 return NS_ERROR_FAILURE;
553 JSContext* cx = api.cx();
554 JS::Rooted<JS::Value> val(cx);
556 bool ok = ToJSValue(cx, NS_ConvertASCIItoUTF16(aString), &val);
557 if (ok) {
558 return FireDataEvent(cx, aType, val);
560 return NS_ERROR_FAILURE;
563 nsresult TCPSocket::FireDataEvent(JSContext* aCx, const nsAString& aType,
564 JS::Handle<JS::Value> aData) {
565 MOZ_ASSERT(!mSocketBridgeParent);
567 RootedDictionary<TCPSocketEventInit> init(aCx);
568 init.mBubbles = false;
569 init.mCancelable = false;
570 init.mData = aData;
572 RefPtr<TCPSocketEvent> event = TCPSocketEvent::Constructor(this, aType, init);
573 event->SetTrusted(true);
574 DispatchEvent(*event);
575 return NS_OK;
578 JSObject* TCPSocket::WrapObject(JSContext* aCx,
579 JS::Handle<JSObject*> aGivenProto) {
580 return TCPSocket_Binding::Wrap(aCx, this, aGivenProto);
583 void TCPSocket::GetHost(nsAString& aHost) { aHost.Assign(mHost); }
585 uint32_t TCPSocket::Port() const { return mPort; }
587 bool TCPSocket::Ssl() const { return mSsl; }
589 void TCPSocket::Suspend() {
590 if (mSocketBridgeChild) {
591 mSocketBridgeChild->SendSuspend();
592 return;
594 if (mInputStreamPump) {
595 mInputStreamPump->Suspend();
597 mSuspendCount++;
600 void TCPSocket::Resume(mozilla::ErrorResult& aRv) {
601 if (mSocketBridgeChild) {
602 mSocketBridgeChild->SendResume();
603 return;
605 if (!mSuspendCount) {
606 aRv.Throw(NS_ERROR_FAILURE);
607 return;
610 if (mInputStreamPump) {
611 mInputStreamPump->Resume();
613 mSuspendCount--;
616 nsresult TCPSocket::MaybeReportErrorAndCloseIfOpen(nsresult status) {
617 // If we're closed, we've already reported the error or just don't need to
618 // report the error.
619 if (mReadyState == TCPReadyState::Closed) {
620 return NS_OK;
623 // go through ::Closing state and then mark ::Closed
624 Close();
625 mReadyState = TCPReadyState::Closed;
627 if (NS_FAILED(status)) {
628 // Convert the status code to an appropriate error message.
630 nsString errorType, errName;
632 // security module? (and this is an error)
633 if ((static_cast<uint32_t>(status) & 0xFF0000) == 0x5a0000) {
634 nsCOMPtr<nsINSSErrorsService> errSvc =
635 do_GetService("@mozilla.org/nss_errors_service;1");
636 // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code
637 // is somehow not in the set of covered errors.
638 uint32_t errorClass;
639 nsresult rv = errSvc->GetErrorClass(status, &errorClass);
640 if (NS_FAILED(rv)) {
641 errorType.AssignLiteral("SecurityProtocol");
642 } else {
643 switch (errorClass) {
644 case nsINSSErrorsService::ERROR_CLASS_BAD_CERT:
645 errorType.AssignLiteral("SecurityCertificate");
646 break;
647 default:
648 errorType.AssignLiteral("SecurityProtocol");
649 break;
653 // NSS_SEC errors (happen below the base value because of negative vals)
654 if ((static_cast<int32_t>(status) & 0xFFFF) <
655 abs(nsINSSErrorsService::NSS_SEC_ERROR_BASE)) {
656 switch (static_cast<SECErrorCodes>(status)) {
657 case SEC_ERROR_EXPIRED_CERTIFICATE:
658 errName.AssignLiteral("SecurityExpiredCertificateError");
659 break;
660 case SEC_ERROR_REVOKED_CERTIFICATE:
661 errName.AssignLiteral("SecurityRevokedCertificateError");
662 break;
663 // per bsmith, we will be unable to tell these errors apart very
664 // soon, so it makes sense to just folder them all together already.
665 case SEC_ERROR_UNKNOWN_ISSUER:
666 case SEC_ERROR_UNTRUSTED_ISSUER:
667 case SEC_ERROR_UNTRUSTED_CERT:
668 case SEC_ERROR_CA_CERT_INVALID:
669 errName.AssignLiteral("SecurityUntrustedCertificateIssuerError");
670 break;
671 case SEC_ERROR_INADEQUATE_KEY_USAGE:
672 errName.AssignLiteral("SecurityInadequateKeyUsageError");
673 break;
674 case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
675 errName.AssignLiteral(
676 "SecurityCertificateSignatureAlgorithmDisabledError");
677 break;
678 default:
679 errName.AssignLiteral("SecurityError");
680 break;
682 } else {
683 // NSS_SSL errors
684 switch (static_cast<SSLErrorCodes>(status)) {
685 case SSL_ERROR_NO_CERTIFICATE:
686 errName.AssignLiteral("SecurityNoCertificateError");
687 break;
688 case SSL_ERROR_BAD_CERTIFICATE:
689 errName.AssignLiteral("SecurityBadCertificateError");
690 break;
691 case SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE:
692 errName.AssignLiteral("SecurityUnsupportedCertificateTypeError");
693 break;
694 case SSL_ERROR_UNSUPPORTED_VERSION:
695 errName.AssignLiteral("SecurityUnsupportedTLSVersionError");
696 break;
697 case SSL_ERROR_BAD_CERT_DOMAIN:
698 errName.AssignLiteral("SecurityCertificateDomainMismatchError");
699 break;
700 default:
701 errName.AssignLiteral("SecurityError");
702 break;
705 } else {
706 // must be network
707 errorType.AssignLiteral("Network");
709 switch (status) {
710 // connect to host:port failed
711 case NS_ERROR_CONNECTION_REFUSED:
712 errName.AssignLiteral("ConnectionRefusedError");
713 break;
714 // network timeout error
715 case NS_ERROR_NET_TIMEOUT:
716 errName.AssignLiteral("NetworkTimeoutError");
717 break;
718 // hostname lookup failed
719 case NS_ERROR_UNKNOWN_HOST:
720 errName.AssignLiteral("DomainNotFoundError");
721 break;
722 case NS_ERROR_NET_INTERRUPT:
723 errName.AssignLiteral("NetworkInterruptError");
724 break;
725 default:
726 errName.AssignLiteral("NetworkError");
727 break;
731 Unused << NS_WARN_IF(NS_FAILED(FireErrorEvent(errName, errorType, status)));
734 return FireEvent(u"close"_ns);
737 void TCPSocket::Close() { CloseHelper(true); }
739 void TCPSocket::CloseImmediately() { CloseHelper(false); }
741 void TCPSocket::CloseHelper(bool waitForUnsentData) {
742 if (mReadyState == TCPReadyState::Closed ||
743 mReadyState == TCPReadyState::Closing) {
744 return;
747 mReadyState = TCPReadyState::Closing;
749 if (mProxyRequest) {
750 mProxyRequest->Cancel(NS_BINDING_ABORTED);
751 mProxyRequest = nullptr;
754 if (mSocketBridgeChild) {
755 mSocketBridgeChild->SendClose();
756 return;
759 if (!mAsyncCopierActive || !waitForUnsentData) {
760 mPendingData.Clear();
761 mPendingDataAfterStartTLS.Clear();
763 if (mSocketOutputStream) {
764 mSocketOutputStream->Close();
765 mSocketOutputStream = nullptr;
769 if (mSocketInputStream) {
770 mSocketInputStream->Close();
771 mSocketInputStream = nullptr;
775 bool TCPSocket::Send(const nsACString& aData, mozilla::ErrorResult& aRv) {
776 if (mReadyState != TCPReadyState::Open) {
777 aRv.Throw(NS_ERROR_FAILURE);
778 return false;
781 uint64_t byteLength;
782 nsCOMPtr<nsIInputStream> stream;
783 if (mSocketBridgeChild) {
784 mSocketBridgeChild->SendSend(aData);
785 byteLength = aData.Length();
786 } else {
787 nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), aData);
788 if (NS_FAILED(rv)) {
789 aRv.Throw(rv);
790 return false;
792 rv = stream->Available(&byteLength);
793 if (NS_FAILED(rv)) {
794 aRv.Throw(rv);
795 return false;
798 return Send(stream, byteLength);
801 bool TCPSocket::Send(const ArrayBuffer& aData, uint32_t aByteOffset,
802 const Optional<uint32_t>& aByteLength,
803 mozilla::ErrorResult& aRv) {
804 if (mReadyState != TCPReadyState::Open) {
805 aRv.Throw(NS_ERROR_FAILURE);
806 return false;
809 nsCOMPtr<nsIArrayBufferInputStream> stream;
811 uint32_t nbytes;
812 auto calculateOffsetAndCount = [&](uint32_t aLength) {
813 uint32_t offset = std::min(aLength, aByteOffset);
814 nbytes = std::min(aLength - aByteOffset,
815 aByteLength.WasPassed() ? aByteLength.Value() : aLength);
816 return std::pair(offset, nbytes);
819 if (mSocketBridgeChild) {
820 nsTArray<uint8_t> arrayBuffer;
821 if (!aData.AppendDataTo(arrayBuffer, calculateOffsetAndCount)) {
822 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
823 return false;
826 mSocketBridgeChild->SendSend(std::move(arrayBuffer));
827 } else {
828 mozilla::Maybe<mozilla::UniquePtr<uint8_t[]>> arrayBuffer =
829 aData.CreateFromData<mozilla::UniquePtr<uint8_t[]>>(
830 calculateOffsetAndCount);
831 if (arrayBuffer.isNothing()) {
832 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
833 return false;
836 stream = do_CreateInstance("@mozilla.org/io/arraybuffer-input-stream;1");
837 nsresult rv = stream->SetData(arrayBuffer.extract(), nbytes);
838 if (NS_WARN_IF(NS_FAILED(rv))) {
839 aRv.Throw(rv);
840 return false;
843 return Send(stream, nbytes);
846 bool TCPSocket::Send(nsIInputStream* aStream, uint32_t aByteLength) {
847 uint64_t newBufferedAmount = BufferedAmount() + aByteLength;
848 bool bufferFull = newBufferedAmount > BUFFER_SIZE;
849 if (bufferFull) {
850 // If we buffered more than some arbitrary amount of data,
851 // (65535 right now) we should tell the caller so they can
852 // wait until ondrain is called if they so desire. Once all the
853 // buffered data has been written to the socket, ondrain is
854 // called.
855 mWaitingForDrain = true;
858 if (mSocketBridgeChild) {
859 // In the child, we just add the buffer length to our bufferedAmount and let
860 // the parent update our bufferedAmount when the data have been sent.
861 mBufferedAmount = newBufferedAmount;
862 return !bufferFull;
865 // This is used to track how many packets we have been told to send. Signaling
866 // this back to the user of this API allows the user to know how many packets
867 // are currently in flight over IPC.
868 ++mTrackingNumber;
869 if (mWaitingForStartTLS) {
870 // When we are waiting for starttls, newStream is added to pendingData
871 // and will be appended to multiplexStream after tls had been set up.
872 mPendingDataAfterStartTLS.AppendElement(aStream);
873 } else {
874 mPendingData.AppendElement(aStream);
877 CalculateBufferedAmount();
878 EnsureCopying();
880 return !bufferFull;
883 TCPReadyState TCPSocket::ReadyState() { return mReadyState; }
885 TCPSocketBinaryType TCPSocket::BinaryType() const {
886 if (mUseArrayBuffers) {
887 return TCPSocketBinaryType::Arraybuffer;
889 return TCPSocketBinaryType::String;
892 already_AddRefed<TCPSocket> TCPSocket::CreateAcceptedSocket(
893 nsIGlobalObject* aGlobal, nsISocketTransport* aTransport,
894 bool aUseArrayBuffers) {
895 RefPtr<TCPSocket> socket =
896 new TCPSocket(aGlobal, u""_ns, 0, false, aUseArrayBuffers);
897 nsresult rv = socket->InitWithTransport(aTransport);
898 NS_ENSURE_SUCCESS(rv, nullptr);
899 return socket.forget();
902 already_AddRefed<TCPSocket> TCPSocket::CreateAcceptedSocket(
903 nsIGlobalObject* aGlobal, TCPSocketChild* aBridge, bool aUseArrayBuffers) {
904 RefPtr<TCPSocket> socket =
905 new TCPSocket(aGlobal, u""_ns, 0, false, aUseArrayBuffers);
906 socket->InitWithSocketChild(aBridge);
907 return socket.forget();
910 already_AddRefed<TCPSocket> TCPSocket::Constructor(
911 const GlobalObject& aGlobal, const nsAString& aHost, uint16_t aPort,
912 const SocketOptions& aOptions, mozilla::ErrorResult& aRv) {
913 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
914 RefPtr<TCPSocket> socket =
915 new TCPSocket(global, aHost, aPort, aOptions.mUseSecureTransport,
916 aOptions.mBinaryType == TCPSocketBinaryType::Arraybuffer);
917 socket->ResolveProxy();
919 return socket.forget();
922 nsresult TCPSocket::ResolveProxy() {
923 nsresult rv;
924 nsCOMPtr<nsIProtocolProxyService> pps =
925 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
926 if (NS_WARN_IF(NS_FAILED(rv))) {
927 return rv;
930 nsCOMPtr<nsIURI> uri;
931 nsCString spec = mSsl ? "https://"_ns : "http://"_ns;
932 bool maybeIPv6 = mHost.FindChar(':') != -1;
933 if (maybeIPv6) spec.Append('[');
934 if (!AppendUTF16toUTF8(mHost, spec, fallible)) {
935 return NS_ERROR_OUT_OF_MEMORY;
937 if (maybeIPv6) spec.Append(']');
938 rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
939 .SetSpec(spec)
940 .SetPort(mPort)
941 .Finalize(uri);
942 if (NS_WARN_IF(NS_FAILED(rv))) {
943 return rv;
946 nsCOMPtr<nsIChannel> channel;
947 rv = NS_NewChannel(getter_AddRefs(channel), uri,
948 nsContentUtils::GetSystemPrincipal(),
949 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
950 nsIContentPolicy::TYPE_OTHER);
951 if (NS_WARN_IF(NS_FAILED(rv))) {
952 return rv;
955 rv = pps->AsyncResolve(channel,
956 nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY,
957 this, nullptr, getter_AddRefs(mProxyRequest));
958 if (NS_WARN_IF(NS_FAILED(rv))) {
959 return rv;
962 return NS_OK;
965 NS_IMETHODIMP
966 TCPSocket::OnProxyAvailable(nsICancelable* aRequest, nsIChannel* aChannel,
967 nsIProxyInfo* aProxyInfo, nsresult aResult) {
968 mProxyRequest = nullptr;
969 if (NS_SUCCEEDED(aResult) && aProxyInfo) {
970 nsCString proxyType;
971 nsresult rv = aProxyInfo->GetType(proxyType);
972 if (NS_WARN_IF(NS_FAILED(rv))) {
973 Close();
974 return rv;
976 // Only supports SOCKS proxy for now.
977 if (proxyType == "socks" || proxyType == "socks4") {
978 return Init(aProxyInfo);
981 return Init(nullptr);
984 nsresult TCPSocket::CreateInputStreamPump() {
985 if (!mSocketInputStream) {
986 return NS_ERROR_NOT_AVAILABLE;
988 nsresult rv;
989 mInputStreamPump =
990 do_CreateInstance("@mozilla.org/network/input-stream-pump;1", &rv);
991 NS_ENSURE_SUCCESS(rv, rv);
993 rv = mInputStreamPump->Init(mSocketInputStream, 0, 0, false, nullptr);
994 NS_ENSURE_SUCCESS(rv, rv);
996 uint64_t suspendCount = mSuspendCount;
997 while (suspendCount--) {
998 mInputStreamPump->Suspend();
1001 rv = mInputStreamPump->AsyncRead(this);
1002 NS_ENSURE_SUCCESS(rv, rv);
1003 return NS_OK;
1006 NS_IMETHODIMP
1007 TCPSocket::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
1008 int64_t aProgress, int64_t aProgressMax) {
1009 if (static_cast<uint32_t>(aStatus) !=
1010 static_cast<uint32_t>(nsISocketTransport::STATUS_CONNECTED_TO)) {
1011 return NS_OK;
1014 mReadyState = TCPReadyState::Open;
1015 nsresult rv = CreateInputStreamPump();
1016 NS_ENSURE_SUCCESS(rv, rv);
1017 FireEvent(u"open"_ns);
1019 return NS_OK;
1022 NS_IMETHODIMP
1023 TCPSocket::OnInputStreamReady(nsIAsyncInputStream* aStream) {
1024 // Only used for detecting if the connection was refused.
1026 uint64_t dummy;
1027 nsresult rv = aStream->Available(&dummy);
1028 if (NS_FAILED(rv)) {
1029 MaybeReportErrorAndCloseIfOpen(NS_ERROR_CONNECTION_REFUSED);
1031 return NS_OK;
1034 NS_IMETHODIMP
1035 TCPSocket::OnStartRequest(nsIRequest* aRequest) { return NS_OK; }
1037 NS_IMETHODIMP
1038 TCPSocket::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream,
1039 uint64_t aOffset, uint32_t aCount) {
1040 if (mUseArrayBuffers) {
1041 nsTArray<uint8_t> buffer;
1042 buffer.SetCapacity(aCount);
1043 uint32_t actual;
1044 nsresult rv = aStream->Read(reinterpret_cast<char*>(buffer.Elements()),
1045 aCount, &actual);
1046 NS_ENSURE_SUCCESS(rv, rv);
1047 MOZ_ASSERT(actual == aCount);
1048 buffer.SetLength(actual);
1050 if (mSocketBridgeParent) {
1051 mSocketBridgeParent->FireArrayBufferDataEvent(buffer, mReadyState);
1052 return NS_OK;
1055 AutoJSAPI api;
1056 if (!api.Init(GetOwnerGlobal())) {
1057 return NS_ERROR_FAILURE;
1059 JSContext* cx = api.cx();
1061 JS::Rooted<JS::Value> value(cx);
1062 if (!ToJSValue(cx, TypedArrayCreator<ArrayBuffer>(buffer), &value)) {
1063 return NS_ERROR_FAILURE;
1065 FireDataEvent(cx, u"data"_ns, value);
1066 return NS_OK;
1069 nsCString data;
1070 nsresult rv = mInputStreamScriptable->ReadBytes(aCount, data);
1071 NS_ENSURE_SUCCESS(rv, rv);
1073 if (mSocketBridgeParent) {
1074 mSocketBridgeParent->FireStringDataEvent(data, mReadyState);
1075 return NS_OK;
1078 AutoJSAPI api;
1079 if (!api.Init(GetOwnerGlobal())) {
1080 return NS_ERROR_FAILURE;
1082 JSContext* cx = api.cx();
1084 JS::Rooted<JS::Value> value(cx);
1085 if (!ToJSValue(cx, NS_ConvertASCIItoUTF16(data), &value)) {
1086 return NS_ERROR_FAILURE;
1088 FireDataEvent(cx, u"data"_ns, value);
1090 return NS_OK;
1093 NS_IMETHODIMP
1094 TCPSocket::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
1095 mInputStreamPump = nullptr;
1097 if (mAsyncCopierActive && NS_SUCCEEDED(aStatus)) {
1098 // If we have some buffered output still, and status is not an
1099 // error, the other side has done a half-close, but we don't
1100 // want to be in the close state until we are done sending
1101 // everything that was buffered. We also don't want to call onclose
1102 // yet.
1103 return NS_OK;
1106 // We call this even if there is no error.
1107 MaybeReportErrorAndCloseIfOpen(aStatus);
1108 return NS_OK;
1111 void TCPSocket::SetSocketBridgeParent(TCPSocketParent* aBridgeParent) {
1112 MOZ_ASSERT(NS_IsMainThread());
1114 mSocketBridgeParent = aBridgeParent;
1117 NS_IMETHODIMP
1118 TCPSocket::UpdateReadyState(uint32_t aReadyState) {
1119 MOZ_ASSERT(mSocketBridgeChild);
1120 mReadyState = static_cast<TCPReadyState>(aReadyState);
1121 return NS_OK;
1124 NS_IMETHODIMP
1125 TCPSocket::UpdateBufferedAmount(uint32_t aBufferedAmount,
1126 uint32_t aTrackingNumber) {
1127 if (aTrackingNumber != mTrackingNumber) {
1128 return NS_OK;
1130 mBufferedAmount = aBufferedAmount;
1131 if (!mBufferedAmount) {
1132 if (mWaitingForDrain) {
1133 mWaitingForDrain = false;
1134 return FireEvent(u"drain"_ns);
1137 return NS_OK;
1140 NS_IMETHODIMP
1141 TCPSocket::Observe(nsISupports* aSubject, const char* aTopic,
1142 const char16_t* aData) {
1143 if (!strcmp(aTopic, "inner-window-destroyed")) {
1144 nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
1145 NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
1146 uint64_t innerID;
1147 nsresult rv = wrapper->GetData(&innerID);
1148 if (NS_WARN_IF(NS_FAILED(rv))) {
1149 return rv;
1152 if (innerID == mInnerWindowID) {
1153 Close();
1155 } else if (!strcmp(aTopic, "profile-change-net-teardown")) {
1156 Close();
1159 return NS_OK;
1162 /* static */
1163 bool TCPSocket::ShouldTCPSocketExist(JSContext* aCx, JSObject* aGlobal) {
1164 JS::Rooted<JSObject*> global(aCx, aGlobal);
1165 return nsContentUtils::ObjectPrincipal(global)->IsSystemPrincipal();