1 /**
2  * @file
3  * String auto-completion routines
4  *
5  * @authors
6  * Copyright (C) 1996-2000,2007 Michael R. Elkins <me@mutt.org>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * @page neo_complete String auto-completion routines
25  *
26  * String auto-completion routines
27  */
28 
29 #include "config.h"
30 #include <dirent.h>
31 #include <errno.h>
32 #include <string.h>
33 #include <sys/stat.h>
34 #include "mutt/lib.h"
35 #include "config/lib.h"
36 #include "core/lib.h"
37 #include "muttlib.h"
38 #include "options.h"
39 #include "protos.h" // IWYU pragma: keep
40 #ifdef USE_IMAP
41 #include "imap/lib.h"
42 #endif
43 #ifdef USE_NNTP
44 #include "nntp/lib.h"
45 #endif
46 
47 /**
48  * mutt_complete - Attempt to complete a partial pathname
49  * @param buf    Buffer containing pathname
50  * @param buflen Length of buffer
51  * @retval  0 Ok
52  * @retval -1 No matches
53  *
54  * Given a partial pathname, fill in as much of the rest of the path as is
55  * unique.
56  */
mutt_complete(char * buf,size_t buflen)57 int mutt_complete(char *buf, size_t buflen)
58 {
59   const char *p = NULL;
60   DIR *dirp = NULL;
61   struct dirent *de = NULL;
62   int init = 0;
63   size_t len;
64   struct Buffer *dirpart = NULL;
65   struct Buffer *exp_dirpart = NULL;
66   struct Buffer *filepart = NULL;
67   struct Buffer *tmp = NULL;
68 #ifdef USE_IMAP
69   struct Buffer *imap_path = NULL;
70   int rc;
71 #endif
72 
73   mutt_debug(LL_DEBUG2, "completing %s\n", buf);
74 
75 #ifdef USE_NNTP
76   if (OptNews)
77     return nntp_complete(buf, buflen);
78 #endif
79 
80   const char *const c_spool_file = cs_subset_string(NeoMutt->sub, "spool_file");
81   const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
82 #ifdef USE_IMAP
83   imap_path = mutt_buffer_pool_get();
84   /* we can use '/' as a delimiter, imap_complete rewrites it */
85   if ((*buf == '=') || (*buf == '+') || (*buf == '!'))
86   {
87     if (*buf == '!')
88       p = NONULL(c_spool_file);
89     else
90       p = NONULL(c_folder);
91 
92     mutt_buffer_concat_path(imap_path, p, buf + 1);
93   }
94   else
95     mutt_buffer_strcpy(imap_path, buf);
96 
97   if (imap_path_probe(mutt_buffer_string(imap_path), NULL) == MUTT_IMAP)
98   {
99     rc = imap_complete(buf, buflen, mutt_buffer_string(imap_path));
100     mutt_buffer_pool_release(&imap_path);
101     return rc;
102   }
103 
104   mutt_buffer_pool_release(&imap_path);
105 #endif
106 
107   dirpart = mutt_buffer_pool_get();
108   exp_dirpart = mutt_buffer_pool_get();
109   filepart = mutt_buffer_pool_get();
110   tmp = mutt_buffer_pool_get();
111 
112   if ((*buf == '=') || (*buf == '+') || (*buf == '!'))
113   {
114     mutt_buffer_addch(dirpart, *buf);
115     if (*buf == '!')
116       mutt_buffer_strcpy(exp_dirpart, NONULL(c_spool_file));
117     else
118       mutt_buffer_strcpy(exp_dirpart, NONULL(c_folder));
119     p = strrchr(buf, '/');
120     if (p)
121     {
122       mutt_buffer_concatn_path(tmp, mutt_buffer_string(exp_dirpart),
123                                mutt_buffer_len(exp_dirpart), buf + 1,
124                                (size_t) (p - buf - 1));
125       mutt_buffer_copy(exp_dirpart, tmp);
126       mutt_buffer_substrcpy(dirpart, buf, p + 1);
127       mutt_buffer_strcpy(filepart, p + 1);
128     }
129     else
130       mutt_buffer_strcpy(filepart, buf + 1);
131     dirp = opendir(mutt_buffer_string(exp_dirpart));
132   }
133   else
134   {
135     p = strrchr(buf, '/');
136     if (p)
137     {
138       if (p == buf) /* absolute path */
139       {
140         p = buf + 1;
141         mutt_buffer_strcpy(dirpart, "/");
142         mutt_buffer_strcpy(filepart, p);
143         dirp = opendir(mutt_buffer_string(dirpart));
144       }
145       else
146       {
147         mutt_buffer_substrcpy(dirpart, buf, p);
148         mutt_buffer_strcpy(filepart, p + 1);
149         mutt_buffer_copy(exp_dirpart, dirpart);
150         mutt_buffer_expand_path(exp_dirpart);
151         dirp = opendir(mutt_buffer_string(exp_dirpart));
152       }
153     }
154     else
155     {
156       /* no directory name, so assume current directory. */
157       mutt_buffer_strcpy(filepart, buf);
158       dirp = opendir(".");
159     }
160   }
161 
162   if (!dirp)
163   {
164     mutt_debug(LL_DEBUG1, "%s: %s (errno %d)\n",
165                mutt_buffer_string(exp_dirpart), strerror(errno), errno);
166     goto cleanup;
167   }
168 
169   /* special case to handle when there is no filepart yet.  find the first
170    * file/directory which is not "." or ".." */
171   len = mutt_buffer_len(filepart);
172   if (len == 0)
173   {
174     while ((de = readdir(dirp)))
175     {
176       if (!mutt_str_equal(".", de->d_name) && !mutt_str_equal("..", de->d_name))
177       {
178         mutt_buffer_strcpy(filepart, de->d_name);
179         init++;
180         break;
181       }
182     }
183   }
184 
185   while ((de = readdir(dirp)))
186   {
187     if (mutt_strn_equal(de->d_name, mutt_buffer_string(filepart), len))
188     {
189       if (init)
190       {
191         char *cp = filepart->data;
192 
193         for (int i = 0; (*cp != '\0') && (de->d_name[i] != '\0'); i++, cp++)
194         {
195           if (*cp != de->d_name[i])
196             break;
197         }
198         *cp = '\0';
199         mutt_buffer_fix_dptr(filepart);
200       }
201       else
202       {
203         struct stat st = { 0 };
204 
205         mutt_buffer_strcpy(filepart, de->d_name);
206 
207         /* check to see if it is a directory */
208         if (mutt_buffer_is_empty(dirpart))
209         {
210           mutt_buffer_reset(tmp);
211         }
212         else
213         {
214           mutt_buffer_copy(tmp, exp_dirpart);
215           mutt_buffer_addch(tmp, '/');
216         }
217         mutt_buffer_addstr(tmp, mutt_buffer_string(filepart));
218         if ((stat(mutt_buffer_string(tmp), &st) != -1) && (st.st_mode & S_IFDIR))
219           mutt_buffer_addch(filepart, '/');
220         init = 1;
221       }
222     }
223   }
224   closedir(dirp);
225 
226   if (!mutt_buffer_is_empty(dirpart))
227   {
228     mutt_str_copy(buf, mutt_buffer_string(dirpart), buflen);
229     if (!mutt_str_equal("/", mutt_buffer_string(dirpart)) &&
230         (mutt_buffer_string(dirpart)[0] != '=') && (mutt_buffer_string(dirpart)[0] != '+'))
231     {
232       mutt_str_copy(buf + strlen(buf), "/", buflen - strlen(buf));
233     }
234     mutt_str_copy(buf + strlen(buf), mutt_buffer_string(filepart), buflen - strlen(buf));
235   }
236   else
237     mutt_str_copy(buf, mutt_buffer_string(filepart), buflen);
238 
239 cleanup:
240   mutt_buffer_pool_release(&dirpart);
241   mutt_buffer_pool_release(&exp_dirpart);
242   mutt_buffer_pool_release(&filepart);
243   mutt_buffer_pool_release(&tmp);
244 
245   return init ? 0 : -1;
246 }
247