[WININET] Sync with Wine Staging 2.16. CORE-13762
[reactos.git] / reactos / dll / win32 / wininet / cookie.c
1 /*
2 * Wininet - cookie handling stuff
3 *
4 * Copyright 2002 TransGaming Technologies Inc.
5 *
6 * David Hammerton
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 */
22
23 #include "internet.h"
24
25 #include <lmcons.h>
26
27 #define RESPONSE_TIMEOUT 30 /* FROM internet.c */
28
29 /* FIXME
30 * Cookies could use A LOT OF MEMORY. We need some kind of memory management here!
31 */
32
33 struct _cookie_domain_t;
34 struct _cookie_container_t;
35
36 typedef struct _cookie_t {
37 struct list entry;
38
39 struct _cookie_container_t *container;
40
41 WCHAR *name;
42 WCHAR *data;
43 DWORD flags;
44 FILETIME expiry;
45 FILETIME create;
46 } cookie_t;
47
48 typedef struct _cookie_container_t {
49 struct list entry;
50
51 WCHAR *cookie_url;
52 substr_t path;
53 struct _cookie_domain_t *domain;
54
55 struct list cookie_list;
56 } cookie_container_t;
57
58 typedef struct _cookie_domain_t {
59 struct list entry;
60
61 WCHAR *domain;
62 unsigned subdomain_len;
63
64 struct _cookie_domain_t *parent;
65 struct list subdomain_list;
66
67 /* List of stored paths sorted by length of the path. */
68 struct list path_list;
69 } cookie_domain_t;
70
71 static CRITICAL_SECTION cookie_cs;
72 static CRITICAL_SECTION_DEBUG cookie_cs_debug =
73 {
74 0, 0, &cookie_cs,
75 { &cookie_cs_debug.ProcessLocksList, &cookie_cs_debug.ProcessLocksList },
76 0, 0, { (DWORD_PTR)(__FILE__ ": cookie_cs") }
77 };
78 static CRITICAL_SECTION cookie_cs = { &cookie_cs_debug, -1, 0, 0, 0, 0 };
79 static struct list domain_list = LIST_INIT(domain_list);
80
81 static cookie_domain_t *get_cookie_domain(substr_t domain, BOOL create)
82 {
83 const WCHAR *ptr = domain.str + domain.len, *ptr_end, *subdomain_ptr;
84 cookie_domain_t *iter, *current_domain, *prev_domain = NULL;
85 struct list *current_list = &domain_list;
86
87 while(1) {
88 for(ptr_end = ptr--; ptr > domain.str && *ptr != '.'; ptr--);
89 subdomain_ptr = *ptr == '.' ? ptr+1 : ptr;
90
91 current_domain = NULL;
92 LIST_FOR_EACH_ENTRY(iter, current_list, cookie_domain_t, entry) {
93 if(ptr_end-subdomain_ptr == iter->subdomain_len
94 && !memcmp(subdomain_ptr, iter->domain, iter->subdomain_len*sizeof(WCHAR))) {
95 current_domain = iter;
96 break;
97 }
98 }
99
100 if(!current_domain) {
101 if(!create)
102 return prev_domain;
103
104 current_domain = heap_alloc(sizeof(*current_domain));
105 if(!current_domain)
106 return NULL;
107
108 current_domain->domain = heap_strndupW(subdomain_ptr, domain.str + domain.len - subdomain_ptr);
109 if(!current_domain->domain) {
110 heap_free(current_domain);
111 return NULL;
112 }
113
114 current_domain->subdomain_len = ptr_end-subdomain_ptr;
115
116 current_domain->parent = prev_domain;
117 list_init(&current_domain->path_list);
118 list_init(&current_domain->subdomain_list);
119
120 list_add_tail(current_list, &current_domain->entry);
121 }
122
123 if(ptr == domain.str)
124 return current_domain;
125
126 prev_domain = current_domain;
127 current_list = &current_domain->subdomain_list;
128 }
129 }
130
131 static WCHAR *create_cookie_url(substr_t domain, substr_t path, substr_t *ret_path)
132 {
133 WCHAR user[UNLEN], *p, *url;
134 DWORD len, user_len, i;
135
136 static const WCHAR cookie_prefix[] = {'C','o','o','k','i','e',':'};
137
138 user_len = sizeof(user)/sizeof(WCHAR);
139 if(!GetUserNameW(user, &user_len))
140 return FALSE;
141 user_len--;
142
143 len = sizeof(cookie_prefix)/sizeof(WCHAR) + user_len + 1 /* @ */ + domain.len + path.len;
144 url = heap_alloc((len+1) * sizeof(WCHAR));
145 if(!url)
146 return NULL;
147
148 memcpy(url, cookie_prefix, sizeof(cookie_prefix));
149 p = url + sizeof(cookie_prefix)/sizeof(WCHAR);
150
151 memcpy(p, user, user_len*sizeof(WCHAR));
152 p += user_len;
153
154 *p++ = '@';
155
156 memcpy(p, domain.str, domain.len*sizeof(WCHAR));
157 p += domain.len;
158
159 for(i=0; i < path.len; i++)
160 p[i] = tolowerW(path.str[i]);
161 p[path.len] = 0;
162
163 ret_path->str = p;
164 ret_path->len = path.len;
165 return url;
166 }
167
168 static cookie_container_t *get_cookie_container(substr_t domain, substr_t path, BOOL create)
169 {
170 cookie_domain_t *cookie_domain;
171 cookie_container_t *cookie_container, *iter;
172
173 cookie_domain = get_cookie_domain(domain, create);
174 if(!cookie_domain)
175 return NULL;
176
177 LIST_FOR_EACH_ENTRY(cookie_container, &cookie_domain->path_list, cookie_container_t, entry) {
178 if(cookie_container->path.len < path.len)
179 break;
180
181 if(path.len == cookie_container->path.len && !strncmpiW(cookie_container->path.str, path.str, path.len))
182 return cookie_container;
183 }
184
185 if(!create)
186 return NULL;
187
188 cookie_container = heap_alloc(sizeof(*cookie_container));
189 if(!cookie_container)
190 return NULL;
191
192 cookie_container->cookie_url = create_cookie_url(substrz(cookie_domain->domain), path, &cookie_container->path);
193 if(!cookie_container->cookie_url) {
194 heap_free(cookie_container);
195 return NULL;
196 }
197
198 cookie_container->domain = cookie_domain;
199 list_init(&cookie_container->cookie_list);
200
201 LIST_FOR_EACH_ENTRY(iter, &cookie_domain->path_list, cookie_container_t, entry) {
202 if(iter->path.len <= path.len) {
203 list_add_before(&iter->entry, &cookie_container->entry);
204 return cookie_container;
205 }
206 }
207
208 list_add_tail(&cookie_domain->path_list, &cookie_container->entry);
209 return cookie_container;
210 }
211
212 static void delete_cookie(cookie_t *cookie)
213 {
214 list_remove(&cookie->entry);
215
216 heap_free(cookie->name);
217 heap_free(cookie->data);
218 heap_free(cookie);
219 }
220
221 static cookie_t *alloc_cookie(substr_t name, substr_t data, FILETIME expiry, FILETIME create_time, DWORD flags)
222 {
223 cookie_t *new_cookie;
224
225 new_cookie = heap_alloc(sizeof(*new_cookie));
226 if(!new_cookie)
227 return NULL;
228
229 new_cookie->expiry = expiry;
230 new_cookie->create = create_time;
231 new_cookie->flags = flags;
232 list_init(&new_cookie->entry);
233
234 new_cookie->name = heap_strndupW(name.str, name.len);
235 new_cookie->data = heap_strndupW(data.str, data.len);
236 if(!new_cookie->name || !new_cookie->data) {
237 delete_cookie(new_cookie);
238 return NULL;
239 }
240
241 return new_cookie;
242 }
243
244 static cookie_t *find_cookie(cookie_container_t *container, substr_t name)
245 {
246 cookie_t *iter;
247
248 LIST_FOR_EACH_ENTRY(iter, &container->cookie_list, cookie_t, entry) {
249 if(strlenW(iter->name) == name.len && !strncmpiW(iter->name, name.str, name.len))
250 return iter;
251 }
252
253 return NULL;
254 }
255
256 static void add_cookie(cookie_container_t *container, cookie_t *new_cookie)
257 {
258 TRACE("Adding %s=%s to %s\n", debugstr_w(new_cookie->name), debugstr_w(new_cookie->data),
259 debugstr_w(container->cookie_url));
260
261 list_add_tail(&container->cookie_list, &new_cookie->entry);
262 new_cookie->container = container;
263 }
264
265 static void replace_cookie(cookie_container_t *container, cookie_t *new_cookie)
266 {
267 cookie_t *old_cookie;
268
269 old_cookie = find_cookie(container, substrz(new_cookie->name));
270 if(old_cookie)
271 delete_cookie(old_cookie);
272
273 add_cookie(container, new_cookie);
274 }
275
276 static BOOL cookie_match_path(cookie_container_t *container, substr_t path)
277 {
278 return path.len >= container->path.len && !strncmpiW(container->path.str, path.str, container->path.len);
279 }
280
281 static BOOL load_persistent_cookie(substr_t domain, substr_t path)
282 {
283 INTERNET_CACHE_ENTRY_INFOW *info;
284 cookie_container_t *cookie_container;
285 cookie_t *new_cookie;
286 HANDLE cookie;
287 char *str = NULL, *pbeg, *pend;
288 DWORD size, flags;
289 WCHAR *name, *data;
290 FILETIME expiry, create, time;
291
292 cookie_container = get_cookie_container(domain, path, TRUE);
293 if(!cookie_container)
294 return FALSE;
295
296 size = 0;
297 RetrieveUrlCacheEntryStreamW(cookie_container->cookie_url, NULL, &size, FALSE, 0);
298 if(GetLastError() != ERROR_INSUFFICIENT_BUFFER)
299 return TRUE;
300 info = heap_alloc(size);
301 if(!info)
302 return FALSE;
303 cookie = RetrieveUrlCacheEntryStreamW(cookie_container->cookie_url, info, &size, FALSE, 0);
304 size = info->dwSizeLow;
305 heap_free(info);
306 if(!cookie)
307 return FALSE;
308
309 if(!(str = heap_alloc(size+1)) || !ReadUrlCacheEntryStream(cookie, 0, str, &size, 0)) {
310 UnlockUrlCacheEntryStream(cookie, 0);
311 heap_free(str);
312 return FALSE;
313 }
314 str[size] = 0;
315 UnlockUrlCacheEntryStream(cookie, 0);
316
317 GetSystemTimeAsFileTime(&time);
318 for(pbeg=str; pbeg && *pbeg; name=data=NULL) {
319 pend = strchr(pbeg, '\n');
320 if(!pend)
321 break;
322 *pend = 0;
323 name = heap_strdupAtoW(pbeg);
324
325 pbeg = pend+1;
326 pend = strchr(pbeg, '\n');
327 if(!pend)
328 break;
329 *pend = 0;
330 data = heap_strdupAtoW(pbeg);
331
332 pbeg = strchr(pend+1, '\n');
333 if(!pbeg)
334 break;
335 sscanf(pbeg, "%u %u %u %u %u", &flags, &expiry.dwLowDateTime, &expiry.dwHighDateTime,
336 &create.dwLowDateTime, &create.dwHighDateTime);
337
338 /* skip "*\n" */
339 pbeg = strchr(pbeg, '*');
340 if(pbeg) {
341 pbeg++;
342 if(*pbeg)
343 pbeg++;
344 }
345
346 if(!name || !data)
347 break;
348
349 if(CompareFileTime(&time, &expiry) <= 0) {
350 new_cookie = alloc_cookie(substr(NULL, 0), substr(NULL, 0), expiry, create, flags);
351 if(!new_cookie)
352 break;
353
354 new_cookie->name = name;
355 new_cookie->data = data;
356
357 replace_cookie(cookie_container, new_cookie);
358 }else {
359 heap_free(name);
360 heap_free(data);
361 }
362 }
363 heap_free(str);
364 heap_free(name);
365 heap_free(data);
366
367 return TRUE;
368 }
369
370 static BOOL save_persistent_cookie(cookie_container_t *container)
371 {
372 static const WCHAR txtW[] = {'t','x','t',0};
373
374 WCHAR cookie_file[MAX_PATH];
375 HANDLE cookie_handle;
376 cookie_t *cookie_container = NULL, *cookie_iter;
377 BOOL do_save = FALSE;
378 char buf[64], *dyn_buf;
379 FILETIME time;
380 DWORD bytes_written;
381 size_t len;
382
383 /* check if there's anything to save */
384 GetSystemTimeAsFileTime(&time);
385 LIST_FOR_EACH_ENTRY_SAFE(cookie_container, cookie_iter, &container->cookie_list, cookie_t, entry)
386 {
387 if((cookie_container->expiry.dwLowDateTime || cookie_container->expiry.dwHighDateTime)
388 && CompareFileTime(&time, &cookie_container->expiry) > 0) {
389 delete_cookie(cookie_container);
390 continue;
391 }
392
393 if(!(cookie_container->flags & INTERNET_COOKIE_IS_SESSION)) {
394 do_save = TRUE;
395 break;
396 }
397 }
398
399 if(!do_save) {
400 DeleteUrlCacheEntryW(container->cookie_url);
401 return TRUE;
402 }
403
404 if(!CreateUrlCacheEntryW(container->cookie_url, 0, txtW, cookie_file, 0))
405 return FALSE;
406
407 cookie_handle = CreateFileW(cookie_file, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
408 if(cookie_handle == INVALID_HANDLE_VALUE) {
409 DeleteFileW(cookie_file);
410 return FALSE;
411 }
412
413 LIST_FOR_EACH_ENTRY(cookie_container, &container->cookie_list, cookie_t, entry)
414 {
415 if(cookie_container->flags & INTERNET_COOKIE_IS_SESSION)
416 continue;
417
418 dyn_buf = heap_strdupWtoA(cookie_container->name);
419 if(!dyn_buf || !WriteFile(cookie_handle, dyn_buf, strlen(dyn_buf), &bytes_written, NULL)) {
420 heap_free(dyn_buf);
421 do_save = FALSE;
422 break;
423 }
424 heap_free(dyn_buf);
425 if(!WriteFile(cookie_handle, "\n", 1, &bytes_written, NULL)) {
426 do_save = FALSE;
427 break;
428 }
429
430 dyn_buf = heap_strdupWtoA(cookie_container->data);
431 if(!dyn_buf || !WriteFile(cookie_handle, dyn_buf, strlen(dyn_buf), &bytes_written, NULL)) {
432 heap_free(dyn_buf);
433 do_save = FALSE;
434 break;
435 }
436 heap_free(dyn_buf);
437 if(!WriteFile(cookie_handle, "\n", 1, &bytes_written, NULL)) {
438 do_save = FALSE;
439 break;
440 }
441
442 dyn_buf = heap_strdupWtoA(container->domain->domain);
443 if(!dyn_buf || !WriteFile(cookie_handle, dyn_buf, strlen(dyn_buf), &bytes_written, NULL)) {
444 heap_free(dyn_buf);
445 do_save = FALSE;
446 break;
447 }
448 heap_free(dyn_buf);
449
450 len = WideCharToMultiByte(CP_ACP, 0, container->path.str, container->path.len, NULL, 0, NULL, NULL);
451 dyn_buf = heap_alloc(len+1);
452 if(dyn_buf) {
453 WideCharToMultiByte(CP_ACP, 0, container->path.str, container->path.len, dyn_buf, len, NULL, NULL);
454 dyn_buf[len] = 0;
455 }
456 if(!dyn_buf || !WriteFile(cookie_handle, dyn_buf, strlen(dyn_buf), &bytes_written, NULL)) {
457 heap_free(dyn_buf);
458 do_save = FALSE;
459 break;
460 }
461 heap_free(dyn_buf);
462
463 sprintf(buf, "\n%u\n%u\n%u\n%u\n%u\n*\n", cookie_container->flags,
464 cookie_container->expiry.dwLowDateTime, cookie_container->expiry.dwHighDateTime,
465 cookie_container->create.dwLowDateTime, cookie_container->create.dwHighDateTime);
466 if(!WriteFile(cookie_handle, buf, strlen(buf), &bytes_written, NULL)) {
467 do_save = FALSE;
468 break;
469 }
470 }
471
472 CloseHandle(cookie_handle);
473 if(!do_save) {
474 ERR("error saving cookie file\n");
475 DeleteFileW(cookie_file);
476 return FALSE;
477 }
478
479 memset(&time, 0, sizeof(time));
480 return CommitUrlCacheEntryW(container->cookie_url, cookie_file, time, time, 0, NULL, 0, txtW, 0);
481 }
482
483 static BOOL cookie_parse_url(const WCHAR *url, substr_t *host, substr_t *path)
484 {
485 URL_COMPONENTSW comp = { sizeof(comp) };
486 static const WCHAR rootW[] = {'/',0};
487
488 comp.dwHostNameLength = 1;
489 comp.dwUrlPathLength = 1;
490
491 if(!InternetCrackUrlW(url, 0, 0, &comp) || !comp.dwHostNameLength)
492 return FALSE;
493
494 /* discard the webpage off the end of the path */
495 while(comp.dwUrlPathLength && comp.lpszUrlPath[comp.dwUrlPathLength-1] != '/')
496 comp.dwUrlPathLength--;
497
498 *host = substr(comp.lpszHostName, comp.dwHostNameLength);
499 *path = comp.dwUrlPathLength ? substr(comp.lpszUrlPath, comp.dwUrlPathLength) : substr(rootW, 1);
500 return TRUE;
501 }
502
503 typedef struct {
504 cookie_t **cookies;
505 unsigned cnt;
506 unsigned size;
507
508 unsigned string_len;
509 } cookie_set_t;
510
511 static DWORD get_cookie(substr_t host, substr_t path, DWORD flags, cookie_set_t *res)
512 {
513 static const WCHAR empty_path[] = { '/',0 };
514
515 const WCHAR *p;
516 cookie_domain_t *domain;
517 cookie_container_t *container;
518 FILETIME tm;
519
520 GetSystemTimeAsFileTime(&tm);
521
522 p = host.str + host.len;
523 while(p > host.str && p[-1] != '.') p--;
524 while(p != host.str) {
525 p--;
526 while(p > host.str && p[-1] != '.') p--;
527 if(p == host.str) break;
528
529 load_persistent_cookie(substr(p, host.str+host.len-p), substr(empty_path, 1));
530 }
531
532 p = path.str + path.len;
533 do {
534 load_persistent_cookie(host, substr(path.str, p-path.str));
535
536 p--;
537 while(p > path.str && p[-1] != '/') p--;
538 }while(p != path.str);
539
540 domain = get_cookie_domain(host, FALSE);
541 if(!domain) {
542 TRACE("Unknown host %s\n", debugstr_wn(host.str, host.len));
543 return ERROR_NO_MORE_ITEMS;
544 }
545
546 for(domain = get_cookie_domain(host, FALSE); domain; domain = domain->parent) {
547 LIST_FOR_EACH_ENTRY(container, &domain->path_list, cookie_container_t, entry) {
548 struct list *cursor, *cursor2;
549
550 if(!cookie_match_path(container, path))
551 continue;
552
553 LIST_FOR_EACH_SAFE(cursor, cursor2, &container->cookie_list) {
554 cookie_t *cookie_iter = LIST_ENTRY(cursor, cookie_t, entry);
555
556 /* check for expiry */
557 if((cookie_iter->expiry.dwLowDateTime != 0 || cookie_iter->expiry.dwHighDateTime != 0)
558 && CompareFileTime(&tm, &cookie_iter->expiry) > 0) {
559 TRACE("Found expired cookie. deleting\n");
560 delete_cookie(cookie_iter);
561 continue;
562 }
563
564 if((cookie_iter->flags & INTERNET_COOKIE_HTTPONLY) && !(flags & INTERNET_COOKIE_HTTPONLY))
565 continue;
566
567 if(!res->size) {
568 res->cookies = heap_alloc(4*sizeof(*res->cookies));
569 if(!res->cookies)
570 continue;
571 res->size = 4;
572 }else if(res->cnt == res->size) {
573 cookie_t **new_cookies = heap_realloc(res->cookies, res->size*2*sizeof(*res->cookies));
574 if(!new_cookies)
575 continue;
576 res->cookies = new_cookies;
577 res->size *= 2;
578 }
579
580 TRACE("%s = %s domain %s path %s\n", debugstr_w(cookie_iter->name), debugstr_w(cookie_iter->data),
581 debugstr_w(domain->domain), debugstr_wn(container->path.str, container->path.len));
582
583 if(res->cnt)
584 res->string_len += 2; /* '; ' */
585 res->cookies[res->cnt++] = cookie_iter;
586
587 res->string_len += strlenW(cookie_iter->name);
588 if(*cookie_iter->data)
589 res->string_len += 1 /* = */ + strlenW(cookie_iter->data);
590 }
591 }
592 }
593
594 return ERROR_SUCCESS;
595 }
596
597 static void cookie_set_to_string(const cookie_set_t *cookie_set, WCHAR *str)
598 {
599 WCHAR *ptr = str;
600 unsigned i, len;
601
602 for(i=0; i<cookie_set->cnt; i++) {
603 if(i) {
604 *ptr++ = ';';
605 *ptr++ = ' ';
606 }
607
608 len = strlenW(cookie_set->cookies[i]->name);
609 memcpy(ptr, cookie_set->cookies[i]->name, len*sizeof(WCHAR));
610 ptr += len;
611
612 if(*cookie_set->cookies[i]->data) {
613 *ptr++ = '=';
614 len = strlenW(cookie_set->cookies[i]->data);
615 memcpy(ptr, cookie_set->cookies[i]->data, len*sizeof(WCHAR));
616 ptr += len;
617 }
618 }
619
620 assert(ptr-str == cookie_set->string_len);
621 TRACE("%s\n", debugstr_wn(str, ptr-str));
622 }
623
624 DWORD get_cookie_header(const WCHAR *host, const WCHAR *path, WCHAR **ret)
625 {
626 cookie_set_t cookie_set = {0};
627 DWORD res;
628
629 static const WCHAR cookieW[] = {'C','o','o','k','i','e',':',' '};
630
631 EnterCriticalSection(&cookie_cs);
632
633 res = get_cookie(substrz(host), substrz(path), INTERNET_COOKIE_HTTPONLY, &cookie_set);
634 if(res != ERROR_SUCCESS) {
635 LeaveCriticalSection(&cookie_cs);
636 return res;
637 }
638
639 if(cookie_set.cnt) {
640 WCHAR *header, *ptr;
641
642 ptr = header = heap_alloc(sizeof(cookieW) + (cookie_set.string_len + 3 /* crlf0 */) * sizeof(WCHAR));
643 if(header) {
644 memcpy(ptr, cookieW, sizeof(cookieW));
645 ptr += sizeof(cookieW)/sizeof(*cookieW);
646
647 cookie_set_to_string(&cookie_set, ptr);
648 heap_free(cookie_set.cookies);
649 ptr += cookie_set.string_len;
650
651 *ptr++ = '\r';
652 *ptr++ = '\n';
653 *ptr++ = 0;
654
655 *ret = header;
656 }else {
657 res = ERROR_NOT_ENOUGH_MEMORY;
658 }
659 }else {
660 *ret = NULL;
661 }
662
663 LeaveCriticalSection(&cookie_cs);
664 return res;
665 }
666
667 static void free_cookie_domain_list(struct list *list)
668 {
669 cookie_container_t *container;
670 cookie_domain_t *domain;
671
672 while(!list_empty(list)) {
673 domain = LIST_ENTRY(list_head(list), cookie_domain_t, entry);
674
675 free_cookie_domain_list(&domain->subdomain_list);
676
677 while(!list_empty(&domain->path_list)) {
678 container = LIST_ENTRY(list_head(&domain->path_list), cookie_container_t, entry);
679
680 while(!list_empty(&container->cookie_list))
681 delete_cookie(LIST_ENTRY(list_head(&container->cookie_list), cookie_t, entry));
682
683 heap_free(container->cookie_url);
684 list_remove(&container->entry);
685 heap_free(container);
686 }
687
688 heap_free(domain->domain);
689 list_remove(&domain->entry);
690 heap_free(domain);
691 }
692 }
693
694 /***********************************************************************
695 * InternetGetCookieExW (WININET.@)
696 *
697 * Retrieve cookie from the specified url
698 *
699 * It should be noted that on windows the lpszCookieName parameter is "not implemented".
700 * So it won't be implemented here.
701 *
702 * RETURNS
703 * TRUE on success
704 * FALSE on failure
705 *
706 */
707 BOOL WINAPI InternetGetCookieExW(LPCWSTR lpszUrl, LPCWSTR lpszCookieName,
708 LPWSTR lpCookieData, LPDWORD lpdwSize, DWORD flags, void *reserved)
709 {
710 cookie_set_t cookie_set = {0};
711 substr_t host, path;
712 DWORD res;
713 BOOL ret;
714
715 TRACE("(%s, %s, %p, %p, %x, %p)\n", debugstr_w(lpszUrl),debugstr_w(lpszCookieName), lpCookieData, lpdwSize, flags, reserved);
716
717 if (flags)
718 FIXME("flags 0x%08x not supported\n", flags);
719
720 if (!lpszUrl)
721 {
722 SetLastError(ERROR_INVALID_PARAMETER);
723 return FALSE;
724 }
725
726 ret = cookie_parse_url(lpszUrl, &host, &path);
727 if (!ret) {
728 SetLastError(ERROR_INVALID_PARAMETER);
729 return FALSE;
730 }
731
732 EnterCriticalSection(&cookie_cs);
733
734 res = get_cookie(host, path, flags, &cookie_set);
735 if(res != ERROR_SUCCESS) {
736 LeaveCriticalSection(&cookie_cs);
737 SetLastError(res);
738 return FALSE;
739 }
740
741 if(cookie_set.cnt) {
742 if(!lpCookieData || cookie_set.string_len+1 > *lpdwSize) {
743 *lpdwSize = (cookie_set.string_len + 1) * sizeof(WCHAR);
744 TRACE("returning %u\n", *lpdwSize);
745 if(lpCookieData) {
746 SetLastError(ERROR_INSUFFICIENT_BUFFER);
747 ret = FALSE;
748 }
749 }else {
750 *lpdwSize = cookie_set.string_len + 1;
751 cookie_set_to_string(&cookie_set, lpCookieData);
752 lpCookieData[cookie_set.string_len] = 0;
753 }
754 }else {
755 TRACE("no cookies found for %s\n", debugstr_wn(host.str, host.len));
756 SetLastError(ERROR_NO_MORE_ITEMS);
757 ret = FALSE;
758 }
759
760 heap_free(cookie_set.cookies);
761 LeaveCriticalSection(&cookie_cs);
762 return ret;
763 }
764
765 /***********************************************************************
766 * InternetGetCookieW (WININET.@)
767 *
768 * Retrieve cookie for the specified URL.
769 */
770 BOOL WINAPI InternetGetCookieW(const WCHAR *url, const WCHAR *name, WCHAR *data, DWORD *size)
771 {
772 TRACE("(%s, %s, %s, %p)\n", debugstr_w(url), debugstr_w(name), debugstr_w(data), size);
773
774 return InternetGetCookieExW(url, name, data, size, 0, NULL);
775 }
776
777 /***********************************************************************
778 * InternetGetCookieExA (WININET.@)
779 *
780 * Retrieve cookie from the specified url
781 *
782 * RETURNS
783 * TRUE on success
784 * FALSE on failure
785 *
786 */
787 BOOL WINAPI InternetGetCookieExA(LPCSTR lpszUrl, LPCSTR lpszCookieName,
788 LPSTR lpCookieData, LPDWORD lpdwSize, DWORD flags, void *reserved)
789 {
790 WCHAR *url, *name;
791 DWORD len, size = 0;
792 BOOL r;
793
794 TRACE("(%s %s %p %p(%u) %x %p)\n", debugstr_a(lpszUrl), debugstr_a(lpszCookieName),
795 lpCookieData, lpdwSize, lpdwSize ? *lpdwSize : 0, flags, reserved);
796
797 url = heap_strdupAtoW(lpszUrl);
798 name = heap_strdupAtoW(lpszCookieName);
799
800 r = InternetGetCookieExW( url, name, NULL, &len, flags, reserved );
801 if( r )
802 {
803 WCHAR *szCookieData;
804
805 szCookieData = heap_alloc(len * sizeof(WCHAR));
806 if( !szCookieData )
807 {
808 r = FALSE;
809 }
810 else
811 {
812 r = InternetGetCookieExW( url, name, szCookieData, &len, flags, reserved );
813
814 if(r) {
815 size = WideCharToMultiByte( CP_ACP, 0, szCookieData, len, NULL, 0, NULL, NULL);
816 if(lpCookieData) {
817 if(*lpdwSize >= size) {
818 WideCharToMultiByte( CP_ACP, 0, szCookieData, len, lpCookieData, *lpdwSize, NULL, NULL);
819 }else {
820 SetLastError(ERROR_INSUFFICIENT_BUFFER);
821 r = FALSE;
822 }
823 }
824 }
825
826 heap_free( szCookieData );
827 }
828 }
829 *lpdwSize = size;
830 heap_free( name );
831 heap_free( url );
832 return r;
833 }
834
835 /***********************************************************************
836 * InternetGetCookieA (WININET.@)
837 *
838 * See InternetGetCookieW.
839 */
840 BOOL WINAPI InternetGetCookieA(const char *url, const char *name, char *data, DWORD *size)
841 {
842 TRACE("(%s, %s, %p, %p)\n", debugstr_a(url), debugstr_a(name), data, size);
843
844 return InternetGetCookieExA(url, name, data, size, 0, NULL);
845 }
846
847 static BOOL is_domain_legal_for_cookie(substr_t domain, substr_t full_domain)
848 {
849 const WCHAR *ptr;
850
851 if(!domain.len || *domain.str == '.' || !full_domain.len || *full_domain.str == '.') {
852 SetLastError(ERROR_INVALID_NAME);
853 return FALSE;
854 }
855
856 if(domain.len > full_domain.len || !memchrW(domain.str, '.', domain.len) || !memchrW(full_domain.str, '.', full_domain.len))
857 return FALSE;
858
859 ptr = full_domain.str + full_domain.len - domain.len;
860 if (strncmpiW(domain.str, ptr, domain.len) || (full_domain.len > domain.len && ptr[-1] != '.')) {
861 SetLastError(ERROR_INVALID_PARAMETER);
862 return FALSE;
863 }
864
865 return TRUE;
866 }
867
868 /***********************************************************************
869 * IsDomainLegalCookieDomainW (WININET.@)
870 */
871 BOOL WINAPI IsDomainLegalCookieDomainW(const WCHAR *domain, const WCHAR *full_domain)
872 {
873 FIXME("(%s, %s) semi-stub\n", debugstr_w(domain), debugstr_w(full_domain));
874
875 if (!domain || !full_domain) {
876 SetLastError(ERROR_INVALID_PARAMETER);
877 return FALSE;
878 }
879
880 return is_domain_legal_for_cookie(substrz(domain), substrz(full_domain));
881 }
882
883 static void substr_skip(substr_t *str, size_t len)
884 {
885 assert(str->len >= len);
886 str->str += len;
887 str->len -= len;
888 }
889
890 DWORD set_cookie(substr_t domain, substr_t path, substr_t name, substr_t data, DWORD flags)
891 {
892 cookie_container_t *container;
893 cookie_t *thisCookie;
894 substr_t value;
895 const WCHAR *end_ptr;
896 FILETIME expiry, create;
897 BOOL expired = FALSE, update_persistent = FALSE;
898 DWORD cookie_flags = 0, len;
899
900 TRACE("%s %s %s=%s %x\n", debugstr_wn(domain.str, domain.len), debugstr_wn(path.str, path.len),
901 debugstr_wn(name.str, name.len), debugstr_wn(data.str, data.len), flags);
902
903 memset(&expiry,0,sizeof(expiry));
904 GetSystemTimeAsFileTime(&create);
905
906 /* lots of information can be parsed out of the cookie value */
907
908 if(!(end_ptr = memchrW(data.str, ';', data.len)))
909 end_ptr = data.str + data.len;
910 value = substr(data.str, end_ptr-data.str);
911 data.str += value.len;
912 data.len -= value.len;
913
914 for(;;) {
915 static const WCHAR szDomain[] = {'d','o','m','a','i','n','='};
916 static const WCHAR szPath[] = {'p','a','t','h','='};
917 static const WCHAR szExpires[] = {'e','x','p','i','r','e','s','='};
918 static const WCHAR szSecure[] = {'s','e','c','u','r','e'};
919 static const WCHAR szHttpOnly[] = {'h','t','t','p','o','n','l','y'};
920 static const WCHAR szVersion[] = {'v','e','r','s','i','o','n','='};
921 static const WCHAR max_ageW[] = {'m','a','x','-','a','g','e','='};
922
923 /* Skip ';' */
924 if(data.len)
925 substr_skip(&data, 1);
926
927 while(data.len && *data.str == ' ')
928 substr_skip(&data, 1);
929
930 if(!data.len)
931 break;
932
933 if(!(end_ptr = memchrW(data.str, ';', data.len)))
934 end_ptr = data.str + data.len;
935
936 if(data.len >= (len = sizeof(szDomain)/sizeof(WCHAR)) && !strncmpiW(data.str, szDomain, len)) {
937 substr_skip(&data, len);
938
939 if(data.len && *data.str == '.')
940 substr_skip(&data, 1);
941
942 if(!is_domain_legal_for_cookie(substr(data.str, end_ptr-data.str), domain))
943 return COOKIE_STATE_UNKNOWN;
944
945 domain = substr(data.str, end_ptr-data.str);
946 TRACE("Parsing new domain %s\n", debugstr_wn(domain.str, domain.len));
947 }else if(data.len >= (len = sizeof(szPath)/sizeof(WCHAR)) && !strncmpiW(data.str, szPath, len)) {
948 substr_skip(&data, len);
949 path = substr(data.str, end_ptr - data.str);
950 TRACE("Parsing new path %s\n", debugstr_wn(path.str, path.len));
951 }else if(data.len >= (len = sizeof(szExpires)/sizeof(WCHAR)) && !strncmpiW(data.str, szExpires, len)) {
952 SYSTEMTIME st;
953 WCHAR buf[128];
954
955 substr_skip(&data, len);
956
957 if(end_ptr - data.str < sizeof(buf)/sizeof(WCHAR)-1) {
958 memcpy(buf, data.str, data.len*sizeof(WCHAR));
959 buf[data.len] = 0;
960
961 if (InternetTimeToSystemTimeW(data.str, &st, 0)) {
962 SystemTimeToFileTime(&st, &expiry);
963
964 if (CompareFileTime(&create,&expiry) > 0) {
965 TRACE("Cookie already expired.\n");
966 expired = TRUE;
967 }
968 }
969 }
970 }else if(data.len >= (len = sizeof(szSecure)/sizeof(WCHAR)) && !strncmpiW(data.str, szSecure, len)) {
971 substr_skip(&data, len);
972 FIXME("secure not handled\n");
973 }else if(data.len >= (len = sizeof(szHttpOnly)/sizeof(WCHAR)) && !strncmpiW(data.str, szHttpOnly, len)) {
974 substr_skip(&data, len);
975
976 if(!(flags & INTERNET_COOKIE_HTTPONLY)) {
977 WARN("HTTP only cookie added without INTERNET_COOKIE_HTTPONLY flag\n");
978 SetLastError(ERROR_INVALID_OPERATION);
979 return COOKIE_STATE_REJECT;
980 }
981
982 cookie_flags |= INTERNET_COOKIE_HTTPONLY;
983 }else if(data.len >= (len = sizeof(szVersion)/sizeof(WCHAR)) && !strncmpiW(data.str, szVersion, len)) {
984 substr_skip(&data, len);
985
986 FIXME("version not handled (%s)\n",debugstr_wn(data.str, data.len));
987 }else if(data.len >= (len = sizeof(max_ageW)/sizeof(WCHAR)) && !strncmpiW(data.str, max_ageW, len)) {
988 /* Native doesn't support Max-Age attribute. */
989 WARN("Max-Age ignored\n");
990 }else if(data.len) {
991 FIXME("Unknown additional option %s\n", debugstr_wn(data.str, data.len));
992 }
993
994 substr_skip(&data, end_ptr - data.str);
995 }
996
997 EnterCriticalSection(&cookie_cs);
998
999 load_persistent_cookie(domain, path);
1000
1001 container = get_cookie_container(domain, path, !expired);
1002 if(!container) {
1003 LeaveCriticalSection(&cookie_cs);
1004 return COOKIE_STATE_ACCEPT;
1005 }
1006
1007 if(!expiry.dwLowDateTime && !expiry.dwHighDateTime)
1008 cookie_flags |= INTERNET_COOKIE_IS_SESSION;
1009 else
1010 update_persistent = TRUE;
1011
1012 if ((thisCookie = find_cookie(container, name))) {
1013 if ((thisCookie->flags & INTERNET_COOKIE_HTTPONLY) && !(flags & INTERNET_COOKIE_HTTPONLY)) {
1014 WARN("An attempt to override httponly cookie\n");
1015 SetLastError(ERROR_INVALID_OPERATION);
1016 LeaveCriticalSection(&cookie_cs);
1017 return COOKIE_STATE_REJECT;
1018 }
1019
1020 if (!(thisCookie->flags & INTERNET_COOKIE_IS_SESSION))
1021 update_persistent = TRUE;
1022 delete_cookie(thisCookie);
1023 }
1024
1025 TRACE("setting cookie %s=%s for domain %s path %s\n", debugstr_wn(name.str, name.len),
1026 debugstr_wn(value.str, value.len), debugstr_w(container->domain->domain),
1027 debugstr_wn(container->path.str, container->path.len));
1028
1029 if (!expired) {
1030 cookie_t *new_cookie;
1031
1032 new_cookie = alloc_cookie(name, value, expiry, create, cookie_flags);
1033 if(!new_cookie) {
1034 LeaveCriticalSection(&cookie_cs);
1035 return COOKIE_STATE_UNKNOWN;
1036 }
1037
1038 add_cookie(container, new_cookie);
1039 }
1040
1041 if (!update_persistent || save_persistent_cookie(container))
1042 {
1043 LeaveCriticalSection(&cookie_cs);
1044 return COOKIE_STATE_ACCEPT;
1045 }
1046 LeaveCriticalSection(&cookie_cs);
1047 return COOKIE_STATE_UNKNOWN;
1048 }
1049
1050 /***********************************************************************
1051 * InternetSetCookieExW (WININET.@)
1052 *
1053 * Sets cookie for the specified url
1054 */
1055 DWORD WINAPI InternetSetCookieExW(LPCWSTR lpszUrl, LPCWSTR lpszCookieName,
1056 LPCWSTR lpCookieData, DWORD flags, DWORD_PTR reserved)
1057 {
1058 substr_t host, path, name, data;
1059 BOOL ret;
1060
1061 TRACE("(%s, %s, %s, %x, %lx)\n", debugstr_w(lpszUrl), debugstr_w(lpszCookieName),
1062 debugstr_w(lpCookieData), flags, reserved);
1063
1064 if (flags & ~INTERNET_COOKIE_HTTPONLY)
1065 FIXME("flags %x not supported\n", flags);
1066
1067 if (!lpszUrl || !lpCookieData)
1068 {
1069 SetLastError(ERROR_INVALID_PARAMETER);
1070 return COOKIE_STATE_UNKNOWN;
1071 }
1072
1073 ret = cookie_parse_url(lpszUrl, &host, &path);
1074 if (!ret || !host.len) return COOKIE_STATE_UNKNOWN;
1075
1076 if (!lpszCookieName) {
1077 const WCHAR *ptr;
1078
1079 /* some apps (or is it us??) try to add a cookie with no cookie name, but
1080 * the cookie data in the form of name[=data].
1081 */
1082 if (!(ptr = strchrW(lpCookieData, '=')))
1083 ptr = lpCookieData + strlenW(lpCookieData);
1084
1085 name = substr(lpCookieData, ptr - lpCookieData);
1086 data = substrz(*ptr == '=' ? ptr+1 : ptr);
1087 }else {
1088 name = substrz(lpszCookieName);
1089 data = substrz(lpCookieData);
1090 }
1091
1092 return set_cookie(host, path, name, data, flags);
1093 }
1094
1095 /***********************************************************************
1096 * InternetSetCookieW (WININET.@)
1097 *
1098 * Sets a cookie for the specified URL.
1099 */
1100 BOOL WINAPI InternetSetCookieW(const WCHAR *url, const WCHAR *name, const WCHAR *data)
1101 {
1102 TRACE("(%s, %s, %s)\n", debugstr_w(url), debugstr_w(name), debugstr_w(data));
1103
1104 return InternetSetCookieExW(url, name, data, 0, 0) == COOKIE_STATE_ACCEPT;
1105 }
1106
1107 /***********************************************************************
1108 * InternetSetCookieA (WININET.@)
1109 *
1110 * Sets cookie for the specified url
1111 *
1112 * RETURNS
1113 * TRUE on success
1114 * FALSE on failure
1115 *
1116 */
1117 BOOL WINAPI InternetSetCookieA(LPCSTR lpszUrl, LPCSTR lpszCookieName,
1118 LPCSTR lpCookieData)
1119 {
1120 LPWSTR data, url, name;
1121 BOOL r;
1122
1123 TRACE("(%s,%s,%s)\n", debugstr_a(lpszUrl),
1124 debugstr_a(lpszCookieName), debugstr_a(lpCookieData));
1125
1126 url = heap_strdupAtoW(lpszUrl);
1127 name = heap_strdupAtoW(lpszCookieName);
1128 data = heap_strdupAtoW(lpCookieData);
1129
1130 r = InternetSetCookieW( url, name, data );
1131
1132 heap_free( data );
1133 heap_free( name );
1134 heap_free( url );
1135 return r;
1136 }
1137
1138 /***********************************************************************
1139 * InternetSetCookieExA (WININET.@)
1140 *
1141 * See InternetSetCookieExW.
1142 */
1143 DWORD WINAPI InternetSetCookieExA( LPCSTR lpszURL, LPCSTR lpszCookieName, LPCSTR lpszCookieData,
1144 DWORD dwFlags, DWORD_PTR dwReserved)
1145 {
1146 WCHAR *data, *url, *name;
1147 DWORD r;
1148
1149 TRACE("(%s, %s, %s, %x, %lx)\n", debugstr_a(lpszURL), debugstr_a(lpszCookieName),
1150 debugstr_a(lpszCookieData), dwFlags, dwReserved);
1151
1152 url = heap_strdupAtoW(lpszURL);
1153 name = heap_strdupAtoW(lpszCookieName);
1154 data = heap_strdupAtoW(lpszCookieData);
1155
1156 r = InternetSetCookieExW(url, name, data, dwFlags, dwReserved);
1157
1158 heap_free( data );
1159 heap_free( name );
1160 heap_free( url );
1161 return r;
1162 }
1163
1164 /***********************************************************************
1165 * InternetClearAllPerSiteCookieDecisions (WININET.@)
1166 *
1167 * Clears all per-site decisions about cookies.
1168 *
1169 * RETURNS
1170 * TRUE on success
1171 * FALSE on failure
1172 *
1173 */
1174 BOOL WINAPI InternetClearAllPerSiteCookieDecisions( VOID )
1175 {
1176 FIXME("stub\n");
1177 return TRUE;
1178 }
1179
1180 /***********************************************************************
1181 * InternetEnumPerSiteCookieDecisionA (WININET.@)
1182 *
1183 * See InternetEnumPerSiteCookieDecisionW.
1184 */
1185 BOOL WINAPI InternetEnumPerSiteCookieDecisionA( LPSTR pszSiteName, ULONG *pcSiteNameSize,
1186 ULONG *pdwDecision, ULONG dwIndex )
1187 {
1188 FIXME("(%s, %p, %p, 0x%08x) stub\n",
1189 debugstr_a(pszSiteName), pcSiteNameSize, pdwDecision, dwIndex);
1190 return FALSE;
1191 }
1192
1193 /***********************************************************************
1194 * InternetEnumPerSiteCookieDecisionW (WININET.@)
1195 *
1196 * Enumerates all per-site decisions about cookies.
1197 *
1198 * RETURNS
1199 * TRUE on success
1200 * FALSE on failure
1201 *
1202 */
1203 BOOL WINAPI InternetEnumPerSiteCookieDecisionW( LPWSTR pszSiteName, ULONG *pcSiteNameSize,
1204 ULONG *pdwDecision, ULONG dwIndex )
1205 {
1206 FIXME("(%s, %p, %p, 0x%08x) stub\n",
1207 debugstr_w(pszSiteName), pcSiteNameSize, pdwDecision, dwIndex);
1208 return FALSE;
1209 }
1210
1211 /***********************************************************************
1212 * InternetGetPerSiteCookieDecisionA (WININET.@)
1213 */
1214 BOOL WINAPI InternetGetPerSiteCookieDecisionA( LPCSTR pwchHostName, ULONG *pResult )
1215 {
1216 FIXME("(%s, %p) stub\n", debugstr_a(pwchHostName), pResult);
1217 return FALSE;
1218 }
1219
1220 /***********************************************************************
1221 * InternetGetPerSiteCookieDecisionW (WININET.@)
1222 */
1223 BOOL WINAPI InternetGetPerSiteCookieDecisionW( LPCWSTR pwchHostName, ULONG *pResult )
1224 {
1225 FIXME("(%s, %p) stub\n", debugstr_w(pwchHostName), pResult);
1226 return FALSE;
1227 }
1228
1229 /***********************************************************************
1230 * InternetSetPerSiteCookieDecisionA (WININET.@)
1231 */
1232 BOOL WINAPI InternetSetPerSiteCookieDecisionA( LPCSTR pchHostName, DWORD dwDecision )
1233 {
1234 FIXME("(%s, 0x%08x) stub\n", debugstr_a(pchHostName), dwDecision);
1235 return FALSE;
1236 }
1237
1238 /***********************************************************************
1239 * InternetSetPerSiteCookieDecisionW (WININET.@)
1240 */
1241 BOOL WINAPI InternetSetPerSiteCookieDecisionW( LPCWSTR pchHostName, DWORD dwDecision )
1242 {
1243 FIXME("(%s, 0x%08x) stub\n", debugstr_w(pchHostName), dwDecision);
1244 return FALSE;
1245 }
1246
1247 void free_cookie(void)
1248 {
1249 EnterCriticalSection(&cookie_cs);
1250
1251 free_cookie_domain_list(&domain_list);
1252
1253 LeaveCriticalSection(&cookie_cs);
1254 }