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-02-22 07:14:56 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 :         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             : }

Generated by: LCOV version 1.14