1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-16 06:01:02 +03:00

libpq: URI parsing fixes

Drop special handling of host component with slashes to mean
Unix-domain socket.  Specify it as separate parameter or using
percent-encoding now.

Allow omitting username, password, and port even if the corresponding
designators are present in URI.

Handle percent-encoding in query parameter keywords.

Alex Shulgin

some documentation improvements by myself
This commit is contained in:
Peter Eisentraut
2012-05-28 22:44:34 +03:00
parent 388d251679
commit 2d612abd4d
4 changed files with 272 additions and 259 deletions

View File

@ -4544,18 +4544,15 @@ 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:
* Parses the connection URI string in 'uri' according to the URI syntax (RFC
* 3986):
*
* postgresql://[user[:pwd]@][unix-socket][:port[/dbname]][?param1=value1&...]
* postgresql://[user[:pwd]@][net-location][:port][/dbname][?param1=value1&...]
* postgresql://[user[:password]@][netloc][: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.
* where "netloc" is a hostname, an IPv4 address, or an IPv6 address surrounded
* by literal square brackets.
*
* Any of those elements might be percent-encoded (%xy).
* Any of the URI parts might use percent-encoding (%xy).
*/
static bool
conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
@ -4566,6 +4563,8 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
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,8 +4592,6 @@ 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]".
@ -4609,14 +4606,8 @@ 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,
if (*user &&
!conninfo_storeval(options, "user", user,
errorMessage, false, true))
goto cleanup;
@ -4628,15 +4619,8 @@ 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,
if (*password &&
!conninfo_storeval(options, "password", password,
errorMessage, false, true))
goto cleanup;
}
@ -4656,88 +4640,66 @@ 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.
* Look for IPv6 address.
*/
if (*p == '/')
if (*p == '[')
{
const char *socket = p;
/* Look for possible port specifier or query parameters */
while (*p && *p != ':' && *p != '?')
host = ++p;
while (*p && *p != ']')
++p;
prevchar = *p;
*p = '\0';
if (!conninfo_storeval(options, "host", socket,
errorMessage, false, true))
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 a unix socket dir: parse as host name or address */
const char *host;
/* not an IPv6 address: DNS-named or IPv4 netloc */
host = p;
/*
*
* Look for IPv6 address
* Look for port specifier (colon) or end of host specifier
* (slash), or query (question mark).
*/
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;
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 == ':')
{
const char *port = ++p; /* advance past host terminator */
@ -4748,14 +4710,8 @@ 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,
if (*port &&
!conninfo_storeval(options, "port", port,
errorMessage, false, true))
goto cleanup;
}
@ -4813,9 +4769,10 @@ conninfo_uri_parse_params(char *params,
{
while (*params)
{
const char *keyword = params;
const char *value = NULL;
char *keyword = params;
char *value = NULL;
char *p = params;
bool malloced = false;
/*
* Scan the params string for '=' and '&', marking the end of keyword
@ -4866,35 +4823,66 @@ conninfo_uri_parse_params(char *params,
++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. Note
* we fail to detect URI-encoded values here, but we don't care.
* 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.
* otherwise. At this point both keyword and value are not
* URI-encoded.
*/
if (!conninfo_storeval(connOptions, keyword, value,
errorMessage, true, true))
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,7 +5005,8 @@ 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 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,32 +5023,16 @@ conninfo_storeval(PQconninfoOption *connOptions,
bool uri_decode)
{
PQconninfoOption *option;
char *value_copy;
char *keyword_copy = NULL;
char *value_copy;
/*
* 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);
option = conninfo_find(connOptions, keyword);
if (option == NULL)
{
if (!ignoreMissing)
printfPQExpBuffer(errorMessage,
libpq_gettext("invalid connection option \"%s\"\n"),
keyword);
goto failed;
return NULL;
}
if (uri_decode)
@ -5067,7 +5040,7 @@ 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;
return NULL;
}
else
{
@ -5076,7 +5049,7 @@ conninfo_storeval(PQconninfoOption *connOptions,
if (value_copy == NULL)
{
printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
goto failed;
return NULL;
}
}
@ -5084,14 +5057,7 @@ 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;
}
/*

View File

@ -20,7 +20,7 @@ trying postgresql://uri-user@host/
user='uri-user' host='host' (inet)
trying postgresql://uri-user@
user='uri-user' host='' (local)
user='uri-user' (local)
trying postgresql://host:12345/
host='host' port='12345' (inet)
@ -38,10 +38,10 @@ trying postgresql://host
host='host' (inet)
trying postgresql://
host='' (local)
(local)
trying postgresql://?hostaddr=127.0.0.1
host='' hostaddr='127.0.0.1' (inet)
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,7 +59,7 @@ 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
WARNING: ignoring unrecognized URI query parameter: uzer
dbname='db' host='host' port='12345' (inet)
trying postgresql://host:12345?user=uri-user
@ -87,10 +87,19 @@ trying postgresql://[::1]
host='::1' (inet)
trying postgres://
host='' (local)
(local)
trying postgres:///tmp
host='/tmp' (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,19 +154,32 @@ uri-regress: invalid percent-encoded token: %
trying postgres://@host
uri-regress: invalid empty username specifier in URI: postgres://@host
host='host' (inet)
trying postgres://host:/
uri-regress: missing port specifier in URI: postgres://host:/
host='host' (inet)
trying postgres://:12345/
port='12345' (local)
trying postgres://otheruser@/no/such/directory
trying postgres://otheruser@?host=/no/such/directory
user='otheruser' host='/no/such/directory' (local)
trying postgres://otheruser@/no/such/socket/path:12345
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@/path/to/socket:12345/db
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)
trying postgres://%2Fvar%2Flib%2Fpostgresql/dbname
dbname='dbname' host='/var/lib/postgresql' (local)

View File

@ -28,7 +28,10 @@ postgresql://[2001:db8::1234]/
postgresql://[200z:db8::1234]/
postgresql://[::1]
postgres://
postgres:///tmp
postgres:///
postgres:///db
postgres://uri-user@/db
postgres://?host=/path/to/socket/dir
postgresql://host?uzer=
postgre://
postgres://[::1
@ -44,6 +47,11 @@ 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
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
postgres://%2Fvar%2Flib%2Fpostgresql/dbname