// Copyright 2020 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 "chromeos/printing/uri_unittest.h" #include "base/strings/string_number_conversions.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& path, const std::vector>& 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 path = {kPrintableASCII}; std::vector> 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. std::for_each(host.begin(), host.end(), [](char& c) { if (c >= 'A' && c <= 'Z') c += 'a' - 'A'; }); 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> 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>; // 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