PHP:在不知道原始字符集的情况下将任何字符串转换为UTF-8,或者至少尝试

时间:2011-11-02 11:27:16

标签: php utf-8 character-encoding

我有一个应用程序来处理来自世界各地的客户,当然,我希望进入我的数据库的所有内容都是UTF-8编码的。

对我来说,主要的问题是我不知道任何字符串的来源编码是什么 - 它可以来自文本框(使用<form accept-charset="utf-8">仅在用户实际提交时才有用表格),或者它可能来自上传的文本文件,所以我真的无法控制输入。

我需要的是一个函数或类,它确保进入我的数据库的内容尽可能采用UTF-8编码。我试过iconv(mb_detect_encoding($text), "UTF-8", $text); 但这有问题(如果输入'未婚妻'它返回'fianc')。我尝试了很多东西= /

对于文件上传,我喜欢要求最终用户指定他们使用的编码,并向他们展示输出结果的预览,但这无助于对抗讨厌的黑客(事实上,它可以让他们的生活更轻松一点。)

我已经阅读了关于这个问题的其他SO问题,但它们似乎都有微妙的差异,例如“我需要解析RSS提要”或“我从网站上搜集数据”(或者,实际上,“你不能“)。

但必须有一些东西至少有一个好的尝试

12 个答案:

答案 0 :(得分:232)

你所要求的是非常困难的。如果可能,让用户指定编码是最好的。防止攻击不应该那么容易或者更难。

但是,您可以尝试这样做:

iconv(mb_detect_encoding($text, mb_detect_order(), true), "UTF-8", $text);

将其设置为严格可能有助于您获得更好的结果。

答案 1 :(得分:27)

在祖国俄罗斯,我们有4种流行的编码,所以你的问题在这里有很大的需求。

只有符号的char代码才能检测到编码,因为代码页相交。一些不同语言的代码页甚至有完整的交集。所以,我们需要另一种方法

使用未知编码的唯一方法是使用概率。因此,我们不想回答“这篇文章的编码是什么?”这一问题,我们试图理解“这篇文章的最可能编码是什么?”。

俄罗斯科技博客中的一个人发明了这种方法:

在您要支持的每个编码中构建字符代码的概率范围。你可以使用你的语言中的一些大文本来构建它(例如一些小说,使用莎士比亚作为英语,托尔斯泰使用俄语,哈哈)。你会得到这样的东西:

    encoding_1:
    190 => 0.095249209893009,
    222 => 0.095249209893009,
    ...
    encoding_2:
    239 => 0.095249209893009,
    207 => 0.095249209893009,
    ...
    encoding_N:
    charcode => probabilty

下一步。您可以使用未知编码的文本和“概率词典”中的每个编码来搜索未知编码文本中每个符号的频率。符号的概率概率。具有更高评级的编码可能是赢家。更大的文本结果更好。

如果您有兴趣,我很乐意帮助您完成此任务。我们可以通过构建两个charcodes概率列表来大大提高准确性。

顺便说一下。 mb_detect_encoding certanly不起作用。是的,完全没有。请查看“ext / mbstring / libmbfl / mbfl / mbfl_ident.c”中的mb_detect_encoding源代码。

答案 2 :(得分:9)

你可能已经尝试过了,但为什么不使用mb_convert_encoding函数呢?它将尝试自动检测所提供文本的字符集,或者您可以将其传递给列表。

另外,我试图运行:

$text = "fiancée";
echo mb_convert_encoding($text, "UTF-8");
echo "<br/><br/>";
echo iconv(mb_detect_encoding($text), "UTF-8", $text);

两者的结果相同。你如何看待你的文字被截断为'fianc'?是在数据库中还是在浏览器中?

答案 3 :(得分:5)

无法识别完全准确的字符串字符集。 有办法尝试猜测字符集。 mb_detect_encoding()是其中一种方式,也许是目前最好的PHP方式。这将扫描您的字符串并查找某些字符集特有的内容。根据您的字符串,可能没有这种可区分的事件。

