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