• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

azjezz / psl / 23100354439

15 Mar 2026 01:10AM UTC coverage: 95.628% (-2.8%) from 98.421%
23100354439

Pull #629

github

azjezz
feat(encoding): introduce streaming IO handles for Base64, QuotedPrintable, and Hex

Signed-off-by: azjezz <azjezz@protonmail.com>
Pull Request #629: feat(encoding): introduce streaming IO handles for Base64, QuotedPrintable, and Hex

479 of 797 new or added lines in 13 files covered. (60.1%)

2 existing lines in 1 file now uncovered.

10455 of 10933 relevant lines covered (95.63%)

32.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.43
/src/Psl/Encoding/QuotedPrintable/DecodingWriteHandle.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Psl\Encoding\QuotedPrintable;
6

7
use Psl\Async\CancellationTokenInterface;
8
use Psl\Async\NullCancellationToken;
9
use Psl\IO;
10

11
use function quoted_printable_decode;
12
use function str_ends_with;
13
use function strlen;
14
use function strpos;
15
use function substr;
16

17
/**
18
 * A write handle that accepts quoted-printable encoded bytes and writes decoded output to an inner handle.
19
 *
20
 * Buffers input until complete lines (terminated by \n) are available,
21
 * handles soft-break continuations, then decodes and writes to the inner handle.
22
 */
23
final class DecodingWriteHandle implements IO\WriteHandleInterface
24
{
25
    use IO\WriteHandleConvenienceMethodsTrait;
26

27
    private string $buffer = '';
28
    private string $accumulated = '';
29
    private bool $hardBreakPending = false;
30

31
    public function __construct(
32
        private readonly IO\WriteHandleInterface $handle,
33
    ) {}
3✔
34

35
    public function tryWrite(string $bytes): int
36
    {
37
        $length = strlen($bytes);
3✔
38
        $this->buffer .= $bytes;
3✔
39
        $this->drainCompleteLines();
3✔
40
        return $length;
3✔
41
    }
42

43
    public function write(string $bytes, CancellationTokenInterface $cancellation = new NullCancellationToken()): int
44
    {
45
        return $this->tryWrite($bytes);
3✔
46
    }
47

48
    /**
49
     * Flush any remaining buffered data through the decoder to the inner handle.
50
     */
51
    public function flush(): void
52
    {
53
        $this->accumulated .= str_ends_with($this->buffer, "\r") ? substr($this->buffer, 0, -1) : $this->buffer;
3✔
54
        $this->buffer = '';
3✔
55

56
        if ($this->accumulated !== '') {
3✔
57
            if ($this->hardBreakPending) {
1✔
58
                $this->handle->writeAll("\r\n");
1✔
59
            }
60

61
            $this->handle->writeAll(quoted_printable_decode($this->accumulated));
1✔
62
            $this->accumulated = '';
1✔
63
            $this->hardBreakPending = false;
1✔
64
        }
65
    }
66

67
    private function drainCompleteLines(): void
68
    {
69
        while (false !== ($pos = strpos($this->buffer, "\n"))) {
3✔
70
            $line = substr($this->buffer, 0, $pos);
3✔
71
            $line = str_ends_with($line, "\r") ? substr($line, 0, -1) : $line;
3✔
72
            $this->buffer = substr($this->buffer, $pos + 1);
3✔
73

74
            if (str_ends_with($line, '=')) {
3✔
75
                /** @var non-negative-int $len */
76
                $len = strlen($line) - 1;
1✔
77
                $this->accumulated .= substr($line, 0, $len);
1✔
78

79
                continue;
1✔
80
            }
81

82
            $this->accumulated .= $line;
3✔
83

84
            if ($this->hardBreakPending) {
3✔
NEW
85
                $this->handle->writeAll("\r\n");
×
86
            }
87

88
            $this->handle->writeAll(quoted_printable_decode($this->accumulated));
3✔
89
            $this->accumulated = '';
3✔
90
            $this->hardBreakPending = true;
3✔
91
        }
92
    }
93
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc