Skip to content

Commit

Permalink
Merge branch 'MDL-79017-311' of https://github.com/paulholden/moodle
Browse files Browse the repository at this point in the history
…into MOODLE_311_STABLE
  • Loading branch information
junpataleta committed Aug 31, 2023
2 parents 3179d4e + 8461a18 commit 7061297
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 42 deletions.
51 changes: 16 additions & 35 deletions lib/moodlelib.php
Expand Up @@ -10323,52 +10323,33 @@ function get_course_display_name_for_list($course) {
* Safe analogue of unserialize() that can only parse arrays
*
* Arrays may contain only integers or strings as both keys and values. Nested arrays are allowed.
* Note: If any string (key or value) has semicolon (;) as part of the string parsing will fail.
* This is a simple method to substitute unnecessary unserialize() in code and not intended to cover all possible cases.
*
* @param string $expression
* @return array|bool either parsed array or false if parsing was impossible.
*/
function unserialize_array($expression) {
$subs = [];
// Find nested arrays, parse them and store in $subs , substitute with special string.
while (preg_match('/([\^;\}])(a:\d+:\{[^\{\}]*\})/', $expression, $matches) && strlen($matches[2]) < strlen($expression)) {
$key = '--SUB' . count($subs) . '--';
$subs[$key] = unserialize_array($matches[2]);
if ($subs[$key] === false) {
return false;
}
$expression = str_replace($matches[2], $key . ';', $expression);
}

// Check the expression is an array.
if (!preg_match('/^a:(\d+):\{([^\}]*)\}$/', $expression, $matches1)) {
return false;
}
// Get the size and elements of an array (key;value;key;value;....).
$parts = explode(';', $matches1[2]);
$size = intval($matches1[1]);
if (count($parts) < $size * 2 + 1) {
if (!preg_match('/^a:(\d+):/', $expression)) {
return false;
}
// Analyze each part and make sure it is an integer or string or a substitute.
$value = [];
for ($i = 0; $i < $size * 2; $i++) {
if (preg_match('/^i:(\d+)$/', $parts[$i], $matches2)) {
$parts[$i] = (int)$matches2[1];
} else if (preg_match('/^s:(\d+):"(.*)"$/', $parts[$i], $matches3) && strlen($matches3[2]) == (int)$matches3[1]) {
$parts[$i] = $matches3[2];
} else if (preg_match('/^--SUB\d+--$/', $parts[$i])) {
$parts[$i] = $subs[$parts[$i]];
} else {
return false;

$values = (array) unserialize_object($expression);

// Callback that returns true if the given value is an unserialized object, executes recursively.
$invalidvaluecallback = static function($value) use (&$invalidvaluecallback): bool {
if (is_array($value)) {
return (bool) array_filter($value, $invalidvaluecallback);
}
return ($value instanceof stdClass) || ($value instanceof __PHP_Incomplete_Class);
};

// Iterate over the result to ensure there are no stray objects.
if (array_filter($values, $invalidvaluecallback)) {
return false;
}
// Combine keys and values.
for ($i = 0; $i < $size * 2; $i += 2) {
$value[$parts[$i]] = $parts[$i+1];
}
return $value;

return $values;
}

/**
Expand Down
16 changes: 9 additions & 7 deletions lib/tests/moodlelib_test.php
Expand Up @@ -4541,24 +4541,26 @@ public function data_email_is_not_allowed_for_denyemailaddresses() {
public function test_unserialize_array() {
$a = [1, 2, 3];
$this->assertEquals($a, unserialize_array(serialize($a)));
$this->assertEquals($a, unserialize_array(serialize($a)));
$a = ['a' => 1, 2 => 2, 'b' => 'cde'];
$this->assertEquals($a, unserialize_array(serialize($a)));
$this->assertEquals($a, unserialize_array(serialize($a)));
$a = ['a' => 1, 2 => 2, 'b' => 'c"d"e'];
$this->assertEquals($a, unserialize_array(serialize($a)));
$a = ['a' => 1, 2 => ['c' => 'd', 'e' => 'f'], 'b' => 'cde'];
$this->assertEquals($a, unserialize_array(serialize($a)));

// Can not unserialize if any string contains semicolons.
$a = ['a' => 1, 2 => ['c' => 'd', 'e' => ['f' => 'g']], 'b' => 'cde'];
$this->assertEquals($a, unserialize_array(serialize($a)));
$a = ['a' => 1, 2 => 2, 'b' => 'c"d";e'];
$this->assertEquals(false, unserialize_array(serialize($a)));
$this->assertEquals($a, unserialize_array(serialize($a)));

// Can not unserialize if there are any objects.
$a = (object)['a' => 1, 2 => 2, 'b' => 'cde'];
$this->assertEquals(false, unserialize_array(serialize($a)));
$this->assertFalse(unserialize_array(serialize($a)));
$a = ['a' => 1, 2 => 2, 'b' => (object)['a' => 'cde']];
$this->assertEquals(false, unserialize_array(serialize($a)));
$this->assertFalse(unserialize_array(serialize($a)));
$a = ['a' => 1, 2 => 2, 'b' => ['c' => (object)['a' => 'cde']]];
$this->assertFalse(unserialize_array(serialize($a)));
$a = ['a' => 1, 2 => 2, 'b' => ['c' => new lang_string('no')]];
$this->assertFalse(unserialize_array(serialize($a)));

// Array used in the grader report.
$a = array('aggregatesonly' => [51, 34], 'gradesonly' => [21, 45, 78]);
Expand Down

0 comments on commit 7061297

Please sign in to comment.