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

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

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

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

/**
     * Retrieve submission step data
     *
     * @param $key - empty when setting
     * @return bool
     */
    public function loadSubmitData($h, $key = '')
    {
        // delete everything in this table older than 30 minutes:
        $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, поэтому мне было интересно, что вы, ребята, можете подумать?

tempdata_value:

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";}

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

/**
     * Save submission step data
     *
     * @return bool
     */
    public function saveSubmitData($h)
    {
        // delete everything in this table older than 30 minutes:
        $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]: Error at offset был списанием invalid serialization data из-за недопустимой длины

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

Что вы можете сделать, это recalculating the length элементов в сериализованном массиве

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

$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));

Выход

Notice: 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

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

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

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

========================= РЕДАКТИРОВАНИЕ 1 ========== ======================

Ошибка

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

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

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

Заменить

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

С

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

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

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

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
                                            ^------- The Error (Element Length)

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));  // Save to database
$fromDatabase = unserialize(base64_decode($data)); //Getting Save Format 

Ответ 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

Там была другая причина unserialize(), потому что вы неправильно разместили сериализованные данные в базе данных, см. Официальное объяснение здесь. Поскольку serialize() возвращает двоичные данные, а переменные php не заботятся о методах кодирования, поэтому при вводе его в TEXT VARCHAR() вызовет эту ошибку.

Решение. Храните сериализованные данные в BLOB в таблице.

Ответ 4

Быстрая починка

Пересчитывая длину элементов в сериализованном массиве - но не используйте (preg_replace), он устарел - лучше используйте preg_replace_callback:

$data = preg_replace_callback('!s:(\d+):"(.*?)";!', function($m) { return 's:'.mb_strlen($m[2]).':"'.$m[2].'";'; }, $data);

Редактировать: Новая версия теперь не просто неправильной длины, но также исправляет разрывы строк и подсчет правильных символов с помощью aczent (благодаря mickmackusa)

// New Version
$data = preg_replace_callback('!s:\d+:"(.*?)";!s', function($m) { return "s:" . strlen($m[1]) . ':"'.$m[1].'";'; }, $data);

Ответ 5

Эта ошибка вызвана тем, что ваша кодировка неверна.

Установить кодировку после открытого тега:

header('Content-Type: text/html; charset=utf-8');

И установите charset utf8 в вашей базе данных:

mysql_query("SET NAMES 'utf8'");

Ответ 6

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

function repairSerializeString($value)
{

    $regex = '/s:([0-9]+):"(.*?)"/';

    return preg_replace_callback(
        $regex, function($match) {
            return "s:".mb_strlen($match[2]).":\"".$match[2]."\""; 
        },
        $value
    );
}

Ответ 7

публичная функция unserializeKeySkills ($ string) {

    $output = array();
    $string = trim(preg_replace('/\s\s+/', ' ',$string));
    $string = preg_replace_callback('!s:(\d+):"(.*?)";!', function($m) { return 's:'.strlen($m[2]).':"'.$m[2].'";'; }, utf8_encode( trim(preg_replace('/\s\s+/', ' ',$string)) ));
    try {
        $output =  unserialize($string);
    } catch (\Exception $e) {
        \Log::error("unserialize Data : " .print_r($string,true));
    }
    return $output;
}

Ответ 8

$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)); // Output: bool(false)

// or

$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"
}

Ответ 9

официальные документы говорит, что он должен вернуть false и установить E_NOTICE

но поскольку вы получили ошибку, сообщение об ошибке настроено на запуск E_NOTICE

здесь есть исправление, позволяющее вам обнаружить ложь, возвращаемое unserialize

$old_err=error_reporting(); 
error_reporting($old_err & ~E_NOTICE);
$object = unserialize($serialized_data);
error_reporting($old_err);

вы можете захотеть использовать base64 encode/decode

$string=base64_encode(serialize($obj));
unserialize(base64_decode($string));

Ответ 10

В моем случае я хранил сериализованные данные в поле BLOB базы данных MySQL, которое, по-видимому, было недостаточно большим, чтобы содержать все значение и усекать его. Очевидно, что такая строка не может быть неэтериализована. После преобразования этого поля в MEDIUMBLOB проблема рассеялась. Также может потребоваться изменить параметры таблицы ROW_FORMAT на DYNAMIC или COMPRESSED.

Ответ 11

После того, как я пробовал некоторые вещи на этой странице без успеха, я посмотрел в источнике страницы и заметил, что все кавычки в сериализованной строке были заменены html-сущностями. Декодирование этих объектов помогает избежать значительной головной боли:

$myVar = html_entity_decode($myVar);

Ответ 12

Вам придется изменить тип сортировки на utf8_unicode_ci, и проблема будет исправлена.

Ответ 13

Другой причиной этой проблемы может быть столбец типа таблицы "полезная нагрузка". Если у вас огромные данные в сеансе, текстового столбца будет недостаточно. Вам понадобится MEDIUMTEXT или даже LONGTEXT.

Ответ 14

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

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

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

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

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

Код: (Демонстрация с данными OP) (Демонстрация с данными произвольного образца) (Демонстрация с заменой условия)

$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',
        //  ^^^- matched/consumed but not captured because not used in replacement
        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',
)

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

проверяет, что ведущий s это:

  • начало всей входной строки или
  • предшествует ;

и проверяет, что "; является:

  • в конце всей входной строки или
  • с последующим } или
  • сопровождаемый строковым или целочисленным объявлением 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',
        //^^^^^^^^--------------^^^^^^^^^^^^^-- some additional validation
        function ($m) {
            return 's:' . strlen($m[1]) . ":\"{$m[1]}\";";
        },
        $corrupted_byte_counts
    );

echo "corrupted serialized array:\n$corrupted_byte_counts";
echo "\n---\n";
echo "repaired serialized array:\n$repaired";
echo "\n---\n";
print_r(unserialize($repaired));

Выход:

corrupted serialized array:
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";}
---
repaired serialized array:
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
)

Ответ 15

Вот онлайн-инструмент для исправления поврежденной сериализованной строки.

Я хотел бы добавить, что это в основном происходит из-за поиска и замены в БД, и данные сериализации (особенно key length) не обновляются в соответствии с заменой, что вызывает "повреждение".

Тем не менее, вышеупомянутый инструмент использует следующую логику для исправления данных сериализации (скопировано отсюда).

function error_correction_serialise($string){
    // at first, check if "fixing" is really needed at all. After that, security checkup.
    if ( @unserialize($string) !== true &&  preg_match('/^[aOs]:/', $string) ) {
         $string = preg_replace_callback( '/s\:(\d+)\:\"(.*?)\";/s',    function($matches){return 's:'.strlen($matches[2]).':"'.$matches[2].'";'; },   $string );
    }
    return $string;
} 

Ответ 16

Я столкнулся с такой же проблемой, в то время как неэтериализуя данные. Выяснилось, что если есть ",", "или" в любом из значений массива, сериализация будет повреждена. У меня был: в моем массиве, поэтому удалил его, и он был исправлен.

Надеюсь, что это поможет кому-то.