NeoMutt  2025-12-11-980-ge38c27
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
rfc2231.c
Go to the documentation of this file.
1
25
37
38#include "config.h"
39#include <limits.h>
40#include <stdbool.h>
41#include <string.h>
42#include "mutt/lib.h"
43#include "config/lib.h"
44#include "core/lib.h"
45#include "rfc2231.h"
46#include "mime.h"
47#include "parameter.h"
48#include "rfc2047.h"
49
54{
55 char *attribute;
56 char *value;
57 int index;
58 bool encoded;
60};
61
66static void purge_empty_parameters(struct ParameterList *pl)
67{
68 struct Parameter *np = NULL;
69 struct Parameter *tmp = NULL;
70 TAILQ_FOREACH_SAFE(np, pl, entries, tmp)
71 {
72 if (!np->attribute || !np->value)
73 {
74 TAILQ_REMOVE(pl, np, entries);
76 }
77 }
78}
79
87static char *get_charset(char *value, char *charset, size_t chslen)
88{
89 char *t = strchr(value, '\'');
90 if (!t)
91 {
92 charset[0] = '\0';
93 return value;
94 }
95
96 *t = '\0';
97 mutt_str_copy(charset, value, chslen);
98
99 char *u = strchr(t + 1, '\'');
100 if (u)
101 return u + 1;
102 return t + 1;
103}
104
110static void decode_one(char *dest, char *src)
111{
112 char *d = NULL;
113
114 for (d = dest; *src; src++)
115 {
116 if ((src[0] == '%') && mutt_isxdigit(src[1]) && mutt_isxdigit(src[2]))
117 {
118 *d++ = (hexval(src[1]) << 4) | hexval(src[2]);
119 src += 2;
120 }
121 else
122 {
123 *d++ = *src;
124 }
125 }
126
127 *d = '\0';
128}
129
134static struct Rfc2231Parameter *parameter_new(void)
135{
136 return MUTT_MEM_CALLOC(1, struct Rfc2231Parameter);
137}
138
147static void list_insert(struct Rfc2231Parameter **list, struct Rfc2231Parameter *par)
148{
149 struct Rfc2231Parameter **last = list;
150 struct Rfc2231Parameter *p = *list;
151
152 while (p)
153 {
154 const int c = mutt_str_cmp(par->attribute, p->attribute);
155 if ((c < 0) || ((c == 0) && (par->index <= p->index)))
156 break;
157
158 last = &p->next;
159 p = p->next;
160 }
161
162 par->next = p;
163 *last = par;
164}
165
170static void parameter_free(struct Rfc2231Parameter **ptr)
171{
172 if (!ptr || !*ptr)
173 return;
174
175 struct Rfc2231Parameter *p = *ptr;
176 FREE(&p->attribute);
177 FREE(&p->value);
178
179 FREE(ptr);
180}
181
187static void join_continuations(struct ParameterList *pl, struct Rfc2231Parameter *par)
188{
189 char attribute[256] = { 0 };
190 char charset[256] = { 0 };
191
192 const char *const c_charset = cc_charset();
193 while (par)
194 {
195 char *value = NULL;
196 size_t l = 0;
197
199
200 const bool encoded = par->encoded;
201 char *valp = NULL;
202 if (encoded)
203 valp = get_charset(par->value, charset, sizeof(charset));
204 else
205 valp = par->value;
206
207 do
208 {
209 if (encoded && par->encoded)
210 decode_one(par->value, valp);
211
212 const size_t vl = strlen(par->value);
213
214 MUTT_MEM_REALLOC(&value, l + vl + 1, char);
215 strcpy(value + l, par->value);
216 l += vl;
217
218 struct Rfc2231Parameter *q = par->next;
219 parameter_free(&par);
220 par = q;
221 if (par)
222 valp = par->value;
223 } while (par && (mutt_str_equal(par->attribute, attribute)));
224
225 if (encoded)
226 {
229 }
230
231 struct Parameter *np = mutt_param_new();
233 np->value = value;
234 TAILQ_INSERT_HEAD(pl, np, entries);
235 }
236}
237
242void rfc2231_decode_parameters(struct ParameterList *pl)
243{
244 if (!pl)
245 return;
246
247 struct Rfc2231Parameter *conthead = NULL;
248 struct Rfc2231Parameter *conttmp = NULL;
249
250 char *s = NULL;
251 char *t = NULL;
252 char charset[256] = { 0 };
253
254 bool encoded;
255 int index = 0;
256 bool dirty = false; /* set when we may have created empty parameters. */
257
259
260 struct Parameter *np = NULL;
261 struct Parameter *tmp = NULL;
262 const bool c_rfc2047_parameters = cs_subset_bool(NeoMutt->sub, "rfc2047_parameters");
263 const struct Slist *c_assumed_charset = cc_assumed_charset();
264 const char *c_charset = cc_charset();
265
266 TAILQ_FOREACH_SAFE(np, pl, entries, tmp)
267 {
268 s = strchr(np->attribute, '*');
269 if (!s)
270 {
271 /* Single value, non encoded:
272 * attr=value
273 */
274 /* Using RFC2047 encoding in MIME parameters is explicitly
275 * forbidden by that document. Nevertheless, it's being
276 * generated by some software, including certain Lotus Notes to
277 * Internet Gateways. So we actually decode it. */
278
279 if (c_rfc2047_parameters && np->value && strstr(np->value, "=?"))
280 {
281 rfc2047_decode(&np->value);
282 }
283 else if (!slist_is_empty(c_assumed_charset))
284 {
285 mutt_ch_convert_nonmime_string(c_assumed_charset, c_charset, &np->value);
286 }
287 }
288 else if (s[1] == '\0')
289 {
290 /* Single value with encoding:
291 * attr*=us-ascii''the%20value
292 */
293 s[0] = '\0';
294
295 s = get_charset(np->value, charset, sizeof(charset));
296 decode_one(np->value, s);
297 mutt_ch_convert_string(&np->value, charset, c_charset, MUTT_ICONV_HOOK_FROM);
299 dirty = true;
300 }
301 else
302 {
303 /* A parameter continuation, which may or may not be encoded:
304 * attr*0=value
305 * -or-
306 * attr*0*=us-ascii''the%20value
307 */
308 s[0] = '\0';
309 s++; /* let s point to the first character of index. */
310 for (t = s; (t[0] != '\0') && mutt_isdigit(t[0]); t++)
311 ; // do nothing
312
313 encoded = (t[0] == '*');
314 t[0] = '\0';
315
316 /* RFC2231 says that the index starts at 0 and increments by 1,
317 * thus an overflow should never occur in a valid message, thus
318 * the value INT_MAX in case of overflow does not really matter
319 * (the goal is just to avoid undefined behaviour). */
320 if (!mutt_str_atoi_full(s, &index))
321 index = INT_MAX;
322
323 conttmp = parameter_new();
324 conttmp->attribute = np->attribute;
325 conttmp->value = np->value;
326 conttmp->encoded = encoded;
327 conttmp->index = index;
328
329 np->attribute = NULL;
330 np->value = NULL;
331 TAILQ_REMOVE(pl, np, entries);
332 FREE(&np);
333
334 list_insert(&conthead, conttmp);
335 }
336 }
337
338 if (conthead)
339 {
340 join_continuations(pl, conthead);
341 dirty = true;
342 }
343
344 if (dirty)
346}
347
357size_t rfc2231_encode_string(struct ParameterList *head, const char *attribute, char *value)
358{
359 if (!attribute || !value)
360 return 0;
361
362 size_t count = 0;
363 bool encode = false;
364 bool add_quotes = false;
365 bool free_src_value = false;
366 bool split = false;
367 int continuation_number = 0;
368 size_t dest_value_len = 0;
369 size_t max_value_len = 0;
370 size_t cur_value_len = 0;
371 char *cur = NULL;
372 char *charset = NULL;
373 char *src_value = NULL;
374 struct Parameter *current = NULL;
375
376 struct Buffer *cur_attribute = buf_pool_get();
377 struct Buffer *cur_value = buf_pool_get();
378
379 // Perform charset conversion
380 for (cur = value; *cur; cur++)
381 {
382 if ((*cur < 0x20) || (*cur >= 0x7f))
383 {
384 encode = true;
385 break;
386 }
387 }
388
389 if (encode)
390 {
391 const struct Slist *const c_send_charset = cs_subset_slist(NeoMutt->sub, "send_charset");
392 const char *c_charset = cc_charset();
393 if (c_charset && c_send_charset)
394 {
395 charset = mutt_ch_choose(c_charset, c_send_charset, value,
396 mutt_str_len(value), &src_value, NULL);
397 }
398 if (src_value)
399 free_src_value = true;
400 if (!charset)
401 charset = mutt_str_dup(c_charset ? c_charset : "unknown-8bit");
402 }
403 if (!src_value)
404 src_value = value;
405
406 // Count the size the resultant value will need in total
407 if (encode)
408 dest_value_len = mutt_str_len(charset) + 2; /* charset'' prefix */
409
410 for (cur = src_value; *cur; cur++)
411 {
412 dest_value_len++;
413
414 if (encode)
415 {
416 /* These get converted to %xx so need a total of three chars */
417 if ((*cur < 0x20) || (*cur >= 0x7f) || strchr(MimeSpecials, *cur) ||
418 strchr("*'%", *cur))
419 {
420 dest_value_len += 2;
421 }
422 }
423 else
424 {
425 /* rfc822_cat() will add outer quotes if it finds MimeSpecials. */
426 if (!add_quotes && strchr(MimeSpecials, *cur))
427 add_quotes = true;
428 /* rfc822_cat() will add a backslash if it finds '\' or '"'. */
429 if ((*cur == '\\') || (*cur == '"'))
430 dest_value_len++;
431 }
432 }
433
434 // Determine if need to split into parameter value continuations
435 max_value_len = 78 - // rfc suggested line length
436 1 - // Leading tab on continuation line
437 mutt_str_len(attribute) - // attribute
438 (encode ? 1 : 0) - // '*' encoding marker
439 1 - // '='
440 (add_quotes ? 2 : 0) - // "...."
441 1; // ';'
442
443 if (max_value_len < 30)
444 max_value_len = 30;
445
446 if (dest_value_len > max_value_len)
447 {
448 split = true;
449 max_value_len -= 4; /* '*n' continuation number and extra encoding
450 * space to keep loop below simpler */
451 }
452
453 // Generate list of parameter continuations
454 cur = src_value;
455 if (encode)
456 {
457 buf_printf(cur_value, "%s''", charset);
458 cur_value_len = buf_len(cur_value);
459 }
460
461 while (*cur)
462 {
463 current = mutt_param_new();
464 TAILQ_INSERT_TAIL(head, current, entries);
465 count++;
466
467 buf_strcpy(cur_attribute, attribute);
468 if (split)
469 buf_add_printf(cur_attribute, "*%d", continuation_number++);
470 if (encode)
471 buf_addch(cur_attribute, '*');
472
473 while (*cur && (!split || (cur_value_len < max_value_len)))
474 {
475 if (encode)
476 {
477 if ((*cur < 0x20) || (*cur >= 0x7f) || strchr(MimeSpecials, *cur) ||
478 strchr("*'%", *cur))
479 {
480 buf_add_printf(cur_value, "%%%02X", (unsigned char) *cur);
481 cur_value_len += 3;
482 }
483 else
484 {
485 buf_addch(cur_value, *cur);
486 cur_value_len++;
487 }
488 }
489 else
490 {
491 buf_addch(cur_value, *cur);
492 cur_value_len++;
493 if ((*cur == '\\') || (*cur == '"'))
494 cur_value_len++;
495 }
496
497 cur++;
498 }
499
500 current->attribute = buf_strdup(cur_attribute);
501 current->value = buf_strdup(cur_value);
502
503 buf_reset(cur_value);
504 cur_value_len = 0;
505 }
506
507 buf_pool_release(&cur_attribute);
508 buf_pool_release(&cur_value);
509
510 FREE(&charset);
511 if (free_src_value)
512 FREE(&src_value);
513
514 return count;
515}
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition buffer.c:168
int buf_add_printf(struct Buffer *buf, const char *fmt,...)
Format a string appending a Buffer.
Definition buffer.c:211
size_t buf_len(const struct Buffer *buf)
Calculate the length of a Buffer.
Definition buffer.c:497
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition buffer.c:89
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition buffer.c:248
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition buffer.c:401
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition buffer.c:577
const struct Slist * cs_subset_slist(const struct ConfigSubset *sub, const char *name)
Get a string-list config item by name.
Definition helpers.c:242
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.
const char * cc_charset(void)
Get the cached value of $charset.
const struct Slist * cc_assumed_charset(void)
Get the cached value of $assumed_charset.
Convenience wrapper for the core headers.
bool mutt_isxdigit(int arg)
Wrapper for isxdigit(3)
Definition ctype.c:111
bool mutt_isdigit(int arg)
Wrapper for isdigit(3)
Definition ctype.c:66
int mutt_mb_filter_unprintable(char **s)
Replace unprintable characters.
Definition mbyte.c:424
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:52
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition memory.h:55
const char MimeSpecials[]
Characters that need special treatment in MIME.
Definition mime.c:69
Constants and macros for managing MIME encoding.
#define hexval(ch)
Convert hexadecimal character to its integer value.
Definition mime.h:82
char * mutt_ch_choose(const char *fromcode, const struct Slist *charsets, const char *u, size_t ulen, char **d, size_t *dlen)
Figure the best charset to encode a string.
Definition charset.c:1096
int mutt_ch_convert_nonmime_string(const struct Slist *const assumed_charset, const char *charset, char **ps)
Try to convert a string using a list of character sets.
Definition charset.c:318
int mutt_ch_convert_string(char **ps, const char *from, const char *to, uint8_t flags)
Convert a string between encodings.
Definition charset.c:819
#define MUTT_ICONV_HOOK_FROM
apply charset-hooks to fromcode
Definition charset.h:67
Convenience wrapper for the library headers.
bool slist_is_empty(const struct Slist *list)
Is the slist empty?
Definition slist.c:140
int mutt_str_cmp(const char *a, const char *b)
Compare two strings, safely.
Definition string.c:403
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:257
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:666
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:503
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition string.c:587
void mutt_param_free_one(struct Parameter **p)
Free a Parameter.
Definition parameter.c:49
struct Parameter * mutt_param_new(void)
Create a new Parameter.
Definition parameter.c:40
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
#define TAILQ_FOREACH_SAFE(var, head, field, tvar)
Definition queue.h:792
#define TAILQ_INSERT_TAIL(head, elm, field)
Definition queue.h:866
#define TAILQ_REMOVE(head, elm, field)
Definition queue.h:901
#define TAILQ_INSERT_HEAD(head, elm, field)
Definition queue.h:853
void rfc2047_decode(char **pd)
Decode any RFC2047-encoded header fields.
Definition rfc2047.c:679
static int encode(const char *d, size_t dlen, int col, const char *fromcode, const struct Slist *charsets, char **e, size_t *elen, const char *specials)
RFC2047-encode a string.
Definition rfc2047.c:438
RFC2047 MIME extensions encoding / decoding routines.
static void purge_empty_parameters(struct ParameterList *pl)
Remove any ill-formed Parameters from a list.
Definition rfc2231.c:66
static void join_continuations(struct ParameterList *pl, struct Rfc2231Parameter *par)
Process continuation parameters.
Definition rfc2231.c:187
static char * get_charset(char *value, char *charset, size_t chslen)
Get the charset from an RFC2231 header.
Definition rfc2231.c:87
static void parameter_free(struct Rfc2231Parameter **ptr)
Free an Rfc2231Parameter.
Definition rfc2231.c:170
static void list_insert(struct Rfc2231Parameter **list, struct Rfc2231Parameter *par)
Insert parameter into an ordered list.
Definition rfc2231.c:147
void rfc2231_decode_parameters(struct ParameterList *pl)
Decode a Parameter list.
Definition rfc2231.c:242
static struct Rfc2231Parameter * parameter_new(void)
Create a new Rfc2231Parameter.
Definition rfc2231.c:134
size_t rfc2231_encode_string(struct ParameterList *head, const char *attribute, char *value)
Encode a string to be suitable for an RFC2231 header.
Definition rfc2231.c:357
static void decode_one(char *dest, char *src)
Decode one percent-encoded character.
Definition rfc2231.c:110
RFC2231 MIME Charset routines.
String manipulation buffer.
Definition buffer.h:36
Container for Accounts, Notifications.
Definition neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49
Attribute associated with a MIME part.
Definition parameter.h:33
char * attribute
Parameter name.
Definition parameter.h:34
char * value
Parameter value.
Definition parameter.h:35
MIME section parameter.
Definition rfc2231.c:54
bool encoded
Is the value encoded?
Definition rfc2231.c:58
int index
Index number in the list.
Definition rfc2231.c:57
char * value
Attribute value.
Definition rfc2231.c:56
struct Rfc2231Parameter * next
Linked list.
Definition rfc2231.c:59
char * attribute
Attribute name.
Definition rfc2231.c:55
String list.
Definition slist.h:37
struct ListHead head
List containing values.
Definition slist.h:38
size_t count
Number of values in list.
Definition slist.h:39