#pragma once #include #include #include #include #include #include #include #include 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 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 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 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 bool load(std::shared_ptr& 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(&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(&pubkey_it->second)) pubkey_raw = std::get_if(pk_sc); if (!pubkey_raw) { next_server(); continue; } ustring_view pubkey{ reinterpret_cast(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(rit->second)) { auto& rooms_dict = std::get(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(&data); if (!data_dict) { ++*it_room; continue; } val = std::make_shared(Comm{}); auto& og = std::get(*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