MySQL无法处理花式(智能)引号

9
我正在从表单向数据库插入一些数据。我使用addslashes来转义文本(也尝试过mysql_real_escape_string,结果相同)。
普通引号被转义了,但其他一些引号没有被转义。例如,字符串:

Homer's blood becomes the secret ingredient in Moe’s new beer.

转换为:

Homer\'s blood becomes the secret ingredient in Moe’s new beer.

我认为花括号未转义不会有影响,但只有以下文本被插入到数据库中:

Homer's blood becomes the secret ingredient in Moe

所以PHP认为花括号没问题,但MySQL却丢失了字符串。但MySQL没有给出任何错误信息。

好问题,我想知道它是否会发生在预处理语句中。 - Allain Lalonde
@Allain:我本来不会这样想,但如果有人想测试,请随便。我应该明确表示我知道预处理语句,这是一些旧代码,最近才在PHP 4上运行。 - DisgruntledGoat
2个回答

8
我会寻找您的Web界面和数据库级别中使用的字符编码不匹配的情况。例如,如果您的Web界面使用UTF-8,而您的数据库使用默认的MySQL编码latin1,那么您需要使用DEFAULT CHARSET=utf8来设置表。
顺便提一下,请使用mysql_real_escape_string()或mysqli。addslashes()不能很好地保护您免受SQL注入攻击。

1
+1 addslashes 永远不应该被用于任何事情。这确实会是一个字符集问题;我的猜测是引号字符实际上正在消失,这是因为 ISO-8859-1 字节被插入到 UTF-8 数据库中。你真正想要的是让所有东西都在 UTF-8 中;从使用该编码服务您的页面开始,这将确保提交的表单也以 UTF-8 形式出现。 - bobince
是的,这是因为网页不是UTF8,但MySQL是。顺便问一下:是否有与mysql_real_escape_string相反的函数?我在手册中找不到任何信息。 - DisgruntledGoat
不需要。可能是因为很难想象你为什么需要一个。如果你从MySQL中取回数据,希望显而易见的是,你不需要反转转义。如果出于某种原因,你需要在将数据推入MySQL之前获取原始数据,只需在创建转义版本时不要丢弃原始数据即可。 - chaos
这是因为我有一个函数,它会递归地添加/删除斜杠(请参见https://dev59.com/BEjSa4cB1Zd3GeqPHrRA)。想法是在提交后将所有表单数据带到一致的状态,例如删除魔术引号,必要时处理数据,然后为MySQL查询添加它们。不过我认为很快就会转向参数化查询,这样可以省去很多麻烦!! - DisgruntledGoat
2
啊,好的。在我看来,魔术引号唯一值得做的事情就是将它们删除,所以你只需要使用 stripslashes() 函数即可。 :) - chaos

7

在您提供的示例字符串中,Moe's 中的撇号是唯一一个在 latin1 编码下无效,但您的 mysql 服务器却期望 utf8 编码的字符。这里有一个简单的演示:

<?php
function foo($s) {
    echo 'len=', strlen($s), ' ';
  for($i=0; $i<strlen($s); $i++) {
    printf('%02X ', ord($s[$i]));
  }
  echo "\n";
}

 // my file is latin1 encoded and so is the string literal
foo('Moe’s');
// now try it with an utf8 encoded string
foo( utf8_encode('Moe’s') );

打印

len=5 4D 6F 65 92 73
len=6 4D 6F 65 C2 92 73

因此问题是:您是否以“错误”的编码方式向mysql服务器提供了某些内容?
每个连接都有一个连接字符集,mysql服务器希望您的客户端(php脚本)发送使用该字符集编码的数据。 您可以通过以下方式找出连接字符集:

SHOW VARIABLES LIKE '%character%'

就像在

$mysql = mysql_connect('..', '..', '..') or die(mysql_error());
mysql_select_db('..', $mysql) or die(mysql_error());

$query = "SHOW VARIABLES like '%character%'";
$result = mysql_query($query, $mysql) or die(__LINE__.mysql_error());
while( false!==($row=mysql_fetch_array($result, MYSQL_ASSOC)) ) {
  echo join(', ', $row), "\n";
}

这应该会打印出类似以下的内容:
character_set_client, utf8
character_set_connection, utf8
character_set_database, latin1
character_set_filesystem, binary
character_set_results, utf8
character_set_server, utf8
character_set_system, utf8

character_set_connection, utf8表示“我的”连接字符集是utf8,即mysql服务器期望从客户端(php)接收utf8编码的字符。 "你"的连接字符集是什么?

然后请查看您参数字符串的实际编码方式,例如:

$foo = mysql_real_escape_string($_POST['foo'], $mysql);

用以下内容替换它:

echo '<div>Debug hex($_POST[foo])=';
for($i=0; $i<strlen($s); $i++) {
    printf('%02X ', ord($_POST['foo'][$i]));
}
echo "</div>\n";
$foo = mysql_real_escape_string($_POST['foo'], $mysql);

并检查您的输入字符串的实际编码是什么。它打印出92还是C2 92?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接