NeoMutt  2025-09-05-55-g97fc89
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
enriched.c
Go to the documentation of this file.
1
22
28
32
33#include "config.h"
34#include <stdbool.h>
35#include <stdio.h>
36#include <wchar.h>
37#include <wctype.h>
38#include "mutt/lib.h"
39#include "email/lib.h"
40#include "enriched.h"
41
42#define INDENT_SIZE 4
43
63
67struct Etags
68{
69 const wchar_t *tag_name;
70 int index;
71};
72
74static const struct Etags EnrichedTags[] = {
75 // clang-format off
76 { L"param", RICH_PARAM },
77 { L"bold", RICH_BOLD },
78 { L"italic", RICH_ITALIC },
79 { L"underline", RICH_UNDERLINE },
80 { L"nofill", RICH_NOFILL },
81 { L"excerpt", RICH_EXCERPT },
82 { L"indent", RICH_INDENT },
83 { L"indentright", RICH_INDENT_RIGHT },
84 { L"center", RICH_CENTER },
85 { L"flushleft", RICH_FLUSHLEFT },
86 { L"flushright", RICH_FLUSHRIGHT },
87 { L"flushboth", RICH_FLUSHLEFT },
88 { L"color", RICH_COLOR },
89 { L"x-color", RICH_COLOR },
90 { NULL, -1 },
91 // clang-format on
92};
93
98{
99 wchar_t *buffer;
100 wchar_t *line;
101 wchar_t *param;
102 size_t buf_len;
103 size_t line_len;
104 size_t line_used;
105 size_t line_max;
107 size_t word_len;
108 size_t buf_used;
110 size_t param_len;
113 struct State *state;
114};
115
120static void enriched_wrap(struct EnrichedState *enriched)
121{
122 if (!enriched)
123 return;
124
125 int x;
126
127 if (enriched->line_len)
128 {
129 if (enriched->tag_level[RICH_CENTER] || enriched->tag_level[RICH_FLUSHRIGHT])
130 {
131 /* Strip trailing white space */
132 size_t y = enriched->line_used - 1;
133
134 while (y && iswspace(enriched->line[y]))
135 {
136 enriched->line[y] = (wchar_t) '\0';
137 y--;
138 enriched->line_used--;
139 enriched->line_len--;
140 }
141 if (enriched->tag_level[RICH_CENTER])
142 {
143 /* Strip leading whitespace */
144 y = 0;
145
146 while (enriched->line[y] && iswspace(enriched->line[y]))
147 y++;
148 if (y)
149 {
150 for (size_t z = y; z <= enriched->line_used; z++)
151 {
152 enriched->line[z - y] = enriched->line[z];
153 }
154
155 enriched->line_len -= y;
156 enriched->line_used -= y;
157 }
158 }
159 }
160
161 const int extra = enriched->wrap_margin - enriched->line_len - enriched->indent_len -
163 if (extra > 0)
164 {
165 if (enriched->tag_level[RICH_CENTER])
166 {
167 x = extra / 2;
168 while (x)
169 {
170 state_putc(enriched->state, ' ');
171 x--;
172 }
173 }
174 else if (enriched->tag_level[RICH_FLUSHRIGHT])
175 {
176 x = extra - 1;
177 while (x)
178 {
179 state_putc(enriched->state, ' ');
180 x--;
181 }
182 }
183 }
184 state_putws(enriched->state, (const wchar_t *) enriched->line);
185 }
186
187 state_putc(enriched->state, '\n');
188 enriched->line[0] = (wchar_t) '\0';
189 enriched->line_len = 0;
190 enriched->line_used = 0;
191 enriched->indent_len = 0;
192 if (enriched->state->prefix)
193 {
194 state_puts(enriched->state, enriched->state->prefix);
195 enriched->indent_len += mutt_str_len(enriched->state->prefix);
196 }
197
198 if (enriched->tag_level[RICH_EXCERPT])
199 {
200 x = enriched->tag_level[RICH_EXCERPT];
201 while (x)
202 {
203 if (enriched->state->prefix)
204 {
205 state_puts(enriched->state, enriched->state->prefix);
206 enriched->indent_len += mutt_str_len(enriched->state->prefix);
207 }
208 else
209 {
210 state_puts(enriched->state, "> ");
211 enriched->indent_len += mutt_str_len("> ");
212 }
213 x--;
214 }
215 }
216 else
217 {
218 enriched->indent_len = 0;
219 }
220 if (enriched->tag_level[RICH_INDENT])
221 {
222 x = enriched->tag_level[RICH_INDENT] * INDENT_SIZE;
223 enriched->indent_len += x;
224 while (x)
225 {
226 state_putc(enriched->state, ' ');
227 x--;
228 }
229 }
230}
231
237static void enriched_flush(struct EnrichedState *enriched, bool wrap)
238{
239 if (!enriched || !enriched->buffer)
240 return;
241
242 if (!enriched->tag_level[RICH_NOFILL] &&
243 ((enriched->line_len + enriched->word_len) >
244 (enriched->wrap_margin - (enriched->tag_level[RICH_INDENT_RIGHT] * INDENT_SIZE) -
245 enriched->indent_len)))
246 {
247 enriched_wrap(enriched);
248 }
249
250 if (enriched->buf_used)
251 {
252 enriched->buffer[enriched->buf_used] = (wchar_t) '\0';
253 enriched->line_used += enriched->buf_used;
254 if (enriched->line_used > enriched->line_max)
255 {
256 enriched->line_max = enriched->line_used;
257 MUTT_MEM_REALLOC(&enriched->line, enriched->line_max + 1, wchar_t);
258 }
259 wcscat(enriched->line, enriched->buffer);
260 enriched->line_len += enriched->word_len;
261 enriched->word_len = 0;
262 enriched->buf_used = 0;
263 }
264 if (wrap)
265 enriched_wrap(enriched);
266 fflush(enriched->state->fp_out);
267}
268
274static void enriched_putwc(wchar_t c, struct EnrichedState *enriched)
275{
276 if (!enriched)
277 return;
278
279 if (enriched->tag_level[RICH_PARAM])
280 {
281 if (enriched->tag_level[RICH_COLOR])
282 {
283 if ((enriched->param_used + 1) >= enriched->param_len)
284 {
285 enriched->param_len += 256;
286 MUTT_MEM_REALLOC(&enriched->param, enriched->param_len, wchar_t);
287 }
288
289 enriched->param[enriched->param_used++] = c;
290 }
291 return; /* nothing to do */
292 }
293
294 /* see if more space is needed (plus extra for possible rich characters) */
295 if ((enriched->buf_len < (enriched->buf_used + 3)) || !enriched->buffer)
296 {
297 enriched->buf_len += 1024;
298 MUTT_MEM_REALLOC(&enriched->buffer, enriched->buf_len + 1, wchar_t);
299 }
300
301 if ((!enriched->tag_level[RICH_NOFILL] && iswspace(c)) || (c == (wchar_t) '\0'))
302 {
303 if (c == (wchar_t) '\t')
304 enriched->word_len += 8 - (enriched->line_len + enriched->word_len) % 8;
305 else
306 enriched->word_len++;
307
308 enriched->buffer[enriched->buf_used++] = c;
309 enriched_flush(enriched, false);
310 }
311 else
312 {
313 if (enriched->state->flags & STATE_DISPLAY)
314 {
315 if (enriched->tag_level[RICH_BOLD])
316 {
317 enriched->buffer[enriched->buf_used++] = c;
318 enriched->buffer[enriched->buf_used++] = (wchar_t) '\010'; // Ctrl-H (backspace)
319 enriched->buffer[enriched->buf_used++] = c;
320 }
321 else if (enriched->tag_level[RICH_UNDERLINE])
322 {
323 enriched->buffer[enriched->buf_used++] = '_';
324 enriched->buffer[enriched->buf_used++] = (wchar_t) '\010'; // Ctrl-H (backspace)
325 enriched->buffer[enriched->buf_used++] = c;
326 }
327 else if (enriched->tag_level[RICH_ITALIC])
328 {
329 enriched->buffer[enriched->buf_used++] = c;
330 enriched->buffer[enriched->buf_used++] = (wchar_t) '\010'; // Ctrl-H (backspace)
331 enriched->buffer[enriched->buf_used++] = '_';
332 }
333 else
334 {
335 enriched->buffer[enriched->buf_used++] = c;
336 }
337 }
338 else
339 {
340 enriched->buffer[enriched->buf_used++] = c;
341 }
342 enriched->word_len++;
343 }
344}
345
351static void enriched_puts(const char *s, struct EnrichedState *enriched)
352{
353 if (!enriched)
354 return;
355
356 const char *c = NULL;
357
358 if ((enriched->buf_len < (enriched->buf_used + mutt_str_len(s))) || !enriched->buffer)
359 {
360 enriched->buf_len += 1024;
361 MUTT_MEM_REALLOC(&enriched->buffer, enriched->buf_len + 1, wchar_t);
362 }
363 c = s;
364 while (*c)
365 {
366 enriched->buffer[enriched->buf_used++] = (wchar_t) *c;
367 c++;
368 }
369}
370
376static void enriched_set_flags(const wchar_t *tag, struct EnrichedState *enriched)
377{
378 if (!enriched)
379 return;
380
381 const wchar_t *tagptr = tag;
382 int i, j;
383
384 if (*tagptr == (wchar_t) '/')
385 tagptr++;
386
387 for (i = 0, j = -1; EnrichedTags[i].tag_name; i++)
388 {
389 if (wcscasecmp(EnrichedTags[i].tag_name, tagptr) == 0)
390 {
391 j = EnrichedTags[i].index;
392 break;
393 }
394 }
395
396 if (j != -1)
397 {
398 if ((j == RICH_CENTER) || (j == RICH_FLUSHLEFT) || (j == RICH_FLUSHRIGHT))
399 enriched_flush(enriched, true);
400
401 if (*tag == (wchar_t) '/')
402 {
403 if (enriched->tag_level[j]) /* make sure not to go negative */
404 enriched->tag_level[j]--;
405 if ((enriched->state->flags & STATE_DISPLAY) && (j == RICH_PARAM) &&
406 enriched->tag_level[RICH_COLOR])
407 {
408 enriched->param[enriched->param_used] = (wchar_t) '\0';
409 if (wcscasecmp(L"black", enriched->param) == 0)
410 {
411 enriched_puts("\033[30m", enriched); // Escape
412 }
413 else if (wcscasecmp(L"red", enriched->param) == 0)
414 {
415 enriched_puts("\033[31m", enriched); // Escape
416 }
417 else if (wcscasecmp(L"green", enriched->param) == 0)
418 {
419 enriched_puts("\033[32m", enriched); // Escape
420 }
421 else if (wcscasecmp(L"yellow", enriched->param) == 0)
422 {
423 enriched_puts("\033[33m", enriched); // Escape
424 }
425 else if (wcscasecmp(L"blue", enriched->param) == 0)
426 {
427 enriched_puts("\033[34m", enriched); // Escape
428 }
429 else if (wcscasecmp(L"magenta", enriched->param) == 0)
430 {
431 enriched_puts("\033[35m", enriched); // Escape
432 }
433 else if (wcscasecmp(L"cyan", enriched->param) == 0)
434 {
435 enriched_puts("\033[36m", enriched); // Escape
436 }
437 else if (wcscasecmp(L"white", enriched->param) == 0)
438 {
439 enriched_puts("\033[37m", enriched); // Escape
440 }
441 }
442 if ((enriched->state->flags & STATE_DISPLAY) && (j == RICH_COLOR))
443 {
444 enriched_puts("\033[0m", enriched); // Escape
445 }
446
447 /* flush parameter buffer when closing the tag */
448 if (j == RICH_PARAM)
449 {
450 enriched->param_used = 0;
451 enriched->param[0] = (wchar_t) '\0';
452 }
453 }
454 else
455 {
456 enriched->tag_level[j]++;
457 }
458
459 if (j == RICH_EXCERPT)
460 enriched_flush(enriched, true);
461 }
462}
463
468int text_enriched_handler(struct Body *b_email, struct State *state)
469{
470 enum
471 {
472 TEXT,
473 LANGLE,
474 TAG,
475 BOGUS_TAG,
476 NEWLINE,
477 ST_EOF,
478 DONE
479 } text_state = TEXT;
480
481 long bytes = b_email->length;
482 struct EnrichedState enriched = { 0 };
483 wint_t wc = 0;
484 int tag_len = 0;
485 wchar_t tag[1024 + 1];
486
487 enriched.state = state;
488 enriched.wrap_margin = ((state->wraplen > 4) &&
489 ((state->flags & STATE_DISPLAY) || (state->wraplen < 76))) ?
490 state->wraplen - 4 :
491 72;
492 enriched.line_max = enriched.wrap_margin * 4;
493 enriched.line = MUTT_MEM_CALLOC(enriched.line_max + 1, wchar_t);
494 enriched.param = MUTT_MEM_CALLOC(256, wchar_t);
495
496 enriched.param_len = 256;
497 enriched.param_used = 0;
498
499 if (state->prefix)
500 {
502 enriched.indent_len += mutt_str_len(state->prefix);
503 }
504
505 while (text_state != DONE)
506 {
507 if (text_state != ST_EOF)
508 {
509 if (!bytes || ((wc = fgetwc(state->fp_in)) == WEOF))
510 text_state = ST_EOF;
511 else
512 bytes--;
513 }
514
515 switch (text_state)
516 {
517 case TEXT:
518 switch (wc)
519 {
520 case '<':
521 text_state = LANGLE;
522 break;
523
524 case '\n':
525 if (enriched.tag_level[RICH_NOFILL])
526 {
527 enriched_flush(&enriched, true);
528 }
529 else
530 {
531 enriched_putwc((wchar_t) ' ', &enriched);
532 text_state = NEWLINE;
533 }
534 break;
535
536 default:
537 enriched_putwc(wc, &enriched);
538 }
539 break;
540
541 case LANGLE:
542 if (wc == (wchar_t) '<')
543 {
544 enriched_putwc(wc, &enriched);
545 text_state = TEXT;
546 break;
547 }
548 else
549 {
550 tag_len = 0;
551 text_state = TAG;
552 }
553 /* Yes, (it wasn't a <<, so this char is first in TAG) */
555
556 case TAG:
557 if (wc == (wchar_t) '>')
558 {
559 tag[tag_len] = (wchar_t) '\0';
560 enriched_set_flags(tag, &enriched);
561 text_state = TEXT;
562 }
563 else if (tag_len < 1024) /* ignore overly long tags */
564 {
565 tag[tag_len++] = wc;
566 }
567 else
568 {
569 text_state = BOGUS_TAG;
570 }
571 break;
572
573 case BOGUS_TAG:
574 if (wc == (wchar_t) '>')
575 text_state = TEXT;
576 break;
577
578 case NEWLINE:
579 if (wc == (wchar_t) '\n')
580 {
581 enriched_flush(&enriched, true);
582 }
583 else
584 {
585 ungetwc(wc, state->fp_in);
586 bytes++;
587 text_state = TEXT;
588 }
589 break;
590
591 case ST_EOF:
592 enriched_putwc((wchar_t) '\0', &enriched);
593 enriched_flush(&enriched, true);
594 text_state = DONE;
595 break;
596
597 default:
598 /* not reached */
599 break;
600 }
601 }
602
603 state_putc(state, '\n'); /* add a final newline */
604
605 FREE(&(enriched.buffer));
606 FREE(&(enriched.line));
607 FREE(&(enriched.param));
608
609 return 0;
610}
Structs that make up an email.
static void enriched_set_flags(const wchar_t *tag, struct EnrichedState *enriched)
Set flags on the enriched text state.
Definition enriched.c:376
static void enriched_wrap(struct EnrichedState *enriched)
Wrap enriched text.
Definition enriched.c:120
RichAttribs
Rich text attributes.
Definition enriched.c:48
@ RICH_FLUSHRIGHT
Right-justified text.
Definition enriched.c:59
@ RICH_NOFILL
Text will not be reformatted.
Definition enriched.c:53
@ RICH_COLOR
Coloured text.
Definition enriched.c:60
@ RICH_PARAM
Parameter label.
Definition enriched.c:49
@ RICH_INDENT
Indented text.
Definition enriched.c:54
@ RICH_BOLD
Bold text.
Definition enriched.c:50
@ RICH_CENTER
Centred text.
Definition enriched.c:57
@ RICH_ITALIC
Italic text.
Definition enriched.c:52
@ RICH_UNDERLINE
Underlined text.
Definition enriched.c:51
@ RICH_EXCERPT
Excerpt text.
Definition enriched.c:56
@ RICH_INDENT_RIGHT
Right-indented text.
Definition enriched.c:55
@ RICH_MAX
Definition enriched.c:61
@ RICH_FLUSHLEFT
Left-justified text.
Definition enriched.c:58
static void enriched_putwc(wchar_t c, struct EnrichedState *enriched)
Write one wide character to the state.
Definition enriched.c:274
static const struct Etags EnrichedTags[]
EnrichedTags - Lookup table of tags allowed in enriched text.
Definition enriched.c:74
#define INDENT_SIZE
A (not so) minimal implementation of RFC1563.
Definition enriched.c:42
static void enriched_flush(struct EnrichedState *enriched, bool wrap)
Write enriched text to the State.
Definition enriched.c:237
static void enriched_puts(const char *s, struct EnrichedState *enriched)
Write an enriched text string to the State.
Definition enriched.c:351
Rich text handler.
int text_enriched_handler(struct Body *b_email, struct State *state)
Handler for enriched text - Implements handler_t -.
Definition enriched.c:468
#define FREE(x)
Definition memory.h:62
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:47
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition memory.h:50
Convenience wrapper for the library headers.
#define FALLTHROUGH
Definition lib.h:113
int state_putws(struct State *state, const wchar_t *ws)
Write a wide string to the state.
Definition state.c:147
#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
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:498
int wcscasecmp(const wchar_t *a, const wchar_t *b)
Compare two wide-character strings, ignoring case.
Definition wcscasecmp.c:41
The body of an email.
Definition body.h:36
LOFF_T length
length (in bytes) of attachment
Definition body.h:53
State of enriched-text parser.
Definition enriched.c:98
wchar_t * buffer
Definition enriched.c:99
size_t buf_len
Definition enriched.c:102
size_t param_used
Definition enriched.c:109
wchar_t * param
Definition enriched.c:101
wchar_t * line
Definition enriched.c:100
int tag_level[RICH_MAX]
Definition enriched.c:111
size_t buf_used
Definition enriched.c:108
size_t line_used
Definition enriched.c:104
struct State * state
Definition enriched.c:113
size_t word_len
Definition enriched.c:107
size_t param_len
Definition enriched.c:110
size_t line_len
Definition enriched.c:103
size_t line_max
Definition enriched.c:105
size_t indent_len
Definition enriched.c:106
Enriched text tags.
Definition enriched.c:68
int index
Index number.
Definition enriched.c:70
const wchar_t * tag_name
Tag name.
Definition enriched.c:69
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_out
File to write to.
Definition state.h:50
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