react-http2/src/Header/HeaderPacker.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];
}
}