<?php
-
/**
- * @file src/Model/GServer.php
- * This file includes the GServer class to handle with servers
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
*/
+
namespace Friendica\Model;
use DOMDocument;
use DOMXPath;
-use Friendica\Core\Config;
use Friendica\Core\Protocol;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
+use Friendica\DI;
use Friendica\Module\Register;
use Friendica\Network\CurlResult;
use Friendica\Util\Network;
/**
* 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, '/');
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', ['last_failure' => DateTimeFormat::utcNow()], ['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(),
+ 'last_failure' => DateTimeFormat::utcNow()]);
+ Logger::info('Set failed status for new server', ['url' => $url]);
}
/**
* 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 = [];
}
// When a nodeinfo is present, we don't need to dig further
- $xrd_timeout = Config::get('system', 'xrd_timeout');
+ $xrd_timeout = DI::config()->get('system', 'xrd_timeout');
$curlResult = Network::curl($url . '/.well-known/nodeinfo', false, ['timeout' => $xrd_timeout]);
if ($curlResult->isTimeout()) {
- DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]);
+ 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)) {
}
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;
}
}
// 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;
}
return;
}
+ // 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', 'relay-subscribe', 'relay-scope'], ['nurl' => Strings::normaliseLink($server_url)]);
if (!DBA::isResult($gserver)) {
return;
}
if (!empty($data['network'])) {
- $serverdata['platform'] = $data['network'];
+ $serverdata['platform'] = strtolower($data['network']);
- if ($serverdata['platform'] == 'Diaspora') {
+ if ($serverdata['platform'] == 'diaspora') {
$serverdata['network'] = Protocol::DIASPORA;
- } elseif ($serverdata['platform'] == 'Friendica') {
+ } elseif ($serverdata['platform'] == 'friendica') {
$serverdata['network'] = Protocol::DFRN;
} elseif ($serverdata['platform'] == 'hubzilla') {
$serverdata['network'] = Protocol::ZOT;
*/
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'])) {
if (is_array($nodeinfo['software'])) {
if (!empty($nodeinfo['software']['name'])) {
- $server['platform'] = $nodeinfo['software']['name'];
+ $server['platform'] = strtolower($nodeinfo['software']['name']);
}
if (!empty($nodeinfo['software']['version'])) {
if (is_array($nodeinfo['software'])) {
if (!empty($nodeinfo['software']['name'])) {
- $server['platform'] = $nodeinfo['software']['name'];
+ $server['platform'] = strtolower($nodeinfo['software']['name']);
}
if (!empty($nodeinfo['software']['version'])) {
}
if (!empty($data['url'])) {
- $serverdata['platform'] = $data['platform'];
+ $serverdata['platform'] = strtolower($data['platform']);
$serverdata['version'] = $data['version'];
}
*/
private static function validHostMeta(string $url)
{
- $xrd_timeout = Config::get('system', 'xrd_timeout');
+ $xrd_timeout = DI::config()->get('system', 'xrd_timeout');
$curlResult = Network::curl($url . '/.well-known/host-meta', false, ['timeout' => $xrd_timeout]);
if (!$curlResult->isSuccess()) {
return false;
}
- $xrd = XML::parseString($curlResult->getBody(), false);
+ $xrd = XML::parseString($curlResult->getBody());
if (!is_object($xrd)) {
return false;
}
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);
$serverdata['site_name'] = $data['title'];
}
+ if (!empty($data['title']) && empty($serverdata['platform']) && empty($serverdata['network'])) {
+ $serverdata['platform'] = 'mastodon';
+ $serverdata['network'] = Protocol::ACTIVITYPUB;
+ }
+
if (!empty($data['description'])) {
$serverdata['info'] = trim($data['description']);
}
}
if (!empty($serverdata['version']) && preg_match('/.*?\(compatible;\s(.*)\s(.*)\)/ism', $serverdata['version'], $matches)) {
- $serverdata['platform'] = $matches[1];
+ $serverdata['platform'] = strtolower($matches[1]);
$serverdata['version'] = $matches[2];
}
- if (!empty($serverdata['version']) && strstr($serverdata['version'], 'Pleroma')) {
+ if (!empty($serverdata['version']) && strstr(strtolower($serverdata['version']), 'pleroma')) {
+ $serverdata['platform'] = 'pleroma';
+ $serverdata['version'] = trim(str_ireplace('pleroma', '', $serverdata['version']));
+ }
+
+ if (!empty($serverdata['platform']) && strstr($serverdata['platform'], 'pleroma')) {
+ $serverdata['version'] = trim(str_ireplace('pleroma', '', $serverdata['platform']));
$serverdata['platform'] = 'pleroma';
- $serverdata['version'] = trim(str_replace('Pleroma', '', $serverdata['version']));
}
return $serverdata;
}
if (!empty($data['site']['platform'])) {
- $serverdata['platform'] = $data['site']['platform']['PLATFORM_NAME'];
+ $serverdata['platform'] = strtolower($data['site']['platform']['PLATFORM_NAME']);
$serverdata['version'] = $data['site']['platform']['STD_VERSION'];
$serverdata['network'] = Protocol::ZOT;
}
if (!empty($data['site']['hubzilla'])) {
- $serverdata['platform'] = $data['site']['hubzilla']['PLATFORM_NAME'];
+ $serverdata['platform'] = strtolower($data['site']['hubzilla']['PLATFORM_NAME']);
$serverdata['version'] = $data['site']['hubzilla']['RED_VERSION'];
$serverdata['network'] = Protocol::ZOT;
}
if (!empty($data['site']['redmatrix'])) {
if (!empty($data['site']['redmatrix']['PLATFORM_NAME'])) {
- $serverdata['platform'] = $data['site']['redmatrix']['PLATFORM_NAME'];
+ $serverdata['platform'] = strtolower($data['site']['redmatrix']['PLATFORM_NAME']);
} elseif (!empty($data['site']['redmatrix']['RED_PLATFORM'])) {
- $serverdata['platform'] = $data['site']['redmatrix']['RED_PLATFORM'];
+ $serverdata['platform'] = strtolower($data['site']['redmatrix']['RED_PLATFORM']);
}
$serverdata['version'] = $data['site']['redmatrix']['RED_VERSION'];
break;
}
- $serverdata['platform'] = $data['platform'] ?? '';
+ $serverdata['platform'] = strtolower($data['platform'] ?? '');
return $serverdata;
}
}
if ($attr['name'] == 'application-name') {
- $serverdata['platform'] = $attr['content'];
+ $serverdata['platform'] = strtolower($attr['content']);
if (in_array($attr['content'], ['Misskey', 'Write.as'])) {
$serverdata['network'] = Protocol::ACTIVITYPUB;
}
}
-
- if ($attr['name'] == 'generator') {
- $serverdata['platform'] = $attr['content'];
-
+ if (($attr['name'] == 'generator') && (empty($serverdata['platform']) || (substr(strtolower($attr['content']), 0, 9) == 'wordpress'))) {
+ $serverdata['platform'] = strtolower($attr['content']);
$version_part = explode(' ', $attr['content']);
if (count($version_part) == 2) {
if (in_array($version_part[0], ['WordPress'])) {
- $serverdata['platform'] = $version_part[0];
+ $serverdata['platform'] = strtolower($version_part[0]);
$serverdata['version'] = $version_part[1];
// We still do need a reliable test if some AP plugin is activated
}
}
if (in_array($version_part[0], ['Friendika', 'Friendica'])) {
- $serverdata['platform'] = $version_part[0];
+ $serverdata['platform'] = strtolower($version_part[0]);
$serverdata['version'] = $version_part[1];
$serverdata['network'] = Protocol::DFRN;
}
}
if ($attr['property'] == 'og:platform') {
- $serverdata['platform'] = $attr['content'];
+ $serverdata['platform'] = strtolower($attr['content']);
if (in_array($attr['content'], ['PeerTube'])) {
$serverdata['network'] = Protocol::ACTIVITYPUB;
}
if ($attr['property'] == 'generator') {
- $serverdata['platform'] = $attr['content'];
+ $serverdata['platform'] = strtolower($attr['content']);
if (in_array($attr['content'], ['hubzilla'])) {
// We later check which compatible protocol modules are loaded.
$no_of_queries = 5;
- $requery_days = intval(Config::get('system', 'poco_requery_days'));
+ $requery_days = intval(DI::config()->get('system', 'poco_requery_days'));
if ($requery_days == 0) {
$requery_days = 7;
*/
private static function discoverFederation()
{
- $last = Config::get('poco', 'last_federation_discovery');
+ $last = DI::config()->get('poco', 'last_federation_discovery');
if ($last) {
$next = $last + (24 * 60 * 60);
if (!empty($servers['pods'])) {
foreach ($servers['pods'] as $server) {
- Worker::add(PRIORITY_LOW, 'UpdateGServer', 'https://' . $server['host']);
+ // Using "only_nodeinfo" since servers that are listed on that page should always have it.
+ Worker::add(PRIORITY_LOW, 'UpdateGServer', 'https://' . $server['host'], true);
}
}
}
// Disvover Mastodon servers
- $accesstoken = Config::get('system', 'instances_social_key');
+ $accesstoken = DI::config()->get('system', 'instances_social_key');
if (!empty($accesstoken)) {
$api = 'https://instances.social/api/1.0/instances/list?count=0';
}
}
- Config::set('poco', 'last_federation_discovery', time());
+ DI::config()->set('poco', 'last_federation_discovery', time());
}
}