session-ios/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp

240 lines
10 KiB
C++

#pragma once
#include <memory>
#include <optional>
#include <session/config.hpp>
#include <session/types.hpp>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
namespace session::config {
/// Base class for types representing a community; this base type handles the url/room/pubkey that
/// such a type need. Generally a class inherits from this to extend with the local
/// community-related values.
struct community {
// 267 = len('https://') + 253 (max valid DNS name length) + len(':XXXXX')
static constexpr size_t URL_MAX_LENGTH = 267;
static constexpr size_t ROOM_MAX_LENGTH = 64;
community() = default;
// Constructs an empty community struct from url, room, and pubkey. `base_url` will be
// normalized if not already. pubkey is 32 bytes.
community(std::string_view base_url, std::string_view room, ustring_view pubkey);
// Same as above, but takes pubkey as an encoded (hex or base32z or base64) string.
community(std::string_view base_url, std::string_view room, std::string_view pubkey_encoded);
// Takes a combined room URL (e.g. https://whatever.com/r/Room?public_key=01234....), either
// new style (with /r/) or old style (without /r/). Note that the URL gets canonicalized so
// the resulting `base_url()` and `room()` values may not be exactly equal to what is given.
//
// See also `parse_full_url` which does the same thing but returns it in pieces rather than
// constructing a new `community` object.
explicit community(std::string_view full_url);
// Replaces the baseurl/room/pubkey of this object from a URL. This parses the URL, then stores
// the values as if passed to set_base_url/set_room/set_pubkey.
//
// The base URL will be normalized; the room name will be case-preserving (but see `set_room`
// for info on limitations on "case-preserving", particularly for volatile configs); and the
// embedded pubkey must be encoded in one of hex, base32z, or base64.
void set_full_url(std::string_view full_url);
// Replaces the base_url of this object. Note that changing the URL and then giving it to `set`
// will end up inserting a *new* record but not removing the *old* one (you need to erase first
// to do that).
void set_base_url(std::string_view new_url);
// Changes the room token. This stores (or updates) the name as given as the localized room,
// and separately stores the normalized (lower-case) token. Note that the localized name does
// not persist across a push or dump in some config contexts (such as volatile room info). If
// the new room given here changes more than just case (i.e. if the normalized room token
// changes) then a call to `set` will end up inserting a *new* record but not removing the *old*
// one (you need to erase first to do that).
void set_room(std::string_view room);
// Updates the pubkey of this community (typically this is not called directly but rather
// via `set_server` or during construction). Throws std::invalid_argument if the given
// pubkey does not look like a valid pubkey. The std::string_view version takes the pubkey
// as any of hex/base64/base32z.
//
// NOTE: the pubkey of all communities with the same URLs are stored in common, so changing
// one community pubkey (and storing) will affect all communities using the same community
// base URL.
void set_pubkey(ustring_view pubkey);
void set_pubkey(std::string_view pubkey);
// Accesses the base url (i.e. not including room or pubkey). Always lower-case/normalized.
const std::string& base_url() const { return base_url_; }
// Accesses the room token; this is case-preserving, where possible. In some contexts, however,
// such as volatile info, the case is not preserved and this will always return the normalized
// (lower-case) form rather than the preferred form.
const std::string& room() const { return localized_room_ ? *localized_room_ : room_; }
// Accesses the normalized room token, i.e. always lower-case.
const std::string& room_norm() const { return room_; }
const ustring& pubkey() const { return pubkey_; } // Accesses the server pubkey (32 bytes).
std::string pubkey_hex() const; // Accesses the server pubkey as hex (64 hex digits).
std::string pubkey_b32z() const; // Accesses the server pubkey as base32z (52 alphanumeric
// digits)
std::string pubkey_b64() const; // Accesses the server pubkey as unpadded base64 (43 from
// alphanumeric, '+', and '/').
// Takes a base URL as input and returns it in canonical form. This involves doing things
// like lower casing it and removing redundant ports (e.g. :80 when using http://). Throws
// std::invalid_argument if given an invalid base URL.
static std::string canonical_url(std::string_view url);
// Takes a room token and returns it in canonical form (i.e. lower-cased). Throws
// std::invalid_argument if given an invalid room token (e.g. too long, or containing token
// other than a-z, 0-9, -, _).
static std::string canonical_room(std::string_view room);
// Same as above, but modifies the argument in-place instead of returning a modified
// copy.
static void canonicalize_url(std::string& url);
static void canonicalize_room(std::string& room);
// Takes a full room URL, splits it up into canonical url (see above), room, and server
// pubkey. We take both the deprecated form (e.g.
// https://example.com/SomeRoom?public_key=...) and new form
// (https://example.com/r/SomeRoom?public_key=...). The public_key is typically specified
// in hex (64 digits), but we also accept base64 (43 chars or 44 with padding) and base32z
// (52 chars) encodings (for slightly shorter URLs).
//
// The returned URL is normalized (lower-cased, and cleaned up).
//
// The returned room name is *not* normalized, that is, it preserve case.
//
// Throw std::invalid_argument if anything in the URL is unparseable or invalid.
static std::tuple<std::string, std::string, ustring> parse_full_url(std::string_view full_url);
protected:
// The canonical base url and room (i.e. lower-cased, URL cleaned up):
std::string base_url_, room_;
// The localized token of this room, that is, with case preserved (so `room_` could be
// `someroom` and this could `SomeRoom`). Omitted if not available.
std::optional<std::string> localized_room_;
// server pubkey
ustring pubkey_;
// Construction without a pubkey for when pubkey isn't known yet but will be set shortly
// after constructing (or when isn't needed, such as when deleting).
community(std::string_view base_url, std::string_view room);
};
struct comm_iterator_helper {
comm_iterator_helper(dict::const_iterator it_server, dict::const_iterator end_server) :
it_server{std::move(it_server)}, end_server{std::move(end_server)} {}
std::optional<dict::const_iterator> it_server, end_server, it_room, end_room;
bool operator==(const comm_iterator_helper& other) const {
return it_server == other.it_server && it_room == other.it_room;
}
void next_server() {
++*it_server;
it_room.reset();
end_room.reset();
}
bool done() const { return !it_server || *it_server == *end_server; }
template <typename Comm, typename Any>
bool load(std::shared_ptr<Any>& val) {
while (it_server) {
if (*it_server == *end_server) {
it_server.reset();
end_server.reset();
return false;
}
auto& [base_url, server_info] = **it_server;
auto* server_info_dict = std::get_if<dict>(&server_info);
if (!server_info_dict) {
next_server();
continue;
}
const std::string* pubkey_raw = nullptr;
if (auto pubkey_it = server_info_dict->find("#"); pubkey_it != server_info_dict->end())
if (auto* pk_sc = std::get_if<scalar>(&pubkey_it->second))
pubkey_raw = std::get_if<std::string>(pk_sc);
if (!pubkey_raw) {
next_server();
continue;
}
ustring_view pubkey{
reinterpret_cast<const unsigned char*>(pubkey_raw->data()), pubkey_raw->size()};
if (!it_room) {
if (auto rit = server_info_dict->find("R");
rit != server_info_dict->end() && std::holds_alternative<dict>(rit->second)) {
auto& rooms_dict = std::get<dict>(rit->second);
it_room = rooms_dict.begin();
end_room = rooms_dict.end();
} else {
next_server();
continue;
}
}
while (it_room) {
if (*it_room == *end_room) {
it_room.reset();
end_room.reset();
break;
}
auto& [room, data] = **it_room;
auto* data_dict = std::get_if<dict>(&data);
if (!data_dict) {
++*it_room;
continue;
}
val = std::make_shared<Any>(Comm{});
auto& og = std::get<Comm>(*val);
try {
og.set_base_url(base_url);
og.set_room(room); // Will be replaced with "n" in the `.load` below
og.set_pubkey(pubkey);
og.load(*data_dict);
} catch (const std::exception& e) {
++*it_room;
continue;
}
return true;
}
++*it_server;
}
return false;
}
bool advance() {
if (it_room) {
++*it_room;
return true;
}
if (it_server) {
++*it_server;
return true;
}
return false;
}
};
} // namespace session::config