2022-05-19 15:24:21 -04:00
< ? php
/**
2023-01-01 09:36:24 -05:00
* @ copyright Copyright ( C ) 2010 - 2023 , the Friendica project
2022-05-19 15:24:21 -04:00
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*
*/
namespace Friendica\Protocol\ActivityPub ;
use Friendica\Core\Logger ;
2022-09-21 15:03:07 -04:00
use Friendica\Core\Worker ;
2022-05-24 03:02:42 -04:00
use Friendica\Database\DBA ;
2022-05-19 15:24:21 -04:00
use Friendica\DI ;
use Friendica\Model\Contact ;
use Friendica\Model\GServer ;
2022-11-03 00:03:39 -04:00
use Friendica\Model\Item ;
2022-05-19 15:24:21 -04:00
use Friendica\Model\Post ;
2022-12-30 01:45:04 -05:00
use Friendica\Model\User ;
2022-05-19 15:24:21 -04:00
use Friendica\Protocol\ActivityPub ;
2022-12-30 16:20:28 -05:00
use Friendica\Protocol\Delivery as ProtocolDelivery ;
2022-05-19 15:24:21 -04:00
use Friendica\Util\HTTPSignature ;
class Delivery
{
2022-05-24 03:02:42 -04:00
/**
* Deliver posts to the given inbox
*
* @ param string $inbox
* @ return array with the elements " success " and " uri_ids " of the failed posts
*/
2022-05-20 00:42:10 -04:00
public static function deliver ( string $inbox ) : array
2022-05-19 15:24:21 -04:00
{
$uri_ids = [];
$posts = Post\Delivery :: selectForInbox ( $inbox );
$serverfail = false ;
foreach ( $posts as $post ) {
2022-12-30 01:45:04 -05:00
$owner = User :: getOwnerDataById ( $post [ 'uid' ]);
if ( ! $owner ) {
Post\Delivery :: remove ( $post [ 'uri-id' ], $inbox );
Post\Delivery :: incrementFailed ( $post [ 'uri-id' ], $inbox );
continue ;
}
2022-05-19 15:24:21 -04:00
if ( ! $serverfail ) {
2022-12-30 01:45:04 -05:00
$result = self :: deliverToInbox ( $post [ 'command' ], 0 , $inbox , $owner , $post [ 'receivers' ], $post [ 'uri-id' ]);
2022-05-19 15:24:21 -04:00
if ( $result [ 'serverfailure' ]) {
// In a timeout situation we assume that every delivery to that inbox will time out.
// So we set the flag and try all deliveries at a later time.
2022-08-31 01:01:22 -04:00
Logger :: notice ( 'Inbox delivery has a server failure' , [ 'inbox' => $inbox ]);
2022-05-19 15:24:21 -04:00
$serverfail = true ;
}
2022-09-21 15:03:07 -04:00
Worker :: coolDown ();
2022-05-19 15:24:21 -04:00
}
2022-05-24 03:02:42 -04:00
if ( $serverfail || ( ! $result [ 'success' ] && ! $result [ 'drop' ])) {
2022-05-19 15:24:21 -04:00
$uri_ids [] = $post [ 'uri-id' ];
}
}
Logger :: debug ( 'Inbox delivery done' , [ 'inbox' => $inbox , 'posts' => count ( $posts ), 'failed' => count ( $uri_ids ), 'serverfailure' => $serverfail ]);
return [ 'success' => empty ( $uri_ids ), 'uri_ids' => $uri_ids ];
}
2022-05-24 03:02:42 -04:00
/**
* Deliver the given post to the given inbox
*
* @ param string $cmd
* @ param integer $item_id
* @ param string $inbox
2022-12-30 01:45:04 -05:00
* @ param array $owner Sender owner - view record
2022-05-24 03:02:42 -04:00
* @ param array $receivers
* @ param integer $uri_id
* @ return array
*/
2022-12-30 01:45:04 -05:00
public static function deliverToInbox ( string $cmd , int $item_id , string $inbox , array $owner , array $receivers , int $uri_id ) : array
2022-05-19 15:24:21 -04:00
{
2022-12-30 01:45:04 -05:00
/** @var int $uid */
$uid = $owner [ 'uid' ];
2022-05-19 15:24:21 -04:00
if ( empty ( $item_id ) && ! empty ( $uri_id ) && ! empty ( $uid )) {
2022-11-03 00:03:39 -04:00
$item = Post :: selectFirst ([ 'id' , 'parent' , 'origin' , 'gravity' , 'verb' ], [ 'uri-id' => $uri_id , 'uid' => [ $uid , 0 ]], [ 'order' => [ 'uid' => true ]]);
2022-05-19 15:24:21 -04:00
if ( empty ( $item [ 'id' ])) {
2022-08-30 15:45:30 -04:00
Logger :: warning ( 'Item not found, removing delivery' , [ 'uri-id' => $uri_id , 'uid' => $uid , 'cmd' => $cmd , 'inbox' => $inbox ]);
2022-05-19 15:24:21 -04:00
Post\Delivery :: remove ( $uri_id , $inbox );
2022-09-16 01:00:06 -04:00
return [ 'success' => true , 'serverfailure' => false , 'drop' => false ];
2022-11-03 00:03:39 -04:00
} elseif ( ! DI :: config () -> get ( 'system' , 'redistribute_activities' ) && ! $item [ 'origin' ] && ( $item [ 'gravity' ] == Item :: GRAVITY_ACTIVITY )) {
Logger :: notice ( 'Activities are not relayed, removing delivery' , [ 'verb' => $item [ 'verb' ], 'uri-id' => $uri_id , 'uid' => $uid , 'cmd' => $cmd , 'inbox' => $inbox ]);
Post\Delivery :: remove ( $uri_id , $inbox );
return [ 'success' => true , 'serverfailure' => false , 'drop' => false ];
2022-05-19 15:24:21 -04:00
} else {
$item_id = $item [ 'id' ];
}
}
$success = true ;
$serverfail = false ;
2022-05-24 03:02:42 -04:00
$drop = false ;
2022-05-19 15:24:21 -04:00
2022-12-30 16:20:28 -05:00
if ( $cmd == ProtocolDelivery :: MAIL ) {
2022-05-19 15:24:21 -04:00
$data = ActivityPub\Transmitter :: createActivityFromMail ( $item_id );
if ( ! empty ( $data )) {
2022-12-30 01:45:04 -05:00
$success = HTTPSignature :: transmit ( $data , $inbox , $owner );
2022-05-19 15:24:21 -04:00
}
2022-12-30 16:20:28 -05:00
} elseif ( $cmd == ProtocolDelivery :: SUGGESTION ) {
2022-12-30 01:45:04 -05:00
$success = ActivityPub\Transmitter :: sendContactSuggestion ( $owner , $inbox , $item_id );
2022-12-30 16:20:28 -05:00
} elseif ( $cmd == ProtocolDelivery :: RELOCATION ) {
2022-05-19 15:24:21 -04:00
// @todo Implementation pending
2022-12-30 16:20:28 -05:00
} elseif ( $cmd == ProtocolDelivery :: REMOVAL ) {
2022-12-30 01:45:04 -05:00
$success = ActivityPub\Transmitter :: sendProfileDeletion ( $owner , $inbox );
2022-12-30 16:20:28 -05:00
} elseif ( $cmd == ProtocolDelivery :: PROFILEUPDATE ) {
2022-12-30 01:45:04 -05:00
$success = ActivityPub\Transmitter :: sendProfileUpdate ( $owner , $inbox );
2022-05-19 15:24:21 -04:00
} else {
2022-07-29 10:17:53 -04:00
$data = ActivityPub\Transmitter :: createCachedActivityFromItem ( $item_id );
2022-05-19 15:24:21 -04:00
if ( ! empty ( $data )) {
$timestamp = microtime ( true );
2022-12-30 01:45:04 -05:00
$response = HTTPSignature :: post ( $data , $inbox , $owner );
2022-05-19 15:24:21 -04:00
$runtime = microtime ( true ) - $timestamp ;
$success = $response -> isSuccess ();
$serverfail = $response -> isTimeout ();
if ( ! $success ) {
2022-05-24 03:02:42 -04:00
// 5xx errors are problems on the server. We don't need to continue delivery then.
2022-05-19 15:24:21 -04:00
if ( ! $serverfail && ( $response -> getReturnCode () >= 500 ) && ( $response -> getReturnCode () <= 599 )) {
$serverfail = true ;
}
2022-05-24 03:02:42 -04:00
// A 404 means that the inbox doesn't exist. We can stop the delivery here.
if ( ! $serverfail && ( $response -> getReturnCode () == 404 )) {
$serverfail = true ;
}
2022-05-19 15:24:21 -04:00
$xrd_timeout = DI :: config () -> get ( 'system' , 'xrd_timeout' );
if ( ! $serverfail && $xrd_timeout && ( $runtime > $xrd_timeout )) {
$serverfail = true ;
}
2022-05-24 03:02:42 -04:00
2022-05-19 15:24:21 -04:00
$curl_timeout = DI :: config () -> get ( 'system' , 'curl_timeout' );
if ( ! $serverfail && $curl_timeout && ( $runtime > $curl_timeout )) {
$serverfail = true ;
}
2022-05-24 03:02:42 -04:00
// Resubscribe to relay server upon client error
if ( ! $serverfail && ( $response -> getReturnCode () >= 400 ) && ( $response -> getReturnCode () <= 499 )) {
$actor = self :: fetchActorForRelayInbox ( $inbox );
if ( ! empty ( $actor )) {
$drop = ! ActivityPub\Transmitter :: sendRelayFollow ( $actor );
Logger :: notice ( 'Resubscribed to relay' , [ 'url' => $actor , 'success' => ! $drop ]);
2023-06-15 18:04:28 -04:00
} elseif ( $cmd == ProtocolDelivery :: DELETION ) {
2022-05-24 08:27:35 -04:00
// Remote systems not always accept our deletion requests, so we drop them if rejected.
// Situation is: In Friendica we allow the thread owner to delete foreign comments to their thread.
// Most AP systems don't allow this, so they will reject the deletion request.
$drop = true ;
2022-05-24 03:02:42 -04:00
}
}
2022-08-31 01:01:22 -04:00
Logger :: notice ( 'Delivery failed' , [ 'retcode' => $response -> getReturnCode (), 'serverfailure' => $serverfail , 'drop' => $drop , 'runtime' => round ( $runtime , 3 ), 'uri-id' => $uri_id , 'uid' => $uid , 'item_id' => $item_id , 'cmd' => $cmd , 'inbox' => $inbox ]);
2022-05-19 15:24:21 -04:00
}
if ( $uri_id ) {
if ( $success ) {
Post\Delivery :: remove ( $uri_id , $inbox );
} else {
Post\Delivery :: incrementFailed ( $uri_id , $inbox );
}
}
2022-11-03 00:03:39 -04:00
} elseif ( $uri_id ) {
Post\Delivery :: remove ( $uri_id , $inbox );
2022-05-19 15:24:21 -04:00
}
}
self :: setSuccess ( $receivers , $success );
2022-05-24 13:28:35 -04:00
Logger :: debug ( 'Delivered' , [ 'uri-id' => $uri_id , 'uid' => $uid , 'item_id' => $item_id , 'cmd' => $cmd , 'inbox' => $inbox , 'success' => $success , 'serverfailure' => $serverfail , 'drop' => $drop ]);
2022-05-19 15:24:21 -04:00
2022-12-30 16:20:28 -05:00
if (( $success || $drop ) && in_array ( $cmd , [ ProtocolDelivery :: POST ])) {
2022-05-19 15:24:21 -04:00
Post\DeliveryData :: incrementQueueDone ( $uri_id , Post\DeliveryData :: ACTIVITYPUB );
}
2022-05-24 03:02:42 -04:00
return [ 'success' => $success , 'serverfailure' => $serverfail , 'drop' => $drop ];
}
/**
* Fetch the actor of the given inbox of an relay server
*
* @ param string $inbox
* @ return string
*/
private static function fetchActorForRelayInbox ( string $inbox ) : string
{
2022-05-24 04:06:48 -04:00
$apcontact = DBA :: selectFirst ( 'apcontact' , [ 'url' ], [ " `sharedinbox` = ? AND `type` = ? AND `url` IN (SELECT `url` FROM `contact` WHERE `uid` = ? AND `rel` = ?) " ,
$inbox , 'Application' , 0 , Contact :: FRIEND ]);
2022-05-24 04:02:55 -04:00
return $apcontact [ 'url' ] ? ? '' ;
2022-05-19 15:24:21 -04:00
}
2022-05-24 03:02:42 -04:00
/**
2023-03-22 00:08:33 -04:00
* mark or unmark the given receivers for archival upon success
2022-05-24 03:02:42 -04:00
*
* @ param array $receivers
* @ param boolean $success
* @ return void
*/
2022-05-19 15:24:21 -04:00
private static function setSuccess ( array $receivers , bool $success )
{
2022-12-28 09:56:12 -05:00
$gsid = null ;
$update_counter = 0 ;
2022-05-19 15:24:21 -04:00
foreach ( $receivers as $receiver ) {
2022-12-28 09:56:12 -05:00
// Only update the first 10 receivers to avoid flooding the remote system with requests
if ( $success && ( $update_counter < 10 ) && Contact :: updateByIdIfNeeded ( $receiver )) {
$update_counter ++ ;
}
2022-05-19 15:24:21 -04:00
$contact = Contact :: getById ( $receiver );
if ( empty ( $contact )) {
continue ;
}
$gsid = $gsid ? : $contact [ 'gsid' ];
if ( $success ) {
Contact :: unmarkForArchival ( $contact );
} else {
Contact :: markForArchival ( $contact );
}
}
if ( ! empty ( $gsid )) {
GServer :: setProtocol ( $gsid , Post\DeliveryData :: ACTIVITYPUB );
}
}
}