Re: [PATCH v2 1/2] Add CTYPE defines for various sets of characters.

Alejandro Colomar via Mutt-dev <[email protected]> Tue, 4 Aug 2026 13:50:16 +0200
Newsgroups gmane.mail.mutt.devel
Message-ID <anHJtLAKsiqIbWbF@devuan>
--b6zvpv5kpg5tbwuo
Content-Type: text/plain; protected-headers=v1; charset=utf-8
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
From: Alejandro Colomar <[email protected]>
To: [email protected]
Subject: Re: [PATCH v2 1/2] Add CTYPE defines for various sets of characters.
Message-ID: <anHJtLAKsiqIbWbF@devuan>
References: <[email protected]>
 <[email protected]>
MIME-Version: 1.0
In-Reply-To: <[email protected]>

Hi Kevin,

> Date: 2026-08-04 13:18:19+0800
> From: "Kevin J. McCarthy" <[email protected]>
>
> Thanks to Alejandro Colomar for this suggestion, and I used his
> examples directly for LOWER_C to PFCHAR_C.  The HEX additions are
> mine, so you can blame those on me. ;-)

:-)

Actually, since there's the standard [:xdigit:] and isxdigit(3), I'd
call it (and actually called it in shadow-utils) CTYPE_XDIGIT_C:

	#define CTYPE_XDIGIT_C         CTYPE_DIGIT_C "abcdefABCDEF"

Here's the full set of APIs I have (they may inspire you to add a few
more similar ones):

	#define CTYPE_CNTRL_C                                                 \
		"\x7F"                                                        \
		"\x1F\x1E\x1D\x1C\x1B\x1A\x19\x18\x17\x16\x15\x14\x13\x12\x11\x10" \
		"\x0F\x0E\x0D\x0C\x0B\x0A\x09\x08\x07\x06\x05\x04\x03\x02\x01" /*NUL*/

	#define CTYPE_LOWER_C          "abcdefghijklmnopqrstuvwxyz"
	#define CTYPE_UPPER_C          "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	#define CTYPE_DIGIT_C          "0123456789"
	#define CTYPE_PUNCT_C          "!\"#$%&'()*+,-./:;<=3D>?@[\\]^_`{|}~"
	#define CTYPE_BLANK_C          " \t"
	#define CTYPE_SPACE_C          CTYPE_BLANK_C "\n\v\f\r"
	#define CTYPE_ALPHA_C          CTYPE_LOWER_C CTYPE_UPPER_C
	#define CTYPE_ALNUM_C          CTYPE_ALPHA_C CTYPE_DIGIT_C
	#define CTYPE_GRAPH_C          CTYPE_ALNUM_C CTYPE_PUNCT_C
	#define CTYPE_PRINT_C          CTYPE_GRAPH_C " "
	#define CTYPE_XDIGIT_C         CTYPE_DIGIT_C "abcdefABCDEF"
	#define CTYPE_ASCII_C          CTYPE_PRINT_C CTYPE_CNTRL_C /*NUL*/
	#define CTYPE_PFCHAR_C         CTYPE_ALNUM_C "._-"  // portable filename c=
haracter set
	#define CTYPE_LDH_RFC1035_C    CTYPE_ALNUM_C "-"  // letter, digit, hyphen


	// isascii_c - is [:ascii:] C-locale
	#define isascii_c(c)           (!!strchr(CTYPE_ASCII_C, c))
	#define iscntrl_c(c)           (!!strchr(CTYPE_CNTRL_C, c))
	#define islower_c(c)           (!streq(strchrnul(CTYPE_LOWER_C, c), ""))
	#define isupper_c(c)           (!streq(strchrnul(CTYPE_UPPER_C, c), ""))
	#define isdigit_c(c)           (!streq(strchrnul(CTYPE_DIGIT_C, c), ""))
	#define ispunct_c(c)           (!streq(strchrnul(CTYPE_PUNCT_C, c), ""))
	#define isblank_c(c)           (!streq(strchrnul(CTYPE_BLANK_C, c), ""))
	#define isspace_c(c)           (!streq(strchrnul(CTYPE_SPACE_C, c), ""))
	#define isalpha_c(c)           (!streq(strchrnul(CTYPE_ALPHA_C, c), ""))
	#define isalnum_c(c)           (!streq(strchrnul(CTYPE_ALNUM_C, c), ""))
	#define isgraph_c(c)           (!streq(strchrnul(CTYPE_GRAPH_C, c), ""))
	#define isprint_c(c)           (!streq(strchrnul(CTYPE_PRINT_C, c), ""))
	#define isxdigit_c(c)          (!streq(strchrnul(CTYPE_XDIGIT_C, c), ""))
	#define ispfchar_c(c)          (!streq(strchrnul(CTYPE_PFCHAR_C, c), ""))
	#define isldh_rfc1035_c(c)     (!streq(strchrnul(CTYPE_LDH_RFC1035_C, c), =
