#include "chromeos/printing/uri.h"
#include <algorithm>
#include <string_view>
#include "base/check_op.h"
#include "base/containers/fixed_flat_map.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "chromeos/printing/uri_impl.h"
namespace chromeos {
namespace {
constexpr unsigned char kFirstPrintableChar = 32;
constexpr unsigned char kLastPrintableChar = 126;
char ToHexDigit(uint8_t val) {
DCHECK_LT(val, 16);
if (val < 10)
return ('0' + val);
return ('A' + val - 10);
}
class Encoder {
public:
explicit Encoder(const std::string& additional) { Allow(additional); }
void Allow(const std::string& chars) {
for (char c : chars) {
const unsigned char uc = static_cast<unsigned char>(c);
DCHECK_GE(uc, kFirstPrintableChar);
DCHECK_LE(uc, kLastPrintableChar);
allowed_[uc - kFirstPrintableChar] = true;
}
}
void Disallow(const std::string& chars) {
for (char c : chars) {
const unsigned char uc = static_cast<unsigned char>(c);
DCHECK_GE(uc, kFirstPrintableChar);
DCHECK_LE(uc, kLastPrintableChar);
allowed_[uc - kFirstPrintableChar] = false;
}
}
void EncodeAndAppend(const std::string& str, std::string* out) const {
for (auto it = str.begin(); it < str.end(); ++it) {
const unsigned char uc = static_cast<unsigned char>(*it);
DCHECK_GE(uc, kFirstPrintableChar);
if (uc <= kLastPrintableChar && allowed_[uc - kFirstPrintableChar]) {
out->push_back(*it);
} else {
out->push_back('%');
out->push_back(ToHexDigit(uc >> 4));
out->push_back(ToHexDigit(uc & 0x0f));
}
}
}
std::string Encode(const std::string& str) const {
std::string out;
out.reserve(str.size() * 5 / 4);
EncodeAndAppend(str, &out);
return out;
}
private:
std::array<bool,95> allowed_ =
{ 0,1,0,0,1,0,0,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1
};
};
bool HasNonASCII(const std::string& str) {
return std::ranges::any_of(
str, [](char c) { return static_cast<unsigned char>(c) > 0x7f; });
}
constexpr auto kDefaultPorts =
base::MakeFixedFlatMap<std::string_view, int>({{"ipp", 631},
{"ipps", 443},
{"http", 80},
{"https", 443},
{"lpd", 515},
{"socket", 9100}});
}
Uri::Pim::Pim() = default;
Uri::Pim::Pim(const Pim&) = default;
Uri::Pim::~Pim() = default;
Uri::Uri() : pim_(std::make_unique<Pim>()) {}
Uri::Uri(std::string_view uri) : pim_(std::make_unique<Pim>()) {
const size_t prefix_size =
uri.size() -
base::TrimWhitespaceASCII(uri, base::TrimPositions::TRIM_LEADING).size();
if (prefix_size == uri.size())
return;
const size_t suffix_size =
uri.size() -
base::TrimWhitespaceASCII(uri, base::TrimPositions::TRIM_TRAILING).size();
pim_->ParseUri(uri.begin() + prefix_size, uri.end() - suffix_size);
pim_->parser_error().parsed_chars += prefix_size;
}
int Uri::GetDefaultPort(const std::string& scheme) {
auto it = kDefaultPorts.find(scheme);
return it != kDefaultPorts.end() ? it->second : -1;
}
Uri::Uri(const Uri& uri) : pim_(std::make_unique<Pim>(*uri.pim_)) {}
Uri::Uri(Uri&& uri) : pim_(std::make_unique<Pim>()) {
pim_.swap(uri.pim_);
}
Uri::~Uri() = default;
Uri& Uri::operator=(const Uri& uri) {
*pim_ = *uri.pim_;
return *this;
}
Uri& Uri::operator=(Uri&& uri) {
pim_.swap(uri.pim_);
return *this;
}
const Uri::ParserError& Uri::GetLastParsingError() const {
return pim_->parser_error();
}
std::string Uri::GetNormalized(bool always_print_port) const {
std::string port;
if (ShouldPrintPort(always_print_port))
port = base::NumberToString(pim_->port());
std::string out = pim_->scheme();
if (!out.empty())
out.push_back(':');
Encoder enc("+&=:");
if (!(pim_->userinfo().empty() && pim_->host().empty() && port.empty())) {
out.append("//");
if (!pim_->userinfo().empty()) {
enc.EncodeAndAppend(pim_->userinfo(), &out);
out.push_back('@');
}
enc.Disallow(":");
enc.EncodeAndAppend(pim_->host(), &out);
if (!port.empty()) {
out.push_back(':');
out.append(port);
}
}
enc.Allow(":@");
for (auto& segment : pim_->path()) {
out.push_back('/');
enc.EncodeAndAppend(segment, &out);
}
enc.Disallow("+&=");
enc.Allow("/?");
for (auto it = pim_->query().begin(); it != pim_->query().end(); ++it) {
if (it == pim_->query().begin()) {
out.push_back('?');
} else {
out.push_back('&');
}
enc.EncodeAndAppend(it->first, &out);
if (!it->second.empty()) {
out.push_back('=');
enc.EncodeAndAppend(it->second, &out);
}
}
enc.Allow("+&=");
if (!pim_->fragment().empty()) {
out.push_back('#');
enc.EncodeAndAppend(pim_->fragment(), &out);
}
return out;
}
bool Uri::IsASCII() const {
if (HasNonASCII(pim_->userinfo()) || HasNonASCII(pim_->host()) ||
HasNonASCII(pim_->fragment())) {
return false;
}
for (auto& s : pim_->path()) {
if (HasNonASCII(s))
return false;
}
for (auto& p : pim_->query()) {
if (HasNonASCII(p.first) || HasNonASCII(p.second))
return false;
}
return true;
}
std::string Uri::GetScheme() const {
return pim_->scheme();
}
bool Uri::SetScheme(const std::string& val) {
return pim_->ParseScheme(val.begin(), val.end());
}
int Uri::GetPort() const {
return pim_->port();
}
bool Uri::SetPort(int val) {
return pim_->SavePort(val);
}
bool Uri::SetPort(const std::string& port) {
return pim_->ParsePort(port.begin(), port.end());
}
std::string Uri::GetUserinfo() const {
return pim_->userinfo();
}
std::string Uri::GetHost() const {
return pim_->host();
}
std::vector<std::string> Uri::GetPath() const {
return pim_->path();
}
std::vector<std::pair<std::string, std::string>> Uri::GetQuery() const {
return pim_->query();
}
std::string Uri::GetFragment() const {
return pim_->fragment();
}
base::flat_map<std::string, std::vector<std::string>> Uri::GetQueryAsMap()
const {
base::flat_map<std::string, std::vector<std::string>> output;
for (const auto& [key, value] : pim_->query()) {
output[key].push_back(value);
}
return output;
}
std::string Uri::GetUserinfoEncoded() const {
Encoder enc("+&=:");
return enc.Encode(pim_->userinfo());
}
std::string Uri::GetHostEncoded() const {
Encoder enc("+&=");
return enc.Encode(pim_->host());
}
std::vector<std::string> Uri::GetPathEncoded() const {
Encoder enc("+&=:@");
std::vector<std::string> out(pim_->path().size());
for (size_t i = 0; i < out.size(); ++i)
out[i] = enc.Encode(pim_->path()[i]);
return out;
}
std::string Uri::GetPathEncodedAsString() const {
Encoder enc("+&=:@");
std::string out;
for (auto& segment : pim_->path())
out += "/" + enc.Encode(segment);
return out;
}
std::vector<std::pair<std::string, std::string>> Uri::GetQueryEncoded() const {
Encoder enc(":@/?");
std::vector<std::pair<std::string, std::string>> out(pim_->query().size());
for (size_t i = 0; i < out.size(); ++i) {
out[i].first = enc.Encode(pim_->query()[i].first);
out[i].second = enc.Encode(pim_->query()[i].second);
}
return out;
}
std::string Uri::GetQueryEncodedAsString() const {
Encoder enc(":@/?");
std::string out;
for (auto& param_value : pim_->query()) {
if (!out.empty())
out.push_back('&');
out += enc.Encode(param_value.first);
if (!param_value.second.empty())
out += "=" + enc.Encode(param_value.second);
}
return out;
}
std::string Uri::GetFragmentEncoded() const {
Encoder enc("+&=:@/?");
return enc.Encode(pim_->fragment());
}
bool Uri::SetUserinfo(const std::string& val) {
return pim_->SaveUserinfo<false>(val);
}
bool Uri::SetHost(const std::string& val) {
return pim_->SaveHost<false>(val);
}
bool Uri::SetPath(const std::vector<std::string>& val) {
return pim_->SavePath<false>(val);
}
bool Uri::SetQuery(
const std::vector<std::pair<std::string, std::string>>& val) {
return pim_->SaveQuery<false>(val);
}
bool Uri::SetFragment(const std::string& val) {
return pim_->SaveFragment<false>(val);
}
bool Uri::SetUserinfoEncoded(const std::string& val) {
return pim_->SaveUserinfo<true>(val);
}
bool Uri::SetHostEncoded(const std::string& val) {
return pim_->SaveHost<true>(val);
}
bool Uri::SetPathEncoded(const std::vector<std::string>& val) {
return pim_->SavePath<true>(val);
}
bool Uri::SetPathEncoded(const std::string& val) {
return pim_->ParsePath(val.begin(), val.end());
}
bool Uri::SetQueryEncoded(
const std::vector<std::pair<std::string, std::string>>& val) {
return pim_->SaveQuery<true>(val);
}
bool Uri::SetQueryEncoded(const std::string& val) {
return pim_->ParseQuery(val.begin(), val.end());
}
bool Uri::SetFragmentEncoded(const std::string& val) {
return pim_->SaveFragment<true>(val);
}
bool Uri::operator<(const Uri& uri) const {
if (pim_->scheme() < uri.pim_->scheme())
return true;
if (pim_->scheme() > uri.pim_->scheme())
return false;
if (pim_->userinfo() < uri.pim_->userinfo())
return true;
if (pim_->userinfo() > uri.pim_->userinfo())
return false;
if (pim_->host() < uri.pim_->host())
return true;
if (pim_->host() > uri.pim_->host())
return false;
if (pim_->port() < uri.pim_->port())
return true;
if (pim_->port() > uri.pim_->port())
return false;
if (pim_->path() < uri.pim_->path())
return true;
if (pim_->path() > uri.pim_->path())
return false;
if (pim_->query() < uri.pim_->query())
return true;
if (pim_->query() > uri.pim_->query())
return false;
return (pim_->fragment() < uri.pim_->fragment());
}
bool Uri::operator==(const Uri& uri) const {
if (pim_->scheme() != uri.pim_->scheme())
return false;
if (pim_->userinfo() != uri.pim_->userinfo())
return false;
if (pim_->host() != uri.pim_->host())
return false;
if (pim_->port() != uri.pim_->port())
return false;
if (pim_->path() != uri.pim_->path())
return false;
if (pim_->query() != uri.pim_->query())
return false;
return (pim_->fragment() == uri.pim_->fragment());
}
bool Uri::ShouldPrintPort(bool always_print_port) const {
if (pim_->port() < 0)
return false;
if (always_print_port)
return true;
auto it = kDefaultPorts.find(pim_->scheme());
if (it == kDefaultPorts.end()) {
return true;
}
return it->second != pim_->port();
}
}