X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=include%2FProbe.php;h=e0d0be2a8edfd322af99edda269e51ed36a0fd74;hb=6a8a36f12d00f35004fbb034972ca87dd1a3c4f5;hp=4dcbc7841156546c2688e74cc6eb4f1aedef7048;hpb=c625b6aba83fa3ad13d57ae46f8821dd3e747496;p=friendica.git diff --git a/include/Probe.php b/include/Probe.php index 4dcbc78411..e0d0be2a8e 100644 --- a/include/Probe.php +++ b/include/Probe.php @@ -1,6 +1,7 @@ $link) { if (isset($link["@attributes"])) $attributes = $link["@attributes"]; @@ -83,12 +96,127 @@ class Probe { return $xrd_data; } + /** + * @brief 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 + * is located, returns an OStatus subscription template (prefixed + * with the string 'stat:' to identify it as on OStatus template). + * If this isn't an email style address just return $webbie. + * Return an empty string if email-style addresses but webfinger fails, + * or if the resultant personal XRD doesn't contain a supported + * subscription/friend-request attribute. + * + * amended 7/9/2011 to return an hcard which could save potentially loading + * a lengthy content page to scrape dfrn attributes + * + * @param string $webbie Address that should be probed + * @param string $hcard Link to the hcard - is returned by reference + * + * @return string profile link + */ + + public static function webfinger_dfrn($webbie, &$hcard) { + + $profile_link = ''; + + $links = self::lrdd($webbie); + logger('webfinger_dfrn: '.$webbie.':'.print_r($links,true), LOGGER_DATA); + if (count($links)) { + foreach ($links as $link) { + if ($link['@attributes']['rel'] === NAMESPACE_DFRN) + $profile_link = $link['@attributes']['href']; + if (($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB) AND ($profile_link == "")) + $profile_link = 'stat:'.$link['@attributes']['template']; + if ($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard') + $hcard = $link['@attributes']['href']; + } + } + return $profile_link; + } + + /** + * @brief Check an URI for LRDD data + * + * this is a replacement for the "lrdd" function in include/network.php. + * 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 string $uri Address that should be probed + * + * @return array uri data + */ + public static function lrdd($uri) { + + $lrdd = self::xrd($uri); + + if (!$lrdd) { + $parts = @parse_url($uri); + if (!$parts) + return array(); + + $host = $parts["host"]; + + $path_parts = explode("/", trim($parts["path"], "/")); + + do { + $lrdd = self::xrd($host); + $host .= "/".array_shift($path_parts); + } while (!$lrdd AND (sizeof($path_parts) > 0)); + } + + if (!$lrdd) + return array(); + + foreach ($lrdd AS $key => $link) { + if ($webfinger) + continue; + + if (!in_array($key, array("lrdd", "lrdd-xml", "lrdd-json"))) + continue; + + $path = str_replace('{uri}', urlencode($uri), $link); + $webfinger = self::webfinger($path); + + if (!$webfinger AND (strstr($uri, "@"))) { + $path = str_replace('{uri}', urlencode("acct:".$uri), $link); + $webfinger = self::webfinger($path); + } + } + + if (!is_array($webfinger["links"])) + return false; + + $data = array(); + + foreach ($webfinger["links"] AS $link) + $data[] = array("@attributes" => $link); + + if (is_array($webfinger["aliases"])) + foreach ($webfinger["aliases"] AS $alias) + $data[] = array("@attributes" => + array("rel" => "alias", + "href" => $alias)); + + return $data; + } + + /** + * @brief 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 + * @param integer $uid User ID for the probe (only used for mails) + * @param boolean $cache Use cached values? + * + * @return array uri data + */ public static function uri($uri, $network = "", $uid = 0, $cache = true) { if ($cache) { $result = Cache::get("probe_url:".$network.":".$uri); if (!is_null($result)) { - $result = unserialize($result); return $result; } } @@ -104,14 +232,23 @@ class Probe { if ($data["photo"] != "") $data["baseurl"] = matching_url(normalise_link($data["baseurl"]), normalise_link($data["photo"])); else - $data["photo"] = App::get_baseurl().'/images/person-175.jpg'; + $data["photo"] = $a->get_baseurl().'/images/person-175.jpg'; + + if (!isset($data["name"]) OR ($data["name"] == "")) { + if (isset($data["nick"])) + $data["name"] = $data["nick"]; - if (!isset($data["name"])) - $data["name"] = $data["url"]; + if ($data["name"] == "") + $data["name"] = $data["url"]; + } - if (!isset($data["nick"])) + if (!isset($data["nick"]) OR ($data["nick"] == "")) { $data["nick"] = strtolower($data["name"]); + if (strpos($data['nick'], ' ')) + $data['nick'] = trim(substr($data['nick'], 0, strpos($data['nick'], ' '))); + } + if (!isset($data["network"])) $data["network"] = NETWORK_PHANTOM; @@ -119,7 +256,7 @@ class Probe { // Only store into the cache if the value seems to be valid if (!in_array($data['network'], array(NETWORK_PHANTOM, NETWORK_MAIL))) { - Cache::set("probe_url:".$network.":".$uri,serialize($data), CACHE_DAY); + Cache::set("probe_url:".$network.":".$uri, $data, CACHE_DAY); /// @todo temporary fix - we need a real contact update function that updates only changing fields /// The biggest problem is the avatar picture that could have a reduced image size. @@ -143,9 +280,20 @@ class Probe { return $data; } + /** + * @brief 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. + * + * @param string $uri Address that should be probed + * @param string $network Test for this specific network + * @param integer $uid User ID for the probe (only used for mails) + * + * @return array uri data + */ private function detect($uri, $network, $uid) { if (strstr($uri, '@')) { - // If the URI starts with "mailto:" then jum directly to the mail detection + // If the URI starts with "mailto:" then jump directly to the mail detection if (strpos($url,'mailto:') !== false) { $uri = str_replace('mailto:', '', $url); return self::mail($uri, $uid); @@ -160,7 +308,11 @@ class Probe { $host = substr($uri,strpos($uri, '@') + 1); $nick = substr($uri,0, strpos($uri, '@')); + if (strpos($uri, '@twitter.com')) + return array("network" => NETWORK_TWITTER); + $lrdd = self::xrd($host); + if (!$lrdd) return self::mail($uri, $uid); @@ -174,6 +326,10 @@ class Probe { // todo: Ports? $host = $parts["host"]; + + if ($host == 'twitter.com') + return array("network" => NETWORK_TWITTER); + $lrdd = self::xrd($host); $path_parts = explode("/", trim($parts["path"], "/")); @@ -188,7 +344,6 @@ class Probe { $nick = array_pop($path_parts); $addr = $nick."@".$host; } - $webfinger = false; /// @todo Do we need the prefix "acct:" or "acct://"? @@ -200,9 +355,28 @@ class Probe { if (!in_array($key, array("lrdd", "lrdd-xml", "lrdd-json"))) continue; + // Try webfinger with the address (user@domain.tld) $path = str_replace('{uri}', urlencode($addr), $link); - $webfinger = self::webfinger($path); + + // Mastodon needs to have it with "acct:" + if (!$webfinger) { + $path = str_replace('{uri}', urlencode("acct:".$addr), $link); + $webfinger = self::webfinger($path); + } + + // If webfinger wasn't successful then try it with the URL - possibly in the format https://... + if (!$webfinger AND ($uri != $addr)) { + $path = str_replace('{uri}', urlencode($uri), $link); + $webfinger = self::webfinger($path); + + // Since the detection with the address wasn't successful, we delete it. + if ($webfinger) { + $nick = ""; + $addr = ""; + } + } + } if (!$webfinger) return self::feed($uri); @@ -224,10 +398,10 @@ class Probe { else { // We overwrite the detected nick with our try if the previois routines hadn't detected it. // Additionally it is overwritten when the nickname doesn't make sense (contains spaces). - if (!isset($result["nick"]) OR ($result["nick"] == "") OR (strstr($result["nick"], " "))) + if ((!isset($result["nick"]) OR ($result["nick"] == "") OR (strstr($result["nick"], " "))) AND ($nick != "")) $result["nick"] = $nick; - if (!isset($result["addr"]) OR ($result["addr"] == "")) + if ((!isset($result["addr"]) OR ($result["addr"] == "")) AND ($addr != "")) $result["addr"] = $addr; } @@ -242,6 +416,15 @@ class Probe { return $result; } + /** + * @brief Perform a webfinger request. + * + * For details see RFC 7033: + * + * @param string $url Address that should be probed + * + * @return array webfinger data + */ private function webfinger($url) { $xrd_timeout = Config::get('system','xrd_timeout', 20); @@ -287,6 +470,17 @@ class Probe { return $webfinger; } + /** + * @brief 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. + * + * @param string $noscrape Link to the noscrape page + * @param array $data The already fetched data + * + * @return array noscrape data + */ private function poll_noscrape($noscrape, $data) { $content = fetch_url($noscrape); if (!$content) @@ -342,6 +536,13 @@ class Probe { return $data; } + /** + * @brief Check for valid DFRN data + * + * @param array $data DFRN data + * + * @return int Number of errors + */ public static function valid_dfrn($data) { $errors = 0; if(!isset($data['key'])) @@ -357,10 +558,19 @@ class Probe { return $errors; } + /** + * @brief Fetch data from a DFRN profile page and via "noscrape" + * + * @param string $profile Link to the profile page + * + * @return array profile data + */ public static function profile($profile) { $data = array(); + logger("Check profile ".$profile, LOGGER_DEBUG); + // Fetch data via noscrape - this is faster $noscrape = str_replace(array("/hcard/", "/profile/"), "/noscrape/", $profile); $data = self::poll_noscrape($noscrape, $data); @@ -383,9 +593,18 @@ class Probe { $prof_data["fn"] = $data["name"]; $prof_data["key"] = $data["pubkey"]; + logger("Result for profile ".$profile.": ".print_r($prof_data, true), LOGGER_DEBUG); + return $prof_data; } + /** + * @brief Check for DFRN contact + * + * @param array $webfinger Webfinger data + * + * @return array DFRN data + */ private function dfrn($webfinger) { $hcard = ""; @@ -434,10 +653,23 @@ class Probe { return $data; } + /** + * @brief Poll the hcard page (Diaspora and Friendica specific) + * + * @param string $hcard Link to the hcard page + * @param array $data The already fetched data + * @param boolean $dfrn Poll DFRN specific data + * + * @return array hcard data + */ private function poll_hcard($hcard, $data, $dfrn = false) { + $content = fetch_url($hcard); + if (!$content) + return false; + $doc = new DOMDocument(); - if (!@$doc->loadHTMLFile($hcard)) + if (!@$doc->loadHTML($content)) return false; $xpath = new DomXPath($doc); @@ -446,40 +678,39 @@ class Probe { if (!is_object($vcards)) return false; - if ($vcards->length == 0) - return false; + if ($vcards->length > 0) { + $vcard = $vcards->item(0); - $vcard = $vcards->item(0); + // We have to discard the guid from the hcard in favour of the guid from lrdd + // Reason: Hubzilla doesn't use the value "uid" in the hcard like Diaspora does. + $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' uid ')]", $vcard); // */ + if (($search->length > 0) AND ($data["guid"] == "")) + $data["guid"] = $search->item(0)->nodeValue; - // We have to discard the guid from the hcard in favour of the guid from lrdd - // Reason: Hubzilla doesn't use the value "uid" in the hcard like Diaspora does. - $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' uid ')]", $vcard); // */ - if (($search->length > 0) AND ($data["guid"] == "")) - $data["guid"] = $search->item(0)->nodeValue; + $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' nickname ')]", $vcard); // */ + if ($search->length > 0) + $data["nick"] = $search->item(0)->nodeValue; - $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' nickname ')]", $vcard); // */ - if ($search->length > 0) - $data["nick"] = $search->item(0)->nodeValue; + $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' fn ')]", $vcard); // */ + if ($search->length > 0) + $data["name"] = $search->item(0)->nodeValue; - $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' fn ')]", $vcard); // */ - if ($search->length > 0) - $data["name"] = $search->item(0)->nodeValue; + $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' searchable ')]", $vcard); // */ + if ($search->length > 0) + $data["searchable"] = $search->item(0)->nodeValue; - $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' searchable ')]", $vcard); // */ - if ($search->length > 0) - $data["searchable"] = $search->item(0)->nodeValue; + $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' key ')]", $vcard); // */ + if ($search->length > 0) { + $data["pubkey"] = $search->item(0)->nodeValue; + if (strstr($data["pubkey"], 'RSA ')) + $data["pubkey"] = rsatopem($data["pubkey"]); + } - $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' key ')]", $vcard); // */ - if ($search->length > 0) { - $data["pubkey"] = $search->item(0)->nodeValue; - if (strstr($data["pubkey"], 'RSA ')) - $data["pubkey"] = rsatopem($data["pubkey"]); + $search = $xpath->query("//*[@id='pod_location']", $vcard); // */ + if ($search->length > 0) + $data["baseurl"] = trim($search->item(0)->nodeValue, "/"); } - $search = $xpath->query("//*[@id='pod_location']", $vcard); // */ - if ($search->length > 0) - $data["baseurl"] = trim($search->item(0)->nodeValue, "/"); - $avatar = array(); $photos = $xpath->query("//*[contains(concat(' ', @class, ' '), ' photo ') or contains(concat(' ', @class, ' '), ' avatar ')]", $vcard); // */ foreach ($photos AS $photo) { @@ -519,6 +750,13 @@ class Probe { return $data; } + /** + * @brief Check for Diaspora contact + * + * @param array $webfinger Webfinger data + * + * @return array Diaspora data + */ private function diaspora($webfinger) { $hcard = ""; @@ -566,6 +804,9 @@ class Probe { isset($data["pubkey"]) AND ($hcard != "")) { $data["network"] = NETWORK_DIASPORA; + // The Diaspora handle must always be lowercase + $data["addr"] = strtolower($data["addr"]); + // We have to overwrite the detected value for "notify" since Hubzilla doesn't send it $data["notify"] = $data["baseurl"]."/receive/users/".$data["guid"]; $data["batch"] = $data["baseurl"]."/receive/public"; @@ -575,10 +816,25 @@ class Probe { return $data; } + /** + * @brief Check for OStatus contact + * + * @param array $webfinger Webfinger data + * + * @return array OStatus data + */ private function ostatus($webfinger) { - $pubkey = ""; $data = array(); + if (is_array($webfinger["aliases"])) + foreach($webfinger["aliases"] AS $alias) + if (strstr($alias, "@")) + $data["addr"] = str_replace('acct:', '', $alias); + + if (is_string($webfinger["subject"]) AND strstr($webfinger["subject"], "@")) + $data["addr"] = str_replace('acct:', '', $webfinger["subject"]); + + $pubkey = ""; foreach ($webfinger["links"] AS $link) { if (($link["rel"] == "http://webfinger.net/rel/profile-page") AND ($link["type"] == "text/html") AND ($link["href"] != "")) @@ -595,7 +851,7 @@ class Probe { $pubkey = substr($pubkey, strpos($pubkey, ',') + 1); else $pubkey = substr($pubkey, 5); - } else + } elseif (normalise_link($pubkey) == 'http://') $pubkey = fetch_url($pubkey); $key = explode(".", $pubkey); @@ -633,6 +889,12 @@ class Probe { if ($feed_data["header"]["author-id"] != "") $data["alias"] = $feed_data["header"]["author-id"]; + if ($feed_data["header"]["author-location"] != "") + $data["location"] = $feed_data["header"]["author-location"]; + + if ($feed_data["header"]["author-about"] != "") + $data["about"] = $feed_data["header"]["author-about"]; + // OStatus has serious issues when the the url doesn't fit (ssl vs. non ssl) // So we take the value that we just fetched, although the other one worked as well if ($feed_data["header"]["author-link"] != "") @@ -642,6 +904,13 @@ class Probe { return $data; } + /** + * @brief Fetch data from a pump.io profile page + * + * @param string $profile Link to the profile page + * + * @return array profile data + */ private function pumpio_profile_data($profile) { $doc = new DOMDocument(); @@ -670,26 +939,32 @@ class Probe { return $data; } + /** + * @brief Check for pump.io contact + * + * @param array $webfinger Webfinger data + * + * @return array pump.io data + */ private function pumpio($webfinger) { + $data = array(); foreach ($webfinger["links"] AS $link) { if (($link["rel"] == "http://webfinger.net/rel/profile-page") AND ($link["type"] == "text/html") AND ($link["href"] != "")) $data["url"] = $link["href"]; elseif (($link["rel"] == "activity-inbox") AND ($link["href"] != "")) - $data["activity-inbox"] = $link["href"]; + $data["notify"] = $link["href"]; elseif (($link["rel"] == "activity-outbox") AND ($link["href"] != "")) - $data["activity-outbox"] = $link["href"]; + $data["poll"] = $link["href"]; elseif (($link["rel"] == "dialback") AND ($link["href"] != "")) $data["dialback"] = $link["href"]; } - if (isset($data["activity-inbox"]) AND isset($data["activity-outbox"]) AND + if (isset($data["poll"]) AND isset($data["notify"]) AND isset($data["dialback"]) AND isset($data["url"])) { // by now we use these fields only for the network type detection // So we unset all data that isn't used at the moment - unset($data["activity-inbox"]); - unset($data["activity-outbox"]); unset($data["dialback"]); $data["network"] = NETWORK_PUMPIO; @@ -706,6 +981,13 @@ class Probe { return $data; } + /** + * @brief Check page for feed link + * + * @param string $url Page link + * + * @return string feed link + */ private function get_feed_link($url) { $doc = new DOMDocument(); @@ -736,6 +1018,14 @@ class Probe { return $feed_url; } + /** + * @brief Check for feed contact + * + * @param string $url Profile link + * @param boolean $probe Do a probe if the page contains a feed link + * + * @return array feed data + */ private function feed($url, $probe = true) { $feed = fetch_url($url); $feed_data = feed_import($feed, $dummy1, $dummy2, $dummy3, true); @@ -777,6 +1067,14 @@ class Probe { return $data; } + /** + * @brief Check for mail contact + * + * @param string $uri Profile link + * @param integer $uid User ID + * + * @return array mail data + */ private function mail($uri, $uid) { if (!validate_email($uri)) @@ -786,7 +1084,7 @@ class Probe { $r = q("SELECT * FROM `mailacct` WHERE `uid` = %d AND `server` != '' LIMIT 1", intval($uid)); - if(count($x) && count($r)) { + if(dbm::is_result($x) && dbm::is_result($r)) { $mailbox = construct_mailbox_name($r[0]); $password = ''; openssl_private_decrypt(hex2bin($r[0]['pass']), $password,$x[0]['prvkey']);