diff --git a/e107_handlers/e_db_pdo_class.php b/e107_handlers/e_db_pdo_class.php index cc5e2c55f..db57eb067 100644 --- a/e107_handlers/e_db_pdo_class.php +++ b/e107_handlers/e_db_pdo_class.php @@ -2295,42 +2295,47 @@ class e_db_pdo implements e_db return false; } - if($fields === '*') - { - $fields = $this->db_FieldList($table); - $unique = $this->_getUnique($table); + for ($retries = 0; $retries < 3; $retries ++) { + if ($fields === '*') { + $fieldList = $this->db_FieldList($table); + $unique = $this->_getUnique($table); - $flds = array(); - // randomize fields that must be unique. - foreach($fields as $fld) - { - if(isset($unique[$fld])) - { - $flds[] = $unique[$fld] === 'PRIMARY' ? 0 : "'rand-".rand(0,999)."'"; // keep it short. - continue; + $flds = array(); + // randomize fields that must be unique. + foreach ($fieldList as $fld) { + if (isset($unique[$fld])) { + $flds[] = $unique[$fld] === 'PRIMARY' ? 0 : + "'rand-" . e107::getUserSession()->generateRandomString('***********') . "'"; + continue; + } + + $flds[] = $fld; } - $flds[] = $fld; + $fieldList = implode(",", $fieldList); + $fieldList2 = implode(",", $flds); + } else { + $fieldList = $fields; + $fieldList2 = $fieldList; } - $fieldList = implode(",", $fields); - $fieldList2 = implode(",", $flds); - } - else - { - $fieldList = $fields; - $fieldList2 = $fieldList; - } + if (empty($fieldList)) { + $this->mysqlLastErrText = "copyRow \$fields list was empty"; + return false; + } - if(empty($fieldList)) - { - $this->mysqlLastErrText = "copyRow \$fields list was empty"; - return false; + $beforeLastInsertId = $this->lastInsertId(); + $id = $this->gen( + "INSERT INTO " . $this->mySQLPrefix . $table . + "(" . $fieldList . ") SELECT " . + $fieldList2 . + " FROM " . $this->mySQLPrefix . $table . + " WHERE " . $args + ); + $lastInsertId = $this->lastInsertId(); + if ($beforeLastInsertId !== $lastInsertId) break; } - $id = $this->gen("INSERT INTO ".$this->mySQLPrefix.$table."(".$fieldList.") SELECT ".$fieldList2." FROM ".$this->mySQLPrefix.$table." WHERE ".$args); - $lastInsertId = $this->lastInsertId(); - return ($id && $lastInsertId) ? $lastInsertId : false; } diff --git a/e107_tests/tests/unit/e_db_pdoTest.php b/e107_tests/tests/unit/e_db_pdoTest.php index 71eb9e280..630d707a6 100644 --- a/e107_tests/tests/unit/e_db_pdoTest.php +++ b/e107_tests/tests/unit/e_db_pdoTest.php @@ -118,4 +118,53 @@ class e_db_pdoTest extends e_db_abstractTest $qry = $this->db->getLastErrorText(); $this->assertGreaterThan(1,$result, $qry); } + + public function test_Db_CopyRowRNGRetry() + { + $original_user_handler = e107::getRegistry('core/e107/singleton/UserHandler'); + $evil_user_handler = $this->make('UserHandler', [ + 'generateRandomString' => function($pattern = '', $seed = '') + { + static $index = 0; + $mock_values = ['same0000000', 'same0000000', 'different00']; + + return $mock_values[$index ++]; + } + ]); + e107::setRegistry('core/e107/singleton/UserHandler', $evil_user_handler); + + // test with table that has unique keys. + $result = $this->db->db_CopyRow('core_media_cat', '*', "media_cat_id = 1"); + $qry = $this->db->getLastErrorText(); + $this->assertGreaterThan(1,$result, $qry); + + // test with table that has unique keys. (same row again) - make sure copyRow duplicates it regardless. + $result = $this->db->db_CopyRow('core_media_cat', '*', "media_cat_id = 1"); + $qry = $this->db->getLastErrorText(); + $this->assertGreaterThan(1,$result, $qry); + + e107::setRegistry('core/e107/singleton/UserHandler', $original_user_handler); + } + + public function test_Db_CopyRowRNGGiveUp() + { + $original_user_handler = e107::getRegistry('core/e107/singleton/UserHandler'); + $evil_user_handler = $this->make('UserHandler', [ + 'generateRandomString' => function($pattern = '', $seed = '') + { + return 'neverchange'; + } + ]); + e107::setRegistry('core/e107/singleton/UserHandler', $evil_user_handler); + + // test with table that has unique keys. + $result = $this->db->db_CopyRow('core_media_cat', '*', "media_cat_id = 1"); + $result = $this->db->db_CopyRow('core_media_cat', '*', "media_cat_id = 1"); + $qry = $this->db->getLastErrorText(); + $this->assertFalse($result, + "Intentionally broken random number generator should have prevented row copy with unique keys" + ); + + e107::setRegistry('core/e107/singleton/UserHandler', $original_user_handler); + } }