// Copyright 2016 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/ui/startup/startup_tab_provider.h"

#include <algorithm>
#include <string>
#include <string_view>

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "chrome/browser/first_run/first_run.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profile_resetter/triggered_profile_resetter.h"
#include "chrome/browser/profile_resetter/triggered_profile_resetter_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/signin_util.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/startup/google_chrome_scheme_util.h"
#include "chrome/browser/ui/startup/startup_browser_creator.h"
#include "chrome/browser/ui/startup/startup_types.h"
#include "chrome/browser/ui/startup/url_util.h"
#include "chrome/browser/ui/tabs/pinned_tab_codec.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/webui/settings/reset_settings_handler.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/prefs/pref_service.h"
#include "components/search_engines/util.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/primary_account_mutator.h"
#include "components/url_formatter/url_fixer.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/common/url_constants.h"
#include "net/base/filename_util.h"
#include "net/base/url_util.h"

#if BUILDFLAG(IS_WIN)
#include "base/strings/string_util_win.h"
#include "base/win/windows_version.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/shell_integration.h"
#endif  // BUILDFLAG(IS_WIN)

#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/search/search.h"
#include "chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h"
#include "chrome/browser/ui/webui/whats_new/whats_new_util.h"
#include "chrome/common/webui_url_constants.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/manifest_handlers/chrome_url_overrides_handler.h"
#endif  // !BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
#include "chrome/browser/headless/headless_mode_util.h"
#endif

namespace {

// Attempts to find an existing, non-empty tabbed browser for this profile.
bool ProfileHasOtherTabbedBrowser(Profile* profile) {
  bool found = false;
  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
      [profile, &found](BrowserWindowInterface* browser) {
        if (browser->GetProfile() == profile &&
            browser->GetType() == BrowserWindowInterface::TYPE_NORMAL &&
            !browser->GetTabStripModel()->empty()) {
          found = true;
        }
        return !found;
      });
  return found;
}

bool IsWelcomePageUrl(const GURL& url) {
  static constexpr std::string_view kChromeUIWelcomeHost = "welcome";
#if BUILDFLAG(IS_WIN)
  static constexpr std::string_view kChromeUIWelcomeWin10Host = "welcome-win10";
#endif

  return url.SchemeIs(content::kChromeUIScheme) &&
         (url.host() == kChromeUIWelcomeHost
#if BUILDFLAG(IS_WIN)
          || url.host() == kChromeUIWelcomeWin10Host
#endif
         );
}

}  // namespace

StartupTabs StartupTabProviderImpl::GetDistributionFirstRunTabs(
    StartupBrowserCreator* browser_creator) const {
  if (!browser_creator) {
    return StartupTabs();
  }
  StartupTabs tabs = GetInitialPrefsTabsForState(
      first_run::IsChromeFirstRun(), browser_creator->first_run_tabs_);
  browser_creator->first_run_tabs_.clear();
  return tabs;
}

StartupTabs StartupTabProviderImpl::GetResetTriggerTabs(
    Profile* profile) const {
  auto* triggered_profile_resetter =
      TriggeredProfileResetterFactory::GetForBrowserContext(profile);
  bool has_reset_trigger = triggered_profile_resetter &&
                           triggered_profile_resetter->HasResetTrigger();
  return GetResetTriggerTabsForState(has_reset_trigger);
}

StartupTabs StartupTabProviderImpl::GetPinnedTabs(
    const base::CommandLine& command_line,
    Profile* profile) const {
  return GetPinnedTabsForState(
      StartupBrowserCreator::GetSessionStartupPref(command_line, profile),
      PinnedTabCodec::ReadPinnedTabs(profile),
      ProfileHasOtherTabbedBrowser(profile));
}

StartupTabs StartupTabProviderImpl::GetPreferencesTabs(
    const base::CommandLine& command_line,
    Profile* profile) const {
  return GetPreferencesTabsForState(
      StartupBrowserCreator::GetSessionStartupPref(command_line, profile),
      ProfileHasOtherTabbedBrowser(profile));
}

