PageRenderTime 25ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/php/protocol.class.php

https://github.com/abdullaheldeep/WhatsAPI
PHP | 707 lines | 561 code | 86 blank | 60 comment | 92 complexity | e6b968b16994268f31463d395633c9d0 MD5 | raw file
  1. <?php
  2. require 'decode.php';
  3. require 'exception.php';
  4. class IncompleteMessageException extends CustomException
  5. {
  6. private $input;
  7. public function __construct($message = null, $code = 0)
  8. {
  9. parent::__construct($message, $code);
  10. }
  11. public function setInput($input)
  12. {
  13. $this->input = $input;
  14. }
  15. public function getInput()
  16. {
  17. return $this->input;
  18. }
  19. }
  20. class ProtocolNode
  21. {
  22. private $tag;
  23. private $attributeHash;
  24. private $children;
  25. private $data;
  26. private static $cli = null;
  27. /**
  28. * check if call is from command line
  29. * @return bool
  30. */
  31. private static function isCli()
  32. {
  33. if(self::$cli === null)
  34. {
  35. //initial setter
  36. if(php_sapi_name() == "cli")
  37. {
  38. self::$cli = true;
  39. }
  40. else
  41. {
  42. self::$cli = false;
  43. }
  44. }
  45. return self::$cli;
  46. }
  47. /**
  48. * @return string
  49. */
  50. public function getData()
  51. {
  52. return $this->data;
  53. }
  54. /**
  55. * @return string
  56. */
  57. public function getTag()
  58. {
  59. return $this->tag;
  60. }
  61. /**
  62. * @return string[]
  63. */
  64. public function getAttributes()
  65. {
  66. return $this->attributeHash;
  67. }
  68. /**
  69. * @return ProtocolNode[]
  70. */
  71. public function getChildren()
  72. {
  73. return $this->children;
  74. }
  75. public function __construct($tag, $attributeHash, $children, $data)
  76. {
  77. $this->tag = $tag;
  78. $this->attributeHash = $attributeHash;
  79. $this->children = $children;
  80. $this->data = $data;
  81. }
  82. /**
  83. * @param string $indent
  84. * @param bool $isChild
  85. * @return string
  86. */
  87. public function nodeString($indent = "", $isChild = false)
  88. {
  89. //formatters
  90. $lt = "<";
  91. $gt = ">";
  92. $nl = "\n";
  93. if(!self::isCli())
  94. {
  95. $lt = "&lt;";
  96. $gt = "&gt;";
  97. $nl = "<br />";
  98. $indent = str_replace(" ", "&nbsp;", $indent);
  99. }
  100. $ret = $indent . $lt . $this->tag;
  101. if ($this->attributeHash != null) {
  102. foreach ($this->attributeHash as $key => $value) {
  103. $ret .= " " . $key . "=\"" . $value . "\"";
  104. }
  105. }
  106. $ret .= $gt;
  107. if (strlen($this->data) > 0) {
  108. if (strlen($this->data) <= 1024) {
  109. //message
  110. $ret .= $this->data;
  111. } else {
  112. //raw data
  113. $ret .= " " . strlen($this->data) . " byte data";
  114. }
  115. }
  116. if ($this->children) {
  117. $ret .= $nl;
  118. $foo = array();
  119. foreach ($this->children as $child) {
  120. $foo[] = $child->nodeString($indent . " ", true);
  121. }
  122. $ret .= implode($nl, $foo);
  123. $ret .= $nl . $indent;
  124. }
  125. $ret .= $lt . "/" . $this->tag . $gt;
  126. if(!$isChild)
  127. {
  128. $ret .= $nl;
  129. if(!self::isCli())
  130. {
  131. $ret .= $nl;
  132. }
  133. }
  134. return $ret;
  135. }
  136. /**
  137. * @param $attribute
  138. * @return string
  139. */
  140. public function getAttribute($attribute)
  141. {
  142. $ret = "";
  143. if (isset($this->attributeHash[$attribute])) {
  144. $ret = $this->attributeHash[$attribute];
  145. }
  146. return $ret;
  147. }
  148. /**
  149. * @param string $needle
  150. * @return boolean
  151. */
  152. public function nodeIdContains($needle)
  153. {
  154. return (strpos($this->getAttribute("id"), $needle) !== false);
  155. }
  156. //get children supports string tag or int index
  157. /**
  158. * @param $tag
  159. * @return ProtocolNode
  160. */
  161. public function getChild($tag)
  162. {
  163. $ret = null;
  164. if ($this->children) {
  165. if(is_int($tag))
  166. {
  167. if(isset($this->children[$tag]))
  168. {
  169. return $this->children[$tag];
  170. }
  171. else
  172. {
  173. return null;
  174. }
  175. }
  176. foreach ($this->children as $child) {
  177. if (strcmp($child->tag, $tag) == 0) {
  178. return $child;
  179. }
  180. $ret = $child->getChild($tag);
  181. if ($ret) {
  182. return $ret;
  183. }
  184. }
  185. }
  186. return null;
  187. }
  188. /**
  189. * @param $tag
  190. * @return bool
  191. */
  192. public function hasChild($tag)
  193. {
  194. return $this->getChild($tag) == null ? false : true;
  195. }
  196. /**
  197. * @param int $offset
  198. */
  199. public function refreshTimes($offset = 0)
  200. {
  201. if (isset($this->attributeHash['id'])) {
  202. $id = $this->attributeHash['id'];
  203. $parts = explode('-', $id);
  204. $parts[0] = time() + $offset;
  205. $this->attributeHash['id'] = implode('-', $parts);
  206. }
  207. if (isset($this->attributeHash['t'])) {
  208. $this->attributeHash['t'] = time();
  209. }
  210. }
  211. /**
  212. * Print human readable ProtocolNode object
  213. *
  214. * @return string
  215. */
  216. public function __toString()
  217. {
  218. $readableNode = array(
  219. 'tag' => $this->tag,
  220. 'attributeHash' => $this->attributeHash,
  221. 'children' => $this->children,
  222. 'data' => $this->data
  223. );
  224. return print_r( $readableNode, true );
  225. }
  226. }
  227. class BinTreeNodeReader
  228. {
  229. private $input;
  230. /** @var $key KeyStream */
  231. private $key;
  232. public function resetKey()
  233. {
  234. $this->key = null;
  235. }
  236. public function setKey($key)
  237. {
  238. $this->key = $key;
  239. }
  240. public function nextTree($input = null)
  241. {
  242. if ($input != null) {
  243. $this->input = $input;
  244. }
  245. $stanzaFlag = ($this->peekInt8() & 0xF0) >> 4;
  246. $stanzaSize = $this->peekInt16(1);
  247. if ($stanzaSize > strlen($this->input)) {
  248. throw new Exception("Incomplete message $stanzaSize != " . strlen($this->input));
  249. }
  250. $this->readInt24();
  251. if ($stanzaFlag & 8) {
  252. if (isset($this->key)) {
  253. $realSize = $stanzaSize - 4;
  254. $this->input = $this->key->DecodeMessage($this->input, $realSize, 0, $realSize);// . $remainingData;
  255. } else {
  256. throw new Exception("Encountered encrypted message, missing key");
  257. }
  258. }
  259. if ($stanzaSize > 0) {
  260. return $this->nextTreeInternal();
  261. }
  262. return null;
  263. }
  264. protected function getToken($token)
  265. {
  266. $ret = "";
  267. $subdict = false;
  268. TokenMap::GetToken($token, $subdict, $ret);
  269. if(!$ret)
  270. {
  271. $token = $this->readInt8();
  272. TokenMap::GetToken($token, $subdict, $ret);
  273. if(!$ret)
  274. {
  275. throw new Exception("BinTreeNodeReader->getToken: Invalid token $token");
  276. }
  277. }
  278. return $ret;
  279. }
  280. protected function readString($token)
  281. {
  282. $ret = "";
  283. if ($token == -1) {
  284. throw new Exception("BinTreeNodeReader->readString: Invalid token $token");
  285. }
  286. if (($token > 4) && ($token < 0xf5)) {
  287. $ret = $this->getToken($token);
  288. } elseif ($token == 0) {
  289. $ret = "";
  290. } elseif ($token == 0xfc) {
  291. $size = $this->readInt8();
  292. $ret = $this->fillArray($size);
  293. } elseif ($token == 0xfd) {
  294. $size = $this->readInt24();
  295. $ret = $this->fillArray($size);
  296. } elseif ($token == 0xfe) {
  297. $token = $this->readInt8();
  298. $ret = $this->getToken($token + 0xf5);
  299. } elseif ($token == 0xfa) {
  300. $user = $this->readString($this->readInt8());
  301. $server = $this->readString($this->readInt8());
  302. if ((strlen($user) > 0) && (strlen($server) > 0)) {
  303. $ret = $user . "@" . $server;
  304. } elseif (strlen($server) > 0) {
  305. $ret = $server;
  306. }
  307. }
  308. return $ret;
  309. }
  310. protected function readAttributes($size)
  311. {
  312. $attributes = array();
  313. $attribCount = ($size - 2 + $size % 2) / 2;
  314. for ($i = 0; $i < $attribCount; $i++) {
  315. $key = $this->readString($this->readInt8());
  316. $value = $this->readString($this->readInt8());
  317. $attributes[$key] = $value;
  318. }
  319. return $attributes;
  320. }
  321. protected function nextTreeInternal()
  322. {
  323. $token = $this->readInt8();
  324. $size = $this->readListSize($token);
  325. $token = $this->readInt8();
  326. if ($token == 1) {
  327. $attributes = $this->readAttributes($size);
  328. return new ProtocolNode("start", $attributes, null, "");
  329. } elseif ($token == 2) {
  330. return null;
  331. }
  332. $tag = $this->readString($token);
  333. $attributes = $this->readAttributes($size);
  334. if (($size % 2) == 1) {
  335. return new ProtocolNode($tag, $attributes, null, "");
  336. }
  337. $token = $this->readInt8();
  338. if ($this->isListTag($token)) {
  339. return new ProtocolNode($tag, $attributes, $this->readList($token), "");
  340. }
  341. return new ProtocolNode($tag, $attributes, null, $this->readString($token));
  342. }
  343. protected function isListTag($token)
  344. {
  345. return (($token == 248) || ($token == 0) || ($token == 249));
  346. }
  347. protected function readList($token)
  348. {
  349. $size = $this->readListSize($token);
  350. $ret = array();
  351. for ($i = 0; $i < $size; $i++) {
  352. array_push($ret, $this->nextTreeInternal());
  353. }
  354. return $ret;
  355. }
  356. protected function readListSize($token)
  357. {
  358. $size = 0;
  359. if ($token == 0xf8) {
  360. $size = $this->readInt8();
  361. } elseif ($token == 0xf9) {
  362. $size = $this->readInt16();
  363. } else {
  364. throw new Exception("BinTreeNodeReader->readListSize: Invalid token $token");
  365. }
  366. return $size;
  367. }
  368. protected function peekInt24($offset = 0)
  369. {
  370. $ret = 0;
  371. if (strlen($this->input) >= (3 + $offset)) {
  372. $ret = ord(substr($this->input, $offset, 1)) << 16;
  373. $ret |= ord(substr($this->input, $offset + 1, 1)) << 8;
  374. $ret |= ord(substr($this->input, $offset + 2, 1)) << 0;
  375. }
  376. return $ret;
  377. }
  378. protected function readInt24()
  379. {
  380. $ret = $this->peekInt24();
  381. if (strlen($this->input) >= 3) {
  382. $this->input = substr($this->input, 3);
  383. }
  384. return $ret;
  385. }
  386. protected function peekInt16($offset = 0)
  387. {
  388. $ret = 0;
  389. if (strlen($this->input) >= (2 + $offset)) {
  390. $ret = ord(substr($this->input, $offset, 1)) << 8;
  391. $ret |= ord(substr($this->input, $offset + 1, 1)) << 0;
  392. }
  393. return $ret;
  394. }
  395. protected function readInt16()
  396. {
  397. $ret = $this->peekInt16();
  398. if ($ret > 0) {
  399. $this->input = substr($this->input, 2);
  400. }
  401. return $ret;
  402. }
  403. protected function peekInt8($offset = 0)
  404. {
  405. $ret = 0;
  406. if (strlen($this->input) >= (1 + $offset)) {
  407. $sbstr = substr($this->input, $offset, 1);
  408. $ret = ord($sbstr);
  409. }
  410. return $ret;
  411. }
  412. protected function readInt8()
  413. {
  414. $ret = $this->peekInt8();
  415. if (strlen($this->input) >= 1) {
  416. $this->input = substr($this->input, 1);
  417. }
  418. return $ret;
  419. }
  420. protected function fillArray($len)
  421. {
  422. $ret = "";
  423. if (strlen($this->input) >= $len) {
  424. $ret = substr($this->input, 0, $len);
  425. $this->input = substr($this->input, $len);
  426. }
  427. return $ret;
  428. }
  429. }
  430. class BinTreeNodeWriter
  431. {
  432. private $output;
  433. /** @var $key KeyStream */
  434. private $key;
  435. public function resetKey()
  436. {
  437. $this->key = null;
  438. }
  439. public function setKey($key)
  440. {
  441. $this->key = $key;
  442. }
  443. public function StartStream($domain, $resource)
  444. {
  445. $attributes = array();
  446. $header = "WA";
  447. $header .= $this->writeInt8(1);
  448. $header .= $this->writeInt8(4);
  449. $attributes["to"] = $domain;
  450. $attributes["resource"] = $resource;
  451. $this->writeListStart(count($attributes) * 2 + 1);
  452. $this->output .= "\x01";
  453. $this->writeAttributes($attributes);
  454. $ret = $header . $this->flushBuffer();
  455. return $ret;
  456. }
  457. /**
  458. * @param ProtocolNode $node
  459. * @return string
  460. */
  461. public function write($node, $encrypt = true)
  462. {
  463. if ($node == null) {
  464. $this->output .= "\x00";
  465. } else {
  466. $this->writeInternal($node);
  467. }
  468. return $this->flushBuffer($encrypt);
  469. }
  470. /**
  471. * @param ProtocolNode $node
  472. */
  473. protected function writeInternal($node)
  474. {
  475. $len = 1;
  476. if ($node->getAttributes() != null) {
  477. $len += count($node->getAttributes()) * 2;
  478. }
  479. if (count($node->getChildren()) > 0) {
  480. $len += 1;
  481. }
  482. if (strlen($node->getData()) > 0) {
  483. $len += 1;
  484. }
  485. $this->writeListStart($len);
  486. $this->writeString($node->getTag());
  487. $this->writeAttributes($node->getAttributes());
  488. if (strlen($node->getData()) > 0) {
  489. $this->writeBytes($node->getData());
  490. }
  491. if ($node->getChildren()) {
  492. $this->writeListStart(count($node->getChildren()));
  493. foreach ($node->getChildren() as $child) {
  494. $this->writeInternal($child);
  495. }
  496. }
  497. }
  498. protected function parseInt24($data)
  499. {
  500. $ret = ord(substr($data, 0, 1)) << 16;
  501. $ret |= ord(substr($data, 1, 1)) << 8;
  502. $ret |= ord(substr($data, 2, 1)) << 0;
  503. return $ret;
  504. }
  505. protected function flushBuffer($encrypt = true)
  506. {
  507. $size = strlen($this->output);
  508. $data = $this->output;
  509. if($this->key != null && $encrypt)
  510. {
  511. $bsize = $this->getInt24($size);
  512. //encrypt
  513. $data = $this->key->EncodeMessage($data, $size, 0, $size);
  514. $len = strlen($data);
  515. $bsize[0] = chr((8 << 4) | (($len & 16711680) >> 16));
  516. $bsize[1] = chr(($len & 65280) >> 8);
  517. $bsize[2] = chr($len & 255);
  518. $size = $this->parseInt24($bsize);
  519. }
  520. $ret = $this->writeInt24($size) . $data;
  521. $this->output = '';
  522. return $ret;
  523. }
  524. protected function getInt24($length)
  525. {
  526. $ret = '';
  527. $ret .= chr((($length & 0xf0000) >> 16));
  528. $ret .= chr((($length & 0xff00) >> 8));
  529. $ret .= chr(($length & 0xff));
  530. return $ret;
  531. }
  532. protected function writeToken($token)
  533. {
  534. if ($token < 0xf5) {
  535. $this->output .= chr($token);
  536. } elseif ($token <= 0x1f4) {
  537. $this->output .= "\xfe" . chr($token - 0xf5);
  538. }
  539. }
  540. protected function writeJid($user, $server)
  541. {
  542. $this->output .= "\xfa";
  543. if (strlen($user) > 0) {
  544. $this->writeString($user);
  545. } else {
  546. $this->writeToken(0);
  547. }
  548. $this->writeString($server);
  549. }
  550. protected function writeInt8($v)
  551. {
  552. $ret = chr($v & 0xff);
  553. return $ret;
  554. }
  555. protected function writeInt16($v)
  556. {
  557. $ret = chr(($v & 0xff00) >> 8);
  558. $ret .= chr(($v & 0x00ff) >> 0);
  559. return $ret;
  560. }
  561. protected function writeInt24($v)
  562. {
  563. $ret = chr(($v & 0xff0000) >> 16);
  564. $ret .= chr(($v & 0x00ff00) >> 8);
  565. $ret .= chr(($v & 0x0000ff) >> 0);
  566. return $ret;
  567. }
  568. protected function writeBytes($bytes)
  569. {
  570. $len = strlen($bytes);
  571. if ($len >= 0x100) {
  572. $this->output .= "\xfd";
  573. $this->output .= $this->writeInt24($len);
  574. } else {
  575. $this->output .= "\xfc";
  576. $this->output .= $this->writeInt8($len);
  577. }
  578. $this->output .= $bytes;
  579. }
  580. protected function writeString($tag)
  581. {
  582. $intVal = -1;
  583. $subdict = false;
  584. if(TokenMap::TryGetToken($tag, $subdict, $intVal))
  585. {
  586. if($subdict)
  587. {
  588. $this->writeToken(236);
  589. }
  590. $this->writeToken($intVal);
  591. return;
  592. }
  593. $index = strpos($tag, '@');
  594. if ($index) {
  595. $server = substr($tag, $index + 1);
  596. $user = substr($tag, 0, $index);
  597. $this->writeJid($user, $server);
  598. } else {
  599. $this->writeBytes($tag);
  600. }
  601. }
  602. protected function writeAttributes($attributes)
  603. {
  604. if ($attributes) {
  605. foreach ($attributes as $key => $value) {
  606. $this->writeString($key);
  607. $this->writeString($value);
  608. }
  609. }
  610. }
  611. protected function writeListStart($len)
  612. {
  613. if ($len == 0) {
  614. $this->output .= "\x00";
  615. } elseif ($len < 256) {
  616. $this->output .= "\xf8" . chr($len);
  617. } else {
  618. $this->output .= "\xf9" . $this->writeInt16($len);
  619. }
  620. }
  621. }