summaryrefslogblamecommitdiffstats
path: root/rfc3676.c
blob: 679a92b7d4aa2dd5e0f775c0fb82168d60397ffe (plain) (tree)
1
2
3
4
5
6
7
8
9
   
        
                                 

           

                                                        
                                                      
                                                     
  
             



                                                                                
  



                                                                                
  

                                                                               
   
 
   
                                                   



                                 
                   

                    
                   
                     
                       
                      
                     
                    
                    
 

                     


                                                             
                  


                
             
  
 




                                                  
                                            

                 
                       
 
                          







                

                                                           
                            
                                      


                                                                    
          
                 
   
                                            
 
                                                              
                                         
                                                                         
                                                       

             
                                                                                         
                               

 

                                                              

                            
                                      


                                                        
                
              
   
                                                         
 
                                    
                 
 
                          
                 
 
                            
                 

                                         
                                                                         
                                             
                 
 
              

 


                                     
                                       


                                                              
                                                                       
 
                 
 
                    

                                                                       
                                                                         

                                                                           
     
           
     

        

                                         

     
                              
   


                             

                 
                           
 
                          

            
                               

 

                                      

                                        
   
                                                                   
 
                     
   
                            
                   
   
                  
 
 

                                                                         

                            
                              



                                                                              
                                                   
 
                                                                                


                                                                            
                                                       




                                                                       
                                                                    

                                                                         
                                             
                                                                            
                                
                                  
            


                                                                 


               

                                                 




                                                  
   
                                                                      
                                                                 
 
                             
                 
            
 
                               
   
                                                


                               


           
                                 
                                      
 
                                                                            
                                        
 
                                                  
   

                                                                              

                                   
                   
     
                                                       

                    
     



                                                                     
                         



                                                                              

                                                                        
     

                                                                        
                                                                

                                                                             
                                          

                                 

                      
                
     

                              
                                                                        
                                  
                                      

                             
            
   

           
                          

 

                                               



                                        
   

                                                                           
 
                                                       
                    

                            
 

                  

 
   
                                                                                              
                   
   
                                                        
 
                   
                              
                     
                
                                 
 
                                                    
                                                   
        
   
                                      
             
                     

   
                                                                  
 
                                                                                     
   
                                             
                                                    
 
                                                                  
                                                                     
                            
                             
 
                       
                        





                                                                       
                                                                     


                                                          
                                                                                   
 

                                                                      
                                                                  
     
                                                            

                                                               


               

                                                                              
                              
 
                                                                     
   
 
                         
 
             
           

 
   

                                                                
                                        













                                                                          
                                                                

                                                 
  



                                                             
  
                                                      
                                                     
   
                                                                   
 


                      
 
                                          
 
                                               
             
              
 

                                                      
              
              
 
                                                                                        
   






                               
        
     
                                                               
                           
                         
     
                        
   
             

                            
                                                     
 
                                                    


              
                                                                                           
   
                                



                                       

                                                     



                            
                             





                                                                        
                                                                                

                                                                           


                                              
                                           

           

                                                  







                                                             
                                           

           

                                                 









































                                                                                
 
/**
 * @file
 * RFC3676 Format Flowed routines
 *
 * @authors
 * Copyright (C) 2005 Andreas Krennmair <ak@synflood.at>
 * Copyright (C) 2005 Peter J. Holzer <hjp@hjp.net>
 * Copyright (C) 2005-2009 Rocco Rutte <pdmef@gmx.net>
 * Copyright (C) 2010 Michael R. Elkins <me@mutt.org>
 *
 * @copyright
 * 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, see <http://www.gnu.org/licenses/>.
 */

/**
 * @page neo_rfc3676 RFC3676 Format Flowed routines
 *
 * RFC3676 Format Flowed routines
 */

#include "config.h"
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include "mutt/lib.h"
#include "config/lib.h"
#include "email/lib.h"
#include "core/lib.h"
#include "gui/lib.h"
#include "rfc3676.h"

#define FLOWED_MAX 72

/**
 * struct FlowedState - State of a Format-Flowed line of text
 */
struct FlowedState
{
  size_t width;
  size_t spaces;
  bool delsp;
};

/**
 * get_quote_level - Get the quote level of a line
 * @param line Text to examine
 * @retval num Quote level
 */
static int get_quote_level(const char *line)
{
  int quoted = 0;
  const char *p = line;

  while (p && (*p == '>'))
  {
    quoted++;
    p++;
  }

  return quoted;
}

/**
 * space_quotes - Should we add spaces between quote levels
 * @param state State to use
 * @retval true Spaces should be added
 *
 * Determines whether to add spacing between/after each quote level:
 * `   >>>foo`
 * becomes
 * `   > > > foo`
 */
