【问题标题】:PHP unserialize fails with non-encoded characters?PHP反序列化因非编码字符而失败?
【发布时间】: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));
};

【问题讨论】:

    标签: php character encode serialization


    【解决方案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环境之间切换时也要小心。在保存到数据库然后尝试在实时服务器上反序列化之前,我在本地机器上遇到了编码问题。调整字符的字符数解决了这个问题。
    • 这可能也是我两年前遇到的一个问题的答案,但从未找到答案。 stackoverflow.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;
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2020-02-17
                    • 1970-01-01
                    • 2011-12-02
                    • 2018-02-20
                    • 1970-01-01
                    相关资源
                    最近更新 更多