MediaWiki REL1_35
UploadedFileStream.php
Go to the documentation of this file.
1<?php
2
4
5use Psr\Http\Message\StreamInterface;
6use RuntimeException;
7use Throwable;
8use Wikimedia\AtEase\AtEase;
9
20class UploadedFileStream implements StreamInterface {
21
23 private $fp;
24
26 private $size = false;
27
37 private static function quietCall( callable $func, array $args, $fail, $msg ) {
38 error_clear_last();
39 $ret = AtEase::quietCall( $func, ...$args );
40 if ( $ret === $fail ) {
41 $err = error_get_last();
42 throw new RuntimeException( "$msg: " . ( $err['message'] ?? 'Unknown error' ) );
43 }
44 return $ret;
45 }
46
50 public function __construct( $filename ) {
51 $this->fp = self::quietCall( 'fopen', [ $filename, 'r' ], false, 'Failed to open file' );
52 }
53
58 private function checkOpen() {
59 if ( !$this->fp ) {
60 throw new RuntimeException( 'Stream is not open' );
61 }
62 }
63
64 public function __destruct() {
65 $this->close();
66 }
67
68 public function __toString() {
69 try {
70 $this->seek( 0 );
71 return $this->getContents();
72 } catch ( Throwable $ex ) {
73 // Not allowed to throw
74 return '';
75 }
76 }
77
78 public function close() {
79 if ( $this->fp ) {
80 // Spec doesn't care about close errors.
81 AtEase::suppressWarnings();
82 try {
83 // PHP 7 emits warnings, suppress
84 fclose( $this->fp );
85 } catch ( \TypeError $unused ) {
86 // While PHP 8 throws exceptions, ignore
87 } finally {
88 AtEase::restoreWarnings();
89 }
90 $this->fp = null;
91 }
92 }
93
94 public function detach() {
95 $ret = $this->fp;
96 $this->fp = null;
97 return $ret;
98 }
99
100 public function getSize() {
101 if ( $this->size === false ) {
102 $this->size = null;
103
104 if ( $this->fp ) {
105 // Spec doesn't care about errors here.
106 AtEase::suppressWarnings();
107 try {
108 $stat = fstat( $this->fp );
109 } catch ( \TypeError $unused ) {
110 } finally {
111 AtEase::restoreWarnings();
112 }
113 $this->size = $stat['size'] ?? null;
114 }
115 }
116
117 return $this->size;
118 }
119
120 public function tell() {
121 $this->checkOpen();
122 return self::quietCall( 'ftell', [ $this->fp ], -1, 'Cannot determine stream position' );
123 }
124
125 public function eof() {
126 // Spec doesn't care about errors here.
127 AtEase::suppressWarnings();
128 try {
129 return !$this->fp || feof( $this->fp );
130 } catch ( \TypeError $unused ) {
131 return true;
132 } finally {
133 AtEase::restoreWarnings();
134 }
135 }
136
137 public function isSeekable() {
138 return (bool)$this->fp;
139 }
140
141 public function seek( $offset, $whence = SEEK_SET ) {
142 $this->checkOpen();
143 self::quietCall( 'fseek', [ $this->fp, $offset, $whence ], -1, 'Seek failed' );
144 }
145
146 public function rewind() {
147 $this->seek( 0 );
148 }
149
150 public function isWritable() {
151 return false;
152 }
153
154 public function write( $string ) {
155 $this->checkOpen();
156 throw new RuntimeException( 'Stream is read-only' );
157 }
158
159 public function isReadable() {
160 return (bool)$this->fp;
161 }
162
163 public function read( $length ) {
164 $this->checkOpen();
165 return self::quietCall( 'fread', [ $this->fp, $length ], false, 'Read failed' );
166 }
167
168 public function getContents() {
169 $this->checkOpen();
170 return self::quietCall( 'stream_get_contents', [ $this->fp ], false, 'Read failed' );
171 }
172
173 public function getMetadata( $key = null ) {
174 $this->checkOpen();
175 $ret = self::quietCall( 'stream_get_meta_data', [ $this->fp ], false, 'Metadata fetch failed' );
176 if ( $key !== null ) {
177 $ret = $ret[$key] ?? null;
178 }
179 return $ret;
180 }
181
182}
Implementation of StreamInterface for a file in $_FILES.
static quietCall(callable $func, array $args, $fail, $msg)
Call, throwing on error.
if( $line===false) $args
Definition mcc.php:124