Web

Как восстановить сериализованную строку, которая была повреждена из-за неправильной длины счетчика байтов

Я использую Hotaru CMS с плагином загрузки изображений и получаю эту ошибку, если пытаюсь прикрепить изображение к сообщению, в противном случае ошибка не проявляется:

unserialize () [function.unserialize]: ошибка смещения

Код нарушения (ошибка указывает на строку с **):

/**

     * Получение данных о шаге представления

     *

     * @param $key — пустой при установке.

     * @return bool

     */

    public function loadSubmitData($h, $key = '') {

        // удалите все в этой таблице старше 30 минут:

        $this->deleteTempData($h->db);

        if (!$key) { return false; }

        $cleanKey = preg_replace('/[^a-z0-9]+/','',$key);

        if (strcmp($key,$cleanKey) != 0) {

            return false;

        } else {

            $sql = "SELECT tempdata_value FROM " . TABLE_TEMPDATA . " WHERE tempdata_key = %s ORDER BY tempdata_updatedts DESC LIMIT 1";

            $submitted_data = $h->db->get_var($h->db->prepare($sql, $key));

            **if ($submitted_data) { return unserialize($submitted_data); } else { return false; }** 

        }

    }

Данные из таблицы. Обратите внимание, что конечный бит содержит информацию об изображении. Я не эксперт в PHP, поэтому мне было интересно, что вы можете сказать насчет данной проблемы?

Значение шаблона:

a:10:{s:16:"submit_editorial";b:0;s:15:"submit_orig_url";s:13:"www.bbc.co.uk";s:12:"submit_title";s:14:"No title found";s:14:"submit_content";s:12:"dnfsdkfjdfdf";s:15:"submit_category";i:2;s:11:"submit_tags";s:3:"bbc";s:9:"submit_id";b:0;s:16:"submit_subscribe";i:0;s:15:"submit_comments";s:4:"open";s:5:"image";s:19:"C:fakepath100.jpg";}

Я думаю, что нашел бит сериализации ...

/**

     * Сохранить данные о шаге представления

     *

     * @return bool

     */

    public function saveSubmitData($h) {

        // удалите все в этой таблице старше 30 минут:

        $this->deleteTempData($h->db);

        $sid = preg_replace('/[^a-z0-9]+/i', '', session_id());

        $key = md5(microtime() . $sid . rand());

        $sql = "INSERT INTO " . TABLE_TEMPDATA . " (tempdata_key, tempdata_value, tempdata_updateby) VALUES (%s,%s, %d)";

        $h->db->query($h->db->prepare($sql, $key, serialize($h->vars['submitted_data']), $h->currentUser->id));

        return $key;

    }

 

Ответ 1

unserialize() [function.unserialize]: Ошибка при смещении была связана с недопустимыми данными сериализации из-за недопустимой длины

Быстрое исправление

Что вы можете сделать, так это пересчитать длину элементов в сериализованном массиве.

Текущие сериализованные данные

$data = 'a:10:{s:16:"submit_editorial";b:0;s:15:"submit_orig_url";s:13:"www.bbc.co.uk";s:12:"submit_title";s:14:"No title found";s:14:"submit_content";s:12:"dnfsdkfjdfdf";s:15:"submit_category";i:2;s:11:"submit_tags";s:3:"bbc";s:9:"submit_id";b:0;s:16:"submit_subscribe";i:0;s:15:"submit_comments";s:4:"open";s:5:"image";s:19:"C:fakepath100.jpg";}';

Пример без пересчета

var_dump(unserialize($data));

Вывод

Уведомление: unserialize() [function.unserialize]: Error at offset 337 of 338 bytes

Пример с пересчетом

$data = preg_replace('!s:(\d+):"(.*?)";!e', "'s:'.strlen('$2').':\"$2\";'", $data);

var_dump(unserialize($data));

Вывод

