Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions docs/config.html
Original file line number Diff line number Diff line change
Expand Up @@ -317,5 +317,59 @@ <h2>How to specify as a connection option</h2>
</ul>
<p>Please refer to a <a href="config-opt.html">keyword list</a> for details. </p>

<h2>Special characters and escaping in connection strings</h2>

<p>A value that contains characters with a special meaning
(<code>;</code>, <code>=</code>, spaces or braces) must be escaped.
It is important to understand that a connection string is processed by
<b>two independent parsers</b>, each with its own escaping rules.</p>

<h3>1. The psqlODBC connection string</h3>

<p>The driver splits the keyword string on <code>;</code> (the attribute
delimiter) and on the first <code>=</code> of each attribute. To include one
of these characters, or a space, in a value, use one of the following:</p>

<ul>
<li><b>Braces:</b> enclose the whole value in <code>{ }</code>. A braced
value may contain <code>;</code>, <code>=</code> and spaces. For example
<b>PWD={my;odd=pass word}</b>. To include a literal closing brace
<code>}</code> inside a braced value, double it as <code>}}</code>.
<br />&nbsp;</li>

<li><b>Percent-encoding:</b> outside of braces the value is URL-decoded, so a
space can be written as <code>+</code> or <code>%20</code>, and an arbitrary
byte as <code>%<i>XX</i></code> (two hex digits). For example
<b>PWD=pass+word</b> or <b>PWD=pass%20word</b>.
<br />&nbsp;</li>
</ul>

<p>A backslash (<code>\</code>) has <b>no special meaning</b> in the
psqlODBC connection string itself; it is passed through literally.</p>

<h3>2. The Libpq parameters (pqopt) value</h3>

<p>The <b>Libpq parameters</b> option (see Advanced Options 3/3 above) is
passed on to libpq and parsed by it as a <a
href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING">conninfo
string</a>, which has its own, different rules. Within this value libpq
separates parameters by spaces, so to include a space (or other special
character) in a libpq parameter value you use libpq's escaping:</p>

<ul>
<li><b>Backslash:</b> <code>\</code> escapes the following character, e.g.
<b>sslcert=C:\ my\ folder\ cert</b>.
<br />&nbsp;</li>
<li><b>Single quotes:</b> enclose the value in <code>' '</code>, e.g.
<b>sslcert='C:\my folder\cert'</b>. A literal single quote or backslash
inside quotes is written as <code>\'</code> and <code>\\</code>.
<br />&nbsp;</li>
</ul>

<p>This is why, in the VBA example above, the libpq paths inside the
<code>D5={...}</code> braces use backslashes: the braces are consumed by the
psqlODBC parser (layer 1), and the contents are then handed to libpq
(layer 2). The two layers can therefore both apply to the same value.</p>

</body>
</html>
3 changes: 3 additions & 0 deletions test/expected/connstring-escape.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
braces protect semicolon: ok
libpq backslash escape: ok
libpq single-quote quoting: ok
118 changes: 118 additions & 0 deletions test/src/connstring-escape-test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Test escaping of special characters in connection-string values.
*
* A connection string is processed by two independent parsers with
* different escaping rules (see docs/config.html):
*
* 1. The psqlODBC parser splits on ';' and '=', and uses braces {} to
* protect a value that contains those characters or spaces.
* 2. The braced pqopt value is then handed to libpq, whose conninfo
* parser separates parameters by spaces and uses backslash and
* single-quote quoting to embed a space in a value.
*
* Each case below sets application_name through one of these mechanisms and
* reads it back with current_setting() to confirm the special character
* survived intact.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "common.h"

static void
check_application_name(const char *label, const char *pqopt_value,
const char *expected)
{
SQLRETURN rc;
SQLHENV henv = SQL_NULL_HENV;
SQLHDBC hdbc = SQL_NULL_HDBC;
SQLHSTMT hstmt = SQL_NULL_HSTMT;
SQLCHAR outstr[256];
SQLSMALLINT outlen = 0;
SQLCHAR appname[256];
SQLLEN ind = 0;
char connstr[1024];

/*
* The braces protect the whole libpq value at the psqlODBC layer; the
* caller supplies the libpq-level escaping inside the braces.
*/
snprintf(connstr, sizeof(connstr),
"DSN=%s;pqopt={application_name=%s}",
get_test_dsn(), pqopt_value);

rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
if (!SQL_SUCCEEDED(rc))
{
fprintf(stderr, "SQLAllocHandle(SQL_HANDLE_ENV) failed\n");
exit(1);
}

rc = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER) SQL_OV_ODBC3, 0);
if (!SQL_SUCCEEDED(rc))
{
print_diag("SQLSetEnvAttr failed", SQL_HANDLE_ENV, henv);
exit(1);
}

rc = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
if (!SQL_SUCCEEDED(rc))
{
print_diag("SQLAllocHandle(SQL_HANDLE_DBC) failed",
SQL_HANDLE_ENV, henv);
exit(1);
}

rc = SQLDriverConnect(hdbc, NULL, (SQLCHAR *) connstr, SQL_NTS,
outstr, sizeof(outstr), &outlen,
SQL_DRIVER_NOPROMPT);
if (!SQL_SUCCEEDED(rc))
{
print_diag("SQLDriverConnect failed", SQL_HANDLE_DBC, hdbc);
exit(1);
}

rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
CHECK_CONN_RESULT(rc, "SQLAllocHandle(SQL_HANDLE_STMT) failed", hdbc);

rc = SQLExecDirect(hstmt,
(SQLCHAR *) "SELECT current_setting('application_name')",
SQL_NTS);
CHECK_STMT_RESULT(rc, "SQLExecDirect failed", hstmt);

rc = SQLFetch(hstmt);
CHECK_STMT_RESULT(rc, "SQLFetch failed", hstmt);

rc = SQLGetData(hstmt, 1, SQL_C_CHAR, appname, sizeof(appname), &ind);
CHECK_STMT_RESULT(rc, "SQLGetData failed", hstmt);

if (strcmp((char *) appname, expected) != 0)
{
printf("%s: FAILED - expected [%s], got [%s]\n",
label, expected, (char *) appname);
exit(1);
}
printf("%s: ok\n", label);

SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
SQLDisconnect(hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
SQLFreeHandle(SQL_HANDLE_ENV, henv);
}

int
main(void)
{
/* psqlODBC layer: braces protect the ';' attribute delimiter */
check_application_name("braces protect semicolon", "a;b", "a;b");

/* libpq layer: backslash escapes a space inside the value */
check_application_name("libpq backslash escape", "my\\ app", "my app");

/* libpq layer: single quotes quote a value containing a space */
check_application_name("libpq single-quote quoting", "'my app'", "my app");

return 0;
}
1 change: 1 addition & 0 deletions test/tests
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,6 @@ TESTBINS = exe/connect-test \
exe/interval-overflow-test \
exe/conn-settings-test \
exe/percent-decode-test \
exe/connstring-escape-test \
exe/dbms-version-test \
exe/surrogate-pair-test
Loading