static int space_quotes(struct State *state)
{
  /* Allow quote spacing in the pager even for `$text_flowed`,
   * but obviously not when replying.  */
  const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
  if (c_text_flowed && (state->flags & STATE_REPLYING))
    return 0;

  const bool c_reflow_space_quotes = cs_subset_bool(NeoMutt->sub, "reflow_space_quotes");
  return c_reflow_space_quotes;
}

/**
 * add_quote_suffix - Should we add a trailing space to quotes
 * @param state State to use
 * @param ql    Quote level
 * @retval true Spaces should be added
 *
 * Determines whether to add a trailing space to quotes:
 * `   >>> foo`
 * as opposed to
 * `   >>>foo`
 */
static bool add_quote_suffix(struct State *state, int ql)
{
  if (state->flags & STATE_REPLYING)
    return false;

  if (space_quotes(state))
    return false;

  if (!ql && !state->prefix)
    return false;

  /* The prefix will add its own space */
  const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
  if (!c_text_flowed && !ql && state->prefix)
    return false;

  return true;
}

/**
 * print_indent - Print indented text
 * @param ql         Quote level
 * @param state      State to work with
 * @param add_suffix If true, write a trailing space character
 * @retval num Number of characters written
 */
static size_t print_indent(int ql, struct State *state, int add_suffix)
{
  size_t wid = 0;

  if (state->prefix)
  {
    /* use given prefix only for format=fixed replies to format=flowed,
     * for format=flowed replies to format=flowed, use '>' indentation */
    const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
    if (c_text_flowed)
    {
      ql++;
    }
    else
    {
      state_puts(state, state->prefix);
      wid = mutt_strwidth(state->prefix);
    }
  }
  for (int i = 0; i < ql; i++)
  {
    state_putc(state, '>');
    if (space_quotes(state))
      state_putc(state, ' ');
  }
  if (add_suffix)
    state_putc(state, ' ');

  if (space_quotes(state))
    ql *= 2;

  return ql + add_suffix + wid;
}

/**
 * flush_par - Write out the paragraph
 * @param state State to work with
 * @param fst   State of the flowed text
 */
static void flush_par(struct State *state, struct FlowedState *fst)
{
  if (fst->width > 0)
  {
    state_putc(state, '\n');
    fst->width = 0;
  }
  fst->spaces = 0;
}

/**
 * quote_width - Calculate the paragraph width based upon the quote level
 * @param state State to use
 * @param ql    Quote level
 * @retval num Paragraph width
 *
 * The start of a quoted line will be ">>> ", so we need to subtract the space
 * required for the prefix from the terminal width.
 */
static int quote_width(struct State *state, int ql)
{
  const int screen_width = (state->flags & STATE_DISPLAY) ? state->wraplen : 80;
  const short c_reflow_wrap = cs_subset_number(NeoMutt->sub, "reflow_wrap");
  int width = mutt_window_wrap_cols(screen_width, c_reflow_wrap);
  const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
  if (c_text_flowed && (state->flags & STATE_REPLYING))
  {
    /* When replying, force a wrap at FLOWED_MAX to comply with RFC3676
     * guidelines */
    if (width > FLOWED_MAX)
      width = FLOWED_MAX;
    ql++; /* When replying, we will add an additional quote level */
  }
  /* adjust the paragraph width subtracting the number of prefix chars */
  width -= space_quotes(state) ? ql * 2 : ql;
  /* When displaying (not replying), there may be a space between the prefix
   * string and the paragraph */
  if (add_quote_suffix(state, ql))
    width--;
  /* failsafe for really long quotes */
  if (width <= 0)
    width = FLOWED_MAX; /* arbitrary, since the line will wrap */
  return width;
}

/**
 * print_flowed_line - Print a format-flowed line
 * @param line  Text to print
 * @param state State to work with
 * @param ql    Quote level
 * @param fst   State of the flowed text
 * @param term  If true, terminate with a new line
 */
static void print_flowed_line(char *line, struct State *state, int ql,
                              struct FlowedState *fst, bool term)
{
  size_t width, w, words = 0;
  char *p = NULL;
  char last;

  if (!line || (*line == '\0'))
  {
    /* flush current paragraph (if any) first */
    flush_par(state, fst);
    print_indent(ql, state, 0);
    state_putc(state, '\n');
    return;
  }

  width = quote_width(state, ql);
  last = line[mutt_str_len(line) - 1];

  mutt_debug(LL_DEBUG5, "f=f: line [%s], width = %ld, spaces = %lu\n", line,
             (long) width, fst->spaces);

