debian/patches/features/compressed-folders.debian.patch ======================================================= Date: Thu, 27 Feb 2014 15:49:18 +0100 Subject: compressed-folders.debian To enable the use of compressed folders. Gbp-Pq: Topic features --- config-debian.h | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 config-debian.h diff --git a/config-debian.h b/config-debian.h new file mode 100644 index 0000000..fdf5322 --- /dev/null +++ b/config-debian.h @@ -0,0 +1,4 @@ +#ifndef USE_COMPRESSED +#error "You forgot to update debian/patches/autotools-update." +#error "Run sh debian/update-autotools.sh" +#endif debian/patches/features/compressed-folders.patch ================================================ Date: Thu, 27 Feb 2014 15:46:00 +0100 Subject: compressed-folders The home page for this patch is: http://www.spinnaker.de/mutt/compressed/ * Patch last synced with upstream: - Date: 2008-05-20 - File: http://www.spinnaker.de/mutt/compressed/patch-1.5.18.rr.compressed.1.gz * Changes made: - 2008-05-20 myon: refreshed to remove hunks in auto* files - 2009-09-15 myon: refreshed for mutt-1.5.19 status.c:103: add sizeof (tmp) to mutt_pretty_mailbox - 2009-09-15 scotton: removed doc/Muttrc for mutt-1.5.19 (only patch doc/Muttrc.head) - 2009-09-11 antonio: removed DefaultMagic, see 541360 - 2010-05-31 myon: remove commented paragraph "Use folders..." in doc/Muttrc.head, see #578096 Signed-off-by: Matteo F. Vescovi Gbp-Pq: Topic features --- Makefile.am | 4 +- compress.c | 499 ++++++++++++++++++++++++++++++++++++++++++++++++++++ compress.h | 27 +++ configure.ac | 5 + curs_main.c | 5 + doc/manual.xml.head | 199 +++++++++++++++++++++ doc/muttrc.man.head | 18 ++ hook.c | 14 ++ init.h | 5 + main.c | 6 + mbox.c | 10 ++ mutt.h | 10 ++ mx.c | 42 ++++- mx.h | 3 + po/POTFILES.in | 1 + po/de.po | 30 ++++ status.c | 8 + 17 files changed, 883 insertions(+), 3 deletions(-) create mode 100644 compress.c create mode 100644 compress.h diff --git a/Makefile.am b/Makefile.am index 8166b1b..09dd64b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -22,7 +22,7 @@ BUILT_SOURCES = keymap_defs.h patchlist.c reldate.h conststrings.c $(HCVERSION) bin_PROGRAMS = mutt @DOTLOCK_TARGET@ @PGPAUX_TARGET@ mutt_SOURCES = \ addrbook.c alias.c attach.c base64.c browser.c buffy.c color.c \ - crypt.c cryptglue.c \ + crypt.c cryptglue.c compress.c \ commands.c complete.c compose.c copy.c curs_lib.c curs_main.c date.c \ edit.c enter.c flags.c init.c filter.c from.c \ getdomain.c group.c \ @@ -61,7 +61,7 @@ EXTRA_mutt_SOURCES = account.c bcache.c crypt-gpgme.c crypt-mod-pgp-classic.c \ bcache.h browser.h hcache.h mbyte.h mutt_idna.h remailer.h url.h EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP OPS.CRYPT OPS.SMIME TODO UPDATING \ - configure account.h \ + configure account.h compress.h \ attach.h buffy.h charset.h copy.h crypthash.h dotlock.h functions.h gen_defs \ globals.h hash.h history.h init.h keymap.h mutt_crypt.h \ mailbox.h mapping.h md5.h mime.h mutt.h mutt_curses.h mutt_menu.h \ diff --git a/compress.c b/compress.c new file mode 100644 index 0000000..3be181b --- /dev/null +++ b/compress.c @@ -0,0 +1,499 @@ +/* + * Copyright (C) 1997 Alain Penders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include "mutt.h" + +#ifdef USE_COMPRESSED + +#include "mx.h" +#include "mailbox.h" +#include "mutt_curses.h" + +#include +#include +#include +#include + +typedef struct +{ + const char *close; /* close-hook command */ + const char *open; /* open-hook command */ + const char *append; /* append-hook command */ + off_t size; /* size of real folder */ +} COMPRESS_INFO; + + +/* + * ctx - context to lock + * excl - exclusive lock? + * retry - should retry if unable to lock? + */ +int mbox_lock_compressed (CONTEXT *ctx, FILE *fp, int excl, int retry) +{ + int r; + + if ((r = mx_lock_file (ctx->realpath, fileno (fp), excl, 1, retry)) == 0) + ctx->locked = 1; + else if (retry && !excl) + { + ctx->readonly = 1; + return 0; + } + + return (r); +} + +void mbox_unlock_compressed (CONTEXT *ctx, FILE *fp) +{ + if (ctx->locked) + { + fflush (fp); + + mx_unlock_file (ctx->realpath, fileno (fp), 1); + ctx->locked = 0; + } +} + +static int is_new (const char *path) +{ + return (access (path, W_OK) != 0 && errno == ENOENT) ? 1 : 0; +} + +static const char* find_compress_hook (int type, const char *path) +{ + const char* c = mutt_find_hook (type, path); + return (!c || !*c) ? NULL : c; +} + +int mutt_can_read_compressed (const char *path) +{ + return find_compress_hook (M_OPENHOOK, path) ? 1 : 0; +} + +/* + * if the file is new, we really do not append, but create, and so use + * close-hook, and not append-hook + */ +static const char* get_append_command (const char *path, const CONTEXT* ctx) +{ + COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo; + return (is_new (path)) ? ci->close : ci->append; +} + +int mutt_can_append_compressed (const char *path) +{ + int magic; + + if (is_new (path)) + { + char *dir_path = safe_strdup(path); + char *aux = strrchr(dir_path, '/'); + int dir_valid = 1; + if (aux) + { + *aux='\0'; + if (access(dir_path, W_OK|X_OK)) + dir_valid = 0; + } + safe_free((void**)&dir_path); + return dir_valid && (find_compress_hook (M_CLOSEHOOK, path) ? 1 : 0); + } + + magic = mx_get_magic (path); + + if (magic != 0 && magic != M_COMPRESSED) + return 0; + + return (find_compress_hook (M_APPENDHOOK, path) + || (find_compress_hook (M_OPENHOOK, path) + && find_compress_hook (M_CLOSEHOOK, path))) ? 1 : 0; +} + +/* open a compressed mailbox */ +static COMPRESS_INFO *set_compress_info (CONTEXT *ctx) +{ + COMPRESS_INFO *ci; + + /* Now lets uncompress this thing */ + ci = safe_malloc (sizeof (COMPRESS_INFO)); + ctx->compressinfo = (void*) ci; + ci->append = find_compress_hook (M_APPENDHOOK, ctx->path); + ci->open = find_compress_hook (M_OPENHOOK, ctx->path); + ci->close = find_compress_hook (M_CLOSEHOOK, ctx->path); + return ci; +} + +static void set_path (CONTEXT* ctx) +{ + char tmppath[_POSIX_PATH_MAX]; + + /* Setup the right paths */ + ctx->realpath = ctx->path; + + /* Uncompress to /tmp */ + mutt_mktemp (tmppath, sizeof(tmppath)); + ctx->path = safe_malloc (strlen (tmppath) + 1); + strcpy (ctx->path, tmppath); +} + +static int get_size (const char* path) +{ + struct stat sb; + if (stat (path, &sb) != 0) + return 0; + return (sb.st_size); +} + +static void store_size (CONTEXT* ctx) +{ + COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo; + ci->size = get_size (ctx->realpath); +} + +static const char * +compresshook_format_str (char *dest, size_t destlen, size_t col, char op, + const char *src, const char *fmt, + const char *ifstring, const char *elsestring, + unsigned long data, format_flag flags) +{ + char tmp[SHORT_STRING]; + + CONTEXT *ctx = (CONTEXT *) data; + switch (op) + { + case 'f': + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, ctx->realpath); + break; + case 't': + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, ctx->path); + break; + } + return (src); +} + +/* + * check that the command has both %f and %t + * 0 means OK, -1 means error + */ +int mutt_test_compress_command (const char* cmd) +{ + return (strstr (cmd, "%f") && strstr (cmd, "%t")) ? 0 : -1; +} + +static char *get_compression_cmd (const char* cmd, const CONTEXT* ctx) +{ + char expanded[_POSIX_PATH_MAX]; + mutt_FormatString (expanded, sizeof (expanded), 0, cmd, + compresshook_format_str, (unsigned long) ctx, 0); + return safe_strdup (expanded); +} + +int mutt_check_mailbox_compressed (CONTEXT* ctx) +{ + COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo; + if (ci->size != get_size (ctx->realpath)) + { + FREE (&ctx->compressinfo); + FREE (&ctx->realpath); + mutt_error _("Mailbox was corrupted!"); + return (-1); + } + return (0); +} + +int mutt_open_read_compressed (CONTEXT *ctx) +{ + char *cmd; + FILE *fp; + int rc; + + COMPRESS_INFO *ci = set_compress_info (ctx); + if (!ci->open) { + ctx->magic = 0; + FREE (&ctx->compressinfo); + return (-1); + } + if (!ci->close || access (ctx->path, W_OK) != 0) + ctx->readonly = 1; + + set_path (ctx); + store_size (ctx); + + if (!ctx->quiet) + mutt_message (_("Decompressing %s..."), ctx->realpath); + + cmd = get_compression_cmd (ci->open, ctx); + if (cmd == NULL) + return (-1); + dprint (2, (debugfile, "DecompressCmd: '%s'\n", cmd)); + + if ((fp = fopen (ctx->realpath, "r")) == NULL) + { + mutt_perror (ctx->realpath); + FREE (&cmd); + return (-1); + } + mutt_block_signals (); + if (mbox_lock_compressed (ctx, fp, 0, 1) == -1) + { + fclose (fp); + mutt_unblock_signals (); + mutt_error _("Unable to lock mailbox!"); + FREE (&cmd); + return (-1); + } + + endwin (); + fflush (stdout); + fprintf (stderr, _("Decompressing %s...\n"),ctx->realpath); + rc = mutt_system (cmd); + mbox_unlock_compressed (ctx, fp); + mutt_unblock_signals (); + fclose (fp); + + if (rc) + { + mutt_any_key_to_continue (NULL); + ctx->magic = 0; + FREE (&ctx->compressinfo); + mutt_error (_("Error executing: %s : unable to open the mailbox!\n"), cmd); + // remove the partial uncompressed file + remove_file (ctx); + restore_path (ctx); + } + FREE (&cmd); + if (rc) + return (-1); + + if (mutt_check_mailbox_compressed (ctx)) + return (-1); + + ctx->magic = mx_get_magic (ctx->path); + + return (0); +} + +void restore_path (CONTEXT* ctx) +{ + FREE (&ctx->path); + ctx->path = ctx->realpath; +} + +/* remove the temporary mailbox */ +void remove_file (CONTEXT* ctx) +{ + if (ctx->magic == M_MBOX || ctx->magic == M_MMDF) + remove (ctx->path); +} + +int mutt_open_append_compressed (CONTEXT *ctx) +{ + FILE *fh; + COMPRESS_INFO *ci = set_compress_info (ctx); + + if (!get_append_command (ctx->path, ctx)) + { + if (ci->open && ci->close) + return (mutt_open_read_compressed (ctx)); + + ctx->magic = 0; + FREE (&ctx->compressinfo); + return (-1); + } + + set_path (ctx); + + if (!is_new (ctx->realpath)) + if ((fh = fopen (ctx->path, "w"))) + fclose (fh); + /* No error checking - the parent function will catch it */ + + return (0); +} + +/* close a compressed mailbox */ +void mutt_fast_close_compressed (CONTEXT *ctx) +{ + dprint (2, (debugfile, "mutt_fast_close_compressed called on '%s'\n", + ctx->path)); + + if (ctx->compressinfo) + { + if (ctx->fp) + fclose (ctx->fp); + ctx->fp = NULL; + /* if the folder was removed, remove the gzipped folder too */ + if ((ctx->magic > 0) + && (access (ctx->path, F_OK) != 0) + && ! option (OPTSAVEEMPTY)) + remove (ctx->realpath); + else + remove_file (ctx); + + restore_path (ctx); + FREE (&ctx->compressinfo); + } +} + +/* return 0 on success, -1 on failure */ +int mutt_sync_compressed (CONTEXT* ctx) +{ + char *cmd; + int rc = 0; + FILE *fp; + COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo; + + if (!ctx->quiet) + mutt_message (_("Compressing %s..."), ctx->realpath); + + cmd = get_compression_cmd (ci->close, ctx); + if (cmd == NULL) + return (-1); + + if ((fp = fopen (ctx->realpath, "a")) == NULL) + { + mutt_perror (ctx->realpath); + FREE (&cmd); + return (-1); + } + mutt_block_signals (); + if (mbox_lock_compressed (ctx, fp, 1, 1) == -1) + { + fclose (fp); + mutt_unblock_signals (); + mutt_error _("Unable to lock mailbox!"); + store_size (ctx); + FREE (&cmd); + return (-1); + } + + dprint (2, (debugfile, "CompressCommand: '%s'\n", cmd)); + + endwin (); + fflush (stdout); + fprintf (stderr, _("Compressing %s...\n"), ctx->realpath); + if (mutt_system (cmd)) + { + mutt_any_key_to_continue (NULL); + mutt_error (_("%s: Error compressing mailbox! Original mailbox deleted, uncompressed one kept!\n"), ctx->path); + rc = -1; + } + + mbox_unlock_compressed (ctx, fp); + mutt_unblock_signals (); + fclose (fp); + + FREE (&cmd); + + store_size (ctx); + + return (rc); +} + +int mutt_slow_close_compressed (CONTEXT *ctx) +{ + FILE *fp; + const char *append; + char *cmd; + COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo; + + dprint (2, (debugfile, "mutt_slow_close_compressed called on '%s'\n", + ctx->path)); + + if (! (ctx->append + && ((append = get_append_command (ctx->realpath, ctx)) + || (append = ci->close)))) + { + /* if we can not or should not append, we only have to remove the */ + /* compressed info, because sync was already called */ + mutt_fast_close_compressed (ctx); + return (0); + } + + if (ctx->fp) + fclose (ctx->fp); + ctx->fp = NULL; + + if (!ctx->quiet) + { + if (append == ci->close) + mutt_message (_("Compressing %s..."), ctx->realpath); + else + mutt_message (_("Compressed-appending to %s..."), ctx->realpath); + } + + cmd = get_compression_cmd (append, ctx); + if (cmd == NULL) + return (-1); + + if ((fp = fopen (ctx->realpath, "a")) == NULL) + { + mutt_perror (ctx->realpath); + FREE (&cmd); + return (-1); + } + mutt_block_signals (); + if (mbox_lock_compressed (ctx, fp, 1, 1) == -1) + { + fclose (fp); + mutt_unblock_signals (); + mutt_error _("Unable to lock mailbox!"); + FREE (&cmd); + return (-1); + } + + dprint (2, (debugfile, "CompressCmd: '%s'\n", cmd)); + + endwin (); + fflush (stdout); + + if (append == ci->close) + fprintf (stderr, _("Compressing %s...\n"), ctx->realpath); + else + fprintf (stderr, _("Compressed-appending to %s...\n"), ctx->realpath); + + if (mutt_system (cmd)) + { + mutt_any_key_to_continue (NULL); + mutt_error (_(" %s: Error compressing mailbox! Uncompressed one kept!\n"), + ctx->path); + FREE (&cmd); + mbox_unlock_compressed (ctx, fp); + mutt_unblock_signals (); + fclose (fp); + return (-1); + } + + mbox_unlock_compressed (ctx, fp); + mutt_unblock_signals (); + fclose (fp); + remove_file (ctx); + restore_path (ctx); + FREE (&cmd); + FREE (&ctx->compressinfo); + + return (0); +} + +#endif /* USE_COMPRESSED */ diff --git a/compress.h b/compress.h new file mode 100644 index 0000000..9dbf027 --- /dev/null +++ b/compress.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 1997 Alain Penders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +int mutt_can_read_compressed (const char *); +int mutt_can_append_compressed (const char *); +int mutt_open_read_compressed (CONTEXT *); +int mutt_open_append_compressed (CONTEXT *); +int mutt_slow_close_compressed (CONTEXT *); +int mutt_sync_compressed (CONTEXT *); +int mutt_test_compress_command (const char *); +int mutt_check_mailbox_compressed (CONTEXT *); +void mutt_fast_close_compressed (CONTEXT *); diff --git a/configure.ac b/configure.ac index 3d5f11b..e7ebe01 100644 --- a/configure.ac +++ b/configure.ac @@ -812,6 +812,11 @@ AC_ARG_ENABLE(locales-fix, AS_HELP_STRING([--enable-locales-fix],[The result of AC_DEFINE(LOCALES_HACK,1,[ Define if the result of isprint() is unreliable. ]) fi]) +AC_ARG_ENABLE(compressed, AC_HELP_STRING([--enable-compressed], [Enable compressed folders support]), + [if test x$enableval = xyes; then + AC_DEFINE(USE_COMPRESSED,1, [ Define to support compressed folders. ]) + fi]) + AC_ARG_WITH(exec-shell, AS_HELP_STRING([--with-exec-shell=SHELL],[Specify alternate shell (ONLY if /bin/sh is broken)]), [if test $withval != yes; then AC_DEFINE_UNQUOTED(EXECSHELL, "$withval", diff --git a/curs_main.c b/curs_main.c index d266708..e7f11bd 100644 --- a/curs_main.c +++ b/curs_main.c @@ -1154,6 +1154,11 @@ int mutt_index_menu (void) { int check; +#ifdef USE_COMPRESSED + if (Context->compressinfo && Context->realpath) + mutt_str_replace (&LastFolder, Context->realpath); + else +#endif mutt_str_replace (&LastFolder, Context->path); oldcount = Context ? Context->msgcount : 0; diff --git a/doc/manual.xml.head b/doc/manual.xml.head index 18ae918..4366758 100644 --- a/doc/manual.xml.head +++ b/doc/manual.xml.head @@ -6121,6 +6121,205 @@ selection. Highest priority has the mailbox given with the + +Compressed folders Support (OPTIONAL) + + +If Mutt was compiled with compressed folders support (by running the +configure script with the +--enable-compressed flag), Mutt can open folders +stored in an arbitrary format, provided that the user has a script to +convert from/to this format to one of the accepted. + +The most common use is to open compressed archived folders e.g. with +gzip. + +In addition, the user can provide a script that gets a folder in an +accepted format and appends its context to the folder in the +user-defined format, which may be faster than converting the entire +folder to the accepted format, appending to it and converting back to +the user-defined format. + +There are three hooks defined (open-hook, close-hook and append-hook) which define commands to +uncompress and compress a folder and to append messages to an existing +compressed folder respectively. + +For example: + + +open-hook \\.gz$ "gzip -cd %f > %t" +close-hook \\.gz$ "gzip -c %t > %f" +append-hook \\.gz$ "gzip -c %t >> %f" + + +You do not have to specify all of the commands. If you omit append-hook, the folder will be open and +closed again each time you will add to it. If you omit close-hook (or give empty command) , the +folder will be open in the mode. If you specify append-hook though you'll be able to +append to the folder. + +Note that Mutt will only try to use hooks if the file is not in one of +the accepted formats. In particular, if the file is empty, mutt +supposes it is not compressed. This is important because it allows the +use of programs that do not have well defined extensions. Just use +"." as a regexp. But this may be surprising if your +compressing script produces empty files. In this situation, unset +$save_empty, so that +the compressed file will be removed if you delete all of the messages. + + + +Open a compressed mailbox for reading + + +Usage: open-hook regexp "command" + +The command is the command that can be used for +opening the folders whose names match regexp. + +The command string is the printf-like format +string, and it should accept two parameters: %f, which is +replaced with the (compressed) folder name, and %t which is +replaced with the name of the temporary folder to which to write. + +%f and %t can be repeated any number of times in the +command string, and all of the entries are replaced with the +appropriate folder name. In addition, %% is replaced by +%, as in printf, and any other %anything is left as is. + +The command should not remove the original compressed file. The +command should return non-zero exit status if it +fails, so mutt knows something's wrong. + +Example: + + +open-hook \\.gz$ "gzip -cd %f > %t" + + +If the command is empty, this operation is +disabled for this file type. + + + + +Write a compressed mailbox + + +Usage: close-hook regexp "command" + +This is used to close the folder that was open with the open-hook command after some changes were +made to it. + +The command string is the command that can be +used for closing the folders whose names match +regexp. It has the same format as in the open-hook command. Temporary folder in this +case is the folder previously produced by the open-hook command. + +The command should not remove the decompressed file. The +command should return non-zero exit status if it +fails, so mutt knows something's wrong. + +Example: + + +close-hook \\.gz$ "gzip -c %t > %f" + + +If the command is empty, this operation is +disabled for this file type, and the file can only be open in the +read-only mode. + +close-hook is not called when you +exit from the folder if the folder was not changed. + + + + +Append a message to a compressed mailbox + + +Usage: append-hook regexp "command" + +This command is used for saving to an existing compressed folder. The +command is the command that can be used for +appending to the folders whose names match +regexp. It has the same format as in the open-hook command. The temporary folder in +this case contains the messages that are being appended. + +The command should not remove the decompressed file. The +command should return non-zero exit status if it +fails, so mutt knows something's wrong. + +Example: + + +append-hook \\.gz$ "gzip -c %t >> %f" + + +When append-hook is used, the folder +is not opened, which saves time, but this means that we can not find +out what the folder type is. Thus the default ($mbox_type) type is always +supposed (i.e. this is the format used for the temporary folder). + +If the file does not exist when you save to it, close-hook is called, and not append-hook. append-hook is only for appending to +existing folders. + +If the command is empty, this operation is +disabled for this file type. In this case, the folder will be open and +closed again (using open-hook and +close-hookrespectively) each time you +will add to it. + + + + +Encrypted folders + + +The compressed folders support can also be used to handle encrypted +folders. If you want to encrypt a folder with PGP, you may want to use +the following hooks: + + +open-hook \\.pgp$ "pgp -f < %f > %t" +close-hook \\.pgp$ "pgp -fe YourPgpUserIdOrKeyId < %t > %f" + + +Please note, that PGP does not support appending to an encrypted +folder, so there is no append-hook defined. + +If you are using GnuPG instead of PGP, you may use the following hooks +instead: + + +open-hook \\.gpg$ "gpg --decrypt < %f > %t" +close-hook \\.gpg$ "gpg --encrypt --recipient YourGpgUserIdOrKeyId < %t > %f" + + +Note: the folder is temporary stored +decrypted in the /tmp directory, where it can be read by your system +administrator. So think about the security aspects of this. + + + + Mutt's MIME Support diff --git a/doc/muttrc.man.head b/doc/muttrc.man.head index 30b96a2..b0ed18c 100644 --- a/doc/muttrc.man.head +++ b/doc/muttrc.man.head @@ -354,6 +354,24 @@ specify the ID of the public key to be used when encrypting messages to a certain recipient. The meaning of "key ID" is to be taken broadly: This can be a different e-mail address, a numerical key ID, or even just an arbitrary search string. +.PP +.nf +\fBopen-hook\fP \fIregexp\fP "\fIcommand\fP" +\fBclose-hook\fP \fIregexp\fP "\fIcommand\fP" +\fBappend-hook\fP \fIregexp\fP "\fIcommand\fP" +.fi +.IP +These commands provide a way to handle compressed folders. The given +\fBregexp\fP specifies which folders are taken as compressed (e.g. +"\fI\\\\.gz$\fP"). The commands tell Mutt how to uncompress a folder +(\fBopen-hook\fP), compress a folder (\fBclose-hook\fP) or append a +compressed mail to a compressed folder (\fBappend-hook\fP). The +\fIcommand\fP string is the +.BR printf (3) +like format string, and it should accept two parameters: \fB%f\fP, +which is replaced with the (compressed) folder name, and \fB%t\fP +which is replaced with the name of the temporary folder to which to +write. .TP \fBpush\fP \fIstring\fP This command adds the named \fIstring\fP to the keyboard buffer. diff --git a/hook.c b/hook.c index 34f3106..2a27419 100644 --- a/hook.c +++ b/hook.c @@ -24,6 +24,10 @@ #include "mailbox.h" #include "mutt_crypt.h" +#ifdef USE_COMPRESSED +#include "compress.h" +#endif + #include #include #include @@ -92,6 +96,16 @@ int mutt_parse_hook (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) memset (&pattern, 0, sizeof (pattern)); pattern.data = safe_strdup (path); } +#ifdef USE_COMPRESSED + else if (data & (M_APPENDHOOK | M_OPENHOOK | M_CLOSEHOOK)) + { + if (mutt_test_compress_command (command.data)) + { + strfcpy (err->data, _("badly formatted command string"), err->dsize); + return (-1); + } + } +#endif else if (DefaultHook && !(data & (M_CHARSETHOOK | M_ICONVHOOK | M_ACCOUNTHOOK)) && (!WithCrypto || !(data & M_CRYPTHOOK)) ) diff --git a/init.h b/init.h index a64992a..0e4f47f 100644 --- a/init.h +++ b/init.h @@ -3578,6 +3578,11 @@ const struct command_t Commands[] = { { "fcc-hook", mutt_parse_hook, M_FCCHOOK }, { "fcc-save-hook", mutt_parse_hook, M_FCCHOOK | M_SAVEHOOK }, { "folder-hook", mutt_parse_hook, M_FOLDERHOOK }, +#ifdef USE_COMPRESSED + { "open-hook", mutt_parse_hook, M_OPENHOOK }, + { "close-hook", mutt_parse_hook, M_CLOSEHOOK }, + { "append-hook", mutt_parse_hook, M_APPENDHOOK }, +#endif { "group", parse_group, M_GROUP }, { "ungroup", parse_group, M_UNGROUP }, { "hdr_order", parse_list, UL &HeaderOrderList }, diff --git a/main.c b/main.c index 0ce245b..5ab1868 100644 --- a/main.c +++ b/main.c @@ -431,6 +431,12 @@ static void show_version (void) #else "-LOCALES_HACK " #endif + +#ifdef USE_COMPRESSED + "+COMPRESSED " +#else + "-COMPRESSED " +#endif #ifdef HAVE_WC_FUNCS "+HAVE_WC_FUNCS " diff --git a/mbox.c b/mbox.c index 253061a..6d3b6bd 100644 --- a/mbox.c +++ b/mbox.c @@ -29,6 +29,10 @@ #include "copy.h" #include "mutt_curses.h" +#ifdef USE_COMPRESSED +#include "compress.h" +#endif + #include #include #include @@ -1069,6 +1073,12 @@ bail: /* Come here in case of disaster */ int mbox_close_mailbox (CONTEXT *ctx) { mx_unlock_file (ctx->path, fileno (ctx->fp), 1); + +#ifdef USE_COMPRESSED + if (ctx->compressinfo) + mutt_slow_close_compressed (ctx); +#endif + mutt_unblock_signals (); mx_fastclose_mailbox (ctx); return 0; diff --git a/mutt.h b/mutt.h index 8cee3d2..b71f071 100644 --- a/mutt.h +++ b/mutt.h @@ -144,6 +144,11 @@ typedef enum #define M_ACCOUNTHOOK (1<<9) #define M_REPLYHOOK (1<<10) #define M_SEND2HOOK (1<<11) +#ifdef USE_COMPRESSED +#define M_OPENHOOK (1<<12) +#define M_APPENDHOOK (1<<13) +#define M_CLOSEHOOK (1<<14) +#endif /* tree characters for linearize_tree and print_enriched_string */ #define M_TREE_LLCORNER 1 @@ -889,6 +894,11 @@ typedef struct _context int flagged; /* how many flagged messages */ int msgnotreadyet; /* which msg "new" in pager, -1 if none */ +#ifdef USE_COMPRESSED + void *compressinfo; /* compressed mbox module private data */ + char *realpath; /* path to compressed mailbox */ +#endif /* USE_COMPRESSED */ + short magic; /* mailbox type */ unsigned char rights[(RIGHTSMAX + 7)/8]; /* ACL bits */ diff --git a/mx.c b/mx.c index cc60517..07dba0c 100644 --- a/mx.c +++ b/mx.c @@ -30,6 +30,10 @@ #include "keymap.h" #include "url.h" +#ifdef USE_COMPRESSED +#include "compress.h" +#endif + #ifdef USE_IMAP #include "imap.h" #endif @@ -414,6 +418,10 @@ int mx_get_magic (const char *path) return (-1); } +#ifdef USE_COMPRESSED + if (magic == 0 && mutt_can_read_compressed (path)) + return M_COMPRESSED; +#endif return (magic); } @@ -453,6 +461,13 @@ static int mx_open_mailbox_append (CONTEXT *ctx, int flags) { struct stat sb; +#ifdef USE_COMPRESSED + /* special case for appending to compressed folders - + * even if we can not open them for reading */ + if (mutt_can_append_compressed (ctx->path)) + mutt_open_append_compressed (ctx); +#endif + ctx->append = 1; #ifdef USE_IMAP @@ -616,7 +631,12 @@ CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx) } ctx->magic = mx_get_magic (path); - + +#ifdef USE_COMPRESSED + if (ctx->magic == M_COMPRESSED) + mutt_open_read_compressed (ctx); +#endif + if(ctx->magic == 0) mutt_error (_("%s is not a mailbox."), path); @@ -721,6 +741,10 @@ void mx_fastclose_mailbox (CONTEXT *ctx) mutt_free_header (&ctx->hdrs[i]); FREE (&ctx->hdrs); FREE (&ctx->v2r); +#ifdef USE_COMPRESSED + if (ctx->compressinfo) + mutt_fast_close_compressed (ctx); +#endif FREE (&ctx->path); FREE (&ctx->pattern); if (ctx->limit_pattern) @@ -773,6 +797,12 @@ static int sync_mailbox (CONTEXT *ctx, int *index_hint) if (tmp && tmp->new == 0) mutt_update_mailbox (tmp); + +#ifdef USE_COMPRESSED + if (rc == 0 && ctx->compressinfo) + return mutt_sync_compressed (ctx); +#endif + return rc; } @@ -1043,6 +1073,11 @@ int mx_close_mailbox (CONTEXT *ctx, int *index_hint) !mutt_is_spool(ctx->path) && !option (OPTSAVEEMPTY)) mx_unlink_empty (ctx->path); +#ifdef USE_COMPRESSED + if (ctx->compressinfo && mutt_slow_close_compressed (ctx)) + return (-1); +#endif + mx_fastclose_mailbox (ctx); return 0; @@ -1361,6 +1396,11 @@ int mx_check_mailbox (CONTEXT *ctx, int *index_hint, int lock) { int rc; +#ifdef USE_COMPRESSED + if (ctx->compressinfo) + return mutt_check_mailbox_compressed (ctx); +#endif + if (ctx) { if (ctx->locked) lock = 0; diff --git a/mx.h b/mx.h index 4a00715..2ef4ec7 100644 --- a/mx.h +++ b/mx.h @@ -36,6 +36,9 @@ enum M_MAILDIR, M_IMAP, M_POP +#ifdef USE_COMPRESSED + , M_COMPRESSED +#endif }; WHERE short DefaultMagic INITVAL (M_MBOX); diff --git a/po/POTFILES.in b/po/POTFILES.in index 2d01add..3654ad1 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -8,6 +8,7 @@ charset.c color.c commands.c compose.c +compress.c crypt-gpgme.c crypt.c cryptglue.c diff --git a/po/de.po b/po/de.po index dd2dda5..75d56f3 100644 --- a/po/de.po +++ b/po/de.po @@ -5178,6 +5178,36 @@ msgstr "Extrahiere unterst msgid "show S/MIME options" msgstr "Zeige S/MIME Optionen" + +#: compress.c:228 compress.c:253 +#, c-format +msgid "Decompressing %s...\n" +msgstr "Entpacke %s...\n" + +#: compress.c:350 compress.c:377 compress.c:423 compress.c:454 +#, c-format +msgid "Compressing %s...\n" +msgstr "Komprimiere %s...\n" + +#: compress.c:381 +#, c-format +msgid "" +"%s: Error compressing mailbox! Original mailbox deleted, uncompressed one " +"kept!\n" +msgstr "" +"%s: Fehler beim Komprimieren der Mailbox! Ursprüngliche Mailbox gelöscht, " +"entpackte gespeichert!\n" + +#: compress.c:425 compress.c:456 +#, c-format +msgid "Compressed-appending to %s...\n" +msgstr "Hänge komprimiert an %s... an\n" + +#: compress.c:461 +#, c-format +msgid " %s: Error compressing mailbox! Uncompressed one kept!\n" +msgstr " %s: Fehler beim packen der Mailbox! Entpackte Mailbox gespeichert!\n" + #~ msgid "Clear" #~ msgstr "Klartext" diff --git a/status.c b/status.c index 6051e3a..e8693c8 100644 --- a/status.c +++ b/status.c @@ -96,6 +96,14 @@ status_format_str (char *buf, size_t buflen, size_t col, char op, const char *sr case 'f': snprintf (fmt, sizeof(fmt), "%%%ss", prefix); +#ifdef USE_COMPRESSED + if (Context && Context->compressinfo && Context->realpath) + { + strfcpy (tmp, Context->realpath, sizeof (tmp)); + mutt_pretty_mailbox (tmp, sizeof (tmp)); + } + else +#endif if (Context && Context->path) { strfcpy (tmp, Context->path, sizeof (tmp)); debian/patches/features/ifdef.patch =================================== Date: Thu, 27 Feb 2014 12:06:21 +0100 Subject: ifdef This command allows to test if a feature has been compiled in before actually attempting to configure / use it. Syntax: ifdef where can be the name of a variable, function, or command. Examples: ifdef imap-fetch-mail 'source ~/.mutt/imap_setup' ifdef trash set trash=~/Mail/trash * Patch last synced with upstream: - Date: 2007-02-15 - File: http://cedricduval.free.fr/mutt/patches/download/patch-1.5.4.cd.ifdef.1 * Changes made: - Updated to 1.5.13 - Also look for commands - Use mutt_strcmp in favor of ascii_strncasecmp to compare strings. Signed-off-by: Matteo F. Vescovi Gbp-Pq: Topic features --- doc/manual.xml.head | 22 ++++++++++++++++++++ init.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ init.h | 2 ++ 3 files changed, 83 insertions(+) diff --git a/doc/manual.xml.head b/doc/manual.xml.head index 0093685..18ae918 100644 --- a/doc/manual.xml.head +++ b/doc/manual.xml.head @@ -4378,6 +4378,28 @@ from which to read input (e.g. source + + +Configuring features conditionally + + +Usage: ifdef item command + + + +This command allows to test if a feature has been compiled in, before +actually executing the command. Item can be either the name of a +function, variable, or command. Example: + + + + +ifdef imap_keepalive 'source ~/.mutt/imap_setup' + + + + + Removing Hooks diff --git a/init.c b/init.c index 4897b9e..cc3cf4b 100644 --- a/init.c +++ b/init.c @@ -601,6 +601,65 @@ static void remove_from_list (LIST **l, const char *str) } } +static int parse_ifdef (BUFFER *tmp, BUFFER *s, unsigned long data, BUFFER *err) +{ + int i, j, res = 0; + BUFFER token; + + memset (&token, 0, sizeof (token)); + mutt_extract_token (tmp, s, 0); + + /* is the item defined as a variable? */ + res = (mutt_option_index (tmp->data) != -1); + + /* a function? */ + if (!res) + for (i = 0; !res && i < MENU_MAX; i++) + { + struct binding_t *b = km_get_table (Menus[i].value); + + if (!b) + continue; + + for (j = 0; b[j].name; j++) + if (!mutt_strcmp (tmp->data, b[j].name)) + { + res = 1; + break; + } + } + + /* a command? */ + if (!res) + for (i = 0; Commands[i].name; i++) + { + if (!mutt_strcmp (tmp->data, Commands[i].name)) + { + res = 1; + break; + } + } + + if (!MoreArgs (s)) + { + snprintf (err->data, err->dsize, _("ifdef: too few arguments")); + return (-1); + } + mutt_extract_token (tmp, s, M_TOKEN_SPACE); + + if (res) + { + if (mutt_parse_rc_line (tmp->data, &token, err) == -1) + { + mutt_error ("Erreur: %s", err->data); + FREE (&token.data); + return (-1); + } + FREE (&token.data); + } + return 0; +} + static int parse_unignore (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) { do diff --git a/init.h b/init.h index 395cd3f..80d05ce 100644 --- a/init.h +++ b/init.h @@ -3487,6 +3487,7 @@ static int parse_lists (BUFFER *, BUFFER *, unsigned long, BUFFER *); static int parse_unlists (BUFFER *, BUFFER *, unsigned long, BUFFER *); static int parse_alias (BUFFER *, BUFFER *, unsigned long, BUFFER *); static int parse_unalias (BUFFER *, BUFFER *, unsigned long, BUFFER *); +static int parse_ifdef (BUFFER *, BUFFER *, unsigned long, BUFFER *); static int parse_ignore (BUFFER *, BUFFER *, unsigned long, BUFFER *); static int parse_unignore (BUFFER *, BUFFER *, unsigned long, BUFFER *); static int parse_source (BUFFER *, BUFFER *, unsigned long, BUFFER *); @@ -3537,6 +3538,7 @@ const struct command_t Commands[] = { { "group", parse_group, M_GROUP }, { "ungroup", parse_group, M_UNGROUP }, { "hdr_order", parse_list, UL &HeaderOrderList }, + { "ifdef", parse_ifdef, 0 }, #ifdef HAVE_ICONV { "iconv-hook", mutt_parse_hook, M_ICONVHOOK }, #endif debian/patches/features/imap_fast_trash.patch ============================================= Date: Thu, 27 Feb 2014 14:29:03 +0100 Subject: imap_fast_trash Make "move to trash folder" use IMAP COPY. By Paul Miller Gbp-Pq: Topic features --- imap/imap.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ imap/imap.h | 3 +++ mx.c | 5 +++++ 3 files changed, 65 insertions(+) diff --git a/imap/imap.c b/imap/imap.c index 93dc06a..393d4ec 100644 --- a/imap/imap.c +++ b/imap/imap.c @@ -886,6 +886,12 @@ static int imap_make_msg_set (IMAP_DATA* idata, BUFFER* buf, int flag, if (hdrs[n]->deleted != HEADER_DATA(hdrs[n])->deleted) match = invert ^ hdrs[n]->deleted; break; + case M_EXPIRED: /* imap_fast_trash version of M_DELETED */ + if (hdrs[n]->purged) + break; + if (hdrs[n]->deleted != HEADER_DATA(hdrs[n])->deleted) + match = invert ^ (hdrs[n]->deleted && !hdrs[n]->appended); + break; case M_FLAG: if (hdrs[n]->flagged != HEADER_DATA(hdrs[n])->flagged) match = invert ^ hdrs[n]->flagged; @@ -2017,3 +2023,54 @@ int imap_complete(char* dest, size_t dlen, char* path) { return -1; } + +int imap_fast_trash() { + + if( Context->magic == M_IMAP && mx_is_imap(TrashPath) ) { + IMAP_MBOX mx; + IMAP_DATA *idata = (IMAP_DATA *) Context->data; + char mbox[LONG_STRING]; + char mmbox[LONG_STRING]; + int rc; + dprint(1, (debugfile, "[itf] trashcan seems to be on imap.\n")); + + if ( imap_parse_path(TrashPath, &mx) == 0 ) { + if( mutt_account_match(&(idata->conn->account), &(mx.account)) ) { + dprint(1, (debugfile, "[itf] trashcan seems to be on the same account.\n")); + + imap_fix_path (idata, mx.mbox, mbox, sizeof (mbox)); + if (!*mbox) + strfcpy (mbox, "INBOX", sizeof (mbox)); + imap_munge_mbox_name (mmbox, sizeof (mmbox), mbox); + + rc = imap_exec_msgset (idata, "UID COPY", mmbox, M_EXPIRED, 0, 0); + if (!rc) { + dprint (1, (debugfile, "imap_copy_messages: No messages del-tagged\n")); + rc = -1; + goto old_way; + + } else if (rc < 0) { + dprint (1, (debugfile, "could not queue copy\n")); + goto old_way; + + } else { + mutt_message (_("Copying %d messages to %s..."), rc, mbox); + return 0; + } + + } else { + dprint(1, (debugfile, "[itf] trashcan seems to be on a different account.\n")); + } + + old_way: + FREE (&mx.mbox); /* we probably only need to free this when the parse works */ + + } else { + dprint(1, (debugfile, "[itf] failed to parse TrashPath.\n" )); + } + + dprint(1, (debugfile, "[itf] giving up and trying old fasioned way.\n" )); + } + + return 1; +} diff --git a/imap/imap.h b/imap/imap.h index 74d7e13..99cd454 100644 --- a/imap/imap.h +++ b/imap/imap.h @@ -72,4 +72,7 @@ void imap_keepalive (void); int imap_account_match (const ACCOUNT* a1, const ACCOUNT* a2); +/* trash */ +int imap_fast_trash(); + #endif diff --git a/mx.c b/mx.c index 89b9431..cc60517 100644 --- a/mx.c +++ b/mx.c @@ -802,6 +802,11 @@ static int trash_append (CONTEXT *ctx) && stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev) return 0; /* we are in the trash folder: simple sync */ + #ifdef USE_IMAP + if( !imap_fast_trash() ) + return 0; + #endif + if ((ctx_trash = mx_open_mailbox (TrashPath, M_APPEND, NULL)) != NULL) { for (i = 0 ; i < ctx->msgcount ; i++) debian/patches/features/purge-message.patch =========================================== Date: Thu, 27 Feb 2014 14:21:59 +0100 Subject: purge-message (requires trash folder patch) This patch adds the purge-message function, which, unlike delete-message, will bypass the trash folder and really delete the mail. You can bind this function to D, for instance, by adding the following lines to your muttrc: bind index \eD purge-message bind pager \eD purge-message Please be very careful with this function, and try to use it as less as possible. The risk resides in getting into the habit of always using purge-message instead of delete-message, which would really defeat the purpose of having a trash folder feature. * Patch last synced with upstream: - Date: 2007-02-15 - File: http://cedricduval.free.fr/mutt/patches/download/patch-1.5.5.1.cd.purge_message.3.4 * Changes made: - Updated to 1.5.13 - Fixed indentation of "purged" in mutt.h. Signed-off-by: Matteo F. Vescovi Gbp-Pq: Topic features --- OPS | 1 + curs_main.c | 12 ++++++++++-- flags.c | 10 ++++++++++ functions.h | 2 ++ mutt.h | 2 ++ mx.c | 1 + pager.c | 8 +++++++- pattern.c | 4 +++- 8 files changed, 36 insertions(+), 4 deletions(-) diff --git a/OPS b/OPS index 8414a8b..02cea8e 100644 --- a/OPS +++ b/OPS @@ -142,6 +142,7 @@ OP_PREV_ENTRY "move to the previous entry" OP_PREV_LINE "scroll up one line" OP_PREV_PAGE "move to the previous page" OP_PRINT "print the current entry" +OP_PURGE_MESSAGE "really delete the current entry, bypassing the trash folder" OP_QUERY "query external program for addresses" OP_QUERY_APPEND "append new query results to current results" OP_QUIT "save changes to mailbox and quit" diff --git a/curs_main.c b/curs_main.c index 16ddbc9..d266708 100644 --- a/curs_main.c +++ b/curs_main.c @@ -1844,6 +1844,7 @@ int mutt_index_menu (void) MAYBE_REDRAW (menu->redraw); break; + case OP_PURGE_MESSAGE: case OP_DELETE: CHECK_MSGCOUNT; @@ -1854,6 +1855,7 @@ int mutt_index_menu (void) if (tag) { mutt_tag_set_flag (M_DELETE, 1); + mutt_tag_set_flag (M_PURGED, (op != OP_PURGE_MESSAGE) ? 0 : 1); if (option (OPTDELETEUNTAG)) mutt_tag_set_flag (M_TAG, 0); menu->redraw = REDRAW_INDEX; @@ -1861,6 +1863,8 @@ int mutt_index_menu (void) else { mutt_set_flag (Context, CURHDR, M_DELETE, 1); + mutt_set_flag (Context, CURHDR, M_PURGED, + (op != OP_PURGE_MESSAGE) ? 0 : 1); if (option (OPTDELETEUNTAG)) mutt_set_flag (Context, CURHDR, M_TAG, 0); if (option (OPTRESOLVE)) @@ -2162,11 +2166,13 @@ int mutt_index_menu (void) if (tag) { mutt_tag_set_flag (M_DELETE, 0); + mutt_tag_set_flag (M_PURGED, 0); menu->redraw = REDRAW_INDEX; } else { mutt_set_flag (Context, CURHDR, M_DELETE, 0); + mutt_set_flag (Context, CURHDR, M_PURGED, 0); if (option (OPTRESOLVE) && menu->current < Context->vcount - 1) { menu->current++; @@ -2187,9 +2193,11 @@ int mutt_index_menu (void) CHECK_ACL(M_ACL_DELETE, _("undelete message(s)")); rc = mutt_thread_set_flag (CURHDR, M_DELETE, 0, - op == OP_UNDELETE_THREAD ? 0 : 1); + op == OP_UNDELETE_THREAD ? 0 : 1) + + mutt_thread_set_flag (CURHDR, M_PURGED, 0, + op == OP_UNDELETE_THREAD ? 0 : 1); - if (rc != -1) + if (rc > -1) { if (option (OPTRESOLVE)) { diff --git a/flags.c b/flags.c index dfa6a50..5309bb7 100644 --- a/flags.c +++ b/flags.c @@ -104,6 +104,16 @@ void _mutt_set_flag (CONTEXT *ctx, HEADER *h, int flag, int bf, int upd_ctx) } break; + case M_PURGED: + if (bf) + { + if (!h->purged) + h->purged = 1; + } + else if (h->purged) + h->purged = 0; + break; + case M_NEW: if (!mutt_bit_isset(ctx->rights,M_ACL_SEEN)) diff --git a/functions.h b/functions.h index 7a1c5a9..a29388c 100644 --- a/functions.h +++ b/functions.h @@ -121,6 +121,7 @@ const struct binding_t OpMain[] = { /* map: index */ { "toggle-write", OP_TOGGLE_WRITE, "%" }, { "next-thread", OP_MAIN_NEXT_THREAD, "\016" }, { "next-subthread", OP_MAIN_NEXT_SUBTHREAD, "\033n" }, + { "purge-message", OP_PURGE_MESSAGE, NULL }, { "query", OP_QUERY, "Q" }, { "quit", OP_QUIT, "q" }, { "reply", OP_REPLY, "r" }, @@ -213,6 +214,7 @@ const struct binding_t OpPager[] = { /* map: pager */ { "print-message", OP_PRINT, "p" }, { "previous-thread", OP_MAIN_PREV_THREAD, "\020" }, { "previous-subthread",OP_MAIN_PREV_SUBTHREAD, "\033p" }, + { "purge-message", OP_PURGE_MESSAGE, NULL }, { "quit", OP_QUIT, "Q" }, { "exit", OP_EXIT, "q" }, { "reply", OP_REPLY, "r" }, diff --git a/mutt.h b/mutt.h index 0b879b9..8cee3d2 100644 --- a/mutt.h +++ b/mutt.h @@ -186,6 +186,7 @@ enum M_UNDELETE, M_DELETED, M_APPENDED, + M_PURGED, M_FLAG, M_TAG, M_UNTAG, @@ -711,6 +712,7 @@ typedef struct header unsigned int flagged : 1; /* marked important? */ unsigned int tagged : 1; unsigned int appended : 1; /* has been saved */ + unsigned int purged : 1; /* bypassing the trash folder */ unsigned int deleted : 1; unsigned int changed : 1; unsigned int attach_del : 1; /* has an attachment marked for deletion */ diff --git a/mx.c b/mx.c index f7fd01f..89b9431 100644 --- a/mx.c +++ b/mx.c @@ -806,6 +806,7 @@ static int trash_append (CONTEXT *ctx) { for (i = 0 ; i < ctx->msgcount ; i++) if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->appended + && !ctx->hdrs[i]->purged && mutt_append_message (ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1) { mx_close_mailbox (ctx_trash, NULL); diff --git a/pager.c b/pager.c index 23eb8ca..b17afb4 100644 --- a/pager.c +++ b/pager.c @@ -2350,12 +2350,15 @@ search_next: MAYBE_REDRAW (redraw); break; + case OP_PURGE_MESSAGE: case OP_DELETE: CHECK_MODE(IsHeader (extra)); CHECK_READONLY; CHECK_ACL(M_ACL_DELETE, _("delete message")); mutt_set_flag (Context, extra->hdr, M_DELETE, 1); + mutt_set_flag (Context, extra->hdr, M_PURGED, + ch != OP_PURGE_MESSAGE ? 0 : 1); if (option (OPTDELETEUNTAG)) mutt_set_flag (Context, extra->hdr, M_TAG, 0); redraw = REDRAW_STATUS | REDRAW_INDEX; @@ -2682,6 +2685,7 @@ search_next: CHECK_ACL(M_ACL_DELETE, _("undelete message")); mutt_set_flag (Context, extra->hdr, M_DELETE, 0); + mutt_set_flag (Context, extra->hdr, M_PURGED, 0); redraw = REDRAW_STATUS | REDRAW_INDEX; if (option (OPTRESOLVE)) { @@ -2697,9 +2701,11 @@ search_next: CHECK_ACL(M_ACL_DELETE, _("undelete message(s)")); r = mutt_thread_set_flag (extra->hdr, M_DELETE, 0, + ch == OP_UNDELETE_THREAD ? 0 : 1) + + mutt_thread_set_flag (extra->hdr, M_PURGED, 0, ch == OP_UNDELETE_THREAD ? 0 : 1); - if (r != -1) + if (r > -1) { if (option (OPTRESOLVE)) { diff --git a/pattern.c b/pattern.c index 7af1c38..4cdbd05 100644 --- a/pattern.c +++ b/pattern.c @@ -1358,8 +1358,10 @@ int mutt_pattern_func (int op, char *prompt) { switch (op) { - case M_DELETE: case M_UNDELETE: + mutt_set_flag (Context, Context->hdrs[Context->v2r[i]], M_PURGED, + 0); + case M_DELETE: mutt_set_flag (Context, Context->hdrs[Context->v2r[i]], M_DELETE, (op == M_DELETE)); break; debian/patches/features/sensible_browser_position.patch ======================================================= Date: Thu, 27 Feb 2014 14:42:36 +0100 Subject: sensible_browser_position This is the sensible browser position patch by Haakon Riiser. * Found in: <20050309162127.GA5656@s> http://lists.df7cb.de/mutt/message/20050309.162127.a244a894.en.html Gbp-Pq: Topic features --- browser.c | 24 +++++++++++++++++++++--- menu.c | 11 +++++++++++ mutt_menu.h | 1 + 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/browser.c b/browser.c index cda4900..dbb31c8 100644 --- a/browser.c +++ b/browser.c @@ -56,6 +56,7 @@ typedef struct folder_t int num; } FOLDER; +static char OldLastDir[_POSIX_PATH_MAX] = ""; static char LastDir[_POSIX_PATH_MAX] = ""; static char LastDirBackup[_POSIX_PATH_MAX] = ""; @@ -536,15 +537,33 @@ static void init_menu (struct browser_state *state, MUTTMENU *menu, char *title, menu->tagged = 0; if (buffy) + { + menu->is_mailbox_list = 1; snprintf (title, titlelen, _("Mailboxes [%d]"), mutt_buffy_check (0)); + } else { + menu->is_mailbox_list = 0; strfcpy (path, LastDir, sizeof (path)); mutt_pretty_mailbox (path, sizeof (path)); #ifdef USE_IMAP if (state->imap_browse && option (OPTIMAPLSUB)) - snprintf (title, titlelen, _("Subscribed [%s], File mask: %s"), - path, NONULL (Mask.pattern)); + { + char *p = strrchr (OldLastDir, '/'); + if (p && p - OldLastDir == mutt_strlen (LastDir) && + mutt_strncmp (LastDir, OldLastDir, p - OldLastDir) == 0) + { + /* If we get here, it means that LastDir is the parent directory of + * OldLastDir. I.e., we're returning from a subdirectory, and we want + * to position the cursor on the directory we're returning from. */ + int i; + for (i = 0; i < state->entrymax; i++) + if (mutt_strcmp (state->entry[i].name, p + 1) == 0) + menu->current = i; + } + snprintf (title, titlelen, _("Directory [%s], File mask: %s"), + path, NONULL(Mask.pattern)); + } else #endif snprintf (title, titlelen, _("Directory [%s], File mask: %s"), @@ -731,7 +750,6 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num #endif ) { - char OldLastDir[_POSIX_PATH_MAX]; /* save the old directory */ strfcpy (OldLastDir, LastDir, sizeof (OldLastDir)); diff --git a/menu.c b/menu.c index e03dd33..27b5f8e 100644 --- a/menu.c +++ b/menu.c @@ -849,8 +849,17 @@ int menu_redraw (MUTTMENU *menu) int mutt_menuLoop (MUTTMENU *menu) { + static int last_position = -1; int i = OP_NULL; + if ( menu->max && menu->is_mailbox_list ) { + if ( last_position > (menu->max-1) ) { + last_position = -1; + } else if (last_position >= 0) { + menu->current = last_position; + } + } + FOREVER { if (option (OPTMENUCALLER)) @@ -1078,6 +1087,8 @@ int mutt_menuLoop (MUTTMENU *menu) break; default: + if (menu->is_mailbox_list) + last_position = menu->current; return (i); } } diff --git a/mutt_menu.h b/mutt_menu.h index 82abecd..404b6dd 100644 --- a/mutt_menu.h +++ b/mutt_menu.h @@ -49,6 +49,7 @@ typedef struct menu_t int offset; /* which screen row to start the index */ int pagelen; /* number of entries per screen */ int tagprefix; + int is_mailbox_list; /* Setting dialog != NULL overrides normal menu behavior. * In dialog mode menubar is hidden and prompt keys are checked before debian/patches/features/trash-folder.patch ========================================== Date: Thu, 27 Feb 2014 12:27:41 +0100 Subject: trash-folder With this patch, if the trash variable is set to a path (unset by default), the deleted mails will be moved to a trash folder instead of being irremediably purged when syncing the mailbox. For instance, set trash="~/Mail/trash" will cause every deleted mail to go to this folder. Note that the append to the trash folder doesn't occur until the resync is done. This allows you to change your mind and undo deletes, and thus the moves to the trash folder are unnecessary. Notes * You might also want to have a look at the purge message feature below which is related to this patch. * IMAP is now supported. To retain the previous behavior, add this to your muttrc: folder-hook ^imap:// 'unset trash' FAQ Every once in a while, someone asks what are the advantages of this patch over a macro based solution. Here's an attempt to answer this question: * The folder history doesn't clutter up with unwanted trash entries. * Delayed move to the trash allows to change one's mind. * No need to treat the case of "normal folders" and trash folders separately with folder-hooks, and to create two sets of macros (one for the index, one for the pager). * Works not only with delete-message, but also with every deletion functions like delete-pattern, delete-thread or delete-subthread. To sum up, it's more integrated and transparent to the user. * Patch last synced with upstream: - Date: 2007-02-15 - File: http://cedricduval.free.fr/mutt/patches/download/patch-1.5.5.1.cd.trash_folder.3.4 * Changes made: - Updated to 1.5.13: - structure of _mutt_save_message changed (commands.c) - context of option (OPTCONFIRMAPPEND) changed (muttlib.c) - Fixed indentation of "appended" in mutt.h. Signed-off-by: Matteo F. Vescovi Gbp-Pq: Topic features --- commands.c | 1 + flags.c | 19 +++++++++++++++++- globals.h | 1 + imap/message.c | 2 ++ init.h | 10 ++++++++++ mutt.h | 3 +++ muttlib.c | 4 +++- mx.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ postpone.c | 3 +++ 9 files changed, 103 insertions(+), 2 deletions(-) diff --git a/commands.c b/commands.c index 6b23e39..300d95c 100644 --- a/commands.c +++ b/commands.c @@ -720,6 +720,7 @@ int _mutt_save_message (HEADER *h, CONTEXT *ctx, int delete, int decode, int dec if (option (OPTDELETEUNTAG)) mutt_set_flag (Context, h, M_TAG, 0); } + mutt_set_flag (Context, h, M_APPENDED, 1); return 0; } diff --git a/flags.c b/flags.c index f0f3d81..dfa6a50 100644 --- a/flags.c +++ b/flags.c @@ -65,7 +65,13 @@ void _mutt_set_flag (CONTEXT *ctx, HEADER *h, int flag, int bf, int upd_ctx) { h->deleted = 0; update = 1; - if (upd_ctx) ctx->deleted--; + if (upd_ctx) + { + ctx->deleted--; + if (h->appended) + ctx->appended--; + } + h->appended = 0; /* when undeleting, also reset the appended flag */ #ifdef USE_IMAP /* see my comment above */ if (ctx->magic == M_IMAP) @@ -87,6 +93,17 @@ void _mutt_set_flag (CONTEXT *ctx, HEADER *h, int flag, int bf, int upd_ctx) } break; + case M_APPENDED: + if (bf) + { + if (!h->appended) + { + h->appended = 1; + if (upd_ctx) ctx->appended++; + } + } + break; + case M_NEW: if (!mutt_bit_isset(ctx->rights,M_ACL_SEEN)) diff --git a/globals.h b/globals.h index 584cd0c..a7e2304 100644 --- a/globals.h +++ b/globals.h @@ -139,6 +139,7 @@ WHERE char *StChars; WHERE char *Status; WHERE char *Tempdir; WHERE char *Tochars; +WHERE char *TrashPath; WHERE char *Username; WHERE char *Visual; WHERE char *XtermTitle; diff --git a/imap/message.c b/imap/message.c index 38da127..3c9822f 100644 --- a/imap/message.c +++ b/imap/message.c @@ -876,6 +876,7 @@ int imap_copy_messages (CONTEXT* ctx, HEADER* h, char* dest, int delete) if (ctx->hdrs[n]->tagged) { mutt_set_flag (ctx, ctx->hdrs[n], M_DELETE, 1); + mutt_set_flag (ctx, ctx->hdrs[n], M_APPENDED, 1); if (option (OPTDELETEUNTAG)) mutt_set_flag (ctx, ctx->hdrs[n], M_TAG, 0); } @@ -883,6 +884,7 @@ int imap_copy_messages (CONTEXT* ctx, HEADER* h, char* dest, int delete) else { mutt_set_flag (ctx, h, M_DELETE, 1); + mutt_set_flag (ctx, h, M_APPENDED, 1); if (option (OPTDELETEUNTAG)) mutt_set_flag (ctx, h, M_TAG, 0); } diff --git a/init.h b/init.h index cf8e75a..d4a882b 100644 --- a/init.h +++ b/init.h @@ -3241,6 +3241,16 @@ struct option_t MuttVars[] = { ** by \fIyou\fP. The sixth character is used to indicate when a mail ** was sent to a mailing-list you subscribe to. */ + { "trash", DT_PATH, R_NONE, UL &TrashPath, 0 }, + /* + ** .pp + ** If set, this variable specifies the path of the trash folder where the + ** mails marked for deletion will be moved, instead of being irremediably + ** purged. + ** .pp + ** NOTE: When you delete a message in the trash folder, it is really + ** deleted, so that you have a way to clean the trash. + */ #ifdef USE_SOCKET { "tunnel", DT_STR, R_NONE, UL &Tunnel, UL 0 }, /* diff --git a/mutt.h b/mutt.h index 1d7e177..0b879b9 100644 --- a/mutt.h +++ b/mutt.h @@ -185,6 +185,7 @@ enum M_DELETE, M_UNDELETE, M_DELETED, + M_APPENDED, M_FLAG, M_TAG, M_UNTAG, @@ -709,6 +710,7 @@ typedef struct header unsigned int mime : 1; /* has a MIME-Version header? */ unsigned int flagged : 1; /* marked important? */ unsigned int tagged : 1; + unsigned int appended : 1; /* has been saved */ unsigned int deleted : 1; unsigned int changed : 1; unsigned int attach_del : 1; /* has an attachment marked for deletion */ @@ -881,6 +883,7 @@ typedef struct _context int new; /* how many new messages? */ int unread; /* how many unread messages? */ int deleted; /* how many deleted messages */ + int appended; /* how many saved messages? */ int flagged; /* how many flagged messages */ int msgnotreadyet; /* which msg "new" in pager, -1 if none */ diff --git a/muttlib.c b/muttlib.c index 8005e9c..0425d45 100644 --- a/muttlib.c +++ b/muttlib.c @@ -1510,7 +1510,9 @@ int mutt_save_confirm (const char *s, struct stat *st) if (magic > 0 && !mx_access (s, W_OK)) { - if (option (OPTCONFIRMAPPEND)) + if (option (OPTCONFIRMAPPEND) && + (!TrashPath || (mutt_strcmp (s, TrashPath) != 0))) + /* if we're appending to the trash, there's no point in asking */ { snprintf (tmp, sizeof (tmp), _("Append messages to %s?"), s); if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO) diff --git a/mx.c b/mx.c index f599f6c..f7fd01f 100644 --- a/mx.c +++ b/mx.c @@ -776,6 +776,53 @@ static int sync_mailbox (CONTEXT *ctx, int *index_hint) return rc; } +/* move deleted mails to the trash folder */ +static int trash_append (CONTEXT *ctx) +{ + CONTEXT *ctx_trash; + int i = 0; + struct stat st, stc; + + if (!TrashPath || !ctx->deleted || + (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) + return 0; + + for (;i < ctx->msgcount && (!ctx->hdrs[i]->deleted || + ctx->hdrs[i]->appended); i++); + if (i == ctx->msgcount) + return 0; /* nothing to be done */ + + if (mutt_save_confirm (TrashPath, &st) != 0) + { + mutt_error _("message(s) not deleted"); + return -1; + } + + if (lstat (ctx->path, &stc) == 0 && stc.st_ino == st.st_ino + && stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev) + return 0; /* we are in the trash folder: simple sync */ + + if ((ctx_trash = mx_open_mailbox (TrashPath, M_APPEND, NULL)) != NULL) + { + for (i = 0 ; i < ctx->msgcount ; i++) + if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->appended + && mutt_append_message (ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1) + { + mx_close_mailbox (ctx_trash, NULL); + return -1; + } + + mx_close_mailbox (ctx_trash, NULL); + } + else + { + mutt_error _("Can't open trash folder"); + return -1; + } + + return 0; +} + /* save changes and close mailbox */ int mx_close_mailbox (CONTEXT *ctx, int *index_hint) { @@ -912,6 +959,7 @@ int mx_close_mailbox (CONTEXT *ctx, int *index_hint) if (mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN) == 0) { mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, 1); + mutt_set_flag (ctx, ctx->hdrs[i], M_APPENDED, 1); } else { @@ -936,6 +984,14 @@ int mx_close_mailbox (CONTEXT *ctx, int *index_hint) return 0; } + /* copy mails to the trash before expunging */ + if (purge && ctx->deleted && mutt_strcmp(ctx->path, TrashPath)) + if (trash_append (ctx) != 0) + { + ctx->closing = 0; + return -1; + } + #ifdef USE_IMAP /* allow IMAP to preserve the deleted flag across sessions */ if (ctx->magic == M_IMAP) @@ -1133,6 +1189,12 @@ int mx_sync_mailbox (CONTEXT *ctx, int *index_hint) msgcount = ctx->msgcount; deleted = ctx->deleted; + if (purge && ctx->deleted && mutt_strcmp(ctx->path, TrashPath)) + { + if (trash_append (ctx) == -1) + return -1; + } + #ifdef USE_IMAP if (ctx->magic == M_IMAP) rc = imap_sync_mailbox (ctx, purge, index_hint); diff --git a/postpone.c b/postpone.c index 801ef10..21e96e6 100644 --- a/postpone.c +++ b/postpone.c @@ -277,6 +277,9 @@ int mutt_get_postponed (CONTEXT *ctx, HEADER *hdr, HEADER **cur, char *fcc, size /* finished with this message, so delete it. */ mutt_set_flag (PostContext, h, M_DELETE, 1); + /* and consider it saved, so that it won't be moved to the trash folder */ + mutt_set_flag (PostContext, h, M_APPENDED, 1); + /* update the count for the status display */ PostCount = PostContext->msgcount - PostContext->deleted; debian/patches/features/xtitles.patch ===================================== Date: Thu, 27 Feb 2014 12:25:51 +0100 Subject: xtitles This is the xterm title patch as found on the mutt mailing lists. * Changes made: - 2007-01-27 myon: using %P caused a segfault, updated status.c to catch menu==NULL. - 2007-02-20 myon: make the note about the xterm_set_titles defaults a comment. - 2008-08-02 myon: move set_xterm_* prototypes into the proper header file (cleaner code, no functional change, evades conflict with sidebar patch) Signed-off-by: Matteo F. Vescovi Gbp-Pq: Topic features --- curs_main.c | 20 ++++++++++++++++++++ globals.h | 2 ++ init.c | 20 ++++++++++++++++++++ init.h | 21 +++++++++++++++++++++ mutt.h | 1 + mutt_menu.h | 2 ++ pager.c | 7 +++++++ status.c | 2 ++ 8 files changed, 75 insertions(+) diff --git a/curs_main.c b/curs_main.c index aa4b044..16ddbc9 100644 --- a/curs_main.c +++ b/curs_main.c @@ -110,6 +110,19 @@ static const char *No_visible = N_("No visible messages."); extern size_t UngetCount; +#define ASCII_CTRL_G 0x07 +#define ASCII_CTRL_OPEN_SQUARE_BRAKET 0x1b + +void set_xterm_title_bar(char *title) +{ + fprintf(stderr ,"%c]2;%s%c", ASCII_CTRL_OPEN_SQUARE_BRAKET, title, ASCII_CTRL_G); +} + +void set_xterm_icon_name(char *name) +{ + fprintf(stderr, "%c]1;%s%c", ASCII_CTRL_OPEN_SQUARE_BRAKET, name, ASCII_CTRL_G); +} + void index_make_entry (char *s, size_t l, MUTTMENU *menu, int num) { format_flag flag = M_FORMAT_MAKEPRINT | M_FORMAT_ARROWCURSOR | M_FORMAT_INDEX; @@ -560,6 +573,13 @@ int mutt_index_menu (void) mutt_paddstr (COLS, buf); NORMAL_COLOR; menu->redraw &= ~REDRAW_STATUS; + if (option(OPTXTERMSETTITLES)) + { + menu_status_line (buf, sizeof (buf), menu, NONULL (XtermTitle)); + set_xterm_title_bar(buf); + menu_status_line (buf, sizeof (buf), menu, NONULL (XtermIcon)); + set_xterm_icon_name(buf); + } } menu->redraw = 0; diff --git a/globals.h b/globals.h index 6fefe5b..584cd0c 100644 --- a/globals.h +++ b/globals.h @@ -141,6 +141,8 @@ WHERE char *Tempdir; WHERE char *Tochars; WHERE char *Username; WHERE char *Visual; +WHERE char *XtermTitle; +WHERE char *XtermIcon; WHERE char *CurrentFolder; WHERE char *LastFolder; diff --git a/init.c b/init.c index cc3cf4b..81bb9e7 100644 --- a/init.c +++ b/init.c @@ -1877,6 +1877,26 @@ static int parse_set (BUFFER *tmp, BUFFER *s, unsigned long data, BUFFER *err) toggle_option (MuttVars[idx].data); else set_option (MuttVars[idx].data); + + /* sanity check for xterm */ + if ((mutt_strcmp (MuttVars[idx].option, "xterm_set_titles") == 0) + && option (OPTXTERMSETTITLES)) + { + char *ep = getenv ("TERM"); + /* Make sure that the terminal can take the control codes */ + if (ep == NULL) unset_option (MuttVars[idx].data); + else if (mutt_strncasecmp (ep, "xterm", 5) && + mutt_strncasecmp (ep, "color-xterm", 11) && + mutt_strncasecmp (ep, "eterm", 5) && + mutt_strncasecmp (ep, "kterm", 5) && + mutt_strncasecmp (ep, "nxterm", 6) && + mutt_strncasecmp (ep, "putty", 5) && + mutt_strncasecmp (ep, "screen", 6) && + mutt_strncasecmp (ep, "cygwin", 6) && + mutt_strncasecmp (ep, "rxvt", 4) ) + unset_option (MuttVars[idx]. data); + + } } else if (myvar || DTYPE (MuttVars[idx].type) == DT_STR || DTYPE (MuttVars[idx].type) == DT_PATH || diff --git a/init.h b/init.h index 80d05ce..cf8e75a 100644 --- a/init.h +++ b/init.h @@ -3412,6 +3412,27 @@ struct option_t MuttVars[] = { ** Also see the $$read_inc, $$net_inc and $$time_inc variables and the ** ``$tuning'' section of the manual for performance considerations. */ + {"xterm_icon", DT_STR, R_BOTH, UL &XtermIcon, UL "M%?n?AIL&ail?"}, + /* + ** .pp + ** Controls the format of the icon title, as long as xterm_set_titles + ** is enabled. This string is identical in formatting to the one used by + ** ``$$status_format''. + */ + {"xterm_set_titles", DT_BOOL, R_BOTH, OPTXTERMSETTITLES, 0}, + /* The default must be off to force in the validity checking. */ + /* + ** .pp + ** Controls whether mutt sets the xterm title bar and icon name + ** (as long as you are in an appropriate terminal). + */ + {"xterm_title", DT_STR, R_BOTH, UL &XtermTitle, UL "Mutt with %?m?%m messages&no messages?%?n? [%n NEW]?"}, + /* + ** .pp + ** Controls the format of the title bar of the xterm provided that + ** xterm_set_titles has been set. This string is identical in formatting + ** to the one used by ``$$status_format''. + */ /*--*/ { NULL, 0, 0, 0, 0 } }; diff --git a/mutt.h b/mutt.h index dcd7dc5..1d7e177 100644 --- a/mutt.h +++ b/mutt.h @@ -449,6 +449,7 @@ enum OPTWRAPSEARCH, OPTWRITEBCC, /* write out a bcc header? */ OPTXMAILER, + OPTXTERMSETTITLES, OPTCRYPTUSEGPGME, OPTCRYPTUSEPKA, diff --git a/mutt_menu.h b/mutt_menu.h index d459bef..82abecd 100644 --- a/mutt_menu.h +++ b/mutt_menu.h @@ -103,6 +103,8 @@ void menu_current_middle (MUTTMENU *); void menu_current_bottom (MUTTMENU *); void menu_check_recenter (MUTTMENU *); void menu_status_line (char *, size_t, MUTTMENU *, const char *); +void set_xterm_title_bar (char *title); +void set_xterm_icon_name (char *name); MUTTMENU *mutt_new_menu (int); void mutt_menuDestroy (MUTTMENU **); diff --git a/pager.c b/pager.c index 486d8c8..23eb8ca 100644 --- a/pager.c +++ b/pager.c @@ -1812,6 +1812,13 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra) mutt_paddstr (COLS, bn); } NORMAL_COLOR; + if (option(OPTXTERMSETTITLES)) + { + menu_status_line (buffer, sizeof (buffer), index, NONULL (XtermTitle)); + set_xterm_title_bar(buffer); + menu_status_line (buffer, sizeof (buffer), index, NONULL (XtermIcon)); + set_xterm_icon_name(buffer); + } } if ((redraw & REDRAW_INDEX) && index) diff --git a/status.c b/status.c index 1bb9a5a..6051e3a 100644 --- a/status.c +++ b/status.c @@ -195,6 +195,8 @@ status_format_str (char *buf, size_t buflen, size_t col, char op, const char *sr break; case 'P': + if (!menu) + break; if (menu->top + menu->pagelen >= menu->max) cp = menu->top ? "end" : "all"; else debian/patches/mutt-patched/904051-CVE-2018-14360.patch ======================================================= From: JerikoOne Date: Tue, 3 Jul 2018 17:08:41 -0500 Subject: [PATCH] Set length modifiers for group and desc nntp_add_group parses a line controlled by the connected nntp server. Restrict the maximum lengths read into the stack buffers group, and desc. --- newsrc.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) --- mutt.git.orig/newsrc.c +++ mutt.git/newsrc.c @@ -495,7 +495,7 @@ { NNTP_SERVER *nserv = data; NNTP_DATA *nntp_data; - char group[LONG_STRING]; + char group[LONG_STRING] = ""; char desc[HUGE_STRING] = ""; char mod; anum_t first, last; @@ -503,9 +503,12 @@ if (!nserv || !line) return 0; - if (sscanf (line, "%s " ANUM " " ANUM " %c %[^\n]", group, - &last, &first, &mod, desc) < 4) + /* These sscanf limits must match the sizes of the group and desc arrays */ + if (sscanf(line, "%1023s " ANUM " " ANUM " %c %8191[^\n]", group, &last, &first, &mod, desc) < 4) + { + mutt_debug(4, "Cannot parse server line: %s\n", line); return 0; + } nntp_data = nntp_data_find (nserv, group); nntp_data->deleted = 0; debian/patches/mutt-patched/904051-CVE-2018-14361.patch ======================================================= From: JerikoOne Date: Tue, 3 Jul 2018 17:22:12 -0500 Subject: [PATCH] Add alloc fail check in nntp_fetch_headers --- nntp.c | 2 ++ 1 file changed, 2 insertions(+) --- mutt.git.orig/nntp.c +++ mutt.git/nntp.c @@ -1201,6 +1201,8 @@ fc.last = last; fc.restore = restore; fc.messages = safe_calloc (last - first + 1, sizeof (unsigned char)); + if (fc.messages == NULL) + return -1; #ifdef USE_HCACHE fc.hc = hc; #endif debian/patches/mutt-patched/904051-CVE-2018-14363.patch ======================================================= From: Richard Russon Date: Thu, 5 Jul 2018 13:32:17 +0100 Subject: [PATCH] sanitise cache paths Co-authored-by: JerikoOne --- newsrc.c | 13 ++++++++++++- pop.c | 29 +++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 7 deletions(-) --- mutt.git.orig/newsrc.c +++ mutt.git/newsrc.c @@ -605,7 +605,18 @@ /* Used by mutt_hcache_open() to compose hcache file name */ static int nntp_hcache_namer (const char *path, char *dest, size_t destlen) { - return snprintf (dest, destlen, "%s.hcache", path); + int count = snprintf(dest, destlen, "%s.hcache", path); + + /* Strip out any directories in the path */ + char *first = strchr(dest, '/'); + char *last = strrchr(dest, '/'); + if (first && last && (last > first)) + { + memmove(first, last, strlen(last) + 1); + count -= (last - first); + } + + return count; } /* Open newsgroup hcache */ debian/patches/mutt-patched/multiple-fcc.patch ============================================== Date: Tue, 4 Mar 2014 15:40:45 +0100 Subject: multiple-fcc A patch that allows multiple FCC separated by commas, written by Omen Wild. Original website: http://www.mandarb.com/mutt/ Bug asking for the inclusion: #586454 Gbp-Pq: Topic mutt-patched --- protos.h | 1 + send.c | 2 +- sendlib.c | 30 ++++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/protos.h b/protos.h index 38c8c01..551b142 100644 --- a/protos.h +++ b/protos.h @@ -362,6 +362,7 @@ int mutt_user_is_recipient (HEADER *); void mutt_update_num_postponed (void); int mutt_wait_filter (pid_t); int mutt_which_case (const char *); +int mutt_write_multiple_fcc (const char *path, HEADER *hdr, const char *msgid, int, char *); int mutt_write_fcc (const char *path, HEADER *hdr, const char *msgid, int, char *); int mutt_write_mime_body (BODY *, FILE *); int mutt_write_mime_header (BODY *, FILE *); diff --git a/send.c b/send.c index 18b9390..893c859 100644 --- a/send.c +++ b/send.c @@ -1747,7 +1747,7 @@ full_fcc: * message was first postponed. */ msg->received = time (NULL); - if (mutt_write_fcc (fcc, msg, NULL, 0, NULL) == -1) + if (mutt_write_multiple_fcc (fcc, msg, NULL, 0, NULL) == -1) { /* * Error writing FCC, we should abort sending. diff --git a/sendlib.c b/sendlib.c index 0f05298..9d83401 100644 --- a/sendlib.c +++ b/sendlib.c @@ -2680,6 +2680,36 @@ static void set_noconv_flags (BODY *b, short flag) } } +/* Handle a Fcc with multiple, comma separated entries. */ +int mutt_write_multiple_fcc (const char *path, HEADER *hdr, const char *msgid, int post, char *fcc) { + char fcc_tok[_POSIX_PATH_MAX]; + char fcc_expanded[_POSIX_PATH_MAX]; + char *tok = NULL; + int status; + + strfcpy(fcc_tok, path, _POSIX_PATH_MAX); + + tok = strtok(fcc_tok, ","); + dprint(1, (debugfile, "Fcc: initial mailbox = '%s'\n", tok)); + /* mutt_expand_path already called above for the first token */ + if((status = mutt_write_fcc (tok, hdr, NULL, 0, NULL)) != 0) + return status; + + while((tok = strtok(NULL, ",")) != NULL) { + if(*tok) { + /* Only call mutt_expand_path iff tok has some data */ + dprint(1, (debugfile, "Fcc: additional mailbox token = '%s'\n", tok)); + strfcpy(fcc_expanded, tok, sizeof(fcc_expanded)); + mutt_expand_path(fcc_expanded, sizeof(fcc_expanded)); + dprint(1, (debugfile, " Additional mailbox expanded = '%s'\n", fcc_expanded)); + if((status = mutt_write_fcc (fcc_expanded, hdr, NULL, 0, NULL)) != 0) + return status; + } + } + + return 0; +} + int mutt_write_fcc (const char *path, HEADER *hdr, const char *msgid, int post, char *fcc) { CONTEXT f; debian/patches/mutt-patched/nntp.patch ====================================== Date: Sun, 16 Mar 2014 15:54:12 +0100 Subject: nntp http://mutt.org.ua/download/mutt-1.5.23/patch-1.5.23.vvv.nntp.gz Signed-off-by: Matteo F. Vescovi Gbp-Pq: Topic mutt-patched --- ChangeLog.nntp | 399 +++++++++ Makefile.am | 2 + OPS | 23 +- account.c | 27 + account.h | 3 +- attach.h | 2 +- browser.c | 470 +++++++++- browser.h | 7 + buffy.c | 12 + complete.c | 64 ++ compose.c | 177 +++- configure.ac | 11 +- curs_main.c | 312 ++++++- doc/Muttrc | 300 +++++++ doc/manual.xml.head | 20 + doc/mutt.man | 8 +- functions.h | 59 +- globals.h | 16 + hash.c | 27 + hash.h | 3 +- hcache.c | 12 + hdrline.c | 25 + headers.c | 3 + init.c | 22 + init.h | 217 +++++ keymap.c | 1 - mailbox.h | 3 + main.c | 54 ++ mutt.h | 39 +- mutt_sasl.c | 5 + muttlib.c | 17 +- mx.c | 75 ++ mx.h | 3 + newsrc.c | 1260 +++++++++++++++++++++++++++ nntp.c | 2404 +++++++++++++++++++++++++++++++++++++++++++++++++++ nntp.h | 167 ++++ pager.c | 81 +- parse.c | 48 +- pattern.c | 8 + po/POTFILES.in | 2 + postpone.c | 11 + protos.h | 1 + recvattach.c | 34 +- recvcmd.c | 61 +- send.c | 163 +++- sendlib.c | 78 +- sort.c | 16 + url.c | 4 +- url.h | 2 + 49 files changed, 6674 insertions(+), 84 deletions(-) create mode 100644 ChangeLog.nntp create mode 100644 newsrc.c create mode 100644 nntp.c create mode 100644 nntp.h diff --git a/ChangeLog.nntp b/ChangeLog.nntp new file mode 100644 index 0000000..70d1126 --- /dev/null +++ b/ChangeLog.nntp @@ -0,0 +1,399 @@ +* Thu Mar 13 2014 Vsevolod Volkov +- update to 1.5.23 + +* Tue Oct 29 2013 Vsevolod Volkov +- minor bug fixed while removing new articles + +* Fri Oct 18 2013 Vsevolod Volkov +- update to 1.5.22 + +* Tue Nov 27 2012 Vsevolod Volkov +- SASL authentication +- new option nntp_authenticators + +* Fri Nov 16 2012 Vsevolod Volkov +- support of NNTP commands: CAPABILITIES, STARTTLS, LIST NEWSGROUPS, + LIST OVERVIEW.FMT, OVER, DATE +- added bcache support +- newss URI scheme renamed to snews +- removed option nntp_reconnect + +* Sun Sep 16 2012 Vsevolod Volkov +- internal header caching replaced with hcache +- new option newsgroups_charset + +* Wed Sep 16 2010 Vsevolod Volkov +- update to 1.5.21 + +* Thu Aug 13 2009 Vsevolod Volkov +- fixed writting references in nntp_save_cache_group() + +* Tue Jun 15 2009 Vsevolod Volkov +- update to 1.5.20 + +* Tue Mar 20 2009 Vsevolod Volkov +- save Date: header of recorded outgoing articles + +* Tue Jan 6 2009 Vsevolod Volkov +- update to 1.5.19 + +* Mon May 19 2008 Vsevolod Volkov +- update to 1.5.18 +- fixed SIGSEGV when followup or forward to newsgroup + +* Sun Nov 4 2007 Vsevolod Volkov +- update to 1.5.17 + +* Tue Jul 3 2007 Vsevolod Volkov +- fixed arguments of nntp_format_str() + +* Fri Jun 15 2007 Vsevolod Volkov +- fixed error selecting news group + +* Tue Jun 12 2007 Vsevolod Volkov +- update to 1.5.16 + +* Wed Apr 11 2007 Vsevolod Volkov +- fixed posting error if $smtp_url is set +- added support of print-style sequence %R (x-comment-to) + +* Sun Apr 8 2007 Vsevolod Volkov +- update to 1.5.15 +- nntp://... url changed to news://... +- added indicator of fetching descriptions progress + +* Tue Feb 28 2007 Vsevolod Volkov +- update to 1.5.14 + +* Tue Aug 15 2006 Vsevolod Volkov +- update to 1.5.13 + +* Mon Jul 17 2006 Vsevolod Volkov +- update to 1.5.12 +- fixed reading empty .newsrc + +* Sat Sep 17 2005 Vsevolod Volkov +- update to 1.5.11 + +* Sat Aug 13 2005 Vsevolod Volkov +- update to 1.5.10 + +* Sun Mar 13 2005 Vsevolod Volkov +- update to 1.5.9 + +* Sun Feb 13 2005 Vsevolod Volkov +- update to 1.5.8 + +* Sat Feb 5 2005 Vsevolod Volkov +- update to 1.5.7 +- function mutt_update_list_file() moved to newsrc.c and changed algorithm + +* Thu Jul 8 2004 Vsevolod Volkov +- fixed error in nntp_logout_all() + +* Sat Apr 3 2004 Vsevolod Volkov +- fixed debug output in mutt_newsrc_update() +- added optional support of LISTGROUP command +- fixed typo in nntp_parse_xref() + +* Tue Feb 3 2004 Vsevolod Volkov +- update to 1.5.6 + +* Thu Dec 18 2003 Vsevolod Volkov +- fixed compose menu + +* Thu Nov 6 2003 Vsevolod Volkov +- update to 1.5.5.1 + +* Wed Nov 5 2003 Vsevolod Volkov +- update to 1.5.5 +- added space after newsgroup name in .newsrc file + +* Sun May 18 2003 Vsevolod Volkov +- nntp patch: fixed SIGSEGV when posting article + +* Sat Mar 22 2003 Vsevolod Volkov +- update to 1.5.4 + +* Sat Dec 21 2002 Vsevolod Volkov +- update to 1.5.3 +- replace safe_free calls by the FREE macro + +* Fri Dec 6 2002 Vsevolod Volkov +- update to 1.5.2 +- nntp authentication can be passed after any command + +* Sat May 4 2002 Vsevolod Volkov +- update to 1.5.1 + +* Thu May 2 2002 Vsevolod Volkov +- update to 1.3.99 + +* Wed Mar 13 2002 Vsevolod Volkov +- update to 1.3.28 +- fixed SIGSEGV in , , , + functions +- fixed message about nntp reconnect +- fixed function using browser +- added support of Followup-To: poster +- added %n (new articles) in group_index_format +- posting articles without inews by default + +* Wed Jan 23 2002 Vsevolod Volkov +- update to 1.3.27 + +* Fri Jan 18 2002 Vsevolod Volkov +- update to 1.3.26 + +* Thu Jan 3 2002 Vsevolod Volkov +- update to 1.3.25 +- accelerated speed of access to news->newsgroups hash (by ) +- added default content disposition + +* Mon Dec 3 2001 Vsevolod Volkov +- update to 1.3.24 + +* Fri Nov 9 2001 Vsevolod Volkov +- update to 1.3.23.2 +- fixed segfault if mutt_conn_find() returns null + +* Wed Oct 31 2001 Vsevolod Volkov +- update to 1.3.23.1 +- added support of LISTGROUP command +- added support for servers with broken overview +- disabled function on news server +- fixed error storing bad authentication information + +* Wed Oct 10 2001 Vsevolod Volkov +- update to 1.3.23 +- fixed typo in buffy.c +- added substitution of %s parameter in $inews variable + +* Fri Aug 31 2001 Vsevolod Volkov +- update to 1.3.22.1 +- update to 1.3.22 + +* Thu Aug 23 2001 Vsevolod Volkov +- update to 1.3.21 + +* Wed Jul 25 2001 Vsevolod Volkov +- update to 1.3.20 +- removed 'server-hook', use 'account-hook' instead +- fixed error opening NNTP server without newsgroup using -f option + +* Fri Jun 8 2001 Vsevolod Volkov +- update to 1.3.19 + +* Sat May 5 2001 Vsevolod Volkov +- update to 1.3.18 +- fixed typo in nntp_attempt_features() +- changed algorithm of XGTITLE command testing +- disabled writing of NNTP password in debug file +- fixed reading and writing of long newsrc lines +- changed checking of last line while reading lines from server +- fixed possible buffer overrun in nntp_parse_newsrc_line() +- removed checking of XHDR command +- compare NNTP return codes without trailing space + +* Thu Mar 29 2001 Vsevolod Volkov +- update to 1.3.17 +- support for 'LIST NEWSGROUPS' command to read descriptions + +* Fri Mar 2 2001 Vsevolod Volkov +- update to 1.3.16 + +* Wed Feb 14 2001 Vsevolod Volkov +- update to 1.3.15 + +* Sun Jan 28 2001 Vsevolod Volkov +- update to 1.3.14 +- show number of tagged messages patch from Felix von Leitner + +* Sun Dec 31 2000 Vsevolod Volkov +- update to 1.3.13 + +* Sat Dec 30 2000 Vsevolod Volkov +- Fixed problem if last article in group is deleted + +* Fri Dec 22 2000 Vsevolod Volkov +- Fixed checking of XGTITLE command on some servers + +* Mon Dec 18 2000 Vsevolod Volkov +- Added \r in AUTHINFO commands + +* Mon Nov 27 2000 Vsevolod Volkov +- update to 1.3.12 + +* Wed Nov 1 2000 Vsevolod Volkov +- update to 1.3.11 +- fixed error opening newsgroup from mutt started with -g or -G + +* Thu Oct 12 2000 Vsevolod Volkov +- update to 1.3.10 +- hotkey 'G' (get-message) replaced with '^G' + +* Thu Sep 21 2000 Vsevolod Volkov +- update to 1.3.9 +- changed delay displaying error messages from 1 to 2 seconds +- fixed error compiling with nntp and without imap + +* Wed Sep 6 2000 Vsevolod Volkov +- fixed catchup in index +- fixed nntp_open_mailbox() + +* Sat Sep 2 2000 Vsevolod Volkov +- functions and disabled +- format of news mailbox names changed to url form +- option nntp_attempts removed +- option reconnect_news renamed to nntp_reconnect +- default value of nntp_poll changed from 30 to 60 +- error handling improved + +* Wed Aug 30 2000 Vsevolod Volkov +- update to 1.3.8 +- new option show_only_unread +- add newsgroup completion + +* Fri Aug 4 2000 Vsevolod Volkov +- update to 1.3.7 + +* Sat Jul 29 2000 Vsevolod Volkov +- update to 1.3.6 + +* Sun Jul 9 2000 Vsevolod Volkov +- update to 1.3.5 +- authentication code update +- fix for changing to newsgroup from mailbox with read messages +- socket code optimization + +* Wed Jun 21 2000 Vsevolod Volkov +- update to 1.3.4 + +* Wed Jun 14 2000 Vsevolod Volkov +- don't substitute current newsgroup with deleted new messages + +* Mon Jun 12 2000 Vsevolod Volkov +- update to 1.3.3 +- fix for substitution of newsgroup after reconnection +- fix for loading newsgroups with very long names +- fix for loading more than 32768 newsgroups + +* Wed May 24 2000 Vsevolod Volkov +- update to 1.3.2 + +* Sat May 20 2000 Vsevolod Volkov +- update to 1.3.1 + +* Fri May 12 2000 Vsevolod Volkov +- update to 1.3 + +* Thu May 11 2000 Vsevolod Volkov +- update to 1.2 + +* Thu May 4 2000 Vsevolod Volkov +- update to 1.1.14 + +* Sun Apr 23 2000 Vsevolod Volkov +- update to 1.1.12 + +* Fri Apr 7 2000 Vsevolod Volkov +- add substitution of newsgroup with new messages by default + +* Wed Apr 5 2000 Vsevolod Volkov +- add attach message from newsgroup +- add one-line help in newsreader mode +- disable 'change-dir' command in newsgroups browser +- add -G option + +* Tue Apr 4 2000 Vsevolod Volkov +- get default news server name from file /etc/nntpserver +- use case insensitive server names +- add print-style sequence %s to $newsrc +- add -g option + +* Sat Apr 1 2000 Vsevolod Volkov +- remove 'X-FTN-Origin' header processing + +* Thu Mar 30 2000 Vsevolod Volkov +- update to 1.1.11 +- update to 1.1.10 + +* Thu Mar 23 2000 Vsevolod Volkov +- fix mutt_select_newsserver() +- remove 'toggle-mode' function +- add 'change-newsgroup' function + +* Wed Mar 22 2000 Vsevolod Volkov +- fix server-hook + +* Tue Mar 21 2000 Vsevolod Volkov +- fix error 'bounce' function after 'post' +- add 'forward to newsgroup' function + +* Mon Mar 20 2000 Vsevolod Volkov +- 'forward' function works in newsreader mode +- add 'post' and 'followup' functions to pager and attachment menu +- fix active descriptions and allowed flag reload + +* Tue Mar 14 2000 Vsevolod Volkov +- update to 1.1.9 +- remove deleted newsgroups from list + +* Mon Mar 13 2000 Vsevolod Volkov +- update .newsrc in browser + +* Sun Mar 12 2000 Vsevolod Volkov +- reload .newsrc if externally modified +- fix active cache update + +* Sun Mar 5 2000 Vsevolod Volkov +- update to 1.1.8 + +* Sat Mar 4 2000 Vsevolod Volkov +- patch *.update_list_file is not required +- count lines when loading descriptions +- remove cache of unsubscribed newsgroups + +* Thu Mar 2 2000 Vsevolod Volkov +- load list of newsgroups from cache faster + +* Wed Mar 1 2000 Vsevolod Volkov +- update to 1.1.7 + +* Tue Feb 29 2000 Vsevolod Volkov +- fix unread messages in browser +- fix newsrc_gen_entries() + +* Mon Feb 28 2000 Vsevolod Volkov +- fix mutt_newsgroup_stat() +- fix nntp_delete_cache() +- fix nntp_get_status() +- fix check_children() +- fix nntp_fetch_headers() + +* Fri Feb 25 2000 Vsevolod Volkov +- update to 1.1.5 + +* Thu Feb 24 2000 Vsevolod Volkov +- fix updating new messages in cache + +* Mon Feb 21 2000 Vsevolod Volkov +- change default cache filenames +- fix updating new messages in cache + +* Fri Feb 18 2000 Vsevolod Volkov +- fix segmentation fault in news groups browser + +* Tue Feb 15 2000 Vsevolod Volkov +- update to 1.1.4 + +* Thu Feb 10 2000 Vsevolod Volkov +- update to 1.1.3 + +* Sun Jan 30 2000 Vsevolod Volkov +- add X-Comment-To editing +- add my_hdr support for Newsgroups:, Followup-To: and X-Comment-To: headers +- add variables $ask_followup_to and $ask_x_comment_to + +* Fri Jan 28 2000 Vsevolod Volkov +- update to 1.1.2 diff --git a/Makefile.am b/Makefile.am index 2fc6b1d..6d56156 100644 --- a/Makefile.am +++ b/Makefile.am @@ -58,6 +58,7 @@ EXTRA_mutt_SOURCES = account.c bcache.c crypt-gpgme.c crypt-mod-pgp-classic.c \ mutt_idna.c mutt_sasl.c mutt_socket.c mutt_ssl.c mutt_ssl_gnutls.c \ mutt_tunnel.c pgp.c pgpinvoke.c pgpkey.c pgplib.c pgpmicalg.c \ pgppacket.c pop.c pop_auth.c pop_lib.c remailer.c resize.c sha1.c \ + nntp.c newsrc.c \ smime.c smtp.c utf8.c wcwidth.c \ bcache.h browser.h hcache.h mbyte.h mutt_idna.h remailer.h url.h @@ -69,6 +70,7 @@ EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP OPS.CRYPT OPS.SMIME TODO UPDATING \ mutt_regex.h mutt_sasl.h mutt_socket.h mutt_ssl.h mutt_tunnel.h \ mx.h pager.h pgp.h pop.h protos.h rfc1524.h rfc2047.h \ rfc2231.h rfc822.h rfc3676.h sha1.h sort.h mime.types VERSION prepare \ + nntp.h ChangeLog.nntp \ _regex.h OPS.MIX README.SECURITY remailer.c remailer.h browser.h \ mbyte.h lib.h extlib.c pgpewrap.c smime_keys.pl pgplib.h \ README.SSL smime.h group.h \ diff --git a/OPS b/OPS index 1ed9c96..b900373 100644 --- a/OPS +++ b/OPS @@ -8,14 +8,16 @@ OP_BOUNCE_MESSAGE "remail a message to another user" OP_BROWSER_NEW_FILE "select a new file in this directory" OP_BROWSER_VIEW_FILE "view file" OP_BROWSER_TELL "display the currently selected file's name" -OP_BROWSER_SUBSCRIBE "subscribe to current mailbox (IMAP only)" -OP_BROWSER_UNSUBSCRIBE "unsubscribe from current mailbox (IMAP only)" +OP_BROWSER_SUBSCRIBE "subscribe to current mbox (IMAP/NNTP only)" +OP_BROWSER_UNSUBSCRIBE "unsubscribe from current mbox (IMAP/NNTP only)" OP_BROWSER_TOGGLE_LSUB "toggle view all/subscribed mailboxes (IMAP only)" OP_BUFFY_LIST "list mailboxes with new mail" +OP_CATCHUP "mark all articles in newsgroup as read" OP_CHANGE_DIRECTORY "change directories" OP_CHECK_NEW "check mailboxes for new mail" OP_COMPOSE_ATTACH_FILE "attach file(s) to this message" OP_COMPOSE_ATTACH_MESSAGE "attach message(s) to this message" +OP_COMPOSE_ATTACH_NEWS_MESSAGE "attach news article(s) to this message" OP_COMPOSE_EDIT_BCC "edit the BCC list" OP_COMPOSE_EDIT_CC "edit the CC list" OP_COMPOSE_EDIT_DESCRIPTION "edit attachment description" @@ -26,7 +28,10 @@ OP_COMPOSE_EDIT_FROM "edit the from field" OP_COMPOSE_EDIT_HEADERS "edit the message with headers" OP_COMPOSE_EDIT_MESSAGE "edit the message" OP_COMPOSE_EDIT_MIME "edit attachment using mailcap entry" +OP_COMPOSE_EDIT_NEWSGROUPS "edit the newsgroups list" OP_COMPOSE_EDIT_REPLY_TO "edit the Reply-To field" +OP_COMPOSE_EDIT_FOLLOWUP_TO "edit the Followup-To field" +OP_COMPOSE_EDIT_X_COMMENT_TO "edit the X-Comment-To field" OP_COMPOSE_EDIT_SUBJECT "edit the subject of this message" OP_COMPOSE_EDIT_TO "edit the TO list" OP_CREATE_MAILBOX "create a new mailbox (IMAP only)" @@ -85,8 +90,13 @@ OP_EXIT "exit this menu" OP_FILTER "filter attachment through a shell command" OP_FIRST_ENTRY "move to the first entry" OP_FLAG_MESSAGE "toggle a message's 'important' flag" +OP_FOLLOWUP "followup to newsgroup" +OP_FORWARD_TO_GROUP "forward to newsgroup" OP_FORWARD_MESSAGE "forward a message with comments" OP_GENERIC_SELECT_ENTRY "select the current entry" +OP_GET_CHILDREN "get all children of the current message" +OP_GET_MESSAGE "get message with Message-Id" +OP_GET_PARENT "get parent of the current message" OP_GROUP_REPLY "reply to all recipients" OP_HALF_DOWN "scroll down 1/2 page" OP_HALF_UP "scroll up 1/2 page" @@ -94,11 +104,14 @@ OP_HELP "this screen" OP_JUMP "jump to an index number" OP_LAST_ENTRY "move to the last entry" OP_LIST_REPLY "reply to specified mailing list" +OP_LOAD_ACTIVE "load list of all newsgroups from NNTP server" OP_MACRO "execute a macro" OP_MAIL "compose a new mail message" OP_MAIN_BREAK_THREAD "break the thread in two" OP_MAIN_CHANGE_FOLDER "open a different folder" OP_MAIN_CHANGE_FOLDER_READONLY "open a different folder in read only mode" +OP_MAIN_CHANGE_GROUP "open a different newsgroup" +OP_MAIN_CHANGE_GROUP_READONLY "open a different newsgroup in read only mode" OP_MAIN_CLEAR_FLAG "clear a status flag from a message" OP_MAIN_DELETE_PATTERN "delete messages matching a pattern" OP_MAIN_IMAP_FETCH "force retrieval of mail from IMAP server" @@ -138,6 +151,7 @@ OP_PAGER_HIDE_QUOTED "toggle display of quoted text" OP_PAGER_SKIP_QUOTED "skip beyond quoted text" OP_PAGER_TOP "jump to the top of the message" OP_PIPE "pipe message/attachment to a shell command" +OP_POST "post message to newsgroup" OP_PREV_ENTRY "move to the previous entry" OP_PREV_LINE "scroll up one line" OP_PREV_PAGE "move to the previous page" @@ -147,6 +161,7 @@ OP_QUERY "query external program for addresses" OP_QUERY_APPEND "append new query results to current results" OP_QUIT "save changes to mailbox and quit" OP_RECALL_MESSAGE "recall a postponed message" +OP_RECONSTRUCT_THREAD "reconstruct thread containing current message" OP_REDRAW "clear and redraw the screen" OP_REFORMAT_WINCH "{internal}" OP_RENAME_MAILBOX "rename the current mailbox (IMAP only)" @@ -161,18 +176,22 @@ OP_SEARCH_TOGGLE "toggle search pattern coloring" OP_SHELL_ESCAPE "invoke a command in a subshell" OP_SORT "sort messages" OP_SORT_REVERSE "sort messages in reverse order" +OP_SUBSCRIBE_PATTERN "subscribe to newsgroups matching a pattern" OP_TAG "tag the current entry" OP_TAG_PREFIX "apply next function to tagged messages" OP_TAG_PREFIX_COND "apply next function ONLY to tagged messages" OP_TAG_SUBTHREAD "tag the current subthread" OP_TAG_THREAD "tag the current thread" OP_TOGGLE_NEW "toggle a message's 'new' flag" +OP_TOGGLE_READ "toggle view of read messages" OP_TOGGLE_WRITE "toggle whether the mailbox will be rewritten" OP_TOGGLE_MAILBOXES "toggle whether to browse mailboxes or all files" OP_TOP_PAGE "move to the top of the page" +OP_UNCATCHUP "mark all articles in newsgroup as unread" OP_UNDELETE "undelete the current entry" OP_UNDELETE_THREAD "undelete all messages in thread" OP_UNDELETE_SUBTHREAD "undelete all messages in subthread" +OP_UNSUBSCRIBE_PATTERN "unsubscribe from newsgroups matching a pattern" OP_VERSION "show the Mutt version number and date" OP_VIEW_ATTACH "view attachment using mailcap entry if necessary" OP_VIEW_ATTACHMENTS "show MIME attachments" diff --git a/account.c b/account.c index bf59995..624f931 100644 --- a/account.c +++ b/account.c @@ -51,8 +51,17 @@ int mutt_account_match (const ACCOUNT* a1, const ACCOUNT* a2) user = PopUser; #endif +#ifdef USE_NNTP + if (a1->type == M_ACCT_TYPE_NNTP && NntpUser) + user = NntpUser; +#endif + if (a1->flags & a2->flags & M_ACCT_USER) return (!strcmp (a1->user, a2->user)); +#ifdef USE_NNTP + if (a1->type == M_ACCT_TYPE_NNTP) + return a1->flags & M_ACCT_USER && a1->user[0] ? 0 : 1; +#endif if (a1->flags & M_ACCT_USER) return (!strcmp (a1->user, user)); if (a2->flags & M_ACCT_USER) @@ -130,6 +139,16 @@ void mutt_account_tourl (ACCOUNT* account, ciss_url_t* url) } #endif +#ifdef USE_NNTP + if (account->type == M_ACCT_TYPE_NNTP) + { + if (account->flags & M_ACCT_SSL) + url->scheme = U_NNTPS; + else + url->scheme = U_NNTP; + } +#endif + url->host = account->host; if (account->flags & M_ACCT_PORT) url->port = account->port; @@ -155,6 +174,10 @@ int mutt_account_getuser (ACCOUNT* account) else if ((account->type == M_ACCT_TYPE_POP) && PopUser) strfcpy (account->user, PopUser, sizeof (account->user)); #endif +#ifdef USE_NNTP + else if ((account->type == M_ACCT_TYPE_NNTP) && NntpUser) + strfcpy (account->user, NntpUser, sizeof (account->user)); +#endif else if (option (OPTNOCURSES)) return -1; /* prompt (defaults to unix username), copy into account->user */ @@ -217,6 +240,10 @@ int mutt_account_getpass (ACCOUNT* account) else if ((account->type == M_ACCT_TYPE_SMTP) && SmtpPass) strfcpy (account->pass, SmtpPass, sizeof (account->pass)); #endif +#ifdef USE_NNTP + else if ((account->type == M_ACCT_TYPE_NNTP) && NntpPass) + strfcpy (account->pass, NntpPass, sizeof (account->pass)); +#endif else if (option (OPTNOCURSES)) return -1; else diff --git a/account.h b/account.h index 662fb32..9febee6 100644 --- a/account.h +++ b/account.h @@ -29,7 +29,8 @@ enum M_ACCT_TYPE_NONE = 0, M_ACCT_TYPE_IMAP, M_ACCT_TYPE_POP, - M_ACCT_TYPE_SMTP + M_ACCT_TYPE_SMTP, + M_ACCT_TYPE_NNTP }; /* account flags */ diff --git a/attach.h b/attach.h index 928408a..071f22c 100644 --- a/attach.h +++ b/attach.h @@ -50,7 +50,7 @@ void mutt_print_attachment_list (FILE *fp, int tag, BODY *top); void mutt_attach_bounce (FILE *, HEADER *, ATTACHPTR **, short, BODY *); void mutt_attach_resend (FILE *, HEADER *, ATTACHPTR **, short, BODY *); -void mutt_attach_forward (FILE *, HEADER *, ATTACHPTR **, short, BODY *); +void mutt_attach_forward (FILE *, HEADER *, ATTACHPTR **, short, BODY *, int); void mutt_attach_reply (FILE *, HEADER *, ATTACHPTR **, short, BODY *, int); #endif /* _ATTACH_H_ */ diff --git a/browser.c b/browser.c index dbb31c8..3c84044 100644 --- a/browser.c +++ b/browser.c @@ -32,6 +32,9 @@ #ifdef USE_IMAP #include "imap.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif #include #include @@ -50,6 +53,19 @@ static const struct mapping_t FolderHelp[] = { { NULL, 0 } }; +#ifdef USE_NNTP +static struct mapping_t FolderNewsHelp[] = { + { N_("Exit"), OP_EXIT }, + { N_("List"), OP_TOGGLE_MAILBOXES }, + { N_("Subscribe"), OP_BROWSER_SUBSCRIBE }, + { N_("Unsubscribe"), OP_BROWSER_UNSUBSCRIBE }, + { N_("Catchup"), OP_CATCHUP }, + { N_("Mask"), OP_ENTER_MASK }, + { N_("Help"), OP_HELP }, + { NULL, 0 } +}; +#endif + typedef struct folder_t { struct folder_file *ff; @@ -116,9 +132,17 @@ static void browser_sort (struct browser_state *state) case SORT_ORDER: return; case SORT_DATE: +#ifdef USE_NNTP + if (option (OPTNEWS)) + return; +#endif f = browser_compare_date; break; case SORT_SIZE: +#ifdef USE_NNTP + if (option (OPTNEWS)) + return; +#endif f = browser_compare_size; break; case SORT_SUBJECT: @@ -325,8 +349,111 @@ folder_format_str (char *dest, size_t destlen, size_t col, char op, const char * return (src); } +#ifdef USE_NNTP +static const char * +newsgroup_format_str (char *dest, size_t destlen, size_t col, char op, const char *src, + const char *fmt, const char *ifstring, const char *elsestring, + unsigned long data, format_flag flags) +{ + char fn[SHORT_STRING], tmp[SHORT_STRING]; + FOLDER *folder = (FOLDER *) data; + + switch (op) + { + case 'C': + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, folder->num + 1); + break; + + case 'f': + strncpy (fn, folder->ff->name, sizeof(fn) - 1); + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, fn); + break; + + case 'N': + snprintf (tmp, sizeof (tmp), "%%%sc", fmt); + if (folder->ff->nd->subscribed) + snprintf (dest, destlen, tmp, ' '); + else + snprintf (dest, destlen, tmp, folder->ff->new ? 'N' : 'u'); + break; + + case 'M': + snprintf (tmp, sizeof (tmp), "%%%sc", fmt); + if (folder->ff->nd->deleted) + snprintf (dest, destlen, tmp, 'D'); + else + snprintf (dest, destlen, tmp, folder->ff->nd->allowed ? ' ' : '-'); + break; + + case 's': + if (flags & M_FORMAT_OPTIONAL) + { + if (folder->ff->nd->unread != 0) + mutt_FormatString (dest, destlen, col, ifstring, newsgroup_format_str, + data, flags); + else + mutt_FormatString (dest, destlen, col, elsestring, newsgroup_format_str, + data, flags); + } + else if (Context && Context->data == folder->ff->nd) + { + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, Context->unread); + } + else + { + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, folder->ff->nd->unread); + } + break; + + case 'n': + if (Context && Context->data == folder->ff->nd) + { + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, Context->new); + } + else if (option (OPTMARKOLD) && + folder->ff->nd->lastCached >= folder->ff->nd->firstMessage && + folder->ff->nd->lastCached <= folder->ff->nd->lastMessage) + { + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, folder->ff->nd->lastMessage - folder->ff->nd->lastCached); + } + else + { + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, folder->ff->nd->unread); + } + break; + + case 'd': + if (folder->ff->nd->desc != NULL) + { + char *buf = safe_strdup (folder->ff->nd->desc); + if (NewsgroupsCharset && *NewsgroupsCharset) + mutt_convert_string (&buf, NewsgroupsCharset, Charset, M_ICONV_HOOK_FROM); + mutt_filter_unprintable (&buf); + + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, buf); + } + else + { + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, ""); + } + break; + } + return (src); +} +#endif /* USE_NNTP */ + static void add_folder (MUTTMENU *m, struct browser_state *state, - const char *name, const struct stat *s, unsigned int new) + const char *name, const struct stat *s, + void *data, unsigned int new) { if (state->entrylen == state->entrymax) { @@ -355,6 +482,10 @@ static void add_folder (MUTTMENU *m, struct browser_state *state, #ifdef USE_IMAP (state->entry)[state->entrylen].imap = 0; #endif +#ifdef USE_NNTP + if (option (OPTNEWS)) + (state->entry)[state->entrylen].nd = (NNTP_DATA *)data; +#endif (state->entrylen)++; } @@ -370,9 +501,36 @@ static void init_state (struct browser_state *state, MUTTMENU *menu) menu->data = state->entry; } +/* get list of all files/newsgroups with mask */ static int examine_directory (MUTTMENU *menu, struct browser_state *state, char *d, const char *prefix) { +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + NNTP_SERVER *nserv = CurrentNewsSrv; + unsigned int i; + +/* mutt_buffy_check (0); */ + init_state (state, menu); + + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + if (!nntp_data) + continue; + if (prefix && *prefix && + strncmp (prefix, nntp_data->group, strlen (prefix))) + continue; + if (!((regexec (Mask.rx, nntp_data->group, 0, NULL, 0) == 0) ^ Mask.not)) + continue; + add_folder (menu, state, nntp_data->group, NULL, + nntp_data, nntp_data->new); + } + } + else +#endif /* USE_NNTP */ + { struct stat s; DIR *dp; struct dirent *de; @@ -433,17 +591,41 @@ static int examine_directory (MUTTMENU *menu, struct browser_state *state, tmp = Incoming; while (tmp && mutt_strcmp (buffer, tmp->path)) tmp = tmp->next; - add_folder (menu, state, de->d_name, &s, (tmp) ? tmp->new : 0); + add_folder (menu, state, de->d_name, &s, NULL, (tmp) ? tmp->new : 0); } closedir (dp); + } browser_sort (state); return 0; } +/* get list of mailboxes/subscribed newsgroups */ static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state) { struct stat s; char buffer[LONG_STRING]; + +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + NNTP_SERVER *nserv = CurrentNewsSrv; + unsigned int i; + +/* mutt_buffy_check (0); */ + init_state (state, menu); + + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + if (nntp_data && (nntp_data->new || (nntp_data->subscribed && + (nntp_data->unread || !option (OPTSHOWONLYUNREAD))))) + add_folder (menu, state, nntp_data->group, NULL, + nntp_data, nntp_data->new); + } + } + else +#endif + { BUFFY *tmp = Incoming; #ifdef USE_IMAP struct mailbox_state mbox; @@ -461,14 +643,21 @@ static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state) if (mx_is_imap (tmp->path)) { imap_mailbox_state (tmp->path, &mbox); - add_folder (menu, state, tmp->path, NULL, mbox.new); + add_folder (menu, state, tmp->path, NULL, NULL, mbox.new); continue; } #endif #ifdef USE_POP if (mx_is_pop (tmp->path)) { - add_folder (menu, state, tmp->path, NULL, tmp->new); + add_folder (menu, state, tmp->path, NULL, NULL, tmp->new); + continue; + } +#endif +#ifdef USE_NNTP + if (mx_is_nntp (tmp->path)) + { + add_folder (menu, state, tmp->path, NULL, NULL, tmp->new); continue; } #endif @@ -497,15 +686,20 @@ static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state) strfcpy (buffer, NONULL(tmp->path), sizeof (buffer)); mutt_pretty_mailbox (buffer, sizeof (buffer)); - add_folder (menu, state, buffer, &s, tmp->new); + add_folder (menu, state, buffer, &s, NULL, tmp->new); } while ((tmp = tmp->next)); + } browser_sort (state); return 0; } static int select_file_search (MUTTMENU *menu, regex_t *re, int n) { +#ifdef USE_NNTP + if (option (OPTNEWS)) + return (regexec (re, ((struct folder_file *) menu->data)[n].desc, 0, NULL, 0)); +#endif return (regexec (re, ((struct folder_file *) menu->data)[n].name, 0, NULL, 0)); } @@ -516,6 +710,12 @@ static void folder_entry (char *s, size_t slen, MUTTMENU *menu, int num) folder.ff = &((struct folder_file *) menu->data)[num]; folder.num = num; +#ifdef USE_NNTP + if (option (OPTNEWS)) + mutt_FormatString (s, slen, 0, NONULL(GroupFormat), newsgroup_format_str, + (unsigned long) &folder, M_FORMAT_ARROWCURSOR); + else +#endif mutt_FormatString (s, slen, 0, NONULL(FolderFormat), folder_format_str, (unsigned long) &folder, M_FORMAT_ARROWCURSOR); } @@ -536,6 +736,17 @@ static void init_menu (struct browser_state *state, MUTTMENU *menu, char *title, menu->tagged = 0; +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + if (buffy) + snprintf (title, titlelen, _("Subscribed newsgroups")); + else + snprintf (title, titlelen, _("Newsgroups on server [%s]"), + CurrentNewsSrv->conn->account.host); + } + else +#endif if (buffy) { menu->is_mailbox_list = 1; @@ -609,6 +820,31 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num if (!folder) strfcpy (LastDirBackup, LastDir, sizeof (LastDirBackup)); +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + if (*f) + strfcpy (prefix, f, sizeof (prefix)); + else + { + NNTP_SERVER *nserv = CurrentNewsSrv; + unsigned int i; + + /* default state for news reader mode is browse subscribed newsgroups */ + buffy = 0; + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + if (nntp_data && nntp_data->subscribed) + { + buffy = 1; + break; + } + } + } + } + else +#endif if (*f) { mutt_expand_path (f, flen); @@ -705,6 +941,9 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num menu->tag = file_tag; menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_FOLDER, +#ifdef USE_NNTP + option (OPTNEWS) ? FolderNewsHelp : +#endif FolderHelp); init_menu (&state, menu, title, sizeof (title), buffy); @@ -842,7 +1081,11 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num } } +#ifdef USE_NNTP + if (buffy || option (OPTNEWS)) +#else if (buffy) +#endif { strfcpy (f, state.entry[menu->current].name, flen); mutt_expand_path (f, flen); @@ -900,14 +1143,6 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num break; #ifdef USE_IMAP - case OP_BROWSER_SUBSCRIBE: - imap_subscribe (state.entry[menu->current].name, 1); - break; - - case OP_BROWSER_UNSUBSCRIBE: - imap_subscribe (state.entry[menu->current].name, 0); - break; - case OP_BROWSER_TOGGLE_LSUB: if (option (OPTIMAPLSUB)) unset_option (OPTIMAPLSUB); @@ -1008,6 +1243,11 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num case OP_CHANGE_DIRECTORY: +#ifdef USE_NNTP + if (option (OPTNEWS)) + break; +#endif + strfcpy (buf, LastDir, sizeof (buf)); #ifdef USE_IMAP if (!state.imap_browse) @@ -1273,6 +1513,210 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num else mutt_error _("Error trying to view file"); } + break; + +#ifdef USE_NNTP + case OP_CATCHUP: + case OP_UNCATCHUP: + if (option (OPTNEWS)) + { + struct folder_file *f = &state.entry[menu->current]; + int rc; + NNTP_DATA *nntp_data; + + rc = nntp_newsrc_parse (CurrentNewsSrv); + if (rc < 0) + break; + + if (i == OP_CATCHUP) + nntp_data = mutt_newsgroup_catchup (CurrentNewsSrv, f->name); + else + nntp_data = mutt_newsgroup_uncatchup (CurrentNewsSrv, f->name); + + if (nntp_data) + { +/* FOLDER folder; + struct folder_file ff; + char buffer[_POSIX_PATH_MAX + SHORT_STRING]; + + folder.ff = &ff; + folder.ff->name = f->name; + folder.ff->st = NULL; + folder.ff->is_new = nntp_data->new; + folder.ff->nntp_data = nntp_data; + FREE (&f->desc); + mutt_FormatString (buffer, sizeof (buffer), 0, NONULL(GroupFormat), + newsgroup_format_str, (unsigned long) &folder, + M_FORMAT_ARROWCURSOR); + f->desc = safe_strdup (buffer); */ + nntp_newsrc_update (CurrentNewsSrv); + if (menu->current + 1 < menu->max) + menu->current++; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + if (rc) + menu->redraw = REDRAW_INDEX; + nntp_newsrc_close (CurrentNewsSrv); + } + break; + + case OP_LOAD_ACTIVE: + if (option (OPTNEWS)) + { + NNTP_SERVER *nserv = CurrentNewsSrv; + unsigned int i; + + if (nntp_newsrc_parse (nserv) < 0) + break; + + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + if (nntp_data) + nntp_data->deleted = 1; + } + nntp_active_fetch (nserv); + nntp_newsrc_update (nserv); + nntp_newsrc_close (nserv); + + destroy_state (&state); + if (buffy) + examine_mailboxes (menu, &state); + else + examine_directory (menu, &state, NULL, NULL); + init_menu (&state, menu, title, sizeof (title), buffy); + } + break; +#endif /* USE_NNTP */ + +#if defined USE_IMAP || defined USE_NNTP + case OP_BROWSER_SUBSCRIBE: + case OP_BROWSER_UNSUBSCRIBE: +#endif +#ifdef USE_NNTP + case OP_SUBSCRIBE_PATTERN: + case OP_UNSUBSCRIBE_PATTERN: + if (option (OPTNEWS)) + { + NNTP_SERVER *nserv = CurrentNewsSrv; + NNTP_DATA *nntp_data; + regex_t *rx = (regex_t *) safe_malloc (sizeof (regex_t)); + char *s = buf; + int rc, j = menu->current; + + if (i == OP_SUBSCRIBE_PATTERN || i == OP_UNSUBSCRIBE_PATTERN) + { + char tmp[STRING]; + int err; + + buf[0] = 0; + if (i == OP_SUBSCRIBE_PATTERN) + snprintf (tmp, sizeof (tmp), _("Subscribe pattern: ")); + else + snprintf (tmp, sizeof (tmp), _("Unsubscribe pattern: ")); + if (mutt_get_field (tmp, buf, sizeof (buf), 0) != 0 || !buf[0]) + { + FREE (&rx); + break; + } + + err = REGCOMP (rx, s, REG_NOSUB); + if (err) + { + regerror (err, rx, buf, sizeof (buf)); + regfree (rx); + FREE (&rx); + mutt_error ("%s", buf); + break; + } + menu->redraw = REDRAW_FULL; + j = 0; + } + else if (!state.entrylen) + { + mutt_error _("No newsgroups match the mask"); + break; + } + + rc = nntp_newsrc_parse (nserv); + if (rc < 0) + break; + + for ( ; j < state.entrylen; j++) + { + struct folder_file *f = &state.entry[j]; + + if (i == OP_BROWSER_SUBSCRIBE || i == OP_BROWSER_UNSUBSCRIBE || + regexec (rx, f->name, 0, NULL, 0) == 0) + { + if (i == OP_BROWSER_SUBSCRIBE || i == OP_SUBSCRIBE_PATTERN) + nntp_data = mutt_newsgroup_subscribe (nserv, f->name); + else + nntp_data = mutt_newsgroup_unsubscribe (nserv, f->name); +/* if (nntp_data) + { + FOLDER folder; + char buffer[_POSIX_PATH_MAX + SHORT_STRING]; + + folder.name = f->name; + folder.f = NULL; + folder.new = nntp_data->new; + folder.nd = nntp_data; + FREE (&f->desc); + mutt_FormatString (buffer, sizeof (buffer), 0, NONULL(GroupFormat), + newsgroup_format_str, (unsigned long) &folder, + M_FORMAT_ARROWCURSOR); + f->desc = safe_strdup (buffer); + } */ + } + if (i == OP_BROWSER_SUBSCRIBE || i == OP_BROWSER_UNSUBSCRIBE) + { + if (menu->current + 1 < menu->max) + menu->current++; + menu->redraw = REDRAW_MOTION_RESYNCH; + break; + } + } + if (i == OP_SUBSCRIBE_PATTERN) + { + unsigned int i; + + for (i = 0; nserv && i < nserv->groups_num; i++) + { + nntp_data = nserv->groups_list[i]; + if (nntp_data && nntp_data->group && !nntp_data->subscribed) + { + if (regexec (rx, nntp_data->group, 0, NULL, 0) == 0) + { + mutt_newsgroup_subscribe (nserv, nntp_data->group); + add_folder (menu, &state, nntp_data->group, NULL, + nntp_data, nntp_data->new); + } + } + } + init_menu (&state, menu, title, sizeof (title), buffy); + } + if (rc > 0) + menu->redraw = REDRAW_FULL; + nntp_newsrc_update (nserv); + nntp_clear_cache (nserv); + nntp_newsrc_close (nserv); + if (i != OP_BROWSER_SUBSCRIBE && i != OP_BROWSER_UNSUBSCRIBE) + regfree (rx); + FREE (&rx); + } +#ifdef USE_IMAP + else +#endif /* USE_IMAP && USE_NNTP */ +#endif /* USE_NNTP */ +#ifdef USE_IMAP + { + if (i == OP_BROWSER_SUBSCRIBE) + imap_subscribe (state.entry[menu->current].name, 1); + else + imap_subscribe (state.entry[menu->current].name, 0); + } +#endif /* USE_IMAP */ } } diff --git a/browser.h b/browser.h index 515d69f..ad89ab2 100644 --- a/browser.h +++ b/browser.h @@ -19,6 +19,10 @@ #ifndef _BROWSER_H #define _BROWSER_H 1 +#ifdef USE_NNTP +#include "nntp.h" +#endif + struct folder_file { mode_t mode; @@ -37,6 +41,9 @@ struct folder_file unsigned selectable : 1; unsigned inferiors : 1; #endif +#ifdef USE_NNTP + NNTP_DATA *nd; +#endif unsigned tagged : 1; }; diff --git a/buffy.c b/buffy.c index 225104d..b1abfa9 100644 --- a/buffy.c +++ b/buffy.c @@ -525,6 +525,9 @@ int mutt_buffy_check (int force) /* check device ID and serial number instead of comparing paths */ if (!Context || Context->magic == M_IMAP || Context->magic == M_POP +#ifdef USE_NNTP + || Context->magic == M_NNTP +#endif || stat (Context->path, &contex_sb) != 0) { contex_sb.st_dev=0; @@ -541,6 +544,11 @@ int mutt_buffy_check (int force) tmp->magic = M_POP; else #endif +#ifdef USE_NNTP + if ((tmp->magic == M_NNTP) || mx_is_nntp (tmp->path)) + tmp->magic = M_NNTP; + else +#endif if (stat (tmp->path, &sb) != 0 || (S_ISREG(sb.st_mode) && sb.st_size == 0) || (!tmp->magic && (tmp->magic = mx_get_magic (tmp->path)) <= 0)) { @@ -556,7 +564,11 @@ int mutt_buffy_check (int force) /* check to see if the folder is the currently selected folder * before polling */ if (!Context || !Context->path || +#ifdef USE_NNTP + (( tmp->magic == M_IMAP || tmp->magic == M_POP || tmp->magic == M_NNTP ) +#else (( tmp->magic == M_IMAP || tmp->magic == M_POP ) +#endif ? mutt_strcmp (tmp->path, Context->path) : (sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino))) { diff --git a/complete.c b/complete.c index d0ee4af..8dc48cd 100644 --- a/complete.c +++ b/complete.c @@ -25,6 +25,9 @@ #include "mailbox.h" #include "imap.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif #include #include @@ -48,9 +51,70 @@ int mutt_complete (char *s, size_t slen) char filepart[_POSIX_PATH_MAX]; #ifdef USE_IMAP char imap_path[LONG_STRING]; +#endif dprint (2, (debugfile, "mutt_complete: completing %s\n", s)); +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + NNTP_SERVER *nserv = CurrentNewsSrv; + unsigned int n = 0; + + strfcpy (filepart, s, sizeof (filepart)); + + /* special case to handle when there is no filepart yet + * find the first subscribed newsgroup */ + len = mutt_strlen (filepart); + if (len == 0) + { + for (; n < nserv->groups_num; n++) + { + NNTP_DATA *nntp_data = nserv->groups_list[n]; + + if (nntp_data && nntp_data->subscribed) + { + strfcpy (filepart, nntp_data->group, sizeof (filepart)); + init = 1; + n++; + break; + } + } + } + + for (; n < nserv->groups_num; n++) + { + NNTP_DATA *nntp_data = nserv->groups_list[n]; + + if (nntp_data && nntp_data->subscribed && + mutt_strncmp (nntp_data->group, filepart, len) == 0) + { + if (init) + { + for (i = 0; filepart[i] && nntp_data->group[i]; i++) + { + if (filepart[i] != nntp_data->group[i]) + { + filepart[i] = 0; + break; + } + } + filepart[i] = 0; + } + else + { + strfcpy (filepart, nntp_data->group, sizeof (filepart)); + init = 1; + } + } + } + + strcpy (s, filepart); + return (init ? 0 : -1); + } +#endif + +#ifdef USE_IMAP /* we can use '/' as a delimiter, imap_complete rewrites it */ if (*s == '=' || *s == '+' || *s == '!') { diff --git a/compose.c b/compose.c index 16576f2..9e23dfd 100644 --- a/compose.c +++ b/compose.c @@ -33,11 +33,16 @@ #include "sort.h" #include "charset.h" #include "sidebar.h" +#include "mx.h" #ifdef MIXMASTER #include "remailer.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif + #include #include #include @@ -68,11 +73,17 @@ enum HDR_CRYPT, HDR_CRYPTINFO, +#ifdef USE_NNTP + HDR_NEWSGROUPS, + HDR_FOLLOWUPTO, + HDR_XCOMMENTTO, +#endif + HDR_ATTACH = (HDR_FCC + 5) /* where to start printing the attachments */ }; -#define HDR_XOFFSET 10 -#define TITLE_FMT "%10s" /* Used for Prompts, which are ASCII */ +#define HDR_XOFFSET 14 +#define TITLE_FMT "%14s" /* Used for Prompts, which are ASCII */ #define W (COLS - HDR_XOFFSET - SidebarWidth) static const char * const Prompts[] = @@ -84,6 +95,16 @@ static const char * const Prompts[] = "Subject: ", "Reply-To: ", "Fcc: " +#ifdef USE_NNTP +#ifdef MIXMASTER + ,"" +#endif + ,"" + ,"" + ,"Newsgroups: " + ,"Followup-To: " + ,"X-Comment-To: " +#endif }; static const struct mapping_t ComposeHelp[] = { @@ -98,6 +119,19 @@ static const struct mapping_t ComposeHelp[] = { { NULL, 0 } }; +#ifdef USE_NNTP +static struct mapping_t ComposeNewsHelp[] = { + { N_("Send"), OP_COMPOSE_SEND_MESSAGE }, + { N_("Abort"), OP_EXIT }, + { "Newsgroups", OP_COMPOSE_EDIT_NEWSGROUPS }, + { "Subj", OP_COMPOSE_EDIT_SUBJECT }, + { N_("Attach file"), OP_COMPOSE_ATTACH_FILE }, + { N_("Descrip"), OP_COMPOSE_EDIT_DESCRIPTION }, + { N_("Help"), OP_HELP }, + { NULL, 0 } +}; +#endif + static void snd_entry (char *b, size_t blen, MUTTMENU *menu, int num) { mutt_FormatString (b, blen, 0, NONULL (AttachFormat), mutt_attach_fmt, @@ -111,7 +145,7 @@ static void snd_entry (char *b, size_t blen, MUTTMENU *menu, int num) static void redraw_crypt_lines (HEADER *msg) { - mvaddstr (HDR_CRYPT, SidebarWidth, "Security: "); + mvprintw (HDR_CRYPT, SidebarWidth, TITLE_FMT, "Security: "); if ((WithCrypto & (APPLICATION_PGP | APPLICATION_SMIME)) == 0) { @@ -149,10 +183,11 @@ static void redraw_crypt_lines (HEADER *msg) if ((WithCrypto & APPLICATION_PGP) && (msg->security & APPLICATION_PGP) && (msg->security & SIGN)) printw ("%s%s", _(" sign as: "), PgpSignAs ? PgpSignAs : _("")); + printw (TITLE_FMT "%s", _(" sign as: "), PgpSignAs ? PgpSignAs : _("")); if ((WithCrypto & APPLICATION_SMIME) && (msg->security & APPLICATION_SMIME) && (msg->security & SIGN)) { - printw ("%s%s", _(" sign as: "), SmimeDefaultKey ? SmimeDefaultKey : _("")); + printw (TITLE_FMT "%s", _(" sign as: "), SmimeDefaultKey ? SmimeDefaultKey : _("")); } if ((WithCrypto & APPLICATION_SMIME) @@ -173,7 +208,7 @@ static void redraw_mix_line (LIST *chain) int c; char *t; - mvaddstr (HDR_MIX, SidebarWidth, " Mix: "); + mvprintw (HDR_MIX, SidebarWidth, TITLE_FMT, "Mix: "); if (!chain) { @@ -248,9 +283,28 @@ static void draw_envelope (HEADER *msg, char *fcc) { draw_sidebar (MENU_COMPOSE); draw_envelope_addr (HDR_FROM, msg->env->from); +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) + { +#endif draw_envelope_addr (HDR_TO, msg->env->to); draw_envelope_addr (HDR_CC, msg->env->cc); draw_envelope_addr (HDR_BCC, msg->env->bcc); +#ifdef USE_NNTP + } + else + { + mvprintw (HDR_TO, 0, TITLE_FMT , Prompts[HDR_NEWSGROUPS - 1]); + mutt_paddstr (W, NONULL (msg->env->newsgroups)); + mvprintw (HDR_CC, 0, TITLE_FMT , Prompts[HDR_FOLLOWUPTO - 1]); + mutt_paddstr (W, NONULL (msg->env->followup_to)); + if (option (OPTXCOMMENTTO)) + { + mvprintw (HDR_BCC, 0, TITLE_FMT , Prompts[HDR_XCOMMENTTO - 1]); + mutt_paddstr (W, NONULL (msg->env->x_comment_to)); + } + } +#endif mvprintw (HDR_SUBJECT, SidebarWidth, TITLE_FMT, Prompts[HDR_SUBJECT - 1]); mutt_paddstr (W, NONULL (msg->env->subject)); draw_envelope_addr (HDR_REPLYTO, msg->env->reply_to); @@ -501,6 +555,12 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ /* Sort, SortAux could be changed in mutt_index_menu() */ int oldSort, oldSortAux; struct stat st; +#ifdef USE_NNTP + int news = 0; /* is it a news article ? */ + + if (option (OPTNEWSSEND)) + news++; +#endif mutt_attach_init (msg->content); idx = mutt_gen_attach_list (msg->content, -1, idx, &idxlen, &idxmax, 0, 1); @@ -511,10 +571,18 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ menu->make_entry = snd_entry; menu->tag = mutt_tag_attach; menu->data = idx; +#ifdef USE_NNTP + if (news) + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_COMPOSE, ComposeNewsHelp); + else +#endif menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_COMPOSE, ComposeHelp); while (loop) { +#ifdef USE_NNTP + unset_option (OPTNEWS); /* for any case */ +#endif switch (op = mutt_menuLoop (menu)) { case OP_REDRAW: @@ -527,17 +595,90 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ mutt_message_hook (NULL, msg, M_SEND2HOOK); break; case OP_COMPOSE_EDIT_TO: +#ifdef USE_NNTP + if (news) + break; +#endif menu->redraw = edit_address_list (HDR_TO, &msg->env->to); mutt_message_hook (NULL, msg, M_SEND2HOOK); break; case OP_COMPOSE_EDIT_BCC: +#ifdef USE_NNTP + if (news) + break; +#endif menu->redraw = edit_address_list (HDR_BCC, &msg->env->bcc); mutt_message_hook (NULL, msg, M_SEND2HOOK); break; case OP_COMPOSE_EDIT_CC: +#ifdef USE_NNTP + if (news) + break; +#endif menu->redraw = edit_address_list (HDR_CC, &msg->env->cc); mutt_message_hook (NULL, msg, M_SEND2HOOK); break; +#ifdef USE_NNTP + case OP_COMPOSE_EDIT_NEWSGROUPS: + if (news) + { + if (msg->env->newsgroups) + strfcpy (buf, msg->env->newsgroups, sizeof (buf)); + else + buf[0] = 0; + if (mutt_get_field ("Newsgroups: ", buf, sizeof (buf), 0) == 0 && + buf[0]) + { + FREE (&msg->env->newsgroups); + mutt_remove_trailing_ws (buf); + msg->env->newsgroups = safe_strdup (mutt_skip_whitespace (buf)); + move (HDR_TO, HDR_XOFFSET); + clrtoeol (); + if (msg->env->newsgroups) + printw ("%-*.*s", W, W, msg->env->newsgroups); + } + } + break; + + case OP_COMPOSE_EDIT_FOLLOWUP_TO: + if (news) + { + buf[0] = 0; + if (msg->env->followup_to) + strfcpy (buf, msg->env->followup_to, sizeof (buf)); + if (mutt_get_field ("Followup-To: ", buf, sizeof (buf), 0) == 0 && + buf[0]) + { + FREE (&msg->env->followup_to); + mutt_remove_trailing_ws (buf); + msg->env->followup_to = safe_strdup (mutt_skip_whitespace (buf)); + move (HDR_CC, HDR_XOFFSET); + clrtoeol (); + if (msg->env->followup_to) + printw ("%-*.*s", W, W, msg->env->followup_to); + } + } + break; + + case OP_COMPOSE_EDIT_X_COMMENT_TO: + if (news && option (OPTXCOMMENTTO)) + { + buf[0] = 0; + if (msg->env->x_comment_to) + strfcpy (buf, msg->env->x_comment_to, sizeof (buf)); + if (mutt_get_field ("X-Comment-To: ", buf, sizeof (buf), 0) == 0 && + buf[0]) + { + FREE (&msg->env->x_comment_to); + msg->env->x_comment_to = safe_strdup (buf); + move (HDR_BCC, HDR_XOFFSET); + clrtoeol (); + if (msg->env->x_comment_to) + printw ("%-*.*s", W, W, msg->env->x_comment_to); + } + } + break; +#endif case OP_COMPOSE_EDIT_SUBJECT: if (msg->env->subject) strfcpy (buf, msg->env->subject, sizeof (buf)); @@ -701,6 +842,9 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ break; case OP_COMPOSE_ATTACH_MESSAGE: +#ifdef USE_NNTP + case OP_COMPOSE_ATTACH_NEWS_MESSAGE: +#endif { char *prompt; HEADER *h; @@ -708,7 +852,22 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ fname[0] = 0; prompt = _("Open mailbox to attach message from"); +#ifdef USE_NNTP + unset_option (OPTNEWS); + if (op == OP_COMPOSE_ATTACH_NEWS_MESSAGE) + { + if (!(CurrentNewsSrv = nntp_select_server (NewsServer, 0))) + break; + + prompt = _("Open newsgroup to attach message from"); + set_option (OPTNEWS); + } +#endif + if (Context) +#ifdef USE_NNTP + if ((op == OP_COMPOSE_ATTACH_MESSAGE) ^ (Context->magic == M_NNTP)) +#endif { strfcpy (fname, NONULL (Context->path), sizeof (fname)); mutt_pretty_mailbox (fname, sizeof (fname)); @@ -717,6 +876,11 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ if (mutt_enter_fname (prompt, fname, sizeof (fname), &menu->redraw, 1) == -1 || !fname[0]) break; +#ifdef USE_NNTP + if (option (OPTNEWS)) + nntp_expand_path (fname, sizeof (fname), &CurrentNewsSrv->conn->account); + else +#endif mutt_expand_path (fname, sizeof (fname)); #ifdef USE_IMAP if (!mx_is_imap (fname)) @@ -724,6 +888,9 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ #ifdef USE_POP if (!mx_is_pop (fname)) #endif +#ifdef USE_NNTP + if (!mx_is_nntp (fname) && !option (OPTNEWS)) +#endif /* check to make sure the file exists and is readable */ if (access (fname, R_OK) == -1) { diff --git a/configure.ac b/configure.ac index 2d57b76..86bd542 100644 --- a/configure.ac +++ b/configure.ac @@ -592,6 +592,15 @@ AC_ARG_ENABLE(imap, AS_HELP_STRING([--enable-imap],[Enable IMAP support]), ]) AM_CONDITIONAL(BUILD_IMAP, test x$need_imap = xyes) +AC_ARG_ENABLE(nntp, AC_HELP_STRING([--enable-nntp],[Enable NNTP support]), +[ if test x$enableval = xyes ; then + AC_DEFINE(USE_NNTP,1,[ Define if you want support for the NNTP protocol. ]) + MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS nntp.o newsrc.o" + need_nntp="yes" + need_socket="yes" + fi +]) + AC_ARG_ENABLE(smtp, AS_HELP_STRING([--enable-smtp],[include internal SMTP relay support]), [if test $enableval = yes; then AC_DEFINE(USE_SMTP, 1, [Include internal SMTP relay support]) @@ -599,7 +608,7 @@ AC_ARG_ENABLE(smtp, AS_HELP_STRING([--enable-smtp],[include internal SMTP relay need_socket="yes" fi]) -if test x"$need_imap" = xyes -o x"$need_pop" = xyes ; then +if test x"$need_imap" = xyes -o x"$need_pop" = xyes -o x"$need_nntp" = xyes ; then MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS bcache.o" fi diff --git a/curs_main.c b/curs_main.c index 5c58f1c..8cec507 100644 --- a/curs_main.c +++ b/curs_main.c @@ -22,6 +22,7 @@ #include "mutt.h" #include "mutt_curses.h" +#include "mx.h" #include "mutt_menu.h" #include "mailbox.h" #include "mapping.h" @@ -40,6 +41,10 @@ #include "mutt_crypt.h" +#ifdef USE_NNTP +#include "nntp.h" +#endif + #include #include @@ -428,12 +433,27 @@ static const struct mapping_t IndexHelp[] = { { NULL, 0 } }; +#ifdef USE_NNTP +struct mapping_t IndexNewsHelp[] = { + { N_("Quit"), OP_QUIT }, + { N_("Del"), OP_DELETE }, + { N_("Undel"), OP_UNDELETE }, + { N_("Save"), OP_SAVE }, + { N_("Post"), OP_POST }, + { N_("Followup"), OP_FOLLOWUP }, + { N_("Catchup"), OP_CATCHUP }, + { N_("Help"), OP_HELP }, + { NULL, 0 } +}; +#endif + /* This function handles the message index window as well as commands returned * from the pager (MENU_PAGER). */ int mutt_index_menu (void) { char buf[LONG_STRING], helpstr[LONG_STRING]; + int flags; int op = OP_NULL; int done = 0; /* controls when to exit the "event" loop */ int i = 0, j; @@ -454,7 +474,11 @@ int mutt_index_menu (void) menu->make_entry = index_make_entry; menu->color = index_color; menu->current = ci_first_message (); - menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_MAIN, IndexHelp); + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_MAIN, +#ifdef USE_NNTP + (Context && (Context->magic == M_NNTP)) ? IndexNewsHelp : +#endif + IndexHelp); if (!attach_msg) mutt_buffy_check(1); /* force the buffy check after we enter the folder */ @@ -710,6 +734,9 @@ int mutt_index_menu (void) mutt_curs_set (1); /* fallback from the pager */ } +#ifdef USE_NNTP + unset_option (OPTNEWS); /* for any case */ +#endif switch (op) { @@ -760,6 +787,161 @@ int mutt_index_menu (void) menu_current_bottom (menu); break; +#ifdef USE_NNTP + case OP_GET_PARENT: + CHECK_MSGCOUNT; + CHECK_VISIBLE; + + case OP_GET_MESSAGE: + CHECK_IN_MAILBOX; + CHECK_READONLY; + CHECK_ATTACH; + if (Context->magic == M_NNTP) + { + HEADER *hdr; + + if (op == OP_GET_MESSAGE) + { + buf[0] = 0; + if (mutt_get_field (_("Enter Message-Id: "), + buf, sizeof (buf), 0) != 0 || !buf[0]) + break; + } + else + { + LIST *ref = CURHDR->env->references; + if (!ref) + { + mutt_error _("Article has no parent reference."); + break; + } + strfcpy (buf, ref->data, sizeof (buf)); + } + if (!Context->id_hash) + Context->id_hash = mutt_make_id_hash (Context); + hdr = hash_find (Context->id_hash, buf); + if (hdr) + { + if (hdr->virtual != -1) + { + menu->current = hdr->virtual; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else if (hdr->collapsed) + { + mutt_uncollapse_thread (Context, hdr); + mutt_set_virtual (Context); + menu->current = hdr->virtual; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else + mutt_error _("Message is not visible in limited view."); + } + else + { + int rc; + + mutt_message (_("Fetching %s from server..."), buf); + rc = nntp_check_msgid (Context, buf); + if (rc == 0) + { + hdr = Context->hdrs[Context->msgcount - 1]; + mutt_sort_headers (Context, 0); + menu->current = hdr->virtual; + menu->redraw = REDRAW_FULL; + } + else if (rc > 0) + mutt_error (_("Article %s not found on the server."), buf); + } + } + break; + + case OP_GET_CHILDREN: + case OP_RECONSTRUCT_THREAD: + CHECK_MSGCOUNT; + CHECK_VISIBLE; + CHECK_READONLY; + CHECK_ATTACH; + if (Context->magic == M_NNTP) + { + int oldmsgcount = Context->msgcount; + int oldindex = CURHDR->index; + int rc = 0; + + if (!CURHDR->env->message_id) + { + mutt_error _("No Message-Id. Unable to perform operation."); + break; + } + + mutt_message _("Fetching message headers..."); + if (!Context->id_hash) + Context->id_hash = mutt_make_id_hash (Context); + strfcpy (buf, CURHDR->env->message_id, sizeof (buf)); + + /* trying to find msgid of the root message */ + if (op == OP_RECONSTRUCT_THREAD) + { + LIST *ref = CURHDR->env->references; + while (ref) + { + if (hash_find (Context->id_hash, ref->data) == NULL) + { + rc = nntp_check_msgid (Context, ref->data); + if (rc < 0) + break; + } + + /* the last msgid in References is the root message */ + if (!ref->next) + strfcpy (buf, ref->data, sizeof (buf)); + ref = ref->next; + } + } + + /* fetching all child messages */ + if (rc >= 0) + rc = nntp_check_children (Context, buf); + + /* at least one message has been loaded */ + if (Context->msgcount > oldmsgcount) + { + HEADER *hdr; + int i, quiet = Context->quiet; + + if (rc < 0) + Context->quiet = 1; + mutt_sort_headers (Context, (op == OP_RECONSTRUCT_THREAD)); + Context->quiet = quiet; + + /* if the root message was retrieved, move to it */ + hdr = hash_find (Context->id_hash, buf); + if (hdr) + menu->current = hdr->virtual; + + /* try to restore old position */ + else + { + for (i = 0; i < Context->msgcount; i++) + { + if (Context->hdrs[i]->index == oldindex) + { + menu->current = Context->hdrs[i]->virtual; + /* as an added courtesy, recenter the menu + * with the current entry at the middle of the screen */ + menu_check_recenter (menu); + menu_current_middle (menu); + } + } + } + menu->redraw = REDRAW_FULL; + } + else if (rc >= 0) + mutt_error _("No deleted messages found in the thread."); + } + break; +#endif + case OP_JUMP: CHECK_MSGCOUNT; @@ -856,11 +1038,33 @@ int mutt_index_menu (void) break; case OP_MAIN_LIMIT: + case OP_TOGGLE_READ: CHECK_IN_MAILBOX; menu->oldcurrent = (Context->vcount && menu->current >= 0 && menu->current < Context->vcount) ? CURHDR->index : -1; - if (mutt_pattern_func (M_LIMIT, _("Limit to messages matching: ")) == 0) + if (op == OP_TOGGLE_READ) + { + char buf[LONG_STRING]; + + if (!Context->pattern || strncmp (Context->pattern, "!~R!~D~s", 8) != 0) + { + snprintf (buf, sizeof (buf), "!~R!~D~s%s", + Context->pattern ? Context->pattern : ".*"); + set_option (OPTHIDEREAD); + } + else + { + strfcpy (buf, Context->pattern + 8, sizeof(buf)); + if (!*buf || strncmp (buf, ".*", 2) == 0) + snprintf (buf, sizeof(buf), "~A"); + unset_option (OPTHIDEREAD); + } + FREE (&Context->pattern); + Context->pattern = safe_strdup (buf); + } + if ((op == OP_TOGGLE_READ && mutt_pattern_func (M_LIMIT, NULL) == 0) || + mutt_pattern_func (M_LIMIT, _("Limit to messages matching: ")) == 0) { if (menu->oldcurrent >= 0) { @@ -1103,15 +1307,22 @@ int mutt_index_menu (void) case OP_SIDEBAR_OPEN: case OP_MAIN_CHANGE_FOLDER: case OP_MAIN_NEXT_UNREAD_MAILBOX: - - if (attach_msg) - op = OP_MAIN_CHANGE_FOLDER_READONLY; - - /* fallback to the readonly case */ - case OP_MAIN_CHANGE_FOLDER_READONLY: +#ifdef USE_NNTP + case OP_MAIN_CHANGE_GROUP: + case OP_MAIN_CHANGE_GROUP_READONLY: + unset_option (OPTNEWS); +#endif + if (attach_msg || option (OPTREADONLY) || +#ifdef USE_NNTP + op == OP_MAIN_CHANGE_GROUP_READONLY || +#endif + op == OP_MAIN_CHANGE_FOLDER_READONLY) + flags = M_READONLY; + else + flags = 0; - if ((op == OP_MAIN_CHANGE_FOLDER_READONLY) || option (OPTREADONLY)) + if (flags) cp = _("Open mailbox in read-only mode"); else cp = _("Open mailbox"); @@ -1130,6 +1341,22 @@ int mutt_index_menu (void) } else { +#ifdef USE_NNTP + if (op == OP_MAIN_CHANGE_GROUP || + op == OP_MAIN_CHANGE_GROUP_READONLY) + { + set_option (OPTNEWS); + CurrentNewsSrv = nntp_select_server (NewsServer, 0); + if (!CurrentNewsSrv) + break; + if (flags) + cp = _("Open newsgroup in read-only mode"); + else + cp = _("Open newsgroup"); + nntp_buffy (buf, sizeof (buf)); + } + else +#endif mutt_buffy (buf, sizeof (buf)); if ( op == OP_SIDEBAR_OPEN ) { @@ -1153,6 +1380,14 @@ int mutt_index_menu (void) } } +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + unset_option (OPTNEWS); + nntp_expand_path (buf, sizeof (buf), &CurrentNewsSrv->conn->account); + } + else +#endif mutt_expand_path (buf, sizeof (buf)); set_curbuffy(buf); if (mx_get_magic (buf) <= 0) @@ -1200,15 +1435,18 @@ int mutt_index_menu (void) CurrentMenu = MENU_MAIN; mutt_folder_hook (buf); - if ((Context = mx_open_mailbox (buf, - (option (OPTREADONLY) || op == OP_MAIN_CHANGE_FOLDER_READONLY) ? - M_READONLY : 0, NULL)) != NULL) + if ((Context = mx_open_mailbox (buf, flags, NULL)) != NULL) { menu->current = ci_first_message (); } else menu->current = 0; +#ifdef USE_NNTP + /* mutt_buffy_check() must be done with mail-reader mode! */ + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_MAIN, + (Context && (Context->magic == M_NNTP)) ? IndexNewsHelp : IndexHelp); +#endif mutt_clear_error (); mutt_buffy_check(1); /* force the buffy check after we have changed the folder */ @@ -1277,6 +1515,7 @@ int mutt_index_menu (void) CHECK_MSGCOUNT; CHECK_VISIBLE; CHECK_READONLY; + CHECK_ACL(M_ACL_WRITE, _("break thread")); if ((Sort & SORT_MASK) != SORT_THREADS) mutt_error _("Threading is not enabled."); @@ -1311,7 +1550,7 @@ int mutt_index_menu (void) CHECK_MSGCOUNT; CHECK_VISIBLE; CHECK_READONLY; - CHECK_ACL(M_ACL_DELETE, _("link threads")); + CHECK_ACL(M_ACL_WRITE, _("link threads")); if ((Sort & SORT_MASK) != SORT_THREADS) mutt_error _("Threading is not enabled."); @@ -1932,6 +2171,20 @@ int mutt_index_menu (void) } break; +#ifdef USE_NNTP + case OP_CATCHUP: + CHECK_MSGCOUNT; + CHECK_READONLY; + CHECK_ATTACH + if (Context && Context->magic == M_NNTP) + { + NNTP_DATA *nntp_data = Context->data; + if (mutt_newsgroup_catchup (nntp_data->nserv, nntp_data->group)) + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + } + break; +#endif + case OP_DISPLAY_ADDRESS: CHECK_MSGCOUNT; @@ -2136,6 +2389,39 @@ int mutt_index_menu (void) menu->redraw = REDRAW_FULL; break; +#ifdef USE_NNTP + case OP_FOLLOWUP: + case OP_FORWARD_TO_GROUP: + + CHECK_MSGCOUNT; + CHECK_VISIBLE; + + case OP_POST: + + CHECK_ATTACH; + if (op != OP_FOLLOWUP || !CURHDR->env->followup_to || + mutt_strcasecmp (CURHDR->env->followup_to, "poster") || + query_quadoption (OPT_FOLLOWUPTOPOSTER, + _("Reply by mail as poster prefers?")) != M_YES) + { + if (Context && Context->magic == M_NNTP && + !((NNTP_DATA *)Context->data)->allowed && + query_quadoption (OPT_TOMODERATED, + _("Posting to this group not allowed, may be moderated. Continue?")) != M_YES) + break; + if (op == OP_POST) + ci_send_message (SENDNEWS, NULL, NULL, Context, NULL); + else + { + CHECK_MSGCOUNT; + ci_send_message ((op == OP_FOLLOWUP ? SENDREPLY : SENDFORWARD) | + SENDNEWS, NULL, NULL, Context, tag ? NULL : CURHDR); + } + menu->redraw = REDRAW_FULL; + break; + } +#endif + case OP_REPLY: CHECK_ATTACH; diff --git a/doc/Muttrc b/doc/Muttrc index bf0e6d0..e03da88 100644 --- a/doc/Muttrc +++ b/doc/Muttrc @@ -240,6 +240,28 @@ attachments -I message/external-body # editing the body of an outgoing message. # # +# set ask_follow_up=no +# +# Name: ask_follow_up +# Type: boolean +# Default: no +# +# +# If set, Mutt will prompt you for follow-up groups before editing +# the body of an outgoing message. +# +# +# set ask_x_comment_to=no +# +# Name: ask_x_comment_to +# Type: boolean +# Default: no +# +# +# If set, Mutt will prompt you for x-comment-to field before editing +# the body of an outgoing message. +# +# # set assumed_charset="" # # Name: assumed_charset @@ -445,6 +467,17 @@ attachments -I message/external-body # visual terminals don't permit making the cursor invisible. # # +# set catchup_newsgroup=ask-yes +# +# Name: catchup_newsgroup +# Type: quadoption +# Default: ask-yes +# +# +# If this variable is set, Mutt will mark all articles in newsgroup +# as read when you quit the newsgroup (catchup newsgroup). +# +# # set certificate_file="~/.mutt_certificates" # # Name: certificate_file @@ -1131,6 +1164,19 @@ attachments -I message/external-body # of the same email for you. # # +# set followup_to_poster=ask-yes +# +# Name: followup_to_poster +# Type: quadoption +# Default: ask-yes +# +# +# If this variable is set and the keyword "poster" is present in +# Followup-To header, follow-up to newsgroup function is not +# permitted. The message will be mailed to the submitter of the +# message via mail. +# +# # set force_name=no # # Name: force_name @@ -1241,6 +1287,27 @@ attachments -I message/external-body # ``Franklin'' to ``Franklin, Steve''. # # +# set group_index_format="%4C %M%N %5s %-45.45f %d" +# +# Name: group_index_format +# Type: string +# Default: "%4C %M%N %5s %-45.45f %d" +# +# +# This variable allows you to customize the newsgroup browser display to +# your personal taste. This string is similar to ``index_format'', but +# has its own set of printf()-like sequences: +# %C current newsgroup number +# %d description of newsgroup (becomes from server) +# %f newsgroup name +# %M - if newsgroup not allowed for direct post (moderated for example) +# %N N if newsgroup is new, u if unsubscribed, blank otherwise +# %n number of new articles in newsgroup +# %s number of unread articles in newsgroup +# %>X right justify the rest of the string and pad with character "X" +# %|X pad to the end of the line with character "X" +# +# # set hdrs=yes # # Name: hdrs @@ -1789,6 +1856,7 @@ attachments -I message/external-body # %E number of messages in current thread # %f sender (address + real name), either From: or Return-Path: # %F author name, or recipient name if the message is from you +# %g newsgroup name (if compiled with NNTP support) # %H spam attribute(s) of this message # %i message-id of the current message # %l number of lines in the message (does not work with maildir, @@ -1804,12 +1872,14 @@ attachments -I message/external-body # stashed the message: list name or recipient name # if not sent to a list # %P progress indicator for the built-in pager (how much of the file has been displayed) +# %R ``X-Comment-To:'' field (if present and compiled with NNTP support) # %s subject of the message # %S status of the message (``N''/``D''/``d''/``!''/``r''/*) # %t ``To:'' field (recipients) # %T the appropriate character from the $to_chars string # %u user (login) name of the author # %v first name of the author, or the recipient if the message is from you +# %W name of organization of author (``Organization:'' field) # %X number of attachments # (please see the ``attachments'' section for possible speed effects) # %y ``X-Label:'' field, if present @@ -1845,6 +1915,27 @@ attachments -I message/external-body # ``save-hook'', ``fcc-hook'' and ``fcc-save-hook'', too. # # +# set inews="" +# +# Name: inews +# Type: path +# Default: "" +# +# +# If set, specifies the program and arguments used to deliver news posted +# by Mutt. Otherwise, mutt posts article using current connection to +# news server. The following printf-style sequence is understood: +# %a account url +# %p port +# %P port if specified +# %s news server name +# %S url schema +# %u username +# +# +# Example: set inews="/usr/local/bin/inews -hS" +# +# # set ispell="ispell" # # Name: ispell @@ -2214,6 +2305,18 @@ attachments -I message/external-body # be attached to the newly composed message if this option is set. # # +# set mime_subject=yes +# +# Name: mime_subject +# Type: boolean +# Default: yes +# +# +# If unset, 8-bit ``subject:'' line in article header will not be +# encoded according to RFC2047 to base64. This is useful when message +# is Usenet article, because MIME for news is nonstandard feature. +# +# # set mix_entry_format="%4n %c %-16s %a" # # Name: mix_entry_format @@ -2280,6 +2383,144 @@ attachments -I message/external-body # See also $read_inc, $write_inc and $net_inc. # # +# set news_cache_dir="~/.mutt" +# +# Name: news_cache_dir +# Type: path +# Default: "~/.mutt" +# +# +# This variable pointing to directory where Mutt will save cached news +# articles and headers in. If unset, articles and headers will not be +# saved at all and will be reloaded from the server each time. +# +# +# set news_server="" +# +# Name: news_server +# Type: string +# Default: "" +# +# +# This variable specifies domain name or address of NNTP server. It +# defaults to the news server specified in the environment variable +# $NNTPSERVER or contained in the file /etc/nntpserver. You can also +# specify username and an alternative port for each news server, ie: +# +# [[s]news://][username[:password]@]server[:port] +# +# +# set newsgroups_charset="utf-8" +# +# Name: newsgroups_charset +# Type: string +# Default: "utf-8" +# +# +# Character set of newsgroups descriptions. +# +# +# set newsrc="~/.newsrc" +# +# Name: newsrc +# Type: path +# Default: "~/.newsrc" +# +# +# The file, containing info about subscribed newsgroups - names and +# indexes of read articles. The following printf-style sequence +# is understood: +# %a account url +# %p port +# %P port if specified +# %s news server name +# %S url schema +# %u username +# +# +# set nntp_authenticators="" +# +# Name: nntp_authenticators +# Type: string +# Default: "" +# +# +# This is a colon-delimited list of authentication methods mutt may +# attempt to use to log in to a news server, in the order mutt should +# try them. Authentication methods are either ``user'' or any +# SASL mechanism, e.g. ``digest-md5'', ``gssapi'' or ``cram-md5''. +# This option is case-insensitive. If it's unset (the default) +# mutt will try all available methods, in order from most-secure to +# least-secure. +# +# Example: +# set nntp_authenticators="digest-md5:user" +# +# Note: Mutt will only fall back to other authentication methods if +# the previous methods are unavailable. If a method is available but +# authentication fails, mutt will not connect to the IMAP server. +# +# +# set nntp_context=1000 +# +# Name: nntp_context +# Type: number +# Default: 1000 +# +# +# This variable defines number of articles which will be in index when +# newsgroup entered. If active newsgroup have more articles than this +# number, oldest articles will be ignored. Also controls how many +# articles headers will be saved in cache when you quit newsgroup. +# +# +# set nntp_load_description=yes +# +# Name: nntp_load_description +# Type: boolean +# Default: yes +# +# +# This variable controls whether or not descriptions for each newsgroup +# must be loaded when newsgroup is added to list (first time list +# loading or new newsgroup adding). +# +# +# set nntp_user="" +# +# Name: nntp_user +# Type: string +# Default: "" +# +# +# Your login name on the NNTP server. If unset and NNTP server requires +# authentification, Mutt will prompt you for your account name when you +# connect to news server. +# +# +# set nntp_pass="" +# +# Name: nntp_pass +# Type: string +# Default: "" +# +# +# Your password for NNTP account. +# +# +# set nntp_poll=60 +# +# Name: nntp_poll +# Type: number +# Default: 60 +# +# +# The time in seconds until any operations on newsgroup except post new +# article will cause recheck for new news. If set to 0, Mutt will +# recheck newsgroup on each operation in index (stepping, read article, +# etc.). +# +# # set pager="builtin" # # Name: pager @@ -2997,6 +3238,19 @@ attachments -I message/external-body # string after the inclusion of a message which is being replied to. # # +# set post_moderated=ask-yes +# +# Name: post_moderated +# Type: quadoption +# Default: ask-yes +# +# +# If set to yes, Mutt will post article to newsgroup that have +# not permissions to posting (e.g. moderated). Note: if news server +# does not support posting to that newsgroup or totally read-only, that +# posting will not have an effect. +# +# # set postpone=ask-yes # # Name: postpone @@ -3605,6 +3859,41 @@ attachments -I message/external-body # shell from /etc/passwd is used. # # +# set save_unsubscribed=no +# +# Name: save_unsubscribed +# Type: boolean +# Default: no +# +# +# When set, info about unsubscribed newsgroups will be saved into +# ``newsrc'' file and into cache. +# +# +# set show_new_news=yes +# +# Name: show_new_news +# Type: boolean +# Default: yes +# +# +# If set, news server will be asked for new newsgroups on entering +# the browser. Otherwise, it will be done only once for a news server. +# Also controls whether or not number of new articles of subscribed +# newsgroups will be then checked. +# +# +# set show_only_unread=no +# +# Name: show_only_unread +# Type: boolean +# Default: no +# +# +# If set, only subscribed newsgroups that contain unread articles +# will be displayed in browser. +# +# # set sig_dashes=yes # # Name: sig_dashes @@ -4851,3 +5140,14 @@ attachments -I message/external-body # ``tuning'' section of the manual for performance considerations. # # +# set x_comment_to=no +# +# Name: x_comment_to +# Type: boolean +# Default: no +# +# +# If set, Mutt will add ``X-Comment-To:'' field (that contains full +# name of original article author) to article that followuped to newsgroup. +# +# diff --git a/doc/manual.xml.head b/doc/manual.xml.head index 4366758..5b523ce 100644 --- a/doc/manual.xml.head +++ b/doc/manual.xml.head @@ -1694,6 +1694,26 @@ See also the $postpone quad-option. + +Reading news via NNTP + + +If compiled with --enable-nntp option, Mutt can +read news from news server via NNTP. You can open a newsgroup with +function ``change-newsgroup'' (default: ``i''). Default news server +can be obtained from $NNTPSERVER environment +variable or from /etc/nntpserver file. Like other +news readers, info about subscribed newsgroups is saved in file by +$newsrc variable. The variable $news_cache_dir can be used to point +to a directory. Mutt will create a hierarchy of subdirectories named +like the account and newsgroup the cache is for. Also the hierarchy +is used to store header cache if Mutt was compiled with header cache support. + + + + diff --git a/doc/mutt.man b/doc/mutt.man index 718f87a..c4f44d1 100644 --- a/doc/mutt.man +++ b/doc/mutt.man @@ -23,8 +23,8 @@ mutt \- The Mutt Mail User Agent .SH SYNOPSIS .PP .B mutt -[\-nRyzZ] -[\-e \fIcmd\fP] [\-F \fIfile\fP] [\-m \fItype\fP] [\-f \fIfile\fP] +[\-GnRyzZ] +[\-e \fIcmd\fP] [\-F \fIfile\fP] [\-g \fIserver\fP] [\-m \fItype\fP] [\-f \fIfile\fP] .PP .B mutt [\-nx] @@ -101,6 +101,10 @@ files. Specify which mailbox to load. .IP "-F \fImuttrc\fP" Specify an initialization file to read instead of ~/.muttrc +.IP "-g \fIserver\fP" +Start Mutt with a listing of subscribed newsgroups at specified news server. +.IP "-G" +Start Mutt with a listing of subscribed newsgroups. .IP "-h" Display help. .IP "-H \fIdraft\fP" diff --git a/functions.h b/functions.h index 363b4d5..66f3345 100644 --- a/functions.h +++ b/functions.h @@ -88,6 +88,10 @@ const struct binding_t OpMain[] = { /* map: index */ { "break-thread", OP_MAIN_BREAK_THREAD, "#" }, { "change-folder", OP_MAIN_CHANGE_FOLDER, "c" }, { "change-folder-readonly", OP_MAIN_CHANGE_FOLDER_READONLY, "\033c" }, +#ifdef USE_NNTP + { "change-newsgroup", OP_MAIN_CHANGE_GROUP, "i" }, + { "change-newsgroup-readonly",OP_MAIN_CHANGE_GROUP_READONLY, "\033i" }, +#endif { "next-unread-mailbox", OP_MAIN_NEXT_UNREAD_MAILBOX, NULL }, { "collapse-thread", OP_MAIN_COLLAPSE_THREAD, "\033v" }, { "collapse-all", OP_MAIN_COLLAPSE_ALL, "\033V" }, @@ -101,7 +105,15 @@ const struct binding_t OpMain[] = { /* map: index */ { "edit", OP_EDIT_MESSAGE, "e" }, { "edit-type", OP_EDIT_TYPE, "\005" }, { "forward-message", OP_FORWARD_MESSAGE, "f" }, - { "flag-message", OP_FLAG_MESSAGE, "F" }, +#ifdef USE_NNTP + { "forward-to-group", OP_FORWARD_TO_GROUP, "\033F" }, + { "followup-message", OP_FOLLOWUP, "F" }, + { "get-children", OP_GET_CHILDREN, NULL }, + { "get-message", OP_GET_MESSAGE, "\007" }, + { "get-parent", OP_GET_PARENT, "\033G" }, + { "reconstruct-thread", OP_RECONSTRUCT_THREAD, NULL }, +#endif + { "flag-message", OP_FLAG_MESSAGE, "\033f" }, { "group-reply", OP_GROUP_REPLY, "g" }, #ifdef USE_POP { "fetch-mail", OP_MAIN_FETCH_MAIL, "G" }, @@ -129,6 +141,9 @@ const struct binding_t OpMain[] = { /* map: index */ { "sort-mailbox", OP_SORT, "o" }, { "sort-reverse", OP_SORT_REVERSE, "O" }, { "print-message", OP_PRINT, "p" }, +#ifdef USE_NNTP + { "post-message", OP_POST, "P" }, +#endif { "previous-thread", OP_MAIN_PREV_THREAD, "\020" }, { "previous-subthread", OP_MAIN_PREV_SUBTHREAD, "\033p" }, { "recall-message", OP_RECALL_MESSAGE, "R" }, @@ -148,6 +163,10 @@ const struct binding_t OpMain[] = { /* map: index */ { "show-version", OP_VERSION, "V" }, { "set-flag", OP_MAIN_SET_FLAG, "w" }, { "clear-flag", OP_MAIN_CLEAR_FLAG, "W" }, + { "toggle-read", OP_TOGGLE_READ, "X" }, +#ifdef USE_NNTP + { "catchup", OP_CATCHUP, "y" }, +#endif { "display-message", OP_DISPLAY_MESSAGE, M_ENTER_S }, { "buffy-list", OP_BUFFY_LIST, "." }, { "sync-mailbox", OP_MAIN_SYNC_FOLDER, "$" }, @@ -159,7 +178,7 @@ const struct binding_t OpMain[] = { /* map: index */ { "previous-new-then-unread", OP_MAIN_PREV_NEW_THEN_UNREAD, "\033\t" }, { "next-unread", OP_MAIN_NEXT_UNREAD, NULL }, { "previous-unread", OP_MAIN_PREV_UNREAD, NULL }, - { "parent-message", OP_MAIN_PARENT_MESSAGE, "P" }, + { "parent-message", OP_MAIN_PARENT_MESSAGE, NULL }, { "extract-keys", OP_EXTRACT_KEYS, "\013" }, @@ -186,6 +205,10 @@ const struct binding_t OpPager[] = { /* map: pager */ { "bounce-message", OP_BOUNCE_MESSAGE, "b" }, { "change-folder", OP_MAIN_CHANGE_FOLDER, "c" }, { "change-folder-readonly", OP_MAIN_CHANGE_FOLDER_READONLY, "\033c" }, +#ifdef USE_NNTP + { "change-newsgroup", OP_MAIN_CHANGE_GROUP, "i" }, + { "change-newsgroup-readonly",OP_MAIN_CHANGE_GROUP_READONLY, "\033i" }, +#endif { "next-unread-mailbox", OP_MAIN_NEXT_UNREAD_MAILBOX, NULL }, { "copy-message", OP_COPY_MESSAGE, "C" }, { "decode-copy", OP_DECODE_COPY, "\033C" }, @@ -196,8 +219,12 @@ const struct binding_t OpPager[] = { /* map: pager */ { "clear-flag", OP_MAIN_CLEAR_FLAG, "W" }, { "edit", OP_EDIT_MESSAGE, "e" }, { "edit-type", OP_EDIT_TYPE, "\005" }, +#ifdef USE_NNTP + { "followup-message", OP_FOLLOWUP, "F" }, + { "forward-to-group", OP_FORWARD_TO_GROUP, "\033F" }, +#endif { "forward-message", OP_FORWARD_MESSAGE, "f" }, - { "flag-message", OP_FLAG_MESSAGE, "F" }, + { "flag-message", OP_FLAG_MESSAGE, "\033f" }, { "group-reply", OP_GROUP_REPLY, "g" }, #ifdef USE_IMAP { "imap-fetch-mail", OP_MAIN_IMAP_FETCH, NULL }, @@ -219,6 +246,9 @@ const struct binding_t OpPager[] = { /* map: pager */ { "sort-mailbox", OP_SORT, "o" }, { "sort-reverse", OP_SORT_REVERSE, "O" }, { "print-message", OP_PRINT, "p" }, +#ifdef USE_NNTP + { "post-message", OP_POST, "P" }, +#endif { "previous-thread", OP_MAIN_PREV_THREAD, "\020" }, { "previous-subthread",OP_MAIN_PREV_SUBTHREAD, "\033p" }, { "purge-message", OP_PURGE_MESSAGE, NULL }, @@ -267,7 +297,7 @@ const struct binding_t OpPager[] = { /* map: pager */ { "half-down", OP_HALF_DOWN, NULL }, { "previous-line", OP_PREV_LINE, NULL }, { "bottom", OP_PAGER_BOTTOM, NULL }, - { "parent-message", OP_MAIN_PARENT_MESSAGE, "P" }, + { "parent-message", OP_MAIN_PARENT_MESSAGE, NULL }, @@ -295,6 +325,10 @@ const struct binding_t OpAttach[] = { /* map: attachment */ { "bounce-message", OP_BOUNCE_MESSAGE, "b" }, { "display-toggle-weed", OP_DISPLAY_HEADERS, "h" }, { "edit-type", OP_EDIT_TYPE, "\005" }, +#ifdef USE_NNTP + { "followup-message", OP_FOLLOWUP, "F" }, + { "forward-to-group", OP_FORWARD_TO_GROUP, "\033F" }, +#endif { "print-entry", OP_PRINT, "p" }, { "save-entry", OP_SAVE, "s" }, { "pipe-entry", OP_PIPE, "|" }, @@ -320,6 +354,7 @@ const struct binding_t OpAttach[] = { /* map: attachment */ const struct binding_t OpCompose[] = { /* map: compose */ { "attach-file", OP_COMPOSE_ATTACH_FILE, "a" }, { "attach-message", OP_COMPOSE_ATTACH_MESSAGE, "A" }, + { "attach-news-message",OP_COMPOSE_ATTACH_NEWS_MESSAGE,"\033a" }, { "edit-bcc", OP_COMPOSE_EDIT_BCC, "b" }, { "edit-cc", OP_COMPOSE_EDIT_CC, "c" }, { "copy-file", OP_SAVE, "C" }, @@ -339,6 +374,11 @@ const struct binding_t OpCompose[] = { /* map: compose */ { "print-entry", OP_PRINT, "l" }, { "edit-mime", OP_COMPOSE_EDIT_MIME, "m" }, { "new-mime", OP_COMPOSE_NEW_MIME, "n" }, +#ifdef USE_NNTP + { "edit-newsgroups", OP_COMPOSE_EDIT_NEWSGROUPS, "N" }, + { "edit-followup-to", OP_COMPOSE_EDIT_FOLLOWUP_TO, "o" }, + { "edit-x-comment-to",OP_COMPOSE_EDIT_X_COMMENT_TO, "x" }, +#endif { "postpone-message", OP_COMPOSE_POSTPONE_MESSAGE, "P" }, { "edit-reply-to", OP_COMPOSE_EDIT_REPLY_TO, "r" }, { "rename-file", OP_COMPOSE_RENAME_FILE, "R" }, @@ -390,14 +430,25 @@ const struct binding_t OpBrowser[] = { /* map: browser */ { "select-new", OP_BROWSER_NEW_FILE, "N" }, { "check-new", OP_CHECK_NEW, NULL }, { "toggle-mailboxes", OP_TOGGLE_MAILBOXES, "\t" }, +#ifdef USE_NNTP + { "reload-active", OP_LOAD_ACTIVE, "g" }, + { "subscribe-pattern", OP_SUBSCRIBE_PATTERN, "S" }, + { "unsubscribe-pattern", OP_UNSUBSCRIBE_PATTERN, "U" }, + { "catchup", OP_CATCHUP, "y" }, + { "uncatchup", OP_UNCATCHUP, "Y" }, +#endif { "view-file", OP_BROWSER_VIEW_FILE, " " }, { "buffy-list", OP_BUFFY_LIST, "." }, #ifdef USE_IMAP { "create-mailbox", OP_CREATE_MAILBOX, "C" }, { "delete-mailbox", OP_DELETE_MAILBOX, "d" }, { "rename-mailbox", OP_RENAME_MAILBOX, "r" }, +#endif +#if defined USE_IMAP || defined USE_NNTP { "subscribe", OP_BROWSER_SUBSCRIBE, "s" }, { "unsubscribe", OP_BROWSER_UNSUBSCRIBE, "u" }, +#endif +#ifdef USE_IMAP { "toggle-subscribed", OP_BROWSER_TOGGLE_LSUB, "T" }, #endif { NULL, 0, NULL } diff --git a/globals.h b/globals.h index 61765a4..0529f94 100644 --- a/globals.h +++ b/globals.h @@ -95,6 +95,17 @@ WHERE char *MixEntryFormat; #endif WHERE char *Muttrc INITVAL (NULL); +#ifdef USE_NNTP +WHERE char *GroupFormat; +WHERE char *Inews; +WHERE char *NewsCacheDir; +WHERE char *NewsServer; +WHERE char *NewsgroupsCharset; +WHERE char *NewsRc; +WHERE char *NntpAuthenticators; +WHERE char *NntpUser; +WHERE char *NntpPass; +#endif WHERE char *Outbox; WHERE char *Pager; WHERE char *PagerFmt; @@ -193,6 +204,11 @@ extern unsigned char QuadOptions[]; WHERE unsigned short Counter INITVAL (0); +#ifdef USE_NNTP +WHERE short NewsPollTimeout; +WHERE short NntpContext; +#endif + WHERE short ConnectTimeout; WHERE short HistSize; WHERE short MenuContext; diff --git a/hash.c b/hash.c index 08f7171..983a7fd 100644 --- a/hash.c +++ b/hash.c @@ -57,6 +57,7 @@ HASH *hash_create (int nelem, int lower) if (nelem == 0) nelem = 2; table->nelem = nelem; + table->curnelem = 0; table->table = safe_calloc (nelem, sizeof (struct hash_elem *)); if (lower) { @@ -71,6 +72,29 @@ HASH *hash_create (int nelem, int lower) return table; } +HASH *hash_resize (HASH *ptr, int nelem, int lower) +{ + HASH *table; + struct hash_elem *elem, *tmp; + int i; + + table = hash_create (nelem, lower); + + for (i = 0; i < ptr->nelem; i++) + { + for (elem = ptr->table[i]; elem; ) + { + tmp = elem; + elem = elem->next; + hash_insert (table, tmp->key, tmp->data, 1); + FREE (&tmp); + } + } + FREE (&ptr->table); + FREE (&ptr); + return table; +} + /* table hash table to update * key key to hash on * data data to associate with `key' @@ -90,6 +114,7 @@ int hash_insert (HASH * table, const char *key, void *data, int allow_dup) { ptr->next = table->table[h]; table->table[h] = ptr; + table->curnelem++; } else { @@ -112,6 +137,7 @@ int hash_insert (HASH * table, const char *key, void *data, int allow_dup) else table->table[h] = ptr; ptr->next = tmp; + table->curnelem++; } return h; } @@ -142,6 +168,7 @@ void hash_delete_hash (HASH * table, int hash, const char *key, const void *data if (destroy) destroy (ptr->data); FREE (&ptr); + table->curnelem--; ptr = *last; } diff --git a/hash.h b/hash.h index fb77d0c..9e7df82 100644 --- a/hash.h +++ b/hash.h @@ -28,7 +28,7 @@ struct hash_elem typedef struct { - int nelem; + int nelem, curnelem; struct hash_elem **table; unsigned int (*hash_string)(const unsigned char *, unsigned int); int (*cmp_string)(const char *, const char *); @@ -41,6 +41,7 @@ HASH; HASH *hash_create (int nelem, int lower); int hash_insert (HASH * table, const char *key, void *data, int allow_dup); +HASH *hash_resize (HASH * table, int nelem, int lower); void *hash_find_hash (const HASH * table, int hash, const char *key); void hash_delete_hash (HASH * table, int hash, const char *key, const void *data, void (*destroy) (void *)); diff --git a/hcache.c b/hcache.c index af17932..555e1d1 100644 --- a/hcache.c +++ b/hcache.c @@ -447,6 +447,12 @@ dump_envelope(ENVELOPE * e, unsigned char *d, int *off, int convert) d = dump_list(e->in_reply_to, d, off, 0); d = dump_list(e->userhdrs, d, off, convert); +#ifdef USE_NNTP + d = dump_char(e->xref, d, off, 0); + d = dump_char(e->followup_to, d, off, 0); + d = dump_char(e->x_comment_to, d, off, convert); +#endif + return d; } @@ -483,6 +489,12 @@ restore_envelope(ENVELOPE * e, const unsigned char *d, int *off, int convert) restore_list(&e->references, d, off, 0); restore_list(&e->in_reply_to, d, off, 0); restore_list(&e->userhdrs, d, off, convert); + +#ifdef USE_NNTP + restore_char(&e->xref, d, off, 0); + restore_char(&e->followup_to, d, off, 0); + restore_char(&e->x_comment_to, d, off, convert); +#endif } static int diff --git a/hdrline.c b/hdrline.c index 21adc28..c05d28a 100644 --- a/hdrline.c +++ b/hdrline.c @@ -211,6 +211,7 @@ int mutt_user_is_recipient (HEADER *h) * %E = number of messages in current thread * %f = entire from line * %F = like %n, unless from self + * %g = newsgroup name (if compiled with NNTP support) * %i = message-id * %l = number of lines in the message * %L = like %F, except `lists' are displayed first @@ -219,12 +220,14 @@ int mutt_user_is_recipient (HEADER *h) * %N = score * %O = like %L, except using address instead of name * %P = progress indicator for builtin pager + * %R = `x-comment-to:' field (if present and compiled with NNTP support) * %s = subject * %S = short message status (e.g., N/O/D/!/r/-) * %t = `to:' field (recipients) * %T = $to_chars * %u = user (login) name of author * %v = first name of author, unless from self + * %W = where user is (organization) * %X = number of MIME attachments * %y = `x-label:' field (if present) * %Y = `x-label:' field (if present, tree unfolded, and != parent's x-label) @@ -457,6 +460,12 @@ hdr_format_str (char *dest, break; +#ifdef USE_NNTP + case 'g': + mutt_format_s (dest, destlen, prefix, hdr->env->newsgroups ? hdr->env->newsgroups : ""); + break; +#endif + case 'i': mutt_format_s (dest, destlen, prefix, hdr->env->message_id ? hdr->env->message_id : ""); break; @@ -548,6 +557,15 @@ hdr_format_str (char *dest, strfcpy(dest, NONULL(hfi->pager_progress), destlen); break; +#ifdef USE_NNTP + case 'R': + if (!optional) + mutt_format_s (dest, destlen, prefix, hdr->env->x_comment_to ? hdr->env->x_comment_to : ""); + else if (!hdr->env->x_comment_to) + optional = 0; + break; +#endif + case 's': if (flags & M_FORMAT_TREE && !hdr->collapsed) @@ -637,6 +655,13 @@ hdr_format_str (char *dest, mutt_format_s (dest, destlen, prefix, buf2); break; + case 'W': + if (!optional) + mutt_format_s (dest, destlen, prefix, hdr->env->organization ? hdr->env->organization : ""); + else if (!hdr->env->organization) + optional = 0; + break; + case 'Z': ch = ' '; diff --git a/headers.c b/headers.c index f701c8e..e817dad 100644 --- a/headers.c +++ b/headers.c @@ -114,6 +114,9 @@ void mutt_edit_headers (const char *editor, $edit_headers set, we remove References: as they're likely invalid; we can simply compare strings as we don't generate References for multiple Message-Ids in IRT anyways */ +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif if (msg->env->in_reply_to && (!n->in_reply_to || mutt_strcmp (n->in_reply_to->data, msg->env->in_reply_to->data) != 0)) diff --git a/init.c b/init.c index 20a66bd..6ab1020 100644 --- a/init.c +++ b/init.c @@ -3104,6 +3104,28 @@ void mutt_init (int skip_sys_rc, LIST *commands) else Fqdn = safe_strdup(NONULL(Hostname)); +#ifdef USE_NNTP + { + FILE *f; + char *i; + + if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) + { + buffer[0] = '\0'; + fgets (buffer, sizeof (buffer), f); + p = &buffer; + SKIPWS (p); + i = p; + while (*i && (*i != ' ') && (*i != '\t') && (*i != '\r') && (*i != '\n')) i++; + *i = '\0'; + NewsServer = safe_strdup (p); + fclose (f); + } + } + if ((p = getenv ("NNTPSERVER"))) + NewsServer = safe_strdup (p); +#endif + if ((p = getenv ("MAIL"))) Spoolfile = safe_strdup (p); else if ((p = getenv ("MAILDIR"))) diff --git a/init.h b/init.h index e20a24e..6efadb2 100644 --- a/init.h +++ b/init.h @@ -176,6 +176,20 @@ struct option_t MuttVars[] = { ** If \fIset\fP, Mutt will prompt you for carbon-copy (Cc) recipients before ** editing the body of an outgoing message. */ +#ifdef USE_NNTP + { "ask_follow_up", DT_BOOL, R_NONE, OPTASKFOLLOWUP, 0 }, + /* + ** .pp + ** If set, Mutt will prompt you for follow-up groups before editing + ** the body of an outgoing message. + */ + { "ask_x_comment_to", DT_BOOL, R_NONE, OPTASKXCOMMENTTO, 0 }, + /* + ** .pp + ** If set, Mutt will prompt you for x-comment-to field before editing + ** the body of an outgoing message. + */ +#endif { "assumed_charset", DT_STR, R_NONE, UL &AssumedCharset, UL 0}, /* ** .pp @@ -328,6 +342,14 @@ struct option_t MuttVars[] = { ** follow these menus. The option is \fIunset\fP by default because many ** visual terminals don't permit making the cursor invisible. */ +#ifdef USE_NNTP + { "catchup_newsgroup", DT_QUAD, R_NONE, OPT_CATCHUP, M_ASKYES }, + /* + ** .pp + ** If this variable is \fIset\fP, Mutt will mark all articles in newsgroup + ** as read when you quit the newsgroup (catchup newsgroup). + */ +#endif #if defined(USE_SSL) { "certificate_file", DT_PATH, R_NONE, UL &SslCertFile, UL "~/.mutt_certificates" }, /* @@ -815,6 +837,16 @@ struct option_t MuttVars[] = { ** sent to both the list and your address, resulting in two copies ** of the same email for you. */ +#ifdef USE_NNTP + { "followup_to_poster", DT_QUAD, R_NONE, OPT_FOLLOWUPTOPOSTER, M_ASKYES }, + /* + ** .pp + ** If this variable is \fIset\fP and the keyword "poster" is present in + ** \fIFollowup-To\fP header, follow-up to newsgroup function is not + ** permitted. The message will be mailed to the submitter of the + ** message via mail. + */ +#endif { "force_name", DT_BOOL, R_NONE, OPTFORCENAME, 0 }, /* ** .pp @@ -897,6 +929,26 @@ struct option_t MuttVars[] = { ** a regular expression that will match the whole name so mutt will expand ** ``Franklin'' to ``Franklin, Steve''. */ +#ifdef USE_NNTP + { "group_index_format", DT_STR, R_BOTH, UL &GroupFormat, UL "%4C %M%N %5s %-45.45f %d" }, + /* + ** .pp + ** This variable allows you to customize the newsgroup browser display to + ** your personal taste. This string is similar to ``$index_format'', but + ** has its own set of printf()-like sequences: + ** .dl + ** .dt %C .dd current newsgroup number + ** .dt %d .dd description of newsgroup (becomes from server) + ** .dt %f .dd newsgroup name + ** .dt %M .dd - if newsgroup not allowed for direct post (moderated for example) + ** .dt %N .dd N if newsgroup is new, u if unsubscribed, blank otherwise + ** .dt %n .dd number of new articles in newsgroup + ** .dt %s .dd number of unread articles in newsgroup + ** .dt %>X .dd right justify the rest of the string and pad with character "X" + ** .dt %|X .dd pad to the end of the line with character "X" + ** .de + */ +#endif { "hdr_format", DT_SYN, R_NONE, UL "index_format", 0 }, /* */ @@ -1278,6 +1330,7 @@ struct option_t MuttVars[] = { ** .dt %E .dd number of messages in current thread ** .dt %f .dd sender (address + real name), either From: or Return-Path: ** .dt %F .dd author name, or recipient name if the message is from you + ** .dt %g .dd newsgroup name (if compiled with NNTP support) ** .dt %H .dd spam attribute(s) of this message ** .dt %i .dd message-id of the current message ** .dt %l .dd number of lines in the message (does not work with maildir, @@ -1293,12 +1346,14 @@ struct option_t MuttVars[] = { ** stashed the message: list name or recipient name ** if not sent to a list ** .dt %P .dd progress indicator for the built-in pager (how much of the file has been displayed) + ** .dt %R .dd ``X-Comment-To:'' field (if present and compiled with NNTP support) ** .dt %s .dd subject of the message ** .dt %S .dd status of the message (``N''/``D''/``d''/``!''/``r''/\(as) ** .dt %t .dd ``To:'' field (recipients) ** .dt %T .dd the appropriate character from the $$to_chars string ** .dt %u .dd user (login) name of the author ** .dt %v .dd first name of the author, or the recipient if the message is from you + ** .dt %W .dd name of organization of author (``Organization:'' field) ** .dt %X .dd number of attachments ** (please see the ``$attachments'' section for possible speed effects) ** .dt %y .dd ``X-Label:'' field, if present @@ -1333,6 +1388,25 @@ struct option_t MuttVars[] = { ** Note that these expandos are supported in ** ``$save-hook'', ``$fcc-hook'' and ``$fcc-save-hook'', too. */ +#ifdef USE_NNTP + { "inews", DT_PATH, R_NONE, UL &Inews, UL "" }, + /* + ** .pp + ** If set, specifies the program and arguments used to deliver news posted + ** by Mutt. Otherwise, mutt posts article using current connection to + ** news server. The following printf-style sequence is understood: + ** .dl + ** .dt %a .dd account url + ** .dt %p .dd port + ** .dt %P .dd port if specified + ** .dt %s .dd news server name + ** .dt %S .dd url schema + ** .dt %u .dd username + ** .de + ** .pp + ** Example: set inews="/usr/local/bin/inews -hS" + */ +#endif { "ispell", DT_PATH, R_NONE, UL &Ispell, UL ISPELL }, /* ** .pp @@ -1567,6 +1641,15 @@ struct option_t MuttVars[] = { ** menu, attachments which cannot be decoded in a reasonable manner will ** be attached to the newly composed message if this option is \fIset\fP. */ +#ifdef USE_NNTP + { "mime_subject", DT_BOOL, R_NONE, OPTMIMESUBJECT, 1 }, + /* + ** .pp + ** If \fIunset\fP, 8-bit ``subject:'' line in article header will not be + ** encoded according to RFC2047 to base64. This is useful when message + ** is Usenet article, because MIME for news is nonstandard feature. + */ +#endif #ifdef MIXMASTER { "mix_entry_format", DT_STR, R_NONE, UL &MixEntryFormat, UL "%4n %c %-16s %a" }, /* @@ -1617,6 +1700,100 @@ struct option_t MuttVars[] = { ** See also $$read_inc, $$write_inc and $$net_inc. */ #endif +#ifdef USE_NNTP + { "news_cache_dir", DT_PATH, R_NONE, UL &NewsCacheDir, UL "~/.mutt" }, + /* + ** .pp + ** This variable pointing to directory where Mutt will save cached news + ** articles and headers in. If \fIunset\fP, articles and headers will not be + ** saved at all and will be reloaded from the server each time. + */ + { "news_server", DT_STR, R_NONE, UL &NewsServer, 0 }, + /* + ** .pp + ** This variable specifies domain name or address of NNTP server. It + ** defaults to the news server specified in the environment variable + ** $$$NNTPSERVER or contained in the file /etc/nntpserver. You can also + ** specify username and an alternative port for each news server, ie: + ** .pp + ** [[s]news://][username[:password]@]server[:port] + */ + { "newsgroups_charset", DT_STR, R_NONE, UL &NewsgroupsCharset, UL "utf-8" }, + /* + ** .pp + ** Character set of newsgroups descriptions. + */ + { "newsrc", DT_PATH, R_NONE, UL &NewsRc, UL "~/.newsrc" }, + /* + ** .pp + ** The file, containing info about subscribed newsgroups - names and + ** indexes of read articles. The following printf-style sequence + ** is understood: + ** .dl + ** .dt %a .dd account url + ** .dt %p .dd port + ** .dt %P .dd port if specified + ** .dt %s .dd news server name + ** .dt %S .dd url schema + ** .dt %u .dd username + ** .de + */ + { "nntp_authenticators", DT_STR, R_NONE, UL &NntpAuthenticators, UL 0 }, + /* + ** .pp + ** This is a colon-delimited list of authentication methods mutt may + ** attempt to use to log in to a news server, in the order mutt should + ** try them. Authentication methods are either ``user'' or any + ** SASL mechanism, e.g. ``digest-md5'', ``gssapi'' or ``cram-md5''. + ** This option is case-insensitive. If it's \fIunset\fP (the default) + ** mutt will try all available methods, in order from most-secure to + ** least-secure. + ** .pp + ** Example: + ** .ts + ** set nntp_authenticators="digest-md5:user" + ** .te + ** .pp + ** \fBNote:\fP Mutt will only fall back to other authentication methods if + ** the previous methods are unavailable. If a method is available but + ** authentication fails, mutt will not connect to the IMAP server. + */ + { "nntp_context", DT_NUM, R_NONE, UL &NntpContext, 1000 }, + /* + ** .pp + ** This variable defines number of articles which will be in index when + ** newsgroup entered. If active newsgroup have more articles than this + ** number, oldest articles will be ignored. Also controls how many + ** articles headers will be saved in cache when you quit newsgroup. + */ + { "nntp_load_description", DT_BOOL, R_NONE, OPTLOADDESC, 1 }, + /* + ** .pp + ** This variable controls whether or not descriptions for each newsgroup + ** must be loaded when newsgroup is added to list (first time list + ** loading or new newsgroup adding). + */ + { "nntp_user", DT_STR, R_NONE, UL &NntpUser, UL "" }, + /* + ** .pp + ** Your login name on the NNTP server. If \fIunset\fP and NNTP server requires + ** authentification, Mutt will prompt you for your account name when you + ** connect to news server. + */ + { "nntp_pass", DT_STR, R_NONE, UL &NntpPass, UL "" }, + /* + ** .pp + ** Your password for NNTP account. + */ + { "nntp_poll", DT_NUM, R_NONE, UL &NewsPollTimeout, 60 }, + /* + ** .pp + ** The time in seconds until any operations on newsgroup except post new + ** article will cause recheck for new news. If set to 0, Mutt will + ** recheck newsgroup on each operation in index (stepping, read article, + ** etc.). + */ +#endif { "pager", DT_PATH, R_NONE, UL &Pager, UL "builtin" }, /* ** .pp @@ -2152,6 +2329,16 @@ struct option_t MuttVars[] = { { "post_indent_str", DT_SYN, R_NONE, UL "post_indent_string", 0 }, /* */ +#ifdef USE_NNTP + { "post_moderated", DT_QUAD, R_NONE, OPT_TOMODERATED, M_ASKYES }, + /* + ** .pp + ** If set to \fIyes\fP, Mutt will post article to newsgroup that have + ** not permissions to posting (e.g. moderated). \fBNote:\fP if news server + ** does not support posting to that newsgroup or totally read-only, that + ** posting will not have an effect. + */ +#endif { "postpone", DT_QUAD, R_NONE, OPT_POSTPONE, M_ASKYES }, /* ** .pp @@ -2576,6 +2763,28 @@ struct option_t MuttVars[] = { ** Command to use when spawning a subshell. By default, the user's login ** shell from \fC/etc/passwd\fP is used. */ +#ifdef USE_NNTP + { "save_unsubscribed", DT_BOOL, R_NONE, OPTSAVEUNSUB, 0 }, + /* + ** .pp + ** When \fIset\fP, info about unsubscribed newsgroups will be saved into + ** ``newsrc'' file and into cache. + */ + { "show_new_news", DT_BOOL, R_NONE, OPTSHOWNEWNEWS, 1 }, + /* + ** .pp + ** If \fIset\fP, news server will be asked for new newsgroups on entering + ** the browser. Otherwise, it will be done only once for a news server. + ** Also controls whether or not number of new articles of subscribed + ** newsgroups will be then checked. + */ + { "show_only_unread", DT_BOOL, R_NONE, OPTSHOWONLYUNREAD, 0 }, + /* + ** .pp + ** If \fIset\fP, only subscribed newsgroups that contain unread articles + ** will be displayed in browser. + */ +#endif { "sig_dashes", DT_BOOL, R_NONE, OPTSIGDASHES, 1 }, /* ** .pp @@ -3511,6 +3720,14 @@ struct option_t MuttVars[] = { ** xterm_set_titles has been set. This string is identical in formatting ** to the one used by ``$$status_format''. */ +#ifdef USE_NNTP + { "x_comment_to", DT_BOOL, R_NONE, OPTXCOMMENTTO, 0 }, + /* + ** .pp + ** If \fIset\fP, Mutt will add ``X-Comment-To:'' field (that contains full + ** name of original article author) to article that followuped to newsgroup. + */ +#endif /*--*/ { NULL, 0, 0, 0, 0 } }; diff --git a/keymap.c b/keymap.c index 9dc87f0..5cdef30 100644 --- a/keymap.c +++ b/keymap.c @@ -784,7 +784,6 @@ void km_init (void) km_bindkey ("", MENU_MAIN, OP_DISPLAY_MESSAGE); km_bindkey ("x", MENU_PAGER, OP_EXIT); - km_bindkey ("i", MENU_PAGER, OP_EXIT); km_bindkey ("", MENU_PAGER, OP_PREV_LINE); km_bindkey ("", MENU_PAGER, OP_NEXT_PAGE); km_bindkey ("", MENU_PAGER, OP_PREV_PAGE); diff --git a/mailbox.h b/mailbox.h index b652628..ed7954c 100644 --- a/mailbox.h +++ b/mailbox.h @@ -75,6 +75,9 @@ int mx_is_imap (const char *); #ifdef USE_POP int mx_is_pop (const char *); #endif +#ifdef USE_NNTP +int mx_is_nntp (const char *); +#endif int mx_access (const char*, int); int mx_check_empty (const char *); diff --git a/main.c b/main.c index 5ab1868..a291397 100644 --- a/main.c +++ b/main.c @@ -62,6 +62,10 @@ #include #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif + static const char *ReachingUs = N_("\ To contact the developers, please mail to .\n\ To report a bug, please visit http://bugs.mutt.org/.\n"); @@ -136,6 +140,8 @@ options:\n\ " -e \tspecify a command to be executed after initialization\n\ -f \tspecify which mailbox to read\n\ -F \tspecify an alternate muttrc file\n\ + -g \tspecify a news server (if compiled with NNTP)\n\ + -G\t\tselect a newsgroup (if compiled with NNTP)\n\ -H \tspecify a draft file to read header and body from\n\ -i \tspecify a file which Mutt should include in the body\n\ -m \tspecify a default mailbox type\n\ @@ -284,6 +290,12 @@ static void show_version (void) "-USE_POP " #endif +#ifdef USE_NNTP + "+USE_NNTP " +#else + "-USE_NNTP " +#endif + #ifdef USE_IMAP "+USE_IMAP " #else @@ -558,6 +570,9 @@ init_extended_keys(); #define M_NOSYSRC (1<<2) /* -n */ #define M_RO (1<<3) /* -R */ #define M_SELECT (1<<4) /* -y */ +#ifdef USE_NNTP +#define M_NEWS (1<<5) /* -g and -G */ +#endif int main (int argc, char **argv) { @@ -630,7 +645,11 @@ int main (int argc, char **argv) argv[nargc++] = argv[optind]; } +#ifdef USE_NNTP + if ((i = getopt (argc, argv, "+A:a:b:F:f:c:Dd:e:g:GH:s:i:hm:npQ:RvxyzZ")) != EOF) +#else if ((i = getopt (argc, argv, "+A:a:b:F:f:c:Dd:e:H:s:i:hm:npQ:RvxyzZ")) != EOF) +#endif switch (i) { case 'A': @@ -727,6 +746,20 @@ int main (int argc, char **argv) flags |= M_SELECT; break; +#ifdef USE_NNTP + case 'g': /* Specify a news server */ + { + char buf[LONG_STRING]; + + snprintf (buf, sizeof (buf), "set news_server=%s", optarg); + commands = mutt_add_list (commands, buf); + } + + case 'G': /* List of newsgroups */ + flags |= M_SELECT | M_NEWS; + break; +#endif + case 'z': flags |= M_IGNORE; break; @@ -1014,6 +1047,18 @@ int main (int argc, char **argv) } else if (flags & M_SELECT) { +#ifdef USE_NNTP + if (flags & M_NEWS) + { + set_option (OPTNEWS); + if(!(CurrentNewsSrv = nntp_select_server (NewsServer, 0))) + { + mutt_endwin (Errorbuf); + exit (1); + } + } + else +#endif if (!Incoming) { mutt_endwin _("No incoming mailboxes defined."); exit (1); @@ -1029,6 +1074,15 @@ int main (int argc, char **argv) if (!folder[0]) strfcpy (folder, NONULL(Spoolfile), sizeof (folder)); + +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + unset_option (OPTNEWS); + nntp_expand_path (folder, sizeof (folder), &CurrentNewsSrv->conn->account); + } + else +#endif mutt_expand_path (folder, sizeof (folder)); mutt_str_replace (&CurrentFolder, folder); diff --git a/mutt.h b/mutt.h index 932ef10..a95b249 100644 --- a/mutt.h +++ b/mutt.h @@ -239,6 +239,9 @@ enum M_PGP_KEY, M_XLABEL, M_MIMEATTACH, +#ifdef USE_NNTP + M_NEWSGROUPS, +#endif /* Options for Mailcap lookup */ M_EDIT, @@ -295,6 +298,11 @@ enum #endif OPT_SUBJECT, OPT_VERIFYSIG, /* verify PGP signatures */ +#ifdef USE_NNTP + OPT_TOMODERATED, + OPT_CATCHUP, + OPT_FOLLOWUPTOPOSTER, +#endif /* THIS MUST BE THE LAST VALUE. */ OPT_MAX @@ -311,6 +319,7 @@ enum #define SENDKEY (1<<7) #define SENDRESEND (1<<8) #define SENDPOSTPONEDFCC (1<<9) /* used by mutt_get_postponed() to signal that the x-mutt-fcc header field was present */ +#define SENDNEWS (1<<10) /* flags to _mutt_select_file() */ #define M_SEL_BUFFY (1<<0) @@ -330,6 +339,8 @@ enum OPTASCIICHARS, OPTASKBCC, OPTASKCC, + OPTASKFOLLOWUP, + OPTASKXCOMMENTTO, OPTATTACHSPLIT, OPTAUTOEDIT, OPTAUTOTAG, @@ -410,6 +421,9 @@ enum OPTMETOO, OPTMHPURGE, OPTMIMEFORWDECODE, +#ifdef USE_NNTP + OPTMIMESUBJECT, /* encode subject line with RFC2047 */ +#endif OPTNARROWTREE, OPTPAGERSTOP, OPTPIPEDECODE, @@ -495,6 +509,16 @@ enum OPTPGPAUTOINLINE, OPTPGPREPLYINLINE, + /* news options */ + +#ifdef USE_NNTP + OPTSHOWNEWNEWS, + OPTSHOWONLYUNREAD, + OPTSAVEUNSUB, + OPTLOADDESC, + OPTXCOMMENTTO, +#endif + /* pseudo options */ OPTAUXSORT, /* (pseudo) using auxiliary sort function */ @@ -515,6 +539,7 @@ enum OPTSORTSUBTHREADS, /* (pseudo) used when $sort_aux changes */ OPTNEEDRESCORE, /* (pseudo) set when the `score' command is used */ OPTATTACHMSG, /* (pseudo) used by attach-message */ + OPTHIDEREAD, /* (pseudo) whether or not hide read messages */ OPTKEEPQUIET, /* (pseudo) shut up the message and refresh * functions while we are executing an * external program. @@ -527,6 +552,11 @@ enum OPTSIDEBARNEWMAILONLY, +#ifdef USE_NNTP + OPTNEWS, /* (pseudo) used to change reader mode */ + OPTNEWSSEND, /* (pseudo) used to change behavior when posting */ +#endif + OPTMAX }; @@ -606,6 +636,13 @@ typedef struct envelope char *supersedes; char *date; char *x_label; + char *organization; +#ifdef USE_NNTP + char *newsgroups; + char *xref; + char *followup_to; + char *x_comment_to; +#endif BUFFER *spam; LIST *references; /* message references (in reverse order) */ LIST *in_reply_to; /* in-reply-to header content */ @@ -792,7 +829,7 @@ typedef struct header int refno; /* message number on server */ #endif -#if defined USE_POP || defined USE_IMAP +#if defined USE_POP || defined USE_IMAP || defined USE_NNTP void *data; /* driver-specific data */ #endif diff --git a/mutt_sasl.c b/mutt_sasl.c index 896825e..21da012 100644 --- a/mutt_sasl.c +++ b/mutt_sasl.c @@ -188,6 +188,11 @@ int mutt_sasl_client_new (CONNECTION* conn, sasl_conn_t** saslconn) case M_ACCT_TYPE_SMTP: service = "smtp"; break; +#ifdef USE_NNTP + case M_ACCT_TYPE_NNTP: + service = "nntp"; + break; +#endif default: mutt_error (_("Unknown SASL profile")); return -1; diff --git a/muttlib.c b/muttlib.c index 9086f07..49389fc 100644 --- a/muttlib.c +++ b/muttlib.c @@ -337,7 +337,7 @@ void mutt_free_header (HEADER **h) #ifdef MIXMASTER mutt_free_list (&(*h)->chain); #endif -#if defined USE_POP || defined USE_IMAP +#if defined USE_POP || defined USE_IMAP || defined USE_NNTP FREE (&(*h)->data); #endif FREE (h); /* __FREE_CHECKED__ */ @@ -725,6 +725,13 @@ void mutt_free_envelope (ENVELOPE **p) FREE (&(*p)->supersedes); FREE (&(*p)->date); FREE (&(*p)->x_label); + FREE (&(*p)->organization); +#ifdef USE_NNTP + FREE (&(*p)->newsgroups); + FREE (&(*p)->xref); + FREE (&(*p)->followup_to); + FREE (&(*p)->x_comment_to); +#endif mutt_buffer_free (&(*p)->spam); @@ -1573,6 +1580,14 @@ int mutt_save_confirm (const char *s, struct stat *st) } } +#ifdef USE_NNTP + if (magic == M_NNTP) + { + mutt_error _("Can't save message to news server."); + return 0; + } +#endif + if (stat (s, st) != -1) { if (magic == -1) diff --git a/mx.c b/mx.c index cbee47d..5bbbf2a 100644 --- a/mx.c +++ b/mx.c @@ -347,6 +347,22 @@ int mx_is_pop (const char *p) } #endif +#ifdef USE_NNTP +int mx_is_nntp (const char *p) +{ + url_scheme_t scheme; + + if (!p) + return 0; + + scheme = url_check_scheme (p); + if (scheme == U_NNTP || scheme == U_NNTPS) + return 1; + + return 0; +} +#endif + int mx_get_magic (const char *path) { struct stat st; @@ -364,6 +380,11 @@ int mx_get_magic (const char *path) return M_POP; #endif /* USE_POP */ +#ifdef USE_NNTP + if (mx_is_nntp (path)) + return M_NNTP; +#endif /* USE_NNTP */ + if (stat (path, &st) == -1) { dprint (1, (debugfile, "mx_get_magic(): unable to stat %s: %s (errno %d).\n", @@ -691,6 +712,12 @@ CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx) break; #endif /* USE_POP */ +#ifdef USE_NNTP + case M_NNTP: + rc = nntp_open_mailbox (ctx); + break; +#endif /* USE_NNTP */ + default: rc = -1; break; @@ -803,6 +830,12 @@ static int sync_mailbox (CONTEXT *ctx, int *index_hint) rc = pop_sync_mailbox (ctx, index_hint); break; #endif /* USE_POP */ + +#ifdef USE_NNTP + case M_NNTP: + rc = nntp_sync_mailbox (ctx); + break; +#endif /* USE_NNTP */ } #if 0 @@ -905,6 +938,25 @@ int mx_close_mailbox (CONTEXT *ctx, int *index_hint) return 0; } +#ifdef USE_NNTP + if (ctx->unread && ctx->magic == M_NNTP) + { + NNTP_DATA *nntp_data = ctx->data; + + if (nntp_data && nntp_data->nserv && nntp_data->group) + { + int rc = query_quadoption (OPT_CATCHUP, _("Mark all articles read?")); + if (rc < 0) + { + ctx->closing = 0; + return -1; + } + else if (rc == M_YES) + mutt_newsgroup_catchup (nntp_data->nserv, nntp_data->group); + } + } +#endif + for (i = 0; i < ctx->msgcount; i++) { if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read @@ -912,6 +964,12 @@ int mx_close_mailbox (CONTEXT *ctx, int *index_hint) read_msgs++; } +#ifdef USE_NNTP + /* don't need to move articles from newsgroup */ + if (ctx->magic == M_NNTP) + read_msgs = 0; +#endif + if (read_msgs && quadoption (OPT_MOVE) != M_NO) { char *p; @@ -1464,6 +1522,11 @@ int mx_check_mailbox (CONTEXT *ctx, int *index_hint, int lock) case M_POP: return (pop_check_mailbox (ctx, index_hint)); #endif /* USE_POP */ + +#ifdef USE_NNTP + case M_NNTP: + return (nntp_check_mailbox (ctx, 0)); +#endif /* USE_NNTP */ } } @@ -1524,6 +1587,15 @@ MESSAGE *mx_open_message (CONTEXT *ctx, int msgno) } #endif /* USE_POP */ +#ifdef USE_NNTP + case M_NNTP: + { + if (nntp_fetch_message (msg, ctx, msgno) != 0) + FREE (&msg); + break; + } +#endif /* USE_NNTP */ + default: dprint (1, (debugfile, "mx_open_message(): function not implemented for mailbox type %d.\n", ctx->magic)); FREE (&msg); @@ -1599,6 +1671,9 @@ int mx_close_message (MESSAGE **msg) int r = 0; if ((*msg)->magic == M_MH || (*msg)->magic == M_MAILDIR +#ifdef USE_NNTP + || (*msg)->magic == M_NNTP +#endif || (*msg)->magic == M_IMAP || (*msg)->magic == M_POP) { r = safe_fclose (&(*msg)->fp); diff --git a/mx.h b/mx.h index 4aabadf..9980c4e 100644 --- a/mx.h +++ b/mx.h @@ -34,6 +34,9 @@ enum M_MMDF, M_MH, M_MAILDIR, +#ifdef USE_NNTP + M_NNTP, +#endif M_IMAP, M_POP #ifdef USE_COMPRESSED diff --git a/newsrc.c b/newsrc.c new file mode 100644 index 0000000..482214c --- /dev/null +++ b/newsrc.c @@ -0,0 +1,1260 @@ +/* + * Copyright (C) 1998 Brandon Long + * Copyright (C) 1999 Andrej Gritsenko + * Copyright (C) 2000-2012 Vsevolod Volkov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mutt.h" +#include "mutt_curses.h" +#include "sort.h" +#include "mx.h" +#include "mime.h" +#include "mailbox.h" +#include "nntp.h" +#include "rfc822.h" +#include "rfc1524.h" +#include "rfc2047.h" +#include "bcache.h" + +#if USE_HCACHE +#include "hcache.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Find NNTP_DATA for given newsgroup or add it */ +static NNTP_DATA *nntp_data_find (NNTP_SERVER *nserv, const char *group) +{ + NNTP_DATA *nntp_data = hash_find (nserv->groups_hash, group); + + if (!nntp_data) + { + /* create NNTP_DATA structure and add it to hash */ + nntp_data = safe_calloc (1, sizeof (NNTP_DATA) + strlen (group) + 1); + nntp_data->group = (char *)nntp_data + sizeof (NNTP_DATA); + strcpy (nntp_data->group, group); + nntp_data->nserv = nserv; + nntp_data->deleted = 1; + if (nserv->groups_hash->nelem < nserv->groups_hash->curnelem * 2) + nserv->groups_hash = hash_resize (nserv->groups_hash, + nserv->groups_hash->nelem * 2, 0); + hash_insert (nserv->groups_hash, nntp_data->group, nntp_data, 0); + + /* add NNTP_DATA to list */ + if (nserv->groups_num >= nserv->groups_max) + { + nserv->groups_max *= 2; + safe_realloc (&nserv->groups_list, + nserv->groups_max * sizeof (nntp_data)); + } + nserv->groups_list[nserv->groups_num++] = nntp_data; + } + return nntp_data; +} + +/* Remove all temporarily cache files */ +void nntp_acache_free (NNTP_DATA *nntp_data) +{ + int i; + + for (i = 0; i < NNTP_ACACHE_LEN; i++) + { + if (nntp_data->acache[i].path) + { + unlink (nntp_data->acache[i].path); + FREE (&nntp_data->acache[i].path); + } + } +} + +/* Free NNTP_DATA, used to destroy hash elements */ +void nntp_data_free (void *data) +{ + NNTP_DATA *nntp_data = data; + + if (!nntp_data) + return; + nntp_acache_free (nntp_data); + mutt_bcache_close (&nntp_data->bcache); + FREE (&nntp_data->newsrc_ent); + FREE (&nntp_data->desc); + FREE (&data); +} + +/* Unlock and close .newsrc file */ +void nntp_newsrc_close (NNTP_SERVER *nserv) +{ + if (!nserv->newsrc_fp) + return; + + dprint (1, (debugfile, "Unlocking %s\n", nserv->newsrc_file)); + mx_unlock_file (nserv->newsrc_file, fileno (nserv->newsrc_fp), 0); + safe_fclose (&nserv->newsrc_fp); +} + +/* Parse .newsrc file: + * 0 - not changed + * 1 - parsed + * -1 - error */ +int nntp_newsrc_parse (NNTP_SERVER *nserv) +{ + unsigned int i; + char *line; + struct stat sb; + + /* if file doesn't exist, create it */ + nserv->newsrc_fp = safe_fopen (nserv->newsrc_file, "a"); + safe_fclose (&nserv->newsrc_fp); + + /* open .newsrc */ + nserv->newsrc_fp = safe_fopen (nserv->newsrc_file, "r"); + if (!nserv->newsrc_fp) + { + mutt_perror (nserv->newsrc_file); + mutt_sleep (2); + return -1; + } + + /* lock it */ + dprint (1, (debugfile, "Locking %s\n", nserv->newsrc_file)); + if (mx_lock_file (nserv->newsrc_file, fileno (nserv->newsrc_fp), 0, 0, 1)) + { + safe_fclose (&nserv->newsrc_fp); + return -1; + } + + if (stat (nserv->newsrc_file, &sb)) + { + mutt_perror (nserv->newsrc_file); + nntp_newsrc_close (nserv); + mutt_sleep (2); + return -1; + } + + if (nserv->size == sb.st_size && nserv->mtime == sb.st_mtime) + return 0; + + nserv->size = sb.st_size; + nserv->mtime = sb.st_mtime; + nserv->newsrc_modified = 1; + dprint (1, (debugfile, "Parsing %s\n", nserv->newsrc_file)); + + /* .newsrc has been externally modified or hasn't been loaded yet */ + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + + if (!nntp_data) + continue; + + nntp_data->subscribed = 0; + nntp_data->newsrc_len = 0; + FREE (&nntp_data->newsrc_ent); + } + + line = safe_malloc (sb.st_size + 1); + while (sb.st_size && fgets (line, sb.st_size + 1, nserv->newsrc_fp)) + { + char *b, *h, *p; + unsigned int subs = 0, i = 1; + NNTP_DATA *nntp_data; + + /* find end of newsgroup name */ + p = strpbrk (line, ":!"); + if (!p) + continue; + + /* ":" - subscribed, "!" - unsubscribed */ + if (*p == ':') + subs++; + *p++ = '\0'; + + /* get newsgroup data */ + nntp_data = nntp_data_find (nserv, line); + FREE (&nntp_data->newsrc_ent); + + /* count number of entries */ + b = p; + while (*b) + if (*b++ == ',') + i++; + nntp_data->newsrc_ent = safe_calloc (i, sizeof (NEWSRC_ENTRY)); + nntp_data->subscribed = subs; + + /* parse entries */ + i = 0; + while (p) + { + b = p; + + /* find end of entry */ + p = strchr (p, ','); + if (p) + *p++ = '\0'; + + /* first-last or single number */ + h = strchr (b, '-'); + if (h) + *h++ = '\0'; + else + h = b; + + if (sscanf (b, ANUM, &nntp_data->newsrc_ent[i].first) == 1 && + sscanf (h, ANUM, &nntp_data->newsrc_ent[i].last) == 1) + i++; + } + if (i == 0) + { + nntp_data->newsrc_ent[i].first = 1; + nntp_data->newsrc_ent[i].last = 0; + i++; + } + if (nntp_data->lastMessage == 0) + nntp_data->lastMessage = nntp_data->newsrc_ent[i - 1].last; + nntp_data->newsrc_len = i; + safe_realloc (&nntp_data->newsrc_ent, i * sizeof (NEWSRC_ENTRY)); + nntp_group_unread_stat (nntp_data); + dprint (2, (debugfile, "nntp_newsrc_parse: %s\n", nntp_data->group)); + } + FREE (&line); + return 1; +} + +/* Generate array of .newsrc entries */ +void nntp_newsrc_gen_entries (CONTEXT *ctx) +{ + NNTP_DATA *nntp_data = ctx->data; + anum_t last = 0, first = 1; + int series, i; + int save_sort = SORT_ORDER; + unsigned int entries; + + if (Sort != SORT_ORDER) + { + save_sort = Sort; + Sort = SORT_ORDER; + mutt_sort_headers (ctx, 0); + } + + entries = nntp_data->newsrc_len; + if (!entries) + { + entries = 5; + nntp_data->newsrc_ent = safe_calloc (entries, sizeof (NEWSRC_ENTRY)); + } + + /* Set up to fake initial sequence from 1 to the article before the + * first article in our list */ + nntp_data->newsrc_len = 0; + series = 1; + for (i = 0; i < ctx->msgcount; i++) + { + /* search for first unread */ + if (series) + { + /* We don't actually check sequential order, since we mark + * "missing" entries as read/deleted */ + last = NHDR (ctx->hdrs[i])->article_num; + if (last >= nntp_data->firstMessage && !ctx->hdrs[i]->deleted && + !ctx->hdrs[i]->read) + { + if (nntp_data->newsrc_len >= entries) + { + entries *= 2; + safe_realloc (&nntp_data->newsrc_ent, entries * sizeof (NEWSRC_ENTRY)); + } + nntp_data->newsrc_ent[nntp_data->newsrc_len].first = first; + nntp_data->newsrc_ent[nntp_data->newsrc_len].last = last - 1; + nntp_data->newsrc_len++; + series = 0; + } + } + + /* search for first read */ + else + { + if (ctx->hdrs[i]->deleted || ctx->hdrs[i]->read) + { + first = last + 1; + series = 1; + } + last = NHDR (ctx->hdrs[i])->article_num; + } + } + + if (series && first <= nntp_data->lastLoaded) + { + if (nntp_data->newsrc_len >= entries) + { + entries++; + safe_realloc (&nntp_data->newsrc_ent, entries * sizeof (NEWSRC_ENTRY)); + } + nntp_data->newsrc_ent[nntp_data->newsrc_len].first = first; + nntp_data->newsrc_ent[nntp_data->newsrc_len].last = nntp_data->lastLoaded; + nntp_data->newsrc_len++; + } + safe_realloc (&nntp_data->newsrc_ent, + nntp_data->newsrc_len * sizeof (NEWSRC_ENTRY)); + + if (save_sort != Sort) + { + Sort = save_sort; + mutt_sort_headers (ctx, 0); + } +} + +/* Update file with new contents */ +static int update_file (char *filename, char *buf) +{ + FILE *fp; + char tmpfile[_POSIX_PATH_MAX]; + int rc = -1; + + while (1) + { + snprintf (tmpfile, sizeof (tmpfile), "%s.tmp", filename); + fp = fopen (tmpfile, "w"); + if (!fp) + { + mutt_perror (tmpfile); + *tmpfile = '\0'; + break; + } + if (fputs (buf, fp) == EOF) + { + mutt_perror (tmpfile); + break; + } + if (fclose (fp) == EOF) + { + mutt_perror (tmpfile); + fp = NULL; + break; + } + fp = NULL; + if (rename (tmpfile, filename) < 0) + { + mutt_perror (filename); + break; + } + *tmpfile = '\0'; + rc = 0; + break; + } + if (fp) + fclose (fp); + if (*tmpfile) + unlink (tmpfile); + if (rc) + mutt_sleep (2); + return rc; +} + +/* Update .newsrc file */ +int nntp_newsrc_update (NNTP_SERVER *nserv) +{ + char *buf; + size_t buflen, off; + unsigned int i; + int rc = -1; + + if (!nserv) + return -1; + + buflen = 10 * LONG_STRING; + buf = safe_calloc (1, buflen); + off = 0; + + /* we will generate full newsrc here */ + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + unsigned int n; + + if (!nntp_data || !nntp_data->newsrc_ent) + continue; + + /* write newsgroup name */ + if (off + strlen (nntp_data->group) + 3 > buflen) + { + buflen *= 2; + safe_realloc (&buf, buflen); + } + snprintf (buf + off, buflen - off, "%s%c ", nntp_data->group, + nntp_data->subscribed ? ':' : '!'); + off += strlen (buf + off); + + /* write entries */ + for (n = 0; n < nntp_data->newsrc_len; n++) + { + if (off + LONG_STRING > buflen) + { + buflen *= 2; + safe_realloc (&buf, buflen); + } + if (n) + buf[off++] = ','; + if (nntp_data->newsrc_ent[n].first == nntp_data->newsrc_ent[n].last) + snprintf (buf + off, buflen - off, "%d", nntp_data->newsrc_ent[n].first); + else if (nntp_data->newsrc_ent[n].first < nntp_data->newsrc_ent[n].last) + snprintf (buf + off, buflen - off, "%d-%d", + nntp_data->newsrc_ent[n].first, nntp_data->newsrc_ent[n].last); + off += strlen (buf + off); + } + buf[off++] = '\n'; + } + buf[off] = '\0'; + + /* newrc being fully rewritten */ + dprint (1, (debugfile, "Updating %s\n", nserv->newsrc_file)); + if (nserv->newsrc_file && update_file (nserv->newsrc_file, buf) == 0) + { + struct stat sb; + + rc = stat (nserv->newsrc_file, &sb); + if (rc == 0) + { + nserv->size = sb.st_size; + nserv->mtime = sb.st_mtime; + } + else + { + mutt_perror (nserv->newsrc_file); + mutt_sleep (2); + } + } + FREE (&buf); + return rc; +} + +/* Make fully qualified cache file name */ +static void cache_expand (char *dst, size_t dstlen, ACCOUNT *acct, char *src) +{ + char *c; + char file[_POSIX_PATH_MAX]; + + /* server subdirectory */ + if (acct) + { + ciss_url_t url; + + mutt_account_tourl (acct, &url); + url.path = src; + url_ciss_tostring (&url, file, sizeof (file), U_PATH); + } + else + strfcpy (file, src ? src : "", sizeof (file)); + + snprintf (dst, dstlen, "%s/%s", NewsCacheDir, file); + + /* remove trailing slash */ + c = dst + strlen (dst) - 1; + if (*c == '/') + *c = '\0'; + mutt_expand_path (dst, dstlen); +} + +/* Make fully qualified url from newsgroup name */ +void nntp_expand_path (char *line, size_t len, ACCOUNT *acct) +{ + ciss_url_t url; + + url.path = safe_strdup (line); + mutt_account_tourl (acct, &url); + url_ciss_tostring (&url, line, len, 0); + FREE (&url.path); +} + +/* Parse newsgroup */ +int nntp_add_group (char *line, void *data) +{ + NNTP_SERVER *nserv = data; + NNTP_DATA *nntp_data; + char group[LONG_STRING]; + char desc[HUGE_STRING] = ""; + char mod; + anum_t first, last; + + if (!nserv || !line) + return 0; + + if (sscanf (line, "%s " ANUM " " ANUM " %c %[^\n]", group, + &last, &first, &mod, desc) < 4) + return 0; + + nntp_data = nntp_data_find (nserv, group); + nntp_data->deleted = 0; + nntp_data->firstMessage = first; + nntp_data->lastMessage = last; + nntp_data->allowed = mod == 'y' || mod == 'm' ? 1 : 0; + mutt_str_replace (&nntp_data->desc, desc); + if (nntp_data->newsrc_ent || nntp_data->lastCached) + nntp_group_unread_stat (nntp_data); + else if (nntp_data->lastMessage && + nntp_data->firstMessage <= nntp_data->lastMessage) + nntp_data->unread = nntp_data->lastMessage - nntp_data->firstMessage + 1; + else + nntp_data->unread = 0; + return 0; +} + +/* Load list of all newsgroups from cache */ +static int active_get_cache (NNTP_SERVER *nserv) +{ + char buf[HUGE_STRING]; + char file[_POSIX_PATH_MAX]; + time_t t; + FILE *fp; + + cache_expand (file, sizeof (file), &nserv->conn->account, ".active"); + dprint (1, (debugfile, "Parsing %s\n", file)); + fp = safe_fopen (file, "r"); + if (!fp) + return -1; + + if (fgets (buf, sizeof (buf), fp) == NULL || + sscanf (buf, "%ld%s", &t, file) != 1 || t == 0) + { + fclose (fp); + return -1; + } + nserv->newgroups_time = t; + + mutt_message _("Loading list of groups from cache..."); + while (fgets (buf, sizeof (buf), fp)) + nntp_add_group (buf, nserv); + nntp_add_group (NULL, NULL); + fclose (fp); + mutt_clear_error (); + return 0; +} + +/* Save list of all newsgroups to cache */ +int nntp_active_save_cache (NNTP_SERVER *nserv) +{ + char file[_POSIX_PATH_MAX]; + char *buf; + size_t buflen, off; + unsigned int i; + int rc; + + if (!nserv->cacheable) + return 0; + + buflen = 10 * LONG_STRING; + buf = safe_calloc (1, buflen); + snprintf (buf, buflen, "%lu\n", (unsigned long)nserv->newgroups_time); + off = strlen (buf); + + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + + if (!nntp_data || nntp_data->deleted) + continue; + + if (off + strlen (nntp_data->group) + + (nntp_data->desc ? strlen (nntp_data->desc) : 0) + 50 > buflen) + { + buflen *= 2; + safe_realloc (&buf, buflen); + } + snprintf (buf + off, buflen - off, "%s %d %d %c%s%s\n", nntp_data->group, + nntp_data->lastMessage, nntp_data->firstMessage, + nntp_data->allowed ? 'y' : 'n', nntp_data->desc ? " " : "", + nntp_data->desc ? nntp_data->desc : ""); + off += strlen (buf + off); + } + + cache_expand (file, sizeof (file), &nserv->conn->account, ".active"); + dprint (1, (debugfile, "Updating %s\n", file)); + rc = update_file (file, buf); + FREE (&buf); + return rc; +} + +#ifdef USE_HCACHE +/* Used by mutt_hcache_open() to compose hcache file name */ +static int nntp_hcache_namer (const char *path, char *dest, size_t destlen) +{ + return snprintf (dest, destlen, "%s.hcache", path); +} + +/* Open newsgroup hcache */ +header_cache_t *nntp_hcache_open (NNTP_DATA *nntp_data) +{ + ciss_url_t url; + char file[_POSIX_PATH_MAX]; + + if (!nntp_data->nserv || !nntp_data->nserv->cacheable || + !nntp_data->nserv->conn || !nntp_data->group || + !(nntp_data->newsrc_ent || nntp_data->subscribed || + option (OPTSAVEUNSUB))) + return NULL; + + mutt_account_tourl (&nntp_data->nserv->conn->account, &url); + url.path = nntp_data->group; + url_ciss_tostring (&url, file, sizeof (file), U_PATH); + return mutt_hcache_open (NewsCacheDir, file, nntp_hcache_namer); +} + +/* Remove stale cached headers */ +void nntp_hcache_update (NNTP_DATA *nntp_data, header_cache_t *hc) +{ + char buf[16]; + int old = 0; + void *hdata; + anum_t first, last, current; + + if (!hc) + return; + + /* fetch previous values of first and last */ + hdata = mutt_hcache_fetch_raw (hc, "index", strlen); + if (hdata) + { + dprint (2, (debugfile, + "nntp_hcache_update: mutt_hcache_fetch index: %s\n", hdata)); + if (sscanf (hdata, ANUM " " ANUM, &first, &last) == 2) + { + old = 1; + nntp_data->lastCached = last; + + /* clean removed headers from cache */ + for (current = first; current <= last; current++) + { + if (current >= nntp_data->firstMessage && + current <= nntp_data->lastMessage) + continue; + + snprintf (buf, sizeof (buf), "%d", current); + dprint (2, (debugfile, + "nntp_hcache_update: mutt_hcache_delete %s\n", buf)); + mutt_hcache_delete (hc, buf, strlen); + } + } + } + + /* store current values of first and last */ + if (!old || nntp_data->firstMessage != first || + nntp_data->lastMessage != last) + { + snprintf (buf, sizeof (buf), "%u %u", nntp_data->firstMessage, + nntp_data->lastMessage); + dprint (2, (debugfile, + "nntp_hcache_update: mutt_hcache_store index: %s\n", buf)); + mutt_hcache_store_raw (hc, "index", buf, strlen (buf) + 1, strlen); + } +} +#endif + +/* Remove bcache file */ +static int nntp_bcache_delete (const char *id, body_cache_t *bcache, void *data) +{ + NNTP_DATA *nntp_data = data; + anum_t anum; + char c; + + if (!nntp_data || sscanf (id, ANUM "%c", &anum, &c) != 1 || + anum < nntp_data->firstMessage || anum > nntp_data->lastMessage) + { + if (nntp_data) + dprint (2, (debugfile, "nntp_bcache_delete: mutt_bcache_del %s\n", id)); + mutt_bcache_del (bcache, id); + } + return 0; +} + +/* Remove stale cached messages */ +void nntp_bcache_update (NNTP_DATA *nntp_data) +{ + mutt_bcache_list (nntp_data->bcache, nntp_bcache_delete, nntp_data); +} + +/* Remove hcache and bcache of newsgroup */ +void nntp_delete_group_cache (NNTP_DATA *nntp_data) +{ + char file[_POSIX_PATH_MAX]; + + if (!nntp_data || !nntp_data->nserv || !nntp_data->nserv->cacheable) + return; + +#ifdef USE_HCACHE + nntp_hcache_namer (nntp_data->group, file, sizeof (file)); + cache_expand (file, sizeof (file), &nntp_data->nserv->conn->account, file); + unlink (file); + nntp_data->lastCached = 0; + dprint (2, (debugfile, "nntp_delete_group_cache: %s\n", file)); +#endif + + if (!nntp_data->bcache) + nntp_data->bcache = mutt_bcache_open (&nntp_data->nserv->conn->account, + nntp_data->group); + if (nntp_data->bcache) + { + dprint (2, (debugfile, "nntp_delete_group_cache: %s/*\n", nntp_data->group)); + mutt_bcache_list (nntp_data->bcache, nntp_bcache_delete, NULL); + mutt_bcache_close (&nntp_data->bcache); + } +} + +/* Remove hcache and bcache of all unexistent and unsubscribed newsgroups */ +void nntp_clear_cache (NNTP_SERVER *nserv) +{ + char file[_POSIX_PATH_MAX]; + char *fp; + struct dirent *entry; + DIR *dp; + + if (!nserv || !nserv->cacheable) + return; + + cache_expand (file, sizeof (file), &nserv->conn->account, NULL); + dp = opendir (file); + if (dp) + { + safe_strncat (file, sizeof (file), "/", 1); + fp = file + strlen (file); + while ((entry = readdir (dp))) + { + char *group = entry->d_name; + struct stat sb; + NNTP_DATA *nntp_data; + NNTP_DATA nntp_tmp; + + if (mutt_strcmp (group, ".") == 0 || + mutt_strcmp (group, "..") == 0) + continue; + *fp = '\0'; + safe_strncat (file, sizeof (file), group, strlen (group)); + if (stat (file, &sb)) + continue; + +#ifdef USE_HCACHE + if (S_ISREG (sb.st_mode)) + { + char *ext = group + strlen (group) - 7; + if (strlen (group) < 8 || mutt_strcmp (ext, ".hcache")) + continue; + *ext = '\0'; + } + else +#endif + if (!S_ISDIR (sb.st_mode)) + continue; + + nntp_data = hash_find (nserv->groups_hash, group); + if (!nntp_data) + { + nntp_data = &nntp_tmp; + nntp_data->nserv = nserv; + nntp_data->group = group; + nntp_data->bcache = NULL; + } + else if (nntp_data->newsrc_ent || nntp_data->subscribed || + option (OPTSAVEUNSUB)) + continue; + + nntp_delete_group_cache (nntp_data); + if (S_ISDIR (sb.st_mode)) + { + rmdir (file); + dprint (2, (debugfile, "nntp_clear_cache: %s\n", file)); + } + } + closedir (dp); + } + return; +} + +/* %a = account url + * %p = port + * %P = port if specified + * %s = news server name + * %S = url schema + * %u = username */ +const char * +nntp_format_str (char *dest, size_t destlen, size_t col, char op, const char *src, + const char *fmt, const char *ifstring, const char *elsestring, + unsigned long data, format_flag flags) +{ + NNTP_SERVER *nserv = (NNTP_SERVER *)data; + ACCOUNT *acct = &nserv->conn->account; + ciss_url_t url; + char fn[SHORT_STRING], tmp[SHORT_STRING], *p; + + switch (op) + { + case 'a': + mutt_account_tourl (acct, &url); + url_ciss_tostring (&url, fn, sizeof (fn), U_PATH); + p = strchr (fn, '/'); + if (p) + *p = '\0'; + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, fn); + break; + case 'p': + snprintf (tmp, sizeof (tmp), "%%%su", fmt); + snprintf (dest, destlen, tmp, acct->port); + break; + case 'P': + *dest = '\0'; + if (acct->flags & M_ACCT_PORT) + { + snprintf (tmp, sizeof (tmp), "%%%su", fmt); + snprintf (dest, destlen, tmp, acct->port); + } + break; + case 's': + strncpy (fn, acct->host, sizeof (fn) - 1); + mutt_strlower (fn); + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, fn); + break; + case 'S': + mutt_account_tourl (acct, &url); + url_ciss_tostring (&url, fn, sizeof (fn), U_PATH); + p = strchr (fn, ':'); + if (p) + *p = '\0'; + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, fn); + break; + case 'u': + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, acct->user); + break; + } + return (src); +} + +/* Automatically loads a newsrc into memory, if necessary. + * Checks the size/mtime of a newsrc file, if it doesn't match, load + * again. Hmm, if a system has broken mtimes, this might mean the file + * is reloaded every time, which we'd have to fix. */ +NNTP_SERVER *nntp_select_server (char *server, int leave_lock) +{ + char file[_POSIX_PATH_MAX]; + char *p; + int rc; + struct stat sb; + ACCOUNT acct; + NNTP_SERVER *nserv; + NNTP_DATA *nntp_data; + CONNECTION *conn; + ciss_url_t url; + + if (!server || !*server) + { + mutt_error _("No news server defined!"); + mutt_sleep (2); + return NULL; + } + + /* create account from news server url */ + acct.flags = 0; + acct.port = NNTP_PORT; + acct.type = M_ACCT_TYPE_NNTP; + snprintf (file, sizeof (file), "%s%s", + strstr (server, "://") ? "" : "news://", server); + if (url_parse_ciss (&url, file) < 0 || + (url.path && *url.path) || + !(url.scheme == U_NNTP || url.scheme == U_NNTPS) || + mutt_account_fromurl (&acct, &url) < 0) + { + mutt_error (_("%s is an invalid news server specification!"), server); + mutt_sleep (2); + return NULL; + } + if (url.scheme == U_NNTPS) + { + acct.flags |= M_ACCT_SSL; + acct.port = NNTP_SSL_PORT; + } + + /* find connection by account */ + conn = mutt_conn_find (NULL, &acct); + if (!conn) + return NULL; + if (!(conn->account.flags & M_ACCT_USER) && acct.flags & M_ACCT_USER) + { + conn->account.flags |= M_ACCT_USER; + conn->account.user[0] = '\0'; + } + + /* news server already exists */ + nserv = conn->data; + if (nserv) + { + if (nserv->status == NNTP_BYE) + nserv->status = NNTP_NONE; + if (nntp_open_connection (nserv) < 0) + return NULL; + + rc = nntp_newsrc_parse (nserv); + if (rc < 0) + return NULL; + + /* check for new newsgroups */ + if (!leave_lock && nntp_check_new_groups (nserv) < 0) + rc = -1; + + /* .newsrc has been externally modified */ + if (rc > 0) + nntp_clear_cache (nserv); + if (rc < 0 || !leave_lock) + nntp_newsrc_close (nserv); + return rc < 0 ? NULL : nserv; + } + + /* new news server */ + nserv = safe_calloc (1, sizeof (NNTP_SERVER)); + nserv->conn = conn; + nserv->groups_hash = hash_create (1009, 0); + nserv->groups_max = 16; + nserv->groups_list = safe_malloc (nserv->groups_max * sizeof (nntp_data)); + + rc = nntp_open_connection (nserv); + + /* try to create cache directory and enable caching */ + nserv->cacheable = 0; + if (rc >= 0 && NewsCacheDir && *NewsCacheDir) + { + cache_expand (file, sizeof (file), &conn->account, NULL); + p = *file == '/' ? file + 1 : file; + while (1) + { + p = strchr (p, '/'); + if (p) + *p = '\0'; + if ((stat (file, &sb) || (sb.st_mode & S_IFDIR) == 0) && + mkdir (file, 0700)) + { + mutt_error (_("Can't create %s: %s."), file, strerror (errno)); + mutt_sleep (2); + break; + } + if (!p) + { + nserv->cacheable = 1; + break; + } + *p++ = '/'; + } + } + + /* load .newsrc */ + if (rc >= 0) + { + mutt_FormatString (file, sizeof (file), 0, NONULL (NewsRc), + nntp_format_str, (unsigned long)nserv, 0); + mutt_expand_path (file, sizeof (file)); + nserv->newsrc_file = safe_strdup (file); + rc = nntp_newsrc_parse (nserv); + } + if (rc >= 0) + { + /* try to load list of newsgroups from cache */ + if (nserv->cacheable && active_get_cache (nserv) == 0) + rc = nntp_check_new_groups (nserv); + + /* load list of newsgroups from server */ + else + rc = nntp_active_fetch (nserv); + } + + if (rc >= 0) + nntp_clear_cache (nserv); + +#ifdef USE_HCACHE + /* check cache files */ + if (rc >= 0 && nserv->cacheable) + { + struct dirent *entry; + DIR *dp = opendir (file); + + if (dp) + { + while ((entry = readdir (dp))) + { + header_cache_t *hc; + void *hdata; + char *group = entry->d_name; + + p = group + strlen (group) - 7; + if (strlen (group) < 8 || strcmp (p, ".hcache")) + continue; + *p = '\0'; + nntp_data = hash_find (nserv->groups_hash, group); + if (!nntp_data) + continue; + + hc = nntp_hcache_open (nntp_data); + if (!hc) + continue; + + /* fetch previous values of first and last */ + hdata = mutt_hcache_fetch_raw (hc, "index", strlen); + if (hdata) + { + anum_t first, last; + + if (sscanf (hdata, ANUM " " ANUM, &first, &last) == 2) + { + if (nntp_data->deleted) + { + nntp_data->firstMessage = first; + nntp_data->lastMessage = last; + } + if (last >= nntp_data->firstMessage && + last <= nntp_data->lastMessage) + { + nntp_data->lastCached = last; + dprint (2, (debugfile, "nntp_select_server: %s lastCached=%u\n", + nntp_data->group, last)); + } + } + } + mutt_hcache_close (hc); + } + closedir (dp); + } + } +#endif + + if (rc < 0 || !leave_lock) + nntp_newsrc_close (nserv); + + if (rc < 0) + { + hash_destroy (&nserv->groups_hash, nntp_data_free); + FREE (&nserv->groups_list); + FREE (&nserv->newsrc_file); + FREE (&nserv->authenticators); + FREE (&nserv); + mutt_socket_close (conn); + mutt_socket_free (conn); + return NULL; + } + + conn->data = nserv; + return nserv; +} + +/* Full status flags are not supported by nntp, but we can fake some of them: + * Read = a read message number is in the .newsrc + * New = not read and not cached + * Old = not read but cached */ +void nntp_article_status (CONTEXT *ctx, HEADER *hdr, char *group, anum_t anum) +{ + NNTP_DATA *nntp_data = ctx->data; + unsigned int i; + + if (group) + nntp_data = hash_find (nntp_data->nserv->groups_hash, group); + + if (!nntp_data) + return; + + for (i = 0; i < nntp_data->newsrc_len; i++) + { + if ((anum >= nntp_data->newsrc_ent[i].first) && + (anum <= nntp_data->newsrc_ent[i].last)) + { + /* can't use mutt_set_flag() because mx_update_context() + didn't called yet */ + hdr->read = 1; + return; + } + } + + /* article was not cached yet, it's new */ + if (anum > nntp_data->lastCached) + return; + + /* article isn't read but cached, it's old */ + if (option (OPTMARKOLD)) + hdr->old = 1; +} + +/* calculate number of unread articles using .newsrc data */ +void nntp_group_unread_stat (NNTP_DATA *nntp_data) +{ + unsigned int i; + anum_t first, last; + + nntp_data->unread = 0; + if (nntp_data->lastMessage == 0 || + nntp_data->firstMessage > nntp_data->lastMessage) + return; + + nntp_data->unread = nntp_data->lastMessage - nntp_data->firstMessage + 1; + for (i = 0; i < nntp_data->newsrc_len; i++) + { + first = nntp_data->newsrc_ent[i].first; + if (first < nntp_data->firstMessage) + first = nntp_data->firstMessage; + last = nntp_data->newsrc_ent[i].last; + if (last > nntp_data->lastMessage) + last = nntp_data->lastMessage; + if (first <= last) + nntp_data->unread -= last - first + 1; + } +} + +/* Subscribe newsgroup */ +NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER *nserv, char *group) +{ + NNTP_DATA *nntp_data; + + if (!nserv || !nserv->groups_hash || !group || !*group) + return NULL; + + nntp_data = nntp_data_find (nserv, group); + nntp_data->subscribed = 1; + if (!nntp_data->newsrc_ent) + { + nntp_data->newsrc_ent = safe_calloc (1, sizeof (NEWSRC_ENTRY)); + nntp_data->newsrc_len = 1; + nntp_data->newsrc_ent[0].first = 1; + nntp_data->newsrc_ent[0].last = 0; + } + return nntp_data; +} + +/* Unsubscribe newsgroup */ +NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER *nserv, char *group) +{ + NNTP_DATA *nntp_data; + + if (!nserv || !nserv->groups_hash || !group || !*group) + return NULL; + + nntp_data = hash_find (nserv->groups_hash, group); + if (!nntp_data) + return NULL; + + nntp_data->subscribed = 0; + if (!option (OPTSAVEUNSUB)) + { + nntp_data->newsrc_len = 0; + FREE (&nntp_data->newsrc_ent); + } + return nntp_data; +} + +/* Catchup newsgroup */ +NNTP_DATA *mutt_newsgroup_catchup (NNTP_SERVER *nserv, char *group) +{ + NNTP_DATA *nntp_data; + + if (!nserv || !nserv->groups_hash || !group || !*group) + return NULL; + + nntp_data = hash_find (nserv->groups_hash, group); + if (!nntp_data) + return NULL; + + if (nntp_data->newsrc_ent) + { + safe_realloc (&nntp_data->newsrc_ent, sizeof (NEWSRC_ENTRY)); + nntp_data->newsrc_len = 1; + nntp_data->newsrc_ent[0].first = 1; + nntp_data->newsrc_ent[0].last = nntp_data->lastMessage; + } + nntp_data->unread = 0; + if (Context && Context->data == nntp_data) + { + unsigned int i; + + for (i = 0; i < Context->msgcount; i++) + mutt_set_flag (Context, Context->hdrs[i], M_READ, 1); + } + return nntp_data; +} + +/* Uncatchup newsgroup */ +NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER *nserv, char *group) +{ + NNTP_DATA *nntp_data; + + if (!nserv || !nserv->groups_hash || !group || !*group) + return NULL; + + nntp_data = hash_find (nserv->groups_hash, group); + if (!nntp_data) + return NULL; + + if (nntp_data->newsrc_ent) + { + safe_realloc (&nntp_data->newsrc_ent, sizeof (NEWSRC_ENTRY)); + nntp_data->newsrc_len = 1; + nntp_data->newsrc_ent[0].first = 1; + nntp_data->newsrc_ent[0].last = nntp_data->firstMessage - 1; + } + if (Context && Context->data == nntp_data) + { + unsigned int i; + + nntp_data->unread = Context->msgcount; + for (i = 0; i < Context->msgcount; i++) + mutt_set_flag (Context, Context->hdrs[i], M_READ, 0); + } + else + nntp_data->unread = nntp_data->lastMessage - nntp_data->newsrc_ent[0].last; + return nntp_data; +} + +/* Get first newsgroup with new messages */ +void nntp_buffy (char *buf, size_t len) +{ + unsigned int i; + + for (i = 0; i < CurrentNewsSrv->groups_num; i++) + { + NNTP_DATA *nntp_data = CurrentNewsSrv->groups_list[i]; + + if (!nntp_data || !nntp_data->subscribed || !nntp_data->unread) + continue; + + if (Context && Context->magic == M_NNTP && + !mutt_strcmp (nntp_data->group, ((NNTP_DATA *)Context->data)->group)) + { + unsigned int i, unread = 0; + + for (i = 0; i < Context->msgcount; i++) + if (!Context->hdrs[i]->read && !Context->hdrs[i]->deleted) + unread++; + if (!unread) + continue; + } + strfcpy (buf, nntp_data->group, len); + break; + } +} diff --git a/nntp.c b/nntp.c new file mode 100644 index 0000000..c78d3fa --- /dev/null +++ b/nntp.c @@ -0,0 +1,2404 @@ +/* + * Copyright (C) 1998 Brandon Long + * Copyright (C) 1999 Andrej Gritsenko + * Copyright (C) 2000-2012 Vsevolod Volkov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mutt.h" +#include "mutt_curses.h" +#include "sort.h" +#include "mx.h" +#include "mime.h" +#include "rfc1524.h" +#include "rfc2047.h" +#include "mailbox.h" +#include "mutt_crypt.h" +#include "nntp.h" + +#if defined(USE_SSL) +#include "mutt_ssl.h" +#endif + +#ifdef HAVE_PGP +#include "pgp.h" +#endif + +#ifdef HAVE_SMIME +#include "smime.h" +#endif + +#if USE_HCACHE +#include "hcache.h" +#endif + +#include +#include +#include +#include + +#ifdef USE_SASL +#include +#include + +#include "mutt_sasl.h" +#endif + +static int nntp_connect_error (NNTP_SERVER *nserv) +{ + nserv->status = NNTP_NONE; + mutt_error _("Server closed connection!"); + mutt_sleep (2); + return -1; +} + +/* Get capabilities: + * -1 - error, connection is closed + * 0 - mode is reader, capabilities setted up + * 1 - need to switch to reader mode */ +static int nntp_capabilities (NNTP_SERVER *nserv) +{ + CONNECTION *conn = nserv->conn; + unsigned int mode_reader = 0; + char buf[LONG_STRING]; + char authinfo[LONG_STRING] = ""; + + nserv->hasCAPABILITIES = 0; + nserv->hasSTARTTLS = 0; + nserv->hasDATE = 0; + nserv->hasLIST_NEWSGROUPS = 0; + nserv->hasLISTGROUP = 0; + nserv->hasOVER = 0; + FREE (&nserv->authenticators); + + if (mutt_socket_write (conn, "CAPABILITIES\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + + /* no capabilities */ + if (mutt_strncmp ("101", buf, 3)) + return 1; + nserv->hasCAPABILITIES = 1; + + /* parse capabilities */ + do + { + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (!mutt_strcmp ("STARTTLS", buf)) + nserv->hasSTARTTLS = 1; + else if (!mutt_strcmp ("MODE-READER", buf)) + mode_reader = 1; + else if (!mutt_strcmp ("READER", buf)) + { + nserv->hasDATE = 1; + nserv->hasLISTGROUP = 1; + } + else if (!mutt_strncmp ("AUTHINFO ", buf, 9)) + { + safe_strcat (buf, sizeof (buf), " "); + strfcpy (authinfo, buf + 8, sizeof (authinfo)); + } +#ifdef USE_SASL + else if (!mutt_strncmp ("SASL ", buf, 5)) + { + char *p = buf + 5; + while (*p == ' ') + p++; + nserv->authenticators = safe_strdup (p); + } +#endif + else if (!mutt_strcmp ("OVER", buf)) + nserv->hasOVER = 1; + else if (!mutt_strncmp ("LIST ", buf, 5)) + { + char *p = strstr (buf, " NEWSGROUPS"); + if (p) + { + p += 11; + if (*p == '\0' || *p == ' ') + nserv->hasLIST_NEWSGROUPS = 1; + } + } + } while (mutt_strcmp (".", buf)); + *buf = '\0'; +#ifdef USE_SASL + if (nserv->authenticators && strcasestr (authinfo, " SASL ")) + strfcpy (buf, nserv->authenticators, sizeof (buf)); +#endif + if (strcasestr (authinfo, " USER ")) + { + if (*buf) + safe_strcat (buf, sizeof (buf), " "); + safe_strcat (buf, sizeof (buf), "USER"); + } + mutt_str_replace (&nserv->authenticators, buf); + + /* current mode is reader */ + if (nserv->hasLISTGROUP) + return 0; + + /* server is mode-switching, need to switch to reader mode */ + if (mode_reader) + return 1; + + mutt_socket_close (conn); + nserv->status = NNTP_BYE; + mutt_error _("Server doesn't support reader mode."); + mutt_sleep (2); + return -1; +} + +char *OverviewFmt = + "Subject:\0" + "From:\0" + "Date:\0" + "Message-ID:\0" + "References:\0" + "Content-Length:\0" + "Lines:\0" + "\0"; + +/* Detect supported commands */ +static int nntp_attempt_features (NNTP_SERVER *nserv) +{ + CONNECTION *conn = nserv->conn; + char buf[LONG_STRING]; + + /* no CAPABILITIES, trying DATE, LISTGROUP, LIST NEWSGROUPS */ + if (!nserv->hasCAPABILITIES) + { + if (mutt_socket_write (conn, "DATE\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("500", buf, 3)) + nserv->hasDATE = 1; + + if (mutt_socket_write (conn, "LISTGROUP\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("500", buf, 3)) + nserv->hasLISTGROUP = 1; + + if (mutt_socket_write (conn, "LIST NEWSGROUPS +\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("500", buf, 3)) + nserv->hasLIST_NEWSGROUPS = 1; + if (!mutt_strncmp ("215", buf, 3)) + { + do + { + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + } while (mutt_strcmp (".", buf)); + } + } + + /* no LIST NEWSGROUPS, trying XGTITLE */ + if (!nserv->hasLIST_NEWSGROUPS) + { + if (mutt_socket_write (conn, "XGTITLE\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("500", buf, 3)) + nserv->hasXGTITLE = 1; + } + + /* no OVER, trying XOVER */ + if (!nserv->hasOVER) + { + if (mutt_socket_write (conn, "XOVER\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("500", buf, 3)) + nserv->hasXOVER = 1; + } + + /* trying LIST OVERVIEW.FMT */ + if (nserv->hasOVER || nserv->hasXOVER) + { + if (mutt_socket_write (conn, "LIST OVERVIEW.FMT\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("215", buf, 3)) + nserv->overview_fmt = OverviewFmt; + else + { + int chunk, cont = 0; + size_t buflen = 2 * LONG_STRING, off = 0, b = 0; + + if (nserv->overview_fmt) + FREE (&nserv->overview_fmt); + nserv->overview_fmt = safe_malloc (buflen); + + while (1) + { + if (buflen - off < LONG_STRING) + { + buflen *= 2; + safe_realloc (&nserv->overview_fmt, buflen); + } + + chunk = mutt_socket_readln (nserv->overview_fmt + off, + buflen - off, conn); + if (chunk < 0) + { + FREE (&nserv->overview_fmt); + return nntp_connect_error (nserv); + } + + if (!cont && !mutt_strcmp (".", nserv->overview_fmt + off)) + break; + + cont = chunk >= buflen - off ? 1 : 0; + off += strlen (nserv->overview_fmt + off); + if (!cont) + { + char *colon; + + if (nserv->overview_fmt[b] == ':') + { + memmove (nserv->overview_fmt + b, + nserv->overview_fmt + b + 1, off - b - 1); + nserv->overview_fmt[off - 1] = ':'; + } + colon = strchr (nserv->overview_fmt + b, ':'); + if (!colon) + nserv->overview_fmt[off++] = ':'; + else if (strcmp (colon + 1, "full")) + off = colon + 1 - nserv->overview_fmt; + if (!strcasecmp (nserv->overview_fmt + b, "Bytes:")) + { + strcpy (nserv->overview_fmt + b, "Content-Length:"); + off = b + strlen (nserv->overview_fmt + b); + } + nserv->overview_fmt[off++] = '\0'; + b = off; + } + } + nserv->overview_fmt[off++] = '\0'; + safe_realloc (&nserv->overview_fmt, off); + } + } + return 0; +} + +/* Get login, password and authenticate */ +static int nntp_auth (NNTP_SERVER *nserv) +{ + CONNECTION *conn = nserv->conn; + char buf[LONG_STRING]; + char authenticators[LONG_STRING] = "USER"; + char *method, *a, *p; + unsigned char flags = conn->account.flags; + + while (1) + { + /* get login and password */ + if (mutt_account_getuser (&conn->account) || !conn->account.user[0] || + mutt_account_getpass (&conn->account) || !conn->account.pass[0]) + break; + + /* get list of authenticators */ + if (NntpAuthenticators && *NntpAuthenticators) + strfcpy (authenticators, NntpAuthenticators, sizeof (authenticators)); + else if (nserv->hasCAPABILITIES) + { + strfcpy (authenticators, NONULL (nserv->authenticators), + sizeof (authenticators)); + p = authenticators; + while (*p) + { + if (*p == ' ') + *p = ':'; + p++; + } + } + p = authenticators; + while (*p) + { + *p = ascii_toupper (*p); + p++; + } + + dprint (1, (debugfile, + "nntp_auth: available methods: %s\n", nserv->authenticators)); + a = authenticators; + while (1) + { + if (!a) + { + mutt_error _("No authenticators available"); + mutt_sleep (2); + break; + } + + method = a; + a = strchr (a, ':'); + if (a) + *a++ = '\0'; + + /* check authenticator */ + if (nserv->hasCAPABILITIES) + { + char *m; + + if (!nserv->authenticators) + continue; + m = strcasestr (nserv->authenticators, method); + if (!m) + continue; + if (m > nserv->authenticators && *(m - 1) != ' ') + continue; + m += strlen (method); + if (*m != '\0' && *m != ' ') + continue; + } + dprint (1, (debugfile, "nntp_auth: trying method %s\n", method)); + + /* AUTHINFO USER authentication */ + if (!strcmp (method, "USER")) + { + mutt_message (_("Authenticating (%s)..."), method); + snprintf (buf, sizeof (buf), "AUTHINFO USER %s\r\n", conn->account.user); + if (mutt_socket_write (conn, buf) < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + break; + + /* authenticated, password is not required */ + if (!mutt_strncmp ("281", buf, 3)) + return 0; + + /* username accepted, sending password */ + if (!mutt_strncmp ("381", buf, 3)) + { +#ifdef DEBUG + if (debuglevel < M_SOCK_LOG_FULL) + dprint (M_SOCK_LOG_CMD, (debugfile, + "%d> AUTHINFO PASS *\n", conn->fd)); +#endif + snprintf (buf, sizeof (buf), "AUTHINFO PASS %s\r\n", + conn->account.pass); + if (mutt_socket_write_d (conn, buf, -1, M_SOCK_LOG_FULL) < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + break; + + /* authenticated */ + if (!mutt_strncmp ("281", buf, 3)) + return 0; + } + + /* server doesn't support AUTHINFO USER, trying next method */ + if (*buf == '5') + continue; + } + + else + { +#ifdef USE_SASL + sasl_conn_t *saslconn; + sasl_interact_t *interaction = NULL; + int rc; + char inbuf[LONG_STRING] = ""; + const char *mech; + const char *client_out = NULL; + unsigned int client_len, len; + + if (mutt_sasl_client_new (conn, &saslconn) < 0) + { + dprint (1, (debugfile, + "nntp_auth: error allocating SASL connection.\n")); + continue; + } + + while (1) + { + rc = sasl_client_start (saslconn, method, &interaction, + &client_out, &client_len, &mech); + if (rc != SASL_INTERACT) + break; + mutt_sasl_interact (interaction); + } + if (rc != SASL_OK && rc != SASL_CONTINUE) + { + sasl_dispose (&saslconn); + dprint (1, (debugfile, + "nntp_auth: error starting SASL authentication exchange.\n")); + continue; + } + + mutt_message (_("Authenticating (%s)..."), method); + snprintf (buf, sizeof (buf), "AUTHINFO SASL %s", method); + + /* looping protocol */ + while (rc == SASL_CONTINUE || (rc == SASL_OK && client_len)) + { + /* send out client response */ + if (client_len) + { +#ifdef DEBUG + if (debuglevel >= M_SOCK_LOG_FULL) + { + char tmp[LONG_STRING]; + memcpy (tmp, client_out, client_len); + for (p = tmp; p < tmp + client_len; p++) + { + if (*p == '\0') + *p = '.'; + } + *p = '\0'; + dprint (1, (debugfile, "SASL> %s\n", tmp)); + } +#endif + + if (*buf) + safe_strcat (buf, sizeof (buf), " "); + len = strlen (buf); + if (sasl_encode64 (client_out, client_len, + buf + len, sizeof (buf) - len, &len) != SASL_OK) + { + dprint (1, (debugfile, + "nntp_auth: error base64-encoding client response.\n")); + break; + } + } + + safe_strcat (buf, sizeof (buf), "\r\n"); +#ifdef DEBUG + if (debuglevel < M_SOCK_LOG_FULL) + { + if (strchr (buf, ' ')) + dprint (M_SOCK_LOG_CMD, (debugfile, "%d> AUTHINFO SASL %s%s\n", + conn->fd, method, client_len ? " sasl_data" : "")); + else + dprint (M_SOCK_LOG_CMD, (debugfile, "%d> sasl_data\n", conn->fd)); + } +#endif + client_len = 0; + if (mutt_socket_write_d (conn, buf, -1, M_SOCK_LOG_FULL) < 0 || + mutt_socket_readln_d (inbuf, sizeof (inbuf), conn, M_SOCK_LOG_FULL) < 0) + break; + if (mutt_strncmp (inbuf, "283 ", 4) && + mutt_strncmp (inbuf, "383 ", 4)) + { +#ifdef DEBUG + if (debuglevel < M_SOCK_LOG_FULL) + dprint (M_SOCK_LOG_CMD, (debugfile, "%d< %s\n", conn->fd, inbuf)); +#endif + break; + } +#ifdef DEBUG + if (debuglevel < M_SOCK_LOG_FULL) + { + inbuf[3] = '\0'; + dprint (M_SOCK_LOG_CMD, (debugfile, + "%d< %s sasl_data\n", conn->fd, inbuf)); + } +#endif + + if (!strcmp ("=", inbuf + 4)) + len = 0; + else if (sasl_decode64 (inbuf + 4, strlen (inbuf + 4), + buf, sizeof (buf) - 1, &len) != SASL_OK) + { + dprint (1, (debugfile, + "nntp_auth: error base64-decoding server response.\n")); + break; + } +#ifdef DEBUG + else if (debuglevel >= M_SOCK_LOG_FULL) + { + char tmp[LONG_STRING]; + memcpy (tmp, buf, len); + for (p = tmp; p < tmp + len; p++) + { + if (*p == '\0') + *p = '.'; + } + *p = '\0'; + dprint (1, (debugfile, "SASL< %s\n", tmp)); + } +#endif + + while (1) + { + rc = sasl_client_step (saslconn, buf, len, + &interaction, &client_out, &client_len); + if (rc != SASL_INTERACT) + break; + mutt_sasl_interact (interaction); + } + if (*inbuf != '3') + break; + + *buf = '\0'; + } /* looping protocol */ + + if (rc == SASL_OK && client_len == 0 && *inbuf == '2') + { + mutt_sasl_setup_conn (conn, saslconn); + return 0; + } + + /* terminate SASL sessoin */ + sasl_dispose (&saslconn); + if (conn->fd < 0) + break; + if (!mutt_strncmp (inbuf, "383 ", 4)) + { + if (mutt_socket_write (conn, "*\r\n") < 0 || + mutt_socket_readln (inbuf, sizeof (inbuf), conn) < 0) + break; + } + + /* server doesn't support AUTHINFO SASL, trying next method */ + if (*inbuf == '5') + continue; +#else + continue; +#endif /* USE_SASL */ + } + + mutt_error (_("%s authentication failed."), method); + mutt_sleep (2); + break; + } + break; + } + + /* error */ + nserv->status = NNTP_BYE; + conn->account.flags = flags; + if (conn->fd < 0) + { + mutt_error _("Server closed connection!"); + mutt_sleep (2); + } + else + mutt_socket_close (conn); + return -1; +} + +/* Connect to server, authenticate and get capabilities */ +int nntp_open_connection (NNTP_SERVER *nserv) +{ + CONNECTION *conn = nserv->conn; + char buf[STRING]; + int cap; + unsigned int posting = 0, auth = 1; + + if (nserv->status == NNTP_OK) + return 0; + if (nserv->status == NNTP_BYE) + return -1; + nserv->status = NNTP_NONE; + + if (mutt_socket_open (conn) < 0) + return -1; + + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + + if (!mutt_strncmp ("200", buf, 3)) + posting = 1; + else if (mutt_strncmp ("201", buf, 3)) + { + mutt_socket_close (conn); + mutt_remove_trailing_ws (buf); + mutt_error ("%s", buf); + mutt_sleep (2); + return -1; + } + + /* get initial capabilities */ + cap = nntp_capabilities (nserv); + if (cap < 0) + return -1; + + /* tell news server to switch to mode reader if it isn't so */ + if (cap > 0) + { + if (mutt_socket_write (conn, "MODE READER\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + + if (!mutt_strncmp ("200", buf, 3)) + posting = 1; + else if (!mutt_strncmp ("201", buf, 3)) + posting = 0; + /* error if has capabilities, ignore result if no capabilities */ + else if (nserv->hasCAPABILITIES) + { + mutt_socket_close (conn); + mutt_error _("Could not switch to reader mode."); + mutt_sleep (2); + return -1; + } + + /* recheck capabilities after MODE READER */ + if (nserv->hasCAPABILITIES) + { + cap = nntp_capabilities (nserv); + if (cap < 0) + return -1; + } + } + + mutt_message (_("Connected to %s. %s"), conn->account.host, + posting ? _("Posting is ok.") : _("Posting is NOT ok.")); + mutt_sleep (1); + +#if defined(USE_SSL) + /* Attempt STARTTLS if available and desired. */ + if (nserv->use_tls != 1 && (nserv->hasSTARTTLS || option (OPTSSLFORCETLS))) + { + if (nserv->use_tls == 0) + nserv->use_tls = option (OPTSSLFORCETLS) || + query_quadoption (OPT_SSLSTARTTLS, + _("Secure connection with TLS?")) == M_YES ? 2 : 1; + if (nserv->use_tls == 2) + { + if (mutt_socket_write (conn, "STARTTLS\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("382", buf, 3)) + { + nserv->use_tls = 0; + mutt_error ("STARTTLS: %s", buf); + mutt_sleep (2); + } + else if (mutt_ssl_starttls (conn)) + { + nserv->use_tls = 0; + nserv->status = NNTP_NONE; + mutt_socket_close (nserv->conn); + mutt_error _("Could not negotiate TLS connection"); + mutt_sleep (2); + return -1; + } + else + { + /* recheck capabilities after STARTTLS */ + cap = nntp_capabilities (nserv); + if (cap < 0) + return -1; + } + } + } +#endif + + /* authentication required? */ + if (conn->account.flags & M_ACCT_USER) + { + if (!conn->account.user[0]) + auth = 0; + } + else + { + if (mutt_socket_write (conn, "STAT\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("480", buf, 3)) + auth = 0; + } + + /* authenticate */ + if (auth && nntp_auth (nserv) < 0) + return -1; + + /* get final capabilities after authentication */ + if (nserv->hasCAPABILITIES && (auth || cap > 0)) + { + cap = nntp_capabilities (nserv); + if (cap < 0) + return -1; + if (cap > 0) + { + mutt_socket_close (conn); + mutt_error _("Could not switch to reader mode."); + mutt_sleep (2); + return -1; + } + } + + /* attempt features */ + if (nntp_attempt_features (nserv) < 0) + return -1; + + nserv->status = NNTP_OK; + return 0; +} + +/* Send data from buffer and receive answer to same buffer */ +static int nntp_query (NNTP_DATA *nntp_data, char *line, size_t linelen) +{ + NNTP_SERVER *nserv = nntp_data->nserv; + char buf[LONG_STRING]; + + if (nserv->status == NNTP_BYE) + return -1; + + while (1) + { + if (nserv->status == NNTP_OK) + { + int rc = 0; + + if (*line) + rc = mutt_socket_write (nserv->conn, line); + else if (nntp_data->group) + { + snprintf (buf, sizeof (buf), "GROUP %s\r\n", nntp_data->group); + rc = mutt_socket_write (nserv->conn, buf); + } + if (rc >= 0) + rc = mutt_socket_readln (buf, sizeof (buf), nserv->conn); + if (rc >= 0) + break; + } + + /* reconnect */ + while (1) + { + nserv->status = NNTP_NONE; + if (nntp_open_connection (nserv) == 0) + break; + + snprintf (buf, sizeof (buf), _("Connection to %s lost. Reconnect?"), + nserv->conn->account.host); + if (mutt_yesorno (buf, M_YES) != M_YES) + { + nserv->status = NNTP_BYE; + return -1; + } + } + + /* select newsgroup after reconnection */ + if (nntp_data->group) + { + snprintf (buf, sizeof (buf), "GROUP %s\r\n", nntp_data->group); + if (mutt_socket_write (nserv->conn, buf) < 0 || + mutt_socket_readln (buf, sizeof (buf), nserv->conn) < 0) + return nntp_connect_error (nserv); + } + if (!*line) + break; + } + + strfcpy (line, buf, linelen); + return 0; +} + +/* This function calls funct(*line, *data) for each received line, + * funct(NULL, *data) if rewind(*data) needs, exits when fail or done: + * 0 - success + * 1 - bad response (answer in query buffer) + * -1 - conection lost + * -2 - error in funct(*line, *data) */ +static int nntp_fetch_lines (NNTP_DATA *nntp_data, char *query, size_t qlen, + char *msg, int (*funct) (char *, void *), void *data) +{ + int done = FALSE; + int rc; + + while (!done) + { + char buf[LONG_STRING]; + char *line; + unsigned int lines = 0; + size_t off = 0; + progress_t progress; + + if (msg) + mutt_progress_init (&progress, msg, M_PROGRESS_MSG, ReadInc, -1); + + strfcpy (buf, query, sizeof (buf)); + if (nntp_query (nntp_data, buf, sizeof (buf)) < 0) + return -1; + if (buf[0] != '2') + { + strfcpy (query, buf, qlen); + return 1; + } + + line = safe_malloc (sizeof (buf)); + rc = 0; + + while (1) + { + char *p; + int chunk = mutt_socket_readln_d (buf, sizeof (buf), + nntp_data->nserv->conn, M_SOCK_LOG_HDR); + if (chunk < 0) + { + nntp_data->nserv->status = NNTP_NONE; + break; + } + + p = buf; + if (!off && buf[0] == '.') + { + if (buf[1] == '\0') + { + done = TRUE; + break; + } + if (buf[1] == '.') + p++; + } + + strfcpy (line + off, p, sizeof (buf)); + + if (chunk >= sizeof (buf)) + off += strlen (p); + else + { + if (msg) + mutt_progress_update (&progress, ++lines, -1); + + if (rc == 0 && funct (line, data) < 0) + rc = -2; + off = 0; + } + + safe_realloc (&line, off + sizeof (buf)); + } + FREE (&line); + funct (NULL, data); + } + return rc; +} + +/* Parse newsgroup description */ +static int fetch_description (char *line, void *data) +{ + NNTP_SERVER *nserv = data; + NNTP_DATA *nntp_data; + char *desc; + + if (!line) + return 0; + + desc = strpbrk (line, " \t"); + if (desc) + { + *desc++ = '\0'; + desc += strspn (desc, " \t"); + } + else + desc = strchr (line, '\0'); + + nntp_data = hash_find (nserv->groups_hash, line); + if (nntp_data && mutt_strcmp (desc, nntp_data->desc)) + { + mutt_str_replace (&nntp_data->desc, desc); + dprint (2, (debugfile, "group: %s, desc: %s\n", line, desc)); + } + return 0; +} + +/* Fetch newsgroups descriptions. + * Returns the same code as nntp_fetch_lines() */ +static int get_description (NNTP_DATA *nntp_data, char *wildmat, char *msg) +{ + NNTP_SERVER *nserv; + char buf[STRING]; + char *cmd; + int rc; + + /* get newsgroup description, if possible */ + nserv = nntp_data->nserv; + if (!wildmat) + wildmat = nntp_data->group; + if (nserv->hasLIST_NEWSGROUPS) + cmd = "LIST NEWSGROUPS"; + else if (nserv->hasXGTITLE) + cmd = "XGTITLE"; + else + return 0; + + snprintf (buf, sizeof (buf), "%s %s\r\n", cmd, wildmat); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), msg, + fetch_description, nserv); + if (rc > 0) + { + mutt_error ("%s: %s", cmd, buf); + mutt_sleep (2); + } + return rc; +} + +/* Update read flag and set article number if empty */ +static void nntp_parse_xref (CONTEXT *ctx, HEADER *hdr) +{ + NNTP_DATA *nntp_data = ctx->data; + char *buf, *p; + + buf = p = safe_strdup (hdr->env->xref); + while (p) + { + char *grp, *colon; + anum_t anum; + + /* skip to next word */ + p += strspn (p, " \t"); + grp = p; + + /* skip to end of word */ + p = strpbrk (p, " \t"); + if (p) + *p++ = '\0'; + + /* find colon */ + colon = strchr (grp, ':'); + if (!colon) + continue; + *colon++ = '\0'; + if (sscanf (colon, ANUM, &anum) != 1) + continue; + + nntp_article_status (ctx, hdr, grp, anum); + if (hdr && !NHDR (hdr)->article_num && !mutt_strcmp (nntp_data->group, grp)) + NHDR (hdr)->article_num = anum; + } + FREE (&buf); +} + +/* Write line to temporarily file */ +static int fetch_tempfile (char *line, void *data) +{ + FILE *fp = data; + + if (!line) + rewind (fp); + else if (fputs (line, fp) == EOF || fputc ('\n', fp) == EOF) + return -1; + return 0; +} + +typedef struct +{ + CONTEXT *ctx; + anum_t first; + anum_t last; + int restore; + unsigned char *messages; + progress_t progress; +#ifdef USE_HCACHE + header_cache_t *hc; +#endif +} FETCH_CTX; + +/* Parse article number */ +static int fetch_numbers (char *line, void *data) +{ + FETCH_CTX *fc = data; + anum_t anum; + + if (!line) + return 0; + if (sscanf (line, ANUM, &anum) != 1) + return 0; + if (anum < fc->first || anum > fc->last) + return 0; + fc->messages[anum - fc->first] = 1; + return 0; +} + +/* Parse overview line */ +static int parse_overview_line (char *line, void *data) +{ + FETCH_CTX *fc = data; + CONTEXT *ctx = fc->ctx; + NNTP_DATA *nntp_data = ctx->data; + HEADER *hdr; + FILE *fp; + char tempfile[_POSIX_PATH_MAX]; + char *header, *field; + int save = 1; + anum_t anum; + + if (!line) + return 0; + + /* parse article number */ + field = strchr (line, '\t'); + if (field) + *field++ = '\0'; + if (sscanf (line, ANUM, &anum) != 1) + return 0; + dprint (2, (debugfile, "parse_overview_line: " ANUM "\n", anum)); + + /* out of bounds */ + if (anum < fc->first || anum > fc->last) + return 0; + + /* not in LISTGROUP */ + if (!fc->messages[anum - fc->first]) + { + /* progress */ + if (!ctx->quiet) + mutt_progress_update (&fc->progress, anum - fc->first + 1, -1); + return 0; + } + + /* convert overview line to header */ + mutt_mktemp (tempfile, sizeof (tempfile)); + fp = safe_fopen (tempfile, "w+"); + if (!fp) + return -1; + + header = nntp_data->nserv->overview_fmt; + while (field) + { + char *b = field; + + if (*header) + { + if (strstr (header, ":full") == NULL && fputs (header, fp) == EOF) + { + fclose (fp); + unlink (tempfile); + return -1; + } + header = strchr (header, '\0') + 1; + } + + field = strchr (field, '\t'); + if (field) + *field++ = '\0'; + if (fputs (b, fp) == EOF || fputc ('\n', fp) == EOF) + { + fclose (fp); + unlink (tempfile); + return -1; + } + } + rewind (fp); + + /* allocate memory for headers */ + if (ctx->msgcount >= ctx->hdrmax) + mx_alloc_memory (ctx); + + /* parse header */ + hdr = ctx->hdrs[ctx->msgcount] = mutt_new_header (); + hdr->env = mutt_read_rfc822_header (fp, hdr, 0, 0); + hdr->env->newsgroups = safe_strdup (nntp_data->group); + hdr->received = hdr->date_sent; + fclose (fp); + unlink (tempfile); + +#ifdef USE_HCACHE + if (fc->hc) + { + void *hdata; + char buf[16]; + + /* try to replace with header from cache */ + snprintf (buf, sizeof (buf), "%d", anum); + hdata = mutt_hcache_fetch (fc->hc, buf, strlen); + if (hdata) + { + dprint (2, (debugfile, + "parse_overview_line: mutt_hcache_fetch %s\n", buf)); + mutt_free_header (&hdr); + ctx->hdrs[ctx->msgcount] = + hdr = mutt_hcache_restore (hdata, NULL); + hdr->read = 0; + hdr->old = 0; + + /* skip header marked as deleted in cache */ + if (hdr->deleted && !fc->restore) + { + if (nntp_data->bcache) + { + dprint (2, (debugfile, + "parse_overview_line: mutt_bcache_del %s\n", buf)); + mutt_bcache_del (nntp_data->bcache, buf); + } + save = 0; + } + } + + /* not chached yet, store header */ + else + { + dprint (2, (debugfile, + "parse_overview_line: mutt_hcache_store %s\n", buf)); + mutt_hcache_store (fc->hc, buf, hdr, 0, strlen, M_GENERATE_UIDVALIDITY); + } + } +#endif + + if (save) + { + hdr->index = ctx->msgcount++; + hdr->read = 0; + hdr->old = 0; + hdr->deleted = 0; + hdr->data = safe_calloc (1, sizeof (NNTP_HEADER_DATA)); + NHDR (hdr)->article_num = anum; + if (fc->restore) + hdr->changed = 1; + else + { + nntp_article_status (ctx, hdr, NULL, anum); + if (!hdr->read) + nntp_parse_xref (ctx, hdr); + } + if (anum > nntp_data->lastLoaded) + nntp_data->lastLoaded = anum; + } + else + mutt_free_header (&hdr); + + /* progress */ + if (!ctx->quiet) + mutt_progress_update (&fc->progress, anum - fc->first + 1, -1); + return 0; +} + +/* Fetch headers */ +static int nntp_fetch_headers (CONTEXT *ctx, void *hc, + anum_t first, anum_t last, int restore) +{ + NNTP_DATA *nntp_data = ctx->data; + FETCH_CTX fc; + HEADER *hdr; + char buf[HUGE_STRING]; + int rc = 0; + int oldmsgcount = ctx->msgcount; + anum_t current; +#ifdef USE_HCACHE + void *hdata; +#endif + + /* if empty group or nothing to do */ + if (!last || first > last) + return 0; + + /* init fetch context */ + fc.ctx = ctx; + fc.first = first; + fc.last = last; + fc.restore = restore; + fc.messages = safe_calloc (last - first + 1, sizeof (unsigned char)); +#ifdef USE_HCACHE + fc.hc = hc; +#endif + + /* fetch list of articles */ + if (nntp_data->nserv->hasLISTGROUP && !nntp_data->deleted) + { + if (!ctx->quiet) + mutt_message _("Fetching list of articles..."); + snprintf (buf, sizeof (buf), "LISTGROUP %s\r\n", nntp_data->group); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL, + fetch_numbers, &fc); + if (rc > 0) + { + mutt_error ("LISTGROUP: %s", buf); + mutt_sleep (2); + } + if (rc == 0) + { + for (current = first; current <= last && rc == 0; current++) + { + if (fc.messages[current - first]) + continue; + + snprintf (buf, sizeof (buf), "%d", current); + if (nntp_data->bcache) + { + dprint (2, (debugfile, + "nntp_fetch_headers: mutt_bcache_del %s\n", buf)); + mutt_bcache_del (nntp_data->bcache, buf); + } + +#ifdef USE_HCACHE + if (fc.hc) + { + dprint (2, (debugfile, + "nntp_fetch_headers: mutt_hcache_delete %s\n", buf)); + mutt_hcache_delete (fc.hc, buf, strlen); + } +#endif + } + } + } + else + for (current = first; current <= last; current++) + fc.messages[current - first] = 1; + + /* fetching header from cache or server, or fallback to fetch overview */ + if (!ctx->quiet) + mutt_progress_init (&fc.progress, _("Fetching message headers..."), + M_PROGRESS_MSG, ReadInc, last - first + 1); + for (current = first; current <= last && rc == 0; current++) + { + if (!ctx->quiet) + mutt_progress_update (&fc.progress, current - first + 1, -1); + +#ifdef USE_HCACHE + snprintf (buf, sizeof (buf), "%d", current); +#endif + + /* delete header from cache that does not exist on server */ + if (!fc.messages[current - first]) + continue; + + /* allocate memory for headers */ + if (ctx->msgcount >= ctx->hdrmax) + mx_alloc_memory (ctx); + +#ifdef USE_HCACHE + /* try to fetch header from cache */ + hdata = mutt_hcache_fetch (fc.hc, buf, strlen); + if (hdata) + { + dprint (2, (debugfile, + "nntp_fetch_headers: mutt_hcache_fetch %s\n", buf)); + ctx->hdrs[ctx->msgcount] = + hdr = mutt_hcache_restore (hdata, NULL); + + /* skip header marked as deleted in cache */ + if (hdr->deleted && !restore) + { + mutt_free_header (&hdr); + if (nntp_data->bcache) + { + dprint (2, (debugfile, + "nntp_fetch_headers: mutt_bcache_del %s\n", buf)); + mutt_bcache_del (nntp_data->bcache, buf); + } + continue; + } + + hdr->read = 0; + hdr->old = 0; + } + else +#endif + + /* don't try to fetch header from removed newsgroup */ + if (nntp_data->deleted) + continue; + + /* fallback to fetch overview */ + else if (nntp_data->nserv->hasOVER || nntp_data->nserv->hasXOVER) + break; + + /* fetch header from server */ + else + { + FILE *fp; + char tempfile[_POSIX_PATH_MAX]; + + mutt_mktemp (tempfile, sizeof (tempfile)); + fp = safe_fopen (tempfile, "w+"); + if (!fp) + { + mutt_perror (tempfile); + mutt_sleep (2); + unlink (tempfile); + rc = -1; + break; + } + + snprintf (buf, sizeof (buf), "HEAD %d\r\n", current); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL, + fetch_tempfile, fp); + if (rc) + { + fclose (fp); + unlink (tempfile); + if (rc < 0) + break; + + /* invalid response */ + if (mutt_strncmp ("423", buf, 3)) + { + mutt_error ("HEAD: %s", buf); + mutt_sleep (2); + break; + } + + /* no such article */ + if (nntp_data->bcache) + { + snprintf (buf, sizeof (buf), "%d", current); + dprint (2, (debugfile, + "nntp_fetch_headers: mutt_bcache_del %s\n", buf)); + mutt_bcache_del (nntp_data->bcache, buf); + } + rc = 0; + continue; + } + + /* parse header */ + hdr = ctx->hdrs[ctx->msgcount] = mutt_new_header (); + hdr->env = mutt_read_rfc822_header (fp, hdr, 0, 0); + hdr->received = hdr->date_sent; + fclose (fp); + unlink (tempfile); + } + + /* save header in context */ + hdr->index = ctx->msgcount++; + hdr->read = 0; + hdr->old = 0; + hdr->deleted = 0; + hdr->data = safe_calloc (1, sizeof (NNTP_HEADER_DATA)); + NHDR (hdr)->article_num = current; + if (restore) + hdr->changed = 1; + else + { + nntp_article_status (ctx, hdr, NULL, NHDR (hdr)->article_num); + if (!hdr->read) + nntp_parse_xref (ctx, hdr); + } + if (current > nntp_data->lastLoaded) + nntp_data->lastLoaded = current; + } + + /* fetch overview information */ + if (current <= last && rc == 0) { + char *cmd = nntp_data->nserv->hasOVER ? "OVER" : "XOVER"; + snprintf (buf, sizeof (buf), "%s %d-%d\r\n", cmd, current, last); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL, + parse_overview_line, &fc); + if (rc > 0) + { + mutt_error ("%s: %s", cmd, buf); + mutt_sleep (2); + } + } + + if (ctx->msgcount > oldmsgcount) + mx_update_context (ctx, ctx->msgcount - oldmsgcount); + + FREE (&fc.messages); + if (rc != 0) + return -1; + mutt_clear_error (); + return 0; +} + +/* Open newsgroup */ +int nntp_open_mailbox (CONTEXT *ctx) +{ + NNTP_SERVER *nserv; + NNTP_DATA *nntp_data; + char buf[HUGE_STRING]; + char server[LONG_STRING]; + char *group; + int rc; + void *hc = NULL; + anum_t first, last, count = 0; + ciss_url_t url; + + strfcpy (buf, ctx->path, sizeof (buf)); + if (url_parse_ciss (&url, buf) < 0 || !url.path || + !(url.scheme == U_NNTP || url.scheme == U_NNTPS)) + { + mutt_error (_("%s is an invalid newsgroup specification!"), ctx->path); + mutt_sleep (2); + return -1; + } + + group = url.path; + url.path = strchr (url.path, '\0'); + url_ciss_tostring (&url, server, sizeof (server), 0); + nserv = nntp_select_server (server, 1); + if (!nserv) + return -1; + CurrentNewsSrv = nserv; + + /* find news group data structure */ + nntp_data = hash_find (nserv->groups_hash, group); + if (!nntp_data) + { + nntp_newsrc_close (nserv); + mutt_error (_("Newsgroup %s not found on the server."), group); + mutt_sleep (2); + return -1; + } + + mutt_bit_unset (ctx->rights, M_ACL_INSERT); + if (!nntp_data->newsrc_ent && !nntp_data->subscribed && + !option (OPTSAVEUNSUB)) + ctx->readonly = 1; + + /* select newsgroup */ + mutt_message (_("Selecting %s..."), group); + buf[0] = '\0'; + if (nntp_query (nntp_data, buf, sizeof (buf)) < 0) + { + nntp_newsrc_close (nserv); + return -1; + } + + /* newsgroup not found, remove it */ + if (!mutt_strncmp ("411", buf, 3)) + { + mutt_error (_("Newsgroup %s has been removed from the server."), + nntp_data->group); + if (!nntp_data->deleted) + { + nntp_data->deleted = 1; + nntp_active_save_cache (nserv); + } + if (nntp_data->newsrc_ent && !nntp_data->subscribed && + !option (OPTSAVEUNSUB)) + { + FREE (&nntp_data->newsrc_ent); + nntp_data->newsrc_len = 0; + nntp_delete_group_cache (nntp_data); + nntp_newsrc_update (nserv); + } + mutt_sleep (2); + } + + /* parse newsgroup info */ + else { + if (sscanf (buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3) + { + nntp_newsrc_close (nserv); + mutt_error ("GROUP: %s", buf); + mutt_sleep (2); + return -1; + } + nntp_data->firstMessage = first; + nntp_data->lastMessage = last; + nntp_data->deleted = 0; + + /* get description if empty */ + if (option (OPTLOADDESC) && !nntp_data->desc) + { + if (get_description (nntp_data, NULL, NULL) < 0) + { + nntp_newsrc_close (nserv); + return -1; + } + if (nntp_data->desc) + nntp_active_save_cache (nserv); + } + } + + time (&nserv->check_time); + ctx->data = nntp_data; + ctx->mx_close = nntp_fastclose_mailbox; + if (!nntp_data->bcache && (nntp_data->newsrc_ent || + nntp_data->subscribed || option (OPTSAVEUNSUB))) + nntp_data->bcache = mutt_bcache_open (&nserv->conn->account, + nntp_data->group); + + /* strip off extra articles if adding context is greater than $nntp_context */ + first = nntp_data->firstMessage; + if (NntpContext && nntp_data->lastMessage - first + 1 > NntpContext) + first = nntp_data->lastMessage - NntpContext + 1; + nntp_data->lastLoaded = first ? first - 1 : 0; + count = nntp_data->firstMessage; + nntp_data->firstMessage = first; + nntp_bcache_update (nntp_data); + nntp_data->firstMessage = count; +#ifdef USE_HCACHE + hc = nntp_hcache_open (nntp_data); + nntp_hcache_update (nntp_data, hc); +#endif + if (!hc) + { + mutt_bit_unset (ctx->rights, M_ACL_WRITE); + mutt_bit_unset (ctx->rights, M_ACL_DELETE); + } + nntp_newsrc_close (nserv); + rc = nntp_fetch_headers (ctx, hc, first, nntp_data->lastMessage, 0); +#ifdef USE_HCACHE + mutt_hcache_close (hc); +#endif + if (rc < 0) + return -1; + nntp_data->lastLoaded = nntp_data->lastMessage; + nserv->newsrc_modified = 0; + return 0; +} + +/* Fetch message */ +int nntp_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno) +{ + NNTP_DATA *nntp_data = ctx->data; + NNTP_ACACHE *acache; + HEADER *hdr = ctx->hdrs[msgno]; + char buf[_POSIX_PATH_MAX]; + char article[16]; + char *fetch_msg = _("Fetching message..."); + int rc; + + /* try to get article from cache */ + acache = &nntp_data->acache[hdr->index % NNTP_ACACHE_LEN]; + if (acache->path) + { + if (acache->index == hdr->index) + { + msg->fp = fopen (acache->path, "r"); + if (msg->fp) + return 0; + } + /* clear previous entry */ + else + { + unlink (acache->path); + FREE (&acache->path); + } + } + snprintf (article, sizeof (article), "%d", NHDR (hdr)->article_num); + msg->fp = mutt_bcache_get (nntp_data->bcache, article); + if (msg->fp) + { + if (NHDR (hdr)->parsed) + return 0; + } + else + { + /* don't try to fetch article from removed newsgroup */ + if (nntp_data->deleted) + return -1; + + /* create new cache file */ + mutt_message (fetch_msg); + msg->fp = mutt_bcache_put (nntp_data->bcache, article, 1); + if (!msg->fp) + { + mutt_mktemp (buf, sizeof (buf)); + acache->path = safe_strdup (buf); + acache->index = hdr->index; + msg->fp = safe_fopen (acache->path, "w+"); + if (!msg->fp) + { + mutt_perror (acache->path); + unlink (acache->path); + FREE (&acache->path); + return -1; + } + } + + /* fetch message to cache file */ + snprintf (buf, sizeof (buf), "ARTICLE %s\r\n", + NHDR (hdr)->article_num ? article : hdr->env->message_id); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), fetch_msg, + fetch_tempfile, msg->fp); + if (rc) + { + safe_fclose (&msg->fp); + if (acache->path) + { + unlink (acache->path); + FREE (&acache->path); + } + if (rc > 0) + { + if (!mutt_strncmp (NHDR (hdr)->article_num ? "423" : "430", buf, 3)) + mutt_error (_("Article %d not found on the server."), + NHDR (hdr)->article_num ? article : hdr->env->message_id); + else + mutt_error ("ARTICLE: %s", buf); + } + return -1; + } + + if (!acache->path) + mutt_bcache_commit (nntp_data->bcache, article); + } + + /* replace envelope with new one + * hash elements must be updated because pointers will be changed */ + if (ctx->id_hash && hdr->env->message_id) + hash_delete (ctx->id_hash, hdr->env->message_id, hdr, NULL); + if (ctx->subj_hash && hdr->env->real_subj) + hash_delete (ctx->subj_hash, hdr->env->real_subj, hdr, NULL); + + mutt_free_envelope (&hdr->env); + hdr->env = mutt_read_rfc822_header (msg->fp, hdr, 0, 0); + + if (ctx->id_hash && hdr->env->message_id) + hash_insert (ctx->id_hash, hdr->env->message_id, hdr, 0); + if (ctx->subj_hash && hdr->env->real_subj) + hash_insert (ctx->subj_hash, hdr->env->real_subj, hdr, 1); + + /* fix content length */ + fseek (msg->fp, 0, SEEK_END); + hdr->content->length = ftell (msg->fp) - hdr->content->offset; + + /* this is called in mutt before the open which fetches the message, + * which is probably wrong, but we just call it again here to handle + * the problem instead of fixing it */ + NHDR (hdr)->parsed = 1; + mutt_parse_mime_message (ctx, hdr); + + /* these would normally be updated in mx_update_context(), but the + * full headers aren't parsed with overview, so the information wasn't + * available then */ + if (WithCrypto) + hdr->security = crypt_query (hdr->content); + + rewind (msg->fp); + mutt_clear_error(); + return 0; +} + +/* Post article */ +int nntp_post (const char *msg) { + NNTP_DATA *nntp_data, nntp_tmp; + FILE *fp; + char buf[LONG_STRING]; + size_t len; + + if (Context && Context->magic == M_NNTP) + nntp_data = Context->data; + else + { + CurrentNewsSrv = nntp_select_server (NewsServer, 0); + if (!CurrentNewsSrv) + return -1; + + nntp_data = &nntp_tmp; + nntp_data->nserv = CurrentNewsSrv; + nntp_data->group = NULL; + } + + fp = safe_fopen (msg, "r"); + if (!fp) + { + mutt_perror (msg); + return -1; + } + + strfcpy (buf, "POST\r\n", sizeof (buf)); + if (nntp_query (nntp_data, buf, sizeof (buf)) < 0) + return -1; + if (buf[0] != '3') + { + mutt_error (_("Can't post article: %s"), buf); + return -1; + } + + buf[0] = '.'; + buf[1] = '\0'; + while (fgets (buf + 1, sizeof (buf) - 2, fp)) + { + len = strlen (buf); + if (buf[len - 1] == '\n') + { + buf[len - 1] = '\r'; + buf[len] = '\n'; + len++; + buf[len] = '\0'; + } + if (mutt_socket_write_d (nntp_data->nserv->conn, + buf[1] == '.' ? buf : buf + 1, -1, M_SOCK_LOG_HDR) < 0) + return nntp_connect_error (nntp_data->nserv); + } + fclose (fp); + + if ((buf[strlen (buf) - 1] != '\n' && + mutt_socket_write_d (nntp_data->nserv->conn, "\r\n", -1, M_SOCK_LOG_HDR) < 0) || + mutt_socket_write_d (nntp_data->nserv->conn, ".\r\n", -1, M_SOCK_LOG_HDR) < 0 || + mutt_socket_readln (buf, sizeof (buf), nntp_data->nserv->conn) < 0) + return nntp_connect_error (nntp_data->nserv); + if (buf[0] != '2') + { + mutt_error (_("Can't post article: %s"), buf); + return -1; + } + return 0; +} + +/* Save changes to .newsrc and cache */ +int nntp_sync_mailbox (CONTEXT *ctx) +{ + NNTP_DATA *nntp_data = ctx->data; + int rc, i; +#ifdef USE_HCACHE + header_cache_t *hc; +#endif + + /* check for new articles */ + nntp_data->nserv->check_time = 0; + rc = nntp_check_mailbox (ctx, 1); + if (rc) + return rc; + +#ifdef USE_HCACHE + nntp_data->lastCached = 0; + hc = nntp_hcache_open (nntp_data); +#endif + + nntp_data->unread = ctx->unread; + for (i = 0; i < ctx->msgcount; i++) + { + HEADER *hdr = ctx->hdrs[i]; + char buf[16]; + + snprintf (buf, sizeof (buf), "%d", NHDR (hdr)->article_num); + if (nntp_data->bcache && hdr->deleted) + { + dprint (2, (debugfile, "nntp_sync_mailbox: mutt_bcache_del %s\n", buf)); + mutt_bcache_del (nntp_data->bcache, buf); + } + +#ifdef USE_HCACHE + if (hc && (hdr->changed || hdr->deleted)) + { + if (hdr->deleted && !hdr->read) + nntp_data->unread--; + dprint (2, (debugfile, "nntp_sync_mailbox: mutt_hcache_store %s\n", buf)); + mutt_hcache_store (hc, buf, hdr, 0, strlen, M_GENERATE_UIDVALIDITY); + } +#endif + } + +#ifdef USE_HCACHE + if (hc) + { + mutt_hcache_close (hc); + nntp_data->lastCached = nntp_data->lastLoaded; + } +#endif + + /* save .newsrc entries */ + nntp_newsrc_gen_entries (ctx); + nntp_newsrc_update (nntp_data->nserv); + nntp_newsrc_close (nntp_data->nserv); + return 0; +} + +/* Free up memory associated with the newsgroup context */ +int nntp_fastclose_mailbox (CONTEXT *ctx) +{ + NNTP_DATA *nntp_data = ctx->data, *nntp_tmp; + + if (!nntp_data) + return 0; + + nntp_acache_free (nntp_data); + if (!nntp_data->nserv || !nntp_data->nserv->groups_hash || !nntp_data->group) + return 0; + + nntp_tmp = hash_find (nntp_data->nserv->groups_hash, nntp_data->group); + if (nntp_tmp == NULL || nntp_tmp != nntp_data) + nntp_data_free (nntp_data); + return 0; +} + +/* Get date and time from server */ +int nntp_date (NNTP_SERVER *nserv, time_t *now) +{ + if (nserv->hasDATE) + { + NNTP_DATA nntp_data; + char buf[LONG_STRING]; + struct tm tm; + + nntp_data.nserv = nserv; + nntp_data.group = NULL; + strfcpy (buf, "DATE\r\n", sizeof (buf)); + if (nntp_query (&nntp_data, buf, sizeof (buf)) < 0) + return -1; + + if (sscanf (buf, "111 %4d%2d%2d%2d%2d%2d%*s", &tm.tm_year, &tm.tm_mon, + &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) + { + tm.tm_year -= 1900; + tm.tm_mon--; + *now = timegm (&tm); + if (*now >= 0) + { + dprint (1, (debugfile, "nntp_date: server time is %d\n", *now)); + return 0; + } + } + } + time (now); + return 0; +} + +/* Fetch list of all newsgroups from server */ +int nntp_active_fetch (NNTP_SERVER *nserv) +{ + NNTP_DATA nntp_data; + char msg[SHORT_STRING]; + char buf[LONG_STRING]; + unsigned int i; + int rc; + + snprintf (msg, sizeof (msg), _("Loading list of groups from server %s..."), + nserv->conn->account.host); + mutt_message (msg); + if (nntp_date (nserv, &nserv->newgroups_time) < 0) + return -1; + + nntp_data.nserv = nserv; + nntp_data.group = NULL; + strfcpy (buf, "LIST\r\n", sizeof (buf)); + rc = nntp_fetch_lines (&nntp_data, buf, sizeof (buf), msg, + nntp_add_group, nserv); + if (rc) + { + if (rc > 0) + { + mutt_error ("LIST: %s", buf); + mutt_sleep (2); + } + return -1; + } + + if (option (OPTLOADDESC) && + get_description (&nntp_data, "*", _("Loading descriptions...")) < 0) + return -1; + + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + + if (nntp_data && nntp_data->deleted && !nntp_data->newsrc_ent) + { + nntp_delete_group_cache (nntp_data); + hash_delete (nserv->groups_hash, nntp_data->group, NULL, nntp_data_free); + nserv->groups_list[i] = NULL; + } + } + nntp_active_save_cache (nserv); + mutt_clear_error (); + return 0; +} + +/* Check newsgroup for new articles: + * 1 - new articles found + * 0 - no change + * -1 - lost connection */ +static int nntp_group_poll (NNTP_DATA *nntp_data, int update_stat) +{ + char buf[LONG_STRING] = ""; + anum_t count, first, last; + + /* use GROUP command to poll newsgroup */ + if (nntp_query (nntp_data, buf, sizeof (buf)) < 0) + return -1; + if (sscanf (buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3) + return 0; + if (first == nntp_data->firstMessage && last == nntp_data->lastMessage) + return 0; + + /* articles have been renumbered */ + if (last < nntp_data->lastMessage) + { + nntp_data->lastCached = 0; + if (nntp_data->newsrc_len) + { + safe_realloc (&nntp_data->newsrc_ent, sizeof (NEWSRC_ENTRY)); + nntp_data->newsrc_len = 1; + nntp_data->newsrc_ent[0].first = 1; + nntp_data->newsrc_ent[0].last = 0; + } + } + nntp_data->firstMessage = first; + nntp_data->lastMessage = last; + if (!update_stat) + return 1; + + /* update counters */ + else if (!last || (!nntp_data->newsrc_ent && !nntp_data->lastCached)) + nntp_data->unread = count; + else + nntp_group_unread_stat (nntp_data); + return 1; +} + +/* Check current newsgroup for new articles: + * M_REOPENED - articles have been renumbered or removed from server + * M_NEW_MAIL - new articles found + * 0 - no change + * -1 - lost connection */ +int nntp_check_mailbox (CONTEXT *ctx, int leave_lock) +{ + NNTP_DATA *nntp_data = ctx->data; + NNTP_SERVER *nserv = nntp_data->nserv; + time_t now = time (NULL); + int i, j; + int rc, ret = 0; + void *hc = NULL; + + if (nserv->check_time + NewsPollTimeout > now) + return 0; + + mutt_message _("Checking for new messages..."); + if (nntp_newsrc_parse (nserv) < 0) + return -1; + + nserv->check_time = now; + rc = nntp_group_poll (nntp_data, 0); + if (rc < 0) + { + nntp_newsrc_close (nserv); + return -1; + } + if (rc) + nntp_active_save_cache (nserv); + + /* articles have been renumbered, remove all headers */ + if (nntp_data->lastMessage < nntp_data->lastLoaded) + { + for (i = 0; i < ctx->msgcount; i++) + mutt_free_header (&ctx->hdrs[i]); + ctx->msgcount = 0; + ctx->tagged = 0; + + if (nntp_data->lastMessage < nntp_data->lastLoaded) + { + nntp_data->lastLoaded = nntp_data->firstMessage - 1; + if (NntpContext && nntp_data->lastMessage - nntp_data->lastLoaded > + NntpContext) + nntp_data->lastLoaded = nntp_data->lastMessage - NntpContext; + } + ret = M_REOPENED; + } + + /* .newsrc has been externally modified */ + if (nserv->newsrc_modified) + { + anum_t anum; +#ifdef USE_HCACHE + unsigned char *messages; + char buf[16]; + void *hdata; + HEADER *hdr; + anum_t first = nntp_data->firstMessage; + + if (NntpContext && nntp_data->lastMessage - first + 1 > NntpContext) + first = nntp_data->lastMessage - NntpContext + 1; + messages = safe_calloc (nntp_data->lastLoaded - first + 1, + sizeof (unsigned char)); + hc = nntp_hcache_open (nntp_data); + nntp_hcache_update (nntp_data, hc); +#endif + + /* update flags according to .newsrc */ + for (i = j = 0; i < ctx->msgcount; i++) + { + int flagged = 0; + anum = NHDR (ctx->hdrs[i])->article_num; + +#ifdef USE_HCACHE + /* check hcache for flagged and deleted flags */ + if (hc) + { + if (anum >= first && anum <= nntp_data->lastLoaded) + messages[anum - first] = 1; + + snprintf (buf, sizeof (buf), "%d", anum); + hdata = mutt_hcache_fetch (hc, buf, strlen); + if (hdata) + { + int deleted; + + dprint (2, (debugfile, + "nntp_check_mailbox: mutt_hcache_fetch %s\n", buf)); + hdr = mutt_hcache_restore (hdata, NULL); + deleted = hdr->deleted; + flagged = hdr->flagged; + mutt_free_header (&hdr); + + /* header marked as deleted, removing from context */ + if (deleted) + { + mutt_set_flag (ctx, ctx->hdrs[i], M_TAG, 0); + mutt_free_header (&ctx->hdrs[i]); + continue; + } + } + } +#endif + + if (!ctx->hdrs[i]->changed) + { + ctx->hdrs[i]->flagged = flagged; + ctx->hdrs[i]->read = 0; + ctx->hdrs[i]->old = 0; + nntp_article_status (ctx, ctx->hdrs[i], NULL, anum); + if (!ctx->hdrs[i]->read) + nntp_parse_xref (ctx, ctx->hdrs[i]); + } + ctx->hdrs[j++] = ctx->hdrs[i]; + } + +#ifdef USE_HCACHE + ctx->msgcount = j; + + /* restore headers without "deleted" flag */ + for (anum = first; anum <= nntp_data->lastLoaded; anum++) + { + if (messages[anum - first]) + continue; + + snprintf (buf, sizeof (buf), "%d", anum); + hdata = mutt_hcache_fetch (hc, buf, strlen); + if (hdata) + { + dprint (2, (debugfile, + "nntp_check_mailbox: mutt_hcache_fetch %s\n", buf)); + if (ctx->msgcount >= ctx->hdrmax) + mx_alloc_memory (ctx); + + ctx->hdrs[ctx->msgcount] = + hdr = mutt_hcache_restore (hdata, NULL); + if (hdr->deleted) + { + mutt_free_header (&hdr); + if (nntp_data->bcache) + { + dprint (2, (debugfile, + "nntp_check_mailbox: mutt_bcache_del %s\n", buf)); + mutt_bcache_del (nntp_data->bcache, buf); + } + continue; + } + + ctx->msgcount++; + hdr->read = 0; + hdr->old = 0; + hdr->data = safe_calloc (1, sizeof (NNTP_HEADER_DATA)); + NHDR (hdr)->article_num = anum; + nntp_article_status (ctx, hdr, NULL, anum); + if (!hdr->read) + nntp_parse_xref (ctx, hdr); + } + } + FREE (&messages); +#endif + + nserv->newsrc_modified = 0; + ret = M_REOPENED; + } + + /* some headers were removed, context must be updated */ + if (ret == M_REOPENED) + { + if (ctx->subj_hash) + hash_destroy (&ctx->subj_hash, NULL); + if (ctx->id_hash) + hash_destroy (&ctx->id_hash, NULL); + mutt_clear_threads (ctx); + + ctx->vcount = 0; + ctx->deleted = 0; + ctx->new = 0; + ctx->unread = 0; + ctx->flagged = 0; + ctx->changed = 0; + ctx->id_hash = NULL; + ctx->subj_hash = NULL; + mx_update_context (ctx, ctx->msgcount); + } + + /* fetch headers of new articles */ + if (nntp_data->lastMessage > nntp_data->lastLoaded) + { + int oldmsgcount = ctx->msgcount; + int quiet = ctx->quiet; + ctx->quiet = 1; +#ifdef USE_HCACHE + if (!hc) + { + hc = nntp_hcache_open (nntp_data); + nntp_hcache_update (nntp_data, hc); + } +#endif + rc = nntp_fetch_headers (ctx, hc, nntp_data->lastLoaded + 1, + nntp_data->lastMessage, 0); + ctx->quiet = quiet; + if (rc >= 0) + nntp_data->lastLoaded = nntp_data->lastMessage; + if (ret == 0 && ctx->msgcount > oldmsgcount) + ret = M_NEW_MAIL; + } + +#ifdef USE_HCACHE + mutt_hcache_close (hc); +#endif + if (ret || !leave_lock) + nntp_newsrc_close (nserv); + mutt_clear_error (); + return ret; +} + +/* Check for new groups and new articles in subscribed groups: + * 1 - new groups found + * 0 - no new groups + * -1 - error */ +int nntp_check_new_groups (NNTP_SERVER *nserv) +{ + NNTP_DATA nntp_data; + time_t now; + struct tm *tm; + char buf[LONG_STRING]; + char *msg = _("Checking for new newsgroups..."); + unsigned int i; + int rc, update_active = FALSE; + + if (!nserv || !nserv->newgroups_time) + return -1; + + /* check subscribed newsgroups for new articles */ + if (option (OPTSHOWNEWNEWS)) + { + mutt_message _("Checking for new messages..."); + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + + if (nntp_data && nntp_data->subscribed) + { + rc = nntp_group_poll (nntp_data, 1); + if (rc < 0) + return -1; + if (rc > 0) + update_active = TRUE; + } + } + } + else if (nserv->newgroups_time) + return 0; + + /* get list of new groups */ + mutt_message (msg); + if (nntp_date (nserv, &now) < 0) + return -1; + nntp_data.nserv = nserv; + if (Context && Context->magic == M_NNTP) + nntp_data.group = ((NNTP_DATA *)Context->data)->group; + else + nntp_data.group = NULL; + i = nserv->groups_num; + tm = gmtime (&nserv->newgroups_time); + snprintf (buf, sizeof (buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n", + tm->tm_year % 100, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + rc = nntp_fetch_lines (&nntp_data, buf, sizeof (buf), msg, + nntp_add_group, nserv); + if (rc) + { + if (rc > 0) + { + mutt_error ("NEWGROUPS: %s", buf); + mutt_sleep (2); + } + return -1; + } + + /* new groups found */ + rc = 0; + if (nserv->groups_num != i) + { + nserv->newgroups_time = now; + + /* loading descriptions */ + if (option (OPTLOADDESC)) + { + unsigned int count = 0; + progress_t progress; + + mutt_progress_init (&progress, _("Loading descriptions..."), + M_PROGRESS_MSG, ReadInc, nserv->groups_num - i); + for (; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + + if (get_description (nntp_data, NULL, NULL) < 0) + return -1; + mutt_progress_update (&progress, ++count, -1); + } + } + update_active = TRUE; + rc = 1; + } + if (update_active) + nntp_active_save_cache (nserv); + mutt_clear_error (); + return rc; +} + +/* Fetch article by Message-ID: + * 0 - success + * 1 - no such article + * -1 - error */ +int nntp_check_msgid (CONTEXT *ctx, const char *msgid) +{ + NNTP_DATA *nntp_data = ctx->data; + HEADER *hdr; + FILE *fp; + char tempfile[_POSIX_PATH_MAX]; + char buf[LONG_STRING]; + int rc; + + mutt_mktemp (tempfile, sizeof (tempfile)); + fp = safe_fopen (tempfile, "w+"); + if (!fp) + { + mutt_perror (tempfile); + unlink (tempfile); + return -1; + } + + snprintf (buf, sizeof (buf), "HEAD %s\r\n", msgid); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL, + fetch_tempfile, fp); + if (rc) + { + fclose (fp); + unlink (tempfile); + if (rc < 0) + return -1; + if (!mutt_strncmp ("430", buf, 3)) + return 1; + mutt_error ("HEAD: %s", buf); + return -1; + } + + /* parse header */ + if (ctx->msgcount == ctx->hdrmax) + mx_alloc_memory (ctx); + hdr = ctx->hdrs[ctx->msgcount] = mutt_new_header (); + hdr->data = safe_calloc (1, sizeof (NNTP_HEADER_DATA)); + hdr->env = mutt_read_rfc822_header (fp, hdr, 0, 0); + fclose (fp); + unlink (tempfile); + + /* get article number */ + if (hdr->env->xref) + nntp_parse_xref (ctx, hdr); + else + { + snprintf (buf, sizeof (buf), "STAT %s\r\n", msgid); + if (nntp_query (nntp_data, buf, sizeof (buf)) < 0) + { + mutt_free_header (&hdr); + return -1; + } + sscanf (buf + 4, ANUM, &NHDR (hdr)->article_num); + } + + /* reset flags */ + hdr->read = 0; + hdr->old = 0; + hdr->deleted = 0; + hdr->changed = 1; + hdr->received = hdr->date_sent; + hdr->index = ctx->msgcount++; + mx_update_context (ctx, 1); + return 0; +} + +typedef struct +{ + CONTEXT *ctx; + unsigned int num; + unsigned int max; + anum_t *child; +} CHILD_CTX; + +/* Parse XPAT line */ +static int fetch_children (char *line, void *data) +{ + CHILD_CTX *cc = data; + anum_t anum; + unsigned int i; + + if (!line || sscanf (line, ANUM, &anum) != 1) + return 0; + for (i = 0; i < cc->ctx->msgcount; i++) + if (NHDR (cc->ctx->hdrs[i])->article_num == anum) + return 0; + if (cc->num >= cc->max) + { + cc->max *= 2; + safe_realloc (&cc->child, sizeof (anum_t) * cc->max); + } + cc->child[cc->num++] = anum; + return 0; +} + +/* Fetch children of article with the Message-ID */ +int nntp_check_children (CONTEXT *ctx, const char *msgid) +{ + NNTP_DATA *nntp_data = ctx->data; + CHILD_CTX cc; + char buf[STRING]; + int i, rc, quiet; + void *hc = NULL; + + if (!nntp_data || !nntp_data->nserv) + return -1; + if (nntp_data->firstMessage > nntp_data->lastLoaded) + return 0; + + /* init context */ + cc.ctx = ctx; + cc.num = 0; + cc.max = 10; + cc.child = safe_malloc (sizeof (anum_t) * cc.max); + + /* fetch numbers of child messages */ + snprintf (buf, sizeof (buf), "XPAT References %d-%d *%s*\r\n", + nntp_data->firstMessage, nntp_data->lastLoaded, msgid); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL, + fetch_children, &cc); + if (rc) + { + FREE (&cc.child); + if (rc > 0) { + if (mutt_strncmp ("500", buf, 3)) + mutt_error ("XPAT: %s", buf); + else + mutt_error _("Unable to find child articles because server does not support XPAT command."); + } + return -1; + } + + /* fetch all found messages */ + quiet = ctx->quiet; + ctx->quiet = 1; +#ifdef USE_HCACHE + hc = nntp_hcache_open (nntp_data); +#endif + for (i = 0; i < cc.num; i++) + { + rc = nntp_fetch_headers (ctx, hc, cc.child[i], cc.child[i], 1); + if (rc < 0) + break; + } +#ifdef USE_HCACHE + mutt_hcache_close (hc); +#endif + ctx->quiet = quiet; + FREE (&cc.child); + return rc < 0 ? -1 : 0; +} diff --git a/nntp.h b/nntp.h new file mode 100644 index 0000000..c937034 --- /dev/null +++ b/nntp.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 1998 Brandon Long + * Copyright (C) 1999 Andrej Gritsenko + * Copyright (C) 2000-2012 Vsevolod Volkov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _NNTP_H_ +#define _NNTP_H_ 1 + +#include "mutt_socket.h" +#include "mailbox.h" +#include "bcache.h" + +#if USE_HCACHE +#include "hcache.h" +#endif + +#include +#include +#include + +#define NNTP_PORT 119 +#define NNTP_SSL_PORT 563 + +/* number of entries in article cache */ +#define NNTP_ACACHE_LEN 10 + +/* article number type and format */ +#define anum_t uint32_t +#define ANUM "%u" + +enum +{ + NNTP_NONE = 0, + NNTP_OK, + NNTP_BYE +}; + +typedef struct +{ + unsigned int hasCAPABILITIES : 1; + unsigned int hasSTARTTLS : 1; + unsigned int hasDATE : 1; + unsigned int hasLIST_NEWSGROUPS : 1; + unsigned int hasXGTITLE : 1; + unsigned int hasLISTGROUP : 1; + unsigned int hasOVER : 1; + unsigned int hasXOVER : 1; + unsigned int use_tls : 3; + unsigned int status : 3; + unsigned int cacheable : 1; + unsigned int newsrc_modified : 1; + FILE *newsrc_fp; + char *newsrc_file; + char *authenticators; + char *overview_fmt; + off_t size; + time_t mtime; + time_t newgroups_time; + time_t check_time; + unsigned int groups_num; + unsigned int groups_max; + void **groups_list; + HASH *groups_hash; + CONNECTION *conn; +} NNTP_SERVER; + +typedef struct +{ + anum_t first; + anum_t last; +} NEWSRC_ENTRY; + +typedef struct +{ + unsigned int index; + char *path; +} NNTP_ACACHE; + +typedef struct +{ + char *group; + char *desc; + anum_t firstMessage; + anum_t lastMessage; + anum_t lastLoaded; + anum_t lastCached; + anum_t unread; + unsigned int subscribed : 1; + unsigned int new : 1; + unsigned int allowed : 1; + unsigned int deleted : 1; + unsigned int newsrc_len; + NEWSRC_ENTRY *newsrc_ent; + NNTP_SERVER *nserv; + NNTP_ACACHE acache[NNTP_ACACHE_LEN]; + body_cache_t *bcache; +} NNTP_DATA; + +typedef struct +{ + anum_t article_num; + unsigned int parsed : 1; +} NNTP_HEADER_DATA; + +#define NHDR(hdr) ((NNTP_HEADER_DATA*)((hdr)->data)) + +/* internal functions */ +int nntp_add_group (char *, void *); +int nntp_active_save_cache (NNTP_SERVER *); +int nntp_check_new_groups (NNTP_SERVER *); +int nntp_fastclose_mailbox (CONTEXT *); +int nntp_open_connection (NNTP_SERVER *); +void nntp_newsrc_gen_entries (CONTEXT *); +void nntp_bcache_update (NNTP_DATA *); +void nntp_article_status (CONTEXT *, HEADER *, char *, anum_t); +void nntp_group_unread_stat (NNTP_DATA *); +void nntp_data_free (void *); +void nntp_acache_free (NNTP_DATA *); +void nntp_delete_group_cache (NNTP_DATA *); + +/* exposed interface */ +NNTP_SERVER *nntp_select_server (char *, int); +NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER *, char *); +NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER *, char *); +NNTP_DATA *mutt_newsgroup_catchup (NNTP_SERVER *, char *); +NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER *, char *); +int nntp_active_fetch (NNTP_SERVER *); +int nntp_newsrc_update (NNTP_SERVER *); +int nntp_open_mailbox (CONTEXT *); +int nntp_sync_mailbox (CONTEXT *); +int nntp_check_mailbox (CONTEXT *, int); +int nntp_fetch_message (MESSAGE *, CONTEXT *, int); +int nntp_post (const char *); +int nntp_check_msgid (CONTEXT *, const char *); +int nntp_check_children (CONTEXT *, const char *); +int nntp_newsrc_parse (NNTP_SERVER *); +void nntp_newsrc_close (NNTP_SERVER *); +void nntp_buffy (char *, size_t); +void nntp_expand_path (char *, size_t, ACCOUNT *); +void nntp_clear_cache (NNTP_SERVER *); +const char *nntp_format_str (char *, size_t, size_t, char, const char *, + const char *, const char *, const char *, + unsigned long, format_flag); + +NNTP_SERVER *CurrentNewsSrv INITVAL (NULL); + +#ifdef USE_HCACHE +header_cache_t *nntp_hcache_open (NNTP_DATA *); +void nntp_hcache_update (NNTP_DATA *, header_cache_t *); +#endif + +#endif /* _NNTP_H_ */ diff --git a/pager.c b/pager.c index d372fc0..e888452 100644 --- a/pager.c +++ b/pager.c @@ -1085,6 +1085,11 @@ fill_buffer (FILE *f, LOFF_T *last_pos, LOFF_T offset, unsigned char **buf, return b_read; } +#ifdef USE_NNTP +#include "mx.h" +#include "nntp.h" +#endif + static int format_line (struct line_t **lineInfo, int n, unsigned char *buf, int flags, ansi_attr *pa, int cnt, @@ -1543,6 +1548,16 @@ static const struct mapping_t PagerHelpExtra[] = { { NULL, 0 } }; +#ifdef USE_NNTP +static struct mapping_t PagerNewsHelpExtra[] = { + { N_("Post"), OP_POST }, + { N_("Followup"), OP_FOLLOWUP }, + { N_("Del"), OP_DELETE }, + { N_("Next"), OP_MAIN_NEXT_UNDELETED }, + { NULL, 0 } +}; +#endif + /* This pager is actually not so simple as it once was. It now operates in @@ -1584,6 +1599,10 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra) int old_PagerIndexLines; /* some people want to resize it * while inside the pager... */ +#ifdef USE_NNTP + char *followup_to; +#endif + if (!(flags & M_SHOWCOLOR)) flags |= M_SHOWFLAT; @@ -1623,7 +1642,11 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra) if (IsHeader (extra)) { strfcpy (tmphelp, helpstr, sizeof (tmphelp)); - mutt_compile_help (buffer, sizeof (buffer), MENU_PAGER, PagerHelpExtra); + mutt_compile_help (buffer, sizeof (buffer), MENU_PAGER, +#ifdef USE_NNTP + (Context && (Context->magic == M_NNTP)) ? PagerNewsHelpExtra : +#endif + PagerHelpExtra); snprintf (helpstr, sizeof (helpstr), "%s %s", tmphelp, buffer); } if (!InHelp) @@ -2551,6 +2574,60 @@ search_next: redraw = REDRAW_FULL; break; +#ifdef USE_NNTP + case OP_POST: + CHECK_MODE(IsHeader (extra) && !IsAttach (extra)); + CHECK_ATTACH; + if (extra->ctx && extra->ctx->magic == M_NNTP && + !((NNTP_DATA *)extra->ctx->data)->allowed && + query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != M_YES) + break; + ci_send_message (SENDNEWS, NULL, NULL, extra->ctx, NULL); + redraw = REDRAW_FULL; + break; + + case OP_FORWARD_TO_GROUP: + CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); + CHECK_ATTACH; + if (extra->ctx && extra->ctx->magic == M_NNTP && + !((NNTP_DATA *)extra->ctx->data)->allowed && + query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != M_YES) + break; + if (IsMsgAttach (extra)) + mutt_attach_forward (extra->fp, extra->hdr, extra->idx, + extra->idxlen, extra->bdy, SENDNEWS); + else + ci_send_message (SENDNEWS|SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr); + redraw = REDRAW_FULL; + break; + + case OP_FOLLOWUP: + CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); + CHECK_ATTACH; + + if (IsMsgAttach (extra)) + followup_to = extra->bdy->hdr->env->followup_to; + else + followup_to = extra->hdr->env->followup_to; + + if (!followup_to || mutt_strcasecmp (followup_to, "poster") || + query_quadoption (OPT_FOLLOWUPTOPOSTER,_("Reply by mail as poster prefers?")) != M_YES) + { + if (extra->ctx && extra->ctx->magic == M_NNTP && + !((NNTP_DATA *)extra->ctx->data)->allowed && + query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != M_YES) + break; + if (IsMsgAttach (extra)) + mutt_attach_reply (extra->fp, extra->hdr, extra->idx, + extra->idxlen, extra->bdy, SENDNEWS|SENDREPLY); + else + ci_send_message (SENDNEWS|SENDREPLY, NULL, NULL, + extra->ctx, extra->hdr); + redraw = REDRAW_FULL; + break; + } +#endif + case OP_REPLY: CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); CHECK_ATTACH; @@ -2597,7 +2674,7 @@ search_next: CHECK_ATTACH; if (IsMsgAttach (extra)) mutt_attach_forward (extra->fp, extra->hdr, extra->idx, - extra->idxlen, extra->bdy); + extra->idxlen, extra->bdy, 0); else ci_send_message (SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr); redraw = REDRAW_FULL; diff --git a/parse.c b/parse.c index 58c7774..36046e9 100644 --- a/parse.c +++ b/parse.c @@ -89,7 +89,7 @@ char *mutt_read_rfc822_line (FILE *f, char *line, size_t *linelen) /* not reached */ } -static LIST *mutt_parse_references (char *s, int in_reply_to) +LIST *mutt_parse_references (char *s, int in_reply_to) { LIST *t, *lst = NULL; char *m; @@ -1072,6 +1072,17 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short e->from = rfc822_parse_adrlist (e->from, p); matched = 1; } +#ifdef USE_NNTP + else if (!mutt_strcasecmp (line+1, "ollowup-to")) + { + if (!e->followup_to) + { + mutt_remove_trailing_ws (p); + e->followup_to = safe_strdup (mutt_skip_whitespace (p)); + } + matched = 1; + } +#endif break; case 'i': @@ -1154,6 +1165,27 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short } break; +#ifdef USE_NNTP + case 'n': + if (!mutt_strcasecmp (line + 1, "ewsgroups")) + { + FREE (&e->newsgroups); + mutt_remove_trailing_ws (p); + e->newsgroups = safe_strdup (mutt_skip_whitespace (p)); + matched = 1; + } + break; +#endif + + case 'o': + /* field `Organization:' saves only for pager! */ + if (!mutt_strcasecmp (line + 1, "rganization")) + { + if (!e->organization && mutt_strcasecmp (p, "unknown")) + e->organization = safe_strdup (p); + } + break; + case 'r': if (!ascii_strcasecmp (line + 1, "eferences")) { @@ -1266,6 +1298,20 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short e->x_label = safe_strdup(p); matched = 1; } +#ifdef USE_NNTP + else if (!mutt_strcasecmp (line + 1, "-comment-to")) + { + if (!e->x_comment_to) + e->x_comment_to = safe_strdup (p); + matched = 1; + } + else if (!mutt_strcasecmp (line + 1, "ref")) + { + if (!e->xref) + e->xref = safe_strdup (p); + matched = 1; + } +#endif default: break; diff --git a/pattern.c b/pattern.c index 4cdbd05..9375a4e 100644 --- a/pattern.c +++ b/pattern.c @@ -92,6 +92,9 @@ Flags[] = { 'U', M_UNREAD, 0, NULL }, { 'v', M_COLLAPSED, 0, NULL }, { 'V', M_CRYPT_VERIFIED, 0, NULL }, +#ifdef USE_NNTP + { 'w', M_NEWSGROUPS, 0, eat_regexp }, +#endif { 'x', M_REFERENCE, 0, eat_regexp }, { 'X', M_MIMEATTACH, 0, eat_range }, { 'y', M_XLABEL, 0, eat_regexp }, @@ -1213,6 +1216,10 @@ mutt_pattern_exec (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, } case M_UNREFERENCED: return (pat->not ^ (h->thread && !h->thread->child)); +#ifdef USE_NNTP + case M_NEWSGROUPS: + return (pat->not ^ (h->env->newsgroups && patmatch (pat, h->env->newsgroups) == 0)); +#endif } mutt_error (_("error: unknown op %d (report this error)."), pat->op); return (-1); @@ -1294,6 +1301,7 @@ int mutt_pattern_func (int op, char *prompt) progress_t progress; strfcpy (buf, NONULL (Context->pattern), sizeof (buf)); + if (prompt || op != M_LIMIT) if (mutt_get_field (prompt, buf, sizeof (buf), M_PATTERN | M_CLEAR) != 0 || !buf[0]) return (-1); diff --git a/po/POTFILES.in b/po/POTFILES.in index 3654ad1..1e499ec 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -47,6 +47,8 @@ mutt_ssl_gnutls.c mutt_tunnel.c muttlib.c mx.c +newsrc.c +nntp.c pager.c parse.c pattern.c diff --git a/postpone.c b/postpone.c index 21e96e6..5144be9 100644 --- a/postpone.c +++ b/postpone.c @@ -125,15 +125,26 @@ int mutt_num_postponed (int force) if (LastModify < st.st_mtime) { +#ifdef USE_NNTP + int optnews = option (OPTNEWS); +#endif LastModify = st.st_mtime; if (access (Postponed, R_OK | F_OK) != 0) return (PostCount = 0); +#ifdef USE_NNTP + if (optnews) + unset_option (OPTNEWS); +#endif if (mx_open_mailbox (Postponed, M_NOSORT | M_QUIET, &ctx) == NULL) PostCount = 0; else PostCount = ctx.msgcount; mx_fastclose_mailbox (&ctx); +#ifdef USE_NNTP + if (optnews) + set_option (OPTNEWS); +#endif } return (PostCount); diff --git a/protos.h b/protos.h index 551b142..bb44d34 100644 --- a/protos.h +++ b/protos.h @@ -111,6 +111,7 @@ HASH *mutt_make_id_hash (CONTEXT *); HASH *mutt_make_subj_hash (CONTEXT *); LIST *mutt_make_references(ENVELOPE *e); +LIST *mutt_parse_references (char *, int); char *mutt_read_rfc822_line (FILE *, char *, size_t *); ENVELOPE *mutt_read_rfc822_header (FILE *, HEADER *, short, short); diff --git a/recvattach.c b/recvattach.c index 31dfcad..05a1cde 100644 --- a/recvattach.c +++ b/recvattach.c @@ -1119,6 +1119,15 @@ void mutt_view_attachments (HEADER *hdr) } #endif +#ifdef USE_NNTP + if (Context->magic == M_NNTP) + { + mutt_flushinp (); + mutt_error _("Can't delete attachment from news server."); + break; + } +#endif + if (WithCrypto && hdr->security & ~PGP_TRADITIONAL_CHECKED) { mutt_message _( @@ -1210,10 +1219,33 @@ void mutt_view_attachments (HEADER *hdr) case OP_FORWARD_MESSAGE: CHECK_ATTACH; mutt_attach_forward (fp, hdr, idx, idxlen, - menu->tagprefix ? NULL : idx[menu->current]->content); + menu->tagprefix ? NULL : idx[menu->current]->content, 0); menu->redraw = REDRAW_FULL; break; +#ifdef USE_NNTP + case OP_FORWARD_TO_GROUP: + CHECK_ATTACH; + mutt_attach_forward (fp, hdr, idx, idxlen, + menu->tagprefix ? NULL : idx[menu->current]->content, SENDNEWS); + menu->redraw = REDRAW_FULL; + break; + + case OP_FOLLOWUP: + CHECK_ATTACH; + + if (!idx[menu->current]->content->hdr->env->followup_to || + mutt_strcasecmp (idx[menu->current]->content->hdr->env->followup_to, "poster") || + query_quadoption (OPT_FOLLOWUPTOPOSTER,_("Reply by mail as poster prefers?")) != M_YES) + { + mutt_attach_reply (fp, hdr, idx, idxlen, + menu->tagprefix ? NULL : idx[menu->current]->content, + SENDNEWS|SENDREPLY); + menu->redraw = REDRAW_FULL; + break; + } +#endif + case OP_REPLY: case OP_GROUP_REPLY: case OP_LIST_REPLY: diff --git a/recvcmd.c b/recvcmd.c index a6a3a91..e633119 100644 --- a/recvcmd.c +++ b/recvcmd.c @@ -401,7 +401,7 @@ static BODY ** copy_problematic_attachments (FILE *fp, static void attach_forward_bodies (FILE * fp, HEADER * hdr, ATTACHPTR ** idx, short idxlen, BODY * cur, - short nattach) + short nattach, int flags) { short i; short mime_fwd_all = 0; @@ -547,7 +547,7 @@ _("Can't decode all tagged attachments. MIME-forward the others?"))) == -1) tmpfp = NULL; /* now that we have the template, send it. */ - ci_send_message (0, tmphdr, tmpbody, NULL, parent); + ci_send_message (flags, tmphdr, tmpbody, NULL, parent); return; bail: @@ -574,7 +574,7 @@ _("Can't decode all tagged attachments. MIME-forward the others?"))) == -1) */ static void attach_forward_msgs (FILE * fp, HEADER * hdr, - ATTACHPTR ** idx, short idxlen, BODY * cur) + ATTACHPTR ** idx, short idxlen, BODY * cur, int flags) { HEADER *curhdr = NULL; HEADER *tmphdr; @@ -679,23 +679,23 @@ static void attach_forward_msgs (FILE * fp, HEADER * hdr, else mutt_free_header (&tmphdr); - ci_send_message (0, tmphdr, *tmpbody ? tmpbody : NULL, + ci_send_message (flags, tmphdr, *tmpbody ? tmpbody : NULL, NULL, curhdr); } void mutt_attach_forward (FILE * fp, HEADER * hdr, - ATTACHPTR ** idx, short idxlen, BODY * cur) + ATTACHPTR ** idx, short idxlen, BODY * cur, int flags) { short nattach; if (check_all_msg (idx, idxlen, cur, 0) == 0) - attach_forward_msgs (fp, hdr, idx, idxlen, cur); + attach_forward_msgs (fp, hdr, idx, idxlen, cur, flags); else { nattach = count_tagged (idx, idxlen); - attach_forward_bodies (fp, hdr, idx, idxlen, cur, nattach); + attach_forward_bodies (fp, hdr, idx, idxlen, cur, nattach, flags); } } @@ -753,28 +753,40 @@ attach_reply_envelope_defaults (ENVELOPE *env, ATTACHPTR **idx, short idxlen, return -1; } - if (parent) +#ifdef USE_NNTP + if ((flags & SENDNEWS)) { - if (mutt_fetch_recips (env, curenv, flags) == -1) - return -1; + /* in case followup set Newsgroups: with Followup-To: if it present */ + if (!env->newsgroups && curenv && + mutt_strcasecmp (curenv->followup_to, "poster")) + env->newsgroups = safe_strdup (curenv->followup_to); } else +#endif { - for (i = 0; i < idxlen; i++) + if (parent) { - if (idx[i]->content->tagged - && mutt_fetch_recips (env, idx[i]->content->hdr->env, flags) == -1) + if (mutt_fetch_recips (env, curenv, flags) == -1) return -1; } + else + { + for (i = 0; i < idxlen; i++) + { + if (idx[i]->content->tagged + && mutt_fetch_recips (env, idx[i]->content->hdr->env, flags) == -1) + return -1; + } + } + + if ((flags & SENDLISTREPLY) && !env->to) + { + mutt_error _("No mailing lists found!"); + return (-1); + } + + mutt_fix_reply_recipients (env); } - - if ((flags & SENDLISTREPLY) && !env->to) - { - mutt_error _("No mailing lists found!"); - return (-1); - } - - mutt_fix_reply_recipients (env); mutt_make_misc_reply_headers (env, Context, curhdr, curenv); if (parent) @@ -835,6 +847,13 @@ void mutt_attach_reply (FILE * fp, HEADER * hdr, char prefix[SHORT_STRING]; int rc; +#ifdef USE_NNTP + if (flags & SENDNEWS) + set_option (OPTNEWSSEND); + else + unset_option (OPTNEWSSEND); +#endif + if (check_all_msg (idx, idxlen, cur, 0) == -1) { nattach = count_tagged (idx, idxlen); diff --git a/send.c b/send.c index 893c859..13358b9 100644 --- a/send.c +++ b/send.c @@ -44,6 +44,11 @@ #include #include +#ifdef USE_NNTP +#include "nntp.h" +#include "mx.h" +#endif + #ifdef MIXMASTER #include "remailer.h" #endif @@ -213,17 +218,51 @@ static int edit_address (ADDRESS **a, /* const */ char *field) return 0; } -static int edit_envelope (ENVELOPE *en) +static int edit_envelope (ENVELOPE *en, int flags) { char buf[HUGE_STRING]; LIST *uh = UserHeader; - if (edit_address (&en->to, "To: ") == -1 || en->to == NULL) - return (-1); - if (option (OPTASKCC) && edit_address (&en->cc, "Cc: ") == -1) - return (-1); - if (option (OPTASKBCC) && edit_address (&en->bcc, "Bcc: ") == -1) - return (-1); +#ifdef USE_NNTP + if (option (OPTNEWSSEND)) + { + if (en->newsgroups) + strfcpy (buf, en->newsgroups, sizeof (buf)); + else + buf[0] = 0; + if (mutt_get_field ("Newsgroups: ", buf, sizeof (buf), 0) != 0) + return (-1); + FREE (&en->newsgroups); + en->newsgroups = safe_strdup (buf); + + if (en->followup_to) + strfcpy (buf, en->followup_to, sizeof (buf)); + else + buf[0] = 0; + if (option (OPTASKFOLLOWUP) && mutt_get_field ("Followup-To: ", buf, sizeof (buf), 0) != 0) + return (-1); + FREE (&en->followup_to); + en->followup_to = safe_strdup (buf); + + if (en->x_comment_to) + strfcpy (buf, en->x_comment_to, sizeof (buf)); + else + buf[0] = 0; + if (option (OPTXCOMMENTTO) && option (OPTASKXCOMMENTTO) && mutt_get_field ("X-Comment-To: ", buf, sizeof (buf), 0) != 0) + return (-1); + FREE (&en->x_comment_to); + en->x_comment_to = safe_strdup (buf); + } + else +#endif + { + if (edit_address (&en->to, "To: ") == -1 || en->to == NULL) + return (-1); + if (option (OPTASKCC) && edit_address (&en->cc, "Cc: ") == -1) + return (-1); + if (option (OPTASKBCC) && edit_address (&en->bcc, "Bcc: ") == -1) + return (-1); + } if (en->subject) { @@ -258,6 +297,14 @@ static int edit_envelope (ENVELOPE *en) return 0; } +#ifdef USE_NNTP +char *nntp_get_header (const char *s) +{ + SKIPWS (s); + return safe_strdup (s); +} +#endif + static void process_user_recips (ENVELOPE *env) { LIST *uh = UserHeader; @@ -270,6 +317,14 @@ static void process_user_recips (ENVELOPE *env) env->cc = rfc822_parse_adrlist (env->cc, uh->data + 3); else if (ascii_strncasecmp ("bcc:", uh->data, 4) == 0) env->bcc = rfc822_parse_adrlist (env->bcc, uh->data + 4); +#ifdef USE_NNTP + else if (ascii_strncasecmp ("newsgroups:", uh->data, 11) == 0) + env->newsgroups = nntp_get_header (uh->data + 11); + else if (ascii_strncasecmp ("followup-to:", uh->data, 12) == 0) + env->followup_to = nntp_get_header (uh->data + 12); + else if (ascii_strncasecmp ("x-comment-to:", uh->data, 13) == 0) + env->x_comment_to = nntp_get_header (uh->data + 13); +#endif } } @@ -308,6 +363,12 @@ static void process_user_header (ENVELOPE *env) else if (ascii_strncasecmp ("to:", uh->data, 3) != 0 && ascii_strncasecmp ("cc:", uh->data, 3) != 0 && ascii_strncasecmp ("bcc:", uh->data, 4) != 0 && +#ifdef USE_NNTP + ascii_strncasecmp ("newsgroups:", uh->data, 11) != 0 && + ascii_strncasecmp ("followup-to:", uh->data, 12) != 0 && + ascii_strncasecmp ("x-comment-to:", uh->data, 13) != 0 && +#endif + ascii_strncasecmp ("supersedes:", uh->data, 11) != 0 && ascii_strncasecmp ("subject:", uh->data, 8) != 0 && ascii_strncasecmp ("return-path:", uh->data, 12) != 0) { @@ -656,6 +717,10 @@ void mutt_add_to_reference_headers (ENVELOPE *env, ENVELOPE *curenv, LIST ***pp, if (pp) *pp = p; if (qq) *qq = q; +#ifdef USE_NNTP + if (option (OPTNEWSSEND) && option (OPTXCOMMENTTO) && curenv->from) + env->x_comment_to = safe_strdup (mutt_get_name (curenv->from)); +#endif } static void @@ -718,6 +783,16 @@ envelope_defaults (ENVELOPE *env, CONTEXT *ctx, HEADER *cur, int flags) if (flags & SENDREPLY) { +#ifdef USE_NNTP + if ((flags & SENDNEWS)) + { + /* in case followup set Newsgroups: with Followup-To: if it present */ + if (!env->newsgroups && curenv && + mutt_strcasecmp (curenv->followup_to, "poster")) + env->newsgroups = safe_strdup (curenv->followup_to); + } + else +#endif if (tag) { HEADER *h; @@ -864,7 +939,18 @@ void mutt_set_followup_to (ENVELOPE *e) * it hasn't already been set */ - if (option (OPTFOLLOWUPTO) && !e->mail_followup_to) + if (!option (OPTFOLLOWUPTO)) + return; +#ifdef USE_NNTP + if (option (OPTNEWSSEND)) + { + if (!e->followup_to && e->newsgroups && (strrchr (e->newsgroups, ','))) + e->followup_to = safe_strdup (e->newsgroups); + return; + } +#endif + + if (!e->mail_followup_to) { if (mutt_is_list_cc (0, e->to, e->cc)) { @@ -1026,6 +1112,9 @@ static int send_message (HEADER *msg) #endif #if USE_SMTP +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif if (SmtpUrl) return mutt_smtp_send (msg->env->from, msg->env->to, msg->env->cc, msg->env->bcc, tempfile, @@ -1137,6 +1226,13 @@ ci_send_message (int flags, /* send mode */ int rv = -1; +#ifdef USE_NNTP + if (flags & SENDNEWS) + set_option (OPTNEWSSEND); + else + unset_option (OPTNEWSSEND); +#endif + if (!flags && !msg && quadoption (OPT_RECALL) != M_NO && mutt_num_postponed (1)) { @@ -1167,6 +1263,22 @@ ci_send_message (int flags, /* send mode */ { if ((flags = mutt_get_postponed (ctx, msg, &cur, fcc, sizeof (fcc))) < 0) goto cleanup; +#ifdef USE_NNTP + /* + * If postponed message is a news article, it have + * a "Newsgroups:" header line, then set appropriate flag. + */ + if (msg->env->newsgroups) + { + flags |= SENDNEWS; + set_option (OPTNEWSSEND); + } + else + { + flags &= ~SENDNEWS; + unset_option (OPTNEWSSEND); + } +#endif } if (flags & (SENDPOSTPONED|SENDRESEND)) @@ -1259,11 +1371,16 @@ ci_send_message (int flags, /* send mode */ if (flags & SENDREPLY) mutt_fix_reply_recipients (msg->env); +#ifdef USE_NNTP + if ((flags & SENDNEWS) && ctx && ctx->magic == M_NNTP && !msg->env->newsgroups) + msg->env->newsgroups = safe_strdup (((NNTP_DATA *)ctx->data)->group); +#endif + if (! (flags & (SENDMAILX|SENDBATCH)) && ! (option (OPTAUTOEDIT) && option (OPTEDITHDRS)) && ! ((flags & SENDREPLY) && option (OPTFASTREPLY))) { - if (edit_envelope (msg->env) == -1) + if (edit_envelope (msg->env, flags) == -1) goto cleanup; } @@ -1546,6 +1663,11 @@ main_loop: if (i == -1) { /* abort */ +#ifdef USE_NNTP + if (flags & SENDNEWS) + mutt_message _("Article not posted."); + else +#endif mutt_message _("Mail not sent."); goto cleanup; } @@ -1578,6 +1700,9 @@ main_loop: } } +#ifdef USE_NNTP + if (!(flags & SENDNEWS)) +#endif if (!has_recips (msg->env->to) && !has_recips (msg->env->cc) && !has_recips (msg->env->bcc)) { @@ -1611,6 +1736,19 @@ main_loop: mutt_error _("No subject specified."); goto main_loop; } +#ifdef USE_NNTP + if ((flags & SENDNEWS) && !msg->env->subject) + { + mutt_error _("No subject specified."); + goto main_loop; + } + + if ((flags & SENDNEWS) && !msg->env->newsgroups) + { + mutt_error _("No newsgroup specified."); + goto main_loop; + } +#endif if (msg->content->next) msg->content = mutt_make_multipart (msg->content); @@ -1817,7 +1955,12 @@ full_fcc: } } else if (!option (OPTNOCURSES) && ! (flags & SENDMAILX)) - mutt_message (i == 0 ? _("Mail sent.") : _("Sending in background.")); + mutt_message (i != 0 ? _("Sending in background.") : +#ifdef USE_NNTP + (flags & SENDNEWS) ? _("Article posted.") : _("Mail sent.")); +#else + _("Mail sent.")); +#endif if (WithCrypto && (msg->security & ENCRYPT)) FREE (&pgpkeylist); diff --git a/sendlib.c b/sendlib.c index 9d83401..46fc511 100644 --- a/sendlib.c +++ b/sendlib.c @@ -46,6 +46,10 @@ #include #include +#ifdef USE_NNTP +#include "nntp.h" +#endif + #ifdef HAVE_SYSEXITS_H #include #else /* Make sure EX_OK is defined */ @@ -1543,6 +1547,14 @@ void mutt_write_references (LIST *r, FILE *f, int trim) { LIST **ref = NULL; int refcnt = 0, refmax = 0; + int multiline = 1; + int space = 0; + + if (trim < 0) + { + trim = -trim; + multiline = 0; + } for ( ; (trim == 0 || refcnt < trim) && r ; r = r->next) { @@ -1553,9 +1565,11 @@ void mutt_write_references (LIST *r, FILE *f, int trim) while (refcnt-- > 0) { - fputc (' ', f); + if (multiline || space) + fputc (' ', f); + space = 1; fputs (ref[refcnt]->data, f); - if (refcnt >= 1) + if (multiline && refcnt >= 1) fputc ('\n', f); } @@ -1962,6 +1976,9 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach, mutt_write_address_list (env->to, fp, 4, 0); } else if (mode > 0) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif fputs ("To: \n", fp); if (env->cc) @@ -1970,6 +1987,9 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach, mutt_write_address_list (env->cc, fp, 4, 0); } else if (mode > 0) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif fputs ("Cc: \n", fp); if (env->bcc && should_write_bcc) @@ -1981,8 +2001,28 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach, } } else if (mode > 0) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif fputs ("Bcc: \n", fp); +#ifdef USE_NNTP + if (env->newsgroups) + fprintf (fp, "Newsgroups: %s\n", env->newsgroups); + else if (mode == 1 && option (OPTNEWSSEND)) + fputs ("Newsgroups: \n", fp); + + if (env->followup_to) + fprintf (fp, "Followup-To: %s\n", env->followup_to); + else if (mode == 1 && option (OPTNEWSSEND)) + fputs ("Followup-To: \n", fp); + + if (env->x_comment_to) + fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to); + else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO)) + fputs ("X-Comment-To: \n", fp); +#endif + if (env->subject) mutt_write_one_header (fp, "Subject", env->subject, NULL, 0, 0); else if (mode == 1) @@ -2001,6 +2041,9 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach, fputs ("Reply-To: \n", fp); if (env->mail_followup_to) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif { fputs ("Mail-Followup-To: ", fp); mutt_write_address_list (env->mail_followup_to, fp, 18, 0); @@ -2344,6 +2387,23 @@ mutt_invoke_sendmail (ADDRESS *from, /* the sender */ size_t argslen = 0, argsmax = 0; int i; +#ifdef USE_NNTP + if (option (OPTNEWSSEND)) + { + char cmd[LONG_STRING]; + + mutt_FormatString (cmd, sizeof (cmd), 0, NONULL (Inews), nntp_format_str, 0, 0); + if (!*cmd) + { + i = nntp_post (msg); + unlink (msg); + return i; + } + + s = safe_strdup (cmd); + } +#endif + /* ensure that $sendmail is set to avoid a crash. http://dev.mutt.org/trac/ticket/3548 */ if (!s) { @@ -2374,6 +2434,10 @@ mutt_invoke_sendmail (ADDRESS *from, /* the sender */ i++; } +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) + { +#endif if (eightbit && option (OPTUSE8BITMIME)) args = add_option (args, &argslen, &argsmax, "-B8BITMIME"); @@ -2405,6 +2469,9 @@ mutt_invoke_sendmail (ADDRESS *from, /* the sender */ args = add_args (args, &argslen, &argsmax, to); args = add_args (args, &argslen, &argsmax, cc); args = add_args (args, &argslen, &argsmax, bcc); +#ifdef USE_NNTP + } +#endif if (argslen == argsmax) safe_realloc (&args, sizeof (char *) * (++argsmax)); @@ -2485,6 +2552,9 @@ void mutt_prepare_envelope (ENVELOPE *env, int final) rfc2047_encode_string (&env->x_label); if (env->subject) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT)) +#endif { rfc2047_encode_string (&env->subject); } @@ -2605,6 +2675,10 @@ int mutt_bounce_message (FILE *fp, HEADER *h, ADDRESS *to) } rfc822_write_address (resent_from, sizeof (resent_from), from, 0); +#ifdef USE_NNTP + unset_option (OPTNEWSSEND); +#endif + /* * prepare recipient list. idna conversion appears to happen before this * function is called, since the user receives confirmation of the address diff --git a/sort.c b/sort.c index 76e9e79..9aa259c 100644 --- a/sort.c +++ b/sort.c @@ -24,6 +24,11 @@ #include "sort.h" #include "mutt_idna.h" +#ifdef USE_NNTP +#include "mx.h" +#include "nntp.h" +#endif + #include #include #include @@ -151,6 +156,17 @@ static int compare_order (const void *a, const void *b) HEADER **ha = (HEADER **) a; HEADER **hb = (HEADER **) b; +#ifdef USE_NNTP + if (Context && Context->magic == M_NNTP) + { + anum_t na = NHDR (*ha)->article_num; + anum_t nb = NHDR (*hb)->article_num; + int result = na == nb ? 0 : na > nb ? 1 : -1; + AUXSORT (result, a, b); + return (SORTCODE (result)); + } + else +#endif /* no need to auxsort because you will never have equality here */ return (SORTCODE ((*ha)->index - (*hb)->index)); } diff --git a/url.c b/url.c index 988cf59..3fd4079 100644 --- a/url.c +++ b/url.c @@ -39,6 +39,8 @@ static const struct mapping_t UrlMap[] = { "imaps", U_IMAPS }, { "pop", U_POP }, { "pops", U_POPS }, + { "news", U_NNTP }, + { "snews", U_NNTPS }, { "mailto", U_MAILTO }, { "smtp", U_SMTP }, { "smtps", U_SMTPS }, @@ -214,7 +216,7 @@ int url_ciss_tostring (ciss_url_t* ciss, char* dest, size_t len, int flags) safe_strcat (dest, len, "//"); len -= (l = strlen (dest)); dest += l; - if (ciss->user) + if (ciss->user && (ciss->user[0] || !(flags & U_PATH))) { char u[STRING]; url_pct_encode (u, sizeof (u), ciss->user); diff --git a/url.h b/url.h index 926416e..15ec9ce 100644 --- a/url.h +++ b/url.h @@ -8,6 +8,8 @@ typedef enum url_scheme U_POPS, U_IMAP, U_IMAPS, + U_NNTP, + U_NNTPS, U_SMTP, U_SMTPS, U_MAILTO, debian/patches/mutt-patched/sidebar-compose.patch ================================================= Date: Fri, 14 Mar 2014 08:54:47 +0100 Subject: sidebar-compose draw_sidebar sets SidebarWidth to 0 when sidebar_visible is false. However, if you start mutt in compose mode, draw_sidebar won't be called until the next redraw and your header lines will be off by the width of the sidebar, even when you did not want a sidebar at all. Can be tested with: HOME=/ LC_ALL=C mutt -e 'unset sidebar_visible' -s test recipient Closes: #502627 Gbp-Pq: Topic mutt-patched --- compose.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compose.c b/compose.c index 5a14d70..16576f2 100644 --- a/compose.c +++ b/compose.c @@ -32,6 +32,7 @@ #include "mailbox.h" #include "sort.h" #include "charset.h" +#include "sidebar.h" #ifdef MIXMASTER #include "remailer.h" @@ -245,6 +246,7 @@ static void draw_envelope_addr (int line, ADDRESS *addr) static void draw_envelope (HEADER *msg, char *fcc) { + draw_sidebar (MENU_COMPOSE); draw_envelope_addr (HDR_FROM, msg->env->from); draw_envelope_addr (HDR_TO, msg->env->to); draw_envelope_addr (HDR_CC, msg->env->cc); debian/patches/mutt-patched/sidebar-delimnullwide.patch ======================================================= Date: Wed, 5 Mar 2014 17:46:07 +0100 Subject: sidebar-delimnullwide SidebarDelim can be NULL and strlen(NULL) is a bad idea, as it will segfault. Wrap it with NONULL(). While at it, change strlen to mbstowcs for better utf8 support. Closes: #696145, #663883 Gbp-Pq: Topic mutt-patched --- sidebar.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sidebar.c b/sidebar.c index 51a25ca..c3ea338 100644 --- a/sidebar.c +++ b/sidebar.c @@ -88,7 +88,7 @@ char *make_sidebar_entry(char *box, int size, int new, int flagged) int box_len, box_bytes; int int_len; int right_offset = 0; - int delim_len = strlen(SidebarDelim); + int delim_len = mbstowcs(NULL, NONULL(SidebarDelim), 0); static char *entry; right_width = left_width = 0; @@ -178,7 +178,7 @@ int draw_sidebar(int menu) { #ifndef USE_SLANG_CURSES attr_t attrs; #endif - short delim_len = strlen(SidebarDelim); + short delim_len = mbstowcs(NULL, NONULL(SidebarDelim), 0); short color_pair; static bool initialized = false; debian/patches/mutt-patched/sidebar-dotpathsep.patch ==================================================== Date: Tue, 4 Mar 2014 21:12:15 +0100 Subject: sidebar-dotpathsep Make path separators for sidebar folders configurable. When using IMAP, a '.' is often used as path separator, hence make the path separators configurable through sidebar_delim_chars variable. It defaults to "/." to work for both mboxes as well as IMAP folders. It can be set to only "/" or "." or whichever character desired as needed. Gbp-Pq: Topic mutt-patched --- globals.h | 1 + init.h | 8 ++++++++ sidebar.c | 31 ++++++++++++++++++++++++------- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/globals.h b/globals.h index 3f83328..61765a4 100644 --- a/globals.h +++ b/globals.h @@ -118,6 +118,7 @@ WHERE char *SendCharset; WHERE char *Sendmail; WHERE char *Shell; WHERE char *SidebarDelim; +WHERE char *SidebarDelimChars INITVAL (NULL); WHERE char *Signature; WHERE char *SimpleSearch; #if USE_SMTP diff --git a/init.h b/init.h index 502f570..b0784d8 100644 --- a/init.h +++ b/init.h @@ -2001,6 +2001,14 @@ struct option_t MuttVars[] = { ** .pp ** The width of the sidebar. */ + { "sidebar_delim_chars", DT_STR, R_NONE, UL &SidebarDelimChars, UL "/." }, + /* + ** .pp + ** This contains the list of characters which you would like to treat + ** as folder separators for displaying paths in the sidebar. If + ** you're not using IMAP folders, you probably prefer setting this to "/" + ** alone. + */ { "pgp_use_gpg_agent", DT_BOOL, R_NONE, OPTUSEGPGAGENT, 0}, /* ** .pp diff --git a/sidebar.c b/sidebar.c index 6098c2a..4356ffc 100644 --- a/sidebar.c +++ b/sidebar.c @@ -249,20 +249,37 @@ int draw_sidebar(int menu) { // calculate depth of current folder and generate its display name with indented spaces int sidebar_folder_depth = 0; char *sidebar_folder_name; - sidebar_folder_name = basename(tmp->path); + int i; + sidebar_folder_name = tmp->path; + /* disregard a trailing separator, so strlen() - 2 + * https://bugs.gentoo.org/show_bug.cgi?id=373197#c16 */ + for (i = strlen(sidebar_folder_name) - 2; i >= 0; i--) { + if (SidebarDelimChars && + strchr(SidebarDelimChars, sidebar_folder_name[i])) + { + sidebar_folder_name += i + 1; + break; + } + } if ( maildir_is_prefix ) { char *tmp_folder_name; - int i; + int lastsep = 0; tmp_folder_name = tmp->path + strlen(Maildir); - for (i = 0; i < strlen(tmp->path) - strlen(Maildir); i++) { - if (tmp_folder_name[i] == '/') sidebar_folder_depth++; - } + for (i = 0; i < strlen(tmp_folder_name) - 1; i++) { + if (SidebarDelimChars && + strchr(SidebarDelimChars, tmp_folder_name[i])) + { + sidebar_folder_depth++; + lastsep = i + 1; + } + } if (sidebar_folder_depth > 0) { - sidebar_folder_name = malloc(strlen(basename(tmp->path)) + sidebar_folder_depth + 1); + tmp_folder_name += lastsep; /* basename */ + sidebar_folder_name = malloc(strlen(tmp_folder_name) + sidebar_folder_depth + 1); for (i=0; i < sidebar_folder_depth; i++) sidebar_folder_name[i]=' '; sidebar_folder_name[i]=0; - strncat(sidebar_folder_name, basename(tmp->path), strlen(basename(tmp->path)) + sidebar_folder_depth); + strncat(sidebar_folder_name, tmp_folder_name, strlen(tmp_folder_name) + sidebar_folder_depth); } } printw( "%.*s", SidebarWidth - delim_len + 1, debian/patches/mutt-patched/sidebar-newonly.patch ================================================= Date: Tue, 4 Mar 2014 22:07:06 +0100 Subject: sidebar-newonly patches written by Steve Kemp, it adds two new functionalities to the sidebar, so only the mailbox with new messages will be shown (and/or) selected See Debian bug http://bugs.debian.org/532510 Gbp-Pq: Topic mutt-patched --- OPS | 2 ++ curs_main.c | 2 ++ functions.h | 4 ++++ init.h | 5 +++++ mutt.h | 2 ++ pager.c | 2 ++ sidebar.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 7 files changed, 70 insertions(+), 2 deletions(-) diff --git a/OPS b/OPS index b036db9..1ed9c96 100644 --- a/OPS +++ b/OPS @@ -185,3 +185,5 @@ OP_SIDEBAR_SCROLL_DOWN "scroll the mailbox pane down 1 page" OP_SIDEBAR_NEXT "go down to next mailbox" OP_SIDEBAR_PREV "go to previous mailbox" OP_SIDEBAR_OPEN "open hilighted mailbox" +OP_SIDEBAR_NEXT_NEW "go down to next mailbox with new mail" +OP_SIDEBAR_PREV_NEW "go to previous mailbox with new mail" diff --git a/curs_main.c b/curs_main.c index 7b81798..5c58f1c 100644 --- a/curs_main.c +++ b/curs_main.c @@ -2262,6 +2262,8 @@ int mutt_index_menu (void) case OP_SIDEBAR_SCROLL_DOWN: case OP_SIDEBAR_NEXT: case OP_SIDEBAR_PREV: + case OP_SIDEBAR_NEXT_NEW: + case OP_SIDEBAR_PREV_NEW: scroll_sidebar(op, menu->menu); break; default: diff --git a/functions.h b/functions.h index ef8937a..363b4d5 100644 --- a/functions.h +++ b/functions.h @@ -174,6 +174,8 @@ const struct binding_t OpMain[] = { /* map: index */ { "sidebar-scroll-down", OP_SIDEBAR_SCROLL_DOWN, NULL }, { "sidebar-next", OP_SIDEBAR_NEXT, NULL }, { "sidebar-prev", OP_SIDEBAR_PREV, NULL }, + { "sidebar-next-new", OP_SIDEBAR_NEXT_NEW, NULL}, + { "sidebar-prev-new", OP_SIDEBAR_PREV_NEW, NULL}, { "sidebar-open", OP_SIDEBAR_OPEN, NULL }, { NULL, 0, NULL } }; @@ -283,6 +285,8 @@ const struct binding_t OpPager[] = { /* map: pager */ { "sidebar-scroll-down", OP_SIDEBAR_SCROLL_DOWN, NULL }, { "sidebar-next", OP_SIDEBAR_NEXT, NULL }, { "sidebar-prev", OP_SIDEBAR_PREV, NULL }, + { "sidebar-next-new", OP_SIDEBAR_NEXT_NEW, NULL}, + { "sidebar-prev-new", OP_SIDEBAR_PREV_NEW, NULL}, { "sidebar-open", OP_SIDEBAR_OPEN, NULL }, { NULL, 0, NULL } }; diff --git a/init.h b/init.h index b0784d8..e20a24e 100644 --- a/init.h +++ b/init.h @@ -2009,6 +2009,11 @@ struct option_t MuttVars[] = { ** you're not using IMAP folders, you probably prefer setting this to "/" ** alone. */ + {"sidebar_newmail_only", DT_BOOL, R_BOTH, OPTSIDEBARNEWMAILONLY, 0 }, + /* + ** .pp + ** Show only new mail in the sidebar. + */ { "pgp_use_gpg_agent", DT_BOOL, R_NONE, OPTUSEGPGAGENT, 0}, /* ** .pp diff --git a/mutt.h b/mutt.h index 61a9612..932ef10 100644 --- a/mutt.h +++ b/mutt.h @@ -525,6 +525,8 @@ enum OPTDONTHANDLEPGPKEYS, /* (pseudo) used to extract PGP keys */ OPTUNBUFFEREDINPUT, /* (pseudo) don't use key buffer */ + OPTSIDEBARNEWMAILONLY, + OPTMAX }; diff --git a/pager.c b/pager.c index 469efe4..d372fc0 100644 --- a/pager.c +++ b/pager.c @@ -2789,6 +2789,8 @@ search_next: case OP_SIDEBAR_SCROLL_DOWN: case OP_SIDEBAR_NEXT: case OP_SIDEBAR_PREV: + case OP_SIDEBAR_NEXT_NEW: + case OP_SIDEBAR_PREV_NEW: scroll_sidebar(ch, MENU_PAGER); break; diff --git a/sidebar.c b/sidebar.c index 8f58f85..51a25ca 100644 --- a/sidebar.c +++ b/sidebar.c @@ -269,8 +269,21 @@ int draw_sidebar(int menu) { SETCOLOR(MT_COLOR_NEW); else if ( tmp->msg_flagged > 0 ) SETCOLOR(MT_COLOR_FLAGGED); - else - SETCOLOR(MT_COLOR_NORMAL); + else { + /* make sure the path is either: + 1. Containing new mail. + 2. The inbox. + 3. The current box. + */ + if ((option (OPTSIDEBARNEWMAILONLY)) && + ( (tmp->msg_unread <= 0) && + ( tmp != Incoming ) && + Context && + ( strcmp( tmp->path, Context->path ) != 0 ) ) ) + continue; + else + SETCOLOR(MT_COLOR_NORMAL); + } move( lines, 0 ); if ( Context && !strcmp( tmp->path, Context->path ) ) { @@ -336,6 +349,29 @@ int draw_sidebar(int menu) { return 0; } +BUFFY * exist_next_new() +{ + BUFFY *tmp = CurBuffy; + if(tmp == NULL) return NULL; + while (tmp->next != NULL) + { + tmp = tmp->next; + if(tmp->msg_unread) return tmp; + } + return NULL; +} + +BUFFY * exist_prev_new() +{ + BUFFY *tmp = CurBuffy; + if(tmp == NULL) return NULL; + while (tmp->prev != NULL) + { + tmp = tmp->prev; + if(tmp->msg_unread) return tmp; + } + return NULL; +} void set_buffystats(CONTEXT* Context) { @@ -352,18 +388,33 @@ void set_buffystats(CONTEXT* Context) void scroll_sidebar(int op, int menu) { + BUFFY *tmp; if(!SidebarWidth) return; if(!CurBuffy) return; switch (op) { case OP_SIDEBAR_NEXT: + if (!option (OPTSIDEBARNEWMAILONLY)) { if ( CurBuffy->next == NULL ) return; CurBuffy = CurBuffy->next; break; + } + case OP_SIDEBAR_NEXT_NEW: + if ( (tmp = exist_next_new()) == NULL) + return; + else CurBuffy = tmp; + break; case OP_SIDEBAR_PREV: + if (!option (OPTSIDEBARNEWMAILONLY)) { if ( CurBuffy->prev == NULL ) return; CurBuffy = CurBuffy->prev; break; + } + case OP_SIDEBAR_PREV_NEW: + if ( (tmp = exist_prev_new()) == NULL) + return; + else CurBuffy = tmp; + break; case OP_SIDEBAR_SCROLL_UP: CurBuffy = TopBuffy; if ( CurBuffy != Incoming ) { debian/patches/mutt-patched/sidebar-utf8.patch ============================================== Date: Tue, 4 Mar 2014 15:39:14 +0100 Subject: sidebar-utf8 This patch fixes a problem with utf-8 strings and the sidebar, it rewrites entirely make_sidebar_entry so it also fixes some segfaults due to misallocations and overflows. See: http://bugs.debian.org/584581 http://bugs.debian.org/603287 Gbp-Pq: Topic mutt-patched --- sidebar.c | 97 +++++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 30 deletions(-) diff --git a/sidebar.c b/sidebar.c index 4356ffc..8f58f85 100644 --- a/sidebar.c +++ b/sidebar.c @@ -30,6 +30,7 @@ #include #include "keymap.h" #include +#include /*BUFFY *CurBuffy = 0;*/ static BUFFY *TopBuffy = 0; @@ -82,36 +83,72 @@ void calc_boundaries (int menu) char *make_sidebar_entry(char *box, int size, int new, int flagged) { - static char *entry = 0; - char *c; - int i = 0; - int delim_len = strlen(SidebarDelim); - - c = realloc(entry, SidebarWidth - delim_len + 2); - if ( c ) entry = c; - entry[SidebarWidth - delim_len + 1] = 0; - for (; i < SidebarWidth - delim_len + 1; entry[i++] = ' ' ); - i = strlen(box); - strncpy( entry, box, i < (SidebarWidth - delim_len + 1) ? i : (SidebarWidth - delim_len + 1) ); - - if (size == -1) - sprintf(entry + SidebarWidth - delim_len - 3, "?"); - else if ( new ) { - if (flagged > 0) { - sprintf( - entry + SidebarWidth - delim_len - 5 - quick_log10(size) - quick_log10(new) - quick_log10(flagged), - "% d(%d)[%d]", size, new, flagged); - } else { - sprintf( - entry + SidebarWidth - delim_len - 3 - quick_log10(size) - quick_log10(new), - "% d(%d)", size, new); - } - } else if (flagged > 0) { - sprintf( entry + SidebarWidth - delim_len - 3 - quick_log10(size) - quick_log10(flagged), "% d[%d]", size, flagged); - } else { - sprintf( entry + SidebarWidth - delim_len - 1 - quick_log10(size), "% d", size); - } - return entry; + char int_store[20]; // up to 64 bits integers + int right_width, left_width; + int box_len, box_bytes; + int int_len; + int right_offset = 0; + int delim_len = strlen(SidebarDelim); + static char *entry; + + right_width = left_width = 0; + box_len = box_bytes = 0; + + // allocate an entry big enough to contain SidebarWidth wide chars + entry = malloc((SidebarWidth*4)+1); // TODO: error check + + // determine the right space (i.e.: how big are the numbers that we want to print) + if ( size > 0 ) { + int_len = snprintf(int_store, sizeof(int_store), "%d", size); + right_width += int_len; + } else { + right_width = 1; // to represent 0 + } + if ( new > 0 ) { + int_len = snprintf(int_store, sizeof(int_store), "%d", new); + right_width += int_len + 2; // 2 is for () + } + if ( flagged > 0 ) { + int_len = snprintf(int_store, sizeof(int_store), "%d", flagged); + right_width += int_len + 2; // 2 is for [] + } + + // determine how much space we have for *box and its padding (if any) + left_width = SidebarWidth - right_width - 1 - delim_len; // 1 is for the space + //fprintf(stdout, "left_width: %d right_width: %d\n", left_width, right_width); + // right side overflow case + if ( left_width <= 0 ) { + snprintf(entry, SidebarWidth*4, "%-*.*s ...", SidebarWidth-4-delim_len, SidebarWidth-4-delim_len, box); + return entry; + } + right_width -= delim_len; + + // to support utf-8 chars we need to add enough space padding in case there + // are less chars than bytes in *box + box_len = mbstowcs(NULL, box, 0); + box_bytes = strlen(box); + // debug + //fprintf(stdout, "box_len: %d box_bytes: %d (diff: %d)\n", box_len, box_bytes, (box_bytes-box_len)); + // if there is less string than the space we allow, then we will add the + // spaces + if ( box_len != -1 && box_len < left_width ) { + left_width += (box_bytes - box_len); + } + // otherwise sprintf will truncate the string for us (therefore, no else case) + + // print the sidebar entry (without new and flagged messages, at the moment) + //fprintf(stdout, "left_width: %d right_width: %d\n", left_width, right_width); + right_offset = snprintf(entry, SidebarWidth*4, "%-*.*s %d", left_width, left_width, box, size); + + // then pad new and flagged messages if any + if ( new > 0 ) { + right_offset += snprintf(entry+right_offset, SidebarWidth*4-right_offset, "(%d)", new); + } + if ( flagged > 0 ) { + right_offset += snprintf(entry+right_offset, SidebarWidth*4-right_offset, "[%d]", flagged); + } + + return entry; } void set_curbuffy(char buf[LONG_STRING]) debian/patches/mutt-patched/sidebar.patch ========================================= Date: Tue, 4 Mar 2014 15:21:10 +0100 Subject: sidebar When enabled, mutt will show a list of mailboxes with (new) message counts in a separate column on the left side of the screen. As this feature is still considered to be unstable, this patch is only applied in the "mutt-patched" package. * Configuration variables: sidebar_delim (string, default "|") This specifies the delimiter between the sidebar (if visible) and other screens. sidebar_visible (boolean, default no) This specifies whether or not to show sidebar (left-side list of folders). sidebar_width (integer, default 0) - The width of the sidebar. * Patch source: - http://www.lunar-linux.org/index.php?page=mutt-sidebar - http://lunar-linux.org/~tchan/mutt/patch-1.5.19.sidebar.20090522.txt * Changes made: - 2008-08-02 myon: Refreshed patch using quilt push -f to remove hunks we do not need (Makefile.in). - 2014-03-04 evgeni: refresh sidebar patch with the version from OpenBSD Source: ftp://ftp.openbsd.org/pub/OpenBSD/distfiles/mutt/sidebar-1.5.22.diff1.gz Signed-off-by: Matteo F. Vescovi Signed-off-by: Evgeni Golov Gbp-Pq: Topic mutt-patched --- Makefile.am | 1 + OPS | 5 + buffy.c | 123 +++++++++++++++++++++ buffy.h | 4 + color.c | 2 + compose.c | 26 ++--- curs_main.c | 30 +++++- flags.c | 3 + functions.h | 10 ++ globals.h | 4 + imap/command.c | 7 ++ imap/imap.c | 2 +- init.h | 21 ++++ mailbox.h | 1 + mbox.c | 2 + menu.c | 20 ++-- mh.c | 22 ++++ mutt.h | 4 + mutt_curses.h | 3 + muttlib.c | 48 +++++++++ mx.c | 15 +++ mx.h | 1 + pager.c | 30 ++++-- sidebar.c | 333 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sidebar.h | 36 +++++++ 25 files changed, 719 insertions(+), 34 deletions(-) create mode 100644 sidebar.c create mode 100644 sidebar.h diff --git a/Makefile.am b/Makefile.am index 09dd64b..2fc6b1d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,6 +32,7 @@ mutt_SOURCES = \ rfc822.c rfc1524.c rfc2047.c rfc2231.c rfc3676.c \ score.c send.c sendlib.c signal.c sort.c \ status.c system.c thread.c charset.c history.c lib.c \ + sidebar.c \ muttlib.c editmsg.c mbyte.c \ url.c ascii.c crypt-mod.c crypt-mod.h safe_asprintf.c diff --git a/OPS b/OPS index 02cea8e..b036db9 100644 --- a/OPS +++ b/OPS @@ -180,3 +180,8 @@ OP_WHAT_KEY "display the keycode for a key press" OP_MAIN_SHOW_LIMIT "show currently active limit pattern" OP_MAIN_COLLAPSE_THREAD "collapse/uncollapse current thread" OP_MAIN_COLLAPSE_ALL "collapse/uncollapse all threads" +OP_SIDEBAR_SCROLL_UP "scroll the mailbox pane up 1 page" +OP_SIDEBAR_SCROLL_DOWN "scroll the mailbox pane down 1 page" +OP_SIDEBAR_NEXT "go down to next mailbox" +OP_SIDEBAR_PREV "go to previous mailbox" +OP_SIDEBAR_OPEN "open hilighted mailbox" diff --git a/buffy.c b/buffy.c index e5a0f79..225104d 100644 --- a/buffy.c +++ b/buffy.c @@ -161,6 +161,49 @@ void mutt_buffy_cleanup (const char *buf, struct stat *st) } } +static int buffy_compare_name(const void *a, const void *b) { + const BUFFY *b1 = * (BUFFY * const *) a; + const BUFFY *b2 = * (BUFFY * const *) b; + + return mutt_strcoll(b1->path, b2->path); +} + +static BUFFY *buffy_sort(BUFFY *b) +{ + BUFFY *tmp = b; + int buffycount = 0; + BUFFY **ary; + int i; + + if (!option(OPTSIDEBARSORT)) + return b; + + for (; tmp != NULL; tmp = tmp->next) + buffycount++; + + ary = (BUFFY **) safe_calloc(buffycount, sizeof (*ary)); + + tmp = b; + for (i = 0; tmp != NULL; tmp = tmp->next, i++) { + ary[i] = tmp; + } + + qsort(ary, buffycount, sizeof(*ary), buffy_compare_name); + + for (i = 0; i < buffycount - 1; i++) { + ary[i]->next = ary[i+1]; + } + ary[buffycount - 1]->next = NULL; + for (i = 1; i < buffycount; i++) { + ary[i]->prev = ary[i-1]; + } + ary[0]->prev = NULL; + + tmp = ary[0]; + free(ary); + return tmp; +} + BUFFY *mutt_find_mailbox (const char *path) { BUFFY *tmp = NULL; @@ -282,6 +325,7 @@ int mutt_parse_mailboxes (BUFFER *path, BUFFER *s, unsigned long data, BUFFER *e else (*tmp)->size = 0; } + Incoming = buffy_sort(Incoming); return 0; } @@ -340,6 +384,68 @@ static int buffy_maildir_hasnew (BUFFY* mailbox) return rc; } +/* update message counts for the sidebar */ +void buffy_maildir_update (BUFFY* mailbox) +{ + char path[_POSIX_PATH_MAX]; + DIR *dirp; + struct dirent *de; + char *p; + + mailbox->msgcount = 0; + mailbox->msg_unread = 0; + mailbox->msg_flagged = 0; + + snprintf (path, sizeof (path), "%s/new", mailbox->path); + + if ((dirp = opendir (path)) == NULL) + { + mailbox->magic = 0; + return; + } + + while ((de = readdir (dirp)) != NULL) + { + if (*de->d_name == '.') + continue; + + if (!(p = strstr (de->d_name, ":2,")) || !strchr (p + 3, 'T')) { + mailbox->new = 1; + mailbox->msgcount++; + mailbox->msg_unread++; + } + } + + closedir (dirp); + snprintf (path, sizeof (path), "%s/cur", mailbox->path); + + if ((dirp = opendir (path)) == NULL) + { + mailbox->magic = 0; + return; + } + + while ((de = readdir (dirp)) != NULL) + { + if (*de->d_name == '.') + continue; + + if (!(p = strstr (de->d_name, ":2,")) || !strchr (p + 3, 'T')) { + mailbox->msgcount++; + if ((p = strstr (de->d_name, ":2,"))) { + if (!strchr (p + 3, 'T')) { + if (!strchr (p + 3, 'S')) + mailbox->msg_unread++; + if (strchr(p + 3, 'F')) + mailbox->msg_flagged++; + } + } + } + } + + closedir (dirp); +} + /* returns 1 if mailbox has new mail */ static int buffy_mbox_hasnew (BUFFY* mailbox, struct stat *sb) { @@ -371,6 +477,20 @@ static int buffy_mbox_hasnew (BUFFY* mailbox, struct stat *sb) return rc; } +/* update message counts for the sidebar */ +void buffy_mbox_update (BUFFY* mailbox) +{ + CONTEXT *ctx = NULL; + + ctx = mx_open_mailbox(mailbox->path, M_READONLY | M_QUIET | M_NOSORT | M_PEEK, NULL); + if(ctx) + { + mailbox->msgcount = ctx->msgcount; + mailbox->msg_unread = ctx->unread; + mx_close_mailbox(ctx, 0); + } +} + int mutt_buffy_check (int force) { BUFFY *tmp; @@ -444,16 +564,19 @@ int mutt_buffy_check (int force) { case M_MBOX: case M_MMDF: + buffy_mbox_update (tmp); if (buffy_mbox_hasnew (tmp, &sb) > 0) BuffyCount++; break; case M_MAILDIR: + buffy_maildir_update (tmp); if (buffy_maildir_hasnew (tmp) > 0) BuffyCount++; break; case M_MH: + mh_buffy_update (tmp->path, &tmp->msgcount, &tmp->msg_unread, &tmp->msg_flagged); mh_buffy(tmp); if (tmp->new) BuffyCount++; diff --git a/buffy.h b/buffy.h index f9fc55a..672d178 100644 --- a/buffy.h +++ b/buffy.h @@ -25,7 +25,11 @@ typedef struct buffy_t char path[_POSIX_PATH_MAX]; off_t size; struct buffy_t *next; + struct buffy_t *prev; short new; /* mailbox has new mail */ + int msgcount; /* total number of messages */ + int msg_unread; /* number of unread messages */ + int msg_flagged; /* number of flagged messages */ short notified; /* user has been notified */ short magic; /* mailbox type */ short newly_created; /* mbox or mmdf just popped into existence */ diff --git a/color.c b/color.c index ef97ca9..2112132 100644 --- a/color.c +++ b/color.c @@ -93,6 +93,8 @@ static const struct mapping_t Fields[] = { "bold", MT_COLOR_BOLD }, { "underline", MT_COLOR_UNDERLINE }, { "index", MT_COLOR_INDEX }, + { "sidebar_new", MT_COLOR_NEW }, + { "sidebar_flagged", MT_COLOR_FLAGGED }, { NULL, 0 } }; diff --git a/compose.c b/compose.c index 9cfa2d4..5a14d70 100644 --- a/compose.c +++ b/compose.c @@ -72,7 +72,7 @@ enum #define HDR_XOFFSET 10 #define TITLE_FMT "%10s" /* Used for Prompts, which are ASCII */ -#define W (COLS - HDR_XOFFSET) +#define W (COLS - HDR_XOFFSET - SidebarWidth) static const char * const Prompts[] = { @@ -110,7 +110,7 @@ static void snd_entry (char *b, size_t blen, MUTTMENU *menu, int num) static void redraw_crypt_lines (HEADER *msg) { - mvaddstr (HDR_CRYPT, 0, "Security: "); + mvaddstr (HDR_CRYPT, SidebarWidth, "Security: "); if ((WithCrypto & (APPLICATION_PGP | APPLICATION_SMIME)) == 0) { @@ -142,7 +142,7 @@ static void redraw_crypt_lines (HEADER *msg) } clrtoeol (); - move (HDR_CRYPTINFO, 0); + move (HDR_CRYPTINFO, SidebarWidth); clrtoeol (); if ((WithCrypto & APPLICATION_PGP) @@ -159,7 +159,7 @@ static void redraw_crypt_lines (HEADER *msg) && (msg->security & ENCRYPT) && SmimeCryptAlg && *SmimeCryptAlg) { - mvprintw (HDR_CRYPTINFO, 40, "%s%s", _("Encrypt with: "), + mvprintw (HDR_CRYPTINFO, SidebarWidth + 40, "%s%s", _("Encrypt with: "), NONULL(SmimeCryptAlg)); } } @@ -172,7 +172,7 @@ static void redraw_mix_line (LIST *chain) int c; char *t; - mvaddstr (HDR_MIX, 0, " Mix: "); + mvaddstr (HDR_MIX, SidebarWidth, " Mix: "); if (!chain) { @@ -187,7 +187,7 @@ static void redraw_mix_line (LIST *chain) if (t && t[0] == '0' && t[1] == '\0') t = ""; - if (c + mutt_strlen (t) + 2 >= COLS) + if (c + mutt_strlen (t) + 2 >= COLS - SidebarWidth) break; addstr (NONULL(t)); @@ -239,7 +239,7 @@ static void draw_envelope_addr (int line, ADDRESS *addr) buf[0] = 0; rfc822_write_address (buf, sizeof (buf), addr, 1); - mvprintw (line, 0, TITLE_FMT, Prompts[line - 1]); + mvprintw (line, SidebarWidth, TITLE_FMT, Prompts[line - 1]); mutt_paddstr (W, buf); } @@ -249,10 +249,10 @@ static void draw_envelope (HEADER *msg, char *fcc) draw_envelope_addr (HDR_TO, msg->env->to); draw_envelope_addr (HDR_CC, msg->env->cc); draw_envelope_addr (HDR_BCC, msg->env->bcc); - mvprintw (HDR_SUBJECT, 0, TITLE_FMT, Prompts[HDR_SUBJECT - 1]); + mvprintw (HDR_SUBJECT, SidebarWidth, TITLE_FMT, Prompts[HDR_SUBJECT - 1]); mutt_paddstr (W, NONULL (msg->env->subject)); draw_envelope_addr (HDR_REPLYTO, msg->env->reply_to); - mvprintw (HDR_FCC, 0, TITLE_FMT, Prompts[HDR_FCC - 1]); + mvprintw (HDR_FCC, SidebarWidth, TITLE_FMT, Prompts[HDR_FCC - 1]); mutt_paddstr (W, fcc); if (WithCrypto) @@ -263,7 +263,7 @@ static void draw_envelope (HEADER *msg, char *fcc) #endif SETCOLOR (MT_COLOR_STATUS); - mvaddstr (HDR_ATTACH - 1, 0, _("-- Attachments")); + mvaddstr (HDR_ATTACH - 1, SidebarWidth, _("-- Attachments")); clrtoeol (); NORMAL_COLOR; @@ -299,7 +299,7 @@ static int edit_address_list (int line, ADDRESS **addr) /* redraw the expanded list so the user can see the result */ buf[0] = 0; rfc822_write_address (buf, sizeof (buf), *addr, 1); - move (line, HDR_XOFFSET); + move (line, HDR_XOFFSET+SidebarWidth); mutt_paddstr (W, buf); return 0; @@ -544,7 +544,7 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ if (mutt_get_field ("Subject: ", buf, sizeof (buf), 0) == 0) { mutt_str_replace (&msg->env->subject, buf); - move (HDR_SUBJECT, HDR_XOFFSET); + move (HDR_SUBJECT, HDR_XOFFSET + SidebarWidth); if (msg->env->subject) mutt_paddstr (W, msg->env->subject); else @@ -562,7 +562,7 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ { strfcpy (fcc, buf, fcclen); mutt_pretty_mailbox (fcc, fcclen); - move (HDR_FCC, HDR_XOFFSET); + move (HDR_FCC, HDR_XOFFSET + SidebarWidth); mutt_paddstr (W, fcc); fccSet = 1; } diff --git a/curs_main.c b/curs_main.c index e7f11bd..7b81798 100644 --- a/curs_main.c +++ b/curs_main.c @@ -26,7 +26,9 @@ #include "mailbox.h" #include "mapping.h" #include "sort.h" +#include "buffy.h" #include "mx.h" +#include "sidebar.h" #ifdef USE_POP #include "pop.h" @@ -532,8 +534,12 @@ int mutt_index_menu (void) menu->redraw |= REDRAW_STATUS; if (do_buffy_notify) { - if (mutt_buffy_notify () && option (OPTBEEPNEW)) - beep (); + if (mutt_buffy_notify ()) + { + menu->redraw |= REDRAW_FULL; + if (option (OPTBEEPNEW)) + beep (); + } } else do_buffy_notify = 1; @@ -545,6 +551,7 @@ int mutt_index_menu (void) if (menu->redraw & REDRAW_FULL) { menu_redraw_full (menu); + draw_sidebar(menu->menu); mutt_show_error (); } @@ -567,9 +574,12 @@ int mutt_index_menu (void) if (menu->redraw & REDRAW_STATUS) { + DrawFullLine = 1; menu_status_line (buf, sizeof (buf), menu, NONULL (Status)); + DrawFullLine = 0; move (option (OPTSTATUSONTOP) ? 0 : LINES-2, 0); SETCOLOR (MT_COLOR_STATUS); + set_buffystats(Context); mutt_paddstr (COLS, buf); NORMAL_COLOR; menu->redraw &= ~REDRAW_STATUS; @@ -589,7 +599,7 @@ int mutt_index_menu (void) menu->oldcurrent = -1; if (option (OPTARROWCURSOR)) - move (menu->current - menu->top + menu->offset, 2); + move (menu->current - menu->top + menu->offset, SidebarWidth + 2); else if (option (OPTBRAILLEFRIENDLY)) move (menu->current - menu->top + menu->offset, 0); else @@ -1090,6 +1100,7 @@ int mutt_index_menu (void) menu->redraw = REDRAW_FULL; break; + case OP_SIDEBAR_OPEN: case OP_MAIN_CHANGE_FOLDER: case OP_MAIN_NEXT_UNREAD_MAILBOX: @@ -1121,7 +1132,11 @@ int mutt_index_menu (void) { mutt_buffy (buf, sizeof (buf)); - if (mutt_enter_fname (cp, buf, sizeof (buf), &menu->redraw, 1) == -1) + if ( op == OP_SIDEBAR_OPEN ) { + if(!CurBuffy) + break; + strncpy( buf, CurBuffy->path, sizeof(buf) ); + } else if (mutt_enter_fname (cp, buf, sizeof (buf), &menu->redraw, 1) == -1) { if (menu->menu == MENU_PAGER) { @@ -1139,6 +1154,7 @@ int mutt_index_menu (void) } mutt_expand_path (buf, sizeof (buf)); + set_curbuffy(buf); if (mx_get_magic (buf) <= 0) { mutt_error (_("%s is not a mailbox."), buf); @@ -2242,6 +2258,12 @@ int mutt_index_menu (void) mutt_what_key(); break; + case OP_SIDEBAR_SCROLL_UP: + case OP_SIDEBAR_SCROLL_DOWN: + case OP_SIDEBAR_NEXT: + case OP_SIDEBAR_PREV: + scroll_sidebar(op, menu->menu); + break; default: if (menu->menu == MENU_MAIN) km_error_key (MENU_MAIN); diff --git a/flags.c b/flags.c index 133fa35..48fb287 100644 --- a/flags.c +++ b/flags.c @@ -22,8 +22,10 @@ #include "mutt.h" #include "mutt_curses.h" +#include "mutt_menu.h" #include "sort.h" #include "mx.h" +#include "sidebar.h" void _mutt_set_flag (CONTEXT *ctx, HEADER *h, int flag, int bf, int upd_ctx) { @@ -290,6 +292,7 @@ void _mutt_set_flag (CONTEXT *ctx, HEADER *h, int flag, int bf, int upd_ctx) */ if (h->searched && (changed != h->changed || deleted != ctx->deleted || tagged != ctx->tagged || flagged != ctx->flagged)) h->searched = 0; + draw_sidebar(0); } void mutt_tag_set_flag (int flag, int bf) diff --git a/functions.h b/functions.h index 26171a0..ef8937a 100644 --- a/functions.h +++ b/functions.h @@ -170,6 +170,11 @@ const struct binding_t OpMain[] = { /* map: index */ { "decrypt-save", OP_DECRYPT_SAVE, NULL }, + { "sidebar-scroll-up", OP_SIDEBAR_SCROLL_UP, NULL }, + { "sidebar-scroll-down", OP_SIDEBAR_SCROLL_DOWN, NULL }, + { "sidebar-next", OP_SIDEBAR_NEXT, NULL }, + { "sidebar-prev", OP_SIDEBAR_PREV, NULL }, + { "sidebar-open", OP_SIDEBAR_OPEN, NULL }, { NULL, 0, NULL } }; @@ -274,6 +279,11 @@ const struct binding_t OpPager[] = { /* map: pager */ { "what-key", OP_WHAT_KEY, NULL }, + { "sidebar-scroll-up", OP_SIDEBAR_SCROLL_UP, NULL }, + { "sidebar-scroll-down", OP_SIDEBAR_SCROLL_DOWN, NULL }, + { "sidebar-next", OP_SIDEBAR_NEXT, NULL }, + { "sidebar-prev", OP_SIDEBAR_PREV, NULL }, + { "sidebar-open", OP_SIDEBAR_OPEN, NULL }, { NULL, 0, NULL } }; diff --git a/globals.h b/globals.h index 5b6e56a..3f83328 100644 --- a/globals.h +++ b/globals.h @@ -117,6 +117,7 @@ WHERE short SearchContext; WHERE char *SendCharset; WHERE char *Sendmail; WHERE char *Shell; +WHERE char *SidebarDelim; WHERE char *Signature; WHERE char *SimpleSearch; #if USE_SMTP @@ -211,6 +212,9 @@ WHERE short ScoreThresholdDelete; WHERE short ScoreThresholdRead; WHERE short ScoreThresholdFlag; +WHERE struct buffy_t *CurBuffy INITVAL(0); +WHERE short DrawFullLine INITVAL(0); +WHERE short SidebarWidth; #ifdef USE_IMAP WHERE short ImapKeepalive; WHERE short ImapPipelineDepth; diff --git a/imap/command.c b/imap/command.c index 32f8417..d68e3ab 100644 --- a/imap/command.c +++ b/imap/command.c @@ -1012,6 +1012,13 @@ static void cmd_parse_status (IMAP_DATA* idata, char* s) opened */ status->uidnext = oldun; + /* Added to make the sidebar show the correct numbers */ + if (status->messages) + { + inc->msgcount = status->messages; + inc->msg_unread = status->unseen; + } + FREE (&value); return; } diff --git a/imap/imap.c b/imap/imap.c index 393d4ec..4b1ec86 100644 --- a/imap/imap.c +++ b/imap/imap.c @@ -1520,7 +1520,7 @@ int imap_buffy_check (int force) imap_munge_mbox_name (munged, sizeof (munged), name); snprintf (command, sizeof (command), - "STATUS %s (UIDNEXT UIDVALIDITY UNSEEN RECENT)", munged); + "STATUS %s (UIDNEXT UIDVALIDITY UNSEEN RECENT MESSAGES)", munged); if (imap_exec (idata, command, IMAP_CMD_QUEUE) < 0) { diff --git a/init.h b/init.h index 08e004c..502f570 100644 --- a/init.h +++ b/init.h @@ -1980,6 +1980,27 @@ struct option_t MuttVars[] = { ** not used. ** (PGP only) */ + {"sidebar_delim", DT_STR, R_BOTH, UL &SidebarDelim, "|"}, + /* + ** .pp + ** This specifies the delimiter between the sidebar (if visible) and + ** other screens. + */ + { "sidebar_visible", DT_BOOL, R_BOTH, OPTSIDEBAR, 0 }, + /* + ** .pp + ** This specifies whether or not to show sidebar (left-side list of folders). + */ + { "sidebar_sort", DT_BOOL, R_BOTH, OPTSIDEBARSORT, 0 }, + /* + ** .pp + ** This specifies whether or not to sort the sidebar alphabetically. + */ + { "sidebar_width", DT_NUM, R_BOTH, UL &SidebarWidth, 0 }, + /* + ** .pp + ** The width of the sidebar. + */ { "pgp_use_gpg_agent", DT_BOOL, R_NONE, OPTUSEGPGAGENT, 0}, /* ** .pp diff --git a/mailbox.h b/mailbox.h index 91e5dc7..b652628 100644 --- a/mailbox.h +++ b/mailbox.h @@ -27,6 +27,7 @@ #define M_NEWFOLDER (1<<4) /* create a new folder - same as M_APPEND, but uses * safe_fopen() for mbox-style folders. */ +#define M_PEEK (1<<5) /* revert atime back after taking a look (if applicable) */ /* mx_open_new_message() */ #define M_ADD_FROM 1 /* add a From_ line */ diff --git a/mbox.c b/mbox.c index 6d3b6bd..fa82eb3 100644 --- a/mbox.c +++ b/mbox.c @@ -104,6 +104,7 @@ int mmdf_parse_mailbox (CONTEXT *ctx) mutt_perror (ctx->path); return (-1); } + ctx->atime = sb.st_atime; ctx->mtime = sb.st_mtime; ctx->size = sb.st_size; @@ -255,6 +256,7 @@ int mbox_parse_mailbox (CONTEXT *ctx) ctx->size = sb.st_size; ctx->mtime = sb.st_mtime; + ctx->atime = sb.st_atime; #ifdef NFS_ATTRIBUTE_HACK if (sb.st_mtime > sb.st_atime) diff --git a/menu.c b/menu.c index 27b5f8e..bc3a02f 100644 --- a/menu.c +++ b/menu.c @@ -24,6 +24,7 @@ #include "mutt_curses.h" #include "mutt_menu.h" #include "mbyte.h" +#include "sidebar.h" extern size_t UngetCount; @@ -186,7 +187,7 @@ static void menu_pad_string (char *s, size_t n) { char *scratch = safe_strdup (s); int shift = option (OPTARROWCURSOR) ? 3 : 0; - int cols = COLS - shift; + int cols = COLS - shift - SidebarWidth; mutt_format_string (s, n, cols, cols, FMT_LEFT, ' ', scratch, mutt_strlen (scratch), 1); s[n - 1] = 0; @@ -239,6 +240,7 @@ void menu_redraw_index (MUTTMENU *menu) int do_color; int attr; + draw_sidebar(1); for (i = menu->top; i < menu->top + menu->pagelen; i++) { if (i < menu->max) @@ -249,7 +251,7 @@ void menu_redraw_index (MUTTMENU *menu) menu_pad_string (buf, sizeof (buf)); ATTRSET(attr); - move(i - menu->top + menu->offset, 0); + move(i - menu->top + menu->offset, SidebarWidth); do_color = 1; if (i == menu->current) @@ -272,7 +274,7 @@ void menu_redraw_index (MUTTMENU *menu) else { NORMAL_COLOR; - CLEARLINE(i - menu->top + menu->offset); + CLEARLINE_WIN(i - menu->top + menu->offset); } } NORMAL_COLOR; @@ -289,7 +291,7 @@ void menu_redraw_motion (MUTTMENU *menu) return; } - move (menu->oldcurrent + menu->offset - menu->top, 0); + move (menu->oldcurrent + menu->offset - menu->top, SidebarWidth); ATTRSET(menu->color (menu->oldcurrent)); if (option (OPTARROWCURSOR)) @@ -301,13 +303,13 @@ void menu_redraw_motion (MUTTMENU *menu) { menu_make_entry (buf, sizeof (buf), menu, menu->oldcurrent); menu_pad_string (buf, sizeof (buf)); - move (menu->oldcurrent + menu->offset - menu->top, 3); + move (menu->oldcurrent + menu->offset - menu->top, SidebarWidth + 3); print_enriched_string (menu->color(menu->oldcurrent), (unsigned char *) buf, 1); } /* now draw it in the new location */ SETCOLOR(MT_COLOR_INDICATOR); - mvaddstr(menu->current + menu->offset - menu->top, 0, "->"); + mvaddstr(menu->current + menu->offset - menu->top, SidebarWidth, "->"); } else { @@ -320,7 +322,7 @@ void menu_redraw_motion (MUTTMENU *menu) menu_make_entry (buf, sizeof (buf), menu, menu->current); menu_pad_string (buf, sizeof (buf)); SETCOLOR(MT_COLOR_INDICATOR); - move(menu->current - menu->top + menu->offset, 0); + move(menu->current - menu->top + menu->offset, SidebarWidth); print_enriched_string (menu->color(menu->current), (unsigned char *) buf, 0); } menu->redraw &= REDRAW_STATUS; @@ -332,7 +334,7 @@ void menu_redraw_current (MUTTMENU *menu) char buf[LONG_STRING]; int attr = menu->color (menu->current); - move (menu->current + menu->offset - menu->top, 0); + move (menu->current + menu->offset - menu->top, SidebarWidth); menu_make_entry (buf, sizeof (buf), menu, menu->current); menu_pad_string (buf, sizeof (buf)); @@ -881,7 +883,7 @@ int mutt_menuLoop (MUTTMENU *menu) if (option (OPTARROWCURSOR)) - move (menu->current - menu->top + menu->offset, 2); + move (menu->current - menu->top + menu->offset, SidebarWidth + 2); else if (option (OPTBRAILLEFRIENDLY)) move (menu->current - menu->top + menu->offset, 0); else diff --git a/mh.c b/mh.c index 21e6491..48a16fb 100644 --- a/mh.c +++ b/mh.c @@ -295,6 +295,28 @@ void mh_buffy(BUFFY *b) mhs_free_sequences (&mhs); } +void mh_buffy_update (const char *path, int *msgcount, int *msg_unread, int *msg_flagged) +{ + int i; + struct mh_sequences mhs; + memset (&mhs, 0, sizeof (mhs)); + + if (mh_read_sequences (&mhs, path) < 0) + return; + + msgcount = 0; + msg_unread = 0; + msg_flagged = 0; + for (i = 0; i <= mhs.max; i++) + msgcount++; + if (mhs_check (&mhs, i) & MH_SEQ_UNSEEN) { + msg_unread++; + } + if (mhs_check (&mhs, i) & MH_SEQ_FLAGGED) + msg_flagged++; + mhs_free_sequences (&mhs); +} + static int mh_mkstemp (CONTEXT * dest, FILE ** fp, char **tgt) { int fd; diff --git a/mutt.h b/mutt.h index 4db92a6..61a9612 100644 --- a/mutt.h +++ b/mutt.h @@ -433,6 +433,8 @@ enum OPTSAVEEMPTY, OPTSAVENAME, OPTSCORE, + OPTSIDEBAR, + OPTSIDEBARSORT, OPTSIGDASHES, OPTSIGONTOP, OPTSORTRE, @@ -876,6 +878,7 @@ typedef struct _context { char *path; FILE *fp; + time_t atime; time_t mtime; off_t size; off_t vsize; @@ -916,6 +919,7 @@ typedef struct _context unsigned int quiet : 1; /* inhibit status messages? */ unsigned int collapsed : 1; /* are all threads collapsed? */ unsigned int closing : 1; /* mailbox is being closed */ + unsigned int peekonly : 1; /* just taking a glance, revert atime */ /* driver hooks */ void *data; /* driver specific data */ diff --git a/mutt_curses.h b/mutt_curses.h index f8d6f88..aee797e 100644 --- a/mutt_curses.h +++ b/mutt_curses.h @@ -64,6 +64,7 @@ #undef lines #endif /* lines */ +#define CLEARLINE_WIN(x) move(x,SidebarWidth), clrtoeol() #define CLEARLINE(x) move(x,0), clrtoeol() #define CENTERLINE(x,y) move(y, (COLS-strlen(x))/2), addstr(x) #define BEEP() do { if (option (OPTBEEP)) beep(); } while (0) @@ -120,6 +121,8 @@ enum MT_COLOR_BOLD, MT_COLOR_UNDERLINE, MT_COLOR_INDEX, + MT_COLOR_NEW, + MT_COLOR_FLAGGED, MT_COLOR_MAX }; diff --git a/muttlib.c b/muttlib.c index c0d2026..9086f07 100644 --- a/muttlib.c +++ b/muttlib.c @@ -1284,6 +1284,8 @@ void mutt_FormatString (char *dest, /* output buffer */ pl = pw = 1; /* see if there's room to add content, else ignore */ + if ( DrawFullLine ) + { if ((col < COLS && wlen < destlen) || soft) { int pad; @@ -1327,6 +1329,52 @@ void mutt_FormatString (char *dest, /* output buffer */ col += wid; src += pl; } + } + else + { + if ((col < COLS-SidebarWidth && wlen < destlen) || soft) + { + int pad; + + /* get contents after padding */ + mutt_FormatString (buf, sizeof (buf), 0, src + pl, callback, data, flags); + len = mutt_strlen (buf); + wid = mutt_strwidth (buf); + + /* try to consume as many columns as we can, if we don't have + * memory for that, use as much memory as possible */ + pad = (COLS - SidebarWidth - col - wid) / pw; + if (pad > 0 && wlen + (pad * pl) + len > destlen) + pad = ((signed)(destlen - wlen - len)) / pl; + if (pad > 0) + { + while (pad--) + { + memcpy (wptr, src, pl); + wptr += pl; + wlen += pl; + col += pw; + } + } + else if (soft && pad < 0) + { + /* \0-terminate dest for length computation in mutt_wstr_trunc() */ + *wptr = 0; + /* make sure right part is at most as wide as display */ + len = mutt_wstr_trunc (buf, destlen, COLS, &wid); + /* truncate left so that right part fits completely in */ + wlen = mutt_wstr_trunc (dest, destlen - len, col + pad, &col); + wptr = dest + wlen; + } + if (len + wlen > destlen) + len = mutt_wstr_trunc (buf, destlen - wlen, COLS - SidebarWidth - col, NULL); + memcpy (wptr, buf, len); + wptr += len; + wlen += len; + col += wid; + src += pl; + } + } break; /* skip rest of input */ } else if (ch == '|') diff --git a/mx.c b/mx.c index 07dba0c..cbee47d 100644 --- a/mx.c +++ b/mx.c @@ -595,6 +595,7 @@ static int mx_open_mailbox_append (CONTEXT *ctx, int flags) * M_APPEND open mailbox for appending * M_READONLY open mailbox in read-only mode * M_QUIET only print error messages + * M_PEEK revert atime where applicable * ctx if non-null, context struct to use */ CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx) @@ -617,6 +618,8 @@ CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx) ctx->quiet = 1; if (flags & M_READONLY) ctx->readonly = 1; + if (flags & M_PEEK) + ctx->peekonly = 1; if (flags & (M_APPEND|M_NEWFOLDER)) { @@ -721,9 +724,21 @@ CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx) void mx_fastclose_mailbox (CONTEXT *ctx) { int i; +#ifndef BUFFY_SIZE + struct utimbuf ut; +#endif if(!ctx) return; +#ifndef BUFFY_SIZE + /* fix up the times so buffy won't get confused */ + if (ctx->peekonly && ctx->path && ctx->mtime > ctx->atime) + { + ut.actime = ctx->atime; + ut.modtime = ctx->mtime; + utime (ctx->path, &ut); + } +#endif /* never announce that a mailbox we've just left has new mail. #3290 * XXX: really belongs in mx_close_mailbox, but this is a nice hook point */ diff --git a/mx.h b/mx.h index 2ef4ec7..4aabadf 100644 --- a/mx.h +++ b/mx.h @@ -60,6 +60,7 @@ void mbox_reset_atime (CONTEXT *, struct stat *); int mh_read_dir (CONTEXT *, const char *); int mh_sync_mailbox (CONTEXT *, int *); int mh_check_mailbox (CONTEXT *, int *); +void mh_buffy_update (const char *, int *, int *, int *); int mh_check_empty (const char *); int maildir_read_dir (CONTEXT *); diff --git a/pager.c b/pager.c index 7b61266..469efe4 100644 --- a/pager.c +++ b/pager.c @@ -29,6 +29,7 @@ #include "pager.h" #include "attach.h" #include "mbyte.h" +#include "sidebar.h" #include "mutt_crypt.h" @@ -1095,6 +1096,7 @@ static int format_line (struct line_t **lineInfo, int n, unsigned char *buf, wchar_t wc; mbstate_t mbstate; int wrap_cols = mutt_term_width ((flags & M_PAGER_NOWRAP) ? 0 : Wrap); + wrap_cols -= SidebarWidth; if (check_attachment_marker ((char *)buf) == 0) wrap_cols = COLS; @@ -1746,7 +1748,7 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra) if ((redraw & REDRAW_BODY) || topline != oldtopline) { do { - move (bodyoffset, 0); + move (bodyoffset, SidebarWidth); curline = oldtopline = topline; lines = 0; force_redraw = 0; @@ -1759,6 +1761,7 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra) &QuoteList, &q_level, &force_redraw, &SearchRE) > 0) lines++; curline++; + move(lines + bodyoffset, SidebarWidth); } last_offset = lineInfo[curline].offset; } while (force_redraw); @@ -1771,6 +1774,7 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra) addch ('~'); addch ('\n'); lines++; + move(lines + bodyoffset, SidebarWidth); } NORMAL_COLOR; @@ -1794,22 +1798,22 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra) strfcpy(pager_progress_str, (topline == 0) ? "all" : "end", sizeof(pager_progress_str)); /* print out the pager status bar */ - move (statusoffset, 0); + move (statusoffset, SidebarWidth); SETCOLOR (MT_COLOR_STATUS); if (IsHeader (extra) || IsMsgAttach (extra)) { - size_t l1 = COLS * MB_LEN_MAX; + size_t l1 = (COLS-SidebarWidth) * MB_LEN_MAX; size_t l2 = sizeof (buffer); hfi.hdr = (IsHeader (extra)) ? extra->hdr : extra->bdy->hdr; mutt_make_string_info (buffer, l1 < l2 ? l1 : l2, NONULL (PagerFmt), &hfi, M_FORMAT_MAKEPRINT); - mutt_paddstr (COLS, buffer); + mutt_paddstr (COLS-SidebarWidth, buffer); } else { char bn[STRING]; snprintf (bn, sizeof (bn), "%s (%s)", banner, pager_progress_str); - mutt_paddstr (COLS, bn); + mutt_paddstr (COLS-SidebarWidth, bn); } NORMAL_COLOR; if (option(OPTXTERMSETTITLES)) @@ -1826,16 +1830,21 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra) /* redraw the pager_index indicator, because the * flags for this message might have changed. */ menu_redraw_current (index); + draw_sidebar(MENU_PAGER); /* print out the index status bar */ menu_status_line (buffer, sizeof (buffer), index, NONULL(Status)); - move (indexoffset + (option (OPTSTATUSONTOP) ? 0 : (indexlen - 1)), 0); + move (indexoffset + (option (OPTSTATUSONTOP) ? 0 : (indexlen - 1)), SidebarWidth); SETCOLOR (MT_COLOR_STATUS); - mutt_paddstr (COLS, buffer); + mutt_paddstr (COLS-SidebarWidth, buffer); NORMAL_COLOR; } + /* if we're not using the index, update every time */ + if ( index == 0 ) + draw_sidebar(MENU_PAGER); + redraw = 0; if (option(OPTBRAILLEFRIENDLY)) { @@ -2776,6 +2785,13 @@ search_next: mutt_what_key (); break; + case OP_SIDEBAR_SCROLL_UP: + case OP_SIDEBAR_SCROLL_DOWN: + case OP_SIDEBAR_NEXT: + case OP_SIDEBAR_PREV: + scroll_sidebar(ch, MENU_PAGER); + break; + default: ch = -1; break; diff --git a/sidebar.c b/sidebar.c new file mode 100644 index 0000000..6098c2a --- /dev/null +++ b/sidebar.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) ????-2004 Justin Hibbits + * Copyright (C) 2004 Thomer M. Gil + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include "mutt.h" +#include "mutt_menu.h" +#include "mutt_curses.h" +#include "sidebar.h" +#include "buffy.h" +#include +#include "keymap.h" +#include + +/*BUFFY *CurBuffy = 0;*/ +static BUFFY *TopBuffy = 0; +static BUFFY *BottomBuffy = 0; +static int known_lines = 0; + +static int quick_log10(int n) +{ + char string[32]; + sprintf(string, "%d", n); + return strlen(string); +} + +void calc_boundaries (int menu) +{ + BUFFY *tmp = Incoming; + + if ( known_lines != LINES ) { + TopBuffy = BottomBuffy = 0; + known_lines = LINES; + } + for ( ; tmp->next != 0; tmp = tmp->next ) + tmp->next->prev = tmp; + + if ( TopBuffy == 0 && BottomBuffy == 0 ) + TopBuffy = Incoming; + if ( BottomBuffy == 0 ) { + int count = LINES - 2 - (menu != MENU_PAGER || option(OPTSTATUSONTOP)); + BottomBuffy = TopBuffy; + while ( --count && BottomBuffy->next ) + BottomBuffy = BottomBuffy->next; + } + else if ( TopBuffy == CurBuffy->next ) { + int count = LINES - 2 - (menu != MENU_PAGER); + BottomBuffy = CurBuffy; + tmp = BottomBuffy; + while ( --count && tmp->prev) + tmp = tmp->prev; + TopBuffy = tmp; + } + else if ( BottomBuffy == CurBuffy->prev ) { + int count = LINES - 2 - (menu != MENU_PAGER); + TopBuffy = CurBuffy; + tmp = TopBuffy; + while ( --count && tmp->next ) + tmp = tmp->next; + BottomBuffy = tmp; + } +} + +char *make_sidebar_entry(char *box, int size, int new, int flagged) +{ + static char *entry = 0; + char *c; + int i = 0; + int delim_len = strlen(SidebarDelim); + + c = realloc(entry, SidebarWidth - delim_len + 2); + if ( c ) entry = c; + entry[SidebarWidth - delim_len + 1] = 0; + for (; i < SidebarWidth - delim_len + 1; entry[i++] = ' ' ); + i = strlen(box); + strncpy( entry, box, i < (SidebarWidth - delim_len + 1) ? i : (SidebarWidth - delim_len + 1) ); + + if (size == -1) + sprintf(entry + SidebarWidth - delim_len - 3, "?"); + else if ( new ) { + if (flagged > 0) { + sprintf( + entry + SidebarWidth - delim_len - 5 - quick_log10(size) - quick_log10(new) - quick_log10(flagged), + "% d(%d)[%d]", size, new, flagged); + } else { + sprintf( + entry + SidebarWidth - delim_len - 3 - quick_log10(size) - quick_log10(new), + "% d(%d)", size, new); + } + } else if (flagged > 0) { + sprintf( entry + SidebarWidth - delim_len - 3 - quick_log10(size) - quick_log10(flagged), "% d[%d]", size, flagged); + } else { + sprintf( entry + SidebarWidth - delim_len - 1 - quick_log10(size), "% d", size); + } + return entry; +} + +void set_curbuffy(char buf[LONG_STRING]) +{ + BUFFY* tmp = CurBuffy = Incoming; + + if (!Incoming) + return; + + while(1) { + if(!strcmp(tmp->path, buf)) { + CurBuffy = tmp; + break; + } + + if(tmp->next) + tmp = tmp->next; + else + break; + } +} + +int draw_sidebar(int menu) { + + int lines = option(OPTHELP) ? 1 : 0; + BUFFY *tmp; +#ifndef USE_SLANG_CURSES + attr_t attrs; +#endif + short delim_len = strlen(SidebarDelim); + short color_pair; + + static bool initialized = false; + static int prev_show_value; + static short saveSidebarWidth; + + /* initialize first time */ + if(!initialized) { + prev_show_value = option(OPTSIDEBAR); + saveSidebarWidth = SidebarWidth; + if(!option(OPTSIDEBAR)) SidebarWidth = 0; + initialized = true; + } + + /* save or restore the value SidebarWidth */ + if(prev_show_value != option(OPTSIDEBAR)) { + if(prev_show_value && !option(OPTSIDEBAR)) { + saveSidebarWidth = SidebarWidth; + SidebarWidth = 0; + } else if(!prev_show_value && option(OPTSIDEBAR)) { + SidebarWidth = saveSidebarWidth; + } + prev_show_value = option(OPTSIDEBAR); + } + + +// if ( SidebarWidth == 0 ) return 0; + if (SidebarWidth > 0 && option (OPTSIDEBAR) + && delim_len >= SidebarWidth) { + unset_option (OPTSIDEBAR); + /* saveSidebarWidth = SidebarWidth; */ + if (saveSidebarWidth > delim_len) { + SidebarWidth = saveSidebarWidth; + mutt_error (_("Value for sidebar_delim is too long. Disabling sidebar.")); + sleep (2); + } else { + SidebarWidth = 0; + mutt_error (_("Value for sidebar_delim is too long. Disabling sidebar. Please set your sidebar_width to a sane value.")); + sleep (4); /* the advise to set a sane value should be seen long enough */ + } + saveSidebarWidth = 0; + return (0); + } + + if ( SidebarWidth == 0 || !option(OPTSIDEBAR)) { + if (SidebarWidth > 0) { + saveSidebarWidth = SidebarWidth; + SidebarWidth = 0; + } + unset_option(OPTSIDEBAR); + return 0; + } + + /* get attributes for divider */ + SETCOLOR(MT_COLOR_STATUS); +#ifndef USE_SLANG_CURSES + attr_get(&attrs, &color_pair, 0); +#else + color_pair = attr_get(); +#endif + SETCOLOR(MT_COLOR_NORMAL); + + /* draw the divider */ + + for ( ; lines < LINES-1-(menu != MENU_PAGER || option(OPTSTATUSONTOP)); lines++ ) { + move(lines, SidebarWidth - delim_len); + addstr(NONULL(SidebarDelim)); +#ifndef USE_SLANG_CURSES + mvchgat(lines, SidebarWidth - delim_len, delim_len, 0, color_pair, NULL); +#endif + } + + if ( Incoming == 0 ) return 0; + lines = option(OPTHELP) ? 1 : 0; /* go back to the top */ + + if ( known_lines != LINES || TopBuffy == 0 || BottomBuffy == 0 ) + calc_boundaries(menu); + if ( CurBuffy == 0 ) CurBuffy = Incoming; + + tmp = TopBuffy; + + SETCOLOR(MT_COLOR_NORMAL); + + for ( ; tmp && lines < LINES-1 - (menu != MENU_PAGER || option(OPTSTATUSONTOP)); tmp = tmp->next ) { + if ( tmp == CurBuffy ) + SETCOLOR(MT_COLOR_INDICATOR); + else if ( tmp->msg_unread > 0 ) + SETCOLOR(MT_COLOR_NEW); + else if ( tmp->msg_flagged > 0 ) + SETCOLOR(MT_COLOR_FLAGGED); + else + SETCOLOR(MT_COLOR_NORMAL); + + move( lines, 0 ); + if ( Context && !strcmp( tmp->path, Context->path ) ) { + tmp->msg_unread = Context->unread; + tmp->msgcount = Context->msgcount; + tmp->msg_flagged = Context->flagged; + } + // check whether Maildir is a prefix of the current folder's path + short maildir_is_prefix = 0; + if ( (strlen(tmp->path) > strlen(Maildir)) && + (strncmp(Maildir, tmp->path, strlen(Maildir)) == 0) ) + maildir_is_prefix = 1; + // calculate depth of current folder and generate its display name with indented spaces + int sidebar_folder_depth = 0; + char *sidebar_folder_name; + sidebar_folder_name = basename(tmp->path); + if ( maildir_is_prefix ) { + char *tmp_folder_name; + int i; + tmp_folder_name = tmp->path + strlen(Maildir); + for (i = 0; i < strlen(tmp->path) - strlen(Maildir); i++) { + if (tmp_folder_name[i] == '/') sidebar_folder_depth++; + } + if (sidebar_folder_depth > 0) { + sidebar_folder_name = malloc(strlen(basename(tmp->path)) + sidebar_folder_depth + 1); + for (i=0; i < sidebar_folder_depth; i++) + sidebar_folder_name[i]=' '; + sidebar_folder_name[i]=0; + strncat(sidebar_folder_name, basename(tmp->path), strlen(basename(tmp->path)) + sidebar_folder_depth); + } + } + printw( "%.*s", SidebarWidth - delim_len + 1, + make_sidebar_entry(sidebar_folder_name, tmp->msgcount, + tmp->msg_unread, tmp->msg_flagged)); + if (sidebar_folder_depth > 0) + free(sidebar_folder_name); + lines++; + } + SETCOLOR(MT_COLOR_NORMAL); + for ( ; lines < LINES-1 - (menu != MENU_PAGER || option(OPTSTATUSONTOP)); lines++ ) { + int i = 0; + move( lines, 0 ); + for ( ; i < SidebarWidth - delim_len; i++ ) + addch(' '); + } + return 0; +} + + +void set_buffystats(CONTEXT* Context) +{ + BUFFY *tmp = Incoming; + while(tmp) { + if(Context && !strcmp(tmp->path, Context->path)) { + tmp->msg_unread = Context->unread; + tmp->msgcount = Context->msgcount; + break; + } + tmp = tmp->next; + } +} + +void scroll_sidebar(int op, int menu) +{ + if(!SidebarWidth) return; + if(!CurBuffy) return; + + switch (op) { + case OP_SIDEBAR_NEXT: + if ( CurBuffy->next == NULL ) return; + CurBuffy = CurBuffy->next; + break; + case OP_SIDEBAR_PREV: + if ( CurBuffy->prev == NULL ) return; + CurBuffy = CurBuffy->prev; + break; + case OP_SIDEBAR_SCROLL_UP: + CurBuffy = TopBuffy; + if ( CurBuffy != Incoming ) { + calc_boundaries(menu); + CurBuffy = CurBuffy->prev; + } + break; + case OP_SIDEBAR_SCROLL_DOWN: + CurBuffy = BottomBuffy; + if ( CurBuffy->next ) { + calc_boundaries(menu); + CurBuffy = CurBuffy->next; + } + break; + default: + return; + } + calc_boundaries(menu); + draw_sidebar(menu); +} + diff --git a/sidebar.h b/sidebar.h new file mode 100644 index 0000000..d195f11 --- /dev/null +++ b/sidebar.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) ????-2004 Justin Hibbits + * Copyright (C) 2004 Thomer M. Gil + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +#ifndef SIDEBAR_H +#define SIDEBAR_H + +struct MBOX_LIST { + char *path; + int msgcount; + int new; +} MBLIST; + +/* parameter is whether or not to go to the status line */ +/* used for omitting the last | that covers up the status bar in the index */ +int draw_sidebar(int); +void scroll_sidebar(int, int); +void set_curbuffy(char*); +void set_buffystats(CONTEXT*); + +#endif /* SIDEBAR_H */