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; }