LCOV - code coverage report
Current view: top level - src/test/modules/test_escape - test_escape.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 211 257 82.1 %
Date: 2025-04-24 12:15:10 Functions: 15 16 93.8 %
Legend: Lines: hit not hit

          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             : }

Generated by: LCOV version 1.14