From 1a1e917c86ca9468bd3f0dfece93ba283552c61f Mon Sep 17 00:00:00 2001 From: Chad Horohoe Date: Sat, 13 Aug 2011 19:03:51 +0000 Subject: [PATCH] Move wfStreamFile() into a class, update all callers in core (only 3 extensions use it afaict), leave wfStreamFile() as a b/c alias for now. Yay less global functions, autoloading and less manual require()s. --- img_auth.php | 3 +- includes/AutoLoader.php | 1 + includes/GlobalFunctions.php | 8 + includes/StreamFile.php | 195 ++++++++++---------- includes/specials/SpecialRevisiondelete.php | 5 +- includes/specials/SpecialUndelete.php | 4 +- thumb.php | 6 +- 7 files changed, 113 insertions(+), 109 deletions(-) diff --git a/img_auth.php b/img_auth.php index ed4c6be4b3..9b330ce2d4 100644 --- a/img_auth.php +++ b/img_auth.php @@ -32,7 +32,6 @@ if ( isset( $_SERVER['MW_COMPILED'] ) ) { require ( dirname( __FILE__ ) . '/includes/WebStart.php' ); } wfProfileIn( 'img_auth.php' ); -require_once( dirname( __FILE__ ) . '/includes/StreamFile.php' ); $wgActionPaths[] = $_SERVER['SCRIPT_NAME']; // See if this is a public Wiki (no protections) @@ -95,7 +94,7 @@ if( !$title->userCanRead() ) // Stream the requested file wfDebugLog( 'img_auth', "Streaming `".$filename."`." ); -wfStreamFile( $filename, array( 'Cache-Control: private', 'Vary: Cookie' ) ); +StreamFile::stream( $filename, array( 'Cache-Control: private', 'Vary: Cookie' ) ); wfLogProfilingData(); /** diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 274b896d2a..ad503a2736 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -213,6 +213,7 @@ $wgAutoloadLocalClasses = array( 'SquidPurgeClient' => 'includes/SquidPurgeClient.php', 'SquidPurgeClientPool' => 'includes/SquidPurgeClient.php', 'Status' => 'includes/Status.php', + 'StreamFile' => 'includes/StreamFile.php', 'StringUtils' => 'includes/StringUtils.php', 'StubContLang' => 'includes/StubObject.php', 'StubObject' => 'includes/StubObject.php', diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index a06fdeff8a..193b84b6c4 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -3196,6 +3196,14 @@ function wfLocalFile( $title ) { return RepoGroup::singleton()->getLocalRepo()->newFile( $title ); } +/** + * Stream a file to the browser. Back-compat alias for StreamFile::stream() + * @deprecated since 1.19 + */ +function wfStreamFile( $fname, $headers = array() ) { + StreamFile::stream( $fname, $headers ); +} + /** * Should low-performance queries be disabled? * diff --git a/includes/StreamFile.php b/includes/StreamFile.php index d08cfec60c..db470ffb44 100644 --- a/includes/StreamFile.php +++ b/includes/StreamFile.php @@ -4,119 +4,122 @@ * * @file */ +class StreamFile { + /** + * Stream a file to the browser, adding all the headings and fun stuff + * @param $fname string Full name and path of the file to stream + * @param $headers array Any additional headers to send + */ + public static function stream( $fname, $headers = array(), $request = null ) { + wfSuppressWarnings(); + $stat = stat( $fname ); + wfRestoreWarnings(); + if ( !$stat ) { + header( 'HTTP/1.0 404 Not Found' ); + 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.

+ + "; + return; + } -/** - * @param $fname string - * @param $headers array - */ -function wfStreamFile( $fname, $headers = array() ) { - wfSuppressWarnings(); - $stat = stat( $fname ); - wfRestoreWarnings(); - if ( !$stat ) { - header( 'HTTP/1.0 404 Not Found' ); - 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.

