new appraoch

This commit is contained in:
Tim Bendt
2025-11-26 13:22:58 -05:00
parent de3d100844
commit c520b7df89
6760 changed files with 1009780 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
<?php
namespace Http\Message\Encoding;
/**
* Transform a regular stream into a chunked one.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class ChunkStream extends FilteredStream
{
protected function readFilter(): string
{
return 'chunk';
}
protected function writeFilter(): string
{
return 'dechunk';
}
protected function fill(): void
{
parent::fill();
if ($this->stream->eof()) {
$this->buffer .= "0\r\n\r\n";
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Psr\Http\Message\StreamInterface;
/**
* Stream compress (RFC 1950).
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class CompressStream extends FilteredStream
{
/**
* @param int $level
*/
public function __construct(StreamInterface $stream, $level = -1)
{
if (!extension_loaded('zlib')) {
throw new \RuntimeException('The zlib extension must be enabled to use this stream');
}
parent::__construct($stream, ['window' => 15, 'level' => $level]);
// @deprecated will be removed in 2.0
$this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 15]);
}
protected function readFilter(): string
{
return 'zlib.deflate';
}
protected function writeFilter(): string
{
return 'zlib.inflate';
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Http\Message\Encoding;
/**
* Decorate a stream which is chunked.
*
* Allow to decode a chunked stream
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class DechunkStream extends FilteredStream
{
protected function readFilter(): string
{
return 'dechunk';
}
protected function writeFilter(): string
{
return 'chunk';
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Psr\Http\Message\StreamInterface;
/**
* Stream decompress (RFC 1950).
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class DecompressStream extends FilteredStream
{
/**
* @param int $level
*/
public function __construct(StreamInterface $stream, $level = -1)
{
if (!extension_loaded('zlib')) {
throw new \RuntimeException('The zlib extension must be enabled to use this stream');
}
parent::__construct($stream, ['window' => 15]);
// @deprecated will be removed in 2.0
$this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 15, 'level' => $level]);
}
protected function readFilter(): string
{
return 'zlib.inflate';
}
protected function writeFilter(): string
{
return 'zlib.deflate';
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Psr\Http\Message\StreamInterface;
/**
* Stream deflate (RFC 1951).
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class DeflateStream extends FilteredStream
{
/**
* @param int $level
*/
public function __construct(StreamInterface $stream, $level = -1)
{
parent::__construct($stream, ['window' => -15, 'level' => $level]);
// @deprecated will be removed in 2.0
$this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => -15]);
}
/**
* {@inheritdoc}
*/
protected function readFilter(): string
{
return 'zlib.deflate';
}
/**
* {@inheritdoc}
*/
protected function writeFilter(): string
{
return 'zlib.inflate';
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Http\Message\Encoding\Filter;
/**
* Userland implementation of the chunk stream filter.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class Chunk extends \php_user_filter
{
public function filter($in, $out, &$consumed, $closing): int
{
while ($bucket = stream_bucket_make_writeable($in)) {
$lenbucket = stream_bucket_new($this->stream, dechex($bucket->datalen)."\r\n");
stream_bucket_append($out, $lenbucket);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
$lenbucket = stream_bucket_new($this->stream, "\r\n");
stream_bucket_append($out, $lenbucket);
}
return PSFS_PASS_ON;
}
}

View File

@@ -0,0 +1,217 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Http\Message\Decorator\StreamDecorator;
use Psr\Http\Message\StreamInterface;
/**
* A filtered stream has a filter for filtering output and a filter for filtering input made to a underlying stream.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
abstract class FilteredStream implements StreamInterface
{
use StreamDecorator {
rewind as private doRewind;
seek as private doSeek;
}
public const BUFFER_SIZE = 8192;
/**
* @var callable
*/
protected $readFilterCallback;
/**
* @var resource
*
* @deprecated since version 1.5, will be removed in 2.0
*/
protected $readFilter;
/**
* @var callable
*
* @deprecated since version 1.5, will be removed in 2.0
*/
protected $writeFilterCallback;
/**
* @var resource
*
* @deprecated since version 1.5, will be removed in 2.0
*/
protected $writeFilter;
/**
* Internal buffer.
*
* @var string
*/
protected $buffer = '';
/**
* @param mixed|null $readFilterOptions
* @param mixed|null $writeFilterOptions deprecated since 1.5, will be removed in 2.0
*/
public function __construct(StreamInterface $stream, $readFilterOptions = null, $writeFilterOptions = null)
{
if (null !== $readFilterOptions) {
$this->readFilterCallback = Filter\fun($this->readFilter(), $readFilterOptions);
} else {
$this->readFilterCallback = Filter\fun($this->readFilter());
}
if (null !== $writeFilterOptions) {
$this->writeFilterCallback = Filter\fun($this->writeFilter(), $writeFilterOptions);
@trigger_error('The $writeFilterOptions argument is deprecated since version 1.5 and will be removed in 2.0.', E_USER_DEPRECATED);
} else {
$this->writeFilterCallback = Filter\fun($this->writeFilter());
}
$this->stream = $stream;
}
public function read(int $length): string
{
if (strlen($this->buffer) >= $length) {
$read = substr($this->buffer, 0, $length);
$this->buffer = substr($this->buffer, $length);
return $read;
}
if ($this->stream->eof()) {
$buffer = $this->buffer;
$this->buffer = '';
return $buffer;
}
$read = $this->buffer;
$this->buffer = '';
$this->fill();
return $read.$this->read($length - strlen($read));
}
public function eof(): bool
{
return $this->stream->eof() && '' === $this->buffer;
}
/**
* Buffer is filled by reading underlying stream.
*
* Callback is reading once more even if the stream is ended.
* This allow to get last data in the PHP buffer otherwise this
* bug is present : https://bugs.php.net/bug.php?id=48725
*/
protected function fill(): void
{
$readFilterCallback = $this->readFilterCallback;
$this->buffer .= $readFilterCallback($this->stream->read(self::BUFFER_SIZE));
if ($this->stream->eof()) {
$this->buffer .= $readFilterCallback();
}
}
/**
* {@inheritdoc}
*/
public function getContents(): string
{
$buffer = '';
while (!$this->eof()) {
$buf = $this->read(self::BUFFER_SIZE);
// Using a loose equality here to match on '' and false.
if (null == $buf) {
break;
}
$buffer .= $buf;
}
return $buffer;
}
/**
* Always returns null because we can't tell the size of a stream when we filter.
*/
public function getSize(): ?int
{
return null;
}
public function __toString(): string
{
return $this->getContents();
}
/**
* Filtered streams are not seekable.
*
* We would need to buffer and process everything to allow seeking.
*/
public function isSeekable(): bool
{
return false;
}
/**
* Filtered streams are not seekable and can thus not be rewound.
*/
public function rewind(): void
{
@trigger_error('Filtered streams are not seekable. This method will start raising an exception in the next major version', E_USER_DEPRECATED);
$this->doRewind();
}
/**
* Filtered streams are not seekable.
*/
public function seek(int $offset, int $whence = SEEK_SET): void
{
@trigger_error('Filtered streams are not seekable. This method will start raising an exception in the next major version', E_USER_DEPRECATED);
$this->doSeek($offset, $whence);
}
/**
* Returns the read filter name.
*
* @deprecated since version 1.5, will be removed in 2.0
*/
public function getReadFilter(): string
{
@trigger_error('The '.__CLASS__.'::'.__METHOD__.' method is deprecated since version 1.5 and will be removed in 2.0.', E_USER_DEPRECATED);
return $this->readFilter();
}
/**
* Returns the write filter name.
*/
abstract protected function readFilter(): string;
/**
* Returns the write filter name.
*
* @deprecated since version 1.5, will be removed in 2.0
*/
public function getWriteFilter(): string
{
@trigger_error('The '.__CLASS__.'::'.__METHOD__.' method is deprecated since version 1.5 and will be removed in 2.0.', E_USER_DEPRECATED);
return $this->writeFilter();
}
/**
* Returns the write filter name.
*/
abstract protected function writeFilter(): string;
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Psr\Http\Message\StreamInterface;
/**
* Stream for decoding from gzip format (RFC 1952).
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class GzipDecodeStream extends FilteredStream
{
/**
* @param int $level
*/
public function __construct(StreamInterface $stream, $level = -1)
{
if (!extension_loaded('zlib')) {
throw new \RuntimeException('The zlib extension must be enabled to use this stream');
}
parent::__construct($stream, ['window' => 31]);
// @deprecated will be removed in 2.0
$this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 31, 'level' => $level]);
}
/**
* {@inheritdoc}
*/
protected function readFilter(): string
{
return 'zlib.inflate';
}
/**
* {@inheritdoc}
*/
protected function writeFilter(): string
{
return 'zlib.deflate';
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Psr\Http\Message\StreamInterface;
/**
* Stream for encoding to gzip format (RFC 1952).
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class GzipEncodeStream extends FilteredStream
{
/**
* @param int $level
*/
public function __construct(StreamInterface $stream, $level = -1)
{
if (!extension_loaded('zlib')) {
throw new \RuntimeException('The zlib extension must be enabled to use this stream');
}
parent::__construct($stream, ['window' => 31, 'level' => $level]);
// @deprecated will be removed in 2.0
$this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 31]);
}
/**
* {@inheritdoc}
*/
protected function readFilter(): string
{
return 'zlib.deflate';
}
/**
* {@inheritdoc}
*/
protected function writeFilter(): string
{
return 'zlib.inflate';
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Psr\Http\Message\StreamInterface;
/**
* Stream inflate (RFC 1951).
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class InflateStream extends FilteredStream
{
/**
* @param int $level
*/
public function __construct(StreamInterface $stream, $level = -1)
{
if (!extension_loaded('zlib')) {
throw new \RuntimeException('The zlib extension must be enabled to use this stream');
}
parent::__construct($stream, ['window' => -15]);
// @deprecated will be removed in 2.0
$this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => -15, 'level' => $level]);
}
protected function readFilter(): string
{
return 'zlib.inflate';
}
protected function writeFilter(): string
{
return 'zlib.deflate';
}
}