Bump imapengine from 1.25.4 to 1.25.6 and dependencies

This commit is contained in:
johnnyq
2026-08-26 13:02:29 -04:00
parent 71cfdf5e39
commit 3d9a41bec4
54 changed files with 978 additions and 543 deletions

View File

@@ -9,7 +9,7 @@ class ImapCommand implements Stringable
/**
* The compiled command lines.
*
* @var string[]
* @var ImapCommandLine[]|null
*/
protected ?array $compiled = null;
@@ -49,7 +49,7 @@ class ImapCommand implements Stringable
/**
* Compile the command into lines for transmission.
*
* @return string[]
* @return ImapCommandLine[]
*/
public function compile(): array
{
@@ -66,9 +66,12 @@ class ImapCommand implements Stringable
// For tokens provided as arrays, the first element is a placeholder
// (for example, "{20}") that signals a literal value will follow.
// The second element holds the actual literal content.
[$placeholder, $literal] = $token;
[$length, $literal] = $token;
$lines[] = "{$line} {$placeholder}";
$lines[] = new ImapCommandLine(
value: "{$line} {$length}",
synchronizing: ! str_contains($length, '+'),
);
$line = $literal;
} else {
@@ -76,7 +79,7 @@ class ImapCommand implements Stringable
}
}
$lines[] = $line;
$lines[] = new ImapCommandLine($line);
return $this->compiled = $lines;
}

View File

@@ -0,0 +1,24 @@
<?php
namespace DirectoryTree\ImapEngine\Connection;
use Stringable;
class ImapCommandLine implements Stringable
{
/**
* Constructor.
*/
public function __construct(
public string $value,
public bool $synchronizing = false,
) {}
/**
* Get the command line as a string.
*/
public function __toString(): string
{
return $this->value;
}
}

View File

@@ -259,7 +259,7 @@ class ImapConnection implements ConnectionInterface
$this->assertTaggedResponse($tag);
return $this->result->responses()->untagged()->firstWhere(
return $this->result->responses()->untagged()->first(
fn (UntaggedResponse $response) => $response->type()->is('STATUS')
);
}
@@ -621,8 +621,7 @@ class ImapConnection implements ConnectionInterface
public function send(string $name, array $tokens = [], ?string &$tag = null): void
{
if (! $tag) {
$this->sequence++;
$tag = 'TAG'.$this->sequence;
$tag = 'TAG'.++$this->sequence;
}
$command = new ImapCommand($tag, $name, $tokens);
@@ -633,7 +632,11 @@ class ImapConnection implements ConnectionInterface
$this->setResult(new Result($command));
foreach ($command->compile() as $line) {
$this->write($line);
$this->write($line->value);
if ($line->synchronizing) {
$this->assertContinuationResponse($command);
}
}
}
@@ -739,6 +742,23 @@ class ImapConnection implements ConnectionInterface
return $response;
}
/**
* Assert the server is ready to receive literal data.
*/
protected function assertContinuationResponse(ImapCommand $command): void
{
$this->assertNextResponse(
fn (Response $response) => (
$response instanceof ContinuationResponse || (
$response instanceof TaggedResponse
&& $response->tag()->is($command->tag())
)
),
fn (Response $response) => $response instanceof ContinuationResponse,
fn (Response $response) => ImapCommandException::make($command, $response),
);
}
/**
* Assert the next response matches the given filter and assertion.
*

View File

@@ -363,7 +363,7 @@ class ImapTokenizer
/**
* Reads an atom token.
*
* ATOMs are sequences of printable ASCII characters that do not contain delimiters.
* ATOMs are sequences of non-control characters that do not contain delimiters.
*/
protected function readAtom(): Atom
{
@@ -496,20 +496,16 @@ class ImapTokenizer
*/
protected function isValidAtomCharacter(string $char): bool
{
// Get the ASCII code.
$code = ord($char);
// Allow only printable ASCII (32-126).
if ($code < 32 || $code > 126) {
// Reject control characters and DEL while tolerating the
// 8-bit response text returned by some IMAP servers.
if ($code < 0x20 || $code === 0x7F) {
return false;
}
// Delimiters are not allowed inside ATOMs.
if ($this->isDelimiter($char)) {
return false;
}
return true;
return ! $this->isDelimiter($char);
}
/**

View File

@@ -229,4 +229,14 @@ class FakeStream implements StreamInterface
Assert::assertTrue($found, "Failed asserting that the string '{$string}' was written to the stream.");
}
/**
* Assert that the given data was not written to the stream.
*/
public function assertNotWritten(string $string): void
{
foreach ($this->written as $written) {
Assert::assertStringNotContainsString($string, $written);
}
}
}

View File

@@ -3,6 +3,7 @@
namespace DirectoryTree\ImapEngine\Support;
use BackedEnum;
use Illuminate\Support\Collection;
class Str
{
@@ -75,18 +76,12 @@ class Str
}
/**
* Make a range set for use in a search command.
* Make an IMAP sequence set.
*/
public static function set(int|string|array $from, int|float|string|null $to = null): string
{
// If $from is an array with multiple elements, return them as a comma-separated list.
if (is_array($from) && count($from) > 1) {
return implode(',', $from);
}
// If $from is an array with a single element, return that element.
if (is_array($from) && count($from) === 1) {
return (string) reset($from);
if (is_array($from)) {
return static::toSequenceSet($from);
}
// At this point, $from is an integer. No upper bound provided, return $from as a string.
@@ -103,6 +98,44 @@ class Str
return $from.':'.$to;
}
/**
* Convert the values into an IMAP sequence set.
*
* @param array<int, int|string> $values
*/
protected static function toSequenceSet(array $values): string
{
return Collection::make(array_values($values))
->chunkWhile(function (int|string $value, int $key, Collection $range) {
$previous = $range->last();
if (! is_numeric($value) || ! is_numeric($previous)) {
return false;
}
$difference = (int) $value - (int) $previous;
$direction = (int) $previous <=> (int) $range->first();
return in_array($difference, [-1, 1], true)
&& ($range->count() === 1 || $difference === $direction);
})
->map(fn (Collection $range) => static::toSequenceRange(
$range->first(),
$range->last(),
))
->implode(',');
}
/**
* Convert the values into an IMAP sequence range.
*/
protected static function toSequenceRange(int|string $start, int|string $end): string
{
return (string) $start === (string) $end
? (string) $start
: $start.':'.$end;
}
/**
* Make a credentials string for use in the AUTHENTICATE command.
*/