Re: [PATCH] Add %F expando to $message_id_format to use from address.

Alejandro Colomar via Mutt-dev <[email protected]> Mon, 3 Aug 2026 13:12:46 +0200
Newsgroups gmane.mail.mutt.devel
Message-ID <anB0GFYGkcA1CMT4@devuan>
--z5lozktmoy5cygj6
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] Add %F expando to $message_id_format to use from address.
Message-ID: <anB0GFYGkcA1CMT4@devuan>
References: <[email protected]>
MIME-Version: 1.0
In-Reply-To: <[email protected]>

Hi Kevin,

> Date: 2026-08-03 12:43:26+0800
> From: "Kevin J. McCarthy" <[email protected]>
>
> Filter out characters, even stricter than RFC5322 requires, to allow
> the message id to be used in a URL.
>=20
> If, for whatever reason, the from address is not available, fall back
> to "@%f".
>=20
> Provide an example value in the manual:
>   set message_id_format=3D"%z_%F"
> noting that %F includes a '@' and so one must not also be inside the
> variable.
> ---
>=20
> This is a simple take on adding %F to use the from address.  I didn't
> try to optimize for speed, but suggestions are welcome.  I added heavy
> filtering to make sure it didn't violate the spec and also didn't
> break adding to a URL without encoding.
>=20
>=20
>  init.h      |  5 +++++
>  messageid.c | 45 ++++++++++++++++++++++++++++++++++++++++++++-
>  protos.h    |  2 +-
>  sendlib.c   |  4 ++--
>  4 files changed, 52 insertions(+), 4 deletions(-)
>=20
> diff --git a/init.h b/init.h
> index 087ebfa8..d9fc0378 100644
> --- a/init.h
> +++ b/init.h
> @@ -2223,11 +2223,16 @@ struct option_t MuttVars[] =3D {
>    ** The old Message-ID format can be used by setting this to:
>    ** ``\fC<%Y%02m%02d%02H%02M%02S.G%c%p@%f>\fP''
>    ** .pp
> +  ** An alternative to using %f is %F.  This expando includes a '@',
> +  ** so when used there must not be a second '@' in the variable:
> +  ** ``\fC<%z_%F>\fP''
> +  ** .pp
>    ** The following \fCprintf(3)\fP-style sequences are understood:
>    ** .dl
>    ** .dt %c .dd step counter looping from ``A'' to ``Z''
>    ** .dt %d .dd current day of the month (GMT)
>    ** .dt %f .dd $$hostname
> +  ** .dt %F .dd from address of the message, including an '@'
>    ** .dt %H .dd current hour using a 24-hour clock (GMT)
>    ** .dt %m .dd current month number (GMT)
>    ** .dt %M .dd current minute of the hour (GMT)
> diff --git a/messageid.c b/messageid.c
> index 397ff31c..a18c7068 100644
> --- a/messageid.c
> +++ b/messageid.c
> @@ -22,6 +22,7 @@
> =20
>  #include "mutt.h"
>  #include "mutt_random.h"
> +#include "mutt_idna.h"
> =20
>  static char MsgIdPfx =3D 'A';
> =20
> @@ -30,8 +31,34 @@ typedef struct msg_id_data
>    time_t now;
>    struct tm tm;
>    const char *fqdn;
> +  ENVELOPE *env;
>  } MSG_ID_DATA;
> =20
> +static const char *ALLOWED_FROM_CHARS =3D "abcdefghijklmnopqrstuvwxyz"
> +                                        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
> +                                        "1234567890-_.";

I suggest a more generic name for this, which documents the fact that
this is the POSIX Portable Filename Character Set.  Also, I suggest
using intermediate character sets that have a well-known name, to make
it easier to distinguish each character set.

	#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 "._-"  // portable filename c=
haracter set

> +
> +/* This function is much stricter than RFC5322 specifies, because we als=
o want
> + * the message-id to be passed in a URL as a path segment or parameter w=
ithout
> + * needing encoding.
> + */
> +static void filter_from_addr(char *from)
> +{
> +  int has_at =3D 0;
> +
> +  if (!from)
> +    return;
> +
> +  while (*from)
> +  {
> +    if (*from =3D=3D '@' && !has_at)
> +      has_at =3D 1;
> +    else if (!strchr(ALLOWED_FROM_CHARS, *from))

And then here I suggest adding another API, inspired by isascii(3):

	// isascii_c - is [:ascii:] C-locale
	#define ispfchar_c(c)  (!streq(strchrnul(CTYPE_PFCHAR_C, c), ""))

	#define streq(s1, s2)  (!strcmp(s1, s2))

To be able to write it as

	else if (!ispfchar_c(*from))

> +      *from =3D '_';
> +    from++;
> +  }
> +}

In any case, the above seems okay.


Have a lovely day!
Alex