array

  'submit_editorial' => boolean false

  'submit_orig_url'  => string 'www.bbc.co.uk' (length=13)

  'submit_title'        => string 'No title found' (length=14)

  'submit_content'  => string 'dnfsdkfjdfdf' (length=12)

  'submit_category' => int 2

  'submit_tags'         => string 'bbc' (length=3)

  'submit_id'             => boolean false

  'submit_subscribe' => int 0

  'submit_comments' => string 'open' (length=4)

  'image'                     => string 'C:fakepath100.jpg' (length=17)

Рекомендация .. I

Вместо того чтобы использовать такое быстрое исправление ... , я советую вам обновить вопрос с помощью:

  • Как вы сериализуете свои данные

  • Как вы их сохраняете 

 

Ошибка

Ошибка возникла из-за использования двойной кавычки «вместо одинарной «, поэтому строка «C:\fakepath\100.png» была преобразована в строку «C:fakepath100.jpg».

Чтобы исправить ошибку

Вам нужно изменить $h->vars['submitted_data'] (обратите внимание на один апостроф)

Замените

 $h->vars['submitted_data']['image'] = "C:\fakepath\100.png" ;

На это:

 $h->vars['submitted_data']['image'] = 'C:\fakepath\100.png' ;

 Дополнительный фильтр

Вы также можете добавить этот простой фильтр перед вызовом сериализации:

function satitize(&$value, $key) {

    $value = addslashes($value);

}

array_walk($h->vars['submitted_data'], "satitize");

Если у вас есть символы UTF, вы также можете запустить:

 $h->vars['submitted_data'] = array_map("utf8_encode",$h->vars['submitted_data']);

Как обнаружить проблему в будущих сериализованных данных

  findSerializeError ( $data1 ) ;

 Вывод

Diffrence 9 != 7

    -> ORD number 57 != 55

    -> Line Number = 315

    -> Section Data1  = pen";s:5:"image";s:19:"C:fakepath100.jpg

    -> Section Data2  = pen";s:5:"image";s:17:"C:fakepath100.jpg

                                            ^------- здесь ошибка (длина элемента)

 Функция findSerializeError

function findSerializeError($data1) {

    echo "<pre>";

    $data2 = preg_replace ( '!s:(\d+):"(.*?)";!e', "'s:'.strlen('$2').':\"$2\";'",$data1 );

    $max = (strlen ( $data1 ) > strlen ( $data2 )) ? strlen ( $data1 ) : strlen ( $data2 );

    echo $data1 . PHP_EOL;

    echo $data2 . PHP_EOL;

    for($i = 0; $i < $max; $i ++) {

        if (@$data1 {$i} !== @$data2 {$i}) {

            echo "Diffrence ", @$data1 {$i}, " != ", @$data2 {$i}, PHP_EOL;

            echo "\t-> ORD number ", ord ( @$data1 {$i} ), " != ", ord ( @$data2 {$i} ), PHP_EOL;

            echo "\t-> Line Number = $i" . PHP_EOL;

            $start = ($i - 20);

            $start = ($start < 0) ? 0 : $start;

            $length = 40;

            $point = $max - $i;

            if ($point < 20) {

                $rlength = 1;

                $rpoint = - $point;

            } else {

                $rpoint = $length - 20;

                $rlength = 1;

            }

            echo "\t-> Section Data1  = ", substr_replace ( substr ( $data1, $start, $length ), "<b style=\"color:green\">{$data1 {$i}}</b>", $rpoint, $rlength ), PHP_EOL;

            echo "\t-> Section Data2  = ", substr_replace ( substr ( $data2, $start, $length ), "<b style=\"color:red\">{$data2 {$i}}</b>", $rpoint, $rlength ), PHP_EOL;

        }

    }

}

Лучший способ для сохранения в базе данных

$toDatabse = base64_encode(serialize($data));  // Сохраняем в базе данных

$fromDatabase = unserialize(base64_decode($data)); //Получение формата сохранения 

  

Ответ 2

Начиная с php 5.5, модификатор «/e» в preg_replace() был полностью устаревшим, и приведенный выше preg_match приведет к ошибке. Документация php рекомендует использовать вместо него preg_match_callback. В качестве альтернативы предложенному выше preg_match предлагаем следующее решение:

$fixed_data = preg_replace_callback ( '!s:(\d+):"(.*?)";!', function($match) {      

    return ($match[1] == strlen($match[2])) ? $match[0] : 's:' . strlen($match[2]) . ':"' . $match[2] . '";';

},$bad_data );



Ответ 3

$badData = 'a:2:{i:0;s:16:"as:45:"d";

Is \n";i:1;s:19:"as:45:"d";

Is \r\n";}';

Вы не можете исправить некорректную строку сериализации с помощью предлагаемых регулярных выражений:

$data = preg_replace('!s:(\d+):"(.*?)";!e', "'s:'.strlen('$2').':\"$2\";'", $badData);

var_dump(@unserialize($data)); // вывод: bool(false)

 

// или

 

$data = preg_replace_callback(

    '/s:(\d+):"(.*?)";/',

    function($m){

        return 's:' . strlen($m[2]) . ':"' . $m[2] . '";';

    },

    $badData

);

var_dump(@unserialize($data)); // Output: bool(false)

 Вы можете исправить некорректную строку сериализации, используя следующее регулярное выражение:

$data = preg_replace_callback(

    '/(?<=^|\{|;)s:(\d+):\"(.*?)\";(?=[asbdiO]\:\d|N;|\}|$)/s',

    function($m){

        return 's:' . strlen($m[2]) . ':"' . $m[2] . '";';

    },

    $badData

);

 

var_dump(@unserialize($data));

Вывод

array(2) {

  [0] => string(17) "as:45:"d";

Is \n"

  [1] => string(19) "as:45:"d";

Is \r\n"

}

или

array(2) {

  [0] => string(16) "as:45:"d";

Is \n"

  [1] => string(18) "as:45:"d";

Is \r\n"

}

 

Ответ 4

Повреждение в этом вопросе изолировано одной подстрокой в конце сериализованной строки, которая, вероятно, была вручную заменена кем-то, кто неудачно хотел обновить имя файла изображения. Этот факт будет очевиден в моей демонстрации, приведенной ниже, с использованием данных, опубликованных ОП. Вкратце: «C:fakepath100.jpg» не имеет длины «19», она должна быть равна «17».

Поскольку повреждение сериализованной строки ограничивается неправильным количеством байтов/символов, нижеприведенная процедура отлично справится с обновлением поврежденной строки правильным значением количества байтов.

Следующая замена на основе regex будет эффективна только для исправления количества байтов, не более того.

Похоже, что многие из предыдущих ответов это просто копирование шаблона regex, взятого у кого-то другого. Нет смысла перехватывать потенциально поврежденный номер подсчета байтов, если он не будет использоваться в замене. Кроме того, добавление модификатора шаблона «s» является разумным включением в том случае, если строковое значение содержит новые строки/возвраты строк.

*Для тех, кто не знает, как обращаться с многобайтовыми символами при сериализации: вы не должны использовать mb_strlen() в пользовательском обратном вызове, потому что сохраняется именно количество байтов, а не количество символов, см. мой вывод...

$corrupted = <<<STRING

