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


146

我有一个与世界各地的客户打交道的应用程序,自然,我希望进入数据库的所有内容都采用UTF-8编码。

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

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

对于文件上传,我喜欢让最终用户指定他们使用的编码,并向他们显示输出结果的预览的想法,但这无助于讨厌的黑客(实际上,这可能会使他们的生活变糟。容易一点)。

我已经阅读了有关该主题的其他SO问题,但是它们似乎都存在细微的差异,例如“我需要解析RSS feed”或“我从网站上抓取数据”(或者实际上是“您不能”)。

但是必须至少尝试一下


5
从定义上讲,基本上不可能完全正确,实际上猜测未知编码的成功率并不高。可以使用试探法,但是少于100%的时间是正确的,具体取决于材料远少于 100%。您需要意识到这一点。不过,也许有人可以至少建议一个具有启发性的图书馆。
deceze

当然,我知道还没有完美的解决方案-因此,人们对至少可以顺利发展的事物的渴望。
Grim ...

这可能会有所帮助:stackoverflow.com/q/505562/642173
Melsi 2011年

您是否尝试过使用UTF-8//IGNOREin中的第二个参数iconv
触发

是的,那就是我最终要做的。显然,随着“未婚夫”变成“未婚夫”,这并不完美,但肯定会更好。TRANSLIT为什么不起作用?
Grim ...

Answers:


255

您要的是非常困难的。如果可能的话,最好让用户指定编码。用这种方法来阻止攻击应该不会变得容易或困难得多。

但是,您可以尝试执行以下操作:

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

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


5
mb_detect_encoding在您的php发行版中查看源代码(此处为ext / mbstring / libmbfl / mbfl / mbfl_ident.c)。此功能根本无法正常工作。对于某些编码,它甚至具有“ return true”,大声笑。其他功能在Ctrl + c Ctrl + v功能中。那是因为如果没有某种字典或统计方法(如我的),就无法检测编码。
Oroboros102,

1
我的理解方式是,mb_detect_encoding遍历提供的编码列表,并接受字符串中没有无效字节序列的第一个编码...对于没有无效字节序列的编码(例如ISO-8859-1),它始终为true 。没有“智能”启发式方法,结果会随您传递的编码列表(和顺序)而有很大差异。
wutz 2011年

这似乎为我工作。我的用户正在使用tinymce在utf8页面上提交文本,但是由于某些未知原因,非utf8字符有时会出现在数据库中。此问题已解决,非常感谢。
giorgio79

@Jeff Day-谢谢你。请原谅我的无知,您的意思是“将其设置为严格”?
Ash501

[Jeff Day] mb_detect_order()即使它是此参数的默认值也正在发送,因为他想将严格的编码检测设置为true(第三个参数):)
jave.web

28

在祖国俄罗斯,我们有4种流行的编码,因此在这里您的问题需求量很大。

由于代码页相交,因此仅凭符号的字符代码就无法检测到编码。某些使用不同语言的代码页甚至具有完整的交集。因此,我们需要另一种方法

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

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

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

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

下一个。您采用未知编码的文本,对于“概率字典”中的每种编码,您都搜索未知编码文本中每个符号的频率。符号的总和概率。评分更高的编码可能是赢家。大文本效果更好。

如果您有兴趣,我们很乐意为您完成此任务。通过建立两个字符的概率列表,我们可以大大提高准确性。

顺便说一句。mb_detect_encoding确实无法正常工作。是的,完全没有。请在“ ext / mbstring / libmbfl / mbfl / mbfl_ident.c”中查看mb_detect_encoding源代码。


11

您可能已经尝试过这样做,但是为什么不只使用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'?是在数据库中还是在浏览器中?


在数据库中,似乎-我刚刚尝试过您的代码,并且我同意。
严峻...

1
检查并确保您在表/列上定义的归类也是UTF-8。
Alexey Gerasimov,

@AlexeyGerasimov我想我真的需要调查iconv。我尝试做一种几乎纯的mb_ *方法。你觉得怎么样
安东尼·鲁特里奇

5

无法识别完全准确的字符串的字符集。有多种方法可以尝试猜测字符集。mb_detect_encoding()是其中一种(可能是/目前是PHP中最好的)方式。这将扫描您的字符串并查找某些字符集所特有的东西。根据您的字符串,可能不会出现此类可区分的事件。

以ISO-8859-1字符集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。关于上传的文件,请尝试通过exec()在服务器上运行unix'file -i'命令(如果可能,在服务器上)以帮助检测(使用文档的BOM)。关于抓取数据,您可以读取HTTP标头,通常指定字符集。解析XML文件时,请查看XML元数据是否包含字符集定义。

与其尝试自动猜测字符集,不如先尝试自己确保一个特定的字符集,或者在尝试进行检测之前,先尝试从获取源的定义中获取定义(如果适用)。


表单和电子邮件注册链接带有加密数据。那就是我要使输入为UTF-8或什么都不输入的地方。您如何看待我的回答?有用的评论表示赞赏。谢谢。
安东尼·鲁特里奇

3

这里有一些非常好的答案,并尝试回答您的问题。我不是编码大师,但是我了解您希望拥有一个 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(等待,未完成)

    b。检测转换值的编码

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

    d。其他,throw new RuntimeException

从我的抽象课 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_ *函数)。没有进一步的研究,我不知道这是否会伤害某些人群(或者,如果我在重要信息上迷失了方向)。所以,我需要了解更多。我找到了这篇文章。

每个程序员绝对肯定要了解与文本一起使用的编码和字符集的知识

此外,将加密数据添加到我的电子邮件注册链接(使用OpenSSLmcrypt)时会发生什么?这会干扰解码吗?Windows-1252呢?那么安全隐患呢?utf8_decode()utf8_encode()in 的使用Sanitizer::isUTF8令人怀疑。

人们指出了PHP mb_ *函数的缺点。我从未花时间进行调查iconv,但是如果它比mb_ * functions更好,请告诉我。



2

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

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

如果是文件,则不会将它以UTF-8编码格式保存为数据库,而是以二进制格式保存。当再次输出文件时,也使用二进制输出,那么这是完全透明的。

您的想法很好,用户可以说出编码,因为他/她在下载文件后仍然可以说出二进制格式的文件。

因此,我必须承认,我没有看到您提出的特定问题。但是也许您可以添加更多详细信息,这是您的问题所在。


您会看到我的答案并提出问题吗?建设性的评论表示赞赏。谢谢。
安东尼·鲁特里奇

1

您可以设置一组指标来尝试猜测正在使用哪种编码。同样,这不是完美的方法,但是可以捕获mb_detect_encoding()中的一些遗漏。


是的,说到mb_detect_encoding()未成年人,您认为我的回答在夏天在撒哈拉沙漠中有滚雪球的机会吗?
安东尼·鲁特里奇

1

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

enca也出现在这里:如何通过脚本在Unix中查找文件的编码


1

看来您的问题已得到很好的回答,但是我有一种方法可以简化您的案例:

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

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

考虑到您可以将所有这些类型的字符串数据以不同的格式和排序规则保存在mysql中,您只需要在php连接文件中将排序规则设置为utf-8,如下所示:

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

Wich表示,首先您以任何格式或排序规则保存数据,并且仅在返回到php文件时才将其转换。

希望对您有所帮助!



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

我尝试过这样的事情。它帮助了我。如果在元字符集信息上找到,则表示我正在转换,否则什么也不做。


errr,能否请您检查一下功能并更正变量?
马丁

什么是$ url?什么是$ html?
马丁
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.