On 02/24/2012 03:18 PM, Florian Weimer wrote:

* Alex Shulgin:

It's ugly, but it's standard practice, and seems better than a separate
-d parameter (which sort of defeats the purpose of URIs).

Hm, do you see anything what's wrong with "?dbname=other" if you don't
like a separate -d?

It's not nice URI syntax, but it's better than an out-of-band mechanism.

Attached is v5 of the patch, adding support for local Unix socket directory specification w/o the need to percent-encode path separators. The path to directory must start with forward slash, like so:

postgres:///path/to/socket/dir

To specify non-default dbname use URI query parameters:

postgres:///path/to/socket/dir?dbname=other

Username/password should be also specified on query parameters in this case, as opposed to "user:pw@host" syntax supported by host URIs.

--
Alex
*** a/src/interfaces/libpq/fe-connect.c
--- b/src/interfaces/libpq/fe-connect.c
***************
*** 282,287 **** static const PQEnvironmentOption EnvironmentOptions[] =
--- 282,290 ----
  	}
  };
  
+ /* The connection URI must start with either of the following designators: */
+ static const char uri_designator[] = "postgresql://";
+ static const char short_uri_designator[] = "postgres://";
  
  static bool connectOptions1(PGconn *conn, const char *conninfo);
  static bool connectOptions2(PGconn *conn);
