From cd7e6fa5d2e0145bf961be55b3aed25faa3d7637 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Fri, 21 Nov 2025 13:59:28 +0100
Subject: [PATCH 3/4] Fix pg_isblank()

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.  The hba parsing uses the standard
isblank().  pg_isblank() is removed.
---
 src/backend/libpq/auth.c | 17 +++++++++++++----
 src/backend/libpq/hba.c  | 15 ++-------------
 src/include/libpq/hba.h  |  1 -
 3 files changed, 15 insertions(+), 18 deletions(-)

diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index ec4dbacf015..5854a2433bb 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -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;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 97a3586000b..787aee49d24 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -138,17 +138,6 @@ 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
-pg_isblank(const char c)
-{
-	return c == ' ' || c == '\t' || c == '\r';
-}
-
-
 /*
  * Grab one token out of the string pointed to by *lineptr.
  *
@@ -198,7 +187,7 @@ next_token(char **lineptr, StringInfo buf,
 	*terminating_comma = false;
 
 	/* Move over any whitespace and commas preceding the next token */
-	while ((c = (*(*lineptr)++)) != '\0' && (pg_isblank(c) || c == ','))
+	while ((c = (*(*lineptr)++)) != '\0' && (isblank((unsigned char) c) || c == ','))
 		;
 
 	/*
@@ -206,7 +195,7 @@ next_token(char **lineptr, StringInfo buf,
 	 * unquoted whitespace.
 	 */
 	while (c != '\0' &&
-		   (!pg_isblank(c) || in_quote))
+		   (!isblank((unsigned char) c) || in_quote))
 	{
 		/* skip comments to EOL */
 		if (c == '#' && !in_quote)
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index e3748d3c8c9..7b93ba4a709 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -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);
-- 
2.51.0

