Fix pg_isblank()
authorPeter Eisentraut <[email protected]>
Fri, 28 Nov 2025 06:53:12 +0000 (07:53 +0100)
committerPeter Eisentraut <[email protected]>
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 <[email protected]>
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);