PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/modules/Dropbox/RequestUtil.php

https://gitlab.com/x33n/ampache
PHP | 301 lines | 168 code | 39 blank | 94 comment | 29 complexity | 34e02972f318c99d1a2247bcbb5eeda7 MD5 | raw file
  1. <?php
  2. namespace Dropbox;
  3. if (!function_exists('curl_init')) {
  4. throw new \Exception("The Dropbox SDK requires the cURL PHP extension, but it looks like you don't have it (couldn't find function \"curl_init\"). Library: \"" . __FILE__ . "\".");
  5. }
  6. if (!function_exists('json_decode')) {
  7. throw new \Exception("The Dropbox SDK requires the JSON PHP extension, but it looks like you don't have it (couldn't find function \"json_decode\"). Library: \"" . __FILE__ . "\".");
  8. }
  9. // If mbstring.func_overload is set, it changes the behavior of the standard string functions in
  10. // ways that makes this library break.
  11. $mbstring_func_overload = ini_get("mbstring.func_overload");
  12. if ($mbstring_func_overload & 2 == 2) {
  13. throw new \Exception("The Dropbox SDK doesn't work when mbstring.func_overload is set to overload the standard string functions (value = ".var_export($mbstring_func_overload, true)."). Library: \"" . __FILE__ . "\".");
  14. }
  15. if (strlen((string) PHP_INT_MAX) < 19) {
  16. // Looks like we're running on a 32-bit build of PHP. This could cause problems because some of the numbers
  17. // we use (file sizes, quota, etc) can be larger than 32-bit ints can handle.
  18. throw new \Exception("The Dropbox SDK uses 64-bit integers, but it looks like we're running on a version of PHP that doesn't support 64-bit integers (PHP_INT_MAX=" . ((string) PHP_INT_MAX) . "). Library: \"" . __FILE__ . "\"");
  19. }
  20. /**
  21. * @internal
  22. */
  23. final class RequestUtil
  24. {
  25. /**
  26. * @param string $userLocale
  27. * @param string $host
  28. * @param string $path
  29. * @param array $params
  30. * @return string
  31. */
  32. static function buildUrlForGetOrPut($userLocale, $host, $path, $params = null)
  33. {
  34. $url = self::buildUri($host, $path);
  35. $url .= "?locale=" . rawurlencode($userLocale);
  36. if ($params !== null) {
  37. foreach ($params as $key => $value) {
  38. Checker::argStringNonEmpty("key in 'params'", $key);
  39. if ($value !== null) {
  40. if (is_bool($value)) {
  41. $value = $value ? "true" : "false";
  42. }
  43. else if (is_int($value)) {
  44. $value = (string) $value;
  45. }
  46. else if (!is_string($value)) {
  47. throw new \InvalidArgumentException("params['$key'] is not a string, int, or bool");
  48. }
  49. $url .= "&" . rawurlencode($key) . "=" . rawurlencode($value);
  50. }
  51. }
  52. }
  53. return $url;
  54. }
  55. /**
  56. * @param string $host
  57. * @param string $path
  58. * @return string
  59. */
  60. static function buildUri($host, $path)
  61. {
  62. Checker::argStringNonEmpty("host", $host);
  63. Checker::argStringNonEmpty("path", $path);
  64. return "https://" . $host . "/" . $path;
  65. }
  66. /**
  67. * @param string $clientIdentifier
  68. * @param string $url
  69. * @return Curl
  70. */
  71. static function mkCurl($clientIdentifier, $url)
  72. {
  73. $curl = new Curl($url);
  74. $curl->set(CURLOPT_CONNECTTIMEOUT, 10);
  75. // If the transfer speed is below 1kB/sec for 10 sec, abort.
  76. $curl->set(CURLOPT_LOW_SPEED_LIMIT, 1024);
  77. $curl->set(CURLOPT_LOW_SPEED_TIME, 10);
  78. //$curl->set(CURLOPT_VERBOSE, true); // For debugging.
  79. // TODO: Figure out how to encode clientIdentifier (urlencode?)
  80. $curl->addHeader("User-Agent: ".$clientIdentifier." Dropbox-PHP-SDK");
  81. return $curl;
  82. }
  83. /**
  84. * @param string $clientIdentifier
  85. * @param string $url
  86. * @param string $authHeaderValue
  87. * @return Curl
  88. */
  89. static function mkCurlWithAuth($clientIdentifier, $url, $authHeaderValue)
  90. {
  91. $curl = self::mkCurl($clientIdentifier, $url);
  92. $curl->addHeader("Authorization: $authHeaderValue");
  93. return $curl;
  94. }
  95. /**
  96. * @param string $clientIdentifier
  97. * @param string $url
  98. * @param string $accessToken
  99. * @return Curl
  100. */
  101. static function mkCurlWithOAuth($clientIdentifier, $url, $accessToken)
  102. {
  103. return self::mkCurlWithAuth($clientIdentifier, $url, "Bearer $accessToken");
  104. }
  105. static function buildPostBody($params)
  106. {
  107. if ($params === null) return "";
  108. $pairs = array();
  109. foreach ($params as $key => $value) {
  110. Checker::argStringNonEmpty("key in 'params'", $key);
  111. if ($value !== null) {
  112. if (is_bool($value)) {
  113. $value = $value ? "true" : "false";
  114. }
  115. else if (is_int($value)) {
  116. $value = (string) $value;
  117. }
  118. else if (!is_string($value)) {
  119. throw new \InvalidArgumentException("params['$key'] is not a string, int, or bool");
  120. }
  121. $pairs[] = rawurlencode($key) . "=" . rawurlencode((string) $value);
  122. }
  123. }
  124. return implode("&", $pairs);
  125. }
  126. /**
  127. * @param string $clientIdentifier
  128. * @param string $accessToken
  129. * @param string $userLocale
  130. * @param string $host
  131. * @param string $path
  132. * @param array|null $params
  133. *
  134. * @return HttpResponse
  135. *
  136. * @throws Exception
  137. */
  138. static function doPost($clientIdentifier, $accessToken, $userLocale, $host, $path, $params = null)
  139. {
  140. Checker::argStringNonEmpty("accessToken", $accessToken);
  141. $url = self::buildUri($host, $path);
  142. if ($params === null) $params = array();
  143. $params['locale'] = $userLocale;
  144. $curl = self::mkCurlWithOAuth($clientIdentifier, $url, $accessToken);
  145. $curl->set(CURLOPT_POST, true);
  146. $curl->set(CURLOPT_POSTFIELDS, self::buildPostBody($params));
  147. $curl->set(CURLOPT_RETURNTRANSFER, true);
  148. return $curl->exec();
  149. }
  150. /**
  151. * @param string $clientIdentifier
  152. * @param string $authHeaderValue
  153. * @param string $userLocale
  154. * @param string $host
  155. * @param string $path
  156. * @param array|null $params
  157. *
  158. * @return HttpResponse
  159. *
  160. * @throws Exception
  161. */
  162. static function doPostWithSpecificAuth($clientIdentifier, $authHeaderValue, $userLocale, $host, $path, $params = null)
  163. {
  164. Checker::argStringNonEmpty("authHeaderValue", $authHeaderValue);
  165. $url = self::buildUri($host, $path);
  166. if ($params === null) $params = array();
  167. $params['locale'] = $userLocale;
  168. $curl = self::mkCurlWithAuth($clientIdentifier, $url, $authHeaderValue);
  169. $curl->set(CURLOPT_POST, true);
  170. $curl->set(CURLOPT_POSTFIELDS, self::buildPostBody($params));
  171. $curl->set(CURLOPT_RETURNTRANSFER, true);
  172. return $curl->exec();
  173. }
  174. /**
  175. * @param string $clientIdentifier
  176. * @param string $accessToken
  177. * @param string $userLocale
  178. * @param string $host
  179. * @param string $path
  180. * @param array|null $params
  181. *
  182. * @return HttpResponse
  183. *
  184. * @throws Exception
  185. */
  186. static function doGet($clientIdentifier, $accessToken, $userLocale, $host, $path, $params = null)
  187. {
  188. Checker::argStringNonEmpty("accessToken", $accessToken);
  189. $url = self::buildUrlForGetOrPut($userLocale, $host, $path, $params);
  190. $curl = self::mkCurlWithOAuth($clientIdentifier, $url, $accessToken);
  191. $curl->set(CURLOPT_HTTPGET, true);
  192. $curl->set(CURLOPT_RETURNTRANSFER, true);
  193. return $curl->exec();
  194. }
  195. /**
  196. * @param string $responseBody
  197. * @return mixed
  198. * @throws Exception_BadResponse
  199. */
  200. static function parseResponseJson($responseBody)
  201. {
  202. $obj = json_decode($responseBody, true, 10);
  203. if ($obj === null) {
  204. throw new Exception_BadResponse("Got bad JSON from server: $responseBody");
  205. }
  206. return $obj;
  207. }
  208. static function unexpectedStatus($httpResponse)
  209. {
  210. $sc = $httpResponse->statusCode;
  211. $message = "HTTP status $sc";
  212. if (is_string($httpResponse->body)) {
  213. // TODO: Maybe only include the first ~200 chars of the body?
  214. $message .= "\n".$httpResponse->body;
  215. }
  216. if ($sc === 400) return new Exception_BadRequest($message);
  217. if ($sc === 401) return new Exception_InvalidAccessToken($message);
  218. if ($sc === 500 || $sc === 502) return new Exception_ServerError($message);
  219. if ($sc === 503) return new Exception_RetryLater($message);
  220. return new Exception_BadResponseCode("Unexpected $message", $sc);
  221. }
  222. /**
  223. * @param int $maxRetries
  224. * The number of times to retry it the action if it fails with one of the transient
  225. * API errors. A value of 1 means we'll try the action once and if it fails, we
  226. * will retry once.
  227. *
  228. * @param callable $action
  229. * The the action you want to retry.
  230. *
  231. * @return mixed
  232. * Whatever is returned by the $action callable.
  233. */
  234. static function runWithRetry($maxRetries, $action)
  235. {
  236. Checker::argNat("maxRetries", $maxRetries);
  237. $retryDelay = 1;
  238. $numRetries = 0;
  239. while (true) {
  240. try {
  241. return $action();
  242. }
  243. // These exception types are the ones we think are possibly transient errors.
  244. catch (Exception_NetworkIO $ex) {
  245. $savedEx = $ex;
  246. }
  247. catch (Exception_ServerError $ex) {
  248. $savedEx = $ex;
  249. }
  250. catch (Exception_RetryLater $ex) {
  251. $savedEx = $ex;
  252. }
  253. // We maxed out our retries. Propagate the last exception we got.
  254. if ($numRetries >= $maxRetries) throw $savedEx;
  255. $numRetries++;
  256. sleep($retryDelay);
  257. $retryDelay *= 2; // Exponential back-off.
  258. }
  259. throw new \RuntimeException("unreachable");
  260. }
  261. }