]> git.mxchange.org Git - friendica.git/blob - src/Network/Probe.php
Merge pull request #3613 from annando/some-ostatus
[friendica.git] / src / Network / Probe.php
1 <?php
2
3 namespace Friendica\Network;
4
5 /**
6  * @file src/Network/Probe.php
7  * @brief Functions for probing URL
8  *
9  */
10
11 use Friendica\App;
12 use Friendica\Core\Config;
13
14 use dbm;
15 use Cache;
16 use xml;
17
18 use DomXPath;
19 use DOMDocument;
20
21 require_once 'include/feed.php';
22 require_once 'include/email.php';
23 require_once 'include/network.php';
24
25 /**
26  * @brief This class contain functions for probing URL
27  *
28  */
29 class Probe {
30
31         private static $baseurl;
32
33         /**
34          * @brief Rearrange the array so that it always has the same order
35          *
36          * @param array $data Unordered data
37          *
38          * @return array Ordered data
39          */
40         private static function rearrangeData($data) {
41                 $fields = array("name", "nick", "guid", "url", "addr", "alias",
42                                 "photo", "community", "keywords", "location", "about",
43                                 "batch", "notify", "poll", "request", "confirm", "poco",
44                                 "priority", "network", "pubkey", "baseurl");
45
46                 $newdata = array();
47                 foreach ($fields as $field) {
48                         if (isset($data[$field])) {
49                                 $newdata[$field] = $data[$field];
50                         } else {
51                                 $newdata[$field] = "";
52                         }
53                 }
54
55                 // We don't use the "priority" field anymore and replace it with a dummy.
56                 $newdata["priority"] = 0;
57
58                 return $newdata;
59         }
60
61         /**
62          * @brief Check if the hostname belongs to the own server
63          *
64          * @param string $host The hostname that is to be checked
65          *
66          * @return bool Does the testes hostname belongs to the own server?
67          */
68         private static function ownHost($host) {
69                 $own_host = get_app()->get_hostname();
70
71                 $parts = parse_url($host);
72
73                 if (!isset($parts['scheme'])) {
74                         $parts = parse_url('http://'.$host);
75                 }
76
77                 if (!isset($parts['host'])) {
78                         return false;
79                 }
80                 return $parts['host'] == $own_host;
81         }
82
83         /**
84          * @brief Probes for XRD data
85          *
86          * @param string $host The host part of an url
87          *
88          * @return array
89          *      'lrdd' => Link to LRDD endpoint
90          *      'lrdd-xml' => Link to LRDD endpoint in XML format
91          *      'lrdd-json' => Link to LRDD endpoint in JSON format
92          */
93         private static function xrd($host) {
94
95                 // Reset the static variable
96                 self::$baseurl = '';
97
98                 $ssl_url = "https://".$host."/.well-known/host-meta";
99                 $url = "http://".$host."/.well-known/host-meta";
100
101                 $xrd_timeout = Config::get('system', 'xrd_timeout', 20);
102                 $redirects = 0;
103
104                 logger("Probing for ".$host, LOGGER_DEBUG);
105
106                 $ret = z_fetch_url($ssl_url, false, $redirects, array('timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml'));
107                 if (($ret['errno'] == CURLE_OPERATION_TIMEDOUT) && !self::ownHost($ssl_url)) {
108                         logger("Probing timeout for ".$ssl_url, LOGGER_DEBUG);
109                         return false;
110                 }
111                 $xml = $ret['body'];
112
113                 $xrd = parse_xml_string($xml, false);
114
115                 if (!is_object($xrd)) {
116                         $ret = z_fetch_url($url, false, $redirects, array('timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml'));
117                         if ($ret['errno'] == CURLE_OPERATION_TIMEDOUT) {
118                                 logger("Probing timeout for ".$url, LOGGER_DEBUG);
119                                 return false;
120                         }
121                         $xml = $ret['body'];
122                         $xrd = parse_xml_string($xml, false);
123                 }
124                 if (!is_object($xrd)) {
125                         logger("No xrd object found for ".$host, LOGGER_DEBUG);
126                         return array();
127                 }
128
129                 $links = xml::element_to_array($xrd);
130                 if (!isset($links["xrd"]["link"])) {
131                         logger("No xrd data found for ".$host, LOGGER_DEBUG);
132                         return array();
133                 }
134
135                 $xrd_data = array();
136
137                 foreach ($links["xrd"]["link"] as $value => $link) {
138                         if (isset($link["@attributes"])) {
139                                 $attributes = $link["@attributes"];
140                         } elseif ($value == "@attributes") {
141                                 $attributes = $link;
142                         } else {
143                                 continue;
144                         }
145
146                         if (($attributes["rel"] == "lrdd")
147                                 && ($attributes["type"] == "application/xrd+xml")
148                         ) {
149                                 $xrd_data["lrdd-xml"] = $attributes["template"];
150                         } elseif (($attributes["rel"] == "lrdd")
151                                 && ($attributes["type"] == "application/json")
152                         ) {
153                                 $xrd_data["lrdd-json"] = $attributes["template"];
154                         } elseif ($attributes["rel"] == "lrdd") {
155                                 $xrd_data["lrdd"] = $attributes["template"];
156                         }
157                 }
158
159                 self::$baseurl = "http://".$host;
160
161                 logger("Probing successful for ".$host, LOGGER_DEBUG);
162
163                 return $xrd_data;
164         }
165
166         /**
167          * @brief Perform Webfinger lookup and return DFRN data
168          *
169          * Given an email style address, perform webfinger lookup and
170          * return the resulting DFRN profile URL, or if no DFRN profile URL
171          * is located, returns an OStatus subscription template (prefixed
172          * with the string 'stat:' to identify it as on OStatus template).
173          * If this isn't an email style address just return $webbie.
174          * Return an empty string if email-style addresses but webfinger fails,
175          * or if the resultant personal XRD doesn't contain a supported
176          * subscription/friend-request attribute.
177          *
178          * amended 7/9/2011 to return an hcard which could save potentially loading
179          * a lengthy content page to scrape dfrn attributes
180          *
181          * @param string $webbie Address that should be probed
182          * @param string $hcard_url Link to the hcard - is returned by reference
183          *
184          * @return string profile link
185          */
186         public static function webfingerDfrn($webbie, &$hcard_url) {
187
188                 $profile_link = '';
189
190                 $links = self::lrdd($webbie);
191                 logger('webfingerDfrn: '.$webbie.':'.print_r($links, true), LOGGER_DATA);
192                 if (count($links)) {
193                         foreach ($links as $link) {
194                                 if ($link['@attributes']['rel'] === NAMESPACE_DFRN) {
195                                         $profile_link = $link['@attributes']['href'];
196                                 }
197                                 if (($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB) && ($profile_link == "")) {
198                                         $profile_link = 'stat:'.$link['@attributes']['template'];
199                                 }
200                                 if ($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard') {
201                                         $hcard_url = $link['@attributes']['href'];
202                                 }
203                         }
204                 }
205                 return $profile_link;
206         }
207
208         /**
209          * @brief Check an URI for LRDD data
210          *
211          * this is a replacement for the "lrdd" function in include/network.php.
212          * It isn't used in this class and has some redundancies in the code.
213          * When time comes we can check the existing calls for "lrdd" if we can rework them.
214          *
215          * @param string $uri Address that should be probed
216          *
217          * @return array uri data
218          */
219         public static function lrdd($uri) {
220
221                 $lrdd = self::xrd($uri);
222                 $webfinger = null;
223
224                 if (is_bool($lrdd)) {
225                         return array();
226                 }
227
228                 if (!$lrdd) {
229                         $parts = @parse_url($uri);
230                         if (!$parts) {
231                                 return array();
232                         }
233
234                         $host = $parts["host"];
235                         if (isset($parts["port"])) {
236                                 $host .= ':'.$parts["port"];
237                         }
238
239                         $path_parts = explode("/", trim($parts["path"], "/"));
240
241                         $nick = array_pop($path_parts);
242
243                         do {
244                                 $lrdd = self::xrd($host);
245                                 $host .= "/".array_shift($path_parts);
246                         } while (!$lrdd && (sizeof($path_parts) > 0));
247                 }
248
249                 if (!$lrdd) {
250                         logger("No lrdd data found for ".$uri, LOGGER_DEBUG);
251                         return array();
252                 }
253
254                 foreach ($lrdd as $key => $link) {
255                         if ($webfinger) {
256                                 continue;
257                         }
258
259                         if (!in_array($key, array("lrdd", "lrdd-xml", "lrdd-json"))) {
260                                 continue;
261                         }
262
263                         $path = str_replace('{uri}', urlencode($uri), $link);
264                         $webfinger = self::webfinger($path);
265
266                         if (!$webfinger && (strstr($uri, "@"))) {
267                                 $path = str_replace('{uri}', urlencode("acct:".$uri), $link);
268                                 $webfinger = self::webfinger($path);
269                         }
270
271                         // Special treatment for Mastodon
272                         // Problem is that Mastodon uses an URL format like http://domain.tld/@nick
273                         // But the webfinger for this format fails.
274                         if (!$webfinger && isset($nick)) {
275                                 // Mastodon uses a "@" as prefix for usernames in their url format
276                                 $nick = ltrim($nick, '@');
277
278                                 $addr = $nick."@".$host;
279
280                                 $path = str_replace('{uri}', urlencode("acct:".$addr), $link);
281                                 $webfinger = self::webfinger($path);
282                         }
283                 }
284
285                 if (!is_array($webfinger["links"])) {
286                         logger("No webfinger links found for ".$uri, LOGGER_DEBUG);
287                         return false;
288                 }
289
290                 $data = array();
291
292                 foreach ($webfinger["links"] as $link) {
293                         $data[] = array("@attributes" => $link);
294                 }
295
296                 if (is_array($webfinger["aliases"])) {
297                         foreach ($webfinger["aliases"] as $alias) {
298                                 $data[] = array("@attributes" =>
299                                                         array("rel" => "alias",
300                                                                 "href" => $alias));
301                         }
302                 }
303
304                 return $data;
305         }
306
307         /**
308          * @brief Fetch information (protocol endpoints and user information) about a given uri
309          *
310          * @param string $uri Address that should be probed
311          * @param string $network Test for this specific network
312          * @param integer $uid User ID for the probe (only used for mails)
313          * @param boolean $cache Use cached values?
314          *
315          * @return array uri data
316          */
317         public static function uri($uri, $network = "", $uid = 0, $cache = true) {
318
319                 if ($cache) {
320                         $result = Cache::get("probe_url:".$network.":".$uri);
321                         if (!is_null($result)) {
322                                 return $result;
323                         }
324                 }
325
326                 if ($uid == 0) {
327                         $uid = local_user();
328                 }
329
330                 $data = self::detect($uri, $network, $uid);
331
332                 if (!isset($data["url"])) {
333                         $data["url"] = $uri;
334                 }
335
336                 if ($data["photo"] != "") {
337                         $data["baseurl"] = matching_url(normalise_link($data["baseurl"]), normalise_link($data["photo"]));
338                 } else {
339                         $data["photo"] = App::get_baseurl().'/images/person-175.jpg';
340                 }
341
342                 if (!isset($data["name"]) || ($data["name"] == "")) {
343                         if (isset($data["nick"])) {
344                                 $data["name"] = $data["nick"];
345                         }
346
347                         if ($data["name"] == "") {
348                                 $data["name"] = $data["url"];
349                         }
350                 }
351
352                 if (!isset($data["nick"]) || ($data["nick"] == "")) {
353                         $data["nick"] = strtolower($data["name"]);
354
355                         if (strpos($data['nick'], ' ')) {
356                                 $data['nick'] = trim(substr($data['nick'], 0, strpos($data['nick'], ' ')));
357                         }
358                 }
359
360                 if (self::$baseurl != "") {
361                         $data["baseurl"] = self::$baseurl;
362                 }
363
364                 if (!isset($data["network"])) {
365                         $data["network"] = NETWORK_PHANTOM;
366                 }
367
368                 $data = self::rearrangeData($data);
369
370                 // Only store into the cache if the value seems to be valid
371                 if (!in_array($data['network'], array(NETWORK_PHANTOM, NETWORK_MAIL))) {
372                         Cache::set("probe_url:".$network.":".$uri, $data, CACHE_DAY);
373
374                         /// @todo temporary fix - we need a real contact update function that updates only changing fields
375                         /// The biggest problem is the avatar picture that could have a reduced image size.
376                         /// It should only be updated if the existing picture isn't existing anymore.
377                         /// We only update the contact when it is no probing for a specific network.
378                         if (($data['network'] != NETWORK_FEED)
379                                 && ($network == "")
380                                 && $data["name"]
381                                 && $data["nick"]
382                                 && $data["url"]
383                                 && $data["addr"]
384                                 && $data["poll"]
385                         ) {
386                                 q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `url` = '%s', `addr` = '%s',
387                                                 `notify` = '%s', `poll` = '%s', `alias` = '%s', `success_update` = '%s'
388                                         WHERE `nurl` = '%s' AND NOT `self` AND `uid` = 0",
389                                         dbesc($data["name"]),
390                                         dbesc($data["nick"]),
391                                         dbesc($data["url"]),
392                                         dbesc($data["addr"]),
393                                         dbesc($data["notify"]),
394                                         dbesc($data["poll"]),
395                                         dbesc($data["alias"]),
396                                         dbesc(datetime_convert()),
397                                         dbesc(normalise_link($data['url']))
398                                 );
399                         }
400                 }
401
402                 return $data;
403         }
404
405         /**
406          * @brief Switch the scheme of an url between http and https
407          *
408          * @param string $url URL
409          *
410          * @return string switched URL
411          */
412         private static function switchScheme($url) {
413                 $parts = parse_url($url);
414
415                 if (!isset($parts['scheme'])) {
416                         return $url;
417                 }
418
419                 if ($parts['scheme'] == 'http') {
420                         $url = str_replace('http://', 'https://', $url);
421                 } elseif ($parts['scheme'] == 'https') {
422                         $url = str_replace('https://', 'http://', $url);
423                 }
424
425                 return $url;
426         }
427
428         /**
429          * @brief Checks if a profile url should be OStatus but only provides partial information
430          *
431          * @param array $webfinger Webfinger data
432          * @param string $lrdd Path template for webfinger request
433          *
434          * @return array fixed webfinger data
435          */
436         private static function fixOstatus($webfinger, $lrdd) {
437                 if (empty($webfinger['links']) || empty($webfinger['subject'])) {
438                         return $webfinger;
439                 }
440
441                 $is_ostatus = false;
442                 $has_key = false;
443
444                 foreach ($webfinger['links'] as $link) {
445                         if ($link['rel'] == NAMESPACE_OSTATUSSUB) {
446                                 $is_ostatus = true;
447                         }
448                         if ($link['rel'] == 'magic-public-key') {
449                                 $has_key = true;
450                         }
451                 }
452
453                 if (!$is_ostatus || $has_key) {
454                         return $webfinger;
455                 }
456
457                 $url = self::switchScheme($webfinger['subject']);
458                 $path = str_replace('{uri}', urlencode($url), $lrdd);
459                 $webfinger2 = self::webfinger($path);
460
461                 // Is the new webfinger detectable as OStatus?
462                 if (self::ostatus($webfinger2, true)) {
463                         $webfinger = $webfinger2;
464                 }
465
466                 return $webfinger;
467         }
468
469         /**
470          * @brief Fetch information (protocol endpoints and user information) about a given uri
471          *
472          * This function is only called by the "uri" function that adds caching and rearranging of data.
473          *
474          * @param string $uri Address that should be probed
475          * @param string $network Test for this specific network
476          * @param integer $uid User ID for the probe (only used for mails)
477          *
478          * @return array uri data
479          */
480         private static function detect($uri, $network, $uid) {
481                 $parts = parse_url($uri);
482
483                 if (isset($parts["scheme"]) && isset($parts["host"]) && isset($parts["path"])) {
484                         $host = $parts["host"];
485                         if (isset($parts["port"])) {
486                                 $host .= ':'.$parts["port"];
487                         }
488
489                         if ($host == 'twitter.com') {
490                                 return array("network" => NETWORK_TWITTER);
491                         }
492                         $lrdd = self::xrd($host);
493
494                         if (is_bool($lrdd)) {
495                                 return array();
496                         }
497
498                         $path_parts = explode("/", trim($parts["path"], "/"));
499
500                         while (!$lrdd && (sizeof($path_parts) > 1)) {
501                                 $host .= "/".array_shift($path_parts);
502                                 $lrdd = self::xrd($host);
503                         }
504                         if (!$lrdd) {
505                                 logger('No XRD data was found for '.$uri, LOGGER_DEBUG);
506                                 return self::feed($uri);
507                         }
508                         $nick = array_pop($path_parts);
509
510                         // Mastodon uses a "@" as prefix for usernames in their url format
511                         $nick = ltrim($nick, '@');
512
513                         $addr = $nick."@".$host;
514
515                 } elseif (strstr($uri, '@')) {
516                         // If the URI starts with "mailto:" then jump directly to the mail detection
517                         if (strpos($uri, 'mailto:') !== false) {
518                                 $uri = str_replace('mailto:', '', $uri);
519                                 return self::mail($uri, $uid);
520                         }
521
522                         if ($network == NETWORK_MAIL) {
523                                 return self::mail($uri, $uid);
524                         }
525                         // Remove "acct:" from the URI
526                         $uri = str_replace('acct:', '', $uri);
527
528                         $host = substr($uri, strpos($uri, '@') + 1);
529                         $nick = substr($uri, 0, strpos($uri, '@'));
530
531                         if (strpos($uri, '@twitter.com')) {
532                                 return array("network" => NETWORK_TWITTER);
533                         }
534                         $lrdd = self::xrd($host);
535
536                         if (is_bool($lrdd)) {
537                                 return array();
538                         }
539
540                         if (!$lrdd) {
541                                 logger('No XRD data was found for '.$uri, LOGGER_DEBUG);
542                                 return self::mail($uri, $uid);
543                         }
544                         $addr = $uri;
545
546                 } else {
547                         logger("Uri ".$uri." was not detectable", LOGGER_DEBUG);
548                         return false;
549                 }
550
551                 $webfinger = false;
552
553                 /// @todo Do we need the prefix "acct:" or "acct://"?
554
555                 foreach ($lrdd as $key => $link) {
556                         if ($webfinger) {
557                                 continue;
558                         }
559                         if (!in_array($key, array("lrdd", "lrdd-xml", "lrdd-json"))) {
560                                 continue;
561                         }
562                         // At first try it with the given uri
563                         $path = str_replace('{uri}', urlencode($uri), $link);
564                         $webfinger = self::webfinger($path);
565
566                         // Fix possible problems with GNU Social probing to wrong scheme
567                         $webfinger = self::fixOstatus($webfinger, $link);
568
569                         // We cannot be sure that the detected address was correct, so we don't use the values
570                         if ($webfinger && ($uri != $addr)) {
571                                 $nick = "";
572                                 $addr = "";
573                         }
574
575                         // Try webfinger with the address (user@domain.tld)
576                         if (!$webfinger) {
577                                 $path = str_replace('{uri}', urlencode($addr), $link);
578                                 $webfinger = self::webfinger($path);
579                         }
580
581                         // Mastodon needs to have it with "acct:"
582                         if (!$webfinger) {
583                                 $path = str_replace('{uri}', urlencode("acct:".$addr), $link);
584                                 $webfinger = self::webfinger($path);
585                         }
586                 }
587                 if (!$webfinger) {
588                         return self::feed($uri);
589                 }
590
591                 $result = false;
592
593                 logger("Probing ".$uri, LOGGER_DEBUG);
594
595                 if (in_array($network, array("", NETWORK_DFRN))) {
596                         $result = self::dfrn($webfinger);
597                 }
598                 if ((!$result && ($network == "")) || ($network == NETWORK_DIASPORA)) {
599                         $result = self::diaspora($webfinger);
600                 }
601                 if ((!$result && ($network == "")) || ($network == NETWORK_OSTATUS)) {
602                         $result = self::ostatus($webfinger);
603                 }
604                 if ((!$result && ($network == "")) || ($network == NETWORK_PUMPIO)) {
605                         $result = self::pumpio($webfinger);
606                 }
607                 if ((!$result && ($network == "")) || ($network == NETWORK_FEED)) {
608                         $result = self::feed($uri);
609                 } else {
610                         // We overwrite the detected nick with our try if the previois routines hadn't detected it.
611                         // Additionally it is overwritten when the nickname doesn't make sense (contains spaces).
612                         if ((!isset($result["nick"]) || ($result["nick"] == "") || (strstr($result["nick"], " "))) && ($nick != "")) {
613                                 $result["nick"] = $nick;
614                         }
615
616                         if ((!isset($result["addr"]) || ($result["addr"] == "")) && ($addr != "")) {
617                                 $result["addr"] = $addr;
618                         }
619                 }
620
621                 logger($uri." is ".$result["network"], LOGGER_DEBUG);
622
623                 if (!isset($result["baseurl"]) || ($result["baseurl"] == "")) {
624                         $pos = strpos($result["url"], $host);
625                         if ($pos) {
626                                 $result["baseurl"] = substr($result["url"], 0, $pos).$host;
627                         }
628                 }
629
630                 return $result;
631         }
632
633         /**
634          * @brief Perform a webfinger request.
635          *
636          * For details see RFC 7033: <https://tools.ietf.org/html/rfc7033>
637          *
638          * @param string $url Address that should be probed
639          *
640          * @return array webfinger data
641          */
642         private static function webfinger($url) {
643
644                 $xrd_timeout = Config::get('system', 'xrd_timeout', 20);
645                 $redirects = 0;
646
647                 $ret = z_fetch_url($url, false, $redirects, array('timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml'));
648                 if ($ret['errno'] == CURLE_OPERATION_TIMEDOUT) {
649                         return false;
650                 }
651                 $data = $ret['body'];
652
653                 $xrd = parse_xml_string($data, false);
654
655                 if (!is_object($xrd)) {
656                         // If it is not XML, maybe it is JSON
657                         $webfinger = json_decode($data, true);
658
659                         if (!isset($webfinger["links"])) {
660                                 logger("No json webfinger links for ".$url, LOGGER_DEBUG);
661                                 return false;
662                         }
663
664                         return $webfinger;
665                 }
666
667                 $xrd_arr = xml::element_to_array($xrd);
668                 if (!isset($xrd_arr["xrd"]["link"])) {
669                         logger("No XML webfinger links for ".$url, LOGGER_DEBUG);
670                         return false;
671                 }
672
673                 $webfinger = array();
674
675                 if (isset($xrd_arr["xrd"]["subject"])) {
676                         $webfinger["subject"] = $xrd_arr["xrd"]["subject"];
677                 }
678
679                 if (isset($xrd_arr["xrd"]["alias"])) {
680                         $webfinger["aliases"] = $xrd_arr["xrd"]["alias"];
681                 }
682
683                 $webfinger["links"] = array();
684
685                 foreach ($xrd_arr["xrd"]["link"] as $value => $data) {
686                         if (isset($data["@attributes"])) {
687                                 $attributes = $data["@attributes"];
688                         } elseif ($value == "@attributes") {
689                                 $attributes = $data;
690                         } else {
691                                 continue;
692                         }
693
694                         $webfinger["links"][] = $attributes;
695                 }
696                 return $webfinger;
697         }
698
699         /**
700          * @brief Poll the Friendica specific noscrape page.
701          *
702          * "noscrape" is a faster alternative to fetch the data from the hcard.
703          * This functionality was originally created for the directory.
704          *
705          * @param string $noscrape_url Link to the noscrape page
706          * @param array $data The already fetched data
707          *
708          * @return array noscrape data
709          */
710         private static function pollNoscrape($noscrape_url, $data) {
711                 $ret = z_fetch_url($noscrape_url);
712                 if ($ret['errno'] == CURLE_OPERATION_TIMEDOUT) {
713                         return false;
714                 }
715                 $content = $ret['body'];
716                 if (!$content) {
717                         logger("Empty body for ".$noscrape_url, LOGGER_DEBUG);
718                         return false;
719                 }
720
721                 $json = json_decode($content, true);
722                 if (!is_array($json)) {
723                         logger("No json data for ".$noscrape_url, LOGGER_DEBUG);
724                         return false;
725                 }
726
727                 if (isset($json["fn"])) {
728                         $data["name"] = $json["fn"];
729                 }
730
731                 if (isset($json["addr"])) {
732                         $data["addr"] = $json["addr"];
733                 }
734
735                 if (isset($json["nick"])) {
736                         $data["nick"] = $json["nick"];
737                 }
738
739                 if (isset($json["comm"])) {
740                         $data["community"] = $json["comm"];
741                 }
742
743                 if (isset($json["tags"])) {
744                         $keywords = implode(" ", $json["tags"]);
745                         if ($keywords != "") {
746                                 $data["keywords"] = $keywords;
747                         }
748                 }
749
750                 $location = formatted_location($json);
751                 if ($location) {
752                         $data["location"] = $location;
753                 }
754
755                 if (isset($json["about"])) {
756                         $data["about"] = $json["about"];
757                 }
758
759                 if (isset($json["key"])) {
760                         $data["pubkey"] = $json["key"];
761                 }
762
763                 if (isset($json["photo"])) {
764                         $data["photo"] = $json["photo"];
765                 }
766
767                 if (isset($json["dfrn-request"])) {
768                         $data["request"] = $json["dfrn-request"];
769                 }
770
771                 if (isset($json["dfrn-confirm"])) {
772                         $data["confirm"] = $json["dfrn-confirm"];
773                 }
774
775                 if (isset($json["dfrn-notify"])) {
776                         $data["notify"] = $json["dfrn-notify"];
777                 }
778
779                 if (isset($json["dfrn-poll"])) {
780                         $data["poll"] = $json["dfrn-poll"];
781                 }
782
783                 return $data;
784         }
785
786         /**
787          * @brief Check for valid DFRN data
788          *
789          * @param array $data DFRN data
790          *
791          * @return int Number of errors
792          */
793         public static function validDfrn($data) {
794                 $errors = 0;
795                 if (!isset($data['key'])) {
796                         $errors ++;
797                 }
798                 if (!isset($data['dfrn-request'])) {
799                         $errors ++;
800                 }
801                 if (!isset($data['dfrn-confirm'])) {
802                         $errors ++;
803                 }
804                 if (!isset($data['dfrn-notify'])) {
805                         $errors ++;
806                 }
807                 if (!isset($data['dfrn-poll'])) {
808                         $errors ++;
809                 }
810                 return $errors;
811         }
812
813         /**
814          * @brief Fetch data from a DFRN profile page and via "noscrape"
815          *
816          * @param string $profile_link Link to the profile page
817          *
818          * @return array profile data
819          */
820         public static function profile($profile_link) {
821
822                 $data = array();
823
824                 logger("Check profile ".$profile_link, LOGGER_DEBUG);
825
826                 // Fetch data via noscrape - this is faster
827                 $noscrape_url = str_replace(array("/hcard/", "/profile/"), "/noscrape/", $profile_link);
828                 $data = self::pollNoscrape($noscrape_url, $data);
829
830                 if (!isset($data["notify"])
831                         || !isset($data["confirm"])
832                         || !isset($data["request"])
833                         || !isset($data["poll"])
834                         || !isset($data["poco"])
835                         || !isset($data["name"])
836                         || !isset($data["photo"])
837                 ) {
838                         $data = self::pollHcard($profile_link, $data, true);
839                 }
840
841                 $prof_data = array();
842                 $prof_data["addr"]         = $data["addr"];
843                 $prof_data["nick"]         = $data["nick"];
844                 $prof_data["dfrn-request"] = $data["request"];
845                 $prof_data["dfrn-confirm"] = $data["confirm"];
846                 $prof_data["dfrn-notify"]  = $data["notify"];
847                 $prof_data["dfrn-poll"]    = $data["poll"];
848                 $prof_data["dfrn-poco"]    = $data["poco"];
849                 $prof_data["photo"]        = $data["photo"];
850                 $prof_data["fn"]           = $data["name"];
851                 $prof_data["key"]          = $data["pubkey"];
852
853                 logger("Result for profile ".$profile_link.": ".print_r($prof_data, true), LOGGER_DEBUG);
854
855                 return $prof_data;
856         }
857
858         /**
859          * @brief Check for DFRN contact
860          *
861          * @param array $webfinger Webfinger data
862          *
863          * @return array DFRN data
864          */
865         private static function dfrn($webfinger) {
866
867                 $hcard_url = "";
868                 $data = array();
869                 foreach ($webfinger["links"] as $link) {
870                         if (($link["rel"] == NAMESPACE_DFRN) && ($link["href"] != "")) {
871                                 $data["network"] = NETWORK_DFRN;
872                         } elseif (($link["rel"] == NAMESPACE_FEED) && ($link["href"] != "")) {
873                                 $data["poll"] = $link["href"];
874                         } elseif (($link["rel"] == "http://webfinger.net/rel/profile-page") && ($link["type"] == "text/html") && ($link["href"] != "")) {
875                                 $data["url"] = $link["href"];
876                         } elseif (($link["rel"] == "http://microformats.org/profile/hcard") && ($link["href"] != "")) {
877                                 $hcard_url = $link["href"];
878                         } elseif (($link["rel"] == NAMESPACE_POCO) && ($link["href"] != "")) {
879                                 $data["poco"] = $link["href"];
880                         } elseif (($link["rel"] == "http://webfinger.net/rel/avatar") && ($link["href"] != "")) {
881                                 $data["photo"] = $link["href"];
882                         } elseif (($link["rel"] == "http://joindiaspora.com/seed_location") && ($link["href"] != "")) {
883                                 $data["baseurl"] = trim($link["href"], '/');
884                         } elseif (($link["rel"] == "http://joindiaspora.com/guid") && ($link["href"] != "")) {
885                                 $data["guid"] = $link["href"];
886                         } elseif (($link["rel"] == "diaspora-public-key") && ($link["href"] != "")) {
887                                 $data["pubkey"] = base64_decode($link["href"]);
888
889                                 //if (strstr($data["pubkey"], 'RSA ') || ($link["type"] == "RSA"))
890                                 if (strstr($data["pubkey"], 'RSA ')) {
891                                         $data["pubkey"] = rsatopem($data["pubkey"]);
892                                 }
893                         }
894                 }
895
896                 if (is_array($webfinger["aliases"])) {
897                         foreach ($webfinger["aliases"] as $alias) {
898                                 if (substr($alias, 0, 5) == 'acct:') {
899                                         $data["addr"] = substr($alias, 5);
900                                 }
901                         }
902                 }
903
904                 if (!isset($data["network"]) || ($hcard_url == "")) {
905                         return false;
906                 }
907
908                 // Fetch data via noscrape - this is faster
909                 $noscrape_url = str_replace("/hcard/", "/noscrape/", $hcard_url);
910                 $data = self::pollNoscrape($noscrape_url, $data);
911
912                 if (isset($data["notify"])
913                         && isset($data["confirm"])
914                         && isset($data["request"])
915                         && isset($data["poll"])
916                         && isset($data["name"])
917                         && isset($data["photo"])
918                 ) {
919                         return $data;
920                 }
921
922                 $data = self::pollHcard($hcard_url, $data, true);
923
924                 return $data;
925         }
926
927         /**
928          * @brief Poll the hcard page (Diaspora and Friendica specific)
929          *
930          * @param string $hcard_url Link to the hcard page
931          * @param array $data The already fetched data
932          * @param boolean $dfrn Poll DFRN specific data
933          *
934          * @return array hcard data
935          */
936         private static function pollHcard($hcard_url, $data, $dfrn = false) {
937                 $ret = z_fetch_url($hcard_url);
938                 if ($ret['errno'] == CURLE_OPERATION_TIMEDOUT) {
939                         return false;
940                 }
941                 $content = $ret['body'];
942                 if (!$content) {
943                         return false;
944                 }
945
946                 $doc = new DOMDocument();
947                 if (!@$doc->loadHTML($content)) {
948                         return false;
949                 }
950
951                 $xpath = new DomXPath($doc);
952
953                 $vcards = $xpath->query("//div[contains(concat(' ', @class, ' '), ' vcard ')]");
954                 if (!is_object($vcards)) {
955                         return false;
956                 }
957
958                 if ($vcards->length > 0) {
959                         $vcard = $vcards->item(0);
960
961                         // We have to discard the guid from the hcard in favour of the guid from lrdd
962                         // Reason: Hubzilla doesn't use the value "uid" in the hcard like Diaspora does.
963                         $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' uid ')]", $vcard); // */
964                         if (($search->length > 0) && ($data["guid"] == "")) {
965                                 $data["guid"] = $search->item(0)->nodeValue;
966                         }
967
968                         $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' nickname ')]", $vcard); // */
969                         if ($search->length > 0) {
970                                 $data["nick"] = $search->item(0)->nodeValue;
971                         }
972
973                         $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' fn ')]", $vcard); // */
974                         if ($search->length > 0) {
975                                 $data["name"] = $search->item(0)->nodeValue;
976                         }
977
978                         $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' searchable ')]", $vcard); // */
979                         if ($search->length > 0) {
980                                 $data["searchable"] = $search->item(0)->nodeValue;
981                         }
982
983                         $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' key ')]", $vcard); // */
984                         if ($search->length > 0) {
985                                 $data["pubkey"] = $search->item(0)->nodeValue;
986                                 if (strstr($data["pubkey"], 'RSA ')) {
987                                         $data["pubkey"] = rsatopem($data["pubkey"]);
988                                 }
989                         }
990
991                         $search = $xpath->query("//*[@id='pod_location']", $vcard); // */
992                         if ($search->length > 0) {
993                                 $data["baseurl"] = trim($search->item(0)->nodeValue, "/");
994                         }
995                 }
996
997                 $avatar = array();
998                 $photos = $xpath->query("//*[contains(concat(' ', @class, ' '), ' photo ') or contains(concat(' ', @class, ' '), ' avatar ')]", $vcard); // */
999                 foreach ($photos as $photo) {
1000                         $attr = array();
1001                         foreach ($photo->attributes as $attribute) {
1002                                 $attr[$attribute->name] = trim($attribute->value);
1003                         }
1004
1005                         if (isset($attr["src"]) && isset($attr["width"])) {
1006                                 $avatar[$attr["width"]] = $attr["src"];
1007                         }
1008
1009                         // We don't have a width. So we just take everything that we got.
1010                         // This is a Hubzilla workaround which doesn't send a width.
1011                         if ((sizeof($avatar) == 0) && isset($attr["src"])) {
1012                                 $avatar[] = $attr["src"];
1013                         }
1014                 }
1015
1016                 if (sizeof($avatar)) {
1017                         ksort($avatar);
1018                         $data["photo"] = self::fixAvatar(array_pop($avatar), $data["baseurl"]);
1019                 }
1020
1021                 if ($dfrn) {
1022                         // Poll DFRN specific data
1023                         $search = $xpath->query("//link[contains(concat(' ', @rel), ' dfrn-')]");
1024                         if ($search->length > 0) {
1025                                 foreach ($search as $link) {
1026                                         //$data["request"] = $search->item(0)->nodeValue;
1027                                         $attr = array();
1028                                         foreach ($link->attributes as $attribute) {
1029                                                 $attr[$attribute->name] = trim($attribute->value);
1030                                         }
1031
1032                                         $data[substr($attr["rel"], 5)] = $attr["href"];
1033                                 }
1034                         }
1035
1036                         // Older Friendica versions had used the "uid" field differently than newer versions
1037                         if ($data["nick"] == $data["guid"]) {
1038                                 unset($data["guid"]);
1039                         }
1040                 }
1041
1042
1043                 return $data;
1044         }
1045
1046         /**
1047          * @brief Check for Diaspora contact
1048          *
1049          * @param array $webfinger Webfinger data
1050          *
1051          * @return array Diaspora data
1052          */
1053         private static function diaspora($webfinger) {
1054
1055                 $hcard_url = "";
1056                 $data = array();
1057                 foreach ($webfinger["links"] as $link) {
1058                         if (($link["rel"] == "http://microformats.org/profile/hcard") && ($link["href"] != "")) {
1059                                 $hcard_url = $link["href"];
1060                         } elseif (($link["rel"] == "http://joindiaspora.com/seed_location") && ($link["href"] != "")) {
1061                                 $data["baseurl"] = trim($link["href"], '/');
1062                         } elseif (($link["rel"] == "http://joindiaspora.com/guid") && ($link["href"] != "")) {
1063                                 $data["guid"] = $link["href"];
1064                         } elseif (($link["rel"] == "http://webfinger.net/rel/profile-page") && ($link["type"] == "text/html") && ($link["href"] != "")) {
1065                                 $data["url"] = $link["href"];
1066                         } elseif (($link["rel"] == NAMESPACE_FEED) && ($link["href"] != "")) {
1067                                 $data["poll"] = $link["href"];
1068                         } elseif (($link["rel"] == NAMESPACE_POCO) && ($link["href"] != "")) {
1069                                 $data["poco"] = $link["href"];
1070                         } elseif (($link["rel"] == "salmon") && ($link["href"] != "")) {
1071                                 $data["notify"] = $link["href"];
1072                         } elseif (($link["rel"] == "diaspora-public-key") && ($link["href"] != "")) {
1073                                 $data["pubkey"] = base64_decode($link["href"]);
1074
1075                                 //if (strstr($data["pubkey"], 'RSA ') || ($link["type"] == "RSA"))
1076                                 if (strstr($data["pubkey"], 'RSA ')) {
1077                                         $data["pubkey"] = rsatopem($data["pubkey"]);
1078                                 }
1079                         }
1080                 }
1081
1082                 if (!isset($data["url"]) || ($hcard_url == "")) {
1083                         return false;
1084                 }
1085
1086                 if (is_array($webfinger["aliases"])) {
1087                         foreach ($webfinger["aliases"] as $alias) {
1088                                 if (normalise_link($alias) != normalise_link($data["url"]) && ! strstr($alias, "@")) {
1089                                         $data["alias"] = $alias;
1090                                 }
1091                         }
1092                 }
1093
1094                 // Fetch further information from the hcard
1095                 $data = self::pollHcard($hcard_url, $data);
1096
1097                 if (!$data) {
1098                         return false;
1099                 }
1100
1101                 if (isset($data["url"])
1102                         && isset($data["guid"])
1103                         && isset($data["baseurl"])
1104                         && isset($data["pubkey"])
1105                         && ($hcard_url != "")
1106                 ) {
1107                         $data["network"] = NETWORK_DIASPORA;
1108
1109                         // The Diaspora handle must always be lowercase
1110                         $data["addr"] = strtolower($data["addr"]);
1111
1112                         // We have to overwrite the detected value for "notify" since Hubzilla doesn't send it
1113                         $data["notify"] = $data["baseurl"] . "/receive/users/" . $data["guid"];
1114                         $data["batch"]  = $data["baseurl"] . "/receive/public";
1115                 } else {
1116                         return false;
1117                 }
1118
1119                 return $data;
1120         }
1121
1122         /**
1123          * @brief Check for OStatus contact
1124          *
1125          * @param array $webfinger Webfinger data
1126          * @param bool $short Short detection mode
1127          *
1128          * @return array|bool OStatus data or "false" on error or "true" on short mode
1129          */
1130         private static function ostatus($webfinger, $short = false) {
1131                 $data = array();
1132                 if (is_array($webfinger["aliases"])) {
1133                         foreach ($webfinger["aliases"] as $alias) {
1134                                 if (strstr($alias, "@")) {
1135                                         $data["addr"] = str_replace('acct:', '', $alias);
1136                                 }
1137                         }
1138                 }
1139
1140                 if (is_string($webfinger["subject"]) && strstr($webfinger["subject"], "@")) {
1141                         $data["addr"] = str_replace('acct:', '', $webfinger["subject"]);
1142                 }
1143                 $pubkey = "";
1144                 foreach ($webfinger["links"] as $link) {
1145                         if (($link["rel"] == "http://webfinger.net/rel/profile-page")
1146                                 && ($link["type"] == "text/html")
1147                                 && ($link["href"] != "")
1148                         ) {
1149                                 $data["url"] = $link["href"];
1150                         } elseif (($link["rel"] == "salmon") && ($link["href"] != "")) {
1151                                 $data["notify"] = $link["href"];
1152                         } elseif (($link["rel"] == NAMESPACE_FEED) && ($link["href"] != "")) {
1153                                 $data["poll"] = $link["href"];
1154                         } elseif (($link["rel"] == "magic-public-key") && ($link["href"] != "")) {
1155                                 $pubkey = $link["href"];
1156
1157                                 if (substr($pubkey, 0, 5) === 'data:') {
1158                                         if (strstr($pubkey, ',')) {
1159                                                 $pubkey = substr($pubkey, strpos($pubkey, ',') + 1);
1160                                         } else {
1161                                                 $pubkey = substr($pubkey, 5);
1162                                         }
1163                                 } elseif (normalise_link($pubkey) == 'http://') {
1164                                         $ret = z_fetch_url($pubkey);
1165                                         if ($ret['errno'] == CURLE_OPERATION_TIMEDOUT) {
1166                                                 return false;
1167                                         }
1168                                         $pubkey = $ret['body'];
1169                                 }
1170
1171                                 $key = explode(".", $pubkey);
1172
1173                                 if (sizeof($key) >= 3) {
1174                                         $m = base64url_decode($key[1]);
1175                                         $e = base64url_decode($key[2]);
1176                                         $data["pubkey"] = metopem($m, $e);
1177                                 }
1178                         }
1179                 }
1180
1181                 if (isset($data["notify"]) && isset($data["pubkey"])
1182                         && isset($data["poll"])
1183                         && isset($data["url"])
1184                 ) {
1185                         $data["network"] = NETWORK_OSTATUS;
1186                 } else {
1187                         return false;
1188                 }
1189
1190                 if ($short) {
1191                         return true;
1192                 }
1193
1194                 // Fetch all additional data from the feed
1195                 $ret = z_fetch_url($data["poll"]);
1196                 if ($ret['errno'] == CURLE_OPERATION_TIMEDOUT) {
1197                         return false;
1198                 }
1199                 $feed = $ret['body'];
1200                 $feed_data = feed_import($feed, $dummy1, $dummy2, $dummy3, true);
1201                 if (!$feed_data) {
1202                         return false;
1203                 }
1204                 if ($feed_data["header"]["author-name"] != "") {
1205                         $data["name"] = $feed_data["header"]["author-name"];
1206                 }
1207                 if ($feed_data["header"]["author-nick"] != "") {
1208                         $data["nick"] = $feed_data["header"]["author-nick"];
1209                 }
1210                 if ($feed_data["header"]["author-avatar"] != "") {
1211                         $data["photo"] = self::fixAvatar($feed_data["header"]["author-avatar"], $data["url"]);
1212                 }
1213                 if ($feed_data["header"]["author-id"] != "") {
1214                         $data["alias"] = $feed_data["header"]["author-id"];
1215                 }
1216                 if ($feed_data["header"]["author-location"] != "") {
1217                         $data["location"] = $feed_data["header"]["author-location"];
1218                 }
1219                 if ($feed_data["header"]["author-about"] != "") {
1220                         $data["about"] = $feed_data["header"]["author-about"];
1221                 }
1222                 // OStatus has serious issues when the the url doesn't fit (ssl vs. non ssl)
1223                 // So we take the value that we just fetched, although the other one worked as well
1224                 if ($feed_data["header"]["author-link"] != "") {
1225                         $data["url"] = $feed_data["header"]["author-link"];
1226                 }
1227                 /// @todo Fetch location and "about" from the feed as well
1228                 return $data;
1229         }
1230
1231         /**
1232          * @brief Fetch data from a pump.io profile page
1233          *
1234          * @param string $profile_link Link to the profile page
1235          *
1236          * @return array profile data
1237          */
1238         private static function pumpioProfileData($profile_link) {
1239
1240                 $doc = new DOMDocument();
1241                 if (!@$doc->loadHTMLFile($profile_link)) {
1242                         return false;
1243                 }
1244
1245                 $xpath = new DomXPath($doc);
1246
1247                 $data = array();
1248
1249                 // This is ugly - but pump.io doesn't seem to know a better way for it
1250                 $data["name"] = trim($xpath->query("//h1[@class='media-header']")->item(0)->nodeValue);
1251                 $pos = strpos($data["name"], chr(10));
1252                 if ($pos) {
1253                         $data["name"] = trim(substr($data["name"], 0, $pos));
1254                 }
1255
1256                 $avatar = $xpath->query("//img[@class='img-rounded media-object']")->item(0);
1257                 if ($avatar) {
1258                         foreach ($avatar->attributes as $attribute) {
1259                                 if ($attribute->name == "src") {
1260                                         $data["photo"] = trim($attribute->value);
1261                                 }
1262                         }
1263                 }
1264
1265                 $data["location"] = $xpath->query("//p[@class='location']")->item(0)->nodeValue;
1266                 $data["about"] = $xpath->query("//p[@class='summary']")->item(0)->nodeValue;
1267
1268                 return $data;
1269         }
1270
1271         /**
1272          * @brief Check for pump.io contact
1273          *
1274          * @param array $webfinger Webfinger data
1275          *
1276          * @return array pump.io data
1277          */
1278         private static function pumpio($webfinger) {
1279
1280                 $data = array();
1281                 foreach ($webfinger["links"] as $link) {
1282                         if (($link["rel"] == "http://webfinger.net/rel/profile-page")
1283                                 && ($link["type"] == "text/html")
1284                                 && ($link["href"] != "")
1285                         ) {
1286                                 $data["url"] = $link["href"];
1287                         } elseif (($link["rel"] == "activity-inbox") && ($link["href"] != "")) {
1288                                 $data["notify"] = $link["href"];
1289                         } elseif (($link["rel"] == "activity-outbox") && ($link["href"] != "")) {
1290                                 $data["poll"] = $link["href"];
1291                         } elseif (($link["rel"] == "dialback") && ($link["href"] != "")) {
1292                                 $data["dialback"] = $link["href"];
1293                         }
1294                 }
1295                 if (isset($data["poll"]) && isset($data["notify"])
1296                         && isset($data["dialback"])
1297                         && isset($data["url"])
1298                 ) {
1299                         // by now we use these fields only for the network type detection
1300                         // So we unset all data that isn't used at the moment
1301                         unset($data["dialback"]);
1302
1303                         $data["network"] = NETWORK_PUMPIO;
1304                 } else {
1305                         return false;
1306                 }
1307
1308                 $profile_data = self::pumpioProfileData($data["url"]);
1309
1310                 if (!$profile_data) {
1311                         return false;
1312                 }
1313
1314                 $data = array_merge($data, $profile_data);
1315
1316                 return $data;
1317         }
1318
1319         /**
1320          * @brief Check page for feed link
1321          *
1322          * @param string $url Page link
1323          *
1324          * @return string feed link
1325          */
1326         private static function getFeedLink($url) {
1327                 $doc = new DOMDocument();
1328
1329                 if (!@$doc->loadHTMLFile($url)) {
1330                         return false;
1331                 }
1332
1333                 $xpath = new DomXPath($doc);
1334
1335                 //$feeds = $xpath->query("/html/head/link[@type='application/rss+xml']");
1336                 $feeds = $xpath->query("/html/head/link[@type='application/rss+xml' and @rel='alternate']");
1337                 if (!is_object($feeds)) {
1338                         return false;
1339                 }
1340
1341                 if ($feeds->length == 0) {
1342                         return false;
1343                 }
1344
1345                 $feed_url = "";
1346
1347                 foreach ($feeds as $feed) {
1348                         $attr = array();
1349                         foreach ($feed->attributes as $attribute) {
1350                                 $attr[$attribute->name] = trim($attribute->value);
1351                         }
1352
1353                         if ($feed_url == "") {
1354                                 $feed_url = $attr["href"];
1355                         }
1356                 }
1357
1358                 return $feed_url;
1359         }
1360
1361         /**
1362          * @brief Check for feed contact
1363          *
1364          * @param string $url Profile link
1365          * @param boolean $probe Do a probe if the page contains a feed link
1366          *
1367          * @return array feed data
1368          */
1369         private static function feed($url, $probe = true) {
1370                 $ret = z_fetch_url($url);
1371                 if ($ret['errno'] == CURLE_OPERATION_TIMEDOUT) {
1372                         return false;
1373                 }
1374                 $feed = $ret['body'];
1375                 $feed_data = feed_import($feed, $dummy1, $dummy2, $dummy3, true);
1376
1377                 if (!$feed_data) {
1378                         if (!$probe) {
1379                                 return false;
1380                         }
1381
1382                         $feed_url = self::getFeedLink($url);
1383
1384                         if (!$feed_url) {
1385                                 return false;
1386                         }
1387
1388                         return self::feed($feed_url, false);
1389                 }
1390
1391                 if ($feed_data["header"]["author-name"] != "") {
1392                         $data["name"] = $feed_data["header"]["author-name"];
1393                 }
1394
1395                 if ($feed_data["header"]["author-nick"] != "") {
1396                         $data["nick"] = $feed_data["header"]["author-nick"];
1397                 }
1398
1399                 if ($feed_data["header"]["author-avatar"] != "") {
1400                         $data["photo"] = $feed_data["header"]["author-avatar"];
1401                 }
1402
1403                 if ($feed_data["header"]["author-id"] != "") {
1404                         $data["alias"] = $feed_data["header"]["author-id"];
1405                 }
1406
1407                 $data["url"] = $url;
1408                 $data["poll"] = $url;
1409
1410                 if ($feed_data["header"]["author-link"] != "") {
1411                         $data["baseurl"] = $feed_data["header"]["author-link"];
1412                 } else {
1413                         $data["baseurl"] = $data["url"];
1414                 }
1415
1416                 $data["network"] = NETWORK_FEED;
1417
1418                 return $data;
1419         }
1420
1421         /**
1422          * @brief Check for mail contact
1423          *
1424          * @param string $uri Profile link
1425          * @param integer $uid User ID
1426          *
1427          * @return array mail data
1428          */
1429         private static function mail($uri, $uid) {
1430
1431                 if (!validate_email($uri)) {
1432                         return false;
1433                 }
1434
1435                 $x = q("SELECT `prvkey` FROM `user` WHERE `uid` = %d LIMIT 1", intval($uid));
1436
1437                 $r = q("SELECT * FROM `mailacct` WHERE `uid` = %d AND `server` != '' LIMIT 1", intval($uid));
1438
1439                 if (dbm::is_result($x) && dbm::is_result($r)) {
1440                         $mailbox = construct_mailbox_name($r[0]);
1441                         $password = '';
1442                         openssl_private_decrypt(hex2bin($r[0]['pass']), $password, $x[0]['prvkey']);
1443                         $mbox = email_connect($mailbox, $r[0]['user'], $password);
1444                         if (!mbox) {
1445                                 return false;
1446                         }
1447                 }
1448
1449                 $msgs = email_poll($mbox, $uri);
1450                 logger('searching '.$uri.', '.count($msgs).' messages found.', LOGGER_DEBUG);
1451
1452                 if (!count($msgs)) {
1453                         return false;
1454                 }
1455
1456                 $phost = substr($uri, strpos($uri, '@') + 1);
1457
1458                 $data = array();
1459                 $data["addr"]    = $uri;
1460                 $data["network"] = NETWORK_MAIL;
1461                 $data["name"]    = substr($uri, 0, strpos($uri, '@'));
1462                 $data["nick"]    = $data["name"];
1463                 $data["photo"]   = avatar_img($uri);
1464                 $data["url"]     = 'http://'.$phost."/".$data["nick"];
1465                 $data["notify"]  = 'smtp '.random_string();
1466                 $data["poll"]    = 'email '.random_string();
1467
1468                 $x = email_msg_meta($mbox, $msgs[0]);
1469                 if (stristr($x[0]->from, $uri)) {
1470                         $adr = imap_rfc822_parse_adrlist($x[0]->from, '');
1471                 } elseif (stristr($x[0]->to, $uri)) {
1472                         $adr = imap_rfc822_parse_adrlist($x[0]->to, '');
1473                 }
1474                 if (isset($adr)) {
1475                         foreach ($adr as $feadr) {
1476                                 if ((strcasecmp($feadr->mailbox, $data["name"]) == 0)
1477                                         &&(strcasecmp($feadr->host, $phost) == 0)
1478                                         && (strlen($feadr->personal))
1479                                 ) {
1480                                         $personal = imap_mime_header_decode($feadr->personal);
1481                                         $data["name"] = "";
1482                                         foreach ($personal as $perspart) {
1483                                                 if ($perspart->charset != "default") {
1484                                                         $data["name"] .= iconv($perspart->charset, 'UTF-8//IGNORE', $perspart->text);
1485                                                 } else {
1486                                                         $data["name"] .= $perspart->text;
1487                                                 }
1488                                         }
1489
1490                                         $data["name"] = notags($data["name"]);
1491                                 }
1492                         }
1493                 }
1494                 imap_close($mbox);
1495
1496                 return $data;
1497         }
1498
1499         /**
1500          * @brief Mix two paths together to possibly fix missing parts
1501          *
1502          * @param string $avatar Path to the avatar
1503          * @param string $base Another path that is hopefully complete
1504          *
1505          * @return string fixed avatar path
1506          */
1507         public static function fixAvatar($avatar, $base) {
1508                 $base_parts = parse_url($base);
1509
1510                 // Remove all parts that could create a problem
1511                 unset($base_parts['path']);
1512                 unset($base_parts['query']);
1513                 unset($base_parts['fragment']);
1514
1515                 $avatar_parts = parse_url($avatar);
1516
1517                 // Now we mix them
1518                 $parts = array_merge($base_parts, $avatar_parts);
1519
1520                 // And put them together again
1521                 $scheme   = isset($parts['scheme'])   ? $parts['scheme'] . '://' : '';
1522                 $host     = isset($parts['host'])     ? $parts['host']           : '';
1523                 $port     = isset($parts['port'])     ? ':' . $parts['port']     : '';
1524                 $path     = isset($parts['path'])     ? $parts['path']           : '';
1525                 $query    = isset($parts['query'])    ? '?' . $parts['query']    : '';
1526                 $fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : '';
1527
1528                 $fixed = $scheme.$host.$port.$path.$query.$fragment;
1529
1530                 logger('Base: '.$base.' - Avatar: '.$avatar.' - Fixed: '.$fixed, LOGGER_DATA);
1531
1532                 return $fixed;
1533         }
1534 }