Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
92.31% covered (success)
92.31%
12 / 13
CRAP
97.62% covered (success)
97.62%
82 / 84
HttpService
0.00% covered (danger)
0.00%
0 / 1
92.31% covered (success)
92.31%
12 / 13
34
97.62% covered (success)
97.62%
82 / 84
 __construct
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 setCurlProxyOptions
0.00% covered (danger)
0.00%
0 / 1
4.10
81.82% covered (warning)
81.82%
9 / 11
 hasCurlAdapterAsDefault
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 proxify
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
17 / 17
 get
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
9 / 9
 post
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 postForm
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 setDefaultAdapter
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 createClient
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
12 / 12
 createQueryString
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 send
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
7 / 7
 isAssocParams
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
4 / 4
 isLocal
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
<?php
/**
 * VuFind HTTP service class file.
 *
 * PHP version 7
 *
 * Copyright (C) Villanova University 2010.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * @category VuFind
 * @package  Http
 * @author   David Maus <maus@hab.de>
 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
 * @link     https://vufind.org/wiki/development
 */
namespace VuFindHttp;
/**
 * VuFind HTTP service.
 *
 * @category VuFind
 * @package  Http
 * @author   David Maus <maus@hab.de>
 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
 * @link     https://vufind.org/wiki/development
 */
