23 use Wikimedia\AtEase\AtEase;
24 use Wikimedia\Timestamp\ConvertibleTimestamp;
54 foreach ( $headers as $name =>
$header ) {
55 $nameLower = strtolower( $name );
56 if ( in_array( $nameLower, [
'range',
'if-modified-since' ],
true ) ) {
57 $optHeaders[$nameLower] =
$header;
59 $rawHeaders[] =
"$name: $header";
62 return [ $rawHeaders, $optHeaders ];
73 $this->obResetFunc = $params[
'obResetFunc'] ?? [ __CLASS__,
'resetOutputBuffers' ];
74 $this->streamMimeFunc = $params[
'streamMimeFunc'] ?? [ __CLASS__,
'contentTypeFromPath' ];
89 $headers = [], $sendErrors =
true, $optHeaders = [], $flags = 0
92 if ( ( ( $flags & self::STREAM_HEADLESS ) == 0 || $headers ) && headers_sent() ) {
93 echo
"Headers already sent, terminating.\n";
101 :
static function (
$header ) {
105 AtEase::suppressWarnings();
106 $info = stat( $this->path );
107 AtEase::restoreWarnings();
109 if ( !is_array( $info ) ) {
117 $mtimeCT =
new ConvertibleTimestamp( $info[
'mtime'] );
118 $headerFunc(
'Last-Modified: ' . $mtimeCT->getTimestamp( TS_RFC2822 ) );
120 if ( ( $flags & self::STREAM_ALLOW_OB ) == 0 ) {
121 call_user_func( $this->obResetFunc );
124 $type = call_user_func( $this->streamMimeFunc, $this->path );
125 if ( $type && $type !=
'unknown/unknown' ) {
126 $headerFunc(
"Content-type: $type" );
132 $headerFunc(
'Content-type: application/x-wiki' );
136 if ( isset( $optHeaders[
'if-modified-since'] ) ) {
137 $modsince = preg_replace(
'/;.*$/',
'', $optHeaders[
'if-modified-since'] );
138 if ( $mtimeCT->getTimestamp( TS_UNIX ) <= strtotime( $modsince ) ) {
140 ini_set(
'zlib.output_compression', 0 );
147 foreach ( $headers as
$header ) {
151 if ( isset( $optHeaders[
'range'] ) ) {
153 if ( is_array( $range ) ) {
155 $headerFunc(
'Content-Length: ' . $range[2] );
156 $headerFunc(
"Content-Range: bytes {$range[0]}-{$range[1]}/{$info['size']}" );
157 } elseif ( $range ===
'invalid' ) {
160 $headerFunc(
'Cache-Control: no-cache' );
161 $headerFunc(
'Content-Type: text/html; charset=utf-8' );
162 $headerFunc(
'Content-Range: bytes */' . $info[
'size'] );
167 $headerFunc(
'Content-Length: ' . $info[
'size'] );
171 $headerFunc(
'Content-Length: ' . $info[
'size'] );
174 if ( is_array( $range ) ) {
175 $handle = fopen( $this->path,
'rb' );
178 fseek( $handle, $range[0] );
179 $remaining = $range[2];
180 while ( $remaining > 0 && $ok ) {
181 $bytes = min( $remaining, 8 * 1024 );
182 $data = fread( $handle, $bytes );
183 $remaining -= $bytes;
184 $ok = ( $data !== false );
191 return readfile( $this->path ) !==
false;
205 if ( ( $flags & self::STREAM_HEADLESS ) == 0 ) {
207 header(
'Cache-Control: no-cache' );
208 header(
'Content-Type: text/html; charset=utf-8' );
210 $encFile = htmlspecialchars( $fname );
211 $encScript = htmlspecialchars( $_SERVER[
'SCRIPT_NAME'] );
212 echo
"<!DOCTYPE html><html><body>
213 <h1>File not found</h1>
214 <p>Although this PHP script ($encScript) exists, the file requested for output
215 ($encFile) does not.</p>
230 if ( preg_match(
'#^bytes=(\d*)-(\d*)$#', $range, $m ) ) {
231 [ , $start, $end ] = $m;
232 if ( $start ===
'' && $end ===
'' ) {
233 $absRange = [ 0, $size - 1 ];
234 } elseif ( $start ===
'' ) {
235 $absRange = [ $size - (int)$end, $size - 1 ];
236 } elseif ( $end ===
'' ) {
237 $absRange = [ (int)$start, $size - 1 ];
239 $absRange = [ (int)$start, (
int)$end ];
241 if ( $absRange[0] >= 0 && $absRange[1] >= $absRange[0] ) {
242 if ( $absRange[0] < $size ) {
243 $absRange[1] = min( $absRange[1], $size - 1 );
244 $absRange[2] = $absRange[1] - $absRange[0] + 1;
246 } elseif ( $absRange[0] == 0 && $size == 0 ) {
247 return 'unrecognized';
252 return 'unrecognized';
256 while ( ob_get_status() ) {
257 if ( !ob_end_clean() ) {
272 $ext = strrchr( $filename,
'.' );
286 return 'unknown/unknown';
Functions related to the output of file content.
static preprocessHeaders( $headers)
Takes HTTP headers in a name => value format and converts them to the weird format expected by stream...
static send404Message( $fname, $flags=0)
Send out a standard 404 message for a file.
static resetOutputBuffers()
static contentTypeFromPath( $filename)
Determine the file type of a file based on the path.
static parseRange( $range, $size)
Convert a Range header value to an absolute (start, end) range tuple.
__construct( $path, array $params=[])
stream( $headers=[], $sendErrors=true, $optHeaders=[], $flags=0)
Stream a file to the browser, adding all the headings and fun stuff.
static header( $code)
Output an HTTP status code header.
if(!is_readable( $file)) $ext