Re: mutt_buffer_concat_path is broken [PATCH: 1/1]

"Kevin J. McCarthy" <[email protected]> Thu, 7 May 2026 11:03:43 +0800
Newsgroups gmane.mail.mutt.devel
Message-ID <afwBD9R-hrxv9h6K@qinghai>
On Wed, May 06, 2026 at 03:56:34PM -0400, Derek Martin wrote:
>So, I came upon this by random happenstance, but this is a problem
>I've had to fix numerous times in production code, because seemingly
>absolutely no one realizes that concatenating paths correctly is
>substantially more complicated to get right than you'd initially
>expect.

Hi Derek,

Thanks for taking the time to write a patch!

First some general comments.

Please don't change the return type.
Mutt's general policy is to not pass NULL strings around, instead it 
wraps parameters that might be NULL inside the NONULL() macro.  See, for 
example, the invocations in init.c:3839,3841.  Similarly, if the caller 
passes a NULL "BUFFER *d" argument, let the program crash: that's just 
completely fubar.

So, deal with !*dir and !*fname, but don't check for dir and fname (or 
d) being NULL.

Also please match the formatting style used in Mutt, i.e. Allman curly 
brace style, being free to omit the braces for single lines if you like.

diff --git a/muttlib.c b/muttlib.c
index 59a48378..5a58d0ec 100644
--- a/muttlib.c
+++ b/muttlib.c
@@ -1387,14 +1387,48 @@ void mutt_safe_path(BUFFER *dest, ADDRESS *a)
        *p = '_';
  }
  
-void mutt_buffer_concat_path(BUFFER *d, const char *dir, const char *fname)
-{
-  const char *fmt = "%s/%s";
-
-  if (!*fname || (*dir && dir[strlen(dir)-1] == '/'))
-    fmt = "%s%s";
-
-  mutt_buffer_printf(d, fmt, dir, fname);
+BUFFER *mutt_buffer_concat_path_new(BUFFER *d, const char *dir, const char *fname)

As commented about, leave this as void return type.

+{
+  /* arguments are broken; return NULL */
+  if (!d || (!dir && !fname)) return NULL;

And don't check this.

Previously, the code implicitly cleared the BUFFER (inside 
mutt_buffer_printf().  So you should call mutt_buffer_clear(d) before 
anything else.

+  if (dir && *dir){

I'll stop repeating this now, but all checks for NULL dir and fname 
should be taken out here and below.

+    if (strcmp(dir, "/") != 0){
+      /* dir IS NOT the root directory */
+      mutt_buffer_addstr(d, dir);
+      if (fname && *fname){
+        if (fname[0] == '/' && dir[strlen(dir)-1] == '/'){
+          /* avoid double slash when dir is the root directory and file name starts with a slash */

I think this comment was what you were talking about in your reply to
yourself, so make sure to fix it.  If you don't think it's too verbose,
it might be great to see examples along with the comment.  e.g.:
           /* avoid double slash: "/dir/" + "/fname/foo.txt" */


+          mutt_buffer_addstr(d, &fname[1]);
+        } else {
+          if (fname[0] != '/' && dir[strlen(dir)-1] != '/'){
+            /* avoid double slash when dir is the root directory */

            /* add missing slash: "/dir" + "fname/foo.txt" */

+            mutt_buffer_addch(d, '/');
+          }
+          /* append the file name */
+          mutt_buffer_addstr(d, fname);
+        }
+      }
+    } else {
+      /* dir IS the root directory */
+      if (fname && *fname){
+        if (fname[0] == '/'){
+          /* file name already starts with a slash, just append it */

           /* "/" + "/fname/foo.txt" */

+          mutt_buffer_addstr(d, fname);
+        } else {

          /* "/" + "fname/foo.txt" */

+            /* file name doesn't start with '/' */
+            mutt_buffer_addch(d, '/');
+            mutt_buffer_addstr(d, fname);
+        }
+      } else {
+        /* dir is the root directory and fname is empty, add a slash */
+        mutt_buffer_addch(d, '/');
+      }
+    }
+  } else {
+    /* dir is empty, just append the file name */
+      mutt_buffer_addstr(d, fname);
+  }
+  return d;
  }


-- 
Kevin J. McCarthy
GPG Fingerprint: 8975 A9B3 3AA3 7910 385C  5308 ADEF 7684 8031 6BDA
signature.asc (application/pgp-signature, 833 B)
-----BEGIN PGP SIGNATURE-----

iQIzBAEBCgAdFiEEiXWpszqjeRA4XFMIre92hIAxa9oFAmn8AQ8ACgkQre92hIAx
a9qRHBAAwtzPd+5oSERqYMtfoGfb5h69kergdiuc6RmN+IDl5/PrUx1I7kCwGzSo
B/xB6n6XtuB9DBNN30MMthLH/9IGMvVdOMFR5+QMhgSE5n2kXm1bfYutPa9kEFtN
d7s3unzfMyFmFXlAzGP8EbMUYDxsxpX25xQMZ8uqcWk90e/TdvufWMZZCSNQDZKS
go90ayBSMHjM1h4WX38Nn619VaOeQAFDsVRSaNF9agaZtRUewzjA4KKDnXcMTmdz
QzHrs8O56pkI4D3olfu2v/AsK8sgb+oOJaTR4klca+31CLjWCQLyY5uFM9iFWQNX
Bm5M5nxiMzjwpOCQBqIiCa5m11VUfSd5hPs0DeIgpwRDHEMjZvHRC0I5PASdehGE
l6ryKgzcljxOVZjqMon8WtZ/tKw3YJft8kP+JYeyIfPo2e03SeqaqaL6V4+NKkhS
2BHa7XqQG0sfqbpTk94PLUI7+4d2tVd1BfYXWRpvlwY7MFB9fjFv2Z4rU02QgFDy
XxdifoQlZzPNHET/tUPG5Prapa46IhOBESt8l9Gu4hesm0rF3/cJPYNZLLUO8VfW
4jymDAnbLsPRsUQa3EdHU6XTxAglGyc9Pcdn5C3iq2WTjqcItTxtLtJ6TkRAXATx
2ws/UD1SSOrGtYobonBhrKfLgb7bZgGzLypE6vrfFZUk7dtFEaw=
=TYjY
-----END PGP SIGNATURE-----