PHP反序列化因非编码字符而失败?
Posted
技术标签:
【中文标题】PHP反序列化因非编码字符而失败?【英文标题】:PHP unserialize fails with non-encoded characters? 【发布时间】:2011-02-20 15:50:23 【问题描述】:$ser = 'a:2:i:0;s:5:"héllö";i:1;s:5:"wörld";'; // fails
$ser2 = 'a:2:i:0;s:5:"hello";i:1;s:5:"world";'; // works
$out = unserialize($ser);
$out2 = unserialize($ser2);
print_r($out);
print_r($out2);
echo "<hr>";
但是为什么呢? 我应该在序列化之前编码吗?怎么样?
我使用 javascript 将序列化字符串写入隐藏字段,而不是 php 的 $_POST 在 JS 我有类似的东西:
function writeImgData()
var caption_arr = new Array();
$('.album img').each(function(index)
caption_arr.push($(this).attr('alt'));
);
$("#hidden-field").attr("value", serializeArray(caption_arr));
;
【问题讨论】:
【参考方案1】:unserialize()
失败的原因是:
$ser = 'a:2:i:0;s:5:"héllö";i:1;s:5:"wörld";';
是因为héllö
和wörld
的长度不对,因为PHP 本身不能正确处理多字节字符串:
echo strlen('héllö'); // 7
echo strlen('wörld'); // 6
但是,如果您尝试 unserialize()
以下正确字符串:
$ser = 'a:2:i:0;s:7:"héllö";i:1;s:6:"wörld";';
echo '<pre>';
print_r(unserialize($ser));
echo '</pre>';
有效:
Array
(
[0] => héllö
[1] => wörld
)
如果您使用 PHP serialize()
,它应该可以正确计算多字节字符串索引的长度。
另一方面,如果您想以多种(编程)语言处理序列化数据,您应该忘记它并转向 JSON 之类的东西,它更加标准化。
【讨论】:
json_encode: "此函数仅适用于 UTF-8 编码数据..." php.net/manual/en/function.json-encode.php 如果您正在使用 serialize( ) 并且 unserialize( ) 仍然失败,请检查您的存储介质。即你应该存储为二进制或blob的mysql。如果您在 mysql 中存储为文本,它将无法处理您的多字节字符。 在php环境之间切换时也要小心。在保存到数据库然后尝试在实时服务器上反序列化之前,我在本地机器上遇到了编码问题。调整字符的字符数解决了这个问题。 这可能也是我两年前遇到的一个问题的答案,但从未找到答案。 ***.com/questions/30289218/…【参考方案2】:我知道这是在一年前发布的,但我只是遇到了这个问题并遇到了这个问题,实际上我找到了解决方案。这段代码就像魅力一样!
背后的想法很简单。它只是通过重新计算上面@Alix 发布的多字节字符串的长度来帮助您。
一些修改应该适合您的代码:
/**
* Mulit-byte Unserialize
*
* UTF-8 will screw up a serialized string
*
* @access private
* @param string
* @return string
*/
function mb_unserialize($string)
$string = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $string);
return unserialize($string);
来源:http://snippets.dzone.com/posts/show/6592
在我的机器上测试过,效果很好!!
【讨论】:
在我的情况下,问题出在数据库编码中,所以我在???
中丢失了部分数据,但是这个功能可以帮助我使代码工作,谢谢
刚刚让我头疼不已!谢谢。
+1 为这项非常有用的工作。我也对其进行了测试,它适用于带有法语口音的 UTF-8 数据(我的服务器上的 PHP 5.3)。
我在下面发布了您的函数已更改为使用 PHP 5.5。感谢您的有用贡献。
其实正则表达式是错误的,因为字符串本身可能包含与序列化模式无关的模式。例如。函数之后的序列化部分...s:28:"some "quotes"; in the middle";...
将返回...s:13:"some \"quotes"; in the middle";...
。这就是首先创建序列化的原因之一。【参考方案3】:
Lionel Chan 答案已修改为使用 PHP >= 5.5:
function mb_unserialize($string)
$string2 = preg_replace_callback(
'!s:(\d+):"(.*?)";!s',
function($m)
$len = strlen($m[2]);
$result = "s:$len:\"$m[2]\";";
return $result;
,
$string);
return unserialize($string2);
此代码从 PHP 5.5 开始使用 preg_replace_callback 作为 preg_replace with the /e modifier is obsolete。
【讨论】:
我不得不使用这个版本来防止编码数组中的 html 字符串在未序列化的字符串中得到错误转义的双引号。 百万感谢@David。我一直在努力转换这个功能很多天了!【参考方案4】:问题是 - Alix 指出 - 与编码有关。
在 PHP 5.4 之前,PHP 的内部编码是 ISO-8859-1,这种编码对 unicode 中为多字节的某些字符使用单字节。结果是在 UTF-8 系统上序列化的多字节值在 ISO-8859-1 系统上将不可读。
避免此类问题确保所有系统使用相同的编码:
mb_internal_encoding('utf-8');
$arr = array('foo' => 'bár');
$buf = serialize($arr);
可以使用utf8_(encode|decode)
进行清理:
// Set system encoding to iso-8859-1
mb_internal_encoding('iso-8859-1');
$arr = unserialize(utf8_encode($serialized));
print_r($arr);
【讨论】:
【参考方案5】:回复上面的@Lionel,实际上如果序列化字符串本身包含字符序列";
(引号后跟分号),则您建议的函数mb_unserialize()将不起作用。
谨慎使用。例如:
$test = 'test";string';
// $test is now 's:12:"test";string";'
$string = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $test);
print $string;
// output: s:4:"test";string"; (Wrong!!)
JSON 是要走的路,正如其他人所提到的,恕我直言
注意:我将此作为新答案发布,因为我不知道如何直接回复(此处为新答案)。
【讨论】:
您很快就能回复 cmets。继续贡献!干杯~ 很高兴知道。有解决办法吗?【参考方案6】:当另一端不是 PHP 时,不要不使用 PHP 序列化/反序列化。它并不意味着是一种可移植的格式——例如,它甚至包括 ascii-1 字符作为受保护的密钥,这在 javascript 中是你不想处理的(即使它工作得很好,但它非常丑陋)。
改为使用 JSON 等可移植格式。 XML 也可以完成这项工作,但 JSON 的开销更少,并且对程序员更友好,因为您可以轻松地将其解析为简单的数据结构,而不必处理 XPath、DOM 树等。
【讨论】:
更不用说从不受信任的来源反序列化会导致任意代码执行。 不幸的是,选择是由其他人的工作强加给我们的。这在从旧项目/系统中导入数据时尤其常见,其中序列化已经在其数据中建立良好。【参考方案7】:这里还有一个细微的变化,希望对某人有所帮助……我正在序列化一个数组,然后将其写入数据库。在检索数据时,反序列化操作失败。
原来我写入的数据库长文本字段使用的是 latin1 而不是 UTF8。当我切换它时,一切都按计划进行。
感谢以上所有提到字符编码并让我走上正轨的人!
【讨论】:
【参考方案8】:这个解决方案对我有用:
$unserialized = unserialize(utf8_encode($st));
【讨论】:
【参考方案9】:我建议你使用 javascript 编码为 json,然后使用json_decode 进行反序列化。
【讨论】:
也就是说,$ser = 'a:2:i:0;s:5:"héllö";i:1;s:5:"wörld";'; var_dump(反序列化($ser));对我很好。你说的失败是什么意思?调用 unserialize() 失败?【参考方案10】:/**
* MULIT-BYTE UNSERIALIZE
*
* UTF-8 will screw up a serialized string
*
* @param string
* @return string
*/
function mb_unserialize($string)
$string = preg_replace_callback('/!s:(\d+):"(.*?)";!se/', function($matches) return 's:'.strlen($matches[1]).':"'.$matches[1].'";'; , $string);
return unserialize($string);
【讨论】:
【参考方案11】:我们可以将字符串分解为一个数组:
$finalArray = array();
$nodeArr = explode('&', $_POST['formData']);
foreach($nodeArr as $value)
$childArr = explode('=', $value);
$finalArray[$childArr[0]] = $childArr[1];
【讨论】:
【参考方案12】:序列化:
foreach ($income_data as $key => &$value)
$value = urlencode($value);
$data_str = serialize($income_data);
反序列化:
$data = unserialize($data_str);
foreach ($data as $key => &$value)
$value = urldecode($value);
【讨论】:
【参考方案13】:这个对我有用。
function mb_unserialize($string)
$string = mb_convert_encoding($string, "UTF-8", mb_detect_encoding($string, "UTF-8, ISO-8859-1, ISO-8859-15", true));
$string = preg_replace_callback(
'/s:([0-9]+):"(.*?)";/',
function ($match)
return "s:".strlen($match[2]).":\"".$match[2]."\";";
,
$string
);
return unserialize($string);
【讨论】:
【参考方案14】:在我的情况下,问题在于 行尾(可能某些编辑器已将我的文件从 DOS 更改为 Unix)。
我将这些自适应包装器放在一起:
function unserialize_fetchError($original, &$unserialized, &$errorMsg)
$unserialized = @unserialize($original);
$errorMsg = error_get_last()['message'];
return ( $unserialized !== false || $original == 'b:0;' ); // "$original == serialize(false)" is a good serialization even if deserialization actually returns false
function unserialize_checkAllLineEndings($original, &$unserialized, &$errorMsg, &$lineEndings)
if ( unserialize_fetchError($original, $unserialized, $errorMsg) )
$lineEndings = 'unchanged';
return true;
elseif ( unserialize_fetchError(str_replace("\n", "\n\r", $original), $unserialized, $errorMsg) )
$lineEndings = '\n to \n\r';
return true;
elseif ( unserialize_fetchError(str_replace("\n\r", "\n", $original), $unserialized, $errorMsg) )
$lineEndings = '\n\r to \n';
return true;
elseif ( unserialize_fetchError(str_replace("\r\n", "\n", $original), $unserialized, $errorMsg) )
$lineEndings = '\r\n to \n';
return true;
//else
return false;
【讨论】:
以上是关于PHP反序列化因非编码字符而失败?的主要内容,如果未能解决你的问题,请参考以下文章