diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 0ec501e5bd..8a820ac007 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -116,10 +116,16 @@ PGconn *PQconnectdbParams(const char * const *keywords, programming. + + The currently recognized parameter key words are listed in + . + + When expand_dbname is non-zero, the dbname key word value is allowed to be recognized - as a conninfo string. See below for details. + as a connection string. More details on the possible formats appear in + . @@ -130,507 +136,6 @@ PGconn *PQconnectdbParams(const char * const *keywords, - The currently recognized parameter key words are: - - - - host - - - Name of host to connect to.host name - If this begins with a slash, it specifies Unix-domain - communication rather than TCP/IP communication; the value is the - name of the directory in which the socket file is stored. The - default behavior when host is not specified - is to connect to a Unix-domain - socketUnix domain socket in - /tmp (or whatever socket directory was specified - when PostgreSQL was built). On machines without - Unix-domain sockets, the default is to connect to localhost. - - - - - - hostaddr - - - Numeric IP address of host to connect to. This should be in the - standard IPv4 address format, e.g., 172.28.40.9. If - your machine supports IPv6, you can also use those addresses. - TCP/IP communication is - always used when a nonempty string is specified for this parameter. - - - - Using hostaddr instead of host allows the - application to avoid a host name look-up, which might be important - in applications with time constraints. However, a host name is - required for Kerberos, GSSAPI, or SSPI authentication - methods, as well as for verify-full SSL - certificate verification. The following rules are used: - - - - If host is specified without hostaddr, - a host name lookup occurs. - - - - - If hostaddr is specified without host, - the value for hostaddr gives the server network address. - The connection attempt will fail if the authentication - method requires a host name. - - - - - If both host and hostaddr are specified, - the value for hostaddr gives the server network address. - The value for host is ignored unless the - authentication method requires it, in which case it will be - used as the host name. - - - - Note that authentication is likely to fail if host - is not the name of the server at network address hostaddr. - Also, note that host rather than hostaddr - is used to identify the connection in ~/.pgpass (see - ). - - - - Without either a host name or host address, - libpq will connect using a - local Unix-domain socket; or on machines without Unix-domain - sockets, it will attempt to connect to localhost. - - - - - - port - - - Port number to connect to at the server host, or socket file - name extension for Unix-domain - connections.port - - - - - - dbname - - - The database name. Defaults to be the same as the user name. - - - - - - user - - - PostgreSQL user name to connect as. - Defaults to be the same as the operating system name of the user - running the application. - - - - - - password - - - Password to be used if the server demands password authentication. - - - - - - connect_timeout - - - Maximum wait for connection, in seconds (write as a decimal integer - string). Zero or not specified means wait indefinitely. It is not - recommended to use a timeout of less than 2 seconds. - - - - - - client_encoding - - - This sets the client_encoding - configuration parameter for this connection. In addition to - the values accepted by the corresponding server option, you - can use auto to determine the right - encoding from the current locale in the client - (LC_CTYPE environment variable on Unix - systems). - - - - - - options - - - Adds command-line options to send to the server at run-time. - For example, setting this to -c geqo=off sets the - session's value of the geqo parameter to - off. For a detailed discussion of the available - options, consult . - - - - - - application_name - - - Specifies a value for the - configuration parameter. - - - - - - fallback_application_name - - - Specifies a fallback value for the configuration parameter. - This value will be used if no value has been given for - application_name via a connection parameter or the - PGAPPNAME environment variable. Specifying - a fallback name is useful in generic utility programs that - wish to set a default application name but allow it to be - overridden by the user. - - - - - - keepalives - - - Controls whether client-side TCP keepalives are used. The default - value is 1, meaning on, but you can change this to 0, meaning off, - if keepalives are not wanted. This parameter is ignored for - connections made via a Unix-domain socket. - - - - - - keepalives_idle - - - Controls the number of seconds of inactivity after which TCP should - send a keepalive message to the server. A value of zero uses the - system default. This parameter is ignored for connections made via a - Unix-domain socket, or if keepalives are disabled. It is only supported - on systems where the TCP_KEEPIDLE or TCP_KEEPALIVE - socket option is available, and on Windows; on other systems, it has no - effect. - - - - - - keepalives_interval - - - Controls the number of seconds after which a TCP keepalive message - that is not acknowledged by the server should be retransmitted. A - value of zero uses the system default. This parameter is ignored for - connections made via a Unix-domain socket, or if keepalives are disabled. - It is only supported on systems where the TCP_KEEPINTVL - socket option is available, and on Windows; on other systems, it has no - effect. - - - - - - keepalives_count - - - Controls the number of TCP keepalives that can be lost before the - client's connection to the server is considered dead. A value of - zero uses the system default. This parameter is ignored for - connections made via a Unix-domain socket, or if keepalives are disabled. - It is only supported on systems where the TCP_KEEPCNT - socket option is available; on other systems, it has no effect. - - - - - - tty - - - Ignored (formerly, this specified where to send server debug output). - - - - - - sslmode - - - This option determines whether or with what priority a secure - SSL TCP/IP connection will be negotiated with the - server. There are six modes: - - - - disable - - - only try a non-SSL connection - - - - - - allow - - - first try a non-SSL connection; if that - fails, try an SSL connection - - - - - - prefer (default) - - - first try an SSL connection; if that fails, - try a non-SSL connection - - - - - - require - - - only try an SSL connection. If a root CA - file is present, verify the certificate in the same way as - if verify-ca was specified - - - - - - verify-ca - - - only try an SSL connection, and verify that - the server certificate is issued by a trusted - certificate authority (CA) - - - - - - verify-full - - - only try an SSL connection, verify that the - server certificate is issued by a - trusted CA and that the server host name - matches that in the certificate - - - - - - See for a detailed description of how - these options work. - - - - sslmode is ignored for Unix domain socket - communication. - If PostgreSQL is compiled without SSL support, - using options require, verify-ca, or - verify-full will cause an error, while - options allow and prefer will be - accepted but libpq will not actually attempt - an SSL - connection.SSLwith libpq - - - - - - requiressl - - - This option is deprecated in favor of the sslmode - setting. - - - - If set to 1, an SSL connection to the server - is required (this is equivalent to sslmode - require). libpq will then refuse - to connect if the server does not accept an - SSL connection. If set to 0 (default), - libpq will negotiate the connection type with - the server (equivalent to sslmode - prefer). This option is only available if - PostgreSQL is compiled with SSL support. - - - - - - sslcompression - - - If set to 1 (default), data sent over SSL connections will be - compressed (this requires OpenSSL version - 0.9.8 or later). - If set to 0, compression will be disabled (this requires - OpenSSL 1.0.0 or later). - This parameter is ignored if a connection without SSL is made, - or if the version of OpenSSL used does not support - it. - - - Compression uses CPU time, but can improve throughput if - the network is the bottleneck. - Disabling compression can improve response time and throughput - if CPU performance is the limiting factor. - - - - - - sslcert - - - This parameter specifies the file name of the client SSL - certificate, replacing the default - ~/.postgresql/postgresql.crt. - This parameter is ignored if an SSL connection is not made. - - - - - - sslkey - - - This parameter specifies the location for the secret key used for - the client certificate. It can either specify a file name that will - be used instead of the default - ~/.postgresql/postgresql.key, or it can specify a key - obtained from an external engine (engines are - OpenSSL loadable modules). An external engine - specification should consist of a colon-separated engine name and - an engine-specific key identifier. This parameter is ignored if an - SSL connection is not made. - - - - - - sslrootcert - - - This parameter specifies the name of a file containing SSL - certificate authority (CA) certificate(s). - If the file exists, the server's certificate will be verified - to be signed by one of these authorities. The default is - ~/.postgresql/root.crt. - - - - - - sslcrl - - - This parameter specifies the file name of the SSL certificate - revocation list (CRL). Certificates listed in this file, if it - exists, will be rejected while attempting to authenticate the - server's certificate. The default is - ~/.postgresql/root.crl. - - - - - - requirepeer - - - This parameter specifies the operating-system user name of the - server, for example requirepeer=postgres. - When making a Unix-domain socket connection, if this - parameter is set, the client checks at the beginning of the - connection that the server process is running under the specified - user name; if it is not, the connection is aborted with an error. - This parameter can be used to provide server authentication similar - to that available with SSL certificates on TCP/IP connections. - (Note that if the Unix-domain socket is in - /tmp or another publicly writable location, - any user could start a server listening there. Use this parameter - to ensure that you are connected to a server run by a trusted user.) - This option is only supported on platforms for which the - peer authentication method is implemented; see - . - - - - - - krbsrvname - - - Kerberos service name to use when authenticating with Kerberos 5 - or GSSAPI. - This must match the service name specified in the server - configuration for Kerberos authentication to succeed. (See also - and .) - - - - - - gsslib - - - GSS library to use for GSSAPI authentication. Only used on Windows. - Set to gssapi to force libpq to use the GSSAPI - library for authentication instead of the default SSPI. - - - - - - service - - - Service name to use for additional parameters. It specifies a service - name in pg_service.conf that holds additional connection parameters. - This allows applications to specify only a service name so connection parameters - can be centrally maintained. See . - - - - - If any parameter is unspecified, then the corresponding environment variable (see ) is checked. If the environment variable is not set either, @@ -638,20 +143,11 @@ PGconn *PQconnectdbParams(const char * const *keywords, - If expand_dbname is non-zero and - dbname contains an = sign, it - is taken as a conninfo string in exactly the same way as - if it had been passed to PQconnectdb(see below). Previously - processed key words will be overridden by key words in the - conninfo string. - - - - In general key words are processed from the beginning of these arrays in index - order. The effect of this is that when key words are repeated, the last processed - value is retained. Therefore, through careful placement of the - dbname key word, it is possible to determine what may - be overridden by a conninfo string, and what may not. + In general key words are processed from the beginning of these arrays in index + order. The effect of this is that when key words are repeated, the last processed + value is retained. Therefore, through careful placement of the + dbname key word, it is possible to determine what may + be overridden by a conninfo string, and what may not. @@ -675,19 +171,13 @@ PGconn *PQconnectdb(const char *conninfo); The passed string can be empty to use all default parameters, or it can - contain one or more parameter settings separated by whitespace. - Each parameter setting is in the form keyword = value. - Spaces around the equal sign are optional. To write an empty value, - or a value containing spaces, surround it with single quotes, e.g., - keyword = 'a value'. Single quotes and backslashes - within the value must be escaped with a backslash, i.e., - \' and \\. - + contain one or more parameter settings separated by whitespace, + or it can contain a URI. + See for details. + - - The currently recognized parameter key words are the same as above. - - + + @@ -714,10 +204,11 @@ PGconn *PQsetdbLogin(const char *pghost, - If the dbName contains an = sign, it + If the dbName contains + an = sign or has a valid connection URI prefix, it is taken as a conninfo string in exactly the same way as if it had been passed to PQconnectdb, and the remaining - parameters are then applied as above. + parameters are then applied as specified for PQconnectdbParams. @@ -795,7 +286,7 @@ PostgresPollingStatusType PQconnectPoll(PGconn *conn); The hostaddr and host parameters are used appropriately to ensure that name and reverse name queries are not made. See the documentation of - these parameters under PQconnectdbParams above for details. + these parameters in for details. @@ -1219,6 +710,617 @@ PGPing PQping(const char *conninfo); + + + Parameter Key Words + + + The currently recognized parameter key words are: + + + + host + + + Name of host to connect to.host name + If this begins with a slash, it specifies Unix-domain + communication rather than TCP/IP communication; the value is the + name of the directory in which the socket file is stored. The + default behavior when host is not specified + is to connect to a Unix-domain + socketUnix domain socket in + /tmp (or whatever socket directory was specified + when PostgreSQL was built). On machines without + Unix-domain sockets, the default is to connect to localhost. + + + + + + hostaddr + + + Numeric IP address of host to connect to. This should be in the + standard IPv4 address format, e.g., 172.28.40.9. If + your machine supports IPv6, you can also use those addresses. + TCP/IP communication is + always used when a nonempty string is specified for this parameter. + + + + Using hostaddr instead of host allows the + application to avoid a host name look-up, which might be important + in applications with time constraints. However, a host name is + required for Kerberos, GSSAPI, or SSPI authentication + methods, as well as for verify-full SSL + certificate verification. The following rules are used: + + + + If host is specified without hostaddr, + a host name lookup occurs. + + + + + If hostaddr is specified without host, + the value for hostaddr gives the server network address. + The connection attempt will fail if the authentication + method requires a host name. + + + + + If both host and hostaddr are specified, + the value for hostaddr gives the server network address. + The value for host is ignored unless the + authentication method requires it, in which case it will be + used as the host name. + + + + Note that authentication is likely to fail if host + is not the name of the server at network address hostaddr. + Also, note that host rather than hostaddr + is used to identify the connection in ~/.pgpass (see + ). + + + + Without either a host name or host address, + libpq will connect using a + local Unix-domain socket; or on machines without Unix-domain + sockets, it will attempt to connect to localhost. + + + + + + port + + + Port number to connect to at the server host, or socket file + name extension for Unix-domain + connections.port + + + + + + dbname + + + The database name. Defaults to be the same as the user name. + In certain contexts, the value is checked for extended + formats; see for more details on + those. + + + + + + user + + + PostgreSQL user name to connect as. + Defaults to be the same as the operating system name of the user + running the application. + + + + + + password + + + Password to be used if the server demands password authentication. + + + + + + connect_timeout + + + Maximum wait for connection, in seconds (write as a decimal integer + string). Zero or not specified means wait indefinitely. It is not + recommended to use a timeout of less than 2 seconds. + + + + + + client_encoding + + + This sets the client_encoding + configuration parameter for this connection. In addition to + the values accepted by the corresponding server option, you + can use auto to determine the right + encoding from the current locale in the client + (LC_CTYPE environment variable on Unix + systems). + + + + + + options + + + Adds command-line options to send to the server at run-time. + For example, setting this to -c geqo=off sets the + session's value of the geqo parameter to + off. For a detailed discussion of the available + options, consult . + + + + + + application_name + + + Specifies a value for the + configuration parameter. + + + + + + fallback_application_name + + + Specifies a fallback value for the configuration parameter. + This value will be used if no value has been given for + application_name via a connection parameter or the + PGAPPNAME environment variable. Specifying + a fallback name is useful in generic utility programs that + wish to set a default application name but allow it to be + overridden by the user. + + + + + + keepalives + + + Controls whether client-side TCP keepalives are used. The default + value is 1, meaning on, but you can change this to 0, meaning off, + if keepalives are not wanted. This parameter is ignored for + connections made via a Unix-domain socket. + + + + + + keepalives_idle + + + Controls the number of seconds of inactivity after which TCP should + send a keepalive message to the server. A value of zero uses the + system default. This parameter is ignored for connections made via a + Unix-domain socket, or if keepalives are disabled. It is only supported + on systems where the TCP_KEEPIDLE or TCP_KEEPALIVE + socket option is available, and on Windows; on other systems, it has no + effect. + + + + + + keepalives_interval + + + Controls the number of seconds after which a TCP keepalive message + that is not acknowledged by the server should be retransmitted. A + value of zero uses the system default. This parameter is ignored for + connections made via a Unix-domain socket, or if keepalives are disabled. + It is only supported on systems where the TCP_KEEPINTVL + socket option is available, and on Windows; on other systems, it has no + effect. + + + + + + keepalives_count + + + Controls the number of TCP keepalives that can be lost before the + client's connection to the server is considered dead. A value of + zero uses the system default. This parameter is ignored for + connections made via a Unix-domain socket, or if keepalives are disabled. + It is only supported on systems where the TCP_KEEPCNT + socket option is available; on other systems, it has no effect. + + + + + + tty + + + Ignored (formerly, this specified where to send server debug output). + + + + + + sslmode + + + This option determines whether or with what priority a secure + SSL TCP/IP connection will be negotiated with the + server. There are six modes: + + + + disable + + + only try a non-SSL connection + + + + + + allow + + + first try a non-SSL connection; if that + fails, try an SSL connection + + + + + + prefer (default) + + + first try an SSL connection; if that fails, + try a non-SSL connection + + + + + + require + + + only try an SSL connection. If a root CA + file is present, verify the certificate in the same way as + if verify-ca was specified + + + + + + verify-ca + + + only try an SSL connection, and verify that + the server certificate is issued by a trusted + certificate authority (CA) + + + + + + verify-full + + + only try an SSL connection, verify that the + server certificate is issued by a + trusted CA and that the server host name + matches that in the certificate + + + + + + See for a detailed description of how + these options work. + + + + sslmode is ignored for Unix domain socket + communication. + If PostgreSQL is compiled without SSL support, + using options require, verify-ca, or + verify-full will cause an error, while + options allow and prefer will be + accepted but libpq will not actually attempt + an SSL + connection.SSLwith libpq + + + + + + requiressl + + + This option is deprecated in favor of the sslmode + setting. + + + + If set to 1, an SSL connection to the server + is required (this is equivalent to sslmode + require). libpq will then refuse + to connect if the server does not accept an + SSL connection. If set to 0 (default), + libpq will negotiate the connection type with + the server (equivalent to sslmode + prefer). This option is only available if + PostgreSQL is compiled with SSL support. + + + + + + sslcompression + + + If set to 1 (default), data sent over SSL connections will be + compressed (this requires OpenSSL version + 0.9.8 or later). + If set to 0, compression will be disabled (this requires + OpenSSL 1.0.0 or later). + This parameter is ignored if a connection without SSL is made, + or if the version of OpenSSL used does not support + it. + + + Compression uses CPU time, but can improve throughput if + the network is the bottleneck. + Disabling compression can improve response time and throughput + if CPU performance is the limiting factor. + + + + + + sslcert + + + This parameter specifies the file name of the client SSL + certificate, replacing the default + ~/.postgresql/postgresql.crt. + This parameter is ignored if an SSL connection is not made. + + + + + + sslkey + + + This parameter specifies the location for the secret key used for + the client certificate. It can either specify a file name that will + be used instead of the default + ~/.postgresql/postgresql.key, or it can specify a key + obtained from an external engine (engines are + OpenSSL loadable modules). An external engine + specification should consist of a colon-separated engine name and + an engine-specific key identifier. This parameter is ignored if an + SSL connection is not made. + + + + + + sslrootcert + + + This parameter specifies the name of a file containing SSL + certificate authority (CA) certificate(s). + If the file exists, the server's certificate will be verified + to be signed by one of these authorities. The default is + ~/.postgresql/root.crt. + + + + + + sslcrl + + + This parameter specifies the file name of the SSL certificate + revocation list (CRL). Certificates listed in this file, if it + exists, will be rejected while attempting to authenticate the + server's certificate. The default is + ~/.postgresql/root.crl. + + + + + + requirepeer + + + This parameter specifies the operating-system user name of the + server, for example requirepeer=postgres. + When making a Unix-domain socket connection, if this + parameter is set, the client checks at the beginning of the + connection that the server process is running under the specified + user name; if it is not, the connection is aborted with an error. + This parameter can be used to provide server authentication similar + to that available with SSL certificates on TCP/IP connections. + (Note that if the Unix-domain socket is in + /tmp or another publicly writable location, + any user could start a server listening there. Use this parameter + to ensure that you are connected to a server run by a trusted user.) + This option is only supported on platforms for which the + peer authentication method is implemented; see + . + + + + + + krbsrvname + + + Kerberos service name to use when authenticating with Kerberos 5 + or GSSAPI. + This must match the service name specified in the server + configuration for Kerberos authentication to succeed. (See also + and .) + + + + + + gsslib + + + GSS library to use for GSSAPI authentication. Only used on Windows. + Set to gssapi to force libpq to use the GSSAPI + library for authentication instead of the default SSPI. + + + + + + service + + + Service name to use for additional parameters. It specifies a service + name in pg_service.conf that holds additional connection parameters. + This allows applications to specify only a service name so connection parameters + can be centrally maintained. See . + + + + + + + + + Connection Strings + + + conninfo + + + + URI + + + + 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. + + + + In the first format, each parameter setting is in the form + keyword = value. Spaces around the equal sign are + optional. To write an empty value, or a value containing spaces, surround it + with single quotes, e.g., keyword = 'a value'. Single + quotes and backslashes within + the value must be escaped with a backslash, i.e., \' and + \\. + + + + The currently recognized parameter key words are listed in + . + + + + 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&...] + + + + + The URI designator can be either + postgresql:// or postgres:// and + each of the URI parts is optional. The following + examples illustrate valid URI syntax uses: + +postgresql:// +postgresql://localhost +postgresql://localhost:5433 +postgresql://localhost/mydb +postgresql://user@localhost +postgresql://user:secret@localhost +postgresql://other@localhost/otherdb + + + + + Percent-encoding may be used to include a symbol with special meaning in + any of the URI parts. + + + + Additional connection parameters may optionally follow the base URI. + Any connection parameters not corresponding to key words listed + in are ignored and a warning message + about them is sent to stderr. + + + + For improved compatibility with JDBC connection URI + syntax, instances of parameter ssl=true are translated + into sslmode=require (see above.) + + + + The host part may be either hostname or an IP address. To specify an + IPv6 host address, enclose it in square brackets: + +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 + + + + diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index b8491015f4..bdcadf3692 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -115,7 +115,10 @@ PostgreSQL documentation argument on the command line. - If this parameter contains an = sign, it is treated as a + If this parameter contains an = sign or starts + with a valid URI prefix + (postgresql:// + or postgres://), it is treated as a conninfo string. See for more information. @@ -596,11 +599,13 @@ PostgreSQL documentation An alternative way to specify connection parameters is in a - conninfo string, which is used instead of a - database name. This mechanism give you very wide control over the + conninfo string or + a URI, which is used instead of a database + name. This mechanism give you very wide control over the connection. For example: $ psql "service=myservice sslmode=require" +$ psql postgresql://dbmaster:5433/mydb?sslmode=require This way you can also use LDAP for connection parameter lookup as described in . diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index d1ded1f0e5..ec4fdd403e 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -121,6 +121,9 @@ install: all installdirs install-lib $(INSTALL_DATA) $(srcdir)/pqexpbuffer.h '$(DESTDIR)$(includedir_internal)' $(INSTALL_DATA) $(srcdir)/pg_service.conf.sample '$(DESTDIR)$(datadir)/pg_service.conf.sample' +installcheck: + $(MAKE) -C test $@ + installdirs: installdirs-lib $(MKDIR_P) '$(DESTDIR)$(includedir)' '$(DESTDIR)$(includedir_internal)' @@ -132,6 +135,7 @@ uninstall: uninstall-lib rm -f '$(DESTDIR)$(datadir)/pg_service.conf.sample' clean distclean: clean-lib + $(MAKE) -C test $@ rm -f $(OBJS) pthread.h libpq.rc # Might be left over from a Win32 client-only build rm -f pg_config_paths.h @@ -142,4 +146,5 @@ clean distclean: clean-lib rm -f encnames.c wchar.c maintainer-clean: distclean maintainer-clean-lib + $(MAKE) -C test $@ rm -f libpq-dist.rc diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 03fd6e45bb..d0b2ea47cb 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -282,6 +282,9 @@ static const PQEnvironmentOption EnvironmentOptions[] = } }; +/* 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); @@ -293,6 +296,10 @@ static void fillPGconn(PGconn *conn, PQconninfoOption *connOptions); static void freePGconn(PGconn *conn); static void closePGconn(PGconn *conn); static PQconninfoOption *conninfo_init(PQExpBuffer errorMessage); +static PQconninfoOption *parse_connection_string(const char *conninfo, + PQExpBuffer errorMessage, bool use_defaults); +static int uri_prefix_length(const char *connstr); +static bool recognized_connection_string(const char *connstr); static PQconninfoOption *conninfo_parse(const char *conninfo, PQExpBuffer errorMessage, bool use_defaults); static PQconninfoOption *conninfo_array_parse(const char *const * keywords, @@ -300,8 +307,22 @@ static PQconninfoOption *conninfo_array_parse(const char *const * keywords, bool use_defaults, int expand_dbname); static bool conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage); -static char *conninfo_getval(PQconninfoOption *connOptions, +static PQconninfoOption *conninfo_uri_parse(const char *uri, + PQExpBuffer errorMessage, bool use_defaults); +static bool conninfo_uri_parse_options(PQconninfoOption *options, + const char *uri, 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, bool uri_decode); +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); static int parseServiceInfo(PQconninfoOption *options, @@ -333,9 +354,9 @@ pgthreadlock_t pg_g_threadlock = default_threadlock; * to the latter). * * If it is desired to connect in a synchronous (blocking) manner, use the - * function PQconnectdb or PQconnectdbParams. The former accepts a string - * of option = value pairs which must be parsed; the latter takes two NULL - * terminated arrays instead. + * function PQconnectdb or PQconnectdbParams. The former accepts a string of + * option = value pairs (or a URI) which must be parsed; the latter takes two + * NULL terminated arrays instead. * * To connect in an asynchronous (non-blocking) manner, use the functions * PQconnectStart or PQconnectStartParams (which differ in the same way as @@ -406,13 +427,14 @@ PQpingParams(const char *const * keywords, * establishes a connection to a postgres backend through the postmaster * using connection information in a string. * - * The conninfo string is a white-separated list of + * The conninfo string is either a whitespace-separated list of * * option = value * - * definitions. Value might be a single value containing no whitespaces or - * a single quoted string. If a single quote should appear anywhere in - * the value, it must be escaped with a backslash like \' + * definitions or a URI (refer to the documentation for details.) Value + * might be a single value containing no whitespaces or a single quoted + * string. If a single quote should appear anywhere in the value, it must be + * escaped with a backslash like \' * * Returns a PGconn* which is needed for all subsequent libpq calls, or NULL * if a memory allocation failed. @@ -583,7 +605,7 @@ PQconnectStart(const char *conninfo) static void fillPGconn(PGconn *conn, PQconninfoOption *connOptions) { - char *tmp; + const char *tmp; /* * Move option values into conn structure @@ -680,7 +702,7 @@ connectOptions1(PGconn *conn, const char *conninfo) /* * Parse the conninfo string */ - connOptions = conninfo_parse(conninfo, &conn->errorMessage, true); + connOptions = parse_connection_string(conninfo, &conn->errorMessage, true); if (connOptions == NULL) { conn->status = CONNECTION_BAD; @@ -881,9 +903,10 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions, return NULL; /* - * If the dbName parameter contains '=', assume it's a conninfo string. + * If the dbName parameter contains what looks like a connection + * string, parse it into conn struct using connectOptions1. */ - if (dbName && strchr(dbName, '=')) + if (dbName && recognized_connection_string(dbName)) { if (!connectOptions1(conn, dbName)) return conn; @@ -3764,7 +3787,7 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options, static int parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage) { - char *service = conninfo_getval(options, "service"); + const char *service = conninfo_getval(options, "service"); char serviceFile[MAXPGPATH]; char *env; bool group_found = false; @@ -3999,7 +4022,7 @@ PQconninfoParse(const char *conninfo, char **errmsg) initPQExpBuffer(&errorBuf); if (PQExpBufferDataBroken(errorBuf)) return NULL; /* out of memory already :-( */ - connOptions = conninfo_parse(conninfo, &errorBuf, false); + connOptions = parse_connection_string(conninfo, &errorBuf, false); if (connOptions == NULL && errmsg) *errmsg = errorBuf.data; else @@ -4023,17 +4046,68 @@ conninfo_init(PQExpBuffer errorMessage) return NULL; } memcpy(options, PQconninfoOptions, sizeof(PQconninfoOptions)); + return options; } /* - * Conninfo parser routine + * Connection string parser * - * If successful, a malloc'd PQconninfoOption array is returned. - * If not successful, NULL is returned and an error message is - * left in errorMessage. - * Defaults are supplied (from a service file, environment variables, etc) - * for unspecified options, but only if use_defaults is TRUE. + * Returns a malloc'd PQconninfoOption array, if parsing is successful. + * Otherwise, NULL is returned and an error message is left in errorMessage. + * + * If use_defaults is TRUE, default values are filled in (from a service file, + * environment variables, etc). + */ +static PQconninfoOption * +parse_connection_string(const char *connstr, PQExpBuffer errorMessage, + bool use_defaults) +{ + /* Parse as URI if connection string matches URI prefix */ + if (uri_prefix_length(connstr) != 0) + return conninfo_uri_parse(connstr, errorMessage, use_defaults); + + /* Parse as default otherwise */ + return conninfo_parse(connstr, errorMessage, use_defaults); +} + +/* + * Checks if connection string starts with either of the valid URI prefix + * designators. + * + * Returns the URI prefix length, 0 if the string doesn't contain a URI prefix. + */ +static int +uri_prefix_length(const char *connstr) +{ + if (strncmp(connstr, uri_designator, + sizeof(uri_designator) - 1) == 0) + return sizeof(uri_designator) - 1; + + if (strncmp(connstr, short_uri_designator, + sizeof(short_uri_designator) - 1) == 0) + return sizeof(short_uri_designator) - 1; + + return 0; +} + +/* + * Recognized connection string either starts with a valid URI prefix or + * contains a "=" in it. + * + * Must be consistent with parse_connection_string: anything for which this + * returns true should at least look like it's parseable by that routine. + */ +static bool +recognized_connection_string(const char *connstr) +{ + return uri_prefix_length(connstr) != 0 || strchr(connstr, '=') != NULL; +} + +/* + * Subroutine for parse_connection_string + * + * Deal with a string containing key=value pairs. */ static PQconninfoOption * conninfo_parse(const char *conninfo, PQExpBuffer errorMessage, @@ -4045,7 +4119,6 @@ conninfo_parse(const char *conninfo, PQExpBuffer errorMessage, char *cp; char *cp2; PQconninfoOption *options; - PQconninfoOption *option; /* Make a working copy of PQconninfoOptions */ options = conninfo_init(errorMessage); @@ -4167,33 +4240,10 @@ conninfo_parse(const char *conninfo, PQExpBuffer errorMessage, } /* - * Now we have the name and the value. Search for the param record. + * Now that we have the name and the value, store the record. */ - for (option = options; option->keyword != NULL; option++) + if (!conninfo_storeval(options, pname, pval, errorMessage, false, false)) { - 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; @@ -4227,10 +4277,10 @@ conninfo_parse(const char *conninfo, PQExpBuffer errorMessage, * Defaults are supplied (from a service file, environment variables, etc) * for unspecified options, but only if use_defaults is TRUE. * - * If expand_dbname is non-zero, and the value passed for keyword "dbname" - * contains an "=", assume it is a conninfo string and process it, - * overriding any previously processed conflicting keywords. Subsequent - * keywords will take precedence, however. + * If expand_dbname is non-zero, and the value passed for keyword "dbname" is a + * connection string (as indicated by recognized_connection_string) then parse + * and process it, overriding any previously processed conflicting + * keywords. Subsequent keywords will take precedence, however. */ static PQconninfoOption * conninfo_array_parse(const char *const * keywords, const char *const * values, @@ -4238,13 +4288,13 @@ conninfo_array_parse(const char *const * keywords, const char *const * values, int expand_dbname) { PQconninfoOption *options; - PQconninfoOption *str_options = NULL; + PQconninfoOption *dbname_options = NULL; PQconninfoOption *option; int i = 0; /* * If expand_dbname is non-zero, check keyword "dbname" to see if val is - * actually a conninfo string + * actually a recognized connection string. */ while (expand_dbname && keywords[i]) { @@ -4252,18 +4302,17 @@ conninfo_array_parse(const char *const * keywords, const char *const * values, const char *pvalue = values[i]; /* first find "dbname" if any */ - if (strcmp(pname, "dbname") == 0) + if (strcmp(pname, "dbname") == 0 && pvalue) { - /* next look for "=" in the value */ - if (pvalue && strchr(pvalue, '=')) + /* + * If value is a connection string, parse it, but do not use defaults + * here -- those get picked up later. We only want to override for + * those parameters actually passed. + */ + if (recognized_connection_string(pvalue)) { - /* - * Must be a conninfo string, so parse it, but do not use - * defaults here -- those get picked up later. We only want to - * override for those parameters actually passed. - */ - str_options = conninfo_parse(pvalue, errorMessage, false); - if (str_options == NULL) + dbname_options = parse_connection_string(pvalue, errorMessage, false); + if (dbname_options == NULL) return NULL; } break; @@ -4275,7 +4324,7 @@ conninfo_array_parse(const char *const * keywords, const char *const * values, options = conninfo_init(errorMessage); if (options == NULL) { - PQconninfoFree(str_options); + PQconninfoFree(dbname_options); return NULL; } @@ -4302,20 +4351,20 @@ conninfo_array_parse(const char *const * keywords, const char *const * values, libpq_gettext("invalid connection option \"%s\"\n"), pname); PQconninfoFree(options); - PQconninfoFree(str_options); + PQconninfoFree(dbname_options); return NULL; } /* * If we are on the dbname parameter, and we have a parsed - * conninfo string, copy those parameters across, overriding any - * existing previous settings + * connection string, copy those parameters across, overriding any + * existing previous settings. */ - if (strcmp(pname, "dbname") == 0 && str_options) + if (strcmp(pname, "dbname") == 0 && dbname_options) { PQconninfoOption *str_option; - for (str_option = str_options; str_option->keyword != NULL; str_option++) + for (str_option = dbname_options; str_option->keyword != NULL; str_option++) { if (str_option->val != NULL) { @@ -4347,14 +4396,14 @@ conninfo_array_parse(const char *const * keywords, const char *const * values, printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n")); PQconninfoFree(options); - PQconninfoFree(str_options); + PQconninfoFree(dbname_options); return NULL; } } } ++i; } - PQconninfoFree(str_options); + PQconninfoFree(dbname_options); /* * Add in defaults if the caller wants that. @@ -4450,16 +4499,618 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage) return true; } +/* + * Subroutine for parse_connection_string + * + * Deal with a URI connection string. + */ +static PQconninfoOption * +conninfo_uri_parse(const char *uri, PQExpBuffer errorMessage, + bool use_defaults) +{ + PQconninfoOption *options; + + /* Make a working copy of PQconninfoOptions */ + options = conninfo_init(errorMessage); + if (options == NULL) + return NULL; + + if (!conninfo_uri_parse_options(options, uri, errorMessage)) + { + PQconninfoFree(options); + return NULL; + } + + /* + * Add in defaults if the caller wants that. + */ + if (use_defaults) + { + if (!conninfo_add_defaults(options, errorMessage)) + { + PQconninfoFree(options); + return NULL; + } + } + + return options; +} + +/* + * conninfo_uri_parse_options + * Actual URI parser. + * + * If successful, returns true while the options array is filled with parsed + * 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, + PQExpBuffer errorMessage) +{ + int prefix_len; + char *p; + char *buf = strdup(uri); /* need a modifiable copy of the input URI */ + char *start = buf; + char prevchar = '\0'; + bool retval = false; + + if (buf == NULL) + { + printfPQExpBuffer(errorMessage, + libpq_gettext("out of memory\n")); + return false; + } + + /* Skip the URI prefix */ + prefix_len = uri_prefix_length(uri); + if (prefix_len == 0) + { + /* Should never happen */ + printfPQExpBuffer(errorMessage, + libpq_gettext("invalid URI propagated to internal parser routine: \"%s\"\n"), + uri); + goto cleanup; + } + start += prefix_len; + p = start; + + /* Look ahead for possible user credentials designator */ + while (*p && *p != '@' && *p != '/') + ++p; + if (*p == '@') + { + char *user; + + /* + * Found username/password designator, so URI should be of the form + * "scheme://user[:password]@[netloc]". + */ + user = start; + + p = user; + while (*p != ':' && *p != '@') + ++p; + + /* Save last char and cut off at end of user name */ + 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; + + if (prevchar == ':') + { + const char *password = p + 1; + + while (*p != '@') + ++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; + } + + /* Advance past end of parsed user name or password token */ + ++p; + } + else + { + /* + * No username/password designator found. Reset to start of URI. + */ + p = start; + } + + /* + * "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 == ':') + { + const char *port = ++p; /* advance past host terminator */ + + while (*p && *p != '/' && *p != '?') + ++p; + + 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; + } + + if (prevchar && prevchar != '?') + { + const char *dbname = ++p; /* advance past host terminator */ + + /* Look for query parameters */ + while (*p && *p != '?') + ++p; + + prevchar = *p; + *p = '\0'; + + /* + * Avoid setting dbname to an empty string, as it forces the default + * value (username) and ignores $PGDATABASE, as opposed to not setting + * it at all. + */ + if (*dbname && + !conninfo_storeval(options, "dbname", dbname, + errorMessage, false, true)) + goto cleanup; + } + + if (prevchar) + { + ++p; /* advance past terminator */ + + if (!conninfo_uri_parse_params(p, options, errorMessage)) + goto cleanup; + } + + /* everything parsed okay */ + retval = true; + +cleanup: + free(buf); + return retval; +} + +/* + * Connection URI parameters parser routine + * + * If successful, returns true while connOptions is filled with parsed + * parameters. Otherwise, returns false and fills errorMessage appropriately. + * + * Destructively modifies 'params' buffer. + */ +static bool +conninfo_uri_parse_params(char *params, + PQconninfoOption *connOptions, + PQExpBuffer errorMessage) +{ + while (*params) + { + const char *keyword = params; + const char *value = NULL; + char *p = params; + + /* + * Scan the params string for '=' and '&', marking the end of keyword + * and value respectively. + */ + for (;;) + { + if (*p == '=') + { + /* Was there '=' already? */ + if (value != NULL) + { + printfPQExpBuffer(errorMessage, + libpq_gettext("extra key/value separator '=' in URI query parameter: %s\n"), + params); + return false; + } + /* Cut off keyword, advance to value */ + *p = '\0'; + value = ++p; + } + else if (*p == '&' || *p == '\0') + { + char prevchar; + + /* Cut off value, remember old value */ + prevchar = *p; + *p = '\0'; + + /* Was there '=' at all? */ + if (value == NULL) + { + printfPQExpBuffer(errorMessage, + libpq_gettext("missing key/value separator '=' in URI query parameter: %s\n"), + params); + return false; + } + /* + * If not at the end, advance; now pointing to start of the + * next parameter, if any. + */ + if (prevchar != '\0') + ++p; + break; + } + + /* Advance, NUL is checked in the 'if' above */ + ++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; + } + + 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; + int c; + + ++q; /* skip the percent sign itself */ + + /* + * Possible EOL will be caught by the first call to get_hexdigit(), + * so we never dereference an invalid q pointer. + */ + if (!(get_hexdigit(*q++, &hi) && get_hexdigit(*q++, &lo))) + { + printfPQExpBuffer(errorMessage, + libpq_gettext("invalid percent-encoded token: %s\n"), + str); + free(buf); + return NULL; + } + + c = (hi << 4) | lo; + if (c == 0) + { + printfPQExpBuffer(errorMessage, + libpq_gettext("forbidden value %%00 in percent-encoded value: %s\n"), + str); + free(buf); + return NULL; + } + *(p++) = c; + } + } + + return buf; +} + +/* + * Convert hexadecimal digit character to its 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; + 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 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. + * The existing value for the option is free'd before replacing, if any. + * + * If not successful, returns NULL and fills errorMessage accordingly. + * However, if the reason of failure is an invalid keyword being passed and + * ignoreMissing is TRUE, errorMessage will be left untouched. + */ +static PQconninfoOption * +conninfo_storeval(PQconninfoOption *connOptions, + const char *keyword, const char *value, + PQExpBuffer errorMessage, bool ignoreMissing, + 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) + { + value_copy = conninfo_uri_decode(value, errorMessage); + if (value_copy == NULL) + /* conninfo_uri_decode already set an error message */ + goto failed; + } + else + { + value_copy = strdup(value); + + if (value_copy == NULL) + { + printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n")); + goto failed; + } + } + + if (option->val) + 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; +} + +/* + * 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->val; + return option; } return NULL; diff --git a/src/interfaces/libpq/test/Makefile b/src/interfaces/libpq/test/Makefile new file mode 100644 index 0000000000..b9023c37f3 --- /dev/null +++ b/src/interfaces/libpq/test/Makefile @@ -0,0 +1,22 @@ +subdir = src/interfaces/libpq/test +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global + +ifeq ($(PORTNAME), win32) +LDLIBS += -lws2_32 +endif + +override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS) +override LDLIBS := $(libpq_pgport) $(LDLIBS) + +PROGS = uri-regress + +all: $(PROGS) + +installcheck: all + SRCDIR='$(top_srcdir)' SUBDIR='$(subdir)' \ + $(SHELL) $(top_srcdir)/$(subdir)/regress.sh + +clean distclean maintainer-clean: + rm -f $(PROGS) + rm -f regress.out regress.diff diff --git a/src/interfaces/libpq/test/README b/src/interfaces/libpq/test/README new file mode 100644 index 0000000000..001ecc378d --- /dev/null +++ b/src/interfaces/libpq/test/README @@ -0,0 +1,7 @@ +This is a testsuite for testing libpq URI connection string syntax. + +To run the suite, use 'make installcheck' command. It works by +running 'regress.sh' from this directory with appropriate environment +set up, which in turn feeds up lines from 'regress.in' to +'uri-regress' test program and compares the output against the correct +one in 'expected.out' file. diff --git a/src/interfaces/libpq/test/expected.out b/src/interfaces/libpq/test/expected.out new file mode 100644 index 0000000000..54a6291bc4 --- /dev/null +++ b/src/interfaces/libpq/test/expected.out @@ -0,0 +1,163 @@ +trying postgresql://uri-user:secret@host:12345/db +user='uri-user' password='secret' dbname='db' host='host' port='12345' (inet) + +trying postgresql://uri-user@host:12345/db +user='uri-user' dbname='db' host='host' port='12345' (inet) + +trying postgresql://uri-user@host/db +user='uri-user' dbname='db' host='host' (inet) + +trying postgresql://host:12345/db +dbname='db' host='host' port='12345' (inet) + +trying postgresql://host/db +dbname='db' host='host' (inet) + +trying postgresql://uri-user@host:12345/ +user='uri-user' host='host' port='12345' (inet) + +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) + +trying postgresql://host:12345 +host='host' port='12345' (inet) + +trying postgresql://host/db +dbname='db' host='host' (inet) + +trying postgresql://host/ +host='host' (inet) + +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) + +trying postgresql://%68ost/ +host='host' (inet) + +trying postgresql://host/db?user=uri-user +user='uri-user' dbname='db' host='host' (inet) + +trying postgresql://host/db?user=uri-user&port=12345 +user='uri-user' dbname='db' host='host' port='12345' (inet) + +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 +user='uri-user' host='host' port='12345' (inet) + +trying postgresql://host?user=uri-user +user='uri-user' host='host' (inet) + +trying postgresql://host? +host='host' (inet) + +trying postgresql://[::1]:12345/db +dbname='db' host='::1' port='12345' (inet) + +trying postgresql://[::1]/db +dbname='db' host='::1' (inet) + +trying postgresql://[2001:db8::1234]/ +host='2001:db8::1234' (inet) + +trying postgresql://[200z:db8::1234]/ +host='200z:db8::1234' (inet) + +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 +host='host' (inet) + +trying postgre:// +uri-regress: missing "=" after "postgre://" in connection info string + + +trying postgres://[::1 +uri-regress: end of string reached when looking for matching ']' in IPv6 host address in URI: postgres://[::1 + + +trying postgres://[] +uri-regress: IPv6 host address may not be empty in URI: postgres://[] + + +trying postgres://[::1]z +uri-regress: unexpected 'z' at position 17 in URI (expecting ':' or '/'): postgres://[::1]z + + +trying postgresql://host?zzz +uri-regress: missing key/value separator '=' in URI query parameter: zzz + + +trying postgresql://host?value1&value2 +uri-regress: missing key/value separator '=' in URI query parameter: value1 + + +trying postgresql://host?key=key=value +uri-regress: extra key/value separator '=' in URI query parameter: key + + +trying postgres://host?dbname=%XXfoo +uri-regress: invalid percent-encoded token: %XXfoo + + +trying postgresql://a%00b +uri-regress: forbidden value %00 in percent-encoded value: a%00b + + +trying postgresql://%zz +uri-regress: invalid percent-encoded token: %zz + + +trying postgresql://%1 +uri-regress: invalid percent-encoded token: %1 + + +trying postgresql://% +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) + diff --git a/src/interfaces/libpq/test/regress.in b/src/interfaces/libpq/test/regress.in new file mode 100644 index 0000000000..8d33ae1ac1 --- /dev/null +++ b/src/interfaces/libpq/test/regress.in @@ -0,0 +1,49 @@ +postgresql://uri-user:secret@host:12345/db +postgresql://uri-user@host:12345/db +postgresql://uri-user@host/db +postgresql://host:12345/db +postgresql://host/db +postgresql://uri-user@host:12345/ +postgresql://uri-user@host/ +postgresql://uri-user@ +postgresql://host:12345/ +postgresql://host:12345 +postgresql://host/db +postgresql://host/ +postgresql://host +postgresql:// +postgresql://?hostaddr=127.0.0.1 +postgresql://example.com?hostaddr=63.1.2.4 +postgresql://%68ost/ +postgresql://host/db?user=uri-user +postgresql://host/db?user=uri-user&port=12345 +postgresql://host/db?u%73er=someotheruser&port=12345 +postgresql://host/db?u%7aer=someotheruser&port=12345 +postgresql://host:12345?user=uri-user +postgresql://host?user=uri-user +postgresql://host? +postgresql://[::1]:12345/db +postgresql://[::1]/db +postgresql://[2001:db8::1234]/ +postgresql://[200z:db8::1234]/ +postgresql://[::1] +postgres:// +postgres:///tmp +postgresql://host?uzer= +postgre:// +postgres://[::1 +postgres://[] +postgres://[::1]z +postgresql://host?zzz +postgresql://host?value1&value2 +postgresql://host?key=key=value +postgres://host?dbname=%XXfoo +postgresql://a%00b +postgresql://%zz +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 diff --git a/src/interfaces/libpq/test/regress.sh b/src/interfaces/libpq/test/regress.sh new file mode 100644 index 0000000000..298d8bdc4a --- /dev/null +++ b/src/interfaces/libpq/test/regress.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +while read line +do + echo "trying $line" + ./uri-regress "$line" + echo "" +done < "${SRCDIR}/${SUBDIR}"/regress.in >regress.out 2>&1 + +if diff -c "${SRCDIR}/${SUBDIR}/"expected.out regress.out >regress.diff; then + echo "========================================" + echo "All tests passed" + exit 0 +else + echo "========================================" + echo "FAILED: the test result differs from the expected output" + echo + echo "Review the difference in ${SUBDIR}/regress.diff" + echo "========================================" + exit 1 +fi diff --git a/src/interfaces/libpq/test/uri-regress.c b/src/interfaces/libpq/test/uri-regress.c new file mode 100644 index 0000000000..17fcce9fb2 --- /dev/null +++ b/src/interfaces/libpq/test/uri-regress.c @@ -0,0 +1,84 @@ +/* + * uri-regress.c + * A test program for libpq URI format + * + * This is a helper for libpq conninfo regression testing. It takes a single + * conninfo string as a parameter, parses it using PQconninfoParse, and then + * prints out the values from the parsed PQconninfoOption struct that differ + * from the defaults (obtained from PQconndefaults). + * + * Portions Copyright (c) 2012, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/interfaces/libpq/test/uri-regress.c + */ + +#include "postgres_fe.h" + +#include "libpq-fe.h" + +int +main(int argc, char *argv[]) +{ + PQconninfoOption *opts; + PQconninfoOption *defs; + PQconninfoOption *opt; + PQconninfoOption *def; + char *errmsg = NULL; + bool local = true; + + if (argc != 2) + return 1; + + opts = PQconninfoParse(argv[1], &errmsg); + if (opts == NULL) + { + fprintf(stderr, "uri-regress: %s\n", errmsg); + return 1; + } + + defs = PQconndefaults(); + if (defs == NULL) + { + fprintf(stderr, "uri-regress: cannot fetch default options\n"); + return 1; + } + + /* + * Loop on the options, and print the value of each if not the default. + * + * XXX this coding assumes that PQconninfoOption structs always have the + * keywords in the same order. + */ + for (opt = opts, def = defs; opt->keyword; ++opt, ++def) + { + if (opt->val != NULL) + { + if (def->val == NULL || strcmp(opt->val, def->val) != 0) + printf("%s='%s' ", opt->keyword, opt->val); + + /* + * Try to detect if this is a Unix-domain socket or inet. This is + * a bit grotty but it's the same thing that libpq itself does. + * + * Note that we directly test for '/' instead of using + * is_absolute_path, as that would be considerably more messy. + * This would fail on Windows, but that platform doesn't have + * Unix-domain sockets anyway. + */ + if (*opt->val && + (strcmp(opt->keyword, "hostaddr") == 0 || + (strcmp(opt->keyword, "host") == 0 && *opt->val != '/'))) + { + local = false; + } + } + } + + if (local) + printf("(local)\n"); + else + printf("(inet)\n"); + + return 0; +}