Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
file_downloader
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
4 / 4
12
100.00% covered (success)
100.00%
1 / 1
 create_client
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 get
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
7
 get_error_string
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_error_number
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14namespace phpbb;
15
16use GuzzleHttp\Client;
17use GuzzleHttp\Exception\RequestException;
18use phpbb\exception\runtime_exception;
19
20class file_downloader
21{
22    const OK = 200;
23    const NOT_FOUND = 404;
24    const REQUEST_TIMEOUT = 408;
25
26    /** @var string Error string */
27    protected string $error_string = '';
28
29    /** @var int Error number */
30    protected int $error_number = 0;
31
32    /**
33     * Create new guzzle client
34     *
35     * @param string $host
36     * @param int $port
37     * @param int $timeout
38     *
39     * @return Client
40     */
41    protected function create_client(string $host, int $port = 443, int $timeout = 6): Client
42    {
43        // Only set URL scheme if not specified in URL
44        $url_parts = parse_url($host);
45        if (!isset($url_parts['scheme']))
46        {
47            $host = (($port === 443) ? 'https://' : 'http://') . $host;
48        }
49
50        // Initialize Guzzle client
51        return new Client([
52            'base_uri' => $host,
53            'timeout'  => $timeout,
54            'headers' => [
55                'user-agent' => 'phpBB/' . PHPBB_VERSION,
56                'accept' => '*/*',
57              ],
58        ]);
59    }
60
61    /**
62     * Retrieve contents from remotely stored file
63     *
64     * @param string    $host            File host
65     * @param string    $directory       Directory file is in
66     * @param string    $filename        Filename of file to retrieve
67     * @param int       $port            Port to connect to; default: 443
68     * @param int       $timeout         Connection timeout in seconds; default: 6
69     *
70     * @return false|string File data as string if file can be read and there is no
71     *         timeout, false if there were errors or the connection timed out
72     *
73     * @throws runtime_exception If data can't be retrieved and no error
74     *         message is returned
75     */
76    public function get(string $host, string $directory, string $filename, int $port = 443, int $timeout = 6): bool|string
77    {
78        // Initialize Guzzle client
79        $client = $this->create_client($host, $port, $timeout);
80
81        // Set default values for error variables
82        $this->error_number = 0;
83        $this->error_string = '';
84
85        try
86        {
87            $response = $client->request('GET', "$directory/$filename");
88
89            // Check if the response status code is 200 (OK)
90            if ($response->getStatusCode() == self::OK)
91            {
92                return $response->getBody()->getContents();
93            }
94            else
95            {
96                $this->error_number = $response->getStatusCode();
97                throw new runtime_exception('FILE_NOT_FOUND', [$filename]);
98            }
99        }
100        catch (RequestException $exception)
101        {
102            if ($exception->hasResponse())
103            {
104                $this->error_number = $exception->getResponse()->getStatusCode();
105
106                if ($this->error_number == self::NOT_FOUND)
107                {
108                    throw new runtime_exception('FILE_NOT_FOUND', [$filename]);
109                }
110            }
111            else
112            {
113                $this->error_number = self::REQUEST_TIMEOUT;
114                throw new runtime_exception('FSOCK_TIMEOUT');
115            }
116
117            $this->error_string = utf8_convert_message($exception->getMessage());
118            return false;
119        }
120        catch (runtime_exception $exception)
121        {
122            // Rethrow runtime_exceptions, only handle unknown cases below
123            throw $exception;
124        }
125        catch (\Throwable $exception)
126        {
127            $this->error_string = utf8_convert_message($exception->getMessage());
128            throw new runtime_exception('FSOCK_DISABLED');
129        }
130    }
131
132    /**
133     * Get error string
134     *
135     * @return string Error string
136     */
137    public function get_error_string(): string
138    {
139        return $this->error_string;
140    }
141
142    /**
143     * Get error number
144     *
145     * @return int Error number
146     */
147    public function get_error_number(): int
148    {
149        return $this->error_number;
150    }
151}