KerioApi.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. <?php
  2. namespace ThurData\Servers\KerioEmail\Api;
  3. /**
  4. * This file is part of the kerio-api-php.
  5. *
  6. * Copyright (c) Kerio Technologies s.r.o.
  7. *
  8. * For the full copyright and license information, please view
  9. * the file license.txt that was distributed with this source code
  10. * or visit Developer Zone. (http://www.kerio.com/developers)
  11. *
  12. * Do not modify this source code.
  13. * Any changes may be overwritten by a new version.
  14. */
  15. require_once(dirname(__FILE__) . '/KerioApiInterface.php');
  16. require_once(dirname(__FILE__) . '/KerioApiSocket.php');
  17. require_once(dirname(__FILE__) . '/KerioApiException.php');
  18. /**
  19. * Kerio API Class.
  20. *
  21. * This is main class.
  22. *
  23. * Example:
  24. * <code>
  25. * <?php
  26. * require_once(dirname(__FILE__) . '/src/KerioApi.php');
  27. *
  28. * class MyApi extents KerioApi {
  29. *
  30. * public function __contruct($name, $vendor, $version) {
  31. * parent::__construct($name, $vendor, $version);
  32. * }
  33. *
  34. * public function getFoo() {
  35. * return $this->sendRequest('...');
  36. * }
  37. * }
  38. * ?>
  39. * </code>
  40. *
  41. * @copyright Copyright &copy; 2012-2012 Kerio Technologies s.r.o.
  42. * @license http://www.kerio.com/developers/license/sdk-agreement
  43. * @version 1.4.0.234
  44. */
  45. class KerioApi implements KerioApiInterface {
  46. /**
  47. * End-Line format
  48. */
  49. const CRLF = "\r\n";
  50. /**
  51. * HTTP server status
  52. */
  53. const HTTP_SERVER_OK = 200;
  54. /**
  55. * Library name
  56. * @var string
  57. */
  58. public $name = 'Kerio APIs Client Library for PHP';
  59. /**
  60. * Library version
  61. * @var string
  62. */
  63. public $version = '1.4.0.234';
  64. /**
  65. * Debug mode
  66. * @var boolean
  67. */
  68. private $debug = FALSE;
  69. /**
  70. * Unique id used in request
  71. * @var integer
  72. */
  73. private $requestId = 0;
  74. /**
  75. * Hostname
  76. * @var string
  77. */
  78. protected $hostname = '';
  79. /**
  80. * X-Token
  81. * @var string
  82. */
  83. protected $token = '';
  84. /**
  85. * Cookies
  86. * @var string
  87. */
  88. protected $cookies = '';
  89. /**
  90. * Application details
  91. * @var array
  92. */
  93. protected $application = array('name' => '', 'vendor' => '', 'version' => '');
  94. /**
  95. * JSON-RPC settings
  96. * @var array
  97. */
  98. protected $jsonRpc = array('version' => '', 'port' => '', 'api' => '');
  99. /**
  100. * HTTP headers
  101. * @var array
  102. */
  103. protected $headers = array();
  104. /**
  105. * Socket handler
  106. * @var resource
  107. */
  108. private $socketHandler = '';
  109. /**
  110. * Socket timeout
  111. * @var integer
  112. */
  113. private $timeout = '';
  114. /**
  115. * Class contructor.
  116. *
  117. * @param string Application name
  118. * @param string Application vendor
  119. * @param string Application version
  120. * @return void
  121. * @throws KerioApiException
  122. */
  123. public function __construct($name, $vendor, $version) {
  124. $this->checkPhpEnvironment();
  125. $this->setApplication($name, $vendor, $version);
  126. $this->setJsonRpc($this->jsonRpc['version'], $this->jsonRpc['port'], $this->jsonRpc['api']);
  127. }
  128. /**
  129. * Check PHP environment.
  130. *
  131. * @param void
  132. * @return void
  133. */
  134. private function checkPhpEnvironment() {
  135. if (version_compare(PHP_VERSION, '5.1.0', '<')) {
  136. die(sprintf('<h1>kerio-api-php error</h1>Minimum PHP version required is 5.1.0. Your installation is %s.<br>Please, upgrade your PHP installation.', phpversion()));
  137. }
  138. if (FALSE === function_exists('openssl_open')) {
  139. die('<h1>kerio-api-php error</h1>Your PHP installation does not have OpenSSL enabled.<br>To configure OpenSSL support in PHP, please edit your php.ini config file and enable row with php_openssl module, e.g. extension=php_openssl.dll<br>For more information see <a href="http://www.php.net/manual/en/openssl.installation.php">http://www.php.net/manual/en/openssl.installation.php</a>.');
  140. }
  141. if (FALSE === function_exists('json_decode')) {
  142. die('<h1>kerio-api-php error</h1>Your PHP installation does not have JSON enabled.<br>To configure JSON support in PHP, please edit your php.ini config file and enable row with php_json module, e.g. extension=php_json.dll<br>For more information see <a href="http://www.php.net/manual/en/json.installation.php">http://www.php.net/manual/en/json.installation.php</a>.');
  143. }
  144. }
  145. /**
  146. * Set application to identify on server.
  147. *
  148. * @param string Application name
  149. * @param string Vendor
  150. * @param string Version
  151. * @return void
  152. * @throws KerioApiException
  153. */
  154. private function setApplication($name, $vendor, $version) {
  155. if (empty($name) && empty($vendor) && empty($version)) {
  156. throw new KerioApiException('Application not defined.');
  157. }
  158. else {
  159. $this->debug(sprintf("Registering application '%s' by '%s' version '%s'<br>", $name, $vendor, $version));
  160. $this->application = array(
  161. 'name' => $name,
  162. 'vendor' => $vendor,
  163. 'version' => $version
  164. );
  165. }
  166. }
  167. /**
  168. * Get application detail.
  169. *
  170. * @param void
  171. * @return array Application details
  172. */
  173. public final function getApplication() {
  174. return $this->application;
  175. }
  176. /**
  177. * Set JSON-RPC settings.
  178. *
  179. * @see class/KerioApiInterface::setJsonRpc()
  180. * @param string JSON-RPC version
  181. * @param integer JSON-RPC port
  182. * @param string JSON-RPC URI
  183. * @return void
  184. * @throws KerioApiException
  185. */
  186. public final function setJsonRpc($version, $port, $api) {
  187. if (empty($version) && empty($port) && empty($api)) {
  188. throw new KerioApiException('JSON-RPC not defined.');
  189. }
  190. else {
  191. $this->debug(sprintf("Registering JSON-RPC %s on %s using port %d", $version, $api, $port));
  192. $this->jsonRpc = array(
  193. 'version' => $version,
  194. 'port' => $port,
  195. 'api' => $api
  196. );
  197. }
  198. }
  199. /**
  200. * Get JSON-RPC settings.
  201. *
  202. * @param void
  203. * @return array JSON-RPC settings
  204. */
  205. public final function getJsonRpc() {
  206. return $this->jsonRpc;
  207. }
  208. /**
  209. * Enable or disable of displaying debug messages.
  210. *
  211. * @param boolean
  212. * @return void
  213. */
  214. public final function setDebug($boolean) {
  215. $this->debug = (bool) $boolean;
  216. }
  217. /**
  218. * Get debug settings.
  219. *
  220. * @param void
  221. * @return boolean
  222. */
  223. public final function getDebug() {
  224. return $this->debug;
  225. }
  226. /**
  227. * Display a message if debug is TRUE.
  228. *
  229. * @param string Message
  230. * @param string CSS class
  231. * @return string Message in &lt;div&gt; tags
  232. */
  233. public function debug($message, $css = 'debug') {
  234. if ($this->debug) {
  235. printf('<div class="%s">%s</div>%s', $css, $message, "\n");
  236. }
  237. }
  238. /**
  239. * Get product API version.
  240. *
  241. * @param void
  242. * @return integer API version
  243. */
  244. public function getApiVersion() {
  245. $method = 'Version.getApiVersion';
  246. $response = $this->sendRequest($method);
  247. return $response['apiVersion'];
  248. }
  249. /**
  250. * Login method.
  251. *
  252. * @see class/KerioApiInterface::login()
  253. * @param string Hostname
  254. * @param string Username
  255. * @param string Password
  256. * @return array Result
  257. * @throws KerioApiException
  258. */
  259. public function login($hostname, $username, $password) {
  260. $this->clean();
  261. if (empty($hostname)) {
  262. throw new KerioApiException('Cannot login. Hostname not set.');
  263. }
  264. elseif (empty($username)) {
  265. throw new KerioApiException('Cannot login. Username not set.');
  266. }
  267. elseif (empty($this->application)) {
  268. throw new KerioApiException('Cannot login. Application not defined.');
  269. }
  270. $this->setHostname($hostname);
  271. $method = 'Session.login';
  272. $params = array(
  273. 'userName' => $username,
  274. 'password' => $password,
  275. 'application' => $this->application
  276. );
  277. $response = $this->sendRequest($method, $params);
  278. return $response;
  279. }
  280. /**
  281. * Logout method.
  282. *
  283. * @see class/KerioApiInterface::logout()
  284. * @param void
  285. * @return array Result
  286. */
  287. public function logout() {
  288. $method = 'Session.logout';
  289. $response = $this->sendRequest($method);
  290. $this->clean();
  291. return $response;
  292. }
  293. /**
  294. * Clean data.
  295. *
  296. * @param void
  297. * @return void
  298. */
  299. public function clean() {
  300. if ($this->token) {
  301. $this->debug('Removing X-Token.');
  302. $this->token = '';
  303. }
  304. if ($this->cookies) {
  305. $this->debug('Removing Cookies.');
  306. $this->cookies = '';
  307. }
  308. $this->hostname = '';
  309. $this->socketHandler = '';
  310. }
  311. /**
  312. * Get full HTTP request.
  313. *
  314. * @param string HTTP method [POST,GET,PUT]
  315. * @param string HTTP body
  316. * @return string HTTP request
  317. * @throws KerioApiException
  318. */
  319. protected function getHttpRequest($method, $body) {
  320. /* Clean data */
  321. $this->headers = array();
  322. $bodyRequest = '';
  323. $fullRequest = '';
  324. /* Prepare headers and get request body*/
  325. switch ($method) {
  326. case 'POST': // common requests
  327. $bodyRequest = $this->getHttpPostRequest($body);
  328. break;
  329. case 'GET': // download
  330. $bodyRequest = $this->getHttpGetRequest($body);
  331. break;
  332. case 'PUT': // upload
  333. $bodyRequest = $this->getHttpPutRequest($body);
  334. break;
  335. default:
  336. throw new KerioApiException('Cannot send request, unknown method.');
  337. }
  338. /* Add port to headers if non-default is used */
  339. $port = ($this->jsonRpc['port'] == 443)
  340. ? ''
  341. : sprintf(':%d', $this->jsonRpc['port']);
  342. /* Set common headers */
  343. $this->headers['Host:'] = sprintf('%s%s', $this->hostname, $port);
  344. $this->headers['Content-Length:'] = strlen($bodyRequest);
  345. $this->headers['Connection:'] = 'close';
  346. /* Set X-Token and Cookies */
  347. if ($this->token) {
  348. $this->headers['Cookie:'] = $this->cookies;
  349. $this->headers['X-Token:'] = $this->token;
  350. }
  351. /* Build request */
  352. foreach ($this->headers as $item => $value){
  353. $fullRequest .= $item . ' ' . $value . self::CRLF;
  354. }
  355. $fullRequest .= self::CRLF;
  356. $fullRequest .= $bodyRequest;
  357. /* Return */
  358. return $fullRequest;
  359. }
  360. /**
  361. * Get headers for POST request.
  362. *
  363. * @param string Request body
  364. * @return string Request body
  365. */
  366. protected function getHttpPostRequest($data) {
  367. $this->headers['POST'] = sprintf('%s HTTP/1.1', $this->jsonRpc['api']);
  368. $this->headers['Accept:'] = 'application/json-rpc';
  369. $this->headers['Content-Type:'] = 'application/json-rpc; charset=UTF-8';
  370. $this->headers['User-Agent:'] = sprintf('%s/%s', $this->name, $this->version);
  371. return str_replace(array("\r", "\r\n", "\n", "\t"), '', $data) . self::CRLF;
  372. }
  373. /**
  374. * Get headers for GET request.
  375. *
  376. * @param string Request body
  377. * @return string Request body
  378. */
  379. protected function getHttpGetRequest($data) {
  380. $this->headers['GET'] = sprintf('%s HTTP/1.1', $data);
  381. $this->headers['Accept:'] = '*/*';
  382. return $data . self::CRLF;
  383. }
  384. /**
  385. * Get headers for PUT request.
  386. *
  387. * @param string Request body
  388. * @return string Request body
  389. */
  390. protected function getHttpPutRequest($data) {
  391. $boundary = sprintf('---------------------%s', substr(md5(rand(0,32000)), 0, 10));
  392. $this->headers['POST'] = sprintf('%s%s HTTP/1.1', $this->jsonRpc['api'], 'upload/');
  393. $this->headers['Accept:'] = '*/*';
  394. $this->headers['Content-Type:'] = sprintf('multipart/form-data; boundary=%s', $boundary);
  395. $body = '--' . $boundary . self::CRLF;
  396. $body .= 'Content-Disposition: form-data; name="unknown"; filename="newFile.bin"' . self::CRLF;
  397. $body .= self::CRLF;
  398. $body .= $data . self::CRLF;
  399. $body .= '--' . $boundary . '--' . self::CRLF;
  400. return $body;
  401. }
  402. /**
  403. * Send request using method and its params.
  404. *
  405. * @see class/KerioApiInterface::sendRequest()
  406. * @param string Interface.method
  407. * @param array Params of 'Interface.method'.
  408. * @return array Returns same type as param is, e.g. JSON if method is also JSON
  409. */
  410. public function sendRequest($method, $params = '') {
  411. $request = array(
  412. 'jsonrpc' => $this->jsonRpc['version'],
  413. 'id' => $this->getRequestId(),
  414. 'token' => $this->token,
  415. 'method' => $method,
  416. 'params' => $params
  417. );
  418. if (empty($this->token)) {
  419. unset($request['token']);
  420. }
  421. if (empty($params)) {
  422. unset($request['params']);
  423. }
  424. $json_request = json_encode($request);
  425. /* Send data to server */
  426. $json_response = $this->send('POST', $json_request);
  427. /* Return */
  428. $response = json_decode($json_response, TRUE);
  429. return $response['result'];
  430. }
  431. /**
  432. * Send JSON request.
  433. *
  434. * @param string JSON request
  435. * @return string JSON response
  436. */
  437. public function sendRequestJson($json) {
  438. return $this->send('POST', $json);
  439. }
  440. /**
  441. * Send data to server.
  442. *
  443. * @param string Request method [POST,GET,PUT]
  444. * @param string Request body
  445. * @return string Server response
  446. * @throws KerioApiException
  447. */
  448. protected function send($method, $data) {
  449. if (empty($this->hostname)) {
  450. throw new KerioApiException('Cannot send data before login.');
  451. }
  452. /* Get full HTTP request */
  453. $request = $this->getHttpRequest($method, $data);
  454. $this->debug(sprintf("&rarr; Raw request:\n<pre>%s</pre>", $request));
  455. /* Open socket */
  456. $this->socketHandler = new KerioApiSocket($this->hostname, $this->jsonRpc['port'], $this->timeout);
  457. /* Send data */
  458. $rawResponse = $this->socketHandler->send($request);
  459. $this->debug(sprintf("&larr; Raw response:\n<pre>%s</pre>", $rawResponse));
  460. /* Parse response */
  461. $headers = $this->socketHandler->getHeaders();
  462. $body = $this->socketHandler->getBody();
  463. $this->checkHttpResponse(self::HTTP_SERVER_OK, $headers);
  464. /* Decode JSON response */
  465. $response = stripslashes($body);
  466. $response = json_decode($body, TRUE);
  467. if (($method == 'POST') && empty($response)) {
  468. throw new KerioApiException('Invalid JSON data, cannot parse response.');
  469. }
  470. /* Set CSRF token */
  471. if (empty($this->token)) {
  472. if (isset($response['result']['token'])) {
  473. $this->setToken($response['result']['token']);
  474. }
  475. }
  476. /* Handle errors */
  477. if (isset($response['error'])) {
  478. if (FALSE === empty($response['error'])) {
  479. $message = $response['error']['message'];
  480. $code = $response['error']['code'];
  481. $params = (isset($response['error']['data']))
  482. ? $response['error']['data']['messageParameters']['positionalParameters']
  483. : '';
  484. throw new KerioApiException($message, $code, $params, $data, $body);
  485. }
  486. }
  487. elseif (isset($response['result']['errors'])) {
  488. if (FALSE === empty($response['result']['errors'])) {
  489. $message = $response['result']['errors'][0]['message'];
  490. $code = $response['result']['errors'][0]['code'];
  491. $params = $response['result']['errors'][0]['messageParameters']['positionalParameters'];
  492. throw new KerioApiException($message, $code, $params, $data, $body);
  493. }
  494. }
  495. /* Handle Cookies */
  496. if (empty($this->cookies)) {
  497. $this->setCookieFromHeaders($headers);
  498. }
  499. /* Return */
  500. return $body;
  501. }
  502. /**
  503. * Get a file from server.
  504. *
  505. * @param string File url
  506. * @param string Save directory
  507. * @param string Save as, optional. Default is file.bin
  508. * @return boolean True on success
  509. * @throws KerioApiException
  510. */
  511. public function downloadFile($url, $directory, $filename = '') {
  512. $saveAs = (empty($filename)) ? 'file.bin' : $filename;
  513. $saveAs = sprintf('%s/%s', $directory, $filename);
  514. $data = $this->send('GET', $url);
  515. $this->debug(sprintf('Saving file %s', $saveAs));
  516. if (FALSE === @file_put_contents($saveAs, $data)) {
  517. throw new KerioApiException(sprintf('Unable to save file %s', $saveAs));
  518. }
  519. return TRUE;
  520. }
  521. /**
  522. * Get a file from server.
  523. *
  524. * @param string File url
  525. * @return string File content
  526. */
  527. public function getFile($url) {
  528. return $this->send('GET', $url);
  529. }
  530. /**
  531. * Put a file to server.
  532. *
  533. * @param string Absolute path to file
  534. * @param integer Reference ID where uploaded file belongs to, optional
  535. * @return array Result
  536. * @throws KerioApiException
  537. */
  538. public function uploadFile($filename, $id = null) {
  539. $data = @file_get_contents($filename);
  540. if ($data) {
  541. $this->debug(sprintf('Uploading file %s', $filename));
  542. $json_response = $this->send('PUT', $data);
  543. }
  544. else {
  545. throw new KerioApiException(sprintf('Unable to open file %s', $filename));
  546. }
  547. $response = json_decode($json_response, TRUE);
  548. return $response['result'];
  549. }
  550. /**
  551. * Check HTTP/1.1 reponse header.
  552. *
  553. * @param integer Requested HTTP code
  554. * @param string HTTP headers
  555. * @return boolean True if match
  556. * @throws KerioApiException
  557. */
  558. protected function checkHttpResponse($code, $headers) {
  559. preg_match('#HTTP/\d+\.\d+ (\d+) (.+)#', $headers, $result);
  560. switch ($result[1]) {
  561. case $code:
  562. return TRUE;
  563. default:
  564. $remote = sprintf('https://%s:%d%s', $this->hostname, $this->jsonRpc['port'], $this->jsonRpc['api']);
  565. throw new KerioApiException(sprintf('%d - %s on remote server %s', $result[1], $result[2], $remote));
  566. }
  567. }
  568. /**
  569. * Set hostname.
  570. *
  571. * @param string Hostname
  572. * @return void
  573. */
  574. public function setHostname($hostname) {
  575. $hostname = preg_split('/:/', $hostname);
  576. $this->hostname = $hostname[0];
  577. if (isset($hostname[1])) {
  578. $this->setJsonRpc($this->jsonRpc['version'], $hostname[1], $this->jsonRpc['api']);
  579. }
  580. }
  581. /**
  582. * Get request ID.
  583. *
  584. * @param void
  585. * @return integer
  586. */
  587. private function getRequestId() {
  588. $this->requestId++;
  589. return $this->requestId;
  590. }
  591. /**
  592. * Set security Cross-Site Request Forgery X-Token.
  593. *
  594. * @param string X-Token value
  595. * @return void
  596. */
  597. protected function setToken($token) {
  598. $this->debug(sprintf('Setting X-Token %s.', $token));
  599. $this->token = $token;
  600. }
  601. /**
  602. * Get security Cross-Site Request Forgery X-Token.
  603. *
  604. * @param void
  605. * @return string X-Token value
  606. */
  607. public function getToken() {
  608. return $this->token;
  609. }
  610. /**
  611. * Set Cookies.
  612. *
  613. * @param string Cookies
  614. * @return void
  615. */
  616. protected function setCookie($cookies) {
  617. $this->cookies = $cookies;
  618. }
  619. /**
  620. * Get Cookies.
  621. *
  622. * @param void
  623. * @return string Cookies
  624. */
  625. public function getCookie() {
  626. return $this->cookies;
  627. }
  628. /**
  629. * Set Cookie from response.
  630. *
  631. * @param string HTTP headers
  632. * @return void
  633. */
  634. private function setCookieFromHeaders($headers) {
  635. foreach (explode("\n", $headers) as $line) {
  636. if (preg_match_all('/Set-Cookie:\s(\w*)=(\w*)/', $line, $result)) {
  637. foreach ($result[1] as $index => $cookie) {
  638. $this->debug(sprintf('Setting %s=%s.', $cookie, $result[2][$index]));
  639. $this->setCookie(sprintf('%s %s=%s;', $this->getCookie(), $cookie, $result[2][$index]));
  640. }
  641. }
  642. }
  643. }
  644. /**
  645. * Set connection timeout.
  646. *
  647. * @param integer Timeout in seconds
  648. * @return void
  649. */
  650. public function setTimeout($timeout) {
  651. $this->timeout = (integer) $timeout;
  652. }
  653. }