  for (words = 0; (p = mutt_str_sep(&line, " "));)
  {
    mutt_debug(LL_DEBUG5, "f=f: word [%s], width: %lu, remaining = [%s]\n", p,
               fst->width, line);

    /* remember number of spaces */
    if (*p == '\0')
    {
      mutt_debug(LL_DEBUG3, "f=f: additional space\n");
      fst->spaces++;
      continue;
    }
    /* there's exactly one space prior to every but the first word */
    if (words)
      fst->spaces++;

    w = mutt_strwidth(p);
    /* see if we need to break the line but make sure the first word is put on
     * the line regardless; if for DelSp=yes only one trailing space is used,
     * we probably have a long word that we should break within (we leave that
     * up to the pager or user) */
    if (!(!fst->spaces && fst->delsp && (last != ' ')) && (w < width) &&
        (w + fst->width + fst->spaces > width))
    {
      mutt_debug(LL_DEBUG3, "f=f: break line at %lu, %lu spaces left\n",
                 fst->width, fst->spaces);
      /* only honor trailing spaces for format=flowed replies */
      const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
      if (c_text_flowed)
        for (; fst->spaces; fst->spaces--)
          state_putc(state, ' ');
      state_putc(state, '\n');
      fst->width = 0;
      fst->spaces = 0;
      words = 0;
    }

    if (!words && !fst->width)
      fst->width = print_indent(ql, state, add_quote_suffix(state, ql));
    fst->width += w + fst->spaces;
    for (; fst->spaces; fst->spaces--)
      state_putc(state, ' ');
    state_puts(state, p);
    words++;
  }

  if (term)
    flush_par(state, fst);
}

/**
 * print_fixed_line - Print a fixed format line
 * @param line  Text to print
 * @param state State to work with
 * @param ql    Quote level
 * @param fst   State of the flowed text
 */
static void print_fixed_line(const char *line, struct State *state, int ql,
                             struct FlowedState *fst)
{
  print_indent(ql, state, add_quote_suffix(state, ql));
  if (line && *line)
    state_puts(state, line);
  state_putc(state, '\n');

  fst->width = 0;
  fst->spaces = 0;
}

/**
 * rfc3676_handler - Handler for format=flowed - Implements ::handler_t - @ingroup handler_api
 * @retval 0 Always
 */
int rfc3676_handler(struct Body *a, struct State *state)
{
  char *buf = NULL;
  unsigned int quotelevel = 0;
  bool delsp = false;
  size_t sz = 0;
  struct FlowedState fst = { 0 };

  /* respect DelSp of RFC3676 only with f=f parts */
  char *t = mutt_param_get(&a->parameter, "delsp");
  if (t)
  {
    delsp = mutt_istr_equal(t, "yes");
    t = NULL;
    fst.delsp = true;
  }

  mutt_debug(LL_DEBUG3, "f=f: DelSp: %s\n", delsp ? "yes" : "no");

  while ((buf = mutt_file_read_line(buf, &sz, state->fp_in, NULL, MUTT_RL_NO_FLAGS)))
  {
    const size_t buf_len = mutt_str_len(buf);
    const unsigned int newql = get_quote_level(buf);

    /* end flowed paragraph (if we're within one) if quoting level
     * changes (should not but can happen, see RFC3676, sec. 4.5.) */
    if (newql != quotelevel)
      flush_par(state, &fst);

    quotelevel = newql;
    int buf_off = newql;

    /* respect sender's space-stuffing by removing one leading space */
    if (buf[buf_off] == ' ')
      buf_off++;

    /* test for signature separator */
    const unsigned int sigsep = mutt_str_equal(buf + buf_off, "-- ");

    /* a fixed line either has no trailing space or is the
     * signature separator */
    const bool fixed = (buf_len == buf_off) || (buf[buf_len - 1] != ' ') || sigsep;

    /* print fixed-and-standalone, fixed-and-empty and sigsep lines as
     * fixed lines */
    if ((fixed && ((fst.width == 0) || (buf_len == 0))) || sigsep)
    {
      /* if we're within a flowed paragraph, terminate it */
      flush_par(state, &fst);
      print_fixed_line(buf + buf_off, state, quotelevel, &fst);
      continue;
    }

    /* for DelSp=yes, we need to strip one SP prior to CRLF on flowed lines */
    if (delsp && !fixed)
      buf[buf_len - 1] = '\0';

    print_flowed_line(buf + buf_off, state, quotelevel, &fst, fixed);
  }

  flush_par(state, &fst);

  FREE(&buf);
  return 0;
}

/**
 * mutt_rfc3676_is_format_flowed - Is the Email "format-flowed"?
 * @param b Email Body to examine
 * @retval true Email is "format-flowed"
 */
