Update imapengine dependancies too

This commit is contained in:
johnnyq
2026-08-02 01:26:40 -04:00
parent cf4446f405
commit 3e532fc792
185 changed files with 9018 additions and 4442 deletions

View File

@@ -9,7 +9,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
php: [8.3, 8.2, 8.1, 8.0]
php: [8.5, 8.4, 8.3, 8.2, 8.1]
stability: [prefer-stable]
name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }}

View File

@@ -1,40 +1,41 @@
# zbateson/stream-decorators
Psr7 stream decorators for character set conversion and common mail format content encodings.
PSR-7 stream decorators for character set conversion and common mail format content encodings.
[![Tests](https://github.com/zbateson/stream-decorators/actions/workflows/tests.yml/badge.svg)](https://github.com/zbateson/stream-decorators/actions/workflows/tests.yml)
[![Code Coverage](https://scrutinizer-ci.com/g/zbateson/stream-decorators/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/zbateson/stream-decorators/?branch=master)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/zbateson/stream-decorators/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/zbateson/stream-decorators/?branch=master)
[![Build Status](https://github.com/zbateson/stream-decorators/actions/workflows/tests.yml/badge.svg)](https://github.com/zbateson/stream-decorators/actions/workflows/tests.yml)
[![Total Downloads](https://poser.pugx.org/zbateson/stream-decorators/downloads)](//packagist.org/packages/zbateson/stream-decorators)
[![Latest Stable Version](https://poser.pugx.org/zbateson/stream-decorators/v)](//packagist.org/packages/zbateson/stream-decorators)
The goals of this project are to be:
* Well written
* Standards-compliant but forgiving
* Tested where possible
To include it for use in your project, please install via composer:
```
composer require zbateson/stream-decorators
```
## Php 7 Support Dropped
## Sponsors
As of stream-decorators 2.0, support for php 7 has been dropped.
[![SecuMailer](https://mail-mime-parser.org/sponsors/logo-secumailer.png)](https://secumailer.com)
A huge thank you to [all my sponsors](https://github.com/sponsors/zbateson). <3
If this project's helped you, please consider [sponsoring me](https://github.com/sponsors/zbateson).
## Requirements
stream-decorators requires PHP 8.0 or newer. Tested on 8.0, 8.1, 8.2 and 8.3.
PHP 8.1 or newer. Tested on PHP 8.1, 8.2, 8.3, 8.4, and 8.5.
## New in 2.0 and 2.1
## Description
Support for guzzlehttp/psr7 1.9 dropped, min supported version is 2.0.
The library provides the following `Psr\Http\Message\StreamInterface` implementations:
zbateson/mb-wrapper has been updated to 2.0 as well, which throws an UnsupportedCharsetException converting from/to an unsupported charset, which changes the behaviour of CharsetStream.
Two new classes are introduced in 2.1, DecoratedCachingStream and a TellZeroStream.
* `Base64Stream` - decodes on read and encodes on write to base64
* `CharsetStream` - encodes from `$streamCharset` to `$stringCharset` on read, and vice-versa on write
* `ChunkSplitStream` - splits written characters into lines of `$lineLength` long (stream implementation of PHP's `chunk_split`)
* `DecoratedCachingStream` - a caching stream that writes to a decorated stream, and reads from the cached undecorated stream
* `NonClosingStream` - overrides `close()` and `detach()`, and simply unsets the attached stream without closing it
* `PregReplaceFilterStream` - calls `preg_replace` with passed arguments on every `read()` call
* `QuotedPrintableStream` - decodes on read and encodes on write to quoted-printable
* `SeekingLimitStream` - similar to GuzzleHttp's `LimitStream`, but maintains an internal current read position
* `TellZeroStream` - `tell()` always returns `0` -- used by `DecoratedCachingStream` to wrap a `BufferStream` in a `CachingStream`
* `UUStream` - decodes on read, encodes on write to uu-encoded
## Usage
@@ -46,32 +47,9 @@ $charsetStream = new ZBateson\StreamDecorators\CharsetStream($b64Stream, 'UTF-32
while (($line = GuzzleHttp\Psr7\Utils::readLine()) !== false) {
echo $line, "\r\n";
}
```
Note that CharsetStream, depending on the target encoding, may return multiple bytes when a single 'char' is read. If using php's 'fread', this will result in a warning:
'read x bytes more data than requested (xxxx read, xxxx max) - excess data will be lost
This is because the parameter to 'fread' is bytes, and so when CharsetStream returns, say, 4 bytes representing a single UTF-32 character, fread will truncate to the first byte when requesting '1' byte. It is recommended to **not** convert to a stream handle (with StreamWrapper) for this reason when using CharsetStream.
The library consists of the following Psr\Http\Message\StreamInterface implementations:
* ZBateson\StreamDecorators\Base64Stream - decodes on read and encodes on write to base64.
* ZBateson\StreamDecorators\CharsetStream - encodes from $streamCharset to $stringCharset on read, and vice-versa on write.
* ZBateson\StreamDecorators\ChunkSplitStream - splits written characters into lines of $lineLength long (stream implementation of php's chunk_split).
* ZBateson\StreamDecorators\DecoratedCachingStream - a caching stream that writes to a decorated stream, and reads from the cached undecorated stream, so for instance a stream could be passed, and decorated with a Base64Stream, and when read, the returned bytes would be base64 encoded.
* ZBateson\StreamDecorators\NonClosingStream - overrides close() and detach(), and simply unsets the attached stream without closing it.
* ZBateson\StreamDecorators\PregReplaceFilterStream - calls preg_replace on with passed arguments on every read() call.
* ZBateson\StreamDecorators\QuotedPrintableStream - decodes on read and encodes on write to quoted-printable.
* ZBateson\StreamDecorators\SeekingLimitStream - similar to GuzzleHttp's LimitStream, but maintains an internal current read position, seeking to it when read() is called, and seeking back to the wrapped stream's position after reading.
* ZBateson\StreamDecorators\TellZeroStream - tell() always returns '0' -- used by DecoratedCachingStream to wrap a BufferStream in a CachingStream. CachingStream calls tell() on its wrapped stream, and BufferStream throws an exception, so TellZeroStream is used to wrap the internal BufferStream to mitigate that.
* ZBateson\StreamDecorators\UUStream - decodes on read, encodes on write to uu-encoded.
QuotedPrintableStream, Base64Stream and UUStream's constructors take a single argument of a StreamInterface.
CharsetStreams's constructor also takes $streamCharset and $stringCharset as arguments respectively, ChunkSplitStream
optionally takes a $lineLength argument (defaults to 76) and a $lineEnding argument (defaults to CRLF).
PregReplaceFilterStream takes a $pattern argument and a $replacement argument. SeekingLimitStream takes optional
$limit and $offset parameters, similar to GuzzleHttp's LimitStream.
Note that `CharsetStream`, depending on the target encoding, may return multiple bytes when a single 'char' is read. If using PHP's `fread`, this will result in a warning. It is recommended to **not** convert to a stream handle (with `StreamWrapper`) when using `CharsetStream`.
## License

View File

@@ -9,12 +9,12 @@
}
],
"require": {
"php": ">=8.0",
"guzzlehttp/psr7": "^2.5",
"zbateson/mb-wrapper": "^2.0"
"php": ">=8.1",
"guzzlehttp/psr7": "^2.5 || ^3.0",
"zbateson/mb-wrapper": "^2.0 || ^3.0"
},
"require-dev": {
"phpunit/phpunit": "^9.6|^10.0",
"phpunit/phpunit": "^10.0 || ^11.0",
"friendsofphp/php-cs-fixer": "*",
"phpstan/phpstan": "*"
},

View File

@@ -47,7 +47,7 @@ class Base64Stream implements StreamInterface
/**
* @var BufferStream buffered bytes
*/
private BufferStream $buffer;
private readonly BufferStream $buffer;
/**
* @var string remainder of write operation if the bytes didn't align to 3
@@ -60,14 +60,8 @@ class Base64Stream implements StreamInterface
*/
private int $position = 0;
/**
* @var StreamInterface $stream
*/
private StreamInterface $stream;
public function __construct(StreamInterface $stream)
public function __construct(private readonly StreamInterface $stream)
{
$this->stream = $stream;
$this->buffer = new BufferStream();
}
@@ -98,7 +92,7 @@ class Base64Stream implements StreamInterface
* @param int $whence
* @throws RuntimeException
*/
public function seek($offset, $whence = SEEK_SET) : void
public function seek($offset, $whence = SEEK_SET) : never
{
throw new RuntimeException('Cannot seek a Base64Stream');
}

View File

@@ -24,18 +24,7 @@ class CharsetStream implements StreamInterface
/**
* @var MbWrapper the charset converter
*/
protected MbWrapper $converter;
/**
* @var string charset of the source stream
*/
protected string $streamCharset = 'ISO-8859-1';
/**
* @var string charset of strings passed in write operations, and returned
* in read operations.
*/
protected string $stringCharset = 'UTF-8';
protected readonly MbWrapper $converter;
/**
* @var int current read/write position
@@ -53,23 +42,18 @@ class CharsetStream implements StreamInterface
*/
private string $buffer = '';
/**
* @var StreamInterface $stream
*/
private StreamInterface $stream;
/**
* @param StreamInterface $stream Stream to decorate
* @param string $streamCharset The underlying stream's charset
* @param string $stringCharset The charset to encode strings to (or
* expected for write)
*/
public function __construct(StreamInterface $stream, string $streamCharset = 'ISO-8859-1', string $stringCharset = 'UTF-8')
{
$this->stream = $stream;
public function __construct(
private readonly StreamInterface $stream,
protected readonly string $streamCharset = 'ISO-8859-1',
protected readonly string $stringCharset = 'UTF-8',
) {
$this->converter = new MbWrapper();
$this->streamCharset = $streamCharset;
$this->stringCharset = $stringCharset;
}
/**
@@ -97,7 +81,7 @@ class CharsetStream implements StreamInterface
* @param int $whence
* @throws RuntimeException
*/
public function seek($offset, $whence = SEEK_SET) : void
public function seek($offset, $whence = SEEK_SET) : never
{
throw new RuntimeException('Cannot seek a CharsetStream');
}

View File

@@ -25,34 +25,18 @@ class ChunkSplitStream implements StreamInterface
* final $lineEnding on close (and so maintained instead of using
* tell() directly)
*/
private int $position;
/**
* @var int The number of characters in a line before inserting $lineEnding.
*/
private int $lineLength;
/**
* @var string The line ending characters to insert.
*/
private string $lineEnding;
private int $position = 0;
/**
* @var int The strlen() of $lineEnding
*/
private int $lineEndingLength;
private readonly int $lineEndingLength;
/**
* @var StreamInterface $stream
*/
private StreamInterface $stream;
public function __construct(StreamInterface $stream, int $lineLength = 76, string $lineEnding = "\r\n")
{
$this->stream = $stream;
$this->position = 0;
$this->lineLength = $lineLength;
$this->lineEnding = $lineEnding;
public function __construct(
private readonly StreamInterface $stream,
private readonly int $lineLength = 76,
private readonly string $lineEnding = "\r\n",
) {
$this->lineEndingLength = \strlen($this->lineEnding);
}

View File

@@ -33,13 +33,13 @@ class DecoratedCachingStream implements StreamInterface
/**
* @var StreamInterface the stream to read from and fill writeStream with
*/
private StreamInterface $readStream;
private readonly StreamInterface $readStream;
/**
* @var StreamInterface the underlying undecorated stream to read from,
* where $writeStream is being written to
*/
private StreamInterface $stream;
private readonly StreamInterface $stream;
/**
* @var StreamInterface decorated $stream that will be written to for
@@ -48,13 +48,6 @@ class DecoratedCachingStream implements StreamInterface
*/
private ?StreamInterface $writeStream;
/**
* @var int Minimum buffer read length. At least this many bytes will be
* read and cached into $writeStream on each call to read from
* $readStream
*/
private int $minBytesCache;
/**
* @param StreamInterface $stream Stream to cache. The cursor is assumed to
* be at the beginning of the stream.
@@ -65,13 +58,12 @@ class DecoratedCachingStream implements StreamInterface
public function __construct(
StreamInterface $stream,
callable $decorator,
int $minBytesCache = 16384
private readonly int $minBytesCache = 16384,
) {
$this->readStream = $stream;
$bufferStream = new TellZeroStream(new BufferStream());
$this->stream = new CachingStream($bufferStream);
$this->writeStream = $decorator(new NonClosingStream($bufferStream));
$this->minBytesCache = $minBytesCache;
}
public function getSize(): ?int
@@ -142,7 +134,7 @@ class DecoratedCachingStream implements StreamInterface
return false;
}
public function write($string): int
public function write($string): never
{
throw new \RuntimeException('Cannot write to a DecoratedCachingStream');
}
@@ -167,9 +159,9 @@ class DecoratedCachingStream implements StreamInterface
private function cacheEntireStream(): int
{
// as-is from CachingStream
$target = new FnStream(['write' => 'strlen']);
$target = new FnStream(['write' => strlen(...)]);
Utils::copyToStream($this, $target);
return $this->tell();
}
}
}

View File

@@ -51,7 +51,7 @@ class NonClosingStream implements StreamInterface
*/
public function close() : void
{
$this->stream = null; // @phpstan-ignore-line
$this->stream = null;
}
/**
@@ -61,7 +61,7 @@ class NonClosingStream implements StreamInterface
*/
public function detach()
{
$this->stream = null; // @phpstan-ignore-line
$this->stream = null;
return null;
}
}

View File

@@ -24,31 +24,16 @@ class PregReplaceFilterStream implements StreamInterface
{
use StreamDecoratorTrait;
/**
* @var string The regex pattern
*/
private string $pattern;
/**
* @var string The replacement
*/
private string $replacement;
/**
* @var BufferStream Buffered stream of input from the underlying stream
*/
private BufferStream $buffer;
private readonly BufferStream $buffer;
/**
* @var StreamInterface $stream
*/
private StreamInterface $stream;
public function __construct(StreamInterface $stream, string $pattern, string $replacement)
{
$this->stream = $stream;
$this->pattern = $pattern;
$this->replacement = $replacement;
public function __construct(
private readonly StreamInterface $stream,
private readonly string $pattern,
private readonly string $replacement,
) {
$this->buffer = new BufferStream();
}
@@ -67,7 +52,7 @@ class PregReplaceFilterStream implements StreamInterface
* @param int $whence
* @throws RuntimeException
*/
public function seek($offset, $whence = SEEK_SET) : void
public function seek($offset, $whence = SEEK_SET) : never
{
throw new RuntimeException('Cannot seek a PregReplaceFilterStream');
}

View File

@@ -34,7 +34,7 @@ class QuotedPrintableStream implements StreamInterface
* @var StreamInterface $stream
* @phpstan-ignore-next-line
*/
private StreamInterface $stream;
private readonly StreamInterface $stream;
/**
* Overridden to return the position in the target encoding.
@@ -61,7 +61,7 @@ class QuotedPrintableStream implements StreamInterface
* @param int $whence
* @throws RuntimeException
*/
public function seek($offset, $whence = SEEK_SET) : void
public function seek($offset, $whence = SEEK_SET) : never
{
throw new RuntimeException('Cannot seek a QuotedPrintableStream');
}
@@ -107,7 +107,7 @@ class QuotedPrintableStream implements StreamInterface
*/
private function decodeBlock(string $block) : string
{
if (\substr($block, -1) === '=') {
if (\str_ends_with($block, '=')) {
$block .= $this->readEncodedChars(2);
} elseif (\substr($block, -2, 1) === '=') {
$first = \substr($block, -1);

View File

@@ -37,11 +37,6 @@ class SeekingLimitStream implements StreamInterface
*/
private int $position = 0;
/**
* @var StreamInterface $stream
*/
private StreamInterface $stream;
/**
* @param StreamInterface $stream Stream to wrap
* @param int $limit Total number of bytes to allow to be read
@@ -49,9 +44,8 @@ class SeekingLimitStream implements StreamInterface
* @param int $offset Position to seek to before reading (only
* works on seekable streams).
*/
public function __construct(StreamInterface $stream, int $limit = -1, int $offset = 0)
public function __construct(private readonly StreamInterface $stream, int $limit = -1, int $offset = 0)
{
$this->stream = $stream;
$this->setLimit($limit);
$this->setOffset($offset);
}
@@ -123,17 +117,11 @@ class SeekingLimitStream implements StreamInterface
*/
public function seek($offset, $whence = SEEK_SET) : void
{
$pos = $offset;
switch ($whence) {
case SEEK_CUR:
$pos = $this->position + $offset;
break;
case SEEK_END:
$pos = $this->limit + $offset;
break;
default:
break;
}
$pos = match ($whence) {
SEEK_CUR => $this->position + $offset,
SEEK_END => $this->limit + $offset,
default => $offset,
};
$this->doSeek($pos);
}

View File

@@ -21,11 +21,12 @@ class TellZeroStream implements StreamInterface
/**
* @var StreamInterface
* @phpstan-ignore-next-line
*/
private StreamInterface $stream;
private readonly StreamInterface $stream;
public function tell() : int
{
return 0;
}
}
}

View File

@@ -26,43 +26,44 @@ class UUStream implements StreamInterface
use StreamDecoratorTrait;
/**
* @var string name of the UUEncoded file
* @var ?string name of the UUEncoded file
*/
protected $filename = null;
protected ?string $filename = null;
/**
* @var BufferStream of read and decoded bytes
*/
private $buffer;
private readonly BufferStream $buffer;
/**
* @var string remainder of write operation if the bytes didn't align to 3
* bytes
*/
private $remainder = '';
private string $remainder = '';
/**
* @var string bytes read past the last line ending, carried to the next read
*/
private string $lineRemainder = '';
/**
* @var int read/write position
*/
private $position = 0;
private int $position = 0;
/**
* @var bool set to true when 'write' is called
*/
private $isWriting = false;
/**
* @var StreamInterface $stream
*/
private $stream;
private bool $isWriting = false;
/**
* @param StreamInterface $stream Stream to decorate
* @param string $filename optional file name
*/
public function __construct(StreamInterface $stream, ?string $filename = null)
{
$this->stream = $stream;
public function __construct(
private readonly StreamInterface $stream,
?string $filename = null,
) {
$this->filename = $filename;
$this->buffer = new BufferStream();
}
@@ -92,7 +93,7 @@ class UUStream implements StreamInterface
* @param int $whence
* @throws RuntimeException
*/
public function seek($offset, $whence = SEEK_SET) : void
public function seek($offset, $whence = SEEK_SET) : never
{
throw new RuntimeException('Cannot seek a UUStream');
}
@@ -111,17 +112,23 @@ class UUStream implements StreamInterface
*/
private function readToEndOfLine(int $length) : string
{
$str = $this->stream->read($length);
$str = $this->lineRemainder . $this->stream->read($length);
$this->lineRemainder = '';
if ($str === '') {
return $str;
}
while (\substr($str, -1) !== "\n") {
$chr = $this->stream->read(1);
while (\strpos($str, "\n") === false) {
$chr = $this->stream->read($length);
if ($chr === '') {
break;
}
$str .= $chr;
}
$eol = \strrpos($str, "\n");
if ($eol !== false && $eol < \strlen($str) - 1) {
$this->lineRemainder = \substr($str, $eol + 1);
$str = \substr($str, 0, $eol + 1);
}
return $str;
}
@@ -139,10 +146,9 @@ class UUStream implements StreamInterface
$this->filename = $matches[1];
}
$ret = \preg_replace('/^\s*begin[^\r\n]+\s*$/im', '', $ret);
} else {
$ret = \preg_replace('/^\s*end\s*$/im', '', $ret);
}
return \convert_uudecode(\trim($ret));
$ret = \trim(\preg_replace('/^\s*end\s*$/im', '', $ret));
return ($ret === '') ? '' : \convert_uudecode($ret);
}
/**
@@ -168,7 +174,7 @@ class UUStream implements StreamInterface
*/
public function eof() : bool
{
return ($this->buffer->eof() && $this->stream->eof());
return ($this->buffer->eof() && $this->stream->eof() && $this->lineRemainder === '');
}
/**
@@ -264,7 +270,7 @@ class UUStream implements StreamInterface
/**
* Returns the filename set in the UUEncoded header (or null)
*/
public function getFilename() : string
public function getFilename() : ?string
{
return $this->filename;
}