rhilip / bencode
A pure and simple PHP library for encoding and decoding Bencode data
Installs: 19 391
Dependents: 1
Suggesters: 0
Security: 0
Stars: 34
Watchers: 5
Forks: 7
Open Issues: 0
Requires
- php: ^7.3|^8.0|^8.1|^8.2|^8.2|^8.3
Requires (Dev)
- ext-json: *
- phpunit/phpunit: ^9.0
Suggests
- php-64bit: Running 64 bit is recommended to prevent integer overflow
README
Bencode is the encoding used by the peer-to-peer file sharing system BitTorrent for storing and transmitting loosely structured data.
This is a pure PHP library that allows you to encode and decode Bencode data, with torrent file parse and vaildate.
This library is fork from OPSnet/bencode-torrent, with same method like sandfoxme/bencode, sandfoxme/torrent-file
Installation
composer require rhilip/bencode
if you don't use Rhilip\Bencode\TorrentFile
class, you can specific version to 1.x.x
and if your PHP version is 5.6
or 7.0-7.2
, please stop at version 1.2.0
and 2.0.0
composer require rhilip/bencode:1.2.0
The only Break Change is ParseErrorException
in 1.x.x
rename to ParseException
in 2.x.x
.
Usage
Class Rhilip\Bencode\Bencode
A pure PHP class to encode and decode Bencode data from file path and string.
<?php require '/path/to/vendor/autoload.php'; use Rhilip\Bencode\Bencode; use Rhilip\Bencode\ParseException; // Decodes a BEncoded string Bencode::decode($string); // Encodes string/array/int to a BEncoded string Bencode::encode($data); // Decodes a BEncoded file From path Bencode::load($path); // Encodes string/array/int to a BEncoded file Bencode::dump($path, $data); // With Error Catch try { Bencode::decode('wrong_string'); } catch (ParseException $e) { // do something }
Class Rhilip\Bencode\TorrentFile
A pure PHP class to work with torrent files
note: Add in version 2
<?php require '/path/to/vendor/autoload.php'; use Rhilip\Bencode\TorrentFile; use Rhilip\Bencode\ParseException; // 0. Defined Const print(TorrentFile::PROTOCOL_V1); // v1 print(TorrentFile::PROTOCOL_V2); // v2 print(TorrentFile::PROTOCOL_HYBRID); // hybrid print(TorrentFile::FILEMODE_SINGLE); // single print(TorrentFile::FILEMODE_MULTI); // multi // 1. Load Torrent and get instance try { $torrent = TorrentFile::load($path); $torrent = TorrentFile::loadFromString($string); } catch (ParseException $e) { // do something } // 2. Save Torrent to path or string (for echo) $dumpStatus = $torrent->dump($path); print($torrent->dumpToString()); // 3. Work with Root Fields $torrent->getRootData(); // $root; $rootField = $torrent->getRootField($field, ?$default); // $root[$field] ?? $default; $torrent->setRootField($field, $value); // $root[$field] = $value; $torrent->unsetRootField($field); // unset($root[$field]); $torrent->cleanRootFields(?$allowedKeys); // remove fields which is not allowed in root $torrent->setAnnounce('udp://example.com/announce'); $announce = $torrent->getAnnounce(); $torrent->setAnnounceList([['https://example1.com/announce'], ['https://example2.com/announce', 'https://example3.com/announce']]); $announceList = $torrent->getAnnounceList(); $torrent->setComment('Rhilip\'s Torrent'); $comment = $torrent->getComment(); $torrent->setCreatedBy('Rhilip'); $createdBy = $torrent->getCreatedBy(); $torrent->setCreationDate(time()); $creationDate = $torrent->getCreationDate(); $torrent->setHttpSeeds(['udp://example.com/seed']); $httpSeeds = $torrent->getHttpSeeds(); $torrent->setNodes(['udp://example.com/seed']); $nodes = $torrent->getNodes(); $torrent->setUrlList(['udp://example.com/seed']); $urlList = $torrent->getUrlList(); // 4. Work with Info Field $torrent->getInfoData(); // $root['info']; $infoField = $torrent->getInfoField($field, ?$default); // $info[$field] ?? $default; $torrent->setInfoField($field, $value); // $info[$field] = $value; $torrent->unsetInfoField($field); // unset($info[$field]); $torrent->cleanInfoFields(?$allowedKeys); // remove fields which is not allowed in info $protocol = $torrent->getProtocol(); // TorrentFile::PROTOCOL_{V1,V2,HYBRID} $fileMode = $torrent->getFileMode(); // TorrentFile::FILEMODE_{SINGLE,MULTI} /** * @note since we may edit $root['info'], so when call ->getInfoHash* method, * we will calculate it each call without cache return-value. */ $torrent->getInfoHashV1(?$binary); // If $binary is true return 20-bytes string, otherwise 40-character hexadecimal number $torrent->getInfoHashV2(?$binary); // If $binary is true return 32-bytes string, otherwise 64-character hexadecimal number $torrent->getInfoHash(?$binary); // return v2-infohash if there is one, otherwise return v1-infohash $torrent->getInfoHashs(?$binary); // return [TorrentFile::PROTOCOL_V1 => v1-infohash, TorrentFile::PROTOCOL_V2 => v2-infohash] $torrent->getInfoHashV1ForAnnounce(); // return the v1 info-hash in announce ( 20-bytes string ) $torrent->getInfoHashV2ForAnnounce(); // return the v2 (truncated) info-hash in announce $torrent->getInfoHashsForAnnounce(); // same as getInfoHashs() but in announce $torrent->getPieceLength(); // int try { $torrent->setName($name); } catch(\InvalidArgumentException $e) { // Do something } $name = $torrent->getName(); $torrent->setSource('Rhilip\'s blog'); $source = $torrent->getSource(); $private = $torrent->isPrivate(); // true or false $torrent->setPrivate(true); $magnetLink = $torrent->getMagnetLink(); // 5. Work with torrent, it will try to parse torrent ( cost time ) $torrent->setParseValidator(function ($filename, $paths) { /** * Before parse torrent ( call getSize, getFileCount, getFileList, getFileTree method ), * you can set a validator to test if filename or paths is valid, * And break parse process by any throw Exception. */ print_r([$filename, $paths]); if (str_contains($filename, 'F**k')) { throw new ParseException('Not allowed filename in torrent'); } }); /** * parse method will automatically called when use getSize, getFileCount, getFileList, getFileTree method, * However you can also call parse method manually. */ $torrent->parse(); // ['total_size' => $totalSize, 'count' => $count, 'files' => $fileList, 'fileTree' => $fileTree] /** * Note: Since we prefer to parse `file tree` in info dict in v2 or hybrid torrent, * The padding file may not count in size, fileCount, fileList and fileTree. */ $size = $torrent->getSize(); $count = $torrent->getFileCount(); /** * Return a list like: * [ * ["path" => "filename1", "size" => 123], // 123 is file size * ["path" => "directory/filename2", "size" => 2345] * ] * */ $fileList = $torrent->getFileList(); /** * Return a dict like: * [ * "torrentname" => [ * "directory" => [ * "filename2" => 2345 * ], * "filename1" => 123 * ] * ] * * > Add in v2.4.0 * You can now pass argument to control the fileTree sort type. By default, this argument is TorrentFile::FILETREE_SORT_NORMAL. * Control Const (see different in `tests/TorrentFileTreeSortTest.php` file): * - TorrentFile::FILETREE_SORT_NORMAL : not sort, also means sort by torrent file parsed order * - TorrentFile::FILETREE_SORT_STRING : sort by filename ASC ("natural ordering" and "case-insensitively") * - TorrentFile::FILETREE_SORT_FOLDER : sort by filetype (first folder, then file) * - TorrentFile::FILETREE_SORT_NATURAL: sort by both filetype and filename ( same as `TorrentFile::FILETREE_SORT_STRING | TorrentFile::FILETREE_SORT_FOLDER` ) * */ $fileTree = $torrent->getFileTree(?$sortType = TorrentFile::FILETREE_SORT_NORMAL); // 6. Other method $torrent->cleanCache(); // Note 1: clean,set,unset method are chaining $torrent ->clean() ->setAnnounce('https://example.com/announce') ->setAnnounceList([ ['https://example.com/announce'], ['https://example1.com/announce'] ]) ->setSource('example.com') ->setPrivate(true); // Note 2: parse method may fail when get a deep invalid torrent, so it can wrapper like this try { $torrent = TorrentFile::load($_POST['torrent']['tmp_name']); $torrent/** ->setParseValidator(function () {}) */->parse(); } catch (ParseException $e) { // do something to notice user. } print($torrent->getFileCount()); // safe to use other method without any ParseException
License
The library is available as open source under the terms of the MIT License.