X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2FStreamFile.php;h=71113a8691dc798388f185086358ea59e7752edf;hp=0fc79802f3de82d4ec49ffcda0f4df252627f950;hb=e7720b8f8e09287384ddee271641d1b1721318bb;hpb=99dbf4e1d4d7f18e02935bdb8cd3be4413ffe729 diff --git a/includes/StreamFile.php b/includes/StreamFile.php index 0fc79802f3..71113a8691 100644 --- a/includes/StreamFile.php +++ b/includes/StreamFile.php @@ -25,9 +25,9 @@ */ class StreamFile { // Do not send any HTTP headers unless requested by caller (e.g. body only) - const STREAM_HEADLESS = 1; + const STREAM_HEADLESS = HTTPFileStreamer::STREAM_HEADLESS; // Do not try to tear down any PHP output buffers - const STREAM_ALLOW_OB = 2; + const STREAM_ALLOW_OB = HTTPFileStreamer::STREAM_ALLOW_OB; /** * Stream a file to the browser, adding all the headings and fun stuff. @@ -38,180 +38,49 @@ class StreamFile { * @param array $headers Any additional headers to send if the file exists * @param bool $sendErrors Send error messages if errors occur (like 404) * @param array $optHeaders HTTP request header map (e.g. "range") (use lowercase keys) - * @param integer $flags Bitfield of STREAM_* constants + * @param int $flags Bitfield of STREAM_* constants * @throws MWException * @return bool Success */ public static function stream( $fname, $headers = [], $sendErrors = true, $optHeaders = [], $flags = 0 ) { - $section = new ProfileSection( __METHOD__ ); - if ( FileBackend::isStoragePath( $fname ) ) { // sanity - throw new MWException( __FUNCTION__ . " given storage path '$fname'." ); - } - - // Don't stream it out as text/html if there was a PHP error - if ( ( ( $flags & self::STREAM_HEADLESS ) == 0 || $headers ) && headers_sent() ) { - echo "Headers already sent, terminating.\n"; - return false; - } - - $headerFunc = ( $flags & self::STREAM_HEADLESS ) - ? function ( $header ) { - // no-op - } - : function ( $header ) { - is_int( $header ) ? HttpStatus::header( $header ) : header( $header ); - }; - - MediaWiki\suppressWarnings(); - $info = stat( $fname ); - MediaWiki\restoreWarnings(); - - if ( !is_array( $info ) ) { - if ( $sendErrors ) { - self::send404Message( $fname, $flags ); - } - return false; - } - - // Send Last-Modified HTTP header for client-side caching - $headerFunc( 'Last-Modified: ' . wfTimestamp( TS_RFC2822, $info['mtime'] ) ); - - if ( ( $flags & self::STREAM_ALLOW_OB ) == 0 ) { - // Cancel output buffering and gzipping if set - wfResetOutputBuffers(); - } - - $type = self::contentTypeFromPath( $fname ); - if ( $type && $type != 'unknown/unknown' ) { - $headerFunc( "Content-type: $type" ); - } else { - // Send a content type which is not known to Internet Explorer, to - // avoid triggering IE's content type detection. Sending a standard - // unknown content type here essentially gives IE license to apply - // whatever content type it likes. - $headerFunc( 'Content-type: application/x-wiki' ); + throw new InvalidArgumentException( __FUNCTION__ . " given storage path '$fname'." ); } - // Don't send if client has up to date cache - if ( isset( $optHeaders['if-modified-since'] ) ) { - $modsince = preg_replace( '/;.*$/', '', $optHeaders['if-modified-since'] ); - if ( wfTimestamp( TS_UNIX, $info['mtime'] ) <= strtotime( $modsince ) ) { - ini_set( 'zlib.output_compression', 0 ); - $headerFunc( 304 ); - return true; // ok - } - } - - // Send additional headers - foreach ( $headers as $header ) { - header( $header ); // always use header(); specifically requested - } - - if ( isset( $optHeaders['range'] ) ) { - $range = self::parseRange( $optHeaders['range'], $info['size'] ); - if ( is_array( $range ) ) { - $headerFunc( 206 ); - $headerFunc( 'Content-Length: ' . $range[2] ); - $headerFunc( "Content-Range: bytes {$range[0]}-{$range[1]}/{$info['size']}" ); - } elseif ( $range === 'invalid' ) { - if ( $sendErrors ) { - $headerFunc( 416 ); - $headerFunc( 'Cache-Control: no-cache' ); - $headerFunc( 'Content-Type: text/html; charset=utf-8' ); - $headerFunc( 'Content-Range: bytes */' . $info['size'] ); - } - return false; - } else { // unsupported Range request (e.g. multiple ranges) - $range = null; - $headerFunc( 'Content-Length: ' . $info['size'] ); - } - } else { - $range = null; - $headerFunc( 'Content-Length: ' . $info['size'] ); - } + $streamer = new HTTPFileStreamer( + $fname, + [ + 'obResetFunc' => 'wfResetOutputBuffers', + 'streamMimeFunc' => [ __CLASS__, 'contentTypeFromPath' ] + ] + ); - if ( is_array( $range ) ) { - $handle = fopen( $fname, 'rb' ); - if ( $handle ) { - $ok = true; - fseek( $handle, $range[0] ); - $remaining = $range[2]; - while ( $remaining > 0 && $ok ) { - $bytes = min( $remaining, 8 * 1024 ); - $data = fread( $handle, $bytes ); - $remaining -= $bytes; - $ok = ( $data !== false ); - print $data; - } - } else { - return false; - } - } else { - return readfile( $fname ) !== false; // faster - } - - return true; + return $streamer->stream( $headers, $sendErrors, $optHeaders, $flags ); } /** * Send out a standard 404 message for a file * * @param string $fname Full name and path of the file to stream - * @param integer $flags Bitfield of STREAM_* constants + * @param int $flags Bitfield of STREAM_* constants * @since 1.24 */ public static function send404Message( $fname, $flags = 0 ) { - if ( ( $flags & self::STREAM_HEADLESS ) == 0 ) { - HttpStatus::header( 404 ); - header( 'Cache-Control: no-cache' ); - header( 'Content-Type: text/html; charset=utf-8' ); - } - $encFile = htmlspecialchars( $fname ); - $encScript = htmlspecialchars( $_SERVER['SCRIPT_NAME'] ); - echo " -

File not found

-

Although this PHP script ($encScript) exists, the file requested for output - ($encFile) does not.

- - "; + HTTPFileStreamer::send404Message( $fname, $flags ); } /** * Convert a Range header value to an absolute (start, end) range tuple * * @param string $range Range header value - * @param integer $size File size + * @param int $size File size * @return array|string Returns error string on failure (start, end, length) * @since 1.24 */ public static function parseRange( $range, $size ) { - $m = []; - if ( preg_match( '#^bytes=(\d*)-(\d*)$#', $range, $m ) ) { - list( , $start, $end ) = $m; - if ( $start === '' && $end === '' ) { - $absRange = [ 0, $size - 1 ]; - } elseif ( $start === '' ) { - $absRange = [ $size - $end, $size - 1 ]; - } elseif ( $end === '' ) { - $absRange = [ $start, $size - 1 ]; - } else { - $absRange = [ $start, $end ]; - } - if ( $absRange[0] >= 0 && $absRange[1] >= $absRange[0] ) { - if ( $absRange[0] < $size ) { - $absRange[1] = min( $absRange[1], $size - 1 ); // stop at EOF - $absRange[2] = $absRange[1] - $absRange[0] + 1; - return $absRange; - } elseif ( $absRange[0] == 0 && $size == 0 ) { - return 'unrecognized'; // the whole file should just be sent - } - } - return 'invalid'; - } - return 'unrecognized'; + return HTTPFileStreamer::parseRange( $range, $size ); } /**