1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-headers.c: HTTP message header parsing
5 * Copyright (C) 2001-2003, Ximian, Inc.
13 #include "soup-headers.h"
14 #include "soup-misc.h"
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
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
28 * This is a low-level method; normally you would use
29 * soup_headers_parse_request() or soup_headers_parse_response().
31 * Return value: success or failure
36 soup_headers_parse (const char *str, int len, SoupMessageHeaders *dest)
38 const char *headers_start;
39 char *headers_copy, *name, *name_end, *value, *value_end;
41 gboolean success = FALSE;
43 g_return_val_if_fail (str != NULL, FALSE);
44 g_return_val_if_fail (dest != NULL, FALSE);
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.
50 if (memchr (str, '\0', len))
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.
58 /* Skip over the Request-Line / Status-Line */
59 headers_start = memchr (str, '\n', len);
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.
67 headers_copy = g_strndup (headers_start, len - (headers_start - str));
68 value_end = headers_copy;
70 while (*(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
79 value_end = strchr (name, '\n');
85 /* Find the end of the value; ie, an end-of-line that
86 * isn't followed by a continuation line.
89 value_end = strchr (name, '\n');
92 while (*(value_end + 1) == ' ' || *(value_end + 1) == '\t') {
93 value_end = strchr (value_end + 1, '\n');
101 /* Skip leading whitespace */
102 while (value < value_end &&
103 (*value == ' ' || *value == '\t' ||
104 *value == '\r' || *value == '\n'))
107 /* Collapse continuation lines */
108 while ((eol = strchr (value, '\n'))) {
109 /* find start of next line */
111 while (*sol == ' ' || *sol == '\t')
114 /* back up over trailing whitespace on current line */
115 while (eol[-1] == ' ' || eol[-1] == '\t' || eol[-1] == '\r')
118 /* Delete all but one SP */
120 g_memmove (eol + 1, sol, strlen (sol) + 1);
123 /* clip trailing whitespace */
124 eol = strchr (value, '\0');
125 while (eol > value &&
126 (eol[-1] == ' ' || eol[-1] == '\t' || eol[-1] == '\r'))
130 soup_message_headers_append (dest, name, value);
135 g_free (headers_copy);
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
146 * @req_path: (out) (allow-none): if non-%NULL, will be filled in with the
148 * @ver: (out) (allow-none): if non-%NULL, will be filled in with the HTTP
151 * Parses the headers of an HTTP request in @str and stores the
152 * results in @req_method, @req_path, @ver, and @req_headers.
154 * Beware that @req_headers may be modified even on failure.
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.
160 soup_headers_parse_request (const char *str,
162 SoupMessageHeaders *req_headers,
165 SoupHTTPVersion *ver)
167 const char *method, *method_end, *path, *path_end;
168 const char *version, *version_end, *headers;
169 unsigned long major_version, minor_version;
172 g_return_val_if_fail (str && *str, SOUP_STATUS_MALFORMED);
174 /* RFC 2616 4.1 "servers SHOULD ignore any empty line(s)
175 * received where a Request-Line is expected."
177 while ((*str == '\r' || *str == '\n') && len > 0) {
182 return SOUP_STATUS_BAD_REQUEST;
184 /* RFC 2616 19.3 "[servers] SHOULD accept any amount of SP or
185 * HT characters between [Request-Line] fields"
188 method = method_end = str;
189 while (method_end < str + len && *method_end != ' ' && *method_end != '\t')
191 if (method_end >= str + len)
192 return SOUP_STATUS_BAD_REQUEST;
195 while (path < str + len && (*path == ' ' || *path == '\t'))
197 if (path >= str + len)
198 return SOUP_STATUS_BAD_REQUEST;
201 while (path_end < str + len && *path_end != ' ' && *path_end != '\t')
203 if (path_end >= str + len)
204 return SOUP_STATUS_BAD_REQUEST;
207 while (version < str + len && (*version == ' ' || *version == '\t'))
209 if (version + 8 >= str + len)
210 return SOUP_STATUS_BAD_REQUEST;
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);
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;
225 headers = version_end;
226 while (headers < str + len && (*headers == '\r' || *headers == ' '))
228 if (headers >= str + len || *headers != '\n')
229 return SOUP_STATUS_BAD_REQUEST;
231 if (!soup_headers_parse (str, len, req_headers))
232 return SOUP_STATUS_BAD_REQUEST;
234 if (soup_message_headers_get_expectations (req_headers) &
235 SOUP_EXPECTATION_UNRECOGNIZED)
236 return SOUP_STATUS_EXPECTATION_FAILED;
238 if (minor_version == 0)
239 soup_message_headers_clean_connection_headers (req_headers);
242 *req_method = g_strndup (method, method_end - method);
244 *req_path = g_strndup (path, path_end - path);
246 *ver = (minor_version == 0) ? SOUP_HTTP_1_0 : SOUP_HTTP_1_1;
248 return SOUP_STATUS_OK;
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
256 * @status_code: (out) (allow-none): if non-%NULL, will be filled in with
258 * @reason_phrase: (out) (allow-none): if non-%NULL, will be filled in with
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".
265 * Return value: %TRUE if @status_line was parsed successfully.
268 soup_headers_parse_status_line (const char *status_line,
269 SoupHTTPVersion *ver,
271 char **reason_phrase)
273 unsigned long major_version, minor_version, code;
274 const char *code_start, *code_end, *phrase_start, *phrase_end;
277 g_return_val_if_fail (status_line != NULL, FALSE);
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]))
284 minor_version = strtoul (p + 1, &p, 10);
285 if (major_version != 1)
287 if (minor_version > 1)
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 */
294 *ver = SOUP_HTTP_1_0;
295 p = (char *)status_line + 3;
300 while (*code_start == ' ' || *code_start == '\t')
302 code_end = code_start;
303 while (*code_end >= '0' && *code_end <= '9')
305 if (code_end != code_start + 3)
307 code = atoi (code_start);
308 if (code < 100 || code > 599)
313 phrase_start = code_end;
314 while (*phrase_start == ' ' || *phrase_start == '\t')
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'))
321 *reason_phrase = g_strndup (phrase_start, phrase_end - phrase_start);
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
333 * @status_code: (out) (allow-none): if non-%NULL, will be filled in with
335 * @reason_phrase: (out) (allow-none): if non-%NULL, will be filled in with
338 * Parses the headers of an HTTP response in @str and stores the
339 * results in @ver, @status_code, @reason_phrase, and @headers.
341 * Beware that @headers may be modified even on failure.
343 * Return value: success or failure.
346 soup_headers_parse_response (const char *str,
348 SoupMessageHeaders *headers,
349 SoupHTTPVersion *ver,
351 char **reason_phrase)
353 SoupHTTPVersion version;
355 g_return_val_if_fail (str && *str, FALSE);
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.
361 while ((*str == '\r' || *str == '\n') && len > 0) {
368 if (!soup_headers_parse (str, len, headers))
371 if (!soup_headers_parse_status_line (str,
380 if (version == SOUP_HTTP_1_0)
381 soup_message_headers_clean_connection_headers (headers);
388 * Parsing of specific HTTP header types
392 skip_lws (const char *s)
394 while (g_ascii_isspace (*s))
400 unskip_lws (const char *s, const char *start)
402 while (s > start && g_ascii_isspace (*(s - 1)))
408 skip_delims (const char *s, char delim)
410 /* The grammar allows for multiple delimiters */
411 while (g_ascii_isspace (*s) || *s == delim)
417 skip_item (const char *s, char delim)
419 gboolean quoted = FALSE;
420 const char *start = s;
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.
431 if (*s == '\\' && *(s + 1))
440 return unskip_lws (s, start);
444 parse_list (const char *header, char delim)
449 header = skip_delims (header, delim);
451 end = skip_item (header, delim);
452 list = g_slist_prepend (list, g_strndup (header, end - header));
453 header = skip_delims (end, delim);
456 return g_slist_reverse (list);
460 * soup_header_parse_list:
461 * @header: a header value
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.
467 * Return value: (transfer full) (element-type utf8): a #GSList of
468 * list elements, as allocated strings
471 soup_header_parse_list (const char *header)
473 g_return_val_if_fail (header != NULL, NULL);
475 return parse_list (header, ',');
484 sort_by_qval (const void *a, const void *b)
486 QualityItem *qia = (QualityItem *)a;
487 QualityItem *qib = (QualityItem *)b;
489 if (qia->qval == qib->qval)
491 else if (qia->qval < qib->qval)
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
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).
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
511 * Return value: (transfer full) (element-type utf8): a #GSList of
512 * acceptable values (as allocated strings), highest-qvalue first.
515 soup_header_parse_quality_list (const char *header, GSList **unacceptable)
519 GSList *sorted, *iter;
521 const char *param, *equal, *value;
525 g_return_val_if_fail (header != NULL, NULL);
528 *unacceptable = NULL;
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) {
535 for (semi = strchr (item, ';'); semi; semi = strchr (semi + 1, ';')) {
536 param = skip_lws (semi + 1);
539 equal = skip_lws (param + 1);
540 if (!equal || *equal != '=')
542 value = skip_lws (equal + 1);
546 if (value[0] != '0' && value[0] != '1')
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;
566 *unacceptable = g_slist_prepend (*unacceptable,
570 array[n].item = item;
571 array[n].qval = qval;
575 g_slist_free (unsorted);
577 qsort (array, n, sizeof (QualityItem), sort_by_qval);
580 sorted = g_slist_prepend (sorted, array[n].item);
587 * soup_header_free_list: (skip)
588 * @list: a #GSList returned from soup_header_parse_list() or
589 * soup_header_parse_quality_list()
594 soup_header_free_list (GSList *list)
598 for (l = list; l; l = l->next)
604 * soup_header_contains:
605 * @header: An HTTP header suitable for parsing with
606 * soup_header_parse_list()
609 * Parses @header to see if it contains the token @token (matched
610 * case-insensitively). Note that this can't be used with lists
613 * Return value: whether or not @header contains @token
616 soup_header_contains (const char *header, const char *token)
619 guint len = strlen (token);
621 g_return_val_if_fail (header != NULL, FALSE);
622 g_return_val_if_fail (token != NULL, FALSE);
624 header = skip_delims (header, ',');
626 end = skip_item (header, ',');
627 if (end - header == len &&
628 !g_ascii_strncasecmp (header, token, len))
630 header = skip_delims (end, ',');
637 decode_quoted_string (char *quoted_string)
641 src = quoted_string + 1;
643 while (*src && *src != '"') {
644 if (*src == '\\' && *(src + 1))
652 decode_rfc5987 (char *encoded_string)
655 gboolean iso_8859_1 = FALSE;
657 q = strchr (encoded_string, '\'');
660 if (g_ascii_strncasecmp (encoded_string, "UTF-8",
661 q - encoded_string) == 0)
663 else if (g_ascii_strncasecmp (encoded_string, "iso-8859-1",
664 q - encoded_string) == 0)
669 q = strchr (q + 1, '\'');
673 decoded = soup_uri_decode (q + 1);
675 char *utf8 = g_convert_with_fallback (decoded, -1, "UTF-8",
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
691 strcpy (encoded_string, decoded);
697 parse_param_list (const char *header, char delim)
701 char *item, *eq, *name_end, *value;
704 list = parse_list (header, delim);
708 params = g_hash_table_new_full (soup_str_case_hash,
712 for (iter = list; iter; iter = iter->next) {
716 eq = strchr (item, '=');
718 name_end = (char *)unskip_lws (eq, item);
719 if (name_end == item) {
720 /* That's no good... */
727 value = (char *)skip_lws (eq + 1);
729 if (name_end[-1] == '*' && name_end > item + 1) {
731 if (!decode_rfc5987 (value)) {
736 } else if (*value == '"')
737 decode_quoted_string (value);
741 if (override || !g_hash_table_lookup (params, item))
742 g_hash_table_replace (params, item, value);
752 * soup_header_parse_param_list:
753 * @header: a header value
755 * Parses a header which is a comma-delimited list of something like:
756 * <literal>token [ "=" ( token | quoted-string ) ]</literal>.
758 * Tokens that don't have an associated value will still be added to
759 * the resulting hash table, but with a %NULL value.
761 * This also handles RFC5987 encoding (which in HTTP is mostly used
762 * for giving UTF8-encoded filenames in the Content-Disposition
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().
770 soup_header_parse_param_list (const char *header)
772 g_return_val_if_fail (header != NULL, NULL);
774 return parse_param_list (header, ',');
778 * soup_header_parse_semi_param_list:
779 * @header: a header value
781 * Parses a header which is a semicolon-delimited list of something
782 * like: <literal>token [ "=" ( token | quoted-string ) ]</literal>.
784 * Tokens that don't have an associated value will still be added to
785 * the resulting hash table, but with a %NULL value.
787 * This also handles RFC5987 encoding (which in HTTP is mostly used
788 * for giving UTF8-encoded filenames in the Content-Disposition
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().
798 soup_header_parse_semi_param_list (const char *header)
800 g_return_val_if_fail (header != NULL, NULL);
802 return parse_param_list (header, ';');
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()
813 soup_header_free_param_list (GHashTable *param_list)
815 g_return_if_fail (param_list != NULL);
817 g_hash_table_destroy (param_list);
821 append_param_rfc5987 (GString *string,
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);
835 append_param_quoted (GString *string,
841 g_string_append (string, name);
842 g_string_append (string, "=\"");
844 while (*value == '\\' || *value == '"') {
845 g_string_append_c (string, '\\');
846 g_string_append_c (string, *value++);
848 len = strcspn (value, "\\\"");
849 g_string_append_len (string, value, len);
852 g_string_append_c (string, '"');
856 append_param_internal (GString *string,
859 gboolean allow_token)
862 gboolean use_token = allow_token;
864 for (v = value; *v; v++) {
866 if (g_utf8_validate (value, -1, NULL)) {
867 append_param_rfc5987 (string, name, value);
873 } else if (!soup_char_is_token (*v))
878 g_string_append (string, name);
879 g_string_append_c (string, '=');
880 g_string_append (string, value);
882 append_param_quoted (string, name, value);
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
891 * Appends something like <literal>@name="@value"</literal> to
892 * @string, taking care to escape any quotes or backslashes in @value.
894 * If @value is (non-ASCII) UTF-8, this will instead use RFC 5987
895 * encoding, just like soup_header_g_string_append_param().
900 soup_header_g_string_append_param_quoted (GString *string,
904 g_return_if_fail (string != NULL);
905 g_return_if_fail (name != NULL);
906 g_return_if_fail (value != NULL);
908 append_param_internal (string, name, value, FALSE);
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
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.
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.
927 * If @value is %NULL, this will just append @name to @string.
932 soup_header_g_string_append_param (GString *string,
936 g_return_if_fail (string != NULL);
937 g_return_if_fail (name != NULL);
940 g_string_append (string, name);
944 append_param_internal (string, name, value, TRUE);