bool mutt_rfc3676_is_format_flowed(struct Body *b)
{
  if (b && (b->type == TYPE_TEXT) && mutt_istr_equal("plain", b->subtype))
  {
    const char *format = mutt_param_get(&b->parameter, "format");
    if (mutt_istr_equal("flowed", format))
      return true;
  }

  return false;
}

/**
 * rfc3676_space_stuff - Perform required RFC3676 space stuffing
 * @param filename Attachment file
 * @param unstuff  If true, remove space stuffing
 *
 * Space stuffing means that we have to add leading spaces to
 * certain lines:
 *   - lines starting with a space
 *   - lines starting with 'From '
 *
 * Care is taken to preserve the e->body->filename, as
 * mutt -i -E can directly edit a passed in filename.
 */
static void rfc3676_space_stuff(const char *filename, bool unstuff)
{
  FILE *fp_out = NULL;
  char *buf = NULL;
  size_t blen = 0;

  struct Buffer *tmpfile = buf_pool_get();

  FILE *fp_in = mutt_file_fopen(filename, "r");
  if (!fp_in)
    goto bail;

  buf_mktemp(tmpfile);
  fp_out = mutt_file_fopen(buf_string(tmpfile), "w+");
  if (!fp_out)
    goto bail;

  while ((buf = mutt_file_read_line(buf, &blen, fp_in, NULL, MUTT_RL_NO_FLAGS)) != NULL)
  {
    if (unstuff)
    {
      if (buf[0] == ' ')
        fputs(buf + 1, fp_out);
      else
        fputs(buf, fp_out);
    }
    else
    {
      if ((buf[0] == ' ') || mutt_str_startswith(buf, "From "))
        fputc(' ', fp_out);
      fputs(buf, fp_out);
    }
    fputc('\n', fp_out);
  }
  FREE(&buf);
  mutt_file_fclose(&fp_in);
  mutt_file_fclose(&fp_out);
  mutt_file_set_mtime(filename, buf_string(tmpfile));

  fp_in = mutt_file_fopen(buf_string(tmpfile), "r");
  if (!fp_in)
    goto bail;

  if ((truncate(filename, 0) == -1) || ((fp_out = mutt_file_fopen(filename, "a")) == NULL))
  {
    mutt_perror("%s", filename);
    goto bail;
  }

  mutt_file_copy_stream(fp_in, fp_out);
  mutt_file_set_mtime(buf_string(tmpfile), filename);
  unlink(buf_string(tmpfile));

bail:
  mutt_file_fclose(&fp_in);
  mutt_file_fclose(&fp_out);
  buf_pool_release(&tmpfile);
}

/**
 * mutt_rfc3676_space_stuff - Perform RFC3676 space stuffing on an Email
 * @param e Email
 *
 * @note We don't check the option `$text_flowed` because we want to stuff based
 *       the actual content type.  The option only decides whether to *set*
 *       format=flowed on new messages.
 */
void mutt_rfc3676_space_stuff(struct Email *e)
{
  if (!e || !e->body || !e->body->filename)
    return;

  if (mutt_rfc3676_is_format_flowed(e->body))
    rfc3676_space_stuff(e->body->filename, false);
}

/**
 * mutt_rfc3676_space_unstuff - Remove RFC3676 space stuffing
 * @param e Email
 */
void mutt_rfc3676_space_unstuff(struct Email *e)
{
  if (!e || !e->body || !e->body->filename)
    return;

  if (mutt_rfc3676_is_format_flowed(e->body))
    rfc3676_space_stuff(e->body->filename, true);
}

/**
 * mutt_rfc3676_space_unstuff_attachment - Unstuff attachments
 * @param b        Email Body (OPTIONAL)
 * @param filename Attachment file
 *
 * This routine is used when saving/piping/viewing rfc3676 attachments.
 *
 * If b is provided, the function will verify that the Email is format-flowed.
 * The filename will be unstuffed, not b->filename or b->fp.
 */
void mutt_rfc3676_space_unstuff_attachment(struct Body *b, const char *filename)
{
  if (!filename)
    return;

  if (b && !mutt_rfc3676_is_format_flowed(b))
    return;

  rfc3676_space_stuff(filename, true);
}

/**
 * mutt_rfc3676_space_stuff_attachment - Stuff attachments
 * @param b        Email Body (OPTIONAL)
 * @param filename Attachment file
 *
 * This routine is used when filtering rfc3676 attachments.
 *
 * If b is provided, the function will verify that the Email is format-flowed.
 * The filename will be unstuffed, not b->filename or b->fp.
 */
void mutt_rfc3676_space_stuff_attachment(struct Body *b, const char *filename)
{
  if (!filename)
    return;

  if (b && !mutt_rfc3676_is_format_flowed(b))
    return;

  rfc3676_space_stuff(filename, false);
}