Logo Search packages:      
Sourcecode: af version File versions  Download package

compose.c

/* Compose.c - compose mode handling for af.
   Copyright (C) 2002 - 2003 Malc Arnold.

   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, 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.  */


#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include "af.h"
#include "keyseq.h"
#include "functions.h"
#include "commands.h"
#include "sendmail.h"
#include "variable.h"
#include "mode.h"
#include "complete.h"
#include "typeout.h"
#include "mime.h"
#include "mailcap.h"
#include "io.h"
#include STRING_HDR

/****************************************************************************/
/* RCS info */

#ifndef lint
static char *RcsId = "$Id: compose.c,v 1.4 2003/11/27 01:45:57 malc Exp $";
#endif /* ! lint */

/****************************************************************************/
/* Global function declarations */

extern char *xstrdup(), *vstrcat(), *expand();
extern char *get_cstr(), *get_dstr(), *formtext();
extern char *content_type(), *encode_header();
extern char *typeonly(), *xtos();
extern int unlink(), interactive(), chk_msg();
extern int edit_message(), edit_composition();
extern int edit_file(), update_composition();
extern int read_composition(), match_contype();
extern int mailcap_compose(), is_fromline();
extern int is_blank(), is_header();
extern int is_mime_header(), long_confirm();
extern unsigned cmodes(), count_messages();
extern void free(), free_forms(), emsg(), emsgl();
extern void typeout(), alldisplay(), first_display();
extern void show_buffer(), insert(), free_messages();
extern void free_composition(), rm_buffer(), set_sys_tags();
extern void make_composition_multipart(), copy_message_text();
extern void update_message_from_text(), check_mime_headers();
extern FORM *exec_comp_key();
extern WINDOW *init_windows();
extern MAILBUF *add_buffer();
extern MESSAGE *get_body_parts(), *make_body_part();
extern MESSAGE *copy_message_list(), *copy_one_message();
extern MESSAGE *composition_message(), *null_msg();
extern COMPOSITION *init_body_part_composition();
extern MAILCAP *find_mailcap();
extern TEXTLINE *insert_text(), *append_text(), *copy_text();
extern TEXTLINE *replace_text(), *find_hdr_line();
extern CLIST *fn_complete();

/* Local function declarations */

static char *find_boundary();
static int test_boundary();
static void set_description();
static void append_body_part();
static MAILBUF *build_compose_buffer();
static COMPOSITION *rebuild_composition();

/****************************************************************************/
/* Import the system error number */

extern int errno;

/****************************************************************************/
/* Import the window, commands, and user quit flag from commands.c */

extern WINDOW *cwin;
extern COMMAND *last_command;
extern COMMAND *this_command;
extern int user_quit;

/****************************************************************************/
/* We store the current headers here for ease of access */

static COMPOSITION *composition = NULL;

