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

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

对我来说主要的问题是我不知道什么编码的string的来源将是 – 它可能是从一个文本框(使用<form accept-charset="utf-8">只有在用户实际上是提交表单),或者它可能来自上传的文本文件,所以我实在无法控制input。

我需要的是一个函数或类,确保进入我的数据库的东西,尽可能地,UTF-8编码。 我试过iconv(mb_detect_encoding($text), "UTF-8", $text); 但有问题(如果input是“未婚妻”,则返回“未婚夫”)。 我已经尝试了很多东西= /

对于file upload,我喜欢让最终用户指定他们使用的编码,并向他们展示输出结果的预览,但是这并不能帮助防止恶意的黑客(事实上,这可能会使他们的生活更容易一点)。

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

但是一定有一些东西至less有一个很好的尝试

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

不过,你可以尝试这样做:

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

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

在祖国俄罗斯我们有4种stream行的编码,所以你的问题在这里是非常需要的。

只有通过字符代码符号,你不能检测编码,因为代码页相交。 一些不同语言的代码页甚至是完全交叉的。 所以, 我们需要另一种方法

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

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

在您想要支持的每种编码中构buildchar代码的概率范围。 你可以使用你的语言的一些大文本(例如一些小说,莎士比亚的英文和托尔斯泰俄文,哈哈)。 你会得到像这样的smth:

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

下一个。 你采取未知编码的文本,并在“概率词典”中的每个编码中search未知编码文本中每个符号的频率。 符号的总和概率。 编码与更大的评级可能是赢家。 更好的结果为更大的文本。

如果你有兴趣 ,我可以很乐意帮你完成这个任务。 我们可以通过构build两个字符的概率列表来大大提高准确性。

顺便说一句。 mb_detect_encoding certanly不起作用。 是的。 请看看“ext / mbstring / libmbfl / mbfl / mbfl_ident.c”中的mb_detect_encoding源代码。

你可能试过这个,但为什么不使用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'? 是在数据库还是在浏览器中?

没有办法确定完全准确的string的字符集。 有尝试猜测字符集的方法。 其中一种方式,也许是/目前最好的PHP,是mb_detect_encoding()。 这将扫描你的string,并查找某些特定字符集的东西的出现。 根据您的string,可能不会有这种可区分的事件。

以ISO-8859-1字符集与ISO-8859-15( http://en.wikipedia.org/wiki/ISO/IEC_8859-15#Changes_from_ISO-8859-1

只有less数不同的字符,而且更糟的是,它们由相同的字节表示。 没有办法检测,给出一个string,而不知道它的编码,字节0xA4应该表示string中的¤或€,所以没有办法知道它是准确的字符集。

(注意:你可以添加一个人的因素,或者一个更高级的扫描技术(比如Oroboros102的build议),试图根据周围的环境来判断,如果angular色应该是¤或€,尽pipe这看起来像一座桥太远)

例如UTF-8和ISO-8859-1之间有更多不同的区别,所以当你不确定时,仍然值得尝试弄清楚,尽pipe你可以也不应该依赖它是正确的。

有趣的阅​​读: 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 )这样做,至less你可以确保通过你的表单提交的每一个文本是utf_8。 关于上传的文件,尝试通过例如exec()(如果可能在您的服务器上)运行Unix上的文件-i命令来帮助检测(使用文档的BOM)。关于抓取数据,您可以读取HTTP头文件,通常指定字符集。 parsingXML文件时,请查看XML元数据是否包含字符集定义。

与其试图自动猜测字符集,你应该首先尝试在可能的情况下自己确保某个字符集,或者试图在采用检测之前从源代码中获取定义(如果适用)。

对我来说,主要的问题是我不知道什么编码的string来源是什么 – 它可能来自一个文本框(使用仅在用户实际提交表单时才有用),或者可以是从上传的文本文件,所以我真的没有控制input。

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

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

你的想法是好的,用户可以告诉编码,他/她可以告诉下载文件后,因为它是二进制的。

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

您可以设置一组度量来尝试猜测正在使用哪种编码。 再次,不完美,但可以从mb_detect_encoding()中捕获一些错过。

如果你愿意“把它带到控制台”,我会推荐enca 。 与简单的mb_detect_encoding不同的是,它使用了“parsing,统计分析,猜测和黑魔法的混合来确定它们的编码”(大声笑 – 参见手册页 )。 但是,如果要检测这些特定于国家/地区的编码,通常必须传递input文件的语言。 (但是, mb_detect_encoding基本上具有相同的要求,因为编码必须出现在“通过的编码列表的正确位置”,以便可检测到)。

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

严峻,有一些非常好的答案,并尝试在这里回答你的问题。 我想感谢大家的回应。 他们都是伟大的。 我不是一个编码大师,但我理解你希望有一个纯粹的 UTF-8堆栈一直到你的数据库。 我一直在使用MySQL的utf8mb4编码表,字段和连接。

我的情况归结为“当数据来自HTML表单或电子邮件注册链接时,我只想要我的消毒剂,validation器,业务逻辑和预处理UTF-8的语句。 所以,以我的简单方式,我开始了这个想法:

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

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

    湾 检测转换后的值的编码

    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的具体子实例中。 然而,我的方法的主要问题是,没有更多的知识,我只是拒绝我不想要的编码types(我依靠PHP mb_ *函数)。 没有进一步的研究,我不知道这是否会伤害一些人口(或者,如果我失去了重要的信息)。 所以,我需要了解更多。 我发现这篇文章。

每个程序员绝对需要了解编码和字符集与文本一起工作

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

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

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

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