diff options
author | Frederic Guillot <fred@kanboard.net> | 2017-10-25 16:22:10 -0700 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2017-10-25 16:22:10 -0700 |
commit | 9e2b2a32fd0e967ad3184e9a5d091a29953acb91 (patch) | |
tree | 00822e24aa1110c73ca455a8d096ef296c008cbc /vendor/miniflux/picofeed/lib/PicoFeed/Client/Client.php | |
parent | c507c5416251c505cb3e088a03c6664bed73c812 (diff) |
Include composer dependencies in repo
Diffstat (limited to 'vendor/miniflux/picofeed/lib/PicoFeed/Client/Client.php')
-rw-r--r-- | vendor/miniflux/picofeed/lib/PicoFeed/Client/Client.php | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/vendor/miniflux/picofeed/lib/PicoFeed/Client/Client.php b/vendor/miniflux/picofeed/lib/PicoFeed/Client/Client.php new file mode 100644 index 00000000..0548d5c6 --- /dev/null +++ b/vendor/miniflux/picofeed/lib/PicoFeed/Client/Client.php @@ -0,0 +1,719 @@ +<?php + +namespace PicoFeed\Client; + +use DateTime; +use Exception; +use LogicException; +use PicoFeed\Logging\Logger; +use PicoFeed\Config\Config; + +/** + * Client class. + * + * @author Frederic Guillot + */ +abstract class Client +{ + /** + * Flag that say if the resource have been modified. + * + * @var bool + */ + private $is_modified = true; + + /** + * HTTP Content-Type. + * + * @var string + */ + private $content_type = ''; + + /** + * HTTP encoding. + * + * @var string + */ + private $encoding = ''; + + /** + * HTTP request headers. + * + * @var array + */ + protected $request_headers = array(); + + /** + * HTTP Etag header. + * + * @var string + */ + protected $etag = ''; + + /** + * HTTP Last-Modified header. + * + * @var string + */ + protected $last_modified = ''; + + /** + * Expiration DateTime + * + * @var DateTime + */ + protected $expiration = null; + + /** + * Proxy hostname. + * + * @var string + */ + protected $proxy_hostname = ''; + + /** + * Proxy port. + * + * @var int + */ + protected $proxy_port = 3128; + + /** + * Proxy username. + * + * @var string + */ + protected $proxy_username = ''; + + /** + * Proxy password. + * + * @var string + */ + protected $proxy_password = ''; + + /** + * Basic auth username. + * + * @var string + */ + protected $username = ''; + + /** + * Basic auth password. + * + * @var string + */ + protected $password = ''; + + /** + * Client connection timeout. + * + * @var int + */ + protected $timeout = 10; + + /** + * User-agent. + * + * @var string + */ + protected $user_agent = 'PicoFeed (https://github.com/miniflux/picoFeed)'; + + /** + * Real URL used (can be changed after a HTTP redirect). + * + * @var string + */ + protected $url = ''; + + /** + * Page/Feed content. + * + * @var string + */ + protected $content = ''; + + /** + * Number maximum of HTTP redirections to avoid infinite loops. + * + * @var int + */ + protected $max_redirects = 5; + + /** + * Maximum size of the HTTP body response. + * + * @var int + */ + protected $max_body_size = 2097152; // 2MB + + /** + * HTTP response status code. + * + * @var int + */ + protected $status_code = 0; + + /** + * Enables direct passthrough to requesting client. + * + * @var bool + */ + protected $passthrough = false; + + /** + * Do the HTTP request. + * + * @abstract + * + * @return array + */ + abstract public function doRequest(); + + /** + * Get client instance: curl or stream driver. + * + * @static + * + * @return \PicoFeed\Client\Client + */ + public static function getInstance() + { + if (function_exists('curl_init')) { + return new Curl(); + } elseif (ini_get('allow_url_fopen')) { + return new Stream(); + } + + throw new LogicException('You must have "allow_url_fopen=1" or curl extension installed'); + } + + /** + * Add HTTP Header to the request. + * + * @param array $headers + */ + public function setHeaders($headers) + { + $this->request_headers = $headers; + } + + /** + * Perform the HTTP request. + * + * @param string $url URL + * + * @return Client + */ + public function execute($url = '') + { + if ($url !== '') { + $this->url = $url; + } + + Logger::setMessage(get_called_class().' Fetch URL: '.$this->url); + Logger::setMessage(get_called_class().' Etag provided: '.$this->etag); + Logger::setMessage(get_called_class().' Last-Modified provided: '.$this->last_modified); + + $response = $this->doRequest(); + + $this->status_code = $response['status']; + $this->handleNotModifiedResponse($response); + $this->handleErrorResponse($response); + $this->handleNormalResponse($response); + + $this->expiration = $this->parseExpiration($response['headers']); + Logger::setMessage(get_called_class().' Expiration: '.$this->expiration->format(DATE_ISO8601)); + + return $this; + } + + /** + * Handle not modified response. + * + * @param array $response Client response + */ + protected function handleNotModifiedResponse(array $response) + { + if ($response['status'] == 304) { + $this->is_modified = false; + } elseif ($response['status'] == 200) { + $this->is_modified = $this->hasBeenModified($response, $this->etag, $this->last_modified); + $this->etag = $this->getHeader($response, 'ETag'); + $this->last_modified = $this->getHeader($response, 'Last-Modified'); + } + + if ($this->is_modified === false) { + Logger::setMessage(get_called_class().' Resource not modified'); + } + } + + /** + * Handle Http Error codes + * + * @param array $response Client response + * @throws ForbiddenException + * @throws InvalidUrlException + * @throws UnauthorizedException + */ + protected function handleErrorResponse(array $response) + { + $status = $response['status']; + if ($status == 401) { + throw new UnauthorizedException('Wrong or missing credentials'); + } else if ($status == 403) { + throw new ForbiddenException('Not allowed to access resource'); + } else if ($status == 404) { + throw new InvalidUrlException('Resource not found'); + } + } + + /** + * Handle normal response. + * + * @param array $response Client response + */ + protected function handleNormalResponse(array $response) + { + if ($response['status'] == 200) { + $this->content = $response['body']; + $this->content_type = $this->findContentType($response); + $this->encoding = $this->findCharset(); + } + } + + /** + * Check if a request has been modified according to the parameters. + * + * @param array $response + * @param string $etag + * @param string $lastModified + * + * @return bool + */ + private function hasBeenModified($response, $etag, $lastModified) + { + $headers = array( + 'Etag' => $etag, + 'Last-Modified' => $lastModified, + ); + + // Compare the values for each header that is present + $presentCacheHeaderCount = 0; + foreach ($headers as $key => $value) { + if (isset($response['headers'][$key])) { + if ($response['headers'][$key] !== $value) { + return true; + } + ++$presentCacheHeaderCount; + } + } + + // If at least one header is present and the values match, the response + // was not modified + if ($presentCacheHeaderCount > 0) { + return false; + } + + return true; + } + + /** + * Find content type from response headers. + * + * @param array $response Client response + * + * @return string + */ + public function findContentType(array $response) + { + return strtolower($this->getHeader($response, 'Content-Type')); + } + + /** + * Find charset from response headers. + * + * @return string + */ + public function findCharset() + { + $result = explode('charset=', $this->content_type); + + return isset($result[1]) ? $result[1] : ''; + } + + /** + * Get header value from a client response. + * + * @param array $response Client response + * @param string $header Header name + * + * @return string + */ + public function getHeader(array $response, $header) + { + return isset($response['headers'][$header]) ? $response['headers'][$header] : ''; + } + + /** + * Set the Last-Modified HTTP header. + * + * @param string $last_modified Header value + * + * @return \PicoFeed\Client\Client + */ + public function setLastModified($last_modified) + { + $this->last_modified = $last_modified; + + return $this; + } + + /** + * Get the value of the Last-Modified HTTP header. + * + * @return string + */ + public function getLastModified() + { + return $this->last_modified; + } + + /** + * Set the value of the Etag HTTP header. + * + * @param string $etag Etag HTTP header value + * + * @return \PicoFeed\Client\Client + */ + public function setEtag($etag) + { + $this->etag = $etag; + + return $this; + } + + /** + * Get the Etag HTTP header value. + * + * @return string + */ + public function getEtag() + { + return $this->etag; + } + + /** + * Get the final url value. + * + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * Set the url. + * + * @param $url + * @return string + */ + public function setUrl($url) + { + $this->url = $url; + return $this; + } + + /** + * Get the HTTP response status code. + * + * @return int + */ + public function getStatusCode() + { + return $this->status_code; + } + + /** + * Get the body of the HTTP response. + * + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * Get the content type value from HTTP headers. + * + * @return string + */ + public function getContentType() + { + return $this->content_type; + } + + /** + * Get the encoding value from HTTP headers. + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Return true if the remote resource has changed. + * + * @return bool + */ + public function isModified() + { + return $this->is_modified; + } + + /** + * return true if passthrough mode is enabled. + * + * @return bool + */ + public function isPassthroughEnabled() + { + return $this->passthrough; + } + + /** + * Set connection timeout. + * + * @param int $timeout Connection timeout + * + * @return \PicoFeed\Client\Client + */ + public function setTimeout($timeout) + { + $this->timeout = $timeout ?: $this->timeout; + + return $this; + } + + /** + * Set a custom user agent. + * + * @param string $user_agent User Agent + * + * @return \PicoFeed\Client\Client + */ + public function setUserAgent($user_agent) + { + $this->user_agent = $user_agent ?: $this->user_agent; + + return $this; + } + + /** + * Set the maximum number of HTTP redirections. + * + * @param int $max Maximum + * + * @return \PicoFeed\Client\Client + */ + public function setMaxRedirections($max) + { + $this->max_redirects = $max ?: $this->max_redirects; + + return $this; + } + + /** + * Set the maximum size of the HTTP body. + * + * @param int $max Maximum + * + * @return \PicoFeed\Client\Client + */ + public function setMaxBodySize($max) + { + $this->max_body_size = $max ?: $this->max_body_size; + + return $this; + } + + /** + * Set the proxy hostname. + * + * @param string $hostname Proxy hostname + * + * @return \PicoFeed\Client\Client + */ + public function setProxyHostname($hostname) + { + $this->proxy_hostname = $hostname ?: $this->proxy_hostname; + + return $this; + } + + /** + * Set the proxy port. + * + * @param int $port Proxy port + * + * @return \PicoFeed\Client\Client + */ + public function setProxyPort($port) + { + $this->proxy_port = $port ?: $this->proxy_port; + + return $this; + } + + /** + * Set the proxy username. + * + * @param string $username Proxy username + * + * @return \PicoFeed\Client\Client + */ + public function setProxyUsername($username) + { + $this->proxy_username = $username ?: $this->proxy_username; + + return $this; + } + + /** + * Set the proxy password. + * + * @param string $password Password + * + * @return \PicoFeed\Client\Client + */ + public function setProxyPassword($password) + { + $this->proxy_password = $password ?: $this->proxy_password; + + return $this; + } + + /** + * Set the username. + * + * @param string $username Basic Auth username + * + * @return \PicoFeed\Client\Client + */ + public function setUsername($username) + { + $this->username = $username ?: $this->username; + + return $this; + } + + /** + * Set the password. + * + * @param string $password Basic Auth Password + * + * @return \PicoFeed\Client\Client + */ + public function setPassword($password) + { + $this->password = $password ?: $this->password; + + return $this; + } + + /** + * Enable the passthrough mode. + * + * @return \PicoFeed\Client\Client + */ + public function enablePassthroughMode() + { + $this->passthrough = true; + + return $this; + } + + /** + * Disable the passthrough mode. + * + * @return \PicoFeed\Client\Client + */ + public function disablePassthroughMode() + { + $this->passthrough = false; + + return $this; + } + + /** + * Set config object. + * + * @param \PicoFeed\Config\Config $config Config instance + * + * @return \PicoFeed\Client\Client + */ + public function setConfig(Config $config) + { + if ($config !== null) { + $this->setTimeout($config->getClientTimeout()); + $this->setUserAgent($config->getClientUserAgent()); + $this->setMaxRedirections($config->getMaxRedirections()); + $this->setMaxBodySize($config->getMaxBodySize()); + $this->setProxyHostname($config->getProxyHostname()); + $this->setProxyPort($config->getProxyPort()); + $this->setProxyUsername($config->getProxyUsername()); + $this->setProxyPassword($config->getProxyPassword()); + } + + return $this; + } + + /** + * Return true if the HTTP status code is a redirection + * + * @access protected + * @param integer $code + * @return boolean + */ + public function isRedirection($code) + { + return $code == 301 || $code == 302 || $code == 303 || $code == 307; + } + + public function parseExpiration(HttpHeaders $headers) + { + try { + + if (isset($headers['Cache-Control'])) { + if (preg_match('/s-maxage=(\d+)/', $headers['Cache-Control'], $matches)) { + return new DateTime('+' . $matches[1] . ' seconds'); + } else if (preg_match('/max-age=(\d+)/', $headers['Cache-Control'], $matches)) { + return new DateTime('+' . $matches[1] . ' seconds'); + } + } + + if (! empty($headers['Expires'])) { + return new DateTime($headers['Expires']); + } + } catch (Exception $e) { + Logger::setMessage('Unable to parse expiration date: '.$e->getMessage()); + } + + return new DateTime(); + } + + /** + * Get expiration date time from "Expires" or "Cache-Control" headers + * + * @return DateTime + */ + public function getExpiration() + { + return $this->expiration ?: new DateTime(); + } +} |