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