// Copyright 2013 The Chromium Authors. All rights reserved.
// 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/log_private/log_private_api.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
#include "chrome/browser/extensions/api/log_private/filter_handler.h"
#include "chrome/browser/extensions/api/log_private/log_parser.h"
#include "chrome/browser/extensions/api/log_private/syslog_parser.h"
#include "chrome/browser/feedback/system_logs/scrubbed_system_logs_fetcher.h"
#include "chrome/browser/io_thread.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/extensions/api/log_private.h"
#include "chrome/common/logging_chrome.h"
#include "components/net_log/chrome_net_log.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_function.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/granted_file_entry.h"
#include "net/log/net_log_entry.h"

#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/system_logs/debug_log_writer.h"
#endif

using content::BrowserThread;

namespace events {
const char kOnCapturedEvents[] = "logPrivate.onCapturedEvents";
}  // namespace events

namespace extensions {
namespace {

const char kAppLogsSubdir[] = "apps";
const char kLogDumpsSubdir[] = "log_dumps";
const char kLogFileNameBase[] = "net-internals";
const int kNetLogEventDelayMilliseconds = 100;

// Gets sequenced task runner for file specific calls within this API.
scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() {
  base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
  return pool->GetSequencedTaskRunnerWithShutdownBehavior(
      pool->GetNamedSequenceToken(FileResource::kSequenceToken),
      base::SequencedWorkerPool::BLOCK_SHUTDOWN);
}

#if DCHECK_IS_ON()
base::LazyInstance<base::SequenceChecker>::Leaky
    g_file_resource_sequence_checker = LAZY_INSTANCE_INITIALIZER;
#endif

// Checks if we are running on sequenced task runner thread.
void AssertCurrentlyOnFileResourceSequence() {
#if DCHECK_IS_ON()
  DCHECK(g_file_resource_sequence_checker.Get().CalledOnValidSequence());
#endif
}

std::unique_ptr<LogParser> CreateLogParser(const std::string& log_type) {
  if (log_type == "syslog")
    return std::unique_ptr<LogParser>(new SyslogParser());
  // TODO(shinfan): Add more parser here

  NOTREACHED() << "Invalid log type: " << log_type;
  return std::unique_ptr<LogParser>();
}

void CollectLogInfo(FilterHandler* filter_handler,
                    system_logs::SystemLogsResponse* logs,
                    std::vector<api::log_private::LogEntry>* output) {
  for (system_logs::SystemLogsResponse::const_iterator
      request_it = logs->begin(); request_it != logs->end(); ++request_it) {
    if (!filter_handler->IsValidSource(request_it->first)) {
      continue;
    }
    std::unique_ptr<LogParser> parser(CreateLogParser(request_it->first));
    if (parser) {
      parser->Parse(request_it->second, output, filter_handler);
    }
  }
}

// Returns directory location of app-specific logs that are initiated with
// logPrivate.startEventRecorder() calls - /home/chronos/user/log/apps
base::FilePath GetAppLogDirectory() {
  return logging::GetSessionLogDir(*base::CommandLine::ForCurrentProcess())
      .Append(kAppLogsSubdir);
}

// Returns directory location where logs dumps initiated with chrome.dumpLogs
// will be stored - /home/chronos/<user_profile_dir>/Downloads/log_dumps
base::FilePath GetLogDumpDirectory(content::BrowserContext* context) {
  const DownloadPrefs* const prefs = DownloadPrefs::FromBrowserContext(context);
  return prefs->DownloadPath().Append(kLogDumpsSubdir);
}

// Removes direcotry content of |logs_dumps| and |app_logs_dir| (only for the
// primary profile).
void CleanUpLeftoverLogs(bool is_primary_profile,
                         const base::FilePath& app_logs_dir,
                         const base::FilePath& logs_dumps) {
  LOG(WARNING) << "Deleting " << app_logs_dir.value();
  LOG(WARNING) << "Deleting " << logs_dumps.value();

  AssertCurrentlyOnFileResourceSequence();
  base::DeleteFile(logs_dumps, true);

  // App-specific logs are stored in /home/chronos/user/log/apps directory that
  // is shared between all profiles in multi-profile case. We should not
  // nuke it for non-primary profiles.
  if (!is_primary_profile)
    return;

  base::DeleteFile(app_logs_dir, true);
}

}  // namespace

const char FileResource::kSequenceToken[] = "log_api_files";

FileResource::FileResource(const std::string& owner_extension_id,
                           const base::FilePath& path)
    : ApiResource(owner_extension_id), path_(path) {
}

FileResource::~FileResource() {
  base::DeleteFile(path_, true);
}

bool FileResource::IsPersistent() const {
  return false;
}

// static
LogPrivateAPI* LogPrivateAPI::Get(content::BrowserContext* context) {
  LogPrivateAPI* api = GetFactoryInstance()->Get(context);
  api->Initialize();
  return api;
}

LogPrivateAPI::LogPrivateAPI(content::BrowserContext* context)
    : browser_context_(context),
      logging_net_internals_(false),
      event_sink_(api::log_private::EVENT_SINK_CAPTURE),
      extension_registry_observer_(this),
      log_file_resources_(context),
      initialized_(false) {
}

LogPrivateAPI::~LogPrivateAPI() {
}

void LogPrivateAPI::StartNetInternalsWatch(
    const std::string& extension_id,
    api::log_private::EventSink event_sink,
    const base::Closure& closure) {
  net_internal_watches_.insert(extension_id);

  // Nuke any leftover app-specific or dumped log files from previous sessions.
  BrowserThread::PostTaskAndReply(
      BrowserThread::IO,
      FROM_HERE,
      base::Bind(&LogPrivateAPI::MaybeStartNetInternalLogging,
                 base::Unretained(this),
                 extension_id,
                 g_browser_process->io_thread(),
                 event_sink),
      closure);
}

void LogPrivateAPI::StopNetInternalsWatch(const std::string& extension_id,
                                          const base::Closure& closure) {
  net_internal_watches_.erase(extension_id);
  MaybeStopNetInternalLogging(closure);
}

void LogPrivateAPI::StopAllWatches(const std::string& extension_id,
                                   const base::Closure& closure) {
  StopNetInternalsWatch(extension_id, closure);
}

void LogPrivateAPI::RegisterTempFile(const std::string& owner_extension_id,
                                     const base::FilePath& file_path) {
  GetSequencedTaskRunner()->PostTask(
      FROM_HERE,
      base::Bind(&LogPrivateAPI::RegisterTempFileOnFileResourceSequence,
                 base::Unretained(this), owner_extension_id, file_path));
}

static base::LazyInstance<BrowserContextKeyedAPIFactory<LogPrivateAPI> >
    g_factory = LAZY_INSTANCE_INITIALIZER;

// static
BrowserContextKeyedAPIFactory<LogPrivateAPI>*
LogPrivateAPI::GetFactoryInstance() {
  return g_factory.Pointer();
}

void LogPrivateAPI::OnAddEntry(const net::NetLogEntry& entry) {
  // We could receive events on whatever thread they happen to be generated,
  // since we are only interested in network events, we should ignore any
  // other thread than BrowserThread::IO.
  if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
    return;
  }

