seria/src/Torrents/AnnounceRouting.php
2024-03-30 00:38:44 +00:00

144 lines
5.1 KiB
PHP

<?php
namespace Seria\Torrents;
use RuntimeException;
use Index\Http\Routing\{HttpGet,RouteHandler};
use Index\Serialisation\Bencode;
use Seria\Users\UsersContext;
class AnnounceRouting extends RouteHandler {
public const INTERVAL = 1800;
public const INTERVAL_MIN = 30;
public function __construct(
private TorrentsContext $torrentsCtx,
private UsersContext $usersCtx
) {}
#[HttpGet('/announce')]
#[HttpGet('/announce.php')]
#[HttpGet('/announce/:key')]
#[HttpGet('/announce.php/:key')]
public function getAnnounce($response, $request, string $key = '') {
if(strlen(inet_pton($_SERVER['REMOTE_ADDR'])) !== 4)
return new AnnounceFailure('Tracker is only supported over IPv4, please reset your DNS cache.');
$torrents = $this->torrentsCtx->getTorrents();
$peers = $this->torrentsCtx->getPeers();
$users = $this->usersCtx->getUsers();
$userInfo = null;
if($key !== '')
try {
$userInfo = $users->getUser($key, 'passkey');
} catch(RuntimeException $ex) {
sleep(3);
return new AnnounceFailure('Authentication failed.');
}
$infoHash = (string)$request->getParam('info_hash');
if($infoHash === '')
return new AnnounceFailure('Missing info hash.');
if(strlen($infoHash) !== 20)
return new AnnounceFailure('Invalid info hash.');
$peerId = (string)$request->getParam('peer_id');
if($peerId === '')
return new AnnounceFailure('Missing peer id.');
if(strlen($peerId) !== 20)
return new AnnounceFailure('Invalid peer id.');
$address = $_SERVER['REMOTE_ADDR'];
$port = (int)$request->getParam('port');
if($port < 1 || $port > 0xFFFF)
return new AnnounceFailure('Invalid port number.');
$key = (string)$request->getParam('key');
if(strlen($key) > 128)
return new AnnounceFailure('Key is ridiculous.');
$event = (string)$request->getParam('event');
if(strlen($event) > 128)
return new AnnounceFailure('Event is fucked up.');
$userAgent = (string)$request->getHeaderLine('User-Agent');
if(strlen($userAgent) > 255)
return new AnnounceFailure('Agent name is stupid.');
$noPeerId = !empty($request->getParam('no_peer_id'));
$compact = !empty($request->getParam('compact'));
$bytesUploaded = (int)$request->getParam('uploaded', FILTER_SANITIZE_NUMBER_INT);
$bytesDownloaded = (int)$request->getParam('downloaded', FILTER_SANITIZE_NUMBER_INT);
$bytesRemaining = (int)$request->getParam('left', FILTER_SANITIZE_NUMBER_INT);
// handle this, return error if too many
$wantsPeerAmount = (int)$request->getParam('numwant', FILTER_SANITIZE_NUMBER_INT);
try {
$torrentInfo = $torrents->getTorrent($infoHash, 'hash');
} catch(RuntimeException $ex) {
return new AnnounceFailure('Info hash not found.');
}
$canDownload = $this->torrentsCtx->canDownloadTorrent($torrentInfo, $userInfo);
if($canDownload !== '')
return new AnnounceFailure(match($canDownload) {
'inactive' => 'This package is inactive.',
'private' => 'You must be logged in for this package.',
'pending' => 'This package is pending approval.',
default => $canDownload,
});
$peerInfo = $peers->getPeer($torrentInfo, $peerId);
if($peerInfo === null) {
// could probably skip this is the event is 'stopped'
$peerInfo = $peers->createPeer(
$torrentInfo, $userInfo, $peerId, $address, $port, self::INTERVAL,
$userAgent, $key, $bytesUploaded, $bytesDownloaded, $bytesRemaining
);
} else {
if(!$peerInfo->verifyKey($key)) {
sleep(3);
return new AnnounceFailure('Peer verification failed.');
}
if(!$peerInfo->verifyUser($userInfo)) {
sleep(3);
return new AnnounceFailure('User verification failed.');
}
if($userInfo !== null)
$users->incrementTransferStats(
$userInfo,
$bytesDownloaded - $peerInfo->getBytesDownloaded(),
$bytesUploaded - $peerInfo->getBytesUploaded()
);
$peers->updatePeer(
$torrentInfo, $peerInfo, $address, $port, self::INTERVAL,
$userAgent, $bytesUploaded, $bytesDownloaded, $bytesRemaining
);
}
if($event === 'stopped') {
$peers->deletePeer($torrentInfo, $peerInfo);
return new AnnounceEmpty($compact);
}
$peers->pruneExpiredPeers();
$peerInfos = $peers->getPeers($torrentInfo);
return new AnnounceInfo(
$peerInfos,
self::INTERVAL,
self::INTERVAL_MIN,
$compact,
$noPeerId
);
}
}