Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
31 / 31
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%
31 / 31
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%
7 / 7
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        ]);
55    }
56
57    /**
58     * Retrieve contents from remotely stored file
59     *
60     * @param string    $host            File host
61     * @param string    $directory       Directory file is in
62     * @param string    $filename        Filename of file to retrieve
63     * @param int       $port            Port to connect to; default: 80
64     * @param int       $timeout         Connection timeout in seconds; default: 6
65     *
66     * @return false|string File data as string if file can be read and there is no
67     *         timeout, false if there were errors or the connection timed out
68     *
69     * @throws runtime_exception If data can't be retrieved and no error
70     *         message is returned
71     */
72    public function get(string $host, string $directory, string $filename, int $port = 443, int $timeout = 6): bool|string
73    {
74        // Initialize Guzzle client
75        $client = $this->create_client($host, $port, $timeout);
76
77        // Set default values for error variables
78        $this->error_number = 0;
79        $this->error_string = '';
80
81        try
82        {
83            $response = $client->request('GET', "$directory/$filename");
84
85            // Check if the response status code is 200 (OK)
86            if ($response->getStatusCode() == self::OK)
87            {
88                return $response->getBody()->getContents();
89            }
90            else
91            {
92                $this->error_number = $response->getStatusCode();
93                throw new runtime_exception('FILE_NOT_FOUND', [$filename]);
94            }
95        }
96        catch (RequestException $exception)
97        {
98            if ($exception->hasResponse())
99            {
100                $this->error_number = $exception->getResponse()->getStatusCode();
101
102                if ($this->error_number == self::NOT_FOUND)
103                {
104                    throw new runtime_exception('FILE_NOT_FOUND', [$filename]);
105                }
106            }
107            else
108            {
109                $this->error_number = self::REQUEST_TIMEOUT;
110                throw new runtime_exception('FSOCK_TIMEOUT');
111            }
112
113            $this->error_string = utf8_convert_message($exception->getMessage());
114            return false;
115        }
116        catch (runtime_exception $exception)
117        {
118            // Rethrow runtime_exceptions, only handle unknown cases below
119            throw $exception;
120        }
121        catch (\Throwable $exception)
122        {
123            $this->error_string = utf8_convert_message($exception->getMessage());
124            throw new runtime_exception('FSOCK_DISABLED');
125        }
126    }
127
128    /**
129     * Get error string
130     *
131     * @return string Error string
132     */
133    public function get_error_string(): string
134    {
135        return $this->error_string;
136    }
137
138    /**
139     * Get error number
140     *
141     * @return int Error number
142     */
143    public function get_error_number(): int
144    {
145        return $this->error_number;
146    }
147}