PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

View File

@ -0,0 +1,19 @@
Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,28 @@
{
"name": "guzzlehttp/streams",
"description": "Provides a simple abstraction over streams of data",
"homepage": "http://guzzlephp.org/",
"keywords": ["stream", "guzzle"],
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"autoload": {
"psr-4": { "GuzzleHttp\\Stream\\": "src/" }
},
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
}
}

View File

@ -0,0 +1,220 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\CannotAttachException;
/**
* Reads from multiple streams, one after the other.
*
* This is a read-only stream decorator.
*/
class AppendStream implements StreamInterface
{
/** @var StreamInterface[] Streams being decorated */
private $streams = [];
private $seekable = true;
private $current = 0;
private $pos = 0;
private $detached = false;
/**
* @param StreamInterface[] $streams Streams to decorate. Each stream must
* be readable.
*/
public function __construct(array $streams = [])
{
foreach ($streams as $stream) {
$this->addStream($stream);
}
}
public function __toString()
{
try {
$this->seek(0);
return $this->getContents();
} catch (\Exception $e) {
return '';
}
}
/**
* Add a stream to the AppendStream
*
* @param StreamInterface $stream Stream to append. Must be readable.
*
* @throws \InvalidArgumentException if the stream is not readable
*/
public function addStream(StreamInterface $stream)
{
if (!$stream->isReadable()) {
throw new \InvalidArgumentException('Each stream must be readable');
}
// The stream is only seekable if all streams are seekable
if (!$stream->isSeekable()) {
$this->seekable = false;
}
$this->streams[] = $stream;
}
public function getContents()
{
return Utils::copyToString($this);
}
/**
* Closes each attached stream.
*
* {@inheritdoc}
*/
public function close()
{
$this->pos = $this->current = 0;
foreach ($this->streams as $stream) {
$stream->close();
}
$this->streams = [];
}
/**
* Detaches each attached stream
*
* {@inheritdoc}
*/
public function detach()
{
$this->close();
$this->detached = true;
}
public function attach($stream)
{
throw new CannotAttachException();
}
public function tell()
{
return $this->pos;
}
/**
* Tries to calculate the size by adding the size of each stream.
*
* If any of the streams do not return a valid number, then the size of the
* append stream cannot be determined and null is returned.
*
* {@inheritdoc}
*/
public function getSize()
{
$size = 0;
foreach ($this->streams as $stream) {
$s = $stream->getSize();
if ($s === null) {
return null;
}
$size += $s;
}
return $size;
}
public function eof()
{
return !$this->streams ||
($this->current >= count($this->streams) - 1 &&
$this->streams[$this->current]->eof());
}
/**
* Attempts to seek to the given position. Only supports SEEK_SET.
*
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
if (!$this->seekable || $whence !== SEEK_SET) {
return false;
}
$success = true;
$this->pos = $this->current = 0;
// Rewind each stream
foreach ($this->streams as $stream) {
if (!$stream->seek(0)) {
$success = false;
}
}
if (!$success) {
return false;
}
// Seek to the actual position by reading from each stream
while ($this->pos < $offset && !$this->eof()) {
$this->read(min(8096, $offset - $this->pos));
}
return $this->pos == $offset;
}
/**
* Reads from all of the appended streams until the length is met or EOF.
*
* {@inheritdoc}
*/
public function read($length)
{
$buffer = '';
$total = count($this->streams) - 1;
$remaining = $length;
while ($remaining > 0) {
// Progress to the next stream if needed.
if ($this->streams[$this->current]->eof()) {
if ($this->current == $total) {
break;
}
$this->current++;
}
$buffer .= $this->streams[$this->current]->read($remaining);
$remaining = $length - strlen($buffer);
}
$this->pos += strlen($buffer);
return $buffer;
}
public function isReadable()
{
return true;
}
public function isWritable()
{
return false;
}
public function isSeekable()
{
return $this->seekable;
}
public function write($string)
{
return false;
}
public function getMetadata($key = null)
{
return $key ? null : [];
}
}

View File