StartupTabs StartupTabProviderImpl::GetNewTabPageTabs(
    const base::CommandLine& command_line,
    Profile* profile) const {
  return GetNewTabPageTabsForState(
      StartupBrowserCreator::GetSessionStartupPref(command_line, profile));
}

StartupTabs StartupTabProviderImpl::GetCommandLineTabs(
    const base::CommandLine& command_line,
    const base::FilePath& cur_dir,
    Profile* profile) const {
  StartupTabs result;

  for (const auto& arg : command_line.GetArgs()) {
    ParsedCommandLineTabArg parsed_arg =
        ParseTabFromCommandLineArg(arg, cur_dir, profile);

    // `ParseTabFromCommandLineArg()` shouldn't return
    // CommandLineTabsPresent::kUnknown when a profile is provided.
    DCHECK_NE(parsed_arg.tab_parsed, CommandLineTabsPresent::kUnknown);

    if (parsed_arg.tab_parsed == CommandLineTabsPresent::kYes) {
      result.emplace_back(std::move(parsed_arg.tab_url),
                          parsed_arg.is_untrusted_launch);
    }
  }

  return result;
}

CommandLineTabsPresent StartupTabProviderImpl::HasCommandLineTabs(
    const base::CommandLine& command_line,
    const base::FilePath& cur_dir) const {
  bool is_unknown = false;
  for (const auto& arg : command_line.GetArgs()) {
    ParsedCommandLineTabArg parsed_arg =
        ParseTabFromCommandLineArg(arg, cur_dir, /*maybe_profile=*/nullptr);
    if (parsed_arg.tab_parsed == CommandLineTabsPresent::kYes) {
      return CommandLineTabsPresent::kYes;
    }
    if (parsed_arg.tab_parsed == CommandLineTabsPresent::kUnknown) {
      is_unknown = true;
    }
  }

  return is_unknown ? CommandLineTabsPresent::kUnknown
                    : CommandLineTabsPresent::kNo;
}

#if !BUILDFLAG(IS_ANDROID)
StartupTabs StartupTabProviderImpl::GetNewFeaturesTabs(
    bool whats_new_enabled) const {
  return GetNewFeaturesTabsForState(whats_new_enabled);
}

#endif  // !BUILDFLAG(IS_ANDROID)

// static
StartupTabs StartupTabProviderImpl::GetInitialPrefsTabsForState(
    bool is_first_run,
    const std::vector<GURL>& first_run_tabs) {
  // Constants: Magic words used by initial preferences files in place of a URL
  // host to indicate that internal pages should appear on first run.
  static constexpr char kNewTabUrlHost[] = "new_tab_page";

  StartupTabs tabs;
  if (is_first_run) {
    tabs.reserve(first_run_tabs.size());
    for (GURL url : first_run_tabs) {
      if (url.host() == kNewTabUrlHost) {
        url = chrome::ChromeUINewTabURLAsGURL();
      }
      if (IsWelcomePageUrl(url)) {
        // These URLs are still referenced from some of the installers. As
        // these chrome UIs are removed, avoid opening tabs pointing to
        // invalid pages.
        // TODO(crbug.com/379999327): Cleanup this block around Chrome M143
        // or if it stops being reached.
        base::UmaHistogramBoolean("Startup.StartupTabs.IsWelcomePageSkipped",
                                  true);
        continue;
      }
      tabs.emplace_back(url);
    }
  }
  return tabs;
}

// static
StartupTabs StartupTabProviderImpl::GetResetTriggerTabsForState(
    bool profile_has_trigger) {
  StartupTabs tabs;
  if (profile_has_trigger) {
    tabs.emplace_back(GetTriggeredResetSettingsUrl());
  }
  return tabs;
}

// static
StartupTabs StartupTabProviderImpl::GetPinnedTabsForState(
    const SessionStartupPref& pref,
    const StartupTabs& pinned_tabs,
    bool profile_has_other_tabbed_browser) {
  if (pref.ShouldRestoreLastSession() || profile_has_other_tabbed_browser) {
    return StartupTabs();
  }
  return pinned_tabs;
}

