From a85c9490b0bc9f5063e77288cb2a816a6da39b2b Mon Sep 17 00:00:00 2001 From: Huong Nguyen Date: Tue, 18 Mar 2025 12:36:58 +0700 Subject: [PATCH] MDL-84907 backup: Fix invalid CTRL characters regex --- backup/moodle2/restore_qtype_plugin.class.php | 3 +-- backup/moodle2/restore_stepslib.php | 3 +-- backup/util/xml/xml_writer.class.php | 6 +++--- lib/classes/text.php | 12 ++++++++++++ lib/upgrade.txt | 4 ++++ .../moodle2/restore_qtype_match_plugin.class.php | 7 ++----- 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/backup/moodle2/restore_qtype_plugin.class.php b/backup/moodle2/restore_qtype_plugin.class.php index 49bd8a18e30..6f2be835f25 100644 --- a/backup/moodle2/restore_qtype_plugin.class.php +++ b/backup/moodle2/restore_qtype_plugin.class.php @@ -168,8 +168,7 @@ abstract class restore_qtype_plugin extends restore_plugin { $this->questionanswercacheid = $newquestionid; // Cache all cleaned answers for a simple text match. foreach ($answers as $answer) { - // MDL-30018: Clean in the same way as {@link xml_writer::xml_safe_utf8()}. - $clean = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is','', $answer->answer); // Clean CTRL chars. + $clean = core_text::trim_ctrl_chars($answer->answer); // Clean CTRL chars. $clean = preg_replace("/\r\n|\r/", "\n", $clean); // Normalize line ending. $this->questionanswercache[$clean] = $answer->id; } diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index 39c5f5a5e1d..d9b114c5a8e 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -5262,8 +5262,7 @@ class restore_create_categories_and_questions extends restore_structure_step { $potentialhints = $DB->get_records('question_hints', array('questionid' => $newquestionid), '', 'id, hint'); foreach ($potentialhints as $potentialhint) { - // Clean in the same way than {@link xml_writer::xml_safe_utf8()}. - $cleanhint = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is','', $potentialhint->hint); // Clean CTRL chars. + $cleanhint = core_text::trim_ctrl_chars($potentialhint->hint); // Clean CTRL chars. $cleanhint = preg_replace("/\r\n|\r/", "\n", $cleanhint); // Normalize line ending. if ($cleanhint === $data->hint) { $newitemid = $data->id; diff --git a/backup/util/xml/xml_writer.class.php b/backup/util/xml/xml_writer.class.php index e048fa431fb..da4ed9ebbf2 100644 --- a/backup/util/xml/xml_writer.class.php +++ b/backup/util/xml/xml_writer.class.php @@ -253,14 +253,14 @@ class xml_writer { } /** - * Perform some UTF-8 cleaning, stripping the control chars (\x0-\x1f) - * but tabs (\x9), newlines (\xa) and returns (\xd). The delete control + * Perform some UTF-8 cleaning, stripping the control chars (\x00-\x1f) + * but tabs (\x09), newlines (\xa) and returns (\xd). The delete control * char (\x7f) is also included. All them are forbiden in XML 1.0 specs. * The expression below seems to be UTF-8 safe too because it simply * ignores the rest of characters. Also normalize linefeeds and return chars. */ protected function xml_safe_utf8($content) { - $content = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is', '', $content ?? ''); // clean CTRL chars. + $content = core_text::trim_ctrl_chars($content ?? ''); $content = preg_replace("/\r\n|\r/", "\n", $content); // Normalize line&return=>line return fix_utf8($content); } diff --git a/lib/classes/text.php b/lib/classes/text.php index b3b3164fc3f..073b99c8892 100644 --- a/lib/classes/text.php +++ b/lib/classes/text.php @@ -676,4 +676,16 @@ class core_text { return mb_convert_case($text, MB_CASE_TITLE, 'UTF-8'); } + + /** + * Trims control characters out of a string. + * Example: (\x00-\x1f) and (\x7f) + * + * @param string $text Input string + * @return string Cleaned string value + */ + public static function trim_ctrl_chars(string $text): string { + // Remove control characters text. + return preg_replace('/[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]/i', '', $text); + } } diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 6e58bee8b9e..b9d5f6a944f 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -1,6 +1,10 @@ This files describes API changes in core libraries and APIs, information provided here is intended especially for developers. +=== 4.3.12 === +* A new method, `core_text::trim_ctrl_chars()`, has been introduced to clean control characters from text. + This ensures cleaner input handling and prevents issues caused by invisible or non-printable characters + === 4.3.8 === * The `navigation_cache` class now uses the Moodle Universal Cache (MUC) to store the navigation cache data instead of storing diff --git a/question/type/match/backup/moodle2/restore_qtype_match_plugin.class.php b/question/type/match/backup/moodle2/restore_qtype_match_plugin.class.php index f9dc84bf59a..991609981d9 100644 --- a/question/type/match/backup/moodle2/restore_qtype_match_plugin.class.php +++ b/question/type/match/backup/moodle2/restore_qtype_match_plugin.class.php @@ -157,13 +157,10 @@ class restore_qtype_match_plugin extends restore_qtype_plugin { $this->questionsubcacheid = $newquestionid; // Cache all cleaned answers and questiontext. foreach ($potentialsubs as $potentialsub) { - // Clean in the same way than {@link xml_writer::xml_safe_utf8()}. - $cleanquestion = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is', - '', $potentialsub->questiontext); // Clean CTRL chars. + $cleanquestion = core_text::trim_ctrl_chars($potentialsub->questiontext); // Clean CTRL chars. $cleanquestion = preg_replace("/\r\n|\r/", "\n", $cleanquestion); // Normalize line ending. - $cleananswer = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is', - '', $potentialsub->answertext); // Clean CTRL chars. + $cleananswer = core_text::trim_ctrl_chars($potentialsub->answertext); // Clean CTRL chars. $cleananswer = preg_replace("/\r\n|\r/", "\n", $cleananswer); // Normalize line ending. $this->questionsubcache[$cleanquestion][$cleananswer] = $potentialsub->id;