1 | <?php |
||
2 | /** |
||
3 | * This file is part of the ZBateson\StreamDecorators project. |
||
4 | * |
||
5 | * @license http://opensource.org/licenses/bsd-license.php BSD |
||
6 | */ |
||
7 | namespace ZBateson\StreamDecorators; |
||
8 | |||
9 | use Psr\Http\Message\StreamInterface; |
||
10 | use GuzzleHttp\Psr7\StreamDecoratorTrait; |
||
11 | use GuzzleHttp\Psr7\BufferStream; |
||
12 | use RuntimeException; |
||
13 | |||
14 | /** |
||
15 | * GuzzleHttp\Psr7 stream decoder extension for UU-Encoded streams. |
||
16 | * |
||
17 | * The size of the underlying stream and the position of bytes can't be |
||
18 | * determined because the number of encoded bytes is indeterminate without |
||
19 | * reading the entire stream. |
||
20 | * |
||
21 | * @author Zaahid Bateson |
||
22 | */ |
||
23 | class UUStream implements StreamInterface |
||
24 | { |
||
25 | use StreamDecoratorTrait; |
||
26 | |||
27 | /** |
||
28 | * @var string name of the UUEncoded file |
||
29 | */ |
||
30 | protected $filename = null; |
||
31 | |||
32 | /** |
||
33 | * @var BufferStream of read and decoded bytes |
||
34 | */ |
||
35 | private $buffer; |
||
36 | |||
37 | /** |
||
38 | * @var string remainder of write operation if the bytes didn't align to 3 |
||
39 | * bytes |
||
40 | */ |
||
41 | private $remainder = ''; |
||
42 | |||
43 | /** |
||
44 | * @var int read/write position |
||
45 | */ |
||
46 | private $position = 0; |
||
47 | |||
48 | /** |
||
49 | * @var boolean set to true when 'write' is called |
||
50 | */ |
||
51 | private $isWriting = false; |
||
52 | |||
53 | /** |
||
54 | * @param StreamInterface $stream Stream to decorate |
||
55 | * @param string optional file name |
||
0 ignored issues
–
show
|
|||
56 | */ |
||
57 | 12 | public function __construct(StreamInterface $stream, $filename = null) |
|
58 | { |
||
59 | 12 | $this->stream = $stream; |
|
60 | 12 | $this->filename = $filename; |
|
61 | 12 | $this->buffer = new BufferStream(); |
|
62 | 12 | } |
|
63 | |||
64 | /** |
||
65 | * Overridden to return the position in the target encoding. |
||
66 | * |
||
67 | * @return int |
||
68 | */ |
||
69 | 3 | public function tell() |
|
70 | { |
||
71 | 3 | return $this->position; |
|
72 | } |
||
73 | |||
74 | /** |
||
75 | * Returns null, getSize isn't supported |
||
76 | * |
||
77 | * @return null |
||
78 | */ |
||
79 | 1 | public function getSize() |
|
80 | { |
||
81 | 1 | return null; |
|
82 | } |
||
83 | |||
84 | /** |
||
85 | * Not supported. |
||
86 | * |
||
87 | * @param int $offset |
||
88 | * @param int $whence |
||
89 | * @throws RuntimeException |
||
90 | */ |
||
91 | 1 | public function seek($offset, $whence = SEEK_SET) |
|
92 | { |
||
93 | 1 | throw new RuntimeException('Cannot seek a UUStream'); |
|
94 | } |
||
95 | |||
96 | /** |
||
97 | * Overridden to return false |
||
98 | * |
||
99 | * @return boolean |
||
100 | */ |
||
101 | 1 | public function isSeekable() |
|
102 | { |
||
103 | 1 | return false; |
|
104 | } |
||
105 | |||
106 | /** |
||
107 | * Finds the next end-of-line character to ensure a line isn't broken up |
||
108 | * while buffering. |
||
109 | * |
||
110 | * @return string |
||
111 | */ |
||
112 | 10 | private function readToEndOfLine($length) |
|
113 | { |
||
114 | 10 | $str = $this->stream->read($length); |
|
115 | 10 | if ($str === false || $str === '') { |
|
116 | 10 | return $str; |
|
117 | } |
||
118 | 10 | while (substr($str, -1) !== "\n") { |
|
119 | 1 | $chr = $this->stream->read(1); |
|
120 | 1 | if ($chr === false || $chr === '') { |
|
121 | 1 | break; |
|
122 | } |
||
123 | $str .= $chr; |
||
124 | } |
||
125 | 10 | return $str; |
|
126 | } |
||
127 | |||
128 | /** |
||
129 | * Removes invalid characters from a uuencoded string, and 'BEGIN' and 'END' |
||
130 | * line headers and footers from the passed string before returning it. |
||
131 | * |
||
132 | * @param string $str |
||
133 | * @return string |
||
134 | */ |
||
135 | 10 | private function filterAndDecode($str) |
|
136 | { |
||
137 | 10 | $ret = str_replace("\r", '', $str); |
|
138 | 10 | $ret = preg_replace('/[^\x21-\xf5`\n]/', '`', $ret); |
|
139 | 10 | if ($this->position === 0) { |
|
140 | 10 | $matches = []; |
|
141 | 10 | if (preg_match('/^\s*begin\s+[^\s+]\s+([^\r\n]+)\s*$/im', $ret, $matches)) { |
|
142 | $this->filename = $matches[1]; |
||
143 | } |
||
144 | 10 | $ret = preg_replace('/^\s*begin[^\r\n]+\s*$/im', '', $ret); |
|
145 | } else { |
||
146 | $ret = preg_replace('/^\s*end\s*$/im', '', $ret); |
||
147 | } |
||
148 | 10 | return convert_uudecode(trim($ret)); |
|
149 | } |
||
150 | |||
151 | /** |
||
152 | * Buffers bytes into $this->buffer, removing uuencoding headers and footers |
||
153 | * and decoding them. |
||
154 | */ |
||
155 | 10 | private function fillBuffer($length) |
|
156 | { |
||
157 | // 5040 = 63 * 80, seems to be good balance for buffering in benchmarks |
||
158 | // testing with a simple 'if ($length < x)' and calculating a better |
||
159 | // size reduces speeds by up to 4x |
||
160 | 10 | while ($this->buffer->getSize() < $length) { |
|
161 | 10 | $read = $this->readToEndOfLine(5040); |
|
162 | 10 | if ($read === false || $read === '') { |
|
163 | 10 | break; |
|
164 | } |
||
165 | 10 | $this->buffer->write($this->filterAndDecode($read)); |
|
166 | } |
||
167 | 10 | } |
|
168 | |||
169 | /** |
||
170 | * Returns true if the end of stream has been reached. |
||
171 | * |
||
172 | * @return boolean |
||
173 | */ |
||
174 | 10 | public function eof() |
|
175 | { |
||
176 | 10 | return ($this->buffer->eof() && $this->stream->eof()); |
|
177 | } |
||
178 | |||
179 | /** |
||
180 | * Attempts to read $length bytes after decoding them, and returns them. |
||
181 | * |
||
182 | * @param int $length |
||
183 | * @return string |
||
184 | */ |
||
185 | 10 | public function read($length) |
|
186 | { |
||
187 | // let Guzzle decide what to do. |
||
188 | 10 | if ($length <= 0 || $this->eof()) { |
|
189 | return $this->stream->read($length); |
||
190 | } |
||
191 | 10 | $this->fillBuffer($length); |
|
192 | 10 | $read = $this->buffer->read($length); |
|
193 | 10 | $this->position += strlen($read); |
|
194 | 10 | return $read; |
|
195 | } |
||
196 | |||
197 | /** |
||
198 | * Writes the 'begin' UU header line. |
||
199 | */ |
||
200 | 2 | private function writeUUHeader() |
|
201 | { |
||
202 | 2 | $filename = (empty($this->filename)) ? 'null' : $this->filename; |
|
203 | 2 | $this->stream->write("begin 666 $filename"); |
|
204 | 2 | } |
|
205 | |||
206 | /** |
||
207 | * Writes the '`' and 'end' UU footer lines. |
||
208 | */ |
||
209 | 2 | private function writeUUFooter() |
|
210 | { |
||
211 | 2 | $this->stream->write("\r\n`\r\nend\r\n"); |
|
212 | 2 | $this->footerWritten = true; |
|
0 ignored issues
–
show
|
|||
213 | 2 | } |
|
214 | |||
215 | /** |
||
216 | * Writes the passed bytes to the underlying stream after encoding them. |
||
217 | * |
||
218 | * @param string $bytes |
||
219 | */ |
||
220 | 2 | private function writeEncoded($bytes) |
|
221 | { |
||
222 | 2 | $encoded = preg_replace('/\r\n|\r|\n/', "\r\n", rtrim(convert_uuencode($bytes))); |
|
223 | // removes ending '`' line |
||
224 | 2 | $this->stream->write("\r\n" . rtrim(substr($encoded, 0, -1))); |
|
225 | 2 | } |
|
226 | |||
227 | /** |
||
228 | * Prepends any existing remainder to the passed string, then checks if the |
||
229 | * string fits into a uuencoded line, and removes and keeps any remainder |
||
230 | * from the string to write. Full lines ready for writing are returned. |
||
231 | * |
||
232 | * @param string $string |
||
233 | * @return string |
||
234 | */ |
||
235 | 2 | private function handleRemainder($string) |
|
236 | { |
||
237 | 2 | $write = $this->remainder . $string; |
|
238 | 2 | $nRem = strlen($write) % 45; |
|
239 | 2 | $this->remainder = ''; |
|
240 | 2 | if ($nRem !== 0) { |
|
241 | 2 | $this->remainder = substr($write, -$nRem); |
|
242 | 2 | $write = substr($write, 0, -$nRem); |
|
243 | } |
||
244 | 2 | return $write; |
|
245 | } |
||
246 | |||
247 | /** |
||
248 | * Writes the passed string to the underlying stream after encoding it. |
||
249 | * |
||
250 | * Note that reading and writing to the same stream without rewinding is not |
||
251 | * supported. |
||
252 | * |
||
253 | * Also note that some bytes may not be written until close or detach are |
||
254 | * called. This happens if written data doesn't align to a complete |
||
255 | * uuencoded 'line' of 45 bytes. In addition, the UU footer is only written |
||
256 | * when closing or detaching as well. |
||
257 | * |
||
258 | * @param string $string |
||
259 | * @return int the number of bytes written |
||
260 | */ |
||
261 | 2 | public function write($string) |
|
262 | { |
||
263 | 2 | $this->isWriting = true; |
|
264 | 2 | if ($this->position === 0) { |
|
265 | 2 | $this->writeUUHeader(); |
|
266 | } |
||
267 | 2 | $write = $this->handleRemainder($string); |
|
268 | 2 | if ($write !== '') { |
|
269 | 2 | $this->writeEncoded($write); |
|
270 | } |
||
271 | 2 | $written = strlen($string); |
|
272 | 2 | $this->position += $written; |
|
273 | 2 | return $written; |
|
274 | } |
||
275 | |||
276 | /** |
||
277 | * Returns the filename set in the UUEncoded header (or null) |
||
278 | * |
||
279 | * @return string |
||
280 | */ |
||
281 | public function getFilename() |
||
282 | { |
||
283 | return $this->filename; |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * Sets the UUEncoded header file name written in the 'begin' header line. |
||
288 | * |
||
289 | * @param string $filename |
||
290 | */ |
||
291 | public function setFilename($filename) |
||
292 | { |
||
293 | $this->filename = $filename; |
||
294 | } |
||
295 | |||
296 | /** |
||
297 | * Writes out any remaining bytes and the UU footer. |
||
298 | */ |
||
299 | 2 | private function beforeClose() |
|
300 | { |
||
301 | 2 | if (!$this->isWriting) { |
|
302 | 1 | return; |
|
303 | } |
||
304 | 2 | if ($this->remainder !== '') { |
|
305 | 2 | $this->writeEncoded($this->remainder); |
|
306 | } |
||
307 | 2 | $this->remainder = ''; |
|
308 | 2 | $this->isWriting = false; |
|
309 | 2 | $this->writeUUFooter(); |
|
310 | 2 | } |
|
311 | |||
312 | /** |
||
313 | * Writes any remaining bytes out followed by the uu-encoded footer, then |
||
314 | * closes the stream. |
||
315 | */ |
||
316 | 2 | public function close() |
|
317 | { |
||
318 | 2 | $this->beforeClose(); |
|
319 | 2 | $this->stream->close(); |
|
320 | 2 | } |
|
321 | |||
322 | /** |
||
323 | * Writes any remaining bytes out followed by the uu-encoded footer, then |
||
324 | * detaches the stream. |
||
325 | */ |
||
326 | public function detach() |
||
327 | { |
||
328 | $this->beforeClose(); |
||
329 | $this->stream->detach(); |
||
330 | } |
||
331 | } |
||
332 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths