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

#ifndef COMPONENTS_URL_PATTERN_SIMPLE_URL_PATTERN_MATCHER_H_
#define COMPONENTS_URL_PATTERN_SIMPLE_URL_PATTERN_MATCHER_H_

#include <memory>
#include <string>
#include <string_view>

#include "base/component_export.h"
#include "base/types/expected.h"
#include "base/types/pass_key.h"
#include "third_party/liburlpattern/parse.h"
#include "third_party/liburlpattern/pattern.h"

class GURL;

namespace re2 {
class RE2;
}

namespace url_pattern {

// This class partially implements URL Pattern spec which is needed for
// Dictionary URL matching of Compression Dictionary Transport.
// https://urlpattern.spec.whatwg.org/
// https://www.ietf.org/archive/id/draft-ietf-httpbis-compression-dictionary-01.html#name-dictionary-url-matching
// It's also used in blink's inspector. The inspector uses it with string inputs
// instead of JS objects, which makes blink::SafeUrlPattern impractical.
//
// This class can be created from a constructor string and a base URL
// and can run the `test` method of URLPattern only with a URL.
//
// This class doesn't support regexp groups, because this class is used in the
// network service, and there is a security concern in allowing user-defined
// regular expressions. Also this class doesn't support setting `ignoreCase`
// option to true.
//
// Note: We share the limitation of regexp groups with ServiceWorker static
// routing API. For that API, we accept URLPattern JS objects without regexp
// groups, and contvet them to blink::SafeUrlPattern structures in Blink, and
// pass them to the browser process via IPC, and evaluate the pattern with
// resource URLs. For Compression Dictionary Transport feature, we parses the
// constructor string and evaluate the pattern with resource URLs in the network
// service. To simplify this class, this class doesn't share any logic with
// ServiceWorker static routing API. But if we will have more usecases of
// URLPattern, we may need to consider combining those logics.
// Context: https://crrev.com/c/5209732/comment/2d53e11c_e4b8c868/
class SimpleUrlPatternMatcher {
 public:
  // This class partially represents `URLPatternInit` in the URL Pattern spec.
  // https://urlpattern.spec.whatwg.org/#dictdef-urlpatterninit
  // The only difference is that this class doesn't have a `baseURL` field.
  class PatternInit {
   public:
    PatternInit(std::optional<std::string> protocol,
                std::optional<std::string> username,
                std::optional<std::string> password,
                std::optional<std::string> hostname,
                std::optional<std::string> port,
                std::optional<std::string> pathname,
                std::optional<std::string> search,
                std::optional<std::string> hash);
    ~PatternInit();
    PatternInit(const PatternInit&) = delete;
    PatternInit& operator=(const PatternInit&) = delete;
    PatternInit(PatternInit&&);
    PatternInit& operator=(PatternInit&&);

    bool operator==(const PatternInit& other) const = default;

    const std::optional<std::string>& protocol() const { return protocol_; }
    const std::optional<std::string>& username() const { return username_; }
    const std::optional<std::string>& password() const { return password_; }
    const std::optional<std::string>& hostname() const { return hostname_; }
    const std::optional<std::string>& port() const { return port_; }
    const std::optional<std::string>& pathname() const { return pathname_; }
    const std::optional<std::string>& search() const { return search_; }
    const std::optional<std::string>& hash() const { return hash_; }

   private:
    std::optional<std::string> protocol_;
    std::optional<std::string> username_;
    std::optional<std::string> password_;
    std::optional<std::string> hostname_;
    std::optional<std::string> port_;
    std::optional<std::string> pathname_;
    std::optional<std::string> search_;
    std::optional<std::string> hash_;
  };

  // This class represents `component` struct in the URL Pattern spec.
  // https://urlpattern.spec.whatwg.org/#component
  class Component {
   public:
    // Compile a component from a pattern.
    // https://urlpattern.spec.whatwg.org/#compile-a-component
    static base::expected<Component, std::string> Create(
        std::optional<std::string_view> pattern,
        liburlpattern::EncodeCallback encode_callback,
        const liburlpattern::Options& options);

