* (bug 8148) Handle non-removable output buffers gracefully when cleaning
authorBrion Vibber <brion@users.mediawiki.org>
Mon, 11 Dec 2006 01:51:21 +0000 (01:51 +0000)
committerBrion Vibber <brion@users.mediawiki.org>
Mon, 11 Dec 2006 01:51:21 +0000 (01:51 +0000)
  buffers for HTTP 304 responses, StreamFile, and Special:Export.
  Duplicated code merged into wfResetOutputBuffers() and wfClearOutputBuffers()

RELEASE-NOTES
includes/GlobalFunctions.php
includes/OutputPage.php
includes/SpecialExport.php
includes/StreamFile.php

index 6b8de8e..185a6b8 100644 (file)
@@ -263,6 +263,9 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
 * Removed a redundant <strong> tag from diff pages that was causing display issues for some users
 * (bug 8203) The keyboard shortcut for "log out" was removed, because users were pressing it
   when they intended to press the shortcut for "preview".
+* (bug 8148) Handle non-removable output buffers gracefully when cleaning
+  buffers for HTTP 304 responses, StreamFile, and Special:Export.
+  Duplicated code merged into wfResetOutputBuffers() and wfClearOutputBuffers()
 
 
 == Languages updated ==
index 4887653..8ebdc29 100644 (file)
@@ -1079,6 +1079,64 @@ function wfHttpError( $code, $label, $desc ) {
                "</p></body></html>\n";
 }
 
+/**
+ * Clear away any user-level output buffers, discarding contents.
+ *
+ * Suitable for 'starting afresh', for instance when streaming
+ * relatively large amounts of data without buffering, or wanting to
+ * output image files without ob_gzhandler's compression.
+ *
+ * The optional $resetGzipEncoding parameter controls suppression of
+ * the Content-Handler header sent by ob_gzhandler; by default it
+ * is left. See comments for wfClearOutputBuffers() for why it would
+ * be used.
+ *
+ * Note that some PHP configuration options may add output buffer
+ * layers which cannot be removed; these are left in place.
+ *
+ * @parameter bool $resetGzipEncoding
+ */
+function wfResetOutputBuffers( $resetGzipEncoding=true ) {
+       while( $status = ob_get_status() ) {
+               if( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
+                       // Probably from zlib.output_compression or other
+                       // PHP-internal setting which can't be removed.
+                       //
+                       // Give up, and hope the result doesn't break
+                       // output behavior.
+                       break;
+               }
+               if( !ob_end_clean() ) {
+                       // Could not remove output buffer handler; abort now
+                       // to avoid getting in some kind of infinite loop.
+                       break;
+               }
+               if( $resetGzipEncoding ) {
+                       if( $status['name'] == 'ob_gzhandler' ) {
+                               // Reset the 'Content-Encoding' field set by this handler
+                               // so we can start fresh.
+                               header( 'Content-Encoding:' );
+                       }
+               }
+       }
+}
+
+/**
+ * More legible than passing a 'false' parameter to wfResetOutputBuffers():
+ *
+ * Clear away output buffers, but keep the Content-Encoding header
+ * produced by ob_gzhandler, if any.
+ *
+ * This should be used for HTTP 304 responses, where you need to
+ * preserve the Content-Encoding header of the real result, but
+ * also need to suppress the output of ob_gzhandler to keep to spec
+ * and avoid breaking Firefox in rare cases where the headers and
+ * body are broken over two packets.
+ */
+function wfClearOutputBuffers() {
+       wfResetOutputBuffers( false );
+}
+
 /**
  * Converts an Accept-* header into an array mapping string values to quality
  * factors
index bbb068b..a314b55 100644 (file)
@@ -144,10 +144,12 @@ class OutputPage {
                                $this->sendCacheControl();
                                wfDebug( "$fname: CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false );
                                $this->disable();
-                               // Don't output compressed blob
-                               while( $status = ob_get_status() ) {
-                                       ob_end_clean();
-                               }
+                               
+                               // Don't output a compressed blob when using ob_gzhandler;
+                               // it's technically against HTTP spec and seems to confuse
+                               // Firefox when the response gets split over two packets.
+                               wfClearOutputBuffers();
+                               
                                return true;
                        } else {
                                wfDebug( "$fname: READY  client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false );
index 26ca7ef..5e6d6d8 100644 (file)
@@ -83,12 +83,7 @@ function wfSpecialExport( $page = '' ) {
                
                // Cancel output buffering and gzipping if set
                // This should provide safer streaming for pages with history
-               while( $status = ob_get_status() ) {
-                       ob_end_clean();
-                       if( $status['name'] == 'ob_gzhandler' ) {
-                               header( 'Content-Encoding:' );
-                       }
-               }
+               wfResetOutputBuffers();
                header( "Content-type: application/xml; charset=utf-8" );
                $pages = explode( "\n", $page );
 
index 6418269..949422d 100644 (file)
@@ -22,12 +22,7 @@ function wfStreamFile( $fname ) {
        header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $stat['mtime'] ) . ' GMT' );
 
        // Cancel output buffering and gzipping if set
-       while( $status = ob_get_status() ) {
-               ob_end_clean();
-               if( $status['name'] == 'ob_gzhandler' ) {
-                       header( 'Content-Encoding:' );
-               }
-       }
+       wfResetOutputBuffers();
        
        $type = wfGetType( $fname );
        if ( $type and $type!="unknown/unknown") {