/** * Returns bytes from the buffer based on the current length or current search byte. * * @return string */ private function remove() : string { if (null !== $this->byte && false !== ($position = $this->buffer->search($this->byte))) { if (0 === $this->length || $position < $this->length) { return $this->buffer->shift($position + 1); } return $this->buffer->shift($this->length); } if (0 === $this->length) { return $this->buffer->drain(); } return $this->buffer->shift($this->length); }
/** * @coroutine * * Reads a single line from the stream. * * Reads from the stream until a newline is reached or the stream is closed. * The newline characters are included in the returned string. If reading ends * in the middle of a character, the trailing bytes are not consumed. * * @param float|int $timeout Number of seconds until the returned promise is rejected with a TimeoutException * if no data is received. Use 0 for no timeout. * * @return \Generator * * @resolve string A line of text read from the stream. * * @throws \Icicle\Awaitable\Exception\TimeoutException If the operation times out. * @throws \Icicle\Stream\Exception\UnreadableException If the stream is no longer readable. * @throws \Icicle\Stream\Exception\ClosedException If the stream is unexpectedly closed. */ public function readLine(float $timeout = 0) : \Generator { $newLineSize = strlen($this->newLine); // Check if a new line is already in the buffer. if (($pos = $this->buffer->search($this->newLine)) !== false) { return $this->buffer->shift($pos + $newLineSize); } $this->buffer->push(yield from Stream\readUntil($this->stream, $this->newLine, 0, $timeout)); if (($pos = $this->buffer->search($this->newLine)) !== false) { return $this->buffer->shift($pos + $newLineSize); } return $this->buffer->shift(strlen(mb_strcut((string) $this->buffer, 0, null, $this->encoding))); }
/** * {@inheritdoc} * * @throws \Icicle\Http\Exception\MessageException If an invalid chunk length is found. */ protected function send(string $data, float $timeout = 0, bool $end = false) : \Generator { $this->buffer->push($data); $data = ''; while (!$this->buffer->isEmpty()) { if (0 === $this->length) { // Read chunk length. if (false === ($position = $this->buffer->search("\r\n"))) { return yield from parent::send($data, $timeout, $end); } $length = rtrim($this->buffer->remove($position + 2), "\r\n"); if ($position = strpos($length, ';')) { $length = substr($length, 0, $position); } if (!preg_match('/^[a-f0-9]+$/i', $length)) { yield from parent::send('', $timeout, true); throw new MessageException(Response::BAD_REQUEST, 'Invalid chunk length.'); } $this->length = hexdec($length) + 2; if (2 === $this->length) { // Termination chunk. $end = true; } } if (2 < $this->length) { // Read chunk. $buffer = $this->buffer->remove($this->length - 2); $this->length -= strlen($buffer); $data .= $buffer; } if (2 >= $this->length) { // Remove \r\n after chunk. $this->length -= strlen($this->buffer->remove($this->length)); } } return yield from parent::send($data, $timeout, $end); }
/** * @param \Icicle\Stream\Structures\Buffer $buffer * @param \Icicle\Socket\Socket $socket * @param float|int $timeout * * @return \Generator * * @throws \Icicle\Http\Exception\MessageException * @throws \Icicle\Http\Exception\ParseException */ protected function readHeaders(Buffer $buffer, Socket $socket, float $timeout = 0) : \Generator { $size = 0; $headers = []; do { while (false === ($position = $buffer->search("\r\n"))) { if ($buffer->getLength() >= $this->maxSize) { throw new MessageException(Response::REQUEST_HEADER_TOO_LARGE, sprintf('Message header exceeded maximum size of %d bytes.', $this->maxSize)); } $buffer->push(yield from $socket->read(0, null, $timeout)); } $length = $position + 2; $line = $buffer->shift($length); if (2 === $length) { return $headers; } $size += $length; $parts = explode(':', $line, 2); if (2 !== count($parts)) { throw new ParseException('Found header without colon.'); } list($name, $value) = $parts; $name = Message\decode($name); $value = Message\decode(trim($value)); // No check for case as Message class will automatically combine similarly named headers. if (!isset($headers[$name])) { $headers[$name] = [$value]; } else { $headers[$name][] = $value; } } while ($size < $this->maxSize); throw new MessageException(Response::REQUEST_HEADER_TOO_LARGE, sprintf('Message header exceeded maximum size of %d bytes.', $this->maxSize)); }