summaryrefslogtreecommitdiffstats
path: root/dom/flyweb
diff options
context:
space:
mode:
Diffstat (limited to 'dom/flyweb')
-rw-r--r--dom/flyweb/FlyWebDiscoveryManager.cpp125
-rw-r--r--dom/flyweb/FlyWebDiscoveryManager.h61
-rw-r--r--dom/flyweb/FlyWebPublishOptionsIPCSerializer.h33
-rw-r--r--dom/flyweb/FlyWebPublishedServer.cpp675
-rw-r--r--dom/flyweb/FlyWebPublishedServer.h109
-rw-r--r--dom/flyweb/FlyWebPublishedServerIPC.h172
-rw-r--r--dom/flyweb/FlyWebServerEvents.cpp141
-rw-r--r--dom/flyweb/FlyWebServerEvents.h88
-rw-r--r--dom/flyweb/FlyWebService.cpp1310
-rw-r--r--dom/flyweb/FlyWebService.h113
-rw-r--r--dom/flyweb/HttpServer.cpp1319
-rw-r--r--dom/flyweb/HttpServer.h193
-rw-r--r--dom/flyweb/PFlyWebPublishedServer.ipdl38
-rw-r--r--dom/flyweb/moz.build42
14 files changed, 4419 insertions, 0 deletions
diff --git a/dom/flyweb/FlyWebDiscoveryManager.cpp b/dom/flyweb/FlyWebDiscoveryManager.cpp
new file mode 100644
index 000000000..5a97eb6d8
--- /dev/null
+++ b/dom/flyweb/FlyWebDiscoveryManager.cpp
@@ -0,0 +1,125 @@
+/* -*- 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 "nsString.h"
+#include "nsTHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsIUUIDGenerator.h"
+#include "jsapi.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Logging.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/dom/FlyWebDiscoveryManager.h"
+#include "mozilla/dom/FlyWebDiscoveryManagerBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+static LazyLogModule gFlyWebDiscoveryManagerLog("FlyWebDiscoveryManager");
+#undef LOG_I
+#define LOG_I(...) MOZ_LOG(mozilla::dom::gFlyWebDiscoveryManagerLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#undef LOG_E
+#define LOG_E(...) MOZ_LOG(mozilla::dom::gFlyWebDiscoveryManagerLog, mozilla::LogLevel::Error, (__VA_ARGS__))
+
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(FlyWebDiscoveryManager)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FlyWebDiscoveryManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FlyWebDiscoveryManager)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FlyWebDiscoveryManager)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+FlyWebDiscoveryManager::FlyWebDiscoveryManager(nsISupports* aParent,
+ FlyWebService* aService)
+ : mParent(aParent)
+ , mService(aService)
+ , mNextId(0)
+{
+}
+
+FlyWebDiscoveryManager::~FlyWebDiscoveryManager()
+{
+ mService->UnregisterDiscoveryManager(this);
+}
+
+JSObject*
+FlyWebDiscoveryManager::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return FlyWebDiscoveryManagerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsISupports*
+FlyWebDiscoveryManager::GetParentObject() const
+{
+ return mParent;
+}
+
+/* static */ already_AddRefed<FlyWebDiscoveryManager>
+FlyWebDiscoveryManager::Constructor(const GlobalObject& aGlobal, ErrorResult& rv)
+{
+ RefPtr<FlyWebService> service = FlyWebService::GetOrCreate();
+ if (!service) {
+ return nullptr;
+ }
+
+ RefPtr<FlyWebDiscoveryManager> result = new FlyWebDiscoveryManager(
+ aGlobal.GetAsSupports(), service);
+ return result.forget();
+}
+
+void
+FlyWebDiscoveryManager::ListServices(nsTArray<FlyWebDiscoveredService>& aServices)
+{
+ return mService->ListDiscoveredServices(aServices);
+}
+
+uint32_t
+FlyWebDiscoveryManager::StartDiscovery(FlyWebDiscoveryCallback& aCallback)
+{
+ uint32_t id = GenerateId();
+ mCallbackMap.Put(id, &aCallback);
+ mService->RegisterDiscoveryManager(this);
+ return id;
+}
+
+void
+FlyWebDiscoveryManager::StopDiscovery(uint32_t aId)
+{
+ mCallbackMap.Remove(aId);
+ if (mCallbackMap.Count() == 0) {
+ mService->UnregisterDiscoveryManager(this);
+ }
+}
+
+void
+FlyWebDiscoveryManager::PairWithService(const nsAString& aServiceId,
+ FlyWebPairingCallback& aCallback)
+{
+ mService->PairWithService(aServiceId, aCallback);
+}
+
+void
+FlyWebDiscoveryManager::NotifyDiscoveredServicesChanged()
+{
+ nsTArray<FlyWebDiscoveredService> services;
+ ListServices(services);
+ Sequence<FlyWebDiscoveredService> servicesSeq;
+ servicesSeq.SwapElements(services);
+ for (auto iter = mCallbackMap.Iter(); !iter.Done(); iter.Next()) {
+ FlyWebDiscoveryCallback *callback = iter.UserData();
+ ErrorResult err;
+ callback->OnDiscoveredServicesChanged(servicesSeq, err);
+ ENSURE_SUCCESS_VOID(err);
+ }
+}
+
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/flyweb/FlyWebDiscoveryManager.h b/dom/flyweb/FlyWebDiscoveryManager.h
new file mode 100644
index 000000000..cb5f692f8
--- /dev/null
+++ b/dom/flyweb/FlyWebDiscoveryManager.h
@@ -0,0 +1,61 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_FlyWebDiscoveryManager_h
+#define mozilla_dom_FlyWebDiscoveryManager_h
+
+#include "nsISupportsImpl.h"
+#include "mozilla/ErrorResult.h"
+#include "nsRefPtrHashtable.h"
+#include "nsWrapperCache.h"
+#include "FlyWebDiscoveryManagerBinding.h"
+#include "FlyWebService.h"
+
+namespace mozilla {
+namespace dom {
+
+class FlyWebDiscoveryManager final : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FlyWebDiscoveryManager)
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+ nsISupports* GetParentObject() const;
+
+ static already_AddRefed<FlyWebDiscoveryManager> Constructor(const GlobalObject& aGlobal,
+ ErrorResult& rv);
+
+ void ListServices(nsTArray<FlyWebDiscoveredService>& aServices);
+ uint32_t StartDiscovery(FlyWebDiscoveryCallback& aCallback);
+ void StopDiscovery(uint32_t aId);
+
+ void PairWithService(const nsAString& aServiceId,
+ FlyWebPairingCallback& callback);
+
+ void NotifyDiscoveredServicesChanged();
+
+private:
+ FlyWebDiscoveryManager(nsISupports* mParent, FlyWebService* aService);
+ ~FlyWebDiscoveryManager();
+
+ uint32_t GenerateId() {
+ return ++mNextId;
+ }
+
+ nsCOMPtr<nsISupports> mParent;
+ RefPtr<FlyWebService> mService;
+
+ uint32_t mNextId;
+
+ nsRefPtrHashtable<nsUint32HashKey, FlyWebDiscoveryCallback> mCallbackMap;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FlyWebDiscoveryManager_h
diff --git a/dom/flyweb/FlyWebPublishOptionsIPCSerializer.h b/dom/flyweb/FlyWebPublishOptionsIPCSerializer.h
new file mode 100644
index 000000000..fa1a44113
--- /dev/null
+++ b/dom/flyweb/FlyWebPublishOptionsIPCSerializer.h
@@ -0,0 +1,33 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_FlyWebPublishOptionsIPCSerialiser_h
+#define mozilla_dom_FlyWebPublishOptionsIPCSerialiser_h
+
+#include "mozilla/dom/FlyWebPublishBinding.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::FlyWebPublishOptions>
+{
+ typedef mozilla::dom::FlyWebPublishOptions paramType;
+
+ // Function to serialize a FlyWebPublishOptions
+ static void Write(Message *aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.mUiUrl);
+ }
+ // Function to de-serialize a FlyWebPublishOptions
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ return ReadParam(aMsg, aIter, &(aResult->mUiUrl));
+ }
+};
+
+}
+
+#endif // mozilla_dom_FlyWebPublishOptionsIPCSerialiser_h
diff --git a/dom/flyweb/FlyWebPublishedServer.cpp b/dom/flyweb/FlyWebPublishedServer.cpp
new file mode 100644
index 000000000..375df332f
--- /dev/null
+++ b/dom/flyweb/FlyWebPublishedServer.cpp
@@ -0,0 +1,675 @@
+/* -*- 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 "mozilla/dom/FlyWebPublishedServerIPC.h"
+#include "mozilla/dom/FlyWebPublishBinding.h"
+#include "mozilla/dom/FlyWebService.h"
+#include "mozilla/dom/Request.h"
+#include "mozilla/dom/FlyWebServerEvents.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/InternalResponse.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/net/IPCTransportProvider.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsGlobalWindow.h"
+#include "WebSocketChannel.h"
+
+namespace mozilla {
+namespace dom {
+
+static LazyLogModule gFlyWebPublishedServerLog("FlyWebPublishedServer");
+#undef LOG_I
+#define LOG_I(...) MOZ_LOG(mozilla::dom::gFlyWebPublishedServerLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#undef LOG_E
+#define LOG_E(...) MOZ_LOG(mozilla::dom::gFlyWebPublishedServerLog, mozilla::LogLevel::Error, (__VA_ARGS__))
+
+/******** FlyWebPublishedServer ********/
+
+FlyWebPublishedServer::FlyWebPublishedServer(nsPIDOMWindowInner* aOwner,
+ const nsAString& aName,
+ const FlyWebPublishOptions& aOptions)
+ : mozilla::DOMEventTargetHelper(aOwner)
+ , mOwnerWindowID(aOwner ? aOwner->WindowID() : 0)
+ , mName(aName)
+ , mUiUrl(aOptions.mUiUrl)
+ , mIsRegistered(true) // Registered by the FlyWebService
+{
+}
+
+void
+FlyWebPublishedServer::LastRelease()
+{
+ // Make sure to unregister to avoid dangling pointers. Use the LastRelease
+ // hook rather than dtor since calling virtual functions during dtor
+ // wouldn't do what we want. Also, LastRelease is called earlier than dtor
+ // for CC objects.
+ Close();
+}
+
+JSObject*
+FlyWebPublishedServer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return FlyWebPublishedServerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+FlyWebPublishedServer::Close()
+{
+ LOG_I("FlyWebPublishedServer::Close(%p)", this);
+
+ // Unregister from server.
+ if (mIsRegistered) {
+ MOZ_ASSERT(FlyWebService::GetExisting());
+ FlyWebService::GetExisting()->UnregisterServer(this);
+ mIsRegistered = false;
+
+ DispatchTrustedEvent(NS_LITERAL_STRING("close"));
+ }
+}
+
+void
+FlyWebPublishedServer::FireFetchEvent(InternalRequest* aRequest)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+ RefPtr<FlyWebFetchEvent> e = new FlyWebFetchEvent(this,
+ new Request(global, aRequest),
+ aRequest);
+ e->Init(this);
+ e->InitEvent(NS_LITERAL_STRING("fetch"), false, false);
+
+ DispatchTrustedEvent(e);
+}
+
+void
+FlyWebPublishedServer::FireWebsocketEvent(InternalRequest* aConnectRequest)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+ RefPtr<FlyWebFetchEvent> e = new FlyWebWebSocketEvent(this,
+ new Request(global, aConnectRequest),
+ aConnectRequest);
+ e->Init(this);
+ e->InitEvent(NS_LITERAL_STRING("websocket"), false, false);
+
+ DispatchTrustedEvent(e);
+}
+
+void
+FlyWebPublishedServer::PublishedServerStarted(nsresult aStatus)
+{
+ LOG_I("FlyWebPublishedServer::PublishedServerStarted(%p)", this);
+
+ RefPtr<FlyWebPublishPromise> promise = mPublishPromise.Ensure(__func__);
+ if (NS_SUCCEEDED(aStatus)) {
+ mPublishPromise.Resolve(this, __func__);
+ } else {
+ Close();
+ mPublishPromise.Reject(aStatus, __func__);
+ }
+}
+
+already_AddRefed<WebSocket>
+FlyWebPublishedServer::OnWebSocketAccept(InternalRequest* aConnectRequest,
+ const Optional<nsAString>& aProtocol,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aConnectRequest);
+
+ LOG_I("FlyWebPublishedServer::OnWebSocketAccept(%p)", this);
+
+ nsCOMPtr<nsITransportProvider> provider =
+ OnWebSocketAcceptInternal(aConnectRequest,
+ aProtocol,
+ aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(provider);
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetOwner());
+ AutoJSContext cx;
+ GlobalObject global(cx, nsGlobalWindow::Cast(window)->FastGetGlobalJSObject());
+
+ nsAutoCString extensions, negotiatedExtensions;
+ aConnectRequest->Headers()->
+ GetFirst(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions, aRv);
+ mozilla::net::ProcessServerWebSocketExtensions(extensions,
+ negotiatedExtensions);
+
+ nsCString url;
+ aConnectRequest->GetURL(url);
+ Sequence<nsString> protocols;
+ if (aProtocol.WasPassed() &&
+ !protocols.AppendElement(aProtocol.Value(), fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ return WebSocket::ConstructorCommon(global,
+ NS_ConvertUTF8toUTF16(url),
+ protocols,
+ provider,
+ negotiatedExtensions,
+ aRv);
+}
+
+/******** FlyWebPublishedServerImpl ********/
+
+NS_IMPL_ISUPPORTS_INHERITED0(FlyWebPublishedServerImpl, mozilla::DOMEventTargetHelper)
+
+FlyWebPublishedServerImpl::FlyWebPublishedServerImpl(nsPIDOMWindowInner* aOwner,
+ const nsAString& aName,
+ const FlyWebPublishOptions& aOptions)
+ : FlyWebPublishedServer(aOwner, aName, aOptions)
+ , mHttpServer(new HttpServer())
+{
+ LOG_I("FlyWebPublishedServerImpl::FlyWebPublishedServerImpl(%p)", this);
+}
+
+void
+FlyWebPublishedServerImpl::PermissionGranted(bool aGranted)
+{
+ LOG_I("FlyWebPublishedServerImpl::PermissionGranted(%b)", aGranted);
+ if (!aGranted) {
+ PublishedServerStarted(NS_ERROR_FAILURE);
+ return;
+ }
+
+ mHttpServer->Init(-1, Preferences::GetBool("flyweb.use-tls", false), this);
+}
+
+void
+FlyWebPublishedServerImpl::Close()
+{
+ FlyWebPublishedServer::Close();
+
+ if (mMDNSCancelRegister) {
+ mMDNSCancelRegister->Cancel(NS_BINDING_ABORTED);
+ mMDNSCancelRegister = nullptr;
+ }
+
+ if (mHttpServer) {
+ RefPtr<HttpServer> server = mHttpServer.forget();
+ server->Close();
+ }
+}
+
+void
+FlyWebPublishedServerImpl::OnServerStarted(nsresult aStatus)
+{
+ if (NS_SUCCEEDED(aStatus)) {
+ FlyWebService::GetOrCreate()->StartDiscoveryOf(this);
+ } else {
+ PublishedServerStarted(aStatus);
+ }
+}
+
+void
+FlyWebPublishedServerImpl::OnFetchResponse(InternalRequest* aRequest,
+ InternalResponse* aResponse)
+{
+ MOZ_ASSERT(aRequest);
+ MOZ_ASSERT(aResponse);
+
+ LOG_I("FlyWebPublishedServerImpl::OnFetchResponse(%p)", this);
+
+ if (mHttpServer) {
+ mHttpServer->SendResponse(aRequest, aResponse);
+ }
+}
+
+void
+FlyWebPublishedServerImpl::OnWebSocketResponse(InternalRequest* aConnectRequest,
+ InternalResponse* aResponse)
+{
+ MOZ_ASSERT(aConnectRequest);
+ MOZ_ASSERT(aResponse);
+
+ LOG_I("FlyWebPublishedMDNSServer::OnWebSocketResponse(%p)", this);
+
+ if (mHttpServer) {
+ mHttpServer->SendWebSocketResponse(aConnectRequest, aResponse);
+ }
+}
+
+already_AddRefed<nsITransportProvider>
+FlyWebPublishedServerImpl::OnWebSocketAcceptInternal(InternalRequest* aConnectRequest,
+ const Optional<nsAString>& aProtocol,
+ ErrorResult& aRv)
+{
+ LOG_I("FlyWebPublishedServerImpl::OnWebSocketAcceptInternal(%p)", this);
+
+ if (!mHttpServer) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ return mHttpServer->AcceptWebSocket(aConnectRequest,
+ aProtocol,
+ aRv);
+}
+
+/******** FlyWebPublishedServerChild ********/
+
+FlyWebPublishedServerChild::FlyWebPublishedServerChild(nsPIDOMWindowInner* aOwner,
+ const nsAString& aName,
+ const FlyWebPublishOptions& aOptions)
+ : FlyWebPublishedServer(aOwner, aName, aOptions)
+ , mActorExists(false)
+{
+ LOG_I("FlyWebPublishedServerChild::FlyWebPublishedServerChild(%p)", this);
+
+ // The matching release happens when the actor is destroyed, in
+ // ContentChild::DeallocPFlyWebPublishedServerChild
+ NS_ADDREF_THIS();
+}
+
+void
+FlyWebPublishedServerChild::PermissionGranted(bool aGranted)
+{
+ if (!aGranted) {
+ PublishedServerStarted(NS_ERROR_FAILURE);
+ return;
+ }
+
+ mActorExists = true;
+ FlyWebPublishOptions options;
+ options.mUiUrl = mUiUrl;
+
+ // Proceed with initialization.
+ ContentChild::GetSingleton()->
+ SendPFlyWebPublishedServerConstructor(this, mName, options);
+}
+
+bool
+FlyWebPublishedServerChild::RecvServerReady(const nsresult& aStatus)
+{
+ LOG_I("FlyWebPublishedServerChild::RecvServerReady(%p)", this);
+ MOZ_ASSERT(mActorExists);
+
+ PublishedServerStarted(aStatus);
+ return true;
+}
+
+bool
+FlyWebPublishedServerChild::RecvServerClose()
+{
+ LOG_I("FlyWebPublishedServerChild::RecvServerClose(%p)", this);
+ MOZ_ASSERT(mActorExists);
+
+ Close();
+
+ return true;
+}
+
+bool
+FlyWebPublishedServerChild::RecvFetchRequest(const IPCInternalRequest& aRequest,
+ const uint64_t& aRequestId)
+{
+ LOG_I("FlyWebPublishedServerChild::RecvFetchRequest(%p)", this);
+ MOZ_ASSERT(mActorExists);
+
+ RefPtr<InternalRequest> request = new InternalRequest(aRequest);
+ mPendingRequests.Put(request, aRequestId);
+ FireFetchEvent(request);
+
+ return true;
+}
+
+bool
+FlyWebPublishedServerChild::RecvWebSocketRequest(const IPCInternalRequest& aRequest,
+ const uint64_t& aRequestId,
+ PTransportProviderChild* aProvider)
+{
+ LOG_I("FlyWebPublishedServerChild::RecvWebSocketRequest(%p)", this);
+ MOZ_ASSERT(mActorExists);
+
+ RefPtr<InternalRequest> request = new InternalRequest(aRequest);
+ mPendingRequests.Put(request, aRequestId);
+
+ // Not addreffing here. The addref was already done when the
+ // PTransportProvider child constructor original ran.
+ mPendingTransportProviders.Put(aRequestId,
+ dont_AddRef(static_cast<TransportProviderChild*>(aProvider)));
+
+ FireWebsocketEvent(request);
+
+ return true;
+}
+
+void
+FlyWebPublishedServerChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOG_I("FlyWebPublishedServerChild::ActorDestroy(%p)", this);
+
+ mActorExists = false;
+}
+
+void
+FlyWebPublishedServerChild::OnFetchResponse(InternalRequest* aRequest,
+ InternalResponse* aResponse)
+{
+ LOG_I("FlyWebPublishedServerChild::OnFetchResponse(%p)", this);
+
+ if (!mActorExists) {
+ LOG_I("FlyWebPublishedServerChild::OnFetchResponse(%p) - No actor!", this);
+ return;
+ }
+
+ uint64_t id = mPendingRequests.Get(aRequest);
+ MOZ_ASSERT(id);
+ mPendingRequests.Remove(aRequest);
+
+ IPCInternalResponse ipcResp;
+ UniquePtr<mozilla::ipc::AutoIPCStream> autoStream;
+ nsIContentChild* cc = static_cast<ContentChild*>(Manager());
+ aResponse->ToIPC(&ipcResp, cc, autoStream);
+ Unused << SendFetchResponse(ipcResp, id);
+ if (autoStream) {
+ autoStream->TakeOptionalValue();
+ }
+}
+
+already_AddRefed<nsITransportProvider>
+FlyWebPublishedServerChild::OnWebSocketAcceptInternal(InternalRequest* aRequest,
+ const Optional<nsAString>& aProtocol,
+ ErrorResult& aRv)
+{
+ LOG_I("FlyWebPublishedServerChild::OnWebSocketAcceptInternal(%p)", this);
+
+ if (!mActorExists) {
+ LOG_I("FlyWebPublishedServerChild::OnWebSocketAcceptInternal(%p) - No actor!", this);
+ return nullptr;
+ }
+
+ uint64_t id = mPendingRequests.Get(aRequest);
+ MOZ_ASSERT(id);
+ mPendingRequests.Remove(aRequest);
+
+ RefPtr<TransportProviderChild> provider;
+ mPendingTransportProviders.Remove(id, getter_AddRefs(provider));
+
+ nsString protocol;
+ if (aProtocol.WasPassed()) {
+ protocol = aProtocol.Value();
+
+ nsAutoCString reqProtocols;
+ aRequest->Headers()->
+ GetFirst(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"), reqProtocols, aRv);
+ if (!ContainsToken(reqProtocols, NS_ConvertUTF16toUTF8(protocol))) {
+ // Should throw a better error here
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ } else {
+ protocol.SetIsVoid(true);
+ }
+
+ Unused << SendWebSocketAccept(protocol, id);
+
+ return provider.forget();
+}
+
+void
+FlyWebPublishedServerChild::OnWebSocketResponse(InternalRequest* aRequest,
+ InternalResponse* aResponse)
+{
+ LOG_I("FlyWebPublishedServerChild::OnFetchResponse(%p)", this);
+
+ if (!mActorExists) {
+ LOG_I("FlyWebPublishedServerChild::OnFetchResponse(%p) - No actor!", this);
+ return;
+ }
+
+ uint64_t id = mPendingRequests.Get(aRequest);
+ MOZ_ASSERT(id);
+ mPendingRequests.Remove(aRequest);
+
+ mPendingTransportProviders.Remove(id);
+
+ IPCInternalResponse ipcResp;
+ UniquePtr<mozilla::ipc::AutoIPCStream> autoStream;
+ nsIContentChild* cc = static_cast<ContentChild*>(Manager());
+ aResponse->ToIPC(&ipcResp, cc, autoStream);
+
+ Unused << SendWebSocketResponse(ipcResp, id);
+ if (autoStream) {
+ autoStream->TakeOptionalValue();
+ }
+}
+
+void
+FlyWebPublishedServerChild::Close()
+{
+ LOG_I("FlyWebPublishedServerChild::Close(%p)", this);
+
+ FlyWebPublishedServer::Close();
+
+ if (mActorExists) {
+ LOG_I("FlyWebPublishedServerChild::Close - sending __delete__ (%p)", this);
+
+ Send__delete__(this);
+ }
+}
+
+/******** FlyWebPublishedServerParent ********/
+
+NS_IMPL_ISUPPORTS(FlyWebPublishedServerParent, nsIDOMEventListener)
+
+FlyWebPublishedServerParent::FlyWebPublishedServerParent(const nsAString& aName,
+ const FlyWebPublishOptions& aOptions)
+ : mActorDestroyed(false)
+ , mNextRequestId(1)
+{
+ LOG_I("FlyWebPublishedServerParent::FlyWebPublishedServerParent(%p)", this);
+
+ RefPtr<FlyWebService> service = FlyWebService::GetOrCreate();
+ if (!service) {
+ Unused << SendServerReady(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<FlyWebPublishPromise> mozPromise =
+ service->PublishServer(aName, aOptions, nullptr);
+ if (!mozPromise) {
+ Unused << SendServerReady(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<FlyWebPublishedServerParent> self = this;
+
+ mozPromise->Then(
+ AbstractThread::MainThread(),
+ __func__,
+ [this, self] (FlyWebPublishedServer* aServer) {
+ mPublishedServer = static_cast<FlyWebPublishedServerImpl*>(aServer);
+ if (mActorDestroyed) {
+ mPublishedServer->Close();
+ return;
+ }
+
+ mPublishedServer->AddEventListener(NS_LITERAL_STRING("fetch"),
+ this, false, false, 2);
+ mPublishedServer->AddEventListener(NS_LITERAL_STRING("websocket"),
+ this, false, false, 2);
+ mPublishedServer->AddEventListener(NS_LITERAL_STRING("close"),
+ this, false, false, 2);
+ Unused << SendServerReady(NS_OK);
+ },
+ [this, self] (nsresult aStatus) {
+ MOZ_ASSERT(NS_FAILED(aStatus));
+ if (!mActorDestroyed) {
+ Unused << SendServerReady(aStatus);
+ }
+ });
+}
+
+NS_IMETHODIMP
+FlyWebPublishedServerParent::HandleEvent(nsIDOMEvent* aEvent)
+{
+ if (mActorDestroyed) {
+ return NS_OK;
+ }
+
+ nsAutoString type;
+ aEvent->GetType(type);
+ if (type.EqualsLiteral("close")) {
+ Unused << SendServerClose();
+ return NS_OK;
+ }
+
+ if (type.EqualsLiteral("fetch")) {
+ RefPtr<InternalRequest> request =
+ static_cast<FlyWebFetchEvent*>(aEvent)->Request()->GetInternalRequest();
+ uint64_t id = mNextRequestId++;
+ mPendingRequests.Put(id, request);
+
+ IPCInternalRequest ipcReq;
+ request->ToIPC(&ipcReq);
+ Unused << SendFetchRequest(ipcReq, id);
+ return NS_OK;
+ }
+
+ if (type.EqualsLiteral("websocket")) {
+ RefPtr<InternalRequest> request =
+ static_cast<FlyWebWebSocketEvent*>(aEvent)->Request()->GetInternalRequest();
+ uint64_t id = mNextRequestId++;
+ mPendingRequests.Put(id, request);
+
+ nsTArray<PNeckoParent*> neckoParents;
+ Manager()->ManagedPNeckoParent(neckoParents);
+ if (neckoParents.Length() != 1) {
+ MOZ_CRASH("Expected exactly 1 PNeckoParent instance per PNeckoChild");
+ }
+
+ RefPtr<TransportProviderParent> provider =
+ static_cast<TransportProviderParent*>(
+ neckoParents[0]->SendPTransportProviderConstructor());
+
+ IPCInternalRequest ipcReq;
+ request->ToIPC(&ipcReq);
+ Unused << SendWebSocketRequest(ipcReq, id, provider);
+
+ mPendingTransportProviders.Put(id, provider.forget());
+ return NS_OK;
+ }
+
+ MOZ_CRASH("Unknown event type");
+
+ return NS_OK;
+}
+
+bool
+FlyWebPublishedServerParent::RecvFetchResponse(const IPCInternalResponse& aResponse,
+ const uint64_t& aRequestId)
+{
+ MOZ_ASSERT(!mActorDestroyed);
+
+ RefPtr<InternalRequest> request;
+ mPendingRequests.Remove(aRequestId, getter_AddRefs(request));
+ if (!request) {
+ static_cast<ContentParent*>(Manager())->KillHard("unknown request id");
+ return false;
+ }
+
+ RefPtr<InternalResponse> response = InternalResponse::FromIPC(aResponse);
+
+ mPublishedServer->OnFetchResponse(request, response);
+
+ return true;
+}
+
+bool
+FlyWebPublishedServerParent::RecvWebSocketResponse(const IPCInternalResponse& aResponse,
+ const uint64_t& aRequestId)
+{
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mPendingTransportProviders.Remove(aRequestId);
+
+ RefPtr<InternalRequest> request;
+ mPendingRequests.Remove(aRequestId, getter_AddRefs(request));
+ if (!request) {
+ static_cast<ContentParent*>(Manager())->KillHard("unknown websocket request id");
+ return false;
+ }
+
+ RefPtr<InternalResponse> response = InternalResponse::FromIPC(aResponse);
+
+ mPublishedServer->OnWebSocketResponse(request, response);
+
+ return true;
+}
+
+bool
+FlyWebPublishedServerParent::RecvWebSocketAccept(const nsString& aProtocol,
+ const uint64_t& aRequestId)
+{
+ MOZ_ASSERT(!mActorDestroyed);
+
+ RefPtr<TransportProviderParent> providerIPC;
+ mPendingTransportProviders.Remove(aRequestId, getter_AddRefs(providerIPC));
+
+ RefPtr<InternalRequest> request;
+ mPendingRequests.Remove(aRequestId, getter_AddRefs(request));
+
+ if (!request || !providerIPC) {
+ static_cast<ContentParent*>(Manager())->KillHard("unknown websocket request id");
+ return false;
+ }
+
+ Optional<nsAString> protocol;
+ if (!aProtocol.IsVoid()) {
+ protocol = &aProtocol;
+ }
+
+ ErrorResult result;
+ nsCOMPtr<nsITransportProvider> providerServer =
+ mPublishedServer->OnWebSocketAcceptInternal(request, protocol, result);
+ if (result.Failed()) {
+ return false;
+ }
+
+ providerServer->SetListener(providerIPC);
+
+ return true;
+}
+
+void
+FlyWebPublishedServerParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOG_I("FlyWebPublishedServerParent::ActorDestroy(%p)", this);
+
+ mActorDestroyed = true;
+}
+
+bool
+FlyWebPublishedServerParent::Recv__delete__()
+{
+ LOG_I("FlyWebPublishedServerParent::Recv__delete__(%p)", this);
+ MOZ_ASSERT(!mActorDestroyed);
+
+ if (mPublishedServer) {
+ mPublishedServer->RemoveEventListener(NS_LITERAL_STRING("fetch"),
+ this, false);
+ mPublishedServer->RemoveEventListener(NS_LITERAL_STRING("websocket"),
+ this, false);
+ mPublishedServer->RemoveEventListener(NS_LITERAL_STRING("close"),
+ this, false);
+ mPublishedServer->Close();
+ mPublishedServer = nullptr;
+ }
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla
+
+
diff --git a/dom/flyweb/FlyWebPublishedServer.h b/dom/flyweb/FlyWebPublishedServer.h
new file mode 100644
index 000000000..ec3a685ec
--- /dev/null
+++ b/dom/flyweb/FlyWebPublishedServer.h
@@ -0,0 +1,109 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_FlyWebPublishedServer_h
+#define mozilla_dom_FlyWebPublishedServer_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/MozPromise.h"
+
+class nsPIDOMWindowInner;
+class nsITransportProvider;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class InternalResponse;
+class InternalRequest;
+class WebSocket;
+struct FlyWebPublishOptions;
+class FlyWebPublishedServer;
+
+typedef MozPromise<RefPtr<FlyWebPublishedServer>, nsresult, false>
+ FlyWebPublishPromise;
+
+class FlyWebPublishedServer : public mozilla::DOMEventTargetHelper
+{
+public:
+ FlyWebPublishedServer(nsPIDOMWindowInner* aOwner,
+ const nsAString& aName,
+ const FlyWebPublishOptions& aOptions);
+
+ virtual void LastRelease() override;
+
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+ uint64_t OwnerWindowID() const {
+ return mOwnerWindowID;
+ }
+
+ void GetName(nsAString& aName)
+ {
+ aName = mName;
+ }
+ nsAString& Name()
+ {
+ return mName;
+ }
+
+ void GetUiUrl(nsAString& aUiUrl)
+ {
+ aUiUrl = mUiUrl;
+ }
+
+ virtual void PermissionGranted(bool aGranted) = 0;
+
+ virtual void OnFetchResponse(InternalRequest* aRequest,
+ InternalResponse* aResponse) = 0;
+ already_AddRefed<WebSocket>
+ OnWebSocketAccept(InternalRequest* aConnectRequest,
+ const Optional<nsAString>& aProtocol,
+ ErrorResult& aRv);
+ virtual void OnWebSocketResponse(InternalRequest* aConnectRequest,
+ InternalResponse* aResponse) = 0;
+ virtual already_AddRefed<nsITransportProvider>
+ OnWebSocketAcceptInternal(InternalRequest* aConnectRequest,
+ const Optional<nsAString>& aProtocol,
+ ErrorResult& aRv) = 0;
+
+ virtual void Close();
+
+ void FireFetchEvent(InternalRequest* aRequest);
+ void FireWebsocketEvent(InternalRequest* aConnectRequest);
+ void PublishedServerStarted(nsresult aStatus);
+
+ IMPL_EVENT_HANDLER(fetch)
+ IMPL_EVENT_HANDLER(websocket)
+ IMPL_EVENT_HANDLER(close)
+
+ already_AddRefed<FlyWebPublishPromise>
+ GetPublishPromise()
+ {
+ return mPublishPromise.Ensure(__func__);
+ }
+
+protected:
+ virtual ~FlyWebPublishedServer()
+ {
+ MOZ_ASSERT(!mIsRegistered, "Subclass dtor forgot to call Close()");
+ }
+
+ uint64_t mOwnerWindowID;
+ MozPromiseHolder<FlyWebPublishPromise> mPublishPromise;
+
+ nsString mName;
+ nsString mUiUrl;
+
+ bool mIsRegistered;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FlyWebPublishedServer_h
diff --git a/dom/flyweb/FlyWebPublishedServerIPC.h b/dom/flyweb/FlyWebPublishedServerIPC.h
new file mode 100644
index 000000000..942c7847e
--- /dev/null
+++ b/dom/flyweb/FlyWebPublishedServerIPC.h
@@ -0,0 +1,172 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_FlyWebPublishedServerIPC_h
+#define mozilla_dom_FlyWebPublishedServerIPC_h
+
+#include "HttpServer.h"
+#include "mozilla/dom/FlyWebPublishedServer.h"
+#include "mozilla/dom/PFlyWebPublishedServerParent.h"
+#include "mozilla/dom/PFlyWebPublishedServerChild.h"
+#include "mozilla/MozPromise.h"
+#include "nsICancelable.h"
+#include "nsIDOMEventListener.h"
+#include "nsISupportsImpl.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace net {
+class TransportProviderParent;
+class TransportProviderChild;
+}
+
+namespace dom {
+
+class FlyWebPublishedServerParent;
+
+class FlyWebPublishedServerImpl final : public FlyWebPublishedServer
+ , public HttpServerListener
+{
+public:
+ FlyWebPublishedServerImpl(nsPIDOMWindowInner* aOwner,
+ const nsAString& aName,
+ const FlyWebPublishOptions& aOptions);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ int32_t Port()
+ {
+ return mHttpServer ? mHttpServer->GetPort() : 0;
+ }
+ void GetCertKey(nsACString& aKey) {
+ if (mHttpServer) {
+ mHttpServer->GetCertKey(aKey);
+ } else {
+ aKey.Truncate();
+ }
+ }
+
+ virtual void PermissionGranted(bool aGranted) override;
+ virtual void OnFetchResponse(InternalRequest* aRequest,
+ InternalResponse* aResponse) override;
+ virtual void OnWebSocketResponse(InternalRequest* aConnectRequest,
+ InternalResponse* aResponse) override;
+ virtual already_AddRefed<nsITransportProvider>
+ OnWebSocketAcceptInternal(InternalRequest* aConnectRequest,
+ const Optional<nsAString>& aProtocol,
+ ErrorResult& aRv) override;
+
+ void SetCancelRegister(nsICancelable* aCancelRegister)
+ {
+ mMDNSCancelRegister = aCancelRegister;
+ }
+
+ virtual void Close() override;
+
+ // HttpServerListener
+ virtual void OnServerStarted(nsresult aStatus) override;
+ virtual void OnRequest(InternalRequest* aRequest) override
+ {
+ FireFetchEvent(aRequest);
+ }
+ virtual void OnWebSocket(InternalRequest* aConnectRequest) override
+ {
+ FireWebsocketEvent(aConnectRequest);
+ }
+ virtual void OnServerClose() override
+ {
+ mHttpServer = nullptr;
+ Close();
+ }
+
+private:
+ ~FlyWebPublishedServerImpl() {}
+
+ RefPtr<HttpServer> mHttpServer;
+ nsCOMPtr<nsICancelable> mMDNSCancelRegister;
+ RefPtr<FlyWebPublishedServerParent> mServerParent;
+};
+
+class FlyWebPublishedServerChild final : public FlyWebPublishedServer
+ , public PFlyWebPublishedServerChild
+{
+public:
+ FlyWebPublishedServerChild(nsPIDOMWindowInner* aOwner,
+ const nsAString& aName,
+ const FlyWebPublishOptions& aOptions);
+
+ virtual void PermissionGranted(bool aGranted) override;
+ virtual bool RecvServerReady(const nsresult& aStatus) override;
+ virtual bool RecvServerClose() override;
+ virtual bool RecvFetchRequest(const IPCInternalRequest& aRequest,
+ const uint64_t& aRequestId) override;
+ virtual bool RecvWebSocketRequest(const IPCInternalRequest& aRequest,
+ const uint64_t& aRequestId,
+ PTransportProviderChild* aProvider) override;
+
+ virtual void OnFetchResponse(InternalRequest* aRequest,
+ InternalResponse* aResponse) override;
+ virtual void OnWebSocketResponse(InternalRequest* aConnectRequest,
+ InternalResponse* aResponse) override;
+ virtual already_AddRefed<nsITransportProvider>
+ OnWebSocketAcceptInternal(InternalRequest* aConnectRequest,
+ const Optional<nsAString>& aProtocol,
+ ErrorResult& aRv) override;
+
+ virtual void Close() override;
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+ ~FlyWebPublishedServerChild() {}
+
+ nsDataHashtable<nsRefPtrHashKey<InternalRequest>, uint64_t> mPendingRequests;
+ nsRefPtrHashtable<nsUint64HashKey, TransportProviderChild>
+ mPendingTransportProviders;
+ bool mActorExists;
+};
+
+class FlyWebPublishedServerParent final : public PFlyWebPublishedServerParent
+ , public nsIDOMEventListener
+{
+public:
+ FlyWebPublishedServerParent(const nsAString& aName,
+ const FlyWebPublishOptions& aOptions);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+private:
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool
+ Recv__delete__() override;
+ virtual bool
+ RecvFetchResponse(const IPCInternalResponse& aResponse,
+ const uint64_t& aRequestId) override;
+ virtual bool
+ RecvWebSocketResponse(const IPCInternalResponse& aResponse,
+ const uint64_t& aRequestId) override;
+ virtual bool
+ RecvWebSocketAccept(const nsString& aProtocol,
+ const uint64_t& aRequestId) override;
+
+ ~FlyWebPublishedServerParent() {}
+
+ bool mActorDestroyed;
+ uint64_t mNextRequestId;
+ nsRefPtrHashtable<nsUint64HashKey, InternalRequest> mPendingRequests;
+ nsRefPtrHashtable<nsUint64HashKey, TransportProviderParent>
+ mPendingTransportProviders;
+ RefPtr<FlyWebPublishedServerImpl> mPublishedServer;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FlyWebPublishedServerIPC_h
diff --git a/dom/flyweb/FlyWebServerEvents.cpp b/dom/flyweb/FlyWebServerEvents.cpp
new file mode 100644
index 000000000..fe774ffb0
--- /dev/null
+++ b/dom/flyweb/FlyWebServerEvents.cpp
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mozilla/dom/EventBinding.h"
+#include "mozilla/dom/FlyWebFetchEventBinding.h"
+#include "mozilla/dom/FlyWebPublishedServer.h"
+#include "mozilla/dom/FlyWebServerEvents.h"
+#include "mozilla/dom/FlyWebWebSocketEventBinding.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Response.h"
+
+#include "js/GCAPI.h"
+
+namespace mozilla {
+namespace dom {
+
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(FlyWebFetchEvent)
+
+NS_IMPL_ADDREF_INHERITED(FlyWebFetchEvent, Event)
+NS_IMPL_RELEASE_INHERITED(FlyWebFetchEvent, Event)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FlyWebFetchEvent, Event)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRequest)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(FlyWebFetchEvent, Event)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FlyWebFetchEvent, Event)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRequest)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FlyWebFetchEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+FlyWebFetchEvent::FlyWebFetchEvent(FlyWebPublishedServer* aServer,
+ class Request* aRequest,
+ InternalRequest* aInternalRequest)
+ : Event(aServer, nullptr, nullptr)
+ , mRequest(aRequest)
+ , mInternalRequest(aInternalRequest)
+ , mServer(aServer)
+ , mResponded(false)
+{
+ MOZ_ASSERT(aServer);
+ MOZ_ASSERT(aRequest);
+ MOZ_ASSERT(aInternalRequest);
+}
+
+FlyWebFetchEvent::~FlyWebFetchEvent()
+{
+}
+
+JSObject*
+FlyWebFetchEvent::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return FlyWebFetchEventBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+FlyWebFetchEvent::RespondWith(Promise& aArg, ErrorResult& aRv)
+{
+ if (mResponded) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ mResponded = true;
+
+ aArg.AppendNativeHandler(this);
+}
+
+void
+FlyWebFetchEvent::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+ RefPtr<Response> response;
+ if (aValue.isObject()) {
+ UNWRAP_OBJECT(Response, &aValue.toObject(), response);
+ }
+
+ RefPtr<InternalResponse> intResponse;
+ if (response && response->Type() != ResponseType::Opaque) {
+ intResponse = response->GetInternalResponse();
+ }
+
+ if (!intResponse) {
+ intResponse = InternalResponse::NetworkError();
+ }
+
+ NotifyServer(intResponse);
+}
+
+void
+FlyWebFetchEvent::NotifyServer(InternalResponse* aResponse)
+{
+ mServer->OnFetchResponse(mInternalRequest, aResponse);
+}
+
+void
+FlyWebFetchEvent::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+ RefPtr<InternalResponse> err = InternalResponse::NetworkError();
+
+ NotifyServer(err);
+}
+
+JSObject*
+FlyWebWebSocketEvent::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return FlyWebWebSocketEventBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<WebSocket>
+FlyWebWebSocketEvent::Accept(const Optional<nsAString>& aProtocol,
+ ErrorResult& aRv)
+{
+ if (mResponded) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ mResponded = true;
+
+ return mServer->OnWebSocketAccept(mInternalRequest, aProtocol, aRv);
+}
+
+
+void
+FlyWebWebSocketEvent::NotifyServer(InternalResponse* aResponse)
+{
+ mServer->OnWebSocketResponse(mInternalRequest, aResponse);
+}
+
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/flyweb/FlyWebServerEvents.h b/dom/flyweb/FlyWebServerEvents.h
new file mode 100644
index 000000000..f00e86018
--- /dev/null
+++ b/dom/flyweb/FlyWebServerEvents.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_dom_FlyWebFetchEvent_h
+#define mozilla_dom_FlyWebFetchEvent_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/FlyWebFetchEventBinding.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/WebSocket.h"
+
+struct JSContext;
+namespace mozilla {
+namespace dom {
+
+class Request;
+class Response;
+class FlyWebPublishedServer;
+class InternalRequest;
+class InternalResponse;
+
+class FlyWebFetchEvent : public Event
+ , public PromiseNativeHandler
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(FlyWebFetchEvent, Event)
+
+ virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+ virtual void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ class Request* Request() const
+ {
+ return mRequest;
+ }
+
+ void RespondWith(Promise& aArg, ErrorResult& aRv);
+
+ FlyWebFetchEvent(FlyWebPublishedServer* aServer,
+ class Request* aRequest,
+ InternalRequest* aInternalRequest);
+
+protected:
+ virtual ~FlyWebFetchEvent();
+
+ virtual void NotifyServer(InternalResponse* aResponse);
+
+ RefPtr<class Request> mRequest;
+ RefPtr<InternalRequest> mInternalRequest;
+ RefPtr<FlyWebPublishedServer> mServer;
+
+ bool mResponded;
+};
+
+class FlyWebWebSocketEvent final : public FlyWebFetchEvent
+{
+public:
+ FlyWebWebSocketEvent(FlyWebPublishedServer* aServer,
+ class Request* aRequest,
+ InternalRequest* aInternalRequest)
+ : FlyWebFetchEvent(aServer, aRequest, aInternalRequest)
+ {}
+
+ virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<WebSocket> Accept(const Optional<nsAString>& aProtocol,
+ ErrorResult& aRv);
+
+private:
+ ~FlyWebWebSocketEvent() {};
+
+ virtual void NotifyServer(InternalResponse* aResponse) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FlyWebFetchEvent_h
diff --git a/dom/flyweb/FlyWebService.cpp b/dom/flyweb/FlyWebService.cpp
new file mode 100644
index 000000000..5f3b0d66f
--- /dev/null
+++ b/dom/flyweb/FlyWebService.cpp
@@ -0,0 +1,1310 @@
+/* -*- 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 "mozilla/dom/FlyWebService.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/FlyWebPublishedServerIPC.h"
+#include "mozilla/AddonPathService.h"
+#include "nsISocketTransportService.h"
+#include "mdns/libmdns/nsDNSServiceInfo.h"
+#include "nsIUUIDGenerator.h"
+#include "nsStandardURL.h"
+#include "mozilla/Services.h"
+#include "nsISupportsPrimitives.h"
+#include "mozilla/dom/FlyWebDiscoveryManagerBinding.h"
+#include "prnetdb.h"
+#include "DNS.h"
+#include "nsContentPermissionHelper.h"
+#include "nsSocketTransportService2.h"
+#include "nsSocketTransport2.h"
+#include "nsHashPropertyBag.h"
+#include "nsNetUtil.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIProperty.h"
+#include "nsICertOverrideService.h"
+
+namespace mozilla {
+namespace dom {
+
+struct FlyWebPublishOptions;
+
+static LazyLogModule gFlyWebServiceLog("FlyWebService");
+#undef LOG_I
+#define LOG_I(...) MOZ_LOG(mozilla::dom::gFlyWebServiceLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+
+#undef LOG_E
+#define LOG_E(...) MOZ_LOG(mozilla::dom::gFlyWebServiceLog, mozilla::LogLevel::Error, (__VA_ARGS__))
+
+#undef LOG_TEST_I
+#define LOG_TEST_I(...) MOZ_LOG_TEST(mozilla::dom::gFlyWebServiceLog, mozilla::LogLevel::Debug)
+
+class FlyWebPublishServerPermissionCheck final
+ : public nsIContentPermissionRequest
+ , public nsIRunnable
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ FlyWebPublishServerPermissionCheck(const nsCString& aServiceName, uint64_t aWindowID,
+ FlyWebPublishedServer* aServer)
+ : mServiceName(aServiceName)
+ , mWindowID(aWindowID)
+ , mServer(aServer)
+ {}
+
+ uint64_t WindowID() const
+ {
+ return mWindowID;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsGlobalWindow* globalWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
+ if (!globalWindow) {
+ return Cancel();
+ }
+ mWindow = globalWindow->AsInner();
+ if (NS_WARN_IF(!mWindow)) {
+ return Cancel();
+ }
+
+ nsCOMPtr<nsIDocument> doc = mWindow->GetDoc();
+ if (NS_WARN_IF(!doc)) {
+ return Cancel();
+ }
+
+ mPrincipal = doc->NodePrincipal();
+ MOZ_ASSERT(mPrincipal);
+
+ mRequester = new nsContentPermissionRequester(mWindow);
+ return nsContentPermissionUtils::AskPermission(this, mWindow);
+ }
+
+ NS_IMETHOD Cancel() override
+ {
+ Resolve(false);
+ return NS_OK;
+ }
+
+ NS_IMETHOD Allow(JS::HandleValue aChoices) override
+ {
+ MOZ_ASSERT(aChoices.isUndefined());
+ Resolve(true);
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetTypes(nsIArray** aTypes) override
+ {
+ nsTArray<nsString> emptyOptions;
+ return nsContentPermissionUtils::CreatePermissionArray(NS_LITERAL_CSTRING("flyweb-publish-server"),
+ NS_LITERAL_CSTRING("unused"), emptyOptions, aTypes);
+ }
+
+ NS_IMETHOD GetRequester(nsIContentPermissionRequester** aRequester) override
+ {
+ NS_ENSURE_ARG_POINTER(aRequester);
+ nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+ requester.forget(aRequester);
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetPrincipal(nsIPrincipal** aRequestingPrincipal) override
+ {
+ NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal);
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetWindow(mozIDOMWindow** aRequestingWindow) override
+ {
+ NS_IF_ADDREF(*aRequestingWindow = mWindow);
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetElement(nsIDOMElement** aRequestingElement) override
+ {
+ *aRequestingElement = nullptr;
+ return NS_OK;
+ }
+
+private:
+ void Resolve(bool aResolve)
+ {
+ mServer->PermissionGranted(aResolve);
+ }
+
+ virtual ~FlyWebPublishServerPermissionCheck() = default;
+
+ nsCString mServiceName;
+ uint64_t mWindowID;
+ RefPtr<FlyWebPublishedServer> mServer;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIContentPermissionRequester> mRequester;
+};
+
+NS_IMPL_ISUPPORTS(FlyWebPublishServerPermissionCheck,
+ nsIContentPermissionRequest,
+ nsIRunnable)
+
+class FlyWebMDNSService final
+ : public nsIDNSServiceDiscoveryListener
+ , public nsIDNSServiceResolveListener
+ , public nsIDNSRegistrationListener
+ , public nsITimerCallback
+{
+ friend class FlyWebService;
+
+private:
+ enum DiscoveryState {
+ DISCOVERY_IDLE,
+ DISCOVERY_STARTING,
+ DISCOVERY_RUNNING,
+ DISCOVERY_STOPPING
+ };
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDNSSERVICEDISCOVERYLISTENER
+ NS_DECL_NSIDNSSERVICERESOLVELISTENER
+ NS_DECL_NSIDNSREGISTRATIONLISTENER
+ NS_DECL_NSITIMERCALLBACK
+
+ explicit FlyWebMDNSService(FlyWebService* aService,
+ const nsACString& aServiceType);
+
+private:
+ virtual ~FlyWebMDNSService() = default;
+
+ nsresult Init();
+ nsresult StartDiscovery();
+ nsresult StopDiscovery();
+
+ void ListDiscoveredServices(nsTArray<FlyWebDiscoveredService>& aServices);
+ bool HasService(const nsAString& aServiceId);
+ nsresult PairWithService(const nsAString& aServiceId,
+ UniquePtr<FlyWebService::PairedInfo>& aInfo);
+
+ nsresult StartDiscoveryOf(FlyWebPublishedServerImpl* aServer);
+
+ void EnsureDiscoveryStarted();
+ void EnsureDiscoveryStopped();
+
+ // Cycle-breaking link to manager.
+ FlyWebService* mService;
+ nsCString mServiceType;
+
+ // Indicates the desired state of the system. If mDiscoveryActive is true,
+ // it indicates that backend discovery "should be happening", and discovery
+ // events should be forwarded to listeners.
+ // If false, the backend discovery "should be idle", and any discovery events
+ // that show up should not be forwarded to listeners.
+ bool mDiscoveryActive;
+
+ uint32_t mNumConsecutiveStartDiscoveryFailures;
+
+ // Represents the internal discovery state as it relates to nsDNSServiceDiscovery.
+ // When mDiscoveryActive is true, this state will periodically loop from
+ // (IDLE => STARTING => RUNNING => STOPPING => IDLE).
+ DiscoveryState mDiscoveryState;
+
+ nsCOMPtr<nsITimer> mDiscoveryStartTimer;
+ nsCOMPtr<nsITimer> mDiscoveryStopTimer;
+ nsCOMPtr<nsIDNSServiceDiscovery> mDNSServiceDiscovery;
+ nsCOMPtr<nsICancelable> mCancelDiscovery;
+ nsTHashtable<nsStringHashKey> mNewServiceSet;
+
+ struct DiscoveredInfo
+ {
+ explicit DiscoveredInfo(nsIDNSServiceInfo* aDNSServiceInfo);
+ FlyWebDiscoveredService mService;
+ nsCOMPtr<nsIDNSServiceInfo> mDNSServiceInfo;
+ };
+ nsClassHashtable<nsStringHashKey, DiscoveredInfo> mServiceMap;
+};
+
+void
+LogDNSInfo(nsIDNSServiceInfo* aServiceInfo, const char* aFunc)
+{
+ if (!LOG_TEST_I()) {
+ return;
+ }
+
+ nsCString tmp;
+ aServiceInfo->GetServiceName(tmp);
+ LOG_I("%s: serviceName=%s", aFunc, tmp.get());
+
+ aServiceInfo->GetHost(tmp);
+ LOG_I("%s: host=%s", aFunc, tmp.get());
+
+ aServiceInfo->GetAddress(tmp);
+ LOG_I("%s: address=%s", aFunc, tmp.get());
+
+ uint16_t port = -2;
+ aServiceInfo->GetPort(&port);
+ LOG_I("%s: port=%d", aFunc, (int)port);
+
+ nsCOMPtr<nsIPropertyBag2> attributes;
+ aServiceInfo->GetAttributes(getter_AddRefs(attributes));
+ if (!attributes) {
+ LOG_I("%s: no attributes", aFunc);
+ } else {
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ attributes->GetEnumerator(getter_AddRefs(enumerator));
+ MOZ_ASSERT(enumerator);
+
+ LOG_I("%s: attributes start", aFunc);
+
+ bool hasMoreElements;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) &&
+ hasMoreElements) {
+ nsCOMPtr<nsISupports> element;
+ MOZ_ALWAYS_SUCCEEDS(enumerator->GetNext(getter_AddRefs(element)));
+ nsCOMPtr<nsIProperty> property = do_QueryInterface(element);
+ MOZ_ASSERT(property);
+
+ nsAutoString name;
+ nsCOMPtr<nsIVariant> value;
+ MOZ_ALWAYS_SUCCEEDS(property->GetName(name));
+ MOZ_ALWAYS_SUCCEEDS(property->GetValue(getter_AddRefs(value)));
+
+ nsAutoCString str;
+ nsresult rv = value->GetAsACString(str);
+ if (NS_SUCCEEDED(rv)) {
+ LOG_I("%s: attribute name=%s value=%s", aFunc,
+ NS_ConvertUTF16toUTF8(name).get(), str.get());
+ } else {
+ uint16_t type;
+ MOZ_ALWAYS_SUCCEEDS(value->GetDataType(&type));
+ LOG_I("%s: attribute *unstringifiable* name=%s type=%d", aFunc,
+ NS_ConvertUTF16toUTF8(name).get(), (int)type);
+ }
+ }
+
+ LOG_I("%s: attributes end", aFunc);
+ }
+}
+
+NS_IMPL_ISUPPORTS(FlyWebMDNSService,
+ nsIDNSServiceDiscoveryListener,
+ nsIDNSServiceResolveListener,
+ nsIDNSRegistrationListener,
+ nsITimerCallback)
+
+FlyWebMDNSService::FlyWebMDNSService(
+ FlyWebService* aService,
+ const nsACString& aServiceType)
+ : mService(aService)
+ , mServiceType(aServiceType)
+ , mDiscoveryActive(false)
+ , mNumConsecutiveStartDiscoveryFailures(0)
+ , mDiscoveryState(DISCOVERY_IDLE)
+{}
+
+nsresult
+FlyWebMDNSService::OnDiscoveryStarted(const nsACString& aServiceType)
+{
+ MOZ_ASSERT(mDiscoveryState == DISCOVERY_STARTING);
+ mDiscoveryState = DISCOVERY_RUNNING;
+ // Reset consecutive start discovery failures.
+ mNumConsecutiveStartDiscoveryFailures = 0;
+ LOG_I("===========================================");
+ LOG_I("MDNSService::OnDiscoveryStarted(%s)", PromiseFlatCString(aServiceType).get());
+ LOG_I("===========================================");
+
+ // Clear the new service array.
+ mNewServiceSet.Clear();
+
+ // If service discovery is inactive, then stop network discovery immediately.
+ if (!mDiscoveryActive) {
+ // Set the stop timer to fire immediately.
+ Unused << NS_WARN_IF(NS_FAILED(mDiscoveryStopTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT)));
+ return NS_OK;
+ }
+
+ // Otherwise, set the stop timer to fire in 5 seconds.
+ Unused << NS_WARN_IF(NS_FAILED(mDiscoveryStopTimer->InitWithCallback(this, 5 * 1000, nsITimer::TYPE_ONE_SHOT)));
+
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnDiscoveryStopped(const nsACString& aServiceType)
+{
+ LOG_I("///////////////////////////////////////////");
+ LOG_I("MDNSService::OnDiscoveryStopped(%s)", PromiseFlatCString(aServiceType).get());
+ LOG_I("///////////////////////////////////////////");
+ MOZ_ASSERT(mDiscoveryState == DISCOVERY_STOPPING);
+ mDiscoveryState = DISCOVERY_IDLE;
+
+ // If service discovery is inactive, then discard all results and do not proceed.
+ if (!mDiscoveryActive) {
+ mServiceMap.Clear();
+ mNewServiceSet.Clear();
+ return NS_OK;
+ }
+
+ // Process the service map, add to the pair map.
+ for (auto iter = mServiceMap.Iter(); !iter.Done(); iter.Next()) {
+ DiscoveredInfo* service = iter.UserData();
+
+ if (!mNewServiceSet.Contains(service->mService.mServiceId)) {
+ iter.Remove();
+ }
+ }
+
+ // Notify FlyWebService of changed service list.
+ mService->NotifyDiscoveredServicesChanged();
+
+ // Start discovery again immediately.
+ Unused << NS_WARN_IF(NS_FAILED(mDiscoveryStartTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT)));
+
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnServiceFound(nsIDNSServiceInfo* aServiceInfo)
+{
+ LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceFound");
+
+ // If discovery is not active, don't do anything with the result.
+ // If there is no discovery underway, ignore this.
+ if (!mDiscoveryActive || mDiscoveryState != DISCOVERY_RUNNING) {
+ return NS_OK;
+ }
+
+ // Discovery is underway - resolve the service.
+ nsresult rv = mDNSServiceDiscovery->ResolveService(aServiceInfo, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnServiceLost(nsIDNSServiceInfo* aServiceInfo)
+{
+ LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceLost");
+
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnStartDiscoveryFailed(const nsACString& aServiceType, int32_t aErrorCode)
+{
+ LOG_E("MDNSService::OnStartDiscoveryFailed(%s): %d", PromiseFlatCString(aServiceType).get(), (int) aErrorCode);
+
+ MOZ_ASSERT(mDiscoveryState == DISCOVERY_STARTING);
+ mDiscoveryState = DISCOVERY_IDLE;
+ mNumConsecutiveStartDiscoveryFailures++;
+
+ // If discovery is active, and the number of consecutive failures is < 3, try starting again.
+ if (mDiscoveryActive && mNumConsecutiveStartDiscoveryFailures < 3) {
+ Unused << NS_WARN_IF(NS_FAILED(mDiscoveryStartTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT)));
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnStopDiscoveryFailed(const nsACString& aServiceType, int32_t aErrorCode)
+{
+ LOG_E("MDNSService::OnStopDiscoveryFailed(%s)", PromiseFlatCString(aServiceType).get());
+ MOZ_ASSERT(mDiscoveryState == DISCOVERY_STOPPING);
+ mDiscoveryState = DISCOVERY_IDLE;
+
+ // If discovery is active, start discovery again immediately.
+ if (mDiscoveryActive) {
+ Unused << NS_WARN_IF(NS_FAILED(mDiscoveryStartTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT)));
+ }
+
+ return NS_OK;
+}
+
+static bool
+IsAcceptableServiceAddress(const nsCString& addr)
+{
+ PRNetAddr prNetAddr;
+ PRStatus status = PR_StringToNetAddr(addr.get(), &prNetAddr);
+ if (status == PR_FAILURE) {
+ return false;
+ }
+ // Only allow ipv4 addreses for now.
+ return prNetAddr.raw.family == PR_AF_INET;
+}
+
+nsresult
+FlyWebMDNSService::OnServiceResolved(nsIDNSServiceInfo* aServiceInfo)
+{
+ LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceResolved");
+
+ // If discovery is not active, don't do anything with the result.
+ // If there is no discovery underway, ignore this resolve.
+ if (!mDiscoveryActive || mDiscoveryState != DISCOVERY_RUNNING) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ nsCString address;
+ rv = aServiceInfo->GetAddress(address);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!IsAcceptableServiceAddress(address)) {
+ return NS_OK;
+ }
+
+ // Create a new serviceInfo and stuff it in the new service array.
+ UniquePtr<DiscoveredInfo> svc(new DiscoveredInfo(aServiceInfo));
+ mNewServiceSet.PutEntry(svc->mService.mServiceId);
+
+ DiscoveredInfo* existingSvc =
+ mServiceMap.Get(svc->mService.mServiceId);
+ if (existingSvc) {
+ // Update the underlying DNS service info, but leave the old object in place.
+ existingSvc->mDNSServiceInfo = aServiceInfo;
+ } else {
+ DiscoveredInfo* info = svc.release();
+ mServiceMap.Put(info->mService.mServiceId, info);
+ }
+
+ // Notify FlyWebService of changed service list.
+ mService->NotifyDiscoveredServicesChanged();
+
+ return NS_OK;
+}
+
+FlyWebMDNSService::DiscoveredInfo::DiscoveredInfo(nsIDNSServiceInfo* aDNSServiceInfo)
+ : mDNSServiceInfo(aDNSServiceInfo)
+{
+ nsCString tmp;
+ DebugOnly<nsresult> drv = aDNSServiceInfo->GetServiceName(tmp);
+ MOZ_ASSERT(NS_SUCCEEDED(drv));
+ CopyUTF8toUTF16(tmp, mService.mDisplayName);
+
+ mService.mTransport = NS_LITERAL_STRING("mdns");
+
+ drv = aDNSServiceInfo->GetServiceType(tmp);
+ MOZ_ASSERT(NS_SUCCEEDED(drv));
+ CopyUTF8toUTF16(tmp, mService.mServiceType);
+
+ nsCOMPtr<nsIPropertyBag2> attrs;
+ drv = aDNSServiceInfo->GetAttributes(getter_AddRefs(attrs));
+ MOZ_ASSERT(NS_SUCCEEDED(drv));
+ if (attrs) {
+ attrs->GetPropertyAsAString(NS_LITERAL_STRING("cert"), mService.mCert);
+ attrs->GetPropertyAsAString(NS_LITERAL_STRING("path"), mService.mPath);
+ }
+
+ // Construct a service id from the name, host, address, and port.
+ nsCString cHost;
+ drv = aDNSServiceInfo->GetHost(cHost);
+ MOZ_ASSERT(NS_SUCCEEDED(drv));
+
+ nsCString cAddress;
+ drv = aDNSServiceInfo->GetAddress(cAddress);
+ MOZ_ASSERT(NS_SUCCEEDED(drv));
+
+ uint16_t port;
+ drv = aDNSServiceInfo->GetPort(&port);
+ MOZ_ASSERT(NS_SUCCEEDED(drv));
+ nsAutoString portStr;
+ portStr.AppendInt(port, 10);
+
+ mService.mServiceId =
+ NS_ConvertUTF8toUTF16(cAddress) +
+ NS_LITERAL_STRING(":") +
+ portStr +
+ NS_LITERAL_STRING("|") +
+ mService.mServiceType +
+ NS_LITERAL_STRING("|") +
+ NS_ConvertUTF8toUTF16(cHost) +
+ NS_LITERAL_STRING("|") +
+ mService.mDisplayName;
+}
+
+
+nsresult
+FlyWebMDNSService::OnResolveFailed(nsIDNSServiceInfo* aServiceInfo, int32_t aErrorCode)
+{
+ LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnResolveFailed");
+
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnServiceRegistered(nsIDNSServiceInfo* aServiceInfo)
+{
+ LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceRegistered");
+
+ nsCString cName;
+ if (NS_WARN_IF(NS_FAILED(aServiceInfo->GetServiceName(cName)))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsString name = NS_ConvertUTF8toUTF16(cName);
+ RefPtr<FlyWebPublishedServer> existingServer =
+ FlyWebService::GetOrCreate()->FindPublishedServerByName(name);
+ if (!existingServer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ existingServer->PublishedServerStarted(NS_OK);
+
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnServiceUnregistered(nsIDNSServiceInfo* aServiceInfo)
+{
+ LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceUnregistered");
+
+ nsCString cName;
+ if (NS_WARN_IF(NS_FAILED(aServiceInfo->GetServiceName(cName)))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsString name = NS_ConvertUTF8toUTF16(cName);
+ RefPtr<FlyWebPublishedServer> existingServer =
+ FlyWebService::GetOrCreate()->FindPublishedServerByName(name);
+ if (!existingServer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG_I("OnServiceRegistered(MDNS): De-advertised server with name %s.", cName.get());
+
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnRegistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t errorCode)
+{
+ LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnRegistrationFailed");
+
+ nsCString cName;
+ if (NS_WARN_IF(NS_FAILED(aServiceInfo->GetServiceName(cName)))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsString name = NS_ConvertUTF8toUTF16(cName);
+ RefPtr<FlyWebPublishedServer> existingServer =
+ FlyWebService::GetOrCreate()->FindPublishedServerByName(name);
+ if (!existingServer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG_I("OnServiceRegistered(MDNS): Registration of server with name %s failed.", cName.get());
+
+ // Remove the nsICancelable from the published server.
+ existingServer->PublishedServerStarted(NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnUnregistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t errorCode)
+{
+ LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnUnregistrationFailed");
+
+ nsCString cName;
+ if (NS_WARN_IF(NS_FAILED(aServiceInfo->GetServiceName(cName)))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsString name = NS_ConvertUTF8toUTF16(cName);
+ RefPtr<FlyWebPublishedServer> existingServer =
+ FlyWebService::GetOrCreate()->FindPublishedServerByName(name);
+ if (!existingServer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG_I("OnServiceRegistered(MDNS): Un-Advertisement of server with name %s failed.", cName.get());
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::Notify(nsITimer* timer)
+{
+ if (timer == mDiscoveryStopTimer.get()) {
+ LOG_I("MDNSService::Notify() got discovery stop timeout");
+ // Internet discovery stop timer has fired.
+ nsresult rv = StopDiscovery();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ if (timer == mDiscoveryStartTimer.get()) {
+ LOG_I("MDNSService::Notify() got discovery start timeout");
+ // Internet discovery start timer has fired.
+ nsresult rv = StartDiscovery();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ LOG_E("MDNSService::Notify got unknown timeout.");
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::Init()
+{
+ MOZ_ASSERT(mDiscoveryState == DISCOVERY_IDLE);
+
+ mDiscoveryStartTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (!mDiscoveryStartTimer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mDiscoveryStopTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (!mDiscoveryStopTimer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ mDNSServiceDiscovery = do_GetService(DNSSERVICEDISCOVERY_CONTRACT_ID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::StartDiscovery()
+{
+ nsresult rv;
+
+ // Always cancel the timer.
+ rv = mDiscoveryStartTimer->Cancel();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG_E("FlyWeb failed to cancel DNS service discovery start timer.");
+ }
+
+ // If discovery is not idle, don't start it.
+ if (mDiscoveryState != DISCOVERY_IDLE) {
+ return NS_OK;
+ }
+
+ LOG_I("FlyWeb starting dicovery.");
+ mDiscoveryState = DISCOVERY_STARTING;
+
+ // start the discovery.
+ rv = mDNSServiceDiscovery->StartDiscovery(mServiceType, this,
+ getter_AddRefs(mCancelDiscovery));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG_E("FlyWeb failed to start DNS service discovery.");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::StopDiscovery()
+{
+ nsresult rv;
+
+ // Always cancel the timer.
+ rv = mDiscoveryStopTimer->Cancel();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG_E("FlyWeb failed to cancel DNS service discovery stop timer.");
+ }
+
+ // If discovery is not running, do nothing.
+ if (mDiscoveryState != DISCOVERY_RUNNING) {
+ return NS_OK;
+ }
+
+ LOG_I("FlyWeb stopping dicovery.");
+
+ // Mark service discovery as stopping.
+ mDiscoveryState = DISCOVERY_STOPPING;
+
+ if (mCancelDiscovery) {
+ LOG_I("MDNSService::StopDiscovery() - mCancelDiscovery exists!");
+ nsCOMPtr<nsICancelable> cancelDiscovery = mCancelDiscovery.forget();
+ rv = cancelDiscovery->Cancel(NS_ERROR_ABORT);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG_E("FlyWeb failed to cancel DNS stop service discovery.");
+ }
+ } else {
+ LOG_I("MDNSService::StopDiscovery() - mCancelDiscovery does not exist!");
+ mDiscoveryState = DISCOVERY_IDLE;
+ }
+
+ return NS_OK;
+}
+
+void
+FlyWebMDNSService::ListDiscoveredServices(nsTArray<FlyWebDiscoveredService>& aServices)
+{
+ for (auto iter = mServiceMap.Iter(); !iter.Done(); iter.Next()) {
+ aServices.AppendElement(iter.UserData()->mService);
+ }
+}
+
+bool
+FlyWebMDNSService::HasService(const nsAString& aServiceId)
+{
+ return mServiceMap.Contains(aServiceId);
+}
+
+nsresult
+FlyWebMDNSService::PairWithService(const nsAString& aServiceId,
+ UniquePtr<FlyWebService::PairedInfo>& aInfo)
+{
+ MOZ_ASSERT(HasService(aServiceId));
+
+ nsresult rv;
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsID id;
+ rv = uuidgen->GenerateUUIDInPlace(&id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aInfo.reset(new FlyWebService::PairedInfo());
+
+ char uuidChars[NSID_LENGTH];
+ id.ToProvidedString(uuidChars);
+ CopyUTF8toUTF16(Substring(uuidChars + 1, uuidChars + NSID_LENGTH - 2),
+ aInfo->mService.mHostname);
+
+ DiscoveredInfo* discInfo = mServiceMap.Get(aServiceId);
+
+ nsAutoString url;
+ if (discInfo->mService.mCert.IsEmpty()) {
+ url.AssignLiteral("http://");
+ } else {
+ url.AssignLiteral("https://");
+ }
+ url.Append(aInfo->mService.mHostname + NS_LITERAL_STRING("/"));
+ nsCOMPtr<nsIURI> uiURL;
+ NS_NewURI(getter_AddRefs(uiURL), url);
+ MOZ_ASSERT(uiURL);
+ if (!discInfo->mService.mPath.IsEmpty()) {
+ nsCOMPtr<nsIURI> tmp = uiURL.forget();
+ NS_NewURI(getter_AddRefs(uiURL), discInfo->mService.mPath, nullptr, tmp);
+ }
+ if (uiURL) {
+ nsAutoCString spec;
+ uiURL->GetSpec(spec);
+ CopyUTF8toUTF16(spec, aInfo->mService.mUiUrl);
+ }
+
+ aInfo->mService.mDiscoveredService = discInfo->mService;
+ aInfo->mDNSServiceInfo = discInfo->mDNSServiceInfo;
+
+ return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::StartDiscoveryOf(FlyWebPublishedServerImpl* aServer)
+{
+
+ RefPtr<FlyWebPublishedServer> existingServer =
+ FlyWebService::GetOrCreate()->FindPublishedServerByName(aServer->Name());
+ MOZ_ASSERT(existingServer);
+
+ // Advertise the service via mdns.
+ RefPtr<net::nsDNSServiceInfo> serviceInfo(new net::nsDNSServiceInfo());
+
+ serviceInfo->SetPort(aServer->Port());
+ serviceInfo->SetServiceType(mServiceType);
+
+ nsCString certKey;
+ aServer->GetCertKey(certKey);
+ nsString uiURL;
+ aServer->GetUiUrl(uiURL);
+
+ if (!uiURL.IsEmpty() || !certKey.IsEmpty()) {
+ RefPtr<nsHashPropertyBag> attrs = new nsHashPropertyBag();
+ if (!uiURL.IsEmpty()) {
+ attrs->SetPropertyAsAString(NS_LITERAL_STRING("path"), uiURL);
+ }
+ if (!certKey.IsEmpty()) {
+ attrs->SetPropertyAsACString(NS_LITERAL_STRING("cert"), certKey);
+ }
+ serviceInfo->SetAttributes(attrs);
+ }
+
+ nsCString cstrName = NS_ConvertUTF16toUTF8(aServer->Name());
+ LOG_I("MDNSService::StartDiscoveryOf() advertising service %s", cstrName.get());
+ serviceInfo->SetServiceName(cstrName);
+
+ LogDNSInfo(serviceInfo, "FlyWebMDNSService::StartDiscoveryOf");
+
+ // Advertise the service.
+ nsCOMPtr<nsICancelable> cancelRegister;
+ nsresult rv = mDNSServiceDiscovery->
+ RegisterService(serviceInfo, this, getter_AddRefs(cancelRegister));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // All done.
+ aServer->SetCancelRegister(cancelRegister);
+
+ return NS_OK;
+}
+
+void
+FlyWebMDNSService::EnsureDiscoveryStarted()
+{
+ mDiscoveryActive = true;
+ // If state is idle, start discovery immediately.
+ if (mDiscoveryState == DISCOVERY_IDLE) {
+ StartDiscovery();
+ }
+}
+
+void
+FlyWebMDNSService::EnsureDiscoveryStopped()
+{
+ // All we need to do is set the flag to false.
+ // If current state is IDLE, it's already the correct state.
+ // Otherwise, the handlers for the internal state
+ // transitions will check this flag and drive the state
+ // towards IDLE.
+ mDiscoveryActive = false;
+}
+
+static StaticRefPtr<FlyWebService> gFlyWebService;
+
+NS_IMPL_ISUPPORTS(FlyWebService, nsIObserver)
+
+FlyWebService::FlyWebService()
+ : mMonitor("FlyWebService::mMonitor")
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "inner-window-destroyed", false);
+ }
+}
+
+FlyWebService::~FlyWebService()
+{
+}
+
+FlyWebService*
+FlyWebService::GetExisting()
+{
+ return gFlyWebService;
+}
+
+FlyWebService*
+FlyWebService::GetOrCreate()
+{
+ if (!gFlyWebService) {
+ gFlyWebService = new FlyWebService();
+ ClearOnShutdown(&gFlyWebService);
+ ErrorResult rv = gFlyWebService->Init();
+ if (rv.Failed()) {
+ gFlyWebService = nullptr;
+ return nullptr;
+ }
+ }
+ return gFlyWebService;
+}
+
+ErrorResult
+FlyWebService::Init()
+{
+ // Most functions of FlyWebService should not be started in the child.
+ // Instead FlyWebService in the child is mainly responsible for tracking
+ // publishedServer lifetimes. Other functions are handled by the
+ // FlyWebService running in the parent.
+ if (XRE_GetProcessType() == GeckoProcessType_Content) {
+ return ErrorResult(NS_OK);
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mMDNSHttpService) {
+ mMDNSHttpService = new FlyWebMDNSService(this, NS_LITERAL_CSTRING("_http._tcp."));
+ ErrorResult rv;
+
+ rv = mMDNSHttpService->Init();
+ if (rv.Failed()) {
+ LOG_E("FlyWebService failed to initialize MDNS _http._tcp.");
+ mMDNSHttpService = nullptr;
+ rv.SuppressException();
+ }
+ }
+
+ if (!mMDNSFlywebService) {
+ mMDNSFlywebService = new FlyWebMDNSService(this, NS_LITERAL_CSTRING("_flyweb._tcp."));
+ ErrorResult rv;
+
+ rv = mMDNSFlywebService->Init();
+ if (rv.Failed()) {
+ LOG_E("FlyWebService failed to initialize MDNS _flyweb._tcp.");
+ mMDNSFlywebService = nullptr;
+ rv.SuppressException();
+ }
+ }
+
+ return ErrorResult(NS_OK);
+}
+
+static already_AddRefed<FlyWebPublishPromise>
+MakeRejectionPromise(const char* name)
+{
+ MozPromiseHolder<FlyWebPublishPromise> holder;
+ RefPtr<FlyWebPublishPromise> promise = holder.Ensure(name);
+ holder.Reject(NS_ERROR_FAILURE, name);
+ return promise.forget();
+}
+
+static bool
+CheckForFlyWebAddon(const nsACString& uriString)
+{
+ // Before proceeding, ensure that the FlyWeb system addon exists.
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriString);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ JSAddonId *addonId = MapURIToAddonID(uri);
+ if (!addonId) {
+ return false;
+ }
+
+ JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(addonId));
+ nsAutoString addonIdString;
+ AssignJSFlatString(addonIdString, flat);
+ if (!addonIdString.EqualsLiteral("flyweb@mozilla.org")) {
+ nsCString addonIdCString = NS_ConvertUTF16toUTF8(addonIdString);
+ return false;
+ }
+
+ return true;
+}
+
+already_AddRefed<FlyWebPublishPromise>
+FlyWebService::PublishServer(const nsAString& aName,
+ const FlyWebPublishOptions& aOptions,
+ nsPIDOMWindowInner* aWindow)
+{
+ // Scan uiUrl for illegal characters
+
+ RefPtr<FlyWebPublishedServer> existingServer =
+ FlyWebService::GetOrCreate()->FindPublishedServerByName(aName);
+ if (existingServer) {
+ LOG_I("PublishServer: Trying to publish server with already-existing name %s.",
+ NS_ConvertUTF16toUTF8(aName).get());
+ return MakeRejectionPromise(__func__);
+ }
+
+ RefPtr<FlyWebPublishedServer> server;
+ if (XRE_GetProcessType() == GeckoProcessType_Content) {
+ server = new FlyWebPublishedServerChild(aWindow, aName, aOptions);
+ } else {
+ server = new FlyWebPublishedServerImpl(aWindow, aName, aOptions);
+
+ // Before proceeding, ensure that the FlyWeb system addon exists.
+ if (!CheckForFlyWebAddon(NS_LITERAL_CSTRING("chrome://flyweb/skin/icon-64.png")) &&
+ !CheckForFlyWebAddon(NS_LITERAL_CSTRING("chrome://flyweb/content/icon-64.png")))
+ {
+ LOG_E("PublishServer: Failed to find FlyWeb system addon.");
+ return MakeRejectionPromise(__func__);
+ }
+ }
+
+ if (aWindow) {
+ nsresult rv;
+
+ MOZ_ASSERT(NS_IsMainThread());
+ rv = NS_DispatchToCurrentThread(
+ MakeAndAddRef<FlyWebPublishServerPermissionCheck>(
+ NS_ConvertUTF16toUTF8(aName), aWindow->WindowID(), server));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG_E("PublishServer: Failed to dispatch permission check runnable for %s",
+ NS_ConvertUTF16toUTF8(aName).get());
+ return MakeRejectionPromise(__func__);
+ }
+ } else {
+ // If aWindow is null, we're definitely in the e10s parent process.
+ // In this case, we know that permission has already been granted
+ // by the user because of content-process prompt.
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ server->PermissionGranted(true);
+ }
+
+ mServers.AppendElement(server);
+
+ return server->GetPublishPromise();
+}
+
+already_AddRefed<FlyWebPublishedServer>
+FlyWebService::FindPublishedServerByName(
+ const nsAString& aName)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ for (FlyWebPublishedServer* publishedServer : mServers) {
+ if (publishedServer->Name().Equals(aName)) {
+ RefPtr<FlyWebPublishedServer> server = publishedServer;
+ return server.forget();
+ }
+ }
+ return nullptr;
+}
+
+void
+FlyWebService::RegisterDiscoveryManager(FlyWebDiscoveryManager* aDiscoveryManager)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mDiscoveryManagerTable.PutEntry(aDiscoveryManager);
+ if (mMDNSHttpService) {
+ mMDNSHttpService->EnsureDiscoveryStarted();
+ }
+ if (mMDNSFlywebService) {
+ mMDNSFlywebService->EnsureDiscoveryStarted();
+ }
+}
+
+void
+FlyWebService::UnregisterDiscoveryManager(FlyWebDiscoveryManager* aDiscoveryManager)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mDiscoveryManagerTable.RemoveEntry(aDiscoveryManager);
+ if (mDiscoveryManagerTable.IsEmpty()) {
+ if (mMDNSHttpService) {
+ mMDNSHttpService->EnsureDiscoveryStopped();
+ }
+ if (mMDNSFlywebService) {
+ mMDNSFlywebService->EnsureDiscoveryStopped();
+ }
+ }
+}
+
+NS_IMETHODIMP
+FlyWebService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (strcmp(aTopic, "inner-window-destroyed")) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+ NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+
+ uint64_t innerID;
+ nsresult rv = wrapper->GetData(&innerID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (FlyWebPublishedServer* server : mServers) {
+ if (server->OwnerWindowID() == innerID) {
+ server->Close();
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+FlyWebService::UnregisterServer(FlyWebPublishedServer* aServer)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ DebugOnly<bool> removed = mServers.RemoveElement(aServer);
+ MOZ_ASSERT(removed);
+}
+
+bool
+FlyWebService::HasConnectionOrServer(uint64_t aWindowID)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ for (FlyWebPublishedServer* server : mServers) {
+ nsPIDOMWindowInner* win = server->GetOwner();
+ if (win && win->WindowID() == aWindowID) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+FlyWebService::NotifyDiscoveredServicesChanged()
+{
+ // Process the service map, add to the pair map.
+ for (auto iter = mDiscoveryManagerTable.Iter(); !iter.Done(); iter.Next()) {
+ iter.Get()->GetKey()->NotifyDiscoveredServicesChanged();
+ }
+}
+
+void
+FlyWebService::ListDiscoveredServices(nsTArray<FlyWebDiscoveredService>& aServices)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mMDNSHttpService) {
+ mMDNSHttpService->ListDiscoveredServices(aServices);
+ }
+ if (mMDNSFlywebService) {
+ mMDNSFlywebService->ListDiscoveredServices(aServices);
+ }
+}
+
+void
+FlyWebService::PairWithService(const nsAString& aServiceId,
+ FlyWebPairingCallback& aCallback)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // See if we have already paired with this service. If so, re-use the
+ // FlyWebPairedService for that.
+ {
+ ReentrantMonitorAutoEnter pairedMapLock(mMonitor);
+ for (auto iter = mPairedServiceTable.Iter(); !iter.Done(); iter.Next()) {
+ PairedInfo* pairInfo = iter.UserData();
+ if (pairInfo->mService.mDiscoveredService.mServiceId.Equals(aServiceId)) {
+ ErrorResult er;
+ ReentrantMonitorAutoExit pairedMapRelease(mMonitor);
+ aCallback.PairingSucceeded(pairInfo->mService, er);
+ ENSURE_SUCCESS_VOID(er);
+ return;
+ }
+ }
+ }
+
+ UniquePtr<PairedInfo> pairInfo;
+
+ nsresult rv = NS_OK;
+ bool notFound = false;
+ if (mMDNSHttpService && mMDNSHttpService->HasService(aServiceId)) {
+ rv = mMDNSHttpService->PairWithService(aServiceId, pairInfo);
+ } else if (mMDNSFlywebService && mMDNSFlywebService->HasService(aServiceId)) {
+ rv = mMDNSFlywebService->PairWithService(aServiceId, pairInfo);
+ } else {
+ notFound = true;
+ }
+
+ if (NS_FAILED(rv)) {
+ ErrorResult result;
+ result.Throw(rv);
+ const nsAString& reason = NS_LITERAL_STRING("Error pairing.");
+ aCallback.PairingFailed(reason, result);
+ ENSURE_SUCCESS_VOID(result);
+ return;
+ }
+
+ if (!pairInfo) {
+ ErrorResult res;
+ const nsAString& reason = notFound ?
+ NS_LITERAL_STRING("No such service.") :
+ NS_LITERAL_STRING("Error pairing.");
+ aCallback.PairingFailed(reason, res);
+ ENSURE_SUCCESS_VOID(res);
+ return;
+ }
+
+ // Add fingerprint to certificate override database.
+ if (!pairInfo->mService.mDiscoveredService.mCert.IsEmpty()) {
+ nsCOMPtr<nsICertOverrideService> override =
+ do_GetService("@mozilla.org/security/certoverride;1");
+ if (!override ||
+ NS_FAILED(override->RememberTemporaryValidityOverrideUsingFingerprint(
+ NS_ConvertUTF16toUTF8(pairInfo->mService.mHostname),
+ -1,
+ NS_ConvertUTF16toUTF8(pairInfo->mService.mDiscoveredService.mCert),
+ nsICertOverrideService::ERROR_UNTRUSTED |
+ nsICertOverrideService::ERROR_MISMATCH))) {
+ ErrorResult res;
+ aCallback.PairingFailed(NS_LITERAL_STRING("Error adding certificate override."), res);
+ ENSURE_SUCCESS_VOID(res);
+ return;
+ }
+ }
+
+ // Grab a weak reference to the PairedInfo so that we can
+ // use it even after ownership has been transferred to mPairedServiceTable
+ PairedInfo* pairInfoWeak = pairInfo.release();
+
+ {
+ ReentrantMonitorAutoEnter pairedMapLock(mMonitor);
+ mPairedServiceTable.Put(
+ NS_ConvertUTF16toUTF8(pairInfoWeak->mService.mHostname), pairInfoWeak);
+ }
+
+ ErrorResult er;
+ aCallback.PairingSucceeded(pairInfoWeak->mService, er);
+ ENSURE_SUCCESS_VOID(er);
+}
+
+nsresult
+FlyWebService::CreateTransportForHost(const char **types,
+ uint32_t typeCount,
+ const nsACString &host,
+ int32_t port,
+ const nsACString &hostRoute,
+ int32_t portRoute,
+ nsIProxyInfo *proxyInfo,
+ nsISocketTransport **result)
+{
+ // This might be called on background threads
+
+ *result = nullptr;
+
+ nsCString ipAddrString;
+ uint16_t discPort;
+
+ {
+ ReentrantMonitorAutoEnter pairedMapLock(mMonitor);
+
+ PairedInfo* info = mPairedServiceTable.Get(host);
+
+ if (!info) {
+ return NS_OK;
+ }
+
+ // Get the ip address of the underlying service.
+ info->mDNSServiceInfo->GetAddress(ipAddrString);
+ info->mDNSServiceInfo->GetPort(&discPort);
+ }
+
+ // Parse it into an NetAddr.
+ PRNetAddr prNetAddr;
+ PRStatus status = PR_StringToNetAddr(ipAddrString.get(), &prNetAddr);
+ NS_ENSURE_FALSE(status == PR_FAILURE, NS_ERROR_FAILURE);
+
+ // Convert PRNetAddr to NetAddr.
+ mozilla::net::NetAddr netAddr;
+ PRNetAddrToNetAddr(&prNetAddr, &netAddr);
+ netAddr.inet.port = htons(discPort);
+
+ RefPtr<mozilla::net::nsSocketTransport> trans = new mozilla::net::nsSocketTransport();
+ nsresult rv = trans->InitPreResolved(
+ types, typeCount, host, port, hostRoute, portRoute, proxyInfo, &netAddr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ trans.forget(result);
+ return NS_OK;
+}
+
+void
+FlyWebService::StartDiscoveryOf(FlyWebPublishedServerImpl* aServer)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv = mMDNSFlywebService ?
+ mMDNSFlywebService->StartDiscoveryOf(aServer) :
+ NS_ERROR_FAILURE;
+
+ if (NS_FAILED(rv)) {
+ aServer->PublishedServerStarted(rv);
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/flyweb/FlyWebService.h b/dom/flyweb/FlyWebService.h
new file mode 100644
index 000000000..f7b983440
--- /dev/null
+++ b/dom/flyweb/FlyWebService.h
@@ -0,0 +1,113 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_FlyWebService_h
+#define mozilla_dom_FlyWebService_h
+
+#include "nsISupportsImpl.h"
+#include "mozilla/ErrorResult.h"
+#include "nsIProtocolHandler.h"
+#include "nsDataHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsIObserver.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/dom/FlyWebDiscoveryManagerBinding.h"
+#include "nsITimer.h"
+#include "nsICancelable.h"
+#include "nsIDNSServiceDiscovery.h"
+
+class nsPIDOMWindowInner;
+class nsIProxyInfo;
+class nsISocketTransport;
+
+namespace mozilla {
+namespace dom {
+
+struct FlyWebPublishOptions;
+struct FlyWebFilter;
+class FlyWebPublishedServer;
+class FlyWebPublishedServerImpl;
+class FlyWebPairingCallback;
+class FlyWebDiscoveryManager;
+class FlyWebMDNSService;
+
+typedef MozPromise<RefPtr<FlyWebPublishedServer>, nsresult, false>
+ FlyWebPublishPromise;
+
+class FlyWebService final : public nsIObserver
+{
+ friend class FlyWebMDNSService;
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ static FlyWebService* GetExisting();
+ static FlyWebService* GetOrCreate();
+ static already_AddRefed<FlyWebService> GetOrCreateAddRefed()
+ {
+ return do_AddRef(GetOrCreate());
+ }
+
+ already_AddRefed<FlyWebPublishPromise>
+ PublishServer(const nsAString& aName,
+ const FlyWebPublishOptions& aOptions,
+ nsPIDOMWindowInner* aWindow);
+
+ void UnregisterServer(FlyWebPublishedServer* aServer);
+
+ bool HasConnectionOrServer(uint64_t aWindowID);
+
+ void ListDiscoveredServices(nsTArray<FlyWebDiscoveredService>& aServices);
+ void PairWithService(const nsAString& aServiceId, FlyWebPairingCallback& aCallback);
+ nsresult CreateTransportForHost(const char **types,
+ uint32_t typeCount,
+ const nsACString &host,
+ int32_t port,
+ const nsACString &hostRoute,
+ int32_t portRoute,
+ nsIProxyInfo *proxyInfo,
+ nsISocketTransport **result);
+
+ already_AddRefed<FlyWebPublishedServer> FindPublishedServerByName(
+ const nsAString& aName);
+
+ void RegisterDiscoveryManager(FlyWebDiscoveryManager* aDiscoveryManager);
+ void UnregisterDiscoveryManager(FlyWebDiscoveryManager* aDiscoveryManager);
+
+ // Should only be called by FlyWebPublishedServerImpl
+ void StartDiscoveryOf(FlyWebPublishedServerImpl* aServer);
+
+private:
+ FlyWebService();
+ ~FlyWebService();
+
+ ErrorResult Init();
+
+ void NotifyDiscoveredServicesChanged();
+
+ // Might want to make these hashes for perf
+ nsTArray<FlyWebPublishedServer*> mServers;
+
+ RefPtr<FlyWebMDNSService> mMDNSHttpService;
+ RefPtr<FlyWebMDNSService> mMDNSFlywebService;
+
+ struct PairedInfo
+ {
+ FlyWebPairedService mService;
+ nsCOMPtr<nsIDNSServiceInfo> mDNSServiceInfo;
+ };
+ nsClassHashtable<nsCStringHashKey, PairedInfo>
+ mPairedServiceTable;
+ ReentrantMonitor mMonitor; // Protecting mPairedServiceTable
+
+ nsTHashtable<nsPtrHashKey<FlyWebDiscoveryManager>> mDiscoveryManagerTable;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FlyWebService_h
diff --git a/dom/flyweb/HttpServer.cpp b/dom/flyweb/HttpServer.cpp
new file mode 100644
index 000000000..26e15d9d5
--- /dev/null
+++ b/dom/flyweb/HttpServer.cpp
@@ -0,0 +1,1319 @@
+/* -*- 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 "mozilla/dom/HttpServer.h"
+#include "nsISocketTransport.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsNetUtil.h"
+#include "nsIStreamTransportService.h"
+#include "nsIAsyncStreamCopier2.h"
+#include "nsIPipe.h"
+#include "nsIOService.h"
+#include "nsIHttpChannelInternal.h"
+#include "Base64.h"
+#include "WebSocketChannel.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsIX509Cert.h"
+
+static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
+
+namespace mozilla {
+namespace dom {
+
+static LazyLogModule gHttpServerLog("HttpServer");
+#undef LOG_I
+#define LOG_I(...) MOZ_LOG(gHttpServerLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#undef LOG_V
+#define LOG_V(...) MOZ_LOG(gHttpServerLog, mozilla::LogLevel::Verbose, (__VA_ARGS__))
+#undef LOG_E
+#define LOG_E(...) MOZ_LOG(gHttpServerLog, mozilla::LogLevel::Error, (__VA_ARGS__))
+
+
+NS_IMPL_ISUPPORTS(HttpServer,
+ nsIServerSocketListener,
+ nsILocalCertGetCallback)
+
+HttpServer::HttpServer()
+ : mPort()
+ , mHttps()
+{
+}
+
+HttpServer::~HttpServer()
+{
+}
+
+void
+HttpServer::Init(int32_t aPort, bool aHttps, HttpServerListener* aListener)
+{
+ mPort = aPort;
+ mHttps = aHttps;
+ mListener = aListener;
+
+ if (mHttps) {
+ nsCOMPtr<nsILocalCertService> lcs =
+ do_CreateInstance("@mozilla.org/security/local-cert-service;1");
+ nsresult rv = lcs->GetOrCreateCert(NS_LITERAL_CSTRING("flyweb"), this);
+ if (NS_FAILED(rv)) {
+ NotifyStarted(rv);
+ }
+ } else {
+ // Make sure to always have an async step before notifying callbacks
+ HandleCert(nullptr, NS_OK);
+ }
+}
+
+NS_IMETHODIMP
+HttpServer::HandleCert(nsIX509Cert* aCert, nsresult aResult)
+{
+ nsresult rv = aResult;
+ if (NS_SUCCEEDED(rv)) {
+ rv = StartServerSocket(aCert);
+ }
+
+ if (NS_FAILED(rv) && mServerSocket) {
+ mServerSocket->Close();
+ mServerSocket = nullptr;
+ }
+
+ NotifyStarted(rv);
+
+ return NS_OK;
+}
+
+void
+HttpServer::NotifyStarted(nsresult aStatus)
+{
+ RefPtr<HttpServerListener> listener = mListener;
+ nsCOMPtr<nsIRunnable> event = NS_NewRunnableFunction([listener, aStatus] ()
+ {
+ listener->OnServerStarted(aStatus);
+ });
+ NS_DispatchToCurrentThread(event);
+}
+
+nsresult
+HttpServer::StartServerSocket(nsIX509Cert* aCert)
+{
+ nsresult rv;
+ mServerSocket =
+ do_CreateInstance(aCert ? "@mozilla.org/network/tls-server-socket;1"
+ : "@mozilla.org/network/server-socket;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mServerSocket->Init(mPort, false, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aCert) {
+ nsCOMPtr<nsITLSServerSocket> tls = do_QueryInterface(mServerSocket);
+ rv = tls->SetServerCert(aCert);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = tls->SetSessionTickets(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCert = aCert;
+ }
+
+ rv = mServerSocket->AsyncListen(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mServerSocket->GetPort(&mPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG_I("HttpServer::StartServerSocket(%p)", this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpServer::OnSocketAccepted(nsIServerSocket* aServ,
+ nsISocketTransport* aTransport)
+{
+ MOZ_ASSERT(SameCOMIdentity(aServ, mServerSocket));
+
+ nsresult rv;
+ RefPtr<Connection> conn = new Connection(aTransport, this, rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG_I("HttpServer::OnSocketAccepted(%p) - Socket %p", this, conn.get());
+
+ mConnections.AppendElement(conn.forget());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpServer::OnStopListening(nsIServerSocket* aServ,
+ nsresult aStatus)
+{
+ MOZ_ASSERT(aServ == mServerSocket || !mServerSocket);
+
+ LOG_I("HttpServer::OnStopListening(%p) - status 0x%lx", this, aStatus);
+
+ Close();
+
+ return NS_OK;
+}
+
+void
+HttpServer::SendResponse(InternalRequest* aRequest, InternalResponse* aResponse)
+{
+ for (Connection* conn : mConnections) {
+ if (conn->TryHandleResponse(aRequest, aResponse)) {
+ return;
+ }
+ }
+
+ MOZ_ASSERT(false, "Unknown request");
+}
+
+already_AddRefed<nsITransportProvider>
+HttpServer::AcceptWebSocket(InternalRequest* aConnectRequest,
+ const Optional<nsAString>& aProtocol,
+ ErrorResult& aRv)
+{
+ for (Connection* conn : mConnections) {
+ if (!conn->HasPendingWebSocketRequest(aConnectRequest)) {
+ continue;
+ }
+ nsCOMPtr<nsITransportProvider> provider =
+ conn->HandleAcceptWebSocket(aProtocol, aRv);
+ if (aRv.Failed()) {
+ conn->Close();
+ }
+ // This connection is now owned by the websocket, or we just closed it
+ mConnections.RemoveElement(conn);
+ return provider.forget();
+ }
+
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ MOZ_ASSERT(false, "Unknown request");
+
+ return nullptr;
+}
+
+void
+HttpServer::SendWebSocketResponse(InternalRequest* aConnectRequest,
+ InternalResponse* aResponse)
+{
+ for (Connection* conn : mConnections) {
+ if (conn->HasPendingWebSocketRequest(aConnectRequest)) {
+ conn->HandleWebSocketResponse(aResponse);
+ return;
+ }
+ }
+
+ MOZ_ASSERT(false, "Unknown request");
+}
+
+void
+HttpServer::Close()
+{
+ if (mServerSocket) {
+ mServerSocket->Close();
+ mServerSocket = nullptr;
+ }
+
+ if (mListener) {
+ RefPtr<HttpServerListener> listener = mListener.forget();
+ listener->OnServerClose();
+ }
+
+ for (Connection* conn : mConnections) {
+ conn->Close();
+ }
+ mConnections.Clear();
+}
+
+void
+HttpServer::GetCertKey(nsACString& aKey)
+{
+ nsAutoString tmp;
+ if (mCert) {
+ mCert->GetSha256Fingerprint(tmp);
+ }
+ LossyCopyUTF16toASCII(tmp, aKey);
+}
+
+NS_IMPL_ISUPPORTS(HttpServer::TransportProvider,
+ nsITransportProvider)
+
+HttpServer::TransportProvider::~TransportProvider()
+{
+}
+
+NS_IMETHODIMP
+HttpServer::TransportProvider::SetListener(nsIHttpUpgradeListener* aListener)
+{
+ MOZ_ASSERT(!mListener);
+ MOZ_ASSERT(aListener);
+
+ mListener = aListener;
+
+ MaybeNotify();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpServer::TransportProvider::GetIPCChild(PTransportProviderChild** aChild)
+{
+ MOZ_CRASH("Don't call this in parent process");
+ *aChild = nullptr;
+ return NS_OK;
+}
+
+void
+HttpServer::TransportProvider::SetTransport(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aInput,
+ nsIAsyncOutputStream* aOutput)
+{
+ MOZ_ASSERT(!mTransport);
+ MOZ_ASSERT(aTransport && aInput && aOutput);
+
+ mTransport = aTransport;
+ mInput = aInput;
+ mOutput = aOutput;
+
+ MaybeNotify();
+}
+
+void
+HttpServer::TransportProvider::MaybeNotify()
+{
+ if (mTransport && mListener) {
+ RefPtr<TransportProvider> self = this;
+ nsCOMPtr<nsIRunnable> event = NS_NewRunnableFunction([self, this] ()
+ {
+ mListener->OnTransportAvailable(mTransport, mInput, mOutput);
+ });
+ NS_DispatchToCurrentThread(event);
+ }
+}
+
+NS_IMPL_ISUPPORTS(HttpServer::Connection,
+ nsIInputStreamCallback,
+ nsIOutputStreamCallback)
+
+HttpServer::Connection::Connection(nsISocketTransport* aTransport,
+ HttpServer* aServer,
+ nsresult& rv)
+ : mServer(aServer)
+ , mTransport(aTransport)
+ , mState(eRequestLine)
+ , mPendingReqVersion()
+ , mRemainingBodySize()
+ , mCloseAfterRequest(false)
+{
+ nsCOMPtr<nsIInputStream> input;
+ rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(input));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ mInput = do_QueryInterface(input);
+
+ nsCOMPtr<nsIOutputStream> output;
+ rv = mTransport->OpenOutputStream(0, 0, 0, getter_AddRefs(output));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ mOutput = do_QueryInterface(output);
+
+ if (mServer->mHttps) {
+ SetSecurityObserver(true);
+ } else {
+ mInput->AsyncWait(this, 0, 0, NS_GetCurrentThread());
+ }
+}
+
+NS_IMETHODIMP
+HttpServer::Connection::OnHandshakeDone(nsITLSServerSocket* aServer,
+ nsITLSClientStatus* aStatus)
+{
+ LOG_I("HttpServer::Connection::OnHandshakeDone(%p)", this);
+
+ // XXX Verify connection security
+
+ SetSecurityObserver(false);
+ mInput->AsyncWait(this, 0, 0, NS_GetCurrentThread());
+
+ return NS_OK;
+}
+
+void
+HttpServer::Connection::SetSecurityObserver(bool aListen)
+{
+ LOG_I("HttpServer::Connection::SetSecurityObserver(%p) - %s", this,
+ aListen ? "On" : "Off");
+
+ nsCOMPtr<nsISupports> secInfo;
+ mTransport->GetSecurityInfo(getter_AddRefs(secInfo));
+ nsCOMPtr<nsITLSServerConnectionInfo> tlsConnInfo =
+ do_QueryInterface(secInfo);
+ MOZ_ASSERT(tlsConnInfo);
+ tlsConnInfo->SetSecurityObserver(aListen ? this : nullptr);
+}
+
+HttpServer::Connection::~Connection()
+{
+}
+
+NS_IMETHODIMP
+HttpServer::Connection::OnInputStreamReady(nsIAsyncInputStream* aStream)
+{
+ MOZ_ASSERT(!mInput || aStream == mInput);
+
+ LOG_I("HttpServer::Connection::OnInputStreamReady(%p)", this);
+
+ if (!mInput || mState == ePause) {
+ return NS_OK;
+ }
+
+ uint64_t avail;
+ nsresult rv = mInput->Available(&avail);
+ if (NS_FAILED(rv)) {
+ LOG_I("HttpServer::Connection::OnInputStreamReady(%p) - Connection closed", this);
+
+ mServer->mConnections.RemoveElement(this);
+ // Connection closed. Handle errors here.
+ return NS_OK;
+ }
+
+ uint32_t numRead;
+ rv = mInput->ReadSegments(ReadSegmentsFunc,
+ this,
+ UINT32_MAX,
+ &numRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mInput->AsyncWait(this, 0, 0, NS_GetCurrentThread());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+HttpServer::Connection::ReadSegmentsFunc(nsIInputStream* aIn,
+ void* aClosure,
+ const char* aBuffer,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ const char* buffer = aBuffer;
+ nsresult rv = static_cast<HttpServer::Connection*>(aClosure)->
+ ConsumeInput(buffer, buffer + aCount);
+
+ *aWriteCount = buffer - aBuffer;
+ MOZ_ASSERT(*aWriteCount <= aCount);
+
+ return rv;
+}
+
+static const char*
+findCRLF(const char* aBuffer, const char* aEnd)
+{
+ if (aBuffer + 1 >= aEnd) {
+ return nullptr;
+ }
+
+ const char* pos;
+ while ((pos = static_cast<const char*>(memchr(aBuffer,
+ '\r',
+ aEnd - aBuffer - 1)))) {
+ if (*(pos + 1) == '\n') {
+ return pos;
+ }
+ aBuffer = pos + 1;
+ }
+ return nullptr;
+}
+
+nsresult
+HttpServer::Connection::ConsumeInput(const char*& aBuffer,
+ const char* aEnd)
+{
+ nsresult rv;
+ while (mState == eRequestLine ||
+ mState == eHeaders) {
+ // Consume line-by-line
+
+ // Check if buffer boundry ended up right between the CR and LF
+ if (!mInputBuffer.IsEmpty() && mInputBuffer.Last() == '\r' &&
+ *aBuffer == '\n') {
+ aBuffer++;
+ rv = ConsumeLine(mInputBuffer.BeginReading(), mInputBuffer.Length() - 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInputBuffer.Truncate();
+ }
+
+ // Look for a CRLF
+ const char* pos = findCRLF(aBuffer, aEnd);
+ if (!pos) {
+ mInputBuffer.Append(aBuffer, aEnd - aBuffer);
+ aBuffer = aEnd;
+ return NS_OK;
+ }
+
+ if (!mInputBuffer.IsEmpty()) {
+ mInputBuffer.Append(aBuffer, pos - aBuffer);
+ aBuffer = pos + 2;
+ rv = ConsumeLine(mInputBuffer.BeginReading(), mInputBuffer.Length() - 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInputBuffer.Truncate();
+ } else {
+ rv = ConsumeLine(aBuffer, pos - aBuffer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aBuffer = pos + 2;
+ }
+ }
+
+ if (mState == eBody) {
+ uint32_t size = std::min(mRemainingBodySize,
+ static_cast<uint32_t>(aEnd - aBuffer));
+ uint32_t written = size;
+
+ if (mCurrentRequestBody) {
+ rv = mCurrentRequestBody->Write(aBuffer, size, &written);
+ // Since we've given the pipe unlimited size, we should never
+ // end up needing to block.
+ MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK);
+ if (NS_FAILED(rv)) {
+ written = size;
+ mCurrentRequestBody = nullptr;
+ }
+ }
+
+ aBuffer += written;
+ mRemainingBodySize -= written;
+ if (!mRemainingBodySize) {
+ mCurrentRequestBody->Close();
+ mCurrentRequestBody = nullptr;
+ mState = eRequestLine;
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+ContainsToken(const nsCString& aList, const nsCString& aToken)
+{
+ nsCCharSeparatedTokenizer tokens(aList, ',');
+ bool found = false;
+ while (!found && tokens.hasMoreTokens()) {
+ found = tokens.nextToken().Equals(aToken);
+ }
+ return found;
+}
+
+static bool
+IsWebSocketRequest(InternalRequest* aRequest, uint32_t aHttpVersion)
+{
+ if (aHttpVersion < 1) {
+ return false;
+ }
+
+ nsAutoCString str;
+ aRequest->GetMethod(str);
+ if (!str.EqualsLiteral("GET")) {
+ return false;
+ }
+
+ InternalHeaders* headers = aRequest->Headers();
+ ErrorResult res;
+
+ headers->GetFirst(NS_LITERAL_CSTRING("upgrade"), str, res);
+ MOZ_ASSERT(!res.Failed());
+ if (!str.EqualsLiteral("websocket")) {
+ return false;
+ }
+
+ headers->GetFirst(NS_LITERAL_CSTRING("connection"), str, res);
+ MOZ_ASSERT(!res.Failed());
+ if (!ContainsToken(str, NS_LITERAL_CSTRING("Upgrade"))) {
+ return false;
+ }
+
+ headers->GetFirst(NS_LITERAL_CSTRING("sec-websocket-key"), str, res);
+ MOZ_ASSERT(!res.Failed());
+ nsAutoCString binary;
+ if (NS_FAILED(Base64Decode(str, binary)) || binary.Length() != 16) {
+ return false;
+ }
+
+ nsresult rv;
+ headers->GetFirst(NS_LITERAL_CSTRING("sec-websocket-version"), str, res);
+ MOZ_ASSERT(!res.Failed());
+ if (str.ToInteger(&rv) != 13 || NS_FAILED(rv)) {
+ return false;
+ }
+
+ return true;
+}
+
+nsresult
+HttpServer::Connection::ConsumeLine(const char* aBuffer,
+ size_t aLength)
+{
+ MOZ_ASSERT(mState == eRequestLine ||
+ mState == eHeaders);
+
+ if (MOZ_LOG_TEST(gHttpServerLog, mozilla::LogLevel::Verbose)) {
+ nsCString line(aBuffer, aLength);
+ LOG_V("HttpServer::Connection::ConsumeLine(%p) - \"%s\"", this, line.get());
+ }
+
+ if (mState == eRequestLine) {
+ LOG_V("HttpServer::Connection::ConsumeLine(%p) - Parsing request line", this);
+ NS_ENSURE_FALSE(mCloseAfterRequest, NS_ERROR_UNEXPECTED);
+
+ if (aLength == 0) {
+ // Ignore empty lines before the request line
+ return NS_OK;
+ }
+ MOZ_ASSERT(!mPendingReq);
+
+ // Process request line
+ nsCWhitespaceTokenizer tokens(Substring(aBuffer, aLength));
+
+ NS_ENSURE_TRUE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
+ nsDependentCSubstring method = tokens.nextToken();
+ NS_ENSURE_TRUE(NS_IsValidHTTPToken(method), NS_ERROR_UNEXPECTED);
+ NS_ENSURE_TRUE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
+ nsDependentCSubstring url = tokens.nextToken();
+ // Seems like it's also allowed to pass full urls with scheme+host+port.
+ // May need to support that.
+ NS_ENSURE_TRUE(url.First() == '/', NS_ERROR_UNEXPECTED);
+ mPendingReq = new InternalRequest(url, /* aURLFragment */ EmptyCString());
+ mPendingReq->SetMethod(method);
+ NS_ENSURE_TRUE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
+ nsDependentCSubstring version = tokens.nextToken();
+ NS_ENSURE_TRUE(StringBeginsWith(version, NS_LITERAL_CSTRING("HTTP/1.")),
+ NS_ERROR_UNEXPECTED);
+ nsresult rv;
+ // This integer parsing is likely not strict enough.
+ nsCString reqVersion;
+ reqVersion = Substring(version, MOZ_ARRAY_LENGTH("HTTP/1.") - 1);
+ mPendingReqVersion = reqVersion.ToInteger(&rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+
+ NS_ENSURE_FALSE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
+
+ LOG_V("HttpServer::Connection::ConsumeLine(%p) - Parsed request line", this);
+
+ mState = eHeaders;
+
+ return NS_OK;
+ }
+
+ if (aLength == 0) {
+ LOG_V("HttpServer::Connection::ConsumeLine(%p) - Found end of headers", this);
+
+ MaybeAddPendingHeader();
+
+ ErrorResult res;
+ mPendingReq->Headers()->SetGuard(HeadersGuardEnum::Immutable, res);
+
+ // Check for WebSocket
+ if (IsWebSocketRequest(mPendingReq, mPendingReqVersion)) {
+ LOG_V("HttpServer::Connection::ConsumeLine(%p) - Fire OnWebSocket", this);
+
+ mState = ePause;
+ mPendingWebSocketRequest = mPendingReq.forget();
+ mPendingReqVersion = 0;
+
+ RefPtr<HttpServerListener> listener = mServer->mListener;
+ RefPtr<InternalRequest> request = mPendingWebSocketRequest;
+ nsCOMPtr<nsIRunnable> event =
+ NS_NewRunnableFunction([listener, request] ()
+ {
+ listener->OnWebSocket(request);
+ });
+ NS_DispatchToCurrentThread(event);
+
+ return NS_OK;
+ }
+
+ nsAutoCString header;
+ mPendingReq->Headers()->GetFirst(NS_LITERAL_CSTRING("connection"),
+ header,
+ res);
+ MOZ_ASSERT(!res.Failed());
+ // 1.0 defaults to closing connections.
+ // 1.1 and higher defaults to keep-alive.
+ if (ContainsToken(header, NS_LITERAL_CSTRING("close")) ||
+ (mPendingReqVersion == 0 &&
+ !ContainsToken(header, NS_LITERAL_CSTRING("keep-alive")))) {
+ mCloseAfterRequest = true;
+ }
+
+ mPendingReq->Headers()->GetFirst(NS_LITERAL_CSTRING("content-length"),
+ header,
+ res);
+ MOZ_ASSERT(!res.Failed());
+
+ LOG_V("HttpServer::Connection::ConsumeLine(%p) - content-length is \"%s\"",
+ this, header.get());
+
+ if (!header.IsEmpty()) {
+ nsresult rv;
+ mRemainingBodySize = header.ToInteger(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mRemainingBodySize = 0;
+ }
+
+ if (mRemainingBodySize) {
+ LOG_V("HttpServer::Connection::ConsumeLine(%p) - Starting consume body", this);
+ mState = eBody;
+
+ // We use an unlimited buffer size here to ensure
+ // that we get to the next request even if the webpage hangs on
+ // to the request indefinitely without consuming the body.
+ nsCOMPtr<nsIInputStream> input;
+ nsCOMPtr<nsIOutputStream> output;
+ nsresult rv = NS_NewPipe(getter_AddRefs(input),
+ getter_AddRefs(output),
+ 0, // Segment size
+ UINT32_MAX, // Unlimited buffer size
+ false, // not nonBlockingInput
+ true); // nonBlockingOutput
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCurrentRequestBody = do_QueryInterface(output);
+ mPendingReq->SetBody(input);
+ } else {
+ LOG_V("HttpServer::Connection::ConsumeLine(%p) - No body", this);
+ mState = eRequestLine;
+ }
+
+ mPendingRequests.AppendElement(PendingRequest(mPendingReq, nullptr));
+
+ LOG_V("HttpServer::Connection::ConsumeLine(%p) - Fire OnRequest", this);
+
+ RefPtr<HttpServerListener> listener = mServer->mListener;
+ RefPtr<InternalRequest> request = mPendingReq.forget();
+ nsCOMPtr<nsIRunnable> event =
+ NS_NewRunnableFunction([listener, request] ()
+ {
+ listener->OnRequest(request);
+ });
+ NS_DispatchToCurrentThread(event);
+
+ mPendingReqVersion = 0;
+
+ return NS_OK;
+ }
+
+ // Parse header line
+ if (aBuffer[0] == ' ' || aBuffer[0] == '\t') {
+ LOG_V("HttpServer::Connection::ConsumeLine(%p) - Add to header %s",
+ this,
+ mPendingHeaderName.get());
+
+ NS_ENSURE_FALSE(mPendingHeaderName.IsEmpty(),
+ NS_ERROR_UNEXPECTED);
+
+ // We might need to do whitespace trimming/compression here.
+ mPendingHeaderValue.Append(aBuffer, aLength);
+ return NS_OK;
+ }
+
+ MaybeAddPendingHeader();
+
+ const char* colon = static_cast<const char*>(memchr(aBuffer, ':', aLength));
+ NS_ENSURE_TRUE(colon, NS_ERROR_UNEXPECTED);
+
+ ToLowerCase(Substring(aBuffer, colon - aBuffer), mPendingHeaderName);
+ mPendingHeaderValue.Assign(colon + 1, aLength - (colon - aBuffer) - 1);
+
+ NS_ENSURE_TRUE(NS_IsValidHTTPToken(mPendingHeaderName),
+ NS_ERROR_UNEXPECTED);
+
+ LOG_V("HttpServer::Connection::ConsumeLine(%p) - Parsed header %s",
+ this,
+ mPendingHeaderName.get());
+
+ return NS_OK;
+}
+
+void
+HttpServer::Connection::MaybeAddPendingHeader()
+{
+ if (mPendingHeaderName.IsEmpty()) {
+ return;
+ }
+
+ // We might need to do more whitespace trimming/compression here.
+ mPendingHeaderValue.Trim(" \t");
+
+ ErrorResult rv;
+ mPendingReq->Headers()->Append(mPendingHeaderName, mPendingHeaderValue, rv);
+ mPendingHeaderName.Truncate();
+}
+
+bool
+HttpServer::Connection::TryHandleResponse(InternalRequest* aRequest,
+ InternalResponse* aResponse)
+{
+ bool handledResponse = false;
+ for (uint32_t i = 0; i < mPendingRequests.Length(); ++i) {
+ PendingRequest& pending = mPendingRequests[i];
+ if (pending.first() == aRequest) {
+ MOZ_ASSERT(!handledResponse);
+ MOZ_ASSERT(!pending.second());
+
+ pending.second() = aResponse;
+ if (i != 0) {
+ return true;
+ }
+ handledResponse = true;
+ }
+
+ if (handledResponse && !pending.second()) {
+ // Shortcut if we've handled the response, and
+ // we don't have more responses to send
+ return true;
+ }
+
+ if (i == 0 && pending.second()) {
+ RefPtr<InternalResponse> resp = pending.second().forget();
+ mPendingRequests.RemoveElementAt(0);
+ QueueResponse(resp);
+ --i;
+ }
+ }
+
+ return handledResponse;
+}
+
+already_AddRefed<nsITransportProvider>
+HttpServer::Connection::HandleAcceptWebSocket(const Optional<nsAString>& aProtocol,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(mPendingWebSocketRequest);
+
+ RefPtr<InternalResponse> response =
+ new InternalResponse(101, NS_LITERAL_CSTRING("Switching Protocols"));
+
+ InternalHeaders* headers = response->Headers();
+ headers->Set(NS_LITERAL_CSTRING("Upgrade"),
+ NS_LITERAL_CSTRING("websocket"),
+ aRv);
+ headers->Set(NS_LITERAL_CSTRING("Connection"),
+ NS_LITERAL_CSTRING("Upgrade"),
+ aRv);
+ if (aProtocol.WasPassed()) {
+ NS_ConvertUTF16toUTF8 protocol(aProtocol.Value());
+ nsAutoCString reqProtocols;
+ mPendingWebSocketRequest->Headers()->
+ GetFirst(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"), reqProtocols, aRv);
+ if (!ContainsToken(reqProtocols, protocol)) {
+ // Should throw a better error here
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ headers->Set(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
+ protocol, aRv);
+ }
+
+ nsAutoCString key, hash;
+ mPendingWebSocketRequest->Headers()->
+ GetFirst(NS_LITERAL_CSTRING("Sec-WebSocket-Key"), key, aRv);
+ nsresult rv = mozilla::net::CalculateWebSocketHashedSecret(key, hash);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ headers->Set(NS_LITERAL_CSTRING("Sec-WebSocket-Accept"), hash, aRv);
+
+ nsAutoCString extensions, negotiatedExtensions;
+ mPendingWebSocketRequest->Headers()->
+ GetFirst(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions, aRv);
+ mozilla::net::ProcessServerWebSocketExtensions(extensions,
+ negotiatedExtensions);
+ if (!negotiatedExtensions.IsEmpty()) {
+ headers->Set(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"),
+ negotiatedExtensions, aRv);
+ }
+
+ RefPtr<TransportProvider> result = new TransportProvider();
+ mWebSocketTransportProvider = result;
+
+ QueueResponse(response);
+
+ return result.forget();
+}
+
+void
+HttpServer::Connection::HandleWebSocketResponse(InternalResponse* aResponse)
+{
+ MOZ_ASSERT(mPendingWebSocketRequest);
+
+ mState = eRequestLine;
+ mPendingWebSocketRequest = nullptr;
+ mInput->AsyncWait(this, 0, 0, NS_GetCurrentThread());
+
+ QueueResponse(aResponse);
+}
+
+void
+HttpServer::Connection::QueueResponse(InternalResponse* aResponse)
+{
+ bool chunked = false;
+
+ RefPtr<InternalHeaders> headers = new InternalHeaders(*aResponse->Headers());
+ {
+ ErrorResult res;
+ headers->SetGuard(HeadersGuardEnum::None, res);
+ }
+ nsCOMPtr<nsIInputStream> body;
+ int64_t bodySize;
+ aResponse->GetBody(getter_AddRefs(body), &bodySize);
+
+ if (body && bodySize >= 0) {
+ nsCString sizeStr;
+ sizeStr.AppendInt(bodySize);
+
+ LOG_V("HttpServer::Connection::QueueResponse(%p) - "
+ "Setting content-length to %s",
+ this, sizeStr.get());
+
+ ErrorResult res;
+ headers->Set(NS_LITERAL_CSTRING("content-length"), sizeStr, res);
+ } else if (body) {
+ // Use chunked transfer encoding
+ LOG_V("HttpServer::Connection::QueueResponse(%p) - Chunked transfer-encoding",
+ this);
+
+ ErrorResult res;
+ headers->Set(NS_LITERAL_CSTRING("transfer-encoding"),
+ NS_LITERAL_CSTRING("chunked"),
+ res);
+ headers->Delete(NS_LITERAL_CSTRING("content-length"), res);
+ chunked = true;
+
+ } else {
+ LOG_V("HttpServer::Connection::QueueResponse(%p) - "
+ "No body - setting content-length to 0", this);
+
+ ErrorResult res;
+ headers->Set(NS_LITERAL_CSTRING("content-length"),
+ NS_LITERAL_CSTRING("0"), res);
+ }
+
+ nsCString head(NS_LITERAL_CSTRING("HTTP/1.1 "));
+ head.AppendInt(aResponse->GetStatus());
+ // XXX is the statustext security checked?
+ head.Append(NS_LITERAL_CSTRING(" ") +
+ aResponse->GetStatusText() +
+ NS_LITERAL_CSTRING("\r\n"));
+
+ AutoTArray<InternalHeaders::Entry, 16> entries;
+ headers->GetEntries(entries);
+
+ for (auto header : entries) {
+ head.Append(header.mName +
+ NS_LITERAL_CSTRING(": ") +
+ header.mValue +
+ NS_LITERAL_CSTRING("\r\n"));
+ }
+
+ head.Append(NS_LITERAL_CSTRING("\r\n"));
+
+ mOutputBuffers.AppendElement()->mString = head;
+ if (body) {
+ OutputBuffer* bodyBuffer = mOutputBuffers.AppendElement();
+ bodyBuffer->mStream = body;
+ bodyBuffer->mChunked = chunked;
+ }
+
+ OnOutputStreamReady(mOutput);
+}
+
+namespace {
+
+typedef MozPromise<nsresult, bool, false> StreamCopyPromise;
+
+class StreamCopier final : public nsIOutputStreamCallback
+ , public nsIInputStreamCallback
+ , public nsIRunnable
+{
+public:
+ static RefPtr<StreamCopyPromise>
+ Copy(nsIInputStream* aSource, nsIAsyncOutputStream* aSink,
+ bool aChunked)
+ {
+ RefPtr<StreamCopier> copier = new StreamCopier(aSource, aSink, aChunked);
+
+ RefPtr<StreamCopyPromise> p = copier->mPromise.Ensure(__func__);
+
+ nsresult rv = copier->mTarget->Dispatch(copier, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ copier->mPromise.Resolve(rv, __func__);
+ }
+
+ return p;
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSIRUNNABLE
+
+private:
+ StreamCopier(nsIInputStream* aSource, nsIAsyncOutputStream* aSink,
+ bool aChunked)
+ : mSource(aSource)
+ , mAsyncSource(do_QueryInterface(aSource))
+ , mSink(aSink)
+ , mTarget(do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID))
+ , mChunkRemaining(0)
+ , mChunked(aChunked)
+ , mAddedFinalSeparator(false)
+ , mFirstChunk(aChunked)
+ {
+ }
+ ~StreamCopier() {}
+
+ static nsresult FillOutputBufferHelper(nsIOutputStream* aOutStr,
+ void* aClosure,
+ char* aBuffer,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t* aCountRead);
+ nsresult FillOutputBuffer(char* aBuffer,
+ uint32_t aCount,
+ uint32_t* aCountRead);
+
+ nsCOMPtr<nsIInputStream> mSource;
+ nsCOMPtr<nsIAsyncInputStream> mAsyncSource;
+ nsCOMPtr<nsIAsyncOutputStream> mSink;
+ MozPromiseHolder<StreamCopyPromise> mPromise;
+ nsCOMPtr<nsIEventTarget> mTarget; // XXX we should cache this somewhere
+ uint32_t mChunkRemaining;
+ nsCString mSeparator;
+ bool mChunked;
+ bool mAddedFinalSeparator;
+ bool mFirstChunk;
+};
+
+NS_IMPL_ISUPPORTS(StreamCopier,
+ nsIOutputStreamCallback,
+ nsIInputStreamCallback,
+ nsIRunnable)
+
+struct WriteState
+{
+ StreamCopier* copier;
+ nsresult sourceRv;
+};
+
+// This function only exists to enable FillOutputBuffer to be a non-static
+// function where we can use member variables more easily.
+nsresult
+StreamCopier::FillOutputBufferHelper(nsIOutputStream* aOutStr,
+ void* aClosure,
+ char* aBuffer,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t* aCountRead)
+{
+ WriteState* ws = static_cast<WriteState*>(aClosure);
+ ws->sourceRv = ws->copier->FillOutputBuffer(aBuffer, aCount, aCountRead);
+ return ws->sourceRv;
+}
+
+nsresult
+CheckForEOF(nsIInputStream* aIn,
+ void* aClosure,
+ const char* aBuffer,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ *static_cast<bool*>(aClosure) = true;
+ *aWriteCount = 0;
+ return NS_BINDING_ABORTED;
+}
+
+nsresult
+StreamCopier::FillOutputBuffer(char* aBuffer,
+ uint32_t aCount,
+ uint32_t* aCountRead)
+{
+ nsresult rv = NS_OK;
+ while (mChunked && mSeparator.IsEmpty() && !mChunkRemaining &&
+ !mAddedFinalSeparator) {
+ uint64_t avail;
+ rv = mSource->Available(&avail);
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ avail = 0;
+ rv = NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mChunkRemaining = avail > UINT32_MAX ? UINT32_MAX :
+ static_cast<uint32_t>(avail);
+
+ if (!mChunkRemaining) {
+ // Either it's an non-blocking stream without any data
+ // currently available, or we're at EOF. Sadly there's no way
+ // to tell other than to read from the stream.
+ bool hadData = false;
+ uint32_t numRead;
+ rv = mSource->ReadSegments(CheckForEOF, &hadData, 1, &numRead);
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ avail = 0;
+ rv = NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(numRead == 0);
+
+ if (hadData) {
+ // The source received data between the call to Available and the
+ // call to ReadSegments. Restart with a new call to Available
+ continue;
+ }
+
+ // We're at EOF, write a separator with 0
+ mAddedFinalSeparator = true;
+ }
+
+ if (mFirstChunk) {
+ mFirstChunk = false;
+ MOZ_ASSERT(mSeparator.IsEmpty());
+ } else {
+ // For all chunks except the first, add the newline at the end
+ // of the previous chunk of data
+ mSeparator.AssignLiteral("\r\n");
+ }
+ mSeparator.AppendInt(mChunkRemaining, 16);
+ mSeparator.AppendLiteral("\r\n");
+
+ if (mAddedFinalSeparator) {
+ mSeparator.AppendLiteral("\r\n");
+ }
+
+ break;
+ }
+
+ // If we're doing chunked encoding, we should either have a chunk size,
+ // or we should have reached the end of the input stream.
+ MOZ_ASSERT_IF(mChunked, mChunkRemaining || mAddedFinalSeparator);
+ // We should only have a separator if we're doing chunked encoding
+ MOZ_ASSERT_IF(!mSeparator.IsEmpty(), mChunked);
+
+ if (!mSeparator.IsEmpty()) {
+ *aCountRead = std::min(mSeparator.Length(), aCount);
+ memcpy(aBuffer, mSeparator.BeginReading(), *aCountRead);
+ mSeparator.Cut(0, *aCountRead);
+ rv = NS_OK;
+ } else if (mChunked) {
+ *aCountRead = 0;
+ if (mChunkRemaining) {
+ rv = mSource->Read(aBuffer,
+ std::min(aCount, mChunkRemaining),
+ aCountRead);
+ mChunkRemaining -= *aCountRead;
+ }
+ } else {
+ rv = mSource->Read(aBuffer, aCount, aCountRead);
+ }
+
+ if (NS_SUCCEEDED(rv) && *aCountRead == 0) {
+ rv = NS_BASE_STREAM_CLOSED;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+StreamCopier::Run()
+{
+ nsresult rv;
+ while (1) {
+ WriteState state = { this, NS_OK };
+ uint32_t written;
+ rv = mSink->WriteSegments(FillOutputBufferHelper, &state,
+ mozilla::net::nsIOService::gDefaultSegmentSize,
+ &written);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || NS_SUCCEEDED(state.sourceRv));
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSink->AsyncWait(this, 0, 0, mTarget);
+ return NS_OK;
+ }
+ if (NS_FAILED(rv)) {
+ mPromise.Resolve(rv, __func__);
+ return NS_OK;
+ }
+
+ if (state.sourceRv == NS_BASE_STREAM_WOULD_BLOCK) {
+ MOZ_ASSERT(mAsyncSource);
+ mAsyncSource->AsyncWait(this, 0, 0, mTarget);
+ mSink->AsyncWait(this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY,
+ 0, mTarget);
+
+ return NS_OK;
+ }
+ if (state.sourceRv == NS_BASE_STREAM_CLOSED) {
+ // We're done!
+ // No longer interested in callbacks about either stream closing
+ mSink->AsyncWait(nullptr, 0, 0, nullptr);
+ if (mAsyncSource) {
+ mAsyncSource->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+
+ mSource->Close();
+ mSource = nullptr;
+ mAsyncSource = nullptr;
+ mSink = nullptr;
+
+ mPromise.Resolve(NS_OK, __func__);
+
+ return NS_OK;
+ }
+
+ if (NS_FAILED(state.sourceRv)) {
+ mPromise.Resolve(state.sourceRv, __func__);
+ return NS_OK;
+ }
+ }
+
+ MOZ_ASSUME_UNREACHABLE_MARKER();
+}
+
+NS_IMETHODIMP
+StreamCopier::OnInputStreamReady(nsIAsyncInputStream* aStream)
+{
+ MOZ_ASSERT(aStream == mAsyncSource ||
+ (!mSource && !mAsyncSource && !mSink));
+ return mSource ? Run() : NS_OK;
+}
+
+NS_IMETHODIMP
+StreamCopier::OnOutputStreamReady(nsIAsyncOutputStream* aStream)
+{
+ MOZ_ASSERT(aStream == mSink ||
+ (!mSource && !mAsyncSource && !mSink));
+ return mSource ? Run() : NS_OK;
+}
+
+} // namespace
+
+NS_IMETHODIMP
+HttpServer::Connection::OnOutputStreamReady(nsIAsyncOutputStream* aStream)
+{
+ MOZ_ASSERT(aStream == mOutput || !mOutput);
+ if (!mOutput) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ while (!mOutputBuffers.IsEmpty()) {
+ if (!mOutputBuffers[0].mStream) {
+ nsCString& buffer = mOutputBuffers[0].mString;
+ while (!buffer.IsEmpty()) {
+ uint32_t written = 0;
+ rv = mOutput->Write(buffer.BeginReading(),
+ buffer.Length(),
+ &written);
+
+ buffer.Cut(0, written);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ return mOutput->AsyncWait(this, 0, 0, NS_GetCurrentThread());
+ }
+
+ if (NS_FAILED(rv)) {
+ Close();
+ return NS_OK;
+ }
+ }
+ mOutputBuffers.RemoveElementAt(0);
+ } else {
+ if (mOutputCopy) {
+ // we're already copying the stream
+ return NS_OK;
+ }
+
+ mOutputCopy =
+ StreamCopier::Copy(mOutputBuffers[0].mStream,
+ mOutput,
+ mOutputBuffers[0].mChunked);
+
+ RefPtr<Connection> self = this;
+
+ mOutputCopy->
+ Then(AbstractThread::MainThread(),
+ __func__,
+ [self, this] (nsresult aStatus) {
+ MOZ_ASSERT(mOutputBuffers[0].mStream);
+ LOG_V("HttpServer::Connection::OnOutputStreamReady(%p) - "
+ "Sent body. Status 0x%lx",
+ this, aStatus);
+
+ mOutputBuffers.RemoveElementAt(0);
+ mOutputCopy = nullptr;
+ OnOutputStreamReady(mOutput);
+ },
+ [] (bool) { MOZ_ASSERT_UNREACHABLE("Reject unexpected"); });
+ }
+ }
+
+ if (mPendingRequests.IsEmpty()) {
+ if (mCloseAfterRequest) {
+ LOG_V("HttpServer::Connection::OnOutputStreamReady(%p) - Closing channel",
+ this);
+ Close();
+ } else if (mWebSocketTransportProvider) {
+ mInput->AsyncWait(nullptr, 0, 0, nullptr);
+ mOutput->AsyncWait(nullptr, 0, 0, nullptr);
+
+ mWebSocketTransportProvider->SetTransport(mTransport, mInput, mOutput);
+ mTransport = nullptr;
+ mInput = nullptr;
+ mOutput = nullptr;
+ mWebSocketTransportProvider = nullptr;
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+HttpServer::Connection::Close()
+{
+ if (!mTransport) {
+ MOZ_ASSERT(!mOutput && !mInput);
+ return;
+ }
+
+ mTransport->Close(NS_BINDING_ABORTED);
+ if (mInput) {
+ mInput->Close();
+ mInput = nullptr;
+ }
+ if (mOutput) {
+ mOutput->Close();
+ mOutput = nullptr;
+ }
+
+ mTransport = nullptr;
+
+ mInputBuffer.Truncate();
+ mOutputBuffers.Clear();
+ mPendingRequests.Clear();
+}
+
+
+} // namespace net
+} // namespace mozilla
diff --git a/dom/flyweb/HttpServer.h b/dom/flyweb/HttpServer.h
new file mode 100644
index 000000000..dab601c24
--- /dev/null
+++ b/dom/flyweb/HttpServer.h
@@ -0,0 +1,193 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_HttpServer_h
+#define mozilla_dom_HttpServer_h
+
+#include "nsISupportsImpl.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsITLSServerSocket.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "mozilla/Variant.h"
+#include "nsIRequestObserver.h"
+#include "mozilla/MozPromise.h"
+#include "nsITransportProvider.h"
+#include "nsILocalCertService.h"
+
+class nsIX509Cert;
+
+namespace mozilla {
+namespace dom {
+
+extern bool
+ContainsToken(const nsCString& aList, const nsCString& aToken);
+
+class InternalRequest;
+class InternalResponse;
+
+class HttpServerListener
+{
+public:
+ // switch to NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING when that lands
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
+
+ virtual void OnServerStarted(nsresult aStatus) = 0;
+ virtual void OnRequest(InternalRequest* aRequest) = 0;
+ virtual void OnWebSocket(InternalRequest* aConnectRequest) = 0;
+ virtual void OnServerClose() = 0;
+};
+
+class HttpServer final : public nsIServerSocketListener,
+ public nsILocalCertGetCallback
+{
+public:
+ HttpServer();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISERVERSOCKETLISTENER
+ NS_DECL_NSILOCALCERTGETCALLBACK
+
+ void Init(int32_t aPort, bool aHttps, HttpServerListener* aListener);
+
+ void SendResponse(InternalRequest* aRequest, InternalResponse* aResponse);
+ already_AddRefed<nsITransportProvider>
+ AcceptWebSocket(InternalRequest* aConnectRequest,
+ const Optional<nsAString>& aProtocol,
+ ErrorResult& aRv);
+ void SendWebSocketResponse(InternalRequest* aConnectRequest,
+ InternalResponse* aResponse);
+
+ void Close();
+
+ void GetCertKey(nsACString& aKey);
+
+ int32_t GetPort()
+ {
+ return mPort;
+ }
+
+private:
+ ~HttpServer();
+
+ nsresult StartServerSocket(nsIX509Cert* aCert);
+ void NotifyStarted(nsresult aStatus);
+
+ class TransportProvider final : public nsITransportProvider
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITRANSPORTPROVIDER
+
+ void SetTransport(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aInput,
+ nsIAsyncOutputStream* aOutput);
+
+ private:
+ virtual ~TransportProvider();
+ void MaybeNotify();
+
+ nsCOMPtr<nsIHttpUpgradeListener> mListener;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mInput;
+ nsCOMPtr<nsIAsyncOutputStream> mOutput;
+ };
+
+ class Connection final : public nsIInputStreamCallback
+ , public nsIOutputStreamCallback
+ , public nsITLSServerSecurityObserver
+ {
+ public:
+ Connection(nsISocketTransport* aTransport,
+ HttpServer* aServer,
+ nsresult& rv);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITLSSERVERSECURITYOBSERVER
+
+ bool TryHandleResponse(InternalRequest* aRequest,
+ InternalResponse* aResponse);
+ already_AddRefed<nsITransportProvider>
+ HandleAcceptWebSocket(const Optional<nsAString>& aProtocol,
+ ErrorResult& aRv);
+ void HandleWebSocketResponse(InternalResponse* aResponse);
+ bool HasPendingWebSocketRequest(InternalRequest* aRequest)
+ {
+ return aRequest == mPendingWebSocketRequest;
+ }
+
+ void Close();
+
+ private:
+ ~Connection();
+
+ void SetSecurityObserver(bool aListen);
+
+ static nsresult ReadSegmentsFunc(nsIInputStream* aIn,
+ void* aClosure,
+ const char* aBuffer,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCount);
+ nsresult ConsumeInput(const char*& aBuffer,
+ const char* aEnd);
+ nsresult ConsumeLine(const char* aBuffer,
+ size_t aLength);
+ void MaybeAddPendingHeader();
+
+ void QueueResponse(InternalResponse* aResponse);
+
+ RefPtr<HttpServer> mServer;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mInput;
+ nsCOMPtr<nsIAsyncOutputStream> mOutput;
+
+ enum { eRequestLine, eHeaders, eBody, ePause } mState;
+ RefPtr<InternalRequest> mPendingReq;
+ uint32_t mPendingReqVersion;
+ nsCString mInputBuffer;
+ nsCString mPendingHeaderName;
+ nsCString mPendingHeaderValue;
+ uint32_t mRemainingBodySize;
+ nsCOMPtr<nsIAsyncOutputStream> mCurrentRequestBody;
+ bool mCloseAfterRequest;
+
+ typedef Pair<RefPtr<InternalRequest>,
+ RefPtr<InternalResponse>> PendingRequest;
+ nsTArray<PendingRequest> mPendingRequests;
+ RefPtr<MozPromise<nsresult, bool, false>> mOutputCopy;
+
+ RefPtr<InternalRequest> mPendingWebSocketRequest;
+ RefPtr<TransportProvider> mWebSocketTransportProvider;
+
+ struct OutputBuffer {
+ nsCString mString;
+ nsCOMPtr<nsIInputStream> mStream;
+ bool mChunked;
+ };
+
+ nsTArray<OutputBuffer> mOutputBuffers;
+ };
+
+ friend class Connection;
+
+ RefPtr<HttpServerListener> mListener;
+ nsCOMPtr<nsIServerSocket> mServerSocket;
+ nsCOMPtr<nsIX509Cert> mCert;
+
+ nsTArray<RefPtr<Connection>> mConnections;
+
+ int32_t mPort;
+ bool mHttps;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HttpServer_h
diff --git a/dom/flyweb/PFlyWebPublishedServer.ipdl b/dom/flyweb/PFlyWebPublishedServer.ipdl
new file mode 100644
index 000000000..4d08a47fc
--- /dev/null
+++ b/dom/flyweb/PFlyWebPublishedServer.ipdl
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* 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 protocol PContent;
+include protocol PSendStream;
+include protocol PFileDescriptorSet;
+include protocol PTransportProvider;
+include FetchTypes;
+include ChannelInfo;
+include PBackgroundSharedTypes;
+
+namespace mozilla {
+namespace dom {
+
+async protocol PFlyWebPublishedServer
+{
+ manager PContent;
+
+child:
+ async ServerReady(nsresult aStatus);
+ async FetchRequest(IPCInternalRequest aRequest, uint64_t aRequestId);
+ async WebSocketRequest(IPCInternalRequest aRequest, uint64_t aRequestId,
+ PTransportProvider aProvider);
+ async ServerClose();
+
+parent:
+ async __delete__();
+
+ async FetchResponse(IPCInternalResponse aResponse, uint64_t aRequestId);
+ async WebSocketResponse(IPCInternalResponse aResponse, uint64_t aRequestId);
+ async WebSocketAccept(nsString aProtocol, uint64_t aRequestId);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/flyweb/moz.build b/dom/flyweb/moz.build
new file mode 100644
index 000000000..aa8c034b7
--- /dev/null
+++ b/dom/flyweb/moz.build
@@ -0,0 +1,42 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ 'FlyWebDiscoveryManager.h',
+ 'FlyWebPublishedServer.h',
+ 'FlyWebPublishedServerIPC.h',
+ 'FlyWebPublishOptionsIPCSerializer.h',
+ 'FlyWebServerEvents.h',
+ 'FlyWebService.h',
+ 'HttpServer.h',
+]
+
+UNIFIED_SOURCES += [
+ 'FlyWebDiscoveryManager.cpp',
+ 'FlyWebPublishedServer.cpp',
+ 'FlyWebServerEvents.cpp',
+ 'FlyWebService.cpp',
+ 'HttpServer.cpp'
+]
+
+IPDL_SOURCES += [
+ 'PFlyWebPublishedServer.ipdl',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/netwerk/base',
+ '/netwerk/dns',
+ '/netwerk/protocol/websocket',
+ '/xpcom/io'
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wshadow']