""))


	// strisascii_c - string is [:ascii:] C-locale
	#define strisdigit_c(s)        streq(stpspn(s, CTYPE_DIGIT_C), "")
	#define strisprint_c(s)        streq(stpspn(s, CTYPE_PRINT_C), "")
	#define strispfchar_c(s)       streq(stpspn(s, CTYPE_PFCHAR_C), "")
	#define strisldh_rfc1035_c(s)  streq(stpspn(s, CTYPE_LDH_RFC1035_C), "")


	// strchriscntrl_c - string character is [:cntrl:] C-locale
	#define strchriscntrl_c(s)     (!!strpbrk(s, CTYPE_CNTRL_C))

> ---
>=20
> Please let me know what you think.  Does it help readability for the
> cases where we use the CTYPE along with extra characters?
> e.g. imap/command.c, lib.c, muttlib.c  below.

Yup, IMO.  Where thr RFCs use a name for that, I'd give them a new name
(see for example, CTYPE_LDH_RFC1035_C above, which corresponds to
RFC1035's <ldh-str>), but where there's not a standard-ish name, I guess
direct use like this is fine.
<https://www.rfc-editor.org/info/rfc1035/#section-2.3.1>

>=20
>  crypt.c         |  2 +-
>  imap/command.c  |  2 +-
>  lib.c           |  2 +-
>  lib.h           | 13 +++++++++++++
>  mutt_sasl_gnu.c |  2 +-
>  muttlib.c       |  2 +-
>  rfc2047.c       |  2 +-
>  url.c           |  2 +-
>  8 files changed, 20 insertions(+), 7 deletions(-)
>=20
> diff --git a/crypt.c b/crypt.c
> index e4c8051d..d141ac6f 100644
> --- a/crypt.c
> +++ b/crypt.c
> @@ -1295,7 +1295,7 @@ short crypt_is_numerical_keyid(const char *s)
>    if (strlen(s) % 8)
>      return 0;
>    while (*s)
> -    if (strchr("0123456789ABCDEFabcdef", *s++) =3D=3D NULL)
> +    if (strchr(CTYPE_HEX_C, *s++) =3D=3D NULL)

This is certainly useful!

>        return 0;
> =20
>    return 1;
> diff --git a/imap/command.c b/imap/command.c
> index 63dedf6c..94c98883 100644
> --- a/imap/command.c
> +++ b/imap/command.c
> @@ -701,7 +701,7 @@ static void cmd_parse_vanished(IMAP_DATA *idata, char=
 *s)
>    end_of_seqset =3D s;
>    while (*end_of_seqset)
>    {
> -    if (!strchr("0123456789:,", *end_of_seqset))
> +    if (!strchr(CTYPE_DIGIT_C ":,", *end_of_seqset))

I wonder if this character set has any name in RFC 7162.  I can't find
it, though.  The only reference to DIGIT seems to be in
mod-sequence-value.

>        *end_of_seqset =3D '\0';
>      else
>        end_of_seqset++;
> diff --git a/lib.c b/lib.c
> index 9776fcdb..fdecda25 100644
> --- a/lib.c
> +++ b/lib.c
> @@ -573,7 +573,7 @@ success:
>  }
> =20
> =20
> -static const char safe_chars[] =3D "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij=
klmnopqrstuvwxyz0123456789+@{}._-:%";
> +static const char safe_chars[] =3D CTYPE_PFCHAR_C "+@{}:%";

LGTM.  Are those extra safe chars specified by any standard, or is it
just of this project?

> =20
>  void mutt_sanitize_filename(char *f, int flags)
>  {
> diff --git a/lib.h b/lib.h
> index 25875656..b622e9c0 100644
> --- a/lib.h
> +++ b/lib.h
> @@ -147,6 +147,19 @@ static inline char *skip_email_wsp(const char *s)
>     on some systems */
>  #define SKIP_LOCALE_WS(c) while (*(c) && IS_LOCALE_WS(*(c))) c++;
> =20
> +/*
> + * Various useful sets of characters
> + */
> +#define CTYPE_LOWER_C        "abcdefghijklmnopqrstuvwxyz"
> +#define CTYPE_UPPER_C        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
> +#define CTYPE_DIGIT_C        "0123456789"
> +#define CTYPE_ALPHA_C        CTYPE_LOWER_C CTYPE_UPPER_C
> +#define CTYPE_ALNUM_C        CTYPE_ALPHA_C CTYPE_DIGIT_C
> +#define CTYPE_PFCHAR_C       CTYPE_ALNUM_C "._-"    // POSIX.1-2008 port=
able
> +                                                    // filename characte=
r set

Actually, that was already true as of POSIX.1-2001 (Issue 6); the first
modern-day POSIX, where POSIX and SUS (the Single UNIX Specification)
were unified

I suspect this was already true way earlier, since I see a reference to
the 'portable filename character set' in XPG Issue 4 (v2) (from which
modern-day POSIX derives).
<https://pubs.opengroup.org/onlinepubs/009656499/toc.pdf#page=3D423>
I don't see the the definition of the p.f.c.s. in that document, because
it was in a separate volume, and I can't find it.  It's also difficult
to find older standards, unless you know where they are.  :)

> +#define CTYPE_UHEX_C         CTYPE_DIGIT_C "ABCDEF" // uppercase hex

Hmmm, I'd maybe call this UXDIGIT?  (Considering that the below would be
XDIGIT, per the usual standards.)

