MediaWiki  REL1_22
StreamFile.php
Go to the documentation of this file.
00001 <?php
00026 class StreamFile {
00027     const READY_STREAM = 1;
00028     const NOT_MODIFIED = 2;
00029 
00041     public static function stream( $fname, $headers = array(), $sendErrors = true ) {
00042         wfProfileIn( __METHOD__ );
00043 
00044         if ( FileBackend::isStoragePath( $fname ) ) { // sanity
00045             wfProfileOut( __METHOD__ );
00046             throw new MWException( __FUNCTION__ . " given storage path '$fname'." );
00047         }
00048 
00049         wfSuppressWarnings();
00050         $stat = stat( $fname );
00051         wfRestoreWarnings();
00052 
00053         $res = self::prepareForStream( $fname, $stat, $headers, $sendErrors );
00054         if ( $res == self::NOT_MODIFIED ) {
00055             $ok = true; // use client cache
00056         } elseif ( $res == self::READY_STREAM ) {
00057             wfProfileIn( __METHOD__ . '-send' );
00058             $ok = readfile( $fname );
00059             wfProfileOut( __METHOD__ . '-send' );
00060         } else {
00061             $ok = false; // failed
00062         }
00063 
00064         wfProfileOut( __METHOD__ );
00065         return $ok;
00066     }
00067 
00081     public static function prepareForStream(
00082         $path, $info, $headers = array(), $sendErrors = true
00083     ) {
00084         if ( !is_array( $info ) ) {
00085             if ( $sendErrors ) {
00086                 header( 'HTTP/1.0 404 Not Found' );
00087                 header( 'Cache-Control: no-cache' );
00088                 header( 'Content-Type: text/html; charset=utf-8' );
00089                 $encFile = htmlspecialchars( $path );
00090                 $encScript = htmlspecialchars( $_SERVER['SCRIPT_NAME'] );
00091                 echo "<html><body>
00092                     <h1>File not found</h1>
00093                     <p>Although this PHP script ($encScript) exists, the file requested for output
00094                     ($encFile) does not.</p>
00095                     </body></html>
00096                     ";
00097             }
00098             return false;
00099         }
00100 
00101         // Sent Last-Modified HTTP header for client-side caching
00102         header( 'Last-Modified: ' . wfTimestamp( TS_RFC2822, $info['mtime'] ) );
00103 
00104         // Cancel output buffering and gzipping if set
00105         wfResetOutputBuffers();
00106 
00107         $type = self::contentTypeFromPath( $path );
00108         if ( $type && $type != 'unknown/unknown' ) {
00109             header( "Content-type: $type" );
00110         } else {
00111             // Send a content type which is not known to Internet Explorer, to
00112             // avoid triggering IE's content type detection. Sending a standard
00113             // unknown content type here essentially gives IE license to apply
00114             // whatever content type it likes.
00115             header( 'Content-type: application/x-wiki' );
00116         }
00117 
00118         // Don't stream it out as text/html if there was a PHP error
00119         if ( headers_sent() ) {
00120             echo "Headers already sent, terminating.\n";
00121             return false;
00122         }
00123 
00124         // Send additional headers
00125         foreach ( $headers as $header ) {
00126             header( $header );
00127         }
00128 
00129         // Don't send if client has up to date cache
00130         if ( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
00131             $modsince = preg_replace( '/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
00132             if ( wfTimestamp( TS_UNIX, $info['mtime'] ) <= strtotime( $modsince ) ) {
00133                 ini_set( 'zlib.output_compression', 0 );
00134                 header( "HTTP/1.0 304 Not Modified" );
00135                 return self::NOT_MODIFIED; // ok
00136             }
00137         }
00138 
00139         header( 'Content-Length: ' . $info['size'] );
00140 
00141         return self::READY_STREAM; // ok
00142     }
00143 
00151     public static function contentTypeFromPath( $filename, $safe = true ) {
00152         global $wgTrivialMimeDetection;
00153 
00154         $ext = strrchr( $filename, '.' );
00155         $ext = $ext === false ? '' : strtolower( substr( $ext, 1 ) );
00156 
00157         # trivial detection by file extension,
00158         # used for thumbnails (thumb.php)
00159         if ( $wgTrivialMimeDetection ) {
00160             switch ( $ext ) {
00161                 case 'gif': return 'image/gif';
00162                 case 'png': return 'image/png';
00163                 case 'jpg': return 'image/jpeg';
00164                 case 'jpeg': return 'image/jpeg';
00165             }
00166 
00167             return 'unknown/unknown';
00168         }
00169 
00170         $magic = MimeMagic::singleton();
00171         // Use the extension only, rather than magic numbers, to avoid opening
00172         // up vulnerabilities due to uploads of files with allowed extensions
00173         // but disallowed types.
00174         $type = $magic->guessTypesForExtension( $ext );
00175 
00180         if ( $safe ) {
00181             global $wgFileBlacklist, $wgCheckFileExtensions, $wgStrictFileExtensions,
00182                 $wgFileExtensions, $wgVerifyMimeType, $wgMimeTypeBlacklist;
00183             list( , $extList ) = UploadBase::splitExtensions( $filename );
00184             if ( UploadBase::checkFileExtensionList( $extList, $wgFileBlacklist ) ) {
00185                 return 'unknown/unknown';
00186             }
00187             if ( $wgCheckFileExtensions && $wgStrictFileExtensions
00188                 && !UploadBase::checkFileExtensionList( $extList, $wgFileExtensions ) )
00189             {
00190                 return 'unknown/unknown';
00191             }
00192             if ( $wgVerifyMimeType && in_array( strtolower( $type ), $wgMimeTypeBlacklist ) ) {
00193                 return 'unknown/unknown';
00194             }
00195         }
00196         return $type;
00197     }
00198 }