Git init
[profile/ivi/libsoup2.4.git] / libsoup / soup-headers.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-headers.c: HTTP message header parsing
4  *
5  * Copyright (C) 2001-2003, Ximian, Inc.
6  */
7
8 #include <stdlib.h>
9 #include <string.h>
10 #include <stdio.h>
11 #include <ctype.h>
12
13 #include "soup-headers.h"
14 #include "soup-misc.h"
15 #include "soup-uri.h"
16
17 /**
18  * soup_headers_parse:
19  * @str: the header string (including the Request-Line or Status-Line,
20  * and the trailing blank line)
21  * @len: length of @str up to (but not including) the terminating blank line.
22  * @dest: #SoupMessageHeaders to store the header values in
23  *
24  * Parses the headers of an HTTP request or response in @str and
25  * stores the results in @dest. Beware that @dest may be modified even
26  * on failure.
27  *
28  * This is a low-level method; normally you would use
29  * soup_headers_parse_request() or soup_headers_parse_response().
30  *
31  * Return value: success or failure
32  *
33  * Since: 2.26
34  **/
35 gboolean
36 soup_headers_parse (const char *str, int len, SoupMessageHeaders *dest)
37 {
38         const char *headers_start;
39         char *headers_copy, *name, *name_end, *value, *value_end;
40         char *eol, *sol;
41         gboolean success = FALSE;
42
43         g_return_val_if_fail (str != NULL, FALSE);
44         g_return_val_if_fail (dest != NULL, FALSE);
45
46         /* Technically, the grammar does allow NUL bytes in the
47          * headers, but this is probably a bug, and if it's not, we
48          * can't deal with them anyway.
49          */
50         if (memchr (str, '\0', len))
51                 return FALSE;
52
53         /* As per RFC 2616 section 19.3, we treat '\n' as the
54          * line terminator, and '\r', if it appears, merely as
55          * ignorable trailing whitespace.
56          */
57
58         /* Skip over the Request-Line / Status-Line */
59         headers_start = memchr (str, '\n', len);
60         if (!headers_start)
61                 return FALSE;
62
63         /* We work on a copy of the headers, which we can write '\0's
64          * into, so that we don't have to individually g_strndup and
65          * then g_free each header name and value.
66          */
67         headers_copy = g_strndup (headers_start, len - (headers_start - str));
68         value_end = headers_copy;
69
70         while (*(value_end + 1)) {
71                 name = value_end + 1;
72                 name_end = strchr (name, ':');
73                 if (!name_end || name + strcspn (name, " \t\r\n") < name_end) {
74                         /* Bad header; just ignore this line. Note
75                          * that if it has continuation lines, we'll
76                          * end up ignoring them too since they'll
77                          * start with spaces.
78                          */
79                         value_end = strchr (name, '\n');
80                         if (!value_end)
81                                 goto done;
82                         continue;
83                 }
84
85                 /* Find the end of the value; ie, an end-of-line that
86                  * isn't followed by a continuation line.
87                  */
88                 value = name_end + 1;
89                 value_end = strchr (name, '\n');
90                 if (!value_end)
91                         goto done;
92                 while (*(value_end + 1) == ' ' || *(value_end + 1) == '\t') {
93                         value_end = strchr (value_end + 1, '\n');
94                         if (!value_end)
95                                 goto done;
96                 }
97
98                 *name_end = '\0';
99                 *value_end = '\0';
100
101                 /* Skip leading whitespace */
102                 while (value < value_end &&
103                        (*value == ' ' || *value == '\t' ||
104                         *value == '\r' || *value == '\n'))
105                         value++;
106
107                 /* Collapse continuation lines */
108                 while ((eol = strchr (value, '\n'))) {
109                         /* find start of next line */
110                         sol = eol + 1;
111                         while (*sol == ' ' || *sol == '\t')
112                                 sol++;
113
114                         /* back up over trailing whitespace on current line */
115                         while (eol[-1] == ' ' || eol[-1] == '\t' || eol[-1] == '\r')
116                                 eol--;
117
118                         /* Delete all but one SP */
119                         *eol = ' ';
120                         g_memmove (eol + 1, sol, strlen (sol) + 1);
121                 }
122
123                 /* clip trailing whitespace */
124                 eol = strchr (value, '\0');
125                 while (eol > value &&
126                        (eol[-1] == ' ' || eol[-1] == '\t' || eol[-1] == '\r'))
127                         eol--;
128                 *eol = '\0';
129
130                 soup_message_headers_append (dest, name, value);
131         }
132         success = TRUE;
133
134 done:
135         g_free (headers_copy);
136         return success;
137 }
138
139 /**
140  * soup_headers_parse_request:
141  * @str: the header string (including the trailing blank line)
142  * @len: length of @str up to (but not including) the terminating blank line.
143  * @req_headers: #SoupMessageHeaders to store the header values in
144  * @req_method: (out) (allow-none): if non-%NULL, will be filled in with the
145  * request method
146  * @req_path: (out) (allow-none): if non-%NULL, will be filled in with the
147  * request path
148  * @ver: (out) (allow-none): if non-%NULL, will be filled in with the HTTP
149  * version
150  *
151  * Parses the headers of an HTTP request in @str and stores the
152  * results in @req_method, @req_path, @ver, and @req_headers.
153  *
154  * Beware that @req_headers may be modified even on failure.
155  *
156  * Return value: %SOUP_STATUS_OK if the headers could be parsed, or an
157  * HTTP error to be returned to the client if they could not be.
158  **/
159 guint
160 soup_headers_parse_request (const char          *str, 
161                             int                  len, 
162                             SoupMessageHeaders  *req_headers,
163                             char               **req_method,
164                             char               **req_path,
165                             SoupHTTPVersion     *ver) 
166 {
167         const char *method, *method_end, *path, *path_end;
168         const char *version, *version_end, *headers;
169         unsigned long major_version, minor_version;
170         char *p;
171
172         g_return_val_if_fail (str && *str, SOUP_STATUS_MALFORMED);
173
174         /* RFC 2616 4.1 "servers SHOULD ignore any empty line(s)
175          * received where a Request-Line is expected."
176          */
177         while ((*str == '\r' || *str == '\n') && len > 0) {
178                 str++;
179                 len--;
180         }
181         if (!len)
182                 return SOUP_STATUS_BAD_REQUEST;
183
184         /* RFC 2616 19.3 "[servers] SHOULD accept any amount of SP or
185          * HT characters between [Request-Line] fields"
186          */
187
188         method = method_end = str;
189         while (method_end < str + len && *method_end != ' ' && *method_end != '\t')
190                 method_end++;
191         if (method_end >= str + len)
192                 return SOUP_STATUS_BAD_REQUEST;
193
194         path = method_end;
195         while (path < str + len && (*path == ' ' || *path == '\t'))
196                 path++;
197         if (path >= str + len)
198                 return SOUP_STATUS_BAD_REQUEST;
199
200         path_end = path;
201         while (path_end < str + len && *path_end != ' ' && *path_end != '\t')
202                 path_end++;
203         if (path_end >= str + len)
204                 return SOUP_STATUS_BAD_REQUEST;
205
206         version = path_end;
207         while (version < str + len && (*version == ' ' || *version == '\t'))
208                 version++;
209         if (version + 8 >= str + len)
210                 return SOUP_STATUS_BAD_REQUEST;
211
212         if (strncmp (version, "HTTP/", 5) != 0 ||
213             !g_ascii_isdigit (version[5]))
214                 return SOUP_STATUS_BAD_REQUEST;
215         major_version = strtoul (version + 5, &p, 10);
216         if (*p != '.' || !g_ascii_isdigit (p[1]))
217                 return SOUP_STATUS_BAD_REQUEST;
218         minor_version = strtoul (p + 1, &p, 10);
219         version_end = p;
220         if (major_version != 1)
221                 return SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED;
222         if (minor_version > 1)
223                 return SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED;
224
225         headers = version_end;
226         while (headers < str + len && (*headers == '\r' || *headers == ' '))
227                 headers++;
228         if (headers >= str + len || *headers != '\n')
229                 return SOUP_STATUS_BAD_REQUEST;
230
231         if (!soup_headers_parse (str, len, req_headers)) 
232                 return SOUP_STATUS_BAD_REQUEST;
233
234         if (soup_message_headers_get_expectations (req_headers) &
235             SOUP_EXPECTATION_UNRECOGNIZED)
236                 return SOUP_STATUS_EXPECTATION_FAILED;
237         /* RFC 2616 14.10 */
238         if (minor_version == 0)
239                 soup_message_headers_clean_connection_headers (req_headers);
240
241         if (req_method)
242                 *req_method = g_strndup (method, method_end - method);
243         if (req_path)
244                 *req_path = g_strndup (path, path_end - path);
245         if (ver)
246                 *ver = (minor_version == 0) ? SOUP_HTTP_1_0 : SOUP_HTTP_1_1;
247
248         return SOUP_STATUS_OK;
249 }
250
251 /**
252  * soup_headers_parse_status_line:
253  * @status_line: an HTTP Status-Line
254  * @ver: (out) (allow-none): if non-%NULL, will be filled in with the HTTP
255  * version
256  * @status_code: (out) (allow-none): if non-%NULL, will be filled in with
257  * the status code
258  * @reason_phrase: (out) (allow-none): if non-%NULL, will be filled in with
259  * the reason phrase
260  *
261  * Parses the HTTP Status-Line string in @status_line into @ver,
262  * @status_code, and @reason_phrase. @status_line must be terminated by
263  * either "\0" or "\r\n".
264  *
265  * Return value: %TRUE if @status_line was parsed successfully.
266  **/
267 gboolean
268 soup_headers_parse_status_line (const char       *status_line,
269                                 SoupHTTPVersion  *ver,
270                                 guint            *status_code,
271                                 char            **reason_phrase)
272 {
273         unsigned long major_version, minor_version, code;
274         const char *code_start, *code_end, *phrase_start, *phrase_end;
275         char *p;
276
277         g_return_val_if_fail (status_line != NULL, FALSE);
278
279         if (strncmp (status_line, "HTTP/", 5) == 0 &&
280             g_ascii_isdigit (status_line[5])) {
281                 major_version = strtoul (status_line + 5, &p, 10);
282                 if (*p != '.' || !g_ascii_isdigit (p[1]))
283                         return FALSE;
284                 minor_version = strtoul (p + 1, &p, 10);
285                 if (major_version != 1)
286                         return FALSE;
287                 if (minor_version > 1)
288                         return FALSE;
289                 if (ver)
290                         *ver = (minor_version == 0) ? SOUP_HTTP_1_0 : SOUP_HTTP_1_1;
291         } else if (!strncmp (status_line, "ICY", 3)) {
292                 /* Shoutcast not-quite-HTTP format */
293                 if (ver)
294                         *ver = SOUP_HTTP_1_0;
295                 p = (char *)status_line + 3;
296         } else
297                 return FALSE;
298
299         code_start = p;
300         while (*code_start == ' ' || *code_start == '\t')
301                 code_start++;
302         code_end = code_start;
303         while (*code_end >= '0' && *code_end <= '9')
304                 code_end++;
305         if (code_end != code_start + 3)
306                 return FALSE;
307         code = atoi (code_start);
308         if (code < 100 || code > 599)
309                 return FALSE;
310         if (status_code)
311                 *status_code = code;
312
313         phrase_start = code_end;
314         while (*phrase_start == ' ' || *phrase_start == '\t')
315                 phrase_start++;
316         phrase_end = phrase_start + strcspn (phrase_start, "\n");
317         while (phrase_end > phrase_start &&
318                (phrase_end[-1] == '\r' || phrase_end[-1] == ' ' || phrase_end[-1] == '\t'))
319                 phrase_end--;
320         if (reason_phrase)
321                 *reason_phrase = g_strndup (phrase_start, phrase_end - phrase_start);
322
323         return TRUE;
324 }
325
326 /**
327  * soup_headers_parse_response:
328  * @str: the header string (including the trailing blank line)
329  * @len: length of @str up to (but not including) the terminating blank line.
330  * @headers: #SoupMessageheaders to store the header values in
331  * @ver: (out) (allow-none): if non-%NULL, will be filled in with the HTTP
332  * version
333  * @status_code: (out) (allow-none): if non-%NULL, will be filled in with
334  * the status code
335  * @reason_phrase: (out) (allow-none): if non-%NULL, will be filled in with
336  * the reason phrase
337  *
338  * Parses the headers of an HTTP response in @str and stores the
339  * results in @ver, @status_code, @reason_phrase, and @headers.
340  *
341  * Beware that @headers may be modified even on failure.
342  *
343  * Return value: success or failure.
344  **/
345 gboolean
346 soup_headers_parse_response (const char          *str, 
347                              int                  len, 
348                              SoupMessageHeaders  *headers,
349                              SoupHTTPVersion     *ver,
350                              guint               *status_code,
351                              char               **reason_phrase)
352 {
353         SoupHTTPVersion version;
354
355         g_return_val_if_fail (str && *str, FALSE);
356
357         /* Workaround for broken servers that send extra line breaks
358          * after a response, which we then see prepended to the next
359          * response on that connection.
360          */
361         while ((*str == '\r' || *str == '\n') && len > 0) {
362                 str++;
363                 len--;
364         }
365         if (!len)
366                 return FALSE;
367
368         if (!soup_headers_parse (str, len, headers)) 
369                 return FALSE;
370
371         if (!soup_headers_parse_status_line (str, 
372                                              &version, 
373                                              status_code, 
374                                              reason_phrase))
375                 return FALSE;
376         if (ver)
377                 *ver = version;
378
379         /* RFC 2616 14.10 */
380         if (version == SOUP_HTTP_1_0)
381                 soup_message_headers_clean_connection_headers (headers);
382
383         return TRUE;
384 }
385
386
387 /*
388  * Parsing of specific HTTP header types
389  */
390
391 static const char *
392 skip_lws (const char *s)
393 {
394         while (g_ascii_isspace (*s))
395                 s++;
396         return s;
397 }
398
399 static const char *
400 unskip_lws (const char *s, const char *start)
401 {
402         while (s > start && g_ascii_isspace (*(s - 1)))
403                 s--;
404         return s;
405 }
406
407 static const char *
408 skip_delims (const char *s, char delim)
409 {
410         /* The grammar allows for multiple delimiters */
411         while (g_ascii_isspace (*s) || *s == delim)
412                 s++;
413         return s;
414 }
415
416 static const char *
417 skip_item (const char *s, char delim)
418 {
419         gboolean quoted = FALSE;
420         const char *start = s;
421
422         /* A list item ends at the last non-whitespace character
423          * before a delimiter which is not inside a quoted-string. Or
424          * at the end of the string.
425          */
426
427         while (*s) {
428                 if (*s == '"')
429                         quoted = !quoted;
430                 else if (quoted) {
431                         if (*s == '\\' && *(s + 1))
432                                 s++;
433                 } else {
434                         if (*s == delim)
435                                 break;
436                 }
437                 s++;
438         }
439
440         return unskip_lws (s, start);
441 }
442
443 static GSList *
444 parse_list (const char *header, char delim)
445 {
446         GSList *list = NULL;
447         const char *end;
448
449         header = skip_delims (header, delim);
450         while (*header) {
451                 end = skip_item (header, delim);
452                 list = g_slist_prepend (list, g_strndup (header, end - header));
453                 header = skip_delims (end, delim);
454         }
455
456         return g_slist_reverse (list);
457 }
458
459 /**
460  * soup_header_parse_list:
461  * @header: a header value
462  *
463  * Parses a header whose content is described by RFC2616 as
464  * "#something", where "something" does not itself contain commas,
465  * except as part of quoted-strings.
466  *
467  * Return value: (transfer full) (element-type utf8): a #GSList of
468  * list elements, as allocated strings
469  **/
470 GSList *
471 soup_header_parse_list (const char *header)
472 {
473         g_return_val_if_fail (header != NULL, NULL);
474
475         return parse_list (header, ',');
476 }
477
478 typedef struct {
479         char *item;
480         double qval;
481 } QualityItem;
482
483 static int
484 sort_by_qval (const void *a, const void *b)
485 {
486         QualityItem *qia = (QualityItem *)a;
487         QualityItem *qib = (QualityItem *)b;
488
489         if (qia->qval == qib->qval)
490                 return 0;
491         else if (qia->qval < qib->qval)
492                 return 1;
493         else
494                 return -1;
495 }
496
497 /**
498  * soup_header_parse_quality_list:
499  * @header: a header value
500  * @unacceptable: (out) (allow-none) (transfer full) (element-type utf8): on
501  * return, will contain a list of unacceptable values
502  *
503  * Parses a header whose content is a list of items with optional
504  * "qvalue"s (eg, Accept, Accept-Charset, Accept-Encoding,
505  * Accept-Language, TE).
506  *
507  * If @unacceptable is not %NULL, then on return, it will contain the
508  * items with qvalue 0. Either way, those items will be removed from
509  * the main list.
510  *
511  * Return value: (transfer full) (element-type utf8): a #GSList of
512  * acceptable values (as allocated strings), highest-qvalue first.
513  **/
514 GSList *
515 soup_header_parse_quality_list (const char *header, GSList **unacceptable)
516 {
517         GSList *unsorted;
518         QualityItem *array;
519         GSList *sorted, *iter;
520         char *item, *semi;
521         const char *param, *equal, *value;
522         double qval;
523         int n;
524
525         g_return_val_if_fail (header != NULL, NULL);
526
527         if (unacceptable)
528                 *unacceptable = NULL;
529
530         unsorted = soup_header_parse_list (header);
531         array = g_new0 (QualityItem, g_slist_length (unsorted));
532         for (iter = unsorted, n = 0; iter; iter = iter->next) {
533                 item = iter->data;
534                 qval = 1.0;
535                 for (semi = strchr (item, ';'); semi; semi = strchr (semi + 1, ';')) {
536                         param = skip_lws (semi + 1);
537                         if (*param != 'q')
538                                 continue;
539                         equal = skip_lws (param + 1);
540                         if (!equal || *equal != '=')
541                                 continue;
542                         value = skip_lws (equal + 1);
543                         if (!value)
544                                 continue;
545
546                         if (value[0] != '0' && value[0] != '1')
547                                 continue;
548                         qval = (double)(value[0] - '0');
549                         if (value[0] == '0' && value[1] == '.') {
550                                 if (g_ascii_isdigit (value[2])) {
551                                         qval += (double)(value[2] - '0') / 10;
552                                         if (g_ascii_isdigit (value[3])) {
553                                                 qval += (double)(value[3] - '0') / 100;
554                                                 if (g_ascii_isdigit (value[4]))
555                                                         qval += (double)(value[4] - '0') / 1000;
556                                         }
557                                 }
558                         }
559
560                         *semi = '\0';
561                         break;
562                 }
563
564                 if (qval == 0.0) {
565                         if (unacceptable) {
566                                 *unacceptable = g_slist_prepend (*unacceptable,
567                                                                  item);
568                         }
569                 } else {
570                         array[n].item = item;
571                         array[n].qval = qval;
572                         n++;
573                 }
574         }
575         g_slist_free (unsorted);
576
577         qsort (array, n, sizeof (QualityItem), sort_by_qval);
578         sorted = NULL;
579         while (n--)
580                 sorted = g_slist_prepend (sorted, array[n].item);
581         g_free (array);
582
583         return sorted;
584 }
585
586 /**
587  * soup_header_free_list: (skip)
588  * @list: a #GSList returned from soup_header_parse_list() or
589  * soup_header_parse_quality_list()
590  *
591  * Frees @list.
592  **/
593 void
594 soup_header_free_list (GSList *list)
595 {
596         GSList *l;
597
598         for (l = list; l; l = l->next)
599                 g_free (l->data);
600         g_slist_free (list);
601 }
602
603 /**
604  * soup_header_contains:
605  * @header: An HTTP header suitable for parsing with
606  * soup_header_parse_list()
607  * @token: a token
608  *
609  * Parses @header to see if it contains the token @token (matched
610  * case-insensitively). Note that this can't be used with lists
611  * that have qvalues.
612  *
613  * Return value: whether or not @header contains @token
614  **/
615 gboolean
616 soup_header_contains (const char *header, const char *token)
617 {
618         const char *end;
619         guint len = strlen (token);
620
621         g_return_val_if_fail (header != NULL, FALSE);
622         g_return_val_if_fail (token != NULL, FALSE);
623
624         header = skip_delims (header, ',');
625         while (*header) {
626                 end = skip_item (header, ',');
627                 if (end - header == len &&
628                     !g_ascii_strncasecmp (header, token, len))
629                         return TRUE;
630                 header = skip_delims (end, ',');
631         }
632
633         return FALSE;
634 }
635
636 static void
637 decode_quoted_string (char *quoted_string)
638 {
639         char *src, *dst;
640
641         src = quoted_string + 1;
642         dst = quoted_string;
643         while (*src && *src != '"') {
644                 if (*src == '\\' && *(src + 1))
645                         src++;
646                 *dst++ = *src++;
647         }
648         *dst = '\0';
649 }
650
651 static gboolean
652 decode_rfc5987 (char *encoded_string)
653 {
654         char *q, *decoded;
655         gboolean iso_8859_1 = FALSE;
656
657         q = strchr (encoded_string, '\'');
658         if (!q)
659                 return FALSE;
660         if (g_ascii_strncasecmp (encoded_string, "UTF-8",
661                                  q - encoded_string) == 0)
662                 ;
663         else if (g_ascii_strncasecmp (encoded_string, "iso-8859-1",
664                                       q - encoded_string) == 0)
665                 iso_8859_1 = TRUE;
666         else
667                 return FALSE;
668
669         q = strchr (q + 1, '\'');
670         if (!q)
671                 return FALSE;
672
673         decoded = soup_uri_decode (q + 1);
674         if (iso_8859_1) {
675                 char *utf8 =  g_convert_with_fallback (decoded, -1, "UTF-8",
676                                                        "iso-8859-1", "_",
677                                                        NULL, NULL, NULL);
678                 g_free (decoded);
679                 if (!utf8)
680                         return FALSE;
681                 decoded = utf8;
682         }
683
684         /* If encoded_string was UTF-8, then each 3-character %-escape
685          * will be converted to a single byte, and so decoded is
686          * shorter than encoded_string. If encoded_string was
687          * iso-8859-1, then each 3-character %-escape will be
688          * converted into at most 2 bytes in UTF-8, and so it's still
689          * shorter.
690          */
691         strcpy (encoded_string, decoded);
692         g_free (decoded);
693         return TRUE;
694 }
695
696 static GHashTable *
697 parse_param_list (const char *header, char delim)
698 {
699         GHashTable *params;
700         GSList *list, *iter;
701         char *item, *eq, *name_end, *value;
702         gboolean override;
703
704         list = parse_list (header, delim);
705         if (!list)
706                 return NULL;
707
708         params = g_hash_table_new_full (soup_str_case_hash, 
709                                         soup_str_case_equal,
710                                         g_free, NULL);
711
712         for (iter = list; iter; iter = iter->next) {
713                 item = iter->data;
714                 override = FALSE;
715
716                 eq = strchr (item, '=');
717                 if (eq) {
718                         name_end = (char *)unskip_lws (eq, item);
719                         if (name_end == item) {
720                                 /* That's no good... */
721                                 g_free (item);
722                                 continue;
723                         }
724
725                         *name_end = '\0';
726
727                         value = (char *)skip_lws (eq + 1);
728
729                         if (name_end[-1] == '*' && name_end > item + 1) {
730                                 name_end[-1] = '\0';
731                                 if (!decode_rfc5987 (value)) {
732                                         g_free (item);
733                                         continue;
734                                 }
735                                 override = TRUE;
736                         } else if (*value == '"')
737                                 decode_quoted_string (value);
738                 } else
739                         value = NULL;
740
741                 if (override || !g_hash_table_lookup (params, item))
742                         g_hash_table_replace (params, item, value);
743                 else
744                         g_free (item);
745         }
746
747         g_slist_free (list);
748         return params;
749 }
750
751 /**
752  * soup_header_parse_param_list:
753  * @header: a header value
754  *
755  * Parses a header which is a comma-delimited list of something like:
756  * <literal>token [ "=" ( token | quoted-string ) ]</literal>.
757  *
758  * Tokens that don't have an associated value will still be added to
759  * the resulting hash table, but with a %NULL value.
760  * 
761  * This also handles RFC5987 encoding (which in HTTP is mostly used
762  * for giving UTF8-encoded filenames in the Content-Disposition
763  * header).
764  *
765  * Return value: (element-type utf8 utf8) (transfer full): a
766  * #GHashTable of list elements, which can be freed with
767  * soup_header_free_param_list().
768  **/
769 GHashTable *
770 soup_header_parse_param_list (const char *header)
771 {
772         g_return_val_if_fail (header != NULL, NULL);
773
774         return parse_param_list (header, ',');
775 }
776
777 /**
778  * soup_header_parse_semi_param_list:
779  * @header: a header value
780  *
781  * Parses a header which is a semicolon-delimited list of something
782  * like: <literal>token [ "=" ( token | quoted-string ) ]</literal>.
783  *
784  * Tokens that don't have an associated value will still be added to
785  * the resulting hash table, but with a %NULL value.
786  * 
787  * This also handles RFC5987 encoding (which in HTTP is mostly used
788  * for giving UTF8-encoded filenames in the Content-Disposition
789  * header).
790  *
791  * Return value: (element-type utf8 utf8) (transfer full): a
792  * #GHashTable of list elements, which can be freed with
793  * soup_header_free_param_list().
794  *
795  * Since: 2.24
796  **/
797 GHashTable *
798 soup_header_parse_semi_param_list (const char *header)
799 {
800         g_return_val_if_fail (header != NULL, NULL);
801
802         return parse_param_list (header, ';');
803 }
804
805 /**
806  * soup_header_free_param_list:
807  * @param_list: (element-type utf8 utf8): a #GHashTable returned from soup_header_parse_param_list()
808  * or soup_header_parse_semi_param_list()
809  *
810  * Frees @param_list.
811  **/
812 void
813 soup_header_free_param_list (GHashTable *param_list)
814 {
815         g_return_if_fail (param_list != NULL);
816
817         g_hash_table_destroy (param_list);
818 }
819
820 static void
821 append_param_rfc5987 (GString    *string,
822                       const char *name,
823                       const char *value)
824 {
825         char *encoded;
826
827         g_string_append (string, name);
828         g_string_append (string, "*=UTF-8''");
829         encoded = soup_uri_encode (value, " *'%()<>@,;:\\\"/[]?=");
830         g_string_append (string, encoded);
831         g_free (encoded);
832 }
833
834 static void
835 append_param_quoted (GString    *string,
836                      const char *name,
837                      const char *value)
838 {
839         int len;
840
841         g_string_append (string, name);
842         g_string_append (string, "=\"");
843         while (*value) {
844                 while (*value == '\\' || *value == '"') {
845                         g_string_append_c (string, '\\');
846                         g_string_append_c (string, *value++);
847                 }
848                 len = strcspn (value, "\\\"");
849                 g_string_append_len (string, value, len);
850                 value += len;
851         }
852         g_string_append_c (string, '"');
853 }
854
855 static void
856 append_param_internal (GString    *string,
857                        const char *name,
858                        const char *value,
859                        gboolean    allow_token)
860 {
861         const char *v;
862         gboolean use_token = allow_token;
863
864         for (v = value; *v; v++) {
865                 if (*v & 0x80) {
866                         if (g_utf8_validate (value, -1, NULL)) {
867                                 append_param_rfc5987 (string, name, value);
868                                 return;
869                         } else {
870                                 use_token = FALSE;
871                                 break;
872                         }
873                 } else if (!soup_char_is_token (*v))
874                         use_token = FALSE;
875         }
876
877         if (use_token) {
878                 g_string_append (string, name);
879                 g_string_append_c (string, '=');
880                 g_string_append (string, value);
881         } else
882                 append_param_quoted (string, name, value);
883 }
884
885 /**
886  * soup_header_g_string_append_param_quoted:
887  * @string: a #GString being used to construct an HTTP header value
888  * @name: a parameter name
889  * @value: a parameter value
890  *
891  * Appends something like <literal>@name="@value"</literal> to
892  * @string, taking care to escape any quotes or backslashes in @value.
893  *
894  * If @value is (non-ASCII) UTF-8, this will instead use RFC 5987
895  * encoding, just like soup_header_g_string_append_param().
896  *
897  * Since: 2.30
898  **/
899 void
900 soup_header_g_string_append_param_quoted (GString    *string,
901                                           const char *name,
902                                           const char *value)
903 {
904         g_return_if_fail (string != NULL);
905         g_return_if_fail (name != NULL);
906         g_return_if_fail (value != NULL);
907
908         append_param_internal (string, name, value, FALSE);
909 }
910
911 /**
912  * soup_header_g_string_append_param:
913  * @string: a #GString being used to construct an HTTP header value
914  * @name: a parameter name
915  * @value: a parameter value, or %NULL
916  *
917  * Appends something like <literal>@name=@value</literal> to @string,
918  * taking care to quote @value if needed, and if so, to escape any
919  * quotes or backslashes in @value.
920  *
921  * Alternatively, if @value is a non-ASCII UTF-8 string, it will be
922  * appended using RFC5987 syntax. Although in theory this is supposed
923  * to work anywhere in HTTP that uses this style of parameter, in
924  * reality, it can only be used portably with the Content-Disposition
925  * "filename" parameter.
926  *
927  * If @value is %NULL, this will just append @name to @string.
928  *
929  * Since: 2.26
930  **/
931 void
932 soup_header_g_string_append_param (GString    *string,
933                                    const char *name,
934                                    const char *value)
935 {
936         g_return_if_fail (string != NULL);
937         g_return_if_fail (name != NULL);
938
939         if (!value) {
940                 g_string_append (string, name);
941                 return;
942         }
943
944         append_param_internal (string, name, value, TRUE);
945 }