/****************************************************************************/
int compose_mail(comp)
COMPOSITION *comp;
{
      /*
       * Compose a multipart message.  This routine sets up an af
       * major mode, where each line displays a single body part.
       * Commands allow you to add, edit, and delete body parts.
       */

      unsigned old_modes;
      COMMAND *old_last, *old_this;
      COMPOSITION *old_composition;
      MESSAGE *old_message;
      MAILBUF *cbuf;
      WINDOW *win, *old_cwin;
      FORM *status = NULL;

      /* Take a backup of the original message text */

      old_message = copy_one_message(comp->message);

      /* Build the compose buffer from the composition */

      if ((cbuf = build_compose_buffer(comp, FALSE)) == NULL) {
            /* This shouldn't ever happen */

            return(FALSE);
      }

      /* If we weren't interactive before, we are now */

      interactive();

      /* Save the current commands and buffer modes */

      old_last = last_command;
      old_this = this_command;
      old_modes = cmodes(0);

      /* Save the composition and update it */

      old_composition = composition;
      composition = comp;

      /* We are now using compose mode */

      (void) cmodes(M_COMPOSE);

      /* Display the buffer in a new window */

      win = init_windows(1);
      show_buffer(win, cbuf);

      /* And make that window current */

      old_cwin = cwin;
      cwin = win;
      first_display(cwin);

      /* Now process user input if required */

      do {
            /* Execute the command clean up */

            status = exec_comp_key();
            free_forms(status);
      } while (status != NULL);

      /* Update the composition from the buffer if required */

      if (!user_quit) {
            /* Keeping changes, rebuild the composition */

            comp = rebuild_composition(comp, cbuf, FALSE);

            /* And free the backup copy */

            free_messages(old_message);
      } else {
            /* Abandoning changes, just update the composition */

            update_composition(comp, old_message, TRUE);

            /* And reset the user quit flag */

            user_quit = FALSE;
      }

      /* Restore the old window setup */

      cwin = old_cwin;
      rm_buffer(win->buf);
      free(win);

      /* Restore the old composition */

      composition = old_composition;

      /* Restore the old commands and buffer modes */

      last_command = old_last;
      this_command = old_this;
      (void) cmodes(old_modes);

      /* Redraw any changed windows */

      if (cwin != NULL) {
            alldisplay(NULL);
      }

      /* All done OK */

      return(comp != NULL);
}
/****************************************************************************/
int attach_messages(comp, attachments, all_headers)
COMPOSITION *comp;
MESSAGE **attachments;
int all_headers;
{
      /*
       * Attach the messages to the composition.  One message can
       * be added as a message/rfc822 body part.  More than one
       * are added as a multipart/digest subpart.
       */

      char *ctype;
      MESSAGE **a, *last, *body_part;
      MAILBUF *cbuf, *ebuf;
      COMPOSITION *encap;
      
      /* Build the compose buffer from the composition */

      if ((cbuf = build_compose_buffer(comp, FALSE)) == NULL) {
            /* This shouldn't ever happen */

            return(FALSE);
      }

      /* The composition is message/rfc822 or multipart/digest */

      if (*(attachments + 1) == NULL) {
            /* The composition is message/rfc822 */

            ctype = vstrcat(MESSAGE_TYPE, "/", RFC822_SUBTYPE, NULL);
      } else {
            /* The composition is multipart/digest */

            ctype = vstrcat(MULTIPART_TYPE, "/", DIGEST_SUBTYPE, NULL);
      }           

      /* Create a new composition for the encapsulated message(s) */

      if ((encap = init_body_part_composition(ctype, NULL, TRUE,
                                     FALSE)) == NULL) {
            /* The user quit or an error happened */

            free(ctype);
            return(FALSE);
      }
      free(ctype);

      /* If we're attaching then just insert the attachment as the body */

      if (*(attachments + 1) == NULL) {
            /* Copy the text into the composition */

            copy_message_text(encap->message, *attachments, NULL,
                          NULL, NULL, all_headers, FALSE, FALSE,
                          all_headers, TRUE, 0);
      }

      /* Build the compose buffer from the encapsulating message */

      if ((ebuf = build_compose_buffer(encap, TRUE)) == NULL) {
            /* This shouldn't ever happen */

            return(FALSE);
      }

      /* Find the last message in the compose buffer */

      for (last = ebuf->messages; last->next != NULL
           && last->next->text != NULL; last = last->next) {
            /* NULL LOOP */
      }

      /* Now add any digest messages to the encapsulated composition */

      for (a = attachments; *(attachments + 1) != NULL
           && a != NULL && *a != NULL; a++) {
            /* Create a new message for the digest entry */

            body_part = composition_message();

            /* Add a blank line after the absense of headers */

            body_part->text = append_text(body_part->text, xstrdup("\n"));

            /* Copy the text of the message into the body part */

            copy_message_text(body_part, *a, NULL, NULL, NULL,
                          all_headers, FALSE, FALSE, all_headers,
                          TRUE, 0);

            /* Update the body part from the text */

            update_message_from_text(body_part);

            /* Put the body part in the right place */

            if (last->text == NULL) {
                  /* Insert the body part at start-of-buffer */

                  ebuf->messages = last->prev = body_part;
                  body_part->prev = NULL;
                  body_part->next = last;
                  last = body_part;
            } else {
                  /* Append the body part to the composition */

                  body_part->prev = last;
                  body_part->next = last->next;
                  last->next = body_part;
                  last = last->next;
            }

            /* And update the message count */

            ebuf->no_msgs++;
      }

      /* Update the encapsulated composition from the buffer */

      encap = rebuild_composition(encap, ebuf, TRUE);

      /* Find the last message in the compose buffer */

      for (last = cbuf->messages; last->next != NULL
           && last->next->text != NULL; last = last->next) {
            /* NULL LOOP */
      }

      /* Copy the encapsulated message(s) from the composition */

      body_part = copy_one_message(encap->message);

      /* Clean up the encapsulating composition */

      free_composition(encap);

      /* Append the message to the composition */

      body_part->prev = last;
      body_part->next = last->next;
      last->next = body_part;
      last = last->next;
      cbuf->no_msgs++;

      /* Update the composition from the buffer and return success */

      comp = rebuild_composition(comp, cbuf, FALSE);
      return(TRUE);
}
/****************************************************************************/
/*ARGSUSED*/
FORM *attach_file(seq, arg, forms)
KEYSEQ *seq;
ARGUMENT *arg;
FORM *forms;
{
      /* Attach a file to the composition */

      char *filnam, *ctype;
      COMPOSITION *comp;
      MESSAGE *body_part;

      /* Get the name of the file to send */

      if ((filnam = get_cstr(forms, "Attach file: ", fn_complete,
                         C_STRICT)) == NULL) {
            return(c_errored());
      }

      /* Expand escape chars in the file name */

      filnam = expand(filnam);

      /* Now see if we can derive a content-type from the file name */

      if ((ctype = content_type(filnam)) != NULL) {
            /* Make an allocated copy of the content-type */

            ctype = xstrdup(ctype);
      } else {
            /* Default the content-type to application/octet-stream */

            ctype = vstrcat(APPLICATION_TYPE, "/",
                        OCTET_STREAM_SUBTYPE, NULL);
      }

      /* Now create a composition from the file */

      if ((comp = init_body_part_composition(ctype, filnam, FALSE,
                                     TRUE)) == NULL) {
            /* The user quit or an error happened */

            free(ctype);
            return(c_errored());
      }

      /* Free the content-type */

      free(ctype);

      /* Now copy the composed message into the body part */

      body_part = copy_one_message(comp->message);

      /* This body part is treated as old and read */

      body_part->new = FALSE;
      body_part->read = TRUE;

      /* Update the body part's tags */

      set_sys_tags(body_part);

      /* Insert the body part and update the display */

      insert(cwin, body_part);
      cwin->buf->mod = TRUE;
      alldisplay(cwin->buf);

      /* Clean up the local storage and return success */

      free(filnam);
      free_composition(comp);
      return(c_t());
}
/****************************************************************************/
/*ARGSUSED*/
FORM *compose_part(seq, arg, forms)
KEYSEQ *seq;
ARGUMENT *arg;
FORM *forms;
{
      /* Compose a new body part and add it to the composition */

      char *ctype, *tfile;
      int fmt, typed;
      COMPOSITION *comp;
      MAILCAP *mcap;
      MESSAGE *body_part;

      /* Get the content-type from the forms if required */

      ctype = (forms != NULL) ? formtext(forms) : NULL;

      /* Now initialise the body part's headers */

      if ((comp = init_body_part_composition(ctype, NULL, FALSE,
                                     FALSE)) == NULL) {
            /* The user quit or an error happened */

            return(c_errored());
      }

      /* Extract the content-type from the composition */

      ctype = comp->message->contype;

      /* Now see if we can find a compose command for the type */

      if ((mcap = find_mailcap(ctype, NULL, MCAP_COMPOSE)) == NULL
          && (mcap = find_mailcap(ctype, NULL, MCAP_TYPED)) == NULL
          && !match_contype(TEXT_TYPE, ctype)) {
            /* No way to compose this body part */

            emsgl("No composition method for content type ",
                  typeonly(ctype), " found in mailcaps", NULL);
            return(c_errored());
      }

      /* Set up the mailcap format and typed composition flag */

      fmt = WF_BODY | WF_SHOW | WF_NOBLANK | WF_DECODE;
      typed = (mcap != NULL && mcap->typed != NULL);

      /* Get a temporary file */

      if ((tfile = tempnam(TFILEDIR, TFILEPFX)) == NULL) {
            emsgl("Can't create temporary file ", strerror(errno), NULL);
            return(c_errored());
      }

      /* Now create the composition */

      if (mcap == NULL && edit_file(tfile) < 0
          || mcap != NULL && mailcap_compose(mcap, tfile, fmt, typed)) {
            /* Error trying to create the file */

            (void) unlink(tfile);
            free(tfile);
            return(c_errored());
      }

      /* Read the composition back from the file */

      if (!read_composition(comp, tfile, typed)) {
            /* Clean up the temporary file */

            (void) unlink(tfile);
            free(tfile);

            /* Clean up and return failure */

            free_composition(comp);
            return(c_errored());
      }
      
      /* Clean up the temporary file */

      (void) unlink(tfile);
      free(tfile);

      /* Now copy the composed message into the body part */

      body_part = copy_one_message(comp->message);

      /* This body part is treated as old and read */

      body_part->new = FALSE;
      body_part->read = TRUE;

      /* Update the body part's tags */

      set_sys_tags(body_part);

      /* Insert the body part and update the display */

      insert(cwin, body_part);
      cwin->buf->mod = TRUE;
      alldisplay(cwin->buf);

      /* Clean up the local storage */

      free_composition(comp);

      /* And return success */

      return(c_t());
}
/****************************************************************************/
/*ARGSUSED*/
FORM *edit_part(seq, arg, forms)
KEYSEQ *seq;
ARGUMENT *arg;
FORM *forms;
{
      /* Edit a body part in the composition */

      /* Check there is a body part to edit */

      if (!chk_msg(cwin, TRUE)) {
            return(c_errored());
      }

      /* Now edit the body part and update it */

      return((edit_message(cwin, cwin->point, TRUE, FALSE))
             ? c_t() : c_errored());
}
/****************************************************************************/
/*ARGSUSED*/
FORM *edit_description(seq, arg, forms)
KEYSEQ *seq;
ARGUMENT *arg;
FORM *forms;
{
      /* Edit the content-description of a body part */

      char *cdesc;

      /* Check there is a body part to edit */

      if (!chk_msg(cwin, TRUE)) {
            return(c_errored());
      }

      /* Get the content-description */

      if ((cdesc = get_dstr(forms, "Content-Description: ",
                        cwin->point->description)) == NULL) {
            return(c_errored());
      }

      /* Set the Content-Description header */

      set_description(cwin->point, cdesc);

      /* Update the display and return success */

      alldisplay(cwin->buf);
      return(c_t());
}
/****************************************************************************/
/*ARGSUSED*/
FORM *edit_headers(seq, arg, forms)
KEYSEQ *seq;
ARGUMENT *arg;
FORM *forms;
{
      /* Edit the headers of the message we're composing */

      edit_composition(composition, V_TRUE, FALSE);
      return(c_t());
}
/****************************************************************************/
/*ARGSUSED*/
FORM *save_comp_and_exit(seq, arg, forms)
KEYSEQ *seq;
ARGUMENT *arg;
FORM *forms;
{
      /* Exit from compose mode, keeping changes intact */

      return(NULL);
}
/****************************************************************************/
/*ARGSUSED*/
FORM *abandon_comp_and_exit(seq, arg, forms)
KEYSEQ *seq;
ARGUMENT *arg;
FORM *forms;
{
      /* Exit from compose mode, discarding changes */

      if (long_confirm("Abandon all changes to message? ", TRUE)) {
            /* User confirmed the abandon */

            user_quit = TRUE;
            return(NULL);
      }

      /* User didn't confirm */

      return(c_errored());
}
/****************************************************************************/
static MAILBUF *build_compose_buffer(comp, digest)
COMPOSITION *comp;
int digest;
{
      /* Build a compose buffer from the headers and temporary file */

      char *boundary;
      MAILBUF *cbuf;
      MESSAGE *body_parts, *b;

      /* Extract any body parts from the message */

      if ((body_parts = get_body_parts(comp->message)) == NULL) {
            /* The body part is the message text, if any */

            body_parts = (comp->multipart) ? NULL
                  : make_body_part(comp->message);
      }

      /* Update the encoding and status of the body parts */

      for (b = body_parts; b != NULL; b = b->next) {
            /* This body part is treated as old and read */

            b->new = FALSE;
            b->read = TRUE;

            /* Update the body part's tags */

            set_sys_tags(b);
      }

      /* Add a terminating null message to the list */

      body_parts = null_msg(body_parts);

      /* Now create a new buffer for the body parts */

      cbuf = add_buffer(NULL, COMPOSEBUF, NULL, NULL, M_COMPOSE);

      /* Set the buffer to contain the body parts */

      free_messages(cbuf->messages);
      cbuf->messages = cbuf->point = body_parts;
      cbuf->no_msgs = count_messages(cbuf->messages, TRUE);

      /* Strip the body from the original composition */

      replace_text(comp->message->text, comp->body, NULL);
      comp->body = NULL;

      /* Fix the MIME headers of the original composition */

      boundary = find_boundary(cbuf->messages);
      make_composition_multipart(comp, boundary, digest);
      (void) update_composition(comp, NULL, FALSE);

      /* And return the compose buffer */

      return(cbuf);
}
/****************************************************************************/
static COMPOSITION *rebuild_composition(comp, cbuf, digest)
COMPOSITION *comp;
MAILBUF *cbuf;
int digest;
{
      /* Update the composition from the compose buffer */

      char *boundary;
      MESSAGE *message, *body_part;
      TEXTLINE *new_text, *t;

      /* Clear the user quit flag; we don't need it */

      user_quit = FALSE;

      /* Check there's something left to read */

      if (!count_messages(cbuf->messages, TRUE)) {
            /* We appear to have lost the message body */

            emsg("No message body; aborting");
            free_composition(comp);
            return(NULL);
      }

      /* We can treat this as recreating the composition */

      comp->updated = FALSE;

      /* Now handle a single body part */

      if (count_messages(cbuf->messages, TRUE) < 2) {
            /* Copy the message */

            message = copy_one_message(cbuf->messages);

            /* Merge the composition headers into the message */

            new_text = message->text;
            t = comp->message->text;

            /* Loop over the headers, adding as required */

            while (t != NULL && !is_blank(t->line)) {
                  /* Add this line if it's not a MIME header */

                  if (!is_mime_header(t->line)) {
                        new_text = insert_text(new_text,
                                           message->text,
                                           xstrdup(t->line));
                  }

                  /* And move on the the next line */

                  t = t->next;
            }
            message->text = new_text;

            /* Note that the composition is encoded */

            comp->encoded = (message->encoding != NULL) 
                  ? xstrdup(message->encoding) : NULL;

            /* Reset the MIME headers of textual messages */

            check_mime_headers(comp, FALSE);

            /* Update and return the composition */

            (void) update_composition(comp, message, TRUE);
            typeout(NULL);
            return(comp);
      }

      /* Find the boundary for the multipart message */

      boundary = find_boundary(cbuf->messages);

      /* Now create the body of the composition */

      for (body_part = cbuf->messages;
           body_part->text != NULL; body_part = body_part->next) {
            /* Append this body part to the message text */

            append_body_part(comp->message, body_part, boundary);
      }

      /* Now write the final delimiter line */

      append_body_part(comp->message, NULL, boundary);

      /* The composition is multipart */

      comp->multipart = TRUE;

      /* Fix the headers of the composition and update it */

      make_composition_multipart(comp, boundary, digest);
      (void) update_composition(comp, NULL, FALSE);

      /* Return the composition */

      return(comp);
}
/****************************************************************************/
static void append_body_part(message, body_part, boundary)
MESSAGE *message, *body_part;
char *boundary;
{
      /*
       * Append a body part to the message.  If boundary is non-null
       * then we'll prefix the body part with a delimiter line.  If
       * body_part is null, and boundary non-null, then we're writing
       * the terminating delimiter line.
       */

      char *line;
      TEXTLINE *t;

      /* Append any boundary to the text */

      if (boundary != NULL) {
            /* Build the delimiter line */

            line = vstrcat(BOUNDARY_DELIM, boundary,
                         (body_part == NULL) ? BOUNDARY_DELIM : "",
                         "\n", NULL);

            /* And append a blank line and boundary to the text */

            append_text(message->text, xstrdup("\n"));
            append_text(message->text, line);
      }

      /* Skip any from lines in the body part's text */

      t = (body_part != NULL) ? body_part->text : NULL;
      while (t != NULL && is_fromline(t->line)) {
            t = t->next;
      }

      /* Now write the text of the body part */

      if (t != NULL) {
            replace_text(message->text, NULL, copy_text(t));
      }

      /* All done */

      return;
}
/****************************************************************************/
static char *find_boundary(messages)
MESSAGE *messages;
{
      /* Return a unique boundary within the text */

      static char *boundary = NULL;
      unsigned level = 0;

      /* Free any old boundary text */

      if (boundary != NULL) {
            free(boundary);
            boundary = NULL;
      }
      
      /* Loop until we find a valid boundary */

      while (boundary == NULL) {
            /* Create the test boundary text */

            boundary = vstrcat(BOUNDARY_TEXT, xtos(level++), NULL);

            /* Check if the boundary is unique */

            if (!test_boundary(messages, boundary)) {
                  /* It isn't unique */

                  free(boundary);
                  boundary = NULL;
            }
      }
      
      /* Return the boundary */

      return(boundary);
}
/****************************************************************************/
static int test_boundary(messages, boundary)
MESSAGE *messages;
char *boundary;
{
      /* Check if boundary is already present in the messages' text */

      char *line;
      MESSAGE *m;
      TEXTLINE *t;
      
      /* Set up the boundary line to search for */

      line = vstrcat(BOUNDARY_DELIM, boundary, NULL);

      /* Search the messages' text for a boundary */

      for (m = messages; boundary != NULL && m != NULL; m = m->next) {
            for (t = m->text; boundary != NULL && t != NULL; t = t->next) {
                  /* Does this line match the boundary? */

                  if (!strncmp(t->line, line, strlen(line))) {
                        /* Not a valid boundary */

                        free(line);
                        return(FALSE);
                  }
            }
      }

      /* This boundary is valid */

      free(line);
      return(TRUE);
}
/****************************************************************************/
static void set_description(message, cdesc)
MESSAGE *message;
char *cdesc;
{
      /* Update the content-description of a header */

      char *line;
      TEXTLINE *hdr, *t;

      /* Set the header value for the message */

      if (message->description != NULL) {
            free(message->description);
      }
      message->description = xstrdup(cdesc);

      /* Set up the encoded header line we're adding */

      line = vstrcat(CONTENT_DESC, " ",
                   encode_header(CONTENT_DESC, cdesc, WR_FOLD),
                   "\n", NULL);

      /* Find the header in the message's text */

      if ((hdr = find_hdr_line(message, CONTENT_DESC)) != NULL) {
            /* Set this line to the content-description */

            free(hdr->line);
            hdr->line = line;
            return;
      }

      /* No such header, we need to append it */

      for (t = message->text; t != NULL; t = t->next) {
            /* Is this the end of the headers? */

            if (is_blank(t->line)) {
                  /* Insert the header line here */

                  message->text = insert_text(message->text, t, line);
                  break;
            }
      }

      /* That's all folks */

      return;
}
/****************************************************************************/

Generated by  Doxygen 1.6.0   Back to index