Nodeinfo: We now parse Nodeinfo 2.1 and 2.2 as well

This commit is contained in:
Michael 2024-06-23 14:38:47 +00:00
parent c380a28039
commit e319621f63
4 changed files with 113 additions and 66 deletions

View file

@ -1,6 +1,6 @@
-- ------------------------------------------ -- ------------------------------------------
-- Friendica 2024.06-rc (Yellow Archangel) -- Friendica 2024.06-rc (Yellow Archangel)
-- DB_UPDATE_VERSION 1567 -- DB_UPDATE_VERSION 1568
-- ------------------------------------------ -- ------------------------------------------
@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS `gserver` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID', `id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`url` varbinary(383) NOT NULL DEFAULT '' COMMENT '', `url` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`nurl` varbinary(383) NOT NULL DEFAULT '' COMMENT '', `nurl` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`version` varchar(255) NOT NULL DEFAULT '' COMMENT '', `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'The version of this server software.',
`site_name` varchar(255) NOT NULL DEFAULT '' COMMENT '', `site_name` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`info` text COMMENT '', `info` text COMMENT '',
`register_policy` tinyint NOT NULL DEFAULT 0 COMMENT '', `register_policy` tinyint NOT NULL DEFAULT 0 COMMENT '',
@ -28,7 +28,9 @@ CREATE TABLE IF NOT EXISTS `gserver` (
`noscrape` varbinary(383) NOT NULL DEFAULT '' COMMENT '', `noscrape` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`network` char(4) NOT NULL DEFAULT '' COMMENT '', `network` char(4) NOT NULL DEFAULT '' COMMENT '',
`protocol` tinyint unsigned COMMENT 'The protocol of the server', `protocol` tinyint unsigned COMMENT 'The protocol of the server',
`platform` varchar(255) NOT NULL DEFAULT '' COMMENT '', `platform` varchar(255) NOT NULL DEFAULT '' COMMENT 'The canonical name of this server software.',
`repository` varbinary(383) COMMENT 'The url of the source code repository of this server software.',
`homepage` varbinary(383) COMMENT 'The url of the homepage of this server software.',
`relay-subscribe` boolean NOT NULL DEFAULT '0' COMMENT 'Has the server subscribed to the relay system', `relay-subscribe` boolean NOT NULL DEFAULT '0' COMMENT 'Has the server subscribed to the relay system',
`relay-scope` varchar(10) NOT NULL DEFAULT '' COMMENT 'The scope of messages that the server wants to get', `relay-scope` varchar(10) NOT NULL DEFAULT '' COMMENT 'The scope of messages that the server wants to get',
`detection-method` tinyint unsigned COMMENT 'Method that had been used to detect that server', `detection-method` tinyint unsigned COMMENT 'Method that had been used to detect that server',

View file

@ -7,11 +7,11 @@ Fields
------ ------
| Field | Description | Type | Null | Key | Default | Extra | | Field | Description | Type | Null | Key | Default | Extra |
| --------------------- | -------------------------------------------------- | ---------------- | ---- | --- | ------------------- | -------------- | | --------------------- | -------------------------------------------------------------- | ---------------- | ---- | --- | ------------------- | -------------- |
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment | | id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
| url | | varbinary(383) | NO | | | | | url | | varbinary(383) | NO | | | |
| nurl | | varbinary(383) | NO | | | | | nurl | | varbinary(383) | NO | | | |
| version | | varchar(255) | NO | | | | | version | The version of this server software. | varchar(255) | NO | | | |
| site_name | | varchar(255) | NO | | | | | site_name | | varchar(255) | NO | | | |
| info | | text | YES | | NULL | | | info | | text | YES | | NULL | |
| register_policy | | tinyint | NO | | 0 | | | register_policy | | tinyint | NO | | 0 | |
@ -28,7 +28,9 @@ Fields
| noscrape | | varbinary(383) | NO | | | | | noscrape | | varbinary(383) | NO | | | |
| network | | char(4) | NO | | | | | network | | char(4) | NO | | | |
| protocol | The protocol of the server | tinyint unsigned | YES | | NULL | | | protocol | The protocol of the server | tinyint unsigned | YES | | NULL | |
| platform | | varchar(255) | NO | | | | | platform | The canonical name of this server software. | varchar(255) | NO | | | |
| repository | The url of the source code repository of this server software. | varbinary(383) | YES | | NULL | |
| homepage | The url of the homepage of this server software. | varbinary(383) | YES | | NULL | |
| relay-subscribe | Has the server subscribed to the relay system | boolean | NO | | 0 | | | relay-subscribe | Has the server subscribed to the relay system | boolean | NO | | 0 | |
| relay-scope | The scope of messages that the server wants to get | varchar(10) | NO | | | | | relay-scope | The scope of messages that the server wants to get | varchar(10) | NO | | | |
| detection-method | Method that had been used to detect that server | tinyint unsigned | YES | | NULL | | | detection-method | Method that had been used to detect that server | tinyint unsigned | YES | | NULL | |

View file

@ -87,9 +87,11 @@ class GServer
// Standardized endpoints // Standardized endpoints
const DETECT_STATISTICS_JSON = 100; const DETECT_STATISTICS_JSON = 100;
const DETECT_NODEINFO_1 = 101; const DETECT_NODEINFO_10 = 101; // Nodeinfo Version 1.0
const DETECT_NODEINFO_2 = 102; const DETECT_NODEINFO_20 = 102; // Nodeinfo Version 2.0
const DETECT_NODEINFO_210 = 103; const DETECT_NODEINFO2_10 = 103; // Nodeinfo2 Version 1.0
const DETECT_NODEINFO_21 = 104; // Nodeinfo Version 2.1
const DETECT_NODEINFO_22 = 105; // Nodeinfo Version 2.2
/** /**
* Check for the existence of a server and adds it in the background if not existant * Check for the existence of a server and adds it in the background if not existant
@ -612,7 +614,7 @@ class GServer
$in_webroot = empty(parse_url($url, PHP_URL_PATH)); $in_webroot = empty(parse_url($url, PHP_URL_PATH));
// When a nodeinfo is present, we don't need to dig further // When a nodeinfo is present, we don't need to dig further
$curlResult = DI::httpClient()->get($url . '/.well-known/x-nodeinfo2', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); $curlResult = DI::httpClient()->get($url . '/.well-known/nodeinfo', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if ($curlResult->isTimeout()) { if ($curlResult->isTimeout()) {
self::setFailureByUrl($url); self::setFailureByUrl($url);
return false; return false;
@ -621,10 +623,11 @@ class GServer
if (!empty($network) && !in_array($network, Protocol::NATIVE_SUPPORT)) { if (!empty($network) && !in_array($network, Protocol::NATIVE_SUPPORT)) {
$serverdata = ['detection-method' => self::DETECT_MANUAL, 'network' => $network, 'platform' => '', 'version' => '', 'site_name' => '', 'info' => '']; $serverdata = ['detection-method' => self::DETECT_MANUAL, 'network' => $network, 'platform' => '', 'version' => '', 'site_name' => '', 'info' => ''];
} else { } else {
$serverdata = self::parseNodeinfo210($curlResult); $serverdata = self::parseNodeinfo($url, $curlResult);
if (empty($serverdata)) {
$curlResult = DI::httpClient()->get($url . '/.well-known/nodeinfo', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if (empty($serverdata) || !in_array($serverdata['detection-method'], [self::DETECT_NODEINFO_20, self::DETECT_NODEINFO_21, self::DETECT_NODEINFO_22])) {
$serverdata = self::fetchNodeinfo($url, $curlResult); $curlResult = DI::httpClient()->get($url . '/.well-known/x-nodeinfo2', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
$serverdata = self::parseNodeinfo2($curlResult) ?: $serverdata;
} }
} }
@ -1049,7 +1052,9 @@ class GServer
} }
/** /**
* Detect server type by using the nodeinfo data * Parses Nodeinfo
*
* @see https://github.com/jhass/nodeinfo
* *
* @param string $url address of the server * @param string $url address of the server
* @param ICanHandleHttpResponses $httpResult * @param ICanHandleHttpResponses $httpResult
@ -1058,7 +1063,7 @@ class GServer
* *
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/ */
private static function fetchNodeinfo(string $url, ICanHandleHttpResponses $httpResult): array private static function parseNodeinfo(string $url, ICanHandleHttpResponses $httpResult): array
{ {
if (!$httpResult->isSuccess()) { if (!$httpResult->isSuccess()) {
return []; return [];
@ -1072,6 +1077,7 @@ class GServer
$nodeinfo1_url = ''; $nodeinfo1_url = '';
$nodeinfo2_url = ''; $nodeinfo2_url = '';
$detection_method = self::DETECT_MANUAL;
foreach ($nodeinfo['links'] as $link) { foreach ($nodeinfo['links'] as $link) {
if (!is_array($link) || empty($link['rel']) || empty($link['href'])) { if (!is_array($link) || empty($link['rel']) || empty($link['href'])) {
@ -1081,8 +1087,15 @@ class GServer
if ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/1.0') { if ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/1.0') {
$nodeinfo1_url = Network::addBasePath($link['href'], $httpResult->getUrl()); $nodeinfo1_url = Network::addBasePath($link['href'], $httpResult->getUrl());
} elseif ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.0') { } elseif (($detection_method < self::DETECT_NODEINFO_20) && ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.0')) {
$nodeinfo2_url = Network::addBasePath($link['href'], $httpResult->getUrl()); $nodeinfo2_url = Network::addBasePath($link['href'], $httpResult->getUrl());
$detection_method = self::DETECT_NODEINFO_20;
} elseif (($detection_method < self::DETECT_NODEINFO_21) && ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.1')) {
$nodeinfo2_url = Network::addBasePath($link['href'], $httpResult->getUrl());
$detection_method = self::DETECT_NODEINFO_21;
} elseif (($detection_method < self::DETECT_NODEINFO_22) && ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.2')) {
$nodeinfo2_url = Network::addBasePath($link['href'], $httpResult->getUrl());
$detection_method = self::DETECT_NODEINFO_22;
} }
} }
@ -1093,18 +1106,20 @@ class GServer
$server = []; $server = [];
if (!empty($nodeinfo2_url)) { if (!empty($nodeinfo2_url)) {
$server = self::parseNodeinfo2($nodeinfo2_url); $server = self::parseNodeinfo_2($nodeinfo2_url, $detection_method);
} }
if (empty($server) && !empty($nodeinfo1_url)) { if (empty($server) && !empty($nodeinfo1_url)) {
$server = self::parseNodeinfo1($nodeinfo1_url); $server = self::parseNodeinfo_1($nodeinfo1_url);
} }
return $server; return $server;
} }
/** /**
* Parses Nodeinfo 1 * Parses Nodeinfo with the version 1.0
*
* @see https://github.com/jhass/nodeinfo/tree/main/schemas/1.0
* *
* @param string $nodeinfo_url address of the nodeinfo path * @param string $nodeinfo_url address of the nodeinfo path
* *
@ -1112,7 +1127,7 @@ class GServer
* *
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/ */
private static function parseNodeinfo1(string $nodeinfo_url): array private static function parseNodeinfo_1(string $nodeinfo_url): array
{ {
$curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); $curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess()) { if (!$curlResult->isSuccess()) {
@ -1125,8 +1140,10 @@ class GServer
return []; return [];
} }
$server = ['detection-method' => self::DETECT_NODEINFO_1, $server = [
'register_policy' => Register::CLOSED]; 'detection-method' => self::DETECT_NODEINFO_10,
'register_policy' => Register::CLOSED
];
if (!empty($nodeinfo['openRegistrations'])) { if (!empty($nodeinfo['openRegistrations'])) {
$server['register_policy'] = Register::OPEN; $server['register_policy'] = Register::OPEN;
@ -1202,17 +1219,20 @@ class GServer
} }
/** /**
* Parses Nodeinfo 2 * Parses Nodeinfo with the versions 2.0, 2.1 and 2.2
* *
* @see https://git.feneas.org/jaywink/nodeinfo2 * @see https://github.com/jhass/nodeinfo/tree/main/schemas/2.0
* @see https://github.com/jhass/nodeinfo/tree/main/schemas/2.1
* @see https://github.com/jhass/nodeinfo/tree/main/schemas/2.2
* *
* @param string $nodeinfo_url address of the nodeinfo path * @param string $nodeinfo_url address of the nodeinfo path
* @param int $detection_method nodeinfo version
* *
* @return array Server data * @return array Server data
* *
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/ */
private static function parseNodeinfo2(string $nodeinfo_url): array private static function parseNodeinfo_2(string $nodeinfo_url, int $detection_method): array
{ {
$curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); $curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess()) { if (!$curlResult->isSuccess()) {
@ -1225,7 +1245,7 @@ class GServer
} }
$server = [ $server = [
'detection-method' => self::DETECT_NODEINFO_2, 'detection-method' => $detection_method,
'register_policy' => Register::CLOSED, 'register_policy' => Register::CLOSED,
'platform' => 'unknown', 'platform' => 'unknown',
]; ];
@ -1234,6 +1254,15 @@ class GServer
$server['register_policy'] = Register::OPEN; $server['register_policy'] = Register::OPEN;
} }
if (!empty($nodeinfo['instance'])) {
if (!empty($nodeinfo['instance']['name'])) {
$server['site_name'] = $nodeinfo['instance']['name'];
}
if (!empty($nodeinfo['instance']['description'])) {
$server['info'] = $nodeinfo['instance']['description'];
}
}
if (!empty($nodeinfo['software'])) { if (!empty($nodeinfo['software'])) {
if (isset($nodeinfo['software']['name'])) { if (isset($nodeinfo['software']['name'])) {
$server['platform'] = strtolower($nodeinfo['software']['name']); $server['platform'] = strtolower($nodeinfo['software']['name']);
@ -1249,6 +1278,13 @@ class GServer
if (($server['platform'] == 'mastodon') && substr($nodeinfo['software']['version'], -5) == '-qoto') { if (($server['platform'] == 'mastodon') && substr($nodeinfo['software']['version'], -5) == '-qoto') {
$server['platform'] = 'qoto'; $server['platform'] = 'qoto';
} }
if (isset($nodeinfo['software']['repository'])) {
$server['repository'] = strtolower($nodeinfo['software']['repository']);
}
if (isset($nodeinfo['software']['homepage'])) {
$server['homepage'] = strtolower($nodeinfo['software']['homepage']);
}
} }
} }
@ -1260,6 +1296,9 @@ class GServer
if (!empty($nodeinfo['metadata']['nodeName'])) { if (!empty($nodeinfo['metadata']['nodeName'])) {
$server['site_name'] = $nodeinfo['metadata']['nodeName']; $server['site_name'] = $nodeinfo['metadata']['nodeName'];
} }
if (!empty($nodeinfo['metadata']['nodeDescription'])) {
$server['info'] = $nodeinfo['metadata']['nodeDescription'];
}
if (!empty($nodeinfo['usage']['users']['total'])) { if (!empty($nodeinfo['usage']['users']['total'])) {
$server['registered-users'] = max($nodeinfo['usage']['users']['total'], 1); $server['registered-users'] = max($nodeinfo['usage']['users']['total'], 1);
@ -1320,9 +1359,9 @@ class GServer
} }
/** /**
* Parses NodeInfo2 protocol 1.0 * Parses NodeInfo2
* *
* @see https://github.com/jaywink/nodeinfo2/blob/master/PROTOCOL.md * @see https://github.com/jaywink/nodeinfo2
* *
* @param string $nodeinfo_url address of the nodeinfo path * @param string $nodeinfo_url address of the nodeinfo path
* *
@ -1330,7 +1369,7 @@ class GServer
* *
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/ */
private static function parseNodeinfo210(ICanHandleHttpResponses $httpResult): array private static function parseNodeinfo2(ICanHandleHttpResponses $httpResult): array
{ {
if (!$httpResult->isSuccess()) { if (!$httpResult->isSuccess()) {
return []; return [];
@ -1342,8 +1381,10 @@ class GServer
return []; return [];
} }
$server = ['detection-method' => self::DETECT_NODEINFO_210, $server = [
'register_policy' => Register::CLOSED]; 'detection-method' => self::DETECT_NODEINFO2_10,
'register_policy' => Register::CLOSED
];
if (!empty($nodeinfo['openRegistrations'])) { if (!empty($nodeinfo['openRegistrations'])) {
$server['register_policy'] = Register::OPEN; $server['register_policy'] = Register::OPEN;

View file

@ -56,7 +56,7 @@ use Friendica\Database\DBA;
// This file is required several times during the test in DbaDefinition which justifies this condition // This file is required several times during the test in DbaDefinition which justifies this condition
if (!defined('DB_UPDATE_VERSION')) { if (!defined('DB_UPDATE_VERSION')) {
define('DB_UPDATE_VERSION', 1567); define('DB_UPDATE_VERSION', 1568);
} }
return [ return [
@ -67,7 +67,7 @@ return [
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"], "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
"url" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""], "url" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
"nurl" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""], "nurl" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
"version" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""], "version" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "The version of this server software."],
"site_name" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""], "site_name" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
"info" => ["type" => "text", "comment" => ""], "info" => ["type" => "text", "comment" => ""],
"register_policy" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => ""], "register_policy" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => ""],
@ -84,7 +84,9 @@ return [
"noscrape" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""], "noscrape" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
"network" => ["type" => "char(4)", "not null" => "1", "default" => "", "comment" => ""], "network" => ["type" => "char(4)", "not null" => "1", "default" => "", "comment" => ""],
"protocol" => ["type" => "tinyint unsigned", "comment" => "The protocol of the server"], "protocol" => ["type" => "tinyint unsigned", "comment" => "The protocol of the server"],
"platform" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""], "platform" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "The canonical name of this server software."],
"repository" => ["type" => "varbinary(383)", "comment" => "The url of the source code repository of this server software."],
"homepage" => ["type" => "varbinary(383)", "comment" => "The url of the homepage of this server software."],
"relay-subscribe" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Has the server subscribed to the relay system"], "relay-subscribe" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Has the server subscribed to the relay system"],
"relay-scope" => ["type" => "varchar(10)", "not null" => "1", "default" => "", "comment" => "The scope of messages that the server wants to get"], "relay-scope" => ["type" => "varchar(10)", "not null" => "1", "default" => "", "comment" => "The scope of messages that the server wants to get"],
"detection-method" => ["type" => "tinyint unsigned", "comment" => "Method that had been used to detect that server"], "detection-method" => ["type" => "tinyint unsigned", "comment" => "Method that had been used to detect that server"],