]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
match on single host/port only for integer port
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 6 Jul 2023 14:06:14 +0000 (10:06 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 6 Jul 2023 14:25:53 +0000 (10:25 -0400)
Fixed regression caused by improvements to PostgreSQL URL parsing in
:ticket:`10004` where "host" query string arguments that had colons in
them, to support various third party proxy servers and/or dialects, would
not parse correctly as these were evaluted as ``host:port`` combinations.
Parsing has been updated to consider a colon as indicating a ``host:port``
value only if the hostname contains only alphanumeric characters with dots
or dashes only (e.g. no slashes), followed by exactly one colon followed by
an all-integer token of zero or more integers.  In all other cases, the
full string is taken as a host.

Fixes: #10069
Change-Id: I77beb27e44abc0a66aa0810de855daa4186dacfd

doc/build/changelog/unreleased_20/10069.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/postgresql/base.py
test/dialect/postgresql/test_dialect.py

diff --git a/doc/build/changelog/unreleased_20/10069.rst b/doc/build/changelog/unreleased_20/10069.rst
new file mode 100644 (file)
index 0000000..f04854e
--- /dev/null
@@ -0,0 +1,13 @@
+.. change::
+    :tags: bug, postgresql
+    :tickets: 10069
+
+    Fixed regression caused by improvements to PostgreSQL URL parsing in
+    :ticket:`10004` where "host" query string arguments that had colons in
+    them, to support various third party proxy servers and/or dialects, would
+    not parse correctly as these were evaluted as ``host:port`` combinations.
+    Parsing has been updated to consider a colon as indicating a ``host:port``
+    value only if the hostname contains only alphanumeric characters with dots
+    or dashes only (e.g. no slashes), followed by exactly one colon followed by
+    an all-integer token of zero or more integers.  In all other cases, the
+    full string is taken as a host.
index 2d5f5c5ac954d341799cbfb2faed786b27dc0bd1..5e0dee0ea38ac97258c3349bf2c3a7c5b818f5f1 100644 (file)
@@ -3120,10 +3120,18 @@ class PGDialect(default.DefaultDialect):
                     and len(hosts) == 1
                     and ":" in hosts[0]
                 ):
-                    integrated_multihost = True
-                    h, p = hosts[0].split(":")
-                    hosts = (h,)
-                    ports = (p,) if p else (None,)
+                    # internet host is alphanumeric plus dots or hyphens.
+                    # this is essentially rfc1123, which refers to rfc952.
+                    # https://stackoverflow.com/questions/3523028/
+                    # valid-characters-of-a-hostname
+                    host_port_match = re.match(
+                        r"^([a-zA-Z0-9\-\.]*)(?:\:(\d*))?$", hosts[0]
+                    )
+                    if host_port_match:
+                        integrated_multihost = True
+                        h, p = host_port_match.group(1, 2)
+                        hosts = (h,)
+                        ports = (p,) if p else (None,)
 
         if "port" in url.query:
             if integrated_multihost:
index 771ffea6251c24e4fcb5cfe630e47b896ea5b222..31335a84c0c2fa07f2d0e9555f7582580f8c33c2 100644 (file)
@@ -268,6 +268,57 @@ class MultiHostConnectTest(fixtures.TestBase):
                     "host": "hostA",
                 },
             ),
+            (
+                # issue #10069 -if there is just one host as x:y with no
+                # integers, treat it as a hostname, to accommodate as many
+                # third party scenarios as possible
+                "postgresql+psycopg2://USER:PASS@/DB?host=hostA:xyz",
+                {
+                    "dbname": "DB",
+                    "user": "USER",
+                    "password": "PASS",
+                    "host": "hostA:xyz",
+                },
+            ),
+            (
+                # also issue #10069 - this parsing is not "defined" right now
+                # but err on the side of single host
+                "postgresql+psycopg2://USER:PASS@/DB?host=hostA:123.456",
+                {
+                    "dbname": "DB",
+                    "user": "USER",
+                    "password": "PASS",
+                    "host": "hostA:123.456",
+                },
+            ),
+            (
+                "postgresql+psycopg2://USER:PASS@/DB?host=192.168.1.50",
+                {
+                    "dbname": "DB",
+                    "user": "USER",
+                    "password": "PASS",
+                    "host": "192.168.1.50",
+                },
+            ),
+            (
+                "postgresql+psycopg2://USER:PASS@/DB?host=192.168.1.50:",
+                {
+                    "dbname": "DB",
+                    "user": "USER",
+                    "password": "PASS",
+                    "host": "192.168.1.50",
+                },
+            ),
+            (
+                "postgresql+psycopg2://USER:PASS@/DB?host=192.168.1.50:5678",
+                {
+                    "dbname": "DB",
+                    "user": "USER",
+                    "password": "PASS",
+                    "host": "192.168.1.50",
+                    "port": "5678",
+                },
+            ),
             (
                 "postgresql+psycopg2://USER:PASS@/DB?host=hostA:",
                 {
@@ -277,6 +328,54 @@ class MultiHostConnectTest(fixtures.TestBase):
                     "host": "hostA",
                 },
             ),
+            (
+                "postgresql+psycopg2://USER:PASS@/DB?host=HOSTNAME",
+                {
+                    "dbname": "DB",
+                    "user": "USER",
+                    "password": "PASS",
+                    "host": "HOSTNAME",
+                },
+            ),
+            (
+                "postgresql+psycopg2://USER:PASS@/DB?host=HOSTNAME:1234",
+                {
+                    "dbname": "DB",
+                    "user": "USER",
+                    "password": "PASS",
+                    "host": "HOSTNAME",
+                    "port": "1234",
+                },
+            ),
+            (
+                # issue #10069
+                "postgresql+psycopg2://USER:PASS@/DB?"
+                "host=/cloudsql/my-gcp-project:us-central1:mydbisnstance",
+                {
+                    "dbname": "DB",
+                    "user": "USER",
+                    "password": "PASS",
+                    "host": "/cloudsql/my-gcp-project:"
+                    "us-central1:mydbisnstance",
+                },
+            ),
+            (
+                # issue #10069
+                "postgresql+psycopg2://USER:PASS@/DB?"
+                "host=/cloudsql/my-gcp-project:4567",
+                {
+                    "dbname": "DB",
+                    "user": "USER",
+                    "password": "PASS",
+                    # full host,because the "hostname" contains slashes.
+                    # this corresponds to PG's "host" mechanics
+                    # at https://www.postgresql.org/docs/current
+                    # /libpq-connect.html#LIBPQ-PARAMKEYWORDS
+                    # "If a host name looks like an absolute path name, it
+                    # specifies Unix-domain communication "
+                    "host": "/cloudsql/my-gcp-project:4567",
+                },
+            ),
             (
                 "postgresql+psycopg2://USER:PASS@/DB?host=hostA:1234",
                 {
@@ -425,8 +524,9 @@ class MultiHostConnectTest(fixtures.TestBase):
             "postgresql+psycopg2://USER:PASS@/DB"
             "?host=hostA:xyz&host=hostB:123",
         ),
-        ("postgresql+psycopg2://USER:PASS@/DB?host=hostA:xyz",),
         ("postgresql+psycopg2://USER:PASS@/DB?host=hostA&port=xyz",),
+        # for single host with :xyz, as of #10069 this is treated as a
+        # hostname by itself, w/ colon plus digits
         argnames="url_string",
     )
     @testing.combinations(