Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / libs / ParamValidator / Util / UploadedFileStream.php
bloba0fbb56742d91d018ae03a74ab4d1a2655de1cbd
1 <?php
3 namespace Wikimedia\ParamValidator\Util;
5 use Psr\Http\Message\StreamInterface;
6 use RuntimeException;
7 use Stringable;
8 use Throwable;
9 use Wikimedia\AtEase\AtEase;
11 /**
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.
18 * @internal
19 * @since 1.34
21 class UploadedFileStream implements Stringable, StreamInterface {
23 /** @var resource|null File handle */
24 private $fp;
26 /** @var int|false|null File size. False if not set yet. */
27 private $size = false;
29 /**
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
35 * @return mixed
36 * @throws RuntimeException if $func returns $fail
38 private static function quietCall( callable $func, array $args, $fail, $msg ) {
39 error_clear_last();
40 $ret = AtEase::quietCall( $func, ...$args );
41 if ( $ret === $fail ) {
42 $err = error_get_last();
43 throw new RuntimeException( "$msg: " . ( $err['message'] ?? 'Unknown error' ) );
45 return $ret;
48 /**
49 * @param string $filename
51 public function __construct( $filename ) {
52 $this->fp = self::quietCall( 'fopen', [ $filename, 'r' ], false, 'Failed to open file' );
55 /**
56 * Check if the stream is open
57 * @throws RuntimeException if closed
59 private function checkOpen() {
60 if ( !$this->fp ) {
61 throw new RuntimeException( 'Stream is not open' );
65 public function __destruct() {
66 $this->close();
69 public function __toString() {
70 try {
71 $this->seek( 0 );
72 return $this->getContents();
73 } catch ( Throwable $ex ) {
74 // Not allowed to throw
75 return '';
79 public function close() {
80 if ( $this->fp ) {
81 // Spec doesn't care about close errors.
82 try {
83 // PHP 7 emits warnings, suppress
84 AtEase::quietCall( 'fclose', $this->fp );
85 } catch ( \TypeError $unused ) {
86 // While PHP 8 throws exceptions, ignore
88 $this->fp = null;
92 public function detach() {
93 $ret = $this->fp;
94 $this->fp = null;
95 return $ret;
98 public function getSize() {
99 if ( $this->size === false ) {
100 $this->size = null;
102 if ( $this->fp ) {
103 // Spec doesn't care about errors here.
104 try {
105 $stat = AtEase::quietCall( 'fstat', $this->fp );
106 } catch ( \TypeError $unused ) {
108 $this->size = $stat['size'] ?? null;
112 return $this->size;
115 public function tell() {
116 $this->checkOpen();
117 return self::quietCall( 'ftell', [ $this->fp ], -1, 'Cannot determine stream position' );
120 public function eof() {
121 // Spec doesn't care about errors here.
122 try {
123 return !$this->fp || AtEase::quietCall( 'feof', $this->fp );
124 } catch ( \TypeError $unused ) {
125 return true;
129 public function isSeekable() {
130 return (bool)$this->fp;
133 public function seek( $offset, $whence = SEEK_SET ) {
134 $this->checkOpen();
135 self::quietCall( 'fseek', [ $this->fp, $offset, $whence ], -1, 'Seek failed' );
138 public function rewind() {
139 $this->seek( 0 );
142 public function isWritable() {
143 return false;
146 public function write( $string ) {
147 // @phan-suppress-previous-line PhanPluginNeverReturnMethod
148 $this->checkOpen();
149 throw new RuntimeException( 'Stream is read-only' );
152 public function isReadable() {
153 return (bool)$this->fp;
156 public function read( $length ) {
157 $this->checkOpen();
158 return self::quietCall( 'fread', [ $this->fp, $length ], false, 'Read failed' );
161 public function getContents() {
162 $this->checkOpen();
163 return self::quietCall( 'stream_get_contents', [ $this->fp ], false, 'Read failed' );
166 public function getMetadata( $key = null ) {
167 $this->checkOpen();
168 $ret = self::quietCall( 'stream_get_meta_data', [ $this->fp ], false, 'Metadata fetch failed' );
169 if ( $key !== null ) {
170 $ret = $ret[$key] ?? null;
172 return $ret;