NeoMutt  2022-04-29-145-g9b6a0e
Teaching an old dog new tricks
DOXYGEN
rfc3676.c
Go to the documentation of this file.
1 
32 #include "config.h"
33 #include <stdbool.h>
34 #include <stdio.h>
35 #include <unistd.h>
36 #include "mutt/lib.h"
37 #include "config/lib.h"
38 #include "email/lib.h"
39 #include "core/lib.h"
40 #include "gui/lib.h"
41 #include "rfc3676.h"
42 #include "muttlib.h"
43 
44 #define FLOWED_MAX 72
45 
50 {
51  size_t width;
52  size_t spaces;
53  bool delsp;
54 };
55 
61 static int get_quote_level(const char *line)
62 {
63  int quoted = 0;
64  const char *p = line;
65 
66  while (p && (*p == '>'))
67  {
68  quoted++;
69  p++;
70  }
71 
72  return quoted;
73 }
74 
85 static int space_quotes(struct State *s)
86 {
87  /* Allow quote spacing in the pager even for `$text_flowed`,
88  * but obviously not when replying. */
89  const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
90  if (c_text_flowed && (s->flags & MUTT_REPLYING))
91  return 0;
92 
93  const bool c_reflow_space_quotes = cs_subset_bool(NeoMutt->sub, "reflow_space_quotes");
94  return c_reflow_space_quotes;
95 }
96 
108 static bool add_quote_suffix(struct State *s, int ql)
109 {
110  if (s->flags & MUTT_REPLYING)
111  return false;
112 
113  if (space_quotes(s))
114  return false;
115 
116  if (!ql && !s->prefix)
117  return false;
118 
119  /* The prefix will add its own space */
120  const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
121  if (!c_text_flowed && !ql && s->prefix)
122  return false;
123 
124  return true;
125 }
126 
134 static size_t print_indent(int ql, struct State *s, int add_suffix)
135 {
136  size_t wid = 0;
137 
138  if (s->prefix)
139  {
140  /* use given prefix only for format=fixed replies to format=flowed,
141  * for format=flowed replies to format=flowed, use '>' indentation */
142  const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
143  if (c_text_flowed)
144  ql++;
145  else
146  {
147  state_puts(s, s->prefix);
148  wid = mutt_strwidth(s->prefix);
149  }
150  }
151  for (int i = 0; i < ql; i++)
152  {
153  state_putc(s, '>');
154  if (space_quotes(s))
155  state_putc(s, ' ');
156  }
157  if (add_suffix)
158  state_putc(s, ' ');
159 
160  if (space_quotes(s))
161  ql *= 2;
162 
163  return ql + add_suffix + wid;
164 }
165 
171 static void flush_par(struct State *s, struct FlowedState *fst)
172 {
173  if (fst->width > 0)
174  {
175  state_putc(s, '\n');
176  fst->width = 0;
177  }
178  fst->spaces = 0;
179 }
180 
190 static int quote_width(struct State *s, int ql)
191 {
192  const int screen_width = (s->flags & MUTT_DISPLAY) ? s->wraplen : 80;
193  const short c_reflow_wrap = cs_subset_number(NeoMutt->sub, "reflow_wrap");
194  int width = mutt_window_wrap_cols(screen_width, c_reflow_wrap);
195  const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
196  if (c_text_flowed && (s->flags & MUTT_REPLYING))
197  {
198  /* When replying, force a wrap at FLOWED_MAX to comply with RFC3676
199  * guidelines */
200  if (width > FLOWED_MAX)
201  width = FLOWED_MAX;
202  ql++; /* When replying, we will add an additional quote level */
203  }
204  /* adjust the paragraph width subtracting the number of prefix chars */
205  width -= space_quotes(s) ? ql * 2 : ql;
206  /* When displaying (not replying), there may be a space between the prefix
207  * string and the paragraph */
208  if (add_quote_suffix(s, ql))
209  width--;
210  /* failsafe for really long quotes */
211  if (width <= 0)
212  width = FLOWED_MAX; /* arbitrary, since the line will wrap */
213  return width;
214 }
215 
224 static void print_flowed_line(char *line, struct State *s, int ql,
225  struct FlowedState *fst, bool term)
226 {
227  size_t width, w, words = 0;
228  char *p = NULL;
229  char last;
230 
231  if (!line || (*line == '\0'))
232  {
233  /* flush current paragraph (if any) first */
234  flush_par(s, fst);
235  print_indent(ql, s, 0);
236  state_putc(s, '\n');
237  return;
238  }
239 
240  width = quote_width(s, ql);
241  last = line[mutt_str_len(line) - 1];
242 
243  mutt_debug(LL_DEBUG5, "f=f: line [%s], width = %ld, spaces = %lu\n", line,
244  (long) width, fst->spaces);
245 
246  for (words = 0; (p = mutt_str_sep(&line, " "));)
247  {
248  mutt_debug(LL_DEBUG5, "f=f: word [%s], width: %lu, remaining = [%s]\n", p,
249  fst->width, line);
250 
251  /* remember number of spaces */
252  if (*p == '\0')
253  {
254  mutt_debug(LL_DEBUG3, "f=f: additional space\n");
255  fst->spaces++;
256  continue;
257  }
258  /* there's exactly one space prior to every but the first word */
259  if (words)
260  fst->spaces++;
261 
262  w = mutt_strwidth(p);
263  /* see if we need to break the line but make sure the first word is put on
264  * the line regardless; if for DelSp=yes only one trailing space is used,
265  * we probably have a long word that we should break within (we leave that
266  * up to the pager or user) */
267  if (!(!fst->spaces && fst->delsp && (last != ' ')) && (w < width) &&
268  (w + fst->width + fst->spaces > width))
269  {
270  mutt_debug(LL_DEBUG3, "f=f: break line at %lu, %lu spaces left\n",
271  fst->width, fst->spaces);
272  /* only honor trailing spaces for format=flowed replies */
273  const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
274  if (c_text_flowed)
275  for (; fst->spaces; fst->spaces--)
276  state_putc(s, ' ');
277  state_putc(s, '\n');
278  fst->width = 0;
279  fst->spaces = 0;
280  words = 0;
281  }
282 
283  if (!words && !fst->width)
284  fst->width = print_indent(ql, s, add_quote_suffix(s, ql));
285  fst->width += w + fst->spaces;
286  for (; fst->spaces; fst->spaces--)
287  state_putc(s, ' ');
288  state_puts(s, p);
289  words++;
290  }
291 
292  if (term)
293  flush_par(s, fst);
294 }
295 
303 static void print_fixed_line(const char *line, struct State *s, int ql, struct FlowedState *fst)
304 {
305  print_indent(ql, s, add_quote_suffix(s, ql));
306  if (line && *line)
307  state_puts(s, line);
308  state_putc(s, '\n');
309 
310  fst->width = 0;
311  fst->spaces = 0;
312 }
313 
318 int rfc3676_handler(struct Body *a, struct State *s)
319 {
320  char *buf = NULL;
321  unsigned int quotelevel = 0;
322  bool delsp = false;
323  size_t sz = 0;
324  struct FlowedState fst = { 0 };
325 
326  /* respect DelSp of RFC3676 only with f=f parts */
327  char *t = mutt_param_get(&a->parameter, "delsp");
328  if (t)
329  {
330  delsp = mutt_istr_equal(t, "yes");
331  t = NULL;
332  fst.delsp = true;
333  }
334 
335  mutt_debug(LL_DEBUG3, "f=f: DelSp: %s\n", delsp ? "yes" : "no");
336 
337  while ((buf = mutt_file_read_line(buf, &sz, s->fp_in, NULL, MUTT_RL_NO_FLAGS)))
338  {
339  const size_t buf_len = mutt_str_len(buf);
340  const unsigned int newql = get_quote_level(buf);
341 
342  /* end flowed paragraph (if we're within one) if quoting level
343  * changes (should not but can happen, see RFC3676, sec. 4.5.) */
344  if (newql != quotelevel)
345  flush_par(s, &fst);
346 
347  quotelevel = newql;
348  int buf_off = newql;
349 
350  /* respect sender's space-stuffing by removing one leading space */
351  if (buf[buf_off] == ' ')
352  buf_off++;
353 
354  /* test for signature separator */
355  const unsigned int sigsep = mutt_str_equal(buf + buf_off, "-- ");
356 
357  /* a fixed line either has no trailing space or is the
358  * signature separator */
359  const bool fixed = (buf_len == buf_off) || (buf[buf_len - 1] != ' ') || sigsep;
360 
361  /* print fixed-and-standalone, fixed-and-empty and sigsep lines as
362  * fixed lines */
363  if ((fixed && ((fst.width == 0) || (buf_len == 0))) || sigsep)
364  {
365  /* if we're within a flowed paragraph, terminate it */
366  flush_par(s, &fst);
367  print_fixed_line(buf + buf_off, s, quotelevel, &fst);
368  continue;
369  }
370 
371  /* for DelSp=yes, we need to strip one SP prior to CRLF on flowed lines */
372  if (delsp && !fixed)
373  buf[buf_len - 1] = '\0';
374 
375  print_flowed_line(buf + buf_off, s, quotelevel, &fst, fixed);
376  }
377 
378  flush_par(s, &fst);
379 
380  FREE(&buf);
381  return 0;
382 }
383 
390 {
391  if (b && (b->type == TYPE_TEXT) && mutt_istr_equal("plain", b->subtype))
392  {
393  const char *format = mutt_param_get(&b->parameter, "format");
394  if (mutt_istr_equal("flowed", format))
395  return true;
396  }
397 
398  return false;
399 }
400 
414 static void rfc3676_space_stuff(const char *filename, bool unstuff)
415 {
416  FILE *fp_out = NULL;
417  char *buf = NULL;
418  size_t blen = 0;
419 
420  struct Buffer *tmpfile = mutt_buffer_pool_get();
421 
422  FILE *fp_in = mutt_file_fopen(filename, "r");
423  if (!fp_in)
424  goto bail;
425 
426  mutt_buffer_mktemp(tmpfile);
427  fp_out = mutt_file_fopen(mutt_buffer_string(tmpfile), "w+");
428  if (!fp_out)
429  goto bail;
430 
431  while ((buf = mutt_file_read_line(buf, &blen, fp_in, NULL, MUTT_RL_NO_FLAGS)) != NULL)
432  {
433  if (unstuff)
434  {
435  if (buf[0] == ' ')
436  fputs(buf + 1, fp_out);
437  else
438  fputs(buf, fp_out);
439  }
440  else
441  {
442  if ((buf[0] == ' ') || mutt_str_startswith(buf, "From "))
443  fputc(' ', fp_out);
444  fputs(buf, fp_out);
445  }
446  fputc('\n', fp_out);
447  }
448  FREE(&buf);
449  mutt_file_fclose(&fp_in);
450  mutt_file_fclose(&fp_out);
451  mutt_file_set_mtime(filename, mutt_buffer_string(tmpfile));
452 
453  fp_in = mutt_file_fopen(mutt_buffer_string(tmpfile), "r");
454  if (!fp_in)
455  goto bail;
456 
457  if ((truncate(filename, 0) == -1) || ((fp_out = mutt_file_fopen(filename, "a")) == NULL))
458  {
459  mutt_perror(filename);
460  goto bail;
461  }
462 
463  mutt_file_copy_stream(fp_in, fp_out);
464  mutt_file_set_mtime(mutt_buffer_string(tmpfile), filename);
465  unlink(mutt_buffer_string(tmpfile));
466 
467 bail:
468  mutt_file_fclose(&fp_in);
469  mutt_file_fclose(&fp_out);
470  mutt_buffer_pool_release(&tmpfile);
471 }
472 
482 {
483  if (!e || !e->body || !e->body->filename)
484  return;
485 
487  rfc3676_space_stuff(e->body->filename, false);
488 }
489 
495 {
496  if (!e || !e->body || !e->body->filename)
497  return;
498 
500  rfc3676_space_stuff(e->body->filename, true);
501 }
502 
513 void mutt_rfc3676_space_unstuff_attachment(struct Body *b, const char *filename)
514 {
515  if (!filename)
516  return;
517 
518  if (b && !mutt_rfc3676_is_format_flowed(b))
519  return;
520 
521  rfc3676_space_stuff(filename, true);
522 }
523 
534 void mutt_rfc3676_space_stuff_attachment(struct Body *b, const char *filename)
535 {
536  if (!filename)
537  return;
538 
539  if (b && !mutt_rfc3676_is_format_flowed(b))
540  return;
541 
542  rfc3676_space_stuff(filename, false);
543 }
static const char * mutt_buffer_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:77
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:169
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
Convenience wrapper for the config headers.
Convenience wrapper for the core headers.
size_t mutt_strwidth(const char *s)
Measure a string's width in screen cells.
Definition: curs_lib.c:904
Structs that make up an email.
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition: file.c:260
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:152
char * mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags)
Read a line from a file.
Definition: file.c:720
void mutt_file_set_mtime(const char *from, const char *to)
Set the modification time of one file from another.
Definition: file.c:1059
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:618
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition: file.h:38
int rfc3676_handler(struct Body *a, struct State *s)
Body handler implementing RFC3676 for format=flowed - Implements handler_t -.
Definition: rfc3676.c:318
#define mutt_debug(LEVEL,...)
Definition: logging.h:84
#define mutt_perror(...)
Definition: logging.h:88
Convenience wrapper for the gui headers.
@ LL_DEBUG3
Log at debug level 3.
Definition: logging.h:42
@ LL_DEBUG5
Log at debug level 5.
Definition: logging.h:44
#define FREE(x)
Definition: memory.h:43
@ TYPE_TEXT
Type: 'text/*'.
Definition: mime.h:38
Convenience wrapper for the library headers.
#define state_puts(STATE, STR)
Definition: state.h:56
#define state_putc(STATE, STR)
Definition: state.h:57
#define MUTT_DISPLAY
Output is displayed to the user.
Definition: state.h:32
#define MUTT_REPLYING
Are we replying?
Definition: state.h:38
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:796
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:784
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:227
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:544
char * mutt_str_sep(char **stringp, const char *delim)
Find first occurance of any of delim characters in *stringp.
Definition: string.c:183
int mutt_window_wrap_cols(int width, short wrap)
Calculate the wrap column for a given screen width.
Definition: mutt_window.c:365
Some miscellaneous functions.
#define mutt_buffer_mktemp(buf)
Definition: muttlib.h:74
char * mutt_param_get(const struct ParameterList *pl, const char *s)
Find a matching Parameter.
Definition: parameter.c:84
void mutt_buffer_pool_release(struct Buffer **pbuf)
Free a Buffer from the pool.
Definition: pool.c:112
struct Buffer * mutt_buffer_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:101
static void rfc3676_space_stuff(const char *filename, bool unstuff)
Perform required RFC3676 space stuffing.
Definition: rfc3676.c:414
#define FLOWED_MAX
Definition: rfc3676.c:44
void mutt_rfc3676_space_unstuff(struct Email *e)
Remove RFC3676 space stuffing.
Definition: rfc3676.c:494
static bool add_quote_suffix(struct State *s, int ql)
Should we add a trailing space to quotes.
Definition: rfc3676.c:108
static void print_fixed_line(const char *line, struct State *s, int ql, struct FlowedState *fst)
Print a fixed format line.
Definition: rfc3676.c:303
void mutt_rfc3676_space_stuff_attachment(struct Body *b, const char *filename)
Stuff attachments.
Definition: rfc3676.c:534
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:224
static int get_quote_level(const char *line)
Get the quote level of a line.
Definition: rfc3676.c:61
void mutt_rfc3676_space_unstuff_attachment(struct Body *b, const char *filename)
Unstuff attachments.
Definition: rfc3676.c:513
static size_t print_indent(int ql, struct State *s, int add_suffix)
Print indented text.
Definition: rfc3676.c:134
static int space_quotes(struct State *s)
Should we add spaces between quote levels.
Definition: rfc3676.c:85
bool mutt_rfc3676_is_format_flowed(struct Body *b)
Is the Email "format-flowed"?
Definition: rfc3676.c:389
void mutt_rfc3676_space_stuff(struct Email *e)
Perform RFC3676 space stuffing on an Email.
Definition: rfc3676.c:481
static int quote_width(struct State *s, int ql)
Calculate the paragraph width based upon the quote level.
Definition: rfc3676.c:190
static void flush_par(struct State *s, struct FlowedState *fst)
Write out the paragraph.
Definition: rfc3676.c:171
RFC3676 Format Flowed routines.
The body of an email.
Definition: body.h:36
struct ParameterList parameter
Parameters of the content-type.
Definition: body.h:62
char * subtype
content-type subtype
Definition: body.h:60
unsigned int type
content-type primary type, ContentType
Definition: body.h:40
char * filename
When sending a message, this is the file to which this structure refers.
Definition: body.h:58
String manipulation buffer.
Definition: buffer.h:34
The envelope/body of an email.
Definition: email.h:37
struct Body * body
List of MIME parts.
Definition: email.h:67
State of a Format-Flowed line of text.
Definition: rfc3676.c:50
bool delsp
Definition: rfc3676.c:53
size_t width
Definition: rfc3676.c:51
size_t spaces
Definition: rfc3676.c:52
Container for Accounts, Notifications.
Definition: neomutt.h:37
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
Keep track when processing files.
Definition: state.h:46
int wraplen
Width to wrap lines to (when flags & MUTT_DISPLAY)
Definition: state.h:51
StateFlags flags
Flags, e.g. MUTT_DISPLAY.
Definition: state.h:50
char * prefix
String to add to the beginning of each output line.
Definition: state.h:49
FILE * fp_in
File to read from.
Definition: state.h:47