Skip to content

Commit f69df72

Browse files
committed
Add support for head & update tests
1 parent 8392ffb commit f69df72

6 files changed

Lines changed: 93 additions & 67 deletions

File tree

src/VerifierServer/Endpoints/EndpointInterface.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ interface EndpointInterface
1919
* @param string $method The HTTP method of the request (e.g., 'GET', 'POST').
2020
* @param ServerRequestInterface|string $request The request payload, typically used for 'POST' requests.
2121
* @param int|string &$response The variable to store the generated response.
22-
* @param array &$content_type The variable to store the content type of the response.
22+
* @param array &$headers The variable to store the headers of the response.
2323
* @param string &$body The variable to store the body of the response.
2424
*/
2525
public function handle(
2626
string $method,
2727
ServerRequestInterface|string $request,
2828
int|string &$response,
29-
array &$content_type,
29+
array &$headers,
3030
string &$body
3131
): void;
3232
}

src/VerifierServer/Endpoints/VerifiedEndpoint.php

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -56,30 +56,38 @@ public function __construct(private PersistentState &$state)
5656
* @param string $method The HTTP method of the request (e.g., 'GET', 'POST').
5757
* @param ServerRequestInterface|string $request The request payload, typically used for 'POST' requests.
5858
* @param string &$response The variable to store the generated response.
59-
* @param array &$content_type The variable to store the content type of the response.
59+
* @param array &$headers The variable to store the headers of the response.
6060
* @param string &$body The variable to store the body of the response.
6161
* @param bool $bypass_token Whether to bypass the token check. Default is false.
6262
*/
6363
public function handle(
6464
string $method,
6565
ServerRequestInterface|string $request,
6666
int|string &$response,
67-
array &$content_type,
67+
array &$headers,
6868
string &$body,
6969
bool $bypass_token = false
7070
): void
7171
{
7272
switch ($method) {
7373
case 'GET':
74-
$this->get($response, $content_type, $body);
74+
$this->get($response, $headers, $body);
75+
break;
76+
case 'HEAD':
77+
$this->head($response, $headers);
7578
break;
7679
case 'POST':
80+
case 'PUT':
7781
case 'DELETE':
78-
$this->post($request, $response, $content_type, $body, $bypass_token);
82+
$this->post($request, $response, $headers, $body, $bypass_token);
7983
break;
84+
case 'PATCH':
85+
case 'OPTIONS':
86+
case 'CONNECT':
87+
case 'TRACE':
8088
default:
8189
$response = Response::STATUS_METHOD_NOT_ALLOWED;
82-
$content_type = ['Content-Type' => 'text/plain'];
90+
$headers = ['Content-Type' => 'text/plain'];
8391
$body = 'Method Not Allowed';
8492
break;
8593
}
@@ -88,26 +96,53 @@ public function handle(
8896
/**
8997
* Handles the GET request and prepares the response.
9098
*
91-
* @param string &$response The response string to be sent back to the client.
92-
* @param array &$content_type The variable to store the content type of the response.
93-
* @param string &$body The variable to store the body of the response.
99+
* @param int|string &$response The response string to be sent back to the client.
100+
* @param array &$headers The variable to store the headers of the response.
101+
* @param string &$body The variable to store the body of the response.
102+
*
103+
* It appends the JSON-encoded verification list to the body of the response.
104+
*/
105+
private function get(int|string &$response, array &$headers, string &$body): void
106+
{
107+
$body = $this->head($response, $headers);
108+
}
109+
110+
/**
111+
* Sets the HTTP response status and content type for the HEAD request.
94112
*
95-
* This method sets the HTTP status code to 200 OK and the Content-Type to application/json.
96-
* It then appends the JSON-encoded verification list to the response.
113+
* @param int|string &$response The response string to be sent back to the client.
114+
* @param array &$headers The variable to store the headers of the response.
115+
*
116+
* @return string The content to be sent in the response body.
117+
*
118+
* This method sets the HTTP status code and headers.
97119
*/
98-
private function get(int|string &$response, array &$content_type, string &$body): void
120+
private function head(int|string &$response, array &$headers): string
99121
{
100122
$response = Response::STATUS_OK;
101-
$content_type = ['Content-Type' => 'application/json'];
102-
$body = json_encode($this->state->getVerifyList());
123+
$headers = ['Content-Type' => 'application/json'];
124+
$headers['Content-Length'] = ($content = $this->__content())
125+
? strlen($content)
126+
: 0;
127+
return $content;
128+
}
129+
130+
/**
131+
* Encodes the verification list retrieved from the state into a JSON string.
132+
*
133+
* @return string|false Returns the JSON-encoded string on success, or false on failure.
134+
*/
135+
private function __content(): string|false
136+
{
137+
return json_encode($this->state->getVerifyList());
103138
}
104139

105140
/**
106141
* Handles POST requests by parsing the request data and performing actions based on the method type.
107142
*
108143
* @param ServerRequestInterface|string $request The raw HTTP request string.
109144
* @param string &$response The response string to be modified based on the request handling.
110-
* @param array &$content_type The variable to store the content type of the response.
145+
* @param array &$headers The variable to store the headers of the response.
111146
* @param string &$body The variable to store the body of the response.
112147
*
113148
* The function performs the following steps:
@@ -118,7 +153,7 @@ private function get(int|string &$response, array &$content_type, string &$body)
118153
* 5. Retrieves the verification list from the state.
119154
* 6. Based on the method type, either deletes an entry from the list or handles the default case.
120155
*/
121-
private function post(ServerRequestInterface|string $request, int|string &$response, array &$content_type, string &$body, bool $bypass_token = false): void
156+
private function post(ServerRequestInterface|string $request, int|string &$response, array &$headers, string &$body, bool $bypass_token = false): void
122157
{
123158
$formData = $request instanceof ServerRequestInterface
124159
? $request->getHeaders()
@@ -141,7 +176,7 @@ private function post(ServerRequestInterface|string $request, int|string &$respo
141176

142177
if (!$bypass_token && ($this->state->getToken() === 'changeme' || $token !== $this->state->getToken())) {
143178
$response = Response::STATUS_UNAUTHORIZED;
144-
$content_type = ['Content-Type' => 'text/plain'];
179+
$headers = ['Content-Type' => 'text/plain'];
145180
$body = 'Unauthorized';
146181
return;
147182
}
@@ -156,7 +191,7 @@ private function post(ServerRequestInterface|string $request, int|string &$respo
156191
$existingIndex,
157192
$list,
158193
$response,
159-
$content_type,
194+
$headers,
160195
$body
161196
);
162197
break;
@@ -166,7 +201,7 @@ private function post(ServerRequestInterface|string $request, int|string &$respo
166201
$ckey,
167202
$discord,
168203
$response,
169-
$content_type,
204+
$headers,
170205
$body
171206
);
172207
break;
@@ -185,17 +220,17 @@ private function post(ServerRequestInterface|string $request, int|string &$respo
185220
* @param string $ckey The ckey to be verified.
186221
* @param string $discord The discord identifier to be verified.
187222
* @param string &$response The response message to be set based on the verification result.
188-
* @param array &$content_type The variable to store the content type of the response.
223+
* @param array &$headers The variable to store the headers of the response.
189224
* @param string &$body The variable to store the body of the response.
190225
*/
191-
private function __post(array &$list, string $ckey, string $discord, int|string &$response, array &$content_type, string &$body): void
226+
private function __post(array &$list, string $ckey, string $discord, int|string &$response, array &$headers, string &$body): void
192227
{
193228
$existingCkeyIndex = array_search($ckey, array_column($list, 'ss13'));
194229
$existingDiscordIndex = array_search($discord, array_column($list, 'discord'));
195230

196231
if ($existingCkeyIndex !== false || $existingDiscordIndex !== false) {
197232
$response = Response::STATUS_FORBIDDEN;
198-
$content_type = ['Content-Type' => 'text/plain'];
233+
$headers = ['Content-Type' => 'text/plain'];
199234
$body = 'Forbidden';
200235
return;
201236
}
@@ -206,9 +241,7 @@ private function __post(array &$list, string $ckey, string $discord, int|string
206241
];
207242
PersistentState::writeJson($this->state->getJsonPath(), $list);
208243
$this->state->setVerifyList($list);
209-
$response = Response::STATUS_OK;
210-
$content_type = ['Content-Type' => 'application/json'];
211-
$body = json_encode($list);
244+
$body = json_encode($this->head($response, $headers));
212245
}
213246

214247
/**
@@ -217,22 +250,24 @@ private function __post(array &$list, string $ckey, string $discord, int|string
217250
* @param int|string|false $existingIndex The index of the item to delete, or false if the item does not exist.
218251
* @param array &$list The list from which the item will be deleted.
219252
* @param string &$response The HTTP response message to be returned.
220-
* @param array &$content_type The variable to store the content type of the response.
253+
* @param array &$headers The variable to store the headers of the response.
221254
* @param string &$body The variable to store the body of the response.
222255
*/
223-
private function delete(int|string|false $existingIndex, array &$list, int|string &$response, array &$content_type, string &$body): void
256+
private function delete(int|string|false $existingIndex, array &$list, int|string &$response, array &$headers, string &$body): void
224257
{
225258
if ($existingIndex === false) {
226259
$response = Response::STATUS_NOT_FOUND;
227-
$content_type = ['Content-Type' => 'text/plain'];
260+
$headers = ['Content-Type' => 'text/plain'];
228261
$body = 'Not Found';
229262
return;
230263
}
231264
$splice = array_splice($list, $existingIndex, 1);
232265
PersistentState::writeJson($this->state->getJsonPath(), $list);
233266
$this->state->setVerifyList($list);
234267
$response = Response::STATUS_OK;
235-
$content_type = ['Content-Type' => 'application/json'];
236-
$body = json_encode($splice);
268+
$headers = ['Content-Type' => 'application/json'];
269+
$headers['Content-Length'] = ($content = json_encode($splice))
270+
? strlen($body = $content)
271+
: 0;
237272
}
238273
}

src/VerifierServer/PersistentState.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,7 @@ public function setVerifyList(array $list, bool $write = true): void
143143
if ($this->pdo->exec("DELETE FROM verify_list") === false) {
144144
throw new PDOException("Failed to delete from verify_list: " . implode(", ", $this->pdo->errorInfo()));
145145
}
146-
$stmt = $this->pdo->prepare("INSERT INTO verify_list (ss13, discord, create_time) VALUES (:ss13, :discord, :create_time)");
147-
if ($stmt === false) {
146+
if (($stmt = $this->pdo->prepare("INSERT INTO verify_list (ss13, discord, create_time) VALUES (:ss13, :discord, :create_time)")) === false) {
148147
throw new PDOException("Failed to prepare statement.");
149148
}
150149
foreach ($list as $item) if (!$stmt->execute($item)) {

src/VerifierServer/Server.php

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ public static function arrayToRequestString(array $formData): string
247247
* @param string $method The HTTP method of the request (e.g., 'GET', 'POST').
248248
* @param ServerRequestInterface|string $request The request payload, typically used for 'POST' requests.
249249
* @param int|string &$response The variable to store the generated response.
250-
* @param array &$content_type The variable to store the content type of the response.
250+
* @param array &$headers The variable to store the content type of the response.
251251
* @param string &$body The variable to store the body of the response.
252252
* @param bool $bypass_token Whether to bypass the token check.
253253
*/
@@ -256,13 +256,13 @@ public function handleEndpoint(
256256
string $method,
257257
ServerRequestInterface|string $request,
258258
int|string &$response,
259-
array &$content_type,
259+
array &$headers,
260260
string &$body,
261261
bool $bypass_token = false
262262
): void
263263
{
264264
if (isset($this->endpoints[$uri]) && $this->endpoints[$uri] instanceof EndpointInterface) {
265-
$this->endpoints[$uri]->handle($method, $request, $response, $content_type, $body, $bypass_token);
265+
$this->endpoints[$uri]->handle($method, $request, $response, $headers, $body, $bypass_token);
266266
}
267267
}
268268

@@ -280,14 +280,14 @@ private function handleReact(ServerRequestInterface $client): ResponseInterface
280280

281281
// Defaults
282282
$response = Response::STATUS_NOT_FOUND;
283-
$content_type = ['Content-Type' => 'text/plain'];
283+
$headers = ['Content-Type' => 'text/plain'];
284284
$body = "Not Found";
285285

286-
$this->handleEndpoint($uri, $method, $client, $response, $content_type, $body);
286+
$this->handleEndpoint($uri, $method, $client, $response, $headers, $body);
287287

288288
return new Response(
289289
$response,
290-
$content_type,
290+
$headers,
291291
$body
292292
);
293293
}
@@ -304,38 +304,29 @@ private function handleReact(ServerRequestInterface $client): ResponseInterface
304304
private function handleResource($client): null
305305
{
306306
$request = fread($client, 1024);
307-
$headers = [];
308-
$lines = explode(PHP_EOL, trim($request));
309-
foreach ($lines as $line) {
310-
if (strpos($line, ':') !== false) {
311-
[$key, $value] = explode(':', $line, 2);
312-
$headers[trim($key)] = [trim($value)];
313-
}
314-
}
315-
316307
if ($request === false) {
317308
throw new Exception("Failed to read from client");
318309
}
319-
$lines = explode(PHP_EOL, trim($request));
320-
$firstLine = explode(' ', $lines[0]);
310+
$client_headers = explode(PHP_EOL, trim($request));
311+
$firstLine = explode(' ', array_shift($client_headers));
321312
$method = $firstLine[0] ?? '';
322313
$uri = $firstLine[1] ?? '/';
314+
$protocol = $firstLine[2] ?? 'HTTP/1.1';
323315

324-
$response = $firstLine[2] ?? "HTTP/1.1";
325-
$response .= " 200 OK";
326-
$content_type = ['Content-Type' => 'application/json'];
316+
$response = $protocol . " 200 OK";
317+
$headers = ['Content-Type' => 'application/json'];
327318
$body = "";
328319

329320
switch ($uri) {
330321
case '/':
331322
case '/verified':
332323
$endpoint = new VerifiedEndpoint($this->state);
333-
$endpoint->handle($method, $request, $response, $content_type, $body);
324+
$endpoint->handle($method, $request, $response, $headers, $body);
334325
break;
335326

336327
default:
337-
$response = "HTTP/1.1 404 Not Found" . PHP_EOL . "Content-Type: text/html" . PHP_EOL . PHP_EOL;
338-
$body = "<h1>Not Found</h1>";
328+
$response = "$protocol 404 Not Found" . PHP_EOL . "Content-Type: text/plain" . PHP_EOL . PHP_EOL;
329+
$body = "Not Found";
339330
break;
340331
}
341332

@@ -353,9 +344,9 @@ private function handleResource($client): null
353344
503 => "Service Unavailable",
354345
];
355346
$statusText = $statusTexts[$response] ?? "Unknown Status";
356-
$response = "HTTP/1.1 $response $statusText";
347+
$response = "$protocol $response $statusText";
357348
}
358-
if (fwrite($client, $response . PHP_EOL . implode(PHP_EOL, array_map(fn($key, $value) => "$key: $value", array_keys($content_type), $content_type)) . PHP_EOL . PHP_EOL . $body) === false) {
349+
if (fwrite($client, $response . PHP_EOL . implode(PHP_EOL, array_map(fn($key, $value) => "$key: $value", array_keys($headers), $headers)) . PHP_EOL . PHP_EOL . $body) === false) {
359350
throw new Exception("Failed to write to client");
360351
}
361352
fclose($client);

tests/PersistentStateTest.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ public function testSetVerifyList(): void
3838
['ss13' => 'test2', 'discord' => 'test2', 'create_time' => date('Y-m-d H:i:s')]
3939
];
4040
$this->state->setVerifyList($list, false);
41-
$verifyList = $this->state->getVerifyList(true);
42-
$this->assertEquals($list, $verifyList);
41+
$this->assertEquals(
42+
$list,
43+
$this->state->getVerifyList(true)
44+
);
4345
}
4446

4547
/**
@@ -51,7 +53,6 @@ public function testSetVerifyList(): void
5153
*/
5254
public function testGetToken(): void
5355
{
54-
$token = $this->state->getToken();
55-
$this->assertIsString($token);
56+
$this->assertIsString($this->state->getToken());
5657
}
5758
}

tests/VerifiedEndpointTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ public function testPost() {
4343
],
4444
];
4545
$formData = [
46-
'method' => 'POST',
47-
'ckey' => $ckey,
46+
'method' => 'POST',
47+
'ckey' => $ckey,
4848
'discord' => $discord,
49-
'token' => 'testToken'
49+
'token' => $this->state->getToken()
5050
];
5151

5252
$method = 'POST';
@@ -58,10 +58,10 @@ public function testPost() {
5858
* @return string The reconstructed string.
5959
*/
6060
$response = 0;
61-
$content_type = [];
61+
$headers = [];
6262
$body = "";
6363
$bypass_token = true;
64-
$this->endpoint->handle($method, Server::arrayToRequestString($formData), $response, $content_type, $body, $bypass_token);
64+
$this->endpoint->handle($method, Server::arrayToRequestString($formData), $response, $headers, $body, $bypass_token);
6565

6666
//$this->assertArrayHasKey($list, $this->state->getVerifyList(true));
6767
$this->assertStringContainsString((string) Response::STATUS_OK, (string) $response);

0 commit comments

Comments
 (0)