MediaWiki master
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 try {
82 // PHP 7 emits warnings, suppress
83 AtEase::quietCall( 'fclose', $this->fp );
84 } catch ( \TypeError $unused ) {
85 // While PHP 8 throws exceptions, ignore
86 }
87 $this->fp = null;
88 }
89 }
90
91 public function detach() {
92 $ret = $this->fp;
93 $this->fp = null;
94 return $ret;
95 }
96
97 public function getSize() {
98 if ( $this->size === false ) {
99 $this->size = null;
100
101 if ( $this->fp ) {
102 // Spec doesn't care about errors here.
103 try {
104 $stat = AtEase::quietCall( 'fstat', $this->fp );
105 } catch ( \TypeError $unused ) {
106 }
107 $this->size = $stat['size'] ?? null;
108 }
109 }
110
111 return $this->size;
112 }
113
114 public function tell() {
115 $this->checkOpen();
116 return self::quietCall( 'ftell', [ $this->fp ], -1, 'Cannot determine stream position' );
117 }
118
119 public function eof() {
120 // Spec doesn't care about errors here.
121 try {
122 return !$this->fp || AtEase::quietCall( 'feof', $this->fp );
123 } catch ( \TypeError $unused ) {
124 return true;
125 }
126 }
127
128 public function isSeekable() {
129 return (bool)$this->fp;
130 }
131
132 public function seek( $offset, $whence = SEEK_SET ) {
133 $this->checkOpen();
134 self::quietCall( 'fseek', [ $this->fp, $offset, $whence ], -1, 'Seek failed' );
135 }
136
137 public function rewind() {
138 $this->seek( 0 );
139 }
140
141 public function isWritable() {
142 return false;
143 }
144
145 public function write( $string ) {
146 // @phan-suppress-previous-line PhanPluginNeverReturnMethod
147 $this->checkOpen();
148 throw new RuntimeException( 'Stream is read-only' );
149 }
150
151 public function isReadable() {
152 return (bool)$this->fp;
153 }
154
155 public function read( $length ) {
156 $this->checkOpen();
157 return self::quietCall( 'fread', [ $this->fp, $length ], false, 'Read failed' );
158 }
159
160 public function getContents() {
161 $this->checkOpen();
162 return self::quietCall( 'stream_get_contents', [ $this->fp ], false, 'Read failed' );
163 }
164
165 public function getMetadata( $key = null ) {
166 $this->checkOpen();
167 $ret = self::quietCall( 'stream_get_meta_data', [ $this->fp ], false, 'Metadata fetch failed' );
168 if ( $key !== null ) {
169 $ret = $ret[$key] ?? null;
170 }
171 return $ret;
172 }
173
174}
Implementation of StreamInterface for a file in $_FILES.