commit 85be2dd10045443c65a1a491228cfe283688dc2f Author: Ismo Vuorinen Date: Thu Nov 1 14:10:35 2018 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d24543 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +.DS_Store \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0f59e7a --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 Ismo Vuorinen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f1d941e --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +Curly PHP Curl client +===================== + +Main features: + +- BASIC, NTLM, DIGEST and SPNEGO auth authentication support +- proxy support +- allowed http methods: GET, POST, PUT, DELETE +- request customization (User Agent, HTTP Version, Content Type, ...) + +## Why? + +Because I needed a CURL library that handled NTLM authentication and +let me choose what parameters should be passed to the CURL instance. + +## Usage + +*todo* + +## License + +This package is released under the MIT License (MIT). +Please see [License File](LICENSE) for more information. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..e0da2b8 --- /dev/null +++ b/composer.json @@ -0,0 +1,41 @@ +{ + "name": "ivuorinen/curly", + "description": "HTTP Curl client capable of NTLM authentication", + "keywords": [ + "http", + "curl", + "ntlm" + ], + "license": "MIT", + "authors": [ + { + "name": "Ismo Vuorinen", + "email": "ismo@ivuorinen.net" + } + ], + "type": "project", + "require": { + "php": ">=7.1", + "league/uri": "^5.3", + "ext-curl": "*", + "ext-zlib": "*" + }, + "require-dev": { + "phpunit/phpunit": "6.*", + "phpstan/phpstan": "^0.10.5" + }, + "autoload": { + "psr-4": { + "ivuorinen\\Curly\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "ivuorinen\\Curly\\Tests\\": "tests/" + } + }, + "scripts": { + "test": "vendor/bin/phpunit --colors --verbose", + "check": "vendor/bin/phpstan analyse src tests --level=7" + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..23ef320 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,21 @@ + + + + + ./tests + + + + + ./src + + + diff --git a/src/Curly.php b/src/Curly.php new file mode 100644 index 0000000..6df9a6a --- /dev/null +++ b/src/Curly.php @@ -0,0 +1,1207 @@ + 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language' => 'en-us,en;q=0.5', + 'Accept-Encoding' => 'gzip,deflate', + 'Accept-Charset' => 'UTF-8;q=0.7,*;q=0.7' + ]; + + /** @var string|null Should we use a proxy? */ + private $proxy = null; + + /** @var string|null */ + private $proxy_auth = null; + + /** @var array Allowed HTTP methods */ + private $supported_auth_methods = ["BASIC", "DIGEST", "SPNEGO", "NTLM"]; + + /** @var array Allowed HTTP authentication */ + private $supported_http_methods = ["GET", "POST", "PUT", "DELETE"]; + + /** @var boolean Are we using curl? */ + private $curl = true; + + /** @var array Received headers */ + private $receivedHeaders = []; + + /** @var int|null Received http status code */ + private $receivedHttpStatus = null; + + /** @var resource|null Transfer channel */ + private $ch = null; + + /** @var string|null */ + private $stream_get_data = null; + + /** @var boolean Output debug messages? */ + private $verbose; + + /** @var boolean Should we blindly trust host? */ + private $skipSSLVerify; + + /** + * Class constructor + * + * @param string|false $address Remote host address + * @param bool $curl Use curl (true) or stream (false) + * + * @throws Exceptions\HTTPException + */ + public function __construct($address = false, $curl = true) + { + $this->reset(); + + if (!empty($address)) { + try { + $this->setHost($address); + } catch (Exceptions\HTTPException $exception) { + throw $exception; + } + } + + $this->setCurl($curl); + } + + /** + * Reset the data channel for new request + */ + public function reset(): void + { + $this->address = ''; + $this->port = 80; + $this->method = "GET"; + $this->timeout = 30; + $this->httpVersion = "1.0"; + $this->authenticationMethod = null; + $this->user = null; + $this->pass = null; + $this->userAgent = "ivuorinen-curly"; + $this->contentType = "application/x-www-form-urlencoded"; + $this->headers = [ + "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language" => "en-us,en;q=0.5", + "Accept-Encoding" => "deflate", + "Accept-Charset" => "UTF-8;q=0.7,*;q=0.7" + ]; + $this->proxy = null; + $this->proxy_auth = null; + $this->receivedHeaders = array(); + $this->receivedHttpStatus = null; + $this->stream_get_data = null; + $this->verbose = false; + $this->skipSSLVerify = false; + if ($this->ch !== null) { + $this->closeTransport(); + } + } + + /** + * Close transport layer + */ + private function closeTransport(): void + { + if ($this->curl && !is_null($this->ch)) { + curl_close($this->ch); + $this->ch = null; + } + } + + /** + * Set remote host address + * + * @param string $address Remote host address + * + * @return self + * @throws Exceptions\HTTPException + */ + public function setHost($address = '') + { + $url = filter_var($address, FILTER_VALIDATE_URL); + + if ($url === false) { + throw new Exceptions\HTTPException("Invalid remote address"); + } + + $this->address = $address; + + return $this; + } + + /** + * @return string|null + */ + public function getHost(): ?string + { + return $this->address; + } + + /** + * Force lib to use curl (default if available) or stream + * + * @param bool $mode Use curl (true) or stream (false) + * + * @return self + */ + public function setCurl($mode = true) + { + $curl = filter_var($mode, FILTER_VALIDATE_BOOLEAN); + + $this->curl = !function_exists("curl_init") OR !$curl + ? false + : true; + + return $this; + } + + /** + * Class destructor + */ + public function __destruct() + { + if ($this->ch !== null) { + $this->closeTransport(); + } + } + + /** + * Is Curl verbose? + * + * @return bool + */ + public function getVerbose() + { + return $this->verbose; + } + + /** + * Set Curl verbosity on, or off + * + * @param bool $value true = verbose + * + * @return self + */ + public function setVerbose($value = false) + { + $this->verbose = filter_var($value, FILTER_VALIDATE_BOOLEAN); + + return $this; + } + + /** + * Are we skipping SSL Verify? + * + * @return bool + */ + public function getSkipSSLVerify() + { + return $this->skipSSLVerify; + } + + /** + * Should we blindly trust host validity? + * This helps cases when we have self signed certificates + * + * @param bool $value + * + * @return $this + */ + public function setSkipSSLVerify($value = false) + { + $this->skipSSLVerify = filter_var($value, FILTER_VALIDATE_BOOLEAN); + + return $this; + } + + /** + * Set http authentication + * + * @param string $method Auth method (BASIC or NTLM) + * @param string $user Username to use + * @param string $pass User password + * + * @return self + * @throws Exceptions\HTTPException + */ + public function setAuth($method, $user, $pass = null) + { + $method = strtoupper($method); + + if (!in_array($method, $this->supported_auth_methods)) { + throw new Exceptions\HTTPException("Unsupported authentication method"); + } + + $this->authenticationMethod = $method; + + if (empty($user)) { + throw new Exceptions\HTTPException("User name cannot be null"); + } + + $this->user = $user; + $this->pass = $pass; + + return $this; + } + + /** + * Set http version (1.0/1.1) + * + * @param string $ver 1.0 or 1.1 + * + * @return self + */ + public function setHttpVersion($ver = "1.1") + { + $this->httpVersion = !in_array($ver, ["1.0", "1.1"]) + ? "NONE" + : $ver; + + return $this; + } + + /** + * Unset header component + * + * @param string $header Header name to remove + * + * @return self + */ + public function unsetHeader($header) + { + if (array_key_exists($header, $this->headers)) { + unset($this->headers[$header]); + } + + return $this; + } + + /** + * Get received headers + * + * @return array + */ + public function getReceivedHeaders(): array + { + return $this->receivedHeaders; + } + + /** + * Get received headers + * + * @return integer|null + */ + public function getHttpStatusCode() + { + return $this->receivedHttpStatus; + } + + /** + * Get transport channel (curl channel or stream context) + * + * @return resource|null + */ + public function getChannel() + { + return $this->ch; + } + + /** + * Init transport and send data to the remote host. + * + * @param string|array|object|null $data + * + * @return string + * @throws Exceptions\HTTPException + */ + public function send($data = null): string + { + try { + if ($this->curl) { + $this->initCurl($data); + + $received = $this->sendUsingCurl(); + } else { + $this->initStream($data); + + $received = $this->sendUsingStream(); + } + } catch (Exceptions\HTTPException $ioe) { + throw $ioe; + } + + return $received; + } + + /** + * Init the CURL channel + * + * @param string|array|object|null $data + * + * @throws Exceptions\HTTPException + */ + private function initCurl($data): void + { + $this->ch = curl_init(); + + if ($this->ch == false) { + throw new Exceptions\HTTPException("Could not init data channel"); + } + + $this->initCurlHTTPVersion(); + + $this->initCurlAuthenticationMethod(); + + $this->initCurlProxy(); + + $this->initCurlMethod($data); + + $headers = []; + if (sizeof($this->headers) != 0) { + foreach ($this->getHeaders() as $header => $value) { + if (is_null($value)) { + array_push($headers, $header); + } else { + array_push($headers, $header . ': ' . $value); + } + } + } + + if ($this->ch == false) { + return; + } + + curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($this->ch, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($this->ch, CURLOPT_PORT, $this->port); + curl_setopt($this->ch, CURLOPT_USERAGENT, $this->userAgent); + curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($this->ch, CURLOPT_HEADER, 1); + curl_setopt($this->ch, CURLOPT_ENCODING, ""); + + if ($this->verbose) { + curl_setopt($this->ch, CURLOPT_VERBOSE, true); + } + + if ($this->skipSSLVerify) { + curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0); + } + } + + private function initCurlHTTPVersion(): void + { + if ($this->ch == null) { + return; + } + + switch ($this->httpVersion) { + case '1.0': + curl_setopt( + $this->ch, + CURLOPT_HTTP_VERSION, + CURL_HTTP_VERSION_1_0 + ); + return; + + case '1.1': + curl_setopt( + $this->ch, + CURLOPT_HTTP_VERSION, + CURL_HTTP_VERSION_1_1 + ); + return; + + default: + curl_setopt( + $this->ch, + CURLOPT_HTTP_VERSION, + CURL_HTTP_VERSION_NONE + ); + return; + } + } + + private function initCurlAuthenticationMethod(): void + { + if ($this->ch == null) { + return; + } + + switch ($this->authenticationMethod) { + case 'BASIC': + curl_setopt( + $this->ch, + CURLOPT_HTTPAUTH, + CURLAUTH_BASIC + ); + curl_setopt( + $this->ch, + CURLOPT_USERPWD, + $this->user . ":" . $this->pass + ); + break; + + case 'DIGEST': + curl_setopt( + $this->ch, + CURLOPT_HTTPAUTH, + CURLAUTH_DIGEST + ); + curl_setopt( + $this->ch, + CURLOPT_USERPWD, + $this->user . ":" . $this->pass + ); + break; + + case 'SPNEGO': + curl_setopt( + $this->ch, + CURLOPT_HTTPAUTH, + CURLAUTH_GSSNEGOTIATE + ); + curl_setopt( + $this->ch, + CURLOPT_USERPWD, + $this->user . ":" . $this->pass + ); + break; + + case 'NTLM': + curl_setopt( + $this->ch, + CURLOPT_HTTPAUTH, + CURLAUTH_NTLM + ); + curl_setopt( + $this->ch, + CURLOPT_USERPWD, + $this->user . ":" . $this->pass + ); + break; + } + } + + private function initCurlProxy(): void + { + if (!is_null($this->proxy) && !is_null($this->ch)) { + curl_setopt($this->ch, CURLOPT_PROXY, $this->proxy); + + if (!is_null($this->proxy_auth)) { + curl_setopt( + $this->ch, + CURLOPT_PROXYUSERPWD, + $this->proxy_auth + ); + } + } + } + + /** + * @param string|array|object|null $data + */ + private function initCurlMethod($data): void + { + if ($this->ch == null) { + return; + } + + switch ($this->method) { + case 'GET': + if (empty($data)) { + curl_setopt( + $this->ch, + CURLOPT_URL, + $this->address + ); + } else { + $payload = $this->address + . "?" . $this->parseData($data); + curl_setopt( + $this->ch, + CURLOPT_URL, + $payload + ); + } + return; + + case 'PUT': + curl_setopt( + $this->ch, + CURLOPT_CUSTOMREQUEST, + "PUT" + ); + + if (!empty($data)) { + curl_setopt( + $this->ch, + CURLOPT_POSTFIELDS, + $this->parseData($data) + ); + + $this->setHeader( + "Content-Type", + $this->contentType + ); + } + + curl_setopt( + $this->ch, + CURLOPT_URL, + $this->address + ); + return; + + case 'POST': + curl_setopt( + $this->ch, + CURLOPT_POST, + true + ); + + if (!empty($data)) { + curl_setopt( + $this->ch, + CURLOPT_POSTFIELDS, + $this->parseData($data) + ); + $this->setHeader( + "Content-Type", + $this->contentType + ); + } + + curl_setopt( + $this->ch, + CURLOPT_URL, + $this->address + ); + return; + + case 'DELETE': + curl_setopt( + $this->ch, + CURLOPT_CUSTOMREQUEST, + "DELETE" + ); + + if (!empty($data)) { + curl_setopt( + $this->ch, + CURLOPT_POSTFIELDS, + $this->parseData($data) + ); + + $this->setHeader( + "Content-Type", + $this->contentType + ); + } + + curl_setopt( + $this->ch, + CURLOPT_URL, + $this->address + ); + return; + } + } + + /** + * Convert string, array or object to http_build_query string + * + * @param string|array|object $data + * + * @return string + */ + public function parseData($data = ''): string + { + return (is_array($data) OR is_object($data)) + ? http_build_query($data) + : $data; + } + + /** + * Set header component + * + * @param string $header Header name + * @param string $value Header content (optional) + * + * @return self + */ + public function setHeader($header, $value = null) + { + $this->headers[$header] = $value; + return $this; + } + + /** + * Get the whole headers array + * + * @return array + */ + public function getHeaders(): array + { + return $this->headers; + } + + /** + * Send data via CURL + * + * @return string + * @throws Exceptions\HTTPException + */ + private function sendUsingCurl(): string + { + if ($this->ch == null) { + throw new Exceptions\HTTPException( + "No CURL available" + ); + } + + $request = curl_exec($this->ch); + + if (is_bool($request)) { + throw new Exceptions\HTTPException( + curl_error($this->ch), + curl_errno($this->ch) + ); + } + + $this->receivedHttpStatus = curl_getinfo( + $this->ch, + CURLINFO_HTTP_CODE + ); + + $header_size = curl_getinfo( + $this->ch, + CURLINFO_HEADER_SIZE + ); + + $headers = substr( + $request, + 0, + $header_size + ); + + $body = substr( + $request, + $header_size + ); + + $this->receivedHeaders = self::tokenizeHeaders($headers); + + return $body; + } + + /** + * Tokenize received headers + * + * @param string $headers + * + * @return array + */ + public static function tokenizeHeaders($headers): array + { + $return = []; + + $headers_array = explode("\r\n", $headers); + + foreach ($headers_array as $header) { + if (empty($header)) { + continue; + } + + $header_components = explode(":", $header); + + if (!isset($header_components[1]) OR @empty($header_components[1])) { + array_push($return, $header_components[0]); + } else { + $return[$header_components[0]] = $header_components[1]; + } + } + + return $return; + } + + /** + * Init the STREAM channel + * + * @param string|array|object|null $data + * + * @throws Exceptions\HTTPException + */ + private function initStream($data): void + { + if (in_array($this->authenticationMethod, ["DIGEST", "SPNEGO", "NTLM"])) { + throw new Exceptions\HTTPException( + sprintf( + "Selected auth method %s not available in stream mode", + $this->authenticationMethod + ) + ); + } + + $stream_options = [ + "http" => [ + "method" => $this->method, + "protocol_version" => $this->httpVersion == "NONE" + ? "1.0" + : $this->httpVersion, + "user_agent" => $this->userAgent, + "timeout" => $this->timeout, + "header" => [ + "Connection: close" + ] + ] + ]; + + if (!is_null($this->proxy)) { + $stream_options['http']['proxy'] = $this->proxy; + + if (!is_null($this->proxy_auth)) { + array_push( + $stream_options['http']['header'], + 'Proxy-Authorization: Basic ' + . base64_encode($this->proxy_auth) + ); + } + } + + if ($this->authenticationMethod == "BASIC") { + array_push( + $stream_options["http"]["header"], + "Authorization: Basic " + . base64_encode($this->user . ":" . $this->pass) + ); + } + + foreach ($this->getHeaders() as $header => $value) { + if (is_null($value)) { + array_push( + $stream_options["http"]["header"], + $header + ); + } else { + array_push( + $stream_options["http"]["header"], + $header . ': ' . $value + ); + } + } + + if (!empty($data)) { + $data_query = $this->parseData($data); + + if ($this->method == "GET") { + $this->stream_get_data = $data_query; + } else { + array_push( + $stream_options['http']['header'], + "Content-Type: " . $this->contentType + ); + + array_push( + $stream_options['http']['header'], + "Content-Length: " . strlen($data_query) + ); + + $stream_options['http']['content'] = $data_query; + } + } + + $this->ch = stream_context_create($stream_options); + + if ($this->ch == false) { + throw new Exceptions\HTTPException("Cannot init data channel"); + } + } + + /** + * Send data via STREAM + * + * @return string + * + * @uses \League\Uri\Uri + * @throws Exceptions\HTTPException + */ + private function sendUsingStream(): string + { + if (is_null($this->address) || mb_strlen($this->address) < 1) { + throw new Exceptions\HTTPException( + "No address to use with stream" + ); + } + + $parts = parse_url($this->address); + + $url = Uri::createFromComponents([ + 'scheme' => $parts['scheme'] ?? null, + 'user' => $parts['user'] ?? $this->user, + 'pass' => $parts['pass'] ?? $this->pass, + 'host' => $parts['host'] ?? null, + 'port' => $this->port != 80 ? $this->port : 80, + 'path' => $parts['path'] ?? null, + 'query' => !is_null($this->stream_get_data) + ? $this->stream_get_data + : null, + 'fragment' => $parts['fragment'] ?? null + ]); + + set_error_handler( + function ($severity, $message, $file, $line) { + unset($severity, $file, $line); + throw new Exceptions\HTTPException($message); + } + ); + + try { + $received = file_get_contents( + $url, + false, + $this->ch + ); + if ($received === false) { + throw new Exceptions\HTTPException( + sprintf( + "No response from %s", + $url + ) + ); + } + } catch (Exceptions\HTTPException $e) { + throw $e; + } + + restore_error_handler(); + + if (!is_string($received)) { + throw new Exceptions\HTTPException( + "Could not read stream socket" + ); + } + + $this->receivedHeaders = self::tokenizeHeaders( + implode("\r\n", $http_response_header) + ); + + $content_encoding = array_key_exists( + "Content-Encoding", + $this->receivedHeaders + ); + + list($version, $this->receivedHttpStatus, $msg) = explode( + ' ', + $this->receivedHeaders[0], + 3 + ); + + unset($version, $msg); + + $gzipped = strpos( + $this->receivedHeaders["Content-Encoding"], + "gzip" + ); + + $response = ($content_encoding === true && $gzipped !== false + ? gzinflate(substr($received, 10, -8)) ?? '' + : $received + ); + + return !is_string($response) + ? '' + : $response; + } + + public function getHeader($header) + { + return $this->headers[$header] ?? false; + } + + /** + * Init transport and get remote content + * + * @return string + * + * @throws Exceptions\HTTPException + */ + public function get(): string + { + try { + if ($this->curl) { + $this->initCurl(null); + + $received = $this->sendUsingCurl(); + } else { + $this->initStream(null); + + $received = $this->sendUsingStream(); + } + } catch (Exceptions\HTTPException $ioe) { + throw $ioe; + } + + return $received; + } + + /** + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * Set TCP port to connect to + * + * @param integer $port TCP port (default 80) + * + * @return self + */ + public function setPort($port = 80) + { + $this->port = filter_var( + $port, + FILTER_VALIDATE_INT, + [ + "options" => [ + "min_range" => 1, + "max_range" => 65535, + "default" => 80 + ] + ] + ); + + return $this; + } + + /** + * @return string + */ + public function getMethod(): string + { + return $this->method; + } + + /** + * Set HTTP method to use + * + * @param string $method HTTP Method (GET, POST, ...) + * + * @return self + * @throws Exceptions\HTTPException + */ + public function setMethod($method = 'GET') + { + $method = strtoupper($method); + + if (!in_array($method, $this->supported_http_methods)) { + throw new Exceptions\HTTPException( + "Unsupported HTTP method: " . $method + . '. Should be one of: ' + . implode(', ', $this->supported_http_methods) + ); + } + + $this->method = $method; + + return $this; + } + + /** + * @return int + */ + public function getTimeout(): int + { + return $this->timeout; + } + + /** + * Set connection timeout + * + * @param int $sec Timeout to wait for (in second) + * + * @return self + */ + public function setTimeout($sec) + { + $time = filter_var($sec, FILTER_VALIDATE_INT); + + $this->timeout = $time; + + return $this; + } + + /** + * @return null|string + */ + public function getAuthenticationMethod(): ?string + { + return $this->authenticationMethod; + } + + /** + * @return null|string + */ + public function getProxy(): ?string + { + return $this->proxy; + } + + /** + * Set HTTP method to use + * + * @param string $address Proxy URL or IP address + * @param string $user (optional) User name for proxy auth + * @param string $pass (optional) User password for proxy auth + * + * @return self + * @throws Exceptions\HTTPException + */ + public function setProxy($address, $user = null, $pass = null) + { + $proxy = filter_var($address, FILTER_VALIDATE_URL); + + if ($proxy == false) { + throw new Exceptions\HTTPException( + "Invalid proxy address or URL" + ); + } + + $this->proxy = $proxy; + + $this->proxy_auth = ( + !is_null($user) && !is_null($pass) + ? $user . ':' . $pass + : (!is_null($user) ? $user : null) + ); + + return $this; + } + + /** + * @return string + */ + public function getUserAgent(): string + { + return $this->userAgent; + } + + /** + * Set user agent for request + * + * @param string $userAgent User Agent + * + * @return self + * @throws Exceptions\HTTPException + */ + public function setUserAgent($userAgent = '') + { + if (empty($userAgent)) { + throw new Exceptions\HTTPException("User Agent cannot be empty"); + } + + $this->userAgent = $userAgent; + + return $this; + } + + /** + * @return string + */ + public function getContentType(): string + { + return $this->contentType; + } + + /** + * Set http content type + * + * @param string $type Content type, application/json, etc. + * + * @return self + * @throws Exceptions\HTTPException + */ + public function setContentType($type) + { + if (empty($type)) { + throw new Exceptions\HTTPException("Content Type cannot be null"); + } + + $this->contentType = $type; + + return $this; + } + + /** + * @return string + */ + public function getHttpVersion(): ?string + { + return $this->httpVersion; + } + + /** + * @return null|string + */ + public function getPass(): ?string + { + return $this->pass; + } + + /** + * @return null|string + */ + public function getUser(): ?string + { + return $this->user; + } +} diff --git a/src/Exceptions/AuthenticationException.php b/src/Exceptions/AuthenticationException.php new file mode 100644 index 0000000..c8e785d --- /dev/null +++ b/src/Exceptions/AuthenticationException.php @@ -0,0 +1,12 @@ +assertTrue(is_object($var)); + unset($var); + } + + /** + * Just check if the Curly has no syntax errors + * + * This is just a simple check to make sure your library has no + * syntax error. This helps you troubleshoot any typo before you + * even use this library in a real project. + * + * @throws \ivuorinen\Curly\Exceptions\HTTPException + */ + public function testParseData() + { + $var = new \ivuorinen\Curly\Curly; + + $expected = "foo=bar&baz=buzz"; + + // String + $this->assertTrue($var->parseData($expected) == $expected); + + // Array + $this->assertEquals( + $expected, + $var->parseData(['foo' => 'bar', 'baz' => 'buzz']) + ); + + // Object + $actual = new \stdClass(); + $actual->foo = "bar"; + $actual->baz = "buzz"; + + $this->assertEquals( + $expected, + $var->parseData($actual) + ); + + unset($var); + } +} diff --git a/tests/CurlyMethodsTest.php b/tests/CurlyMethodsTest.php new file mode 100644 index 0000000..f92aa86 --- /dev/null +++ b/tests/CurlyMethodsTest.php @@ -0,0 +1,207 @@ +assertEquals(80, $this->curly->getPort()); + $this->curly->setPort(1234); + $this->assertEquals(1234, $this->curly->getPort()); + } + + public function testTimeout() + { + $this->assertEquals(30, $this->curly->getTimeout()); + $this->curly->setTimeout(10); + $this->assertEquals(10, $this->curly->getTimeout()); + } + + public function testSkipSSLVerify() + { + $this->assertTrue(method_exists( + $this->curly, + 'getSkipSSLVerify' + )); + $this->assertTrue(method_exists( + $this->curly, + 'setSkipSSLVerify' + )); + + $this->assertFalse($this->curly->getSkipSSLVerify()); + $this->curly->setSkipSSLVerify(true); + $this->assertTrue($this->curly->getSkipSSLVerify()); + $this->curly->setSkipSSLVerify(false); + $this->assertFalse($this->curly->getSkipSSLVerify()); + } + + public function testHttpMethod() + { + $this->assertTrue(method_exists($this->curly, 'getMethod')); + $this->assertTrue(method_exists($this->curly, 'setMethod')); + + $this->assertEquals('GET', $this->curly->getMethod()); + + $this->curly->setMethod('POST'); + $this->assertEquals( + 'POST', + $this->curly->getMethod() + ); + } + + public function testVerbose() + { + $this->assertTrue(method_exists($this->curly, 'getVerbose')); + $this->assertTrue(method_exists($this->curly, 'setVerbose')); + + $this->assertFalse($this->curly->getVerbose()); + $this->curly->setVerbose(true); + $this->assertTrue($this->curly->getVerbose()); + } + + public function testProxy() + { + $this->assertTrue(method_exists($this->curly, 'getProxy')); + $this->assertTrue(method_exists($this->curly, 'setProxy')); + + $this->assertNull($this->curly->getProxy()); + + $proxy = 'http://example.com'; + $this->curly->setProxy($proxy); + $this->assertEquals($proxy, $this->curly->getProxy()); + } + + public function testUserAgent() + { + $this->assertTrue(method_exists($this->curly, 'getUserAgent')); + $this->assertTrue(method_exists($this->curly, 'setUserAgent')); + + $this->assertEquals( + 'ivuorinen-curly', + $this->curly->getUserAgent() + ); + + $this->curly->setUserAgent('testing'); + $this->assertEquals( + 'testing', + $this->curly->getUserAgent() + ); + } + + public function testHeaders() + { + $this->assertTrue(method_exists( + $this->curly, 'getHeaders' + )); + $this->assertTrue(method_exists( + $this->curly, 'setHeader' + )); + $this->assertTrue(method_exists( + $this->curly, 'getHeader' + )); + $this->assertTrue(method_exists( + $this->curly, 'unsetHeader' + )); + + $headers = $this->curly->getHeaders(); + + $defaultHeaders = [ + 'Accept', + 'Accept-Language', + 'Accept-Encoding', + 'Accept-Charset' + ]; + foreach ($defaultHeaders as $defaultHeader) { + $this->assertArrayHasKey( + $defaultHeader, $headers, $defaultHeader + ); + + $this->curly->setHeader($defaultHeader, 'testing'); + + $this->assertEquals( + 'testing', + $this->curly->getHeader($defaultHeader) + ); + + $this->curly->unsetHeader($defaultHeader); + $this->assertFalse($this->curly->getHeader($defaultHeader)); + } + } + + public function testContentType() + { + $this->assertTrue(method_exists( + $this->curly, 'getContentType' + )); + $this->assertTrue(method_exists( + $this->curly, 'setContentType' + )); + + $default = 'application/x-www-form-urlencoded'; + $this->assertEquals($default, $this->curly->getContentType()); + + $this->curly->setContentType('testing'); + $this->assertEquals( + 'testing', + $this->curly->getContentType() + ); + } + + public function testAuth() + { + $this->curly->setAuth( + 'BASIC', + 'user', + 'pass' + ); + + $this->assertEquals( + 'BASIC', + $this->curly->getAuthenticationMethod() + ); + $this->assertEquals('user', $this->curly->getUser()); + $this->assertEquals('pass', $this->curly->getPass()); + } + + public function testHttpVersion() + { + $this->assertEquals( + '1.0', + $this->curly->getHttpVersion() + ); + + $this->curly->setHttpVersion('1.1'); + $this->assertEquals( + '1.1', + $this->curly->getHttpVersion() + ); + + $this->curly->setHttpVersion('1.2'); + $this->assertEquals( + 'NONE', + $this->curly->getHttpVersion() + ); + } + + public function testHost() + { + $test = 'http://user:password@example.com/xyz'; + $this->curly->setHost($test); + $this->assertEquals($test, $this->curly->getHost()); + } + + /** + * @throws \ivuorinen\Curly\Exceptions\HTTPException + */ + protected function setUp() + { + parent::setUp(); + $this->curly = new Curly; + } +}