Commit d5ae81e5 d5ae81e503facb6b39cebd5dbf6697dfa429ef07 by Sergey Poznyakoff

Make mu_message_(a?)get_attachment_name RFC-compatible.

* mailbox/attachment.c (_header_get_param): Rewrite.
(_get_attachment_name): New auxiliary function.
(mu_message_aget_attachment_name)
(mu_message_get_attachment_name): Rewrite using _get_attachment_name.
* mh/mhn.c (store_handler): Use mu_message_aget_attachment_name.
1 parent 52fffebc
...@@ -188,78 +188,243 @@ _attachment_free (struct _msg_info *info, int free_message) ...@@ -188,78 +188,243 @@ _attachment_free (struct _msg_info *info, int free_message)
188 /* See RFC 2045, 5.1. Syntax of the Content-Type Header Field */ 188 /* See RFC 2045, 5.1. Syntax of the Content-Type Header Field */
189 #define _ISSPECIAL(c) !!strchr ("()<>@,;:\\\"/[]?=", c) 189 #define _ISSPECIAL(c) !!strchr ("()<>@,;:\\\"/[]?=", c)
190 190
191 static char * 191 /* _header_get_param - an auxiliary function to extract values from
192 _header_get_param (char *field_body, const char *param, size_t *len) 192 Content-Type, Content-Disposition and similar headers.
193
194 Arguments:
195
196 FIELD_BODY Header value, complying to RFCs 2045, 2183, 2231.3;
197 DISP Disposition. Unless it is NULL, the disposition part
198 of FIELD_BODY is compared with it. If they differ,
199 the function returns MU_ERR_NOENT.
200 PARAM Name of the parameter to extract from FIELD_BODY;
201 BUF Where to extract the value to;
202 BUFSZ Size of BUF;
203 PRET Pointer to the memory location for the return buffer (see
204 below).
205 PLEN Pointer to the return size.
206
207 The function parses FIELD_BODY and extracts the value of the parameter
208 PARAM.
209
210 If BUF is not NULL and BUFSZ is not 0, the extracted value is stored into
211 BUF. At most BUFSZ-1 bytes are copied.
212
213 Otherwise, if PRET is not NULL, the function allocates enough memory to
214 hold the extracted value, copies there the result, and stores the
215 pointer to the allocated memory into the location pointed to by PRET.
216
217 If PLEN is not NULL, the size of the extracted value (without terminating
218 NUL character) is stored there.
219
220 If BUF==NULL *and* PRET==NULL, no memory is allocated, but PLEN is
221 honored anyway, i.e. unless it is NULL it receives size of the result.
222 This can be used to estimate the needed buffer size.
223
224 Return values:
225 0 on success.
226 MU_ERR_NOENT, requested parameter not found, or disposition does
227 not match DISP.
228 MU_ERR_PARSE, if FIELD_BODY does not comply to any of the abovemntioned
229 RFCs.
230 ENOMEM , if unable to allocate memory.
231 */
232
233 int
234 _header_get_param (char *field_body,
235 const char *disp,
236 const char *param,
237 char *buf, size_t bufsz,
238 char **pret, size_t *plen)
193 { 239 {
194 char *str, *p, *v, *e; 240 int res = MU_ERR_NOENT; /* Return value, pessimistic default */
195 int quoted = 0, was_quoted = 0; 241 size_t param_len = strlen (param);
196 242 char *p;
197 if (len == NULL || (str = field_body) == NULL) 243 char *mem = NULL; /* Allocated memory storage */
198 return NULL; 244 size_t retlen = 0; /* Total number of bytes copied */
245 unsigned long cind = 0; /* Expected continued parameter index.
246 See RFC 2231, Section 3,
247 "Parameter Value Continuations" */
248
249 if (field_body == NULL)
250 return EINVAL;
199 251
200 p = strchr (str, ';'); 252 if (bufsz == 0) /* Make sure buf value is meaningful */
201 while (p) 253 buf = NULL;
254
255 p = strchr (field_body, ';');
256 if (!p)
257 return MU_ERR_NOENT;
258 if (disp && mu_c_strncasecmp (field_body, disp, p - field_body))
259 return MU_ERR_NOENT;
260
261 while (p && *p)
202 { 262 {
203 p++; 263 char *v, *e;
204 while (mu_isspace (*p)) /* walk upto start of param */ 264 size_t len, escaped_chars = 0;
205 p++; 265
266 if (*p != ';')
267 {
268 res = MU_ERR_PARSE;
269 break;
270 }
271
272 /* walk upto start of param */
273 p = mu_str_skip_class (p + 1, MU_CTYPE_SPACE);
206 if ((v = strchr (p, '=')) == NULL) 274 if ((v = strchr (p, '=')) == NULL)
207 break; 275 break;
208 *len = 0; 276 v++;
209 v = e = v + 1; 277 /* Find end of the parameter */
210 while (*e && (quoted || (!_ISSPECIAL (*e) && !mu_isspace (*e)))) 278 if (*v == '"')
211 { /* skip pass value and calc len */ 279 {
212 if (*e == '\"') 280 /* Quoted string */
213 quoted = ~quoted, was_quoted = 1; 281 for (e = ++v; *e != '"'; e++)
214 else 282 {
215 (*len)++; 283 if (*e == 0) /* Malformed header */
284 {
285 res = MU_ERR_PARSE;
286 break;
287 }
288 if (*e == '\\')
289 {
290 if (*++e == 0)
291 {
292 res = MU_ERR_PARSE;
293 break;
294 }
295 escaped_chars++;
296 }
297 }
298 if (res == MU_ERR_PARSE)
299 break;
300 len = e - v;
216 e++; 301 e++;
217 } 302 }
218 if (mu_c_strncasecmp (p, param, strlen (param))) 303 else
219 { /* no match jump to next */ 304 {
305 for (e = v + 1; !(_ISSPECIAL (*e) || mu_isspace (*e)); e++)
306 ;
307 len = e - v;
308 }
309
310 /* Is it our parameter? */
311 if (mu_c_strncasecmp (p, param, param_len))
312 { /* nope, jump to next */
220 p = strchr (e, ';'); 313 p = strchr (e, ';');
221 continue; 314 continue;
222 } 315 }
223 else 316
224 return was_quoted ? v + 1 : v; /* return unquoted value */ 317 res = 0; /* Indicate success */
225 } 318
226 return NULL; 319 if (p[param_len] == '*')
227 } 320 {
321 /* Parameter value continuation (RFC 2231, Section 3).
322 See if the index is OK */
323 char *end;
324 unsigned long n = strtoul (p + param_len + 1, &end, 10);
325 if (*end != '=' || n != cind)
326 {
327 res = MU_ERR_PARSE;
328 break;
329 }
330 /* Everything OK, increase the estimation */
331 cind++;
332 }
333
334 /* Prepare P for the next iteration */
335 p = e;
228 336
229 int 337 /* Escape characters that appear in quoted-pairs are
230 mu_message_aget_attachment_name (mu_message_t msg, char **name) 338 semantically "invisible" (RFC 2822, Section 3.2.2,
231 { 339 "Quoted characters") */
232 size_t sz = 0; 340 len -= escaped_chars;
233 int ret = 0;
234 341
235 if (name == NULL) 342 /* Adjust len if nearing end of the buffer */
236 return MU_ERR_OUT_PTR_NULL; 343 if (bufsz && len >= bufsz)
344 len = bufsz - 1;
237 345
238 if ((ret = mu_message_get_attachment_name (msg, NULL, 0, &sz)) != 0) 346 if (pret)
239 return ret; 347 {
348 /* The caller wants us to allocate the memory */
349 if (!buf && !mem)
350 {
351 mem = malloc (len + 1);
352 if (!mem)
353 {
354 res = ENOMEM;
355 break;
356 }
357 buf = mem;
358 }
359 else if (mem)
360 {
361 /* If we got here, it means we are iterating over
362 a parameter value continuation, and cind=0 has
363 already been passed. Reallocate the memory to
364 accomodate next chunk of data. */
365 char *newmem = realloc (mem, retlen + len + 1);
366 if (!newmem)
367 {
368 res = ENOMEM;
369 break;
370 }
371 mem = newmem;
372 }
373 }
240 374
241 *name = malloc (sz + 1); 375 if (buf)
242 if (!*name) 376 {
243 return ENOMEM; 377 /* Actually copy the data. Buf is not NULL either because
244 378 the user passed it as an argument, or because we allocated
245 if ((ret = mu_message_get_attachment_name (msg, *name, sz + 1, NULL)) != 0) 379 memory for it. */
246 { 380 if (escaped_chars)
247 free (*name); 381 {
248 *name = NULL; 382 int i;
383 for (i = 0; i < len; i++)
384 {
385 if (*v == '\\')
386 ++v;
387 buf[retlen + i] = *v++;
388 }
389 }
390 else
391 memcpy (buf + retlen, v, len);
392 }
393 /* Adjust total result size ... */
394 retlen += len;
395 /* ... and remaining buffer size, if necessary */
396 if (bufsz)
397 {
398 bufsz -= len;
399 if (bufsz == 0)
400 break;
401 }
249 } 402 }
250 403
251 return ret; 404 if (res == 0)
405 {
406 /* Everything OK, prepare the returned data. */
407 if (buf)
408 buf[retlen] = 0;
409 if (plen)
410 *plen = retlen;
411 if (pret)
412 *pret = mem;
413 }
414 else if (mem)
415 free (mem);
416 return res;
252 } 417 }
253 418
254 int 419 /* Get the attachment name from MSG. See _header_get_param, for a
255 mu_message_get_attachment_name (mu_message_t msg, char *buf, size_t bufsz, 420 description of the rest of arguments. */
256 size_t *sz) 421 static int
422 _get_attachment_name (mu_message_t msg, char *buf, size_t bufsz,
423 char **pbuf, size_t *sz)
257 { 424 {
258 int ret = EINVAL; 425 int ret = EINVAL;
259 mu_header_t hdr; 426 mu_header_t hdr;
260 char *value = NULL; 427 char *value = NULL;
261 char *name = NULL;
262 size_t namesz = 0;
263 428
264 if (!msg) 429 if (!msg)
265 return ret; 430 return ret;
...@@ -276,41 +441,39 @@ mu_message_get_attachment_name (mu_message_t msg, char *buf, size_t bufsz, ...@@ -276,41 +441,39 @@ mu_message_get_attachment_name (mu_message_t msg, char *buf, size_t bufsz,
276 441
277 if (ret == 0 && value != NULL) 442 if (ret == 0 && value != NULL)
278 { 443 {
279 /* FIXME: this is cheezy, it should check the value of the 444 ret = _header_get_param (value, "attachment",
280 Content-Disposition field, not strstr it. */ 445 "filename", buf, bufsz, pbuf, sz);
281 446 free (value);
282 if (strstr (value, "attachment") != NULL) 447 value = NULL;
283 name = _header_get_param (value, "filename", &namesz); 448 if (ret == 0 || ret != MU_ERR_NOENT)
449 return ret;
284 } 450 }
285 451
286 /* If we didn't get the name, we fall back on the Content-Type name 452 /* If we didn't get the name, we fall back on the Content-Type name
287 parameter. */ 453 parameter. */
288 454
289 if (name == NULL) 455 free (value);
290 { 456 ret = mu_header_aget_value (hdr, "Content-Type", &value);
291 if (value) 457 if (ret == 0)
292 free (value); 458 ret = _header_get_param (value, NULL, "name", buf, bufsz, pbuf, sz);
293 459 free (value);
294 ret = mu_header_aget_value (hdr, "Content-Type", &value);
295 name = _header_get_param (value, "name", &namesz);
296 }
297
298 if (name)
299 {
300 ret = 0;
301
302 name[namesz] = '\0';
303 460
304 if (sz) 461 return ret;
305 *sz = namesz; 462 }
306 463
307 if (buf) 464 int
308 strncpy (buf, name, bufsz); 465 mu_message_aget_attachment_name (mu_message_t msg, char **name)
309 } 466 {
310 else 467 if (name == NULL)
311 ret = MU_ERR_NOENT; 468 return MU_ERR_OUT_PTR_NULL;
469 return _get_attachment_name (msg, NULL, 0, name, NULL);
470 }
312 471
313 return ret; 472 int
473 mu_message_get_attachment_name (mu_message_t msg, char *buf, size_t bufsz,
474 size_t *sz)
475 {
476 return _get_attachment_name (msg, buf, bufsz, NULL, sz);
314 } 477 }
315 478
316 int 479 int
......
...@@ -1588,59 +1588,12 @@ store_handler (mu_message_t msg, msg_part_t part, char *type, char *encoding, ...@@ -1588,59 +1588,12 @@ store_handler (mu_message_t msg, msg_part_t part, char *type, char *encoding,
1588 1588
1589 if (mode_options & OPT_AUTO) 1589 if (mode_options & OPT_AUTO)
1590 { 1590 {
1591 mu_header_t hdr;
1592 char *val; 1591 char *val;
1593 1592
1594 if (mu_message_get_header (msg, &hdr) == 0) 1593 if (mu_message_aget_attachment_name (msg, &val) == 0)
1595 { 1594 {
1596 int argc; 1595 name = normalize_path (dir, val);
1597 char **argv; 1596 free (val);
1598
1599 if (mu_header_aget_value (hdr, MU_HEADER_CONTENT_DISPOSITION, &val) == 0)
1600 {
1601 if (mu_argcv_get (val, "=", NULL, &argc, &argv) == 0)
1602 {
1603 int i;
1604
1605 for (i = 0; i < argc; i++)
1606 {
1607 if (strcmp (argv[i], "filename") == 0
1608 && ++i < argc
1609 && argv[i][0] == '='
1610 && ++i < argc)
1611 {
1612 name = normalize_path (dir, argv[i]);
1613 break;
1614 }
1615 }
1616 mu_argcv_free (argc, argv);
1617 }
1618 free (val);
1619 }
1620
1621 if (!name
1622 && mu_header_aget_value (hdr, MU_HEADER_CONTENT_TYPE, &val) == 0)
1623 {
1624 if (mu_argcv_get (val, "=", NULL, &argc, &argv) == 0)
1625 {
1626 int i;
1627
1628 for (i = 0; i < argc; i++)
1629 {
1630 if ((strcmp (argv[i], "filename") == 0
1631 || strcmp (argv[i], "name") == 0)
1632 && ++i < argc
1633 && argv[i][0] == '='
1634 && ++i < argc)
1635 {
1636 name = normalize_path (dir, argv[i]);
1637 break;
1638 }
1639 }
1640 mu_argcv_free (argc, argv);
1641 }
1642 free (val);
1643 }
1644 } 1597 }
1645 } 1598 }
1646 1599
......