// Copyright 2017 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 "tools/traffic_annotation/auditor/traffic_annotation_file_filter.h"

#include <fstream>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/process/launch.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "third_party/re2/src/re2/re2.h"

namespace {

// List of keywords that indicate a file may be related to network traffic
// annotations. This list includes all keywords related to defining annotations
// and all functions that need one.
const char* kRelevantKeywords[] = {
    "network_traffic_annotation",
    "network_traffic_annotation_test_helper",
    "NetworkTrafficAnnotationTag",
    "PartialNetworkTrafficAnnotationTag",
    "DefineNetworkTrafficAnnotation",
    "DefinePartialNetworkTrafficAnnotation",
    "CompleteNetworkTrafficAnnotation",
    "BranchedCompleteNetworkTrafficAnnotation",
    "NO_TRAFFIC_ANNOTATION_YET",
    "NO_PARTIAL_TRAFFIC_ANNOTATION_YET",
    "MISSING_TRAFFIC_ANNOTATION",
    "TRAFFIC_ANNOTATION_FOR_TESTS",
    "PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS",
    "URLFetcher::Create",  // This one is used with class as it's too generic.
    "CreateRequest",       // URLRequestContext::
    nullptr                // End Marker
};

}  // namespace

TrafficAnnotationFileFilter::TrafficAnnotationFileFilter() = default;

TrafficAnnotationFileFilter::~TrafficAnnotationFileFilter() = default;

void TrafficAnnotationFileFilter::GetFilesFromGit(
    const base::FilePath& source_path) {
  // Change directory to source path to access git and check files.
  base::FilePath original_path;
  base::GetCurrentDirectory(&original_path);
  base::SetCurrentDirectory(source_path);

  std::string git_list;
  if (git_file_for_test_.empty()) {
    const base::CommandLine::CharType* args[] =
#if defined(OS_WIN)
        {FILE_PATH_LITERAL("git.bat"), FILE_PATH_LITERAL("ls-files")};
#else
        {"git", "ls-files"};
#endif
    base::CommandLine cmdline(2, args);

    // Get list of files from git.
    if (!base::GetAppOutput(cmdline, &git_list)) {
      LOG(ERROR) << "Could not get files from git.";
      git_list.clear();
    }
  } else {
    if (!base::ReadFileToString(git_file_for_test_, &git_list)) {
      LOG(ERROR) << "Could not load mock git list file from "
                 << git_file_for_test_.MaybeAsASCII().c_str();
      git_list.clear();
    }
  }

  for (const std::string file_path : base::SplitString(
           git_list, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) {
    if (IsFileRelevant(file_path))
      git_files_.push_back(file_path);
  }

  base::SetCurrentDirectory(original_path);
}

bool TrafficAnnotationFileFilter::IsFileRelevant(const std::string& file_path) {
  // Check file extension.
  int pos = file_path.length() - 3;

  if (pos < 0 || (strcmp(".mm", file_path.c_str() + pos) &&
                  strcmp(".cc", file_path.c_str() + pos))) {
    return false;
  }

  // Ignore test files to speed up the tests. They would be only tested when
  // filters are disabled.
  pos = file_path.length() - 7;
  if (pos >= 0 && (!strcmp("test.cc", file_path.c_str() + pos) ||
                   !strcmp("test.mm", file_path.c_str() + pos))) {
    return false;
  }

  base::FilePath converted_file_path =
#if defined(OS_WIN)
      base::FilePath(
          base::FilePath::StringPieceType(base::UTF8ToWide(file_path)));
#else
      base::FilePath(base::FilePath::StringPieceType(file_path));
#endif

  // Check file content.
  std::string file_content;
  if (!base::ReadFileToString(converted_file_path, &file_content)) {
    LOG(ERROR) << "Could not open file: " << file_path;
    return false;
  }

  for (int i = 0; kRelevantKeywords[i]; i++) {
    if (file_content.find(kRelevantKeywords[i]) != std::string::npos)
      return true;
  }

  return false;
}

void TrafficAnnotationFileFilter::GetRelevantFiles(
    const base::FilePath& source_path,
    const std::vector<std::string>& ignore_list,
    std::string directory_name,
    std::vector<std::string>* file_paths) {
  if (!git_files_.size())
    GetFilesFromGit(source_path);

#if defined(FILE_PATH_USES_WIN_SEPARATORS)
  std::replace(directory_name.begin(), directory_name.end(), L'\\', L'/');
#endif

  size_t name_length = directory_name.length();
  for (const std::string& file_path : git_files_) {
    if (!strncmp(file_path.c_str(), directory_name.c_str(), name_length)) {
      bool ignore = false;
      for (const std::string& ignore_pattern : ignore_list) {
        if (re2::RE2::FullMatch(file_path.c_str(), ignore_pattern)) {
          ignore = true;
          break;
        }
      }
      if (!ignore)
        file_paths->push_back(file_path);
    }
  }
}
