From 19985dbb8c0aa66dc4bf7905abc1148de909097d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Luka=20=C5=A0ijanec?= Date: Tue, 11 Jan 2022 12:35:47 +0100 Subject: prvi-commit --- .../web-service-common/src/WebService/Client.php | 488 +++++++++++++++++++++ .../src/WebService/Http/CurlRequest.php | 136 ++++++ .../src/WebService/Http/Request.php | 29 ++ .../src/WebService/Http/RequestFactory.php | 49 +++ 4 files changed, 702 insertions(+) create mode 100644 vendor/maxmind/web-service-common/src/WebService/Client.php create mode 100644 vendor/maxmind/web-service-common/src/WebService/Http/CurlRequest.php create mode 100644 vendor/maxmind/web-service-common/src/WebService/Http/Request.php create mode 100644 vendor/maxmind/web-service-common/src/WebService/Http/RequestFactory.php (limited to 'vendor/maxmind/web-service-common/src/WebService') diff --git a/vendor/maxmind/web-service-common/src/WebService/Client.php b/vendor/maxmind/web-service-common/src/WebService/Client.php new file mode 100644 index 0000000..98e6e3b --- /dev/null +++ b/vendor/maxmind/web-service-common/src/WebService/Client.php @@ -0,0 +1,488 @@ +accountId = $accountId; + $this->licenseKey = $licenseKey; + + $this->httpRequestFactory = isset($options['httpRequestFactory']) + ? $options['httpRequestFactory'] + : new RequestFactory(); + + if (isset($options['host'])) { + $this->host = $options['host']; + } + if (isset($options['userAgent'])) { + $this->userAgentPrefix = $options['userAgent'] . ' '; + } + + $this->caBundle = isset($options['caBundle']) ? + $this->caBundle = $options['caBundle'] : $this->getCaBundle(); + + if (isset($options['connectTimeout'])) { + $this->connectTimeout = $options['connectTimeout']; + } + if (isset($options['timeout'])) { + $this->timeout = $options['timeout']; + } + + if (isset($options['proxy'])) { + $this->proxy = $options['proxy']; + } + } + + /** + * @param string $service name of the service querying + * @param string $path the URI path to use + * @param array $input the data to be posted as JSON + * + * @throws InvalidInputException when the request has missing or invalid + * data + * @throws AuthenticationException when there is an issue authenticating the + * request + * @throws InsufficientFundsException when your account is out of funds + * @throws InvalidRequestException when the request is invalid for some + * other reason, e.g., invalid JSON in the POST. + * @throws HttpException when an unexpected HTTP error occurs + * @throws WebServiceException when some other error occurs. This also + * serves as the base class for the above exceptions. + * + * @return array The decoded content of a successful response + */ + public function post($service, $path, $input) + { + $requestBody = json_encode($input); + if ($requestBody === false) { + throw new InvalidInputException( + 'Error encoding input as JSON: ' + . $this->jsonErrorDescription() + ); + } + + $request = $this->createRequest( + $path, + ['Content-Type: application/json'] + ); + + list($statusCode, $contentType, $responseBody) = $request->post($requestBody); + + return $this->handleResponse( + $statusCode, + $contentType, + $responseBody, + $service, + $path + ); + } + + public function get($service, $path) + { + $request = $this->createRequest($path); + + list($statusCode, $contentType, $responseBody) = $request->get(); + + return $this->handleResponse( + $statusCode, + $contentType, + $responseBody, + $service, + $path + ); + } + + private function userAgent() + { + $curlVersion = curl_version(); + + return $this->userAgentPrefix . 'MaxMind-WS-API/' . self::VERSION . ' PHP/' . PHP_VERSION . + ' curl/' . $curlVersion['version']; + } + + private function createRequest($path, $headers = []) + { + array_push( + $headers, + 'Authorization: Basic ' + . base64_encode($this->accountId . ':' . $this->licenseKey), + 'Accept: application/json' + ); + + return $this->httpRequestFactory->request( + $this->urlFor($path), + [ + 'caBundle' => $this->caBundle, + 'connectTimeout' => $this->connectTimeout, + 'headers' => $headers, + 'proxy' => $this->proxy, + 'timeout' => $this->timeout, + 'userAgent' => $this->userAgent(), + ] + ); + } + + /** + * @param int $statusCode the HTTP status code of the response + * @param string $contentType the Content-Type of the response + * @param string $responseBody the response body + * @param string $service the name of the service + * @param string $path the path used in the request + * + * @throws AuthenticationException when there is an issue authenticating the + * request + * @throws InsufficientFundsException when your account is out of funds + * @throws InvalidRequestException when the request is invalid for some + * other reason, e.g., invalid JSON in the POST. + * @throws HttpException when an unexpected HTTP error occurs + * @throws WebServiceException when some other error occurs. This also + * serves as the base class for the above exceptions + * + * @return array The decoded content of a successful response + */ + private function handleResponse( + $statusCode, + $contentType, + $responseBody, + $service, + $path + ) { + if ($statusCode >= 400 && $statusCode <= 499) { + $this->handle4xx($statusCode, $contentType, $responseBody, $service, $path); + } elseif ($statusCode >= 500) { + $this->handle5xx($statusCode, $service, $path); + } elseif ($statusCode !== 200 && $statusCode !== 204) { + $this->handleUnexpectedStatus($statusCode, $service, $path); + } + + return $this->handleSuccess($statusCode, $responseBody, $service); + } + + /** + * @return string describing the JSON error + */ + private function jsonErrorDescription() + { + $errno = json_last_error(); + switch ($errno) { + case JSON_ERROR_DEPTH: + return 'The maximum stack depth has been exceeded.'; + case JSON_ERROR_STATE_MISMATCH: + return 'Invalid or malformed JSON.'; + case JSON_ERROR_CTRL_CHAR: + return 'Control character error.'; + case JSON_ERROR_SYNTAX: + return 'Syntax error.'; + case JSON_ERROR_UTF8: + return 'Malformed UTF-8 characters.'; + default: + return "Other JSON error ($errno)."; + } + } + + /** + * @param string $path the path to use in the URL + * + * @return string the constructed URL + */ + private function urlFor($path) + { + return 'https://' . $this->host . $path; + } + + /** + * @param int $statusCode the HTTP status code + * @param string $contentType the response content-type + * @param string $body the response body + * @param string $service the service name + * @param string $path the path used in the request + * + * @throws AuthenticationException + * @throws HttpException + * @throws InsufficientFundsException + * @throws InvalidRequestException + */ + private function handle4xx( + $statusCode, + $contentType, + $body, + $service, + $path + ) { + if (\strlen($body) === 0) { + throw new HttpException( + "Received a $statusCode error for $service with no body", + $statusCode, + $this->urlFor($path) + ); + } + if (!strstr($contentType, 'json')) { + throw new HttpException( + "Received a $statusCode error for $service with " . + 'the following body: ' . $body, + $statusCode, + $this->urlFor($path) + ); + } + + $message = json_decode($body, true); + if ($message === null) { + throw new HttpException( + "Received a $statusCode error for $service but could " . + 'not decode the response as JSON: ' + . $this->jsonErrorDescription() . ' Body: ' . $body, + $statusCode, + $this->urlFor($path) + ); + } + + if (!isset($message['code']) || !isset($message['error'])) { + throw new HttpException( + 'Error response contains JSON but it does not ' . + 'specify code or error keys: ' . $body, + $statusCode, + $this->urlFor($path) + ); + } + + $this->handleWebServiceError( + $message['error'], + $message['code'], + $statusCode, + $path + ); + } + + /** + * @param string $message the error message from the web service + * @param string $code the error code from the web service + * @param int $statusCode the HTTP status code + * @param string $path the path used in the request + * + * @throws AuthenticationException + * @throws InvalidRequestException + * @throws InsufficientFundsException + */ + private function handleWebServiceError( + $message, + $code, + $statusCode, + $path + ) { + switch ($code) { + case 'IP_ADDRESS_NOT_FOUND': + case 'IP_ADDRESS_RESERVED': + throw new IpAddressNotFoundException( + $message, + $code, + $statusCode, + $this->urlFor($path) + ); + case 'ACCOUNT_ID_REQUIRED': + case 'ACCOUNT_ID_UNKNOWN': + case 'AUTHORIZATION_INVALID': + case 'LICENSE_KEY_REQUIRED': + case 'USER_ID_REQUIRED': + case 'USER_ID_UNKNOWN': + throw new AuthenticationException( + $message, + $code, + $statusCode, + $this->urlFor($path) + ); + case 'OUT_OF_QUERIES': + case 'INSUFFICIENT_FUNDS': + throw new InsufficientFundsException( + $message, + $code, + $statusCode, + $this->urlFor($path) + ); + case 'PERMISSION_REQUIRED': + throw new PermissionRequiredException( + $message, + $code, + $statusCode, + $this->urlFor($path) + ); + default: + throw new InvalidRequestException( + $message, + $code, + $statusCode, + $this->urlFor($path) + ); + } + } + + /** + * @param int $statusCode the HTTP status code + * @param string $service the service name + * @param string $path the URI path used in the request + * + * @throws HttpException + */ + private function handle5xx($statusCode, $service, $path) + { + throw new HttpException( + "Received a server error ($statusCode) for $service", + $statusCode, + $this->urlFor($path) + ); + } + + /** + * @param int $statusCode the HTTP status code + * @param string $service the service name + * @param string $path the URI path used in the request + * + * @throws HttpException + */ + private function handleUnexpectedStatus($statusCode, $service, $path) + { + throw new HttpException( + 'Received an unexpected HTTP status ' . + "($statusCode) for $service", + $statusCode, + $this->urlFor($path) + ); + } + + /** + * @param int $statusCode the HTTP status code + * @param string $body the successful request body + * @param string $service the service name + * + * @throws WebServiceException if a response body is included but not + * expected, or is not expected but not + * included, or is expected and included + * but cannot be decoded as JSON + * + * @return array the decoded request body + */ + private function handleSuccess($statusCode, $body, $service) + { + // A 204 should have no response body + if ($statusCode === 204) { + if (\strlen($body) !== 0) { + throw new WebServiceException( + "Received a 204 response for $service along with an " . + "unexpected HTTP body: $body" + ); + } + + return null; + } + + // A 200 should have a valid JSON body + if (\strlen($body) === 0) { + throw new WebServiceException( + "Received a 200 response for $service but did not " . + 'receive a HTTP body.' + ); + } + + $decodedContent = json_decode($body, true); + if ($decodedContent === null) { + throw new WebServiceException( + "Received a 200 response for $service but could " . + 'not decode the response as JSON: ' + . $this->jsonErrorDescription() . ' Body: ' . $body + ); + } + + return $decodedContent; + } + + private function getCaBundle() + { + $curlVersion = curl_version(); + + // On OS X, when the SSL version is "SecureTransport", the system's + // keychain will be used. + if ($curlVersion['ssl_version'] === 'SecureTransport') { + return null; + } + $cert = CaBundle::getSystemCaRootBundlePath(); + + // Check if the cert is inside a phar. If so, we need to copy the cert + // to a temp file so that curl can see it. + if (substr($cert, 0, 7) === 'phar://') { + $tempDir = sys_get_temp_dir(); + $newCert = tempnam($tempDir, 'geoip2-'); + if ($newCert === false) { + throw new \RuntimeException( + "Unable to create temporary file in $tempDir" + ); + } + if (!copy($cert, $newCert)) { + throw new \RuntimeException( + "Could not copy $cert to $newCert: " + . var_export(error_get_last(), true) + ); + } + + // We use a shutdown function rather than the destructor as the + // destructor isn't called on a fatal error such as an uncaught + // exception. + register_shutdown_function( + function () use ($newCert) { + unlink($newCert); + } + ); + $cert = $newCert; + } + if (!file_exists($cert)) { + throw new \RuntimeException("CA cert does not exist at $cert"); + } + + return $cert; + } +} diff --git a/vendor/maxmind/web-service-common/src/WebService/Http/CurlRequest.php b/vendor/maxmind/web-service-common/src/WebService/Http/CurlRequest.php new file mode 100644 index 0000000..501b2af --- /dev/null +++ b/vendor/maxmind/web-service-common/src/WebService/Http/CurlRequest.php @@ -0,0 +1,136 @@ +url = $url; + $this->options = $options; + $this->ch = $options['curlHandle']; + } + + /** + * @param string $body + * + * @throws HttpException + * + * @return array + */ + public function post($body) + { + $curl = $this->createCurl(); + + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, $body); + + return $this->execute($curl); + } + + public function get() + { + $curl = $this->createCurl(); + + curl_setopt($curl, CURLOPT_HTTPGET, true); + + return $this->execute($curl); + } + + /** + * @return resource + */ + private function createCurl() + { + curl_reset($this->ch); + + $opts = []; + $opts[CURLOPT_URL] = $this->url; + + if (!empty($this->options['caBundle'])) { + $opts[CURLOPT_CAINFO] = $this->options['caBundle']; + } + + $opts[CURLOPT_ENCODING] = ''; + $opts[CURLOPT_SSL_VERIFYHOST] = 2; + $opts[CURLOPT_FOLLOWLOCATION] = false; + $opts[CURLOPT_SSL_VERIFYPEER] = true; + $opts[CURLOPT_RETURNTRANSFER] = true; + + $opts[CURLOPT_HTTPHEADER] = $this->options['headers']; + $opts[CURLOPT_USERAGENT] = $this->options['userAgent']; + $opts[CURLOPT_PROXY] = $this->options['proxy']; + + // The defined()s are here as the *_MS opts are not available on older + // cURL versions + $connectTimeout = $this->options['connectTimeout']; + if (\defined('CURLOPT_CONNECTTIMEOUT_MS')) { + $opts[CURLOPT_CONNECTTIMEOUT_MS] = ceil($connectTimeout * 1000); + } else { + $opts[CURLOPT_CONNECTTIMEOUT] = ceil($connectTimeout); + } + + $timeout = $this->options['timeout']; + if (\defined('CURLOPT_TIMEOUT_MS')) { + $opts[CURLOPT_TIMEOUT_MS] = ceil($timeout * 1000); + } else { + $opts[CURLOPT_TIMEOUT] = ceil($timeout); + } + + curl_setopt_array($this->ch, $opts); + + return $this->ch; + } + + /** + * @param resource $curl + * + * @throws HttpException + * + * @return array + */ + private function execute($curl) + { + $body = curl_exec($curl); + if ($errno = curl_errno($curl)) { + $errorMessage = curl_error($curl); + + throw new HttpException( + "cURL error ({$errno}): {$errorMessage}", + 0, + $this->url + ); + } + + $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE); + + return [$statusCode, $contentType, $body]; + } +} diff --git a/vendor/maxmind/web-service-common/src/WebService/Http/Request.php b/vendor/maxmind/web-service-common/src/WebService/Http/Request.php new file mode 100644 index 0000000..283e05c --- /dev/null +++ b/vendor/maxmind/web-service-common/src/WebService/Http/Request.php @@ -0,0 +1,29 @@ +ch)) { + curl_close($this->ch); + } + } + + private function getCurlHandle() + { + if (empty($this->ch)) { + $this->ch = curl_init(); + } + + return $this->ch; + } + + /** + * @param string $url + * @param array $options + * + * @return Request + */ + public function request($url, $options) + { + $options['curlHandle'] = $this->getCurlHandle(); + + return new CurlRequest($url, $options); + } +} -- cgit v1.2.3