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

#include "components/url_pattern_index/url_rule_util.h"

#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "components/url_pattern_index/flat/url_pattern_index_generated.h"

namespace url_pattern_index {

namespace {

std::string AnchorToString(url_pattern_index::flat::AnchorType anchor_type) {
  switch (anchor_type) {
    case url_pattern_index::flat::AnchorType_NONE:
      return std::string();
    case url_pattern_index::flat::AnchorType_SUBDOMAIN:
      return "||";
    case url_pattern_index::flat::AnchorType_BOUNDARY:
      return "|";
  }
  NOTREACHED();
}

// Class that aids in appending options to a pretty-printed rule.
class OptionsPrinter {
 public:
  OptionsPrinter() = default;

  OptionsPrinter(const OptionsPrinter&) = delete;
  OptionsPrinter& operator=(const OptionsPrinter&) = delete;

  // If this is the first printed option for the rule, add a $ separator,
  // otherwise a comma.
  std::string PrintOption(const std::string& option) {
    std::string out = printed_options_ ? "," : "$";
    printed_options_ = true;
    return out + option;
  }

 private:
  bool printed_options_ = false;
};

std::string PartyOptionsToString(
    OptionsPrinter* options_printer,
    const url_pattern_index::flat::UrlRule* flat_rule) {
  std::string out;
  bool third_party = flat_rule->options() &
                     url_pattern_index::flat::OptionFlag_APPLIES_TO_THIRD_PARTY;
  bool first_party = flat_rule->options() &
                     url_pattern_index::flat::OptionFlag_APPLIES_TO_FIRST_PARTY;
  if (first_party ^ third_party) {
    if (first_party)
      out += options_printer->PrintOption("~third-party");
    else
      out += options_printer->PrintOption("third-party");
  }
  return out;
}

std::string TypeOptionsToString(
    OptionsPrinter* options_printer,
    const url_pattern_index::flat::UrlRule* flat_rule) {
  std::string out;

  if (flat_rule->activation_types() &
      url_pattern_index::flat::ActivationType_DOCUMENT) {
    out += options_printer->PrintOption("document");
  }

  if (flat_rule->activation_types() &
      url_pattern_index::flat::ActivationType_GENERIC_BLOCK) {
    out += options_printer->PrintOption("genericblock");
  }

  // Filterlists do not support the "main_frame" and "csp_report" element types.
  // Hence we ignore them here.
  constexpr uint16_t kSupportedElementTypes =
      url_pattern_index::flat::ElementType_ANY &
      ~url_pattern_index::flat::ElementType_MAIN_FRAME &
      ~url_pattern_index::flat::ElementType_CSP_REPORT;
  uint16_t types = flat_rule->element_types() & kSupportedElementTypes;
  if (types == kSupportedElementTypes)
    return out;

  if (types & url_pattern_index::flat::ElementType_OTHER)
    out += options_printer->PrintOption("other");
  if (types & url_pattern_index::flat::ElementType_SCRIPT)
    out += options_printer->PrintOption("script");
  if (types & url_pattern_index::flat::ElementType_IMAGE)
    out += options_printer->PrintOption("image");
  if (types & url_pattern_index::flat::ElementType_STYLESHEET)
    out += options_printer->PrintOption("stylesheet");
  if (types & url_pattern_index::flat::ElementType_OBJECT)
    out += options_printer->PrintOption("object");
  if (types & url_pattern_index::flat::ElementType_XMLHTTPREQUEST)
    out += options_printer->PrintOption("xmlhttprequest");
  if (types & url_pattern_index::flat::ElementType_OBJECT_SUBREQUEST)
    out += options_printer->PrintOption("object-subrequest");
  if (types & url_pattern_index::flat::ElementType_SUBDOCUMENT)
    out += options_printer->PrintOption("subdocument");
  if (types & url_pattern_index::flat::ElementType_PING)
    out += options_printer->PrintOption("ping");
  if (types & url_pattern_index::flat::ElementType_MEDIA)
    out += options_printer->PrintOption("media");
  if (types & url_pattern_index::flat::ElementType_FONT)
    out += options_printer->PrintOption("font");
  if (types & url_pattern_index::flat::ElementType_WEBSOCKET)
    out += options_printer->PrintOption("websocket");
  if (types & url_pattern_index::flat::ElementType_WEBTRANSPORT)
    out += options_printer->PrintOption("webtransport");
  if (types & url_pattern_index::flat::ElementType_WEBBUNDLE)
    out += options_printer->PrintOption("webbundle");

  return out;
}

std::string ConvertFlatString(const flatbuffers::String* string) {
  return string ? std::string(string->data(), string->size()) : "";
}

std::string DomainOptionsToString(
    OptionsPrinter* options_printer,
    const url_pattern_index::flat::UrlRule* flat_rule) {
  std::string out;
  if (!flat_rule->initiator_domains_included() &&
      !flat_rule->initiator_domains_excluded())
    return "";

  // Note: For "main frame" ($document) matching, the $domain option should use
  //       the included/excluded request domains instead of initiator domains.
  //       This logic is OK for now however since $document filters aren't
  //       generated by `FlatUrlRuleToFilterlistString` yet.

  out += options_printer->PrintOption("domain=");

  bool first = true;
  if (flat_rule->initiator_domains_included()) {
    for (auto* domain : *flat_rule->initiator_domains_included()) {
      if (!first)
        out += "|";
      first = false;
      out += ConvertFlatString(domain);
    }
  }
  if (flat_rule->initiator_domains_excluded()) {
    for (auto* domain : *flat_rule->initiator_domains_excluded()) {
      if (!first)
        out += "|";
      first = false;
      out += "~" + ConvertFlatString(domain);
    }
  }
  return out;
}

}  // namespace

std::string FlatUrlRuleToFilterlistString(const flat::UrlRule* flat_rule) {
  std::string out;

  if (flat_rule->options() & url_pattern_index::flat::OptionFlag_IS_ALLOWLIST)
    out += "@@";

  out += AnchorToString(flat_rule->anchor_left());

  std::string pattern = ConvertFlatString(flat_rule->url_pattern());

  // Add a wildcard to pattern if necessary to differentiate it from a regex.
  // E.g., /foo/ should be /foo/*.
  if (flat_rule->url_pattern_type() !=
          url_pattern_index::flat::UrlPatternType_REGEXP &&
      pattern.size() >= 2 && pattern[0] == '/' &&
      pattern[pattern.size() - 1] == '/') {
    pattern += "*";
  }
  out += pattern;

  out += AnchorToString(flat_rule->anchor_right());

  OptionsPrinter options_printer;

  out += PartyOptionsToString(&options_printer, flat_rule);

  if (flat_rule->options() &
      url_pattern_index::flat::OptionFlag_IS_MATCH_CASE) {
    out += options_printer.PrintOption("match-case");
  }

  out += TypeOptionsToString(&options_printer, flat_rule);

  out += DomainOptionsToString(&options_printer, flat_rule);

  return out;
}

}  // namespace url_pattern_index
