|
@@ -1,360 +0,0 @@
|
|
|
-<?php
|
|
|
-/*
|
|
|
- Bencoding OOP wrapper for PHP | by Proger_XP | In public domain
|
|
|
- http://proger.i-forge.net/BEncoded/7Tn
|
|
|
-
|
|
|
- Based on lightenc.php functions from
|
|
|
- http://wiki.theory.org/Decoding_encoding_bencoded_data_with_PHP
|
|
|
-*/
|
|
|
-
|
|
|
-/* BEncoded classes by Proger_XP */
|
|
|
-class BEncoded {
|
|
|
- public $nodes;
|
|
|
- public $tempChar;
|
|
|
-
|
|
|
- static function Decode($str) {
|
|
|
- $res = bdecode($str);
|
|
|
- if ($res === null) {
|
|
|
- throw new EBEncode(null, 'Cannot decode bencoded data.', 'string: '.$str);
|
|
|
- } else {
|
|
|
- return $res;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- static function Encode($data, $tempChar = null) {
|
|
|
- $res = bencode($data, $tempChar);
|
|
|
- if ($res === null) {
|
|
|
- throw new EBEncode(null, 'Cannot encode bencoded data of type '.gettype($data).'.');
|
|
|
- }
|
|
|
- return $res;
|
|
|
- }
|
|
|
-
|
|
|
- static function TypeOf($value) {
|
|
|
- if (is_scalar($value) or $value === null) {
|
|
|
- return ((is_int($value) or is_float($value)) ? 'int' : 'str');
|
|
|
- } else {
|
|
|
- return empty($value['isDct']) ? 'list' : 'dict';
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- static function HashOf($nodes, $raw = false) {
|
|
|
- return strtoupper(sha1(self::Encode($nodes), $raw));
|
|
|
- }
|
|
|
-
|
|
|
- /* Instance methods */
|
|
|
-
|
|
|
- function __construct($str = null) {
|
|
|
- if ($str !== null) {
|
|
|
- $this->FromString($str);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- function FromString($str) {
|
|
|
- $nodes = self::Decode($str);
|
|
|
-
|
|
|
- if (!is_array($nodes)) {
|
|
|
- throw new EBEncode($this, 'Cannot load bencoded string - it decodes to'.
|
|
|
- ' a non-array ('.gettype($nodes).').');
|
|
|
- }
|
|
|
-
|
|
|
- $this->nodes = &$nodes;
|
|
|
- }
|
|
|
-
|
|
|
- function FromFile($file) {
|
|
|
- $str = file_get_contents($file);
|
|
|
- if (!is_string($str)) {
|
|
|
- throw new EBEncode($this, 'File to load bencoded file from doesn\'t exist.', 'file: '.$file);
|
|
|
- }
|
|
|
- $this->FromString($str);
|
|
|
- }
|
|
|
-
|
|
|
- function ToString() {
|
|
|
- return self::Encode($this->nodes, $this->tempChar);
|
|
|
- }
|
|
|
-
|
|
|
- function ToFile($file) {
|
|
|
- $bytes = file_put_contents($file, $this->ToString, LOCK_EX);
|
|
|
- if (!is_int($bytes)) {
|
|
|
- throw new EBEncode($this, 'Cannot save bencoded file.', 'dest file: '.$file);
|
|
|
- }
|
|
|
- return $bytes;
|
|
|
- }
|
|
|
-
|
|
|
- // returns a shallow copy of root; to operate directly $this->nodes can be used.
|
|
|
- function Root() { return $this->nodes; }
|
|
|
-
|
|
|
- // returns null if node doesn't exist. $name = "/" or "" returns (sets/deletes) root.
|
|
|
- function ValueOf($name) { return $this->Alter($name); }
|
|
|
- // alias to ValueOf():
|
|
|
- function Get($name) { return $this->ValueOf($name); }
|
|
|
-
|
|
|
- function Set($name, $value) { return $this->Alter($name, 'set', $value); }
|
|
|
- function Copy($src, $dest) { return $this->Set($dest, $this->ValueOf($src)); }
|
|
|
-
|
|
|
- function Exchange($node_1, $node_2) {
|
|
|
- $temp = $this->ValueOf($node_2);
|
|
|
- $this->Set($node_2, $this->ValueOf($node_1));
|
|
|
- $this->Set($node_1, $temp);
|
|
|
- }
|
|
|
-
|
|
|
- function Delete($name) { return $this->Alter($name, 'delete'); }
|
|
|
-
|
|
|
- // $op: (g)et / (s)et, returns new value / (d)elete.
|
|
|
- protected function Alter($name, $op = 'get', $arg = null) {
|
|
|
- $lastSlash = strpbrk(mb_substr($name, -1), '\\/');
|
|
|
-
|
|
|
- $name = trim( strtr($name, '\\', '/'), '/' );
|
|
|
- $path = $name === '' ? array() : explode('/', $name);
|
|
|
-
|
|
|
- $parent = &$this->nodes;
|
|
|
- while ($path and is_array($parent)) {
|
|
|
- $value = &$parent[array_shift($path)];
|
|
|
-
|
|
|
- if ($op[0] === 'd') {
|
|
|
- if (!$path and $lastSlash == is_array($value)) {
|
|
|
- $value = null;
|
|
|
- }
|
|
|
- } elseif ($op[0] === 's') {
|
|
|
- if ($value === null and $path) {
|
|
|
- $value = array();
|
|
|
- if (( (string) $path[0] ) !== '0') {
|
|
|
- $value['isDct'] = true;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- $parent = &$value;
|
|
|
- }
|
|
|
-
|
|
|
- if ($op[0] === 's') {
|
|
|
- $parent = $arg;
|
|
|
- } elseif ($op[0] === 'd' and !$name) {
|
|
|
- $parent = array();
|
|
|
- }
|
|
|
-
|
|
|
- return $parent;
|
|
|
- }
|
|
|
-
|
|
|
- function Export($name = '') { return $this->Dump( $this->ValueOf($name) ); }
|
|
|
-
|
|
|
- function Dump($value, $prefix = '') {
|
|
|
- $type = self::TypeOf($value);
|
|
|
-
|
|
|
- if ($type === 'int') {
|
|
|
- return is_float($value) ? sprintf('%1.1f', $value) : $value;
|
|
|
- } elseif ($type === 'str') {
|
|
|
- return var_export($value, true);
|
|
|
- } else {
|
|
|
- $res = '';
|
|
|
-
|
|
|
- $isDict = $type === 'dict';
|
|
|
- foreach ($value as $key => &$item) {
|
|
|
- if (!bskip($key, $item, $this->tempChar)) {
|
|
|
- $res .= $prefix;
|
|
|
- $res .= $isDict ? "$key:" : "#$key";
|
|
|
- $res .= is_array($item) ? "\n" : ' ';
|
|
|
- $res .= $this->Dump($item, "$prefix ")."\n";
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return substr($res, 0, -1);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // type: int|str|list|dict; other type throws exception.
|
|
|
- function NewNode($type, $name) {
|
|
|
- switch ($type = strtolower($type)) {
|
|
|
- case 'int': return $this->Set($name, 0);
|
|
|
- case 'str': return $this->Set($name, '');
|
|
|
- case 'list': return $this->Set($name, array());
|
|
|
- case 'dict': return $this->Set($name, array('isDct' => true));
|
|
|
- default: throw new EBEncode($this, 'Cannot create bencoded node because type '.$type.' is unknown.');
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- function SetEmpty($name) {
|
|
|
- $value = $this->ValueOf($name);
|
|
|
-
|
|
|
- if (is_int($value) or is_float($value)) {
|
|
|
- $value = 0;
|
|
|
- } elseif (is_string($value) or $value === null) {
|
|
|
- $value = '';
|
|
|
- } elseif (empty($value['isDct'])) {
|
|
|
- $value = array();
|
|
|
- } else {
|
|
|
- $value = array('isDct' => true);
|
|
|
- }
|
|
|
-
|
|
|
- return $this->Set($name, $value);
|
|
|
- }
|
|
|
-
|
|
|
- function Cast($name, $asType, $onlyIfNum = false) {
|
|
|
- $value = $this->ValueOf($name);
|
|
|
- if ($value === null) {
|
|
|
- throw new EBEncode($this, 'Cannot cast node '.$name.' into '.$asType.' because node doesn\'t exist.');
|
|
|
- }
|
|
|
-
|
|
|
- $asType = strtolower($asType);
|
|
|
- if (!in_array($asType, array('int', 'str', 'list', 'dict'))) {
|
|
|
- throw new EBEncode($this, 'Cannot cast node "'.$name.'" because new type ('.$asType.') is invalid.');
|
|
|
- }
|
|
|
-
|
|
|
- $type = self::TypeOf($value);
|
|
|
- if ($type !== $asType) {
|
|
|
- if ($type === 'int' or $type === 'str') {
|
|
|
- switch ($asType) {
|
|
|
- // str -> int:
|
|
|
- case 'int':
|
|
|
- if (!is_numeric($value)) {
|
|
|
- if (!$onlyIfNum) {
|
|
|
- throw new EBEncode($this, 'Cannot cast string "'.$value.' to integer because it\'s not a number.');
|
|
|
- }
|
|
|
- } else {
|
|
|
- $value = (float) $value;
|
|
|
- }
|
|
|
-
|
|
|
- break;
|
|
|
-
|
|
|
- // int -> str:
|
|
|
- case 'str': $value = (string) $value; break;
|
|
|
- case 'list': $value = array(0 => $value); break;
|
|
|
- case 'dict': $value = array('isDct' => true, 0 => $value); break;
|
|
|
- }
|
|
|
- } elseif ($asType === 'int' or $asType === 'str') {
|
|
|
- throw new EBException($this, 'Casting list/dict node "'.$name.'" into int/str isn\'t allowed.');
|
|
|
- } elseif ($asType === 'dict') { // list -> dict
|
|
|
- $value['isDct'] = true;
|
|
|
- } else { // dict -> list
|
|
|
- unset($value['isDct']);
|
|
|
- }
|
|
|
-
|
|
|
- $this->Set($name, $value);
|
|
|
- }
|
|
|
-
|
|
|
- return $value;
|
|
|
- }
|
|
|
-
|
|
|
- function TempChar($new = null) {
|
|
|
- $new === null or $this->tempChar = $new === '' ? null : $new;
|
|
|
- return $this->tempChar;
|
|
|
- }
|
|
|
-
|
|
|
- function InfoHash($raw = false) {
|
|
|
- $info = &$this->nodes['info'];
|
|
|
- if (empty($info)) {
|
|
|
- throw new EBEncode($this, 'Cannot calculate info hash because there is no \'info\' dictionary.');
|
|
|
- } else {
|
|
|
- return self::HashOf($info, $raw);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
- class EBEncode extends Exception {
|
|
|
- public $obj;
|
|
|
-
|
|
|
- function __construct($bencObj, $msg, $details = '', $previous = null) {
|
|
|
- $this->obj = $bencObj;
|
|
|
- parent::__construct(rtrim($msg, '.').": $details", $previous);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-/* lightenc.php */
|
|
|
-function bdecode($s, &$pos=0) {
|
|
|
- if($pos>=strlen($s)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- switch($s[$pos]){
|
|
|
- case 'd':
|
|
|
- $pos++;
|
|
|
- $retval=array();
|
|
|
- while ($s[$pos]!='e'){
|
|
|
- $key=bdecode($s, $pos);
|
|
|
- $val=bdecode($s, $pos);
|
|
|
- if ($key===null || $val===null)
|
|
|
- break;
|
|
|
- $retval[$key]=$val;
|
|
|
- }
|
|
|
- $retval["isDct"]=true;
|
|
|
- $pos++;
|
|
|
- return $retval;
|
|
|
-
|
|
|
- case 'l':
|
|
|
- $pos++;
|
|
|
- $retval=array();
|
|
|
- while ($s[$pos]!='e'){
|
|
|
- $val=bdecode($s, $pos);
|
|
|
- if ($val===null)
|
|
|
- break;
|
|
|
- $retval[]=$val;
|
|
|
- }
|
|
|
- $pos++;
|
|
|
- return $retval;
|
|
|
-
|
|
|
- case 'i':
|
|
|
- $pos++;
|
|
|
- $digits=strpos($s, 'e', $pos)-$pos;
|
|
|
- // Proger_XP: changed (int) -> (float) to avoid trimming of values exceeding
|
|
|
- // signed int's max value (2147483647).
|
|
|
- $val=(float)substr($s, $pos, $digits);
|
|
|
- $pos+=$digits+1;
|
|
|
- return $val;
|
|
|
-
|
|
|
-// case "0": case "1": case "2": case "3": case "4":
|
|
|
-// case "5": case "6": case "7": case "8": case "9":
|
|
|
- default:
|
|
|
- $digits=strpos($s, ':', $pos)-$pos;
|
|
|
- if ($digits<0 || $digits >20)
|
|
|
- return null;
|
|
|
- $len=(float)substr($s, $pos, $digits);
|
|
|
- $pos+=$digits+1;
|
|
|
- $str=substr($s, $pos, $len);
|
|
|
- $pos+=$len;
|
|
|
- //echo "pos: $pos str: [$str] len: $len digits: $digits\n";
|
|
|
- return (string)$str;
|
|
|
- }
|
|
|
- return null;
|
|
|
-}
|
|
|
-
|
|
|
-// Proger_XP: added added skipping for null values and $tempChar prefix for list/dicts.
|
|
|
-function bencode(&$d, $tempChar = null){
|
|
|
- if(is_array($d)){
|
|
|
- $ret="l";
|
|
|
- $isDict=!empty($d["isDct"]);
|
|
|
- if($isDict){
|
|
|
- $ret="d";
|
|
|
- // this is required by the specs, and BitTornado actualy chokes on unsorted dictionaries
|
|
|
- ksort($d, SORT_STRING);
|
|
|
- }
|
|
|
- foreach($d as $key=>$value) {
|
|
|
- if($isDict){
|
|
|
- // skip the isDct element, only if it's set by us
|
|
|
- if (!bskip($key, $value, $tempChar)) {
|
|
|
- $ret .= strlen($key).":$key";
|
|
|
- }
|
|
|
- } elseif (!is_int($key) and !is_float($key) and trim($key, '0..9') !== '') {
|
|
|
- // Proger_XP: added exception raising for non-numeric list keys.
|
|
|
- throw new EBEncode(null, 'Cannot bencode() a list - it contains a non-numeric key "'.$key.'".');
|
|
|
- }
|
|
|
-
|
|
|
- if (is_string($value)) {
|
|
|
- $ret.=strlen($value).":".$value;
|
|
|
- } elseif (is_int($value) or is_float($value)){
|
|
|
- $ret.="i${value}e";
|
|
|
- } else {
|
|
|
- $ret.=bencode ($value);
|
|
|
- }
|
|
|
- }
|
|
|
- return $ret."e";
|
|
|
- } elseif (is_string($d)) { // fallback if we're given a single bencoded string or int
|
|
|
- return strlen($d).":".$d;
|
|
|
- } elseif (is_int($d) or is_float($d)) {
|
|
|
- return "i${d}e";
|
|
|
- } else {
|
|
|
- return null;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// bskip by Proger_XP.
|
|
|
-function bskip($key, &$value, $tempChar = null) {
|
|
|
- return ($key === 'isDct' and $value) or $value === null or strpos($key, $tempChar) === 0;
|
|
|
-}
|