Skip to content

Commit 727c251

Browse files
committed
Fix FR #71885 (Allow escaping question mark placeholders)
1 parent 455db49 commit 727c251

File tree

5 files changed

+257
-99
lines changed

5 files changed

+257
-99
lines changed

ext/pdo/pdo_sql_parser.c

Lines changed: 81 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
#define PDO_PARSER_TEXT 1
2828
#define PDO_PARSER_BIND 2
2929
#define PDO_PARSER_BIND_POS 3
30-
#define PDO_PARSER_EOI 4
30+
#define PDO_PARSER_ESCAPED_QUESTION 4
31+
#define PDO_PARSER_EOI 5
32+
33+
#define PDO_PARSER_BINDNO_ESCAPED_CHAR -1
3134

3235
#define RET(i) {s->cur = cursor; return i; }
3336
#define SKIP_ONE(i) {s->cur = s->tok + 1; return i; }
@@ -47,11 +50,11 @@ static int scan(Scanner *s)
4750
char *cursor = s->cur;
4851

4952
s->tok = cursor;
50-
#line 55 "ext/pdo/pdo_sql_parser.re"
53+
#line 59 "ext/pdo/pdo_sql_parser.re"
5154

5255

5356

54-
#line 55 "ext/pdo/pdo_sql_parser.c"
57+
#line 58 "ext/pdo/pdo_sql_parser.c"
5558
{
5659
YYCTYPE yych;
5760

@@ -80,9 +83,9 @@ static int scan(Scanner *s)
8083
yych = *(YYMARKER = ++YYCURSOR);
8184
if (yych >= 0x01) goto yy37;
8285
yy4:
83-
#line 63 "ext/pdo/pdo_sql_parser.re"
86+
#line 68 "ext/pdo/pdo_sql_parser.re"
8487
{ SKIP_ONE(PDO_PARSER_TEXT); }
85-
#line 86 "ext/pdo/pdo_sql_parser.c"
88+
#line 89 "ext/pdo/pdo_sql_parser.c"
8689
yy5:
8790
yych = *(YYMARKER = ++YYCURSOR);
8891
if (yych <= 0x00) goto yy4;
@@ -152,8 +155,8 @@ static int scan(Scanner *s)
152155
case 'w':
153156
case 'x':
154157
case 'y':
155-
case 'z': goto yy26;
156-
case ':': goto yy29;
158+
case 'z': goto yy25;
159+
case ':': goto yy28;
157160
default: goto yy4;
158161
}
159162
yy7:
@@ -163,9 +166,9 @@ static int scan(Scanner *s)
163166
default: goto yy8;
164167
}
165168
yy8:
166-
#line 62 "ext/pdo/pdo_sql_parser.re"
169+
#line 67 "ext/pdo/pdo_sql_parser.re"
167170
{ RET(PDO_PARSER_BIND_POS); }
168-
#line 169 "ext/pdo/pdo_sql_parser.c"
171+
#line 172 "ext/pdo/pdo_sql_parser.c"
169172
yy9:
170173
yych = *++YYCURSOR;
171174
goto yy4;
@@ -202,9 +205,9 @@ static int scan(Scanner *s)
202205
default: goto yy12;
203206
}
204207
yy14:
205-
#line 65 "ext/pdo/pdo_sql_parser.re"
208+
#line 70 "ext/pdo/pdo_sql_parser.re"
206209
{ RET(PDO_PARSER_TEXT); }
207-
#line 208 "ext/pdo/pdo_sql_parser.c"
210+
#line 211 "ext/pdo/pdo_sql_parser.c"
208211
yy15:
209212
++YYCURSOR;
210213
if (YYLIMIT <= YYCURSOR) YYFILL(1);
@@ -225,9 +228,9 @@ static int scan(Scanner *s)
225228
yy19:
226229
++YYCURSOR;
227230
yy20:
228-
#line 64 "ext/pdo/pdo_sql_parser.re"
231+
#line 69 "ext/pdo/pdo_sql_parser.re"
229232
{ RET(PDO_PARSER_TEXT); }
230-
#line 231 "ext/pdo/pdo_sql_parser.c"
233+
#line 234 "ext/pdo/pdo_sql_parser.c"
231234
yy21:
232235
++YYCURSOR;
233236
if (YYLIMIT <= YYCURSOR) YYFILL(1);
@@ -239,17 +242,10 @@ static int scan(Scanner *s)
239242
}
240243
yy23:
241244
++YYCURSOR;
242-
if (YYLIMIT <= YYCURSOR) YYFILL(1);
243-
yych = *YYCURSOR;
244-
switch (yych) {
245-
case '?': goto yy23;
246-
default: goto yy25;
247-
}
245+
#line 65 "ext/pdo/pdo_sql_parser.re"
246+
{ RET(PDO_PARSER_ESCAPED_QUESTION); }
247+
#line 248 "ext/pdo/pdo_sql_parser.c"
248248
yy25:
249-
#line 60 "ext/pdo/pdo_sql_parser.re"
250-
{ RET(PDO_PARSER_TEXT); }
251-
#line 252 "ext/pdo/pdo_sql_parser.c"
252-
yy26:
253249
++YYCURSOR;
254250
if (YYLIMIT <= YYCURSOR) YYFILL(1);
255251
yych = *YYCURSOR;
@@ -316,21 +312,25 @@ static int scan(Scanner *s)
316312
case 'w':
317313
case 'x':
318314
case 'y':
319-
case 'z': goto yy26;
320-
default: goto yy28;
315+
case 'z': goto yy25;
316+
default: goto yy27;
321317
}
322-
yy28:
323-
#line 61 "ext/pdo/pdo_sql_parser.re"
318+
yy27:
319+
#line 66 "ext/pdo/pdo_sql_parser.re"
324320
{ RET(PDO_PARSER_BIND); }
325-
#line 326 "ext/pdo/pdo_sql_parser.c"
326-
yy29:
321+
#line 322 "ext/pdo/pdo_sql_parser.c"
322+
yy28:
327323
++YYCURSOR;
328324
if (YYLIMIT <= YYCURSOR) YYFILL(1);
329325
yych = *YYCURSOR;
330326
switch (yych) {
331-
case ':': goto yy29;
332-
default: goto yy25;
327+
case ':': goto yy28;
328+
default: goto yy30;
333329
}
330+
yy30:
331+
#line 64 "ext/pdo/pdo_sql_parser.re"
332+
{ RET(PDO_PARSER_TEXT); }
333+
#line 334 "ext/pdo/pdo_sql_parser.c"
334334
yy31:
335335
++YYCURSOR;
336336
if (YYLIMIT <= YYCURSOR) YYFILL(1);
@@ -350,7 +350,7 @@ static int scan(Scanner *s)
350350
goto yy31;
351351
yy34:
352352
++YYCURSOR;
353-
#line 59 "ext/pdo/pdo_sql_parser.re"
353+
#line 63 "ext/pdo/pdo_sql_parser.re"
354354
{ RET(PDO_PARSER_TEXT); }
355355
#line 356 "ext/pdo/pdo_sql_parser.c"
356356
yy36:
@@ -372,11 +372,11 @@ static int scan(Scanner *s)
372372
goto yy36;
373373
yy39:
374374
++YYCURSOR;
375-
#line 58 "ext/pdo/pdo_sql_parser.re"
375+
#line 62 "ext/pdo/pdo_sql_parser.re"
376376
{ RET(PDO_PARSER_TEXT); }
377377
#line 378 "ext/pdo/pdo_sql_parser.c"
378378
}
379-
#line 66 "ext/pdo/pdo_sql_parser.re"
379+
#line 71 "ext/pdo/pdo_sql_parser.re"
380380

