mirror of
https://github.com/OPSnet/bencode-torrent.git
synced 2026-11-07 17:38:37 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe026fb3a3 | ||
|
|
922a60224d | ||
|
|
a56d7d85c2 | ||
|
|
d2dacb03ec | ||
|
|
9a030b6823 | ||
|
|
2addd8d588 | ||
|
|
ea558fc679 | ||
|
|
42f5170482 | ||
|
|
917cf865a9 | ||
|
|
ca9617422a | ||
|
|
5ce1e6c6cc | ||
|
|
6778e228fb | ||
|
|
9748eef34d | ||
|
|
3efb191299 | ||
|
|
bdbc17e173 | ||
|
|
a9229a067b | ||
|
|
93707559cc | ||
|
|
1b414569bd | ||
|
|
e07f029d12 | ||
|
|
95374ae069 | ||
|
|
11c2e4519c | ||
|
|
911e2899f4 | ||
|
|
dd0243afeb | ||
|
|
da95cbacce | ||
|
|
c0e4100135 | ||
|
|
d634179b9b | ||
|
|
007c0fe9f3 | ||
|
|
d95a0c1f8c | ||
|
|
6b9d56bfc7 | ||
|
|
5b94cf1056 | ||
|
|
74929fdac8 |
No files matched your search
@@ -0,0 +1,46 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '*' ]
|
||||
pull_request:
|
||||
branches: [ '*' ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.1', '8.2']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer, pecl
|
||||
coverage: pcov
|
||||
|
||||
- name: Validate composer.json and composer.lock
|
||||
run: composer validate
|
||||
|
||||
- name: Cache Composer packages
|
||||
id: composer-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: vendor
|
||||
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.php-version }}-composer-
|
||||
${{ runner.os }}-composer-
|
||||
- name: Install dependencies
|
||||
if: steps.composer-cache.outputs.cache-hit != 'true'
|
||||
run: composer install --prefer-dist --no-progress --no-suggest
|
||||
|
||||
- run: composer lint
|
||||
|
||||
- run: composer stan
|
||||
|
||||
- run: composer test
|
||||
@@ -3,3 +3,5 @@ report/
|
||||
vendor/
|
||||
.DS_Store
|
||||
composer.lock
|
||||
.phpunit.result.cache
|
||||
.phpunit.cache
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
language: php
|
||||
sudo: true
|
||||
dist: trusty
|
||||
|
||||
php:
|
||||
- '7.0'
|
||||
- '7.1'
|
||||
- '7.2'
|
||||
- nightly
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
|
||||
install:
|
||||
- composer install
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit
|
||||
- vendor/bin/phpcs src
|
||||
- vendor/bin/phpcs tests
|
||||
@@ -1,16 +1,41 @@
|
||||
BEncode Torrent
|
||||
===============
|
||||
|
||||
PHP library for encoding and decoding BitTorrent BEncode data, focused around
|
||||
[Gazelle](https://github.com/ApolloRIP/Gazelle).
|
||||
[](https://github.com/OPSnet/bencode-torrent/actions/workflows/test.yml)
|
||||
[](https://packagist.org/packages/orpheusnet/bencode-torrent)
|
||||

|
||||
|
||||
PHP library for encoding and decoding BitTorrent BEncode data, focused around usage within
|
||||
[Gazelle](https://github.com/OPSnet/Gazelle).
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
`composer require orpheusnet/bencode-torrent`
|
||||
|
||||
```php
|
||||
require('vendor/autoload.php');
|
||||
$bencode = new \OrpheusNET\BencodeTorrent\BencodeTorrent();
|
||||
$bencode->decodeFile('path/to/file.torrent');
|
||||
var_dump($bencode->getName());
|
||||
var_dump($bencode->getHexInfoHash());
|
||||
var_dump($bencode->getFileList());
|
||||
```
|
||||
|
||||
See the [`BencodeTorrent`](src/BencodeTorrent.php) class for more information on the available methods.
|
||||
Additionally, see [`Bencode`](src/Bencode.php) for the underlying library to encode/decode BEncode data.
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
BEncode is the encoding used by BitTorrent to store and transmitting loosely structured data. It supports:
|
||||
|
||||
BEncode is the encoding used by BitTorrent to store and transmitting loosely structured data. It supports
|
||||
* byte strings
|
||||
* integers
|
||||
* lists
|
||||
* dictionaries (associative arrays, where keys are sorted alphabetically)
|
||||
|
||||
You can see more information about how these types are supported at
|
||||
You can see more information about how these types are supported at
|
||||
[BitTorrentSpecification#Bencoding](https://wiki.theory.org/index.php/BitTorrentSpecification#Bencoding).
|
||||
|
||||
In addition to the above, torrent files are expected to be BEncoded dictionaries that contain minimally the keys
|
||||
@@ -23,8 +48,9 @@ As such, this library will make some checks when loading data that these mandato
|
||||
raised. More information on these fields can be found at
|
||||
[BitTorrentSpecification#Metainfo_File_Structure](https://wiki.theory.org/index.php/BitTorrentSpecification#Metainfo_File_Structure).
|
||||
|
||||
Finally, this library is primarily aimed at being used within the [Gazelle](https://github.com/ApolloRIP/Gazelle) so
|
||||
Finally, this library is primarily aimed at being used within the [Gazelle](https://github.com/OPSnet/Gazelle) so
|
||||
we have some utility functions within the library that make sense there to accomplish the following things:
|
||||
|
||||
* Ensuring torrent files are marked as 'private'
|
||||
* Setting a 'source' on torrents (to ensure unique info hash)
|
||||
* Cleaning out unnecessary fields that also reveal stuff about a user (like __announce list__ and __created by__)
|
||||
@@ -32,5 +58,5 @@ we have some utility functions within the library that make sense there to accom
|
||||
|
||||
This is based (loosely) off the code in the two separate BEncode libraries within WCD's Gazelle
|
||||
([bencodetorrent.class.php](https://github.com/WhatCD/Gazelle/blob/master/classes/bencodetorrent.class.php) and
|
||||
[torrent.class.php](https://github.com/WhatCD/Gazelle/blob/master/classes/torrent.class.php)), but without the
|
||||
necessary 32bit shims as well as make it a unified library used for both uploading and downloading the torrent files.
|
||||
[torrent.class.php](https://github.com/WhatCD/Gazelle/blob/master/classes/torrent.class.php)), but without the now
|
||||
unnecessary 32bit shims as well as making it a unified library used for both uploading and downloading the torrent files.
|
||||
+17
-9
@@ -1,24 +1,32 @@
|
||||
{
|
||||
"type": "library",
|
||||
"name": "apollorip/bencode-torrent",
|
||||
"name": "orpheusnet/bencode-torrent",
|
||||
"description": "PHP Library for decoding and encoding BitTorrent BEncoded data, built for Gazelle",
|
||||
"license": "Unlicense",
|
||||
"authors": [
|
||||
{
|
||||
"name": "itismadness",
|
||||
"email": "[email protected]"
|
||||
"name": "Orpheus",
|
||||
"email": "[email protected]"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ApolloRIP\\BencodeTorrent\\": "src/"
|
||||
"OrpheusNET\\BencodeTorrent\\": "src/"
|
||||
}
|
||||
},
|
||||
"require": {},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6",
|
||||
"squizlabs/php_codesniffer": "3.*",
|
||||
"php": "^7",
|
||||
"ext-mbstring": "^7"
|
||||
"phpunit/phpunit": "^10.4.1",
|
||||
"squizlabs/php_codesniffer": "^3.7.2",
|
||||
"phpstan/phpstan": "^1.10.38"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit",
|
||||
"lint": "phpcs",
|
||||
"lint:fix": "phpcbf",
|
||||
"stan": "phpstan analyse"
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset name="Gazelle Standard">
|
||||
<description>The coding standard for the Gazelle project</description>
|
||||
<ruleset name="PSR12 Standard">
|
||||
<file>src</file>
|
||||
<file>tests</file>
|
||||
|
||||
<!-- Include the whole PSR-1 standard -->
|
||||
<rule ref="PSR1"/>
|
||||
<!-- Include the whole PSR-2 standard -->
|
||||
<rule ref="PSR2">
|
||||
<exclude name="PSR2.Classes.ClassDeclaration.OpenBraceNewLine"/>
|
||||
</rule>
|
||||
|
||||
<!-- Do not allow trailing whitespace at the end of lines -->
|
||||
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace" />
|
||||
<rule ref="Squiz.Functions.MultiLineFunctionDeclaration.BraceOnSameLine">
|
||||
<severity>0</severity>
|
||||
</rule>
|
||||
<rule ref="Squiz.ControlStructures.ControlSignature.SpaceAfterCloseBrace">
|
||||
<severity>0</severity>
|
||||
</rule>
|
||||
|
||||
<rule ref="Generic.Classes.OpeningBraceSameLine"/>
|
||||
<rule ref="Generic.Functions.OpeningFunctionBraceKernighanRitchie"/>
|
||||
|
||||
<!-- Ensure that proper line endings are used -->
|
||||
<rule ref="Generic.Files.LineEndings">
|
||||
<properties>
|
||||
<property name="eolChar" value="\n"/>
|
||||
</properties>
|
||||
</rule>
|
||||
|
||||
<!-- Increase PHPCS/PEAR limit of 85 chars per line to 100 -->
|
||||
<rule ref="Generic.Files.LineLength">
|
||||
<properties>
|
||||
<property name="lineLimit" value="100"/>
|
||||
<property name="absoluteLineLimit" value="0"/>
|
||||
</properties>
|
||||
</rule>
|
||||
</ruleset>
|
||||
<rule ref="PSR12" />
|
||||
</ruleset>
|
||||
@@ -0,0 +1,5 @@
|
||||
parameters:
|
||||
level: 5
|
||||
paths:
|
||||
- src
|
||||
- tests
|
||||
+20
-19
@@ -1,19 +1,20 @@
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.5/phpunit.xsd"
|
||||
colors="true"
|
||||
bootstrap="vendor/autoload.php">
|
||||
<testsuites>
|
||||
<testsuite name="unit">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||
<directory suffix=".php">src</directory>
|
||||
<exclude />
|
||||
</whitelist>
|
||||
</filter>
|
||||
<logging>
|
||||
<log type="coverage-html" target="report" />
|
||||
</logging>
|
||||
</phpunit>
|
||||
<?xml version="1.0"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.4/phpunit.xsd" colors="true" bootstrap="vendor/autoload.php" cacheDirectory=".phpunit.cache">
|
||||
<coverage>
|
||||
<report>
|
||||
<html outputDirectory="report"/>
|
||||
</report>
|
||||
</coverage>
|
||||
<testsuites>
|
||||
<testsuite name="unit">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<logging/>
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">src</directory>
|
||||
</include>
|
||||
<exclude/>
|
||||
</source>
|
||||
</phpunit>
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OrpheusNET\BencodeTorrent;
|
||||
|
||||
class Bencode
|
||||
{
|
||||
/** @var array */
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Sets the internal data array
|
||||
* @param mixed $data
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a BEncoded string and decode it
|
||||
* @param string $data
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function decodeString(string $data)
|
||||
{
|
||||
$this->data = $this->decode($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a path to a file, decode the contents of it
|
||||
*
|
||||
* @param string $path
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function decodeFile(string $path)
|
||||
{
|
||||
$this->data = $this->decode(file_get_contents($path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a BEncoded string to the following values:
|
||||
* - Dictionary (starts with d, ends with e)
|
||||
* - List (starts with l, ends with e
|
||||
* - Integer (starts with i, ends with e
|
||||
* - String (starts with number denoting number of characters followed by : and then the string)
|
||||
*
|
||||
* @see https://wiki.theory.org/index.php/BitTorrentSpecification
|
||||
*
|
||||
* @param string $data
|
||||
* @param int $pos
|
||||
* @return mixed
|
||||
*/
|
||||
protected function decode(string $data, int &$pos = 0)
|
||||
{
|
||||
$start_decode = $pos === 0;
|
||||
if ($data[$pos] === 'd') {
|
||||
$pos++;
|
||||
$return = [];
|
||||
while ($data[$pos] !== 'e') {
|
||||
$key = $this->decode($data, $pos);
|
||||
$value = $this->decode($data, $pos);
|
||||
if ($key === null || $value === null) {
|
||||
break;
|
||||
}
|
||||
if (!is_string($key)) {
|
||||
throw new \RuntimeException('Invalid key type, must be string: ' . gettype($key));
|
||||
}
|
||||
$return[$key] = $value;
|
||||
}
|
||||
ksort($return);
|
||||
$pos++;
|
||||
} elseif ($data[$pos] === 'l') {
|
||||
$pos++;
|
||||
$return = [];
|
||||
while ($data[$pos] !== 'e') {
|
||||
$value = $this->decode($data, $pos);
|
||||
$return[] = $value;
|
||||
}
|
||||
$pos++;
|
||||
} elseif ($data[$pos] === 'i') {
|
||||
$pos++;
|
||||
$digits = strpos($data, 'e', $pos) - $pos;
|
||||
$return = substr($data, $pos, $digits);
|
||||
if ($return === '-0') {
|
||||
throw new \RuntimeException('Cannot have integer value -0');
|
||||
}
|
||||
$multiplier = 1;
|
||||
if ($return[0] === '-') {
|
||||
$multiplier = -1;
|
||||
$return = substr($return, 1);
|
||||
}
|
||||
if (!ctype_digit($return)) {
|
||||
$msg = 'Cannot have non-digit values in integer number: ' . $return;
|
||||
throw new \RuntimeException($msg);
|
||||
}
|
||||
$return = $multiplier * ((int) $return);
|
||||
$pos += $digits + 1;
|
||||
} else {
|
||||
$digits = strpos($data, ':', $pos) - $pos;
|
||||
$len = (int) substr($data, $pos, $digits);
|
||||
$pos += ($digits + 1);
|
||||
$return = substr($data, $pos, $len);
|
||||
$pos += $len;
|
||||
}
|
||||
if ($start_decode) {
|
||||
if ($pos !== strlen($data)) {
|
||||
throw new \RuntimeException('Could not fully decode bencode string');
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internal data array
|
||||
* @return mixed
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function hasData()
|
||||
{
|
||||
if ($this->data === null) {
|
||||
throw new \RuntimeException('Must decode proper bencode string first');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEncode(): string
|
||||
{
|
||||
$this->hasData();
|
||||
return $this->encodeVal($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
* @return string
|
||||
*/
|
||||
protected function encodeVal($data): string
|
||||
{
|
||||
if (is_array($data)) {
|
||||
$return = '';
|
||||
$check = -1;
|
||||
$list = true;
|
||||
foreach ($data as $key => $value) {
|
||||
if ($key !== ++$check) {
|
||||
$list = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($list) {
|
||||
$return .= 'l';
|
||||
foreach ($data as $value) {
|
||||
$return .= $this->encodeVal($value);
|
||||
}
|
||||
} else {
|
||||
$return .= 'd';
|
||||
foreach ($data as $key => $value) {
|
||||
$return .= $this->encodeVal(strval($key));
|
||||
$return .= $this->encodeVal($value);
|
||||
}
|
||||
}
|
||||
$return .= 'e';
|
||||
} elseif (is_integer($data)) {
|
||||
$return = 'i' . $data . 'e';
|
||||
} else {
|
||||
$return = strlen($data) . ':' . $data;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
+104
-185
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace ApolloRip\BencodeTorrent;
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OrpheusNET\BencodeTorrent;
|
||||
|
||||
/**
|
||||
* BEncode service that allows us to encode PHP objects into BEncode and decode
|
||||
@@ -24,13 +26,14 @@ namespace ApolloRip\BencodeTorrent;
|
||||
* For Gazelle, this also acts as a unification of the two original BEncode implementations
|
||||
* which were both used in separate areas of the codebase.
|
||||
*/
|
||||
class BencodeTorrent {
|
||||
const FILELIST_DELIM = 0xF7;
|
||||
class BencodeTorrent extends Bencode
|
||||
{
|
||||
public const FILELIST_DELIM = 0xF7;
|
||||
/** @var string */
|
||||
private static $utf8_filelist_delim = null;
|
||||
|
||||
private $data;
|
||||
|
||||
public function __construct() {
|
||||
public function __construct()
|
||||
{
|
||||
$this->setDelim();
|
||||
}
|
||||
|
||||
@@ -39,9 +42,10 @@ class BencodeTorrent {
|
||||
* and char to set a class constant or variable, so we wait till the class is initialized
|
||||
* for the first time to set it.
|
||||
*/
|
||||
private function setDelim() {
|
||||
private function setDelim(): void
|
||||
{
|
||||
if (self::$utf8_filelist_delim === null) {
|
||||
self::$utf8_filelist_delim = utf8_encode(chr(self::FILELIST_DELIM));
|
||||
self::$utf8_filelist_delim = mb_convert_encoding(chr(self::FILELIST_DELIM), 'UTF-8', 'ISO-8859-1');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +54,9 @@ class BencodeTorrent {
|
||||
* @param array $data
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function setData(array $data) {
|
||||
$this->data = $data;
|
||||
public function setData($data): void
|
||||
{
|
||||
parent::setData($data);
|
||||
$this->validate();
|
||||
}
|
||||
|
||||
@@ -60,8 +65,9 @@ class BencodeTorrent {
|
||||
* @param string $data
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function decodeString(string $data) {
|
||||
$this->data = $this->decode($data);
|
||||
public function decodeString(string $data): void
|
||||
{
|
||||
parent::decodeString($data);
|
||||
$this->validate();
|
||||
}
|
||||
|
||||
@@ -71,77 +77,21 @@ class BencodeTorrent {
|
||||
* @param string $path
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function decodeFile(string $path) {
|
||||
$this->data = $this->decode(file_get_contents($path, FILE_BINARY));
|
||||
public function decodeFile(string $path): void
|
||||
{
|
||||
parent::decodeFile($path);
|
||||
$this->validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a BEncoded string to the following values:
|
||||
* - Dictionary (starts with d, ends with e)
|
||||
* - List (starts with l, ends with e
|
||||
* - Integer (starts with i, ends with e
|
||||
* - String (starts with number denoting number of characters followed by : and then the string)
|
||||
*
|
||||
* @see https://wiki.theory.org/index.php/BitTorrentSpecification
|
||||
*
|
||||
* @param string $data
|
||||
* @param int $pos
|
||||
* @return array|bool|float|string
|
||||
*/
|
||||
private function decode(string $data, int &$pos = 0) {
|
||||
if ($data[$pos] === 'd') {
|
||||
$pos++;
|
||||
$return = [];
|
||||
while ($data[$pos] !== 'e') {
|
||||
$key = $this->decode($data, $pos);
|
||||
$value = $this->decode($data, $pos);
|
||||
if (empty($key) || empty($value)) {
|
||||
break;
|
||||
}
|
||||
$return[$key] = $value;
|
||||
}
|
||||
ksort($return);
|
||||
$pos++;
|
||||
}
|
||||
elseif ($data[$pos] === 'l') {
|
||||
$pos++;
|
||||
$return = [];
|
||||
while ($data[$pos] !== 'e') {
|
||||
$value = $this->decode($data, $pos);
|
||||
$return[] = $value;
|
||||
}
|
||||
$pos++;
|
||||
}
|
||||
elseif ($data[$pos] === 'i') {
|
||||
$pos++;
|
||||
$digits = strpos($data, 'e', $pos) - $pos;
|
||||
$return = (int) substr($data, $pos, $digits);
|
||||
$pos += $digits + 1;
|
||||
}
|
||||
else {
|
||||
$digits = strpos($data, ':', $pos) - $pos;
|
||||
$len = (int) substr($data, $pos, $digits);
|
||||
$pos += ($digits + 1);
|
||||
$return = substr($data, $pos, $len);
|
||||
$pos += $len;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internal data array
|
||||
* @return array
|
||||
*/
|
||||
public function getData() : array {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the internal data array
|
||||
* @throws \RuntimeException
|
||||
* @throws \TypeError|\RuntimeException
|
||||
*/
|
||||
public function validate() {
|
||||
public function validate(): void
|
||||
{
|
||||
if (!is_array($this->data)) {
|
||||
throw new \TypeError('Data must be an array');
|
||||
}
|
||||
if (empty($this->data['info'])) {
|
||||
throw new \RuntimeException("Torrent dictionary doesn't have info key");
|
||||
}
|
||||
@@ -155,71 +105,14 @@ class BencodeTorrent {
|
||||
return strlen($element) === 0;
|
||||
}
|
||||
);
|
||||
if (count($filter) > 0) {
|
||||
throw new \RuntimeException('Cannot have empty path for a file');
|
||||
if (count($filter) === count($file[$path_key]) && $file['length'] !== 0) {
|
||||
throw new \RuntimeException('Cannot have a file with no path');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function hasData() {
|
||||
if (empty($this->data) || !is_array($this->data)) {
|
||||
throw new \RuntimeException('Must decode proper bencode string first');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEncode() : string {
|
||||
$this->hasData();
|
||||
return $this->encodeVal($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
* @return string
|
||||
*/
|
||||
private function encodeVal($data) : string {
|
||||
if (is_array($data)) {
|
||||
$return = '';
|
||||
$check = -1;
|
||||
$list = true;
|
||||
foreach ($data as $key => $value) {
|
||||
if ($key !== ++$check) {
|
||||
$list = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($list) {
|
||||
$return .= 'l';
|
||||
foreach ($data as $value) {
|
||||
$return .= $this->encodeVal($value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$return .= 'd';
|
||||
foreach ($data as $key => $value) {
|
||||
$return .= $this->encodeVal(strval($key));
|
||||
$return .= $this->encodeVal($value);
|
||||
}
|
||||
}
|
||||
$return .= 'e';
|
||||
}
|
||||
elseif (is_integer($data)) {
|
||||
$return = 'i'.$data.'e';
|
||||
}
|
||||
else {
|
||||
$return = strlen($data) . ':' . $data;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to clean out keys in the data and info dictionaries that we don't need in
|
||||
* our torrent file when we go to store it in the DB or serve it up to the user (with the
|
||||
@@ -228,7 +121,8 @@ class BencodeTorrent {
|
||||
*
|
||||
* @return bool flag to indicate if we altered the info dictionary
|
||||
*/
|
||||
public function clean() : bool {
|
||||
public function clean(): bool
|
||||
{
|
||||
$this->cleanDataDictionary();
|
||||
return $this->cleanInfoDictionary();
|
||||
}
|
||||
@@ -238,7 +132,8 @@ class BencodeTorrent {
|
||||
* overwritten dynamically on any downloaded torrent (like announce or comment), so that we
|
||||
* store the smallest encoded string within the database and cuts down on potential waste.
|
||||
*/
|
||||
public function cleanDataDictionary() {
|
||||
public function cleanDataDictionary(): void
|
||||
{
|
||||
$allowed_keys = array('encoding', 'info');
|
||||
foreach ($this->data as $key => $value) {
|
||||
if (!in_array($key, $allowed_keys)) {
|
||||
@@ -261,7 +156,8 @@ class BencodeTorrent {
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function cleanInfoDictionary() : bool {
|
||||
public function cleanInfoDictionary(): bool
|
||||
{
|
||||
$cleaned = false;
|
||||
$allowed_keys = array('files', 'name', 'piece length', 'pieces', 'private', 'length',
|
||||
'name.utf8', 'name.utf-8', 'md5sum', 'sha1', 'source',
|
||||
@@ -281,7 +177,8 @@ class BencodeTorrent {
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPrivate() : bool {
|
||||
public function isPrivate(): bool
|
||||
{
|
||||
$this->hasData();
|
||||
return isset($this->data['info']['private']) && $this->data['info']['private'] === 1;
|
||||
}
|
||||
@@ -295,7 +192,8 @@ class BencodeTorrent {
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function makePrivate() : bool {
|
||||
public function makePrivate(): bool
|
||||
{
|
||||
$this->hasData();
|
||||
if ($this->isPrivate()) {
|
||||
return false;
|
||||
@@ -311,12 +209,9 @@ class BencodeTorrent {
|
||||
* 'official' flag (no accepted BEP on it), but it has become the defacto standard with more
|
||||
* clients supporting it natively. Returns a boolean on whether or not the source was changed
|
||||
* so that an appropriate screen can be shown to the user.
|
||||
*
|
||||
* @param string $source
|
||||
*
|
||||
* @return bool true if the source was set/changed, false if no change
|
||||
*/
|
||||
public function setSource(string $source) : bool {
|
||||
public function setSource(string $source): bool
|
||||
{
|
||||
$this->hasData();
|
||||
if (isset($this->data['info']['source']) && $this->data['info']['source'] === $source) {
|
||||
return false;
|
||||
@@ -329,15 +224,32 @@ class BencodeTorrent {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source flag if one has been set
|
||||
*/
|
||||
public function getSource(): ?string
|
||||
{
|
||||
$this->hasData();
|
||||
return $this->data['info']['source'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the creation date for torrent if one has been set
|
||||
*/
|
||||
public function getCreationDate(): ?int
|
||||
{
|
||||
$this->hasData();
|
||||
return $this->data['creation date'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to allow you set any number of keys and values in the data dictionary. You can
|
||||
* set the value in a dictionary by concatenating the keys into a string with a period
|
||||
* separator (ex: info.name will set name field in the info dictionary) so that the rest
|
||||
* of the dictionary is unaffected.
|
||||
*
|
||||
* @param array $array
|
||||
*/
|
||||
public function setValue(array $array) {
|
||||
public function setValue(array $array): void
|
||||
{
|
||||
foreach ($array as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
ksort($value);
|
||||
@@ -370,19 +282,20 @@ class BencodeTorrent {
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getInfoHash() : string {
|
||||
public function getInfoHash(): string
|
||||
{
|
||||
$this->hasData();
|
||||
return sha1($this->encodeVal($this->data['info']));
|
||||
}
|
||||
|
||||
public function getHexInfoHash(): string {
|
||||
public function getHexInfoHash(): string
|
||||
{
|
||||
return pack('H*', $this->getInfoHash());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
public function getName(): string
|
||||
{
|
||||
$this->hasData();
|
||||
if (isset($this->data['info']['name.utf-8'])) {
|
||||
return $this->data['info']['name.utf-8'];
|
||||
}
|
||||
@@ -393,15 +306,14 @@ class BencodeTorrent {
|
||||
* Get the total size in bytes of the files in the torrent. For a single file torrent, it'll
|
||||
* just be the 'length' key in the 'info' dictionary, else we iterate through the 'files' list
|
||||
* and add up the 'length' of each element.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSize() : int {
|
||||
public function getSize(): int
|
||||
{
|
||||
$this->hasData();
|
||||
$cur_size = 0;
|
||||
if (!isset($this->data['info']['files'])) {
|
||||
$cur_size = $this->data['info']['length'];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
foreach ($this->data['info']['files'] as $file) {
|
||||
$cur_size += $file['length'];
|
||||
}
|
||||
@@ -418,7 +330,8 @@ class BencodeTorrent {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFileList() : array {
|
||||
public function getFileList(): array
|
||||
{
|
||||
$files = [];
|
||||
if (!isset($this->data['info']['files'])) {
|
||||
// Single-file torrent
|
||||
@@ -426,27 +339,32 @@ class BencodeTorrent {
|
||||
$this->data['info']['name.utf-8'] :
|
||||
$this->data['info']['name']);
|
||||
$size = $this->data['info']['length'];
|
||||
$files[] = array('name' => $name, 'size' => $size);
|
||||
}
|
||||
else {
|
||||
$path_key = isset($this->data['info']['files'][0]['path.utf-8']) ?
|
||||
'path.utf-8' :
|
||||
'path';
|
||||
$files[] = ['path' => $name, 'size' => $size];
|
||||
} else {
|
||||
$size = 0;
|
||||
foreach ($this->data['info']['files'] as $file) {
|
||||
$tmp_path = array();
|
||||
foreach ($file[$path_key] as $sub_path) {
|
||||
$tmp_path[] = $sub_path;
|
||||
}
|
||||
$files[] = array('name' => implode('/', $tmp_path), 'size' => $file['length']);
|
||||
$size += $file['length'];
|
||||
$path_key = isset($file['path.utf-8']) ? 'path.utf-8' : 'path';
|
||||
$files[] = ['path' => implode('/', $file[$path_key]), 'size' => $file['length']];
|
||||
}
|
||||
uasort(
|
||||
usort(
|
||||
$files,
|
||||
function ($a, $b) {
|
||||
return strnatcasecmp($a['name'], $b['name']);
|
||||
return strnatcasecmp($a['path'], $b['path']);
|
||||
}
|
||||
);
|
||||
}
|
||||
return $files;
|
||||
return array('total_size' => $size, 'files' => $files);
|
||||
}
|
||||
|
||||
public function hasFiles(): bool
|
||||
{
|
||||
return isset($this->data['info']['files']);
|
||||
}
|
||||
|
||||
public function hasEncryptedFiles(): bool
|
||||
{
|
||||
return isset($this->data['encrypted_files']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -459,17 +377,18 @@ class BencodeTorrent {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getGazelleFileList() : array {
|
||||
public function getGazelleFileList(): array
|
||||
{
|
||||
$files = [];
|
||||
foreach ($this->getFileList() as $file) {
|
||||
$name = $file['name'];
|
||||
foreach ($this->getFileList()['files'] as $file) {
|
||||
$path = $file['path'];
|
||||
$size = $file['size'];
|
||||
$name = $this->makeUTF8(strtr($name, "\n\r\t", ' '));
|
||||
$ext_pos = strrpos($name, '.');
|
||||
$path = $this->makeUTF8(strtr($path, "\n\r\t", ' '));
|
||||
$ext_pos = strrpos($path, '.');
|
||||
// Should not be $ExtPos !== false. Extension-less files that start with a .
|
||||
// should not get extensions
|
||||
$ext = ($ext_pos ? trim(substr($name, $ext_pos + 1)) : '');
|
||||
$files[] = sprintf("%s s%ds %s %s", ".$ext", $size, $name, self::$utf8_filelist_delim);
|
||||
$ext = ($ext_pos ? trim(substr($path, $ext_pos + 1)) : '');
|
||||
$files[] = sprintf("%s s%ds %s %s", ".$ext", $size, $path, self::$utf8_filelist_delim);
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
@@ -481,7 +400,8 @@ class BencodeTorrent {
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function makeUTF8(string $str) : string {
|
||||
private function makeUTF8(string $str): string
|
||||
{
|
||||
if (preg_match('//u', $str)) {
|
||||
$encoding = 'UTF-8';
|
||||
}
|
||||
@@ -496,8 +416,7 @@ class BencodeTorrent {
|
||||
// @codeCoverageIgnoreEnd
|
||||
if ($encoding === 'UTF-8') {
|
||||
return $str;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return @mb_convert_encoding($str, 'UTF-8', $encoding);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OrpheusNET\BencodeTorrent;
|
||||
|
||||
class BencodeTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public static function dataProvider(): array
|
||||
{
|
||||
return [
|
||||
['i0e', 0],
|
||||
['i-1e', -1],
|
||||
['i20e', 20],
|
||||
['le', []],
|
||||
['d4:item5:valuee', ['item' => 'value']],
|
||||
['0:', ''],
|
||||
['1:a', 'a'],
|
||||
['9:123456789', '123456789']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProvider
|
||||
* @param mixed $expected
|
||||
*/
|
||||
public function testDecodeEncode(string $bencoded_string, $expected): void
|
||||
{
|
||||
$bencode = new Bencode();
|
||||
$bencode->decodeString($bencoded_string);
|
||||
$this->assertEquals($expected, $bencode->getData());
|
||||
$this->assertEquals($bencoded_string, $bencode->getEncode());
|
||||
}
|
||||
|
||||
public function testEmptyDict(): void
|
||||
{
|
||||
$bencode = new Bencode();
|
||||
$bencode->decodeString('de');
|
||||
$this->assertEquals([], $bencode->getData());
|
||||
$this->assertEquals('le', $bencode->getEncode());
|
||||
}
|
||||
|
||||
public function testInvalidDictionaryKey(): void
|
||||
{
|
||||
$bencode = new Bencode();
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$bencode->decodeString('di1e5:valuee');
|
||||
}
|
||||
|
||||
public static function invalidIntegers(): array
|
||||
{
|
||||
return [['-0'], ['a'], ['1.0']];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @dataProvider invalidIntegers
|
||||
*/
|
||||
public function testInvalidInteger(string $value): void
|
||||
{
|
||||
$bencode = new Bencode();
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$bencode->decodeString("i{$value}e");
|
||||
}
|
||||
|
||||
public function testInvalidString(): void
|
||||
{
|
||||
$bencode = new Bencode();
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$bencode->decodeString('i1e0e');
|
||||
}
|
||||
}
|
||||
+174
-43
@@ -1,16 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace ApolloRip\BencodeTorrent;
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
namespace OrpheusNET\BencodeTorrent;
|
||||
|
||||
class BencodeTorrentTest extends TestCase {
|
||||
public function testLoadTorrent() {
|
||||
class BencodeTorrentTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testLoadTorrent(): void
|
||||
{
|
||||
$bencode = new BencodeTorrent();
|
||||
try {
|
||||
$bencode->decodeFile(__DIR__.'/data/test_1.torrent');
|
||||
}
|
||||
catch (\Exception $exc) {
|
||||
$bencode->decodeFile(__DIR__ . '/data/test_1.torrent');
|
||||
} catch (\Exception $exc) {
|
||||
$this->fail('Decode should not have thrown exception');
|
||||
}
|
||||
$data = $bencode->getData();
|
||||
@@ -85,16 +86,69 @@ class BencodeTorrentTest extends TestCase {
|
||||
$this->assertEquals(16020, strlen($data['info']['pieces']));
|
||||
$this->assertEquals(1, $data['info']['private']);
|
||||
$this->assertEquals('APL', $data['info']['source']);
|
||||
$this->assertStringEqualsFile(__DIR__.'/data/test_1.torrent', $bencode->getEncode());
|
||||
$this->assertStringEqualsFile(__DIR__ . '/data/test_1.torrent', $bencode->getEncode());
|
||||
$this->assertEquals(104916260, $bencode->getSize());
|
||||
$this->assertTrue($bencode->isPrivate());
|
||||
$bencode->decodeString($bencode->getEncode());
|
||||
$this->assertStringEqualsFile(__DIR__.'/data/test_1.torrent', $bencode->getEncode());
|
||||
$this->assertStringEqualsFile(__DIR__ . '/data/test_1.torrent', $bencode->getEncode());
|
||||
$this->assertEquals('1f830103427029a88dd5fde85be74e622ee07951', $bencode->getInfoHash());
|
||||
$this->assertEquals($bencode->getInfoHash(), unpack('H*', $bencode->getHexInfoHash())[1]);
|
||||
$this->assertFalse($bencode->hasEncryptedFiles());
|
||||
$file_list = [
|
||||
'total_size' => 104916260,
|
||||
'files' => [
|
||||
[
|
||||
'size' => 9584196,
|
||||
'path' => '01 Death with Dignity.mp3'
|
||||
],
|
||||
[
|
||||
'size' => 12310347,
|
||||
'path' => '02 Should have known better.mp3'
|
||||
],
|
||||
[
|
||||
'size' => 8871591,
|
||||
'path' => '03 All of me wants all of you.mp3'
|
||||
],
|
||||
[
|
||||
'size' => 7942661,
|
||||
'path' => '04 Drawn to the Blood.mp3'
|
||||
],
|
||||
[
|
||||
'size' => 5878964,
|
||||
'path' => '05 Eugene.mp3'
|
||||
],
|
||||
[
|
||||
'size' => 11175567,
|
||||
'path' => '06 Fourth of July.mp3'
|
||||
],
|
||||
[
|
||||
'size' => 11367829,
|
||||
'path' => '07 The Only Thing.mp3'
|
||||
],
|
||||
[
|
||||
'size' => 7789055,
|
||||
'path' => '08 Carrie & Lowell.mp3'
|
||||
],
|
||||
[
|
||||
'size' => 12197480,
|
||||
'path' => '09 John My Beloved.mp3'
|
||||
],
|
||||
[
|
||||
'size' => 6438044,
|
||||
'path' => '10 No shade in the shadow of the cross.mp3'
|
||||
],
|
||||
[
|
||||
'size' => 11360526,
|
||||
'path' => '11 Blue Bucket of Gold.mp3'
|
||||
]
|
||||
]
|
||||
];
|
||||
$this->assertEquals($file_list, $bencode->getFileList());
|
||||
$this->assertSame(1425699508, $bencode->getCreationDate());
|
||||
}
|
||||
|
||||
public function testSetData() {
|
||||
public function testSetData(): void
|
||||
{
|
||||
$data = [
|
||||
'encoding' => 'UTF8',
|
||||
'info' => [
|
||||
@@ -112,27 +166,41 @@ class BencodeTorrentTest extends TestCase {
|
||||
$this->assertEquals('test', $bencode->getName());
|
||||
$this->assertFalse($bencode->isPrivate());
|
||||
$this->assertEquals(1213134, $bencode->getSize());
|
||||
$this->assertEquals([['name' => 'test', 'size' => 1213134]], $bencode->getFileList());
|
||||
$this->assertEquals(
|
||||
[
|
||||
'total_size' => 1213134,
|
||||
'files' => [
|
||||
[
|
||||
'path' => 'test',
|
||||
'size' => 1213134
|
||||
]
|
||||
]
|
||||
],
|
||||
$bencode->getFileList()
|
||||
);
|
||||
}
|
||||
|
||||
public function testEmptyDictionary() {
|
||||
public function testEmptyDictionary(): void
|
||||
{
|
||||
$expected = [
|
||||
'encoding' => 'UTF8',
|
||||
'info' => [
|
||||
'name' => 'test',
|
||||
'length' => 1213134,
|
||||
'pieces' => 'fake pieces string'
|
||||
]
|
||||
'pieces' => 'fake pieces string',
|
||||
],
|
||||
'test' => ''
|
||||
];
|
||||
$bencode = new BencodeTorrent();
|
||||
$bencode->decodeString(
|
||||
'd8:encoding4:UTF84:infod4:name4:test6:lengthi1213134e'.
|
||||
'd8:encoding4:UTF84:infod4:name4:test6:lengthi1213134e' .
|
||||
'6:pieces18:fake pieces stringe4:test0:e'
|
||||
);
|
||||
$this->assertEquals($expected, $bencode->getData());
|
||||
}
|
||||
|
||||
public function testClean() {
|
||||
public function testClean(): void
|
||||
{
|
||||
$data = [
|
||||
'encoding' => 'UTF8',
|
||||
'info' => [
|
||||
@@ -165,7 +233,8 @@ class BencodeTorrentTest extends TestCase {
|
||||
$this->assertEquals($data, $bencode->getData());
|
||||
}
|
||||
|
||||
public function testSetPrivate() {
|
||||
public function testSetPrivate(): void
|
||||
{
|
||||
$data = [
|
||||
'encoding' => 'UTF8',
|
||||
'info' => [
|
||||
@@ -185,7 +254,8 @@ class BencodeTorrentTest extends TestCase {
|
||||
$this->assertEquals(1, $actual['info']['private']);
|
||||
}
|
||||
|
||||
public function testSetAlreadyPrivate() {
|
||||
public function testSetAlreadyPrivate(): void
|
||||
{
|
||||
$data = [
|
||||
'encoding' => 'UTF8',
|
||||
'info' => [
|
||||
@@ -199,7 +269,8 @@ class BencodeTorrentTest extends TestCase {
|
||||
$this->assertFalse($bencode->makePrivate());
|
||||
}
|
||||
|
||||
public function testSetSource() {
|
||||
public function testSetSource(): void
|
||||
{
|
||||
$data = [
|
||||
'encoding' => 'UTF8',
|
||||
'info' => [
|
||||
@@ -211,14 +282,17 @@ class BencodeTorrentTest extends TestCase {
|
||||
];
|
||||
$bencode = new BencodeTorrent();
|
||||
$bencode->setData($data);
|
||||
$this->assertNull($bencode->getSource());
|
||||
$this->assertTrue($bencode->setSource('APL'));
|
||||
$actual = $bencode->getData();
|
||||
$this->assertEquals('APL', $actual['info']['source']);
|
||||
$this->assertSame('APL', $bencode->getSource());
|
||||
$this->assertFalse($bencode->setSource('APL'));
|
||||
$this->assertNotContains('unique', $actual['info']);
|
||||
$this->assertNotContains('x_cross_seed', $actual['info']);
|
||||
}
|
||||
|
||||
public function testSetAlreadySourced() {
|
||||
public function testSetAlreadySourced(): void
|
||||
{
|
||||
$data = [
|
||||
'encoding' => 'UTF8',
|
||||
'info' => [
|
||||
@@ -236,7 +310,8 @@ class BencodeTorrentTest extends TestCase {
|
||||
$this->assertEquals($data, $bencode->getData());
|
||||
}
|
||||
|
||||
public function testSetDifferentSource() {
|
||||
public function testSetDifferentSource(): void
|
||||
{
|
||||
$data = [
|
||||
'encoding' => 'UTF8',
|
||||
'info' => [
|
||||
@@ -256,7 +331,8 @@ class BencodeTorrentTest extends TestCase {
|
||||
$this->assertNotContains('x_cross_seed', $actual['info']);
|
||||
}
|
||||
|
||||
public function testSetValue() {
|
||||
public function testSetValue(): void
|
||||
{
|
||||
$data = [
|
||||
'encoding' => 'UTF8',
|
||||
'info' => [
|
||||
@@ -310,9 +386,11 @@ class BencodeTorrentTest extends TestCase {
|
||||
['.mp3 s1234s 01 Test??.mp3 ÷', '.mp3 s2345s 03 Test.mp3 ÷'],
|
||||
$bencode->getGazelleFileList()
|
||||
);
|
||||
$this->assertTrue($bencode->hasFiles());
|
||||
}
|
||||
|
||||
public function testGetUtf8Name() {
|
||||
public function testGetUtf8Name(): void
|
||||
{
|
||||
$data = [
|
||||
'encoding' => 'UTF8',
|
||||
'info' => [
|
||||
@@ -326,11 +404,23 @@ class BencodeTorrentTest extends TestCase {
|
||||
$bencode->setData($data);
|
||||
$this->assertEquals('test!!', $bencode->getName());
|
||||
$this->assertEquals(12345, $bencode->getSize());
|
||||
$this->assertEquals([['name' => 'test!!', 'size' => 12345]], $bencode->getFileList());
|
||||
$this->assertEquals(
|
||||
[
|
||||
'total_size' => 12345,
|
||||
'files' => [
|
||||
[
|
||||
'path' => 'test!!',
|
||||
'size' => 12345
|
||||
]
|
||||
]
|
||||
],
|
||||
$bencode->getFileList()
|
||||
);
|
||||
$this->assertEquals(['. s12345s test!! ÷'], $bencode->getGazelleFileList());
|
||||
}
|
||||
|
||||
public function testIso88591Name() {
|
||||
public function testIso88591Name(): void
|
||||
{
|
||||
$data = [
|
||||
'info' => [
|
||||
'length' => 1234,
|
||||
@@ -340,21 +430,19 @@ class BencodeTorrentTest extends TestCase {
|
||||
$bencode = new BencodeTorrent();
|
||||
$bencode->setData($data);
|
||||
$this->assertEquals(['.,!?)óÉ s1234s (test.,!?)óÉ ÷'], $bencode->getGazelleFileList());
|
||||
$this->assertFalse($bencode->hasFiles());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
public function testInvalidSet() {
|
||||
public function testInvalidSet(): void
|
||||
{
|
||||
$data = ['encoding' => 'UTF8', 'announce' => 'http://localhost:8080/announce'];
|
||||
$bencode = new BencodeTorrent();
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$bencode->setData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
public function testInvalidSetValue() {
|
||||
public function testInvalidSetValue(): void
|
||||
{
|
||||
$data = [
|
||||
'encoding' => 'UTF8',
|
||||
'info' => [
|
||||
@@ -366,28 +454,29 @@ class BencodeTorrentTest extends TestCase {
|
||||
|
||||
$bencode = new BencodeTorrent();
|
||||
$bencode->setData($data);
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$bencode->setValue(['info' => []]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
public function testGetEncodeNoData() {
|
||||
public function testGetEncodeNoData(): void
|
||||
{
|
||||
$bencode = new BencodeTorrent();
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$bencode->getEncode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
public function testInvalidPath() {
|
||||
public function testEmptyPathPart(): void
|
||||
{
|
||||
$data = [
|
||||
'encoding' => 'UTF8',
|
||||
'info' => [
|
||||
'files' => [
|
||||
0 => [
|
||||
'length' => 1234,
|
||||
'path' => ['']
|
||||
'path' => [
|
||||
'',
|
||||
'01 - test test',
|
||||
]
|
||||
]
|
||||
],
|
||||
'name' => 'test',
|
||||
@@ -397,5 +486,47 @@ class BencodeTorrentTest extends TestCase {
|
||||
];
|
||||
$bencode = new BencodeTorrent();
|
||||
$bencode->setData($data);
|
||||
$this->assertSame(
|
||||
[
|
||||
'total_size' => 1234,
|
||||
'files' => [
|
||||
0 => [
|
||||
'path' => '/01 - test test',
|
||||
'size' => 1234,
|
||||
],
|
||||
],
|
||||
],
|
||||
$bencode->getFileList()
|
||||
);
|
||||
}
|
||||
|
||||
public function testEmptyPath(): void
|
||||
{
|
||||
$data = [
|
||||
'encoding' => 'UTF8',
|
||||
'info' => [
|
||||
'files' => [
|
||||
0 => [
|
||||
'length' => 1234,
|
||||
'path' => [
|
||||
'',
|
||||
]
|
||||
]
|
||||
],
|
||||
'name' => 'test',
|
||||
'length' => 1213134,
|
||||
'pieces' => 'fake pieces string'
|
||||
]
|
||||
];
|
||||
$bencode = new BencodeTorrent();
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$bencode->setData($data);
|
||||
}
|
||||
|
||||
public function testValidateEmptyData(): void
|
||||
{
|
||||
$bencode = new BencodeTorrent();
|
||||
$this->expectException(\TypeError::class);
|
||||
$bencode->validate();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user