1 /* GNU Mailutils -- a suite of utilities for electronic mail
2 Copyright (C) 1999-2021 Free Software Foundation, Inc.
3
4 GNU Mailutils is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3, or (at your option)
7 any later version.
8
9 GNU Mailutils is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
16
17 #include "mail.h"
18 #include <sys/stat.h>
19 #include <dirent.h>
20 #include <mailutils/folder.h>
21
22 #ifdef WITH_READLINE
23 static char **ml_command_completion (char *cmd, int start, int end);
24 static char *ml_command_generator (const char *text, int state);
25 static void ml_sig_handler (int);
26 #else
27 # define ml_sig_handler(n)
28 #endif
29
30 static volatile int _interrupted;
31
32 static RETSIGTYPE
sig_handler(int signo)33 sig_handler (int signo)
34 {
35 switch (signo)
36 {
37 case SIGINT:
38 if (mailvar_is_true (mailvar_name_quit))
39 exit (0);
40 _interrupted++;
41 break;
42
43 #if defined (SIGWINCH)
44 case SIGWINCH:
45 util_do_command ("set screen=%d", util_getlines ());
46 util_do_command ("set columns=%d", util_getcols ());
47 page_invalidate (1);
48 break;
49 #endif
50 }
51 ml_sig_handler (signo);
52 #ifndef HAVE_SIGACTION
53 signal (signo, sig_handler);
54 #endif
55 }
56
57 void
ml_clear_interrupt(void)58 ml_clear_interrupt (void)
59 {
60 _interrupted = 0;
61 }
62
63 int
ml_got_interrupt(void)64 ml_got_interrupt (void)
65 {
66 int rc = _interrupted;
67 _interrupted = 0;
68 return rc;
69 }
70
71 static int
ml_getc(FILE * stream)72 ml_getc (FILE *stream)
73 {
74 unsigned char c;
75
76 while (1)
77 {
78 if (read (fileno (stream), &c, 1) == 1)
79 return c;
80 if (errno == EINTR)
81 {
82 if (_interrupted)
83 break;
84 /* keep going if we handled the signal */
85 }
86 else
87 break;
88 }
89 return EOF;
90 }
91
92 void
ml_readline_init(void)93 ml_readline_init (void)
94 {
95 if (!interactive)
96 return;
97
98 #ifdef WITH_READLINE
99 rl_readline_name = "mail";
100 rl_attempted_completion_function =
101 (rl_completion_func_t*) ml_command_completion;
102 rl_getc_function = ml_getc;
103 rl_catch_signals = 0;
104 #endif
105 #ifdef HAVE_SIGACTION
106 {
107 struct sigaction act;
108 act.sa_handler = sig_handler;
109 sigemptyset (&act.sa_mask);
110 act.sa_flags = 0;
111 sigaction (SIGINT, &act, NULL);
112 #if defined(SIGWINCH)
113 sigaction (SIGWINCH, &act, NULL);
114 #endif
115 }
116 #else
117 signal (SIGINT, sig_handler);
118 #if defined(SIGWINCH)
119 signal (SIGWINCH, sig_handler);
120 #endif
121 #endif
122 }
123
124 char *
ml_readline_internal(void)125 ml_readline_internal (void)
126 {
127 char *buf = NULL;
128 size_t size = 0, n;
129 int rc;
130
131 rc = mu_stream_getline (mu_strin, &buf, &size, &n);
132 if (rc)
133 {
134 mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_getline", NULL, rc);
135 return NULL;
136 }
137 if (_interrupted)
138 {
139 free (buf);
140 return NULL;
141 }
142 if (n == 0)
143 {
144 free (buf);
145 return NULL;
146 }
147 mu_rtrim_cset (buf, "\n");
148 return buf;
149 }
150
151 char *
ml_readline(const char * prompt)152 ml_readline (const char *prompt)
153 {
154 if (interactive)
155 return readline (prompt);
156 return ml_readline_internal ();
157 }
158
159 char *
ml_readline_with_intr(const char * prompt)160 ml_readline_with_intr (const char *prompt)
161 {
162 char *str = ml_readline (prompt);
163 if (_interrupted)
164 mu_printf ("\n");
165 return str;
166 }
167
168 #ifdef WITH_READLINE
169
170 /* Readline-specific signal handling */
171 static void
ml_sig_handler(int sig)172 ml_sig_handler (int sig)
173 {
174 switch (sig)
175 {
176 case SIGINT:
177 rl_free_line_state ();
178 break;
179
180 #if defined (SIGWINCH)
181 case SIGWINCH:
182 rl_resize_terminal ();
183 break;
184 #endif
185 }
186 rl_reset_after_signal ();
187 }
188
189 static char *insert_text;
190
191 static int
ml_insert_hook(void)192 ml_insert_hook (void)
193 {
194 if (insert_text)
195 rl_insert_text (insert_text);
196 return 0;
197 }
198
199 int
ml_reread(const char * prompt,char ** text)200 ml_reread (const char *prompt, char **text)
201 {
202 char *s;
203
204 ml_clear_interrupt ();
205 insert_text = *text;
206 rl_startup_hook = ml_insert_hook;
207 s = readline ((char *)prompt);
208 if (!ml_got_interrupt ())
209 {
210 if (*text)
211 free (*text);
212 *text = s;
213 }
214 else
215 {
216 putc('\n', stdout);
217 }
218 rl_startup_hook = NULL;
219 return 0;
220 }
221
222 /*
223 * readline tab completion
224 */
225 char **
ml_command_completion(char * cmd,int start,int end)226 ml_command_completion (char *cmd, int start, int end)
227 {
228 char **ret;
229 char *p;
230 struct mu_wordsplit ws;
231
232 for (p = rl_line_buffer; p < rl_line_buffer + start && mu_isblank (*p); p++)
233 ;
234
235 if (mu_wordsplit_len (p, end, &ws, MU_WRDSF_DEFFLAGS))
236 {
237 mu_error (_("mu_wordsplit_len failed: %s"),
238 mu_wordsplit_strerror (&ws));
239 return NULL;
240 }
241 rl_completion_append_character = ' ';
242
243 if (ws.ws_wordc == 0 ||
244 (ws.ws_wordc == 1 && strlen (ws.ws_wordv[0]) <= end - start))
245 {
246 ret = rl_completion_matches (cmd, ml_command_generator);
247 rl_attempted_completion_over = 1;
248 }
249 else
250 {
251 const struct mail_command_entry *entry =
252 mail_find_command (ws.ws_wordv[0]);
253 if (entry && entry->command_completion)
254 {
255 int point = COMPL_DFL;
256
257 if (start == end)
258 point |= COMPL_WS;
259 if (mu_str_skip_class (p + end, MU_CTYPE_SPACE)[0] == 0)
260 point |= COMPL_LASTARG;
261
262 ret = entry->command_completion (ws.ws_wordc, ws.ws_wordv, point);
263 }
264 else
265 ret = NULL;
266 }
267 mu_wordsplit_free (&ws);
268 return ret;
269 }
270
271 /*
272 * more readline
273 */
274 char *
ml_command_generator(const char * text,int state)275 ml_command_generator (const char *text, int state)
276 {
277 static int i, len;
278 const char *name;
279 const struct mail_command *cp;
280
281 if (!state)
282 {
283 i = 0;
284 len = strlen (text);
285 }
286
287 while ((cp = mail_command_name (i)))
288 {
289 name = cp->longname;
290 if (strlen (cp->shortname) > strlen (name))
291 name = cp->shortname;
292 i++;
293 if (strncmp (name, text, len) == 0)
294 return mu_strdup (name);
295 }
296
297 return NULL;
298 }
299
300 void
ml_set_completion_append_character(int c)301 ml_set_completion_append_character (int c)
302 {
303 rl_completion_append_character = c;
304 }
305
306 void
ml_attempted_completion_over(void)307 ml_attempted_completion_over (void)
308 {
309 rl_attempted_completion_over = 1;
310 }
311
312
313 /* Concatenate results of two completions. Both A and B are expected to
314 be returned by rl_completion_matches, i.e. their first entry is the
315 longest common prefix of the remaining entries, which are sorted
316 lexicographically. Array B is treated as case-insensitive.
317
318 If either of the arrays is NULL, the other one is returned unchanged.
319
320 Otherwise, if A[0] begins with a lowercase letter, all items from B
321 will be converted to lowercase.
322
323 Both A and B (but not their elements) are freed prior to returning.
324
325 Note: This function assumes that no string from A is present in B and
326 vice versa.
327 */
328 static char **
compl_concat(char ** a,char ** b)329 compl_concat (char **a, char **b)
330 {
331 size_t i, j, k, n = 0, an, bn;
332 char **ret;
333 int lwr = 0;
334
335 if (a)
336 {
337 lwr = mu_islower (a[0][0]);
338 for (an = 0; a[an]; an++)
339 ;
340 }
341 else
342 return b;
343
344 if (b)
345 {
346 for (bn = 0; b[bn]; bn++)
347 {
348 if (lwr)
349 mu_strlower (b[bn]);
350 }
351 }
352 else
353 return a;
354
355 i = an == 1 ? 0 : 1;
356 j = bn == 1 ? 0 : 1;
357
358 n = (an - i) + (bn - j) + 1;
359 ret = mu_calloc (n + 1, sizeof (ret[0]));
360
361 if (an == bn && an == 1)
362 {
363 /* Compute LCP of both entries */
364 for (i = 0; a[i] && b[i] && a[i] == b[i]; i++)
365 ;
366 ret[0] = mu_alloc (i + 1);
367 memcpy (ret[0], a[0], i);
368 ret[0][i] = 0;
369 }
370 else
371 /* The first entry is the LCP of the rest. Select the shortest one. */
372 ret[0] = mu_strdup ((strlen (a[0]) < strlen (b[0])) ? a[0] : b[0]);
373
374 if (i)
375 free (a[0]);
376 if (j)
377 free (b[0]);
378
379 k = 1;
380 while (k < n)
381 {
382 if (!a[i])
383 {
384 memcpy (ret + k, b + j, sizeof (b[0]) * (bn - j));
385 break;
386 }
387 else if (!b[j])
388 {
389 memcpy (ret + k, a + i, sizeof (a[0]) * (an - i));
390 break;
391 }
392 else
393 ret[k++] = (strcmp (a[i], b[j]) < 0) ? a[i++] : b[j++];
394 }
395 ret[n] = NULL;
396 free (a);
397 free (b);
398 return ret;
399 }
400
401 static char *header_generator (const char *text, int state);
402
403 /* Internal completion generator for commands that take a message list
404 (if MATCHES is NULL), or a messages list followed by another argument
405 (if MATCHES is not NULL).
406
407 In the latter case the MATCHES function generates expansions for the
408 last argument. It is used for such commands as write, where the last
409 object is a mailbox or pipe, where it is a command).
410
411 CLOSURE supplies argument for MATCHES. It is ignored if MATCHES is NULL.
412 */
413 static char **
msglist_closure_compl(int argc,char ** argv,int point,char ** (* matches)(void *),void * closure)414 msglist_closure_compl (int argc, char **argv, int point,
415 char **(*matches) (void*),
416 void *closure)
417 {
418 char *text = (point & COMPL_WS) ? "" : argv[argc-1];
419 size_t len = strlen (text);
420
421 if (text[0] == ':')
422 {
423 if (text[1] && (text[1] == '/' || text[2]))
424 {
425 ml_set_completion_append_character (0);
426 ml_attempted_completion_over ();
427 return NULL;
428 }
429 ml_set_completion_append_character (' ');
430 return rl_completion_matches (text, msgtype_generator);
431 }
432
433 ml_set_completion_append_character (0);
434 if (len && text[len-1] == ':')
435 {
436 char **ret = mu_calloc(2, sizeof (ret[0]));
437 ret[0] = strcat (strcpy (mu_alloc (len + 2), text), "/");
438 return ret;
439 }
440
441 if (matches && (point & COMPL_LASTARG))
442 return compl_concat (matches (closure),
443 rl_completion_matches (text, header_generator));
444 else
445 return rl_completion_matches (text, header_generator);
446 }
447
448 /* Completion functions */
449 char **
no_compl(int argc MU_ARG_UNUSED,char ** argv MU_ARG_UNUSED,int flags MU_ARG_UNUSED)450 no_compl (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED,
451 int flags MU_ARG_UNUSED)
452 {
453 ml_attempted_completion_over ();
454 return NULL;
455 }
456
457 char **
msglist_compl(int argc,char ** argv,int point)458 msglist_compl (int argc, char **argv, int point)
459 {
460 return msglist_closure_compl (argc, argv, point, NULL, NULL);
461 }
462
463 char **
command_compl(int argc,char ** argv,int point)464 command_compl (int argc, char **argv, int point)
465 {
466 ml_set_completion_append_character (0);
467 if (point & COMPL_WS)
468 return NULL;
469 return rl_completion_matches (argv[argc-1], ml_command_generator);
470 }
471
472 struct filegen
473 {
474 mu_list_t list; /* List of matches */
475 mu_iterator_t itr; /* Iterator over this list */
476 size_t prefix_len; /* Length of initial prefix */
477 char repl; /* Character to replace initial prefix with */
478 int flags;
479 };
480
481 static void
filegen_free(struct filegen * fg)482 filegen_free (struct filegen *fg)
483 {
484 mu_iterator_destroy (&fg->itr);
485 mu_list_destroy (&fg->list);
486 }
487
488 #define PREFIX_AUTO 0
489
490 static int
folder_match_url(mu_folder_t folder,mu_url_t url)491 folder_match_url (mu_folder_t folder, mu_url_t url)
492 {
493 mu_url_t furl;
494 int rc = mu_folder_get_url (folder, &furl);
495 if (rc)
496 {
497 mu_diag_funcall (MU_DIAG_ERROR, "mu_folder_get_url", NULL, rc);
498 return 0;
499 }
500 return mu_url_is_same_scheme (url, furl)
501 && mu_url_is_same_user (url, furl)
502 && mu_url_is_same_host (url, furl)
503 && mu_url_is_same_portstr (url, furl);
504 }
505
506 static int
filegen_init(struct filegen * fg,const char * text,const char * folder_path,int type,size_t prefix_len,int repl,int flags)507 filegen_init (struct filegen *fg,
508 const char *text,
509 const char *folder_path,
510 int type,
511 size_t prefix_len,
512 int repl,
513 int flags)
514 {
515 char *pathref;
516 char *wcard;
517 mu_folder_t folder;
518 size_t count, i, len;
519 mu_url_t url;
520 int rc;
521 int free_folder;
522
523 rc = mu_url_create (&url, folder_path);
524 if (rc)
525 {
526 mu_diag_funcall (MU_DIAG_ERROR, "mu_url_create", folder_path, rc);
527 return -1;
528 }
529
530 rc = mu_mailbox_get_folder (mbox, &folder);
531 if (rc == 0)
532 {
533 if (folder_match_url (folder, url))
534 free_folder = 0;
535 else
536 folder = NULL;
537 }
538 else
539 folder = NULL;
540
541 if (!folder)
542 {
543 if (util_get_folder (&folder, url, type))
544 return -1;
545 free_folder = 1;
546 }
547
548 pathref = mu_strdup (text);
549 len = strlen (pathref);
550 for (i = len; i > 0; i--)
551 if (pathref[i-1] == '/')
552 break;
553 wcard = mu_alloc (len - i + 2);
554 strcpy (wcard, pathref + i);
555 strcat (wcard, "%");
556 pathref[i] = 0;
557
558 fg->prefix_len = prefix_len;
559
560 mu_folder_list (folder, pathref, wcard, 1, &fg->list);
561 free (wcard);
562 free (pathref);
563 if (free_folder)
564 mu_folder_destroy (&folder);
565
566 if (mu_list_count (fg->list, &count) || count == 0)
567 {
568 mu_list_destroy (&fg->list);
569 return -1;
570 }
571 else if (count == 1)
572 {
573 ml_set_completion_append_character (0);
574 if (flags & MU_FOLDER_ATTRIBUTE_DIRECTORY)
575 {
576 struct mu_list_response *resp;
577 mu_list_head (fg->list, (void**)&resp);
578 if ((resp->type & MU_FOLDER_ATTRIBUTE_DIRECTORY)
579 && strcmp (resp->name, text) == 0)
580 ml_set_completion_append_character (resp->separator);
581 }
582 }
583
584 if (mu_list_get_iterator (fg->list, &fg->itr))
585 {
586 mu_list_destroy (&fg->list);
587 return -1;
588 }
589 mu_iterator_first (fg->itr);
590 fg->repl = repl;
591 fg->flags = flags;
592 return 0;
593 }
594
595 static char *
filegen_next(struct filegen * fg)596 filegen_next (struct filegen *fg)
597 {
598 while (!mu_iterator_is_done (fg->itr))
599 {
600 struct mu_list_response *resp;
601 mu_iterator_current (fg->itr, (void**)&resp);
602 mu_iterator_next (fg->itr);
603 if (resp->type & fg->flags)
604 {
605 char *ret;
606 char *name;
607 char *ptr;
608
609 name = resp->name + fg->prefix_len;
610 ret = mu_alloc (strlen (name) + (fg->repl ? 1 : 0) + 1);
611 ptr = ret;
612 if (fg->repl)
613 *ptr++ = fg->repl;
614 strcpy (ptr, name);
615 return ret;
616 }
617 }
618 filegen_free (fg);
619 return NULL;
620 }
621
622 static char *
folder_generator(const char * text,int state)623 folder_generator (const char *text, int state)
624 {
625 static struct filegen fg;
626
627 if (!state)
628 {
629 int rc;
630 char *path = util_folder_path ("+");
631 if (!path)
632 return NULL;
633
634 rc = filegen_init (&fg, text, path,
635 any_folder,
636 PREFIX_AUTO, '+',
637 MU_FOLDER_ATTRIBUTE_ALL);
638 free (path);
639 if (rc)
640 return NULL;
641 }
642 return filegen_next (&fg);
643 }
644
645 static char *
header_generator(const char * text,int state)646 header_generator (const char *text, int state)
647 {
648 static int i, len;
649 char *hdr;
650 char *hdrlist[] = {
651 MU_HEADER_RETURN_PATH,
652 MU_HEADER_RECEIVED,
653 MU_HEADER_DATE,
654 MU_HEADER_DCC,
655 MU_HEADER_FROM,
656 MU_HEADER_SENDER,
657 MU_HEADER_RESENT_FROM,
658 MU_HEADER_SUBJECT,
659 MU_HEADER_RESENT_SENDER,
660 MU_HEADER_TO,
661 MU_HEADER_RESENT_TO,
662 MU_HEADER_CC,
663 MU_HEADER_RESENT_CC,
664 MU_HEADER_BCC,
665 MU_HEADER_RESENT_BCC,
666 MU_HEADER_REPLY_TO,
667 MU_HEADER_RESENT_REPLY_TO,
668 MU_HEADER_MESSAGE_ID,
669 MU_HEADER_RESENT_MESSAGE_ID,
670 MU_HEADER_IN_REPLY_TO,
671 MU_HEADER_REFERENCE,
672 MU_HEADER_REFERENCES,
673 MU_HEADER_ENCRYPTED,
674 MU_HEADER_PRECEDENCE,
675 MU_HEADER_STATUS,
676 MU_HEADER_CONTENT_LENGTH,
677 MU_HEADER_CONTENT_LANGUAGE,
678 MU_HEADER_CONTENT_TRANSFER_ENCODING,
679 MU_HEADER_CONTENT_ID,
680 MU_HEADER_CONTENT_TYPE,
681 MU_HEADER_CONTENT_DESCRIPTION,
682 MU_HEADER_CONTENT_DISPOSITION,
683 MU_HEADER_CONTENT_MD5,
684 MU_HEADER_CONTENT_LOCATION,
685 MU_HEADER_MIME_VERSION,
686 MU_HEADER_X_MAILER,
687 MU_HEADER_X_UIDL,
688 MU_HEADER_X_UID,
689 MU_HEADER_X_IMAPBASE,
690 MU_HEADER_ENV_SENDER,
691 MU_HEADER_ENV_DATE,
692 MU_HEADER_FCC,
693 MU_HEADER_DELIVERY_DATE,
694 MU_HEADER_ENVELOPE_TO,
695 MU_HEADER_X_EXPIRE_TIMESTAMP,
696 MU_HEADER_USER_AGENT,
697 NULL
698 };
699
700 if (!state)
701 {
702 i = 0;
703 len = strlen (text);
704 }
705
706 while ((hdr = hdrlist[i]))
707 {
708 i++;
709 if (mu_c_strncasecmp (hdr, text, len) == 0)
710 return strcat (strcpy (mu_alloc (strlen (hdr) + 3), hdr), ":/");
711 }
712
713 return NULL;
714 }
715
716 char **
file_compl(int argc,char ** argv,int point)717 file_compl (int argc, char **argv, int point)
718 {
719 char *text;
720
721 if (point & COMPL_WS)
722 {
723 ml_set_completion_append_character (0);
724 ml_attempted_completion_over ();
725 return NULL;
726 }
727
728 text = argv[argc-1];
729
730 switch (text[0])
731 {
732 case '+':
733 return rl_completion_matches (text + 1, folder_generator);
734
735 case '%':
736 case '#':
737 case '&':
738 ml_attempted_completion_over ();
739 break;
740
741 default:
742 /* Suppose it is a file name */
743 return rl_completion_matches (text, rl_filename_completion_function);
744 }
745
746 return NULL;
747 }
748
749 struct compl_closure
750 {
751 int argc;
752 char **argv;
753 int point;
754 };
755
756 static char **
file_compl_matches(void * closure)757 file_compl_matches (void *closure)
758 {
759 struct compl_closure *cp = closure;
760 return file_compl (cp->argc, cp->argv, cp->point);
761 }
762
763 char **
msglist_file_compl(int argc,char ** argv,int point)764 msglist_file_compl (int argc, char **argv, int point)
765 {
766 struct compl_closure clos = { argc, argv, point };
767 return msglist_closure_compl (argc, argv, point, file_compl_matches, &clos);
768 }
769
770 static char *
dir_generator(const char * text,int state)771 dir_generator (const char *text, int state)
772 {
773 static struct filegen fg;
774
775 if (!state)
776 {
777 char *path;
778 char *p;
779 int rc;
780 char repl;
781 size_t prefix_len;
782
783 switch (text[0])
784 {
785 case '+':
786 {
787 char *f;
788 repl = '+';
789 f = util_folder_path ("+");
790 prefix_len = strlen (f);
791 path = mu_make_file_name (f, text + 1);
792 free (f);
793 }
794 break;
795
796 case '~':
797 repl = '~';
798 if (text[1] == '/')
799 {
800 char *home = mu_get_homedir ();
801 prefix_len = strlen (home);
802 path = mu_make_file_name (home, text + 2);
803 free (home);
804 }
805 else
806 {
807 ml_attempted_completion_over ();
808 return NULL;
809 /* FIXME: implement user-name completion */
810 }
811 break;
812
813 case '/':
814 path = mu_strdup (text);
815 prefix_len = 0;
816 repl = 0;
817 break;
818
819 default:
820 {
821 char *cwd = mu_getcwd ();
822 prefix_len = strlen (cwd);
823 path = mu_make_file_name (cwd, text);
824 free (cwd);
825 }
826 repl = 0;
827 }
828 p = strrchr (path, '/');
829 if (*p)
830 {
831 if (p[1])
832 *p++ = 0;
833 else
834 p = "";
835 rc = filegen_init (&fg, p, path[0] ? path : "/",
836 local_folder,
837 prefix_len, repl,
838 MU_FOLDER_ATTRIBUTE_DIRECTORY);
839 }
840 else
841 {
842 ml_attempted_completion_over ();
843 rc = -1;
844 }
845 if (rc)
846 return NULL;
847 }
848
849 return filegen_next (&fg);
850 }
851
852 char **
dir_compl(int argc,char ** argv,int point)853 dir_compl (int argc, char **argv, int point)
854 {
855 ml_attempted_completion_over ();
856 if (point & COMPL_WS)
857 {
858 ml_set_completion_append_character (0);
859 return NULL;
860 }
861 return rl_completion_matches (argv[argc-1], dir_generator);
862 }
863
864 static char *
alias_generator(const char * text,int state)865 alias_generator (const char *text, int state)
866 {
867 static alias_iterator_t itr;
868 const char *p;
869
870 if (!state)
871 p = alias_iterate_first (text, &itr);
872 else
873 p = alias_iterate_next (itr);
874
875 if (!p)
876 {
877 alias_iterate_end (&itr);
878 return NULL;
879 }
880 return mu_strdup (p);
881 }
882
883 char **
alias_compl(int argc,char ** argv,int point)884 alias_compl (int argc, char **argv, int point)
885 {
886 ml_attempted_completion_over ();
887 return rl_completion_matches ((point & COMPL_WS) ? "" : argv[argc-1],
888 alias_generator);
889 }
890
891 static char *
exec_generator(const char * text,int state)892 exec_generator (const char *text, int state)
893 {
894 static int prefix_len;
895 static char *var;
896 static char *dir;
897 static size_t dsize;
898 static DIR *dp;
899 struct dirent *ent;
900
901 if (!state)
902 {
903 var = getenv ("PATH");
904 if (!var)
905 return NULL;
906 prefix_len = strlen (text);
907 dsize = 0;
908 dir = NULL;
909 dp = NULL;
910 }
911
912 while (1)
913 {
914 if (!dp)
915 {
916 char *p;
917 size_t len;
918
919 if (*var == 0)
920 break;
921 else
922 var++;
923
924 p = strchr (var, ':');
925 if (!p)
926 len = strlen (var) + 1;
927 else
928 len = p - var + 1;
929
930 if (dsize == 0)
931 {
932 dir = malloc (len);
933 dsize = len;
934 }
935 else if (len > dsize)
936 {
937 dir = realloc (dir, len);
938 dsize = len;
939 }
940
941 if (!dir)
942 return NULL;
943 memcpy (dir, var, len - 1);
944 dir[len - 1] = 0;
945 var += len - 1;
946
947 dp = opendir (dir);
948 if (!dp)
949 continue;
950 }
951
952 while ((ent = readdir (dp)))
953 {
954 char *name = mu_make_file_name (dir, ent->d_name);
955 if (name)
956 {
957 int rc = access (name, X_OK);
958 if (rc == 0)
959 {
960 struct stat st;
961 rc = !(stat (name, &st) == 0 && S_ISREG (st.st_mode));
962 }
963
964 free (name);
965 if (rc == 0
966 && strlen (ent->d_name) >= prefix_len
967 && strncmp (ent->d_name, text, prefix_len) == 0)
968 return mu_strdup (ent->d_name);
969 }
970 }
971
972 closedir (dp);
973 dp = NULL;
974 }
975
976 free (dir);
977 return NULL;
978 }
979
980 static char **
exec_matches(void * closure)981 exec_matches (void *closure)
982 {
983 return rl_completion_matches ((char *)closure, exec_generator);
984 }
985
986 char **
exec_compl(int argc,char ** argv,int point)987 exec_compl (int argc, char **argv, int point)
988 {
989 return msglist_closure_compl (argc, argv, point,
990 exec_matches,
991 (point & COMPL_WS) ? "" : argv[argc-1]);
992 }
993
994 char **
shell_compl(int argc,char ** argv,int point)995 shell_compl (int argc, char **argv, int point)
996 {
997 if (argc == 2)
998 return rl_completion_matches (argv[1], exec_generator);
999 return no_compl (argc, argv, point);
1000 }
1001
1002 #else
1003
1004 #include <sys/ioctl.h>
1005
1006 #define STDOUT 1
1007 #ifndef TIOCSTI
1008 static int ch_erase;
1009 static int ch_kill;
1010 #endif
1011
1012 #if defined(TIOCSTI)
1013 #elif defined(HAVE_TERMIOS_H)
1014 # include <termios.h>
1015
1016 static struct termios term_settings;
1017
1018 int
set_tty(void)1019 set_tty (void)
1020 {
1021 struct termios new_settings;
1022
1023 if (tcgetattr(STDOUT, &term_settings) == -1)
1024 return 1;
1025
1026 ch_erase = term_settings.c_cc[VERASE];
1027 ch_kill = term_settings.c_cc[VKILL];
1028
1029 new_settings = term_settings;
1030 new_settings.c_lflag &= ~(ICANON|ECHO);
1031 #if defined(TAB3)
1032 new_settings.c_oflag &= ~(TAB3);
1033 #elif defined(OXTABS)
1034 new_settings.c_oflag &= ~(OXTABS);
1035 #endif
1036 new_settings.c_cc[VMIN] = 1;
1037 new_settings.c_cc[VTIME] = 0;
1038 tcsetattr(STDOUT, TCSADRAIN, &new_settings);
1039 return 0;
1040 }
1041
1042 void
restore_tty(void)1043 restore_tty (void)
1044 {
1045 tcsetattr (STDOUT, TCSADRAIN, &term_settings);
1046 }
1047
1048 #elif defined(HAVE_TERMIO_H)
1049 # include <termio.h>
1050
1051 static struct termio term_settings;
1052
1053 int
set_tty(void)1054 set_tty (void)
1055 {
1056 struct termio new_settings;
1057
1058 if (ioctl(STDOUT, TCGETA, &term_settings) < 0)
1059 return -1;
1060
1061 ch_erase = term_settings.c_cc[VERASE];
1062 ch_kill = term_settings.c_cc[VKILL];
1063
1064 new_settings = term_settings;
1065 new_settings.c_lflag &= ~(ICANON | ECHO);
1066 new_settings.c_oflag &= ~(TAB3);
1067 new_settings.c_cc[VMIN] = 1;
1068 new_settings.c_cc[VTIME] = 0;
1069 ioctl(STDOUT, TCSETA, &new_settings);
1070 return 0;
1071 }
1072
1073 void
restore_tty(void)1074 restore_tty (void)
1075 {
1076 ioctl(STDOUT, TCSETA, &term_settings);
1077 }
1078
1079 #elif defined(HAVE_SGTTY_H)
1080 # include <sgtty.h>
1081
1082 static struct sgttyb term_settings;
1083
1084 int
set_tty(void)1085 set_tty (void)
1086 {
1087 struct sgttyb new_settings;
1088
1089 if (ioctl(STDOUT, TIOCGETP, &term_settings) < 0)
1090 return 1;
1091
1092 ch_erase = term_settings.sg_erase;
1093 ch_kill = term_settings.sg_kill;
1094
1095 new_settings = term_settings;
1096 new_settings.sg_flags |= CBREAK;
1097 new_settings.sg_flags &= ~(ECHO | XTABS);
1098 ioctl(STDOUT, TIOCSETP, &new_settings);
1099 return 0;
1100 }
1101
1102 void
restore_tty(void)1103 restore_tty (void)
1104 {
1105 ioctl(STDOUT, TIOCSETP, &term_settings);
1106 }
1107
1108 #else
1109 # define DUMB_MODE
1110 #endif
1111
1112
1113 #define LINE_INC 80
1114
1115 int
ml_reread(const char * prompt,char ** text)1116 ml_reread (const char *prompt, char **text)
1117 {
1118 int ch;
1119 char *line;
1120 int line_size;
1121 int pos;
1122 char *p;
1123
1124 if (*text)
1125 {
1126 line = strdup (*text);
1127 if (line)
1128 {
1129 pos = strlen (line);
1130 line_size = pos + 1;
1131 }
1132 }
1133 else
1134 {
1135 line_size = LINE_INC;
1136 line = malloc (line_size);
1137 pos = 0;
1138 }
1139
1140 if (!line)
1141 {
1142 mu_error (_("Not enough memory to edit the line"));
1143 return -1;
1144 }
1145
1146 line[pos] = 0;
1147
1148 if (prompt)
1149 {
1150 fputs (prompt, stdout);
1151 fflush (stdout);
1152 }
1153
1154 #ifdef TIOCSTI
1155
1156 for (p = line; *p; p++)
1157 {
1158 ioctl(0, TIOCSTI, p);
1159 }
1160
1161 pos = 0;
1162
1163 while ((ch = ml_getc (stdin)) != EOF && ch != '\n')
1164 {
1165 if (pos >= line_size)
1166 {
1167 if ((p = realloc (line, line_size + LINE_INC)) == NULL)
1168 {
1169 fputs ("\n", stdout);
1170 mu_error (_("Not enough memory to edit the line"));
1171 break;
1172 }
1173 else
1174 {
1175 line_size += LINE_INC;
1176 line = p;
1177 }
1178 }
1179 line[pos++] = ch;
1180 }
1181
1182 #else
1183
1184 fputs (line, stdout);
1185 fflush (stdout);
1186
1187 # ifndef DUMB_MODE
1188 set_tty ();
1189
1190 while ((ch = ml_getc (stdin)) != EOF)
1191 {
1192 if (ch == ch_erase)
1193 {
1194 /* kill last char */
1195 if (pos > 0)
1196 line[--pos] = 0;
1197 putc('\b', stdout);
1198 }
1199 else if (ch == ch_kill)
1200 {
1201 /* kill the entire line */
1202 pos = 0;
1203 line[pos] = 0;
1204 putc ('\r', stdout);
1205 if (prompt)
1206 fputs (prompt, stdout);
1207 }
1208 else if (ch == '\n' || ch == EOF)
1209 break;
1210 else
1211 {
1212 if (pos >= line_size)
1213 {
1214 if ((p = realloc (line, line_size + LINE_INC)) == NULL)
1215 {
1216 fputs ("\n", stdout);
1217 mu_error (_("Not enough memory to edit the line"));
1218 break;
1219 }
1220 else
1221 {
1222 line_size += LINE_INC;
1223 line = p;
1224 }
1225 }
1226 line[pos++] = ch;
1227 putc(ch, stdout);
1228 }
1229 fflush (stdout);
1230 }
1231
1232 putc ('\n', stdout);
1233 restore_tty ();
1234 # else
1235 /* Dumb mode: the last resort */
1236
1237 putc ('\n', stdout);
1238 if (prompt)
1239 {
1240 fputs (prompt, stdout);
1241 fflush (stdout);
1242 }
1243
1244 pos = 0;
1245
1246 while ((ch = ml_getc (stdin)) != EOF && ch != '\n')
1247 {
1248 if (pos >= line_size)
1249 {
1250 if ((p = realloc (line, line_size + LINE_INC)) == NULL)
1251 {
1252 fputs ("\n", stdout);
1253 mu_error (_("Not enough memory to edit the line"));
1254 break;
1255 }
1256 else
1257 {
1258 line_size += LINE_INC;
1259 line = p;
1260 }
1261 }
1262 line[pos++] = ch;
1263 }
1264 # endif
1265 #endif
1266
1267 line[pos] = 0;
1268
1269 if (ml_got_interrupt ())
1270 free (line);
1271 else
1272 {
1273 if (*text)
1274 free (*text);
1275 *text = line;
1276 }
1277 return 0;
1278 }
1279
1280 char *
readline(char * prompt)1281 readline (char *prompt)
1282 {
1283 if (prompt)
1284 {
1285 mu_printf ("%s", prompt);
1286 mu_stream_flush (mu_strout);
1287 }
1288
1289 return ml_readline_internal ();
1290 }
1291
1292 void
ml_set_completion_append_character(int c MU_ARG_UNUSED)1293 ml_set_completion_append_character (int c MU_ARG_UNUSED)
1294 {
1295 }
1296
1297 void
ml_attempted_completion_over(void)1298 ml_attempted_completion_over (void)
1299 {
1300 }
1301
1302 #endif
1303