我正在尝试使用pdo_sqlsrv将ASCII NUL字符(\0
aka U+0000
)插入到PHP的SQL Server数据库中。这是处理PHP序列化字符串的要求,它包含NUL字符来表示私有/受保护变量。
但是,有一些关于PDO :: quote()的内容正在破坏字符串。
要重现的代码(用适当的值替换DBNAME
,USERNAME
和PASSWORD
):
<?php
try {
$dsn = 'sqlsrv:Server=.\SQLEXPRESS;Database=DBNAME';
$user = 'USERNAME';
$pass = 'PASSWORD';
$connection = new PDO($dsn, $user, $pass);
} catch (PDOException $e) {
die("Connection error: " . $e->getMessage());
}
$str = "XX\0XX";
header("Content-Type: text/plain");
print("Original: " . str_replace("\0", "{NUL}", $str) . "\n");
$str = $connection->quote($str);
print("Quoted: " . str_replace("\0", "{NUL}", $str) . "\n");
?>
预期输出:
Original: XX{NUL}XX
Quoted: 'XX{NUL}XX'
实际输出:
Original: XX{NUL}XX
Quoted: 'XX'{NUL}{NUL}a
任何人都可以解释这种奇怪的行为 - 更重要的是 - 解释如何解决它?
更新
似乎最终字符是随机的,因为在后续运行中它是e
。这意味着某种形式的存储器访问错误,例如,读过字符串的结尾。也许是pdo_sqlsrv实现中的一个错误?
答案 0 :(得分:2)
任何人都可以解释这种奇怪的行为......
这似乎是pdo_sqlsrv扩展中的一个错误(可能是非PDO sqlsrv扩展中的等效函数)。
我在扩展程序的问题跟踪器(ticket #538)中记录了该问题,开发人员已经确认他们可以复制问题并且这是一个需要修复的错误。
... - 更重要的是 - 解释如何解决它?
我们实现的解决方案是检测这种情况并重写字符串以删除任何NUL字符:
try {
$dsn = 'sqlsrv:Server=.\SQLEXPRESS;Database=DBNAME';
$user = 'USERNAME';
$pass = 'PASSWORD';
$connection = new PDO($dsn, $user, $pass);
} catch (PDOException $e) {
die("Connection error: " . $e->getMessage());
}
$str = "XX\0XX";
header("Content-Type: text/plain");
print("Original: " . str_replace("\0", "{NUL}", $str) . "\n");
$str = safeQuote($str, $connection);
print("Quoted: " . str_replace("\0", "{NUL}", $str) . "\n");
function safeQuote($str, $connection) {
// Special handling of ASCII NUL characters, which cause breakages.
// This appears to be due to a bug in the pdo_sqlsrv driver.
// For performance reasons, we only do this for strings that contain \0.
if (is_string($str) && strpos($str, "\0") !== false) {
$arrParts = explode("\0", $str);
foreach ($arrParts as $Key => $Part) {
$arrParts[$Key] = $connection->quote($Part);
}
$str = implode(" + CHAR(0) + ", $arrParts);
}
// Otherwise, prepare the quoted string in the standard way.
else {
$str = $connection->quote($str);
}
return $str;
}
?>
这将输出以下内容,据我所知,它会在SQL Server中生成正确的插入内容:
Original: XX{NUL}XX
Quoted: 'XX' + CHAR(0) + 'XX'
该错误已在SQLSRV/5.2.0修复。
答案 1 :(得分:0)
由于\
可以在LIKE
语句中使用,因此您需要在引用之前添加斜杠,从而转义原始斜杠。
而不是使用PDO::quote
使用PDO::quote
文档中提到的预准备语句。
如果您使用此函数构建SQL语句,强烈建议您使用PDO :: prepare()来准备带有绑定参数的SQL语句,而不是使用PDO :: quote()将用户输入插入到SQL语句中。带有绑定参数的预处理语句不仅更易于移植,更方便,不受SQL注入的影响,但执行速度通常比内插查询快得多,因为服务器端和客户端都可以缓存查询的编译形式。
直播示例
<强>输出强>
<?php
$pdo = new \PDO('sqlite::memory:', null, null);
$str = "XX\0XX";
print($pdo->quote(addSlashes($str))); <----> 'XX\0XX'
?>
答案 2 :(得分:0)
在PHP / 5.6.21下的32位SQLSRV / 3.2设置中运行稍微修改过的测试代码版本,表现出一种奇怪的行为:
$str = "XX\0XX";
for($i=0; $i<5; $i++){
$connection = new PDO($dsn, $user, $pass);
printf("0x%s -> 0x%s\n", bin2hex($str), bin2hex($connection->quote($str)));
printf("0x%s -> 0x%s\n", bin2hex($str), bin2hex($connection->quote($str)));
}
0x5858005858 -> 0x27585827000005
0x5858005858 -> 0x27585827000005
0x5858005858 -> 0x2758582700b72b
0x5858005858 -> 0x2758582700b72b
0x5858005858 -> 0x27585827000306
0x5858005858 -> 0x27585827000306
0x5858005858 -> 0x2758582700b72b
0x5858005858 -> 0x2758582700b72b
0x5858005858 -> 0x27585827000005
0x5858005858 -> 0x27585827000005
每次运行时实际字节都会发生变化。
我的发现:
CharacterSet
连接选项(此处显示)无影响所以这一切都指向了库中的一个错误。我已经尝试了最新的稳定版本(32位PHP / 7.1.9下的v4.3.0)并且它仍然存在(但至少在我的系统上,结果仍然是任意的但不是随机的)
无论是功能错误,我都说PDO::quote()
在SQLSRV下不是二进制安全的。如果底层框架不允许准备好的语句,您可能需要考虑converting to hex并发送为(未引用的)十六进制文字,例如:
INSERT INTO foo (bar) VALUES (0x5858005858);