  if (!pending_entries_.get()) {
    pending_entries_.reset(new base::ListValue());
    BrowserThread::PostDelayedTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&LogPrivateAPI::PostPendingEntries, base::Unretained(this)),
        base::TimeDelta::FromMilliseconds(kNetLogEventDelayMilliseconds));
  }
  pending_entries_->Append(entry.ToValue());
}

void LogPrivateAPI::PostPendingEntries() {
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&LogPrivateAPI:: AddEntriesOnUI,
                 base::Unretained(this),
                 base::Passed(&pending_entries_)));
}

void LogPrivateAPI::AddEntriesOnUI(std::unique_ptr<base::ListValue> value) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  for (const std::string& extension_id : net_internal_watches_) {
    // Create the event's arguments value.
    std::unique_ptr<base::ListValue> event_args(new base::ListValue());
    event_args->Append(value->CreateDeepCopy());
    std::unique_ptr<Event> event(
        new Event(::extensions::events::LOG_PRIVATE_ON_CAPTURED_EVENTS,
                  ::events::kOnCapturedEvents, std::move(event_args)));
    EventRouter::Get(browser_context_)
        ->DispatchEventToExtension(extension_id, std::move(event));
  }
}

void LogPrivateAPI::CreateTempNetLogFile(const std::string& owner_extension_id,
                                         base::ScopedFILE* file) {
  AssertCurrentlyOnFileResourceSequence();

  // Create app-specific subdirectory in session logs folder.
  base::FilePath app_log_dir = GetAppLogDirectory().Append(owner_extension_id);
  if (!base::DirectoryExists(app_log_dir)) {
    if (!base::CreateDirectory(app_log_dir)) {
      LOG(ERROR) << "Could not create dir " << app_log_dir.value();
      return;
    }
  }

  base::FilePath file_path = app_log_dir.Append(kLogFileNameBase);
  file_path = logging::GenerateTimestampedName(file_path, base::Time::Now());
  FILE* file_ptr = fopen(file_path.value().c_str(), "w");
  if (file_ptr == nullptr) {
    LOG(ERROR) << "Could not open " << file_path.value();
    return;
  }

  RegisterTempFileOnFileResourceSequence(owner_extension_id, file_path);
  return file->reset(file_ptr);
}

