diff --git a/CHANGELOG.md b/CHANGELOG.md index c726d7f3..933bd71d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Updated StatusController, restrict edits to 24 hours ([ae24433b](https://github.com/pixelfed/pixelfed/commit/ae24433b)) - Updated RateLimit, add max post edits per hour and day ([51fbfcdc](https://github.com/pixelfed/pixelfed/commit/51fbfcdc)) - Updated Timeline.vue, move announcements from sidebar to top of timeline ([228f5044](https://github.com/pixelfed/pixelfed/commit/228f5044)) +- Updated lexer autolinker and extractor, add support for mentioned usernames containing dashes, periods and underscore characters ([f911c96d](https://github.com/pixelfed/pixelfed/commit/f911c96d)) ## [v0.10.8 (2020-01-29)](https://github.com/pixelfed/pixelfed/compare/v0.10.7...v0.10.8) ### Added diff --git a/app/Services/ActivityPubDeliveryService.php b/app/Services/ActivityPubDeliveryService.php new file mode 100644 index 00000000..1e1b5851 --- /dev/null +++ b/app/Services/ActivityPubDeliveryService.php @@ -0,0 +1,61 @@ +sender = $profile; + return $this; + } + + public function to(string $url) + { + $this->to = $url; + return $this; + } + + public function payload($payload) + { + $this->payload = $payload; + return $this; + } + + public function send() + { + return $this->queueDelivery(); + } + + protected function queueDelivery() + { + abort_if(!$this->sender || !$this->to || !$this->payload, 400); + abort_if(!Helpers::validateUrl($this->to), 400); + abort_if($this->sender->domain != null || $this->sender->status != null, 400); + + $body = $this->payload; + $payload = json_encode($body); + $headers = HttpSignature::sign($this->sender, $this->to, $body); + + $ch = curl_init($this->to); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + curl_setopt($ch, CURLOPT_HEADER, true); + curl_exec($ch); + } + +} \ No newline at end of file diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index e6d85bae..da74a1cb 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -23,6 +23,7 @@ use App\Jobs\ImageOptimizePipeline\{ImageOptimize,ImageThumbnail}; use App\Jobs\StatusPipeline\NewStatusPipeline; use App\Util\ActivityPub\HttpSignature; use Illuminate\Support\Str; +use App\Services\ActivityPubDeliveryService; class Helpers { @@ -435,35 +436,12 @@ class Helpers { return self::profileFirstOrNew($url); } - public static function sendSignedObject($senderProfile, $url, $body) + public static function sendSignedObject($profile, $url, $body) { - abort_if(!self::validateUrl($url), 400); - - $payload = json_encode($body); - $headers = HttpSignature::sign($senderProfile, $url, $body); - - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); - curl_setopt($ch, CURLOPT_HEADER, true); - $response = curl_exec($ch); - return; - } - - public static function apSignedPostRequest($senderProfile, $url, $body) - { - abort_if(!self::validateUrl($url), 400); - - $payload = json_encode($body); - $headers = HttpSignature::sign($senderProfile, $url, $body); - - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); - curl_setopt($ch, CURLOPT_HEADER, true); - $response = curl_exec($ch); - return; + ActivityPubDeliveryService::queue() + ->from($profile) + ->to($url) + ->payload($body) + ->send(); } } diff --git a/app/Util/Lexer/Regex.php b/app/Util/Lexer/Regex.php index c24e0d4b..ecc468d0 100755 --- a/app/Util/Lexer/Regex.php +++ b/app/Util/Lexer/Regex.php @@ -162,9 +162,9 @@ abstract class Regex // look-ahead capture here and don't append $after when we return. $tmp['valid_mention_preceding_chars'] = '([^a-zA-Z0-9_!#\$%&*@@\/]|^|(?:^|[^a-z0-9_+~.-])RT:?)'; - $re['valid_mentions_or_lists'] = '/'.$tmp['valid_mention_preceding_chars'].'(['.$tmp['at_signs'].'])([a-z0-9_]{1,20})((\/[a-z][a-z0-9_\-]{0,24})?(?=(.*|$))(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i'; + $re['valid_mentions_or_lists'] = '/'.$tmp['valid_mention_preceding_chars'].'(['.$tmp['at_signs'].'])([a-z0-9_\-.]{1,20})((\/[a-z][a-z0-9_\-]{0,24})?(?=(.*|$))(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i'; - $re['valid_reply'] = '/^(?:['.$tmp['spaces'].'])*['.$tmp['at_signs'].']([a-z0-9_]{1,20})(?=(.*|$))/iu'; + $re['valid_reply'] = '/^(?:['.$tmp['spaces'].'])*['.$tmp['at_signs'].']([a-z0-9_\-.]{1,20})(?=(.*|$))/iu'; $re['end_mention_match'] = '/\A(?:['.$tmp['at_signs'].']|['.$tmp['latin_accents'].']|:\/\/)/iu'; // URL related hash regex collection diff --git a/tests/Unit/Lexer/UsernameTest.php b/tests/Unit/Lexer/UsernameTest.php new file mode 100644 index 00000000..e5c310db --- /dev/null +++ b/tests/Unit/Lexer/UsernameTest.php @@ -0,0 +1,179 @@ +extract($username); + $autolink = Autolink::create()->autolink($username); + $expectedAutolink = '@dansup'; + $expectedEntity = [ + "hashtags" => [], + "urls" => [], + "mentions" => [ + "dansup", + ], + "replyto" => "dansup", + "hashtags_with_indices" => [], + "urls_with_indices" => [], + "mentions_with_indices" => [ + [ + "screen_name" => "dansup", + "indices" => [ + 0, + 7, + ], + ], + ], + ]; + $this->assertEquals($expectedAutolink, $autolink); + $this->assertEquals($expectedEntity, $entities); + } + + /** @test **/ + public function usernameWithPeriod() + { + $username = '@dansup.two'; + $autolink = Autolink::create()->autolink($username); + $entities = Extractor::create()->extract($username); + $expectedAutolink = '@dansup.two'; + $expectedEntity = [ + "hashtags" => [], + "urls" => [], + "mentions" => [ + "dansup.two", + ], + "replyto" => "dansup.two", + "hashtags_with_indices" => [], + "urls_with_indices" => [], + "mentions_with_indices" => [ + [ + "screen_name" => "dansup.two", + "indices" => [ + 0, + 11, + ], + ], + ], + ]; + $this->assertEquals($expectedAutolink, $autolink); + $this->assertEquals($expectedEntity, $entities); + } + + /** @test **/ + public function usernameWithDash() + { + $username = '@dansup-too'; + $autolink = Autolink::create()->autolink($username); + $entities = Extractor::create()->extract($username); + $expectedAutolink = '@dansup-too'; + $expectedEntity = [ + "hashtags" => [], + "urls" => [], + "mentions" => [ + "dansup-too", + ], + "replyto" => "dansup-too", + "hashtags_with_indices" => [], + "urls_with_indices" => [], + "mentions_with_indices" => [ + [ + "screen_name" => "dansup-too", + "indices" => [ + 0, + 11, + ], + ], + ], + ]; + $this->assertEquals($expectedAutolink, $autolink); + $this->assertEquals($expectedEntity, $entities); + } + + /** @test **/ + public function usernameWithUnderscore() + { + $username = '@dansup_too'; + $autolink = Autolink::create()->autolink($username); + $entities = Extractor::create()->extract($username); + $expectedAutolink = '@dansup_too'; + $expectedEntity = [ + "hashtags" => [], + "urls" => [], + "mentions" => [ + "dansup_too", + ], + "replyto" => "dansup_too", + "hashtags_with_indices" => [], + "urls_with_indices" => [], + "mentions_with_indices" => [ + [ + "screen_name" => "dansup_too", + "indices" => [ + 0, + 11, + ], + ], + ], + ]; + $this->assertEquals($expectedAutolink, $autolink); + $this->assertEquals($expectedEntity, $entities); + } + + /** @test **/ + public function multipleMentions() + { + $text = 'hello @dansup and @pixelfed.team from @username_underscore'; + $autolink = Autolink::create()->autolink($text); + $entities = Extractor::create()->extract($text); + $expectedAutolink = 'hello @dansup and @pixelfed.team from @username_underscore'; + $expectedEntity = [ + "hashtags" => [], + "urls" => [], + "mentions" => [ + "dansup", + "pixelfed.team", + "username_underscore", + ], + "replyto" => null, + "hashtags_with_indices" => [], + "urls_with_indices" => [], + "mentions_with_indices" => [ + [ + "screen_name" => "dansup", + "indices" => [ + 6, + 13, + ], + ], + [ + "screen_name" => "pixelfed.team", + "indices" => [ + 18, + 32, + ], + ], + [ + "screen_name" => "username_underscore", + "indices" => [ + 38, + 58, + ], + ], + ], + ]; + + $this->assertEquals($expectedAutolink, $autolink); + $this->assertEquals($expectedEntity, $entities); + } + +} \ No newline at end of file