144 lines
5.1 KiB
PHP
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
|
|
);
|
|
}
|
|
}
|