Commit f8ee6bd1 authored by Piotr Pawliczek's avatar Piotr Pawliczek Committed by Commit Bot

Simple URI: A class representing simplified URI - part 3

This is a part of implementation of class Uri. This CL contains
implementation of URI parser along with normalization and validation.
The file with unit tests was also included.
Other CLs:
 - https://crrev.com/c/2103143
 - https://crrev.com/c/2118426

BUG=chromium:821497
TEST=on my workstation

Change-Id: Ieb9f3c105d842d4cb2a8f2678d34130e3b94b374
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2123640
Commit-Queue: Piotr Pawliczek <pawliczek@chromium.org>
Reviewed-by: default avatarSean Kau <skau@chromium.org>
Cr-Commit-Position: refs/heads/master@{#761030}
parent d0e620d5
......@@ -71,8 +71,12 @@ component("chromeos") {
"printing/printer_configuration.h",
"printing/printer_translator.cc",
"printing/printer_translator.h",
"printing/uri.cc",
"printing/uri.h",
"printing/uri_components.cc",
"printing/uri_components.h",
"printing/uri_impl.cc",
"printing/uri_impl.h",
"printing/usb_printer_id.cc",
"printing/usb_printer_id.h",
"process_proxy/process_output_watcher.cc",
......@@ -200,6 +204,9 @@ test("chromeos_unittests") {
"printing/ppd_line_reader_unittest.cc",
"printing/printer_configuration_unittest.cc",
"printing/printer_translator_unittest.cc",
"printing/uri_unittest.cc",
"printing/uri_unittest.h",
"printing/uri_unittest_consistency.cc",
"printing/usb_printer_id_unittest.cc",
"process_proxy/process_output_watcher_unittest.cc",
"process_proxy/process_proxy_unittest.cc",
......
......@@ -104,6 +104,10 @@ bool HasNonASCII(const std::string& str) {
} // namespace
Uri::Pim::Pim() = default;
Uri::Pim::Pim(const Pim&) = default;
Uri::Pim::~Pim() = default;
Uri::Uri() : pim_(std::make_unique<Pim>()) {}
Uri::Uri(const std::string& uri) : pim_(std::make_unique<Pim>()) {
......@@ -178,6 +182,7 @@ std::string Uri::GetNormalized(bool always_print_port) const {
out.push_back('@');
}
// Host.
enc.Disallow(":");
enc.EncodeAndAppend(pim_->host(), &out);
// Port.
if (!port.empty()) {
......@@ -187,7 +192,7 @@ std::string Uri::GetNormalized(bool always_print_port) const {
}
// Adds Path.
enc.Allow("@");
enc.Allow(":@");
for (auto& segment : pim_->path()) {
out.push_back('/');
enc.EncodeAndAppend(segment, &out);
......
......@@ -10,6 +10,8 @@
#include <utility>
#include <vector>
#include "chromeos/chromeos_export.h"
namespace chromeos {
// This is a simple URI builder/parser.
......@@ -206,7 +208,7 @@ namespace chromeos {
// Case-sensitive : YES
//
class Uri {
class CHROMEOS_EXPORT Uri {
public:
enum class ParserStatus {
kNoErrors,
......@@ -236,6 +238,8 @@ class Uri {
struct ParserError {
ParserStatus status = ParserStatus::kNoErrors;
// The position in the input string where the parser error occurred.
// When an error occurred for %-escaped character, it is the position of
// the corresponding '%' sign.
// If |status| == kNoErrors, then this value is undefined.
size_t parsed_chars = 0;
// This field is relevant only for the methods SetQuery(...),
......@@ -361,7 +365,7 @@ class Uri {
bool operator!=(const Uri& uri) const { return !(*this == uri); }
private:
struct Pim;
class Pim;
std::unique_ptr<Pim> pim_;
};
......
This diff is collapsed.
......@@ -25,6 +25,10 @@ class Uri::Pim {
// The map with pairs scheme -> default_port.
static const std::map<std::string, int> kDefaultPorts;
Pim();
Pim(const Pim&);
~Pim();
// Resets the internal field |parser_error|.
void ResetParserError() {
parser_error_.parsed_chars = 0;
......
This diff is collapsed.
// 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.
#ifndef CHROMEOS_PRINTING_URI_UNITTEST_H_
#define CHROMEOS_PRINTING_URI_UNITTEST_H_
#include <string>
#include <utility>
#include <vector>
#include "chromeos/printing/uri.h"
// This file contains a declaration of struct and constant used only in the
// implementation of unit tests for class Uri declared in uri.h. This file is
// not supposed to be included anywhere outside the files uri_unittest*.cc.
namespace chromeos {
// All printable ASCII characters ('"' and '\' are escaped with \).
constexpr char kPrintableASCII[] =
" !\"#$%&'()*+,-./0123456789:;<=>?"
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
"`abcdefghijklmnopqrstuvwxyz{|}~";
namespace uri_unittest {
// A simple structure with all URI components.
struct UriComponents {
std::string scheme;
std::string userinfo;
std::string host;
int port = -1; // -1 means "unspecified"
std::vector<std::string> path;
std::vector<std::pair<std::string, std::string>> query;
std::string fragment;
UriComponents();
UriComponents(const UriComponents&);
UriComponents(
const std::string& scheme,
const std::string& userinfo,
const std::string& host,
int port = -1,
const std::vector<std::string>& path = {},
const std::vector<std::pair<std::string, std::string>>& query = {},
const std::string& fragment = "");
~UriComponents();
};
} // namespace uri_unittest
} // namespace chromeos
#endif // CHROMEOS_PRINTING_URI_UNITTEST_H_
// 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 "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) || allowed_chars.find(c) != std::string::npos) {
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 expected_uri = uri_.GetScheme() + ":";
// 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();
if (uri_.GetPort() != -1 &&
uri_.GetPort() != Uri::GetDefaultPort(uri_.GetScheme())) {
authority_encoded += ":" + base::NumberToString(uri_.GetPort());
}
// If Authority is not empty, add it to |expected_uri|.
if (!authority_encoded.empty())
expected_uri += "//" + authority_encoded;
// Add Path and Query.
expected_uri += uri_.GetPathEncodedAsString();
const std::string expected_query = uri_.GetQueryEncodedAsString();
if (!expected_query.empty())
expected_uri += "?" + expected_query;
// Add Fragment to |expected_uri|.
if (!uri_.GetFragmentEncoded().empty())
expected_uri += "#" + uri_.GetFragmentEncoded();
EXPECT_EQ(uri_.GetNormalized(), expected_uri);
}
// 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(), uri2.GetNormalized());
// 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment