friendship ended with social-app. php is my new best friend
1<?php
2
3namespace React\Http\Io;
4
5use Evenement\EventEmitter;
6use React\Stream\ReadableStreamInterface;
7use React\Stream\Util;
8use React\Stream\WritableStreamInterface;
9use Exception;
10
11/**
12 * [Internal] Decodes "Transfer-Encoding: chunked" from given stream and returns only payload data.
13 *
14 * This is used internally to decode incoming requests with this encoding.
15 *
16 * @internal
17 */
18class ChunkedDecoder extends EventEmitter implements ReadableStreamInterface
19{
20 const CRLF = "\r\n";
21 const MAX_CHUNK_HEADER_SIZE = 1024;
22
23 private $closed = false;
24 private $input;
25 private $buffer = '';
26 private $chunkSize = 0;
27 private $transferredSize = 0;
28 private $headerCompleted = false;
29
30 public function __construct(ReadableStreamInterface $input)
31 {
32 $this->input = $input;
33
34 $this->input->on('data', array($this, 'handleData'));
35 $this->input->on('end', array($this, 'handleEnd'));
36 $this->input->on('error', array($this, 'handleError'));
37 $this->input->on('close', array($this, 'close'));
38 }
39
40 public function isReadable()
41 {
42 return !$this->closed && $this->input->isReadable();
43 }
44
45 public function pause()
46 {
47 $this->input->pause();
48 }
49
50 public function resume()
51 {
52 $this->input->resume();
53 }
54
55 public function pipe(WritableStreamInterface $dest, array $options = array())
56 {
57 Util::pipe($this, $dest, $options);
58
59 return $dest;
60 }
61
62 public function close()
63 {
64 if ($this->closed) {
65 return;
66 }
67
68 $this->buffer = '';
69
70 $this->closed = true;
71
72 $this->input->close();
73
74 $this->emit('close');
75 $this->removeAllListeners();
76 }
77
78 /** @internal */
79 public function handleEnd()
80 {
81 if (!$this->closed) {
82 $this->handleError(new Exception('Unexpected end event'));
83 }
84 }
85
86 /** @internal */
87 public function handleError(Exception $e)
88 {
89 $this->emit('error', array($e));
90 $this->close();
91 }
92
93 /** @internal */
94 public function handleData($data)
95 {
96 $this->buffer .= $data;
97
98 while ($this->buffer !== '') {
99 if (!$this->headerCompleted) {
100 $positionCrlf = \strpos($this->buffer, static::CRLF);
101
102 if ($positionCrlf === false) {
103 // Header shouldn't be bigger than 1024 bytes
104 if (isset($this->buffer[static::MAX_CHUNK_HEADER_SIZE])) {
105 $this->handleError(new Exception('Chunk header size inclusive extension bigger than' . static::MAX_CHUNK_HEADER_SIZE. ' bytes'));
106 }
107 return;
108 }
109
110 $header = \strtolower((string)\substr($this->buffer, 0, $positionCrlf));
111 $hexValue = $header;
112
113 if (\strpos($header, ';') !== false) {
114 $array = \explode(';', $header);
115 $hexValue = $array[0];
116 }
117
118 if ($hexValue !== '') {
119 $hexValue = \ltrim(\trim($hexValue), "0");
120 if ($hexValue === '') {
121 $hexValue = "0";
122 }
123 }
124
125 $this->chunkSize = @\hexdec($hexValue);
126 if (!\is_int($this->chunkSize) || \dechex($this->chunkSize) !== $hexValue) {
127 $this->handleError(new Exception($hexValue . ' is not a valid hexadecimal number'));
128 return;
129 }
130
131 $this->buffer = (string)\substr($this->buffer, $positionCrlf + 2);
132 $this->headerCompleted = true;
133 if ($this->buffer === '') {
134 return;
135 }
136 }
137
138 $chunk = (string)\substr($this->buffer, 0, $this->chunkSize - $this->transferredSize);
139
140 if ($chunk !== '') {
141 $this->transferredSize += \strlen($chunk);
142 $this->emit('data', array($chunk));
143 $this->buffer = (string)\substr($this->buffer, \strlen($chunk));
144 }
145
146 $positionCrlf = \strpos($this->buffer, static::CRLF);
147
148 if ($positionCrlf === 0) {
149 if ($this->chunkSize === 0) {
150 $this->emit('end');
151 $this->close();
152 return;
153 }
154 $this->chunkSize = 0;
155 $this->headerCompleted = false;
156 $this->transferredSize = 0;
157 $this->buffer = (string)\substr($this->buffer, 2);
158 } elseif ($this->chunkSize === 0) {
159 // end chunk received, skip all trailer data
160 $this->buffer = (string)\substr($this->buffer, $positionCrlf);
161 }
162
163 if ($positionCrlf !== 0 && $this->chunkSize !== 0 && $this->chunkSize === $this->transferredSize && \strlen($this->buffer) > 2) {
164 // the first 2 characters are not CRLF, send error event
165 $this->handleError(new Exception('Chunk does not end with a CRLF'));
166 return;
167 }
168
169 if ($positionCrlf !== 0 && \strlen($this->buffer) < 2) {
170 // No CRLF found, wait for additional data which could be a CRLF
171 return;
172 }
173 }
174 }
175}