|
|
|
@ -716,13 +716,24 @@
|
|
|
|
|
public static function poll_reachable(array $servers): array {
|
|
|
|
|
$reachable_servers = [];
|
|
|
|
|
|
|
|
|
|
// Synchronous for-loop for now.
|
|
|
|
|
foreach ($servers as $server) {
|
|
|
|
|
if (!($server->fetch_rooms())) continue;
|
|
|
|
|
if (!($server->fetch_pubkey())) continue;
|
|
|
|
|
$reachable_servers[] = $server;
|
|
|
|
|
$fetch_job = function() use ($server, &$reachable_servers): Generator {
|
|
|
|
|
if (!yield from $server->fetch_rooms_coroutine()) return;
|
|
|
|
|
if (!yield from $server->fetch_pubkey_coroutine()) return;
|
|
|
|
|
$reachable_servers[] = $server;
|
|
|
|
|
};
|
|
|
|
|
// passthrough hack
|
|
|
|
|
// all nested coroutines are allowed to do their own filtering
|
|
|
|
|
$coroutines[] = (new FetchingCoroutine($fetch_job()))
|
|
|
|
|
->set_response_filter(function(CurlHandle $handle) {
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$runner = new FetchingCoroutineRunner($coroutines);
|
|
|
|
|
|
|
|
|
|
$runner->fetch_all();
|
|
|
|
|
|
|
|
|
|
return $reachable_servers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -768,6 +779,14 @@
|
|
|
|
|
return "$base_url/rooms?all=1";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the URL for the endpoint of the particular room.
|
|
|
|
|
*/
|
|
|
|
|
function get_room_api_url(string $token): string {
|
|
|
|
|
$base_url = $this->base_url;
|
|
|
|
|
return "$base_url/room/$token";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the server's public key.
|
|
|
|
|
* @return string SOGS pubkey as used in the Session protocol.
|
|
|
|
@ -841,171 +860,175 @@
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Attempts to fetch the current server's room listing.
|
|
|
|
|
* Downgrades the server's scheme to HTTP if necessary.
|
|
|
|
|
* @return array|false Associative data about rooms if successful.
|
|
|
|
|
* @return \Generator<int,CurlHandle,CurlHandle|false,array|null>
|
|
|
|
|
*/
|
|
|
|
|
private function fetch_room_list(): array|bool {
|
|
|
|
|
private function fetch_room_list_coroutine(): Generator {
|
|
|
|
|
global $FAST_FETCH_MODE;
|
|
|
|
|
|
|
|
|
|
$base_url = $this->base_url;
|
|
|
|
|
list($rooms, $downgrade) = curl_get_contents_downgrade($this->get_rooms_api_url(), retries: $FAST_FETCH_MODE ? 2 : 4);
|
|
|
|
|
if (!$rooms) {
|
|
|
|
|
log_info("Failed fetching /rooms.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ($downgrade) $this->downgrade_scheme();
|
|
|
|
|
$room_data = json_decode($rooms, true);
|
|
|
|
|
if ($room_data == null) {
|
|
|
|
|
log_info("Failed parsing /rooms.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
log_debug("Fetched /rooms successfully");
|
|
|
|
|
return $room_data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return \Generator<int,CurlHandle,CurlHandle|false,array|bool>
|
|
|
|
|
*/
|
|
|
|
|
private function fetch_room_list_coroutine(): Generator {
|
|
|
|
|
global $FAST_FETCH_MODE;
|
|
|
|
|
/** @var CurlHandle|false $rooms_api_response */
|
|
|
|
|
$rooms_api_response =
|
|
|
|
|
yield from FetchingCoroutine
|
|
|
|
|
::from_url($this->get_rooms_api_url())
|
|
|
|
|
->retryable($FAST_FETCH_MODE ? 2 : 4)
|
|
|
|
|
->downgradeable($did_downgrade)
|
|
|
|
|
->run();
|
|
|
|
|
|
|
|
|
|
$rooms_api_coroutine =
|
|
|
|
|
FetchingCoroutine::from_url($this->get_rooms_api_url())
|
|
|
|
|
->downgradeable($does_downgrade)
|
|
|
|
|
->retryable($FAST_FETCH_MODE ? 2 : 4);
|
|
|
|
|
$rooms_raw = $rooms_api_response ? curl_multi_getcontent($rooms_api_response) : null;
|
|
|
|
|
|
|
|
|
|
/** @var CurlHandle|false $rooms_api_handle */
|
|
|
|
|
// assemble & propagate request to runner
|
|
|
|
|
$rooms_api_handle = yield $rooms_api_coroutine->current_request();
|
|
|
|
|
$rooms_raw = $rooms_api_handle && curl_multi_getcontent($rooms_api_handle);
|
|
|
|
|
if (!$rooms_raw) {
|
|
|
|
|
log_info("Failed fetching /rooms.");
|
|
|
|
|
return false;
|
|
|
|
|
log_info("Failed fetching /rooms for $base_url.");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if ($does_downgrade) $this->downgrade_scheme();
|
|
|
|
|
|
|
|
|
|
if ($did_downgrade) $this->downgrade_scheme();
|
|
|
|
|
|
|
|
|
|
$room_data = json_decode($rooms_raw, true);
|
|
|
|
|
|
|
|
|
|
if ($room_data == null) {
|
|
|
|
|
log_info("Failed parsing /rooms.");
|
|
|
|
|
return false;
|
|
|
|
|
log_info("Failed parsing /rooms for $base_url.");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
log_debug("Fetched /rooms successfully");
|
|
|
|
|
|
|
|
|
|
log_debug("Fetched /rooms successfully for $base_url");
|
|
|
|
|
// log_value($room_data);
|
|
|
|
|
|
|
|
|
|
return $room_data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Attempts to fetch the current server's rooms using observed room names.
|
|
|
|
|
* Downgrades the server's scheme to HTTP if necessary.
|
|
|
|
|
* @return ?array Associative data about rooms if successful.
|
|
|
|
|
* @return Generator<int,CurlHandle,CurlHandle|false,array|null>
|
|
|
|
|
*/
|
|
|
|
|
private function fetch_room_hints(): ?array {
|
|
|
|
|
private function fetch_room_hints_coroutine(): Generator {
|
|
|
|
|
global $FAST_FETCH_MODE;
|
|
|
|
|
|
|
|
|
|
$base_url = $this->base_url;
|
|
|
|
|
|
|
|
|
|
$rooms = [];
|
|
|
|
|
|
|
|
|
|
if (empty($this->room_hints)) {
|
|
|
|
|
log_debug("No room hints to scan for $base_url.");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($this->room_hints as $token) {
|
|
|
|
|
log_debug("Testing room /$token.");
|
|
|
|
|
list($room_raw, $downgrade) = curl_get_contents_downgrade("$base_url/room/$token", retries: 2);
|
|
|
|
|
log_debug("Testing room /$token at $base_url.");
|
|
|
|
|
|
|
|
|
|
// FIXME: This fetches room hints sequentially per each server
|
|
|
|
|
// Would need to allow yielding handle arrays
|
|
|
|
|
|
|
|
|
|
$room_api_response = yield from FetchingCoroutine
|
|
|
|
|
::from_url($this->get_room_api_url($token))
|
|
|
|
|
// Afford more attempts thanks to reachability test
|
|
|
|
|
// TODO Move retryability to outer invocation
|
|
|
|
|
->retryable(retries: $FAST_FETCH_MODE ? 2 : 4)
|
|
|
|
|
->downgradeable($did_downgrade)
|
|
|
|
|
->run();
|
|
|
|
|
|
|
|
|
|
$room_raw = $room_api_response ? curl_multi_getcontent($room_api_response) : null;
|
|
|
|
|
|
|
|
|
|
if (!$room_raw) {
|
|
|
|
|
log_info("Room /$token not reachable.");
|
|
|
|
|
log_info("Room /$token not reachable at $base_url.");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ($downgrade) $this->downgrade_scheme();
|
|
|
|
|
|
|
|
|
|
if ($did_downgrade) $this->downgrade_scheme();
|
|
|
|
|
|
|
|
|
|
$room_data = json_decode($room_raw, true);
|
|
|
|
|
|
|
|
|
|
if ($room_data == null) {
|
|
|
|
|
if (count($rooms) == 0) {
|
|
|
|
|
log_info("Room /$token not parsable.");
|
|
|
|
|
log_info("Room /$token not parsable at $base_url.");
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
log_debug("Room /$token not parsable, continuing.");
|
|
|
|
|
log_debug("Room /$token not parsable at $base_url, continuing.");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$rooms[] = $room_data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mark no rooms as failure.
|
|
|
|
|
if (count($rooms) == 0) {
|
|
|
|
|
log_debug("No room hints were valid.");
|
|
|
|
|
if (empty($rooms)) {
|
|
|
|
|
log_debug("No room hints were valid at $base_url.");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $rooms;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Attempt to fetch rooms for tbe current server using SOGS API.
|
|
|
|
|
*
|
|
|
|
|
* @return bool True if successful, false otherwise.
|
|
|
|
|
*/
|
|
|
|
|
function fetch_rooms(): bool {
|
|
|
|
|
function check_reachability_coroutine() {
|
|
|
|
|
global $FAST_FETCH_MODE;
|
|
|
|
|
|
|
|
|
|
$this->log_details();
|
|
|
|
|
$base_url = $this->base_url;
|
|
|
|
|
|
|
|
|
|
// Check reachability before polling too much.
|
|
|
|
|
if (count($this->room_hints) >= 2) {
|
|
|
|
|
log_info("Checking reachability for $base_url first...");
|
|
|
|
|
if (!url_is_reachable($base_url, retries: $FAST_FETCH_MODE ? 1 : 4)) {
|
|
|
|
|
log_warning("Reachability test failed by $base_url.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log_info("Fetching rooms for $base_url.");
|
|
|
|
|
$room_data = $this->fetch_room_list();
|
|
|
|
|
if (!$room_data) $room_data = $this->fetch_room_hints();
|
|
|
|
|
if ($room_data == null) {
|
|
|
|
|
log_warning("Could not fetch rooms for $base_url.");
|
|
|
|
|
log_info("Checking reachability for $base_url first...");
|
|
|
|
|
|
|
|
|
|
/** @var CurlHandle|false $response_handle */
|
|
|
|
|
$response_handle =
|
|
|
|
|
yield from FetchingCoroutine
|
|
|
|
|
::from_url($base_url, [CURLOPT_NOBODY => true])
|
|
|
|
|
->set_response_filter(function (CurlHandle $handle) {
|
|
|
|
|
$code = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
|
|
|
|
|
$url = curl_getinfo($handle, CURLINFO_EFFECTIVE_URL);
|
|
|
|
|
log_debug("Got $code for $url in custom filter.");
|
|
|
|
|
return $code != 0;
|
|
|
|
|
})
|
|
|
|
|
->retryable(retries: $FAST_FETCH_MODE ? 2 : 4)
|
|
|
|
|
->downgradeable($did_downgrade)
|
|
|
|
|
->run();
|
|
|
|
|
|
|
|
|
|
if (!$response_handle) {
|
|
|
|
|
log_warning("Reachability test failed by $base_url.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$this->rooms = CommunityRoom::from_details_array($this, $room_data);
|
|
|
|
|
|
|
|
|
|
if ($did_downgrade) $this->downgrade_scheme();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fetch_rooms_coroutine() {
|
|
|
|
|
/**
|
|
|
|
|
* @return \Generator<int,CurlHandle,CurlHandle|false,bool>
|
|
|
|
|
*/
|
|
|
|
|
function fetch_rooms_coroutine(): Generator {
|
|
|
|
|
$this->log_details();
|
|
|
|
|
$base_url = $this->base_url;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
// Check reachability before polling too much.
|
|
|
|
|
if (count($this->room_hints) >= 2) {
|
|
|
|
|
log_info("Checking reachability for $base_url first...");
|
|
|
|
|
if (!url_is_reachable($base_url, retries: $FAST_FETCH_MODE ? 1 : 4)) {
|
|
|
|
|
log_warning("Reachability test failed by $base_url.");
|
|
|
|
|
if (!yield from $this->check_reachability_coroutine()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
log_info("Fetching rooms for $base_url.");
|
|
|
|
|
yield from ($room_list_coroutine = $this->fetch_room_list_coroutine());
|
|
|
|
|
$room_data = $room_list_coroutine->getReturn();
|
|
|
|
|
if ($room_data === null) {
|
|
|
|
|
yield from ($room_hints_coroutine = $this->fetch_room_hints_coroutine());
|
|
|
|
|
$room_data = $room_hints_coroutine->getReturn();
|
|
|
|
|
}
|
|
|
|
|
/** @var array|null $room_data */
|
|
|
|
|
$room_data =
|
|
|
|
|
(yield from $this->fetch_room_list_coroutine()) ??
|
|
|
|
|
(yield from $this->fetch_room_hints_coroutine());
|
|
|
|
|
|
|
|
|
|
if ($room_data === null) {
|
|
|
|
|
log_warning("Could not fetch rooms for $base_url.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->rooms = CommunityRoom::from_details_array($this, $room_data);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Attempt to fetch server public key by parsing SOGS HTML preview.
|
|
|
|
|
*
|
|
|
|
|
* @return bool True iff no key conflict has arised and we have a pubkey.
|
|
|
|
|
* @return \Generator<int,CurlHandle,CurlHandle|false,bool>
|
|
|
|
|
*/
|
|
|
|
|
function fetch_pubkey() {
|
|
|
|
|
function fetch_pubkey_coroutine(): Generator {
|
|
|
|
|
global $FAST_FETCH_MODE;
|
|
|
|
|
|
|
|
|
|
$base_url = $this->base_url;
|
|
|
|
|
|
|
|
|
|
if (empty($this->rooms)) {
|
|
|
|
|
log_warning("Server has no rooms to poll for public key");
|
|
|
|
|
log_warning("Server $base_url has no rooms to poll for public key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1018,7 +1041,14 @@
|
|
|
|
|
$preview_url = $this->rooms[0]->get_preview_url();
|
|
|
|
|
|
|
|
|
|
log_info("Fetching pubkey from $preview_url");
|
|
|
|
|
$room_view = curl_get_contents($preview_url, retries: $has_pubkey || $FAST_FETCH_MODE ? 1 : 5);
|
|
|
|
|
$room_view_response = yield from FetchingCoroutine
|
|
|
|
|
::from_url($preview_url)
|
|
|
|
|
->retryable($has_pubkey || $FAST_FETCH_MODE ? 1 : 5)
|
|
|
|
|
->run();
|
|
|
|
|
|
|
|
|
|
$room_view = $room_view_response
|
|
|
|
|
? curl_multi_getcontent($room_view_response)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
if (!$room_view) {
|
|
|
|
|
log_debug("Failed to fetch room preview from $preview_url.");
|
|
|
|
|