author | Kannan Vijayan <kvijayan@mozilla.com>, Jonas Sicking <jonas@sicking.cc> |
Thu, 02 Jun 2016 02:47:00 -0400 | |
changeset 300046 | 576019c741038e5d6d8100b93c8218c8cc8ea5f8 |
parent 300045 | bc8e2b503a967e9cfdfa16e5c689bc71c595d79e |
child 300047 | d66df97e2190f0b1cf345ad2008a8b9967ceed41 |
push id | 77803 |
push user | kvijayan@mozilla.com |
push date | Thu, 02 Jun 2016 06:47:13 +0000 |
treeherder | mozilla-inbound@576019c74103 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | baku, hurley |
bugs | 1272099, 1272101 |
milestone | 49.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -32,16 +32,17 @@ #include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" #include "BatteryManager.h" #include "mozilla/dom/DeviceStorageAreaListener.h" #include "mozilla/dom/PowerManager.h" #include "mozilla/dom/WakeLock.h" #include "mozilla/dom/power/PowerManagerService.h" #include "mozilla/dom/CellBroadcast.h" +#include "mozilla/dom/FlyWebService.h" #include "mozilla/dom/IccManager.h" #include "mozilla/dom/InputPortManager.h" #include "mozilla/dom/MobileMessageManager.h" #include "mozilla/dom/Permissions.h" #include "mozilla/dom/Presentation.h" #include "mozilla/dom/ServiceWorkerContainer.h" #include "mozilla/dom/TCPSocket.h" #include "mozilla/dom/Telephony.h" @@ -1614,16 +1615,30 @@ Navigator::GetDeprecatedBattery(ErrorRes if (doc) { doc->WarnOnceAbout(nsIDocument::eNavigatorBattery); } return mBatteryManager; } already_AddRefed<Promise> +Navigator::PublishServer(const nsAString& aName, + const FlyWebPublishOptions& aOptions, + ErrorResult& aRv) +{ + RefPtr<FlyWebService> service = FlyWebService::GetOrCreate(); + if (!service) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + return service->PublishServer(aName, aOptions, mWindow, aRv); +} + +already_AddRefed<Promise> Navigator::GetFeature(const nsAString& aName, ErrorResult& aRv) { nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow); RefPtr<Promise> p = Promise::Create(go, aRv); if (aRv.Failed()) { return nullptr; }
--- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -38,16 +38,18 @@ class Geolocation; class systemMessageCallback; class MediaDevices; struct MediaStreamConstraints; class WakeLock; class ArrayBufferViewOrBlobOrStringOrFormData; struct MobileIdOptions; class ServiceWorkerContainer; class DOMRequest; +struct FlyWebPublishOptions; +struct FlyWebFilter; } // namespace dom } // namespace mozilla //***************************************************************************** // Navigator: Script "navigator" object //***************************************************************************** namespace mozilla { @@ -162,16 +164,19 @@ public: nsMimeTypeArray* GetMimeTypes(ErrorResult& aRv); nsPluginArray* GetPlugins(ErrorResult& aRv); Permissions* GetPermissions(ErrorResult& aRv); // The XPCOM GetDoNotTrack is ok Geolocation* GetGeolocation(ErrorResult& aRv); Promise* GetBattery(ErrorResult& aRv); battery::BatteryManager* GetDeprecatedBattery(ErrorResult& aRv); + already_AddRefed<Promise> PublishServer(const nsAString& aName, + const FlyWebPublishOptions& aOptions, + ErrorResult& aRv); static void AppName(nsAString& aAppName, bool aUsePrefOverriddenValue); static nsresult GetPlatform(nsAString& aPlatform, bool aUsePrefOverriddenValue); static nsresult GetAppVersion(nsAString& aAppVersion, bool aUsePrefOverriddenValue);
--- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -159,16 +159,17 @@ #include "nsEscape.h" #include "nsObjectLoadingContent.h" #include "nsHtml5TreeOpExecutor.h" #include "mozilla/dom/HTMLLinkElement.h" #include "mozilla/dom/HTMLMediaElement.h" #include "mozilla/dom/HTMLIFrameElement.h" #include "mozilla/dom/HTMLImageElement.h" #include "mozilla/dom/MediaSource.h" +#include "mozilla/dom/FlyWebService.h" #include "mozAutoDocUpdate.h" #include "nsGlobalWindow.h" #include "mozilla/dom/EncodingUtils.h" #include "nsDOMNavigationTiming.h" #include "nsSMILAnimationController.h" #include "imgIContainer.h" @@ -8983,16 +8984,23 @@ nsDocument::CanSavePresentation(nsIReque #endif // Don't save presentations for documents containing MSE content, to // reduce memory usage. if (ContainsMSEContent()) { return false; } + // Don't save presentation if there are active FlyWeb connections or FlyWeb + // servers. + FlyWebService* flyWebService = FlyWebService::GetExisting(); + if (flyWebService && flyWebService->HasConnectionOrServer(win->WindowID())) { + return false; + } + if (mSubDocuments) { for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) { auto entry = static_cast<SubDocMapEntry*>(iter.Get()); nsIDocument* subdoc = entry->mSubDocument; // The aIgnoreRequest we were passed is only for us, so don't pass it on. bool canCache = subdoc ? subdoc->CanSavePresentation(nullptr) : false; if (!canCache) {
--- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -493,16 +493,26 @@ DOMInterfaces = { 'FileReader': { 'implicitJSContext': [ 'readAsArrayBuffer' ], }, 'FileReaderSync': { 'wrapperCache': False, }, +'FlyWebFetchEvent': { + 'headerFile': 'FlyWebServerEvents.h', + 'nativeType': 'mozilla::dom::FlyWebFetchEvent', +}, + +'FlyWebWebSocketEvent': { + 'headerFile': 'FlyWebServerEvents.h', + 'nativeType': 'mozilla::dom::FlyWebWebSocketEvent', +}, + 'FontFaceSet': { 'implicitJSContext': [ 'load' ], }, 'FontFaceSetIterator': { 'wrapperCache': False, },
--- a/dom/events/test/test_all_synthetic_events.html +++ b/dom/events/test/test_all_synthetic_events.html @@ -234,16 +234,20 @@ const kEventConstructors = { aProps.button, aProps.relatedTarget, aProps.dataTransfer); return e; }, }, ErrorEvent: { create: function (aName, aProps) { return new ErrorEvent(aName, aProps); }, }, + FlyWebFetchEvent: { create: null, // Cannot create untrusted event from JS. + }, + FlyWebWebSocketEvent: { create: null, // Cannot create untrusted event from JS. + }, FocusEvent: { create: function (aName, aProps) { return new FocusEvent(aName, aProps); }, }, FontFaceSetLoadEvent: { create: function (aName, aProps) { return new FontFaceSetLoadEvent(aName, aProps); }, },
new file mode 100644 --- /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
new file mode 100644 --- /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
new file mode 100644 --- /dev/null +++ b/dom/flyweb/FlyWebPublishedServer.cpp @@ -0,0 +1,212 @@ +/* -*- 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/FlyWebPublishedServer.h" +#include "mozilla/dom/FlyWebPublishBinding.h" +#include "mozilla/dom/FlyWebService.h" +#include "mozilla/dom/Request.h" +#include "mozilla/dom/FlyWebServerEvents.h" +#include "mozilla/Preferences.h" +#include "nsGlobalWindow.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__)) + +NS_IMPL_ISUPPORTS_INHERITED0(FlyWebPublishedServer, mozilla::DOMEventTargetHelper) + +FlyWebPublishedServer::FlyWebPublishedServer(nsPIDOMWindowInner* aOwner, + const nsAString& aName, + const FlyWebPublishOptions& aOptions, + Promise* aPublishPromise) + : mozilla::DOMEventTargetHelper(aOwner) + , mOwnerWindowID(aOwner ? aOwner->WindowID() : 0) + , mPublishPromise(aPublishPromise) + , mName(aName) + , mCategory(aOptions.mCategory) + , mHttp(aOptions.mHttp) + , mMessage(aOptions.mMessage) + , mUiUrl(aOptions.mUiUrl) + , mIsRegistered(true) // Registered by the FlyWebService +{ + if (mCategory.IsEmpty()) { + mCategory.SetIsVoid(true); + } + + mHttpServer = new HttpServer(); + mHttpServer->Init(-1, Preferences::GetBool("flyweb.use-tls", false), this); +} + +FlyWebPublishedServer::~FlyWebPublishedServer() +{ + // Make sure to unregister to avoid dangling pointers + Close(); +} + +JSObject* +FlyWebPublishedServer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return FlyWebPublishedServerBinding::Wrap(aCx, this, aGivenProto); +} + +void +FlyWebPublishedServer::Close() +{ + // Unregister from server. + if (mIsRegistered) { + FlyWebService::GetOrCreate()->UnregisterServer(this); + mIsRegistered = false; + } + + if (mMDNSCancelRegister) { + mMDNSCancelRegister->Cancel(NS_BINDING_ABORTED); + mMDNSCancelRegister = nullptr; + } + + if (mHttpServer) { + RefPtr<HttpServer> server = mHttpServer.forget(); + server->Close(); + } +} + +void +FlyWebPublishedServer::OnServerStarted(nsresult aStatus) +{ + if (NS_SUCCEEDED(aStatus)) { + FlyWebService::GetOrCreate()->StartDiscoveryOf(this); + } else { + DiscoveryStarted(aStatus); + } +} + +void +FlyWebPublishedServer::OnServerClose() +{ + mHttpServer = nullptr; + Close(); + + DispatchTrustedEvent(NS_LITERAL_STRING("close")); +} + +void +FlyWebPublishedServer::OnRequest(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::OnWebSocket(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::OnFetchResponse(InternalRequest* aRequest, + InternalResponse* aResponse) +{ + MOZ_ASSERT(aRequest); + MOZ_ASSERT(aResponse); + + LOG_I("FlyWebPublishedMDNSServer::OnFetchResponse(%p)", this); + + if (mHttpServer) { + mHttpServer->SendResponse(aRequest, aResponse); + } +} + +already_AddRefed<WebSocket> +FlyWebPublishedServer::OnWebSocketAccept(InternalRequest* aConnectRequest, + const Optional<nsAString>& aProtocol, + ErrorResult& aRv) +{ + MOZ_ASSERT(aConnectRequest); + + LOG_I("FlyWebPublishedMDNSServer::OnWebSocketAccept(%p)", this); + + if (!mHttpServer) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + nsAutoCString negotiatedExtensions; + nsCOMPtr<nsITransportProvider> provider = + mHttpServer->AcceptWebSocket(aConnectRequest, + aProtocol, + negotiatedExtensions, + aRv); + if (aRv.Failed()) { + return nullptr; + } + MOZ_ASSERT(provider); + + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetOwner()); + AutoJSContext cx; + GlobalObject global(cx, nsGlobalWindow::Cast(window)->FastGetGlobalJSObject()); + + 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); +} + +void +FlyWebPublishedServer::OnWebSocketResponse(InternalRequest* aConnectRequest, + InternalResponse* aResponse) +{ + MOZ_ASSERT(aConnectRequest); + MOZ_ASSERT(aResponse); + + LOG_I("FlyWebPublishedMDNSServer::OnWebSocketResponse(%p)", this); + + if (mHttpServer) { + mHttpServer->SendWebSocketResponse(aConnectRequest, aResponse); + } +} + +void FlyWebPublishedServer::DiscoveryStarted(nsresult aStatus) +{ + if (NS_SUCCEEDED(aStatus)) { + mPublishPromise->MaybeResolve(this); + } else { + Close(); + mPublishPromise->MaybeReject(aStatus); + } +} + +} // namespace dom +} // namespace mozilla + +
new file mode 100644 --- /dev/null +++ b/dom/flyweb/FlyWebPublishedServer.h @@ -0,0 +1,134 @@ +/* -*- 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 "nsISupportsImpl.h" +#include "nsICancelable.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/ErrorResult.h" +#include "HttpServer.h" +#include "mozilla/dom/WebSocket.h" + +class nsPIDOMWindowInner; + +namespace mozilla { +namespace dom { + +class Promise; +class InternalResponse; +class InternalRequest; +struct FlyWebPublishOptions; +class FlyWebPublishedServer; + +class FlyWebPublishedServer final : public mozilla::DOMEventTargetHelper + , public HttpServerListener +{ +public: + FlyWebPublishedServer(nsPIDOMWindowInner* aOwner, + const nsAString& aName, + const FlyWebPublishOptions& aOptions, + Promise* aPublishPromise); + + virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override; + + NS_DECL_ISUPPORTS_INHERITED + + uint64_t OwnerWindowID() const { + return mOwnerWindowID; + } + + int32_t Port() + { + return mHttpServer ? mHttpServer->GetPort() : 0; + } + void GetCertKey(nsACString& aKey) { + if (mHttpServer) { + mHttpServer->GetCertKey(aKey); + } else { + aKey.Truncate(); + } + } + + void GetName(nsAString& aName) + { + aName = mName; + } + nsAString& Name() + { + return mName; + } + + void GetCategory(nsAString& aCategory) + { + aCategory = mCategory; + } + + bool Http() + { + return mHttp; + } + + bool Message() + { + return mMessage; + } + + void GetUiUrl(nsAString& aUiUrl) + { + aUiUrl = mUiUrl; + } + + void OnFetchResponse(InternalRequest* aRequest, + InternalResponse* aResponse); + already_AddRefed<WebSocket> + OnWebSocketAccept(InternalRequest* aConnectRequest, + const Optional<nsAString>& aProtocol, + ErrorResult& aRv); + void OnWebSocketResponse(InternalRequest* aConnectRequest, + InternalResponse* aResponse); + + void SetCancelRegister(nsICancelable* aCancelRegister) + { + mMDNSCancelRegister = aCancelRegister; + } + + void Close(); + + // HttpServerListener + virtual void OnServerStarted(nsresult aStatus) override; + virtual void OnRequest(InternalRequest* aRequest) override; + virtual void OnWebSocket(InternalRequest* aConnectRequest) override; + virtual void OnServerClose() override; + + IMPL_EVENT_HANDLER(fetch) + IMPL_EVENT_HANDLER(websocket) + IMPL_EVENT_HANDLER(close) + + void DiscoveryStarted(nsresult aStatus); + +private: + ~FlyWebPublishedServer(); + + uint64_t mOwnerWindowID; + RefPtr<HttpServer> mHttpServer; + RefPtr<Promise> mPublishPromise; + nsCOMPtr<nsICancelable> mMDNSCancelRegister; + + nsString mName; + nsString mCategory; + bool mHttp; + bool mMessage; + nsString mUiUrl; + + bool mIsRegistered; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_FlyWebPublishedServer_h
new file mode 100644 --- /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
new file mode 100644 --- /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
new file mode 100644 --- /dev/null +++ b/dom/flyweb/FlyWebService.cpp @@ -0,0 +1,1127 @@ +/* -*- 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/StaticPtr.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/FlyWebPublishedServer.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 "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 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(FlyWebPublishedServer* 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. + 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. + 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. + 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) { + 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) { + 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->DiscoveryStarted(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->DiscoveryStarted(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(FlyWebPublishedServer* 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(); + ErrorResult rv = gFlyWebService->Init(); + if (rv.Failed()) { + gFlyWebService = nullptr; + return nullptr; + } + } + return gFlyWebService; +} + +ErrorResult +FlyWebService::Init() +{ + 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); +} + +already_AddRefed<Promise> +FlyWebService::PublishServer(const nsAString& aName, + const FlyWebPublishOptions& aOptions, + nsPIDOMWindowInner* aWindow, + ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aWindow); + RefPtr<Promise> promise = Promise::Create(global, aRv); + if (aRv.Failed()) { + return nullptr; + } + + // 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()); + promise->MaybeReject(NS_ERROR_FAILURE); + return promise.forget(); + } + + RefPtr<FlyWebPublishedServer> server = + new FlyWebPublishedServer(aWindow, aName, aOptions, promise); + + mServers.AppendElement(server); + + return promise.forget(); +} + +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(FlyWebPublishedServer* aServer) +{ + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv = mMDNSFlywebService ? + mMDNSFlywebService->StartDiscoveryOf(aServer) : + NS_ERROR_FAILURE; + + if (NS_FAILED(rv)) { + aServer->DiscoveryStarted(rv); + } +} + +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/flyweb/FlyWebService.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_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/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 { + +class Promise; +struct FlyWebPublishOptions; +struct FlyWebFilter; +class FlyWebPublishedServer; +class FlyWebPairingCallback; +class FlyWebDiscoveryManager; +class FlyWebMDNSService; + +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<Promise> PublishServer(const nsAString& aName, + const FlyWebPublishOptions& aOptions, + nsPIDOMWindowInner* aWindow, + ErrorResult& aRv); + + 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 FlyWebPublishedServer + void StartDiscoveryOf(FlyWebPublishedServer* 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
new file mode 100644 --- /dev/null +++ b/dom/flyweb/HttpServer.cpp @@ -0,0 +1,1315 @@ +/* -*- 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() +{ +} + +HttpServer::~HttpServer() +{ +} + +void +HttpServer::Init(int32_t aPort, bool aHttps, HttpServerListener* aListener) +{ + mPort = aPort; + mHttps = aHttps; + mListener = aListener; + + nsCOMPtr<nsIPrefBranch> prefService; + prefService = do_GetService(NS_PREFSERVICE_CONTRACTID); + + 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, + nsACString& aNegotiatedExtensions, + ErrorResult& aRv) +{ + for (Connection* conn : mConnections) { + if (!conn->HasPendingWebSocketRequest(aConnectRequest)) { + continue; + } + nsCOMPtr<nsITransportProvider> provider = + conn->HandleAcceptWebSocket(aProtocol, aNegotiatedExtensions, 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) { + mListener->OnServerClose(); + mListener = nullptr; + } + + 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; +} + +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) + , 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; +} + +NS_METHOD +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; +} + +static 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->Get(NS_LITERAL_CSTRING("upgrade"), str, res); + MOZ_ASSERT(!res.Failed()); + if (!str.EqualsLiteral("websocket")) { + return false; + } + + headers->Get(NS_LITERAL_CSTRING("connection"), str, res); + MOZ_ASSERT(!res.Failed()); + if (!ContainsToken(str, NS_LITERAL_CSTRING("Upgrade"))) { + return false; + } + + headers->Get(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->Get(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); + 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()->Get(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()->Get(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, + nsACString& aNegotiatedExtensions, + 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()-> + Get(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()-> + Get(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; + mPendingWebSocketRequest->Headers()-> + Get(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions, aRv); + mozilla::net::ProcessServerWebSocketExtensions(extensions, + aNegotiatedExtensions); + if (!aNegotiatedExtensions.IsEmpty()) { + headers->Set(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), + aNegotiatedExtensions, 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")); + + nsTArray<InternalHeaders::Entry> 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 NS_METHOD 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. +NS_METHOD +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; +} + +NS_METHOD +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; + 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
new file mode 100644 --- /dev/null +++ b/dom/flyweb/HttpServer.h @@ -0,0 +1,192 @@ +/* -*- 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 { + +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, + nsACString& aNegotiatedExtensions, + 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, + nsACString& aNegotiatedExtensions, + ErrorResult& aRv); + void HandleWebSocketResponse(InternalResponse* aResponse); + bool HasPendingWebSocketRequest(InternalRequest* aRequest) + { + return aRequest == mPendingWebSocketRequest; + } + + void Close(); + + private: + ~Connection(); + + void SetSecurityObserver(bool aListen); + + static NS_METHOD 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
new file mode 100644 --- /dev/null +++ b/dom/flyweb/moz.build @@ -0,0 +1,36 @@ +# -*- Mode: python; c-basic-offset: 4; 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', + 'FlyWebServerEvents.h', + 'FlyWebService.h', + 'HttpServer.h', +] + +UNIFIED_SOURCES += [ + 'FlyWebDiscoveryManager.cpp', + 'FlyWebPublishedServer.cpp', + 'FlyWebServerEvents.cpp', + 'FlyWebService.cpp', + 'HttpServer.cpp' +] + +#include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '/dom/base', + '/netwerk/base', + '/netwerk/dns', + '/netwerk/protocol/websocket', + '/xpcom/io' +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wshadow']
--- a/dom/moz.build +++ b/dom/moz.build @@ -55,16 +55,17 @@ DIRS += [ 'phonenumberutils', 'alarm', 'devicestorage', 'encoding', 'events', 'fetch', 'filehandle', 'filesystem', + 'flyweb', 'fmradio', 'geolocation', 'html', 'icc', 'inputport', 'json', 'jsurl', 'asmjscache',
new file mode 100644 --- /dev/null +++ b/dom/webidl/FlyWebDiscoveryManager.webidl @@ -0,0 +1,39 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + */ + +dictionary FlyWebDiscoveredService { + DOMString serviceId = ""; + DOMString displayName = ""; + DOMString transport = ""; + DOMString serviceType = ""; + DOMString cert = ""; + DOMString path = ""; +}; + +dictionary FlyWebPairedService { + FlyWebDiscoveredService discoveredService; + DOMString hostname = ""; + DOMString uiUrl = ""; +}; + +callback interface FlyWebPairingCallback { + void pairingSucceeded(optional FlyWebPairedService service); + void pairingFailed(DOMString error); +}; + +callback interface FlyWebDiscoveryCallback { + void onDiscoveredServicesChanged(sequence<FlyWebDiscoveredService> serviceList); +}; + +[ChromeOnly, ChromeConstructor, Exposed=(Window,System)] +interface FlyWebDiscoveryManager { + sequence<FlyWebDiscoveredService> listServices(); + + unsigned long startDiscovery(FlyWebDiscoveryCallback aCallback); + void stopDiscovery(unsigned long aId); + + void pairWithService(DOMString serviceId, FlyWebPairingCallback callback); +};
new file mode 100644 --- /dev/null +++ b/dom/webidl/FlyWebFetchEvent.webidl @@ -0,0 +1,13 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + */ + +[Pref="dom.flyweb.enabled"] +interface FlyWebFetchEvent : Event { + [SameObject] readonly attribute Request request; + + [Throws] + void respondWith(Promise<Response> r); +};
new file mode 100644 --- /dev/null +++ b/dom/webidl/FlyWebPublish.webidl @@ -0,0 +1,29 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + */ + +[Pref="dom.flyweb.enabled"] +interface FlyWebPublishedServer : EventTarget { + readonly attribute DOMString name; + readonly attribute DOMString? category; + readonly attribute boolean http; + readonly attribute boolean message; + readonly attribute DOMString? uiUrl; + + void close(); + + attribute EventHandler onclose; + attribute EventHandler onfetch; + attribute EventHandler onwebsocket; +}; + +dictionary FlyWebPublishOptions { + DOMString category = ""; + boolean http = false; + boolean message = false; + DOMString? uiUrl = null; // URL to user interface. Can be different server. Makes + // endpoint show up in browser's "local services" UI. + // If relative, resolves against the root of the server. +};
new file mode 100644 --- /dev/null +++ b/dom/webidl/FlyWebWebSocketEvent.webidl @@ -0,0 +1,16 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + */ + +[Pref="dom.flyweb.enabled"] +interface FlyWebWebSocketEvent : Event { + [SameObject] readonly attribute Request request; + + [Throws] + WebSocket accept(optional DOMString protocol); + + [Throws] + void respondWith(Promise<Response> r); +};
--- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -134,16 +134,22 @@ partial interface Navigator { Promise<BatteryManager> getBattery(); // Deprecated. Use getBattery() instead. // XXXbz Per spec this should be non-nullable, but we return null in // torn-down windows. See bug 884925. [Throws, Pref="dom.battery.enabled", BinaryName="deprecatedBattery"] readonly attribute BatteryManager? battery; }; +partial interface Navigator { + [NewObject, Pref="dom.flyweb.enabled"] + Promise<FlyWebPublishedServer> publishServer(DOMString name, + optional FlyWebPublishOptions options); +}; + // http://www.w3.org/TR/vibration/#vibration-interface partial interface Navigator { // We don't support sequences in unions yet //boolean vibrate ((unsigned long or sequence<unsigned long>) pattern); boolean vibrate(unsigned long duration); boolean vibrate(sequence<unsigned long> pattern); };
--- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -155,16 +155,20 @@ WEBIDL_FILES = [ 'FakePluginTagInit.webidl', 'Fetch.webidl', 'FetchEvent.webidl', 'File.webidl', 'FileList.webidl', 'FileMode.webidl', 'FileReader.webidl', 'FileReaderSync.webidl', + 'FlyWebDiscoveryManager.webidl', + 'FlyWebFetchEvent.webidl', + 'FlyWebPublish.webidl', + 'FlyWebWebSocketEvent.webidl', 'FocusEvent.webidl', 'FontFace.webidl', 'FontFaceSet.webidl', 'FontFaceSource.webidl', 'FormData.webidl', 'Function.webidl', 'GainNode.webidl', 'Geolocation.webidl',
--- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -230,16 +230,18 @@ static void Shutdown(); #include "nsIPowerManagerService.h" #include "nsIAlarmHalService.h" #include "nsIMediaManager.h" #include "mozilla/dom/nsMixedContentBlocker.h" #include "AudioChannelService.h" #include "mozilla/net/WebSocketEventService.h" +#include "mozilla/dom/FlyWebService.h" + #include "mozilla/dom/power/PowerManagerService.h" #include "mozilla/dom/alarm/AlarmHalService.h" #include "mozilla/dom/time/TimeService.h" #include "StreamingProtocolService.h" #include "nsIPresentationService.h" #include "nsITelephonyService.h" #include "nsIVoicemailService.h" @@ -630,22 +632,27 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(Geol { 0x404d02a, 0x1CA, 0xAAAB, { 0x47, 0x62, 0x94, 0x4b, 0x1b, 0xf2, 0xf7, 0xb5 } } #define NS_AUDIOCHANNEL_SERVICE_CID \ { 0xf712e983, 0x048a, 0x443f, { 0x88, 0x02, 0xfc, 0xc3, 0xd9, 0x27, 0xce, 0xac }} #define NS_WEBSOCKETEVENT_SERVICE_CID \ { 0x31689828, 0xda66, 0x49a6, { 0x87, 0x0c, 0xdf, 0x62, 0xb8, 0x3f, 0xe7, 0x89 }} +#define NS_FLYWEB_SERVICE_CID \ + { 0x5de19ef0, 0x895e, 0x4c0c, { 0xa6, 0xe0, 0xea, 0xe0, 0x23, 0x2b, 0x84, 0x5a } } + NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsGeolocationService, nsGeolocationService::GetGeolocationService) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AudioChannelService, AudioChannelService::GetOrCreate) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(WebSocketEventService, WebSocketEventService::GetOrCreate) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(FlyWebService, FlyWebService::GetOrCreateAddRefed) + #ifdef MOZ_WEBSPEECH_TEST_BACKEND NS_GENERIC_FACTORY_CONSTRUCTOR(FakeSpeechRecognitionService) #endif #ifdef MOZ_WEBSPEECH_POCKETSPHINX NS_GENERIC_FACTORY_CONSTRUCTOR(PocketSphinxSpeechRecognitionService) #endif NS_GENERIC_FACTORY_CONSTRUCTOR(nsContentSecurityManager) @@ -789,16 +796,17 @@ NS_DEFINE_NAMED_CID(NS_EDITORCONTROLLER_ NS_DEFINE_NAMED_CID(NS_EDITINGCONTROLLER_CID); NS_DEFINE_NAMED_CID(NS_EDITORCOMMANDTABLE_CID); NS_DEFINE_NAMED_CID(NS_EDITINGCOMMANDTABLE_CID); NS_DEFINE_NAMED_CID(NS_TEXTSERVICESDOCUMENT_CID); NS_DEFINE_NAMED_CID(NS_GEOLOCATION_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_GEOLOCATION_CID); NS_DEFINE_NAMED_CID(NS_AUDIOCHANNEL_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_WEBSOCKETEVENT_SERVICE_CID); +NS_DEFINE_NAMED_CID(NS_FLYWEB_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_FOCUSMANAGER_CID); NS_DEFINE_NAMED_CID(NS_CONTENTSECURITYMANAGER_CID); NS_DEFINE_NAMED_CID(CSPSERVICE_CID); NS_DEFINE_NAMED_CID(NS_CSPCONTEXT_CID); NS_DEFINE_NAMED_CID(NS_MIXEDCONTENTBLOCKER_CID); NS_DEFINE_NAMED_CID(NS_EVENTLISTENERSERVICE_CID); NS_DEFINE_NAMED_CID(NS_GLOBALMESSAGEMANAGER_CID); NS_DEFINE_NAMED_CID(NS_PARENTPROCESSMESSAGEMANAGER_CID); @@ -1096,16 +1104,17 @@ static const mozilla::Module::CIDEntry k { &kNS_EDITINGCONTROLLER_CID, false, nullptr, nsEditingControllerConstructor }, { &kNS_EDITORCOMMANDTABLE_CID, false, nullptr, nsEditorCommandTableConstructor }, { &kNS_EDITINGCOMMANDTABLE_CID, false, nullptr, nsEditingCommandTableConstructor }, { &kNS_TEXTSERVICESDOCUMENT_CID, false, nullptr, nsTextServicesDocumentConstructor }, { &kNS_GEOLOCATION_SERVICE_CID, false, nullptr, nsGeolocationServiceConstructor }, { &kNS_GEOLOCATION_CID, false, nullptr, GeolocationConstructor }, { &kNS_AUDIOCHANNEL_SERVICE_CID, false, nullptr, AudioChannelServiceConstructor }, { &kNS_WEBSOCKETEVENT_SERVICE_CID, false, nullptr, WebSocketEventServiceConstructor }, + { &kNS_FLYWEB_SERVICE_CID, false, nullptr, FlyWebServiceConstructor }, { &kNS_FOCUSMANAGER_CID, false, nullptr, CreateFocusManager }, #ifdef MOZ_WEBSPEECH_TEST_BACKEND { &kNS_FAKE_SPEECH_RECOGNITION_SERVICE_CID, false, nullptr, FakeSpeechRecognitionServiceConstructor }, #endif #ifdef MOZ_WEBSPEECH_POCKETSPHINX { &kNS_POCKETSPHINX_SPEECH_RECOGNITION_SERVICE_CID, false, nullptr, PocketSphinxSpeechRecognitionServiceConstructor }, #endif #ifdef MOZ_WEBSPEECH @@ -1263,16 +1272,18 @@ static const mozilla::Module::ContractID { "@mozilla.org/editor/htmleditor;1", &kNS_HTMLEDITOR_CID }, { "@mozilla.org/editor/editorcontroller;1", &kNS_EDITORCONTROLLER_CID }, { "@mozilla.org/editor/editingcontroller;1", &kNS_EDITINGCONTROLLER_CID }, { "@mozilla.org/textservices/textservicesdocument;1", &kNS_TEXTSERVICESDOCUMENT_CID }, { "@mozilla.org/geolocation/service;1", &kNS_GEOLOCATION_SERVICE_CID }, { "@mozilla.org/geolocation;1", &kNS_GEOLOCATION_CID }, { "@mozilla.org/audiochannel/service;1", &kNS_AUDIOCHANNEL_SERVICE_CID }, { "@mozilla.org/websocketevent/service;1", &kNS_WEBSOCKETEVENT_SERVICE_CID }, + { "@mozilla.org/flyweb-service;1", &kNS_FLYWEB_SERVICE_CID }, + { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "flyweb", &kNS_FLYWEB_SERVICE_CID }, { "@mozilla.org/focus-manager;1", &kNS_FOCUSMANAGER_CID }, #ifdef MOZ_WEBSPEECH_TEST_BACKEND { NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX "fake", &kNS_FAKE_SPEECH_RECOGNITION_SERVICE_CID }, #endif #ifdef MOZ_WEBSPEECH_POCKETSPHINX { NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX "pocketsphinx-en-US", &kNS_POCKETSPHINX_SPEECH_RECOGNITION_SERVICE_CID }, #endif #ifdef MOZ_WEBSPEECH
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4956,16 +4956,18 @@ pref("captivedetect.maxRetryCount", 5); pref("dom.forms.inputmode", false); #else pref("dom.forms.inputmode", true); #endif // InputMethods for soft keyboards in B2G pref("dom.mozInputMethod.enabled", false); +pref("dom.flyweb.enabled", false); + // Telephony API #ifdef MOZ_B2G_RIL pref("dom.telephony.enabled", true); #else pref("dom.telephony.enabled", false); #endif // Numeric default service id for WebTelephony API calls with |serviceId| // parameter omitted.
--- a/netwerk/base/nsSocketTransport2.cpp +++ b/netwerk/base/nsSocketTransport2.cpp @@ -740,16 +740,17 @@ nsSocketTransport::nsSocketTransport() , mConnectionFlags(0) , mState(STATE_CLOSED) , mAttached(false) , mInputClosed(true) , mOutputClosed(true) , mResolving(false) , mNetAddrIsSet(false) , mSelfAddrIsSet(false) + , mNetAddrPreResolved(false) , mLock("nsSocketTransport.mLock") , mFD(this) , mFDref(0) , mFDconnected(false) , mSocketTransportService(gSocketTransportService) , mInput(this) , mOutput(this) , mQoSBits(0x00) @@ -880,16 +881,33 @@ nsSocketTransport::Init(const char **typ } } } return NS_OK; } nsresult +nsSocketTransport::InitPreResolved(const char **socketTypes, uint32_t typeCount, + const nsACString &host, uint16_t port, + const nsACString &hostRoute, uint16_t portRoute, + nsIProxyInfo *proxyInfo, + const mozilla::net::NetAddr* addr) +{ + nsresult rv = Init(socketTypes, typeCount, host, port, hostRoute, portRoute, proxyInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mNetAddr = *addr; + mNetAddrPreResolved = true; + return NS_OK; +} + +nsresult nsSocketTransport::InitWithFilename(const char *filename) { #if defined(XP_UNIX) size_t filenameLength = strlen(filename); if (filenameLength > sizeof(mNetAddr.local.path) - 1) return NS_ERROR_FILE_NAME_TOO_LONG; @@ -1010,16 +1028,21 @@ nsSocketTransport::ResolveHost() { SOCKET_LOG(("nsSocketTransport::ResolveHost [this=%p %s:%d%s]\n", this, SocketHost().get(), SocketPort(), mConnectionFlags & nsSocketTransport::BYPASS_CACHE ? " bypass cache" : "")); nsresult rv; + if (mNetAddrPreResolved) { + mState = STATE_RESOLVING; + return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr); + } + if (!mProxyHost.IsEmpty()) { if (!mProxyTransparent || mProxyTransparentResolvesHost) { #if defined(XP_UNIX) MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL, "Unix domain sockets can't be used with proxies"); #endif // When not resolving mHost locally, we still want to ensure that // it only contains valid characters. See bug 304904 for details. @@ -1872,18 +1895,19 @@ nsSocketTransport::OnSocketEvent(uint32_ // transport resolves the real host here, so there's no fixup // (see bug 226943). if ((status == NS_ERROR_UNKNOWN_HOST) && !mProxyTransparent && !mProxyHost.IsEmpty()) mCondition = NS_ERROR_UNKNOWN_PROXY_HOST; else mCondition = status; } - else if (mState == STATE_RESOLVING) + else if (mState == STATE_RESOLVING) { mCondition = InitiateSocket(); + } break; case MSG_RETRY_INIT_SOCKET: mCondition = InitiateSocket(); break; case MSG_INPUT_CLOSED: SOCKET_LOG((" MSG_INPUT_CLOSED\n"));
--- a/netwerk/base/nsSocketTransport2.h +++ b/netwerk/base/nsSocketTransport2.h @@ -128,16 +128,25 @@ public: // this method instructs the socket transport to open a socket of the // given type(s) to the given host or proxy. nsresult Init(const char **socketTypes, uint32_t typeCount, const nsACString &host, uint16_t port, const nsACString &hostRoute, uint16_t portRoute, nsIProxyInfo *proxyInfo); + // Alternative Init method for when the IP-address of the host + // has been pre-resolved using a alternative means (e.g. FlyWeb service + // info). + nsresult InitPreResolved(const char **socketTypes, uint32_t typeCount, + const nsACString &host, uint16_t port, + const nsACString &hostRoute, uint16_t portRoute, + nsIProxyInfo *proxyInfo, + const mozilla::net::NetAddr* addr); + // this method instructs the socket transport to use an already connected // socket with the given address. nsresult InitWithConnectedSocket(PRFileDesc *socketFD, const NetAddr *addr); // this method instructs the socket transport to use an already connected // socket with the given address, and additionally supplies security info. nsresult InitWithConnectedSocket(PRFileDesc* aSocketFD, @@ -321,16 +330,17 @@ private: // mNetAddr/mSelfAddr is valid from GetPeerAddr()/GetSelfAddr() once we have // reached STATE_TRANSFERRING. It must not change after that. void SetSocketName(PRFileDesc *fd); NetAddr mNetAddr; NetAddr mSelfAddr; // getsockname() Atomic<bool, Relaxed> mNetAddrIsSet; Atomic<bool, Relaxed> mSelfAddrIsSet; + Atomic<bool, Relaxed> mNetAddrPreResolved; nsAutoPtr<NetAddr> mBindAddr; // socket methods (these can only be called on the socket thread): void SendStatus(nsresult status); nsresult ResolveHost(); nsresult BuildSocket(PRFileDesc *&, bool &, bool &);
--- a/netwerk/base/nsSocketTransportService2.cpp +++ b/netwerk/base/nsSocketTransportService2.cpp @@ -21,16 +21,17 @@ #include "mozilla/Likely.h" #include "mozilla/PublicSSL.h" #include "mozilla/ChaosMode.h" #include "mozilla/PodOperations.h" #include "mozilla/Telemetry.h" #include "nsThreadUtils.h" #include "nsIFile.h" #include "nsIWidget.h" +#include "mozilla/dom/FlyWebService.h" #if defined(XP_WIN) #include "mozilla/WindowsVersion.h" #endif namespace mozilla { namespace net { @@ -720,16 +721,30 @@ nsSocketTransportService::CreateRoutedTr uint32_t typeCount, const nsACString &host, int32_t port, const nsACString &hostRoute, int32_t portRoute, nsIProxyInfo *proxyInfo, nsISocketTransport **result) { + // Check FlyWeb table for host mappings. If one exists, then use that. + RefPtr<mozilla::dom::FlyWebService> fws = + mozilla::dom::FlyWebService::GetExisting(); + if (fws) { + nsresult rv = fws->CreateTransportForHost(types, typeCount, host, port, + hostRoute, portRoute, + proxyInfo, result); + NS_ENSURE_SUCCESS(rv, rv); + + if (*result) { + return NS_OK; + } + } + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); NS_ENSURE_TRUE(port >= 0 && port <= 0xFFFF, NS_ERROR_ILLEGAL_VALUE); RefPtr<nsSocketTransport> trans = new nsSocketTransport(); nsresult rv = trans->Init(types, typeCount, host, port, hostRoute, portRoute, proxyInfo); if (NS_FAILED(rv)) { return rv; }