diff --git a/CHANGELOG.md b/CHANGELOG.md index 98dae9e33..f71ca6ff7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,13 @@ - Updated ApiV1Controller, fix unlisted replies. ([c13bca76](https://github.com/pixelfed/pixelfed/commit/c13bca76)) - Updated SearchApiV2Service, filter banned instances. ([281443d7](https://github.com/pixelfed/pixelfed/commit/281443d7)) - Updated DiscoverController, fix favourited state on memories. ([b91747b4](https://github.com/pixelfed/pixelfed/commit/b91747b4)) +- Updated InboxPipeline, fixes #3306. ([20710f4d](https://github.com/pixelfed/pixelfed/commit/20710f4d)) +- Updated inbox workers, fixes #3304. ([cd4f73be](https://github.com/pixelfed/pixelfed/commit/cd4f73be)) +- Updated Inbox, fixes #3305. ([14231632](https://github.com/pixelfed/pixelfed/commit/14231632)) +- Updated Inbox, fixes #3313. ([1c3e72c0](https://github.com/pixelfed/pixelfed/commit/1c3e72c0)) +- Updated Inbox, fixes #3314. ([dfcd2e6d](https://github.com/pixelfed/pixelfed/commit/dfcd2e6d)) +- Updated search service, fix banned instance edge case. ([74018e9c](https://github.com/pixelfed/pixelfed/commit/74018e9c)) +- Updated inbox, fixes #3315. ([c3c3ce18](https://github.com/pixelfed/pixelfed/commit/c3c3ce18)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.11.2 (2022-01-09)](https://github.com/pixelfed/pixelfed/compare/v0.11.1...v0.11.2) diff --git a/app/Jobs/InboxPipeline/InboxValidator.php b/app/Jobs/InboxPipeline/InboxValidator.php index bfc3457d9..4fc1b75a2 100644 --- a/app/Jobs/InboxPipeline/InboxValidator.php +++ b/app/Jobs/InboxPipeline/InboxValidator.php @@ -59,7 +59,7 @@ class InboxValidator implements ShouldQueue // Job processed already return 1; } - Cache::put($lockKey, 1, 300); + Cache::put($lockKey, 1, 3600); } if(!isset($headers['signature']) || !isset($headers['date'])) { @@ -155,6 +155,9 @@ class InboxValidator implements ShouldQueue ) { return; } + if(!isset($bodyDecoded['id'])) { + return; + } $signatureData = HttpSignature::parseSignatureHeader($signature); $keyId = Helpers::validateUrl($signatureData['keyId']); $id = Helpers::validateUrl($bodyDecoded['id']); diff --git a/app/Jobs/InboxPipeline/InboxWorker.php b/app/Jobs/InboxPipeline/InboxWorker.php index a888fd3c6..44e3a1cb0 100644 --- a/app/Jobs/InboxPipeline/InboxWorker.php +++ b/app/Jobs/InboxPipeline/InboxWorker.php @@ -55,7 +55,7 @@ class InboxWorker implements ShouldQueue // Job processed already return 1; } - Cache::put($lockKey, 1, 300); + Cache::put($lockKey, 1, 3600); } if(!isset($headers['signature']) || !isset($headers['date'])) { @@ -145,6 +145,9 @@ class InboxWorker implements ShouldQueue ) { return; } + if(!isset($bodyDecoded['id'])) { + return; + } $signatureData = HttpSignature::parseSignatureHeader($signature); $keyId = Helpers::validateUrl($signatureData['keyId']); $id = Helpers::validateUrl($bodyDecoded['id']); diff --git a/app/Jobs/StatusPipeline/StatusReplyPipeline.php b/app/Jobs/StatusPipeline/StatusReplyPipeline.php index 438d78eba..a64682cb2 100644 --- a/app/Jobs/StatusPipeline/StatusReplyPipeline.php +++ b/app/Jobs/StatusPipeline/StatusReplyPipeline.php @@ -68,6 +68,14 @@ class StatusReplyPipeline implements ShouldQueue return 1; } + if(config('database.default') === 'mysql') { + DB::transaction(function() use($reply) { + $count = DB::select( DB::raw("select id, in_reply_to_id from statuses, (select @pv := :kid) initialisation where id > @pv and find_in_set(in_reply_to_id, @pv) > 0 and @pv := concat(@pv, ',', id)"), [ 'kid' => $reply->id]); + $reply->reply_count = count($count); + $reply->save(); + }); + } + DB::transaction(function() use($target, $actor, $status) { $notification = new Notification(); $notification->profile_id = $target->id; @@ -83,6 +91,15 @@ class StatusReplyPipeline implements ShouldQueue NotificationService::set($notification->profile_id, $notification->id); }); + if($exists = Cache::get('status:replies:all:' . $reply->id)) { + if($exists && $exists->count() == 3) { + } else { + Cache::forget('status:replies:all:' . $reply->id); + } + } else { + Cache::forget('status:replies:all:' . $reply->id); + } + return 1; } diff --git a/app/Services/SearchApiV2Service.php b/app/Services/SearchApiV2Service.php index fdda0da07..ffebb2e40 100644 --- a/app/Services/SearchApiV2Service.php +++ b/app/Services/SearchApiV2Service.php @@ -149,6 +149,9 @@ class SearchApiV2Service ->get() ->map(function($status) { return StatusService::get($status->id); + }) + ->filter(function($status) { + return $status && isset($status['account']); }); return $results; } @@ -188,6 +191,7 @@ class SearchApiV2Service try { $res = ActivityPubFetchService::get($query); + $banned = InstanceService::getBannedDomains(); if($res) { $json = json_decode($res, true); @@ -202,10 +206,14 @@ class SearchApiV2Service switch($json['type']) { case 'Note': $obj = Helpers::statusFetch($query); - if(!$obj) { + if(!$obj || !isset($obj['id'])) { return $default; } - $default['statuses'][] = StatusService::get($obj['id']); + $note = StatusService::get($obj['id']); + if(!$note) { + return $default; + } + $default['statuses'][] = $note; return $default; break; @@ -214,6 +222,9 @@ class SearchApiV2Service if(!$obj) { return $default; } + if(in_array($obj['domain'], $banned)) { + return $default; + } $default['accounts'][] = AccountService::get($obj['id']); return $default; break; diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index b9a4f26e1..b964fa26a 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -434,39 +434,126 @@ class Helpers { ); return $status; } - return DB::transaction(function() use($profile, $res, $url, $ts, $reply_to, $cw, $scope, $id) { - $status = new Status; - $status->profile_id = $profile->id; - $status->url = isset($res['url']) && is_string($res['url']) ? $res['url'] : $url; - $status->uri = isset($res['url']) && is_string($res['url']) ? $res['url'] : $url; - $status->object_url = $id; - $status->caption = strip_tags($res['content']); - $status->rendered = Purify::clean($res['content']); - $status->created_at = Carbon::parse($ts); - $status->in_reply_to_id = $reply_to; - $status->local = false; - $status->is_nsfw = $cw; - $status->scope = $scope; - $status->visibility = $scope; - $status->cw_summary = $cw == true && isset($res['summary']) ? - Purify::clean(strip_tags($res['summary'])) : null; - $status->save(); - if($reply_to == null) { - self::importNoteAttachment($res, $status); - } else { - StatusReplyPipeline::dispatch($status); - } - if(isset($res['tag']) && is_array($res['tag']) && !empty($res['tag'])) { - StatusTagsPipeline::dispatch($res, $status); - } - return $status; - }); + return self::storeStatus($url, $profile, $res); }); return $status; } + public static function storeStatus($url, $profile, $activity) + { + $id = isset($activity['id']) ? self::pluckval($activity['id']) : self::pluckval($activity['url']); + $url = isset($activity['url']) && is_string($activity['url']) ? $activity['url'] : $id; + $idDomain = parse_url($id, PHP_URL_HOST); + $urlDomain = parse_url($url, PHP_URL_HOST); + if(!self::validateUrl($id) || !self::validateUrl($url)) { + return; + } + + $reply_to = self::getReplyTo($activity); + + return DB::transaction(function() use($url, $profile, $activity, $reply_to, $id) { + $ts = self::pluckval($activity['published']); + $scope = self::getScope($activity); + $cw = self::getSensitive($activity); + + $status = new Status; + $status->profile_id = $profile->id; + $status->url = $url; + $status->uri = $url; + $status->object_url = $id; + $status->caption = strip_tags($activity['content']); + $status->rendered = Purify::clean($activity['content']); + $status->created_at = Carbon::parse($ts); + $status->in_reply_to_id = $reply_to; + $status->local = false; + $status->is_nsfw = $cw; + $status->scope = $scope; + $status->visibility = $scope; + $status->cw_summary = $cw == true && isset($activity['summary']) ? + Purify::clean(strip_tags($activity['summary'])) : null; + $status->save(); + + if($reply_to == null) { + self::importNoteAttachment($activity, $status); + } else { + StatusReplyPipeline::dispatch($status); + } + + if(isset($activity['tag']) && is_array($activity['tag']) && !empty($activity['tag'])) { + StatusTagsPipeline::dispatch($activity, $status); + } + return $status; + }); + } + + public static function getSensitive($activity) + { + $id = isset($activity['id']) ? self::pluckval($activity['id']) : self::pluckval($url); + $url = isset($activity['url']) ? self::pluckval($activity['url']) : $id; + $urlDomain = parse_url($url, PHP_URL_HOST); + + $cw = isset($activity['sensitive']) ? (bool) $activity['sensitive'] : false; + + if(in_array($urlDomain, InstanceService::getNsfwDomains())) { + $cw = true; + } + + return $cw; + } + + public static function getReplyTo($activity) + { + $reply_to = null; + $inReplyTo = isset($activity['inReplyTo']) && !empty($activity['inReplyTo']) ? + self::pluckval($activity['inReplyTo']) : + false; + + if($inReplyTo) { + $reply_to = self::statusFirstOrFetch($inReplyTo); + if($reply_to) { + $reply_to = optional($reply_to)->id; + } + } else { + $reply_to = null; + } + + return $reply_to; + } + + public static function getScope($activity) + { + $id = isset($activity['id']) ? self::pluckval($activity['id']) : self::pluckval($url); + $url = isset($activity['url']) ? self::pluckval($activity['url']) : $id; + $urlDomain = parse_url($url, PHP_URL_HOST); + $scope = 'private'; + + if(isset($activity['to']) == true) { + if(is_array($activity['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $activity['to'])) { + $scope = 'public'; + } + if(is_string($activity['to']) && 'https://www.w3.org/ns/activitystreams#Public' == $activity['to']) { + $scope = 'public'; + } + } + + if(isset($activity['cc']) == true) { + if(is_array($activity['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $activity['cc'])) { + $scope = 'unlisted'; + } + if(is_string($activity['cc']) && 'https://www.w3.org/ns/activitystreams#Public' == $activity['cc']) { + $scope = 'unlisted'; + } + } + + if($scope == 'public' && in_array($urlDomain, InstanceService::getUnlistedDomains())) { + $scope = 'unlisted'; + } + + return $scope; + } + private static function storePoll($profile, $res, $url, $ts, $reply_to, $cw, $scope, $id) { if(!isset($res['endTime']) || !isset($res['oneOf']) || !is_array($res['oneOf']) || count($res['oneOf']) > 4) { diff --git a/app/Util/ActivityPub/Inbox.php b/app/Util/ActivityPub/Inbox.php index 68940c502..dd0182d2c 100644 --- a/app/Util/ActivityPub/Inbox.php +++ b/app/Util/ActivityPub/Inbox.php @@ -182,6 +182,9 @@ class Inbox if(!$actor || $actor->domain == null) { return; } + if(!isset($activity['to'])) { + return; + } $to = $activity['to']; $cc = isset($activity['cc']) ? $activity['cc'] : []; @@ -259,10 +262,16 @@ class Inbox } $url = isset($activity['url']) ? $activity['url'] : $activity['id']; + if(Status::whereUrl($url)->exists()) { return; } - Helpers::statusFetch($url); + + Helpers::storeStatus( + $url, + $actor, + $activity + ); return; } @@ -591,6 +600,9 @@ class Inbox DeleteRemoteProfilePipeline::dispatchNow($profile); return; } else { + if(!isset($obj['id'], $this->payload['object'], $this->payload['object']['id'])) { + return; + } $type = $this->payload['object']['type']; $typeCheck = in_array($type, ['Person', 'Tombstone', 'Story']); if(!Helpers::validateUrl($actor) || !Helpers::validateUrl($obj['id']) || !$typeCheck) { @@ -687,6 +699,11 @@ class Inbox $profile = self::actorFirstOrCreate($actor); $obj = $this->payload['object']; + // TODO: Some implementations do not inline the object, skip for now + if(!$obj || !is_array($obj) || !isset($obj['type'])) { + return; + } + switch ($obj['type']) { case 'Accept': break;