class HttpService implements HttpServiceInterface
{
    /**
     * Default regular expression matching a request to localhost.
     *
     * @var string
     */
    const LOCAL_ADDRESS_RE = '@^(localhost|127(\.\d+){3}|\[::1\])@';
    /**
     * Proxy configuration.
     *
     * @see \Laminas\Http\Client\Adapter\Proxy::$config
     *
     * @var array
     */
    protected $proxyConfig;
    /**
     * Regular expression matching a request to localhost or hosts
     * that are not proxied.
     *
     * @see \Laminas\Http\Client\Adapter\Proxy::$config
     *
     * @var array
     */
    protected $localAddressesRegEx = self::LOCAL_ADDRESS_RE;
    /**
     * Default client options.
     *
     * @var array
     */
    protected $defaults;
    /**
     * Default adapter
     *
     * @var \Laminas\Http\Client\Adapter\AdapterInterface
     */
    protected $defaultAdapter = null;
    /**
     * Constructor.
     *
     * @param array $proxyConfig Proxy configuration
     * @param array $defaults    Default HTTP options
     * @param array $config      Other configuration
     *
     * @return void
     */
    public function __construct(array $proxyConfig = [],
        array $defaults = [], array $config = []
    ) {
        $this->proxyConfig = $proxyConfig;
        $this->defaults = $defaults;
        if (isset($config['localAddressesRegEx'])) {
            $this->localAddressesRegEx = $config['localAddressesRegEx'];
        }
    }
    /**
     * Set proxy options in a Curl adapter.
     *
     * @param \Laminas\Http\Client\Adapter\Curl $adapter Adapter to configure
     *
     * @return void
     */
    protected function setCurlProxyOptions($adapter)
    {
        $adapter->setCurlOption(CURLOPT_PROXY, $this->proxyConfig['proxy_host']);
        if (!empty($this->proxyConfig['proxy_port'])) {
            $adapter
                ->setCurlOption(CURLOPT_PROXYPORT, $this->proxyConfig['proxy_port']);
        }
        // HTTP is default, so handle only the SOCKS 5 proxy types
        switch ($this->proxyConfig['proxy_type'] ?? '') {
        case 'socks5':
            $adapter->setCurlOption(CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
            break;
        case 'socks5_hostname':
            $adapter->setCurlOption(CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
            break;
        }
    }
    /**
     * Are we configured to use the CURL adapter?
     *
     * @return bool
     */
    protected function hasCurlAdapterAsDefault()
    {
        $default = $this->defaults['adapter']
            ?? ($this->defaultAdapter ? get_class($this->defaultAdapter) : '');
        return $default === 'Laminas\Http\Client\Adapter\Curl';
    }
    /**
     * Proxify an existing client.
     *
     * Returns the client given as argument with appropriate proxy setup.
     *
     * @param \Laminas\Http\Client $client  HTTP client
     * @param array                $options Laminas ProxyAdapter options
     *
     * @return \Laminas\Http\Client
     */
    public function proxify(\Laminas\Http\Client $client, array $options = [])
    {
        if ($this->proxyConfig) {
            $host = $client->getUri()->getHost();
            if (!$this->isLocal($host)) {
                $proxyType = $this->proxyConfig['proxy_type'] ?? 'default';
                if (in_array($proxyType, ['socks5', 'socks5_hostname'])) {
                    $adapter = new \Laminas\Http\Client\Adapter\Curl();
                    // Apply proxy options for Curl adapter:
                    $this->setCurlProxyOptions($adapter);
                    $client->setAdapter($adapter);
                } elseif ($proxyType == 'default') {
                    // If the user has manually configured a Curl adapter,
                    // configure it for proxy compatibility; otherwise, create
                    // a fresh Proxy adapter.
                    if ($this->hasCurlAdapterAsDefault()) {
                        $adapter = new \Laminas\Http\Client\Adapter\Curl();
                        $this->setCurlProxyOptions($adapter);
                    } else {
                        $adapter = new \Laminas\Http\Client\Adapter\Proxy();
                        $options = array_replace($this->proxyConfig, $options);
                        $adapter->setOptions($options);
                    }
                    $client->setAdapter($adapter);
                }
            }
        }
        return $client;
    }
    /**
     * Perform a GET request.
     *
     * @param string $url     Request URL
     * @param array  $params  Request parameters
     * @param float  $timeout Request timeout in seconds
     * @param array  $headers Request headers
     *
     * @return \Laminas\Http\Response
     */
    public function get($url, array $params = [], $timeout = null,
        array $headers = []
    ) {
        if ($params) {
            $query = $this->createQueryString($params);
            if (strpos($url, '?') !== false) {
                $url .= '&' . $query;
            } else {
                $url .= '?' . $query;
            }
        }
        $client
            = $this->createClient($url, \Laminas\Http\Request::METHOD_GET, $timeout);
        if ($headers) {
            $client->setHeaders($headers);
        }
        return $this->send($client);
    }
    /**
     * Perform a POST request.
     *
     * @param string $url     Request URL
     * @param mixed  $body    Request body document
     * @param string $type    Request body content type
     * @param float  $timeout Request timeout in seconds
     * @param array  $headers Request http-headers
     *
     * @return \Laminas\Http\Response
     */
    public function post($url, $body = null, $type = 'application/octet-stream',
        $timeout = null, array $headers = []
    ) {
        $client = $this
            ->createClient($url, \Laminas\Http\Request::METHOD_POST, $timeout);
        $client->setRawBody($body);
        $client->setHeaders(
            array_merge(
                ['Content-Type' => $type, 'Content-Length' => strlen($body)],
                $headers
            )
        );
        return $this->send($client);
    }
    /**
     * Post form data.
     *
     * @param string $url     Request URL
     * @param array  $params  Form data
     * @param float  $timeout Request timeout in seconds
     *
     * @return \Laminas\Http\Response
     */
    public function postForm($url, array $params = [], $timeout = null)
    {
        $body = $this->createQueryString($params);
        return $this->post(
            $url, $body, \Laminas\Http\Client::ENC_URLENCODED, $timeout
        );
    }
    /**
     * Set a default HTTP adapter (primarily for testing purposes).
     *
     * @param \Laminas\Http\Client\Adapter\AdapterInterface $adapter Adapter
     *
     * @return void
     */
    public function setDefaultAdapter(
        \Laminas\Http\Client\Adapter\AdapterInterface $adapter
    ) {
        $this->defaultAdapter = $adapter;
    }
    /**
     * Return a new HTTP client.
     *
     * @param string $url     Target URL
     * @param string $method  Request method
     * @param float  $timeout Request timeout in seconds
     *
     * @return \Laminas\Http\Client
     */
    public function createClient($url = null,
        $method = \Laminas\Http\Request::METHOD_GET, $timeout = null
    ) {
        $client = new \Laminas\Http\Client();
        $client->setMethod($method);
        if (!empty($this->defaults)) {
            $client->setOptions($this->defaults);
        }
        if (null !== $this->defaultAdapter) {
            $client->setAdapter($this->defaultAdapter);
        }
        if (null !== $url) {
            $client->setUri($url);
        }
        if ($timeout) {
            $client->setOptions(['timeout' => $timeout]);
        }
        $this->proxify($client);
        return $client;
    }
    /// Internal API
    /**
     * Return query string based on params.
     *
     * @param array $params Parameters
     *
     * @return string
     */
    protected function createQueryString(array $params = [])
    {
        if ($this->isAssocParams($params)) {
            return http_build_query($params);
        } else {
            return implode('&', $params);
        }
    }
    /**
     * Send HTTP request and return response.
     *
     * @param \Laminas\Http\Client $client HTTP client to use
     *
     * @throws Exception\RuntimeException
     * @return \Laminas\Http\Response
     *
     * @todo Catch more exceptions, maybe?
     */
    protected function send(\Laminas\Http\Client $client)
    {
        try {
            $response = $client->send();
        } catch (\Laminas\Http\Client\Exception\RuntimeException $e) {
            throw new Exception\RuntimeException(
                sprintf('Laminas HTTP Client exception: %s', $e),
                -1,
                $e
            );
        }
        return $response;
    }
    /**
     * Return TRUE if argument is an associative array.
     *
     * @param array $array Array to test
     *
     * @return boolean
     */
    public static function isAssocParams(array $array)
    {
        foreach (array_keys($array) as $key) {
            if (!is_numeric($key)) {
                return true;
            }
        }
        return false;
    }
    /**
     * Return TRUE if argument refers to localhost.
     *
     * @param string $host Host to check
     *
     * @return boolean
     */
    protected function isLocal($host)
    {
        return preg_match($this->localAddressesRegEx, $host);
    }
}