Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 51 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
GuzzleService | |
0.00% |
0 / 51 |
|
0.00% |
0 / 4 |
272 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
createClient | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGuzzleConfig | |
0.00% |
0 / 46 |
|
0.00% |
0 / 1 |
132 | |||
isLocal | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | /** |
4 | * Guzzle service. |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) The National Library of Finland 2024. |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License version 2, |
12 | * as published by the Free Software Foundation. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | * GNU General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU General Public License |
20 | * along with this program; if not, write to the Free Software |
21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 | * |
23 | * @category VuFind |
24 | * @package Http |
25 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
26 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
27 | * @link https://vufind.org/wiki/development |
28 | */ |
29 | |
30 | namespace VuFind\Http; |
31 | |
32 | /** |
33 | * Guzzle service. |
34 | * |
35 | * N.B. Use only for dependencies that require Guzzle. |
36 | * |
37 | * @category VuFind |
38 | * @package Http |
39 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
40 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
41 | * @link https://vufind.org/wiki/development |
42 | * @todo Merge with PSR-18 HTTP Client Service when implemented |
43 | */ |
44 | class GuzzleService |
45 | { |
46 | /** |
47 | * Default regular expression matching a request to localhost. |
48 | * |
49 | * @var string |
50 | */ |
51 | public const LOCAL_ADDRESS_RE = '@^(localhost|127(\.\d+){3}|\[::1\])@'; |
52 | |
53 | /** |
54 | * VuFind configuration |
55 | * |
56 | * @var array |
57 | */ |
58 | protected $config; |
59 | |
60 | /** |
61 | * Regular expression matching a request to localhost or hosts |
62 | * that are not proxied. |
63 | * |
64 | * @see \Laminas\Http\Client\Adapter\Proxy::$config |
65 | * |
66 | * @var string |
67 | */ |
68 | protected $localAddressesRegEx = self::LOCAL_ADDRESS_RE; |
69 | |
70 | /** |
71 | * Mappings from VuFind HTTP settings to Guzzle |
72 | * |
73 | * @var array |
74 | */ |
75 | protected $guzzleHttpSettingsMap = [ |
76 | 'timeout' => 'timeout', |
77 | 'curloptions' => 'curl', |
78 | ]; |
79 | |
80 | /** |
81 | * Constructor. |
82 | * |
83 | * @param array $config VuFind configuration |
84 | * |
85 | * @return void |
86 | */ |
87 | public function __construct(array $config) |
88 | { |
89 | $this->config = $config; |
90 | if (isset($config['Proxy']['localAddressesRegEx'])) { |
91 | $this->localAddressesRegEx = $config['Proxy']['localAddressesRegEx']; |
92 | } |
93 | } |
94 | |
95 | /** |
96 | * Return a new Guzzle client. |
97 | * |
98 | * @param ?string $url Target URL (required for proper proxy setup for non-local addresses) |
99 | * @param ?float $timeout Request timeout in seconds (overrides configuration) |
100 | * |
101 | * @return \GuzzleHttp\ClientInterface |
102 | */ |
103 | public function createClient(?string $url = null, ?float $timeout = null): \GuzzleHttp\ClientInterface |
104 | { |
105 | return new \GuzzleHttp\Client($this->getGuzzleConfig($url, $timeout)); |
106 | } |
107 | |
108 | /** |
109 | * Get Guzzle options |
110 | * |
111 | * @param ?string $url Target URL (required for proper proxy setup for non-local addresses) |
112 | * @param ?float $timeout Request timeout in seconds |
113 | * |
114 | * @return array |
115 | */ |
116 | protected function getGuzzleConfig(?string $url, ?float $timeout): array |
117 | { |
118 | $guzzleConfig = $this->config['Http'] ?? []; |
119 | |
120 | // Map known one-to-one configuration settings to Guzzle settings: |
121 | $guzzleConfig = array_combine( |
122 | array_map( |
123 | function ($key) { |
124 | return $this->guzzleHttpSettingsMap[$key] ?? $key; |
125 | }, |
126 | array_keys($guzzleConfig) |
127 | ), |
128 | array_values($guzzleConfig) |
129 | ); |
130 | |
131 | // Override timeout if requested: |
132 | if (null !== $timeout) { |
133 | $guzzleConfig['timeout'] = $timeout; |
134 | } |
135 | |
136 | // Handle maxredirects: |
137 | if (isset($guzzleConfig['maxredirects'])) { |
138 | $guzzleConfig['allow_redirects'] = [ |
139 | 'max' => $guzzleConfig['maxredirects'], |
140 | 'strict' => $guzzleConfig['strictredirects'] ?? false, |
141 | 'referer' => false, |
142 | 'protocols' => ['http', 'https'], |
143 | 'track_redirects' => false, |
144 | ]; |
145 | unset($guzzleConfig['maxredirects']); |
146 | unset($guzzleConfig['strictredirects']); |
147 | } |
148 | |
149 | // Handle useragent: |
150 | if (isset($guzzleConfig['useragent'])) { |
151 | $guzzleConfig['headers']['User-Agent'] = $guzzleConfig['useragent']; |
152 | unset($guzzleConfig['useragent']); |
153 | } |
154 | |
155 | // Handle sslcapath, sslcafile and sslverifypeer: |
156 | if ($guzzleConfig['sslverifypeer'] ?? true) { |
157 | if ($verify = $guzzleConfig['sslcafile'] ?? $guzzleConfig['sslcapath'] ?? null) { |
158 | $guzzleConfig['verify'] = $verify; |
159 | } |
160 | } else { |
161 | $guzzleConfig['verify'] = false; |
162 | } |
163 | unset($guzzleConfig['sslverifypeer']); |
164 | unset($guzzleConfig['sslcapath']); |
165 | unset($guzzleConfig['sslcafile']); |
166 | |
167 | // Handle proxy configuration: |
168 | if (!$this->isLocal($url)) { |
169 | $proxyConfig = $this->config['Proxy'] ?? []; |
170 | if (!empty($proxyConfig['host'])) { |
171 | $guzzleConfig['curl'][CURLOPT_PROXY] = $proxyConfig['host']; |
172 | } |
173 | if (!empty($proxyConfig['port'])) { |
174 | $guzzleConfig['curl'][CURLOPT_PROXYPORT] = $proxyConfig['port']; |
175 | } |
176 | // HTTP is default, so handle only the SOCKS 5 proxy types |
177 | switch ($proxyConfig['type'] ?? '') { |
178 | case 'socks5': |
179 | $guzzleConfig['curl'][CURLOPT_PROXYTYPE] = CURLPROXY_SOCKS5; |
180 | break; |
181 | case 'socks5_hostname': |
182 | $guzzleConfig['curl'][CURLOPT_PROXYTYPE] = CURLPROXY_SOCKS5_HOSTNAME; |
183 | break; |
184 | } |
185 | } |
186 | return $guzzleConfig; |
187 | } |
188 | |
189 | /** |
190 | * Check if given URL is a local address |
191 | * |
192 | * @param ?string $host Host to check |
193 | * |
194 | * @return bool |
195 | */ |
196 | protected function isLocal(?string $host): bool |
197 | { |
198 | return $host && preg_match($this->localAddressesRegEx, $host); |
199 | } |
200 | } |