KerioApi.php 18 KB

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