***************
*** 297,303 **** static PQconninfoOption *conninfo_parse(const char *conninfo,
  static PQconninfoOption *conninfo_array_parse(const char *const * keywords,
  					 const char *const * values, PQExpBuffer errorMessage,
  					 bool use_defaults, int expand_dbname);
! static char *conninfo_getval(PQconninfoOption *connOptions,
  				const char *keyword);
  static void defaultNoticeReceiver(void *arg, const PGresult *res);
  static void defaultNoticeProcessor(void *arg, const char *message);
--- 300,333 ----
  static PQconninfoOption *conninfo_array_parse(const char *const * keywords,
  					 const char *const * values, PQExpBuffer errorMessage,
  					 bool use_defaults, int expand_dbname);
! static PQconninfoOption *conninfo_uri_parse(const char *uri,
! 				PQExpBuffer errorMessage);
! static bool conninfo_uri_parse_options(PQconninfoOption *options,
! 				const char *uri, char *buf,
! 				PQExpBuffer errorMessage);
! static char *conninfo_uri_parse_local_socket_dir(PQconninfoOption *options,
! 				const char *uri,
! 				char *buf, char *lastc,
! 				PQExpBuffer errorMessage);
! static char *conninfo_uri_parse_remote_host(PQconninfoOption *options,
! 				const char *uri,
! 				char *buf, char *lastc,
! 				PQExpBuffer errorMessage);
! static bool conninfo_uri_parse_params(char *params,
! 				PQconninfoOption *connOptions,
! 				PQExpBuffer errorMessage);
! static char *conninfo_uri_decode(const char *str, PQExpBuffer errorMessage);
! static bool get_hexdigit(char digit, int *value);
! static const char *conninfo_getval(PQconninfoOption *connOptions,
! 				const char *keyword);
! static PQconninfoOption *conninfo_storeval(PQconninfoOption *connOptions,
! 				const char *keyword, const char *value,
! 				PQExpBuffer errorMessage, bool ignoreMissing);
! static PQconninfoOption *conninfo_store_uri_encoded_value(
! 				PQconninfoOption *connOptions,
! 				const char *keyword, const char *encoded_value,
! 				PQExpBuffer errorMessage, bool ignoreMissing);
! static PQconninfoOption *conninfo_find(PQconninfoOption *connOptions,
  				const char *keyword);
  static void defaultNoticeReceiver(void *arg, const PGresult *res);
  static void defaultNoticeProcessor(void *arg, const char *message);
***************
*** 580,586 **** PQconnectStart(const char *conninfo)
  static void
  fillPGconn(PGconn *conn, PQconninfoOption *connOptions)
  {
! 	char	   *tmp;
  
  	/*
  	 * Move option values into conn structure
--- 610,616 ----
  static void
  fillPGconn(PGconn *conn, PQconninfoOption *connOptions)
  {
! 	const char	   *tmp;
  
  	/*
  	 * Move option values into conn structure
***************
*** 3739,3745 **** ldapServiceLookup(const char *purl, PQconninfoOption *options,
  static int
  parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
  {
! 	char	   *service = conninfo_getval(options, "service");
  	char		serviceFile[MAXPGPATH];
  	char	   *env;
  	bool		group_found = false;
--- 3769,3775 ----
  static int
  parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
  {
! 	const char *service = conninfo_getval(options, "service");
  	char		serviceFile[MAXPGPATH];
  	char	   *env;
  	bool		group_found = false;
***************
*** 4129,4161 **** conninfo_parse(const char *conninfo, PQExpBuffer errorMessage,
  		}
  
  		/*
! 		 * Now we have the name and the value. Search for the param record.
! 		 */
! 		for (option = options; option->keyword != NULL; option++)
! 		{
! 			if (strcmp(option->keyword, pname) == 0)
! 				break;
! 		}
! 		if (option->keyword == NULL)
! 		{
! 			printfPQExpBuffer(errorMessage,
! 						 libpq_gettext("invalid connection option \"%s\"\n"),
! 							  pname);
! 			PQconninfoFree(options);
! 			free(buf);
! 			return NULL;
! 		}
! 
! 		/*
! 		 * Store the value
  		 */
! 		if (option->val)
! 			free(option->val);
! 		option->val = strdup(pval);
! 		if (!option->val)
  		{
- 			printfPQExpBuffer(errorMessage,
- 							  libpq_gettext("out of memory\n"));
  			PQconninfoFree(options);
  			free(buf);
  			return NULL;
--- 4159,4168 ----
  		}
  
  		/*
! 		 * Now that we have the name and the value, store the record.
  		 */
! 		if (!conninfo_storeval(options, pname, pval, errorMessage, false))
  		{
  			PQconninfoFree(options);
  			free(buf);
  			return NULL;
***************
*** 4276,4283 **** conninfo_array_parse(const char *const * keywords, const char *const * values,
  		/* first find "dbname" if any */
  		if (strcmp(pname, "dbname") == 0)
  		{
  			/* next look for "=" in the value */
! 			if (pvalue && strchr(pvalue, '='))
  			{
  				/*
  				 * Must be a conninfo string, so parse it, but do not use
--- 4283,4305 ----
  		/* first find "dbname" if any */
  		if (strcmp(pname, "dbname") == 0)
  		{
+ 			/*
+ 			 * Look for URI prefix.  This has to come before the check for "=",
+ 			 * since URI might as well contain "=" if extra parameters are
+ 			 * given.
+ 			 */
+ 			if (pvalue && (
+ 					(strncmp(pvalue, uri_designator,
+ 							 sizeof(uri_designator) - 1) == 0) ||
+ 					(strncmp(pvalue, short_uri_designator,
+ 							 sizeof(short_uri_designator) - 1) == 0)))
+ 			{
+ 				str_options = conninfo_uri_parse(pvalue, errorMessage);
+ 				if (str_options == NULL)
+ 					return NULL;
+ 			}
  			/* next look for "=" in the value */
! 			else if (pvalue && strchr(pvalue, '='))
  			{
  				/*
  				 * Must be a conninfo string, so parse it, but do not use
***************
*** 4452,4467 **** conninfo_array_parse(const char *const * keywords, const char *const * values,
  	return options;
  }
  
  static char *
  conninfo_getval(PQconninfoOption *connOptions,
  				const char *keyword)
  {
  	PQconninfoOption *option;
  
  	for (option = connOptions; option->keyword != NULL; option++)
  	{
  		if (strcmp(option->keyword, keyword) == 0)
! 			return option->val;
  	}
  
  	return NULL;
--- 4474,4995 ----
  	return options;
  }
  
+ /*
+  * Connection URI parser routine
+  *
+  * If successful, a malloc'd PQconninfoOption array is returned.
+  * If not successful, NULL is returned and an error message is
+  * left in errorMessage.
+  *
+  * This is only a wrapper for conninfo_uri_parse_options, which does all of the
+  * heavy lifting, while this function handles proper (de-)allocation of memory
+  * buffers.
+  */
+ static PQconninfoOption *
+ conninfo_uri_parse(const char *uri, PQExpBuffer errorMessage)
+ {
+ 	PQconninfoOption *options;
+ 	char *buf;
+ 
+ 	resetPQExpBuffer(errorMessage);
+ 
+ 	/* Make a working copy of PQconninfoOptions */
+ 	options = malloc(sizeof(PQconninfoOptions));
+ 	if (options == NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
+ 		return NULL;
+ 	}
+ 	memcpy(options, PQconninfoOptions, sizeof(PQconninfoOptions));
+ 
+ 	/* Make a writable copy of the URI buffer */
+ 	buf = strdup(uri);
+ 	if (buf == NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
+ 		free(options);
+ 		return NULL;
+ 	}
+ 
+ 	if (!conninfo_uri_parse_options(options, uri, buf, errorMessage))
+ 	{
+ 		free(options);
+ 		options = NULL;
+ 	}
+ 
+ 	free(buf);
+ 	return options;
+ }
+ 
+ /*
+  * Connection URI parser: actual parser routine
+  *
+  * If successful, returns true while the options array is filled with parsed
+  * options from the passed URI
+  * If not successful, returns false and fills errorMessage accordingly.
+  *
+  * Parses the connection URI string in 'buf' (while destructively modifying
+  * it,) according to the URI syntax below.  The 'uri' parameter is only used
+  * for error reporting and 'buf' should initially contain a writable copy of
+  * 'uri'.
+  *
+  * The general form for connection URI is the following:
+  *
+  *   postgresql://user:pw@host:port/database
+  *
+  * To specify a IPv6 host address, enclose the address in square brackets:
+  *
+  *   postgresql://[::1]/database
+  *
+  * Connection parameters may follow the base URI using this syntax:
+  *
+  *   postgresql://host/database?param1=value1&param2=value2&...
+  */
+ static bool
+ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
+ 						   char *buf, PQExpBuffer errorMessage)
+ {
+ 	char *p = buf;
+ 	char lastc = '\0';
+ 
+ 	/* Assume URI prefix is already verified by the caller */
+ 	if (strncmp(p, uri_designator, sizeof(uri_designator) - 1) == 0)
+ 		p += sizeof(uri_designator) - 1;
+ 	else
+ 		p += sizeof(short_uri_designator) - 1;
+ 
+ 	if (*p == '/')
+ 		p = conninfo_uri_parse_local_socket_dir(options, uri, p, &lastc, errorMessage);
+ 	else
+ 		p = conninfo_uri_parse_remote_host(options, uri, p, &lastc, errorMessage);
+ 	if (!p)
+ 		return false;
+ 
+ 	if (lastc && lastc != '?')
+ 	{
+ 		const char *dbname = ++p; /* advance past host terminator */
+ 
+ 		/* Look for query parameters */
+ 		while (*p && *p != '?')
+ 			++p;
+ 
+ 		lastc = *p;
+ 		*p = '\0';
+ 
+ 		if (!conninfo_store_uri_encoded_value(options, "dbname", dbname,
+ 											  errorMessage, false))
+ 			return false;
+ 	}
+ 
+ 	if (lastc)
+ 	{
+ 		++p; /* advance past terminator */
+ 
+ 		if (!conninfo_uri_parse_params(p, options, errorMessage))
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
  static char *
+ conninfo_uri_parse_local_socket_dir(PQconninfoOption *options, const char *uri,
+ 									char *buf, char *lastc, PQExpBuffer errorMessage)
+ {
+ 	char *p = buf;
+ 
+ 	/* Look for possible query parameters */
+ 	while (*p && *p != '?')
+ 		++p;
+ 	*lastc = *p;
+ 	*p = '\0';
+ 
+ 	if (!conninfo_store_uri_encoded_value(options, "host", buf, errorMessage,
+ 										  false))
+ 		return NULL;
+ 
+ 	return p;
+ }
+ 
+ static char *
+ conninfo_uri_parse_remote_host(PQconninfoOption *options, const char *uri,
+ 							   char *buf, char *lastc, PQExpBuffer errorMessage)
+ {
+ 	const char *host;
+ 	char *p = buf;
+ 
+ 	/* Look ahead for possible user credentials designator */
+ 	while (*p && *p != '@' && *p != '/')
+ 		++p;
+ 	if (*p != '@')
+ 	{
+ 		/* Reset to start of URI and parse as hostname/addr instead */
+ 		p = buf;
+ 	}
+ 	else
+ 	{
+ 		char *user = buf;
+ 
+ 		p = user;
+ 		while (*p != ':' && *p != '@')
+ 			++p;
+ 
+ 		/* Save last char and cut off at end of user name */
+ 		*lastc = *p;
+ 		*p = '\0';
+ 
+ 		if (!conninfo_store_uri_encoded_value(options, "user", user,
+ 											  errorMessage, false))
+ 			return NULL;
+ 
+ 		if (*lastc == ':')
+ 		{
+ 			const char *password = p + 1;
+ 
+ 			while (*p != '@')
+ 				++p;
+ 			*p = '\0';
+ 
+ 			if (!conninfo_store_uri_encoded_value(options, "password", password,
+ 												  errorMessage, false))
+ 				return NULL;
+ 		}
+ 
+ 		/* Advance past end of parsed user name or password token */
+ 		++p;
+ 	}
+ 
+ 	/* 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);
+ 			return NULL;
+ 		}
+ 		if (p == host)
+ 		{
+ 			printfPQExpBuffer(errorMessage,
+ 							  libpq_gettext("IPv6 host address may not be empty in URI: %s\n"),
+ 							  uri);
+ 			return NULL;
+ 		}
+ 
+ 		/* Cut off the bracket and advance */
+ 		*(p++) = '\0';
+ 
+ 		/* 
+ 		 * The address must 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 %td in URI (expecting ':' or '/'): %s\n"),
+ 							  *p, p - buf + 1, uri);
+ 			return NULL;
+ 		}
+ 	}
+ 	else /* no IPv6 address */
+ 	{
+ 		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 */
+ 	*lastc = *p;
+ 	*p = '\0';
+ 
+ 	if (!conninfo_store_uri_encoded_value(options, "host", host, errorMessage,
+ 										  false))
+ 		return NULL;
+ 
+ 	if (*lastc == ':')
+ 	{
+ 		const char *port = ++p; /* advance past host terminator */
+ 
+ 		while (*p && *p != '/' && *p != '?')
+ 			++p;
+ 
+ 		*lastc = *p;
+ 		*p = '\0';
+ 
+ 		if (!conninfo_store_uri_encoded_value(options, "port", port,
+ 											  errorMessage, false))
+ 			return NULL;
+ 	}
+ 
+ 	return p;
+ }
+ 
+ /*
+  * Connection URI parameters parser routine
+  *
+  * If successful, returns true while connOptions is filled with parsed
+  * parameters.
+  * If not successful, returns false and fills errorMessage accordingly.
+  *
+  * Destructively modifies 'params' buffer.
+  */
+ static bool
+ conninfo_uri_parse_params(char *params,
+ 						  PQconninfoOption *connOptions,
+ 						  PQExpBuffer errorMessage)
+ {
+ 	char *token;
+ 	char *savep;
+ 
+ 	while ((token = strtok_r(params, "&", &savep)))
+ 	{
+ 		const char *keyword;
+ 		const char *value;
+ 		char *p = strchr(token, '=');
+ 
+ 		if (p == NULL)
+ 		{
+ 			printfPQExpBuffer(errorMessage,
+ 							  libpq_gettext("missing key/value separator '=' in URI query parameter: %s\n"),
+ 							  token);
+ 			return false;
+ 		}
+ 
+ 		/* Cut off keyword and advance to value */
+ 		*(p++) = '\0';
+ 
+ 		keyword = token;
+ 		value = p;
+ 
+ 		/* Special keyword handling for improved JDBC compatibility */
+ 		if (strcmp(keyword, "ssl") == 0 &&
+ 			strcmp(value, "true") == 0)
+ 		{
+ 			keyword = "sslmode";
+ 			value = "require";
+ 		}
+ 
+ 		/*
+ 		 * Store the value if corresponding option was found -- ignore
+ 		 * otherwise.
+ 		 *
+ 		 * In theory, the keyword might be also percent-encoded, but hardly
+ 		 * that's ever needed by anyone.
+ 		 */
+ 		if (!conninfo_store_uri_encoded_value(connOptions, keyword, value,
+ 											  errorMessage, true))
+ 		{
+ 			fprintf(stderr,
+ 					libpq_gettext("WARNING: ignoring unrecognized URI query parameter: %s\n"),
+ 					keyword);
+ 		}
+ 
+ 		params = NULL; /* proceed to the next token with strtok_r */
+ 	}
+ 
+ 	return true;
+ }
+ 
+ /*
+  * Connection URI decoder routine
+  *
+  * If successful, returns the malloc'd decoded string.
+  * If not successful, returns NULL and fills errorMessage accordingly.
+  *
+  * The string is decoded by replacing any percent-encoded tokens with
+  * corresponding characters, while preserving any non-encoded characters.  A
+  * percent-encoded token is a character triplet: a percent sign, followed by a
+  * pair of hexadecimal digits (0-9A-F), where lower- and upper-case letters are
+  * treated identically.
+  */
+ static char *
+ conninfo_uri_decode(const char *str, PQExpBuffer errorMessage)
+ {
+ 	char *buf = malloc(strlen(str) + 1);
+ 	char *p = buf;
+ 	const char *q = str;
+ 
+ 	if (buf == NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
+ 		return NULL;
+ 	}
+ 
+ 	for (;;)
+ 	{
+ 		if (*q != '%')
+ 		{
+ 			/* copy and check for NUL terminator */
+ 			if (!(*(p++) = *(q++)))
+ 				break;
+ 		}
+ 		else
+ 		{
+ 			int hi;
+ 			int lo;
+ 
+ 			++q; /* skip the percent sign itself */
+ 
+ 			if (!(get_hexdigit(*q++, &hi) && get_hexdigit(*q++, &lo)))
+ 			{
+ 				printfPQExpBuffer(errorMessage,
+ 								  libpq_gettext("invalid percent-encoded token (a pair of hexadecimal digits expected after every '%%' symbol, use '%%25' to encode percent symbol itself): %s\n"),
+ 								  str);
+ 				free(buf);
+ 				return NULL;
+ 			}
+ 
+ 			*(p++) = (hi << 4) | lo;
+ 		}
+ 	}
+ 
+ 	return buf;
+ }
+ 
+ /*
+  * Convert hexadecimal digit character to it's integer value.
+  *
+  * If successful, returns true and value is filled with digit's base 16 value.
+  * If not successful, returns false.
+  *
+  * Lower- and upper-case letters in the range A-F are treated identically.
+  */
+ static bool
+ get_hexdigit(char digit, int *value)
+ {
+ 	if ('0' <= digit && digit <= '9')
+ 	{
+ 		*value = digit - '0';
+ 	}
+ 	else if ('A' <= digit && digit <= 'F')
+ 	{
+ 		*value = digit - 'A' + 10;
+ 	}
+ 	else if ('a' <= digit && digit <= 'f')
+ 	{
+ 		*value = digit - 'a' + 10;
+ 	}
+ 	else
+ 	{
+ 		return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ /*
+  * Find an option value corresponding to the keyword in the connOptions array.
+  *
+  * If successful, returns a pointer to the corresponding option's value.
+  * If not successful, returns NULL.
+  */
+ static const char *
  conninfo_getval(PQconninfoOption *connOptions,
  				const char *keyword)
  {
+ 	PQconninfoOption *option = conninfo_find(connOptions, keyword);
+ 
+ 	return option ? option->val : NULL;
+ }
+ 
+ /*
+  * Store a (new) value for an option corresponding to the keyword in
+  * connOptions array.
+  *
+  * If successful, returns a pointer to the corresponding PQconninfoOption,
+  * which value is replaced with a strdup'd copy of the passed value string.
+  * The existing value for the option is free'd before replacing, if any.
+  *
+  * If not successful, returns NULL and fills errorMessage accordingly.
+  */
+ static PQconninfoOption *
+ conninfo_storeval(PQconninfoOption *connOptions,
+ 				  const char *keyword, const char *value,
+ 				  PQExpBuffer errorMessage, bool ignoreMissing)
+ {
+ 	PQconninfoOption *option = conninfo_find(connOptions, keyword);
+ 	char *value_copy;
+ 
+ 	if (option == NULL)
+ 	{
+ 		if (!ignoreMissing)
+ 			printfPQExpBuffer(errorMessage,
+ 							  libpq_gettext("invalid connection option \"%s\"\n"),
+ 							  keyword);
+ 		return NULL;
+ 	}
+ 
+ 	value_copy = strdup(value);
+ 	if (value_copy == NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
+ 		return NULL;
+ 	}
+ 
+ 	if (option->val)
+ 		free(option->val);
+ 	option->val = value_copy;
+ 
+ 	return option;
+ }
+ 
+ /*
+  * Store a (new) possibly URI-encoded value for an option corresponding to the
+  * keyword in connOptions array.
+  *
+  * If successful, returns a pointer to the corresponding PQconninfoOption,
+  * which value is replaced with a URI-decoded copy of the passed value string.
+  * The existing value for the option is free'd before replacing, if any.
+  *
+  * If not successful, returns NULL and fills errorMessage accordingly.  If the
+  * corresponding option was not found, but ignoreMissing is true: doesn't fill
+  * errorMessage.
+  *
+  * See also conninfo_storeval.
+  */
+ static PQconninfoOption *
+ conninfo_store_uri_encoded_value(PQconninfoOption *connOptions,
+ 								 const char *keyword, const char *encoded_value,
+ 								 PQExpBuffer errorMessage, bool ignoreMissing)
+ {
+ 	PQconninfoOption *option;
+ 	char *decoded_value = conninfo_uri_decode(encoded_value, errorMessage);
+ 
+ 	if (decoded_value == NULL)
+ 		return NULL;
+ 
+ 	option = conninfo_storeval(connOptions, keyword, decoded_value,
+ 							   errorMessage, ignoreMissing);
+ 	free(decoded_value);
+ 
+ 	return option;
+ }
+ 
+ /*
+  * Find a PQconninfoOption option corresponding to the keyword in the
+  * connOptions array.
+  *
+  * If successful, returns a pointer to the corresponding PQconninfoOption
+  * structure.
+  * If not successful, returns NULL.
+  */
+ static PQconninfoOption *
+ conninfo_find(PQconninfoOption *connOptions, const char *keyword)
+ {
  	PQconninfoOption *option;
  
  	for (option = connOptions; option->keyword != NULL; option++)
  	{
  		if (strcmp(option->keyword, keyword) == 0)
! 			return option;
  	}
  
  	return NULL;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to