4 * @file src/Model/GServer.php
5 * @brief This file includes the GServer class to handle with servers
7 namespace Friendica\Model;
11 use Friendica\Core\Config;
12 use Friendica\Core\Protocol;
13 use Friendica\Database\DBA;
14 use Friendica\Module\Register;
15 use Friendica\Util\Network;
16 use Friendica\Util\DateTimeFormat;
17 use Friendica\Util\Strings;
18 use Friendica\Util\XML;
19 use Friendica\Core\Logger;
22 * @brief This class handles GServer related functions
27 * Detect server data (type, protocol, version number, ...)
28 * The detected data is then updated or inserted in the gserver table.
30 * @param string $url Server url
32 * @return boolean 'true' if server could be detected
34 public static function detect($url)
38 // When a nodeinfo is present, we don't need to dig further
39 $xrd_timeout = Config::get('system', 'xrd_timeout');
40 $curlResult = Network::curl($url . '/.well-known/nodeinfo', false, ['timeout' => $xrd_timeout]);
41 if ($curlResult->isTimeout()) {
42 DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]);
46 $nodeinfo = self::fetchNodeinfo($url, $curlResult);
48 // When nodeinfo isn't present, we use the older 'statistics.json' endpoint
49 if (empty($nodeinfo)) {
50 $nodeinfo = self::fetchStatistics($url);
53 // If that didn't work out well, we use some protocol specific endpoints
54 if (empty($nodeinfo) || empty($nodeinfo['network']) || ($nodeinfo['network'] == Protocol::DFRN)) {
55 // Fetch the landing page, possibly it reveals some data
56 $curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout]);
57 if ($curlResult->isSuccess()) {
58 $serverdata = self::analyseRootHeader($curlResult, $serverdata);
59 $serverdata = self::analyseRootBody($curlResult, $serverdata);
62 if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
63 DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]);
67 if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::DFRN)) {
68 $serverdata = self::detectFriendica($url, $serverdata);
71 if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::ACTIVITYPUB)) {
72 $serverdata = self::detectMastodonAlikes($url, $serverdata);
75 // the 'siteinfo.json' is some specific endpoint of Hubzilla and Red
76 if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::ZOT)) {
77 $serverdata = self::fetchSiteinfo($url, $serverdata);
80 // The 'siteinfo.json' doesn't seem to be present on older Hubzilla installations
81 if (empty($serverdata['network'])) {
82 $serverdata = self::detectHubzilla($url, $serverdata);
85 if (empty($serverdata['network'])) {
86 $serverdata = self::detectNextcloud($url, $serverdata);
89 if (empty($serverdata['network'])) {
90 $serverdata = self::detectGNUSocial($url, $serverdata);
93 $serverdata = $nodeinfo;
96 $serverdata = self::checkPoCo($url, $serverdata);
98 // We can't detect the network type. Possibly it is some system that we don't know yet
99 if (empty($serverdata['network'])) {
100 $serverdata['network'] = Protocol::PHANTOM;
103 // Check host-meta for phantom networks.
104 // Although this is not needed, it is a good indicator for a living system,
105 // since most systems had implemented it.
106 if (($serverdata['network'] == Protocol::PHANTOM) && !self::validHostMeta($url)) {
107 DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]);
111 $serverdata['url'] = $url;
112 $serverdata['nurl'] = Strings::normaliseLink($url);
114 // We take the highest number that we do find
115 $registeredUsers = $serverdata['registered-users'] ?? 0;
117 // On an active server there has to be at least a single user
118 if (($serverdata['network'] != Protocol::PHANTOM) && ($registeredUsers == 0)) {
119 $registeredUsers = 1;
122 $gcontacts = DBA::count('gcontact', ['server_url' => [$url, $serverdata['nurl']]]);
123 $apcontacts = DBA::count('apcontact', ['baseurl' => [$url, $serverdata['nurl']]]);
124 $contacts = DBA::count('contact', ['uid' => 0, 'baseurl' => [$url, $serverdata['nurl']]]);
125 $serverdata['registered-users'] = max($gcontacts, $apcontacts, $contacts, $registeredUsers);
127 $serverdata['last_contact'] = DateTimeFormat::utcNow();
129 if (!DBA::exists('gserver', ['nurl' => Strings::normaliseLink($url)])) {
130 $serverdata['created'] = DateTimeFormat::utcNow();
131 $ret = DBA::insert('gserver', $serverdata);
133 $ret = DBA::update('gserver', $serverdata, ['nurl' => $serverdata['nurl']]);
139 private static function fetchStatistics($url)
141 $curlResult = Network::curl($url . '/statistics.json');
142 if (!$curlResult->isSuccess()) {
146 $data = json_decode($curlResult->getBody(), true);
153 if (!empty($data['version'])) {
154 $serverdata['version'] = $data['version'];
155 // Version numbers on statistics.json are presented with additional info, e.g.:
156 // 0.6.3.0-p1702cc1c, 0.6.99.0-p1b9ab160 or 3.4.3-2-1191.
157 $serverdata['version'] = preg_replace('=(.+)-(.{4,})=ism', '$1', $serverdata['version']);
160 if (!empty($data['name'])) {
161 $serverdata['site_name'] = $data['name'];
164 if (!empty($data['network'])) {
165 $serverdata['platform'] = $data['network'];
167 if ($serverdata['platform'] == 'Diaspora') {
168 $serverdata['network'] = Protocol::DIASPORA;
169 } elseif ($serverdata['platform'] == 'Friendica') {
170 $serverdata['network'] = Protocol::DFRN;
171 } elseif ($serverdata['platform'] == 'hubzilla') {
172 $serverdata['network'] = Protocol::ZOT;
173 } elseif ($serverdata['platform'] == 'redmatrix') {
174 $serverdata['network'] = Protocol::ZOT;
179 if (!empty($data['registrations_open'])) {
180 $serverdata['register_policy'] = Register::OPEN;
182 $serverdata['register_policy'] = Register::CLOSED;
189 * @brief Detect server type by using the nodeinfo data
191 * @param string $url address of the server
192 * @return array Server data
193 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
195 private static function fetchNodeinfo($url, $curlResult)
197 $nodeinfo = json_decode($curlResult->getBody(), true);
199 if (!is_array($nodeinfo) || empty($nodeinfo['links'])) {
206 foreach ($nodeinfo['links'] as $link) {
207 if (!is_array($link) || empty($link['rel']) || empty($link['href'])) {
208 Logger::info('Invalid nodeinfo format', ['url' => $url]);
211 if ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/1.0') {
212 $nodeinfo1_url = $link['href'];
213 } elseif ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.0') {
214 $nodeinfo2_url = $link['href'];
218 if ($nodeinfo1_url . $nodeinfo2_url == '') {
224 // When the nodeinfo url isn't on the same host, then there is obviously something wrong
225 if (!empty($nodeinfo2_url) && (parse_url($url, PHP_URL_HOST) == parse_url($nodeinfo2_url, PHP_URL_HOST))) {
226 $server = self::parseNodeinfo2($nodeinfo2_url);
229 // When the nodeinfo url isn't on the same host, then there is obviously something wrong
230 if (empty($server) && !empty($nodeinfo1_url) && (parse_url($url, PHP_URL_HOST) == parse_url($nodeinfo1_url, PHP_URL_HOST))) {
231 $server = self::parseNodeinfo1($nodeinfo1_url);
238 * @brief Parses Nodeinfo 1
240 * @param string $nodeinfo_url address of the nodeinfo path
241 * @return array Server data
242 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
244 private static function parseNodeinfo1($nodeinfo_url)
246 $curlResult = Network::curl($nodeinfo_url);
248 if (!$curlResult->isSuccess()) {
252 $nodeinfo = json_decode($curlResult->getBody(), true);
254 if (!is_array($nodeinfo)) {
260 $server['register_policy'] = Register::CLOSED;
262 if (!empty($nodeinfo['openRegistrations'])) {
263 $server['register_policy'] = Register::OPEN;
266 if (is_array($nodeinfo['software'])) {
267 if (!empty($nodeinfo['software']['name'])) {
268 $server['platform'] = $nodeinfo['software']['name'];
271 if (!empty($nodeinfo['software']['version'])) {
272 $server['version'] = $nodeinfo['software']['version'];
273 // Version numbers on Nodeinfo are presented with additional info, e.g.:
274 // 0.6.3.0-p1702cc1c, 0.6.99.0-p1b9ab160 or 3.4.3-2-1191.
275 $server['version'] = preg_replace('=(.+)-(.{4,})=ism', '$1', $server['version']);
279 if (!empty($nodeinfo['metadata']['nodeName'])) {
280 $server['site_name'] = $nodeinfo['metadata']['nodeName'];
283 if (!empty($nodeinfo['usage']['users']['total'])) {
284 $server['registered-users'] = $nodeinfo['usage']['users']['total'];
287 if (!empty($nodeinfo['protocols']['inbound']) && is_array($nodeinfo['protocols']['inbound'])) {
289 foreach ($nodeinfo['protocols']['inbound'] as $protocol) {
290 $protocols[$protocol] = true;
293 if (!empty($protocols['friendica'])) {
294 $server['network'] = Protocol::DFRN;
295 } elseif (!empty($protocols['activitypub'])) {
296 $server['network'] = Protocol::ACTIVITYPUB;
297 } elseif (!empty($protocols['diaspora'])) {
298 $server['network'] = Protocol::DIASPORA;
299 } elseif (!empty($protocols['ostatus'])) {
300 $server['network'] = Protocol::OSTATUS;
301 } elseif (!empty($protocols['gnusocial'])) {
302 $server['network'] = Protocol::OSTATUS;
303 } elseif (!empty($protocols['zot'])) {
304 $server['network'] = Protocol::ZOT;
316 * @brief Parses Nodeinfo 2
318 * @param string $nodeinfo_url address of the nodeinfo path
319 * @return array Server data
320 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
322 private static function parseNodeinfo2($nodeinfo_url)
324 $curlResult = Network::curl($nodeinfo_url);
325 if (!$curlResult->isSuccess()) {
329 $nodeinfo = json_decode($curlResult->getBody(), true);
331 if (!is_array($nodeinfo)) {
337 $server['register_policy'] = Register::CLOSED;
339 if (!empty($nodeinfo['openRegistrations'])) {
340 $server['register_policy'] = Register::OPEN;
343 if (is_array($nodeinfo['software'])) {
344 if (!empty($nodeinfo['software']['name'])) {
345 $server['platform'] = $nodeinfo['software']['name'];
348 if (!empty($nodeinfo['software']['version'])) {
349 $server['version'] = $nodeinfo['software']['version'];
350 // Version numbers on Nodeinfo are presented with additional info, e.g.:
351 // 0.6.3.0-p1702cc1c, 0.6.99.0-p1b9ab160 or 3.4.3-2-1191.
352 $server['version'] = preg_replace('=(.+)-(.{4,})=ism', '$1', $server['version']);
356 if (!empty($nodeinfo['metadata']['nodeName'])) {
357 $server['site_name'] = $nodeinfo['metadata']['nodeName'];
360 if (!empty($nodeinfo['usage']['users']['total'])) {
361 $server['registered-users'] = $nodeinfo['usage']['users']['total'];
364 if (!empty($nodeinfo['protocols'])) {
366 foreach ($nodeinfo['protocols'] as $protocol) {
367 $protocols[$protocol] = true;
370 if (!empty($protocols['friendica'])) {
371 $server['network'] = Protocol::DFRN;
372 } elseif (!empty($protocols['activitypub'])) {
373 $server['network'] = Protocol::ACTIVITYPUB;
374 } elseif (!empty($protocols['diaspora'])) {
375 $server['network'] = Protocol::DIASPORA;
376 } elseif (!empty($protocols['ostatus'])) {
377 $server['network'] = Protocol::OSTATUS;
378 } elseif (!empty($protocols['gnusocial'])) {
379 $server['network'] = Protocol::OSTATUS;
380 } elseif (!empty($protocols['zot'])) {
381 $server['network'] = Protocol::ZOT;
385 if (empty($server)) {
392 private static function fetchSiteinfo($url, $serverdata)
394 $curlResult = Network::curl($url . '/siteinfo.json');
395 if (!$curlResult->isSuccess()) {
399 $data = json_decode($curlResult->getBody(), true);
404 if (!empty($data['url'])) {
405 $serverdata['platform'] = $data['platform'];
406 $serverdata['version'] = $data['version'];
409 if (!empty($data['plugins'])) {
410 if (in_array('pubcrawl', $data['plugins'])) {
411 $serverdata['network'] = Protocol::ACTIVITYPUB;
412 } elseif (in_array('diaspora', $data['plugins'])) {
413 $serverdata['network'] = Protocol::DIASPORA;
414 } elseif (in_array('gnusoc', $data['plugins'])) {
415 $serverdata['network'] = Protocol::OSTATUS;
417 $serverdata['network'] = Protocol::ZOT;
421 if (!empty($data['site_name'])) {
422 $serverdata['site_name'] = $data['site_name'];
425 if (!empty($data['channels_total'])) {
426 $serverdata['registered-users'] = $data['channels_total'];
429 if (!empty($data['register_policy'])) {
430 switch ($data['register_policy']) {
431 case 'REGISTER_OPEN':
432 $serverdata['register_policy'] = Register::OPEN;
435 case 'REGISTER_APPROVE':
436 $serverdata['register_policy'] = Register::APPROVE;
439 case 'REGISTER_CLOSED':
441 $serverdata['register_policy'] = Register::CLOSED;
449 private static function validHostMeta($url)
451 $xrd_timeout = Config::get('system', 'xrd_timeout');
452 $curlResult = Network::curl($url . '/.well-known/host-meta', false, ['timeout' => $xrd_timeout]);
453 if (!$curlResult->isSuccess()) {
457 $xrd = XML::parseString($curlResult->getBody(), false);
458 if (!is_object($xrd)) {
462 $elements = XML::elementToArray($xrd);
463 if (empty($elements) || empty($elements['xrd']) || empty($elements['xrd']['link'])) {
468 foreach ($elements['xrd']['link'] as $link) {
469 if (empty($link['rel']) || empty($link['type']) || empty($link['template'])) {
473 if ($link['type'] == 'application/xrd+xml') {
474 // When the webfinger host is the same like the system host, it should be ok.
475 $valid = (parse_url($url, PHP_URL_HOST) == parse_url($link['template'], PHP_URL_HOST));
482 private static function checkPoCo($url, $serverdata)
484 $curlResult = Network::curl($url. '/poco');
485 if (!$curlResult->isSuccess()) {
489 $data = json_decode($curlResult->getBody(), true);
494 if (!empty($data['totalResults'])) {
495 $registeredUsers = $serverdata['registered-users'] ?? 0;
496 $serverdata['registered-users'] = max($data['totalResults'], $registeredUsers);
497 $serverdata['poco'] = $url . '/poco';
499 $serverdata['poco'] = '';
505 private static function detectNextcloud($url, $serverdata)
507 $curlResult = Network::curl($url . '/status.php');
509 if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) {
513 $data = json_decode($curlResult->getBody(), true);
518 if (!empty($data['version'])) {
519 $serverdata['platform'] = 'nextcloud';
520 $serverdata['version'] = $data['version'];
521 $serverdata['network'] = Protocol::ACTIVITYPUB;
527 private static function detectMastodonAlikes($url, $serverdata)
529 $curlResult = Network::curl($url . '/api/v1/instance');
531 if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) {
535 $data = json_decode($curlResult->getBody(), true);
540 if (!empty($data['version'])) {
541 $serverdata['platform'] = 'mastodon';
542 $serverdata['version'] = defaults($data, 'version', '');
543 $serverdata['network'] = Protocol::ACTIVITYPUB;
546 if (!empty($data['title'])) {
547 $serverdata['site_name'] = $data['title'];
550 if (!empty($data['description'])) {
551 $serverdata['info'] = trim($data['description']);
554 if (!empty($data['stats']['user_count'])) {
555 $serverdata['registered-users'] = $data['stats']['user_count'];
558 if (!empty($serverdata['version']) && preg_match('/.*?\(compatible;\s(.*)\s(.*)\)/ism', $serverdata['version'], $matches)) {
559 $serverdata['platform'] = $matches[1];
560 $serverdata['version'] = $matches[2];
563 if (!empty($serverdata['version']) && strstr($serverdata['version'], 'Pleroma')) {
564 $serverdata['platform'] = 'pleroma';
565 $serverdata['version'] = trim(str_replace('Pleroma', '', $serverdata['version']));
571 private static function detectHubzilla($url, $serverdata)
573 $curlResult = Network::curl($url . '/api/statusnet/config.json');
574 if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) {
578 $data = json_decode($curlResult->getBody(), true);
583 if (!empty($data['site']['name'])) {
584 $serverdata['site_name'] = $data['site']['name'];
587 if (!empty($data['site']['platform'])) {
588 $serverdata['platform'] = $data['site']['platform']['PLATFORM_NAME'];
589 $serverdata['version'] = $data['site']['platform']['STD_VERSION'];
590 $serverdata['network'] = Protocol::ZOT;
593 if (!empty($data['site']['hubzilla'])) {
594 $serverdata['platform'] = $data['site']['hubzilla']['PLATFORM_NAME'];
595 $serverdata['version'] = $data['site']['hubzilla']['RED_VERSION'];
596 $serverdata['network'] = Protocol::ZOT;
599 if (!empty($data['site']['redmatrix'])) {
600 if (!empty($data['site']['redmatrix']['PLATFORM_NAME'])) {
601 $serverdata['platform'] = $data['site']['redmatrix']['PLATFORM_NAME'];
602 } elseif (!empty($data['site']['redmatrix']['RED_PLATFORM'])) {
603 $serverdata['platform'] = $data['site']['redmatrix']['RED_PLATFORM'];
606 $serverdata['version'] = $data['site']['redmatrix']['RED_VERSION'];
607 $serverdata['network'] = Protocol::ZOT;
614 if (!empty($data['site']['closed'])) {
615 $closed = self::toBoolean($data['site']['closed']);
618 if (!empty($data['site']['private'])) {
619 $private = self::toBoolean($data['site']['private']);
622 if (!empty($data['site']['inviteonly'])) {
623 $inviteonly = self::toBoolean($data['site']['inviteonly']);
626 if (!$closed && !$private and $inviteonly) {
627 $register_policy = Register::APPROVE;
628 } elseif (!$closed && !$private) {
629 $register_policy = Register::OPEN;
631 $register_policy = Register::CLOSED;
637 private static function toBoolean($val)
639 if (($val == 'true') || ($val == 1)) {
641 } elseif (($val == 'false') || ($val == 0)) {
648 private static function detectGNUSocial($url, $serverdata)
650 $curlResult = Network::curl($url . '/api/statusnet/version.json');
652 if ($curlResult->isSuccess() && ($curlResult->getBody() != '{"error":"not implemented"}') &&
653 ($curlResult->getBody() != '') && (strlen($curlResult->getBody()) < 30)) {
654 $serverdata['platform'] = 'StatusNet';
655 // Remove junk that some GNU Social servers return
656 $serverdata['version'] = str_replace(chr(239).chr(187).chr(191), '', $curlResult->getBody());
657 $serverdata['version'] = trim($serverdata['version'], '"');
658 $serverdata['network'] = Protocol::OSTATUS;
661 // Test for GNU Social
662 $curlResult = Network::curl($url . '/api/gnusocial/version.json');
664 if ($curlResult->isSuccess() && ($curlResult->getBody() != '{"error":"not implemented"}') &&
665 ($curlResult->getBody() != '') && (strlen($curlResult->getBody()) < 30)) {
666 $serverdata['platform'] = 'GNU Social';
667 // Remove junk that some GNU Social servers return
668 $serverdata['version'] = str_replace(chr(239) . chr(187) . chr(191), '', $curlResult->getBody());
669 $serverdata['version'] = trim($serverdata['version'], '"');
670 $serverdata['network'] = Protocol::OSTATUS;
676 private static function detectFriendica($url, $serverdata)
678 $curlResult = Network::curl($url . '/friendica/json');
679 if (!$curlResult->isSuccess()) {
680 $curlResult = Network::curl($url . '/friendika/json');
683 if (!$curlResult->isSuccess()) {
687 $data = json_decode($curlResult->getBody(), true);
688 if (empty($data) || empty($data['version'])) {
692 $serverdata['network'] = Protocol::DFRN;
693 $serverdata['version'] = $data['version'];
695 if (!empty($data['no_scrape_url'])) {
696 $serverdata['noscrape'] = $data['no_scrape_url'];
699 if (!empty($data['site_name'])) {
700 $serverdata['site_name'] = $data['site_name'];
703 if (!empty($data['info'])) {
704 $serverdata['info'] = trim($data['info']);
707 $register_policy = defaults($data, 'register_policy', 'REGISTER_CLOSED');
708 switch ($register_policy) {
709 case 'REGISTER_OPEN':
710 $serverdata['register_policy'] = Register::OPEN;
713 case 'REGISTER_APPROVE':
714 $serverdata['register_policy'] = Register::APPROVE;
717 case 'REGISTER_CLOSED':
718 case 'REGISTER_INVITATION':
719 $serverdata['register_policy'] = Register::CLOSED;
722 Logger::info('Register policy is invalid', ['policy' => $register_policy, 'server' => $url]);
723 $serverdata['register_policy'] = Register::CLOSED;
727 $serverdata['platform'] = defaults($data, 'platform', '');
732 private static function analyseRootBody($curlResult, $serverdata)
734 $doc = new DOMDocument();
735 @$doc->loadHTML($curlResult->getBody());
736 $xpath = new DOMXPath($doc);
738 $title = trim(XML::getFirstNodeValue($xpath, '//head/title/text()'));
739 if (!empty($title)) {
740 $serverdata['site_name'] = $title;
743 $list = $xpath->query('//meta[@name]');
745 foreach ($list as $node) {
747 if ($node->attributes->length) {
748 foreach ($node->attributes as $attribute) {
749 $attribute->value = trim($attribute->value);
750 if (empty($attribute->value)) {
754 $attr[$attribute->name] = $attribute->value;
757 if (empty($attr['name']) || empty($attr['content'])) {
762 if ($attr['name'] == 'description') {
763 $serverdata['info'] = $attr['content'];
766 if ($attr['name'] == 'application-name') {
767 $serverdata['platform'] = $attr['content'];
768 if (in_array($attr['content'], ['Misskey', 'Write.as'])) {
769 $serverdata['network'] = Protocol::ACTIVITYPUB;
773 if ($attr['name'] == 'generator') {
774 $serverdata['platform'] = $attr['content'];
776 $version_part = explode(' ', $attr['content']);
778 if (count($version_part) == 2) {
779 if (in_array($version_part[0], ['WordPress'])) {
780 $serverdata['platform'] = $version_part[0];
781 $serverdata['version'] = $version_part[1];
782 $serverdata['network'] = Protocol::ACTIVITYPUB;
784 if (in_array($version_part[0], ['Friendika', 'Friendica'])) {
785 $serverdata['platform'] = $version_part[0];
786 $serverdata['version'] = $version_part[1];
787 $serverdata['network'] = Protocol::DFRN;
793 $list = $xpath->query('//meta[@property]');
795 foreach ($list as $node) {
797 if ($node->attributes->length) {
798 foreach ($node->attributes as $attribute) {
799 $attribute->value = trim($attribute->value);
800 if (empty($attribute->value)) {
804 $attr[$attribute->name] = $attribute->value;
807 if (empty($attr['property']) || empty($attr['content'])) {
812 if ($attr['property'] == 'og:site_name') {
813 $serverdata['site_name'] = $attr['content'];
816 if ($attr['property'] == 'og:description') {
817 $serverdata['info'] = $attr['content'];
820 if ($attr['property'] == 'og:platform') {
821 $serverdata['platform'] = $attr['content'];
823 if (in_array($attr['content'], ['PeerTube'])) {
824 $serverdata['network'] = Protocol::ACTIVITYPUB;
828 if ($attr['property'] == 'generator') {
829 $serverdata['platform'] = $attr['content'];
831 if (in_array($attr['content'], ['hubzilla'])) {
832 // We later check which compatible protocol modules are loaded.
833 $serverdata['network'] = Protocol::ZOT;
841 private static function analyseRootHeader($curlResult, $serverdata)
843 if ($curlResult->getHeader('server') == 'Mastodon') {
844 $serverdata['platform'] = 'mastodon';
845 $serverdata['network'] = $network = Protocol::ACTIVITYPUB;
846 } elseif ($curlResult->inHeader('x-diaspora-version')) {
847 $serverdata['platform'] = 'diaspora';
848 $serverdata['network'] = $network = Protocol::DIASPORA;
849 $serverdata['version'] = $curlResult->getHeader('x-diaspora-version');
851 } elseif ($curlResult->inHeader('x-friendica-version')) {
852 $serverdata['platform'] = 'friendica';
853 $serverdata['network'] = $network = Protocol::DFRN;
854 $serverdata['version'] = $curlResult->getHeader('x-friendica-version');