mirror of https://github.com/pixelfed/pixelfed
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
3.5 KiB
PHP
134 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace App\Federation\Validators;
|
|
|
|
use App\Models\Profile;
|
|
use App\Util\ActivityPub\Helpers;
|
|
|
|
class DirectMessageValidator
|
|
{
|
|
public const PUBLIC_URIS = [
|
|
'https://www.w3.org/ns/activitystreams#Public',
|
|
'as:Public',
|
|
'Public',
|
|
];
|
|
|
|
/**
|
|
* Everyone the object is addressed to, as a flat list of ids.
|
|
*
|
|
* @return array<int, string>
|
|
*/
|
|
public static function audience(array $object): array
|
|
{
|
|
$audience = [];
|
|
|
|
foreach (['to', 'cc'] as $field) {
|
|
$value = $object[$field] ?? [];
|
|
|
|
if (is_string($value)) {
|
|
$value = [$value];
|
|
}
|
|
|
|
if (! is_array($value)) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($value as $entry) {
|
|
if (is_array($entry)) {
|
|
$entry = $entry['id'] ?? null;
|
|
}
|
|
|
|
if (is_string($entry) && $entry !== '') {
|
|
$audience[] = $entry;
|
|
}
|
|
}
|
|
}
|
|
|
|
return array_values(array_unique($audience));
|
|
}
|
|
|
|
/**
|
|
* A Note is direct when it is addressed to people and nothing else: not
|
|
* the public collection and not anyone's followers.
|
|
*
|
|
* This has to be decided before a Note is considered as a post or a
|
|
* reply. Those paths store anything that is not public as followers-only,
|
|
* which would show a private message to the sender's local followers.
|
|
*/
|
|
public static function isDirect(array $object, Profile $actor): bool
|
|
{
|
|
if (($object['type'] ?? null) !== 'Note') {
|
|
return false;
|
|
}
|
|
|
|
$audience = self::audience($object);
|
|
|
|
if (empty($audience)) {
|
|
return false;
|
|
}
|
|
|
|
foreach ($audience as $uri) {
|
|
if (in_array($uri, self::PUBLIC_URIS, true)) {
|
|
return false;
|
|
}
|
|
|
|
if (self::isFollowersCollection($uri, $actor)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Followers collections do not share a URL shape across software, but
|
|
* every known implementation has a /followers segment in it.
|
|
*/
|
|
public static function isFollowersCollection(string $uri, Profile $actor): bool
|
|
{
|
|
if ($actor->followers_url && $uri === $actor->followers_url) {
|
|
return true;
|
|
}
|
|
|
|
return str_contains(strtolower($uri), '/followers');
|
|
}
|
|
|
|
/**
|
|
* The object itself is well formed. Whether the sender is allowed to
|
|
* speak for it is checked by the inbox before this runs.
|
|
*/
|
|
public static function validate(array $object): bool
|
|
{
|
|
$id = Helpers::pluckval($object['id'] ?? null);
|
|
|
|
if (! is_string($id) || strlen($id) > 1024 || ! Helpers::validateUrl($id)) {
|
|
return false;
|
|
}
|
|
|
|
if (isset($object['content']) && ! is_string($object['content'])) {
|
|
return false;
|
|
}
|
|
|
|
if (isset($object['attachment']) && ! is_array($object['attachment'])) {
|
|
return false;
|
|
}
|
|
|
|
$hasContent = isset($object['content']) && trim(strip_tags($object['content'])) !== '';
|
|
$hasAttachment = ! empty($object['attachment']);
|
|
|
|
if (! $hasContent && ! $hasAttachment) {
|
|
return false;
|
|
}
|
|
|
|
if (isset($object['published'])) {
|
|
$published = Helpers::pluckval($object['published']);
|
|
|
|
if (! is_string($published) || ! Helpers::validateTimestamp($published)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|