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 forked from OPSnet/bencode-torrent, with almost same methods like arokettu/bencode, arokettu/torrent-file
composer require rhilip/bencodeif 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.0The only Break Change is ParseErrorException in 1.x.x rename to ParseException in 2.x.x.
A pure PHP class to encode and decode Bencode data from file path and string.
<?phprequire'/path/to/vendor/autoload.php';
useRhilip\Bencode\Bencode;
useRhilip\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 Catchtry {
Bencode::decode('wrong_string');
} catch (ParseException$e) {
// do something
}A pure PHP class to work with torrent files
note: Add in version 2
<?phprequire'/path/to/vendor/autoload.php';
useRhilip\Bencode\TorrentFile;
useRhilip\Bencode\ParseException;
// 0. Defined Constprint(TorrentFile::PROTOCOL_V1); // v1print(TorrentFile::PROTOCOL_V2); // v2print(TorrentFile::PROTOCOL_HYBRID); // hybridprint(TorrentFile::FILEMODE_SINGLE); // singleprint(TorrentFile::FILEMODE_MULTI); // multi// 1. Load Torrent and get instancetry {
$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([
['127.0.0.1', 6881],
['your.router.node', 4804],
['2001:db8:100:0:d5c8:db3f:995e:c0f7', 1941],
]);
$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}/** * @since v2.5.5, you can use isDirectory() method to quick check if the torrent is a directory or not. */$isDirectory = $torrent->isDirectory();
/** * @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(); // inttry {
$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')) {
thrownewParseException('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] * ] * * @since v2.5.5, extended file attributes (BEP47) will parse into `attr` field if exist, * and symlink path will be in `symlink path` field with string format if file is symlink * * for example, * [ * ["path" => "filename1", "size" => 123, "attr" => ""], * ["path" => "filename2", "size" => 123, "attr" => "phx"], * ["path" => "filename3", "size" => 0, "attr" => "l", "symlink path" => "dir1/target.ext"], * ] */$fileList = $torrent->getFileList();
/** * Return a dict like: * [ * "torrentname" => [ * "directory" => [ * "filename2" => 2345 * ], * "filename1" => 123 * ] * ] * * @since 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. unhybridized/** * Create an unhybridized copy of a hybrid torrent for the specified single protocol version * (does not modify the original instance). * * This method returns a clone of the current object and removes metadata fields from the clone * that are incompatible with the target version to produce an "unhybridized" torrent. * For example: * when the target is `TorrentFile::PROTOCOL_V1`, v2 fields are removed; * when the target is `TorrentFile::PROTOCOL_V2`, v1 fields are removed. * * @since v2.5.0 */$v1ProtocolOnlyTorrent = $hybridTorrent->unhybridizedTo(TorrentFile::PROTOCOL_V1);
$v2ProtocolOnlyTorrent = $hybridTorrent->unhybridizedTo(TorrentFile::PROTOCOL_V2);
// 7. 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 thistry {
$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 ParseExceptionThe library is available as open source under the terms of the MIT License.