]> git.mxchange.org Git - friendica.git/blobdiff - include/diaspora.php
modified: view/theme/smoothly/style.css
[friendica.git] / include / diaspora.php
old mode 100644 (file)
new mode 100755 (executable)
index 8b8050a..baee042
@@ -5,6 +5,7 @@ require_once('include/items.php');
 require_once('include/bb2diaspora.php');
 require_once('include/contact_selectors.php');
 require_once('include/queue_fn.php');
+require_once('include/lock.php');
 
 
 function diaspora_dispatch_public($msg) {
@@ -60,10 +61,10 @@ function diaspora_dispatch($importer,$msg) {
                $ret = diaspora_request($importer,$xmlbase->request);
        }
        elseif($xmlbase->status_message) {
-               $ret = diaspora_post($importer,$xmlbase->status_message);
+               $ret = diaspora_post($importer,$xmlbase->status_message,$msg);
        }
        elseif($xmlbase->profile) {
-               $ret = diaspora_profile($importer,$xmlbase->profile);
+               $ret = diaspora_profile($importer,$xmlbase->profile,$msg);
        }
        elseif($xmlbase->comment) {
                $ret = diaspora_comment($importer,$xmlbase->comment,$msg);
@@ -72,10 +73,10 @@ function diaspora_dispatch($importer,$msg) {
                $ret = diaspora_like($importer,$xmlbase->like,$msg);
        }
        elseif($xmlbase->asphoto) {
-               $ret = diaspora_asphoto($importer,$xmlbase->asphoto);
+               $ret = diaspora_asphoto($importer,$xmlbase->asphoto,$msg);
        }
        elseif($xmlbase->reshare) {
-               $ret = diaspora_reshare($importer,$xmlbase->reshare);
+               $ret = diaspora_reshare($importer,$xmlbase->reshare,$msg);
        }
        elseif($xmlbase->retraction) {
                $ret = diaspora_retraction($importer,$xmlbase->retraction,$msg);
@@ -101,6 +102,37 @@ function diaspora_dispatch($importer,$msg) {
        return $ret;
 }
 
+function diaspora_handle_from_contact($contact_id) {
+       $handle = False;
+
+       logger("diaspora_handle_from_contact: contact id is " . $contact_id, LOGGER_DEBUG);
+
+       $r = q("SELECT network, addr, self, url, nick FROM contact WHERE id = %d",
+              intval($contact_id)
+       );
+       if($r) {
+               $contact = $r[0];
+
+               logger("diaspora_handle_from_contact: contact 'self' = " . $contact['self'] . " 'url' = " . $contact['url'], LOGGER_DEBUG);
+
+               if($contact['network'] === NETWORK_DIASPORA) {
+                       $handle = $contact['addr'];
+
+//                     logger("diaspora_handle_from_contact: contact id is a Diaspora person, handle = " . $handle, LOGGER_DEBUG);
+               }
+               elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) {
+                       $baseurl_start = strpos($contact['url'],'://') + 3;
+                       $baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle
+                       $baseurl = substr($contact['url'], $baseurl_start, $baseurl_length);
+                       $handle = $contact['nick'] . '@' . $baseurl;
+
+//                     logger("diaspora_handle_from_contact: contact id is a DFRN person, handle = " . $handle, LOGGER_DEBUG);
+               }
+       }
+
+       return $handle;
+}
+
 function diaspora_get_contact_by_handle($uid,$handle) {
        $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `addr` = '%s' LIMIT 1",
                dbesc(NETWORK_DIASPORA),
@@ -113,27 +145,93 @@ function diaspora_get_contact_by_handle($uid,$handle) {
 }
 
 function find_diaspora_person_by_handle($handle) {
+
+       $person = false;
        $update = false;
-       $r = q("select * from fcontact where network = '%s' and addr = '%s' limit 1",
-               dbesc(NETWORK_DIASPORA),
-               dbesc($handle)
-       );
-       if(count($r)) {
-               logger('find_diaspora_person_by handle: in cache ' . print_r($r,true), LOGGER_DEBUG);
-               // update record occasionally so it doesn't get stale
-               $d = strtotime($r[0]['updated'] . ' +00:00');
-               if($d > strtotime('now - 14 days'))
-                       return $r[0];
-               $update = true;
-       }
-       logger('find_diaspora_person_by_handle: refresh',LOGGER_DEBUG);
-       require_once('include/Scrape.php');
-       $r = probe_url($handle, PROBE_DIASPORA);
-       if((count($r)) && ($r['network'] === NETWORK_DIASPORA)) {
-               add_fcontact($r,$update);
-               return ($r);
-       }
-       return false;
+       $got_lock = false;
+
+       $endlessloop = 0;
+       $maxloops = 10;
+
+       do {
+               $r = q("select * from fcontact where network = '%s' and addr = '%s' limit 1",
+                       dbesc(NETWORK_DIASPORA),
+                       dbesc($handle)
+               );
+               if(count($r)) {
+                       $person = $r[0];
+                       logger('find_diaspora_person_by handle: in cache ' . print_r($r,true), LOGGER_DEBUG);
+
+                       // update record occasionally so it doesn't get stale
+                       $d = strtotime($person['updated'] . ' +00:00');
+                       if($d < strtotime('now - 14 days'))
+                               $update = true;
+               }
+
+
+               // FETCHING PERSON INFORMATION FROM REMOTE SERVER
+               //
+               // If the person isn't in our 'fcontact' table, or if he/she is but
+               // his/her information hasn't been updated for more than 14 days, then
+               // we want to fetch the person's information from the remote server.
+               //
+               // Note that $person isn't changed by this block of code unless the
+               // person's information has been successfully fetched from the remote
+               // server. So if $person was 'false' to begin with (because he/she wasn't
+               // in the local cache), it'll stay false, and if $person held the local
+               // cache information to begin with, it'll keep that information. That way
+               // if there's a problem with the remote fetch, we can at least use our
+               // cached information--it's better than nothing.
+
+               if((! $person) || ($update))  {
+                       // Lock the function to prevent race conditions if multiple items
+                       // come in at the same time from a person who doesn't exist in
+                       // fcontact
+                       //
+                       // Don't loop forever. On the last loop, try to create the contact
+                       // whether the function is locked or not. Maybe the locking thread
+                       // has died or something. At any rate, a duplicate in 'fcontact'
+                       // is a much smaller problem than a deadlocked thread
+                       $got_lock = lock_function('find_diaspora_person_by_handle', false);
+                       if(($endlessloop + 1) >= $maxloops)
+                               $got_lock = true;
+
+                       if($got_lock) {
+                               logger('find_diaspora_person_by_handle: create or refresh', LOGGER_DEBUG);
+                               require_once('include/Scrape.php');
+                               $r = probe_url($handle, PROBE_DIASPORA);
+
+                               // Note that Friendica contacts can return a "Diaspora person"
+                               // if Diaspora connectivity is enabled on their server
+                               if((count($r)) && ($r['network'] === NETWORK_DIASPORA)) {
+                                       add_fcontact($r,$update);
+                                       $person = ($r);
+                               }
+
+                               unlock_function('find_diaspora_person_by_handle');
+                       }
+                       else {
+                               logger('find_diaspora_person_by_handle: couldn\'t lock function', LOGGER_DEBUG);
+                               if(! $person)
+                                       block_on_function_lock('find_diaspora_person_by_handle');
+                       }
+               }
+       } while((! $person) && (! $got_lock) && (++$endlessloop < $maxloops));
+       // We need to try again if the person wasn't in 'fcontact' but the function was locked.
+       // The fact that the function was locked may mean that another process was creating the
+       // person's record. It could also mean another process was creating or updating an unrelated
+       // person.
+       //
+       // At any rate, we need to keep trying until we've either got the person or had a chance to
+       // try to fetch his/her remote information. But we don't want to block on locking the
+       // function, because if the other process is creating the record, then when we acquire the lock
+       // we'll dive right into creating another, duplicate record. We DO want to at least wait
+       // until the lock is released, so we don't flood the database with requests.
+       //
+       // If the person was in the 'fcontact' table, don't try again. It's not worth the time, since
+       // we do have some information for the person
+
+       return $person;
 }
 
 
@@ -654,12 +752,17 @@ function diaspora_post_allow($importer,$contact) {
 }
 
 
-function diaspora_post($importer,$xml) {
+function diaspora_post($importer,$xml,$msg) {
 
        $a = get_app();
        $guid = notags(unxmlify($xml->guid));
        $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
 
+       if($diaspora_handle != $msg['author']) {
+               logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.');
+               return 202;
+       }
+
        $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle);
        if(! $contact)
                return;
@@ -680,7 +783,7 @@ function diaspora_post($importer,$xml) {
                return;
        }
 
-        // allocate a guid on our system - we aren't fixing any collisions.
+       // allocate a guid on our system - we aren't fixing any collisions.
        // we're ignoring them
 
        $g = q("select * from guid where guid = '%s' limit 1",
@@ -770,7 +873,7 @@ function diaspora_post($importer,$xml) {
 
 }
 
-function diaspora_reshare($importer,$xml) {
+function diaspora_reshare($importer,$xml,$msg) {
 
        logger('diaspora_reshare: init: ' . print_r($xml,true));
 
@@ -779,6 +882,11 @@ function diaspora_reshare($importer,$xml) {
        $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
 
 
+       if($diaspora_handle != $msg['author']) {
+               logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.');
+               return 202;
+       }
+
        $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle);
        if(! $contact)
                return;
@@ -844,10 +952,10 @@ function diaspora_reshare($importer,$xml) {
        else
                $details = $orig_author;
        
-       $prefix = '&#x2672; ' . $details . "\n"; 
+       $prefix = html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8') . $details . "\n"; 
 
 
-        // allocate a guid on our system - we aren't fixing any collisions.
+       // allocate a guid on our system - we aren't fixing any collisions.
        // we're ignoring them
 
        $g = q("select * from guid where guid = '%s' limit 1",
@@ -924,13 +1032,18 @@ function diaspora_reshare($importer,$xml) {
 }
 
 
-function diaspora_asphoto($importer,$xml) {
+function diaspora_asphoto($importer,$xml,$msg) {
        logger('diaspora_asphoto called');
 
        $a = get_app();
        $guid = notags(unxmlify($xml->guid));
        $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
 
+       if($diaspora_handle != $msg['author']) {
+               logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.');
+               return 202;
+       }
+
        $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle);
        if(! $contact)
                return;
@@ -951,7 +1064,7 @@ function diaspora_asphoto($importer,$xml) {
                return;
        }
 
-        // allocate a guid on our system - we aren't fixing any collisions.
+       // allocate a guid on our system - we aren't fixing any collisions.
        // we're ignoring them
 
        $g = q("select * from guid where guid = '%s' limit 1",
@@ -1060,45 +1173,64 @@ function diaspora_comment($importer,$xml,$msg) {
        }
        $parent_item = $r[0];
 
-       $author_signed_data = $guid . ';' . $parent_guid . ';' . $text . ';' . $diaspora_handle;
-
-       $author_signature = base64_decode($author_signature);
 
-       if(strcasecmp($diaspora_handle,$msg['author']) == 0) {
-               $person = $contact;
-               $key = $msg['key'];
-       }
-       else {
-               $person = find_diaspora_person_by_handle($diaspora_handle);     
+       /* How Diaspora performs comment signature checking:
 
-               if(is_array($person) && x($person,'pubkey'))
-                       $key = $person['pubkey'];
-               else {
-                       logger('diaspora_comment: unable to find author details');
-                       return;
-               }
-       }
+          - If an item has been sent by the comment author to the top-level post owner to relay on
+            to the rest of the contacts on the top-level post, the top-level post owner should check
+            the author_signature, then create a parent_author_signature before relaying the comment on
+          - If an item has been relayed on by the top-level post owner, the contacts who receive it
+            check only the parent_author_signature. Basically, they trust that the top-level post
+            owner has already verified the authenticity of anything he/she sends out
+          - In either case, the signature that get checked is the signature created by the person
+            who sent the salmon
+       */
 
-       if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) {
-               logger('diaspora_comment: verification failed.');
-               return;
-       }
+       $signed_data = $guid . ';' . $parent_guid . ';' . $text . ';' . $diaspora_handle;
+       $key = $msg['key'];
 
        if($parent_author_signature) {
-               $owner_signed_data = $guid . ';' . $parent_guid . ';' . $text . ';' . $diaspora_handle;
+               // If a parent_author_signature exists, then we've received the comment
+               // relayed from the top-level post owner. There's no need to check the
+               // author_signature if the parent_author_signature is valid
 
                $parent_author_signature = base64_decode($parent_author_signature);
 
-               $key = $msg['key'];
+               if(! rsa_verify($signed_data,$parent_author_signature,$key,'sha256')) {
+                       logger('diaspora_comment: top-level owner verification failed.');
+                       return;
+               }
+       }
+       else {
+               // If there's no parent_author_signature, then we've received the comment
+               // from the comment creator. In that case, the person is commenting on
+               // our post, so he/she must be a contact of ours and his/her public key
+               // should be in $msg['key']
+
+               $author_signature = base64_decode($author_signature);
 
-               if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) {
-                       logger('diaspora_comment: owner verification failed.');
+               if(! rsa_verify($signed_data,$author_signature,$key,'sha256')) {
+                       logger('diaspora_comment: comment author verification failed.');
                        return;
                }
        }
 
        // Phew! Everything checks out. Now create an item.
 
+       // Find the original comment author information.
+       // We need this to make sure we display the comment author
+       // information (name and avatar) correctly.
+       if(strcasecmp($diaspora_handle,$msg['author']) == 0)
+               $person = $contact;
+       else {
+               $person = find_diaspora_person_by_handle($diaspora_handle);     
+
+               if(! is_array($person)) {
+                       logger('diaspora_comment: unable to find author details');
+                       return;
+               }
+       }
+
        $body = diaspora2bb($text);
 
        $message_id = $diaspora_handle . ':' . $guid;
@@ -1135,6 +1267,7 @@ function diaspora_comment($importer,$xml,$msg) {
 
        $datarray['uid'] = $importer['uid'];
        $datarray['contact-id'] = $contact['id'];
+       $datarray['type'] = 'remote-comment';
        $datarray['wall'] = $parent_item['wall'];
        $datarray['gravity'] = GRAVITY_COMMENT;
        $datarray['guid'] = $guid;
@@ -1171,7 +1304,7 @@ function diaspora_comment($importer,$xml,$msg) {
        if(($parent_item['origin']) && (! $parent_author_signature)) {
                q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
                        intval($message_id),
-                       dbesc($author_signed_data),
+                       dbesc($signed_data),
                        dbesc(base64_encode($author_signature)),
                        dbesc($diaspora_handle)
                );
@@ -1180,7 +1313,7 @@ function diaspora_comment($importer,$xml,$msg) {
                // the existence of parent_author_signature means the parent_author or owner
                // is already relaying.
 
-               proc_run('php','include/notifier.php','comment',$message_id);
+               proc_run('php','include/notifier.php','comment-import',$message_id);
        }
 
        $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0 ",
@@ -1217,7 +1350,7 @@ function diaspora_comment($importer,$xml,$msg) {
                                'verb'         => ACTIVITY_POST,
                                'otype'        => 'item',
                                'parent'       => $conv_parent,
-
+                               'parent_uri'   => $parent_uri
                        ));
 
                        // only send one notification
@@ -1541,7 +1674,8 @@ function diaspora_photo($importer,$xml,$msg) {
 
        $link_text = '[img]' . $remote_photo_path . $remote_photo_name . '[/img]' . "\n";
 
-       $link_text = scale_external_images($link_text);
+       $link_text = scale_external_images($link_text, true,
+                                          array($remote_photo_name, 'scaled_full_' . $remote_photo_name));
 
        if(strpos($parent_item['body'],$link_text) === false) {
                $r = q("update item set `body` = '%s', `visible` = 1 where `id` = %d and `uid` = %d limit 1",
@@ -1571,8 +1705,8 @@ function diaspora_like($importer,$xml,$msg) {
 
        // likes on comments not supported here and likes on photos not supported by Diaspora
 
-       if($target_type !== 'Post')
-               return;
+//     if($target_type !== 'Post')
+//             return;
 
        $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']);
        if(! $contact) {
@@ -1605,60 +1739,85 @@ function diaspora_like($importer,$xml,$msg) {
                        logger('diaspora_like: duplicate like: ' . $guid);
                        return;
                } 
+               // Note: I don't think "Like" objects with positive = "false" are ever actually used
+               // It looks like "RelayableRetractions" are used for "unlike" instead
                if($positive === 'false') {
-                       q("UPDATE `item` SET `deleted` = 1 WHERE `id` = %d AND `uid` = %d LIMIT 1",
+                       logger('diaspora_like: received a like with positive set to "false"...ignoring');
+/*                     q("UPDATE `item` SET `deleted` = 1 WHERE `id` = %d AND `uid` = %d LIMIT 1",
                                intval($r[0]['id']),
                                intval($importer['uid'])
-                       );
-                       // FIXME
+                       );*/
+                       // FIXME--actually don't unless it turns out that Diaspora does indeed send out "false" likes
                        //  send notification via proc_run()
                        return;
                }
        }
+       // Note: I don't think "Like" objects with positive = "false" are ever actually used
+       // It looks like "RelayableRetractions" are used for "unlike" instead
        if($positive === 'false') {
-               logger('diaspora_like: unlike received with no corresponding like');
+               logger('diaspora_like: received a like with positive set to "false"');
+               logger('diaspora_like: unlike received with no corresponding like...ignoring');
                return; 
        }
 
-       $author_signed_data = $guid . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $diaspora_handle;
 
-       $author_signature = base64_decode($author_signature);
+       /* How Diaspora performs "like" signature checking:
 
-       if(strcasecmp($diaspora_handle,$msg['author']) == 0) {
-               $person = $contact;
-               $key = $msg['key'];
-       }
-       else {
-               $person = find_diaspora_person_by_handle($diaspora_handle);     
-               if(is_array($person) && x($person,'pubkey'))
-                       $key = $person['pubkey'];
-               else {
-                       logger('diaspora_like: unable to find author details');
-                       return;
-               }
-       }
+          - If an item has been sent by the like author to the top-level post owner to relay on
+            to the rest of the contacts on the top-level post, the top-level post owner should check
+            the author_signature, then create a parent_author_signature before relaying the like on
+          - If an item has been relayed on by the top-level post owner, the contacts who receive it
+            check only the parent_author_signature. Basically, they trust that the top-level post
+            owner has already verified the authenticity of anything he/she sends out
+          - In either case, the signature that get checked is the signature created by the person
+            who sent the salmon
+       */
 
-       if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) {
-               logger('diaspora_like: verification failed.');
-               return;
-       }
+       $signed_data = $guid . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $diaspora_handle;
+       $key = $msg['key'];
 
        if($parent_author_signature) {
-
-               $owner_signed_data = $guid . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $diaspora_handle;
+               // If a parent_author_signature exists, then we've received the like
+               // relayed from the top-level post owner. There's no need to check the
+               // author_signature if the parent_author_signature is valid
 
                $parent_author_signature = base64_decode($parent_author_signature);
 
-               $key = $msg['key'];
+               if(! rsa_verify($signed_data,$parent_author_signature,$key,'sha256')) {
+                       logger('diaspora_like: top-level owner verification failed.');
+                       return;
+               }
+       }
+       else {
+               // If there's no parent_author_signature, then we've received the like
+               // from the like creator. In that case, the person is "like"ing
+               // our post, so he/she must be a contact of ours and his/her public key
+               // should be in $msg['key']
+
+               $author_signature = base64_decode($author_signature);
 
-               if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) {
-                       logger('diaspora_like: owner verification failed.');
+               if(! rsa_verify($signed_data,$author_signature,$key,'sha256')) {
+                       logger('diaspora_like: like creator verification failed.');
                        return;
                }
        }
 
        // Phew! Everything checks out. Now create an item.
 
+       // Find the original comment author information.
+       // We need this to make sure we display the comment author
+       // information (name and avatar) correctly.
+       if(strcasecmp($diaspora_handle,$msg['author']) == 0)
+               $person = $contact;
+       else {
+               $person = find_diaspora_person_by_handle($diaspora_handle);
+
+               if(! is_array($person)) {
+                       logger('diaspora_like: unable to find author details');
+                       return;
+               }
+       }
+
        $uri = $diaspora_handle . ':' . $guid;
 
        $activity = ACTIVITY_LIKE;
@@ -1728,7 +1887,7 @@ EOT;
        if(! $parent_author_signature) {
                q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
                        intval($message_id),
-                       dbesc($author_signed_data),
+                       dbesc($signed_data),
                        dbesc(base64_encode($author_signature)),
                        dbesc($diaspora_handle)
                );
@@ -1739,7 +1898,7 @@ EOT;
        // is already relaying. The parent_item['origin'] indicates the message was created on our system
 
        if(($parent_item['origin']) && (! $parent_author_signature))
-               proc_run('php','include/notifier.php','comment',$message_id);
+               proc_run('php','include/notifier.php','comment-import',$message_id);
 
        return;
 }
@@ -1796,53 +1955,78 @@ function diaspora_signed_retraction($importer,$xml,$msg) {
 
 
        $signed_data = $guid . ';' . $type ;
+       $key = $msg['key'];
 
-       $sig = base64_decode($sig);
+       /* How Diaspora performs relayable_retraction signature checking:
 
-       if(strcasecmp($diaspora_handle,$msg['author']) == 0) {
-               $person = $contact;
-               $key = $msg['key'];
-       }
-       else {
-               $person = find_diaspora_person_by_handle($diaspora_handle);     
+          - If an item has been sent by the item author to the top-level post owner to relay on
+            to the rest of the contacts on the top-level post, the top-level post owner checks
+            the author_signature, then creates a parent_author_signature before relaying the item on
+          - If an item has been relayed on by the top-level post owner, the contacts who receive it
+            check only the parent_author_signature. Basically, they trust that the top-level post
+            owner has already verified the authenticity of anything he/she sends out
+          - In either case, the signature that get checked is the signature created by the person
+            who sent the salmon
+       */
 
-               if(is_array($person) && x($person,'pubkey'))
-                       $key = $person['pubkey'];
-               else {
-                       logger('diaspora_signed_retraction: unable to find author details');
+       if($parent_author_signature) {
+
+               $parent_author_signature = base64_decode($parent_author_signature);
+
+               if(! rsa_verify($signed_data,$parent_author_signature,$key,'sha256')) {
+                       logger('diaspora_signed_retraction: top-level post owner verification failed');
                        return;
                }
-       }
 
-       if(! rsa_verify($signed_data,$sig,$key,'sha256')) {
-               logger('diaspora_signed_retraction: retraction-owner verification failed.' . print_r($msg,true));
-               return;
        }
+       else {
 
-       if($parent_author_signature) {
-               $owner_signed_data = $guid . ';' . $type ;
-
-               $parent_author_signature = base64_decode($parent_author_signature);
-
-               $key = $msg['key'];
+               $sig_decode = base64_decode($sig);
 
-               if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) {
-                       logger('diaspora_signed_retraction: failed to verify person relaying the retraction (e.g. owner of a post relaying a retracted comment');
+               if(! rsa_verify($signed_data,$sig_decode,$key,'sha256')) {
+                       logger('diaspora_signed_retraction: retraction owner verification failed.' . print_r($msg,true));
                        return;
                }
        }
 
-       if($type === 'StatusMessage' || $type === 'Comment') {
+       if($type === 'StatusMessage' || $type === 'Comment' || $type === 'Like') {
                $r = q("select * from item where guid = '%s' and uid = %d and not file like '%%[%%' limit 1",
                        dbesc($guid),
                        intval($importer['uid'])
                );
                if(count($r)) {
                        if(link_compare($r[0]['author-link'],$contact['url'])) {
-                               q("update item set `deleted` = 1, `changed` = '%s' where `id` = %d limit 1",
+                               q("update item set `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' where `id` = %d limit 1",
+                                       dbesc(datetime_convert()),                      
                                        dbesc(datetime_convert()),                      
                                        intval($r[0]['id'])
                                );
+       
+                               // Now check if the retraction needs to be relayed by us
+                               //
+                               // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always
+                               // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent.
+                               // The only item with `parent` and `id` as the parent id is the parent item.
+                               $p = q("select origin from item where parent = %d and id = %d limit 1",
+                                       $r[0]['parent'],
+                                       $r[0]['parent']
+                               );
+                               if(count($p)) {
+                                       if(($p[0]['origin']) && (! $parent_author_signature)) {
+                                               q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
+                                                       $r[0]['id'],
+                                                       dbesc($signed_data),
+                                                       dbesc($sig),
+                                                       dbesc($diaspora_handle)
+                                               );
+
+                                               // the existence of parent_author_signature would have meant the parent_author or owner
+                                               // is already relaying.
+                                               logger('diaspora_signed_retraction: relaying relayable_retraction');
+
+                                               proc_run('php','include/notifier.php','drop',$r[0]['id']);
+                                       }
+                               }
                        }
                }
        }
@@ -1853,11 +2037,17 @@ function diaspora_signed_retraction($importer,$xml,$msg) {
        // NOTREACHED
 }
 
-function diaspora_profile($importer,$xml) {
+function diaspora_profile($importer,$xml,$msg) {
 
        $a = get_app();
        $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
 
+
+       if($diaspora_handle != $msg['author']) {
+               logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.');
+               return 202;
+       }
+
        $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle);
        if(! $contact)
                return;
@@ -1936,6 +2126,7 @@ function diaspora_share($me,$contact) {
        ));
 
        $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey'])));
+       //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey']));
 
        return(diaspora_transmit($owner,$contact,$slap, false));
 }
@@ -1953,13 +2144,13 @@ function diaspora_unshare($me,$contact) {
        ));
 
        $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey'])));
+       //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey']));
 
        return(diaspora_transmit($owner,$contact,$slap, false));
 
 }
 
 
-
 function diaspora_send_status($item,$owner,$contact,$public_batch = false) {
 
        $a = get_app();
@@ -1991,13 +2182,19 @@ function diaspora_send_status($item,$owner,$contact,$public_batch = false) {
                        $images[] = $detail;
                        $body = str_replace($detail['str'],$mtch[1],$body);
                }
-       }       
+       }
 */
+
+       //if(strlen($title))
+       //      $body = "[b]".html_entity_decode($title)."[/b]\n\n".$body;
+
+       // convert to markdown
        $body = xmlify(html_entity_decode(bb2diaspora($body)));
+       //$body = bb2diaspora($body);
 
+       // Adding the title
        if(strlen($title))
-               $body = xmlify('**' . html_entity_decode($title) . '**' . "\n") . $body;
-
+               $body = "## ".html_entity_decode($title)."\n\n".$body;
 
        if($item['attach']) {
                $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism',$item['attach'],$matches,PREG_SET_ORDER);
@@ -2027,6 +2224,7 @@ function diaspora_send_status($item,$owner,$contact,$public_batch = false) {
        logger('diaspora_send_status: ' . $owner['username'] . ' -> ' . $contact['name'] . ' base message: ' . $msg, LOGGER_DATA);
 
        $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
+       //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
 
        $return_code = diaspora_transmit($owner,$contact,$slap,$public_batch);
 
@@ -2071,6 +2269,7 @@ function diaspora_send_images($item,$owner,$contact,$images,$public_batch = fals
 
                logger('diaspora_send_photo: base message: ' . $msg, LOGGER_DATA);
                $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
+               //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
 
                diaspora_transmit($owner,$contact,$slap,$public_batch);
        }
@@ -2081,25 +2280,38 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) {
 
        $a = get_app();
        $myaddr = $owner['nickname'] . '@' .  substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
-       $theiraddr = $contact['addr'];
-
-       // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always
-       // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent.
-       // The only item with `parent` and `id` as the parent id is the parent item.
-       $p = q("select guid from item where parent = %d and id = %d limit 1",
-               intval($item['parent']),
-               intval($item['parent'])
-       );
+//     $theiraddr = $contact['addr'];
+
+       // Diaspora doesn't support threaded comments
+       /*if($item['thr-parent']) {
+               $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1",
+                       dbesc($item['thr-parent'])
+                     );
+       }
+       else {*/
+               // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always
+               // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent.
+               // The only item with `parent` and `id` as the parent id is the parent item.
+               $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1",
+                       intval($item['parent']),
+                       intval($item['parent'])
+               );
+       //}
        if(count($p))
-               $parent_guid = $p[0]['guid'];
+               $parent = $p[0];
        else
                return;
 
        if($item['verb'] === ACTIVITY_LIKE) {
                $tpl = get_markup_template('diaspora_like.tpl');
                $like = true;
-               $target_type = 'Post';
-               $positive = (($item['deleted']) ? 'false' : 'true');
+               $target_type = ( $parent['uri'] === $parent['parent-uri']  ? 'Post' : 'Comment');
+//             $target_type = (strpos($parent['type'], 'comment') ? 'Comment' : 'Post');
+//             $positive = (($item['deleted']) ? 'false' : 'true');
+               $positive = 'true';
+
+               if(($item['deleted']))
+                       logger('diaspora_send_followup: received deleted "like". Those should go to diaspora_send_retraction');
        }
        else {
                $tpl = get_markup_template('diaspora_comment.tpl');
@@ -2111,15 +2323,15 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) {
        // sign it
 
        if($like)
-               $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $myaddr;
+               $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $myaddr;
        else
-               $signed_text = $item['guid'] . ';' . $parent_guid . ';' . $text . ';' . $myaddr;
+               $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $myaddr;
 
        $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
 
        $msg = replace_macros($tpl,array(
                '$guid' => xmlify($item['guid']),
-               '$parent_guid' => xmlify($parent_guid),
+               '$parent_guid' => xmlify($parent['guid']),
                '$target_type' =>xmlify($target_type),
                '$authorsig' => xmlify($authorsig),
                '$body' => xmlify($text),
@@ -2130,6 +2342,7 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) {
        logger('diaspora_followup: base message: ' . $msg, LOGGER_DATA);
 
        $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
+       //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
 
        return(diaspora_transmit($owner,$contact,$slap,$public_batch));
 }
@@ -2139,45 +2352,61 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) {
 
 
        $a = get_app();
-       $myaddr = $owner['nickname'] . '@' .  substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
-       $theiraddr = $contact['addr'];
+       $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
+//     $theiraddr = $contact['addr'];
 
+       $body = $item['body'];
+       $text = html_entity_decode(bb2diaspora($body));
 
-       // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always
-       // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent.
-       // The only item with `parent` and `id` as the parent id is the parent item.
-       $p = q("select guid from item where parent = %d and id = %d limit 1",
-               intval($item['parent']),
-               intval($item['parent'])
-       );
+       // Diaspora doesn't support threaded comments
+       /*if($item['thr-parent']) {
+               $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1",
+                       dbesc($item['thr-parent'])
+                     );
+       }
+       else {*/
+               // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always
+               // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent.
+               // The only item with `parent` and `id` as the parent id is the parent item.
+               $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1",
+                      intval($item['parent']),
+                      intval($item['parent'])
+                     );
+       //}
        if(count($p))
-               $parent_guid = $p[0]['guid'];
+               $parent = $p[0];
        else
                return;
 
-       if($item['verb'] === ACTIVITY_LIKE) {
-               $tpl = get_markup_template('diaspora_like_relay.tpl');
+       $like = false;
+       $relay_retract = false;
+       $sql_sign_id = 'iid';
+       if( $item['deleted']) {
+               $relay_retract = true;
+
+               $target_type = ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
+
+               $sql_sign_id = 'retract_iid';
+               $tpl = get_markup_template('diaspora_relayable_retraction.tpl');
+       }
+       elseif($item['verb'] === ACTIVITY_LIKE) {
                $like = true;
-               $target_type = 'Post';
-               $positive = (($item['deleted']) ? 'false' : 'true');
+
+               $target_type = ( $parent['uri'] === $parent['parent-uri']  ? 'Post' : 'Comment');
+//             $positive = (($item['deleted']) ? 'false' : 'true');
+               $positive = 'true';
+
+               $tpl = get_markup_template('diaspora_like_relay.tpl');
        }
-       else {
+       else { // item is a comment
                $tpl = get_markup_template('diaspora_comment_relay.tpl');
-               $like = false;
        }
 
-       $body = $item['body'];
 
-       $text = html_entity_decode(bb2diaspora($body));
+       // fetch the original signature if the relayable was created by a Diaspora
+       // or DFRN user. Relayables for other networks are not supported.
 
-       // fetch the original signature if somebody sent the post to us to relay
-       // If we are relaying for a reply originating on our own account, there wasn't a 'send to relay'
-       // action. It wasn't needed. In that case create the original signature and the 
-       // owner (parent author) signature
-       // comments from other networks will be relayed under our name, with a brief 
-       // preamble to describe what's happening and noting the real author
-
-       $r = q("select * from sign where iid = %d limit 1",
+/*     $r = q("select * from sign where " . $sql_sign_id . " = %d limit 1",
                intval($item['id'])
        );
        if(count($r)) { 
@@ -2188,54 +2417,67 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) {
        }
        else {
 
-               $itemcontact = q("select * from contact where `id` = %d limit 1",
-                       intval($item['contact-id'])
-               );
-               if(count($itemcontact)) {
-                       if(! $itemcontact[0]['self']) {
-                               $prefix = sprintf( t('[Relayed] Comment authored by %s from network %s'),
-                                       '['. $item['author-name'] . ']' . '(' . $item['author-link'] . ')',  
-                                       network_to_name($itemcontact['network'])) . "\n";
-                               $body = $prefix . $body;
-                       }
-               }
-               else {
-
-                       if($like)
-                               $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $myaddr;
-                       else
-                               $signed_text = $item['guid'] . ';' . $parent_guid . ';' . $text . ';' . $myaddr;
+               // Author signature information (for likes, comments, and retractions of likes or comments,
+               // whether from Diaspora or Friendica) must be placed in the `sign` table before this 
+               // function is called
+               logger('diaspora_send_relay: original author signature not found, cannot send relayable');
+               return;
+       }*/
+
+       /* Since the author signature is only checked by the parent, not by the relay recipients,
+        * I think it may not be necessary for us to do so much work to preserve all the original
+        * signatures. The important thing that Diaspora DOES need is the original creator's handle.
+        * Let's just generate that and forget about all the original author signature stuff.
+        *
+        * Note: this might be more of an problem if we want to support likes on comments for older
+        * versions of Diaspora (diaspora-pistos), but since there are a number of problems with
+        * doing that, let's ignore it for now.
+        *
+        * Currently, only DFRN contacts are supported. StatusNet shouldn't be hard, but it hasn't
+        * been done yet
+        */
+
+       $handle = diaspora_handle_from_contact($item['contact-id']);
+       if(! $handle)
+               return;
 
-                       $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
 
-                       q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
-                               intval($item['id']),
-                               dbesc($signed_text),
-                               dbesc(base64_encode($authorsig)),
-                               dbesc($myaddr)
-                       );
-                       $handle = $myaddr;
-               }
-       }
+       if($relay_retract)
+               $sender_signed_text = $item['guid'] . ';' . $target_type;
+       elseif($like)
+               $sender_signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $handle;
+       else
+               $sender_signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $handle;
 
-       // sign it
+       // Sign the relayable with the top-level owner's signature
+       //
+       // We'll use the $sender_signed_text that we just created, instead of the $signed_text
+       // stored in the database, because that provides the best chance that Diaspora will
+       // be able to reconstruct the signed text the same way we did. This is particularly a
+       // concern for the comment, whose signed text includes the text of the comment. The
+       // smallest change in the text of the comment, including removing whitespace, will
+       // make the signature verification fail. Since we translate from BB code to Diaspora's
+       // markup at the top of this function, which is AFTER we placed the original $signed_text
+       // in the database, it's hazardous to trust the original $signed_text.
 
-       $parentauthorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
+       $parentauthorsig = base64_encode(rsa_sign($sender_signed_text,$owner['uprvkey'],'sha256'));
 
        $msg = replace_macros($tpl,array(
                '$guid' => xmlify($item['guid']),
-               '$parent_guid' => xmlify($parent_guid),
+               '$parent_guid' => xmlify($parent['guid']),
                '$target_type' =>xmlify($target_type),
-               '$authorsig' => xmlify($orig_sign['signature']),
+               '$authorsig' => xmlify($authorsig),
                '$parentsig' => xmlify($parentauthorsig),
                '$body' => xmlify($text),
                '$positive' => xmlify($positive),
                '$handle' => xmlify($handle)
        ));
 
-       logger('diaspora_relay_comment: base message: ' . $msg, LOGGER_DATA);
+       logger('diaspora_send_relay: base message: ' . $msg, LOGGER_DATA);
+
 
        $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
+       //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
 
        return(diaspora_transmit($owner,$contact,$slap,$public_batch));
 
@@ -2248,17 +2490,29 @@ function diaspora_send_retraction($item,$owner,$contact,$public_batch = false) {
        $a = get_app();
        $myaddr = $owner['nickname'] . '@' .  substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
 
-       $signed_text = $item['guid'] . ';' . 'StatusMessage';
+       // Check whether the retraction is for a top-level post or whether it's a relayable
+       if( $item['uri'] !== $item['parent-uri'] ) {
+
+               $tpl = get_markup_template('diaspora_relay_retraction.tpl');
+               $target_type = (($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
+       }
+       else {
+               
+               $tpl = get_markup_template('diaspora_signed_retract.tpl');
+               $target_type = 'StatusMessage';
+       }
+
+       $signed_text = $item['guid'] . ';' . $target_type;
 
-       $tpl = get_markup_template('diaspora_signed_retract.tpl');
        $msg = replace_macros($tpl, array(
-               '$guid'   => $item['guid'],
-               '$type'   => 'StatusMessage',
-               '$handle' => $myaddr,
-               '$signature' => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))
+               '$guid'   => xmlify($item['guid']),
+               '$type'   => xmlify($target_type),
+               '$handle' => xmlify($myaddr),
+               '$signature' => xmlify(base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')))
        ));
 
        $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
+       //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
 
        return(diaspora_transmit($owner,$contact,$slap,$public_batch));
 }
@@ -2319,13 +2573,14 @@ function diaspora_send_mail($item,$owner,$contact) {
        logger('diaspora_conversation: ' . print_r($xmsg,true), LOGGER_DATA);
 
        $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($xmsg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],false)));
+       //$slap = 'xml=' . urlencode(diaspora_msg_build($xmsg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],false));
 
        return(diaspora_transmit($owner,$contact,$slap,false));
 
 
 }
 
-function diaspora_transmit($owner,$contact,$slap,$public_batch) {
+function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false) {
 
        $enabled = intval(get_config('system','diaspora_enabled'));
        if(! $enabled) {
@@ -2342,7 +2597,7 @@ function diaspora_transmit($owner,$contact,$slap,$public_batch) {
 
        logger('diaspora_transmit: ' . $logid . ' ' . $dest_url);
 
-       if(was_recently_delayed($contact['id'])) {
+       if( (! $queue_run) && (was_recently_delayed($contact['id'])) ) {
                $return_code = 0;
        }
        else {
@@ -2379,3 +2634,5 @@ function diaspora_transmit($owner,$contact,$slap,$public_batch) {
 
        return(($return_code) ? $return_code : (-1));
 }
+
+