155 lines
5.2 KiB
PHP
155 lines
5.2 KiB
PHP
<?php
|
|
|
|
namespace NoccyLabs\React\Http2\Header;
|
|
|
|
use NoccyLabs\React\Http2\Huffman\Codec;
|
|
use NoccyLabs\React\Http2\Huffman\Dictionary;
|
|
|
|
/**
|
|
* Per-connection header packer, implements HPACK for HTTP/2 as per RFC7541
|
|
*
|
|
*/
|
|
class HeaderPacker
|
|
{
|
|
/** @var Codec $huffman The codec and dictionary is static and shared */
|
|
private static ?Codec $huffman = null;
|
|
|
|
private static $staticTable = [
|
|
1 => [ ':authority', null],
|
|
2 => [ ':method', 'GET' ],
|
|
3 => [ ':method', 'POST'],
|
|
4 => [ ':path', '/'],
|
|
5 => [ ':path', '/index.html'],
|
|
6 => [ ':scheme', 'http'],
|
|
7 => [ ':scheme', 'https'],
|
|
8 => [ ':status', '200'],
|
|
9 => [ ':status', '204'],
|
|
10 => [ ':status', '206'],
|
|
11 => [ ':status', '304'],
|
|
12 => [ ':status', '400'],
|
|
13 => [ ':status', '404'],
|
|
14 => [ ':status', '500'],
|
|
15 => [ 'accept-charset', null],
|
|
16 => [ 'accept-encoding', 'gzip, deflate'],
|
|
17 => [ 'accept-language', null],
|
|
18 => [ 'accept-ranges', null],
|
|
19 => [ 'accept', null ],
|
|
20 => [ 'access-control-allow-origin', null],
|
|
21 => [ 'age', null],
|
|
22 => [ 'allow', null],
|
|
23 => [ 'authorization', null],
|
|
24 => [ 'cache-control', null],
|
|
25 => [ 'content-disposition', null],
|
|
26 => [ 'content-encoding', null],
|
|
27 => [ 'content-language', null],
|
|
28 => [ 'content-length', null],
|
|
29 => [ 'content-location', null],
|
|
30 => [ 'content-range', null],
|
|
31 => [ 'content-type', null],
|
|
32 => [ 'cookie', null],
|
|
33 => [ 'date', null],
|
|
34 => [ 'etag', null],
|
|
35 => [ 'expect', null],
|
|
36 => [ 'expires', null],
|
|
37 => [ 'from', null],
|
|
38 => [ 'host', null],
|
|
39 => [ 'if-match', null],
|
|
40 => [ 'if-modified-since', null],
|
|
41 => [ 'if-none-match', null],
|
|
42 => [ 'if-range', null],
|
|
43 => [ 'if-unmodified-since', null],
|
|
44 => [ 'last-modified', null],
|
|
45 => [ 'link', null],
|
|
46 => [ 'location', null],
|
|
47 => [ 'max-forwards', null],
|
|
48 => [ 'proxy-authenticate', null],
|
|
49 => [ 'proxy-authorization', null],
|
|
50 => [ 'range', null],
|
|
51 => [ 'referer', null],
|
|
52 => [ 'refresh', null],
|
|
53 => [ 'retry-after', null],
|
|
54 => [ 'server', null],
|
|
55 => [ 'set-cookie', null],
|
|
56 => [ 'strict-transport-security', null],
|
|
57 => [ 'transfer-encoding', null],
|
|
58 => [ 'user-agent', null],
|
|
59 => [ 'vary', null],
|
|
60 => [ 'via', null],
|
|
61 => [ 'www-authenticate', null],
|
|
];
|
|
private const STATIC_TABLE_SIZE = 62; // considering unused index 0
|
|
|
|
public function __construct()
|
|
{
|
|
if (!self::$huffman) self::$huffman = new Codec(Dictionary::createRfc7541Dictionary());
|
|
}
|
|
|
|
public function packHeaders(HeaderBag $headers): string
|
|
{
|
|
$packed = '';
|
|
|
|
|
|
return $packed;
|
|
}
|
|
|
|
public function unpackHeaders(string $raw): HeaderBag
|
|
{
|
|
$headers = new HeaderBag();
|
|
$bytes = array_map("ord", str_split($raw));
|
|
|
|
while (count($bytes) > 0) {
|
|
$head = array_shift($bytes);
|
|
if ($head & 0x80) {
|
|
// Indexed field
|
|
[$header,$value] = $this->getIndexedHeader($head & 0x7F);
|
|
$headers->append($header,$value);
|
|
} elseif ($head & 0x40) {
|
|
// Indexed with literal
|
|
[$header,$value] = $this->getIndexedHeader($head & 0x3F);
|
|
$valueHead = array_shift($bytes);
|
|
$encoded = (bool)($valueHead & 0x80);
|
|
$length = ($valueHead & 0x7F);
|
|
while ($length-- > 0) {
|
|
$value .= chr(array_shift($bytes));
|
|
}
|
|
if ($encoded) {
|
|
$value = self::$huffman->decode($value);
|
|
}
|
|
$headers->append($header,$value);
|
|
} else {
|
|
// Literal field
|
|
$valueHead = array_shift($bytes);
|
|
$encoded = (bool)($valueHead & 0x80);
|
|
$length = ($valueHead & 0x7F);
|
|
$header = '';
|
|
while ($length-- > 0) {
|
|
$header .= chr(array_shift($bytes));
|
|
}
|
|
if ($encoded) {
|
|
$header = self::$huffman->decode($header);
|
|
}
|
|
|
|
$valueHead = array_shift($bytes);
|
|
$encoded = (bool)($valueHead & 0x80);
|
|
$length = ($valueHead & 0x7F);
|
|
$value = '';
|
|
while ($length-- > 0) {
|
|
$value .= chr(array_shift($bytes));
|
|
}
|
|
if ($encoded) {
|
|
$value = self::$huffman->decode($value);
|
|
}
|
|
|
|
$headers->append($header,$value);
|
|
}
|
|
}
|
|
|
|
return $headers;
|
|
}
|
|
|
|
private function getIndexedHeader(int $index): array
|
|
{
|
|
return self::$staticTable[$index];
|
|
}
|
|
|
|
} |