/** * {@inheritdoc} */ protected function send(string $data, float $timeout = 0, bool $end = false) : \Generator { if (false === ($data = deflate_add($this->resource, $data, $end ? \ZLIB_FINISH : \ZLIB_SYNC_FLUSH))) { throw new FailureException('Failed adding date to deflate stream.'); } return yield from parent::send($data, $timeout, $end); }
/** * {@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); }
/** * @coroutine * * @param \Icicle\Http\Message\Message $message * @param float|int $timeout * * @return \Generator * * @resolve \Icicle\Http\Message\Message * * @throws \Icicle\Http\Exception\MessageException */ private function buildIncomingStream(Message $message, float $timeout = 0) : \Generator { $body = $message->getBody(); if ($body instanceof SeekableStream && $body->isOpen()) { yield from $body->seek(0); } if (!$body->isReadable()) { return $message; } if (strtolower($message->getHeader('Transfer-Encoding') === 'chunked')) { $stream = new ChunkedDecoder($this->hwm); $coroutine = new Coroutine(Stream\pipe($body, $stream, true, 0, null, $timeout)); $coroutine->done(null, [$stream, 'close']); $message = $message->withBody($stream); } elseif ($message->hasHeader('Content-Length')) { $length = (int) $message->getHeader('Content-Length'); if (0 > $length) { throw new MessageException(Response::BAD_REQUEST, 'Content-Length header invalid.'); } $stream = new MemoryStream($this->hwm); if (0 === $length) { yield from $stream->end(); } else { $coroutine = new Coroutine(Stream\pipe($body, $stream, true, $length, null, $timeout)); $coroutine->done(null, [$stream, 'close']); } $message = $message->withBody($stream); } elseif ($message instanceof Request) { switch ($message->getMethod()) { case 'POST': case 'PUT': // Post and put messages must have content length or be transfer encoded. throw new MessageException(Response::LENGTH_REQUIRED, 'Content-Length header required.'); default: // Assume 0 length body. $stream = new MemoryStream(); yield from $stream->end(); // Creates empty request body. return $message->withBody($stream); } } elseif (strtolower($message->getHeader('Connection')) !== 'close') { throw new MessageException(Response::LENGTH_REQUIRED, 'Content-Length header required.'); } $contentEncoding = strtolower($message->getHeader('Content-Encoding')); switch ($contentEncoding) { case 'deflate': $stream = new ZlibDecoder(ZlibDecoder::DEFLATE, $this->hwm); break; case 'gzip': $stream = new ZlibDecoder(ZlibDecoder::GZIP, $this->hwm); break; case '': // No content encoding. return $message; default: throw new MessageException(Response::BAD_REQUEST, sprintf('Unsupported content encoding received: %s', $contentEncoding)); } $coroutine = new Coroutine(Stream\pipe($message->getBody(), $stream, true, 0, null, $timeout)); $coroutine->done(null, [$stream, 'close']); return $message->withBody($stream); }