采用ISO-8859-1 charset vs ISO-8859-15(http://en.wikipedia.org/wiki/ISO/IEC_8859-15#Changes_from_ISO-8859-1

只有少数几个不同的角色,更糟糕的是,它们由相同的字节表示。在没有知道编码的情况下,没有办法检测到字符串被赋予字符串0xA4是否应该表示字符串中的¤或€,因此无法知道它是否是精确的字符集。

(注意:你可以添加一个人为因素,或者更高级的扫描技术(例如Oroboros102建议的),试图根据周围环境弄清楚,如果角色应该是¤或€,尽管这似乎像一座桥太远了)

例如,有更明显的区别。 UTF-8和ISO-8859-1,所以当你不确定时仍然值得尝试解决它,尽管你可以而且绝不应该依赖它是正确的。

有趣的阅读:http://kore-nordmann.de/blog/php_charset_encoding_FAQ.html#how-do-i-determine-the-charset-encoding-of-a-string

但是还有其他方法可以确保正确的字符集。关于表单,尝试尽可能强制执行UTF-8(查看雪人以确保您在每个浏览器中提交的都是UTF-8:http://intertwingly.net/blog/2010/07/29/Rails-and-Snowmen) 这样做,至少你可以肯定通过表单提交的每个文本都是utf_8。关于上传的文件,请尝试在其上运行unix'file -i'命令,例如exec()(如果可能在您的服务器上)以帮助检测(使用文档的BOM)。 关于抓取数据,您可以读取通常指定字符集的HTTP标头。解析XML文件时,请查看XML元数据是否包含字符集定义。

您应该首先尝试在可能的情况下自行确定某个字符集,或者在尝试检测之前尝试从源代码中获取定义(如果适用),而不是尝试自动猜测字符集。 / p>

答案 4 :(得分:2)

  

对我来说,主要的问题是我不知道任何字符串的来源是什么编码 - 它可以来自文本框(使用仅在用户实际提交表单时才有用),或者它可能来自上传的文本文件,所以我实际上无法控制输入。

我不认为这是一个问题。应用程序知道输入的来源。如果它来自表单,请在您的情况下使用UTF-8编码。这样可行。只需验证提供的数据是否正确编码(验证)。请记住,并非所有数据库都支持UTF-8的全部范围。

如果是文件,则不会将UTF-8编码保存到数据库中,而是以二进制形式保存。当你再次输出文件时,也使用二进制输出,然后这是完全透明的。

您的想法很好,用户可以告诉编码,无论如何,他/她可以在下载文件后告诉它,因为它是二进制文件。

所以我必须承认,我没有看到你提出的具体问题。但也许你可以添加更多细节你的问题。

答案 5 :(得分:2)

有一些非常好的答案,并试图在这里回答你的问题。我不是编码大师,但我理解你希望将 UTF-8堆栈一直到你的数据库。我一直在使用MySQL的utf8mb4编码来表,字段和连接。

我的情况归结为“当数据来自HTML表单或电子邮件注册链接时,我只想要我的清洁剂,验证器,业务逻辑和准备好的语句来处理UTF-8。”所以,以我简单的方式,我开始提出这个想法:

  1. 尝试检测编码:$encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];
  2. 如果无法检测到编码,throw new RuntimeException
  3. 如果输入为UTF-8,请继续。
  4. 否则,如果是ISO-8859-1ASCII

    一个。尝试转换为UTF-8(等待,未完成)

    湾检测转换值的编码

    ℃。如果报告的编码和转换值均为UTF-8,请继续。

    d。否则,throw new RuntimeException

  5. 来自我的抽象课Sanitizer

    Sanitizer

        private function isUTF8($encoding, $value)
        {
            return (($encoding === 'UTF-8') && (utf8_encode(utf8_decode($value)) === $value));
        }
    
        private function utf8tify(&$value)
        {
            $encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];
    
            mb_internal_encoding('UTF-8');
            mb_substitute_character(0xfffd); //REPLACEMENT CHARACTER
            mb_detect_order($encodings);
    
            $stringEncoding = mb_detect_encoding($value, $encodings, true);
    
            if (!$stringEncoding) {
                $value = null;
                throw new \RuntimeException("Unable to identify character encoding in sanitizer.");
            }
    
            if ($this->isUTF8($stringEncoding, $value)) {
                return;
            } else {
                $value = mb_convert_encoding($value, 'UTF-8', $stringEncoding);
                $stringEncoding = mb_detect_encoding($value, $encodings, true);
    
                if ($this->isUTF8($stringEncoding, $value)) {
                    return;
                } else {
                    $value = null;
                    throw new \RuntimeException("Unable to convert character encoding from ISO-8859-1, or ASCII, to UTF-8 in Sanitizer.");
                }
            }
    
            return;
        }
    

    我可以提出一个论点,我应该从我的抽象Sanitizer类中分离编码问题,只需将Encoder对象注入Sanitizer的具体子实例中}。然而,我的方法的主要问题是,没有更多的知识,我只是拒绝我不想要的编码类型(我依赖于PHP mb_ *函数)。没有进一步研究,我不知道是否会伤害一些人群(或者,如果我失去了重要信息)。所以,我需要了解更多。我找到了这篇文章。

    What every programmer absolutely, positively needs to know about encodings and character sets to work with text

    此外,将加密数据添加到我的电子邮件注册链接(使用OpenSSLmcrypt)会发生什么?这会干扰解码吗?那么Windows-1252呢?安全影响如何?在utf8_decode()中使用utf8_encode()Sanitizer::isUTF8是可疑的。

    人们已经指出了PHP mb_ *函数中的缺点。我从来没有花时间调查iconv,但如果它比mb_ *函数效果更好,请告诉我。

