php 如何修复因字节计数长度不正确而损坏的序列化字符串?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10152904/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 21:31:17  来源:igfitidea点击:

How to repair a serialized string which has been corrupted by an incorrect byte count length?

phpmysqlserializationcontent-management-system

提问by user576820

I am using Hotaru CMS with the Image Upload plugin, I get this error if I try to attach an image to a post, otherwise there is no error:

我将 Hotaru CMS 与图像上传插件一起使用,如果我尝试将图像附加到帖子中,则会出现此错误,否则不会出现错误:

unserialize() [function.unserialize]: Error at offset

unserialize() [function.unserialize]:偏移量错误

The offending code (error points to line with **):

违规代码(错误指向**):

/**
     * 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; }** 
        }
    }

Data from the table, notice the end bit has the image info, I am not an expert in PHP so I was wondering what you guys/gals might think?

表格中的数据,注意末尾有图像信息,我不是 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";}

Edit: I think I've found the serialize bit...

编辑:我想我已经找到了序列化位...

/**
     * 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;
    }

回答by Baba

unserialize() [function.unserialize]: Error at offsetwas dues to invalid serialization datadue to invalid length

unserialize() [function.unserialize]: Error at offsetinvalid serialization data由于长度无效造成的

Quick Fix

快速解决

What you can do is is recalculating the lengthof the elements in serialized array

你可以做的是recalculating the length序列化数组中的元素

You current serialized data

您当前的序列化数据

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

Example without recalculation

没有重新计算的示例

var_dump(unserialize($data));

Output

输出

Notice: unserialize() [function.unserialize]: Error at offset 337 of 338 bytes

Recalculating

重新计算

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

Output

输出

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)

Recommendation.. I

推荐..我

Instead of using this kind of quick fix ... i"ll advice you update the question with

而不是使用这种快速修复......我会建议你更新问题

  • How you are serializing your data

  • How you are Saving it ..

  • 您如何序列化数据

  • 你是如何保存它的..

================================ EDIT 1 ===============================

================================ 编辑 1 ================ ================

The Error

错误

The Error was generated because of use of double quote "instead single quote 'that is why C:\fakepath\100.pngwas converted to C:fakepath100.jpg

由于使用双引号"而不是单引号而生成错误',这C:\fakepath\100.png就是转换为C:fakepath100.jpg

To fix the error

修复错误

You need to change $h->vars['submitted_data']From (Note the singe quite ')

你需要改变$h->vars['submitted_data']From (注意单曲')

Replace

代替

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

With

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

Additional Filter

附加过滤器

You can also add this simple filter before you call serialize

您还可以在调用序列化之前添加这个简单的过滤器

function satitize(&$value, $key)
{
    $value = addslashes($value);
}

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

If you have UTF Characters you can also run

如果你有 UTF 字符,你也可以运行

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

How to detect the problem in future serialized data

如何检测未来序列化数据中的问题

  findSerializeError ( $data1 ) ;

Output

输出

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)

findSerializeErrorFunction

findSerializeError功能

function findSerializeError($data1) {
    echo "<pre>";
    $data2 = preg_replace ( '!s:(\d+):"(.*?)";!e', "'s:'.strlen('').':\"\";'",$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;
        }

    }

}

A better way to save to Database

保存到数据库的更好方法

$toDatabse = base64_encode(serialize($data));  // Save to database
$fromDatabase = unserialize(base64_decode($data)); //Getting Save Format 

回答by r00tAcc3ss

I don't have enough reputation to comment, so I hope this is seen by people using the above "correct" answer:

我没有足够的声誉来发表评论,所以我希望使用上述“正确”答案的人能看到这一点:

Since php 5.5 the /e modifier in preg_replace() has been deprecated completely and the preg_match above will error out. The php documentation recommends using preg_match_callback in its place.

从 php 5.5 开始, preg_replace() 中的 /e 修饰符已被完全弃用,上面的 preg_match 将出错。php 文档建议在其位置使用 preg_match_callback。

Please find the following solution as an alternative to the above proposed preg_match.

请找到以下解决方案作为上述建议的 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 );

回答by Ge Rong

There's another reason unserialize()failed because you improperly put serialized data into the database see Official Explanationhere. Since serialize()returns binary data and php variables don't care encoding methods, so that putting it into TEXT, VARCHAR() will cause this error.

unserialize()失败的另一个原因是您未正确地将序列化数据放入数据库,请参阅此处的官方说明。由于serialize()返回的二进制数据和php变量不关心编码方式,所以把它放到TEXT中,VARCHAR()会导致这个错误。

Solution: store serialized data into BLOB in your table.

解决方案:将序列化数据存储到表中的 BLOB 中。

回答by adilbo

Quick Fix

快速解决

Recalculating the length of the elements in serialized array - but don't use (preg_replace) it's deprecated - better use preg_replace_callback:

重新计算序列化数组中元素的长度 - 但不要使用 (preg_replace) 它已被弃用 - 最好使用 preg_replace_callback:

Edit: New Version now not just wrong length but it also fix line-breaks and count correct characters with aczent (thanks to mickmackusa)

编辑:新版本现在不仅长度错误,而且还修复了换行符并使用 aczent 计算正确的字符(感谢mickmackusa

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

回答by Will

This error is caused because your charset is wrong.

此错误是由于您的字符集错误引起的。

Set charset after open tag:

在打开标签后设置字符集:

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

And set charset utf8 in your database :

并在您的数据库中设置字符集 utf8 :

mysql_query("SET NAMES 'utf8'");

回答by Rajesh Meniya

You can fix broken serialize string using following function, with multibyte characterhandling.

您可以使用以下函数修复损坏的序列化字符串,以及多字节字符处理。

function repairSerializeString($value)
{

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

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

回答by Даниил Путилин

$badData = 'a:2:{i:0;s:16:"as:45:"d";
Is \n";i:1;s:19:"as:45:"d";
Is \r\n";}';

You can not fix a broken serialize string using the proposed regexes:

您无法使用建议的正则表达式修复损坏的序列化字符串:

$data = preg_replace('!s:(\d+):"(.*?)";!e', "'s:'.strlen('').':\"\";'", $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)

You can fix broken serialize string using following regex:

您可以使用以下正则表达式修复损坏的序列化字符串:

$data = preg_replace_callback(
    '/(?<=^|\{|;)s:(\d+):\"(.*?)\";(?=[asbdiO]\:\d|N;|\}|$)/s',
    function($m){
        return 's:' . strlen($m[2]) . ':"' . $m[2] . '";';
    },
    $badData
);

var_dump(@unserialize($data));

Output

输出

array(2) {
  [0] =>
  string(17) "as:45:"d";
Is \n"
  [1] =>
  string(19) "as:45:"d";
Is \r\n"
}

or

或者

array(2) {
  [0] =>
  string(16) "as:45:"d";
Is \n"
  [1] =>
  string(18) "as:45:"d";
Is \r\n"
}

回答by Pardeep Goyal

public function unserializeKeySkills($string) {

公共函数 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;
}

回答by Muayyad Alsadi

the official docssays it should return false and set E_NOTICE

官方的文档说,它应该返回false和集E_NOTICE

but since you got error then the error reporting is set to be triggered by E_NOTICE

但是由于您遇到错误,因此错误报告设置为由 E_NOTICE 触发

here is a fix to allow you detect false returned by unserialize

这是一个修复程序,可让您检测返回的错误 unserialize

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

you might want to consider use base64 encode/decode

您可能需要考虑使用 base64 编码/解码

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

回答by Adam Bubela

In my case I was storing serialized data in BLOBfield of MySQL DB which apparently wasn't big enough to contain the whole value and truncated it. Such a string obviously could not be unserialized.
Once converted that field to MEDIUMBLOBthe problem dissipated. Also it may be needed to switch in table options ROW_FORMATto DYNAMICor COMPRESSED.

在我的情况下,我将序列化数据存储在BLOBMySQL DB 的字段中,这显然不足以包含整个值并截断它。这样的字符串显然无法反序列化。
一旦将该字段转换MEDIUMBLOB为问题就消失了。此外,可能需要将表选项切换ROW_FORMATDYNAMICCOMPRESSED