Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
64.86% covered (warning)
64.86%
24 / 37
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
file_downloader
64.86% covered (warning)
64.86%
24 / 37
50.00% covered (danger)
50.00%
2 / 4
20.33
0.00% covered (danger)
0.00%
0 / 1
 create_client
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 get
91.67% covered (success)
91.67%
22 / 24
0.00% covered (danger)
0.00%
0 / 1
8.04
 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        try
79        {
80            // Initialize Guzzle client
81            $client = $this->create_client($host, $port, $timeout);
82        }
83        catch (\RuntimeException $exception)
84        {
85            throw new runtime_exception('HTTP_HANDLER_NOT_FOUND');
86        }
87
88        // Set default values for error variables
89        $this->error_number = 0;
90        $this->error_string = '';
91
92        try
93        {
94            $response = $client->request('GET', "$directory/$filename");
95
96            // Check if the response status code is 200 (OK)
97            if ($response->getStatusCode() == self::OK)
98            {
99                return $response->getBody()->getContents();
100            }
101            else
102            {
103                $this->error_number = $response->getStatusCode();
104                throw new runtime_exception('FILE_NOT_FOUND', [$filename]);
105            }
106        }
107        catch (RequestException $exception)
108        {
109            if ($exception->hasResponse())
110            {
111                $this->error_number = $exception->getResponse()->getStatusCode();
112
113                if ($this->error_number == self::NOT_FOUND)
114                {
115                    throw new runtime_exception('FILE_NOT_FOUND', [$filename]);
116                }
117            }
118            else
119            {
120                $this->error_number = self::REQUEST_TIMEOUT;
121                throw new runtime_exception('FSOCK_TIMEOUT');
122            }
123
124            $this->error_string = utf8_convert_message($exception->getMessage());
125            return false;
126        }
127        catch (runtime_exception $exception)
128        {
129            // Rethrow runtime_exceptions, only handle unknown cases below
130            throw $exception;
131        }
132        catch (\Throwable $exception)
133        {
134            $this->error_string = utf8_convert_message($exception->getMessage());
135            throw new runtime_exception('FSOCK_DISABLED');
136        }
137    }
138
139    /**
140     * Get error string
141     *
142     * @return string Error string
143     */
144    public function get_error_string(): string
145    {
146        return $this->error_string;
147    }
148
149    /**
150     * Get error number
151     *
152     * @return int Error number
153     */
154    public function get_error_number(): int
155    {
156        return $this->error_number;
157    }
158}