// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/browser/shared_module_service.h"

#include <set>
#include <vector>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/version.h"
#include "extensions/browser/delayed_install_manager.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/pending_extension_manager.h"
#include "extensions/browser/uninstall_reason.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_urls.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

namespace extensions {

namespace {

typedef std::vector<SharedModuleInfo::ImportInfo> ImportInfoVector;
typedef std::list<SharedModuleInfo::ImportInfo> ImportInfoList;

bool IsSharedModule(const Extension* extension,
                    content::BrowserContext* context) {
  return SharedModuleInfo::IsSharedModule(extension);
}

}  // namespace

SharedModuleService::SharedModuleService(content::BrowserContext* context)
    : browser_context_(context) {
  extension_registry_observation_.Observe(
      ExtensionRegistry::Get(browser_context_));
}

SharedModuleService::~SharedModuleService() = default;

void SharedModuleService::Shutdown() {
  extension_registry_observation_.Reset();
}

SharedModuleService::ImportStatus SharedModuleService::CheckImports(
    const Extension* extension,
    ImportInfoList* missing_modules,
    ImportInfoList* outdated_modules) {
  DCHECK(extension);
  DCHECK(missing_modules && missing_modules->empty());
  DCHECK(outdated_modules && outdated_modules->empty());

  ImportStatus status = IMPORT_STATUS_OK;

  // TODO(crbug.com/40387578): Code like this lives in CrxInstaller and
  // UnpackedInstaller.  If a change is made here that is important to enforce
  // at install time, those locations need to be updated.
  ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_);
  const ImportInfoVector& imports = SharedModuleInfo::GetImports(extension);
  for (auto iter = imports.begin(); iter != imports.end(); ++iter) {
    base::Version version_required(iter->minimum_version);
    const Extension* imported_module = registry->GetExtensionById(
        iter->extension_id, ExtensionRegistry::EVERYTHING);
    if (!imported_module) {
      if (extension->from_webstore()) {
        status = IMPORT_STATUS_UNSATISFIED;
        missing_modules->push_back(*iter);
      } else {
        return IMPORT_STATUS_UNRECOVERABLE;
      }
    } else if (!SharedModuleInfo::IsSharedModule(imported_module)) {
      return IMPORT_STATUS_UNRECOVERABLE;
    } else if (version_required.IsValid() &&
               imported_module->version().CompareTo(version_required) < 0) {
      if (imported_module->from_webstore()) {
        outdated_modules->push_back(*iter);
        status = IMPORT_STATUS_UNSATISFIED;
      } else {
        return IMPORT_STATUS_UNRECOVERABLE;
      }
    }
  }

  return status;
}

SharedModuleService::ImportStatus SharedModuleService::SatisfyImports(
    const Extension* extension) {
  ImportInfoList missing_modules;
  ImportInfoList outdated_modules;
  ImportStatus status =
      CheckImports(extension, &missing_modules, &outdated_modules);

  PendingExtensionManager* pending_extension_manager =
      PendingExtensionManager::Get(browser_context_);
  DCHECK(pending_extension_manager);

  if (status == IMPORT_STATUS_UNSATISFIED) {
    for (ImportInfoList::const_iterator iter = missing_modules.begin();
         iter != missing_modules.end(); ++iter) {
      pending_extension_manager->AddFromExtensionImport(
          iter->extension_id, extension_urls::GetWebstoreUpdateUrl(),
          IsSharedModule);
    }
    ExtensionsBrowserClient::Get()->UpdateCheckIfEnabled(browser_context_);
  }
  return status;
}

std::unique_ptr<ExtensionSet> SharedModuleService::GetDependentExtensions(
    const Extension* extension) {
  std::unique_ptr<ExtensionSet> dependents(new ExtensionSet());

  if (SharedModuleInfo::IsSharedModule(extension)) {
    ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_);

    ExtensionSet set_to_check;
    set_to_check.InsertAll(registry->enabled_extensions());
    set_to_check.InsertAll(registry->disabled_extensions());
    set_to_check.InsertAll(
        DelayedInstallManager::Get(browser_context_)->delayed_installs());

    for (ExtensionSet::const_iterator iter = set_to_check.begin();
         iter != set_to_check.end(); ++iter) {
      if (SharedModuleInfo::ImportsExtensionById(iter->get(),
                                                 extension->id())) {
        dependents->Insert(*iter);
      }
    }
  }
  return dependents;
}

InstallGate::Action SharedModuleService::ShouldDelay(const Extension* extension,
                                                     bool install_immediately) {
  ImportStatus status = SatisfyImports(extension);
  switch (status) {
    case IMPORT_STATUS_OK:
      return INSTALL;
    case IMPORT_STATUS_UNSATISFIED:
      return DELAY;
    case IMPORT_STATUS_UNRECOVERABLE:
      return ABORT;
  }

  NOTREACHED();
}

void SharedModuleService::PruneSharedModules() {
  ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_);
  ExtensionRegistrar* registrar = ExtensionRegistrar::Get(browser_context_);

  ExtensionSet set_to_check;
  set_to_check.InsertAll(registry->enabled_extensions());
  set_to_check.InsertAll(registry->disabled_extensions());
  set_to_check.InsertAll(
      DelayedInstallManager::Get(browser_context_)->delayed_installs());

  std::vector<std::string> shared_modules;
  std::set<std::string> used_shared_modules;

  for (ExtensionSet::const_iterator iter = set_to_check.begin();
       iter != set_to_check.end(); ++iter) {
    if (SharedModuleInfo::IsSharedModule(iter->get())) {
      shared_modules.push_back(iter->get()->id());
    }

    const ImportInfoVector& imports = SharedModuleInfo::GetImports(iter->get());
    for (auto imports_iter = imports.begin(); imports_iter != imports.end();
         ++imports_iter) {
      used_shared_modules.insert(imports_iter->extension_id);
    }
  }

  std::vector<std::string>::const_iterator shared_modules_iter;
  for (shared_modules_iter = shared_modules.begin();
       shared_modules_iter != shared_modules.end(); shared_modules_iter++) {
    if (used_shared_modules.count(*shared_modules_iter)) {
      continue;
    }
    registrar->UninstallExtension(
        *shared_modules_iter,
        extensions::UNINSTALL_REASON_ORPHANED_SHARED_MODULE,
        nullptr);  // Ignore error.
  }
}

void SharedModuleService::OnExtensionInstalled(
    content::BrowserContext* browser_context,
    const Extension* extension,
    bool is_update) {
  if (is_update) {
    PruneSharedModules();
  }
}

void SharedModuleService::OnExtensionUninstalled(
    content::BrowserContext* browser_context,
    const Extension* extension,
    extensions::UninstallReason reason) {
  // Do not call PruneSharedModules() for an uninstall that we were responsible
  // for.
  if (reason == extensions::UNINSTALL_REASON_ORPHANED_SHARED_MODULE) {
    return;
  }

  PruneSharedModules();
}

}  // namespace extensions
