/* * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include "windef.h" #include "winbase.h" #include "wininet.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 are currently memory only. * Cookies are NOT THREAD SAFE * Cookies could use ALOT OF MEMORY. We need some kind of memory management here! * Cookies should care about the expiry time */ typedef struct _cookie_domain cookie_domain; typedef struct _cookie cookie; struct _cookie { struct _cookie *next; struct _cookie *prev; struct _cookie_domain *parent; LPSTR lpCookieName; LPSTR lpCookieData; time_t expiry; /* FIXME: not used */ }; struct _cookie_domain { struct _cookie_domain *next; struct _cookie_domain *prev; LPSTR lpCookieDomain; LPSTR lpCookiePath; cookie *cookie_tail; }; static cookie_domain *cookieDomainTail; static cookie *COOKIE_addCookie(cookie_domain *domain, LPCSTR name, LPCSTR data); static cookie *COOKIE_findCookie(cookie_domain *domain, LPCSTR lpszCookieName); static void COOKIE_deleteCookie(cookie *deadCookie, BOOL deleteDomain); static cookie_domain *COOKIE_addDomain(LPCSTR domain, LPCSTR path); static cookie_domain *COOKIE_addDomainFromUrl(LPCSTR lpszUrl); static cookie_domain *COOKIE_findNextDomain(LPCSTR lpszCookieDomain, LPCSTR lpszCookiePath, cookie_domain *prev_domain, BOOL allow_partial); static cookie_domain *COOKIE_findNextDomainFromUrl(LPCSTR lpszUrl, cookie_domain *prev_domain, BOOL allow_partial); static void COOKIE_deleteDomain(cookie_domain *deadDomain); /* adds a cookie to the domain */ static cookie *COOKIE_addCookie(cookie_domain *domain, LPCSTR name, LPCSTR data) { cookie *newCookie = HeapAlloc(GetProcessHeap(), 0, sizeof(cookie)); newCookie->next = NULL; newCookie->prev = NULL; newCookie->lpCookieName = NULL; newCookie->lpCookieData = NULL; if (name) { newCookie->lpCookieName = HeapAlloc(GetProcessHeap(), 0, strlen(name) + 1); strcpy(newCookie->lpCookieName, name); } if (data) { newCookie->lpCookieData = HeapAlloc(GetProcessHeap(), 0, strlen(data) + 1); strcpy(newCookie->lpCookieData, data); } TRACE("added cookie %p (data is %s)\n", newCookie, data); newCookie->prev = domain->cookie_tail; newCookie->parent = domain; domain->cookie_tail = newCookie; return newCookie; } /* finds a cookie in the domain matching the cookie name */ static cookie *COOKIE_findCookie(cookie_domain *domain, LPCSTR lpszCookieName) { cookie *searchCookie = domain->cookie_tail; TRACE("(%p, %s)\n", domain, debugstr_a(lpszCookieName)); while (searchCookie) { BOOL candidate = TRUE; if (candidate && lpszCookieName) { if (candidate && !searchCookie->lpCookieName) candidate = FALSE; if (candidate && strcmp(lpszCookieName, searchCookie->lpCookieName) != 0) candidate = FALSE; } if (candidate) return searchCookie; searchCookie = searchCookie->prev; } return NULL; } /* removes a cookie from the list, if its the last cookie we also remove the domain */ static void COOKIE_deleteCookie(cookie *deadCookie, BOOL deleteDomain) { if (deadCookie->lpCookieName) HeapFree(GetProcessHeap(), 0, deadCookie->lpCookieName); if (deadCookie->lpCookieData) HeapFree(GetProcessHeap(), 0, deadCookie->lpCookieData); if (deadCookie->prev) deadCookie->prev->next = deadCookie->next; if (deadCookie->next) deadCookie->next->prev = deadCookie->prev; if (deadCookie == deadCookie->parent->cookie_tail) { /* special case: last cookie, lets remove the domain to save memory */ deadCookie->parent->cookie_tail = deadCookie->prev; if (!deadCookie->parent->cookie_tail && deleteDomain) COOKIE_deleteDomain(deadCookie->parent); } } /* allocates a domain and adds it to the end */ static cookie_domain *COOKIE_addDomain(LPCSTR domain, LPCSTR path) { cookie_domain *newDomain = HeapAlloc(GetProcessHeap(), 0, sizeof(cookie_domain)); newDomain->next = NULL; newDomain->prev = NULL; newDomain->cookie_tail = NULL; newDomain->lpCookieDomain = NULL; newDomain->lpCookiePath = NULL; if (domain) { newDomain->lpCookieDomain = HeapAlloc(GetProcessHeap(), 0, strlen(domain) + 1); strcpy(newDomain->lpCookieDomain, domain); } if (path) { newDomain->lpCookiePath = HeapAlloc(GetProcessHeap(), 0, strlen(path) + 1); strcpy(newDomain->lpCookiePath, path); } newDomain->prev = cookieDomainTail; cookieDomainTail = newDomain; TRACE("Adding domain: %p\n", newDomain); return newDomain; } static cookie_domain *COOKIE_addDomainFromUrl(LPCSTR lpszUrl) { char hostName[2048], path[2048]; URL_COMPONENTSA UrlComponents; UrlComponents.lpszExtraInfo = NULL; UrlComponents.lpszPassword = NULL; UrlComponents.lpszScheme = NULL; UrlComponents.lpszUrlPath = path; UrlComponents.lpszUserName = NULL; UrlComponents.lpszHostName = hostName; UrlComponents.dwHostNameLength = 2048; UrlComponents.dwUrlPathLength = 2048; InternetCrackUrlA(lpszUrl, 0, 0, &UrlComponents); TRACE("Url cracked. Domain: %s, Path: %s.\n", debugstr_a(UrlComponents.lpszHostName), debugstr_a(UrlComponents.lpszUrlPath)); /* hack for now - FIXME - There seems to be a bug in InternetCrackUrl?? */ UrlComponents.lpszUrlPath = NULL; return COOKIE_addDomain(UrlComponents.lpszHostName, UrlComponents.lpszUrlPath); } /* find a domain. domain must match if its not NULL. path must match if its not NULL */ static cookie_domain *COOKIE_findNextDomain(LPCSTR lpszCookieDomain, LPCSTR lpszCookiePath, cookie_domain *prev_domain, BOOL allow_partial) { cookie_domain *searchDomain; if (prev_domain) { if(!prev_domain->prev) { TRACE("no more domains available, it would seem.\n"); return NULL; } searchDomain = prev_domain->prev; } else searchDomain = cookieDomainTail; while (searchDomain) { BOOL candidate = TRUE; TRACE("searching on domain %p\n", searchDomain); if (candidate && lpszCookieDomain) { if (candidate && !searchDomain->lpCookieDomain) candidate = FALSE; TRACE("candidate! (%p)\n", searchDomain->lpCookieDomain); TRACE("comparing domain %s with %s\n", lpszCookieDomain, searchDomain->lpCookieDomain); if (candidate && allow_partial && !strstr(lpszCookieDomain, searchDomain->lpCookieDomain)) candidate = FALSE; else if (candidate && !allow_partial && strcmp(lpszCookieDomain, searchDomain->lpCookieDomain) != 0) candidate = FALSE; } if (candidate && lpszCookiePath) { TRACE("comparing paths\n"); if (candidate && !searchDomain->lpCookiePath) candidate = FALSE; if (candidate && strcmp(lpszCookiePath, searchDomain->lpCookiePath) != 0) candidate = FALSE; } if (candidate) { TRACE("returning the domain %p\n", searchDomain); return searchDomain; } searchDomain = searchDomain->prev; } TRACE("found no domain, returning NULL\n"); return NULL; } static cookie_domain *COOKIE_findNextDomainFromUrl(LPCSTR lpszUrl, cookie_domain *previous_domain, BOOL allow_partial) { char hostName[2048], path[2048]; URL_COMPONENTSA UrlComponents; UrlComponents.lpszExtraInfo = NULL; UrlComponents.lpszPassword = NULL; UrlComponents.lpszScheme = NULL; UrlComponents.lpszUrlPath = path; UrlComponents.lpszUserName = NULL; UrlComponents.lpszHostName = hostName; UrlComponents.dwHostNameLength = 2048; UrlComponents.dwUrlPathLength = 2048; InternetCrackUrlA(lpszUrl, 0, 0, &UrlComponents); TRACE("Url cracked. Domain: %s, Path: %s.\n", debugstr_a(UrlComponents.lpszHostName), debugstr_a(UrlComponents.lpszUrlPath)); /* hack for now - FIXME - There seems to be a bug in InternetCrackUrl?? */ UrlComponents.lpszUrlPath = NULL; return COOKIE_findNextDomain(UrlComponents.lpszHostName, UrlComponents.lpszUrlPath, previous_domain, allow_partial); } /* remove a domain from the list and delete it */ static void COOKIE_deleteDomain(cookie_domain *deadDomain) { while (deadDomain->cookie_tail) COOKIE_deleteCookie(deadDomain->cookie_tail, FALSE); if (deadDomain->lpCookieDomain) HeapFree(GetProcessHeap(), 0, deadDomain->lpCookieDomain); if (deadDomain->lpCookiePath) HeapFree(GetProcessHeap(), 0, deadDomain->lpCookiePath); if (deadDomain->prev) deadDomain->prev->next = deadDomain->next; if (deadDomain->next) deadDomain->next->prev = deadDomain->prev; if (cookieDomainTail == deadDomain) cookieDomainTail = deadDomain->prev; HeapFree(GetProcessHeap(), 0, deadDomain); } /*********************************************************************** * InternetGetCookieA (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 InternetGetCookieA(LPCSTR lpszUrl, LPCSTR lpszCookieName, LPSTR lpCookieData, LPDWORD lpdwSize) { cookie_domain *cookiesDomain = NULL; cookie *thisCookie; int cnt = 0, domain_count = 0; /* Ok, this is just ODD!. During my tests, it appears M$ like to send out * a cookie called 'MtrxTracking' to some urls. Also returns it from InternetGetCookie. * I'm not exactly sure what to make of this, so its here for now. * It'd be nice to know what exactly is going on, M$ tracking users? Does this need * to be unique? Should I generate a random number here? etc. */ char *TrackingString = "MtrxTrackingID=01234567890123456789012345678901"; TRACE("(%s, %s, %p, %p)\n", debugstr_a(lpszUrl),debugstr_a(lpszCookieName), lpCookieData, lpdwSize); if (lpCookieData) cnt += snprintf(lpCookieData + cnt, *lpdwSize - cnt, "%s", TrackingString); else cnt += strlen(TrackingString); while ((cookiesDomain = COOKIE_findNextDomainFromUrl(lpszUrl, cookiesDomain, TRUE))) { domain_count++; TRACE("found domain %p\n", cookiesDomain); thisCookie = cookiesDomain->cookie_tail; if (lpCookieData == NULL) /* return the size of the buffer required to lpdwSize */ { while (thisCookie) { cnt += 2; /* '; ' */ cnt += strlen(thisCookie->lpCookieName); cnt += 1; /* = */ cnt += strlen(thisCookie->lpCookieData); thisCookie = thisCookie->prev; } } while (thisCookie) { cnt += snprintf(lpCookieData + cnt, *lpdwSize - cnt, "; "); cnt += snprintf(lpCookieData + cnt, *lpdwSize - cnt, "%s=%s", thisCookie->lpCookieName, thisCookie->lpCookieData); thisCookie = thisCookie->prev; } } if (lpCookieData == NULL) { cnt += 1; /* NULL */ *lpdwSize = cnt; TRACE("returning\n"); return TRUE; } if (!domain_count) return FALSE; *lpdwSize = cnt + 1; TRACE("Returning %i (from %i domains): %s\n", cnt, domain_count, lpCookieData); return (cnt ? TRUE : FALSE); } /*********************************************************************** * InternetGetCookieW (WININET.@) * * Retrieve cookie from the specified url * * RETURNS * TRUE on success * FALSE on failure * */ BOOL WINAPI InternetGetCookieW(LPCSTR lpszUrl, LPCWSTR lpszCookieName, LPWSTR lpCookieData, LPDWORD lpdwSize) { FIXME("STUB\n"); TRACE("(%s,%s,%p)\n", debugstr_a(lpszUrl), debugstr_w(lpszCookieName), lpCookieData); return FALSE; } /*********************************************************************** * InternetSetCookieA (WININET.@) * * Sets cookie for the specified url * * RETURNS * TRUE on success * FALSE on failure * */ BOOL WINAPI InternetSetCookieA(LPCSTR lpszUrl, LPCSTR lpszCookieName, LPCSTR lpCookieData) { cookie *thisCookie; cookie_domain *thisCookieDomain; TRACE("(%s,%s,%s)\n", debugstr_a(lpszUrl), debugstr_a(lpszCookieName), lpCookieData); if (!lpCookieData || !strlen(lpCookieData)) { TRACE("no cookie data, not adding\n"); return FALSE; } if (!lpszCookieName) { /* 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. */ /* FIXME, probably a bug here, for now I don't care */ char *ourCookieName, *ourCookieData; int ourCookieNameSize; BOOL ret; if (!(ourCookieData = strchr(lpCookieData, '='))) { TRACE("something terribly wrong with cookie data %s\n", ourCookieData); return FALSE; } ourCookieNameSize = ourCookieData - lpCookieData; ourCookieData += 1; ourCookieName = HeapAlloc(GetProcessHeap(), 0, ourCookieNameSize + 1); strncpy(ourCookieName, ourCookieData, ourCookieNameSize); ourCookieName[ourCookieNameSize] = '\0'; TRACE("setting (hacked) cookie of %s, %s\n", ourCookieName, ourCookieData); ret = InternetSetCookieA(lpszUrl, ourCookieName, ourCookieData); HeapFree(GetProcessHeap(), 0, ourCookieName); return ret; } if (!(thisCookieDomain = COOKIE_findNextDomainFromUrl(lpszUrl, NULL, FALSE))) thisCookieDomain = COOKIE_addDomainFromUrl(lpszUrl); if ((thisCookie = COOKIE_findCookie(thisCookieDomain, lpszCookieName))) COOKIE_deleteCookie(thisCookie, FALSE); thisCookie = COOKIE_addCookie(thisCookieDomain, lpszCookieName, lpCookieData); return TRUE; } /*********************************************************************** * InternetSetCookieW (WININET.@) * * Sets cookie for the specified url * * RETURNS * TRUE on success * FALSE on failure * */ BOOL WINAPI InternetSetCookieW(LPCSTR lpszUrl, LPCWSTR lpszCookieName, LPCWSTR lpCookieData) { FIXME("STUB\n"); TRACE("(%s,%s,%s)\n", debugstr_a(lpszUrl), debugstr_w(lpszCookieName), debugstr_w(lpCookieData)); return FALSE; }