(bug 34428) Fixed hash mismatch errors in DiffHistoryBlob::patch() by simulating...
authorTim Starling <tstarling@users.mediawiki.org>
Thu, 16 Feb 2012 23:27:00 +0000 (23:27 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Thu, 16 Feb 2012 23:27:00 +0000 (23:27 +0000)
RELEASE-NOTES-1.20
includes/HistoryBlob.php
tests/phpunit/includes/DiffHistoryBlobTest.php [new file with mode: 0644]

index bb894f8..5090672 100644 (file)
@@ -29,6 +29,8 @@ production.
 * (bug 12021) Added user talk link on Special:Listusers
 * (bug 34445) section edit and TOC hide/show links are excluded from selection and
   copy/paste on supporting browsers
+* (bug 34428) Fixed incorrect hash mismatch errors in the DiffHistoryBlob 
+  history compression method.
 
 === API changes in 1.20 ===
 * (bug 34316) Add ability to retrieve maximum upload size from MediaWiki API.
index cece3ec..7887ff8 100644 (file)
@@ -515,14 +515,11 @@ class DiffHistoryBlob implements HistoryBlob {
 
                $header = unpack( 'Vofp/Vcsize', substr( $diff, 0, 8 ) );
                
-               # Check the checksum if mhash is available
-               if ( extension_loaded( 'mhash' ) ) {
-                       $ofp = mhash( MHASH_ADLER32, $base );
-                       if ( $ofp !== substr( $diff, 0, 4 ) ) {
-                               wfDebug( __METHOD__. ": incorrect base checksum\n" );
-                               // Temp patch for bug 34428: don't return false
-                               //return false;
-                       }
+               # Check the checksum if hash/mhash is available
+               $ofp = $this->xdiffAdler32( $base );
+               if ( $ofp !== false && $ofp !== substr( $diff, 0, 4 ) ) {
+                       wfDebug( __METHOD__. ": incorrect base checksum\n" );
+                       return false;
                }
                if ( $header['csize'] != strlen( $base ) ) {
                        wfDebug( __METHOD__. ": incorrect base length\n" );
@@ -561,6 +558,29 @@ class DiffHistoryBlob implements HistoryBlob {
                return $out;
        }
 
+       /**
+        * Compute a binary "Adler-32" checksum as defined by LibXDiff, i.e. with 
+        * the bytes backwards and initialised with 0 instead of 1. See bug 34428.
+        *
+        * Returns false if no hashing library is available
+        */
+       function xdiffAdler32( $s ) {
+               static $init;
+               if ( $init === null ) {
+                       // The real Adler-32 checksum of this string is zero, so it 
+                       // initialises the state to the LibXDiff initial value.
+                       $init = str_repeat( "\xf0", 205 ) . "\xee" . str_repeat( "\xf0", 67 ) . "\x02";
+               }
+               if ( function_exists( 'hash' ) ) {
+                       $hash = hash( 'adler32', $init . $s, true );
+               } elseif ( function_exists( 'mhash' ) ) {
+                       $hash = mhash( MHASH_ADLER32, $init . $s );
+               } else {
+                       return false;
+               }
+               return strrev( $hash );
+       }
+
        function uncompress() {
                if ( !$this->mDiffs ) {
                        return;
diff --git a/tests/phpunit/includes/DiffHistoryBlobTest.php b/tests/phpunit/includes/DiffHistoryBlobTest.php
new file mode 100644 (file)
index 0000000..eda55e0
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+class DiffHistoryBlobTest extends MediaWikiTestCase {
+       function setUp() {
+               if ( !extension_loaded( 'xdiff' ) ) {
+                       $this->markTestSkipped( 'The xdiff extension is not available' );
+                       return;
+               }
+               if ( !extension_loaded( 'hash' ) && !extension_loaded( 'mhash' ) ) {
+                       $this->markTestSkipped( 'Neither the hash nor mhash extension is available' );
+                       return;
+               }
+       }
+
+       /**
+        * Test for DiffHistoryBlob::xdiffAdler32()
+        * @dataProvider provideXdiffAdler32
+        */
+       function testXdiffAdler32( $input ) {
+               $xdiffHash = substr( xdiff_string_rabdiff( $input, '' ),  0, 4 );
+               $dhb = new DiffHistoryBlob;
+               $myHash = $dhb->xdiffAdler32( $input );
+               $this->assertSame( bin2hex( $xdiffHash ), bin2hex( $myHash ),
+                       "Hash of " . addcslashes( $input, "\0..\37!@\@\177..\377" ) );
+       }
+
+       function provideXdiffAdler32() {
+               return array(
+                       array( '', 'Empty string' ),
+                       array( "\0", 'Null' ),
+                       array( "\0\0\0", "Several nulls" ),
+                       array( "Hello", "An ASCII string" ),
+                       array( str_repeat( "x", 6000 ), "A string larger than xdiff's NMAX (5552)" )
+               );
+       }
+}