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

/public/wp-content/plugins/wp-statistics/vendor/maxmind/web-service-common/src/WebService/Client.php

https://gitlab.com/kath.de/cibedo_cibedo.de
PHP | 427 lines | 290 code | 36 blank | 101 comment | 29 complexity | 9b89007d29d7d070b0308c346367dfed MD5 | raw file
  1. <?php
  2. namespace MaxMind\WebService;
  3. use MaxMind\Exception\AuthenticationException;
  4. use MaxMind\Exception\HttpException;
  5. use MaxMind\Exception\InsufficientFundsException;
  6. use MaxMind\Exception\InvalidInputException;
  7. use MaxMind\Exception\InvalidRequestException;
  8. use MaxMind\Exception\IpAddressNotFoundException;
  9. use MaxMind\Exception\WebServiceException;
  10. use MaxMind\WebService\Http\RequestFactory;
  11. /**
  12. * This class is not intended to be used directly by an end-user of a
  13. * MaxMind web service. Please use the appropriate client API for the service
  14. * that you are using.
  15. * @package MaxMind\WebService
  16. * @internal
  17. */
  18. class Client
  19. {
  20. const VERSION = '0.0.1';
  21. private $userId;
  22. private $licenseKey;
  23. private $userAgentPrefix;
  24. private $host = 'api.maxmind.com';
  25. private $httpRequestFactory;
  26. private $timeout;
  27. private $connectTimeout;
  28. private $caBundle;
  29. /**
  30. * @param int $userId Your MaxMind user ID
  31. * @param string $licenseKey Your MaxMind license key
  32. * @param array $options An array of options. Possible keys:
  33. *
  34. * * `host` - The host to use when connecting to the web service.
  35. * * `userAgent` - The prefix of the User-Agent to use in the request.
  36. * * `caBundle` - The bundle of CA root certificates to use in the request.
  37. * * `connectTimeout` - The connect timeout to use for the request.
  38. * * `timeout` - The timeout to use for the request.
  39. */
  40. public function __construct(
  41. $userId,
  42. $licenseKey,
  43. $options = array()
  44. ) {
  45. $this->userId = $userId;
  46. $this->licenseKey = $licenseKey;
  47. $this->httpRequestFactory = isset($options['httpRequestFactory'])
  48. ? $options['httpRequestFactory']
  49. : new RequestFactory();
  50. if (isset($options['host'])) {
  51. $this->host = $options['host'];
  52. }
  53. if (isset($options['userAgent'])) {
  54. $this->userAgentPrefix = $options['userAgent'] . ' ';
  55. }
  56. $this->caBundle = isset($options['caBundle']) ?
  57. $this->caBundle = $options['caBundle'] : $this->getCaBundle();
  58. if (isset($options['connectTimeout'])) {
  59. $this->connectTimeout = $options['connectTimeout'];
  60. }
  61. if (isset($options['timeout'])) {
  62. $this->timeout = $options['timeout'];
  63. }
  64. }
  65. /**
  66. * @param string $service name of the service querying
  67. * @param string $path the URI path to use
  68. * @param array $input the data to be posted as JSON
  69. * @return array The decoded content of a successful response
  70. * @throws InvalidInputException when the request has missing or invalid
  71. * data.
  72. * @throws AuthenticationException when there is an issue authenticating the
  73. * request.
  74. * @throws InsufficientFundsException when your account is out of funds.
  75. * @throws InvalidRequestException when the request is invalid for some
  76. * other reason, e.g., invalid JSON in the POST.
  77. * @throws HttpException when an unexpected HTTP error occurs.
  78. * @throws WebServiceException when some other error occurs. This also
  79. * serves as the base class for the above exceptions.
  80. */
  81. public function post($service, $path, $input)
  82. {
  83. $body = json_encode($input);
  84. if ($body === false) {
  85. throw new InvalidInputException(
  86. 'Error encoding input as JSON: '
  87. . $this->jsonErrorDescription()
  88. );
  89. }
  90. $request = $this->createRequest(
  91. $path,
  92. array('Content-Type: application/json')
  93. );
  94. list($statusCode, $contentType, $body) = $request->post($body);
  95. return $this->handleResponse(
  96. $statusCode,
  97. $contentType,
  98. $body,
  99. $service,
  100. $path
  101. );
  102. }
  103. public function get($service, $path)
  104. {
  105. $request = $this->createRequest($path);
  106. list($statusCode, $contentType, $body) = $request->get();
  107. return $this->handleResponse(
  108. $statusCode,
  109. $contentType,
  110. $body,
  111. $service,
  112. $path
  113. );
  114. }
  115. private function userAgent()
  116. {
  117. $curlVersion = curl_version();
  118. return $this->userAgentPrefix . 'MaxMind-WS-API/' . Client::VERSION . ' PHP/' . PHP_VERSION .
  119. ' curl/' . $curlVersion['version'];
  120. }
  121. private function createRequest($path, $headers = array())
  122. {
  123. array_push(
  124. $headers,
  125. 'Authorization: Basic '
  126. . base64_encode($this->userId . ':' . $this->licenseKey),
  127. 'Accept: application/json'
  128. );
  129. return $this->httpRequestFactory->request(
  130. $this->urlFor($path),
  131. array(
  132. 'caBundle' => $this->caBundle,
  133. 'headers' => $headers,
  134. 'userAgent' => $this->userAgent(),
  135. 'connectTimeout' => $this->connectTimeout,
  136. 'timeout' => $this->timeout,
  137. )
  138. );
  139. }
  140. /**
  141. * @param integer $statusCode the HTTP status code of the response
  142. * @param string $contentType the Content-Type of the response
  143. * @param string $body the response body
  144. * @param string $service the name of the service
  145. * @param string $path the path used in the request
  146. * @return array The decoded content of a successful response
  147. * @throws AuthenticationException when there is an issue authenticating the
  148. * request.
  149. * @throws InsufficientFundsException when your account is out of funds.
  150. * @throws InvalidRequestException when the request is invalid for some
  151. * other reason, e.g., invalid JSON in the POST.
  152. * @throws HttpException when an unexpected HTTP error occurs.
  153. * @throws WebServiceException when some other error occurs. This also
  154. * serves as the base class for the above exceptions
  155. */
  156. private function handleResponse(
  157. $statusCode,
  158. $contentType,
  159. $body,
  160. $service,
  161. $path
  162. ) {
  163. if ($statusCode >= 400 && $statusCode <= 499) {
  164. $this->handle4xx($statusCode, $contentType, $body, $service, $path);
  165. } elseif ($statusCode >= 500) {
  166. $this->handle5xx($statusCode, $service, $path);
  167. } elseif ($statusCode != 200) {
  168. $this->handleUnexpectedStatus($statusCode, $service, $path);
  169. }
  170. return $this->handleSuccess($body, $service);
  171. }
  172. /**
  173. * @return string describing the JSON error
  174. */
  175. private function jsonErrorDescription()
  176. {
  177. $errno = json_last_error();
  178. switch ($errno) {
  179. case JSON_ERROR_DEPTH:
  180. return 'The maximum stack depth has been exceeded.';
  181. case JSON_ERROR_STATE_MISMATCH:
  182. return 'Invalid or malformed JSON.';
  183. case JSON_ERROR_CTRL_CHAR:
  184. return 'Control character error.';
  185. case JSON_ERROR_SYNTAX:
  186. return 'Syntax error.';
  187. case JSON_ERROR_UTF8:
  188. return 'Malformed UTF-8 characters.';
  189. default:
  190. return "Other JSON error ($errno).";
  191. }
  192. }
  193. /**
  194. * @param string $path The path to use in the URL
  195. * @return string The constructed URL
  196. */
  197. private function urlFor($path)
  198. {
  199. return 'https://' . $this->host . $path;
  200. }
  201. /**
  202. * @param int $statusCode The HTTP status code
  203. * @param string $contentType The response content-type
  204. * @param string $body The response body
  205. * @param string $service The service name
  206. * @param string $path The path used in the request
  207. * @throws AuthenticationException
  208. * @throws HttpException
  209. * @throws InsufficientFundsException
  210. * @throws InvalidRequestException
  211. */
  212. private function handle4xx(
  213. $statusCode,
  214. $contentType,
  215. $body,
  216. $service,
  217. $path
  218. ) {
  219. if (strlen($body) === 0) {
  220. throw new HttpException(
  221. "Received a $statusCode error for $service with no body",
  222. $statusCode,
  223. $this->urlFor($path)
  224. );
  225. }
  226. if (!strstr($contentType, 'json')) {
  227. throw new HttpException(
  228. "Received a $statusCode error for $service with " .
  229. "the following body: " . $body,
  230. $statusCode,
  231. $this->urlFor($path)
  232. );
  233. }
  234. $message = json_decode($body, true);
  235. if ($message === null) {
  236. throw new HttpException(
  237. "Received a $statusCode error for $service but could " .
  238. 'not decode the response as JSON: '
  239. . $this->jsonErrorDescription() . ' Body: ' . $body,
  240. $statusCode,
  241. $this->urlFor($path)
  242. );
  243. }
  244. if (!isset($message['code']) || !isset($message['error'])) {
  245. throw new HttpException(
  246. 'Error response contains JSON but it does not ' .
  247. 'specify code or error keys: ' . $body,
  248. $statusCode,
  249. $this->urlFor($path)
  250. );
  251. }
  252. $this->handleWebServiceError(
  253. $message['error'],
  254. $message['code'],
  255. $statusCode,
  256. $path
  257. );
  258. }
  259. /**
  260. * @param string $message The error message from the web service
  261. * @param string $code The error code from the web service
  262. * @param int $statusCode The HTTP status code
  263. * @param string $path The path used in the request
  264. * @throws AuthenticationException
  265. * @throws InvalidRequestException
  266. * @throws InsufficientFundsException
  267. */
  268. private function handleWebServiceError(
  269. $message,
  270. $code,
  271. $statusCode,
  272. $path
  273. ) {
  274. switch ($code) {
  275. case 'IP_ADDRESS_NOT_FOUND':
  276. case 'IP_ADDRESS_RESERVED':
  277. throw new IpAddressNotFoundException(
  278. $message,
  279. $code,
  280. $statusCode,
  281. $this->urlFor($path)
  282. );
  283. case 'AUTHORIZATION_INVALID':
  284. case 'LICENSE_KEY_REQUIRED':
  285. case 'USER_ID_REQUIRED':
  286. throw new AuthenticationException(
  287. $message,
  288. $code,
  289. $statusCode,
  290. $this->urlFor($path)
  291. );
  292. case 'OUT_OF_QUERIES':
  293. case 'INSUFFICIENT_FUNDS':
  294. throw new InsufficientFundsException(
  295. $message,
  296. $code,
  297. $statusCode,
  298. $this->urlFor($path)
  299. );
  300. default:
  301. throw new InvalidRequestException(
  302. $message,
  303. $code,
  304. $statusCode,
  305. $this->urlFor($path)
  306. );
  307. }
  308. }
  309. /**
  310. * @param int $statusCode The HTTP status code
  311. * @param string $service The service name
  312. * @param string $path The URI path used in the request
  313. * @throws HttpException
  314. */
  315. private function handle5xx($statusCode, $service, $path)
  316. {
  317. throw new HttpException(
  318. "Received a server error ($statusCode) for $service",
  319. $statusCode,
  320. $this->urlFor($path)
  321. );
  322. }
  323. /**
  324. * @param int $statusCode The HTTP status code
  325. * @param string $service The service name
  326. * @param string $path The URI path used in the request
  327. * @throws HttpException
  328. */
  329. private function handleUnexpectedStatus($statusCode, $service, $path)
  330. {
  331. throw new HttpException(
  332. 'Received an unexpected HTTP status ' .
  333. "($statusCode) for $service",
  334. $statusCode,
  335. $this->urlFor($path)
  336. );
  337. }
  338. /**
  339. * @param string $body The successful request body
  340. * @param string $service The service name
  341. * @return array The decoded request body
  342. * @throws WebServiceException if the request body cannot be decoded as
  343. * JSON
  344. */
  345. private function handleSuccess($body, $service)
  346. {
  347. if (strlen($body) == 0) {
  348. throw new WebServiceException(
  349. "Received a 200 response for $service but did not " .
  350. "receive a HTTP body."
  351. );
  352. }
  353. $decodedContent = json_decode($body, true);
  354. if ($decodedContent === null) {
  355. throw new WebServiceException(
  356. "Received a 200 response for $service but could " .
  357. 'not decode the response as JSON: '
  358. . $this->jsonErrorDescription() . ' Body: ' . $body
  359. );
  360. }
  361. return $decodedContent;
  362. }
  363. private function getCaBundle()
  364. {
  365. $cert = __DIR__ . '/cacert.pem';
  366. // Check if we are inside a phar. If so, we need to copy the cert to a
  367. // temp file so that curl can see it.
  368. if (substr($cert, 0, 7) == 'phar://') {
  369. $newCert = tempnam(sys_get_temp_dir(), 'geoip2-');
  370. if (!copy($cert, $newCert)) {
  371. throw new \RuntimeException(
  372. "Could not copy $cert to $newCert: "
  373. . var_export(error_get_last(), true)
  374. );
  375. }
  376. // We use a shutdown function rather than the destructor as the
  377. // destructor isn't called on a fatal error such as an uncaught
  378. // exception.
  379. register_shutdown_function(
  380. function () use ($newCert) {
  381. unlink($newCert);
  382. }
  383. );
  384. $cert = $newCert;
  385. }
  386. if (!file_exists($cert)) {
  387. throw new \RuntimeException("CA cert does not exist at $cert");
  388. }
  389. return $cert;
  390. }
  391. }