Php 回溯MySQL实体
我有以下方法可以保护MySQL实体:Php 回溯MySQL实体,php,sql,mysql,string,escaping,Php,Sql,Mysql,String,Escaping,我有以下方法可以保护MySQL实体: public function Tick($string) { $string = explode('.', str_replace('`', '', $string)); foreach ($string as $key => $value) { if ($value != '*') { $string[$key] = '`' . trim($value) . '`';
public function Tick($string)
{
$string = explode('.', str_replace('`', '', $string));
foreach ($string as $key => $value)
{
if ($value != '*')
{
$string[$key] = '`' . trim($value) . '`';
}
}
return implode('.', $string);
}
这对我使用它来说相当有效
它保护数据库、表、字段名,甚至*运算符,但现在我还希望它保护函数调用,即:
AVG(database.employees.salary)
应成为:
AVG(`database`.`employees`.`salary`) and not `AVG(database`.`employees`.`salary)`
我该怎么做?我应该使用正则表达式吗
另外,我如何支持更高级的东西,从:
MAX(AVG(database.table.field1), MAX(database.table.field2))
MAX(AVG(`database`.`table`.`field1`), MAX(`database`.`table`.`field2`))
至:
MAX(AVG(database.table.field1), MAX(database.table.field2))
MAX(AVG(`database`.`table`.`field1`), MAX(`database`.`table`.`field2`))
请记住,我希望此方法尽可能简单/快速,因为它几乎会迭代我数据库中的所有实体名称。您可以使用
preg\u replace\u callback()
与Tick()
方法结合使用,以跳过至少一个级别的参数:
public function tick($str)
{
return preg_replace_callback('/[^()]*/', array($this, '_tick_replace_callback'), $str);
}
protected function _tick_replace_callback($str) {
$string = explode('.', str_replace('`', '', $string));
foreach ($string as $key => $value)
{
if ($value != '*')
{
$string[$key] = '`' . trim($value) . '`';
}
}
return implode('.', $string);
}
如果这是引用SQL语句的一部分,并且它们只有您描述的复杂性,那么正则表达式是一种很好的方法。另一方面,如果需要对完整的SQL语句或更复杂的语句组件(如“MAX(AVG(val)、MAX(val2)))执行此操作,则需要对字符串进行标记化或解析,并对其有更深入的理解,以便准确地进行此引用 如果使用正则表达式方法,您可能会发现将函数名分解为一个步骤更容易,然后使用当前代码引用数据库/表/列名。这可以在一次重做中完成,但要做到正确,这将是一个骗局 不管怎样,我强烈建议编写几个单元测试用例。事实上,对于这种方法来说,这是一种理想的情况:编写测试很容易,您有一些可用的现有案例(您不想破坏这些案例),并且您只需要再添加一个案例 您的测试可以简单地开始,如下所示:
assert '`ticked`' == Tick('ticked');
assert '`table`.`ticked`' == Tick('table.ticked');
assert 'db`.`table`.`ticked`' == Tick('db.table.ticked');
然后添加:
assert 'FN(`ticked`)' == Tick('FN(ticked)');
etc.
在按句点分解字符串之前,请检查最后一个字符是否为括号。如果是,则此调用是一个函数
<?php
$string = str_replace('`', '', $string)
$function = "";
if (substr($string,-1) == ")") {
// Strip off function call first
$opening = strpos($string, "(");
$function = substr($string, 0, $opening+1);
$string = substr($string, $opening+1, -1);
}
// Do your existing parsing to $string
if ($function == "") {
// Put function back on string
$string = $function . $string . ")";
}
?>
如果您需要涵盖更高级的情况,例如使用嵌套函数,或者在一个“$string”变量中按顺序使用多个函数,那么这将成为一个更高级的函数,您最好问问自己,为什么这些元素没有首先正确地勾选,并且不需要任何进一步的解析
编辑:根据原始后期编辑更新嵌套函数
要让上面的函数处理多个嵌套函数,您可能需要一些可以“展开”嵌套函数的东西。我还没有对此进行测试,但是下面的函数可能会让您走上正确的道路
<?php
function unwrap($str) {
$pos = strpos($str, "(");
if ($pos === false) return $str; // There's no function call here
$last_close = 0;
$cur_offset = 0; // Start at the beginning
while ($cur_offset <= strlen($str)) {
$first_close = strpos($str, ")", $offset); // Find first deep function
$pos = strrpos($str, "(", $first_close-1); // Find associated opening
if ($pos > $last_close) {
// This function is entirely after the previous function
$ticked = Tick(substr($str, $pos+1, $first_close-$pos)); // Tick the string inside
$str = substr($str, 0, $pos)."{".$ticked."}".substr($str,$first_close); // Replace parenthesis by curly braces temporarily
$first_close += strlen($ticked)-($first_close-$pos); // Shift parenthesis location due to new ticks being added
} else {
// This function wraps other functions; don't tick it
$str = substr($str, 0, $pos)."{".substr($str,$pos+1, $first_close-$pos)."}".substr($str,$first_close);
}
$last_close = $first_close;
$offset = $first_close+1;
}
// Replace the curly braces with parenthesis again
$str = str_replace(array("{","}"), array("(",")"), $str);
}
将整个SQL传递给函数通常是个坏主意。这样,除非您完全解析SQL语法,否则当它不起作用时,您总会发现一个例子
在前面某个抽象级别的名称上打勾,该抽象级别构成SQL。您是在生成SQL查询还是正在将其传递给您?如果生成查询,我不会传递整个查询字符串,而只传递要在backticks中包装的参数/值或其他需要的内容
例如:
function addTick($var) {
return '`' . $var . '`';
}
$condition = addTick($condition);
$SQL = 'SELECT' . $what . '
FROM ' . $table . '
WHERE ' . $condition . ' = ' . $constraint;
这只是一个模拟,但您可以传递或循环代码并构建查询字符串,而不是解析查询字符串并添加反勾号 如果要在代码中添加函数调用,而不是通过纯字符串接口传入,则可以用类型检查替换字符串解析:
function Tick($value) {
if (is_object($value)) {
$result = $value->value;
} else {
$result = '`'.str_replace(array('`', '.'), array('', '`.`'), $value).'`';
}
return $result;
}
class SqlFunction {
var $value;
function SqlFunction($function, $params) {
$sane = implode(', ', array_map('Tick', $params));
$this->value = "$function($sane)";
}
}
function Maximum($column) {
return new SqlFunction('MAX', array($column));
}
function Avg($column) {
return new SqlFunction('AVG', array($column));
}
function Greatest() {
$params = func_get_args();
return new SqlFunction('GREATEST', $params);
}
$cases = array(
"'simple'" => Tick('simple'),
"'table.field'" => Tick('table.field'),
"'table.*'" => Tick('table.*'),
"'evil`hack'" => Tick('evil`hack'),
"Avg('database.table.field')" => Tick(Avg('database.table.field')),
"Greatest(Avg('table.field1'), Maximum('table.field2'))" => Tick(Greatest(Avg('table.field1'), Maximum('table.field2'))),
);
echo "<table>";
foreach ($cases as $case => $result) {
echo "<tr><td>$case</td><td>$result</td></tr>";
}
echo "</table>";
函数勾选($value){
如果(是对象($value)){
$result=$value->value;
}否则{
$result='''''.str_replace(数组('''','.')、数组(''''.''''')、$value)。''''';
}
返回$result;
}
类SqlFunction{
var$价值;
函数SqlFunction($function,$params){
$sane=内爆(',',数组映射('Tick',$params));
$this->value=“$function($sane)”;
}
}
函数最大值($列){
返回新的SqlFunction('MAX',数组($column));
}
函数平均值($列){
返回新的SqlFunction('AVG',数组($column));
}
函数(){
$params=func_get_args();
返回新的SqlFunction('magest',$params);
}
$cases=数组(
“'simple'”=>勾选('simple'),
“'table.field'”=>勾选('table.field'),
“'table.*'”=>勾选('table.*'),
“'evil'hack'”=>勾选('evil'hack'),
“Avg('database.table.field')”=>勾选(Avg('database.table.field')),
“最大值(平均值('table.field1')、最大值('table.field2')”=>勾选(最大值(平均值('table.field1')、最大值('table.field2')),
);
回声“;
foreach($cases as$case=>$result){
回显“$case$result”;
}
回声“;
这避免了任何可能的SQL注入,同时对代码的未来读者保持清晰。使用给出的测试用例,我创建了一个正则表达式来为您完成这项艰巨的工作。下面的正则表达式将替换单词周围未后跟左括号的所有单词边界
\b(\w+)\b(?!\()
Tick()功能将在PHP中实现,如下所示:
function Tick($string)
{
return preg_replace( '/\b(\w+)\b(?!\()/', '`\1`', $string );
}
返回“`Array``Array`)`Array`”=\我不会传递整个SQL,通常只传递字段名,但是我需要检测字段名何时也包含函数。我没有别的办法,相信我。它会永远是一个函数吗?表达方式呢?当db
tab
col
在db
othertab
col2
之间时,10 ELSE 20A定制ORM确实是一个强大的对手。我正在生成SQL,但不是像你的例子那样,tick函数通常只处理列名,但我也需要它来处理作用于列名的函数。可以理解,但如果生成查询,这应该没有问题。我正在使用类似于生成SQL的东西。为什么要这样做?倒勾是为了减少保留字标识符(和特殊字符等)。倒勾所有内容都是浪费时间我有类似的情况:我无法指定需要/想要倒勾哪些列。@Alix Axel我想你做得不对。你应该定义你的目标