Initial Commit

This commit is contained in:
David Stone
2024-11-30 18:24:12 -07:00
commit e8f7955c1c
5432 changed files with 1397750 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,203 @@
<?php
declare (strict_types=1);
namespace WP_Ultimo\Dependencies\Amp\Http\Internal;
use WP_Ultimo\Dependencies\Amp\Http\HPack;
use WP_Ultimo\Dependencies\Amp\Http\HPackException;
use FFI;
/**
* @internal
* @psalm-import-type HeaderArray from HPack
*/
final class HPackNghttp2
{
private const FLAG_NO_INDEX = 0x1;
private const FLAG_NO_COPY_NAME = 0x2;
private const FLAG_NO_COPY_VALUE = 0x4;
private const FLAG_NO_COPY = self::FLAG_NO_COPY_NAME | self::FLAG_NO_COPY_VALUE;
private const FLAG_NO_COPY_SENSITIVE = self::FLAG_NO_COPY | self::FLAG_NO_INDEX;
private const SENSITIVE_HEADERS = ['authorization' => self::FLAG_NO_COPY_SENSITIVE, 'cookie' => self::FLAG_NO_COPY_SENSITIVE, 'proxy-authorization' => self::FLAG_NO_COPY_SENSITIVE, 'set-cookie' => self::FLAG_NO_COPY_SENSITIVE];
private static $ffi;
private static $deflatePtrType;
private static $inflatePtrType;
private static $nvType;
private static $nvSize;
private static $charType;
private static $uint8Type;
private static $uint8PtrType;
private static $decodeNv;
private static $decodeNvPtr;
private static $decodeFlags;
private static $decodeFlagsPtr;
private static $supported;
public static function isSupported() : bool
{
if (isset(self::$supported)) {
return self::$supported;
}
if (!\extension_loaded('ffi')) {
return self::$supported = \false;
}
if (!\class_exists(FFI::class)) {
return self::$supported = \false;
}
try {
self::init();
return self::$supported = \true;
} catch (\Throwable $e) {
return self::$supported = \false;
}
}
private static function init() : void
{
if (self::$ffi !== null) {
return;
}
$header = \file_get_contents(__DIR__ . '/amp-hpack.h');
$files = ['libnghttp2.so.14', 'libnghttp2.so', 'libnghttp2.dylib', '/opt/homebrew/lib/libnghttp2.dylib'];
$error = null;
foreach ($files as $file) {
try {
self::$ffi = FFI::cdef($header, $file);
$error = null;
break;
} catch (\Throwable $exception) {
$error = $error ?? $exception;
}
}
if ($error) {
throw $error;
}
self::$deflatePtrType = self::$ffi->type('nghttp2_hd_deflater*');
self::$inflatePtrType = self::$ffi->type('nghttp2_hd_inflater*');
self::$nvType = self::$ffi->type('nghttp2_nv');
self::$nvSize = FFI::sizeof(self::$nvType);
self::$charType = self::$ffi->type('char');
self::$uint8Type = self::$ffi->type('uint8_t');
self::$uint8PtrType = self::$ffi->type('uint8_t*');
self::$decodeNv = self::$ffi->new(self::$nvType);
self::$decodeNvPtr = FFI::addr(self::$decodeNv);
self::$decodeFlags = self::$ffi->new('int');
self::$decodeFlagsPtr = FFI::addr(self::$decodeFlags);
}
private static function createBufferFromString(string $value) : ?FFI\CData
{
$length = \strlen($value);
if (!$length) {
return null;
}
$buffer = self::$ffi->new(FFI::arrayType(self::$uint8Type, [$length]));
FFI::memcpy($buffer, $value, $length);
return $buffer;
}
private $deflatePtr;
private $inflatePtr;
/**
* @param int $maxSize Upper limit on table size.
*/
public function __construct(int $maxSize = 4096)
{
self::init();
$this->deflatePtr = self::$ffi->new(self::$deflatePtrType);
$this->inflatePtr = self::$ffi->new(self::$inflatePtrType);
$return = self::$ffi->nghttp2_hd_deflate_new(FFI::addr($this->deflatePtr), $maxSize);
if ($return !== 0) {
throw new \RuntimeException('Failed to init deflate context');
}
$return = self::$ffi->nghttp2_hd_inflate_new(FFI::addr($this->inflatePtr));
if ($return !== 0) {
throw new \RuntimeException('Failed to init inflate context');
}
}
/**
* @param string $input Encoded headers.
* @param int $maxSize Maximum length of the decoded header string.
*
* @return string[][]|null Returns null if decoding fails or if $maxSize is exceeded.
*/
public function decode(string $input, int $maxSize) : ?array
{
$ffi = self::$ffi;
$pair = self::$decodeNv;
$pairPtr = self::$decodeNvPtr;
$flags = self::$decodeFlags;
$flagsPtr = self::$decodeFlagsPtr;
$inflate = $this->inflatePtr;
$size = 0;
$bufferLength = \strlen($input);
$buffer = self::createBufferFromString($input);
if ($buffer === null) {
return [];
}
$offset = 0;
$bufferPtr = $ffi->cast(self::$uint8PtrType, $buffer);
$headers = [];
while (\true) {
$read = $ffi->nghttp2_hd_inflate_hd2($inflate, $pairPtr, $flagsPtr, $bufferPtr, $bufferLength - $offset, 1);
if ($read < 0) {
return null;
}
$offset += $read;
$bufferPtr += $read;
$cFlags = $flags->cdata;
if ($cFlags & 0x2) {
// NGHTTP2_HD_INFLATE_EMIT
$nameLength = $pair->namelen;
$valueLength = $pair->valuelen;
$headers[] = [FFI::string($pair->name, $nameLength), FFI::string($pair->value, $valueLength)];
$size += $nameLength + $valueLength;
if ($size > $maxSize) {
return null;
}
}
if ($cFlags & 0x1) {
// NGHTTP2_HD_INFLATE_FINAL
$ffi->nghttp2_hd_inflate_end_headers($inflate);
FFI::memset($pair, 0, self::$nvSize);
return $headers;
}
if ($read === 0 || $offset > $bufferLength) {
return null;
}
}
return null;
}
/**
* @param HeaderArray $headers
*
* @return string Encoded headers.
*/
public function encode(array $headers) : string
{
$ffi = self::$ffi;
// To keep memory buffers
$buffers = [];
$headerCount = \count($headers);
$current = 0;
$pairs = $ffi->new(FFI::arrayType(self::$nvType, [$headerCount]));
foreach ($headers as $index => [$name, $value]) {
\assert($index === $current);
$name = (string) $name;
$value = (string) $value;
$pair = $pairs[$current];
$nameBuffer = self::createBufferFromString($name);
$valueBuffer = self::createBufferFromString($value);
$pair->name = $ffi->cast(self::$uint8PtrType, $nameBuffer);
$pair->namelen = \strlen($name);
$pair->value = $ffi->cast(self::$uint8PtrType, $valueBuffer);
$pair->valuelen = \strlen($value);
$pair->flags = self::SENSITIVE_HEADERS[$name] ?? self::FLAG_NO_COPY;
$buffers[] = $nameBuffer;
$buffers[] = $valueBuffer;
$current++;
}
$bufferLength = $ffi->nghttp2_hd_deflate_bound($this->deflatePtr, $pairs, $headerCount);
$buffer = $ffi->new(FFI::arrayType(self::$uint8Type, [$bufferLength]));
$bufferLength = $ffi->nghttp2_hd_deflate_hd($this->deflatePtr, $buffer, $bufferLength, $pairs, $headerCount);
if ($bufferLength < 0) {
throw new HPackException('Failed to compress headers using nghttp2');
}
return FFI::string($buffer, $bufferLength);
}
}

View File

@ -0,0 +1,29 @@
#define FFI_SCOPE "amphp-hpack-nghttp2"
#define FFI_LIB "libnghttp2.so"
typedef struct nghttp2_hd_deflater nghttp2_hd_deflater;
typedef struct nghttp2_hd_inflater nghttp2_hd_inflater;
typedef struct {
uint8_t *name;
uint8_t *value;
size_t namelen;
size_t valuelen;
uint8_t flags;
} nghttp2_nv;
int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, size_t deflate_hd_table_bufsize_max);
ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, uint8_t *buf, size_t buflen, const nghttp2_nv *nva, size_t nvlen);
size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, const nghttp2_nv *nva, size_t nvlen);
int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr);
ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out, int *inflate_flags, const uint8_t *in, size_t inlen, int in_final);
int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long