diff options
Diffstat (limited to 'dom/flyweb')
-rw-r--r-- | dom/flyweb/FlyWebDiscoveryManager.cpp | 125 | ||||
-rw-r--r-- | dom/flyweb/FlyWebDiscoveryManager.h | 61 | ||||
-rw-r--r-- | dom/flyweb/FlyWebPublishOptionsIPCSerializer.h | 33 | ||||
-rw-r--r-- | dom/flyweb/FlyWebPublishedServer.cpp | 675 | ||||
-rw-r--r-- | dom/flyweb/FlyWebPublishedServer.h | 109 | ||||
-rw-r--r-- | dom/flyweb/FlyWebPublishedServerIPC.h | 172 | ||||
-rw-r--r-- | dom/flyweb/FlyWebServerEvents.cpp | 141 | ||||
-rw-r--r-- | dom/flyweb/FlyWebServerEvents.h | 88 | ||||
-rw-r--r-- | dom/flyweb/FlyWebService.cpp | 1310 | ||||
-rw-r--r-- | dom/flyweb/FlyWebService.h | 113 | ||||
-rw-r--r-- | dom/flyweb/HttpServer.cpp | 1319 | ||||
-rw-r--r-- | dom/flyweb/HttpServer.h | 193 | ||||
-rw-r--r-- | dom/flyweb/PFlyWebPublishedServer.ipdl | 38 | ||||
-rw-r--r-- | dom/flyweb/moz.build | 42 |
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'] |