Client.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. <?php
  2. namespace Gogs\Lib\Curl {
  3. /**
  4. * A trait used for every class referencing the api-url and token.
  5. *
  6. * @author Joachim M. Giaever (joachim[]giaever.org)
  7. * @package curl
  8. * @version 0.1.3
  9. */
  10. trait Client {
  11. private $_version = "0.1.3";
  12. private static $log = array();
  13. protected $url;
  14. protected $token;
  15. protected $basic = false;
  16. protected $user_agent = "Gogs PHP API Client/%s (%s) PHP/%s Client\\%s %s";
  17. protected $timeout = 30;
  18. protected $max_redirects = 4;
  19. /**
  20. * Basic sets the user for basic HTTP-authentication.
  21. *
  22. * @param string $user
  23. */
  24. protected function basic(string $user) {
  25. $this->basic = $user;
  26. }
  27. /**
  28. * Set param into array
  29. *
  30. * The specified callback will only run if the expected
  31. * parameter is set. This callback can either overwrite
  32. * paramtere as passing them as reference or throw an exception
  33. * to indicate invalid data.
  34. *
  35. * @param array &$params Array to insert to
  36. * @param string $param_name Index in params-array
  37. * @param array $args Arguments array
  38. * @param int $index Index in arguments array
  39. * @param string $type Expected type of data
  40. * @param mixed $default Default if not expected type on index
  41. * @param callback $f Callback method if param is set
  42. */
  43. protected function set_param(array &$params, string $param_name, array $args, int $index, string $type, $default = null, callable $f = null) {
  44. switch ($type) {
  45. case "str":
  46. $type = "string";
  47. break;
  48. case "int":
  49. $type = "integer";
  50. break;
  51. case "float":
  52. $type = "double";
  53. break;
  54. case "bool":
  55. $type = "boolean";
  56. break;
  57. }
  58. $type = $type == "bool" ? "boolean" : ($type == "float" ? "double" : $type);
  59. $params[$param_name] = isset($args[$index]) && gettype($args[$index]) == $type ? $args[$index] : $default;
  60. if ($f != null && $params[$param_name] != $default)
  61. $f($params[$param_name]);
  62. }
  63. /**
  64. * Filter out NULL values from parameters.
  65. *
  66. * Saves transferring size.
  67. *
  68. * @param array &$params Parameters
  69. */
  70. protected function filter_params(array &$params) {
  71. $params = array_filter($params, function($val) {
  72. return $val != null;
  73. });
  74. }
  75. /**
  76. * array_2_params takes an array and converts it into a
  77. * query string (e.g param=val&param2=val2).
  78. *
  79. * @param array $params parameters to pass
  80. * @return string
  81. */
  82. private function array_2_params(array $params) {
  83. return join("&", array_map(function($k, $v) {
  84. return sprintf("%s=%s", $k, rawurlencode(is_bool($v) ? ($v ? "true" : "false") : $v ));
  85. }, array_keys($params), $params));
  86. }
  87. /**
  88. * array_2_json takes an array and converts it into a
  89. * json-string (e.g {'name': 'This'}) which is typically
  90. * used in a request body.
  91. *
  92. * @param array $params paramters to pass
  93. * @return string
  94. */
  95. private function array_2_json(array $params) {
  96. return count($params) == 0 ? null : json_encode($params);
  97. }
  98. /**
  99. * Initializes a curl request of different kinds, depending
  100. * on the specified method. This can be
  101. *
  102. * DELETE, PATCH, POST or GET. An unidentified value will
  103. * become a GET-request.
  104. *
  105. * @param string $method either DELETE, PATCH, POST, GET
  106. * @param string &$req variable to store request body in
  107. * @param string $scope scope within the API (e.g /user/repos)
  108. * @param array $params parameters to pass
  109. * @param bool $ret return transfer
  110. * @return int the status code
  111. */
  112. protected function method(string $method, string &$req, string $scope, array $params, bool $ret) {
  113. $c = curl_init();
  114. if (!$c) {
  115. return false;
  116. }
  117. $headers = array();
  118. $url = sprintf("%s%s", $this->url, $scope);
  119. curl_setopt($c, CURLOPT_USERAGENT, $agent = sprintf($this->user_agent, $this->_version, PHP_OS, phpversion(), get_class($this), self::VERSION));
  120. self::$log[] = sprintf(
  121. "%s:[%s] %s, %s, %s",
  122. date("y-m-d H:i:s"),
  123. $method,
  124. $url,
  125. !empty($p = $this->array_2_json($params)) ? $p : "none",
  126. $agent
  127. );
  128. if (!$this->basic)
  129. $headers[] = sprintf("Authorization: token %s", $this->token);
  130. else
  131. curl_setopt($c, CURLOPT_USERPWD, sprintf("%s:%s", $this->basic, $this->token));
  132. if (in_array($method, array("DELETE", "PATCH", "POST"))) {
  133. $json = $this->array_2_json($params);
  134. curl_setopt($c, CURLOPT_CUSTOMREQUEST, $method);
  135. curl_setopt($c, CURLOPT_POSTFIELDS, $json);
  136. array_unshift($headers, "Content-Type: application/json");
  137. array_push($headers, "Content-Length: " . strlen($json));
  138. } else {
  139. $url .= !empty($params = $this->array_2_params($params)) ? "?" . $params : "";
  140. }
  141. curl_setopt($c, CURLOPT_URL, $url);
  142. curl_setopt($c, CURLOPT_HTTPHEADER, $headers);
  143. curl_setopt($c, CURLOPT_RETURNTRANSFER, $ret);
  144. curl_setopt($c, CURLOPT_TIMEOUT, $this->timeout);
  145. curl_setopt($c, CURLOPT_MAXREDIRS, $this->max_redirects);
  146. curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
  147. $req = curl_exec($c);
  148. $status_code = curl_getinfo($c, CURLINFO_HTTP_CODE);
  149. curl_close($c);
  150. array_push(
  151. self::$log,
  152. sprintf(
  153. "%s:[%s] %s, %d, %s",
  154. date("y-m-d H:i:s"),
  155. $method,
  156. $url,
  157. $status_code,
  158. substr($req, 0, 100) . (strlen($req) > 100 ? "..." : ".")
  159. )
  160. );
  161. return $status_code;
  162. }
  163. /**
  164. * Checks if the user is authorized for the scope. Shouldn't
  165. * be used frequently. One test for one scope should be enough,
  166. * but if you know for sure thats you're programming with the
  167. * use of an authorized user you should leave this and just
  168. * handle the NotAuthorizedExeption whenever thrown.
  169. *
  170. * @param $scope the scope, a relative uri.
  171. * @throws Not AuthorizedException if server responde with a 401
  172. * @return bool
  173. */
  174. protected function authorized(string $scope = "") {
  175. $ret = "";
  176. if (in_array(($code = $this->method("GET", $ret, $scope, array(), false)),
  177. array(400, 401, 402, 403)
  178. )) {
  179. throw new NotAuthorizedException("Not authorized", 401);
  180. }
  181. return true;
  182. }
  183. /**
  184. * Post method.
  185. *
  186. * @param string $scope the scope, a relative uri.
  187. * @param array $params the parameters to post.
  188. * @throws NotAuthorizedException on 401, 403
  189. * @throws HTTPUnexpectedResponse when not 200,201,401,403
  190. * @return string the request content.
  191. */
  192. private function post(string $scope = "", array $params = array()) {
  193. $req = "";
  194. $code = $this->method("POST", $req, $scope, $params, true);
  195. switch ($code) {
  196. case 200:
  197. case 201:
  198. return $req;
  199. case 400:
  200. case 401:
  201. case 403:
  202. throw new Exception\NotAuthorizedException($req, $code);
  203. default:
  204. throw new Exception\HTTPUnexpectedResponse($req, $code);
  205. }
  206. }
  207. /**
  208. * Delete method.
  209. *
  210. * @param string $scope the scope, a relative uri.
  211. * @throws NotAuthorizedException on 401, 403
  212. * @throws HTTPUnexpectedResponse when not 200,204,401,403
  213. * @return string the request content.
  214. */
  215. private function delete(string $scope = "") {
  216. $req = "";
  217. $code = $this->method("DELETE", $req, $scope, array(), true);
  218. switch ($code) {
  219. case 200:
  220. case 204:
  221. return true;
  222. case 401:
  223. case 403:
  224. throw new Exception\NotAuthorizedException($req, $code);
  225. default:
  226. throw new Exception\HTTPUnexpectedResponse($req, $code);
  227. }
  228. }
  229. /**
  230. * GET method.
  231. *
  232. * @param string $scope the scope, a relative uri.
  233. * @param array $params the parameters to post.
  234. * @throws NotAuthorizedException on 401, 403
  235. * @throws HTTPUnexpectedResponse when not 200,401,403
  236. * @return string the request content.
  237. */
  238. private function get($scope = "", $params = array()) {
  239. $req = "";
  240. $code = $this->method("GET", $req, $scope, $params, true);
  241. switch ($code) {
  242. case 200:
  243. return $req;
  244. case 401:
  245. case 403:
  246. throw new Exception\NotAuthorizedException($req, $code);
  247. default:
  248. throw new Exception\HTTPUnexpectedResponse($req, $code);
  249. }
  250. }
  251. /**
  252. * Returns log entries for the client.
  253. *
  254. * @return array
  255. */
  256. public static function get_log() {
  257. if (empty(self::$log))
  258. return self::$log;
  259. return array_merge(self::$log, array("\n"));
  260. }
  261. }
  262. }
  263. ?>