Coverage Report

Created: 2025-07-09 06:18

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