Преглед на файлове

Refactor ActivityPub helpers

Daniel Supernault преди 6 месеца
родител
ревизия
12b400707f
променени са 1 файла, в които са добавени 816 реда и са изтрити 428 реда
  1. 816 428
      app/Util/ActivityPub/Helpers.php

+ 816 - 428
app/Util/ActivityPub/Helpers.php

@@ -24,7 +24,6 @@ use App\Status;
 use App\Util\Media\License;
 use Cache;
 use Carbon\Carbon;
-use Illuminate\Support\Str;
 use Illuminate\Validation\Rule;
 use League\Uri\Exceptions\UriException;
 use League\Uri\Uri;
@@ -33,16 +32,32 @@ use Validator;
 
 class Helpers
 {
-    public static function validateObject($data)
+    private const PUBLIC_TIMELINE = 'https://www.w3.org/ns/activitystreams#Public';
+
+    private const CACHE_TTL = 14440;
+
+    private const URL_CACHE_PREFIX = 'helpers:url:';
+
+    private const FETCH_CACHE_TTL = 15;
+
+    private const LOCALHOST_DOMAINS = [
+        'localhost',
+        '127.0.0.1',
+        '::1',
+        'broadcasthost',
+        'ip6-localhost',
+        'ip6-loopback',
+    ];
+
+    /**
+     * Validate an ActivityPub object
+     */
+    public static function validateObject(array $data): bool
     {
         $verbs = ['Create', 'Announce', 'Like', 'Follow', 'Delete', 'Accept', 'Reject', 'Undo', 'Tombstone'];
 
-        $valid = Validator::make($data, [
-            'type' => [
-                'required',
-                'string',
-                Rule::in($verbs),
-            ],
+        return Validator::make($data, [
+            'type' => ['required', 'string', Rule::in($verbs)],
             'id' => 'required|string',
             'actor' => 'required|string|url',
             'object' => 'required',
@@ -50,105 +65,88 @@ class Helpers
             'object.attributedTo' => 'required_if:type,Create|url',
             'published' => 'required_if:type,Create|date',
         ])->passes();
-
-        return $valid;
     }
 
-    public static function verifyAttachments($data)
+    /**
+     * Validate media attachments
+     */
+    public static function verifyAttachments(array $data): bool
     {
         if (! isset($data['object']) || empty($data['object'])) {
             $data = ['object' => $data];
         }
 
         $activity = $data['object'];
-
         $mimeTypes = explode(',', config_cache('pixelfed.media_types'));
-        $mediaTypes = in_array('video/mp4', $mimeTypes) ? ['Document', 'Image', 'Video'] : ['Document', 'Image'];
-
-        // Peertube
-        // $mediaTypes = in_array('video/mp4', $mimeTypes) ? ['Document', 'Image', 'Video', 'Link'] : ['Document', 'Image'];
+        $mediaTypes = in_array('video/mp4', $mimeTypes) ?
+            ['Document', 'Image', 'Video'] :
+            ['Document', 'Image'];
 
         if (! isset($activity['attachment']) || empty($activity['attachment'])) {
             return false;
         }
 
-        // peertube
-        // $attachment = is_array($activity['url']) ?
-        //  collect($activity['url'])
-        //  ->filter(function($media) {
-        //      return $media['type'] == 'Link' && $media['mediaType'] == 'video/mp4';
-        //  })
-        //  ->take(1)
-        //  ->values()
-        //  ->toArray()[0] : $activity['attachment'];
-
-        $attachment = $activity['attachment'];
-
-        $valid = Validator::make($attachment, [
-            '*.type' => [
-                'required',
-                'string',
-                Rule::in($mediaTypes),
-            ],
+        return Validator::make($activity['attachment'], [
+            '*.type' => ['required', 'string', Rule::in($mediaTypes)],
             '*.url' => 'required|url',
-            '*.mediaType' => [
-                'required',
-                'string',
-                Rule::in($mimeTypes),
-            ],
+            '*.mediaType' => ['required', 'string', Rule::in($mimeTypes)],
             '*.name' => 'sometimes|nullable|string',
             '*.blurhash' => 'sometimes|nullable|string|min:6|max:164',
             '*.width' => 'sometimes|nullable|integer|min:1|max:5000',
             '*.height' => 'sometimes|nullable|integer|min:1|max:5000',
         ])->passes();
-
-        return $valid;
     }
 
-    public static function normalizeAudience($data, $localOnly = true)
+    /**
+     * Normalize ActivityPub audience
+     */
+    public static function normalizeAudience(array $data, bool $localOnly = true): ?array
     {
         if (! isset($data['to'])) {
-            return;
+            return null;
         }
 
-        $audience = [];
-        $audience['to'] = [];
-        $audience['cc'] = [];
-        $scope = 'private';
+        $audience = [
+            'to' => [],
+            'cc' => [],
+            'scope' => 'private',
+        ];
 
         if (is_array($data['to']) && ! empty($data['to'])) {
             foreach ($data['to'] as $to) {
-                if ($to == 'https://www.w3.org/ns/activitystreams#Public') {
-                    $scope = 'public';
+                if ($to == self::PUBLIC_TIMELINE) {
+                    $audience['scope'] = 'public';
 
                     continue;
                 }
                 $url = $localOnly ? self::validateLocalUrl($to) : self::validateUrl($to);
-                if ($url != false) {
-                    array_push($audience['to'], $url);
+                if ($url) {
+                    $audience['to'][] = $url;
                 }
             }
         }
 
         if (is_array($data['cc']) && ! empty($data['cc'])) {
             foreach ($data['cc'] as $cc) {
-                if ($cc == 'https://www.w3.org/ns/activitystreams#Public') {
-                    $scope = 'unlisted';
+                if ($cc == self::PUBLIC_TIMELINE) {
+                    $audience['scope'] = 'unlisted';
 
                     continue;
                 }
                 $url = $localOnly ? self::validateLocalUrl($cc) : self::validateUrl($cc);
-                if ($url != false) {
-                    array_push($audience['cc'], $url);
+                if ($url) {
+                    $audience['cc'][] = $url;
                 }
             }
         }
-        $audience['scope'] = $scope;
 
         return $audience;
     }
 
-    public static function userInAudience($profile, $data)
+    /**
+     * Check if user is in audience
+     */
+    public static function userInAudience(Profile $profile, array $data): bool
     {
         $audience = self::normalizeAudience($data);
         $url = $profile->permalink();
@@ -156,96 +154,167 @@ class Helpers
         return in_array($url, $audience['to']) || in_array($url, $audience['cc']);
     }
 
-    public static function validateUrl($url = null, $disableDNSCheck = false, $forceBanCheck = false)
+    /**
+     * Validate URL with various security and format checks
+     */
+    public static function validateUrl(?string $url, bool $disableDNSCheck = false, bool $forceBanCheck = false): string|bool
     {
-        if (is_array($url) && ! empty($url)) {
-            $url = $url[0];
-        }
-        if (! $url || strlen($url) === 0) {
+        if (! $normalizedUrl = self::normalizeUrl($url)) {
             return false;
         }
+
         try {
-            $uri = Uri::new($url);
+            $uri = Uri::new($normalizedUrl);
 
-            if (! $uri) {
+            if (! self::isValidUri($uri)) {
                 return false;
             }
 
-            if ($uri->getScheme() !== 'https') {
+            $host = $uri->getHost();
+            if (! self::isValidHost($host)) {
                 return false;
             }
 
-            $host = $uri->getHost();
-
-            if (! $host || $host === '') {
+            if (! self::passesSecurityChecks($host, $disableDNSCheck, $forceBanCheck)) {
                 return false;
             }
 
-            if (! filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
+            return $uri->toString();
+
+        } catch (UriException $e) {
+            return false;
+        }
+    }
+
+    /**
+     * Normalize URL input
+     */
+    private static function normalizeUrl(?string $url): ?string
+    {
+        if (is_array($url) && ! empty($url)) {
+            $url = $url[0];
+        }
+
+        return (! $url || strlen($url) === 0) ? null : $url;
+    }
+
+    /**
+     * Validate basic URI requirements
+     */
+    private static function isValidUri(Uri $uri): bool
+    {
+        return $uri && $uri->getScheme() === 'https';
+    }
+
+    /**
+     * Validate host requirements
+     */
+    private static function isValidHost(?string $host): bool
+    {
+        if (! $host || $host === '') {
+            return false;
+        }
+
+        if (! filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
+            return false;
+        }
+
+        if (! str_contains($host, '.')) {
+            return false;
+        }
+
+        if (in_array($host, self::LOCALHOST_DOMAINS)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Check DNS and banned status if required
+     */
+    private static function passesSecurityChecks(string $host, bool $disableDNSCheck, bool $forceBanCheck): bool
+    {
+        if ($disableDNSCheck !== true && self::shouldCheckDNS()) {
+            if (! self::hasValidDNS($host)) {
                 return false;
             }
+        }
 
-            if (! str_contains($host, '.')) {
+        if ($forceBanCheck || self::shouldCheckBans()) {
+            if (self::isHostBanned($host)) {
                 return false;
             }
+        }
 
-            $localhosts = [
-                'localhost',
-                '127.0.0.1',
-                '::1',
-                'broadcasthost',
-                'ip6-localhost',
-                'ip6-loopback',
-            ];
+        return true;
+    }
 
-            if (in_array($host, $localhosts)) {
-                return false;
-            }
+    /**
+     * Check if DNS validation is required
+     */
+    private static function shouldCheckDNS(): bool
+    {
+        return app()->environment() === 'production' &&
+               (bool) config('security.url.verify_dns');
+    }
 
-            if ($disableDNSCheck !== true && app()->environment() === 'production' && (bool) config('security.url.verify_dns')) {
-                $hash = hash('sha256', $host);
-                $key = "helpers:url:valid-dns:sha256-{$hash}";
-                $domainValidDns = Cache::remember($key, 14440, function () use ($host) {
-                    return DomainService::hasValidDns($host);
-                });
-                if (! $domainValidDns) {
-                    return false;
-                }
-            }
+    /**
+     * Validate domain DNS records
+     */
+    private static function hasValidDNS(string $host): bool
+    {
+        $hash = hash('sha256', $host);
+        $key = self::URL_CACHE_PREFIX."valid-dns:sha256-{$hash}";
 
-            if ($forceBanCheck || $disableDNSCheck !== true && app()->environment() === 'production') {
-                $bannedInstances = InstanceService::getBannedDomains();
-                if (in_array($host, $bannedInstances)) {
-                    return false;
-                }
-            }
+        return Cache::remember($key, self::CACHE_TTL, function () use ($host) {
+            return DomainService::hasValidDns($host);
+        });
+    }
 
-            return $uri->toString();
-        } catch (UriException $e) {
-            return false;
-        }
+    /**
+     * Check if domain bans should be validated
+     */
+    private static function shouldCheckBans(): bool
+    {
+        return app()->environment() === 'production';
+    }
+
+    /**
+     * Check if host is in banned domains list
+     */
+    private static function isHostBanned(string $host): bool
+    {
+        $bannedInstances = InstanceService::getBannedDomains();
+
+        return in_array($host, $bannedInstances);
     }
 
-    public static function validateLocalUrl($url)
+    /**
+     * Validate local URL
+     */
+    private static function validateLocalUrl(string $url): string|bool
     {
         $url = self::validateUrl($url);
-        if ($url == true) {
+        if ($url) {
             $domain = config('pixelfed.domain.app');
-
             $uri = Uri::new($url);
             $host = $uri->getHost();
+
             if (! $host || empty($host)) {
                 return false;
             }
-            $url = strtolower($domain) === strtolower($host) ? $url : false;
 
-            return $url;
+            return strtolower($domain) === strtolower($host) ? $url : false;
         }
 
         return false;
     }
 
-    public static function zttpUserAgent()
+    /**
+     * Get user agent string
+     */
+    public static function zttpUserAgent(): array
     {
         $version = config('pixelfed.version');
         $url = config('app.url');
@@ -314,298 +383,372 @@ class Helpers
         }
     }
 
-    public static function statusFirstOrFetch($url, $replyTo = false)
+    /**
+     * Fetch or create a status from URL
+     */
+    public static function statusFirstOrFetch(string $url, bool $replyTo = false): ?Status
     {
-        $url = self::validateUrl($url);
-        if ($url == false) {
-            return;
+        if (! $validUrl = self::validateUrl($url)) {
+            return null;
         }
 
+        if ($status = self::findExistingStatus($url)) {
+            return $status;
+        }
+
+        return self::createStatusFromUrl($url, $replyTo);
+    }
+
+    /**
+     * Find existing status by URL
+     */
+    private static function findExistingStatus(string $url): ?Status
+    {
         $host = parse_url($url, PHP_URL_HOST);
-        $local = config('pixelfed.domain.app') == $host ? true : false;
 
-        if ($local) {
+        if (self::isLocalDomain($host)) {
             $id = (int) last(explode('/', $url));
 
-            return Status::whereNotIn('scope', ['draft', 'archived'])->findOrFail($id);
+            return Status::whereNotIn('scope', ['draft', 'archived'])
+                ->findOrFail($id);
         }
 
-        $cached = Status::whereNotIn('scope', ['draft', 'archived'])
-            ->whereUri($url)
-            ->orWhere('object_url', $url)
+        return Status::whereNotIn('scope', ['draft', 'archived'])
+            ->where(function ($query) use ($url) {
+                $query->whereUri($url)
+                    ->orWhere('object_url', $url);
+            })
             ->first();
+    }
 
-        if ($cached) {
-            return $cached;
-        }
-
+    /**
+     * Create a new status from ActivityPub data
+     */
+    private static function createStatusFromUrl(string $url, bool $replyTo): ?Status
+    {
         $res = self::fetchFromUrl($url);
 
-        if (! $res || empty($res) || isset($res['error']) || ! isset($res['@context']) || ! isset($res['published'])) {
-            return;
+        if (! $res || ! self::isValidStatusData($res)) {
+            return null;
         }
 
         if (! self::validateTimestamp($res['published'])) {
-            return;
+            return null;
         }
 
-        if (config('autospam.live_filters.enabled')) {
-            $filters = config('autospam.live_filters.filters');
-            if (! empty($filters) && isset($res['content']) && ! empty($res['content']) && strlen($filters) > 3) {
-                $filters = array_map('trim', explode(',', $filters));
-                $content = $res['content'];
-                foreach ($filters as $filter) {
-                    $filter = trim(strtolower($filter));
-                    if (! $filter || ! strlen($filter)) {
-                        continue;
-                    }
-                    if (str_contains(strtolower($content), $filter)) {
-                        return;
-                    }
-                }
-            }
+        if (! self::passesContentFilters($res)) {
+            return null;
         }
 
-        if (isset($res['object'])) {
-            $activity = $res;
-        } else {
-            $activity = ['object' => $res];
+        $activity = isset($res['object']) ? $res : ['object' => $res];
+
+        if (! $profile = self::getStatusProfile($activity)) {
+            return null;
         }
 
-        $scope = 'private';
+        if (! self::validateStatusUrls($url, $activity)) {
+            return null;
+        }
 
-        $cw = isset($res['sensitive']) ? (bool) $res['sensitive'] : false;
+        $reply_to = self::getReplyToId($activity, $profile, $replyTo);
+        $scope = self::getScope($activity, $url);
+        $cw = self::getSensitive($activity, $url);
 
-        if (isset($res['to']) == true) {
-            if (is_array($res['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['to'])) {
-                $scope = 'public';
-            }
-            if (is_string($res['to']) && $res['to'] == 'https://www.w3.org/ns/activitystreams#Public') {
-                $scope = 'public';
-            }
+        if ($res['type'] === 'Question') {
+            return self::storePoll(
+                $profile,
+                $res,
+                $url,
+                $res['published'],
+                $reply_to,
+                $cw,
+                $scope,
+                $activity['id'] ?? $url
+            );
         }
 
-        if (isset($res['cc']) == true) {
-            if (is_array($res['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['cc'])) {
-                $scope = 'unlisted';
-            }
-            if (is_string($res['cc']) && $res['cc'] == 'https://www.w3.org/ns/activitystreams#Public') {
-                $scope = 'unlisted';
-            }
-        }
+        return self::storeStatus($url, $profile, $res);
+    }
 
-        if (config('costar.enabled') == true) {
-            $blockedKeywords = config('costar.keyword.block');
-            if ($blockedKeywords !== null) {
-                $keywords = config('costar.keyword.block');
-                foreach ($keywords as $kw) {
-                    if (Str::contains($res['content'], $kw) == true) {
-                        return;
-                    }
-                }
-            }
+    /**
+     * Validate status data
+     */
+    private static function isValidStatusData(?array $res): bool
+    {
+        return $res &&
+               ! empty($res) &&
+               ! isset($res['error']) &&
+               isset($res['@context']) &&
+               isset($res['published']);
+    }
 
-            $unlisted = config('costar.domain.unlisted');
-            if (in_array(parse_url($url, PHP_URL_HOST), $unlisted) == true) {
-                $unlisted = true;
-                $scope = 'unlisted';
-            } else {
-                $unlisted = false;
-            }
+    /**
+     * Check if content passes filters
+     */
+    private static function passesContentFilters(array $res): bool
+    {
+        if (! config('autospam.live_filters.enabled')) {
+            return true;
+        }
 
-            $cwDomains = config('costar.domain.cw');
-            if (in_array(parse_url($url, PHP_URL_HOST), $cwDomains) == true) {
-                $cw = true;
-            }
+        $filters = config('autospam.live_filters.filters');
+        if (empty($filters) || ! isset($res['content']) || strlen($filters) <= 3) {
+            return true;
         }
 
-        $id = isset($res['id']) ? self::pluckval($res['id']) : self::pluckval($url);
-        $idDomain = parse_url($id, PHP_URL_HOST);
-        $urlDomain = parse_url($url, PHP_URL_HOST);
+        $filters = array_map('trim', explode(',', $filters));
+        $content = strtolower($res['content']);
 
-        if ($idDomain && $urlDomain && strtolower($idDomain) !== strtolower($urlDomain)) {
-            return;
+        foreach ($filters as $filter) {
+            $filter = trim(strtolower($filter));
+            if ($filter && str_contains($content, $filter)) {
+                return false;
+            }
         }
 
-        if (! self::validateUrl($id)) {
-            return;
-        }
+        return true;
+    }
 
+    /**
+     * Get profile for status
+     */
+    private static function getStatusProfile(array $activity): ?Profile
+    {
         if (! isset($activity['object']['attributedTo'])) {
-            return;
+            return null;
         }
 
-        $attributedTo = is_string($activity['object']['attributedTo']) ?
-            $activity['object']['attributedTo'] :
-            (is_array($activity['object']['attributedTo']) ?
-                collect($activity['object']['attributedTo'])
-                    ->filter(function ($o) {
-                        return $o && isset($o['type']) && $o['type'] == 'Person';
-                    })
-                    ->pluck('id')
-                    ->first() : null
-            );
+        $attributedTo = self::extractAttributedTo($activity['object']['attributedTo']);
 
-        if ($attributedTo) {
-            $actorDomain = parse_url($attributedTo, PHP_URL_HOST);
-            if (! self::validateUrl($attributedTo) ||
-                $idDomain !== $actorDomain ||
-                $actorDomain !== $urlDomain
-            ) {
-                return;
-            }
+        return $attributedTo ? self::profileFirstOrNew($attributedTo) : null;
+    }
+
+    /**
+     * Extract attributed to value
+     */
+    private static function extractAttributedTo(string|array $attributedTo): ?string
+    {
+        if (is_string($attributedTo)) {
+            return $attributedTo;
         }
 
-        if ($idDomain !== $urlDomain) {
-            return;
+        if (is_array($attributedTo)) {
+            return collect($attributedTo)
+                ->filter(fn ($o) => $o && isset($o['type']) && $o['type'] == 'Person')
+                ->pluck('id')
+                ->first();
         }
 
-        $profile = self::profileFirstOrNew($attributedTo);
+        return null;
+    }
 
-        if (! $profile) {
-            return;
-        }
+    /**
+     * Validate status URLs match
+     */
+    private static function validateStatusUrls(string $url, array $activity): bool
+    {
+        $id = isset($activity['id']) ?
+            self::pluckval($activity['id']) :
+            self::pluckval($url);
 
-        if (isset($activity['object']['inReplyTo']) && ! empty($activity['object']['inReplyTo']) || $replyTo == true) {
-            $reply_to = self::statusFirstOrFetch(self::pluckval($activity['object']['inReplyTo']), false);
-            if ($reply_to) {
-                $blocks = UserFilterService::blocks($reply_to->profile_id);
-                if (in_array($profile->id, $blocks)) {
-                    return;
-                }
-            }
-            $reply_to = optional($reply_to)->id;
-        } else {
-            $reply_to = null;
-        }
-        $ts = self::pluckval($res['published']);
+        $idDomain = parse_url($id, PHP_URL_HOST);
+        $urlDomain = parse_url($url, PHP_URL_HOST);
 
-        if ($scope == 'public' && in_array($urlDomain, InstanceService::getUnlistedDomains())) {
-            $scope = 'unlisted';
-        }
+        return $idDomain &&
+               $urlDomain &&
+               strtolower($idDomain) === strtolower($urlDomain);
+    }
 
-        if (in_array($urlDomain, InstanceService::getNsfwDomains())) {
-            $cw = true;
+    /**
+     * Get reply-to status ID
+     */
+    private static function getReplyToId(array $activity, Profile $profile, bool $replyTo): ?int
+    {
+        $inReplyTo = $activity['object']['inReplyTo'] ?? null;
+
+        if (! $inReplyTo && ! $replyTo) {
+            return null;
         }
 
-        if ($res['type'] === 'Question') {
-            $status = self::storePoll(
-                $profile,
-                $res,
-                $url,
-                $ts,
-                $reply_to,
-                $cw,
-                $scope,
-                $id
-            );
+        $reply = self::statusFirstOrFetch(self::pluckval($inReplyTo), false);
 
-            return $status;
-        } else {
-            $status = self::storeStatus($url, $profile, $res);
+        if (! $reply) {
+            return null;
         }
 
-        return $status;
+        $blocks = UserFilterService::blocks($reply->profile_id);
+
+        return in_array($profile->id, $blocks) ? null : $reply->id;
     }
 
-    public static function storeStatus($url, $profile, $activity)
+    /**
+     * Store a new regular status
+     */
+    public static function storeStatus(string $url, Profile $profile, array $activity): Status
     {
         $originalUrl = $url;
-        $id = isset($activity['id']) ? self::pluckval($activity['id']) : self::pluckval($activity['url']);
-        $url = isset($activity['url']) && is_string($activity['url']) ? self::pluckval($activity['url']) : self::pluckval($id);
-        $idDomain = parse_url($id, PHP_URL_HOST);
-        $urlDomain = parse_url($url, PHP_URL_HOST);
-        $originalUrlDomain = parse_url($originalUrl, PHP_URL_HOST);
-        if (! self::validateUrl($id) || ! self::validateUrl($url)) {
-            return;
-        }
+        $id = self::getStatusId($activity, $url);
+        $url = self::getStatusUrl($activity, $id);
 
-        if (strtolower($originalUrlDomain) !== strtolower($idDomain) ||
-            strtolower($originalUrlDomain) !== strtolower($urlDomain)) {
-            return;
+        if (! self::validateStatusDomains($originalUrl, $id, $url)) {
+            throw new \Exception('Invalid status domains');
         }
 
         $reply_to = self::getReplyTo($activity);
-
         $ts = self::pluckval($activity['published']);
         $scope = self::getScope($activity, $url);
+        $commentsDisabled = $activity['commentsEnabled'] ?? false;
         $cw = self::getSensitive($activity, $url);
-        $pid = is_object($profile) ? $profile->id : (is_array($profile) ? $profile['id'] : null);
-        $isUnlisted = is_object($profile) ? $profile->unlisted : (is_array($profile) ? $profile['unlisted'] : false);
-        $commentsDisabled = isset($activity['commentsEnabled']) ? ! boolval($activity['commentsEnabled']) : false;
 
-        if (! $pid) {
-            return;
+        if ($profile->unlisted) {
+            $scope = 'unlisted';
         }
 
-        if ($scope == 'public') {
-            if ($isUnlisted == true) {
-                $scope = 'unlisted';
+        $status = self::createOrUpdateStatus($url, $profile, $id, $activity, $ts, $reply_to, $cw, $scope, $commentsDisabled);
+
+        if ($reply_to === null) {
+            self::importNoteAttachment($activity, $status);
+        } else {
+            if (isset($activity['attachment']) && ! empty($activity['attachment'])) {
+                self::importNoteAttachment($activity, $status);
             }
+            StatusReplyPipeline::dispatch($status);
+        }
+
+        if (isset($activity['tag']) && is_array($activity['tag']) && ! empty($activity['tag'])) {
+            StatusTagsPipeline::dispatch($activity, $status);
+        }
+
+        self::handleStatusPostProcessing($status, $profile->id, $url);
+
+        return $status;
+    }
+
+    /**
+     * Get status ID from activity
+     */
+    private static function getStatusId(array $activity, string $url): string
+    {
+        return isset($activity['id']) ?
+            self::pluckval($activity['id']) :
+            self::pluckval($url);
+    }
+
+    /**
+     * Get status URL from activity
+     */
+    private static function getStatusUrl(array $activity, string $id): string
+    {
+        return isset($activity['url']) && is_string($activity['url']) ?
+            self::pluckval($activity['url']) :
+            self::pluckval($id);
+    }
+
+    /**
+     * Validate status domain consistency
+     */
+    private static function validateStatusDomains(string $originalUrl, string $id, string $url): bool
+    {
+        if (! self::validateUrl($id) || ! self::validateUrl($url)) {
+            return false;
         }
-        $defaultCaption = config_cache('database.default') === 'mysql' ? null : "";
-        $status = Status::updateOrCreate(
+
+        $originalDomain = parse_url($originalUrl, PHP_URL_HOST);
+        $idDomain = parse_url($id, PHP_URL_HOST);
+        $urlDomain = parse_url($url, PHP_URL_HOST);
+
+        return strtolower($originalDomain) === strtolower($idDomain) &&
+               strtolower($originalDomain) === strtolower($urlDomain);
+    }
+
+    /**
+     * Create or update status record
+     */
+    private static function createOrUpdateStatus(
+        string $url,
+        Profile $profile,
+        string $id,
+        array $activity,
+        string $ts,
+        ?int $reply_to,
+        bool $cw,
+        string $scope,
+        bool $commentsDisabled
+    ): Status {
+        $caption = isset($activity['content']) ?
+            Purify::clean($activity['content']) :
+            '';
+
+        return Status::updateOrCreate(
+            ['uri' => $url],
             [
-                'uri' => $url,
-            ], [
-                'profile_id' => $pid,
+                'profile_id' => $profile->id,
                 'url' => $url,
                 'object_url' => $id,
-                'caption' => isset($activity['content']) ? Purify::clean(strip_tags($activity['content'])) : $defaultCaption,
-                'rendered' => $defaultCaption,
+                'caption' => strip_tags($caption),
+                'rendered' => $caption,
                 'created_at' => Carbon::parse($ts)->tz('UTC'),
                 'in_reply_to_id' => $reply_to,
                 'local' => false,
                 'is_nsfw' => $cw,
                 'scope' => $scope,
                 'visibility' => $scope,
-                'cw_summary' => ($cw == true && isset($activity['summary']) ?
-                    Purify::clean(strip_tags($activity['summary'])) : null),
+                'cw_summary' => ($cw && isset($activity['summary'])) ?
+                    Purify::clean(strip_tags($activity['summary'])) :
+                    null,
                 'comments_disabled' => $commentsDisabled,
             ]
         );
+    }
 
-        if ($reply_to == null) {
-            self::importNoteAttachment($activity, $status);
-        } else {
-            if (isset($activity['attachment']) && ! empty($activity['attachment'])) {
-                self::importNoteAttachment($activity, $status);
-            }
-            StatusReplyPipeline::dispatch($status);
-        }
-
-        if (isset($activity['tag']) && is_array($activity['tag']) && ! empty($activity['tag'])) {
-            StatusTagsPipeline::dispatch($activity, $status);
-        }
-
+    /**
+     * Handle post-creation status processing
+     */
+    private static function handleStatusPostProcessing(Status $status, int $profileId, string $url): void
+    {
         if (config('instance.timeline.network.cached') &&
-            $status->in_reply_to_id === null &&
-            $status->reblog_of_id === null &&
-            in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) &&
-            $status->created_at->gt(now()->subHours(config('instance.timeline.network.max_hours_old'))) &&
-            (config('instance.hide_nsfw_on_public_feeds') == true ? $status->is_nsfw == false : true)
+            self::isEligibleForNetwork($status)
         ) {
-            $filteredDomains = collect(InstanceService::getBannedDomains())
-                ->merge(InstanceService::getUnlistedDomains())
-                ->unique()
-                ->values()
-                ->toArray();
+            $urlDomain = parse_url($url, PHP_URL_HOST);
+            $filteredDomains = self::getFilteredDomains();
+
             if (! in_array($urlDomain, $filteredDomains)) {
-                if (! $isUnlisted) {
-                    NetworkTimelineService::add($status->id);
-                }
+                NetworkTimelineService::add($status->id);
             }
         }
 
-        AccountStatService::incrementPostCount($pid);
+        AccountStatService::incrementPostCount($profileId);
 
         if ($status->in_reply_to_id === null &&
             in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
         ) {
-            FeedInsertRemotePipeline::dispatch($status->id, $pid)->onQueue('feed');
+            FeedInsertRemotePipeline::dispatch($status->id, $profileId)
+                ->onQueue('feed');
         }
+    }
 
-        return $status;
+    /**
+     * Check if status is eligible for network timeline
+     */
+    private static function isEligibleForNetwork(Status $status): bool
+    {
+        return $status->in_reply_to_id === null &&
+               $status->reblog_of_id === null &&
+               in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) &&
+               $status->created_at->gt(now()->subHours(config('instance.timeline.network.max_hours_old'))) &&
+               (config('instance.hide_nsfw_on_public_feeds') ? ! $status->is_nsfw : true);
+    }
+
+    /**
+     * Get filtered domains list
+     */
+    private static function getFilteredDomains(): array
+    {
+        return collect(InstanceService::getBannedDomains())
+            ->merge(InstanceService::getUnlistedDomains())
+            ->unique()
+            ->values()
+            ->toArray();
     }
 
     public static function getSensitive($activity, $url)
@@ -689,14 +832,14 @@ class Helpers
             return $option['replies']['totalItems'] ?? 0;
         })->toArray();
 
-        $defaultCaption = "";
+        $defaultCaption = '';
         $status = new Status;
         $status->profile_id = $profile->id;
         $status->url = isset($res['url']) ? $res['url'] : $url;
         $status->uri = isset($res['url']) ? $res['url'] : $url;
         $status->object_url = $id;
         $status->caption = strip_tags(Purify::clean($res['content'])) ?? $defaultCaption;
-        $status->rendered = $defaultCaption;
+        $status->rendered = Purify::clean($res['content'] ?? $defaultCaption);
         $status->created_at = Carbon::parse($ts)->tz('UTC');
         $status->in_reply_to_id = null;
         $status->local = false;
@@ -730,185 +873,430 @@ class Helpers
         return self::statusFirstOrFetch($url);
     }
 
-    public static function importNoteAttachment($data, Status $status)
+    /**
+     * Process and store note attachments
+     */
+    public static function importNoteAttachment(array $data, Status $status): void
     {
-        if (self::verifyAttachments($data) == false) {
-            // \Log::info('importNoteAttachment::failedVerification.', [$data['id']]);
+        if (! self::verifyAttachments($data)) {
             $status->viewType();
 
             return;
         }
-        $attachments = isset($data['object']) ? $data['object']['attachment'] : $data['attachment'];
-        // peertube
-        // if(!$attachments) {
-        //  $obj = isset($data['object']) ? $data['object'] : $data;
-        //  $attachments = is_array($obj['url']) ? $obj['url'] : null;
-        // }
-        $user = $status->profile;
-        $storagePath = MediaPathService::get($user, 2);
-        $allowed = explode(',', config_cache('pixelfed.media_types'));
+
+        $attachments = self::getAttachments($data);
+        $profile = $status->profile;
+        $storagePath = MediaPathService::get($profile, 2);
+        $allowedTypes = explode(',', config_cache('pixelfed.media_types'));
 
         foreach ($attachments as $key => $media) {
-            $type = $media['mediaType'];
-            $url = $media['url'];
-            $valid = self::validateUrl($url);
-            if (in_array($type, $allowed) == false || $valid == false) {
+            if (! self::isValidAttachment($media, $allowedTypes)) {
                 continue;
             }
-            $blurhash = isset($media['blurhash']) ? $media['blurhash'] : null;
-            $license = isset($media['license']) ? License::nameToId($media['license']) : null;
-            $caption = isset($media['name']) ? Purify::clean($media['name']) : null;
-            $width = isset($media['width']) ? $media['width'] : false;
-            $height = isset($media['height']) ? $media['height'] : false;
-
-            $media = new Media;
-            $media->blurhash = $blurhash;
-            $media->remote_media = true;
-            $media->status_id = $status->id;
-            $media->profile_id = $status->profile_id;
-            $media->user_id = null;
-            $media->media_path = $url;
-            $media->remote_url = $url;
-            $media->caption = $caption;
-            $media->order = $key + 1;
-            if ($width) {
-                $media->width = $width;
-            }
-            if ($height) {
-                $media->height = $height;
-            }
-            if ($license) {
-                $media->license = $license;
-            }
-            $media->mime = $type;
-            $media->version = 3;
-            $media->save();
 
-            if ((bool) config_cache('pixelfed.cloud_storage') == true) {
-                MediaStoragePipeline::dispatch($media);
-            }
+            $mediaModel = self::createMediaAttachment($media, $status, $key);
+            self::handleMediaStorage($mediaModel);
         }
 
         $status->viewType();
+    }
 
+    /**
+     * Get attachments from ActivityPub data
+     */
+    private static function getAttachments(array $data): array
+    {
+        return isset($data['object']) ?
+            $data['object']['attachment'] :
+            $data['attachment'];
     }
 
-    public static function profileFirstOrNew($url)
+    /**
+     * Validate individual attachment
+     */
+    private static function isValidAttachment(array $media, array $allowedTypes): bool
     {
-        $url = self::validateUrl($url);
-        if ($url == false) {
-            return;
+        $type = $media['mediaType'];
+        $url = $media['url'];
+
+        return in_array($type, $allowedTypes) &&
+               self::validateUrl($url);
+    }
+
+    /**
+     * Create media attachment record
+     */
+    private static function createMediaAttachment(array $media, Status $status, int $key): Media
+    {
+        $mediaModel = new Media;
+
+        self::setBasicMediaAttributes($mediaModel, $media, $status, $key);
+        self::setOptionalMediaAttributes($mediaModel, $media);
+
+        $mediaModel->save();
+
+        return $mediaModel;
+    }
+
+    /**
+     * Set basic media attributes
+     */
+    private static function setBasicMediaAttributes(Media $media, array $data, Status $status, int $key): void
+    {
+        $media->remote_media = true;
+        $media->status_id = $status->id;
+        $media->profile_id = $status->profile_id;
+        $media->user_id = null;
+        $media->media_path = $data['url'];
+        $media->remote_url = $data['url'];
+        $media->mime = $data['mediaType'];
+        $media->version = 3;
+        $media->order = $key + 1;
+    }
+
+    /**
+     * Set optional media attributes
+     */
+    private static function setOptionalMediaAttributes(Media $media, array $data): void
+    {
+        $media->blurhash = $data['blurhash'] ?? null;
+        $media->caption = isset($data['name']) ?
+            Purify::clean($data['name']) :
+            null;
+
+        if (isset($data['width'])) {
+            $media->width = $data['width'];
         }
 
-        $host = parse_url($url, PHP_URL_HOST);
-        $local = config('pixelfed.domain.app') == $host ? true : false;
+        if (isset($data['height'])) {
+            $media->height = $data['height'];
+        }
 
-        if ($local == true) {
-            $id = last(explode('/', $url));
+        if (isset($data['license'])) {
+            $media->license = License::nameToId($data['license']);
+        }
+    }
 
-            return Profile::whereNull('status')
-                ->whereNull('domain')
-                ->whereUsername($id)
-                ->firstOrFail();
+    /**
+     * Handle media storage processing
+     */
+    private static function handleMediaStorage(Media $media): void
+    {
+        if ((bool) config_cache('pixelfed.cloud_storage')) {
+            MediaStoragePipeline::dispatch($media);
         }
+    }
 
-        if ($profile = Profile::whereRemoteUrl($url)->first()) {
-            if ($profile->last_fetched_at && $profile->last_fetched_at->lt(now()->subHours(24))) {
-                return self::profileUpdateOrCreate($url);
-            }
+    /**
+     * Validate attachment collection
+     */
+    private static function validateAttachmentCollection(array $attachments, array $mediaTypes, array $mimeTypes): bool
+    {
+        return Validator::make($attachments, [
+            '*.type' => [
+                'required',
+                'string',
+                Rule::in($mediaTypes),
+            ],
+            '*.url' => 'required|url',
+            '*.mediaType' => [
+                'required',
+                'string',
+                Rule::in($mimeTypes),
+            ],
+            '*.name' => 'sometimes|nullable|string',
+            '*.blurhash' => 'sometimes|nullable|string|min:6|max:164',
+            '*.width' => 'sometimes|nullable|integer|min:1|max:5000',
+            '*.height' => 'sometimes|nullable|integer|min:1|max:5000',
+        ])->passes();
+    }
 
-            return $profile;
+    /**
+     * Get supported media types
+     */
+    private static function getSupportedMediaTypes(): array
+    {
+        $mimeTypes = explode(',', config_cache('pixelfed.media_types'));
+
+        return in_array('video/mp4', $mimeTypes) ?
+            ['Document', 'Image', 'Video'] :
+            ['Document', 'Image'];
+    }
+
+    /**
+     * Process specific media type attachment
+     */
+    private static function processMediaTypeAttachment(array $media, Status $status, int $order): ?Media
+    {
+        if (! self::isValidMediaType($media)) {
+            return null;
         }
 
-        return self::profileUpdateOrCreate($url);
+        $mediaModel = new Media;
+        self::setMediaAttributes($mediaModel, $media, $status, $order);
+        $mediaModel->save();
+
+        return $mediaModel;
     }
 
-    public static function profileUpdateOrCreate($url, $movedToCheck = false)
+    /**
+     * Validate media type
+     */
+    private static function isValidMediaType(array $media): bool
     {
-        $movedToPid = null;
-        $res = self::fetchProfileFromUrl($url);
-        if (! $res || isset($res['id']) == false) {
-            return;
+        $requiredFields = ['mediaType', 'url'];
+
+        foreach ($requiredFields as $field) {
+            if (! isset($media[$field]) || empty($media[$field])) {
+                return false;
+            }
         }
-        if (! self::validateUrl($res['inbox'])) {
-            return;
+
+        return true;
+    }
+
+    /**
+     * Set media attributes
+     */
+    private static function setMediaAttributes(Media $media, array $data, Status $status, int $order): void
+    {
+        $media->remote_media = true;
+        $media->status_id = $status->id;
+        $media->profile_id = $status->profile_id;
+        $media->user_id = null;
+        $media->media_path = $data['url'];
+        $media->remote_url = $data['url'];
+        $media->mime = $data['mediaType'];
+        $media->version = 3;
+        $media->order = $order;
+
+        // Optional attributes
+        if (isset($data['blurhash'])) {
+            $media->blurhash = $data['blurhash'];
         }
-        if (! self::validateUrl($res['id'])) {
-            return;
+
+        if (isset($data['name'])) {
+            $media->caption = Purify::clean($data['name']);
         }
 
-        if (ModeratedProfile::whereProfileUrl($res['id'])->whereIsBanned(true)->exists()) {
-            return;
+        if (isset($data['width'])) {
+            $media->width = $data['width'];
         }
 
-        $urlDomain = parse_url($url, PHP_URL_HOST);
-        $domain = parse_url($res['id'], PHP_URL_HOST);
-        if (strtolower($urlDomain) !== strtolower($domain)) {
-            return;
+        if (isset($data['height'])) {
+            $media->height = $data['height'];
         }
-        if (! isset($res['preferredUsername']) && ! isset($res['nickname'])) {
-            return;
+
+        if (isset($data['license'])) {
+            $media->license = License::nameToId($data['license']);
         }
-        // skip invalid usernames
-        if (! ctype_alnum($res['preferredUsername'])) {
-            $tmpUsername = str_replace(['_', '.', '-'], '', $res['preferredUsername']);
-            if (! ctype_alnum($tmpUsername)) {
-                return;
-            }
+    }
+
+    /**
+     * Fetch or create a profile from a URL
+     */
+    public static function profileFirstOrNew(string $url): ?Profile
+    {
+        if (! $validatedUrl = self::validateUrl($url)) {
+            return null;
         }
-        $username = (string) Purify::clean($res['preferredUsername'] ?? $res['nickname']);
-        if (empty($username)) {
-            return;
+
+        $host = parse_url($validatedUrl, PHP_URL_HOST);
+
+        if (self::isLocalDomain($host)) {
+            return self::getLocalProfile($validatedUrl);
         }
-        $remoteUsername = $username;
-        $webfinger = "@{$username}@{$domain}";
 
-        $instance = Instance::updateOrCreate([
-            'domain' => $domain,
-        ]);
-        if ($instance->wasRecentlyCreated == true) {
-            \App\Jobs\InstancePipeline\FetchNodeinfoPipeline::dispatch($instance)->onQueue('low');
+        return self::getOrFetchRemoteProfile($validatedUrl);
+    }
+
+    /**
+     * Check if domain is local
+     */
+    private static function isLocalDomain(string $host): bool
+    {
+        return config('pixelfed.domain.app') == $host;
+    }
+
+    /**
+     * Get local profile from URL
+     */
+    private static function getLocalProfile(string $url): ?Profile
+    {
+        $username = last(explode('/', $url));
+
+        return Profile::whereNull('status')
+            ->whereNull('domain')
+            ->whereUsername($username)
+            ->firstOrFail();
+    }
+
+    /**
+     * Get existing or fetch new remote profile
+     */
+    private static function getOrFetchRemoteProfile(string $url): ?Profile
+    {
+        $profile = Profile::whereRemoteUrl($url)->first();
+
+        if ($profile && ! self::needsFetch($profile)) {
+            return $profile;
         }
 
-        if (! $movedToCheck && isset($res['movedTo']) && Helpers::validateUrl($res['movedTo'])) {
-            $movedTo = self::profileUpdateOrCreate($res['movedTo'], true);
-            if ($movedTo) {
-                $movedToPid = $movedTo->id;
-            }
+        return self::profileUpdateOrCreate($url);
+    }
+
+    /**
+     * Check if profile needs to be fetched
+     */
+    private static function needsFetch(?Profile $profile): bool
+    {
+        return ! $profile?->last_fetched_at ||
+               $profile->last_fetched_at->lt(now()->subHours(24));
+    }
+
+    /**
+     * Update or create a profile from ActivityPub data
+     */
+    public static function profileUpdateOrCreate(string $url, bool $movedToCheck = false): ?Profile
+    {
+        $res = self::fetchProfileFromUrl($url);
+
+        if (! self::isValidProfileData($res, $url)) {
+            return null;
+        }
+
+        $domain = parse_url($res['id'], PHP_URL_HOST);
+        $username = self::extractUsername($res);
+
+        if (! $username || self::isProfileBanned($res['id'])) {
+            return null;
         }
 
+        $webfinger = "@{$username}@{$domain}";
+        $instance = self::getOrCreateInstance($domain);
+        $movedToPid = $movedToCheck ? null : self::handleMovedTo($res);
+
         $profile = Profile::updateOrCreate(
             [
                 'domain' => strtolower($domain),
                 'username' => Purify::clean($webfinger),
             ],
-            [
-                'webfinger' => Purify::clean($webfinger),
-                'key_id' => $res['publicKey']['id'],
-                'remote_url' => $res['id'],
-                'name' => isset($res['name']) ? Purify::clean($res['name']) : 'user',
-                'bio' => isset($res['summary']) ? Purify::clean($res['summary']) : null,
-                'sharedInbox' => isset($res['endpoints']) && isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : null,
-                'inbox_url' => $res['inbox'],
-                'outbox_url' => isset($res['outbox']) ? $res['outbox'] : null,
-                'public_key' => $res['publicKey']['publicKeyPem'],
-                'indexable' => isset($res['indexable']) && is_bool($res['indexable']) ? $res['indexable'] : false,
-                'moved_to_profile_id' => $movedToPid,
-            ]
+            self::buildProfileData($res, $webfinger, $movedToPid)
         );
 
-        if ($profile->last_fetched_at == null ||
+        self::handleProfileAvatar($profile);
+
+        return $profile;
+    }
+
+    /**
+     * Validate profile data from ActivityPub
+     */
+    private static function isValidProfileData(?array $res, string $url): bool
+    {
+        if (! $res || ! isset($res['id']) || ! isset($res['inbox'])) {
+            return false;
+        }
+
+        if (! self::validateUrl($res['inbox']) || ! self::validateUrl($res['id'])) {
+            return false;
+        }
+
+        $urlDomain = parse_url($url, PHP_URL_HOST);
+        $domain = parse_url($res['id'], PHP_URL_HOST);
+
+        return strtolower($urlDomain) === strtolower($domain);
+    }
+
+    /**
+     * Extract username from profile data
+     */
+    private static function extractUsername(array $res): ?string
+    {
+        $username = $res['preferredUsername'] ?? $res['nickname'] ?? null;
+
+        if (! $username || ! ctype_alnum(str_replace(['_', '.', '-'], '', $username))) {
+            return null;
+        }
+
+        return Purify::clean($username);
+    }
+
+    /**
+     * Check if profile is banned
+     */
+    private static function isProfileBanned(string $profileUrl): bool
+    {
+        return ModeratedProfile::whereProfileUrl($profileUrl)
+            ->whereIsBanned(true)
+            ->exists();
+    }
+
+    /**
+     * Get or create federation instance
+     */
+    private static function getOrCreateInstance(string $domain): Instance
+    {
+        $instance = Instance::updateOrCreate(['domain' => $domain]);
+
+        if ($instance->wasRecentlyCreated) {
+            \App\Jobs\InstancePipeline\FetchNodeinfoPipeline::dispatch($instance)
+                ->onQueue('low');
+        }
+
+        return $instance;
+    }
+
+    /**
+     * Handle moved profile references
+     */
+    private static function handleMovedTo(array $res): ?int
+    {
+        if (! isset($res['movedTo']) || ! self::validateUrl($res['movedTo'])) {
+            return null;
+        }
+
+        $movedTo = self::profileUpdateOrCreate($res['movedTo'], true);
+
+        return $movedTo?->id;
+    }
+
+    /**
+     * Build profile data array for database
+     */
+    private static function buildProfileData(array $res, string $webfinger, ?int $movedToPid): array
+    {
+        return [
+            'webfinger' => Purify::clean($webfinger),
+            'key_id' => $res['publicKey']['id'],
+            'remote_url' => $res['id'],
+            'name' => isset($res['name']) ? Purify::clean($res['name']) : 'user',
+            'bio' => isset($res['summary']) ? Purify::clean($res['summary']) : null,
+            'sharedInbox' => $res['endpoints']['sharedInbox'] ?? null,
+            'inbox_url' => $res['inbox'],
+            'outbox_url' => $res['outbox'] ?? null,
+            'public_key' => $res['publicKey']['publicKeyPem'],
+            'indexable' => $res['indexable'] ?? false,
+            'moved_to_profile_id' => $movedToPid,
+        ];
+    }
+
+    /**
+     * Handle profile avatar updates
+     */
+    private static function handleProfileAvatar(Profile $profile): void
+    {
+        if (! $profile->last_fetched_at ||
             $profile->last_fetched_at->lt(now()->subMonths(3))
         ) {
             RemoteAvatarFetch::dispatch($profile);
         }
+
         $profile->last_fetched_at = now();
         $profile->save();
-
-        return $profile;
     }
 
-    public static function profileFetch($url)
+    public static function profileFetch($url): ?Profile
     {
         return self::profileFirstOrNew($url);
     }