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