3 namespace Wikimedia\ParamValidator\Util
;
6 use Psr\Http\Message\StreamInterface
;
9 use Wikimedia\AtEase\AtEase
;
12 * Implementation of StreamInterface for a file in $_FILES
14 * This exists so ParamValidator needn't depend on any specific PSR-7
15 * implementation for a class implementing UploadedFileInterface. It shouldn't
16 * be used directly by other code.
21 class UploadedFileStream
implements StreamInterface
{
23 /** @var resource File handle */
26 /** @var int|false|null File size. False if not set yet. */
27 private $size = false;
30 * Call, throwing on error
31 * @param callable $func Callable to call
32 * @param array $args Arguments
33 * @param mixed $fail Failure return value
34 * @param string $msg Message prefix
36 * @throws RuntimeException if $func returns $fail
38 private static function quietCall( callable
$func, array $args, $fail, $msg ) {
39 // TODO remove the function_exists check once we drop HHVM support
40 if ( function_exists( 'error_clear_last' ) ) {
43 $ret = AtEase
::quietCall( $func, ...$args );
44 if ( $ret === $fail ) {
45 $err = error_get_last();
46 throw new RuntimeException( "$msg: " . ( $err['message'] ??
'Unknown error' ) );
52 * @param string $filename
54 public function __construct( $filename ) {
55 $this->fp
= self
::quietCall( 'fopen', [ $filename, 'r' ], false, 'Failed to open file' );
59 * Check if the stream is open
60 * @throws RuntimeException if closed
62 private function checkOpen() {
64 throw new RuntimeException( 'Stream is not open' );
68 public function __destruct() {
72 public function __toString() {
75 return $this->getContents();
76 } catch ( Exception
$ex ) {
77 // Not allowed to throw
79 } catch ( Throwable
$ex ) {
80 // Not allowed to throw
85 public function close() {
87 // Spec doesn't care about close errors.
88 AtEase
::quietCall( 'fclose', $this->fp
);
93 public function detach() {
99 public function getSize() {
100 if ( $this->size
=== false ) {
104 // Spec doesn't care about errors here.
105 $stat = AtEase
::quietCall( 'fstat', $this->fp
);
106 $this->size
= $stat['size'] ??
null;
113 public function tell() {
115 return self
::quietCall( 'ftell', [ $this->fp
], -1, 'Cannot determine stream position' );
118 public function eof() {
119 // Spec doesn't care about errors here.
120 return !$this->fp || AtEase
::quietCall( 'feof', $this->fp
);
123 public function isSeekable() {
124 return (bool)$this->fp
;
127 public function seek( $offset, $whence = SEEK_SET
) {
129 self
::quietCall( 'fseek', [ $this->fp
, $offset, $whence ], -1, 'Seek failed' );
132 public function rewind() {
136 public function isWritable() {
140 public function write( $string ) {
142 throw new RuntimeException( 'Stream is read-only' );
145 public function isReadable() {
146 return (bool)$this->fp
;
149 public function read( $length ) {
151 return self
::quietCall( 'fread', [ $this->fp
, $length ], false, 'Read failed' );
154 public function getContents() {
156 return self
::quietCall( 'stream_get_contents', [ $this->fp
], false, 'Read failed' );
159 public function getMetadata( $key = null ) {
161 $ret = self
::quietCall( 'stream_get_meta_data', [ $this->fp
], false, 'Metadata fetch failed' );
162 if ( $key !== null ) {
163 $ret = $ret[$key] ??
null;