mutt stable branch with some hacks
at master 732 lines 18 kB view raw
1/* 2 * Copyright (C) 1996-2000,2010,2013 Michael R. Elkins <me@mutt.org> 3 * Copyright (C) 2016 Kevin J. McCarthy <kevin@8t8.us> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 */ 19 20#if HAVE_CONFIG_H 21# include "config.h" 22#endif 23 24#include "mutt.h" 25#include "buffy.h" 26#include "mailbox.h" 27#include "mx.h" 28 29#include "mutt_curses.h" 30 31#ifdef USE_SIDEBAR 32#include "sidebar.h" 33#endif 34 35#ifdef USE_IMAP 36#include "imap.h" 37#endif 38 39#include <string.h> 40#include <sys/stat.h> 41#include <dirent.h> 42#include <utime.h> 43#include <ctype.h> 44#include <unistd.h> 45 46#include <stdio.h> 47 48static time_t BuffyTime = 0; /* last time we started checking for mail */ 49static time_t BuffyStatsTime = 0; /* last time we check performed mail_check_stats */ 50time_t BuffyDoneTime = 0; /* last time we knew for sure how much mail there was. */ 51static short BuffyCount = 0; /* how many boxes with new mail */ 52static short BuffyNotify = 0; /* # of unnotified new boxes */ 53 54static BUFFY* buffy_get (const char *path); 55 56/* Find the last message in the file. 57 * upon success return 0. If no message found - return -1 */ 58 59static int fseek_last_message (FILE * f) 60{ 61 LOFF_T pos; 62 char buffer[BUFSIZ + 9]; /* 7 for "\n\nFrom " */ 63 int bytes_read; 64 int i; /* Index into `buffer' for scanning. */ 65 66 memset (buffer, 0, sizeof(buffer)); 67 fseek (f, 0, SEEK_END); 68 pos = ftello (f); 69 70 /* Set `bytes_read' to the size of the last, probably partial, buffer; 0 < 71 * `bytes_read' <= `BUFSIZ'. */ 72 bytes_read = pos % BUFSIZ; 73 if (bytes_read == 0) 74 bytes_read = BUFSIZ; 75 /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that all 76 * reads will be on block boundaries, which might increase efficiency. */ 77 while ((pos -= bytes_read) >= 0) 78 { 79 /* we save in the buffer at the end the first 7 chars from the last read */ 80 strncpy (buffer + BUFSIZ, buffer, 5+2); /* 2 == 2 * mutt_strlen(CRLF) */ 81 fseeko (f, pos, SEEK_SET); 82 bytes_read = fread (buffer, sizeof (char), bytes_read, f); 83 if (bytes_read == -1) 84 return -1; 85 for (i = bytes_read; --i >= 0;) 86 if (!mutt_strncmp (buffer + i, "\n\nFrom ", mutt_strlen ("\n\nFrom "))) 87 { /* found it - go to the beginning of the From */ 88 fseeko (f, pos + i + 2, SEEK_SET); 89 return 0; 90 } 91 bytes_read = BUFSIZ; 92 } 93 94 /* here we are at the beginning of the file */ 95 if (!mutt_strncmp ("From ", buffer, 5)) 96 { 97 fseek (f, 0, 0); 98 return (0); 99 } 100 101 return (-1); 102} 103 104/* Return 1 if the last message is new */ 105static int test_last_status_new (FILE * f) 106{ 107 HEADER *hdr; 108 ENVELOPE* tmp_envelope; 109 int result = 0; 110 111 if (fseek_last_message (f) == -1) 112 return (0); 113 114 hdr = mutt_new_header (); 115 tmp_envelope = mutt_read_rfc822_header (f, hdr, 0, 0); 116 if (!(hdr->read || hdr->old)) 117 result = 1; 118 119 mutt_free_envelope(&tmp_envelope); 120 mutt_free_header (&hdr); 121 122 return result; 123} 124 125static int test_new_folder (const char *path) 126{ 127 FILE *f; 128 int rc = 0; 129 int typ; 130 131 typ = mx_get_magic (path); 132 133 if (typ != MUTT_MBOX && typ != MUTT_MMDF) 134 return 0; 135 136 if ((f = fopen (path, "rb"))) 137 { 138 rc = test_last_status_new (f); 139 safe_fclose (&f); 140 } 141 142 return rc; 143} 144 145void mutt_buffy_cleanup (const char *buf, struct stat *st) 146{ 147 struct utimbuf ut; 148 BUFFY *tmp; 149 150 if (option(OPTCHECKMBOXSIZE)) 151 { 152 tmp = mutt_find_mailbox (buf); 153 if (tmp && !tmp->new) 154 mutt_update_mailbox (tmp); 155 } 156 else 157 { 158 /* fix up the times so buffy won't get confused */ 159 if (st->st_mtime > st->st_atime) 160 { 161 ut.actime = st->st_atime; 162 ut.modtime = time (NULL); 163 utime (buf, &ut); 164 } 165 else 166 utime (buf, NULL); 167 } 168} 169 170BUFFY *mutt_find_mailbox (const char *path) 171{ 172 BUFFY *tmp = NULL; 173 struct stat sb; 174 struct stat tmp_sb; 175 176 if (stat (path,&sb) != 0) 177 return NULL; 178 179 for (tmp = Incoming; tmp; tmp = tmp->next) 180 { 181 if (stat (tmp->path,&tmp_sb) ==0 && 182 sb.st_dev == tmp_sb.st_dev && sb.st_ino == tmp_sb.st_ino) 183 break; 184 } 185 return tmp; 186} 187 188void mutt_update_mailbox (BUFFY * b) 189{ 190 struct stat sb; 191 192 if (!b) 193 return; 194 195 if (stat (b->path, &sb) == 0) 196 b->size = (off_t) sb.st_size; 197 else 198 b->size = 0; 199 return; 200} 201 202static BUFFY *buffy_new (const char *path) 203{ 204 BUFFY* buffy; 205 char rp[PATH_MAX] = ""; 206 char *r = NULL; 207 208 buffy = (BUFFY *) safe_calloc (1, sizeof (BUFFY)); 209 strfcpy (buffy->path, path, sizeof (buffy->path)); 210 r = realpath (path, rp); 211 strfcpy (buffy->realpath, r ? rp : path, sizeof (buffy->realpath)); 212 buffy->next = NULL; 213 buffy->magic = 0; 214 215 return buffy; 216} 217 218static void buffy_free (BUFFY **mailbox) 219{ 220 FREE (mailbox); /* __FREE_CHECKED__ */ 221} 222 223int mutt_parse_mailboxes (BUFFER *path, BUFFER *s, unsigned long data, BUFFER *err) 224{ 225 BUFFY **tmp,*tmp1; 226 char buf[_POSIX_PATH_MAX]; 227 struct stat sb; 228 char f1[PATH_MAX]; 229 char *p; 230 231 while (MoreArgs (s)) 232 { 233 mutt_extract_token (path, s, 0); 234 strfcpy (buf, path->data, sizeof (buf)); 235 236 if(data == MUTT_UNMAILBOXES && mutt_strcmp(buf,"*") == 0) 237 { 238 for (tmp = &Incoming; *tmp;) 239 { 240 tmp1=(*tmp)->next; 241#ifdef USE_SIDEBAR 242 mutt_sb_notify_mailbox (*tmp, 0); 243#endif 244 buffy_free (tmp); 245 *tmp=tmp1; 246 } 247 return 0; 248 } 249 250 mutt_expand_path (buf, sizeof (buf)); 251 252 /* Skip empty tokens. */ 253 if(!*buf) continue; 254 255 /* avoid duplicates */ 256 p = realpath (buf, f1); 257 for (tmp = &Incoming; *tmp; tmp = &((*tmp)->next)) 258 { 259 if (mutt_strcmp (p ? p : buf, (*tmp)->realpath) == 0) 260 { 261 dprint(3,(debugfile,"mailbox '%s' already registered as '%s'\n", buf, (*tmp)->path)); 262 break; 263 } 264 } 265 266 if(data == MUTT_UNMAILBOXES) 267 { 268 if(*tmp) 269 { 270 tmp1=(*tmp)->next; 271#ifdef USE_SIDEBAR 272 mutt_sb_notify_mailbox (*tmp, 0); 273#endif 274 buffy_free (tmp); 275 *tmp=tmp1; 276 } 277 continue; 278 } 279 280 if (!*tmp) { 281 *tmp = buffy_new (buf); 282#ifdef USE_SIDEBAR 283 mutt_sb_notify_mailbox (*tmp, 1); 284#endif 285 } 286 287 (*tmp)->new = 0; 288 (*tmp)->notified = 1; 289 (*tmp)->newly_created = 0; 290 291 /* for check_mbox_size, it is important that if the folder is new (tested by 292 * reading it), the size is set to 0 so that later when we check we see 293 * that it increased . without check_mbox_size we probably don't care. 294 */ 295 if (option(OPTCHECKMBOXSIZE) && 296 stat ((*tmp)->path, &sb) == 0 && !test_new_folder ((*tmp)->path)) 297 { 298 /* some systems out there don't have an off_t type */ 299 (*tmp)->size = (off_t) sb.st_size; 300 } 301 else 302 (*tmp)->size = 0; 303 } 304 return 0; 305} 306 307/* Checks the specified maildir subdir (cur or new) for new mail or mail counts. 308 * check_new: if true, check for new mail. 309 * check_stats: if true, count total, new, and flagged mesages. 310 * Returns 1 if the dir has new mail. 311 */ 312static int buffy_maildir_check_dir (BUFFY* mailbox, const char *dir_name, int check_new, 313 int check_stats) 314{ 315 char path[_POSIX_PATH_MAX]; 316 char msgpath[_POSIX_PATH_MAX]; 317 DIR *dirp; 318 struct dirent *de; 319 char *p; 320 int rc = 0; 321 struct stat sb; 322 323 snprintf (path, sizeof (path), "%s/%s", mailbox->path, dir_name); 324 325 /* when $mail_check_recent is set, if the new/ directory hasn't been modified since 326 * the user last exited the mailbox, then we know there is no recent mail. 327 */ 328 if (check_new && option(OPTMAILCHECKRECENT)) 329 { 330 if (stat(path, &sb) == 0 && sb.st_mtime < mailbox->last_visited) 331 { 332 rc = 0; 333 check_new = 0; 334 } 335 } 336 337 if (! (check_new || check_stats)) 338 return rc; 339 340 if ((dirp = opendir (path)) == NULL) 341 { 342 mailbox->magic = 0; 343 return 0; 344 } 345 346 while ((de = readdir (dirp)) != NULL) 347 { 348 if (*de->d_name == '.') 349 continue; 350 351 p = strstr (de->d_name, ":2,"); 352 if (p && strchr (p + 3, 'T')) 353 continue; 354 355 if (check_stats) 356 { 357 mailbox->msg_count++; 358 if (p && strchr (p + 3, 'F')) 359 mailbox->msg_flagged++; 360 } 361 if (!p || !strchr (p + 3, 'S')) 362 { 363 if (check_stats) 364 mailbox->msg_unread++; 365 if (check_new) 366 { 367 if (option(OPTMAILCHECKRECENT)) 368 { 369 snprintf(msgpath, sizeof(msgpath), "%s/%s", path, de->d_name); 370 /* ensure this message was received since leaving this mailbox */ 371 if (stat(msgpath, &sb) == 0 && (sb.st_ctime <= mailbox->last_visited)) 372 continue; 373 } 374 mailbox->new = 1; 375 rc = 1; 376 check_new = 0; 377 if (!check_stats) 378 break; 379 } 380 } 381 } 382 383 closedir (dirp); 384 385 return rc; 386} 387 388/* Checks new mail for a maildir mailbox. 389 * check_stats: if true, also count total, new, and flagged mesages. 390 * Returns 1 if the mailbox has new mail. 391 */ 392static int buffy_maildir_check (BUFFY* mailbox, int check_stats) 393{ 394 int rc, check_new = 1; 395 396 if (check_stats) 397 { 398 mailbox->msg_count = 0; 399 mailbox->msg_unread = 0; 400 mailbox->msg_flagged = 0; 401 } 402 403 rc = buffy_maildir_check_dir (mailbox, "new", check_new, check_stats); 404 405 check_new = !rc && option (OPTMAILDIRCHECKCUR); 406 if (check_new || check_stats) 407 if (buffy_maildir_check_dir (mailbox, "cur", check_new, check_stats)) 408 rc = 1; 409 410 return rc; 411} 412 413/* Checks new mail for an mbox mailbox 414 * check_stats: if true, also count total, new, and flagged mesages. 415 * Returns 1 if the mailbox has new mail. 416 */ 417static int buffy_mbox_check (BUFFY* mailbox, struct stat *sb, int check_stats) 418{ 419 int rc = 0; 420 int new_or_changed; 421 CONTEXT ctx; 422 423 if (option (OPTCHECKMBOXSIZE)) 424 new_or_changed = sb->st_size > mailbox->size; 425 else 426 new_or_changed = sb->st_mtime > sb->st_atime 427 || (mailbox->newly_created && sb->st_ctime == sb->st_mtime && sb->st_ctime == sb->st_atime); 428 429 if (new_or_changed) 430 { 431 if (!option(OPTMAILCHECKRECENT) || sb->st_mtime > mailbox->last_visited) 432 { 433 rc = 1; 434 mailbox->new = 1; 435 } 436 } 437 else if (option(OPTCHECKMBOXSIZE)) 438 { 439 /* some other program has deleted mail from the folder */ 440 mailbox->size = (off_t) sb->st_size; 441 } 442 443 if (mailbox->newly_created && 444 (sb->st_ctime != sb->st_mtime || sb->st_ctime != sb->st_atime)) 445 mailbox->newly_created = 0; 446 447 if (check_stats && 448 (mailbox->stats_last_checked < sb->st_mtime)) 449 { 450 if (mx_open_mailbox (mailbox->path, 451 MUTT_READONLY | MUTT_QUIET | MUTT_NOSORT | MUTT_PEEK, 452 &ctx) != NULL) 453 { 454 mailbox->msg_count = ctx.msgcount; 455 mailbox->msg_unread = ctx.unread; 456 mailbox->msg_flagged = ctx.flagged; 457 mailbox->stats_last_checked = ctx.mtime; 458 mx_close_mailbox (&ctx, 0); 459 } 460 } 461 462 return rc; 463} 464 465/* Check all Incoming for new mail and total/new/flagged messages 466 * force: if true, ignore BuffyTimeout and check for new mail anyway 467 */ 468int mutt_buffy_check (int force) 469{ 470 BUFFY *tmp; 471 struct stat sb; 472 struct stat contex_sb; 473 time_t t; 474 int check_stats = 0; 475#ifdef USE_SIDEBAR 476 short orig_new; 477 int orig_count, orig_unread, orig_flagged; 478#endif 479 480 sb.st_size=0; 481 contex_sb.st_dev=0; 482 contex_sb.st_ino=0; 483 484#ifdef USE_IMAP 485 /* update postponed count as well, on force */ 486 if (force) 487 mutt_update_num_postponed (); 488#endif 489 490 /* fastest return if there are no mailboxes */ 491 if (!Incoming) 492 return 0; 493 t = time (NULL); 494 if (!force && (t - BuffyTime < BuffyTimeout)) 495 return BuffyCount; 496 497 if (option (OPTMAILCHECKSTATS) && 498 (t - BuffyStatsTime >= BuffyCheckStatsInterval)) 499 { 500 check_stats = 1; 501 BuffyStatsTime = t; 502 } 503 504 BuffyTime = t; 505 BuffyCount = 0; 506 BuffyNotify = 0; 507 508#ifdef USE_IMAP 509 BuffyCount += imap_buffy_check (force, check_stats); 510#endif 511 512 /* check device ID and serial number instead of comparing paths */ 513 if (!Context || Context->magic == MUTT_IMAP || Context->magic == MUTT_POP 514 || stat (Context->path, &contex_sb) != 0) 515 { 516 contex_sb.st_dev=0; 517 contex_sb.st_ino=0; 518 } 519 520 for (tmp = Incoming; tmp; tmp = tmp->next) 521 { 522#ifdef USE_SIDEBAR 523 orig_new = tmp->new; 524 orig_count = tmp->msg_count; 525 orig_unread = tmp->msg_unread; 526 orig_flagged = tmp->msg_flagged; 527#endif 528 529 if (tmp->magic != MUTT_IMAP) 530 { 531 tmp->new = 0; 532#ifdef USE_POP 533 if (mx_is_pop (tmp->path)) 534 tmp->magic = MUTT_POP; 535 else 536#endif 537 if (stat (tmp->path, &sb) != 0 || (S_ISREG(sb.st_mode) && sb.st_size == 0) || 538 (!tmp->magic && (tmp->magic = mx_get_magic (tmp->path)) <= 0)) 539 { 540 /* if the mailbox still doesn't exist, set the newly created flag to 541 * be ready for when it does. */ 542 tmp->newly_created = 1; 543 tmp->magic = 0; 544 tmp->size = 0; 545 continue; 546 } 547 } 548 549 /* check to see if the folder is the currently selected folder 550 * before polling */ 551 if (!Context || !Context->path || 552 (( tmp->magic == MUTT_IMAP || tmp->magic == MUTT_POP ) 553 ? mutt_strcmp (tmp->path, Context->path) : 554 (sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino))) 555 { 556 switch (tmp->magic) 557 { 558 case MUTT_MBOX: 559 case MUTT_MMDF: 560 if (buffy_mbox_check (tmp, &sb, check_stats) > 0) 561 BuffyCount++; 562 break; 563 564 case MUTT_MAILDIR: 565 if (buffy_maildir_check (tmp, check_stats) > 0) 566 BuffyCount++; 567 break; 568 569 case MUTT_MH: 570 if (mh_buffy (tmp, check_stats) > 0) 571 BuffyCount++; 572 break; 573 } 574 } 575 else if (option(OPTCHECKMBOXSIZE) && Context && Context->path) 576 tmp->size = (off_t) sb.st_size; /* update the size of current folder */ 577 578#ifdef USE_SIDEBAR 579 if ((orig_new != tmp->new) || 580 (orig_count != tmp->msg_count) || 581 (orig_unread != tmp->msg_unread) || 582 (orig_flagged != tmp->msg_flagged)) 583 SidebarNeedsRedraw = 1; 584#endif 585 586 if (!tmp->new) 587 tmp->notified = 0; 588 else if (!tmp->notified) 589 BuffyNotify++; 590 } 591 592 BuffyDoneTime = BuffyTime; 593 return (BuffyCount); 594} 595 596int mutt_buffy_list (void) 597{ 598 BUFFY *tmp; 599 char path[_POSIX_PATH_MAX]; 600 char buffylist[2*STRING]; 601 size_t pos = 0; 602 int first = 1; 603 604 int have_unnotified = BuffyNotify; 605 606 buffylist[0] = 0; 607 pos += strlen (strncat (buffylist, _("New mail in "), sizeof (buffylist) - 1 - pos)); /* __STRNCAT_CHECKED__ */ 608 for (tmp = Incoming; tmp; tmp = tmp->next) 609 { 610 /* Is there new mail in this mailbox? */ 611 if (!tmp->new || (have_unnotified && tmp->notified)) 612 continue; 613 614 strfcpy (path, tmp->path, sizeof (path)); 615 mutt_pretty_mailbox (path, sizeof (path)); 616 617 if (!first && (MuttMessageWindow->cols >= 7) && 618 (pos + strlen (path) >= (size_t)MuttMessageWindow->cols - 7)) 619 break; 620 621 if (!first) 622 pos += strlen (strncat(buffylist + pos, ", ", sizeof(buffylist)-1-pos)); /* __STRNCAT_CHECKED__ */ 623 624 /* Prepend an asterisk to mailboxes not already notified */ 625 if (!tmp->notified) 626 { 627 /* pos += strlen (strncat(buffylist + pos, "*", sizeof(buffylist)-1-pos)); __STRNCAT_CHECKED__ */ 628 tmp->notified = 1; 629 BuffyNotify--; 630 } 631 pos += strlen (strncat(buffylist + pos, path, sizeof(buffylist)-1-pos)); /* __STRNCAT_CHECKED__ */ 632 first = 0; 633 } 634 if (!first && tmp) 635 { 636 strncat (buffylist + pos, ", ...", sizeof (buffylist) - 1 - pos); /* __STRNCAT_CHECKED__ */ 637 } 638 if (!first) 639 { 640 mutt_message ("%s", buffylist); 641 return (1); 642 } 643 /* there were no mailboxes needing to be notified, so clean up since 644 * BuffyNotify has somehow gotten out of sync 645 */ 646 BuffyNotify = 0; 647 return (0); 648} 649 650void mutt_buffy_setnotified (const char *path) 651{ 652 BUFFY *buffy; 653 654 buffy = buffy_get(path); 655 if (!buffy) 656 return; 657 658 buffy->notified = 1; 659 time(&buffy->last_visited); 660} 661 662int mutt_buffy_notify (void) 663{ 664 if (mutt_buffy_check (0) && BuffyNotify) 665 { 666 return (mutt_buffy_list ()); 667 } 668 return (0); 669} 670 671/* 672 * mutt_buffy() -- incoming folders completion routine 673 * 674 * given a folder name, this routine gives the next incoming folder with new 675 * mail. 676 */ 677void mutt_buffy (char *s, size_t slen) 678{ 679 BUFFY *tmp = Incoming; 680 int pass, found = 0; 681 682 mutt_expand_path (s, slen); 683 684 if (mutt_buffy_check (0)) 685 { 686 for (pass = 0; pass < 2; pass++) 687 for (tmp = Incoming; tmp; tmp = tmp->next) 688 { 689 mutt_expand_path (tmp->path, sizeof (tmp->path)); 690 if ((found || pass) && tmp->new) 691 { 692 strfcpy (s, tmp->path, slen); 693 mutt_pretty_mailbox (s, slen); 694 return; 695 } 696 if (mutt_strcmp (s, tmp->path) == 0) 697 found = 1; 698 } 699 700 mutt_buffy_check (1); /* buffy was wrong - resync things */ 701 } 702 703 /* no folders with new mail */ 704 *s = '\0'; 705} 706 707/* fetch buffy object for given path, if present */ 708static BUFFY* buffy_get (const char *path) 709{ 710 BUFFY *cur; 711 char *epath; 712 713 if (!path) 714 return NULL; 715 716 epath = safe_strdup(path); 717 mutt_expand_path(epath, mutt_strlen(epath)); 718 719 for (cur = Incoming; cur; cur = cur->next) 720 { 721 /* must be done late because e.g. IMAP delimiter may change */ 722 mutt_expand_path (cur->path, sizeof (cur->path)); 723 if (!mutt_strcmp(cur->path, path)) 724 { 725 FREE (&epath); 726 return cur; 727 } 728 } 729 730 FREE (&epath); 731 return NULL; 732}