void LogPrivateAPI::StartObservingNetEvents(
    IOThread* io_thread,
    base::ScopedFILE* file) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (!file->get())
    return;

  write_to_file_observer_.reset(new net::WriteToFileNetLogObserver());
  write_to_file_observer_->set_capture_mode(
      net::NetLogCaptureMode::IncludeCookiesAndCredentials());
  write_to_file_observer_->StartObserving(io_thread->net_log(),
                                          std::move(*file), nullptr, nullptr);
}

void LogPrivateAPI::MaybeStartNetInternalLogging(
    const std::string& caller_extension_id,
    IOThread* io_thread,
    api::log_private::EventSink event_sink) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (!logging_net_internals_) {
    logging_net_internals_ = true;
    event_sink_ = event_sink;
    switch (event_sink_) {
      case api::log_private::EVENT_SINK_CAPTURE: {
        io_thread->net_log()->DeprecatedAddObserver(
            this, net::NetLogCaptureMode::IncludeCookiesAndCredentials());
        break;
      }
      case api::log_private::EVENT_SINK_FILE: {
        base::ScopedFILE* file = new base::ScopedFILE();
        // Initialize a FILE on the blocking pool and start observing event
        // on IO thread.
        GetSequencedTaskRunner()->PostTaskAndReply(
            FROM_HERE,
            base::Bind(&LogPrivateAPI::CreateTempNetLogFile,
                       base::Unretained(this),
                       caller_extension_id,
                       file),
            base::Bind(&LogPrivateAPI::StartObservingNetEvents,
                       base::Unretained(this),
                       io_thread,
                       base::Owned(file)));
        break;
      }
      case api::log_private::EVENT_SINK_NONE: {
        NOTREACHED();
        break;
      }
    }
  }
}

void LogPrivateAPI::MaybeStopNetInternalLogging(const base::Closure& closure) {
  if (net_internal_watches_.empty()) {
    if (closure.is_null()) {
      BrowserThread::PostTask(
          BrowserThread::IO,
          FROM_HERE,
          base::Bind(&LogPrivateAPI::StopNetInternalLogging,
                     base::Unretained(this)));
    } else {
      BrowserThread::PostTaskAndReply(
          BrowserThread::IO,
          FROM_HERE,
          base::Bind(&LogPrivateAPI::StopNetInternalLogging,
                     base::Unretained(this)),
          closure);
    }
  }
}

void LogPrivateAPI::StopNetInternalLogging() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (net_log() && logging_net_internals_) {
    logging_net_internals_ = false;
    switch (event_sink_) {
      case api::log_private::EVENT_SINK_CAPTURE:
        net_log()->DeprecatedRemoveObserver(this);
        break;
      case api::log_private::EVENT_SINK_FILE:
        write_to_file_observer_->StopObserving(nullptr);
        write_to_file_observer_.reset();
        break;
      case api::log_private::EVENT_SINK_NONE:
        NOTREACHED();
        break;
    }
  }
}

void LogPrivateAPI::Initialize() {
  if (initialized_)
    return;

  // Clean up temp files and folders from the previous sessions.
  initialized_ = true;
  extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
  GetSequencedTaskRunner()->PostTask(
      FROM_HERE,
      base::Bind(&CleanUpLeftoverLogs,
                 Profile::FromBrowserContext(browser_context_) ==
                     ProfileManager::GetPrimaryUserProfile(),
                 GetAppLogDirectory(),
                 GetLogDumpDirectory(browser_context_)));
}

void LogPrivateAPI::RegisterTempFileOnFileResourceSequence(
    const std::string& owner_extension_id,
    const base::FilePath& file_path) {
  AssertCurrentlyOnFileResourceSequence();
  log_file_resources_.Add(new FileResource(owner_extension_id, file_path));
}

void LogPrivateAPI::OnExtensionUnloaded(
    content::BrowserContext* browser_context,
    const Extension* extension,
    UnloadedExtensionInfo::Reason reason) {
  StopNetInternalsWatch(extension->id(), base::Closure());
}

LogPrivateGetHistoricalFunction::LogPrivateGetHistoricalFunction() {
}

LogPrivateGetHistoricalFunction::~LogPrivateGetHistoricalFunction() {
}

bool LogPrivateGetHistoricalFunction::RunAsync() {
  // Get parameters
  std::unique_ptr<api::log_private::GetHistorical::Params> params(
      api::log_private::GetHistorical::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());
  filter_handler_.reset(new FilterHandler(params->filter));

  system_logs::SystemLogsFetcherBase* fetcher;
  if ((params->filter).scrub) {
    fetcher = new system_logs::ScrubbedSystemLogsFetcher();
  } else {
    fetcher = new system_logs::AboutSystemLogsFetcher();
  }
  fetcher->Fetch(
      base::Bind(&LogPrivateGetHistoricalFunction::OnSystemLogsLoaded, this));

  return true;
}

