// 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 "chromeos/printing/uri_unittest.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "chromeos/printing/uri.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace uri_unittest {
UriComponents::UriComponents() = default;
UriComponents::UriComponents(const UriComponents&) = default;
UriComponents::UriComponents(
const std::string& scheme,
const std::string& userinfo,
const std::string& host,
int port,
const std::vector<std::string>& path,
const std::vector<std::pair<std::string, std::string>>& query,
const std::string& fragment)
: scheme(scheme),
userinfo(userinfo),
host(host),
port(port),
path(path),
query(query),
fragment(fragment) {}
UriComponents::~UriComponents() = default;
} // namespace uri_unittest
namespace {
using UriComponents = uri_unittest::UriComponents;
// Verifies that |components| set by Set*() methods produces given
// |normalized_uri|. Runs also consistency test on the created Uri object.
void TestBuilder(const UriComponents& components,
const std::string& normalized_uri) {
Uri uri;
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);
// Check URI.
EXPECT_EQ(uri.GetNormalized(false), normalized_uri);
}
// Verifies that |input_uri| set as parameter in Uri constructor is parsed
// as |components|. Runs also consistency test on the created Uri object.
void TestParser(const std::string& input_uri, const UriComponents& components) {
Uri uri(input_uri);
ASSERT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
// Check components values.
EXPECT_EQ(uri.GetScheme(), components.scheme);
EXPECT_EQ(uri.GetUserinfo(), components.userinfo);
EXPECT_EQ(uri.GetHost(), components.host);
EXPECT_EQ(uri.GetPort(), components.port);
EXPECT_EQ(uri.GetPath(), components.path);
EXPECT_EQ(uri.GetQuery(), components.query);
EXPECT_EQ(uri.GetFragment(), components.fragment);
}
// Verifies that |input_uri| set as parameter in Uri constructor is normalized
// to |normalized_uri|. Runs also consistency test on the created Uri object.
void TestNormalization(const std::string& input_uri,
const std::string& normalized_uri) {
Uri uri(input_uri);
ASSERT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
EXPECT_EQ(uri.GetNormalized(false), normalized_uri);
}
TEST(UriTest, DefaultConstructor) {
Uri uri;
EXPECT_EQ(uri.GetNormalized(), "");
EXPECT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
EXPECT_EQ(uri.GetScheme(), "");
EXPECT_EQ(uri.GetUserinfo(), "");
EXPECT_EQ(uri.GetUserinfoEncoded(), "");
EXPECT_EQ(uri.GetHost(), "");
EXPECT_EQ(uri.GetHostEncoded(), "");
EXPECT_EQ(uri.GetPort(), -1);
EXPECT_TRUE(uri.GetPath().empty());
EXPECT_TRUE(uri.GetPathEncoded().empty());
EXPECT_TRUE(uri.GetQuery().empty());
EXPECT_TRUE(uri.GetQueryEncoded().empty());
EXPECT_EQ(uri.GetFragment(), "");
EXPECT_EQ(uri.GetFragmentEncoded(), "");
}
TEST(UriTest, SchemeIsCaseInsensitive) {
Uri uri;
uri.SetScheme("ExAmplE+SchemA-X");
EXPECT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
EXPECT_EQ(uri.GetScheme(), "example+schema-x");
}
TEST(UriTest, HostIsCaseInsensitive) {
Uri uri;
uri.SetHost("ExAmplE.COM");
EXPECT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
EXPECT_EQ(uri.GetHost(), "example.com");
EXPECT_EQ(uri.GetHostEncoded(), "example.com");
}
TEST(UriTest, EncodingInHostComponent) {
Uri uri;
uri.SetHost("new.EX%41MPLE.COM");
EXPECT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
EXPECT_EQ(uri.GetHost(), "new.ex%41mple.com");
EXPECT_EQ(uri.GetHostEncoded(),
"new.ex%2541mple.com"); // %-character was escaped
uri.SetHostEncoded("new.EX%41MPLE.COM");
EXPECT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
EXPECT_EQ(uri.GetHost(), "new.example.com");
EXPECT_EQ(uri.GetHostEncoded(), "new.example.com");
uri.SetHost("ExAmPlE._!_@_#_$_%_^_");
EXPECT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
EXPECT_EQ(uri.GetHost(), "example._!_@_#_$_%_^_");
EXPECT_EQ(uri.GetHostEncoded(), "example._!_%40_%23_$_%25_%5E_");
uri.SetHostEncoded("ExAmPlE._!_@_#_$_%25_^_._%21_%40_%23_%24_%25_%5E_");
EXPECT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
EXPECT_EQ(uri.GetHost(), "example._!_@_#_$_%_^_._!_@_#_$_%_^_");
EXPECT_EQ(uri.GetHostEncoded(),
"example._!_%40_%23_$_%25_%5E_._!_%40_%23_$_%25_%5E_");
}
TEST(UriTest, SetPortFromString) {
Uri uri1;
Uri uri2;
EXPECT_TRUE(uri1.SetPort(1234));
EXPECT_TRUE(uri2.SetPort("1234"));
EXPECT_EQ(uri1, uri2);
// -1 and empty string mean "unspecified port".
EXPECT_TRUE(uri1.SetPort(-1));
EXPECT_TRUE(uri2.SetPort(""));
EXPECT_EQ(uri1, uri2);
EXPECT_FALSE(uri2.SetPort("65536"));
EXPECT_FALSE(uri2.SetPort("-2"));
EXPECT_FALSE(uri2.SetPort("+2"));
EXPECT_FALSE(uri2.SetPort(" 2133"));
EXPECT_FALSE(uri2.SetPort("0x123"));
}
TEST(UriTest, UriWithAllPrintableASCII) {
Uri uri;
std::string host = kPrintableASCII;
const std::vector<std::string> path = {kPrintableASCII};
std::vector<std::pair<std::string, std::string>> query = {
{kPrintableASCII, kPrintableASCII}};
uri.SetUserinfo(kPrintableASCII);
ASSERT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
uri.SetHost(host);
ASSERT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
uri.SetPath(path);
ASSERT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
uri.SetQuery(query);
ASSERT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
uri.SetFragment(kPrintableASCII);
ASSERT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
// Host is case-insensitive, uppercase letters are normalized to lowercase.
base::ranges::transform(host, host.begin(), &base::ToLowerASCII<char>);
EXPECT_EQ(uri.GetUserinfo(), kPrintableASCII);
EXPECT_EQ(uri.GetHost(), host);
EXPECT_EQ(uri.GetPath(), path);
EXPECT_EQ(uri.GetQuery(), query);
EXPECT_EQ(uri.GetFragment(), kPrintableASCII);
}
TEST(UriTest, BuildingHttpUriWithQuery) {
UriComponents components("http", "", "example.com", 1234);
components.query = {{"par1", "val1"}, {"par2", ""}, {"par3", "val3"}};
TestBuilder(components, "http://example.com:1234?par1=val1&par2&par3=val3");
}
TEST(UriTest, BuildingUriWithAllComponents) {
UriComponents components("A", "B", "C", 1);
components.path = {"D", "E"};
components.query = {{"F", "G"}, {"H", "I"}};
components.fragment = "J";
TestBuilder(components, "a://B@c:1/D/E?F=G&H=I#J");
}
TEST(UriTest, BuildingUriWithoutAuthority) {
UriComponents components("A+1-b.C", "", "", -1);
components.path = {"//", " "};
components.fragment = "?#@/";
TestBuilder(components, "a+1-b.c:/%2F%2F/%20#?%23@/");
}
// Special path segments "." and ".." are reduced when possible.
TEST(UriTest, ParsingOfUriWithReduciblePath) {
const std::string input_uri =
"hTTp://exAmple.c%4Fm:234"
"/very/../../long/.././pAth?parAm=vAlue#?%3f?";
UriComponents components("http", "", "example.com", 234);
components.path = {"..", "pAth"};
components.query = {{"parAm", "vAlue"}};
components.fragment = "???";
TestParser(input_uri, components);
}
TEST(UriTest, ParsingOfUriWithoutPort) {
// When a Port is not specified and the Scheme has a default port number,
// the default port number is set.
TestParser("hTTp://exAmple.com",
UriComponents("http", "", "example.com", 80));
// When the Scheme does not have a default port number, the value of Port
// remains "unspecified".
TestParser("X-x://exAmple.com", UriComponents("x-x", "", "example.com"));
}
TEST(UriTest, ParsingOfUriWithUTF8Characters) {
// On the input, bytes defining UTF-8 characters can be %-escaped or
// specified directly.
const std::string uri =
"http://utf8.test?"
"zażółć=za%c5%bc%c3%b3%c5%82%c4%87&"
"gęślą=\x67\xC4\x99\xC5%9B%6C%C4\x85&"
"jaźń=ja%c5%ba%c5%84";
UriComponents components("http", "", "utf8.test", 80);
components.query = {
{"zażółć", "zażółć"}, {"gęślą", "gęślą"}, {"jaźń", "jaźń"}};
TestParser(uri, components);
}
// Leading and trailing whitespaces are ignored.
TEST(UriTest, ParsingOfUriWithLeadingAndTrailingWhitespaces) {
const std::string uri = " \t\n\r\f\vSC://WITH.whitespaces# END \t\n\r\f\v";
UriComponents components("sc", "", "with.whitespaces");
components.fragment = " END";
TestParser(uri, components);
}
// Empty components are accepted.
TEST(UriTest, NormalizationOfEmptyUri) {
TestNormalization("://@:/?#", "");
}
TEST(UriTest, NormalizationOfUriWithoutAuthority) {
// When Userinfo, Host and Port are not specified, the "//" prefix is
// skipped.
TestNormalization("xx://@:/my/path?#fragment", "xx:/my/path#fragment");
TestNormalization("xx:///my/path?#fragment", "xx:/my/path#fragment");
// The same happens when the Port number is equal to the default port number
// of the Scheme.
TestNormalization("http://:80/my/path?#fragment", "http:/my/path#fragment");
}
// In the normalized URI, all bytes being part of UTF-8 characters must be
// %-escaped.
TEST(UriTest, NormalizationOfUriWithUTF8Characters) {
const std::string uri =
"http://utf8.test?"
"zażółć=za%c5%bc%c3%b3%c5%82%c4%87&"
"gęślą=\x67\xC4\x99\xC5%9B%6C%C4\x85&"
"jaźń=ja%c5%ba%c5%84";
const std::string uri_normalized =
"http://utf8.test?"
"za%C5%BC%C3%B3%C5%82%C4%87=za%C5%BC%C3%B3%C5%82%C4%87&"
"g%C4%99%C5%9Bl%C4%85=g%C4%99%C5%9Bl%C4%85&"
"ja%C5%BA%C5%84=ja%C5%BA%C5%84";
TestNormalization(uri, uri_normalized);
}
TEST(UriTest, ParserErrorDisallowedASCIICharacter) {
// Non-printable character (0xFF) inside the Host component.
Uri uri(" \t\n\r\f\vHTTP://BAD.\xff.CHaracter# \t\n\r\f\v");
const Uri::ParserError pe = uri.GetLastParsingError();
EXPECT_EQ(pe.status, Uri::ParserStatus::kDisallowedASCIICharacter);
EXPECT_EQ(pe.parsed_chars, 17u);
EXPECT_EQ(pe.parsed_strings, 0u);
}
TEST(UriTest, ParserErrorInvalidPercentEncoding) {
Uri uri;
// The first percent character has no following ASCII code.
uri.SetHostEncoded("ExAmPlE._!_@_#_$_%_^_._%21_%40_%23_%24_%25_%5E_");
EXPECT_EQ(uri.GetLastParsingError().status,
Uri::ParserStatus::kInvalidPercentEncoding);
EXPECT_EQ(uri.GetLastParsingError().parsed_chars, 17u);
}
TEST(UriTest, ParserErrorInvalidUTF8Character) {
// Broken UTF-8 character in the Path (the byte after 0xC5 is wrong).
Uri uri("http://host/utf8_\xC5\x3C_is_broken");
const Uri::ParserError pe = uri.GetLastParsingError();
EXPECT_EQ(pe.status, Uri::ParserStatus::kInvalidUTF8Character);
EXPECT_EQ(pe.parsed_chars, 18u);
EXPECT_EQ(pe.parsed_strings, 0u);
}
// Parameters in Query cannot have empty names.
TEST(UriTest, ParserErrorEmptyParameterNameInQuery) {
Uri uri;
std::vector<std::pair<std::string, std::string>> query;
query = {{"name1", "value1"}, {"", "value2"}};
EXPECT_FALSE(uri.SetQuery(query));
const Uri::ParserError pe1 = uri.GetLastParsingError();
EXPECT_EQ(pe1.status, Uri::ParserStatus::kEmptyParameterNameInQuery);
EXPECT_EQ(pe1.parsed_chars, 0u);
EXPECT_EQ(pe1.parsed_strings, 2u);
}
// Port number cannot have non-digit characters.
TEST(UriTest, ParserErrorInvalidPortNumber) {
Uri uri("http://my.weird.port.number:+123");
const Uri::ParserError pe = uri.GetLastParsingError();
EXPECT_EQ(pe.status, Uri::ParserStatus::kInvalidPortNumber);
EXPECT_EQ(pe.parsed_chars, 28u);
EXPECT_EQ(pe.parsed_strings, 0u);
}
// Path cannot have empty segments.
TEST(UriTest, ParserErrorEmptySegmentInPath) {
Uri uri;
EXPECT_FALSE(uri.SetPathEncoded("/segment1//segment3"));
const Uri::ParserError pe2 = uri.GetLastParsingError();
EXPECT_EQ(pe2.status, Uri::ParserStatus::kEmptySegmentInPath);
EXPECT_EQ(pe2.parsed_chars, 10u);
EXPECT_EQ(pe2.parsed_strings, 0u);
}
TEST(UriTest, ParserErrorInPath) {
// Non-printable character (0xBA) inside the path.
Uri uri(
" HTTP://example.org/aa/\xba_d/cc"
"?name1&name2=param2&\xba_d=character#here\xba ");
const Uri::ParserError pe = uri.GetLastParsingError();
EXPECT_EQ(pe.status, Uri::ParserStatus::kDisallowedASCIICharacter);
EXPECT_EQ(pe.parsed_chars, 24u);
EXPECT_EQ(pe.parsed_strings, 0u);
}
TEST(UriTest, ParserErrorInQuery) {
// Non-printable character (0xBA) inside the query.
Uri uri(
" HTTP://example.org/aa/bb/cc"
"?name1&name2=param2&\xba_d=character#here\xba ");
const Uri::ParserError pe = uri.GetLastParsingError();
EXPECT_EQ(pe.status, Uri::ParserStatus::kDisallowedASCIICharacter);
EXPECT_EQ(pe.parsed_chars, 49u);
EXPECT_EQ(pe.parsed_strings, 0u);
}
TEST(UriTest, ParserErrorInFragment) {
// Non-printable character (0xBA) inside the fragment.
Uri uri(
" HTTP://example.org/aa/bb/cc"
"?name1&name2=param2&good=character#here\xba ");
const Uri::ParserError pe = uri.GetLastParsingError();
EXPECT_EQ(pe.status, Uri::ParserStatus::kDisallowedASCIICharacter);
EXPECT_EQ(pe.parsed_chars, 68u);
EXPECT_EQ(pe.parsed_strings, 0u);
}
TEST(UriTest, GetQueryAsMap) {
Uri uri("ipp://example.org?p1&p2=val&p1=123&p3=aa&p1=&p2=val&other=x&end");
EXPECT_EQ(uri.GetLastParsingError().status, Uri::ParserStatus::kNoErrors);
using KeyValue = std::pair<std::string, std::vector<std::string>>;
// Parameters from the query sorted by keys.
const KeyValue e1("end", {""});
const KeyValue e2("other", {"x"});
const KeyValue e3("p1", {"", "123", ""});
const KeyValue e4("p2", {"val", "val"});
const KeyValue e5("p3", {"aa"});
EXPECT_THAT(uri.GetQueryAsMap(), testing::ElementsAre(e1, e2, e3, e4, e5));
}
} // namespace
} // namespace chromeos