NeoMutt  2018-07-16 +2481-68dcde
Teaching an old dog new tricks
DOXYGEN
rfc3676.c
Go to the documentation of this file.
1 
32 #include "config.h"
33 #include <limits.h>
34 #include <stdbool.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include "mutt/mutt.h"
39 #include "email/lib.h"
40 #include "curs_lib.h"
41 #include "globals.h"
42 #include "mutt_window.h"
43 #include "muttlib.h"
44 #include "state.h"
45 
46 /* These Config Variables are only used in rfc3676.c */
48 short C_ReflowWrap;
49 
50 #define FLOWED_MAX 72
51 
56 {
57  size_t width;
58  size_t spaces;
59  bool delsp;
60 };
61 
67 static int get_quote_level(const char *line)
68 {
69  int quoted = 0;
70  const char *p = line;
71 
72  while (p && (*p == '>'))
73  {
74  quoted++;
75  p++;
76  }
77 
78  return quoted;
79 }
80 
91 static int space_quotes(struct State *s)
92 {
93  /* Allow quote spacing in the pager even for C_TextFlowed,
94  * but obviously not when replying. */
95  if (C_TextFlowed && (s->flags & MUTT_REPLYING))
96  return 0;
97 
98  return C_ReflowSpaceQuotes;
99 }
100 
112 static bool add_quote_suffix(struct State *s, int ql)
113 {
114  if (s->flags & MUTT_REPLYING)
115  return false;
116 
117  if (space_quotes(s))
118  return false;
119 
120  if (!ql && !s->prefix)
121  return false;
122 
123  /* The prefix will add its own space */
124  if (!C_TextFlowed && !ql && s->prefix)
125  return false;
126 
127  return true;
128 }
129 
137 static size_t print_indent(int ql, struct State *s, int add_suffix)
138 {
139  size_t wid = 0;
140 
141  if (s->prefix)
142  {
143  /* use given prefix only for format=fixed replies to format=flowed,
144  * for format=flowed replies to format=flowed, use '>' indentation */
145  if (C_TextFlowed)
146  ql++;
147  else
148  {
149  state_puts(s->prefix, s);
150  wid = mutt_strwidth(s->prefix);
151  }
152  }
153  for (int i = 0; i < ql; i++)
154  {
155  state_putc('>', s);
156  if (space_quotes(s))
157  state_putc(' ', s);
158  }
159  if (add_suffix)
160  state_putc(' ', s);
161 
162  if (space_quotes(s))
163  ql *= 2;
164 
165  return ql + add_suffix + wid;
166 }
167 
173 static void flush_par(struct State *s, struct FlowedState *fst)
174 {
175  if (fst->width > 0)
176  {
177  state_putc('\n', s);
178  fst->width = 0;
179  }
180  fst->spaces = 0;
181 }
182 
192 static int quote_width(struct State *s, int ql)
193 {
195  if (C_TextFlowed && (s->flags & MUTT_REPLYING))
196  {
197  /* When replying, force a wrap at FLOWED_MAX to comply with RFC3676
198  * guidelines */
199  if (width > FLOWED_MAX)
200  width = FLOWED_MAX;
201  ql++; /* When replying, we will add an additional quote level */
202  }
203  /* adjust the paragraph width subtracting the number of prefix chars */
204  width -= space_quotes(s) ? ql * 2 : ql;
205  /* When displaying (not replying), there may be a space between the prefix
206  * string and the paragraph */
207  if (add_quote_suffix(s, ql))
208  width--;
209  /* failsafe for really long quotes */
210  if (width <= 0)
211  width = FLOWED_MAX; /* arbitrary, since the line will wrap */
212  return width;
213 }
214 
223 static void print_flowed_line(char *line, struct State *s, int ql,
224  struct FlowedState *fst, bool term)
225 {
226  size_t width, w, words = 0;
227  char *p = NULL;
228  char last;
229 
230  if (!line || !*line)
231  {
232  /* flush current paragraph (if any) first */
233  flush_par(s, fst);
234  print_indent(ql, s, 0);
235  state_putc('\n', s);
236  return;
237  }
238 
239  width = quote_width(s, ql);
240  last = line[mutt_str_strlen(line) - 1];
241 
242  mutt_debug(LL_DEBUG5, "f=f: line [%s], width = %ld, spaces = %lu\n", line,
243  (long) width, fst->spaces);
244 
245  for (words = 0; (p = strsep(&line, " "));)
246  {
247  mutt_debug(LL_DEBUG5, "f=f: word [%s], width: %lu, remaining = [%s]\n", p,
248  fst->width, line);
249 
250  /* remember number of spaces */
251  if (!*p)
252  {
253  mutt_debug(LL_DEBUG3, "f=f: additional space\n");
254  fst->spaces++;
255  continue;
256  }
257  /* there's exactly one space prior to every but the first word */
258  if (words)
259  fst->spaces++;
260 
261  w = mutt_strwidth(p);
262  /* see if we need to break the line but make sure the first word is put on
263  * the line regardless; if for DelSp=yes only one trailing space is used,
264  * we probably have a long word that we should break within (we leave that
265  * up to the pager or user) */
266  if (!(!fst->spaces && fst->delsp && (last != ' ')) && (w < width) &&
267  (w + fst->width + fst->spaces > width))
268  {
269  mutt_debug(LL_DEBUG3, "f=f: break line at %lu, %lu spaces left\n",
270  fst->width, fst->spaces);
271  /* only honor trailing spaces for format=flowed replies */
272  if (C_TextFlowed)
273  for (; fst->spaces; fst->spaces--)
274  state_putc(' ', s);
275  state_putc('\n', s);
276  fst->width = 0;
277  fst->spaces = 0;
278  words = 0;
279  }
280 
281  if (!words && !fst->width)
282  fst->width = print_indent(ql, s, add_quote_suffix(s, ql));
283  fst->width += w + fst->spaces;
284  for (; fst->spaces; fst->spaces--)
285  state_putc(' ', s);
286  state_puts(p, s);
287  words++;
288  }
289 
290  if (term)
291  flush_par(s, fst);
292 }
293 
301 static void print_fixed_line(const char *line, struct State *s, int ql, struct FlowedState *fst)
302 {
303  print_indent(ql, s, add_quote_suffix(s, ql));
304  if (line && *line)
305  state_puts(line, s);
306  state_putc('\n', s);
307 
308  fst->width = 0;
309  fst->spaces = 0;
310 }
311 
316 int rfc3676_handler(struct Body *a, struct State *s)
317 {
318  char *buf = NULL;
319  unsigned int quotelevel = 0;
320  bool delsp = false;
321  size_t sz = 0;
322  struct FlowedState fst = { 0 };
323 
324  /* respect DelSp of RFC3676 only with f=f parts */
325  char *t = mutt_param_get(&a->parameter, "delsp");
326  if (t)
327  {
328  delsp = (mutt_str_strcasecmp(t, "yes") == 0);
329  t = NULL;
330  fst.delsp = true;
331  }
332 
333  mutt_debug(LL_DEBUG3, "f=f: DelSp: %s\n", delsp ? "yes" : "no");
334 
335  while ((buf = mutt_file_read_line(buf, &sz, s->fp_in, NULL, 0)))
336  {
337  const size_t buf_len = mutt_str_strlen(buf);
338  const unsigned int newql = get_quote_level(buf);
339 
340  /* end flowed paragraph (if we're within one) if quoting level
341  * changes (should not but can happen, see RFC3676, sec. 4.5.) */
342  if (newql != quotelevel)
343  flush_par(s, &fst);
344 
345  quotelevel = newql;
346  int buf_off = newql;
347 
348  /* respect sender's space-stuffing by removing one leading space */
349  if (buf[buf_off] == ' ')
350  buf_off++;
351 
352  /* test for signature separator */
353  const unsigned int sigsep = (mutt_str_strcmp(buf + buf_off, "-- ") == 0);
354 
355  /* a fixed line either has no trailing space or is the
356  * signature separator */
357  const bool fixed = (buf_len == buf_off) || (buf[buf_len - 1] != ' ') || sigsep;
358 
359  /* print fixed-and-standalone, fixed-and-empty and sigsep lines as
360  * fixed lines */
361  if ((fixed && ((fst.width == 0) || (buf_len == 0))) || sigsep)
362  {
363  /* if we're within a flowed paragraph, terminate it */
364  flush_par(s, &fst);
365  print_fixed_line(buf + buf_off, s, quotelevel, &fst);
366  continue;
367  }
368 
369  /* for DelSp=yes, we need to strip one SP prior to CRLF on flowed lines */
370  if (delsp && !fixed)
371  buf[buf_len - 1] = '\0';
372 
373  print_flowed_line(buf + buf_off, s, quotelevel, &fst, fixed);
374  }
375 
376  flush_par(s, &fst);
377 
378  FREE(&buf);
379  return 0;
380 }
381 
398 void rfc3676_space_stuff(struct Email *e)
399 {
400  int lc = 0;
401  unsigned char c = '\0';
402  char buf[1024];
403  char tmpfile[PATH_MAX];
404 
405  if (!e || !e->content || !e->content->filename)
406  return;
407 
408  mutt_debug(LL_DEBUG2, "f=f: postprocess %s\n", e->content->filename);
409 
410  FILE *fp_in = mutt_file_fopen(e->content->filename, "r");
411  if (!fp_in)
412  return;
413 
414  mutt_mktemp(tmpfile, sizeof(tmpfile));
415  FILE *fp_out = mutt_file_fopen(tmpfile, "w+");
416  if (!fp_out)
417  {
418  mutt_file_fclose(&fp_in);
419  return;
420  }
421 
422  while (fgets(buf, sizeof(buf), fp_in))
423  {
424  if ((buf[0] == ' ') || mutt_str_startswith(buf, "From ", CASE_MATCH))
425  {
426  fputc(' ', fp_out);
427  lc++;
428  size_t len = mutt_str_strlen(buf);
429  if (len > 0)
430  {
431  c = buf[len - 1];
432  buf[len - 1] = '\0';
433  }
434  mutt_debug(LL_DEBUG5, "f=f: line %d needs space-stuffing: '%s'\n", lc, buf);
435  if (len > 0)
436  buf[len - 1] = c;
437  }
438  fputs(buf, fp_out);
439  }
440  mutt_file_fclose(&fp_in);
441  mutt_file_fclose(&fp_out);
442  mutt_file_set_mtime(e->content->filename, tmpfile);
443  unlink(e->content->filename);
444  mutt_str_replace(&e->content->filename, tmpfile);
445 }
char * filename
when sending a message, this is the file to which this structure refers
Definition: body.h:46
The envelope/body of an email.
Definition: email.h:39
Window management.
GUI miscellaneous curses (window drawing) routines.
void mutt_file_set_mtime(const char *from, const char *to)
Set the modification time of one file from another.
Definition: file.c:992
Structs that make up an email.
static bool add_quote_suffix(struct State *s, int ql)
Should we add a trailing space to quotes.
Definition: rfc3676.c:112
static int space_quotes(struct State *s)
Should we add spaces between quote levels.
Definition: rfc3676.c:91
struct Body * content
List of MIME parts.
Definition: email.h:92
static int quote_width(struct State *s, int ql)
Calculate the paragraph width based upon the quote level.
Definition: rfc3676.c:192
char * prefix
String to add to the beginning of each output line.
Definition: state.h:48
char * mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, int flags)
Read a line from a file.
Definition: file.c:662
size_t spaces
Definition: rfc3676.c:58
Match case when comparing strings.
Definition: string2.h:67
size_t mutt_str_strlen(const char *a)
Calculate the length of a string, safely.
Definition: string.c:666
FILE * fp_in
File to read from.
Definition: state.h:46
The body of an email.
Definition: body.h:34
Hundreds of global variables to back the user variables.
Some miscellaneous functions.
StateFlags flags
Flags, e.g. MUTT_DISPLAY.
Definition: state.h:49
Log at debug level 2.
Definition: logging.h:57
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:150
const char * line
Definition: common.c:36
#define mutt_mktemp(buf, buflen)
Definition: muttlib.h:76
static size_t print_indent(int ql, struct State *s, int add_suffix)
Print indented text.
Definition: rfc3676.c:137
int mutt_strwidth(const char *s)
Measure a string&#39;s width in screen cells.
Definition: curs_lib.c:1266
#define PATH_MAX
Definition: mutt.h:52
struct MuttWindow * MuttIndexWindow
Index Window.
Definition: mutt_window.c:40
static int get_quote_level(const char *line)
Get the quote level of a line.
Definition: rfc3676.c:67
int mutt_window_wrap_cols(int width, short wrap)
Calculate the wrap column for a given screen width.
Definition: mutt_window.c:337
static void print_fixed_line(const char *line, struct State *s, int ql, struct FlowedState *fst)
Print a fixed format line.
Definition: rfc3676.c:301
void mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:453
void rfc3676_space_stuff(struct Email *e)
Perform required RFC3676 space stuffing.
Definition: rfc3676.c:398
size_t width
Definition: rfc3676.c:57
size_t mutt_str_startswith(const char *str, const char *prefix, enum CaseSensitivity cs)
Check whether a string starts with a prefix.
Definition: string.c:168
Keep track when processing files.
WHERE bool C_TextFlowed
Config: Generate &#39;format=flowed&#39; messages.
Definition: globals.h:268
#define state_puts(str, state)
Definition: state.h:54
State of a Format-Flowed line of text.
Definition: rfc3676.c:55
#define state_putc(str, state)
Definition: state.h:55
int mutt_str_strcasecmp(const char *a, const char *b)
Compare two strings ignoring case, safely.
Definition: string.c:628
#define FREE(x)
Definition: memory.h:40
char * mutt_param_get(const struct ParameterList *pl, const char *s)
Find a matching Parameter.
Definition: parameter.c:84
#define FLOWED_MAX
Definition: rfc3676.c:50
#define MUTT_REPLYING
Are we replying?
Definition: state.h:38
bool C_ReflowSpaceQuotes
Config: Insert spaces into reply quotes for &#39;format=flowed&#39; messages.
Definition: rfc3676.c:47
Keep track when processing files.
Definition: state.h:44
static void flush_par(struct State *s, struct FlowedState *fst)
Write out the paragraph.
Definition: rfc3676.c:173
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
int rfc3676_handler(struct Body *a, struct State *s)
Body handler implementing RFC3676 for format=flowed - Implements handler_t.
Definition: rfc3676.c:316
Log at debug level 5.
Definition: logging.h:60
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:583
static void print_flowed_line(char *line, struct State *s, int ql, struct FlowedState *fst, bool term)
Print a format-flowed line.
Definition: rfc3676.c:223
bool delsp
Definition: rfc3676.c:59
struct ParameterList parameter
parameters of the content-type
Definition: body.h:39
int mutt_str_strcmp(const char *a, const char *b)
Compare two strings, safely.
Definition: string.c:615
Log at debug level 3.
Definition: logging.h:58
short C_ReflowWrap
Config: Maximum paragraph width for reformatting &#39;format=flowed&#39; text.
Definition: rfc3676.c:48