NeoMutt  2023-03-22
Teaching an old dog new tricks
DOXYGEN
path.c
Go to the documentation of this file.
1
29#include "config.h"
30#include <errno.h>
31#include <libgen.h>
32#include <limits.h>
33#include <pwd.h>
34#include <stdbool.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <unistd.h>
39#include "path.h"
40#include "buffer.h"
41#include "logging.h"
42#include "memory.h"
43#include "message.h"
44#include "string2.h"
45
54bool mutt_path_tidy_slash(char *buf, bool is_dir)
55{
56 if (!buf)
57 return false;
58
59 char *r = buf;
60 char *w = buf;
61
62 while (*r != '\0')
63 {
64 *w++ = *r++;
65
66 if (r[-1] == '/') /* After a '/' ... */
67 {
68 for (; (r[0] == '/') || (r[0] == '.'); r++)
69 {
70 if (r[0] == '/') /* skip multiple /s */
71 continue;
72 if (r[0] == '.')
73 {
74 if (r[1] == '/') /* skip /./ */
75 {
76 r++;
77 continue;
78 }
79 else if (r[1] == '\0') /* skip /.$ */
80 {
81 r[0] = '\0';
82 }
83 break; /* dot-anything-else isn't special */
84 }
85 }
86 }
87 }
88
89 /* Strip a trailing / as long as it's not the only character */
90 if (is_dir && (w > (buf + 1)) && (w[-1] == '/'))
91 w--;
92
93 *w = '\0';
94
95 return true;
96}
97
106{
107 if (!buf || (buf[0] != '/'))
108 return false;
109
110 char *dd = buf;
111
112 mutt_debug(LL_DEBUG3, "Collapse path: %s\n", buf);
113 while ((dd = strstr(dd, "/..")))
114 {
115 if (dd[3] == '/') /* paths follow dots */
116 {
117 char *dest = NULL;
118 if (dd != buf) /* not at start of string */
119 {
120 dd[0] = '\0';
121 dest = strrchr(buf, '/');
122 }
123 if (!dest)
124 dest = buf;
125
126 memmove(dest, dd + 3, strlen(dd + 3) + 1);
127 }
128 else if (dd[3] == '\0') /* dots at end of string */
129 {
130 if (dd == buf) /* at start of string */
131 {
132 dd[1] = '\0';
133 }
134 else
135 {
136 dd[0] = '\0';
137 char *s = strrchr(buf, '/');
138 if (s == buf)
139 s[1] = '\0';
140 else if (s)
141 s[0] = '\0';
142 }
143 }
144 else
145 {
146 dd += 3; /* skip over '/..dir/' */
147 continue;
148 }
149
150 dd = buf; /* restart at the beginning */
151 }
152
153 mutt_debug(LL_DEBUG3, "Collapsed to: %s\n", buf);
154 return true;
155}
156
165bool mutt_path_tidy(char *buf, bool is_dir)
166{
167 if (!buf || (buf[0] != '/'))
168 return false;
169
170 if (!mutt_path_tidy_slash(buf, is_dir))
171 return false;
172
173 return mutt_path_tidy_dotdot(buf);
174}
175
186bool mutt_path_pretty(char *buf, size_t buflen, const char *homedir, bool is_dir)
187{
188 if (!buf)
189 return false;
190
191 mutt_path_tidy(buf, is_dir);
192
193 size_t len = mutt_str_startswith(buf, homedir);
194 if (len == 0)
195 return false;
196
197 if ((buf[len] != '/') && (buf[len] != '\0'))
198 return false;
199
200 buf[0] = '~';
201 if (buf[len] == '\0')
202 {
203 buf[1] = '\0';
204 return true;
205 }
206
207 mutt_str_copy(buf + 1, buf + len, buflen - len);
208 return true;
209}
210
223bool mutt_path_tilde(char *buf, size_t buflen, const char *homedir)
224{
225 if (!buf || (buf[0] != '~'))
226 return false;
227
228 char result[PATH_MAX] = { 0 };
229 char *dir = NULL;
230 size_t len = 0;
231
232 if ((buf[1] == '/') || (buf[1] == '\0'))
233 {
234 if (!homedir)
235 {
236 mutt_debug(LL_DEBUG3, "no homedir\n");
237 return false;
238 }
239
240 len = mutt_str_copy(result, homedir, sizeof(result));
241 dir = buf + 1;
242 }
243 else
244 {
245 char user[128] = { 0 };
246 dir = strchr(buf + 1, '/');
247 if (dir)
248 mutt_str_copy(user, buf + 1, MIN(dir - buf, (unsigned) sizeof(user)));
249 else
250 mutt_str_copy(user, buf + 1, sizeof(user));
251
252 struct passwd *pw = getpwnam(user);
253 if (!pw || !pw->pw_dir)
254 {
255 mutt_debug(LL_DEBUG1, "no such user: %s\n", user);
256 return false;
257 }
258
259 len = mutt_str_copy(result, pw->pw_dir, sizeof(result));
260 }
261
262 size_t dirlen = mutt_str_len(dir);
263 if ((len + dirlen) >= buflen)
264 {
265 mutt_debug(LL_DEBUG3, "result too big for the buffer %ld >= %ld\n", len + dirlen, buflen);
266 return false;
267 }
268
269 mutt_str_copy(result + len, dir, sizeof(result) - len);
270 mutt_str_copy(buf, result, buflen);
271
272 return true;
273}
274
285bool mutt_path_canon(char *buf, size_t buflen, const char *homedir, bool is_dir)
286{
287 if (!buf)
288 return false;
289
290 char result[PATH_MAX] = { 0 };
291
292 if (buf[0] == '~')
293 {
294 mutt_path_tilde(buf, buflen, homedir);
295 }
296 else if (buf[0] != '/')
297 {
298 if (!getcwd(result, sizeof(result)))
299 {
300 mutt_debug(LL_DEBUG1, "getcwd failed: %s (%d)\n", strerror(errno), errno);
301 return false;
302 }
303
304 size_t cwdlen = mutt_str_len(result);
305 size_t dirlen = mutt_str_len(buf);
306 if ((cwdlen + dirlen + 1) >= buflen)
307 {
308 mutt_debug(LL_DEBUG3, "result too big for the buffer %ld >= %ld\n",
309 cwdlen + dirlen + 1, buflen);
310 return false;
311 }
312
313 result[cwdlen] = '/';
314 mutt_str_copy(result + cwdlen + 1, buf, sizeof(result) - cwdlen - 1);
315 mutt_str_copy(buf, result, buflen);
316 }
317
318 if (!mutt_path_tidy(buf, is_dir))
319 return false;
320
321 return true;
322}
323
329const char *mutt_path_basename(const char *f)
330{
331 if (!f)
332 return NULL;
333
334 const char *p = strrchr(f, '/');
335 if (p)
336 return p + 1;
337 return f;
338}
339
351char *mutt_path_concat(char *d, const char *dir, const char *fname, size_t l)
352{
353 if (!d || !dir || !fname)
354 return NULL;
355
356 const char *fmt = "%s/%s";
357
358 if ((fname[0] == '\0') || ((dir[0] != '\0') && (dir[strlen(dir) - 1] == '/')))
359 fmt = "%s%s";
360
361 snprintf(d, l, fmt, dir, fname);
362 return d;
363}
364
376char *mutt_path_dirname(const char *path)
377{
378 if (!path)
379 return NULL;
380
381 char buf[PATH_MAX] = { 0 };
382 mutt_str_copy(buf, path, sizeof(buf));
383 return mutt_str_dup(dirname(buf));
384}
385
397bool mutt_path_to_absolute(char *path, const char *reference)
398{
399 if (!path || !reference)
400 return false;
401
402 char abs_path[PATH_MAX] = { 0 };
403 int path_len;
404
405 /* if path is already absolute, don't do anything */
406 if ((strlen(path) > 1) && (path[0] == '/'))
407 {
408 return true;
409 }
410
411 char *dirpath = mutt_path_dirname(reference);
412 mutt_str_copy(abs_path, dirpath, sizeof(abs_path));
413 FREE(&dirpath);
414 mutt_strn_cat(abs_path, sizeof(abs_path), "/", 1); /* append a / at the end of the path */
415
416 path_len = sizeof(abs_path) - strlen(path);
417
418 mutt_strn_cat(abs_path, sizeof(abs_path), path, (path_len > 0) ? path_len : 0);
419
420 path = realpath(abs_path, path);
421 if (!path && (errno != ENOENT))
422 {
423 mutt_perror(_("Error: converting path to absolute"));
424 return false;
425 }
426
427 return true;
428}
429
440size_t mutt_path_realpath(char *buf)
441{
442 if (!buf)
443 return 0;
444
445 char s[PATH_MAX] = { 0 };
446
447 if (!realpath(buf, s))
448 return 0;
449
450 return mutt_str_copy(buf, s, sizeof(s));
451}
452
458bool mutt_path_parent(char *buf)
459{
460 if (!buf)
461 return false;
462
463 int n = mutt_str_len(buf);
464 if (n < 2)
465 return false;
466
467 if (buf[n - 1] == '/')
468 n--;
469
470 // Find the previous '/'
471 for (n--; ((n >= 0) && (buf[n] != '/')); n--)
472 ; // do nothing
473
474 if (n == 0) // Always keep at least one '/'
475 n++;
476
477 buf[n] = '\0';
478 return true;
479}
480
490bool mutt_path_abbr_folder(char *buf, const char *folder)
491{
492 if (!buf || !folder)
493 return false;
494
495 size_t flen = mutt_str_len(folder);
496 if (flen < 2)
497 return false;
498
499 if (folder[flen - 1] == '/')
500 flen--;
501
502 if (!mutt_strn_equal(buf, folder, flen))
503 return false;
504
505 if (buf[flen + 1] == '\0') // Don't abbreviate to '=/'
506 return false;
507
508 size_t rlen = mutt_str_len(buf + flen + 1);
509
510 buf[0] = '=';
511 memmove(buf + 1, buf + flen + 1, rlen + 1);
512 return true;
513}
514
520char *mutt_path_escape(const char *src)
521{
522 if (!src)
523 return NULL;
524
525 static char dest[STR_COMMAND];
526 char *destp = dest;
527 int destsize = 0;
528
529 while (*src && (destsize < sizeof(dest) - 1))
530 {
531 if (*src != '\'')
532 {
533 *destp++ = *src++;
534 destsize++;
535 }
536 else
537 {
538 /* convert ' into '\'' */
539 if (destsize + 4 < sizeof(dest))
540 {
541 *destp++ = *src++;
542 *destp++ = '\\';
543 *destp++ = '\'';
544 *destp++ = '\'';
545 destsize += 4;
546 }
547 else
548 break;
549 }
550 }
551 *destp = '\0';
552
553 return dest;
554}
555
561const char *mutt_path_getcwd(struct Buffer *cwd)
562{
563 if (!cwd)
564 return NULL;
565
567 char *retval = getcwd(cwd->data, cwd->dsize);
568 while (!retval && (errno == ERANGE))
569 {
570 mutt_buffer_alloc(cwd, cwd->dsize + 256);
571 retval = getcwd(cwd->data, cwd->dsize);
572 }
573 if (retval)
575 else
577
578 return retval;
579}
void mutt_buffer_alloc(struct Buffer *buf, size_t new_size)
Make sure a buffer can store at least new_size bytes.
Definition: buffer.c:313
void mutt_buffer_fix_dptr(struct Buffer *buf)
Move the dptr to end of the Buffer.
Definition: buffer.c:189
void mutt_buffer_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:85
General purpose object for storing and parsing strings.
#define mutt_debug(LEVEL,...)
Definition: logging.h:84
#define mutt_perror(...)
Definition: logging.h:88
Logging Dispatcher.
@ LL_DEBUG3
Log at debug level 3.
Definition: logging.h:42
@ LL_DEBUG1
Log at debug level 1.
Definition: logging.h:40
Memory management wrappers.
#define FREE(x)
Definition: memory.h:43
#define MIN(a, b)
Definition: memory.h:31
Message logging.
#define _(a)
Definition: message.h:28
bool mutt_path_tidy_dotdot(char *buf)
Remove dot-dot-slash from a path.
Definition: path.c:105
bool mutt_path_tidy(char *buf, bool is_dir)
Remove unnecessary parts of a path.
Definition: path.c:165
const char * mutt_path_basename(const char *f)
Find the last component for a pathname.
Definition: path.c:329
bool mutt_path_tilde(char *buf, size_t buflen, const char *homedir)
Expand '~' in a path.
Definition: path.c:223
char * mutt_path_concat(char *d, const char *dir, const char *fname, size_t l)
Join a directory name and a filename.
Definition: path.c:351
bool mutt_path_to_absolute(char *path, const char *reference)
Convert relative filepath to an absolute path.
Definition: path.c:397
bool mutt_path_abbr_folder(char *buf, const char *folder)
Create a folder abbreviation.
Definition: path.c:490
char * mutt_path_escape(const char *src)
Escapes single quotes in a path for a command string.
Definition: path.c:520
bool mutt_path_pretty(char *buf, size_t buflen, const char *homedir, bool is_dir)
Tidy a filesystem path.
Definition: path.c:186
char * mutt_path_dirname(const char *path)
Return a path up to, but not including, the final '/'.
Definition: path.c:376
bool mutt_path_tidy_slash(char *buf, bool is_dir)
Remove unnecessary slashes and dots.
Definition: path.c:54
bool mutt_path_canon(char *buf, size_t buflen, const char *homedir, bool is_dir)
Create the canonical version of a path.
Definition: path.c:285
const char * mutt_path_getcwd(struct Buffer *cwd)
Get the current working directory.
Definition: path.c:561
bool mutt_path_parent(char *buf)
Find the parent of a path.
Definition: path.c:458
size_t mutt_path_realpath(char *buf)
Resolve path, unraveling symlinks.
Definition: path.c:440
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
bool mutt_strn_equal(const char *a, const char *b, size_t num)
Check for equality of two strings (to a maximum), safely.
Definition: string.c:496
char * mutt_strn_cat(char *d, size_t l, const char *s, size_t sl)
Concatenate two strings.
Definition: string.c:294
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:227
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:567
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:652
#define PATH_MAX
Definition: mutt.h:41
Path manipulation functions.
String manipulation functions.
#define STR_COMMAND
Enough space for a long command line.
Definition: string2.h:35
String manipulation buffer.
Definition: buffer.h:34
size_t dsize
Length of data.
Definition: buffer.h:37
char * data
Pointer to data.
Definition: buffer.h:35