NeoMutt  2025-12-11-435-g4ac674
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
rfc3676.c
Go to the documentation of this file.
1
28
34
35#include "config.h"
36#include <stdbool.h>
37#include <stdio.h>
38#include <unistd.h>
39#include "mutt/lib.h"
40#include "config/lib.h"
41#include "core/lib.h"
42#include "gui/lib.h"
43#include "rfc3676.h"
44#include "body.h"
45#include "email.h"
46#include "mime.h"
47#include "parameter.h"
48
50#define FLOWED_MAX 72
51
56{
57 size_t width;
58 size_t spaces;
59 bool delsp;
60};
61
67static 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
91static int space_quotes(struct State *state)
92{
93 /* Allow quote spacing in the pager even for `$text_flowed`,
94 * but obviously not when replying. */
95 const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
96 if (c_text_flowed && (state->flags & STATE_REPLYING))
97 return 0;
98
99 const bool c_reflow_space_quotes = cs_subset_bool(NeoMutt->sub, "reflow_space_quotes");
100 return c_reflow_space_quotes;
101}
102
114static bool add_quote_suffix(struct State *state, int ql)
115{
116 if (state->flags & STATE_REPLYING)
117 return false;
118
119 if (space_quotes(state))
120 return false;
121
122 if (!ql && !state->prefix)
123 return false;
124
125 /* The prefix will add its own space */
126 const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
127 if (!c_text_flowed && !ql && state->prefix)
128 return false;
129
130 return true;
131}
132
140static size_t print_indent(int ql, struct State *state, int add_suffix)
141{
142 size_t wid = 0;
143
144 if (state->prefix)
145 {
146 /* use given prefix only for format=fixed replies to format=flowed,
147 * for format=flowed replies to format=flowed, use '>' indentation */
148 const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
149 if (c_text_flowed)
150 {
151 ql++;
152 }
153 else
154 {
155 state_puts(state, state->prefix);
156 wid = mutt_strwidth(state->prefix);
157 }
158 }
159 for (int i = 0; i < ql; i++)
160 {
161 state_putc(state, '>');
162 if (space_quotes(state))
163 state_putc(state, ' ');
164 }
165 if (add_suffix)
166 state_putc(state, ' ');
167
168 if (space_quotes(state))
169 ql *= 2;
170
171 return ql + add_suffix + wid;
172}
173
179static void flush_par(struct State *state, struct FlowedState *fst)
180{
181 if (fst->width > 0)
182 {
183 state_putc(state, '\n');
184 fst->width = 0;
185 }
186 fst->spaces = 0;
187}
188
198static int quote_width(struct State *state, int ql)
199{
200 const int screen_width = (state->flags & STATE_DISPLAY) ? state->wraplen : 80;
201 const short c_reflow_wrap = cs_subset_number(NeoMutt->sub, "reflow_wrap");
202 int width = mutt_window_wrap_cols(screen_width, c_reflow_wrap);
203 const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
204 if (c_text_flowed && (state->flags & STATE_REPLYING))
205 {
206 /* When replying, force a wrap at FLOWED_MAX to comply with RFC3676
207 * guidelines */
208 if (width > FLOWED_MAX)
209 width = FLOWED_MAX;
210 ql++; /* When replying, we will add an additional quote level */
211 }
212 /* adjust the paragraph width subtracting the number of prefix chars */
213 width -= space_quotes(state) ? ql * 2 : ql;
214 /* When displaying (not replying), there may be a space between the prefix
215 * string and the paragraph */
216 if (add_quote_suffix(state, ql))
217 width--;
218 /* failsafe for really long quotes */
219 if (width <= 0)
220 width = FLOWED_MAX; /* arbitrary, since the line will wrap */
221 return width;
222}
223
232static void print_flowed_line(char *line, struct State *state, int ql,
233 struct FlowedState *fst, bool term)
234{
235 size_t width, w, words = 0;
236 char *p = NULL;
237 char last;
238
239 if (!line || (*line == '\0'))
240 {
241 /* flush current paragraph (if any) first */
242 flush_par(state, fst);
243 print_indent(ql, state, 0);
244 state_putc(state, '\n');
245 return;
246 }
247
248 width = quote_width(state, ql);
249 last = line[mutt_str_len(line) - 1];
250
251 mutt_debug(LL_DEBUG5, "f=f: line [%s], width = %ld, spaces = %zu\n", line,
252 (long) width, fst->spaces);
253
254 for (words = 0; (p = mutt_str_sep(&line, " "));)
255 {
256 mutt_debug(LL_DEBUG5, "f=f: word [%s], width: %zu, remaining = [%s]\n", p,
257 fst->width, line);
258
259 /* remember number of spaces */
260 if (*p == '\0')
261 {
262 mutt_debug(LL_DEBUG3, "f=f: additional space\n");
263 fst->spaces++;
264 continue;
265 }
266 /* there's exactly one space prior to every but the first word */
267 if (words)
268 fst->spaces++;
269
270 w = mutt_strwidth(p);
271 /* see if we need to break the line but make sure the first word is put on
272 * the line regardless; if for DelSp=yes only one trailing space is used,
273 * we probably have a long word that we should break within (we leave that
274 * up to the pager or user) */
275 if (!(!fst->spaces && fst->delsp && (last != ' ')) && (w < width) &&
276 (w + fst->width + fst->spaces > width))
277 {
278 mutt_debug(LL_DEBUG3, "f=f: break line at %zu, %zu spaces left\n",
279 fst->width, fst->spaces);
280 /* only honor trailing spaces for format=flowed replies */
281 const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
282 if (c_text_flowed)
283 for (; fst->spaces; fst->spaces--)
284 state_putc(state, ' ');
285 state_putc(state, '\n');
286 fst->width = 0;
287 fst->spaces = 0;
288 words = 0;
289 }
290
291 if (!words && !fst->width)
292 fst->width = print_indent(ql, state, add_quote_suffix(state, ql));
293 fst->width += w + fst->spaces;
294 for (; fst->spaces; fst->spaces--)
295 state_putc(state, ' ');
296 state_puts(state, p);
297 words++;
298 }
299
300 if (term)
301 flush_par(state, fst);
302}
303
311static void print_fixed_line(const char *line, struct State *state, int ql,
312 struct FlowedState *fst)
313{
314 print_indent(ql, state, add_quote_suffix(state, ql));
315 if (line && *line)
316 state_puts(state, line);
317 state_putc(state, '\n');
318
319 fst->width = 0;
320 fst->spaces = 0;
321}
322
327int rfc3676_handler(struct Body *b_email, struct State *state)
328{
329 char *buf = NULL;
330 unsigned int quotelevel = 0;
331 bool delsp = false;
332 size_t sz = 0;
333 struct FlowedState fst = { 0 };
334
335 /* respect DelSp of RFC3676 only with f=f parts */
336 char *t = mutt_param_get(&b_email->parameter, "delsp");
337 if (t)
338 {
339 delsp = mutt_istr_equal(t, "yes");
340 t = NULL;
341 fst.delsp = true;
342 }
343
344 mutt_debug(LL_DEBUG3, "f=f: DelSp: %s\n", delsp ? "yes" : "no");
345
346 while ((buf = mutt_file_read_line(buf, &sz, state->fp_in, NULL, MUTT_RL_NO_FLAGS)))
347 {
348 const size_t buflen = mutt_str_len(buf);
349 const unsigned int newql = get_quote_level(buf);
350
351 /* end flowed paragraph (if we're within one) if quoting level
352 * changes (should not but can happen, see RFC3676, sec. 4.5.) */
353 if (newql != quotelevel)
354 flush_par(state, &fst);
355
356 quotelevel = newql;
357 int buf_off = newql;
358
359 /* respect sender's space-stuffing by removing one leading space */
360 if (buf[buf_off] == ' ')
361 buf_off++;
362
363 /* test for signature separator */
364 const unsigned int sigsep = mutt_str_equal(buf + buf_off, "-- ");
365
366 /* a fixed line either has no trailing space or is the
367 * signature separator */
368 const bool fixed = (buflen == buf_off) || (buf[buflen - 1] != ' ') || sigsep;
369
370 /* print fixed-and-standalone, fixed-and-empty and sigsep lines as
371 * fixed lines */
372 if ((fixed && ((fst.width == 0) || (buflen == 0))) || sigsep)
373 {
374 /* if we're within a flowed paragraph, terminate it */
375 flush_par(state, &fst);
376 print_fixed_line(buf + buf_off, state, quotelevel, &fst);
377 continue;
378 }
379
380 /* for DelSp=yes, we need to strip one SP prior to CRLF on flowed lines */
381 if (delsp && !fixed)
382 buf[buflen - 1] = '\0';
383
384 print_flowed_line(buf + buf_off, state, quotelevel, &fst, fixed);
385 }
386
387 flush_par(state, &fst);
388
389 FREE(&buf);
390 return 0;
391}
392
399{
400 if (b && (b->type == TYPE_TEXT) && mutt_istr_equal("plain", b->subtype))
401 {
402 const char *format = mutt_param_get(&b->parameter, "format");
403 if (mutt_istr_equal("flowed", format))
404 return true;
405 }
406
407 return false;
408}
409
423static void rfc3676_space_stuff(const char *filename, bool unstuff)
424{
425 FILE *fp_out = NULL;
426 char *buf = NULL;
427 size_t blen = 0;
428
429 struct Buffer *tempfile = buf_pool_get();
430
431 FILE *fp_in = mutt_file_fopen(filename, "r");
432 if (!fp_in)
433 goto bail;
434
435 buf_mktemp(tempfile);
436 fp_out = mutt_file_fopen(buf_string(tempfile), "w+");
437 if (!fp_out)
438 goto bail;
439
440 while ((buf = mutt_file_read_line(buf, &blen, fp_in, NULL, MUTT_RL_NO_FLAGS)) != NULL)
441 {
442 if (unstuff)
443 {
444 if (buf[0] == ' ')
445 fputs(buf + 1, fp_out);
446 else
447 fputs(buf, fp_out);
448 }
449 else
450 {
451 if ((buf[0] == ' ') || mutt_str_startswith(buf, "From "))
452 fputc(' ', fp_out);
453 fputs(buf, fp_out);
454 }
455 fputc('\n', fp_out);
456 }
457 FREE(&buf);
458 mutt_file_fclose(&fp_in);
459 mutt_file_fclose(&fp_out);
460 mutt_file_set_mtime(filename, buf_string(tempfile));
461
462 fp_in = mutt_file_fopen(buf_string(tempfile), "r");
463 if (!fp_in)
464 goto bail;
465
466 if ((truncate(filename, 0) == -1) || ((fp_out = mutt_file_fopen(filename, "a")) == NULL))
467 {
468 mutt_perror("%s", filename);
469 goto bail;
470 }
471
472 mutt_file_copy_stream(fp_in, fp_out);
473 mutt_file_set_mtime(buf_string(tempfile), filename);
474 unlink(buf_string(tempfile));
475
476bail:
477 mutt_file_fclose(&fp_in);
478 mutt_file_fclose(&fp_out);
479 buf_pool_release(&tempfile);
480}
481
491{
492 if (!e || !e->body || !e->body->filename)
493 return;
494
497}
498
504{
505 if (!e || !e->body || !e->body->filename)
506 return;
507
510}
511
522void mutt_rfc3676_space_unstuff_attachment(struct Body *b, const char *filename)
523{
524 if (!filename)
525 return;
526
528 return;
529
530 rfc3676_space_stuff(filename, true);
531}
532
543void mutt_rfc3676_space_stuff_attachment(struct Body *b, const char *filename)
544{
545 if (!filename)
546 return;
547
549 return;
550
551 rfc3676_space_stuff(filename, false);
552}
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition helpers.c:143
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition helpers.c:47
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:444
Representation of the body of an email.
Representation of an email.
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition file.c:222
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:682
void mutt_file_set_mtime(const char *from, const char *to)
Set the modification time of one file from another.
Definition file.c:942
#define mutt_file_fclose(FP)
Definition file.h:139
#define mutt_file_fopen(PATH, MODE)
Definition file.h:138
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition file.h:40
int rfc3676_handler(struct Body *b_email, struct State *state)
Handler for format=flowed - Implements handler_t -.
Definition rfc3676.c:327
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
#define mutt_perror(...)
Definition logging2.h:95
Convenience wrapper for the gui headers.
@ LL_DEBUG3
Log at debug level 3.
Definition logging2.h:47
@ LL_DEBUG5
Log at debug level 5.
Definition logging2.h:49
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
Constants and macros for managing MIME encoding.
@ TYPE_TEXT
Type: 'text/*'.
Definition mime.h:38
Convenience wrapper for the library headers.
#define state_puts(STATE, STR)
Definition state.h:58
#define STATE_DISPLAY
Output is displayed to the user.
Definition state.h:33
#define state_putc(STATE, STR)
Definition state.h:59
#define STATE_REPLYING
Are we replying?
Definition state.h:39
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition string.c:674
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:662
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition string.c:234
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:500
char * mutt_str_sep(char **stringp, const char *delim)
Find first occurrence of any of delim characters in *stringp.
Definition string.c:190
int mutt_window_wrap_cols(int width, short wrap)
Calculate the wrap column for a given screen width.
char * mutt_param_get(const struct ParameterList *pl, const char *s)
Find a matching Parameter.
Definition parameter.c:85
Store attributes associated with a MIME part.
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:91
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:111
static void print_fixed_line(const char *line, struct State *state, int ql, struct FlowedState *fst)
Print a fixed format line.
Definition rfc3676.c:311
static void rfc3676_space_stuff(const char *filename, bool unstuff)
Perform required RFC3676 space stuffing.
Definition rfc3676.c:423
static size_t print_indent(int ql, struct State *state, int add_suffix)
Print indented text.
Definition rfc3676.c:140
#define FLOWED_MAX
Maximum line length for format=flowed text (RFC 3676)
Definition rfc3676.c:50
void mutt_rfc3676_space_unstuff(struct Email *e)
Remove RFC3676 space stuffing.
Definition rfc3676.c:503
static bool add_quote_suffix(struct State *state, int ql)
Should we add a trailing space to quotes.
Definition rfc3676.c:114
static int quote_width(struct State *state, int ql)
Calculate the paragraph width based upon the quote level.
Definition rfc3676.c:198
void mutt_rfc3676_space_stuff_attachment(struct Body *b, const char *filename)
Stuff attachments.
Definition rfc3676.c:543
static int space_quotes(struct State *state)
Should we add spaces between quote levels.
Definition rfc3676.c:91
static int get_quote_level(const char *line)
Get the quote level of a line.
Definition rfc3676.c:67
void mutt_rfc3676_space_unstuff_attachment(struct Body *b, const char *filename)
Unstuff attachments.
Definition rfc3676.c:522
static void print_flowed_line(char *line, struct State *state, int ql, struct FlowedState *fst, bool term)
Print a format-flowed line.
Definition rfc3676.c:232
static void flush_par(struct State *state, struct FlowedState *fst)
Write out the paragraph.
Definition rfc3676.c:179
bool mutt_rfc3676_is_format_flowed(struct Body *b)
Is the Email "format-flowed"?
Definition rfc3676.c:398
void mutt_rfc3676_space_stuff(struct Email *e)
Perform RFC3676 space stuffing on an Email.
Definition rfc3676.c:490
RFC3676 Format Flowed routines.
The body of an email.
Definition body.h:36
struct ParameterList parameter
Parameters of the content-type.
Definition body.h:63
char * subtype
content-type subtype
Definition body.h:61
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:59
String manipulation buffer.
Definition buffer.h:36
The envelope/body of an email.
Definition email.h:39
struct Body * body
List of MIME parts.
Definition email.h:69
State of a Format-Flowed line of text.
Definition rfc3676.c:56
bool delsp
Delete trailing space.
Definition rfc3676.c:59
size_t width
Wrap width.
Definition rfc3676.c:57
size_t spaces
Number of spaces to insert.
Definition rfc3676.c:58
Container for Accounts, Notifications.
Definition neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49
Keep track when processing files.
Definition state.h:48
int wraplen
Width to wrap lines to (when flags & STATE_DISPLAY)
Definition state.h:53
StateFlags flags
Flags, e.g. STATE_DISPLAY.
Definition state.h:52
FILE * fp_in
File to read from.
Definition state.h:49
const char * prefix
String to add to the beginning of each output line.
Definition state.h:51
#define buf_mktemp(buf)
Definition tmp.h:33