22 use Wikimedia\Timestamp\ConvertibleTimestamp;
53 $nameLower = strtolower(
$name );
54 if ( in_array( $nameLower, [
'range',
'if-modified-since' ],
true ) ) {
55 $optHeaders[$nameLower] =
$header;
57 $rawHeaders[] =
"$name: $header";
60 return [ $rawHeaders, $optHeaders ];
71 $this->obResetFunc = isset(
$params[
'obResetFunc'] )
73 : [ __CLASS__,
'resetOutputBuffers' ];
74 $this->streamMimeFunc = isset(
$params[
'streamMimeFunc'] )
76 : [ __CLASS__,
'contentTypeFromPath' ];
92 $headers = [], $sendErrors =
true, $optHeaders = [], $flags = 0
95 if ( ( ( $flags & self::STREAM_HEADLESS ) == 0 || $headers ) && headers_sent() ) {
96 echo
"Headers already sent, terminating.\n";
108 Wikimedia\suppressWarnings();
109 $info = stat( $this->path );
110 Wikimedia\restoreWarnings();
112 if ( !is_array( $info ) ) {
120 $mtimeCT =
new ConvertibleTimestamp( $info[
'mtime'] );
121 $headerFunc(
'Last-Modified: ' . $mtimeCT->getTimestamp( TS_RFC2822 ) );
123 if ( ( $flags & self::STREAM_ALLOW_OB ) == 0 ) {
124 call_user_func( $this->obResetFunc );
127 $type = call_user_func( $this->streamMimeFunc, $this->path );
129 $headerFunc(
"Content-type: $type" );
135 $headerFunc(
'Content-type: application/x-wiki' );
139 if ( isset( $optHeaders[
'if-modified-since'] ) ) {
140 $modsince = preg_replace(
'/;.*$/',
'', $optHeaders[
'if-modified-since'] );
141 if ( $mtimeCT->getTimestamp( TS_UNIX ) <= strtotime( $modsince ) ) {
142 ini_set(
'zlib.output_compression', 0 );
153 if ( isset( $optHeaders[
'range'] ) ) {
155 if ( is_array( $range ) ) {
157 $headerFunc(
'Content-Length: ' . $range[2] );
158 $headerFunc(
"Content-Range: bytes {$range[0]}-{$range[1]}/{$info['size']}" );
159 } elseif ( $range ===
'invalid' ) {
162 $headerFunc(
'Cache-Control: no-cache' );
163 $headerFunc(
'Content-Type: text/html; charset=utf-8' );
164 $headerFunc(
'Content-Range: bytes */' . $info[
'size'] );
169 $headerFunc(
'Content-Length: ' . $info[
'size'] );
173 $headerFunc(
'Content-Length: ' . $info[
'size'] );
176 if ( is_array( $range ) ) {
177 $handle = fopen( $this->path,
'rb' );
180 fseek( $handle, $range[0] );
181 $remaining = $range[2];
182 while ( $remaining > 0 && $ok ) {
183 $bytes = min( $remaining, 8 * 1024 );
184 $data = fread( $handle, $bytes );
185 $remaining -= $bytes;
186 $ok = ( $data !==
false );
193 return readfile( $this->path ) !==
false;
207 if ( ( $flags & self::STREAM_HEADLESS ) == 0 ) {
209 header(
'Cache-Control: no-cache' );
210 header(
'Content-Type: text/html; charset=utf-8' );
212 $encFile = htmlspecialchars(
$fname );
213 $encScript = htmlspecialchars( $_SERVER[
'SCRIPT_NAME'] );
214 echo
"<!DOCTYPE html><html><body>
215 <h1>File not found</h1>
216 <p>Although this PHP script ($encScript) exists, the file requested for output
217 ($encFile) does not.</p>
232 if ( preg_match(
'#^bytes=(\d*)-(\d*)$#', $range, $m ) ) {
233 list( , $start, $end ) = $m;
234 if ( $start ===
'' && $end ===
'' ) {
235 $absRange = [ 0, $size - 1 ];
236 } elseif ( $start ===
'' ) {
237 $absRange = [ $size - $end, $size - 1 ];
238 } elseif ( $end ===
'' ) {
239 $absRange = [ $start, $size - 1 ];
241 $absRange = [ $start, $end ];
243 if ( $absRange[0] >= 0 && $absRange[1] >= $absRange[0] ) {
244 if ( $absRange[0] < $size ) {
245 $absRange[1] = min( $absRange[1], $size - 1 );
246 $absRange[2] = $absRange[1] - $absRange[0] + 1;
248 } elseif ( $absRange[0] == 0 && $size == 0 ) {
249 return 'unrecognized';
254 return 'unrecognized';
258 while ( ob_get_status() ) {
259 if ( !ob_end_clean() ) {
274 $ext = strrchr( $filename,
'.' );
275 $ext =
$ext ===
false ?
'' : strtolower( substr(
$ext, 1 ) );
288 return 'unknown/unknown';