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