// 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/renderer/searchbox/searchbox.h"

#include <map>
#include <string>

#include "base/basictypes.h"
#include "chrome/common/instant_types.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace {

const auto FAVICON = SearchBox::FAVICON;
const auto LARGE_ICON = SearchBox::LARGE_ICON;
const auto FALLBACK_ICON = SearchBox::FALLBACK_ICON;
const auto THUMB = SearchBox::THUMB;

const char* kUrlString1 = "http://www.google.com";
const char* kUrlString2 = "http://www.chromium.org/path/q=3#r=4";
const char* kUrlString3 = "http://www.youtube.com:8080/hosps";

// Mock helper to test internal::TranslateIconRestrictedUrl().
class MockIconURLHelper: public SearchBox::IconURLHelper {
 public:
  MockIconURLHelper();
  ~MockIconURLHelper() override;
  int GetViewID() const override;
  std::string GetURLStringFromRestrictedID(InstantRestrictedID rid) const
      override;

 private:
  std::map<InstantRestrictedID, std::string> rid_to_url_string_;
};

MockIconURLHelper::MockIconURLHelper() {
  rid_to_url_string_[1] = kUrlString1;
  rid_to_url_string_[2] = kUrlString2;
  rid_to_url_string_[3] = kUrlString3;
}

MockIconURLHelper::~MockIconURLHelper() {
}

int MockIconURLHelper::GetViewID() const {
  return 137;
}

std::string MockIconURLHelper::GetURLStringFromRestrictedID(
    InstantRestrictedID rid) const {
  auto it = rid_to_url_string_.find(rid);
  return it == rid_to_url_string_.end() ? std::string() : it->second;
}

}  // namespace

