]> git.mxchange.org Git - friendica.git/blob - src/Model/GServer.php
a1f194ed9a4e6de6fc767e8003de9935bd3134ff
[friendica.git] / src / Model / GServer.php
1 <?php
2
3 /**
4  * @file src/Model/GServer.php
5  * @brief This file includes the GServer class to handle with servers
6  */
7 namespace Friendica\Model;
8
9 use DOMDocument;
10 use DOMXPath;
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;
20
21 /**
22  * @brief This class handles GServer related functions
23  */
24 class GServer
25 {
26         /**
27          * Detect server data (type, protocol, version number, ...)
28          * The detected data is then updated or inserted in the gserver table.
29          *
30          * @param string  $url   Server url
31          *
32          * @return boolean 'true' if server could be detected
33          */
34         public static function detect($url)
35         {
36                 /// @Todo:
37                 // - poco endpoint
38                 // - Pleroma version number
39
40                 $serverdata = [];
41
42                 // When a nodeinfo is present, we don't need to dig further
43                 $xrd_timeout = Config::get('system', 'xrd_timeout');
44                 $curlResult = Network::curl($url . '/.well-known/nodeinfo', false, ['timeout' => $xrd_timeout]);
45                 if ($curlResult->isTimeout()) {
46                         DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]);
47                         return false;
48                 }
49
50                 $nodeinfo = self::fetchNodeinfo($url, $curlResult);
51
52                 // When nodeinfo isn't present, we use the older 'statistics.json' endpoint
53                 if (empty($nodeinfo)) {
54                         $nodeinfo = self::fetchStatistics($url);
55                 }
56
57                 // If that didn't work out well, we use some protocol specific endpoints
58                 if (empty($nodeinfo) || ($nodeinfo['network'] == Protocol::DFRN)) {
59                         // Fetch the landing page, possibly it reveals some data
60                         $curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout]);
61                         if ($curlResult->isSuccess()) {
62                                 $serverdata = self::analyseRootHeader($curlResult, $serverdata);
63                                 $serverdata = self::analyseRootBody($curlResult, $serverdata);
64                         }
65
66                         if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
67                                 DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]);
68                                 return false;
69                         }
70
71                         if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::DFRN)) {
72                                 $serverdata = self::detectFriendica($url, $serverdata);
73                         }
74
75                         if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::ACTIVITYPUB)) {
76                                 $serverdata = self::detectMastodonAlikes($url, $serverdata);
77                         }
78
79                         // the 'siteinfo.json' is some specific endpoint of Hubzilla and Red
80                         if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::ZOT)) {
81                                 $serverdata = self::fetchSiteinfo($url, $serverdata);
82                         }
83
84                         // The 'siteinfo.json' doesn't seem to be present on older Hubzilla installations
85                         if (empty($serverdata['network'])) {
86                                 $serverdata = self::detectHubzilla($url, $serverdata);
87                         }
88
89                         if (empty($serverdata['network'])) {
90                                 $serverdata = self::detectNextcloud($url, $serverdata);
91                         }
92
93                         if (empty($serverdata['network'])) {
94                                 $serverdata = self::detectGNUSocial($url, $serverdata);
95                         }
96                 } else {
97                         $serverdata = $nodeinfo;
98                 }
99
100                 // We can't detect the network type. Possibly it is some system that we don't know yet
101                 if (empty($serverdata['network'])) {
102                         $serverdata['network'] = Protocol::PHANTOM;
103                 }
104
105                 $serverdata['url'] = $url;
106                 $serverdata['nurl'] = Strings::normaliseLink($url);
107
108                 // When we don't have the registered users, we simply count what we know
109                 if (empty($serverdata['registered-users'])) {
110                         $gcontacts = DBA::count('gcontact', ['server_url' => [$url, $serverdata['nurl']]]);
111                         $apcontacts = DBA::count('apcontact', ['baseurl' => [$url, $serverdata['nurl']]]);
112                         $contacts = DBA::count('contact', ['uid' => 0, 'baseurl' => [$url, $serverdata['nurl']]]);
113                         $serverdata['registered-users'] = max($gcontacts, $apcontacts, $contacts);
114                 }
115
116                 $serverdata['last_contact'] = DateTimeFormat::utcNow();
117
118                 if (!DBA::exists('gserver', ['nurl' => Strings::normaliseLink($url)])) {
119                         $serverdata['created'] = DateTimeFormat::utcNow();
120                         $ret = DBA::insert('gserver', $serverdata);
121                 } else {
122                         $ret = DBA::update('gserver', $serverdata, ['nurl' => $serverdata['nurl']]);
123                 }
124
125                 print_r($serverdata);
126
127                 return $ret;
128         }
129
130         private static function fetchStatistics($url)
131         {
132                 $curlResult = Network::curl($url . '/statistics.json');
133                 if (!$curlResult->isSuccess()) {
134                         return [];
135                 }
136
137                 $data = json_decode($curlResult->getBody(), true);
138                 if (empty($data)) {
139                         return [];
140                 }
141
142                 $serverdata = [];
143
144                 if (!empty($data['version'])) {
145                         $serverdata['version'] = $data['version'];
146                         // Version numbers on statistics.json are presented with additional info, e.g.:
147                         // 0.6.3.0-p1702cc1c, 0.6.99.0-p1b9ab160 or 3.4.3-2-1191.
148                         $serverdata['version'] = preg_replace('=(.+)-(.{4,})=ism', '$1', $serverdata['version']);
149                 }
150
151                 if (!empty($data['name'])) {
152                         $serverdata['site_name'] = $data['name'];
153                 }
154
155                 if (!empty($data['network'])) {
156                         $serverdata['platform'] = $data['network'];
157
158                         if ($serverdata['platform'] == 'Diaspora') {
159                                 $serverdata['network'] = Protocol::DIASPORA;
160                         } elseif ($serverdata['platform'] == 'Friendica') {
161                                 $serverdata['network'] = Protocol::DFRN;
162                         } elseif ($serverdata['platform'] == 'hubzilla') {
163                                 $serverdata['network'] = Protocol::ZOT;
164                         } elseif ($serverdata['platform'] == 'redmatrix') {
165                                 $serverdata['network'] = Protocol::ZOT;
166                         }
167                 }
168
169
170                 if (!empty($data['registrations_open'])) {
171                         $serverdata['register_policy'] = Register::OPEN;
172                 } else {
173                         $serverdata['register_policy'] = Register::CLOSED;
174                 }
175
176                 return $serverdata;
177         }
178
179         /**
180          * @brief Detect server type by using the nodeinfo data
181          *
182          * @param string $url address of the server
183          * @return array Server data
184          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
185          */
186         private static function fetchNodeinfo($url, $curlResult)
187         {
188                 $nodeinfo = json_decode($curlResult->getBody(), true);
189
190                 if (!is_array($nodeinfo) || empty($nodeinfo['links'])) {
191                         return [];
192                 }
193
194                 $nodeinfo1_url = '';
195                 $nodeinfo2_url = '';
196
197                 foreach ($nodeinfo['links'] as $link) {
198                         if (!is_array($link) || empty($link['rel']) || empty($link['href'])) {
199                                 Logger::info('Invalid nodeinfo format', ['url' => $url]);
200                                 continue;
201                         }
202                         if ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/1.0') {
203                                 $nodeinfo1_url = $link['href'];
204                         } elseif ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.0') {
205                                 $nodeinfo2_url = $link['href'];
206                         }
207                 }
208
209                 if ($nodeinfo1_url . $nodeinfo2_url == '') {
210                         return [];
211                 }
212
213                 $server = [];
214
215                 // When the nodeinfo url isn't on the same host, then there is obviously something wrong
216                 if (!empty($nodeinfo2_url) && (parse_url($url, PHP_URL_HOST) == parse_url($nodeinfo2_url, PHP_URL_HOST))) {
217                         $server = self::parseNodeinfo2($nodeinfo2_url);
218                 }
219
220                 // When the nodeinfo url isn't on the same host, then there is obviously something wrong
221                 if (empty($server) && !empty($nodeinfo1_url) && (parse_url($url, PHP_URL_HOST) == parse_url($nodeinfo1_url, PHP_URL_HOST))) {
222                         $server = self::parseNodeinfo1($nodeinfo1_url);
223                 }
224
225                 return $server;
226         }
227
228         /**
229          * @brief Parses Nodeinfo 1
230          *
231          * @param string $nodeinfo_url address of the nodeinfo path
232          * @return array Server data
233          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
234          */
235         private static function parseNodeinfo1($nodeinfo_url)
236         {
237                 $curlResult = Network::curl($nodeinfo_url);
238
239                 if (!$curlResult->isSuccess()) {
240                         return false;
241                 }
242
243                 $nodeinfo = json_decode($curlResult->getBody(), true);
244
245                 if (!is_array($nodeinfo)) {
246                         return false;
247                 }
248
249                 $server = [];
250
251                 $server['register_policy'] = Register::CLOSED;
252
253                 if (!empty($nodeinfo['openRegistrations'])) {
254                         $server['register_policy'] = Register::OPEN;
255                 }
256
257                 if (is_array($nodeinfo['software'])) {
258                         if (!empty($nodeinfo['software']['name'])) {
259                                 $server['platform'] = $nodeinfo['software']['name'];
260                         }
261
262                         if (!empty($nodeinfo['software']['version'])) {
263                                 $server['version'] = $nodeinfo['software']['version'];
264                                 // Version numbers on Nodeinfo are presented with additional info, e.g.:
265                                 // 0.6.3.0-p1702cc1c, 0.6.99.0-p1b9ab160 or 3.4.3-2-1191.
266                                 $server['version'] = preg_replace('=(.+)-(.{4,})=ism', '$1', $server['version']);
267                         }
268                 }
269
270                 if (!empty($nodeinfo['metadata']['nodeName'])) {
271                         $server['site_name'] = $nodeinfo['metadata']['nodeName'];
272                 }
273
274                 if (!empty($nodeinfo['usage']['users']['total'])) {
275                         $server['registered-users'] = $nodeinfo['usage']['users']['total'];
276                 }
277
278                 if (!empty($nodeinfo['protocols']['inbound']) && is_array($nodeinfo['protocols']['inbound'])) {
279                         $protocols = [];
280                         foreach ($nodeinfo['protocols']['inbound'] as $protocol) {
281                                 $protocols[$protocol] = true;
282                         }
283
284                         if (!empty($protocols['friendica'])) {
285                                 $server['network'] = Protocol::DFRN;
286                         } elseif (!empty($protocols['activitypub'])) {
287                                 $server['network'] = Protocol::ACTIVITYPUB;
288                         } elseif (!empty($protocols['diaspora'])) {
289                                 $server['network'] = Protocol::DIASPORA;
290                         } elseif (!empty($protocols['ostatus'])) {
291                                 $server['network'] = Protocol::OSTATUS;
292                         } elseif (!empty($protocols['gnusocial'])) {
293                                 $server['network'] = Protocol::OSTATUS;
294                         } elseif (!empty($protocols['zot'])) {
295                                 $server['network'] = Protocol::ZOT;
296                         }
297                 }
298
299                 if (!$server) {
300                         return false;
301                 }
302
303                 return $server;
304         }
305
306         /**
307          * @brief Parses Nodeinfo 2
308          *
309          * @param string $nodeinfo_url address of the nodeinfo path
310          * @return array Server data
311          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
312          */
313         private static function parseNodeinfo2($nodeinfo_url)
314         {
315                 $curlResult = Network::curl($nodeinfo_url);
316                 if (!$curlResult->isSuccess()) {
317                         return false;
318                 }
319
320                 $nodeinfo = json_decode($curlResult->getBody(), true);
321
322                 if (!is_array($nodeinfo)) {
323                         return false;
324                 }
325
326                 $server = [];
327
328                 $server['register_policy'] = Register::CLOSED;
329
330                 if (!empty($nodeinfo['openRegistrations'])) {
331                         $server['register_policy'] = Register::OPEN;
332                 }
333
334                 if (is_array($nodeinfo['software'])) {
335                         if (!empty($nodeinfo['software']['name'])) {
336                                 $server['platform'] = $nodeinfo['software']['name'];
337                         }
338
339                         if (!empty($nodeinfo['software']['version'])) {
340                                 $server['version'] = $nodeinfo['software']['version'];
341                                 // Version numbers on Nodeinfo are presented with additional info, e.g.:
342                                 // 0.6.3.0-p1702cc1c, 0.6.99.0-p1b9ab160 or 3.4.3-2-1191.
343                                 $server['version'] = preg_replace('=(.+)-(.{4,})=ism', '$1', $server['version']);
344                         }
345                 }
346
347                 if (!empty($nodeinfo['metadata']['nodeName'])) {
348                         $server['site_name'] = $nodeinfo['metadata']['nodeName'];
349                 }
350
351                 if (!empty($nodeinfo['usage']['users']['total'])) {
352                         $server['registered-users'] = $nodeinfo['usage']['users']['total'];
353                 }
354
355                 if (!empty($nodeinfo['protocols'])) {
356                         $protocols = [];
357                         foreach ($nodeinfo['protocols'] as $protocol) {
358                                 $protocols[$protocol] = true;
359                         }
360
361                         if (!empty($protocols['friendica'])) {
362                                 $server['network'] = Protocol::DFRN;
363                         } elseif (!empty($protocols['activitypub'])) {
364                                 $server['network'] = Protocol::ACTIVITYPUB;
365                         } elseif (!empty($protocols['diaspora'])) {
366                                 $server['network'] = Protocol::DIASPORA;
367                         } elseif (!empty($protocols['ostatus'])) {
368                                 $server['network'] = Protocol::OSTATUS;
369                         } elseif (!empty($protocols['gnusocial'])) {
370                                 $server['network'] = Protocol::OSTATUS;
371                         } elseif (!empty($protocols['zot'])) {
372                                 $server['network'] = Protocol::ZOT;
373                         }
374                 }
375
376                 if (empty($server)) {
377                         return false;
378                 }
379
380                 return $server;
381         }
382
383         private static function fetchSiteinfo($url, $serverdata)
384         {
385                 $curlResult = Network::curl($url . '/siteinfo.json');
386                 if (!$curlResult->isSuccess()) {
387                         return $serverdata;
388                 }
389
390                 $data = json_decode($curlResult->getBody(), true);
391                 if (empty($data)) {
392                         return $serverdata;
393                 }
394
395                 if (!empty($data['url'])) {
396                         $serverdata['platform'] = $data['platform'];
397                         $serverdata['version'] = $data['version'];
398                 }
399
400                 if (!empty($data['plugins'])) {
401                         if (in_array('pubcrawl', $data['plugins'])) {
402                                 $serverdata['network'] = Protocol::ACTIVITYPUB;
403                         } elseif (in_array('diaspora', $data['plugins'])) {
404                                 $serverdata['network'] = Protocol::DIASPORA;
405                         } elseif (in_array('gnusoc', $data['plugins'])) {
406                                 $serverdata['network'] = Protocol::OSTATUS;
407                         } else {
408                                 $serverdata['network'] = Protocol::ZOT;
409                         }
410                 }
411
412                 if (!empty($data['site_name'])) {
413                         $serverdata['site_name'] = $data['site_name'];
414                 }
415
416                 if (!empty($data['channels_total'])) {
417                         $serverdata['registered-users'] = $data['channels_total'];
418                 }
419
420                 if (!empty($data['register_policy'])) {
421                         switch ($data['register_policy']) {
422                                 case 'REGISTER_OPEN':
423                                         $serverdata['register_policy'] = Register::OPEN;
424                                         break;
425
426                                 case 'REGISTER_APPROVE':
427                                         $serverdata['register_policy'] = Register::APPROVE;
428                                         break;
429
430                                 case 'REGISTER_CLOSED':
431                                 default:
432                                         $serverdata['register_policy'] = Register::CLOSED;
433                                         break;
434                         }
435                 }
436
437                 return $serverdata;
438         }
439
440         private static function detectNextcloud($url, $serverdata)
441         {
442                 $curlResult = Network::curl($url . '/status.php');
443
444                 if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) {
445                         return $serverdata;
446                 }
447
448                 $data = json_decode($curlResult->getBody(), true);
449                 if (empty($data)) {
450                         return $serverdata;
451                 }
452
453                 if (!empty($data['version'])) {
454                         $serverdata['platform'] = 'nextcloud';
455                         $serverdata['version'] = $data['version'];
456                         $serverdata['network'] = Protocol::ACTIVITYPUB;
457                 }
458
459                 return $serverdata;
460         }
461
462         private static function detectMastodonAlikes($url, $serverdata)
463         {
464                 $curlResult = Network::curl($url . '/api/v1/instance');
465
466                 if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) {
467                         return $serverdata;
468                 }
469
470                 $data = json_decode($curlResult->getBody(), true);
471                 if (empty($data)) {
472                         return $serverdata;
473                 }
474
475                 if (!empty($data['version'])) {
476                         $serverdata['platform'] = 'mastodon';
477                         $serverdata['version'] = defaults($data, 'version', '');
478                         $serverdata['network'] = Protocol::ACTIVITYPUB;
479                 }
480
481                 if (!empty($data['title'])) {
482                         $serverdata['site_name'] = $data['title'];
483                 }
484
485                 if (!empty($data['description'])) {
486                         $serverdata['info'] = trim($data['description']);
487                 }
488
489                 if (!empty($data['stats']['user_count'])) {
490                         $serverdata['registered-users'] = $data['stats']['user_count'];
491                 }
492
493                 if (!empty($serverdata['version']) && strstr($serverdata['version'], 'Pleroma')) {
494                         $serverdata['platform'] = 'pleroma';
495                         $serverdata['version'] = trim(str_replace('Pleroma', '', $serverdata['version'])); // 2.7.2 (compatible; Pleroma 1.0.0-1225-gf31ad554-develop)
496                 }
497
498                 return $serverdata;
499         }
500
501         private static function detectHubzilla($url, $serverdata)
502         {
503                 $curlResult = Network::curl($url . '/api/statusnet/config.json');
504                 if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) {
505                         return $serverdata;
506                 }
507
508                 $data = json_decode($curlResult->getBody(), true);
509                 if (empty($data)) {
510                         return $serverdata;
511                 }
512
513                 if (!empty($data['site']['name'])) {
514                         $serverdata['site_name'] = $data['site']['name'];
515                 }
516
517                 if (!empty($data['site']['platform'])) {
518                         $serverdata['platform'] = $data['site']['platform']['PLATFORM_NAME'];
519                         $serverdata['version'] = $data['site']['platform']['STD_VERSION'];
520                         $serverdata['network'] = Protocol::ZOT;
521                 }
522
523                 if (!empty($data['site']['hubzilla'])) {
524                         $serverdata['platform'] = $data['site']['hubzilla']['PLATFORM_NAME'];
525                         $serverdata['version'] = $data['site']['hubzilla']['RED_VERSION'];
526                         $serverdata['network'] = Protocol::ZOT;
527                 }
528
529                 if (!empty($data['site']['redmatrix'])) {
530                         if (!empty($data['site']['redmatrix']['PLATFORM_NAME'])) {
531                                 $serverdata['platform'] = $data['site']['redmatrix']['PLATFORM_NAME'];
532                         } elseif (!empty($data['site']['redmatrix']['RED_PLATFORM'])) {
533                                 $serverdata['platform'] = $data['site']['redmatrix']['RED_PLATFORM'];
534                         }
535
536                         $serverdata['version'] = $data['site']['redmatrix']['RED_VERSION'];
537                         $serverdata['network'] = Protocol::ZOT;
538                 }
539
540                 $private = false;
541                 $inviteonly = false;
542                 $closed = false;
543
544                 if (!empty($data['site']['closed'])) {
545                         $closed = self::toBoolean($data['site']['closed']);
546                 }
547
548                 if (!empty($data['site']['private'])) {
549                         $private = self::toBoolean($data['site']['private']);
550                 }
551
552                 if (!empty($data['site']['inviteonly'])) {
553                         $inviteonly = self::toBoolean($data['site']['inviteonly']);
554                 }
555
556                 if (!$closed && !$private and $inviteonly) {
557                         $register_policy = Register::APPROVE;
558                 } elseif (!$closed && !$private) {
559                         $register_policy = Register::OPEN;
560                 } else {
561                         $register_policy = Register::CLOSED;
562                 }
563
564                 return $serverdata;
565         }
566
567         private static function toBoolean($val)
568         {
569                 if (($val == 'true') || ($val == 1)) {
570                         return true;
571                 } elseif (($val == 'false') || ($val == 0)) {
572                         return false;
573                 }
574
575                 return $val;
576         }
577
578         private static function detectGNUSocial($url, $serverdata)
579         {
580                 $curlResult = Network::curl($url . '/api/statusnet/version.json');
581
582                 if ($curlResult->isSuccess() && ($curlResult->getBody() != '{"error":"not implemented"}') &&
583                         ($curlResult->getBody() != '') && (strlen($curlResult->getBody()) < 30)) {
584                         $serverdata['platform'] = 'StatusNet';
585                         // Remove junk that some GNU Social servers return
586                         $serverdata['version'] = str_replace(chr(239).chr(187).chr(191), '', $curlResult->getBody());
587                         $serverdata['version'] = trim($serverdata['version'], '"');
588                         $serverdata['network'] = Protocol::OSTATUS;
589                 }
590
591                 // Test for GNU Social
592                 $curlResult = Network::curl($url . '/api/gnusocial/version.json');
593
594                 if ($curlResult->isSuccess() && ($curlResult->getBody() != '{"error":"not implemented"}') &&
595                         ($curlResult->getBody() != '') && (strlen($curlResult->getBody()) < 30)) {
596                         $serverdata['platform'] = 'GNU Social';
597                         // Remove junk that some GNU Social servers return
598                         $serverdata['version'] = str_replace(chr(239) . chr(187) . chr(191), '', $curlResult->getBody());
599                         $serverdata['version'] = trim($serverdata['version'], '"');
600                         $serverdata['network'] = Protocol::OSTATUS;
601                 }
602
603                 return $serverdata;
604         }
605
606         private static function detectFriendica($url, $serverdata)
607         {
608                 $curlResult = Network::curl($url . '/friendica/json');
609                 if (!$curlResult->isSuccess()) {
610                         $curlResult = Network::curl($url . '/friendika/json');
611                 }
612
613                 if (!$curlResult->isSuccess()) {
614                         return $serverdata;
615                 }
616
617                 $data = json_decode($curlResult->getBody(), true);
618                 if (empty($data) || empty($data['version'])) {
619                         return $serverdata;
620                 }
621
622                 $serverdata['network'] = Protocol::DFRN;
623                 $serverdata['version'] = $data['version'];
624
625                 if (!empty($data['no_scrape_url'])) {
626                         $serverdata['noscrape'] = $data['no_scrape_url'];
627                 }
628
629                 if (!empty($data['site_name'])) {
630                         $serverdata['site_name'] = $data['site_name'];
631                 }
632
633                 if (!empty($data['info'])) {
634                         $serverdata['info'] = trim($data['info']);
635                 }
636
637                 $register_policy = defaults($data, 'register_policy', 'REGISTER_CLOSED');
638                 switch ($register_policy) {
639                         case 'REGISTER_OPEN':
640                                 $serverdata['register_policy'] = Register::OPEN;
641                                 break;
642
643                         case 'REGISTER_APPROVE':
644                                 $serverdata['register_policy'] = Register::APPROVE;
645                                 break;
646
647                         case 'REGISTER_CLOSED':
648                         case 'REGISTER_INVITATION':
649                                 $serverdata['register_policy'] = Register::CLOSED;
650                                 break;
651                         default:
652                                 Logger::info('Register policy is invalid', ['policy' => $register_policy, 'server' => $url]);
653                                 $serverdata['register_policy'] = Register::CLOSED;
654                                 break;
655                 }
656
657                 $serverdata['platform'] = defaults($data, 'platform', '');
658
659                 return $serverdata;
660         }
661
662         private static function analyseRootBody($curlResult, $serverdata)
663         {
664                 $doc = new DOMDocument();
665                 @$doc->loadHTML($curlResult->getBody());
666                 $xpath = new DOMXPath($doc);
667
668                 $title = trim(XML::getFirstNodeValue($xpath, '//head/title/text()'));
669                 if (!empty($title)) {
670                         $serverdata['site_name'] = $title;
671                 }
672
673                 $list = $xpath->query('//meta[@name]');
674
675                 foreach ($list as $node) {
676                         $attr = [];
677                         if ($node->attributes->length) {
678                                 foreach ($node->attributes as $attribute) {
679                                         $attribute->value = trim($attribute->value);
680                                         if (empty($attribute->value)) {
681                                                 continue;
682                                         }
683
684                                         $attr[$attribute->name] = $attribute->value;
685                                 }
686
687                                 if (empty($attr['name']) || empty($attr['content'])) {
688                                         continue;
689                                 }
690                         }
691 //print_r($attr);
692                         if ($attr['name'] == 'description') {
693                                 $serverdata['info'] = $attr['content'];
694                         }
695
696                         if ($attr['name'] == 'application-name') {
697                                 $serverdata['platform'] = $attr['content'];
698                                 if (in_array($attr['content'], ['Misskey', 'Write.as'])) {
699                                         $serverdata['network'] = Protocol::ACTIVITYPUB;
700                                 }
701                         }
702
703                         if ($attr['name'] == 'generator') {
704                                 $serverdata['platform'] = $attr['content'];
705
706                                 $version_part = explode(' ', $attr['content']);
707
708                                 if (count($version_part) == 2) {
709                                         if (in_array($version_part[0], ['WordPress'])) {
710                                                 $serverdata['platform'] = $version_part[0];
711                                                 $serverdata['version'] = $version_part[1];
712                                                 $serverdata['network'] = Protocol::ACTIVITYPUB;
713                                         }
714                                         if (in_array($version_part[0], ['Friendika', 'Friendica'])) {
715                                                 $serverdata['platform'] = $version_part[0];
716                                                 $serverdata['version'] = $version_part[1];
717                                                 $serverdata['network'] = Protocol::DFRN;
718                                         }
719                                 }
720                         }
721                 }
722
723                 $list = $xpath->query('//meta[@property]');
724
725                 foreach ($list as $node) {
726                         $attr = [];
727                         if ($node->attributes->length) {
728                                 foreach ($node->attributes as $attribute) {
729                                         $attribute->value = trim($attribute->value);
730                                         if (empty($attribute->value)) {
731                                                 continue;
732                                         }
733
734                                         $attr[$attribute->name] = $attribute->value;
735                                 }
736
737                                 if (empty($attr['property']) || empty($attr['content'])) {
738                                         continue;
739                                 }
740                         }
741 //print_r($attr);
742
743                         if ($attr['property'] == 'og:site_name') {
744                                 $serverdata['site_name'] = $attr['content'];
745                         }
746
747                         if ($attr['property'] == 'og:description') {
748                                 $serverdata['info'] = $attr['content'];
749                         }
750
751                         if ($attr['property'] == 'og:platform') {
752                                 $serverdata['platform'] = $attr['content'];
753
754                                 if (in_array($attr['content'], ['PeerTube'])) {
755                                         $serverdata['network'] = Protocol::ACTIVITYPUB;
756                                 }
757                         }
758
759                         if ($attr['property'] == 'generator') {
760                                 $serverdata['platform'] = $attr['content'];
761
762                                 if (in_array($attr['content'], ['hubzilla'])) {
763                                         // We later check which compatible protocol modules are loaded.
764                                         $serverdata['network'] = Protocol::ZOT;
765                                 }
766                         }
767                 }
768
769                 return $serverdata;
770         }
771
772         private static function analyseRootHeader($curlResult, $serverdata)
773         {
774                 if ($curlResult->getHeader('server') == 'Mastodon') {
775                         $serverdata['platform'] = 'mastodon';
776                         $serverdata['network'] = $network = Protocol::ACTIVITYPUB;
777                 } elseif ($curlResult->inHeader('x-diaspora-version')) {
778                         $serverdata['platform'] = 'diaspora';
779                         $serverdata['network'] = $network = Protocol::DIASPORA;
780                         $serverdata['version'] = $curlResult->getHeader('x-diaspora-version');
781
782                 } elseif ($curlResult->inHeader('x-friendica-version')) {
783                         $serverdata['platform'] = 'friendica';
784                         $serverdata['network'] = $network = Protocol::DFRN;
785                         $serverdata['version'] = $curlResult->getHeader('x-friendica-version');
786
787                 } else {
788 //print_r($curlResult->getHeaderArray());
789                 }
790                 return $serverdata;
791         }
792 }