381381
}
382382

@@ -401,7 +401,7 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len
401401
char *ptr, *newbuffer;
402402
int t;
403403
uint32_t bindno = 0;
404-
int ret = 0;
404+
int ret = 0, escapes = 0;
405405
size_t newbuffer_len;
406406
HashTable *params;
407407
struct pdo_bound_param_data *param;
@@ -414,14 +414,19 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len
414414

415415
/* phase 1: look for args */
416416
while((t = scan(&s)) != PDO_PARSER_EOI) {
417-
if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS) {
417+
if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS || t == PDO_PARSER_ESCAPED_QUESTION) {
418+
if (t == PDO_PARSER_ESCAPED_QUESTION && stmt->supports_placeholders == PDO_PLACEHOLDER_POSITIONAL) {
419+
/* escaped question marks unsupported, treat as text */
420+
continue;
421+
}
422+
418423
if (t == PDO_PARSER_BIND) {
419424
int len = s.cur - s.tok;
420425
if ((inquery < (s.cur - len)) && isalnum(*(s.cur - len - 1))) {
421426
continue;
422427
}
423428
query_type |= PDO_PLACEHOLDER_NAMED;
424-
} else {
429+
} else if (t == PDO_PARSER_BIND_POS) {
425430
query_type |= PDO_PLACEHOLDER_POSITIONAL;
426431
}
427432

@@ -430,7 +435,16 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len
430435
plc->next = NULL;
431436
plc->pos = s.tok;
432437
plc->len = s.cur - s.tok;
433-
plc->bindno = bindno++;
438+
439+
if (t == PDO_PARSER_ESCAPED_QUESTION) {
440+
plc->bindno = PDO_PARSER_BINDNO_ESCAPED_CHAR;
441+
plc->quoted = "?";
442+
plc->qlen = 1;
443+
plc->freeq = 0;
444+
escapes++;
445+
} else {
446+
plc->bindno = bindno++;
447+
}
434448

435449
if (placetail) {
436450
placetail->next = plc;
@@ -441,7 +455,7 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len
441455
}
442456
}
443457

