diff options
Diffstat (limited to 'dom/flyweb/FlyWebService.cpp')
-rw-r--r-- | dom/flyweb/FlyWebService.cpp | 1310 |
1 files changed, 1310 insertions, 0 deletions
diff --git a/dom/flyweb/FlyWebService.cpp b/dom/flyweb/FlyWebService.cpp new file mode 100644 index 000000000..5f3b0d66f --- /dev/null +++ b/dom/flyweb/FlyWebService.cpp @@ -0,0 +1,1310 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/FlyWebService.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/FlyWebPublishedServerIPC.h" +#include "mozilla/AddonPathService.h" +#include "nsISocketTransportService.h" +#include "mdns/libmdns/nsDNSServiceInfo.h" +#include "nsIUUIDGenerator.h" +#include "nsStandardURL.h" +#include "mozilla/Services.h" +#include "nsISupportsPrimitives.h" +#include "mozilla/dom/FlyWebDiscoveryManagerBinding.h" +#include "prnetdb.h" +#include "DNS.h" +#include "nsContentPermissionHelper.h" +#include "nsSocketTransportService2.h" +#include "nsSocketTransport2.h" +#include "nsHashPropertyBag.h" +#include "nsNetUtil.h" +#include "nsISimpleEnumerator.h" +#include "nsIProperty.h" +#include "nsICertOverrideService.h" + +namespace mozilla { +namespace dom { + +struct FlyWebPublishOptions; + +static LazyLogModule gFlyWebServiceLog("FlyWebService"); +#undef LOG_I +#define LOG_I(...) MOZ_LOG(mozilla::dom::gFlyWebServiceLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +#undef LOG_E +#define LOG_E(...) MOZ_LOG(mozilla::dom::gFlyWebServiceLog, mozilla::LogLevel::Error, (__VA_ARGS__)) + +#undef LOG_TEST_I +#define LOG_TEST_I(...) MOZ_LOG_TEST(mozilla::dom::gFlyWebServiceLog, mozilla::LogLevel::Debug) + +class FlyWebPublishServerPermissionCheck final + : public nsIContentPermissionRequest + , public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + FlyWebPublishServerPermissionCheck(const nsCString& aServiceName, uint64_t aWindowID, + FlyWebPublishedServer* aServer) + : mServiceName(aServiceName) + , mWindowID(aWindowID) + , mServer(aServer) + {} + + uint64_t WindowID() const + { + return mWindowID; + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + + nsGlobalWindow* globalWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID); + if (!globalWindow) { + return Cancel(); + } + mWindow = globalWindow->AsInner(); + if (NS_WARN_IF(!mWindow)) { + return Cancel(); + } + + nsCOMPtr<nsIDocument> doc = mWindow->GetDoc(); + if (NS_WARN_IF(!doc)) { + return Cancel(); + } + + mPrincipal = doc->NodePrincipal(); + MOZ_ASSERT(mPrincipal); + + mRequester = new nsContentPermissionRequester(mWindow); + return nsContentPermissionUtils::AskPermission(this, mWindow); + } + + NS_IMETHOD Cancel() override + { + Resolve(false); + return NS_OK; + } + + NS_IMETHOD Allow(JS::HandleValue aChoices) override + { + MOZ_ASSERT(aChoices.isUndefined()); + Resolve(true); + return NS_OK; + } + + NS_IMETHOD GetTypes(nsIArray** aTypes) override + { + nsTArray<nsString> emptyOptions; + return nsContentPermissionUtils::CreatePermissionArray(NS_LITERAL_CSTRING("flyweb-publish-server"), + NS_LITERAL_CSTRING("unused"), emptyOptions, aTypes); + } + + NS_IMETHOD GetRequester(nsIContentPermissionRequester** aRequester) override + { + NS_ENSURE_ARG_POINTER(aRequester); + nsCOMPtr<nsIContentPermissionRequester> requester = mRequester; + requester.forget(aRequester); + return NS_OK; + } + + NS_IMETHOD GetPrincipal(nsIPrincipal** aRequestingPrincipal) override + { + NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal); + return NS_OK; + } + + NS_IMETHOD GetWindow(mozIDOMWindow** aRequestingWindow) override + { + NS_IF_ADDREF(*aRequestingWindow = mWindow); + return NS_OK; + } + + NS_IMETHOD GetElement(nsIDOMElement** aRequestingElement) override + { + *aRequestingElement = nullptr; + return NS_OK; + } + +private: + void Resolve(bool aResolve) + { + mServer->PermissionGranted(aResolve); + } + + virtual ~FlyWebPublishServerPermissionCheck() = default; + + nsCString mServiceName; + uint64_t mWindowID; + RefPtr<FlyWebPublishedServer> mServer; + nsCOMPtr<nsPIDOMWindowInner> mWindow; + nsCOMPtr<nsIPrincipal> mPrincipal; + nsCOMPtr<nsIContentPermissionRequester> mRequester; +}; + +NS_IMPL_ISUPPORTS(FlyWebPublishServerPermissionCheck, + nsIContentPermissionRequest, + nsIRunnable) + +class FlyWebMDNSService final + : public nsIDNSServiceDiscoveryListener + , public nsIDNSServiceResolveListener + , public nsIDNSRegistrationListener + , public nsITimerCallback +{ + friend class FlyWebService; + +private: + enum DiscoveryState { + DISCOVERY_IDLE, + DISCOVERY_STARTING, + DISCOVERY_RUNNING, + DISCOVERY_STOPPING + }; + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDNSSERVICEDISCOVERYLISTENER + NS_DECL_NSIDNSSERVICERESOLVELISTENER + NS_DECL_NSIDNSREGISTRATIONLISTENER + NS_DECL_NSITIMERCALLBACK + + explicit FlyWebMDNSService(FlyWebService* aService, + const nsACString& aServiceType); + +private: + virtual ~FlyWebMDNSService() = default; + + nsresult Init(); + nsresult StartDiscovery(); + nsresult StopDiscovery(); + + void ListDiscoveredServices(nsTArray<FlyWebDiscoveredService>& aServices); + bool HasService(const nsAString& aServiceId); + nsresult PairWithService(const nsAString& aServiceId, + UniquePtr<FlyWebService::PairedInfo>& aInfo); + + nsresult StartDiscoveryOf(FlyWebPublishedServerImpl* aServer); + + void EnsureDiscoveryStarted(); + void EnsureDiscoveryStopped(); + + // Cycle-breaking link to manager. + FlyWebService* mService; + nsCString mServiceType; + + // Indicates the desired state of the system. If mDiscoveryActive is true, + // it indicates that backend discovery "should be happening", and discovery + // events should be forwarded to listeners. + // If false, the backend discovery "should be idle", and any discovery events + // that show up should not be forwarded to listeners. + bool mDiscoveryActive; + + uint32_t mNumConsecutiveStartDiscoveryFailures; + + // Represents the internal discovery state as it relates to nsDNSServiceDiscovery. + // When mDiscoveryActive is true, this state will periodically loop from + // (IDLE => STARTING => RUNNING => STOPPING => IDLE). + DiscoveryState mDiscoveryState; + + nsCOMPtr<nsITimer> mDiscoveryStartTimer; + nsCOMPtr<nsITimer> mDiscoveryStopTimer; + nsCOMPtr<nsIDNSServiceDiscovery> mDNSServiceDiscovery; + nsCOMPtr<nsICancelable> mCancelDiscovery; + nsTHashtable<nsStringHashKey> mNewServiceSet; + + struct DiscoveredInfo + { + explicit DiscoveredInfo(nsIDNSServiceInfo* aDNSServiceInfo); + FlyWebDiscoveredService mService; + nsCOMPtr<nsIDNSServiceInfo> mDNSServiceInfo; + }; + nsClassHashtable<nsStringHashKey, DiscoveredInfo> mServiceMap; +}; + +void +LogDNSInfo(nsIDNSServiceInfo* aServiceInfo, const char* aFunc) +{ + if (!LOG_TEST_I()) { + return; + } + + nsCString tmp; + aServiceInfo->GetServiceName(tmp); + LOG_I("%s: serviceName=%s", aFunc, tmp.get()); + + aServiceInfo->GetHost(tmp); + LOG_I("%s: host=%s", aFunc, tmp.get()); + + aServiceInfo->GetAddress(tmp); + LOG_I("%s: address=%s", aFunc, tmp.get()); + + uint16_t port = -2; + aServiceInfo->GetPort(&port); + LOG_I("%s: port=%d", aFunc, (int)port); + + nsCOMPtr<nsIPropertyBag2> attributes; + aServiceInfo->GetAttributes(getter_AddRefs(attributes)); + if (!attributes) { + LOG_I("%s: no attributes", aFunc); + } else { + nsCOMPtr<nsISimpleEnumerator> enumerator; + attributes->GetEnumerator(getter_AddRefs(enumerator)); + MOZ_ASSERT(enumerator); + + LOG_I("%s: attributes start", aFunc); + + bool hasMoreElements; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) && + hasMoreElements) { + nsCOMPtr<nsISupports> element; + MOZ_ALWAYS_SUCCEEDS(enumerator->GetNext(getter_AddRefs(element))); + nsCOMPtr<nsIProperty> property = do_QueryInterface(element); + MOZ_ASSERT(property); + + nsAutoString name; + nsCOMPtr<nsIVariant> value; + MOZ_ALWAYS_SUCCEEDS(property->GetName(name)); + MOZ_ALWAYS_SUCCEEDS(property->GetValue(getter_AddRefs(value))); + + nsAutoCString str; + nsresult rv = value->GetAsACString(str); + if (NS_SUCCEEDED(rv)) { + LOG_I("%s: attribute name=%s value=%s", aFunc, + NS_ConvertUTF16toUTF8(name).get(), str.get()); + } else { + uint16_t type; + MOZ_ALWAYS_SUCCEEDS(value->GetDataType(&type)); + LOG_I("%s: attribute *unstringifiable* name=%s type=%d", aFunc, + NS_ConvertUTF16toUTF8(name).get(), (int)type); + } + } + + LOG_I("%s: attributes end", aFunc); + } +} + +NS_IMPL_ISUPPORTS(FlyWebMDNSService, + nsIDNSServiceDiscoveryListener, + nsIDNSServiceResolveListener, + nsIDNSRegistrationListener, + nsITimerCallback) + +FlyWebMDNSService::FlyWebMDNSService( + FlyWebService* aService, + const nsACString& aServiceType) + : mService(aService) + , mServiceType(aServiceType) + , mDiscoveryActive(false) + , mNumConsecutiveStartDiscoveryFailures(0) + , mDiscoveryState(DISCOVERY_IDLE) +{} + +nsresult +FlyWebMDNSService::OnDiscoveryStarted(const nsACString& aServiceType) +{ + MOZ_ASSERT(mDiscoveryState == DISCOVERY_STARTING); + mDiscoveryState = DISCOVERY_RUNNING; + // Reset consecutive start discovery failures. + mNumConsecutiveStartDiscoveryFailures = 0; + LOG_I("==========================================="); + LOG_I("MDNSService::OnDiscoveryStarted(%s)", PromiseFlatCString(aServiceType).get()); + LOG_I("==========================================="); + + // Clear the new service array. + mNewServiceSet.Clear(); + + // If service discovery is inactive, then stop network discovery immediately. + if (!mDiscoveryActive) { + // Set the stop timer to fire immediately. + Unused << NS_WARN_IF(NS_FAILED(mDiscoveryStopTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT))); + return NS_OK; + } + + // Otherwise, set the stop timer to fire in 5 seconds. + Unused << NS_WARN_IF(NS_FAILED(mDiscoveryStopTimer->InitWithCallback(this, 5 * 1000, nsITimer::TYPE_ONE_SHOT))); + + return NS_OK; +} + +nsresult +FlyWebMDNSService::OnDiscoveryStopped(const nsACString& aServiceType) +{ + LOG_I("///////////////////////////////////////////"); + LOG_I("MDNSService::OnDiscoveryStopped(%s)", PromiseFlatCString(aServiceType).get()); + LOG_I("///////////////////////////////////////////"); + MOZ_ASSERT(mDiscoveryState == DISCOVERY_STOPPING); + mDiscoveryState = DISCOVERY_IDLE; + + // If service discovery is inactive, then discard all results and do not proceed. + if (!mDiscoveryActive) { + mServiceMap.Clear(); + mNewServiceSet.Clear(); + return NS_OK; + } + + // Process the service map, add to the pair map. + for (auto iter = mServiceMap.Iter(); !iter.Done(); iter.Next()) { + DiscoveredInfo* service = iter.UserData(); + + if (!mNewServiceSet.Contains(service->mService.mServiceId)) { + iter.Remove(); + } + } + + // Notify FlyWebService of changed service list. + mService->NotifyDiscoveredServicesChanged(); + + // Start discovery again immediately. + Unused << NS_WARN_IF(NS_FAILED(mDiscoveryStartTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT))); + + return NS_OK; +} + +nsresult +FlyWebMDNSService::OnServiceFound(nsIDNSServiceInfo* aServiceInfo) +{ + LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceFound"); + + // If discovery is not active, don't do anything with the result. + // If there is no discovery underway, ignore this. + if (!mDiscoveryActive || mDiscoveryState != DISCOVERY_RUNNING) { + return NS_OK; + } + + // Discovery is underway - resolve the service. + nsresult rv = mDNSServiceDiscovery->ResolveService(aServiceInfo, this); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +FlyWebMDNSService::OnServiceLost(nsIDNSServiceInfo* aServiceInfo) +{ + LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceLost"); + + return NS_OK; +} + +nsresult +FlyWebMDNSService::OnStartDiscoveryFailed(const nsACString& aServiceType, int32_t aErrorCode) +{ + LOG_E("MDNSService::OnStartDiscoveryFailed(%s): %d", PromiseFlatCString(aServiceType).get(), (int) aErrorCode); + + MOZ_ASSERT(mDiscoveryState == DISCOVERY_STARTING); + mDiscoveryState = DISCOVERY_IDLE; + mNumConsecutiveStartDiscoveryFailures++; + + // If discovery is active, and the number of consecutive failures is < 3, try starting again. + if (mDiscoveryActive && mNumConsecutiveStartDiscoveryFailures < 3) { + Unused << NS_WARN_IF(NS_FAILED(mDiscoveryStartTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT))); + } + + return NS_OK; +} + +nsresult +FlyWebMDNSService::OnStopDiscoveryFailed(const nsACString& aServiceType, int32_t aErrorCode) +{ + LOG_E("MDNSService::OnStopDiscoveryFailed(%s)", PromiseFlatCString(aServiceType).get()); + MOZ_ASSERT(mDiscoveryState == DISCOVERY_STOPPING); + mDiscoveryState = DISCOVERY_IDLE; + + // If discovery is active, start discovery again immediately. + if (mDiscoveryActive) { + Unused << NS_WARN_IF(NS_FAILED(mDiscoveryStartTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT))); + } + + return NS_OK; +} + +static bool +IsAcceptableServiceAddress(const nsCString& addr) +{ + PRNetAddr prNetAddr; + PRStatus status = PR_StringToNetAddr(addr.get(), &prNetAddr); + if (status == PR_FAILURE) { + return false; + } + // Only allow ipv4 addreses for now. + return prNetAddr.raw.family == PR_AF_INET; +} + +nsresult +FlyWebMDNSService::OnServiceResolved(nsIDNSServiceInfo* aServiceInfo) +{ + LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceResolved"); + + // If discovery is not active, don't do anything with the result. + // If there is no discovery underway, ignore this resolve. + if (!mDiscoveryActive || mDiscoveryState != DISCOVERY_RUNNING) { + return NS_OK; + } + + nsresult rv; + + nsCString address; + rv = aServiceInfo->GetAddress(address); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!IsAcceptableServiceAddress(address)) { + return NS_OK; + } + + // Create a new serviceInfo and stuff it in the new service array. + UniquePtr<DiscoveredInfo> svc(new DiscoveredInfo(aServiceInfo)); + mNewServiceSet.PutEntry(svc->mService.mServiceId); + + DiscoveredInfo* existingSvc = + mServiceMap.Get(svc->mService.mServiceId); + if (existingSvc) { + // Update the underlying DNS service info, but leave the old object in place. + existingSvc->mDNSServiceInfo = aServiceInfo; + } else { + DiscoveredInfo* info = svc.release(); + mServiceMap.Put(info->mService.mServiceId, info); + } + + // Notify FlyWebService of changed service list. + mService->NotifyDiscoveredServicesChanged(); + + return NS_OK; +} + +FlyWebMDNSService::DiscoveredInfo::DiscoveredInfo(nsIDNSServiceInfo* aDNSServiceInfo) + : mDNSServiceInfo(aDNSServiceInfo) +{ + nsCString tmp; + DebugOnly<nsresult> drv = aDNSServiceInfo->GetServiceName(tmp); + MOZ_ASSERT(NS_SUCCEEDED(drv)); + CopyUTF8toUTF16(tmp, mService.mDisplayName); + + mService.mTransport = NS_LITERAL_STRING("mdns"); + + drv = aDNSServiceInfo->GetServiceType(tmp); + MOZ_ASSERT(NS_SUCCEEDED(drv)); + CopyUTF8toUTF16(tmp, mService.mServiceType); + + nsCOMPtr<nsIPropertyBag2> attrs; + drv = aDNSServiceInfo->GetAttributes(getter_AddRefs(attrs)); + MOZ_ASSERT(NS_SUCCEEDED(drv)); + if (attrs) { + attrs->GetPropertyAsAString(NS_LITERAL_STRING("cert"), mService.mCert); + attrs->GetPropertyAsAString(NS_LITERAL_STRING("path"), mService.mPath); + } + + // Construct a service id from the name, host, address, and port. + nsCString cHost; + drv = aDNSServiceInfo->GetHost(cHost); + MOZ_ASSERT(NS_SUCCEEDED(drv)); + + nsCString cAddress; + drv = aDNSServiceInfo->GetAddress(cAddress); + MOZ_ASSERT(NS_SUCCEEDED(drv)); + + uint16_t port; + drv = aDNSServiceInfo->GetPort(&port); + MOZ_ASSERT(NS_SUCCEEDED(drv)); + nsAutoString portStr; + portStr.AppendInt(port, 10); + + mService.mServiceId = + NS_ConvertUTF8toUTF16(cAddress) + + NS_LITERAL_STRING(":") + + portStr + + NS_LITERAL_STRING("|") + + mService.mServiceType + + NS_LITERAL_STRING("|") + + NS_ConvertUTF8toUTF16(cHost) + + NS_LITERAL_STRING("|") + + mService.mDisplayName; +} + + +nsresult +FlyWebMDNSService::OnResolveFailed(nsIDNSServiceInfo* aServiceInfo, int32_t aErrorCode) +{ + LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnResolveFailed"); + + return NS_OK; +} + +nsresult +FlyWebMDNSService::OnServiceRegistered(nsIDNSServiceInfo* aServiceInfo) +{ + LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceRegistered"); + + nsCString cName; + if (NS_WARN_IF(NS_FAILED(aServiceInfo->GetServiceName(cName)))) { + return NS_ERROR_FAILURE; + } + + nsString name = NS_ConvertUTF8toUTF16(cName); + RefPtr<FlyWebPublishedServer> existingServer = + FlyWebService::GetOrCreate()->FindPublishedServerByName(name); + if (!existingServer) { + return NS_ERROR_FAILURE; + } + + existingServer->PublishedServerStarted(NS_OK); + + return NS_OK; +} + +nsresult +FlyWebMDNSService::OnServiceUnregistered(nsIDNSServiceInfo* aServiceInfo) +{ + LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceUnregistered"); + + nsCString cName; + if (NS_WARN_IF(NS_FAILED(aServiceInfo->GetServiceName(cName)))) { + return NS_ERROR_FAILURE; + } + + nsString name = NS_ConvertUTF8toUTF16(cName); + RefPtr<FlyWebPublishedServer> existingServer = + FlyWebService::GetOrCreate()->FindPublishedServerByName(name); + if (!existingServer) { + return NS_ERROR_FAILURE; + } + + LOG_I("OnServiceRegistered(MDNS): De-advertised server with name %s.", cName.get()); + + return NS_OK; +} + +nsresult +FlyWebMDNSService::OnRegistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t errorCode) +{ + LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnRegistrationFailed"); + + nsCString cName; + if (NS_WARN_IF(NS_FAILED(aServiceInfo->GetServiceName(cName)))) { + return NS_ERROR_FAILURE; + } + + nsString name = NS_ConvertUTF8toUTF16(cName); + RefPtr<FlyWebPublishedServer> existingServer = + FlyWebService::GetOrCreate()->FindPublishedServerByName(name); + if (!existingServer) { + return NS_ERROR_FAILURE; + } + + LOG_I("OnServiceRegistered(MDNS): Registration of server with name %s failed.", cName.get()); + + // Remove the nsICancelable from the published server. + existingServer->PublishedServerStarted(NS_ERROR_FAILURE); + return NS_OK; +} + +nsresult +FlyWebMDNSService::OnUnregistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t errorCode) +{ + LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnUnregistrationFailed"); + + nsCString cName; + if (NS_WARN_IF(NS_FAILED(aServiceInfo->GetServiceName(cName)))) { + return NS_ERROR_FAILURE; + } + + nsString name = NS_ConvertUTF8toUTF16(cName); + RefPtr<FlyWebPublishedServer> existingServer = + FlyWebService::GetOrCreate()->FindPublishedServerByName(name); + if (!existingServer) { + return NS_ERROR_FAILURE; + } + + LOG_I("OnServiceRegistered(MDNS): Un-Advertisement of server with name %s failed.", cName.get()); + return NS_OK; +} + +nsresult +FlyWebMDNSService::Notify(nsITimer* timer) +{ + if (timer == mDiscoveryStopTimer.get()) { + LOG_I("MDNSService::Notify() got discovery stop timeout"); + // Internet discovery stop timer has fired. + nsresult rv = StopDiscovery(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + } + + if (timer == mDiscoveryStartTimer.get()) { + LOG_I("MDNSService::Notify() got discovery start timeout"); + // Internet discovery start timer has fired. + nsresult rv = StartDiscovery(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + } + + LOG_E("MDNSService::Notify got unknown timeout."); + return NS_OK; +} + +nsresult +FlyWebMDNSService::Init() +{ + MOZ_ASSERT(mDiscoveryState == DISCOVERY_IDLE); + + mDiscoveryStartTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (!mDiscoveryStartTimer) { + return NS_ERROR_FAILURE; + } + + mDiscoveryStopTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (!mDiscoveryStopTimer) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + mDNSServiceDiscovery = do_GetService(DNSSERVICEDISCOVERY_CONTRACT_ID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +FlyWebMDNSService::StartDiscovery() +{ + nsresult rv; + + // Always cancel the timer. + rv = mDiscoveryStartTimer->Cancel(); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG_E("FlyWeb failed to cancel DNS service discovery start timer."); + } + + // If discovery is not idle, don't start it. + if (mDiscoveryState != DISCOVERY_IDLE) { + return NS_OK; + } + + LOG_I("FlyWeb starting dicovery."); + mDiscoveryState = DISCOVERY_STARTING; + + // start the discovery. + rv = mDNSServiceDiscovery->StartDiscovery(mServiceType, this, + getter_AddRefs(mCancelDiscovery)); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG_E("FlyWeb failed to start DNS service discovery."); + return rv; + } + + return NS_OK; +} + +nsresult +FlyWebMDNSService::StopDiscovery() +{ + nsresult rv; + + // Always cancel the timer. + rv = mDiscoveryStopTimer->Cancel(); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG_E("FlyWeb failed to cancel DNS service discovery stop timer."); + } + + // If discovery is not running, do nothing. + if (mDiscoveryState != DISCOVERY_RUNNING) { + return NS_OK; + } + + LOG_I("FlyWeb stopping dicovery."); + + // Mark service discovery as stopping. + mDiscoveryState = DISCOVERY_STOPPING; + + if (mCancelDiscovery) { + LOG_I("MDNSService::StopDiscovery() - mCancelDiscovery exists!"); + nsCOMPtr<nsICancelable> cancelDiscovery = mCancelDiscovery.forget(); + rv = cancelDiscovery->Cancel(NS_ERROR_ABORT); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG_E("FlyWeb failed to cancel DNS stop service discovery."); + } + } else { + LOG_I("MDNSService::StopDiscovery() - mCancelDiscovery does not exist!"); + mDiscoveryState = DISCOVERY_IDLE; + } + + return NS_OK; +} + +void +FlyWebMDNSService::ListDiscoveredServices(nsTArray<FlyWebDiscoveredService>& aServices) +{ + for (auto iter = mServiceMap.Iter(); !iter.Done(); iter.Next()) { + aServices.AppendElement(iter.UserData()->mService); + } +} + +bool +FlyWebMDNSService::HasService(const nsAString& aServiceId) +{ + return mServiceMap.Contains(aServiceId); +} + +nsresult +FlyWebMDNSService::PairWithService(const nsAString& aServiceId, + UniquePtr<FlyWebService::PairedInfo>& aInfo) +{ + MOZ_ASSERT(HasService(aServiceId)); + + nsresult rv; + nsCOMPtr<nsIUUIDGenerator> uuidgen = + do_GetService("@mozilla.org/uuid-generator;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsID id; + rv = uuidgen->GenerateUUIDInPlace(&id); + NS_ENSURE_SUCCESS(rv, rv); + + aInfo.reset(new FlyWebService::PairedInfo()); + + char uuidChars[NSID_LENGTH]; + id.ToProvidedString(uuidChars); + CopyUTF8toUTF16(Substring(uuidChars + 1, uuidChars + NSID_LENGTH - 2), + aInfo->mService.mHostname); + + DiscoveredInfo* discInfo = mServiceMap.Get(aServiceId); + + nsAutoString url; + if (discInfo->mService.mCert.IsEmpty()) { + url.AssignLiteral("http://"); + } else { + url.AssignLiteral("https://"); + } + url.Append(aInfo->mService.mHostname + NS_LITERAL_STRING("/")); + nsCOMPtr<nsIURI> uiURL; + NS_NewURI(getter_AddRefs(uiURL), url); + MOZ_ASSERT(uiURL); + if (!discInfo->mService.mPath.IsEmpty()) { + nsCOMPtr<nsIURI> tmp = uiURL.forget(); + NS_NewURI(getter_AddRefs(uiURL), discInfo->mService.mPath, nullptr, tmp); + } + if (uiURL) { + nsAutoCString spec; + uiURL->GetSpec(spec); + CopyUTF8toUTF16(spec, aInfo->mService.mUiUrl); + } + + aInfo->mService.mDiscoveredService = discInfo->mService; + aInfo->mDNSServiceInfo = discInfo->mDNSServiceInfo; + + return NS_OK; +} + +nsresult +FlyWebMDNSService::StartDiscoveryOf(FlyWebPublishedServerImpl* aServer) +{ + + RefPtr<FlyWebPublishedServer> existingServer = + FlyWebService::GetOrCreate()->FindPublishedServerByName(aServer->Name()); + MOZ_ASSERT(existingServer); + + // Advertise the service via mdns. + RefPtr<net::nsDNSServiceInfo> serviceInfo(new net::nsDNSServiceInfo()); + + serviceInfo->SetPort(aServer->Port()); + serviceInfo->SetServiceType(mServiceType); + + nsCString certKey; + aServer->GetCertKey(certKey); + nsString uiURL; + aServer->GetUiUrl(uiURL); + + if (!uiURL.IsEmpty() || !certKey.IsEmpty()) { + RefPtr<nsHashPropertyBag> attrs = new nsHashPropertyBag(); + if (!uiURL.IsEmpty()) { + attrs->SetPropertyAsAString(NS_LITERAL_STRING("path"), uiURL); + } + if (!certKey.IsEmpty()) { + attrs->SetPropertyAsACString(NS_LITERAL_STRING("cert"), certKey); + } + serviceInfo->SetAttributes(attrs); + } + + nsCString cstrName = NS_ConvertUTF16toUTF8(aServer->Name()); + LOG_I("MDNSService::StartDiscoveryOf() advertising service %s", cstrName.get()); + serviceInfo->SetServiceName(cstrName); + + LogDNSInfo(serviceInfo, "FlyWebMDNSService::StartDiscoveryOf"); + + // Advertise the service. + nsCOMPtr<nsICancelable> cancelRegister; + nsresult rv = mDNSServiceDiscovery-> + RegisterService(serviceInfo, this, getter_AddRefs(cancelRegister)); + NS_ENSURE_SUCCESS(rv, rv); + + // All done. + aServer->SetCancelRegister(cancelRegister); + + return NS_OK; +} + +void +FlyWebMDNSService::EnsureDiscoveryStarted() +{ + mDiscoveryActive = true; + // If state is idle, start discovery immediately. + if (mDiscoveryState == DISCOVERY_IDLE) { + StartDiscovery(); + } +} + +void +FlyWebMDNSService::EnsureDiscoveryStopped() +{ + // All we need to do is set the flag to false. + // If current state is IDLE, it's already the correct state. + // Otherwise, the handlers for the internal state + // transitions will check this flag and drive the state + // towards IDLE. + mDiscoveryActive = false; +} + +static StaticRefPtr<FlyWebService> gFlyWebService; + +NS_IMPL_ISUPPORTS(FlyWebService, nsIObserver) + +FlyWebService::FlyWebService() + : mMonitor("FlyWebService::mMonitor") +{ + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "inner-window-destroyed", false); + } +} + +FlyWebService::~FlyWebService() +{ +} + +FlyWebService* +FlyWebService::GetExisting() +{ + return gFlyWebService; +} + +FlyWebService* +FlyWebService::GetOrCreate() +{ + if (!gFlyWebService) { + gFlyWebService = new FlyWebService(); + ClearOnShutdown(&gFlyWebService); + ErrorResult rv = gFlyWebService->Init(); + if (rv.Failed()) { + gFlyWebService = nullptr; + return nullptr; + } + } + return gFlyWebService; +} + +ErrorResult +FlyWebService::Init() +{ + // Most functions of FlyWebService should not be started in the child. + // Instead FlyWebService in the child is mainly responsible for tracking + // publishedServer lifetimes. Other functions are handled by the + // FlyWebService running in the parent. + if (XRE_GetProcessType() == GeckoProcessType_Content) { + return ErrorResult(NS_OK); + } + + MOZ_ASSERT(NS_IsMainThread()); + if (!mMDNSHttpService) { + mMDNSHttpService = new FlyWebMDNSService(this, NS_LITERAL_CSTRING("_http._tcp.")); + ErrorResult rv; + + rv = mMDNSHttpService->Init(); + if (rv.Failed()) { + LOG_E("FlyWebService failed to initialize MDNS _http._tcp."); + mMDNSHttpService = nullptr; + rv.SuppressException(); + } + } + + if (!mMDNSFlywebService) { + mMDNSFlywebService = new FlyWebMDNSService(this, NS_LITERAL_CSTRING("_flyweb._tcp.")); + ErrorResult rv; + + rv = mMDNSFlywebService->Init(); + if (rv.Failed()) { + LOG_E("FlyWebService failed to initialize MDNS _flyweb._tcp."); + mMDNSFlywebService = nullptr; + rv.SuppressException(); + } + } + + return ErrorResult(NS_OK); +} + +static already_AddRefed<FlyWebPublishPromise> +MakeRejectionPromise(const char* name) +{ + MozPromiseHolder<FlyWebPublishPromise> holder; + RefPtr<FlyWebPublishPromise> promise = holder.Ensure(name); + holder.Reject(NS_ERROR_FAILURE, name); + return promise.forget(); +} + +static bool +CheckForFlyWebAddon(const nsACString& uriString) +{ + // Before proceeding, ensure that the FlyWeb system addon exists. + nsresult rv; + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), uriString); + if (NS_FAILED(rv)) { + return false; + } + + JSAddonId *addonId = MapURIToAddonID(uri); + if (!addonId) { + return false; + } + + JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(addonId)); + nsAutoString addonIdString; + AssignJSFlatString(addonIdString, flat); + if (!addonIdString.EqualsLiteral("flyweb@mozilla.org")) { + nsCString addonIdCString = NS_ConvertUTF16toUTF8(addonIdString); + return false; + } + + return true; +} + +already_AddRefed<FlyWebPublishPromise> +FlyWebService::PublishServer(const nsAString& aName, + const FlyWebPublishOptions& aOptions, + nsPIDOMWindowInner* aWindow) +{ + // Scan uiUrl for illegal characters + + RefPtr<FlyWebPublishedServer> existingServer = + FlyWebService::GetOrCreate()->FindPublishedServerByName(aName); + if (existingServer) { + LOG_I("PublishServer: Trying to publish server with already-existing name %s.", + NS_ConvertUTF16toUTF8(aName).get()); + return MakeRejectionPromise(__func__); + } + + RefPtr<FlyWebPublishedServer> server; + if (XRE_GetProcessType() == GeckoProcessType_Content) { + server = new FlyWebPublishedServerChild(aWindow, aName, aOptions); + } else { + server = new FlyWebPublishedServerImpl(aWindow, aName, aOptions); + + // Before proceeding, ensure that the FlyWeb system addon exists. + if (!CheckForFlyWebAddon(NS_LITERAL_CSTRING("chrome://flyweb/skin/icon-64.png")) && + !CheckForFlyWebAddon(NS_LITERAL_CSTRING("chrome://flyweb/content/icon-64.png"))) + { + LOG_E("PublishServer: Failed to find FlyWeb system addon."); + return MakeRejectionPromise(__func__); + } + } + + if (aWindow) { + nsresult rv; + + MOZ_ASSERT(NS_IsMainThread()); + rv = NS_DispatchToCurrentThread( + MakeAndAddRef<FlyWebPublishServerPermissionCheck>( + NS_ConvertUTF16toUTF8(aName), aWindow->WindowID(), server)); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG_E("PublishServer: Failed to dispatch permission check runnable for %s", + NS_ConvertUTF16toUTF8(aName).get()); + return MakeRejectionPromise(__func__); + } + } else { + // If aWindow is null, we're definitely in the e10s parent process. + // In this case, we know that permission has already been granted + // by the user because of content-process prompt. + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + server->PermissionGranted(true); + } + + mServers.AppendElement(server); + + return server->GetPublishPromise(); +} + +already_AddRefed<FlyWebPublishedServer> +FlyWebService::FindPublishedServerByName( + const nsAString& aName) +{ + MOZ_ASSERT(NS_IsMainThread()); + for (FlyWebPublishedServer* publishedServer : mServers) { + if (publishedServer->Name().Equals(aName)) { + RefPtr<FlyWebPublishedServer> server = publishedServer; + return server.forget(); + } + } + return nullptr; +} + +void +FlyWebService::RegisterDiscoveryManager(FlyWebDiscoveryManager* aDiscoveryManager) +{ + MOZ_ASSERT(NS_IsMainThread()); + mDiscoveryManagerTable.PutEntry(aDiscoveryManager); + if (mMDNSHttpService) { + mMDNSHttpService->EnsureDiscoveryStarted(); + } + if (mMDNSFlywebService) { + mMDNSFlywebService->EnsureDiscoveryStarted(); + } +} + +void +FlyWebService::UnregisterDiscoveryManager(FlyWebDiscoveryManager* aDiscoveryManager) +{ + MOZ_ASSERT(NS_IsMainThread()); + mDiscoveryManagerTable.RemoveEntry(aDiscoveryManager); + if (mDiscoveryManagerTable.IsEmpty()) { + if (mMDNSHttpService) { + mMDNSHttpService->EnsureDiscoveryStopped(); + } + if (mMDNSFlywebService) { + mMDNSFlywebService->EnsureDiscoveryStopped(); + } + } +} + +NS_IMETHODIMP +FlyWebService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (strcmp(aTopic, "inner-window-destroyed")) { + return NS_OK; + } + + nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject); + NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE); + + uint64_t innerID; + nsresult rv = wrapper->GetData(&innerID); + NS_ENSURE_SUCCESS(rv, rv); + + for (FlyWebPublishedServer* server : mServers) { + if (server->OwnerWindowID() == innerID) { + server->Close(); + } + } + + return NS_OK; +} + +void +FlyWebService::UnregisterServer(FlyWebPublishedServer* aServer) +{ + MOZ_ASSERT(NS_IsMainThread()); + DebugOnly<bool> removed = mServers.RemoveElement(aServer); + MOZ_ASSERT(removed); +} + +bool +FlyWebService::HasConnectionOrServer(uint64_t aWindowID) +{ + MOZ_ASSERT(NS_IsMainThread()); + for (FlyWebPublishedServer* server : mServers) { + nsPIDOMWindowInner* win = server->GetOwner(); + if (win && win->WindowID() == aWindowID) { + return true; + } + } + + return false; +} + +void +FlyWebService::NotifyDiscoveredServicesChanged() +{ + // Process the service map, add to the pair map. + for (auto iter = mDiscoveryManagerTable.Iter(); !iter.Done(); iter.Next()) { + iter.Get()->GetKey()->NotifyDiscoveredServicesChanged(); + } +} + +void +FlyWebService::ListDiscoveredServices(nsTArray<FlyWebDiscoveredService>& aServices) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (mMDNSHttpService) { + mMDNSHttpService->ListDiscoveredServices(aServices); + } + if (mMDNSFlywebService) { + mMDNSFlywebService->ListDiscoveredServices(aServices); + } +} + +void +FlyWebService::PairWithService(const nsAString& aServiceId, + FlyWebPairingCallback& aCallback) +{ + MOZ_ASSERT(NS_IsMainThread()); + // See if we have already paired with this service. If so, re-use the + // FlyWebPairedService for that. + { + ReentrantMonitorAutoEnter pairedMapLock(mMonitor); + for (auto iter = mPairedServiceTable.Iter(); !iter.Done(); iter.Next()) { + PairedInfo* pairInfo = iter.UserData(); + if (pairInfo->mService.mDiscoveredService.mServiceId.Equals(aServiceId)) { + ErrorResult er; + ReentrantMonitorAutoExit pairedMapRelease(mMonitor); + aCallback.PairingSucceeded(pairInfo->mService, er); + ENSURE_SUCCESS_VOID(er); + return; + } + } + } + + UniquePtr<PairedInfo> pairInfo; + + nsresult rv = NS_OK; + bool notFound = false; + if (mMDNSHttpService && mMDNSHttpService->HasService(aServiceId)) { + rv = mMDNSHttpService->PairWithService(aServiceId, pairInfo); + } else if (mMDNSFlywebService && mMDNSFlywebService->HasService(aServiceId)) { + rv = mMDNSFlywebService->PairWithService(aServiceId, pairInfo); + } else { + notFound = true; + } + + if (NS_FAILED(rv)) { + ErrorResult result; + result.Throw(rv); + const nsAString& reason = NS_LITERAL_STRING("Error pairing."); + aCallback.PairingFailed(reason, result); + ENSURE_SUCCESS_VOID(result); + return; + } + + if (!pairInfo) { + ErrorResult res; + const nsAString& reason = notFound ? + NS_LITERAL_STRING("No such service.") : + NS_LITERAL_STRING("Error pairing."); + aCallback.PairingFailed(reason, res); + ENSURE_SUCCESS_VOID(res); + return; + } + + // Add fingerprint to certificate override database. + if (!pairInfo->mService.mDiscoveredService.mCert.IsEmpty()) { + nsCOMPtr<nsICertOverrideService> override = + do_GetService("@mozilla.org/security/certoverride;1"); + if (!override || + NS_FAILED(override->RememberTemporaryValidityOverrideUsingFingerprint( + NS_ConvertUTF16toUTF8(pairInfo->mService.mHostname), + -1, + NS_ConvertUTF16toUTF8(pairInfo->mService.mDiscoveredService.mCert), + nsICertOverrideService::ERROR_UNTRUSTED | + nsICertOverrideService::ERROR_MISMATCH))) { + ErrorResult res; + aCallback.PairingFailed(NS_LITERAL_STRING("Error adding certificate override."), res); + ENSURE_SUCCESS_VOID(res); + return; + } + } + + // Grab a weak reference to the PairedInfo so that we can + // use it even after ownership has been transferred to mPairedServiceTable + PairedInfo* pairInfoWeak = pairInfo.release(); + + { + ReentrantMonitorAutoEnter pairedMapLock(mMonitor); + mPairedServiceTable.Put( + NS_ConvertUTF16toUTF8(pairInfoWeak->mService.mHostname), pairInfoWeak); + } + + ErrorResult er; + aCallback.PairingSucceeded(pairInfoWeak->mService, er); + ENSURE_SUCCESS_VOID(er); +} + +nsresult +FlyWebService::CreateTransportForHost(const char **types, + uint32_t typeCount, + const nsACString &host, + int32_t port, + const nsACString &hostRoute, + int32_t portRoute, + nsIProxyInfo *proxyInfo, + nsISocketTransport **result) +{ + // This might be called on background threads + + *result = nullptr; + + nsCString ipAddrString; + uint16_t discPort; + + { + ReentrantMonitorAutoEnter pairedMapLock(mMonitor); + + PairedInfo* info = mPairedServiceTable.Get(host); + + if (!info) { + return NS_OK; + } + + // Get the ip address of the underlying service. + info->mDNSServiceInfo->GetAddress(ipAddrString); + info->mDNSServiceInfo->GetPort(&discPort); + } + + // Parse it into an NetAddr. + PRNetAddr prNetAddr; + PRStatus status = PR_StringToNetAddr(ipAddrString.get(), &prNetAddr); + NS_ENSURE_FALSE(status == PR_FAILURE, NS_ERROR_FAILURE); + + // Convert PRNetAddr to NetAddr. + mozilla::net::NetAddr netAddr; + PRNetAddrToNetAddr(&prNetAddr, &netAddr); + netAddr.inet.port = htons(discPort); + + RefPtr<mozilla::net::nsSocketTransport> trans = new mozilla::net::nsSocketTransport(); + nsresult rv = trans->InitPreResolved( + types, typeCount, host, port, hostRoute, portRoute, proxyInfo, &netAddr); + NS_ENSURE_SUCCESS(rv, rv); + + trans.forget(result); + return NS_OK; +} + +void +FlyWebService::StartDiscoveryOf(FlyWebPublishedServerImpl* aServer) +{ + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv = mMDNSFlywebService ? + mMDNSFlywebService->StartDiscoveryOf(aServer) : + NS_ERROR_FAILURE; + + if (NS_FAILED(rv)) { + aServer->PublishedServerStarted(rv); + } +} + +} // namespace dom +} // namespace mozilla |