]> git.mxchange.org Git - friendica.git/blobdiff - src/Network/Probe.php
Probe: Additional checks for connection problems
[friendica.git] / src / Network / Probe.php
index 0094609b26fab1c9fa9b14d5f2b02c7f9345026e..fac76e0dacc7df60ab075e59066cf28915288ea3 100644 (file)
@@ -17,12 +17,13 @@ use Friendica\Core\Logger;
 use Friendica\Core\Protocol;
 use Friendica\Core\System;
 use Friendica\Database\DBA;
+use Friendica\Model\Contact;
 use Friendica\Model\Profile;
+use Friendica\Protocol\ActivityNamespace;
 use Friendica\Protocol\ActivityPub;
 use Friendica\Protocol\Email;
 use Friendica\Protocol\Feed;
 use Friendica\Util\Crypto;
-use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Network;
 use Friendica\Util\Strings;
 use Friendica\Util\XML;
@@ -45,8 +46,8 @@ class Probe
         */
        private static function rearrangeData($data)
        {
-               $fields = ["name", "nick", "guid", "url", "addr", "alias", "photo",
-                               "account-type", "community", "keywords", "location", "about",
+               $fields = ["name", "nick", "guid", "url", "addr", "alias", "photo", "account-type",
+                               "community", "keywords", "location", "about", "gender", "hide",
                                "batch", "notify", "poll", "request", "confirm", "poco",
                                "following", "followers", "inbox", "outbox", "sharedinbox",
                                "priority", "network", "pubkey", "baseurl"];
@@ -114,19 +115,29 @@ class Probe
                $xrd = null;
 
                $curlResult = Network::curl($ssl_url, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
+               $ssl_connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0);
                if ($curlResult->isSuccess()) {
                        $xml = $curlResult->getBody();
                        $xrd = XML::parseString($xml, false);
                        $host_url = 'https://'.$host;
+               } elseif ($curlResult->isTimeout()) {
+                       Logger::info('Probing timeout', ['url' => $ssl_url], Logger::DEBUG);
+                       self::$istimeout = true;
+                       return false;
                }
 
                if (!is_object($xrd)) {
                        $curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
+                       $connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0);
                        if ($curlResult->isTimeout()) {
-                               Logger::log("Probing timeout for " . $url, Logger::DEBUG);
+                               Logger::info('Probing timeout', ['url' => $url], Logger::DEBUG);
+                               self::$istimeout = true;
+                               return false;
+                       } elseif ($connection_error && $ssl_connection_error) {
                                self::$istimeout = true;
                                return false;
                        }
+
                        $xml = $curlResult->getBody();
                        $xrd = XML::parseString($xml, false);
                        $host_url = 'http://'.$host;
@@ -142,11 +153,7 @@ class Probe
                        return [];
                }
 
-               $lrdd = [];
-               // The following webfinger path is defined in RFC 7033 https://tools.ietf.org/html/rfc7033
-               // Problem is that Hubzilla currently doesn't provide all data in the JSON webfinger
-               // compared to the XML webfinger. So this is commented out by now.
-               // $lrdd = array("application/jrd+json" => $host_url.'/.well-known/webfinger?resource={uri}');
+               $lrdd = ['application/jrd+json' => $host_url . '/.well-known/webfinger?resource={uri}'];
 
                foreach ($links["xrd"]["link"] as $value => $link) {
                        if (!empty($link["@attributes"])) {
@@ -200,10 +207,10 @@ class Probe
                Logger::log('webfingerDfrn: '.$webbie.':'.print_r($links, true), Logger::DATA);
                if (!empty($links) && is_array($links)) {
                        foreach ($links as $link) {
-                               if ($link['@attributes']['rel'] === NAMESPACE_DFRN) {
+                               if ($link['@attributes']['rel'] === ActivityNamespace::DFRN) {
                                        $profile_link = $link['@attributes']['href'];
                                }
-                               if (($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB) && ($profile_link == "")) {
+                               if (($link['@attributes']['rel'] === ActivityNamespace::OSTATUSSUB) && ($profile_link == "")) {
                                        $profile_link = 'stat:'.$link['@attributes']['template'];
                                }
                                if ($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard') {
@@ -348,9 +355,10 @@ class Probe
                if (!self::$istimeout) {
                        $ap_profile = ActivityPub::probeProfile($uri);
 
-                       if (!empty($ap_profile) && empty($network) && (defaults($data, 'network', '') != Protocol::DFRN)) {
+                       if (empty($data) || (!empty($ap_profile) && empty($network) && (($data['network'] ?? '') != Protocol::DFRN))) {
                                $data = $ap_profile;
                        } elseif (!empty($ap_profile)) {
+                               $ap_profile['batch'] = '';
                                $data = array_merge($ap_profile, $data);
                        }
                } else {
@@ -361,9 +369,9 @@ class Probe
                        $data['url'] = $uri;
                }
 
-               if (!empty($data['photo'])) {
-                       $data['baseurl'] = Network::getUrlMatch(Strings::normaliseLink(defaults($data, 'baseurl', '')), Strings::normaliseLink($data['photo']));
-               } else {
+               if (!empty($data['photo']) && !empty($data['baseurl'])) {
+                       $data['baseurl'] = Network::getUrlMatch(Strings::normaliseLink($data['baseurl']), Strings::normaliseLink($data['photo']));
+               } elseif (empty($data['photo'])) {
                        $data['photo'] = System::baseUrl() . '/images/person-300.jpg';
                }
 
@@ -393,6 +401,10 @@ class Probe
                        $data['network'] = Protocol::PHANTOM;
                }
 
+               if (!isset($data['hide']) && in_array($data['network'], Protocol::FEDERATED)) {
+                       $data['hide'] = self::getHideStatus($data['url']);
+               }
+
                $data = self::rearrangeData($data);
 
                // Only store into the cache if the value seems to be valid
@@ -403,6 +415,70 @@ class Probe
                return $data;
        }
 
+
+       /**
+        * Fetches the "hide" status from the profile
+        *
+        * @param string $url URL of the profile
+        *
+        * @return boolean "hide" status
+        */
+       private static function getHideStatus($url)
+       {
+               $curlResult = Network::curl($url);
+               if (!$curlResult->isSuccess()) {
+                       return false;
+               }
+
+               // If the file is too large then exit
+               if (($curlResult->getInfo()['download_content_length'] ?? 0) > 1000000) {
+                       return false;
+               }
+
+               // If it isn't a HTML file then exit
+               if (($curlResult->getContentType() != '') && !strstr(strtolower($curlResult->getContentType()), 'html')) {
+                       return false;
+               }
+
+               $body = $curlResult->getBody();
+
+               $doc = new DOMDocument();
+               @$doc->loadHTML($body);
+
+               $xpath = new DOMXPath($doc);
+
+               $list = $xpath->query('//meta[@name]');
+               foreach ($list as $node) {
+                       $meta_tag = [];
+                       if ($node->attributes->length) {
+                               foreach ($node->attributes as $attribute) {
+                                       $meta_tag[$attribute->name] = $attribute->value;
+                               }
+                       }
+
+                       if (empty($meta_tag['content'])) {
+                               continue;
+                       }
+
+                       $content = strtolower(trim($meta_tag['content']));
+
+                       switch (strtolower(trim($meta_tag['name']))) {
+                               case 'dfrn-global-visibility':
+                                       if ($content == 'false') {
+                                               return true;
+                                       }
+                                       break;
+                               case 'robots':
+                                       if (strpos($content, 'noindex') !== false) {
+                                               return true;
+                                       }
+                                       break;
+                       }
+               }
+
+               return false;
+       }
+
        /**
         * @brief Checks if a profile url should be OStatus but only provides partial information
         *
@@ -423,7 +499,7 @@ class Probe
                $has_key = false;
 
                foreach ($webfinger['links'] as $link) {
-                       if ($link['rel'] == NAMESPACE_OSTATUSSUB) {
+                       if ($link['rel'] == ActivityNamespace::OSTATUSSUB) {
                                $is_ostatus = true;
                        }
                        if ($link['rel'] == 'magic-public-key') {
@@ -470,7 +546,7 @@ class Probe
                        }
 
                        if ($host == 'twitter.com') {
-                               return ["network" => Protocol::TWITTER];
+                               return self::twitter($uri);
                        }
                        $lrdd = self::hostMeta($host);
 
@@ -478,7 +554,7 @@ class Probe
                                return [];
                        }
 
-                       $path_parts = explode("/", trim(defaults($parts, 'path', ''), "/"));
+                       $path_parts = explode("/", trim($parts['path'] ?? '', "/"));
 
                        while (!$lrdd && (sizeof($path_parts) > 1)) {
                                $host .= "/".array_shift($path_parts);
@@ -511,7 +587,7 @@ class Probe
                        $nick = substr($uri, 0, strpos($uri, '@'));
 
                        if (strpos($uri, '@twitter.com')) {
-                               return ["network" => Protocol::TWITTER];
+                               return self::twitter($uri);
                        }
                        $lrdd = self::hostMeta($host);
 
@@ -581,6 +657,9 @@ class Probe
                if ((!$result && ($network == "")) || ($network == Protocol::OSTATUS)) {
                        $result = self::ostatus($webfinger);
                }
+               if (in_array($network, ['', Protocol::ZOT])) {
+                       $result = self::zot($webfinger, $result);
+               }
                if ((!$result && ($network == "")) || ($network == Protocol::PUMPIO)) {
                        $result = self::pumpio($webfinger, $addr);
                }
@@ -608,7 +687,7 @@ class Probe
 
                Logger::log($uri." is ".$result["network"], Logger::DEBUG);
 
-               if (empty($result["baseurl"])) {
+               if (empty($result["baseurl"]) && ($result["network"] != Protocol::PHANTOM)) {
                        $pos = strpos($result["url"], $host);
                        if ($pos) {
                                $result["baseurl"] = substr($result["url"], 0, $pos).$host;
@@ -617,6 +696,151 @@ class Probe
                return $result;
        }
 
+       /**
+        * Check for Zot contact
+        *
+        * @param array $webfinger Webfinger data
+        * @param array $data      previously probed data
+        *
+        * @return array Zot data
+        * @throws HTTPException\InternalServerErrorException
+        */
+       private static function zot($webfinger, $data)
+       {
+               if (!empty($webfinger["aliases"]) && is_array($webfinger["aliases"])) {
+                       foreach ($webfinger["aliases"] as $alias) {
+                               if (substr($alias, 0, 5) == 'acct:') {
+                                       $data["addr"] = substr($alias, 5);
+                               }
+                       }
+               }
+
+               if (!empty($webfinger["subject"]) && (substr($webfinger["subject"], 0, 5) == "acct:")) {
+                       $data["addr"] = substr($webfinger["subject"], 5);
+               }
+
+               $zot_url = '';
+               foreach ($webfinger['links'] as $link) {
+                       if (($link['rel'] == 'http://purl.org/zot/protocol') && !empty($link['href'])) {
+                               $zot_url = $link['href'];
+                       }
+               }
+
+               if (empty($zot_url) && !empty($data['addr']) && !empty(self::$baseurl)) {
+                       $condition = ['nurl' => Strings::normaliseLink(self::$baseurl), 'platform' => ['hubzilla']];
+                       if (!DBA::exists('gserver', $condition)) {
+                               return $data;
+                       }
+                       $zot_url = self::$baseurl . '/.well-known/zot-info?address=' . $data['addr'];
+               }
+
+               if (empty($zot_url)) {
+                       return $data;
+               }
+
+               $data = self::pollZot($zot_url, $data);
+
+               if (!empty($data['url']) && !empty($webfinger['aliases']) && is_array($webfinger['aliases'])) {
+                       foreach ($webfinger['aliases'] as $alias) {
+                               if (!strstr($alias, '@') && Strings::normaliseLink($alias) != Strings::normaliseLink($data['url'])) {
+                                       $data['alias'] = $alias;
+                               }
+                       }
+               }
+
+               return $data;
+       }
+
+       public static function pollZot($url, $data)
+       {
+               $curlResult = Network::curl($url);
+               if ($curlResult->isTimeout()) {
+                       return $data;
+               }
+               $content = $curlResult->getBody();
+               if (!$content) {
+                       return $data;
+               }
+
+               $json = json_decode($content, true);
+               if (!is_array($json)) {
+                       return $data;
+               }
+
+               if (empty($data['network'])) {
+                       if (!empty($json['protocols']) && in_array('zot', $json['protocols'])) {
+                               $data['network'] = Protocol::ZOT;
+                       } elseif (!isset($json['protocols'])) {
+                               $data['network'] = Protocol::ZOT;
+                       }
+               }
+
+               if (!empty($json['guid']) && empty($data['guid'])) {
+                       $data['guid'] = $json['guid'];
+               }
+               if (!empty($json['key']) && empty($data['pubkey'])) {
+                       $data['pubkey'] = $json['key'];
+               }
+               if (!empty($json['name'])) {
+                       $data['name'] = $json['name'];
+               }
+               if (!empty($json['photo'])) {
+                       $data['photo'] = $json['photo'];
+                       if (!empty($json['photo_updated'])) {
+                               $data['photo'] .= '?rev=' . urlencode($json['photo_updated']);
+                       }
+               }
+               if (!empty($json['address'])) {
+                       $data['addr'] = $json['address'];
+               }
+               if (!empty($json['url'])) {
+                       $data['url'] = $json['url'];
+               }
+               if (!empty($json['connections_url'])) {
+                       $data['poco'] = $json['connections_url'];
+               }
+               if (isset($json['searchable'])) {
+                       $data['hide'] = !$json['searchable'];
+               }
+               if (!empty($json['public_forum'])) {
+                       $data['community'] = $json['public_forum'];
+                       $data['account-type'] = Contact::PAGE_COMMUNITY;
+               }
+
+               if (!empty($json['profile'])) {
+                       $profile = $json['profile'];
+                       if (!empty($profile['description'])) {
+                               $data['about'] = $profile['description'];
+                       }
+                       if (!empty($profile['gender'])) {
+                               $data['gender'] = $profile['gender'];
+                       }
+                       if (!empty($profile['keywords'])) {
+                               $keywords = implode(', ', $profile['keywords']);
+                               if (!empty($keywords)) {
+                                       $data['keywords'] = $keywords;
+                               }
+                       }
+
+                       $loc = [];
+                       if (!empty($profile['region'])) {
+                               $loc['region'] = $profile['region'];
+                       }
+                       if (!empty($profile['country'])) {
+                               $loc['country-name'] = $profile['country'];
+                       }
+                       if (!empty($profile['hometown'])) {
+                               $loc['locality'] = $profile['hometown'];
+                       }
+                       $location = Profile::formatLocation($loc);
+                       if (!empty($location)) {
+                               $data['location'] = $location;
+                       }
+               }
+
+               return $data;
+       }
+
        /**
         * @brief Perform a webfinger request.
         *
@@ -739,7 +963,7 @@ class Probe
                }
 
                if (!empty($json["tags"])) {
-                       $keywords = implode(" ", $json["tags"]);
+                       $keywords = implode(", ", $json["tags"]);
                        if ($keywords != "") {
                                $data["keywords"] = $keywords;
                        }
@@ -754,6 +978,10 @@ class Probe
                        $data["about"] = $json["about"];
                }
 
+               if (!empty($json["gender"])) {
+                       $data["gender"] = $json["gender"];
+               }
+
                if (!empty($json["key"])) {
                        $data["pubkey"] = $json["key"];
                }
@@ -778,6 +1006,12 @@ class Probe
                        $data["poll"] = $json["dfrn-poll"];
                }
 
+               if (isset($json["hide"])) {
+                       $data["hide"] = (bool)$json["hide"];
+               } else {
+                       $data["hide"] = false;
+               }
+
                return $data;
        }
 
@@ -842,19 +1076,19 @@ class Probe
 
                if (empty($data["addr"]) || empty($data["nick"])) {
                        $probe_data = self::uri($profile_link);
-                       $data["addr"] = defaults($data, "addr", $probe_data["addr"]);
-                       $data["nick"] = defaults($data, "nick", $probe_data["nick"]);
+                       $data["addr"] = ($data["addr"] ?? '') ?: $probe_data["addr"];
+                       $data["nick"] = ($data["nick"] ?? '') ?: $probe_data["nick"];
                }
 
                $prof_data["addr"]         = $data["addr"];
                $prof_data["nick"]         = $data["nick"];
-               $prof_data["dfrn-request"] = defaults($data, 'request', null);
-               $prof_data["dfrn-confirm"] = defaults($data, 'confirm', null);
-               $prof_data["dfrn-notify"]  = defaults($data, 'notify' , null);
-               $prof_data["dfrn-poll"]    = defaults($data, 'poll'   , null);
-               $prof_data["photo"]        = defaults($data, 'photo'  , null);
-               $prof_data["fn"]           = defaults($data, 'name'   , null);
-               $prof_data["key"]          = defaults($data, 'pubkey' , null);
+               $prof_data["dfrn-request"] = $data['request'] ?? null;
+               $prof_data["dfrn-confirm"] = $data['confirm'] ?? null;
+               $prof_data["dfrn-notify"]  = $data['notify']  ?? null;
+               $prof_data["dfrn-poll"]    = $data['poll']    ?? null;
+               $prof_data["photo"]        = $data['photo']   ?? null;
+               $prof_data["fn"]           = $data['name']    ?? null;
+               $prof_data["key"]          = $data['pubkey']  ?? null;
 
                Logger::log("Result for profile ".$profile_link.": ".print_r($prof_data, true), Logger::DEBUG);
 
@@ -876,15 +1110,15 @@ class Probe
                // The array is reversed to take into account the order of preference for same-rel links
                // See: https://tools.ietf.org/html/rfc7033#section-4.4.4
                foreach (array_reverse($webfinger["links"]) as $link) {
-                       if (($link["rel"] == NAMESPACE_DFRN) && !empty($link["href"])) {
+                       if (($link["rel"] == ActivityNamespace::DFRN) && !empty($link["href"])) {
                                $data["network"] = Protocol::DFRN;
-                       } elseif (($link["rel"] == NAMESPACE_FEED) && !empty($link["href"])) {
+                       } elseif (($link["rel"] == ActivityNamespace::FEED) && !empty($link["href"])) {
                                $data["poll"] = $link["href"];
-                       } elseif (($link["rel"] == "http://webfinger.net/rel/profile-page") && (defaults($link, "type", "") == "text/html") && !empty($link["href"])) {
+                       } elseif (($link["rel"] == "http://webfinger.net/rel/profile-page") && (($link["type"] ?? "") == "text/html") && !empty($link["href"])) {
                                $data["url"] = $link["href"];
                        } elseif (($link["rel"] == "http://microformats.org/profile/hcard") && !empty($link["href"])) {
                                $hcard_url = $link["href"];
-                       } elseif (($link["rel"] == NAMESPACE_POCO) && !empty($link["href"])) {
+                       } elseif (($link["rel"] == ActivityNamespace::POCO) && !empty($link["href"])) {
                                $data["poco"] = $link["href"];
                        } elseif (($link["rel"] == "http://webfinger.net/rel/avatar") && !empty($link["href"])) {
                                $data["photo"] = $link["href"];
@@ -1081,6 +1315,7 @@ class Probe
        {
                $hcard_url = "";
                $data = [];
+
                // The array is reversed to take into account the order of preference for same-rel links
                // See: https://tools.ietf.org/html/rfc7033#section-4.4.4
                foreach (array_reverse($webfinger["links"]) as $link) {
@@ -1090,11 +1325,11 @@ class Probe
                                $data["baseurl"] = trim($link["href"], '/');
                        } elseif (($link["rel"] == "http://joindiaspora.com/guid") && !empty($link["href"])) {
                                $data["guid"] = $link["href"];
-                       } elseif (($link["rel"] == "http://webfinger.net/rel/profile-page") && (defaults($link, "type", "") == "text/html") && !empty($link["href"])) {
+                       } elseif (($link["rel"] == "http://webfinger.net/rel/profile-page") && (($link["type"] ?? "") == "text/html") && !empty($link["href"])) {
                                $data["url"] = $link["href"];
-                       } elseif (($link["rel"] == NAMESPACE_FEED) && !empty($link["href"])) {
+                       } elseif (($link["rel"] == ActivityNamespace::FEED) && !empty($link["href"])) {
                                $data["poll"] = $link["href"];
-                       } elseif (($link["rel"] == NAMESPACE_POCO) && !empty($link["href"])) {
+                       } elseif (($link["rel"] == ActivityNamespace::POCO) && !empty($link["href"])) {
                                $data["poco"] = $link["href"];
                        } elseif (($link["rel"] == "salmon") && !empty($link["href"])) {
                                $data["notify"] = $link["href"];
@@ -1108,7 +1343,7 @@ class Probe
                        }
                }
 
-               if (!isset($data["url"]) || ($hcard_url == "")) {
+               if (empty($data["url"]) || empty($hcard_url)) {
                        return false;
                }
 
@@ -1133,11 +1368,11 @@ class Probe
                        return false;
                }
 
-               if (isset($data["url"])
-                       && isset($data["guid"])
-                       && isset($data["baseurl"])
-                       && isset($data["pubkey"])
-                       && ($hcard_url != "")
+               if (!empty($data["url"])
+                       && !empty($data["guid"])
+                       && !empty($data["baseurl"])
+                       && !empty($data["pubkey"])
+                       && !empty($hcard_url)
                ) {
                        $data["network"] = Protocol::DIASPORA;
 
@@ -1188,13 +1423,13 @@ class Probe
                        // See: https://tools.ietf.org/html/rfc7033#section-4.4.4
                        foreach (array_reverse($webfinger["links"]) as $link) {
                                if (($link["rel"] == "http://webfinger.net/rel/profile-page")
-                                       && (defaults($link, "type", "") == "text/html")
+                                       && (($link["type"] ?? "") == "text/html")
                                        && ($link["href"] != "")
                                ) {
                                        $data["url"] = $link["href"];
                                } elseif (($link["rel"] == "salmon") && !empty($link["href"])) {
                                        $data["notify"] = $link["href"];
-                               } elseif (($link["rel"] == NAMESPACE_FEED) && !empty($link["href"])) {
+                               } elseif (($link["rel"] == ActivityNamespace::FEED) && !empty($link["href"])) {
                                        $data["poll"] = $link["href"];
                                } elseif (($link["rel"] == "magic-public-key") && !empty($link["href"])) {
                                        $pubkey = $link["href"];
@@ -1295,8 +1530,13 @@ class Probe
         */
        private static function pumpioProfileData($profile_link)
        {
+               $curlResult = Network::curl($profile_link);
+               if (!$curlResult->isSuccess()) {
+                       return false;
+               }
+
                $doc = new DOMDocument();
-               if (!@$doc->loadHTMLFile($profile_link)) {
+               if (!@$doc->loadHTML($curlResult->getBody())) {
                        return false;
                }
 
@@ -1345,9 +1585,8 @@ class Probe
        /**
         * @brief Check for pump.io contact
         *
-        * @param array $webfinger Webfinger data
-        *
-        * @param       $addr
+        * @param array  $webfinger Webfinger data
+        * @param string $addr
         * @return array pump.io data
         */
        private static function pumpio($webfinger, $addr)
@@ -1357,7 +1596,7 @@ class Probe
                // See: https://tools.ietf.org/html/rfc7033#section-4.4.4
                foreach (array_reverse($webfinger["links"]) as $link) {
                        if (($link["rel"] == "http://webfinger.net/rel/profile-page")
-                               && (defaults($link, "type", "") == "text/html")
+                               && (($link["type"] ?? "") == "text/html")
                                && ($link["href"] != "")
                        ) {
                                $data["url"] = $link["href"];
@@ -1400,6 +1639,66 @@ class Probe
                return $data;
        }
 
+       /**
+        * @brief Check for twitter contact
+        *
+        * @param string $uri
+        *
+        * @return array twitter data
+        */
+       private static function twitter($uri)
+       {
+               if (preg_match('=(.*)@twitter.com=i', $uri, $matches)) {
+                       $nick = $matches[1];
+               } elseif (preg_match('=https?://twitter.com/(.*)=i', $uri, $matches)) {
+                       $nick = $matches[1];
+               } else {
+                       return [];
+               }
+
+               $data = [];
+               $data['url'] = 'https://twitter.com/' . $nick;
+               $data['addr'] = $nick . '@twitter.com';
+               $data['nick'] = $data['name'] = $nick;
+               $data['network'] = Protocol::TWITTER;
+               $data['baseurl'] = 'https://twitter.com';
+
+               $curlResult = Network::curl($data['url'], false);
+               if (!$curlResult->isSuccess()) {
+                       return [];
+               }
+
+               $body = $curlResult->getBody();
+               $doc = new DOMDocument();
+               @$doc->loadHTML($body);
+               $xpath = new DOMXPath($doc);
+
+               $list = $xpath->query('//img[@class]');
+               foreach ($list as $node) {
+                       $img_attr = [];
+                       if ($node->attributes->length) {
+                               foreach ($node->attributes as $attribute) {
+                                       $img_attr[$attribute->name] = $attribute->value;
+                               }
+                       }
+
+                       if (empty($img_attr['class'])) {
+                               continue;
+                       }
+
+                       if (strpos($img_attr['class'], 'ProfileAvatar-image') !== false) {
+                               if (!empty($img_attr['src'])) {
+                                       $data['photo'] = $img_attr['src'];
+                               }
+                               if (!empty($img_attr['alt'])) {
+                                       $data['name'] = $img_attr['alt'];
+                               }
+                       }
+               }
+
+               return $data;
+       }
+
        /**
         * @brief Check page for feed link
         *
@@ -1409,9 +1708,13 @@ class Probe
         */
        private static function getFeedLink($url)
        {
-               $doc = new DOMDocument();
+               $curlResult = Network::curl($url);
+               if (!$curlResult->isSuccess()) {
+                       return false;
+               }
 
-               if (!@$doc->loadHTMLFile($url)) {
+               $doc = new DOMDocument();
+               if (!@$doc->loadHTML($curlResult->getBody())) {
                        return false;
                }
 
@@ -1435,7 +1738,7 @@ class Probe
                                $attr[$attribute->name] = trim($attribute->value);
                        }
 
-                       if ($feed_url == "") {
+                       if (empty($feed_url) && !empty($attr['href'])) {
                                $feed_url = $attr["href"];
                        }
                }