    Component(liburlpattern::Pattern pattern,
              std::unique_ptr<re2::RE2> regex,
              base::PassKey<Component>);
    ~Component();
    Component(const Component&) = delete;
    Component& operator=(const Component&) = delete;
    Component(Component&&);
    Component& operator=(Component&&);

    // This method partially implements `match` method of URLPattern in the
    // URL Pattern spec.
    // https://urlpattern.spec.whatwg.org/#urlpattern-match
    bool Match(std::string_view value) const;

   private:
    liburlpattern::Pattern pattern_;
    // This is null if `pattern_` supports direct matching.
    std::unique_ptr<re2::RE2> regex_;
  };

  // Creates a SimpleUrlPatternMatcher from a constructor string and a base URL.
  // If the constructor string is invalid or it contains regexp group, this
  // method returns an error string. If `base_url` is nullptr, relative patterns
  // in `constructor_string` produce an error.
  static base::expected<std::unique_ptr<SimpleUrlPatternMatcher>, std::string>
  Create(std::string_view constructor_string, const GURL* base_url);

  SimpleUrlPatternMatcher(Component protocol,
                          Component username,
                          Component password,
                          Component hostname,
                          Component port,
                          Component pathname,
                          Component search,
                          Component hash,
                          base::PassKey<SimpleUrlPatternMatcher>);

  SimpleUrlPatternMatcher(const SimpleUrlPatternMatcher&) = delete;
  SimpleUrlPatternMatcher& operator=(const SimpleUrlPatternMatcher&) = delete;
  ~SimpleUrlPatternMatcher();

  // Performs `test` method of URLPattern with a URL.
  // https://urlpattern.spec.whatwg.org/#dom-urlpattern-test
  bool Match(const GURL& url) const;

  // Convenience method for testing against only the `host` component of the
  // pattern. Used by Connection Allowlists
  // (https://wicg.github.io/connection-allowlists/) to determine if host
  // resolution requests for a given host are allowed. From a spec standpoint,
  // this is equivalent to creating a new URLPattern using only the `host`
  // component of this pattern, and testing against the new pattern instead.
  bool HostOnlyMatch(const GURL& url) const;

 private:
  friend class SimpleUrlPatternMatcherTest;

  // Creates a `PatternInit` with the result of parsing a constructor string and
  // apply `base_url` if given. And returns the result of processing a
  // URLPatternInit, and processing the default port of the protocol component.
  // If `base_url` is nullptr, relative or incomplete PatternInits produce an
  // error.
  // https://urlpattern.spec.whatwg.org/#parse-a-constructor-string
  // https://urlpattern.spec.whatwg.org/#process-a-urlpatterninit
  //
  // `protocol_component_out` and `protocol_matches_a_special_scheme_flag_out`
  // are optional out parameter. When they are set, this method fills them with
  // a Component for the protocol component and `protocol matches a special
  // scheme flag` in the spec, which are computed while parsing the constructor
  // string.
  // https://urlpattern.spec.whatwg.org/#compute-protocol-matches-a-special-scheme-flag
  static base::expected<PatternInit, std::string> CreatePatternInit(
      std::string_view url_pattern,
      const GURL* base_url,
      std::optional<Component>* protocol_component_out,
      bool* protocol_matches_a_special_scheme_flag_out);

  // Creates a SimpleUrlPatternMatcher from a `PatternInit` and a precomputed
  // protocol component, and a `protocol_matches_a_special_scheme_flag` flag.
  static base::expected<std::unique_ptr<SimpleUrlPatternMatcher>, std::string>
  CreateFromPatternInit(const PatternInit& pattern,
                        std::optional<Component> precomputed_protocol_component,
                        bool protocol_matches_a_special_scheme_flag);

  Component protocol_;
  Component username_;
  Component password_;
  Component hostname_;
  Component port_;
  Component pathname_;
  Component search_;
  Component hash_;
};

}  // namespace url_pattern

#endif  // COMPONENTS_URL_PATTERN_SIMPLE_URL_PATTERN_MATCHER_H_
