function diaspora_discover(&$a, &$b) { require_once 'include/network.php'; $result = array(); $network = null; $diaspora = false; $diaspora_base = ''; $diaspora_guid = ''; $diaspora_key = ''; $dfrn = false; $x = old_webfinger($webbie); if ($x) { logger('old_webfinger: ' . print_r($x, true)); foreach ($x as $link) { if ($link['@attributes']['rel'] === NAMESPACE_DFRN) { $dfrn = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'salmon') { $notify = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === NAMESPACE_FEED) { $poll = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard') { $hcard = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://webfinger.net/rel/profile-page') { $profile = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://portablecontacts.net/spec/1.0') { $poco = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://joindiaspora.com/seed_location') { $diaspora_base = unamp($link['@attributes']['href']); $diaspora = true; } if ($link['@attributes']['rel'] === 'http://joindiaspora.com/guid') { $diaspora_guid = unamp($link['@attributes']['href']); $diaspora = true; } if ($link['@attributes']['rel'] === 'diaspora-public-key') { $diaspora_key = base64_decode(unamp($link['@attributes']['href'])); if (strstr($diaspora_key, 'RSA ')) { $pubkey = rsatopem($diaspora_key); } else { $pubkey = $diaspora_key; } $diaspora = true; } } if ($diaspora && $diaspora_base && $diaspora_guid) { $guid = $diaspora_guid; $diaspora_base = trim($diaspora_base, '/'); $notify = $diaspora_base . '/receive'; if (strpos($webbie, '@')) { $addr = str_replace('acct:', '', $webbie); $hostname = substr($webbie, strpos($webbie, '@') + 1); } $network = 'diaspora'; // until we get a dfrn layer, we'll use diaspora protocols for Friendica, // but give it a different network so we can go back and fix these when we get proper support. // It really should be just 'friendica' but we also want to distinguish // between Friendica sites that we can use D* protocols with and those we can't. // Some Friendica sites will have Diaspora disabled. if ($dfrn) { $network = 'friendica-over-diaspora'; } if ($hcard) { $vcard = scrape_vcard($hcard); $vcard['nick'] = substr($webbie, 0, strpos($webbie, '@')); if (!$vcard['fn']) { $vcard['fn'] = $webbie; } } $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($addr)); /** * * Diaspora communications are notoriously unreliable and receiving profile update messages (indeed any messages) * are pretty much random luck. We'll check the timestamp of the xchan_name_date at a higher level and refresh * this record once a month; because if you miss a profile update message and they update their profile photo or name * you're otherwise stuck with stale info until they change their profile again - which could be years from now. * */ if ($r) { $r = q("update xchan set xchan_name = '%s', xchan_network = '%s', xchan_name_date = '%s' where xchan_hash = '%s' limit 1", dbesc($vcard['fn']), dbesc($network), dbesc(datetime_convert()), dbesc($addr)); } else { $r = q("insert into xchan ( xchan_hash, xchan_guid, xchan_pubkey, xchan_addr, xchan_url, xchan_name, xchan_network, xchan_name_date ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", dbesc($addr), dbesc($guid), dbesc($pubkey), dbesc($addr), dbesc($profile), dbesc($vcard['fn']), dbesc($network), dbescdate(datetime_convert())); } $r = q("select * from hubloc where hubloc_hash = '%s' limit 1", dbesc($webbie)); if (!$r) { $r = q("insert into hubloc ( hubloc_guid, hubloc_hash, hubloc_addr, hubloc_network, hubloc_url, hubloc_host, hubloc_callback, hubloc_updated, hubloc_primary ) values ('%s','%s','%s','%s','%s','%s','%s','%s', 1)", dbesc($guid), dbesc($addr), dbesc($addr), dbesc($network), dbesc(trim($diaspora_base, '/')), dbesc($hostname), dbesc($notify), dbescdate(datetime_convert())); } $photos = import_xchan_photo($vcard['photo'], $addr); $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_hash = '%s'", dbescdate(datetime_convert('UTC', 'UTC', $arr['photo_updated'])), dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), dbesc($photos[3]), dbesc($addr)); return true; } return false; /* $vcard['fn'] = notags($vcard['fn']); $vcard['nick'] = str_replace(' ','',notags($vcard['nick'])); $result['name'] = $vcard['fn']; $result['nick'] = $vcard['nick']; $result['guid'] = $guid; $result['url'] = $profile; $result['hostname'] = $hostname; $result['addr'] = $addr; $result['batch'] = $batch; $result['notify'] = $notify; $result['poll'] = $poll; $result['request'] = $request; $result['confirm'] = $confirm; $result['poco'] = $poco; $result['photo'] = $vcard['photo']; $result['priority'] = $priority; $result['network'] = $network; $result['alias'] = $alias; $result['pubkey'] = $pubkey; logger('probe_url: ' . print_r($result,true), LOGGER_DEBUG); return $result; */ /* Sample Diaspora result. Array ( [name] => Mike Macgirvin [nick] => macgirvin [guid] => a9174a618f8d269a [url] => https://joindiaspora.com/u/macgirvin [hostname] => joindiaspora.com [addr] => macgirvin@joindiaspora.com [batch] => [notify] => https://joindiaspora.com/receive [poll] => https://joindiaspora.com/public/macgirvin.atom [request] => [confirm] => [poco] => [photo] => https://joindiaspora.s3.amazonaws.com/uploads/images/thumb_large_fec4e6eef13ae5e56207.jpg [priority] => [network] => diaspora [alias] => [pubkey] => -----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtihtyIuRDWkDpCA+I1UaQ jI4S7k625+A7EEJm+pL2ZVSJxeCKiFeEgHBQENjLMNNm8l8F6blxgQqE6ZJ9Spa7f tlaXYTRCrfxKzh02L3hR7sNA+JS/nXJaUAIo+IwpIEspmcIRbD9GB7Wv/rr+M28uH 31EeYyDz8QL6InU/bJmnCdFvmEMBQxJOw1ih9tQp7UNJAbUMCje0WYFzBz7sfcaHL OyYcCOqOCBLdGucUoJzTQ9iDBVzB8j1r1JkIHoEb2moUoKUp+tkCylNfd/3IVELF9 7w1Qjmit3m50OrJk2DQOXvCW9KQxaQNdpRPSwhvemIt98zXSeyZ1q/YjjOwG0DWDq AF8aLj3/oQaZndTPy/6tMiZogKaijoxj8xFLuPYDTw5VpKquriVC0z8oxyRbv4t9v 8JZZ9BXqzmayvY3xZGGp8NulrfjW+me2bKh0/df1aHaBwpZdDTXQ6kqAiS2FfsuPN vg57fhfHbL1yJ4oDbNNNeI0kJTGchXqerr8C20khU/cQ2Xt31VyEZtnTB665Ceugv kp3t2qd8UpAVKl430S5Quqx2ymfUIdxdW08CEjnoRNEL3aOWOXfbf4gSVaXmPCR4i LSIeXnd14lQYK/uxW/8cTFjcmddsKxeXysoQxbSa9VdDK+KkpZdgYXYrTTofXs6v+ 4afAEhRaaY+MCAwEAAQ== -----END PUBLIC KEY----- ) */ } }
function discover_by_webbie($webbie) { require_once 'library/HTML5/Parser.php'; $result = array(); $network = null; $diaspora = false; $gnusoc = false; $dfrn = false; $has_salmon = false; $salmon_key = false; $atom_feed = false; $diaspora_base = ''; $diaspora_guid = ''; $diaspora_key = ''; $webbie = strtolower($webbie); $x = webfinger_rfc7033($webbie, true); if ($x && array_key_exists('links', $x) && $x['links']) { foreach ($x['links'] as $link) { if (array_key_exists('rel', $link)) { // If we discover zot - don't search further; grab the info and get out of // here. if ($link['rel'] === PROTOCOL_ZOT) { logger('discover_by_webbie: zot found for ' . $webbie, LOGGER_DEBUG); if (array_key_exists('zot', $x) && $x['zot']['success']) { $i = import_xchan($x['zot']); } else { $z = z_fetch_url($link['href']); if ($z['success']) { $j = json_decode($z['body'], true); $i = import_xchan($j); return true; } } } if ($link['rel'] == NAMESPACE_DFRN) { $dfrn = $link['href']; } if ($link['rel'] == 'magic-public-key') { if (substr($link['href'], 0, 5) === 'data:') { $salmon_key = convert_salmon_key($link['href']); } } if ($link['rel'] == 'salmon') { $has_salmon = true; $salmon = $link['href']; } if ($link['rel'] == 'http://schemas.google.com/g/2010#updates-from') { $atom_feed = $link['href']; } } } } logger('webfinger: ' . print_r($x, true), LOGGER_DATA, LOG_INFO); $arr = array('address' => $webbie, 'success' => false, 'webfinger' => $x); call_hooks('discover_channel_webfinger', $arr); if ($arr['success']) { return true; } $aliases = array(); // Now let's make some decisions on what we may need // to obtain further info $probe_atom = false; $probe_old = false; $probe_hcard = false; $address = ''; $location = ''; $nickname = ''; $fullname = ''; $avatar = ''; $pubkey = ''; if (is_array($x)) { if (array_key_exists('address', $x)) { $address = $x['address']; } if (array_key_exists('location', $x)) { $location = $x['location']; } if (array_key_exists('nickname', $x)) { $nickname = $x['nickname']; } } if (!$x) { $probe_old = true; } if (!$dfrn && !$has_salmon) { $probe_old = true; } if ($probe_old) { $y = old_webfinger($webbie); if ($y) { logger('old_webfinger: ' . print_r($x, true)); foreach ($y as $link) { if ($link['@attributes']['rel'] === NAMESPACE_DFRN) { $dfrn = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'salmon') { $notify = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === NAMESPACE_FEED) { $poll = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard') { $hcard = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://webfinger.net/rel/profile-page') { $profile = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://portablecontacts.net/spec/1.0') { $poco = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://joindiaspora.com/seed_location') { $diaspora_base = unamp($link['@attributes']['href']); $diaspora = true; } if ($link['@attributes']['rel'] === 'http://joindiaspora.com/guid') { $diaspora_guid = unamp($link['@attributes']['href']); $diaspora = true; } if ($link['@attributes']['rel'] === 'diaspora-public-key') { $diaspora_key = base64_decode(unamp($link['@attributes']['href'])); if (strstr($diaspora_key, 'RSA ')) { $pubkey = rsatopem($diaspora_key); } else { $pubkey = $diaspora_key; } $diaspora = true; } if ($link['@attributes']['rel'] == 'magic-public-key') { if (substr($link['@attributes']['href'], 0, 5) === 'data:') { $salmon_key = convert_salmon_key($link['@attributes']['href']); } } if ($link['@attributes']['rel'] == 'salmon') { $has_salmon = true; $salmon = $link['@attributes']['href']; } if ($link['@attributes']['rel'] == 'http://schemas.google.com/g/2010#updates-from') { $atom_feed = $link['@attributes']['href']; } if ($link['@attributes']['rel'] === 'alias') { $aliases[] = $link['@attributes']['href']; } if ($link['@attributes']['rel'] === 'subject') { $subject = $link['@attributes']['href']; } } } } if ($subject || $aliases) { if (strpos($webbie, '@')) { $rhs = substr($webbie, strpos($webbie, '@') + 1); } else { $m = parse_url($webbie); if ($m) { $rhs = $m['host'] . ($m['port'] ? ':' . $m['port'] : ''); } } $v = array('subject' => $subject, 'aliases' => $aliases); $address = find_webfinger_address($v, $rhs); $location = find_webfinger_location($v, $rhs); if ($address) { $nickname = substr($address, 0, strpos($address, '@')); } } if ($salmon_key && $has_salmon && $atom_feed && !$dfrn && !$diaspora) { $gnusoc = true; $probe_atom = true; } if (!$pubkey) { $pubkey = $salmon_key; } if (($dfrn || $diaspora) && $hcard) { $probe_hcard = true; } if (!$fullname) { $fullname = $nickname; } if ($probe_atom) { $k = z_fetch_url($atom_feed); if ($k['success']) { $feed_meta = feed_meta($k['body']); } if ($feed_meta) { // stash any discovered pubsubhubbub hubs in case we need to follow them // this will save an expensive lookup later if ($feed_meta['hubs'] && $address) { set_xconfig($address, 'system', 'push_hubs', $feed_meta['hubs']); set_xconfig($address, 'system', 'feed_url', $atom_feed); } if ($feed_meta['author']['author_name']) { $fullname = $feed_meta['author']['author_name']; } if (!$avatar) { if ($feed_meta['author']['author_photo']) { $avatar = $feed_meta['author']['author_photo']; } } // for GNU-social over-ride any url aliases we may have picked up in webfinger // The author.uri element in the feed is likely to be more accurate if ($gnusoc && $feed_meta['author']['author_uri']) { $location = $feed_meta['author']['author_uri']; } } } else { if ($probe_hcard) { $vcard = scrape_vcard($hcard); if ($vcard) { logger('vcard: ' . print_r($vcard, true), LOGGER_DATA); if ($vcard['fn']) { $fullname = $vcard['fn']; } if ($vcard['photo'] && strpos($vcard['photo'], 'http') !== 0) { $vcard['photo'] = $diaspora_base . '/' . $vcard['photo']; } if (!$avatar) { $avatar = $vcard['photo']; } } } } if ($profile && !$location) { $location = $profile; } if ($location) { $m = parse_url($location); $base = $m['scheme'] . '://' . $m['host']; $host = $m['host']; } if ($diaspora && $diaspora_base && $diaspora_guid) { if ($dfrn) { $network = 'friendica-over-diaspora'; } else { $network = 'diaspora'; } $base = trim($diaspora_base, '/'); $notify = $base . '/receive'; } else { if ($gnusoc) { $network = 'gnusoc'; $notify = $salmon; } } logger('network: ' . $network); logger('address: ' . $address); logger('fullname: ' . $fullname); logger('pubkey: ' . $pubkey); logger('location: ' . $location); // if we have everything we need, let's create the records if ($network && $address && $fullname && $pubkey && $location) { $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($address)); if ($r) { $r = q("update xchan set xchan_name = '%s', xchan_network = '%s', xchan_name_date = '%s' where xchan_hash = '%s' limit 1", dbesc($fullname), dbesc($network), dbesc(datetime_convert()), dbesc($address)); } else { $r = q("insert into xchan ( xchan_hash, xchan_guid, xchan_pubkey, xchan_addr, xchan_url, xchan_name, xchan_network, xchan_name_date ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", dbesc($address), dbesc($diaspora_guid ? $diaspora_guid : $location), dbesc($pubkey), dbesc($address), dbesc($location), dbesc($fullname), dbesc($network), dbescdate(datetime_convert())); } $r = q("select * from hubloc where hubloc_hash = '%s' limit 1", dbesc($address)); if (!$r) { $r = q("insert into hubloc ( hubloc_guid, hubloc_hash, hubloc_addr, hubloc_network, hubloc_url, hubloc_host, hubloc_callback, hubloc_updated, hubloc_primary ) values ('%s','%s','%s','%s','%s','%s','%s','%s', 1)", dbesc($diaspora_guid ? $diaspora_guid : $location), dbesc($address), dbesc($address), dbesc($network), dbesc($base), dbesc($host), dbesc($notify), dbescdate(datetime_convert())); } $photos = import_xchan_photo($avatar, $address); $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_hash = '%s'", dbescdate(datetime_convert()), dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), dbesc($photos[3]), dbesc($address)); return true; } return false; }
function probe_url($url, $mode = PROBE_NORMAL) { require_once 'include/email.php'; $result = array(); if (!$url) { return $result; } $network = null; $diaspora = false; $diaspora_base = ''; $diaspora_guid = ''; $diaspora_key = ''; $has_lrdd = false; $email_conversant = false; $twitter = strpos($url, 'twitter.com') !== false ? true : false; $at_addr = strpos($url, '@') !== false ? true : false; if (!$twitter) { if (strpos($url, 'mailto:') !== false && $at_addr) { $url = str_replace('mailto:', '', $url); $links = array(); } else { $links = lrdd($url); } if (count($links)) { $has_lrdd = true; logger('probe_url: found lrdd links: ' . print_r($links, true), LOGGER_DATA); foreach ($links as $link) { if ($link['@attributes']['rel'] === NAMESPACE_ZOT) { $zot = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === NAMESPACE_DFRN) { $dfrn = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'salmon') { $notify = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === NAMESPACE_FEED) { $poll = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard') { $hcard = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://webfinger.net/rel/profile-page') { $profile = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://portablecontacts.net/spec/1.0') { $poco = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://joindiaspora.com/seed_location') { $diaspora_base = unamp($link['@attributes']['href']); $diaspora = true; } if ($link['@attributes']['rel'] === 'http://joindiaspora.com/guid') { $diaspora_guid = unamp($link['@attributes']['href']); $diaspora = true; } if ($link['@attributes']['rel'] === 'diaspora-public-key') { $diaspora_key = base64_decode(unamp($link['@attributes']['href'])); $pubkey = rsatopem($diaspora_key); $diaspora = true; } } // Status.Net can have more than one profile URL. We need to match the profile URL // to a contact on incoming messages to prevent spam, and we won't know which one // to match. So in case of two, one of them is stored as an alias. Only store URL's // and not webfinger user@host aliases. If they've got more than two non-email style // aliases, let's hope we're lucky and get one that matches the feed author-uri because // otherwise we're screwed. foreach ($links as $link) { if ($link['@attributes']['rel'] === 'alias') { if (strpos($link['@attributes']['href'], '@') === false) { if (isset($profile)) { if ($link['@attributes']['href'] !== $profile) { $alias = unamp($link['@attributes']['href']); } } else { $profile = unamp($link['@attributes']['href']); } } } } } elseif ($mode == PROBE_NORMAL) { // Check email $orig_url = $url; if (strpos($orig_url, '@') && validate_email($orig_url)) { $x = q("SELECT `prvkey` FROM `user` WHERE `uid` = %d LIMIT 1", intval(local_user())); $r = q("SELECT * FROM `mailacct` WHERE `uid` = %d AND `server` != '' LIMIT 1", intval(local_user())); if (count($x) && count($r)) { $mailbox = construct_mailbox_name($r[0]); $password = ''; openssl_private_decrypt(hex2bin($r[0]['pass']), $password, $x[0]['prvkey']); $mbox = email_connect($mailbox, $r[0]['user'], $password); if (!$mbox) { logger('probe_url: email_connect failed.'); } unset($password); } if ($mbox) { $msgs = email_poll($mbox, $orig_url); logger('probe_url: searching ' . $orig_url . ', ' . count($msgs) . ' messages found.', LOGGER_DEBUG); if (count($msgs)) { $addr = $orig_url; $network = NETWORK_MAIL; $name = substr($url, 0, strpos($url, '@')); $phost = substr($url, strpos($url, '@') + 1); $profile = 'http://' . $phost; // fix nick character range $vcard = array('fn' => $name, 'nick' => $name, 'photo' => avatar_img($url)); $notify = 'smtp ' . random_string(); $poll = 'email ' . random_string(); $priority = 0; $x = email_msg_meta($mbox, $msgs[0]); if (stristr($x->from, $orig_url)) { $adr = imap_rfc822_parse_adrlist($x->from, ''); } elseif (stristr($x->to, $orig_url)) { $adr = imap_rfc822_parse_adrlist($x->to, ''); } if (isset($adr)) { foreach ($adr as $feadr) { if (strcasecmp($feadr->mailbox, $name) == 0 && strcasecmp($feadr->host, $phost) == 0 && strlen($feadr->personal)) { $personal = imap_mime_header_decode($feadr->personal); $vcard['fn'] = ""; foreach ($personal as $perspart) { if ($perspart->charset != "default") { $vcard['fn'] .= iconv($perspart->charset, 'UTF-8//IGNORE', $perspart->text); } else { $vcard['fn'] .= $perspart->text; } } $vcard['fn'] = notags($vcard['fn']); } } } } imap_close($mbox); } } } } if ($mode == PROBE_NORMAL) { if (strlen($zot)) { $s = fetch_url($zot); if ($s) { $j = json_decode($s); if ($j) { $network = NETWORK_ZOT; $vcard = array('fn' => $j->fullname, 'nick' => $j->nickname, 'photo' => $j->photo); $profile = $j->url; $notify = $j->post; $pubkey = $j->pubkey; $poll = 'N/A'; } } } if (strlen($dfrn)) { $ret = scrape_dfrn($hcard ? $hcard : $dfrn); if (is_array($ret) && x($ret, 'dfrn-request')) { $network = NETWORK_DFRN; $request = $ret['dfrn-request']; $confirm = $ret['dfrn-confirm']; $notify = $ret['dfrn-notify']; $poll = $ret['dfrn-poll']; $vcard = array(); $vcard['fn'] = $ret['fn']; $vcard['nick'] = $ret['nick']; $vcard['photo'] = $ret['photo']; } } } if ($diaspora && $diaspora_base && $diaspora_guid) { if ($mode == PROBE_DIASPORA || !$notify) { $notify = $diaspora_base . 'receive/users/' . $diaspora_guid; $batch = $diaspora_base . 'receive/public'; } if (strpos($url, '@')) { $addr = str_replace('acct:', '', $url); } } if ($network !== NETWORK_ZOT && $network !== NETWORK_DFRN && $network !== NETWORK_MAIL) { if ($diaspora) { $network = NETWORK_DIASPORA; } elseif ($has_lrdd) { $network = NETWORK_OSTATUS; } $priority = 0; if ($hcard && !$vcard) { $vcard = scrape_vcard($hcard); // Google doesn't use absolute url in profile photos if (x($vcard, 'photo') && substr($vcard['photo'], 0, 1) == '/') { $h = @parse_url($hcard); if ($h) { $vcard['photo'] = $h['scheme'] . '://' . $h['host'] . $vcard['photo']; } } logger('probe_url: scrape_vcard: ' . print_r($vcard, true), LOGGER_DATA); } if ($twitter) { logger('twitter: setup'); $tid = basename($url); $tapi = 'https://api.twitter.com/1/statuses/user_timeline.rss'; if (intval($tid)) { $poll = $tapi . '?user_id=' . $tid; } else { $poll = $tapi . '?screen_name=' . $tid; } $profile = 'http://twitter.com/#!/' . $tid; $vcard['photo'] = 'https://api.twitter.com/1/users/profile_image/' . $tid; $vcard['nick'] = $tid; $vcard['fn'] = $tid . '@twitter'; } if (!x($vcard, 'fn')) { if (x($vcard, 'nick')) { $vcard['fn'] = $vcard['nick']; } } $check_feed = false; if ($twitter || !$poll) { $check_feed = true; } if (!isset($vcard) || !x($vcard, 'fn') || !$profile) { $check_feed = true; } if ($at_addr && !count($links)) { $check_feed = false; } if ($check_feed) { $feedret = scrape_feed($poll ? $poll : $url); logger('probe_url: scrape_feed ' . ($poll ? $poll : $url) . ' returns: ' . print_r($feedret, true), LOGGER_DATA); if (count($feedret) && ($feedret['feed_atom'] || $feedret['feed_rss'])) { $poll = x($feedret, 'feed_atom') ? unamp($feedret['feed_atom']) : unamp($feedret['feed_rss']); if (!x($vcard)) { $vcard = array(); } } if (x($feedret, 'photo') && !x($vcard, 'photo')) { $vcard['photo'] = $feedret['photo']; } require_once 'library/simplepie/simplepie.inc'; $feed = new SimplePie(); $xml = fetch_url($poll); logger('probe_url: fetch feed: ' . $poll . ' returns: ' . $xml, LOGGER_DATA); $a = get_app(); logger('probe_url: scrape_feed: headers: ' . $a->get_curl_headers(), LOGGER_DATA); $feed->set_raw_data($xml); $feed->init(); if ($feed->error()) { logger('probe_url: scrape_feed: Error parsing XML: ' . $feed->error()); } if (!x($vcard, 'photo')) { $vcard['photo'] = $feed->get_image_url(); } $author = $feed->get_author(); if ($author) { $vcard['fn'] = unxmlify(trim($author->get_name())); if (!$vcard['fn']) { $vcard['fn'] = trim(unxmlify($author->get_email())); } if (strpos($vcard['fn'], '@') !== false) { $vcard['fn'] = substr($vcard['fn'], 0, strpos($vcard['fn'], '@')); } $email = unxmlify($author->get_email()); if (!$profile && $author->get_link()) { $profile = trim(unxmlify($author->get_link())); } if (!$vcard['photo']) { $rawtags = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); if ($rawtags) { $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; if (x($elems, 'link') && $elems['link'][0]['attribs']['']['rel'] === 'photo') { $vcard['photo'] = $elems['link'][0]['attribs']['']['href']; } } } } else { $item = $feed->get_item(0); if ($item) { $author = $item->get_author(); if ($author) { $vcard['fn'] = trim(unxmlify($author->get_name())); if (!$vcard['fn']) { $vcard['fn'] = trim(unxmlify($author->get_email())); } if (strpos($vcard['fn'], '@') !== false) { $vcard['fn'] = substr($vcard['fn'], 0, strpos($vcard['fn'], '@')); } $email = unxmlify($author->get_email()); if (!$profile && $author->get_link()) { $profile = trim(unxmlify($author->get_link())); } } if (!$vcard['photo']) { $rawmedia = $item->get_item_tags('http://search.yahoo.com/mrss/', 'thumbnail'); if ($rawmedia && $rawmedia[0]['attribs']['']['url']) { $vcard['photo'] = unxmlify($rawmedia[0]['attribs']['']['url']); } } if (!$vcard['photo']) { $rawtags = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); if ($rawtags) { $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; if (x($elems, 'link') && $elems['link'][0]['attribs']['']['rel'] === 'photo') { $vcard['photo'] = $elems['link'][0]['attribs']['']['href']; } } } } } if (!$vcard['photo'] && strlen($email)) { $vcard['photo'] = avatar_img($email); } if ($poll === $profile) { $lnk = $feed->get_permalink(); } if (isset($lnk) && strlen($lnk)) { $profile = $lnk; } if (!x($vcard, 'fn')) { $vcard['fn'] = notags($feed->get_title()); } if (!x($vcard, 'fn')) { $vcard['fn'] = notags($feed->get_description()); } if (strpos($vcard['fn'], 'Twitter / ') !== false) { $vcard['fn'] = substr($vcard['fn'], strpos($vcard['fn'], '/') + 1); $vcard['fn'] = trim($vcard['fn']); } if (!x($vcard, 'nick')) { $vcard['nick'] = strtolower(notags(unxmlify($vcard['fn']))); if (strpos($vcard['nick'], ' ')) { $vcard['nick'] = trim(substr($vcard['nick'], 0, strpos($vcard['nick'], ' '))); } } if (!$network) { $network = NETWORK_FEED; } if (!$priority) { $priority = 2; } } } if (!x($vcard, 'photo')) { $a = get_app(); $vcard['photo'] = $a->get_baseurl() . '/images/person-175.jpg'; } if (!$profile) { $profile = $url; } // No human could be associated with this link, use the URL as the contact name if ($network === NETWORK_FEED && $poll && !x($vcard, 'fn')) { $vcard['fn'] = $url; } $vcard['fn'] = notags($vcard['fn']); $vcard['nick'] = str_replace(' ', '', notags($vcard['nick'])); $result['name'] = $vcard['fn']; $result['nick'] = $vcard['nick']; $result['url'] = $profile; $result['addr'] = $addr; $result['batch'] = $batch; $result['notify'] = $notify; $result['poll'] = $poll; $result['request'] = $request; $result['confirm'] = $confirm; $result['poco'] = $poco; $result['photo'] = $vcard['photo']; $result['priority'] = $priority; $result['network'] = $network; $result['alias'] = $alias; $result['pubkey'] = $pubkey; logger('probe_url: ' . print_r($result, true), LOGGER_DEBUG); return $result; }
function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { require_once 'include/email.php'; $result = array(); if (!$url) { return $result; } $result = Cache::get("probe_url:" . $mode . ":" . $url); if (!is_null($result)) { $result = unserialize($result); return $result; } $network = null; $diaspora = false; $diaspora_base = ''; $diaspora_guid = ''; $diaspora_key = ''; $has_lrdd = false; $email_conversant = false; $connectornetworks = false; $appnet = false; if (strpos($url, 'twitter.com')) { $connectornetworks = true; $network = NETWORK_TWITTER; } // Twitter is deactivated since twitter closed its old API //$twitter = ((strpos($url,'twitter.com') !== false) ? true : false); $lastfm = strpos($url, 'last.fm/user') !== false ? true : false; $at_addr = strpos($url, '@') !== false ? true : false; if (!$appnet && !$lastfm && !$connectornetworks) { if (strpos($url, 'mailto:') !== false && $at_addr) { $url = str_replace('mailto:', '', $url); $links = array(); } else { $links = lrdd($url); } if (count($links)) { $has_lrdd = true; logger('probe_url: found lrdd links: ' . print_r($links, true), LOGGER_DATA); foreach ($links as $link) { if ($link['@attributes']['rel'] === NAMESPACE_ZOT) { $zot = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === NAMESPACE_DFRN) { $dfrn = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'salmon') { $notify = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === NAMESPACE_FEED) { $poll = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard') { $hcard = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://webfinger.net/rel/profile-page') { $profile = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://portablecontacts.net/spec/1.0') { $poco = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://joindiaspora.com/seed_location') { $diaspora_base = unamp($link['@attributes']['href']); $diaspora = true; } if ($link['@attributes']['rel'] === 'http://joindiaspora.com/guid') { $diaspora_guid = unamp($link['@attributes']['href']); $diaspora = true; } if ($link['@attributes']['rel'] === 'diaspora-public-key') { $diaspora_key = base64_decode(unamp($link['@attributes']['href'])); if (strstr($diaspora_key, 'RSA ')) { $pubkey = rsatopem($diaspora_key); } else { $pubkey = $diaspora_key; } $diaspora = true; } if ($link['@attributes']['rel'] === 'http://ostatus.org/schema/1.0/subscribe' and $mode == PROBE_NORMAL) { $diaspora = false; } } // Status.Net can have more than one profile URL. We need to match the profile URL // to a contact on incoming messages to prevent spam, and we won't know which one // to match. So in case of two, one of them is stored as an alias. Only store URL's // and not webfinger user@host aliases. If they've got more than two non-email style // aliases, let's hope we're lucky and get one that matches the feed author-uri because // otherwise we're screwed. foreach ($links as $link) { if ($link['@attributes']['rel'] === 'alias') { if (strpos($link['@attributes']['href'], '@') === false) { if (isset($profile)) { if ($link['@attributes']['href'] !== $profile) { $alias = unamp($link['@attributes']['href']); } } else { $profile = unamp($link['@attributes']['href']); } } } } // If the profile is different from the url then the url is abviously an alias if ($alias == "" and $profile != "" and !$at_addr and normalise_link($profile) != normalise_link($url)) { $alias = $url; } } elseif ($mode == PROBE_NORMAL) { // Check email $orig_url = $url; if (strpos($orig_url, '@') && validate_email($orig_url)) { $x = q("SELECT `prvkey` FROM `user` WHERE `uid` = %d LIMIT 1", intval(local_user())); $r = q("SELECT * FROM `mailacct` WHERE `uid` = %d AND `server` != '' LIMIT 1", intval(local_user())); if (count($x) && count($r)) { $mailbox = construct_mailbox_name($r[0]); $password = ''; openssl_private_decrypt(hex2bin($r[0]['pass']), $password, $x[0]['prvkey']); $mbox = email_connect($mailbox, $r[0]['user'], $password); if (!$mbox) { logger('probe_url: email_connect failed.'); } unset($password); } if ($mbox) { $msgs = email_poll($mbox, $orig_url); logger('probe_url: searching ' . $orig_url . ', ' . count($msgs) . ' messages found.', LOGGER_DEBUG); if (count($msgs)) { $addr = $orig_url; $network = NETWORK_MAIL; $name = substr($url, 0, strpos($url, '@')); $phost = substr($url, strpos($url, '@') + 1); $profile = 'http://' . $phost; // fix nick character range $vcard = array('fn' => $name, 'nick' => $name, 'photo' => avatar_img($url)); $notify = 'smtp ' . random_string(); $poll = 'email ' . random_string(); $priority = 0; $x = email_msg_meta($mbox, $msgs[0]); if (stristr($x[0]->from, $orig_url)) { $adr = imap_rfc822_parse_adrlist($x[0]->from, ''); } elseif (stristr($x[0]->to, $orig_url)) { $adr = imap_rfc822_parse_adrlist($x[0]->to, ''); } if (isset($adr)) { foreach ($adr as $feadr) { if (strcasecmp($feadr->mailbox, $name) == 0 && strcasecmp($feadr->host, $phost) == 0 && strlen($feadr->personal)) { $personal = imap_mime_header_decode($feadr->personal); $vcard['fn'] = ""; foreach ($personal as $perspart) { if ($perspart->charset != "default") { $vcard['fn'] .= iconv($perspart->charset, 'UTF-8//IGNORE', $perspart->text); } else { $vcard['fn'] .= $perspart->text; } } $vcard['fn'] = notags($vcard['fn']); } } } } imap_close($mbox); } } } } if ($mode == PROBE_NORMAL) { if (strlen($zot)) { $s = fetch_url($zot); if ($s) { $j = json_decode($s); if ($j) { $network = NETWORK_ZOT; $vcard = array('fn' => $j->fullname, 'nick' => $j->nickname, 'photo' => $j->photo); $profile = $j->url; $notify = $j->post; $pubkey = $j->pubkey; $poll = 'N/A'; } } } if (strlen($dfrn)) { $ret = scrape_dfrn($hcard ? $hcard : $dfrn, true); if (is_array($ret) && x($ret, 'dfrn-request')) { $network = NETWORK_DFRN; $request = $ret['dfrn-request']; $confirm = $ret['dfrn-confirm']; $notify = $ret['dfrn-notify']; $poll = $ret['dfrn-poll']; $vcard = array(); $vcard['fn'] = $ret['fn']; $vcard['nick'] = $ret['nick']; $vcard['photo'] = $ret['photo']; } } } if ($diaspora && $diaspora_base && $diaspora_guid) { if ($mode == PROBE_DIASPORA || !$notify) { $notify = $diaspora_base . 'receive/users/' . $diaspora_guid; $batch = $diaspora_base . 'receive/public'; } if (strpos($url, '@')) { $addr = str_replace('acct:', '', $url); } } if ($network !== NETWORK_ZOT && $network !== NETWORK_DFRN && $network !== NETWORK_MAIL) { if ($diaspora) { $network = NETWORK_DIASPORA; } elseif ($has_lrdd and $notify) { $network = NETWORK_OSTATUS; } if (strpos($url, '@')) { $addr = str_replace('acct:', '', $url); } $priority = 0; if ($hcard && !$vcard) { $vcard = scrape_vcard($hcard); // Google doesn't use absolute url in profile photos if (x($vcard, 'photo') && substr($vcard['photo'], 0, 1) == '/') { $h = @parse_url($hcard); if ($h) { $vcard['photo'] = $h['scheme'] . '://' . $h['host'] . $vcard['photo']; } } logger('probe_url: scrape_vcard: ' . print_r($vcard, true), LOGGER_DATA); } if ($diaspora && $addr) { // Diaspora returns the name as the nick. As the nick will never be updated, // let's use the Diaspora nickname (the first part of the handle) as the nick instead $addr_parts = explode('@', $addr); $vcard['nick'] = $addr_parts[0]; } /* if($twitter) { logger('twitter: setup'); $tid = basename($url); $tapi = 'https://api.twitter.com/1/statuses/user_timeline.rss'; if(intval($tid)) $poll = $tapi . '?user_id=' . $tid; else $poll = $tapi . '?screen_name=' . $tid; $profile = 'http://twitter.com/#!/' . $tid; //$vcard['photo'] = 'https://api.twitter.com/1/users/profile_image/' . $tid; $vcard['photo'] = 'https://api.twitter.com/1/users/profile_image?screen_name=' . $tid . '&size=bigger'; $vcard['nick'] = $tid; $vcard['fn'] = $tid; } */ if ($lastfm) { $profile = $url; $poll = str_replace(array('www.', 'last.fm/'), array('', 'ws.audioscrobbler.com/1.0/'), $url) . '/recenttracks.rss'; $vcard['nick'] = basename($url); $vcard['fn'] = $vcard['nick'] . t(' on Last.fm'); $network = NETWORK_FEED; } if (!x($vcard, 'fn')) { if (x($vcard, 'nick')) { $vcard['fn'] = $vcard['nick']; } } $check_feed = false; if (stristr($url, 'tumblr.com') && !stristr($url, '/rss')) { $poll = $url . '/rss'; $check_feed = true; // Will leave it to others to figure out how to grab the avatar, which is on the $url page in the open graph meta links } if ($appnet || !$poll) { $check_feed = true; } if (!isset($vcard) || !x($vcard, 'fn') || !$profile) { $check_feed = true; } if ($at_addr && !count($links)) { $check_feed = false; } if ($connectornetworks) { $check_feed = false; } if ($check_feed) { $feedret = scrape_feed($poll ? $poll : $url); logger('probe_url: scrape_feed ' . ($poll ? $poll : $url) . ' returns: ' . print_r($feedret, true), LOGGER_DATA); if (count($feedret) && ($feedret['feed_atom'] || $feedret['feed_rss'])) { $poll = x($feedret, 'feed_atom') ? unamp($feedret['feed_atom']) : unamp($feedret['feed_rss']); if (!x($vcard)) { $vcard = array(); } } if (x($feedret, 'photo') && !x($vcard, 'photo')) { $vcard['photo'] = $feedret['photo']; } require_once 'library/simplepie/simplepie.inc'; $feed = new SimplePie(); $xml = fetch_url($poll); logger('probe_url: fetch feed: ' . $poll . ' returns: ' . $xml, LOGGER_DATA); $a = get_app(); logger('probe_url: scrape_feed: headers: ' . $a->get_curl_headers(), LOGGER_DATA); // Don't try and parse an empty string $feed->set_raw_data($xml ? $xml : '<?xml version="1.0" encoding="utf-8" ?><xml></xml>'); $feed->init(); if ($feed->error()) { logger('probe_url: scrape_feed: Error parsing XML: ' . $feed->error()); $network = NETWORK_PHANTOM; } if (!x($vcard, 'photo')) { $vcard['photo'] = $feed->get_image_url(); } $author = $feed->get_author(); if ($author) { $vcard['fn'] = unxmlify(trim($author->get_name())); if (!$vcard['fn']) { $vcard['fn'] = trim(unxmlify($author->get_email())); } if (strpos($vcard['fn'], '@') !== false) { $vcard['fn'] = substr($vcard['fn'], 0, strpos($vcard['fn'], '@')); } $email = unxmlify($author->get_email()); if (!$profile && $author->get_link()) { $profile = trim(unxmlify($author->get_link())); } if (!$vcard['photo']) { $rawtags = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); if ($rawtags) { $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; if (x($elems, 'link') && $elems['link'][0]['attribs']['']['rel'] === 'photo') { $vcard['photo'] = $elems['link'][0]['attribs']['']['href']; } } } // Fetch fullname via poco:displayName $pocotags = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); if ($pocotags) { $elems = $pocotags[0]['child']['http://portablecontacts.net/spec/1.0']; if (isset($elems["displayName"])) { $vcard['fn'] = $elems["displayName"][0]["data"]; } if (isset($elems["preferredUsername"])) { $vcard['nick'] = $elems["preferredUsername"][0]["data"]; } } } else { $item = $feed->get_item(0); if ($item) { $author = $item->get_author(); if ($author) { $vcard['fn'] = trim(unxmlify($author->get_name())); if (!$vcard['fn']) { $vcard['fn'] = trim(unxmlify($author->get_email())); } if (strpos($vcard['fn'], '@') !== false) { $vcard['fn'] = substr($vcard['fn'], 0, strpos($vcard['fn'], '@')); } $email = unxmlify($author->get_email()); if (!$profile && $author->get_link()) { $profile = trim(unxmlify($author->get_link())); } } if (!$vcard['photo']) { $rawmedia = $item->get_item_tags('http://search.yahoo.com/mrss/', 'thumbnail'); if ($rawmedia && $rawmedia[0]['attribs']['']['url']) { $vcard['photo'] = unxmlify($rawmedia[0]['attribs']['']['url']); } } if (!$vcard['photo']) { $rawtags = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); if ($rawtags) { $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; if (x($elems, 'link') && $elems['link'][0]['attribs']['']['rel'] === 'photo') { $vcard['photo'] = $elems['link'][0]['attribs']['']['href']; } } } } } // Workaround for misconfigured Friendica servers if ($network == "" and strstr($url, "/profile/")) { $noscrape = str_replace("/profile/", "/noscrape/", $url); $noscrapejson = fetch_url($noscrape); if ($noscrapejson) { $network = NETWORK_DFRN; $poco = str_replace("/profile/", "/poco/", $url); $noscrapedata = json_decode($noscrapejson, true); if (isset($noscrapedata["addr"])) { $addr = $noscrapedata["addr"]; } if (isset($noscrapedata["fn"])) { $vcard["fn"] = $noscrapedata["fn"]; } if (isset($noscrapedata["key"])) { $pubkey = $noscrapedata["key"]; } if (isset($noscrapedata["photo"])) { $vcard["photo"] = $noscrapedata["photo"]; } if (isset($noscrapedata["dfrn-request"])) { $request = $noscrapedata["dfrn-request"]; } if (isset($noscrapedata["dfrn-confirm"])) { $confirm = $noscrapedata["dfrn-confirm"]; } if (isset($noscrapedata["dfrn-notify"])) { $notify = $noscrapedata["dfrn-notify"]; } if (isset($noscrapedata["dfrn-poll"])) { $poll = $noscrapedata["dfrn-poll"]; } } } if (!$vcard['photo'] && strlen($email)) { $vcard['photo'] = avatar_img($email); } if ($poll === $profile) { $lnk = $feed->get_permalink(); } if (isset($lnk) && strlen($lnk)) { $profile = $lnk; } if (!$network) { $network = NETWORK_FEED; // If it is a feed, don't take the author name as feed name unset($vcard['fn']); } if (!x($vcard, 'fn')) { $vcard['fn'] = notags($feed->get_title()); } if (!x($vcard, 'fn')) { $vcard['fn'] = notags($feed->get_description()); } if (strpos($vcard['fn'], 'Twitter / ') !== false) { $vcard['fn'] = substr($vcard['fn'], strpos($vcard['fn'], '/') + 1); $vcard['fn'] = trim($vcard['fn']); } if (!x($vcard, 'nick')) { $vcard['nick'] = strtolower(notags(unxmlify($vcard['fn']))); if (strpos($vcard['nick'], ' ')) { $vcard['nick'] = trim(substr($vcard['nick'], 0, strpos($vcard['nick'], ' '))); } } if (!$priority) { $priority = 2; } } } if (!x($vcard, 'photo')) { $a = get_app(); $vcard['photo'] = $a->get_baseurl() . '/images/person-175.jpg'; } if (!$profile) { $profile = $url; } // No human could be associated with this link, use the URL as the contact name if ($network === NETWORK_FEED && $poll && !x($vcard, 'fn')) { $vcard['fn'] = $url; } if ($notify != "" and $poll != "") { $baseurl = matching(normalise_link($notify), normalise_link($poll)); $baseurl2 = matching($baseurl, normalise_link($profile)); if ($baseurl2 != "") { $baseurl = $baseurl2; } } if ($baseurl == "" and $notify != "") { $baseurl = matching(normalise_link($profile), normalise_link($notify)); } if ($baseurl == "" and $poll != "") { $baseurl = matching(normalise_link($profile), normalise_link($poll)); } $baseurl = rtrim($baseurl, "/"); if (strpos($url, '@') and $addr == "" and $network == NETWORK_DFRN) { $addr = str_replace('acct:', '', $url); } $vcard['fn'] = notags($vcard['fn']); $vcard['nick'] = str_replace(' ', '', notags($vcard['nick'])); $result['name'] = $vcard['fn']; $result['nick'] = $vcard['nick']; $result['url'] = $profile; $result['addr'] = $addr; $result['batch'] = $batch; $result['notify'] = $notify; $result['poll'] = $poll; $result['request'] = $request; $result['confirm'] = $confirm; $result['poco'] = $poco; $result['photo'] = $vcard['photo']; $result['priority'] = $priority; $result['network'] = $network; $result['alias'] = $alias; $result['pubkey'] = $pubkey; $result['baseurl'] = $baseurl; logger('probe_url: ' . print_r($result, true), LOGGER_DEBUG); if ($level == 1) { // Trying if it maybe a diaspora account if ($result['network'] == NETWORK_FEED or $result['addr'] == "") { require_once 'include/bbcode.php'; $address = GetProfileUsername($url, "", true); $result2 = probe_url($address, $mode, ++$level); if ($result2['network'] != "") { $result = $result2; } } // Maybe it's some non standard GNU Social installation (Single user, subfolder or no uri rewrite) if ($result['network'] == NETWORK_FEED and $result['baseurl'] != "" and $result['nick'] != "") { $addr = $result['nick'] . '@' . str_replace("http://", "", $result['baseurl']); $result2 = probe_url($addr, $mode, ++$level); if ($result2['network'] != "" and $result2['network'] != NETWORK_FEED) { $result = $result2; } } } // Only store into the cache if the value seems to be valid if ($result['network'] != NETWORK_PHANTOM) { Cache::set("probe_url:" . $mode . ":" . $url, serialize($result), CACHE_DAY); } return $result; }