444-
if (bindno == 0) {
458+
if (!placeholders) {
445459
/* nothing to do; good! */
446460
return 0;
447461
}
@@ -456,11 +470,11 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len
456470

457471
if (stmt->supports_placeholders == query_type && !stmt->named_rewrite_template) {
458472
/* query matches native syntax */
459-
ret = 0;
460-
goto clean_up;
473+
newbuffer_len = inquery_len;
474+
goto rewrite;
461475
}
462476

463-
if (stmt->named_rewrite_template) {
477+
if (query_type == PDO_PLACEHOLDER_NAMED && stmt->named_rewrite_template) {
464478
/* magic/hack.
465479
* We we pretend that the query was positional even if
466480
* it was named so that we fall into the
@@ -471,14 +485,7 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len
471485

472486
params = stmt->bound_params;
473487

474-
/* Do we have placeholders but no bound params */
475-
if (bindno && !params && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
476-
pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "no parameters were bound");
477-
ret = -1;
478-
goto clean_up;
479-
}
480-
481-
if (params && bindno != zend_hash_num_elements(params) && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
488+
if (bindno && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE && params && bindno != zend_hash_num_elements(params)) {
482489
/* extra bit of validation for instances when same params are bound more than once */
483490
if (query_type != PDO_PLACEHOLDER_POSITIONAL && bindno > zend_hash_num_elements(params)) {
484491
int ok = 1;
@@ -504,7 +511,16 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len
504511
newbuffer_len = inquery_len;
505512

506513
/* let's quote all the values */
507-
for (plc = placeholders; plc; plc = plc->next) {
514+
for (plc = placeholders; plc && params; plc = plc->next) {
515+
if (plc->bindno == PDO_PARSER_BINDNO_ESCAPED_CHAR) {
516+
/* escaped character */
517+
continue;
518+
}
519+
520+
if (query_type == PDO_PLACEHOLDER_NONE) {
521+
continue;
522+
}
523+
508524
if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
509525
param = zend_hash_index_find_ptr(params, plc->bindno);
510526
} else {
@@ -618,7 +634,7 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len
618634

619635
rewrite:
620636
/* allocate output buffer */
621-
newbuffer = emalloc(newbuffer_len + 1);
637+
newbuffer = emalloc(newbuffer_len - escapes + 1);
622638
*outquery = newbuffer;
623639

624640
/* and build the query */
@@ -631,8 +647,13 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len
631647
memcpy(newbuffer, ptr, t);
632648
newbuffer += t;
633649
}
634-
memcpy(newbuffer, plc->quoted, plc->qlen);
635-
newbuffer += plc->qlen;
650+
if (plc->quoted) {
651+
memcpy(newbuffer, plc->quoted, plc->qlen);
652+
newbuffer += plc->qlen;
653+
} else {
654+
memcpy(newbuffer, plc->pos, plc->len);
655+
newbuffer += plc->len;
656+
}
636657
ptr = plc->pos + plc->len;
637658

638659
plc = plc->next;
@@ -665,6 +686,11 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len
665686
for (plc = placeholders; plc; plc = plc->next) {
666687
int skip_map = 0;
667688
char *p;
689+
690+
if (plc->bindno == PDO_PARSER_BINDNO_ESCAPED_CHAR) {
691+
continue;
692+
}
693+
668694
name = estrndup(plc->pos, plc->len);
669695

670696
/* check if bound parameter is already available */
@@ -710,6 +736,7 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len
710736
efree(name);
711737
plc->quoted = "?";
712738
plc->qlen = 1;
739+
newbuffer_len -= plc->len - 1;
713740
}
714741

715742
goto rewrite;

0 commit comments

Comments
 (0)