chromium/chromeos/printing/uri_unittest_consistency.cc

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

#include "base/containers/contains.h"
#include "base/strings/string_number_conversions.h"
#include "chromeos/printing/uri.h"
#include "chromeos/printing/uri_unittest.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace chromeos {
namespace {

using UriComponents = uri_unittest::UriComponents;

// Returns true <=> |c| belongs to STD_CHARS.
bool IsStdChar(char c) {
  if (c >= 'A' && c <= 'Z')
    return true;
  if (c >= 'a' && c <= 'z')
    return true;
  if (c >= '0' && c <= '9')
    return true;
  return (c == '-' || c == '.' || c == '_' || c == '~' || c == '!' ||
          c == '$' || c == '\'' || c == '(' || c == ')' || c == '*' ||
          c == ',' || c == ';');
}

// Returns a copy of |input| where all characters outside the set
// {STD_CHARS + |allowed_schars|} are replaced by %-escaped sequences.
std::string Encode(const std::string& input, const std::string& allowed_chars) {
  std::string out;
  for (char c : input) {
    if (IsStdChar(c) || base::Contains(allowed_chars, c)) {
      out.push_back(c);
    } else {
      out.push_back('%');
      out.append(base::HexEncode(&c, 1));
    }
  }
  return out;
}

// A version of Encode function for a different parameter type.
std::vector<std::string> Encode(const std::vector<std::string>& input,
                                const std::string& allowed_chars) {
  std::vector<std::string> v;
  for (auto& s : input)
    v.push_back(Encode(s, allowed_chars));
  return v;
}

// A version of Encode function for a different parameter type.
std::vector<std::pair<std::string, std::string>> Encode(
    const std::vector<std::pair<std::string, std::string>>& input,
    const std::string& allowed_chars) {
  std::vector<std::pair<std::string, std::string>> v;
  for (auto& p : input)
    v.push_back(std::make_pair(Encode(p.first, allowed_chars),
                               Encode(p.second, allowed_chars)));
  return v;
}

// This test suite consists of tests accepting a single parameter of type
// UriComponents. Each test creates Uri object from the parameter and checks
// its consistency by comparing results returned by different methods.
class UriConsistencyTest : public testing::TestWithParam<UriComponents> {
 public:
  void SetUp() override {
    const UriComponents& components = GetParam();
    uri_.SetFragment(components.fragment);
    ASSERT_EQ(uri_.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
    uri_.SetHost(components.host);
    ASSERT_EQ(uri_.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
    uri_.SetPath(components.path);
    ASSERT_EQ(uri_.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
    uri_.SetPort(components.port);
    ASSERT_EQ(uri_.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
    uri_.SetQuery(components.query);
    ASSERT_EQ(uri_.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
    uri_.SetScheme(components.scheme);
    ASSERT_EQ(uri_.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
    uri_.SetUserinfo(components.userinfo);
    ASSERT_EQ(uri_.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
  }

 protected:
  Uri uri_;
};

// Make sure that components returned by Get*Encoded() methods are %-escaped
// versions of components returned by corresponding Get*() methods.
TEST_P(UriConsistencyTest, ComponentsEncoding) {
  EXPECT_EQ(uri_.GetUserinfoEncoded(), Encode(uri_.GetUserinfo(), "+&=:"));
  EXPECT_EQ(uri_.GetHostEncoded(), Encode(uri_.GetHost(), "+&="));
  EXPECT_EQ(uri_.GetPathEncoded(), Encode(uri_.GetPath(), "+&=:@"));
  EXPECT_EQ(uri_.GetQueryEncoded(), Encode(uri_.GetQuery(), ":@/?"));
  EXPECT_EQ(uri_.GetFragmentEncoded(), Encode(uri_.GetFragment(), "+&=:@/?"));
}

// Build Path and verify GetPathEncodedAsString().
TEST_P(UriConsistencyTest, PathBuilding) {
  std::string expected_path;
  for (auto& segment : uri_.GetPathEncoded())
    expected_path += "/" + segment;
  EXPECT_EQ(expected_path, uri_.GetPathEncodedAsString());
}

// Build Query and verify GetQueryEncodedAsString().
TEST_P(UriConsistencyTest, QueryBuilding) {
  std::string expected_query;
  for (auto& param_value : uri_.GetQueryEncoded()) {
    if (!expected_query.empty())
      expected_query += "&";
    expected_query += param_value.first;
    if (!param_value.second.empty())
      expected_query += "=" + param_value.second;
  }
  EXPECT_EQ(expected_query, uri_.GetQueryEncodedAsString());
}

// Build normalized URI from encoded components and make sure that it is
// equal to the value returned by GetNormalized().
TEST_P(UriConsistencyTest, UriBuilding) {
  std::string scheme = uri_.GetScheme();
  if (!scheme.empty())
    scheme += ":";

  // Build a part of URI called Authority (Userinfo@Host:Port).
  std::string authority_encoded;
  if (!uri_.GetUserinfoEncoded().empty())
    authority_encoded = uri_.GetUserinfoEncoded() + "@";
  authority_encoded += uri_.GetHostEncoded();
  std::string authority_with_port_encoded = authority_encoded;
  if (uri_.GetPort() != -1) {
    if (uri_.GetPort() != Uri::GetDefaultPort(uri_.GetScheme()))
      authority_encoded += ":" + base::NumberToString(uri_.GetPort());
    authority_with_port_encoded += ":" + base::NumberToString(uri_.GetPort());
  }
  // If Authority is not empty, prefix it with "//".
  if (!authority_encoded.empty())
    authority_encoded = "//" + authority_encoded;
  if (!authority_with_port_encoded.empty())
    authority_with_port_encoded = "//" + authority_with_port_encoded;

  // Build Path, Query and Fragment.
  std::string path_query_fragment_encoded = uri_.GetPathEncodedAsString();
  if (!uri_.GetQueryEncodedAsString().empty())
    path_query_fragment_encoded += "?" + uri_.GetQueryEncodedAsString();
  if (!uri_.GetFragmentEncoded().empty())
    path_query_fragment_encoded += "#" + uri_.GetFragmentEncoded();

  EXPECT_EQ(uri_.GetNormalized(false),
            scheme + authority_encoded + path_query_fragment_encoded);
  EXPECT_EQ(uri_.GetNormalized(true),
            scheme + authority_with_port_encoded + path_query_fragment_encoded);
}

// Checks if the normalization algorithm is consistent.
TEST_P(UriConsistencyTest, Normalization) {
  // Normalization of normalized uri must not change it.
  Uri uri2(uri_.GetNormalized());
  EXPECT_EQ(uri2.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
  EXPECT_EQ(uri_.GetNormalized(true), uri2.GetNormalized(true));
  EXPECT_EQ(uri_.GetNormalized(false), uri2.GetNormalized(false));

  // Normalization of normalized Path must not change it.
  uri2.SetPathEncoded(uri_.GetPathEncodedAsString());
  EXPECT_EQ(uri2.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
  EXPECT_EQ(uri_.GetPath(), uri2.GetPath());

  // Normalization of normalized Query must not change it.
  uri2.SetQueryEncoded(uri_.GetQueryEncodedAsString());
  EXPECT_EQ(uri2.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
  EXPECT_EQ(uri_.GetQuery(), uri2.GetQuery());
}

INSTANTIATE_TEST_SUITE_P(
    UriConsistencyTestInstantiation,
    UriConsistencyTest,
    testing::Values(
        UriComponents(),  // empty URI
        UriComponents("ExAmplE+SchemA-X",
                      "",
                      "ExAmplE.COM",
                      123,
                      {"D", "E"},
                      {{"F", "G"}, {"H", "I"}},
                      "J"),
        UriComponents("",
                      kPrintableASCII,
                      kPrintableASCII,
                      0,
                      {kPrintableASCII},
                      {{kPrintableASCII, kPrintableASCII}},
                      kPrintableASCII),
        UriComponents("A+1-b.C", "", "", -1, {"//", " "}, {}, "?#@/"),
        UriComponents("http",
                      "",
                      "utf8.test",
                      -1,
                      {},
                      {{"zażółć", "za\xc5\xbc\xc3\xb3\xc5\x82\xc4\x87"},
                       {"gęślą", "\x67\xC4\x99\xC5\x9B\x6C\xC4\x85"},
                       {"jaźń", "ja\xc5\xba\xc5\x84"}})));

}  // namespace
}  // namespace chromeos