PageRenderTime 99ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/contrib/php/bencode.inc.php

https://github.com/jonnyrules96/cjdns
PHP | 360 lines | 315 code | 24 blank | 21 comment | 18 complexity | 1fc163ba8ac4cca987f8bdbaba911f6c MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0, BSD-3-Clause
  1. <?php
  2. /*
  3. Bencoding OOP wrapper for PHP | by Proger_XP | In public domain
  4. http://proger.i-forge.net/BEncoded/7Tn
  5. Based on lightenc.php functions from
  6. http://wiki.theory.org/Decoding_encoding_bencoded_data_with_PHP
  7. */
  8. /* BEncoded classes by Proger_XP */
  9. class BEncoded {
  10. public $nodes;
  11. public $tempChar;
  12. static function Decode($str) {
  13. $res = bdecode($str);
  14. if ($res === null) {
  15. throw new EBEncode(null, 'Cannot decode bencoded data.', 'string: '.$str);
  16. } else {
  17. return $res;
  18. }
  19. }
  20. static function Encode($data, $tempChar = null) {
  21. $res = bencode($data, $tempChar);
  22. if ($res === null) {
  23. throw new EBEncode(null, 'Cannot encode bencoded data of type '.gettype($data).'.');
  24. }
  25. return $res;
  26. }
  27. static function TypeOf($value) {
  28. if (is_scalar($value) or $value === null) {
  29. return ((is_int($value) or is_float($value)) ? 'int' : 'str');
  30. } else {
  31. return empty($value['isDct']) ? 'list' : 'dict';
  32. }
  33. }
  34. static function HashOf($nodes, $raw = false) {
  35. return strtoupper(sha1(self::Encode($nodes), $raw));
  36. }
  37. /* Instance methods */
  38. function __construct($str = null) {
  39. if ($str !== null) {
  40. $this->FromString($str);
  41. }
  42. }
  43. function FromString($str) {
  44. $nodes = self::Decode($str);
  45. if (!is_array($nodes)) {
  46. throw new EBEncode($this, 'Cannot load bencoded string - it decodes to'.
  47. ' a non-array ('.gettype($nodes).').');
  48. }
  49. $this->nodes = &$nodes;
  50. }
  51. function FromFile($file) {
  52. $str = file_get_contents($file);
  53. if (!is_string($str)) {
  54. throw new EBEncode($this, 'File to load bencoded file from doesn\'t exist.', 'file: '.$file);
  55. }
  56. $this->FromString($str);
  57. }
  58. function ToString() {
  59. return self::Encode($this->nodes, $this->tempChar);
  60. }
  61. function ToFile($file) {
  62. $bytes = file_put_contents($file, $this->ToString, LOCK_EX);
  63. if (!is_int($bytes)) {
  64. throw new EBEncode($this, 'Cannot save bencoded file.', 'dest file: '.$file);
  65. }
  66. return $bytes;
  67. }
  68. // returns a shallow copy of root; to operate directly $this->nodes can be used.
  69. function Root() { return $this->nodes; }
  70. // returns null if node doesn't exist. $name = "/" or "" returns (sets/deletes) root.
  71. function ValueOf($name) { return $this->Alter($name); }
  72. // alias to ValueOf():
  73. function Get($name) { return $this->ValueOf($name); }
  74. function Set($name, $value) { return $this->Alter($name, 'set', $value); }
  75. function Copy($src, $dest) { return $this->Set($dest, $this->ValueOf($src)); }
  76. function Exchange($node_1, $node_2) {
  77. $temp = $this->ValueOf($node_2);
  78. $this->Set($node_2, $this->ValueOf($node_1));
  79. $this->Set($node_1, $temp);
  80. }
  81. function Delete($name) { return $this->Alter($name, 'delete'); }
  82. // $op: (g)et / (s)et, returns new value / (d)elete.
  83. protected function Alter($name, $op = 'get', $arg = null) {
  84. $lastSlash = strpbrk(mb_substr($name, -1), '\\/');
  85. $name = trim( strtr($name, '\\', '/'), '/' );
  86. $path = $name === '' ? array() : explode('/', $name);
  87. $parent = &$this->nodes;
  88. while ($path and is_array($parent)) {
  89. $value = &$parent[array_shift($path)];
  90. if ($op[0] === 'd') {
  91. if (!$path and $lastSlash == is_array($value)) {
  92. $value = null;
  93. }
  94. } elseif ($op[0] === 's') {
  95. if ($value === null and $path) {
  96. $value = array();
  97. if (( (string) $path[0] ) !== '0') {
  98. $value['isDct'] = true;
  99. }
  100. }
  101. }
  102. $parent = &$value;
  103. }
  104. if ($op[0] === 's') {
  105. $parent = $arg;
  106. } elseif ($op[0] === 'd' and !$name) {
  107. $parent = array();
  108. }
  109. return $parent;
  110. }
  111. function Export($name = '') { return $this->Dump( $this->ValueOf($name) ); }
  112. function Dump($value, $prefix = '') {
  113. $type = self::TypeOf($value);
  114. if ($type === 'int') {
  115. return is_float($value) ? sprintf('%1.1f', $value) : $value;
  116. } elseif ($type === 'str') {
  117. return var_export($value, true);
  118. } else {
  119. $res = '';
  120. $isDict = $type === 'dict';
  121. foreach ($value as $key => &$item) {
  122. if (!bskip($key, $item, $this->tempChar)) {
  123. $res .= $prefix;
  124. $res .= $isDict ? "$key:" : "#$key";
  125. $res .= is_array($item) ? "\n" : ' ';
  126. $res .= $this->Dump($item, "$prefix ")."\n";
  127. }
  128. }
  129. return substr($res, 0, -1);
  130. }
  131. }
  132. // type: int|str|list|dict; other type throws exception.
  133. function NewNode($type, $name) {
  134. switch ($type = strtolower($type)) {
  135. case 'int': return $this->Set($name, 0);
  136. case 'str': return $this->Set($name, '');
  137. case 'list': return $this->Set($name, array());
  138. case 'dict': return $this->Set($name, array('isDct' => true));
  139. default: throw new EBEncode($this, 'Cannot create bencoded node because type '.$type.' is unknown.');
  140. }
  141. }
  142. function SetEmpty($name) {
  143. $value = $this->ValueOf($name);
  144. if (is_int($value) or is_float($value)) {
  145. $value = 0;
  146. } elseif (is_string($value) or $value === null) {
  147. $value = '';
  148. } elseif (empty($value['isDct'])) {
  149. $value = array();
  150. } else {
  151. $value = array('isDct' => true);
  152. }
  153. return $this->Set($name, $value);
  154. }
  155. function Cast($name, $asType, $onlyIfNum = false) {
  156. $value = $this->ValueOf($name);
  157. if ($value === null) {
  158. throw new EBEncode($this, 'Cannot cast node '.$name.' into '.$asType.' because node doesn\'t exist.');
  159. }
  160. $asType = strtolower($asType);
  161. if (!in_array($asType, array('int', 'str', 'list', 'dict'))) {
  162. throw new EBEncode($this, 'Cannot cast node "'.$name.'" because new type ('.$asType.') is invalid.');
  163. }
  164. $type = self::TypeOf($value);
  165. if ($type !== $asType) {
  166. if ($type === 'int' or $type === 'str') {
  167. switch ($asType) {
  168. // str -> int:
  169. case 'int':
  170. if (!is_numeric($value)) {
  171. if (!$onlyIfNum) {
  172. throw new EBEncode($this, 'Cannot cast string "'.$value.' to integer because it\'s not a number.');
  173. }
  174. } else {
  175. $value = (float) $value;
  176. }
  177. break;
  178. // int -> str:
  179. case 'str': $value = (string) $value; break;
  180. case 'list': $value = array(0 => $value); break;
  181. case 'dict': $value = array('isDct' => true, 0 => $value); break;
  182. }
  183. } elseif ($asType === 'int' or $asType === 'str') {
  184. throw new EBException($this, 'Casting list/dict node "'.$name.'" into int/str isn\'t allowed.');
  185. } elseif ($asType === 'dict') { // list -> dict
  186. $value['isDct'] = true;
  187. } else { // dict -> list
  188. unset($value['isDct']);
  189. }
  190. $this->Set($name, $value);
  191. }
  192. return $value;
  193. }
  194. function TempChar($new = null) {
  195. $new === null or $this->tempChar = $new === '' ? null : $new;
  196. return $this->tempChar;
  197. }
  198. function InfoHash($raw = false) {
  199. $info = &$this->nodes['info'];
  200. if (empty($info)) {
  201. throw new EBEncode($this, 'Cannot calculate info hash because there is no \'info\' dictionary.');
  202. } else {
  203. return self::HashOf($info, $raw);
  204. }
  205. }
  206. }
  207. class EBEncode extends Exception {
  208. public $obj;
  209. function __construct($bencObj, $msg, $details = '', $previous = null) {
  210. $this->obj = $bencObj;
  211. parent::__construct(rtrim($msg, '.').": $details", $previous);
  212. }
  213. }
  214. /* lightenc.php */
  215. function bdecode($s, &$pos=0) {
  216. if($pos>=strlen($s)) {
  217. return null;
  218. }
  219. switch($s[$pos]){
  220. case 'd':
  221. $pos++;
  222. $retval=array();
  223. while ($s[$pos]!='e'){
  224. $key=bdecode($s, $pos);
  225. $val=bdecode($s, $pos);
  226. if ($key===null || $val===null)
  227. break;
  228. $retval[$key]=$val;
  229. }
  230. $retval["isDct"]=true;
  231. $pos++;
  232. return $retval;
  233. case 'l':
  234. $pos++;
  235. $retval=array();
  236. while ($s[$pos]!='e'){
  237. $val=bdecode($s, $pos);
  238. if ($val===null)
  239. break;
  240. $retval[]=$val;
  241. }
  242. $pos++;
  243. return $retval;
  244. case 'i':
  245. $pos++;
  246. $digits=strpos($s, 'e', $pos)-$pos;
  247. // Proger_XP: changed (int) -> (float) to avoid trimming of values exceeding
  248. // signed int's max value (2147483647).
  249. $val=(float)substr($s, $pos, $digits);
  250. $pos+=$digits+1;
  251. return $val;
  252. // case "0": case "1": case "2": case "3": case "4":
  253. // case "5": case "6": case "7": case "8": case "9":
  254. default:
  255. $digits=strpos($s, ':', $pos)-$pos;
  256. if ($digits<0 || $digits >20)
  257. return null;
  258. $len=(float)substr($s, $pos, $digits);
  259. $pos+=$digits+1;
  260. $str=substr($s, $pos, $len);
  261. $pos+=$len;
  262. //echo "pos: $pos str: [$str] len: $len digits: $digits\n";
  263. return (string)$str;
  264. }
  265. return null;
  266. }
  267. // Proger_XP: added added skipping for null values and $tempChar prefix for list/dicts.
  268. function bencode(&$d, $tempChar = null){
  269. if(is_array($d)){
  270. $ret="l";
  271. $isDict=!empty($d["isDct"]);
  272. if($isDict){
  273. $ret="d";
  274. // this is required by the specs, and BitTornado actualy chokes on unsorted dictionaries
  275. ksort($d, SORT_STRING);
  276. }
  277. foreach($d as $key=>$value) {
  278. if($isDict){
  279. // skip the isDct element, only if it's set by us
  280. if (!bskip($key, $value, $tempChar)) {
  281. $ret .= strlen($key).":$key";
  282. }
  283. } elseif (!is_int($key) and !is_float($key) and trim($key, '0..9') !== '') {
  284. // Proger_XP: added exception raising for non-numeric list keys.
  285. throw new EBEncode(null, 'Cannot bencode() a list - it contains a non-numeric key "'.$key.'".');
  286. }
  287. if (is_string($value)) {
  288. $ret.=strlen($value).":".$value;
  289. } elseif (is_int($value) or is_float($value)){
  290. $ret.="i${value}e";
  291. } else {
  292. $ret.=bencode ($value);
  293. }
  294. }
  295. return $ret."e";
  296. } elseif (is_string($d)) { // fallback if we're given a single bencoded string or int
  297. return strlen($d).":".$d;
  298. } elseif (is_int($d) or is_float($d)) {
  299. return "i${d}e";
  300. } else {
  301. return null;
  302. }
  303. }
  304. // bskip by Proger_XP.
  305. function bskip($key, &$value, $tempChar = null) {
  306. return ($key === 'isDct' and $value) or $value === null or strpos($key, $tempChar) === 0;
  307. }