From: Michael J. Bazzinotti Date: Wed, 31 Jan 2018 17:20:05 +0000 (-0500) Subject: browser: mutt_realpath() X-Git-Tag: neomutt-20180223~23 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=refs%2Fpull%2F1037%2Fhead;p=neomutt browser: mutt_realpath() For "regular" filesystems (not IMAP or Notmuch folder hierarchies), and the browser GUI now resolve paths via a new mutt library function `mutt_realpath`. This means no more path buildup such as "a/b/../../c". Additionally, Symlinks are resolved automatically. This is a circumstance of realpath(). Note that a future PR is planned to add a non-symlinking option. The new function has a default shortcut key of 'p' for parent. Since this function uses the pre-existing `mutt_get_parent_path()`, may also work on IMAP and Notmuch folder hierarchies (tested and working on IMAP) Note: The paths passed to `mutt_get_parent_path()` formerly would not parse correctly a path which included a final trailing slash (eg 'abc/') So, I added it a check to that function to remove it before hitting the parser. Note2: The browser for IMAP was janky before this commit, but the browser is not the primary mailbox navigation tool, afterall. At least 'p' adds to its functionality. I expect a similar evaluation for Notmuch folder browsing, although I have never used Notmuch. Any contributions to make a better IMAP/Notmuch folder browsing experience are welcome. I have reported some of the fallbacks in Github PR #1037 comments (look for IMAP headlines). --- diff --git a/browser.c b/browser.c index 53723c273..4d2a3b594 100644 --- a/browser.c +++ b/browser.c @@ -1509,16 +1509,22 @@ void mutt_select_file(char *f, size_t flen, int flags, char ***files, int *numfi } else #endif - if (examine_directory(menu, &state, LastDir, prefix) == -1) { - /* try to restore the old values */ - mutt_str_strfcpy(LastDir, OldLastDir, sizeof(LastDir)); if (examine_directory(menu, &state, LastDir, prefix) == -1) { - mutt_str_strfcpy(LastDir, NONULL(HomeDir), sizeof(LastDir)); - goto bail; + /* try to restore the old values */ + mutt_str_strfcpy(LastDir, OldLastDir, sizeof(LastDir)); + if (examine_directory(menu, &state, LastDir, prefix) == -1) + { + mutt_str_strfcpy(LastDir, NONULL(HomeDir), sizeof(LastDir)); + goto bail; + } } + /* resolve paths navigated from GUI */ + if (mutt_realpath(LastDir) == 0) + break; } + browser_highlight_default(&state, menu); init_menu(&state, menu, title, sizeof(title), buffy); if (GotoSwapper[0]) @@ -1686,6 +1692,7 @@ void mutt_select_file(char *f, size_t flen, int flags, char ***files, int *numfi break; #endif + case OP_GOTO_PARENT: case OP_CHANGE_DIRECTORY: #ifdef USE_NNTP @@ -1707,7 +1714,16 @@ void mutt_select_file(char *f, size_t flen, int flags, char ***files, int *numfi } } - if (mutt_get_field(_("Chdir to: "), buf, sizeof(buf), MUTT_FILE) == 0 && buf[0]) + if (i == OP_CHANGE_DIRECTORY) + { + int ret = mutt_get_field(_("Chdir to: "), buf, sizeof(buf), MUTT_FILE); + if (ret != 0) + break; + } + else if (i == OP_GOTO_PARENT) + mutt_get_parent_path(buf, buf, sizeof(buf)); + + if (buf[0]) { buffy = 0; mutt_expand_path(buf, sizeof(buf)); @@ -1735,6 +1751,12 @@ void mutt_select_file(char *f, size_t flen, int flags, char ***files, int *numfi mutt_file_concat_path(tmp, LastDir, buf, sizeof(tmp)); mutt_str_strfcpy(buf, tmp, sizeof(buf)); } + /* Resolve path from + * Avoids buildup such as /a/b/../../c + * Symlinks are always unraveled to keep code simple */ + if (mutt_realpath(buf) == 0) + break; + if (stat(buf, &st) == 0) { if (S_ISDIR(st.st_mode)) diff --git a/functions.h b/functions.h index 4430c41fa..23645fd5b 100644 --- a/functions.h +++ b/functions.h @@ -472,6 +472,7 @@ const struct Binding OpAlias[] = { /* map: alias */ /* The file browser */ const struct Binding OpBrowser[] = { /* map: browser */ { "change-dir", OP_CHANGE_DIRECTORY, "c" }, + { "goto-parent", OP_GOTO_PARENT, "p" }, { "display-filename", OP_BROWSER_TELL, "@" }, { "enter-mask", OP_ENTER_MASK, "m" }, { "sort", OP_SORT, "o" }, diff --git a/muttlib.c b/muttlib.c index 322e9a0d5..a412b1eda 100644 --- a/muttlib.c +++ b/muttlib.c @@ -1466,6 +1466,10 @@ void mutt_get_parent_path(char *output, char *path, size_t olen) mutt_str_strfcpy(output, path, olen); int n = mutt_str_strlen(output); + /* remove any final trailing '/' */ + if (output[n - 1] == '/') + output[n - 1] = '\0'; + /* Remove everything until the next slash */ for (n--; ((n >= 0) && (output[n] != '/')); n--) ; @@ -1480,6 +1484,26 @@ void mutt_get_parent_path(char *output, char *path, size_t olen) } } +/** + * mutt_realpath - resolve path, unraveling symlinks + * @param buf Buffer containing path + * @retval len String length of resolved path + * @retval 0 Error, buf is not overwritten + * + * Resolve and overwrite the path in buf. + * + * @note Size of buf should be at least PATH_MAX bytes. + */ +size_t mutt_realpath(char *buf) +{ + char s[PATH_MAX]; + + if (realpath(buf, s) == NULL) + return 0; + + return mutt_str_strfcpy(buf, s, PATH_MAX); +} + char debugfilename[_POSIX_PATH_MAX]; FILE *debugfile = NULL; int debuglevel; diff --git a/opcodes.h b/opcodes.h index 090c490c6..5dab7238b 100644 --- a/opcodes.h +++ b/opcodes.h @@ -43,6 +43,7 @@ _fmt(OP_BUFFY_LIST, N_("list mailboxes with new mail")) \ _fmt(OP_CATCHUP, N_("mark all articles in newsgroup as read")) \ _fmt(OP_CHANGE_DIRECTORY, N_("change directories")) \ + _fmt(OP_GOTO_PARENT, N_("go to parent directory")) \ _fmt(OP_CHECK_NEW, N_("check mailboxes for new mail")) \ _fmt(OP_COMPOSE_ATTACH_FILE, N_("attach file(s) to this message")) \ _fmt(OP_COMPOSE_ATTACH_MESSAGE, N_("attach message(s) to this message")) \ diff --git a/protos.h b/protos.h index e2595e95a..75b42288a 100644 --- a/protos.h +++ b/protos.h @@ -338,6 +338,7 @@ int mutt_save_confirm(const char *s, struct stat *st); void mutt_browser_select_dir(char *f); void mutt_get_parent_path(char *output, char *path, size_t olen); +size_t mutt_realpath(char *buf); #define MUTT_RANDTAG_LEN (16) void mutt_rand_base32(void *out, size_t len);