root/browser.c

Revision 5443:8b783c916893, 29.7 kB (checked in by Rocco Rutte <pdmef@…>, 7 weeks ago)

Use realpath() in mutt_pretty_mailbox() for paths that may need it.
This requires to add the buffer size as parameter since the result may
be longer than the original but still fit in the buffer. Closes #2948.

Line 
1/*
2 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
3 *
4 *     This program 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 2 of the License, or
7 *     (at your option) any later version.
8 *
9 *     This program 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 this program; if not, write to the Free Software
16 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 */ 
18
19#if HAVE_CONFIG_H
20# include "config.h"
21#endif
22
23#include "mutt.h"
24#include "mutt_curses.h"
25#include "mutt_menu.h"
26#include "attach.h"
27#include "buffy.h"
28#include "mapping.h"
29#include "sort.h"
30#include "mailbox.h"
31#include "browser.h"
32#ifdef USE_IMAP
33#include "imap.h"
34#endif
35
36#include <stdlib.h>
37#include <dirent.h>
38#include <string.h>
39#include <ctype.h>
40#include <unistd.h>
41#include <sys/stat.h>
42#include <errno.h>
43
44static struct mapping_t FolderHelp[] = {
45  { N_("Exit"),  OP_EXIT },
46  { N_("Chdir"), OP_CHANGE_DIRECTORY },
47  { N_("Mask"),  OP_ENTER_MASK },
48  { N_("Help"),  OP_HELP },
49  { NULL }
50};
51
52typedef struct folder_t
53{
54  struct folder_file *ff;
55  int num;
56} FOLDER;
57
58static char LastDir[_POSIX_PATH_MAX] = "";
59static char LastDirBackup[_POSIX_PATH_MAX] = "";
60
61/* Frees up the memory allocated for the local-global variables.  */
62static void destroy_state (struct browser_state *state)
63{
64  int c;
65
66  for (c = 0; c < state->entrylen; c++)
67  {
68    FREE (&((state->entry)[c].name));
69    FREE (&((state->entry)[c].desc));
70    FREE (&((state->entry)[c].st));
71  }
72#ifdef USE_IMAP
73  FREE (&state->folder);
74#endif
75  FREE (&state->entry);
76}
77
78static int browser_compare_subject (const void *a, const void *b)
79{
80  struct folder_file *pa = (struct folder_file *) a;
81  struct folder_file *pb = (struct folder_file *) b;
82
83  int r = mutt_strcoll (pa->name, pb->name);
84
85  return ((BrowserSort & SORT_REVERSE) ? -r : r);
86}
87
88static int browser_compare_date (const void *a, const void *b)
89{
90  struct folder_file *pa = (struct folder_file *) a;
91  struct folder_file *pb = (struct folder_file *) b;
92
93  int r = pa->mtime - pb->mtime;
94
95  return ((BrowserSort & SORT_REVERSE) ? -r : r);
96}
97
98static int browser_compare_size (const void *a, const void *b)
99{
100  struct folder_file *pa = (struct folder_file *) a;
101  struct folder_file *pb = (struct folder_file *) b;
102
103  int r = pa->size - pb->size;
104
105  return ((BrowserSort & SORT_REVERSE) ? -r : r);
106}
107
108static void browser_sort (struct browser_state *state)
109{
110  int (*f) (const void *, const void *);
111
112  switch (BrowserSort & SORT_MASK)
113  {
114    case SORT_ORDER:
115      return;
116    case SORT_DATE:
117      f = browser_compare_date;
118      break;
119    case SORT_SIZE:
120      f = browser_compare_size;
121      break;
122    case SORT_SUBJECT:
123    default:
124      f = browser_compare_subject;
125      break;
126  }
127  qsort (state->entry, state->entrylen, sizeof (struct folder_file), f);
128}
129
130static int link_is_dir (const char *folder, const char *path)
131{
132  struct stat st;
133  char fullpath[_POSIX_PATH_MAX];
134 
135  mutt_concat_path (fullpath, folder, path, sizeof (fullpath));
136 
137  if (stat (fullpath, &st) == 0)
138    return (S_ISDIR (st.st_mode));
139  else
140    return 0;
141}
142
143static const char *
144folder_format_str (char *dest, size_t destlen, size_t col, char op, const char *src,
145                   const char *fmt, const char *ifstring, const char *elsestring,
146                   unsigned long data, format_flag flags)
147{
148  char fn[SHORT_STRING], tmp[SHORT_STRING], permission[11];
149  char date[16], *t_fmt;
150  time_t tnow;
151  FOLDER *folder = (FOLDER *) data;
152  struct passwd *pw;
153  struct group *gr;
154  int optional = (flags & M_FORMAT_OPTIONAL);
155
156  switch (op)
157  {
158    case 'C':
159      snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
160      snprintf (dest, destlen, tmp, folder->num + 1);
161      break;
162     
163    case 'd':
164      if (folder->ff->st != NULL)
165      {
166        tnow = time (NULL);
167        t_fmt = tnow - folder->ff->st->st_mtime < 31536000 ? "%b %d %H:%M" : "%b %d  %Y";
168        strftime (date, sizeof (date), t_fmt, localtime (&folder->ff->st->st_mtime));
169        mutt_format_s (dest, destlen, fmt, date);
170      }
171      else
172        mutt_format_s (dest, destlen, fmt, "");
173      break;
174     
175    case 'f':
176    {
177      char *s;
178#ifdef USE_IMAP
179      if (folder->ff->imap)
180        s = NONULL (folder->ff->desc);
181      else
182#endif
183        s = NONULL (folder->ff->name);
184
185      snprintf (fn, sizeof (fn), "%s%s", s,
186                folder->ff->st ? (S_ISLNK (folder->ff->st->st_mode) ? "@" :             
187                                  (S_ISDIR (folder->ff->st->st_mode) ? "/" : 
188                                   ((folder->ff->st->st_mode & S_IXUSR) != 0 ? "*" : ""))) : "");
189     
190      mutt_format_s (dest, destlen, fmt, fn);
191      break;
192    }
193    case 'F':
194      if (folder->ff->st != NULL)
195      {
196        snprintf (permission, sizeof (permission), "%c%c%c%c%c%c%c%c%c%c",
197                  S_ISDIR(folder->ff->st->st_mode) ? 'd' : (S_ISLNK(folder->ff->st->st_mode) ? 'l' : '-'),
198                  (folder->ff->st->st_mode & S_IRUSR) != 0 ? 'r': '-',
199                  (folder->ff->st->st_mode & S_IWUSR) != 0 ? 'w' : '-',
200                  (folder->ff->st->st_mode & S_ISUID) != 0 ? 's' : (folder->ff->st->st_mode & S_IXUSR) != 0 ? 'x': '-',
201                  (folder->ff->st->st_mode & S_IRGRP) != 0 ? 'r' : '-',
202                  (folder->ff->st->st_mode & S_IWGRP) != 0 ? 'w' : '-',
203                  (folder->ff->st->st_mode & S_ISGID) != 0 ? 's' : (folder->ff->st->st_mode & S_IXGRP) != 0 ? 'x': '-',
204                  (folder->ff->st->st_mode & S_IROTH) != 0 ? 'r' : '-',
205                  (folder->ff->st->st_mode & S_IWOTH) != 0 ? 'w' : '-',
206                  (folder->ff->st->st_mode & S_ISVTX) != 0 ? 't' : (folder->ff->st->st_mode & S_IXOTH) != 0 ? 'x': '-');
207        mutt_format_s (dest, destlen, fmt, permission);
208      }
209#ifdef USE_IMAP
210      else if (folder->ff->imap)
211      {
212        /* mark folders with subfolders AND mail */
213        snprintf (permission, sizeof (permission), "IMAP %c",
214                  (folder->ff->inferiors && folder->ff->selectable) ? '+' : ' ');
215        mutt_format_s (dest, destlen, fmt, permission);
216      }                                       
217#endif
218      else
219        mutt_format_s (dest, destlen, fmt, "");
220      break;
221     
222    case 'g':
223      if (folder->ff->st != NULL)
224      {
225        if ((gr = getgrgid (folder->ff->st->st_gid)))
226          mutt_format_s (dest, destlen, fmt, gr->gr_name);
227        else
228        {
229          snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
230          snprintf (dest, destlen, tmp, folder->ff->st->st_gid);
231        }
232      }
233      else
234        mutt_format_s (dest, destlen, fmt, "");
235      break;
236     
237    case 'l':
238      if (folder->ff->st != NULL)
239      {
240        snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
241        snprintf (dest, destlen, tmp, folder->ff->st->st_nlink);
242      }
243      else
244        mutt_format_s (dest, destlen, fmt, "");
245      break;
246     
247    case 'N':
248#ifdef USE_IMAP
249      if (mx_is_imap (folder->ff->desc))
250      {
251        if (!optional)
252        {
253          snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
254          snprintf (dest, destlen, tmp, folder->ff->new);
255        }
256        else if (!folder->ff->new)
257          optional = 0;
258        break;
259      }
260#endif
261      snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
262      snprintf (dest, destlen, tmp, folder->ff->new ? 'N' : ' ');
263      break;
264     
265    case 's':
266      if (folder->ff->st != NULL)
267      {
268        mutt_pretty_size(fn, sizeof(fn), folder->ff->st->st_size);
269        snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
270        snprintf (dest, destlen, tmp, fn);
271      }
272      else
273        mutt_format_s (dest, destlen, fmt, "");
274      break;
275
276    case 't':
277      snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
278      snprintf (dest, destlen, tmp, folder->ff->tagged ? '*' : ' ');
279      break;
280
281    case 'u':
282      if (folder->ff->st != NULL)
283      {
284        if ((pw = getpwuid (folder->ff->st->st_uid)))
285          mutt_format_s (dest, destlen, fmt, pw->pw_name);
286        else
287        {
288          snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
289          snprintf (dest, destlen, tmp, folder->ff->st->st_uid);
290        }
291      }
292      else
293        mutt_format_s (dest, destlen, fmt, "");
294      break;
295
296    default:
297      snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
298      snprintf (dest, destlen, tmp, op);
299      break;
300  }
301
302  if (optional)
303    mutt_FormatString (dest, destlen, col, ifstring, folder_format_str, data, 0);
304  else if (flags & M_FORMAT_OPTIONAL)
305    mutt_FormatString (dest, destlen, col, elsestring, folder_format_str, data, 0);
306
307  return (src);
308}
309
310static void add_folder (MUTTMENU *m, struct browser_state *state,
311                        const char *name, const struct stat *s, int new)
312{
313  if (state->entrylen == state->entrymax)
314  {
315    /* need to allocate more space */
316    safe_realloc (&state->entry,
317                  sizeof (struct folder_file) * (state->entrymax += 256));
318    memset (&state->entry[state->entrylen], 0,
319            sizeof (struct folder_file) * 256);
320    if (m)
321      m->data = state->entry;
322  }
323
324  if (s != NULL)
325  {
326    (state->entry)[state->entrylen].mode = s->st_mode;
327    (state->entry)[state->entrylen].mtime = s->st_mtime;
328    (state->entry)[state->entrylen].size = s->st_size;
329   
330    (state->entry)[state->entrylen].st = safe_malloc (sizeof (struct stat));
331    memcpy ((state->entry)[state->entrylen].st, s, sizeof (struct stat));
332  }
333
334  (state->entry)[state->entrylen].new = new;
335  (state->entry)[state->entrylen].name = safe_strdup (name);
336  (state->entry)[state->entrylen].desc = safe_strdup (name);
337#ifdef USE_IMAP
338  (state->entry)[state->entrylen].imap = 0;
339#endif
340  (state->entrylen)++;
341}
342
343static void init_state (struct browser_state *state, MUTTMENU *menu)
344{
345  state->entrylen = 0;
346  state->entrymax = 256;
347  state->entry = (struct folder_file *) safe_calloc (state->entrymax, sizeof (struct folder_file));
348#ifdef USE_IMAP
349  state->imap_browse = 0;
350#endif
351  if (menu)
352    menu->data = state->entry;
353}
354
355static int examine_directory (MUTTMENU *menu, struct browser_state *state,
356                              char *d, const char *prefix)
357{
358  struct stat s;
359  DIR *dp;
360  struct dirent *de;
361  char buffer[_POSIX_PATH_MAX + SHORT_STRING];
362  BUFFY *tmp;
363
364  while (stat (d, &s) == -1)
365  {
366    if (errno == ENOENT)
367    {
368      /* The last used directory is deleted, try to use the parent dir. */
369      char *c = strrchr (d, '/');
370
371      if (c && (c > d))
372      {
373        *c = 0;
374        continue;
375      }
376    }
377    mutt_perror (d);
378    return (-1);
379  }
380
381  if (!S_ISDIR (s.st_mode))
382  {
383    mutt_error (_("%s is not a directory."), d);
384    return (-1);
385  }
386
387  mutt_buffy_check (0);
388
389  if ((dp = opendir (d)) == NULL)
390  {
391    mutt_perror (d);
392    return (-1);
393  }
394
395  init_state (state, menu);
396
397  while ((de = readdir (dp)) != NULL)
398  {
399    if (mutt_strcmp (de->d_name, ".") == 0)
400      continue;    /* we don't need . */
401   
402    if (prefix && *prefix && mutt_strncmp (prefix, de->d_name, mutt_strlen (prefix)) != 0)
403      continue;
404    if (!((regexec (Mask.rx, de->d_name, 0, NULL, 0) == 0) ^ Mask.not))
405      continue;
406
407    mutt_concat_path (buffer, d, de->d_name, sizeof (buffer));
408    if (lstat (buffer, &s) == -1)
409      continue;
410   
411    if ((! S_ISREG (s.st_mode)) && (! S_ISDIR (s.st_mode)) &&
412        (! S_ISLNK (s.st_mode)))
413      continue;
414   
415    tmp = Incoming;
416    while (tmp && mutt_strcmp (buffer, tmp->path))
417      tmp = tmp->next;
418    add_folder (menu, state, de->d_name, &s, (tmp) ? tmp->new : 0);
419  }
420  closedir (dp); 
421  browser_sort (state);
422  return 0;
423}
424
425static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state)
426{
427  struct stat s;
428  char buffer[LONG_STRING];
429  BUFFY *tmp = Incoming;
430#ifdef USE_IMAP
431  struct mailbox_state mbox;
432#endif
433
434  if (!Incoming)
435    return (-1);
436  mutt_buffy_check (0);
437
438  init_state (state, menu);
439
440  do
441  {
442#ifdef USE_IMAP
443    if (mx_is_imap (tmp->path))
444    {
445      imap_mailbox_state (tmp->path, &mbox);
446      add_folder (menu, state, tmp->path, NULL, mbox.new);
447      continue;
448    }
449#endif
450#ifdef USE_POP
451    if (mx_is_pop (tmp->path))
452    {
453      add_folder (menu, state, tmp->path, NULL, tmp->new);
454      continue;
455    }
456#endif
457    if (lstat (tmp->path, &s) == -1)
458      continue;
459
460    if ((! S_ISREG (s.st_mode)) && (! S_ISDIR (s.st_mode)) &&
461        (! S_ISLNK (s.st_mode)))
462      continue;
463   
464    strfcpy (buffer, NONULL(tmp->path), sizeof (buffer));
465    mutt_pretty_mailbox (buffer, sizeof (buffer));
466
467    add_folder (menu, state, buffer, &s, tmp->new);
468  }
469  while ((tmp = tmp->next));
470  browser_sort (state);
471  return 0;
472}
473
474static int select_file_search (MUTTMENU *menu, regex_t *re, int n)
475{
476  return (regexec (re, ((struct folder_file *) menu->data)[n].name, 0, NULL, 0));
477}
478
479static void folder_entry (char *s, size_t slen, MUTTMENU *menu, int num)
480{
481  FOLDER folder;
482
483  folder.ff = &((struct folder_file *) menu->data)[num];
484  folder.num = num;
485 
486  mutt_FormatString (s, slen, 0, NONULL(FolderFormat), folder_format_str, 
487      (unsigned long) &folder, M_FORMAT_ARROWCURSOR);
488}
489
490static void init_menu (struct browser_state *state, MUTTMENU *menu, char *title,
491                       size_t titlelen, int buffy)
492{
493  char path[_POSIX_PATH_MAX];
494
495  menu->max = state->entrylen;
496
497  if(menu->current >= menu->max)
498    menu->current = menu->max - 1;
499  if (menu->current < 0)
500    menu->current = 0;
501  if (menu->top > menu->current)
502    menu->top = 0;
503
504  menu->tagged = 0;
505 
506  if (buffy)
507    snprintf (title, titlelen, _("Mailboxes [%d]"), mutt_buffy_check (0));
508  else
509  {
510    strfcpy (path, LastDir, sizeof (path));
511    mutt_pretty_mailbox (path, sizeof (path));
512#ifdef USE_IMAP
513  if (state->imap_browse && option (OPTIMAPLSUB))
514    snprintf (title, titlelen, _("Subscribed [%s], File mask: %s"),
515              path, NONULL (Mask.pattern));
516  else
517#endif
518    snprintf (title, titlelen, _("Directory [%s], File mask: %s"),
519              path, NONULL(Mask.pattern));
520  }
521  menu->redraw = REDRAW_FULL;
522}
523
524static int file_tag (MUTTMENU *menu, int n, int m)
525{
526  struct folder_file *ff = &(((struct folder_file *)menu->data)[n]);
527  int ot;
528  if (S_ISDIR (ff->mode) || (S_ISLNK (ff->mode) && link_is_dir (LastDir, ff->name)))
529  {
530    mutt_error _("Can't attach a directory!");
531    return 0;
532  }
533 
534  ot = ff->tagged;
535  ff->tagged = (m >= 0 ? m : !ff->tagged);
536 
537  return ff->tagged - ot;
538}
539
540void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *numfiles)
541{
542  char buf[_POSIX_PATH_MAX];
543  char prefix[_POSIX_PATH_MAX] = "";
544  char helpstr[LONG_STRING];
545  char title[STRING];
546  struct browser_state state;
547  MUTTMENU *menu;
548  struct stat st;
549  int i, killPrefix = 0;
550  int multiple = (flags & M_SEL_MULTI)  ? 1 : 0;
551  int folder   = (flags & M_SEL_FOLDER) ? 1 : 0;
552  int buffy    = (flags & M_SEL_BUFFY)  ? 1 : 0;
553
554  buffy = buffy && folder;
555 
556  memset (&state, 0, sizeof (struct browser_state));
557
558  if (!folder)
559    strfcpy (LastDirBackup, LastDir, sizeof (LastDirBackup));
560
561  if (*f)
562  {
563    mutt_expand_path (f, flen);
564#ifdef USE_IMAP
565    if (mx_is_imap (f))
566    {
567      init_state (&state, NULL);
568      state.imap_browse = 1;
569      if (!imap_browse (f, &state))
570        strfcpy (LastDir, state.folder, sizeof (LastDir));
571    }
572    else
573    {
574#endif
575    for (i = mutt_strlen (f) - 1; i > 0 && f[i] != '/' ; i--);
576    if (i > 0)
577    {
578      if (f[0] == '/')
579      {
580        if (i > sizeof (LastDir) - 1) i = sizeof (LastDir) - 1;
581        strncpy (LastDir, f, i);
582        LastDir[i] = 0;
583      }
584      else
585      {
586        getcwd (LastDir, sizeof (LastDir));
587        safe_strcat (LastDir, sizeof (LastDir), "/");
588        safe_strncat (LastDir, sizeof (LastDir), f, i);
589      }
590    }
591    else
592    {
593      if (f[0] == '/')
594        strcpy (LastDir, "/");          /* __STRCPY_CHECKED__ */
595      else
596        getcwd (LastDir, sizeof (LastDir));
597    }
598
599    if (i <= 0 && f[0] != '/')
600      strfcpy (prefix, f, sizeof (prefix));
601    else
602      strfcpy (prefix, f + i + 1, sizeof (prefix));
603    killPrefix = 1;
604#ifdef USE_IMAP
605    }
606#endif
607  }
608  else 
609  {
610    if (!folder)
611      getcwd (LastDir, sizeof (LastDir));
612    else if (!LastDir[0])
613      strfcpy (LastDir, NONULL(Maildir), sizeof (LastDir));
614   
615#ifdef USE_IMAP
616    if (!buffy && mx_is_imap (LastDir))
617    {
618      init_state (&state, NULL);
619      state.imap_browse = 1;
620      imap_browse (LastDir, &state);
621      browser_sort (&state);
622    }
623    else
624#endif
625    {
626      i = mutt_strlen (LastDir);
627      while (i && LastDir[--i] == '/')
628        LastDir[i] = '\0';
629      if (!LastDir[0])
630        getcwd (LastDir, sizeof (LastDir));
631    }
632  }
633
634  *f = 0;