// 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.

#include "chrome/browser/web_applications/url_pattern_with_regex_matcher.h"

#include "base/types/expected.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/status/status.h"
#include "third_party/blink/public/common/safe_url_pattern.h"
#include "third_party/liburlpattern/parse.h"
#include "third_party/liburlpattern/pattern.h"
#include "url/gurl.h"

namespace {

std::vector<liburlpattern::Part> ParsePatternInitField(std::string_view field) {
  base::expected<liburlpattern::Pattern, absl::Status> parse_result =
      liburlpattern::Parse(
          field, [](std::string_view input) { return std::string(input); });

  // Should never fail because the input is coming from the test.
  DCHECK(parse_result.has_value());

  return parse_result.value().PartList();
}

TEST(UrlPatternWithRegexMatcher, Empty) {
  blink::SafeUrlPattern pattern;
  web_app::UrlPatternWithRegexMatcher matcher(pattern);

  // All fields can match any value.
  EXPECT_TRUE(matcher.Match(
      GURL("http://user:pass@example.com:1234/foo/bar?x=y#anchor")));
}

TEST(UrlPatternWithRegexMatcher, Protocol) {
  blink::SafeUrlPattern pattern;
  pattern.protocol = ParsePatternInitField("http*");
  web_app::UrlPatternWithRegexMatcher matcher(pattern);

  // Basic test.
  EXPECT_TRUE(matcher.Match(GURL("https://example.com/foo/bar")));

  // All other fields can match any value.
  EXPECT_TRUE(matcher.Match(
      GURL("http://user:pass@example.org:1234/foo/bar?x=y#anchor")));

  // Fail if the scheme does not match.
  EXPECT_FALSE(matcher.Match(GURL("ftp://example.com/foo/bar")));
}

TEST(UrlPatternWithRegexMatcher, Hostname) {
  blink::SafeUrlPattern pattern;
  pattern.hostname = ParsePatternInitField("example.*.com");
  web_app::UrlPatternWithRegexMatcher matcher(pattern);

  // Basic test.
  EXPECT_TRUE(matcher.Match(GURL("https://example.a.com/foo/bar")));

  // All other fields can match any value.
  EXPECT_TRUE(matcher.Match(
      GURL("https://user:pass@example.b.com:1234/foo/bar?x=y#anchor")));

  // Fail if the hostname does not match.
  EXPECT_FALSE(matcher.Match(GURL("https://example.a.org/foo/bar")));
}

TEST(UrlPatternWithRegexMatcher, Port) {
  blink::SafeUrlPattern pattern;
  pattern.port = ParsePatternInitField("12*4");
  web_app::UrlPatternWithRegexMatcher matcher(pattern);

  // Basic test.
  EXPECT_TRUE(matcher.Match(GURL("https://example.com:1234/foo/bar")));

  // All other fields can match any value.
  EXPECT_TRUE(matcher.Match(
      GURL("https://user:pass@example.com:12984/foo/bar?x=y#anchor")));

  // Fail if the port does not match.
  EXPECT_FALSE(matcher.Match(GURL("https://example.com:1233/foo/bar")));
}

TEST(UrlPatternWithRegexMatcher, Pathname) {
  blink::SafeUrlPattern pattern;
  pattern.pathname = ParsePatternInitField("/foo/*/bar");
  web_app::UrlPatternWithRegexMatcher matcher(pattern);

  // Basic test.
  EXPECT_TRUE(matcher.Match(GURL("https://example.com/foo/x/bar")));

  // All other fields can match any value.
  EXPECT_TRUE(matcher.Match(
      GURL("https://user:pass@example.org:1234/foo/y/bar?x=y#anchor")));

  // Fail if the path does not match.
  EXPECT_FALSE(matcher.Match(GURL("https://example.com/foo/x/baz")));
}

TEST(UrlPatternWithRegexMatcher, Search) {
  blink::SafeUrlPattern pattern;
  pattern.search = ParsePatternInitField("x=*&p=q");
  web_app::UrlPatternWithRegexMatcher matcher(pattern);

  // Basic test.
  EXPECT_TRUE(matcher.Match(GURL("https://example.com/foo/bar?x=y&p=q")));

  // All other fields can match any value.
  EXPECT_TRUE(matcher.Match(
      GURL("https://user:pass@example.com:1234/foo/bar?x=z&p=q#anchor")));

  // Fail if the search does not match.
  EXPECT_FALSE(matcher.Match(GURL("https://example.com/foo/bar?x=y&p=r")));

  // The order of query parameters matters (i.e. this is a simple string match).
  EXPECT_FALSE(matcher.Match(GURL("https://example.com/foo/bar?p=q&x=y")));
}

TEST(UrlPatternWithRegexMatcher, Hash) {
  blink::SafeUrlPattern pattern;
  pattern.hash = ParsePatternInitField("tr*t");
  web_app::UrlPatternWithRegexMatcher matcher(pattern);

  // Basic test.
  EXPECT_TRUE(matcher.Match(GURL("https://example.com/foo/bar#treat")));

  // All other fields can match any value.
  EXPECT_TRUE(matcher.Match(
      GURL("https://user:pass@example.com:1234/foo/bar?x=y#trout")));

  // Fail if the search does not match.
  EXPECT_FALSE(matcher.Match(GURL("https://example.com/foo/bar#thought")));
}

TEST(UrlPatternWithRegexMatcher, All) {
  blink::SafeUrlPattern pattern;
  pattern.protocol = ParsePatternInitField("http*");
  pattern.username = ParsePatternInitField("us*r");
  pattern.password = ParsePatternInitField("pa*word");
  pattern.hostname = ParsePatternInitField("example.*.com");
  pattern.port = ParsePatternInitField("12*4");
  pattern.pathname = ParsePatternInitField("/foo/*/bar");
  pattern.search = ParsePatternInitField("x=*&p=q");
  pattern.hash = ParsePatternInitField("tr*t");
  web_app::UrlPatternWithRegexMatcher matcher(pattern);

  // Basic test.
  EXPECT_TRUE(matcher.Match(GURL(
      "https://user:password@example.a.com:1234/foo/x/bar?x=y&p=q#treat")));

  // Change all the fields but still match.
  EXPECT_TRUE(matcher.Match(GURL(
      "https://usir:pantword@example.b.com:12984/foo/y/bar?x=z&p=q#trout")));

  // If any one field doesn't match, the match fails.
  EXPECT_FALSE(matcher.Match(
      GURL("ftp://user:password@example.a.com:1234/foo/x/bar?x=y&p=q#treat")));
  EXPECT_FALSE(matcher.Match(GURL(
      "https://uber:password@example.a.com:1234/foo/x/bar?x=y&p=q#treat")));
  EXPECT_FALSE(matcher.Match(GURL(
      "https://user:passworn@example.a.com:1234/foo/x/bar?x=y&p=q#treat")));
  EXPECT_FALSE(matcher.Match(GURL(
      "https://user:password@example.a.org:1234/foo/x/bar?x=y&p=q#treat")));
  EXPECT_FALSE(matcher.Match(GURL(
      "https://user:password@example.a.com:1233/foo/x/bar?x=y&p=q#treat")));
  EXPECT_FALSE(matcher.Match(GURL(
      "https://user:password@example.a.com:1234/foo/x/baz?x=y&p=q#treat")));
  EXPECT_FALSE(matcher.Match(GURL(
      "https://user:password@example.a.com:1234/foo/x/bar?x=y&p=r#treat")));
  EXPECT_FALSE(matcher.Match(GURL(
      "https://user:password@example.a.com:1234/foo/x/bar?x=y&p=q#thought")));
}

}  // namespace
