60 lines
2.7 KiB
C++
60 lines
2.7 KiB
C++
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
module Crafter.Review:PgWire_impl;
|
||
|
|
import :PgWire;
|
||
|
|
import :Json;
|
||
|
|
import :Text;
|
||
|
|
import std;
|
||
|
|
|
||
|
|
namespace Crafter::Review::PgWire {
|
||
|
|
Url ParseUrl(std::string_view text) {
|
||
|
|
const Dyn::SplitUrl split = Dyn::UrlSplit(text);
|
||
|
|
Url url;
|
||
|
|
url.scheme = split.scheme;
|
||
|
|
url.host = Dyn::Lower(split.Hostname().value_or(""));
|
||
|
|
const std::optional<std::int32_t> port = split.Port();
|
||
|
|
url.port = port && *port != 0 ? *port : 5432;
|
||
|
|
const std::optional<std::string> user = split.Username();
|
||
|
|
url.user = Dyn::Unquote(user && !user->empty() ? *user : "postgres");
|
||
|
|
url.password = Dyn::Unquote(split.Password().value_or(""));
|
||
|
|
const std::string path = split.path.empty() ? "/" : split.path;
|
||
|
|
const std::string database = Dyn::LStrip(path, "/");
|
||
|
|
url.database = Dyn::Unquote(database.empty() ? "postgres" : database);
|
||
|
|
return url;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::optional<std::string> CheckUrl(const Json& url) {
|
||
|
|
if (!url.is_string() || url.get_ref<const std::string&>().empty()) return "no database URL";
|
||
|
|
Url parsed;
|
||
|
|
try {
|
||
|
|
parsed = ParseUrl(url.get_ref<const std::string&>());
|
||
|
|
} catch (const std::invalid_argument& error) {
|
||
|
|
return std::format("not a URL ({})", error.what());
|
||
|
|
}
|
||
|
|
if (parsed.scheme != "postgres" && parsed.scheme != "postgresql") return "not a postgres:// or postgresql:// URL";
|
||
|
|
if (!std::ranges::contains(Loopback, parsed.host)) {
|
||
|
|
const std::vector<std::string> names(Loopback.begin(), Loopback.end());
|
||
|
|
return std::format("host {} is not this machine ({}): the database under test is a local stack, never a hosted one",
|
||
|
|
parsed.host.empty() ? "(none)" : parsed.host, Dyn::Join(names, ", "));
|
||
|
|
}
|
||
|
|
return std::nullopt;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string Redact(std::string_view url) {
|
||
|
|
try {
|
||
|
|
const Dyn::SplitUrl split = Dyn::UrlSplit(url);
|
||
|
|
if (!split.Password()) return std::string(url);
|
||
|
|
const std::string host = split.netloc.substr(split.netloc.rfind('@') + 1);
|
||
|
|
const std::string netloc = std::format("{}:***@{}", split.Username().value_or(""), host);
|
||
|
|
return Dyn::UrlUnsplit(split.scheme, netloc, split.path, split.query, split.fragment);
|
||
|
|
} catch (const std::invalid_argument&) {
|
||
|
|
return "(unparsable URL)";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string Where(std::string_view url) {
|
||
|
|
const Url parsed = ParseUrl(url);
|
||
|
|
return std::format("{} on {}:{} as {}", parsed.database, parsed.host, parsed.port, parsed.user);
|
||
|
|
}
|
||
|
|
}
|