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