generated from Nodarx/template
fix: message streaming
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -39,7 +39,7 @@ final class FetchResponseParser
|
|||||||
public function parseMany(ResponseStream $responses): Generator
|
public function parseMany(ResponseStream $responses): Generator
|
||||||
{
|
{
|
||||||
foreach ($responses as $response) {
|
foreach ($responses as $response) {
|
||||||
if ($response instanceof UntaggedResponse && MessageParser::isFetchMessage($response->payload())) {
|
if ($response instanceof UntaggedResponse && MessageParser::isFetchMessage($response->raw())) {
|
||||||
yield MessageParser::parse($response->raw());
|
yield MessageParser::parse($response->raw());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -55,4 +55,4 @@ final class FetchResponseParser
|
|||||||
|
|
||||||
throw new ImapException('FETCH did not receive a tagged completion response.');
|
throw new ImapException('FETCH did not receive a tagged completion response.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,19 +8,21 @@ use DateTimeInterface;
|
|||||||
|
|
||||||
final class MessageParser
|
final class MessageParser
|
||||||
{
|
{
|
||||||
public static function isFetchMessage(string $payload): bool
|
public static function isFetchMessage(string $raw): bool
|
||||||
{
|
{
|
||||||
return str_contains(strtoupper($payload), 'FETCH (');
|
return preg_match('/^\*\s+\d+\s+FETCH\s+\(/i', $raw) === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function parse(string $raw): Message
|
public static function parse(string $raw): Message
|
||||||
{
|
{
|
||||||
if (!preg_match('/^\*\s+(\d+)\s+FETCH\s+\((.*)\)$/is', $raw, $matches)) {
|
if (preg_match('/^\*\s+(\d+)\s+FETCH\s+\(/iA', $raw, $matches) !== 1
|
||||||
|
|| !str_ends_with($raw, ')')) {
|
||||||
throw new ImapException('Unable to parse FETCH response: ' . $raw);
|
throw new ImapException('Unable to parse FETCH response: ' . $raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sequence = (int) $matches[1];
|
$sequence = (int) $matches[1];
|
||||||
$attributes = self::parseAttributes($matches[2]);
|
$offset = strlen($matches[0]);
|
||||||
|
$attributes = self::parseAttributes($raw, $offset, strlen($raw) - 1);
|
||||||
$uid = self::toInt($attributes['UID'] ?? null, 'FETCH response is missing UID: ' . $raw);
|
$uid = self::toInt($attributes['UID'] ?? null, 'FETCH response is missing UID: ' . $raw);
|
||||||
$envelope = is_array($attributes['ENVELOPE'] ?? null) ? $attributes['ENVELOPE'] : null;
|
$envelope = is_array($attributes['ENVELOPE'] ?? null) ? $attributes['ENVELOPE'] : null;
|
||||||
$bodyStructure = isset($attributes['BODYSTRUCTURE']) ? self::parseBodyPart($attributes['BODYSTRUCTURE'], '') : null;
|
$bodyStructure = isset($attributes['BODYSTRUCTURE']) ? self::parseBodyPart($attributes['BODYSTRUCTURE'], '') : null;
|
||||||
@@ -52,15 +54,13 @@ final class MessageParser
|
|||||||
/**
|
/**
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
private static function parseAttributes(string $payload): array
|
private static function parseAttributes(string $payload, int &$offset, int $end): array
|
||||||
{
|
{
|
||||||
$attributes = [];
|
$attributes = [];
|
||||||
$offset = 0;
|
|
||||||
$length = strlen($payload);
|
|
||||||
|
|
||||||
while ($offset < $length) {
|
while ($offset < $end) {
|
||||||
self::skipWhitespace($payload, $offset);
|
self::skipWhitespace($payload, $offset);
|
||||||
if ($offset >= $length) {
|
if ($offset >= $end) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -784,4 +784,4 @@ final class MessageParser
|
|||||||
$value,
|
$value,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,16 +56,27 @@ final class ProtocolReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (str_starts_with($raw, '* ')) {
|
if (str_starts_with($raw, '* ')) {
|
||||||
$parts = preg_split('/\s+/', substr($raw, 2), 2) ?: [];
|
// Keep the payload as a slice of the raw response. FETCH payloads can
|
||||||
$label = strtoupper($parts[0] ?? '');
|
// contain large message literals, so eagerly splitting here would keep
|
||||||
|
// a second full copy alive while the message is parsed.
|
||||||
|
$labelEnd = 2;
|
||||||
|
while (isset($raw[$labelEnd]) && !ctype_space($raw[$labelEnd])) {
|
||||||
|
$labelEnd++;
|
||||||
|
}
|
||||||
|
$label = strtoupper(substr($raw, 2, $labelEnd - 2));
|
||||||
|
$payloadOffset = $labelEnd;
|
||||||
|
while (isset($raw[$payloadOffset]) && ctype_space($raw[$payloadOffset])) {
|
||||||
|
$payloadOffset++;
|
||||||
|
}
|
||||||
$this->logger?->debug('IMAP untagged response received: {raw}', [
|
$this->logger?->debug('IMAP untagged response received: {raw}', [
|
||||||
'label' => $label,
|
'label' => $label,
|
||||||
'raw' => $raw,
|
'raw' => $raw,
|
||||||
]);
|
]);
|
||||||
return new UntaggedResponse(
|
return new UntaggedResponse(
|
||||||
$label,
|
$label,
|
||||||
$parts[1] ?? '',
|
'',
|
||||||
$raw,
|
$raw,
|
||||||
|
$payloadOffset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,4 +192,4 @@ final class ProtocolReader
|
|||||||
|
|
||||||
return $raw;
|
return $raw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ final class UntaggedResponse implements ResponseInterface
|
|||||||
private readonly string $label,
|
private readonly string $label,
|
||||||
private readonly string $payload,
|
private readonly string $payload,
|
||||||
private readonly string $raw,
|
private readonly string $raw,
|
||||||
|
private readonly ?int $payloadOffset = null,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function label(): string
|
public function label(): string
|
||||||
@@ -19,6 +20,12 @@ final class UntaggedResponse implements ResponseInterface
|
|||||||
|
|
||||||
public function payload(): string
|
public function payload(): string
|
||||||
{
|
{
|
||||||
|
// ProtocolReader records an offset for potentially large responses and
|
||||||
|
// only materializes the payload for command parsers that actually need it.
|
||||||
|
if ($this->payloadOffset !== null) {
|
||||||
|
return substr($this->raw, $this->payloadOffset);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->payload;
|
return $this->payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +34,7 @@ final class UntaggedResponse implements ResponseInterface
|
|||||||
*/
|
*/
|
||||||
public function payloadTokens(): array
|
public function payloadTokens(): array
|
||||||
{
|
{
|
||||||
$payload = trim($this->payload);
|
$payload = trim($this->payload());
|
||||||
|
|
||||||
if ($payload === '') {
|
if ($payload === '') {
|
||||||
return [];
|
return [];
|
||||||
@@ -40,4 +47,4 @@ final class UntaggedResponse implements ResponseInterface
|
|||||||
{
|
{
|
||||||
return $this->raw;
|
return $this->raw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user