> +
>  static const char *id_format_str(char *dest, size_t destlen, size_t col,
>                                   int cols, char op, const char *src,
>                                   const char *fmt, const char *ifstring,
> @@ -44,6 +71,7 @@ static const char *id_format_str(char *dest, size_t des=
tlen, size_t col,
>    unsigned char r_out[4 + 1];
>    unsigned char z_raw[12]; /* 32 bit timestamp, plus 64 bit randomness */
>    unsigned char z_out[16 + 1];
> +  ADDRESS *from;
> =20
>    switch (op)
>    {
> @@ -107,12 +135,26 @@ static const char *id_format_str(char *dest, size_t=
 destlen, size_t col,
>      case 'f':
>        mutt_format_s(dest, destlen, fmt, id_data->fqdn);
>        break;
> +
> +    case 'F':
> +      from =3D rfc822_cpy_adr(id_data->env->from, 1);
> +      if (!from || !from->mailbox)
> +      {
> +        snprintf(tmp, sizeof(tmp), "@%s", id_data->fqdn);
> +        mutt_format_s(dest, destlen, fmt, tmp);
> +        break;
> +      }
> +      mutt_addrlist_to_intl(from, NULL);
> +      filter_from_addr(from->mailbox);
> +      mutt_format_s(dest, destlen, fmt, from->mailbox);
> +      rfc822_free_address(&from);
> +      break;
>    }
> =20
>    return (src);
>  }
> =20
> -char *mutt_gen_msgid(void)
> +char *mutt_gen_msgid(ENVELOPE *env)
>  {
>    MSG_ID_DATA id_data;
>    BUFFER *buf, *tmp;
> @@ -123,6 +165,7 @@ char *mutt_gen_msgid(void)
>    memcpy(&id_data.tm, gmtime(&id_data.now), sizeof(id_data.tm));
>    if (!(id_data.fqdn =3D mutt_fqdn(0)))
>      id_data.fqdn =3D NONULL(Hostname);
> +  id_data.env =3D env;
> =20
>    fmt =3D MessageIdFormat;
>    if (!fmt)
> diff --git a/protos.h b/protos.h
> index 7ea95ae5..e03ca0eb 100644
> --- a/protos.h
> +++ b/protos.h
> @@ -156,7 +156,7 @@ void mutt_buffer_expand_multi_path_norel(BUFFER *src,=
 const char *delimiter);
>  void mutt_buffer_remove_path_password(BUFFER *dest, const char *src);
>  char *mutt_find_hook(int, const char *);
>  char *mutt_gecos_name(char *, size_t, struct passwd *);
> -char *mutt_gen_msgid(void);
> +char *mutt_gen_msgid(ENVELOPE *);
>  char *mutt_get_body_charset(char *, size_t, BODY *);
>  const char *mutt_get_name(ADDRESS *);
>  char *mutt_get_parameter(const char *, PARAMETER *);
> diff --git a/sendlib.c b/sendlib.c
> index 150bc72d..612b0503 100644
> --- a/sendlib.c
> +++ b/sendlib.c
> @@ -2912,7 +2912,7 @@ void mutt_prepare_envelope(ENVELOPE *env, int final)
>      mutt_set_followup_to(env);
> =20
>      if (!env->message_id)
> -      env->message_id =3D mutt_gen_msgid();
> +      env->message_id =3D mutt_gen_msgid(env);
>    }
> =20
>    /* Take care of 8-bit =3D> 7-bit conversion. */
> @@ -2975,7 +2975,7 @@ static int _mutt_bounce_message(FILE *fp, HEADER *h=
, ADDRESS *to, const char *re
>      fprintf(f, "Resent-Date: %s\n", mutt_b2s(date));
>      mutt_buffer_pool_release(&date);
> =20
> -    msgid_str =3D mutt_gen_msgid();
> +    msgid_str =3D mutt_gen_msgid(h->env);
>      fprintf(f, "Resent-Message-ID: %s\n", msgid_str);
>      fputs("Resent-To: ", f);
>      mutt_write_address_list(to, f, 11, 0);
> --=20
> 2.55.0
>=20

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

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

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

iQIzBAABCgAdFiEES7Jt9u9GbmlWADAi64mZXMKQwqkFAmpwd6cACgkQ64mZXMKQ
wqkn0A/+O7z/sF9yQXvRNwCb20OMLqlmmlXcr7XumUh3AQu+bkyGbHJvo7/P5Rsq
AkhOkcXP9m+Ezs+SlOrfvdGp0rMytpVXe75WD1pjzFqutBgoUdsYll/AwpokAbm8
bNW0vj3EaxC5q/ywq85egEvmHjapeZqAi5ed628upWA+BAiPN9uLQDYiIkQhx4kO
5KumIC6TVHqmgdJMZDuwv87RfS5cPaGIrKi0+H7HbVFcUxwl3UzsHDBwodNicS8y
58rWr/XdJT7OEaMR3ZwdbTvFmqurXfbLqi4Cwv8joh4gWvJZnDmL3QAiM2/0GeWI
TwUH/RnLDNCNM4IU5/FTCBjLotYzclqZYBOYEojBDDjASNeEqAIAHMTGcZNXcCzN
CCGLwIHiHflc/0NKdeep5q7FuwIKFAW3Z0thhS2S3WI+Cl/dIUncARyvcmHGFy34
1E8yYWJkiqzmbyrVuXUmeNBex4nlaLsBQ5cCsE5VjuDKTmd9VFUCJ9zb/CDRV3HU
K6sDqEf6ZPhjSxGRpdzqNa3HMOq6IW8E93tjOlHqISiRWk1AkHPJvpW/Uk5r8P4r
UHjWKHhDloCRscDk3hZZHIejWZxl7IGXMBnY7ND4iW9jXSiojT6H0mM9RrYDSGCU
V8ZrE5VxhQMeRkvewS3ETlH3HAh1e+G7qb5a3tD46r6n5Aa3fU0=
=G30O
-----END PGP SIGNATURE-----

--z5lozktmoy5cygj6--