Skip to content

Commit 3f31153

Browse files
noahheckbarryvdh
authored andcommitted
Traced statement interpolation fixes (php-debugbar#381)
* Prevent back reference issues in TracedStatement * Prevent substring replacement If a previously replaced value in the query string contains the placeholder for a future replacement, the string inside was being replaced Also, PHP allows the bindParameter syntax to omit the leading ':', so we test to make sure it's there and add it if not
1 parent afa317d commit 3f31153

File tree

2 files changed

+100
-6
lines changed

2 files changed

+100
-6
lines changed

src/DebugBar/DataCollector/PDO/TracedStatement.php

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,28 @@ public function getSqlWithParams($quotationChar = '<>')
107107
}
108108

109109
$sql = $this->sql;
110+
111+
$cleanBackRefCharMap = array('%'=>'%%', '$'=>'$%', '\\'=>'\\%');
112+
110113
foreach ($this->parameters as $k => $v) {
111-
$v = "$quoteLeft$v$quoteRight";
112-
if (!is_numeric($k)) {
113-
$sql = preg_replace("/{$k}\b/", $v, $sql, 1);
114+
115+
$backRefSafeV = strtr($v, $cleanBackRefCharMap);
116+
117+
$v = "$quoteLeft$backRefSafeV$quoteRight";
118+
119+
if (is_numeric($k)) {
120+
$marker = "\?";
114121
} else {
115-
$p = strpos($sql, '?');
116-
$sql = substr($sql, 0, $p) . $v. substr($sql, $p + 1);
122+
$marker = (preg_match("/^:/", $k)) ? $k : ":" . $k;
117123
}
124+
125+
$matchRule = "/({$marker}(?!\w))(?=(?:[^$quotationChar]|[$quotationChar][^$quotationChar]*[$quotationChar])*$)/";
126+
127+
$sql = preg_replace($matchRule, $v, $sql, 1);
118128
}
129+
130+
$sql = strtr($sql, array_flip($cleanBackRefCharMap));
131+
119132
return $sql;
120133
}
121134

tests/DebugBar/Tests/TracedStatementTest.php

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,85 @@ public function testReplacementParamsQuery()
3737
$result = $traced->getSqlWithParams();
3838
$this->assertEquals($expected, $result);
3939
}
40-
}
40+
41+
public function testReplacementParamsContainingBackReferenceSyntaxGeneratesCorrectString()
42+
{
43+
$hashedPassword = '$2y$10$S3Y/kSsx8Z5BPtdd9.k3LOkbQ0egtsUHBT9EGQ.spxsmaEWbrxBW2';
44+
$sql = "UPDATE user SET password = :password";
45+
46+
$params = array(
47+
':password' => $hashedPassword,
48+
);
49+
50+
$traced = new TracedStatement($sql, $params);
51+
52+
$result = $traced->getSqlWithParams();
53+
54+
$expected = "UPDATE user SET password = <$hashedPassword>";
55+
56+
$this->assertEquals($expected, $result);
57+
}
58+
59+
public function testReplacementParamsContainingPotentialAdditionalQuestionMarkPlaceholderGeneratesCorrectString()
60+
{
61+
$hasQuestionMark = "Asking a question?";
62+
$string = "Asking for a friend";
63+
64+
$sql = "INSERT INTO questions SET question = ?, detail = ?";
65+
66+
$params = array($hasQuestionMark, $string);
67+
68+
$traced = new TracedStatement($sql, $params);
69+
70+
$result = $traced->getSqlWithParams();
71+
72+
$expected = "INSERT INTO questions SET question = <$hasQuestionMark>, detail = <$string>";
73+
74+
$this->assertEquals($expected, $result);
75+
76+
$result = $traced->getSqlWithParams("'");
77+
78+
$expected = "INSERT INTO questions SET question = '$hasQuestionMark', detail = '$string'";
79+
80+
$this->assertEquals($expected, $result);
81+
82+
$result = $traced->getSqlWithParams('"');
83+
84+
$expected = "INSERT INTO questions SET question = \"$hasQuestionMark\", detail = \"$string\"";
85+
86+
$this->assertEquals($expected, $result);
87+
}
88+
89+
public function testReplacementParamsContainingPotentialAdditionalNamedPlaceholderGeneratesCorrectString()
90+
{
91+
$hasQuestionMark = "Asking a question with a :string inside";
92+
$string = "Asking for a friend";
93+
94+
$sql = "INSERT INTO questions SET question = :question, detail = :string";
95+
96+
$params = array(
97+
':question' => $hasQuestionMark,
98+
':string' => $string,
99+
);
100+
101+
$traced = new TracedStatement($sql, $params);
102+
103+
$result = $traced->getSqlWithParams();
104+
105+
$expected = "INSERT INTO questions SET question = <$hasQuestionMark>, detail = <$string>";
106+
107+
$this->assertEquals($expected, $result);
108+
109+
$result = $traced->getSqlWithParams("'");
110+
111+
$expected = "INSERT INTO questions SET question = '$hasQuestionMark', detail = '$string'";
112+
113+
$this->assertEquals($expected, $result);
114+
115+
$result = $traced->getSqlWithParams('"');
116+
117+
$expected = "INSERT INTO questions SET question = \"$hasQuestionMark\", detail = \"$string\"";
118+
119+
$this->assertEquals($expected, $result);
120+
}
121+
}

0 commit comments

Comments
 (0)