void LogPrivateGetHistoricalFunction::OnSystemLogsLoaded(
    std::unique_ptr<system_logs::SystemLogsResponse> sys_info) {
  // Prepare result
  api::log_private::Result result;
  CollectLogInfo(filter_handler_.get(), sys_info.get(), &result.data);
  api::log_private::Filter::Populate(
      *((filter_handler_->GetFilter())->ToValue()), &result.filter);
  SetResult(result.ToValue());
  SendResponse(true);
}

LogPrivateStartEventRecorderFunction::LogPrivateStartEventRecorderFunction() {
}

LogPrivateStartEventRecorderFunction::~LogPrivateStartEventRecorderFunction() {
}

bool LogPrivateStartEventRecorderFunction::RunAsync() {
  std::unique_ptr<api::log_private::StartEventRecorder::Params> params(
      api::log_private::StartEventRecorder::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());
  switch (params->event_type) {
    case api::log_private::EVENT_TYPE_NETWORK:
      LogPrivateAPI::Get(Profile::FromBrowserContext(browser_context()))
          ->StartNetInternalsWatch(
              extension_id(),
              params->sink,
              base::Bind(
                  &LogPrivateStartEventRecorderFunction::OnEventRecorderStarted,
                  this));
      break;
    case api::log_private::EVENT_TYPE_NONE:
      NOTREACHED();
      return false;
  }

  return true;
}

void LogPrivateStartEventRecorderFunction::OnEventRecorderStarted() {
  SendResponse(true);
}

LogPrivateStopEventRecorderFunction::LogPrivateStopEventRecorderFunction() {
}

LogPrivateStopEventRecorderFunction::~LogPrivateStopEventRecorderFunction() {
}

bool LogPrivateStopEventRecorderFunction::RunAsync() {
  std::unique_ptr<api::log_private::StopEventRecorder::Params> params(
      api::log_private::StopEventRecorder::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());
  switch (params->event_type) {
    case api::log_private::EVENT_TYPE_NETWORK:
      LogPrivateAPI::Get(Profile::FromBrowserContext(browser_context()))
          ->StopNetInternalsWatch(
              extension_id(),
              base::Bind(
                  &LogPrivateStopEventRecorderFunction::OnEventRecorderStopped,
                  this));
      break;
    case api::log_private::EVENT_TYPE_NONE:
      NOTREACHED();
      return false;
  }
  return true;
}

void LogPrivateStopEventRecorderFunction::OnEventRecorderStopped() {
  SendResponse(true);
}

LogPrivateDumpLogsFunction::LogPrivateDumpLogsFunction() {
}

LogPrivateDumpLogsFunction::~LogPrivateDumpLogsFunction() {
}

bool LogPrivateDumpLogsFunction::RunAsync() {
  LogPrivateAPI::Get(Profile::FromBrowserContext(browser_context()))
      ->StopAllWatches(
          extension_id(),
          base::Bind(&LogPrivateDumpLogsFunction::OnStopAllWatches, this));
  return true;
}

void LogPrivateDumpLogsFunction::OnStopAllWatches() {
  chromeos::DebugLogWriter::StoreCombinedLogs(
      GetLogDumpDirectory(browser_context()).Append(extension_id()),
      FileResource::kSequenceToken,
      base::Bind(&LogPrivateDumpLogsFunction::OnStoreLogsCompleted, this));
}

void LogPrivateDumpLogsFunction::OnStoreLogsCompleted(
    const base::FilePath& log_path,
    bool succeeded) {
  if (succeeded) {
    LogPrivateAPI::Get(Profile::FromBrowserContext(browser_context()))
        ->RegisterTempFile(extension_id(), log_path);
  }

  std::unique_ptr<base::DictionaryValue> response(new base::DictionaryValue());
  extensions::GrantedFileEntry file_entry =
      extensions::app_file_handler_util::CreateFileEntry(
          Profile::FromBrowserContext(browser_context()),
          extension(),
          render_frame_host()->GetProcess()->GetID(),
          log_path,
          false);

  auto entry = base::MakeUnique<base::DictionaryValue>();
  entry->SetString("fileSystemId", file_entry.filesystem_id);
  entry->SetString("baseName", file_entry.registered_name);
  entry->SetString("id", file_entry.id);
  entry->SetBoolean("isDirectory", false);
  base::ListValue* entry_list = new base::ListValue();
  entry_list->Append(std::move(entry));
  response->Set("entries", entry_list);
  response->SetBoolean("multiple", false);
  SetResult(std::move(response));
  SendResponse(succeeded);
}

}  // namespace extensions
