| /* | |
| * Wininet - cookie handling stuff | |
| * | |
| * Copyright 2002 TransGaming Technologies Inc. | |
| * | |
| * David Hammerton | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | |
| */ | |
| #include "ws2tcpip.h" | |
| #include <stdarg.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <assert.h> | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "wininet.h" | |
| #include "lmcons.h" | |
| #include "winerror.h" | |
| #include "wine/debug.h" | |
| #include "internet.h" | |
| #define RESPONSE_TIMEOUT 30 /* FROM internet.c */ | |
| WINE_DEFAULT_DEBUG_CHANNEL(wininet); | |
| /* FIXME | |
| * Cookies could use A LOT OF MEMORY. We need some kind of memory management here! | |
| */ | |
| struct _cookie_domain_t; | |
| struct _cookie_container_t; | |
| typedef struct _cookie_t { | |
| struct list entry; | |
| struct _cookie_container_t *container; | |
| WCHAR *name; | |
| WCHAR *data; | |
| DWORD flags; | |
| FILETIME expiry; | |
| FILETIME create; | |
| } cookie_t; | |
| typedef struct _cookie_container_t { | |
| struct list entry; | |
| WCHAR *cookie_url; | |
| substr_t path; | |
| struct _cookie_domain_t *domain; | |
| struct list cookie_list; | |
| } cookie_container_t; | |
| typedef struct _cookie_domain_t { | |
| struct list entry; | |
| WCHAR *domain; | |
| unsigned subdomain_len; | |
| struct _cookie_domain_t *parent; | |
| struct list subdomain_list; | |
| /* List of stored paths sorted by length of the path. */ | |
| struct list path_list; | |
| } cookie_domain_t; | |
| static CRITICAL_SECTION cookie_cs; | |
| static CRITICAL_SECTION_DEBUG cookie_cs_debug = | |
| { | |
| 0, 0, &cookie_cs, | |
| { &cookie_cs_debug.ProcessLocksList, &cookie_cs_debug.ProcessLocksList }, | |
| 0, 0, { (DWORD_PTR)(__FILE__ ": cookie_cs") } | |
| }; | |
| static CRITICAL_SECTION cookie_cs = { &cookie_cs_debug, -1, 0, 0, 0, 0 }; | |
| static struct list domain_list = LIST_INIT(domain_list); | |
| static cookie_domain_t *get_cookie_domain(substr_t domain, BOOL create) | |
| { | |
| const WCHAR *ptr = domain.str + domain.len, *ptr_end, *subdomain_ptr; | |
| cookie_domain_t *iter, *current_domain, *prev_domain = NULL; | |
| struct list *current_list = &domain_list; | |
| while(1) { | |
| for(ptr_end = ptr--; ptr > domain.str && *ptr != '.'; ptr--); | |
| subdomain_ptr = *ptr == '.' ? ptr+1 : ptr; | |
| current_domain = NULL; | |
| LIST_FOR_EACH_ENTRY(iter, current_list, cookie_domain_t, entry) { | |
| if(ptr_end-subdomain_ptr == iter->subdomain_len | |
| && !memcmp(subdomain_ptr, iter->domain, iter->subdomain_len*sizeof(WCHAR))) { | |
| current_domain = iter; | |
| break; | |
| } | |
| } | |
| if(!current_domain) { | |
| if(!create) | |
| return prev_domain; | |
| current_domain = heap_alloc(sizeof(*current_domain)); | |
| if(!current_domain) | |
| return NULL; | |
| current_domain->domain = heap_strndupW(subdomain_ptr, domain.str + domain.len - subdomain_ptr); | |
| if(!current_domain->domain) { | |
| heap_free(current_domain); | |
| return NULL; | |
| } | |
| current_domain->subdomain_len = ptr_end-subdomain_ptr; | |
| current_domain->parent = prev_domain; | |
| list_init(¤t_domain->path_list); | |
| list_init(¤t_domain->subdomain_list); | |
| list_add_tail(current_list, ¤t_domain->entry); | |
| } | |
| if(ptr == domain.str) | |
| return current_domain; | |
| prev_domain = current_domain; | |
| current_list = ¤t_domain->subdomain_list; | |
| } | |
| } | |
| static WCHAR *create_cookie_url(substr_t domain, substr_t path, substr_t *ret_path) | |
| { | |
| WCHAR user[UNLEN], *p, *url; | |
| DWORD len, user_len, i; | |
| static const WCHAR cookie_prefix[] = {'C','o','o','k','i','e',':'}; | |
| user_len = sizeof(user)/sizeof(WCHAR); | |
| if(!GetUserNameW(user, &user_len)) | |
| return FALSE; | |
| user_len--; | |
| len = sizeof(cookie_prefix)/sizeof(WCHAR) + user_len + 1 /* @ */ + domain.len + path.len; | |
| url = heap_alloc((len+1) * sizeof(WCHAR)); | |
| if(!url) | |
| return NULL; | |
| memcpy(url, cookie_prefix, sizeof(cookie_prefix)); | |
| p = url + sizeof(cookie_prefix)/sizeof(WCHAR); | |
| memcpy(p, user, user_len*sizeof(WCHAR)); | |
| p += user_len; | |
| *p++ = '@'; | |
| memcpy(p, domain.str, domain.len*sizeof(WCHAR)); | |
| p += domain.len; | |
| for(i=0; i < path.len; i++) | |
| p[i] = tolowerW(path.str[i]); | |
| p[path.len] = 0; | |
| ret_path->str = p; | |
| ret_path->len = path.len; | |
| return url; | |
| } | |
| static cookie_container_t *get_cookie_container(substr_t domain, substr_t path, BOOL create) | |
| { | |
| cookie_domain_t *cookie_domain; | |
| cookie_container_t *cookie_container, *iter; | |
| cookie_domain = get_cookie_domain(domain, create); | |
| if(!cookie_domain) | |
| return NULL; | |
| LIST_FOR_EACH_ENTRY(cookie_container, &cookie_domain->path_list, cookie_container_t, entry) { | |
| if(cookie_container->path.len < path.len) | |
| break; | |
| if(path.len == cookie_container->path.len && !strncmpiW(cookie_container->path.str, path.str, path.len)) | |
| return cookie_container; | |
| } | |
| if(!create) | |
| return NULL; | |
| cookie_container = heap_alloc(sizeof(*cookie_container)); | |
| if(!cookie_container) | |
| return NULL; | |
| cookie_container->cookie_url = create_cookie_url(substrz(cookie_domain->domain), path, &cookie_container->path); | |
| if(!cookie_container->cookie_url) { | |
| heap_free(cookie_container); | |
| return NULL; | |
| } | |
| cookie_container->domain = cookie_domain; | |
| list_init(&cookie_container->cookie_list); | |
| LIST_FOR_EACH_ENTRY(iter, &cookie_domain->path_list, cookie_container_t, entry) { | |
| if(iter->path.len <= path.len) { | |
| list_add_before(&iter->entry, &cookie_container->entry); | |
| return cookie_container; | |
| } | |
| } | |
| list_add_tail(&cookie_domain->path_list, &cookie_container->entry); | |
| return cookie_container; | |
| } | |
| static void delete_cookie(cookie_t *cookie) | |
| { | |
| list_remove(&cookie->entry); | |
| heap_free(cookie->name); | |
| heap_free(cookie->data); | |
| heap_free(cookie); | |
| } | |
| static cookie_t *alloc_cookie(substr_t name, substr_t data, FILETIME expiry, FILETIME create_time, DWORD flags) | |
| { | |
| cookie_t *new_cookie; | |
| new_cookie = heap_alloc(sizeof(*new_cookie)); | |
| if(!new_cookie) | |
| return NULL; | |
| new_cookie->expiry = expiry; | |
| new_cookie->create = create_time; | |
| new_cookie->flags = flags; | |
| list_init(&new_cookie->entry); | |
| new_cookie->name = heap_strndupW(name.str, name.len); | |
| new_cookie->data = heap_strndupW(data.str, data.len); | |
| if(!new_cookie->name || !new_cookie->data) { | |
| delete_cookie(new_cookie); | |
| return NULL; | |
| } | |
| return new_cookie; | |
| } | |
| static cookie_t *find_cookie(cookie_container_t *container, substr_t name) | |
| { | |
| cookie_t *iter; | |
| LIST_FOR_EACH_ENTRY(iter, &container->cookie_list, cookie_t, entry) { | |
| if(strlenW(iter->name) == name.len && !strncmpiW(iter->name, name.str, name.len)) | |
| return iter; | |
| } | |
| return NULL; | |
| } | |
| static void add_cookie(cookie_container_t *container, cookie_t *new_cookie) | |
| { | |
| TRACE("Adding %s=%s to %s\n", debugstr_w(new_cookie->name), debugstr_w(new_cookie->data), | |
| debugstr_w(container->cookie_url)); | |
| list_add_tail(&container->cookie_list, &new_cookie->entry); | |
| new_cookie->container = container; | |
| } | |
| static void replace_cookie(cookie_container_t *container, cookie_t *new_cookie) | |
| { | |
| cookie_t *old_cookie; | |
| old_cookie = find_cookie(container, substrz(new_cookie->name)); | |
| if(old_cookie) | |
| delete_cookie(old_cookie); | |
| add_cookie(container, new_cookie); | |
| } | |
| static BOOL cookie_match_path(cookie_container_t *container, substr_t path) | |
| { | |
| return path.len >= container->path.len && !strncmpiW(container->path.str, path.str, container->path.len); | |
| } | |
| static BOOL load_persistent_cookie(substr_t domain, substr_t path) | |
| { | |
| INTERNET_CACHE_ENTRY_INFOW *info; | |
| cookie_container_t *cookie_container; | |
| cookie_t *new_cookie; | |
| HANDLE cookie; | |
| char *str = NULL, *pbeg, *pend; | |
| DWORD size, flags; | |
| WCHAR *name, *data; | |
| FILETIME expiry, create, time; | |
| cookie_container = get_cookie_container(domain, path, TRUE); | |
| if(!cookie_container) | |
| return FALSE; | |
| size = 0; | |
| RetrieveUrlCacheEntryStreamW(cookie_container->cookie_url, NULL, &size, FALSE, 0); | |
| if(GetLastError() != ERROR_INSUFFICIENT_BUFFER) | |
| return TRUE; | |
| info = heap_alloc(size); | |
| if(!info) | |
| return FALSE; | |
| cookie = RetrieveUrlCacheEntryStreamW(cookie_container->cookie_url, info, &size, FALSE, 0); | |
| size = info->dwSizeLow; | |
| heap_free(info); | |
| if(!cookie) | |
| return FALSE; | |
| if(!(str = heap_alloc(size+1)) || !ReadUrlCacheEntryStream(cookie, 0, str, &size, 0)) { | |
| UnlockUrlCacheEntryStream(cookie, 0); | |
| heap_free(str); | |
| return FALSE; | |
| } | |
| str[size] = 0; | |
| UnlockUrlCacheEntryStream(cookie, 0); | |
| GetSystemTimeAsFileTime(&time); | |
| for(pbeg=str; pbeg && *pbeg; name=data=NULL) { | |
| pend = strchr(pbeg, '\n'); | |
| if(!pend) | |
| break; | |
| *pend = 0; | |
| name = heap_strdupAtoW(pbeg); | |
| pbeg = pend+1; | |
| pend = strchr(pbeg, '\n'); | |
| if(!pend) | |
| break; | |
| *pend = 0; | |
| data = heap_strdupAtoW(pbeg); | |
| pbeg = strchr(pend+1, '\n'); | |
| if(!pbeg) | |
| break; | |
| sscanf(pbeg, "%u %u %u %u %u", &flags, &expiry.dwLowDateTime, &expiry.dwHighDateTime, | |
| &create.dwLowDateTime, &create.dwHighDateTime); | |
| /* skip "*\n" */ | |
| pbeg = strchr(pbeg, '*'); | |
| if(pbeg) { | |
| pbeg++; | |
| if(*pbeg) | |
| pbeg++; | |
| } | |
| if(!name || !data) | |
| break; | |
| if(CompareFileTime(&time, &expiry) <= 0) { | |
| new_cookie = alloc_cookie(substr(NULL, 0), substr(NULL, 0), expiry, create, flags); | |
| if(!new_cookie) | |
| break; | |
| new_cookie->name = name; | |
| new_cookie->data = data; | |
| replace_cookie(cookie_container, new_cookie); | |
| }else { | |
| heap_free(name); | |
| heap_free(data); | |
| } | |
| } | |
| heap_free(str); | |
| heap_free(name); | |
| heap_free(data); | |
| return TRUE; | |
| } | |
| static BOOL save_persistent_cookie(cookie_container_t *container) | |
| { | |
| static const WCHAR txtW[] = {'t','x','t',0}; | |
| WCHAR cookie_file[MAX_PATH]; | |
| HANDLE cookie_handle; | |
| cookie_t *cookie_container = NULL, *cookie_iter; | |
| BOOL do_save = FALSE; | |
| char buf[64], *dyn_buf; | |
| FILETIME time; | |
| DWORD bytes_written; | |
| size_t len; | |
| /* check if there's anything to save */ | |
| GetSystemTimeAsFileTime(&time); | |
| LIST_FOR_EACH_ENTRY_SAFE(cookie_container, cookie_iter, &container->cookie_list, cookie_t, entry) | |
| { | |
| if((cookie_container->expiry.dwLowDateTime || cookie_container->expiry.dwHighDateTime) | |
| && CompareFileTime(&time, &cookie_container->expiry) > 0) { | |
| delete_cookie(cookie_container); | |
| continue; | |
| } | |
| if(!(cookie_container->flags & INTERNET_COOKIE_IS_SESSION)) { | |
| do_save = TRUE; | |
| break; | |
| } | |
| } | |
| if(!do_save) { | |
| DeleteUrlCacheEntryW(container->cookie_url); | |
| return TRUE; | |
| } | |
| if(!CreateUrlCacheEntryW(container->cookie_url, 0, txtW, cookie_file, 0)) | |
| return FALSE; | |
| cookie_handle = CreateFileW(cookie_file, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); | |
| if(cookie_handle == INVALID_HANDLE_VALUE) { | |
| DeleteFileW(cookie_file); | |
| return FALSE; | |
| } | |
| LIST_FOR_EACH_ENTRY(cookie_container, &container->cookie_list, cookie_t, entry) | |
| { | |
| if(cookie_container->flags & INTERNET_COOKIE_IS_SESSION) | |
| continue; | |
| dyn_buf = heap_strdupWtoA(cookie_container->name); | |
| if(!dyn_buf || !WriteFile(cookie_handle, dyn_buf, strlen(dyn_buf), &bytes_written, NULL)) { | |
| heap_free(dyn_buf); | |
| do_save = FALSE; | |
| break; | |
| } | |
| heap_free(dyn_buf); | |
| if(!WriteFile(cookie_handle, "\n", 1, &bytes_written, NULL)) { | |
| do_save = FALSE; | |
| break; | |
| } | |
| dyn_buf = heap_strdupWtoA(cookie_container->data); | |
| if(!dyn_buf || !WriteFile(cookie_handle, dyn_buf, strlen(dyn_buf), &bytes_written, NULL)) { | |
| heap_free(dyn_buf); | |
| do_save = FALSE; | |
| break; | |
| } | |
| heap_free(dyn_buf); | |
| if(!WriteFile(cookie_handle, "\n", 1, &bytes_written, NULL)) { | |
| do_save = FALSE; | |
| break; | |
| } | |
| dyn_buf = heap_strdupWtoA(container->domain->domain); | |
| if(!dyn_buf || !WriteFile(cookie_handle, dyn_buf, strlen(dyn_buf), &bytes_written, NULL)) { | |
| heap_free(dyn_buf); | |
| do_save = FALSE; | |
| break; | |
| } | |
| heap_free(dyn_buf); | |
| len = WideCharToMultiByte(CP_ACP, 0, container->path.str, container->path.len, NULL, 0, NULL, NULL); | |
| dyn_buf = heap_alloc(len+1); | |
| if(dyn_buf) { | |
| WideCharToMultiByte(CP_ACP, 0, container->path.str, container->path.len, dyn_buf, len, NULL, NULL); | |
| dyn_buf[len] = 0; | |
| } | |
| if(!dyn_buf || !WriteFile(cookie_handle, dyn_buf, strlen(dyn_buf), &bytes_written, NULL)) { | |
| heap_free(dyn_buf); | |
| do_save = FALSE; | |
| break; | |
| } | |
| heap_free(dyn_buf); | |
| sprintf(buf, "\n%u\n%u\n%u\n%u\n%u\n*\n", cookie_container->flags, | |
| cookie_container->expiry.dwLowDateTime, cookie_container->expiry.dwHighDateTime, | |
| cookie_container->create.dwLowDateTime, cookie_container->create.dwHighDateTime); | |
| if(!WriteFile(cookie_handle, buf, strlen(buf), &bytes_written, NULL)) { | |
| do_save = FALSE; | |
| break; | |
| } | |
| } | |
| CloseHandle(cookie_handle); | |
| if(!do_save) { | |
| ERR("error saving cookie file\n"); | |
| DeleteFileW(cookie_file); | |
| return FALSE; | |
| } | |
| memset(&time, 0, sizeof(time)); | |
| return CommitUrlCacheEntryW(container->cookie_url, cookie_file, time, time, 0, NULL, 0, txtW, 0); | |
| } | |
| static BOOL cookie_parse_url(const WCHAR *url, substr_t *host, substr_t *path) | |
| { | |
| URL_COMPONENTSW comp = { sizeof(comp) }; | |
| static const WCHAR rootW[] = {'/',0}; | |
| comp.dwHostNameLength = 1; | |
| comp.dwUrlPathLength = 1; | |
| if(!InternetCrackUrlW(url, 0, 0, &comp) || !comp.dwHostNameLength) | |
| return FALSE; | |
| /* discard the webpage off the end of the path */ | |
| while(comp.dwUrlPathLength && comp.lpszUrlPath[comp.dwUrlPathLength-1] != '/') | |
| comp.dwUrlPathLength--; | |
| *host = substr(comp.lpszHostName, comp.dwHostNameLength); | |
| *path = comp.dwUrlPathLength ? substr(comp.lpszUrlPath, comp.dwUrlPathLength) : substr(rootW, 1); | |
| return TRUE; | |
| } | |
| typedef struct { | |
| cookie_t **cookies; | |
| unsigned cnt; | |
| unsigned size; | |
| unsigned string_len; | |
| } cookie_set_t; | |
| static DWORD get_cookie(substr_t host, substr_t path, DWORD flags, cookie_set_t *res) | |
| { | |
| static const WCHAR empty_path[] = { '/',0 }; | |
| const WCHAR *p; | |
| cookie_domain_t *domain; | |
| cookie_container_t *container; | |
| FILETIME tm; | |
| GetSystemTimeAsFileTime(&tm); | |
| p = host.str + host.len; | |
| while(p > host.str && p[-1] != '.') p--; | |
| while(p != host.str) { | |
| p--; | |
| while(p > host.str && p[-1] != '.') p--; | |
| if(p == host.str) break; | |
| load_persistent_cookie(substr(p, host.str+host.len-p), substr(empty_path, 1)); | |
| } | |
| p = path.str + path.len; | |
| do { | |
| load_persistent_cookie(host, substr(path.str, p-path.str)); | |
| p--; | |
| while(p > path.str && p[-1] != '/') p--; | |
| }while(p != path.str); | |
| domain = get_cookie_domain(host, FALSE); | |
| if(!domain) { | |
| TRACE("Unknown host %s\n", debugstr_wn(host.str, host.len)); | |
| return ERROR_NO_MORE_ITEMS; | |
| } | |
| for(domain = get_cookie_domain(host, FALSE); domain; domain = domain->parent) { | |
| LIST_FOR_EACH_ENTRY(container, &domain->path_list, cookie_container_t, entry) { | |
| struct list *cursor, *cursor2; | |
| if(!cookie_match_path(container, path)) | |
| continue; | |
| LIST_FOR_EACH_SAFE(cursor, cursor2, &container->cookie_list) { | |
| cookie_t *cookie_iter = LIST_ENTRY(cursor, cookie_t, entry); | |
| /* check for expiry */ | |
| if((cookie_iter->expiry.dwLowDateTime != 0 || cookie_iter->expiry.dwHighDateTime != 0) | |
| && CompareFileTime(&tm, &cookie_iter->expiry) > 0) { | |
| TRACE("Found expired cookie. deleting\n"); | |
| delete_cookie(cookie_iter); | |
| continue; | |
| } | |
| if((cookie_iter->flags & INTERNET_COOKIE_HTTPONLY) && !(flags & INTERNET_COOKIE_HTTPONLY)) | |
| continue; | |
| if(!res->size) { | |
| res->cookies = heap_alloc(4*sizeof(*res->cookies)); | |
| if(!res->cookies) | |
| continue; | |
| res->size = 4; | |
| }else if(res->cnt == res->size) { | |
| cookie_t **new_cookies = heap_realloc(res->cookies, res->size*2*sizeof(*res->cookies)); | |
| if(!new_cookies) | |
| continue; | |
| res->cookies = new_cookies; | |
| res->size *= 2; | |
| } | |
| TRACE("%s = %s domain %s path %s\n", debugstr_w(cookie_iter->name), debugstr_w(cookie_iter->data), | |
| debugstr_w(domain->domain), debugstr_wn(container->path.str, container->path.len)); | |
| if(res->cnt) | |
| res->string_len += 2; /* '; ' */ | |
| res->cookies[res->cnt++] = cookie_iter; | |
| res->string_len += strlenW(cookie_iter->name); | |
| if(*cookie_iter->data) | |
| res->string_len += 1 /* = */ + strlenW(cookie_iter->data); | |
| } | |
| } | |
| } | |
| return ERROR_SUCCESS; | |
| } | |
| static void cookie_set_to_string(const cookie_set_t *cookie_set, WCHAR *str) | |
| { | |
| WCHAR *ptr = str; | |
| unsigned i, len; | |
| for(i=0; i<cookie_set->cnt; i++) { | |
| if(i) { | |
| *ptr++ = ';'; | |
| *ptr++ = ' '; | |
| } | |
| len = strlenW(cookie_set->cookies[i]->name); | |
| memcpy(ptr, cookie_set->cookies[i]->name, len*sizeof(WCHAR)); | |
| ptr += len; | |
| if(*cookie_set->cookies[i]->data) { | |
| *ptr++ = '='; | |
| len = strlenW(cookie_set->cookies[i]->data); | |
| memcpy(ptr, cookie_set->cookies[i]->data, len*sizeof(WCHAR)); | |
| ptr += len; | |
| } | |
| } | |
| assert(ptr-str == cookie_set->string_len); | |
| TRACE("%s\n", debugstr_wn(str, ptr-str)); | |
| } | |
| DWORD get_cookie_header(const WCHAR *host, const WCHAR *path, WCHAR **ret) | |
| { | |
| cookie_set_t cookie_set = {0}; | |
| DWORD res; | |
| static const WCHAR cookieW[] = {'C','o','o','k','i','e',':',' '}; | |
| EnterCriticalSection(&cookie_cs); | |
| res = get_cookie(substrz(host), substrz(path), INTERNET_COOKIE_HTTPONLY, &cookie_set); | |
| if(res != ERROR_SUCCESS) { | |
| LeaveCriticalSection(&cookie_cs); | |
| return res; | |
| } | |
| if(cookie_set.cnt) { | |
| WCHAR *header, *ptr; | |
| ptr = header = heap_alloc(sizeof(cookieW) + (cookie_set.string_len + 3 /* crlf0 */) * sizeof(WCHAR)); | |
| if(header) { | |
| memcpy(ptr, cookieW, sizeof(cookieW)); | |
| ptr += sizeof(cookieW)/sizeof(*cookieW); | |
| cookie_set_to_string(&cookie_set, ptr); | |
| heap_free(cookie_set.cookies); | |
| ptr += cookie_set.string_len; | |
| *ptr++ = '\r'; | |
| *ptr++ = '\n'; | |
| *ptr++ = 0; | |
| *ret = header; | |
| }else { | |
| res = ERROR_NOT_ENOUGH_MEMORY; | |
| } | |
| }else { | |
| *ret = NULL; | |
| } | |
| LeaveCriticalSection(&cookie_cs); | |
| return res; | |
| } | |
| static void free_cookie_domain_list(struct list *list) | |
| { | |
| cookie_container_t *container; | |
| cookie_domain_t *domain; | |
| while(!list_empty(list)) { | |
| domain = LIST_ENTRY(list_head(list), cookie_domain_t, entry); | |
| free_cookie_domain_list(&domain->subdomain_list); | |
| while(!list_empty(&domain->path_list)) { | |
| container = LIST_ENTRY(list_head(&domain->path_list), cookie_container_t, entry); | |
| while(!list_empty(&container->cookie_list)) | |
| delete_cookie(LIST_ENTRY(list_head(&container->cookie_list), cookie_t, entry)); | |
| heap_free(container->cookie_url); | |
| list_remove(&container->entry); | |
| heap_free(container); | |
| } | |
| heap_free(domain->domain); | |
| list_remove(&domain->entry); | |
| heap_free(domain); | |
| } | |
| } | |
| /*********************************************************************** | |
| * InternetGetCookieExW (WININET.@) | |
| * | |
| * Retrieve cookie from the specified url | |
| * | |
| * It should be noted that on windows the lpszCookieName parameter is "not implemented". | |
| * So it won't be implemented here. | |
| * | |
| * RETURNS | |
| * TRUE on success | |
| * FALSE on failure | |
| * | |
| */ | |
| BOOL WINAPI InternetGetCookieExW(LPCWSTR lpszUrl, LPCWSTR lpszCookieName, | |
| LPWSTR lpCookieData, LPDWORD lpdwSize, DWORD flags, void *reserved) | |
| { | |
| cookie_set_t cookie_set = {0}; | |
| substr_t host, path; | |
| DWORD res; | |
| BOOL ret; | |
| TRACE("(%s, %s, %p, %p, %x, %p)\n", debugstr_w(lpszUrl),debugstr_w(lpszCookieName), lpCookieData, lpdwSize, flags, reserved); | |
| if (flags) | |
| FIXME("flags 0x%08x not supported\n", flags); | |
| if (!lpszUrl) | |
| { | |
| SetLastError(ERROR_INVALID_PARAMETER); | |
| return FALSE; | |
| } | |
| ret = cookie_parse_url(lpszUrl, &host, &path); | |
| if (!ret) { | |
| SetLastError(ERROR_INVALID_PARAMETER); | |
| return FALSE; | |
| } | |
| EnterCriticalSection(&cookie_cs); | |
| res = get_cookie(host, path, flags, &cookie_set); | |
| if(res != ERROR_SUCCESS) { | |
| LeaveCriticalSection(&cookie_cs); | |
| SetLastError(res); | |
| return FALSE; | |
| } | |
| if(cookie_set.cnt) { | |
| if(!lpCookieData || cookie_set.string_len+1 > *lpdwSize) { | |
| *lpdwSize = (cookie_set.string_len + 1) * sizeof(WCHAR); | |
| TRACE("returning %u\n", *lpdwSize); | |
| if(lpCookieData) { | |
| SetLastError(ERROR_INSUFFICIENT_BUFFER); | |
| ret = FALSE; | |
| } | |
| }else { | |
| *lpdwSize = cookie_set.string_len + 1; | |
| cookie_set_to_string(&cookie_set, lpCookieData); | |
| lpCookieData[cookie_set.string_len] = 0; | |
| } | |
| }else { | |
| TRACE("no cookies found for %s\n", debugstr_wn(host.str, host.len)); | |
| SetLastError(ERROR_NO_MORE_ITEMS); | |
| ret = FALSE; | |
| } | |
| heap_free(cookie_set.cookies); | |
| LeaveCriticalSection(&cookie_cs); | |
| return ret; | |
| } | |
| /*********************************************************************** | |
| * InternetGetCookieW (WININET.@) | |
| * | |
| * Retrieve cookie for the specified URL. | |
| */ | |
| BOOL WINAPI InternetGetCookieW(const WCHAR *url, const WCHAR *name, WCHAR *data, DWORD *size) | |
| { | |
| TRACE("(%s, %s, %s, %p)\n", debugstr_w(url), debugstr_w(name), debugstr_w(data), size); | |
| return InternetGetCookieExW(url, name, data, size, 0, NULL); | |
| } | |
| /*********************************************************************** | |
| * InternetGetCookieExA (WININET.@) | |
| * | |
| * Retrieve cookie from the specified url | |
| * | |
| * RETURNS | |
| * TRUE on success | |
| * FALSE on failure | |
| * | |
| */ | |
| BOOL WINAPI InternetGetCookieExA(LPCSTR lpszUrl, LPCSTR lpszCookieName, | |
| LPSTR lpCookieData, LPDWORD lpdwSize, DWORD flags, void *reserved) | |
| { | |
| WCHAR *url, *name; | |
| DWORD len, size = 0; | |
| BOOL r; | |
| TRACE("(%s %s %p %p(%u) %x %p)\n", debugstr_a(lpszUrl), debugstr_a(lpszCookieName), | |
| lpCookieData, lpdwSize, lpdwSize ? *lpdwSize : 0, flags, reserved); | |
| url = heap_strdupAtoW(lpszUrl); | |
| name = heap_strdupAtoW(lpszCookieName); | |
| r = InternetGetCookieExW( url, name, NULL, &len, flags, reserved ); | |
| if( r ) | |
| { | |
| WCHAR *szCookieData; | |
| szCookieData = heap_alloc(len * sizeof(WCHAR)); | |
| if( !szCookieData ) | |
| { | |
| r = FALSE; | |
| } | |
| else | |
| { | |
| r = InternetGetCookieExW( url, name, szCookieData, &len, flags, reserved ); | |
| if(r) { | |
| size = WideCharToMultiByte( CP_ACP, 0, szCookieData, len, NULL, 0, NULL, NULL); | |
| if(lpCookieData) { | |
| if(*lpdwSize >= size) { | |
| WideCharToMultiByte( CP_ACP, 0, szCookieData, len, lpCookieData, *lpdwSize, NULL, NULL); | |
| }else { | |
| SetLastError(ERROR_INSUFFICIENT_BUFFER); | |
| r = FALSE; | |
| } | |
| } | |
| } | |
| heap_free( szCookieData ); | |
| } | |
| } | |
| *lpdwSize = size; | |
| heap_free( name ); | |
| heap_free( url ); | |
| return r; | |
| } | |
| /*********************************************************************** | |
| * InternetGetCookieA (WININET.@) | |
| * | |
| * See InternetGetCookieW. | |
| */ | |
| BOOL WINAPI InternetGetCookieA(const char *url, const char *name, char *data, DWORD *size) | |
| { | |
| TRACE("(%s, %s, %p, %p)\n", debugstr_a(url), debugstr_a(name), data, size); | |
| return InternetGetCookieExA(url, name, data, size, 0, NULL); | |
| } | |
| static BOOL is_domain_legal_for_cookie(substr_t domain, substr_t full_domain) | |
| { | |
| const WCHAR *ptr; | |
| if(!domain.len || *domain.str == '.' || !full_domain.len || *full_domain.str == '.') { | |
| SetLastError(ERROR_INVALID_NAME); | |
| return FALSE; | |
| } | |
| if(domain.len > full_domain.len || !memchrW(domain.str, '.', domain.len) || !memchrW(full_domain.str, '.', full_domain.len)) | |
| return FALSE; | |
| ptr = full_domain.str + full_domain.len - domain.len; | |
| if (strncmpiW(domain.str, ptr, domain.len) || (full_domain.len > domain.len && ptr[-1] != '.')) { | |
| SetLastError(ERROR_INVALID_PARAMETER); | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| /*********************************************************************** | |
| * IsDomainLegalCookieDomainW (WININET.@) | |
| */ | |
| BOOL WINAPI IsDomainLegalCookieDomainW(const WCHAR *domain, const WCHAR *full_domain) | |
| { | |
| FIXME("(%s, %s) semi-stub\n", debugstr_w(domain), debugstr_w(full_domain)); | |
| if (!domain || !full_domain) { | |
| SetLastError(ERROR_INVALID_PARAMETER); | |
| return FALSE; | |
| } | |
| return is_domain_legal_for_cookie(substrz(domain), substrz(full_domain)); | |
| } | |
| static void substr_skip(substr_t *str, size_t len) | |
| { | |
| assert(str->len >= len); | |
| str->str += len; | |
| str->len -= len; | |
| } | |
| DWORD set_cookie(substr_t domain, substr_t path, substr_t name, substr_t data, DWORD flags) | |
| { | |
| cookie_container_t *container; | |
| cookie_t *thisCookie; | |
| substr_t value; | |
| const WCHAR *end_ptr; | |
| FILETIME expiry, create; | |
| BOOL expired = FALSE, update_persistent = FALSE; | |
| DWORD cookie_flags = 0, len; | |
| TRACE("%s %s %s=%s %x\n", debugstr_wn(domain.str, domain.len), debugstr_wn(path.str, path.len), | |
| debugstr_wn(name.str, name.len), debugstr_wn(data.str, data.len), flags); | |
| memset(&expiry,0,sizeof(expiry)); | |
| GetSystemTimeAsFileTime(&create); | |
| /* lots of information can be parsed out of the cookie value */ | |
| if(!(end_ptr = memchrW(data.str, ';', data.len))) | |
| end_ptr = data.str + data.len; | |
| value = substr(data.str, end_ptr-data.str); | |
| data.str += value.len; | |
| data.len -= value.len; | |
| for(;;) { | |
| static const WCHAR szDomain[] = {'d','o','m','a','i','n','='}; | |
| static const WCHAR szPath[] = {'p','a','t','h','='}; | |
| static const WCHAR szExpires[] = {'e','x','p','i','r','e','s','='}; | |
| static const WCHAR szSecure[] = {'s','e','c','u','r','e'}; | |
| static const WCHAR szHttpOnly[] = {'h','t','t','p','o','n','l','y'}; | |
| static const WCHAR szVersion[] = {'v','e','r','s','i','o','n','='}; | |
| static const WCHAR max_ageW[] = {'m','a','x','-','a','g','e','='}; | |
| /* Skip ';' */ | |
| if(data.len) | |
| substr_skip(&data, 1); | |
| while(data.len && *data.str == ' ') | |
| substr_skip(&data, 1); | |
| if(!data.len) | |
| break; | |
| if(!(end_ptr = memchrW(data.str, ';', data.len))) | |
| end_ptr = data.str + data.len; | |
| if(data.len >= (len = sizeof(szDomain)/sizeof(WCHAR)) && !strncmpiW(data.str, szDomain, len)) { | |
| substr_skip(&data, len); | |
| if(data.len && *data.str == '.') | |
| substr_skip(&data, 1); | |
| if(!is_domain_legal_for_cookie(substr(data.str, end_ptr-data.str), domain)) | |
| return COOKIE_STATE_UNKNOWN; | |
| domain = substr(data.str, end_ptr-data.str); | |
| TRACE("Parsing new domain %s\n", debugstr_wn(domain.str, domain.len)); | |
| }else if(data.len >= (len = sizeof(szPath)/sizeof(WCHAR)) && !strncmpiW(data.str, szPath, len)) { | |
| substr_skip(&data, len); | |
| path = substr(data.str, end_ptr - data.str); | |
| TRACE("Parsing new path %s\n", debugstr_wn(path.str, path.len)); | |
| }else if(data.len >= (len = sizeof(szExpires)/sizeof(WCHAR)) && !strncmpiW(data.str, szExpires, len)) { | |
| SYSTEMTIME st; | |
| WCHAR buf[128]; | |
| substr_skip(&data, len); | |
| if(end_ptr - data.str < sizeof(buf)/sizeof(WCHAR)-1) { | |
| memcpy(buf, data.str, data.len*sizeof(WCHAR)); | |
| buf[data.len] = 0; | |
| if (InternetTimeToSystemTimeW(data.str, &st, 0)) { | |
| SystemTimeToFileTime(&st, &expiry); | |
| if (CompareFileTime(&create,&expiry) > 0) { | |
| TRACE("Cookie already expired.\n"); | |
| expired = TRUE; | |
| } | |
| } | |
| } | |
| }else if(data.len >= (len = sizeof(szSecure)/sizeof(WCHAR)) && !strncmpiW(data.str, szSecure, len)) { | |
| substr_skip(&data, len); | |
| FIXME("secure not handled\n"); | |
| }else if(data.len >= (len = sizeof(szHttpOnly)/sizeof(WCHAR)) && !strncmpiW(data.str, szHttpOnly, len)) { | |
| substr_skip(&data, len); | |
| if(!(flags & INTERNET_COOKIE_HTTPONLY)) { | |
| WARN("HTTP only cookie added without INTERNET_COOKIE_HTTPONLY flag\n"); | |
| SetLastError(ERROR_INVALID_OPERATION); | |
| return COOKIE_STATE_REJECT; | |
| } | |
| cookie_flags |= INTERNET_COOKIE_HTTPONLY; | |
| }else if(data.len >= (len = sizeof(szVersion)/sizeof(WCHAR)) && !strncmpiW(data.str, szVersion, len)) { | |
| substr_skip(&data, len); | |
| FIXME("version not handled (%s)\n",debugstr_wn(data.str, data.len)); | |
| }else if(data.len >= (len = sizeof(max_ageW)/sizeof(WCHAR)) && !strncmpiW(data.str, max_ageW, len)) { | |
| /* Native doesn't support Max-Age attribute. */ | |
| WARN("Max-Age ignored\n"); | |
| }else if(data.len) { | |
| FIXME("Unknown additional option %s\n", debugstr_wn(data.str, data.len)); | |
| } | |
| substr_skip(&data, end_ptr - data.str); | |
| } | |
| EnterCriticalSection(&cookie_cs); | |
| load_persistent_cookie(domain, path); | |
| container = get_cookie_container(domain, path, !expired); | |
| if(!container) { | |
| LeaveCriticalSection(&cookie_cs); | |
| return COOKIE_STATE_ACCEPT; | |
| } | |
| if(!expiry.dwLowDateTime && !expiry.dwHighDateTime) | |
| cookie_flags |= INTERNET_COOKIE_IS_SESSION; | |
| else | |
| update_persistent = TRUE; | |
| if ((thisCookie = find_cookie(container, name))) { | |
| if ((thisCookie->flags & INTERNET_COOKIE_HTTPONLY) && !(flags & INTERNET_COOKIE_HTTPONLY)) { | |
| WARN("An attempt to override httponly cookie\n"); | |
| SetLastError(ERROR_INVALID_OPERATION); | |
| LeaveCriticalSection(&cookie_cs); | |
| return COOKIE_STATE_REJECT; | |
| } | |
| if (!(thisCookie->flags & INTERNET_COOKIE_IS_SESSION)) | |
| update_persistent = TRUE; | |
| delete_cookie(thisCookie); | |
| } | |
| TRACE("setting cookie %s=%s for domain %s path %s\n", debugstr_wn(name.str, name.len), | |
| debugstr_wn(value.str, value.len), debugstr_w(container->domain->domain), | |
| debugstr_wn(container->path.str, container->path.len)); | |
| if (!expired) { | |
| cookie_t *new_cookie; | |
| new_cookie = alloc_cookie(name, value, expiry, create, cookie_flags); | |
| if(!new_cookie) { | |
| LeaveCriticalSection(&cookie_cs); | |
| return COOKIE_STATE_UNKNOWN; | |
| } | |
| add_cookie(container, new_cookie); | |
| } | |
| if (!update_persistent || save_persistent_cookie(container)) | |
| { | |
| LeaveCriticalSection(&cookie_cs); | |
| return COOKIE_STATE_ACCEPT; | |
| } | |
| LeaveCriticalSection(&cookie_cs); | |
| return COOKIE_STATE_UNKNOWN; | |
| } | |
| /*********************************************************************** | |
| * InternetSetCookieExW (WININET.@) | |
| * | |
| * Sets cookie for the specified url | |
| */ | |
| DWORD WINAPI InternetSetCookieExW(LPCWSTR lpszUrl, LPCWSTR lpszCookieName, | |
| LPCWSTR lpCookieData, DWORD flags, DWORD_PTR reserved) | |
| { | |
| substr_t host, path, name, data; | |
| BOOL ret; | |
| TRACE("(%s, %s, %s, %x, %lx)\n", debugstr_w(lpszUrl), debugstr_w(lpszCookieName), | |
| debugstr_w(lpCookieData), flags, reserved); | |
| if (flags & ~INTERNET_COOKIE_HTTPONLY) | |
| FIXME("flags %x not supported\n", flags); | |
| if (!lpszUrl || !lpCookieData) | |
| { | |
| SetLastError(ERROR_INVALID_PARAMETER); | |
| return COOKIE_STATE_UNKNOWN; | |
| } | |
| ret = cookie_parse_url(lpszUrl, &host, &path); | |
| if (!ret || !host.len) return COOKIE_STATE_UNKNOWN; | |
| if (!lpszCookieName) { | |
| const WCHAR *ptr; | |
| /* some apps (or is it us??) try to add a cookie with no cookie name, but | |
| * the cookie data in the form of name[=data]. | |
| */ | |
| if (!(ptr = strchrW(lpCookieData, '='))) | |
| ptr = lpCookieData + strlenW(lpCookieData); | |
| name = substr(lpCookieData, ptr - lpCookieData); | |
| data = substrz(*ptr == '=' ? ptr+1 : ptr); | |
| }else { | |
| name = substrz(lpszCookieName); | |
| data = substrz(lpCookieData); | |
| } | |
| return set_cookie(host, path, name, data, flags); | |
| } | |
| /*********************************************************************** | |
| * InternetSetCookieW (WININET.@) | |
| * | |
| * Sets a cookie for the specified URL. | |
| */ | |
| BOOL WINAPI InternetSetCookieW(const WCHAR *url, const WCHAR *name, const WCHAR *data) | |
| { | |
| TRACE("(%s, %s, %s)\n", debugstr_w(url), debugstr_w(name), debugstr_w(data)); | |
| return InternetSetCookieExW(url, name, data, 0, 0) == COOKIE_STATE_ACCEPT; | |
| } | |
| /*********************************************************************** | |
| * InternetSetCookieA (WININET.@) | |
| * | |
| * Sets cookie for the specified url | |
| * | |
| * RETURNS | |
| * TRUE on success | |
| * FALSE on failure | |
| * | |
| */ | |
| BOOL WINAPI InternetSetCookieA(LPCSTR lpszUrl, LPCSTR lpszCookieName, | |
| LPCSTR lpCookieData) | |
| { | |
| LPWSTR data, url, name; | |
| BOOL r; | |
| TRACE("(%s,%s,%s)\n", debugstr_a(lpszUrl), | |
| debugstr_a(lpszCookieName), debugstr_a(lpCookieData)); | |
| url = heap_strdupAtoW(lpszUrl); | |
| name = heap_strdupAtoW(lpszCookieName); | |
| data = heap_strdupAtoW(lpCookieData); | |
| r = InternetSetCookieW( url, name, data ); | |
| heap_free( data ); | |
| heap_free( name ); | |
| heap_free( url ); | |
| return r; | |
| } | |
| /*********************************************************************** | |
| * InternetSetCookieExA (WININET.@) | |
| * | |
| * See InternetSetCookieExW. | |
| */ | |
| DWORD WINAPI InternetSetCookieExA( LPCSTR lpszURL, LPCSTR lpszCookieName, LPCSTR lpszCookieData, | |
| DWORD dwFlags, DWORD_PTR dwReserved) | |
| { | |
| WCHAR *data, *url, *name; | |
| DWORD r; | |
| TRACE("(%s, %s, %s, %x, %lx)\n", debugstr_a(lpszURL), debugstr_a(lpszCookieName), | |
| debugstr_a(lpszCookieData), dwFlags, dwReserved); | |
| url = heap_strdupAtoW(lpszURL); | |
| name = heap_strdupAtoW(lpszCookieName); | |
| data = heap_strdupAtoW(lpszCookieData); | |
| r = InternetSetCookieExW(url, name, data, dwFlags, dwReserved); | |
| heap_free( data ); | |
| heap_free( name ); | |
| heap_free( url ); | |
| return r; | |
| } | |
| /*********************************************************************** | |
| * InternetClearAllPerSiteCookieDecisions (WININET.@) | |
| * | |
| * Clears all per-site decisions about cookies. | |
| * | |
| * RETURNS | |
| * TRUE on success | |
| * FALSE on failure | |
| * | |
| */ | |
| BOOL WINAPI InternetClearAllPerSiteCookieDecisions( VOID ) | |
| { | |
| FIXME("stub\n"); | |
| return TRUE; | |
| } | |
| /*********************************************************************** | |
| * InternetEnumPerSiteCookieDecisionA (WININET.@) | |
| * | |
| * See InternetEnumPerSiteCookieDecisionW. | |
| */ | |
| BOOL WINAPI InternetEnumPerSiteCookieDecisionA( LPSTR pszSiteName, ULONG *pcSiteNameSize, | |
| ULONG *pdwDecision, ULONG dwIndex ) | |
| { | |
| FIXME("(%s, %p, %p, 0x%08x) stub\n", | |
| debugstr_a(pszSiteName), pcSiteNameSize, pdwDecision, dwIndex); | |
| return FALSE; | |
| } | |
| /*********************************************************************** | |
| * InternetEnumPerSiteCookieDecisionW (WININET.@) | |
| * | |
| * Enumerates all per-site decisions about cookies. | |
| * | |
| * RETURNS | |
| * TRUE on success | |
| * FALSE on failure | |
| * | |
| */ | |
| BOOL WINAPI InternetEnumPerSiteCookieDecisionW( LPWSTR pszSiteName, ULONG *pcSiteNameSize, | |
| ULONG *pdwDecision, ULONG dwIndex ) | |
| { | |
| FIXME("(%s, %p, %p, 0x%08x) stub\n", | |
| debugstr_w(pszSiteName), pcSiteNameSize, pdwDecision, dwIndex); | |
| return FALSE; | |
| } | |
| /*********************************************************************** | |
| * InternetGetPerSiteCookieDecisionA (WININET.@) | |
| */ | |
| BOOL WINAPI InternetGetPerSiteCookieDecisionA( LPCSTR pwchHostName, ULONG *pResult ) | |
| { | |
| FIXME("(%s, %p) stub\n", debugstr_a(pwchHostName), pResult); | |
| return FALSE; | |
| } | |
| /*********************************************************************** | |
| * InternetGetPerSiteCookieDecisionW (WININET.@) | |
| */ | |
| BOOL WINAPI InternetGetPerSiteCookieDecisionW( LPCWSTR pwchHostName, ULONG *pResult ) | |
| { | |
| FIXME("(%s, %p) stub\n", debugstr_w(pwchHostName), pResult); | |
| return FALSE; | |
| } | |
| /*********************************************************************** | |
| * InternetSetPerSiteCookieDecisionA (WININET.@) | |
| */ | |
| BOOL WINAPI InternetSetPerSiteCookieDecisionA( LPCSTR pchHostName, DWORD dwDecision ) | |
| { | |
| FIXME("(%s, 0x%08x) stub\n", debugstr_a(pchHostName), dwDecision); | |
| return FALSE; | |
| } | |
| /*********************************************************************** | |
| * InternetSetPerSiteCookieDecisionW (WININET.@) | |
| */ | |
| BOOL WINAPI InternetSetPerSiteCookieDecisionW( LPCWSTR pchHostName, DWORD dwDecision ) | |
| { | |
| FIXME("(%s, 0x%08x) stub\n", debugstr_w(pchHostName), dwDecision); | |
| return FALSE; | |
| } | |
| void free_cookie(void) | |
| { | |
| EnterCriticalSection(&cookie_cs); | |
| free_cookie_domain_list(&domain_list); | |
| LeaveCriticalSection(&cookie_cs); | |
| } |