1 /* Operations on RFC-2231-compliant mail headers fields.
2    GNU Mailutils -- a suite of utilities for electronic mail
3    Copyright (C) 1999-2021 Free Software Foundation, Inc.
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 3 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General
16    Public License along with this library.  If not,
17    see <http://www.gnu.org/licenses/>. */
18 
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22 
23 #include <errno.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include <mailutils/cctype.h>
28 #include <mailutils/cstr.h>
29 #include <mailutils/errno.h>
30 #include <mailutils/message.h>
31 #include <mailutils/header.h>
32 #include <mailutils/stream.h>
33 #include <mailutils/mime.h>
34 #include <mailutils/filter.h>
35 #include <mailutils/util.h>
36 #include <mailutils/wordsplit.h>
37 #include <mailutils/assoc.h>
38 #include <mailutils/iterator.h>
39 #include <mailutils/diag.h>
40 #include <mailutils/nls.h>
41 
42 #define MU_MIMEHDR_MULTILINE 0x01  /* Parameter was multiline */
43 #define MU_MIMEHDR_CSINFO    0x02  /* Parameter contains charset/language
44 				      info */
45 
46 /* Free the memory allocated for mu_mime_param. */
47 void
mu_mime_param_free(struct mu_mime_param * p)48 mu_mime_param_free (struct mu_mime_param *p)
49 {
50   if (p)
51     {
52       free (p->lang);
53       free (p->cset);
54       free (p->value);
55       free (p);
56     }
57 }
58 
59 /* Treat ITEM as a pointer to struct mu_mime_param and reclaim all
60    memory associated with it.
61 
62    This is intended for use as a destroy_item method of assoc tables. */
63 static void
_mu_mime_param_free_item(void * item)64 _mu_mime_param_free_item (void *item)
65 {
66   mu_mime_param_free (item);
67 }
68 
69 /* Recode a string between two charsets.
70 
71    Input:
72      TEXT  - A string.
73      ICS   - Charset of TEXT.
74      OCS   - Charset to convert TEXT to.
75    Output:
76      PRESULT - On success, the pointer to the resulting string is stored here.
77 */
78 static int
_recode_string(char * text,const char * ics,const char * ocs,char ** presult)79 _recode_string (char *text, const char *ics, const char *ocs, char **presult)
80 {
81   mu_stream_t istr, ostr, cvt;
82   mu_off_t size;
83   char *decoded;
84   int rc;
85 
86   rc = mu_static_memory_stream_create (&istr, text, strlen (text));
87   if (rc)
88     return rc;
89   rc = mu_memory_stream_create (&ostr, 0);
90   if (rc)
91     return rc;
92   rc = mu_decode_filter (&cvt, istr, NULL, ics, ocs);
93   mu_stream_unref (istr);
94   if (rc)
95     {
96       mu_stream_unref (ostr);
97       return rc;
98     }
99   rc = mu_stream_copy (ostr, cvt, 0, &size);
100   mu_stream_unref (cvt);
101   if (rc)
102     {
103       mu_stream_unref (ostr);
104       return rc;
105     }
106 
107   decoded = malloc (size + 1);
108   if (!decoded)
109     {
110       mu_stream_unref (ostr);
111       return ENOMEM;
112     }
113 
114   mu_stream_seek (ostr, 0, MU_SEEK_SET, NULL);
115   rc = mu_stream_read (ostr, decoded, size, NULL);
116   mu_stream_unref (ostr);
117   if (rc)
118     free (decoded);
119   else
120     {
121       decoded[size] = 0;
122       *presult = decoded;
123     }
124   return rc;
125 }
126 
127 /* Structure for composing continued parameters.
128    See RFC 2231, Section 3, "Parameter Value Continuations" */
129 struct param_continuation
130 {
131   char *param_name;    /* Parameter name */
132   size_t param_length;        /* Length of param_name */
133   mu_stream_t param_value;    /* Its value (memory stream) */
134   int param_cind;             /* Expected continued parameter index. */
135   /* Language/character set information */
136   const char *param_lang;
137   const char *param_cset;
138 };
139 
140 static void
free_param_continuation(struct param_continuation * p)141 free_param_continuation (struct param_continuation *p)
142 {
143   free (p->param_name);
144   mu_stream_destroy (&p->param_value);
145   /* param_lang and param_cset are handled separately */
146   memset (p, 0, sizeof (*p));
147 }
148 
149 /* Auxiliary function to store the data collected in CONT into ASSOC.
150    If SUBSET is True, ASSOC is populated with empty mu_mime_param
151    structures. In this case data will be stored only if CONT->param_name
152    is already in ASSOC. If OUTCHARSET is not NULL, the value from
153    CONT->param_value will be recoded to that charset before storing it. */
154 static int
flush_param(struct param_continuation * cont,mu_assoc_t assoc,const char * outcharset)155 flush_param (struct param_continuation *cont, mu_assoc_t assoc,
156 	     const char *outcharset)
157 {
158   int rc;
159   struct mu_mime_param *param;
160   mu_off_t size;
161 
162   param = calloc (1, sizeof *param);
163   if (!param)
164     return errno;
165 
166   if (cont->param_lang)
167     {
168       param->lang = strdup (cont->param_lang);
169       if (!param->lang)
170 	{
171 	  mu_mime_param_free (param);
172 	  return ENOMEM;
173 	}
174     }
175   else
176     param->lang = NULL;
177 
178   if (outcharset || cont->param_cset)
179     {
180       param->cset = strdup (outcharset ? outcharset : cont->param_cset);
181       if (!param->cset)
182 	{
183 	  mu_mime_param_free (param);
184 	  return ENOMEM;
185 	}
186     }
187 
188   rc = mu_stream_size (cont->param_value, &size);
189   if (rc == 0)
190     {
191       param->value = malloc (size + 1);
192       if (!param->value)
193 	{
194 	  mu_mime_param_free (param);
195 	  rc = ENOMEM;
196 	}
197     }
198 
199   if (rc == 0)
200     {
201       rc = mu_stream_seek (cont->param_value, 0, MU_SEEK_SET, NULL);
202       if (rc == 0)
203 	rc = mu_stream_read (cont->param_value, param->value, size, NULL);
204       param->value[size] = 0;
205     }
206 
207   if (rc)
208     {
209       mu_mime_param_free (param);
210       return rc;
211     }
212 
213   if (cont->param_cset && outcharset &&
214       mu_c_strcasecmp (cont->param_cset, outcharset))
215     {
216       char *tmp;
217       rc = _recode_string (param->value, cont->param_cset, outcharset, &tmp);
218       free (param->value);
219       if (rc)
220 	{
221 	  mu_mime_param_free (param);
222 	  return rc;
223 	}
224       param->value = tmp;
225     }
226 
227   rc = mu_assoc_install (assoc, cont->param_name, param);
228   if (rc)
229     mu_mime_param_free (param);
230 
231   return rc;
232 }
233 
234 /* Create and initialize an empty associative array for parameters. */
235 int
mu_mime_param_assoc_create(mu_assoc_t * paramtab)236 mu_mime_param_assoc_create (mu_assoc_t *paramtab)
237 {
238   mu_assoc_t assoc;
239   int rc = mu_assoc_create (&assoc, MU_ASSOC_ICASE);
240   if (rc == 0)
241     mu_assoc_set_destroy_item (assoc, _mu_mime_param_free_item);
242   *paramtab = assoc;
243   return rc;
244 }
245 
246 /* Add an empty structure for the slot NAME in ASSOC. */
247 int
mu_mime_param_assoc_add(mu_assoc_t assoc,const char * name)248 mu_mime_param_assoc_add (mu_assoc_t assoc, const char *name)
249 {
250   return mu_assoc_install (assoc, name, NULL);
251 }
252 
253 static inline char *
getword(struct mu_wordsplit * ws,size_t * pi)254 getword (struct mu_wordsplit *ws, size_t *pi)
255 {
256   if (*pi == ws->ws_wordc)
257     return NULL;
258   return ws->ws_wordv[(*pi)++];
259 }
260 
261 static int
parse_param(struct mu_wordsplit * ws,size_t * pi,mu_assoc_t assoc,struct param_continuation * param_cont,const char * outcharset)262 parse_param (struct mu_wordsplit *ws, size_t *pi, mu_assoc_t assoc,
263 	     struct param_continuation *param_cont,
264 	     const char *outcharset)
265 {
266   size_t klen;
267   char *key;
268   char *val;
269   const char *lang = NULL;
270   const char *cset = NULL;
271   char *langp = NULL;
272   char *csetp = NULL;
273   char *p;
274   char *decoded;
275   int flags = 0;
276   struct mu_mime_param *param;
277   int rc;
278 
279   key = getword (ws, pi);
280   if (key == NULL)
281     return MU_ERR_USER0;
282 
283   if (strcmp (key, ";") == 0)
284     {
285       mu_assoc_tail_set_mark (assoc, 0);
286       /* Reportedly, some MUAs insert several semicolons */
287       do
288 	{
289 	  key = getword (ws, pi);
290 	  if (key == NULL)
291 	    return MU_ERR_USER0;
292 	}
293       while (strcmp (key, ";") == 0);
294     }
295   else
296     {
297       mu_debug (MU_DEBCAT_MIME, MU_DEBUG_TRACE0,
298 		(_("semicolon missing (found %s)"), key));
299       return MU_ERR_PARSE;
300     }
301 
302   p = strchr (key, '=');
303   if (p)
304     {
305       *p++ = 0;
306       if (*p)
307 	{
308 	  /* key=val */
309 	  val = p;
310 	}
311       else if ((val = getword (ws, pi)) == NULL)
312 	{
313 	  mu_debug (MU_DEBCAT_MIME, MU_DEBUG_TRACE0,
314 		    (_("missing parameter value")));
315 	  return MU_ERR_PARSE;
316 	}
317       /* key= WSP val */
318     }
319   else
320     {
321       p = getword (ws, pi);
322       if (p && p[0] == '=')
323 	{
324 	  if (p[1])
325 	    {
326 	      /* key WSP =val */
327 	      val = p + 1;
328 	    }
329 	  else if ((val = getword (ws, pi)) == NULL)
330 	    {
331 	      mu_debug (MU_DEBCAT_MIME, MU_DEBUG_TRACE0,
332 			(_("missing parameter value")));
333 	      return MU_ERR_PARSE;
334 	    }
335 	  /* key WSP = WSP val */
336 	}
337       else
338 	{
339 	  mu_debug (MU_DEBCAT_MIME, MU_DEBUG_TRACE0,
340 		    (_("missing = after parameter name")));
341 	  return MU_ERR_PARSE;
342 	}
343     }
344 
345   klen = strlen (key);
346   if (klen == 0)
347     /* Ignore empty parameter */
348     return 0;
349 
350   p = strchr (key, '*');
351   if (p)
352     {
353       /* It is a parameter value continuation (RFC 2231, Section 3)
354 	 or parameter value character set and language information
355 	 (ibid., Section 4). */
356       klen = p - key;
357       if (p[1])
358 	{
359 	  if (mu_isdigit (p[1]))
360 	    {
361 	      char *q;
362 	      unsigned long n = strtoul (p + 1, &q, 10);
363 
364 	      if (*q && *q != '*')
365 		{
366 		  mu_debug (MU_DEBCAT_MIME, MU_DEBUG_TRACE0,
367 			    (_("malformed parameter name %s"),
368 			     key));
369 		  return MU_ERR_PARSE;
370 		}
371 
372 	      if (n != param_cont->param_cind)
373 		{
374 		  mu_debug (MU_DEBCAT_MIME, MU_DEBUG_TRACE0,
375 			    (_("continuation index out of sequence in %s: "
376 			       "skipping"),
377 			     key));
378 		  return MU_ERR_PARSE;
379 		}
380 
381 	      if (n == 0)
382 		{
383 		  param_cont->param_name = malloc (klen + 1);
384 		  if (!param_cont->param_name)
385 		    return ENOMEM;
386 		  param_cont->param_length = klen;
387 		  memcpy (param_cont->param_name, key, klen);
388 		  param_cont->param_name[klen] = 0;
389 
390 		  rc = mu_memory_stream_create (&param_cont->param_value,
391 						MU_STREAM_RDWR);
392 		  if (rc)
393 		    return rc;
394 		}
395 	      else if (param_cont->param_length != klen ||
396 		       memcmp (param_cont->param_name, key, klen))
397 		{
398 		  mu_debug (MU_DEBCAT_MIME, MU_DEBUG_TRACE0,
399 			    (_("continuation name mismatch: %s"), key));
400 		  return MU_ERR_PARSE;
401 		}
402 
403 	      if (*q == '*')
404 		flags |= MU_MIMEHDR_CSINFO;
405 
406 	      param_cont->param_cind++;
407 	      flags |= MU_MIMEHDR_MULTILINE;
408 	    }
409 	}
410       else
411 	{
412 	  flags |= MU_MIMEHDR_CSINFO;
413 	  *p = 0;
414 	}
415     }
416   else if (param_cont->param_name)
417     {
418       rc = flush_param (param_cont, assoc, outcharset);
419       free_param_continuation (param_cont);
420       if (rc)
421 	return rc;
422     }
423 
424   if (flags & MU_MIMEHDR_CSINFO)
425     {
426       p = strchr (val, '\'');
427       if (p)
428 	{
429 	  char *q = strchr (p + 1, '\'');
430 	  if (q)
431 	    {
432 	      cset = val;
433 	      *p++ = 0;
434 	      lang = p;
435 	      *q++ = 0;
436 	      val = q;
437 	    }
438 	}
439 
440       if ((flags & MU_MIMEHDR_MULTILINE) && param_cont->param_cind == 1)
441 	{
442 	  param_cont->param_lang = lang;
443 	  param_cont->param_cset = cset;
444 	}
445     }
446 
447   if (flags & MU_MIMEHDR_CSINFO)
448     {
449       char *tmp;
450 
451       rc = mu_str_url_decode (&tmp, val);
452       if (rc)
453 	return rc;
454       if (!(flags & MU_MIMEHDR_MULTILINE))
455 	{
456 	  if (!outcharset || mu_c_strcasecmp (cset, outcharset) == 0)
457 	    decoded = tmp;
458 	  else
459 	    {
460 	      rc = _recode_string (tmp, cset, outcharset, &decoded);
461 	      free (tmp);
462 	      if (rc)
463 		return rc;
464 	    }
465 	}
466       else
467 	decoded = tmp;
468     }
469   else
470     {
471       struct mu_mime_param *param;
472       rc = mu_rfc2047_decode_param (outcharset, val, &param);
473       if (rc)
474 	return rc;
475       cset = csetp = param->cset;
476       lang = langp = param->lang;
477       decoded = param->value;
478       free (param);
479     }
480   val = decoded;
481 
482   if (flags & MU_MIMEHDR_MULTILINE)
483     {
484       rc = mu_stream_write (param_cont->param_value, val, strlen (val), NULL);
485       free (decoded);
486       free (csetp);
487       free (langp);
488       return rc;
489     }
490 
491   param = calloc (1, sizeof (*param));
492   if (!param)
493     rc = ENOMEM;
494   else
495     {
496       if (lang)
497 	{
498 	  param->lang = strdup (lang);
499 	  if (!param->lang)
500 	    rc = ENOMEM;
501 	}
502 
503       if (rc == 0 && cset)
504 	{
505 	  param->cset = strdup (cset);
506 	  if (!param->cset)
507 	    {
508 	      free (param->lang);
509 	      rc = ENOMEM;
510 	    }
511 	}
512 
513       free (csetp);
514       free (langp);
515     }
516 
517   if (rc)
518     {
519       free (decoded);
520       return rc;
521     }
522 
523   param->value = strdup (val);
524   free (decoded);
525   if (!param->value)
526     {
527       mu_mime_param_free (param);
528       return ENOMEM;
529     }
530 
531   rc = mu_assoc_install (assoc, key, param);
532   switch (rc)
533     {
534     case 0:
535       break;
536 
537     case MU_ERR_EXISTS:
538       mu_debug (MU_DEBCAT_MIME, MU_DEBUG_ERROR,
539 		("MIME parameter %s duplicated", key));
540       break;
541 
542     default:
543       mu_mime_param_free (param);
544       return rc;
545     }
546   mu_assoc_tail_set_mark (assoc, 1);
547 
548   return 0;
549 }
550 
551 
552 /* A working horse of this module.  Parses input string, which should
553    be a header field value complying to RFCs 2045, 2183, 2231.3.
554 
555    Input:
556     TEXT   - The string.
557     ASSOC  - Associative array of parameters indexed by their names.
558     SUBSET - If true, store only those parameters that are already
559              in ASSOC.
560    Output:
561     PVALUE - Unless NULL, a pointer to the field value is stored here on
562              success.
563     ASSOC  - Unless NULL, parameters are stored here.
564 
565    Both output pointers can be NULL, meaning that the corresponding data
566    are of no interest to the caller.
567 
568    The value returned in PVALUE is the initial part of TEXT up to the
569    start of parameters (i.e. to the first semicolon) with leading and
570    trailing whitespace removed.  No other syntactic checking is done on
571    the value.  It is the responsibility of the caller to verify that it
572    complies to the syntax of the particular header.
573 */
574 static int
_mime_header_parse(const char * text,char ** pvalue,mu_assoc_t assoc,const char * outcharset,mu_assoc_t subset)575 _mime_header_parse (const char *text, char **pvalue,
576 		    mu_assoc_t assoc, const char *outcharset,
577 		    mu_assoc_t subset)
578 {
579   int rc = 0;
580   struct mu_wordsplit ws;
581   struct param_continuation cont;
582   size_t i;
583   char *value = NULL;
584   size_t val_len;
585 
586   val_len = strcspn (text, ";");
587   if (pvalue)
588     {
589       value = malloc (val_len + 1);
590       if (!value)
591 	return ENOMEM;
592       memcpy (value, text, val_len);
593       value[val_len] = 0;
594       mu_rtrim_class (value, MU_CTYPE_SPACE);
595       mu_ltrim_class (value, MU_CTYPE_SPACE);
596       if (value[0] == 0)
597 	{
598 	  free (value);
599 	  return MU_ERR_PARSE;
600 	}
601     }
602 
603   text += val_len;
604 
605   ws.ws_delim = " \t\r\n;";
606   ws.ws_escape[0] = ws.ws_escape[1] = "\\\\\"\"";
607   ws.ws_options = 0;
608   MU_WRDSO_ESC_SET (&ws, 0, MU_WRDSO_BSKEEP);
609   MU_WRDSO_ESC_SET (&ws, 1, MU_WRDSO_BSKEEP);
610   if (mu_wordsplit (text, &ws,
611 		    MU_WRDSF_DELIM | MU_WRDSF_ESCAPE |
612 		    MU_WRDSF_NOVAR | MU_WRDSF_NOCMD |
613 		    MU_WRDSF_DQUOTE | MU_WRDSF_SQUEEZE_DELIMS |
614 		    MU_WRDSF_RETURN_DELIMS | MU_WRDSF_WS | MU_WRDSF_OPTIONS))
615     {
616       mu_debug (MU_DEBCAT_MIME, MU_DEBUG_ERROR,
617 		(_("wordsplit: %s"), mu_wordsplit_strerror (&ws)));
618       mu_wordsplit_free (&ws);
619       free (value);
620       return MU_ERR_PARSE;
621     }
622 
623   if (!assoc)
624     {
625       if (pvalue)
626 	*pvalue = value;
627       mu_wordsplit_free (&ws);
628       return 0;
629     }
630 
631   memset (&cont, 0, sizeof (cont));
632   i = 0;
633   while (1)
634     {
635       rc = parse_param (&ws, &i, assoc, &cont, outcharset);
636       if (rc)
637 	{
638 	  if (rc == MU_ERR_PARSE)
639 	    {
640 	      char *p;
641 	      mu_assoc_sweep (assoc);
642 	      /* Attempt error recovery */
643 	      do
644 		p = getword (&ws, &i);
645 	      while (p && strcmp (p, ";"));
646 	      if (p)
647 		{
648 		  mu_debug (MU_DEBCAT_MIME, MU_DEBUG_TRACE0,
649 			    (_("finished error recovery at ; %s"),
650 			     ws.ws_wordv[i]));
651 		  /* put the semicolon back */
652 		  i--;
653 		  continue;
654 		}
655 	      rc = 0;
656 	    }
657 	  else if (rc == MU_ERR_USER0)
658 	    rc = 0;
659 	  break;
660 	}
661     }
662 
663   if (rc == 0 && cont.param_name)
664     rc = flush_param (&cont, assoc, outcharset);
665   free_param_continuation (&cont);
666   mu_assoc_tail_set_mark (assoc, 0);
667 
668   if (rc == 0)
669     {
670       if (pvalue)
671 	*pvalue = value;
672     }
673   else
674     free (value);
675   mu_wordsplit_free (&ws);
676 
677   if (subset)
678     mu_assoc_pull (subset, assoc);
679 
680   return rc;
681 }
682 
683 /* Parse header value from TEXT and return its value and a subset of
684    parameters.
685 
686    Input:
687      TEXT   - Header value.
688      CSET   - Output charset.  Can be NULL, in which case no conversions
689               take place.
690      ASSOC  - Parameter array initialized with empty slots for those
691               parameters, which are wanted on output.  It should be
692 	      created using mu_mime_param_assoc_create and populated
693 	      using mu_mime_param_assoc_add.
694    Output:
695      PVALUE - A pointer to the field value is stored here on success.
696      ASSOC  - Receives available parameters matching the input subset.
697 
698    Either PVALUE or ASSOC (but not both) can be NULL, meaning that the
699    corresponding data are of no interest to the caller.
700 */
701 int
mu_mime_header_parse_subset(const char * text,const char * cset,char ** pvalue,mu_assoc_t assoc)702 mu_mime_header_parse_subset (const char *text, const char *cset,
703 			     char **pvalue, mu_assoc_t assoc)
704 {
705   mu_assoc_t tmp;
706   int rc = mu_mime_param_assoc_create (&tmp);
707   if (rc == 0)
708     {
709       rc = _mime_header_parse (text, pvalue, tmp, cset, assoc);
710       mu_assoc_destroy (&tmp);
711     }
712   return rc;
713 }
714 
715 /* Parse header value from TEXT and return its value and parameters.
716 
717    Input:
718      TEXT   - Header value.
719      CSET   - Output charset.  Can be NULL, in which case no conversions
720               take place.
721    Output:
722      PVALUE - A pointer to the field value is stored here on success.
723      PASSOC - Receives an associative array of parameters.
724 
725    Either PVALUE or PASSOC (but not both) can be NULL, meaning that the
726    corresponding data are of no interest to the caller.
727 */
728 int
mu_mime_header_parse(const char * text,char const * cset,char ** pvalue,mu_assoc_t * passoc)729 mu_mime_header_parse (const char *text, char const *cset, char **pvalue,
730 		      mu_assoc_t *passoc)
731 {
732   int rc;
733   mu_assoc_t assoc;
734 
735   rc = mu_mime_param_assoc_create (&assoc);
736   if (rc == 0)
737     {
738       rc = _mime_header_parse (text, pvalue, assoc, cset, NULL);
739       if (rc || !passoc)
740 	mu_assoc_destroy (&assoc);
741       else
742 	*passoc = assoc;
743     }
744 
745   return rc;
746 }
747 
748 /* TEXT is a value of a structured MIME header, e.g. Content-Type.
749    This function returns the `disposition part' of it.  In other
750    words, it returns disposition, if TEXT is a Content-Disposition
751    value, and `type/subtype' part, if it is a Content-Type value.
752 */
753 int
mu_mimehdr_get_disp(const char * text,char * buf,size_t bufsz,size_t * retsz)754 mu_mimehdr_get_disp (const char *text, char *buf, size_t bufsz, size_t *retsz)
755 {
756   int rc;
757   char *value;
758 
759   rc = mu_mime_header_parse (text, NULL, &value, NULL);
760   if (rc == 0)
761     {
762       size_t size = strlen (value);
763       if (size > bufsz)
764 	size = bufsz;
765       if (buf)
766 	size = mu_cpystr (buf, value, size);
767       if (retsz)
768 	*retsz = size;
769     }
770   free (value);
771   return 0;
772 }
773 
774 /* Same as mu_mimehdr_get_disp, but allocates memory */
775 int
mu_mimehdr_aget_disp(const char * text,char ** pvalue)776 mu_mimehdr_aget_disp (const char *text, char **pvalue)
777 {
778   return mu_mime_header_parse (text, NULL, pvalue, NULL);
779 }
780 
781 /* Get the value of parameter NAME from STR, which must be
782    a value of a structured MIME header.
783    At most BUFSZ-1 of data are stored in BUF.  A terminating NUL
784    character is appended to it.
785 
786    Unless NULL, RETSZ is filled with the actual length of the
787    returned data (not including the NUL terminator).
788 
789    BUF may be NULL, in which case the function will only fill
790    RETSZ, as described above. */
791 int
mu_mimehdr_get_param(const char * str,const char * name,char * buf,size_t bufsz,size_t * retsz)792 mu_mimehdr_get_param (const char *str, const char *name,
793 		      char *buf, size_t bufsz, size_t *retsz)
794 {
795   int rc;
796   char *value;
797 
798   rc = mu_mimehdr_aget_param (str, name, &value);
799   if (rc == 0)
800     {
801       size_t size = strlen (value);
802       if (size > bufsz)
803 	size = bufsz;
804       if (buf)
805 	size = mu_cpystr (buf, value, size);
806       if (retsz)
807 	*retsz = size;
808     }
809   free (value);
810   return rc;
811 }
812 
813 /* Same as mu_mimehdr_get_param, but allocates memory. */
814 int
mu_mimehdr_aget_param(const char * str,const char * name,char ** pval)815 mu_mimehdr_aget_param (const char *str, const char *name, char **pval)
816 {
817   return mu_mimehdr_aget_decoded_param (str, name, NULL, pval, NULL);
818 }
819 
820 
821 /* Similar to mu_mimehdr_aget_param, but the returned value is decoded
822    according to the CHARSET.  Unless PLANG is NULL, it receives malloc'ed
823    language name from STR.  If there was no language name, *PLANG is set
824    to NULL.
825 */
826 int
mu_mimehdr_aget_decoded_param(const char * str,const char * name,const char * charset,char ** pval,char ** plang)827 mu_mimehdr_aget_decoded_param (const char *str, const char *name,
828 			       const char *charset,
829 			       char **pval, char **plang)
830 {
831   mu_assoc_t assoc;
832   int rc;
833 
834   rc = mu_mime_param_assoc_create (&assoc);
835   if (rc == 0)
836     {
837       rc = mu_mime_param_assoc_add (assoc, name);
838       if (rc == 0)
839 	{
840 	  rc = mu_mime_header_parse_subset (str, charset, NULL, assoc);
841 	  if (rc == 0)
842 	    {
843 	      struct mu_mime_param *param = mu_assoc_get (assoc, name);
844 	      if (!param)
845 		rc = MU_ERR_NOENT;
846 	      else
847 		{
848 		  *pval = param->value;
849 		  if (plang)
850 		    {
851 		      *plang = param->lang;
852 		      param->lang = NULL;
853 		    }
854 		  param->value = NULL;
855 		}
856 	    }
857 	}
858       mu_assoc_destroy (&assoc);
859     }
860   return rc;
861 }
862 
863 /* Get the attachment name from a message.
864 
865    Input:
866      MSG     - The input message.
867      CHARSET - Character set to recode output values to.  Can be NULL.
868    Output:
869      PBUF    - Output value.
870      PSZ     - Its size in bytes, not counting the terminating zero.
871      PLANG   - Language the name is written in, if provided in the header.
872 
873    Either PSZ or PLAN (or both) can be NULL.
874 */
875 static int
_get_attachment_name(mu_message_t msg,const char * charset,char ** pbuf,size_t * psz,char ** plang)876 _get_attachment_name (mu_message_t msg, const char *charset,
877 		      char **pbuf, size_t *psz, char **plang)
878 {
879   int ret = EINVAL;
880   mu_header_t hdr;
881   char *value = NULL;
882   mu_assoc_t assoc;
883 
884   if (!msg)
885     return ret;
886 
887   if ((ret = mu_message_get_header (msg, &hdr)) != 0)
888     return ret;
889 
890   ret = mu_header_aget_value_unfold (hdr, MU_HEADER_CONTENT_DISPOSITION,
891 				     &value);
892 
893   /* If the header wasn't there, we'll fall back to Content-Type, but
894      other errors are fatal. */
895   if (ret != 0 && ret != MU_ERR_NOENT)
896     return ret;
897 
898   if (ret == 0 && value != NULL)
899     {
900       ret = mu_mime_param_assoc_create (&assoc);
901       if (ret)
902 	return ret;
903       ret = mu_mime_param_assoc_add (assoc, "filename");
904       if (ret == 0)
905 	{
906 	  char *disp;
907 
908 	  ret = mu_mime_header_parse_subset (value, charset, &disp, assoc);
909 	  if (ret == 0)
910 	    {
911 	      struct mu_mime_param *param;
912 	      if (mu_c_strcasecmp (disp, "attachment") == 0 &&
913 		  (param = mu_assoc_get (assoc, "filename")))
914 		{
915 		  *pbuf = param->value;
916 		  if (psz)
917 		    *psz = strlen (*pbuf);
918 		  param->value = NULL;
919 		  if (plang)
920 		    {
921 		      *plang = param->lang;
922 		      param->lang = NULL;
923 		    }
924 		}
925 	      else
926 		ret = MU_ERR_NOENT;
927 	      free (disp);
928 	      mu_assoc_destroy (&assoc);
929 	    }
930 	}
931     }
932 
933   free (value);
934 
935   if (ret == 0)
936     return ret;
937 
938   /* If we didn't get the name, we fall back on the Content-Type name
939      parameter. */
940 
941   ret = mu_header_aget_value_unfold (hdr, MU_HEADER_CONTENT_TYPE, &value);
942   if (ret == 0)
943     {
944       ret = mu_mime_param_assoc_create (&assoc);
945       if (ret)
946 	return ret;
947       ret = mu_mime_param_assoc_add (assoc, "name");
948       if (ret == 0)
949 	{
950 	  ret = mu_mime_header_parse_subset (value, charset, NULL, assoc);
951 	  if (ret == 0)
952 	    {
953 	      struct mu_mime_param *param;
954 	      if ((param = mu_assoc_get (assoc, "name")))
955 		{
956 		  *pbuf = param->value;
957 		  if (psz)
958 		    *psz = strlen (*pbuf);
959 		  param->value = NULL;
960 		  if (plang)
961 		    {
962 		      *plang = param->lang;
963 		      param->lang = NULL;
964 		    }
965 		}
966 	      else
967 		ret = MU_ERR_NOENT;
968 	    }
969 	}
970       free (value);
971     }
972 
973   return ret;
974 }
975 
976 int
mu_message_aget_attachment_name(mu_message_t msg,char ** name)977 mu_message_aget_attachment_name (mu_message_t msg, char **name)
978 {
979   if (name == NULL)
980     return MU_ERR_OUT_PTR_NULL;
981   return _get_attachment_name (msg, NULL, name, NULL, NULL);
982 }
983 
984 int
mu_message_aget_decoded_attachment_name(mu_message_t msg,const char * charset,char ** pval,char ** plang)985 mu_message_aget_decoded_attachment_name (mu_message_t msg,
986 					 const char *charset,
987 					 char **pval,
988 					 char **plang)
989 {
990   if (pval == NULL)
991     return MU_ERR_OUT_PTR_NULL;
992   return _get_attachment_name (msg, charset, pval, NULL, plang);
993 }
994 
995 int
mu_message_get_attachment_name(mu_message_t msg,char * buf,size_t bufsz,size_t * sz)996 mu_message_get_attachment_name (mu_message_t msg, char *buf, size_t bufsz,
997 				size_t *sz)
998 {
999   char *tmp;
1000   size_t size;
1001   int rc = _get_attachment_name (msg, NULL, &tmp, &size, NULL);
1002   if (rc == 0)
1003     {
1004       if (size > bufsz)
1005 	size = bufsz;
1006       if (buf)
1007 	size = mu_cpystr (buf, tmp, size);
1008       if (sz)
1009 	*sz = size;
1010     }
1011   free (tmp);
1012   return rc;
1013 }
1014 
1015