/src/libsoup/libsoup/cookies/soup-cookie.c
Line | Count | Source |
1 | | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ |
2 | | /* |
3 | | * soup-cookie.c |
4 | | * |
5 | | * Copyright (C) 2007 Red Hat, 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-cookie.h" |
16 | | #include "soup-date-utils-private.h" |
17 | | #include "soup-message-headers-private.h" |
18 | | #include "soup-misc.h" |
19 | | #include "soup-uri-utils-private.h" |
20 | | #include "soup.h" |
21 | | |
22 | | /** |
23 | | * SoupCookie: |
24 | | * |
25 | | * Implements HTTP cookies, as described by |
26 | | * [RFC 6265](http://tools.ietf.org/html/rfc6265.txt). |
27 | | * |
28 | | * To have a [class@Session] handle cookies for your appliction |
29 | | * automatically, use a [class@CookieJar]. |
30 | | * |
31 | | * @name and @value will be set for all cookies. If the cookie is |
32 | | * generated from a string that appears to have no name, then @name |
33 | | * will be the empty string. |
34 | | * |
35 | | * @domain and @path give the host or domain, and path within that |
36 | | * host/domain, to restrict this cookie to. If @domain starts with |
37 | | * ".", that indicates a domain (which matches the string after the |
38 | | * ".", or any hostname that has @domain as a suffix). Otherwise, it |
39 | | * is a hostname and must match exactly. |
40 | | * |
41 | | * @expires will be non-%NULL if the cookie uses either the original |
42 | | * "expires" attribute, or the newer "max-age" attribute. If @expires |
43 | | * is %NULL, it indicates that neither "expires" nor "max-age" was |
44 | | * specified, and the cookie expires at the end of the session. |
45 | | * |
46 | | * If @http_only is set, the cookie should not be exposed to untrusted |
47 | | * code (eg, javascript), so as to minimize the danger posed by |
48 | | * cross-site scripting attacks. |
49 | | **/ |
50 | | |
51 | | struct _SoupCookie { |
52 | | char *name; |
53 | | char *value; |
54 | | char *domain; |
55 | | char *path; |
56 | | GDateTime *expires; |
57 | | gboolean secure; |
58 | | gboolean http_only; |
59 | | SoupSameSitePolicy same_site_policy; |
60 | | }; |
61 | | |
62 | 0 | G_DEFINE_BOXED_TYPE (SoupCookie, soup_cookie, soup_cookie_copy, soup_cookie_free) |
63 | 0 |
|
64 | 0 | /** |
65 | 0 | * soup_cookie_copy: |
66 | 0 | * @cookie: a #SoupCookie |
67 | 0 | * |
68 | 0 | * Copies @cookie. |
69 | 0 | * |
70 | 0 | * Returns: a copy of @cookie |
71 | 0 | **/ |
72 | 0 | SoupCookie * |
73 | 0 | soup_cookie_copy (SoupCookie *cookie) |
74 | 0 | { |
75 | 0 | SoupCookie *copy = g_slice_new0 (SoupCookie); |
76 | |
|
77 | 0 | copy->name = g_strdup (cookie->name); |
78 | 0 | copy->value = g_strdup (cookie->value); |
79 | 0 | copy->domain = g_strdup (cookie->domain); |
80 | 0 | copy->path = g_strdup (cookie->path); |
81 | 0 | if (cookie->expires) |
82 | 0 | copy->expires = g_date_time_ref (cookie->expires); |
83 | 0 | copy->secure = cookie->secure; |
84 | 0 | copy->http_only = cookie->http_only; |
85 | 0 | copy->same_site_policy = cookie->same_site_policy; |
86 | |
|
87 | 0 | return copy; |
88 | 0 | } |
89 | | |
90 | | /** |
91 | | * soup_cookie_domain_matches: |
92 | | * @cookie: a #SoupCookie |
93 | | * @host: a URI |
94 | | * |
95 | | * Checks if the @cookie's domain and @host match. |
96 | | * |
97 | | * The domains match if @cookie should be sent when making a request to @host, |
98 | | * or that @cookie should be accepted when receiving a response from @host. |
99 | | * |
100 | | * Returns: %TRUE if the domains match, %FALSE otherwise |
101 | | **/ |
102 | | gboolean |
103 | | soup_cookie_domain_matches (SoupCookie *cookie, const char *host) |
104 | 0 | { |
105 | 0 | g_return_val_if_fail (cookie != NULL, FALSE); |
106 | 0 | g_return_val_if_fail (host != NULL, FALSE); |
107 | | |
108 | 0 | return soup_host_matches_host (cookie->domain, host); |
109 | 0 | } |
110 | | |
111 | | static inline gboolean |
112 | | is_white_space (char c) |
113 | 92.2k | { |
114 | 92.2k | return (c == ' ' || c == '\t'); |
115 | 92.2k | } |
116 | | |
117 | | static inline const char * |
118 | | skip_lws (const char *s) |
119 | 43.9k | { |
120 | 49.5k | while (is_white_space (*s)) |
121 | 5.51k | s++; |
122 | 43.9k | return s; |
123 | 43.9k | } |
124 | | |
125 | | static inline const char * |
126 | | unskip_lws (const char *s, const char *start) |
127 | 40.8k | { |
128 | 46.1k | while (s > start && is_white_space (*(s - 1))) |
129 | 5.32k | s--; |
130 | 40.8k | return s; |
131 | 40.8k | } |
132 | | |
133 | 10.6M | #define is_attr_ender(ch) ((ch) == '\0' || (ch) == ';' || (ch) == ',' || (ch) == '=') |
134 | 7.06M | #define is_value_ender(ch) ((ch) == '\0' || (ch) == ';') |
135 | | |
136 | | static char * |
137 | | parse_value (const char **val_p, gboolean copy) |
138 | 20.6k | { |
139 | 20.6k | const char *start, *end, *p; |
140 | 20.6k | char *value; |
141 | | |
142 | 20.6k | p = *val_p; |
143 | 20.6k | if (*p == '=') |
144 | 17.5k | p++; |
145 | 20.6k | start = skip_lws (p); |
146 | 7.06M | for (p = start; !is_value_ender (*p); p++) |
147 | 7.04M | ; |
148 | 20.6k | end = unskip_lws (p, start); |
149 | | |
150 | 20.6k | if (copy) |
151 | 19.9k | value = g_strndup (start, end - start); |
152 | 776 | else |
153 | 776 | value = NULL; |
154 | | |
155 | 20.6k | *val_p = p; |
156 | 20.6k | return value; |
157 | 20.6k | } |
158 | | |
159 | | static GDateTime * |
160 | | parse_date (const char **val_p) |
161 | 12.8k | { |
162 | 12.8k | char *value; |
163 | 12.8k | GDateTime *date; |
164 | | |
165 | 12.8k | value = parse_value (val_p, TRUE); |
166 | 12.8k | date = soup_date_time_new_from_http_string (value); |
167 | 12.8k | g_free (value); |
168 | 12.8k | return date; |
169 | 12.8k | } |
170 | | |
171 | 1.81k | #define MAX_AGE_CAP_IN_SECONDS 31536000 // 1 year |
172 | 3.39k | #define MAX_ATTRIBUTE_SIZE 1024 |
173 | | |
174 | | static SoupCookie * |
175 | | parse_one_cookie (const char *header, GUri *origin) |
176 | 3.22k | { |
177 | 3.22k | const char *start, *end, *p; |
178 | 3.22k | gboolean has_value; |
179 | 3.22k | SoupCookie *cookie; |
180 | | |
181 | 3.22k | cookie = g_slice_new0 (SoupCookie); |
182 | 3.22k | soup_cookie_set_same_site_policy (cookie, SOUP_SAME_SITE_POLICY_LAX); |
183 | | |
184 | | /* Parse the NAME */ |
185 | 3.22k | start = skip_lws (header); |
186 | 19.0k | for (p = start; !is_attr_ender (*p); p++) |
187 | 15.8k | ; |
188 | 3.22k | if (*p == '=') { |
189 | 53 | end = unskip_lws (p, start); |
190 | 53 | cookie->name = g_strndup (start, end - start); |
191 | 3.17k | } else { |
192 | | /* No NAME; Set cookie->name to "" and then rewind to |
193 | | * re-parse the string as a VALUE. |
194 | | */ |
195 | 3.17k | cookie->name = g_strdup (""); |
196 | 3.17k | p = start; |
197 | 3.17k | } |
198 | | |
199 | | /* Parse the VALUE */ |
200 | 3.22k | cookie->value = parse_value (&p, TRUE); |
201 | | |
202 | 3.22k | if (!*cookie->name && !*cookie->value) { |
203 | 40 | soup_cookie_free (cookie); |
204 | 40 | return NULL; |
205 | 40 | } |
206 | | |
207 | 3.18k | if (strlen (cookie->name) + strlen (cookie->value) > 4096) { |
208 | 1 | soup_cookie_free (cookie); |
209 | 1 | return NULL; |
210 | 1 | } |
211 | | |
212 | | /* Parse attributes */ |
213 | 23.2k | while (*p == ';') { |
214 | 20.0k | start = skip_lws (p + 1); |
215 | 10.6M | for (p = start; !is_attr_ender (*p); p++) |
216 | 10.6M | ; |
217 | 20.0k | end = unskip_lws (p, start); |
218 | | |
219 | 20.0k | has_value = (*p == '='); |
220 | 111k | #define MATCH_NAME(name) ((end - start == strlen (name)) && !g_ascii_strncasecmp (start, name, end - start)) |
221 | | |
222 | 20.0k | if (MATCH_NAME ("domain") && has_value) { |
223 | 709 | char *new_domain = parse_value (&p, TRUE); |
224 | 709 | if (strlen (new_domain) > MAX_ATTRIBUTE_SIZE) { |
225 | 204 | g_free (new_domain); |
226 | 204 | continue; |
227 | 204 | } |
228 | 505 | g_free (cookie->domain); |
229 | 505 | cookie->domain = g_steal_pointer (&new_domain); |
230 | 505 | if (!*cookie->domain) { |
231 | 213 | g_free (cookie->domain); |
232 | 213 | cookie->domain = NULL; |
233 | 213 | } |
234 | 19.3k | } else if (MATCH_NAME ("expires") && has_value) { |
235 | 12.8k | g_clear_pointer (&cookie->expires, g_date_time_unref); |
236 | 12.8k | cookie->expires = parse_date (&p); |
237 | 12.8k | } else if (MATCH_NAME ("httponly")) { |
238 | 445 | cookie->http_only = TRUE; |
239 | 445 | if (has_value) |
240 | 251 | parse_value (&p, FALSE); |
241 | 6.07k | } else if (MATCH_NAME ("max-age") && has_value) { |
242 | 2.01k | char *max_age_str = parse_value (&p, TRUE), *mae; |
243 | 2.01k | if (strlen (max_age_str) > MAX_ATTRIBUTE_SIZE) { |
244 | 203 | g_free (max_age_str); |
245 | 203 | continue; |
246 | 203 | } |
247 | 1.80k | long max_age = strtol (max_age_str, &mae, 10); |
248 | 1.80k | if (!*mae) { |
249 | 1.57k | if (max_age < 0) |
250 | 276 | max_age = 0; |
251 | 1.57k | if (max_age > MAX_AGE_CAP_IN_SECONDS) |
252 | 244 | max_age = MAX_AGE_CAP_IN_SECONDS; |
253 | 1.57k | soup_cookie_set_max_age (cookie, max_age); |
254 | 1.57k | } |
255 | 1.80k | g_free (max_age_str); |
256 | 4.06k | } else if (MATCH_NAME ("path") && has_value) { |
257 | 671 | char *new_path = parse_value (&p, TRUE); |
258 | 671 | if (strlen (new_path) > MAX_ATTRIBUTE_SIZE) { |
259 | 204 | g_free (new_path); |
260 | 204 | continue; |
261 | 204 | } |
262 | 467 | g_free (cookie->path); |
263 | 467 | cookie->path = g_steal_pointer (&new_path); |
264 | 467 | if (*cookie->path != '/') { |
265 | 269 | g_free (cookie->path); |
266 | 269 | cookie->path = NULL; |
267 | 269 | } |
268 | 3.39k | } else if (MATCH_NAME ("secure")) { |
269 | 446 | cookie->secure = TRUE; |
270 | 446 | if (has_value) |
271 | 252 | parse_value (&p, FALSE); |
272 | 2.94k | } else if (MATCH_NAME ("samesite")) { |
273 | 661 | if (has_value) { |
274 | 467 | char *policy = parse_value (&p, TRUE); |
275 | 467 | if (g_ascii_strcasecmp (policy, "None") == 0) |
276 | 194 | soup_cookie_set_same_site_policy (cookie, SOUP_SAME_SITE_POLICY_NONE); |
277 | 273 | else if (g_ascii_strcasecmp (policy, "Strict") == 0) |
278 | 0 | soup_cookie_set_same_site_policy (cookie, SOUP_SAME_SITE_POLICY_STRICT); |
279 | | /* There is an explicit "Lax" value which is the default */ |
280 | 467 | g_free (policy); |
281 | 467 | } |
282 | | /* Note that earlier versions of the same-site RFC treated invalid values as strict but |
283 | | the latest revision assigns invalid SameSite values to Lax. */ |
284 | 2.28k | } else { |
285 | | /* Ignore unknown attributes, but we still have |
286 | | * to skip over the value. |
287 | | */ |
288 | 2.28k | if (has_value) |
289 | 273 | parse_value (&p, FALSE); |
290 | 2.28k | } |
291 | 20.0k | } |
292 | | |
293 | 3.18k | if (cookie->domain) { |
294 | | /* Domain must have at least one '.' (not counting an |
295 | | * initial one. (We check this now, rather than |
296 | | * bailing out sooner, because we don't want to force |
297 | | * any cookies after this one in the Set-Cookie header |
298 | | * to be discarded.) |
299 | | */ |
300 | 80 | if (!strchr (cookie->domain + 1, '.')) { |
301 | 64 | soup_cookie_free (cookie); |
302 | 64 | return NULL; |
303 | 64 | } |
304 | | |
305 | | /* If the domain string isn't an IP addr, and doesn't |
306 | | * start with a '.', prepend one. |
307 | | */ |
308 | 16 | if (!g_hostname_is_ip_address (cookie->domain) && |
309 | 15 | cookie->domain[0] != '.') { |
310 | 14 | char *tmp = g_strdup_printf (".%s", cookie->domain); |
311 | 14 | g_free (cookie->domain); |
312 | 14 | cookie->domain = tmp; |
313 | 14 | } |
314 | 16 | } |
315 | | |
316 | 3.12k | if (origin) { |
317 | | /* Sanity-check domain */ |
318 | 0 | if (cookie->domain) { |
319 | 0 | if (!soup_cookie_domain_matches (cookie, g_uri_get_host (origin))) { |
320 | 0 | soup_cookie_free (cookie); |
321 | 0 | return NULL; |
322 | 0 | } |
323 | 0 | } else |
324 | 0 | cookie->domain = g_strdup (g_uri_get_host (origin)); |
325 | | |
326 | | /* The original cookie spec didn't say that pages |
327 | | * could only set cookies for paths they were under. |
328 | | * RFC 2109 adds that requirement, but some sites |
329 | | * depend on the old behavior |
330 | | * (https://bugzilla.mozilla.org/show_bug.cgi?id=156725#c20). |
331 | | * So we don't check the path. |
332 | | */ |
333 | | |
334 | 0 | if (!cookie->path) { |
335 | 0 | GUri *normalized_origin = soup_uri_copy_with_normalized_flags (origin); |
336 | 0 | char *slash; |
337 | 0 | const char *origin_path = g_uri_get_path (normalized_origin); |
338 | |
|
339 | 0 | slash = strrchr (origin_path, '/'); |
340 | 0 | if (!slash || slash == origin_path) |
341 | 0 | cookie->path = g_strdup ("/"); |
342 | 0 | else { |
343 | 0 | cookie->path = g_strndup (origin_path, |
344 | 0 | slash - origin_path); |
345 | 0 | } |
346 | |
|
347 | 0 | g_uri_unref (normalized_origin); |
348 | 0 | } |
349 | |
|
350 | 3.12k | } else if (!cookie->path) { |
351 | 3.11k | cookie->path = g_strdup ("/"); |
352 | 3.11k | } |
353 | | |
354 | 3.12k | return cookie; |
355 | 3.12k | } |
356 | | |
357 | | static SoupCookie * |
358 | | cookie_new_internal (const char *name, const char *value, |
359 | | const char *domain, const char *path, |
360 | | int max_age) |
361 | 0 | { |
362 | 0 | SoupCookie *cookie; |
363 | |
|
364 | 0 | cookie = g_slice_new0 (SoupCookie); |
365 | 0 | cookie->name = g_strdup (name); |
366 | 0 | cookie->value = g_strdup (value); |
367 | 0 | cookie->domain = g_strdup (domain); |
368 | 0 | cookie->path = g_strdup (path); |
369 | 0 | soup_cookie_set_max_age (cookie, max_age); |
370 | 0 | cookie->same_site_policy = SOUP_SAME_SITE_POLICY_LAX; |
371 | |
|
372 | 0 | return cookie; |
373 | 0 | } |
374 | | |
375 | | /** |
376 | | * soup_cookie_new: |
377 | | * @name: cookie name |
378 | | * @value: cookie value |
379 | | * @domain: cookie domain or hostname |
380 | | * @path: cookie path, or %NULL |
381 | | * @max_age: max age of the cookie, or -1 for a session cookie |
382 | | * |
383 | | * Creates a new [struct@Cookie] with the given attributes. |
384 | | * |
385 | | * Use [method@Cookie.set_secure] and [method@Cookie.set_http_only] if you |
386 | | * need to set those attributes on the returned cookie. |
387 | | * |
388 | | * If @domain starts with ".", that indicates a domain (which matches |
389 | | * the string after the ".", or any hostname that has @domain as a |
390 | | * suffix). Otherwise, it is a hostname and must match exactly. |
391 | | * |
392 | | * @max_age is used to set the "expires" attribute on the cookie; pass |
393 | | * -1 to not include the attribute (indicating that the cookie expires |
394 | | * with the current session), 0 for an already-expired cookie, or a |
395 | | * lifetime in seconds. You can use the constants |
396 | | * %SOUP_COOKIE_MAX_AGE_ONE_HOUR, %SOUP_COOKIE_MAX_AGE_ONE_DAY, |
397 | | * %SOUP_COOKIE_MAX_AGE_ONE_WEEK and %SOUP_COOKIE_MAX_AGE_ONE_YEAR (or |
398 | | * multiples thereof) to calculate this value. (If you really care |
399 | | * about setting the exact time that the cookie will expire, use |
400 | | * [method@Cookie.set_expires].) |
401 | | * |
402 | | * As of version 3.4.0 the default value of a cookie's same-site-policy |
403 | | * is %SOUP_SAME_SITE_POLICY_LAX. |
404 | | * |
405 | | * Returns: a new #SoupCookie. |
406 | | **/ |
407 | | SoupCookie * |
408 | | soup_cookie_new (const char *name, const char *value, |
409 | | const char *domain, const char *path, |
410 | | int max_age) |
411 | 0 | { |
412 | 0 | g_return_val_if_fail (name != NULL, NULL); |
413 | 0 | g_return_val_if_fail (value != NULL, NULL); |
414 | | |
415 | | /* We ought to return if domain is NULL too, but this used to |
416 | | * do be incorrectly documented as legal, and it wouldn't |
417 | | * break anything as long as you called |
418 | | * soup_cookie_set_domain() immediately after. So we warn but |
419 | | * don't return, to discourage that behavior but not actually |
420 | | * break anyone doing it. |
421 | | */ |
422 | 0 | g_warn_if_fail (domain != NULL); |
423 | |
|
424 | 0 | return cookie_new_internal (name, value, domain, path, max_age); |
425 | 0 | } |
426 | | |
427 | | /** |
428 | | * soup_cookie_parse: |
429 | | * @header: a cookie string (eg, the value of a Set-Cookie header) |
430 | | * @origin: (nullable): origin of the cookie |
431 | | * |
432 | | * Parses @header and returns a [struct@Cookie]. |
433 | | * |
434 | | * If @header contains multiple cookies, only the first one will be parsed. |
435 | | * |
436 | | * If @header does not have "path" or "domain" attributes, they will |
437 | | * be defaulted from @origin. If @origin is %NULL, path will default |
438 | | * to "/", but domain will be left as %NULL. Note that this is not a |
439 | | * valid state for a [struct@Cookie], and you will need to fill in some |
440 | | * appropriate string for the domain if you want to actually make use |
441 | | * of the cookie. |
442 | | * |
443 | | * As of version 3.4.0 the default value of a cookie's same-site-policy |
444 | | * is %SOUP_SAME_SITE_POLICY_LAX. |
445 | | * |
446 | | * Returns: (nullable): a new #SoupCookie, or %NULL if it could |
447 | | * not be parsed, or contained an illegal "domain" attribute for a |
448 | | * cookie originating from @origin. |
449 | | **/ |
450 | | SoupCookie * |
451 | | soup_cookie_parse (const char *cookie, GUri *origin) |
452 | 3.22k | { |
453 | 3.22k | g_return_val_if_fail (cookie != NULL, NULL); |
454 | 3.22k | g_return_val_if_fail (origin == NULL || g_uri_get_host (origin) != NULL, NULL); |
455 | | |
456 | 3.22k | return parse_one_cookie (cookie, origin); |
457 | 3.22k | } |
458 | | |
459 | | /** |
460 | | * soup_cookie_get_name: |
461 | | * @cookie: a #SoupCookie |
462 | | * |
463 | | * Gets @cookie's name. |
464 | | * |
465 | | * Returns: @cookie's name |
466 | | **/ |
467 | | const char * |
468 | | soup_cookie_get_name (SoupCookie *cookie) |
469 | 0 | { |
470 | 0 | return cookie->name; |
471 | 0 | } |
472 | | |
473 | | /** |
474 | | * soup_cookie_set_name: |
475 | | * @cookie: a #SoupCookie |
476 | | * @name: the new name |
477 | | * |
478 | | * Sets @cookie's name to @name. |
479 | | **/ |
480 | | void |
481 | | soup_cookie_set_name (SoupCookie *cookie, const char *name) |
482 | 0 | { |
483 | 0 | g_free (cookie->name); |
484 | 0 | cookie->name = g_strdup (name); |
485 | 0 | } |
486 | | |
487 | | /** |
488 | | * soup_cookie_get_value: |
489 | | * @cookie: a #SoupCookie |
490 | | * |
491 | | * Gets @cookie's value. |
492 | | * |
493 | | * Returns: @cookie's value |
494 | | **/ |
495 | | const char * |
496 | | soup_cookie_get_value (SoupCookie *cookie) |
497 | 0 | { |
498 | 0 | return cookie->value; |
499 | 0 | } |
500 | | |
501 | | /** |
502 | | * soup_cookie_set_value: |
503 | | * @cookie: a #SoupCookie |
504 | | * @value: the new value |
505 | | * |
506 | | * Sets @cookie's value to @value. |
507 | | **/ |
508 | | void |
509 | | soup_cookie_set_value (SoupCookie *cookie, const char *value) |
510 | 0 | { |
511 | 0 | g_free (cookie->value); |
512 | 0 | cookie->value = g_strdup (value); |
513 | 0 | } |
514 | | |
515 | | /** |
516 | | * soup_cookie_get_domain: |
517 | | * @cookie: a #SoupCookie |
518 | | * |
519 | | * Gets @cookie's domain. |
520 | | * |
521 | | * Returns: @cookie's domain |
522 | | **/ |
523 | | const char * |
524 | | soup_cookie_get_domain (SoupCookie *cookie) |
525 | 0 | { |
526 | 0 | return cookie->domain; |
527 | 0 | } |
528 | | |
529 | | /** |
530 | | * soup_cookie_set_domain: |
531 | | * @cookie: a #SoupCookie |
532 | | * @domain: the new domain |
533 | | * |
534 | | * Sets @cookie's domain to @domain. |
535 | | **/ |
536 | | void |
537 | | soup_cookie_set_domain (SoupCookie *cookie, const char *domain) |
538 | 0 | { |
539 | 0 | g_free (cookie->domain); |
540 | 0 | cookie->domain = g_strdup (domain); |
541 | 0 | } |
542 | | |
543 | | /** |
544 | | * soup_cookie_get_path: |
545 | | * @cookie: a #SoupCookie |
546 | | * |
547 | | * Gets @cookie's path. |
548 | | * |
549 | | * Returns: @cookie's path |
550 | | **/ |
551 | | const char * |
552 | | soup_cookie_get_path (SoupCookie *cookie) |
553 | 0 | { |
554 | 0 | return cookie->path; |
555 | 0 | } |
556 | | |
557 | | /** |
558 | | * soup_cookie_set_path: |
559 | | * @cookie: a #SoupCookie |
560 | | * @path: the new path |
561 | | * |
562 | | * Sets @cookie's path to @path. |
563 | | **/ |
564 | | void |
565 | | soup_cookie_set_path (SoupCookie *cookie, const char *path) |
566 | 0 | { |
567 | 0 | g_free (cookie->path); |
568 | 0 | cookie->path = g_strdup (path); |
569 | 0 | } |
570 | | |
571 | | /** |
572 | | * soup_cookie_set_max_age: |
573 | | * @cookie: a #SoupCookie |
574 | | * @max_age: the new max age |
575 | | * |
576 | | * Sets @cookie's max age to @max_age. |
577 | | * |
578 | | * If @max_age is -1, the cookie is a session cookie, and will expire at the end |
579 | | * of the client's session. Otherwise, it is the number of seconds until the |
580 | | * cookie expires. You can use the constants %SOUP_COOKIE_MAX_AGE_ONE_HOUR, |
581 | | * %SOUP_COOKIE_MAX_AGE_ONE_DAY, %SOUP_COOKIE_MAX_AGE_ONE_WEEK and |
582 | | * %SOUP_COOKIE_MAX_AGE_ONE_YEAR (or multiples thereof) to calculate this value. |
583 | | * (A value of 0 indicates that the cookie should be considered |
584 | | * already-expired.) |
585 | | * |
586 | | * This sets the same property as [method@Cookie.set_expires]. |
587 | | **/ |
588 | | void |
589 | | soup_cookie_set_max_age (SoupCookie *cookie, int max_age) |
590 | 1.57k | { |
591 | 1.57k | if (cookie->expires) |
592 | 933 | g_date_time_unref (cookie->expires); |
593 | | |
594 | 1.57k | if (max_age == -1) |
595 | 0 | cookie->expires = NULL; |
596 | 1.57k | else if (max_age == 0) { |
597 | | /* Use a date way in the past, to protect against |
598 | | * clock skew. |
599 | | */ |
600 | 1.06k | cookie->expires = g_date_time_new_from_unix_utc (0); |
601 | 1.06k | } else { |
602 | 507 | GDateTime *now = g_date_time_new_now_utc (); |
603 | 507 | cookie->expires = g_date_time_add_seconds (now, max_age); |
604 | 507 | g_date_time_unref (now); |
605 | 507 | } |
606 | 1.57k | } |
607 | | |
608 | | /** |
609 | | * SOUP_COOKIE_MAX_AGE_ONE_HOUR: |
610 | | * |
611 | | * A constant corresponding to 1 hour. |
612 | | * |
613 | | * For use with [ctor@Cookie.new] and [method@Cookie.set_max_age]. |
614 | | **/ |
615 | | /** |
616 | | * SOUP_COOKIE_MAX_AGE_ONE_DAY: |
617 | | * |
618 | | * A constant corresponding to 1 day. |
619 | | * |
620 | | * For use with [ctor@Cookie.new] and [method@Cookie.set_max_age]. |
621 | | **/ |
622 | | /** |
623 | | * SOUP_COOKIE_MAX_AGE_ONE_WEEK: |
624 | | * |
625 | | * A constant corresponding to 1 week. |
626 | | * |
627 | | * For use with [ctor@Cookie.new] and [method@Cookie.set_max_age]. |
628 | | **/ |
629 | | /** |
630 | | * SOUP_COOKIE_MAX_AGE_ONE_YEAR: |
631 | | * |
632 | | * A constant corresponding to 1 year. |
633 | | * |
634 | | * For use with [ctor@Cookie.new] and [method@Cookie.set_max_age]. |
635 | | **/ |
636 | | |
637 | | /** |
638 | | * soup_cookie_get_expires: |
639 | | * @cookie: a #GDateTime |
640 | | * |
641 | | * Gets @cookie's expiration time. |
642 | | * |
643 | | * Returns: (nullable) (transfer none): @cookie's expiration time, which is |
644 | | * owned by @cookie and should not be modified or freed. |
645 | | **/ |
646 | | GDateTime * |
647 | | soup_cookie_get_expires (SoupCookie *cookie) |
648 | 0 | { |
649 | 0 | return cookie->expires; |
650 | 0 | } |
651 | | |
652 | | /** |
653 | | * soup_cookie_set_expires: |
654 | | * @cookie: a #SoupCookie |
655 | | * @expires: the new expiration time, or %NULL |
656 | | * |
657 | | * Sets @cookie's expiration time to @expires. |
658 | | * |
659 | | * If @expires is %NULL, @cookie will be a session cookie and will expire at the |
660 | | * end of the client's session. |
661 | | * |
662 | | * (This sets the same property as [method@Cookie.set_max_age].) |
663 | | **/ |
664 | | void |
665 | | soup_cookie_set_expires (SoupCookie *cookie, GDateTime *expires) |
666 | 0 | { |
667 | 0 | if (cookie->expires) |
668 | 0 | g_date_time_unref (cookie->expires); |
669 | |
|
670 | 0 | if (expires) |
671 | 0 | cookie->expires = g_date_time_ref (expires); |
672 | 0 | else |
673 | 0 | cookie->expires = NULL; |
674 | 0 | } |
675 | | |
676 | | /** |
677 | | * soup_cookie_get_secure: |
678 | | * @cookie: a #SoupCookie |
679 | | * |
680 | | * Gets @cookie's secure attribute. |
681 | | * |
682 | | * Returns: @cookie's secure attribute |
683 | | **/ |
684 | | gboolean |
685 | | soup_cookie_get_secure (SoupCookie *cookie) |
686 | 0 | { |
687 | 0 | return cookie->secure; |
688 | 0 | } |
689 | | |
690 | | /** |
691 | | * soup_cookie_set_secure: |
692 | | * @cookie: a #SoupCookie |
693 | | * @secure: the new value for the secure attribute |
694 | | * |
695 | | * Sets @cookie's secure attribute to @secure. |
696 | | * |
697 | | * If %TRUE, @cookie will only be transmitted from the client to the server over |
698 | | * secure (https) connections. |
699 | | **/ |
700 | | void |
701 | | soup_cookie_set_secure (SoupCookie *cookie, gboolean secure) |
702 | 0 | { |
703 | 0 | cookie->secure = secure; |
704 | 0 | } |
705 | | |
706 | | /** |
707 | | * soup_cookie_get_http_only: |
708 | | * @cookie: a #SoupCookie |
709 | | * |
710 | | * Gets @cookie's HttpOnly attribute. |
711 | | * |
712 | | * Returns: @cookie's HttpOnly attribute |
713 | | **/ |
714 | | gboolean |
715 | | soup_cookie_get_http_only (SoupCookie *cookie) |
716 | 0 | { |
717 | 0 | return cookie->http_only; |
718 | 0 | } |
719 | | |
720 | | /** |
721 | | * soup_cookie_set_http_only: |
722 | | * @cookie: a #SoupCookie |
723 | | * @http_only: the new value for the HttpOnly attribute |
724 | | * |
725 | | * Sets @cookie's HttpOnly attribute to @http_only. |
726 | | * |
727 | | * If %TRUE, @cookie will be marked as "http only", meaning it should not be |
728 | | * exposed to web page scripts or other untrusted code. |
729 | | **/ |
730 | | void |
731 | | soup_cookie_set_http_only (SoupCookie *cookie, gboolean http_only) |
732 | 0 | { |
733 | 0 | cookie->http_only = http_only; |
734 | 0 | } |
735 | | |
736 | | static void |
737 | | serialize_cookie (SoupCookie *cookie, GString *header, gboolean set_cookie) |
738 | 0 | { |
739 | 0 | SoupSameSitePolicy same_site_policy; |
740 | |
|
741 | 0 | if (!*cookie->name && !*cookie->value) |
742 | 0 | return; |
743 | | |
744 | 0 | if (header->len) { |
745 | 0 | if (set_cookie) |
746 | 0 | g_string_append (header, ", "); |
747 | 0 | else |
748 | 0 | g_string_append (header, "; "); |
749 | 0 | } |
750 | |
|
751 | 0 | if (set_cookie || *cookie->name) { |
752 | 0 | g_string_append (header, cookie->name); |
753 | 0 | g_string_append (header, "="); |
754 | 0 | } |
755 | 0 | g_string_append (header, cookie->value); |
756 | 0 | if (!set_cookie) |
757 | 0 | return; |
758 | | |
759 | 0 | if (cookie->expires) { |
760 | 0 | char *timestamp; |
761 | 0 | timestamp = soup_date_time_to_string (cookie->expires, |
762 | 0 | SOUP_DATE_COOKIE); |
763 | 0 | if (timestamp) { |
764 | 0 | g_string_append (header, "; expires="); |
765 | 0 | g_string_append (header, timestamp); |
766 | 0 | g_free (timestamp); |
767 | 0 | } |
768 | 0 | } |
769 | 0 | if (cookie->path) { |
770 | 0 | g_string_append (header, "; path="); |
771 | 0 | g_string_append (header, cookie->path); |
772 | 0 | } |
773 | 0 | if (cookie->domain) { |
774 | 0 | g_string_append (header, "; domain="); |
775 | 0 | g_string_append (header, cookie->domain); |
776 | 0 | } |
777 | |
|
778 | 0 | same_site_policy = soup_cookie_get_same_site_policy (cookie); |
779 | 0 | if (same_site_policy != SOUP_SAME_SITE_POLICY_NONE) { |
780 | 0 | g_string_append (header, "; SameSite="); |
781 | 0 | if (same_site_policy == SOUP_SAME_SITE_POLICY_LAX) |
782 | 0 | g_string_append (header, "Lax"); |
783 | 0 | else |
784 | 0 | g_string_append (header, "Strict"); |
785 | 0 | } |
786 | 0 | if (cookie->secure) |
787 | 0 | g_string_append (header, "; secure"); |
788 | 0 | if (cookie->http_only) |
789 | 0 | g_string_append (header, "; HttpOnly"); |
790 | 0 | } |
791 | | |
792 | | /** |
793 | | * soup_cookie_set_same_site_policy: |
794 | | * @cookie: a #SoupCookie |
795 | | * @policy: a #SoupSameSitePolicy |
796 | | * |
797 | | * When used in conjunction with |
798 | | * [method@CookieJar.get_cookie_list_with_same_site_info] this sets the policy |
799 | | * of when this cookie should be exposed. |
800 | | **/ |
801 | | void |
802 | | soup_cookie_set_same_site_policy (SoupCookie *cookie, |
803 | | SoupSameSitePolicy policy) |
804 | 3.42k | { |
805 | 3.42k | switch (policy) { |
806 | 194 | case SOUP_SAME_SITE_POLICY_NONE: |
807 | 194 | case SOUP_SAME_SITE_POLICY_STRICT: |
808 | 3.42k | case SOUP_SAME_SITE_POLICY_LAX: |
809 | 3.42k | cookie->same_site_policy = policy; |
810 | 3.42k | break; |
811 | 0 | default: |
812 | 0 | g_return_if_reached (); |
813 | 3.42k | } |
814 | 3.42k | } |
815 | | |
816 | | /** |
817 | | * soup_cookie_get_same_site_policy: |
818 | | * @cookie: a #SoupCookie |
819 | | * |
820 | | * Returns the same-site policy for this cookie. |
821 | | * |
822 | | * Returns: a #SoupSameSitePolicy |
823 | | **/ |
824 | | SoupSameSitePolicy |
825 | | soup_cookie_get_same_site_policy (SoupCookie *cookie) |
826 | 0 | { |
827 | 0 | return cookie->same_site_policy; |
828 | 0 | } |
829 | | |
830 | | /** |
831 | | * soup_cookie_to_set_cookie_header: |
832 | | * @cookie: a #SoupCookie |
833 | | * |
834 | | * Serializes @cookie in the format used by the Set-Cookie header. |
835 | | * |
836 | | * i.e. for sending a cookie from a [class@Server] to a client. |
837 | | * |
838 | | * Returns: the header |
839 | | **/ |
840 | | char * |
841 | | soup_cookie_to_set_cookie_header (SoupCookie *cookie) |
842 | 0 | { |
843 | 0 | GString *header = g_string_new (NULL); |
844 | |
|
845 | 0 | serialize_cookie (cookie, header, TRUE); |
846 | 0 | return g_string_free (header, FALSE); |
847 | 0 | } |
848 | | |
849 | | /** |
850 | | * soup_cookie_to_cookie_header: |
851 | | * @cookie: a #SoupCookie |
852 | | * |
853 | | * Serializes @cookie in the format used by the Cookie header (ie, for |
854 | | * returning a cookie from a [class@Session] to a server). |
855 | | * |
856 | | * Returns: the header |
857 | | **/ |
858 | | char * |
859 | | soup_cookie_to_cookie_header (SoupCookie *cookie) |
860 | 0 | { |
861 | 0 | GString *header = g_string_new (NULL); |
862 | |
|
863 | 0 | serialize_cookie (cookie, header, FALSE); |
864 | 0 | return g_string_free (header, FALSE); |
865 | 0 | } |
866 | | |
867 | | /** |
868 | | * soup_cookie_free: |
869 | | * @cookie: a #SoupCookie |
870 | | * |
871 | | * Frees @cookie. |
872 | | **/ |
873 | | void |
874 | | soup_cookie_free (SoupCookie *cookie) |
875 | 3.22k | { |
876 | 3.22k | g_return_if_fail (cookie != NULL); |
877 | | |
878 | 3.22k | g_free (cookie->name); |
879 | 3.22k | g_free (cookie->value); |
880 | 3.22k | g_free (cookie->domain); |
881 | 3.22k | g_free (cookie->path); |
882 | 3.22k | g_clear_pointer (&cookie->expires, g_date_time_unref); |
883 | | |
884 | 3.22k | g_dataset_destroy (cookie); |
885 | 3.22k | g_slice_free (SoupCookie, cookie); |
886 | 3.22k | } |
887 | | |
888 | | /** |
889 | | * soup_cookies_from_response: |
890 | | * @msg: a #SoupMessage containing a "Set-Cookie" response header |
891 | | * |
892 | | * Parses @msg's Set-Cookie response headers and returns a [struct@GLib.SList] |
893 | | * of `SoupCookie`s. |
894 | | * |
895 | | * Cookies that do not specify "path" or "domain" attributes will have their |
896 | | * values defaulted from @msg. |
897 | | * |
898 | | * Returns: (element-type SoupCookie) (transfer full): a #GSList of |
899 | | * `SoupCookie`s, which can be freed with [method@Cookie.free]. |
900 | | **/ |
901 | | GSList * |
902 | | soup_cookies_from_response (SoupMessage *msg) |
903 | 0 | { |
904 | 0 | GUri *origin; |
905 | 0 | const char *name, *value; |
906 | 0 | SoupCookie *cookie; |
907 | 0 | GSList *cookies = NULL; |
908 | 0 | SoupMessageHeadersIter iter; |
909 | |
|
910 | 0 | origin = soup_message_get_uri (msg); |
911 | | |
912 | | /* We have to use soup_message_headers_iter rather than |
913 | | * soup_message_headers_get_list() since Set-Cookie isn't |
914 | | * properly mergeable/unmergeable. |
915 | | */ |
916 | 0 | soup_message_headers_iter_init (&iter, soup_message_get_response_headers (msg)); |
917 | 0 | while (soup_message_headers_iter_next (&iter, &name, &value)) { |
918 | 0 | if (g_ascii_strcasecmp (name, "Set-Cookie") != 0) |
919 | 0 | continue; |
920 | | |
921 | 0 | cookie = parse_one_cookie (value, origin); |
922 | 0 | if (cookie) |
923 | 0 | cookies = g_slist_prepend (cookies, cookie); |
924 | 0 | } |
925 | 0 | return g_slist_reverse (cookies); |
926 | 0 | } |
927 | | |
928 | | /** |
929 | | * soup_cookies_from_request: |
930 | | * @msg: a #SoupMessage containing a "Cookie" request header |
931 | | * |
932 | | * Parses @msg's Cookie request header and returns a [struct@GLib.SList] of |
933 | | * `SoupCookie`s. |
934 | | * |
935 | | * As the "Cookie" header, unlike "Set-Cookie", only contains cookie names and |
936 | | * values, none of the other [struct@Cookie] fields will be filled in. (Thus, you |
937 | | * can't generally pass a cookie returned from this method directly to |
938 | | * [func@cookies_to_response].) |
939 | | * |
940 | | * Returns: (element-type SoupCookie) (transfer full): a #GSList of |
941 | | * `SoupCookie`s, which can be freed with [method@Cookie.free]. |
942 | | **/ |
943 | | GSList * |
944 | | soup_cookies_from_request (SoupMessage *msg) |
945 | 0 | { |
946 | 0 | SoupCookie *cookie; |
947 | 0 | GSList *cookies = NULL; |
948 | 0 | GHashTable *params; |
949 | 0 | GHashTableIter iter; |
950 | 0 | gpointer name, value; |
951 | 0 | const char *header; |
952 | |
|
953 | 0 | header = soup_message_headers_get_one_common (soup_message_get_request_headers (msg), SOUP_HEADER_COOKIE); |
954 | 0 | if (!header) |
955 | 0 | return NULL; |
956 | | |
957 | 0 | params = soup_header_parse_semi_param_list (header); |
958 | 0 | g_hash_table_iter_init (&iter, params); |
959 | 0 | while (g_hash_table_iter_next (&iter, &name, &value)) { |
960 | 0 | if (name && value) { |
961 | 0 | cookie = cookie_new_internal (name, value, |
962 | 0 | NULL, NULL, 0); |
963 | 0 | cookies = g_slist_prepend (cookies, cookie); |
964 | 0 | } |
965 | 0 | } |
966 | 0 | soup_header_free_param_list (params); |
967 | |
|
968 | 0 | return g_slist_reverse (cookies); |
969 | 0 | } |
970 | | |
971 | | /** |
972 | | * soup_cookies_to_response: |
973 | | * @cookies: (element-type SoupCookie): a #GSList of [struct@Cookie] |
974 | | * @msg: a #SoupMessage |
975 | | * |
976 | | * Appends a "Set-Cookie" response header to @msg for each cookie in |
977 | | * @cookies. |
978 | | * |
979 | | * This is in addition to any other "Set-Cookie" headers |
980 | | * @msg may already have. |
981 | | **/ |
982 | | void |
983 | | soup_cookies_to_response (GSList *cookies, SoupMessage *msg) |
984 | 0 | { |
985 | 0 | GString *header; |
986 | |
|
987 | 0 | header = g_string_new (NULL); |
988 | 0 | while (cookies) { |
989 | 0 | serialize_cookie (cookies->data, header, TRUE); |
990 | 0 | soup_message_headers_append_common (soup_message_get_response_headers (msg), |
991 | 0 | SOUP_HEADER_SET_COOKIE, header->str); |
992 | 0 | g_string_truncate (header, 0); |
993 | 0 | cookies = cookies->next; |
994 | 0 | } |
995 | 0 | g_string_free (header, TRUE); |
996 | 0 | } |
997 | | |
998 | | /** |
999 | | * soup_cookies_to_request: |
1000 | | * @cookies: (element-type SoupCookie): a #GSList of [struct@Cookie] |
1001 | | * @msg: a #SoupMessage |
1002 | | * |
1003 | | * Adds the name and value of each cookie in @cookies to @msg's |
1004 | | * "Cookie" request. |
1005 | | * |
1006 | | * If @msg already has a "Cookie" request header, these cookies will be appended |
1007 | | * to the cookies already present. Be careful that you do not append the same |
1008 | | * cookies twice, eg, when requeuing a message. |
1009 | | **/ |
1010 | | void |
1011 | | soup_cookies_to_request (GSList *cookies, SoupMessage *msg) |
1012 | 0 | { |
1013 | 0 | GString *header; |
1014 | |
|
1015 | 0 | header = g_string_new (soup_message_headers_get_one_common (soup_message_get_request_headers (msg), |
1016 | 0 | SOUP_HEADER_COOKIE)); |
1017 | 0 | while (cookies) { |
1018 | 0 | serialize_cookie (cookies->data, header, FALSE); |
1019 | 0 | cookies = cookies->next; |
1020 | 0 | } |
1021 | 0 | soup_message_headers_replace_common (soup_message_get_request_headers (msg), |
1022 | 0 | SOUP_HEADER_COOKIE, header->str); |
1023 | 0 | g_string_free (header, TRUE); |
1024 | 0 | } |
1025 | | |
1026 | | /** |
1027 | | * soup_cookies_free: (skip) |
1028 | | * @cookies: (element-type SoupCookie): a #GSList of [struct@Cookie] |
1029 | | * |
1030 | | * Frees @cookies. |
1031 | | **/ |
1032 | | void |
1033 | | soup_cookies_free (GSList *cookies) |
1034 | 0 | { |
1035 | 0 | g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free); |
1036 | 0 | } |
1037 | | |
1038 | | /** |
1039 | | * soup_cookies_to_cookie_header: |
1040 | | * @cookies: (element-type SoupCookie): a #GSList of [struct@Cookie] |
1041 | | * |
1042 | | * Serializes a [struct@GLib.SList] of [struct@Cookie] into a string suitable for |
1043 | | * setting as the value of the "Cookie" header. |
1044 | | * |
1045 | | * Returns: the serialization of @cookies |
1046 | | **/ |
1047 | | char * |
1048 | | soup_cookies_to_cookie_header (GSList *cookies) |
1049 | 0 | { |
1050 | 0 | GString *str; |
1051 | |
|
1052 | 0 | g_return_val_if_fail (cookies != NULL, NULL); |
1053 | | |
1054 | 0 | str = g_string_new (NULL); |
1055 | 0 | while (cookies) { |
1056 | 0 | serialize_cookie (cookies->data, str, FALSE); |
1057 | 0 | cookies = cookies->next; |
1058 | 0 | } |
1059 | |
|
1060 | 0 | return g_string_free (str, FALSE); |
1061 | 0 | } |
1062 | | |
1063 | | /** |
1064 | | * soup_cookie_applies_to_uri: |
1065 | | * @cookie: a #SoupCookie |
1066 | | * @uri: a #GUri |
1067 | | * |
1068 | | * Tests if @cookie should be sent to @uri. |
1069 | | * |
1070 | | * (At the moment, this does not check that @cookie's domain matches |
1071 | | * @uri, because it assumes that the caller has already done that. |
1072 | | * But don't rely on that; it may change in the future.) |
1073 | | * |
1074 | | * Returns: %TRUE if @cookie should be sent to @uri, %FALSE if not |
1075 | | **/ |
1076 | | gboolean |
1077 | | soup_cookie_applies_to_uri (SoupCookie *cookie, GUri *uri) |
1078 | 0 | { |
1079 | 0 | int plen; |
1080 | |
|
1081 | 0 | g_return_val_if_fail (cookie != NULL, FALSE); |
1082 | 0 | g_return_val_if_fail (uri != NULL, FALSE); |
1083 | | |
1084 | 0 | if (cookie->secure && !soup_uri_is_https (uri)) |
1085 | 0 | return FALSE; |
1086 | | |
1087 | 0 | if (cookie->expires && soup_date_time_is_past (cookie->expires)) |
1088 | 0 | return FALSE; |
1089 | | |
1090 | 0 | plen = strlen (cookie->path); |
1091 | 0 | if (plen == 0) |
1092 | 0 | return TRUE; |
1093 | | |
1094 | 0 | GUri *normalized_uri = soup_uri_copy_with_normalized_flags (uri); |
1095 | 0 | const char *uri_path = g_uri_get_path (normalized_uri); |
1096 | 0 | if (strncmp (cookie->path, uri_path, plen) != 0 || |
1097 | 0 | (cookie->path[plen - 1] != '/' && uri_path[plen] && |
1098 | 0 | uri_path[plen] != '/')) { |
1099 | 0 | g_uri_unref (normalized_uri); |
1100 | 0 | return FALSE; |
1101 | 0 | } |
1102 | | |
1103 | 0 | g_uri_unref (normalized_uri); |
1104 | 0 | return TRUE; |
1105 | 0 | } |
1106 | | |
1107 | | /** |
1108 | | * soup_cookie_equal: |
1109 | | * @cookie1: a #SoupCookie |
1110 | | * @cookie2: a #SoupCookie |
1111 | | * |
1112 | | * Tests if @cookie1 and @cookie2 are equal. |
1113 | | * |
1114 | | * Note that currently, this does not check that the cookie domains |
1115 | | * match. This may change in the future. |
1116 | | * |
1117 | | * Returns: whether the cookies are equal. |
1118 | | */ |
1119 | | gboolean |
1120 | | soup_cookie_equal (SoupCookie *cookie1, SoupCookie *cookie2) |
1121 | 0 | { |
1122 | 0 | g_return_val_if_fail (cookie1, FALSE); |
1123 | 0 | g_return_val_if_fail (cookie2, FALSE); |
1124 | | |
1125 | 0 | return (!strcmp (cookie1->name, cookie2->name) && |
1126 | 0 | !strcmp (cookie1->value, cookie2->value) && |
1127 | 0 | !g_strcmp0 (cookie1->path, cookie2->path)); |
1128 | 0 | } |