答案 6 :(得分:1)

您可以设置一组指标,以尝试猜测正在使用的编码。再次,不完美,但可以从mb_detect_encoding()中捕获一些未命中。

答案 7 :(得分:1)

如果您愿意“将其带到控制台”,我建议enca。与相当简单的mb_detect_encoding不同,它使用“解析,统计分析,猜测和黑魔法的混合来确定它们的编码”(笑声 - 见man page)。但是,如果要检测此类特定于国家/地区的编码,通常必须传递输入文件的语言。 (但是,mb_detect_encoding基本上具有相同的要求,因为编码必须在传递的编码列表中“出现在正确的位置”才能被检测到。)

enca也出现在这里:How to find encoding of a file in Unix via script(s)

答案 8 :(得分:0)

似乎你的问题得到了很好的回答,但我的方法可以简化你的案例:

我有一个类似的问题,试图从mysql返回字符串数据,甚至配置数据库和php以返回格式化为utf-8的字符串。我得到错误的唯一方法实际上是从数据库返回它们。

最后,通过网络浏览我发现了一种非常简单的方法来处理它:

假设您可以使用不同的格式和排序规则将所有类型的字符串数据保存在mysql中,您只需要在php连接文件中将collat​​ion设置为utf-8,如下所示:< / p>

$connection = new mysqli($server, $user, $pass, $db);
$connection->set_charset("utf8");

这意味着首先以任何格式或整理方式保存数据,并且仅在返回到php文件时进行转换。

希望它有用!

答案 9 :(得分:0)

如果从mysql数据库中检索到文本,则可以尝试在BD连接之后添加此文本。

mysqli_set_charset($ con,“ utf8”);

https://www.php.net/manual/en/mysqli.set-charset.php

答案 10 :(得分:0)

那里有两个图书馆。 onnov/detect-encoding看起来很有前途。它声称比 mb_detect_encoding

做得更好

将未知字符编码的字符串转换为UTF-8的示例用法:

use Onnov\DetectEncoding\EncodingDetector;
$detector->iconvXtoEncoding('Проверяемый текст')

要简单地检测编码:

$encoding = $detector->getEncoding('Проверяемый текст');

答案 11 :(得分:-1)

public function convertToUtf8($text) {
    if(!$this->html)
        $this->html = cURL('http://'.$this->url, array('timeout' => 15));

    $html = $this->html;
    preg_match('/<meta.*?charset=(|\")(.*?)("|\")/i', $html, $matches);

    $charset = $matches[2];

    if($charset)
        return mb_convert_encoding($text, 'UTF-8', $charset);
    else
        return $text;
}

cURL默认选项:

curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);

我试过这样的事情。它帮助了我。如果在meta charset info上找到,我正在转换,否则什么也不做。