workflow for federated/non-dfrn followers

This commit is contained in:
Mike Macgirvin 2010-10-24 20:39:24 -07:00
parent b8b227b328
commit b41218ca30
16 changed files with 670 additions and 382 deletions

View file

@ -52,10 +52,12 @@ define ( 'NAMESPACE_DFRN' , 'http://purl.org/macgirvin/dfrn/1.0' );
define ( 'NAMESPACE_THREAD' , 'http://purl.org/syndication/thread/1.0' ); define ( 'NAMESPACE_THREAD' , 'http://purl.org/syndication/thread/1.0' );
define ( 'NAMESPACE_TOMB' , 'http://purl.org/atompub/tombstones/1.0' ); define ( 'NAMESPACE_TOMB' , 'http://purl.org/atompub/tombstones/1.0' );
define ( 'NAMESPACE_ACTIVITY', 'http://activitystrea.ms/spec/1.0/' ); define ( 'NAMESPACE_ACTIVITY', 'http://activitystrea.ms/spec/1.0/' );
define ( 'NAMESPACE_ACTIVITY_SCHEMA', 'http://activitystrea.ms/schema/1.0/'); define ( 'NAMESPACE_ACTIVITY_SCHEMA', 'http://activitystrea.ms/schema/1.0/' );
define ( 'NAMESPACE_SALMON_ME', 'http://salmon-protocol.org/ns/magic-env'); define ( 'NAMESPACE_SALMON_ME', 'http://salmon-protocol.org/ns/magic-env' );
define ( 'NAMESPACE_OSTATUSSUB', 'http://ostatus.org/schema/1.0/subscribe'); define ( 'NAMESPACE_OSTATUSSUB', 'http://ostatus.org/schema/1.0/subscribe' );
define ( 'NAMESPACE_GEORSS', 'http://www.georss.org/georss'); define ( 'NAMESPACE_GEORSS', 'http://www.georss.org/georss' );
define ( 'NAMESPACE_POCO', 'http://portablecontacts.net/spec/1.0' );
define ( 'NAMESPACE_FEED', 'http://schemas.google.com/g/2010#updates-from' );
// activity stream defines // activity stream defines
@ -961,11 +963,11 @@ function webfinger($s) {
if(strlen($host)) { if(strlen($host)) {
$tpl = fetch_lrdd_template($host); $tpl = fetch_lrdd_template($host);
if(strlen($tpl)) { if(strlen($tpl)) {
$pxrd = str_replace('{uri}', urlencode('acct://'.$s), $tpl); $pxrd = str_replace('{uri}', urlencode('acct:'.$s), $tpl);
$links = fetch_xrd_links($pxrd); $links = fetch_xrd_links($pxrd);
if(! count($links)) { if(! count($links)) {
// try without the double slashes // try with double slashes
$pxrd = str_replace('{uri}', urlencode('acct:'.$s), $tpl); $pxrd = str_replace('{uri}', urlencode('acct://'.$s), $tpl);
$links = fetch_xrd_links($pxrd); $links = fetch_xrd_links($pxrd);
} }
return $links; return $links;
@ -974,6 +976,54 @@ function webfinger($s) {
return array(); return array();
}} }}
if(! function_exists('lrdd')) {
function lrdd($uri) {
$a = get_app();
if(strstr($uri,'@')) {
return(webfinger($uri));
}
else {
$html = fetch_url($uri);
$headers = $a->get_curl_headers();
$lines = explode("\n",$headers);
if(count($lines)) {
foreach($lines as $line) {
// TODO alter the following regex to support multiple relations (space separated)
if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
$link = $matches[1];
break;
}
}
}
if(! isset($link)) {
// parse the page of the supplied URL looking for rel links
require_once('library/HTML5/Parser.php');
$dom = HTML5_Parser::parse($html);
if($dom) {
$items = $dom->getElementsByTagName('link');
foreach($items as $item) {
$x = $item->getAttribute('rel');
if($x == "lrdd") {
$link = $item->getAttribute('href');
break;
}
}
}
}
if(isset($link))
return(fetch_xrd_links($link));
}
return array();
}}
// Given a host name, locate the LRDD template from that // Given a host name, locate the LRDD template from that
// host. Returns the LRDD template or an empty string on // host. Returns the LRDD template or an empty string on
// error/failure. // error/failure.

View file

@ -267,28 +267,89 @@ function construct_activity($item) {
function get_atom_elements($item) { function get_atom_elements($feed,$item) {
require_once('library/HTMLPurifier.auto.php'); require_once('library/HTMLPurifier.auto.php');
require_once('include/html2bbcode.php'); require_once('include/html2bbcode.php');
$res = array(); $best_photo = array();
$raw_author = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author'); $res = array();
if($raw_author) {
if($raw_author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'][0]['attribs']['']['rel'] === 'photo')
$res['author-avatar'] = unxmlify($raw_author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'][0]['attribs']['']['href']);
}
$author = $item->get_author(); $author = $item->get_author();
$res['author-name'] = unxmlify($author->get_name()); $res['author-name'] = unxmlify($author->get_name());
$res['author-link'] = unxmlify($author->get_link()); $res['author-link'] = unxmlify($author->get_link());
if(! $res['author-avatar'])
$res['author-avatar'] = unxmlify($author->get_avatar());
$res['uri'] = unxmlify($item->get_id()); $res['uri'] = unxmlify($item->get_id());
$res['title'] = unxmlify($item->get_title()); $res['title'] = unxmlify($item->get_title());
$res['body'] = unxmlify($item->get_content()); $res['body'] = unxmlify($item->get_content());
// look for a photo. We should check media size and find the best one,
// but for now let's just find any author photo
$rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
$base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
foreach($base as $link) {
if(! $res['author-avatar']) {
if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
$res['author-avatar'] = unxmlify($link['attribs']['']['href']);
}
}
}
$rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
if($rawactor && $rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'] === ACTIVITY_OBJ_PERSON) {
$base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
if($base && count($base)) {
foreach($base as $link) {
if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
$res['author-link'] = unxmlify($link['attribs']['']['href']);
if(! $res['author-avatar']) {
if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
$res['author-avatar'] = unxmlify($link['attribs']['']['href']);
}
}
}
}
// No photo/profile-link on the item - look at the feed level
if((! $res['author-link']) || (! $res['author-avatar'])) {
$rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
$base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
foreach($base as $link) {
if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
$res['author-link'] = unxmlify($link['attribs']['']['href']);
if(! $res['author-avatar']) {
if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
$res['author-avatar'] = unxmlify($link['attribs']['']['href']);
}
}
}
$rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
if($rawactor && $rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'] === ACTIVITY_OBJ_PERSON) {
$base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
if($base && count($base)) {
foreach($base as $link) {
if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
$res['author-link'] = unxmlify($link['attribs']['']['href']);
if(! $res['author-avatar']) {
if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
$res['author-avatar'] = unxmlify($link['attribs']['']['href']);
}
}
}
}
}
$maxlen = get_max_import_size(); $maxlen = get_max_import_size();
if($maxlen && (strlen($res['body']) > $maxlen)) if($maxlen && (strlen($res['body']) > $maxlen))
$res['body'] = substr($res['body'],0, $maxlen); $res['body'] = substr($res['body'],0, $maxlen);
@ -310,7 +371,7 @@ function get_atom_elements($item) {
'[youtube]$1[/youtube]', $res['body']); '[youtube]$1[/youtube]', $res['body']);
$config = HTMLPurifier_Config::createDefault(); $config = HTMLPurifier_Config::createDefault();
$config->set('Core.DefinitionCache', null); $config->set('Cache.DefinitionImpl', null);
// we shouldn't need a whitelist, because the bbcode converter // we shouldn't need a whitelist, because the bbcode converter
// will strip out any unsupported tags. // will strip out any unsupported tags.
@ -353,27 +414,21 @@ function get_atom_elements($item) {
elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']) elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
$res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']); $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'][0]['attribs']['']['rel'] === 'photo') if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
$res['owner-avatar'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'][0]['attribs']['']['href']); $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
elseif($rawowner[0]['child'][NAMESPACE_DFRN]['avatar'][0]['data'])
$res['owner-avatar'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']); foreach($base as $link) {
if(! $res['owner-avatar']) {
if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
$res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
}
}
}
$rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point'); $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
if($rawgeo) if($rawgeo)
$res['coord'] = unxmlify($rawgeo[0]['data']); $res['coord'] = unxmlify($rawgeo[0]['data']);
$rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
if($rawactor && $rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'] === ACTIVITY_OBJ_PERSON) {
$base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
if($base && count($base)) {
foreach($base as $link) {
if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
$res['author-link'] = unxmlify($link['attribs']['']['href']);
if($link['attribs']['']['rel'] === 'avatar' && (! $res['author-avatar']))
$res['author-avatar'] = unxmlify($link['attribs']['']['href']);
}
}
}
$rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb'); $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
// select between supported verbs // select between supported verbs
@ -405,7 +460,7 @@ function get_atom_elements($item) {
'[youtube]$1[/youtube]', $body); '[youtube]$1[/youtube]', $body);
$config = HTMLPurifier_Config::createDefault(); $config = HTMLPurifier_Config::createDefault();
$config->set('Core.DefinitionCache', null); $config->set('Cache.DefinitionImpl', null);
$purifier = new HTMLPurifier($config); $purifier = new HTMLPurifier($config);
$body = $purifier->purify($body); $body = $purifier->purify($body);
@ -423,9 +478,6 @@ function get_atom_elements($item) {
function item_store($arr) { function item_store($arr) {
//print_r($arr);
if($arr['gravity']) if($arr['gravity'])
$arr['gravity'] = intval($arr['gravity']); $arr['gravity'] = intval($arr['gravity']);
elseif($arr['parent-uri'] == $arr['uri']) elseif($arr['parent-uri'] == $arr['uri'])
@ -648,6 +700,7 @@ function consume_feed($xml,$importer,$contact, &$hub) {
$feed->init(); $feed->init();
// Check at the feed level for updated contact name and/or photo // Check at the feed level for updated contact name and/or photo
$debugging = get_config('system','debugging');
$name_updated = ''; $name_updated = '';
$new_name = ''; $new_name = '';
@ -832,11 +885,13 @@ function consume_feed($xml,$importer,$contact, &$hub) {
} }
continue; continue;
} }
$datarray = get_atom_elements($item); $datarray = get_atom_elements($feed,$item);
if($contact['network'] === 'stat' && strlen($datarray['title']))
unset($datarray['title']);
$datarray['parent-uri'] = $parent_uri; $datarray['parent-uri'] = $parent_uri;
$datarray['uid'] = $importer['uid']; $datarray['uid'] = $importer['uid'];
$datarray['contact-id'] = $contact['id']; $datarray['contact-id'] = $contact['id'];
if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) { if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
$datarray['type'] = 'activity'; $datarray['type'] = 'activity';
$datarray['gravity'] = GRAVITY_LIKE; $datarray['gravity'] = GRAVITY_LIKE;
} }
@ -865,17 +920,23 @@ function consume_feed($xml,$importer,$contact, &$hub) {
} }
continue; continue;
} }
$datarray = get_atom_elements($item); $datarray = get_atom_elements($feed,$item);
if(($datarray['verb'] === ACTIVITY_FOLLOW) && (! is_array($contact))) {
new_follower($importer,$datarray); if($datarray['verb'] === ACTIVITY_FOLLOW) {
if($debugging)
file_put_contents('salmon.out',"\n" . 'New follower.' . "\n", FILE_APPEND);
new_follower($importer,$contact,$datarray,$item);
return; return;
} }
if($datarray['verb'] === ACTIVITY_UNFOLLOW) { if($datarray['verb'] === ACTIVITY_UNFOLLOW) {
lose_follower($importer,$contact,$datarray); lose_follower($importer,$contact,$datarray,$item);
return; return;
} }
if(! is_array($contact)) if(! is_array($contact))
return; return;
if($contact['network'] === 'stat' && strlen($datarray['title']))
unset($datarray['title']);
$datarray['parent-uri'] = $item_id; $datarray['parent-uri'] = $item_id;
$datarray['uid'] = $importer['uid']; $datarray['uid'] = $importer['uid'];
$datarray['contact-id'] = $contact['id']; $datarray['contact-id'] = $contact['id'];
@ -888,14 +949,76 @@ function consume_feed($xml,$importer,$contact, &$hub) {
} }
function new_follower($importer,$datarray) { function new_follower($importer,$contact,$datarray,$item) {
$url = notags(trim($datarray['author-link']));
$name = notags(trim($datarray['author-name']));
$photo = notags(trim($datarray['author-avatar']));
$rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
$nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
if(is_array($contact)) {
if($contact['network'] == 'stat' && $contact['rel'] == REL_FAN) {
$q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d AND `uid` = %d LIMIT 1",
intval(REL_BUD),
intval($contact['id']),
intval($importer['uid'])
);
}
// send email notification to owner?
}
else {
// create contact record - set to readonly
$r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `name`, `nick`, `photo`, `network`, `rel`,
`blocked`, `readonly`, `pending` )
VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 1, 1 ) ",
intval($importer['uid']),
dbesc(datetime_convert()),
dbesc($url),
dbesc($name),
dbesc($nick),
dbesc($photo),
dbesc('stat'),
intval(REL_VIP)
);
$r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 AND `rel` = %d LIMIT 1",
intval($importer['uid']),
dbesc($url),
intval(REL_VIP)
);
if(count($r))
$contact_record = $r[0];
// create notification
$hash = random_string();
if(is_array($contact_record)) {
$ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
VALUES ( %d, %d, 0, 0, '%s', '%s' )",
intval($importer['uid']),
intval($contact_record['id']),
dbesc($hash),
dbesc(datetime_convert())
);
}
}
} }
function lose_follower($importer,$contact,$datarray) { function lose_follower($importer,$contact,$datarray,$item) {
if($contact['rel'] == REL_BUD) {
q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d LIMIT 1",
intval(REL_FAN),
intval($contact['id'])
);
}
else {
contact_remove($contact['id']);
}
} }

