Fix pg_isblank()
authorPeter Eisentraut <peter@eisentraut.org>
Fri, 28 Nov 2025 06:53:12 +0000 (07:53 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Fri, 28 Nov 2025 07:33:07 +0000 (08:33 +0100)
There was a pg_isblank() function that claimed to be a replacement for
the standard isblank() function, which was thought to be "not very
portable yet".  We can now assume that it's portable (it's in C99).

But pg_isblank() actually diverged from the standard isblank() by also
accepting '\r', while the standard one only accepts space and tab.
This was added to support parsing pg_hba.conf under Windows.  But the
hba parsing code now works completely differently and already handles
line endings before we get to pg_isblank().  The other user of
pg_isblank() is for ident protocol message parsing, which also handles
'\r' separately.  So this behavior is now obsolete and confusing.

To improve clarity, I separated those concerns.  The ident parsing now
gets its own function that hardcodes the whitespace characters
mentioned by the relevant RFC.  pg_isblank() is now static in hba.c
and is a wrapper around the standard isblank(), with some extra logic
to ensure robust treatment of non-ASCII characters.

Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://www.postgresql.org/message-id/flat/170308e6-a7a3-4484-87b2-f960bb564afa%40eisentraut.org

src/backend/libpq/auth.c
src/backend/libpq/hba.c
src/include/libpq/hba.h

index ec4dbacf01552fee8e329fb278c7489c28f53022..5854a2433bb6a705e87ea23e88b09c165e8a0119 100644 (file)
@@ -1580,6 +1580,15 @@ pg_SSPI_make_upn(char *accountname,
  *----------------------------------------------------------------
  */
 
+/*
+ * Per RFC 1413, space and tab are whitespace in ident messages.
+ */
+static bool
+is_ident_whitespace(const char c)
+{
+   return c == ' ' || c == '\t';
+}
+
 /*
  * Parse the string "*ident_response" as a response from a query to an Ident
  * server.  If it's a normal response indicating a user name, return true
@@ -1613,14 +1622,14 @@ interpret_ident_response(const char *ident_response,
            int         i;      /* Index into *response_type */
 
            cursor++;           /* Go over colon */
-           while (pg_isblank(*cursor))
+           while (is_ident_whitespace(*cursor))
                cursor++;       /* skip blanks */
            i = 0;
-           while (*cursor != ':' && *cursor != '\r' && !pg_isblank(*cursor) &&
+           while (*cursor != ':' && *cursor != '\r' && !is_ident_whitespace(*cursor) &&
                   i < (int) (sizeof(response_type) - 1))
                response_type[i++] = *cursor++;
            response_type[i] = '\0';
-           while (pg_isblank(*cursor))
+           while (is_ident_whitespace(*cursor))
                cursor++;       /* skip blanks */
            if (strcmp(response_type, "USERID") != 0)
                return false;
@@ -1643,7 +1652,7 @@ interpret_ident_response(const char *ident_response,
                    else
                    {
                        cursor++;   /* Go over colon */
-                       while (pg_isblank(*cursor))
+                       while (is_ident_whitespace(*cursor))
                            cursor++;   /* skip blanks */
                        /* Rest of line is user name.  Copy it over. */
                        i = 0;
index 97a3586000bb29cb1eb475be80b95dcc5b11c88f..6cb25399c26ed85321b7a27c54f58f9b3fa20585 100644 (file)
@@ -138,14 +138,11 @@ static int    regexec_auth_token(const char *match, AuthToken *token,
 static void tokenize_error_callback(void *arg);
 
 
-/*
- * isblank() exists in the ISO C99 spec, but it's not very portable yet,
- * so provide our own version.
- */
-bool
+static bool
 pg_isblank(const char c)
 {
-   return c == ' ' || c == '\t' || c == '\r';
+   /* don't accept non-ASCII data */
+   return (!IS_HIGHBIT_SET(c) && isblank(c));
 }
 
 
index e3748d3c8c963628c204834f6ed9881d39503b17..7b93ba4a709f78b31e743481c8a4f1875b8770a3 100644 (file)
@@ -181,7 +181,6 @@ extern int  check_usermap(const char *usermap_name,
                          bool case_insensitive);
 extern HbaLine *parse_hba_line(TokenizedAuthLine *tok_line, int elevel);
 extern IdentLine *parse_ident_line(TokenizedAuthLine *tok_line, int elevel);
-extern bool pg_isblank(const char c);
 extern FILE *open_auth_file(const char *filename, int elevel, int depth,
                            char **err_msg);
 extern void free_auth_file(FILE *file, int depth);