*** 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