/Bencode

A pure and simple PHP library for encoding and decoding Bencode data, with torrent file parse and vaildate.

Primary LanguagePHPMIT LicenseMIT

PHP Bencode Library

Codacy Badge FOSSA Status

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.

FOSSA Status