1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2010 Igalia S.L.
4 */
5
6 #include "test-utils.h"
7
8 SoupServer *server;
9 SoupURI *first_party_uri, *third_party_uri;
10 const char *first_party = "http://127.0.0.1/";
11 const char *third_party = "http://localhost/";
12
13 static void
server_callback(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * context,gpointer data)14 server_callback (SoupServer *server, SoupMessage *msg,
15 const char *path, GHashTable *query,
16 SoupClientContext *context, gpointer data)
17 {
18 if (g_str_equal (path, "/index.html")) {
19 soup_message_headers_replace (msg->response_headers,
20 "Set-Cookie",
21 "foo=bar");
22 } else if (g_str_equal (path, "/foo.jpg")) {
23 soup_message_headers_replace (msg->response_headers,
24 "Set-Cookie",
25 "baz=qux");
26 } else if (soup_message_headers_get_one (msg->request_headers,
27 "Echo-Set-Cookie")) {
28 soup_message_headers_replace (msg->response_headers,
29 "Set-Cookie",
30 soup_message_headers_get_one (msg->request_headers,
31 "Echo-Set-Cookie"));
32 }
33
34 soup_message_set_status (msg, SOUP_STATUS_OK);
35 }
36
37 typedef struct {
38 SoupCookieJarAcceptPolicy policy;
39 gboolean try_third_party_again;
40 int n_cookies;
41 } CookiesForPolicy;
42
43 static const CookiesForPolicy validResults[] = {
44 { SOUP_COOKIE_JAR_ACCEPT_ALWAYS, FALSE, 2 },
45 { SOUP_COOKIE_JAR_ACCEPT_NEVER, FALSE, 0 },
46 { SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY, FALSE, 1 },
47 { SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY, FALSE, 1 },
48 { SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY, TRUE, 2 }
49 };
50
51 static void
do_cookies_accept_policy_test(void)52 do_cookies_accept_policy_test (void)
53 {
54 SoupSession *session;
55 SoupMessage *msg;
56 SoupURI *uri;
57 SoupCookieJar *jar;
58 GSList *l, *p;
59 int i;
60
61 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
62 soup_session_add_feature_by_type (session, SOUP_TYPE_COOKIE_JAR);
63 jar = SOUP_COOKIE_JAR (soup_session_get_feature (session, SOUP_TYPE_COOKIE_JAR));
64
65 for (i = 0; i < G_N_ELEMENTS (validResults); i++) {
66 soup_cookie_jar_set_accept_policy (jar, validResults[i].policy);
67
68 /* We can't use two servers due to limitations in
69 * test_server, so let's swap first and third party here
70 * to simulate a cookie coming from a third party.
71 */
72 uri = soup_uri_new_with_base (first_party_uri, "/foo.jpg");
73 msg = soup_message_new_from_uri ("GET", uri);
74 soup_message_set_first_party (msg, third_party_uri);
75 soup_session_send_message (session, msg);
76 soup_uri_free (uri);
77 g_object_unref (msg);
78
79 uri = soup_uri_new_with_base (first_party_uri, "/index.html");
80 msg = soup_message_new_from_uri ("GET", uri);
81 soup_message_set_first_party (msg, first_party_uri);
82 soup_session_send_message (session, msg);
83 soup_uri_free (uri);
84 g_object_unref (msg);
85
86 if (validResults[i].try_third_party_again) {
87 uri = soup_uri_new_with_base (first_party_uri, "/foo.jpg");
88 msg = soup_message_new_from_uri ("GET", uri);
89 soup_message_set_first_party (msg, third_party_uri);
90 soup_session_send_message (session, msg);
91 soup_uri_free (uri);
92 g_object_unref (msg);
93 }
94
95 l = soup_cookie_jar_all_cookies (jar);
96 g_assert_cmpint (g_slist_length (l), ==, validResults[i].n_cookies);
97
98 for (p = l; p; p = p->next) {
99 soup_cookie_jar_delete_cookie (jar, p->data);
100 soup_cookie_free (p->data);
101 }
102
103 g_slist_free (l);
104 }
105
106 soup_test_session_abort_unref (session);
107 }
108
109 static void
do_cookies_subdomain_policy_test(void)110 do_cookies_subdomain_policy_test (void)
111 {
112 SoupCookieJar *jar;
113 GSList *cookies;
114 SoupURI *uri1;
115 SoupURI *uri2;
116 SoupURI *uri3;
117
118 g_test_bug ("792130");
119
120 /* Only the base domain should be considered when deciding
121 * whether a cookie is a third-party cookie.
122 */
123 uri1 = soup_uri_new ("https://www.gnome.org");
124 uri2 = soup_uri_new ("https://foundation.gnome.org");
125 uri3 = soup_uri_new ("https://www.gnome.org.");
126
127 /* We can't check subdomains with a test server running on
128 * localhost, so we'll just check the cookie jar API itself.
129 */
130
131 /* Cookie should be accepted. One cookie in the jar. */
132 jar = soup_cookie_jar_new ();
133 soup_cookie_jar_set_accept_policy (jar, SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY);
134 soup_cookie_jar_set_cookie_with_first_party (jar, uri1, uri2, "1=foo");
135 cookies = soup_cookie_jar_all_cookies (jar);
136 g_assert_cmpint (g_slist_length (cookies), ==, 1);
137 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
138
139 /* Cookie should be accepted. Two cookies in the jar. */
140 soup_cookie_jar_set_cookie_with_first_party (jar, uri2, uri1, "2=foo");
141 cookies = soup_cookie_jar_all_cookies (jar);
142 g_assert_cmpint (g_slist_length (cookies), ==, 2);
143 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
144
145 /* Third-party cookie should be rejected, so there are still
146 * only two cookies in the jar.
147 */
148 soup_cookie_jar_set_cookie_with_first_party (jar, third_party_uri, uri1, "3=foo");
149 cookies = soup_cookie_jar_all_cookies (jar);
150 g_assert_cmpint (g_slist_length (cookies), ==, 2);
151 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
152
153 /* Now, we allow the "third-party" to set one cookie as the
154 * first party. Three cookies in the jar.
155 */
156 soup_cookie_jar_set_cookie_with_first_party (jar, third_party_uri, third_party_uri, "4=foo");
157 cookies = soup_cookie_jar_all_cookies (jar);
158 g_assert_cmpint (g_slist_length (cookies), ==, 3);
159 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
160
161 /* Third-party cookie should now be allowed by grandfathering, though
162 * it was blocked before on the same URL. Four cookies in the jar.
163 */
164 soup_cookie_jar_set_accept_policy (jar, SOUP_COOKIE_JAR_ACCEPT_GRANDFATHERED_THIRD_PARTY);
165 soup_cookie_jar_set_cookie_with_first_party (jar, third_party_uri, uri1, "5=foo");
166 cookies = soup_cookie_jar_all_cookies (jar);
167 g_assert_cmpint (g_slist_length (cookies), ==, 4);
168 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
169
170 /* Now some Domain attribute tests.*/
171 soup_cookie_jar_set_accept_policy (jar, SOUP_COOKIE_JAR_ACCEPT_ALWAYS);
172
173 /* The cookie must be rejected if the Domain is not an appropriate
174 * match for the URI. Still four cookies in the jar.
175 */
176 soup_cookie_jar_set_cookie (jar, uri1, "6=foo; Domain=gitlab.gnome.org");
177 cookies = soup_cookie_jar_all_cookies (jar);
178 g_assert_cmpint (g_slist_length (cookies), ==, 4);
179 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
180
181 /* Now the Domain is an appropriate match. Five cookies in the jar. */
182 soup_cookie_jar_set_cookie (jar, uri1, "7=foo; Domain=gnome.org");
183 cookies = soup_cookie_jar_all_cookies (jar);
184 g_assert_cmpint (g_slist_length (cookies), ==, 5);
185 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
186
187 /* A leading dot in the domain property should not affect things.
188 * This cookie should be accepted. Six cookies in the jar.
189 */
190 soup_cookie_jar_set_cookie (jar, uri1, "8=foo; Domain=.www.gnome.org");
191 cookies = soup_cookie_jar_all_cookies (jar);
192 g_assert_cmpint (g_slist_length (cookies), ==, 6);
193 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
194
195 /* The cookie must be rejected if the Domain ends in a trailing dot
196 * but the uri doesn't.
197 */
198 soup_cookie_jar_set_cookie (jar, uri1, "9=foo; Domain=www.gnome.org.");
199 cookies = soup_cookie_jar_all_cookies (jar);
200 g_assert_cmpint (g_slist_length (cookies), ==, 6);
201 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
202
203 /* The cookie should be accepted if both Domain and URI end with a trailing
204 * dot and they are a match. Seven cookies in the jar.
205 */
206 soup_cookie_jar_set_cookie (jar, uri3, "10=foo; Domain=gnome.org.");
207 cookies = soup_cookie_jar_all_cookies (jar);
208 g_assert_cmpint (g_slist_length (cookies), ==, 7);
209 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
210
211 /* The cookie should be rejected if URI has trailing dot but Domain doesn't.
212 * Seven cookies in the jar.
213 */
214 soup_cookie_jar_set_cookie (jar, uri3, "11=foo; Domain=gnome.org");
215 cookies = soup_cookie_jar_all_cookies (jar);
216 g_assert_cmpint (g_slist_length (cookies), ==, 7);
217 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
218
219 /* It should not be possible to set a cookie for a TLD. Still seven
220 * cookies in the jar.
221 */
222 soup_cookie_jar_set_cookie (jar, uri1, "12=foo; Domain=.org");
223 cookies = soup_cookie_jar_all_cookies (jar);
224 g_assert_cmpint (g_slist_length (cookies), ==, 7);
225 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
226
227 /* It should still not be possible to set a cookie for a TLD, even if
228 * we are tricksy and have a trailing dot. Still only seven cookies.
229 */
230 soup_cookie_jar_set_cookie (jar, uri3, "13=foo; Domain=.org.");
231 cookies = soup_cookie_jar_all_cookies (jar);
232 g_assert_cmpint (g_slist_length (cookies), ==, 7);
233 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
234
235 soup_uri_free (uri1);
236 soup_uri_free (uri2);
237 soup_uri_free (uri3);
238 g_object_unref (jar);
239 }
240
241 static void
do_cookies_strict_secure_test(void)242 do_cookies_strict_secure_test (void)
243 {
244 SoupCookieJar *jar;
245 GSList *cookies;
246 SoupURI *insecure_uri;
247 SoupURI *secure_uri;
248
249 insecure_uri = soup_uri_new ("http://gnome.org");
250 secure_uri = soup_uri_new ("https://gnome.org");
251 jar = soup_cookie_jar_new ();
252
253 /* Set a cookie from secure origin */
254 soup_cookie_jar_set_cookie (jar, secure_uri, "1=foo; secure");
255 cookies = soup_cookie_jar_all_cookies (jar);
256 g_assert_cmpint (g_slist_length (cookies), ==, 1);
257 g_assert_cmpstr (soup_cookie_get_value(cookies->data), ==, "foo");
258 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
259
260 /* Do not allow an insecure origin to overwrite a secure cookie */
261 soup_cookie_jar_set_cookie (jar, insecure_uri, "1=bar");
262 cookies = soup_cookie_jar_all_cookies (jar);
263 g_assert_cmpint (g_slist_length (cookies), ==, 1);
264 g_assert_cmpstr (soup_cookie_get_value(cookies->data), ==, "foo");
265 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
266
267 /* Secure can only be set by from secure origin */
268 soup_cookie_jar_set_cookie (jar, insecure_uri, "2=foo; secure");
269 cookies = soup_cookie_jar_all_cookies (jar);
270 g_assert_cmpint (g_slist_length (cookies), ==, 1);
271 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
272
273 /* But we can make one for another path */
274 soup_cookie_jar_set_cookie (jar, insecure_uri, "1=foo; path=/foo");
275 cookies = soup_cookie_jar_all_cookies (jar);
276 g_assert_cmpint (g_slist_length (cookies), ==, 2);
277 g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free);
278
279 soup_uri_free (insecure_uri);
280 soup_uri_free (secure_uri);
281 g_object_unref (jar);
282 }
283
284 /* FIXME: moar tests! */
285 static void
do_cookies_parsing_test(void)286 do_cookies_parsing_test (void)
287 {
288 SoupSession *session;
289 SoupMessage *msg;
290 SoupCookieJar *jar;
291 GSList *cookies, *iter;
292 SoupCookie *cookie;
293 gboolean got1, got2, got3;
294
295 g_test_bug ("678753");
296
297 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
298 soup_session_add_feature_by_type (session, SOUP_TYPE_COOKIE_JAR);
299 jar = SOUP_COOKIE_JAR (soup_session_get_feature (session, SOUP_TYPE_COOKIE_JAR));
300
301 /* "httponly" is case-insensitive, and its value (if any) is ignored */
302 msg = soup_message_new_from_uri ("GET", first_party_uri);
303 soup_message_headers_append (msg->request_headers, "Echo-Set-Cookie",
304 "one=1; httponly; max-age=100");
305 soup_session_send_message (session, msg);
306 g_object_unref (msg);
307
308 msg = soup_message_new_from_uri ("GET", first_party_uri);
309 soup_message_headers_append (msg->request_headers, "Echo-Set-Cookie",
310 "two=2; HttpOnly; max-age=100; SameSite=Invalid");
311 soup_session_send_message (session, msg);
312 g_object_unref (msg);
313
314 msg = soup_message_new_from_uri ("GET", first_party_uri);
315 soup_message_headers_append (msg->request_headers, "Echo-Set-Cookie",
316 "three=3; httpONLY=Wednesday; max-age=100; SameSite=Lax");
317 soup_session_send_message (session, msg);
318 g_object_unref (msg);
319
320 cookies = soup_cookie_jar_get_cookie_list (jar, first_party_uri, TRUE);
321 got1 = got2 = got3 = FALSE;
322
323 for (iter = cookies; iter; iter = iter->next) {
324 cookie = iter->data;
325
326 if (!strcmp (soup_cookie_get_name (cookie), "one")) {
327 got1 = TRUE;
328 g_assert_true (soup_cookie_get_http_only (cookie));
329 g_assert_true (soup_cookie_get_expires (cookie) != NULL);
330 } else if (!strcmp (soup_cookie_get_name (cookie), "two")) {
331 got2 = TRUE;
332 g_assert_true (soup_cookie_get_http_only (cookie));
333 g_assert_true (soup_cookie_get_expires (cookie) != NULL);
334 g_assert_cmpint (soup_cookie_get_same_site_policy (cookie), ==, SOUP_SAME_SITE_POLICY_NONE);
335 } else if (!strcmp (soup_cookie_get_name (cookie), "three")) {
336 got3 = TRUE;
337 g_assert_true (soup_cookie_get_http_only (cookie));
338 g_assert_true (soup_cookie_get_expires (cookie) != NULL);
339 g_assert_cmpint (soup_cookie_get_same_site_policy (cookie), ==, SOUP_SAME_SITE_POLICY_LAX);
340 } else {
341 soup_test_assert (FALSE, "got unexpected cookie '%s'",
342 soup_cookie_get_name (cookie));
343 }
344
345 soup_cookie_free (cookie);
346 }
347 g_slist_free (cookies);
348
349 g_assert_true (got1);
350 g_assert_true (got2);
351 g_assert_true (got3);
352
353 soup_test_session_abort_unref (session);
354 }
355
356 static void
do_cookies_parsing_nopath_nullorigin(void)357 do_cookies_parsing_nopath_nullorigin (void)
358 {
359 SoupCookie *cookie = soup_cookie_parse ("NAME=Value", NULL);
360 g_assert_nonnull (cookie);
361 g_assert_cmpstr ("/", ==, soup_cookie_get_path (cookie));
362 soup_cookie_free (cookie);
363 }
364
365 static void
do_get_cookies_empty_host_test(void)366 do_get_cookies_empty_host_test (void)
367 {
368 SoupCookieJar *jar;
369 SoupURI *uri;
370 char *cookies;
371
372 jar = soup_cookie_jar_new ();
373 uri = soup_uri_new ("file:///whatever.html");
374
375 cookies = soup_cookie_jar_get_cookies (jar, uri, FALSE);
376
377 g_assert_null (cookies);
378
379 g_object_unref (jar);
380 soup_uri_free (uri);
381 }
382
383 static void
send_callback(GObject * source_object,GAsyncResult * res,GMainLoop * loop)384 send_callback (GObject *source_object,
385 GAsyncResult *res,
386 GMainLoop *loop)
387 {
388 g_main_loop_quit (loop);
389 }
390
391 static void
do_remove_feature_test(void)392 do_remove_feature_test (void)
393 {
394 SoupSession *session;
395 SoupMessage *msg;
396 SoupURI *uri;
397 GMainLoop *loop;
398
399 session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
400 soup_session_add_feature_by_type (session, SOUP_TYPE_COOKIE_JAR);
401 uri = soup_uri_new_with_base (first_party_uri, "/index.html");
402 msg = soup_message_new_from_uri ("GET", uri);
403 soup_message_set_first_party (msg, first_party_uri);
404
405 loop = g_main_loop_new (NULL, TRUE);
406 soup_session_send_async (session, msg, NULL, (GAsyncReadyCallback)send_callback, loop);
407 soup_session_remove_feature_by_type (session, SOUP_TYPE_COOKIE_JAR);
408
409 g_main_loop_run(loop);
410
411 g_main_loop_unref (loop);
412 g_object_unref (msg);
413 soup_uri_free (uri);
414 }
415
416 int
main(int argc,char ** argv)417 main (int argc, char **argv)
418 {
419 SoupURI *server_uri;
420 int ret;
421
422 test_init (argc, argv, NULL);
423
424 server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
425 soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
426 server_uri = soup_test_server_get_uri (server, "http", NULL);
427
428 first_party_uri = soup_uri_new (first_party);
429 third_party_uri = soup_uri_new (third_party);
430 soup_uri_set_port (first_party_uri, server_uri->port);
431 soup_uri_set_port (third_party_uri, server_uri->port);
432
433 g_test_add_func ("/cookies/accept-policy", do_cookies_accept_policy_test);
434 g_test_add_func ("/cookies/accept-policy-subdomains", do_cookies_subdomain_policy_test);
435 g_test_add_func ("/cookies/parsing", do_cookies_parsing_test);
436 g_test_add_func ("/cookies/parsing/no-path-null-origin", do_cookies_parsing_nopath_nullorigin);
437 g_test_add_func ("/cookies/get-cookies/empty-host", do_get_cookies_empty_host_test);
438 g_test_add_func ("/cookies/remove-feature", do_remove_feature_test);
439 g_test_add_func ("/cookies/secure-cookies", do_cookies_strict_secure_test);
440
441 ret = g_test_run ();
442
443 soup_uri_free (first_party_uri);
444 soup_uri_free (third_party_uri);
445 soup_uri_free (server_uri);
446 soup_test_server_quit_unref (server);
447
448 test_cleanup ();
449 return ret;
450 }
451