@ -0,0 +1,207 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Represents an asynchronous read-only stream that supports a drain event and
* pumping data from a source stream.
*
* The AsyncReadStream can be used as a completely asynchronous stream, meaning
* the data you can read from the stream will immediately return only
* the data that is currently buffered.
*
* AsyncReadStream can also be used in a "blocking" manner if a "pump" function
* is provided. When a caller requests more bytes than are available in the
* buffer, then the pump function is used to block until the requested number
* of bytes are available or the remote source stream has errored, closed, or
* timed-out. This behavior isn't strictly "blocking" because the pump function
* can send other transfers while waiting on the desired buffer size to be
* ready for reading (e.g., continue to tick an event loop).
*
* @unstable This class is subject to change.
*/
class AsyncReadStream implements StreamInterface
{
use StreamDecoratorTrait;
/** @var callable|null Fn used to notify writers the buffer has drained */
private $drain;
/** @var callable|null Fn used to block for more data */
private $pump;
/** @var int|null Highwater mark of the underlying buffer */
private $hwm;
/** @var bool Whether or not drain needs to be called at some point */
private $needsDrain;
/** @var int The expected size of the remote source */
private $size;
/**
* In order to utilize high water marks to tell writers to slow down, the
* provided stream must answer to the "hwm" stream metadata variable,
* providing the high water mark. If no "hwm" metadata value is available,
* then the "drain" functionality is not utilized.
*
* This class accepts an associative array of configuration options.
*
* - drain: (callable) Function to invoke when the stream has drained,
* meaning the buffer is now writable again because the size of the
* buffer is at an acceptable level (e.g., below the high water mark).
* The function accepts a single argument, the buffer stream object that
* has drained.
* - pump: (callable) A function that accepts the number of bytes to read
* from the source stream. This function will block until all of the data
* that was requested has been read, EOF of the source stream, or the
* source stream is closed.
* - size: (int) The expected size in bytes of the data that will be read
* (if known up-front).
*
* @param StreamInterface $buffer Buffer that contains the data that has
* been read by the event loop.
* @param array $config Associative array of options.
*
* @throws \InvalidArgumentException if the buffer is not readable and
* writable.
*/
public function __construct(
StreamInterface $buffer,
array $config = []
) {
if (!$buffer->isReadable() || !$buffer->isWritable()) {
throw new \InvalidArgumentException(
'Buffer must be readable and writable'
);
}
if (isset($config['size'])) {
$this->size = $config['size'];
}
static $callables = ['pump', 'drain'];
foreach ($callables as $check) {
if (isset($config[$check])) {
if (!is_callable($config[$check])) {
throw new \InvalidArgumentException(
$check . ' must be callable'
);
}
$this->{$check} = $config[$check];
}
}
$this->hwm = $buffer->getMetadata('hwm');
// Cannot drain when there's no high water mark.
if ($this->hwm === null) {
$this->drain = null;
}
$this->stream = $buffer;
}
/**
* Factory method used to create new async stream and an underlying buffer
* if no buffer is provided.
*
* This function accepts the same options as AsyncReadStream::__construct,
* but added the following key value pairs:
*
* - buffer: (StreamInterface) Buffer used to buffer data. If none is
* provided, a default buffer is created.
* - hwm: (int) High water mark to use if a buffer is created on your
* behalf.
* - max_buffer: (int) If provided, wraps the utilized buffer in a
* DroppingStream decorator to ensure that buffer does not exceed a given
* length. When exceeded, the stream will begin dropping data. Set the
* max_buffer to 0, to use a NullStream which does not store data.
* - write: (callable) A function that is invoked when data is written
* to the underlying buffer. The function accepts the buffer as the first
* argument, and the data being written as the second. The function MUST
* return the number of bytes that were written or false to let writers
* know to slow down.
* - drain: (callable) See constructor documentation.
* - pump: (callable) See constructor documentation.
*
* @param array $options Associative array of options.
*
* @return array Returns an array containing the buffer used to buffer
* data, followed by the ready to use AsyncReadStream object.
*/
public static function create(array $options = [])
{
$maxBuffer = isset($options['max_buffer'])
? $options['max_buffer']
: null;
if ($maxBuffer === 0) {
$buffer = new NullStream();
} elseif (isset($options['buffer'])) {
$buffer = $options['buffer'];
} else {
$hwm = isset($options['hwm']) ? $options['hwm'] : 16384;
$buffer = new BufferStream($hwm);
}
if ($maxBuffer > 0) {
$buffer = new DroppingStream($buffer, $options['max_buffer']);
}
// Call the on_write callback if an on_write function was provided.
if (isset($options['write'])) {
$onWrite = $options['write'];
$buffer = FnStream::decorate($buffer, [
'write' => function ($string) use ($buffer, $onWrite) {
$result = $buffer->write($string);
$onWrite($buffer, $string);
return $result;
}
]);
}
return [$buffer, new self($buffer, $options)];
}
public function getSize()
{
return $this->size;
}
public function isWritable()
{
return false;
}
public function write($string)
{
return false;
}
public function read($length)
{
if (!$this->needsDrain && $this->drain) {
$this->needsDrain = $this->stream->getSize() >= $this->hwm;
}
$result = $this->stream->read($length);
// If we need to drain, then drain when the buffer is empty.
if ($this->needsDrain && $this->stream->getSize() === 0) {
$this->needsDrain = false;
$drainFn = $this->drain;
$drainFn($this->stream);
}
$resultLen = strlen($result);
// If a pump was provided, the buffer is still open, and not enough
// data was given, then block until the data is provided.
if ($this->pump && $resultLen < $length) {
$pumpFn = $this->pump;
$result .= $pumpFn($length - $resultLen);
}
return $result;
}
}

View File

@ -0,0 +1,138 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\CannotAttachException;
/**
* Provides a buffer stream that can be written to to fill a buffer, and read
* from to remove bytes from the buffer.
*
* This stream returns a "hwm" metadata value that tells upstream consumers
* what the configured high water mark of the stream is, or the maximum
* preferred size of the buffer.
*
* @package GuzzleHttp\Stream
*/
class BufferStream implements StreamInterface
{
private $hwm;
private $buffer = '';
/**
* @param int $hwm High water mark, representing the preferred maximum
* buffer size. If the size of the buffer exceeds the high
* water mark, then calls to write will continue to succeed
* but will return false to inform writers to slow down
* until the buffer has been drained by reading from it.
*/
public function __construct($hwm = 16384)
{
$this->hwm = $hwm;
}
public function __toString()
{
return $this->getContents();
}
public function getContents()
{
$buffer = $this->buffer;
$this->buffer = '';
return $buffer;
}
public function close()
{
$this->buffer = '';
}
public function detach()
{
$this->close();
}
public function attach($stream)
{
throw new CannotAttachException();
}
public function getSize()
{
return strlen($this->buffer);
}
public function isReadable()
{
return true;
}
public function isWritable()
{
return true;
}
public function isSeekable()
{
return false;
}
public function seek($offset, $whence = SEEK_SET)
{
return false;
}
public function eof()
{
return strlen($this->buffer) === 0;
}
public function tell()
{
return false;
}
/**
* Reads data from the buffer.
*/
public function read($length)
{
$currentLength = strlen($this->buffer);
if ($length >= $currentLength) {
// No need to slice the buffer because we don't have enough data.
$result = $this->buffer;
$this->buffer = '';
} else {
// Slice up the result to provide a subset of the buffer.
$result = substr($this->buffer, 0, $length);
$this->buffer = substr($this->buffer, $length);
}
return $result;
}
/**
* Writes data to the buffer.
*/
public function write($string)
{
$this->buffer .= $string;
if (strlen($this->buffer) >= $this->hwm) {
return false;
}
return strlen($string);
}
public function getMetadata($key = null)
{
if ($key == 'hwm') {
return $this->hwm;
}
return $key ? null : [];
}
}

