X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FGServer.php;h=713d6f114601d901b53036b7a46ade4b83805a0e;hb=1342825401e6e9e079eeec212271046930e1f475;hp=0f5f55ab671a19979fd4b1a02659d199a889fb24;hpb=097620b62799c96d610d73410ec07a6b8cdf82f0;p=friendica.git diff --git a/src/Model/GServer.php b/src/Model/GServer.php index 0f5f55ab67..713d6f1146 100644 --- a/src/Model/GServer.php +++ b/src/Model/GServer.php @@ -23,20 +23,20 @@ namespace Friendica\Model; use DOMDocument; use DOMXPath; +use Friendica\Core\Logger; use Friendica\Core\Protocol; +use Friendica\Core\System; use Friendica\Core\Worker; +use Friendica\Database\Database; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Module\Register; use Friendica\Network\CurlResult; -use Friendica\Util\Network; +use Friendica\Protocol\Relay; use Friendica\Util\DateTimeFormat; +use Friendica\Util\Network; use Friendica\Util\Strings; use Friendica\Util\XML; -use Friendica\Core\Logger; -use Friendica\Protocol\PortableContact; -use Friendica\Protocol\Diaspora; -use Friendica\Network\Probe; /** * This class handles GServer related functions @@ -47,6 +47,57 @@ class GServer const DT_NONE = 0; const DT_POCO = 1; const DT_MASTODON = 2; + + // Methods to detect server types + + // Non endpoint specific methods + const DETECT_MANUAL = 0; + const DETECT_HEADER = 1; + const DETECT_BODY = 2; + + // Implementation specific endpoints + const DETECT_FRIENDIKA = 10; + const DETECT_FRIENDICA = 11; + const DETECT_STATUSNET = 12; + const DETECT_GNUSOCIAL = 13; + const DETECT_CONFIG_JSON = 14; // Statusnet, GNU Social, Older Hubzilla/Redmatrix + const DETECT_SITEINFO_JSON = 15; // Newer Hubzilla + const DETECT_MASTODON_API = 16; + const DETECT_STATUS_PHP = 17; // Nextcloud + + // Standardized endpoints + const DETECT_STATISTICS_JSON = 100; + const DETECT_NODEINFO_1 = 101; + const DETECT_NODEINFO_2 = 102; + + /** + * Get the ID for the given server URL + * + * @param string $url + * @param boolean $no_check Don't check if the server hadn't been found + * @return int gserver id + */ + public static function getID(string $url, bool $no_check = false) + { + if (empty($url)) { + return null; + } + + $url = self::cleanURL($url); + + $gserver = DBA::selectFirst('gserver', ['id'], ['nurl' => Strings::normaliseLink($url)]); + if (DBA::isResult($gserver)) { + Logger::info('Got ID for URL', ['id' => $gserver['id'], 'url' => $url, 'callstack' => System::callstack(20)]); + return $gserver['id']; + } + + if ($no_check || !self::check($url)) { + return null; + } + + return self::getID($url, true); + } + /** * Checks if the given server is reachable * @@ -60,7 +111,10 @@ class GServer public static function reachable(string $profile, string $server = '', string $network = '', bool $force = false) { if ($server == '') { - $server = GContact::getBasepath($profile); + $contact = Contact::getByURL($profile, null, ['baseurl']); + if (!empty($contact['baseurl'])) { + $server = $contact['baseurl']; + } } if ($server == '') { @@ -128,17 +182,16 @@ class GServer /** * Checks the state of the given server. * - * @param string $server_url URL of the given server - * @param string $network Network value that is used, when detection failed - * @param boolean $force Force an update. + * @param string $server_url URL of the given server + * @param string $network Network value that is used, when detection failed + * @param boolean $force Force an update. + * @param boolean $only_nodeinfo Only use nodeinfo for server detection * * @return boolean 'true' if server seems vital */ - public static function check(string $server_url, string $network = '', bool $force = false) + public static function check(string $server_url, string $network = '', bool $force = false, bool $only_nodeinfo = false) { - // Unify the server address - $server_url = trim($server_url, '/'); - $server_url = str_replace('/index.php', '', $server_url); + $server_url = self::cleanURL($server_url); if ($server_url == '') { return false; @@ -174,47 +227,103 @@ class GServer Logger::info('Server is unknown. Start discovery.', ['Server' => $server_url]); } - return self::detect($server_url, $network); + return self::detect($server_url, $network, $only_nodeinfo); + } + + /** + * Set failed server status + * + * @param string $url + */ + private static function setFailure(string $url) + { + if (DBA::exists('gserver', ['nurl' => Strings::normaliseLink($url)])) { + DBA::update('gserver', ['failed' => true, 'last_failure' => DateTimeFormat::utcNow(), 'detection-method' => null], + ['nurl' => Strings::normaliseLink($url)]); + Logger::info('Set failed status for existing server', ['url' => $url]); + return; + } + DBA::insert('gserver', ['url' => $url, 'nurl' => Strings::normaliseLink($url), + 'network' => Protocol::PHANTOM, 'created' => DateTimeFormat::utcNow(), + 'failed' => true, 'last_failure' => DateTimeFormat::utcNow()]); + Logger::info('Set failed status for new server', ['url' => $url]); + } + + /** + * Remove unwanted content from the given URL + * + * @param string $url + * @return string cleaned URL + */ + public static function cleanURL(string $url) + { + $url = trim($url, '/'); + $url = str_replace('/index.php', '', $url); + + $urlparts = parse_url($url); + unset($urlparts['user']); + unset($urlparts['pass']); + unset($urlparts['query']); + unset($urlparts['fragment']); + return Network::unparseURL($urlparts); + } + + /** + * Return the base URL + * + * @param string $url + * @return string base URL + */ + private static function getBaseURL(string $url) + { + $urlparts = parse_url(self::cleanURL($url)); + unset($urlparts['path']); + return Network::unparseURL($urlparts); } /** * Detect server data (type, protocol, version number, ...) * The detected data is then updated or inserted in the gserver table. * - * @param string $url URL of the given server - * @param string $network Network value that is used, when detection failed + * @param string $url URL of the given server + * @param string $network Network value that is used, when detection failed + * @param boolean $only_nodeinfo Only use nodeinfo for server detection * * @return boolean 'true' if server could be detected */ - public static function detect(string $url, string $network = '') + public static function detect(string $url, string $network = '', bool $only_nodeinfo = false) { Logger::info('Detect server type', ['server' => $url]); - $serverdata = []; + $serverdata = ['detection-method' => self::DETECT_MANUAL]; $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); + $url = self::cleanURL($url); + + // Get base URL + $baseurl = self::getBaseURL($url); // 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)]); + DBA::update('gserver', ['failed' => true, 'last_failure' => DateTimeFormat::utcNow()], + ['nurl' => Strings::normaliseLink($original_url)]); } // When a nodeinfo is present, we don't need to dig further $xrd_timeout = DI::config()->get('system', 'xrd_timeout'); - $curlResult = Network::curl($url . '/.well-known/nodeinfo', false, ['timeout' => $xrd_timeout]); + $curlResult = DI::httpRequest()->get($url . '/.well-known/nodeinfo', ['timeout' => $xrd_timeout]); if ($curlResult->isTimeout()) { - DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]); + self::setFailure($url); return false; } $nodeinfo = self::fetchNodeinfo($url, $curlResult); + if ($only_nodeinfo && empty($nodeinfo)) { + Logger::info('Invalid nodeinfo in nodeinfo-mode, server is marked as failure', ['url' => $url]); + self::setFailure($url); + return false; + } // When nodeinfo isn't present, we use the older 'statistics.json' endpoint if (empty($nodeinfo)) { @@ -224,18 +333,53 @@ class GServer // If that didn't work out well, we use some protocol specific endpoints // 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])) { + if (!empty($nodeinfo['detection-method'])) { + $serverdata['detection-method'] = $nodeinfo['detection-method']; + } + // Fetch the landing page, possibly it reveals some data if (empty($nodeinfo['network'])) { - $curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout]); + if ($baseurl == $url) { + $basedata = $serverdata; + } else { + $basedata = ['detection-method' => self::DETECT_MANUAL]; + } + + $curlResult = DI::httpRequest()->get($baseurl, ['timeout' => $xrd_timeout]); if ($curlResult->isSuccess()) { - $serverdata = self::analyseRootHeader($curlResult, $serverdata); - $serverdata = self::analyseRootBody($curlResult, $serverdata, $url); + $basedata = self::analyseRootHeader($curlResult, $basedata); + $basedata = self::analyseRootBody($curlResult, $basedata, $baseurl); } if (!$curlResult->isSuccess() || empty($curlResult->getBody()) || self::invalidBody($curlResult->getBody())) { - DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]); + self::setFailure($url); return false; } + + if ($baseurl == $url) { + $serverdata = $basedata; + } else { + // When the base path doesn't seem to contain a social network we try the complete path. + // Most detectable system have to be installed in the root directory. + // We checked the base to avoid false positives. + $curlResult = DI::httpRequest()->get($url, ['timeout' => $xrd_timeout]); + if ($curlResult->isSuccess()) { + $urldata = self::analyseRootHeader($curlResult, $serverdata); + $urldata = self::analyseRootBody($curlResult, $urldata, $url); + + $comparebase = $basedata; + unset($comparebase['info']); + unset($comparebase['site_name']); + $compareurl = $urldata; + unset($compareurl['info']); + unset($compareurl['site_name']); + + // We assume that no one will install the identical system in the root and a subfolder + if (!empty(array_diff($comparebase, $compareurl))) { + $serverdata = $urldata; + } + } + } } if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::ACTIVITYPUB)) { @@ -246,7 +390,7 @@ class GServer // 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)]); + self::setFailure($url); return false; } @@ -271,6 +415,8 @@ class GServer if (empty($serverdata['network'])) { $serverdata = self::detectGNUSocial($url, $serverdata); } + + $serverdata = array_merge($nodeinfo, $serverdata); } else { $serverdata = $nodeinfo; } @@ -301,22 +447,19 @@ class GServer $registeredUsers = 1; } - if ($serverdata['network'] != Protocol::PHANTOM) { - $gcontacts = DBA::count('gcontact', ['server_url' => [$url, $serverdata['nurl']]]); - $apcontacts = DBA::count('apcontact', ['baseurl' => [$url, $serverdata['nurl']]]); - $contacts = DBA::count('contact', ['uid' => 0, 'baseurl' => [$url, $serverdata['nurl']]]); - $serverdata['registered-users'] = max($gcontacts, $apcontacts, $contacts, $registeredUsers); - } else { + if ($serverdata['network'] == Protocol::PHANTOM) { $serverdata['registered-users'] = $registeredUsers; $serverdata = self::detectNetworkViaContacts($url, $serverdata); } $serverdata['last_contact'] = DateTimeFormat::utcNow(); + $serverdata['failed'] = false; $gserver = DBA::selectFirst('gserver', ['network'], ['nurl' => Strings::normaliseLink($url)]); if (!DBA::isResult($gserver)) { $serverdata['created'] = DateTimeFormat::utcNow(); $ret = DBA::insert('gserver', $serverdata); + $id = DBA::lastInsertId(); } else { // Don't override the network with 'unknown' when there had been a valid entry before if (($serverdata['network'] == Protocol::PHANTOM) && !empty($gserver['network'])) { @@ -324,11 +467,25 @@ class GServer } $ret = DBA::update('gserver', $serverdata, ['nurl' => $serverdata['nurl']]); + $gserver = DBA::selectFirst('gserver', ['id'], ['nurl' => $serverdata['nurl']]); + if (DBA::isResult($gserver)) { + $id = $gserver['id']; + } + } + + if (!empty($serverdata['network']) && !empty($id) && ($serverdata['network'] != Protocol::PHANTOM)) { + $apcontacts = DBA::count('apcontact', ['gsid' => $id]); + $contacts = DBA::count('contact', ['uid' => 0, 'gsid' => $id]); + $max_users = max($apcontacts, $contacts, $registeredUsers); + if ($max_users > $registeredUsers) { + Logger::info('Update registered users', ['id' => $id, 'url' => $serverdata['nurl'], 'registered-users' => $max_users]); + DBA::update('gserver', ['registered-users' => $max_users], ['id' => $id]); + } } if (!empty($serverdata['network']) && in_array($serverdata['network'], [Protocol::DFRN, Protocol::DIASPORA])) { - self::discoverRelay($url); - } + self::discoverRelay($url); + } return $ret; } @@ -343,7 +500,7 @@ class GServer { Logger::info('Discover relay data', ['server' => $server_url]); - $curlResult = Network::curl($server_url . '/.well-known/x-social-relay'); + $curlResult = DI::httpRequest()->get($server_url . '/.well-known/x-social-relay'); if (!$curlResult->isSuccess()) { return; } @@ -353,7 +510,16 @@ class GServer return; } - $gserver = DBA::selectFirst('gserver', ['id', 'relay-subscribe', 'relay-scope'], ['nurl' => Strings::normaliseLink($server_url)]); + // Sanitize incoming data, see https://github.com/friendica/friendica/issues/8565 + $data['subscribe'] = (bool)$data['subscribe'] ?? false; + + if (!$data['subscribe'] || empty($data['scope']) || !in_array(strtolower($data['scope']), ['all', 'tags'])) { + $data['scope'] = ''; + $data['subscribe'] = false; + $data['tags'] = []; + } + + $gserver = DBA::selectFirst('gserver', ['id', 'url', 'network', 'relay-subscribe', 'relay-scope'], ['nurl' => Strings::normaliseLink($server_url)]); if (!DBA::isResult($gserver)) { return; } @@ -376,7 +542,7 @@ class GServer } foreach ($tags as $tag) { - DBA::insert('gserver-tag', ['gserver-id' => $gserver['id'], 'tag' => $tag], true); + DBA::insert('gserver-tag', ['gserver-id' => $gserver['id'], 'tag' => $tag], Database::INSERT_IGNORE); } } @@ -402,8 +568,22 @@ class GServer $fields['batch'] = $data['protocols']['dfrn']; } } + + if (isset($data['protocols']['activitypub'])) { + $fields['network'] = Protocol::ACTIVITYPUB; + + if (!empty($data['protocols']['activitypub']['actor'])) { + $fields['url'] = $data['protocols']['activitypub']['actor']; + } + if (!empty($data['protocols']['activitypub']['receive'])) { + $fields['batch'] = $data['protocols']['activitypub']['receive']; + } + } } - Diaspora::setRelayContact($server_url, $fields); + + Logger::info('Discovery ended', ['server' => $server_url, 'data' => $fields]); + + Relay::updateContact($gserver, $fields); } /** @@ -415,7 +595,7 @@ class GServer */ private static function fetchStatistics(string $url) { - $curlResult = Network::curl($url . '/statistics.json'); + $curlResult = DI::httpRequest()->get($url . '/statistics.json'); if (!$curlResult->isSuccess()) { return []; } @@ -425,7 +605,7 @@ class GServer return []; } - $serverdata = []; + $serverdata = ['detection-method' => self::DETECT_STATISTICS_JSON]; if (!empty($data['version'])) { $serverdata['version'] = $data['version']; @@ -472,6 +652,10 @@ class GServer */ private static function fetchNodeinfo(string $url, CurlResult $curlResult) { + if (!$curlResult->isSuccess()) { + return []; + } + $nodeinfo = json_decode($curlResult->getBody(), true); if (!is_array($nodeinfo) || empty($nodeinfo['links'])) { @@ -521,7 +705,7 @@ class GServer */ private static function parseNodeinfo1(string $nodeinfo_url) { - $curlResult = Network::curl($nodeinfo_url); + $curlResult = DI::httpRequest()->get($nodeinfo_url); if (!$curlResult->isSuccess()) { return []; @@ -533,9 +717,8 @@ class GServer return []; } - $server = []; - - $server['register_policy'] = Register::CLOSED; + $server = ['detection-method' => self::DETECT_NODEINFO_1, + 'register_policy' => Register::CLOSED]; if (!empty($nodeinfo['openRegistrations'])) { $server['register_policy'] = Register::OPEN; @@ -599,7 +782,7 @@ class GServer */ private static function parseNodeinfo2(string $nodeinfo_url) { - $curlResult = Network::curl($nodeinfo_url); + $curlResult = DI::httpRequest()->get($nodeinfo_url); if (!$curlResult->isSuccess()) { return []; } @@ -610,9 +793,8 @@ class GServer return []; } - $server = []; - - $server['register_policy'] = Register::CLOSED; + $server = ['detection-method' => self::DETECT_NODEINFO_2, + 'register_policy' => Register::CLOSED]; if (!empty($nodeinfo['openRegistrations'])) { $server['register_policy'] = Register::OPEN; @@ -677,7 +859,7 @@ class GServer */ private static function fetchSiteinfo(string $url, array $serverdata) { - $curlResult = Network::curl($url . '/siteinfo.json'); + $curlResult = DI::httpRequest()->get($url . '/siteinfo.json'); if (!$curlResult->isSuccess()) { return $serverdata; } @@ -687,6 +869,10 @@ class GServer return $serverdata; } + if (in_array($serverdata['detection-method'], [self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) { + $serverdata['detection-method'] = self::DETECT_SITEINFO_JSON; + } + if (!empty($data['url'])) { $serverdata['platform'] = strtolower($data['platform']); $serverdata['version'] = $data['version']; @@ -742,12 +928,12 @@ class GServer private static function validHostMeta(string $url) { $xrd_timeout = DI::config()->get('system', 'xrd_timeout'); - $curlResult = Network::curl($url . '/.well-known/host-meta', false, ['timeout' => $xrd_timeout]); + $curlResult = DI::httpRequest()->get($url . '/.well-known/host-meta', ['timeout' => $xrd_timeout]); if (!$curlResult->isSuccess()) { return false; } - $xrd = XML::parseString($curlResult->getBody(), false); + $xrd = XML::parseString($curlResult->getBody()); if (!is_object($xrd)) { return false; } @@ -789,20 +975,14 @@ class GServer { $contacts = []; - $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', ['url'], ['baseurl' => [$url, $serverdata['nurl']]]); - while ($gcontact = DBA::fetch($gcontacts)) { + while ($apcontact = DBA::fetch($apcontacts)) { $contacts[Strings::normaliseLink($apcontact['url'])] = $apcontact['url']; } DBA::close($apcontacts); $pcontacts = DBA::select('contact', ['url', 'nurl'], ['uid' => 0, 'baseurl' => [$url, $serverdata['nurl']]]); - while ($gcontact = DBA::fetch($gcontacts)) { + while ($pcontact = DBA::fetch($pcontacts)) { $contacts[$pcontact['nurl']] = $pcontact['url']; } DBA::close($pcontacts); @@ -812,8 +992,8 @@ class GServer } foreach ($contacts as $contact) { - $probed = Probe::uri($contact); - if (in_array($probed['network'], Protocol::FEDERATED)) { + $probed = Contact::getByURL($contact); + if (!empty($probed) && in_array($probed['network'], Protocol::FEDERATED)) { $serverdata['network'] = $probed['network']; break; } @@ -838,7 +1018,7 @@ class GServer { $serverdata['poco'] = ''; - $curlResult = Network::curl($url. '/poco'); + $curlResult = DI::httpRequest()->get($url . '/poco'); if (!$curlResult->isSuccess()) { return $serverdata; } @@ -868,7 +1048,7 @@ class GServer */ public static function checkMastodonDirectory(string $url, array $serverdata) { - $curlResult = Network::curl($url . '/api/v1/directory?limit=1'); + $curlResult = DI::httpRequest()->get($url . '/api/v1/directory?limit=1'); if (!$curlResult->isSuccess()) { return $serverdata; } @@ -895,7 +1075,7 @@ class GServer */ private static function detectNextcloud(string $url, array $serverdata) { - $curlResult = Network::curl($url . '/status.php'); + $curlResult = DI::httpRequest()->get($url . '/status.php'); if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) { return $serverdata; @@ -910,6 +1090,10 @@ class GServer $serverdata['platform'] = 'nextcloud'; $serverdata['version'] = $data['version']; $serverdata['network'] = Protocol::ACTIVITYPUB; + + if (in_array($serverdata['detection-method'], [self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) { + $serverdata['detection-method'] = self::DETECT_STATUS_PHP; + } } return $serverdata; @@ -925,7 +1109,7 @@ class GServer */ private static function detectMastodonAlikes(string $url, array $serverdata) { - $curlResult = Network::curl($url . '/api/v1/instance'); + $curlResult = DI::httpRequest()->get($url . '/api/v1/instance'); if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) { return $serverdata; @@ -936,6 +1120,10 @@ class GServer return $serverdata; } + if (in_array($serverdata['detection-method'], [self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) { + $serverdata['detection-method'] = self::DETECT_MASTODON_API; + } + if (!empty($data['version'])) { $serverdata['platform'] = 'mastodon'; $serverdata['version'] = $data['version'] ?? ''; @@ -987,13 +1175,13 @@ class GServer */ private static function detectHubzilla(string $url, array $serverdata) { - $curlResult = Network::curl($url . '/api/statusnet/config.json'); + $curlResult = DI::httpRequest()->get($url . '/api/statusnet/config.json'); if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) { return $serverdata; } $data = json_decode($curlResult->getBody(), true); - if (empty($data)) { + if (empty($data) || empty($data['site'])) { return $serverdata; } @@ -1041,11 +1229,16 @@ class GServer } if (!$closed && !$private and $inviteonly) { - $register_policy = Register::APPROVE; + $serverdata['register_policy'] = Register::APPROVE; } elseif (!$closed && !$private) { - $register_policy = Register::OPEN; + $serverdata['register_policy'] = Register::OPEN; } else { - $register_policy = Register::CLOSED; + $serverdata['register_policy'] = Register::CLOSED; + } + + if (!empty($serverdata['network']) && in_array($serverdata['detection-method'], + [self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) { + $serverdata['detection-method'] = self::DETECT_CONFIG_JSON; } return $serverdata; @@ -1080,7 +1273,7 @@ class GServer private static function detectGNUSocial(string $url, array $serverdata) { // Test for GNU Social - $curlResult = Network::curl($url . '/api/gnusocial/version.json'); + $curlResult = DI::httpRequest()->get($url . '/api/gnusocial/version.json'); if ($curlResult->isSuccess() && ($curlResult->getBody() != '{"error":"not implemented"}') && ($curlResult->getBody() != '') && (strlen($curlResult->getBody()) < 30)) { $serverdata['platform'] = 'gnusocial'; @@ -1089,11 +1282,16 @@ class GServer $serverdata['version'] = str_replace(["\r", "\n", "\t"], '', $serverdata['version']); $serverdata['version'] = trim($serverdata['version'], '"'); $serverdata['network'] = Protocol::OSTATUS; + + if (in_array($serverdata['detection-method'], [self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) { + $serverdata['detection-method'] = self::DETECT_GNUSOCIAL; + } + return $serverdata; } // Test for Statusnet - $curlResult = Network::curl($url . '/api/statusnet/version.json'); + $curlResult = DI::httpRequest()->get($url . '/api/statusnet/version.json'); if ($curlResult->isSuccess() && ($curlResult->getBody() != '{"error":"not implemented"}') && ($curlResult->getBody() != '') && (strlen($curlResult->getBody()) < 30)) { @@ -1110,6 +1308,10 @@ class GServer $serverdata['platform'] = 'statusnet'; $serverdata['network'] = Protocol::OSTATUS; } + + if (in_array($serverdata['detection-method'], [self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) { + $serverdata['detection-method'] = self::DETECT_STATUSNET; + } } return $serverdata; @@ -1125,9 +1327,14 @@ class GServer */ private static function detectFriendica(string $url, array $serverdata) { - $curlResult = Network::curl($url . '/friendica/json'); + $curlResult = DI::httpRequest()->get($url . '/friendica/json'); if (!$curlResult->isSuccess()) { - $curlResult = Network::curl($url . '/friendika/json'); + $curlResult = DI::httpRequest()->get($url . '/friendika/json'); + $friendika = true; + $platform = 'Friendika'; + } else { + $friendika = false; + $platform = 'Friendica'; } if (!$curlResult->isSuccess()) { @@ -1139,6 +1346,10 @@ class GServer return $serverdata; } + if (in_array($serverdata['detection-method'], [self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) { + $serverdata['detection-method'] = $friendika ? self::DETECT_FRIENDIKA : self::DETECT_FRIENDICA; + } + $serverdata['network'] = Protocol::DFRN; $serverdata['version'] = $data['version']; @@ -1174,7 +1385,7 @@ class GServer break; } - $serverdata['platform'] = strtolower($data['platform'] ?? ''); + $serverdata['platform'] = strtolower($data['platform'] ?? $platform); return $serverdata; } @@ -1222,7 +1433,8 @@ class GServer $serverdata['info'] = $attr['content']; } - if ($attr['name'] == 'application-name') { + if (in_array($attr['name'], ['application-name', 'al:android:app_name', 'al:ios:app_name', + 'twitter:app:name:googleplay', 'twitter:app:name:iphone', 'twitter:app:name:ipad'])) { $serverdata['platform'] = strtolower($attr['content']); if (in_array($attr['content'], ['Misskey', 'Write.as'])) { $serverdata['network'] = Protocol::ACTIVITYPUB; @@ -1243,6 +1455,10 @@ class GServer } else { $serverdata['network'] = Protocol::FEED; } + + if ($serverdata['detection-method'] == self::DETECT_MANUAL) { + $serverdata['detection-method'] = self::DETECT_BODY; + } } if (in_array($version_part[0], ['Friendika', 'Friendica'])) { $serverdata['platform'] = strtolower($version_part[0]); @@ -1298,6 +1514,10 @@ class GServer } } + if (!empty($serverdata['network']) && ($serverdata['detection-method'] == self::DETECT_MANUAL)) { + $serverdata['detection-method'] = self::DETECT_BODY; + } + return $serverdata; } @@ -1313,16 +1533,23 @@ class GServer { if ($curlResult->getHeader('server') == 'Mastodon') { $serverdata['platform'] = 'mastodon'; - $serverdata['network'] = $network = Protocol::ACTIVITYPUB; + $serverdata['network'] = Protocol::ACTIVITYPUB; } elseif ($curlResult->inHeader('x-diaspora-version')) { $serverdata['platform'] = 'diaspora'; - $serverdata['network'] = $network = Protocol::DIASPORA; + $serverdata['network'] = Protocol::DIASPORA; $serverdata['version'] = $curlResult->getHeader('x-diaspora-version'); } elseif ($curlResult->inHeader('x-friendica-version')) { $serverdata['platform'] = 'friendica'; - $serverdata['network'] = $network = Protocol::DFRN; + $serverdata['network'] = Protocol::DFRN; $serverdata['version'] = $curlResult->getHeader('x-friendica-version'); + } else { + return $serverdata; + } + + if ($serverdata['detection-method'] == self::DETECT_MANUAL) { + $serverdata['detection-method'] = self::DETECT_HEADER; } + return $serverdata; } @@ -1339,20 +1566,6 @@ class GServer 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 */ @@ -1371,12 +1584,12 @@ class GServer $last_update = date('c', time() - (60 * 60 * 24 * $requery_days)); - $gservers = DBA::p("SELECT `id`, `url`, `nurl`, `network`, `poco` + $gservers = DBA::p("SELECT `id`, `url`, `nurl`, `network`, `poco`, `directory-type` FROM `gserver` - WHERE `last_contact` >= `last_failure` - AND `poco` != '' + WHERE NOT `failed` + AND `directory-type` != ? AND `last_poco_query` < ? - ORDER BY RAND()", $last_update + ORDER BY RAND()", self::DT_NONE, $last_update ); while ($gserver = DBA::fetch($gservers)) { @@ -1387,9 +1600,15 @@ class GServer continue; } + Logger::info('Update peer list', ['server' => $gserver['url'], 'id' => $gserver['id']]); + Worker::add(PRIORITY_LOW, 'UpdateServerPeers', $gserver['url']); + Logger::info('Update directory', ['server' => $gserver['url'], 'id' => $gserver['id']]); Worker::add(PRIORITY_LOW, 'UpdateServerDirectory', $gserver); + $fields = ['last_poco_query' => DateTimeFormat::utcNow()]; + DBA::update('gserver', $fields, ['nurl' => $gserver['nurl']]); + if (--$no_of_queries == 0) { break; } @@ -1414,14 +1633,17 @@ class GServer } // 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']); + $protocols = ['activitypub', 'diaspora', 'dfrn', 'ostatus']; + foreach ($protocols as $protocol) { + $query = '{nodes(protocol:"' . $protocol . '"){host}}'; + $curlResult = DI::httpRequest()->fetch('https://the-federation.info/graphql?query=' . urlencode($query)); + if (!empty($curlResult)) { + $data = json_decode($curlResult, true); + if (!empty($data['data']['nodes'])) { + foreach ($data['data']['nodes'] as $server) { + // Using "only_nodeinfo" since servers that are listed on that page should always have it. + Worker::add(PRIORITY_LOW, 'UpdateGServer', 'https://' . $server['host'], true); + } } } } @@ -1432,7 +1654,7 @@ class GServer 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]); + $curlResult = DI::httpRequest()->get($api, ['header' => $header]); if ($curlResult->isSuccess()) { $servers = json_decode($curlResult->getBody(), true);