a:4:{i:0;s:3:"three";i:1;s:5:"five";i:2;s:2:"newline1

newline2";i:3;s:6:"garçon";}

STRING;

 

$repaired = preg_replace_callback(

        '/s:\d+:"(.*?)";/s',

        // ^^^^- совпали/поглощены, но не захвачены, потому что не используются в замене

        function ($m) {

            return "s:" . strlen($m[1]) . ":\"{$m[1]}\";";

        },

        $corrupted

    );

echo $corrupted , "\n" , $repaired;

echo "\n---\n";

var_export(unserialize($repaired));

Вывод:

a:4:{i:0;s:3:"three";i:1;s:5:"five";i:2;s:2:"newline1

Newline2";i:3;s:6:"garçon";}

a:4:{i:0;s:5:"three";i:1;s:4:"five";i:2;s:17:"newline1

Newline2";i:3;s:7:"garçon";}

---

array (

  0 => 'three',

  1 => 'five',

  2 => 'newline1

Newline2',

  3 => 'garçon',

)

Вышеописанное работает, даже если в строковом значении встречаются двойные кавычки, но если строковое значение содержит «;» или какую-либо другую substring, вам придется пойти немного дальше и реализовать «обходные пути». Мой новый шаблон проверяет, что ведущее «s» является:

  1. началом всей входной строки

  2. или предшествует «;»

и проверяет, что «;» находится:

  1. в конце всей строки ввода 

  2. или за ним следует «}» 

  3. или за которым следует строковое или целочисленное объявление s: или i:

Я не проверял каждую возможность; на самом деле, я не совсем знаком со всеми возможностями сериализованной строки, потому что я никогда не работаю с сериализованными данными я всегда использую json в современных приложениях. 

Расширенный фрагмент:

$corrupted_byte_counts = <<<STRING

a:12:{i:0;s:3:"three";i:1;s:5:"five";i:2;s:2:"newline1

newline2";i:3;s:6:"garçon";i:4;s:111:"double " quote \"escaped";i:5;s:1:"a,comma";i:6;s:9:"a:colon";i:7;s:0:"single 'quote";i:8;s:999:"semi;colon";s:5:"assoc";s:3:"yes";i:9;s:1:"monkey";wrenching doublequote-semicolon";s:3:"s:";s:9:"val s: val";}

STRING;

 

$repaired = preg_replace_callback(

        '/(?<=^|;)s:\d+:"(.*?)";(?=$|}|[si]:)/s',

        //^^^^^^^^--------------^^^^^^^^^^^^^-- некоторая дополнительная проверка

        function ($m) {

            return 's:' . strlen($m[1]) . ":\"{$m[1]}\";";

        },

        $corrupted_byte_counts

    );

echo "разрушенный сериализованный массив:\n$corrupted_byte_counts";

echo "\n---\n";

echo "восстановленный сериализованный массив:\n$repaired";

echo "\n---\n";

print_r(unserialize($repaired));

 Вывод:

разрушенный сериализованный массив:

a:12:{i:0;s:3:"three";i:1;s:5:"five";i:2;s:2:"newline1

newline2";i:3;s:6:"garçon";i:4;s:111:"double " quote \"escaped";i:5;s:1:"a,comma";i:6;s:9:"a:colon";i:7;s:0:"single 'quote";i:8;s:999:"semi;colon";s:5:"assoc";s:3:"yes";i:9;s:1:"monkey";wrenching doublequote-semicolon";s:3:"s:";s:9:"val s: val";}

---

восстановленный сериализованный массив:

a:12:{i:0;s:5:"three";i:1;s:4:"five";i:2;s:17:"newline1

newline2";i:3;s:7:"garçon";i:4;s:24:"double " quote \"escaped";i:5;s:7:"a,comma";i:6;s:7:"a:colon";i:7;s:13:"single 'quote";i:8;s:10:"semi;colon";s:5:"assoc";s:3:"yes";i:9;s:39:"monkey";wrenching doublequote-semicolon";s:2:"s:";s:10:"val s: val";}

---

Array (

    [0] => three

    [1] => five

    [2] => newline1

newline2

    [3] => garçon

    [4] => double " quote \"escaped

    [5] => a,comma

    [6] => a:colon

    [7] => single 'quote

    [8] => semi;colon

    [assoc] => yes

    [9] => monkey";wrenching doublequote-semicolon

    [s:] => val s: val

)

 

Схожие статьи

Web

Как проверить, является ли массив PHP ассоциативным или последовательным

Сравнение VPN-сервисов: выбираем лучший по нескольким критериям
Web

Сравнение VPN-сервисов: выбираем лучший по нескольким критериям

Web

Как определить страну посетителя сайта по его IP адресу

Web

Преобразовать разделенных двоеточиями массив ключ: значение в массив с ключами