X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=include%2FProbe.php;h=5c2ba86ccde08e0ec1a56926bc97f139859d9c99;hb=61c1317f804d8519492af249f8b4b8985f6cec5e;hp=8b6039a6f661856108f120367a25dbdace0a9624;hpb=edfee710feee5dd41255b6b3aa104d4a7798f3a4;p=friendica.git diff --git a/include/Probe.php b/include/Probe.php index 8b6039a6f6..5c2ba86ccd 100644 --- a/include/Probe.php +++ b/include/Probe.php @@ -1,15 +1,7 @@ $link) { if (isset($link["@attributes"])) $attributes = $link["@attributes"]; @@ -100,7 +97,113 @@ class Probe { } /** - * @brief Fetch information about a given uri + * @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 @@ -114,7 +217,6 @@ class Probe { if ($cache) { $result = Cache::get("probe_url:".$network.":".$uri); if (!is_null($result)) { - $result = unserialize($result); return $result; } } @@ -132,12 +234,21 @@ class Probe { else $data["photo"] = App::get_baseurl().'/images/person-175.jpg'; - if (!isset($data["name"])) - $data["name"] = $data["url"]; + if (!isset($data["name"]) OR ($data["name"] == "")) { + if (isset($data["nick"])) + $data["name"] = $data["nick"]; - if (!isset($data["nick"])) + if ($data["name"] == "") + $data["name"] = $data["url"]; + } + + 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; @@ -145,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. @@ -170,7 +281,9 @@ class Probe { } /** - * @brief Detect information about a given uri + * @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 @@ -180,7 +293,7 @@ class Probe { */ 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); @@ -195,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); @@ -209,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"], "/")); @@ -223,7 +344,6 @@ class Probe { $nick = array_pop($path_parts); $addr = $nick."@".$host; } - $webfinger = false; /// @todo Do we need the prefix "acct:" or "acct://"? @@ -235,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); @@ -259,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; } @@ -278,7 +417,9 @@ class Probe { } /** - * @brief Do a webfinger request + * @brief Perform a webfinger request. + * + * For details see RFC 7033: * * @param string $url Address that should be probed * @@ -330,7 +471,10 @@ class Probe { } /** - * @brief Poll the noscrape page (Friendica specific) + * @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 @@ -415,7 +559,7 @@ class Probe { } /** - * @brief Fetch data from a DFRN profile page + * @brief Fetch data from a DFRN profile page and via "noscrape" * * @param string $profile Link to the profile page * @@ -425,6 +569,8 @@ class Probe { $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); @@ -447,6 +593,8 @@ 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; } @@ -516,8 +664,12 @@ class Probe { */ 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); @@ -526,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) { @@ -653,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"; @@ -671,8 +825,16 @@ class Probe { */ 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"] != "")) @@ -689,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); @@ -727,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"] != "") @@ -779,25 +947,24 @@ class Probe { * @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; @@ -917,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']);