From a5063aa8b174db286a0e83b8ffdd4e65c521f733 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Wed, 24 Jul 2019 14:28:13 -0700 Subject: [PATCH] Do not treat ~nosuchuser as an absolute file name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Derived from Ken Brown’s patch (Bug#36502#97). * doc/lispref/files.texi (Relative File Names): * etc/NEWS: Document this. * src/fileio.c (user_homedir): New function. (Fexpand_file_name, file_name_absolute_p): Use it. (search_embedded_absfilename): Simplify via file_name_absolute_p. * test/src/fileio-tests.el (fileio-tests--no-such-user): New test. --- doc/lispref/files.texi | 10 +++- etc/NEWS | 3 + src/fileio.c | 121 ++++++++++++++++++--------------------- test/src/fileio-tests.el | 11 ++++ 4 files changed, 78 insertions(+), 67 deletions(-) diff --git a/doc/lispref/files.texi b/doc/lispref/files.texi index 0519f787dc..0ea8a4f0a1 100644 --- a/doc/lispref/files.texi +++ b/doc/lispref/files.texi @@ -2154,7 +2154,11 @@ Relative File Names @defun file-name-absolute-p filename This function returns @code{t} if file @var{filename} is an absolute -file name or begins with @samp{~}, @code{nil} otherwise. +file name, @code{nil} otherwise. A file name is considered to be +absolute if its first component is @samp{~}, or is @samp{~@var{user}} +where @var{user} is a valid login name. In the following examples, +assume that there is a user named @samp{rms} but no user named +@samp{nosuchuser}. @example @group @@ -2162,6 +2166,10 @@ Relative File Names @result{} t @end group @group +(file-name-absolute-p "~nosuchuser/foo") + @result{} nil +@end group +@group (file-name-absolute-p "rms/foo") @result{} nil @end group diff --git a/etc/NEWS b/etc/NEWS index 5313270411..08f0e654f7 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1815,6 +1815,9 @@ relative to the 'default-directory' of the current buffer. We recommend always setting "$HOME" to an absolute file name, so that its meaning is independent of where Emacs was started. +** file-name-absolute-p no longer considers "~foo" to be an absolute +file name if there is no user named "foo". + ** The FILENAME argument to 'file-name-base' is now mandatory and no longer defaults to 'buffer-file-name'. diff --git a/src/fileio.c b/src/fileio.c index e4269b96a3..d1a7f39ac9 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -744,6 +744,31 @@ file_name_absolute_no_tilde_p (Lisp_Object name) return IS_ABSOLUTE_FILE_NAME (SSDATA (name)); } +/* Return the home directory of the user NAME, or a null pointer if + NAME is empty or the user does not exist or the user's home + directory is not an absolute file name. NAME is an array of bytes + that continues up to (but not including) the next NUL byte or + directory separator. The returned string lives in storage good + until the next call to this or similar functions. */ +static char * +user_homedir (char const *name) +{ + ptrdiff_t length; + for (length = 0; name[length] && !IS_DIRECTORY_SEP (name[length]); length++) + continue; + if (length == 0) + return NULL; + USE_SAFE_ALLOCA; + char *p = SAFE_ALLOCA (length + 1); + memcpy (p, name, length); + p[length] = 0; + struct passwd *pw = getpwnam (p); + SAFE_FREE (); + if (!pw || (pw->pw_dir && !IS_ABSOLUTE_FILE_NAME (pw->pw_dir))) + return NULL; + return pw->pw_dir; +} + DEFUN ("expand-file-name", Fexpand_file_name, Sexpand_file_name, 1, 2, 0, doc: /* Convert filename NAME to absolute, and canonicalize it. Second arg DEFAULT-DIRECTORY is directory to start with if NAME is relative @@ -788,7 +813,6 @@ the root directory. */) char *target; ptrdiff_t tlen; - struct passwd *pw; #ifdef DOS_NT int drive = 0; bool collapse_newdir = true; @@ -1153,39 +1177,29 @@ the root directory. */) } else /* ~user/filename */ { - char *o, *p; - for (p = nm; *p && !IS_DIRECTORY_SEP (*p); p++) - continue; - o = SAFE_ALLOCA (p - nm + 1); - memcpy (o, nm, p - nm); - o[p - nm] = 0; - - block_input (); - pw = getpwnam (o + 1); - unblock_input (); - if (pw) + char *nmhome = user_homedir (nm + 1); + if (nmhome) { - Lisp_Object tem; - - newdir = pw->pw_dir; - /* `getpwnam' may return a unibyte string, which will - bite us when we expect the directory to be multibyte. */ - tem = make_unibyte_string (newdir, strlen (newdir)); - newdirlim = newdir + SBYTES (tem); - if (multibyte && !STRING_MULTIBYTE (tem)) + ptrdiff_t nmhomelen = strlen (nmhome); + newdir = nmhome; + newdirlim = newdir + nmhomelen; + if (multibyte) { - hdir = DECODE_FILE (tem); + AUTO_STRING_WITH_LEN (lisp_nmhome, nmhome, nmhomelen); + hdir = DECODE_FILE (lisp_nmhome); newdir = SSDATA (hdir); newdirlim = newdir + SBYTES (hdir); } - nm = p; + + while (*++nm && !IS_DIRECTORY_SEP (*nm)) + continue; #ifdef DOS_NT collapse_newdir = false; #endif } /* If we don't find a user of that name, leave the name - unchanged; don't move nm forward to p. */ + unchanged. */ } } @@ -1667,18 +1681,6 @@ See also the function `substitute-in-file-name'.") } #endif -bool -file_name_absolute_p (const char *filename) -{ - return - (IS_DIRECTORY_SEP (*filename) || *filename == '~' -#ifdef DOS_NT - || (IS_DRIVE (*filename) && IS_DEVICE_SEP (filename[1]) - && IS_DIRECTORY_SEP (filename[2])) -#endif - ); -} - /* Put into BUF the concatenation of DIR and FILE, with an intervening directory separator if needed. Return a pointer to the NUL byte at the end of the concatenated string. */ @@ -1774,7 +1776,10 @@ get_homedir (void) return ahome; } -/* If /~ or // appears, discard everything through first slash. */ +/* If a directory separator followed by an absolute file name (e.g., + "//foo", "/~", "/~someuser") appears in NM, return the address of + the absolute file name. Otherwise return NULL. ENDP is the + address of the null byte at the end of NM. */ static char * search_embedded_absfilename (char *nm, char *endp) { @@ -1784,34 +1789,8 @@ search_embedded_absfilename (char *nm, char *endp) && !IS_DIRECTORY_SEP (p[1])); #endif for (; p < endp; p++) - { - if (IS_DIRECTORY_SEP (p[-1]) && file_name_absolute_p (p)) - { - char *s; - for (s = p; *s && !IS_DIRECTORY_SEP (*s); s++) - continue; - if (p[0] == '~' && s > p + 1) /* We've got "/~something/". */ - { - USE_SAFE_ALLOCA; - char *o = SAFE_ALLOCA (s - p + 1); - struct passwd *pw; - memcpy (o, p, s - p); - o [s - p] = 0; - - /* If we have ~user and `user' exists, discard - everything up to ~. But if `user' does not exist, leave - ~user alone, it might be a literal file name. */ - block_input (); - pw = getpwnam (o + 1); - unblock_input (); - SAFE_FREE (); - if (pw) - return p; - } - else - return p; - } - } + if (IS_DIRECTORY_SEP (p[-1]) && file_name_absolute_p (p)) + return p; return NULL; } @@ -2696,13 +2675,23 @@ This happens for interactive use with M-x. */) DEFUN ("file-name-absolute-p", Ffile_name_absolute_p, Sfile_name_absolute_p, 1, 1, 0, - doc: /* Return t if FILENAME is an absolute file name or starts with `~'. -On Unix, absolute file names start with `/'. */) + doc: /* Return t if FILENAME is an absolute file name. +On Unix, absolute file names start with `/'. In Emacs, an absolute +file name can also start with an initial `~' or `~USER' component, +where USER is a valid login name. */) (Lisp_Object filename) { CHECK_STRING (filename); return file_name_absolute_p (SSDATA (filename)) ? Qt : Qnil; } + +bool +file_name_absolute_p (char const *filename) +{ + return (IS_ABSOLUTE_FILE_NAME (filename) + || (filename[0] == '~' + && (!filename[1] || user_homedir (&filename[1])))); +} DEFUN ("file-exists-p", Ffile_exists_p, Sfile_exists_p, 1, 1, 0, doc: /* Return t if file FILENAME exists (whether or not you can read it). diff --git a/test/src/fileio-tests.el b/test/src/fileio-tests.el index 813ee5f798..09a5b147e1 100644 --- a/test/src/fileio-tests.el +++ b/test/src/fileio-tests.el @@ -136,3 +136,14 @@ fileio-tests--symlink-failure (name (expand-file-name "bar"))) (should (and (file-name-absolute-p name) (not (eq (aref name 0) ?~)))))) + +(ert-deftest fileio-tests--no-such-user () + "Test file-name-absolute-p on ~nosuchuser." + (unless (user-full-name "nosuchuser") + (should (not (file-name-absolute-p "~nosuchuser"))) + (should (not (file-name-absolute-p "~nosuchuser/"))) + (should (not (file-name-absolute-p "~nosuchuser//"))) + (should (not (file-name-absolute-p "~nosuchuser/foo"))) + (should (not (file-name-absolute-p "~nosuchuser/foo/"))) + (should (not (file-name-absolute-p "~nosuchuser/foo//"))) + (should (not (file-name-absolute-p "~nosuchuser/foo/bar"))))) -- 2.17.1