| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/api/mdns/mdns_api.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/lazy_instance.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/common/extensions/api/mdns.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/browser/extension_function.h" |
| #include "extensions/browser/extension_host.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/buildflags/buildflags.h" |
| #include "extensions/common/extension_id.h" |
| #include "extensions/common/mojom/event_dispatcher.mojom.h" |
| |
| static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE)); |
| |
| namespace extensions { |
| |
| namespace mdns = api::mdns; |
| |
| using DnsSdRegistry = media_router::DnsSdRegistry; |
| |
| MDnsAPI::MDnsAPI(content::BrowserContext* context) |
| : browser_context_(context), dns_sd_registry_(nullptr) { |
| DCHECK(browser_context_); |
| extensions::EventRouter* event_router = EventRouter::Get(context); |
| DCHECK(event_router); |
| event_router->RegisterObserver(this, mdns::OnServiceList::kEventName); |
| } |
| |
| MDnsAPI::~MDnsAPI() = default; |
| |
| // static |
| MDnsAPI* MDnsAPI::Get(content::BrowserContext* context) { |
| return BrowserContextKeyedAPIFactory<MDnsAPI>::Get(context); |
| } |
| |
| static base::LazyInstance<BrowserContextKeyedAPIFactory<MDnsAPI>>:: |
| DestructorAtExit g_mdns_api_factory = LAZY_INSTANCE_INITIALIZER; |
| |
| // static |
| BrowserContextKeyedAPIFactory<MDnsAPI>* MDnsAPI::GetFactoryInstance() { |
| return g_mdns_api_factory.Pointer(); |
| } |
| |
| void MDnsAPI::Shutdown() { |
| if (dns_sd_registry_) { |
| dns_sd_registry_->RemoveObserver(this); |
| } |
| } |
| |
| void MDnsAPI::SetDnsSdRegistryForTesting(DnsSdRegistry* dns_sd_registry) { |
| dns_sd_registry_ = dns_sd_registry; |
| if (dns_sd_registry_) { |
| dns_sd_registry_->AddObserver(this); |
| } |
| } |
| |
| void MDnsAPI::ForceDiscovery() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DnsSdRegistry* registry = dns_sd_registry(); |
| return registry->ResetAndDiscover(); |
| } |
| |
| DnsSdRegistry* MDnsAPI::dns_sd_registry() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (!dns_sd_registry_) { |
| dns_sd_registry_ = media_router::DnsSdRegistry::GetInstance(); |
| dns_sd_registry_->AddObserver(this); |
| } |
| return dns_sd_registry_; |
| } |
| |
| void MDnsAPI::OnListenerAdded(const EventListenerInfo& details) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| UpdateMDnsListeners(); |
| } |
| |
| void MDnsAPI::OnListenerRemoved(const EventListenerInfo& details) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| UpdateMDnsListeners(); |
| } |
| |
| void MDnsAPI::UpdateMDnsListeners() { |
| std::set<std::string> new_service_types; |
| ServiceTypeCounts current_service_counts; |
| GetValidOnServiceListListeners( |
| "" /* service_type_filter - blank = all services */, |
| nullptr /* extension_ids */, ¤t_service_counts); |
| |
| DnsSdRegistry* registry = dns_sd_registry(); |
| |
| // Check if the counts of per-service-type event handlers has changed since |
| // the previous invocation, and take appropriate action if a change was |
| // detected. |
| // |
| // mDNS registration is performed for difference(cur, previous). |
| // mDNS unregistration is performed for difference(previous, cur). |
| // The mDNS device list is refreshed if the listener count has grown for |
| // a service type in union(cur, previous). |
| auto i_cur = current_service_counts.begin(); |
| auto i_prev = prev_service_counts_.begin(); |
| while (i_cur != current_service_counts.end() || |
| i_prev != prev_service_counts_.end()) { |
| if (i_prev == prev_service_counts_.end() || |
| (i_cur != current_service_counts.end() && |
| i_cur->first < i_prev->first)) { |
| DVLOG(2) << "Registering listener for mDNS service " << i_cur->first; |
| registry->RegisterDnsSdListener(i_cur->first); |
| i_cur++; |
| } else if (i_cur == current_service_counts.end() || |
| (i_prev != prev_service_counts_.end() && |
| i_prev->first < i_cur->first)) { |
| DVLOG(2) << "Unregistering listener for mDNS service " << i_prev->first; |
| registry->UnregisterDnsSdListener(i_prev->first); |
| i_prev++; |
| } else { |
| if (i_cur->second > i_prev->second) { |
| DVLOG(2) << "Additional listeners added for mDNS service " |
| << i_cur->first; |
| registry->Publish(i_cur->first); |
| } |
| ++i_cur; |
| ++i_prev; |
| } |
| } |
| prev_service_counts_.swap(current_service_counts); |
| } |
| |
| void MDnsAPI::OnDnsSdEvent(const std::string& service_type, |
| const DnsSdRegistry::DnsSdServiceList& services) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| std::vector<mdns::MDnsService> args; |
| for (const auto& service : services) { |
| if (static_cast<int>(args.size()) == |
| api::mdns::MAX_SERVICE_INSTANCES_PER_EVENT) { |
| // TODO(reddaly): This is not the most meaningful way of notifying the |
| // application that something bad happened. It will go to the user's |
| // console (which most users don't look at)and the developer will be none |
| // the wiser. Instead, changing the event to pass the number of |
| // discovered instances would allow the caller to know when the list is |
| // truncated and tell the user something meaningful in the extension/app. |
| WriteToConsole( |
| service_type, blink::mojom::ConsoleMessageLevel::kWarning, |
| base::StringPrintf("Truncating number of service instances in " |
| "onServiceList to maximum allowed: %d", |
| api::mdns::MAX_SERVICE_INSTANCES_PER_EVENT)); |
| break; |
| } |
| mdns::MDnsService mdns_service; |
| mdns_service.service_name = service.service_name; |
| mdns_service.service_host_port = service.service_host_port.ToString(); |
| mdns_service.ip_address = service.ip_address; |
| mdns_service.service_data = service.service_data; |
| args.push_back(std::move(mdns_service)); |
| } |
| |
| auto results = mdns::OnServiceList::Create(args); |
| auto event = std::make_unique<Event>(events::MDNS_ON_SERVICE_LIST, |
| mdns::OnServiceList::kEventName, |
| std::move(results), browser_context_); |
| event->filter_info->service_type = service_type; |
| |
| // TODO(justinlin): To avoid having listeners without filters getting all |
| // events, modify API to have this event require filters. |
| // TODO(reddaly): If event isn't on allowlist, ensure it does not get |
| // broadcast to extensions. |
| extensions::EventRouter::Get(browser_context_) |
| ->BroadcastEvent(std::move(event)); |
| } |
| |
| const extensions::EventListenerMap::ListenerList& MDnsAPI::GetEventListeners() { |
| return extensions::EventRouter::Get(browser_context_) |
| ->listeners() |
| .GetEventListenersByName(mdns::OnServiceList::kEventName); |
| } |
| |
| bool MDnsAPI::IsMDnsAllowed(const ExtensionId& extension_id) const { |
| const extensions::Extension* extension = |
| ExtensionRegistry::Get(browser_context_) |
| ->enabled_extensions() |
| .GetByID(extension_id); |
| return extension; |
| } |
| |
| void MDnsAPI::GetValidOnServiceListListeners( |
| const std::string& service_type_filter, |
| std::set<ExtensionId>* extension_ids, |
| ServiceTypeCounts* service_type_counts) { |
| for (const auto& listener : GetEventListeners()) { |
| const base::Value::Dict* filter = listener->filter(); |
| |
| const std::string* service_type = |
| filter->FindString(kEventFilterServiceTypeKey); |
| if (!service_type || service_type->empty() || |
| !base::IsStringASCII(*service_type)) { |
| continue; |
| } |
| |
| // Match service type when filter isn't "" |
| if (!service_type_filter.empty() && service_type_filter != *service_type) { |
| continue; |
| } |
| |
| // Don't listen for services associated only with disabled extensions. |
| if (!IsMDnsAllowed(listener->extension_id())) { |
| continue; |
| } |
| |
| if (extension_ids) { |
| extension_ids->insert(listener->extension_id()); |
| } |
| if (service_type_counts) { |
| (*service_type_counts)[*service_type]++; |
| } |
| } |
| } |
| |
| void MDnsAPI::WriteToConsole(const std::string& service_type, |
| blink::mojom::ConsoleMessageLevel level, |
| const std::string& message) { |
| // Get all the extensions with an onServiceList listener for a particular |
| // service type. |
| std::set<ExtensionId> extension_ids; |
| ServiceTypeCounts counts; |
| GetValidOnServiceListListeners(service_type, &extension_ids, |
| nullptr /* service_type_counts */); |
| |
| std::string logged_message(std::string("[chrome.mdns] ") + message); |
| |
| // Log to the consoles of the background pages for those extensions. |
| // TODO(devlin): It's a little weird to log to the background pages, |
| // especially when it might be dormant. We should probably just log to a place |
| // like the ErrorConsole instead. |
| for (const ExtensionId& extension_id : extension_ids) { |
| extensions::ExtensionHost* host = |
| extensions::ProcessManager::Get(browser_context_) |
| ->GetBackgroundHostForExtension(extension_id); |
| content::RenderFrameHost* render_frame_host = |
| host ? host->host_contents()->GetPrimaryMainFrame() : nullptr; |
| if (render_frame_host) { |
| render_frame_host->AddMessageToConsole(level, logged_message); |
| } |
| } |
| } |
| |
| ExtensionFunction::ResponseAction MdnsForceDiscoveryFunction::Run() { |
| MDnsAPI* api = MDnsAPI::Get(browser_context()); |
| if (!api) { |
| return RespondNow(Error("Unknown error.")); |
| } |
| api->ForceDiscovery(); |
| return RespondNow(NoArguments()); |
| } |
| |
| } // namespace extensions |