View file

@ -87,6 +87,9 @@
// If this is a public conversation, notify the feed hub // If this is a public conversation, notify the feed hub
$notify_hub = true; $notify_hub = true;
// fill this in with a salmon slap if applicable
$slap = '';
if($cmd != 'mail') { if($cmd != 'mail') {
require_once('include/group.php'); require_once('include/group.php');
@ -197,7 +200,7 @@
$actobj = construct_activity($item); $actobj = construct_activity($item);
if($item['id'] == $item_id) { if($item['id'] == $item_id) {
$atom .= replace_macros($cmnt_template, array( $slap = replace_macros($cmnt_template, array(
'$name' => xmlify($owner['name']), '$name' => xmlify($owner['name']),
'$profile_page' => xmlify($owner['url']), '$profile_page' => xmlify($owner['url']),
'$thumb' => xmlify($owner['thumb']), '$thumb' => xmlify($owner['thumb']),
@ -220,6 +223,7 @@
)); ));
} }
} }
$atom .= $slap;
} }
else { else {
foreach($items as $item) { foreach($items as $item) {
@ -303,6 +307,8 @@
// delivery loop // delivery loop
foreach($r as $contact) { foreach($r as $contact) {
if($contact['self']) if($contact['self'])
continue; continue;
@ -314,6 +320,10 @@
$deliver_status = dfrn_deliver($owner,$contact,$atom,$debugging); $deliver_status = dfrn_deliver($owner,$contact,$atom,$debugging);
break; break;
default: default:
if($followup) {
require_once('include/salmon.php');
slapper($owner,$contact,$slap);
}
break; break;
} }

View file

@ -22,7 +22,8 @@
$a->set_baseurl(get_config('system','url')); $a->set_baseurl(get_config('system','url'));
$contacts = q("SELECT * FROM `contact` $contacts = q("SELECT * FROM `contact`
WHERE `network` = 'dfrn' AND ( `dfrn-id` != '' OR (`issued-id` != '' AND `duplex` = 1)) WHERE ( ( `network` = 'dfrn' AND ( `dfrn-id` != '' OR (`issued-id` != '' AND `duplex` = 1)))
OR ( `network` = 'stat' AND `poll` != '' ) )
AND `self` = 0 AND `blocked` = 0 AND `readonly` = 0 ORDER BY RAND()"); AND `self` = 0 AND `blocked` = 0 AND `readonly` = 0 ORDER BY RAND()");
if(! count($contacts)) if(! count($contacts))
@ -88,6 +89,8 @@
: datetime_convert('UTC','UTC',$contact['last-update'], ATOM_TIME) : datetime_convert('UTC','UTC',$contact['last-update'], ATOM_TIME)
); );
if($contact['network'] === 'dfrn') {
$idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']); $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
if(intval($contact['duplex']) && $contact['dfrn-id']) if(intval($contact['duplex']) && $contact['dfrn-id'])
@ -139,7 +142,6 @@
if(($contact['duplex']) && strlen($contact['prvkey'])) { if(($contact['duplex']) && strlen($contact['prvkey'])) {
openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']); openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']); openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
} }
else { else {
openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']); openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
@ -161,6 +163,12 @@
$postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION; $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
$xml = post_url($contact['poll'],$postvars); $xml = post_url($contact['poll'],$postvars);
}
else {
// $contact['network'] !== 'dfrn'
$xml = fetch_url($contact['poll']);
}
if($debugging) { if($debugging) {
echo "XML response:" . $xml . "\n"; echo "XML response:" . $xml . "\n";
@ -170,10 +178,8 @@
if(! strlen($xml)) if(! strlen($xml))
continue; continue;
consume_feed($xml,$importer,$contact,$hub); consume_feed($xml,$importer,$contact,$hub);
if((strlen($hub)) && ($contact['rel'] == REL_BUD) && ($contact['priority'] == 0)) { if((strlen($hub)) && ($contact['rel'] == REL_BUD) && ($contact['priority'] == 0)) {
$hubs = explode(',', $hub); $hubs = explode(',', $hub);
if(count($hubs)) { if(count($hubs)) {
@ -192,6 +198,7 @@
intval($contact['id']) intval($contact['id'])
); );
// loop - next contact
} }
killme(); killme();

View file

@ -33,61 +33,9 @@ function get_salmon_key($uri,$keyhash) {
if($debugging) if($debugging)
file_put_contents('salmon.out', "\n" . 'Fetch key' . "\n", FILE_APPEND); file_put_contents('salmon.out', "\n" . 'Fetch key' . "\n", FILE_APPEND);
if(strstr($uri,'@')) { $arr = lrdd($uri);
$arr = webfinger($uri);
if($debugging)
file_put_contents('salmon.out', "\n" . 'Fetch key from webfinger' . "\n", FILE_APPEND);
}
else {
$html = fetch_url($uri);
$a = get_app();
$h = $a->get_curl_headers();
if($debugging)
file_put_contents('salmon.out', "\n" . 'Fetch key via HTTP header: ' . $h . "\n", FILE_APPEND);
$l = explode("\n",$h); if(is_array($arr)) {
if(count($l)) {
foreach($l as $line) {
// TODO alter the following regex to support multiple relations (space separated)
if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
$link = $matches[1];
if($debugging)
file_put_contents('salmon.out', "\n" . 'Fetch key via HTML Link: ' . $link . "\n", FILE_APPEND);
break;
}
}
}
if(! isset($link)) {
// parse the page of the supplied URL looking for rel links
require_once('library/HTML5/Parser.php');
$dom = HTML5_Parser::parse($html);
if(! $dom)
return '';
$items = $dom->getElementsByTagName('link');
foreach($items as $item) {
$x = $item->getAttribute('rel');
if($x == "lrdd") {
$link = $item->getAttribute('href');
if($debugging)
file_put_contents('salmon.out', "\n" . 'Fetch key via HTML body' . $link . "\n", FILE_APPEND);
break;
}
}
}
if(! isset($link))
return '';
$arr = fetch_xrd_links($link);
}
if($arr) {
foreach($arr as $a) { foreach($arr as $a) {
if($a['@attributes']['rel'] === 'magic-public-key') { if($a['@attributes']['rel'] === 'magic-public-key') {
$ret[] = $a['@attributes']['href']; $ret[] = $a['@attributes']['href'];
@ -140,4 +88,60 @@ function get_salmon_key($uri,$keyhash) {
function slapper($owner,$contact,$slap) {
// does contact have a salmon endpoint?
if(! strlen($contact['notify']))
return;
// add all namespaces to item
$namespaces = <<< EOT
<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xmlns:at="http://purl.org/atompub/tombstones/1.0"
xmlns:media="http://purl.org/syndication/atommedia"
xmlns:dfrn="http://purl.org/macgirvin/dfrn/1.0"
xmlns:as="http://activitystrea.ms/spec/1.0/"
xmlns:georss="http://www.georss.org/georss" >
EOT;
$slap = str_replace('<entry>',$namespaces,$slap);
// create a magic envelope
$data = base64url_encode($slap);
$data_type = 'application/atom+xml';
$encoding = 'base64url';
$algorithm = 'RSA-SHA256';
$keyhash = base64url_encode(hash('sha256',salmon_key($owner['spubkey'])));
// Setup RSA stuff to PKCS#1 sign the data
set_include_path(get_include_path() . PATH_SEPARATOR . 'phpsec');
require_once('phpsec/Crypt/RSA.php');
$rsa = new CRYPT_RSA();
$rsa->signatureMode = CRYPT_RSA_SIGNATURE_PKCS1;
$rsa->setHash('sha256');
$rsa->loadKey($owner['sprvkey']);
$signature = $rsa->sign($data);
$salmon_tpl = load_view_file('view/magicsig.tpl');
$salmon = replace_macros($salmon_tpl,array(
'$data' => $data,
'$encoding' => $encoding,
'$algorithm' => $algorithm,
'$keyhash' => $keyhash,
'$signature' => $signature
));
// slap them
post_url($contact['notify'],$salmon);
return;
}

View file

@ -271,7 +271,6 @@ function contacts_content(&$a) {
default: default:
break; break;
} }
if(($rr['network'] === 'dfrn') && ($rr['rel'])) { if(($rr['network'] === 'dfrn') && ($rr['rel'])) {
$url = "redir/{$rr['id']}"; $url = "redir/{$rr['id']}";
$sparkle = ' class="sparkle" '; $sparkle = ' class="sparkle" ';

View file

@ -7,7 +7,7 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
if(is_array($handsfree)) { if(is_array($handsfree)) {
// called directly from dfrn_request due to automatic friend acceptance // called directly from dfrn_request due to automatic friend acceptance
// any $_POST parameters we might need are supplied in the $handsfree array // any $_POST parameters we may require are supplied in the $handsfree array
$node = $handsfree['node']; $node = $handsfree['node'];
$a->interactive = false; // notice() becomes a no-op since nobody is there to see it $a->interactive = false; // notice() becomes a no-op since nobody is there to see it
@ -19,7 +19,7 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
} }
// Main entry point. Our user received a friend request notification (perhaps // Main entry point. Our user received a friend request notification (perhaps
// from another site) and clicked 'Accept'. $POST['source_url'] is not set. // from another site) and clicked 'Approve'. $POST['source_url'] is not set.
// OR we have been called directly from dfrn_request ($handsfree != null) due to // OR we have been called directly from dfrn_request ($handsfree != null) due to
// this being a page type which supports automatic friend acceptance. // this being a page type which supports automatic friend acceptance.
@ -42,7 +42,7 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
} }
// These come from the friend request notification form or $handsfree reply. // These come from either the friend request notification form or $handsfree array.
if(is_array($handsfree)) { if(is_array($handsfree)) {
$dfrn_id = $handsfree['dfrn_id']; $dfrn_id = $handsfree['dfrn_id'];
@ -53,13 +53,16 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
$dfrn_id = ((x($_POST,'dfrn_id')) ? notags(trim($_POST['dfrn_id'])) : ""); $dfrn_id = ((x($_POST,'dfrn_id')) ? notags(trim($_POST['dfrn_id'])) : "");
$intro_id = intval($_POST['intro_id']); $intro_id = intval($_POST['intro_id']);
$duplex = intval($_POST['duplex']); $duplex = intval($_POST['duplex']);
$cid = intval($_POST['contact_id']);
} }
// The other person will have been issued an ID when they first requested friendship. // The other person will have been issued an ID when they first requested friendship.
// Locate their record. At this time, their record will have both pending and blocked set to 1. // Locate their record. At this time, their record will have both pending and blocked set to 1.
// There won't be any dfrn_id if this is a network follower, so use the contact_id instead.
$r = q("SELECT * FROM `contact` WHERE `issued-id` = '%s' AND `uid` = %d LIMIT 1", $r = q("SELECT * FROM `contact` WHERE ( ( `issued-id` != '' AND `issued-id` = '%s' ) OR ( `id` = %d AND `id` != 0 ) ) AND `uid` = %d LIMIT 1",
dbesc($dfrn_id), dbesc($dfrn_id),
intval($cid),
intval($uid) intval($uid)
); );
@ -68,12 +71,16 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
return; return;
} }
$contact_id = $r[0]['id']; $contact = $r[0];
$relation = $r[0]['rel'];
$site_pubkey = $r[0]['site-pubkey'];
$dfrn_confirm = $r[0]['confirm'];
$aes_allow = $r[0]['aes_allow'];
$contact_id = $contact['id'];
$relation = $contact['rel'];
$site_pubkey = $contact['site-pubkey'];
$dfrn_confirm = $contact['confirm'];
$aes_allow = $contact['aes_allow'];
if($contact['network'] === 'dfrn') {
// Generate a key pair for all further communications with this person. // Generate a key pair for all further communications with this person.
// We have a keypair for every contact, and a site key for unknown people. // We have a keypair for every contact, and a site key for unknown people.
@ -214,6 +221,7 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
if($status != 0) if($status != 0)
return; return;
}
// We have now established a relationship with the other site. // We have now established a relationship with the other site.
// Let's make our own personal copy of their profile photo so we don't have // Let's make our own personal copy of their profile photo so we don't have
@ -223,12 +231,8 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
$photo_failure = false; $photo_failure = false;
$r = q("SELECT `photo` FROM `contact` WHERE `id` = %d LIMIT 1", $filename = basename($contact['photo']);
intval($contact_id)); $img_str = fetch_url($contact['photo'],true);
if(count($r)) {
$filename = basename($r[0]['photo']);
$img_str = fetch_url($r[0]['photo'],true);
$img = new Photo($img_str); $img = new Photo($img_str);
if($img->is_valid()) { if($img->is_valid()) {
@ -253,15 +257,15 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
} }
else else
$photo_failure = true; $photo_failure = true;
}
else
$photo_failure = true;
if($photo_failure) { if($photo_failure) {
$photo = $a->get_baseurl() . '/images/default-profile.jpg'; $photo = $a->get_baseurl() . '/images/default-profile.jpg';
$thumb = $a->get_baseurl() . '/images/default-profile-sm.jpg'; $thumb = $a->get_baseurl() . '/images/default-profile-sm.jpg';
} }
if($contact['network'] === 'dfrn') {
$new_relation = REL_VIP; $new_relation = REL_VIP;
if(($relation == REL_FAN) || ($duplex)) if(($relation == REL_FAN) || ($duplex))
$new_relation = REL_BUD; $new_relation = REL_BUD;
@ -286,6 +290,51 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
intval($duplex), intval($duplex),
intval($contact_id) intval($contact_id)
); );
}
else {
$notify = '';
$poll = '';
// $contact['network'] !== 'dfrn'
$arr = lrdd($contact['url']);
if(count($arr)) {
foreach($arr as $link) {
if($link['@attributes']['rel'] === 'salmon')
$notify = $link['@attributes']['href'];
if($link['@attributes']['rel'] === NAMESPACE_FEED)
$poll = $link['@attributes']['href'];
}
}
$r = q("DELETE FROM `intro` WHERE `id` = %d AND `uid` = %d LIMIT 1",
intval($intro_id),
intval($uid)
);
$r = q("UPDATE `contact` SET `photo` = '%s',
`thumb` = '%s',
`name-date` = '%s',
`uri-date` = '%s',
`avatar-date` = '%s',
`notify` = '%s',
`poll` = '%s',
`blocked` = 0,
`pending` = 0
WHERE `id` = %d LIMIT 1
",
dbesc($photo),
dbesc($thumb),
dbesc(datetime_convert()),
dbesc(datetime_convert()),
dbesc(datetime_convert()),
dbesc($notify),
dbesc($poll),
intval($contact_id)
);
}
if($r === false) if($r === false)
notice( t('Unable to set contact photo.') . EOL); notice( t('Unable to set contact photo.') . EOL);
@ -296,6 +345,7 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
if($handsfree === null) if($handsfree === null)
goaway($a->get_baseurl() . '/contacts/' . intval($contact_id)); goaway($a->get_baseurl() . '/contacts/' . intval($contact_id));
return; //NOTREACHED return; //NOTREACHED
} }

View file

@ -205,7 +205,7 @@ function dfrn_notify_post(&$a) {
if($is_reply) { if($is_reply) {
if($feed->get_item_quantity() == 1) { if($feed->get_item_quantity() == 1) {
// remote reply to our post. Import and then notify everybody else. // remote reply to our post. Import and then notify everybody else.
$datarray = get_atom_elements($item); $datarray = get_atom_elements($feed,$item);
$datarray['type'] = 'remote-comment'; $datarray['type'] = 'remote-comment';
$datarray['wall'] = 1; $datarray['wall'] = 1;
$datarray['parent-uri'] = $parent_uri; $datarray['parent-uri'] = $parent_uri;
@ -287,7 +287,7 @@ function dfrn_notify_post(&$a) {
} }
continue; continue;
} }
$datarray = get_atom_elements($item); $datarray = get_atom_elements($feed,$item);
$datarray['parent-uri'] = $parent_uri; $datarray['parent-uri'] = $parent_uri;
$datarray['uid'] = $importer['importer_uid']; $datarray['uid'] = $importer['importer_uid'];
$datarray['contact-id'] = $importer['id']; $datarray['contact-id'] = $importer['id'];
@ -354,7 +354,7 @@ function dfrn_notify_post(&$a) {
} }
$datarray = get_atom_elements($item); $datarray = get_atom_elements($feed,$item);
$datarray['parent-uri'] = $item_id; $datarray['parent-uri'] = $item_id;
$datarray['uid'] = $importer['importer_uid']; $datarray['uid'] = $importer['importer_uid'];
$datarray['contact-id'] = $importer['id']; $datarray['contact-id'] = $importer['id'];

View file

@ -200,7 +200,7 @@ function network_content(&$a, $update = 0) {
$template = $wallwall; $template = $wallwall;
$commentww = 'ww'; $commentww = 'ww';
} }
if($item['type'] === 'remote' && ($item['owner-link'] != $item['author-link'])) { if(($item['type'] === 'remote') && (strlen($item['owner-link'])) && ($item['owner-link'] != $item['author-link'])) {
// Could be anybody. // Could be anybody.
$owner_url = $item['owner-link']; $owner_url = $item['owner-link'];
$owner_photo = $item['owner-avatar']; $owner_photo = $item['owner-avatar'];

View file

@ -71,7 +71,7 @@ function notifications_content(&$a) {
'$hide_text' => ((strlen($sql_extra)) ? t('Show Ignored Requests') : t('Hide Ignored Requests')) '$hide_text' => ((strlen($sql_extra)) ? t('Show Ignored Requests') : t('Hide Ignored Requests'))
)); ));
$r = q("SELECT `intro`.`id` AS `intro-id`, `intro`.*, `contact`.* $r = q("SELECT `intro`.`id` AS `intro_id`, `intro`.*, `contact`.*
FROM `intro` LEFT JOIN `contact` ON `intro`.`contact-id` = `contact`.`id` FROM `intro` LEFT JOIN `contact` ON `intro`.`contact-id` = `contact`.`id`
WHERE `intro`.`uid` = %d $sql_extra AND `intro`.`blocked` = 0 ", WHERE `intro`.`uid` = %d $sql_extra AND `intro`.`blocked` = 0 ",
intval($_SESSION['uid'])); intval($_SESSION['uid']));
@ -83,15 +83,36 @@ function notifications_content(&$a) {
foreach($r as $rr) { foreach($r as $rr) {
$friend_selected = (($rr['network'] === 'dfrn') ? ' checked="checked" ' : ' disabled ');
$fan_selected = (($rr['network'] === 'stat') ? ' checked="checked" disabled ' : '');
$dfrn_tpl = load_view_file('view/netfriend.tpl');
$knowyou = '';
$dfrn_text = '';
if($rr['network'] === 'dfrn') {
$knowyou = t('Claims to be known to you: ') . (($rr['knowyou']) ? t('yes') : t('no'));
$dfrn_text = replace_macros($dfrn_tpl,array(
'$intro_id' => $rr['intro_id'],
'$friend_selected' => $friend_selected,
'$fan_selected' => $fan_selected,
));
}
$o .= replace_macros($tpl,array( $o .= replace_macros($tpl,array(
'$intro_id' => $rr['intro-id'], '$notify_type' => (($rr['network'] === 'dfrn') ? t('Friend/Connect Request') : t('New Follower')),
'$dfrn-id' => $rr['issued-id'], '$dfrn_text' => $dfrn_text,
'$dfrn_id' => $rr['issued-id'],
'$uid' => $_SESSION['uid'], '$uid' => $_SESSION['uid'],
'$contact-id' => $rr['contact-id'], '$intro_id' => $rr['intro_id'],
'$contact_id' => $rr['contact-id'],
'$photo' => ((x($rr,'photo')) ? $rr['photo'] : "images/default-profile.jpg"), '$photo' => ((x($rr,'photo')) ? $rr['photo'] : "images/default-profile.jpg"),
'$fullname' => $rr['name'], '$fullname' => $rr['name'],
'$knowyou' => (($rr['knowyou']) ? t('yes') : t('no')),
'$url' => $rr['url'], '$url' => $rr['url'],
'$knowyou' => $knowyou,
'$note' => $rr['note'] '$note' => $rr['note']
)); ));
} }

View file

@ -195,14 +195,16 @@ function salmon_post(&$a) {
* *
*/ */
$r = q("SELECT * FROM `contact` WHERE `network` = 'stat' AND `lrdd` = '%s' AND `uid` = %d LIMIT 1", $r = q("SELECT * FROM `contact` WHERE `network` = 'stat' AND ( `url` = '%s' OR `lrdd` = '%s') AND `uid` = %d
AND `readonly` = 0 LIMIT 1",
dbesc($author_link),
dbesc($author_link), dbesc($author_link),
intval($importer['uid']) intval($importer['uid'])
); );
if(! count($r)) { if(! count($r)) {
if($debugging) if($debugging)
file_put_contents('salmon.out',"\n" . 'Author unknown to us.' . "\n", FILE_APPEND); file_put_contents('salmon.out',"\n" . 'Author unknown to us.' . "\n", FILE_APPEND);
salmon_return(500);
} }
require_once('include/items.php'); require_once('include/items.php');
@ -212,7 +214,9 @@ function salmon_post(&$a) {
$hub = ''; $hub = '';
consume_feed($feedxml,$importer,$r[0],$hub); // consume_feed will only accept a follow activity from this person if there is no contact record.
consume_feed($feedxml,$importer,((count($r)) ? $r[0] : null),$hub);
salmon_return(200); salmon_return(200);
} }

View file

@ -50,7 +50,7 @@ function contact_select($selname, $selclass, $preselected = false, $size = 4, $p
$r = q("SELECT `id`, `name`, `url` FROM `contact` $r = q("SELECT `id`, `name`, `url` FROM `contact`
WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 AND `network` = 'dfrn'
$sql_extra $sql_extra
ORDER BY `name` ASC ", ORDER BY `name` ASC ",
$_SESSION['uid'] $_SESSION['uid']

View file

@ -1,12 +1,12 @@
<div class="intro-wrapper" id="intro-$contact-id" > <div class="intro-wrapper" id="intro-$contact_id" >
<p class="intro-desc">Notification type: Friend/Connect Request</p> <p class="intro-desc">Notification type: $notify_type</p>
<div class="intro-fullname" id="intro-fullname-$contact-id" >$fullname</div> <div class="intro-fullname" id="intro-fullname-$contact_id" >$fullname</div>
<a class="intro-url-link" id="intro-url-link-$contact-id" href="$url" ><img id="photo-$contact-id" class="intro-photo" src="$photo" width="175" height=175" name="$fullname" alt="fullname" /></a> <a class="intro-url-link" id="intro-url-link-$contact_id" href="$url" ><img id="photo-$contact_id" class="intro-photo" src="$photo" width="175" height=175" name="$fullname" alt="fullname" /></a>
<div class="intro-knowyou">Presumably known to you? <strong>$knowyou</strong></div> <div class="intro-knowyou">$knowyou</div>
<div class="intro-note" id="intro-note-$contact-id">$note</div> <div class="intro-note" id="intro-note-$contact_id">$note</div>
<div class="intro-wrapper-end" id="intro-wrapper-end-$contact-id"></div> <div class="intro-wrapper-end" id="intro-wrapper-end-$contact_id"></div>
<form class="intro-form" action="notifications/$intro_id" method="post"> <form class="intro-form" action="notifications/$intro_id" method="post">
<input class="intro-submit-ignore" type="submit" name="submit" value="Ignore" /> <input class="intro-submit-ignore" type="submit" name="submit" value="Ignore" />
<input class="intro-submit-discard" type="submit" name="submit" value="Discard" /> <input class="intro-submit-discard" type="submit" name="submit" value="Discard" />
@ -14,23 +14,11 @@
<div class="intro-form-end"></div> <div class="intro-form-end"></div>
<form class="intro-approve-form" action="dfrn_confirm" method="post"> <form class="intro-approve-form" action="dfrn_confirm" method="post">
<input type="hidden" name="dfrn_id" value="$dfrn-id" > <input type="hidden" name="dfrn_id" value="$dfrn_id" >
<input type="hidden" name="intro_id" value="$intro_id" > <input type="hidden" name="intro_id" value="$intro_id" >
<input type="hidden" name="contact_id" value="$contact_id" >
<div class="intro-approve-as-friend-desc">Approve as: </div> $dfrn_text
<div class="intro-approve-as-friend-wrapper">
<label class="intro-approve-as-friend-label" for="intro-approve-as-friend-$intro_id">Friend</label>
<input type="radio" name="duplex" id="intro-approve-as-friend-$intro_id" class="intro-approve-as-friend" checked="checked" value="1" />
<div class="intro-approve-friend-break" ></div>
</div>
<div class="intro-approve-as-friend-end"></div>
<div class="intro-approve-as-fan-wrapper">
<label class="intro-approve-as-fan-label" for="intro-approve-as-fan-$intro_id">Fan/Admirer</label>
<input type="radio" name="duplex" id="intro-approve-as-fan-$intro_id" class="intro-approve-as-fan" $fan_selected value="0" />
<div class="intro-approve-fan-break"></div>
</div>
<div class="intro-approve-as-end"></div>
<input class="intro-submit-approve" type="submit" name="submit" value="Approve" /> <input class="intro-submit-approve" type="submit" name="submit" value="Approve" />
</form> </form>

9
view/magicsig.tpl Normal file
View file

@ -0,0 +1,9 @@
<?xml version='1.0' encoding='UTF-8'?>
<me:env xmlns:me='http://salmon-protocol.org/ns/magic-env'>
<me:data type='application/atom+xml'>
$data
</me:data>
<me:encoding>$encoding</me:encoding>
<me:alg>$algorithm</me:/alg>
<me:sig keyash="$keyhash">$signature</me:sig>
</me:env>

14
view/netfriend.tpl Normal file
View file

@ -0,0 +1,14 @@
<div class="intro-approve-as-friend-desc">Approve as: </div>
<div class="intro-approve-as-friend-wrapper">
<label class="intro-approve-as-friend-label" for="intro-approve-as-friend-$intro_id">Friend</label>
<input type="radio" name="duplex" id="intro-approve-as-friend-$intro_id" class="intro-approve-as-friend" $friend_selected value="1" />
<div class="intro-approve-friend-break" ></div>
</div>
<div class="intro-approve-as-friend-end"></div>
<div class="intro-approve-as-fan-wrapper">
<label class="intro-approve-as-fan-label" for="intro-approve-as-fan-$intro_id">Fan/Admirer</label>
<input type="radio" name="duplex" id="intro-approve-as-fan-$intro_id" class="intro-approve-as-fan" $fan_selected value="0" />
<div class="intro-approve-fan-break"></div>
</div>
<div class="intro-approve-as-end"></div>

View file

@ -527,6 +527,10 @@ input#dfrn-url {
clear: both; clear: both;
} }
.intro-wrapper {
margin-top: 20px;
}
.intro-fullname { .intro-fullname {
font-size: 1.1em; font-size: 1.1em;
font-weight: bold; font-weight: bold;
@ -534,6 +538,7 @@ input#dfrn-url {
} }
.intro-desc { .intro-desc {
margin-bottom: 20px; margin-bottom: 20px;
font-weight: bold;
} }
.intro-note { .intro-note {
@ -556,6 +561,10 @@ input#dfrn-url {
.intro-submit-approve, .intro-submit-ignore { .intro-submit-approve, .intro-submit-ignore {
margin-right: 20px; margin-right: 20px;
} }
.intro-submit-approve {
margin-top: 15px;
}
.intro-approve-as-friend-label, .intro-approve-as-fan-label { .intro-approve-as-friend-label, .intro-approve-as-fan-label {
float: left; float: left;
width: 100px; width: 100px;