Skip to content

Instantly share code, notes, and snippets.

@devhammed
Last active January 31, 2025 17:19
Show Gist options
  • Save devhammed/3a370391bc6e67a001b743d5c6d96db5 to your computer and use it in GitHub Desktop.
Save devhammed/3a370391bc6e67a001b743d5c6d96db5 to your computer and use it in GitHub Desktop.
Parse HTTP Request in PHP
<?php
function parse_http_request(string $request): array
{
[$rawHeaders, $rawBody] = mb_split('\r\n\r\n', $request, 2);
$rawHeaders = mb_split('\r\n', $rawHeaders);
$requestLine = array_shift($rawHeaders);
[$method, $path, $protocol] = mb_split(' ', $requestLine, 3);
$headers = [];
foreach ($rawHeaders as $rawHeader) {
[$key, $value] = mb_split(':', $rawHeader, 2);
if ( ! $key || ! $value) {
continue;
}
$key = mb_trim($key);
$key = mb_strtolower($key);
if ( ! isset($headers[$key])) {
$headers[$key] = [];
}
$headers[$key][] = mb_trim($value);
}
$contentType = $headers['content-type'][0] ?? 'application/octet-stream';
if (mb_stripos($contentType, 'application/x-www-form-urlencoded') !== false) {
$body = [];
$pairs = mb_split('&', $rawBody);
foreach ($pairs as $pair) {
if (mb_stripos($pair, '=') !== false) {
[$key, $value] = mb_split('=', $pair, 2);
$body[urldecode($key)] = urldecode($value);
}
}
} elseif (mb_stripos($contentType, 'multipart/form-data') !== false) {
$body = [];
$boundary = preg_match('/boundary=(.*)$/', $contentType, $matches) ? $matches[1] : '';
if ($boundary) {
$blocks = mb_split('--' . $boundary, $rawBody);
foreach ($blocks as $block) {
$block = mb_trim($block);
if ( ! $block || $block === '--') {
continue;
}
[$blockHeaders, $blockBody] = mb_split('\r\n\r\n', $block, 2);
if ( ! $blockHeaders || ! $blockBody) {
continue;
}
preg_match(
'/Content-Disposition: form-data; name="([^"]+)"(?:; filename="([^"]+)")?/', $blockHeaders,
$matches,
);
if ( ! $matches) {
continue;
}
$name = $matches[1] ?? null;
if ( ! $name) {
continue;
}
$filename = $matches[2] ?? null;
if ($filename) {
preg_match('/Content-Type: ([a-zA-Z0-9\-\/]+)/', $blockHeaders, $mimeMatches);
$tmpFile = tempnam(sys_get_temp_dir(), 'php');
$body[$name] = [
'name' => $filename,
'type' => $mimeMatches[1] ?? 'application/octet-stream',
'tmp_name' => $tmpFile,
'error' => UPLOAD_ERR_OK,
'size' => mb_strlen($blockBody),
];
file_put_contents($tmpFile, $blockBody);
} else {
$body[$name] = mb_trim($blockBody);
}
}
}
} elseif (mb_stripos($contentType, 'application/json') !== false) {
$body = json_decode($rawBody, true) ?? [];
} else {
$body = $rawBody;
}
return compact('method', 'path', 'protocol', 'headers', 'body');
}
/**
* Parsing URL-encoded Form.
*
* @example
* [
* "method" => "POST",
* "path" => "/users",
* "protocol" => "HTTP/1.1",
* "headers" => [
* "host" => [
* "example.com",
* ],
* "content-type" => [
* "application/x-www-form-urlencoded",
* ],
* ],
* "body" => [
* "name" => "Jane Doe",
* "age" => "25"
* ],
* ]
*/
parse_http_request(
<<<FORM
POST /users HTTP/1.1\r
Host: example.com\r
Content-Type: application/x-www-form-urlencoded\r
\r
name=Jane%20Doe&age=25
FORM,
);
/**
* Parsing JSON body.
*
* @example
* [
* "method" => "POST",
* "path" => "/users",
* "protocol" => "HTTP/1.1",
* "headers" => [
* "host" => [
* "example.com",
* ],
* "content-type" => [
* "application/json",
* ],
* ],
* "body" => [
* "name" => "John Doe",
* "age" => 24
* ],
* ]
*/
parse_http_request(
<<<JSON
POST /users HTTP/1.1\r
Host: example.com\r
Content-Type: application/json\r
\r
{"name":"John Doe","age":24}
JSON,
);
/**
* Parsing Multipart Form Data.
*
* @example
* [
* "method" => "POST",
* "path" => "/users",
* "protocol" => "HTTP/1.1",
* "headers" => [
* "host" => [
* "example.com",
* ],
* "content-type" => [
* "multipart/form-data; boundary=boundary123",
* ],
* ],
* "body" => [
* "name" => "Bad Doe",
* "age" => "26",
* "secret" => [
* "name" => "secret.txt",
* "type" => "text/plain",
* "tmp_name" => "/private/var/folders/fd/wjmd6ylx3zqczr6ptpvgfbw00000gp/T/php2pqSS9",
* "error" => 0,
* "size" => 12,
* ],
* ],
* ]
*/
parse_http_request(
<<<MULTIPART
POST /users HTTP/1.1\r
Host: example.com\r
Content-Type: multipart/form-data; boundary=boundary123\r
\r
--boundary123\r
Content-Disposition: form-data; name="name"\r
\r
Bad Doe
--boundary123\r
Content-Disposition: form-data; name="age"\r
\r
25
--boundary123\r
Content-Disposition: form-data; name="secret"; filename="secret.txt"\r
Content-Type: text/plain\r
\r
test content\r
--boundary123--\r
MULTIPART,
);
@Mofe-Bankole
Copy link

Peak stuff

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment