X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FGServer.php;h=0f5f55ab671a19979fd4b1a02659d199a889fb24;hb=7b7132971a64a92a685a5fd860fe4709dce1765a;hp=77d556357587d76f1f4c8e0c235a12f33be05ac2;hpb=9df7aa93fd04dc402e47d9c8b9b81f01acefc606;p=friendica.git diff --git a/src/Model/GServer.php b/src/Model/GServer.php index 77d5563575..0f5f55ab67 100644 --- a/src/Model/GServer.php +++ b/src/Model/GServer.php @@ -1,17 +1,34 @@ . + * */ + namespace Friendica\Model; use DOMDocument; use DOMXPath; -use Friendica\Core\Config; use Friendica\Core\Protocol; +use Friendica\Core\Worker; use Friendica\Database\DBA; +use Friendica\DI; use Friendica\Module\Register; +use Friendica\Network\CurlResult; use Friendica\Util\Network; use Friendica\Util\DateTimeFormat; use Friendica\Util\Strings; @@ -26,6 +43,88 @@ use Friendica\Network\Probe; */ class GServer { + // Directory types + const DT_NONE = 0; + const DT_POCO = 1; + const DT_MASTODON = 2; + /** + * Checks if the given server is reachable + * + * @param string $profile URL of the given profile + * @param string $server URL of the given server (If empty, taken from profile) + * @param string $network Network value that is used, when detection failed + * @param boolean $force Force an update. + * + * @return boolean 'true' if server seems vital + */ + public static function reachable(string $profile, string $server = '', string $network = '', bool $force = false) + { + if ($server == '') { + $server = GContact::getBasepath($profile); + } + + if ($server == '') { + return true; + } + + return self::check($server, $network, $force); + } + + /** + * Decides if a server needs to be updated, based upon several date fields + * + * @param date $created Creation date of that server entry + * @param date $updated When had the server entry be updated + * @param date $last_failure Last failure when contacting that server + * @param date $last_contact Last time the server had been contacted + * + * @return boolean Does the server record needs an update? + */ + public static function updateNeeded($created, $updated, $last_failure, $last_contact) + { + $now = strtotime(DateTimeFormat::utcNow()); + + if ($updated > $last_contact) { + $contact_time = strtotime($updated); + } else { + $contact_time = strtotime($last_contact); + } + + $failure_time = strtotime($last_failure); + $created_time = strtotime($created); + + // If there is no "created" time then use the current time + if ($created_time <= 0) { + $created_time = $now; + } + + // If the last contact was less than 24 hours then don't update + if (($now - $contact_time) < (60 * 60 * 24)) { + return false; + } + + // If the last failure was less than 24 hours then don't update + if (($now - $failure_time) < (60 * 60 * 24)) { + return false; + } + + // If the last contact was less than a week ago and the last failure is older than a week then don't update + //if ((($now - $contact_time) < (60 * 60 * 24 * 7)) && ($contact_time > $failure_time)) + // return false; + + // If the last contact time was more than a week ago and the contact was created more than a week ago, then only try once a week + if ((($now - $contact_time) > (60 * 60 * 24 * 7)) && (($now - $created_time) > (60 * 60 * 24 * 7)) && (($now - $failure_time) < (60 * 60 * 24 * 7))) { + return false; + } + + // If the last contact time was more than a month ago and the contact was created more than a month ago, then only try once a month + if ((($now - $contact_time) > (60 * 60 * 24 * 30)) && (($now - $created_time) > (60 * 60 * 24 * 30)) && (($now - $failure_time) < (60 * 60 * 24 * 30))) { + return false; + } + + return true; + } + /** * Checks the state of the given server. * @@ -66,7 +165,7 @@ class GServer $last_failure = DBA::NULL_DATETIME; } - if (!$force && !PortableContact::updateNeeded($gserver['created'], '', $last_failure, $last_contact)) { + if (!$force && !self::updateNeeded($gserver['created'], '', $last_failure, $last_contact)) { Logger::info('No update needed', ['server' => $server_url]); return ($last_contact >= $last_failure); } @@ -89,10 +188,26 @@ class GServer */ public static function detect(string $url, string $network = '') { + Logger::info('Detect server type', ['server' => $url]); $serverdata = []; + $original_url = $url; + + // Remove URL content that is not supposed to exist for a server url + $urlparts = parse_url($url); + unset($urlparts['user']); + unset($urlparts['pass']); + unset($urlparts['query']); + unset($urlparts['fragment']); + $url = Network::unparseURL($urlparts); + + // If the URL missmatches, then we mark the old entry as failure + if ($url != $original_url) { + DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($original_url)]); + } + // When a nodeinfo is present, we don't need to dig further - $xrd_timeout = Config::get('system', 'xrd_timeout'); + $xrd_timeout = DI::config()->get('system', 'xrd_timeout'); $curlResult = Network::curl($url . '/.well-known/nodeinfo', false, ['timeout' => $xrd_timeout]); if ($curlResult->isTimeout()) { DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]); @@ -107,27 +222,38 @@ class GServer } // If that didn't work out well, we use some protocol specific endpoints - if (empty($nodeinfo) || empty($nodeinfo['network']) || ($nodeinfo['network'] == Protocol::DFRN)) { + // For Friendica and Zot based networks we have to dive deeper to reveal more details + if (empty($nodeinfo['network']) || in_array($nodeinfo['network'], [Protocol::DFRN, Protocol::ZOT])) { // Fetch the landing page, possibly it reveals some data - $curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout]); - if ($curlResult->isSuccess()) { - $serverdata = self::analyseRootHeader($curlResult, $serverdata); - $serverdata = self::analyseRootBody($curlResult, $serverdata); + if (empty($nodeinfo['network'])) { + $curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout]); + if ($curlResult->isSuccess()) { + $serverdata = self::analyseRootHeader($curlResult, $serverdata); + $serverdata = self::analyseRootBody($curlResult, $serverdata, $url); + } + + if (!$curlResult->isSuccess() || empty($curlResult->getBody()) || self::invalidBody($curlResult->getBody())) { + DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]); + return false; + } } - if (!$curlResult->isSuccess() || empty($curlResult->getBody())) { + if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::ACTIVITYPUB)) { + $serverdata = self::detectMastodonAlikes($url, $serverdata); + } + + // All following checks are done for systems that always have got a "host-meta" endpoint. + // With this check we don't have to waste time and ressources for dead systems. + // Also this hopefully prevents us from receiving abuse messages. + if (empty($serverdata['network']) && !self::validHostMeta($url)) { DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]); return false; } - if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::DFRN)) { + if (empty($serverdata['network']) || in_array($serverdata['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB])) { $serverdata = self::detectFriendica($url, $serverdata); } - if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::ACTIVITYPUB)) { - $serverdata = self::detectMastodonAlikes($url, $serverdata); - } - // the 'siteinfo.json' is some specific endpoint of Hubzilla and Red if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::ZOT)) { $serverdata = self::fetchSiteinfo($url, $serverdata); @@ -149,7 +275,10 @@ class GServer $serverdata = $nodeinfo; } + // Detect the directory type + $serverdata['directory-type'] = self::DT_NONE; $serverdata = self::checkPoCo($url, $serverdata); + $serverdata = self::checkMastodonDirectory($url, $serverdata); // We can't detect the network type. Possibly it is some system that we don't know yet if (empty($serverdata['network'])) { @@ -161,14 +290,6 @@ class GServer $serverdata['network'] = $network; } - // Check host-meta for phantom networks. - // Although this is not needed, it is a good indicator for a living system, - // since most systems had implemented it. - if (($serverdata['network'] == Protocol::PHANTOM) && !self::validHostMeta($url)) { - DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]); - return false; - } - $serverdata['url'] = $url; $serverdata['nurl'] = Strings::normaliseLink($url); @@ -318,11 +439,11 @@ class GServer } if (!empty($data['network'])) { - $serverdata['platform'] = $data['network']; + $serverdata['platform'] = strtolower($data['network']); - if ($serverdata['platform'] == 'Diaspora') { + if ($serverdata['platform'] == 'diaspora') { $serverdata['network'] = Protocol::DIASPORA; - } elseif ($serverdata['platform'] == 'Friendica') { + } elseif ($serverdata['platform'] == 'friendica') { $serverdata['network'] = Protocol::DFRN; } elseif ($serverdata['platform'] == 'hubzilla') { $serverdata['network'] = Protocol::ZOT; @@ -344,11 +465,12 @@ class GServer /** * Detect server type by using the nodeinfo data * - * @param string $url address of the server + * @param string $url address of the server + * @param CurlResult $curlResult * @return array Server data * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - private static function fetchNodeinfo(string $url, $curlResult) + private static function fetchNodeinfo(string $url, CurlResult $curlResult) { $nodeinfo = json_decode($curlResult->getBody(), true); @@ -421,7 +543,7 @@ class GServer if (is_array($nodeinfo['software'])) { if (!empty($nodeinfo['software']['name'])) { - $server['platform'] = $nodeinfo['software']['name']; + $server['platform'] = strtolower($nodeinfo['software']['name']); } if (!empty($nodeinfo['software']['version'])) { @@ -498,7 +620,7 @@ class GServer if (is_array($nodeinfo['software'])) { if (!empty($nodeinfo['software']['name'])) { - $server['platform'] = $nodeinfo['software']['name']; + $server['platform'] = strtolower($nodeinfo['software']['name']); } if (!empty($nodeinfo['software']['version'])) { @@ -523,7 +645,7 @@ class GServer $protocols[$protocol] = true; } - if (!empty($protocols['friendica'])) { + if (!empty($protocols['dfrn'])) { $server['network'] = Protocol::DFRN; } elseif (!empty($protocols['activitypub'])) { $server['network'] = Protocol::ACTIVITYPUB; @@ -566,7 +688,7 @@ class GServer } if (!empty($data['url'])) { - $serverdata['platform'] = $data['platform']; + $serverdata['platform'] = strtolower($data['platform']); $serverdata['version'] = $data['version']; } @@ -619,7 +741,7 @@ class GServer */ private static function validHostMeta(string $url) { - $xrd_timeout = Config::get('system', 'xrd_timeout'); + $xrd_timeout = DI::config()->get('system', 'xrd_timeout'); $curlResult = Network::curl($url . '/.well-known/host-meta', false, ['timeout' => $xrd_timeout]); if (!$curlResult->isSuccess()) { return false; @@ -637,11 +759,16 @@ class GServer $valid = false; foreach ($elements['xrd']['link'] as $link) { - if (empty($link['rel']) || empty($link['type']) || empty($link['template'])) { + // When there is more than a single "link" element, the array looks slightly different + if (!empty($link['@attributes'])) { + $link = $link['@attributes']; + } + + if (empty($link['rel']) || empty($link['template'])) { continue; } - if ($link['type'] == 'application/xrd+xml') { + if ($link['rel'] == 'lrdd') { // When the webfinger host is the same like the system host, it should be ok. $valid = (parse_url($url, PHP_URL_HOST) == parse_url($link['template'], PHP_URL_HOST)); } @@ -660,22 +787,21 @@ class GServer */ private static function detectNetworkViaContacts(string $url, array $serverdata) { - $contacts = ''; - $fields = ['nurl', 'url']; + $contacts = []; - $gcontacts = DBA::select('gcontact', $fields, ['server_url' => [$url, $serverdata['nurl']]]); + $gcontacts = DBA::select('gcontact', ['url', 'nurl'], ['server_url' => [$url, $serverdata['nurl']]]); while ($gcontact = DBA::fetch($gcontacts)) { $contacts[$gcontact['nurl']] = $gcontact['url']; } DBA::close($gcontacts); - $apcontacts = DBA::select('apcontact', $fields, ['baseurl' => [$url, $serverdata['nurl']]]); + $apcontacts = DBA::select('apcontact', ['url'], ['baseurl' => [$url, $serverdata['nurl']]]); while ($gcontact = DBA::fetch($gcontacts)) { - $contacts[$apcontact['nurl']] = $apcontact['url']; + $contacts[Strings::normaliseLink($apcontact['url'])] = $apcontact['url']; } DBA::close($apcontacts); - $pcontacts = DBA::select('contact', $fields, ['uid' => 0, 'baseurl' => [$url, $serverdata['nurl']]]); + $pcontacts = DBA::select('contact', ['url', 'nurl'], ['uid' => 0, 'baseurl' => [$url, $serverdata['nurl']]]); while ($gcontact = DBA::fetch($gcontacts)) { $contacts[$pcontact['nurl']] = $pcontact['url']; } @@ -710,6 +836,8 @@ class GServer */ private static function checkPoCo(string $url, array $serverdata) { + $serverdata['poco'] = ''; + $curlResult = Network::curl($url. '/poco'); if (!$curlResult->isSuccess()) { return $serverdata; @@ -723,9 +851,35 @@ class GServer if (!empty($data['totalResults'])) { $registeredUsers = $serverdata['registered-users'] ?? 0; $serverdata['registered-users'] = max($data['totalResults'], $registeredUsers); + $serverdata['directory-type'] = self::DT_POCO; $serverdata['poco'] = $url . '/poco'; - } else { - $serverdata['poco'] = ''; + } + + return $serverdata; + } + + /** + * Checks if the given server does have a Mastodon style directory endpoint. + * + * @param string $url URL of the given server + * @param array $serverdata array with server data + * + * @return array server data + */ + public static function checkMastodonDirectory(string $url, array $serverdata) + { + $curlResult = Network::curl($url . '/api/v1/directory?limit=1'); + if (!$curlResult->isSuccess()) { + return $serverdata; + } + + $data = json_decode($curlResult->getBody(), true); + if (empty($data)) { + return $serverdata; + } + + if (count($data) == 1) { + $serverdata['directory-type'] = self::DT_MASTODON; } return $serverdata; @@ -784,7 +938,7 @@ class GServer if (!empty($data['version'])) { $serverdata['platform'] = 'mastodon'; - $serverdata['version'] = defaults($data, 'version', ''); + $serverdata['version'] = $data['version'] ?? ''; $serverdata['network'] = Protocol::ACTIVITYPUB; } @@ -792,6 +946,11 @@ class GServer $serverdata['site_name'] = $data['title']; } + if (!empty($data['title']) && empty($serverdata['platform']) && empty($serverdata['network'])) { + $serverdata['platform'] = 'mastodon'; + $serverdata['network'] = Protocol::ACTIVITYPUB; + } + if (!empty($data['description'])) { $serverdata['info'] = trim($data['description']); } @@ -801,13 +960,18 @@ class GServer } if (!empty($serverdata['version']) && preg_match('/.*?\(compatible;\s(.*)\s(.*)\)/ism', $serverdata['version'], $matches)) { - $serverdata['platform'] = $matches[1]; + $serverdata['platform'] = strtolower($matches[1]); $serverdata['version'] = $matches[2]; } - if (!empty($serverdata['version']) && strstr($serverdata['version'], 'Pleroma')) { + if (!empty($serverdata['version']) && strstr(strtolower($serverdata['version']), 'pleroma')) { + $serverdata['platform'] = 'pleroma'; + $serverdata['version'] = trim(str_ireplace('pleroma', '', $serverdata['version'])); + } + + if (!empty($serverdata['platform']) && strstr($serverdata['platform'], 'pleroma')) { + $serverdata['version'] = trim(str_ireplace('pleroma', '', $serverdata['platform'])); $serverdata['platform'] = 'pleroma'; - $serverdata['version'] = trim(str_replace('Pleroma', '', $serverdata['version'])); } return $serverdata; @@ -838,22 +1002,22 @@ class GServer } if (!empty($data['site']['platform'])) { - $serverdata['platform'] = $data['site']['platform']['PLATFORM_NAME']; + $serverdata['platform'] = strtolower($data['site']['platform']['PLATFORM_NAME']); $serverdata['version'] = $data['site']['platform']['STD_VERSION']; $serverdata['network'] = Protocol::ZOT; } if (!empty($data['site']['hubzilla'])) { - $serverdata['platform'] = $data['site']['hubzilla']['PLATFORM_NAME']; + $serverdata['platform'] = strtolower($data['site']['hubzilla']['PLATFORM_NAME']); $serverdata['version'] = $data['site']['hubzilla']['RED_VERSION']; $serverdata['network'] = Protocol::ZOT; } if (!empty($data['site']['redmatrix'])) { if (!empty($data['site']['redmatrix']['PLATFORM_NAME'])) { - $serverdata['platform'] = $data['site']['redmatrix']['PLATFORM_NAME']; + $serverdata['platform'] = strtolower($data['site']['redmatrix']['PLATFORM_NAME']); } elseif (!empty($data['site']['redmatrix']['RED_PLATFORM'])) { - $serverdata['platform'] = $data['site']['redmatrix']['RED_PLATFORM']; + $serverdata['platform'] = strtolower($data['site']['redmatrix']['RED_PLATFORM']); } $serverdata['version'] = $data['site']['redmatrix']['RED_VERSION']; @@ -915,27 +1079,37 @@ class GServer */ private static function detectGNUSocial(string $url, array $serverdata) { - $curlResult = Network::curl($url . '/api/statusnet/version.json'); - + // Test for GNU Social + $curlResult = Network::curl($url . '/api/gnusocial/version.json'); if ($curlResult->isSuccess() && ($curlResult->getBody() != '{"error":"not implemented"}') && ($curlResult->getBody() != '') && (strlen($curlResult->getBody()) < 30)) { - $serverdata['platform'] = 'StatusNet'; + $serverdata['platform'] = 'gnusocial'; // Remove junk that some GNU Social servers return - $serverdata['version'] = str_replace(chr(239).chr(187).chr(191), '', $curlResult->getBody()); + $serverdata['version'] = str_replace(chr(239) . chr(187) . chr(191), '', $curlResult->getBody()); + $serverdata['version'] = str_replace(["\r", "\n", "\t"], '', $serverdata['version']); $serverdata['version'] = trim($serverdata['version'], '"'); $serverdata['network'] = Protocol::OSTATUS; + return $serverdata; } - // Test for GNU Social - $curlResult = Network::curl($url . '/api/gnusocial/version.json'); - + // Test for Statusnet + $curlResult = Network::curl($url . '/api/statusnet/version.json'); if ($curlResult->isSuccess() && ($curlResult->getBody() != '{"error":"not implemented"}') && ($curlResult->getBody() != '') && (strlen($curlResult->getBody()) < 30)) { - $serverdata['platform'] = 'GNU Social'; + // Remove junk that some GNU Social servers return - $serverdata['version'] = str_replace(chr(239) . chr(187) . chr(191), '', $curlResult->getBody()); + $serverdata['version'] = str_replace(chr(239).chr(187).chr(191), '', $curlResult->getBody()); + $serverdata['version'] = str_replace(["\r", "\n", "\t"], '', $serverdata['version']); $serverdata['version'] = trim($serverdata['version'], '"'); - $serverdata['network'] = Protocol::OSTATUS; + + if (!empty($serverdata['version']) && strtolower(substr($serverdata['version'], 0, 7)) == 'pleroma') { + $serverdata['platform'] = 'pleroma'; + $serverdata['version'] = trim(str_ireplace('pleroma', '', $serverdata['version'])); + $serverdata['network'] = Protocol::ACTIVITYPUB; + } else { + $serverdata['platform'] = 'statusnet'; + $serverdata['network'] = Protocol::OSTATUS; + } } return $serverdata; @@ -980,7 +1154,7 @@ class GServer $serverdata['info'] = trim($data['info']); } - $register_policy = defaults($data, 'register_policy', 'REGISTER_CLOSED'); + $register_policy = ($data['register_policy'] ?? '') ?: 'REGISTER_CLOSED'; switch ($register_policy) { case 'REGISTER_OPEN': $serverdata['register_policy'] = Register::OPEN; @@ -1000,7 +1174,7 @@ class GServer break; } - $serverdata['platform'] = defaults($data, 'platform', ''); + $serverdata['platform'] = strtolower($data['platform'] ?? ''); return $serverdata; } @@ -1010,10 +1184,11 @@ class GServer * * @param object $curlResult result of curl execution * @param array $serverdata array with server data + * @param string $url Server URL * * @return array server data */ - private static function analyseRootBody($curlResult, array $serverdata) + private static function analyseRootBody($curlResult, array $serverdata, string $url) { $doc = new DOMDocument(); @$doc->loadHTML($curlResult->getBody()); @@ -1030,12 +1205,12 @@ class GServer $attr = []; if ($node->attributes->length) { foreach ($node->attributes as $attribute) { - $attribute->value = trim($attribute->value); - if (empty($attribute->value)) { + $value = trim($attribute->value); + if (empty($value)) { continue; } - $attr[$attribute->name] = $attribute->value; + $attr[$attribute->name] = $value; } if (empty($attr['name']) || empty($attr['content'])) { @@ -1048,25 +1223,29 @@ class GServer } if ($attr['name'] == 'application-name') { - $serverdata['platform'] = $attr['content']; + $serverdata['platform'] = strtolower($attr['content']); if (in_array($attr['content'], ['Misskey', 'Write.as'])) { $serverdata['network'] = Protocol::ACTIVITYPUB; } } - - if ($attr['name'] == 'generator') { - $serverdata['platform'] = $attr['content']; - + if (($attr['name'] == 'generator') && (empty($serverdata['platform']) || (substr(strtolower($attr['content']), 0, 9) == 'wordpress'))) { + $serverdata['platform'] = strtolower($attr['content']); $version_part = explode(' ', $attr['content']); if (count($version_part) == 2) { if (in_array($version_part[0], ['WordPress'])) { - $serverdata['platform'] = $version_part[0]; + $serverdata['platform'] = strtolower($version_part[0]); $serverdata['version'] = $version_part[1]; - $serverdata['network'] = Protocol::ACTIVITYPUB; + + // We still do need a reliable test if some AP plugin is activated + if (DBA::exists('apcontact', ['baseurl' => $url])) { + $serverdata['network'] = Protocol::ACTIVITYPUB; + } else { + $serverdata['network'] = Protocol::FEED; + } } if (in_array($version_part[0], ['Friendika', 'Friendica'])) { - $serverdata['platform'] = $version_part[0]; + $serverdata['platform'] = strtolower($version_part[0]); $serverdata['version'] = $version_part[1]; $serverdata['network'] = Protocol::DFRN; } @@ -1080,12 +1259,12 @@ class GServer $attr = []; if ($node->attributes->length) { foreach ($node->attributes as $attribute) { - $attribute->value = trim($attribute->value); - if (empty($attribute->value)) { + $value = trim($attribute->value); + if (empty($value)) { continue; } - $attr[$attribute->name] = $attribute->value; + $attr[$attribute->name] = $value; } if (empty($attr['property']) || empty($attr['content'])) { @@ -1102,7 +1281,7 @@ class GServer } if ($attr['property'] == 'og:platform') { - $serverdata['platform'] = $attr['content']; + $serverdata['platform'] = strtolower($attr['content']); if (in_array($attr['content'], ['PeerTube'])) { $serverdata['network'] = Protocol::ACTIVITYPUB; @@ -1110,7 +1289,7 @@ class GServer } if ($attr['property'] == 'generator') { - $serverdata['platform'] = $attr['content']; + $serverdata['platform'] = strtolower($attr['content']); if (in_array($attr['content'], ['hubzilla'])) { // We later check which compatible protocol modules are loaded. @@ -1139,7 +1318,6 @@ class GServer $serverdata['platform'] = 'diaspora'; $serverdata['network'] = $network = Protocol::DIASPORA; $serverdata['version'] = $curlResult->getHeader('x-diaspora-version'); - } elseif ($curlResult->inHeader('x-friendica-version')) { $serverdata['platform'] = 'friendica'; $serverdata['network'] = $network = Protocol::DFRN; @@ -1147,4 +1325,125 @@ class GServer } return $serverdata; } + + /** + * Test if the body contains valid content + * + * @param string $body + * @return boolean + */ + private static function invalidBody(string $body) + { + // Currently we only test for a HTML element. + // Possibly we enhance this in the future. + return !strpos($body, '>'); + } + + /** + * Update the user directory of a given gserver record + * + * @param array $gserver gserver record + */ + public static function updateDirectory(array $gserver) + { + /// @todo Add Mastodon API directory + + if (!empty($gserver['poco'])) { + PortableContact::discoverSingleServer($gserver['id']); + } + } + + /** + * Update GServer entries + */ + public static function discover() + { + // Update the server list + self::discoverFederation(); + + $no_of_queries = 5; + + $requery_days = intval(DI::config()->get('system', 'poco_requery_days')); + + if ($requery_days == 0) { + $requery_days = 7; + } + + $last_update = date('c', time() - (60 * 60 * 24 * $requery_days)); + + $gservers = DBA::p("SELECT `id`, `url`, `nurl`, `network`, `poco` + FROM `gserver` + WHERE `last_contact` >= `last_failure` + AND `poco` != '' + AND `last_poco_query` < ? + ORDER BY RAND()", $last_update + ); + + while ($gserver = DBA::fetch($gservers)) { + if (!GServer::check($gserver['url'], $gserver['network'])) { + // The server is not reachable? Okay, then we will try it later + $fields = ['last_poco_query' => DateTimeFormat::utcNow()]; + DBA::update('gserver', $fields, ['nurl' => $gserver['nurl']]); + continue; + } + + Logger::info('Update directory', ['server' => $gserver['url'], 'id' => $gserver['id']]); + Worker::add(PRIORITY_LOW, 'UpdateServerDirectory', $gserver); + + if (--$no_of_queries == 0) { + break; + } + } + + DBA::close($gservers); + } + + /** + * Discover federated servers + */ + private static function discoverFederation() + { + $last = DI::config()->get('poco', 'last_federation_discovery'); + + if ($last) { + $next = $last + (24 * 60 * 60); + + if ($next > time()) { + return; + } + } + + // Discover federated servers + $curlResult = Network::fetchUrl("http://the-federation.info/pods.json"); + + if (!empty($curlResult)) { + $servers = json_decode($curlResult, true); + + if (!empty($servers['pods'])) { + foreach ($servers['pods'] as $server) { + Worker::add(PRIORITY_LOW, 'UpdateGServer', 'https://' . $server['host']); + } + } + } + + // Disvover Mastodon servers + $accesstoken = DI::config()->get('system', 'instances_social_key'); + + if (!empty($accesstoken)) { + $api = 'https://instances.social/api/1.0/instances/list?count=0'; + $header = ['Authorization: Bearer '.$accesstoken]; + $curlResult = Network::curl($api, false, ['headers' => $header]); + + if ($curlResult->isSuccess()) { + $servers = json_decode($curlResult->getBody(), true); + + foreach ($servers['instances'] as $server) { + $url = (is_null($server['https_score']) ? 'http' : 'https') . '://' . $server['name']; + Worker::add(PRIORITY_LOW, 'UpdateGServer', $url); + } + } + } + + DI::config()->set('poco', 'last_federation_discovery', time()); + } }