Line data Source code
1 : /*
2 : * test_escape.c Test escape functions
3 : *
4 : * Copyright (c) 2022-2025, PostgreSQL Global Development Group
5 : *
6 : * IDENTIFICATION
7 : * src/test/modules/test_escape/test_escape.c
8 : */
9 :
10 : #include "postgres_fe.h"
11 :
12 : #include <string.h>
13 : #include <stdio.h>
14 :
15 : #include "fe_utils/psqlscan.h"
16 : #include "fe_utils/string_utils.h"
17 : #include "getopt_long.h"
18 : #include "libpq-fe.h"
19 : #include "mb/pg_wchar.h"
20 : #include "utils/memdebug.h"
21 :
22 :
23 : typedef struct pe_test_config
24 : {
25 : int verbosity;
26 : bool force_unsupported;
27 : const char *conninfo;
28 : PGconn *conn;
29 :
30 : int test_count;
31 : int failure_count;
32 : } pe_test_config;
33 :
34 :
35 : /*
36 : * An escape function to be tested by this test.
37 : */
38 : typedef struct pe_test_escape_func
39 : {
40 : const char *name;
41 :
42 : /*
43 : * Can the escape method report errors? If so, we validate that it does in
44 : * case of various invalid inputs.
45 : */
46 : bool reports_errors;
47 :
48 : /*
49 : * Is the escape method known to not handle invalidly encoded input? If
50 : * so, we don't run the test unless --force-unsupported is used.
51 : */
52 : bool supports_only_valid;
53 :
54 : /*
55 : * Is the escape method known to only handle encodings where no byte in a
56 : * multi-byte characters are valid ascii.
57 : */
58 : bool supports_only_ascii_overlap;
59 :
60 : /*
61 : * Does the escape function have a length input?
62 : */
63 : bool supports_input_length;
64 :
65 : bool (*escape) (PGconn *conn, PQExpBuffer target,
66 : const char *unescaped, size_t unescaped_len,
67 : PQExpBuffer escape_err);
68 : } pe_test_escape_func;
69 :
70 : /*
71 : * A single test input for this test.
72 : */
73 : typedef struct pe_test_vector
74 : {
75 : const char *client_encoding;
76 : size_t escape_len;
77 : const char *escape;
78 : } pe_test_vector;
79 :
80 :
81 : /*
82 : * Callback functions from flex lexer. Not currently used by the test.
83 : */
84 : static const PsqlScanCallbacks test_scan_callbacks = {
85 : NULL
86 : };
87 :
88 :
89 : static bool
90 118 : escape_literal(PGconn *conn, PQExpBuffer target,
91 : const char *unescaped, size_t unescaped_len,
92 : PQExpBuffer escape_err)
93 : {
94 : char *escaped;
95 :
96 118 : escaped = PQescapeLiteral(conn, unescaped, unescaped_len);
97 118 : if (!escaped)
98 : {
99 70 : appendPQExpBufferStr(escape_err, PQerrorMessage(conn));
100 70 : escape_err->data[escape_err->len - 1] = 0;
101 70 : escape_err->len--;
102 70 : return false;
103 : }
104 : else
105 : {
106 48 : appendPQExpBufferStr(target, escaped);
107 48 : PQfreemem(escaped);
108 48 : return true;
109 : }
110 : }
111 :
112 : static bool
113 118 : escape_identifier(PGconn *conn, PQExpBuffer target,
114 : const char *unescaped, size_t unescaped_len,
115 : PQExpBuffer escape_err)
116 : {
117 : char *escaped;
118 :
119 118 : escaped = PQescapeIdentifier(conn, unescaped, unescaped_len);
120 118 : if (!escaped)
121 : {
122 70 : appendPQExpBufferStr(escape_err, PQerrorMessage(conn));
123 70 : escape_err->data[escape_err->len - 1] = 0;
124 70 : escape_err->len--;
125 70 : return false;
126 : }
127 : else
128 : {
129 48 : appendPQExpBufferStr(target, escaped);
130 48 : PQfreemem(escaped);
131 48 : return true;
132 : }
133 : }
134 :
135 : static bool
136 118 : escape_string_conn(PGconn *conn, PQExpBuffer target,
137 : const char *unescaped, size_t unescaped_len,
138 : PQExpBuffer escape_err)
139 : {
140 : int error;
141 : size_t sz;
142 :
143 118 : appendPQExpBufferChar(target, '\'');
144 118 : enlargePQExpBuffer(target, unescaped_len * 2 + 1);
145 118 : sz = PQescapeStringConn(conn, target->data + target->len,
146 : unescaped, unescaped_len,
147 : &error);
148 :
149 118 : target->len += sz;
150 118 : appendPQExpBufferChar(target, '\'');
151 :
152 118 : if (error)
153 : {
154 70 : appendPQExpBufferStr(escape_err, PQerrorMessage(conn));
155 70 : escape_err->data[escape_err->len - 1] = 0;
156 70 : escape_err->len--;
157 70 : return false;
158 : }
159 : else
160 : {
161 48 : return true;
162 : }
163 : }
164 :
165 : static bool
166 118 : escape_string(PGconn *conn, PQExpBuffer target,
167 : const char *unescaped, size_t unescaped_len,
168 : PQExpBuffer escape_err)
169 : {
170 : size_t sz;
171 :
172 118 : appendPQExpBufferChar(target, '\'');
173 118 : enlargePQExpBuffer(target, unescaped_len * 2 + 1);
174 118 : sz = PQescapeString(target->data + target->len,
175 : unescaped, unescaped_len);
176 118 : target->len += sz;
177 118 : appendPQExpBufferChar(target, '\'');
178 :
179 :
180 118 : return true;
181 : }
182 :
183 : /*
184 : * Escape via s/'/''/. Non-core drivers invariably wrap libpq or use this
185 : * method. It suffices iff the input passes encoding validation, so it's
186 : * marked as supports_only_valid.
187 : */
188 : static bool
189 20 : escape_replace(PGconn *conn, PQExpBuffer target,
190 : const char *unescaped, size_t unescaped_len,
191 : PQExpBuffer escape_err)
192 : {
193 20 : const char *s = unescaped;
194 :
195 20 : appendPQExpBufferChar(target, '\'');
196 :
197 50 : for (int i = 0; i < unescaped_len; i++)
198 : {
199 30 : char c = *s;
200 :
201 30 : if (c == '\'')
202 : {
203 8 : appendPQExpBufferStr(target, "''");
204 : }
205 : else
206 22 : appendPQExpBufferChar(target, c);
207 30 : s++;
208 : }
209 20 : appendPQExpBufferChar(target, '\'');
210 :
211 20 : return true;
212 : }
213 :
214 : static bool
215 118 : escape_append_literal(PGconn *conn, PQExpBuffer target,
216 : const char *unescaped, size_t unescaped_len,
217 : PQExpBuffer escape_err)
218 : {
219 118 : appendStringLiteral(target, unescaped, PQclientEncoding(conn), 1);
220 :
221 118 : return true;
222 : }
223 :
224 : static bool
225 118 : escape_fmt_id(PGconn *conn, PQExpBuffer target,
226 : const char *unescaped, size_t unescaped_len,
227 : PQExpBuffer escape_err)
228 : {
229 118 : setFmtEncoding(PQclientEncoding(conn));
230 118 : appendPQExpBufferStr(target, fmtId(unescaped));
231 :
232 118 : return true;
233 : }
234 :
235 : static pe_test_escape_func pe_test_escape_funcs[] =
236 : {
237 : {
238 : .name = "PQescapeLiteral",
239 : .reports_errors = true,
240 : .supports_input_length = true,
241 : .escape = escape_literal,
242 : },
243 : {
244 : .name = "PQescapeIdentifier",
245 : .reports_errors = true,
246 : .supports_input_length = true,
247 : .escape = escape_identifier
248 : },
249 : {
250 : .name = "PQescapeStringConn",
251 : .reports_errors = true,
252 : .supports_input_length = true,
253 : .escape = escape_string_conn
254 : },
255 : {
256 : .name = "PQescapeString",
257 : .reports_errors = false,
258 : .supports_input_length = true,
259 : .escape = escape_string
260 : },
261 : {
262 : .name = "replace",
263 : .reports_errors = false,
264 : .supports_only_valid = true,
265 : .supports_only_ascii_overlap = true,
266 : .supports_input_length = true,
267 : .escape = escape_replace
268 : },
269 : {
270 : .name = "appendStringLiteral",
271 : .reports_errors = false,
272 : .escape = escape_append_literal
273 : },
274 : {
275 : .name = "fmtId",
276 : .reports_errors = false,
277 : .escape = escape_fmt_id
278 : },
279 : };
280 :
281 :
282 : #define TV(enc, string) {.client_encoding = (enc), .escape=string, .escape_len=sizeof(string) - 1, }
283 : #define TV_LEN(enc, string, len) {.client_encoding = (enc), .escape=string, .escape_len=len, }
284 : static pe_test_vector pe_test_vectors[] =
285 : {
286 : /* expected to work sanity checks */
287 : TV("UTF-8", "1"),
288 : TV("UTF-8", "'"),
289 : TV("UTF-8", "\""),
290 :
291 : TV("UTF-8", "\'"),
292 : TV("UTF-8", "\""),
293 :
294 : TV("UTF-8", "\\"),
295 :
296 : TV("UTF-8", "\\'"),
297 : TV("UTF-8", "\\\""),
298 :
299 : /* trailing multi-byte character, paddable in available space */
300 : TV("UTF-8", "1\xC0"),
301 : TV("UTF-8", "1\xE0 "),
302 : TV("UTF-8", "1\xF0 "),
303 : TV("UTF-8", "1\xF0 "),
304 : TV("UTF-8", "1\xF0 "),
305 :
306 : /* trailing multi-byte character, not enough space to pad */
307 : TV("UTF-8", "1\xE0"),
308 : TV("UTF-8", "1\xF0"),
309 : TV("UTF-8", "\xF0"),
310 :
311 : /* try to smuggle in something in invalid characters */
312 : TV("UTF-8", "1\xE0'"),
313 : TV("UTF-8", "1\xE0\""),
314 : TV("UTF-8", "1\xF0'"),
315 : TV("UTF-8", "1\xF0\""),
316 : TV("UTF-8", "1\xF0'; "),
317 : TV("UTF-8", "1\xF0\"; "),
318 : TV("UTF-8", "1\xF0';;;;"),
319 : TV("UTF-8", "1\xF0 ';;;;"),
320 : TV("UTF-8", "1\xF0 \";;;;"),
321 : TV("UTF-8", "1\xE0'; \\l ; "),
322 : TV("UTF-8", "1\xE0\"; \\l ; "),
323 :
324 : /* null byte handling */
325 : TV("UTF-8", "some\0thing"),
326 : TV("UTF-8", "some\0"),
327 : TV("UTF-8", "some\xF0'\0"),
328 : TV("UTF-8", "some\xF0'\0'"),
329 : TV("UTF-8", "some\xF0" "ab\0'"),
330 :
331 : /* GB18030's 4 byte encoding requires a 2nd byte limited values */
332 : TV("GB18030", "\x90\x31"),
333 : TV("GB18030", "\\\x81\x5c'"),
334 : TV("GB18030", "\\\x81\x5c\""),
335 : TV("GB18030", "\\\x81\x5c\0'"),
336 :
337 : /*
338 : * \x81 indicates a 2 byte char. ' and " are not a valid second byte, but
339 : * that requires encoding verification to know. E.g. replace_string()
340 : * doesn't cope.
341 : */
342 : TV("GB18030", "\\\x81';"),
343 : TV("GB18030", "\\\x81\";"),
344 :
345 : /*
346 : * \x81 indicates a 2 byte char. \ is a valid second character.
347 : */
348 : TV("GB18030", "\\\x81\\';"),
349 : TV("GB18030", "\\\x81\\\";"),
350 : TV("GB18030", "\\\x81\0;"),
351 : TV("GB18030", "\\\x81\0'"),
352 : TV("GB18030", "\\\x81'\0"),
353 :
354 : TV("SJIS", "\xF0\x40;"),
355 :
356 : TV("SJIS", "\xF0';"),
357 : TV("SJIS", "\xF0\";"),
358 : TV("SJIS", "\xF0\0'"),
359 : TV("SJIS", "\\\xF0\\';"),
360 : TV("SJIS", "\\\xF0\\\";"),
361 :
362 : TV("gbk", "\x80';"),
363 : TV("gbk", "\x80"),
364 : TV("gbk", "\x80'"),
365 : TV("gbk", "\x80\""),
366 : TV("gbk", "\x80\\"),
367 :
368 : TV("mule_internal", "\\\x9c';\0;"),
369 :
370 : TV("sql_ascii", "1\xC0'"),
371 :
372 : /*
373 : * Testcases that are not null terminated for the specified input length.
374 : * That's interesting to verify that escape functions don't read beyond
375 : * the intended input length.
376 : */
377 : TV_LEN("gbk", "\x80", 1),
378 : TV_LEN("UTF-8", "\xC3\xb6 ", 1),
379 : TV_LEN("UTF-8", "\xC3\xb6 ", 2),
380 : };
381 :
382 :
383 : /*
384 : * Print the string into buf, making characters outside of plain ascii
385 : * somewhat easier to recognize.
386 : *
387 : * The output format could stand to be improved significantly, it's not at all
388 : * unambiguous.
389 : */
390 : static void
391 2736 : escapify(PQExpBuffer buf, const char *str, size_t len)
392 : {
393 16036 : for (size_t i = 0; i < len; i++)
394 : {
395 13300 : char c = *str;
396 :
397 13300 : if (c == '\n')
398 0 : appendPQExpBufferStr(buf, "\\n");
399 13300 : else if (c == '\0')
400 288 : appendPQExpBufferStr(buf, "\\0");
401 13012 : else if (c < ' ' || c > '~')
402 2240 : appendPQExpBuffer(buf, "\\x%2x", (uint8_t) c);
403 : else
404 10772 : appendPQExpBufferChar(buf, c);
405 13300 : str++;
406 : }
407 2736 : }
408 :
409 : static void
410 2258 : report_result(pe_test_config *tc,
411 : bool success,
412 : PQExpBuffer testname,
413 : PQExpBuffer details,
414 : const char *subname,
415 : const char *resultdesc)
416 : {
417 2258 : int test_id = ++tc->test_count;
418 2258 : bool print_details = true;
419 2258 : bool print_result = true;
420 :
421 2258 : if (success)
422 : {
423 2258 : if (tc->verbosity <= 0)
424 2258 : print_details = false;
425 2258 : if (tc->verbosity < 0)
426 0 : print_result = false;
427 : }
428 : else
429 0 : tc->failure_count++;
430 :
431 2258 : if (print_details)
432 0 : printf("%s", details->data);
433 :
434 2258 : if (print_result)
435 2258 : printf("%s %d - %s: %s: %s\n",
436 : success ? "ok" : "not ok",
437 : test_id, testname->data,
438 : subname,
439 : resultdesc);
440 2258 : }
441 :
442 : /*
443 : * Return true for encodings in which bytes in a multi-byte character look
444 : * like valid ascii characters.
445 : */
446 : static bool
447 118 : encoding_conflicts_ascii(int encoding)
448 : {
449 : /*
450 : * We don't store this property directly anywhere, but whether an encoding
451 : * is a client-only encoding is a good proxy.
452 : */
453 118 : if (encoding > PG_ENCODING_BE_LAST)
454 46 : return true;
455 72 : return false;
456 : }
457 :
458 : static const char *
459 588 : scan_res_s(PsqlScanResult res)
460 : {
461 : #define TOSTR_CASE(sym) case sym: return #sym
462 :
463 588 : switch (res)
464 : {
465 0 : TOSTR_CASE(PSCAN_SEMICOLON);
466 0 : TOSTR_CASE(PSCAN_BACKSLASH);
467 0 : TOSTR_CASE(PSCAN_INCOMPLETE);
468 588 : TOSTR_CASE(PSCAN_EOL);
469 : }
470 :
471 0 : pg_unreachable();
472 : return ""; /* silence compiler */
473 : }
474 :
475 : /*
476 : * Verify that psql parses the input as a single statement. If this property
477 : * is violated, the escape function does not effectively protect against
478 : * smuggling in a second statement.
479 : */
480 : static void
481 588 : test_psql_parse(pe_test_config *tc, PQExpBuffer testname,
482 : PQExpBuffer input_buf, PQExpBuffer details)
483 : {
484 : PsqlScanState scan_state;
485 : PsqlScanResult scan_result;
486 : PQExpBuffer query_buf;
487 588 : promptStatus_t prompt_status = PROMPT_READY;
488 588 : int matches = 0;
489 : bool test_fails;
490 : const char *resdesc;
491 :
492 588 : query_buf = createPQExpBuffer();
493 :
494 588 : scan_state = psql_scan_create(&test_scan_callbacks);
495 :
496 : /*
497 : * TODO: This hardcodes standard conforming strings, it would be useful to
498 : * test without as well.
499 : */
500 588 : psql_scan_setup(scan_state, input_buf->data, input_buf->len,
501 588 : PQclientEncoding(tc->conn), 1);
502 :
503 : do
504 : {
505 588 : resetPQExpBuffer(query_buf);
506 :
507 588 : scan_result = psql_scan(scan_state, query_buf,
508 : &prompt_status);
509 :
510 588 : appendPQExpBuffer(details,
511 : "#\t\t %d: scan_result: %s prompt: %u, query_buf: ",
512 : matches, scan_res_s(scan_result), prompt_status);
513 588 : escapify(details, query_buf->data, query_buf->len);
514 588 : appendPQExpBufferChar(details, '\n');
515 :
516 588 : matches++;
517 : }
518 588 : while (scan_result != PSCAN_INCOMPLETE && scan_result != PSCAN_EOL);
519 :
520 588 : psql_scan_destroy(scan_state);
521 588 : destroyPQExpBuffer(query_buf);
522 :
523 588 : test_fails = matches > 1 || scan_result != PSCAN_EOL;
524 :
525 588 : if (matches > 1)
526 0 : resdesc = "more than one match";
527 588 : else if (scan_result != PSCAN_EOL)
528 0 : resdesc = "unexpected end state";
529 : else
530 588 : resdesc = "ok";
531 :
532 588 : report_result(tc, !test_fails, testname, details,
533 : "psql parse",
534 588 : resdesc);
535 588 : }
536 :
537 : static void
538 826 : test_one_vector_escape(pe_test_config *tc, const pe_test_vector *tv, const pe_test_escape_func *ef)
539 : {
540 : PQExpBuffer testname;
541 : PQExpBuffer details;
542 : PQExpBuffer raw_buf;
543 : PQExpBuffer escape_buf;
544 : PQExpBuffer escape_err;
545 : size_t input_encoding_validlen;
546 : bool input_encoding_valid;
547 : size_t input_encoding0_validlen;
548 : bool input_encoding0_valid;
549 : bool escape_success;
550 : size_t escape_encoding_length;
551 : bool escape_encoding_valid;
552 :
553 826 : escape_err = createPQExpBuffer();
554 826 : testname = createPQExpBuffer();
555 826 : details = createPQExpBuffer();
556 826 : raw_buf = createPQExpBuffer();
557 826 : escape_buf = createPQExpBuffer();
558 :
559 944 : if (ef->supports_only_ascii_overlap &&
560 118 : encoding_conflicts_ascii(PQclientEncoding(tc->conn)))
561 : {
562 46 : goto out;
563 : }
564 :
565 : /* name to describe the test */
566 780 : appendPQExpBufferChar(testname, '>');
567 780 : escapify(testname, tv->escape, tv->escape_len);
568 780 : appendPQExpBuffer(testname, "< - %s - %s",
569 : tv->client_encoding, ef->name);
570 :
571 : /* details to describe the test, to allow for debugging */
572 780 : appendPQExpBuffer(details, "#\t input: %zd bytes: ",
573 : tv->escape_len);
574 780 : escapify(details, tv->escape, tv->escape_len);
575 780 : appendPQExpBufferChar(details, '\n');
576 780 : appendPQExpBuffer(details, "#\t encoding: %s\n",
577 : tv->client_encoding);
578 :
579 :
580 : /* check encoding of input, to compare with after the test */
581 780 : input_encoding_validlen = pg_encoding_verifymbstr(PQclientEncoding(tc->conn),
582 : tv->escape,
583 780 : tv->escape_len);
584 780 : input_encoding_valid = input_encoding_validlen == tv->escape_len;
585 780 : appendPQExpBuffer(details, "#\t input encoding valid: %d\n",
586 : input_encoding_valid);
587 :
588 780 : input_encoding0_validlen = pg_encoding_verifymbstr(PQclientEncoding(tc->conn),
589 : tv->escape,
590 780 : strnlen(tv->escape, tv->escape_len));
591 780 : input_encoding0_valid = input_encoding0_validlen == strnlen(tv->escape, tv->escape_len);
592 780 : appendPQExpBuffer(details, "#\t input encoding valid till 0: %d\n",
593 : input_encoding0_valid);
594 :
595 780 : appendPQExpBuffer(details, "#\t escape func: %s\n",
596 : ef->name);
597 :
598 780 : if (!input_encoding_valid && ef->supports_only_valid
599 52 : && !tc->force_unsupported)
600 52 : goto out;
601 :
602 :
603 : /*
604 : * Put the to-be-escaped data into a buffer, so that we
605 : *
606 : * a) can mark memory beyond end of the string as inaccessible when using
607 : * valgrind
608 : *
609 : * b) can append extra data beyond the length passed to the escape
610 : * function, to verify that that data is not processed.
611 : *
612 : * TODO: Should we instead/additionally escape twice, once with unmodified
613 : * and once with appended input? That way we could compare the two.
614 : */
615 728 : appendBinaryPQExpBuffer(raw_buf, tv->escape, tv->escape_len);
616 :
617 : #define NEVER_ACCESS_STR "\xff never-to-be-touched"
618 728 : if (ef->supports_input_length)
619 : {
620 : /*
621 : * Append likely invalid string that does *not* contain a null byte
622 : * (which'd prevent some invalid accesses to later memory).
623 : */
624 492 : appendPQExpBufferStr(raw_buf, NEVER_ACCESS_STR);
625 :
626 : VALGRIND_MAKE_MEM_NOACCESS(&raw_buf->data[tv->escape_len],
627 : raw_buf->len - tv->escape_len);
628 : }
629 : else
630 : {
631 : /* append invalid string, after \0 */
632 236 : appendPQExpBufferChar(raw_buf, 0);
633 236 : appendPQExpBufferStr(raw_buf, NEVER_ACCESS_STR);
634 :
635 : VALGRIND_MAKE_MEM_NOACCESS(&raw_buf->data[tv->escape_len + 1],
636 : raw_buf->len - tv->escape_len - 1);
637 : }
638 :
639 : /* call the to-be-tested escape function */
640 728 : escape_success = ef->escape(tc->conn, escape_buf,
641 728 : raw_buf->data, tv->escape_len,
642 : escape_err);
643 728 : if (!escape_success)
644 : {
645 210 : appendPQExpBuffer(details, "#\t escape error: %s\n",
646 : escape_err->data);
647 : }
648 :
649 728 : if (escape_buf->len > 0)
650 : {
651 : bool contains_never;
652 :
653 588 : appendPQExpBuffer(details, "#\t escaped string: %zd bytes: ", escape_buf->len);
654 588 : escapify(details, escape_buf->data, escape_buf->len);
655 588 : appendPQExpBufferChar(details, '\n');
656 :
657 588 : escape_encoding_length = pg_encoding_verifymbstr(PQclientEncoding(tc->conn),
658 588 : escape_buf->data,
659 588 : escape_buf->len);
660 588 : escape_encoding_valid = escape_encoding_length == escape_buf->len;
661 :
662 588 : appendPQExpBuffer(details, "#\t escape encoding valid: %d\n",
663 : escape_encoding_valid);
664 :
665 : /*
666 : * Verify that no data beyond the end of the input is included in the
667 : * escaped string. It'd be better to use something like memmem()
668 : * here, but that's not available everywhere.
669 : */
670 588 : contains_never = strstr(escape_buf->data, NEVER_ACCESS_STR) == NULL;
671 588 : report_result(tc, contains_never, testname, details,
672 : "escaped data beyond end of input",
673 : contains_never ? "no" : "all secrets revealed");
674 : }
675 : else
676 : {
677 140 : escape_encoding_length = 0;
678 140 : escape_encoding_valid = 1;
679 : }
680 :
681 : /*
682 : * If the test reports errors, and the input was invalidly encoded,
683 : * escaping should fail. One edge-case that we accept for now is that the
684 : * input could have an embedded null byte, which the escape functions will
685 : * just treat as a shorter string. If the encoding error is after the zero
686 : * byte, the output thus won't contain it.
687 : */
688 728 : if (ef->reports_errors)
689 : {
690 354 : bool ok = true;
691 354 : const char *resdesc = "ok";
692 :
693 354 : if (escape_success)
694 : {
695 144 : if (!input_encoding0_valid)
696 : {
697 0 : ok = false;
698 0 : resdesc = "invalid input escaped successfully";
699 : }
700 144 : else if (!input_encoding_valid)
701 18 : resdesc = "invalid input escaped successfully, due to zero byte";
702 : }
703 : else
704 : {
705 210 : if (input_encoding0_valid)
706 : {
707 0 : ok = false;
708 0 : resdesc = "valid input failed to escape";
709 : }
710 210 : else if (input_encoding_valid)
711 0 : resdesc = "valid input failed to escape, due to zero byte";
712 : }
713 :
714 354 : report_result(tc, ok, testname, details,
715 : "input validity vs escape success",
716 : resdesc);
717 : }
718 :
719 : /*
720 : * If the input is invalidly encoded, the output should also be invalidly
721 : * encoded. We accept the same zero-byte edge case as above.
722 : */
723 : {
724 728 : bool ok = true;
725 728 : const char *resdesc = "ok";
726 :
727 728 : if (input_encoding0_valid && !input_encoding_valid && escape_encoding_valid)
728 : {
729 36 : resdesc = "invalid input produced valid output, due to zero byte";
730 : }
731 692 : else if (input_encoding0_valid && !escape_encoding_valid)
732 : {
733 0 : ok = false;
734 0 : resdesc = "valid input produced invalid output";
735 : }
736 692 : else if (!input_encoding0_valid &&
737 420 : (!ef->reports_errors || escape_success) &&
738 : escape_encoding_valid)
739 : {
740 0 : ok = false;
741 0 : resdesc = "invalid input produced valid output";
742 : }
743 :
744 728 : report_result(tc, ok, testname, details,
745 : "input and escaped encoding validity",
746 : resdesc);
747 : }
748 :
749 : /*
750 : * Test psql parsing whenever we get any string back, even if the escape
751 : * function returned a failure.
752 : */
753 728 : if (escape_buf->len > 0)
754 : {
755 588 : test_psql_parse(tc, testname,
756 : escape_buf, details);
757 : }
758 :
759 140 : out:
760 826 : destroyPQExpBuffer(escape_err);
761 826 : destroyPQExpBuffer(details);
762 826 : destroyPQExpBuffer(testname);
763 826 : destroyPQExpBuffer(escape_buf);
764 826 : destroyPQExpBuffer(raw_buf);
765 826 : }
766 :
767 : static void
768 118 : test_one_vector(pe_test_config *tc, const pe_test_vector *tv)
769 : {
770 118 : if (PQsetClientEncoding(tc->conn, tv->client_encoding))
771 : {
772 0 : fprintf(stderr, "failed to set encoding to %s:\n%s\n",
773 0 : tv->client_encoding, PQerrorMessage(tc->conn));
774 0 : exit(1);
775 : }
776 :
777 944 : for (int escoff = 0; escoff < lengthof(pe_test_escape_funcs); escoff++)
778 : {
779 826 : const pe_test_escape_func *ef = &pe_test_escape_funcs[escoff];
780 :
781 826 : test_one_vector_escape(tc, tv, ef);
782 : }
783 118 : }
784 :
785 : static void
786 0 : usage(const char *hint)
787 : {
788 0 : if (hint)
789 0 : fprintf(stderr, "Error: %s\n\n", hint);
790 :
791 0 : printf("PostgreSQL escape function test\n"
792 : "\n"
793 : "Usage:\n"
794 : " test_escape --conninfo=CONNINFO [OPTIONS]\n"
795 : "\n"
796 : "Options:\n"
797 : " -h, --help show this help\n"
798 : " -c, --conninfo=CONNINFO connection information to use\n"
799 : " -v, --verbose show test details even for successes\n"
800 : " -q, --quiet only show failures\n"
801 : " -f, --force-unsupported test invalid input even if unsupported\n"
802 : );
803 :
804 0 : if (hint)
805 0 : exit(1);
806 0 : }
807 :
808 : int
809 2 : main(int argc, char *argv[])
810 : {
811 2 : pe_test_config tc = {0};
812 : int c;
813 : int option_index;
814 :
815 : static const struct option long_options[] = {
816 : {"help", no_argument, NULL, 'h'},
817 : {"conninfo", required_argument, NULL, 'c'},
818 : {"verbose", no_argument, NULL, 'v'},
819 : {"quiet", no_argument, NULL, 'q'},
820 : {"force-unsupported", no_argument, NULL, 'f'},
821 : {NULL, 0, NULL, 0},
822 : };
823 :
824 6 : while ((c = getopt_long(argc, argv, "c:fhqv", long_options, &option_index)) != -1)
825 : {
826 2 : switch (c)
827 : {
828 0 : case 'h':
829 0 : usage(NULL);
830 0 : exit(0);
831 : break;
832 2 : case 'c':
833 2 : tc.conninfo = optarg;
834 2 : break;
835 0 : case 'v':
836 0 : tc.verbosity++;
837 0 : break;
838 0 : case 'q':
839 0 : tc.verbosity--;
840 0 : break;
841 0 : case 'f':
842 0 : tc.force_unsupported = true;
843 0 : break;
844 : }
845 4 : }
846 :
847 2 : if (argc - optind >= 1)
848 0 : usage("unused option(s) specified");
849 :
850 2 : if (tc.conninfo == NULL)
851 0 : usage("--conninfo needs to be specified");
852 :
853 2 : tc.conn = PQconnectdb(tc.conninfo);
854 :
855 2 : if (!tc.conn || PQstatus(tc.conn) != CONNECTION_OK)
856 : {
857 0 : fprintf(stderr, "could not connect: %s\n",
858 0 : PQerrorMessage(tc.conn));
859 0 : exit(1);
860 : }
861 :
862 120 : for (int i = 0; i < lengthof(pe_test_vectors); i++)
863 : {
864 118 : test_one_vector(&tc, &pe_test_vectors[i]);
865 : }
866 :
867 2 : PQfinish(tc.conn);
868 :
869 2 : printf("# %d failures\n", tc.failure_count);
870 2 : printf("1..%d\n", tc.test_count);
871 2 : return tc.failure_count > 0;
872 : }
|