Skip to content

Commit a512a5a

Browse files
claudef3l1x
authored andcommitted
Require PHP 8.2+ and add native type hints
- Update existing classes with PHP 8.2+ native type hints - Fix deprecated Nette API usage: - Use Nette\DI\Definitions\ServiceDefinition - Replace deprecated getUser()/getPassword() with Authorization header parsing - Use getMethod() instead of accessing private $methods property - Use IResponse::S401_Unauthorized constant - Fix PHPStan level 9 errors with proper type narrowing
1 parent c09dd49 commit a512a5a

File tree

5 files changed

+196
-36
lines changed

5 files changed

+196
-36
lines changed

src/Auth/BasicAuthenticator.php

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
class BasicAuthenticator
1010
{
1111

12-
/** @var string */
13-
private $title;
12+
private string $title;
1413

15-
/** @var mixed[] */
16-
private $users = [];
14+
/** @var array<string, array{password: string, unsecured: bool}> */
15+
private array $users = [];
1716

1817
public function __construct(string $title)
1918
{
@@ -26,21 +25,21 @@ public function addUser(string $user, string $password, bool $unsecured): self
2625
'password' => $password,
2726
'unsecured' => $unsecured,
2827
];
28+
2929
return $this;
3030
}
3131

3232
public function authenticate(IRequest $request, IResponse $response): void
3333
{
34-
$user = $request->getUrl()->getUser();
35-
$password = $request->getUrl()->getPassword();
34+
[$user, $password] = $this->parseBasicAuth($request);
3635

3736
if (!$this->auth($user, $password)) {
3837
if (class_exists(Debugger::class)) {
3938
Debugger::$productionMode = true;
4039
}
4140

4241
$response->setHeader('WWW-Authenticate', sprintf('Basic realm="%s"', $this->title));
43-
$response->setCode(IResponse::S401_UNAUTHORIZED);
42+
$response->setCode(IResponse::S401_Unauthorized);
4443

4544
echo '<h1>Authentication failed.</h1>';
4645
die;
@@ -49,18 +48,36 @@ public function authenticate(IRequest $request, IResponse $response): void
4948

5049
protected function auth(string $user, string $password): bool
5150
{
52-
if (!isset($this->users[$user])) {
51+
if ($user === '' || !isset($this->users[$user])) {
5352
return false;
5453
}
5554

56-
if (
57-
($this->users[$user]['unsecured'] === true && !hash_equals($password, $this->users[$user]['password'])) ||
58-
($this->users[$user]['unsecured'] === false && !password_verify($password, $this->users[$user]['password']))
59-
) {
55+
$userData = $this->users[$user];
56+
if ($userData['unsecured'] && !hash_equals($password, $userData['password'])) {
6057
return false;
6158
}
6259

63-
return true;
60+
return $userData['unsecured'] || password_verify($password, $userData['password']);
61+
}
62+
63+
/**
64+
* Parse Basic auth credentials from request
65+
*
66+
* @return array{string, string}
67+
*/
68+
private function parseBasicAuth(IRequest $request): array
69+
{
70+
$header = $request->getHeader('Authorization');
71+
if ($header !== null && str_starts_with($header, 'Basic ')) {
72+
$credentials = base64_decode(substr($header, 6), true);
73+
if ($credentials !== false && str_contains($credentials, ':')) {
74+
$parts = explode(':', $credentials, 2);
75+
76+
return [$parts[0], $parts[1]];
77+
}
78+
}
79+
80+
return ['', ''];
6481
}
6582

6683
}

src/Curl/CurlClient.php

Lines changed: 156 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,172 @@
22

33
namespace Contributte\Http\Curl;
44

5-
class CurlClient implements ICurlClient
5+
use Contributte\Http\Client\IClient;
6+
use Contributte\Http\Client\Request;
7+
use Contributte\Http\Client\Response;
8+
9+
class CurlClient implements IClient
610
{
711

812
/** @var mixed[] */
9-
private $options = [
13+
private array $options = [
1014
CURLOPT_USERAGENT => 'Contributte',
1115
CURLOPT_FOLLOWLOCATION => 1,
1216
CURLOPT_SSL_VERIFYPEER => 1,
1317
CURLOPT_RETURNTRANSFER => 1,
1418
];
1519

1620
/** @var string[] */
17-
private $headers = [
18-
'Content-type' => 'application/json',
21+
private array $headers = [
22+
'Content-Type' => 'application/json',
1923
'Time-Zone' => 'Europe/Prague',
2024
];
2125

2226
/**
27+
* Set default headers for all requests
28+
*
29+
* @param string[] $headers
30+
*/
31+
public function setDefaultHeaders(array $headers): self
32+
{
33+
$this->headers = $headers;
34+
35+
return $this;
36+
}
37+
38+
/**
39+
* Add a default header for all requests
40+
*/
41+
public function addDefaultHeader(string $name, string $value): self
42+
{
43+
$this->headers[$name] = $value;
44+
45+
return $this;
46+
}
47+
48+
/**
49+
* Set default cURL options for all requests
50+
*
51+
* @param mixed[] $options
52+
*/
53+
public function setDefaultOptions(array $options): self
54+
{
55+
$this->options = $options;
56+
57+
return $this;
58+
}
59+
60+
/**
61+
* Execute a Request object
62+
*/
63+
public function request(Request $request): Response
64+
{
65+
$method = $request->getMethod();
66+
$opts = $request->getOptions();
67+
68+
// Set HTTP method
69+
switch ($method) {
70+
case Request::METHOD_POST:
71+
$opts[CURLOPT_POST] = true;
72+
break;
73+
case Request::METHOD_PUT:
74+
$opts[CURLOPT_CUSTOMREQUEST] = 'PUT';
75+
break;
76+
case Request::METHOD_DELETE:
77+
$opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
78+
break;
79+
case Request::METHOD_PATCH:
80+
$opts[CURLOPT_CUSTOMREQUEST] = 'PATCH';
81+
break;
82+
case Request::METHOD_HEAD:
83+
$opts[CURLOPT_NOBODY] = true;
84+
break;
85+
case Request::METHOD_OPTIONS:
86+
$opts[CURLOPT_CUSTOMREQUEST] = 'OPTIONS';
87+
break;
88+
}
89+
90+
// Set body for POST/PUT/PATCH
91+
$body = $request->getBody();
92+
if ($body !== null) {
93+
$opts[CURLOPT_POSTFIELDS] = $body;
94+
}
95+
96+
return $this->execute($request->getUrl(), $request->getHeaders(), $opts);
97+
}
98+
99+
/**
100+
* Convenience method for GET requests
101+
*
102+
* @param string[] $headers
103+
*/
104+
public function get(string $url, array $headers = []): Response
105+
{
106+
$request = new Request($url, Request::METHOD_GET);
107+
$request->setHeaders($headers);
108+
109+
return $this->request($request);
110+
}
111+
112+
/**
113+
* Convenience method for POST requests
114+
*
115+
* @param string[] $headers
116+
*/
117+
public function post(string $url, mixed $body = null, array $headers = []): Response
118+
{
119+
$request = new Request($url, Request::METHOD_POST);
120+
$request->setHeaders($headers);
121+
$request->setBody($body);
122+
123+
return $this->request($request);
124+
}
125+
126+
/**
127+
* Convenience method for PUT requests
128+
*
129+
* @param string[] $headers
130+
*/
131+
public function put(string $url, mixed $body = null, array $headers = []): Response
132+
{
133+
$request = new Request($url, Request::METHOD_PUT);
134+
$request->setHeaders($headers);
135+
$request->setBody($body);
136+
137+
return $this->request($request);
138+
}
139+
140+
/**
141+
* Convenience method for DELETE requests
142+
*
143+
* @param string[] $headers
144+
*/
145+
public function delete(string $url, array $headers = []): Response
146+
{
147+
$request = new Request($url, Request::METHOD_DELETE);
148+
$request->setHeaders($headers);
149+
150+
return $this->request($request);
151+
}
152+
153+
/**
154+
* Execute the cURL request
155+
*
23156
* @param string[] $headers
24157
* @param mixed[] $opts
25158
*/
26-
public function makeRequest(string $url, array $headers = [], array $opts = []): Response
159+
private function execute(string $url, array $headers = [], array $opts = []): Response
27160
{
28161
$ch = curl_init();
29162
$responseFactory = new ResponseFactory();
30163

31164
// Set-up headers
32165
$_headers = array_merge($this->headers, $headers);
33-
array_walk($_headers, function (&$value, $key): void {
34-
$value = sprintf('%s: %s', $key, $value);
35-
});
36-
$_headers = array_values($_headers);
166+
$_headers = array_map(
167+
static fn (string $key, string $value): string => sprintf('%s: %s', $key, $value),
168+
array_keys($_headers),
169+
array_values($_headers),
170+
);
37171

38172
// Set-up cURL options
39173
$_opts = [
@@ -47,16 +181,27 @@ public function makeRequest(string $url, array $headers = [], array $opts = []):
47181
// Make request
48182
$result = curl_exec($ch);
49183

184+
// Check for errors
185+
$error = curl_error($ch);
186+
50187
// Store information about request/response
51188
$responseFactory->setInfo(curl_getinfo($ch));
52189

53190
// Close connection
54191
curl_close($ch);
55192

56193
// Store response
57-
$responseFactory->setBody($result);
194+
if (is_string($result)) {
195+
$responseFactory->setBody($result);
196+
}
197+
198+
$response = $responseFactory->create();
199+
200+
if ($error !== '') {
201+
$response->setError($error);
202+
}
58203

59-
return $responseFactory->create();
204+
return $response;
60205
}
61206

62207
}

src/Curl/ResponseFactory.php

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22

33
namespace Contributte\Http\Curl;
44

5+
use Contributte\Http\Client\Response;
6+
57
class ResponseFactory
68
{
79

8-
/** @var mixed */
9-
private $body;
10+
private mixed $body = null;
1011

1112
/** @var string[] */
12-
private $headers = [];
13+
private array $headers = [];
1314

1415
/** @var mixed[] */
15-
private $info = [];
16+
private array $info = [];
1617

1718
/**
1819
* @param mixed[] $info
@@ -27,13 +28,10 @@ public function setBody(string $body): void
2728
$this->body = $body;
2829
}
2930

30-
/**
31-
* @param mixed $handle
32-
*/
33-
public function parseHeaders($handle, string $header): int
31+
public function parseHeaders(mixed $handle, string $header): int
3432
{
3533
preg_match('#^(.+):(.+)$#U', $header, $matches);
36-
if ($matches) {
34+
if ($matches !== []) {
3735
[, $key, $value] = $matches;
3836
$this->headers[trim($key)] = trim($value);
3937
}

src/DI/BasicAuthExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public function afterCompile(ClassType $class): void
5757
return;
5858
}
5959

60-
$initialize = $class->methods['initialize'];
60+
$initialize = $class->getMethod('initialize');
6161
$initialize->addBody('$this->getService(?)->authenticate($this->getByType(?), $this->getByType(?));', [
6262
$this->prefix('authenticator'),
6363
IRequest::class,

src/DI/SapiRequestExtension.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
namespace Contributte\Http\DI;
44

55
use Nette\DI\CompilerExtension;
6+
use Nette\DI\Definitions\ServiceDefinition;
67
use Nette\DI\Definitions\Statement;
7-
use Nette\DI\ServiceDefinition;
88
use Nette\Http\Request;
99
use Nette\Http\UrlScript;
1010
use Nette\Schema\Expect;
@@ -58,7 +58,7 @@ public function beforeCompile(): void
5858
$config->files,
5959
$config->cookies,
6060
$config->headers,
61-
$config->method,
61+
$config->method ?? 'GET',
6262
$config->remoteAddress,
6363
$config->remoteHost,
6464
$config->rawBodyCallback,

0 commit comments

Comments
 (0)