View File

@ -0,0 +1,122 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\SeekException;
/**
* Stream decorator that can cache previously read bytes from a sequentially
* read stream.
*/
class CachingStream implements StreamInterface
{
use StreamDecoratorTrait;
/** @var StreamInterface Stream being wrapped */
private $remoteStream;
/** @var int Number of bytes to skip reading due to a write on the buffer */
private $skipReadBytes = 0;
/**
* We will treat the buffer object as the body of the stream
*
* @param StreamInterface $stream Stream to cache
* @param StreamInterface $target Optionally specify where data is cached
*/
public function __construct(
StreamInterface $stream,
StreamInterface $target = null
) {
$this->remoteStream = $stream;
$this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
}
public function getSize()
{
return max($this->stream->getSize(), $this->remoteStream->getSize());
}
/**
* {@inheritdoc}
* @throws SeekException When seeking with SEEK_END or when seeking
* past the total size of the buffer stream
*/
public function seek($offset, $whence = SEEK_SET)
{
if ($whence == SEEK_SET) {
$byte = $offset;
} elseif ($whence == SEEK_CUR) {
$byte = $offset + $this->tell();
} else {
return false;
}
// You cannot skip ahead past where you've read from the remote stream
if ($byte > $this->stream->getSize()) {
throw new SeekException(
$this,
$byte,
sprintf('Cannot seek to byte %d when the buffered stream only'
. ' contains %d bytes', $byte, $this->stream->getSize())
);
}
return $this->stream->seek($byte);
}
public function read($length)
{
// Perform a regular read on any previously read data from the buffer
$data = $this->stream->read($length);
$remaining = $length - strlen($data);
// More data was requested so read from the remote stream
if ($remaining) {
// If data was written to the buffer in a position that would have
// been filled from the remote stream, then we must skip bytes on
// the remote stream to emulate overwriting bytes from that
// position. This mimics the behavior of other PHP stream wrappers.
$remoteData = $this->remoteStream->read(
$remaining + $this->skipReadBytes
);
if ($this->skipReadBytes) {
$len = strlen($remoteData);
$remoteData = substr($remoteData, $this->skipReadBytes);
$this->skipReadBytes = max(0, $this->skipReadBytes - $len);
}
$data .= $remoteData;
$this->stream->write($remoteData);
}
return $data;
}
public function write($string)
{
// When appending to the end of the currently read stream, you'll want
// to skip bytes from being read from the remote stream to emulate
// other stream wrappers. Basically replacing bytes of data of a fixed
// length.
$overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
if ($overflow > 0) {
$this->skipReadBytes += $overflow;
}
return $this->stream->write($string);
}
public function eof()
{
return $this->stream->eof() && $this->remoteStream->eof();
}
/**
* Close both the remote stream and buffer stream
*/
public function close()
{
$this->remoteStream->close() && $this->stream->close();
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Stream decorator that begins dropping data once the size of the underlying
* stream becomes too full.
*/
class DroppingStream implements StreamInterface
{
use StreamDecoratorTrait;
private $maxLength;
/**
* @param StreamInterface $stream Underlying stream to decorate.
* @param int $maxLength Maximum size before dropping data.
*/
public function __construct(StreamInterface $stream, $maxLength)
{
$this->stream = $stream;
$this->maxLength = $maxLength;
}
public function write($string)
{
$diff = $this->maxLength - $this->stream->getSize();
// Begin returning false when the underlying stream is too large.
if ($diff <= 0) {
return false;
}
// Write the stream or a subset of the stream if needed.
if (strlen($string) < $diff) {
return $this->stream->write($string);
}
$this->stream->write(substr($string, 0, $diff));
return false;
}
}

View File

@ -0,0 +1,4 @@
<?php
namespace GuzzleHttp\Stream\Exception;
class CannotAttachException extends \RuntimeException {}

View File

@ -0,0 +1,27 @@
<?php
namespace GuzzleHttp\Stream\Exception;
use GuzzleHttp\Stream\StreamInterface;
/**
* Exception thrown when a seek fails on a stream.
*/
class SeekException extends \RuntimeException
{
private $stream;
public function __construct(StreamInterface $stream, $pos = 0, $msg = '')
{
$this->stream = $stream;
$msg = $msg ?: 'Could not seek the stream to position ' . $pos;
parent::__construct($msg);
}
/**
* @return StreamInterface
*/
public function getStream()
{
return $this->stream;
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Compose stream implementations based on a hash of functions.
*
* Allows for easy testing and extension of a provided stream without needing
* to create a concrete class for a simple extension point.
*/
class FnStream implements StreamInterface
{
/** @var array */
private $methods;
/** @var array Methods that must be implemented in the given array */
private static $slots = ['__toString', 'close', 'detach', 'attach',
'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
'isReadable', 'read', 'getContents', 'getMetadata'];
/**
* @param array $methods Hash of method name to a callable.
*/
public function __construct(array $methods)
{
$this->methods = $methods;
// Create the functions on the class
foreach ($methods as $name => $fn) {
$this->{'_fn_' . $name} = $fn;
}
}
/**
* Lazily determine which methods are not implemented.
* @throws \BadMethodCallException
*/
public function __get($name)
{
throw new \BadMethodCallException(str_replace('_fn_', '', $name)
. '() is not implemented in the FnStream');
}
/**
* The close method is called on the underlying stream only if possible.
*/
public function __destruct()
{
if (isset($this->_fn_close)) {
call_user_func($this->_fn_close);
}
}
/**
* Adds custom functionality to an underlying stream by intercepting
* specific method calls.
*
* @param StreamInterface $stream Stream to decorate
* @param array $methods Hash of method name to a closure
*
* @return FnStream
*/
public static function decorate(StreamInterface $stream, array $methods)
{
// If any of the required methods were not provided, then simply
// proxy to the decorated stream.
foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
$methods[$diff] = [$stream, $diff];
}
return new self($methods);
}
public function __toString()
{
return call_user_func($this->_fn___toString);
}
public function close()
{
return call_user_func($this->_fn_close);
}
public function detach()
{
return call_user_func($this->_fn_detach);
}
public function attach($stream)
{
return call_user_func($this->_fn_attach, $stream);
}
public function getSize()
{
return call_user_func($this->_fn_getSize);
}
public function tell()
{
return call_user_func($this->_fn_tell);
}
public function eof()
{
return call_user_func($this->_fn_eof);
}
public function isSeekable()
{
return call_user_func($this->_fn_isSeekable);
}
public function seek($offset, $whence = SEEK_SET)
{
return call_user_func($this->_fn_seek, $offset, $whence);
}
public function isWritable()
{
return call_user_func($this->_fn_isWritable);
}
public function write($string)
{
return call_user_func($this->_fn_write, $string);
}
public function isReadable()
{
return call_user_func($this->_fn_isReadable);
}
public function read($length)
{
return call_user_func($this->_fn_read, $length);
}
public function getContents()
{
return call_user_func($this->_fn_getContents);
}
public function getMetadata($key = null)
{
return call_user_func($this->_fn_getMetadata, $key);
}
}

View File

@ -0,0 +1,117 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Converts Guzzle streams into PHP stream resources.
*/
class GuzzleStreamWrapper
{
/** @var resource */
public $context;
/** @var StreamInterface */
private $stream;
/** @var string r, r+, or w */
private $mode;
/**
* Returns a resource representing the stream.
*
* @param StreamInterface $stream The stream to get a resource for
*
* @return resource
* @throws \InvalidArgumentException if stream is not readable or writable
*/
public static function getResource(StreamInterface $stream)
{
self::register();
if ($stream->isReadable()) {
$mode = $stream->isWritable() ? 'r+' : 'r';
} elseif ($stream->isWritable()) {
$mode = 'w';
} else {
throw new \InvalidArgumentException('The stream must be readable, '
. 'writable, or both.');
}
return fopen('guzzle://stream', $mode, null, stream_context_create([
'guzzle' => ['stream' => $stream]
]));
}
/**
* Registers the stream wrapper if needed
*/
public static function register()
{
if (!in_array('guzzle', stream_get_wrappers())) {
stream_wrapper_register('guzzle', __CLASS__);
}
}
public function stream_open($path, $mode, $options, &$opened_path)
{
$options = stream_context_get_options($this->context);
if (!isset($options['guzzle']['stream'])) {
return false;
}
$this->mode = $mode;
$this->stream = $options['guzzle']['stream'];
return true;
}
public function stream_read($count)
{
return $this->stream->read($count);
}
public function stream_write($data)
{
return (int) $this->stream->write($data);
}
public function stream_tell()
{
return $this->stream->tell();
}
public function stream_eof()
{
return $this->stream->eof();
}
public function stream_seek($offset, $whence)
{
return $this->stream->seek($offset, $whence);
}
public function stream_stat()
{
static $modeMap = [
'r' => 33060,
'r+' => 33206,
'w' => 33188
];
return [
'dev' => 0,
'ino' => 0,
'mode' => $modeMap[$this->mode],
'nlink' => 0,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => $this->stream->getSize() ?: 0,
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'blksize' => 0,
'blocks' => 0
];
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.
*
* This stream decorator skips the first 10 bytes of the given stream to remove
* the gzip header, converts the provided stream to a PHP stream resource,
* then appends the zlib.inflate filter. The stream is then converted back
* to a Guzzle stream resource to be used as a Guzzle stream.
*
* @link http://tools.ietf.org/html/rfc1952
* @link http://php.net/manual/en/filters.compression.php
*/
class InflateStream implements StreamInterface
{
use StreamDecoratorTrait;
public function __construct(StreamInterface $stream)
{
// Skip the first 10 bytes
$stream = new LimitStream($stream, -1, 10);
$resource = GuzzleStreamWrapper::getResource($stream);
stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
$this->stream = new Stream($resource);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Lazily reads or writes to a file that is opened only after an IO operation
* take place on the stream.
*/
class LazyOpenStream implements StreamInterface
{
use StreamDecoratorTrait;
/** @var string File to open */
private $filename;
/** @var string $mode */
private $mode;
/**
* @param string $filename File to lazily open
* @param string $mode fopen mode to use when opening the stream
*/
public function __construct($filename, $mode)
{
$this->filename = $filename;
$this->mode = $mode;
}
/**
* Creates the underlying stream lazily when required.
*
* @return StreamInterface
*/
protected function createStream()
{
return Stream::factory(Utils::open($this->filename, $this->mode));
}
}

View File

@ -0,0 +1,161 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\SeekException;
/**
* Decorator used to return only a subset of a stream
*/
class LimitStream implements StreamInterface
{
use StreamDecoratorTrait;
/** @var int Offset to start reading from */
private $offset;
/** @var int Limit the number of bytes that can be read */
private $limit;
/**
* @param StreamInterface $stream Stream to wrap
* @param int $limit Total number of bytes to allow to be read
* from the stream. Pass -1 for no limit.
* @param int|null $offset Position to seek to before reading (only
* works on seekable streams).
*/
public function __construct(
StreamInterface $stream,
$limit = -1,
$offset = 0
) {
$this->stream = $stream;
$this->setLimit($limit);
$this->setOffset($offset);
}
public function eof()
{
// Always return true if the underlying stream is EOF
if ($this->stream->eof()) {
return true;
}
// No limit and the underlying stream is not at EOF
if ($this->limit == -1) {
return false;
}
$tell = $this->stream->tell();
if ($tell === false) {
return false;
}
return $tell >= $this->offset + $this->limit;
}
/**
* Returns the size of the limited subset of data
* {@inheritdoc}
*/
public function getSize()
{
if (null === ($length = $this->stream->getSize())) {
return null;
} elseif ($this->limit == -1) {
return $length - $this->offset;
} else {
return min($this->limit, $length - $this->offset);
}
}
/**
* Allow for a bounded seek on the read limited stream
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
if ($whence !== SEEK_SET || $offset < 0) {
return false;
}
$offset += $this->offset;
if ($this->limit !== -1) {
if ($offset > $this->offset + $this->limit) {
$offset = $this->offset + $this->limit;
}
}
return $this->stream->seek($offset);
}
/**
* Give a relative tell()
* {@inheritdoc}
*/
public function tell()
{
return $this->stream->tell() - $this->offset;
}
/**
* Set the offset to start limiting from
*
* @param int $offset Offset to seek to and begin byte limiting from
*
* @return self
* @throws SeekException
*/
public function setOffset($offset)
{
$current = $this->stream->tell();
if ($current !== $offset) {
// If the stream cannot seek to the offset position, then read to it
if (!$this->stream->seek($offset)) {
if ($current > $offset) {
throw new SeekException($this, $offset);
} else {
$this->stream->read($offset - $current);
}
}
}
$this->offset = $offset;
return $this;
}
/**
* Set the limit of bytes that the decorator allows to be read from the
* stream.
*
* @param int $limit Number of bytes to allow to be read from the stream.
* Use -1 for no limit.
* @return self
*/
public function setLimit($limit)
{
$this->limit = $limit;
return $this;
}
public function read($length)
{
if ($this->limit == -1) {
return $this->stream->read($length);
}
// Check if the current position is less than the total allowed
// bytes + original offset
$remaining = ($this->offset + $this->limit) - $this->stream->tell();
if ($remaining > 0) {
// Only return the amount of requested data, ensuring that the byte
// limit is not exceeded
return $this->stream->read(min($remaining, $length));
} else {
return false;
}
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace GuzzleHttp\Stream;
/**
* This interface is deprecated and should no longer be used. Just use
* StreamInterface now that the getMetadata method has been added to
* StreamInterface.
*
* @deprecated
*/
interface MetadataStreamInterface extends StreamInterface {}

View File

@ -0,0 +1,25 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Stream decorator that prevents a stream from being seeked
*/
class NoSeekStream implements StreamInterface
{
use StreamDecoratorTrait;
public function seek($offset, $whence = SEEK_SET)
{
return false;
}
public function isSeekable()
{
return false;
}
public function attach($stream)
{
$this->stream->attach($stream);
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\CannotAttachException;
/**
* Does not store any data written to it.
*/
class NullStream implements StreamInterface
{
public function __toString()
{
return '';
}
public function getContents()
{
return '';
}
public function close() {}
public function detach() {}
public function attach($stream)
{
throw new CannotAttachException();
}
public function getSize()
{
return 0;
}
public function isReadable()
{
return true;
}
public function isWritable()
{
return true;
}
public function isSeekable()
{
return true;
}
public function eof()
{
return true;
}
public function tell()
{
return 0;
}
public function seek($offset, $whence = SEEK_SET)
{
return false;
}
public function read($length)
{
return false;
}
public function write($string)
{
return strlen($string);
}
public function getMetadata($key = null)
{
return $key ? null : [];
}
}

View File

@ -0,0 +1,161 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\CannotAttachException;
/**
* Provides a read only stream that pumps data from a PHP callable.
*
* When invoking the provided callable, the PumpStream will pass the amount of
* data requested to read to the callable. The callable can choose to ignore
* this value and return fewer or more bytes than requested. Any extra data
* returned by the provided callable is buffered internally until drained using
* the read() function of the PumpStream. The provided callable MUST return
* false when there is no more data to read.
*/
class PumpStream implements StreamInterface
{
/** @var callable */
private $source;
/** @var int */
private $size;
/** @var int */
private $tellPos = 0;
/** @var array */
private $metadata;
/** @var BufferStream */
private $buffer;
/**
* @param callable $source Source of the stream data. The callable MAY
* accept an integer argument used to control the
* amount of data to return. The callable MUST
* return a string when called, or false on error
* or EOF.
* @param array $options Stream options:
* - metadata: Hash of metadata to use with stream.
* - size: Size of the stream, if known.
*/
public function __construct(callable $source, array $options = [])
{
$this->source = $source;
$this->size = isset($options['size']) ? $options['size'] : null;
$this->metadata = isset($options['metadata']) ? $options['metadata'] : [];
$this->buffer = new BufferStream();
}
public function __toString()
{
return Utils::copyToString($this);
}
public function close()
{
$this->detach();
}
public function detach()
{
$this->tellPos = false;
$this->source = null;
}
public function attach($stream)
{
throw new CannotAttachException();
}
public function getSize()
{
return $this->size;
}
public function tell()
{
return $this->tellPos;
}
public function eof()
{
return !$this->source;
}
public function isSeekable()
{
return false;
}
public function seek($offset, $whence = SEEK_SET)
{
return false;
}
public function isWritable()
{
return false;
}
public function write($string)
{
return false;
}
public function isReadable()
{
return true;
}
public function read($length)
{
$data = $this->buffer->read($length);
$readLen = strlen($data);
$this->tellPos += $readLen;
$remaining = $length - $readLen;
if ($remaining) {
$this->pump($remaining);
$data .= $this->buffer->read($remaining);
$this->tellPos += strlen($data) - $readLen;
}
return $data;
}
public function getContents()
{
$result = '';
while (!$this->eof()) {
$result .= $this->read(1000000);
}
return $result;
}
public function getMetadata($key = null)
{
if (!$key) {
return $this->metadata;
}
return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
}
private function pump($length)
{
if ($this->source) {
do {
$data = call_user_func($this->source, $length);
if ($data === false || $data === null) {
$this->source = null;
return;
}
$this->buffer->write($data);
$length -= strlen($data);
} while ($length > 0);
}
}
}

View File

@ -0,0 +1,261 @@
<?php
namespace GuzzleHttp\Stream;
/**
* PHP stream implementation
*/
class Stream implements StreamInterface
{
private $stream;
private $size;
private $seekable;
private $readable;
private $writable;
private $uri;
private $customMetadata;
/** @var array Hash of readable and writable stream types */
private static $readWriteHash = [
'read' => [
'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
'x+t' => true, 'c+t' => true, 'a+' => true
],
'write' => [
'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
]
];
/**
* Create a new stream based on the input type.
*
* This factory accepts the same associative array of options as described
* in the constructor.
*
* @param resource|string|StreamInterface $resource Entity body data
* @param array $options Additional options
*
* @return Stream
* @throws \InvalidArgumentException if the $resource arg is not valid.
*/
public static function factory($resource = '', array $options = [])
{
$type = gettype($resource);
if ($type == 'string') {
$stream = fopen('php://temp', 'r+');
if ($resource !== '') {
fwrite($stream, $resource);
fseek($stream, 0);
}
return new self($stream, $options);
}
if ($type == 'resource') {
return new self($resource, $options);
}
if ($resource instanceof StreamInterface) {
return $resource;
}
if ($type == 'object' && method_exists($resource, '__toString')) {
return self::factory((string) $resource, $options);
}
if (is_callable($resource)) {
return new PumpStream($resource, $options);
}
if ($resource instanceof \Iterator) {
return new PumpStream(function () use ($resource) {
if (!$resource->valid()) {
return false;
}
$result = $resource->current();
$resource->next();
return $result;
}, $options);
}
throw new \InvalidArgumentException('Invalid resource type: ' . $type);
}
/**
* This constructor accepts an associative array of options.
*
* - size: (int) If a read stream would otherwise have an indeterminate
* size, but the size is known due to foreknownledge, then you can
* provide that size, in bytes.
* - metadata: (array) Any additional metadata to return when the metadata
* of the stream is accessed.
*
* @param resource $stream Stream resource to wrap.
* @param array $options Associative array of options.
*
* @throws \InvalidArgumentException if the stream is not a stream resource
*/
public function __construct($stream, $options = [])
{
if (!is_resource($stream)) {
throw new \InvalidArgumentException('Stream must be a resource');
}
if (isset($options['size'])) {
$this->size = $options['size'];
}
$this->customMetadata = isset($options['metadata'])
? $options['metadata']
: [];
$this->attach($stream);
}
/**
* Closes the stream when the destructed
*/
public function __destruct()
{
$this->close();
}
public function __toString()
{
if (!$this->stream) {
return '';
}
$this->seek(0);
return (string) stream_get_contents($this->stream);
}
public function getContents()
{
return $this->stream ? stream_get_contents($this->stream) : '';
}
public function close()
{
if (is_resource($this->stream)) {
fclose($this->stream);
}
$this->detach();
}
public function detach()
{
$result = $this->stream;
$this->stream = $this->size = $this->uri = null;
$this->readable = $this->writable = $this->seekable = false;
return $result;
}
public function attach($stream)
{
$this->stream = $stream;
$meta = stream_get_meta_data($this->stream);
$this->seekable = $meta['seekable'];
$this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
$this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
$this->uri = $this->getMetadata('uri');
}
public function getSize()
{
if ($this->size !== null) {
return $this->size;
}
if (!$this->stream) {
return null;
}
// Clear the stat cache if the stream has a URI
if ($this->uri) {
clearstatcache(true, $this->uri);
}
$stats = fstat($this->stream);
if (isset($stats['size'])) {
$this->size = $stats['size'];
return $this->size;
}
return null;
}
public function isReadable()
{
return $this->readable;
}
public function isWritable()
{
return $this->writable;
}
public function isSeekable()
{
return $this->seekable;
}
public function eof()
{
return !$this->stream || feof($this->stream);
}
public function tell()
{
return $this->stream ? ftell($this->stream) : false;
}
public function setSize($size)
{
$this->size = $size;
return $this;
}
public function seek($offset, $whence = SEEK_SET)
{
return $this->seekable
? fseek($this->stream, $offset, $whence) === 0
: false;
}
public function read($length)
{
return $this->readable ? fread($this->stream, $length) : false;
}
public function write($string)
{
// We can't know the size after writing anything
$this->size = null;
return $this->writable ? fwrite($this->stream, $string) : false;
}
public function getMetadata($key = null)
{
if (!$this->stream) {
return $key ? null : [];
} elseif (!$key) {
return $this->customMetadata + stream_get_meta_data($this->stream);
} elseif (isset($this->customMetadata[$key])) {
return $this->customMetadata[$key];
}
$meta = stream_get_meta_data($this->stream);
return isset($meta[$key]) ? $meta[$key] : null;
}
}

View File

@ -0,0 +1,143 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\CannotAttachException;
/**
* Stream decorator trait
* @property StreamInterface stream
*/
trait StreamDecoratorTrait
{
/**
* @param StreamInterface $stream Stream to decorate
*/
public function __construct(StreamInterface $stream)
{
$this->stream = $stream;
}
/**
* Magic method used to create a new stream if streams are not added in
* the constructor of a decorator (e.g., LazyOpenStream).
*/
public function __get($name)
{
if ($name == 'stream') {
$this->stream = $this->createStream();
return $this->stream;
}
throw new \UnexpectedValueException("$name not found on class");
}
public function __toString()
{
try {
$this->seek(0);
return $this->getContents();
} catch (\Exception $e) {
// Really, PHP? https://bugs.php.net/bug.php?id=53648
trigger_error('StreamDecorator::__toString exception: '
. (string) $e, E_USER_ERROR);
return '';
}
}
public function getContents()
{
return Utils::copyToString($this);
}
/**
* Allow decorators to implement custom methods
*
* @param string $method Missing method name
* @param array $args Method arguments
*
* @return mixed
*/
public function __call($method, array $args)
{
$result = call_user_func_array(array($this->stream, $method), $args);
// Always return the wrapped object if the result is a return $this
return $result === $this->stream ? $this : $result;
}
public function close()
{
$this->stream->close();
}
public function getMetadata($key = null)
{
return $this->stream->getMetadata($key);
}
public function detach()
{
return $this->stream->detach();
}
public function attach($stream)
{
throw new CannotAttachException();
}
public function getSize()
{
return $this->stream->getSize();
}
public function eof()
{
return $this->stream->eof();
}
public function tell()
{
return $this->stream->tell();
}
public function isReadable()
{
return $this->stream->isReadable();
}
public function isWritable()
{
return $this->stream->isWritable();
}
public function isSeekable()
{
return $this->stream->isSeekable();
}
public function seek($offset, $whence = SEEK_SET)
{
return $this->stream->seek($offset, $whence);
}
public function read($length)
{
return $this->stream->read($length);
}
public function write($string)
{
return $this->stream->write($string);
}
/**
* Implement in subclasses to dynamically create streams when requested.
*
* @return StreamInterface
* @throws \BadMethodCallException
*/
protected function createStream()
{
throw new \BadMethodCallException('createStream() not implemented in '
. get_class($this));
}
}

View File

@ -0,0 +1,159 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Describes a stream instance.
*/
interface StreamInterface
{
/**
* Attempts to seek to the beginning of the stream and reads all data into
* a string until the end of the stream is reached.
*
* Warning: This could attempt to load a large amount of data into memory.
*
* @return string
*/
public function __toString();
/**
* Closes the stream and any underlying resources.
*/
public function close();
/**
* Separates any underlying resources from the stream.
*
* After the underlying resource has been detached, the stream object is in
* an unusable state. If you wish to use a Stream object as a PHP stream
* but keep the Stream object in a consistent state, use
* {@see GuzzleHttp\Stream\GuzzleStreamWrapper::getResource}.
*
* @return resource|null Returns the underlying PHP stream resource or null
* if the Stream object did not utilize an underlying
* stream resource.
*/
public function detach();
/**
* Replaces the underlying stream resource with the provided stream.
*
* Use this method to replace the underlying stream with another; as an
* example, in server-side code, if you decide to return a file, you
* would replace the original content-oriented stream with the file
* stream.
*
* Any internal state such as caching of cursor position should be reset
* when attach() is called, as the stream has changed.
*
* @param resource $stream
*
* @return void
*/
public function attach($stream);
/**
* Get the size of the stream if known
*
* @return int|null Returns the size in bytes if known, or null if unknown
*/
public function getSize();
/**
* Returns the current position of the file read/write pointer
*
* @return int|bool Returns the position of the file pointer or false on error
*/
public function tell();
/**
* Returns true if the stream is at the end of the stream.
*
* @return bool
*/
public function eof();
/**
* Returns whether or not the stream is seekable
*
* @return bool
*/
public function isSeekable();
/**
* Seek to a position in the stream
*
* @param int $offset Stream offset
* @param int $whence Specifies how the cursor position will be calculated
* based on the seek offset. Valid values are identical
* to the built-in PHP $whence values for `fseek()`.
* SEEK_SET: Set position equal to offset bytes
* SEEK_CUR: Set position to current location plus offset
* SEEK_END: Set position to end-of-stream plus offset
*
* @return bool Returns true on success or false on failure
* @link http://www.php.net/manual/en/function.fseek.php
*/
public function seek($offset, $whence = SEEK_SET);
/**
* Returns whether or not the stream is writable
*
* @return bool
*/
public function isWritable();
/**
* Write data to the stream
*
* @param string $string The string that is to be written.
*
* @return int|bool Returns the number of bytes written to the stream on
* success returns false on failure (e.g., broken pipe,
* writer needs to slow down, buffer is full, etc.)
*/
public function write($string);
/**
* Returns whether or not the stream is readable
*
* @return bool
*/
public function isReadable();
/**
* Read data from the stream
*
* @param int $length Read up to $length bytes from the object and return
* them. Fewer than $length bytes may be returned if
* underlying stream call returns fewer bytes.
*
* @return string Returns the data read from the stream.
*/
public function read($length);
/**
* Returns the remaining contents of the stream as a string.
*
* Note: this could potentially load a large amount of data into memory.
*
* @return string
*/
public function getContents();
/**
* Get stream metadata as an associative array or retrieve a specific key.
*
* The keys returned are identical to the keys returned from PHP's
* stream_get_meta_data() function.
*
* @param string $key Specific metadata to retrieve.
*
* @return array|mixed|null Returns an associative array if no key is
* no key is provided. Returns a specific key
* value if a key is provided and the value is
* found, or null if the key is not found.
* @see http://php.net/manual/en/function.stream-get-meta-data.php
*/
public function getMetadata($key = null);
}

View File

@ -0,0 +1,196 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\SeekException;
/**
* Static utility class because PHP's autoloaders don't support the concept
* of namespaced function autoloading.
*/
class Utils
{
/**
* Safely opens a PHP stream resource using a filename.
*
* When fopen fails, PHP normally raises a warning. This function adds an
* error handler that checks for errors and throws an exception instead.
*
* @param string $filename File to open
* @param string $mode Mode used to open the file
*
* @return resource
* @throws \RuntimeException if the file cannot be opened
*/
public static function open($filename, $mode)
{
$ex = null;
set_error_handler(function () use ($filename, $mode, &$ex) {
$ex = new \RuntimeException(sprintf(
'Unable to open %s using mode %s: %s',
$filename,
$mode,
func_get_args()[1]
));
});
$handle = fopen($filename, $mode);
restore_error_handler();
if ($ex) {
/** @var $ex \RuntimeException */
throw $ex;
}
return $handle;
}
/**
* Copy the contents of a stream into a string until the given number of
* bytes have been read.
*
* @param StreamInterface $stream Stream to read
* @param int $maxLen Maximum number of bytes to read. Pass -1
* to read the entire stream.
* @return string
*/
public static function copyToString(StreamInterface $stream, $maxLen = -1)
{
$buffer = '';
if ($maxLen === -1) {
while (!$stream->eof()) {
$buf = $stream->read(1048576);
if ($buf === false) {
break;
}
$buffer .= $buf;
}
return $buffer;
}
$len = 0;
while (!$stream->eof() && $len < $maxLen) {
$buf = $stream->read($maxLen - $len);
if ($buf === false) {
break;
}
$buffer .= $buf;
$len = strlen($buffer);
}
return $buffer;
}
/**
* Copy the contents of a stream into another stream until the given number
* of bytes have been read.
*
* @param StreamInterface $source Stream to read from
* @param StreamInterface $dest Stream to write to
* @param int $maxLen Maximum number of bytes to read. Pass -1
* to read the entire stream.
*/
public static function copyToStream(
StreamInterface $source,
StreamInterface $dest,
$maxLen = -1
) {
if ($maxLen === -1) {
while (!$source->eof()) {
if (!$dest->write($source->read(1048576))) {
break;
}
}
return;
}
$bytes = 0;
while (!$source->eof()) {
$buf = $source->read($maxLen - $bytes);
if (!($len = strlen($buf))) {
break;
}
$bytes += $len;
$dest->write($buf);
if ($bytes == $maxLen) {
break;
}
}
}
/**
* Calculate a hash of a Stream
*
* @param StreamInterface $stream Stream to calculate the hash for
* @param string $algo Hash algorithm (e.g. md5, crc32, etc)
* @param bool $rawOutput Whether or not to use raw output
*
* @return string Returns the hash of the stream
* @throws SeekException
*/
public static function hash(
StreamInterface $stream,
$algo,
$rawOutput = false
) {
$pos = $stream->tell();
if ($pos > 0 && !$stream->seek(0)) {
throw new SeekException($stream);
}
$ctx = hash_init($algo);
while (!$stream->eof()) {
hash_update($ctx, $stream->read(1048576));
}
$out = hash_final($ctx, (bool) $rawOutput);
$stream->seek($pos);
return $out;
}
/**
* Read a line from the stream up to the maximum allowed buffer length
*
* @param StreamInterface $stream Stream to read from
* @param int $maxLength Maximum buffer length
*
* @return string|bool
*/
public static function readline(StreamInterface $stream, $maxLength = null)
{
$buffer = '';
$size = 0;
while (!$stream->eof()) {
if (false === ($byte = $stream->read(1))) {
return $buffer;
}
$buffer .= $byte;
// Break when a new line is found or the max length - 1 is reached
if ($byte == PHP_EOL || ++$size == $maxLength - 1) {
break;
}
}
return $buffer;
}
/**
* Alias of GuzzleHttp\Stream\Stream::factory.
*
* @param mixed $resource Resource to create
* @param array $options Associative array of stream options defined in
* {@see \GuzzleHttp\Stream\Stream::__construct}
*
* @return StreamInterface
*
* @see GuzzleHttp\Stream\Stream::factory
* @see GuzzleHttp\Stream\Stream::__construct
*/
public static function create($resource, array $options = [])
{
return Stream::factory($resource, $options);
}
}