才有用),或者它可能来自上传的文本文件,所以我真的无法控制输入。我需要的是一个函数或类,以确保进入我的数据库的内容尽可......" /> 才有用),或者它可能来自上传的文本文件,所以我真的无法控制输入。我需要的是一个函数或类,以确保进入我的数据库的内容尽可......"> 才有用),或者它可能来自上传的文本文件,所以我真的无法控制输入。我需要的是一个函数或类,以确保进入我的数据库的内容尽可......" />
ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

对于文件上传,我喜欢要求最终用户指定他们使用的编码,并向他们展示输出的预览,但这无助于对付讨厌的黑客(事实上,这可能会影响他们的生活)容易一点)。

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

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

根据定义,基本上不可能完全正确,实际上猜测未知编码的成功率并不高。可以使用启发式方法,但正确率低于 100%,具体取决于远低于 100% 的材料。你需要意识到这一点。也许这里有人至少可以建议一个具有良好启发式的库。
当然,我知道没有完美的解决方案——因此,我们渴望至少能顺利进行的事情。
这可能会有所帮助:stackoverflow.com/q/505562/642173
您是否尝试过使用 UTF-8//IGNORE 作为 iconv 中的第二个参数?
是的,这就是我最终做的。显然,这并不完美,因为“未婚妻”变成了“未婚夫”,但肯定更好。为什么 TRANSLIT 不起作用?

J
Jeff Day

你所要求的非常困难。如果可能,让用户指定编码是最好的。以这种方式预防攻击应该不会变得更容易或更难。

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

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

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


请查看您的 php 发行版中的 mb_detect_encoding 源代码(此处某处:ext/mbstring/libmbfl/mbfl/mbfl_ident.c)。此功能根本无法正常工作。对于某些编码,它甚至具有“return true”,哈哈。其他在 Ctrl+c Ctrl+v 函数中。那是因为如果没有某种字典或统计方法(如我的),您将无法检测编码。
我理解它的方式,mb_detect_encoding 遍历提供的编码列表,并接受第一个在字符串中没有无效字节序列的编码......对于没有无效字节序列的编码,例如 ISO-8859-1,它总是正确的。没有“智能”启发式方法,结果会因您传递的编码列表(和顺序)而有很大差异。
这似乎对我有用。我的用户使用 tinymce 在 utf8 页面上提交文本,但由于某些未知原因,非 utf8 字符有时会出现在数据库中。这解决了它,所以非常感谢你。
@Jeff Day - 谢谢你。请原谅我的无知,“设置为严格”是什么意思?
[Jeff Day] 发送 mb_detect_order() 即使它是此参数的默认值,因为他想将严格编码检测设置为 true(第三个参数):)
P
Peter Mortensen

在祖国俄罗斯,我们有四种流行的编码,所以你的问题在这里很受欢迎。

仅通过符号的字符代码您无法检测到编码,因为代码页相交。一些不同语言的代码页甚至有完全的交集。所以,我们需要另一种方法。

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

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

在您想要支持的每种编码中构建字符代码的概率范围。您可以使用您的语言中的一些大文本来构建它(例如,一些小说,使用 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 源代码。


P
Peter Mortensen

只需使用 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);

两者的结果是相同的。


在数据库中,似乎 - 我刚刚尝试过您的代码,我同意。
检查以确保您在表/列上定义的排序规则也是 UTF-8。
@AlexeyGerasimov 我想我真的需要调查 iconv。我尝试了一种几乎纯粹的 mb_* 方式。你怎么看?
P
Peter Mortensen

没有办法完全准确地识别字符串的字符集。

有一些方法可以尝试猜测字符集。 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 元数据是否包含字符集定义。

与其尝试自动猜测字符集,不如先尝试在可能的情况下自己确保某个字符集,或者尝试从获取它的源(如果适用)中获取定义,然后再进行检测。


带有加密数据的表格和电子邮件注册链接。这就是我试图让我的输入为 UTF-8 或什么都没有的地方。你觉得我的回答怎么样?有用的意见表示赞赏。谢谢。
P
Peter Mortensen

这里有一些非常好的答案和尝试回答你的问题。我不是编码大师,但我理解您希望有一个 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_* 函数)。如果没有进一步的研究,我不知道这是否会伤害某些人群(或者,如果我丢失了重要信息)。所以,我需要了解更多。我找到了这篇文章。

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

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

人们已经指出了 PHP mb_* 函数的缺点。我从未花时间研究 iconv,但如果它比 mb_*functions 更有效,请告诉我。


我发现了这个,stackoverflow.com/a/3521396/1429677 这个问题的绝佳答案,这里是库 github.com/neitanod/forceutf8
P
Peter Mortensen

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

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

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

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

所以我必须承认,我没有看到你提出的具体问题。


你会看到我的回答并提出问题吗?建设性的意见表示赞赏。谢谢。
P
Peter Mortensen

看来您的问题已经得到了很好的回答,但我有一种方法可以简化您的情况:

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

最后,通过网络航行,我找到了一种非常简单的方法来处理它:

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

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

这意味着首先您以任何格式或排序规则保存数据,并且仅在返回到您的 PHP 文件时将其转换。


C
Community

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

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


作为matthiasmullie said,统计分析可能没有太大帮助。
r
rosell.dk

那里有几个图书馆。 onnov/detect-encoding 看起来很有希望。它声称比 mb_detect_encoding 做得更好

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

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

简单地检测编码:

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

P
Peter Mortensen

您可以设置一组指标来尝试猜测正在使用哪种编码。同样,它并不完美,但它可以捕获 mb_detect_encoding() 中的一些缺失。


是的,说到 mb_detect_encoding() 次失误,你认为我的回答在撒哈拉沙漠的夏天有滚雪球的机会吗?
M
MAChitgarha

因为 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);
}

简单、安全、快速。


哈哈哇,我11年前问过这个问题(老实说我不记得为什么)!谢谢你的回答,读起来还是很有趣的。我有一个问题,但只是因为我感兴趣 - 为什么是 !== false 而不是 === true
@Grim ...,因为在这种情况下 \mb_detect_encoding() 的返回类型是 string|falsestringfalse)。它可能永远不会等于 true。也许您目前使用强类型语言编写代码。 ;)
哈哈哈 很久没用PHP了!不得不承认我可能只是使用 if (!$utf8) 因为我很懒 :-)
P
Peter Mortensen

如果文本是从 MySQL 数据库中检索的,您可以尝试在数据库连接后添加它。

mysqli_set_charset($con, "utf8");

mysqli::set_charset


什么是“BD连接”?你的意思是“数据库连接”吗?
是的@PeterMortensen 更改了我原来的帖子。