diff --git a/src/Model/APContact.php b/src/Model/APContact.php index 37faf240ce..68e81072e6 100644 --- a/src/Model/APContact.php +++ b/src/Model/APContact.php @@ -147,14 +147,25 @@ class APContact $url = $apcontact['url']; } - $data = ActivityPub::fetchContent($url); - if (empty($data)) { + $curlResult = HTTPSignature::fetchRaw($url); + $failed = empty($curlResult) || empty($curlResult->getBody()) || + (!$curlResult->isSuccess() && ($curlResult->getReturnCode() != 410)); + + if (!$failed) { + $data = json_decode($curlResult->getBody(), true); + $failed = empty($data) || !is_array($data); + } + + if (!$failed && ($curlResult->getReturnCode() == 410)) { + $data = ['@context' => ActivityPub::CONTEXT, 'id' => $url, 'type' => 'Tombstone']; + } + + if ($failed) { self::markForArchival($fetched_contact ?: []); return $fetched_contact; } $compacted = JsonLD::compact($data); - if (empty($compacted['@id'])) { return $fetched_contact; } @@ -207,8 +218,11 @@ class APContact } // Quit if none of the basic values are set - if (empty($apcontact['url']) || empty($apcontact['inbox']) || empty($apcontact['type'])) { + if (empty($apcontact['url']) || empty($apcontact['type']) || (($apcontact['type'] != 'Tombstone') && empty($apcontact['inbox']))) { return $fetched_contact; + } elseif ($apcontact['type'] == 'Tombstone') { + // The "inbox" field must have a content + $apcontact['inbox'] = ''; } // Quit if this doesn't seem to be an account at all diff --git a/src/Model/Contact.php b/src/Model/Contact.php index df21796c18..2908385f58 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -724,7 +724,7 @@ class Contact { // We want just to make sure that we don't delete our "self" contact $contact = DBA::selectFirst('contact', ['uid'], ['id' => $id, 'self' => false]); - if (!DBA::isResult($contact) || !intval($contact['uid'])) { + if (!DBA::isResult($contact)) { return; } @@ -1762,6 +1762,17 @@ class Contact DBA::update('contact', $fields, ['id' => $cids]); } + public static function deleteContactByUrl(string $url) + { + // Update contact data for all users + $condition = ['self' => false, 'nurl' => Strings::normaliseLink($url)]; + $contacts = DBA::select('contact', ['id', 'uid'], $condition); + while ($contact = DBA::fetch($contacts)) { + Logger::info('Deleting contact', ['id' => $contact['id'], 'uid' => $contact['uid'], 'url' => $url]); + self::remove($contact['id']); + } + } + /** * Helper function for "updateFromProbe". Updates personal and public contact * @@ -1914,6 +1925,15 @@ class Contact return false; } + if (!empty($ret['account-type']) && $ret['account-type'] == User::ACCOUNT_TYPE_DELETED) { + Logger::info('Deleted account', ['id' => $id, 'url' => $ret['url'], 'ret' => $ret]); + self::remove($id); + + // Delete all contacts with the same URL + self::deleteContactByUrl($ret['url']); + return true; + } + $uid = $contact['uid']; unset($contact['uid']); diff --git a/src/Model/User.php b/src/Model/User.php index d3f3dfd1a9..6960c5c2cf 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -97,6 +97,7 @@ class User const ACCOUNT_TYPE_NEWS = 2; const ACCOUNT_TYPE_COMMUNITY = 3; const ACCOUNT_TYPE_RELAY = 4; + const ACCOUNT_TYPE_DELETED = 127; /** * @} */ diff --git a/src/Module/Proxy.php b/src/Module/Proxy.php index e1231f0b13..f20b13bcef 100644 --- a/src/Module/Proxy.php +++ b/src/Module/Proxy.php @@ -104,7 +104,7 @@ class Proxy extends BaseModule // It shouldn't happen but it does - spaces in URL $request['url'] = str_replace(' ', '+', $request['url']); - $fetchResult = HTTPSignature::fetchRaw($request['url'], local_user(), true, ['timeout' => 10]); + $fetchResult = HTTPSignature::fetchRaw($request['url'], local_user(), ['timeout' => 10]); $img_str = $fetchResult->getBody(); // If there is an error then return a blank image diff --git a/src/Protocol/ActivityPub.php b/src/Protocol/ActivityPub.php index 19eb8c8bce..45279576b7 100644 --- a/src/Protocol/ActivityPub.php +++ b/src/Protocol/ActivityPub.php @@ -68,7 +68,7 @@ class ActivityPub 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'sensitive' => 'as:sensitive', 'Hashtag' => 'as:Hashtag', 'directMessage' => 'litepub:directMessage']]; - const ACCOUNT_TYPES = ['Person', 'Organization', 'Service', 'Group', 'Application']; + const ACCOUNT_TYPES = ['Person', 'Organization', 'Service', 'Group', 'Application', 'Tombstone']; /** * Checks if the web request is done for the AP protocol * @@ -113,6 +113,9 @@ class ActivityPub case 'Application': $accounttype = User::ACCOUNT_TYPE_RELAY; break; + case 'Tombstone': + $accounttype = User::ACCOUNT_TYPE_DELETED; + break; } return $accounttype; diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php index 5b7bb02a3e..1ede550885 100644 --- a/src/Util/HTTPSignature.php +++ b/src/Util/HTTPSignature.php @@ -376,8 +376,7 @@ class HTTPSignature */ public static function fetch($request, $uid) { - $opts = ['accept_content' => 'application/activity+json, application/ld+json']; - $curlResult = self::fetchRaw($request, $uid, false, $opts); + $curlResult = self::fetchRaw($request, $uid); if (empty($curlResult)) { return false; @@ -410,7 +409,7 @@ class HTTPSignature * @return object CurlResult * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public static function fetchRaw($request, $uid = 0, $binary = false, $opts = []) + public static function fetchRaw($request, $uid = 0, $opts = ['accept_content' => 'application/activity+json, application/ld+json']) { $header = []; diff --git a/src/Worker/RemoveContact.php b/src/Worker/RemoveContact.php index 05771e2dc5..a84cb0d537 100644 --- a/src/Worker/RemoveContact.php +++ b/src/Worker/RemoveContact.php @@ -31,15 +31,21 @@ use Friendica\Model\Photo; */ class RemoveContact { public static function execute($id) { - // Only delete if the contact is to be deleted $contact = DBA::selectFirst('contact', ['uid'], ['deleted' => true, 'id' => $id]); if (!DBA::isResult($contact)) { return; } + Logger::info('Start deleting contact', ['id' => $id]); // Now we delete the contact and all depending tables - $condition = ['uid' => $contact['uid'], 'contact-id' => $id]; + if ($contact['uid'] == 0) { + DBA::delete('post-tag', ['cid' => $id]); + $condition = ["`author-id` = ? OR `owner-id` = ? OR `causer-id` = ? OR `contact-id` = ?", + $id, $id, $id, $id]; + } else { + $condition = ['uid' => $contact['uid'], 'contact-id' => $id]; + } do { $items = Item::select(['id', 'guid'], $condition, ['limit' => 100]); while ($item = Item::fetch($items)) { @@ -49,7 +55,8 @@ class RemoveContact { DBA::close($items); } while (Item::exists($condition)); - Photo::delete(['uid' => $contact['uid'], 'contact-id' => $id]); - DBA::delete('contact', ['id' => $id]); + Photo::delete(['contact-id' => $id]); + $ret = DBA::delete('contact', ['id' => $id]); + Logger::info('Deleted contact', ['id' => $id, 'result' => $ret]); } }