|
|
|
@ -218,6 +218,13 @@
|
|
|
|
|
*/
|
|
|
|
|
private array $room_hints = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var bool $merge_error
|
|
|
|
|
*
|
|
|
|
|
* Flag specifying whether the server is invalidated as a result of merging.
|
|
|
|
|
*/
|
|
|
|
|
private bool $merge_error = false;
|
|
|
|
|
|
|
|
|
|
private function __construct() {}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -266,8 +273,10 @@
|
|
|
|
|
/**
|
|
|
|
|
* Absorbs extra info from another instance of the same server.
|
|
|
|
|
* @param CommunityServer $server
|
|
|
|
|
*
|
|
|
|
|
* @return True if successful, false in case of mistmatch.
|
|
|
|
|
*/
|
|
|
|
|
private function merge_from($server) {
|
|
|
|
|
private function merge_from($server): bool {
|
|
|
|
|
// Merge room hint information.
|
|
|
|
|
$this->room_hints = [
|
|
|
|
|
...$this->room_hints,
|
|
|
|
@ -275,8 +284,24 @@
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Merge public key information.
|
|
|
|
|
if (!$this->has_pubkey() && $server->has_pubkey()) {
|
|
|
|
|
$this->pubkey = $server->pubkey;
|
|
|
|
|
// In case of error, set the `merge_error` flag.
|
|
|
|
|
if (!$this->set_pubkey($server->pubkey)) {
|
|
|
|
|
if ($this->merge_error) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$base_url = $this->base_url;
|
|
|
|
|
$other_base_url = $server->base_url;
|
|
|
|
|
$pubkey_old = $this->pubkey;
|
|
|
|
|
$pubkey_new = $server->pubkey;
|
|
|
|
|
log_error(
|
|
|
|
|
"Key collision for $base_url:" .
|
|
|
|
|
"Have $pubkey_old, fetched $pubkey_new" .
|
|
|
|
|
"from server $other_base_url"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->merge_error = true;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prefer HTTPS URLs over HTTP.
|
|
|
|
@ -288,14 +313,27 @@
|
|
|
|
|
if (filter_var($this->get_hostname(), FILTER_VALIDATE_IP)) {
|
|
|
|
|
$this->base_url = $this->get_scheme() . "://" . $server->get_hostname();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Re-introduces the server to a consistent state after merging.
|
|
|
|
|
* Re-introduces the servers to a consistent state after merging.
|
|
|
|
|
* @param CommunityServer[] $servers
|
|
|
|
|
* @return CommunityServer[]
|
|
|
|
|
*/
|
|
|
|
|
private function merge_consistency() {
|
|
|
|
|
private static function ensure_merge_consistency(array $servers) {
|
|
|
|
|
// Exclude servers with merge errors.
|
|
|
|
|
$servers = array_filter($servers, function(\CommunityServer $server) {
|
|
|
|
|
return !$server->merge_error;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Remove duplicate room hints; does not require sorting.
|
|
|
|
|
$this->room_hints = array_unique($this->room_hints);
|
|
|
|
|
foreach ($servers as $server) {
|
|
|
|
|
$server->room_hints = array_unique($server->room_hints);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $servers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -336,7 +374,7 @@
|
|
|
|
|
|
|
|
|
|
CommunityServer::merge_by($servers, "get_hostname");
|
|
|
|
|
|
|
|
|
|
foreach ($servers as $server) $server->merge_consistency();
|
|
|
|
|
$servers = CommunityServer::ensure_merge_consistency($servers);
|
|
|
|
|
|
|
|
|
|
return $servers;
|
|
|
|
|
}
|
|
|
|
@ -351,7 +389,7 @@
|
|
|
|
|
|
|
|
|
|
CommunityServer::merge_by($servers, "get_pubkey");
|
|
|
|
|
|
|
|
|
|
foreach ($servers as $server) $server->merge_consistency();
|
|
|
|
|
$servers = CommunityServer::ensure_merge_consistency($servers);
|
|
|
|
|
|
|
|
|
|
return $servers;
|
|
|
|
|
}
|
|
|
|
@ -400,6 +438,7 @@
|
|
|
|
|
foreach ($join_urls as $join_url) {
|
|
|
|
|
$server = new CommunityServer();
|
|
|
|
|
|
|
|
|
|
// Call must succeed with no default public key.
|
|
|
|
|
$server->initialize_from_url($join_url);
|
|
|
|
|
|
|
|
|
|
$servers[] = $server;
|
|
|
|
@ -465,8 +504,7 @@
|
|
|
|
|
// Synchronous for-loop for now.
|
|
|
|
|
foreach ($servers as $server) {
|
|
|
|
|
if (!($server->fetch_rooms())) continue;
|
|
|
|
|
// Accept failures to fetch pubkey if already known.
|
|
|
|
|
if (!$server->fetch_or_has_pubkey()) continue;
|
|
|
|
|
if (!($server->fetch_pubkey())) continue;
|
|
|
|
|
$reachable_servers[] = $server;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -515,28 +553,44 @@
|
|
|
|
|
return $this->pubkey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function set_pubkey($pubkey) {
|
|
|
|
|
/**
|
|
|
|
|
* Attempts to set the server public key.
|
|
|
|
|
* @param string $pubkey SOGS public key.
|
|
|
|
|
* @return bool True if successful, false in case of mismatch.
|
|
|
|
|
*/
|
|
|
|
|
function set_pubkey(string $pubkey): bool {
|
|
|
|
|
if ($this->has_pubkey() && $this->pubkey != $pubkey) {
|
|
|
|
|
$base_url = $this->base_url;
|
|
|
|
|
throw new ValueError("Pubkey mismatch for $base_url");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->pubkey = $pubkey;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function set_pubkey_from_url($join_url) {
|
|
|
|
|
$url_components = parse_url($join_url);
|
|
|
|
|
parse_str($url_components['query'], $query_components);
|
|
|
|
|
$this->pubkey = $query_components['public_key'];
|
|
|
|
|
/**
|
|
|
|
|
* Attempts to read the server public key from a join URL.
|
|
|
|
|
* @param string $join_url Join URL for any of the server's rooms.
|
|
|
|
|
* @return bool True if successful, false in case of mismatch.
|
|
|
|
|
*/
|
|
|
|
|
function set_pubkey_from_url(string $join_url): bool {
|
|
|
|
|
return $this->set_pubkey(url_get_pubkey($join_url));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets base url, pubkey, and room hint from join URL.
|
|
|
|
|
* Learns server info from a room's join URL.
|
|
|
|
|
* The base URL and public key are saved,
|
|
|
|
|
* and the room token is added as a fallback for room polling.
|
|
|
|
|
* @param string $join_url Room join URL to initialize with.
|
|
|
|
|
* @return bool True if successful, false in case of public key mismatch.
|
|
|
|
|
*/
|
|
|
|
|
function initialize_from_url($join_url) {
|
|
|
|
|
function initialize_from_url($join_url): bool {
|
|
|
|
|
if (!$this->set_pubkey_from_url($join_url)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$this->base_url = url_get_base($join_url);
|
|
|
|
|
$this->set_pubkey_from_url($join_url);
|
|
|
|
|
$this->room_hints[] = url_get_token($join_url);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -641,21 +695,9 @@
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fetch the server's SOGS public key if absent.
|
|
|
|
|
* @return bool True if pubkey is present or has been fetched, false otherwise.
|
|
|
|
|
*/
|
|
|
|
|
function fetch_or_has_pubkey(): bool {
|
|
|
|
|
$base_url = $this->base_url;
|
|
|
|
|
// Do not use 'or' here; I learned the hard way.
|
|
|
|
|
$result = $this->fetch_pubkey() || $this->has_pubkey();
|
|
|
|
|
if (!$result) log_warning("Failed to fetch pubkey for $base_url.");
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Attempt to fetch own public key by parsing SOGS HTML preview.
|
|
|
|
|
* Attempt to fetch server public key by parsing SOGS HTML preview.
|
|
|
|
|
*
|
|
|
|
|
* @return bool True if successful, false otherwise.
|
|
|
|
|
* @return bool True iff no key conflict has arised and we have a pubkey.
|
|
|
|
|
*/
|
|
|
|
|
function fetch_pubkey() {
|
|
|
|
|
if (empty($this->rooms)) {
|
|
|
|
@ -672,17 +714,29 @@
|
|
|
|
|
|
|
|
|
|
if (!$room_view) {
|
|
|
|
|
log_debug("Failed to fetch room preview from $preview_url.");
|
|
|
|
|
return false;
|
|
|
|
|
return $has_pubkey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$links = parse_join_links($room_view);
|
|
|
|
|
$link = $links[0];
|
|
|
|
|
|
|
|
|
|
if (!isset($links[0])) {
|
|
|
|
|
if (!isset($link)) {
|
|
|
|
|
log_debug("Could not locate join link in preview at $preview_url.");
|
|
|
|
|
return false;
|
|
|
|
|
return $has_pubkey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->set_pubkey_from_url($links[0]);
|
|
|
|
|
if (!$this->set_pubkey_from_url($link)) {
|
|
|
|
|
// More information needs to be logged for errors
|
|
|
|
|
// in case of lack of context due to lower verbosity.
|
|
|
|
|
$base_url = $this->base_url;
|
|
|
|
|
$pubkey_old = $this->pubkey;
|
|
|
|
|
$pubkey_new = url_get_pubkey($link);
|
|
|
|
|
log_error(
|
|
|
|
|
"Key collision for $base_url:" .
|
|
|
|
|
"Have $pubkey_old, fetched $pubkey_new from $preview_url"
|
|
|
|
|
);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|