才有用),或者它可能来自上传的文本文件,所以我真的无法控制输入。我需要的是一个函数或类,以确保进入我的数据库的内容尽可......" /> 才有用),或者它可能来自上传的文本文件,所以我真的无法控制输入。我需要的是一个函数或类,以确保进入我的数据库的内容尽可......"> 才有用),或者它可能来自上传的文本文件,所以我真的无法控制输入。我需要的是一个函数或类,以确保进入我的数据库的内容尽可......" />
我有一个处理来自世界各地的客户的应用程序,当然,我希望进入我的数据库的所有内容都是 UTF-8 编码的。
对我来说主要问题是我不知道任何字符串的源将是什么编码 - 它可能来自文本框(只有在用户实际提交表单时使用 <form accept-charset="utf-8">
才有用),或者它可能来自上传的文本文件,所以我真的无法控制输入。
我需要的是一个函数或类,以确保进入我的数据库的内容尽可能采用 UTF-8 编码。我试过 iconv(mb_detect_encoding($text), "UTF-8", $text);
但这有问题(如果输入是 'fiancée' 它返回 'fianc')。我已经尝试了很多东西 =/
对于文件上传,我喜欢要求最终用户指定他们使用的编码,并向他们展示输出的预览,但这无助于对付讨厌的黑客(事实上,这可能会影响他们的生活)容易一点)。
我已经阅读了有关该主题的其他 Stack Overflow 问题,但它们似乎都有细微的差异,例如“我需要解析 RSS 提要”或“我从网站上抓取数据”(或者,实际上,“你不能”)。
但必须有一些东西至少有一个很好的尝试!
UTF-8//IGNORE
作为 iconv
中的第二个参数?
你所要求的非常困难。如果可能,让用户指定编码是最好的。以这种方式预防攻击应该不会变得更容易或更难。
但是,您可以尝试这样做:
iconv(mb_detect_encoding($text, mb_detect_order(), true), "UTF-8", $text);
将其设置为严格可能会帮助您获得更好的结果。
在祖国俄罗斯,我们有四种流行的编码,所以你的问题在这里很受欢迎。
仅通过符号的字符代码您无法检测到编码,因为代码页相交。一些不同语言的代码页甚至有完全的交集。所以,我们需要另一种方法。
使用未知编码的唯一方法是使用概率。所以,我们不想回答“这个文本的编码是什么?”这个问题,我们试图理解“这个文本最有可能的编码是什么?”。
一个受欢迎的俄罗斯科技博客中的一个人发明了这种方法:
在您想要支持的每种编码中构建字符代码的概率范围。您可以使用您的语言中的一些大文本来构建它(例如,一些小说,使用 Shakespeare 表示英语,使用 Tolstoy 表示俄语,LOL)。你会得到这样的东西:
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 源代码。
只需使用 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);
两者的结果是相同的。
iconv
。我尝试了一种几乎纯粹的 mb_* 方式。你怎么看?
没有办法完全准确地识别字符串的字符集。
有一些方法可以尝试猜测字符集。 mb_detect_encoding 是其中一种方法,并且可能/目前是 PHP 中最好的方法。这将扫描您的字符串并查找某些字符集独有的内容。根据您的字符串,可能不会出现这种可区分的情况。
以 the ISO-8859-1 character set vs ISO-8859-15。
只有少数几个不同的字符,更糟糕的是,它们由相同的字节表示。没有办法检测,在不知道其编码的情况下给定一个字符串,字节 0xA4 是否应该在您的字符串中表示 ¤ 或 €,因此无法知道其确切的字符集。
(注意:您可以添加人为因素,或者更高级的扫描技术(例如,什么 Oroboros102 suggests),以尝试根据周围的上下文找出字符应该是 ¤ 还是 €,尽管这看起来像一座桥太远了。)
例如 UTF-8 和 ISO-8859-1 之间有更多可区分的区别,因此当您不确定时,仍然值得尝试弄清楚,尽管您可以而且永远不应该依赖它是正确的。
有趣的阅读:How do I determine the charset/encoding of a string?
不过,还有其他方法可以确保正确的字符集。关于表单,尽量强制使用 UTF-8(查看 snowman 以确保您的提交在每个浏览器中都是 UTF-8:Rails and Snowmen)
这样做后,至少您可以确定通过表单提交的每个文本都是 utf_8。关于上传的文件,请尝试通过例如 exec()(如果可能在您的服务器上)在其上运行 Unix 'file -i' 命令以帮助检测(使用文档的 BOM)。
关于抓取数据,您可以读取通常指定字符集的 HTTP 标头。解析 XML 文件时,查看 XML 元数据是否包含字符集定义。
与其尝试自动猜测字符集,不如先尝试在可能的情况下自己确保某个字符集,或者尝试从获取它的源(如果适用)中获取定义,然后再进行检测。
这里有一些非常好的答案和尝试回答你的问题。我不是编码大师,但我理解您希望有一个 pure UTF-8 堆栈一直到您的数据库。我一直在对表、字段和连接使用 MySQL 的 utf8mb4
编码。
我的情况归结为“当数据来自 HTML 表单或电子邮件注册链接时,我只希望我的清理程序、验证程序、业务逻辑和准备好的语句处理 UTF-8。”所以,以我的简单方式,我从这个想法开始:
尝试检测编码:$encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];如果无法检测到编码,则抛出新的 RuntimeException 如果输入为 UTF-8,则继续。否则,如果它是 ISO-8859-1 或 ASCII a。尝试转换为 UTF-8(等待,未完成) b.检测转换值的编码 c.如果上报的编码和转换后的值都是UTF-8,则继续。 d。否则,抛出新的 RuntimeException
从我的抽象类 Sanitizer
https://i.stack.imgur.com/GY5Fh.png
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_* 函数)。如果没有进一步的研究,我不知道这是否会伤害某些人群(或者,如果我丢失了重要信息)。所以,我需要了解更多。我找到了这篇文章。
此外,将加密数据添加到我的电子邮件注册链接(使用 OpenSSL
或 mcrypt
)时会发生什么?这会干扰解码吗? Windows-1252 呢?安全隐患如何?在 Sanitizer::isUTF8
中使用 utf8_decode()
和 utf8_encode()
是可疑的。
人们已经指出了 PHP mb_* 函数的缺点。我从未花时间研究 iconv
,但如果它比 mb_*functions 更有效,请告诉我。
对我来说主要问题是我不知道任何字符串的源将是什么编码 - 它可能来自文本框(只有在用户实际提交表单时才有用),或者它可能是来自上传的文本文件,所以我真的无法控制输入。
我不认为这是一个问题。应用程序知道输入的来源。如果它来自表单,请在您的情况下使用 UTF-8 编码。这样可行。只需验证提供的数据是否正确编码(验证)。请记住,并非所有数据库都支持全范围的 UTF-8。
如果它是一个文件,您不会将其以 UTF-8 编码保存到数据库中,而是以二进制形式保存。当您再次输出文件时,也使用二进制输出,那么这是完全透明的。
您的想法很好,用户可以告诉编码,因为他/她在下载文件后无论如何都可以告诉,因为它是二进制的。
所以我必须承认,我没有看到你提出的具体问题。
看来您的问题已经得到了很好的回答,但我有一种方法可以简化您的情况:
我在尝试从 MySQL 返回字符串数据时遇到了类似的问题,甚至将数据库和 PHP 都配置为返回格式化为 UTF-8 的字符串。我得到错误的唯一方法实际上是从数据库中返回它们。
最后,通过网络航行,我找到了一种非常简单的方法来处理它:
假设您可以在 MySQL 中以不同的格式和排序规则保存所有这些类型的字符串数据,您只需在 php 连接文件中将排序规则设置为 UTF-8,如下所示:
$connection = new mysqli($server, $user, $pass, $db);
$connection->set_charset("utf8");
这意味着首先您以任何格式或排序规则保存数据,并且仅在返回到您的 PHP 文件时将其转换。
如果您愿意“将其带到控制台”,我建议您使用 enca
。与相当简单的 mb_detect_encoding
不同,它使用“解析、统计分析、猜测和黑魔法的混合来确定它们的编码”(大声笑 - 参见 man page)。但是,如果要检测此类特定于国家/地区的编码,通常必须传递输入文件的语言。 (但是,mb_detect_encoding
本质上具有相同的要求,因为编码必须出现在传递的编码列表中的“正确位置”才能被检测到。)
enca
也出现在这里:How to find encoding of a file in Unix via script(s)
那里有几个图书馆。 onnov/detect-encoding 看起来很有希望。它声称比 mb_detect_encoding 做得更好
将未知字符编码的字符串转换为 UTF-8 的示例用法:
use Onnov\DetectEncoding\EncodingDetector;
$detector->iconvXtoEncoding('Проверяемый текст')
简单地检测编码:
$encoding = $detector->getEncoding('Проверяемый текст');
您可以设置一组指标来尝试猜测正在使用哪种编码。同样,它并不完美,但它可以捕获 mb_detect_encoding() 中的一些缺失。
mb_detect_encoding()
次失误,你认为我的回答在撒哈拉沙漠的夏天有滚雪球的机会吗?
因为 UTF-8 的使用很广泛,你可以假设它是默认的,如果不是,尝试猜测和转换编码。这是代码:
function make_utf8(string $string)
{
// Test it and see if it is UTF-8 or not
$utf8 = \mb_detect_encoding($string, ["UTF-8"], true);
if ($utf8 !== false) {
return $string;
}
// From now on, it is a safe assumption that $string is NOT UTF-8-encoded
// The detection strictness (i.e. third parameter) is up to you
// You may set it to false to return the closest matching encoding
$encoding = \mb_detect_encoding($string, mb_detect_order(), true);
if ($encoding === false) {
throw new \RuntimeException("String encoding cannot be detected");
}
return \mb_convert_encoding($string, "UTF-8", $encoding);
}
简单、安全、快速。
!== false
而不是 === true
?
\mb_detect_encoding()
的返回类型是 string|false
(string
或 false
)。它可能永远不会等于 true
。也许您目前使用强类型语言编写代码。 ;)
if (!$utf8)
因为我很懒 :-)
如果文本是从 MySQL 数据库中检索的,您可以尝试在数据库连接后添加它。
mysqli_set_charset($con, "utf8");
mb_detect_encoding
源代码(此处某处:ext/mbstring/libmbfl/mbfl/mbfl_ident.c)。此功能根本无法正常工作。对于某些编码,它甚至具有“return true”,哈哈。其他在 Ctrl+c Ctrl+v 函数中。那是因为如果没有某种字典或统计方法(如我的),您将无法检测编码。mb_detect_encoding
遍历提供的编码列表,并接受第一个在字符串中没有无效字节序列的编码......对于没有无效字节序列的编码,例如 ISO-8859-1,它总是正确的。没有“智能”启发式方法,结果会因您传递的编码列表(和顺序)而有很大差异。mb_detect_order()
即使它是此参数的默认值,因为他想将严格编码检测设置为 true(第三个参数):)