*** a/doc/src/sgml/libpq.sgml
--- b/doc/src/sgml/libpq.sgml
***************
*** 1235,1241 **** PGPing PQping(const char *conninfo);
Several libpq> functions parse a user-specified string to obtain
connection parameters. There are two accepted formats for these strings:
! plain keyword = value strings, and URIs.
--- 1235,1243 ----
Several libpq> functions parse a user-specified string to obtain
connection parameters. There are two accepted formats for these strings:
! plain keyword = value strings,
! and RFC
! 3986 URIs.
***************
*** 1257,1264 **** PGPing PQping(const char *conninfo);
The general form for connection URI is the
following:
! postgresql://[user[:password]@][unix-socket][:port[/dbname]][?param1=value1&...]
! postgresql://[user[:password]@][net-location][:port][/dbname][?param1=value1&...]
--- 1259,1265 ----
The general form for connection URI is the
following:
! postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]
***************
*** 1302,1323 **** postgresql://other@localhost/otherdb
postgresql://[2001:db8::1234]/database
! As a special case, a host part which starts with / is
! treated as a local Unix socket directory to look for the connection
! socket special file:
!
! postgresql:///path/to/pgsql/socket/dir
!
! The whole connection string up to the extra parameters designator
! (?) or the port designator (:) is treated
! as the absolute path to the socket directory
! (/path/to/pgsql/socket/dir in this example.) To specify
! a non-default database name in this case you can use either of the following
! syntaxes:
!
! postgresql:///path/to/pgsql/socket/dir?dbname=otherdb
! postgresql:///path/to/pgsql/socket/dir:5432/otherdb
!
--- 1303,1323 ----
postgresql://[2001:db8::1234]/database
!
!
! If the host part is present, a TCP/IP connection is initiated. If
! the host part is empty, the connection is made by the means of
! local Unix sockets (on platforms which support that.) To specify
! a non-standard local Unix socket directory to look for the
! connection socket special file, omit the host specification in the
! URI and use the optional host parameter (the parameter value
! should start with a slash />, and is treated as the
! absolute path:)
!
! postgresql:///dbname?host=/path/to/pgsql/socket/dir
!
! The database name in above syntax may be also omitted, in this
! case the connection is made to the default database.
*** a/src/interfaces/libpq/fe-connect.c
--- b/src/interfaces/libpq/fe-connect.c
***************
*** 4544,4561 **** conninfo_uri_parse(const char *uri, PQExpBuffer errorMessage,
* options from the URI.
* If not successful, returns false and fills errorMessage accordingly.
*
! * Parses the connection URI string in 'uri' according to the URI syntax:
*
! * postgresql://[user[:pwd]@][unix-socket][:port[/dbname]][?param1=value1&...]
! * postgresql://[user[:pwd]@][net-location][:port][/dbname][?param1=value1&...]
*
! * "net-location" is a hostname, an IPv4 address, or an IPv6 address surrounded
! * by literal square brackets. To be recognized as a unix-domain socket, the
! * value must start with a slash '/'. Note slight inconsistency in that dbname
! * can always be specified after net-location, but after unix-socket it can only
! * be specified if there is a port specification.
*
! * Any of those elements might be percent-encoded (%xy).
*/
static bool
conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
--- 4544,4558 ----
* options from the URI.
* If not successful, returns false and fills errorMessage accordingly.
*
! * Parses the connection URI string in 'uri' according to the URI syntax (RFC
! * 3986):
*
! * postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]
*
! * where "netloc" is a hostname, an IPv4 address, or an IPv6 address surrounded
! * by literal square brackets.
*
! * Any of the URI parts might use percent-encoding (%xy).
*/
static bool
conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
***************
*** 4566,4571 **** conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
--- 4563,4570 ----
char *buf = strdup(uri); /* need a modifiable copy of the input URI */
char *start = buf;
char prevchar = '\0';
+ char *user = NULL;
+ char *host = NULL;
bool retval = false;
if (buf == NULL)
***************
*** 4593,4600 **** conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
++p;
if (*p == '@')
{
- char *user;
-
/*
* Found username/password designator, so URI should be of the form
* "scheme://user[:password]@[netloc]".
--- 4592,4597 ----
***************
*** 4609,4622 **** conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
prevchar = *p;
*p = '\0';
! if (!*user)
! {
! printfPQExpBuffer(errorMessage,
! libpq_gettext("invalid empty username specifier in URI: %s\n"),
! uri);
! goto cleanup;
! }
! if (!conninfo_storeval(options, "user", user,
errorMessage, false, true))
goto cleanup;
--- 4606,4613 ----
prevchar = *p;
*p = '\0';
! if (*user &&
! !conninfo_storeval(options, "user", user,
errorMessage, false, true))
goto cleanup;
***************
*** 4628,4642 **** conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
++p;
*p = '\0';
! if (!*password)
! {
! printfPQExpBuffer(errorMessage,
! libpq_gettext("invalid empty password specifier in URI: %s\n"),
! uri);
! goto cleanup;
! }
!
! if (!conninfo_storeval(options, "password", password,
errorMessage, false, true))
goto cleanup;
}
--- 4619,4626 ----
++p;
*p = '\0';
! if (*password &&
! !conninfo_storeval(options, "password", password,
errorMessage, false, true))
goto cleanup;
}
***************
*** 4656,4742 **** conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
* "p" has been incremented past optional URI credential information at
* this point and now points at the "netloc" part of the URI.
*
! * Check for local unix socket dir.
*/
! if (*p == '/')
{
! const char *socket = p;
!
! /* Look for possible port specifier or query parameters */
! while (*p && *p != ':' && *p != '?')
++p;
! prevchar = *p;
! *p = '\0';
! if (!conninfo_storeval(options, "host", socket,
! errorMessage, false, true))
goto cleanup;
}
else
{
! /* Not a unix socket dir: parse as host name or address */
! const char *host;
/*
! *
! * Look for IPv6 address
*/
! if (*p == '[')
! {
! host = ++p;
! while (*p && *p != ']')
! ++p;
! if (!*p)
! {
! printfPQExpBuffer(errorMessage,
! libpq_gettext("end of string reached when looking for matching ']' in IPv6 host address in URI: %s\n"),
! uri);
! goto cleanup;
! }
! if (p == host)
! {
! printfPQExpBuffer(errorMessage,
! libpq_gettext("IPv6 host address may not be empty in URI: %s\n"),
! uri);
! goto cleanup;
! }
!
! /* Cut off the bracket and advance */
! *(p++) = '\0';
!
! /*
! * The address may be followed by a port specifier or a slash or a
! * query.
! */
! if (*p && *p != ':' && *p != '/' && *p != '?')
! {
! printfPQExpBuffer(errorMessage,
! libpq_gettext("unexpected '%c' at position %d in URI (expecting ':' or '/'): %s\n"),
! *p, (int) (p - buf + 1), uri);
! goto cleanup;
! }
! }
! else
! {
! /* not an IPv6 address: DNS-named or IPv4 netloc */
! host = p;
! /*
! * Look for port specifier (colon) or end of host specifier
! * (slash), or query (question mark).
! */
! while (*p && *p != ':' && *p != '/' && *p != '?')
! ++p;
! }
! /* Save the hostname terminator before we null it */
! prevchar = *p;
! *p = '\0';
- if (!conninfo_storeval(options, "host", host,
- errorMessage, false, true))
- goto cleanup;
- }
if (prevchar == ':')
{
--- 4640,4704 ----
* "p" has been incremented past optional URI credential information at
* this point and now points at the "netloc" part of the URI.
*
! * Look for IPv6 address.
*/
! if (*p == '[')
{
! host = ++p;
! while (*p && *p != ']')
++p;
! if (!*p)
! {
! printfPQExpBuffer(errorMessage,
! libpq_gettext("end of string reached when looking for matching ']' in IPv6 host address in URI: %s\n"),
! uri);
! goto cleanup;
! }
! if (p == host)
! {
! printfPQExpBuffer(errorMessage,
! libpq_gettext("IPv6 host address may not be empty in URI: %s\n"),
! uri);
! goto cleanup;
! }
! /* Cut off the bracket and advance */
! *(p++) = '\0';
!
! /*
! * The address may be followed by a port specifier or a slash or a
! * query.
! */
! if (*p && *p != ':' && *p != '/' && *p != '?')
! {
! printfPQExpBuffer(errorMessage,
! libpq_gettext("unexpected '%c' at position %d in URI (expecting ':' or '/'): %s\n"),
! *p, (int) (p - buf + 1), uri);
goto cleanup;
+ }
}
else
{
! /* not an IPv6 address: DNS-named or IPv4 netloc */
! host = p;
/*
! * Look for port specifier (colon) or end of host specifier
! * (slash), or query (question mark).
*/
! while (*p && *p != ':' && *p != '/' && *p != '?')
! ++p;
! }
! /* Save the hostname terminator before we null it */
! prevchar = *p;
! *p = '\0';
! if (*host &&
! !conninfo_storeval(options, "host", host,
! errorMessage, false, true))
! goto cleanup;
if (prevchar == ':')
{
***************
*** 4748,4761 **** conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
prevchar = *p;
*p = '\0';
! if (!*port)
! {
! printfPQExpBuffer(errorMessage,
! libpq_gettext("missing port specifier in URI: %s\n"),
! uri);
! goto cleanup;
! }
! if (!conninfo_storeval(options, "port", port,
errorMessage, false, true))
goto cleanup;
}
--- 4710,4717 ----
prevchar = *p;
*p = '\0';
! if (*port &&
! !conninfo_storeval(options, "port", port,
errorMessage, false, true))
goto cleanup;
}
***************
*** 4813,4821 **** conninfo_uri_parse_params(char *params,
{
while (*params)
{
! const char *keyword = params;
! const char *value = NULL;
char *p = params;
/*
* Scan the params string for '=' and '&', marking the end of keyword
--- 4769,4778 ----
{
while (*params)
{
! char *keyword = params;
! char *value = NULL;
char *p = params;
+ bool malloced = false;
/*
* Scan the params string for '=' and '&', marking the end of keyword
***************
*** 4866,4900 **** conninfo_uri_parse_params(char *params,
++p;
}
/*
! * Special keyword handling for improved JDBC compatibility. Note
! * we fail to detect URI-encoded values here, but we don't care.
*/
if (strcmp(keyword, "ssl") == 0 &&
strcmp(value, "true") == 0)
{
keyword = "sslmode";
value = "require";
}
/*
* Store the value if the corresponding option exists; ignore
! * otherwise.
*/
if (!conninfo_storeval(connOptions, keyword, value,
! errorMessage, true, true))
{
/*
* Check if there was a hard error when decoding or storing the
* option.
*/
if (errorMessage->len != 0)
return false;
fprintf(stderr,
libpq_gettext("WARNING: ignoring unrecognized URI query parameter: %s\n"),
keyword);
}
/* Proceed to next key=value pair */
params = p;
--- 4823,4888 ----
++p;
}
+ keyword = conninfo_uri_decode(keyword, errorMessage);
+ if (keyword == NULL)
+ {
+ /* conninfo_uri_decode already set an error message */
+ return false;
+ }
+ value = conninfo_uri_decode(value, errorMessage);
+ if (value == NULL)
+ {
+ /* conninfo_uri_decode already set an error message */
+ free(keyword);
+ return false;
+ }
+ malloced = true;
+
/*
! * Special keyword handling for improved JDBC compatibility.
*/
if (strcmp(keyword, "ssl") == 0 &&
strcmp(value, "true") == 0)
{
+ free(keyword);
+ free(value);
+ malloced = false;
+
keyword = "sslmode";
value = "require";
}
/*
* Store the value if the corresponding option exists; ignore
! * otherwise. At this point both keyword and value are not
! * URI-encoded.
*/
if (!conninfo_storeval(connOptions, keyword, value,
! errorMessage, true, false))
{
/*
* Check if there was a hard error when decoding or storing the
* option.
*/
if (errorMessage->len != 0)
+ {
+ if (malloced)
+ {
+ free(keyword);
+ free(value);
+ }
return false;
+ }
fprintf(stderr,
libpq_gettext("WARNING: ignoring unrecognized URI query parameter: %s\n"),
keyword);
}
+ if (malloced)
+ {
+ free(keyword);
+ free(value);
+ }
/* Proceed to next key=value pair */
params = p;
***************
*** 5017,5023 **** conninfo_getval(PQconninfoOption *connOptions,
* Store a (new) value for an option corresponding to the keyword in
* connOptions array.
*
! * If uri_decode is true, keyword and value are URI-decoded.
*
* If successful, returns a pointer to the corresponding PQconninfoOption,
* which value is replaced with a strdup'd copy of the passed value string.
--- 5005,5012 ----
* Store a (new) value for an option corresponding to the keyword in
* connOptions array.
*
! * If uri_decode is true, the value is URI-decoded. The keyword is always
! * assumed to be non URI-encoded.
*
* If successful, returns a pointer to the corresponding PQconninfoOption,
* which value is replaced with a strdup'd copy of the passed value string.
***************
*** 5034,5065 **** conninfo_storeval(PQconninfoOption *connOptions,
bool uri_decode)
{
PQconninfoOption *option;
! char *value_copy;
! char *keyword_copy = NULL;
!
! /*
! * Decode the keyword. XXX this is seldom necessary as keywords do not
! * normally need URI-escaping. It'd be good to do away with the
! * malloc/free overhead and the general ugliness, but I don't see a
! * better way to handle it.
! */
! if (uri_decode)
! {
! keyword_copy = conninfo_uri_decode(keyword, errorMessage);
! if (keyword_copy == NULL)
! /* conninfo_uri_decode already set an error message */
! goto failed;
! }
! option = conninfo_find(connOptions,
! keyword_copy != NULL ? keyword_copy : keyword);
if (option == NULL)
{
if (!ignoreMissing)
printfPQExpBuffer(errorMessage,
libpq_gettext("invalid connection option \"%s\"\n"),
keyword);
! goto failed;
}
if (uri_decode)
--- 5023,5038 ----
bool uri_decode)
{
PQconninfoOption *option;
! char *value_copy;
! option = conninfo_find(connOptions, keyword);
if (option == NULL)
{
if (!ignoreMissing)
printfPQExpBuffer(errorMessage,
libpq_gettext("invalid connection option \"%s\"\n"),
keyword);
! return NULL;
}
if (uri_decode)
***************
*** 5067,5073 **** conninfo_storeval(PQconninfoOption *connOptions,
value_copy = conninfo_uri_decode(value, errorMessage);
if (value_copy == NULL)
/* conninfo_uri_decode already set an error message */
! goto failed;
}
else
{
--- 5040,5046 ----
value_copy = conninfo_uri_decode(value, errorMessage);
if (value_copy == NULL)
/* conninfo_uri_decode already set an error message */
! return NULL;
}
else
{
***************
*** 5076,5082 **** conninfo_storeval(PQconninfoOption *connOptions,
if (value_copy == NULL)
{
printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
! goto failed;
}
}
--- 5049,5055 ----
if (value_copy == NULL)
{
printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
! return NULL;
}
}
***************
*** 5084,5097 **** conninfo_storeval(PQconninfoOption *connOptions,
free(option->val);
option->val = value_copy;
- if (keyword_copy != NULL)
- free(keyword_copy);
return option;
-
- failed:
- if (keyword_copy != NULL)
- free(keyword_copy);
- return NULL;
}
/*
--- 5057,5063 ----
*** a/src/interfaces/libpq/test/expected.out
--- b/src/interfaces/libpq/test/expected.out
***************
*** 20,26 **** trying postgresql://uri-user@host/
user='uri-user' host='host' (inet)
trying postgresql://uri-user@
! user='uri-user' host='' (local)
trying postgresql://host:12345/
host='host' port='12345' (inet)
--- 20,26 ----
user='uri-user' host='host' (inet)
trying postgresql://uri-user@
! user='uri-user' (local)
trying postgresql://host:12345/
host='host' port='12345' (inet)
***************
*** 38,47 **** trying postgresql://host
host='host' (inet)
trying postgresql://
! host='' (local)
trying postgresql://?hostaddr=127.0.0.1
! host='' hostaddr='127.0.0.1' (inet)
trying postgresql://example.com?hostaddr=63.1.2.4
host='example.com' hostaddr='63.1.2.4' (inet)
--- 38,47 ----
host='host' (inet)
trying postgresql://
! (local)
trying postgresql://?hostaddr=127.0.0.1
! hostaddr='127.0.0.1' (inet)
trying postgresql://example.com?hostaddr=63.1.2.4
host='example.com' hostaddr='63.1.2.4' (inet)
***************
*** 59,65 **** trying postgresql://host/db?u%73er=someotheruser&port=12345
user='someotheruser' dbname='db' host='host' port='12345' (inet)
trying postgresql://host/db?u%7aer=someotheruser&port=12345
! WARNING: ignoring unrecognized URI query parameter: u%7aer
dbname='db' host='host' port='12345' (inet)
trying postgresql://host:12345?user=uri-user
--- 59,65 ----
user='someotheruser' dbname='db' host='host' port='12345' (inet)
trying postgresql://host/db?u%7aer=someotheruser&port=12345
! WARNING: ignoring unrecognized URI query parameter: uzer
dbname='db' host='host' port='12345' (inet)
trying postgresql://host:12345?user=uri-user
***************
*** 87,96 **** trying postgresql://[::1]
host='::1' (inet)
trying postgres://
! host='' (local)
! trying postgres:///tmp
! host='/tmp' (local)
trying postgresql://host?uzer=
WARNING: ignoring unrecognized URI query parameter: uzer
--- 87,105 ----
host='::1' (inet)
trying postgres://
! (local)
!
! trying postgres:///
! (local)
!
! trying postgres:///db
! dbname='db' (local)
! trying postgres://uri-user@/db
! user='uri-user' dbname='db' (local)
!
! trying postgres://?host=/path/to/socket/dir
! host='/path/to/socket/dir' (local)
trying postgresql://host?uzer=
WARNING: ignoring unrecognized URI query parameter: uzer
***************
*** 145,163 **** uri-regress: invalid percent-encoded token: %
trying postgres://@host
! uri-regress: invalid empty username specifier in URI: postgres://@host
!
trying postgres://host:/
! uri-regress: missing port specifier in URI: postgres://host:/
! trying postgres://otheruser@/no/such/directory
user='otheruser' host='/no/such/directory' (local)
! trying postgres://otheruser@/no/such/socket/path:12345
user='otheruser' host='/no/such/socket/path' port='12345' (local)
! trying postgres://otheruser@/path/to/socket:12345/db
user='otheruser' dbname='db' host='/path/to/socket' port='12345' (local)
--- 154,182 ----
trying postgres://@host
! host='host' (inet)
trying postgres://host:/
! host='host' (inet)
!
! trying postgres://:12345/
! port='12345' (local)
+ trying postgres://otheruser@?host=/no/such/directory
+ user='otheruser' host='/no/such/directory' (local)
! trying postgres://otheruser@/?host=/no/such/directory
user='otheruser' host='/no/such/directory' (local)
! trying postgres://otheruser@:12345?host=/no/such/socket/path
user='otheruser' host='/no/such/socket/path' port='12345' (local)
! trying postgres://otheruser@:12345/db?host=/path/to/socket
user='otheruser' dbname='db' host='/path/to/socket' port='12345' (local)
+ trying postgres://:12345/db?host=/path/to/socket
+ dbname='db' host='/path/to/socket' port='12345' (local)
+
+ trying postgres://:12345?host=/path/to/socket
+ host='/path/to/socket' port='12345' (local)
+
*** a/src/interfaces/libpq/test/regress.in
--- b/src/interfaces/libpq/test/regress.in
***************
*** 28,34 **** postgresql://[2001:db8::1234]/
postgresql://[200z:db8::1234]/
postgresql://[::1]
postgres://
! postgres:///tmp
postgresql://host?uzer=
postgre://
postgres://[::1
--- 28,37 ----
postgresql://[200z:db8::1234]/
postgresql://[::1]
postgres://
! postgres:///
! postgres:///db
! postgres://uri-user@/db
! postgres://?host=/path/to/socket/dir
postgresql://host?uzer=
postgre://
postgres://[::1
***************
*** 44,49 **** postgresql://%1
postgresql://%
postgres://@host
postgres://host:/
! postgres://otheruser@/no/such/directory
! postgres://otheruser@/no/such/socket/path:12345
! postgres://otheruser@/path/to/socket:12345/db
--- 47,56 ----
postgresql://%
postgres://@host
postgres://host:/
! postgres://:12345/
! postgres://otheruser@?host=/no/such/directory
! postgres://otheruser@/?host=/no/such/directory
! postgres://otheruser@:12345?host=/no/such/socket/path
! postgres://otheruser@:12345/db?host=/path/to/socket
! postgres://:12345/db?host=/path/to/socket
! postgres://:12345?host=/path/to/socket