// static
StartupTabs StartupTabProviderImpl::GetPreferencesTabsForState(
    const SessionStartupPref& pref,
    bool profile_has_other_tabbed_browser) {
  StartupTabs tabs;
  if (pref.ShouldOpenUrls() && !pref.urls.empty() &&
      !profile_has_other_tabbed_browser) {
    for (const auto& url : pref.urls) {
      tabs.emplace_back(url, pref.type == SessionStartupPref::LAST_AND_URLS
                                 ? StartupTab::Type::kFromLastAndUrlsStartupPref
                                 : StartupTab::Type::kNormal);
    }
  }
  return tabs;
}

// static
StartupTabs StartupTabProviderImpl::GetNewTabPageTabsForState(
    const SessionStartupPref& pref) {
  StartupTabs tabs;
  if (!pref.ShouldRestoreLastSession()) {
    tabs.emplace_back(chrome::ChromeUINewTabURLAsGURL());
  }
  return tabs;
}

#if !BUILDFLAG(IS_ANDROID)
// static
StartupTabs StartupTabProviderImpl::GetNewFeaturesTabsForState(
    bool whats_new_enabled) {
  StartupTabs tabs;
  if (whats_new_enabled) {
    tabs.emplace_back(whats_new::GetWebUIStartupURL());
  }
  return tabs;
}

#endif

// static
GURL StartupTabProviderImpl::GetTriggeredResetSettingsUrl() {
  return GURL(
      chrome::GetSettingsUrl(chrome::kTriggeredResetProfileSettingsSubPage));
}

// static
StartupTabProviderImpl::ParsedCommandLineTabArg
StartupTabProviderImpl::ParseTabFromCommandLineArg(
    base::FilePath::StringViewType arg,
    const base::FilePath& cur_dir,
    Profile* maybe_profile) {
  // Note: Type/encoding of |arg| matches with the one of FilePath.
  // So, we use them for encoding conversions.

  // Handle Vista way of searching - "? <search-term>"
  if (base::StartsWith(arg, FILE_PATH_LITERAL("? "))) {
    if (maybe_profile == nullptr) {
      // In the absence of profile, we are not able to resolve the search URL.
      // We indicate that we don't know whether a tab would be successfully
      // created or not.
      return {CommandLineTabsPresent::kUnknown, GURL()};
    }

    GURL url(GetDefaultSearchURLForSearchTerms(
        TemplateURLServiceFactory::GetForProfile(maybe_profile),
        base::FilePath(arg).LossyDisplayName().substr(/* remove "? " */ 2)));
    if (url.is_valid()) {
      return {CommandLineTabsPresent::kYes, std::move(url)};
    }
  } else {
    // Otherwise, fall through to treating it as a URL; stripping off the
    // direct-launch scheme (e.g. "google-chrome://") if present.
    // This will create a file URL or a regular URL.
    const bool was_stripped = startup::StripGoogleChromeScheme(arg);
    if (!was_stripped || !arg.empty()) {
      const base::FilePath arg_path(arg);
      GURL url(arg_path.MaybeAsASCII());

      // This call can (in rare circumstances) block the UI thread.
      // FixupRelativeFile may access to current working directory, which is a
      // blocking API. http://crbug.com/40466600
      // http://crbug.com/40364501: Only use URLFixerUpper if we don't have a
      // valid URL, otherwise we will look in the current directory for a file
      // named 'about' if the browser was started with a about:foo argument.
      // http://crbug.com/40389934: Always use URLFixerUpper on file:// URLs,
      // otherwise we wouldn't correctly handle '#' in a file name.
      if (!url.is_valid() || url.SchemeIsFile()) {
        base::ScopedAllowBlocking allow_blocking;
        url = url_formatter::FixupRelativeFile(cur_dir, arg_path);
      }

      const bool is_valid = was_stripped
                                ? startup::ValidateLaunchUrlWebSafe(url)
                                : startup::ValidateLaunchUrlWebUnsafe(url);
      if (is_valid) {
        return {CommandLineTabsPresent::kYes, std::move(url), was_stripped};
      }
    }
  }
  return {CommandLineTabsPresent::kNo, GURL()};
}
