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

azjezz / psl / 23101178730

15 Mar 2026 02:02AM UTC coverage: 95.57%. Remained the same
23101178730

push

github

web-flow
bc: use $camelCase variable naming everywhere (#631)

930 of 1013 new or added lines in 219 files covered. (91.81%)

10 existing lines in 8 files now uncovered.

10462 of 10947 relevant lines covered (95.57%)

32.64 hits per line

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

70.25
/src/Psl/Encoding/Base64/DecodingReadHandle.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Psl\Encoding\Base64;
6

7
use Psl;
8
use Psl\Async\CancellationTokenInterface;
9
use Psl\Async\NullCancellationToken;
10
use Psl\Encoding\Exception;
11
use Psl\IO;
12

13
use function preg_replace;
14
use function strlen;
15
use function strpos;
16
use function substr;
17

18
/**
19
 * A read handle that decodes base64-encoded data from an inner readable handle.
20
 *
21
 * Reads chunks from the inner handle, strips whitespace, and decodes complete 4-byte groups.
22
 * On EOF, decodes any remaining bytes (which must be valid base64 with proper padding).
23
 */
24
final class DecodingReadHandle implements IO\BufferedReadHandleInterface
25
{
26
    use IO\ReadHandleConvenienceMethodsTrait;
27

28
    private string $buffer = '';
29
    private string $remainder = '';
30
    private bool $eof = false;
31

32
    public function __construct(
33
        private readonly IO\ReadHandleInterface $handle,
34
        private readonly Variant $variant = Variant::Standard,
35
        private readonly bool $padding = true,
36
    ) {}
18✔
37

38
    /**
39
     * {@inheritDoc}
40
     */
41
    public function reachedEndOfDataSource(): bool
42
    {
43
        return $this->eof && $this->buffer === '';
7✔
44
    }
45

46
    /**
47
     * {@inheritDoc}
48
     */
49
    public function tryRead(null|int $maxBytes = null): string
50
    {
51
        if ($this->buffer === '' && !$this->eof) {
×
52
            $this->fillBuffer();
×
53
        }
54

55
        if ($this->buffer === '') {
×
56
            return '';
×
57
        }
58

NEW
59
        if (null === $maxBytes || $maxBytes >= strlen($this->buffer)) {
×
60
            $result = $this->buffer;
×
61
            $this->buffer = '';
×
62
            return $result;
×
63
        }
64

NEW
65
        $result = substr($this->buffer, 0, $maxBytes);
×
NEW
66
        $this->buffer = substr($this->buffer, $maxBytes);
×
UNCOV
67
        return $result;
×
68
    }
69

70
    /**
71
     * {@inheritDoc}
72
     */
73
    public function read(
74
        null|int $maxBytes = null,
75
        CancellationTokenInterface $cancellation = new NullCancellationToken(),
76
    ): string {
77
        if ($this->eof && $this->buffer === '') {
8✔
78
            return '';
×
79
        }
80

81
        if ($this->buffer === '') {
8✔
82
            $this->fillBuffer($cancellation);
8✔
83
        }
84

85
        if ($this->buffer === '') {
7✔
86
            return '';
7✔
87
        }
88

89
        if (null === $maxBytes || $maxBytes >= strlen($this->buffer)) {
6✔
90
            $result = $this->buffer;
6✔
91
            $this->buffer = '';
6✔
92
            return $result;
6✔
93
        }
94

95
        $result = substr($this->buffer, 0, $maxBytes);
1✔
96
        $this->buffer = substr($this->buffer, $maxBytes);
1✔
97
        return $result;
1✔
98
    }
99

100
    public function readByte(CancellationTokenInterface $cancellation = new NullCancellationToken()): string
101
    {
102
        if ($this->buffer === '' && !$this->eof) {
2✔
103
            $this->fillBuffer($cancellation);
2✔
104
        }
105

106
        if ($this->buffer === '') {
2✔
107
            throw new IO\Exception\RuntimeException('Reached EOF without any more data.');
1✔
108
        }
109

110
        $ret = $this->buffer[0];
1✔
111
        if ($ret === $this->buffer) {
1✔
112
            $this->buffer = '';
1✔
113
            return $ret;
1✔
114
        }
115

116
        $this->buffer = substr($this->buffer, 1);
1✔
117
        return $ret;
1✔
118
    }
119

120
    public function readLine(CancellationTokenInterface $cancellation = new NullCancellationToken()): null|string
121
    {
122
        $line = $this->readUntil("\n", $cancellation);
3✔
123
        if ($line !== null) {
3✔
124
            if ($line !== '' && $line[-1] === "\r") {
2✔
125
                return substr($line, 0, -1);
1✔
126
            }
127

128
            return $line;
1✔
129
        }
130

131
        // No EOL found; return whatever remains, or null if empty
132
        if ($this->buffer === '' && !$this->eof) {
3✔
133
            $this->fillBuffer($cancellation);
×
134
        }
135

136
        if ($this->buffer === '') {
3✔
137
            return null;
3✔
138
        }
139

140
        $result = $this->buffer;
3✔
141
        $this->buffer = '';
3✔
142
        return $result;
3✔
143
    }
144

145
    public function readUntil(
146
        string $suffix,
147
        CancellationTokenInterface $cancellation = new NullCancellationToken(),
148
    ): null|string {
149
        $suffixLen = strlen($suffix);
5✔
150
        $idx = strpos($this->buffer, $suffix);
5✔
151
        if ($idx !== false) {
5✔
152
            $result = substr($this->buffer, 0, $idx);
2✔
153
            $this->buffer = substr($this->buffer, $idx + $suffixLen);
2✔
154
            return $result;
2✔
155
        }
156

157
        while (!$this->eof) {
5✔
158
            $offset = strlen($this->buffer) - $suffixLen + 1;
5✔
159
            $offset = $offset > 0 ? $offset : 0;
5✔
160

161
            $this->fillBuffer($cancellation);
5✔
162

163
            $idx = strpos($this->buffer, $suffix, $offset);
5✔
164
            if ($idx !== false) {
5✔
165
                $result = substr($this->buffer, 0, $idx);
3✔
166
                $this->buffer = substr($this->buffer, $idx + $suffixLen);
3✔
167
                return $result;
3✔
168
            }
169
        }
170

171
        return null;
5✔
172
    }
173

174
    public function readUntilBounded(
175
        string $suffix,
176
        int $maxBytes,
177
        CancellationTokenInterface $cancellation = new NullCancellationToken(),
178
    ): null|string {
179
        $suffixLen = strlen($suffix);
3✔
180
        $idx = strpos($this->buffer, $suffix);
3✔
181
        if ($idx !== false) {
3✔
NEW
182
            if ($idx > $maxBytes) {
×
183
                throw new IO\Exception\OverflowException(Psl\Str\format(
×
184
                    'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
NEW
185
                    $maxBytes,
×
186
                    $suffix,
×
187
                ));
×
188
            }
189

190
            $result = substr($this->buffer, 0, $idx);
×
NEW
191
            $this->buffer = substr($this->buffer, $idx + $suffixLen);
×
192
            return $result;
×
193
        }
194

195
        if (strlen($this->buffer) > $maxBytes) {
3✔
196
            throw new IO\Exception\OverflowException(Psl\Str\format(
×
197
                'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
NEW
198
                $maxBytes,
×
199
                $suffix,
×
200
            ));
×
201
        }
202

203
        while (!$this->eof) {
3✔
204
            $offset = strlen($this->buffer) - $suffixLen + 1;
3✔
205
            $offset = $offset > 0 ? $offset : 0;
3✔
206

207
            $this->fillBuffer($cancellation);
3✔
208

209
            $idx = strpos($this->buffer, $suffix, $offset);
3✔
210
            if ($idx !== false) {
3✔
211
                if ($idx > $maxBytes) {
2✔
212
                    throw new IO\Exception\OverflowException(Psl\Str\format(
1✔
213
                        'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
1✔
214
                        $maxBytes,
1✔
215
                        $suffix,
1✔
216
                    ));
1✔
217
                }
218

219
                $result = substr($this->buffer, 0, $idx);
1✔
220
                $this->buffer = substr($this->buffer, $idx + $suffixLen);
1✔
221
                return $result;
1✔
222
            }
223

224
            if (strlen($this->buffer) > $maxBytes) {
1✔
225
                throw new IO\Exception\OverflowException(Psl\Str\format(
×
226
                    'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
NEW
227
                    $maxBytes,
×
228
                    $suffix,
×
229
                ));
×
230
            }
231
        }
232

233
        return null;
1✔
234
    }
235

236
    /**
237
     * @throws Exception\RangeException If the base64 data is invalid.
238
     */
239
    private function fillBuffer(CancellationTokenInterface $cancellation = new NullCancellationToken()): void
240
    {
241
        if ($this->eof) {
18✔
242
            return;
×
243
        }
244

245
        $chunk = $this->handle->read(4096, $cancellation);
18✔
246
        if ($chunk === '' && $this->handle->reachedEndOfDataSource()) {
18✔
247
            $this->eof = true;
14✔
248
            // Decode any remaining bytes.
249
            if ($this->remainder !== '') {
14✔
250
                $this->buffer .= decode($this->remainder, $this->variant, $this->padding);
×
251
                $this->remainder = '';
×
252
            }
253

254
            return;
14✔
255
        }
256

257
        // Strip whitespace.
258
        /** @var string $chunk */
259
        $chunk = preg_replace('/\s+/', '', $chunk);
16✔
260
        $data = $this->remainder . $chunk;
16✔
261

262
        // Decode complete 4-byte groups only.
263
        $length = strlen($data);
16✔
264
        $usable = $length - ($length % 4);
16✔
265

266
        if ($usable > 0) {
16✔
267
            $this->buffer .= decode(substr($data, 0, $usable), $this->variant, $this->padding);
16✔
268
            $this->remainder = substr($data, $usable);
15✔
269
        } else {
270
            $this->remainder = $data;
×
271
        }
272
    }
273
}
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