- -"; - return; - } - - header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $stat['mtime'] ) . ' GMT' ); - - // Cancel output buffering and gzipping if set - wfResetOutputBuffers(); - - $type = wfGetType( $fname ); - if ( $type and $type!="unknown/unknown") { - header("Content-type: $type"); - } else { - header('Content-type: application/x-wiki'); - } - - // Don't stream it out as text/html if there was a PHP error - if ( headers_sent() ) { - echo "Headers already sent, terminating.\n"; - return; - } + header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $stat['mtime'] ) . ' GMT' ); - global $wgLanguageCode; - header( "Content-Disposition: inline;filename*=utf-8'$wgLanguageCode'" . urlencode( basename( $fname ) ) ); + // Cancel output buffering and gzipping if set + wfResetOutputBuffers(); - foreach ( $headers as $header ) { - header( $header ); - } + $type = self::getType( $fname ); + if ( $type && $type != 'unknown/unknown' ) { + header( "Content-type: $type" ); + } else { + header( 'Content-type: application/x-wiki' ); + } - if ( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) { - $modsince = preg_replace( '/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE'] ); - $sinceTime = strtotime( $modsince ); - if ( $stat['mtime'] <= $sinceTime ) { - ini_set('zlib.output_compression', 0); - header( "HTTP/1.0 304 Not Modified" ); + // Don't stream it out as text/html if there was a PHP error + if ( headers_sent() ) { + echo "Headers already sent, terminating.\n"; return; } - } - header( 'Content-Length: ' . $stat['size'] ); + global $wgLanguageCode; + header( "Content-Disposition: inline;filename*=utf-8'$wgLanguageCode'" . urlencode( basename( $fname ) ) ); - readfile( $fname ); -} - -/** - * @param $filename string - * @param $safe bool - * @return null|string - */ -function wfGetType( $filename, $safe = true ) { - global $wgTrivialMimeDetection; - - $ext = strrchr($filename, '.'); - $ext = $ext === false ? '' : strtolower( substr( $ext, 1 ) ); + foreach ( $headers as $header ) { + header( $header ); + } - # trivial detection by file extension, - # used for thumbnails (thumb.php) - if ($wgTrivialMimeDetection) { - switch ($ext) { - case 'gif': return 'image/gif'; - case 'png': return 'image/png'; - case 'jpg': return 'image/jpeg'; - case 'jpeg': return 'image/jpeg'; + if ( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) { + $modsince = preg_replace( '/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE'] ); + $sinceTime = strtotime( $modsince ); + if ( $stat['mtime'] <= $sinceTime ) { + ini_set( 'zlib.output_compression', 0 ); + header( "HTTP/1.0 304 Not Modified" ); + return; + } } - return 'unknown/unknown'; + header( 'Content-Length: ' . $stat['size'] ); + + readfile( $fname ); } - - $magic = MimeMagic::singleton(); - // Use the extension only, rather than magic numbers, to avoid opening - // up vulnerabilities due to uploads of files with allowed extensions - // but disallowed types. - $type = $magic->guessTypesForExtension( $ext ); /** - * Double-check some security settings that were done on upload but might - * have changed since. + * Determine the filetype we're dealing with + * @param $filename string + * @param $safe bool + * @return null|string */ - if ( $safe ) { - global $wgFileBlacklist, $wgCheckFileExtensions, $wgStrictFileExtensions, - $wgFileExtensions, $wgVerifyMimeType, $wgMimeTypeBlacklist; - list( , $extList ) = UploadBase::splitExtensions( $filename ); - if ( UploadBase::checkFileExtensionList( $extList, $wgFileBlacklist ) ) { - return 'unknown/unknown'; - } - if ( $wgCheckFileExtensions && $wgStrictFileExtensions - && !UploadBase::checkFileExtensionList( $extList, $wgFileExtensions ) ) - { + private static function getType( $filename, $safe = true ) { + global $wgTrivialMimeDetection; + + $ext = strrchr( $filename, '.' ); + $ext = $ext === false ? '' : strtolower( substr( $ext, 1 ) ); + + # trivial detection by file extension, + # used for thumbnails (thumb.php) + if ( $wgTrivialMimeDetection ) { + switch ( $ext ) { + case 'gif': return 'image/gif'; + case 'png': return 'image/png'; + case 'jpg': return 'image/jpeg'; + case 'jpeg': return 'image/jpeg'; + } + return 'unknown/unknown'; } - if ( $wgVerifyMimeType && in_array( strtolower( $type ), $wgMimeTypeBlacklist ) ) { - return 'unknown/unknown'; + + $magic = MimeMagic::singleton(); + // Use the extension only, rather than magic numbers, to avoid opening + // up vulnerabilities due to uploads of files with allowed extensions + // but disallowed types. + $type = $magic->guessTypesForExtension( $ext ); + + /** + * Double-check some security settings that were done on upload but might + * have changed since. + */ + if ( $safe ) { + global $wgFileBlacklist, $wgCheckFileExtensions, $wgStrictFileExtensions, + $wgFileExtensions, $wgVerifyMimeType, $wgMimeTypeBlacklist; + list( , $extList ) = UploadBase::splitExtensions( $filename ); + if ( UploadBase::checkFileExtensionList( $extList, $wgFileBlacklist ) ) { + return 'unknown/unknown'; + } + if ( $wgCheckFileExtensions && $wgStrictFileExtensions + && !UploadBase::checkFileExtensionList( $extList, $wgFileExtensions ) ) + { + return 'unknown/unknown'; + } + if ( $wgVerifyMimeType && in_array( strtolower( $type ), $wgMimeTypeBlacklist ) ) { + return 'unknown/unknown'; + } } + return $type; } - return $type; } diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php index 90a203045b..9518e196b7 100644 --- a/includes/specials/SpecialRevisiondelete.php +++ b/includes/specials/SpecialRevisiondelete.php @@ -316,12 +316,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $this->getRequest()->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); $this->getRequest()->response()->header( 'Pragma: no-cache' ); - # Stream the file to the client - global $IP; - require_once( "$IP/includes/StreamFile.php" ); $key = $oimage->getStorageKey(); $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key; - wfStreamFile( $path ); + StreamFile::stream( $path ); } /** diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php index 54a792bab8..b4e0097640 100644 --- a/includes/specials/SpecialUndelete.php +++ b/includes/specials/SpecialUndelete.php @@ -1013,11 +1013,9 @@ class SpecialUndelete extends SpecialPage { $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); $response->header( 'Pragma: no-cache' ); - global $IP; - require_once( "$IP/includes/StreamFile.php" ); $repo = RepoGroup::singleton()->getLocalRepo(); $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key; - wfStreamFile( $path ); + StreamFile::stream( $path ); } private function showHistory() { diff --git a/thumb.php b/thumb.php index 1fe564d8fa..61fd788c9b 100644 --- a/thumb.php +++ b/thumb.php @@ -15,8 +15,6 @@ if ( isset( $_SERVER['MW_COMPILED'] ) ) { $wgTrivialMimeDetection = true; //don't use fancy mime detection, just check the file extension for jpg/gif/png. -require_once( "$IP/includes/StreamFile.php" ); - wfThumbMain(); wfLogProfilingData(); @@ -126,7 +124,7 @@ function wfThumbMain() { $thumbPath = $img->getThumbPath( $thumbName ); if ( is_file( $thumbPath ) ) { - wfStreamFile( $thumbPath, $headers ); + StreamFile::stream( $thumbPath, $headers ); wfProfileOut( __METHOD__ ); return; } @@ -155,7 +153,7 @@ function wfThumbMain() { $errorMsg = wfMsgHtml( 'thumbnail_error', 'Image was not scaled, ' . 'is the requested width bigger than the source?' ); } else { - wfStreamFile( $thumb->getPath(), $headers ); + StreamFile::stream( $thumb->getPath(), $headers ); } if ( $errorMsg !== false ) { wfThumbError( 500, $errorMsg ); -- 2.20.1