Re: [PATCH] Add filename filters when saving attachments.

Alejandro Colomar via Mutt-dev <[email protected]> Sat, 9 May 2026 14:02:54 +0200
Newsgroups gmane.mail.mutt.devel
Message-ID <af8guf6an9ZhO6vM@devuan>
Hi Kevin,

On 2026-05-09T18:59:22+0800, Kevin J. McCarthy wrote:
> In send mode (the compose menu), simply take the basename, since it's
> attached by the user already.
> 
> In receive mode, try to keep the whole filename, even if it orginally
> contained path characters.  Filter "/" and non-printables into an
> underscore "_".
> ---
> 
> This is an early v2 patch, sent out to solicit feedback.
> 
> The attachment saving code is a bit of a maze, so there isn't a need to
> understand it all.
> 
> Suffice to say I've swapped out the various places in it that directly
> reference the attachment name to use a version that has been filtered by
> safe_attachment_filename() just below.
> 
> Right now, in receive mode, this filters "/" and unprintables into "_".
> I could use a stricter sanitizer function, but I think that would be too
> strict and irritate users.
> 
> I could also simply filter only "/" -> "_", which might be all we need
> to do.  Comments?

I think I'd do the simplest change.  And if we want to do more, maybe
split that into its own commit, with a different justification in the
commit message.

> 
>  muttlib.c    | 11 ++++++++---
>  recvattach.c | 51 ++++++++++++++++++++++++++++++++++++++++++---------
>  2 files changed, 50 insertions(+), 12 deletions(-)
> 
> diff --git a/muttlib.c b/muttlib.c
> index 6eabdec2..858a4bcf 100644
> --- a/muttlib.c
> +++ b/muttlib.c
> @@ -1277,8 +1277,13 @@ void mutt_expand_fmt(BUFFER *dest, const char *fmt, const char *src)
>    }
>  }
>  
> -/* return 0 on success, -1 on abort, 1 on error */
> -int mutt_check_overwrite(const char *attname, const char *path,
> +/* return 0 on success, -1 on abort, 1 on error
> + *
> + * safe_attname should have been filtered through safe_attachment_filename()
> + * in recvattach.c to either get the basename in sendmode,
> + * or to filter '/' and unsafe characters in recvmode.
> + * */
> +int mutt_check_overwrite(const char *safe_attname, const char *path,
>                           BUFFER *fname, int *append, char **directory)
>  {
>    int rc = 0;
> @@ -1321,7 +1326,7 @@ int mutt_check_overwrite(const char *attname, const char *path,
>        return (rc == MUTT_NO) ? 1 : -1;
>  
>      tmp = mutt_buffer_pool_get();
> -    mutt_buffer_strcpy(tmp, mutt_basename(NONULL(attname)));
> +    mutt_buffer_strcpy(tmp, safe_attname);
>      if ((mutt_buffer_get_field(_("File under directory: "), tmp,
>                                 MUTT_FILE | MUTT_CLEAR) != 0) ||
>          !mutt_buffer_len(tmp))
> diff --git a/recvattach.c b/recvattach.c
> index 3b172e4c..f64a0008 100644
> --- a/recvattach.c
> +++ b/recvattach.c
> @@ -518,23 +518,49 @@ static int save_attachment_helper(FILE *fp, BODY *m, const char *path,
>    return rc;
>  }
>  
> +static void safe_attachment_filename(BUFFER *safe_filename, const char *filename,
> +                                     int sendmode)
> +{
> +  if (sendmode)
> +    mutt_buffer_strcpy(safe_filename, mutt_basename(filename));
> +  else
> +  {
> +    /* Alternatively, we could use
> +     *   mutt_buffer_sanitize_filename(safe_filename, filename, MUTT_SANITIZE_ALLOW_8BIT);
> +     * Comments?
> +     */
> +    mutt_buffer_clear(safe_filename);
> +
> +    for (; *filename; filename++)
> +    {
> +      if (*filename == '/' || !IsPrint((unsigned char) *filename))

AFAICS, IsPrint() already does the necessary cast.

	alx@devuan:~/src/mutt/mutt/master$ grepc -B1 -A3 -n IsPrint .
	./protos.h-452-#ifdef LOCALES_HACK
	./protos.h:453:#define IsPrint(c) (isprint((unsigned char)(c)) ||      \
			    ((unsigned char)(c) >= 0xa0))
	./protos.h-455-#define IsWPrint(wc) (iswprint(wc) || wc >= 0xa0)
	./protos.h-456-#else
	./protos.h:457:#define IsPrint(c) (isprint((unsigned char)(c)) ||      \
			    (option(OPTLOCALES) ? 0 :           \
			     ((unsigned char)(c) >= 0xa0)))
	./protos.h-460-#define IsWPrint(wc) (iswprint(wc) ||                           \
	./protos.h-461-                      (option(OPTLOCALES) ? 0 : (wc >= 0xa0)))
	./protos.h-462-#endif

> +        mutt_buffer_addch(safe_filename, '_');
> +      else
> +        mutt_buffer_addch(safe_filename, *filename);
> +    }
> +  }
> +}


Have a lovely day!
Alex

-- 
<https://www.alejandro-colomar.es>
signature.asc (application/pgp-signature, 833 B)
-----BEGIN PGP SIGNATURE-----

iQIzBAABCgAdFiEES7Jt9u9GbmlWADAi64mZXMKQwqkFAmn/ImgACgkQ64mZXMKQ
wqngphAAoe4r+I3p4/08/2h1MksgzZB6eFoSZtZUfBPQCrt8pSo3Nt0LunbiPIl8
pqdCYq7Qw/uVR9UYGB60x8WLCBC8SyLAbIJI/J63jPpOlK3KKpKf2UuDhGEF6F6r
a7HOCPYjuyl2oZ9/DLaTe28gTbYn38VAevuk1wblyl0SO4UVhFZDLZWuIL4ehp/i
jk4neZmq+3e50dAcj/G+Sj36uBf8Mz47ufVLRqFsOw5Wd5QzsPgXtWLPSu6dUnyF
p1KFGbBlEFPc3K1ij+mYK5W6D7xdD3d3wdd7duZzuU3g42WndNDQrmpaMss2I6uG
1AUyaFKUKaEl+4He2kwx528S10MK3T4/yt43RB3SM5zhix83xy59Ytj2tz0VER9C
pADEdC6Vo5pa9FbRSdARPlyxrS28NnVbD0l4E7p8QE8v5AjQibpJFFczTG19aWQz
pYcZCnp9h0Ue9QXhw2aEi1mEXnC2ehWCYvjE4ZKrVsaMRiovZ4jNtMKbM/PUG+ca
ne2813oWDRs/0GfauKsR8WnyLDoGdkBMh2Bku8BVXs8OXlpFv3oGBbErbOJaCIqS
uhgPrSJH/6l9tPaWdT161be9nLYtApSs2Hjthjy1YBiTsTj23XOMh+sal3avbmnw
5EEEAXhpbclsZb2D9ZazyAIWzyvJxrrkt7ORurXSbJa7JrNF2/M=
=tuBD
-----END PGP SIGNATURE-----