> +#define CTYPE_HEX_C          CTYPE_UHEX_C "abcdef"
> +
>  /*
>   * These functions aren't defined in lib.c, but
>   * they are used there.
> diff --git a/mutt_sasl_gnu.c b/mutt_sasl_gnu.c
> index 9a7a4b87..2bd5afbf 100644
> --- a/mutt_sasl_gnu.c
> +++ b/mutt_sasl_gnu.c
> @@ -66,7 +66,7 @@ void mutt_gsasl_done(void)
>  }
> =20
>  static const char *VALID_MECHANISM_CHARACTERS =3D
> -  "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
> +  CTYPE_UPPER_C CTYPE_DIGIT_C "-_";

LGTM.


Have a lovely day!
Alex

> =20
>  /* This logic is derived from the libgsasl suggest code */
>  static int mechlist_contains(const char *uc_mech, const char *uc_mechlis=
t)
> diff --git a/muttlib.c b/muttlib.c
> index d24a37f0..ee29cc17 100644
> --- a/muttlib.c
> +++ b/muttlib.c
> @@ -1178,7 +1178,7 @@ void _mutt_buffer_quote_filename(BUFFER *d, const c=
har *f, int add_outer)
>      mutt_buffer_addch(d, '\'');
>  }
> =20
> -static const char safe_chars[] =3D "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij=
klmnopqrstuvwxyz0123456789+@{}._-:%";
> +static const char safe_chars[] =3D CTYPE_PFCHAR_C "+@{}:%";
> =20
>  void mutt_buffer_sanitize_filename(BUFFER *d, const char *f, int flags)
>  {
> diff --git a/rfc2047.c b/rfc2047.c
> index 2bdd8553..1bacb54f 100644
> --- a/rfc2047.c
> +++ b/rfc2047.c
> @@ -257,7 +257,7 @@ static size_t b_encoder(char *s, ICONV_CONST char *d,=
 size_t dlen,
>  static size_t q_encoder(char *s, ICONV_CONST char *d, size_t dlen,
>                          const char *tocode)
>  {
> -  static const char hex[] =3D "0123456789ABCDEF";
> +  static const char hex[] =3D CTYPE_UHEX_C;
>    char *s0 =3D s;
> =20
>    memcpy(s, "=3D?", 2), s +=3D 2;
> diff --git a/url.c b/url.c
> index 02dbf34c..176877cd 100644
> --- a/url.c
> +++ b/url.c
> @@ -193,7 +193,7 @@ int url_parse_ciss(ciss_url_t *ciss, char *src)
> =20
>  static void url_pct_encode(char *dst, size_t l, const char *src)
>  {
> -  static const char *alph =3D "0123456789ABCDEF";
> +  static const char *alph =3D CTYPE_UHEX_C;
> =20
>    *dst =3D 0;
>    l--;
> --=20
> 2.55.0
>=20

--=20
<https://www.alejandro-colomar.es>

--b6zvpv5kpg5tbwuo
Content-Type: application/pgp-signature; name="signature.asc"

-----BEGIN PGP SIGNATURE-----

iQIzBAABCgAdFiEES7Jt9u9GbmlWADAi64mZXMKQwqkFAmpx0fIACgkQ64mZXMKQ
wqlPZA/8DF1wZPbxxXkzWAIiLoA1yNLBwrb+YUFJGRNowLfgqB2bVrgnPbsfXP0U
WBgt55ZKuAe0nCEr9cGQZN6G7GteSqBHqghfSzgd3zPmkGJfnfbQbyzS5dCB5iGI
jj6WdlDDE+ZRXGiHX33KeMictMKuXpgYwd+EuENlnwWRyggIC9KxKRIcnOkpaQ/V
WYTFEFrUoCzQJAsOMD3BqndHKToCJCPgCSFVL/NdmlXxspu0q2P2Rd6cOrmejjHq
S8HC2Bs2o/crRUp1zHV3wbRC1Pt37ax9fUrDnkMhPeqf1sbQZaHjj0S1ni65AT2f
+vr9CHBEYJk5wvAKcFWl+gzuEgTINi5TMtsxF6c9qVLuhQSsc8WQsBfbRXWDlGoo
tqYSboRa/X7MUvtXdbUu+nTyQRXn9XtJ7riNJx9zyACsUwcCjUeT1rCRivB3uiOE
zMIalUrB/41oBsts1GfozemZj01gIxe+Lv52qgBV0KI4eOtmL3X673zPvNeLQeEm
IeIIvm8I4JGBPKB1ONX9jluYfcT32iRmveFC/yNkuHn+kTOsFHoSdu5b8hUEYG7y
9nk2Q1Ld+5q723tjqxAspTL0Tyvqy90A+k8aOZSWbDzK8VnY8utXUHRSUdOW+oB4
W+WngQlSwy9QoVUHAPbLmGX8qk5j5n65CCnrcKWEgCTgAtDcrKg=
=2MkP
-----END PGP SIGNATURE-----

--b6zvpv5kpg5tbwuo--