<?php
/**
- * @file src/Network/Probe.php
+ * @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\Network;
-/**
- * @file src/Network/Probe.php
- * @brief Functions for probing URL
- */
+namespace Friendica\Network;
use DOMDocument;
use DomXPath;
-use Friendica\Core\Cache\Cache;
-use Friendica\Core\Config;
+use Friendica\Core\Cache\Duration;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
+use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Util\XML;
/**
- * @brief This class contain functions for probing URL
- *
+ * This class contain functions for probing URL
*/
class Probe
{
private static $istimeout;
/**
- * @brief Rearrange the array so that it always has the same order
+ * Remove stuff from an URI that doesn't belong there
+ *
+ * @param string $URI
+ * @return string Cleaned URI
+ */
+ public static function cleanURI(string $URI)
+ {
+ // At first remove leading and trailing junk
+ $URI = trim($URI, "@#?:/ \t\n\r\0\x0B");
+
+ $parts = parse_url($URI);
+
+ if (empty($parts['scheme'])) {
+ return $URI;
+ }
+
+ // Remove the URL fragment, since these shouldn't be part of any profile URL
+ unset($parts['fragment']);
+
+ $URI = Network::unparseURL($parts);
+
+ return $URI;
+ }
+
+ /**
+ * Rearrange the array so that it always has the same order
*
* @param array $data Unordered data
*
private static function rearrangeData($data)
{
$fields = ["name", "nick", "guid", "url", "addr", "alias", "photo", "account-type",
- "community", "keywords", "location", "about", "gender", "hide",
+ "community", "keywords", "location", "about", "hide",
"batch", "notify", "poll", "request", "confirm", "poco",
"following", "followers", "inbox", "outbox", "sharedinbox",
"priority", "network", "pubkey", "baseurl"];
}
/**
- * @brief Check if the hostname belongs to the own server
+ * Check if the hostname belongs to the own server
*
* @param string $host The hostname that is to be checked
*
}
/**
- * @brief Probes for webfinger path via "host-meta"
+ * Probes for webfinger path via "host-meta"
*
* We have to check if the servers in the future still will offer this.
* It seems as if it was dropped from the standard.
// Reset the static variable
self::$baseurl = '';
- $ssl_url = "https://".$host."/.well-known/host-meta";
- $url = "http://".$host."/.well-known/host-meta";
+ // Handles the case when the hostname contains the scheme
+ if (!parse_url($host, PHP_URL_SCHEME)) {
+ $ssl_url = "https://" . $host . "/.well-known/host-meta";
+ $url = "http://" . $host . "/.well-known/host-meta";
+ } else {
+ $ssl_url = $host . "/.well-known/host-meta";
+ $url = '';
+ }
- $xrd_timeout = Config::get('system', 'xrd_timeout', 20);
+ $xrd_timeout = DI::config()->get('system', 'xrd_timeout', 20);
- Logger::log("Probing for ".$host, Logger::DEBUG);
+ Logger::info('Probing', ['host' => $host, 'ssl_url' => $ssl_url, 'url' => $url, 'callstack' => System::callstack(20)]);
$xrd = null;
$curlResult = Network::curl($ssl_url, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
$ssl_connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0);
if ($curlResult->isSuccess()) {
$xml = $curlResult->getBody();
- $xrd = XML::parseString($xml, false);
- $host_url = 'https://'.$host;
+ $xrd = XML::parseString($xml, true);
+ if (!empty($url)) {
+ $host_url = 'https://' . $host;
+ } else {
+ $host_url = $host;
+ }
} elseif ($curlResult->isTimeout()) {
Logger::info('Probing timeout', ['url' => $ssl_url], Logger::DEBUG);
self::$istimeout = true;
return false;
}
- if (!is_object($xrd)) {
+ if (!is_object($xrd) && !empty($url)) {
$curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
$connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0);
if ($curlResult->isTimeout()) {
}
$xml = $curlResult->getBody();
- $xrd = XML::parseString($xml, false);
+ $xrd = XML::parseString($xml, true);
$host_url = 'http://'.$host;
}
if (!is_object($xrd)) {
}
/**
- * @brief Perform Webfinger lookup and return DFRN data
+ * Perform Webfinger lookup and return DFRN data
*
* Given an email style address, perform webfinger lookup and
* return the resulting DFRN profile URL, or if no DFRN profile URL
}
/**
- * @brief Check an URI for LRDD data
+ * Get the link for the remote follow page for a given profile link
*
- * this is a replacement for the "lrdd" function.
- * It isn't used in this class and has some redundancies in the code.
- * When time comes we can check the existing calls for "lrdd" if we can rework them.
+ * @param sting $profile
+ * @return string Remote follow page link
+ */
+ public static function getRemoteFollowLink(string $profile)
+ {
+ $follow_link = '';
+
+ $links = self::lrdd($profile);
+
+ if (!empty($links) && is_array($links)) {
+ foreach ($links as $link) {
+ if ($link['@attributes']['rel'] === ActivityNamespace::OSTATUSSUB) {
+ $follow_link = $link['@attributes']['template'];
+ }
+ }
+ }
+ return $follow_link;
+ }
+
+ /**
+ * Check an URI for LRDD data
*
* @param string $uri Address that should be probed
*
return [];
}
- $host = $parts["host"];
+ $host = $parts['scheme'] . '://' . $parts["host"];
if (!empty($parts["port"])) {
$host .= ':'.$parts["port"];
}
}
/**
- * @brief Fetch information (protocol endpoints and user information) about a given uri
+ * Fetch information (protocol endpoints and user information) about a given uri
*
* @param string $uri Address that should be probed
* @param string $network Test for this specific network
$data['network'] = Protocol::PHANTOM;
}
+ // Ensure that local connections always are DFRN
+ if (($network == '') && ($data['network'] != Protocol::PHANTOM) && (self::ownHost($data['baseurl'] ?? '') || self::ownHost($data['url']))) {
+ $data['network'] = Protocol::DFRN;
+ }
+
if (!isset($data['hide']) && in_array($data['network'], Protocol::FEDERATED)) {
$data['hide'] = self::getHideStatus($data['url']);
}
// Only store into the cache if the value seems to be valid
if (!in_array($data['network'], [Protocol::PHANTOM, Protocol::MAIL])) {
- DI::cache()->set('Probe::uri:' . $network . ':' . $uri, $data, Cache::DAY);
+ DI::cache()->set('Probe::uri:' . $network . ':' . $uri, $data, Duration::DAY);
}
return $data;
}
/**
- * @brief Checks if a profile url should be OStatus but only provides partial information
+ * Checks if a profile url should be OStatus but only provides partial information
*
* @param array $webfinger Webfinger data
* @param string $lrdd Path template for webfinger request
}
/**
- * @brief Fetch information (protocol endpoints and user information) about a given uri
+ * Fetch information (protocol endpoints and user information) about a given uri
*
* This function is only called by the "uri" function that adds caching and rearranging of data.
*
if (!empty($profile['description'])) {
$data['about'] = $profile['description'];
}
- if (!empty($profile['gender'])) {
- $data['gender'] = $profile['gender'];
- }
if (!empty($profile['keywords'])) {
$keywords = implode(', ', $profile['keywords']);
if (!empty($keywords)) {
if (!empty($profile['country'])) {
$loc['country-name'] = $profile['country'];
}
- if (!empty($profile['hometown'])) {
- $loc['locality'] = $profile['hometown'];
- }
$location = Profile::formatLocation($loc);
if (!empty($location)) {
$data['location'] = $location;
}
/**
- * @brief Perform a webfinger request.
+ * Perform a webfinger request.
*
* For details see RFC 7033: <https://tools.ietf.org/html/rfc7033>
*
*/
private static function webfinger($url, $type)
{
- $xrd_timeout = Config::get('system', 'xrd_timeout', 20);
+ $xrd_timeout = DI::config()->get('system', 'xrd_timeout', 20);
$curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout, 'accept_content' => $type]);
if ($curlResult->isTimeout()) {
}
// If it is not JSON, maybe it is XML
- $xrd = XML::parseString($data, false);
+ $xrd = XML::parseString($data, true);
if (!is_object($xrd)) {
Logger::log("No webfinger data retrievable for ".$url, Logger::DEBUG);
return false;
}
/**
- * @brief Poll the Friendica specific noscrape page.
+ * Poll the Friendica specific noscrape page.
*
* "noscrape" is a faster alternative to fetch the data from the hcard.
* This functionality was originally created for the directory.
$data["about"] = $json["about"];
}
- if (!empty($json["gender"])) {
- $data["gender"] = $json["gender"];
- }
-
if (!empty($json["key"])) {
$data["pubkey"] = $json["key"];
}
}
/**
- * @brief Check for valid DFRN data
+ * Check for valid DFRN data
*
* @param array $data DFRN data
*
}
/**
- * @brief Fetch data from a DFRN profile page and via "noscrape"
+ * Fetch data from a DFRN profile page and via "noscrape"
*
* @param string $profile_link Link to the profile page
*
}
/**
- * @brief Check for DFRN contact
+ * Check for DFRN contact
*
* @param array $webfinger Webfinger data
*
}
/**
- * @brief Poll the hcard page (Diaspora and Friendica specific)
+ * Poll the hcard page (Diaspora and Friendica specific)
*
* @param string $hcard_url Link to the hcard page
* @param array $data The already fetched data
}
/**
- * @brief Check for Diaspora contact
+ * Check for Diaspora contact
*
* @param array $webfinger Webfinger data
*
}
/**
- * @brief Check for OStatus contact
+ * Check for OStatus contact
*
* @param array $webfinger Webfinger data
* @param bool $short Short detection mode
}
/**
- * @brief Fetch data from a pump.io profile page
+ * Fetch data from a pump.io profile page
*
* @param string $profile_link Link to the profile page
*
}
/**
- * @brief Check for pump.io contact
+ * Check for pump.io contact
*
* @param array $webfinger Webfinger data
* @param string $addr
}
/**
- * @brief Check for twitter contact
+ * Check for twitter contact
*
* @param string $uri
*
}
/**
- * @brief Check page for feed link
+ * Check page for feed link
*
* @param string $url Page link
*
}
/**
- * @brief Check for feed contact
+ * Check for feed contact
*
* @param string $url Profile link
* @param boolean $probe Do a probe if the page contains a feed link
}
/**
- * @brief Check for mail contact
+ * Check for mail contact
*
* @param string $uri Profile link
* @param integer $uid User ID
}
/**
- * @brief Mix two paths together to possibly fix missing parts
+ * Mix two paths together to possibly fix missing parts
*
* @param string $avatar Path to the avatar
* @param string $base Another path that is hopefully complete