namespace internal {

// Defined in searchbox.cc
bool ParseViewIdAndRestrictedId(const std::string& id_part,
                                int* view_id_out,
                                InstantRestrictedID* rid_out);

// Defined in searchbox.cc
bool ParseIconRestrictedUrl(const GURL& url,
                            SearchBox::ImageSourceType type,
                            std::string* param_part,
                            int* view_id,
                            InstantRestrictedID* rid);

// Defined in searchbox.cc
bool TranslateIconRestrictedUrl(const GURL& transient_url,
                                SearchBox::ImageSourceType type,
                                const SearchBox::IconURLHelper& helper,
                                GURL* url);

TEST(SearchBoxUtilTest, ParseViewIdAndRestrictedIdSuccess) {
  int view_id = -1;
  InstantRestrictedID rid = -1;

  EXPECT_TRUE(ParseViewIdAndRestrictedId("2/3", &view_id, &rid));
  EXPECT_EQ(2, view_id);
  EXPECT_EQ(3, rid);

  EXPECT_TRUE(ParseViewIdAndRestrictedId("0/0", &view_id, &rid));
  EXPECT_EQ(0, view_id);
  EXPECT_EQ(0, rid);

  EXPECT_TRUE(ParseViewIdAndRestrictedId("1048576/314", &view_id, &rid));
  EXPECT_EQ(1048576, view_id);
  EXPECT_EQ(314, rid);

  // Odd but not fatal.
  EXPECT_TRUE(ParseViewIdAndRestrictedId("00/09", &view_id, &rid));
  EXPECT_EQ(0, view_id);
  EXPECT_EQ(9, rid);

  // Tolerates multiple, leading, and trailing "/".
  EXPECT_TRUE(ParseViewIdAndRestrictedId("2////3", &view_id, &rid));
  EXPECT_EQ(2, view_id);
  EXPECT_EQ(3, rid);

  EXPECT_TRUE(ParseViewIdAndRestrictedId("5/6/", &view_id, &rid));
  EXPECT_EQ(5, view_id);
  EXPECT_EQ(6, rid);

  EXPECT_TRUE(ParseViewIdAndRestrictedId("/7/8", &view_id, &rid));
  EXPECT_EQ(7, view_id);
  EXPECT_EQ(8, rid);
}

TEST(SearchBoxUtilTest, ParseViewIdAndRestrictedIdFailure) {
  const char* test_cases[] = {
    "",
    "    ",
    "/",
    "2/",
    "/3",
    "2a/3",
    "2/3a",
    " 2/3",
    "2/ 3",
    "2 /3 ",
    "23",
    "2,3",
    "-2/3",
    "2/-3",
    "2/3/1",
    "blahblah",
    "0xA/0x10",
  };
  for (size_t i = 0; i < arraysize(test_cases); ++i) {
    int view_id = -1;
    InstantRestrictedID rid = -1;
    EXPECT_FALSE(ParseViewIdAndRestrictedId(test_cases[i], &view_id, &rid))
      << " for test_cases[" << i << "]";
    EXPECT_EQ(-1, view_id);
    EXPECT_EQ(-1, rid);
  }
}

TEST(SearchBoxUtilTest, ParseIconRestrictedUrlFaviconSuccess) {
  struct {
    SearchBox::ImageSourceType type;
    const char* transient_url_str;
    const char* expected_param_part;
    int expected_view_id;
    InstantRestrictedID expected_rid;
  } test_cases[] = {
    {FAVICON, "chrome-search://favicon/1/2", "", 1, 2},
    {FAVICON, "chrome-search://favicon/size/16@2x/3/4", "size/16@2x/", 3, 4},
    {FAVICON, "chrome-search://favicon/largest/5/6", "largest/", 5, 6},
    {FAVICON, "chrome-search://favicon/origin/7/8", "origin/", 7, 8},
    {FAVICON, "chrome-search://favicon/iconurl/9/10", "iconurl/", 9, 10},
    {LARGE_ICON, "chrome-search://large-icon/96/1/2", "96/", 1, 2},
    {LARGE_ICON, "chrome-search://large-icon/1/3/4", "1/", 3, 4},
    // Size restriction is *not* enforced during parsing, but later.
    {LARGE_ICON, "chrome-search://large-icon/1000000/5/6", "1000000/", 5, 6},
    {FALLBACK_ICON, "chrome-search://fallback-icon/,,,,/1/2", ",,,,/", 1, 2},
    {FALLBACK_ICON, "chrome-search://fallback-icon/1,,,,/3/4", "1,,,,/", 3, 4},
    {FALLBACK_ICON, "chrome-search://fallback-icon/64,fff,black,0.4,0.6/5/6",
         "64,fff,black,0.4,0.6/", 5, 6},
    {THUMB, "chrome-search://thumb/1/2", "", 1, 2},
  };
  for (size_t i = 0; i < arraysize(test_cases); ++i) {
    std::string param_part = "(unwritten)";
    int view_id = -1;
    InstantRestrictedID rid = -1;
    EXPECT_TRUE(ParseIconRestrictedUrl(GURL(test_cases[i].transient_url_str),
        test_cases[i].type, &param_part, &view_id, &rid))
            << " for test_cases[" << i << "]";
    EXPECT_EQ(test_cases[i].expected_param_part, param_part)
        << " for test_cases[" << i << "]";
    EXPECT_EQ(test_cases[i].expected_view_id, view_id)
        << " for test_cases[" << i << "]";
    EXPECT_EQ(test_cases[i].expected_rid, rid)
        << " for test_cases[" << i << "]";
  }
}

TEST(SearchBoxUtilTest, ParseIconRestrictedUrlFailure) {
  struct {
    SearchBox::ImageSourceType type;
    const char* transient_url_str;
  } test_cases[] = {
    {FAVICON, "chrome-search://favicon/"},
    {FAVICON, "chrome-search://favicon/3/"},
    {FAVICON, "chrome-search://favicon/size/3/4"},
    {FAVICON, "chrome-search://favicon/largest/http://www.google.com"},
    {FAVICON, "chrome-search://favicon/size/16@2x/-1/10"},
    {LARGE_ICON, "chrome-search://large-icon/"},
    {LARGE_ICON, "chrome-search://large-icon/3"},
    {LARGE_ICON, "chrome-search://large-icon/3/4"},
    {LARGE_ICON, "chrome-search://large-icon/-1/3/4"},
    {LARGE_ICON, "chrome-search://large-icon/0/3/4"},
    {LARGE_ICON, "chrome-search://large-icon/64/http://www.google.com"},
    {LARGE_ICON, "chrome-search://large-icon/bad-size/3/4"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/3"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/3/4"},
    {FALLBACK_ICON, "chrome-search://fallback-icon//3/4"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/,,/3/4"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/bad-spec/3/4"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/-1,,,,/3/4"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/,junk,,,/3/4"},
    // This case is a bit stringent. Since we always render fallback icons,
    // search privider can't use this to probe user history. We'll consider
    // relaxing the check if the need arises.
    {FALLBACK_ICON, "chrome-search://fallback-icon/,,,,/http://www.google.com"},
    {THUMB, "chrome-search://thumb"},
    {THUMB, "chrome-search://thumb/"},
    {THUMB, "chrome-search://thumb/123"},
    {THUMB, "chrome-search://thumb/xyz"},
    {THUMB, "chrome-search://thumb/123/"},
    {THUMB, "chrome-search://thumb/123/xyz"},
    {THUMB, "chrome-search://thumb/http://www.google.com"},
  };
  for (size_t i = 0; i < arraysize(test_cases); ++i) {
    std::string param_part = "(unwritten)";
    int view_id = -1;
    InstantRestrictedID rid = -1;
    EXPECT_FALSE(ParseIconRestrictedUrl(GURL(test_cases[i].transient_url_str),
        test_cases[i].type, &param_part, &view_id, &rid))
            << " for test_cases[" << i << "]";
    EXPECT_EQ("(unwritten)", param_part);
    EXPECT_EQ(-1, view_id);
    EXPECT_EQ(-1, rid);
  }
}

TEST(SearchBoxUtilTest, TranslateIconRestrictedUrlSuccess) {
  struct {
    SearchBox::ImageSourceType type;
    const char* transient_url_str;
    std::string expected_url_str;
  } test_cases[] = {
    {FAVICON, "chrome-search://favicon/137/1",
        std::string("chrome-search://favicon/") + kUrlString1},
    // FAVICON is permission: invalid input just yields default endpoint.
    {FAVICON, "chrome-search://favicon/", "chrome-search://favicon/"},
    {FAVICON, "chrome-search://favicon/314", "chrome-search://favicon/"},
    {FAVICON, "chrome-search://favicon/314/1", "chrome-search://favicon/"},
    {FAVICON, "chrome-search://favicon/137/255", "chrome-search://favicon/"},
    {FAVICON, "chrome-search://favicon/-3/-1", "chrome-search://favicon/"},
    {FAVICON, "chrome-search://favicon/invalidstuff",
        "chrome-search://favicon/"},
    {FAVICON, "chrome-search://favicon/size/16@2x/http://www.google.com",
        "chrome-search://favicon/"},
    // Other types of icons.
    {LARGE_ICON, "chrome-search://large-icon/64/137/2",
        std::string("chrome-search://large-icon/64/") + kUrlString2},
    {LARGE_ICON, "chrome-search://large-icon/1/137/1",
        std::string("chrome-search://large-icon/1/") + kUrlString1},
    {FALLBACK_ICON, "chrome-search://fallback-icon/,,,,/137/3",
        std::string("chrome-search://fallback-icon/,,,,/") + kUrlString3},
    {FALLBACK_ICON, "chrome-search://fallback-icon/64,fff,,,1/137/1",
        std::string("chrome-search://fallback-icon/64,fff,,,1/") + kUrlString1},
    {THUMB, "chrome-search://thumb/137/3",
        std::string("chrome-search://thumb/") + kUrlString3},
  };

  MockIconURLHelper helper;
  for (size_t i = 0; i < arraysize(test_cases); ++i) {
    GURL url;
    GURL transient_url(test_cases[i].transient_url_str);
    EXPECT_TRUE(TranslateIconRestrictedUrl(transient_url, test_cases[i].type,
                    helper, &url))
        << " for test_cases[" << i << "]";
    EXPECT_EQ(GURL(test_cases[i].expected_url_str), url)
        << " for test_cases[" << i << "]";
  }
}

// For Non-FAVICON only.
TEST(SearchBoxUtilTest, TranslateIconRestrictedUrlFailure) {
  struct {
    SearchBox::ImageSourceType type;
    const char* transient_url_str;
  } test_cases[] = {
    // Empty.
    {LARGE_ICON, "chrome-search://large-icon/"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/"},
    {THUMB, "chrome-search://thumb/"},
    // Bad view_id.
    {LARGE_ICON, "chrome-search://large-icon/64/314/2"},
    {LARGE_ICON, "chrome-search://large-icon/1/314/1"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/,,,,/314/3"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/64,fff,,,1/314/1"},
    {THUMB, "chrome-search://thumb/314/1"},
    // Missing rid.
    {LARGE_ICON, "chrome-search://large-icon/64/137/"},
    {LARGE_ICON, "chrome-search://large-icon/64/137/blah"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/,,,,/137/"},
    {THUMB, "chrome-search://thumb/314/"},
    // Bad params.
    {LARGE_ICON, "chrome-search://large-icon/137/2"},
    {LARGE_ICON, "chrome-search://large-icon//137/2"},
    {LARGE_ICON, "chrome-search://large-icon/96"},
    {LARGE_ICON, "chrome-search://large-icon/-1/137/2"},
    {LARGE_ICON, "chrome-search://large-icon/blah/137/2"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/137/3"},
    {FALLBACK_ICON, "chrome-search://fallback-icon//137/3"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/-64,fff,,,1/137/3"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/springfront/137/3"},
    // Use Page URL.
    {LARGE_ICON, "chrome-search://large-icon/96/http://www.google.com"},
    {FALLBACK_ICON, "chrome-search://fallback-icon/,,,,/http://www.google.com"},
    {THUMB, "chrome-search://thumb/http://www.google.com"},
  };

  MockIconURLHelper helper;
  for (size_t i = 0; i < arraysize(test_cases); ++i) {
    GURL url;
    GURL transient_url(test_cases[i].transient_url_str);
    EXPECT_FALSE(TranslateIconRestrictedUrl(transient_url, test_cases[i].type,
                     helper, &url))
        << " for test_cases[" << i << "]";
    EXPECT_TRUE(url.is_empty()) << " for test_cases[" << i << "]";
  }
}

}  // namespace internal
