--- /dev/null
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @see http://github.com/zendframework/zend-diactoros for the canonical source repository
+ * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
+ */
+
+namespace Zend\Diactoros;
+
+use InvalidArgumentException;
+use RuntimeException;
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Implementation of PSR HTTP streams
+ */
+class Stream implements StreamInterface
+{
+ /**
+ * @var resource|null
+ */
+ protected $resource;
+
+ /**
+ * @var string|resource
+ */
+ protected $stream;
+
+ /**
+ * @param string|resource $stream
+ * @param string $mode Mode with which to open stream
+ * @throws InvalidArgumentException
+ */
+ public function __construct($stream, $mode = 'r')
+ {
+ $this->setStream($stream, $mode);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __toString()
+ {
+ if (! $this->isReadable()) {
+ return '';
+ }
+
+ try {
+ $this->rewind();
+ return $this->getContents();
+ } catch (RuntimeException $e) {
+ return '';
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ if (! $this->resource) {
+ return;
+ }
+
+ $resource = $this->detach();
+ fclose($resource);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function detach()
+ {
+ $resource = $this->resource;
+ $this->resource = null;
+ return $resource;
+ }
+
+ /**
+ * Attach a new stream/resource to the instance.
+ *
+ * @param string|resource $resource
+ * @param string $mode
+ * @throws InvalidArgumentException for stream identifier that cannot be
+ * cast to a resource
+ * @throws InvalidArgumentException for non-resource stream
+ */
+ public function attach($resource, $mode = 'r')
+ {
+ $this->setStream($resource, $mode);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ if (null === $this->resource) {
+ return null;
+ }
+
+ $stats = fstat($this->resource);
+ return $stats['size'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tell()
+ {
+ if (! $this->resource) {
+ throw new RuntimeException('No resource available; cannot tell position');
+ }
+
+ $result = ftell($this->resource);
+ if (! is_int($result)) {
+ throw new RuntimeException('Error occurred during tell operation');
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function eof()
+ {
+ if (! $this->resource) {
+ return true;
+ }
+
+ return feof($this->resource);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isSeekable()
+ {
+ if (! $this->resource) {
+ return false;
+ }
+
+ $meta = stream_get_meta_data($this->resource);
+ return $meta['seekable'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (! $this->resource) {
+ throw new RuntimeException('No resource available; cannot seek position');
+ }
+
+ if (! $this->isSeekable()) {
+ throw new RuntimeException('Stream is not seekable');
+ }
+
+ $result = fseek($this->resource, $offset, $whence);
+
+ if (0 !== $result) {
+ throw new RuntimeException('Error seeking within stream');
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rewind()
+ {
+ return $this->seek(0);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isWritable()
+ {
+ if (! $this->resource) {
+ return false;
+ }
+
+ $meta = stream_get_meta_data($this->resource);
+ $mode = $meta['mode'];
+
+ return (
+ strstr($mode, 'x')
+ || strstr($mode, 'w')
+ || strstr($mode, 'c')
+ || strstr($mode, 'a')
+ || strstr($mode, '+')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($string)
+ {
+ if (! $this->resource) {
+ throw new RuntimeException('No resource available; cannot write');
+ }
+
+ if (! $this->isWritable()) {
+ throw new RuntimeException('Stream is not writable');
+ }
+
+ $result = fwrite($this->resource, $string);
+
+ if (false === $result) {
+ throw new RuntimeException('Error writing to stream');
+ }
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isReadable()
+ {
+ if (! $this->resource) {
+ return false;
+ }
+
+ $meta = stream_get_meta_data($this->resource);
+ $mode = $meta['mode'];
+
+ return (strstr($mode, 'r') || strstr($mode, '+'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($length)
+ {
+ if (! $this->resource) {
+ throw new RuntimeException('No resource available; cannot read');
+ }
+
+ if (! $this->isReadable()) {
+ throw new RuntimeException('Stream is not readable');
+ }
+
+ $result = fread($this->resource, $length);
+
+ if (false === $result) {
+ throw new RuntimeException('Error reading stream');
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getContents()
+ {
+ if (! $this->isReadable()) {
+ throw new RuntimeException('Stream is not readable');
+ }
+
+ $result = stream_get_contents($this->resource);
+ if (false === $result) {
+ throw new RuntimeException('Error reading from stream');
+ }
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadata($key = null)
+ {
+ if (null === $key) {
+ return stream_get_meta_data($this->resource);
+ }
+
+ $metadata = stream_get_meta_data($this->resource);
+ if (! array_key_exists($key, $metadata)) {
+ return null;
+ }
+
+ return $metadata[$key];
+ }
+
+ /**
+ * Set the internal stream resource.
+ *
+ * @param string|resource $stream String stream target or stream resource.
+ * @param string $mode Resource mode for stream target.
+ * @throws InvalidArgumentException for invalid streams or resources.
+ */
+ private function setStream($stream, $mode = 'r')
+ {
+ $error = null;
+ $resource = $stream;
+
+ if (is_string($stream)) {
+ set_error_handler(function ($e) use (&$error) {
+ $error = $e;
+ }, E_WARNING);
+ $resource = fopen($stream, $mode);
+ restore_error_handler();
+ }
+
+ if ($error) {
+ throw new InvalidArgumentException('Invalid stream reference provided');
+ }
+
+ if (! is_resource($resource) || 'stream' !== get_resource_type($resource)) {
+ throw new InvalidArgumentException(
+ 'Invalid stream provided; must be a string stream identifier or stream resource'
+ );
+ }
+
+ if ($stream !== $resource) {
+ $this->stream = $stream;
+ }
+
+ $this->resource = $resource;
+ }
+}