Make the library PHP ^5.5 compatible

This commit is contained in:
Jeremy Dormitzer 2019-02-13 22:27:47 -05:00
parent 9c4c39fb0b
commit 1b5003727d
48 changed files with 782 additions and 595 deletions

View File

@ -1,54 +1,60 @@
{
"name": "pterotype/activitypub-php",
"description": "An ActivityPub library",
"license": "MIT",
"authors": [
{
"name": "Jeremy Dormitzer",
"email": "jeremy@dormitzer.net",
"homepage": "https://jeremydormitzer.com",
"role": "Developer"
}
],
"scripts": {
"test": "phpunit -c ./test/config.xml test",
"test-debug": "XDEBUG_CONFIG='idekey=ap_session' php ./vendor/bin/phpunit test",
"docs": "phpdoc -d ./src -t ./docs"
},
"repositories": [
{
"type": "vcs",
"url": "https://git.friendi.ca/friendica/php-json-ld"
}
],
"require": {
"doctrine/orm": "^2.6",
"friendica/json-ld": "^1.1",
"guzzlehttp/guzzle": "^6.3",
"phpseclib/phpseclib": "^2.0",
"psr/http-message": "^1.0",
"symfony/dependency-injection": "^4.2",
"symfony/http-kernel": "^4.2",
"symfony/psr-http-message-bridge": "^1.1",
"zendframework/zend-diactoros": "^1.8",
"ext-json": "*"
},
"require-dev": {
"phpunit/dbunit": "^2.0",
"phpunit/phpunit": "^4.0",
"ext-pdo": "*"
},
"autoload": {
"psr-4": {
"ActivityPub\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"ActivityPub\\Test\\": "test/"
}
},
"config": {
"sort-packages": true
"name": "pterotype/activitypub-php",
"description": "An ActivityPub library",
"license": "MIT",
"authors": [
{
"name": "Jeremy Dormitzer",
"email": "jeremy@dormitzer.net",
"homepage": "https://jeremydormitzer.com",
"role": "Developer"
}
],
"scripts": {
"test": "phpunit -c ./test/config.xml test",
"test-debug": "XDEBUG_CONFIG='idekey=ap_session' php ./vendor/bin/phpunit test",
"docs": "phpdoc -d ./src -t ./docs"
},
"repositories": [
{
"type": "vcs",
"url": "https://git.friendi.ca/friendica/php-json-ld"
}
],
"require": {
"ext-json": "*",
"doctrine/annotations": "1.2.7",
"doctrine/cache": "1.6.2",
"doctrine/common": "2.6.2",
"doctrine/instantiator": "1.0.5",
"doctrine/orm": "2.5.14",
"friendica/json-ld": "^1.1",
"guzzlehttp/guzzle": "^6.3",
"phpseclib/phpseclib": "^2.0",
"psr/http-message": "^1.0",
"symfony/dependency-injection": "^3.4",
"symfony/event-dispatcher": "^3.4",
"symfony/http-foundation": "^3.4",
"symfony/http-kernel": "^3.4",
"symfony/psr-http-message-bridge": "^1.1",
"zendframework/zend-diactoros": "1.4.1"
},
"require-dev": {
"phpunit/dbunit": "^2.0",
"phpunit/phpunit": "^4.0",
"ext-pdo": "*"
},
"autoload": {
"psr-4": {
"ActivityPub\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"ActivityPub\\Test\\": "test/"
}
},
"config": {
"sort-packages": true
}
}

698
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,81 @@
<?php
namespace ActivityPub\Activities;
use ActivityPub\Objects\CollectionsService;
use ActivityPub\Objects\ObjectsService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class AcceptHandler implements EventSubscriberInterface
{
/**
* @var ObjectsService
*/
private $objectsService;
/**
* @var CollectionsService
*/
private $collectionsService;
public static function getSubscribedEvents()
{
return array(
InboxActivityEvent::NAME => 'handleInbox',
OutboxActivityEvent::NAME => 'handleOutbox',
);
}
public function __construct( ObjectsService $objectsService,
CollectionsService $collectionsService )
{
$this->objectsService = $objectsService;
$this->collectionsService = $collectionsService;
}
public function handleInbox( InboxActivityEvent $event )
{
$activity = $event->getActivity();
if ( $activity['type'] !== 'Accept' ) {
return;
}
// add to following collection
}
public function handleOutbox( OutboxActivityEvent $event )
{
$activity = $event->getActivity();
if ( $activity['type'] !== 'Accept' ) {
return;
}
$request = $event->getRequest();
// either there is a 'follow' key on the request,
// in which case this is an auto-accept dispatched from
// the FollowHandler so the Follow won't be in the database yet,
// or there isn't, in which case this is an ordinary Accept
// sent by a client and the Follow is in the database
$follow = $request->attributes->get( 'follow' );
if ( ! $follow ) {
$followId = $activity['object'];
if ( is_array( $followId ) && array_key_exists( 'id', $followId ) ) {
$followId = $followId['id'];
}
if ( ! is_string( $followId ) ) {
return;
}
$follow = $this->objectsService->dereference( $followId )->asArray( -1 );
}
if ( ! $follow || ! array_key_exists( 'object', $follow ) ) {
return;
}
$followObjectId = $follow['object'];
if ( is_array( $followObjectId ) && array_key_exists( 'id', $followObjectId ) ) {
$followObjectId = $followObjectId['id'];
}
$actor = $event->getActor();
if ( $followObjectId !== $actor['id'] ) {
return;
}
$this->collectionsService->addItem( $actor['followers'], $activity['actor'] );
}
}

View File

@ -1,9 +1,6 @@
<?php
namespace ActivityPub\Activities;
use ActivityPub\Activities\ActivityEvent;
use ActivityPub\Activities\InboxActivityEvent;
use ActivityPub\Activities\OutboxActivityEvent;
use ActivityPub\Objects\ObjectsService;
use ActivityPub\Utils\DateTimeProvider;
use DateTime;

View File

@ -1,10 +1,7 @@
<?php
namespace ActivityPub\Activities;
use ActivityPub\Activities\InboxActivityEvent;
use ActivityPub\Activities\OutboxActivityEvent;
use ActivityPub\Objects\ContextProvider;
use GuzzleHttp\Client;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
@ -35,9 +32,11 @@ class FollowHandler implements EventSubscriberInterface
$this->contextProvider = $contextProvider;
}
public function handleInbox( InboxActivityEvent $event,
$eventName,
EventDispatcher $eventDispatcher )
public function handleInbox(InboxActivityEvent $event,
/** @noinspection PhpUnusedParameterInspection */
$eventName,
EventDispatcher $eventDispatcher )
{
$activity = $event->getActivity();
if ( ! $activity['type'] === 'Follow' ) {

View File

@ -1,8 +1,6 @@
<?php
namespace ActivityPub\Activities;
use ActivityPub\Activities\ActivityEvent;
class InboxActivityEvent extends ActivityEvent
{
const NAME = 'inbox.activity';

View File

@ -1,11 +1,9 @@
<?php
namespace ActivityPub\Activities;
use ActivityPub\Activities\OutboxActivityEvent;
use ActivityPub\Entities\ActivityPubObject;
use ActivityPub\Objects\ContextProvider;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* The NonActivityHandler wraps non-activity objects sent to the outbox in a
@ -49,22 +47,20 @@ class NonActivityHandler implements EventSubscriberInterface
if ( in_array( $object['type'], self::activityTypes() ) ) {
return;
}
$request = $event->getRequest();
$actor = $event->getActor();
$create = $this->makeCreate( $request, $object, $actor );
$create = $this->makeCreate( $object, $actor );
$event->setActivity( $create );
}
/**
* Makes a new Create activity with $object as the object
*
* @param Request $request The current request
* @param array $object The object
* @param ActivityPubObject $actorId The actor creating the object
* @param ActivityPubObject $actor The actor creating the object
*
* @return array The Create activity
*/
private function makeCreate( Request $request, array $object,
private function makeCreate( array $object,
ActivityPubObject $actor )
{
$create = array(

View File

@ -1,8 +1,6 @@
<?php
namespace ActivityPub\Activities;
use ActivityPub\Activities\ActivityEvent;
class OutboxActivityEvent extends ActivityEvent
{
const NAME = 'outbox.activity';

View File

@ -1,8 +1,6 @@
<?php
namespace ActivityPub\Activities;
use ActivityPub\Activities\InboxActivityEvent;
use ActivityPub\Activities\OutboxActivityEvent;
use ActivityPub\Objects\ObjectsService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;

View File

@ -1,8 +1,6 @@
<?php
namespace ActivityPub\Activities;
use ActivityPub\Activities\InboxActivityEvent;
use ActivityPub\Activities\OutboxActivityEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

View File

@ -1,4 +1,6 @@
<?php
<?php /** @noinspection PhpDocMissingThrowsInspection */
/** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub;

View File

@ -1,4 +1,5 @@
<?php
<?php /** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Auth;
use Exception;
@ -40,7 +41,7 @@ class AuthListener implements EventSubscriberInterface
* Constructs a new AuthenticationService
*
* @param Callable $authFunction A Callable that should accept
*
* @param ObjectsService $objectsService
*/
public function __construct( Callable $authFunction, ObjectsService $objectsService )
{

View File

@ -42,6 +42,7 @@ class SignatureListener implements EventSubscriberInterface
* Check for a valid HTTP signature on the request. If the request has a valid
* signature, set the 'signed' and 'signedBy' keys on the request ('signedBy' is
* the id of the actor whose key signed the request)
* @param GetResponseEvent $event
*/
public function validateHttpSignature( GetResponseEvent $event )
{

View File

@ -1,9 +1,6 @@
<?php
namespace ActivityPub\Config;
use ActivityPub\Config\ActivityPubConfigBuilder;
use ActivityPub\Objects\ContextProvider;
/**
* The ActivityPubConfig is a data class to hold ActivityPub configuration options
*/

View File

@ -1,7 +1,6 @@
<?php
namespace ActivityPub\Config;
use ActivityPub\Config\ActivityPubConfig;
use ActivityPub\Objects\ContextProvider;
use ActivityPub\Objects\IdProvider;
use Exception;

View File

@ -1,4 +1,7 @@
<?php
<?php /** @noinspection PhpDocMissingThrowsInspection */
/** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Config;
use ActivityPub\Activities\CreateHandler;
@ -11,7 +14,6 @@ use ActivityPub\Auth\AuthService;
use ActivityPub\Auth\SignatureListener;
use ActivityPub\Controllers\GetController;
use ActivityPub\Controllers\PostController;
use ActivityPub\Config\ActivityPubConfig;
use ActivityPub\Crypto\HttpSignatureService;
use ActivityPub\Database\PrefixNamingStrategy;
use ActivityPub\Http\Router;

View File

@ -65,7 +65,8 @@ class GetController
if ( $object->hasField( 'type' ) &&
( $object['type'] === 'Collection' ||
$object['type'] === 'OrderedCollection' ) ) {
return $this->collectionsService->pageAndFilterCollection( $request, $object );
$pagedCollection = $this->collectionsService->pageAndFilterCollection( $request, $object );
return new JsonResponse( $pagedCollection );
}
$response = new JsonResponse( $object->asArray() );
if ( $object->hasField( 'type' ) &&

View File

@ -7,6 +7,7 @@ use ActivityPub\Entities\ActivityPubObject;
use ActivityPub\Objects\ObjectsService;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@ -40,6 +41,7 @@ class PostController
* Either dispatches an inbox/outbox activity event or throws the appropriate
* HTTP error.
* @param Request $request The request
* @return Response
*/
public function handle( Request $request )
{
@ -112,15 +114,6 @@ class PostController
return true;
}
private function objectWithField( $name, $value )
{
$results = $this->objectsService->query( array( $name => $value ) );
if ( count( $results ) === 0 ) {
return false;
}
return $results[0];
}
private function getUriWithoutQuery( Request $request )
{
$uri = $request->getUri();

View File

@ -1,13 +1,13 @@
<?php
namespace ActivityPub\Crypto;
use ActivityPub\Utils\HeaderUtils;
use DateTime;
use ActivityPub\Utils\DateTimeProvider;
use ActivityPub\Utils\SimpleDateTimeProvider;
use Psr\Http\Message\RequestInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\HeaderUtils;
/**
* The HttpSignatureService provides methods to generate and verify HTTP signatures
@ -54,10 +54,10 @@ class HttpSignatureService
/**
* Generates a signature given the request and private key
*
* @param RequestInterface $psrRequest The request to be signed
* @param RequestInterface $request The request to be signed
* @param string $privateKey The private key to use to sign the request
* @param string $keyId The id of the signing key
* @param array $headers The headers to use in the signature
* @param array $headers|null The headers to use in the signature
* (default ['(request-target)', 'host', 'date'])
* @return string The Signature header value
*/
@ -70,7 +70,7 @@ class HttpSignatureService
$headers = array_map( 'strtolower', $headers );
$signingString = $this->getSigningString( $request, $headers );
$keypair = RsaKeypair::fromPrivateKey( $privateKey );
$signature = base64_encode( $keypair->sign( $signingString, 'rsa256' ) );
$signature = base64_encode( $keypair->sign( $signingString, 'sha256' ) );
$headersStr = implode( ' ', $headers );
return "keyId=\"$keyId\"," .
"algorithm=\"rsa-sha256\"," .
@ -127,7 +127,7 @@ class HttpSignatureService
/**
* Returns the signing string from the request
*
* @param Request $request The request
* @param RequestInterface $request The request
* @param array $headers The headers to use to generate the signing string
* @return string The signing string
*/

View File

@ -65,7 +65,7 @@ class RsaKeypair
);
}
$rsa = new RSA();
$rsa->setHash( 'sha256' );
$rsa->setHash( $hash );
$rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
$rsa->loadKey( $this->privateKey );
return $rsa->sign( $data );

View File

@ -1,10 +1,12 @@
<?php
<?php /** @noinspection PhpDocMissingThrowsInspection */
/** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Entities;
use ArrayAccess;
use BadMethodCallException;
use DateTime;
use ActivityPub\Utils\Util;
use Doctrine\Common\Collections\ArrayCollection;
/**
@ -137,6 +139,7 @@ class ActivityPubObject implements ArrayAccess
/**
* Returns true if the object contains a field with key $name
*
* @param mixed $name
* @return boolean
*/
public function hasField( $name )
@ -171,6 +174,7 @@ class ActivityPubObject implements ArrayAccess
* The value is either a string, another ActivityPubObject, or null
* if no such key exists.
*
* @param mixed $name
* @return string|ActivityPubObject|null The field's value, or null if
* the field is not found
*/
@ -192,6 +196,7 @@ class ActivityPubObject implements ArrayAccess
* $object.
*
* @param Field $field
* @param DateTime|null $time
*/
public function addField( Field $field, DateTime $time = null )
{
@ -205,6 +210,7 @@ class ActivityPubObject implements ArrayAccess
/**
* Returns true if the object is referenced by a field with key $name
*
* @param mixed $name
* @return boolean
*/
public function hasReferencingField( $name )

View File

@ -1,4 +1,7 @@
<?php
<?php /** @noinspection PhpDocMissingThrowsInspection */
/** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Entities;
use DateTime;
@ -82,7 +85,9 @@ class Field
* @param ActivityPubObject $object The object to which this field belongs
* @param string $name The name of the field
* @param string $value The value of the field
* @param DateTime|null $time
* @return Field The new field
* @throws \Exception
*/
public static function withValue( ActivityPubObject $object, $name, $value, DateTime $time = null )
{
@ -102,7 +107,9 @@ class Field
* @param ActivityPubObject $object The object to which this field belongs
* @param string $name The name of the field
* @param ActivityPubObject $targetObject The object that this field holds
* @param DateTime|null $time
* @return Field The new field
* @throws \Exception
*/
public static function withObject( ActivityPubObject $object,
$name,
@ -269,6 +276,7 @@ class Field
/**
* Returns true if $this is equal to $other
*
* @param Field $other
* @return bool
*/
public function equals( Field $other )

View File

@ -1,8 +1,6 @@
<?php
namespace ActivityPub\Entities;
use ActivityPub\Entities\ActivityPubObject;
/**
* The keys table holds the private keys associated with ActivityPub actors
*

View File

@ -1,4 +1,7 @@
<?php
<?php /** @noinspection PhpDocMissingThrowsInspection */
/** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Objects;
use ActivityPub\Auth\AuthService;
@ -56,6 +59,9 @@ class CollectionsService
* Returns an array representation of the $collection
*
* Returns the collection paged and filtered by the request's authorization status
* @param Request $request
* @param ActivityPubObject $collection
* @return array
*/
public function pageAndFilterCollection( Request $request,
ActivityPubObject $collection )
@ -125,6 +131,7 @@ class CollectionsService
*/
public function addItem( ActivityPubObject $collection, array $item )
{
// TODO implement me
if ( ! $collection->hasField( 'items' ) ) {
$items = new ActivityPubObject(
$this->dateTimeProvider->getTime( 'collections-service.create' )
@ -189,7 +196,7 @@ class CollectionsService
$idx = $offset;
$count = 0;
while ( $count < $pageSize ) {
$item = $collectionItems->getFieldValue( strval( $idx ) );
$item = $collectionItems->getFieldValue( $idx );
if ( ! $item ) {
break;
}
@ -225,14 +232,14 @@ class CollectionsService
private function hasNextItem( Request $request, ActivityPubObject $collectionItems, $idx )
{
$next = $collectionItems->getFieldValue( strval( $idx ) );
$next = $collectionItems->getFieldValue( $idx );
while ( $next ) {
if ( is_string( $next ) ||
$this->authService->isAuthorized( $request, $next ) ) {
return $idx;
}
$idx++;
$next = $collectionItems->getFieldValue( strval( $idx ) );
$next = $collectionItems->getFieldValue( $idx );
}
return false;
}

View File

@ -1,7 +1,6 @@
<?php
namespace ActivityPub\Objects;
use ActivityPub\Objects\ObjectsService;
use ActivityPub\Utils\RandomProvider;
use Symfony\Component\HttpFoundation\Request;

View File

@ -1,10 +1,11 @@
<?php
<?php /** @noinspection PhpDocMissingThrowsInspection */
/** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Objects;
use DateTime;
use ActivityPub\Entities\ActivityPubObject;
use ActivityPub\Entities\Field;
use ActivityPub\Utils\Util;
use ActivityPub\Utils\DateTimeProvider;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\QueryBuilder;
@ -66,17 +67,17 @@ class ObjectsService
/** @noinspection PhpUnhandledExceptionInspection */
$this->entityManager->flush();
return $object;
}
}/** @noinspection PhpDocMissingThrowsInspection */
/**
* Persists a field.
* Persists a field.
*
* If the field is an array, persist it as a child object
*
* @param ActivityPubObject $object
* @param string $fieldName
* @param string|array $fieldValue
*
* @param string $context
*/
private function persistField( $object, $fieldName, $fieldValue, $context = 'objects-service.create' )
{

220
src/Utils/HeaderUtils.php Normal file
View File

@ -0,0 +1,220 @@
<?php
/*
* Backported from Symfony 4.1
* Original copyright:
*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ActivityPub\Utils;
/**
* HTTP header utility functions.
*
* @author Christian Schmidt <github@chsc.dk>
*/
class HeaderUtils
{
const DISPOSITION_ATTACHMENT = 'attachment';
const DISPOSITION_INLINE = 'inline';
/**
* This class should not be instantiated.
*/
private function __construct()
{
}
/**
* Splits an HTTP header by one or more separators.
*
* Example:
*
* HeaderUtils::split("da, en-gb;q=0.8", ",;")
* // => ['da'], ['en-gb', 'q=0.8']]
*
* @param string $header HTTP header value
* @param string $separators List of characters to split on, ordered by
* precedence, e.g. ",", ";=", or ",;="
*
* @return array Nested array with as many levels as there are characters in
* $separators
*/
public static function split($header, $separators)
{
$quotedSeparators = preg_quote($separators, '/');
preg_match_all('
/
(?!\s)
(?:
# quoted-string
"(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$)
|
# token
[^"'.$quotedSeparators.']+
)+
(?<!\s)
|
# separator
\s*
(?<separator>['.$quotedSeparators.'])
\s*
/x', trim($header), $matches, PREG_SET_ORDER);
return self::groupParts($matches, $separators);
}
/**
* Combines an array of arrays into one associative array.
*
* Each of the nested arrays should have one or two elements. The first
* value will be used as the keys in the associative array, and the second
* will be used as the values, or true if the nested array only contains one
* element. Array keys are lowercased.
*
* Example:
*
* HeaderUtils::combine([["foo", "abc"], ["bar"]])
* // => ["foo" => "abc", "bar" => true]
* @param array $parts
* @return array
*/
public static function combine(array $parts)
{
$assoc = [];
foreach ($parts as $part) {
$name = strtolower($part[0]);
if (isset($part[1])) {
$value = $part[1];
} else {
$value = true;
}
$assoc[$name] = $value;
}
return $assoc;
}
/**
* Joins an associative array into a string for use in an HTTP header.
*
* The key and value of each entry are joined with "=", and all entries
* are joined with the specified separator and an additional space (for
* readability). Values are quoted if necessary.
*
* Example:
*
* HeaderUtils::toString(["foo" => "abc", "bar" => true, "baz" => "a b c"], ",")
* // => 'foo=abc, bar, baz="a b c"'
* @param array $assoc
* @param $separator
* @return string
*/
public static function toString(array $assoc, $separator)
{
$parts = [];
foreach ($assoc as $name => $value) {
if (true === $value) {
$parts[] = $name;
} else {
$parts[] = $name.'='.self::quote($value);
}
}
return implode($separator.' ', $parts);
}
/**
* Encodes a string as a quoted string, if necessary.
*
* If a string contains characters not allowed by the "token" construct in
* the HTTP specification, it is backslash-escaped and enclosed in quotes
* to match the "quoted-string" construct.
* @param $s
* @return string
*/
public static function quote($s)
{
if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) {
return $s;
}
return '"'.addcslashes($s, '"\\"').'"';
}
/**
* Decodes a quoted string.
*
* If passed an unquoted string that matches the "token" construct (as
* defined in the HTTP specification), it is passed through verbatimly.
* @param $s
* @return string|string[]|null
*/
public static function unquote($s)
{
return preg_replace('/\\\\(.)|"/', '$1', $s);
}
/**
* Generates a HTTP Content-Disposition field-value.
*
* @param string $disposition One of "inline" or "attachment"
* @param string $filename A unicode string
* @param string $filenameFallback A string containing only ASCII characters that
* is semantically equivalent to $filename. If the filename is already ASCII,
* it can be omitted, or just copied from $filename
*
* @return string A string suitable for use as a Content-Disposition field-value
*
* @throws \InvalidArgumentException
*
* @see RFC 6266
*/
public static function makeDisposition($disposition, $filename, $filenameFallback = '')
{
if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) {
throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
}
if ('' === $filenameFallback) {
$filenameFallback = $filename;
}
// filenameFallback is not ASCII.
if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
}
// percent characters aren't safe in fallback.
if (false !== strpos($filenameFallback, '%')) {
throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
}
// path separators aren't allowed in either.
if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) {
throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
}
$params = ['filename' => $filenameFallback];
if ($filename !== $filenameFallback) {
$params['filename*'] = "utf-8''".rawurlencode($filename);
}
return $disposition.'; '.self::toString($params, ';');
}
private static function groupParts(array $matches, $separators)
{
$separator = $separators[0];
$partSeparators = substr($separators, 1);
$i = 0;
$partMatches = [];
foreach ($matches as $match) {
if (isset($match['separator']) && $match['separator'] === $separator) {
++$i;
} else {
$partMatches[$i][] = $match;
}
}
$parts = [];
if ($partSeparators) {
foreach ($partMatches as $matches) {
$parts[] = self::groupParts($matches, $partSeparators);
}
} else {
foreach ($partMatches as $matches) {
$parts[] = self::unquote($matches[0][0]);
}
}
return $parts;
}
}

View File

@ -1,11 +1,12 @@
<?php
<?php /** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Utils;
use DateTime;
use ActivityPub\Utils\DateTimeProvider;
class SimpleDateTimeProvider implements DateTimeProvider
{
/** @noinspection PhpDocMissingThrowsInspection */
/**
* Returns a new DateTime for the given context
*

View File

@ -0,0 +1,20 @@
<?php
namespace ActivityPub\Test\Activities;
use ActivityPub\Test\TestConfig\APTestCase;
class AcceptHandlerTest extends APTestCase
{
public function testHandleInbox()
{
// TODO implement me
$this->assertTrue( false );
}
public function testHandleOutbox()
{
// TODO implement me
$this->assertTrue( false );
}
}

View File

@ -18,6 +18,9 @@ use Symfony\Component\HttpFoundation\Request;
class CreateHandlerTest extends APTestCase
{
/**
* @var EventDispatcher
*/
private $eventDispatcher;
public function setUp()

View File

@ -81,7 +81,7 @@ class FollowHandlerTest extends APTestCase
'type' => 'Follow',
'object' => 'https://example.com/actor/2',
);
$eventDispatcher->addListener( OutboxActivityEvent::NAME, function( $event )
$eventDispatcher->addListener( OutboxActivityEvent::NAME, function()
use ( &$outboxDispatched )
{
$outboxDispatched = true;

View File

@ -1,4 +1,5 @@
<?php
<?php /** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Test;
use ActivityPub\ActivityPub;

View File

@ -1,4 +1,5 @@
<?php
<?php /** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Test\Auth;
use ActivityPub\Auth\AuthListener;

View File

@ -8,6 +8,9 @@ use Symfony\Component\HttpFoundation\Request;
class AuthServiceTest extends APTestCase
{
/**
* @var AuthService
*/
private $authService;
public function setUp()

View File

@ -4,8 +4,6 @@ namespace ActivityPub\Test\Auth;
use DateTime;
use ActivityPub\Auth\SignatureListener;
use ActivityPub\Crypto\HttpSignatureService;
use ActivityPub\Entities\ActivityPubObject;
use ActivityPub\Entities\Field;
use ActivityPub\Objects\ObjectsService;
use ActivityPub\Test\TestUtils\TestActivityPubObject;
use ActivityPub\Test\TestUtils\TestDateTimeProvider;

View File

@ -1,4 +1,5 @@
<?php
<?php /** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Test\Config;
use ActivityPub\Config\ActivityPubConfig;
@ -9,6 +10,9 @@ use ActivityPub\Test\TestConfig\APTestCase;
class ActivityPubModuleTest extends APTestCase
{
/**
* @var ActivityPubModule
*/
private $module;
public function setUp()

View File

@ -3,8 +3,6 @@ namespace ActivityPub\Test\Controllers;
use ActivityPub\Auth\AuthService;
use ActivityPub\Controllers\GetController;
use ActivityPub\Entities\ActivityPubObject;
use ActivityPub\Entities\Field;
use ActivityPub\Objects\ContextProvider;
use ActivityPub\Objects\CollectionsService;
use ActivityPub\Objects\ObjectsService;

View File

@ -33,6 +33,9 @@ G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
-----END RSA PRIVATE KEY-----";
/**
* @var HttpSignatureService
*/
private $httpSignatureService;
public function setUp()

View File

@ -37,7 +37,7 @@ class RsaKeypairTest extends APTestCase
$data = 'This is some data';
$signature = 'not a real signature';
$this->setExpectedException( \PHPUnit_Framework_Error::class );
$verified = $keypair->verify( $data, $signature );
$keypair->verify( $data, $signature );
}
public function testItReturnsNotVerifiedForValidButWrongSignature()

View File

@ -1,8 +1,9 @@
<?php
<?php /** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Test\Entities;
use ActivityPub\Utils\DateTimeProvider;
use DateTime;
use ActivityPub\Crypto\RsaKeypair;
use ActivityPub\Entities\ActivityPubObject;
use ActivityPub\Database\PrefixNamingStrategy;
use ActivityPub\Test\TestConfig\ArrayDataSet;
@ -13,7 +14,13 @@ use Doctrine\ORM\Tools\Setup;
class EntityTest extends SQLiteTestCase
{
/**
* @var EntityManager
*/
protected $entityManager;
/**
* @var DateTimeProvider
*/
protected $dateTimeProvider;
protected function getDataSet()
@ -87,7 +94,7 @@ class EntityTest extends SQLiteTestCase
$this->entityManager->flush();
$newPrivateKey = 'a new private key';
$object->setPrivateKey( $newPrivateKey );
$this->entityManager->persiste( $object );
$this->entityManager->persist( $object );
$this->entityManager->flush();
$now = $this->getTime( 'objects-service.create' );
$expected = new ArrayDataSet( array(

View File

@ -2,7 +2,6 @@
namespace ActivityPub\Test\Objects;
use ActivityPub\Utils\SimpleDateTimeProvider;
use Exception;
use ActivityPub\Auth\AuthService;
use ActivityPub\Objects\ContextProvider;
use ActivityPub\Objects\CollectionsService;

View File

@ -57,7 +57,6 @@ class IdProviderTest extends APTestCase
);
$idProvider = new IdProvider( $this->objectsService, $randomProvider, 'ap' );
$request = Request::create( 'https://example.com' );
$id = '';
if ( array_key_exists( 'path', $testCase ) ) {
$id = $idProvider->getId( $request, $testCase['path'] );
} else {

View File

@ -1,16 +1,16 @@
<?php
<?php /** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Test\Objects;
use ActivityPub\Utils\DateTimeProvider;
use DateTime;
use BadMethodCallException;
use ActivityPub\Test\TestConfig\SQLiteTestCase;
use ActivityPub\Test\TestConfig\ArrayDataSet;
use ActivityPub\Entities\ActivityPubObject;
use ActivityPub\Entities\Field;
use Doctrine\ORM\EntityManager;
use ActivityPub\Objects\ObjectsService;
use ActivityPub\Database\PrefixNamingStrategy;
use ActivityPub\Test\TestUtils\TestActivityPubObject;
use ActivityPub\Test\TestUtils\TestDateTimeProvider;
use Doctrine\ORM\Tools\Setup;
use GuzzleHttp\Client;
@ -18,9 +18,21 @@ use GuzzleHttp\Psr7\Response;
class ObjectsServiceTest extends SQLiteTestCase
{
/**
* @var EntityManager
*/
protected $entityManager;
/**
* @var ObjectsService
*/
protected $objectsService;
/**
* @var DateTimeProvider
*/
protected $dateTimeProvider;
/**
* @var Client
*/
protected $httpClient;
protected function getDataSet()
@ -67,7 +79,7 @@ class ObjectsServiceTest extends SQLiteTestCase
'content' => 'This is a note',
);
$now = $this->getTime( 'objects-service.create' );
$object = $this->objectsService->persist( $fields );
$this->objectsService->persist( $fields );
$expected = new ArrayDataSet( array(
'objects' => array(
array( 'id' => 1, 'created' => $now, 'lastUpdated' => $now )
@ -166,7 +178,7 @@ class ObjectsServiceTest extends SQLiteTestCase
),
);
$now = $this->getTime( 'objects-service.create' );
$object = $this->objectsService->persist( $fields );
$this->objectsService->persist( $fields );
$expected = new ArrayDataSet( array(
'objects' => array(
array( 'id' => 1, 'created' => $now, 'lastUpdated' => $now ),
@ -267,7 +279,7 @@ class ObjectsServiceTest extends SQLiteTestCase
),
);
$now = $this->getTime( 'objects-service.create' );
$object = $this->objectsService->persist( $fields );
$this->objectsService->persist( $fields );
$expected = new ArrayDataSet( array(
'objects' => array(
array( 'id' => 1, 'created' => $now, 'lastUpdated' => $now ),
@ -366,7 +378,7 @@ class ObjectsServiceTest extends SQLiteTestCase
),
);
$now = $this->getTime( 'objects-service.create' );
$object = $this->objectsService->persist( $fields );
$this->objectsService->persist( $fields );
$expected = new ArrayDataSet( array(
'objects' => array(
array( 'id' => 1, 'created' => $now, 'lastUpdated' => $now ),
@ -535,7 +547,7 @@ class ObjectsServiceTest extends SQLiteTestCase
'type' => 'Collection',
'items' => array( "https://example.com/items/1" ),
);
$object = $this->objectsService->persist( $fields );
$this->objectsService->persist( $fields );
$query = array(
'id' => 'https://example.com/collections/1'
);
@ -782,7 +794,7 @@ class ObjectsServiceTest extends SQLiteTestCase
'type' => 'Note',
'content' => 'This is a note'
);
$object = $this->objectsService->persist( $fields );
$this->objectsService->persist( $fields );
$found = $this->objectsService->dereference( 'https://example.com/note/2' );
$this->assertNull( $found );
}
@ -795,7 +807,7 @@ class ObjectsServiceTest extends SQLiteTestCase
'content' => 'This is a note'
);
$createTime = $this->getTime( 'objects-service.create' );
$object = $this->objectsService->persist( $fields );
$this->objectsService->persist( $fields );
$update = array( 'content' => 'This note has been updated' );
$updateTime = $this->getTime( 'objects-service.update' );
$this->objectsService->update( 'https://example.com/notes/1', $update );
@ -860,7 +872,7 @@ class ObjectsServiceTest extends SQLiteTestCase
),
);
$createTime = $this->getTime( 'objects-service.create' );
$object = $this->objectsService->persist( $fields );
$this->objectsService->persist( $fields );
$update = array( 'attributedTo' => array(
'id' => 'https://example.com/actors/2',
) );
@ -965,7 +977,7 @@ class ObjectsServiceTest extends SQLiteTestCase
),
);
$createTime = $this->getTime( 'objects-service.create' );
$object = $this->objectsService->persist( $fields );
$this->objectsService->persist( $fields );
$update = array( 'likes' => array(
'https://example.com/likes/3',
'https://example.com/likes/4',
@ -1062,7 +1074,7 @@ class ObjectsServiceTest extends SQLiteTestCase
'content' => 'This is a note'
);
$createTime = $this->getTime( 'objects-service.create' );
$object = $this->objectsService->persist( $fields );
$this->objectsService->persist( $fields );
$update = array( 'content' => null );
$updateTime = $this->getTime( 'objects-service.update' );
$this->objectsService->update( 'https://example.com/notes/1', $update );
@ -1114,7 +1126,6 @@ class ObjectsServiceTest extends SQLiteTestCase
'type' => 'Note',
'content' => 'This is a note',
);
$now = $this->getTime( 'objects-service.create' );
$object = $this->objectsService->persist( $fields );
$this->assertEquals( $object['content'], 'This is a note' );
$this->assertNull( $object['attributedTo'] );
@ -1127,7 +1138,6 @@ class ObjectsServiceTest extends SQLiteTestCase
'type' => 'Note',
'content' => 'This is a note',
);
$now = $this->getTime( 'objects-service.create' );
$object = $this->objectsService->persist( $fields );
$this->setExpectedException( BadMethodCallException::class );
$object['content'] = 'This should break';
@ -1140,7 +1150,6 @@ class ObjectsServiceTest extends SQLiteTestCase
'type' => 'Note',
'content' => 'This is a note',
);
$now = $this->getTime( 'objects-service.create' );
$object = $this->objectsService->persist( $fields );
$this->setExpectedException( BadMethodCallException::class );
unset( $object['content'] );
@ -1156,7 +1165,6 @@ class ObjectsServiceTest extends SQLiteTestCase
'id' => 'https://example.com/actor/1'
),
);
$now = $this->getTime( 'objects-service.create' );
$object = $this->objectsService->persist( $fields );
$this->assertEquals( $object['content'], 'This is a note' );
$this->assertInstanceOf( ActivityPubObject::class, $object['attributedTo'] );
@ -1271,9 +1279,10 @@ class ObjectsServiceTest extends SQLiteTestCase
if ( array_key_exists( 'expectedQueryResults', $testCase ) ) {
foreach ( $testCase['expectedQueryResults'] as $expectedQueryResult ) {
$result = array_map(
function( $obj ) { return $obj->asArray(); },
function( ActivityPubObject $obj ) { return $obj->asArray(); },
$this->objectsService->query( $expectedQueryResult['query'] )
);
$this->assertEquals( $expectedQueryResult['expectedResult'], $result );
}
}
}

View File

@ -1,9 +1,12 @@
<?php
<?php /** @noinspection PhpUnhandledExceptionInspection */
/** @noinspection PhpUnhandledExceptionInspection */
/** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Test\TestConfig;
use ActivityPub\ActivityPub;
use ActivityPub\Config\ActivityPubConfig;
use ActivityPub\Test\TestConfig\APTestCase;
abstract class SQLiteTestCase extends APTestCase
{

View File

@ -1,9 +1,9 @@
<?php
<?php /** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Test\TestUtils;
use ActivityPub\Entities\ActivityPubObject;
use ActivityPub\Entities\Field;
use ActivityPub\Test\TestUtils\TestField;
use DateTime;
/**

View File

@ -1,4 +1,5 @@
<?php
<?php /** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Test\TestUtils;
use DateTime;

View File

@ -3,7 +3,7 @@ namespace ActivityPub\Test\TestUtils;
use ActivityPub\Entities\ActivityPubObject;
use ActivityPub\Entities\Field;
use ActivityPub\Test\TestUtils\TestActivityPubObject;
use DateTime;
/**
* Like a Field, but with fixed timestamps for testing
@ -21,24 +21,24 @@ class TestField extends Field
$this->fixedTime = $time;
}
public function setTargetObject( ActivityPubObject $targetObject, $time = null )
public function setTargetObject( ActivityPubObject $targetObject, DateTime $time = null )
{
parent::setTargetObject( $targetObject, $time );
$this->lastUpdated = $this->fixedTime;
}
public function setValue( $value, $time = null )
public function setValue( $value, DateTime $time = null )
{
parent::setValue( $value, $time );
$this->lastUpdated = $this->fixedTime;
}
protected function setCreated( $timestamp )
protected function setCreated( DateTime $timestamp )
{
// don't set created
}
protected function setLastupdated( $timestamp )
protected function setLastupdated( DateTime $timestamp )
{
// don't set lastUpdated
}

View File

@ -1,4 +1,5 @@
<?php
<?php /** @noinspection PhpUnhandledExceptionInspection */
namespace ActivityPub\Test;
use ActivityPub\ActivityPub;